@zhivex-ai/openai 0.13.6 → 0.13.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/dist/index.js CHANGED
@@ -1,10 +1,14 @@
1
+ import { jsonHeaders, parseJson, RESERVED_REQUEST_HEADERS } from "./http.js";
2
+ import { OpenAIRealtimeModel, openAIRealtimeMcpMetadataKey, resolveOpenAIRealtimeHeaders } from "./realtime.js";
3
+ export { openAIRealtimeMcpMetadataKey } from "./realtime.js";
4
+ import { OpenAIEmbeddingModel, OpenAITranscriptionModel, OpenAISpeechModel, toUint8Array } from "./resource-models.js";
1
5
  import { OpenAIAgentsClient } from "./agents.js";
2
6
  export { OpenAIAgentsClient } from "./agents.js";
3
7
  import { toJSONSchema, z } from "zod";
4
8
  import { OpenAILiveModel, isOpenAILiveModel } from "./live.js";
5
9
  import { createOpenAIImageGenerationModel, normalizeOpenAIImageGenerationCall, normalizeOpenAIImageGenerationPartialImage } from "./image-generation.js";
6
- import { capabilities, groundedCapabilities, inferOpenAIRealtimeMode, supportsOpenAIModernResponses, isOpenAIAstraModel, isOpenAISolLunaModel, modelCapabilities, openAIRealtimeSupportsImageInput, realtimeCapabilities, speechCapabilities, supportsOpenAIApplyPatchAndSkills, supportsOpenAIShell, transcriptionCapabilities, } from "./capabilities.js";
7
- import { imageInputToDataUrl, CallbackRealtimeSession, ConfigurationError, ProviderHTTPError, ProviderResponseTooLargeError, ProviderToolCallError, assertTrustedEndpoint, decodeBase64WithLimit, readBodyWithLimit, readErrorBodyWithLimit, readJsonWithLimit, resolveAudioResponseLimits, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, encodeAudioFrame, encodeMediaFrame, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, toToolSet, toolResultPayload, withRetry, withResponseRetry, withTimeoutSignal } from "@zhivex-ai/core/provider";
10
+ import { groundedCapabilities, supportsOpenAIModernResponses, isOpenAIAstraModel, isOpenAISolLunaModel, modelCapabilities, supportsOpenAIApplyPatchAndSkills, supportsOpenAIShell } from "./capabilities.js";
11
+ import { imageInputToDataUrl, ConfigurationError, ProviderHTTPError, ProviderResponseTooLargeError, ProviderToolCallError, assertTrustedEndpoint, resolveAudioResponseLimits, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, streamChatCompletions, toolResultPayload, withRetry, withResponseRetry, withTimeoutSignal } from "@zhivex-ai/core/provider";
8
12
  const MIB = 1024 * 1024;
9
13
  const DEFAULT_HOSTED_IMAGE_EVENT_BYTES = 32 * MIB;
10
14
  const DEFAULT_HOSTED_IMAGE_TOTAL_BYTES = 128 * MIB;
@@ -59,42 +63,6 @@ const assertOpenAIToolPathInsideRoot = async (rootDir, targetPath, label) => {
59
63
  }
60
64
  return target;
61
65
  };
62
- export const openAIRealtimeMcpMetadataKey = "openai.realtime_mcp";
63
- const jsonHeaders = (apiKey) => ({
64
- "content-type": "application/json",
65
- authorization: `Bearer ${apiKey}`
66
- });
67
- const OPENAI_JSON_RESPONSE_MAX_BYTES = 128 * 1024 * 1024;
68
- const OPENAI_REALTIME_AUDIO_MAX_BYTES = 16 * 1024 * 1024;
69
- const RESERVED_REQUEST_HEADERS = new Set([
70
- "authorization",
71
- "content-type",
72
- "content-length",
73
- "host",
74
- "connection",
75
- "transfer-encoding"
76
- ]);
77
- const resolveOpenAIRealtimeHeaders = (apiKey, providerOptions, includeContentType = false) => {
78
- const rawHeaders = providerOptions?.headers;
79
- const customHeaders = rawHeaders && typeof rawHeaders === "object" && !Array.isArray(rawHeaders)
80
- ? { ...rawHeaders }
81
- : {};
82
- const safetyIdentifier = providerOptions?.safety_identifier;
83
- for (const key of Object.keys(customHeaders)) {
84
- const normalizedKey = key.toLowerCase();
85
- if (RESERVED_REQUEST_HEADERS.has(normalizedKey) ||
86
- (typeof safetyIdentifier === "string" && safetyIdentifier && normalizedKey === "openai-safety-identifier")) {
87
- delete customHeaders[key];
88
- }
89
- }
90
- return {
91
- ...(includeContentType ? jsonHeaders(apiKey) : { authorization: `Bearer ${apiKey}` }),
92
- ...customHeaders,
93
- ...(typeof safetyIdentifier === "string" && safetyIdentifier
94
- ? { "OpenAI-Safety-Identifier": safetyIdentifier }
95
- : {})
96
- };
97
- };
98
66
  const resolveOpenAILanguageRequestOptions = (providerOptions, apiKey) => {
99
67
  const bodyOptions = { ...(providerOptions ?? {}) };
100
68
  const apiMode = bodyOptions.apiMode ?? "auto";
@@ -139,22 +107,7 @@ const openAIResponsesBodyOptions = (options, modelId) => options.store === false
139
107
  }
140
108
  : options;
141
109
  const getRequestOptions = (input) => withTimeoutSignal(input);
142
- const toUint8Array = (data) => {
143
- if (data instanceof Uint8Array) {
144
- return data;
145
- }
146
- if (data instanceof ArrayBuffer) {
147
- return new Uint8Array(data);
148
- }
149
- return Uint8Array.from(Buffer.from(data, "base64"));
150
- };
151
110
  const toBase64 = (data) => typeof data === "string" ? data : Buffer.from(data instanceof Uint8Array ? data : new Uint8Array(data)).toString("base64");
152
- const createAudioFile = (audio) => {
153
- const bytes = toUint8Array(audio.data);
154
- return new File([bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)], audio.filename ?? "audio", {
155
- type: audio.mediaType
156
- });
157
- };
158
111
  const inferOpenAIAudioFormat = (mediaType, explicitFormat) => {
159
112
  if (explicitFormat) {
160
113
  return explicitFormat;
@@ -180,20 +133,6 @@ const inferOpenAIAudioFormat = (mediaType, explicitFormat) => {
180
133
  }
181
134
  return normalized?.replace(/^audio\//, "") || mediaType;
182
135
  };
183
- const parseJson = async (response, options = {}) => {
184
- if (!response.ok) {
185
- const body = await readErrorBodyWithLimit(response, options.errorBodyBytes);
186
- throw new ProviderHTTPError(`OpenAI request failed with status ${response.status}.`, response.status, {
187
- responseBody: body
188
- });
189
- }
190
- return readJsonWithLimit(response, {
191
- maxBytes: options.maxBytes ?? OPENAI_JSON_RESPONSE_MAX_BYTES,
192
- provider: options.provider ?? "openai",
193
- endpoint: options.endpoint,
194
- abort: options.abort
195
- });
196
- };
197
136
  const isOpenAIPromptCacheBreakpointPart = (part) => part.type === "provider-data" &&
198
137
  part.provider === "openai" &&
199
138
  part.data !== null &&
@@ -389,28 +328,6 @@ const mapTools = (input) => input
389
328
  };
390
329
  })
391
330
  : undefined;
392
- const mapRealtimeTools = (input) => input
393
- ? Object.values(input).map((tool) => {
394
- if (isCallableToolDefinition(tool)) {
395
- return {
396
- type: "function",
397
- name: tool.name,
398
- description: tool.description,
399
- parameters: toJSONSchema(tool.schema)
400
- };
401
- }
402
- if (tool.provider && tool.provider !== "openai") {
403
- throw new UnsupportedFeatureError(`Provider "openai" does not support hosted tools declared for provider "${tool.provider}".`);
404
- }
405
- if (tool.type !== "mcp") {
406
- throw new UnsupportedFeatureError(`Provider "openai" Realtime does not support the hosted tool type "${tool.type}". Use function or MCP tools.`);
407
- }
408
- return {
409
- type: tool.type,
410
- ...(tool.config && typeof tool.config === "object" ? tool.config : {})
411
- };
412
- })
413
- : undefined;
414
331
  const mapResponsesTools = (input) => input
415
332
  ? Object.values(input).map((tool) => {
416
333
  if (isCallableToolDefinition(tool)) {
@@ -461,359 +378,6 @@ const mapResponsesToolChoice = (toolChoice) => {
461
378
  name: toolChoice.toolName
462
379
  };
463
380
  };
464
- const mapRealtimeToolChoice = (toolChoice) => {
465
- if (!toolChoice || typeof toolChoice === "string") {
466
- return toolChoice;
467
- }
468
- return {
469
- type: "function",
470
- name: toolChoice.toolName
471
- };
472
- };
473
- const mapRealtimeProviderOptions = (providerOptions) => {
474
- if (!providerOptions) {
475
- return {};
476
- }
477
- return Object.fromEntries(Object.entries(providerOptions).filter(([key]) => !["headers", "safety_identifier", "realtime_url", "realtime_query", "expires_after"].includes(key)));
478
- };
479
- const mapRealtimeAudioFormat = (mediaType, sampleRateHz) => mediaType || sampleRateHz
480
- ? {
481
- ...(mediaType ? { type: mediaType } : {}),
482
- ...(sampleRateHz ? { rate: sampleRateHz } : {})
483
- }
484
- : undefined;
485
- const mapRealtimeSessionConfig = (config, modelId) => {
486
- const mode = inferOpenAIRealtimeMode(modelId ?? "", config.mode);
487
- const tools = mapRealtimeTools(toToolSet(config.tools));
488
- const audio = {
489
- input: {
490
- format: mapRealtimeAudioFormat(config.inputAudioMediaType, config.inputSampleRateHz),
491
- transcription: mode === "transcription" || config.inputTranscription
492
- ? {
493
- model: config.inputTranscription?.model ?? (mode === "transcription" ? modelId : undefined),
494
- language: config.inputTranscription?.language,
495
- prompt: config.inputTranscription?.prompt,
496
- delay: config.inputTranscription?.delay
497
- }
498
- : undefined,
499
- noise_reduction: config.noiseReduction ?? undefined,
500
- turn_detection: config.turnDetection ?? undefined
501
- },
502
- ...(mode === "transcription"
503
- ? {}
504
- : {
505
- output: {
506
- format: mapRealtimeAudioFormat(config.outputAudioMediaType, config.outputSampleRateHz),
507
- voice: config.voice
508
- }
509
- })
510
- };
511
- return {
512
- type: mode === "transcription" ? "transcription" : "realtime",
513
- model: modelId,
514
- instructions: config.translation?.instructions ?? config.instructions,
515
- output_modalities: mode === "transcription" ? undefined : config.outputAudioMediaType || config.voice || mode === "translation" ? ["audio"] : ["text"],
516
- tools: mode === "conversation" ? tools : undefined,
517
- tool_choice: mode === "conversation" && config.toolChoice ? mapRealtimeToolChoice(config.toolChoice) : undefined,
518
- reasoning: mode === "conversation" && config.reasoning?.effort
519
- ? {
520
- effort: config.reasoning.effort
521
- }
522
- : undefined,
523
- include: config.inputTranscription?.includeLogprobs && mode === "transcription" ? ["item.input_audio_transcription.logprobs"] : undefined,
524
- translation: mode === "translation"
525
- ? {
526
- target_language: config.translation?.targetLanguage,
527
- source_language: config.translation?.sourceLanguage
528
- }
529
- : undefined,
530
- audio,
531
- ...mapRealtimeProviderOptions(config.providerOptions)
532
- };
533
- };
534
- const openAIRealtimeURL = (baseURL, modelId, mode, providerOptions) => {
535
- const override = providerOptions?.realtime_url;
536
- if (typeof override === "string" && override) {
537
- return override;
538
- }
539
- const url = new URL(baseURL);
540
- url.protocol = url.protocol === "https:" ? "wss:" : url.protocol === "http:" ? "ws:" : url.protocol;
541
- const endpoint = mode === "translation" ? "realtime/translations" : mode === "transcription" ? "realtime/transcription_sessions" : "realtime";
542
- url.pathname = `${url.pathname.replace(/\/+$/, "")}/${endpoint}`;
543
- url.searchParams.set("model", modelId);
544
- const extraQuery = providerOptions?.realtime_query;
545
- if (extraQuery && typeof extraQuery === "object" && !Array.isArray(extraQuery)) {
546
- for (const [key, value] of Object.entries(extraQuery)) {
547
- if (value != null) {
548
- url.searchParams.set(key, String(value));
549
- }
550
- }
551
- }
552
- return url.toString();
553
- };
554
- const parseRealtimeProviderMetadata = (payload) => payload;
555
- const parseRealtimeJsonValue = (value) => {
556
- if (typeof value === "string") {
557
- try {
558
- return JSON.parse(value);
559
- }
560
- catch {
561
- return value;
562
- }
563
- }
564
- return value == null ? null : value;
565
- };
566
- const openAIRealtimeMcpProviderDataEvent = (payload, data) => [
567
- {
568
- type: "realtime-provider-data",
569
- provider: "openai",
570
- data: {
571
- ...data,
572
- raw_event: payload
573
- }
574
- }
575
- ];
576
- const openAIRealtimeMcpCommonData = (payload, item) => {
577
- const itemId = typeof payload.item_id === "string" ? payload.item_id : typeof item?.id === "string" ? item.id : undefined;
578
- const serverLabel = typeof payload.server_label === "string"
579
- ? payload.server_label
580
- : typeof item?.server_label === "string"
581
- ? item.server_label
582
- : undefined;
583
- const name = typeof payload.name === "string" ? payload.name : typeof item?.name === "string" ? item.name : undefined;
584
- return {
585
- ...(itemId ? { item_id: itemId } : {}),
586
- ...(serverLabel ? { server_label: serverLabel } : {}),
587
- ...(name ? { name } : {})
588
- };
589
- };
590
- const parseOpenAIRealtimeEvent = (payload) => {
591
- const type = String(payload.type ?? "");
592
- if (type === "session.created" || type === "session.updated") {
593
- return [];
594
- }
595
- if (type === "mcp_list_tools.in_progress" || type === "mcp_list_tools.completed" || type === "mcp_list_tools.failed") {
596
- const status = type.slice("mcp_list_tools.".length);
597
- return openAIRealtimeMcpProviderDataEvent(payload, {
598
- type: "mcp_list_tools",
599
- status,
600
- ...openAIRealtimeMcpCommonData(payload),
601
- ...(payload.error !== undefined ? { error: parseRealtimeJsonValue(payload.error) } : {})
602
- });
603
- }
604
- if (type === "conversation.item.done") {
605
- const item = payload.item && typeof payload.item === "object" ? payload.item : undefined;
606
- if (item?.type === "mcp_list_tools") {
607
- return openAIRealtimeMcpProviderDataEvent(payload, {
608
- type: "mcp_list_tools",
609
- status: "completed",
610
- ...openAIRealtimeMcpCommonData(payload, item),
611
- ...(item.tools !== undefined ? { tools: parseRealtimeJsonValue(item.tools) } : {})
612
- });
613
- }
614
- if (item?.type === "mcp_approval_request") {
615
- const approvalRequestId = typeof item.id === "string" ? item.id : undefined;
616
- return openAIRealtimeMcpProviderDataEvent(payload, {
617
- type: "mcp_approval_request",
618
- status: "approval_required",
619
- ...openAIRealtimeMcpCommonData(payload, item),
620
- ...(approvalRequestId ? { approval_request_id: approvalRequestId } : {}),
621
- ...(item.arguments !== undefined ? { arguments: parseRealtimeJsonValue(item.arguments) } : {})
622
- });
623
- }
624
- if (item?.type === "mcp_approval_response") {
625
- const approve = item.approve === true;
626
- return openAIRealtimeMcpProviderDataEvent(payload, {
627
- type: "mcp_approval_response",
628
- status: approve ? "approved" : "rejected",
629
- approve,
630
- ...openAIRealtimeMcpCommonData(payload, item),
631
- ...(typeof item.approval_request_id === "string" ? { approval_request_id: item.approval_request_id } : {}),
632
- ...(typeof item.reason === "string" ? { reason: item.reason } : {})
633
- });
634
- }
635
- }
636
- if (type === "response.mcp_call_arguments.delta" || type === "response.mcp_call_arguments.done") {
637
- const isDone = type.endsWith(".done");
638
- return openAIRealtimeMcpProviderDataEvent(payload, {
639
- type: "mcp_call",
640
- status: isDone ? "arguments_done" : "arguments_delta",
641
- ...openAIRealtimeMcpCommonData(payload),
642
- ...(isDone && payload.arguments !== undefined
643
- ? { arguments: parseRealtimeJsonValue(payload.arguments) }
644
- : payload.delta !== undefined
645
- ? { delta: parseRealtimeJsonValue(payload.delta) }
646
- : {})
647
- });
648
- }
649
- if (type === "response.mcp_call.in_progress" || type === "response.mcp_call.completed" || type === "response.mcp_call.failed") {
650
- const status = type.slice("response.mcp_call.".length);
651
- return openAIRealtimeMcpProviderDataEvent(payload, {
652
- type: "mcp_call",
653
- status,
654
- ...openAIRealtimeMcpCommonData(payload),
655
- ...(payload.output !== undefined ? { output: parseRealtimeJsonValue(payload.output) } : {}),
656
- ...(payload.error !== undefined ? { error: parseRealtimeJsonValue(payload.error) } : {})
657
- });
658
- }
659
- if (type === "response.output_item.done") {
660
- const item = payload.item && typeof payload.item === "object" ? payload.item : undefined;
661
- if (item?.type === "mcp_call") {
662
- return openAIRealtimeMcpProviderDataEvent(payload, {
663
- type: "mcp_call",
664
- status: item.status === "failed" ? "failed" : "completed",
665
- ...openAIRealtimeMcpCommonData(payload, item),
666
- ...(item.arguments !== undefined ? { arguments: parseRealtimeJsonValue(item.arguments) } : {}),
667
- ...(item.output !== undefined ? { output: parseRealtimeJsonValue(item.output) } : {}),
668
- ...(item.error !== undefined ? { error: parseRealtimeJsonValue(item.error) } : {}),
669
- ...(typeof item.approval_request_id === "string" ? { approval_request_id: item.approval_request_id } : {})
670
- });
671
- }
672
- }
673
- if (type === "response.text.delta" || type === "response.output_text.delta") {
674
- return [
675
- {
676
- type: "realtime-text-delta",
677
- textDelta: String(payload.delta ?? ""),
678
- itemId: typeof payload.item_id === "string" ? payload.item_id : undefined,
679
- responseId: typeof payload.response_id === "string" ? payload.response_id : undefined,
680
- role: "assistant",
681
- providerMetadata: parseRealtimeProviderMetadata(payload)
682
- }
683
- ];
684
- }
685
- if (type === "response.audio.delta" || type === "response.output_audio.delta") {
686
- return [
687
- {
688
- type: "realtime-audio-output",
689
- audio: decodeBase64WithLimit(String(payload.delta ?? ""), {
690
- maxBytes: OPENAI_REALTIME_AUDIO_MAX_BYTES,
691
- provider: "openai",
692
- endpoint: "realtime"
693
- }),
694
- mediaType: typeof payload.media_type === "string" ? payload.media_type : "audio/pcm",
695
- sampleRateHz: typeof payload.sample_rate_hz === "number" ? payload.sample_rate_hz : undefined,
696
- channels: typeof payload.channels === "number" ? payload.channels : undefined,
697
- itemId: typeof payload.item_id === "string" ? payload.item_id : undefined,
698
- responseId: typeof payload.response_id === "string" ? payload.response_id : undefined,
699
- providerMetadata: parseRealtimeProviderMetadata(payload)
700
- }
701
- ];
702
- }
703
- if (type === "conversation.item.input_audio_transcription.delta" || type === "input_audio_buffer.transcription.delta") {
704
- return [
705
- {
706
- type: "realtime-transcript",
707
- text: String(payload.delta ?? ""),
708
- role: "user",
709
- isFinal: false,
710
- itemId: typeof payload.item_id === "string" ? payload.item_id : undefined,
711
- providerMetadata: parseRealtimeProviderMetadata(payload)
712
- }
713
- ];
714
- }
715
- if (type === "conversation.item.input_audio_transcription.completed" || type === "input_audio_buffer.transcription.completed") {
716
- return [
717
- {
718
- type: "realtime-transcript",
719
- text: String(payload.transcript ?? ""),
720
- role: "user",
721
- isFinal: true,
722
- itemId: typeof payload.item_id === "string" ? payload.item_id : undefined,
723
- providerMetadata: parseRealtimeProviderMetadata(payload)
724
- }
725
- ];
726
- }
727
- if (type === "response.audio_transcript.delta" || type === "response.audio_transcription.delta" || type === "response.output_audio_transcript.delta") {
728
- return [
729
- {
730
- type: "realtime-transcript",
731
- text: String(payload.delta ?? ""),
732
- role: "assistant",
733
- isFinal: false,
734
- itemId: typeof payload.item_id === "string" ? payload.item_id : undefined,
735
- responseId: typeof payload.response_id === "string" ? payload.response_id : undefined,
736
- providerMetadata: parseRealtimeProviderMetadata(payload)
737
- }
738
- ];
739
- }
740
- if (type === "response.audio_transcript.done" || type === "response.audio_transcription.done" || type === "response.output_audio_transcript.done") {
741
- return [
742
- {
743
- type: "realtime-transcript",
744
- text: String(payload.transcript ?? ""),
745
- role: "assistant",
746
- isFinal: true,
747
- itemId: typeof payload.item_id === "string" ? payload.item_id : undefined,
748
- responseId: typeof payload.response_id === "string" ? payload.response_id : undefined,
749
- providerMetadata: parseRealtimeProviderMetadata(payload)
750
- }
751
- ];
752
- }
753
- if (type === "response.function_call_arguments.done" || type === "response.output_item.done") {
754
- const name = typeof payload.name === "string"
755
- ? payload.name
756
- : payload.item && typeof payload.item === "object" && typeof payload.item.name === "string"
757
- ? String(payload.item.name)
758
- : undefined;
759
- const callId = typeof payload.call_id === "string"
760
- ? payload.call_id
761
- : payload.item && typeof payload.item === "object" && typeof payload.item.call_id === "string"
762
- ? String(payload.item.call_id)
763
- : undefined;
764
- const rawArgs = typeof payload.arguments === "string"
765
- ? payload.arguments
766
- : payload.item && typeof payload.item === "object" && typeof payload.item.arguments === "string"
767
- ? String(payload.item.arguments)
768
- : "{}";
769
- if (!name || !callId) {
770
- return [];
771
- }
772
- return [
773
- {
774
- type: "realtime-tool-call",
775
- toolCall: {
776
- id: callId,
777
- name,
778
- input: JSON.parse(rawArgs || "{}")
779
- }
780
- }
781
- ];
782
- }
783
- if (type === "response.done") {
784
- return [
785
- {
786
- type: "realtime-response-complete",
787
- reason: typeof payload.status === "string" ? payload.status : undefined,
788
- providerMetadata: parseRealtimeProviderMetadata(payload)
789
- }
790
- ];
791
- }
792
- if (type === "error") {
793
- const error = payload.error && typeof payload.error === "object" ? payload.error : undefined;
794
- return [
795
- {
796
- type: "realtime-error",
797
- message: typeof payload.message === "string"
798
- ? payload.message
799
- : typeof error?.message === "string"
800
- ? error.message
801
- : "Realtime API error.",
802
- providerMetadata: parseRealtimeProviderMetadata(payload)
803
- }
804
- ];
805
- }
806
- if (type === "session.end") {
807
- return [
808
- {
809
- type: "realtime-end",
810
- reason: "session-end",
811
- providerMetadata: parseRealtimeProviderMetadata(payload)
812
- }
813
- ];
814
- }
815
- return [];
816
- };
817
381
  const mapStructuredOutput = (input) => {
818
382
  if (!input.structuredOutput || input.structuredOutput.mode !== "native") {
819
383
  return undefined;
@@ -2098,61 +1662,7 @@ class OpenAILanguageModel {
2098
1662
  }), { ...input, abortSignal: signal }, "OpenAI").catch((error) => { cleanup(); throw error; });
2099
1663
  return (async function* () {
2100
1664
  try {
2101
- const toolBuffers = new Map();
2102
- let lastFinishReason;
2103
- let lastUsage;
2104
- for await (const event of streamSSE(response)) {
2105
- if (event.data === "[DONE]") {
2106
- break;
2107
- }
2108
- const json = JSON.parse(event.data);
2109
- const choice = json.choices?.[0];
2110
- const delta = choice?.delta;
2111
- if (delta?.content) {
2112
- yield { type: "text-delta", textDelta: delta.content };
2113
- }
2114
- for (const toolCall of delta?.tool_calls ?? []) {
2115
- const index = typeof toolCall.index === "number" && Number.isSafeInteger(toolCall.index) && toolCall.index >= 0
2116
- ? toolCall.index
2117
- : 0;
2118
- const existing = toolBuffers.get(index) ?? {
2119
- id: toolCall.id ?? `${index}`,
2120
- name: toolCall.function?.name ?? "",
2121
- args: ""
2122
- };
2123
- existing.id = toolCall.id ?? existing.id;
2124
- existing.name ||= toolCall.function?.name ?? "";
2125
- existing.args += toolCall.function?.arguments ?? "";
2126
- toolBuffers.set(index, existing);
2127
- }
2128
- if (choice?.finish_reason === "tool_calls") {
2129
- for (const [, toolCall] of [...toolBuffers.entries()].sort(([left], [right]) => left - right)) {
2130
- yield {
2131
- type: "tool-call",
2132
- toolCall: {
2133
- id: toolCall.id,
2134
- name: toolCall.name,
2135
- input: JSON.parse(toolCall.args || "{}")
2136
- }
2137
- };
2138
- }
2139
- toolBuffers.clear();
2140
- }
2141
- if (choice?.finish_reason) {
2142
- lastFinishReason = choice.finish_reason;
2143
- }
2144
- if (json.usage) {
2145
- lastUsage = json.usage;
2146
- }
2147
- }
2148
- if (lastFinishReason || lastUsage) {
2149
- yield {
2150
- type: "finish",
2151
- finishReason: normalizeFinishReason(lastFinishReason),
2152
- providerFinishReason: lastFinishReason,
2153
- usage: mapChatUsage(lastUsage)
2154
- };
2155
- }
1665
+ yield* streamChatCompletions(response, "openai");
2156
1666
  }
2157
1667
  finally {
2158
1668
  cleanup();
@@ -2160,159 +1670,6 @@ class OpenAILanguageModel {
2160
1670
  })();
2161
1671
  }
2162
1672
  }
2163
- class OpenAIEmbeddingModel {
2164
- modelId;
2165
- apiKey;
2166
- baseURL;
2167
- fetcher;
2168
- provider = "openai";
2169
- capabilities = capabilities;
2170
- constructor(modelId, apiKey, baseURL, fetcher) {
2171
- this.modelId = modelId;
2172
- this.apiKey = apiKey;
2173
- this.baseURL = baseURL;
2174
- this.fetcher = fetcher;
2175
- }
2176
- async embed(input) {
2177
- const { signal, cleanup } = withTimeoutSignal(input);
2178
- const values = input.values.map((value) => {
2179
- if (typeof value !== "string") {
2180
- throw new UnsupportedFeatureError('Provider "openai" does not support multimodal embedding values.');
2181
- }
2182
- return value;
2183
- });
2184
- try {
2185
- const response = await withRetry(() => this.fetcher(`${this.baseURL}/embeddings`, {
2186
- method: "POST",
2187
- headers: jsonHeaders(this.apiKey),
2188
- signal,
2189
- body: JSON.stringify({
2190
- model: this.modelId,
2191
- input: values
2192
- })
2193
- }), input);
2194
- const json = await parseJson(response);
2195
- return {
2196
- embeddings: json.data.map((entry) => entry.embedding),
2197
- usage: {
2198
- inputTokens: json.usage?.prompt_tokens,
2199
- totalTokens: json.usage?.total_tokens
2200
- },
2201
- rawResponse: json
2202
- };
2203
- }
2204
- finally {
2205
- cleanup();
2206
- }
2207
- }
2208
- }
2209
- class OpenAITranscriptionModel {
2210
- modelId;
2211
- apiKey;
2212
- baseURL;
2213
- fetcher;
2214
- responseLimits;
2215
- provider = "openai";
2216
- capabilities = transcriptionCapabilities;
2217
- constructor(modelId, apiKey, baseURL, fetcher, responseLimits = resolveAudioResponseLimits()) {
2218
- this.modelId = modelId;
2219
- this.apiKey = apiKey;
2220
- this.baseURL = baseURL;
2221
- this.fetcher = fetcher;
2222
- this.responseLimits = responseLimits;
2223
- }
2224
- async transcribe(input) {
2225
- const { signal, cleanup, abort } = withTimeoutSignal(input);
2226
- const form = new FormData();
2227
- for (const [key, value] of Object.entries(input.providerOptions ?? {})) {
2228
- if (["model", "file", "prompt", "language"].includes(key)) {
2229
- continue;
2230
- }
2231
- form.set(key, typeof value === "string" ? value : JSON.stringify(value));
2232
- }
2233
- form.set("model", this.modelId);
2234
- form.set("file", createAudioFile(input.audio));
2235
- if (input.prompt) {
2236
- form.set("prompt", input.prompt);
2237
- }
2238
- if (input.language) {
2239
- form.set("language", input.language);
2240
- }
2241
- try {
2242
- const response = await withRetry(() => this.fetcher(`${this.baseURL}/audio/transcriptions`, {
2243
- method: "POST",
2244
- redirect: "error",
2245
- headers: { authorization: `Bearer ${this.apiKey}` },
2246
- signal,
2247
- body: form
2248
- }), input);
2249
- const json = await parseJson(response, {
2250
- maxBytes: this.responseLimits.transcriptionBytes,
2251
- errorBodyBytes: this.responseLimits.errorBodyBytes,
2252
- endpoint: "audio/transcriptions",
2253
- abort
2254
- });
2255
- return {
2256
- text: json.text ?? "",
2257
- rawResponse: json
2258
- };
2259
- }
2260
- finally {
2261
- cleanup();
2262
- }
2263
- }
2264
- }
2265
- class OpenAISpeechModel {
2266
- modelId;
2267
- apiKey;
2268
- baseURL;
2269
- fetcher;
2270
- responseLimits;
2271
- provider = "openai";
2272
- capabilities = speechCapabilities;
2273
- constructor(modelId, apiKey, baseURL, fetcher, responseLimits = resolveAudioResponseLimits()) {
2274
- this.modelId = modelId;
2275
- this.apiKey = apiKey;
2276
- this.baseURL = baseURL;
2277
- this.fetcher = fetcher;
2278
- this.responseLimits = responseLimits;
2279
- }
2280
- async generateSpeech(input) {
2281
- const { signal, cleanup, abort } = withTimeoutSignal(input);
2282
- try {
2283
- const response = await withRetry(() => this.fetcher(`${this.baseURL}/audio/speech`, {
2284
- method: "POST",
2285
- headers: jsonHeaders(this.apiKey),
2286
- signal,
2287
- body: JSON.stringify({
2288
- ...input.providerOptions,
2289
- model: this.modelId,
2290
- input: input.input,
2291
- voice: input.voice ?? "alloy"
2292
- })
2293
- }), input);
2294
- if (!response.ok) {
2295
- const body = await readErrorBodyWithLimit(response, this.responseLimits.errorBodyBytes);
2296
- throw new ProviderHTTPError(`OpenAI request failed with status ${response.status}.`, response.status, {
2297
- responseBody: body
2298
- });
2299
- }
2300
- return {
2301
- audio: await readBodyWithLimit(response, {
2302
- maxBytes: this.responseLimits.speechBytes,
2303
- provider: "openai",
2304
- endpoint: "audio/speech",
2305
- abort
2306
- }),
2307
- mediaType: response.headers.get("content-type") ?? "audio/mpeg",
2308
- rawResponse: undefined
2309
- };
2310
- }
2311
- finally {
2312
- cleanup();
2313
- }
2314
- }
2315
- }
2316
1673
  class OpenAIGroundedLanguageModel {
2317
1674
  modelId;
2318
1675
  apiKey;
@@ -2360,234 +1717,6 @@ class OpenAIGroundedLanguageModel {
2360
1717
  }
2361
1718
  }
2362
1719
  }
2363
- class OpenAIRealtimeModel {
2364
- modelId;
2365
- apiKey;
2366
- baseURL;
2367
- fetcher;
2368
- connectionFactory;
2369
- realtimeURL;
2370
- browserTokenURL;
2371
- allowUnsafeEndpoints;
2372
- provider = "openai";
2373
- capabilities;
2374
- constructor(modelId, apiKey, baseURL, fetcher, connectionFactory, realtimeURL, browserTokenURL, allowUnsafeEndpoints = false) {
2375
- this.modelId = modelId;
2376
- this.apiKey = apiKey;
2377
- this.baseURL = baseURL;
2378
- this.fetcher = fetcher;
2379
- this.connectionFactory = connectionFactory;
2380
- this.realtimeURL = realtimeURL;
2381
- this.browserTokenURL = browserTokenURL;
2382
- this.allowUnsafeEndpoints = allowUnsafeEndpoints;
2383
- this.capabilities = realtimeCapabilities(modelId);
2384
- }
2385
- resolveConfig(config) {
2386
- if (config.delegation)
2387
- throw new UnsupportedFeatureError("Client delegation requires a GPT-Live model.");
2388
- const mode = inferOpenAIRealtimeMode(this.modelId, config.mode);
2389
- if (mode !== "conversation" && (config.tools || config.toolChoice)) {
2390
- throw new UnsupportedFeatureError(`Provider "openai" model "${this.modelId}" does not support realtime tools in ${mode} mode.`);
2391
- }
2392
- if (mode === "translation" && !config.translation?.targetLanguage) {
2393
- throw new ConfigurationError('OpenAI realtime translation sessions require "translation.targetLanguage".');
2394
- }
2395
- if (mode === "transcription" && config.voice) {
2396
- throw new UnsupportedFeatureError(`Provider "openai" model "${this.modelId}" does not support realtime audio output in transcription mode.`);
2397
- }
2398
- return {
2399
- mode,
2400
- autoResponse: mode === "conversation",
2401
- ...config
2402
- };
2403
- }
2404
- async connect(config = {}, options) {
2405
- const initialConfig = this.resolveConfig(config);
2406
- const providerOptions = initialConfig.providerOptions;
2407
- const headers = resolveOpenAIRealtimeHeaders(this.apiKey, providerOptions);
2408
- const trustedRealtimeHost = new URL(this.realtimeURL ?? this.baseURL).hostname;
2409
- const realtimeEndpoint = assertTrustedEndpoint(this.realtimeURL ??
2410
- openAIRealtimeURL(this.baseURL, this.modelId, inferOpenAIRealtimeMode(this.modelId, initialConfig.mode), providerOptions), {
2411
- label: "OpenAI realtime endpoint",
2412
- protocols: ["wss"],
2413
- allowedHosts: [trustedRealtimeHost],
2414
- allowUnsafe: this.allowUnsafeEndpoints
2415
- }).toString();
2416
- const connection = await (this.connectionFactory ?? openWebSocketConnection)(realtimeEndpoint, headers, options);
2417
- const session = new CallbackRealtimeSession({
2418
- provider: this.provider,
2419
- modelId: this.modelId,
2420
- capabilities: this.capabilities,
2421
- config: initialConfig,
2422
- connection,
2423
- callbacks: {
2424
- parseEvent: parseOpenAIRealtimeEvent,
2425
- buildAudioPayloads: (frame, sessionConfig) => {
2426
- const mode = inferOpenAIRealtimeMode(this.modelId, sessionConfig.mode);
2427
- const payloads = [
2428
- {
2429
- type: "input_audio_buffer.append",
2430
- audio: encodeAudioFrame(frame)
2431
- }
2432
- ];
2433
- if (frame.isFinal) {
2434
- payloads.push({ type: "input_audio_buffer.commit" });
2435
- if (mode === "conversation" && (sessionConfig.autoResponse ?? true)) {
2436
- payloads.push({ type: "response.create" });
2437
- }
2438
- }
2439
- return payloads;
2440
- },
2441
- buildMediaPayloads: (frame) => {
2442
- if (!openAIRealtimeSupportsImageInput(this.modelId)) {
2443
- throw new UnsupportedFeatureError(`Provider "openai" model "${this.modelId}" does not support realtime image input.`);
2444
- }
2445
- if (!frame.mediaType.startsWith("image/")) {
2446
- throw new UnsupportedFeatureError(`Provider "openai" only supports realtime image media input, but received "${frame.mediaType}".`);
2447
- }
2448
- return [
2449
- {
2450
- type: "conversation.item.create",
2451
- item: {
2452
- type: "message",
2453
- role: "user",
2454
- content: [
2455
- {
2456
- type: "input_image",
2457
- image_url: `data:${frame.mediaType};base64,${encodeMediaFrame(frame)}`
2458
- }
2459
- ]
2460
- }
2461
- }
2462
- ];
2463
- },
2464
- buildTextPayloads: (text, sessionConfig) => {
2465
- const mode = inferOpenAIRealtimeMode(this.modelId, sessionConfig.mode);
2466
- if (mode !== "conversation") {
2467
- throw new UnsupportedFeatureError(`Provider "openai" model "${this.modelId}" does not support realtime text input in ${mode} mode.`);
2468
- }
2469
- const payloads = [
2470
- {
2471
- type: "conversation.item.create",
2472
- item: {
2473
- type: "message",
2474
- role: "user",
2475
- content: [{ type: "input_text", text }]
2476
- }
2477
- }
2478
- ];
2479
- if (sessionConfig.autoResponse ?? true) {
2480
- payloads.push({ type: "response.create" });
2481
- }
2482
- return payloads;
2483
- },
2484
- buildToolResultPayloads: (result, sessionConfig) => {
2485
- const mode = inferOpenAIRealtimeMode(this.modelId, sessionConfig.mode);
2486
- if (mode !== "conversation") {
2487
- throw new UnsupportedFeatureError(`Provider "openai" model "${this.modelId}" does not support realtime tools in ${mode} mode.`);
2488
- }
2489
- const mcpMetadata = result.providerMetadata?.[openAIRealtimeMcpMetadataKey];
2490
- if (mcpMetadata && typeof mcpMetadata === "object" && !Array.isArray(mcpMetadata)) {
2491
- const approval = mcpMetadata;
2492
- if (approval.type === "mcp_approval_response" &&
2493
- typeof approval.approval_request_id === "string" &&
2494
- typeof approval.approve === "boolean") {
2495
- return [
2496
- {
2497
- type: "conversation.item.create",
2498
- item: {
2499
- ...(typeof approval.id === "string" ? { id: approval.id } : {}),
2500
- type: "mcp_approval_response",
2501
- approval_request_id: approval.approval_request_id,
2502
- approve: approval.approve,
2503
- ...(typeof approval.reason === "string" ? { reason: approval.reason } : {})
2504
- }
2505
- }
2506
- ];
2507
- }
2508
- }
2509
- const payloads = [
2510
- {
2511
- type: "conversation.item.create",
2512
- item: {
2513
- type: "function_call_output",
2514
- call_id: result.toolCallId,
2515
- output: JSON.stringify(toolResultPayload(result))
2516
- }
2517
- }
2518
- ];
2519
- if (sessionConfig.autoResponse ?? true) {
2520
- payloads.push({ type: "response.create" });
2521
- }
2522
- return payloads;
2523
- },
2524
- buildUpdatePayloads: (sessionConfig) => [
2525
- {
2526
- type: "session.update",
2527
- session: mapRealtimeSessionConfig(this.resolveConfig(sessionConfig), this.modelId)
2528
- }
2529
- ],
2530
- buildInitialPayloads: (sessionConfig) => [
2531
- {
2532
- type: "session.update",
2533
- session: mapRealtimeSessionConfig(sessionConfig, this.modelId)
2534
- }
2535
- ]
2536
- }
2537
- });
2538
- await session.initialize();
2539
- return session;
2540
- }
2541
- async createBrowserToken(config = {}, options) {
2542
- const resolvedConfig = this.resolveConfig(config);
2543
- const providerOptions = { ...(resolvedConfig.providerOptions ?? {}) };
2544
- const expiresAfter = providerOptions.expires_after;
2545
- delete providerOptions.expires_after;
2546
- const { signal, cleanup } = withTimeoutSignal({
2547
- abortSignal: options?.signal,
2548
- timeoutMs: options?.timeoutMs
2549
- });
2550
- try {
2551
- const response = await withRetry(() => this.fetcher(this.browserTokenURL ?? `${this.baseURL}/realtime/client_secrets`, {
2552
- method: "POST",
2553
- redirect: "error",
2554
- headers: resolveOpenAIRealtimeHeaders(this.apiKey, providerOptions, true),
2555
- body: JSON.stringify({
2556
- ...(expiresAfter ? { expires_after: expiresAfter } : {}),
2557
- session: mapRealtimeSessionConfig({
2558
- ...resolvedConfig,
2559
- providerOptions
2560
- }, this.modelId)
2561
- }),
2562
- signal
2563
- }));
2564
- const payload = await parseJson(response);
2565
- const secret = payload.client_secret;
2566
- const value = secret && typeof secret === "object" && typeof secret.value === "string"
2567
- ? secret.value
2568
- : typeof payload.token === "string"
2569
- ? payload.token
2570
- : typeof payload.value === "string"
2571
- ? payload.value
2572
- : "";
2573
- const expiresAtMs = secret && typeof secret === "object" && typeof secret.expires_at_ms === "number"
2574
- ? secret.expires_at_ms
2575
- : typeof payload.expires_at_ms === "number"
2576
- ? payload.expires_at_ms
2577
- : typeof payload.expires_at === "number"
2578
- ? payload.expires_at * 1000
2579
- : undefined;
2580
- return {
2581
- value,
2582
- expiresAtMs,
2583
- rawResponse: payload
2584
- };
2585
- }
2586
- finally {
2587
- cleanup();
2588
- }
2589
- }
2590
- }
2591
1720
  export const createOpenAI = (options = {}) => {
2592
1721
  const apiKey = options.apiKey ?? process.env.OPENAI_API_KEY;
2593
1722
  if (!apiKey) {