@ai-sdk/openai 4.0.66 → 4.0.68
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 +31 -0
- package/README.md +26 -1
- package/dist/index.d.ts +125 -28
- package/dist/index.js +612 -108
- package/dist/index.js.map +1 -1
- package/docs/03-openai.mdx +102 -7
- package/package.json +3 -3
- 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/openai-provider.ts +11 -32
- package/src/realtime/openai-realtime-event-mapper.ts +8 -1
- package/src/realtime/openai-realtime-factory.ts +88 -0
|
@@ -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
|
+
}
|
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
|
+
}
|