@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +234 -22
  2. package/README.md +2 -0
  3. package/dist/index.d.mts +134 -35
  4. package/dist/index.d.ts +134 -35
  5. package/dist/index.js +1700 -1139
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +1697 -1117
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/internal/index.d.mts +107 -41
  10. package/dist/internal/index.d.ts +107 -41
  11. package/dist/internal/index.js +1380 -939
  12. package/dist/internal/index.js.map +1 -1
  13. package/dist/internal/index.mjs +1371 -917
  14. package/dist/internal/index.mjs.map +1 -1
  15. package/docs/03-openai.mdx +274 -9
  16. package/package.json +3 -5
  17. package/src/chat/convert-openai-chat-usage.ts +2 -2
  18. package/src/chat/convert-to-openai-chat-messages.ts +26 -15
  19. package/src/chat/map-openai-finish-reason.ts +2 -2
  20. package/src/chat/openai-chat-language-model.ts +32 -24
  21. package/src/chat/openai-chat-options.ts +5 -0
  22. package/src/chat/openai-chat-prepare-tools.ts +6 -6
  23. package/src/completion/convert-openai-completion-usage.ts +2 -2
  24. package/src/completion/convert-to-openai-completion-prompt.ts +2 -2
  25. package/src/completion/map-openai-finish-reason.ts +2 -2
  26. package/src/completion/openai-completion-language-model.ts +20 -20
  27. package/src/embedding/openai-embedding-model.ts +5 -5
  28. package/src/files/openai-files-api.ts +17 -0
  29. package/src/files/openai-files-options.ts +18 -0
  30. package/src/files/openai-files.ts +102 -0
  31. package/src/image/openai-image-model.ts +9 -9
  32. package/src/index.ts +2 -0
  33. package/src/openai-config.ts +5 -5
  34. package/src/openai-language-model-capabilities.ts +3 -2
  35. package/src/openai-provider.ts +39 -21
  36. package/src/openai-tools.ts +12 -1
  37. package/src/responses/convert-openai-responses-usage.ts +2 -2
  38. package/src/responses/convert-to-openai-responses-input.ts +188 -14
  39. package/src/responses/map-openai-responses-finish-reason.ts +2 -2
  40. package/src/responses/openai-responses-api.ts +136 -2
  41. package/src/responses/openai-responses-language-model.ts +233 -37
  42. package/src/responses/openai-responses-options.ts +24 -2
  43. package/src/responses/openai-responses-prepare-tools.ts +34 -9
  44. package/src/responses/openai-responses-provider-metadata.ts +10 -0
  45. package/src/speech/openai-speech-model.ts +7 -7
  46. package/src/tool/custom.ts +0 -6
  47. package/src/tool/tool-search.ts +98 -0
  48. package/src/transcription/openai-transcription-model.ts +8 -8
@@ -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: stepCountIs(2),
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: stepCountIs(5),
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, stepCountIs } from 'ai';
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: stepCountIs(5),
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, stepCountIs } from 'ai';
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: stepCountIs(3),
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.2",
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.0",
40
- "@ai-sdk/provider-utils": "5.0.0-beta.1"
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 { LanguageModelV3Usage } from '@ai-sdk/provider';
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
- ): LanguageModelV3Usage {
19
+ ): LanguageModelV4Usage {
20
20
  if (usage == null) {
21
21
  return {
22
22
  inputTokens: {
@@ -1,23 +1,27 @@
1
1
  import {
2
- SharedV3Warning,
3
- LanguageModelV3Prompt,
2
+ SharedV4Warning,
3
+ LanguageModelV4Prompt,
4
4
  UnsupportedFunctionalityError,
5
5
  } from '@ai-sdk/provider';
6
6
  import { OpenAIChatPrompt } from './openai-chat-prompt';
7
- import { convertToBase64 } from '@ai-sdk/provider-utils';
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: LanguageModelV3Prompt;
17
+ prompt: LanguageModelV4Prompt;
14
18
  systemMessageMode?: 'system' | 'developer' | 'remove';
15
19
  }): {
16
20
  messages: OpenAIChatPrompt;
17
- warnings: Array<SharedV3Warning>;
21
+ warnings: Array<SharedV4Warning>;
18
22
  } {
19
23
  const messages: OpenAIChatPrompt = [];
20
- const warnings: Array<SharedV3Warning> = [];
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
- typeof part.data === 'string' &&
128
- part.data.startsWith('file-')
129
- ? { file_id: part.data }
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 { LanguageModelV3FinishReason } from '@ai-sdk/provider';
1
+ import { LanguageModelV4FinishReason } from '@ai-sdk/provider';
2
2
 
3
3
  export function mapOpenAIFinishReason(
4
4
  finishReason: string | null | undefined,
5
- ): LanguageModelV3FinishReason['unified'] {
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
- LanguageModelV3,
4
- LanguageModelV3CallOptions,
5
- LanguageModelV3Content,
6
- LanguageModelV3FinishReason,
7
- LanguageModelV3GenerateResult,
8
- LanguageModelV3StreamPart,
9
- LanguageModelV3StreamResult,
10
- SharedV3ProviderMetadata,
11
- SharedV3Warning,
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 LanguageModelV3 {
52
- readonly specificationVersion = 'v3';
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
- }: LanguageModelV3CallOptions) {
86
- const warnings: SharedV3Warning[] = [];
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: openaiOptions.reasoningEffort,
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
- openaiOptions.reasoningEffort !== 'none' ||
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: LanguageModelV3CallOptions,
318
- ): Promise<LanguageModelV3GenerateResult> {
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<LanguageModelV3Content> = [];
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: SharedV3ProviderMetadata = { openai: {} };
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: LanguageModelV3CallOptions,
406
- ): Promise<LanguageModelV3StreamResult> {
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: LanguageModelV3FinishReason = {
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: SharedV3ProviderMetadata = { openai: {} };
458
+ const providerMetadata: SharedV4ProviderMetadata = { openai: {} };
451
459
 
452
460
  return {
453
461
  stream: response.pipeThrough(
454
462
  new TransformStream<
455
463
  ParseResult<OpenAIChatChunk>,
456
- LanguageModelV3StreamPart
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 & {});