From 177f14ecb4a30f3cbd811c3169bc82cc39bbf308 Mon Sep 17 00:00:00 2001 From: Adam Urpsis Date: Fri, 11 Sep 2026 12:47:46 -0600 Subject: [PATCH] feat: raise the sync config caps to match the service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sync API raised its `config` caps, so the SDK was rejecting values the server now accepts: prompt 4096 → 6000, keyterms_prompt 2048 → 8000, and conversation_context 100 → 500 turns / 4096 → 16000 characters. Also enforce the server's 100-term cap on keyterms_prompt, which had no client-side check. Mirrors the Python SDK's 1.5.2. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 4 ++++ README.md | 4 ++-- package.json | 2 +- src/services/sync/service.ts | 17 +++++++++++++---- tests/unit/sync.test.ts | 21 +++++++++++++++------ 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 492d98c..b5cbda2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [4.39.1] + +- Raise the `client.sync` config caps to match the service (the API raised them; the SDK was rejecting values the server now accepts): `prompt` 4096 → 6000 characters, `keyterms_prompt` 2048 → 8000 characters, and `conversation_context` 100 → 500 turns / 4096 → 16 000 characters. `keyterms_prompt` also gains the server's 100-term cap, which the SDK did not enforce + ## [4.39.0] - Add `client.sync.transcribeLive(audio, config?, options?)` — sync transcription that uploads audio while it is still being recorded, from an async iterable (Node streams included), a sync iterable, or a web `ReadableStream`. The request starts before the audio exists, so authorization, the upload and every speech segment but the last resolve while the caller is still recording. Audio already held whole (bytes, a Blob, a path) is rejected by name — it belongs in `transcribe()`, which is faster for it diff --git a/README.md b/README.md index c4c096e..bc3cd4b 100644 --- a/README.md +++ b/README.md @@ -332,8 +332,8 @@ stream — but not a URL. ```typescript const result = await client.sync.transcribe("./call.wav", { - prompt: "Transcribe verbatim. Preserve disfluencies.", // max 4096 chars - keyterms_prompt: ["AssemblyAI", "Lemur"], // max 2048 chars total + prompt: "Transcribe verbatim. Preserve disfluencies.", // max 6000 chars + keyterms_prompt: ["AssemblyAI", "Lemur"], // max 100 terms / 8000 chars total language_codes: ["es"], // or e.g. ["en", "es"] for multilingual; defaults to English conversation_context: [ // prior turns, oldest first diff --git a/package.json b/package.json index 16e9d7a..fa396c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "assemblyai", - "version": "4.39.0", + "version": "4.39.1", "description": "The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription.", "engines": { "node": ">=18" diff --git a/src/services/sync/service.ts b/src/services/sync/service.ts index 5fe4bb5..7c9cda4 100644 --- a/src/services/sync/service.ts +++ b/src/services/sync/service.ts @@ -34,10 +34,14 @@ const defaultTimeoutMs = 60_000; // default must clear the 120 s audio cap plus the final segment. const defaultLiveTimeoutMs = 180_000; const warmTimeoutMs = 10_000; -const maxPromptLength = 4096; -const maxKeytermsPromptLength = 2048; -const maxContextTurns = 100; -const maxContextLength = 4096; +// Caps mirror the sync service's `config` part. `prompt` and +// `keyterms_prompt` over their caps are rejected; `conversation_context` over +// its caps is trimmed, oldest turns first. +const maxPromptLength = 6000; +const maxKeytermsPromptLength = 8000; +const maxKeytermsCount = 100; +const maxContextTurns = 500; +const maxContextLength = 16000; // Extensions that signal raw S16LE PCM rather than a WAV container. const pcmSuffixes = [".pcm", ".raw"]; @@ -434,6 +438,11 @@ function normalizeKeytermsPrompt( const terms = keytermsPrompt .map((term) => term.trim()) .filter((term) => term.length > 0); + if (terms.length > maxKeytermsCount) { + throw new Error( + `keyterms_prompt exceeds ${maxKeytermsCount} terms (got ${terms.length})`, + ); + } const total = terms.reduce((sum, term) => sum + term.length, 0); if (total > maxKeytermsPromptLength) { throw new Error( diff --git a/tests/unit/sync.test.ts b/tests/unit/sync.test.ts index 534665d..d68ad93 100644 --- a/tests/unit/sync.test.ts +++ b/tests/unit/sync.test.ts @@ -146,15 +146,15 @@ describe("sync", () => { it("should trim the oldest conversation turns over the char cap", async () => { mockOk(); await assembly.sync.transcribe(fakeWavBytes, { - conversation_context: ["a".repeat(3000), "b".repeat(3000)], + conversation_context: ["a".repeat(10000), "b".repeat(10000)], }); const config = await configPart(); - expect(config?.conversation_context).toEqual(["b".repeat(3000)]); + expect(config?.conversation_context).toEqual(["b".repeat(10000)]); }); it("should trim the oldest conversation turns over the turn cap", async () => { mockOk(); - const turns = Array.from({ length: 120 }, (_, i) => `turn ${i}`); + const turns = Array.from({ length: 520 }, (_, i) => `turn ${i}`); await assembly.sync.transcribe(fakeWavBytes, { conversation_context: turns, }); @@ -165,7 +165,7 @@ describe("sync", () => { it("should trim to nothing when a single turn is over the char cap", async () => { mockOk(); await assembly.sync.transcribe(fakeWavBytes, { - conversation_context: ["a".repeat(5000)], + conversation_context: ["a".repeat(20000)], }); expect(requestBody().get("config")).toBeNull(); }); @@ -294,15 +294,24 @@ describe("sync", () => { it("should reject an oversized keyterms_prompt", async () => { await expect( assembly.sync.transcribe(fakeWavBytes, { - keyterms_prompt: ["x".repeat(3000)], + keyterms_prompt: ["x".repeat(9000)], }), ).rejects.toThrow("keyterms_prompt exceeds"); expect(fetchMock).not.toHaveBeenCalled(); }); + it("should reject a keyterms_prompt over the term count", async () => { + await expect( + assembly.sync.transcribe(fakeWavBytes, { + keyterms_prompt: Array.from({ length: 101 }, (_, i) => `term ${i}`), + }), + ).rejects.toThrow("keyterms_prompt exceeds 100 terms"); + expect(fetchMock).not.toHaveBeenCalled(); + }); + it("should reject an oversized prompt", async () => { await expect( - assembly.sync.transcribe(fakeWavBytes, { prompt: "x".repeat(5000) }), + assembly.sync.transcribe(fakeWavBytes, { prompt: "x".repeat(7000) }), ).rejects.toThrow("prompt exceeds"); expect(fetchMock).not.toHaveBeenCalled(); });