@ai-sdk/openai 4.0.0-beta.20 → 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 +9 -0
- package/dist/index.d.mts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +1299 -1162
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1270 -1121
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.d.mts +5 -5
- package/dist/internal/index.d.ts +5 -5
- package/dist/internal/index.js +32 -5
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +39 -6
- package/dist/internal/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/chat/convert-to-openai-chat-messages.ts +21 -10
- 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/index.ts +1 -0
- package/src/openai-config.ts +5 -5
- package/src/openai-provider.ts +18 -0
- package/src/responses/convert-to-openai-responses-input.ts +29 -2
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",
|
|
@@ -4,7 +4,11 @@ import {
|
|
|
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,
|
|
@@ -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({
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { lazySchema, zodSchema } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
export const openaiFilesResponseSchema = lazySchema(() =>
|
|
5
|
+
zodSchema(
|
|
6
|
+
z.object({
|
|
7
|
+
id: z.string(),
|
|
8
|
+
object: z.string().nullish(),
|
|
9
|
+
bytes: z.number().nullish(),
|
|
10
|
+
created_at: z.number().nullish(),
|
|
11
|
+
filename: z.string().nullish(),
|
|
12
|
+
purpose: z.string().nullish(),
|
|
13
|
+
status: z.string().nullish(),
|
|
14
|
+
expires_at: z.number().nullish(),
|
|
15
|
+
}),
|
|
16
|
+
),
|
|
17
|
+
);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { InferSchema, lazySchema, zodSchema } from '@ai-sdk/provider-utils';
|
|
2
|
+
import { z } from 'zod/v4';
|
|
3
|
+
|
|
4
|
+
export const openaiFilesOptionsSchema = lazySchema(() =>
|
|
5
|
+
zodSchema(
|
|
6
|
+
z.object({
|
|
7
|
+
/*
|
|
8
|
+
* Required by the OpenAI API, but optional here because
|
|
9
|
+
* the SDK defaults to "assistants" — by far the most common
|
|
10
|
+
* purpose when uploading files in this context.
|
|
11
|
+
*/
|
|
12
|
+
purpose: z.string().optional(),
|
|
13
|
+
expiresAfter: z.number().optional(),
|
|
14
|
+
}),
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export type OpenAIFilesOptions = InferSchema<typeof openaiFilesOptionsSchema>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FilesV4,
|
|
3
|
+
FilesV4UploadFileCallOptions,
|
|
4
|
+
FilesV4UploadFileResult,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
6
|
+
import {
|
|
7
|
+
combineHeaders,
|
|
8
|
+
convertBase64ToUint8Array,
|
|
9
|
+
createJsonResponseHandler,
|
|
10
|
+
FetchFunction,
|
|
11
|
+
parseProviderOptions,
|
|
12
|
+
postFormDataToApi,
|
|
13
|
+
} from '@ai-sdk/provider-utils';
|
|
14
|
+
import { openaiFailedResponseHandler } from '../openai-error';
|
|
15
|
+
import { openaiFilesResponseSchema } from './openai-files-api';
|
|
16
|
+
import {
|
|
17
|
+
openaiFilesOptionsSchema,
|
|
18
|
+
OpenAIFilesOptions,
|
|
19
|
+
} from './openai-files-options';
|
|
20
|
+
|
|
21
|
+
interface OpenAIFilesConfig {
|
|
22
|
+
provider: string;
|
|
23
|
+
baseURL: string;
|
|
24
|
+
headers: () => Record<string, string | undefined>;
|
|
25
|
+
fetch?: FetchFunction;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class OpenAIFiles implements FilesV4 {
|
|
29
|
+
readonly specificationVersion = 'v4';
|
|
30
|
+
|
|
31
|
+
get provider(): string {
|
|
32
|
+
return this.config.provider;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
constructor(private readonly config: OpenAIFilesConfig) {}
|
|
36
|
+
|
|
37
|
+
async uploadFile({
|
|
38
|
+
data,
|
|
39
|
+
mediaType,
|
|
40
|
+
filename,
|
|
41
|
+
providerOptions,
|
|
42
|
+
}: FilesV4UploadFileCallOptions): Promise<FilesV4UploadFileResult> {
|
|
43
|
+
const openaiOptions = (await parseProviderOptions({
|
|
44
|
+
provider: 'openai',
|
|
45
|
+
providerOptions,
|
|
46
|
+
schema: openaiFilesOptionsSchema,
|
|
47
|
+
})) as OpenAIFilesOptions | undefined;
|
|
48
|
+
|
|
49
|
+
const fileBytes =
|
|
50
|
+
data instanceof Uint8Array ? data : convertBase64ToUint8Array(data);
|
|
51
|
+
|
|
52
|
+
const blob = new Blob([fileBytes], {
|
|
53
|
+
type: mediaType,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const formData = new FormData();
|
|
57
|
+
if (filename != null) {
|
|
58
|
+
formData.append('file', blob, filename);
|
|
59
|
+
} else {
|
|
60
|
+
formData.append('file', blob);
|
|
61
|
+
}
|
|
62
|
+
formData.append('purpose', openaiOptions?.purpose ?? 'assistants');
|
|
63
|
+
|
|
64
|
+
if (openaiOptions?.expiresAfter != null) {
|
|
65
|
+
formData.append('expires_after', String(openaiOptions.expiresAfter));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { value: response } = await postFormDataToApi({
|
|
69
|
+
url: `${this.config.baseURL}/files`,
|
|
70
|
+
headers: combineHeaders(this.config.headers()),
|
|
71
|
+
formData,
|
|
72
|
+
failedResponseHandler: openaiFailedResponseHandler,
|
|
73
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
74
|
+
openaiFilesResponseSchema,
|
|
75
|
+
),
|
|
76
|
+
fetch: this.config.fetch,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
warnings: [],
|
|
81
|
+
providerReference: { openai: response.id },
|
|
82
|
+
...((response.filename ?? filename)
|
|
83
|
+
? { filename: response.filename ?? filename }
|
|
84
|
+
: {}),
|
|
85
|
+
...(mediaType != null ? { mediaType } : {}),
|
|
86
|
+
providerMetadata: {
|
|
87
|
+
openai: {
|
|
88
|
+
...(response.filename != null ? { filename: response.filename } : {}),
|
|
89
|
+
...(response.purpose != null ? { purpose: response.purpose } : {}),
|
|
90
|
+
...(response.bytes != null ? { bytes: response.bytes } : {}),
|
|
91
|
+
...(response.created_at != null
|
|
92
|
+
? { createdAt: response.created_at }
|
|
93
|
+
: {}),
|
|
94
|
+
...(response.status != null ? { status: response.status } : {}),
|
|
95
|
+
...(response.expires_at != null
|
|
96
|
+
? { expiresAt: response.expires_at }
|
|
97
|
+
: {}),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type { OpenAILanguageModelCompletionOptions } from './completion/openai-c
|
|
|
14
14
|
export type { OpenAIEmbeddingModelOptions } from './embedding/openai-embedding-options';
|
|
15
15
|
export type { OpenAISpeechModelOptions } from './speech/openai-speech-options';
|
|
16
16
|
export type { OpenAITranscriptionModelOptions } from './transcription/openai-transcription-options';
|
|
17
|
+
export type { OpenAIFilesOptions } from './files/openai-files-options';
|
|
17
18
|
export type {
|
|
18
19
|
OpenaiResponsesCompactionProviderMetadata,
|
|
19
20
|
OpenaiResponsesProviderMetadata,
|
package/src/openai-config.ts
CHANGED
|
@@ -7,12 +7,12 @@ export type OpenAIConfig = {
|
|
|
7
7
|
fetch?: FetchFunction;
|
|
8
8
|
generateId?: () => string;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* This is soft-deprecated. Use provider references (e.g. `{ openai: 'file-abc123' }`)
|
|
11
|
+
* in file part data instead. File ID prefixes used to identify file IDs
|
|
12
|
+
* in Responses API. When undefined, all string file data is treated as
|
|
13
|
+
* base64 content.
|
|
12
14
|
*
|
|
13
|
-
*
|
|
14
|
-
* - OpenAI: ['file-'] for IDs like 'file-abc123'
|
|
15
|
-
* - Azure OpenAI: ['assistant-'] for IDs like 'assistant-abc123'
|
|
15
|
+
* TODO: remove in v8
|
|
16
16
|
*/
|
|
17
17
|
fileIdPrefixes?: readonly string[];
|
|
18
18
|
};
|
package/src/openai-provider.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EmbeddingModelV4,
|
|
3
|
+
FilesV4,
|
|
3
4
|
ImageModelV4,
|
|
4
5
|
LanguageModelV4,
|
|
5
6
|
ProviderV4,
|
|
@@ -18,6 +19,7 @@ import { OpenAIChatModelId } from './chat/openai-chat-options';
|
|
|
18
19
|
import { OpenAICompletionLanguageModel } from './completion/openai-completion-language-model';
|
|
19
20
|
import { OpenAICompletionModelId } from './completion/openai-completion-options';
|
|
20
21
|
import { OpenAIEmbeddingModel } from './embedding/openai-embedding-model';
|
|
22
|
+
import { OpenAIFiles } from './files/openai-files';
|
|
21
23
|
import { OpenAIEmbeddingModelId } from './embedding/openai-embedding-options';
|
|
22
24
|
import { OpenAIImageModel } from './image/openai-image-model';
|
|
23
25
|
import { OpenAIImageModelId } from './image/openai-image-options';
|
|
@@ -93,6 +95,11 @@ export interface OpenAIProvider extends ProviderV4 {
|
|
|
93
95
|
*/
|
|
94
96
|
speech(modelId: OpenAISpeechModelId): SpeechModelV4;
|
|
95
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Returns a FilesV4 interface for uploading files to OpenAI.
|
|
100
|
+
*/
|
|
101
|
+
files(): FilesV4;
|
|
102
|
+
|
|
96
103
|
/**
|
|
97
104
|
* OpenAI-specific tools.
|
|
98
105
|
*/
|
|
@@ -216,6 +223,14 @@ export function createOpenAI(
|
|
|
216
223
|
fetch: options.fetch,
|
|
217
224
|
});
|
|
218
225
|
|
|
226
|
+
const createFiles = () =>
|
|
227
|
+
new OpenAIFiles({
|
|
228
|
+
provider: `${providerName}.files`,
|
|
229
|
+
baseURL,
|
|
230
|
+
headers: getHeaders,
|
|
231
|
+
fetch: options.fetch,
|
|
232
|
+
});
|
|
233
|
+
|
|
219
234
|
const createLanguageModel = (modelId: OpenAIResponsesModelId) => {
|
|
220
235
|
if (new.target) {
|
|
221
236
|
throw new Error(
|
|
@@ -232,6 +247,7 @@ export function createOpenAI(
|
|
|
232
247
|
url: ({ path }) => `${baseURL}${path}`,
|
|
233
248
|
headers: getHeaders,
|
|
234
249
|
fetch: options.fetch,
|
|
250
|
+
// Soft-deprecated. TODO: remove in v8
|
|
235
251
|
fileIdPrefixes: ['file-'],
|
|
236
252
|
});
|
|
237
253
|
};
|
|
@@ -259,6 +275,8 @@ export function createOpenAI(
|
|
|
259
275
|
provider.speech = createSpeechModel;
|
|
260
276
|
provider.speechModel = createSpeechModel;
|
|
261
277
|
|
|
278
|
+
provider.files = createFiles;
|
|
279
|
+
|
|
262
280
|
provider.tools = openaiTools;
|
|
263
281
|
|
|
264
282
|
return provider as OpenAIProvider;
|
|
@@ -7,8 +7,10 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
convertToBase64,
|
|
9
9
|
isNonNullable,
|
|
10
|
+
isProviderReference,
|
|
10
11
|
parseJSON,
|
|
11
12
|
parseProviderOptions,
|
|
13
|
+
resolveProviderReference,
|
|
12
14
|
ToolNameMapping,
|
|
13
15
|
validateTypes,
|
|
14
16
|
} from '@ai-sdk/provider-utils';
|
|
@@ -35,8 +37,10 @@ import {
|
|
|
35
37
|
} from '../tool/tool-search';
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
+
* This is soft-deprecated. Use provider references instead. Kept for backward compatibility
|
|
41
|
+
* with the `fileIdPrefixes` option.
|
|
42
|
+
*
|
|
43
|
+
* TODO: remove in v8
|
|
40
44
|
*/
|
|
41
45
|
function isFileId(data: string, prefixes?: readonly string[]): boolean {
|
|
42
46
|
if (!prefixes) return false;
|
|
@@ -60,6 +64,7 @@ export async function convertToOpenAIResponsesInput({
|
|
|
60
64
|
toolNameMapping: ToolNameMapping;
|
|
61
65
|
systemMessageMode: 'system' | 'developer' | 'remove';
|
|
62
66
|
providerOptionsName: string;
|
|
67
|
+
/** @deprecated Use provider references instead. */
|
|
63
68
|
fileIdPrefixes?: readonly string[];
|
|
64
69
|
store: boolean;
|
|
65
70
|
hasConversation?: boolean; // when true, skip assistant messages that already have item IDs
|
|
@@ -113,6 +118,28 @@ export async function convertToOpenAIResponsesInput({
|
|
|
113
118
|
return { type: 'input_text', text: part.text };
|
|
114
119
|
}
|
|
115
120
|
case 'file': {
|
|
121
|
+
if (isProviderReference(part.data)) {
|
|
122
|
+
const fileId = resolveProviderReference({
|
|
123
|
+
reference: part.data,
|
|
124
|
+
provider: providerOptionsName,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (part.mediaType.startsWith('image/')) {
|
|
128
|
+
return {
|
|
129
|
+
type: 'input_image',
|
|
130
|
+
file_id: fileId,
|
|
131
|
+
detail:
|
|
132
|
+
part.providerOptions?.[providerOptionsName]
|
|
133
|
+
?.imageDetail,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
type: 'input_file',
|
|
139
|
+
file_id: fileId,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
116
143
|
if (part.mediaType.startsWith('image/')) {
|
|
117
144
|
const mediaType =
|
|
118
145
|
part.mediaType === 'image/*'
|