diff --git a/CHANGELOG.md b/CHANGELOG.md index c679e77..7a2706a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [4.35.3] + +- Allow `language_codes` in `updateConfiguration()` — re-steer the transcription language mid-stream without reconnecting; pass `[]` to clear steering and restore the model's default multilingual code-switching (Universal-3.5 Pro Streaming only) + +## [4.35.2] + +- Add `opus` and `ogg_opus` streaming `encoding` values — accepts compressed Opus audio (raw packet-per-message or Ogg-encapsulated). `sampleRate` is ignored for both; the Opus stream is self-describing + +## [4.35.1] + +- Add `languageCodes` streaming parameter — steers transcription toward a set of languages (Universal-3.5 Pro Streaming only). Recommended replacement for `languageCode`, which is now deprecated but still supported + ## [4.34.6] - Add `keepAlive()` method to `StreamingTranscriber` — sends a `KeepAlive` message to reset the server's inactivity timer when `inactivityTimeout` is configured diff --git a/README.md b/README.md index c80f89a..b02be9c 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ const audioFile = "https://assembly.ai/sports_injuries.mp3"; const params = { audio: audioFile, - speech_models: ["universal-3-pro", "universal-2"], + speech_models: ["universal-3-5-pro", "universal-2"], language_detection: true, }; @@ -151,7 +151,7 @@ If you don't want to wait until the transcript is ready, you can use `submit`: ```js let transcript = await client.transcripts.submit({ audio: "https://assembly.ai/espn.m4a", - speech_models: ["universal-3-pro", "universal-2"], + speech_models: ["universal-3-5-pro", "universal-2"], language_detection: true, }); ``` @@ -167,7 +167,7 @@ When you create a transcript, you can either pass in a URL to an audio file or u // Upload a file via local path and transcribe let transcript = await client.transcripts.transcribe({ audio: "./news.mp4", - speech_models: ["universal-3-pro", "universal-2"], + speech_models: ["universal-3-5-pro", "universal-2"], language_detection: true, }); ``` @@ -182,7 +182,7 @@ If you don't want to wait until the transcript is ready, you can use `submit`: ```js let transcript = await client.transcripts.submit({ audio: "./news.mp4", - speech_models: ["universal-3-pro", "universal-2"], + speech_models: ["universal-3-5-pro", "universal-2"], language_detection: true, }); ``` @@ -206,7 +206,7 @@ const audioFile = "https://assembly.ai/wildfires.mp3"; const params = { audio: audioFile, - speech_models: ["universal-3-pro", "universal-2"], + speech_models: ["universal-3-5-pro", "universal-2"], language_detection: true, speaker_labels: true, }; @@ -321,7 +321,6 @@ Create the streaming transcriber. const transcriber = client.streaming.transcriber({ speechModel: "universal-3-5-pro", sampleRate: 16_000, - formatTurns: true, }); ``` diff --git a/package.json b/package.json index 1965353..22429d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "assemblyai", - "version": "4.35.0", + "version": "4.35.3", "description": "The AssemblyAI JavaScript SDK provides an easy-to-use interface for interacting with the AssemblyAI API, which supports async and real-time transcription, as well as the latest LeMUR models.", "engines": { "node": ">=18" diff --git a/src/exports/streaming.ts b/src/exports/streaming.ts index d0db881..a595f7a 100644 --- a/src/exports/streaming.ts +++ b/src/exports/streaming.ts @@ -1,7 +1,7 @@ export * from "../types/asyncapi.generated"; export * from "../types/realtime"; export * from "../types/helpers"; -export * from "../types/streaming/dual-channel"; +export * from "../types/streaming"; export * from "../services/realtime/service"; export * from "../services/streaming/service"; export * from "../services/streaming/factory"; diff --git a/src/services/streaming/service.ts b/src/services/streaming/service.ts index 2fa74c3..87a1ddb 100644 --- a/src/services/streaming/service.ts +++ b/src/services/streaming/service.ts @@ -313,9 +313,19 @@ export class StreamingTranscriber { } if (this.params.languageCode !== undefined) { + console.warn( + "[Deprecation Warning] `languageCode` is deprecated and will be removed in a future release. Please use `languageCodes` instead.", + ); searchParams.set("language_code", this.params.languageCode); } + if (this.params.languageCodes !== undefined) { + searchParams.set( + "language_codes", + JSON.stringify(this.params.languageCodes), + ); + } + if (this.params.languageDetection !== undefined) { searchParams.set( "language_detection", diff --git a/src/types/asyncapi.generated.ts b/src/types/asyncapi.generated.ts index da8197a..5b70c8a 100644 --- a/src/types/asyncapi.generated.ts +++ b/src/types/asyncapi.generated.ts @@ -25,7 +25,7 @@ export type AudioData = ArrayBufferLike; * The encoding of the audio data * @defaultValue "pcm_s16"le */ -export type AudioEncoding = "pcm_s16le" | "pcm_mulaw"; +export type AudioEncoding = "pcm_s16le" | "pcm_mulaw" | "opus" | "ogg_opus"; /** * Configure the threshold for how long to wait before ending an utterance. Default is 700ms. diff --git a/src/types/streaming/index.ts b/src/types/streaming/index.ts index 5354e7b..6a9a557 100644 --- a/src/types/streaming/index.ts +++ b/src/types/streaming/index.ts @@ -109,7 +109,19 @@ export type StreamingTranscriberParams = { prompt?: string; agentContext?: string; speechModel?: StreamingSpeechModel; + /** + * @deprecated Use `languageCodes` instead (pass a single-element array, e.g. `["es"]`, + * for the same behavior). Still supported for backward compatibility. + */ languageCode?: string; + /** + * Recommended way to select languages. Steers transcription toward a set of + * languages by biasing output toward them on a per-token basis while still + * allowing native code-switching among them. Pass the languages you expect + * (e.g. `["en", "es"]`), or a single-element array (e.g. `["es"]`) for a + * monolingual session. Universal-3.5 Pro Streaming only. + */ + languageCodes?: string[]; languageDetection?: boolean; domain?: StreamingDomain; inactivityTimeout?: number; @@ -359,6 +371,12 @@ export type StreamingUpdateConfiguration = { filter_profanity?: boolean; interruption_delay?: number; turn_left_pad_ms?: number; + /** + * Steer transcription toward a set of languages mid-stream. Pass an empty + * array (`[]`) to clear steering and restore the model's default + * multilingual code-switching. Universal-3.5 Pro Streaming only. + */ + language_codes?: string[]; }; export type StreamingForceEndpoint = { diff --git a/tests/unit/streaming.test.ts b/tests/unit/streaming.test.ts index 18cff66..af393d6 100644 --- a/tests/unit/streaming.test.ts +++ b/tests/unit/streaming.test.ts @@ -231,6 +231,26 @@ describe("streaming", () => { ); }); + it("should include language_codes in updateConfiguration message", async () => { + rt.updateConfiguration({ language_codes: ["en", "es"] }); + await expect(server).toReceiveMessage( + JSON.stringify({ + type: "UpdateConfiguration", + language_codes: ["en", "es"], + }), + ); + }); + + it("should send an empty language_codes array in updateConfiguration to clear steering", async () => { + rt.updateConfiguration({ language_codes: [] }); + await expect(server).toReceiveMessage( + JSON.stringify({ + type: "UpdateConfiguration", + language_codes: [], + }), + ); + }); + it("should send KeepAlive message on keepAlive()", async () => { rt.keepAlive(); await expect(server).toReceiveMessage( @@ -347,6 +367,47 @@ describe("streaming", () => { await connect(rt, server); }); + it("should include language_codes in connection URL", async () => { + await cleanup(); + WS.clean(); + + const languageCodes = ["en", "es"]; + const wsUrl = + `${websocketBaseUrl}?token=123&sample_rate=16000&speech_model=universal-3-5-pro` + + `&language_codes=${encodeURIComponent(JSON.stringify(languageCodes))}`; + server = new WS(wsUrl); + rt = new StreamingTranscriber({ + websocketBaseUrl, + token: "123", + sampleRate: 16_000, + speechModel: "universal-3-5-pro", + languageCodes: ["en", "es"], + }); + onOpen = jest.fn(); + rt.on("open", onOpen); + await connect(rt, server); + }); + + it.each(["opus", "ogg_opus"] as const)( + "should include %s encoding in connection URL", + async (encoding) => { + await cleanup(); + WS.clean(); + + const wsUrl = `${websocketBaseUrl}?token=123&sample_rate=16000&encoding=${encoding}`; + server = new WS(wsUrl); + rt = new StreamingTranscriber({ + websocketBaseUrl, + token: "123", + sampleRate: 16_000, + encoding, + }); + onOpen = jest.fn(); + rt.on("open", onOpen); + await connect(rt, server); + }, + ); + it("should include whisper-rt speech model in connection URL", async () => { await cleanup(); WS.clean();