@ai-sdk/cartesia 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @ai-sdk/cartesia
2
2
 
3
+ ## 3.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 529a5ef: Add Ink 2 streaming transcription through `TranscriptionModelV4.doStream` and the shared WebSocket transport.
8
+
9
+ ## 3.0.3
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [31c7be8]
14
+ - @ai-sdk/provider-utils@5.0.10
15
+
3
16
  ## 3.0.2
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # AI SDK - Cartesia Provider
2
2
 
3
3
  The **[Cartesia provider](https://ai-sdk.dev/providers/ai-sdk-providers/cartesia)** for the [AI SDK](https://ai-sdk.dev/docs)
4
- contains Sonic 3.5 speech generation, Ink-Whisper batch transcription, and Ink 2 realtime transcription support.
4
+ contains Sonic 3.5 speech generation, Ink-Whisper batch transcription, and Ink 2 realtime and streaming transcription support.
5
5
 
6
6
  > **Deploying to Vercel?** With Vercel's AI Gateway you can access Cartesia (and hundreds of models from other providers) — no additional packages, API keys, or extra cost. [Get started with AI Gateway](https://vercel.com/ai-gateway).
7
7
 
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as _ai_sdk_provider from '@ai-sdk/provider';
2
- import { SpeechModelV4, ProviderV4, TranscriptionModelV4, Experimental_RealtimeFactoryV4, Experimental_RealtimeModelV4, Experimental_RealtimeModelV4ClientSecretOptions, Experimental_RealtimeModelV4ClientSecretResult, Experimental_RealtimeModelV4ServerEvent, Experimental_RealtimeModelV4ClientEvent, Experimental_RealtimeModelV4SessionConfig } from '@ai-sdk/provider';
3
- import { FetchFunction, WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@ai-sdk/provider-utils';
2
+ import { SpeechModelV4, ProviderV4, TranscriptionModelV4, Experimental_RealtimeFactoryV4, Experimental_TranscriptionModelV4StreamOptions, Experimental_RealtimeModelV4, Experimental_RealtimeModelV4ClientSecretOptions, Experimental_RealtimeModelV4ClientSecretResult, Experimental_RealtimeModelV4ServerEvent, Experimental_RealtimeModelV4ClientEvent, Experimental_RealtimeModelV4SessionConfig } from '@ai-sdk/provider';
3
+ import { FetchFunction, WebSocketConstructor, WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE } from '@ai-sdk/provider-utils';
4
4
  import { z } from 'zod/v4';
5
5
 
6
- type CartesiaTranscriptionModelId = 'ink-whisper' | (string & {});
6
+ type CartesiaTranscriptionModelId = 'ink-whisper' | 'ink-2' | (string & {});
7
7
 
8
8
  type CartesiaConfig = {
9
9
  provider: string;
@@ -13,6 +13,8 @@ type CartesiaConfig = {
13
13
  }) => string;
14
14
  headers?: () => Record<string, string | undefined>;
15
15
  fetch?: FetchFunction;
16
+ version?: string;
17
+ webSocket?: WebSocketConstructor;
16
18
  generateId?: () => string;
17
19
  };
18
20
 
@@ -81,6 +83,10 @@ interface CartesiaProviderSettings {
81
83
  * or to provide a custom fetch implementation for e.g. testing.
82
84
  */
83
85
  fetch?: FetchFunction;
86
+ /**
87
+ * Custom WebSocket implementation for streaming transcription.
88
+ */
89
+ webSocket?: WebSocketConstructor;
84
90
  }
85
91
  /**
86
92
  * Create a Cartesia provider instance.
@@ -112,6 +118,8 @@ declare class CartesiaTranscriptionModel implements TranscriptionModelV4 {
112
118
  constructor(modelId: CartesiaTranscriptionModelId, config: CartesiaTranscriptionModelConfig);
113
119
  private getArgs;
114
120
  doGenerate(options: Parameters<TranscriptionModelV4['doGenerate']>[0]): Promise<Awaited<ReturnType<TranscriptionModelV4['doGenerate']>>>;
121
+ doStream(options: Experimental_TranscriptionModelV4StreamOptions): Promise<Awaited<ReturnType<NonNullable<TranscriptionModelV4['doStream']>>>>;
122
+ private createStreamingAccessToken;
115
123
  }
116
124
 
117
125
  type CartesiaRealtimeModelId = 'ink-2' | (string & {});
@@ -160,10 +168,10 @@ declare const cartesiaSpeechModelOptionsSchema: z.ZodObject<{
160
168
  mp3: "mp3";
161
169
  }>>>;
162
170
  encoding: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
163
- pcm_f32le: "pcm_f32le";
164
171
  pcm_s16le: "pcm_s16le";
165
172
  pcm_mulaw: "pcm_mulaw";
166
173
  pcm_alaw: "pcm_alaw";
174
+ pcm_f32le: "pcm_f32le";
167
175
  }>>>;
168
176
  sampleRate: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<8000>, z.ZodLiteral<16000>, z.ZodLiteral<22050>, z.ZodLiteral<24000>, z.ZodLiteral<44100>, z.ZodLiteral<48000>]>>>;
169
177
  bitRate: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<32000>, z.ZodLiteral<64000>, z.ZodLiteral<96000>, z.ZodLiteral<128000>, z.ZodLiteral<192000>]>>>;
@@ -177,6 +185,9 @@ declare const cartesiaTranscriptionModelOptionsSchema: z.ZodObject<{
177
185
  timestampGranularities: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodEnum<{
178
186
  word: "word";
179
187
  }>>>>;
188
+ streaming: z.ZodOptional<z.ZodObject<{
189
+ turnDetection: z.ZodOptional<z.ZodBoolean>;
190
+ }, z.core.$strip>>;
180
191
  }, z.core.$strip>;
181
192
  type CartesiaTranscriptionModelOptions = z.infer<typeof cartesiaTranscriptionModelOptionsSchema>;
182
193
 
package/dist/index.js CHANGED
@@ -8,14 +8,23 @@ import {
8
8
  } from "@ai-sdk/provider-utils";
9
9
 
10
10
  // src/cartesia-transcription-model.ts
11
+ import {
12
+ InvalidArgumentError,
13
+ UnsupportedFunctionalityError
14
+ } from "@ai-sdk/provider";
11
15
  import {
12
16
  combineHeaders,
17
+ connectToWebSocket,
13
18
  convertBase64ToUint8Array,
14
19
  createJsonResponseHandler,
15
20
  mediaTypeToExtension,
16
21
  parseProviderOptions,
17
22
  postFormDataToApi,
23
+ postJsonToApi,
24
+ safeParseJSON,
18
25
  serializeModelOptions,
26
+ toWebSocketUrl,
27
+ waitForWebSocketBufferDrain,
19
28
  WORKFLOW_SERIALIZE,
20
29
  WORKFLOW_DESERIALIZE
21
30
  } from "@ai-sdk/provider-utils";
@@ -42,7 +51,15 @@ var cartesiaTranscriptionModelOptionsSchema = z2.object({
42
51
  /** The language of the audio (ISO 639-1 code). Defaults to English. */
43
52
  language: z2.string().nullish(),
44
53
  /** The timestamp granularities to populate. Currently only `word` is supported. */
45
- timestampGranularities: z2.array(z2.enum(["word"])).nullish()
54
+ timestampGranularities: z2.array(z2.enum(["word"])).nullish(),
55
+ /** Options for realtime Ink 2 transcription over WebSocket. */
56
+ streaming: z2.object({
57
+ /**
58
+ * Use Cartesia's native turn detection endpoint. Defaults to true.
59
+ * Disable this to finalize the transcript only when the audio stream ends.
60
+ */
61
+ turnDetection: z2.boolean().optional()
62
+ }).optional()
46
63
  });
47
64
 
48
65
  // src/cartesia-transcription-model.ts
@@ -75,6 +92,13 @@ var CartesiaTranscriptionModel = class _CartesiaTranscriptionModel {
75
92
  providerOptions,
76
93
  schema: cartesiaTranscriptionModelOptionsSchema
77
94
  });
95
+ if ((cartesiaOptions == null ? void 0 : cartesiaOptions.streaming) != null) {
96
+ warnings.push({
97
+ type: "unsupported",
98
+ feature: "providerOptions.cartesia.streaming",
99
+ details: "Cartesia batch transcription does not support streaming options."
100
+ });
101
+ }
78
102
  const formData = new FormData();
79
103
  const blob = audio instanceof Uint8Array ? new Blob([audio]) : new Blob([convertBase64ToUint8Array(audio)]);
80
104
  const fileExtension = mediaTypeToExtension(mediaType);
@@ -101,6 +125,11 @@ var CartesiaTranscriptionModel = class _CartesiaTranscriptionModel {
101
125
  }
102
126
  async doGenerate(options) {
103
127
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
128
+ if (isStreamingTranscriptionModelId(this.modelId)) {
129
+ throw new UnsupportedFunctionalityError({
130
+ functionality: `non-streaming transcription with ${this.modelId}`
131
+ });
132
+ }
104
133
  const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
105
134
  const { formData, warnings } = await this.getArgs(options);
106
135
  const {
@@ -139,7 +168,287 @@ var CartesiaTranscriptionModel = class _CartesiaTranscriptionModel {
139
168
  }
140
169
  };
141
170
  }
171
+ async doStream(options) {
172
+ var _a, _b, _c, _d, _e, _f;
173
+ if (!isStreamingTranscriptionModelId(this.modelId)) {
174
+ throw new UnsupportedFunctionalityError({
175
+ functionality: `streaming transcription with ${this.modelId}`
176
+ });
177
+ }
178
+ const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
179
+ const warnings = [];
180
+ const cartesiaOptions = await parseProviderOptions({
181
+ provider: "cartesia",
182
+ providerOptions: options.providerOptions,
183
+ schema: cartesiaTranscriptionModelOptionsSchema
184
+ });
185
+ if ((cartesiaOptions == null ? void 0 : cartesiaOptions.language) != null && cartesiaOptions.language !== "en") {
186
+ throw new InvalidArgumentError({
187
+ argument: "providerOptions",
188
+ message: "Cartesia Ink 2 currently supports English only."
189
+ });
190
+ }
191
+ if ((cartesiaOptions == null ? void 0 : cartesiaOptions.timestampGranularities) != null) {
192
+ warnings.push({
193
+ type: "unsupported",
194
+ feature: "providerOptions.cartesia.timestampGranularities",
195
+ details: "Cartesia streaming transcription does not support timestamp granularities."
196
+ });
197
+ }
198
+ const token = await this.createStreamingAccessToken(options);
199
+ const useTurnDetection = ((_d = cartesiaOptions == null ? void 0 : cartesiaOptions.streaming) == null ? void 0 : _d.turnDetection) !== false;
200
+ const url = buildCartesiaStreamingTranscriptionUrl({
201
+ baseURL: this.config.url({ path: "/", modelId: this.modelId }),
202
+ version: (_e = this.config.version) != null ? _e : "2026-03-01",
203
+ modelId: this.modelId,
204
+ inputAudioFormat: options.inputAudioFormat,
205
+ providerOptions: cartesiaOptions,
206
+ token,
207
+ useTurnDetection
208
+ });
209
+ const requestUrl = new URL(url);
210
+ requestUrl.searchParams.delete("access_token");
211
+ return {
212
+ request: { body: requestUrl.toString() },
213
+ response: {
214
+ timestamp: currentDate,
215
+ modelId: this.modelId
216
+ },
217
+ stream: createCartesiaStreamingTranscriptionStream({
218
+ webSocket: this.config.webSocket,
219
+ url,
220
+ warnings,
221
+ language: (_f = cartesiaOptions == null ? void 0 : cartesiaOptions.language) != null ? _f : "en",
222
+ useTurnDetection,
223
+ audio: options.audio,
224
+ abortSignal: options.abortSignal,
225
+ includeRawChunks: options.includeRawChunks
226
+ })
227
+ };
228
+ }
229
+ async createStreamingAccessToken(options) {
230
+ var _a, _b;
231
+ const { value } = await postJsonToApi({
232
+ url: this.config.url({ path: "/access-token", modelId: this.modelId }),
233
+ headers: combineHeaders((_b = (_a = this.config).headers) == null ? void 0 : _b.call(_a), options.headers),
234
+ body: { grants: { stt: true } },
235
+ failedResponseHandler: cartesiaFailedResponseHandler,
236
+ successfulResponseHandler: createJsonResponseHandler(
237
+ cartesiaAccessTokenResponseSchema
238
+ ),
239
+ abortSignal: options.abortSignal,
240
+ fetch: this.config.fetch
241
+ });
242
+ return value.token;
243
+ }
142
244
  };
245
+ function createCartesiaStreamingTranscriptionStream({
246
+ webSocket,
247
+ url,
248
+ warnings,
249
+ language,
250
+ useTurnDetection,
251
+ audio,
252
+ abortSignal,
253
+ includeRawChunks
254
+ }) {
255
+ let finished = false;
256
+ let cleanup = () => {
257
+ };
258
+ return new ReadableStream({
259
+ start: (controller) => {
260
+ const finalTexts = [];
261
+ let durationInSeconds = 0;
262
+ let audioReader;
263
+ let connection;
264
+ cleanup = (closeCode) => {
265
+ if (audioReader != null) {
266
+ void audioReader.cancel().catch(() => {
267
+ });
268
+ } else {
269
+ void audio.cancel().catch(() => {
270
+ });
271
+ }
272
+ connection == null ? void 0 : connection.close(closeCode);
273
+ };
274
+ const finishWithError = (error) => {
275
+ if (finished) return;
276
+ finished = true;
277
+ cleanup();
278
+ controller.error(error);
279
+ };
280
+ const finish = () => {
281
+ if (finished) return;
282
+ finished = true;
283
+ controller.enqueue({
284
+ type: "finish",
285
+ text: finalTexts.join(useTurnDetection ? " " : ""),
286
+ segments: [],
287
+ language,
288
+ ...durationInSeconds > 0 ? { durationInSeconds } : {}
289
+ });
290
+ controller.close();
291
+ cleanup(1e3);
292
+ };
293
+ const sendAudio = async (socket) => {
294
+ audioReader = audio.getReader();
295
+ try {
296
+ while (true) {
297
+ const { done, value } = await audioReader.read();
298
+ if (done || finished) break;
299
+ socket.send(
300
+ value instanceof Uint8Array ? value : convertBase64ToUint8Array(value)
301
+ );
302
+ await waitForWebSocketBufferDrain(socket);
303
+ }
304
+ } finally {
305
+ audioReader.releaseLock();
306
+ audioReader = void 0;
307
+ }
308
+ if (!finished) {
309
+ socket.send(
310
+ useTurnDetection ? JSON.stringify({ type: "close" }) : "finalize"
311
+ );
312
+ }
313
+ };
314
+ connection = connectToWebSocket({
315
+ url,
316
+ webSocket,
317
+ abortSignal,
318
+ onAbort: finishWithError,
319
+ onProcessingError: finishWithError,
320
+ onOpen: (socket) => {
321
+ controller.enqueue({ type: "stream-start", warnings });
322
+ void sendAudio(socket).catch(finishWithError);
323
+ },
324
+ onMessageText: async (text) => {
325
+ var _a, _b, _c, _d, _e, _f, _g;
326
+ const parsed = await safeParseJSON({ text });
327
+ if (!parsed.success) return;
328
+ const raw = parsed.value;
329
+ if (includeRawChunks) {
330
+ controller.enqueue({ type: "raw", rawValue: raw });
331
+ }
332
+ switch (raw.type) {
333
+ case "turn.update":
334
+ case "turn.eager_end": {
335
+ controller.enqueue({
336
+ type: "transcript-partial",
337
+ id: raw.request_id,
338
+ text: (_a = raw.transcript) != null ? _a : ""
339
+ });
340
+ break;
341
+ }
342
+ case "turn.end": {
343
+ const text2 = (_b = raw.transcript) != null ? _b : "";
344
+ finalTexts.push(text2);
345
+ controller.enqueue({
346
+ type: "transcript-final",
347
+ id: raw.request_id,
348
+ text: text2
349
+ });
350
+ break;
351
+ }
352
+ case "transcript": {
353
+ const transcript = (_c = raw.text) != null ? _c : "";
354
+ if (raw.is_final === true) {
355
+ finalTexts.push(transcript);
356
+ durationInSeconds += (_d = raw.duration) != null ? _d : 0;
357
+ controller.enqueue({
358
+ type: "transcript-final",
359
+ id: raw.request_id,
360
+ text: transcript
361
+ });
362
+ } else {
363
+ controller.enqueue({
364
+ type: "transcript-partial",
365
+ id: raw.request_id,
366
+ text: transcript,
367
+ ...raw.duration != null ? { durationInSeconds: raw.duration } : {}
368
+ });
369
+ }
370
+ break;
371
+ }
372
+ case "flush_done": {
373
+ (_e = connection == null ? void 0 : connection.socket) == null ? void 0 : _e.send("close");
374
+ break;
375
+ }
376
+ case "done": {
377
+ finish();
378
+ break;
379
+ }
380
+ case "error": {
381
+ finishWithError(
382
+ new Error(
383
+ (_g = (_f = raw.message) != null ? _f : raw.error_code) != null ? _g : "Cartesia streaming transcription error"
384
+ )
385
+ );
386
+ break;
387
+ }
388
+ }
389
+ },
390
+ onSocketError: () => {
391
+ finishWithError(new Error("Cartesia streaming transcription error"));
392
+ },
393
+ onClose: () => {
394
+ if (!finished) finish();
395
+ }
396
+ });
397
+ },
398
+ cancel: () => {
399
+ if (finished) return;
400
+ finished = true;
401
+ cleanup();
402
+ }
403
+ });
404
+ }
405
+ function buildCartesiaStreamingTranscriptionUrl({
406
+ baseURL,
407
+ version,
408
+ modelId,
409
+ inputAudioFormat,
410
+ providerOptions,
411
+ token,
412
+ useTurnDetection
413
+ }) {
414
+ var _a;
415
+ const url = toWebSocketUrl(
416
+ new URL(
417
+ useTurnDetection ? "/stt/turns/websocket" : "/stt/websocket",
418
+ baseURL
419
+ )
420
+ );
421
+ url.searchParams.set("model", modelId);
422
+ url.searchParams.set(
423
+ "encoding",
424
+ cartesiaEncodingFromInputAudioFormat(inputAudioFormat.type)
425
+ );
426
+ url.searchParams.set("sample_rate", String((_a = inputAudioFormat.rate) != null ? _a : 24e3));
427
+ url.searchParams.set("cartesia_version", version);
428
+ url.searchParams.set("access_token", token);
429
+ if (!useTurnDetection && (providerOptions == null ? void 0 : providerOptions.language) != null) {
430
+ url.searchParams.set("language", providerOptions.language);
431
+ }
432
+ return url;
433
+ }
434
+ function cartesiaEncodingFromInputAudioFormat(type) {
435
+ switch (type) {
436
+ case "audio/pcm":
437
+ return "pcm_s16le";
438
+ case "audio/pcmu":
439
+ return "pcm_mulaw";
440
+ case "audio/pcma":
441
+ return "pcm_alaw";
442
+ default:
443
+ throw new InvalidArgumentError({
444
+ argument: "inputAudioFormat",
445
+ message: `Unsupported Cartesia streaming audio format: ${type}`
446
+ });
447
+ }
448
+ }
449
+ function isStreamingTranscriptionModelId(modelId) {
450
+ return modelId === "ink-2" || modelId.startsWith("ink-2-");
451
+ }
143
452
  var cartesiaTranscriptionResponseSchema = z3.object({
144
453
  text: z3.string(),
145
454
  language: z3.string().nullish(),
@@ -152,13 +461,16 @@ var cartesiaTranscriptionResponseSchema = z3.object({
152
461
  })
153
462
  ).nullish()
154
463
  });
464
+ var cartesiaAccessTokenResponseSchema = z3.object({
465
+ token: z3.string().min(1)
466
+ });
155
467
 
156
468
  // src/cartesia-speech-model.ts
157
469
  import {
158
470
  combineHeaders as combineHeaders2,
159
471
  createBinaryResponseHandler,
160
472
  parseProviderOptions as parseProviderOptions2,
161
- postJsonToApi,
473
+ postJsonToApi as postJsonToApi2,
162
474
  serializeModelOptions as serializeModelOptions2,
163
475
  WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2,
164
476
  WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2
@@ -359,7 +671,7 @@ var CartesiaSpeechModel = class _CartesiaSpeechModel {
359
671
  value: audio,
360
672
  responseHeaders,
361
673
  rawValue: rawResponse
362
- } = await postJsonToApi({
674
+ } = await postJsonToApi2({
363
675
  url: this.config.url({
364
676
  path: "/tts/bytes",
365
677
  modelId: this.modelId
@@ -617,7 +929,7 @@ function resolveCartesiaEncoding(type) {
617
929
  }
618
930
 
619
931
  // src/version.ts
620
- var VERSION = true ? "3.0.2" : "0.0.0-test";
932
+ var VERSION = true ? "3.0.4" : "0.0.0-test";
621
933
 
622
934
  // src/cartesia-provider.ts
623
935
  var CARTESIA_API_VERSION = "2026-03-01";
@@ -637,12 +949,17 @@ function createCartesia(options = {}) {
637
949
  `ai-sdk/cartesia/${VERSION}`
638
950
  );
639
951
  };
640
- const createTranscriptionModel = (modelId) => new CartesiaTranscriptionModel(modelId, {
641
- provider: `cartesia.transcription`,
642
- url: ({ path }) => `https://api.cartesia.ai${path}`,
643
- headers: getHeaders,
644
- fetch: options.fetch
645
- });
952
+ const createTranscriptionModel = (modelId) => {
953
+ var _a;
954
+ return new CartesiaTranscriptionModel(modelId, {
955
+ provider: `cartesia.transcription`,
956
+ url: ({ path }) => `https://api.cartesia.ai${path}`,
957
+ headers: getHeaders,
958
+ fetch: options.fetch,
959
+ version: (_a = options.version) != null ? _a : CARTESIA_API_VERSION,
960
+ webSocket: options.webSocket
961
+ });
962
+ };
646
963
  const createSpeechModel = (modelId) => new CartesiaSpeechModel(modelId, {
647
964
  provider: `cartesia.speech`,
648
965
  url: ({ path }) => `https://api.cartesia.ai${path}`,