UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

242 lines (241 loc) 10.3 kB
import { C as resolveExpiresAtMsFromDurationMs, S as resolveDateTimestampMs, o as asDateTimestampMs } from "./number-coercion-CJQ8TR--.js"; import { c as resolveDefaultAgentId } from "./agent-scope-config-CgCYpZfK.js"; import { i as emitAgentEvent } from "./agent-events-C1B8VhOg.js"; import { n as isAbortRequestText } from "./abort-primitives-CNNnD8oN.js"; import { r as jsonUtf8Bytes } from "./json-utf8-bytes-C14lActR.js"; import { n as projectLiveAssistantBufferedText } from "./live-chat-projector-CnA2pXR3.js"; //#region src/gateway/chat-abort.ts const DEFAULT_CHAT_RUN_ABORT_GRACE_MS = 6e4; function isChatStopCommandText(text) { return isAbortRequestText(text); } function createChatAbortSignalReason(stopReason) { if (stopReason !== "timeout") return; const reason = /* @__PURE__ */ new Error("chat run timed out"); reason.name = "TimeoutError"; return reason; } function resolveChatRunExpiresAtMs(params) { const { now, timeoutMs, graceMs = DEFAULT_CHAT_RUN_ABORT_GRACE_MS, minMs = 2 * 6e4, maxMs = 1440 * 6e4 } = params; const safeNow = asDateTimestampMs(now); if (safeNow === void 0) return 0; const target = resolveExpiresAtMsFromDurationMs(Math.max(0, timeoutMs) + graceMs, { nowMs: safeNow }); const min = resolveExpiresAtMsFromDurationMs(minMs, { nowMs: safeNow }); const max = resolveExpiresAtMsFromDurationMs(maxMs, { nowMs: safeNow }); if (target === void 0 || min === void 0 || max === void 0) return 0; return Math.min(max, Math.max(min, target)); } function resolveAgentRunExpiresAtMs(params) { const graceMs = Math.max(0, params.graceMs ?? DEFAULT_CHAT_RUN_ABORT_GRACE_MS); return resolveChatRunExpiresAtMs({ now: params.now, timeoutMs: params.timeoutMs, graceMs, minMs: graceMs, maxMs: Math.max(0, params.timeoutMs) + graceMs }); } function registerChatAbortController(params) { const controller = new AbortController(); const cleanup = () => { if (params.chatAbortControllers.get(params.runId)?.controller === controller) params.chatAbortControllers.delete(params.runId); }; if (!params.sessionKey || params.chatAbortControllers.has(params.runId)) return { controller, registered: false, cleanup }; const rawNow = params.now ?? Date.now(); const now = resolveDateTimestampMs(rawNow, 0); const explicitExpiresAtMs = params.expiresAtMs === void 0 ? void 0 : asDateTimestampMs(params.expiresAtMs) ?? 0; const entry = { controller, sessionId: params.sessionId, sessionKey: params.sessionKey, agentId: normalizeActiveAgentId(params.agentId), startedAtMs: now, expiresAtMs: explicitExpiresAtMs ?? resolveChatRunExpiresAtMs({ now: rawNow, timeoutMs: params.timeoutMs }), ownerConnId: params.ownerConnId, ownerDeviceId: params.ownerDeviceId, providerId: normalizeProviderIdForActiveRun(params.providerId), authProviderId: normalizeProviderIdForActiveRun(params.authProviderId), controlUiVisible: params.controlUiVisible, projectSessionActive: true, kind: params.kind }; params.chatAbortControllers.set(params.runId, entry); return { controller, registered: true, entry, cleanup }; } function normalizeProviderIdForActiveRun(providerId) { return providerId?.trim().toLowerCase() || void 0; } function normalizeActiveAgentId(agentId) { return agentId?.trim().toLowerCase() || void 0; } /** * Snapshot the live assistant text of any in-flight run for a session+agent. Used * by chat.history so a run that kept streaming while the client was switched away * — whose deltas the gateway delivered to a delivery key this client is no longer * subscribed to — is restored on switch-back. * * Matches a run the same way sessions.list's active-run projection does: an abort * entry can hold the requested key while chat run state holds the canonical store * key, so accept a match on EITHER `requestedSessionKey` or `canonicalSessionKey`, * scoping the shared "global" session by agent. Only runs still projected active * (`projectSessionActive !== false`, matching sessions.list; the terminal lifecycle * flips it to false), not aborted, and visible chat-send runs are returned, so a * finalized run — already in persisted history — is not duplicated and hidden * agent runs cannot be adopted by chat clients that will not receive their final * events. */ function resolveInFlightRunSnapshot(params) { const matchesKey = (entry, key) => { if (entry.sessionKey !== key) return false; if (key !== "global") return true; const requestedAgentId = normalizeActiveAgentId(params.agentId) ?? normalizeActiveAgentId(params.defaultAgentId); if (!requestedAgentId) return false; return (normalizeActiveAgentId(entry.agentId) ?? normalizeActiveAgentId(params.defaultAgentId)) === requestedAgentId; }; if (!(params.chatAbortControllers instanceof Map)) return; let best; for (const [runId, entry] of params.chatAbortControllers) { if (entry.projectSessionActive === false || entry.controlUiVisible === false || entry.controller.signal.aborted || entry.kind === "agent") continue; if (!matchesKey(entry, params.requestedSessionKey) && !matchesKey(entry, params.canonicalSessionKey)) continue; const newer = best === void 0 || entry.startedAtMs > best.startedAtMs; const tie = best !== void 0 && entry.startedAtMs === best.startedAtMs && runId > best.runId; if (newer || tie) best = { runId, startedAtMs: entry.startedAtMs }; } if (best === void 0) return; const projected = projectLiveAssistantBufferedText(params.chatRunBuffers?.get(best.runId) ?? "", { suppressLeadFragments: true }); return { runId: best.runId, text: projected.suppress ? "" : projected.text }; } function boundInFlightRunSnapshotForChatHistory(params) { if (!params.snapshot?.text) return params.snapshot; if (jsonUtf8Bytes(params.messages) + jsonUtf8Bytes(params.snapshot) <= params.maxBytes) return params.snapshot; return { runId: params.snapshot.runId, text: "" }; } function abortTrackedChatRunById(ops, params) { return abortChatRunById({ chatAbortControllers: ops.chatAbortControllers, chatRunBuffers: ops.chatRunBuffers, chatAbortedRuns: ops.chatRunState.abortedRuns, clearChatRunState: ops.chatRunState.clearRun, removeChatRun: ops.removeChatRun, agentRunSeq: ops.agentRunSeq, broadcast: ops.broadcast, nodeSendToSession: ops.nodeSendToSession }, params); } function resolveChatAbortDeliverySessionKeys(ops, sessionKey, agentId) { if (sessionKey !== "global") return [sessionKey]; const scopedAgentId = normalizeActiveAgentId(agentId); if (!scopedAgentId) return [sessionKey]; const keys = [`agent:${scopedAgentId}:global`]; const cfg = ops.getRuntimeConfig?.(); const defaultAgentId = cfg ? resolveDefaultAgentId(cfg) : void 0; if (defaultAgentId && scopedAgentId === defaultAgentId) keys.push(sessionKey); return keys; } function broadcastChatAborted(ops, params) { const { runId, sessionKey, stopReason, partialText } = params; const defaultGlobalAgentId = sessionKey === "global" ? normalizeActiveAgentId(resolveDefaultGlobalAgentId(ops)) : void 0; const payloadAgentId = sessionKey === "global" ? normalizeActiveAgentId(params.agentId) ?? defaultGlobalAgentId : normalizeActiveAgentId(params.agentId); const payload = { runId, sessionKey, ...payloadAgentId ? { agentId: payloadAgentId } : {}, seq: (ops.agentRunSeq.get(runId) ?? 0) + 1, state: "aborted", stopReason, message: partialText ? { role: "assistant", content: [{ type: "text", text: partialText }], timestamp: Date.now() } : void 0 }; ops.broadcast("chat", payload); for (const deliverySessionKey of resolveChatAbortDeliverySessionKeys(ops, sessionKey, payloadAgentId)) ops.nodeSendToSession(deliverySessionKey, "chat", payload); } function resolveDefaultGlobalAgentId(ops) { const cfg = ops.getRuntimeConfig?.(); return cfg ? resolveDefaultAgentId(cfg) : void 0; } function abortChatRunById(ops, params) { const { runId, sessionKey, stopReason } = params; const active = ops.chatAbortControllers.get(runId); if (!active) return { aborted: false }; if (active.sessionKey !== sessionKey) return { aborted: false }; const bufferedText = ops.chatRunBuffers.get(runId); const partialText = bufferedText && bufferedText.trim() ? bufferedText : void 0; ops.chatAbortedRuns.set(runId, Date.now()); if (stopReason) active.abortStopReason = stopReason; active.controller.abort(createChatAbortSignalReason(stopReason)); ops.chatAbortControllers.delete(runId); ops.clearChatRunState(runId); const removed = ops.removeChatRun(runId, runId, sessionKey); if (active.controlUiVisible !== false) broadcastChatAborted(ops, { runId, sessionKey, agentId: active.agentId, stopReason, partialText }); emitAgentEvent({ runId, sessionKey, agentId: active.agentId, stream: "lifecycle", data: { phase: "end", status: "cancelled", aborted: true, stopReason, startedAt: active.startedAtMs, endedAt: Date.now() } }); ops.agentRunSeq.delete(runId); if (removed?.clientRunId) ops.agentRunSeq.delete(removed.clientRunId); return { aborted: true }; } function updateChatRunProvider(chatAbortControllers, params) { const entry = chatAbortControllers.get(params.runId); if (!entry) return false; entry.providerId = normalizeProviderIdForActiveRun(params.providerId); entry.authProviderId = normalizeProviderIdForActiveRun(params.authProviderId); return true; } function abortChatRunsForProvider(ops, params) { const providerId = normalizeProviderIdForActiveRun(params.providerId); if (!providerId) return { runIds: [] }; const matches = [...ops.chatAbortControllers.entries()].filter(([, entry]) => normalizeProviderIdForActiveRun(entry.authProviderId) === providerId || normalizeProviderIdForActiveRun(entry.providerId) === providerId); const runIds = []; for (const [runId, entry] of matches) if (abortChatRunById(ops, { runId, sessionKey: entry.sessionKey, stopReason: params.stopReason }).aborted) runIds.push(runId); return { runIds }; } //#endregion export { isChatStopCommandText as a, resolveInFlightRunSnapshot as c, boundInFlightRunSnapshotForChatHistory as i, updateChatRunProvider as l, abortChatRunsForProvider as n, registerChatAbortController as o, abortTrackedChatRunById as r, resolveAgentRunExpiresAtMs as s, abortChatRunById as t };