@ai-sdk/openai 4.0.65 → 4.0.67
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 +34 -0
- package/README.md +26 -1
- package/dist/index.d.ts +133 -28
- package/dist/index.js +787 -130
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +7 -0
- package/dist/internal/index.js +164 -24
- package/dist/internal/index.js.map +1 -1
- package/docs/03-openai.mdx +102 -7
- package/package.json +3 -3
- package/src/chat/openai-chat-language-model.ts +11 -2
- package/src/chat/openai-chat-prepare-tools.ts +9 -2
- package/src/index.ts +10 -0
- package/src/live/openai-live-event-mapper.ts +286 -0
- package/src/live/openai-live-session-config.ts +145 -0
- package/src/live/openai-realtime-model-live-options.ts +65 -0
- package/src/live/openai-realtime-model-live.ts +107 -0
- package/src/normalize-openai-json-schema.ts +161 -0
- package/src/openai-batch.ts +16 -0
- package/src/openai-config.ts +7 -0
- package/src/openai-provider.ts +11 -32
- package/src/realtime/openai-realtime-event-mapper.ts +8 -1
- package/src/realtime/openai-realtime-factory.ts +88 -0
- package/src/responses/openai-responses-language-model-options.ts +9 -0
- package/src/responses/openai-responses-language-model.ts +16 -3
- package/src/responses/openai-responses-prepare-tools.ts +16 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
export type OpenAIRealtimeModelLiveId = 'gpt-live-1' | (string & {});
|
|
4
|
+
|
|
5
|
+
const serverEventSelectorSchema = z
|
|
6
|
+
.strictObject({
|
|
7
|
+
type: z.string(),
|
|
8
|
+
responseEvent: z.string().optional(),
|
|
9
|
+
})
|
|
10
|
+
.refine(
|
|
11
|
+
selector =>
|
|
12
|
+
(selector.type === 'response.event') ===
|
|
13
|
+
(selector.responseEvent !== undefined),
|
|
14
|
+
'responseEvent is required for response.event and forbidden for other event types.',
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export const openaiRealtimeModelLiveOptionsSchema = z.strictObject({
|
|
18
|
+
client: z
|
|
19
|
+
.strictObject({
|
|
20
|
+
dataChannel: z.strictObject({
|
|
21
|
+
allowedClientEvents: z
|
|
22
|
+
.union([z.literal('all'), z.array(z.string())])
|
|
23
|
+
.optional(),
|
|
24
|
+
allowedServerEvents: z
|
|
25
|
+
.union([z.literal('all'), z.array(serverEventSelectorSchema)])
|
|
26
|
+
.optional(),
|
|
27
|
+
}),
|
|
28
|
+
})
|
|
29
|
+
.optional(),
|
|
30
|
+
delegation: z
|
|
31
|
+
.strictObject({ type: z.literal('client') })
|
|
32
|
+
.nullable()
|
|
33
|
+
.optional(),
|
|
34
|
+
input: z
|
|
35
|
+
.array(
|
|
36
|
+
z.discriminatedUnion('role', [
|
|
37
|
+
z.strictObject({
|
|
38
|
+
type: z.literal('message'),
|
|
39
|
+
role: z.enum(['developer', 'user']),
|
|
40
|
+
content: z.tuple([
|
|
41
|
+
z.strictObject({ type: z.literal('input_text'), text: z.string() }),
|
|
42
|
+
]),
|
|
43
|
+
}),
|
|
44
|
+
z.strictObject({
|
|
45
|
+
type: z.literal('message'),
|
|
46
|
+
role: z.literal('assistant'),
|
|
47
|
+
content: z.tuple([
|
|
48
|
+
z.strictObject({
|
|
49
|
+
type: z.enum(['text', 'output_text']),
|
|
50
|
+
text: z.string(),
|
|
51
|
+
}),
|
|
52
|
+
]),
|
|
53
|
+
}),
|
|
54
|
+
]),
|
|
55
|
+
)
|
|
56
|
+
.max(128)
|
|
57
|
+
.optional(),
|
|
58
|
+
store: z.boolean().optional(),
|
|
59
|
+
voice: z.strictObject({ id: z.string().min(1) }).optional(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** Experimental Live options under sessionConfig.providerOptions.openai. */
|
|
63
|
+
export type OpenAIRealtimeModelLiveOptions = z.infer<
|
|
64
|
+
typeof openaiRealtimeModelLiveOptionsSchema
|
|
65
|
+
>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Experimental_RealtimeModelV4 as RealtimeModelV4,
|
|
3
|
+
type Experimental_RealtimeModelV4ClientEvent as RealtimeModelV4ClientEvent,
|
|
4
|
+
type Experimental_RealtimeModelV4SessionConfig as RealtimeModelV4SessionConfig,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
6
|
+
import {
|
|
7
|
+
createJsonResponseHandler,
|
|
8
|
+
postJsonToApi,
|
|
9
|
+
} from '@ai-sdk/provider-utils';
|
|
10
|
+
import { z } from 'zod/v4';
|
|
11
|
+
import { openaiFailedResponseHandler } from '../openai-error';
|
|
12
|
+
import {
|
|
13
|
+
createOpenAILiveServerEventParser,
|
|
14
|
+
parseOpenAILiveServerEvent,
|
|
15
|
+
serializeOpenAILiveClientEvent,
|
|
16
|
+
} from './openai-live-event-mapper';
|
|
17
|
+
import type { OpenAIRealtimeModelLiveId } from './openai-realtime-model-live-options';
|
|
18
|
+
import { buildOpenAILiveSessionConfig } from './openai-live-session-config';
|
|
19
|
+
import type { OpenAIRealtimeModelConfig } from '../realtime/openai-realtime-model';
|
|
20
|
+
|
|
21
|
+
export type OpenAIRealtimeModelLiveConfig = OpenAIRealtimeModelConfig;
|
|
22
|
+
|
|
23
|
+
const webRTCSessionSchema = z.object({
|
|
24
|
+
session: z.object({ id: z.string().min(1) }),
|
|
25
|
+
transport: z.object({ type: z.literal('webrtc'), sdp: z.string().min(1) }),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export class OpenAIRealtimeModelLive implements RealtimeModelV4 {
|
|
29
|
+
readonly specificationVersion = 'v4' as const;
|
|
30
|
+
readonly capabilities = {
|
|
31
|
+
conversation: 'continuous',
|
|
32
|
+
transports: ['websocket', 'webrtc'],
|
|
33
|
+
connections: ['server-websocket', 'webrtc'],
|
|
34
|
+
startup: 'session-start',
|
|
35
|
+
finalization: 'session-close',
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
readonly modelId: OpenAIRealtimeModelLiveId,
|
|
40
|
+
private readonly config: OpenAIRealtimeModelLiveConfig,
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
get provider(): string {
|
|
44
|
+
return this.config.provider;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getWebRTCConfig(): { dataChannelLabel: string } {
|
|
48
|
+
return { dataChannelLabel: 'oai-events' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getServerWebSocketConfig(): { url: string; headers: Record<string, string> } {
|
|
52
|
+
const url = new URL(`${this.config.baseURL}/live/sessions`);
|
|
53
|
+
url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:';
|
|
54
|
+
const headers: Record<string, string> = {};
|
|
55
|
+
for (const [key, value] of Object.entries(this.config.headers())) {
|
|
56
|
+
if (value !== undefined) headers[key] = value;
|
|
57
|
+
}
|
|
58
|
+
return { url: url.toString(), headers };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async doCreateWebRTCSession({
|
|
62
|
+
sdp,
|
|
63
|
+
sessionConfig = {},
|
|
64
|
+
abortSignal,
|
|
65
|
+
}: {
|
|
66
|
+
sdp: string;
|
|
67
|
+
sessionConfig?: RealtimeModelV4SessionConfig;
|
|
68
|
+
abortSignal?: AbortSignal;
|
|
69
|
+
}): Promise<{ sessionId: string; sdp: string }> {
|
|
70
|
+
const session = buildOpenAILiveSessionConfig(
|
|
71
|
+
sessionConfig,
|
|
72
|
+
this.modelId,
|
|
73
|
+
'webrtc',
|
|
74
|
+
);
|
|
75
|
+
const { value } = await postJsonToApi({
|
|
76
|
+
url: `${this.config.baseURL}/live/sessions`,
|
|
77
|
+
headers: this.config.headers(),
|
|
78
|
+
body: {
|
|
79
|
+
session,
|
|
80
|
+
transport: { type: 'webrtc', sdp: z.string().min(1).parse(sdp) },
|
|
81
|
+
},
|
|
82
|
+
failedResponseHandler: openaiFailedResponseHandler,
|
|
83
|
+
successfulResponseHandler: createJsonResponseHandler(webRTCSessionSchema),
|
|
84
|
+
abortSignal,
|
|
85
|
+
fetch: this.config.fetch,
|
|
86
|
+
});
|
|
87
|
+
return { sessionId: value.session.id, sdp: value.transport.sdp };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
parseServerEvent(raw: unknown) {
|
|
91
|
+
return parseOpenAILiveServerEvent(raw);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
createServerEventParser() {
|
|
95
|
+
return createOpenAILiveServerEventParser();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
serializeClientEvent(event: RealtimeModelV4ClientEvent): unknown {
|
|
99
|
+
return serializeOpenAILiveClientEvent(event, this.modelId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
buildSessionConfig(
|
|
103
|
+
config: RealtimeModelV4SessionConfig,
|
|
104
|
+
): Record<string, unknown> {
|
|
105
|
+
return buildOpenAILiveSessionConfig(config, this.modelId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UnsupportedFunctionalityError,
|
|
3
|
+
type JSONSchema7,
|
|
4
|
+
type JSONSchema7Definition,
|
|
5
|
+
type SharedV4Warning,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Normalizes JSON Schema for OpenAI structured outputs.
|
|
10
|
+
*
|
|
11
|
+
* OpenAI does not support the JSON Schema `propertyNames` keyword. Property
|
|
12
|
+
* names in JSON objects are always strings, so string-based constraints can be
|
|
13
|
+
* left to client-side validation after removing the keyword. This
|
|
14
|
+
* compatibility layer does not rewrite non-string property name schemas.
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeOpenAIJsonSchema(schema: JSONSchema7): {
|
|
17
|
+
schema: JSONSchema7;
|
|
18
|
+
warnings: SharedV4Warning[];
|
|
19
|
+
} {
|
|
20
|
+
let removedPropertyNames = false;
|
|
21
|
+
|
|
22
|
+
const normalizedSchema = normalizeSchema(schema);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
schema: normalizedSchema,
|
|
26
|
+
warnings: removedPropertyNames
|
|
27
|
+
? [
|
|
28
|
+
{
|
|
29
|
+
type: 'compatibility',
|
|
30
|
+
feature: 'JSON Schema propertyNames',
|
|
31
|
+
details:
|
|
32
|
+
'OpenAI does not support JSON Schema propertyNames. It was removed before sending the schema, so OpenAI will not enforce property-name constraints.',
|
|
33
|
+
},
|
|
34
|
+
]
|
|
35
|
+
: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function normalizeSchema(schema: JSONSchema7): JSONSchema7 {
|
|
39
|
+
const propertyNames = schema.propertyNames;
|
|
40
|
+
|
|
41
|
+
if (propertyNames != null) {
|
|
42
|
+
if (
|
|
43
|
+
typeof propertyNames === 'boolean' ||
|
|
44
|
+
propertyNames.type !== 'string'
|
|
45
|
+
) {
|
|
46
|
+
throw new UnsupportedFunctionalityError({
|
|
47
|
+
functionality:
|
|
48
|
+
'JSON Schema propertyNames that does not use a string schema',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
removedPropertyNames = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const normalizedSchema = { ...schema };
|
|
56
|
+
delete normalizedSchema.propertyNames;
|
|
57
|
+
|
|
58
|
+
if (normalizedSchema.properties != null) {
|
|
59
|
+
normalizedSchema.properties = normalizeSchemaRecord(
|
|
60
|
+
normalizedSchema.properties,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (normalizedSchema.patternProperties != null) {
|
|
65
|
+
normalizedSchema.patternProperties = normalizeSchemaRecord(
|
|
66
|
+
normalizedSchema.patternProperties,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (normalizedSchema.additionalProperties != null) {
|
|
71
|
+
normalizedSchema.additionalProperties = normalizeDefinition(
|
|
72
|
+
normalizedSchema.additionalProperties,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (normalizedSchema.additionalItems != null) {
|
|
77
|
+
normalizedSchema.additionalItems = normalizeDefinition(
|
|
78
|
+
normalizedSchema.additionalItems,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (normalizedSchema.items != null) {
|
|
83
|
+
normalizedSchema.items = Array.isArray(normalizedSchema.items)
|
|
84
|
+
? normalizedSchema.items.map(normalizeDefinition)
|
|
85
|
+
: normalizeDefinition(normalizedSchema.items);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (normalizedSchema.contains != null) {
|
|
89
|
+
normalizedSchema.contains = normalizeDefinition(
|
|
90
|
+
normalizedSchema.contains,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (normalizedSchema.not != null) {
|
|
95
|
+
normalizedSchema.not = normalizeDefinition(normalizedSchema.not);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (normalizedSchema.allOf != null) {
|
|
99
|
+
normalizedSchema.allOf = normalizedSchema.allOf.map(normalizeDefinition);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (normalizedSchema.anyOf != null) {
|
|
103
|
+
normalizedSchema.anyOf = normalizedSchema.anyOf.map(normalizeDefinition);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (normalizedSchema.oneOf != null) {
|
|
107
|
+
normalizedSchema.oneOf = normalizedSchema.oneOf.map(normalizeDefinition);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (normalizedSchema.definitions != null) {
|
|
111
|
+
normalizedSchema.definitions = normalizeSchemaRecord(
|
|
112
|
+
normalizedSchema.definitions,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (normalizedSchema.$defs != null) {
|
|
117
|
+
normalizedSchema.$defs = normalizeSchemaRecord(normalizedSchema.$defs);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (normalizedSchema.dependencies != null) {
|
|
121
|
+
normalizedSchema.dependencies = Object.fromEntries(
|
|
122
|
+
Object.entries(normalizedSchema.dependencies).map(
|
|
123
|
+
([key, dependency]) => [
|
|
124
|
+
key,
|
|
125
|
+
Array.isArray(dependency)
|
|
126
|
+
? dependency
|
|
127
|
+
: normalizeDefinition(dependency),
|
|
128
|
+
],
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const keyword of ['if', 'then', 'else'] as const) {
|
|
134
|
+
const conditionalSchema = normalizedSchema[keyword];
|
|
135
|
+
if (conditionalSchema != null) {
|
|
136
|
+
normalizedSchema[keyword] = normalizeDefinition(conditionalSchema);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return normalizedSchema;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeSchemaRecord(
|
|
144
|
+
schemas: Record<string, JSONSchema7Definition>,
|
|
145
|
+
): Record<string, JSONSchema7Definition> {
|
|
146
|
+
return Object.fromEntries(
|
|
147
|
+
Object.entries(schemas).map(([key, schema]) => [
|
|
148
|
+
key,
|
|
149
|
+
normalizeDefinition(schema),
|
|
150
|
+
]),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function normalizeDefinition(
|
|
155
|
+
definition: JSONSchema7Definition,
|
|
156
|
+
): JSONSchema7Definition {
|
|
157
|
+
return typeof definition === 'boolean'
|
|
158
|
+
? definition
|
|
159
|
+
: normalizeSchema(definition);
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/openai-batch.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
InvalidArgumentError,
|
|
3
3
|
InvalidResponseDataError,
|
|
4
|
+
UnsupportedFunctionalityError,
|
|
4
5
|
type Experimental_BatchV4 as BatchV4,
|
|
5
6
|
type Experimental_BatchV4CancelResult as BatchV4CancelResult,
|
|
6
7
|
type Experimental_BatchV4StartResult as BatchV4StartResult,
|
|
@@ -89,6 +90,20 @@ type OpenAIBatchResultConversion =
|
|
|
89
90
|
| { success: true; result: LanguageModelV4GenerateResult }
|
|
90
91
|
| { success: false; error: BatchV4Error };
|
|
91
92
|
|
|
93
|
+
function assertTextBatchRequests(
|
|
94
|
+
requests: BatchV4StartOptions['requests'],
|
|
95
|
+
): asserts requests is ReadonlyArray<OpenAIBatchRequest> {
|
|
96
|
+
for (const request of requests) {
|
|
97
|
+
const requestType = request.type;
|
|
98
|
+
if (requestType !== 'text') {
|
|
99
|
+
throw new UnsupportedFunctionalityError({
|
|
100
|
+
functionality: `batch request type: ${requestType}`,
|
|
101
|
+
message: `The OpenAI Batch API does not support batch requests with type "${requestType}".`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
92
107
|
const openaiBatchResponseZodSchema = () =>
|
|
93
108
|
z.object({
|
|
94
109
|
id: z.string(),
|
|
@@ -174,6 +189,7 @@ export class OpenAIBatch implements BatchV4<OpenAIBatchModelIds> {
|
|
|
174
189
|
async doStartBatch(
|
|
175
190
|
options: BatchV4StartOptions<OpenAIBatchModelIds>,
|
|
176
191
|
): Promise<BatchV4StartResult> {
|
|
192
|
+
assertTextBatchRequests(options.requests);
|
|
177
193
|
validateSingleModel(options.requests);
|
|
178
194
|
|
|
179
195
|
const fileParts: string[] = [];
|
package/src/openai-config.ts
CHANGED
|
@@ -24,6 +24,13 @@ export type OpenAIConfig = {
|
|
|
24
24
|
* @see https://github.com/vercel/ai/issues/20180
|
|
25
25
|
*/
|
|
26
26
|
explicitMessageItemType?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Whether the provider supports the
|
|
29
|
+
* `web_search_call.action.sources` Responses API include value.
|
|
30
|
+
*
|
|
31
|
+
* Defaults to `true`.
|
|
32
|
+
*/
|
|
33
|
+
supportsWebSearchSourcesInclude?: boolean;
|
|
27
34
|
/**
|
|
28
35
|
* This is soft-deprecated. Use provider references (e.g. `{ openai: 'file-abc123' }`)
|
|
29
36
|
* in file part data instead. File ID prefixes used to identify file IDs
|
package/src/openai-provider.ts
CHANGED
|
@@ -5,8 +5,6 @@ import type {
|
|
|
5
5
|
ImageModelV4,
|
|
6
6
|
LanguageModelV4,
|
|
7
7
|
ProviderV4,
|
|
8
|
-
Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
|
|
9
|
-
Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
|
|
10
8
|
SpeechModelV4,
|
|
11
9
|
SkillsV4,
|
|
12
10
|
TranscriptionModelV4,
|
|
@@ -33,7 +31,10 @@ import type { OpenAIImageModelId } from './image/openai-image-model-options';
|
|
|
33
31
|
import { openaiTools } from './openai-tools';
|
|
34
32
|
import { OpenAIBatch } from './openai-batch';
|
|
35
33
|
import { OpenAIResponsesLanguageModel } from './responses/openai-responses-language-model';
|
|
36
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
createOpenAIRealtimeFactory,
|
|
36
|
+
type OpenAIRealtimeFactory,
|
|
37
|
+
} from './realtime/openai-realtime-factory';
|
|
37
38
|
import type { OpenAIResponsesModelId } from './responses/openai-responses-language-model-options';
|
|
38
39
|
import { OpenAISpeechModel } from './speech/openai-speech-model';
|
|
39
40
|
import type { OpenAISpeechModelId } from './speech/openai-speech-model-options';
|
|
@@ -125,7 +126,7 @@ export interface OpenAIProvider extends ProviderV4 {
|
|
|
125
126
|
* Creates an experimental realtime model for bidirectional audio/text
|
|
126
127
|
* communication over WebSocket.
|
|
127
128
|
*/
|
|
128
|
-
experimental_realtime:
|
|
129
|
+
experimental_realtime: OpenAIRealtimeFactory;
|
|
129
130
|
|
|
130
131
|
/**
|
|
131
132
|
* Returns a FilesV4 interface for uploading files to OpenAI.
|
|
@@ -337,33 +338,6 @@ export function createOpenAI(
|
|
|
337
338
|
},
|
|
338
339
|
});
|
|
339
340
|
|
|
340
|
-
const createRealtimeModel = (modelId: string) =>
|
|
341
|
-
new OpenAIRealtimeModel(modelId, {
|
|
342
|
-
provider: `${providerName}.realtime`,
|
|
343
|
-
baseURL,
|
|
344
|
-
headers: getHeaders,
|
|
345
|
-
fetch: options.fetch,
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
const experimentalRealtimeFactory = Object.assign(
|
|
349
|
-
(modelId: string) => createRealtimeModel(modelId),
|
|
350
|
-
{
|
|
351
|
-
getToken: async (tokenOptions: RealtimeFactoryV4GetTokenOptions) => {
|
|
352
|
-
const model = createRealtimeModel(tokenOptions.model);
|
|
353
|
-
const secret = await model.doCreateClientSecret({
|
|
354
|
-
sessionConfig: tokenOptions.sessionConfig,
|
|
355
|
-
expiresAfterSeconds: tokenOptions.expiresAfterSeconds,
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
return {
|
|
359
|
-
token: secret.token,
|
|
360
|
-
url: secret.url,
|
|
361
|
-
expiresAt: secret.expiresAt,
|
|
362
|
-
};
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
) as RealtimeFactoryV4;
|
|
366
|
-
|
|
367
341
|
const provider = function (modelId: OpenAIResponsesModelId) {
|
|
368
342
|
return createLanguageModel(modelId);
|
|
369
343
|
};
|
|
@@ -393,7 +367,12 @@ export function createOpenAI(
|
|
|
393
367
|
provider.skills = createSkills;
|
|
394
368
|
provider.experimental_batch = createBatch;
|
|
395
369
|
|
|
396
|
-
provider.experimental_realtime =
|
|
370
|
+
provider.experimental_realtime = createOpenAIRealtimeFactory({
|
|
371
|
+
provider: providerName,
|
|
372
|
+
baseURL,
|
|
373
|
+
headers: getHeaders,
|
|
374
|
+
fetch: options.fetch,
|
|
375
|
+
});
|
|
397
376
|
|
|
398
377
|
provider.tools = openaiTools;
|
|
399
378
|
|
|
@@ -9,7 +9,11 @@ type OpenAIRealtimeWireEvent = {
|
|
|
9
9
|
session?: { id?: string };
|
|
10
10
|
item?: { id?: string } & Record<string, unknown>;
|
|
11
11
|
response?: { id?: string; status?: string };
|
|
12
|
-
error?: {
|
|
12
|
+
error?: {
|
|
13
|
+
message?: string | null;
|
|
14
|
+
code?: string | null;
|
|
15
|
+
event_id?: string | null;
|
|
16
|
+
} | null;
|
|
13
17
|
item_id: string;
|
|
14
18
|
previous_item_id?: string;
|
|
15
19
|
response_id: string;
|
|
@@ -217,6 +221,7 @@ export function parseOpenAIRealtimeServerEvent(
|
|
|
217
221
|
type: 'error',
|
|
218
222
|
message: event.error?.message ?? event.message ?? 'Unknown error',
|
|
219
223
|
code: event.error?.code ?? event.code,
|
|
224
|
+
clientEventId: event.error?.event_id ?? undefined,
|
|
220
225
|
raw,
|
|
221
226
|
};
|
|
222
227
|
|
|
@@ -238,12 +243,14 @@ export function serializeOpenAIRealtimeClientEvent(
|
|
|
238
243
|
return {
|
|
239
244
|
type: 'session.update',
|
|
240
245
|
session: buildOpenAISessionConfig(event.config, modelId),
|
|
246
|
+
...(event.eventId != null ? { event_id: event.eventId } : {}),
|
|
241
247
|
};
|
|
242
248
|
|
|
243
249
|
case 'input-audio-append':
|
|
244
250
|
return {
|
|
245
251
|
type: 'input_audio_buffer.append',
|
|
246
252
|
audio: event.audio,
|
|
253
|
+
...(event.eventId != null ? { event_id: event.eventId } : {}),
|
|
247
254
|
};
|
|
248
255
|
|
|
249
256
|
case 'input-audio-commit':
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InvalidArgumentError,
|
|
3
|
+
UnsupportedFunctionalityError,
|
|
4
|
+
type Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
|
|
5
|
+
type Experimental_RealtimeModelV4 as RealtimeModelV4,
|
|
6
|
+
} from '@ai-sdk/provider';
|
|
7
|
+
import { OpenAIRealtimeModelLive } from '../live/openai-realtime-model-live';
|
|
8
|
+
import {
|
|
9
|
+
OpenAIRealtimeModel,
|
|
10
|
+
type OpenAIRealtimeModelConfig,
|
|
11
|
+
} from './openai-realtime-model';
|
|
12
|
+
|
|
13
|
+
const knownLiveModelIds = ['gpt-live-1'] as const;
|
|
14
|
+
|
|
15
|
+
export type OpenAIRealtimeOptions = {
|
|
16
|
+
/** Overrides model ID routing, including for early-access models. */
|
|
17
|
+
api?: 'live' | 'realtime';
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface OpenAIRealtimeFactory extends RealtimeFactoryV4 {
|
|
21
|
+
(modelId: string, options: { api: 'live' }): OpenAIRealtimeModelLive;
|
|
22
|
+
(modelId: string, options: { api: 'realtime' }): OpenAIRealtimeModel;
|
|
23
|
+
(
|
|
24
|
+
modelId: (typeof knownLiveModelIds)[number],
|
|
25
|
+
options?: { api?: undefined },
|
|
26
|
+
): OpenAIRealtimeModelLive;
|
|
27
|
+
(modelId: string, options?: OpenAIRealtimeOptions): RealtimeModelV4;
|
|
28
|
+
|
|
29
|
+
getToken(
|
|
30
|
+
options: Parameters<RealtimeFactoryV4['getToken']>[0] &
|
|
31
|
+
OpenAIRealtimeOptions,
|
|
32
|
+
): ReturnType<RealtimeFactoryV4['getToken']>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveRealtimeApi(
|
|
36
|
+
modelId: string,
|
|
37
|
+
{ api }: OpenAIRealtimeOptions = {},
|
|
38
|
+
): 'live' | 'realtime' {
|
|
39
|
+
if (api !== undefined) {
|
|
40
|
+
if (api !== 'live' && api !== 'realtime') {
|
|
41
|
+
throw new InvalidArgumentError({
|
|
42
|
+
argument: 'api',
|
|
43
|
+
message: 'OpenAI realtime api must be "live" or "realtime".',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return api;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return knownLiveModelIds.some(knownModelId => knownModelId === modelId)
|
|
50
|
+
? 'live'
|
|
51
|
+
: 'realtime';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createOpenAIRealtimeFactory(
|
|
55
|
+
config: OpenAIRealtimeModelConfig,
|
|
56
|
+
): OpenAIRealtimeFactory {
|
|
57
|
+
const createModel = (modelId: string, options?: OpenAIRealtimeOptions) => {
|
|
58
|
+
const api = resolveRealtimeApi(modelId, options);
|
|
59
|
+
const modelConfig = { ...config, provider: `${config.provider}.${api}` };
|
|
60
|
+
return api === 'live'
|
|
61
|
+
? new OpenAIRealtimeModelLive(modelId, modelConfig)
|
|
62
|
+
: new OpenAIRealtimeModel(modelId, modelConfig);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return Object.assign(createModel, {
|
|
66
|
+
getToken: async (
|
|
67
|
+
options: Parameters<OpenAIRealtimeFactory['getToken']>[0],
|
|
68
|
+
) => {
|
|
69
|
+
const model = createModel(options.model, options);
|
|
70
|
+
if (model instanceof OpenAIRealtimeModelLive) {
|
|
71
|
+
throw new UnsupportedFunctionalityError({
|
|
72
|
+
functionality:
|
|
73
|
+
'Short-lived OpenAI credentials for the Live API. Use server WebSocket setup via getServerWebSocketConfig() with a server-side API key instead.',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const secret = await model.doCreateClientSecret({
|
|
78
|
+
sessionConfig: options.sessionConfig,
|
|
79
|
+
expiresAfterSeconds: options.expiresAfterSeconds,
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
token: secret.token,
|
|
83
|
+
url: secret.url,
|
|
84
|
+
expiresAt: secret.expiresAt,
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
}) as OpenAIRealtimeFactory;
|
|
88
|
+
}
|
|
@@ -179,6 +179,15 @@ export const openaiLanguageModelResponsesOptionsSchema = lazySchema(() =>
|
|
|
179
179
|
)
|
|
180
180
|
.nullish(),
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Whether to automatically include web search action sources in the
|
|
184
|
+
* response. Disable this for OpenAI-compatible providers that do not
|
|
185
|
+
* support the `web_search_call.action.sources` include value.
|
|
186
|
+
*
|
|
187
|
+
* Defaults to `true`.
|
|
188
|
+
*/
|
|
189
|
+
includeWebSearchSources: z.boolean().optional(),
|
|
190
|
+
|
|
182
191
|
/**
|
|
183
192
|
* Instructions for the model.
|
|
184
193
|
* They can be used to change the system or developer message when continuing a conversation using the `previousResponseId` option.
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
isUndeclaredParallelToolCall,
|
|
71
71
|
} from './expand-parallel-tool-call';
|
|
72
72
|
import { mapOpenAIResponseFinishReason } from './map-openai-responses-finish-reason';
|
|
73
|
+
import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
|
|
73
74
|
import {
|
|
74
75
|
openaiResponsesChunkSchema,
|
|
75
76
|
openaiResponsesResponseSchema,
|
|
@@ -444,6 +445,14 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
|
|
|
444
445
|
}
|
|
445
446
|
|
|
446
447
|
const strictJsonSchema = openaiOptions?.strictJsonSchema ?? true;
|
|
448
|
+
const normalizedResponseFormatSchema =
|
|
449
|
+
responseFormat?.type === 'json' && responseFormat.schema != null
|
|
450
|
+
? normalizeOpenAIJsonSchema(responseFormat.schema)
|
|
451
|
+
: undefined;
|
|
452
|
+
|
|
453
|
+
if (normalizedResponseFormatSchema != null) {
|
|
454
|
+
warnings.push(...normalizedResponseFormatSchema.warnings);
|
|
455
|
+
}
|
|
447
456
|
|
|
448
457
|
let include: OpenAIResponsesIncludeOptions = openaiOptions?.include;
|
|
449
458
|
|
|
@@ -486,7 +495,11 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
|
|
|
486
495
|
) as LanguageModelV4ProviderTool | undefined
|
|
487
496
|
)?.name;
|
|
488
497
|
|
|
489
|
-
if (
|
|
498
|
+
if (
|
|
499
|
+
webSearchToolName &&
|
|
500
|
+
config.supportsWebSearchSourcesInclude !== false &&
|
|
501
|
+
openaiOptions?.includeWebSearchSources !== false
|
|
502
|
+
) {
|
|
490
503
|
addInclude('web_search_call.action.sources');
|
|
491
504
|
}
|
|
492
505
|
|
|
@@ -513,13 +526,13 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
|
|
|
513
526
|
text: {
|
|
514
527
|
...(responseFormat?.type === 'json' && {
|
|
515
528
|
format:
|
|
516
|
-
|
|
529
|
+
normalizedResponseFormatSchema != null
|
|
517
530
|
? {
|
|
518
531
|
type: 'json_schema',
|
|
519
532
|
strict: strictJsonSchema,
|
|
520
533
|
name: responseFormat.name ?? 'response',
|
|
521
534
|
description: responseFormat.description,
|
|
522
|
-
schema:
|
|
535
|
+
schema: normalizedResponseFormatSchema.schema,
|
|
523
536
|
}
|
|
524
537
|
: { type: 'json_object' },
|
|
525
538
|
}),
|