@ai-sdk/openai 4.0.107 → 4.0.109
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 +38 -0
- package/dist/index.d.ts +67 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +620 -378
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +244 -4
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +481 -345
- package/dist/internal/index.js.map +1 -1
- package/docs/03-openai.mdx +156 -18
- package/package.json +3 -3
- package/src/chat/convert-to-openai-chat-messages.ts +29 -4
- package/src/chat/openai-chat-language-model-options.ts +13 -0
- package/src/chat/openai-chat-language-model.ts +10 -10
- package/src/chat/openai-chat-prompt.ts +4 -0
- package/src/image/openai-image-model-options.ts +48 -0
- package/src/image/openai-image-model.ts +49 -4
- package/src/index.ts +1 -0
- package/src/internal/index.ts +2 -0
- package/src/live/openai-realtime-model-live.ts +21 -2
- package/src/openai-model-catalog.ts +3 -1
- package/src/openai-tools.ts +4 -3
- package/src/realtime/openai-realtime-event-mapper.ts +172 -11
- package/src/realtime/openai-realtime-model.ts +57 -3
- package/src/responses/openai-responses-api.ts +19 -1
- package/src/responses/openai-responses-prepare-tools.ts +18 -1
- package/src/tool/code-interpreter.ts +38 -2
- package/src/tool/mcp.ts +27 -8
- package/src/transcription/openai-transcription-model.ts +43 -8
package/src/internal/index.ts
CHANGED
|
@@ -13,10 +13,12 @@ export * from '../speech/openai-speech-model-options';
|
|
|
13
13
|
export * from '../responses/openai-responses-language-model';
|
|
14
14
|
export * from '../responses/openai-responses-provider-metadata';
|
|
15
15
|
export * from '../tool/apply-patch';
|
|
16
|
+
export * from '../tool/custom';
|
|
16
17
|
export * from '../tool/code-interpreter';
|
|
17
18
|
export * from '../tool/file-search';
|
|
18
19
|
export * from '../tool/image-generation';
|
|
19
20
|
export * from '../tool/programmatic-tool-calling';
|
|
21
|
+
export * from '../tool/shell';
|
|
20
22
|
export * from '../tool/tool-search';
|
|
21
23
|
export * from '../tool/web-search';
|
|
22
24
|
export * from '../tool/web-search-preview';
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type Experimental_RealtimeModelV4 as RealtimeModelV4,
|
|
3
3
|
type Experimental_RealtimeModelV4ClientEvent as RealtimeModelV4ClientEvent,
|
|
4
4
|
type Experimental_RealtimeModelV4SessionConfig as RealtimeModelV4SessionConfig,
|
|
5
|
+
type Experimental_RealtimeModelV4SessionIntent as RealtimeModelV4SessionIntent,
|
|
5
6
|
} from '@ai-sdk/provider';
|
|
6
7
|
import {
|
|
7
8
|
resolveWith,
|
|
@@ -54,7 +55,10 @@ export class OpenAIRealtimeModelLive implements RealtimeModelV4 {
|
|
|
54
55
|
return { dataChannelLabel: 'oai-events' };
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
getServerWebSocketConfig(
|
|
58
|
+
getServerWebSocketConfig(options?: {
|
|
59
|
+
intent?: RealtimeModelV4SessionIntent;
|
|
60
|
+
}) {
|
|
61
|
+
this.assertConversationIntent(options);
|
|
58
62
|
return resolveWith(this.config.headers, configHeaders => {
|
|
59
63
|
const url = new URL(`${this.config.baseURL}/live/sessions`);
|
|
60
64
|
url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:';
|
|
@@ -103,13 +107,28 @@ export class OpenAIRealtimeModelLive implements RealtimeModelV4 {
|
|
|
103
107
|
return createOpenAILiveServerEventParser();
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
|
|
110
|
+
private assertConversationIntent(options?: {
|
|
111
|
+
intent?: RealtimeModelV4SessionIntent;
|
|
112
|
+
}): void {
|
|
113
|
+
if (options?.intent != null && options.intent !== 'conversation')
|
|
114
|
+
throw new Error(
|
|
115
|
+
`OpenAI Live session intent '${options.intent}' is not supported.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
serializeClientEvent(
|
|
120
|
+
event: RealtimeModelV4ClientEvent,
|
|
121
|
+
options?: { intent?: RealtimeModelV4SessionIntent },
|
|
122
|
+
): unknown {
|
|
123
|
+
this.assertConversationIntent(options);
|
|
107
124
|
return serializeOpenAILiveClientEvent(event, this.modelId);
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
buildSessionConfig(
|
|
111
128
|
config: RealtimeModelV4SessionConfig,
|
|
129
|
+
options?: { intent?: RealtimeModelV4SessionIntent },
|
|
112
130
|
): Record<string, unknown> {
|
|
131
|
+
this.assertConversationIntent(options);
|
|
113
132
|
return buildOpenAILiveSessionConfig(config, this.modelId);
|
|
114
133
|
}
|
|
115
134
|
}
|
|
@@ -461,6 +461,8 @@ function createGptImageEntry(
|
|
|
461
461
|
|
|
462
462
|
const openaiImageModelEntries: Readonly<Record<string, OpenAIImageModelEntry>> =
|
|
463
463
|
{
|
|
464
|
+
'gpt-image-2.6-flare': createGptImageEntry(customImageSizes),
|
|
465
|
+
'gpt-image-2.6-sunburst': createGptImageEntry(customImageSizes),
|
|
464
466
|
'gpt-image-2.5-flare': createGptImageEntry(customImageSizes),
|
|
465
467
|
'gpt-image-2.5-sunburst': createGptImageEntry(customImageSizes),
|
|
466
468
|
'gpt-image-2': createGptImageEntry(gptImageSizes),
|
|
@@ -757,7 +759,7 @@ const realtimeModelEntry = {
|
|
|
757
759
|
realtime: {
|
|
758
760
|
conversation: 'turn-based',
|
|
759
761
|
transports: ['websocket'],
|
|
760
|
-
connections: ['client-secret-websocket'],
|
|
762
|
+
connections: ['client-secret-websocket', 'server-websocket'],
|
|
761
763
|
},
|
|
762
764
|
},
|
|
763
765
|
requestConfig: {
|
package/src/openai-tools.ts
CHANGED
|
@@ -126,16 +126,17 @@ export const openaiTools = {
|
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
128
|
* MCP (Model Context Protocol) allows models to call tools exposed by
|
|
129
|
-
* remote MCP servers or
|
|
129
|
+
* remote MCP servers, service connectors, or Secure MCP tunnels.
|
|
130
130
|
*
|
|
131
131
|
* @param serverLabel - Label to identify the MCP server.
|
|
132
132
|
* @param allowedTools - Allowed tool names or filter object.
|
|
133
|
-
* @param authorization - OAuth access token for the MCP server
|
|
133
|
+
* @param authorization - OAuth access token for the MCP server or connector (including tunneled servers).
|
|
134
134
|
* @param connectorId - Identifier for a service connector.
|
|
135
135
|
* @param headers - Optional headers to include in MCP requests.
|
|
136
|
-
*
|
|
136
|
+
* @param requireApproval - Approval policy ('always'|'never'|filter object).
|
|
137
137
|
* @param serverDescription - Optional description of the server.
|
|
138
138
|
* @param serverUrl - URL for the MCP server.
|
|
139
|
+
* @param tunnelId - Identifier for an OpenAI Secure MCP tunnel.
|
|
139
140
|
*/
|
|
140
141
|
mcp,
|
|
141
142
|
|
|
@@ -2,13 +2,48 @@ import type {
|
|
|
2
2
|
Experimental_RealtimeModelV4ClientEvent as RealtimeModelV4ClientEvent,
|
|
3
3
|
Experimental_RealtimeModelV4ServerEvent as RealtimeModelV4ServerEvent,
|
|
4
4
|
Experimental_RealtimeModelV4SessionConfig as RealtimeModelV4SessionConfig,
|
|
5
|
+
Experimental_RealtimeModelV4SessionIntent as RealtimeModelV4SessionIntent,
|
|
6
|
+
Experimental_RealtimeModelV4Usage as RealtimeModelV4Usage,
|
|
5
7
|
} from '@ai-sdk/provider';
|
|
6
8
|
|
|
9
|
+
type OpenAIRealtimeSessionOptions = {
|
|
10
|
+
intent?: RealtimeModelV4SessionIntent;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type OpenAIRealtimeTokenDetails = {
|
|
14
|
+
audio_tokens?: number;
|
|
15
|
+
text_tokens?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type OpenAIRealtimeInputTokenDetails = OpenAIRealtimeTokenDetails & {
|
|
19
|
+
cached_tokens?: number;
|
|
20
|
+
cached_tokens_details?: OpenAIRealtimeTokenDetails;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type OpenAIRealtimeResponseUsage = {
|
|
24
|
+
input_tokens?: number;
|
|
25
|
+
output_tokens?: number;
|
|
26
|
+
input_token_details?: OpenAIRealtimeInputTokenDetails;
|
|
27
|
+
output_token_details?: OpenAIRealtimeTokenDetails;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type OpenAIRealtimeTranscriptionUsage = {
|
|
31
|
+
input_tokens?: number;
|
|
32
|
+
type?: string;
|
|
33
|
+
seconds?: number;
|
|
34
|
+
output_tokens?: number;
|
|
35
|
+
input_token_details?: OpenAIRealtimeTokenDetails;
|
|
36
|
+
};
|
|
37
|
+
|
|
7
38
|
type OpenAIRealtimeWireEvent = {
|
|
8
39
|
type: string;
|
|
9
40
|
session?: { id?: string };
|
|
10
41
|
item?: { id?: string } & Record<string, unknown>;
|
|
11
|
-
response?: {
|
|
42
|
+
response?: {
|
|
43
|
+
id?: string;
|
|
44
|
+
status?: string;
|
|
45
|
+
usage?: OpenAIRealtimeResponseUsage;
|
|
46
|
+
};
|
|
12
47
|
error?: {
|
|
13
48
|
message?: string | null;
|
|
14
49
|
code?: string | null;
|
|
@@ -25,6 +60,7 @@ type OpenAIRealtimeWireEvent = {
|
|
|
25
60
|
arguments: string;
|
|
26
61
|
message?: string;
|
|
27
62
|
code?: string;
|
|
63
|
+
usage?: OpenAIRealtimeTranscriptionUsage;
|
|
28
64
|
};
|
|
29
65
|
|
|
30
66
|
/**
|
|
@@ -80,13 +116,16 @@ export function parseOpenAIRealtimeServerEvent(
|
|
|
80
116
|
raw,
|
|
81
117
|
};
|
|
82
118
|
|
|
83
|
-
case 'conversation.item.input_audio_transcription.completed':
|
|
119
|
+
case 'conversation.item.input_audio_transcription.completed': {
|
|
120
|
+
const usage = mapTranscriptionUsage(event.usage);
|
|
84
121
|
return {
|
|
85
122
|
type: 'input-transcription-completed',
|
|
86
123
|
itemId: event.item_id,
|
|
87
124
|
transcript: event.transcript ?? '',
|
|
125
|
+
...(usage != null ? { usage } : {}),
|
|
88
126
|
raw,
|
|
89
127
|
};
|
|
128
|
+
}
|
|
90
129
|
|
|
91
130
|
// ── Response lifecycle ──────────────────────────────────────────
|
|
92
131
|
case 'response.created':
|
|
@@ -96,13 +135,16 @@ export function parseOpenAIRealtimeServerEvent(
|
|
|
96
135
|
raw,
|
|
97
136
|
};
|
|
98
137
|
|
|
99
|
-
case 'response.done':
|
|
138
|
+
case 'response.done': {
|
|
139
|
+
const usage = mapResponseUsage(event.response?.usage);
|
|
100
140
|
return {
|
|
101
141
|
type: 'response-done',
|
|
102
142
|
responseId: event.response?.id ?? event.response_id,
|
|
103
143
|
status: event.response?.status ?? 'completed',
|
|
144
|
+
...(usage != null ? { usage } : {}),
|
|
104
145
|
raw,
|
|
105
146
|
};
|
|
147
|
+
}
|
|
106
148
|
|
|
107
149
|
// ── Output item lifecycle ───────────────────────────────────────
|
|
108
150
|
case 'response.output_item.added':
|
|
@@ -231,18 +273,86 @@ export function parseOpenAIRealtimeServerEvent(
|
|
|
231
273
|
}
|
|
232
274
|
}
|
|
233
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Maps OpenAI realtime `response.done` usage to normalized usage.
|
|
278
|
+
*
|
|
279
|
+
* Input buckets are reported gross (cache-inclusive) with the cached portion
|
|
280
|
+
* surfaced separately, so consumers can apply their own cached-vs-uncached
|
|
281
|
+
* billing split without re-reading `raw`.
|
|
282
|
+
*/
|
|
283
|
+
function mapResponseUsage(
|
|
284
|
+
usage: OpenAIRealtimeResponseUsage | undefined,
|
|
285
|
+
): RealtimeModelV4Usage | undefined {
|
|
286
|
+
if (usage == null) return undefined;
|
|
287
|
+
|
|
288
|
+
const input = usage.input_token_details;
|
|
289
|
+
const output = usage.output_token_details;
|
|
290
|
+
const cached = input?.cached_tokens_details;
|
|
291
|
+
|
|
292
|
+
return compactUsage({
|
|
293
|
+
inputTokens: usage.input_tokens,
|
|
294
|
+
outputTokens: usage.output_tokens,
|
|
295
|
+
cachedInputTokens: input?.cached_tokens,
|
|
296
|
+
inputAudioTokens: input?.audio_tokens,
|
|
297
|
+
inputTextTokens: input?.text_tokens,
|
|
298
|
+
outputAudioTokens: output?.audio_tokens,
|
|
299
|
+
outputTextTokens: output?.text_tokens,
|
|
300
|
+
cachedInputAudioTokens: cached?.audio_tokens,
|
|
301
|
+
cachedInputTextTokens: cached?.text_tokens,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Maps OpenAI realtime transcription-completed usage to normalized usage.
|
|
307
|
+
* Duration-billed transcription reports `seconds`; token-billed reports tokens.
|
|
308
|
+
*/
|
|
309
|
+
function mapTranscriptionUsage(
|
|
310
|
+
usage: OpenAIRealtimeTranscriptionUsage | undefined,
|
|
311
|
+
): RealtimeModelV4Usage | undefined {
|
|
312
|
+
if (usage == null) return undefined;
|
|
313
|
+
|
|
314
|
+
if (usage.type === 'duration') {
|
|
315
|
+
return usage.seconds != null ? { seconds: usage.seconds } : undefined;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const input = usage.input_token_details;
|
|
319
|
+
return compactUsage({
|
|
320
|
+
inputTokens: usage.input_tokens,
|
|
321
|
+
outputTokens: usage.output_tokens,
|
|
322
|
+
inputAudioTokens: input?.audio_tokens,
|
|
323
|
+
inputTextTokens: input?.text_tokens,
|
|
324
|
+
outputTextTokens: usage.output_tokens,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Drop `undefined` fields; return `undefined` when nothing is observable. */
|
|
329
|
+
function compactUsage(
|
|
330
|
+
usage: RealtimeModelV4Usage,
|
|
331
|
+
): RealtimeModelV4Usage | undefined {
|
|
332
|
+
const result: RealtimeModelV4Usage = {};
|
|
333
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
334
|
+
if (value != null) {
|
|
335
|
+
result[key as keyof RealtimeModelV4Usage] = value;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
234
341
|
/**
|
|
235
342
|
* Serializes a normalized client event into OpenAI's Realtime API format.
|
|
236
343
|
*/
|
|
237
344
|
export function serializeOpenAIRealtimeClientEvent(
|
|
238
345
|
event: RealtimeModelV4ClientEvent,
|
|
239
346
|
modelId: string,
|
|
347
|
+
options?: OpenAIRealtimeSessionOptions,
|
|
240
348
|
): unknown {
|
|
349
|
+
assertSupportedOpenAIIntent(options?.intent);
|
|
350
|
+
|
|
241
351
|
switch (event.type) {
|
|
242
352
|
case 'session-update':
|
|
243
353
|
return {
|
|
244
354
|
type: 'session.update',
|
|
245
|
-
session: buildOpenAISessionConfig(event.config, modelId),
|
|
355
|
+
session: buildOpenAISessionConfig(event.config, modelId, options),
|
|
246
356
|
...(event.eventId != null ? { event_id: event.eventId } : {}),
|
|
247
357
|
};
|
|
248
358
|
|
|
@@ -332,23 +442,43 @@ export function serializeOpenAIRealtimeClientEvent(
|
|
|
332
442
|
export function buildOpenAISessionConfig(
|
|
333
443
|
config: RealtimeModelV4SessionConfig,
|
|
334
444
|
modelId: string,
|
|
445
|
+
options?: OpenAIRealtimeSessionOptions,
|
|
335
446
|
): Record<string, unknown> {
|
|
447
|
+
const intent = options?.intent ?? 'conversation';
|
|
448
|
+
assertSupportedOpenAIIntent(intent);
|
|
449
|
+
if (intent === 'transcription') {
|
|
450
|
+
for (const key of [
|
|
451
|
+
'instructions',
|
|
452
|
+
'voice',
|
|
453
|
+
'outputModalities',
|
|
454
|
+
'outputAudioFormat',
|
|
455
|
+
'outputAudioTranscription',
|
|
456
|
+
'tools',
|
|
457
|
+
] as const) {
|
|
458
|
+
if (config[key] != null)
|
|
459
|
+
throw new Error(
|
|
460
|
+
`OpenAI realtime transcription sessions do not support '${key}'.`,
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
336
465
|
const session: Record<string, unknown> = {
|
|
337
|
-
type: 'realtime',
|
|
338
|
-
model: modelId,
|
|
466
|
+
type: intent === 'transcription' ? 'transcription' : 'realtime',
|
|
467
|
+
...(intent === 'conversation' ? { model: modelId } : {}),
|
|
339
468
|
};
|
|
340
469
|
|
|
341
|
-
if (config.instructions != null) {
|
|
470
|
+
if (intent === 'conversation' && config.instructions != null) {
|
|
342
471
|
session.instructions = config.instructions;
|
|
343
472
|
}
|
|
344
473
|
|
|
345
|
-
if (config.outputModalities != null) {
|
|
474
|
+
if (intent === 'conversation' && config.outputModalities != null) {
|
|
346
475
|
session.output_modalities = config.outputModalities;
|
|
347
476
|
}
|
|
348
477
|
|
|
349
478
|
const audio: Record<string, unknown> = {};
|
|
350
479
|
|
|
351
480
|
if (
|
|
481
|
+
intent === 'transcription' ||
|
|
352
482
|
config.inputAudioFormat != null ||
|
|
353
483
|
config.inputAudioTranscription != null ||
|
|
354
484
|
config.turnDetection != null
|
|
@@ -387,7 +517,17 @@ export function buildOpenAISessionConfig(
|
|
|
387
517
|
}
|
|
388
518
|
}
|
|
389
519
|
|
|
390
|
-
if (
|
|
520
|
+
if (intent === 'transcription') {
|
|
521
|
+
input.transcription = {
|
|
522
|
+
model: config.inputAudioTranscription?.model ?? modelId,
|
|
523
|
+
...(config.inputAudioTranscription?.language != null
|
|
524
|
+
? { language: config.inputAudioTranscription.language }
|
|
525
|
+
: {}),
|
|
526
|
+
...(config.inputAudioTranscription?.prompt != null
|
|
527
|
+
? { prompt: config.inputAudioTranscription.prompt }
|
|
528
|
+
: {}),
|
|
529
|
+
};
|
|
530
|
+
} else if (config.inputAudioTranscription != null) {
|
|
391
531
|
input.transcription = {
|
|
392
532
|
model: config.inputAudioTranscription.model ?? 'gpt-realtime-whisper',
|
|
393
533
|
...(config.inputAudioTranscription.language != null
|
|
@@ -402,7 +542,10 @@ export function buildOpenAISessionConfig(
|
|
|
402
542
|
audio.input = input;
|
|
403
543
|
}
|
|
404
544
|
|
|
405
|
-
if (
|
|
545
|
+
if (
|
|
546
|
+
intent === 'conversation' &&
|
|
547
|
+
(config.outputAudioFormat != null || config.voice != null)
|
|
548
|
+
) {
|
|
406
549
|
const output: Record<string, unknown> = {};
|
|
407
550
|
|
|
408
551
|
if (config.outputAudioFormat != null) {
|
|
@@ -425,7 +568,11 @@ export function buildOpenAISessionConfig(
|
|
|
425
568
|
session.audio = audio;
|
|
426
569
|
}
|
|
427
570
|
|
|
428
|
-
if (
|
|
571
|
+
if (
|
|
572
|
+
intent === 'conversation' &&
|
|
573
|
+
config.tools != null &&
|
|
574
|
+
config.tools.length > 0
|
|
575
|
+
) {
|
|
429
576
|
session.tools = config.tools.map(tool => ({
|
|
430
577
|
type: tool.type,
|
|
431
578
|
name: tool.name,
|
|
@@ -441,3 +588,17 @@ export function buildOpenAISessionConfig(
|
|
|
441
588
|
|
|
442
589
|
return session;
|
|
443
590
|
}
|
|
591
|
+
|
|
592
|
+
function assertSupportedOpenAIIntent(
|
|
593
|
+
intent: RealtimeModelV4SessionIntent | undefined,
|
|
594
|
+
): void {
|
|
595
|
+
if (
|
|
596
|
+
intent != null &&
|
|
597
|
+
intent !== 'conversation' &&
|
|
598
|
+
intent !== 'transcription'
|
|
599
|
+
) {
|
|
600
|
+
throw new Error(
|
|
601
|
+
`OpenAI realtime session intent '${intent}' is not supported.`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Resolvable,
|
|
3
3
|
resolve,
|
|
4
|
+
resolveWith,
|
|
5
|
+
removeUndefinedEntries,
|
|
4
6
|
type FetchFunction,
|
|
5
7
|
} from '@ai-sdk/provider-utils';
|
|
6
8
|
import type {
|
|
@@ -8,8 +10,10 @@ import type {
|
|
|
8
10
|
Experimental_RealtimeModelV4ClientEvent as RealtimeModelV4ClientEvent,
|
|
9
11
|
Experimental_RealtimeModelV4ClientSecretOptions as RealtimeModelV4ClientSecretOptions,
|
|
10
12
|
Experimental_RealtimeModelV4ClientSecretResult as RealtimeModelV4ClientSecretResult,
|
|
13
|
+
Experimental_RealtimeModelV4ServerConnection as RealtimeModelV4ServerConnection,
|
|
11
14
|
Experimental_RealtimeModelV4ServerEvent as RealtimeModelV4ServerEvent,
|
|
12
15
|
Experimental_RealtimeModelV4SessionConfig as RealtimeModelV4SessionConfig,
|
|
16
|
+
Experimental_RealtimeModelV4SessionIntent as RealtimeModelV4SessionIntent,
|
|
13
17
|
} from '@ai-sdk/provider';
|
|
14
18
|
import {
|
|
15
19
|
buildOpenAISessionConfig,
|
|
@@ -26,6 +30,11 @@ export type OpenAIRealtimeModelConfig = {
|
|
|
26
30
|
|
|
27
31
|
export class OpenAIRealtimeModel implements RealtimeModelV4 {
|
|
28
32
|
readonly specificationVersion = 'v4' as const;
|
|
33
|
+
readonly capabilities = {
|
|
34
|
+
conversation: 'turn-based',
|
|
35
|
+
transports: ['websocket'],
|
|
36
|
+
connections: ['client-secret-websocket', 'server-websocket'],
|
|
37
|
+
} as const;
|
|
29
38
|
readonly provider: string;
|
|
30
39
|
readonly modelId: string;
|
|
31
40
|
|
|
@@ -99,17 +108,62 @@ export class OpenAIRealtimeModel implements RealtimeModelV4 {
|
|
|
99
108
|
};
|
|
100
109
|
}
|
|
101
110
|
|
|
111
|
+
getServerWebSocketConfig(options?: {
|
|
112
|
+
intent?: RealtimeModelV4SessionIntent;
|
|
113
|
+
}):
|
|
114
|
+
| RealtimeModelV4ServerConnection
|
|
115
|
+
| PromiseLike<RealtimeModelV4ServerConnection> {
|
|
116
|
+
return resolveWith(this.config.headers, headers => ({
|
|
117
|
+
url: buildServerUrl(
|
|
118
|
+
this.config.baseURL,
|
|
119
|
+
this.modelId,
|
|
120
|
+
options?.intent ?? 'conversation',
|
|
121
|
+
),
|
|
122
|
+
headers: removeUndefinedEntries(headers),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
|
|
102
126
|
parseServerEvent(raw: unknown): RealtimeModelV4ServerEvent {
|
|
103
127
|
return parseOpenAIRealtimeServerEvent(raw);
|
|
104
128
|
}
|
|
105
129
|
|
|
106
|
-
serializeClientEvent(
|
|
107
|
-
|
|
130
|
+
serializeClientEvent(
|
|
131
|
+
event: RealtimeModelV4ClientEvent,
|
|
132
|
+
options?: { intent?: RealtimeModelV4SessionIntent },
|
|
133
|
+
): unknown {
|
|
134
|
+
return serializeOpenAIRealtimeClientEvent(event, this.modelId, options);
|
|
108
135
|
}
|
|
109
136
|
|
|
110
137
|
buildSessionConfig(
|
|
111
138
|
config: RealtimeModelV4SessionConfig,
|
|
139
|
+
options?: { intent?: RealtimeModelV4SessionIntent },
|
|
112
140
|
): Record<string, unknown> {
|
|
113
|
-
return buildOpenAISessionConfig(config, this.modelId);
|
|
141
|
+
return buildOpenAISessionConfig(config, this.modelId, options);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Builds the upstream realtime WebSocket URL for a server-initiated connection.
|
|
147
|
+
* Transcription keeps the model in `session.update`; a `?model=` here would
|
|
148
|
+
* open a voice session instead.
|
|
149
|
+
*/
|
|
150
|
+
function buildServerUrl(
|
|
151
|
+
baseURL: string,
|
|
152
|
+
modelId: string,
|
|
153
|
+
intent: RealtimeModelV4SessionIntent,
|
|
154
|
+
): string {
|
|
155
|
+
const url = new URL(baseURL);
|
|
156
|
+
const protocol = url.protocol === 'http:' ? 'ws:' : 'wss:';
|
|
157
|
+
const base = `${protocol}//${url.host}${url.pathname.replace(/\/$/, '')}`;
|
|
158
|
+
|
|
159
|
+
switch (intent) {
|
|
160
|
+
case 'transcription':
|
|
161
|
+
return `${base}/realtime?intent=transcription`;
|
|
162
|
+
case 'conversation':
|
|
163
|
+
return `${base}/realtime?model=${encodeURIComponent(modelId)}`;
|
|
164
|
+
default:
|
|
165
|
+
throw new Error(
|
|
166
|
+
`OpenAI realtime session intent '${intent}' is not supported.`,
|
|
167
|
+
);
|
|
114
168
|
}
|
|
115
169
|
}
|
|
@@ -647,7 +647,24 @@ export type OpenAIResponsesTool =
|
|
|
647
647
|
}
|
|
648
648
|
| {
|
|
649
649
|
type: 'code_interpreter';
|
|
650
|
-
container:
|
|
650
|
+
container:
|
|
651
|
+
| string
|
|
652
|
+
| {
|
|
653
|
+
type: 'auto';
|
|
654
|
+
file_ids: string[] | undefined;
|
|
655
|
+
memory_limit?: '1g' | '4g' | '16g' | '64g';
|
|
656
|
+
network_policy?:
|
|
657
|
+
| { type: 'disabled' }
|
|
658
|
+
| {
|
|
659
|
+
type: 'allowlist';
|
|
660
|
+
allowed_domains: string[];
|
|
661
|
+
domain_secrets?: Array<{
|
|
662
|
+
domain: string;
|
|
663
|
+
name: string;
|
|
664
|
+
value: string;
|
|
665
|
+
}>;
|
|
666
|
+
};
|
|
667
|
+
};
|
|
651
668
|
}
|
|
652
669
|
| {
|
|
653
670
|
type: 'file_search';
|
|
@@ -712,6 +729,7 @@ export type OpenAIResponsesTool =
|
|
|
712
729
|
| undefined;
|
|
713
730
|
server_description: string | undefined;
|
|
714
731
|
server_url: string | undefined;
|
|
732
|
+
tunnel_id: string | undefined;
|
|
715
733
|
}
|
|
716
734
|
| {
|
|
717
735
|
type: 'custom';
|
|
@@ -333,7 +333,23 @@ export async function prepareResponsesTools({
|
|
|
333
333
|
? { type: 'auto', file_ids: undefined }
|
|
334
334
|
: typeof args.container === 'string'
|
|
335
335
|
? args.container
|
|
336
|
-
: {
|
|
336
|
+
: {
|
|
337
|
+
type: 'auto',
|
|
338
|
+
file_ids: args.container.fileIds,
|
|
339
|
+
memory_limit: args.container.memoryLimit,
|
|
340
|
+
network_policy:
|
|
341
|
+
args.container.networkPolicy == null
|
|
342
|
+
? undefined
|
|
343
|
+
: args.container.networkPolicy.type === 'disabled'
|
|
344
|
+
? { type: 'disabled' }
|
|
345
|
+
: {
|
|
346
|
+
type: 'allowlist',
|
|
347
|
+
allowed_domains:
|
|
348
|
+
args.container.networkPolicy.allowedDomains,
|
|
349
|
+
domain_secrets:
|
|
350
|
+
args.container.networkPolicy.domainSecrets,
|
|
351
|
+
},
|
|
352
|
+
},
|
|
337
353
|
});
|
|
338
354
|
break;
|
|
339
355
|
}
|
|
@@ -407,6 +423,7 @@ export async function prepareResponsesTools({
|
|
|
407
423
|
require_approval: requireApprovalParam ?? 'never',
|
|
408
424
|
server_description: args.serverDescription,
|
|
409
425
|
server_url: args.serverUrl,
|
|
426
|
+
tunnel_id: args.tunnelId,
|
|
410
427
|
});
|
|
411
428
|
|
|
412
429
|
break;
|
|
@@ -37,6 +37,25 @@ export const codeInterpreterArgsSchema = lazySchema(() =>
|
|
|
37
37
|
z.string(),
|
|
38
38
|
z.object({
|
|
39
39
|
fileIds: z.array(z.string()).optional(),
|
|
40
|
+
memoryLimit: z.enum(['1g', '4g', '16g', '64g']).optional(),
|
|
41
|
+
networkPolicy: z
|
|
42
|
+
.discriminatedUnion('type', [
|
|
43
|
+
z.object({ type: z.literal('disabled') }),
|
|
44
|
+
z.object({
|
|
45
|
+
type: z.literal('allowlist'),
|
|
46
|
+
allowedDomains: z.array(z.string()),
|
|
47
|
+
domainSecrets: z
|
|
48
|
+
.array(
|
|
49
|
+
z.object({
|
|
50
|
+
domain: z.string(),
|
|
51
|
+
name: z.string(),
|
|
52
|
+
value: z.string(),
|
|
53
|
+
}),
|
|
54
|
+
)
|
|
55
|
+
.optional(),
|
|
56
|
+
}),
|
|
57
|
+
])
|
|
58
|
+
.optional(),
|
|
40
59
|
}),
|
|
41
60
|
])
|
|
42
61
|
.optional(),
|
|
@@ -48,9 +67,26 @@ type CodeInterpreterArgs = {
|
|
|
48
67
|
/**
|
|
49
68
|
* The code interpreter container.
|
|
50
69
|
* Can be a container ID
|
|
51
|
-
* or an object that
|
|
70
|
+
* or an object that configures uploaded file IDs, the memory limit,
|
|
71
|
+
* and the network policy for a new container.
|
|
52
72
|
*/
|
|
53
|
-
container?:
|
|
73
|
+
container?:
|
|
74
|
+
| string
|
|
75
|
+
| {
|
|
76
|
+
fileIds?: string[];
|
|
77
|
+
memoryLimit?: '1g' | '4g' | '16g' | '64g';
|
|
78
|
+
networkPolicy?:
|
|
79
|
+
| { type: 'disabled' }
|
|
80
|
+
| {
|
|
81
|
+
type: 'allowlist';
|
|
82
|
+
allowedDomains: string[];
|
|
83
|
+
domainSecrets?: Array<{
|
|
84
|
+
domain: string;
|
|
85
|
+
name: string;
|
|
86
|
+
value: string;
|
|
87
|
+
}>;
|
|
88
|
+
};
|
|
89
|
+
};
|
|
54
90
|
};
|
|
55
91
|
|
|
56
92
|
export const codeInterpreterToolFactory = createProviderExecutedToolFactory<
|
package/src/tool/mcp.ts
CHANGED
|
@@ -51,10 +51,16 @@ export const mcpArgsSchema = lazySchema(() =>
|
|
|
51
51
|
.optional(),
|
|
52
52
|
serverDescription: z.string().optional(),
|
|
53
53
|
serverUrl: z.string().optional(),
|
|
54
|
+
tunnelId: z.string().optional(),
|
|
54
55
|
})
|
|
55
56
|
.refine(
|
|
56
|
-
v => v.serverUrl != null || v.connectorId != null,
|
|
57
|
-
'One of serverUrl or
|
|
57
|
+
v => v.serverUrl != null || v.connectorId != null || v.tunnelId != null,
|
|
58
|
+
'One of serverUrl, connectorId, or tunnelId must be provided.',
|
|
59
|
+
)
|
|
60
|
+
.refine(
|
|
61
|
+
v =>
|
|
62
|
+
v.tunnelId == null || (v.serverUrl == null && v.connectorId == null),
|
|
63
|
+
'tunnelId cannot be combined with serverUrl or connectorId.',
|
|
58
64
|
),
|
|
59
65
|
),
|
|
60
66
|
);
|
|
@@ -74,7 +80,7 @@ export const mcpOutputSchema = lazySchema(() =>
|
|
|
74
80
|
),
|
|
75
81
|
);
|
|
76
82
|
|
|
77
|
-
type
|
|
83
|
+
type McpCommonArgs = {
|
|
78
84
|
/** A label for this MCP server, used to identify it in tool calls. */
|
|
79
85
|
serverLabel: string;
|
|
80
86
|
/** List of allowed tool names or a filter object. */
|
|
@@ -84,10 +90,8 @@ type McpArgs = {
|
|
|
84
90
|
readOnly?: boolean;
|
|
85
91
|
toolNames?: string[];
|
|
86
92
|
};
|
|
87
|
-
/** OAuth access token usable with the remote MCP server or connector. */
|
|
93
|
+
/** OAuth access token usable with the remote MCP server or connector (including tunneled servers). */
|
|
88
94
|
authorization?: string;
|
|
89
|
-
/** Identifier for a service connector. */
|
|
90
|
-
connectorId?: string;
|
|
91
95
|
/** Optional HTTP headers to send to the MCP server. */
|
|
92
96
|
headers?: Record<string, string>;
|
|
93
97
|
/**
|
|
@@ -103,10 +107,25 @@ type McpArgs = {
|
|
|
103
107
|
};
|
|
104
108
|
/** Optional description of the MCP server. */
|
|
105
109
|
serverDescription?: string;
|
|
106
|
-
/** URL for the MCP server. One of serverUrl or connectorId must be provided. */
|
|
107
|
-
serverUrl?: string;
|
|
108
110
|
};
|
|
109
111
|
|
|
112
|
+
type McpArgs = McpCommonArgs &
|
|
113
|
+
(
|
|
114
|
+
| {
|
|
115
|
+
/** URL for the MCP server. */
|
|
116
|
+
serverUrl?: string;
|
|
117
|
+
/** Identifier for a service connector. */
|
|
118
|
+
connectorId?: string;
|
|
119
|
+
tunnelId?: never;
|
|
120
|
+
}
|
|
121
|
+
| {
|
|
122
|
+
/** Identifier for an OpenAI Secure MCP tunnel. */
|
|
123
|
+
tunnelId: string;
|
|
124
|
+
serverUrl?: never;
|
|
125
|
+
connectorId?: never;
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
110
129
|
export const mcpToolFactory = createProviderExecutedToolFactory<
|
|
111
130
|
{},
|
|
112
131
|
{
|