@ai-sdk/openai 4.0.110 → 4.0.113

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.110",
3
+ "version": "4.0.113",
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.36",
39
- "@ai-sdk/provider-utils": "5.0.75"
38
+ "@ai-sdk/provider": "4.0.37",
39
+ "@ai-sdk/provider-utils": "5.0.78"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.4",
@@ -19,8 +19,8 @@ import {
19
19
  createJsonResponseHandler,
20
20
  generateId,
21
21
  isCustomReasoning,
22
- parseProviderOptions,
23
22
  postJsonToApi,
23
+ resolveProviderOptions,
24
24
  serializeModelOptions,
25
25
  WORKFLOW_DESERIALIZE,
26
26
  WORKFLOW_SERIALIZE,
@@ -123,13 +123,20 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
123
123
  }: LanguageModelV4CallOptions) {
124
124
  const warnings: SharedV4Warning[] = [];
125
125
 
126
- // Parse provider options
126
+ // `openai` and the Azure key are merged; the Azure key wins (like Azure
127
+ // completion models).
128
+ const providerOptionsNames = this.config.provider.includes('azure')
129
+ ? (['openai', 'azure'] as const)
130
+ : (['openai'] as const);
127
131
  const openaiOptions =
128
- (await parseProviderOptions({
129
- provider: 'openai',
130
- providerOptions,
131
- schema: openaiLanguageModelChatOptions,
132
- })) ?? {};
132
+ (
133
+ await resolveProviderOptions({
134
+ providerOptions,
135
+ namespaces: providerOptionsNames,
136
+ resolution: 'merge',
137
+ schema: openaiLanguageModelChatOptions,
138
+ })
139
+ ).options ?? {};
133
140
 
134
141
  const {
135
142
  capabilities: { language: modelCapabilities },
@@ -178,7 +185,11 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
178
185
 
179
186
  // reasoningSummary is a Responses API option that the chat options schema strips,
180
187
  // so check the raw provider options to avoid silently ignoring it:
181
- if (providerOptions?.openai?.reasoningSummary != null) {
188
+ if (
189
+ providerOptionsNames.some(
190
+ name => providerOptions?.[name]?.reasoningSummary != null,
191
+ )
192
+ ) {
182
193
  warnings.push({
183
194
  type: 'unsupported',
184
195
  feature: 'reasoningSummary',
@@ -14,8 +14,8 @@ import {
14
14
  combineHeaders,
15
15
  createEventSourceResponseHandler,
16
16
  createJsonResponseHandler,
17
- parseProviderOptions,
18
17
  postJsonToApi,
18
+ resolveProviderOptions,
19
19
  serializeModelOptions,
20
20
  WORKFLOW_DESERIALIZE,
21
21
  WORKFLOW_SERIALIZE,
@@ -111,18 +111,16 @@ export class OpenAICompletionLanguageModel implements LanguageModelV4 {
111
111
  const warnings: SharedV4Warning[] = [];
112
112
 
113
113
  // Parse provider options
114
- const openaiOptions = {
115
- ...(await parseProviderOptions({
116
- provider: 'openai',
117
- providerOptions,
118
- schema: openaiLanguageModelCompletionOptions,
119
- })),
120
- ...(await parseProviderOptions({
121
- provider: this.providerOptionsName,
122
- providerOptions,
123
- schema: openaiLanguageModelCompletionOptions,
124
- })),
125
- };
114
+ // `openai` and the provider key are merged; the provider key wins.
115
+ const openaiOptions =
116
+ (
117
+ await resolveProviderOptions({
118
+ providerOptions,
119
+ namespaces: ['openai', this.providerOptionsName],
120
+ resolution: 'merge',
121
+ schema: openaiLanguageModelCompletionOptions,
122
+ })
123
+ ).options ?? {};
126
124
 
127
125
  if (topK != null) {
128
126
  warnings.push({ type: 'unsupported', feature: 'topK' });
@@ -100,71 +100,82 @@ export type OpenAIImageModelOptions = InferSchema<
100
100
  typeof openaiImageModelOptions
101
101
  >;
102
102
 
103
+ /**
104
+ * Shape of the OpenAI image generation options. Exported so that routers
105
+ * that also call the OpenAI image API (e.g. Azure) can accept these options
106
+ * under their own providerOptions key.
107
+ */
108
+ export const openaiImageModelGenerationOptionsShape = () =>
109
+ baseImageModelOptionsObject().extend({
110
+ /**
111
+ * Style of the generated image. `vivid` produces hyper-real and
112
+ * dramatic images; `natural` produces more subdued, less hyper-real
113
+ * looking images.
114
+ */
115
+ style: z.enum(['vivid', 'natural']).optional(),
116
+
117
+ /**
118
+ * Format in which the generated image(s) are returned. `gif` produces
119
+ * an animated GIF and is supported by GPT Image 2.6 models.
120
+ */
121
+ outputFormat: z.enum(['png', 'jpeg', 'webp', 'gif']).optional(),
122
+
123
+ /**
124
+ * Number of frames (2-64) of an animated GIF. Only applies to the `gif`
125
+ * output format. The API defaults to 9.
126
+ */
127
+ frameCount: z.number().int().min(2).max(64).optional(),
128
+
129
+ /**
130
+ * Duration of each frame in milliseconds (20-2000) of an animated GIF.
131
+ * The API rounds it to the nearest 10 ms. Only applies to the `gif`
132
+ * output format. The API defaults to 200.
133
+ */
134
+ frameDurationMs: z.number().int().min(20).max(2000).optional(),
135
+
136
+ /**
137
+ * Content moderation level for the generated image(s). `low` applies
138
+ * less restrictive filtering.
139
+ */
140
+ moderation: z.enum(['auto', 'low']).optional(),
141
+ }).shape;
142
+
103
143
  export const openaiImageModelGenerationOptions = lazySchema(() =>
104
- zodSchema(
105
- baseImageModelOptionsObject().extend({
106
- /**
107
- * Style of the generated image. `vivid` produces hyper-real and
108
- * dramatic images; `natural` produces more subdued, less hyper-real
109
- * looking images.
110
- */
111
- style: z.enum(['vivid', 'natural']).optional(),
112
-
113
- /**
114
- * Format in which the generated image(s) are returned. `gif` produces
115
- * an animated GIF and is supported by GPT Image 2.6 models.
116
- */
117
- outputFormat: z.enum(['png', 'jpeg', 'webp', 'gif']).optional(),
118
-
119
- /**
120
- * Number of frames (2-64) of an animated GIF. Only applies to the `gif`
121
- * output format. The API defaults to 9.
122
- */
123
- frameCount: z.number().int().min(2).max(64).optional(),
124
-
125
- /**
126
- * Duration of each frame in milliseconds (20-2000) of an animated GIF.
127
- * The API rounds it to the nearest 10 ms. Only applies to the `gif`
128
- * output format. The API defaults to 200.
129
- */
130
- frameDurationMs: z.number().int().min(20).max(2000).optional(),
131
-
132
- /**
133
- * Content moderation level for the generated image(s). `low` applies
134
- * less restrictive filtering.
135
- */
136
- moderation: z.enum(['auto', 'low']).optional(),
137
- }),
138
- ),
144
+ zodSchema(z.object(openaiImageModelGenerationOptionsShape())),
139
145
  );
140
146
 
141
147
  export type OpenAIImageModelGenerationOptions = InferSchema<
142
148
  typeof openaiImageModelGenerationOptions
143
149
  >;
144
150
 
151
+ /**
152
+ * Shape of the OpenAI image edit options. Exported so that routers can accept
153
+ * these options under their own providerOptions key.
154
+ */
155
+ export const openaiImageModelEditOptionsShape = () =>
156
+ baseImageModelOptionsObject().extend({
157
+ /**
158
+ * Fidelity of the output image(s) to the input image(s).
159
+ */
160
+ inputFidelity: z.enum(['high', 'low']).optional(),
161
+
162
+ /**
163
+ * A transparent PNG overlaid on the first input image to mark up the
164
+ * regions to change, as an HTTPS URL or a base64 data URL. It must match
165
+ * the dimensions of the first image. Supported by GPT Image 2.5 and 2.6
166
+ * models. Cannot be combined with a `mask`.
167
+ */
168
+ markup: z.string().optional(),
169
+
170
+ /**
171
+ * Content moderation level for the edited image(s). `low` applies
172
+ * less restrictive filtering. Supported by GPT Image models.
173
+ */
174
+ moderation: z.enum(['auto', 'low']).optional(),
175
+ }).shape;
176
+
145
177
  export const openaiImageModelEditOptions = lazySchema(() =>
146
- zodSchema(
147
- baseImageModelOptionsObject().extend({
148
- /**
149
- * Fidelity of the output image(s) to the input image(s).
150
- */
151
- inputFidelity: z.enum(['high', 'low']).optional(),
152
-
153
- /**
154
- * A transparent PNG overlaid on the first input image to mark up the
155
- * regions to change, as an HTTPS URL or a base64 data URL. It must match
156
- * the dimensions of the first image. Supported by GPT Image 2.5 and 2.6
157
- * models. Cannot be combined with a `mask`.
158
- */
159
- markup: z.string().optional(),
160
-
161
- /**
162
- * Content moderation level for the edited image(s). `low` applies
163
- * less restrictive filtering. Supported by GPT Image models.
164
- */
165
- moderation: z.enum(['auto', 'low']).optional(),
166
- }),
167
- ),
178
+ zodSchema(z.object(openaiImageModelEditOptionsShape())),
168
179
  );
169
180
 
170
181
  export type OpenAIImageModelEditOptions = InferSchema<
@@ -0,0 +1,64 @@
1
+ import { withTelemetryRedaction } from '@ai-sdk/provider-utils';
2
+
3
+ /** OpenAI owns both the SDK configuration paths and their wire equivalents. */
4
+ export function redactOpenAIToolArgs(
5
+ type: unknown,
6
+ args: Record<string, unknown>,
7
+ wireFormat = false,
8
+ ): Record<string, unknown> {
9
+ if (type === 'mcp') {
10
+ return {
11
+ ...args,
12
+ ...(args.authorization != null ? { authorization: '[redacted]' } : {}),
13
+ ...(isRecord(args.headers)
14
+ ? {
15
+ headers: Object.fromEntries(
16
+ Object.keys(args.headers).map(name => [name, '[redacted]']),
17
+ ),
18
+ }
19
+ : {}),
20
+ };
21
+ }
22
+
23
+ const key =
24
+ type === 'code_interpreter'
25
+ ? 'container'
26
+ : type === 'shell'
27
+ ? 'environment'
28
+ : undefined;
29
+ if (key == null || !isRecord(args[key])) return args;
30
+ const container = args[key];
31
+ const policyKey = wireFormat ? 'network_policy' : 'networkPolicy';
32
+ const secretsKey = wireFormat ? 'domain_secrets' : 'domainSecrets';
33
+ const policy = container[policyKey];
34
+ if (!isRecord(policy) || !Array.isArray(policy[secretsKey])) return args;
35
+ return {
36
+ ...args,
37
+ [key]: {
38
+ ...container,
39
+ [policyKey]: {
40
+ ...policy,
41
+ [secretsKey]: policy[secretsKey].map(secret =>
42
+ isRecord(secret) && 'value' in secret
43
+ ? { ...secret, value: '[redacted]' }
44
+ : secret,
45
+ ),
46
+ },
47
+ },
48
+ };
49
+ }
50
+
51
+ /** Preserve raw request/response bodies while providing a safe telemetry view. */
52
+ export function withOpenAIResponseRedaction<T>(value: T): T {
53
+ if (!isRecord(value) || !Array.isArray(value.tools)) return value;
54
+ return withTelemetryRedaction(value, body => ({
55
+ ...body,
56
+ tools: (body.tools as unknown[]).map(tool =>
57
+ isRecord(tool) ? redactOpenAIToolArgs(tool.type, tool, true) : tool,
58
+ ),
59
+ }));
60
+ }
61
+
62
+ function isRecord(value: unknown): value is Record<string, unknown> {
63
+ return value != null && typeof value === 'object' && !Array.isArray(value);
64
+ }
@@ -311,7 +311,7 @@ async function convertFunctionToolResultOutput({
311
311
  }
312
312
 
313
313
  type ParallelToolResultGroup = {
314
- metadata: ParallelToolCallMetadata;
314
+ metadata: ParallelToolCallMetadata & { input: string };
315
315
  results: Array<LanguageModelV4ToolResultPart>;
316
316
  };
317
317
 
@@ -323,7 +323,9 @@ function hasSameParallelToolCall(
323
323
  first.itemId === second.itemId &&
324
324
  first.toolCallId === second.toolCallId &&
325
325
  first.toolName === second.toolName &&
326
- first.input === second.input &&
326
+ (first.input === undefined ||
327
+ second.input === undefined ||
328
+ first.input === second.input) &&
327
329
  first.count === second.count
328
330
  );
329
331
  }
@@ -381,6 +383,12 @@ function collectCompleteParallelToolResultGroups({
381
383
  continue;
382
384
  }
383
385
 
386
+ if (
387
+ existing.metadata.input === undefined &&
388
+ metadata.input !== undefined
389
+ ) {
390
+ existing.metadata = metadata;
391
+ }
384
392
  existing.results.set(metadata.index, part);
385
393
  }
386
394
  }
@@ -388,7 +396,11 @@ function collectCompleteParallelToolResultGroups({
388
396
  const completeGroups = new Map<string, ParallelToolResultGroup>();
389
397
 
390
398
  for (const [toolCallId, group] of pendingGroups) {
391
- if (group.invalid || group.results.size !== group.metadata.count) {
399
+ if (
400
+ group.invalid ||
401
+ group.results.size !== group.metadata.count ||
402
+ group.metadata.input === undefined
403
+ ) {
392
404
  continue;
393
405
  }
394
406
 
@@ -398,7 +410,7 @@ function collectCompleteParallelToolResultGroups({
398
410
 
399
411
  if (results.every(isNonNullable)) {
400
412
  completeGroups.set(toolCallId, {
401
- metadata: group.metadata,
413
+ metadata: { ...group.metadata, input: group.metadata.input },
402
414
  results,
403
415
  });
404
416
  }
@@ -17,7 +17,8 @@ export type ParallelToolCallMetadata = {
17
17
  itemId: string;
18
18
  toolCallId: string;
19
19
  toolName: string;
20
- input: string;
20
+ /** Retained only on the first child; older histories may repeat it. */
21
+ input?: string;
21
22
  index: number;
22
23
  count: number;
23
24
  };
@@ -36,7 +37,7 @@ export function getParallelToolCallMetadata({
36
37
  typeof metadata.itemId !== 'string' ||
37
38
  typeof metadata.toolCallId !== 'string' ||
38
39
  typeof metadata.toolName !== 'string' ||
39
- typeof metadata.input !== 'string' ||
40
+ (metadata.input !== undefined && typeof metadata.input !== 'string') ||
40
41
  typeof metadata.index !== 'number' ||
41
42
  !Number.isInteger(metadata.index) ||
42
43
  typeof metadata.count !== 'number' ||
@@ -129,7 +130,7 @@ export async function expandParallelToolCall({
129
130
  itemId,
130
131
  toolCallId: toolCall.toolCallId,
131
132
  toolName: toolCall.toolName,
132
- input: toolCall.input,
133
+ ...(index === 0 && { input: toolCall.input }),
133
134
  index,
134
135
  count: toolUses.length,
135
136
  } satisfies ParallelToolCallMetadata,
@@ -23,8 +23,8 @@ import {
23
23
  createJsonResponseHandler,
24
24
  createToolNameMapping,
25
25
  generateId,
26
- parseProviderOptions,
27
26
  postJsonToApi,
27
+ resolveProviderOptions,
28
28
  serializeModelOptions,
29
29
  WORKFLOW_DESERIALIZE,
30
30
  WORKFLOW_SERIALIZE,
@@ -32,6 +32,7 @@ import {
32
32
  type ParseResult,
33
33
  } from '@ai-sdk/provider-utils';
34
34
  import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
35
+ import { withOpenAIResponseRedaction } from '../redact-openai-tool-credentials';
35
36
  import {
36
37
  prepareOpenAIConfigForWorkflowDeserialize,
37
38
  type OpenAIConfig,
@@ -331,20 +332,15 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
331
332
  const providerOptionsName = config.provider.includes('azure')
332
333
  ? 'azure'
333
334
  : 'openai';
334
- let openaiOptions = await parseProviderOptions({
335
- provider: providerOptionsName,
335
+ // The provider key (`azure`) and `openai` are alternatives: the first
336
+ // present one is used as a whole.
337
+ const { options: openaiOptions } = await resolveProviderOptions({
336
338
  providerOptions,
339
+ namespaces: [providerOptionsName, 'openai'],
340
+ resolution: 'first',
337
341
  schema: openaiLanguageModelResponsesOptionsSchema,
338
342
  });
339
343
 
340
- if (openaiOptions == null && providerOptionsName !== 'openai') {
341
- openaiOptions = await parseProviderOptions({
342
- provider: 'openai',
343
- providerOptions,
344
- schema: openaiLanguageModelResponsesOptionsSchema,
345
- });
346
- }
347
-
348
344
  const resolvedReasoningEffort = resolveOpenAIReasoningEffort({
349
345
  providerReasoningEffort: openaiOptions?.reasoningEffort,
350
346
  reasoning,
@@ -814,11 +810,11 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
814
810
 
815
811
  return {
816
812
  webSearchToolName,
817
- args: {
813
+ args: withOpenAIResponseRedaction({
818
814
  ...baseArgs,
819
815
  tools: openaiTools,
820
816
  tool_choice: openaiToolChoice,
821
- },
817
+ }),
822
818
  warnings: [...warnings, ...toolWarnings],
823
819
  store,
824
820
  toolNameMapping,
@@ -1564,7 +1560,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1564
1560
  timestamp: new Date(response.created_at! * 1000),
1565
1561
  modelId: response.model,
1566
1562
  headers: responseHeaders,
1567
- body: rawResponse,
1563
+ body: withOpenAIResponseRedaction(rawResponse),
1568
1564
  },
1569
1565
  providerMetadata,
1570
1566
  warnings,
@@ -1595,10 +1591,10 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1595
1591
  await resolve(this.config.headers),
1596
1592
  options.headers,
1597
1593
  ),
1598
- body: {
1594
+ body: withOpenAIResponseRedaction({
1599
1595
  ...body,
1600
1596
  stream: true,
1601
- },
1597
+ }),
1602
1598
  failedResponseHandler: openaiFailedResponseHandler,
1603
1599
  successfulResponseHandler: createEventSourceResponseHandler(
1604
1600
  openaiResponsesChunkSchema,
@@ -16,13 +16,18 @@ export type OpenAISpeechModelId =
16
16
  | (string & {});
17
17
 
18
18
  // https://platform.openai.com/docs/api-reference/audio/createSpeech
19
+ /**
20
+ * Shape of the OpenAI speech model options. Exported so that routers that
21
+ * also call the OpenAI speech API (e.g. Azure) can accept these options under
22
+ * their own providerOptions key.
23
+ */
24
+ export const openaiSpeechModelOptionsShape = () => ({
25
+ instructions: z.string().nullish(),
26
+ speed: z.number().min(0.25).max(4.0).nullish(),
27
+ });
28
+
19
29
  export const openaiSpeechModelOptionsSchema = lazySchema(() =>
20
- zodSchema(
21
- z.object({
22
- instructions: z.string().nullish(),
23
- speed: z.number().min(0.25).max(4.0).nullish(),
24
- }),
25
- ),
30
+ zodSchema(z.object(openaiSpeechModelOptionsShape())),
26
31
  );
27
32
 
28
33
  export type OpenAISpeechModelOptions = InferSchema<
@@ -4,6 +4,7 @@ import {
4
4
  zodSchema,
5
5
  } from '@ai-sdk/provider-utils';
6
6
  import { z } from 'zod/v4';
7
+ import { redactOpenAIToolArgs } from '../redact-openai-tool-credentials';
7
8
 
8
9
  export const codeInterpreterInputSchema = lazySchema(() =>
9
10
  zodSchema(
@@ -128,6 +129,7 @@ export const codeInterpreterToolFactory = createProviderExecutedToolFactory<
128
129
  CodeInterpreterArgs
129
130
  >({
130
131
  id: 'openai.code_interpreter',
132
+ redactArgs: args => redactOpenAIToolArgs('code_interpreter', args),
131
133
  inputSchema: codeInterpreterInputSchema,
132
134
  outputSchema: codeInterpreterOutputSchema,
133
135
  });
package/src/tool/mcp.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  } from '@ai-sdk/provider-utils';
6
6
  import type { JSONValue } from '@ai-sdk/provider';
7
7
  import { z } from 'zod/v4';
8
+ import { redactOpenAIToolArgs } from '../redact-openai-tool-credentials';
8
9
 
9
10
  let cachedJsonValueSchema: z.ZodType<JSONValue> | undefined;
10
11
  const jsonValueSchema = (): z.ZodType<JSONValue> =>
@@ -139,6 +140,7 @@ export const mcpToolFactory = createProviderExecutedToolFactory<
139
140
  McpArgs
140
141
  >({
141
142
  id: 'openai.mcp',
143
+ redactArgs: args => redactOpenAIToolArgs('mcp', args),
142
144
  inputSchema: mcpInputSchema,
143
145
  outputSchema: mcpOutputSchema,
144
146
  });
package/src/tool/shell.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  } from '@ai-sdk/provider-utils';
6
6
  import type { SharedV4ProviderReference } from '@ai-sdk/provider';
7
7
  import { z } from 'zod/v4';
8
+ import { redactOpenAIToolArgs } from '../redact-openai-tool-credentials';
8
9
 
9
10
  export const shellInputSchema = lazySchema(() =>
10
11
  zodSchema(
@@ -210,6 +211,7 @@ export const shell = createProviderDefinedToolFactoryWithOutputSchema<
210
211
  ShellArgs
211
212
  >({
212
213
  id: 'openai.shell',
214
+ redactArgs: args => redactOpenAIToolArgs('shell', args),
213
215
  inputSchema: shellInputSchema,
214
216
  outputSchema: shellOutputSchema,
215
217
  });