@ai-sdk/openai 4.0.108 → 4.0.109

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.
@@ -1005,7 +1005,20 @@ const result = await generateText({
1005
1005
 
1006
1006
  The code interpreter tool can be configured with:
1007
1007
 
1008
- - **container**: Either a container ID string or an object with `fileIds` to specify uploaded files that should be available to the code interpreter
1008
+ - **container**: Either:
1009
+ - a container ID (`string`) to reuse an existing container, or
1010
+ - a configuration `object` to create and configure a new container.
1011
+
1012
+ - **container configuration object**
1013
+ - **fileIds** _string[]_ — File IDs to make available in the container at startup
1014
+ - **memoryLimit** _'1g' | '4g' | '16g' | '64g'_ — Memory limit for the container
1015
+ - **networkPolicy** — Network access policy for outbound requests:
1016
+ - `{ type: 'disabled' }` — No outbound network access
1017
+ - `{ type: 'allowlist', allowedDomains: string[], domainSecrets?: Array<{ domain, name, value }> }`
1018
+ — Allow outbound access only to the specified domains, with optional per-domain secrets
1019
+
1020
+ The organization's hosted tool settings must permit the requested network policy.
1021
+ An allowlist request cannot enable networking when the organization disables it.
1009
1022
 
1010
1023
  <Note>
1011
1024
  When working with files generated by the Code Interpreter, reference
@@ -1016,7 +1029,7 @@ The code interpreter tool can be configured with:
1016
1029
 
1017
1030
  #### MCP Tool
1018
1031
 
1019
- The OpenAI responses API supports connecting to [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers through the `openai.tools.mcp` tool. This allows models to call tools exposed by remote MCP servers or service connectors.
1032
+ The OpenAI responses API supports connecting to [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers through the `openai.tools.mcp` tool. This allows models to call tools exposed by remote MCP servers, service connectors, or [Secure MCP tunnels](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels).
1020
1033
 
1021
1034
  ```ts
1022
1035
  import { openai } from '@ai-sdk/openai';
@@ -1035,19 +1048,47 @@ const result = await generateText({
1035
1048
  });
1036
1049
  ```
1037
1050
 
1051
+ For a private MCP server exposed through an OpenAI Secure MCP tunnel, provide
1052
+ its tunnel ID instead of a server URL or connector ID. First configure the tunnel
1053
+ in OpenAI and run its tunnel client against your MCP server. The SDK uses the
1054
+ existing tunnel; it does not create or discover one:
1055
+
1056
+ ```ts
1057
+ const result = await generateText({
1058
+ model: openai.responses('gpt-5-mini'),
1059
+ prompt: 'Search the private knowledge base',
1060
+ tools: {
1061
+ mcp: openai.tools.mcp({
1062
+ serverLabel: 'private-knowledge',
1063
+ tunnelId: 'tunnel_0123456789abcdef0123456789abcdef',
1064
+ serverDescription: 'A private knowledge base',
1065
+ allowedTools: { readOnly: true },
1066
+ requireApproval: 'always',
1067
+ }),
1068
+ },
1069
+ });
1070
+ ```
1071
+
1072
+ When reading a tunnel ID from an environment variable, check that it is defined
1073
+ before passing it to `tunnelId`, which requires a string.
1074
+
1038
1075
  The MCP tool can be configured with:
1039
1076
 
1040
1077
  - **serverLabel** _string_ (required)
1041
1078
 
1042
1079
  A label to identify the MCP server. This label is used in tool calls to distinguish between multiple MCP servers.
1043
1080
 
1044
- - **serverUrl** _string_ (required if `connectorId` is not provided)
1081
+ - **serverUrl** _string_ (required if `connectorId` and `tunnelId` are not provided)
1082
+
1083
+ The URL for the MCP server. Cannot be combined with `tunnelId`.
1084
+
1085
+ - **connectorId** _string_ (required if `serverUrl` and `tunnelId` are not provided)
1045
1086
 
1046
- The URL for the MCP server. Either `serverUrl` or `connectorId` must be provided.
1087
+ Identifier for a service connector. Cannot be combined with `tunnelId`.
1047
1088
 
1048
- - **connectorId** _string_ (required if `serverUrl` is not provided)
1089
+ - **tunnelId** _string_ (required if `serverUrl` and `connectorId` are not provided)
1049
1090
 
1050
- Identifier for a service connector. Either `serverUrl` or `connectorId` must be provided.
1091
+ Identifier for a configured OpenAI Secure MCP tunnel. Cannot be combined with `serverUrl` or `connectorId`. At least one target must be provided; existing URL and connector configurations remain compatible.
1051
1092
 
1052
1093
  - **serverDescription** _string_ (optional)
1053
1094
 
@@ -1067,7 +1108,7 @@ The MCP tool can be configured with:
1067
1108
 
1068
1109
  - **authorization** _string_ (optional)
1069
1110
 
1070
- OAuth access token for authenticating with the MCP server or connector.
1111
+ OAuth access token for authenticating with the MCP server or connector (including tunneled servers).
1071
1112
 
1072
1113
  - **headers** _Record&lt;string, string&gt;_ (optional)
1073
1114
 
@@ -2501,6 +2542,49 @@ The following optional provider options are available for OpenAI chat models:
2501
2542
 
2502
2543
  Force treating this model as a reasoning model. This is useful for "stealth" reasoning models (e.g. via a custom baseURL) where the model ID is not recognized by the SDK's allowlist. When enabled, the SDK applies reasoning-model parameter compatibility rules and defaults `systemMessageMode` to `developer` unless overridden.
2503
2544
 
2545
+ #### Message Names
2546
+
2547
+ Chat Completions accepts an optional participant name on each system, developer,
2548
+ user, or assistant message. Set `providerOptions.openai.name` on the message to
2549
+ help the model distinguish participants with the same role. Use pseudonyms instead
2550
+ of personal identifiers when possible; names are sent to the provider with the
2551
+ message.
2552
+
2553
+ ```ts
2554
+ import { openai, type OpenAIChatMessageProviderOptions } from '@ai-sdk/openai';
2555
+ import { generateText } from 'ai';
2556
+
2557
+ const result = await generateText({
2558
+ model: openai.chat('gpt-4.1-mini'),
2559
+ messages: [
2560
+ {
2561
+ role: 'user',
2562
+ content: 'My favorite color is blue.',
2563
+ providerOptions: {
2564
+ openai: { name: 'alice' } satisfies OpenAIChatMessageProviderOptions,
2565
+ },
2566
+ },
2567
+ {
2568
+ role: 'user',
2569
+ content: 'My favorite color is green. What are our favorite colors?',
2570
+ providerOptions: {
2571
+ openai: { name: 'bob' } satisfies OpenAIChatMessageProviderOptions,
2572
+ },
2573
+ },
2574
+ ],
2575
+ });
2576
+ ```
2577
+
2578
+ Names also work with `streamText`. Configure them on the message, rather than on
2579
+ individual content parts or in the call-level provider options. Non-string values
2580
+ are rejected during provider option validation. Tool messages do not support
2581
+ participant names: a configured name is omitted with an `unsupported` warning.
2582
+
2583
+ This option applies to Chat Completions through `openai.chat()`, including Azure
2584
+ Chat models using `providerOptions.openai.name`. The default OpenAI Responses
2585
+ model does not support message names. A name identifies a conversation participant;
2586
+ it is separate from the call-level `user` and `safetyIdentifier` options.
2587
+
2504
2588
  #### Reasoning
2505
2589
 
2506
2590
  OpenAI has introduced the `o1`,`o3`, and `o4` series of [reasoning models](https://platform.openai.com/docs/guides/reasoning).
@@ -3164,6 +3248,8 @@ is supported.
3164
3248
 
3165
3249
  ## Realtime Models
3166
3250
 
3251
+ OpenAI Realtime also supports trusted [server-owned connections](/docs/ai-sdk-core/realtime#server-owned-connections) through `getServerWebSocketConfig({ intent })`, with conversation and transcription intents and normalized completion usage. Use the same intent for connection setup and event serialization. OpenAI Live retains its conversation-only server connection API and duration snapshots.
3252
+
3167
3253
  <Note type="warning">Realtime is an experimental feature.</Note>
3168
3254
 
3169
3255
  Use `.experimental_realtime()` for both [OpenAI Realtime](https://platform.openai.com/docs/guides/realtime)
@@ -3422,6 +3508,31 @@ const { images } = await generateImage({
3422
3508
  });
3423
3509
  ```
3424
3510
 
3511
+ #### Moderation
3512
+
3513
+ GPT Image models accept a `moderation` level on edits as well as on generations. The default, `auto`, is the standard filtering; `low` applies less restrictive filtering:
3514
+
3515
+ ```ts
3516
+ import { readFileSync } from 'node:fs';
3517
+ import { openai, type OpenAIImageModelEditOptions } from '@ai-sdk/openai';
3518
+ import { generateImage } from 'ai';
3519
+
3520
+ const imageBuffer = readFileSync('./input-image.png');
3521
+
3522
+ const { images } = await generateImage({
3523
+ model: openai.image('gpt-image-2.5-flare'),
3524
+ prompt: {
3525
+ text: 'Draw the same scene at dusk',
3526
+ images: [imageBuffer],
3527
+ },
3528
+ providerOptions: {
3529
+ openai: {
3530
+ moderation: 'low',
3531
+ } satisfies OpenAIImageModelEditOptions,
3532
+ },
3533
+ });
3534
+ ```
3535
+
3425
3536
  #### Multi-Image Combining
3426
3537
 
3427
3538
  Combine multiple reference images into a single output. `gpt-image-*` models support up to 16 input images:
@@ -3453,6 +3564,8 @@ const { images } = await generateImage({
3453
3564
 
3454
3565
  | Model | Sizes |
3455
3566
  | ------------------------ | -------------------------------------- |
3567
+ | `gpt-image-2.6-flare` | Standard presets and custom dimensions |
3568
+ | `gpt-image-2.6-sunburst` | Standard presets and custom dimensions |
3456
3569
  | `gpt-image-2.5-flare` | Standard presets and custom dimensions |
3457
3570
  | `gpt-image-2.5-sunburst` | Standard presets and custom dimensions |
3458
3571
  | `gpt-image-2` | 1024x1024, 1536x1024, 1024x1536 |
@@ -3499,6 +3612,28 @@ const { image } = await generateImage({
3499
3612
  });
3500
3613
  ```
3501
3614
 
3615
+ The `gpt-image-2.6-flare` and `gpt-image-2.6-sunburst` models support these additional options:
3616
+
3617
+ - **complexity** _'low' | 'medium' | 'high'_ - how much detail the model puts into the image (generation and editing). The API defaults to `high`.
3618
+ - **outputFormat: 'gif'** _(generation only)_ - generates an animated GIF. Use **frameCount** (2-64, API default 9) and **frameDurationMs** (20-2000, API default 200) to control the animation.
3619
+ - **markup** _string_ _(editing only)_ - a transparent PNG (HTTPS URL or base64 data URL) overlaid on the first input image to mark up the regions to change. Supported by `gpt-image-2.5-*` and `gpt-image-2.6-*` models. It cannot be combined with `mask`; providing both throws an error.
3620
+
3621
+ ```ts
3622
+ const { image } = await generateImage({
3623
+ model: openai.image('gpt-image-2.6-flare'),
3624
+ prompt: 'A red panda waving hello, flat illustration',
3625
+ providerOptions: {
3626
+ openai: {
3627
+ outputFormat: 'gif',
3628
+ frameCount: 9,
3629
+ frameDurationMs: 200,
3630
+ } satisfies OpenAIImageModelGenerationOptions,
3631
+ },
3632
+ });
3633
+ ```
3634
+
3635
+ All image options also accept `safetyIdentifier` (max 128 characters) to help OpenAI detect abuse by your end users.
3636
+
3502
3637
  For more on `generateImage()` see [Image Generation](/docs/ai-sdk-core/image-generation).
3503
3638
 
3504
3639
  OpenAI's image models return additional metadata in the response that can be
@@ -3569,27 +3704,30 @@ The API validates model support and language codes. These two options currently
3569
3704
  apply to REST transcription; the provider reports unsupported-option warnings
3570
3705
  if they are supplied to `experimental_streamTranscribe`.
3571
3706
 
3572
- To get word-level timestamps, specify the granularity:
3707
+ To get word and caption timings together, use the shared setting with a
3708
+ model that supports `verbose_json`, such as Whisper:
3573
3709
 
3574
- ```ts highlight="8-9"
3710
+ ```ts
3575
3711
  import { transcribe } from 'ai';
3576
- import { openai, type OpenAITranscriptionModelOptions } from '@ai-sdk/openai';
3712
+ import { openai } from '@ai-sdk/openai';
3577
3713
 
3578
3714
  const result = await transcribe({
3579
3715
  model: openai.transcription('whisper-1'),
3580
3716
  audio: new Uint8Array([1, 2, 3, 4]),
3581
- providerOptions: {
3582
- openai: {
3583
- //timestampGranularities: ['word'],
3584
- timestampGranularities: ['segment'],
3585
- } satisfies OpenAITranscriptionModelOptions,
3586
- },
3717
+ timestampGranularities: ['word', 'segment'],
3587
3718
  });
3588
3719
 
3589
- // Access word-level timestamps
3590
- console.log(result.segments); // Array of segments with startSecond/endSecond
3720
+ console.log(result.words);
3721
+ console.log(result.segments);
3591
3722
  ```
3592
3723
 
3724
+ `providerOptions.openai.timestampGranularities` takes precedence over the shared
3725
+ setting. If neither is set, `verbose_json` defaults to segment timings.
3726
+ `timestamp_granularities` is sent only for `verbose_json`; other formats warn
3727
+ about unavailable requested timings. `diarized_json` provides segment timings
3728
+ but does not provide word timings. Existing segment behavior is preserved:
3729
+ if the response has words but no segments, `segments` also contains those words.
3730
+
3593
3731
  `gpt-4o-transcribe-diarize` identifies speakers in a recording. It defaults to
3594
3732
  the `diarized_json` response format and automatic chunking:
3595
3733
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/openai",
3
- "version": "4.0.108",
3
+ "version": "4.0.109",
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.35",
39
- "@ai-sdk/provider-utils": "5.0.74"
38
+ "@ai-sdk/provider": "4.0.36",
39
+ "@ai-sdk/provider-utils": "5.0.75"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@ai-sdk/test-server": "2.0.4",
@@ -6,6 +6,7 @@ import {
6
6
  } from '@ai-sdk/provider';
7
7
  import type { OpenAIChatPrompt } from './openai-chat-prompt';
8
8
  import {
9
+ parseProviderOptions,
9
10
  secureJsonParse,
10
11
  stringifyWithBigIntAndCycles,
11
12
  convertToBase64,
@@ -14,6 +15,8 @@ import {
14
15
  resolveProviderReference,
15
16
  } from '@ai-sdk/provider-utils';
16
17
 
18
+ import { openaiChatMessageProviderOptions } from './openai-chat-language-model-options';
19
+
17
20
  function serializeToolCallArguments(input: unknown): string {
18
21
  if (typeof input === 'string') {
19
22
  try {
@@ -47,20 +50,27 @@ function getPromptCacheBreakpoint(
47
50
  | undefined;
48
51
  }
49
52
 
50
- export function convertToOpenAIChatMessages({
53
+ export async function convertToOpenAIChatMessages({
51
54
  prompt,
52
55
  systemMessageMode = 'system',
53
56
  }: {
54
57
  prompt: LanguageModelV4Prompt;
55
58
  systemMessageMode?: 'system' | 'developer' | 'remove';
56
- }): {
59
+ }): Promise<{
57
60
  messages: OpenAIChatPrompt;
58
61
  warnings: Array<SharedV4Warning>;
59
- } {
62
+ }> {
60
63
  const messages: OpenAIChatPrompt = [];
61
64
  const warnings: Array<SharedV4Warning> = [];
62
65
 
63
66
  for (const { role, content, providerOptions } of prompt) {
67
+ const messageOptions = await parseProviderOptions({
68
+ provider: 'openai',
69
+ providerOptions,
70
+ schema: openaiChatMessageProviderOptions,
71
+ });
72
+ const name = messageOptions?.name;
73
+
64
74
  switch (role) {
65
75
  case 'system': {
66
76
  switch (systemMessageMode) {
@@ -69,6 +79,7 @@ export function convertToOpenAIChatMessages({
69
79
  getPromptCacheBreakpoint(providerOptions);
70
80
  messages.push({
71
81
  role: 'system',
82
+ ...(name != null && { name }),
72
83
  content:
73
84
  promptCacheBreakpoint == null
74
85
  ? content
@@ -87,6 +98,7 @@ export function convertToOpenAIChatMessages({
87
98
  getPromptCacheBreakpoint(providerOptions);
88
99
  messages.push({
89
100
  role: 'developer',
101
+ ...(name != null && { name }),
90
102
  content:
91
103
  promptCacheBreakpoint == null
92
104
  ? content
@@ -123,12 +135,17 @@ export function convertToOpenAIChatMessages({
123
135
  content[0].type === 'text' &&
124
136
  getPromptCacheBreakpoint(content[0].providerOptions) == null
125
137
  ) {
126
- messages.push({ role: 'user', content: content[0].text });
138
+ messages.push({
139
+ role: 'user',
140
+ content: content[0].text,
141
+ ...(name != null && { name }),
142
+ });
127
143
  break;
128
144
  }
129
145
 
130
146
  messages.push({
131
147
  role: 'user',
148
+ ...(name != null && { name }),
132
149
  content: content.map((part, index) => {
133
150
  switch (part.type) {
134
151
  case 'text': {
@@ -311,6 +328,7 @@ export function convertToOpenAIChatMessages({
311
328
 
312
329
  messages.push({
313
330
  role: 'assistant',
331
+ ...(name != null && { name }),
314
332
  content: hasPromptCacheBreakpoint
315
333
  ? textParts
316
334
  : toolCalls.length > 0
@@ -323,6 +341,13 @@ export function convertToOpenAIChatMessages({
323
341
  }
324
342
 
325
343
  case 'tool': {
344
+ if (name != null) {
345
+ warnings.push({
346
+ type: 'unsupported',
347
+ feature: 'message name on tool messages',
348
+ });
349
+ }
350
+
326
351
  for (const toolResponse of content) {
327
352
  if (toolResponse.type === 'tool-approval-response') {
328
353
  continue;
@@ -232,3 +232,16 @@ export const openaiLanguageModelChatOptions = lazySchema(() =>
232
232
  export type OpenAILanguageModelChatOptions = InferSchema<
233
233
  typeof openaiLanguageModelChatOptions
234
234
  >;
235
+
236
+ export const openaiChatMessageProviderOptions = lazySchema(() =>
237
+ zodSchema(
238
+ z.object({
239
+ /** The participant name on a system, developer, user, or assistant message. */
240
+ name: z.string().optional(),
241
+ }),
242
+ ),
243
+ );
244
+
245
+ export type OpenAIChatMessageProviderOptions = InferSchema<
246
+ typeof openaiChatMessageProviderOptions
247
+ >;
@@ -178,16 +178,15 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
178
178
  });
179
179
  }
180
180
 
181
- const { messages, warnings: messageWarnings } = convertToOpenAIChatMessages(
182
- {
181
+ const { messages, warnings: messageWarnings } =
182
+ await convertToOpenAIChatMessages({
183
183
  prompt,
184
184
  systemMessageMode:
185
185
  openaiOptions.systemMessageMode ??
186
186
  (isReasoningModel
187
187
  ? 'developer'
188
188
  : modelRequestConfig.systemMessageMode),
189
- },
190
- );
189
+ });
191
190
 
192
191
  warnings.push(...messageWarnings);
193
192
 
@@ -414,16 +413,17 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
414
413
  options: LanguageModelV4CallOptions,
415
414
  ): Promise<LanguageModelV4GenerateResult> {
416
415
  const { args: body, warnings } = await this.getArgs(options);
416
+ const url = this.config.url({
417
+ path: '/chat/completions',
418
+ modelId: this.config.requestModelId ?? this.modelId,
419
+ });
417
420
 
418
421
  const {
419
422
  responseHeaders,
420
423
  value: response,
421
424
  rawValue: rawResponse,
422
425
  } = await postJsonToApi({
423
- url: this.config.url({
424
- path: '/chat/completions',
425
- modelId: this.config.requestModelId ?? this.modelId,
426
- }),
426
+ url,
427
427
  headers: combineHeaders(
428
428
  await resolve(this.config.headers),
429
429
  options.headers,
@@ -499,7 +499,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
499
499
  raw: choice.finish_reason ?? undefined,
500
500
  },
501
501
  usage: convertOpenAIChatUsage(response.usage),
502
- request: { body },
502
+ request: { body, url },
503
503
  response: {
504
504
  ...getResponseMetadata(response),
505
505
  headers: responseHeaders,
@@ -705,7 +705,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV4 {
705
705
  },
706
706
  }),
707
707
  ),
708
- request: { body },
708
+ request: { body, url },
709
709
  response: { headers: responseHeaders },
710
710
  };
711
711
 
@@ -8,16 +8,19 @@ export type ChatCompletionMessage =
8
8
  | ChatCompletionToolMessage;
9
9
 
10
10
  export interface ChatCompletionSystemMessage {
11
+ name?: string;
11
12
  role: 'system';
12
13
  content: string | Array<ChatCompletionContentPartText>;
13
14
  }
14
15
 
15
16
  export interface ChatCompletionDeveloperMessage {
17
+ name?: string;
16
18
  role: 'developer';
17
19
  content: string | Array<ChatCompletionContentPartText>;
18
20
  }
19
21
 
20
22
  export interface ChatCompletionUserMessage {
23
+ name?: string;
21
24
  role: 'user';
22
25
  content: string | Array<ChatCompletionContentPart>;
23
26
  }
@@ -53,6 +56,7 @@ export interface ChatCompletionContentPartFile {
53
56
  }
54
57
 
55
58
  export interface ChatCompletionAssistantMessage {
59
+ name?: string;
56
60
  role: 'assistant';
57
61
  content?: string | null | Array<ChatCompletionContentPartText>;
58
62
  tool_calls?: Array<ChatCompletionMessageToolCall>;
@@ -16,6 +16,8 @@ export type OpenAIImageModelId =
16
16
  | 'gpt-image-1-mini'
17
17
  | 'gpt-image-1.5'
18
18
  | 'gpt-image-2'
19
+ | 'gpt-image-2.6-flare'
20
+ | 'gpt-image-2.6-sunburst'
19
21
  | 'gpt-image-2.5-flare'
20
22
  | 'gpt-image-2.5-flare-2026-09-08'
21
23
  | 'gpt-image-2.5-sunburst'
@@ -64,6 +66,12 @@ const baseImageModelOptionsObject = () =>
64
66
  */
65
67
  outputFormat: z.enum(['png', 'jpeg', 'webp']).optional(),
66
68
 
69
+ /**
70
+ * How much detail the model puts into the image. Supported by GPT Image
71
+ * 2.6 models. The API defaults to `high`.
72
+ */
73
+ complexity: z.enum(['low', 'medium', 'high']).optional(),
74
+
67
75
  /**
68
76
  * Compression level (0-100) for the generated image(s). Applies to the
69
77
  * `jpeg` and `webp` output formats.
@@ -75,6 +83,13 @@ const baseImageModelOptionsObject = () =>
75
83
  * to monitor and detect abuse.
76
84
  */
77
85
  user: z.string().optional(),
86
+
87
+ /**
88
+ * A stable identifier used to help detect users of your application that
89
+ * may be violating OpenAI's usage policies. Should be a hashed username
90
+ * or email address.
91
+ */
92
+ safetyIdentifier: z.string().max(128).optional(),
78
93
  });
79
94
 
80
95
  export const openaiImageModelOptions = lazySchema(() =>
@@ -95,6 +110,25 @@ export const openaiImageModelGenerationOptions = lazySchema(() =>
95
110
  */
96
111
  style: z.enum(['vivid', 'natural']).optional(),
97
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
+
98
132
  /**
99
133
  * Content moderation level for the generated image(s). `low` applies
100
134
  * less restrictive filtering.
@@ -115,6 +149,20 @@ export const openaiImageModelEditOptions = lazySchema(() =>
115
149
  * Fidelity of the output image(s) to the input image(s).
116
150
  */
117
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(),
118
166
  }),
119
167
  ),
120
168
  );
@@ -1,7 +1,8 @@
1
- import type {
2
- ImageModelV4,
3
- ImageModelV4File,
4
- SharedV4Warning,
1
+ import {
2
+ InvalidArgumentError,
3
+ type ImageModelV4,
4
+ type ImageModelV4File,
5
+ type SharedV4Warning,
5
6
  } from '@ai-sdk/provider';
6
7
  import {
7
8
  resolve,
@@ -128,6 +129,13 @@ export class OpenAIImageModel implements ImageModelV4 {
128
129
  schema: openaiImageModelEditOptions,
129
130
  })) ?? {};
130
131
 
132
+ if (mask != null && openaiOptions.markup != null) {
133
+ throw new InvalidArgumentError({
134
+ argument: 'providerOptions.openai.markup',
135
+ message: '`markup` cannot be combined with `mask`.',
136
+ });
137
+ }
138
+
131
139
  const { value: response, responseHeaders } = await postFormDataToApi({
132
140
  url: this.config.url({
133
141
  path: '/images/edits',
@@ -156,14 +164,21 @@ export class OpenAIImageModel implements ImageModelV4 {
156
164
  ),
157
165
  ),
158
166
  mask: mask != null ? await fileToBlob(mask, abortSignal) : undefined,
167
+ markup:
168
+ openaiOptions.markup != null
169
+ ? await markupToBlob(openaiOptions.markup, abortSignal)
170
+ : undefined,
159
171
  n,
160
172
  size,
161
173
  quality: openaiOptions.quality,
162
174
  background: openaiOptions.background,
163
175
  output_format: openaiOptions.outputFormat,
164
176
  output_compression: openaiOptions.outputCompression,
177
+ complexity: openaiOptions.complexity,
165
178
  input_fidelity: openaiOptions.inputFidelity,
179
+ moderation: openaiOptions.moderation,
166
180
  user: openaiOptions.user,
181
+ safety_identifier: openaiOptions.safetyIdentifier,
167
182
  }),
168
183
  failedResponseHandler: openaiFailedResponseHandler,
169
184
  successfulResponseHandler: createJsonResponseHandler(
@@ -235,7 +250,11 @@ export class OpenAIImageModel implements ImageModelV4 {
235
250
  moderation: openaiOptions.moderation,
236
251
  output_format: openaiOptions.outputFormat,
237
252
  output_compression: openaiOptions.outputCompression,
253
+ complexity: openaiOptions.complexity,
254
+ frame_count: openaiOptions.frameCount,
255
+ frame_duration_ms: openaiOptions.frameDurationMs,
238
256
  user: openaiOptions.user,
257
+ safety_identifier: openaiOptions.safetyIdentifier,
239
258
  ...(!hasDefaultResponseFormat(this.modelId)
240
259
  ? { response_format: 'b64_json' }
241
260
  : {}),
@@ -326,16 +345,42 @@ type OpenAIImageEditInput = {
326
345
  prompt?: string;
327
346
  image: Blob | Blob[];
328
347
  mask?: Blob;
348
+ markup?: Blob;
329
349
  n?: number;
330
350
  size?: `${number}x${number}`;
331
351
  quality?: OpenAIImageModelEditOptions['quality'];
332
352
  background?: OpenAIImageModelEditOptions['background'];
333
353
  output_format?: OpenAIImageModelEditOptions['outputFormat'];
334
354
  output_compression?: OpenAIImageModelEditOptions['outputCompression'];
355
+ complexity?: OpenAIImageModelEditOptions['complexity'];
335
356
  input_fidelity?: OpenAIImageModelEditOptions['inputFidelity'];
357
+ moderation?: OpenAIImageModelEditOptions['moderation'];
336
358
  user?: OpenAIImageModelEditOptions['user'];
359
+ safety_identifier?: OpenAIImageModelEditOptions['safetyIdentifier'];
337
360
  };
338
361
 
362
+ async function markupToBlob(
363
+ markup: string,
364
+ abortSignal: AbortSignal | undefined,
365
+ ): Promise<Blob> {
366
+ const dataUrl = /^data:([^;,]*)(;base64)?,(.*)$/s.exec(markup);
367
+
368
+ if (dataUrl == null) {
369
+ return downloadBlob(markup, { abortSignal });
370
+ }
371
+
372
+ if (dataUrl[2] == null) {
373
+ throw new InvalidArgumentError({
374
+ argument: 'providerOptions.openai.markup',
375
+ message: '`markup` data URLs must be base64 encoded.',
376
+ });
377
+ }
378
+
379
+ return new Blob([convertBase64ToUint8Array(dataUrl[3]) as BlobPart], {
380
+ type: dataUrl[1] || 'image/png',
381
+ });
382
+ }
383
+
339
384
  async function fileToBlob(
340
385
  file: ImageModelV4File | undefined,
341
386
  abortSignal: AbortSignal | undefined,
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export type {
22
22
  export type { OpenAIToolOptions } from './responses/openai-responses-prepare-tools';
23
23
  export type {
24
24
  OpenAILanguageModelChatOptions,
25
+ OpenAIChatMessageProviderOptions,
25
26
  /** @deprecated Use `OpenAILanguageModelChatOptions` instead. */
26
27
  OpenAILanguageModelChatOptions as OpenAIChatLanguageModelOptions,
27
28
  } from './chat/openai-chat-language-model-options';