openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
149 lines (148 loc) • 8.29 kB
JavaScript
import { i as executeSqliteQueryTakeFirstSync } from "./kysely-sync-COmh4HWh.js";
import { t as MODEL_CONTEXT_PRIVATE_METADATA_KEYS } from "./model-context-message-Dmngk3hL.js";
import { n as SYNTHETIC_MISSING_TOOL_RESULT_DETAIL_KEY, t as DEFAULT_MISSING_TOOL_RESULT_TEXT } from "./tool-result-pairing-Bccl6DhI.js";
import { i as getSessionKysely } from "./session-accessor.sqlite-scope-2KfMzb44.js";
import { AsyncLocalStorage } from "node:async_hooks";
import { sql } from "kysely";
//#region src/config/sessions/session-model-context-projection.ts
/** Exclude storage-only fields in SQLite, before a row's JSON crosses into JavaScript. */
function projectModelContextEventSql(event, omitCheckpoint) {
const paths = MODEL_CONTEXT_PRIVATE_METADATA_KEYS.map((key) => `$.message.__openclaw.${key}`);
const projected = sql`json_remove(${event}, ${sql.join(paths)})`;
const modelEvent = sql`CASE WHEN json_extract(${event}, '$.message.role') = 'toolResult'
THEN json_remove(${projected}, '$.message.details') ELSE ${projected} END`;
return sql`CASE WHEN ${omitCheckpoint} = 1
THEN json_remove(${modelEvent}, '$.message.providerReplay') ELSE ${modelEvent} END`;
}
function pickJsonObject(value, keys) {
return sql`(SELECT json_group_object(key, CASE type
WHEN 'object' THEN json(value) WHEN 'array' THEN json(value)
WHEN 'true' THEN json('true') WHEN 'false' THEN json('false')
ELSE value END) FROM json_each(${value}) WHERE key IN (${sql.join(keys)}))`;
}
const TRANSCRIPT_NAVIGATION_KEYS = [
"type",
"id",
"parentId",
"targetId",
"appendParentId",
"appendMode"
];
/** Cursor resolution needs only tree facts, even when a row has an opaque body. */
function projectTranscriptNavigationSql(event) {
return pickJsonObject(event, TRANSCRIPT_NAVIGATION_KEYS);
}
/** Reset boundaries select ancestry and replay roles without loading message bodies. */
function projectResetBoundaryNavigationSql(event) {
const entry = pickJsonObject(event, [
...TRANSCRIPT_NAVIGATION_KEYS,
"timestamp",
"firstKeptEntryId"
]);
return sql`CASE WHEN json_valid(${event}) THEN
CASE WHEN json_type(${event}) = 'object' THEN
json_set(${entry}, '$.message', json_object('role', json_extract(${event}, '$.message.role')))
ELSE ${event} END
ELSE ${event} END`;
}
/** Lightweight tree/state records; these never serve as persisted transcript evidence. */
function projectModelContextNavigationSql(event) {
const entry = pickJsonObject(event, [
...TRANSCRIPT_NAVIGATION_KEYS,
"timestamp",
"version",
"cwd",
"firstKeptEntryId",
"reason",
"tokensBefore",
"thinkingLevel",
"provider",
"modelId",
"fromId",
"customType",
"display",
"label",
"name"
]);
const messageFacts = pickJsonObject(sql`json_extract(${event}, '$.message')`, [
"role",
"provider",
"model",
"timestamp",
"excludeFromContext",
"toolCallId",
"toolUseId",
"tool_call_id",
"tool_use_id",
"callId",
"call_id",
"toolName",
"isError",
"stopReason",
"customType",
"display"
]);
const calls = sql`(SELECT json_group_array(json_object(
'type', json_extract(value, '$.type'), 'id', json_extract(value, '$.id'),
'name', json_extract(value, '$.name')))
FROM json_each(${event}, '$.message.content') WHERE type = 'object'
AND json_extract(value, '$.type') IN ('toolCall', 'toolUse', 'functionCall'))`;
const synthetic = sql`COALESCE(json_extract(${event}, ${`$.message.details.${SYNTHETIC_MISSING_TOOL_RESULT_DETAIL_KEY}`}), 0) = 1 OR EXISTS (
SELECT 1 FROM json_each(${event}, '$.message.content') WHERE type = 'object'
AND json_extract(value, '$.type') = 'text' AND json_extract(value, '$.text') = ${DEFAULT_MISSING_TOOL_RESULT_TEXT})`;
return sql`CASE json_extract(${event}, '$.type')
WHEN 'message' THEN json_set(${entry}, '$.message', json_set(${messageFacts},
'$.content', json(${calls}), '$.command', '', '$.output', '',
'$.providerReplay', json_object('type', json_extract(${event}, '$.message.providerReplay.type')),
'$.details', json_object(${SYNTHETIC_MISSING_TOOL_RESULT_DETAIL_KEY}, json(CASE WHEN (${synthetic}) THEN 'true' ELSE 'false' END))))
WHEN 'custom_message' THEN json_set(${entry}, '$.content', json('[]'))
WHEN 'compaction' THEN json_set(${entry}, '$.summary', '')
WHEN 'branch_summary' THEN json_set(${entry}, '$.summary', '')
ELSE ${entry} END`;
}
//#endregion
//#region src/config/sessions/session-transcript-read-fence.ts
const transcriptReadFenceStorage = new AsyncLocalStorage();
var SessionTranscriptReadFenceError = class extends Error {
constructor(message) {
super(message);
this.name = "SessionTranscriptReadFenceError";
}
};
function runWithSessionTranscriptReadFence(receipt, run) {
return receipt ? transcriptReadFenceStorage.run(receipt, run) : run();
}
function withSessionContextAdmission(target, admission, read) {
if (admission && (target.agentId !== admission.agentId || target.sessionId !== admission.sessionId || target.sessionKey !== admission.sessionKey)) throw new SessionTranscriptReadFenceError("Current-turn transcript admission belongs to a different transcript target");
return runWithSessionTranscriptReadFence(admission, read);
}
function resolveSessionTranscriptReadFence(session) {
const receipt = transcriptReadFenceStorage.getStore();
return receipt?.agentId === session.agentId && receipt.sessionId === session.sessionId ? receipt : void 0;
}
function resolveSqliteSessionTranscriptReadFence(params) {
const receipt = resolveSessionTranscriptReadFence(params);
if (!receipt) return;
if (receipt.role !== "user") throw new SessionTranscriptReadFenceError(`Current-turn transcript admission is not a user message: ${receipt.entryId}`);
if (params.database.path !== receipt.storePath) throw new SessionTranscriptReadFenceError("Current-turn transcript admission belongs to a different transcript store");
if (params.sessionKey !== void 0 && params.sessionKey !== receipt.sessionKey) throw new SessionTranscriptReadFenceError("Current-turn transcript admission belongs to a different session key");
const db = getSessionKysely(params.database.db);
const boundary = executeSqliteQueryTakeFirstSync(params.database.db, db.selectFrom("transcript_event_identities as identity").innerJoin("session_transcript_active_events as active", (join) => join.onRef("active.session_id", "=", "identity.session_id").onRef("active.event_seq", "=", "identity.seq")).innerJoin("transcript_events as event", (join) => join.onRef("event.session_id", "=", "identity.session_id").onRef("event.seq", "=", "identity.seq")).innerJoin("transcript_rewrite_watermarks as rewrite", (join) => join.onRef("rewrite.session_id", "=", "identity.session_id")).select([
"identity.seq",
"identity.parent_id",
"active.message_position",
"rewrite.generation",
sql`json_extract(event.event_json, '$.type')`.as("event_type"),
sql`json_extract(event.event_json, '$.message.role')`.as("message_role")
]).where("identity.session_id", "=", params.sessionId).where("identity.event_id", "=", receipt.entryId).limit(1));
if (boundary?.message_position === null || boundary?.message_position === void 0) throw new SessionTranscriptReadFenceError(`Current-turn transcript admission is no longer a visible message: ${receipt.entryId}`);
if (boundary.generation !== receipt.generation || boundary.seq !== receipt.rawSeq || boundary.parent_id !== receipt.effectiveParentId || boundary.message_position !== receipt.activeMessagePosition) throw new SessionTranscriptReadFenceError(`Current-turn transcript admission identity changed: ${receipt.entryId}`);
if (boundary.event_type !== "message" || boundary.message_role !== "user") throw new SessionTranscriptReadFenceError(`Current-turn transcript admission is not a user message: ${receipt.entryId}`);
return {
admission: receipt,
beforeActiveMessagePosition: boundary.message_position,
beforeRawSeq: boundary.seq
};
}
//#endregion
export { withSessionContextAdmission as a, projectResetBoundaryNavigationSql as c, runWithSessionTranscriptReadFence as i, projectTranscriptNavigationSql as l, resolveSessionTranscriptReadFence as n, projectModelContextEventSql as o, resolveSqliteSessionTranscriptReadFence as r, projectModelContextNavigationSql as s, SessionTranscriptReadFenceError as t };