@ai-sdk/openai 4.0.0-beta.7 → 4.0.0-beta.74

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 (73) hide show
  1. package/CHANGELOG.md +636 -24
  2. package/README.md +2 -0
  3. package/dist/index.d.ts +240 -44
  4. package/dist/index.js +3345 -1683
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +390 -36
  7. package/dist/internal/index.js +2707 -1706
  8. package/dist/internal/index.js.map +1 -1
  9. package/docs/03-openai.mdx +413 -39
  10. package/package.json +16 -17
  11. package/src/chat/convert-openai-chat-usage.ts +1 -1
  12. package/src/chat/convert-to-openai-chat-messages.ts +96 -68
  13. package/src/chat/map-openai-finish-reason.ts +1 -1
  14. package/src/chat/openai-chat-api.ts +6 -2
  15. package/src/chat/{openai-chat-options.ts → openai-chat-language-model-options.ts} +11 -1
  16. package/src/chat/openai-chat-language-model.ts +82 -148
  17. package/src/chat/openai-chat-prepare-tools.ts +3 -3
  18. package/src/completion/convert-openai-completion-usage.ts +1 -1
  19. package/src/completion/convert-to-openai-completion-prompt.ts +1 -2
  20. package/src/completion/map-openai-finish-reason.ts +1 -1
  21. package/src/completion/openai-completion-api.ts +5 -2
  22. package/src/completion/{openai-completion-options.ts → openai-completion-language-model-options.ts} +5 -1
  23. package/src/completion/openai-completion-language-model.ts +53 -17
  24. package/src/embedding/{openai-embedding-options.ts → openai-embedding-model-options.ts} +5 -1
  25. package/src/embedding/openai-embedding-model.ts +22 -5
  26. package/src/files/openai-files-api.ts +17 -0
  27. package/src/files/openai-files-options.ts +22 -0
  28. package/src/files/openai-files.ts +100 -0
  29. package/src/image/openai-image-model-options.ts +123 -0
  30. package/src/image/openai-image-model.ts +62 -83
  31. package/src/index.ts +15 -6
  32. package/src/internal/index.ts +7 -6
  33. package/src/openai-config.ts +7 -7
  34. package/src/openai-language-model-capabilities.ts +5 -4
  35. package/src/openai-provider.ts +80 -9
  36. package/src/openai-stream-error.ts +181 -0
  37. package/src/openai-tools.ts +12 -1
  38. package/src/realtime/index.ts +2 -0
  39. package/src/realtime/openai-realtime-event-mapper.ts +436 -0
  40. package/src/realtime/openai-realtime-model-options.ts +3 -0
  41. package/src/realtime/openai-realtime-model.ts +111 -0
  42. package/src/responses/convert-openai-responses-usage.ts +1 -1
  43. package/src/responses/convert-to-openai-responses-input.ts +345 -90
  44. package/src/responses/map-openai-responses-finish-reason.ts +1 -1
  45. package/src/responses/openai-responses-api.ts +186 -17
  46. package/src/responses/{openai-responses-options.ts → openai-responses-language-model-options.ts} +55 -1
  47. package/src/responses/openai-responses-language-model.ts +330 -52
  48. package/src/responses/openai-responses-prepare-tools.ts +129 -18
  49. package/src/responses/openai-responses-provider-metadata.ts +12 -2
  50. package/src/skills/openai-skills-api.ts +31 -0
  51. package/src/skills/openai-skills.ts +83 -0
  52. package/src/speech/{openai-speech-options.ts → openai-speech-model-options.ts} +5 -1
  53. package/src/speech/openai-speech-model.ts +23 -7
  54. package/src/tool/apply-patch.ts +33 -32
  55. package/src/tool/code-interpreter.ts +40 -41
  56. package/src/tool/custom.ts +2 -8
  57. package/src/tool/file-search.ts +3 -3
  58. package/src/tool/image-generation.ts +2 -2
  59. package/src/tool/local-shell.ts +2 -2
  60. package/src/tool/mcp.ts +3 -3
  61. package/src/tool/shell.ts +9 -4
  62. package/src/tool/tool-search.ts +98 -0
  63. package/src/tool/web-search-preview.ts +2 -2
  64. package/src/tool/web-search.ts +10 -2
  65. package/src/transcription/{openai-transcription-options.ts → openai-transcription-model-options.ts} +5 -1
  66. package/src/transcription/openai-transcription-model.ts +35 -13
  67. package/dist/index.d.mts +0 -1107
  68. package/dist/index.mjs +0 -6509
  69. package/dist/index.mjs.map +0 -1
  70. package/dist/internal/index.d.mts +0 -1137
  71. package/dist/internal/index.mjs +0 -6322
  72. package/dist/internal/index.mjs.map +0 -1
  73. package/src/image/openai-image-options.ts +0 -31
@@ -1,33 +1,40 @@
1
- import {
1
+ import type {
2
2
  EmbeddingModelV4,
3
+ FilesV4,
3
4
  ImageModelV4,
4
5
  LanguageModelV4,
5
6
  ProviderV4,
7
+ Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
8
+ Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
6
9
  SpeechModelV4,
10
+ SkillsV4,
7
11
  TranscriptionModelV4,
8
12
  } from '@ai-sdk/provider';
9
13
  import {
10
- FetchFunction,
11
14
  loadApiKey,
12
15
  loadOptionalSetting,
13
16
  withoutTrailingSlash,
14
17
  withUserAgentSuffix,
18
+ type FetchFunction,
15
19
  } from '@ai-sdk/provider-utils';
16
20
  import { OpenAIChatLanguageModel } from './chat/openai-chat-language-model';
17
- import { OpenAIChatModelId } from './chat/openai-chat-options';
21
+ import type { OpenAIChatModelId } from './chat/openai-chat-language-model-options';
18
22
  import { OpenAICompletionLanguageModel } from './completion/openai-completion-language-model';
19
- import { OpenAICompletionModelId } from './completion/openai-completion-options';
23
+ import type { OpenAICompletionModelId } from './completion/openai-completion-language-model-options';
20
24
  import { OpenAIEmbeddingModel } from './embedding/openai-embedding-model';
21
- import { OpenAIEmbeddingModelId } from './embedding/openai-embedding-options';
25
+ import { OpenAIFiles } from './files/openai-files';
26
+ import type { OpenAIEmbeddingModelId } from './embedding/openai-embedding-model-options';
22
27
  import { OpenAIImageModel } from './image/openai-image-model';
23
- import { OpenAIImageModelId } from './image/openai-image-options';
28
+ import type { OpenAIImageModelId } from './image/openai-image-model-options';
24
29
  import { openaiTools } from './openai-tools';
30
+ import { OpenAIRealtimeModel } from './realtime/openai-realtime-model';
25
31
  import { OpenAIResponsesLanguageModel } from './responses/openai-responses-language-model';
26
- import { OpenAIResponsesModelId } from './responses/openai-responses-options';
32
+ import type { OpenAIResponsesModelId } from './responses/openai-responses-language-model-options';
27
33
  import { OpenAISpeechModel } from './speech/openai-speech-model';
28
- import { OpenAISpeechModelId } from './speech/openai-speech-options';
34
+ import type { OpenAISpeechModelId } from './speech/openai-speech-model-options';
29
35
  import { OpenAITranscriptionModel } from './transcription/openai-transcription-model';
30
- import { OpenAITranscriptionModelId } from './transcription/openai-transcription-options';
36
+ import type { OpenAITranscriptionModelId } from './transcription/openai-transcription-model-options';
37
+ import { OpenAISkills } from './skills/openai-skills';
31
38
  import { VERSION } from './version';
32
39
 
33
40
  export interface OpenAIProvider extends ProviderV4 {
@@ -93,6 +100,22 @@ export interface OpenAIProvider extends ProviderV4 {
93
100
  */
94
101
  speech(modelId: OpenAISpeechModelId): SpeechModelV4;
95
102
 
103
+ /**
104
+ * Creates an experimental realtime model for bidirectional audio/text
105
+ * communication over WebSocket.
106
+ */
107
+ experimental_realtime: RealtimeFactoryV4;
108
+
109
+ /**
110
+ * Returns a FilesV4 interface for uploading files to OpenAI.
111
+ */
112
+ files(): FilesV4;
113
+
114
+ /**
115
+ * Returns a SkillsV4 interface for uploading skills to OpenAI.
116
+ */
117
+ skills(): SkillsV4;
118
+
96
119
  /**
97
120
  * OpenAI-specific tools.
98
121
  */
@@ -216,6 +239,22 @@ export function createOpenAI(
216
239
  fetch: options.fetch,
217
240
  });
218
241
 
242
+ const createFiles = () =>
243
+ new OpenAIFiles({
244
+ provider: `${providerName}.files`,
245
+ baseURL,
246
+ headers: getHeaders,
247
+ fetch: options.fetch,
248
+ });
249
+
250
+ const createSkills = () =>
251
+ new OpenAISkills({
252
+ provider: `${providerName}.skills`,
253
+ url: ({ path }) => `${baseURL}${path}`,
254
+ headers: getHeaders,
255
+ fetch: options.fetch,
256
+ });
257
+
219
258
  const createLanguageModel = (modelId: OpenAIResponsesModelId) => {
220
259
  if (new.target) {
221
260
  throw new Error(
@@ -232,10 +271,38 @@ export function createOpenAI(
232
271
  url: ({ path }) => `${baseURL}${path}`,
233
272
  headers: getHeaders,
234
273
  fetch: options.fetch,
274
+ // Soft-deprecated. TODO: remove in v8
235
275
  fileIdPrefixes: ['file-'],
236
276
  });
237
277
  };
238
278
 
279
+ const createRealtimeModel = (modelId: string) =>
280
+ new OpenAIRealtimeModel(modelId, {
281
+ provider: `${providerName}.realtime`,
282
+ baseURL,
283
+ headers: getHeaders,
284
+ fetch: options.fetch,
285
+ });
286
+
287
+ const experimentalRealtimeFactory = Object.assign(
288
+ (modelId: string) => createRealtimeModel(modelId),
289
+ {
290
+ getToken: async (tokenOptions: RealtimeFactoryV4GetTokenOptions) => {
291
+ const model = createRealtimeModel(tokenOptions.model);
292
+ const secret = await model.doCreateClientSecret({
293
+ sessionConfig: tokenOptions.sessionConfig,
294
+ expiresAfterSeconds: tokenOptions.expiresAfterSeconds,
295
+ });
296
+
297
+ return {
298
+ token: secret.token,
299
+ url: secret.url,
300
+ expiresAt: secret.expiresAt,
301
+ };
302
+ },
303
+ },
304
+ ) as RealtimeFactoryV4;
305
+
239
306
  const provider = function (modelId: OpenAIResponsesModelId) {
240
307
  return createLanguageModel(modelId);
241
308
  };
@@ -258,6 +325,10 @@ export function createOpenAI(
258
325
 
259
326
  provider.speech = createSpeechModel;
260
327
  provider.speechModel = createSpeechModel;
328
+ provider.files = createFiles;
329
+ provider.skills = createSkills;
330
+
331
+ provider.experimental_realtime = experimentalRealtimeFactory;
261
332
 
262
333
  provider.tools = openaiTools;
263
334
 
@@ -0,0 +1,181 @@
1
+ import { APICallError } from '@ai-sdk/provider';
2
+ import type { ParseResult } from '@ai-sdk/provider-utils';
3
+
4
+ type StreamError = {
5
+ message: string;
6
+ code?: string | number | null;
7
+ type?: string | null;
8
+ frame: unknown;
9
+ };
10
+
11
+ export async function throwIfOpenAIStreamErrorBeforeOutput<T>({
12
+ stream,
13
+ getError,
14
+ isOutputChunk,
15
+ url,
16
+ requestBodyValues,
17
+ responseHeaders,
18
+ }: {
19
+ stream: ReadableStream<ParseResult<T>>;
20
+ getError: (chunk: T) => unknown | undefined;
21
+ isOutputChunk: (chunk: T) => boolean;
22
+ url: string;
23
+ requestBodyValues: unknown;
24
+ responseHeaders?: Record<string, string>;
25
+ }): Promise<ReadableStream<ParseResult<T>>> {
26
+ const [streamForEarlyError, streamForConsumer] = stream.tee();
27
+ const reader = streamForEarlyError.getReader();
28
+
29
+ try {
30
+ while (true) {
31
+ const result = await reader.read();
32
+
33
+ if (result.done) {
34
+ return streamForConsumer;
35
+ }
36
+
37
+ const chunk = result.value;
38
+
39
+ if (!chunk.success) {
40
+ return streamForConsumer;
41
+ }
42
+
43
+ const errorFrame = getError(chunk.value);
44
+
45
+ if (errorFrame != null) {
46
+ streamForConsumer.cancel().catch(() => {});
47
+ throw createOpenAIStreamError({
48
+ frame: errorFrame,
49
+ url,
50
+ requestBodyValues,
51
+ responseHeaders,
52
+ });
53
+ }
54
+
55
+ if (isOutputChunk(chunk.value)) {
56
+ return streamForConsumer;
57
+ }
58
+ }
59
+ } finally {
60
+ reader.cancel().catch(() => {});
61
+ reader.releaseLock();
62
+ }
63
+ }
64
+
65
+ function createOpenAIStreamError({
66
+ frame,
67
+ url,
68
+ requestBodyValues,
69
+ responseHeaders,
70
+ }: {
71
+ frame: unknown;
72
+ url: string;
73
+ requestBodyValues: unknown;
74
+ responseHeaders?: Record<string, string>;
75
+ }): APICallError {
76
+ const streamError = parseStreamError(frame);
77
+ return new APICallError({
78
+ message:
79
+ streamError?.message ??
80
+ 'OpenAI stream failed before any output was generated',
81
+ url,
82
+ requestBodyValues,
83
+ statusCode: streamError == null ? 500 : getStatusCode(streamError),
84
+ responseHeaders,
85
+ responseBody: JSON.stringify(frame),
86
+ data: frame,
87
+ });
88
+ }
89
+
90
+ function parseStreamError(frame: unknown): StreamError | undefined {
91
+ const value = asRecord(frame);
92
+
93
+ if (value == null) {
94
+ return undefined;
95
+ }
96
+
97
+ if (value.type === 'response.failed') {
98
+ const response = asRecord(value.response);
99
+ const responseError = asRecord(response?.error);
100
+
101
+ return typeof responseError?.message === 'string'
102
+ ? {
103
+ message: responseError.message,
104
+ code: getStringOrNumber(responseError.code),
105
+ type: 'response.failed',
106
+ frame,
107
+ }
108
+ : undefined;
109
+ }
110
+
111
+ const error = asRecord(value.error) ?? value;
112
+
113
+ return typeof error.message === 'string' &&
114
+ (asRecord(value.error) != null ||
115
+ typeof error.type === 'string' ||
116
+ 'code' in error ||
117
+ 'param' in error)
118
+ ? {
119
+ message: error.message,
120
+ code: getStringOrNumber(error.code),
121
+ type: typeof error.type === 'string' ? error.type : undefined,
122
+ frame,
123
+ }
124
+ : undefined;
125
+ }
126
+
127
+ function getStatusCode(error: StreamError): number {
128
+ if (typeof error.code === 'number' && isHttpErrorStatusCode(error.code)) {
129
+ return error.code;
130
+ }
131
+
132
+ if (typeof error.code === 'string' && /^\d{3}$/.test(error.code)) {
133
+ const numericCode = Number(error.code);
134
+ if (isHttpErrorStatusCode(numericCode)) {
135
+ return numericCode;
136
+ }
137
+ }
138
+
139
+ const discriminator = [error.code, error.type]
140
+ .filter(value => typeof value === 'string' || typeof value === 'number')
141
+ .join(' ')
142
+ .toLowerCase();
143
+
144
+ if (
145
+ ['insufficient_quota', 'rate_limit'].some(term =>
146
+ discriminator.includes(term),
147
+ )
148
+ ) {
149
+ return 429;
150
+ }
151
+ if (discriminator.includes('authentication')) return 401;
152
+ if (discriminator.includes('permission')) return 403;
153
+ if (discriminator.includes('not_found')) return 404;
154
+ if (
155
+ ['invalid', 'bad_request', 'context_length'].some(term =>
156
+ discriminator.includes(term),
157
+ )
158
+ ) {
159
+ return 400;
160
+ }
161
+ if (discriminator.includes('overload')) return 503;
162
+ if (discriminator.includes('timeout')) return 504;
163
+
164
+ return 500;
165
+ }
166
+
167
+ function asRecord(value: unknown): Record<string, unknown> | undefined {
168
+ return typeof value === 'object' && value != null
169
+ ? (value as Record<string, unknown>)
170
+ : undefined;
171
+ }
172
+
173
+ function getStringOrNumber(value: unknown): string | number | undefined {
174
+ return typeof value === 'string' || typeof value === 'number'
175
+ ? value
176
+ : undefined;
177
+ }
178
+
179
+ function isHttpErrorStatusCode(value: number): boolean {
180
+ return Number.isInteger(value) && value >= 400 && value <= 599;
181
+ }
@@ -5,6 +5,7 @@ import { fileSearch } from './tool/file-search';
5
5
  import { imageGeneration } from './tool/image-generation';
6
6
  import { localShell } from './tool/local-shell';
7
7
  import { shell } from './tool/shell';
8
+ import { toolSearch } from './tool/tool-search';
8
9
  import { webSearch } from './tool/web-search';
9
10
  import { webSearchPreview } from './tool/web-search-preview';
10
11
  import { mcp } from './tool/mcp';
@@ -24,7 +25,6 @@ export const openaiTools = {
24
25
  * Lark syntax). The model returns a `custom_tool_call` output item whose
25
26
  * `input` field is a string matching the specified grammar.
26
27
  *
27
- * @param name - The name of the custom tool.
28
28
  * @param description - An optional description of the tool.
29
29
  * @param format - The output format constraint (grammar type, syntax, and definition).
30
30
  */
@@ -123,4 +123,15 @@ export const openaiTools = {
123
123
  * @param serverUrl - URL for the MCP server.
124
124
  */
125
125
  mcp,
126
+
127
+ /**
128
+ * Tool search allows the model to dynamically search for and load deferred
129
+ * tools into the model's context as needed. This helps reduce overall token
130
+ * usage, cost, and latency by only loading tools when the model needs them.
131
+ *
132
+ * To use tool search, mark functions or namespaces with `defer_loading: true`
133
+ * in the tools array. The model will use tool search to load these tools
134
+ * when it determines they are needed.
135
+ */
136
+ toolSearch,
126
137
  };
@@ -0,0 +1,2 @@
1
+ export { OpenAIRealtimeModel as Experimental_OpenAIRealtimeModel } from './openai-realtime-model';
2
+ export type { OpenAIRealtimeModelConfig as Experimental_OpenAIRealtimeModelConfig } from './openai-realtime-model';