ai 7.0.162 → 7.0.164

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.
Files changed (69) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/dist/index.d.ts +166 -41
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +539 -112
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +43 -10
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +209 -24
  9. package/dist/internal/index.js.map +1 -1
  10. package/dist/test/index.d.ts +3 -1
  11. package/dist/test/index.d.ts.map +1 -1
  12. package/dist/test/index.js +2 -1
  13. package/dist/test/index.js.map +1 -1
  14. package/docs/02-foundations/02-providers-and-models.mdx +2 -0
  15. package/docs/03-agents/04-loop-control.mdx +34 -0
  16. package/docs/03-agents/06-memory.mdx +42 -0
  17. package/docs/03-ai-sdk-core/05-generating-text.mdx +1 -0
  18. package/docs/03-ai-sdk-core/16-mcp-tools.mdx +12 -0
  19. package/docs/03-ai-sdk-core/36-realtime.mdx +74 -1
  20. package/docs/03-ai-sdk-core/36-transcription.mdx +51 -4
  21. package/docs/03-ai-sdk-core/40-middleware.mdx +7 -2
  22. package/docs/03-ai-sdk-core/60-telemetry.mdx +37 -4
  23. package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +13 -0
  24. package/docs/06-advanced/04-caching.mdx +13 -4
  25. package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +33 -28
  26. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +58 -1
  27. package/docs/07-reference/01-ai-sdk-core/11-transcribe.mdx +17 -2
  28. package/docs/07-reference/01-ai-sdk-core/13-generate-video.mdx +8 -1
  29. package/docs/07-reference/01-ai-sdk-core/31-ui-message.mdx +23 -0
  30. package/docs/07-reference/02-ai-sdk-ui/05-use-realtime.mdx +37 -8
  31. package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +1 -49
  32. package/docs/08-migration-guides/23-migration-guide-7-0.mdx +36 -34
  33. package/package.json +12 -12
  34. package/src/generate-object/generate-object.ts +2 -1
  35. package/src/generate-object/stream-object.ts +7 -3
  36. package/src/generate-text/generate-text.ts +23 -3
  37. package/src/generate-text/is-output-chunk.ts +1 -0
  38. package/src/generate-text/language-model-events.ts +3 -0
  39. package/src/generate-text/output.ts +90 -37
  40. package/src/generate-text/smooth-stream.ts +5 -0
  41. package/src/generate-text/steering.ts +5 -4
  42. package/src/generate-text/stream-language-model-call.ts +16 -1
  43. package/src/generate-text/stream-text-result.ts +29 -1
  44. package/src/generate-text/stream-text.ts +25 -1
  45. package/src/generate-text/to-response-messages.ts +6 -2
  46. package/src/generate-video/generate-video-result.ts +11 -2
  47. package/src/generate-video/generate-video.ts +26 -2
  48. package/src/model/as-transcription-model-v4.ts +25 -0
  49. package/src/prompt/convert-to-language-model-prompt.ts +86 -14
  50. package/src/realtime/index.ts +3 -0
  51. package/src/realtime/realtime-attempt.ts +1 -0
  52. package/src/realtime/realtime-session.ts +142 -21
  53. package/src/telemetry/create-telemetry-dispatcher.ts +8 -1
  54. package/src/telemetry/filter-telemetry-event.ts +165 -0
  55. package/src/telemetry/get-request-origin.ts +25 -0
  56. package/src/telemetry/tracing-channel-publisher.ts +19 -4
  57. package/src/test/mock-transcription-model-v4.ts +4 -0
  58. package/src/transcribe/transcribe-result.ts +20 -0
  59. package/src/transcribe/transcribe.ts +107 -20
  60. package/src/transcribe/transcription-events.ts +28 -8
  61. package/src/types/language-model-request-metadata.ts +3 -0
  62. package/src/types/realtime-model.ts +9 -0
  63. package/src/ui/convert-to-model-messages.ts +36 -28
  64. package/src/ui/index.ts +1 -0
  65. package/src/ui/process-ui-message-stream.ts +10 -0
  66. package/src/ui/ui-messages.ts +20 -0
  67. package/src/ui/validate-ui-messages.ts +21 -0
  68. package/src/ui-message-stream/to-ui-message-chunk.ts +72 -1
  69. package/src/ui-message-stream/ui-message-chunks.ts +31 -0
@@ -39,8 +39,12 @@ export async function toResponseMessages<TOOLS extends ToolSet>({
39
39
  continue;
40
40
  }
41
41
 
42
- // Skip empty text
43
- if (part.type === 'text' && part.text.length === 0) {
42
+ // Skip empty text without provider metadata.
43
+ if (
44
+ part.type === 'text' &&
45
+ part.text.length === 0 &&
46
+ part.providerMetadata == null
47
+ ) {
44
48
  continue;
45
49
  }
46
50
 
@@ -9,15 +9,24 @@ import type { Warning } from '../types/warning';
9
9
  */
10
10
  export interface GenerateVideoResult {
11
11
  /**
12
- * The first video that was generated.
12
+ * The first video available to the SDK.
13
+ *
14
+ * @throws {NoVideoGeneratedError} When all videos were delivered directly
15
+ * to caller-managed storage. Use `uploadedVideos` to handle upload-only results.
13
16
  */
14
17
  readonly video: GeneratedFile;
15
18
 
16
19
  /**
17
- * All videos that were generated.
20
+ * All videos available to the SDK, excluding videos uploaded by the provider.
18
21
  */
19
22
  readonly videos: Array<GeneratedFile>;
20
23
 
24
+ /**
25
+ * Number of videos delivered directly to caller-managed storage.
26
+ * These videos are not downloaded and are not included in `videos`.
27
+ */
28
+ readonly uploadedVideos: number;
29
+
21
30
  /**
22
31
  * Warnings for the call, e.g. unsupported settings.
23
32
  */
@@ -377,12 +377,18 @@ export async function experimental_generateVideo({
377
377
 
378
378
  // collect result videos, warnings, and response metadata
379
379
  const videos: Array<GeneratedFile> = [];
380
+ let uploadedVideos = 0;
380
381
  const responses: Array<VideoModelResponseMetadata> = [];
381
382
  const providerMetadata: SharedV4ProviderMetadata = {};
382
383
 
383
384
  for (const result of results) {
384
385
  for (const videoData of result.videos) {
385
386
  switch (videoData.type) {
387
+ case 'uploaded': {
388
+ uploadedVideos++;
389
+ break;
390
+ }
391
+
386
392
  case 'url': {
387
393
  const { data, mediaType: downloadedMediaType } = await downloadFn({
388
394
  url: new URL(videoData.url),
@@ -455,7 +461,7 @@ export async function experimental_generateVideo({
455
461
  }
456
462
  }
457
463
 
458
- if (videos.length === 0) {
464
+ if (videos.length === 0 && uploadedVideos === 0) {
459
465
  throw new NoVideoGeneratedError({ responses });
460
466
  }
461
467
 
@@ -467,13 +473,31 @@ export async function experimental_generateVideo({
467
473
  });
468
474
  }
469
475
 
470
- return {
476
+ const result = {
471
477
  video: videos[0],
472
478
  videos,
479
+ uploadedVideos,
473
480
  warnings,
474
481
  responses,
475
482
  providerMetadata,
476
483
  };
484
+
485
+ if (videos.length === 0) {
486
+ // Keep upload-only results safe to serialize or spread. Normal results
487
+ // retain their existing enumerable video data property.
488
+ Object.defineProperty(result, 'video', {
489
+ enumerable: false,
490
+ get() {
491
+ throw new NoVideoGeneratedError({
492
+ message:
493
+ 'Videos were delivered to the upload destination. Use uploadedVideos instead of video.',
494
+ responses,
495
+ });
496
+ },
497
+ });
498
+ }
499
+
500
+ return result;
477
501
  }
478
502
 
479
503
  async function executeStartStatusFlow({
@@ -19,6 +19,31 @@ export function asTranscriptionModelV4(
19
19
  return new Proxy(v3Model, {
20
20
  get(target, prop: keyof TranscriptionModelV3) {
21
21
  if (prop === 'specificationVersion') return 'v4';
22
+ if (prop === 'doGenerate') {
23
+ return async ({
24
+ timestampGranularities,
25
+ ...options
26
+ }: Parameters<TranscriptionModelV4['doGenerate']>[0]) => {
27
+ // Bind to the original model, including when target is a v2 proxy.
28
+ const result = await target.doGenerate.call(model, options);
29
+
30
+ if (!timestampGranularities?.length) {
31
+ return result;
32
+ }
33
+
34
+ return {
35
+ ...result,
36
+ warnings: [
37
+ ...result.warnings,
38
+ {
39
+ type: 'unsupported' as const,
40
+ feature: 'timestampGranularities',
41
+ details: `Shared timestamp granularities are not supported by ${model.specificationVersion} transcription models.`,
42
+ },
43
+ ],
44
+ };
45
+ };
46
+ }
22
47
  return target[prop];
23
48
  },
24
49
  }) as unknown as TranscriptionModelV4;
@@ -445,8 +445,10 @@ export async function downloadAssets(
445
445
  }
446
446
 
447
447
  for (const contentPart of part.output.value) {
448
- if (contentPart.type === 'file') {
449
- downloadableFiles.push(contentPart);
448
+ const filePart = getDownloadableFilePart(contentPart);
449
+
450
+ if (filePart != null) {
451
+ downloadableFiles.push(filePart);
450
452
  }
451
453
  }
452
454
  }
@@ -461,8 +463,10 @@ export async function downloadAssets(
461
463
  continue;
462
464
  }
463
465
  for (const contentPart of part.output.value) {
464
- if (contentPart.type === 'file') {
465
- downloadableFiles.push(contentPart);
466
+ const filePart = getDownloadableFilePart(contentPart);
467
+
468
+ if (filePart != null) {
469
+ downloadableFiles.push(filePart);
466
470
  }
467
471
  }
468
472
  }
@@ -503,6 +507,74 @@ export async function downloadAssets(
503
507
  );
504
508
  }
505
509
 
510
+ /**
511
+ * Resolves the tool result content parts that point at a remote file to the
512
+ * tagged file shape, including the deprecated `file-url` and `image-url`
513
+ * types, so that they are downloaded and inlined exactly like their `file`
514
+ * replacement. Returns `undefined` for content that is already inline or
515
+ * provider-referenced.
516
+ */
517
+ function getDownloadableFilePart(
518
+ contentPart: Extract<ToolResultOutput, { type: 'content' }>['value'][number],
519
+ ): FilePart | undefined {
520
+ switch (contentPart.type) {
521
+ case 'file':
522
+ return contentPart;
523
+ case 'file-url':
524
+ return {
525
+ type: 'file',
526
+ data: { type: 'url', url: new URL(contentPart.url) },
527
+ mediaType:
528
+ contentPart.mediaType ?? getMediaTypeFromUrl(contentPart.url),
529
+ providerOptions: contentPart.providerOptions,
530
+ };
531
+ case 'image-url':
532
+ return {
533
+ type: 'file',
534
+ data: { type: 'url', url: new URL(contentPart.url) },
535
+ mediaType: 'image',
536
+ providerOptions: contentPart.providerOptions,
537
+ };
538
+ default:
539
+ return undefined;
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Resolves file data and media type for a remote tool result content part: the downloaded
545
+ * bytes when the asset was fetched, otherwise the URL reference. `originalUrl`
546
+ * is preserved when the URL string does not round-trip through `URL`.
547
+ */
548
+ function resolveDownloadedFile({
549
+ url,
550
+ originalUrl,
551
+ downloadedAssets,
552
+ mediaType,
553
+ }: {
554
+ url: URL;
555
+ originalUrl: string;
556
+ downloadedAssets: Record<
557
+ string,
558
+ { mediaType: string | undefined; data: Uint8Array }
559
+ >;
560
+ mediaType: string;
561
+ }): Pick<LanguageModelV4FilePart, 'data' | 'mediaType'> {
562
+ const convertedPart = convertPartToLanguageModelPart(
563
+ {
564
+ type: 'file',
565
+ data: {
566
+ type: 'url',
567
+ url,
568
+ ...(url.toString() !== originalUrl ? { originalUrl } : {}),
569
+ },
570
+ mediaType,
571
+ },
572
+ downloadedAssets,
573
+ ) as LanguageModelV4FilePart;
574
+
575
+ return { data: convertedPart.data, mediaType: convertedPart.mediaType };
576
+ }
577
+
506
578
  /**
507
579
  * Convert part of a user message to a LanguageModelV4Part.
508
580
  *
@@ -641,12 +713,12 @@ export function mapToolResultOutput({
641
713
  });
642
714
  return {
643
715
  type: 'file' as const,
644
- data: {
645
- type: 'url' as const,
716
+ ...resolveDownloadedFile({
646
717
  url,
647
- ...(url.toString() !== item.url ? { originalUrl: item.url } : {}),
648
- },
649
- mediaType,
718
+ originalUrl: item.url,
719
+ downloadedAssets,
720
+ mediaType,
721
+ }),
650
722
  providerOptions: item.providerOptions,
651
723
  };
652
724
  }
@@ -709,12 +781,12 @@ export function mapToolResultOutput({
709
781
  });
710
782
  return {
711
783
  type: 'file' as const,
712
- data: {
713
- type: 'url' as const,
784
+ ...resolveDownloadedFile({
714
785
  url,
715
- ...(url.toString() !== item.url ? { originalUrl: item.url } : {}),
716
- },
717
- mediaType: 'image',
786
+ originalUrl: item.url,
787
+ downloadedAssets,
788
+ mediaType: 'image',
789
+ }),
718
790
  providerOptions: item.providerOptions,
719
791
  };
720
792
  }
@@ -18,7 +18,10 @@ export type {
18
18
  RealtimeFactoryGetTokenOptions as Experimental_RealtimeFactoryGetTokenOptions,
19
19
  RealtimeFactoryGetTokenResult as Experimental_RealtimeFactoryGetTokenResult,
20
20
  RealtimeModel as Experimental_RealtimeModel,
21
+ RealtimeServerConnection as Experimental_RealtimeServerConnection,
21
22
  RealtimeServerEvent as Experimental_RealtimeServerEvent,
22
23
  RealtimeSessionConfig as Experimental_RealtimeSessionConfig,
24
+ RealtimeSessionIntent as Experimental_RealtimeSessionIntent,
23
25
  RealtimeToolDefinition as Experimental_RealtimeToolDefinition,
26
+ RealtimeUsage as Experimental_RealtimeUsage,
24
27
  } from '../types/realtime-model';
@@ -1,6 +1,7 @@
1
1
  /** Cancellation and settlement belong to one connection attempt. */
2
2
  export class RealtimeAttempt {
3
3
  readonly abort = new AbortController();
4
+ readonly reportedTransportErrors = new WeakSet<Error>();
4
5
  private timers = new Map<string, ReturnType<typeof setTimeout>>();
5
6
  private settleClose?: () => void;
6
7
  closePromise?: Promise<void>;
@@ -1,4 +1,7 @@
1
- import { UnsupportedFunctionalityError } from '@ai-sdk/provider';
1
+ import {
2
+ UnsupportedFunctionalityError,
3
+ type Experimental_RealtimeModelV4Transport as RealtimeModelTransport,
4
+ } from '@ai-sdk/provider';
2
5
  import type {
3
6
  RealtimeClientEvent,
4
7
  RealtimeModel,
@@ -28,15 +31,35 @@ export type { RealtimeState, RealtimeStatus } from './realtime-event-reducer';
28
31
 
29
32
  export type RealtimeSessionOptions = {
30
33
  model: RealtimeModel;
31
- /** websocket uses a raw-protocol relay; session exchanges SDP; token fetches client-secret setup. */
34
+ /** Application endpoints for token, SDP, provider-managed, or relay connections. */
32
35
  api:
33
- | { token: string; session?: never; websocket?: never; protocols?: never }
34
- | { session: string; token?: never; websocket?: never; protocols?: never }
36
+ | {
37
+ token: string;
38
+ session?: never;
39
+ websocket?: never;
40
+ protocols?: never;
41
+ provider?: never;
42
+ }
43
+ | {
44
+ session: string;
45
+ token?: never;
46
+ websocket?: never;
47
+ protocols?: never;
48
+ provider?: never;
49
+ }
50
+ | {
51
+ provider: string;
52
+ token?: never;
53
+ session?: never;
54
+ websocket?: never;
55
+ protocols?: never;
56
+ }
35
57
  | {
36
58
  websocket: string;
37
59
  protocols?: string[];
38
60
  token?: never;
39
61
  session?: never;
62
+ provider?: never;
40
63
  };
41
64
  sessionConfig?: Partial<RealtimeSessionConfig>;
42
65
  sampleRate?: number;
@@ -72,6 +95,7 @@ export abstract class AbstractRealtimeSession {
72
95
  private audio?: BrowserRealtimeAudio;
73
96
  private pcm?: BrowserRealtimeLiveWebSocket;
74
97
  private rtc?: BrowserRealtimeWebRTC;
98
+ private providerTransport?: RealtimeModelTransport;
75
99
  private suppliedStream?: MediaStream;
76
100
  private captureGeneration = 0;
77
101
  private captureRequested = false;
@@ -116,28 +140,36 @@ export abstract class AbstractRealtimeSession {
116
140
  private validateConnection(): void {
117
141
  const { model, api } = this.options;
118
142
  if (
119
- (!this.continuous || api.session != null) &&
143
+ (!this.continuous || api.session != null || api.provider != null) &&
120
144
  this.options.maxPlaybackBufferSeconds != null
121
145
  )
122
146
  throw new Error(
123
147
  'maxPlaybackBufferSeconds is supported only for continuous PCM sessions',
124
148
  );
125
149
  const connection =
126
- api.session != null
127
- ? 'webrtc'
128
- : api.token != null
129
- ? 'client-secret-websocket'
130
- : 'server-websocket';
150
+ api.provider != null
151
+ ? 'provider'
152
+ : api.session != null
153
+ ? 'webrtc'
154
+ : api.token != null
155
+ ? 'client-secret-websocket'
156
+ : 'server-websocket';
131
157
  if (
132
158
  !(
133
159
  model.capabilities?.connections ?? ['client-secret-websocket']
134
160
  ).includes(connection) ||
135
- (model.capabilities?.transports != null &&
161
+ (api.provider == null &&
162
+ model.capabilities?.transports != null &&
136
163
  !model.capabilities.transports.includes(
137
164
  api.session != null ? 'webrtc' : 'websocket',
138
165
  ))
139
166
  )
140
167
  throw new Error(`Realtime model does not support ${connection}`);
168
+ if (api.provider != null) {
169
+ if (model.createTransport == null)
170
+ throw new Error('Realtime model does not support a provider transport');
171
+ return;
172
+ }
141
173
  if (api.session != null) {
142
174
  if (model.getWebRTCConfig == null)
143
175
  throw new Error('Realtime model does not support WebRTC configuration');
@@ -179,7 +211,8 @@ export abstract class AbstractRealtimeSession {
179
211
  this.captureRequested &&
180
212
  !this.sessionLifecycle &&
181
213
  !this.continuous &&
182
- api.session == null
214
+ api.session == null &&
215
+ api.provider == null
183
216
  )
184
217
  this.startAudioCapture(connectOptions.stream);
185
218
  else this.suppliedStream = connectOptions.stream;
@@ -206,7 +239,7 @@ export abstract class AbstractRealtimeSession {
206
239
  if (current())
207
240
  this.fail(new Error('Realtime session startup timed out'));
208
241
  });
209
- const reportedTransportErrors = new WeakSet<Error>();
242
+ const reportedTransportErrors = attempt.reportedTransportErrors;
210
243
  const callbacks = {
211
244
  model,
212
245
  onEvent: async (event: RealtimeServerEvent) => {
@@ -217,7 +250,12 @@ export abstract class AbstractRealtimeSession {
217
250
  if (current())
218
251
  this.fail(
219
252
  error,
220
- (this.rtc ?? this.pcm ?? this.transport)?.finish(),
253
+ (
254
+ this.providerTransport ??
255
+ this.rtc ??
256
+ this.pcm ??
257
+ this.transport
258
+ )?.finish(),
221
259
  );
222
260
  }
223
261
  },
@@ -248,6 +286,19 @@ export abstract class AbstractRealtimeSession {
248
286
  const finalizationConfirmed =
249
287
  model.capabilities?.finalization === 'session-close' &&
250
288
  this.state.session?.finalization === 'confirmed';
289
+ // Local media can close before remote cleanup settles. The provider's
290
+ // close promise and its deadline own completion during shutdown.
291
+ if (api.provider != null && attempt.closing) {
292
+ if (
293
+ error != null &&
294
+ !finalizationConfirmed &&
295
+ !reportedTransportErrors.has(error)
296
+ ) {
297
+ reportedTransportErrors.add(error);
298
+ void this.reportError(error, attempt);
299
+ }
300
+ return;
301
+ }
251
302
  if (error != null && !finalizationConfirmed)
252
303
  this.fail(error, undefined, !reportedTransportErrors.has(error));
253
304
  else if (!attempt.ready && !finalizationConfirmed)
@@ -263,7 +314,21 @@ export abstract class AbstractRealtimeSession {
263
314
  if (current()) this.applyState({ ...this.state, isPlaying });
264
315
  },
265
316
  };
266
- if (api.session != null) {
317
+ if (api.provider != null) {
318
+ const transport = model.createTransport!(callbacks);
319
+ if (!current()) {
320
+ transport.dispose();
321
+ return;
322
+ }
323
+ this.providerTransport = transport;
324
+ await transport.connect({
325
+ api: api.provider,
326
+ sessionConfig,
327
+ stream: connectOptions?.stream,
328
+ capture: connectOptions?.capture,
329
+ abortSignal: attempt.abort.signal,
330
+ });
331
+ } else if (api.session != null) {
267
332
  this.rtc = new BrowserRealtimeWebRTC({
268
333
  ...callbacks,
269
334
  disconnectTimeoutMs: this.options.rtcDisconnectTimeoutMs,
@@ -394,8 +459,13 @@ export abstract class AbstractRealtimeSession {
394
459
  if (this.attempt !== attempt || !attempt.active || attempt.transportClosing)
395
460
  return;
396
461
  attempt.clearTimer('close');
397
- const transport = this.rtc ?? this.pcm ?? this.transport;
462
+ const transport =
463
+ this.providerTransport ?? this.rtc ?? this.pcm ?? this.transport;
398
464
  void this.drainAttempt(attempt, transport?.finish());
465
+ if (error instanceof Error) {
466
+ if (attempt.reportedTransportErrors.has(error)) return;
467
+ attempt.reportedTransportErrors.add(error);
468
+ }
399
469
  void this.reportError(error, attempt);
400
470
  }
401
471
 
@@ -408,16 +478,19 @@ export abstract class AbstractRealtimeSession {
408
478
  const audio = this.audio;
409
479
  const pcm = this.pcm;
410
480
  const rtc = this.rtc;
481
+ const providerTransport = this.providerTransport;
411
482
  this.transport = undefined;
412
483
  this.audio = undefined;
413
484
  this.pcm = undefined;
414
485
  this.rtc = undefined;
486
+ this.providerTransport = undefined;
415
487
  this.commands = undefined;
416
488
  attempt?.retire();
417
489
  transport?.dispose();
418
490
  audio?.dispose();
419
491
  pcm?.dispose();
420
492
  rtc?.dispose();
493
+ providerTransport?.dispose();
421
494
  if (this.attempt !== attempt) return;
422
495
  const session = this.state.session;
423
496
  this.applyState({
@@ -436,10 +509,28 @@ export abstract class AbstractRealtimeSession {
436
509
  });
437
510
  }
438
511
 
439
- /** Wait for final usage when the model supports a session-close acknowledgement. */
512
+ /** Wait for provider cleanup or supported session-close finalization. */
440
513
  close(options?: { eventId?: string }): Promise<void> {
441
514
  const attempt = this.attempt;
442
515
  if (attempt?.closePromise != null) return attempt.closePromise;
516
+ const providerTransport = this.providerTransport;
517
+ if (providerTransport != null && attempt?.active) {
518
+ const promise = attempt.beginClose();
519
+ attempt.clearTimer('startup');
520
+ this.applyState({ ...this.state, status: 'closing' });
521
+ if (this.attempt !== attempt || !attempt.active) return promise;
522
+ this.stopAudioCapture();
523
+ if (this.attempt !== attempt || !attempt.active) return promise;
524
+ attempt.timer('close', this.options.closeTimeoutMs ?? 15_000, () => {
525
+ if (this.attempt === attempt && attempt.active)
526
+ this.fail(new Error('Realtime provider session cleanup timed out'));
527
+ });
528
+ void providerTransport.close().then(
529
+ () => this.finishAttempt(attempt),
530
+ error => this.closeFailed(attempt, error),
531
+ );
532
+ return promise;
533
+ }
443
534
  if (
444
535
  this.state.status !== 'connected' ||
445
536
  this.options.model.capabilities?.finalization !== 'session-close' ||
@@ -475,8 +566,18 @@ export abstract class AbstractRealtimeSession {
475
566
  guard?: () => boolean,
476
567
  automaticAudio = false,
477
568
  ): Promise<void> {
478
- const transport = this.rtc ?? this.pcm ?? this.transport;
569
+ const transport =
570
+ this.providerTransport ?? this.rtc ?? this.pcm ?? this.transport;
479
571
  if (transport == null) throw new Error('Realtime connection is not open');
572
+ if (transport === this.providerTransport) {
573
+ const attempt = this.attempt;
574
+ const sent = transport.sendEvent(event, guard);
575
+ void sent.catch(error => {
576
+ if (attempt?.active && this.attempt === attempt)
577
+ void this.reportError(error, attempt);
578
+ });
579
+ return sent;
580
+ }
480
581
  if (transport === this.transport)
481
582
  return this.transport.sendEvent(event, guard, automaticAudio);
482
583
  return transport.sendEvent(event, guard);
@@ -606,6 +707,7 @@ export abstract class AbstractRealtimeSession {
606
707
 
607
708
  private maybeRequestToolResponse(): void {
608
709
  if (
710
+ this.providerTransport != null ||
609
711
  this.state.status !== 'connected' ||
610
712
  this.attempt?.closing ||
611
713
  !this.responseToolCallsClosed ||
@@ -625,7 +727,8 @@ export abstract class AbstractRealtimeSession {
625
727
  const legacy =
626
728
  !this.sessionLifecycle &&
627
729
  !this.continuous &&
628
- this.options.api.session == null;
730
+ this.options.api.session == null &&
731
+ this.options.api.provider == null;
629
732
  if (!legacy && this.state.status !== 'connected')
630
733
  throw new Error('Realtime session is not accepting capture');
631
734
  if (
@@ -655,6 +758,8 @@ export abstract class AbstractRealtimeSession {
655
758
  if (!accepting())
656
759
  throw new Error('Realtime session is not accepting capture');
657
760
  this.captureRequested = true;
761
+ if (this.providerTransport != null)
762
+ return this.providerTransport.startCapture(this.suppliedStream);
658
763
  if (this.rtc != null) return this.rtc.startCapture(this.suppliedStream);
659
764
  if (this.pcm != null) return this.pcm.resumeCapture(this.suppliedStream);
660
765
  const audio = this.audio;
@@ -694,7 +799,13 @@ export abstract class AbstractRealtimeSession {
694
799
  const pcm = this.pcm;
695
800
  const audio = this.audio;
696
801
  const rtc = this.rtc;
802
+ const providerTransport = this.providerTransport;
697
803
  const attempt = this.attempt;
804
+ if (providerTransport != null)
805
+ void providerTransport.stopCapture().catch(error => {
806
+ if (attempt?.active && this.attempt === attempt)
807
+ void this.reportError(error, attempt);
808
+ });
698
809
  if (rtc != null)
699
810
  void rtc.stopCapture().catch(error => {
700
811
  if (attempt?.active && this.attempt === attempt)
@@ -704,10 +815,20 @@ export abstract class AbstractRealtimeSession {
704
815
  audio?.stopCapture();
705
816
  }
706
817
  stopPlayback(): void {
707
- (this.rtc ?? this.pcm ?? this.audio)?.stopPlayback();
818
+ (
819
+ this.providerTransport ??
820
+ this.rtc ??
821
+ this.pcm ??
822
+ this.audio
823
+ )?.stopPlayback();
708
824
  }
709
825
  async resumePlayback(): Promise<void> {
710
- await (this.rtc ?? this.pcm ?? this.audio)?.resumePlayback();
826
+ await (
827
+ this.providerTransport ??
828
+ this.rtc ??
829
+ this.pcm ??
830
+ this.audio
831
+ )?.resumePlayback();
711
832
  }
712
833
  dispose(): void {
713
834
  this.disconnect();
@@ -18,6 +18,7 @@ import {
18
18
  } from './tracing-channel-publisher';
19
19
  import { getGlobalTelemetryIntegrations } from './telemetry-registry';
20
20
  import type { TelemetryOptions } from './telemetry-options';
21
+ import { filterTelemetryEvent } from './filter-telemetry-event';
21
22
 
22
23
  /**
23
24
  * The subset of `TelemetryDispatcher` keys whose values are Callback callbacks.
@@ -100,7 +101,7 @@ function augmentEvent<EVENT>(
100
101
  }
101
102
  }
102
103
 
103
- return augmentedEvent;
104
+ return filterTelemetryEvent(augmentedEvent, telemetry);
104
105
  }
105
106
 
106
107
  /**
@@ -141,6 +142,9 @@ export function createTelemetryDispatcher({
141
142
  includeRuntimeContext: telemetry?.includeRuntimeContext,
142
143
  includeToolsContext: telemetry?.includeToolsContext,
143
144
  };
145
+ // Raw operation results may contain both inputs and outputs.
146
+ const recordResult =
147
+ telemetry?.recordInputs !== false && telemetry?.recordOutputs !== false;
144
148
 
145
149
  const mergeTelemetryCallback = <KEY extends TelemetryCallbackKey>(
146
150
  key: KEY,
@@ -199,6 +203,7 @@ export function createTelemetryDispatcher({
199
203
  event: augmentEvent(event, telemetryMetadata, true),
200
204
  },
201
205
  execute,
206
+ recordResult,
202
207
  ),
203
208
 
204
209
  startTracingChannelContext: ({ type, event, completion }) =>
@@ -208,6 +213,7 @@ export function createTelemetryDispatcher({
208
213
  event: augmentEvent(event, telemetryMetadata, true),
209
214
  },
210
215
  completion,
216
+ recordResult,
211
217
  }),
212
218
 
213
219
  onStart: mergeTelemetryCallback('onStart'),
@@ -276,6 +282,7 @@ export function createTelemetryDispatcher({
276
282
  return await runWithTracingChannelSpan(
277
283
  { type: 'languageModelCall', event: augmentedEvent },
278
284
  wrappedExecute,
285
+ recordResult,
279
286
  );
280
287
  },
281
288