UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

169 lines (168 loc) 6.59 kB
import { s as asFiniteNumber } from "./number-coercion-CJQ8TR--.js"; import { u as normalizeResolvedSecretInputString } from "./types.secrets-_0JOMGE5.js"; import { c as resolveProviderRequestHeaders } from "./provider-request-config-0Zg8QIAI.js"; import { i as isProviderAuthProfileConfigured, s as resolveProviderAuthProfileApiKey } from "./provider-auth-DAOC_qI9.js"; import "./secret-input-DVCzFGxN.js"; import "./provider-http-BXCBCi4E.js"; import { t as createRealtimeTranscriptionWebSocketSession } from "./realtime-transcription-DMdRb-KG.js"; import { a as resolveOpenAIProviderConfigRecord, i as readRealtimeErrorDetail, o as trimToUndefined, r as createOpenAIRealtimeTranscriptionClientSecret } from "./realtime-provider-shared-C_he3i03.js"; //#region extensions/openai/realtime-transcription-provider.ts const OPENAI_REALTIME_TRANSCRIPTION_URL = "wss://api.openai.com/v1/realtime?intent=transcription"; const OPENAI_REALTIME_TRANSCRIPTION_CONNECT_TIMEOUT_MS = 1e4; const OPENAI_REALTIME_TRANSCRIPTION_MAX_RECONNECT_ATTEMPTS = 5; const OPENAI_REALTIME_TRANSCRIPTION_RECONNECT_DELAY_MS = 1e3; const OPENAI_REALTIME_TRANSCRIPTION_DEFAULT_MODEL = "gpt-4o-transcribe"; function normalizeProviderConfig(config) { const raw = resolveOpenAIProviderConfigRecord(config); return { apiKey: normalizeResolvedSecretInputString({ value: raw?.apiKey, path: "plugins.entries.voice-call.config.streaming.providers.openai.apiKey" }) ?? normalizeResolvedSecretInputString({ value: raw?.openaiApiKey, path: "plugins.entries.voice-call.config.streaming.openaiApiKey" }), language: trimToUndefined(raw?.language), model: trimToUndefined(raw?.model) ?? trimToUndefined(raw?.sttModel), prompt: trimToUndefined(raw?.prompt), silenceDurationMs: normalizeNonNegativeInteger(raw?.silenceDurationMs), vadThreshold: normalizeVadThreshold(raw?.vadThreshold) }; } function normalizeNonNegativeInteger(value) { const number = asFiniteNumber(value); if (number === void 0 || !Number.isSafeInteger(number) || number < 0) return; return number; } function normalizeVadThreshold(value) { const number = asFiniteNumber(value); if (number === void 0 || number < 0 || number > 1) return; return number; } function buildOpenAIRealtimeTranscriptionSessionPayload(config) { return { type: "transcription", audio: { input: { format: { type: "audio/pcmu" }, transcription: { model: config.model, ...config.language ? { language: config.language } : {}, ...config.prompt ? { prompt: config.prompt } : {} }, turn_detection: { type: "server_vad", threshold: config.vadThreshold, prefix_padding_ms: 300, silence_duration_ms: config.silenceDurationMs } } } }; } async function resolveOpenAIRealtimeTranscriptionAuthorization(config) { const apiKey = config.apiKey || process.env.OPENAI_API_KEY; if (apiKey) return apiKey; const authToken = await resolveProviderAuthProfileApiKey({ provider: "openai", cfg: config.cfg }); if (!authToken) throw new Error("OpenAI API key or Codex OAuth missing"); return (await createOpenAIRealtimeTranscriptionClientSecret({ authToken, auditContext: "openai-realtime-transcription-session", session: buildOpenAIRealtimeTranscriptionSessionPayload(config) })).value; } function createOpenAIRealtimeTranscriptionSession(config) { let pendingTranscript = ""; const handleEvent = (event, transport) => { switch (event.type) { case "session.updated": case "transcription_session.updated": transport.markReady(); return; case "conversation.item.input_audio_transcription.delta": if (event.delta) { pendingTranscript += event.delta; config.onPartial?.(pendingTranscript); } return; case "conversation.item.input_audio_transcription.completed": if (event.transcript) config.onTranscript?.(event.transcript); pendingTranscript = ""; return; case "input_audio_buffer.speech_started": pendingTranscript = ""; config.onSpeechStart?.(); return; case "error": { const detail = readRealtimeErrorDetail(event.error); const error = new Error(detail); if (!transport.isReady()) transport.failConnect(error); else config.onError?.(error); } default: } }; return createRealtimeTranscriptionWebSocketSession({ providerId: "openai", callbacks: config, url: OPENAI_REALTIME_TRANSCRIPTION_URL, headers: async () => { const bearer = await resolveOpenAIRealtimeTranscriptionAuthorization(config); return resolveProviderRequestHeaders({ provider: "openai", baseUrl: OPENAI_REALTIME_TRANSCRIPTION_URL, capability: "audio", transport: "websocket", defaultHeaders: { Authorization: `Bearer ${bearer}` } }) ?? { Authorization: `Bearer ${bearer}` }; }, connectTimeoutMs: OPENAI_REALTIME_TRANSCRIPTION_CONNECT_TIMEOUT_MS, maxReconnectAttempts: OPENAI_REALTIME_TRANSCRIPTION_MAX_RECONNECT_ATTEMPTS, reconnectDelayMs: OPENAI_REALTIME_TRANSCRIPTION_RECONNECT_DELAY_MS, connectTimeoutMessage: "OpenAI realtime transcription connection timeout", connectClosedBeforeReadyMessage: "OpenAI realtime transcription connection closed before ready", reconnectLimitMessage: "OpenAI realtime transcription reconnect limit reached", sendAudio: (audio, transport) => { transport.sendJson({ type: "input_audio_buffer.append", audio: audio.toString("base64") }); }, onOpen: (transport) => { transport.sendJson({ type: "session.update", session: buildOpenAIRealtimeTranscriptionSessionPayload(config) }); }, onMessage: handleEvent }); } function buildOpenAIRealtimeTranscriptionProvider() { return { id: "openai", label: "OpenAI Realtime Transcription", aliases: ["openai-realtime"], defaultModel: OPENAI_REALTIME_TRANSCRIPTION_DEFAULT_MODEL, autoSelectOrder: 10, resolveConfig: ({ rawConfig }) => normalizeProviderConfig(rawConfig), isConfigured: ({ cfg, providerConfig }) => Boolean(normalizeProviderConfig(providerConfig).apiKey || process.env.OPENAI_API_KEY || isProviderAuthProfileConfigured({ provider: "openai", cfg })), createSession: (req) => { const config = normalizeProviderConfig(req.providerConfig); return createOpenAIRealtimeTranscriptionSession({ ...req, apiKey: config.apiKey, language: config.language, model: config.model ?? OPENAI_REALTIME_TRANSCRIPTION_DEFAULT_MODEL, prompt: config.prompt, silenceDurationMs: config.silenceDurationMs ?? 800, vadThreshold: config.vadThreshold ?? .5 }); } }; } //#endregion export { buildOpenAIRealtimeTranscriptionProvider as t };