@ai-sdk/openai 3.0.30 → 3.0.32

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.
@@ -123,7 +123,8 @@ const { text } = await generateText({
123
123
  });
124
124
  ```
125
125
 
126
- OpenAI language models can also be used in the `streamText`, `generateObject`, and `streamObject` functions
126
+ OpenAI language models can also be used in the `streamText` function
127
+ and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output)
127
128
  (see [AI SDK Core](/docs/ai-sdk-core)).
128
129
 
129
130
  ### Responses Models
@@ -340,6 +341,67 @@ console.log('Reasoning:', result.reasoning);
340
341
 
341
342
  Learn more about reasoning summaries in the [OpenAI documentation](https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries).
342
343
 
344
+ #### WebSocket Transport
345
+
346
+ OpenAI's [WebSocket API](https://developers.openai.com/api/docs/guides/websocket-mode) keeps a persistent connection open, which can significantly
347
+ reduce Time-to-First-Byte (TTFB) in agentic workflows with many tool calls.
348
+ After the initial connection, subsequent requests skip TCP/TLS/HTTP negotiation entirely.
349
+
350
+ The [`ai-sdk-openai-websocket-fetch`](https://www.npmjs.com/package/ai-sdk-openai-websocket-fetch)
351
+ package provides a drop-in `fetch` replacement that routes streaming requests
352
+ through a persistent WebSocket connection.
353
+
354
+ <Tabs items={['pnpm', 'npm', 'yarn', 'bun']}>
355
+ <Tab>
356
+ <Snippet text="pnpm add ai-sdk-openai-websocket-fetch" dark />
357
+ </Tab>
358
+ <Tab>
359
+ <Snippet text="npm install ai-sdk-openai-websocket-fetch" dark />
360
+ </Tab>
361
+ <Tab>
362
+ <Snippet text="yarn add ai-sdk-openai-websocket-fetch" dark />
363
+ </Tab>
364
+ <Tab>
365
+ <Snippet text="bun add ai-sdk-openai-websocket-fetch" dark />
366
+ </Tab>
367
+ </Tabs>
368
+
369
+ Pass the WebSocket fetch to `createOpenAI` via the `fetch` option:
370
+
371
+ ```ts highlight="2,6-7,15"
372
+ import { createOpenAI } from '@ai-sdk/openai';
373
+ import { createWebSocketFetch } from 'ai-sdk-openai-websocket-fetch';
374
+ import { streamText } from 'ai';
375
+
376
+ // Create a WebSocket-backed fetch instance
377
+ const wsFetch = createWebSocketFetch();
378
+ const openai = createOpenAI({ fetch: wsFetch });
379
+
380
+ const result = streamText({
381
+ model: openai('gpt-4.1-mini'),
382
+ prompt: 'Hello!',
383
+ tools: {
384
+ // ...
385
+ },
386
+ onFinish: () => wsFetch.close(), // close the WebSocket when done
387
+ });
388
+ ```
389
+
390
+ The first request will be slower because it must establish the WebSocket connection
391
+ (DNS + TCP + TLS + WebSocket upgrade). After that, subsequent steps in a
392
+ multi-step tool-calling loop reuse the open connection, resulting in lower TTFB
393
+ per step.
394
+
395
+ <Note>
396
+ The WebSocket transport only routes streaming requests to the OpenAI Responses
397
+ API (`POST /responses` with `stream: true`) through the WebSocket. All other
398
+ requests (non-streaming, embeddings, etc.) fall through to the standard
399
+ `fetch` implementation.
400
+ </Note>
401
+
402
+ You can see a live side-by-side comparison of HTTP vs WebSocket streaming performance
403
+ in the [demo app](https://github.com/vercel-labs/ai-sdk-openai-websocket).
404
+
343
405
  #### Verbosity Control
344
406
 
345
407
  You can control the length and detail of model responses using the `textVerbosity` parameter:
@@ -999,37 +1061,26 @@ and the `mediaType` should be set to `'application/pdf'`.
999
1061
 
1000
1062
  #### Structured Outputs
1001
1063
 
1002
- The OpenAI Responses API supports structured outputs. You can enforce structured outputs using `generateObject` or `streamObject`, which expose a `schema` option. Additionally, you can pass a Zod or JSON Schema object to the `output` option when using `generateText` or `streamText`.
1064
+ The OpenAI Responses API supports structured outputs. You can use `generateText` or `streamText` with [`Output`](/docs/reference/ai-sdk-core/output) to enforce structured outputs.
1003
1065
 
1004
1066
  ```ts
1005
- // Using generateObject
1006
- const result = await generateObject({
1007
- model: openai('gpt-4.1'),
1008
- schema: z.object({
1009
- recipe: z.object({
1010
- name: z.string(),
1011
- ingredients: z.array(
1012
- z.object({
1013
- name: z.string(),
1014
- amount: z.string(),
1015
- }),
1016
- ),
1017
- steps: z.array(z.string()),
1018
- }),
1019
- }),
1020
- prompt: 'Generate a lasagna recipe.',
1021
- });
1022
-
1023
- // Using generateText
1024
1067
  const result = await generateText({
1025
1068
  model: openai('gpt-4.1'),
1026
- prompt: 'How do I make a pizza?',
1027
1069
  output: Output.object({
1028
1070
  schema: z.object({
1029
- ingredients: z.array(z.string()),
1030
- steps: z.array(z.string()),
1071
+ recipe: z.object({
1072
+ name: z.string(),
1073
+ ingredients: z.array(
1074
+ z.object({
1075
+ name: z.string(),
1076
+ amount: z.string(),
1077
+ }),
1078
+ ),
1079
+ steps: z.array(z.string()),
1080
+ }),
1031
1081
  }),
1032
1082
  }),
1083
+ prompt: 'Generate a lasagna recipe.',
1033
1084
  });
1034
1085
  ```
1035
1086
 
@@ -1458,32 +1509,34 @@ You can disable them by setting the `strictJsonSchema` option to `false`.
1458
1509
 
1459
1510
  ```ts highlight="7"
1460
1511
  import { openai, OpenAILanguageModelChatOptions } from '@ai-sdk/openai';
1461
- import { generateObject } from 'ai';
1512
+ import { generateText, Output } from 'ai';
1462
1513
  import { z } from 'zod';
1463
1514
 
1464
- const result = await generateObject({
1515
+ const result = await generateText({
1465
1516
  model: openai.chat('gpt-4o-2024-08-06'),
1466
1517
  providerOptions: {
1467
1518
  openai: {
1468
1519
  strictJsonSchema: false,
1469
1520
  } satisfies OpenAILanguageModelChatOptions,
1470
1521
  },
1471
- schemaName: 'recipe',
1472
- schemaDescription: 'A recipe for lasagna.',
1473
- schema: z.object({
1474
- name: z.string(),
1475
- ingredients: z.array(
1476
- z.object({
1477
- name: z.string(),
1478
- amount: z.string(),
1479
- }),
1480
- ),
1481
- steps: z.array(z.string()),
1522
+ output: Output.object({
1523
+ schema: z.object({
1524
+ name: z.string(),
1525
+ ingredients: z.array(
1526
+ z.object({
1527
+ name: z.string(),
1528
+ amount: z.string(),
1529
+ }),
1530
+ ),
1531
+ steps: z.array(z.string()),
1532
+ }),
1533
+ schemaName: 'recipe',
1534
+ schemaDescription: 'A recipe for lasagna.',
1482
1535
  }),
1483
1536
  prompt: 'Generate a lasagna recipe.',
1484
1537
  });
1485
1538
 
1486
- console.log(JSON.stringify(result.object, null, 2));
1539
+ console.log(JSON.stringify(result.output, null, 2));
1487
1540
  ```
1488
1541
 
1489
1542
  <Note type="warning">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/openai",
3
- "version": "3.0.30",
3
+ "version": "3.0.32",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -44,8 +44,8 @@
44
44
  "tsup": "^8",
45
45
  "typescript": "5.8.3",
46
46
  "zod": "3.25.76",
47
- "@vercel/ai-tsconfig": "0.0.0",
48
- "@ai-sdk/test-server": "1.0.3"
47
+ "@ai-sdk/test-server": "1.0.3",
48
+ "@vercel/ai-tsconfig": "0.0.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "zod": "^3.25.76 || ^4.1.8"
@@ -599,29 +599,31 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
599
599
  type: z.literal('web_search_call'),
600
600
  id: z.string(),
601
601
  status: z.string(),
602
- action: z.discriminatedUnion('type', [
603
- z.object({
604
- type: z.literal('search'),
605
- query: z.string().nullish(),
606
- sources: z
607
- .array(
608
- z.discriminatedUnion('type', [
609
- z.object({ type: z.literal('url'), url: z.string() }),
610
- z.object({ type: z.literal('api'), name: z.string() }),
611
- ]),
612
- )
613
- .nullish(),
614
- }),
615
- z.object({
616
- type: z.literal('open_page'),
617
- url: z.string().nullish(),
618
- }),
619
- z.object({
620
- type: z.literal('find_in_page'),
621
- url: z.string().nullish(),
622
- pattern: z.string().nullish(),
623
- }),
624
- ]),
602
+ action: z
603
+ .discriminatedUnion('type', [
604
+ z.object({
605
+ type: z.literal('search'),
606
+ query: z.string().nullish(),
607
+ sources: z
608
+ .array(
609
+ z.discriminatedUnion('type', [
610
+ z.object({ type: z.literal('url'), url: z.string() }),
611
+ z.object({ type: z.literal('api'), name: z.string() }),
612
+ ]),
613
+ )
614
+ .nullish(),
615
+ }),
616
+ z.object({
617
+ type: z.literal('open_page'),
618
+ url: z.string().nullish(),
619
+ }),
620
+ z.object({
621
+ type: z.literal('find_in_page'),
622
+ url: z.string().nullish(),
623
+ pattern: z.string().nullish(),
624
+ }),
625
+ ])
626
+ .nullish(),
625
627
  }),
626
628
  z.object({
627
629
  type: z.literal('file_search_call'),
@@ -966,29 +968,34 @@ export const openaiResponsesResponseSchema = lazySchema(() =>
966
968
  type: z.literal('web_search_call'),
967
969
  id: z.string(),
968
970
  status: z.string(),
969
- action: z.discriminatedUnion('type', [
970
- z.object({
971
- type: z.literal('search'),
972
- query: z.string().nullish(),
973
- sources: z
974
- .array(
975
- z.discriminatedUnion('type', [
976
- z.object({ type: z.literal('url'), url: z.string() }),
977
- z.object({ type: z.literal('api'), name: z.string() }),
978
- ]),
979
- )
980
- .nullish(),
981
- }),
982
- z.object({
983
- type: z.literal('open_page'),
984
- url: z.string().nullish(),
985
- }),
986
- z.object({
987
- type: z.literal('find_in_page'),
988
- url: z.string().nullish(),
989
- pattern: z.string().nullish(),
990
- }),
991
- ]),
971
+ action: z
972
+ .discriminatedUnion('type', [
973
+ z.object({
974
+ type: z.literal('search'),
975
+ query: z.string().nullish(),
976
+ sources: z
977
+ .array(
978
+ z.discriminatedUnion('type', [
979
+ z.object({ type: z.literal('url'), url: z.string() }),
980
+ z.object({
981
+ type: z.literal('api'),
982
+ name: z.string(),
983
+ }),
984
+ ]),
985
+ )
986
+ .nullish(),
987
+ }),
988
+ z.object({
989
+ type: z.literal('open_page'),
990
+ url: z.string().nullish(),
991
+ }),
992
+ z.object({
993
+ type: z.literal('find_in_page'),
994
+ url: z.string().nullish(),
995
+ pattern: z.string().nullish(),
996
+ }),
997
+ ])
998
+ .nullish(),
992
999
  }),
993
1000
  z.object({
994
1001
  type: z.literal('file_search_call'),
@@ -1969,8 +1969,12 @@ function isErrorChunk(
1969
1969
  }
1970
1970
 
1971
1971
  function mapWebSearchOutput(
1972
- action: OpenAIResponsesWebSearchAction,
1972
+ action: OpenAIResponsesWebSearchAction | null | undefined,
1973
1973
  ): InferSchema<typeof webSearchOutputSchema> {
1974
+ if (action == null) {
1975
+ return {};
1976
+ }
1977
+
1974
1978
  switch (action.type) {
1975
1979
  case 'search':
1976
1980
  return {
@@ -42,6 +42,8 @@ export const openaiResponsesReasoningModelIds = [
42
42
  'gpt-5.2',
43
43
  'gpt-5.2-chat-latest',
44
44
  'gpt-5.2-pro',
45
+ 'gpt-5.2-codex',
46
+ 'gpt-5.3-codex',
45
47
  ] as const;
46
48
 
47
49
  export const openaiResponsesModelIds = [
@@ -110,6 +112,8 @@ export type OpenAIResponsesModelId =
110
112
  | 'gpt-5.2'
111
113
  | 'gpt-5.2-chat-latest'
112
114
  | 'gpt-5.2-pro'
115
+ | 'gpt-5.2-codex'
116
+ | 'gpt-5.3-codex'
113
117
  | 'gpt-5-2025-08-07'
114
118
  | 'gpt-5-chat-latest'
115
119
  | 'gpt-5-codex'
@@ -29,21 +29,23 @@ export const webSearchPreviewInputSchema = lazySchema(() =>
29
29
  const webSearchPreviewOutputSchema = lazySchema(() =>
30
30
  zodSchema(
31
31
  z.object({
32
- action: z.discriminatedUnion('type', [
33
- z.object({
34
- type: z.literal('search'),
35
- query: z.string().optional(),
36
- }),
37
- z.object({
38
- type: z.literal('openPage'),
39
- url: z.string().nullish(),
40
- }),
41
- z.object({
42
- type: z.literal('findInPage'),
43
- url: z.string().nullish(),
44
- pattern: z.string().nullish(),
45
- }),
46
- ]),
32
+ action: z
33
+ .discriminatedUnion('type', [
34
+ z.object({
35
+ type: z.literal('search'),
36
+ query: z.string().optional(),
37
+ }),
38
+ z.object({
39
+ type: z.literal('openPage'),
40
+ url: z.string().nullish(),
41
+ }),
42
+ z.object({
43
+ type: z.literal('findInPage'),
44
+ url: z.string().nullish(),
45
+ pattern: z.string().nullish(),
46
+ }),
47
+ ])
48
+ .optional(),
47
49
  }),
48
50
  ),
49
51
  );
@@ -57,7 +59,7 @@ export const webSearchPreview = createProviderToolFactoryWithOutputSchema<
57
59
  * An object describing the specific action taken in this web search call.
58
60
  * Includes details on how the model used the web (search, open_page, find_in_page).
59
61
  */
60
- action:
62
+ action?:
61
63
  | {
62
64
  /**
63
65
  * Action type "search" - Performs a web search query.
@@ -31,21 +31,23 @@ const webSearchInputSchema = lazySchema(() => zodSchema(z.object({})));
31
31
  export const webSearchOutputSchema = lazySchema(() =>
32
32
  zodSchema(
33
33
  z.object({
34
- action: z.discriminatedUnion('type', [
35
- z.object({
36
- type: z.literal('search'),
37
- query: z.string().optional(),
38
- }),
39
- z.object({
40
- type: z.literal('openPage'),
41
- url: z.string().nullish(),
42
- }),
43
- z.object({
44
- type: z.literal('findInPage'),
45
- url: z.string().nullish(),
46
- pattern: z.string().nullish(),
47
- }),
48
- ]),
34
+ action: z
35
+ .discriminatedUnion('type', [
36
+ z.object({
37
+ type: z.literal('search'),
38
+ query: z.string().optional(),
39
+ }),
40
+ z.object({
41
+ type: z.literal('openPage'),
42
+ url: z.string().nullish(),
43
+ }),
44
+ z.object({
45
+ type: z.literal('findInPage'),
46
+ url: z.string().nullish(),
47
+ pattern: z.string().nullish(),
48
+ }),
49
+ ])
50
+ .optional(),
49
51
  sources: z
50
52
  .array(
51
53
  z.discriminatedUnion('type', [
@@ -67,7 +69,7 @@ export const webSearchToolFactory = createProviderToolFactoryWithOutputSchema<
67
69
  * An object describing the specific action taken in this web search call.
68
70
  * Includes details on how the model used the web (search, open_page, find_in_page).
69
71
  */
70
- action:
72
+ action?:
71
73
  | {
72
74
  /**
73
75
  * Action type "search" - Performs a web search query.