@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.
@@ -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
+ }
@@ -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 { OpenAIRealtimeModel } from './realtime/openai-realtime-model';
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: RealtimeFactoryV4;
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 = experimentalRealtimeFactory;
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?: { message?: string; code?: string };
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
+ }