openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
296 lines (295 loc) • 12.1 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { n as parseBooleanValue } from "./boolean-DmBL0YJK.js";
import { l as redactSecrets } from "./redact-BtvPPfTi.js";
import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js";
import { t as truncateUtf8Prefix } from "./utf8-truncate-CCgJrv8A.js";
import "./session-accessor-YsytfDtG.js";
import { l as loadSessionEntry } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { n as parseSqliteSessionFileMarker } from "./legacy-sqlite-marker-COPKCuIN.js";
import { r as TRAJECTORY_RUNTIME_EVENT_MAX_BYTES } from "./paths-DBZSw_GC.js";
import { t as sanitizeDiagnosticPayload } from "./payload-redaction-DPHUXa81.js";
import { t as safeJsonStringify } from "./safe-json-CY5cd4H1.js";
import { t as appendSqliteTrajectoryRuntimeEvents } from "./runtime-store.sqlite-CvHzANNM.js";
import path from "node:path";
//#region src/trajectory/runtime.ts
const TRAJECTORY_RUNTIME_DATA_STRING_MAX_CHARS = 32768;
const TRAJECTORY_RUNTIME_DATA_ARRAY_MAX_ITEMS = 64;
const TRAJECTORY_RUNTIME_DATA_OBJECT_MAX_KEYS = 64;
const TRAJECTORY_RUNTIME_DATA_MAX_DEPTH = 6;
const TRAJECTORY_RUNTIME_FINAL_PROMPT_MAX_BYTES = 4096;
const TRAJECTORY_RUNTIME_OVERSIZE_DROP_FIRST_DATA_KEYS = [
"messagesSnapshot",
"messages",
"systemPrompt"
];
const OVERSIZE_PRESERVED_DATA_KEYS = [
"threadId",
"turnId",
"timedOut",
"yieldDetected",
"aborted",
"promptError",
"stopReason",
"usage",
"promptCache",
"prompt"
];
function truncateOversizedTrajectoryEvent(event, line) {
const bytes = Buffer.byteLength(line, "utf8");
if (bytes <= 262144) return line;
const originalData = event.data ?? {};
const originalDataKeys = Object.keys(originalData);
const preservedDataKeys = /* @__PURE__ */ new Set();
const baseData = {
truncated: true,
originalBytes: bytes,
limitBytes: TRAJECTORY_RUNTIME_EVENT_MAX_BYTES,
reason: "trajectory-event-size-limit"
};
const reducedData = { ...originalData };
const reducedDroppedFields = [];
for (const key of TRAJECTORY_RUNTIME_OVERSIZE_DROP_FIRST_DATA_KEYS) {
if (!Object.hasOwn(reducedData, key)) continue;
delete reducedData[key];
reducedDroppedFields.push(key);
const reduced = safeJsonStringify({
...event,
data: {
...reducedData,
...baseData,
droppedFields: reducedDroppedFields
}
});
if (reduced && Buffer.byteLength(reduced, "utf8") <= 262144) return reduced;
}
const buildTruncatedEventLine = (includeDroppedFields) => {
const data = { ...baseData };
for (const key of OVERSIZE_PRESERVED_DATA_KEYS) if (preservedDataKeys.has(key)) data[key] = originalData[key];
if (includeDroppedFields) {
const droppedFields = originalDataKeys.filter((key) => !preservedDataKeys.has(key));
if (droppedFields.length > 0) data.droppedFields = droppedFields;
}
const truncated = safeJsonStringify({
...event,
data
});
if (truncated && Buffer.byteLength(truncated, "utf8") <= 262144) return truncated;
};
let best = buildTruncatedEventLine(true) ?? buildTruncatedEventLine(false);
if (!best) return;
for (const key of OVERSIZE_PRESERVED_DATA_KEYS) {
if (!Object.hasOwn(originalData, key)) continue;
preservedDataKeys.add(key);
const next = buildTruncatedEventLine(true) ?? buildTruncatedEventLine(false);
if (next) {
best = next;
continue;
}
preservedDataKeys.delete(key);
}
return best;
}
function truncatedTrajectoryValue(reason, details = {}) {
return {
truncated: true,
reason,
...details
};
}
function limitTrajectoryPayloadValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
if (typeof value === "string") {
if (value.length > TRAJECTORY_RUNTIME_DATA_STRING_MAX_CHARS) return truncatedTrajectoryValue("trajectory-field-size-limit", {
originalChars: value.length,
limitChars: TRAJECTORY_RUNTIME_DATA_STRING_MAX_CHARS
});
return value;
}
if (typeof value !== "object" || value === null) return value;
if (seen.has(value)) return truncatedTrajectoryValue("trajectory-circular-reference");
if (depth >= TRAJECTORY_RUNTIME_DATA_MAX_DEPTH) return truncatedTrajectoryValue("trajectory-depth-limit", { limitDepth: TRAJECTORY_RUNTIME_DATA_MAX_DEPTH });
seen.add(value);
if (Array.isArray(value)) {
const limited = value.slice(0, TRAJECTORY_RUNTIME_DATA_ARRAY_MAX_ITEMS).map((item) => limitTrajectoryPayloadValue(item, depth + 1, seen));
if (value.length > TRAJECTORY_RUNTIME_DATA_ARRAY_MAX_ITEMS) limited.push(truncatedTrajectoryValue("trajectory-array-size-limit", {
originalLength: value.length,
limitItems: TRAJECTORY_RUNTIME_DATA_ARRAY_MAX_ITEMS
}));
seen.delete(value);
return limited;
}
const record = value;
const keys = Object.keys(record);
const limited = {};
for (const key of keys.slice(0, TRAJECTORY_RUNTIME_DATA_OBJECT_MAX_KEYS)) limited[key] = limitTrajectoryPayloadValue(record[key], depth + 1, seen);
if (keys.length > TRAJECTORY_RUNTIME_DATA_OBJECT_MAX_KEYS) limited["_truncated"] = truncatedTrajectoryValue("trajectory-object-size-limit", {
originalKeys: keys.length,
limitKeys: TRAJECTORY_RUNTIME_DATA_OBJECT_MAX_KEYS
});
seen.delete(value);
return limited;
}
function sanitizeTrajectoryPayload(data) {
const finalPromptText = data.finalPromptText;
const redactedFinalPromptText = typeof finalPromptText === "string" ? redactSecrets(finalPromptText) : void 0;
const boundedData = typeof finalPromptText === "string" && typeof redactedFinalPromptText === "string" && (Buffer.byteLength(finalPromptText, "utf8") > TRAJECTORY_RUNTIME_FINAL_PROMPT_MAX_BYTES || Buffer.byteLength(redactedFinalPromptText, "utf8") > TRAJECTORY_RUNTIME_FINAL_PROMPT_MAX_BYTES) ? {
...data,
finalPromptText: truncateUtf8Prefix(redactedFinalPromptText, TRAJECTORY_RUNTIME_FINAL_PROMPT_MAX_BYTES),
finalPromptTextOriginalLength: finalPromptText.length
} : typeof redactedFinalPromptText === "string" ? {
...data,
finalPromptText: redactedFinalPromptText
} : data;
return redactSecrets(sanitizeDiagnosticPayload(limitTrajectoryPayloadValue(boundedData)));
}
function describeTrajectoryWriterFlushState(writer) {
const diagnostics = writer.describeQueue?.();
if (!diagnostics) return;
const parts = [
`pendingWrites=${diagnostics.pendingWrites}`,
`queuedBytes=${diagnostics.queuedBytes}`,
`activeOperation=${diagnostics.activeOperation}`,
`yieldBeforeWrite=${diagnostics.yieldBeforeWrite}`
];
if (diagnostics.activeWriteBytes !== void 0) parts.push(`activeWriteBytes=${diagnostics.activeWriteBytes}`);
if (diagnostics.maxQueuedBytes !== void 0) parts.push(`maxQueuedBytes=${diagnostics.maxQueuedBytes}`);
if (diagnostics.maxFileBytes !== void 0) parts.push(`maxFileBytes=${diagnostics.maxFileBytes}`);
return parts.join(" ");
}
function createFileTrajectoryRuntimeSink(writer) {
return {
describeFlushState: () => describeTrajectoryWriterFlushState(writer),
flush: async () => {
await writer.flush();
},
nextSourceSeq: writer.nextSourceSeq,
write: (_event, line) => {
writer.write(`${line}\n`);
}
};
}
function createSqliteTrajectoryRuntimeSink(params) {
const target = params.sessionTarget ? {
agentId: normalizeOptionalString(params.sessionTarget.agentId),
sessionId: normalizeOptionalString(params.sessionTarget.sessionId),
sessionKey: normalizeOptionalString(params.sessionTarget.sessionKey),
storePath: normalizeOptionalString(params.sessionTarget.storePath)
} : void 0;
const legacyMarker = parseSqliteSessionFileMarker(params.sessionFile);
const completeTarget = Boolean(target?.agentId && target.sessionId && target.sessionKey && target.storePath);
const targetKeyAgentId = parseAgentSessionKey(target?.sessionKey)?.agentId;
const requestedSessionKey = normalizeOptionalString(params.sessionKey);
const completeTargetKeyEntry = completeTarget && target?.agentId && target.sessionKey && target.storePath ? loadSessionEntry({
agentId: target.agentId,
sessionKey: target.sessionKey,
storePath: target.storePath
}) : void 0;
if (completeTarget && (requestedSessionKey && target?.sessionKey !== requestedSessionKey || targetKeyAgentId && target?.agentId !== targetKeyAgentId || completeTargetKeyEntry && completeTargetKeyEntry.sessionId !== target?.sessionId)) return null;
const targetKeyEntry = target?.sessionKey && legacyMarker && !completeTarget ? loadSessionEntry({
agentId: legacyMarker.agentId,
sessionKey: target.sessionKey,
storePath: legacyMarker.storePath
}) : void 0;
if (target && !completeTarget && legacyMarker && (target.agentId && target.agentId !== legacyMarker.agentId || target.sessionId && target.sessionId !== legacyMarker.sessionId || targetKeyAgentId && targetKeyAgentId !== legacyMarker.agentId || target.sessionKey && targetKeyEntry?.sessionId !== legacyMarker.sessionId || target.storePath && path.resolve(target.storePath) !== path.resolve(legacyMarker.storePath))) return null;
const marker = target?.agentId && target.sessionId && target.sessionKey && target.storePath ? {
agentId: target.agentId,
sessionId: target.sessionId,
sessionKey: target.sessionKey,
storePath: target.storePath
} : legacyMarker;
if (!marker || marker.sessionId !== params.sessionId) return null;
let pendingEvents = [];
let queuedBytes = 0;
return {
describeFlushState: () => pendingEvents.length > 0 ? `pendingRows=${pendingEvents.length} queuedBytes=${queuedBytes} activeOperation=sqlite-append` : void 0,
flush: async () => {
if (pendingEvents.length === 0) return;
const events = pendingEvents;
pendingEvents = [];
queuedBytes = 0;
appendSqliteTrajectoryRuntimeEvents({
agentId: marker.agentId,
env: params.env,
maxRuntimeBytes: params.maxRuntimeFileBytes,
sessionId: marker.sessionId,
storePath: marker.storePath
}, events);
},
write: (event, line) => {
pendingEvents.push(event);
queuedBytes += Buffer.byteLength(line, "utf8") + 1;
}
};
}
function toTrajectoryToolDefinitions(tools) {
return tools.flatMap((tool) => {
const name = tool.name?.trim();
if (!name) return [];
return [{
name,
description: tool.description,
parameters: sanitizeDiagnosticPayload(limitTrajectoryPayloadValue(tool.parameters))
}];
}).toSorted((left, right) => left.name.localeCompare(right.name));
}
function createTrajectoryRuntimeRecorder(params) {
const env = params.env ?? process.env;
if (!(parseBooleanValue(env.OPENCLAW_TRAJECTORY) ?? true)) return null;
const maxRuntimeFileBytes = Math.max(1, Math.floor(params.maxRuntimeFileBytes ?? 10485760));
const sink = params.writer ? createFileTrajectoryRuntimeSink(params.writer) : createSqliteTrajectoryRuntimeSink({
env,
maxRuntimeFileBytes,
sessionFile: params.sessionFile,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
sessionTarget: params.sessionTarget
});
if (!sink) return null;
let seq = 0;
const traceId = params.sessionId;
const buildEvent = (type, data) => {
const nextSeq = seq + 1;
const sourceSeq = sink.nextSourceSeq?.() ?? nextSeq;
const event = {
traceSchema: "openclaw-trajectory",
schemaVersion: 1,
traceId,
source: "runtime",
type,
ts: (/* @__PURE__ */ new Date()).toISOString(),
seq: nextSeq,
sourceSeq,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
runId: params.runId,
workspaceDir: params.workspaceDir,
provider: params.provider,
modelId: params.modelId,
modelApi: params.modelApi,
data: data ? sanitizeTrajectoryPayload(data) : void 0
};
const line = safeJsonStringify(event);
if (!line) return;
const boundedLine = truncateOversizedTrajectoryEvent(event, line);
if (!boundedLine) return;
const boundedEvent = JSON.parse(boundedLine);
seq = nextSeq;
return {
event: boundedEvent,
line: boundedLine
};
};
return {
enabled: true,
recordEvent: (type, data) => {
const built = buildEvent(type, data);
if (!built) return;
sink.write(built.event, built.line);
},
flush: async () => {
await sink.flush();
},
describeFlushState: () => sink.describeFlushState()
};
}
//#endregion
export { toTrajectoryToolDefinitions as n, createTrajectoryRuntimeRecorder as t };