UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

332 lines (331 loc) • 12.1 kB
import { D as serializeXaiRealtimeToolResult, d as createXaiRealtimeVoiceProviderMetadata, m as normalizeXaiRealtimeTranscriptionProviderConfig, s as assertXaiRealtimeVoiceRequestSupported, u as createXaiRealtimeTranscriptionProviderMetadata } from "./.setup/capability-provider-metadata-factory-Hx0z45WX.mjs"; import { n as createXaiSpeechProviderMetadata } from "./.setup/speech-provider-metadata-factory-Da2Rd33b.mjs"; import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { createRealtimeVoiceAudioQueue } from "openclaw/plugin-sdk/realtime-voice-audio-queue"; import { createLazyRealtimeVoiceBridgeLifecycle } from "openclaw/plugin-sdk/realtime-voice-provider"; //#region extensions/xai/realtime-voice-lazy.ts const MAX_LAZY_REALTIME_VOICE_USER_MESSAGES = 128; const MAX_LAZY_REALTIME_VOICE_USER_MESSAGE_BYTES = 262144; const MAX_LAZY_REALTIME_VOICE_TOOL_RESULTS = 128; const MAX_LAZY_REALTIME_VOICE_TOOL_RESULT_BYTES = 262144; const loadXaiRealtimeVoiceProvider = createLazyRuntimeModule(async () => (await import("./.setup/realtime-voice-provider-Bgn42bpx.mjs")).buildXaiRealtimeVoiceProvider()); function createLazyXaiRealtimeVoiceBridge(req) { assertXaiRealtimeVoiceRequestSupported(req); let acceptsInput = false; let pendingMediaTimestamp; let pendingGreeting; let pendingUserMessageCount = 0; let pendingUserMessageBytes = 0; let pendingToolResultCount = 0; let pendingToolResultBytes = 0; const pendingAudio = createRealtimeVoiceAudioQueue("reject-newest"); const pendingOperations = []; const lifecycle = createLazyRealtimeVoiceBridgeLifecycle({ label: "xAI", request: req, load: async (request) => (await loadXaiRealtimeVoiceProvider()).createBridge(request), clearPending: () => { acceptsInput = false; pendingAudio.clear(); pendingOperations.length = 0; pendingMediaTimestamp = void 0; pendingGreeting = void 0; pendingUserMessageCount = 0; pendingUserMessageBytes = 0; pendingToolResultCount = 0; pendingToolResultBytes = 0; }, onConnected: (bridge, isCurrent) => flushPendingInput(bridge, isCurrent) }); const replacePendingOperation = (previous, next) => { if (previous) { const previousIndex = pendingOperations.indexOf(previous); if (previousIndex >= 0) pendingOperations.splice(previousIndex, 1); } pendingOperations.push(next); return next; }; const flushPendingInput = async (loadedBridge, isCurrent) => { if (!isCurrent()) return; while (true) { if (!isCurrent()) return; const operation = pendingOperations.shift(); if (!operation) { acceptsInput = true; return; } switch (operation.type) { case "audio": { const chunk = pendingAudio.dequeue(); if (!chunk) throw new Error("xAI realtime voice pending audio queue invariant violated"); loadedBridge.sendAudio(chunk); break; } case "media-timestamp": if (pendingMediaTimestamp === operation) pendingMediaTimestamp = void 0; loadedBridge.setMediaTimestamp(operation.timestamp); break; case "user-message": loadedBridge.sendUserMessage?.(operation.text); break; case "tool-result": await loadedBridge.submitToolResult(operation.callId, operation.result, operation.options); break; case "greeting": if (pendingGreeting === operation) pendingGreeting = void 0; loadedBridge.triggerGreeting?.(operation.instructions); } if (!isCurrent()) return; if (operation.type === "user-message") { pendingUserMessageCount -= 1; pendingUserMessageBytes -= operation.bytes; } else if (operation.type === "tool-result") { pendingToolResultCount -= 1; pendingToolResultBytes -= operation.bytes; } } }; return { get supportsToolResultContinuation() { return lifecycle.bridge?.supportsToolResultContinuation ?? false; }, connect: lifecycle.connect, sendAudio: (audio) => { if (!lifecycle.isActive()) return; const bridge = lifecycle.bridge; if (acceptsInput && bridge) { bridge.sendAudio(audio); return; } if (pendingAudio.enqueue(audio)) pendingOperations.push({ type: "audio" }); }, setMediaTimestamp: (timestamp) => { if (!lifecycle.isActive()) return; const bridge = lifecycle.bridge; if (acceptsInput && bridge) { bridge.setMediaTimestamp(timestamp); return; } pendingMediaTimestamp = replacePendingOperation(pendingMediaTimestamp, { timestamp, type: "media-timestamp" }); }, sendUserMessage: (text) => { if (!lifecycle.isActive()) return; const bridge = lifecycle.bridge; if (acceptsInput && bridge) { bridge.sendUserMessage?.(text); return; } const messageBytes = Buffer.byteLength(text, "utf8"); if (pendingUserMessageCount >= MAX_LAZY_REALTIME_VOICE_USER_MESSAGES || pendingUserMessageBytes + messageBytes > MAX_LAZY_REALTIME_VOICE_USER_MESSAGE_BYTES) { req.onError?.(/* @__PURE__ */ new Error("xAI realtime voice pending user message overflow during lazy startup")); return; } pendingOperations.push({ bytes: messageBytes, text, type: "user-message" }); pendingUserMessageCount += 1; pendingUserMessageBytes += messageBytes; }, triggerGreeting: (instructions) => { if (!lifecycle.isActive()) return; const bridge = lifecycle.bridge; if (acceptsInput && bridge) { bridge.triggerGreeting?.(instructions); return; } pendingGreeting = replacePendingOperation(pendingGreeting, { instructions, type: "greeting" }); }, handleBargeIn: (options) => { if (lifecycle.isActive()) lifecycle.bridge?.handleBargeIn?.(options); }, submitToolResult: (callId, result, options) => { if (!lifecycle.isActive() || options?.willContinue === true) return; const bridge = lifecycle.bridge; if (acceptsInput && bridge) return bridge.submitToolResult(callId, result, options); let serialized; try { serialized = serializeXaiRealtimeToolResult(result); } catch (error) { req.onError?.(error); throw error; } const pending = { callId, result: JSON.parse(serialized), ...options ? { options } : {} }; const resultBytes = Buffer.byteLength(JSON.stringify(pending), "utf8"); if (pendingToolResultCount >= MAX_LAZY_REALTIME_VOICE_TOOL_RESULTS || pendingToolResultBytes + resultBytes > MAX_LAZY_REALTIME_VOICE_TOOL_RESULT_BYTES) { const error = /* @__PURE__ */ new Error("xAI realtime voice pending tool result overflow during lazy startup"); req.onError?.(error); throw error; } pendingOperations.push({ ...pending, bytes: resultBytes, type: "tool-result" }); pendingToolResultCount += 1; pendingToolResultBytes += resultBytes; }, acknowledgeMark: (markName) => { if (lifecycle.isActive()) lifecycle.bridge?.acknowledgeMark(markName); }, close: lifecycle.close, isConnected: () => lifecycle.isActive() && (lifecycle.bridge?.isConnected() ?? false) }; } //#endregion //#region extensions/xai/lazy-capability-provider-factories.ts const MAX_LAZY_REALTIME_TRANSCRIPTION_AUDIO_BYTES = 2097152; createLazyRuntimeModule(async () => (await import("./.setup/image-generation-provider-D68QbrsN.mjs")).buildXaiImageGenerationProvider()); createLazyRuntimeModule(async () => (await import("./.setup/stt-CptCc3n8.mjs")).buildXaiMediaUnderstandingProvider()); const loadXaiSpeechProvider = createLazyRuntimeModule(async () => (await import("./.setup/speech-provider-DRZqI7O9.mjs")).buildXaiSpeechProvider()); createLazyRuntimeModule(async () => (await import("./.setup/video-generation-provider-NM0m1TQY.mjs")).buildXaiVideoGenerationProvider()); function createPendingTranscriptionAudioQueue() { let chunks = []; let head = 0; let bytes = 0; const clear = () => { chunks = []; head = 0; bytes = 0; }; return { clear, drain: () => { const pending = chunks.slice(head).filter((chunk) => chunk !== void 0); clear(); return pending; }, enqueue: (audio) => { if (audio.byteLength > MAX_LAZY_REALTIME_TRANSCRIPTION_AUDIO_BYTES) return; const chunk = Buffer.from(audio); chunks.push(chunk); bytes += chunk.byteLength; while (bytes > MAX_LAZY_REALTIME_TRANSCRIPTION_AUDIO_BYTES && head < chunks.length) { const dropped = chunks[head]; chunks[head] = void 0; head += 1; bytes -= dropped?.byteLength ?? 0; } if (head > 256 && head * 2 >= chunks.length) { chunks = chunks.slice(head); head = 0; } } }; } function createLazyXaiRealtimeTranscriptionSession(req, loadXaiRealtimeTranscriptionProvider) { let session; let sessionPromise; let activeConnect; let generation = 0; let closedSessionGeneration; let closed = false; let acceptsInput = false; const pendingAudio = createPendingTranscriptionAudioQueue(); const closeSession = (closeGeneration, loadedSession = session) => { if (!loadedSession || closedSessionGeneration === closeGeneration) return; closedSessionGeneration = closeGeneration; loadedSession.close(); }; const loadSession = async () => { if (!sessionPromise) sessionPromise = loadXaiRealtimeTranscriptionProvider().then((provider) => provider.createSession(req)); session = await sessionPromise; return session; }; return { connect: async () => { if (closed) { generation += 1; closed = false; } const connectGeneration = generation; if (activeConnect?.generation === connectGeneration) { await activeConnect.promise; return; } const promise = (async () => { const loadedSession = await loadSession(); if (connectGeneration !== generation || closed) { if (connectGeneration === generation && closed) closeSession(connectGeneration, loadedSession); return; } const providerConnect = loadedSession.connect(); for (const audio of pendingAudio.drain()) loadedSession.sendAudio(audio); acceptsInput = true; await providerConnect; if (connectGeneration === generation && closed) closeSession(connectGeneration, loadedSession); })(); const connectTask = { generation: connectGeneration, promise }; activeConnect = connectTask; try { await promise; } finally { if (activeConnect === connectTask) activeConnect = void 0; } }, sendAudio: (audio) => { if (closed) return; if (acceptsInput && session) { session.sendAudio(audio); return; } pendingAudio.enqueue(audio); }, close: () => { if (closed) return; closed = true; acceptsInput = false; pendingAudio.clear(); closeSession(generation); }, isConnected: () => !closed && (session?.isConnected() ?? false) }; } function createLazyXaiSpeechProvider(context) { return { ...createXaiSpeechProviderMetadata(context), listVoices: async (req) => (await loadXaiSpeechProvider()).listVoices(req), synthesize: async (req) => await (await loadXaiSpeechProvider()).synthesize(req), streamSynthesize: async (req) => (await loadXaiSpeechProvider()).streamSynthesize(req), synthesizeTelephony: async (req) => (await loadXaiSpeechProvider()).synthesizeTelephony(req) }; } function createLazyXaiRealtimeTranscriptionProvider(context) { const loadProvider = createLazyRuntimeModule(async () => (await import("./.setup/realtime-transcription-provider-factory-Cyzp_3pD.mjs")).buildXaiRealtimeTranscriptionProvider(context)); return { ...createXaiRealtimeTranscriptionProviderMetadata(context), createSession: (req) => { normalizeXaiRealtimeTranscriptionProviderConfig(req.providerConfig); return createLazyXaiRealtimeTranscriptionSession(req, loadProvider); } }; } function createLazyXaiRealtimeVoiceProvider(context) { return { ...createXaiRealtimeVoiceProviderMetadata(context), createBridge: createLazyXaiRealtimeVoiceBridge }; } //#endregion //#region extensions/xai/capability-catalog.ts const catalog = (context) => ({ speechProviders: [createLazyXaiSpeechProvider(context)], realtimeTranscriptionProviders: [createLazyXaiRealtimeTranscriptionProvider(context)], realtimeVoiceProviders: [createLazyXaiRealtimeVoiceProvider(context)] }); //#endregion export { catalog as default };