UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

364 lines (363 loc) • 14.8 kB
import { c as readErrorName, i as extractErrorCode, u as toErrorObject } from "./error-coercion-1P2One0X.mjs"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import { t as canonicalizeBase64 } from "./base64-xteYEv8l.mjs"; import { n as rawDataToString } from "./ws-BdD3UP1C.mjs"; import { o as buildRealtimeVoiceAgentControlSpeechMessage } from "./agent-run-control-shared-Cc6QkFgv.mjs"; import "./realtime-voice-provider-D_Fp61Jy.mjs"; import { r as isOpenAIGptLiveApiModel } from "./realtime-quicksilver-0VQEPDSP.mjs"; import { t as OpenAILiveDelegationQueue } from "./realtime-live-delegation-queue-uq4IZI5i.mjs"; import { r as buildOpenAIQuicksilverDelegationPrompt } from "./realtime-quicksilver-instructions-CkYwSLJz.mjs"; import { t as parseOpenAIQuicksilverEvent } from "./realtime-quicksilver-events-BptwDZ_Q.mjs"; import { n as buildOpenAIQuicksilverContextAppend } from "./realtime-quicksilver-protocol-vamHcsOE.mjs"; import { o as projectOpenAIQuicksilverErrorMessage } from "./realtime-quicksilver-redaction-Cfc2vSbu.mjs"; import { c as chunkOpenAIQuicksilverAppendText, r as boundOpenAIQuicksilverDelegationResult } from "./realtime-quicksilver-wire-DVntsqk5.mjs"; import { t as OpenAIQuicksilverTranscript } from "./realtime-quicksilver-transcript-BHoYGF3g.mjs"; //#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 projectWireEventType(event, model) { const live = isOpenAIGptLiveApiModel(model); switch (event.kind) { case "session-started": return "session.started"; case "session-closed": return "session.closed"; case "audio-cleared": return "output_audio_buffer.cleared"; case "audio": return live ? "session.output_audio.delta" : "output_audio.delta"; case "transcript-delta": return live ? `session.${event.role === "user" ? "input" : "output"}_transcript.delta` : event.role === "user" ? "input_transcript.added" : "output_transcript.added"; case "transcript-done": return "turn.done"; case "delegation": return live ? "session.delegation.created" : "delegation.created"; case "error": return "error"; case "ignored": return event.eventType === "session.updated" ? "session.updated" : void 0; case "unknown": 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.delegationGeneration = 0; 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 = new OpenAIQuicksilverTranscript(); if (isOpenAIGptLiveApiModel(options.model)) this.publicDelegations = new OpenAILiveDelegationQueue({ isActive: () => !this.stopped && !this.drainDisposition && !options.signal.aborted, readInput: () => this.transcript.latestUserInput(), dispatch: (id, input) => this.startDelegation(id, input), onExpired: (id) => { this.sendAppend("Ask the user to repeat their request; no user transcript was received.", "speakable", id); }, onError: (error) => this.fail(error) }); this.completionClaimsAdopted = options.runAgentConsult.adoptCompletionClaims !== void 0; options.runAgentConsult.adoptCompletionClaims?.(); 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); const event = parseOpenAIQuicksilverEvent(payload, this.options.model); if (event) { if (event.kind === "session-closed") { this.handleEvent(event); return; } const eventType = this.drainDisposition ? void 0 : projectWireEventType(event, this.options.model); if (eventType) this.options.onWireEventType?.(eventType); this.handleEvent(event); } } handleEvent(event) { if (this.stopped || event.kind === "ignored" || event.kind === "audio-cleared") return; if (this.drainDisposition && event.kind !== "transcript-delta" && event.kind !== "transcript-done" && event.kind !== "session-closed") return; if (event.kind === "unknown") { this.options.logger.debug?.("OpenAI GPT-Live ignored an unsupported sideband event"); return; } if (event.kind === "session-started") { this.options.onSessionStarted?.(event.expiresAt); return; } if (event.kind === "session-closed") { try { this.options.onSessionClosed?.(event.reason); } finally { if (this.drainDisposition === "detach") this.detach(); else this.stop(/* @__PURE__ */ new Error("GPT-Live session closed")); } return; } if (event.kind === "transcript-delta" || event.kind === "transcript-done") { if (isOpenAIGptLiveApiModel(this.options.model)) { this.transcript.appendPublic(event, { onTranscript: this.options.onTranscript, canAppend: () => !this.stopped }); this.publicDelegations?.resume(); } else { this.transcript.append(event); this.options.onTranscript?.(event.role, event.text, event.kind === "transcript-done"); } return; } if (event.kind === "error") { const error = new Error(projectOpenAIQuicksilverErrorMessage("provider")); this.options.logger.warn(error.message); if (event.fatalAuth) this.options.onFatalError(error); else this.options.onError?.(error); return; } if (event.kind === "audio") { if (!this.options.onAudio) return; const audio = canonicalizeBase64(event.data); if (!audio) { this.fail(/* @__PURE__ */ new Error("OpenAI GPT-Live returned malformed base64 audio")); return; } this.options.onAudio(Buffer.from(audio, "base64")); return; } if (this.publicDelegations) this.publicDelegations.enqueue(event.id); else { const input = event.prompt ?? this.transcript.latestUserInput(); if (event.prompt === void 0 && !input.trim()) { this.sendAppend("Ask the user to repeat their request; no user transcript was received.", "speakable", event.id); return; } this.startDelegation(event.id, input); } } sendSessionContext(text, channel) { const content = text.trim(); if (content) this.sendAppend(content, channel); } beginTranscriptDrain(disposition) { if (this.stopped || this.drainDisposition) return; this.drainDisposition = disposition; this.publicDelegations?.stop(); this.revokeRequesterFinal(); this.pendingDelegation = void 0; if (disposition === "abort") this.consultController?.abort(/* @__PURE__ */ new Error("GPT-Live session closing")); } stop(reason) { if (this.stopped) return; this.publicDelegations?.stop(); this.flushTranscript(); 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.publicDelegations?.stop(); this.flushTranscript(); this.markStopped(); } flushTranscript() { if (isOpenAIGptLiveApiModel(this.options.model)) this.consumeTranscript(); } consumeTranscript() { const snapshot = this.transcript.consume(); this.transcript.publish(snapshot.publication, { onTranscript: this.options.onTranscript }); return snapshot.context; } startDelegation(id, input) { if (this.stopped || this.drainDisposition || 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(message, "speakable", id, socket); } catch (error) { this.fail(toErrorObject(error, "OpenAI GPT-Live control response failed")); } }; try { if (handleInput(input, respond) === "control") { this.transcript.clearPendingUserInput(); return; } } catch (error) { this.fail(toErrorObject(error, "OpenAI GPT-Live control admission failed")); return; } } if (this.stopped || this.drainDisposition || this.options.signal.aborted) return; const transcript = this.consumeTranscript(); if (this.stopped || this.drainDisposition || this.options.signal.aborted) return; const delegation = { id, prompt: buildOpenAIQuicksilverDelegationPrompt({ input, transcript }) }; if (this.consultController) { this.pendingDelegation = delegation; const runner = this.options.runAgentConsult; if (runner.steer) this.schedulePendingSteering(this.consultController, runner.steer); else { this.revokeRequesterFinal(); this.consultController.abort(/* @__PURE__ */ new Error("Realtime delegation superseded")); } return; } this.launchDelegation(delegation); } launchDelegation(delegation) { if (this.stopped || this.options.signal.aborted) return; const controller = new AbortController(); const generation = ++this.delegationGeneration; this.consultController = controller; this.activeDelegationId = delegation.id; this.requesterFinalOwner = { delegationId: delegation.id, generation }; this.runDelegation(delegation, generation, controller.signal).catch((error) => this.fail(toErrorObject(error, "OpenAI GPT-Live delegation failed"))).finally(() => { if (this.consultController !== controller) return; this.consultController = void 0; this.activeDelegationId = void 0; const pending = this.pendingDelegation; this.pendingDelegation = void 0; if (pending) this.launchDelegation(pending); }); } schedulePendingSteering(controller, steer) { if (this.steeringPromise) return; const completion = (async () => { await Promise.resolve(); while (!this.stopped && !controller.signal.aborted && this.consultController === controller) { const delegation = this.pendingDelegation; this.pendingDelegation = void 0; if (!delegation) return; try { await steer({ prompt: delegation.prompt, signal: controller.signal }); } catch (error) { this.revokeRequesterFinal(); if (this.stopped || controller.signal.aborted || readErrorName(error) === "AbortError" || extractErrorCode(error) === "ABORT_ERR") return; const fatal = toErrorObject(error, "Realtime delegation steering failed"); this.pendingDelegation = void 0; controller.abort(fatal); this.fail(fatal); return; } if (this.stopped || controller.signal.aborted || this.consultController !== controller) return; this.activeDelegationId = delegation.id; const requesterFinalOwner = this.requesterFinalOwner; if (requesterFinalOwner) this.requesterFinalOwner = { delegationId: delegation.id, generation: requesterFinalOwner.generation }; } })().finally(() => { if (this.steeringPromise === completion) this.steeringPromise = void 0; if (this.pendingDelegation && !this.stopped) this.schedulePendingSteering(controller, steer); }); this.steeringPromise = completion; } markStopped() { this.stopped = true; this.revokeRequesterFinal(); this.options.signal.removeEventListener("abort", this.onSessionAbort); this.pendingDelegation = void 0; this.transcript.clear(); } async runDelegation(delegation, generation, signal) { let text; let failed = false; const runner = this.options.runAgentConsult; try { if (this.options.handleDelegationInput) this.sendSessionContext(buildRealtimeVoiceAgentControlSpeechMessage("I’ll check that request."), "speakable"); const result = await runner({ prompt: delegation.prompt, signal, requesterFinal: { append: (finalText) => this.appendRequesterFinal(generation, finalText) } }); if (signal.aborted) { runner.claimAppend?.(); this.revokeRequesterFinal(); return; } text = boundOpenAIQuicksilverDelegationResult(result.text); } catch (error) { if (signal.aborted || readErrorName(error) === "AbortError" || extractErrorCode(error) === "ABORT_ERR") { runner.claimAppend?.(); this.revokeRequesterFinal(); return; } const reason = this.formatErrorMessage(error).replaceAll(/\s+/g, " ").trim(); this.options.logger.warn(`OpenAI GPT-Live delegation consult failed: ${truncateUtf16Safe(reason, 180) || "unknown error"}`); failed = true; text = CONSULT_FAILURE_TEXT; } while (this.steeringPromise) await this.steeringPromise; if (signal.aborted || this.stopped) { runner.claimAppend?.(); this.revokeRequesterFinal(); return; } const claim = failed ? runner.claimFailureAppend : runner.claimAppend; if (claim) { if (!claim()) { this.revokeRequesterFinal(); return; } } else if (this.completionClaimsAdopted) { this.revokeRequesterFinal(); this.fail(/* @__PURE__ */ new Error(failed ? "Realtime delegation failure ownership is unavailable" : "Realtime delegation completion ownership is unavailable")); return; } const delegationId = this.activeDelegationId; if (!delegationId) { this.revokeRequesterFinal(); return; } if (!this.sendAppend(text, "speakable", delegationId)) this.revokeRequesterFinal(); } appendRequesterFinal(generation, text) { const owner = this.requesterFinalOwner; if (this.stopped || !owner || owner.generation !== generation) return false; this.requesterFinalOwner = void 0; return this.sendAppend(boundOpenAIQuicksilverDelegationResult(text), "speakable", owner.delegationId); } revokeRequesterFinal() { this.requesterFinalOwner = void 0; this.options.runAgentConsult.revokeRequesterFinal?.(); } sendAppend(text, channel, delegationId, socket = this.options.getSocket()) { for (const chunk of chunkOpenAIQuicksilverAppendText(text)) { if (this.stopped || this.drainDisposition || this.options.signal.aborted || !socket || socket !== this.options.getSocket() || socket.readyState !== WEBSOCKET_OPEN) return false; socket.send(JSON.stringify(buildOpenAIQuicksilverContextAppend({ model: this.options.model, text: chunk, channel, delegationId }))); } return true; } fail(error) { if (this.stopped) return; this.options.logger.warn(error.message); this.options.onFatalError(error); } }; //#endregion export { OpenAIQuicksilverDelegationController as t };