@openai/agents-openai 0.5.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +2 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/metadata.js +3 -3
  7. package/dist/metadata.mjs +3 -3
  8. package/dist/openaiChatCompletionsConverter.d.ts +1 -0
  9. package/dist/openaiChatCompletionsConverter.js +122 -16
  10. package/dist/openaiChatCompletionsConverter.js.map +1 -1
  11. package/dist/openaiChatCompletionsConverter.mjs +121 -16
  12. package/dist/openaiChatCompletionsConverter.mjs.map +1 -1
  13. package/dist/openaiChatCompletionsModel.d.ts +2 -1
  14. package/dist/openaiChatCompletionsModel.js +20 -3
  15. package/dist/openaiChatCompletionsModel.js.map +1 -1
  16. package/dist/openaiChatCompletionsModel.mjs +22 -5
  17. package/dist/openaiChatCompletionsModel.mjs.map +1 -1
  18. package/dist/openaiResponsesModel.d.ts +19 -5
  19. package/dist/openaiResponsesModel.js +724 -74
  20. package/dist/openaiResponsesModel.js.map +1 -1
  21. package/dist/openaiResponsesModel.mjs +726 -76
  22. package/dist/openaiResponsesModel.mjs.map +1 -1
  23. package/dist/retryAdvice.d.ts +2 -0
  24. package/dist/retryAdvice.js +98 -0
  25. package/dist/retryAdvice.js.map +1 -0
  26. package/dist/retryAdvice.mjs +95 -0
  27. package/dist/retryAdvice.mjs.map +1 -0
  28. package/dist/tools.d.ts +22 -1
  29. package/dist/tools.js +38 -0
  30. package/dist/tools.js.map +1 -1
  31. package/dist/tools.mjs +37 -0
  32. package/dist/tools.mjs.map +1 -1
  33. package/dist/types/providerData.d.ts +4 -0
  34. package/dist/utils/providerData.d.ts +26 -2
  35. package/dist/utils/providerData.js +60 -8
  36. package/dist/utils/providerData.js.map +1 -1
  37. package/dist/utils/providerData.mjs +57 -8
  38. package/dist/utils/providerData.mjs.map +1 -1
  39. package/package.json +3 -3
@@ -1,13 +1,114 @@
1
1
  import { RequestUsage, Usage, withResponseSpan, createResponseSpan, setCurrentSpan, resetCurrentSpan, UserError, } from '@openai/agents-core';
2
2
  import OpenAI from 'openai';
3
3
  import logger from "./logger.mjs";
4
+ import { getOpenAIRetryAdvice } from "./retryAdvice.mjs";
4
5
  import { z } from 'zod';
5
6
  import { HEADERS } from "./defaults.mjs";
6
7
  import { ResponsesWebSocketConnection, ResponsesWebSocketInternalError, isWebSocketNotOpenError, shouldWrapNoEventWebSocketError, throwIfAborted, webSocketFrameToText, withAbortSignal, withTimeout, } from "./responsesWebSocketConnection.mjs";
7
8
  import { applyHeadersToAccumulator, createHeaderAccumulator, ensureResponsesWebSocketPath, headerAccumulatorToRecord, headerAccumulatorToSDKHeaders, mergeQueryParamsIntoURL, splitResponsesTransportOverrides, } from "./responsesTransportUtils.mjs";
8
9
  import { CodeInterpreterStatus, FileSearchStatus, ImageGenerationStatus, WebSearchStatus, } from "./tools.mjs";
9
- import { camelOrSnakeToSnakeCase } from "./utils/providerData.mjs";
10
- import { encodeUint8ArrayToBase64 } from '@openai/agents-core/utils';
10
+ import { camelOrSnakeToSnakeCase, getSnakeCasedProviderDataWithoutReservedKeys, } from "./utils/providerData.mjs";
11
+ import { encodeUint8ArrayToBase64, getToolSearchExecution, getToolSearchProviderCallId, } from '@openai/agents-core/utils';
12
+ /**
13
+ * Tool search outputs are replayed through agents-core protocol items, which use camelCase
14
+ * field names, while the Responses API wire shape uses snake_case. Keep this codec even with
15
+ * first-class upstream types because the local protocol still normalizes these payloads.
16
+ */
17
+ function toOpenAIToolSearchOutputToolPayload(tool, _withinNamespace = false) {
18
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) {
19
+ return tool;
20
+ }
21
+ if (tool.type === 'tool_reference' && typeof tool.functionName === 'string') {
22
+ return {
23
+ type: 'tool_reference',
24
+ function_name: tool.functionName,
25
+ ...(typeof tool.namespace === 'string'
26
+ ? { namespace: tool.namespace }
27
+ : {}),
28
+ };
29
+ }
30
+ if (tool.type === 'namespace' && Array.isArray(tool.tools)) {
31
+ return {
32
+ ...tool,
33
+ tools: tool.tools.map((entry) => toOpenAIToolSearchOutputToolPayload(entry, true)),
34
+ };
35
+ }
36
+ if (tool.type === 'function') {
37
+ const { deferLoading, ...rest } = tool;
38
+ return {
39
+ ...rest,
40
+ ...(typeof deferLoading === 'boolean'
41
+ ? { defer_loading: deferLoading }
42
+ : {}),
43
+ };
44
+ }
45
+ return tool;
46
+ }
47
+ function fromOpenAIToolSearchOutputToolPayload(tool, _withinNamespace = false) {
48
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) {
49
+ return tool;
50
+ }
51
+ if (tool.type === 'tool_reference' &&
52
+ typeof tool.function_name === 'string') {
53
+ return {
54
+ type: 'tool_reference',
55
+ functionName: tool.function_name,
56
+ ...(typeof tool.namespace === 'string'
57
+ ? { namespace: tool.namespace }
58
+ : {}),
59
+ };
60
+ }
61
+ if (tool.type === 'namespace' && Array.isArray(tool.tools)) {
62
+ return {
63
+ ...tool,
64
+ tools: tool.tools.map((entry) => fromOpenAIToolSearchOutputToolPayload(entry, true)),
65
+ };
66
+ }
67
+ if (tool.type === 'function') {
68
+ const { defer_loading, ...rest } = tool;
69
+ return {
70
+ ...rest,
71
+ ...(typeof defer_loading === 'boolean'
72
+ ? { deferLoading: defer_loading }
73
+ : {}),
74
+ };
75
+ }
76
+ return tool;
77
+ }
78
+ function isNeverSentWebSocketError(error) {
79
+ if (isWebSocketNotOpenError(error)) {
80
+ return true;
81
+ }
82
+ const errorCause = error instanceof Error
83
+ ? error.cause
84
+ : undefined;
85
+ if (error instanceof ResponsesWebSocketInternalError &&
86
+ error.code === 'connection_closed_before_opening') {
87
+ return true;
88
+ }
89
+ if (errorCause instanceof ResponsesWebSocketInternalError &&
90
+ errorCause.code === 'connection_closed_before_opening') {
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+ function isAmbiguousWebSocketReplayError(error) {
96
+ if (error instanceof ResponsesWebSocketInternalError &&
97
+ error.code === 'connection_closed_before_terminal_response_event') {
98
+ return true;
99
+ }
100
+ const errorCause = error instanceof Error
101
+ ? error.cause
102
+ : undefined;
103
+ return (errorCause instanceof ResponsesWebSocketInternalError &&
104
+ errorCause.code === 'connection_closed_before_terminal_response_event');
105
+ }
106
+ function hasSerializedComputerDisplayMetadata(tool) {
107
+ return (typeof tool.environment === 'string' &&
108
+ Array.isArray(tool.dimensions) &&
109
+ tool.dimensions.length === 2 &&
110
+ tool.dimensions.every((value) => typeof value === 'number'));
111
+ }
11
112
  const HostedToolChoice = z.enum([
12
113
  'file_search',
13
114
  'web_search',
@@ -16,12 +117,16 @@ const HostedToolChoice = z.enum([
16
117
  'image_generation',
17
118
  'mcp',
18
119
  // Specialized local tools
19
- 'computer_use_preview',
20
120
  'shell',
21
121
  'apply_patch',
22
122
  ]);
23
123
  const DefaultToolChoice = z.enum(['auto', 'required', 'none']);
24
- function getToolChoice(toolChoice) {
124
+ const BuiltinComputerToolChoice = z.enum([
125
+ 'computer',
126
+ 'computer_use',
127
+ 'computer_use_preview',
128
+ ]);
129
+ function getToolChoice(toolChoice, options) {
25
130
  if (typeof toolChoice === 'undefined') {
26
131
  return undefined;
27
132
  }
@@ -29,12 +134,202 @@ function getToolChoice(toolChoice) {
29
134
  if (resultDefaultCheck.success) {
30
135
  return resultDefaultCheck.data;
31
136
  }
137
+ const builtinComputerToolChoice = BuiltinComputerToolChoice.safeParse(toolChoice);
138
+ if (builtinComputerToolChoice.success) {
139
+ if (hasBuiltinComputerTool(options?.tools) ||
140
+ options?.allowPromptSuppliedComputerTool === true) {
141
+ return getBuiltinComputerToolChoice(builtinComputerToolChoice.data, {
142
+ model: options?.model,
143
+ });
144
+ }
145
+ if (builtinComputerToolChoice.data === 'computer_use_preview') {
146
+ return { type: 'computer_use_preview' };
147
+ }
148
+ return { type: 'function', name: builtinComputerToolChoice.data };
149
+ }
32
150
  const result = HostedToolChoice.safeParse(toolChoice);
33
151
  if (result.success) {
34
152
  return { type: result.data };
35
153
  }
36
154
  return { type: 'function', name: toolChoice };
37
155
  }
156
+ function normalizeToolSearchStatus(status) {
157
+ return status === 'in_progress' ||
158
+ status === 'completed' ||
159
+ status === 'incomplete'
160
+ ? status
161
+ : null;
162
+ }
163
+ function hasBuiltinComputerTool(tools) {
164
+ return (tools ?? []).some((tool) => tool.type === 'computer' ||
165
+ tool.type === 'computer_use' ||
166
+ tool.type === 'computer_use_preview');
167
+ }
168
+ function isPreviewComputerModel(model) {
169
+ return typeof model === 'string' && model.startsWith('computer-use-preview');
170
+ }
171
+ function shouldUsePreviewComputerTool(options) {
172
+ if (isPreviewComputerModel(options?.model)) {
173
+ return true;
174
+ }
175
+ if (typeof options?.model === 'string') {
176
+ return false;
177
+ }
178
+ if (options?.toolChoice === 'computer' ||
179
+ options?.toolChoice === 'computer_use') {
180
+ return false;
181
+ }
182
+ return true;
183
+ }
184
+ function getBuiltinComputerToolChoice(toolChoice, options) {
185
+ if (shouldUsePreviewComputerTool({
186
+ model: options?.model,
187
+ toolChoice,
188
+ })) {
189
+ return { type: 'computer_use_preview' };
190
+ }
191
+ if (toolChoice === 'computer_use') {
192
+ return { type: 'computer_use' };
193
+ }
194
+ return { type: 'computer' };
195
+ }
196
+ function isBuiltinComputerToolType(type) {
197
+ return (type === 'computer' ||
198
+ type === 'computer_use' ||
199
+ type === 'computer_use_preview');
200
+ }
201
+ function isCompatibleBuiltinComputerToolChoice(toolChoiceType, toolType) {
202
+ if (!isBuiltinComputerToolType(toolChoiceType)) {
203
+ return false;
204
+ }
205
+ if (toolChoiceType === 'computer_use_preview') {
206
+ return toolType === 'computer_use_preview';
207
+ }
208
+ return toolType === 'computer';
209
+ }
210
+ function isToolChoiceAvailable(toolChoice, tools) {
211
+ if (toolChoice === 'auto' || toolChoice === 'none') {
212
+ return true;
213
+ }
214
+ if (toolChoice === 'required') {
215
+ return tools.length > 0;
216
+ }
217
+ if (toolChoice.type === 'function') {
218
+ return hasFunctionToolChoiceName(toolChoice.name, tools);
219
+ }
220
+ return tools.some((tool) => isCompatibleBuiltinComputerToolChoice(toolChoice.type, tool.type)
221
+ ? true
222
+ : tool.type === toolChoice.type);
223
+ }
224
+ function hasFunctionToolChoiceName(toolChoiceName, tools, namespacePrefix) {
225
+ return (findFunctionToolChoice(toolChoiceName, tools, namespacePrefix) !== undefined);
226
+ }
227
+ function findFunctionToolChoice(toolChoiceName, tools, namespacePrefix) {
228
+ for (const tool of tools) {
229
+ if (isNamedFunctionTool(tool)) {
230
+ const qualifiedName = namespacePrefix
231
+ ? `${namespacePrefix}.${tool.name}`
232
+ : tool.name;
233
+ if (toolChoiceName === qualifiedName) {
234
+ return tool;
235
+ }
236
+ continue;
237
+ }
238
+ if (isNamespaceTool(tool)) {
239
+ const nestedNamespace = namespacePrefix
240
+ ? `${namespacePrefix}.${tool.name}`
241
+ : tool.name;
242
+ const matchedTool = findFunctionToolChoice(toolChoiceName, tool.tools, nestedNamespace);
243
+ if (matchedTool) {
244
+ return matchedTool;
245
+ }
246
+ }
247
+ }
248
+ return undefined;
249
+ }
250
+ function collectAvailableToolChoiceNames(tools, namespacePrefix) {
251
+ const availableToolChoices = [];
252
+ for (const tool of tools) {
253
+ if (isNamedFunctionTool(tool)) {
254
+ availableToolChoices.push(namespacePrefix ? `${namespacePrefix}.${tool.name}` : tool.name);
255
+ continue;
256
+ }
257
+ if (isNamespaceTool(tool)) {
258
+ const nestedNamespace = namespacePrefix
259
+ ? `${namespacePrefix}.${tool.name}`
260
+ : tool.name;
261
+ availableToolChoices.push(...collectAvailableToolChoiceNames(tool.tools, nestedNamespace));
262
+ continue;
263
+ }
264
+ availableToolChoices.push(tool.type);
265
+ }
266
+ return availableToolChoices;
267
+ }
268
+ function isNamedFunctionTool(tool) {
269
+ return (tool.type === 'function' &&
270
+ typeof tool.name === 'string');
271
+ }
272
+ function isNamespaceTool(tool) {
273
+ const candidate = tool;
274
+ return (tool.type === 'namespace' &&
275
+ typeof candidate.name === 'string' &&
276
+ Array.isArray(candidate.tools));
277
+ }
278
+ function getExtraBodyToolsForToolChoiceValidation(extraBody) {
279
+ if (!extraBody || !Array.isArray(extraBody.tools)) {
280
+ return [];
281
+ }
282
+ return extraBody.tools;
283
+ }
284
+ function assertSupportedToolChoice(toolChoice, tools, options) {
285
+ const allowPromptSuppliedTools = options?.allowPromptSuppliedTools === true;
286
+ if (!toolChoice ||
287
+ toolChoice === 'auto' ||
288
+ toolChoice === 'required' ||
289
+ toolChoice === 'none' ||
290
+ toolChoice.type !== 'function') {
291
+ return;
292
+ }
293
+ const matchedFunctionTool = findFunctionToolChoice(toolChoice.name, tools);
294
+ if (!matchedFunctionTool &&
295
+ allowPromptSuppliedTools &&
296
+ toolChoice.name !== 'tool_search') {
297
+ return;
298
+ }
299
+ if (matchedFunctionTool
300
+ ?.defer_loading === true) {
301
+ throw new UserError(`modelSettings.toolChoice="${toolChoice.name}" cannot force a deferred function tool in Responses. Use "auto" so tool_search can load it.`);
302
+ }
303
+ if (toolChoice.name === 'tool_search' &&
304
+ !hasFunctionToolChoiceName(toolChoice.name, tools)) {
305
+ throw new UserError('modelSettings.toolChoice="tool_search" is only supported for a custom function named "tool_search". Responses does not support forcing the built-in tool_search tool. Use "auto" instead.');
306
+ }
307
+ }
308
+ function getCompatibleToolChoice(toolChoice, tools, options) {
309
+ const allowPromptSuppliedTools = options?.allowPromptSuppliedTools === true;
310
+ if (typeof toolChoice === 'undefined') {
311
+ return undefined;
312
+ }
313
+ if (isToolChoiceAvailable(toolChoice, tools) || allowPromptSuppliedTools) {
314
+ return toolChoice;
315
+ }
316
+ const availableToolChoices = [
317
+ ...new Set(collectAvailableToolChoiceNames(tools)),
318
+ ];
319
+ const availableToolChoicesMessage = availableToolChoices.length > 0
320
+ ? ` Available tools: ${availableToolChoices.join(', ')}.`
321
+ : ' No tools are available in the outgoing Responses request.';
322
+ if (toolChoice === 'required') {
323
+ throw new UserError(`modelSettings.toolChoice="required" requires at least one available tool in the outgoing Responses request.${availableToolChoicesMessage}`);
324
+ }
325
+ if (toolChoice === 'auto' || toolChoice === 'none') {
326
+ throw new Error(`Unexpected unavailable tool choice: ${JSON.stringify(toolChoice)}`);
327
+ }
328
+ if (toolChoice.type === 'function') {
329
+ throw new UserError(`modelSettings.toolChoice="${toolChoice.name}" does not match any available tool in the outgoing Responses request.${availableToolChoicesMessage}`);
330
+ }
331
+ throw new UserError(`modelSettings.toolChoice="${toolChoice.type}" is unavailable in the outgoing Responses request.${availableToolChoicesMessage}`);
332
+ }
38
333
  function getResponseFormat(outputType, otherProperties) {
39
334
  if (outputType === 'text') {
40
335
  return otherProperties;
@@ -88,12 +383,13 @@ function convertLegacyToolOutputContent(output) {
88
383
  const legacyImageUrl = output.imageUrl;
89
384
  const legacyFileId = output.fileId;
90
385
  const dataValue = output.data;
386
+ const topLevelInlineMediaType = getImageInlineMediaType(output);
91
387
  if (typeof output.image === 'string' && output.image.length > 0) {
92
388
  structured.image = output.image;
93
389
  }
94
390
  else if (isRecord(output.image)) {
95
391
  const imageObj = output.image;
96
- const inlineMediaType = getImageInlineMediaType(imageObj);
392
+ const inlineMediaType = getImageInlineMediaType(imageObj) ?? topLevelInlineMediaType;
97
393
  if (typeof imageObj.url === 'string' && imageObj.url.length > 0) {
98
394
  structured.image = imageObj.url;
99
395
  }
@@ -133,7 +429,7 @@ function convertLegacyToolOutputContent(output) {
133
429
  base64Data = encodeUint8ArrayToBase64(dataValue);
134
430
  }
135
431
  if (base64Data) {
136
- structured.image = base64Data;
432
+ structured.image = formatInlineData(base64Data, topLevelInlineMediaType);
137
433
  }
138
434
  }
139
435
  if (output.providerData) {
@@ -378,9 +674,16 @@ function getImageInlineMediaType(source) {
378
674
  if (typeof source.mediaType === 'string' && source.mediaType.length > 0) {
379
675
  return source.mediaType;
380
676
  }
677
+ if (typeof source.mimeType === 'string' &&
678
+ source.mimeType.length > 0) {
679
+ return source.mimeType;
680
+ }
381
681
  return undefined;
382
682
  }
383
683
  function formatInlineData(data, mediaType) {
684
+ if (typeof data === 'string' && data.startsWith('data:')) {
685
+ return data;
686
+ }
384
687
  const base64 = typeof data === 'string' ? data : encodeUint8ArrayToBase64(data);
385
688
  return mediaType ? `data:${mediaType};base64,${base64}` : base64;
386
689
  }
@@ -497,11 +800,70 @@ function toOpenAIShellEnvironment(environment) {
497
800
  }
498
801
  throw new UserError(`Unsupported shell environment type: ${String(environment.type)}`);
499
802
  }
500
- function getTools(tools, handoffs) {
803
+ function getTools(tools, handoffs, options) {
501
804
  const openaiTools = [];
502
805
  const include = [];
806
+ const namespaceStateByName = new Map();
807
+ let hasDeferredSearchableTool = false;
808
+ let hasToolSearch = false;
809
+ const usePreviewComputerTool = shouldUsePreviewComputerTool({
810
+ model: options?.model,
811
+ toolChoice: options?.toolChoice,
812
+ });
503
813
  for (const tool of tools) {
504
- const { tool: openaiTool, include: openaiIncludes } = converTool(tool);
814
+ if (tool.type === 'function') {
815
+ const isDeferredFunction = tool.deferLoading === true;
816
+ hasDeferredSearchableTool ||= isDeferredFunction;
817
+ const namespaceName = typeof tool.namespace === 'string' ? tool.namespace.trim() : '';
818
+ if (namespaceName.length > 0) {
819
+ const namespaceDescription = typeof tool.namespaceDescription === 'string'
820
+ ? tool.namespaceDescription.trim()
821
+ : '';
822
+ if (namespaceDescription.length === 0) {
823
+ throw new UserError(`All tools in namespace "${namespaceName}" must provide a non-empty description.`);
824
+ }
825
+ let namespaceState = namespaceStateByName.get(namespaceName);
826
+ if (!namespaceState) {
827
+ namespaceState = {
828
+ index: openaiTools.length,
829
+ description: namespaceDescription,
830
+ functionNames: new Set(),
831
+ tools: [],
832
+ };
833
+ namespaceStateByName.set(namespaceName, namespaceState);
834
+ openaiTools.push({});
835
+ }
836
+ else if (namespaceState.description !== namespaceDescription) {
837
+ throw new UserError(`All tools in namespace "${namespaceName}" must share the same description.`);
838
+ }
839
+ const { tool: openaiTool, include: openaiIncludes } = converTool(tool, {
840
+ usePreviewComputerTool,
841
+ });
842
+ if (namespaceState.functionNames.has(tool.name)) {
843
+ throw new UserError(`Namespace "${namespaceName}" cannot contain duplicate function tool name "${tool.name}".`);
844
+ }
845
+ namespaceState.functionNames.add(tool.name);
846
+ namespaceState.tools.push(openaiTool);
847
+ if (openaiIncludes && openaiIncludes.length > 0) {
848
+ for (const item of openaiIncludes) {
849
+ include.push(item);
850
+ }
851
+ }
852
+ continue;
853
+ }
854
+ }
855
+ if (tool.type === 'hosted_tool' &&
856
+ tool.providerData?.type === 'tool_search') {
857
+ hasToolSearch = true;
858
+ }
859
+ if (tool.type === 'hosted_tool' &&
860
+ tool.providerData?.type === 'mcp' &&
861
+ tool.providerData.defer_loading === true) {
862
+ hasDeferredSearchableTool = true;
863
+ }
864
+ const { tool: openaiTool, include: openaiIncludes } = converTool(tool, {
865
+ usePreviewComputerTool,
866
+ });
505
867
  openaiTools.push(openaiTool);
506
868
  if (openaiIncludes && openaiIncludes.length > 0) {
507
869
  for (const item of openaiIncludes) {
@@ -509,31 +871,57 @@ function getTools(tools, handoffs) {
509
871
  }
510
872
  }
511
873
  }
874
+ if (hasDeferredSearchableTool && !hasToolSearch) {
875
+ throw new UserError('Deferred function tools and hosted MCP tools with deferLoading: true require toolSearchTool() in the same request.');
876
+ }
877
+ for (const [namespaceName, namespaceState,] of namespaceStateByName.entries()) {
878
+ openaiTools[namespaceState.index] = {
879
+ type: 'namespace',
880
+ name: namespaceName,
881
+ description: namespaceState.description,
882
+ tools: namespaceState.tools,
883
+ };
884
+ }
512
885
  return {
513
886
  tools: [...openaiTools, ...handoffs.map(getHandoffTool)],
514
887
  include,
515
888
  };
516
889
  }
517
- function converTool(tool) {
890
+ function converTool(tool, options) {
518
891
  if (tool.type === 'function') {
892
+ const openaiTool = {
893
+ type: 'function',
894
+ name: tool.name,
895
+ description: tool.description,
896
+ parameters: tool.parameters,
897
+ strict: tool.strict,
898
+ };
899
+ if (tool.deferLoading) {
900
+ openaiTool.defer_loading = true;
901
+ }
519
902
  return {
520
- tool: {
521
- type: 'function',
522
- name: tool.name,
523
- description: tool.description,
524
- parameters: tool.parameters,
525
- strict: tool.strict,
526
- },
903
+ tool: openaiTool,
527
904
  include: undefined,
528
905
  };
529
906
  }
530
907
  else if (tool.type === 'computer') {
908
+ if (options?.usePreviewComputerTool) {
909
+ if (!hasSerializedComputerDisplayMetadata(tool)) {
910
+ throw new UserError('Preview computer tools require environment and dimensions. Provide them on your Computer implementation or target a GA computer model such as gpt-5.4.');
911
+ }
912
+ return {
913
+ tool: {
914
+ type: 'computer_use_preview',
915
+ environment: tool.environment,
916
+ display_width: tool.dimensions[0],
917
+ display_height: tool.dimensions[1],
918
+ },
919
+ include: undefined,
920
+ };
921
+ }
531
922
  return {
532
923
  tool: {
533
- type: 'computer_use_preview',
534
- environment: tool.environment,
535
- display_width: tool.dimensions[0],
536
- display_height: tool.dimensions[1],
924
+ type: 'computer',
537
925
  },
538
926
  include: undefined,
539
927
  };
@@ -604,6 +992,17 @@ function converTool(tool) {
604
992
  include: undefined,
605
993
  };
606
994
  }
995
+ else if (tool.providerData?.type === 'tool_search') {
996
+ return {
997
+ tool: {
998
+ type: 'tool_search',
999
+ execution: tool.providerData.execution,
1000
+ description: tool.providerData.description,
1001
+ parameters: tool.providerData.parameters,
1002
+ },
1003
+ include: undefined,
1004
+ };
1005
+ }
607
1006
  else if (tool.providerData?.type === 'image_generation') {
608
1007
  return {
609
1008
  tool: {
@@ -623,17 +1022,22 @@ function converTool(tool) {
623
1022
  };
624
1023
  }
625
1024
  else if (tool.providerData?.type === 'mcp') {
1025
+ const openaiTool = {
1026
+ type: 'mcp',
1027
+ server_label: tool.providerData.server_label,
1028
+ server_url: tool.providerData.server_url,
1029
+ connector_id: tool.providerData.connector_id,
1030
+ authorization: tool.providerData.authorization,
1031
+ allowed_tools: tool.providerData.allowed_tools,
1032
+ headers: tool.providerData.headers,
1033
+ require_approval: convertMCPRequireApproval(tool.providerData.require_approval),
1034
+ server_description: tool.providerData.server_description,
1035
+ };
1036
+ if (tool.providerData.defer_loading === true) {
1037
+ openaiTool.defer_loading = true;
1038
+ }
626
1039
  return {
627
- tool: {
628
- type: 'mcp',
629
- server_label: tool.providerData.server_label,
630
- server_url: tool.providerData.server_url,
631
- connector_id: tool.providerData.connector_id,
632
- authorization: tool.providerData.authorization,
633
- allowed_tools: tool.providerData.allowed_tools,
634
- headers: tool.providerData.headers,
635
- require_approval: convertMCPRequireApproval(tool.providerData.require_approval),
636
- },
1040
+ tool: openaiTool,
637
1041
  include: undefined,
638
1042
  };
639
1043
  }
@@ -672,7 +1076,10 @@ function getInputMessageContent(entry) {
672
1076
  return {
673
1077
  type: 'input_text',
674
1078
  text: entry.text,
675
- ...camelOrSnakeToSnakeCase(entry.providerData),
1079
+ ...getSnakeCasedProviderDataWithoutReservedKeys(entry.providerData, [
1080
+ 'type',
1081
+ 'text',
1082
+ ]),
676
1083
  };
677
1084
  }
678
1085
  else if (entry.type === 'input_image') {
@@ -694,7 +1101,12 @@ function getInputMessageContent(entry) {
694
1101
  }
695
1102
  return {
696
1103
  ...imageEntry,
697
- ...camelOrSnakeToSnakeCase(entry.providerData),
1104
+ ...getSnakeCasedProviderDataWithoutReservedKeys(entry.providerData, [
1105
+ 'type',
1106
+ 'detail',
1107
+ 'image_url',
1108
+ 'file_id',
1109
+ ]),
698
1110
  };
699
1111
  }
700
1112
  else if (entry.type === 'input_file') {
@@ -739,25 +1151,54 @@ function getInputMessageContent(entry) {
739
1151
  }
740
1152
  return {
741
1153
  ...fileEntry,
742
- ...camelOrSnakeToSnakeCase(entry.providerData),
1154
+ ...getSnakeCasedProviderDataWithoutReservedKeys(entry.providerData, [
1155
+ 'type',
1156
+ 'file_data',
1157
+ 'file_url',
1158
+ 'file_id',
1159
+ 'filename',
1160
+ ]),
743
1161
  };
744
1162
  }
745
1163
  throw new UserError(`Unsupported input content type: ${JSON.stringify(entry)}`);
746
1164
  }
1165
+ function getProviderDataField(providerData, keys) {
1166
+ if (!providerData ||
1167
+ typeof providerData !== 'object' ||
1168
+ Array.isArray(providerData)) {
1169
+ return undefined;
1170
+ }
1171
+ const record = providerData;
1172
+ for (const key of keys) {
1173
+ if (typeof record[key] !== 'undefined') {
1174
+ return record[key];
1175
+ }
1176
+ }
1177
+ return undefined;
1178
+ }
747
1179
  function getOutputMessageContent(entry) {
748
1180
  if (entry.type === 'output_text') {
1181
+ const annotations = getProviderDataField(entry.providerData, ['annotations']);
1182
+ const normalizedAnnotations = Array.isArray(annotations) ? annotations : [];
749
1183
  return {
750
1184
  type: 'output_text',
751
1185
  text: entry.text,
752
- annotations: [],
753
- ...camelOrSnakeToSnakeCase(entry.providerData),
1186
+ annotations: normalizedAnnotations,
1187
+ ...getSnakeCasedProviderDataWithoutReservedKeys(entry.providerData, [
1188
+ 'type',
1189
+ 'text',
1190
+ 'annotations',
1191
+ ]),
754
1192
  };
755
1193
  }
756
1194
  if (entry.type === 'refusal') {
757
1195
  return {
758
1196
  type: 'refusal',
759
1197
  refusal: entry.refusal,
760
- ...camelOrSnakeToSnakeCase(entry.providerData),
1198
+ ...getSnakeCasedProviderDataWithoutReservedKeys(entry.providerData, [
1199
+ 'type',
1200
+ 'refusal',
1201
+ ]),
761
1202
  };
762
1203
  }
763
1204
  throw new UserError(`Unsupported output content type: ${JSON.stringify(entry)}`);
@@ -768,7 +1209,11 @@ function getMessageItem(item) {
768
1209
  id: item.id,
769
1210
  role: 'system',
770
1211
  content: item.content,
771
- ...camelOrSnakeToSnakeCase(item.providerData),
1212
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1213
+ 'id',
1214
+ 'role',
1215
+ 'content',
1216
+ ]),
772
1217
  };
773
1218
  }
774
1219
  if (item.role === 'user') {
@@ -777,14 +1222,22 @@ function getMessageItem(item) {
777
1222
  id: item.id,
778
1223
  role: 'user',
779
1224
  content: item.content,
780
- ...camelOrSnakeToSnakeCase(item.providerData),
1225
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1226
+ 'id',
1227
+ 'role',
1228
+ 'content',
1229
+ ]),
781
1230
  };
782
1231
  }
783
1232
  return {
784
1233
  id: item.id,
785
1234
  role: 'user',
786
1235
  content: item.content.map(getInputMessageContent),
787
- ...camelOrSnakeToSnakeCase(item.providerData),
1236
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1237
+ 'id',
1238
+ 'role',
1239
+ 'content',
1240
+ ]),
788
1241
  };
789
1242
  }
790
1243
  if (item.role === 'assistant') {
@@ -794,7 +1247,13 @@ function getMessageItem(item) {
794
1247
  role: 'assistant',
795
1248
  content: item.content.map(getOutputMessageContent),
796
1249
  status: item.status,
797
- ...camelOrSnakeToSnakeCase(item.providerData),
1250
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1251
+ 'type',
1252
+ 'id',
1253
+ 'role',
1254
+ 'content',
1255
+ 'status',
1256
+ ]),
798
1257
  };
799
1258
  return assistantMessage;
800
1259
  }
@@ -841,6 +1300,52 @@ function getInputItems(input) {
841
1300
  if (isMessageItem(item)) {
842
1301
  return getMessageItem(item);
843
1302
  }
1303
+ if (item.type === 'tool_search_call') {
1304
+ const status = normalizeToolSearchStatus(item.status);
1305
+ const callId = getToolSearchProviderCallId(item);
1306
+ const execution = getToolSearchExecution(item);
1307
+ const toolSearchCall = {
1308
+ type: 'tool_search_call',
1309
+ id: item.id,
1310
+ ...(status !== null ? { status } : {}),
1311
+ arguments: item.arguments,
1312
+ ...(callId ? { call_id: callId } : {}),
1313
+ ...(execution ? { execution } : {}),
1314
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1315
+ 'type',
1316
+ 'id',
1317
+ 'status',
1318
+ 'arguments',
1319
+ 'call_id',
1320
+ 'callId',
1321
+ 'execution',
1322
+ ]),
1323
+ };
1324
+ return toolSearchCall;
1325
+ }
1326
+ if (item.type === 'tool_search_output') {
1327
+ const status = normalizeToolSearchStatus(item.status);
1328
+ const callId = getToolSearchProviderCallId(item);
1329
+ const execution = getToolSearchExecution(item);
1330
+ const toolSearchOutput = {
1331
+ type: 'tool_search_output',
1332
+ id: item.id,
1333
+ ...(status !== null ? { status } : {}),
1334
+ tools: item.tools.map((tool) => toOpenAIToolSearchOutputToolPayload(tool)),
1335
+ ...(callId ? { call_id: callId } : {}),
1336
+ ...(execution ? { execution } : {}),
1337
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1338
+ 'type',
1339
+ 'id',
1340
+ 'status',
1341
+ 'tools',
1342
+ 'call_id',
1343
+ 'callId',
1344
+ 'execution',
1345
+ ]),
1346
+ };
1347
+ return toolSearchOutput;
1348
+ }
844
1349
  if (item.type === 'function_call') {
845
1350
  const entry = {
846
1351
  id: item.id,
@@ -849,7 +1354,18 @@ function getInputItems(input) {
849
1354
  call_id: item.callId,
850
1355
  arguments: item.arguments,
851
1356
  status: item.status,
852
- ...camelOrSnakeToSnakeCase(item.providerData),
1357
+ ...(typeof item.namespace === 'string'
1358
+ ? { namespace: item.namespace }
1359
+ : {}),
1360
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1361
+ 'id',
1362
+ 'type',
1363
+ 'name',
1364
+ 'call_id',
1365
+ 'arguments',
1366
+ 'status',
1367
+ 'namespace',
1368
+ ]),
853
1369
  };
854
1370
  return entry;
855
1371
  }
@@ -861,45 +1377,98 @@ function getInputItems(input) {
861
1377
  call_id: item.callId,
862
1378
  output: normalizedOutput,
863
1379
  status: item.status,
864
- ...camelOrSnakeToSnakeCase(item.providerData),
1380
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1381
+ 'type',
1382
+ 'id',
1383
+ 'call_id',
1384
+ 'output',
1385
+ 'status',
1386
+ 'namespace',
1387
+ ]),
865
1388
  };
866
1389
  return entry;
867
1390
  }
868
1391
  if (item.type === 'reasoning') {
1392
+ const encryptedContent = getProviderDataField(item.providerData, [
1393
+ 'encryptedContent',
1394
+ 'encrypted_content',
1395
+ ]);
869
1396
  const entry = {
870
1397
  id: item.id,
871
1398
  type: 'reasoning',
872
1399
  summary: item.content.map((content) => ({
873
1400
  type: 'summary_text',
874
1401
  text: content.text,
875
- ...camelOrSnakeToSnakeCase(content.providerData),
1402
+ ...getSnakeCasedProviderDataWithoutReservedKeys(content.providerData, ['type', 'text']),
876
1403
  })),
877
- encrypted_content: item.providerData?.encryptedContent,
878
- ...camelOrSnakeToSnakeCase(item.providerData),
1404
+ ...(typeof encryptedContent === 'string'
1405
+ ? { encrypted_content: encryptedContent }
1406
+ : {}),
1407
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1408
+ 'id',
1409
+ 'type',
1410
+ 'summary',
1411
+ 'encrypted_content',
1412
+ ]),
879
1413
  };
880
1414
  return entry;
881
1415
  }
882
1416
  if (item.type === 'computer_call') {
1417
+ const pendingSafetyChecks = getProviderDataField(item.providerData, ['pendingSafetyChecks', 'pending_safety_checks']);
1418
+ const normalizedPendingSafetyChecks = Array.isArray(pendingSafetyChecks) ? pendingSafetyChecks : [];
1419
+ const batchedActions = Array.isArray(item.actions)
1420
+ ? (item
1421
+ .actions ?? [])
1422
+ : [];
1423
+ const actionPayload = batchedActions.length > 0
1424
+ ? {
1425
+ action: item.action ?? batchedActions[0],
1426
+ actions: batchedActions,
1427
+ }
1428
+ : item.action
1429
+ ? { action: item.action }
1430
+ : {};
883
1431
  const entry = {
884
1432
  type: 'computer_call',
885
1433
  call_id: item.callId,
886
1434
  id: item.id,
887
- action: item.action,
888
1435
  status: item.status,
889
- pending_safety_checks: [],
890
- ...camelOrSnakeToSnakeCase(item.providerData),
1436
+ pending_safety_checks: normalizedPendingSafetyChecks,
1437
+ ...actionPayload,
1438
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1439
+ 'type',
1440
+ 'call_id',
1441
+ 'id',
1442
+ 'action',
1443
+ 'actions',
1444
+ 'status',
1445
+ 'pending_safety_checks',
1446
+ ]),
891
1447
  };
892
1448
  return entry;
893
1449
  }
894
1450
  if (item.type === 'computer_call_result') {
1451
+ const acknowledgedSafetyChecks = getProviderDataField(item.providerData, [
1452
+ 'acknowledgedSafetyChecks',
1453
+ 'acknowledged_safety_checks',
1454
+ ]);
895
1455
  const entry = {
896
1456
  type: 'computer_call_output',
897
1457
  id: item.id,
898
1458
  call_id: item.callId,
899
1459
  output: buildResponseOutput(item),
900
1460
  status: item.providerData?.status,
901
- acknowledged_safety_checks: item.providerData?.acknowledgedSafetyChecks,
902
- ...camelOrSnakeToSnakeCase(item.providerData),
1461
+ acknowledged_safety_checks: Array.isArray(acknowledgedSafetyChecks)
1462
+ ? acknowledgedSafetyChecks
1463
+ : [],
1464
+ ...getSnakeCasedProviderDataWithoutReservedKeys(item.providerData, [
1465
+ 'type',
1466
+ 'id',
1467
+ 'call_id',
1468
+ 'output',
1469
+ 'status',
1470
+ 'acknowledged_safety_checks',
1471
+ ]),
903
1472
  };
904
1473
  return entry;
905
1474
  }
@@ -1152,6 +1721,30 @@ function convertToOutputItem(items) {
1152
1721
  providerData,
1153
1722
  };
1154
1723
  }
1724
+ else if (item.type === 'tool_search_call') {
1725
+ const { id, type: _type, status, arguments: args, ...providerData } = item;
1726
+ const output = {
1727
+ type: 'tool_search_call',
1728
+ id,
1729
+ status,
1730
+ arguments: args,
1731
+ providerData,
1732
+ };
1733
+ return output;
1734
+ }
1735
+ else if (item.type === 'tool_search_output') {
1736
+ const { id, type: _type, status, tools, ...providerData } = item;
1737
+ const output = {
1738
+ type: 'tool_search_output',
1739
+ id,
1740
+ status,
1741
+ tools: Array.isArray(tools)
1742
+ ? tools.map((tool) => fromOpenAIToolSearchOutputToolPayload(tool))
1743
+ : [],
1744
+ providerData,
1745
+ };
1746
+ return output;
1747
+ }
1155
1748
  else if (item.type === 'file_search_call' ||
1156
1749
  item.type === 'web_search_call' ||
1157
1750
  item.type === 'image_generation_call' ||
@@ -1174,12 +1767,14 @@ function convertToOutputItem(items) {
1174
1767
  return output;
1175
1768
  }
1176
1769
  else if (item.type === 'function_call') {
1177
- const { call_id, name, status, arguments: args, ...providerData } = item;
1770
+ const functionCall = item;
1771
+ const { call_id, name, namespace, status, arguments: args, ...providerData } = functionCall;
1178
1772
  const output = {
1179
1773
  type: 'function_call',
1180
- id: item.id,
1774
+ id: functionCall.id,
1181
1775
  callId: call_id,
1182
1776
  name,
1777
+ ...(typeof namespace === 'string' ? { namespace } : {}),
1183
1778
  status,
1184
1779
  arguments: args,
1185
1780
  providerData,
@@ -1187,12 +1782,13 @@ function convertToOutputItem(items) {
1187
1782
  return output;
1188
1783
  }
1189
1784
  else if (item.type === 'function_call_output') {
1190
- const { call_id, status, output: rawOutput, name: toolName, function_name: functionName, ...providerData } = item;
1785
+ const { call_id, status, output: rawOutput, name: toolName, function_name: functionName, namespace, ...providerData } = item;
1191
1786
  const output = {
1192
1787
  type: 'function_call_result',
1193
1788
  id: item.id,
1194
1789
  callId: call_id,
1195
1790
  name: toolName ?? functionName ?? call_id,
1791
+ ...(typeof namespace === 'string' ? { namespace } : {}),
1196
1792
  status: status ?? 'completed',
1197
1793
  output: convertFunctionCallOutputToProtocol(rawOutput),
1198
1794
  providerData,
@@ -1200,13 +1796,18 @@ function convertToOutputItem(items) {
1200
1796
  return output;
1201
1797
  }
1202
1798
  else if (item.type === 'computer_call') {
1203
- const { call_id, status, action, ...providerData } = item;
1799
+ const { call_id, status, action, actions, ...providerData } = item;
1800
+ const normalizedActions = Array.isArray(actions) && actions.length > 0 ? actions : undefined;
1801
+ if (!normalizedActions && !action) {
1802
+ throw new UserError(`Unsupported computer call item without an action or actions: ${JSON.stringify(item)}`);
1803
+ }
1204
1804
  const output = {
1205
1805
  type: 'computer_call',
1206
1806
  id: item.id,
1207
1807
  callId: call_id,
1208
1808
  status,
1209
- action,
1809
+ action: action ?? normalizedActions?.[0],
1810
+ ...(normalizedActions ? { actions: normalizedActions } : {}),
1210
1811
  providerData,
1211
1812
  };
1212
1813
  return output;
@@ -1451,15 +2052,22 @@ export class OpenAIResponsesModel {
1451
2052
  this._client = client;
1452
2053
  this._model = model;
1453
2054
  }
2055
+ getRetryAdvice(args) {
2056
+ return getOpenAIRetryAdvice(args);
2057
+ }
1454
2058
  async _fetchResponse(request, stream) {
1455
2059
  const builtRequest = this._buildResponsesCreateRequest(request, stream);
1456
- const responsePromise = this._client.responses.create(builtRequest.requestData, {
2060
+ const requestOptions = {
1457
2061
  headers: builtRequest.sdkRequestHeaders,
1458
2062
  signal: builtRequest.signal,
1459
2063
  ...(builtRequest.transportExtraQuery
1460
2064
  ? { query: builtRequest.transportExtraQuery }
1461
2065
  : {}),
1462
- });
2066
+ };
2067
+ if (request._internal?.runnerManagedRetry === true) {
2068
+ requestOptions.maxRetries = 0;
2069
+ }
2070
+ const responsePromise = this._client.responses.create(builtRequest.requestData, requestOptions);
1463
2071
  let response;
1464
2072
  if (stream) {
1465
2073
  const withResponse = responsePromise
@@ -1486,9 +2094,30 @@ export class OpenAIResponsesModel {
1486
2094
  }
1487
2095
  _buildResponsesCreateRequest(request, stream) {
1488
2096
  const input = getInputItems(request.input);
1489
- const { tools, include } = getTools(request.tools, request.handoffs);
1490
- const toolChoice = getToolChoice(request.modelSettings.toolChoice);
2097
+ const prompt = getPrompt(request.prompt);
2098
+ // When a prompt template already declares a model, skip sending the agent's default model.
2099
+ // If the caller explicitly requests an override, include the resolved model name in the request.
2100
+ const shouldSendModel = !request.prompt || request.overridePromptModel === true;
2101
+ const effectiveRequestModel = shouldSendModel ? this._model : undefined;
1491
2102
  const { providerData: providerDataWithoutTransport, overrides: transportOverrides, } = splitResponsesTransportOverrides(request.modelSettings.providerData);
2103
+ const { tools, include } = getTools(request.tools, request.handoffs, {
2104
+ model: effectiveRequestModel,
2105
+ toolChoice: request.modelSettings.toolChoice,
2106
+ });
2107
+ const toolChoiceValidationTools = [
2108
+ ...tools,
2109
+ ...getExtraBodyToolsForToolChoiceValidation(transportOverrides.extraBody),
2110
+ ];
2111
+ const allowPromptSuppliedTools = Boolean(request.prompt) &&
2112
+ !(request.toolsExplicitlyProvided === true && tools.length === 0);
2113
+ const toolChoice = getToolChoice(request.modelSettings.toolChoice, {
2114
+ tools: toolChoiceValidationTools,
2115
+ model: effectiveRequestModel,
2116
+ allowPromptSuppliedComputerTool: allowPromptSuppliedTools,
2117
+ });
2118
+ assertSupportedToolChoice(toolChoice, toolChoiceValidationTools, {
2119
+ allowPromptSuppliedTools,
2120
+ });
1492
2121
  const { text, ...restOfProviderData } = providerDataWithoutTransport;
1493
2122
  if (request.modelSettings.reasoning) {
1494
2123
  // Merge top-level reasoning settings with provider data.
@@ -1503,25 +2132,19 @@ export class OpenAIResponsesModel {
1503
2132
  mergedText = { ...request.modelSettings.text, ...text };
1504
2133
  }
1505
2134
  const responseFormat = getResponseFormat(request.outputType, mergedText);
1506
- const prompt = getPrompt(request.prompt);
1507
2135
  let parallelToolCalls = undefined;
1508
2136
  if (typeof request.modelSettings.parallelToolCalls === 'boolean') {
1509
- if (request.modelSettings.parallelToolCalls && tools.length === 0) {
1510
- throw new Error('Parallel tool calls are not supported without tools');
1511
- }
1512
2137
  parallelToolCalls = request.modelSettings.parallelToolCalls;
1513
2138
  }
1514
- // When a prompt template already declares a model, skip sending the agent's default model.
1515
- // If the caller explicitly requests an override, include the resolved model name in the request.
1516
- const shouldSendModel = !request.prompt || request.overridePromptModel === true;
1517
2139
  const shouldSendTools = tools.length > 0 ||
1518
2140
  request.toolsExplicitlyProvided === true ||
1519
2141
  !request.prompt;
1520
- const shouldOmitToolChoice = Boolean(request.prompt) &&
1521
- !shouldSendTools &&
1522
- typeof toolChoice === 'object';
2142
+ const compatibleToolChoice = getCompatibleToolChoice(toolChoice, toolChoiceValidationTools, {
2143
+ allowPromptSuppliedTools,
2144
+ });
2145
+ const shouldOmitToolChoice = typeof compatibleToolChoice === 'undefined';
1523
2146
  let requestData = {
1524
- ...(shouldSendModel ? { model: this._model } : {}),
2147
+ ...(effectiveRequestModel ? { model: effectiveRequestModel } : {}),
1525
2148
  instructions: normalizeInstructions(request.systemInstructions),
1526
2149
  input,
1527
2150
  include,
@@ -1538,7 +2161,7 @@ export class OpenAIResponsesModel {
1538
2161
  truncation: request.modelSettings.truncation,
1539
2162
  max_output_tokens: request.modelSettings.maxTokens,
1540
2163
  ...(!shouldOmitToolChoice
1541
- ? { tool_choice: toolChoice }
2164
+ ? { tool_choice: compatibleToolChoice }
1542
2165
  : {}),
1543
2166
  parallel_tool_calls: parallelToolCalls,
1544
2167
  stream,
@@ -1729,6 +2352,23 @@ export class OpenAIResponsesWSModel extends OpenAIResponsesModel {
1729
2352
  this.#websocketBaseURL = options.websocketBaseURL;
1730
2353
  this.#reuseConnection = options.reuseConnection ?? true;
1731
2354
  }
2355
+ getRetryAdvice(args) {
2356
+ if (isNeverSentWebSocketError(args.error)) {
2357
+ return {
2358
+ suggested: true,
2359
+ replaySafety: 'safe',
2360
+ reason: args.error instanceof Error ? args.error.message : undefined,
2361
+ };
2362
+ }
2363
+ if (isAmbiguousWebSocketReplayError(args.error)) {
2364
+ return {
2365
+ suggested: false,
2366
+ replaySafety: 'unsafe',
2367
+ reason: args.error instanceof Error ? args.error.message : undefined,
2368
+ };
2369
+ }
2370
+ return super.getRetryAdvice(args);
2371
+ }
1732
2372
  async _fetchResponse(request, stream) {
1733
2373
  // The websocket transport always uses streamed Responses events, then callers either
1734
2374
  // consume the stream directly or collapse it into the final terminal response.
@@ -1737,12 +2377,22 @@ export class OpenAIResponsesWSModel extends OpenAIResponsesModel {
1737
2377
  return this.#iterWebSocketResponseEvents(builtRequest);
1738
2378
  }
1739
2379
  let finalResponse;
1740
- for await (const event of this.#iterWebSocketResponseEvents(builtRequest)) {
1741
- const eventType = event.type;
1742
- if (isTerminalResponsesStreamEventType(eventType)) {
1743
- finalResponse = event
1744
- .response;
2380
+ let receivedAnyEvent = false;
2381
+ try {
2382
+ for await (const event of this.#iterWebSocketResponseEvents(builtRequest)) {
2383
+ receivedAnyEvent = true;
2384
+ const eventType = event.type;
2385
+ if (isTerminalResponsesStreamEventType(eventType)) {
2386
+ finalResponse = event
2387
+ .response;
2388
+ }
2389
+ }
2390
+ }
2391
+ catch (error) {
2392
+ if (receivedAnyEvent && error instanceof Error) {
2393
+ error.unsafeToReplay = true;
1745
2394
  }
2395
+ throw error;
1746
2396
  }
1747
2397
  if (!finalResponse) {
1748
2398
  throw new Error('Responses websocket stream ended without a terminal response event.');