@ai-sdk/openai 4.0.51 → 4.0.53

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/openai",
3
- "version": "4.0.51",
3
+ "version": "4.0.53",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -35,8 +35,8 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@ai-sdk/provider": "4.0.8",
39
- "@ai-sdk/provider-utils": "5.0.33"
38
+ "@ai-sdk/provider": "4.0.9",
39
+ "@ai-sdk/provider-utils": "5.0.34"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.1",
@@ -3,10 +3,17 @@ import type {
3
3
  WebSocketConstructor,
4
4
  } from '@ai-sdk/provider-utils';
5
5
 
6
+ type OpenAIHeaders = Record<string, string | undefined>;
7
+
8
+ type SerializedOpenAIConfig = Omit<Partial<OpenAIConfig>, 'headers'> & {
9
+ headers?: (() => OpenAIHeaders) | OpenAIHeaders;
10
+ };
11
+
6
12
  export type OpenAIConfig = {
7
13
  provider: string;
14
+ baseURL?: string;
8
15
  url: (options: { modelId: string; path: string }) => string;
9
- headers?: () => Record<string, string | undefined>;
16
+ headers?: () => OpenAIHeaders;
10
17
  fetch?: FetchFunction;
11
18
  webSocket?: WebSocketConstructor;
12
19
  generateId?: () => string;
@@ -20,3 +27,36 @@ export type OpenAIConfig = {
20
27
  */
21
28
  fileIdPrefixes?: readonly string[];
22
29
  };
30
+
31
+ export function prepareOpenAIConfigForWorkflowDeserialize(
32
+ config: SerializedOpenAIConfig,
33
+ ): OpenAIConfig {
34
+ if (config.provider == null) {
35
+ throw new Error(
36
+ 'OpenAI model is missing provider after workflow deserialization.',
37
+ );
38
+ }
39
+
40
+ return {
41
+ ...config,
42
+ provider: config.provider,
43
+ url:
44
+ typeof config.url === 'function'
45
+ ? config.url
46
+ : ({ path }) => {
47
+ if (config.baseURL == null) {
48
+ throw new Error(
49
+ 'OpenAI model is missing baseURL after workflow deserialization.',
50
+ );
51
+ }
52
+
53
+ return `${config.baseURL}${path}`;
54
+ },
55
+ headers:
56
+ typeof config.headers === 'function'
57
+ ? config.headers
58
+ : config.headers == null
59
+ ? undefined
60
+ : () => config.headers as OpenAIHeaders,
61
+ };
62
+ }
@@ -308,6 +308,7 @@ export function createOpenAI(
308
308
  const createResponsesModel = (modelId: OpenAIResponsesModelId) => {
309
309
  return new OpenAIResponsesBatchLanguageModel(modelId, {
310
310
  provider: `${providerName}.responses`,
311
+ baseURL,
311
312
  url: ({ path }) => `${baseURL}${path}`,
312
313
  headers: getHeaders,
313
314
  fetch: options.fetch,
@@ -33,7 +33,10 @@ import {
33
33
  openaiErrorDataSchema,
34
34
  openaiFailedResponseHandler,
35
35
  } from './openai-error';
36
- import type { OpenAIConfig } from './openai-config';
36
+ import {
37
+ prepareOpenAIConfigForWorkflowDeserialize,
38
+ type OpenAIConfig,
39
+ } from './openai-config';
37
40
  import { openaiFilesResponseSchema } from './files/openai-files-api';
38
41
  import { convertOpenAIResponsesUsage } from './responses/convert-openai-responses-usage';
39
42
  import { mapOpenAIResponseFinishReason } from './responses/map-openai-responses-finish-reason';
@@ -393,12 +396,12 @@ export class OpenAIResponsesBatchLanguageModel
393
396
  }
394
397
 
395
398
  static [WORKFLOW_DESERIALIZE](options: {
396
- modelId: OpenAIResponsesModelId;
397
- config: OpenAIConfig;
399
+ modelId: string;
400
+ config: Parameters<typeof prepareOpenAIConfigForWorkflowDeserialize>[0];
398
401
  }) {
399
402
  return new OpenAIResponsesBatchLanguageModel(
400
- options.modelId,
401
- options.config,
403
+ options.modelId as OpenAIResponsesModelId,
404
+ prepareOpenAIConfigForWorkflowDeserialize(options.config),
402
405
  );
403
406
  }
404
407
 
@@ -71,12 +71,14 @@ async function convertFunctionToolResultOutput({
71
71
  output,
72
72
  toolName,
73
73
  outputSchemaToolNames,
74
+ promptCacheBreakpoint,
74
75
  providerOptionsName,
75
76
  warnings,
76
77
  }: {
77
78
  output: LanguageModelV4ToolResultOutput;
78
79
  toolName: string;
79
80
  outputSchemaToolNames: Set<string> | undefined;
81
+ promptCacheBreakpoint?: OpenAIPromptCacheBreakpoint;
80
82
  providerOptionsName: string;
81
83
  warnings: Array<SharedV4Warning>;
82
84
  }): Promise<OpenAIResponsesFunctionCallOutput['output']> {
@@ -84,18 +86,34 @@ async function convertFunctionToolResultOutput({
84
86
  // parses the contents of that string as JSON. Text-like results therefore
85
87
  // need JSON.stringify to become valid JSON string literals.
86
88
  const hasOutputSchema = outputSchemaToolNames?.has(toolName);
89
+ const convertScalarOutput = (
90
+ value: string,
91
+ ): OpenAIResponsesFunctionCallOutput['output'] =>
92
+ promptCacheBreakpoint == null
93
+ ? value
94
+ : [
95
+ {
96
+ type: 'input_text',
97
+ text: value,
98
+ prompt_cache_breakpoint: promptCacheBreakpoint,
99
+ },
100
+ ];
87
101
 
88
102
  switch (output.type) {
89
103
  case 'text':
90
104
  case 'error-text':
91
- return hasOutputSchema ? JSON.stringify(output.value) : output.value;
105
+ return convertScalarOutput(
106
+ hasOutputSchema ? JSON.stringify(output.value) : output.value,
107
+ );
92
108
  case 'execution-denied': {
93
109
  const reason = output.reason ?? 'Tool call execution denied.';
94
- return hasOutputSchema ? JSON.stringify(reason) : reason;
110
+ return convertScalarOutput(
111
+ hasOutputSchema ? JSON.stringify(reason) : reason,
112
+ );
95
113
  }
96
114
  case 'json':
97
115
  case 'error-json':
98
- return JSON.stringify(output.value);
116
+ return convertScalarOutput(JSON.stringify(output.value));
99
117
  case 'content':
100
118
  return output.value
101
119
  .map(item => {
@@ -289,6 +307,24 @@ function getPromptCacheBreakpoint(
289
307
  | undefined;
290
308
  }
291
309
 
310
+ function getScalarToolResultPromptCacheBreakpoint({
311
+ output,
312
+ toolResultProviderOptions,
313
+ providerOptionsName,
314
+ }: {
315
+ output: LanguageModelV4ToolResultOutput;
316
+ toolResultProviderOptions: SharedV4ProviderOptions | undefined;
317
+ providerOptionsName: string;
318
+ }): OpenAIPromptCacheBreakpoint | undefined {
319
+ return output.type === 'content'
320
+ ? undefined
321
+ : (getPromptCacheBreakpoint(output.providerOptions, providerOptionsName) ??
322
+ getPromptCacheBreakpoint(
323
+ toolResultProviderOptions,
324
+ providerOptionsName,
325
+ ));
326
+ }
327
+
292
328
  /**
293
329
  * This is soft-deprecated. Use provider references instead. Kept for backward compatibility
294
330
  * with the `fileIdPrefixes` option.
@@ -1218,15 +1254,31 @@ export async function convertToOpenAIResponsesInput({
1218
1254
  );
1219
1255
 
1220
1256
  const toolOutputs = await Promise.all(
1221
- parallelToolResultGroup.results.map(async result =>
1222
- convertFunctionToolResultOutput({
1223
- output: result.output,
1224
- toolName: result.toolName,
1225
- outputSchemaToolNames,
1226
- providerOptionsName,
1227
- warnings,
1228
- }),
1229
- ),
1257
+ parallelToolResultGroup.results.map(async result => {
1258
+ const promptCacheBreakpoint =
1259
+ getScalarToolResultPromptCacheBreakpoint({
1260
+ output: result.output,
1261
+ toolResultProviderOptions: result.providerOptions,
1262
+ providerOptionsName,
1263
+ });
1264
+
1265
+ return {
1266
+ output: await convertFunctionToolResultOutput({
1267
+ output: result.output,
1268
+ toolName: result.toolName,
1269
+ outputSchemaToolNames,
1270
+ providerOptionsName,
1271
+ warnings,
1272
+ }),
1273
+ promptCacheBreakpoint,
1274
+ };
1275
+ }),
1276
+ );
1277
+ const serializedToolOutputs = toolOutputs.map(({ output }) =>
1278
+ typeof output === 'string' ? output : JSON.stringify(output),
1279
+ );
1280
+ const hasPromptCacheBreakpoint = toolOutputs.some(
1281
+ ({ promptCacheBreakpoint }) => promptCacheBreakpoint != null,
1230
1282
  );
1231
1283
 
1232
1284
  input.push({
@@ -1234,13 +1286,16 @@ export async function convertToOpenAIResponsesInput({
1234
1286
  call_id: parallelToolResultGroup.metadata.toolCallId,
1235
1287
  // The internal wrapper returns one output containing the child
1236
1288
  // results in the same order as the original tool_uses array.
1237
- output: toolOutputs
1238
- .map(output =>
1239
- typeof output === 'string'
1240
- ? output
1241
- : JSON.stringify(output),
1242
- )
1243
- .join('\n'),
1289
+ output: hasPromptCacheBreakpoint
1290
+ ? serializedToolOutputs.map((text, index) => ({
1291
+ type: 'input_text',
1292
+ text: index === 0 ? text : `\n${text}`,
1293
+ ...(toolOutputs[index].promptCacheBreakpoint != null && {
1294
+ prompt_cache_breakpoint:
1295
+ toolOutputs[index].promptCacheBreakpoint,
1296
+ }),
1297
+ }))
1298
+ : serializedToolOutputs.join('\n'),
1244
1299
  });
1245
1300
  }
1246
1301
  continue;
@@ -1374,18 +1429,38 @@ export async function convertToOpenAIResponsesInput({
1374
1429
  }
1375
1430
 
1376
1431
  if (customProviderToolNames?.has(resolvedToolName)) {
1432
+ const promptCacheBreakpoint =
1433
+ getScalarToolResultPromptCacheBreakpoint({
1434
+ output,
1435
+ toolResultProviderOptions: part.providerOptions,
1436
+ providerOptionsName,
1437
+ });
1438
+ const convertScalarOutput = (
1439
+ value: string,
1440
+ ): OpenAIResponsesCustomToolCallOutput['output'] =>
1441
+ promptCacheBreakpoint == null
1442
+ ? value
1443
+ : [
1444
+ {
1445
+ type: 'input_text',
1446
+ text: value,
1447
+ prompt_cache_breakpoint: promptCacheBreakpoint,
1448
+ },
1449
+ ];
1377
1450
  let outputValue: OpenAIResponsesCustomToolCallOutput['output'];
1378
1451
  switch (output.type) {
1379
1452
  case 'text':
1380
1453
  case 'error-text':
1381
- outputValue = output.value;
1454
+ outputValue = convertScalarOutput(output.value);
1382
1455
  break;
1383
1456
  case 'execution-denied':
1384
- outputValue = output.reason ?? 'Tool call execution denied.';
1457
+ outputValue = convertScalarOutput(
1458
+ output.reason ?? 'Tool call execution denied.',
1459
+ );
1385
1460
  break;
1386
1461
  case 'json':
1387
1462
  case 'error-json':
1388
- outputValue = JSON.stringify(output.value);
1463
+ outputValue = convertScalarOutput(JSON.stringify(output.value));
1389
1464
  break;
1390
1465
  case 'content':
1391
1466
  outputValue = output.value
@@ -1484,6 +1559,11 @@ export async function convertToOpenAIResponsesInput({
1484
1559
  output,
1485
1560
  toolName: part.toolName,
1486
1561
  outputSchemaToolNames,
1562
+ promptCacheBreakpoint: getScalarToolResultPromptCacheBreakpoint({
1563
+ output,
1564
+ toolResultProviderOptions: part.providerOptions,
1565
+ providerOptionsName,
1566
+ }),
1487
1567
  providerOptionsName,
1488
1568
  warnings,
1489
1569
  });
@@ -30,7 +30,10 @@ import {
30
30
  type InferSchema,
31
31
  type ParseResult,
32
32
  } from '@ai-sdk/provider-utils';
33
- import type { OpenAIConfig } from '../openai-config';
33
+ import {
34
+ prepareOpenAIConfigForWorkflowDeserialize,
35
+ type OpenAIConfig,
36
+ } from '../openai-config';
34
37
  import { openaiFailedResponseHandler } from '../openai-error';
35
38
  import { getOpenAILanguageModelCapabilities } from '../openai-language-model-capabilities';
36
39
  import {
@@ -214,10 +217,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
214
217
  }
215
218
 
216
219
  static [WORKFLOW_DESERIALIZE](options: {
217
- modelId: OpenAIResponsesModelId;
218
- config: OpenAIConfig;
220
+ modelId: string;
221
+ config: Parameters<typeof prepareOpenAIConfigForWorkflowDeserialize>[0];
219
222
  }) {
220
- return new OpenAIResponsesLanguageModel(options.modelId, options.config);
223
+ return new OpenAIResponsesLanguageModel(
224
+ options.modelId as OpenAIResponsesModelId,
225
+ prepareOpenAIConfigForWorkflowDeserialize(options.config),
226
+ );
221
227
  }
222
228
 
223
229
  constructor(modelId: OpenAIResponsesModelId, config: OpenAIConfig) {