@ai-sdk/openai 4.0.6 → 4.0.7
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.ts +11 -2
- package/dist/index.js +261 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +15 -4
- package/dist/internal/index.js +271 -14
- package/dist/internal/index.js.map +1 -1
- package/docs/03-openai.mdx +15 -5
- package/package.json +3 -3
- package/src/openai-config.ts +5 -1
- package/src/openai-provider.ts +8 -0
- package/src/transcription/openai-transcription-model-options.ts +20 -0
- package/src/transcription/openai-transcription-model.ts +328 -4
package/docs/03-openai.mdx
CHANGED
|
@@ -2698,13 +2698,23 @@ The following provider options are available:
|
|
|
2698
2698
|
- **include** _string[]_
|
|
2699
2699
|
Additional information to include in the transcription response.
|
|
2700
2700
|
|
|
2701
|
+
- **streaming** _object_
|
|
2702
|
+
Options for streaming transcription models such as `gpt-realtime-whisper`.
|
|
2703
|
+
Use with `experimental_streamTranscribe`.
|
|
2704
|
+
- **delay** _'minimal' | 'low' | 'medium' | 'high' | 'xhigh'_
|
|
2705
|
+
Latency/accuracy tradeoff for realtime transcription.
|
|
2706
|
+
|
|
2707
|
+
- **include** _string[]_
|
|
2708
|
+
Additional fields to include in realtime transcription events, such as `item.input_audio_transcription.logprobs`.
|
|
2709
|
+
|
|
2701
2710
|
### Model Capabilities
|
|
2702
2711
|
|
|
2703
|
-
| Model | Transcription | Duration | Segments | Language |
|
|
2704
|
-
| ------------------------ | ------------------- | ------------------- | ------------------- | ------------------- |
|
|
2705
|
-
| `whisper-1` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2706
|
-
| `gpt-4o-mini-transcribe` | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
|
|
2707
|
-
| `gpt-4o-transcribe` | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
|
|
2712
|
+
| Model | Transcription | Streaming | Duration | Segments | Language |
|
|
2713
|
+
| ------------------------ | ------------------- | ------------------- | ------------------- | ------------------- | ------------------- |
|
|
2714
|
+
| `whisper-1` | <Check size={18} /> | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
|
|
2715
|
+
| `gpt-4o-mini-transcribe` | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
|
|
2716
|
+
| `gpt-4o-transcribe` | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
|
|
2717
|
+
| `gpt-realtime-whisper` | <Cross size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
|
|
2708
2718
|
|
|
2709
2719
|
## Speech Models
|
|
2710
2720
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/openai",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ai-sdk/provider
|
|
39
|
-
"@ai-sdk/provider": "
|
|
38
|
+
"@ai-sdk/provider": "4.0.2",
|
|
39
|
+
"@ai-sdk/provider-utils": "5.0.5"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "22.19.19",
|
package/src/openai-config.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
FetchFunction,
|
|
3
|
+
WebSocketConstructor,
|
|
4
|
+
} from '@ai-sdk/provider-utils';
|
|
2
5
|
|
|
3
6
|
export type OpenAIConfig = {
|
|
4
7
|
provider: string;
|
|
5
8
|
url: (options: { modelId: string; path: string }) => string;
|
|
6
9
|
headers?: () => Record<string, string | undefined>;
|
|
7
10
|
fetch?: FetchFunction;
|
|
11
|
+
webSocket?: WebSocketConstructor;
|
|
8
12
|
generateId?: () => string;
|
|
9
13
|
/**
|
|
10
14
|
* This is soft-deprecated. Use provider references (e.g. `{ openai: 'file-abc123' }`)
|
package/src/openai-provider.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
withoutTrailingSlash,
|
|
17
17
|
withUserAgentSuffix,
|
|
18
18
|
type FetchFunction,
|
|
19
|
+
type WebSocketConstructor,
|
|
19
20
|
} from '@ai-sdk/provider-utils';
|
|
20
21
|
import { OpenAIChatLanguageModel } from './chat/openai-chat-language-model';
|
|
21
22
|
import type { OpenAIChatModelId } from './chat/openai-chat-language-model-options';
|
|
@@ -158,6 +159,12 @@ export interface OpenAIProviderSettings {
|
|
|
158
159
|
* or to provide a custom fetch implementation for e.g. testing.
|
|
159
160
|
*/
|
|
160
161
|
fetch?: FetchFunction;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Custom WebSocket implementation. This is useful for testing or for
|
|
165
|
+
* runtimes that need a WebSocket constructor with header support.
|
|
166
|
+
*/
|
|
167
|
+
webSocket?: WebSocketConstructor;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
/**
|
|
@@ -229,6 +236,7 @@ export function createOpenAI(
|
|
|
229
236
|
url: ({ path }) => `${baseURL}${path}`,
|
|
230
237
|
headers: getHeaders,
|
|
231
238
|
fetch: options.fetch,
|
|
239
|
+
webSocket: options.webSocket,
|
|
232
240
|
});
|
|
233
241
|
|
|
234
242
|
const createSpeechModel = (modelId: OpenAISpeechModelId) =>
|
|
@@ -12,6 +12,7 @@ export type OpenAITranscriptionModelId =
|
|
|
12
12
|
| 'gpt-4o-mini-transcribe-2025-12-15'
|
|
13
13
|
| 'gpt-4o-transcribe'
|
|
14
14
|
| 'gpt-4o-transcribe-diarize'
|
|
15
|
+
| 'gpt-realtime-whisper'
|
|
15
16
|
| (string & {});
|
|
16
17
|
|
|
17
18
|
// https://platform.openai.com/docs/api-reference/audio/createTranscription
|
|
@@ -48,6 +49,25 @@ export const openAITranscriptionModelOptions = lazySchema(() =>
|
|
|
48
49
|
.array(z.enum(['word', 'segment']))
|
|
49
50
|
.default(['segment'])
|
|
50
51
|
.optional(),
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Options for streaming transcription models such as `gpt-realtime-whisper`.
|
|
55
|
+
*/
|
|
56
|
+
streaming: z
|
|
57
|
+
.object({
|
|
58
|
+
/**
|
|
59
|
+
* Latency/accuracy tradeoff for realtime transcription.
|
|
60
|
+
*/
|
|
61
|
+
delay: z
|
|
62
|
+
.enum(['minimal', 'low', 'medium', 'high', 'xhigh'])
|
|
63
|
+
.optional(),
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Additional fields to include in realtime transcription events.
|
|
67
|
+
*/
|
|
68
|
+
include: z.array(z.string()).optional(),
|
|
69
|
+
})
|
|
70
|
+
.optional(),
|
|
51
71
|
}),
|
|
52
72
|
),
|
|
53
73
|
);
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
SharedV4Warning,
|
|
1
|
+
import {
|
|
2
|
+
UnsupportedFunctionalityError,
|
|
3
|
+
type Experimental_TranscriptionModelV4StreamOptions as TranscriptionModelV4StreamOptions,
|
|
4
|
+
type SharedV4Warning,
|
|
5
|
+
type TranscriptionModelV4,
|
|
6
|
+
type TranscriptionModelV4CallOptions,
|
|
5
7
|
} from '@ai-sdk/provider';
|
|
6
8
|
import {
|
|
7
9
|
combineHeaders,
|
|
8
10
|
convertBase64ToUint8Array,
|
|
11
|
+
convertToBase64,
|
|
9
12
|
createJsonResponseHandler,
|
|
13
|
+
getWebSocketConstructor,
|
|
10
14
|
mediaTypeToExtension,
|
|
11
15
|
parseProviderOptions,
|
|
12
16
|
postFormDataToApi,
|
|
17
|
+
readWebSocketMessageText,
|
|
18
|
+
safeParseJSON,
|
|
13
19
|
serializeModelOptions,
|
|
20
|
+
toWebSocketUrl,
|
|
14
21
|
WORKFLOW_DESERIALIZE,
|
|
15
22
|
WORKFLOW_SERIALIZE,
|
|
16
23
|
} from '@ai-sdk/provider-utils';
|
|
@@ -31,6 +38,35 @@ export type OpenAITranscriptionCallOptions = Omit<
|
|
|
31
38
|
};
|
|
32
39
|
};
|
|
33
40
|
|
|
41
|
+
export type OpenAITranscriptionStreamOptions = Omit<
|
|
42
|
+
TranscriptionModelV4StreamOptions,
|
|
43
|
+
'providerOptions'
|
|
44
|
+
> & {
|
|
45
|
+
providerOptions?: {
|
|
46
|
+
openai?: OpenAITranscriptionModelOptions;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type OpenAIRealtimeTranscriptionEvent = {
|
|
51
|
+
type?: string;
|
|
52
|
+
item_id?: string;
|
|
53
|
+
delta?: string;
|
|
54
|
+
transcript?: string;
|
|
55
|
+
error?: { message?: string };
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Realtime transcription model IDs stream over the realtime WebSocket
|
|
60
|
+
* and do not support the REST transcription endpoint. Prefix matching
|
|
61
|
+
* keeps dated snapshots (e.g. `gpt-realtime-whisper-2026-01-01`) working.
|
|
62
|
+
*/
|
|
63
|
+
function isRealtimeTranscriptionModelId(modelId: string): boolean {
|
|
64
|
+
return (
|
|
65
|
+
modelId === 'gpt-realtime-whisper' ||
|
|
66
|
+
modelId.startsWith('gpt-realtime-whisper-')
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
34
70
|
interface OpenAITranscriptionModelConfig extends OpenAIConfig {
|
|
35
71
|
_internal?: {
|
|
36
72
|
currentDate?: () => Date;
|
|
@@ -199,6 +235,12 @@ export class OpenAITranscriptionModel implements TranscriptionModelV4 {
|
|
|
199
235
|
async doGenerate(
|
|
200
236
|
options: OpenAITranscriptionCallOptions,
|
|
201
237
|
): Promise<Awaited<ReturnType<TranscriptionModelV4['doGenerate']>>> {
|
|
238
|
+
if (isRealtimeTranscriptionModelId(this.modelId)) {
|
|
239
|
+
throw new UnsupportedFunctionalityError({
|
|
240
|
+
functionality: `non-streaming transcription with ${this.modelId}`,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
202
244
|
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
203
245
|
const { formData, warnings } = await this.getArgs(options);
|
|
204
246
|
|
|
@@ -251,4 +293,286 @@ export class OpenAITranscriptionModel implements TranscriptionModelV4 {
|
|
|
251
293
|
},
|
|
252
294
|
};
|
|
253
295
|
}
|
|
296
|
+
|
|
297
|
+
async doStream(
|
|
298
|
+
options: OpenAITranscriptionStreamOptions,
|
|
299
|
+
): Promise<
|
|
300
|
+
Awaited<ReturnType<NonNullable<TranscriptionModelV4['doStream']>>>
|
|
301
|
+
> {
|
|
302
|
+
if (!isRealtimeTranscriptionModelId(this.modelId)) {
|
|
303
|
+
throw new UnsupportedFunctionalityError({
|
|
304
|
+
functionality: `streaming transcription with ${this.modelId}`,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
309
|
+
const openAIOptions = await parseProviderOptions({
|
|
310
|
+
provider: 'openai',
|
|
311
|
+
providerOptions: options.providerOptions,
|
|
312
|
+
schema: openAITranscriptionModelOptions,
|
|
313
|
+
});
|
|
314
|
+
const warnings: SharedV4Warning[] = [];
|
|
315
|
+
|
|
316
|
+
// options that only apply to the REST transcription endpoint
|
|
317
|
+
// (checked on the raw options because some have schema defaults):
|
|
318
|
+
const rawOpenAIOptions = options.providerOptions?.openai ?? {};
|
|
319
|
+
for (const option of [
|
|
320
|
+
'include',
|
|
321
|
+
'prompt',
|
|
322
|
+
'temperature',
|
|
323
|
+
'timestampGranularities',
|
|
324
|
+
]) {
|
|
325
|
+
if (rawOpenAIOptions[option as keyof typeof rawOpenAIOptions] != null) {
|
|
326
|
+
warnings.push({
|
|
327
|
+
type: 'unsupported',
|
|
328
|
+
feature: `providerOptions.openai.${option}`,
|
|
329
|
+
details: `OpenAI streaming transcription does not support ${option}.`,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const headers = combineHeaders(this.config.headers?.(), options.headers);
|
|
335
|
+
const sessionUpdate = buildOpenAIRealtimeTranscriptionSession({
|
|
336
|
+
modelId: this.modelId,
|
|
337
|
+
inputAudioFormat: options.inputAudioFormat,
|
|
338
|
+
providerOptions: openAIOptions,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
request: { body: sessionUpdate },
|
|
343
|
+
response: {
|
|
344
|
+
timestamp: currentDate,
|
|
345
|
+
modelId: this.modelId,
|
|
346
|
+
},
|
|
347
|
+
stream: createOpenAIRealtimeTranscriptionStream({
|
|
348
|
+
webSocket: this.config.webSocket,
|
|
349
|
+
url: toWebSocketUrl(
|
|
350
|
+
this.config.url({
|
|
351
|
+
path: '/realtime?intent=transcription',
|
|
352
|
+
modelId: this.modelId,
|
|
353
|
+
}),
|
|
354
|
+
),
|
|
355
|
+
headers,
|
|
356
|
+
sessionUpdate,
|
|
357
|
+
language: openAIOptions?.language,
|
|
358
|
+
warnings,
|
|
359
|
+
audio: options.audio,
|
|
360
|
+
abortSignal: options.abortSignal,
|
|
361
|
+
includeRawChunks: options.includeRawChunks,
|
|
362
|
+
}),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function createOpenAIRealtimeTranscriptionStream({
|
|
368
|
+
webSocket,
|
|
369
|
+
url,
|
|
370
|
+
headers,
|
|
371
|
+
sessionUpdate,
|
|
372
|
+
language,
|
|
373
|
+
warnings,
|
|
374
|
+
audio,
|
|
375
|
+
abortSignal,
|
|
376
|
+
includeRawChunks,
|
|
377
|
+
}: {
|
|
378
|
+
webSocket: OpenAIConfig['webSocket'];
|
|
379
|
+
url: URL;
|
|
380
|
+
headers: Record<string, string | undefined>;
|
|
381
|
+
sessionUpdate: unknown;
|
|
382
|
+
language: string | undefined;
|
|
383
|
+
warnings: SharedV4Warning[];
|
|
384
|
+
audio: ReadableStream<Uint8Array | string>;
|
|
385
|
+
abortSignal: AbortSignal | undefined;
|
|
386
|
+
includeRawChunks: boolean | undefined;
|
|
387
|
+
}) {
|
|
388
|
+
let finished = false;
|
|
389
|
+
let cleanup: (closeCode?: number) => void = () => {};
|
|
390
|
+
|
|
391
|
+
return new ReadableStream({
|
|
392
|
+
start: controller => {
|
|
393
|
+
const WebSocketConstructor = getWebSocketConstructor(webSocket);
|
|
394
|
+
const ws = new WebSocketConstructor(
|
|
395
|
+
url,
|
|
396
|
+
getOpenAIRealtimeProtocols(headers),
|
|
397
|
+
{ headers },
|
|
398
|
+
);
|
|
399
|
+
let audioReader:
|
|
400
|
+
| ReadableStreamDefaultReader<Uint8Array | string>
|
|
401
|
+
| undefined;
|
|
402
|
+
|
|
403
|
+
cleanup = (closeCode?: number) => {
|
|
404
|
+
abortSignal?.removeEventListener('abort', abort);
|
|
405
|
+
void audioReader?.cancel().catch(() => {});
|
|
406
|
+
try {
|
|
407
|
+
ws.close(closeCode);
|
|
408
|
+
} catch {}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const finishWithError = (error: unknown) => {
|
|
412
|
+
if (finished) return;
|
|
413
|
+
finished = true;
|
|
414
|
+
cleanup();
|
|
415
|
+
controller.error(error);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const finish = (text: string, id?: string) => {
|
|
419
|
+
if (finished) return;
|
|
420
|
+
finished = true;
|
|
421
|
+
if (id != null) {
|
|
422
|
+
controller.enqueue({ type: 'transcript-final', id, text });
|
|
423
|
+
}
|
|
424
|
+
controller.enqueue({
|
|
425
|
+
type: 'finish',
|
|
426
|
+
text,
|
|
427
|
+
segments: [],
|
|
428
|
+
language,
|
|
429
|
+
});
|
|
430
|
+
controller.close();
|
|
431
|
+
cleanup(1000);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const abort = () => {
|
|
435
|
+
finishWithError(abortSignal?.reason ?? new Error('Aborted'));
|
|
436
|
+
};
|
|
437
|
+
if (abortSignal?.aborted) {
|
|
438
|
+
abort();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
abortSignal?.addEventListener('abort', abort, { once: true });
|
|
442
|
+
|
|
443
|
+
const sendAudio = async () => {
|
|
444
|
+
audioReader = audio.getReader();
|
|
445
|
+
try {
|
|
446
|
+
while (true) {
|
|
447
|
+
const { done, value } = await audioReader.read();
|
|
448
|
+
if (done || finished) break;
|
|
449
|
+
ws.send(
|
|
450
|
+
JSON.stringify({
|
|
451
|
+
type: 'input_audio_buffer.append',
|
|
452
|
+
audio: convertToBase64(value),
|
|
453
|
+
}),
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
} finally {
|
|
457
|
+
audioReader.releaseLock();
|
|
458
|
+
}
|
|
459
|
+
if (!finished) {
|
|
460
|
+
ws.send(JSON.stringify({ type: 'input_audio_buffer.commit' }));
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
ws.onopen = () => {
|
|
465
|
+
controller.enqueue({ type: 'stream-start', warnings });
|
|
466
|
+
ws.send(JSON.stringify(sessionUpdate));
|
|
467
|
+
void sendAudio().catch(finishWithError);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
ws.onmessage = event => {
|
|
471
|
+
void readWebSocketMessageText(event.data)
|
|
472
|
+
.then(async text => {
|
|
473
|
+
const parsed = await safeParseJSON({ text });
|
|
474
|
+
if (!parsed.success) return;
|
|
475
|
+
const raw = parsed.value as OpenAIRealtimeTranscriptionEvent;
|
|
476
|
+
|
|
477
|
+
if (includeRawChunks) {
|
|
478
|
+
controller.enqueue({ type: 'raw', rawValue: raw });
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
switch (raw.type) {
|
|
482
|
+
case 'conversation.item.input_audio_transcription.delta': {
|
|
483
|
+
controller.enqueue({
|
|
484
|
+
type: 'transcript-delta',
|
|
485
|
+
id: raw.item_id,
|
|
486
|
+
delta: raw.delta ?? '',
|
|
487
|
+
});
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
case 'conversation.item.input_audio_transcription.completed': {
|
|
492
|
+
finish(raw.transcript ?? '', raw.item_id);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
case 'error': {
|
|
497
|
+
finishWithError(
|
|
498
|
+
new Error(raw.error?.message ?? 'OpenAI realtime error'),
|
|
499
|
+
);
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
.catch(finishWithError);
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
ws.onerror = () => {
|
|
508
|
+
finishWithError(new Error('OpenAI realtime transcription error'));
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
ws.onclose = () => {
|
|
512
|
+
if (finished) return;
|
|
513
|
+
finished = true;
|
|
514
|
+
cleanup();
|
|
515
|
+
controller.close();
|
|
516
|
+
};
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
cancel: () => {
|
|
520
|
+
if (finished) return;
|
|
521
|
+
finished = true;
|
|
522
|
+
cleanup();
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function buildOpenAIRealtimeTranscriptionSession({
|
|
528
|
+
modelId,
|
|
529
|
+
inputAudioFormat,
|
|
530
|
+
providerOptions,
|
|
531
|
+
}: {
|
|
532
|
+
modelId: string;
|
|
533
|
+
inputAudioFormat: TranscriptionModelV4StreamOptions['inputAudioFormat'];
|
|
534
|
+
providerOptions: OpenAITranscriptionModelOptions | undefined;
|
|
535
|
+
}) {
|
|
536
|
+
return {
|
|
537
|
+
type: 'session.update',
|
|
538
|
+
session: {
|
|
539
|
+
type: 'transcription',
|
|
540
|
+
audio: {
|
|
541
|
+
input: {
|
|
542
|
+
format: {
|
|
543
|
+
type: inputAudioFormat.type,
|
|
544
|
+
...(inputAudioFormat.rate != null
|
|
545
|
+
? { rate: inputAudioFormat.rate }
|
|
546
|
+
: {}),
|
|
547
|
+
},
|
|
548
|
+
transcription: {
|
|
549
|
+
model: modelId,
|
|
550
|
+
...(providerOptions?.language != null
|
|
551
|
+
? { language: providerOptions.language }
|
|
552
|
+
: {}),
|
|
553
|
+
...(providerOptions?.streaming?.delay != null
|
|
554
|
+
? { delay: providerOptions.streaming.delay }
|
|
555
|
+
: {}),
|
|
556
|
+
},
|
|
557
|
+
turn_detection: null,
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
...(providerOptions?.streaming?.include != null
|
|
561
|
+
? { include: providerOptions.streaming.include }
|
|
562
|
+
: {}),
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function getOpenAIRealtimeProtocols(
|
|
568
|
+
headers: Record<string, string | undefined>,
|
|
569
|
+
): string[] {
|
|
570
|
+
const authorization = headers.Authorization ?? headers.authorization;
|
|
571
|
+
const token = authorization?.startsWith('Bearer ')
|
|
572
|
+
? authorization.slice('Bearer '.length)
|
|
573
|
+
: undefined;
|
|
574
|
+
|
|
575
|
+
return token == null
|
|
576
|
+
? ['realtime']
|
|
577
|
+
: ['realtime', `openai-insecure-api-key.${token}`];
|
|
254
578
|
}
|