@depup/ai-sdk__openai 3.0.41-depup.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/CHANGELOG.md +3101 -0
- package/LICENSE +13 -0
- package/README.md +25 -0
- package/changes.json +5 -0
- package/dist/index.d.mts +1107 -0
- package/dist/index.d.ts +1107 -0
- package/dist/index.js +6408 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6493 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal/index.d.mts +1137 -0
- package/dist/internal/index.d.ts +1137 -0
- package/dist/internal/index.js +6256 -0
- package/dist/internal/index.js.map +1 -0
- package/dist/internal/index.mjs +6306 -0
- package/dist/internal/index.mjs.map +1 -0
- package/docs/03-openai.mdx +2396 -0
- package/internal.d.ts +1 -0
- package/package.json +96 -0
- package/src/chat/convert-openai-chat-usage.ts +57 -0
- package/src/chat/convert-to-openai-chat-messages.ts +225 -0
- package/src/chat/get-response-metadata.ts +15 -0
- package/src/chat/map-openai-finish-reason.ts +19 -0
- package/src/chat/openai-chat-api.ts +198 -0
- package/src/chat/openai-chat-language-model.ts +703 -0
- package/src/chat/openai-chat-options.ts +192 -0
- package/src/chat/openai-chat-prepare-tools.ts +84 -0
- package/src/chat/openai-chat-prompt.ts +70 -0
- package/src/completion/convert-openai-completion-usage.ts +46 -0
- package/src/completion/convert-to-openai-completion-prompt.ts +93 -0
- package/src/completion/get-response-metadata.ts +15 -0
- package/src/completion/map-openai-finish-reason.ts +19 -0
- package/src/completion/openai-completion-api.ts +81 -0
- package/src/completion/openai-completion-language-model.ts +336 -0
- package/src/completion/openai-completion-options.ts +61 -0
- package/src/embedding/openai-embedding-api.ts +13 -0
- package/src/embedding/openai-embedding-model.ts +95 -0
- package/src/embedding/openai-embedding-options.ts +30 -0
- package/src/image/openai-image-api.ts +35 -0
- package/src/image/openai-image-model.ts +349 -0
- package/src/image/openai-image-options.ts +31 -0
- package/src/index.ts +23 -0
- package/src/internal/index.ts +19 -0
- package/src/openai-config.ts +18 -0
- package/src/openai-error.ts +22 -0
- package/src/openai-language-model-capabilities.ts +52 -0
- package/src/openai-provider.ts +270 -0
- package/src/openai-tools.ts +126 -0
- package/src/responses/convert-openai-responses-usage.ts +53 -0
- package/src/responses/convert-to-openai-responses-input.ts +735 -0
- package/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/responses/openai-responses-api.ts +1260 -0
- package/src/responses/openai-responses-language-model.ts +2098 -0
- package/src/responses/openai-responses-options.ts +299 -0
- package/src/responses/openai-responses-prepare-tools.ts +408 -0
- package/src/responses/openai-responses-provider-metadata.ts +62 -0
- package/src/speech/openai-speech-api.ts +38 -0
- package/src/speech/openai-speech-model.ts +137 -0
- package/src/speech/openai-speech-options.ts +26 -0
- package/src/tool/apply-patch.ts +141 -0
- package/src/tool/code-interpreter.ts +104 -0
- package/src/tool/custom.ts +64 -0
- package/src/tool/file-search.ts +145 -0
- package/src/tool/image-generation.ts +126 -0
- package/src/tool/local-shell.ts +72 -0
- package/src/tool/mcp.ts +125 -0
- package/src/tool/shell.ts +203 -0
- package/src/tool/web-search-preview.ts +141 -0
- package/src/tool/web-search.ts +181 -0
- package/src/transcription/openai-transcription-api.ts +37 -0
- package/src/transcription/openai-transcription-model.ts +232 -0
- package/src/transcription/openai-transcription-options.ts +53 -0
- package/src/transcription/transcription-test.mp3 +0 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LanguageModelV3Prompt,
|
|
3
|
+
LanguageModelV3ToolApprovalResponsePart,
|
|
4
|
+
SharedV3Warning,
|
|
5
|
+
UnsupportedFunctionalityError,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
7
|
+
import {
|
|
8
|
+
convertToBase64,
|
|
9
|
+
isNonNullable,
|
|
10
|
+
parseProviderOptions,
|
|
11
|
+
ToolNameMapping,
|
|
12
|
+
validateTypes,
|
|
13
|
+
} from '@ai-sdk/provider-utils';
|
|
14
|
+
import { z } from 'zod/v4';
|
|
15
|
+
import {
|
|
16
|
+
applyPatchInputSchema,
|
|
17
|
+
applyPatchOutputSchema,
|
|
18
|
+
} from '../tool/apply-patch';
|
|
19
|
+
import {
|
|
20
|
+
localShellInputSchema,
|
|
21
|
+
localShellOutputSchema,
|
|
22
|
+
} from '../tool/local-shell';
|
|
23
|
+
import { shellInputSchema, shellOutputSchema } from '../tool/shell';
|
|
24
|
+
import {
|
|
25
|
+
OpenAIResponsesCustomToolCallOutput,
|
|
26
|
+
OpenAIResponsesFunctionCallOutput,
|
|
27
|
+
OpenAIResponsesInput,
|
|
28
|
+
OpenAIResponsesReasoning,
|
|
29
|
+
} from './openai-responses-api';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if a string is a file ID based on the given prefixes
|
|
33
|
+
* Returns false if prefixes is undefined (disables file ID detection)
|
|
34
|
+
*/
|
|
35
|
+
function isFileId(data: string, prefixes?: readonly string[]): boolean {
|
|
36
|
+
if (!prefixes) return false;
|
|
37
|
+
return prefixes.some(prefix => data.startsWith(prefix));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function convertToOpenAIResponsesInput({
|
|
41
|
+
prompt,
|
|
42
|
+
toolNameMapping,
|
|
43
|
+
systemMessageMode,
|
|
44
|
+
providerOptionsName,
|
|
45
|
+
fileIdPrefixes,
|
|
46
|
+
store,
|
|
47
|
+
hasConversation = false,
|
|
48
|
+
hasLocalShellTool = false,
|
|
49
|
+
hasShellTool = false,
|
|
50
|
+
hasApplyPatchTool = false,
|
|
51
|
+
customProviderToolNames,
|
|
52
|
+
}: {
|
|
53
|
+
prompt: LanguageModelV3Prompt;
|
|
54
|
+
toolNameMapping: ToolNameMapping;
|
|
55
|
+
systemMessageMode: 'system' | 'developer' | 'remove';
|
|
56
|
+
providerOptionsName: string;
|
|
57
|
+
fileIdPrefixes?: readonly string[];
|
|
58
|
+
store: boolean;
|
|
59
|
+
hasConversation?: boolean; // when true, skip assistant messages that already have item IDs
|
|
60
|
+
hasLocalShellTool?: boolean;
|
|
61
|
+
hasShellTool?: boolean;
|
|
62
|
+
hasApplyPatchTool?: boolean;
|
|
63
|
+
customProviderToolNames?: Set<string>;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
input: OpenAIResponsesInput;
|
|
66
|
+
warnings: Array<SharedV3Warning>;
|
|
67
|
+
}> {
|
|
68
|
+
const input: OpenAIResponsesInput = [];
|
|
69
|
+
const warnings: Array<SharedV3Warning> = [];
|
|
70
|
+
const processedApprovalIds = new Set<string>();
|
|
71
|
+
|
|
72
|
+
for (const { role, content } of prompt) {
|
|
73
|
+
switch (role) {
|
|
74
|
+
case 'system': {
|
|
75
|
+
switch (systemMessageMode) {
|
|
76
|
+
case 'system': {
|
|
77
|
+
input.push({ role: 'system', content });
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case 'developer': {
|
|
81
|
+
input.push({ role: 'developer', content });
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case 'remove': {
|
|
85
|
+
warnings.push({
|
|
86
|
+
type: 'other',
|
|
87
|
+
message: 'system messages are removed for this model',
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
default: {
|
|
92
|
+
const _exhaustiveCheck: never = systemMessageMode;
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Unsupported system message mode: ${_exhaustiveCheck}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case 'user': {
|
|
102
|
+
input.push({
|
|
103
|
+
role: 'user',
|
|
104
|
+
content: content.map((part, index) => {
|
|
105
|
+
switch (part.type) {
|
|
106
|
+
case 'text': {
|
|
107
|
+
return { type: 'input_text', text: part.text };
|
|
108
|
+
}
|
|
109
|
+
case 'file': {
|
|
110
|
+
if (part.mediaType.startsWith('image/')) {
|
|
111
|
+
const mediaType =
|
|
112
|
+
part.mediaType === 'image/*'
|
|
113
|
+
? 'image/jpeg'
|
|
114
|
+
: part.mediaType;
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
type: 'input_image',
|
|
118
|
+
...(part.data instanceof URL
|
|
119
|
+
? { image_url: part.data.toString() }
|
|
120
|
+
: typeof part.data === 'string' &&
|
|
121
|
+
isFileId(part.data, fileIdPrefixes)
|
|
122
|
+
? { file_id: part.data }
|
|
123
|
+
: {
|
|
124
|
+
image_url: `data:${mediaType};base64,${convertToBase64(part.data)}`,
|
|
125
|
+
}),
|
|
126
|
+
detail:
|
|
127
|
+
part.providerOptions?.[providerOptionsName]?.imageDetail,
|
|
128
|
+
};
|
|
129
|
+
} else if (part.mediaType === 'application/pdf') {
|
|
130
|
+
if (part.data instanceof URL) {
|
|
131
|
+
return {
|
|
132
|
+
type: 'input_file',
|
|
133
|
+
file_url: part.data.toString(),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
type: 'input_file',
|
|
138
|
+
...(typeof part.data === 'string' &&
|
|
139
|
+
isFileId(part.data, fileIdPrefixes)
|
|
140
|
+
? { file_id: part.data }
|
|
141
|
+
: {
|
|
142
|
+
filename: part.filename ?? `part-${index}.pdf`,
|
|
143
|
+
file_data: `data:application/pdf;base64,${convertToBase64(part.data)}`,
|
|
144
|
+
}),
|
|
145
|
+
};
|
|
146
|
+
} else {
|
|
147
|
+
throw new UnsupportedFunctionalityError({
|
|
148
|
+
functionality: `file part media type ${part.mediaType}`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
case 'assistant': {
|
|
160
|
+
const reasoningMessages: Record<string, OpenAIResponsesReasoning> = {};
|
|
161
|
+
|
|
162
|
+
for (const part of content) {
|
|
163
|
+
switch (part.type) {
|
|
164
|
+
case 'text': {
|
|
165
|
+
const providerOpts = part.providerOptions?.[providerOptionsName];
|
|
166
|
+
const id = providerOpts?.itemId as string | undefined;
|
|
167
|
+
const phase = providerOpts?.phase as
|
|
168
|
+
| 'commentary'
|
|
169
|
+
| 'final_answer'
|
|
170
|
+
| null
|
|
171
|
+
| undefined;
|
|
172
|
+
|
|
173
|
+
// when using conversation, skip items that already exist in the conversation context to avoid "Duplicate item found" errors
|
|
174
|
+
if (hasConversation && id != null) {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// item references reduce the payload size
|
|
179
|
+
if (store && id != null) {
|
|
180
|
+
input.push({ type: 'item_reference', id });
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
input.push({
|
|
185
|
+
role: 'assistant',
|
|
186
|
+
content: [{ type: 'output_text', text: part.text }],
|
|
187
|
+
id,
|
|
188
|
+
...(phase != null && { phase }),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case 'tool-call': {
|
|
194
|
+
const id = (part.providerOptions?.[providerOptionsName]?.itemId ??
|
|
195
|
+
(
|
|
196
|
+
part as {
|
|
197
|
+
providerMetadata?: {
|
|
198
|
+
[providerOptionsName]?: { itemId?: string };
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
).providerMetadata?.[providerOptionsName]?.itemId) as
|
|
202
|
+
| string
|
|
203
|
+
| undefined;
|
|
204
|
+
|
|
205
|
+
if (hasConversation && id != null) {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (part.providerExecuted) {
|
|
210
|
+
if (store && id != null) {
|
|
211
|
+
input.push({ type: 'item_reference', id });
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (store && id != null) {
|
|
217
|
+
input.push({ type: 'item_reference', id });
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const resolvedToolName = toolNameMapping.toProviderToolName(
|
|
222
|
+
part.toolName,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (hasLocalShellTool && resolvedToolName === 'local_shell') {
|
|
226
|
+
const parsedInput = await validateTypes({
|
|
227
|
+
value: part.input,
|
|
228
|
+
schema: localShellInputSchema,
|
|
229
|
+
});
|
|
230
|
+
input.push({
|
|
231
|
+
type: 'local_shell_call',
|
|
232
|
+
call_id: part.toolCallId,
|
|
233
|
+
id: id!,
|
|
234
|
+
action: {
|
|
235
|
+
type: 'exec',
|
|
236
|
+
command: parsedInput.action.command,
|
|
237
|
+
timeout_ms: parsedInput.action.timeoutMs,
|
|
238
|
+
user: parsedInput.action.user,
|
|
239
|
+
working_directory: parsedInput.action.workingDirectory,
|
|
240
|
+
env: parsedInput.action.env,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (hasShellTool && resolvedToolName === 'shell') {
|
|
248
|
+
const parsedInput = await validateTypes({
|
|
249
|
+
value: part.input,
|
|
250
|
+
schema: shellInputSchema,
|
|
251
|
+
});
|
|
252
|
+
input.push({
|
|
253
|
+
type: 'shell_call',
|
|
254
|
+
call_id: part.toolCallId,
|
|
255
|
+
id: id!,
|
|
256
|
+
status: 'completed',
|
|
257
|
+
action: {
|
|
258
|
+
commands: parsedInput.action.commands,
|
|
259
|
+
timeout_ms: parsedInput.action.timeoutMs,
|
|
260
|
+
max_output_length: parsedInput.action.maxOutputLength,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (hasApplyPatchTool && resolvedToolName === 'apply_patch') {
|
|
268
|
+
const parsedInput = await validateTypes({
|
|
269
|
+
value: part.input,
|
|
270
|
+
schema: applyPatchInputSchema,
|
|
271
|
+
});
|
|
272
|
+
input.push({
|
|
273
|
+
type: 'apply_patch_call',
|
|
274
|
+
call_id: parsedInput.callId,
|
|
275
|
+
id: id!,
|
|
276
|
+
status: 'completed',
|
|
277
|
+
operation: parsedInput.operation,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (customProviderToolNames?.has(resolvedToolName)) {
|
|
284
|
+
input.push({
|
|
285
|
+
type: 'custom_tool_call',
|
|
286
|
+
call_id: part.toolCallId,
|
|
287
|
+
name: resolvedToolName,
|
|
288
|
+
input:
|
|
289
|
+
typeof part.input === 'string'
|
|
290
|
+
? part.input
|
|
291
|
+
: JSON.stringify(part.input),
|
|
292
|
+
id,
|
|
293
|
+
});
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
input.push({
|
|
298
|
+
type: 'function_call',
|
|
299
|
+
call_id: part.toolCallId,
|
|
300
|
+
name: resolvedToolName,
|
|
301
|
+
arguments: JSON.stringify(part.input),
|
|
302
|
+
id,
|
|
303
|
+
});
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// assistant tool result parts are from provider-executed tools:
|
|
308
|
+
case 'tool-result': {
|
|
309
|
+
// Skip execution-denied results - these are synthetic results from denied
|
|
310
|
+
// approvals and have no corresponding item in OpenAI's store.
|
|
311
|
+
// Check both the direct type and if it was transformed to json with execution-denied inside
|
|
312
|
+
if (
|
|
313
|
+
part.output.type === 'execution-denied' ||
|
|
314
|
+
(part.output.type === 'json' &&
|
|
315
|
+
typeof part.output.value === 'object' &&
|
|
316
|
+
part.output.value != null &&
|
|
317
|
+
'type' in part.output.value &&
|
|
318
|
+
part.output.value.type === 'execution-denied')
|
|
319
|
+
) {
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (hasConversation) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const resolvedResultToolName = toolNameMapping.toProviderToolName(
|
|
328
|
+
part.toolName,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
/*
|
|
332
|
+
* Shell tool results are separate output items (shell_call_output)
|
|
333
|
+
* with their own item IDs distinct from the shell_call's item ID.
|
|
334
|
+
* Since the pipeline only preserves the shell_call's item ID in
|
|
335
|
+
* callProviderMetadata, we reconstruct the full shell_call_output
|
|
336
|
+
* instead of using an item_reference with the wrong ID.
|
|
337
|
+
*/
|
|
338
|
+
if (hasShellTool && resolvedResultToolName === 'shell') {
|
|
339
|
+
if (part.output.type === 'json') {
|
|
340
|
+
const parsedOutput = await validateTypes({
|
|
341
|
+
value: part.output.value,
|
|
342
|
+
schema: shellOutputSchema,
|
|
343
|
+
});
|
|
344
|
+
input.push({
|
|
345
|
+
type: 'shell_call_output',
|
|
346
|
+
call_id: part.toolCallId,
|
|
347
|
+
output: parsedOutput.output.map(item => ({
|
|
348
|
+
stdout: item.stdout,
|
|
349
|
+
stderr: item.stderr,
|
|
350
|
+
outcome:
|
|
351
|
+
item.outcome.type === 'timeout'
|
|
352
|
+
? { type: 'timeout' as const }
|
|
353
|
+
: {
|
|
354
|
+
type: 'exit' as const,
|
|
355
|
+
exit_code: item.outcome.exitCode,
|
|
356
|
+
},
|
|
357
|
+
})),
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (store) {
|
|
364
|
+
const itemId =
|
|
365
|
+
(
|
|
366
|
+
part.providerOptions?.[providerOptionsName] as
|
|
367
|
+
| { itemId?: string }
|
|
368
|
+
| undefined
|
|
369
|
+
)?.itemId ?? part.toolCallId;
|
|
370
|
+
input.push({ type: 'item_reference', id: itemId });
|
|
371
|
+
} else {
|
|
372
|
+
warnings.push({
|
|
373
|
+
type: 'other',
|
|
374
|
+
message: `Results for OpenAI tool ${part.toolName} are not sent to the API when store is false`,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
case 'reasoning': {
|
|
382
|
+
const providerOptions = await parseProviderOptions({
|
|
383
|
+
provider: providerOptionsName,
|
|
384
|
+
providerOptions: part.providerOptions,
|
|
385
|
+
schema: openaiResponsesReasoningProviderOptionsSchema,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const reasoningId = providerOptions?.itemId;
|
|
389
|
+
|
|
390
|
+
if (hasConversation && reasoningId != null) {
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (reasoningId != null) {
|
|
395
|
+
const reasoningMessage = reasoningMessages[reasoningId];
|
|
396
|
+
|
|
397
|
+
if (store) {
|
|
398
|
+
// use item references to refer to reasoning (single reference)
|
|
399
|
+
// when the first part is encountered
|
|
400
|
+
if (reasoningMessage === undefined) {
|
|
401
|
+
input.push({ type: 'item_reference', id: reasoningId });
|
|
402
|
+
|
|
403
|
+
// store unused reasoning message to mark id as used
|
|
404
|
+
reasoningMessages[reasoningId] = {
|
|
405
|
+
type: 'reasoning',
|
|
406
|
+
id: reasoningId,
|
|
407
|
+
summary: [],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
const summaryParts: Array<{
|
|
412
|
+
type: 'summary_text';
|
|
413
|
+
text: string;
|
|
414
|
+
}> = [];
|
|
415
|
+
|
|
416
|
+
if (part.text.length > 0) {
|
|
417
|
+
summaryParts.push({
|
|
418
|
+
type: 'summary_text',
|
|
419
|
+
text: part.text,
|
|
420
|
+
});
|
|
421
|
+
} else if (reasoningMessage !== undefined) {
|
|
422
|
+
warnings.push({
|
|
423
|
+
type: 'other',
|
|
424
|
+
message: `Cannot append empty reasoning part to existing reasoning sequence. Skipping reasoning part: ${JSON.stringify(part)}.`,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (reasoningMessage === undefined) {
|
|
429
|
+
reasoningMessages[reasoningId] = {
|
|
430
|
+
type: 'reasoning',
|
|
431
|
+
id: reasoningId,
|
|
432
|
+
encrypted_content:
|
|
433
|
+
providerOptions?.reasoningEncryptedContent,
|
|
434
|
+
summary: summaryParts,
|
|
435
|
+
};
|
|
436
|
+
input.push(reasoningMessages[reasoningId]);
|
|
437
|
+
} else {
|
|
438
|
+
reasoningMessage.summary.push(...summaryParts);
|
|
439
|
+
|
|
440
|
+
// updated encrypted content to enable setting it in the last summary part:
|
|
441
|
+
if (providerOptions?.reasoningEncryptedContent != null) {
|
|
442
|
+
reasoningMessage.encrypted_content =
|
|
443
|
+
providerOptions.reasoningEncryptedContent;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
// No itemId — fall back to encrypted_content if available.
|
|
449
|
+
// The OpenAI Responses API accepts reasoning items without an
|
|
450
|
+
// id when encrypted_content is provided, enabling multi-turn
|
|
451
|
+
// reasoning even when server-side item persistence is not used
|
|
452
|
+
// or when itemId has been stripped from providerOptions.
|
|
453
|
+
const encryptedContent =
|
|
454
|
+
providerOptions?.reasoningEncryptedContent;
|
|
455
|
+
|
|
456
|
+
if (encryptedContent != null) {
|
|
457
|
+
const summaryParts: Array<{
|
|
458
|
+
type: 'summary_text';
|
|
459
|
+
text: string;
|
|
460
|
+
}> = [];
|
|
461
|
+
if (part.text.length > 0) {
|
|
462
|
+
summaryParts.push({
|
|
463
|
+
type: 'summary_text',
|
|
464
|
+
text: part.text,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
input.push({
|
|
468
|
+
type: 'reasoning',
|
|
469
|
+
encrypted_content: encryptedContent,
|
|
470
|
+
summary: summaryParts,
|
|
471
|
+
});
|
|
472
|
+
} else {
|
|
473
|
+
warnings.push({
|
|
474
|
+
type: 'other',
|
|
475
|
+
message: `Non-OpenAI reasoning parts are not supported. Skipping reasoning part: ${JSON.stringify(part)}.`,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
case 'tool': {
|
|
488
|
+
for (const part of content) {
|
|
489
|
+
if (part.type === 'tool-approval-response') {
|
|
490
|
+
const approvalResponse =
|
|
491
|
+
part as LanguageModelV3ToolApprovalResponsePart;
|
|
492
|
+
|
|
493
|
+
if (processedApprovalIds.has(approvalResponse.approvalId)) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
processedApprovalIds.add(approvalResponse.approvalId);
|
|
497
|
+
|
|
498
|
+
if (store) {
|
|
499
|
+
input.push({
|
|
500
|
+
type: 'item_reference',
|
|
501
|
+
id: approvalResponse.approvalId,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
input.push({
|
|
506
|
+
type: 'mcp_approval_response',
|
|
507
|
+
approval_request_id: approvalResponse.approvalId,
|
|
508
|
+
approve: approvalResponse.approved,
|
|
509
|
+
});
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const output = part.output;
|
|
514
|
+
|
|
515
|
+
// Skip execution-denied with approvalId - already handled via tool-approval-response
|
|
516
|
+
if (output.type === 'execution-denied') {
|
|
517
|
+
const approvalId = (
|
|
518
|
+
output.providerOptions?.openai as { approvalId?: string }
|
|
519
|
+
)?.approvalId;
|
|
520
|
+
|
|
521
|
+
if (approvalId) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const resolvedToolName = toolNameMapping.toProviderToolName(
|
|
527
|
+
part.toolName,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
if (
|
|
531
|
+
hasLocalShellTool &&
|
|
532
|
+
resolvedToolName === 'local_shell' &&
|
|
533
|
+
output.type === 'json'
|
|
534
|
+
) {
|
|
535
|
+
const parsedOutput = await validateTypes({
|
|
536
|
+
value: output.value,
|
|
537
|
+
schema: localShellOutputSchema,
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
input.push({
|
|
541
|
+
type: 'local_shell_call_output',
|
|
542
|
+
call_id: part.toolCallId,
|
|
543
|
+
output: parsedOutput.output,
|
|
544
|
+
});
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (
|
|
549
|
+
hasShellTool &&
|
|
550
|
+
resolvedToolName === 'shell' &&
|
|
551
|
+
output.type === 'json'
|
|
552
|
+
) {
|
|
553
|
+
const parsedOutput = await validateTypes({
|
|
554
|
+
value: output.value,
|
|
555
|
+
schema: shellOutputSchema,
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
input.push({
|
|
559
|
+
type: 'shell_call_output',
|
|
560
|
+
call_id: part.toolCallId,
|
|
561
|
+
output: parsedOutput.output.map(item => ({
|
|
562
|
+
stdout: item.stdout,
|
|
563
|
+
stderr: item.stderr,
|
|
564
|
+
outcome:
|
|
565
|
+
item.outcome.type === 'timeout'
|
|
566
|
+
? { type: 'timeout' as const }
|
|
567
|
+
: {
|
|
568
|
+
type: 'exit' as const,
|
|
569
|
+
exit_code: item.outcome.exitCode,
|
|
570
|
+
},
|
|
571
|
+
})),
|
|
572
|
+
});
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (
|
|
577
|
+
hasApplyPatchTool &&
|
|
578
|
+
part.toolName === 'apply_patch' &&
|
|
579
|
+
output.type === 'json'
|
|
580
|
+
) {
|
|
581
|
+
const parsedOutput = await validateTypes({
|
|
582
|
+
value: output.value,
|
|
583
|
+
schema: applyPatchOutputSchema,
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
input.push({
|
|
587
|
+
type: 'apply_patch_call_output',
|
|
588
|
+
call_id: part.toolCallId,
|
|
589
|
+
status: parsedOutput.status,
|
|
590
|
+
output: parsedOutput.output,
|
|
591
|
+
});
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (customProviderToolNames?.has(resolvedToolName)) {
|
|
596
|
+
let outputValue: OpenAIResponsesCustomToolCallOutput['output'];
|
|
597
|
+
switch (output.type) {
|
|
598
|
+
case 'text':
|
|
599
|
+
case 'error-text':
|
|
600
|
+
outputValue = output.value;
|
|
601
|
+
break;
|
|
602
|
+
case 'execution-denied':
|
|
603
|
+
outputValue = output.reason ?? 'Tool execution denied.';
|
|
604
|
+
break;
|
|
605
|
+
case 'json':
|
|
606
|
+
case 'error-json':
|
|
607
|
+
outputValue = JSON.stringify(output.value);
|
|
608
|
+
break;
|
|
609
|
+
case 'content':
|
|
610
|
+
outputValue = output.value
|
|
611
|
+
.map(item => {
|
|
612
|
+
switch (item.type) {
|
|
613
|
+
case 'text':
|
|
614
|
+
return { type: 'input_text' as const, text: item.text };
|
|
615
|
+
case 'image-data':
|
|
616
|
+
return {
|
|
617
|
+
type: 'input_image' as const,
|
|
618
|
+
image_url: `data:${item.mediaType};base64,${item.data}`,
|
|
619
|
+
};
|
|
620
|
+
case 'image-url':
|
|
621
|
+
return {
|
|
622
|
+
type: 'input_image' as const,
|
|
623
|
+
image_url: item.url,
|
|
624
|
+
};
|
|
625
|
+
case 'file-data':
|
|
626
|
+
return {
|
|
627
|
+
type: 'input_file' as const,
|
|
628
|
+
filename: item.filename ?? 'data',
|
|
629
|
+
file_data: `data:${item.mediaType};base64,${item.data}`,
|
|
630
|
+
};
|
|
631
|
+
default:
|
|
632
|
+
warnings.push({
|
|
633
|
+
type: 'other',
|
|
634
|
+
message: `unsupported custom tool content part type: ${item.type}`,
|
|
635
|
+
});
|
|
636
|
+
return undefined;
|
|
637
|
+
}
|
|
638
|
+
})
|
|
639
|
+
.filter(isNonNullable);
|
|
640
|
+
break;
|
|
641
|
+
default:
|
|
642
|
+
outputValue = '';
|
|
643
|
+
}
|
|
644
|
+
input.push({
|
|
645
|
+
type: 'custom_tool_call_output',
|
|
646
|
+
call_id: part.toolCallId,
|
|
647
|
+
output: outputValue,
|
|
648
|
+
} satisfies OpenAIResponsesCustomToolCallOutput);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
let contentValue: OpenAIResponsesFunctionCallOutput['output'];
|
|
653
|
+
switch (output.type) {
|
|
654
|
+
case 'text':
|
|
655
|
+
case 'error-text':
|
|
656
|
+
contentValue = output.value;
|
|
657
|
+
break;
|
|
658
|
+
case 'execution-denied':
|
|
659
|
+
contentValue = output.reason ?? 'Tool execution denied.';
|
|
660
|
+
break;
|
|
661
|
+
case 'json':
|
|
662
|
+
case 'error-json':
|
|
663
|
+
contentValue = JSON.stringify(output.value);
|
|
664
|
+
break;
|
|
665
|
+
case 'content':
|
|
666
|
+
contentValue = output.value
|
|
667
|
+
.map(item => {
|
|
668
|
+
switch (item.type) {
|
|
669
|
+
case 'text': {
|
|
670
|
+
return { type: 'input_text' as const, text: item.text };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
case 'image-data': {
|
|
674
|
+
return {
|
|
675
|
+
type: 'input_image' as const,
|
|
676
|
+
image_url: `data:${item.mediaType};base64,${item.data}`,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
case 'image-url': {
|
|
681
|
+
return {
|
|
682
|
+
type: 'input_image' as const,
|
|
683
|
+
image_url: item.url,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
case 'file-data': {
|
|
688
|
+
return {
|
|
689
|
+
type: 'input_file' as const,
|
|
690
|
+
filename: item.filename ?? 'data',
|
|
691
|
+
file_data: `data:${item.mediaType};base64,${item.data}`,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
default: {
|
|
696
|
+
warnings.push({
|
|
697
|
+
type: 'other',
|
|
698
|
+
message: `unsupported tool content part type: ${item.type}`,
|
|
699
|
+
});
|
|
700
|
+
return undefined;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
})
|
|
704
|
+
.filter(isNonNullable);
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
input.push({
|
|
709
|
+
type: 'function_call_output',
|
|
710
|
+
call_id: part.toolCallId,
|
|
711
|
+
output: contentValue,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
default: {
|
|
719
|
+
const _exhaustiveCheck: never = role;
|
|
720
|
+
throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return { input, warnings };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const openaiResponsesReasoningProviderOptionsSchema = z.object({
|
|
729
|
+
itemId: z.string().nullish(),
|
|
730
|
+
reasoningEncryptedContent: z.string().nullish(),
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
export type OpenAIResponsesReasoningProviderOptions = z.infer<
|
|
734
|
+
typeof openaiResponsesReasoningProviderOptionsSchema
|
|
735
|
+
>;
|