UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

303 lines (302 loc) 12.3 kB
import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { m as readNonBlankString } from "./string-coerce-CIXf7egm.js"; import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js"; import "./session-key-BnWWjqNc.js"; import { $ as readTranscriptRawDelta, At as withTranscriptWriteLock, B as resolveSessionTranscriptRuntimeTarget, F as persistSessionTranscriptTurn, I as bindSessionTranscriptStoreScope, M as isSessionTranscriptProjectionUnavailableError, Mt as selectVisibleTranscriptEventEntries, Nt as selectVisibleTranscriptEvents, P as appendTranscriptMessages, m as readSessionTranscriptVisibleMessageDeltaCore, wt as appendTranscriptMessage } from "./session-accessor-YsytfDtG.js"; import { P as redactTranscriptMessage, b as loadTranscriptEvents, v as loadLatestAssistantText } from "./session-accessor.sqlite-transcript-store-B0zw3fAU.js"; import { l as loadSessionEntry } from "./session-accessor.sqlite-entry-CWk3jL7s.js"; import { _ as publishTranscriptUpdate } from "./session-accessor.sqlite-lifecycle-state-BTh4yZ7R.js"; import { t as extractAssistantPhaseText } from "./chat-message-content-Dws_XUEQ.js"; import "./prompt-image-input-BzVFAmOx.js"; import { t as resolveMirroredTranscriptText } from "./transcript-mirror-BTvMHa-8.js"; import { t as formatSessionTranscriptMemoryHitKey } from "./session-transcript-memory-hit-ClMZaPAU.js"; import { t as withProjectedSessionTranscriptWriteLock } from "./session-transcript-lock-runtime-9IW28E3u.js"; //#region src/plugin-sdk/session-transcript-runtime.ts /** * Resolves the public identity for a transcript without returning its file path. */ async function resolveSessionTranscriptIdentity(params) { const target = await resolveSessionTranscriptRuntimeTarget(params); const agentId = normalizeAgentId(target.agentId); return { agentId, memoryKey: formatSessionTranscriptMemoryHitKey({ agentId, sessionId: target.sessionId }), sessionId: target.sessionId, sessionKey: target.sessionKey }; } /** * Resolves the public target for transcript operations without exposing the * current storage path as identity. */ async function resolveSessionTranscriptTarget(params) { return projectPublicTarget({ ...await resolveSessionTranscriptRuntimeTarget(params), targetKind: "runtime-session" }); } /** * Reads transcript events by public session identity instead of file path. */ async function readSessionTranscriptEvents(params) { return await loadTranscriptEvents(bindSessionTranscriptStoreScope(params)); } /** Reads one bounded raw page; the opaque cursor survives append and resets after replacement. */ async function readSessionTranscriptRawDelta(params) { const { cursor, maxBytes, maxEvents, ...target } = params; return readTranscriptRawDelta(bindSessionTranscriptStoreScope(target), { ...cursor !== void 0 ? { cursor } : {}, ...maxBytes !== void 0 ? { maxBytes } : {}, ...maxEvents !== void 0 ? { maxEvents } : {} }); } /** Reads one bounded active-path page that resumes appends and resets after discontinuities. */ async function readSessionTranscriptVisibleMessageDelta(params) { const { cursor, maxBytes, maxMessages, ...target } = params; let result; try { result = readSessionTranscriptVisibleMessageDeltaCore(bindSessionTranscriptStoreScope(target), { ...cursor !== void 0 ? { cursor } : {}, ...maxBytes !== void 0 ? { maxBytes } : {}, ...maxMessages !== void 0 ? { maxMessages } : {} }); } catch (error) { if (isSessionTranscriptProjectionUnavailableError(error)) return { kind: "unavailable", reason: "projection_rebuilding" }; throw error; } if (result.kind !== "page") return result; const { events, ...page } = result; return { ...page, entries: events.flatMap((entry) => projectVisibleMessageEntry({ event: entry.event, parentId: entry.parentId, seq: entry.seq })) }; } /** * Reads visible transcript message entries by scoped identity. * * This is a branch-safe message projection over the current full transcript * read. `seq` is ordered read metadata, not a resumable cursor. */ async function readVisibleSessionTranscriptMessageEntries(params) { return selectVisibleTranscriptEventEntries(await readSessionTranscriptEvents(params)).flatMap(projectVisibleMessageEntry); } /** * Reads the latest visible assistant text by scoped identity. */ async function readLatestAssistantTextByIdentity(params) { return loadLatestAssistantText(bindSessionTranscriptStoreScope(params)); } /** * Appends a delivery-mirror assistant message through the SQLite transcript accessor. */ async function appendAssistantMirrorMessageByIdentity(params) { params.signal?.throwIfAborted(); const text = resolveMirroredTranscriptText({ ...params.mediaUrls !== void 0 ? { mediaUrls: params.mediaUrls } : {}, ...params.text !== void 0 ? { text: params.text } : {} }); if (!text) return { ok: false, reason: "empty message" }; const message = createAssistantMirrorMessage({ ...params.deliveryMirror !== void 0 ? { deliveryMirror: params.deliveryMirror } : {}, ...params.idempotencyKey !== void 0 ? { idempotencyKey: params.idempotencyKey } : {}, text }); const scope = bindSessionTranscriptStoreScope(params, params.config); return await withTranscriptWriteLock(scope, async (locked) => { params.signal?.throwIfAborted(); const currentEntry = loadSessionEntry(scope); if (!currentEntry?.sessionId) return { ok: false, reason: "missing active session", code: "blocked" }; if (params.sessionId && currentEntry.sessionId !== params.sessionId) return { ok: false, reason: "session changed", code: "session-rebound" }; const latestEquivalentAssistantId = !params.idempotencyKey && isDeliveryMirrorAssistantMessage(message) ? findLatestEquivalentAssistantMessageId(selectVisibleTranscriptEvents(await locked.readEvents()), message, params.config) : void 0; if (latestEquivalentAssistantId) return { ok: true, messageId: latestEquivalentAssistantId }; params.signal?.throwIfAborted(); const appendResult = await locked.appendMessage({ ...params.config !== void 0 ? { config: params.config } : {}, ...params.idempotencyKey ? { idempotencyLookup: "scan" } : {}, message }); if (!appendResult) return { ok: false, reason: "message skipped", code: "blocked" }; if (params.updateMode !== "none" && appendResult.appended) { params.signal?.throwIfAborted(); await publishTranscriptUpdate(scope, { messageId: appendResult.messageId }); } return { ok: true, messageId: appendResult.messageId }; }); } /** * Appends an already-canonical transcript message by scoped transcript target. * Media-bearing user turns use ordered `message.__openclaw.media` facts; this * low-level API does not infer deprecated top-level Media* projections. */ async function appendSessionTranscriptMessageByIdentity(params) { return await appendTranscriptMessage(bindSessionTranscriptStoreScope(params, params.config), params); } /** Appends one message while preserving distinct suppression and session-rebind outcomes. */ async function appendSessionTranscriptMessageByIdentityStrict(params) { const expectedSessionId = params.sessionId?.trim(); if (!expectedSessionId) throw new Error("Cannot strictly append a transcript message without an exact session id"); const turn = await persistSessionTranscriptTurn(params, { ...params.config ? { config: params.config } : {}, ...params.cwd ? { cwd: params.cwd } : {}, expectedSessionId, messages: [{ ...params.eventId !== void 0 ? { eventId: params.eventId } : {}, ...params.idempotencyLookup !== void 0 ? { idempotencyLookup: params.idempotencyLookup } : {}, message: params.message, ...params.now !== void 0 ? { now: params.now } : {}, ...params.parentId !== void 0 ? { parentId: params.parentId } : {}, ...params.prepareMessageAfterIdempotencyCheck ? { prepareMessageAfterIdempotencyCheck: (message) => params.prepareMessageAfterIdempotencyCheck?.(message) } : {}, ...params.useRawWhenLinear !== void 0 ? { useRawWhenLinear: params.useRawWhenLinear } : {} }], updateMode: "none" }); if (turn.rejectedReason) return { kind: "rejected", reason: turn.rejectedReason }; const result = turn.messages[0]; return result ? { kind: "result", result } : { kind: "suppressed" }; } /** * Atomically appends one ordered, already-hooked message group. Preparation and * redaction finish before SQLite begins; this is the canonical future harness seam. */ async function appendSessionTranscriptMessagesByIdentity(params) { return await appendTranscriptMessages(params, params); } /** * Publishes a transcript update by scoped transcript target. */ async function publishSessionTranscriptUpdateByIdentity(params) { const target = await resolveSessionTranscriptRuntimeTarget(params); await publishTranscriptUpdate({ ...params, ...target }, params.update); } /** * Runs transcript work under the write lock for the resolved scoped target. */ async function withSessionTranscriptWriteLock(params, run) { return await withProjectedSessionTranscriptWriteLock(params, run, (context) => context); } function createAssistantMirrorMessage(params) { return { role: "assistant", content: [{ type: "text", text: params.text }], api: "openai-responses", provider: "openclaw", model: "delivery-mirror", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, stopReason: "stop", timestamp: Date.now(), ...params.idempotencyKey ? { idempotencyKey: params.idempotencyKey } : {}, ...params.deliveryMirror ? { openclawDeliveryMirror: params.deliveryMirror } : {} }; } function findLatestEquivalentAssistantMessageId(events, message, config) { const expectedText = extractAssistantMirrorComparableText(message, config); if (!expectedText) return; for (let index = events.length - 1; index >= 0; index -= 1) { const event = events[index]; if (!event || typeof event !== "object") continue; const record = event; const candidate = record.message; if (!candidate) continue; if (candidate.role !== "assistant") return; return extractAssistantMirrorComparableText(candidate, config) === expectedText && typeof record.id === "string" && record.id ? record.id : void 0; } } function extractAssistantMirrorComparableText(message, config) { const redacted = redactTranscriptMessage(message, config); return extractAssistantPhaseText(redacted)?.trim() || void 0; } function isDeliveryMirrorAssistantMessage(message) { return message.provider === "openclaw" && message.model === "delivery-mirror"; } function isAgentMessageRecord(value) { return isRecord(value) && readNonBlankString(value.role) !== void 0; } function projectVisibleMessageEntry(entry) { const event = entry.event; if (!isRecord(event) || event.type !== "message") return []; const entryId = readNonBlankString(event.id); const message = event.message; if (!entryId || !isAgentMessageRecord(message)) return []; const createdAt = readNonBlankString(event.timestamp); const idempotencyKey = readNonBlankString(message.idempotencyKey); return [{ entryId, parentId: entry.parentId, seq: entry.seq, message, role: message.role, ...createdAt ? { createdAt } : {}, ...idempotencyKey ? { idempotencyKey } : {} }]; } function projectPublicTarget(target) { const agentId = normalizeAgentId(target.agentId); return { agentId, memoryKey: formatSessionTranscriptMemoryHitKey({ agentId, sessionId: target.sessionId }), sessionId: target.sessionId, sessionKey: target.sessionKey, targetKind: target.targetKind }; } //#endregion export { publishSessionTranscriptUpdateByIdentity as a, readSessionTranscriptRawDelta as c, resolveSessionTranscriptIdentity as d, resolveSessionTranscriptTarget as f, appendSessionTranscriptMessagesByIdentity as i, readSessionTranscriptVisibleMessageDelta as l, appendSessionTranscriptMessageByIdentity as n, readLatestAssistantTextByIdentity as o, withSessionTranscriptWriteLock as p, appendSessionTranscriptMessageByIdentityStrict as r, readSessionTranscriptEvents as s, appendAssistantMirrorMessageByIdentity as t, readVisibleSessionTranscriptMessageEntries as u };