openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
237 lines (236 loc) • 10.9 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { l as asPositiveSafeInteger } from "./number-coercion-CJQ8TR--.js";
import "./agent-scope-MrLta7Pq.js";
import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js";
import { c as resolveDefaultAgentId } from "./agent-scope-config-CgCYpZfK.js";
import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js";
import { t as loadCombinedSessionStoreForGateway } from "./combined-store-gateway-Drfj1Kad.js";
import { l as readSessionMessageCountAsync, t as attachOpenClawTranscriptMeta } from "./session-utils.fs-D_8-X4jl.js";
import { a as resolveSessionTranscriptCandidates } from "./session-transcript-files.fs-CM11t-8l.js";
import { g as resolveGatewaySessionStoreTarget, l as loadGatewaySessionRow, u as loadSessionEntry } from "./session-utils-CHNCuY8a.js";
import { t as resolvePreferredSessionKeyForSessionIdMatches } from "./session-id-resolution-CcSU7jfY.js";
import { i as projectChatDisplayMessage } from "./chat-display-projection-Bv2HD4Rr.js";
import { t as resolveTranscriptPathForComparison } from "./session-transcript-path-p3PNGZXC.js";
//#region src/gateway/session-transcript-key.ts
const TRANSCRIPT_SESSION_KEY_CACHE = /* @__PURE__ */ new Map();
const TRANSCRIPT_SESSION_KEY_CACHE_MAX = 256;
function sessionKeyMatchesTranscriptPath(params) {
const entry = params.store[params.key];
if (!entry?.sessionId) return false;
const target = resolveGatewaySessionStoreTarget({
cfg: params.cfg,
key: params.key,
scanLegacyKeys: false,
store: params.store
});
const sessionAgentId = normalizeAgentId(target.agentId);
return resolveSessionTranscriptCandidates(entry.sessionId, target.storePath, entry.sessionFile, sessionAgentId).some((candidate) => resolveTranscriptPathForComparison(candidate) === params.targetPath);
}
/** Resolve the most likely Gateway session key for a transcript file path. */
function resolveSessionKeyForTranscriptFile(sessionFile) {
const targetPath = resolveTranscriptPathForComparison(sessionFile);
if (!targetPath) return;
const cfg = getRuntimeConfig();
const { store } = loadCombinedSessionStoreForGateway(cfg);
const cachedKey = TRANSCRIPT_SESSION_KEY_CACHE.get(targetPath);
if (cachedKey && sessionKeyMatchesTranscriptPath({
cfg,
store,
key: cachedKey,
targetPath
})) return cachedKey;
const matchingEntries = [];
for (const [key, entry] of Object.entries(store)) {
if (!entry?.sessionId || key === cachedKey) continue;
if (sessionKeyMatchesTranscriptPath({
cfg,
store,
key,
targetPath
})) matchingEntries.push([key, entry]);
}
if (matchingEntries.length > 0) {
const matchesBySessionId = /* @__PURE__ */ new Map();
for (const entry of matchingEntries) {
const sessionId = entry[1].sessionId;
if (!sessionId) continue;
const group = matchesBySessionId.get(sessionId);
if (group) group.push(entry);
else matchesBySessionId.set(sessionId, [entry]);
}
const resolvedMatches = Array.from(matchesBySessionId.entries()).map(([sessionId, matches]) => {
const resolvedKey = resolvePreferredSessionKeyForSessionIdMatches(matches, sessionId) ?? matches[0]?.[0];
const resolvedEntry = resolvedKey ? matches.find(([key]) => key === resolvedKey)?.[1] : void 0;
return resolvedKey && resolvedEntry ? {
key: resolvedKey,
updatedAt: resolvedEntry.updatedAt ?? 0
} : void 0;
}).filter((match) => match !== void 0);
const [freshestMatch, secondFreshestMatch] = [...resolvedMatches].toSorted((a, b) => b.updatedAt - a.updatedAt);
const resolvedKey = resolvedMatches.length === 1 ? freshestMatch?.key : (freshestMatch?.updatedAt ?? 0) > (secondFreshestMatch?.updatedAt ?? 0) ? freshestMatch?.key : void 0;
if (resolvedKey) {
if (!TRANSCRIPT_SESSION_KEY_CACHE.has(targetPath) && TRANSCRIPT_SESSION_KEY_CACHE.size >= TRANSCRIPT_SESSION_KEY_CACHE_MAX) {
const oldest = TRANSCRIPT_SESSION_KEY_CACHE.keys().next().value;
if (oldest !== void 0) TRANSCRIPT_SESSION_KEY_CACHE.delete(oldest);
}
TRANSCRIPT_SESSION_KEY_CACHE.set(targetPath, resolvedKey);
return resolvedKey;
}
}
TRANSCRIPT_SESSION_KEY_CACHE.delete(targetPath);
}
//#endregion
//#region src/gateway/server-session-events.ts
function resolveSessionMessageBroadcastKeys(sessionKey, agentId) {
const normalizedAgentId = normalizeOptionalString(agentId);
if (sessionKey === "global") {
const defaultAgentId = normalizeAgentId(resolveDefaultAgentId(getRuntimeConfig()));
if (normalizedAgentId) {
const scopedKey = `agent:${normalizeAgentId(normalizedAgentId)}:global`;
return normalizeAgentId(normalizedAgentId) === defaultAgentId ? [scopedKey, sessionKey] : [scopedKey];
}
return [`agent:${defaultAgentId}:global`, sessionKey];
}
return [sessionKey];
}
function buildGatewaySessionSnapshot(params) {
const { sessionRow } = params;
if (!sessionRow) return {};
const omitUnscopedGlobalGoal = sessionRow.key === "global" && !params.agentId;
const session = params.includeSession ? { ...sessionRow } : void 0;
if (session && omitUnscopedGlobalGoal) delete session.goal;
return {
...session ? { session } : {},
updatedAt: sessionRow.updatedAt ?? void 0,
sessionId: sessionRow.sessionId,
kind: sessionRow.kind,
channel: sessionRow.channel,
subject: sessionRow.subject,
groupChannel: sessionRow.groupChannel,
space: sessionRow.space,
chatType: sessionRow.chatType,
origin: sessionRow.origin,
spawnedBy: sessionRow.spawnedBy,
spawnedWorkspaceDir: sessionRow.spawnedWorkspaceDir,
spawnedCwd: sessionRow.spawnedCwd,
forkedFromParent: sessionRow.forkedFromParent,
spawnDepth: sessionRow.spawnDepth,
subagentRole: sessionRow.subagentRole,
subagentControlScope: sessionRow.subagentControlScope,
label: params.label ?? sessionRow.label,
displayName: params.displayName ?? sessionRow.displayName,
deliveryContext: sessionRow.deliveryContext,
parentSessionKey: params.parentSessionKey ?? sessionRow.parentSessionKey,
childSessions: sessionRow.childSessions,
thinkingLevel: sessionRow.thinkingLevel,
fastMode: sessionRow.fastMode,
verboseLevel: sessionRow.verboseLevel,
reasoningLevel: sessionRow.reasoningLevel,
elevatedLevel: sessionRow.elevatedLevel,
sendPolicy: sessionRow.sendPolicy,
systemSent: sessionRow.systemSent,
abortedLastRun: sessionRow.abortedLastRun,
inputTokens: sessionRow.inputTokens,
outputTokens: sessionRow.outputTokens,
lastChannel: sessionRow.lastChannel,
lastTo: sessionRow.lastTo,
lastAccountId: sessionRow.lastAccountId,
lastThreadId: sessionRow.lastThreadId,
totalTokens: sessionRow.totalTokens,
totalTokensFresh: sessionRow.totalTokensFresh,
...omitUnscopedGlobalGoal ? {} : { goal: sessionRow.goal ?? null },
contextTokens: sessionRow.contextTokens,
estimatedCostUsd: sessionRow.estimatedCostUsd,
responseUsage: sessionRow.responseUsage,
modelProvider: sessionRow.modelProvider,
model: sessionRow.model,
status: sessionRow.status,
subagentRunState: sessionRow.subagentRunState,
hasActiveSubagentRun: sessionRow.hasActiveSubagentRun,
startedAt: sessionRow.startedAt,
endedAt: sessionRow.endedAt,
runtimeMs: sessionRow.runtimeMs,
compactionCheckpointCount: sessionRow.compactionCheckpointCount,
latestCompactionCheckpoint: sessionRow.latestCompactionCheckpoint
};
}
/** Creates a serialized transcript-update broadcaster for session websocket clients. */
function createTranscriptUpdateBroadcastHandler(params) {
let broadcastQueue = Promise.resolve();
return (update) => {
broadcastQueue = broadcastQueue.then(() => handleTranscriptUpdateBroadcast(params, update)).catch(() => void 0);
};
}
async function handleTranscriptUpdateBroadcast(params, update) {
const sessionKey = update.sessionKey ?? resolveSessionKeyForTranscriptFile(update.sessionFile);
if (!sessionKey || update.message === void 0) return;
const effectiveAgentId = update.agentId;
const defaultGlobalAgentId = sessionKey === "global" ? normalizeAgentId(resolveDefaultAgentId(getRuntimeConfig())) : void 0;
const visibleAgentId = update.agentId ?? (effectiveAgentId && effectiveAgentId !== defaultGlobalAgentId ? effectiveAgentId : void 0);
const connIds = /* @__PURE__ */ new Set();
for (const connId of params.sessionEventSubscribers.getAll()) connIds.add(connId);
for (const broadcastKey of resolveSessionMessageBroadcastKeys(sessionKey, effectiveAgentId)) for (const connId of params.sessionMessageSubscribers.get(broadcastKey)) connIds.add(connId);
if (connIds.size === 0) return;
let messageSeq = asPositiveSafeInteger(update.messageSeq);
if (messageSeq === void 0) {
const { entry, storePath } = loadSessionEntry(sessionKey, { agentId: visibleAgentId });
messageSeq = entry?.sessionId ? asPositiveSafeInteger(await readSessionMessageCountAsync(entry.sessionId, storePath, entry.sessionFile)) : void 0;
}
const sessionSnapshot = buildGatewaySessionSnapshot({
sessionRow: loadGatewaySessionRow(sessionKey, {
agentId: visibleAgentId,
transcriptUsageMaxBytes: 64 * 1024
}),
agentId: visibleAgentId,
includeSession: true
});
const message = projectChatDisplayMessage(attachOpenClawTranscriptMeta(update.message, {
...typeof update.messageId === "string" ? { id: update.messageId } : {},
...messageSeq !== void 0 ? { seq: messageSeq } : {}
}));
if (message) {
params.broadcastToConnIds("session.message", {
sessionKey,
...visibleAgentId ? { agentId: visibleAgentId } : {},
message,
...typeof update.messageId === "string" ? { messageId: update.messageId } : {},
...messageSeq !== void 0 ? { messageSeq } : {},
...sessionSnapshot
}, connIds, { dropIfSlow: true });
return;
}
const sessionEventConnIds = params.sessionEventSubscribers.getAll();
if (sessionEventConnIds.size === 0) return;
params.broadcastToConnIds("sessions.changed", {
sessionKey,
...visibleAgentId ? { agentId: visibleAgentId } : {},
phase: "message",
ts: Date.now(),
...typeof update.messageId === "string" ? { messageId: update.messageId } : {},
...messageSeq !== void 0 ? { messageSeq } : {},
...sessionSnapshot
}, sessionEventConnIds, { dropIfSlow: true });
}
/** Creates a lifecycle-event broadcaster for session list refreshes. */
function createLifecycleEventBroadcastHandler(params) {
return (event) => {
const connIds = params.sessionEventSubscribers.getAll();
if (connIds.size === 0) return;
params.broadcastToConnIds("sessions.changed", {
sessionKey: event.sessionKey,
reason: event.reason,
parentSessionKey: event.parentSessionKey,
label: event.label,
displayName: event.displayName,
ts: Date.now(),
...buildGatewaySessionSnapshot({
sessionRow: loadGatewaySessionRow(event.sessionKey),
label: event.label,
displayName: event.displayName,
parentSessionKey: event.parentSessionKey
})
}, connIds, { dropIfSlow: true });
};
}
//#endregion
export { createLifecycleEventBroadcastHandler, createTranscriptUpdateBroadcastHandler };