@ai-sdk/openai 4.0.108 → 4.0.110

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/index.d.ts +79 -15
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +788 -423
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +389 -9
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +629 -380
  9. package/dist/internal/index.js.map +1 -1
  10. package/docs/03-openai.mdx +224 -18
  11. package/package.json +3 -3
  12. package/src/chat/convert-to-openai-chat-messages.ts +29 -4
  13. package/src/chat/map-openai-finish-reason.ts +11 -4
  14. package/src/chat/openai-chat-api.ts +36 -0
  15. package/src/chat/openai-chat-language-model-options.ts +13 -0
  16. package/src/chat/openai-chat-language-model.ts +111 -22
  17. package/src/chat/openai-chat-prompt.ts +4 -0
  18. package/src/image/openai-image-model-options.ts +48 -0
  19. package/src/image/openai-image-model.ts +49 -4
  20. package/src/index.ts +1 -0
  21. package/src/internal/index.ts +4 -0
  22. package/src/live/openai-realtime-model-live.ts +21 -2
  23. package/src/openai-batch.ts +8 -0
  24. package/src/openai-model-catalog.ts +3 -1
  25. package/src/openai-tools.ts +4 -3
  26. package/src/realtime/openai-realtime-event-mapper.ts +172 -11
  27. package/src/realtime/openai-realtime-model.ts +57 -3
  28. package/src/responses/map-openai-responses-finish-reason.ts +7 -1
  29. package/src/responses/openai-responses-api.ts +90 -49
  30. package/src/responses/openai-responses-language-model.ts +56 -1
  31. package/src/responses/openai-responses-prepare-tools.ts +18 -1
  32. package/src/tool/code-interpreter.ts +38 -2
  33. package/src/tool/mcp.ts +27 -8
  34. package/src/transcription/openai-transcription-model.ts +43 -8
@@ -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)
1045
1082
 
1046
- The URL for the MCP server. Either `serverUrl` or `connectorId` must be provided.
1083
+ The URL for the MCP server. Cannot be combined with `tunnelId`.
1047
1084
 
1048
- - **connectorId** _string_ (required if `serverUrl` is not provided)
1085
+ - **connectorId** _string_ (required if `serverUrl` and `tunnelId` are not provided)
1049
1086
 
1050
- Identifier for a service connector. Either `serverUrl` or `connectorId` must be provided.
1087
+ Identifier for a service connector. Cannot be combined with `tunnelId`.
1088
+
1089
+ - **tunnelId** _string_ (required if `serverUrl` and `connectorId` are not provided)
1090
+
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
 
@@ -1964,6 +2005,47 @@ message into `result.output`. This prevents commentary or repeated final answers
1964
2005
  from being joined into invalid JSON. The returned `text`, `content`, response
1965
2006
  messages, and token usage still include the complete response as before.
1966
2007
 
2008
+ #### Refusals
2009
+
2010
+ The Responses API can answer a request with a `refusal` content part instead of
2011
+ `output_text`. The provider surfaces the refusal explanation as text, and
2012
+ reports a `content-filter` finish reason in both `generateText` and
2013
+ `streamText`. When the same response also contains a client-side function call,
2014
+ the finish reason is `tool-calls` instead, so the tools are still executed; the
2015
+ refusal text remains part of the content.
2016
+
2017
+ Refusal content parts mainly appear with structured outputs (`Output.object` or
2018
+ a JSON schema passed to the model). With structured output, a refusal means no
2019
+ object can be generated: `generateText` throws `NoObjectGeneratedError` with the
2020
+ refusal text on `error.text` and `error.finishReason` set to `'content-filter'`.
2021
+
2022
+ ```ts
2023
+ import { NoObjectGeneratedError, Output, generateText } from 'ai';
2024
+ import { openai } from '@ai-sdk/openai';
2025
+ import { z } from 'zod';
2026
+
2027
+ try {
2028
+ await generateText({
2029
+ model: openai.responses('gpt-4o'),
2030
+ output: Output.object({
2031
+ schema: z.object({ steps: z.array(z.string()) }),
2032
+ }),
2033
+ prompt: '...',
2034
+ });
2035
+ } catch (error) {
2036
+ if (NoObjectGeneratedError.isInstance(error)) {
2037
+ // the model's refusal explanation
2038
+ console.log(error.text);
2039
+ // 'content-filter'
2040
+ console.log(error.finishReason);
2041
+ }
2042
+ }
2043
+ ```
2044
+
2045
+ Without structured output, the model may still decline in plain text. Such a
2046
+ response arrives as ordinary text with a `stop` finish reason, so check the text
2047
+ in your application if you need to detect declines.
2048
+
1967
2049
  #### Typed providerMetadata in Text Parts
1968
2050
 
1969
2051
  When using the OpenAI Responses API, the SDK attaches OpenAI-specific metadata to output parts via `providerMetadata`.
@@ -2501,6 +2583,49 @@ The following optional provider options are available for OpenAI chat models:
2501
2583
 
2502
2584
  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
2585
 
2586
+ #### Message Names
2587
+
2588
+ Chat Completions accepts an optional participant name on each system, developer,
2589
+ user, or assistant message. Set `providerOptions.openai.name` on the message to
2590
+ help the model distinguish participants with the same role. Use pseudonyms instead
2591
+ of personal identifiers when possible; names are sent to the provider with the
2592
+ message.
2593
+
2594
+ ```ts
2595
+ import { openai, type OpenAIChatMessageProviderOptions } from '@ai-sdk/openai';
2596
+ import { generateText } from 'ai';
2597
+
2598
+ const result = await generateText({
2599
+ model: openai.chat('gpt-4.1-mini'),
2600
+ messages: [
2601
+ {
2602
+ role: 'user',
2603
+ content: 'My favorite color is blue.',
2604
+ providerOptions: {
2605
+ openai: { name: 'alice' } satisfies OpenAIChatMessageProviderOptions,
2606
+ },
2607
+ },
2608
+ {
2609
+ role: 'user',
2610
+ content: 'My favorite color is green. What are our favorite colors?',
2611
+ providerOptions: {
2612
+ openai: { name: 'bob' } satisfies OpenAIChatMessageProviderOptions,
2613
+ },
2614
+ },
2615
+ ],
2616
+ });
2617
+ ```
2618
+
2619
+ Names also work with `streamText`. Configure them on the message, rather than on
2620
+ individual content parts or in the call-level provider options. Non-string values
2621
+ are rejected during provider option validation. Tool messages do not support
2622
+ participant names: a configured name is omitted with an `unsupported` warning.
2623
+
2624
+ This option applies to Chat Completions through `openai.chat()`, including Azure
2625
+ Chat models using `providerOptions.openai.name`. The default OpenAI Responses
2626
+ model does not support message names. A name identifies a conversation participant;
2627
+ it is separate from the call-level `user` and `safetyIdentifier` options.
2628
+
2504
2629
  #### Reasoning
2505
2630
 
2506
2631
  OpenAI has introduced the `o1`,`o3`, and `o4` series of [reasoning models](https://platform.openai.com/docs/guides/reasoning).
@@ -2621,6 +2746,33 @@ You need to change Zod `.nullish()` and `.optional()` to `.nullable()`.
2621
2746
 
2622
2747
  </Note>
2623
2748
 
2749
+ #### Refusals
2750
+
2751
+ Refusals mainly happen when you request structured outputs. When OpenAI declines
2752
+ a request, the provider surfaces the refusal explanation as text and reports a
2753
+ `content-filter` finish reason in both `generateText` and `streamText`:
2754
+
2755
+ ```ts
2756
+ const { text, finishReason } = await generateText({
2757
+ model: openai.chat('gpt-4o'),
2758
+ prompt: '...',
2759
+ });
2760
+
2761
+ if (finishReason === 'content-filter') {
2762
+ // `text` contains the model's refusal explanation
2763
+ }
2764
+ ```
2765
+
2766
+ When you request structured output, a refusal means that no object can be
2767
+ generated: `generateText` throws `NoObjectGeneratedError` with the refusal text
2768
+ on `error.text`.
2769
+
2770
+ When you request logprobs, `providerMetadata.openai.logprobs` contains the
2771
+ logprobs of all emitted tokens: the content logprobs first, followed by the
2772
+ refusal logprobs. This is the same for `generateText` and `streamText`; with
2773
+ `streamText`, the logprobs of all streamed tokens are reported when the stream
2774
+ finishes.
2775
+
2624
2776
  #### Logprobs
2625
2777
 
2626
2778
  OpenAI provides logprobs information for completion/chat models.
@@ -3164,6 +3316,8 @@ is supported.
3164
3316
 
3165
3317
  ## Realtime Models
3166
3318
 
3319
+ 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.
3320
+
3167
3321
  <Note type="warning">Realtime is an experimental feature.</Note>
3168
3322
 
3169
3323
  Use `.experimental_realtime()` for both [OpenAI Realtime](https://platform.openai.com/docs/guides/realtime)
@@ -3422,6 +3576,31 @@ const { images } = await generateImage({
3422
3576
  });
3423
3577
  ```
3424
3578
 
3579
+ #### Moderation
3580
+
3581
+ 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:
3582
+
3583
+ ```ts
3584
+ import { readFileSync } from 'node:fs';
3585
+ import { openai, type OpenAIImageModelEditOptions } from '@ai-sdk/openai';
3586
+ import { generateImage } from 'ai';
3587
+
3588
+ const imageBuffer = readFileSync('./input-image.png');
3589
+
3590
+ const { images } = await generateImage({
3591
+ model: openai.image('gpt-image-2.5-flare'),
3592
+ prompt: {
3593
+ text: 'Draw the same scene at dusk',
3594
+ images: [imageBuffer],
3595
+ },
3596
+ providerOptions: {
3597
+ openai: {
3598
+ moderation: 'low',
3599
+ } satisfies OpenAIImageModelEditOptions,
3600
+ },
3601
+ });
3602
+ ```
3603
+
3425
3604
  #### Multi-Image Combining
3426
3605
 
3427
3606
  Combine multiple reference images into a single output. `gpt-image-*` models support up to 16 input images:
@@ -3453,6 +3632,8 @@ const { images } = await generateImage({
3453
3632
 
3454
3633
  | Model | Sizes |
3455
3634
  | ------------------------ | -------------------------------------- |
3635
+ | `gpt-image-2.6-flare` | Standard presets and custom dimensions |
3636
+ | `gpt-image-2.6-sunburst` | Standard presets and custom dimensions |
3456
3637
  | `gpt-image-2.5-flare` | Standard presets and custom dimensions |
3457
3638
  | `gpt-image-2.5-sunburst` | Standard presets and custom dimensions |
3458
3639
  | `gpt-image-2` | 1024x1024, 1536x1024, 1024x1536 |
@@ -3499,6 +3680,28 @@ const { image } = await generateImage({
3499
3680
  });
3500
3681
  ```
3501
3682
 
3683
+ The `gpt-image-2.6-flare` and `gpt-image-2.6-sunburst` models support these additional options:
3684
+
3685
+ - **complexity** _'low' | 'medium' | 'high'_ - how much detail the model puts into the image (generation and editing). The API defaults to `high`.
3686
+ - **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.
3687
+ - **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.
3688
+
3689
+ ```ts
3690
+ const { image } = await generateImage({
3691
+ model: openai.image('gpt-image-2.6-flare'),
3692
+ prompt: 'A red panda waving hello, flat illustration',
3693
+ providerOptions: {
3694
+ openai: {
3695
+ outputFormat: 'gif',
3696
+ frameCount: 9,
3697
+ frameDurationMs: 200,
3698
+ } satisfies OpenAIImageModelGenerationOptions,
3699
+ },
3700
+ });
3701
+ ```
3702
+
3703
+ All image options also accept `safetyIdentifier` (max 128 characters) to help OpenAI detect abuse by your end users.
3704
+
3502
3705
  For more on `generateImage()` see [Image Generation](/docs/ai-sdk-core/image-generation).
3503
3706
 
3504
3707
  OpenAI's image models return additional metadata in the response that can be
@@ -3569,27 +3772,30 @@ The API validates model support and language codes. These two options currently
3569
3772
  apply to REST transcription; the provider reports unsupported-option warnings
3570
3773
  if they are supplied to `experimental_streamTranscribe`.
3571
3774
 
3572
- To get word-level timestamps, specify the granularity:
3775
+ To get word and caption timings together, use the shared setting with a
3776
+ model that supports `verbose_json`, such as Whisper:
3573
3777
 
3574
- ```ts highlight="8-9"
3778
+ ```ts
3575
3779
  import { transcribe } from 'ai';
3576
- import { openai, type OpenAITranscriptionModelOptions } from '@ai-sdk/openai';
3780
+ import { openai } from '@ai-sdk/openai';
3577
3781
 
3578
3782
  const result = await transcribe({
3579
3783
  model: openai.transcription('whisper-1'),
3580
3784
  audio: new Uint8Array([1, 2, 3, 4]),
3581
- providerOptions: {
3582
- openai: {
3583
- //timestampGranularities: ['word'],
3584
- timestampGranularities: ['segment'],
3585
- } satisfies OpenAITranscriptionModelOptions,
3586
- },
3785
+ timestampGranularities: ['word', 'segment'],
3587
3786
  });
3588
3787
 
3589
- // Access word-level timestamps
3590
- console.log(result.segments); // Array of segments with startSecond/endSecond
3788
+ console.log(result.words);
3789
+ console.log(result.segments);
3591
3790
  ```
3592
3791
 
3792
+ `providerOptions.openai.timestampGranularities` takes precedence over the shared
3793
+ setting. If neither is set, `verbose_json` defaults to segment timings.
3794
+ `timestamp_granularities` is sent only for `verbose_json`; other formats warn
3795
+ about unavailable requested timings. `diarized_json` provides segment timings
3796
+ but does not provide word timings. Existing segment behavior is preserved:
3797
+ if the response has words but no segments, `segments` also contains those words.
3798
+
3593
3799
  `gpt-4o-transcribe-diarize` identifies speakers in a recording. It defaults to
3594
3800
  the `diarized_json` response format and automatic chunking:
3595
3801
 
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.110",
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;
@@ -1,11 +1,18 @@
1
1
  import type { LanguageModelV4FinishReason } from '@ai-sdk/provider';
2
2
 
3
- export function mapOpenAIFinishReason(
4
- finishReason: string | null | undefined,
5
- ): LanguageModelV4FinishReason['unified'] {
3
+ export function mapOpenAIFinishReason({
4
+ finishReason,
5
+ hasRefusal = false,
6
+ }: {
7
+ finishReason: string | null | undefined;
8
+ // flag that checks if the model refused to answer:
9
+ hasRefusal?: boolean;
10
+ }): LanguageModelV4FinishReason['unified'] {
6
11
  switch (finishReason) {
7
12
  case 'stop':
8
- return 'stop';
13
+ // OpenAI reports a completed response for refusals, so the refusal is
14
+ // the reason the response ended:
15
+ return hasRefusal ? 'content-filter' : 'stop';
9
16
  case 'length':
10
17
  return 'length';
11
18
  case 'content_filter':
@@ -36,6 +36,8 @@ export const openaiChatResponseSchema = lazySchema(() =>
36
36
  message: z.object({
37
37
  role: z.literal('assistant').nullish(),
38
38
  content: z.string().nullish(),
39
+ // set instead of `content` when the model refuses to answer:
40
+ refusal: z.string().nullish(),
39
41
  audio: z
40
42
  .object({
41
43
  transcript: z.string().nullish(),
@@ -84,6 +86,22 @@ export const openaiChatResponseSchema = lazySchema(() =>
84
86
  }),
85
87
  )
86
88
  .nullish(),
89
+ // logprobs of the refusal tokens (set instead of `content` when
90
+ // the model refuses to answer):
91
+ refusal: z
92
+ .array(
93
+ z.object({
94
+ token: z.string(),
95
+ logprob: z.number(),
96
+ top_logprobs: z.array(
97
+ z.object({
98
+ token: z.string(),
99
+ logprob: z.number(),
100
+ }),
101
+ ),
102
+ }),
103
+ )
104
+ .nullish(),
87
105
  })
88
106
  .nullish(),
89
107
  finish_reason: z.string().nullish(),
@@ -128,6 +146,8 @@ export const openaiChatChunkSchema = lazySchema(() =>
128
146
  .object({
129
147
  role: z.enum(['assistant']).nullish(),
130
148
  content: z.string().nullish(),
149
+ // set instead of `content` when the model refuses to answer:
150
+ refusal: z.string().nullish(),
131
151
  tool_calls: z
132
152
  .array(
133
153
  z.object({
@@ -172,6 +192,22 @@ export const openaiChatChunkSchema = lazySchema(() =>
172
192
  }),
173
193
  )
174
194
  .nullish(),
195
+ // logprobs of the refusal tokens (set instead of `content` when
196
+ // the model refuses to answer):
197
+ refusal: z
198
+ .array(
199
+ z.object({
200
+ token: z.string(),
201
+ logprob: z.number(),
202
+ top_logprobs: z.array(
203
+ z.object({
204
+ token: z.string(),
205
+ logprob: z.number(),
206
+ }),
207
+ ),
208
+ }),
209
+ )
210
+ .nullish(),
175
211
  })
176
212
  .nullish(),
177
213
  finish_reason: z.string().nullish(),
@@ -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
+ >;