openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
212 lines (211 loc) • 7.93 kB
JavaScript
import { i as extractErrorCode, l as toErrorObject, s as readErrorName } from "./error-coercion-D_-xJ90S.js";
import { n as rawDataToString } from "./ws-C3ckvj65.js";
import { o as buildRealtimeVoiceAgentControlSpeechMessage } from "./agent-run-control-shared-zVaKVJuy.js";
import "./realtime-voice-provider-CoGArOTN.js";
import { r as buildOpenAIQuicksilverDelegationPrompt } from "./realtime-quicksilver-instructions-kTl3roXd.js";
import { t as parseOpenAIQuicksilverEvent } from "./realtime-quicksilver-events-CbzMdwSI.js";
import { n as boundOpenAIQuicksilverDelegationResult, s as chunkOpenAIQuicksilverAppendText, t as boundOpenAIQuicksilverContextItems } from "./realtime-quicksilver-wire-0W0-YPkD.js";
//#region extensions/openai/realtime-quicksilver-delegation-controller.ts
const WEBSOCKET_OPEN = 1;
const CONSULT_FAILURE_TEXT = "The agent task failed. Tell the user it did not complete and offer to try again.";
function shortFailureReason(error, formatErrorMessage) {
return formatErrorMessage(error).replaceAll(/\s+/g, " ").trim().slice(0, 180) || "unknown error";
}
function readWireEventType(payload) {
try {
const decoded = JSON.parse(payload);
return typeof decoded.type === "string" ? decoded.type : void 0;
} catch {
return;
}
}
/** Owns the provider's single active delegation and its once-consumed transcript context. */
var OpenAIQuicksilverDelegationController = class {
constructor(options, formatErrorMessage) {
this.options = options;
this.formatErrorMessage = formatErrorMessage;
this.onSessionAbort = () => {
const reason = this.options.signal.reason;
this.stop(reason instanceof Error ? reason : /* @__PURE__ */ new Error("GPT-Live session stopped"));
};
this.stopped = false;
this.transcript = [];
if (options.signal.aborted) this.onSessionAbort();
else options.signal.addEventListener("abort", this.onSessionAbort, { once: true });
}
handleFrame(data, isBinary) {
if (this.stopped) return;
if (isBinary) {
this.fail(/* @__PURE__ */ new Error("OpenAI GPT-Live sideband returned an unexpected binary frame"));
return;
}
const payload = rawDataToString(data);
if (this.options.onWireEventType) {
const eventType = readWireEventType(payload);
if (eventType) this.options.onWireEventType(eventType);
}
const event = parseOpenAIQuicksilverEvent(payload);
if (event) this.handleEvent(event);
}
handleEvent(event) {
if (this.stopped || event.kind === "ignored") return;
if (event.kind === "unknown") {
this.options.logger.debug?.(`OpenAI GPT-Live ignored sideband event: ${event.eventType}`);
return;
}
if (event.kind === "session-started") {
this.options.onSessionStarted?.(event.expiresAt);
return;
}
if (event.kind === "transcript-delta" || event.kind === "transcript-done") {
this.appendTranscript(event);
this.options.onTranscript?.(event.role, event.text, event.kind === "transcript-done");
return;
}
if (event.kind === "error") {
const error = /* @__PURE__ */ new Error(`OpenAI GPT-Live sideband error: ${event.message}`);
this.options.logger.warn(error.message);
if (event.fatalAuth) this.options.onFatalError(error);
else this.options.onError?.(error);
return;
}
if (event.kind === "audio") return;
this.startDelegation(event.id, event.prompt);
}
sendSessionContext(text, channel) {
const content = text.trim();
if (content) this.sendAppend({ type: "session.context.append" }, content, channel);
}
stop(reason) {
if (this.stopped) return;
this.markStopped();
this.consultController?.abort(reason);
this.consultController = void 0;
}
/** Releases sideband ownership without canceling work already accepted by the host. */
detach() {
if (this.stopped) return;
this.markStopped();
}
appendTranscript(event) {
const last = this.transcript.at(-1);
if (event.kind === "transcript-delta") {
if (last?.role === event.role && this.partialTranscriptRole === event.role) last.text += event.text;
else this.transcript.push({
role: event.role,
text: event.text
});
this.partialTranscriptRole = event.role;
} else {
if (last?.role === event.role && this.partialTranscriptRole === event.role) last.text = event.text;
else this.transcript.push({
role: event.role,
text: event.text
});
this.partialTranscriptRole = void 0;
}
this.transcript = boundOpenAIQuicksilverContextItems(this.transcript);
}
startDelegation(id, input) {
if (this.stopped || this.options.signal.aborted || !input.trim()) return;
const handleInput = this.options.handleDelegationInput;
if (handleInput) {
const socket = this.options.getSocket();
let responded = false;
const respond = (message) => {
if (responded) return;
responded = true;
if (!socket || socket !== this.options.getSocket()) return;
try {
this.sendAppend({
type: "delegation.context.append",
delegation_item_id: id
}, message, "speakable", socket);
} catch (error) {
this.fail(toErrorObject(error, "OpenAI GPT-Live control response failed"));
}
};
try {
if (handleInput(input, respond) === "control") return;
} catch (error) {
this.fail(toErrorObject(error, "OpenAI GPT-Live control admission failed"));
return;
}
}
const transcript = this.transcript;
this.transcript = [];
this.partialTranscriptRole = void 0;
const delegation = {
id,
prompt: buildOpenAIQuicksilverDelegationPrompt({
input,
transcript
})
};
if (this.consultController) {
this.pendingDelegation = delegation;
this.consultController.abort(/* @__PURE__ */ new Error("GPT-Live delegation superseded"));
return;
}
this.launchDelegation(delegation);
}
launchDelegation(delegation) {
if (this.stopped || this.options.signal.aborted) return;
const controller = new AbortController();
this.consultController = controller;
this.runDelegation(delegation, controller.signal).catch((error) => this.fail(toErrorObject(error, "OpenAI GPT-Live delegation failed"))).finally(() => {
if (this.consultController !== controller) return;
this.consultController = void 0;
const pending = this.pendingDelegation;
this.pendingDelegation = void 0;
if (pending) this.launchDelegation(pending);
});
}
markStopped() {
this.stopped = true;
this.options.signal.removeEventListener("abort", this.onSessionAbort);
this.pendingDelegation = void 0;
this.partialTranscriptRole = void 0;
this.transcript = [];
}
async runDelegation(delegation, signal) {
let text;
try {
if (this.options.handleDelegationInput) this.sendSessionContext(buildRealtimeVoiceAgentControlSpeechMessage("I’ll check that request."), "speakable");
const result = await this.options.runAgentConsult({
prompt: delegation.prompt,
signal
});
if (signal.aborted) return;
text = boundOpenAIQuicksilverDelegationResult(result.text);
} catch (error) {
if (signal.aborted || readErrorName(error) === "AbortError" || extractErrorCode(error) === "ABORT_ERR") return;
this.options.logger.warn(`OpenAI GPT-Live delegation consult failed: ${shortFailureReason(error, this.formatErrorMessage)}`);
text = CONSULT_FAILURE_TEXT;
}
this.sendAppend({
type: "delegation.context.append",
delegation_item_id: delegation.id
}, text, "speakable");
}
sendAppend(target, text, channel, socket = this.options.getSocket()) {
for (const chunk of chunkOpenAIQuicksilverAppendText(text)) {
if (this.stopped || this.options.signal.aborted || !socket || socket !== this.options.getSocket() || socket.readyState !== WEBSOCKET_OPEN) return;
socket.send(JSON.stringify({
...target,
channel,
content: [{
type: "input_text",
text: chunk
}]
}));
}
}
fail(error) {
if (this.stopped) return;
this.options.logger.warn(error.message);
this.options.onFatalError(error);
}
};
//#endregion
export { OpenAIQuicksilverDelegationController as t };