@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.
- package/CHANGELOG.md +54 -0
- package/dist/index.d.ts +79 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +788 -423
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +389 -9
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +629 -380
- package/dist/internal/index.js.map +1 -1
- package/docs/03-openai.mdx +224 -18
- package/package.json +3 -3
- package/src/chat/convert-to-openai-chat-messages.ts +29 -4
- package/src/chat/map-openai-finish-reason.ts +11 -4
- package/src/chat/openai-chat-api.ts +36 -0
- package/src/chat/openai-chat-language-model-options.ts +13 -0
- package/src/chat/openai-chat-language-model.ts +111 -22
- package/src/chat/openai-chat-prompt.ts +4 -0
- package/src/image/openai-image-model-options.ts +48 -0
- package/src/image/openai-image-model.ts +49 -4
- package/src/index.ts +1 -0
- package/src/internal/index.ts +4 -0
- package/src/live/openai-realtime-model-live.ts +21 -2
- package/src/openai-batch.ts +8 -0
- package/src/openai-model-catalog.ts +3 -1
- package/src/openai-tools.ts +4 -3
- package/src/realtime/openai-realtime-event-mapper.ts +172 -11
- package/src/realtime/openai-realtime-model.ts +57 -3
- package/src/responses/map-openai-responses-finish-reason.ts +7 -1
- package/src/responses/openai-responses-api.ts +90 -49
- package/src/responses/openai-responses-language-model.ts +56 -1
- package/src/responses/openai-responses-prepare-tools.ts +18 -1
- package/src/tool/code-interpreter.ts +38 -2
- package/src/tool/mcp.ts +27 -8
- package/src/transcription/openai-transcription-model.ts +43 -8
package/docs/03-openai.mdx
CHANGED
|
@@ -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
|
|
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
|
|
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`
|
|
1081
|
+
- **serverUrl** _string_ (required if `connectorId` and `tunnelId` are not provided)
|
|
1045
1082
|
|
|
1046
|
-
The URL for the MCP server.
|
|
1083
|
+
The URL for the MCP server. Cannot be combined with `tunnelId`.
|
|
1047
1084
|
|
|
1048
|
-
- **connectorId** _string_ (required if `serverUrl`
|
|
1085
|
+
- **connectorId** _string_ (required if `serverUrl` and `tunnelId` are not provided)
|
|
1049
1086
|
|
|
1050
|
-
Identifier for a service connector.
|
|
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<string, string>_ (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
|
|
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
|
|
3778
|
+
```ts
|
|
3575
3779
|
import { transcribe } from 'ai';
|
|
3576
|
-
import { 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
|
-
|
|
3582
|
-
openai: {
|
|
3583
|
-
//timestampGranularities: ['word'],
|
|
3584
|
-
timestampGranularities: ['segment'],
|
|
3585
|
-
} satisfies OpenAITranscriptionModelOptions,
|
|
3586
|
-
},
|
|
3785
|
+
timestampGranularities: ['word', 'segment'],
|
|
3587
3786
|
});
|
|
3588
3787
|
|
|
3589
|
-
|
|
3590
|
-
console.log(result.segments);
|
|
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.
|
|
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.
|
|
39
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
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({
|
|
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
|
|
5
|
-
|
|
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
|
-
|
|
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
|
+
>;
|