@zhivex-ai/openai 0.9.6 → 0.10.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.
package/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { toJSONSchema, z } from "zod";
2
2
  import { createOpenAIImageGenerationModel, normalizeOpenAIImageGenerationCall, normalizeOpenAIImageGenerationPartialImage } from "./image-generation.js";
3
- import { CallbackRealtimeSession, ConfigurationError, ProviderHTTPError, ProviderResponseTooLargeError, assertTrustedEndpoint, decodeBase64WithLimit, readBodyWithLimit, readErrorBodyWithLimit, readJsonWithLimit, resolveAudioResponseLimits, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, encodeAudioFrame, encodeMediaFrame, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, toToolSet, toolResultPayload, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
3
+ import { capabilities, groundedCapabilities, inferOpenAIRealtimeMode, isOpenAIGpt56Model, modelCapabilities, openAIRealtimeSupportsImageInput, realtimeCapabilities, speechCapabilities, supportsOpenAIApplyPatchAndSkills, supportsOpenAIShell, transcriptionCapabilities, } from "./capabilities.js";
4
+ import { CallbackRealtimeSession, ConfigurationError, ProviderHTTPError, ProviderResponseTooLargeError, ProviderToolCallError, assertTrustedEndpoint, decodeBase64WithLimit, readBodyWithLimit, readErrorBodyWithLimit, readJsonWithLimit, resolveAudioResponseLimits, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, encodeAudioFrame, encodeMediaFrame, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, toToolSet, toolResultPayload, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
4
5
  const MIB = 1024 * 1024;
5
6
  const DEFAULT_HOSTED_IMAGE_EVENT_BYTES = 32 * MIB;
6
7
  const DEFAULT_HOSTED_IMAGE_TOTAL_BYTES = 128 * MIB;
8
+ const DEFAULT_TOOL_CALL_ARGUMENT_CHARS = MIB;
7
9
  const HOSTED_IMAGE_EVENT_JSON_OVERHEAD_CHARS = MIB;
10
+ export const OPENAI_RESPONSES_TOOL_CALL_ERROR_CODE = "OPENAI_RESPONSES_TOOL_CALL_INVALID";
8
11
  const normalizeOpenAIResponseLimit = (value, fallback, name) => {
9
12
  const resolved = value ?? fallback;
10
13
  if (!Number.isSafeInteger(resolved) || resolved <= 0) {
@@ -15,7 +18,16 @@ const normalizeOpenAIResponseLimit = (value, fallback, name) => {
15
18
  const resolveOpenAIResponseLimits = (limits = {}) => ({
16
19
  ...resolveAudioResponseLimits(limits),
17
20
  hostedImageEventBytes: normalizeOpenAIResponseLimit(limits.hostedImageEventBytes, DEFAULT_HOSTED_IMAGE_EVENT_BYTES, "hostedImageEventBytes"),
18
- hostedImageTotalBytes: normalizeOpenAIResponseLimit(limits.hostedImageTotalBytes, DEFAULT_HOSTED_IMAGE_TOTAL_BYTES, "hostedImageTotalBytes")
21
+ hostedImageTotalBytes: normalizeOpenAIResponseLimit(limits.hostedImageTotalBytes, DEFAULT_HOSTED_IMAGE_TOTAL_BYTES, "hostedImageTotalBytes"),
22
+ toolCallArgumentChars: normalizeOpenAIResponseLimit(limits.toolCallArgumentChars, DEFAULT_TOOL_CALL_ARGUMENT_CHARS, "toolCallArgumentChars")
23
+ });
24
+ const openAIResponsesToolCallError = (reason, effectsPossible = false) => new ProviderToolCallError({
25
+ provider: "openai",
26
+ transport: "responses",
27
+ diagnosticCode: OPENAI_RESPONSES_TOOL_CALL_ERROR_CODE,
28
+ reason,
29
+ retryable: !effectsPossible,
30
+ effectsPossible
19
31
  });
20
32
  const openAIResponsesToolMetadataKey = "openai.responses_tool_type";
21
33
  const openAIResponsesFunctionConfigMetadataKey = "openai.responses_function_config";
@@ -45,174 +57,6 @@ const assertOpenAIToolPathInsideRoot = async (rootDir, targetPath, label) => {
45
57
  return target;
46
58
  };
47
59
  export const openAIRealtimeMcpMetadataKey = "openai.realtime_mcp";
48
- const capabilities = {
49
- streaming: true,
50
- tools: true,
51
- structuredOutput: true,
52
- jsonMode: true,
53
- toolChoice: true,
54
- parallelToolCalls: true,
55
- vision: true,
56
- files: false,
57
- audioInput: false,
58
- audioOutput: false,
59
- embeddings: true,
60
- reasoning: true,
61
- webSearch: true,
62
- agentCapabilities: {
63
- supportTier: "tier-a",
64
- toolChoiceNone: true,
65
- approvalRequests: true,
66
- hostedWebSearch: true,
67
- hostedFileSearch: true,
68
- remoteMcp: true,
69
- computerUse: true,
70
- codeExecution: true,
71
- shell: true,
72
- applyPatch: true,
73
- toolSearch: true,
74
- skills: true,
75
- toolsets: false
76
- }
77
- };
78
- const normalizeModelId = (modelId) => modelId.trim().toLowerCase();
79
- const isOpenAIGpt56Model = (modelId) => /^gpt-5\.6(?:$|-(?:sol|terra|luna)(?:-|$)|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
80
- const isOpenAIGpt55BaseModel = (modelId) => /^gpt-5\.5(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
81
- const isOpenAIGpt55ProModel = (modelId) => /^gpt-5\.5-pro(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
82
- const isOpenAIGpt54BaseModel = (modelId) => /^gpt-5\.4(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
83
- const isOpenAIGpt54MiniModel = (modelId) => /^gpt-5\.4-mini(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
84
- const isOpenAIGpt54NanoModel = (modelId) => /^gpt-5\.4-nano(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
85
- const isOpenAIGpt54ProModel = (modelId) => /^gpt-5\.4-pro(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
86
- const supportsOpenAIToolSearch = (modelId) => isOpenAIGpt56Model(modelId) || isOpenAIGpt55BaseModel(modelId) || isOpenAIGpt54BaseModel(modelId) || isOpenAIGpt54MiniModel(modelId);
87
- const supportsOpenAIComputerUse = (modelId) => isOpenAIGpt56Model(modelId) || isOpenAIGpt55BaseModel(modelId) || isOpenAIGpt54BaseModel(modelId) || isOpenAIGpt54MiniModel(modelId);
88
- const supportsOpenAIShell = (modelId) => isOpenAIGpt56Model(modelId) ||
89
- isOpenAIGpt55BaseModel(modelId) ||
90
- isOpenAIGpt55ProModel(modelId) ||
91
- isOpenAIGpt54BaseModel(modelId) ||
92
- isOpenAIGpt54MiniModel(modelId) ||
93
- isOpenAIGpt54NanoModel(modelId) ||
94
- isOpenAIGpt54ProModel(modelId);
95
- const supportsOpenAIApplyPatchAndSkills = (modelId) => isOpenAIGpt56Model(modelId) ||
96
- isOpenAIGpt55BaseModel(modelId) ||
97
- isOpenAIGpt54BaseModel(modelId) ||
98
- isOpenAIGpt54MiniModel(modelId) ||
99
- isOpenAIGpt54NanoModel(modelId);
100
- const supportsOpenAIChatAudio = (modelId) => {
101
- const normalized = normalizeModelId(modelId);
102
- return /^(?:gpt-audio(?:-|$)|gpt-4o(?:-mini)?-audio-preview(?:-|$))/.test(normalized);
103
- };
104
- const modelCapabilities = (modelId) => ({
105
- ...capabilities,
106
- explicitPromptCaching: isOpenAIGpt56Model(modelId),
107
- files: isOpenAIGpt56Model(modelId),
108
- reasoningEfforts: isOpenAIGpt56Model(modelId)
109
- ? ["none", "low", "medium", "high", "xhigh", "max"]
110
- : undefined,
111
- reasoningModes: isOpenAIGpt56Model(modelId) ? ["standard", "pro"] : undefined,
112
- reasoningContexts: isOpenAIGpt56Model(modelId) ? ["auto", "current_turn", "all_turns"] : undefined,
113
- audioInput: supportsOpenAIChatAudio(modelId),
114
- audioOutput: supportsOpenAIChatAudio(modelId),
115
- agentCapabilities: {
116
- ...capabilities.agentCapabilities,
117
- computerUse: supportsOpenAIComputerUse(modelId),
118
- shell: supportsOpenAIShell(modelId),
119
- applyPatch: supportsOpenAIApplyPatchAndSkills(modelId),
120
- skills: supportsOpenAIApplyPatchAndSkills(modelId),
121
- toolSearch: supportsOpenAIToolSearch(modelId),
122
- programmaticToolCalling: isOpenAIGpt56Model(modelId),
123
- multiAgent: isOpenAIGpt56Model(modelId)
124
- }
125
- });
126
- const transcriptionCapabilities = {
127
- ...capabilities,
128
- streaming: false,
129
- tools: false,
130
- structuredOutput: false,
131
- jsonMode: false,
132
- toolChoice: false,
133
- parallelToolCalls: false,
134
- vision: false,
135
- audioInput: true,
136
- audioOutput: false,
137
- embeddings: false,
138
- reasoning: false,
139
- webSearch: false,
140
- agentCapabilities: {
141
- supportTier: "tier-c",
142
- toolChoiceNone: false,
143
- approvalRequests: false,
144
- hostedWebSearch: false,
145
- hostedFileSearch: false,
146
- remoteMcp: false,
147
- computerUse: false,
148
- codeExecution: false,
149
- toolsets: false
150
- }
151
- };
152
- const speechCapabilities = {
153
- ...transcriptionCapabilities,
154
- audioInput: false,
155
- audioOutput: true
156
- };
157
- const groundedCapabilities = {
158
- ...capabilities,
159
- webSearch: true
160
- };
161
- const isOpenAIRealtimeTranslationModel = (modelId) => /^gpt-realtime-translate(?:[-@]|$)/.test(modelId);
162
- const isOpenAIRealtimeTranscriptionModel = (modelId) => /^gpt-realtime-whisper(?:[-@]|$)/.test(modelId);
163
- const inferOpenAIRealtimeMode = (modelId, mode) => {
164
- if (mode) {
165
- return mode;
166
- }
167
- if (isOpenAIRealtimeTranslationModel(modelId)) {
168
- return "translation";
169
- }
170
- if (isOpenAIRealtimeTranscriptionModel(modelId)) {
171
- return "transcription";
172
- }
173
- return "conversation";
174
- };
175
- const isOpenAIRealtimeReasoningModel = (modelId) => /^gpt-realtime-2(?:\.1(?:-mini)?)?(?:-\d{4}-\d{2}-\d{2}|@.*)?$/.test(modelId);
176
- const openAIRealtimeSupportsImageInput = (modelId) => /^(?:gpt-realtime|gpt-realtime-mini|gpt-realtime-1\.5|gpt-realtime-2(?:\.1(?:-mini)?)?)(?:-\d{4}-\d{2}-\d{2}|@.*)?$/.test(modelId);
177
- const realtimeCapabilities = (modelId) => ({
178
- ...capabilities,
179
- streaming: false,
180
- structuredOutput: false,
181
- jsonMode: false,
182
- embeddings: false,
183
- audioInput: true,
184
- audioOutput: !isOpenAIRealtimeTranscriptionModel(modelId),
185
- tools: !isOpenAIRealtimeTranslationModel(modelId) && !isOpenAIRealtimeTranscriptionModel(modelId),
186
- toolChoice: !isOpenAIRealtimeTranslationModel(modelId) && !isOpenAIRealtimeTranscriptionModel(modelId),
187
- parallelToolCalls: !isOpenAIRealtimeTranslationModel(modelId) && !isOpenAIRealtimeTranscriptionModel(modelId),
188
- vision: openAIRealtimeSupportsImageInput(modelId),
189
- reasoning: isOpenAIRealtimeReasoningModel(modelId),
190
- webSearch: false,
191
- agentCapabilities: {
192
- ...capabilities.agentCapabilities,
193
- supportTier: isOpenAIRealtimeTranslationModel(modelId) || isOpenAIRealtimeTranscriptionModel(modelId) ? "tier-c" : "tier-a",
194
- toolChoiceNone: !isOpenAIRealtimeTranslationModel(modelId) && !isOpenAIRealtimeTranscriptionModel(modelId),
195
- approvalRequests: !isOpenAIRealtimeTranslationModel(modelId) && !isOpenAIRealtimeTranscriptionModel(modelId),
196
- hostedWebSearch: false,
197
- hostedFileSearch: false,
198
- remoteMcp: !isOpenAIRealtimeTranslationModel(modelId) && !isOpenAIRealtimeTranscriptionModel(modelId),
199
- computerUse: false,
200
- codeExecution: false,
201
- shell: false,
202
- applyPatch: false,
203
- toolSearch: false,
204
- skills: false,
205
- toolsets: false
206
- },
207
- realtime: {
208
- sessions: true,
209
- audioInput: true,
210
- audioOutput: !isOpenAIRealtimeTranscriptionModel(modelId),
211
- imageInput: openAIRealtimeSupportsImageInput(modelId),
212
- tools: !isOpenAIRealtimeTranslationModel(modelId) && !isOpenAIRealtimeTranscriptionModel(modelId),
213
- browserTokens: true
214
- }
215
- });
216
60
  const jsonHeaders = (apiKey) => ({
217
61
  "content-type": "application/json",
218
62
  authorization: `Bearer ${apiKey}`
@@ -1220,6 +1064,23 @@ const parseAssistantMessage = (message) => ({
1220
1064
  })) ?? [])
1221
1065
  ]
1222
1066
  });
1067
+ const parseOpenAIResponsesToolArguments = (rawArguments, maxChars, effectsPossible = false) => {
1068
+ if (typeof rawArguments !== "string") {
1069
+ throw openAIResponsesToolCallError("incomplete_arguments", effectsPossible);
1070
+ }
1071
+ if (rawArguments.trim().length === 0) {
1072
+ throw openAIResponsesToolCallError("empty_arguments", effectsPossible);
1073
+ }
1074
+ if (rawArguments.length > maxChars) {
1075
+ throw openAIResponsesToolCallError("arguments_too_large", effectsPossible);
1076
+ }
1077
+ try {
1078
+ return JSON.parse(rawArguments);
1079
+ }
1080
+ catch {
1081
+ throw openAIResponsesToolCallError("invalid_json", effectsPossible);
1082
+ }
1083
+ };
1223
1084
  const extractAudioOutputs = (message) => message.parts
1224
1085
  .filter((part) => part.type === "audio")
1225
1086
  .map((part) => ({
@@ -1301,9 +1162,29 @@ const toResponsesInput = (messages) => {
1301
1162
  }
1302
1163
  return input;
1303
1164
  };
1304
- const parseResponsesAssistantMessage = (json, multiAgentEnabled = false, localTools = new Map()) => {
1165
+ const parseResponsesAssistantMessage = (json, multiAgentEnabled = false, localTools = new Map(), toolCallArgumentChars = DEFAULT_TOOL_CALL_ARGUMENT_CHARS) => {
1305
1166
  const parts = [];
1306
- for (const [index, item] of (json.output ?? []).entries()) {
1167
+ const output = Array.isArray(json.output) ? json.output : [];
1168
+ const responseStatus = typeof json.status === "string" ? json.status : undefined;
1169
+ const isExecutableToolOutput = (item) => item?.type === "function_call" ||
1170
+ (item?.type === "shell_call" && localTools.has("shell")) ||
1171
+ (item?.type === "apply_patch_call" && localTools.has("apply_patch")) ||
1172
+ (item?.type === "computer_call" && localTools.has("computer"));
1173
+ const hasExecutableToolOutput = output.some(isExecutableToolOutput);
1174
+ if (hasExecutableToolOutput && responseStatus !== "completed") {
1175
+ throw openAIResponsesToolCallError(responseStatus === "failed"
1176
+ ? "response_failed"
1177
+ : responseStatus === "incomplete"
1178
+ ? "response_incomplete"
1179
+ : "inconsistent_metadata");
1180
+ }
1181
+ const nonCompletedToolOutput = output.find((item) => isExecutableToolOutput(item) &&
1182
+ typeof item.status === "string" &&
1183
+ item.status !== "completed");
1184
+ if (nonCompletedToolOutput) {
1185
+ throw openAIResponsesToolCallError(nonCompletedToolOutput.status === "failed" ? "response_failed" : "incomplete_arguments");
1186
+ }
1187
+ for (const [index, item] of output.entries()) {
1307
1188
  if (item?.type === "message") {
1308
1189
  if (multiAgentEnabled && (item.agent?.agent_name !== "/root" || item.phase !== "final_answer")) {
1309
1190
  continue;
@@ -1319,13 +1200,21 @@ const parseResponsesAssistantMessage = (json, multiAgentEnabled = false, localTo
1319
1200
  continue;
1320
1201
  }
1321
1202
  if (item?.type === "function_call") {
1322
- const callId = item.call_id ?? item.id ?? `${item.name}-${index}`;
1203
+ if (typeof item.call_id !== "string" ||
1204
+ item.call_id.length === 0 ||
1205
+ typeof item.name !== "string" ||
1206
+ item.name.length === 0) {
1207
+ throw openAIResponsesToolCallError("inconsistent_metadata");
1208
+ }
1209
+ if (item.status === "incomplete" || item.status === "failed") {
1210
+ throw openAIResponsesToolCallError(item.status === "failed" ? "response_failed" : "incomplete_arguments");
1211
+ }
1323
1212
  parts.push({
1324
1213
  type: "tool-call",
1325
1214
  toolCall: {
1326
- id: callId,
1215
+ id: item.call_id,
1327
1216
  name: item.name,
1328
- input: JSON.parse(item.arguments ?? "{}"),
1217
+ input: parseOpenAIResponsesToolArguments(item.arguments, toolCallArgumentChars),
1329
1218
  ...(item.caller && typeof item.caller === "object"
1330
1219
  ? { providerMetadata: { caller: item.caller } }
1331
1220
  : {})
@@ -1404,6 +1293,9 @@ const extractResponsesImageOutputs = (response, outputFormat = "png") => (Array.
1404
1293
  return normalized?.image ? [normalized.image] : [];
1405
1294
  });
1406
1295
  const normalizeResponsesFinishReason = (status, hasToolCalls, hasRefusal = false) => {
1296
+ if (status === "failed") {
1297
+ return "error";
1298
+ }
1407
1299
  if (hasToolCalls) {
1408
1300
  return "tool-calls";
1409
1301
  }
@@ -1413,9 +1305,6 @@ const normalizeResponsesFinishReason = (status, hasToolCalls, hasRefusal = false
1413
1305
  if (status === "completed") {
1414
1306
  return "stop";
1415
1307
  }
1416
- if (status === "failed") {
1417
- return "error";
1418
- }
1419
1308
  return normalizeFinishReason(status);
1420
1309
  };
1421
1310
  const mapResponsesUsage = (usage) => usage
@@ -1453,14 +1342,19 @@ const addTokenUsage = (left, right) => {
1453
1342
  totalTokens: sum(left.totalTokens, right.totalTokens)
1454
1343
  };
1455
1344
  };
1456
- const streamResponses = async function* (response, multiAgentEnabled = false, localTools = new Map(), imageGeneration) {
1345
+ const streamResponses = async function* (response, multiAgentEnabled = false, localTools = new Map(), imageGeneration, toolCallArgumentChars = DEFAULT_TOOL_CALL_ARGUMENT_CHARS) {
1457
1346
  const toolBuffers = new Map();
1347
+ const toolBuffersByOutputIndex = new Map();
1348
+ const pendingExecutableEvents = [];
1349
+ const pendingExecutableProviderEvents = [];
1458
1350
  const outputAgents = new Map();
1459
1351
  const hostedImageEventMaxBytes = imageGeneration?.eventMaxBytes ?? DEFAULT_HOSTED_IMAGE_EVENT_BYTES;
1460
1352
  const hostedImageTotalMaxBytes = imageGeneration?.totalMaxBytes ?? DEFAULT_HOSTED_IMAGE_TOTAL_BYTES;
1461
1353
  let hostedImageBytes = 0;
1462
1354
  let sawToolCalls = false;
1463
1355
  let sawRefusal = false;
1356
+ let sawTerminalResponse = false;
1357
+ let nextToolOrder = 0;
1464
1358
  const recordHostedImageBytes = (image) => {
1465
1359
  const receivedBytes = hostedImageBytes + (image.data?.byteLength ?? 0);
1466
1360
  if (receivedBytes > hostedImageTotalMaxBytes) {
@@ -1473,32 +1367,155 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1473
1367
  }
1474
1368
  hostedImageBytes = receivedBytes;
1475
1369
  };
1476
- const emitToolCall = (key) => {
1477
- const toolCall = toolBuffers.get(key);
1478
- if (!toolCall || toolCall.emitted || !toolCall.name) {
1479
- return undefined;
1370
+ const toolBufferKey = (itemId, outputIndex) => {
1371
+ if (typeof itemId === "string" && itemId.length > 0) {
1372
+ return `item:${itemId}`;
1480
1373
  }
1481
- toolCall.emitted = true;
1482
- sawToolCalls = true;
1483
- return {
1484
- type: "tool-call",
1485
- toolCall: {
1486
- id: toolCall.callId,
1487
- name: toolCall.name,
1488
- input: JSON.parse(toolCall.args || "{}"),
1489
- ...(toolCall.caller !== undefined ? { providerMetadata: { caller: toolCall.caller } } : {})
1374
+ if (typeof outputIndex === "number" && Number.isSafeInteger(outputIndex) && outputIndex >= 0) {
1375
+ return `output:${outputIndex}`;
1376
+ }
1377
+ throw openAIResponsesToolCallError("inconsistent_metadata", sawToolCalls);
1378
+ };
1379
+ const getToolBuffer = (key, itemId, outputIndex) => {
1380
+ const canonicalItemId = typeof itemId === "string" && itemId.length > 0 ? itemId : undefined;
1381
+ const canonicalOutputIndex = typeof outputIndex === "number" && Number.isSafeInteger(outputIndex) && outputIndex >= 0
1382
+ ? outputIndex
1383
+ : undefined;
1384
+ const bindItemId = (toolCall) => {
1385
+ if (canonicalItemId !== undefined &&
1386
+ toolCall.itemId !== undefined &&
1387
+ toolCall.itemId !== canonicalItemId) {
1388
+ throw openAIResponsesToolCallError("inconsistent_metadata", sawToolCalls);
1389
+ }
1390
+ if (canonicalItemId !== undefined) {
1391
+ toolCall.itemId = canonicalItemId;
1490
1392
  }
1491
1393
  };
1394
+ const bindOutputIndex = (toolCall) => {
1395
+ if (canonicalOutputIndex === undefined) {
1396
+ return;
1397
+ }
1398
+ if (toolCall.outputIndex !== undefined && toolCall.outputIndex !== canonicalOutputIndex) {
1399
+ throw openAIResponsesToolCallError("inconsistent_metadata", sawToolCalls);
1400
+ }
1401
+ const indexed = toolBuffersByOutputIndex.get(canonicalOutputIndex);
1402
+ if (indexed && indexed !== toolCall) {
1403
+ throw openAIResponsesToolCallError("inconsistent_metadata", sawToolCalls);
1404
+ }
1405
+ toolCall.outputIndex = canonicalOutputIndex;
1406
+ toolBuffersByOutputIndex.set(canonicalOutputIndex, toolCall);
1407
+ };
1408
+ const existing = toolBuffers.get(key);
1409
+ if (existing) {
1410
+ bindItemId(existing);
1411
+ bindOutputIndex(existing);
1412
+ return existing;
1413
+ }
1414
+ if (canonicalOutputIndex !== undefined) {
1415
+ const indexed = toolBuffersByOutputIndex.get(canonicalOutputIndex);
1416
+ if (indexed) {
1417
+ bindItemId(indexed);
1418
+ toolBuffers.set(key, indexed);
1419
+ return indexed;
1420
+ }
1421
+ }
1422
+ const created = {
1423
+ ...(canonicalItemId !== undefined ? { itemId: canonicalItemId } : {}),
1424
+ deltaArguments: "",
1425
+ argumentsDone: false,
1426
+ outputItemDone: false,
1427
+ emitted: false,
1428
+ order: nextToolOrder++,
1429
+ ...(canonicalOutputIndex !== undefined ? { outputIndex: canonicalOutputIndex } : {})
1430
+ };
1431
+ toolBuffers.set(key, created);
1432
+ if (created.outputIndex !== undefined) {
1433
+ toolBuffersByOutputIndex.set(created.outputIndex, created);
1434
+ }
1435
+ return created;
1436
+ };
1437
+ const mergeToolString = (toolCall, field, value) => {
1438
+ if (typeof value !== "string" || value.length === 0) {
1439
+ return;
1440
+ }
1441
+ if (toolCall[field] !== undefined && toolCall[field] !== value) {
1442
+ throw openAIResponsesToolCallError("inconsistent_metadata", sawToolCalls);
1443
+ }
1444
+ toolCall[field] = value;
1445
+ };
1446
+ const ensureArgumentLimit = (argumentsValue) => {
1447
+ if (argumentsValue.length > toolCallArgumentChars) {
1448
+ throw openAIResponsesToolCallError("arguments_too_large", sawToolCalls);
1449
+ }
1450
+ };
1451
+ const setFinalArguments = (toolCall, argumentsValue) => {
1452
+ if (typeof argumentsValue !== "string") {
1453
+ return;
1454
+ }
1455
+ ensureArgumentLimit(argumentsValue);
1456
+ if (toolCall.finalArguments !== undefined && toolCall.finalArguments !== argumentsValue) {
1457
+ throw openAIResponsesToolCallError("inconsistent_metadata", sawToolCalls);
1458
+ }
1459
+ if (toolCall.deltaArguments.length > 0 && toolCall.deltaArguments !== argumentsValue) {
1460
+ throw openAIResponsesToolCallError("inconsistent_metadata", sawToolCalls);
1461
+ }
1462
+ toolCall.finalArguments = argumentsValue;
1463
+ };
1464
+ const materializeToolCalls = () => {
1465
+ const pending = [...new Set(toolBuffers.values())]
1466
+ .filter((toolCall) => !toolCall.emitted)
1467
+ .sort((left, right) => (left.outputIndex ?? Number.MAX_SAFE_INTEGER) - (right.outputIndex ?? Number.MAX_SAFE_INTEGER) ||
1468
+ left.order - right.order);
1469
+ const events = pending.map((toolCall) => {
1470
+ if (!toolCall.callId ||
1471
+ !toolCall.name ||
1472
+ (!toolCall.argumentsDone && !toolCall.outputItemDone)) {
1473
+ throw openAIResponsesToolCallError("incomplete_arguments", sawToolCalls);
1474
+ }
1475
+ if (toolCall.itemStatus === "failed" || toolCall.itemStatus === "incomplete") {
1476
+ throw openAIResponsesToolCallError(toolCall.itemStatus === "failed" ? "response_failed" : "incomplete_arguments", sawToolCalls);
1477
+ }
1478
+ const rawArguments = toolCall.finalArguments ?? toolCall.deltaArguments;
1479
+ const input = parseOpenAIResponsesToolArguments(rawArguments, toolCallArgumentChars, sawToolCalls);
1480
+ return {
1481
+ type: "tool-call",
1482
+ toolCall: {
1483
+ id: toolCall.callId,
1484
+ name: toolCall.name,
1485
+ input,
1486
+ ...(toolCall.caller !== undefined ? { providerMetadata: { caller: toolCall.caller } } : {})
1487
+ }
1488
+ };
1489
+ });
1490
+ for (const toolCall of pending) {
1491
+ toolCall.emitted = true;
1492
+ }
1493
+ if (events.length > 0) {
1494
+ sawToolCalls = true;
1495
+ }
1496
+ return events;
1492
1497
  };
1493
1498
  const maxHostedImageEventChars = Math.min(Number.MAX_SAFE_INTEGER, 4 * Math.ceil(hostedImageEventMaxBytes / 3) + HOSTED_IMAGE_EVENT_JSON_OVERHEAD_CHARS);
1494
1499
  for await (const event of streamSSE(response, imageGeneration
1495
1500
  ? { maxEventChars: maxHostedImageEventChars, maxBufferChars: maxHostedImageEventChars }
1496
1501
  : undefined)) {
1497
1502
  if (event.data === "[DONE]") {
1498
- return;
1503
+ break;
1504
+ }
1505
+ let json;
1506
+ try {
1507
+ json = JSON.parse(event.data);
1508
+ }
1509
+ catch (error) {
1510
+ if (toolBuffers.size > 0 || sawToolCalls) {
1511
+ throw openAIResponsesToolCallError("stream_truncated", sawToolCalls);
1512
+ }
1513
+ throw error;
1499
1514
  }
1500
- const json = JSON.parse(event.data);
1501
1515
  const type = json.type;
1516
+ if (sawTerminalResponse) {
1517
+ continue;
1518
+ }
1502
1519
  if (type === "response.image_generation_call.partial_image") {
1503
1520
  const normalized = normalizeOpenAIImageGenerationPartialImage(json, imageGeneration?.outputFormat ?? "png", hostedImageEventMaxBytes);
1504
1521
  if (normalized) {
@@ -1516,6 +1533,9 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1516
1533
  continue;
1517
1534
  }
1518
1535
  if (type === "error") {
1536
+ if (toolBuffers.size > 0 || pendingExecutableEvents.length > 0 || sawToolCalls) {
1537
+ throw openAIResponsesToolCallError("response_failed", sawToolCalls);
1538
+ }
1519
1539
  throw new ProviderHTTPError(`OpenAI Responses stream failed: ${json.error?.message ?? json.message ?? "unknown error"}`, 500, { responseBody: JSON.stringify(json) });
1520
1540
  }
1521
1541
  if (type === "response.output_text.delta" &&
@@ -1536,6 +1556,16 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1536
1556
  if (type === "response.output_item.added" || type === "response.output_item.done") {
1537
1557
  const item = json.item;
1538
1558
  let handledLocalToolCall = false;
1559
+ const isLocalExecutableItem = item?.type === "function_call" ||
1560
+ (item?.type === "shell_call" && localTools.has("shell")) ||
1561
+ (item?.type === "apply_patch_call" && localTools.has("apply_patch")) ||
1562
+ (item?.type === "computer_call" && localTools.has("computer"));
1563
+ if (type === "response.output_item.done" &&
1564
+ isLocalExecutableItem &&
1565
+ typeof item.status === "string" &&
1566
+ item.status !== "completed") {
1567
+ throw openAIResponsesToolCallError(item.status === "failed" ? "response_failed" : "incomplete_arguments", sawToolCalls);
1568
+ }
1539
1569
  if (item?.type === "image_generation_call" && type === "response.output_item.done") {
1540
1570
  const normalized = normalizeOpenAIImageGenerationCall(item, imageGeneration?.outputFormat ?? "png", hostedImageEventMaxBytes);
1541
1571
  if (normalized?.image) {
@@ -1556,31 +1586,26 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1556
1586
  });
1557
1587
  }
1558
1588
  if (item?.type === "function_call") {
1559
- const key = item.id ?? json.item_id ?? `${json.output_index ?? toolBuffers.size}`;
1560
- const existing = toolBuffers.get(key) ?? {
1561
- callId: item.call_id ?? key,
1562
- name: item.name ?? "",
1563
- args: "",
1564
- emitted: false
1565
- };
1566
- existing.callId = item.call_id ?? existing.callId;
1567
- existing.name ||= item.name ?? "";
1589
+ const itemId = item.id ?? json.item_id;
1590
+ const key = toolBufferKey(itemId, json.output_index);
1591
+ const existing = getToolBuffer(key, itemId, json.output_index);
1592
+ mergeToolString(existing, "callId", item.call_id);
1593
+ mergeToolString(existing, "name", item.name);
1568
1594
  if (item.caller && typeof item.caller === "object") {
1569
1595
  existing.caller = item.caller;
1570
1596
  }
1571
- if (typeof item.arguments === "string") {
1572
- existing.args = item.arguments;
1573
- }
1574
- toolBuffers.set(key, existing);
1575
1597
  if (type === "response.output_item.done") {
1576
- const emitted = emitToolCall(key);
1577
- if (emitted) {
1578
- yield emitted;
1579
- }
1598
+ existing.outputItemDone = true;
1599
+ existing.itemStatus = typeof item.status === "string" ? item.status : existing.itemStatus;
1600
+ setFinalArguments(existing, item.arguments);
1601
+ }
1602
+ else if (typeof item.arguments === "string" && item.arguments.length > 0) {
1603
+ ensureArgumentLimit(item.arguments);
1604
+ existing.deltaArguments = item.arguments;
1580
1605
  }
1581
1606
  }
1582
1607
  if (item?.type === "shell_call" && type === "response.output_item.done" && localTools.has("shell")) {
1583
- yield {
1608
+ pendingExecutableEvents.push({
1584
1609
  type: "tool-call",
1585
1610
  toolCall: {
1586
1611
  id: item.call_id ?? item.id ?? `${json.output_index ?? "shell"}`,
@@ -1588,12 +1613,11 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1588
1613
  input: parseShellCallInput(item),
1589
1614
  providerMetadata: { responsesToolType: "shell" }
1590
1615
  }
1591
- };
1592
- sawToolCalls = true;
1616
+ });
1593
1617
  handledLocalToolCall = true;
1594
1618
  }
1595
1619
  if (item?.type === "apply_patch_call" && type === "response.output_item.done" && localTools.has("apply_patch")) {
1596
- yield {
1620
+ pendingExecutableEvents.push({
1597
1621
  type: "tool-call",
1598
1622
  toolCall: {
1599
1623
  id: item.call_id ?? item.id ?? `${json.output_index ?? "apply_patch"}`,
@@ -1601,12 +1625,11 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1601
1625
  input: parseApplyPatchCallInput(item),
1602
1626
  providerMetadata: { responsesToolType: "apply_patch" }
1603
1627
  }
1604
- };
1605
- sawToolCalls = true;
1628
+ });
1606
1629
  handledLocalToolCall = true;
1607
1630
  }
1608
1631
  if (item?.type === "computer_call" && type === "response.output_item.done" && localTools.has("computer")) {
1609
- yield {
1632
+ pendingExecutableEvents.push({
1610
1633
  type: "tool-call",
1611
1634
  toolCall: {
1612
1635
  id: item.call_id ?? item.id ?? `${json.output_index ?? "computer"}`,
@@ -1616,8 +1639,7 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1616
1639
  },
1617
1640
  providerMetadata: { responsesToolType: "computer" }
1618
1641
  }
1619
- };
1620
- sawToolCalls = true;
1642
+ });
1621
1643
  handledLocalToolCall = true;
1622
1644
  }
1623
1645
  const providerData = parseResponsesProviderData(item);
@@ -1629,7 +1651,7 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1629
1651
  };
1630
1652
  }
1631
1653
  if (item && type === "response.output_item.done") {
1632
- yield {
1654
+ const outputEvent = {
1633
1655
  type: "provider-data",
1634
1656
  provider: "openai",
1635
1657
  data: {
@@ -1637,41 +1659,60 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1637
1659
  items: [item]
1638
1660
  }
1639
1661
  };
1662
+ if (item.type === "function_call" || handledLocalToolCall) {
1663
+ pendingExecutableProviderEvents.push(outputEvent);
1664
+ }
1665
+ else {
1666
+ yield outputEvent;
1667
+ }
1640
1668
  }
1641
1669
  continue;
1642
1670
  }
1643
1671
  if (type === "response.function_call_arguments.delta") {
1644
- const key = json.item_id ?? `${json.output_index ?? toolBuffers.size}`;
1645
- const existing = toolBuffers.get(key) ?? {
1646
- callId: key,
1647
- name: "",
1648
- args: "",
1649
- emitted: false
1650
- };
1651
- existing.args += typeof json.delta === "string" ? json.delta : "";
1652
- toolBuffers.set(key, existing);
1672
+ const key = toolBufferKey(json.item_id, json.output_index);
1673
+ const existing = getToolBuffer(key, json.item_id, json.output_index);
1674
+ mergeToolString(existing, "callId", json.call_id);
1675
+ mergeToolString(existing, "name", json.name);
1676
+ if (typeof json.delta === "string") {
1677
+ const nextArguments = existing.deltaArguments + json.delta;
1678
+ ensureArgumentLimit(nextArguments);
1679
+ existing.deltaArguments = nextArguments;
1680
+ }
1653
1681
  continue;
1654
1682
  }
1655
1683
  if (type === "response.function_call_arguments.done") {
1656
- const key = json.item_id ?? `${json.output_index ?? toolBuffers.size}`;
1657
- const existing = toolBuffers.get(key) ?? {
1658
- callId: key,
1659
- name: "",
1660
- args: "",
1661
- emitted: false
1662
- };
1663
- if (typeof json.arguments === "string") {
1664
- existing.args = json.arguments;
1665
- }
1666
- toolBuffers.set(key, existing);
1667
- const emitted = emitToolCall(key);
1668
- if (emitted) {
1669
- yield emitted;
1670
- }
1684
+ const key = toolBufferKey(json.item_id, json.output_index);
1685
+ const existing = getToolBuffer(key, json.item_id, json.output_index);
1686
+ mergeToolString(existing, "callId", json.call_id);
1687
+ mergeToolString(existing, "name", json.name);
1688
+ existing.argumentsDone = true;
1689
+ setFinalArguments(existing, json.arguments);
1671
1690
  continue;
1672
1691
  }
1673
1692
  if (type === "response.completed" || type === "response.failed" || type === "response.incomplete") {
1674
1693
  const responseData = json.response ?? {};
1694
+ const responseStatus = typeof responseData.status === "string"
1695
+ ? responseData.status
1696
+ : type.slice("response.".length);
1697
+ sawTerminalResponse = true;
1698
+ if (responseStatus === "completed") {
1699
+ for (const toolCallEvent of materializeToolCalls()) {
1700
+ yield toolCallEvent;
1701
+ }
1702
+ if (pendingExecutableEvents.length > 0) {
1703
+ sawToolCalls = true;
1704
+ for (const toolCallEvent of pendingExecutableEvents) {
1705
+ yield toolCallEvent;
1706
+ }
1707
+ }
1708
+ for (const providerEvent of pendingExecutableProviderEvents) {
1709
+ yield providerEvent;
1710
+ }
1711
+ }
1712
+ else if ((responseStatus === "failed" || responseStatus === "incomplete") &&
1713
+ (toolBuffers.size > 0 || pendingExecutableEvents.length > 0 || sawToolCalls)) {
1714
+ throw openAIResponsesToolCallError(responseStatus === "failed" ? "response_failed" : "response_incomplete", sawToolCalls);
1715
+ }
1675
1716
  if (typeof responseData.id === "string") {
1676
1717
  yield {
1677
1718
  type: "provider-data",
@@ -1681,12 +1722,17 @@ const streamResponses = async function* (response, multiAgentEnabled = false, lo
1681
1722
  }
1682
1723
  yield {
1683
1724
  type: "finish",
1684
- finishReason: normalizeResponsesFinishReason(responseData.status, sawToolCalls, sawRefusal),
1685
- providerFinishReason: responseData.status,
1725
+ finishReason: normalizeResponsesFinishReason(responseStatus, sawToolCalls, sawRefusal),
1726
+ providerFinishReason: responseStatus,
1686
1727
  usage: mapResponsesUsage(responseData.usage)
1687
1728
  };
1729
+ continue;
1688
1730
  }
1689
1731
  }
1732
+ if (!sawTerminalResponse &&
1733
+ (toolBuffers.size > 0 || pendingExecutableEvents.length > 0 || sawToolCalls)) {
1734
+ throw openAIResponsesToolCallError("stream_truncated", sawToolCalls);
1735
+ }
1690
1736
  };
1691
1737
  const streamGenerateResult = async function* (result) {
1692
1738
  for (const message of result.messages ?? (result.message ? [result.message] : [])) {
@@ -1805,7 +1851,7 @@ class OpenAILanguageModel {
1805
1851
  }), input);
1806
1852
  const json = await parseJson(response);
1807
1853
  accumulatedUsage = addTokenUsage(accumulatedUsage, mapResponsesUsage(json.usage));
1808
- const assistantMessage = parseResponsesAssistantMessage(json, options.multiAgentEnabled, localResponsesTools(input.tools));
1854
+ const assistantMessage = parseResponsesAssistantMessage(json, options.multiAgentEnabled, localResponsesTools(input.tools), this.responseLimits.toolCallArgumentChars);
1809
1855
  const currentOutput = Array.isArray(json.output)
1810
1856
  ? json.output
1811
1857
  : [];
@@ -1937,9 +1983,10 @@ class OpenAILanguageModel {
1937
1983
  })
1938
1984
  }), input);
1939
1985
  const imageGeneration = responsesImageGenerationConfig(input.tools, this.responseLimits);
1986
+ const toolCallArgumentChars = this.responseLimits.toolCallArgumentChars;
1940
1987
  return (async function* () {
1941
1988
  try {
1942
- yield* streamResponses(response, options.multiAgentEnabled, localResponsesTools(input.tools), imageGeneration);
1989
+ yield* streamResponses(response, options.multiAgentEnabled, localResponsesTools(input.tools), imageGeneration, toolCallArgumentChars);
1943
1990
  }
1944
1991
  finally {
1945
1992
  cleanup();