@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/README.md +237 -11
- package/dist/index.d.ts +101 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +780 -213
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
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:
|
|
77
|
-
applyPatch:
|
|
78
|
-
skills:
|
|
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) =>
|
|
167
|
-
|
|
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.
|
|
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
|
|
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
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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 (
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
:
|
|
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 === "
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 (
|
|
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
|
|
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 === "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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:
|
|
1563
|
+
headers: options.headers,
|
|
1088
1564
|
signal,
|
|
1089
1565
|
body: JSON.stringify({
|
|
1090
|
-
...
|
|
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
|
-
...
|
|
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
|
-
|
|
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:
|
|
1620
|
+
headers: options.headers,
|
|
1131
1621
|
signal,
|
|
1132
1622
|
body: JSON.stringify({
|
|
1133
|
-
...
|
|
1623
|
+
...responseBodyOptions,
|
|
1134
1624
|
model: this.modelId,
|
|
1135
|
-
|
|
1625
|
+
...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
|
|
1626
|
+
...(messages.length ? { input: toResponsesInput(messages) } : {}),
|
|
1136
1627
|
tools: mapResponsesTools(input.tools),
|
|
1137
|
-
tool_choice:
|
|
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
|
-
...
|
|
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:
|
|
1648
|
+
headers: options.headers,
|
|
1158
1649
|
signal,
|
|
1159
1650
|
body: JSON.stringify({
|
|
1160
|
-
...
|
|
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
|
-
...
|
|
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.
|
|
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:
|
|
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:
|
|
1885
|
+
headers: options.headers,
|
|
1382
1886
|
signal,
|
|
1383
1887
|
body: JSON.stringify({
|
|
1384
|
-
...
|
|
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
|
-
|
|
2209
|
+
const result = await config.execute(input);
|
|
2210
|
+
return Array.isArray(result) ? result : [result];
|
|
1669
2211
|
}
|
|
1670
|
-
const
|
|
1671
|
-
|
|
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 ??
|
|
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
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
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
|