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.
- package/CHANGELOG.md +78 -0
- package/dist/index.d.ts +166 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +539 -112
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +43 -10
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +209 -24
- package/dist/internal/index.js.map +1 -1
- package/dist/test/index.d.ts +3 -1
- package/dist/test/index.d.ts.map +1 -1
- package/dist/test/index.js +2 -1
- package/dist/test/index.js.map +1 -1
- package/docs/02-foundations/02-providers-and-models.mdx +2 -0
- package/docs/03-agents/04-loop-control.mdx +34 -0
- package/docs/03-agents/06-memory.mdx +42 -0
- package/docs/03-ai-sdk-core/05-generating-text.mdx +1 -0
- package/docs/03-ai-sdk-core/16-mcp-tools.mdx +12 -0
- package/docs/03-ai-sdk-core/36-realtime.mdx +74 -1
- package/docs/03-ai-sdk-core/36-transcription.mdx +51 -4
- package/docs/03-ai-sdk-core/40-middleware.mdx +7 -2
- package/docs/03-ai-sdk-core/60-telemetry.mdx +37 -4
- package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +13 -0
- package/docs/06-advanced/04-caching.mdx +13 -4
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +33 -28
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +58 -1
- package/docs/07-reference/01-ai-sdk-core/11-transcribe.mdx +17 -2
- package/docs/07-reference/01-ai-sdk-core/13-generate-video.mdx +8 -1
- package/docs/07-reference/01-ai-sdk-core/31-ui-message.mdx +23 -0
- package/docs/07-reference/02-ai-sdk-ui/05-use-realtime.mdx +37 -8
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +1 -49
- package/docs/08-migration-guides/23-migration-guide-7-0.mdx +36 -34
- package/package.json +12 -12
- package/src/generate-object/generate-object.ts +2 -1
- package/src/generate-object/stream-object.ts +7 -3
- package/src/generate-text/generate-text.ts +23 -3
- package/src/generate-text/is-output-chunk.ts +1 -0
- package/src/generate-text/language-model-events.ts +3 -0
- package/src/generate-text/output.ts +90 -37
- package/src/generate-text/smooth-stream.ts +5 -0
- package/src/generate-text/steering.ts +5 -4
- package/src/generate-text/stream-language-model-call.ts +16 -1
- package/src/generate-text/stream-text-result.ts +29 -1
- package/src/generate-text/stream-text.ts +25 -1
- package/src/generate-text/to-response-messages.ts +6 -2
- package/src/generate-video/generate-video-result.ts +11 -2
- package/src/generate-video/generate-video.ts +26 -2
- package/src/model/as-transcription-model-v4.ts +25 -0
- package/src/prompt/convert-to-language-model-prompt.ts +86 -14
- package/src/realtime/index.ts +3 -0
- package/src/realtime/realtime-attempt.ts +1 -0
- package/src/realtime/realtime-session.ts +142 -21
- package/src/telemetry/create-telemetry-dispatcher.ts +8 -1
- package/src/telemetry/filter-telemetry-event.ts +165 -0
- package/src/telemetry/get-request-origin.ts +25 -0
- package/src/telemetry/tracing-channel-publisher.ts +19 -4
- package/src/test/mock-transcription-model-v4.ts +4 -0
- package/src/transcribe/transcribe-result.ts +20 -0
- package/src/transcribe/transcribe.ts +107 -20
- package/src/transcribe/transcription-events.ts +28 -8
- package/src/types/language-model-request-metadata.ts +3 -0
- package/src/types/realtime-model.ts +9 -0
- package/src/ui/convert-to-model-messages.ts +36 -28
- package/src/ui/index.ts +1 -0
- package/src/ui/process-ui-message-stream.ts +10 -0
- package/src/ui/ui-messages.ts +20 -0
- package/src/ui/validate-ui-messages.ts +21 -0
- package/src/ui-message-stream/to-ui-message-chunk.ts +72 -1
- 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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
449
|
-
|
|
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
|
-
|
|
465
|
-
|
|
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
|
-
|
|
645
|
-
type: 'url' as const,
|
|
716
|
+
...resolveDownloadedFile({
|
|
646
717
|
url,
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
-
|
|
713
|
-
type: 'url' as const,
|
|
784
|
+
...resolveDownloadedFile({
|
|
714
785
|
url,
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
786
|
+
originalUrl: item.url,
|
|
787
|
+
downloadedAssets,
|
|
788
|
+
mediaType: 'image',
|
|
789
|
+
}),
|
|
718
790
|
providerOptions: item.providerOptions,
|
|
719
791
|
};
|
|
720
792
|
}
|
package/src/realtime/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
/**
|
|
34
|
+
/** Application endpoints for token, SDP, provider-managed, or relay connections. */
|
|
32
35
|
api:
|
|
33
|
-
| {
|
|
34
|
-
|
|
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.
|
|
127
|
-
? '
|
|
128
|
-
: api.
|
|
129
|
-
? '
|
|
130
|
-
:
|
|
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
|
-
(
|
|
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 =
|
|
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
|
-
(
|
|
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.
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
(
|
|
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 (
|
|
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
|
|