openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
557 lines (556 loc) • 22.5 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-6kR-86XC.mjs";
import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.mjs";
import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs";
import { t as AgentSelectionRequiredError } from "./agent-scope-config-BU4qc9bA.mjs";
import { A as parseAgentSessionKey, _ as toAgentStoreSessionKey, l as resolveAgentIdFromSessionKey } from "./session-key-BC4m_Ly5.mjs";
import { g as resolveSessionAgentId } from "./agent-scope-DWB4x52T.mjs";
import { s as readSessionTranscriptUpdateVersion } from "./transcript-events-Ce9wKss8.mjs";
import { _ as validateArtifactsGetParams, g as validateArtifactsDownloadParams, v as validateArtifactsListParams } from "./src-D2gvuSyP.mjs";
import { t as ErrorCodes } from "./gateway-error-details-DIefe7Zy.mjs";
import { f as errorShape } from "./error-codes-DI20vDEB.mjs";
import { r as isSessionTranscriptProjectionUnavailableError } from "./session-transcript-projection-error-B7C6SQei.mjs";
import { r as resolveSessionTranscriptReadFence } from "./session-transcript-read-fence--kRTH14M.mjs";
import { a as tryResolveSessionCompatibilityOwnerAgentId, n as resolveRequestedSessionAgentId } from "./session-request-agent-D8Xj-iBG.mjs";
import { a as resolveStoredSessionKeyForAgentStore, n as resolveSessionStoreAgentId } from "./session-store-key-11PJMhso.mjs";
import "./session-accessor.sqlite-active-events-DgaYrqkj.mjs";
import { a as readGatewayRequestMutationAuthority } from "./session-mutation-guards-C7E56jUW.mjs";
import { n as readSessionArtifacts } from "./session-transcript-readers-42zk6rbI.mjs";
import { _ as resolveSessionSharingTarget, n as authorizeIncognitoSessionTarget } from "./session-sharing-policy-QAgS-DcQ.mjs";
import { n as getSessionRowProjection } from "./session-row-projection-access-CUCrJD4M.mjs";
import { i as loadGatewaySessionEntryReadOnly } from "./session-utils-store-CA6jfef8.mjs";
import { b as createSessionListEntryFilter } from "./session-sharing-KNQSWoOJ.mjs";
import "./session-utils-DrY1lc9W.mjs";
import { t as parseTranscriptImageArtifactId } from "./transcript-image-artifacts-JZUt0-cM.mjs";
import { f as resolveManagedOutgoingMediaArtifactDownload, p as resolveManagedOutgoingMediaUrlDownload } from "./managed-image-attachments-bqFcPGbP.mjs";
import { i as parseManagedOutgoingArtifactId } from "./managed-outgoing-artifact-id-D000BooM.mjs";
import { t as assertValidParams } from "./validation-B7TobU6-.mjs";
import { n as createArtifactDownload, t as canCreateArtifactDownload } from "./artifact-downloads-Dij24LKD.mjs";
import { i as toArtifactSummary } from "./artifacts-content-nSEZ5B0t.mjs";
import { t as resolveSessionKeyForRun } from "./server-session-key-CXTQnCMm.mjs";
import { randomUUID } from "node:crypto";
//#region src/gateway/server-methods/artifacts-session-resolution.ts
function resolveArtifactSessionAgentId(sessionKey, cfg) {
const key = normalizeOptionalString(sessionKey);
if (!key) return;
const parsed = parseAgentSessionKey(key);
if (!parsed && key.toLowerCase().startsWith("agent:")) return;
if (cfg) {
const owner = resolveRequestedSessionAgentId(cfg, key);
if (!owner.ok) throw new ArtifactSessionResolutionError(owner.error);
return owner.agentId;
}
return parsed?.agentId ?? resolveAgentIdFromSessionKey(key);
}
function resolveScopedArtifactSessionKey(sessionKey, agentId, cfg) {
const key = normalizeOptionalString(sessionKey);
if (!key) return;
const scopedAgentId = normalizeOptionalString(agentId);
if (!scopedAgentId) return key;
const parsed = parseAgentSessionKey(key);
if (!parsed && key.toLowerCase().startsWith("agent:")) return;
if (!cfg) return parsed && parsed.agentId !== normalizeAgentId(scopedAgentId) ? void 0 : toAgentStoreSessionKey({
agentId: scopedAgentId,
requestKey: key
});
const scopedKey = resolveStoredSessionKeyForAgentStore({
cfg,
agentId: scopedAgentId,
sessionKey: key
});
return scopedKey !== "global" && scopedKey !== "unknown" && resolveSessionStoreAgentId(cfg, scopedKey) !== normalizeAgentId(scopedAgentId) ? void 0 : scopedKey;
}
function resolveQuerySession(query, cfg, projection) {
if (query.sessionKey) {
const sessionKey = resolveScopedArtifactSessionKey(query.sessionKey, query.agentId, cfg);
return sessionKey ? {
sessionKey,
...query.agentId ? { agentId: query.agentId } : {}
} : void 0;
}
if (query.runId) {
const sessionKey = resolveSessionKeyForRun(query.runId, {
...query.agentId ? { agentId: query.agentId } : {},
...projection ? { projection } : {}
});
const agentId = query.agentId ?? resolveArtifactSessionAgentId(sessionKey, cfg) ?? resolveSessionAgentId({ config: cfg });
const scopedSessionKey = resolveScopedArtifactSessionKey(sessionKey, agentId, cfg);
return scopedSessionKey ? {
sessionKey: scopedSessionKey,
agentId
} : void 0;
}
}
var ArtifactSessionResolutionError = class extends Error {
constructor(shape) {
super(shape.message);
this.shape = shape;
}
};
function artifactResponseIsCurrent(found, respond) {
try {
found.assertCurrent?.();
return true;
} catch (error) {
if (!(error instanceof ArtifactSessionResolutionError)) throw error;
respond(false, void 0, error.shape);
return false;
}
}
async function prepareArtifactSessionResolution(input, projection) {
const query = { ...input };
if (!query.sessionKey && query.runId && projection?.sharingRevision === void 0) await projection?.ensureMaterialized();
return (cfg, client) => {
const sessionKey = normalizeOptionalString(query.sessionKey);
let scopedQuery = query;
if (sessionKey && cfg) {
const owner = resolveRequestedSessionAgentId(cfg, sessionKey, query.agentId);
if (!owner.ok) throw new ArtifactSessionResolutionError(owner.error);
scopedQuery = {
...query,
agentId: owner.agentId
};
}
const resolved = resolveQuerySession(scopedQuery, cfg, projection);
if (!resolved) return;
const target = resolveSessionSharingTarget({
cfg: cfg ?? {},
sessionKey: resolved.sessionKey,
agentId: resolved.agentId
});
const error = authorizeIncognitoSessionTarget({
client,
sessionKey: query.sessionKey ?? resolved.sessionKey,
target
});
const visibilityDenied = Boolean(target && createSessionListEntryFilter({
client,
cfg
})?.(target.storeKey, target.entry) === false);
if (!error && !visibilityDenied) return resolved;
throw new ArtifactSessionResolutionError(query.sessionKey && error ? error : errorShape(ErrorCodes.INVALID_REQUEST, "no session found for artifact query", { details: { type: "artifact_scope_not_found" } }));
};
}
//#endregion
//#region src/gateway/server-methods/artifacts-image-page.ts
const CURSOR_TTL_MS = 9e5;
const cursors = /* @__PURE__ */ new WeakMap();
const internalCaller = {};
async function readArtifactImagePage(params) {
const owner = params.client ?? internalCaller;
let state = cursors.get(owner);
if (!state) {
state = /* @__PURE__ */ new Map();
cursors.set(owner, state);
}
const now = Date.now();
for (const [key, value] of state) if (value.expiresAt <= now) state.delete(key);
const cursor = params.cursor ? state.get(params.cursor) : void 0;
if (params.cursor && (!cursor || cursor.binding !== params.binding)) throw new ArtifactSessionResolutionError(errorShape(ErrorCodes.INVALID_REQUEST, "Image cursor expired or belongs to another session; restart artifacts.list", { details: { type: "artifact_cursor_invalid" } }));
const page = await readSessionArtifacts(params.scope, {
kind: "image-page",
sessionKey: params.sessionKey,
...params.filters,
limit: params.limit,
beforeSeq: cursor?.beforeSeq,
imageOffset: cursor?.imageOffset,
readWindow: cursor?.readWindow
}).catch((error) => {
if (cursor && isSessionTranscriptProjectionUnavailableError(error)) throw new ArtifactSessionResolutionError(errorShape(ErrorCodes.INVALID_REQUEST, "Transcript changed; restart artifacts.list", { details: { type: "artifact_cursor_invalid" } }));
throw error;
});
let nextCursor;
if (page.next) {
nextCursor = randomUUID();
state.set(nextCursor, {
...page.next,
binding: params.binding,
expiresAt: now + CURSOR_TTL_MS
});
pruneMapToMaxSize(state, 128);
}
return {
artifacts: page.artifacts,
...nextCursor ? { nextCursor } : {},
...page.omittedOversized ? { omittedOversized: true } : {}
};
}
//#endregion
//#region src/gateway/server-methods/artifacts-session-read.ts
/** Retain the selected physical transcript independently of later query resolution. */
async function prepareArtifactSessionRead(query, getRuntimeConfig, client, projection) {
const resolveSession = await prepareArtifactSessionResolution(query, projection);
const resolved = resolveSession(getRuntimeConfig(), client);
if (!resolved) return;
const { sessionKey } = resolved;
const unscopedAgentId = parseAgentSessionKey(sessionKey) ? void 0 : resolved.agentId;
const readEntry = () => unscopedAgentId ? loadGatewaySessionEntryReadOnly(sessionKey, { agentId: unscopedAgentId }) : loadGatewaySessionEntryReadOnly(sessionKey);
const { storePath, entry } = readEntry();
const sessionId = entry?.sessionId;
if (!sessionId || !storePath) return { sessionKey };
const lifecycleRevision = entry.lifecycleRevision;
return {
sessionKey,
scope: {
agentId: resolved.agentId ?? resolveAgentIdFromSessionKey(sessionKey),
sessionEntry: entry,
sessionId,
sessionKey,
storePath
},
assertCurrent: () => {
const authorized = resolveSession(getRuntimeConfig(), client);
const current = readEntry();
if (authorized?.sessionKey !== sessionKey || authorized.agentId !== resolved.agentId || current.storePath !== storePath || current.entry?.sessionId !== sessionId || current.entry.lifecycleRevision !== lifecycleRevision) throw new ArtifactSessionResolutionError(errorShape(ErrorCodes.UNAVAILABLE, "session changed while reading artifact; reload the conversation", { retryable: true }));
}
};
}
//#endregion
//#region src/gateway/server-methods/artifacts-transcript-images.ts
/** Recover only the referenced persisted bitmap; transcript bytes remain in their existing owner. */
async function findTranscriptImageArtifact(params, getRuntimeConfig, includeData, client, projection) {
const selected = await prepareArtifactSessionRead(params, getRuntimeConfig, client, projection);
if (!selected?.scope) return { sessionKey: selected?.sessionKey };
const { artifact } = await readSessionArtifacts(selected.scope, {
kind: "image",
sessionKey: selected.sessionKey,
artifactId: params.artifactId,
includeData,
runId: params.runId,
messageRole: params.messageRole
});
selected.assertCurrent();
return {
sessionKey: selected.sessionKey,
assertCurrent: selected.assertCurrent,
artifact
};
}
//#endregion
//#region src/gateway/server-methods/artifacts.ts
const queuedArtifactDownloads = /* @__PURE__ */ new Map();
function createArtifactRequestAccess(request) {
const { assertCurrent } = readGatewayRequestMutationAuthority(request);
const { context, respond } = request;
assertCurrent();
return {
assertCurrent,
projection: getSessionRowProjection(context),
getRuntimeConfig: () => {
assertCurrent();
return context.getRuntimeConfig?.();
},
respond: (...args) => {
assertCurrent();
respond(...args);
}
};
}
function artifactError(type, message, details) {
return errorShape(ErrorCodes.INVALID_REQUEST, message, { details: {
type,
...details
} });
}
/** Loads artifacts from the transcript selected by sessionKey or runId. */
async function loadArtifacts(query, getRuntimeConfig, opts = {}, client = null) {
const selected = await prepareArtifactSessionRead(query, getRuntimeConfig, client, opts.projection);
if (!selected?.scope) return {
sessionKey: selected?.sessionKey,
artifacts: []
};
const { sessionKey, scope, assertCurrent } = selected;
const { storePath, sessionId, sessionEntry: entry } = scope;
if (query.type === "image") {
const page = await readArtifactImagePage({
scope,
binding: JSON.stringify([
sessionKey,
scope.agentId,
sessionId,
query.runId,
query.messageRole
]),
client,
cursor: query.cursor,
limit: query.limit ?? 4,
sessionKey,
filters: {
runId: query.runId,
messageRole: query.messageRole
}
});
assertCurrent();
return {
...page,
sessionKey,
assertCurrent
};
}
const downloadIds = opts.downloadArtifactId ? /* @__PURE__ */ new Set([opts.downloadArtifactId]) : void 0;
const queuedKey = downloadIds && !resolveSessionTranscriptReadFence(scope) ? JSON.stringify([
storePath,
scope.agentId,
sessionKey,
sessionId,
entry?.lifecycleRevision,
query.runId,
query.messageRole,
readSessionTranscriptUpdateVersion()
]) : void 0;
const queued = queuedKey ? queuedArtifactDownloads.get(queuedKey) : void 0;
if (queued && opts.downloadArtifactId) {
queued.ids.add(opts.downloadArtifactId);
const loaded = await queued.result;
assertCurrent();
return {
...loaded,
assertCurrent
};
}
const collect = async () => {
if (queuedKey) queuedArtifactDownloads.delete(queuedKey);
const { artifacts } = await readSessionArtifacts(scope, {
kind: "list",
sessionKey,
runId: query.runId,
messageRole: query.messageRole,
includeDownloadData: opts.includeDownloadData,
downloadArtifactIds: downloadIds ? [...downloadIds] : void 0
});
return {
sessionKey,
artifacts
};
};
const result = queuedKey && downloadIds ? Promise.resolve().then(collect) : collect();
if (queuedKey && downloadIds) queuedArtifactDownloads.set(queuedKey, {
ids: downloadIds,
result
});
const loaded = await result;
assertCurrent();
return {
...loaded,
assertCurrent
};
}
function requireQueryable(params, respond) {
if (params.sessionKey || params.runId) return true;
respond(false, void 0, artifactError("artifact_query_unsupported", "artifacts require sessionKey or runId"));
return false;
}
function respondArtifactNotFound(respond, requestedArtifactId) {
respond(false, void 0, artifactError("artifact_not_found", "artifact not found", { artifactId: requestedArtifactId }));
}
async function runArtifactSessionOperation(respond, operation) {
try {
return {
ok: true,
value: await operation()
};
} catch (error) {
if (error instanceof ArtifactSessionResolutionError) {
respond(false, void 0, error.shape);
return { ok: false };
}
if (error instanceof AgentSelectionRequiredError) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, error.message));
return { ok: false };
}
throw error;
}
}
async function findArtifact(params, getRuntimeConfig, opts = {}, client = null) {
if (parseTranscriptImageArtifactId(params.artifactId)) return findTranscriptImageArtifact(params, getRuntimeConfig, opts.includeDownloadData !== false, client, opts.projection);
const loaded = await loadArtifacts(params, getRuntimeConfig, opts, client);
return {
sessionKey: loaded.sessionKey,
artifact: loaded.artifacts.find((artifact) => artifact.id === params.artifactId),
assertCurrent: loaded.assertCurrent
};
}
async function prepareDownload(query, getRuntimeConfig, client, projection) {
const selected = await prepareArtifactSessionRead(query, getRuntimeConfig, client, projection);
if (!selected?.scope) return { sessionKey: selected?.sessionKey };
const { selection } = await readSessionArtifacts(selected.scope, {
...query,
kind: "download-grant",
sessionKey: selected.sessionKey
});
selected.assertCurrent();
return {
sessionKey: selected.sessionKey,
assertCurrent: selected.assertCurrent,
...selection?.kind === "prepared" ? {
artifact: selection.download.artifact,
download: selection.download
} : { artifact: selection?.artifact }
};
}
async function respondManagedArtifactDownload(query, getRuntimeConfig, client, respond, projection, matched) {
await runArtifactSessionOperation(respond, async () => {
const resolveSession = await prepareArtifactSessionResolution(query, projection);
const cfg = getRuntimeConfig();
const resolved = resolveSession(cfg, client);
const defaultAgentId = resolved ? tryResolveSessionCompatibilityOwnerAgentId(cfg ?? {}, resolved.sessionKey) : void 0;
const managed = resolved && (!matched || matched.sessionKey === resolved.sessionKey) ? await resolveManagedOutgoingMediaArtifactDownload({
sessionKey: resolved.sessionKey,
...resolved.agentId ? { agentId: resolved.agentId } : {},
...defaultAgentId ? { defaultAgentId } : {},
artifactId: query.artifactId
}) : null;
if (!managed) {
respondArtifactNotFound(respond, query.artifactId);
return;
}
respond(true, {
artifact: {
id: managed.artifactId,
type: managed.type,
title: managed.title,
...managed.mimeType ? { mimeType: managed.mimeType } : {},
...managed.sizeBytes !== void 0 ? { sizeBytes: managed.sizeBytes } : {},
sessionKey: managed.sessionKey,
...matched?.runId ? { runId: matched.runId } : {},
...matched?.messageSeq !== void 0 ? { messageSeq: matched.messageSeq } : {},
source: "session-transcript",
download: { mode: "url" }
},
url: managed.url,
expiresAt: managed.expiresAt
});
});
}
/** Gateway handlers for listing, summarizing, and downloading transcript artifacts. */
const artifactsHandlers = {
"artifacts.list": async (request) => {
const { params, client } = request;
const { getRuntimeConfig, respond, projection } = createArtifactRequestAccess(request);
if (!assertValidParams(params, validateArtifactsListParams, "artifacts.list", respond)) return;
if (!requireQueryable(params, respond)) return;
if (params.type !== "image" && (params.limit !== void 0 || params.cursor !== void 0)) {
respond(false, void 0, artifactError("artifact_query_unsupported", "limit and cursor require type image"));
return;
}
const query = { ...params };
const loaded = await runArtifactSessionOperation(respond, () => loadArtifacts(query, getRuntimeConfig, {
includeDownloadData: false,
projection
}, client));
if (!loaded.ok) return;
const { artifacts, sessionKey, nextCursor, omittedOversized } = loaded.value;
if (!sessionKey && query.runId) {
respond(false, void 0, artifactError("artifact_scope_not_found", "no session found for artifact query"));
return;
}
respond(true, {
artifacts: artifacts.map(toArtifactSummary),
...nextCursor ? { nextCursor } : {},
...omittedOversized ? { omittedOversized: true } : {}
});
},
"artifacts.get": async (request) => {
const { params, client } = request;
const { getRuntimeConfig, respond, projection } = createArtifactRequestAccess(request);
if (!assertValidParams(params, validateArtifactsGetParams, "artifacts.get", respond)) return;
if (!requireQueryable(params, respond)) return;
const query = { ...params };
const found = await runArtifactSessionOperation(respond, () => findArtifact(query, getRuntimeConfig, {
includeDownloadData: false,
projection
}, client));
if (!found.ok) return;
const { artifact } = found.value;
if (!artifact) {
respondArtifactNotFound(respond, query.artifactId);
return;
}
if (artifactResponseIsCurrent(found.value, respond)) respond(true, { artifact: toArtifactSummary(artifact) });
},
"artifacts.download": async (request) => {
const { params, client } = request;
const { getRuntimeConfig, respond, assertCurrent, projection } = createArtifactRequestAccess(request);
if (!assertValidParams(params, validateArtifactsDownloadParams, "artifacts.download", respond)) return;
if (!requireQueryable(params, respond)) return;
const query = { ...params };
if (query.sessionKey && !query.runId && !query.messageRole && parseManagedOutgoingArtifactId(query.artifactId)) {
await respondManagedArtifactDownload(query, getRuntimeConfig, client, respond, projection);
return;
}
let found = await runArtifactSessionOperation(respond, () => query.transport === "http" && canCreateArtifactDownload(client) ? prepareDownload(query, getRuntimeConfig, client, projection) : findArtifact(query, getRuntimeConfig, {
downloadArtifactId: query.artifactId,
projection
}, client));
assertCurrent();
if (found.ok && found.value.download && !canCreateArtifactDownload(client)) {
found = await runArtifactSessionOperation(respond, () => findArtifact(query, getRuntimeConfig, {
downloadArtifactId: query.artifactId,
projection
}, client));
assertCurrent();
}
if (!found.ok) return;
const { artifact } = found.value;
if (!artifact) {
respondArtifactNotFound(respond, query.artifactId);
return;
}
if (parseManagedOutgoingArtifactId(artifact.id)) {
await respondManagedArtifactDownload(query, getRuntimeConfig, client, respond, projection, artifact);
return;
}
if (artifact.download.mode === "unsupported") {
respond(false, void 0, artifactError("artifact_download_unsupported", "artifact download is unsupported", { artifactId: artifact.id }));
return;
}
if (found.value.download) {
const assertArtifactCurrent = found.value.assertCurrent;
const download = createArtifactDownload({
client,
prepared: found.value.download,
assertCurrent: () => {
assertCurrent();
assertArtifactCurrent?.();
},
read: async (response) => {
const selected = await prepareArtifactSessionRead(query, getRuntimeConfig, client, projection);
if (!selected?.scope) return;
const current = await readSessionArtifacts(selected.scope, {
...query,
kind: "download-response",
sessionKey: selected.sessionKey,
response
});
selected.assertCurrent();
return current.response;
}
});
respond(true, {
artifact: {
...toArtifactSummary(artifact),
download: { mode: "url" }
},
...download
});
return;
}
const managedUrl = artifact.download.mode === "url" && artifact.url && artifact.sessionKey ? await resolveManagedOutgoingMediaUrlDownload({
sessionKey: artifact.sessionKey,
url: artifact.url
}) : null;
if (!artifactResponseIsCurrent(found.value, respond)) return;
respond(true, {
artifact: toArtifactSummary(artifact),
...artifact.download.mode === "bytes" ? {
encoding: "base64",
data: artifact.data
} : {},
...artifact.download.mode === "url" ? {
url: managedUrl?.url ?? artifact.url,
...managedUrl ? { expiresAt: managedUrl.expiresAt } : {}
} : {}
});
}
};
//#endregion
export { artifactsHandlers };