@zhivex-ai/openai 0.7.2 → 0.8.1

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/dist/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import { toJSONSchema, z } from "zod";
2
- import { CallbackRealtimeSession, ConfigurationError, ProviderHTTPError, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, encodeAudioFrame, encodeMediaFrame, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, toToolSet, toolResultPayload, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
2
+ import { CallbackRealtimeSession, ConfigurationError, ProviderHTTPError, readBodyWithLimit, readErrorBodyWithLimit, readJsonWithLimit, resolveAudioResponseLimits, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, encodeAudioFrame, encodeMediaFrame, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, toToolSet, toolResultPayload, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
3
3
  const openAIResponsesToolMetadataKey = "openai.responses_tool_type";
4
+ const openAIResponsesFunctionConfigMetadataKey = "openai.responses_function_config";
5
+ const openAIResponsesFunctionConfig = (tool) => {
6
+ const config = tool.metadata?.[openAIResponsesFunctionConfigMetadataKey];
7
+ return config && typeof config === "object" && !Array.isArray(config) ? config : undefined;
8
+ };
4
9
  const openAILocalResponsesToolType = (tool) => {
5
10
  const type = tool.metadata?.[openAIResponsesToolMetadataKey];
6
11
  return typeof type === "string" ? type : undefined;
@@ -53,30 +58,51 @@ const capabilities = {
53
58
  }
54
59
  };
55
60
  const normalizeModelId = (modelId) => modelId.trim().toLowerCase();
56
- const supportsOpenAIToolSearch = (modelId) => {
57
- const normalized = normalizeModelId(modelId);
58
- return /^gpt-5\.4(?:$|-20|-pro)/.test(normalized);
59
- };
60
- const supportsOpenAIComputerUse = (modelId) => {
61
- const normalized = normalizeModelId(modelId);
62
- return /^(?:gpt-5\.5|gpt-5\.4)(?:$|-20|-pro|-mini)/.test(normalized);
63
- };
64
- const supportsOpenAIHostedHarnessTools = (modelId) => /^gpt-5\.4(?:$|-)/.test(normalizeModelId(modelId));
61
+ const isOpenAIGpt56Model = (modelId) => /^gpt-5\.6(?:$|-(?:sol|terra|luna)(?:-|$)|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
62
+ const isOpenAIGpt55BaseModel = (modelId) => /^gpt-5\.5(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
63
+ const isOpenAIGpt55ProModel = (modelId) => /^gpt-5\.5-pro(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
64
+ const isOpenAIGpt54BaseModel = (modelId) => /^gpt-5\.4(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
65
+ const isOpenAIGpt54MiniModel = (modelId) => /^gpt-5\.4-mini(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
66
+ const isOpenAIGpt54NanoModel = (modelId) => /^gpt-5\.4-nano(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
67
+ const isOpenAIGpt54ProModel = (modelId) => /^gpt-5\.4-pro(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
68
+ const supportsOpenAIToolSearch = (modelId) => isOpenAIGpt56Model(modelId) || isOpenAIGpt55BaseModel(modelId) || isOpenAIGpt54BaseModel(modelId) || isOpenAIGpt54MiniModel(modelId);
69
+ const supportsOpenAIComputerUse = (modelId) => isOpenAIGpt56Model(modelId) || isOpenAIGpt55BaseModel(modelId) || isOpenAIGpt54BaseModel(modelId) || isOpenAIGpt54MiniModel(modelId);
70
+ const supportsOpenAIShell = (modelId) => isOpenAIGpt56Model(modelId) ||
71
+ isOpenAIGpt55BaseModel(modelId) ||
72
+ isOpenAIGpt55ProModel(modelId) ||
73
+ isOpenAIGpt54BaseModel(modelId) ||
74
+ isOpenAIGpt54MiniModel(modelId) ||
75
+ isOpenAIGpt54NanoModel(modelId) ||
76
+ isOpenAIGpt54ProModel(modelId);
77
+ const supportsOpenAIApplyPatchAndSkills = (modelId) => isOpenAIGpt56Model(modelId) ||
78
+ isOpenAIGpt55BaseModel(modelId) ||
79
+ isOpenAIGpt54BaseModel(modelId) ||
80
+ isOpenAIGpt54MiniModel(modelId) ||
81
+ isOpenAIGpt54NanoModel(modelId);
65
82
  const supportsOpenAIChatAudio = (modelId) => {
66
83
  const normalized = normalizeModelId(modelId);
67
84
  return /^(?:gpt-audio(?:-|$)|gpt-4o(?:-mini)?-audio-preview(?:-|$))/.test(normalized);
68
85
  };
69
86
  const modelCapabilities = (modelId) => ({
70
87
  ...capabilities,
88
+ explicitPromptCaching: isOpenAIGpt56Model(modelId),
89
+ files: isOpenAIGpt56Model(modelId),
90
+ reasoningEfforts: isOpenAIGpt56Model(modelId)
91
+ ? ["none", "low", "medium", "high", "xhigh", "max"]
92
+ : undefined,
93
+ reasoningModes: isOpenAIGpt56Model(modelId) ? ["standard", "pro"] : undefined,
94
+ reasoningContexts: isOpenAIGpt56Model(modelId) ? ["auto", "current_turn", "all_turns"] : undefined,
71
95
  audioInput: supportsOpenAIChatAudio(modelId),
72
96
  audioOutput: supportsOpenAIChatAudio(modelId),
73
97
  agentCapabilities: {
74
98
  ...capabilities.agentCapabilities,
75
99
  computerUse: supportsOpenAIComputerUse(modelId),
76
- shell: supportsOpenAIHostedHarnessTools(modelId),
77
- applyPatch: supportsOpenAIHostedHarnessTools(modelId),
78
- skills: supportsOpenAIHostedHarnessTools(modelId),
79
- toolSearch: supportsOpenAIToolSearch(modelId)
100
+ shell: supportsOpenAIShell(modelId),
101
+ applyPatch: supportsOpenAIApplyPatchAndSkills(modelId),
102
+ skills: supportsOpenAIApplyPatchAndSkills(modelId),
103
+ toolSearch: supportsOpenAIToolSearch(modelId),
104
+ programmaticToolCalling: isOpenAIGpt56Model(modelId),
105
+ multiAgent: isOpenAIGpt56Model(modelId)
80
106
  }
81
107
  });
82
108
  const transcriptionCapabilities = {
@@ -152,6 +178,44 @@ const jsonHeaders = (apiKey) => ({
152
178
  "content-type": "application/json",
153
179
  authorization: `Bearer ${apiKey}`
154
180
  });
181
+ const resolveOpenAILanguageRequestOptions = (providerOptions, apiKey) => {
182
+ const bodyOptions = { ...(providerOptions ?? {}) };
183
+ const apiMode = bodyOptions.apiMode ?? "auto";
184
+ const customHeaders = { ...(bodyOptions.headers ?? {}) };
185
+ const betaValues = new Set(bodyOptions.betas ?? []);
186
+ const multiAgentEnabled = bodyOptions.multi_agent?.enabled === true;
187
+ if (multiAgentEnabled) {
188
+ betaValues.add("responses_multi_agent=v1");
189
+ }
190
+ const existingBetaHeaderKey = Object.keys(customHeaders).find((key) => key.toLowerCase() === "openai-beta");
191
+ if (existingBetaHeaderKey) {
192
+ for (const value of customHeaders[existingBetaHeaderKey]?.split(",") ?? []) {
193
+ if (value.trim()) {
194
+ betaValues.add(value.trim());
195
+ }
196
+ }
197
+ delete customHeaders[existingBetaHeaderKey];
198
+ }
199
+ delete bodyOptions.apiMode;
200
+ delete bodyOptions.headers;
201
+ delete bodyOptions.betas;
202
+ return {
203
+ apiMode,
204
+ bodyOptions,
205
+ multiAgentEnabled,
206
+ headers: {
207
+ ...jsonHeaders(apiKey),
208
+ ...customHeaders,
209
+ ...(betaValues.size ? { "OpenAI-Beta": [...betaValues].join(",") } : {})
210
+ }
211
+ };
212
+ };
213
+ const openAIResponsesBodyOptions = (options, modelId) => options.store === false && isOpenAIGpt56Model(modelId)
214
+ ? {
215
+ ...options,
216
+ include: [...new Set([...(options.include ?? []), "reasoning.encrypted_content"])]
217
+ }
218
+ : options;
155
219
  const getRequestOptions = (input) => withTimeoutSignal(input);
156
220
  const toUint8Array = (data) => {
157
221
  if (data instanceof Uint8Array) {
@@ -163,9 +227,12 @@ const toUint8Array = (data) => {
163
227
  return Uint8Array.from(Buffer.from(data, "base64"));
164
228
  };
165
229
  const toBase64 = (data) => typeof data === "string" ? data : Buffer.from(data instanceof Uint8Array ? data : new Uint8Array(data)).toString("base64");
166
- const createAudioFile = (audio) => new File([toUint8Array(audio.data).buffer], audio.filename ?? "audio", {
167
- type: audio.mediaType
168
- });
230
+ const createAudioFile = (audio) => {
231
+ const bytes = toUint8Array(audio.data);
232
+ return new File([bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)], audio.filename ?? "audio", {
233
+ type: audio.mediaType
234
+ });
235
+ };
169
236
  const inferOpenAIAudioFormat = (mediaType, explicitFormat) => {
170
237
  if (explicitFormat) {
171
238
  return explicitFormat;
@@ -191,41 +258,100 @@ const inferOpenAIAudioFormat = (mediaType, explicitFormat) => {
191
258
  }
192
259
  return normalized?.replace(/^audio\//, "") || mediaType;
193
260
  };
194
- const parseJson = async (response) => {
261
+ const parseJson = async (response, options = {}) => {
195
262
  if (!response.ok) {
196
- const body = await response.text();
263
+ const body = await readErrorBodyWithLimit(response, options.errorBodyBytes);
197
264
  throw new ProviderHTTPError(`OpenAI request failed with status ${response.status}.`, response.status, {
198
265
  responseBody: body
199
266
  });
200
267
  }
201
- return response.json();
268
+ return options.maxBytes
269
+ ? readJsonWithLimit(response, {
270
+ maxBytes: options.maxBytes,
271
+ provider: options.provider ?? "openai",
272
+ endpoint: options.endpoint,
273
+ abort: options.abort
274
+ })
275
+ : response.json();
276
+ };
277
+ const isOpenAIPromptCacheBreakpointPart = (part) => part.type === "provider-data" &&
278
+ part.provider === "openai" &&
279
+ part.data !== null &&
280
+ typeof part.data === "object" &&
281
+ part.data.type === "prompt_cache_breakpoint";
282
+ const promptCacheBreakpointForContentPart = (part) => {
283
+ const metadata = part
284
+ .providerMetadata;
285
+ const openAIMetadata = metadata?.openai && typeof metadata.openai === "object" && !Array.isArray(metadata.openai)
286
+ ? metadata.openai
287
+ : metadata;
288
+ const breakpoint = openAIMetadata?.prompt_cache_breakpoint;
289
+ return breakpoint && typeof breakpoint === "object" && !Array.isArray(breakpoint)
290
+ ? { prompt_cache_breakpoint: { mode: "explicit" } }
291
+ : {};
292
+ };
293
+ const imageDetailForPart = (part) => {
294
+ const metadata = part.providerMetadata;
295
+ const detail = metadata?.openaiDetail ??
296
+ (metadata?.openai && typeof metadata.openai === "object" && !Array.isArray(metadata.openai)
297
+ ? metadata.openai.detail
298
+ : undefined);
299
+ return detail === "auto" || detail === "low" || detail === "high" || detail === "original" ? detail : undefined;
300
+ };
301
+ const markLastContentBlockForPromptCaching = (content) => {
302
+ const last = content.at(-1);
303
+ if (!last) {
304
+ throw new ConfigurationError("OpenAI prompt cache breakpoints must follow a cacheable content part.");
305
+ }
306
+ last.prompt_cache_breakpoint = { mode: "explicit" };
202
307
  };
203
308
  const mapContentParts = (message) => {
204
- const textParts = message.parts.filter((part) => part.type === "text");
205
- const imageParts = message.parts.filter((part) => part.type === "image");
206
- const audioParts = message.parts.filter((part) => part.type === "audio");
207
- if (!imageParts.length && !audioParts.length) {
208
- return textParts.map((part) => part.text).join("");
309
+ const hasRichContent = message.parts.some((part) => part.type === "image" ||
310
+ part.type === "audio" ||
311
+ part.type === "file" ||
312
+ Object.keys(promptCacheBreakpointForContentPart(part)).length > 0 ||
313
+ isOpenAIPromptCacheBreakpointPart(part));
314
+ if (!hasRichContent) {
315
+ return message.parts
316
+ .filter((part) => part.type === "text")
317
+ .map((part) => part.text)
318
+ .join("");
319
+ }
320
+ const content = [];
321
+ for (const part of message.parts) {
322
+ if (part.type === "text") {
323
+ content.push({ type: "text", text: part.text, ...promptCacheBreakpointForContentPart(part) });
324
+ }
325
+ else if (part.type === "image") {
326
+ const detail = imageDetailForPart(part);
327
+ content.push({
328
+ type: "image_url",
329
+ image_url: { url: part.image, ...(detail ? { detail } : {}) },
330
+ ...promptCacheBreakpointForContentPart(part)
331
+ });
332
+ }
333
+ else if (part.type === "file") {
334
+ content.push({
335
+ type: "file",
336
+ file: { file_id: part.data },
337
+ ...promptCacheBreakpointForContentPart(part)
338
+ });
339
+ }
340
+ else if (part.type === "audio") {
341
+ content.push({
342
+ type: "input_audio",
343
+ input_audio: {
344
+ data: toBase64(part.data),
345
+ format: inferOpenAIAudioFormat(part.mediaType, part.format)
346
+ },
347
+ ...promptCacheBreakpointForContentPart(part)
348
+ });
349
+ }
350
+ else if (isOpenAIPromptCacheBreakpointPart(part)) {
351
+ markLastContentBlockForPromptCaching(content);
352
+ }
209
353
  }
210
- return [
211
- ...textParts.map((part) => ({
212
- type: "text",
213
- text: part.text
214
- })),
215
- ...imageParts.map((part) => ({
216
- type: "image_url",
217
- image_url: {
218
- url: part.image
219
- }
220
- })),
221
- ...audioParts.map((part) => ({
222
- type: "input_audio",
223
- input_audio: {
224
- data: toBase64(part.data),
225
- format: inferOpenAIAudioFormat(part.mediaType, part.format)
226
- }
227
- }))
228
- ];
354
+ return content;
229
355
  };
230
356
  const mapMessages = (messages) => messages.map((message) => {
231
357
  if (message.role === "tool") {
@@ -258,6 +384,13 @@ const mapMessages = (messages) => messages.map((message) => {
258
384
  return payload;
259
385
  });
260
386
  const hasResponsesOnlyTools = (tools) => Object.values(tools ?? {}).some((tool) => isHostedToolDefinition(tool) || (isCallableToolDefinition(tool) && openAILocalResponsesToolType(tool)));
387
+ const hasProgrammaticToolCalling = (tools) => Object.values(tools ?? {}).some((tool) => isHostedToolDefinition(tool)
388
+ ? tool.type === "programmatic_tool_calling"
389
+ : openAILocalResponsesToolType(tool) === "programmatic_tool_calling");
390
+ const localResponsesTools = (tools) => new Map(Object.values(tools ?? {})
391
+ .filter(isCallableToolDefinition)
392
+ .map((tool) => [openAILocalResponsesToolType(tool), tool.name])
393
+ .filter((entry) => Boolean(entry[0])));
261
394
  const assertResponsesToolsSupported = (modelId, tools) => {
262
395
  const currentCapabilities = modelCapabilities(modelId).agentCapabilities;
263
396
  for (const definition of Object.values(tools ?? {})) {
@@ -268,9 +401,26 @@ const assertResponsesToolsSupported = (modelId, tools) => {
268
401
  if (type === "computer_use_preview" && !currentCapabilities?.computerUse) {
269
402
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses computer_use tool.`);
270
403
  }
271
- if ((type === "shell" || type === "apply_patch") && !supportsOpenAIHostedHarnessTools(modelId)) {
404
+ if (type === "computer" && !currentCapabilities?.computerUse) {
405
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses computer tool.`);
406
+ }
407
+ if (type === "shell" && !supportsOpenAIShell(modelId)) {
408
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses ${type} tool.`);
409
+ }
410
+ if (type === "apply_patch" && !supportsOpenAIApplyPatchAndSkills(modelId)) {
272
411
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses ${type} tool.`);
273
412
  }
413
+ if (type === "skill" && !currentCapabilities?.skills) {
414
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses skills tool.`);
415
+ }
416
+ if (type === "programmatic_tool_calling" && !isOpenAIGpt56Model(modelId)) {
417
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support Programmatic Tool Calling.`);
418
+ }
419
+ }
420
+ };
421
+ const assertOpenAIResponsesOptionsSupported = (modelId, options) => {
422
+ if (options.multi_agent?.enabled && !isOpenAIGpt56Model(modelId)) {
423
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support Multi-agent.`);
274
424
  }
275
425
  };
276
426
  const normalizeWebSearchConfig = (config = {}) => ({
@@ -313,7 +463,8 @@ const mapResponsesTools = (input) => input
313
463
  type: "function",
314
464
  name: tool.name,
315
465
  description: tool.description,
316
- parameters: toJSONSchema(tool.schema)
466
+ parameters: toJSONSchema(tool.schema),
467
+ ...openAIResponsesFunctionConfig(tool)
317
468
  };
318
469
  }
319
470
  if (tool.provider && tool.provider !== "openai") {
@@ -339,6 +490,15 @@ const mapToolChoice = (toolChoice) => {
339
490
  }
340
491
  };
341
492
  };
493
+ const mapResponsesToolChoice = (toolChoice) => {
494
+ if (!toolChoice || typeof toolChoice === "string") {
495
+ return toolChoice;
496
+ }
497
+ return {
498
+ type: "function",
499
+ name: toolChoice.toolName
500
+ };
501
+ };
342
502
  const mapRealtimeProviderOptions = (providerOptions) => {
343
503
  if (!providerOptions) {
344
504
  return {};
@@ -592,18 +752,45 @@ const mapResponsesStructuredOutput = (input) => {
592
752
  }
593
753
  };
594
754
  };
595
- const mapReasoning = (input) => {
755
+ const getOpenAIReasoning = (input) => {
596
756
  if (!input.reasoning) {
597
- return {};
757
+ return undefined;
598
758
  }
599
759
  if (input.reasoning.budgetTokens !== undefined) {
600
760
  throw new UnsupportedFeatureError('Provider "openai" does not support "reasoning.budgetTokens".');
601
761
  }
762
+ return input.reasoning;
763
+ };
764
+ const mapChatReasoning = (input) => {
765
+ const reasoning = getOpenAIReasoning(input);
766
+ if (!reasoning) {
767
+ return {};
768
+ }
769
+ if (reasoning.mode !== undefined || reasoning.context !== undefined) {
770
+ throw new UnsupportedFeatureError('Provider "openai" only supports "reasoning.mode" and "reasoning.context" through the Responses API.');
771
+ }
602
772
  return {
603
- reasoning_effort: input.reasoning.effort,
773
+ ...(reasoning.effort !== undefined ? { reasoning_effort: reasoning.effort } : {}),
604
774
  max_completion_tokens: input.maxTokens
605
775
  };
606
776
  };
777
+ const mapResponsesReasoning = (input, providerReasoning) => {
778
+ const reasoning = getOpenAIReasoning(input);
779
+ if (!reasoning) {
780
+ return {};
781
+ }
782
+ return {
783
+ reasoning: {
784
+ ...(providerReasoning && typeof providerReasoning === "object" && !Array.isArray(providerReasoning)
785
+ ? providerReasoning
786
+ : {}),
787
+ ...(reasoning.effort !== undefined ? { effort: reasoning.effort } : {}),
788
+ ...(reasoning.mode !== undefined ? { mode: reasoning.mode } : {}),
789
+ ...(reasoning.context !== undefined ? { context: reasoning.context } : {}),
790
+ ...(reasoning.includeThoughts ? { summary: "auto" } : {})
791
+ }
792
+ };
793
+ };
607
794
  const getProviderResponseId = (messages) => {
608
795
  for (let index = messages.length - 1; index >= 0; index -= 1) {
609
796
  const message = messages[index];
@@ -627,20 +814,52 @@ const getProviderResponseId = (messages) => {
627
814
  const serializeToolOutput = (message) => message.parts
628
815
  .filter((part) => part.type === "tool-result")
629
816
  .map((part) => {
630
- if (part.toolResult.toolName === "shell") {
817
+ const responsesToolType = part.toolResult.providerMetadata?.responsesToolType;
818
+ if (part.toolResult.toolName === "shell" || responsesToolType === "shell") {
819
+ const rawOutput = part.toolResult.output;
820
+ const rawOutputs = Array.isArray(rawOutput)
821
+ ? rawOutput
822
+ : rawOutput && typeof rawOutput === "object" && Array.isArray(rawOutput.output)
823
+ ? rawOutput.output
824
+ : rawOutput
825
+ ? [rawOutput]
826
+ : [];
827
+ const firstRawOutput = rawOutputs[0] && typeof rawOutputs[0] === "object"
828
+ ? rawOutputs[0]
829
+ : undefined;
830
+ const output = rawOutputs.map((entry) => {
831
+ const result = entry && typeof entry === "object" ? entry : {};
832
+ const outcome = result.outcome && typeof result.outcome === "object"
833
+ ? result.outcome
834
+ : { type: "exit", exit_code: 0 };
835
+ return {
836
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
837
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
838
+ outcome: {
839
+ type: outcome.type ?? "exit",
840
+ exit_code: outcome.exit_code ?? outcome.exitCode
841
+ }
842
+ };
843
+ });
631
844
  return {
632
845
  type: "shell_call_output",
633
846
  call_id: part.toolResult.toolCallId,
847
+ max_output_length: rawOutput && typeof rawOutput === "object"
848
+ ? (rawOutput.max_output_length ??
849
+ rawOutput.maxOutputLength ??
850
+ firstRawOutput?.max_output_length ??
851
+ firstRawOutput?.maxOutputLength)
852
+ : undefined,
634
853
  output: part.toolResult.isError
635
- ? {
636
- stdout: "",
637
- stderr: part.toolResult.error?.message ?? "Shell execution failed.",
638
- outcome: { type: "exit", exitCode: 1 }
639
- }
640
- : part.toolResult.output
854
+ ? [{
855
+ stdout: "",
856
+ stderr: part.toolResult.error?.message ?? "Shell execution failed.",
857
+ outcome: { type: "exit", exit_code: 1 }
858
+ }]
859
+ : output
641
860
  };
642
861
  }
643
- if (part.toolResult.toolName === "apply_patch") {
862
+ if (part.toolResult.toolName === "apply_patch" || responsesToolType === "apply_patch") {
644
863
  const output = part.toolResult.output;
645
864
  const outputRecord = output && typeof output === "object" && !Array.isArray(output)
646
865
  ? output
@@ -652,10 +871,23 @@ const serializeToolOutput = (message) => message.parts
652
871
  output: part.toolResult.isError ? part.toolResult.error?.message : outputRecord?.output
653
872
  };
654
873
  }
874
+ if (part.toolResult.toolName === "computer" || responsesToolType === "computer") {
875
+ if (part.toolResult.isError) {
876
+ throw new Error(`OpenAI computer action execution failed: ${part.toolResult.error?.message ?? "unknown error"}`);
877
+ }
878
+ return {
879
+ type: "computer_call_output",
880
+ call_id: part.toolResult.toolCallId,
881
+ output: part.toolResult.output
882
+ };
883
+ }
655
884
  return {
656
885
  type: "function_call_output",
657
886
  call_id: part.toolResult.toolCallId,
658
- output: JSON.stringify(part.toolResult.isError ? part.toolResult.error : part.toolResult.output ?? null)
887
+ output: JSON.stringify(part.toolResult.isError ? part.toolResult.error : part.toolResult.output ?? null),
888
+ ...(part.toolResult.providerMetadata?.caller !== undefined
889
+ ? { caller: part.toolResult.providerMetadata.caller }
890
+ : {})
659
891
  };
660
892
  });
661
893
  const serializeProviderDataInput = (message) => message.parts
@@ -663,15 +895,27 @@ const serializeProviderDataInput = (message) => message.parts
663
895
  part.provider === "openai" &&
664
896
  part.data !== null &&
665
897
  typeof part.data === "object" &&
666
- part.data.type === "mcp_approval_response")
898
+ typeof part.data.type === "string" &&
899
+ !["prompt_cache_breakpoint", "responses_output"].includes(part.data.type))
667
900
  .map((part) => part.data);
901
+ const serializedResponsesOutput = (message) => message.parts.flatMap((part) => {
902
+ if (part.type !== "provider-data" ||
903
+ part.provider !== "openai" ||
904
+ !part.data ||
905
+ typeof part.data !== "object" ||
906
+ part.data.type !== "responses_output" ||
907
+ !Array.isArray(part.data.items)) {
908
+ return [];
909
+ }
910
+ return part.data.items;
911
+ });
668
912
  const parseResponsesProviderData = (item) => {
669
913
  if (!item || typeof item !== "object") {
670
914
  return undefined;
671
915
  }
672
916
  const typedItem = item;
673
917
  if (typeof typedItem.type !== "string" ||
674
- ["message", "function_call", "shell_call", "apply_patch_call"].includes(typedItem.type)) {
918
+ ["message", "function_call"].includes(typedItem.type)) {
675
919
  return undefined;
676
920
  }
677
921
  return item;
@@ -679,7 +923,13 @@ const parseResponsesProviderData = (item) => {
679
923
  const parseShellCallInput = (item) => {
680
924
  const action = item.action && typeof item.action === "object" ? item.action : undefined;
681
925
  return {
682
- command: typeof action?.command === "string" ? action.command : typeof item.command === "string" ? item.command : undefined,
926
+ command: typeof action?.command === "string"
927
+ ? action.command
928
+ : Array.isArray(action?.commands) && typeof action.commands[0] === "string"
929
+ ? action.commands[0]
930
+ : typeof item.command === "string"
931
+ ? item.command
932
+ : undefined,
683
933
  action: action,
684
934
  maxOutputLength: typeof action?.max_output_length === "number"
685
935
  ? action.max_output_length
@@ -753,26 +1003,52 @@ const toResponsesInput = (messages) => {
753
1003
  input.push(...serializeToolOutput(message));
754
1004
  continue;
755
1005
  }
1006
+ const rawResponsesOutput = serializedResponsesOutput(message);
1007
+ if (message.role === "assistant" && rawResponsesOutput.length) {
1008
+ input.push(...rawResponsesOutput);
1009
+ continue;
1010
+ }
756
1011
  input.push(...serializeProviderDataInput(message));
757
1012
  const content = [];
1013
+ const assistantOutputItems = [];
758
1014
  for (const part of message.parts) {
759
1015
  switch (part.type) {
760
1016
  case "text":
761
- content.push({ type: "input_text", text: part.text });
1017
+ content.push({ type: "input_text", text: part.text, ...promptCacheBreakpointForContentPart(part) });
762
1018
  break;
763
1019
  case "image":
764
- content.push({ type: "input_image", image_url: part.image });
1020
+ const detail = imageDetailForPart(part);
1021
+ content.push({
1022
+ type: "input_image",
1023
+ image_url: part.image,
1024
+ ...(detail ? { detail } : {}),
1025
+ ...promptCacheBreakpointForContentPart(part)
1026
+ });
1027
+ break;
1028
+ case "file":
1029
+ content.push({
1030
+ type: "input_file",
1031
+ file_id: part.data,
1032
+ ...promptCacheBreakpointForContentPart(part)
1033
+ });
765
1034
  break;
766
1035
  case "tool-call":
767
1036
  if (message.role === "assistant") {
768
- content.push({
1037
+ const caller = part.toolCall.providerMetadata?.caller;
1038
+ assistantOutputItems.push({
769
1039
  type: "function_call",
770
1040
  call_id: part.toolCall.id,
771
1041
  name: part.toolCall.name,
772
- arguments: JSON.stringify(part.toolCall.input)
1042
+ arguments: JSON.stringify(part.toolCall.input),
1043
+ ...(caller !== undefined ? { caller } : {})
773
1044
  });
774
1045
  }
775
1046
  break;
1047
+ case "provider-data":
1048
+ if (isOpenAIPromptCacheBreakpointPart(part)) {
1049
+ markLastContentBlockForPromptCaching(content);
1050
+ }
1051
+ break;
776
1052
  }
777
1053
  }
778
1054
  if (content.length) {
@@ -781,49 +1057,76 @@ const toResponsesInput = (messages) => {
781
1057
  content
782
1058
  });
783
1059
  }
1060
+ input.push(...assistantOutputItems);
784
1061
  }
785
1062
  return input;
786
1063
  };
787
- const parseResponsesAssistantMessage = (json) => {
1064
+ const parseResponsesAssistantMessage = (json, multiAgentEnabled = false, localTools = new Map()) => {
788
1065
  const parts = [];
789
1066
  for (const [index, item] of (json.output ?? []).entries()) {
790
1067
  if (item?.type === "message") {
1068
+ if (multiAgentEnabled && (item.agent?.agent_name !== "/root" || item.phase !== "final_answer")) {
1069
+ continue;
1070
+ }
791
1071
  for (const content of item.content ?? []) {
792
1072
  if (typeof content?.text === "string" && content.text) {
793
1073
  parts.push({ type: "text", text: content.text });
794
1074
  }
1075
+ else if (typeof content?.refusal === "string" && content.refusal) {
1076
+ parts.push({ type: "text", text: content.refusal });
1077
+ }
795
1078
  }
796
1079
  continue;
797
1080
  }
798
1081
  if (item?.type === "function_call") {
1082
+ const callId = item.call_id ?? item.id ?? `${item.name}-${index}`;
799
1083
  parts.push({
800
1084
  type: "tool-call",
801
1085
  toolCall: {
802
- id: item.call_id ?? item.id ?? `${item.name}-${index}`,
1086
+ id: callId,
803
1087
  name: item.name,
804
- input: JSON.parse(item.arguments ?? "{}")
1088
+ input: JSON.parse(item.arguments ?? "{}"),
1089
+ ...(item.caller && typeof item.caller === "object"
1090
+ ? { providerMetadata: { caller: item.caller } }
1091
+ : {})
805
1092
  }
806
1093
  });
807
1094
  continue;
808
1095
  }
809
- if (item?.type === "shell_call") {
1096
+ if (item?.type === "shell_call" && localTools.has("shell")) {
810
1097
  parts.push({
811
1098
  type: "tool-call",
812
1099
  toolCall: {
813
1100
  id: item.call_id ?? item.id ?? `shell-${index}`,
814
- name: "shell",
815
- input: parseShellCallInput(item)
1101
+ name: localTools.get("shell"),
1102
+ input: parseShellCallInput(item),
1103
+ providerMetadata: { responsesToolType: "shell" }
816
1104
  }
817
1105
  });
818
1106
  continue;
819
1107
  }
820
- if (item?.type === "apply_patch_call") {
1108
+ if (item?.type === "apply_patch_call" && localTools.has("apply_patch")) {
821
1109
  parts.push({
822
1110
  type: "tool-call",
823
1111
  toolCall: {
824
1112
  id: item.call_id ?? item.id ?? `apply_patch-${index}`,
825
- name: "apply_patch",
826
- input: parseApplyPatchCallInput(item)
1113
+ name: localTools.get("apply_patch"),
1114
+ input: parseApplyPatchCallInput(item),
1115
+ providerMetadata: { responsesToolType: "apply_patch" }
1116
+ }
1117
+ });
1118
+ continue;
1119
+ }
1120
+ if (item?.type === "computer_call" && localTools.has("computer")) {
1121
+ parts.push({
1122
+ type: "tool-call",
1123
+ toolCall: {
1124
+ id: item.call_id ?? item.id ?? `computer-${index}`,
1125
+ name: localTools.get("computer"),
1126
+ input: {
1127
+ actions: Array.isArray(item.actions) ? item.actions : []
1128
+ },
1129
+ providerMetadata: { responsesToolType: "computer" }
827
1130
  }
828
1131
  });
829
1132
  continue;
@@ -833,7 +1136,13 @@ const parseResponsesAssistantMessage = (json) => {
833
1136
  parts.push(providerDataPart("openai", providerData));
834
1137
  }
835
1138
  }
836
- if (!parts.some((part) => part.type === "text") && typeof json.output_text === "string" && json.output_text) {
1139
+ if (Array.isArray(json.output) && json.output.length) {
1140
+ parts.push(providerDataPart("openai", {
1141
+ type: "responses_output",
1142
+ items: json.output
1143
+ }));
1144
+ }
1145
+ if (!multiAgentEnabled && !parts.some((part) => part.type === "text") && typeof json.output_text === "string" && json.output_text) {
837
1146
  parts.push({ type: "text", text: json.output_text });
838
1147
  }
839
1148
  if (typeof json.id === "string") {
@@ -850,10 +1159,13 @@ const parseResponsesAssistantMessage = (json) => {
850
1159
  parts
851
1160
  };
852
1161
  };
853
- const normalizeResponsesFinishReason = (status, hasToolCalls) => {
1162
+ const normalizeResponsesFinishReason = (status, hasToolCalls, hasRefusal = false) => {
854
1163
  if (hasToolCalls) {
855
1164
  return "tool-calls";
856
1165
  }
1166
+ if (hasRefusal) {
1167
+ return "refusal";
1168
+ }
857
1169
  if (status === "completed") {
858
1170
  return "stop";
859
1171
  }
@@ -862,9 +1174,46 @@ const normalizeResponsesFinishReason = (status, hasToolCalls) => {
862
1174
  }
863
1175
  return normalizeFinishReason(status);
864
1176
  };
865
- const streamResponses = async function* (response) {
1177
+ const mapResponsesUsage = (usage) => usage
1178
+ ? {
1179
+ inputTokens: usage.input_tokens,
1180
+ cachedInputTokens: usage.input_tokens_details?.cached_tokens,
1181
+ cacheWriteTokens: usage.input_tokens_details?.cache_write_tokens,
1182
+ outputTokens: usage.output_tokens,
1183
+ reasoningTokens: usage.output_tokens_details?.reasoning_tokens,
1184
+ totalTokens: usage.total_tokens
1185
+ }
1186
+ : undefined;
1187
+ const mapChatUsage = (usage) => usage
1188
+ ? {
1189
+ inputTokens: usage.prompt_tokens,
1190
+ cachedInputTokens: usage.prompt_tokens_details?.cached_tokens,
1191
+ cacheWriteTokens: usage.prompt_tokens_details?.cache_write_tokens,
1192
+ outputTokens: usage.completion_tokens,
1193
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,
1194
+ totalTokens: usage.total_tokens
1195
+ }
1196
+ : undefined;
1197
+ const addTokenUsage = (left, right) => {
1198
+ if (!left)
1199
+ return right;
1200
+ if (!right)
1201
+ return left;
1202
+ const sum = (a, b) => a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0);
1203
+ return {
1204
+ inputTokens: sum(left.inputTokens, right.inputTokens),
1205
+ cachedInputTokens: sum(left.cachedInputTokens, right.cachedInputTokens),
1206
+ cacheWriteTokens: sum(left.cacheWriteTokens, right.cacheWriteTokens),
1207
+ outputTokens: sum(left.outputTokens, right.outputTokens),
1208
+ reasoningTokens: sum(left.reasoningTokens, right.reasoningTokens),
1209
+ totalTokens: sum(left.totalTokens, right.totalTokens)
1210
+ };
1211
+ };
1212
+ const streamResponses = async function* (response, multiAgentEnabled = false, localTools = new Map()) {
866
1213
  const toolBuffers = new Map();
1214
+ const outputAgents = new Map();
867
1215
  let sawToolCalls = false;
1216
+ let sawRefusal = false;
868
1217
  const emitToolCall = (key) => {
869
1218
  const toolCall = toolBuffers.get(key);
870
1219
  if (!toolCall || toolCall.emitted || !toolCall.name) {
@@ -877,7 +1226,8 @@ const streamResponses = async function* (response) {
877
1226
  toolCall: {
878
1227
  id: toolCall.callId,
879
1228
  name: toolCall.name,
880
- input: JSON.parse(toolCall.args || "{}")
1229
+ input: JSON.parse(toolCall.args || "{}"),
1230
+ ...(toolCall.caller !== undefined ? { providerMetadata: { caller: toolCall.caller } } : {})
881
1231
  }
882
1232
  };
883
1233
  };
@@ -887,12 +1237,32 @@ const streamResponses = async function* (response) {
887
1237
  }
888
1238
  const json = JSON.parse(event.data);
889
1239
  const type = json.type;
890
- if (type === "response.output_text.delta" && typeof json.delta === "string") {
1240
+ if (type === "error") {
1241
+ throw new ProviderHTTPError(`OpenAI Responses stream failed: ${json.error?.message ?? json.message ?? "unknown error"}`, 500, { responseBody: JSON.stringify(json) });
1242
+ }
1243
+ if (type === "response.output_text.delta" &&
1244
+ typeof json.delta === "string" &&
1245
+ (!multiAgentEnabled ||
1246
+ (outputAgents.get(json.output_index)?.agentName ?? "/root") === "/root")) {
1247
+ yield { type: "text-delta", textDelta: json.delta };
1248
+ continue;
1249
+ }
1250
+ if (type === "response.refusal.delta" &&
1251
+ typeof json.delta === "string" &&
1252
+ (!multiAgentEnabled ||
1253
+ (outputAgents.get(json.output_index)?.agentName ?? "/root") === "/root")) {
1254
+ sawRefusal = true;
891
1255
  yield { type: "text-delta", textDelta: json.delta };
892
1256
  continue;
893
1257
  }
894
1258
  if (type === "response.output_item.added" || type === "response.output_item.done") {
895
1259
  const item = json.item;
1260
+ let handledLocalToolCall = false;
1261
+ if (typeof json.output_index === "number" && item?.type === "message") {
1262
+ outputAgents.set(json.output_index, {
1263
+ agentName: item.agent?.agent_name
1264
+ });
1265
+ }
896
1266
  if (item?.type === "function_call") {
897
1267
  const key = item.id ?? json.item_id ?? `${json.output_index ?? toolBuffers.size}`;
898
1268
  const existing = toolBuffers.get(key) ?? {
@@ -903,6 +1273,9 @@ const streamResponses = async function* (response) {
903
1273
  };
904
1274
  existing.callId = item.call_id ?? existing.callId;
905
1275
  existing.name ||= item.name ?? "";
1276
+ if (item.caller && typeof item.caller === "object") {
1277
+ existing.caller = item.caller;
1278
+ }
906
1279
  if (typeof item.arguments === "string") {
907
1280
  existing.args = item.arguments;
908
1281
  }
@@ -914,36 +1287,65 @@ const streamResponses = async function* (response) {
914
1287
  }
915
1288
  }
916
1289
  }
917
- if (item?.type === "shell_call" && type === "response.output_item.done") {
1290
+ if (item?.type === "shell_call" && type === "response.output_item.done" && localTools.has("shell")) {
918
1291
  yield {
919
1292
  type: "tool-call",
920
1293
  toolCall: {
921
1294
  id: item.call_id ?? item.id ?? `${json.output_index ?? "shell"}`,
922
- name: "shell",
923
- input: parseShellCallInput(item)
1295
+ name: localTools.get("shell"),
1296
+ input: parseShellCallInput(item),
1297
+ providerMetadata: { responsesToolType: "shell" }
924
1298
  }
925
1299
  };
926
1300
  sawToolCalls = true;
1301
+ handledLocalToolCall = true;
927
1302
  }
928
- if (item?.type === "apply_patch_call" && type === "response.output_item.done") {
1303
+ if (item?.type === "apply_patch_call" && type === "response.output_item.done" && localTools.has("apply_patch")) {
929
1304
  yield {
930
1305
  type: "tool-call",
931
1306
  toolCall: {
932
1307
  id: item.call_id ?? item.id ?? `${json.output_index ?? "apply_patch"}`,
933
- name: "apply_patch",
934
- input: parseApplyPatchCallInput(item)
1308
+ name: localTools.get("apply_patch"),
1309
+ input: parseApplyPatchCallInput(item),
1310
+ providerMetadata: { responsesToolType: "apply_patch" }
935
1311
  }
936
1312
  };
937
1313
  sawToolCalls = true;
1314
+ handledLocalToolCall = true;
1315
+ }
1316
+ if (item?.type === "computer_call" && type === "response.output_item.done" && localTools.has("computer")) {
1317
+ yield {
1318
+ type: "tool-call",
1319
+ toolCall: {
1320
+ id: item.call_id ?? item.id ?? `${json.output_index ?? "computer"}`,
1321
+ name: localTools.get("computer"),
1322
+ input: {
1323
+ actions: Array.isArray(item.actions) ? item.actions : []
1324
+ },
1325
+ providerMetadata: { responsesToolType: "computer" }
1326
+ }
1327
+ };
1328
+ sawToolCalls = true;
1329
+ handledLocalToolCall = true;
938
1330
  }
939
1331
  const providerData = parseResponsesProviderData(item);
940
- if (providerData && type === "response.output_item.done") {
1332
+ if (providerData && type === "response.output_item.done" && !handledLocalToolCall) {
941
1333
  yield {
942
1334
  type: "provider-data",
943
1335
  provider: "openai",
944
1336
  data: providerData
945
1337
  };
946
1338
  }
1339
+ if (item && type === "response.output_item.done") {
1340
+ yield {
1341
+ type: "provider-data",
1342
+ provider: "openai",
1343
+ data: {
1344
+ type: "responses_output",
1345
+ items: [item]
1346
+ }
1347
+ };
1348
+ }
947
1349
  continue;
948
1350
  }
949
1351
  if (type === "response.function_call_arguments.delta") {
@@ -978,21 +1380,47 @@ const streamResponses = async function* (response) {
978
1380
  }
979
1381
  if (type === "response.completed" || type === "response.failed" || type === "response.incomplete") {
980
1382
  const responseData = json.response ?? {};
1383
+ if (typeof responseData.id === "string") {
1384
+ yield {
1385
+ type: "provider-data",
1386
+ provider: "openai",
1387
+ data: { responseId: responseData.id }
1388
+ };
1389
+ }
981
1390
  yield {
982
1391
  type: "finish",
983
- finishReason: normalizeResponsesFinishReason(responseData.status, sawToolCalls),
1392
+ finishReason: normalizeResponsesFinishReason(responseData.status, sawToolCalls, sawRefusal),
984
1393
  providerFinishReason: responseData.status,
985
- usage: responseData.usage
986
- ? {
987
- inputTokens: responseData.usage.input_tokens,
988
- outputTokens: responseData.usage.output_tokens,
989
- totalTokens: responseData.usage.total_tokens
990
- }
991
- : undefined
1394
+ usage: mapResponsesUsage(responseData.usage)
992
1395
  };
993
1396
  }
994
1397
  }
995
1398
  };
1399
+ const streamGenerateResult = async function* (result) {
1400
+ for (const message of result.messages ?? (result.message ? [result.message] : [])) {
1401
+ for (const part of message.parts) {
1402
+ if (part.type === "text" && part.text) {
1403
+ yield { type: "text-delta", textDelta: part.text };
1404
+ }
1405
+ else if (part.type === "tool-call") {
1406
+ yield { type: "tool-call", toolCall: part.toolCall };
1407
+ }
1408
+ else if (part.type === "provider-data") {
1409
+ yield {
1410
+ type: "provider-data",
1411
+ provider: part.provider,
1412
+ data: part.data
1413
+ };
1414
+ }
1415
+ }
1416
+ }
1417
+ yield {
1418
+ type: "finish",
1419
+ finishReason: result.finishReason,
1420
+ providerFinishReason: result.providerFinishReason,
1421
+ usage: result.usage
1422
+ };
1423
+ };
996
1424
  const extractSources = (value) => {
997
1425
  const sources = [];
998
1426
  const visit = (node) => {
@@ -1033,69 +1461,117 @@ class OpenAILanguageModel {
1033
1461
  this.fetcher = fetcher;
1034
1462
  this.capabilities = modelCapabilities(modelId);
1035
1463
  }
1036
- usesResponsesAPI(input) {
1037
- return hasResponsesOnlyTools(input.tools);
1464
+ usesResponsesAPI(input, options) {
1465
+ const requiresResponses = hasResponsesOnlyTools(input.tools) || options.multiAgentEnabled;
1466
+ if (options.apiMode === "chat") {
1467
+ if (requiresResponses) {
1468
+ throw new UnsupportedFeatureError('Provider "openai" cannot use apiMode "chat" with Responses-only tools or Multi-agent.');
1469
+ }
1470
+ return false;
1471
+ }
1472
+ return options.apiMode === "responses" || requiresResponses || isOpenAIGpt56Model(this.modelId);
1038
1473
  }
1039
- async generateViaResponses(input, signal) {
1474
+ async generateViaResponses(input, signal, options) {
1475
+ const responseBodyOptions = openAIResponsesBodyOptions(options.bodyOptions, this.modelId);
1040
1476
  assertResponsesToolsSupported(this.modelId, input.tools);
1041
- const previousResponse = getProviderResponseId(input.messages);
1477
+ assertOpenAIResponsesOptionsSupported(this.modelId, responseBodyOptions);
1478
+ const previousResponse = responseBodyOptions.store === false ? undefined : getProviderResponseId(input.messages);
1042
1479
  const messages = previousResponse && previousResponse.index < input.messages.length - 1
1043
1480
  ? input.messages.slice(previousResponse.index + 1)
1044
1481
  : input.messages;
1045
- const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1046
- method: "POST",
1047
- headers: jsonHeaders(this.apiKey),
1048
- signal,
1049
- body: JSON.stringify({
1050
- ...input.providerOptions,
1051
- model: this.modelId,
1052
- ...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
1053
- ...(messages.length ? { input: toResponsesInput(messages) } : {}),
1054
- tools: mapResponsesTools(input.tools),
1055
- tool_choice: mapToolChoice(input.toolChoice),
1056
- text: mapResponsesStructuredOutput(input),
1057
- temperature: input.temperature,
1058
- max_output_tokens: input.maxTokens,
1059
- ...mapReasoning(input)
1060
- })
1061
- }), input);
1062
- const json = await parseJson(response);
1063
- const assistantMessage = parseResponsesAssistantMessage(json);
1064
- const hasToolCalls = assistantMessage.parts.some((part) => part.type === "tool-call");
1065
- return {
1066
- messages: [assistantMessage],
1067
- text: extractMessageText(assistantMessage),
1068
- audio: extractAudioOutputs(assistantMessage),
1069
- finishReason: normalizeResponsesFinishReason(json.status, hasToolCalls),
1070
- providerFinishReason: json.status,
1071
- usage: {
1072
- inputTokens: json.usage?.input_tokens,
1073
- outputTokens: json.usage?.output_tokens,
1074
- totalTokens: json.usage?.total_tokens
1075
- },
1076
- rawResponse: json
1077
- };
1482
+ let nextPreviousResponseId = previousResponse?.responseId;
1483
+ let nextInput = messages.length ? toResponsesInput(messages) : [];
1484
+ let accumulatedUsage;
1485
+ let statelessInternalOutputs = [];
1486
+ for (let continuation = 0; continuation < 8; continuation += 1) {
1487
+ const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1488
+ method: "POST",
1489
+ headers: options.headers,
1490
+ signal,
1491
+ body: JSON.stringify({
1492
+ ...responseBodyOptions,
1493
+ model: this.modelId,
1494
+ ...(nextPreviousResponseId ? { previous_response_id: nextPreviousResponseId } : {}),
1495
+ ...(nextInput.length ? { input: nextInput } : {}),
1496
+ tools: mapResponsesTools(input.tools),
1497
+ ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1498
+ text: mapResponsesStructuredOutput(input),
1499
+ temperature: input.temperature,
1500
+ max_output_tokens: input.maxTokens,
1501
+ ...mapResponsesReasoning(input, responseBodyOptions.reasoning)
1502
+ })
1503
+ }), input);
1504
+ const json = await parseJson(response);
1505
+ accumulatedUsage = addTokenUsage(accumulatedUsage, mapResponsesUsage(json.usage));
1506
+ const assistantMessage = parseResponsesAssistantMessage(json, options.multiAgentEnabled, localResponsesTools(input.tools));
1507
+ const currentOutput = Array.isArray(json.output)
1508
+ ? json.output
1509
+ : [];
1510
+ const completeStatelessOutput = [...statelessInternalOutputs, ...currentOutput];
1511
+ if (responseBodyOptions.store === false && completeStatelessOutput.length) {
1512
+ assistantMessage.parts = assistantMessage.parts.filter((part) => part.type !== "provider-data" ||
1513
+ part.provider !== "openai" ||
1514
+ !part.data ||
1515
+ typeof part.data !== "object" ||
1516
+ part.data.type !== "responses_output");
1517
+ assistantMessage.parts.push(providerDataPart("openai", {
1518
+ type: "responses_output",
1519
+ items: completeStatelessOutput
1520
+ }));
1521
+ }
1522
+ const hasToolCalls = assistantMessage.parts.some((part) => part.type === "tool-call");
1523
+ const hasRefusal = (json.output ?? []).some((item) => item?.type === "message" &&
1524
+ (item.content ?? []).some((content) => content?.type === "refusal"));
1525
+ const hasFinalMessage = (json.output ?? []).some((item) => item?.type === "message" &&
1526
+ (!options.multiAgentEnabled || (item.agent?.agent_name === "/root" && item.phase === "final_answer")));
1527
+ const shouldContinueProgram = hasProgrammaticToolCalling(input.tools) &&
1528
+ json.status === "completed" &&
1529
+ !hasToolCalls &&
1530
+ !hasFinalMessage &&
1531
+ (json.output ?? []).some((item) => item?.type === "program" || item?.type === "program_output");
1532
+ if (!shouldContinueProgram) {
1533
+ return {
1534
+ messages: [assistantMessage],
1535
+ text: extractMessageText(assistantMessage),
1536
+ audio: extractAudioOutputs(assistantMessage),
1537
+ finishReason: normalizeResponsesFinishReason(json.status, hasToolCalls, hasRefusal),
1538
+ providerFinishReason: json.status,
1539
+ usage: accumulatedUsage,
1540
+ rawResponse: json
1541
+ };
1542
+ }
1543
+ if (responseBodyOptions.store === false) {
1544
+ statelessInternalOutputs = completeStatelessOutput;
1545
+ nextInput = [...nextInput, ...currentOutput];
1546
+ }
1547
+ else {
1548
+ nextPreviousResponseId = json.id;
1549
+ nextInput = [];
1550
+ }
1551
+ }
1552
+ throw new ProviderHTTPError("OpenAI Programmatic Tool Calling exceeded 8 internal continuations.", 500);
1078
1553
  }
1079
1554
  async generate(input) {
1080
1555
  const { signal, cleanup } = getRequestOptions(input);
1556
+ const options = resolveOpenAILanguageRequestOptions(input.providerOptions, this.apiKey);
1081
1557
  try {
1082
- if (this.usesResponsesAPI(input)) {
1083
- return await this.generateViaResponses(input, signal);
1558
+ if (this.usesResponsesAPI(input, options)) {
1559
+ return await this.generateViaResponses(input, signal, options);
1084
1560
  }
1085
1561
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
1086
1562
  method: "POST",
1087
- headers: jsonHeaders(this.apiKey),
1563
+ headers: options.headers,
1088
1564
  signal,
1089
1565
  body: JSON.stringify({
1090
- ...input.providerOptions,
1566
+ ...options.bodyOptions,
1091
1567
  model: this.modelId,
1092
1568
  messages: mapMessages(input.messages),
1093
1569
  tools: mapTools(input.tools),
1094
- tool_choice: mapToolChoice(input.toolChoice),
1570
+ ...(input.toolChoice ? { tool_choice: mapToolChoice(input.toolChoice) } : {}),
1095
1571
  response_format: mapStructuredOutput(input),
1096
1572
  temperature: input.temperature,
1097
1573
  ...(input.reasoning ? {} : { max_tokens: input.maxTokens }),
1098
- ...mapReasoning(input),
1574
+ ...mapChatReasoning(input),
1099
1575
  stream: false
1100
1576
  })
1101
1577
  }), input);
@@ -1109,11 +1585,7 @@ class OpenAILanguageModel {
1109
1585
  audio: extractAudioOutputs(assistantMessage),
1110
1586
  finishReason: normalizeFinishReason(choice?.finish_reason),
1111
1587
  providerFinishReason: choice?.finish_reason,
1112
- usage: {
1113
- inputTokens: json.usage?.prompt_tokens,
1114
- outputTokens: json.usage?.completion_tokens,
1115
- totalTokens: json.usage?.total_tokens
1116
- },
1588
+ usage: mapChatUsage(json.usage),
1117
1589
  rawResponse: json
1118
1590
  };
1119
1591
  }
@@ -1122,29 +1594,48 @@ class OpenAILanguageModel {
1122
1594
  }
1123
1595
  }
1124
1596
  async stream(input) {
1125
- if (this.usesResponsesAPI(input)) {
1597
+ const options = resolveOpenAILanguageRequestOptions(input.providerOptions, this.apiKey);
1598
+ if (this.usesResponsesAPI(input, options)) {
1599
+ const responseBodyOptions = openAIResponsesBodyOptions(options.bodyOptions, this.modelId);
1126
1600
  assertResponsesToolsSupported(this.modelId, input.tools);
1601
+ assertOpenAIResponsesOptionsSupported(this.modelId, responseBodyOptions);
1127
1602
  const { signal, cleanup } = getRequestOptions(input);
1603
+ if (hasProgrammaticToolCalling(input.tools)) {
1604
+ const result = await this.generateViaResponses(input, signal, options);
1605
+ return (async function* () {
1606
+ try {
1607
+ yield* streamGenerateResult(result);
1608
+ }
1609
+ finally {
1610
+ cleanup();
1611
+ }
1612
+ })();
1613
+ }
1614
+ const previousResponse = responseBodyOptions.store === false ? undefined : getProviderResponseId(input.messages);
1615
+ const messages = previousResponse && previousResponse.index < input.messages.length - 1
1616
+ ? input.messages.slice(previousResponse.index + 1)
1617
+ : input.messages;
1128
1618
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1129
1619
  method: "POST",
1130
- headers: jsonHeaders(this.apiKey),
1620
+ headers: options.headers,
1131
1621
  signal,
1132
1622
  body: JSON.stringify({
1133
- ...input.providerOptions,
1623
+ ...responseBodyOptions,
1134
1624
  model: this.modelId,
1135
- input: toResponsesInput(input.messages),
1625
+ ...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
1626
+ ...(messages.length ? { input: toResponsesInput(messages) } : {}),
1136
1627
  tools: mapResponsesTools(input.tools),
1137
- tool_choice: mapToolChoice(input.toolChoice),
1628
+ ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1138
1629
  text: mapResponsesStructuredOutput(input),
1139
1630
  temperature: input.temperature,
1140
1631
  max_output_tokens: input.maxTokens,
1141
- ...mapReasoning(input),
1632
+ ...mapResponsesReasoning(input, responseBodyOptions.reasoning),
1142
1633
  stream: true
1143
1634
  })
1144
1635
  }), input);
1145
1636
  return (async function* () {
1146
1637
  try {
1147
- yield* streamResponses(response);
1638
+ yield* streamResponses(response, options.multiAgentEnabled, localResponsesTools(input.tools));
1148
1639
  }
1149
1640
  finally {
1150
1641
  cleanup();
@@ -1154,18 +1645,18 @@ class OpenAILanguageModel {
1154
1645
  const { signal, cleanup } = getRequestOptions(input);
1155
1646
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
1156
1647
  method: "POST",
1157
- headers: jsonHeaders(this.apiKey),
1648
+ headers: options.headers,
1158
1649
  signal,
1159
1650
  body: JSON.stringify({
1160
- ...input.providerOptions,
1651
+ ...options.bodyOptions,
1161
1652
  model: this.modelId,
1162
1653
  messages: mapMessages(input.messages),
1163
1654
  tools: mapTools(input.tools),
1164
- tool_choice: mapToolChoice(input.toolChoice),
1655
+ ...(input.toolChoice ? { tool_choice: mapToolChoice(input.toolChoice) } : {}),
1165
1656
  response_format: mapStructuredOutput(input),
1166
1657
  temperature: input.temperature,
1167
1658
  ...(input.reasoning ? {} : { max_tokens: input.maxTokens }),
1168
- ...mapReasoning(input),
1659
+ ...mapChatReasoning(input),
1169
1660
  stream: true,
1170
1661
  stream_options: { include_usage: true }
1171
1662
  })
@@ -1208,13 +1699,7 @@ class OpenAILanguageModel {
1208
1699
  type: "finish",
1209
1700
  finishReason: normalizeFinishReason(choice.finish_reason),
1210
1701
  providerFinishReason: choice.finish_reason,
1211
- usage: json.usage
1212
- ? {
1213
- inputTokens: json.usage.prompt_tokens,
1214
- outputTokens: json.usage.completion_tokens,
1215
- totalTokens: json.usage.total_tokens
1216
- }
1217
- : undefined
1702
+ usage: mapChatUsage(json.usage)
1218
1703
  };
1219
1704
  }
1220
1705
  }
@@ -1276,17 +1761,25 @@ class OpenAITranscriptionModel {
1276
1761
  apiKey;
1277
1762
  baseURL;
1278
1763
  fetcher;
1764
+ responseLimits;
1279
1765
  provider = "openai";
1280
1766
  capabilities = transcriptionCapabilities;
1281
- constructor(modelId, apiKey, baseURL, fetcher) {
1767
+ constructor(modelId, apiKey, baseURL, fetcher, responseLimits = resolveAudioResponseLimits()) {
1282
1768
  this.modelId = modelId;
1283
1769
  this.apiKey = apiKey;
1284
1770
  this.baseURL = baseURL;
1285
1771
  this.fetcher = fetcher;
1772
+ this.responseLimits = responseLimits;
1286
1773
  }
1287
1774
  async transcribe(input) {
1288
- const { signal, cleanup } = withTimeoutSignal(input);
1775
+ const { signal, cleanup, abort } = withTimeoutSignal(input);
1289
1776
  const form = new FormData();
1777
+ for (const [key, value] of Object.entries(input.providerOptions ?? {})) {
1778
+ if (["model", "file", "prompt", "language"].includes(key)) {
1779
+ continue;
1780
+ }
1781
+ form.set(key, typeof value === "string" ? value : JSON.stringify(value));
1782
+ }
1290
1783
  form.set("model", this.modelId);
1291
1784
  form.set("file", createAudioFile(input.audio));
1292
1785
  if (input.prompt) {
@@ -1295,9 +1788,6 @@ class OpenAITranscriptionModel {
1295
1788
  if (input.language) {
1296
1789
  form.set("language", input.language);
1297
1790
  }
1298
- for (const [key, value] of Object.entries(input.providerOptions ?? {})) {
1299
- form.set(key, typeof value === "string" ? value : JSON.stringify(value));
1300
- }
1301
1791
  try {
1302
1792
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/audio/transcriptions`, {
1303
1793
  method: "POST",
@@ -1305,7 +1795,12 @@ class OpenAITranscriptionModel {
1305
1795
  signal,
1306
1796
  body: form
1307
1797
  }), input);
1308
- const json = await parseJson(response);
1798
+ const json = await parseJson(response, {
1799
+ maxBytes: this.responseLimits.transcriptionBytes,
1800
+ errorBodyBytes: this.responseLimits.errorBodyBytes,
1801
+ endpoint: "audio/transcriptions",
1802
+ abort
1803
+ });
1309
1804
  return {
1310
1805
  text: json.text ?? "",
1311
1806
  rawResponse: json
@@ -1321,16 +1816,18 @@ class OpenAISpeechModel {
1321
1816
  apiKey;
1322
1817
  baseURL;
1323
1818
  fetcher;
1819
+ responseLimits;
1324
1820
  provider = "openai";
1325
1821
  capabilities = speechCapabilities;
1326
- constructor(modelId, apiKey, baseURL, fetcher) {
1822
+ constructor(modelId, apiKey, baseURL, fetcher, responseLimits = resolveAudioResponseLimits()) {
1327
1823
  this.modelId = modelId;
1328
1824
  this.apiKey = apiKey;
1329
1825
  this.baseURL = baseURL;
1330
1826
  this.fetcher = fetcher;
1827
+ this.responseLimits = responseLimits;
1331
1828
  }
1332
1829
  async generateSpeech(input) {
1333
- const { signal, cleanup } = withTimeoutSignal(input);
1830
+ const { signal, cleanup, abort } = withTimeoutSignal(input);
1334
1831
  try {
1335
1832
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/audio/speech`, {
1336
1833
  method: "POST",
@@ -1344,13 +1841,18 @@ class OpenAISpeechModel {
1344
1841
  })
1345
1842
  }), input);
1346
1843
  if (!response.ok) {
1347
- const body = await response.text();
1844
+ const body = await readErrorBodyWithLimit(response, this.responseLimits.errorBodyBytes);
1348
1845
  throw new ProviderHTTPError(`OpenAI request failed with status ${response.status}.`, response.status, {
1349
1846
  responseBody: body
1350
1847
  });
1351
1848
  }
1352
1849
  return {
1353
- audio: new Uint8Array(await response.arrayBuffer()),
1850
+ audio: await readBodyWithLimit(response, {
1851
+ maxBytes: this.responseLimits.speechBytes,
1852
+ provider: "openai",
1853
+ endpoint: "audio/speech",
1854
+ abort
1855
+ }),
1354
1856
  mediaType: response.headers.get("content-type") ?? "audio/mpeg",
1355
1857
  rawResponse: undefined
1356
1858
  };
@@ -1375,29 +1877,28 @@ class OpenAIGroundedLanguageModel {
1375
1877
  }
1376
1878
  async generate(input) {
1377
1879
  const { signal, cleanup } = withTimeoutSignal(input);
1880
+ const options = resolveOpenAILanguageRequestOptions(input.providerOptions, this.apiKey);
1881
+ const responseBodyOptions = openAIResponsesBodyOptions(options.bodyOptions, this.modelId);
1378
1882
  try {
1379
1883
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1380
1884
  method: "POST",
1381
- headers: jsonHeaders(this.apiKey),
1885
+ headers: options.headers,
1382
1886
  signal,
1383
1887
  body: JSON.stringify({
1384
- ...input.providerOptions,
1888
+ ...responseBodyOptions,
1385
1889
  model: this.modelId,
1386
1890
  input: toResponsesInput(input.messages),
1387
1891
  tools: [{ type: "web_search_preview" }],
1388
1892
  temperature: input.temperature,
1389
- max_output_tokens: input.maxTokens
1893
+ max_output_tokens: input.maxTokens,
1894
+ ...mapResponsesReasoning(input, responseBodyOptions.reasoning)
1390
1895
  })
1391
1896
  }), input);
1392
1897
  const json = await parseJson(response);
1393
1898
  return {
1394
1899
  text: json.output_text ?? "",
1395
1900
  sources: extractSources(json),
1396
- usage: {
1397
- inputTokens: json.usage?.input_tokens,
1398
- outputTokens: json.usage?.output_tokens,
1399
- totalTokens: json.usage?.total_tokens
1400
- },
1901
+ usage: mapResponsesUsage(json.usage),
1401
1902
  finishReason: normalizeFinishReason(json.status),
1402
1903
  providerFinishReason: json.status,
1403
1904
  rawResponse: json
@@ -1605,12 +2106,13 @@ export const createOpenAI = (options = {}) => {
1605
2106
  }
1606
2107
  const baseURL = options.baseURL ?? "https://api.openai.com/v1";
1607
2108
  const fetcher = options.fetch ?? globalThis.fetch;
2109
+ const responseLimits = resolveAudioResponseLimits(options.responseLimits);
1608
2110
  return createProviderAdapter({
1609
2111
  name: "openai",
1610
2112
  languageModel: (modelId) => new OpenAILanguageModel(modelId, apiKey, baseURL, fetcher),
1611
2113
  embeddingModel: (modelId) => new OpenAIEmbeddingModel(modelId, apiKey, baseURL, fetcher),
1612
- transcriptionModel: (modelId) => new OpenAITranscriptionModel(modelId, apiKey, baseURL, fetcher),
1613
- speechModel: (modelId) => new OpenAISpeechModel(modelId, apiKey, baseURL, fetcher),
2114
+ transcriptionModel: (modelId) => new OpenAITranscriptionModel(modelId, apiKey, baseURL, fetcher, responseLimits),
2115
+ speechModel: (modelId) => new OpenAISpeechModel(modelId, apiKey, baseURL, fetcher, responseLimits),
1614
2116
  realtimeModel: (modelId) => new OpenAIRealtimeModel(modelId, apiKey, baseURL, fetcher, options.realtimeConnectionFactory, options.realtimeURL, options.browserTokenURL),
1615
2117
  groundedLanguageModel: (modelId) => new OpenAIGroundedLanguageModel(modelId, apiKey, baseURL, fetcher),
1616
2118
  rawFetch: fetcher
@@ -1644,6 +2146,22 @@ export const openAIToolSearchTool = (config = {}) => hostedTool({
1644
2146
  toolClass: "tool-search",
1645
2147
  config: config
1646
2148
  });
2149
+ export const openAIProgrammaticToolCallingTool = () => hostedTool({
2150
+ name: "programmatic_tool_calling",
2151
+ provider: "openai",
2152
+ type: "programmatic_tool_calling",
2153
+ toolClass: "code-execution"
2154
+ });
2155
+ export const openAIProgrammaticTool = (definition, options = {}) => ({
2156
+ ...definition,
2157
+ metadata: {
2158
+ ...definition.metadata,
2159
+ [openAIResponsesFunctionConfigMetadataKey]: {
2160
+ allowed_callers: options.allowedCallers ?? ["programmatic"],
2161
+ ...(options.outputSchema ? { output_schema: toJSONSchema(options.outputSchema) } : {})
2162
+ }
2163
+ }
2164
+ });
1647
2165
  export const openAIRemoteMcpTool = (config) => hostedTool({
1648
2166
  name: config.server_label ?? "mcp",
1649
2167
  provider: "openai",
@@ -1656,6 +2174,10 @@ export const openAIMcpApprovalResponse = (response) => providerDataPart("openai"
1656
2174
  type: "mcp_approval_response",
1657
2175
  ...response
1658
2176
  });
2177
+ export const openAIPromptCacheBreakpoint = () => providerDataPart("openai", {
2178
+ type: "prompt_cache_breakpoint",
2179
+ mode: "explicit"
2180
+ });
1659
2181
  export const openAIComputerUseTool = (config) => hostedTool({
1660
2182
  name: "computer",
1661
2183
  provider: "openai",
@@ -1663,63 +2185,106 @@ export const openAIComputerUseTool = (config) => hostedTool({
1663
2185
  toolClass: "computer-use",
1664
2186
  config: config
1665
2187
  });
2188
+ export const openAIComputerTool = (config) => ({
2189
+ name: config.name ?? "computer",
2190
+ description: "Execute batched actions requested by the OpenAI Responses computer tool and return a screenshot.",
2191
+ requiresApproval: config.requiresApproval ?? true,
2192
+ metadata: {
2193
+ [openAIResponsesToolMetadataKey]: "computer",
2194
+ "openai.responses_tool_config": {}
2195
+ },
2196
+ schema: z.object({
2197
+ actions: z.array(z.record(z.string(), z.unknown()))
2198
+ }),
2199
+ execute: async (input) => {
2200
+ const output = await config.execute(input);
2201
+ return {
2202
+ ...output,
2203
+ detail: "original"
2204
+ };
2205
+ }
2206
+ });
1666
2207
  const runOpenAIShellCommand = async (input, config) => {
1667
2208
  if (config.execute) {
1668
- return config.execute(input);
2209
+ const result = await config.execute(input);
2210
+ return Array.isArray(result) ? result : [result];
1669
2211
  }
1670
- const command = input.command ?? input.action?.command;
1671
- if (!command) {
2212
+ const commands = input.action?.commands?.length
2213
+ ? input.action.commands
2214
+ : [input.command ?? input.action?.command].filter((command) => Boolean(command));
2215
+ if (!commands.length) {
1672
2216
  throw new Error("OpenAI shell tool did not provide a command.");
1673
2217
  }
1674
2218
  const { exec } = await import("node:child_process");
1675
2219
  const { promisify } = await import("node:util");
1676
2220
  const execAsync = promisify(exec);
1677
- const maxBuffer = Math.max(1024, config.maxOutputLength ?? input.maxOutputLength ?? input.max_output_length ?? input.action?.maxOutputLength ?? input.action?.max_output_length ?? 20000);
2221
+ const maxBuffer = Math.max(1024, config.maxOutputLength ??
2222
+ input.maxOutputLength ??
2223
+ input.max_output_length ??
2224
+ input.action?.maxOutputLength ??
2225
+ input.action?.max_output_length ??
2226
+ 20000);
1678
2227
  const cwd = config.rootDir
1679
2228
  ? await assertOpenAIToolPathInsideRoot(config.rootDir, config.cwd ?? ".", "shell cwd")
1680
2229
  : config.cwd;
1681
- try {
1682
- const result = await execAsync(command, {
1683
- cwd,
1684
- timeout: config.timeoutMs,
1685
- maxBuffer
1686
- });
1687
- return {
1688
- stdout: result.stdout,
1689
- stderr: result.stderr,
1690
- outcome: {
1691
- type: "exit",
1692
- exitCode: 0
1693
- },
1694
- maxOutputLength: maxBuffer
1695
- };
1696
- }
1697
- catch (error) {
1698
- const err = error;
1699
- return {
1700
- stdout: err.stdout ?? "",
1701
- stderr: err.stderr ?? err.message ?? "",
1702
- outcome: {
1703
- type: err.killed || err.signal === "SIGTERM" ? "timeout" : "exit",
1704
- exitCode: typeof err.code === "number" ? err.code : 1
1705
- },
1706
- maxOutputLength: maxBuffer
1707
- };
2230
+ const output = [];
2231
+ for (const command of commands) {
2232
+ try {
2233
+ const result = await execAsync(command, {
2234
+ cwd,
2235
+ timeout: config.timeoutMs ?? input.action?.timeout_ms,
2236
+ maxBuffer
2237
+ });
2238
+ output.push({
2239
+ stdout: result.stdout,
2240
+ stderr: result.stderr,
2241
+ outcome: { type: "exit", exit_code: 0 },
2242
+ maxOutputLength: maxBuffer
2243
+ });
2244
+ }
2245
+ catch (error) {
2246
+ const err = error;
2247
+ output.push({
2248
+ stdout: err.stdout ?? "",
2249
+ stderr: err.stderr ?? err.message ?? "",
2250
+ outcome: {
2251
+ type: err.killed || err.signal === "SIGTERM" ? "timeout" : "exit",
2252
+ exit_code: typeof err.code === "number" ? err.code : 1
2253
+ },
2254
+ maxOutputLength: maxBuffer
2255
+ });
2256
+ }
1708
2257
  }
2258
+ return output;
1709
2259
  };
2260
+ export const openAIHostedShellTool = (config) => hostedTool({
2261
+ name: "shell",
2262
+ provider: "openai",
2263
+ type: "shell",
2264
+ toolClass: "shell",
2265
+ config: {
2266
+ environment: config.environment,
2267
+ ...(config.allowedCallers ? { allowed_callers: config.allowedCallers } : {})
2268
+ }
2269
+ });
1710
2270
  export const openAIShellTool = (config = {}) => ({
1711
2271
  name: config.name ?? "shell",
1712
2272
  description: "Run a shell command requested by the OpenAI Responses shell tool.",
1713
2273
  requiresApproval: true,
1714
2274
  metadata: {
1715
2275
  [openAIResponsesToolMetadataKey]: "shell",
1716
- "openai.responses_tool_config": {}
2276
+ "openai.responses_tool_config": {
2277
+ environment: config.environment ?? { type: "local" },
2278
+ ...(config.allowedCallers ? { allowed_callers: config.allowedCallers } : {})
2279
+ }
1717
2280
  },
1718
2281
  schema: z.object({
1719
2282
  command: z.string().optional(),
1720
2283
  action: z
1721
2284
  .object({
1722
2285
  command: z.string().optional(),
2286
+ commands: z.array(z.string()).optional(),
2287
+ timeout_ms: z.number().optional(),
1723
2288
  max_output_length: z.number().optional(),
1724
2289
  maxOutputLength: z.number().optional()
1725
2290
  })
@@ -1742,7 +2307,9 @@ export const openAIApplyPatchTool = (config) => ({
1742
2307
  requiresApproval: true,
1743
2308
  metadata: {
1744
2309
  [openAIResponsesToolMetadataKey]: "apply_patch",
1745
- "openai.responses_tool_config": {}
2310
+ "openai.responses_tool_config": {
2311
+ ...(config.allowedCallers ? { allowed_callers: config.allowedCallers } : {})
2312
+ }
1746
2313
  },
1747
2314
  schema: z.object({
1748
2315
  operation: z