@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.
@@ -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
- serializeClientEvent(event: RealtimeModelV4ClientEvent): unknown {
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: {
@@ -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 service connectors.
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/connector.
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
- * // param requireApproval - Approval policy ('always'|'never'|filter object). (Removed - always 'never')
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?: { id?: string; status?: string };
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 (config.inputAudioTranscription != null) {
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 (config.outputAudioFormat != null || config.voice != null) {
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 (config.tools != null && config.tools.length > 0) {
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(event: RealtimeModelV4ClientEvent): unknown {
107
- return serializeOpenAIRealtimeClientEvent(event, this.modelId);
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: string | { type: 'auto'; file_ids: string[] | undefined };
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
- : { type: 'auto', file_ids: args.container.fileIds },
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 specifies uploaded file IDs to make available to your code.
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?: string | { fileIds?: string[] };
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 connectorId must be provided.',
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 McpArgs = {
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
  {