mirror of https://github.com/openclaw/openclaw.git
167 lines
4.9 KiB
TypeScript
167 lines
4.9 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import type { IncomingMessage } from "node:http";
|
|
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
import {
|
|
buildAllowedModelSet,
|
|
modelKey,
|
|
parseModelRef,
|
|
resolveDefaultModelForAgent,
|
|
} from "../agents/model-selection.js";
|
|
import { loadConfig } from "../config/config.js";
|
|
import { buildAgentMainSessionKey, normalizeAgentId } from "../routing/session-key.js";
|
|
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
|
import { loadGatewayModelCatalog } from "./server-model-catalog.js";
|
|
|
|
export const OPENCLAW_MODEL_ID = "openclaw";
|
|
export const OPENCLAW_DEFAULT_MODEL_ID = "openclaw/default";
|
|
|
|
export function getHeader(req: IncomingMessage, name: string): string | undefined {
|
|
const raw = req.headers[name.toLowerCase()];
|
|
if (typeof raw === "string") {
|
|
return raw;
|
|
}
|
|
if (Array.isArray(raw)) {
|
|
return raw[0];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function getBearerToken(req: IncomingMessage): string | undefined {
|
|
const raw = getHeader(req, "authorization")?.trim() ?? "";
|
|
if (!raw.toLowerCase().startsWith("bearer ")) {
|
|
return undefined;
|
|
}
|
|
const token = raw.slice(7).trim();
|
|
return token || undefined;
|
|
}
|
|
|
|
export function resolveAgentIdFromHeader(req: IncomingMessage): string | undefined {
|
|
const raw =
|
|
getHeader(req, "x-openclaw-agent-id")?.trim() ||
|
|
getHeader(req, "x-openclaw-agent")?.trim() ||
|
|
"";
|
|
if (!raw) {
|
|
return undefined;
|
|
}
|
|
return normalizeAgentId(raw);
|
|
}
|
|
|
|
export function resolveAgentIdFromModel(
|
|
model: string | undefined,
|
|
cfg = loadConfig(),
|
|
): string | undefined {
|
|
const raw = model?.trim();
|
|
if (!raw) {
|
|
return undefined;
|
|
}
|
|
const lowered = raw.toLowerCase();
|
|
if (lowered === OPENCLAW_MODEL_ID || lowered === OPENCLAW_DEFAULT_MODEL_ID) {
|
|
return resolveDefaultAgentId(cfg);
|
|
}
|
|
|
|
const m =
|
|
raw.match(/^openclaw[:/](?<agentId>[a-z0-9][a-z0-9_-]{0,63})$/i) ??
|
|
raw.match(/^agent:(?<agentId>[a-z0-9][a-z0-9_-]{0,63})$/i);
|
|
const agentId = m?.groups?.agentId;
|
|
if (!agentId) {
|
|
return undefined;
|
|
}
|
|
return normalizeAgentId(agentId);
|
|
}
|
|
|
|
export async function resolveOpenAiCompatModelOverride(params: {
|
|
req: IncomingMessage;
|
|
agentId: string;
|
|
model: string | undefined;
|
|
}): Promise<{ modelOverride?: string; errorMessage?: string }> {
|
|
const requestModel = params.model?.trim();
|
|
if (requestModel && !resolveAgentIdFromModel(requestModel)) {
|
|
return {
|
|
errorMessage: "Invalid `model`. Use `openclaw` or `openclaw/<agentId>`.",
|
|
};
|
|
}
|
|
|
|
const raw = getHeader(params.req, "x-openclaw-model")?.trim();
|
|
if (!raw) {
|
|
return {};
|
|
}
|
|
|
|
const cfg = loadConfig();
|
|
const defaultModelRef = resolveDefaultModelForAgent({ cfg, agentId: params.agentId });
|
|
const defaultProvider = defaultModelRef.provider;
|
|
const parsed = parseModelRef(raw, defaultProvider);
|
|
if (!parsed) {
|
|
return { errorMessage: "Invalid `x-openclaw-model`." };
|
|
}
|
|
|
|
const catalog = await loadGatewayModelCatalog();
|
|
const allowed = buildAllowedModelSet({
|
|
cfg,
|
|
catalog,
|
|
defaultProvider,
|
|
agentId: params.agentId,
|
|
});
|
|
const normalized = modelKey(parsed.provider, parsed.model);
|
|
if (!allowed.allowAny && !allowed.allowedKeys.has(normalized)) {
|
|
return {
|
|
errorMessage: `Model '${normalized}' is not allowed for agent '${params.agentId}'.`,
|
|
};
|
|
}
|
|
|
|
return { modelOverride: raw };
|
|
}
|
|
|
|
export function resolveAgentIdForRequest(params: {
|
|
req: IncomingMessage;
|
|
model: string | undefined;
|
|
}): string {
|
|
const cfg = loadConfig();
|
|
const fromHeader = resolveAgentIdFromHeader(params.req);
|
|
if (fromHeader) {
|
|
return fromHeader;
|
|
}
|
|
|
|
const fromModel = resolveAgentIdFromModel(params.model, cfg);
|
|
return fromModel ?? resolveDefaultAgentId(cfg);
|
|
}
|
|
|
|
export function resolveSessionKey(params: {
|
|
req: IncomingMessage;
|
|
agentId: string;
|
|
user?: string | undefined;
|
|
prefix: string;
|
|
}): string {
|
|
const explicit = getHeader(params.req, "x-openclaw-session-key")?.trim();
|
|
if (explicit) {
|
|
return explicit;
|
|
}
|
|
|
|
const user = params.user?.trim();
|
|
const mainKey = user ? `${params.prefix}-user:${user}` : `${params.prefix}:${randomUUID()}`;
|
|
return buildAgentMainSessionKey({ agentId: params.agentId, mainKey });
|
|
}
|
|
|
|
export function resolveGatewayRequestContext(params: {
|
|
req: IncomingMessage;
|
|
model: string | undefined;
|
|
user?: string | undefined;
|
|
sessionPrefix: string;
|
|
defaultMessageChannel: string;
|
|
useMessageChannelHeader?: boolean;
|
|
}): { agentId: string; sessionKey: string; messageChannel: string } {
|
|
const agentId = resolveAgentIdForRequest({ req: params.req, model: params.model });
|
|
const sessionKey = resolveSessionKey({
|
|
req: params.req,
|
|
agentId,
|
|
user: params.user,
|
|
prefix: params.sessionPrefix,
|
|
});
|
|
|
|
const messageChannel = params.useMessageChannelHeader
|
|
? (normalizeMessageChannel(getHeader(params.req, "x-openclaw-message-channel")) ??
|
|
params.defaultMessageChannel)
|
|
: params.defaultMessageChannel;
|
|
|
|
return { agentId, sessionKey, messageChannel };
|
|
}
|