openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
181 lines (180 loc) • 7.49 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { D as tryResolveLegacyCompatibilityAgentId, i as listAgentEntries, m as resolveAgentWorkspaceDir } from "./agent-scope-config-DcbEhP0R.js";
import "./session-key-BnWWjqNc.js";
import "./legacy.default-agent-owner-BGwEdQRe.js";
import "./agent-scope-DbtJyKUL.js";
import { f as isRenderableAvatarImageDataUrl, n as isAvatarDataUrl, o as isWindowsAbsolutePath, r as isAvatarHttpUrl, s as looksLikeAvatarPath, t as hasAvatarUriScheme, u as AVATAR_MAX_DATA_URL_CHARS } from "./avatar-policy-D8FQ_Wte.js";
import { n as resolveAgentIdentity } from "./identity-L3YWE18q.js";
import { i as matchControlUiResourceUrl, r as buildControlUiResourcePath } from "./control-ui-contract-zYW4RcpK.js";
import { t as openLocalAgentAvatarFile } from "./identity-avatar-file-D-HGbA6u.js";
import { n as gatewayAvatarImageRevision, t as createGatewayAvatarDataUrlCache } from "./assistant-avatar-cache-4x6DyL8R.js";
import { r as loadAgentIdentityFromWorkspace } from "./identity-file-Dnl4bkpm.js";
import fs from "node:fs";
//#region src/gateway/assistant-identity.ts
const ASSISTANT_IDENTITY_LIMITS = {
name: 50,
emoji: 16
};
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) return;
let hasNonAscii = false;
for (let i = 0; i < value.length; i += 1) if (value.charCodeAt(i) > 127) {
hasNonAscii = true;
break;
}
if (!hasNonAscii) 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. */
function resolveAssistantIdentity(params) {
const agentId = resolveAssistantAgentId(params.cfg, params.agentId);
const workspaceDir = params.workspaceDir ?? resolveAgentWorkspaceDir(params.cfg, agentId);
const agentIdentity = resolveAgentIdentity(params.cfg, agentId);
const fileIdentity = workspaceDir ? loadAgentIdentityFromWorkspace(workspaceDir) : null;
const agentName = normalizeIdentityValue("name", agentIdentity?.name);
const fileName = normalizeIdentityValue("name", fileIdentity?.name);
const [name, nameSource] = (agentName ? [agentName, "agent"] : fileName ? [fileName, "workspace"] : void 0) ?? [DEFAULT_ASSISTANT_IDENTITY.name, "default"];
return {
agentId,
name,
nameSource,
avatar: [
normalizeAvatarValue(agentIdentity?.avatar),
normalizeAvatarValue(agentIdentity?.emoji),
normalizeAvatarValue(fileIdentity?.avatar),
normalizeAvatarValue(fileIdentity?.emoji)
].find(Boolean) ?? DEFAULT_ASSISTANT_IDENTITY.avatar,
emoji: [
normalizeIdentityValue("emoji", agentIdentity?.emoji),
normalizeIdentityValue("emoji", fileIdentity?.emoji),
normalizeIdentityValue("emoji", agentIdentity?.avatar),
normalizeIdentityValue("emoji", fileIdentity?.avatar)
].map((candidate) => normalizeEmojiValue(candidate)).find(Boolean)
};
}
//#endregion
//#region src/gateway/assistant-avatar.ts
const gatewayAvatarDataUrlCache = createGatewayAvatarDataUrlCache();
function gatewayAssistantAvatarUrl(projection, basePath, agentId) {
const source = projection.openedFile ? { file: projection.openedFile } : projection.resolution?.kind === "data" ? { dataUrl: projection.resolution.url } : void 0;
return source ? `${buildControlUiResourcePath("agentAvatar", basePath, agentId)}?v=${gatewayAvatarImageRevision(source)}` : 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;
}
/**
* Resolve and open a selected local avatar for route delivery.
* A projection with `openedFile` transfers fd ownership to the caller.
*/
function openGatewayAssistantAvatar(params) {
const { cfg, identity } = params;
const source = identity.avatar;
if (isAvatarHttpUrl(source)) return { resolution: {
kind: "remote",
url: source,
source
} };
if (isRenderableAvatarImageDataUrl(source)) return { resolution: {
kind: "data",
url: source,
source
} };
if (isAvatarDataUrl(source)) return { 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 opened = openLocalAgentAvatarFile({
cfg,
agentId: identity.agentId,
source
});
if (!opened.ok) return { resolution: {
kind: "none",
reason: opened.reason,
source
} };
return {
resolution: {
kind: "local",
filePath: opened.file.path,
source
},
openedFile: opened.file
};
}
/** Resolve one selected identity avatar and its matching public metadata. */
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 opened = openGatewayAssistantAvatar(params);
if (opened.resolution?.kind === "none") return {
avatar: identity.emoji ?? DEFAULT_ASSISTANT_IDENTITY.avatar,
resolution: opened.resolution
};
if (params.httpBasePath !== void 0) {
if (opened.openedFile) fs.closeSync(opened.openedFile.fd);
return {
avatar: gatewayAssistantAvatarUrl(opened, params.httpBasePath, identity.agentId) ?? source,
resolution: opened.resolution
};
}
if (!opened.openedFile) return {
avatar: source,
resolution: opened.resolution
};
const dataUrl = gatewayAvatarDataUrlCache.read(opened.openedFile);
if (!dataUrl) return {
avatar: identity.emoji ?? DEFAULT_ASSISTANT_IDENTITY.avatar,
resolution: {
kind: "none",
reason: "unreadable",
source
}
};
return {
avatar: dataUrl,
resolution: opened.resolution
};
}
//#endregion
export { resolveAssistantAgentId as a, DEFAULT_ASSISTANT_IDENTITY as i, openGatewayAssistantAvatar as n, resolveAssistantIdentity as o, resolveGatewayAssistantAvatar as r, gatewayAssistantAvatarUrl as t };