openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
443 lines (442 loc) • 16.9 kB
JavaScript
import { r as createLazyRuntimeModule } from "./lazy-runtime-CgCh8H_K.js";
import { n as createRealtimeVoiceAudioQueue, t as RealtimeVoiceSessionLifecycle } from "./realtime-session-lifecycle-B2wf87mH.js";
import "./realtime-voice-provider-CoGArOTN.js";
import { y as serializeXaiRealtimeToolResult } from "./realtime-voice-config-BzEI0RLc.js";
import { c as createXaiImageGenerationProviderMetadata, d as createXaiRealtimeVoiceProviderMetadata, f as createXaiVideoGenerationProviderMetadata, l as createXaiMediaUnderstandingProviderMetadata, m as normalizeXaiRealtimeTranscriptionProviderConfig, s as assertXaiRealtimeVoiceRequestSupported, u as createXaiRealtimeTranscriptionProviderMetadata } from "./capability-provider-metadata-factory-BjaSolS_.js";
import { n as createXaiSpeechProviderMetadata } from "./speech-provider-metadata-factory-B1Lvk3rT.js";
//#region extensions/xai/lazy-capability-provider-factories.ts
const MAX_LAZY_REALTIME_TRANSCRIPTION_AUDIO_BYTES = 2097152;
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 loadXaiImageGenerationProvider = createLazyRuntimeModule(async () => (await import("./extensions/xai/image-generation-provider.js")).buildXaiImageGenerationProvider());
const loadXaiMediaUnderstandingProvider = createLazyRuntimeModule(async () => (await import("./extensions/xai/stt.js")).buildXaiMediaUnderstandingProvider());
const loadXaiRealtimeVoiceProvider = createLazyRuntimeModule(async () => (await import("./extensions/xai/realtime-voice-provider.js")).buildXaiRealtimeVoiceProvider());
const loadXaiSpeechProvider = createLazyRuntimeModule(async () => (await import("./extensions/xai/speech-provider.js")).buildXaiSpeechProvider());
const loadXaiVideoGenerationProvider = createLazyRuntimeModule(async () => (await import("./extensions/xai/video-generation-provider.js")).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 createLazyXaiRealtimeVoiceBridge(req) {
assertXaiRealtimeVoiceRequestSupported(req);
const getPlaybackState = req.getPlaybackState;
let bridge;
let bridgeState;
let acceptsInput = false;
const lifecycle = new RealtimeVoiceSessionLifecycle("xAI lazy");
let pendingMediaTimestamp;
let pendingGreeting;
let pendingUserMessageCount = 0;
let pendingUserMessageBytes = 0;
let pendingToolResultCount = 0;
let pendingToolResultBytes = 0;
const closedBridges = /* @__PURE__ */ new WeakSet();
const pendingAudio = createRealtimeVoiceAudioQueue("reject-newest");
const pendingOperations = [];
const clearPendingInput = () => {
pendingAudio.clear();
pendingOperations.length = 0;
pendingMediaTimestamp = void 0;
pendingGreeting = void 0;
pendingUserMessageCount = 0;
pendingUserMessageBytes = 0;
pendingToolResultCount = 0;
pendingToolResultBytes = 0;
};
const emitTerminal = (connection, outcome) => {
const terminalOutcome = lifecycle.close(connection, outcome);
if (!terminalOutcome) return;
acceptsInput = false;
clearPendingInput();
req.onClose?.(terminalOutcome);
};
const closeBridge = (loadedBridge = bridge) => {
if (!loadedBridge || closedBridges.has(loadedBridge)) return;
closedBridges.add(loadedBridge);
loadedBridge.close();
};
const throwTerminalBridgeError = (connection, loadedBridge, primaryError) => {
if (lifecycle.failure(connection)) {
try {
req.onError?.(primaryError instanceof Error ? primaryError : new Error(String(primaryError)));
} catch {}
try {
emitTerminal(connection, "error");
} catch {}
}
try {
closeBridge(loadedBridge);
} catch {}
throw primaryError;
};
const acceptsProviderCallback = (connection) => lifecycle.acceptsEvents(connection);
const guardProviderCallback = (connection, callback) => {
return (...args) => {
if (acceptsProviderCallback(connection)) callback(...args);
};
};
const loadBridge = async (connection) => {
const existingState = bridgeState;
const state = existingState?.connection.id === connection.id ? existingState : {
connection,
promise: loadXaiRealtimeVoiceProvider().then((provider) => provider.createBridge({
...req,
onAudio: guardProviderCallback(connection, req.onAudio),
...getPlaybackState ? { getPlaybackState: () => {
if (!acceptsProviderCallback(connection)) return [];
const playback = getPlaybackState();
return acceptsProviderCallback(connection) ? playback : [];
} } : {},
onClearAudio: guardProviderCallback(connection, req.onClearAudio),
...req.onMark ? { onMark: guardProviderCallback(connection, req.onMark) } : {},
...req.onTranscript ? { onTranscript: guardProviderCallback(connection, req.onTranscript) } : {},
...req.onEvent ? { onEvent: guardProviderCallback(connection, req.onEvent) } : {},
...req.onResponseDone ? { onResponseDone: guardProviderCallback(connection, req.onResponseDone) } : {},
...req.onToolCall ? { onToolCall: guardProviderCallback(connection, req.onToolCall) } : {},
...req.onReady ? { onReady: guardProviderCallback(connection, req.onReady) } : {},
...req.onError ? { onError: guardProviderCallback(connection, req.onError) } : {},
onClose: (outcome) => emitTerminal(connection, outcome)
}))
};
if (state !== existingState) bridgeState = state;
const loadedBridge = await state.promise;
if (bridgeState === state && lifecycle.isCurrent(connection)) bridge = loadedBridge;
return loadedBridge;
};
const replacePendingOperation = (previous, next) => {
if (previous) {
const previousIndex = pendingOperations.indexOf(previous);
if (previousIndex >= 0) pendingOperations.splice(previousIndex, 1);
}
pendingOperations.push(next);
return next;
};
const acceptsCurrentInput = () => lifecycle.phase() !== "terminal";
const flushPendingInput = async (loadedBridge, connection) => {
if (!lifecycle.acceptsEvents(connection)) return;
while (true) {
if (!lifecycle.acceptsEvents(connection)) return;
const operation = pendingOperations.shift();
if (!operation) {
acceptsInput = lifecycle.ready(connection);
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 (!lifecycle.acceptsEvents(connection)) 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 bridge?.supportsToolResultContinuation ?? false;
},
connect: () => lifecycle.connect(async (connection) => {
acceptsInput = false;
bridge = void 0;
const loadedBridge = await loadBridge(connection);
if (!lifecycle.acceptsEvents(connection)) {
closeBridge(loadedBridge);
return;
}
try {
await loadedBridge.connect();
} catch (error) {
throwTerminalBridgeError(connection, loadedBridge, error);
}
if (!lifecycle.acceptsEvents(connection)) {
closeBridge(loadedBridge);
return;
}
try {
await flushPendingInput(loadedBridge, connection);
} catch (error) {
throwTerminalBridgeError(connection, loadedBridge, error);
}
if (!lifecycle.acceptsEvents(connection)) closeBridge(loadedBridge);
}),
sendAudio: (audio) => {
if (!acceptsCurrentInput()) return;
if (acceptsInput && bridge) {
bridge.sendAudio(audio);
return;
}
if (pendingAudio.enqueue(audio)) pendingOperations.push({ type: "audio" });
},
setMediaTimestamp: (timestamp) => {
if (!acceptsCurrentInput()) return;
if (acceptsInput && bridge) {
bridge.setMediaTimestamp(timestamp);
return;
}
pendingMediaTimestamp = replacePendingOperation(pendingMediaTimestamp, {
timestamp,
type: "media-timestamp"
});
},
sendUserMessage: (text) => {
if (!acceptsCurrentInput()) return;
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 (!acceptsCurrentInput()) return;
if (acceptsInput && bridge) {
bridge.triggerGreeting?.(instructions);
return;
}
pendingGreeting = replacePendingOperation(pendingGreeting, {
instructions,
type: "greeting"
});
},
handleBargeIn: (options) => {
if (acceptsCurrentInput()) bridge?.handleBargeIn?.(options);
},
submitToolResult: (callId, result, options) => {
if (!acceptsCurrentInput() || options?.willContinue === true) return;
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 (acceptsCurrentInput()) bridge?.acknowledgeMark(markName);
},
close: () => {
const connection = lifecycle.currentConnection();
if (!lifecycle.cancel()) return;
acceptsInput = false;
clearPendingInput();
closeBridge();
if (connection) emitTerminal(connection, "completed");
else req.onClose?.("completed");
},
isConnected: () => acceptsCurrentInput() && (bridge?.isConnected() ?? false)
};
}
function createLazyXaiImageGenerationProvider() {
return {
...createXaiImageGenerationProviderMetadata(),
generateImage: async (req) => (await loadXaiImageGenerationProvider()).generateImage(req)
};
}
function createLazyXaiMediaUnderstandingProvider() {
return {
...createXaiMediaUnderstandingProviderMetadata(),
transcribeAudio: async (req) => {
const provider = await loadXaiMediaUnderstandingProvider();
if (!provider.transcribeAudio) throw new Error("xAI media understanding provider missing transcribeAudio");
return await provider.transcribeAudio(req);
}
};
}
function createLazyXaiVideoGenerationProvider(context) {
return {
...createXaiVideoGenerationProviderMetadata(context),
generateVideo: async (req) => (await loadXaiVideoGenerationProvider()).generateVideo(req)
};
}
function createLazyXaiSpeechProvider(context) {
return {
...createXaiSpeechProviderMetadata(context),
listVoices: async (req) => {
const provider = await loadXaiSpeechProvider();
if (!provider.listVoices) throw new Error("xAI speech provider missing listVoices");
return await provider.listVoices(req);
},
synthesize: async (req) => await (await loadXaiSpeechProvider()).synthesize(req),
streamSynthesize: async (req) => {
const provider = await loadXaiSpeechProvider();
if (!provider.streamSynthesize) throw new Error("xAI speech provider missing streamSynthesize");
return await provider.streamSynthesize(req);
},
synthesizeTelephony: async (req) => {
const provider = await loadXaiSpeechProvider();
if (!provider.synthesizeTelephony) throw new Error("xAI speech provider missing synthesizeTelephony");
return await provider.synthesizeTelephony(req);
}
};
}
function createLazyXaiRealtimeTranscriptionProvider(context) {
const loadProvider = createLazyRuntimeModule(async () => (await import("./extensions/xai/realtime-transcription-provider-factory.js")).buildXaiRealtimeTranscriptionProvider(context));
return {
...createXaiRealtimeTranscriptionProviderMetadata(context),
createSession: (req) => {
normalizeXaiRealtimeTranscriptionProviderConfig(req.providerConfig);
return createLazyXaiRealtimeTranscriptionSession(req, loadProvider);
}
};
}
function createLazyXaiRealtimeVoiceProvider(context) {
return {
...createXaiRealtimeVoiceProviderMetadata(context),
createBridge: createLazyXaiRealtimeVoiceBridge
};
}
//#endregion
export { createLazyXaiSpeechProvider as a, createLazyXaiRealtimeVoiceProvider as i, createLazyXaiMediaUnderstandingProvider as n, createLazyXaiVideoGenerationProvider as o, createLazyXaiRealtimeTranscriptionProvider as r, createLazyXaiImageGenerationProvider as t };