Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
17 changes: 13 additions & 4 deletions src/services/sync/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"];

Expand Down Expand Up @@ -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(
Expand Down
21 changes: 15 additions & 6 deletions tests/unit/sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand All @@ -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();
});
Expand Down Expand Up @@ -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();
});
Expand Down
Loading