@ai-sdk/openai 4.0.0-beta.2 → 4.0.0-beta.21
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 +234 -22
- package/README.md +2 -0
- package/dist/index.d.mts +134 -35
- package/dist/index.d.ts +134 -35
- package/dist/index.js +1700 -1139
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1697 -1117
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.d.mts +107 -41
- package/dist/internal/index.d.ts +107 -41
- package/dist/internal/index.js +1380 -939
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +1371 -917
- package/dist/internal/index.mjs.map +1 -1
- package/docs/03-openai.mdx +274 -9
- package/package.json +3 -5
- package/src/chat/convert-openai-chat-usage.ts +2 -2
- package/src/chat/convert-to-openai-chat-messages.ts +26 -15
- package/src/chat/map-openai-finish-reason.ts +2 -2
- package/src/chat/openai-chat-language-model.ts +32 -24
- package/src/chat/openai-chat-options.ts +5 -0
- package/src/chat/openai-chat-prepare-tools.ts +6 -6
- package/src/completion/convert-openai-completion-usage.ts +2 -2
- package/src/completion/convert-to-openai-completion-prompt.ts +2 -2
- package/src/completion/map-openai-finish-reason.ts +2 -2
- package/src/completion/openai-completion-language-model.ts +20 -20
- package/src/embedding/openai-embedding-model.ts +5 -5
- package/src/files/openai-files-api.ts +17 -0
- package/src/files/openai-files-options.ts +18 -0
- package/src/files/openai-files.ts +102 -0
- package/src/image/openai-image-model.ts +9 -9
- package/src/index.ts +2 -0
- package/src/openai-config.ts +5 -5
- package/src/openai-language-model-capabilities.ts +3 -2
- package/src/openai-provider.ts +39 -21
- package/src/openai-tools.ts +12 -1
- package/src/responses/convert-openai-responses-usage.ts +2 -2
- package/src/responses/convert-to-openai-responses-input.ts +188 -14
- package/src/responses/map-openai-responses-finish-reason.ts +2 -2
- package/src/responses/openai-responses-api.ts +136 -2
- package/src/responses/openai-responses-language-model.ts +233 -37
- package/src/responses/openai-responses-options.ts +24 -2
- package/src/responses/openai-responses-prepare-tools.ts +34 -9
- package/src/responses/openai-responses-provider-metadata.ts +10 -0
- package/src/speech/openai-speech-model.ts +7 -7
- package/src/tool/custom.ts +0 -6
- package/src/tool/tool-search.ts +98 -0
- package/src/transcription/openai-transcription-model.ts +8 -8
package/docs/03-openai.mdx
CHANGED
|
@@ -257,6 +257,11 @@ The following provider options are available:
|
|
|
257
257
|
- **forceReasoning** _boolean_
|
|
258
258
|
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.
|
|
259
259
|
|
|
260
|
+
- **contextManagement** _Array<object>_
|
|
261
|
+
Enable server-side context management (compaction). When configured, the server automatically compresses conversation context when token usage crosses a specified threshold. Each object in the array should have:
|
|
262
|
+
- `type`: `'compaction'`
|
|
263
|
+
- `compactThreshold`: _number_ — the token count at which compaction is triggered
|
|
264
|
+
|
|
260
265
|
The OpenAI responses provider also returns provider-specific metadata:
|
|
261
266
|
|
|
262
267
|
For Responses models, you can type this metadata using `OpenaiResponsesProviderMetadata`:
|
|
@@ -764,7 +769,7 @@ const result = await generateText({
|
|
|
764
769
|
}),
|
|
765
770
|
},
|
|
766
771
|
prompt: 'List the files in my home directory.',
|
|
767
|
-
stopWhen:
|
|
772
|
+
stopWhen: isStepCount(2),
|
|
768
773
|
});
|
|
769
774
|
```
|
|
770
775
|
|
|
@@ -922,7 +927,7 @@ const result = await generateText({
|
|
|
922
927
|
}),
|
|
923
928
|
},
|
|
924
929
|
prompt: 'Use the skill to solve this problem.',
|
|
925
|
-
stopWhen:
|
|
930
|
+
stopWhen: isStepCount(5),
|
|
926
931
|
});
|
|
927
932
|
```
|
|
928
933
|
|
|
@@ -937,7 +942,7 @@ enabling iterative, multi-step code editing workflows.
|
|
|
937
942
|
|
|
938
943
|
```ts
|
|
939
944
|
import { openai } from '@ai-sdk/openai';
|
|
940
|
-
import { generateText,
|
|
945
|
+
import { generateText, isStepCount } from 'ai';
|
|
941
946
|
|
|
942
947
|
const result = await generateText({
|
|
943
948
|
model: openai('gpt-5.1'),
|
|
@@ -949,7 +954,7 @@ const result = await generateText({
|
|
|
949
954
|
}),
|
|
950
955
|
},
|
|
951
956
|
prompt: 'Create a python file that calculates the factorial of a number',
|
|
952
|
-
stopWhen:
|
|
957
|
+
stopWhen: isStepCount(5),
|
|
953
958
|
});
|
|
954
959
|
```
|
|
955
960
|
|
|
@@ -958,6 +963,145 @@ Your execute function must return:
|
|
|
958
963
|
- **status** _'completed' | 'failed'_ - Whether the patch was applied successfully
|
|
959
964
|
- **output** _string_ (optional) - Human-readable log text (e.g., results or error messages)
|
|
960
965
|
|
|
966
|
+
#### Tool Search
|
|
967
|
+
|
|
968
|
+
Tool search allows the model to dynamically search for and load tools into context as needed,
|
|
969
|
+
rather than loading all tool definitions up front. This can reduce token usage, cost, and latency
|
|
970
|
+
when you have many tools. Mark the tools you want to make searchable with `deferLoading: true`
|
|
971
|
+
in their `providerOptions`.
|
|
972
|
+
|
|
973
|
+
There are two execution modes:
|
|
974
|
+
|
|
975
|
+
- **Server-executed (hosted):** OpenAI searches across the deferred tools declared in the request and returns the loaded subset in the same response. No extra round-trip is needed.
|
|
976
|
+
- **Client-executed:** The model emits a `tool_search_call`, your application performs the lookup, and you return the matching tools via the `execute` callback.
|
|
977
|
+
|
|
978
|
+
##### Server-Executed (Hosted) Tool Search
|
|
979
|
+
|
|
980
|
+
Use hosted tool search when the candidate tools are already known at request time.
|
|
981
|
+
Add `openai.tools.toolSearch()` with no arguments and mark your tools with `deferLoading: true`:
|
|
982
|
+
|
|
983
|
+
```ts
|
|
984
|
+
import { openai } from '@ai-sdk/openai';
|
|
985
|
+
import { generateText, tool, isStepCount } from 'ai';
|
|
986
|
+
import { z } from 'zod';
|
|
987
|
+
|
|
988
|
+
const result = await generateText({
|
|
989
|
+
model: openai.responses('gpt-5.4'),
|
|
990
|
+
prompt: 'What is the weather in San Francisco?',
|
|
991
|
+
stopWhen: isStepCount(10),
|
|
992
|
+
tools: {
|
|
993
|
+
toolSearch: openai.tools.toolSearch(),
|
|
994
|
+
|
|
995
|
+
get_weather: tool({
|
|
996
|
+
description: 'Get the current weather at a specific location',
|
|
997
|
+
inputSchema: z.object({
|
|
998
|
+
location: z.string(),
|
|
999
|
+
unit: z.enum(['celsius', 'fahrenheit']),
|
|
1000
|
+
}),
|
|
1001
|
+
execute: async ({ location, unit }) => ({
|
|
1002
|
+
location,
|
|
1003
|
+
temperature: unit === 'celsius' ? 18 : 64,
|
|
1004
|
+
}),
|
|
1005
|
+
providerOptions: {
|
|
1006
|
+
openai: { deferLoading: true },
|
|
1007
|
+
},
|
|
1008
|
+
}),
|
|
1009
|
+
|
|
1010
|
+
search_files: tool({
|
|
1011
|
+
description: 'Search through files in the workspace',
|
|
1012
|
+
inputSchema: z.object({ query: z.string() }),
|
|
1013
|
+
execute: async ({ query }) => ({
|
|
1014
|
+
results: [`Found 3 files matching "${query}"`],
|
|
1015
|
+
}),
|
|
1016
|
+
providerOptions: {
|
|
1017
|
+
openai: { deferLoading: true },
|
|
1018
|
+
},
|
|
1019
|
+
}),
|
|
1020
|
+
},
|
|
1021
|
+
});
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
In hosted mode, the model internally searches the deferred tools, loads the relevant ones, and
|
|
1025
|
+
proceeds to call them — all within a single response. The `tool_search_call` and
|
|
1026
|
+
`tool_search_output` items appear in the response with `execution: 'server'` and `call_id: null`.
|
|
1027
|
+
|
|
1028
|
+
##### Client-Executed Tool Search
|
|
1029
|
+
|
|
1030
|
+
Use client-executed tool search when tool discovery depends on runtime state — for example,
|
|
1031
|
+
tools that vary per tenant, project, or external system. Pass `execution: 'client'` along with
|
|
1032
|
+
a `description`, `parameters` schema, and an `execute` callback:
|
|
1033
|
+
|
|
1034
|
+
```ts
|
|
1035
|
+
import { openai } from '@ai-sdk/openai';
|
|
1036
|
+
import { generateText, tool, isStepCount } from 'ai';
|
|
1037
|
+
import { z } from 'zod';
|
|
1038
|
+
|
|
1039
|
+
const result = await generateText({
|
|
1040
|
+
model: openai.responses('gpt-5.4'),
|
|
1041
|
+
prompt: 'What is the weather in San Francisco?',
|
|
1042
|
+
stopWhen: isStepCount(10),
|
|
1043
|
+
tools: {
|
|
1044
|
+
toolSearch: openai.tools.toolSearch({
|
|
1045
|
+
execution: 'client',
|
|
1046
|
+
description: 'Search for available tools based on what the user needs.',
|
|
1047
|
+
parameters: {
|
|
1048
|
+
type: 'object',
|
|
1049
|
+
properties: {
|
|
1050
|
+
goal: {
|
|
1051
|
+
type: 'string',
|
|
1052
|
+
description: 'What the user is trying to accomplish',
|
|
1053
|
+
},
|
|
1054
|
+
},
|
|
1055
|
+
required: ['goal'],
|
|
1056
|
+
additionalProperties: false,
|
|
1057
|
+
},
|
|
1058
|
+
execute: async ({ arguments: args }) => {
|
|
1059
|
+
// Your custom tool discovery logic here.
|
|
1060
|
+
// Return the tools that match the search goal.
|
|
1061
|
+
return {
|
|
1062
|
+
tools: [
|
|
1063
|
+
{
|
|
1064
|
+
type: 'function',
|
|
1065
|
+
name: 'get_weather',
|
|
1066
|
+
description: 'Get the current weather at a specific location',
|
|
1067
|
+
deferLoading: true,
|
|
1068
|
+
parameters: {
|
|
1069
|
+
type: 'object',
|
|
1070
|
+
properties: {
|
|
1071
|
+
location: { type: 'string' },
|
|
1072
|
+
},
|
|
1073
|
+
required: ['location'],
|
|
1074
|
+
additionalProperties: false,
|
|
1075
|
+
},
|
|
1076
|
+
},
|
|
1077
|
+
],
|
|
1078
|
+
};
|
|
1079
|
+
},
|
|
1080
|
+
}),
|
|
1081
|
+
|
|
1082
|
+
get_weather: tool({
|
|
1083
|
+
description: 'Get the current weather at a specific location',
|
|
1084
|
+
inputSchema: z.object({ location: z.string() }),
|
|
1085
|
+
execute: async ({ location }) => ({
|
|
1086
|
+
location,
|
|
1087
|
+
temperature: 64,
|
|
1088
|
+
condition: 'Partly cloudy',
|
|
1089
|
+
}),
|
|
1090
|
+
providerOptions: {
|
|
1091
|
+
openai: { deferLoading: true },
|
|
1092
|
+
},
|
|
1093
|
+
}),
|
|
1094
|
+
},
|
|
1095
|
+
});
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
In client mode, the flow spans two steps:
|
|
1099
|
+
|
|
1100
|
+
1. **Step 1:** The model emits a `tool_search_call` with `execution: 'client'` and a non-null `call_id`. The SDK calls your `execute` callback with the search arguments. Your callback returns the discovered tools.
|
|
1101
|
+
2. **Step 2:** The SDK sends the `tool_search_output` (with the matching `call_id`) back to the model. The model can now call the loaded tools as normal function calls.
|
|
1102
|
+
|
|
1103
|
+
For more details, see the [OpenAI Tool Search documentation](https://platform.openai.com/docs/guides/tools-tool-search).
|
|
1104
|
+
|
|
961
1105
|
#### Custom Tool
|
|
962
1106
|
|
|
963
1107
|
The OpenAI Responses API supports
|
|
@@ -969,13 +1113,12 @@ SQL queries, code snippets, or any output that must match a specific pattern.
|
|
|
969
1113
|
|
|
970
1114
|
```ts
|
|
971
1115
|
import { openai } from '@ai-sdk/openai';
|
|
972
|
-
import { generateText,
|
|
1116
|
+
import { generateText, isStepCount } from 'ai';
|
|
973
1117
|
|
|
974
1118
|
const result = await generateText({
|
|
975
1119
|
model: openai.responses('gpt-5.2-codex'),
|
|
976
1120
|
tools: {
|
|
977
1121
|
write_sql: openai.tools.customTool({
|
|
978
|
-
name: 'write_sql',
|
|
979
1122
|
description: 'Write a SQL SELECT query to answer the user question.',
|
|
980
1123
|
format: {
|
|
981
1124
|
type: 'grammar',
|
|
@@ -991,7 +1134,7 @@ const result = await generateText({
|
|
|
991
1134
|
},
|
|
992
1135
|
toolChoice: 'required',
|
|
993
1136
|
prompt: 'Write a SQL query to get all users older than 25.',
|
|
994
|
-
stopWhen:
|
|
1137
|
+
stopWhen: isStepCount(3),
|
|
995
1138
|
});
|
|
996
1139
|
```
|
|
997
1140
|
|
|
@@ -1005,7 +1148,6 @@ const result = streamText({
|
|
|
1005
1148
|
model: openai.responses('gpt-5.2-codex'),
|
|
1006
1149
|
tools: {
|
|
1007
1150
|
write_sql: openai.tools.customTool({
|
|
1008
|
-
name: 'write_sql',
|
|
1009
1151
|
description: 'Write a SQL SELECT query to answer the user question.',
|
|
1010
1152
|
format: {
|
|
1011
1153
|
type: 'grammar',
|
|
@@ -1028,7 +1170,6 @@ for await (const chunk of result.fullStream) {
|
|
|
1028
1170
|
|
|
1029
1171
|
The custom tool can be configured with:
|
|
1030
1172
|
|
|
1031
|
-
- **name** _string_ (required) - The name of the custom tool. Used to identify the tool in tool calls.
|
|
1032
1173
|
- **description** _string_ (optional) - A description of what the tool does, to help the model understand when to use it.
|
|
1033
1174
|
- **format** _object_ (optional) - The output format constraint. Omit for unconstrained text output.
|
|
1034
1175
|
- **type** _'grammar' | 'text'_ - The format type. Use `'grammar'` for constrained output or `'text'` for explicit unconstrained text.
|
|
@@ -1375,6 +1516,125 @@ for (const part of result.content) {
|
|
|
1375
1516
|
are fields like `filename` that are directly available on the source object.
|
|
1376
1517
|
</Note>
|
|
1377
1518
|
|
|
1519
|
+
#### Compaction
|
|
1520
|
+
|
|
1521
|
+
The OpenAI Responses API supports server-side context compaction. When enabled, the server automatically compresses conversation context when token usage crosses a configured threshold. This is useful for long-running conversations or agent loops where you want to stay within token limits without manually managing context.
|
|
1522
|
+
|
|
1523
|
+
The compaction item returned by the server is opaque and encrypted — it carries forward key prior state and reasoning into the next turn using fewer tokens. The AI SDK handles this automatically: compaction items are returned as text parts with special `providerMetadata`, and when passed back in subsequent requests they are sent as compaction input items.
|
|
1524
|
+
|
|
1525
|
+
```ts highlight="7-11"
|
|
1526
|
+
import {
|
|
1527
|
+
openai,
|
|
1528
|
+
type OpenAILanguageModelResponsesOptions,
|
|
1529
|
+
} from '@ai-sdk/openai';
|
|
1530
|
+
import { generateText } from 'ai';
|
|
1531
|
+
|
|
1532
|
+
const result = await generateText({
|
|
1533
|
+
model: openai.responses('gpt-5.2'),
|
|
1534
|
+
messages: conversationHistory,
|
|
1535
|
+
providerOptions: {
|
|
1536
|
+
openai: {
|
|
1537
|
+
store: false,
|
|
1538
|
+
contextManagement: [{ type: 'compaction', compactThreshold: 50000 }],
|
|
1539
|
+
} satisfies OpenAILanguageModelResponsesOptions,
|
|
1540
|
+
},
|
|
1541
|
+
});
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
**Configuration:**
|
|
1545
|
+
|
|
1546
|
+
- **type** — Must be `'compaction'`
|
|
1547
|
+
- **compactThreshold** — The token count at which compaction is triggered. When the rendered input token count crosses this threshold, the server runs a compaction pass before continuing inference.
|
|
1548
|
+
|
|
1549
|
+
<Note>
|
|
1550
|
+
Server-side compaction is ZDR-friendly when you set `store: false` on your
|
|
1551
|
+
requests.
|
|
1552
|
+
</Note>
|
|
1553
|
+
|
|
1554
|
+
##### Detecting Compaction in Streams
|
|
1555
|
+
|
|
1556
|
+
When using `streamText`, you can detect compaction by checking the `providerMetadata` on `text-start` and `text-end` events:
|
|
1557
|
+
|
|
1558
|
+
```ts
|
|
1559
|
+
import {
|
|
1560
|
+
openai,
|
|
1561
|
+
type OpenAILanguageModelResponsesOptions,
|
|
1562
|
+
} from '@ai-sdk/openai';
|
|
1563
|
+
import { streamText } from 'ai';
|
|
1564
|
+
|
|
1565
|
+
const result = streamText({
|
|
1566
|
+
model: openai.responses('gpt-5.2'),
|
|
1567
|
+
messages: conversationHistory,
|
|
1568
|
+
providerOptions: {
|
|
1569
|
+
openai: {
|
|
1570
|
+
store: false,
|
|
1571
|
+
contextManagement: [{ type: 'compaction', compactThreshold: 50000 }],
|
|
1572
|
+
} satisfies OpenAILanguageModelResponsesOptions,
|
|
1573
|
+
},
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
for await (const part of result.fullStream) {
|
|
1577
|
+
switch (part.type) {
|
|
1578
|
+
case 'text-start': {
|
|
1579
|
+
const isCompaction = part.providerMetadata?.openai?.type === 'compaction';
|
|
1580
|
+
if (isCompaction) {
|
|
1581
|
+
// ... your logic
|
|
1582
|
+
}
|
|
1583
|
+
break;
|
|
1584
|
+
}
|
|
1585
|
+
case 'text-end': {
|
|
1586
|
+
const isCompaction = part.providerMetadata?.openai?.type === 'compaction';
|
|
1587
|
+
if (isCompaction) {
|
|
1588
|
+
// ... your logic
|
|
1589
|
+
}
|
|
1590
|
+
break;
|
|
1591
|
+
}
|
|
1592
|
+
case 'text-delta': {
|
|
1593
|
+
process.stdout.write(part.text);
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
##### Compaction in UI Applications
|
|
1601
|
+
|
|
1602
|
+
When using `useChat` or other UI hooks, compaction items appear as text parts with `providerMetadata`. You can detect and style them differently in your UI:
|
|
1603
|
+
|
|
1604
|
+
```tsx
|
|
1605
|
+
{
|
|
1606
|
+
message.parts.map((part, index) => {
|
|
1607
|
+
if (part.type === 'text') {
|
|
1608
|
+
const isCompaction =
|
|
1609
|
+
(part.providerMetadata?.openai as { type?: string } | undefined)
|
|
1610
|
+
?.type === 'compaction';
|
|
1611
|
+
|
|
1612
|
+
if (isCompaction) {
|
|
1613
|
+
return (
|
|
1614
|
+
<div
|
|
1615
|
+
key={index}
|
|
1616
|
+
className="bg-yellow-100 border-l-4 border-yellow-500 p-2"
|
|
1617
|
+
>
|
|
1618
|
+
<span className="font-bold">[Context Compacted]</span>
|
|
1619
|
+
<p className="text-sm text-yellow-700">
|
|
1620
|
+
The server compressed the conversation context to reduce token
|
|
1621
|
+
usage.
|
|
1622
|
+
</p>
|
|
1623
|
+
</div>
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
return <div key={index}>{part.text}</div>;
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
```
|
|
1631
|
+
|
|
1632
|
+
The metadata includes the following fields:
|
|
1633
|
+
|
|
1634
|
+
- **type** — Always `'compaction'`
|
|
1635
|
+
- **itemId** _string_ — The ID of the compaction item in the Responses API
|
|
1636
|
+
- **encryptedContent** _string_ (optional) — The encrypted compaction state. This is automatically sent back to the API when the message is included in subsequent requests.
|
|
1637
|
+
|
|
1378
1638
|
### Chat Models
|
|
1379
1639
|
|
|
1380
1640
|
You can create models that call the [OpenAI chat API](https://platform.openai.com/docs/api-reference/chat) using the `.chat()` factory method.
|
|
@@ -2041,6 +2301,11 @@ The following optional provider options are available for OpenAI completion mode
|
|
|
2041
2301
|
|
|
2042
2302
|
| Model | Image Input | Audio Input | Object Generation | Tool Usage |
|
|
2043
2303
|
| --------------------- | ------------------- | ------------------- | ------------------- | ------------------- |
|
|
2304
|
+
| `gpt-5.4-pro` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2305
|
+
| `gpt-5.4` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2306
|
+
| `gpt-5.4-mini` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2307
|
+
| `gpt-5.4-nano` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2308
|
+
| `gpt-5.3-chat-latest` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2044
2309
|
| `gpt-5.2-pro` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2045
2310
|
| `gpt-5.2-chat-latest` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2046
2311
|
| `gpt-5.2` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/openai",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.21",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@ai-sdk/provider": "4.0.0-beta.
|
|
40
|
-
"@ai-sdk/provider-utils": "5.0.0-beta.
|
|
39
|
+
"@ai-sdk/provider": "4.0.0-beta.6",
|
|
40
|
+
"@ai-sdk/provider-utils": "5.0.0-beta.10"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "20.17.24",
|
|
@@ -71,9 +71,7 @@
|
|
|
71
71
|
"build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
|
|
72
72
|
"build:watch": "pnpm clean && tsup --watch",
|
|
73
73
|
"clean": "del-cli dist docs *.tsbuildinfo",
|
|
74
|
-
"lint": "eslint \"./**/*.ts*\"",
|
|
75
74
|
"type-check": "tsc --build",
|
|
76
|
-
"prettier-check": "prettier --check \"./**/*.ts*\"",
|
|
77
75
|
"test": "pnpm test:node && pnpm test:edge",
|
|
78
76
|
"test:update": "pnpm test:node -u",
|
|
79
77
|
"test:watch": "vitest --config vitest.node.config.js",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LanguageModelV4Usage } from '@ai-sdk/provider';
|
|
2
2
|
|
|
3
3
|
export type OpenAIChatUsage = {
|
|
4
4
|
prompt_tokens?: number | null;
|
|
@@ -16,7 +16,7 @@ export type OpenAIChatUsage = {
|
|
|
16
16
|
|
|
17
17
|
export function convertOpenAIChatUsage(
|
|
18
18
|
usage: OpenAIChatUsage | undefined | null,
|
|
19
|
-
):
|
|
19
|
+
): LanguageModelV4Usage {
|
|
20
20
|
if (usage == null) {
|
|
21
21
|
return {
|
|
22
22
|
inputTokens: {
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
SharedV4Warning,
|
|
3
|
+
LanguageModelV4Prompt,
|
|
4
4
|
UnsupportedFunctionalityError,
|
|
5
5
|
} from '@ai-sdk/provider';
|
|
6
6
|
import { OpenAIChatPrompt } from './openai-chat-prompt';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
convertToBase64,
|
|
9
|
+
isProviderReference,
|
|
10
|
+
resolveProviderReference,
|
|
11
|
+
} from '@ai-sdk/provider-utils';
|
|
8
12
|
|
|
9
13
|
export function convertToOpenAIChatMessages({
|
|
10
14
|
prompt,
|
|
11
15
|
systemMessageMode = 'system',
|
|
12
16
|
}: {
|
|
13
|
-
prompt:
|
|
17
|
+
prompt: LanguageModelV4Prompt;
|
|
14
18
|
systemMessageMode?: 'system' | 'developer' | 'remove';
|
|
15
19
|
}): {
|
|
16
20
|
messages: OpenAIChatPrompt;
|
|
17
|
-
warnings: Array<
|
|
21
|
+
warnings: Array<SharedV4Warning>;
|
|
18
22
|
} {
|
|
19
23
|
const messages: OpenAIChatPrompt = [];
|
|
20
|
-
const warnings: Array<
|
|
24
|
+
const warnings: Array<SharedV4Warning> = [];
|
|
21
25
|
|
|
22
26
|
for (const { role, content } of prompt) {
|
|
23
27
|
switch (role) {
|
|
@@ -62,6 +66,18 @@ export function convertToOpenAIChatMessages({
|
|
|
62
66
|
return { type: 'text', text: part.text };
|
|
63
67
|
}
|
|
64
68
|
case 'file': {
|
|
69
|
+
if (isProviderReference(part.data)) {
|
|
70
|
+
return {
|
|
71
|
+
type: 'file',
|
|
72
|
+
file: {
|
|
73
|
+
file_id: resolveProviderReference({
|
|
74
|
+
reference: part.data,
|
|
75
|
+
provider: 'openai',
|
|
76
|
+
}),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
65
81
|
if (part.mediaType.startsWith('image/')) {
|
|
66
82
|
const mediaType =
|
|
67
83
|
part.mediaType === 'image/*'
|
|
@@ -76,7 +92,6 @@ export function convertToOpenAIChatMessages({
|
|
|
76
92
|
? part.data.toString()
|
|
77
93
|
: `data:${mediaType};base64,${convertToBase64(part.data)}`,
|
|
78
94
|
|
|
79
|
-
// OpenAI specific extension: image detail
|
|
80
95
|
detail: part.providerOptions?.openai?.imageDetail,
|
|
81
96
|
},
|
|
82
97
|
};
|
|
@@ -123,14 +138,10 @@ export function convertToOpenAIChatMessages({
|
|
|
123
138
|
|
|
124
139
|
return {
|
|
125
140
|
type: 'file',
|
|
126
|
-
file:
|
|
127
|
-
|
|
128
|
-
part.data
|
|
129
|
-
|
|
130
|
-
: {
|
|
131
|
-
filename: part.filename ?? `part-${index}.pdf`,
|
|
132
|
-
file_data: `data:application/pdf;base64,${convertToBase64(part.data)}`,
|
|
133
|
-
},
|
|
141
|
+
file: {
|
|
142
|
+
filename: part.filename ?? `part-${index}.pdf`,
|
|
143
|
+
file_data: `data:application/pdf;base64,${convertToBase64(part.data)}`,
|
|
144
|
+
},
|
|
134
145
|
};
|
|
135
146
|
} else {
|
|
136
147
|
throw new UnsupportedFunctionalityError({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LanguageModelV4FinishReason } from '@ai-sdk/provider';
|
|
2
2
|
|
|
3
3
|
export function mapOpenAIFinishReason(
|
|
4
4
|
finishReason: string | null | undefined,
|
|
5
|
-
):
|
|
5
|
+
): LanguageModelV4FinishReason['unified'] {
|
|
6
6
|
switch (finishReason) {
|
|
7
7
|
case 'stop':
|
|
8
8
|
return 'stop';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
InvalidResponseDataError,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
LanguageModelV4,
|
|
4
|
+
LanguageModelV4CallOptions,
|
|
5
|
+
LanguageModelV4Content,
|
|
6
|
+
LanguageModelV4FinishReason,
|
|
7
|
+
LanguageModelV4GenerateResult,
|
|
8
|
+
LanguageModelV4StreamPart,
|
|
9
|
+
LanguageModelV4StreamResult,
|
|
10
|
+
SharedV4ProviderMetadata,
|
|
11
|
+
SharedV4Warning,
|
|
12
12
|
} from '@ai-sdk/provider';
|
|
13
13
|
import {
|
|
14
14
|
FetchFunction,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
createEventSourceResponseHandler,
|
|
18
18
|
createJsonResponseHandler,
|
|
19
19
|
generateId,
|
|
20
|
+
isCustomReasoning,
|
|
20
21
|
isParsableJson,
|
|
21
22
|
parseProviderOptions,
|
|
22
23
|
postJsonToApi,
|
|
@@ -48,8 +49,8 @@ type OpenAIChatConfig = {
|
|
|
48
49
|
fetch?: FetchFunction;
|
|
49
50
|
};
|
|
50
51
|
|
|
51
|
-
export class OpenAIChatLanguageModel implements
|
|
52
|
-
readonly specificationVersion = '
|
|
52
|
+
export class OpenAIChatLanguageModel implements LanguageModelV4 {
|
|
53
|
+
readonly specificationVersion = 'v4';
|
|
53
54
|
|
|
54
55
|
readonly modelId: OpenAIChatModelId;
|
|
55
56
|
|
|
@@ -81,9 +82,10 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
81
82
|
seed,
|
|
82
83
|
tools,
|
|
83
84
|
toolChoice,
|
|
85
|
+
reasoning,
|
|
84
86
|
providerOptions,
|
|
85
|
-
}:
|
|
86
|
-
const warnings:
|
|
87
|
+
}: LanguageModelV4CallOptions) {
|
|
88
|
+
const warnings: SharedV4Warning[] = [];
|
|
87
89
|
|
|
88
90
|
// Parse provider options
|
|
89
91
|
const openaiOptions =
|
|
@@ -94,6 +96,12 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
94
96
|
})) ?? {};
|
|
95
97
|
|
|
96
98
|
const modelCapabilities = getOpenAILanguageModelCapabilities(this.modelId);
|
|
99
|
+
|
|
100
|
+
// AI SDK reasoning values map directly to the OpenAI reasoning values.
|
|
101
|
+
const resolvedReasoningEffort =
|
|
102
|
+
openaiOptions.reasoningEffort ??
|
|
103
|
+
(isCustomReasoning(reasoning) ? reasoning : undefined);
|
|
104
|
+
|
|
97
105
|
const isReasoningModel =
|
|
98
106
|
openaiOptions.forceReasoning ?? modelCapabilities.isReasoningModel;
|
|
99
107
|
|
|
@@ -168,7 +176,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
168
176
|
store: openaiOptions.store,
|
|
169
177
|
metadata: openaiOptions.metadata,
|
|
170
178
|
prediction: openaiOptions.prediction,
|
|
171
|
-
reasoning_effort:
|
|
179
|
+
reasoning_effort: resolvedReasoningEffort,
|
|
172
180
|
service_tier: openaiOptions.serviceTier,
|
|
173
181
|
prompt_cache_key: openaiOptions.promptCacheKey,
|
|
174
182
|
prompt_cache_retention: openaiOptions.promptCacheRetention,
|
|
@@ -184,7 +192,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
184
192
|
// when reasoning effort is none, gpt-5.1 models allow temperature, topP, logprobs
|
|
185
193
|
// https://platform.openai.com/docs/guides/latest-model#gpt-5-1-parameter-compatibility
|
|
186
194
|
if (
|
|
187
|
-
|
|
195
|
+
resolvedReasoningEffort !== 'none' ||
|
|
188
196
|
!modelCapabilities.supportsNonReasoningParameters
|
|
189
197
|
) {
|
|
190
198
|
if (baseArgs.temperature != null) {
|
|
@@ -314,8 +322,8 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
314
322
|
}
|
|
315
323
|
|
|
316
324
|
async doGenerate(
|
|
317
|
-
options:
|
|
318
|
-
): Promise<
|
|
325
|
+
options: LanguageModelV4CallOptions,
|
|
326
|
+
): Promise<LanguageModelV4GenerateResult> {
|
|
319
327
|
const { args: body, warnings } = await this.getArgs(options);
|
|
320
328
|
|
|
321
329
|
const {
|
|
@@ -338,7 +346,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
338
346
|
});
|
|
339
347
|
|
|
340
348
|
const choice = response.choices[0];
|
|
341
|
-
const content: Array<
|
|
349
|
+
const content: Array<LanguageModelV4Content> = [];
|
|
342
350
|
|
|
343
351
|
// text content:
|
|
344
352
|
const text = choice.message.content;
|
|
@@ -370,7 +378,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
370
378
|
// provider metadata:
|
|
371
379
|
const completionTokenDetails = response.usage?.completion_tokens_details;
|
|
372
380
|
const promptTokenDetails = response.usage?.prompt_tokens_details;
|
|
373
|
-
const providerMetadata:
|
|
381
|
+
const providerMetadata: SharedV4ProviderMetadata = { openai: {} };
|
|
374
382
|
if (completionTokenDetails?.accepted_prediction_tokens != null) {
|
|
375
383
|
providerMetadata.openai.acceptedPredictionTokens =
|
|
376
384
|
completionTokenDetails?.accepted_prediction_tokens;
|
|
@@ -402,8 +410,8 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
402
410
|
}
|
|
403
411
|
|
|
404
412
|
async doStream(
|
|
405
|
-
options:
|
|
406
|
-
): Promise<
|
|
413
|
+
options: LanguageModelV4CallOptions,
|
|
414
|
+
): Promise<LanguageModelV4StreamResult> {
|
|
407
415
|
const { args, warnings } = await this.getArgs(options);
|
|
408
416
|
|
|
409
417
|
const body = {
|
|
@@ -439,7 +447,7 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
439
447
|
hasFinished: boolean;
|
|
440
448
|
}> = [];
|
|
441
449
|
|
|
442
|
-
let finishReason:
|
|
450
|
+
let finishReason: LanguageModelV4FinishReason = {
|
|
443
451
|
unified: 'other',
|
|
444
452
|
raw: undefined,
|
|
445
453
|
};
|
|
@@ -447,13 +455,13 @@ export class OpenAIChatLanguageModel implements LanguageModelV3 {
|
|
|
447
455
|
let metadataExtracted = false;
|
|
448
456
|
let isActiveText = false;
|
|
449
457
|
|
|
450
|
-
const providerMetadata:
|
|
458
|
+
const providerMetadata: SharedV4ProviderMetadata = { openai: {} };
|
|
451
459
|
|
|
452
460
|
return {
|
|
453
461
|
stream: response.pipeThrough(
|
|
454
462
|
new TransformStream<
|
|
455
463
|
ParseResult<OpenAIChatChunk>,
|
|
456
|
-
|
|
464
|
+
LanguageModelV4StreamPart
|
|
457
465
|
>({
|
|
458
466
|
start(controller) {
|
|
459
467
|
controller.enqueue({ type: 'stream-start', warnings });
|
|
@@ -51,8 +51,13 @@ export type OpenAIChatModelId =
|
|
|
51
51
|
| 'gpt-5.2-chat-latest'
|
|
52
52
|
| 'gpt-5.2-pro'
|
|
53
53
|
| 'gpt-5.2-pro-2025-12-11'
|
|
54
|
+
| 'gpt-5.3-chat-latest'
|
|
54
55
|
| 'gpt-5.4'
|
|
55
56
|
| 'gpt-5.4-2026-03-05'
|
|
57
|
+
| 'gpt-5.4-mini'
|
|
58
|
+
| 'gpt-5.4-mini-2026-03-17'
|
|
59
|
+
| 'gpt-5.4-nano'
|
|
60
|
+
| 'gpt-5.4-nano-2026-03-17'
|
|
56
61
|
| 'gpt-5.4-pro'
|
|
57
62
|
| 'gpt-5.4-pro-2026-03-05'
|
|
58
63
|
| (string & {});
|