openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
151 lines (150 loc) • 5.38 kB
JavaScript
import { d as toStringifiedError } from "./error-coercion-1P2One0X.mjs";
import "./backoff-DG9ukw7j.mjs";
import { t as RealtimeVoiceSessionLifecycle } from "./realtime-session-lifecycle-B-hGG-bm.mjs";
import "./audio-energy-fMmNST7n.mjs";
//#region src/talk/realtime-voice-lazy-lifecycle.ts
/** Owns a lazy bridge generation while provider-local queues own input admission and ordering. */
function createLazyRealtimeVoiceBridgeLifecycle(params) {
const request = params.request;
const { getPlaybackState, handleDelegationInput, runAgentConsult } = request;
const lifecycle = new RealtimeVoiceSessionLifecycle(`${params.label} lazy`);
const disposedBridges = /* @__PURE__ */ new WeakMap();
let loaded;
let closePromise;
let closing;
const dispose = (state, bridge) => {
if (disposedBridges.has(bridge)) return disposedBridges.get(bridge);
disposedBridges.set(bridge, void 0);
const completion = state.closeOptions ? bridge.close(state.closeOptions) : bridge.close();
disposedBridges.set(bridge, completion);
return completion;
};
const notifyTerminal = (connection, outcome) => {
if (closing?.connection === connection && lifecycle.isCurrent(connection)) {
if (outcome === "error") closing.outcome = outcome;
return;
}
const reason = lifecycle.close(connection, outcome);
if (reason) {
params.clearPending();
request.onClose?.(reason);
}
};
const close = (outcome, primaryError, options) => {
const connection = lifecycle.currentConnection();
if (!(outcome === "error" && connection ? lifecycle.failure(connection) : lifecycle.cancel())) return closePromise;
const state = loaded;
if (state) state.closeOptions = options;
const owner = {
connection,
outcome
};
closing = owner;
params.clearPending();
const finish = (reason = owner.outcome) => {
if (closing === owner) {
closing = void 0;
if (outcome === "error") try {
request.onError?.(toStringifiedError(primaryError));
} catch {}
}
if (connection ? lifecycle.close(connection, reason) : !lifecycle.currentConnection()) request.onClose?.(reason);
};
const fail = (error) => {
try {
finish("error");
} catch {}
throw error;
};
let pending;
try {
pending = state?.bridge ? dispose(state, state.bridge) : state && state.connection === connection ? state.promise.then((bridge) => dispose(state, bridge)) : void 0;
} catch (error) {
return fail(error);
}
if (pending) {
const completion = pending.then(() => finish(), fail);
if (closing === owner) closePromise = completion;
return completion;
}
finish();
};
const guardRequest = (connection) => {
const isCurrent = () => lifecycle.acceptsEvents(connection);
const guard = (callback) => (...args) => {
if (isCurrent()) callback(...args);
};
return {
...request,
onAudio: guard(request.onAudio),
onClearAudio: guard(request.onClearAudio),
...request.onMark ? { onMark: guard(request.onMark) } : {},
...request.onEvent ? { onEvent: guard(request.onEvent) } : {},
...request.onResponseDone ? { onResponseDone: guard(request.onResponseDone) } : {},
...request.onToolCall ? { onToolCall: guard(request.onToolCall) } : {},
...request.onError ? { onError: guard(request.onError) } : {},
...getPlaybackState ? { getPlaybackState: () => {
if (!isCurrent()) return [];
const playback = getPlaybackState();
return isCurrent() ? playback : [];
} } : {},
...handleDelegationInput ? { handleDelegationInput: (text, respond) => {
if (!isCurrent()) return "control";
return handleDelegationInput(text, (message) => {
if (isCurrent()) respond(message);
});
} } : {},
...runAgentConsult ? { runAgentConsult: (input) => isCurrent() ? runAgentConsult(input) : Promise.reject(/* @__PURE__ */ new Error(`${params.label} realtime voice session closed`)) } : {},
...request.onTranscript ? { onTranscript: (...args) => {
const isFinal = args[2];
if (isCurrent() || isFinal && closing?.connection === connection && lifecycle.isCurrent(connection)) request.onTranscript?.(...args);
} } : {},
onReady: () => {
if (isCurrent()) {
request.onReady?.();
if (isCurrent()) params.onProviderReady?.(loaded?.bridge);
}
},
onClose: (reason) => notifyTerminal(connection, reason)
};
};
return {
get bridge() {
return loaded?.bridge;
},
isActive: () => lifecycle.phase() !== "terminal",
connect: () => lifecycle.connect(async (connection) => {
closePromise = void 0;
closing = void 0;
const state = {
connection,
promise: Promise.resolve().then(() => params.load(guardRequest(connection)))
};
loaded = state;
const bridge = await state.promise;
const isCurrent = () => lifecycle.acceptsEvents(connection);
if (!isCurrent()) {
await dispose(state, bridge);
return;
}
state.bridge = bridge;
try {
await bridge.connect();
if (isCurrent()) {
await params.onConnected?.(bridge, isCurrent);
lifecycle.ready(connection);
}
} catch (error) {
try {
if (isCurrent()) await close("error", error);
else await dispose(state, bridge);
} catch {}
throw error;
}
if (!isCurrent()) await dispose(state, bridge);
}),
close: (options) => close("completed", void 0, options)
};
}
//#endregion
export { createLazyRealtimeVoiceBridgeLifecycle as t };