UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

239 lines (238 loc) • 9.33 kB
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import { t as pruneMapToMaxSize } from "./map-size-BlrH_CnE.mjs"; import { t as LruCache } from "./lru-cache-C3Xgvc4Q.mjs"; import { r as sha256HexPrefixCore } from "./node-crypto-BG4u3UnX.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import { T as tryResolveLegacyCompatibilityAgentId, d as resolveAgentWorkspaceDir } from "./agent-scope-config-CDYpzA0r.mjs"; import "./session-key-C09ow4h0.mjs"; import { n as listAgentEntries } from "./agent-roster-BXD1uMJD.mjs"; import "./agent-scope-CG17uXFn.mjs"; import "./legacy.default-agent-owner-CiZjlN5C.mjs"; import { n as AVATAR_MAX_DATA_URL_CHARS, o as isRenderableAvatarImageDataUrl } from "./avatar-limits-2506OuP3.mjs"; import { c as resolveAvatarMime, n as isAvatarDataUrl, o as isWindowsAbsolutePath, r as isAvatarHttpUrl, s as looksLikeAvatarPath, t as hasAvatarUriScheme } from "./avatar-policy-DiIdI6oM.mjs"; import { n as prepareLocalAgentAvatarFile } from "./identity-avatar-file-CzAdV6tU.mjs"; import { a as loadAgentIdentityFromWorkspaceAsync } from "./identity-file-DQ9_U4uO.mjs"; import { n as resolveAgentIdentity } from "./identity-uzCeiPuC.mjs"; import { n as buildControlUiResourcePath, r as matchControlUiResourceUrl } from "./control-ui-resource-routes-y_jZ6jnp.mjs"; import "./control-ui-contract-B_YMMx3J.mjs"; //#region src/gateway/assistant-avatar-cache.ts const fileSources = /* @__PURE__ */ new WeakMap(); const inlineFiles = /* @__PURE__ */ new WeakMap(); const dataSources = new LruCache(4); function prepareGatewayAvatarFile(file) { let source = fileSources.get(file); if (!source) { source = { file, revision: sha256HexPrefixCore(`thumbnail-128-png-v1:${JSON.stringify([file.path, file.stat])}`, 16) }; fileSources.set(file, source); } return source; } function prepareGatewayAvatarDataUrl(dataUrl) { const cached = dataSources.get(dataUrl); if (cached) return cached; if (!isRenderableAvatarImageDataUrl(dataUrl)) return; const source = { dataUrl, revision: sha256HexPrefixCore(`thumbnail-128-png-v1:${dataUrl}`, 16) }; dataSources.set(dataUrl, source); return source; } function gatewayAvatarFileDataUrl(file) { if (!file.body) return; let dataUrl = inlineFiles.get(file); if (!dataUrl) { dataUrl = `data:${resolveAvatarMime(file.path)};base64,${file.body.toString("base64")}`; inlineFiles.set(file, dataUrl); } return dataUrl; } //#endregion //#region src/gateway/assistant-identity.ts const ASSISTANT_IDENTITY_LIMITS = { name: 50, emoji: 16 }; const preparedIdentities = /* @__PURE__ */ new WeakMap(); const DEFAULT_ASSISTANT_IDENTITY = { name: "Assistant", avatar: "A" }; function normalizeIdentityValue(field, value) { const trimmed = normalizeOptionalString(value); return trimmed ? truncateUtf16Safe(trimmed, ASSISTANT_IDENTITY_LIMITS[field]) : void 0; } function isAvatarUrl(value) { return isAvatarHttpUrl(value) || isRenderableAvatarImageDataUrl(value); } function normalizeAvatarValue(value) { const trimmed = normalizeOptionalString(value); if (!trimmed || trimmed.length > AVATAR_MAX_DATA_URL_CHARS) return; if (isAvatarUrl(trimmed)) return trimmed; if (hasAvatarUriScheme(trimmed) && !isWindowsAbsolutePath(trimmed)) return; if (looksLikeAvatarPath(trimmed)) return trimmed; if (!/\s/.test(trimmed) && trimmed.length <= 4) return trimmed; } function normalizeEmojiValue(value) { if (!value || !/\P{ASCII}/u.test(value)) return; if (isAvatarUrl(value) || hasAvatarUriScheme(value) && !isWindowsAbsolutePath(value) || looksLikeAvatarPath(value)) return; return value; } function resolveAssistantAgentId(cfg, agentId) { return normalizeAgentId(agentId ?? tryResolveLegacyCompatibilityAgentId(cfg) ?? listAgentEntries(cfg)[0]?.id ?? "main"); } /** Resolve the display name/avatar/emoji for an agent-facing assistant identity. */ async function resolveAssistantIdentity(params) { const agentId = resolveAssistantAgentId(params.cfg, params.agentId); const workspaceDir = params.workspaceDir ?? resolveAgentWorkspaceDir(params.cfg, agentId); const { name: configuredName, emoji: configuredEmoji, avatar: configuredAvatar } = resolveAgentIdentity(params.cfg, agentId) ?? {}; const fileIdentity = workspaceDir ? await loadAgentIdentityFromWorkspaceAsync(workspaceDir) : null; let prepared = preparedIdentities.get(params.cfg); if (!prepared) { prepared = /* @__PURE__ */ new Map(); preparedIdentities.set(params.cfg, prepared); } const key = JSON.stringify([agentId, workspaceDir]); const cached = prepared.get(key); if (cached && cached.file === fileIdentity && cached.name === configuredName && cached.emoji === configuredEmoji && cached.avatar === configuredAvatar) { prepared.delete(key); prepared.set(key, cached); return cached.identity; } const agentName = normalizeIdentityValue("name", configuredName); const fileName = normalizeIdentityValue("name", fileIdentity?.name); const [name, nameSource] = (agentName ? [agentName, "agent"] : fileName ? [fileName, "workspace"] : void 0) ?? [DEFAULT_ASSISTANT_IDENTITY.name, "default"]; const identity = { agentId, name, nameSource, avatar: [ normalizeAvatarValue(configuredAvatar), normalizeAvatarValue(configuredEmoji), normalizeAvatarValue(fileIdentity?.avatar), normalizeAvatarValue(fileIdentity?.emoji) ].find(Boolean) ?? DEFAULT_ASSISTANT_IDENTITY.avatar, emoji: [ normalizeIdentityValue("emoji", configuredEmoji), normalizeIdentityValue("emoji", fileIdentity?.emoji), normalizeIdentityValue("emoji", configuredAvatar), normalizeIdentityValue("emoji", fileIdentity?.avatar) ].map((candidate) => normalizeEmojiValue(candidate)).find(Boolean) }; prepared.set(key, { file: fileIdentity, name: configuredName, emoji: configuredEmoji, avatar: configuredAvatar, identity }); pruneMapToMaxSize(prepared, 4); return identity; } //#endregion //#region src/gateway/assistant-avatar.ts function gatewayAssistantAvatarUrl(projection, basePath, agentId) { return projection.image ? `${buildControlUiResourcePath("agentAvatar", basePath, agentId)}?v=${projection.image.revision}` : void 0; } function resolveSameOriginAvatarUrl(basePath, source) { const unbased = matchControlUiResourceUrl("agentAvatar", source); if (unbased) return `${buildControlUiResourcePath("agentAvatar", basePath, unbased.value)}${unbased.search}${unbased.hash}`; return matchControlUiResourceUrl("agentAvatar", source, basePath) ? source : void 0; } /** Prepare a selected source; the file owner retains descriptor custody in its worker. */ async function prepareGatewayAssistantAvatar(params) { const { cfg, identity } = params; const source = identity.avatar; if (isAvatarHttpUrl(source)) return { resolution: { kind: "remote", url: source, source } }; if (isAvatarDataUrl(source)) { const image = prepareGatewayAvatarDataUrl(source); return image ? { resolution: { kind: "data", url: source, source }, image } : { resolution: { kind: "none", reason: "unsupported_data_url", source } }; } if (hasAvatarUriScheme(source) && !isWindowsAbsolutePath(source)) return { resolution: { kind: "none", reason: "unsupported_uri", source } }; if (resolveSameOriginAvatarUrl(cfg.gateway?.controlUi?.basePath, source)) return { resolution: null }; if (!looksLikeAvatarPath(source)) return { resolution: null }; const prepared = await prepareLocalAgentAvatarFile({ cfg, agentId: identity.agentId, source, readBody: params.readBody }); if (!prepared.ok) return { resolution: { kind: "none", reason: prepared.reason, source } }; return { resolution: { kind: "local", filePath: prepared.file.path, source }, file: prepared.file, image: prepareGatewayAvatarFile(prepared.file) }; } /** Resolve one selected identity avatar and its matching public metadata. */ async function resolveGatewayAssistantAvatar(params) { const { cfg, identity } = params; const source = identity.avatar; const sameOriginAvatarUrl = resolveSameOriginAvatarUrl(params.httpBasePath ?? cfg.gateway?.controlUi?.basePath, source); if (sameOriginAvatarUrl) return { avatar: sameOriginAvatarUrl, resolution: null }; const prepared = await prepareGatewayAssistantAvatar({ ...params, readBody: params.httpBasePath === void 0 }); if (prepared.resolution?.kind === "none") return { avatar: identity.emoji ?? DEFAULT_ASSISTANT_IDENTITY.avatar, resolution: prepared.resolution }; if (params.httpBasePath !== void 0) return { avatar: gatewayAssistantAvatarUrl(prepared, params.httpBasePath, identity.agentId) ?? source, resolution: prepared.resolution }; if (!prepared.file) return { avatar: source, resolution: prepared.resolution }; const dataUrl = gatewayAvatarFileDataUrl(prepared.file); if (!dataUrl) return { avatar: identity.emoji ?? DEFAULT_ASSISTANT_IDENTITY.avatar, resolution: { kind: "none", reason: "unreadable", source } }; return { avatar: dataUrl, resolution: prepared.resolution }; } //#endregion export { resolveAssistantAgentId as a, DEFAULT_ASSISTANT_IDENTITY as i, prepareGatewayAssistantAvatar as n, resolveAssistantIdentity as o, resolveGatewayAssistantAvatar as r, gatewayAssistantAvatarUrl as t };