openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
513 lines (512 loc) • 20.8 kB
JavaScript
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { m as resolveAgentWorkspaceDir, o as listAgentIds } from "./agent-scope-config-DcbEhP0R.js";
import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.js";
import "./session-key-BnWWjqNc.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as createDeferredCore } from "./deferred-D0La5CRk.js";
import { S as resolveRuntimeConfigCacheKey } from "./runtime-snapshot-BaQikjTR.js";
import { A as resolveCollapsedSessionAuthPinSource, j as resolveSessionAuthProfileOverrideSource } from "./agent-scope-DbtJyKUL.js";
import { f as getActivePluginRegistryVersion } from "./runtime-BL4wZfTq.js";
import { a as parseUserModelAuthProfileId, i as isUserModelAuthProfileId } from "./profile-usage-stats-dkZh2v8y.js";
import { s as readUserModelAccountSummary } from "./user-model-accounts-BMkC9h8s.js";
import { l as getRuntimeAuthProfileStoreSnapshotRevision } from "./runtime-snapshots-CHAErv1O.js";
import { s as getPreparedRuntimeAuthProfileStoreSnapshot } from "./store-F1B2duCT.js";
import { n as getPreparedModelFullCatalogAuth, r as getPreparedModelRuntimeAuthMaterializations } from "./prepared-model-runtime-auth-CnrySjUa.js";
import { c as resolveUserProfileId, p as getUserProfileDisplay } from "./user-profiles-4AB7AmiH.js";
import { i as getPublishedPreparedModelCatalogOwnerSnapshot } from "./prepared-model-catalog-Bt4dYhnY.js";
import "./auth-profiles-BdUEhE7u.js";
import { r as getSkillsSnapshotVersion } from "./refresh-state-DHnXO3IV.js";
import { n as resolveSessionModelRef } from "./session-model-ref-CPZiclLt.js";
import { t as readSessionRuntimeOwnership } from "./session-runtime-ownership-PPRaZqWL.js";
import { t as resolveSwarmConfig } from "./swarm-config-XTe8M2h4.js";
//#region src/gateway/server-methods/chat-account-selection.ts
/** The session owns this preference; it is not a receipt for the account that served a turn. */
function resolveChatAccountSelection(params) {
const authProfileId = params.sessionEntry?.authProfileOverride?.trim();
if (!authProfileId) return {
kind: "automatic",
label: "Automatic account selection"
};
const source = resolveSessionAuthProfileOverrideSource(params.sessionEntry);
if (!isUserModelAuthProfileId(authProfileId)) return {
kind: "shared",
authProfileId,
label: (params.authStore.profiles[authProfileId]?.displayName?.trim() || authProfileId).slice(0, 256),
source
};
const personal = params.requesterProfileId ? readUserModelAccountSummary({
profileId: params.requesterProfileId,
authProfileId
}) : void 0;
if (personal) return {
kind: "personal",
authProfileId,
label: personal.label,
source
};
const locator = parseUserModelAuthProfileId(authProfileId);
const owner = locator ? resolveUserProfileId(locator.ownerProfileId) : void 0;
const displayName = owner ? getUserProfileDisplay(owner).displayName?.trim() : void 0;
return {
kind: "personal",
label: displayName ? `${displayName}'s account`.slice(0, 256) : "Personal account",
source
};
}
//#endregion
//#region src/gateway/server-methods/chat-metadata-session-projection.ts
async function prepareChatMetadataModelProjection(params) {
const { prepareModelsListResult, createGatewayAgentModelCatalogProjector } = await import("./models-list-result-C6qbOlEO.js");
params.assertCurrent?.();
const snapshot = params.facts.modelCatalog;
const projector = createGatewayAgentModelCatalogProjector({
cfg: params.facts.owner.config,
agentId: params.facts.agentId,
snapshot,
metadataSnapshot: params.facts.owner.metadataSnapshot,
preparedAuthStore: params.facts.authStore,
requesterProfileId: params.requesterProfileId,
preparedRuntimeAuthModes: params.facts.authModes,
preparedRuntimeAuthMaterializations: getPreparedModelRuntimeAuthMaterializations(params.facts.owner),
pluginRegistry: params.facts.owner.pluginRegistry,
isCurrent: params.facts.owner.isCurrent,
observationConfig: params.facts.owner.observationConfig,
...params.preferredProfileId ? { preferredProfileId: params.preferredProfileId } : {},
...params.pinnedProfileId ? { pinnedProfileId: params.pinnedProfileId } : {}
});
const [modelCatalog, readModels] = await Promise.all([projector.projectCatalog(), prepareModelsListResult({
context: params.context,
agentId: params.facts.agentId,
params: { view: "configured" },
preloadedCatalog: {
agentId: params.facts.agentId,
config: params.facts.owner.config,
snapshot
},
preloadedOnly: true,
catalogProjector: projector
})]);
return {
modelCatalog,
read: () => ({ models: readModels.read().models }),
isCurrent: readModels.isCurrent
};
}
function projectChatSessionMetadata(readParams, metadata, config) {
const ownership = readSessionRuntimeOwnership({
...readParams,
config
});
if (ownership?.auth !== "native" || !metadata.models) return metadata;
const renderedModel = ownership.modelRef ?? resolveSessionModelRef(config, readParams.sessionEntry, readParams.agentId, { allowPluginNormalization: false });
return {
...metadata,
models: metadata.models.map((model) => {
const row = asOptionalRecord(model);
if (row?.provider !== renderedModel.provider || row.id !== renderedModel.model) return model;
const { available: _available, unavailableReason: _reason, unavailableUntil: _until, ...native } = row;
return native;
})
};
}
//#endregion
//#region src/gateway/server-methods/chat-metadata-runtime.ts
const CHAT_METADATA_CACHE_MAX_ENTRIES = 64;
function createMetadataReplacement() {
const replacement = createDeferredCore();
replacement.promise.catch(() => {});
return replacement;
}
var ChatMetadataSnapshotUnavailableError = class extends Error {
constructor(message = "prepared chat metadata snapshot is unavailable") {
super(message);
this.name = "ChatMetadataSnapshotUnavailableError";
}
};
function captureGenerationFacts(deps) {
const config = deps.getConfig();
const agents = listAgentIds(config).map((rawAgentId) => {
const agentId = normalizeAgentId(rawAgentId);
const owner = deps.getPreparedOwner({
agentId,
config
});
if (!owner) throw new ChatMetadataSnapshotUnavailableError(`prepared chat metadata owner is unavailable for agent "${agentId}"`);
const workspaceDir = owner.workspaceDir ?? resolveAgentWorkspaceDir(config, agentId);
const fullModelCatalog = owner.readFullModelCatalog?.();
const fullCatalogAuth = fullModelCatalog ? getPreparedModelFullCatalogAuth(fullModelCatalog) : void 0;
if (fullModelCatalog && !fullCatalogAuth) throw new Error("prepared full model catalog omitted its auth generation");
return {
agentId,
owner,
authStore: fullCatalogAuth?.authStore ?? deps.getPreparedAuthStore(owner.agentDir, owner.inheritedAuthDir) ?? {
version: 1,
profiles: {}
},
authModes: fullCatalogAuth?.authModes ?? owner.authModes,
authStoreRevision: `${deps.getAuthStoreRevision(owner.agentDir)}:${deps.getAuthStoreRevision(owner.inheritedAuthDir)}`,
modelCatalog: fullModelCatalog ?? owner.modelCatalog,
skillsVersion: deps.getSkillsVersion(workspaceDir)
};
});
return {
config,
configKey: resolveRuntimeConfigCacheKey(config),
pluginRegistryVersion: deps.getPluginRegistryVersion(),
agents
};
}
function generationFactsMatch(left, right) {
if (left.configKey !== right.configKey || left.pluginRegistryVersion !== right.pluginRegistryVersion || left.agents.length !== right.agents.length) return false;
return left.agents.every((agent, index) => {
const candidate = right.agents[index];
return candidate?.agentId === agent.agentId && candidate.owner === agent.owner && candidate.authStoreRevision === agent.authStoreRevision && candidate.modelCatalog === agent.modelCatalog && candidate.skillsVersion === agent.skillsVersion;
});
}
function resolveSessionProfiles(sessionEntry) {
const profileId = sessionEntry?.authProfileOverride?.trim();
if (!profileId) return {};
return {
preferredProfileId: profileId,
...resolveCollapsedSessionAuthPinSource(sessionEntry) === "user" ? { pinnedProfileId: profileId } : {}
};
}
function sessionProjectionKey(agentId, profiles) {
return [
normalizeAgentId(agentId),
profiles.preferredProfileId ?? "",
profiles.pinnedProfileId ?? ""
].join("\0");
}
async function defaultBuildCommands(params) {
const { buildCommandsListResult } = await import("./commands-list-result-CjzeHmCr.js");
return buildCommandsListResult({
cfg: params.cfg,
agentId: params.agentId,
includeArgs: true,
scope: "text"
});
}
function createGatewayChatMetadataRuntime(params) {
const deps = {
getConfig: params.getConfig,
getContext: params.getContext,
getPreparedOwner: getPublishedPreparedModelCatalogOwnerSnapshot,
getPreparedAuthStore: getPreparedRuntimeAuthProfileStoreSnapshot,
getAuthStoreRevision: getRuntimeAuthProfileStoreSnapshotRevision,
getSkillsVersion: getSkillsSnapshotVersion,
getPluginRegistryVersion: getActivePluginRegistryVersion,
buildCommands: defaultBuildCommands,
buildProjection: prepareChatMetadataModelProjection,
...params.deps
};
let current;
let lastError;
let replacement;
let invalidationEpoch = 0;
let refreshVersion = 0;
let lastSettlement;
let refreshTail = Promise.resolve();
let stoppedError;
const activeWork = /* @__PURE__ */ new Set();
const trackWork = (work) => {
activeWork.add(work);
const settled = () => activeWork.delete(work);
work.then(settled, settled);
return work;
};
const assertOpen = () => {
if (stoppedError) throw stoppedError;
};
let pending;
const projectAgent = async (generation, agent, sessionEntry, requesterProfileId, assertCurrent, useRequesterDefaults = false) => {
assertOpen();
assertCurrent?.();
const profiles = resolveSessionProfiles(sessionEntry);
const neutral = profiles.preferredProfileId === void 0 && profiles.pinnedProfileId === void 0;
const defaultProfileId = useRequesterDefaults ? requesterProfileId : void 0;
const projections = neutral && defaultProfileId || isUserModelAuthProfileId(profiles.preferredProfileId ?? "") ? /* @__PURE__ */ new Map() : neutral ? generation.neutralProjectionByAgentId : generation.sessionProjectionByKey;
const key = neutral ? agent.agentId : sessionProjectionKey(agent.agentId, profiles);
const existing = projections.get(key);
if (existing) {
const prepared = existing.state === "ready" ? existing.projection : await existing.promise;
if (prepared.isCurrent()) return prepared;
if (projections.get(key) === existing) projections.delete(key);
return projectAgent(generation, agent, sessionEntry, requesterProfileId, assertCurrent, useRequesterDefaults);
}
const projection = deps.buildProjection({
context: deps.getContext(),
facts: agent,
requesterProfileId: defaultProfileId,
...assertCurrent ? { assertCurrent } : {},
...profiles
}).then((prepared) => {
assertCurrent?.();
const preparedProjection = {
...prepared,
read: () => {
assertCurrent?.();
return {
...prepared.read(),
...agent.commands !== void 0 ? { commands: agent.commands } : {},
swarmEnabled: agent.swarmEnabled,
accountSelection: resolveChatAccountSelection({
authStore: agent.authStore,
sessionEntry,
requesterProfileId
})
};
}
};
if (generation.epoch === invalidationEpoch && projections.get(key) === entry) projections.set(key, {
state: "ready",
projection: preparedProjection
});
return preparedProjection;
}).catch((error) => {
if (projections.get(key) === entry) projections.delete(key);
throw error;
});
trackWork(projection);
const entry = {
state: "pending",
promise: projection
};
projections.set(key, entry);
if (!neutral) pruneMapToMaxSize(projections, CHAT_METADATA_CACHE_MAX_ENTRIES);
return projection;
};
const buildGeneration = async (facts, epoch) => {
const agents = await Promise.all(facts.agents.map(async (agent) => {
let commands;
try {
commands = (await trackWork(deps.buildCommands({
cfg: facts.config,
agentId: agent.agentId
}))).commands;
} catch (error) {
params.log.warn(`chat metadata continuing without text commands for ${agent.agentId}: ${formatErrorMessage(error)}`);
}
return {
...agent,
...commands !== void 0 ? { commands } : {},
swarmEnabled: resolveSwarmConfig(facts.config, agent.agentId).enabled
};
}));
const generation = {
epoch,
facts,
agentsById: new Map(agents.map((agent) => [agent.agentId, agent])),
neutralProjectionByAgentId: /* @__PURE__ */ new Map(),
sessionProjectionByKey: /* @__PURE__ */ new Map()
};
if (epoch !== invalidationEpoch) return;
await Promise.all(agents.map((agent) => projectAgent(generation, agent)));
return generation;
};
const runRefresh = async (version) => {
assertOpen();
await params.beforeRefresh?.();
if (version !== refreshVersion) return;
for (;;) {
const epoch = invalidationEpoch;
try {
const facts = captureGenerationFacts(deps);
if (current && generationFactsMatch(current.facts, facts)) return;
const generation = await buildGeneration(facts, epoch);
if (version !== refreshVersion) return;
if (generation && epoch === invalidationEpoch && generationFactsMatch(facts, captureGenerationFacts(deps))) {
current = generation;
return;
}
} catch (error) {
if (version !== refreshVersion) return;
if (epoch === invalidationEpoch) throw error;
}
}
};
const refresh = () => {
if (stoppedError) return Promise.reject(stoppedError);
const trackRefresh = (promise, facts) => {
refreshTail = promise;
pending = {
...facts ? { facts } : {},
promise
};
promise.then(() => {
if (pending?.promise !== promise) return;
pending = void 0;
if (current?.epoch !== invalidationEpoch) return;
lastError = void 0;
const committedReplacement = replacement;
replacement = void 0;
committedReplacement?.resolve();
if (lastSettlement !== current) {
lastSettlement = current;
params.onChanged?.();
}
}, (error) => {
if (pending?.promise !== promise) return;
pending = void 0;
fail(error);
});
return promise;
};
if (params.beforeRefresh) {
if (pending) return pending.promise;
const version = ++refreshVersion;
return trackRefresh(refreshTail.catch(() => {}).then(() => runRefresh(version)));
}
let facts;
try {
facts = captureGenerationFacts(deps);
} catch (error) {
const refreshError = error instanceof Error ? error : new Error(formatErrorMessage(error));
fail(refreshError);
return Promise.reject(refreshError);
}
if (current && generationFactsMatch(current.facts, facts)) return Promise.resolve();
if (pending?.facts && generationFactsMatch(pending.facts, facts)) return pending.promise;
const version = ++refreshVersion;
return trackRefresh(refreshTail.catch(() => {}).then(() => runRefresh(version)), facts);
};
const readCurrent = async (project) => {
for (;;) {
assertOpen();
const replacementPromise = replacement?.promise;
if (replacementPromise) {
await replacementPromise;
continue;
}
const refreshPromise = pending?.promise;
if (refreshPromise) {
await refreshPromise;
continue;
}
let generation = current;
const retryUnavailableOwner = lastError instanceof ChatMetadataSnapshotUnavailableError;
if (!generation && (params.refreshOnRead || retryUnavailableOwner)) {
await refresh();
generation = current;
}
if (!generation) {
if (lastError) throw lastError;
throw new ChatMetadataSnapshotUnavailableError();
}
if (params.refreshOnRead) {
let latest;
try {
latest = captureGenerationFacts(deps);
} catch {
await refresh();
generation = current;
}
if (latest && generation && !generationFactsMatch(generation.facts, latest)) {
await refresh();
generation = current;
}
}
if (!generation) throw new ChatMetadataSnapshotUnavailableError();
if (params.refreshOnRead) {
const latest = captureGenerationFacts(deps);
if (!generationFactsMatch(generation.facts, latest)) throw new ChatMetadataSnapshotUnavailableError("prepared chat metadata snapshot is stale while its replacement is publishing");
}
try {
const readProjection = await project(generation);
if (current === generation && generation.epoch === invalidationEpoch && readProjection.isCurrent()) return readProjection.read();
} catch (error) {
if (current === generation && generation.epoch === invalidationEpoch) throw error;
}
}
};
const read = async (readParams) => {
const draft = readParams.draftAccountSelection;
const sessionEntry = draft ? {
authProfileOverride: draft.authProfileId,
authProfileOverrideSource: "user"
} : readParams.sessionEntry;
return await readCurrent(async (generation) => {
const agentId = normalizeAgentId(readParams.agentId);
const agent = generation.agentsById.get(agentId);
if (!agent) throw new ChatMetadataSnapshotUnavailableError(`prepared chat metadata is unavailable for agent "${agentId}"`);
const projection = await projectAgent(generation, agent, sessionEntry, draft?.owner ?? readParams.requesterProfileId, draft?.assertCurrent, !readParams.sessionKey && !readParams.sessionEntry);
return {
isCurrent: projection.isCurrent,
read: () => projectChatSessionMetadata(readParams, projection.read(), deps.getConfig())
};
});
};
const readStartup = async (readParams) => {
const profiles = resolveSessionProfiles(readParams.sessionEntry);
const assemble = (neutral, session) => ({
...readParams.readPolicy === "ready" ? {} : { metadata: projectChatSessionMetadata(readParams, session.read(), deps.getConfig()) },
sessionModelCatalog: session.modelCatalog,
defaultModelCatalog: neutral.modelCatalog
});
const projectStartup = async (generation) => {
const agentId = normalizeAgentId(readParams.agentId);
const agent = generation.agentsById.get(agentId);
if (!agent) throw new ChatMetadataSnapshotUnavailableError(`prepared chat startup projection is unavailable for agent "${agentId}"`);
const readNeutral = await projectAgent(generation, agent);
const readSession = profiles.preferredProfileId ? await projectAgent(generation, agent, readParams.sessionEntry, readParams.requesterProfileId) : readNeutral;
return {
isCurrent: () => readNeutral.isCurrent() && readSession.isCurrent(),
read: () => assemble(readNeutral, readSession)
};
};
if (readParams.readPolicy !== "ready" && profiles.preferredProfileId) return readCurrent(projectStartup);
if (isUserModelAuthProfileId(profiles.preferredProfileId ?? "")) return;
const generation = current;
if (!generation || replacement || pending || generation.epoch !== invalidationEpoch) return;
if (params.refreshOnRead) try {
if (!generationFactsMatch(generation.facts, captureGenerationFacts(deps))) return;
} catch {
return;
}
const agentId = normalizeAgentId(readParams.agentId);
const neutral = generation.neutralProjectionByAgentId.get(agentId);
const session = profiles.preferredProfileId ? generation.sessionProjectionByKey.get(sessionProjectionKey(agentId, profiles)) : neutral;
if (neutral?.state !== "ready" || session?.state !== "ready" || !neutral.projection.isCurrent() || !session.projection.isCurrent()) return;
return assemble(neutral.projection, session.projection);
};
const invalidate = () => {
if (stoppedError) return;
invalidationEpoch += 1;
current = void 0;
lastError = void 0;
replacement ??= createMetadataReplacement();
};
const fail = (error) => {
const replacementError = error instanceof Error ? error : new Error(formatErrorMessage(error));
refreshVersion += 1;
pending = void 0;
current = void 0;
lastError = replacementError;
const failedReplacement = replacement;
replacement = void 0;
failedReplacement?.reject(replacementError);
if (!stoppedError && lastSettlement !== invalidationEpoch) {
lastSettlement = invalidationEpoch;
params.onChanged?.();
}
};
const stop = async () => {
if (!stoppedError) {
stoppedError = new ChatMetadataSnapshotUnavailableError("gateway chat metadata runtime is stopped");
invalidationEpoch += 1;
fail(stoppedError);
}
while (activeWork.size > 0) await Promise.allSettled(activeWork);
};
return {
fail,
invalidate,
read: (readParams) => trackWork(read(readParams)),
readStartup: (startupParams) => trackWork(readStartup(startupParams)),
refresh: () => trackWork(refresh()),
stop
};
}
//#endregion
export { ChatMetadataSnapshotUnavailableError, createGatewayChatMetadataRuntime };