import { listAgentIds } from "../../agents/agent-scope.js"; import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js"; import { buildModelAliasIndex, modelKey, parseModelRef, resolveModelRefFromString, } from "../../agents/model-selection.js"; import { formatCliCommand } from "../../cli/command-format.js"; import { type OpenClawConfig, readConfigFileSnapshot, writeConfigFile, } from "../../config/config.js"; import { normalizeAgentId } from "../../routing/session-key.js"; export const ensureFlagCompatibility = (opts: { json?: boolean; plain?: boolean }) => { if (opts.json && opts.plain) { throw new Error("Choose either --json or --plain, not both."); } }; export const formatTokenK = (value?: number | null) => { if (!value || !Number.isFinite(value)) { return "-"; } if (value < 1024) { return `${Math.round(value)}`; } return `${Math.round(value / 1024)}k`; }; export const formatMs = (value?: number | null) => { if (value === null || value === undefined) { return "-"; } if (!Number.isFinite(value)) { return "-"; } if (value < 1000) { return `${Math.round(value)}ms`; } return `${Math.round(value / 100) / 10}s`; }; export async function updateConfig( mutator: (cfg: OpenClawConfig) => OpenClawConfig, ): Promise { const snapshot = await readConfigFileSnapshot(); if (!snapshot.valid) { const issues = snapshot.issues.map((issue) => `- ${issue.path}: ${issue.message}`).join("\n"); throw new Error(`Invalid config at ${snapshot.path}\n${issues}`); } const next = mutator(snapshot.config); await writeConfigFile(next); return next; } export function resolveModelTarget(params: { raw: string; cfg: OpenClawConfig }): { provider: string; model: string; } { const aliasIndex = buildModelAliasIndex({ cfg: params.cfg, defaultProvider: DEFAULT_PROVIDER, }); const resolved = resolveModelRefFromString({ raw: params.raw, defaultProvider: DEFAULT_PROVIDER, aliasIndex, }); if (!resolved) { throw new Error(`Invalid model reference: ${params.raw}`); } return resolved.ref; } export function buildAllowlistSet(cfg: OpenClawConfig): Set { const allowed = new Set(); const models = cfg.agents?.defaults?.models ?? {}; for (const raw of Object.keys(models)) { const parsed = parseModelRef(String(raw ?? ""), DEFAULT_PROVIDER); if (!parsed) { continue; } allowed.add(modelKey(parsed.provider, parsed.model)); } return allowed; } export function normalizeAlias(alias: string): string { const trimmed = alias.trim(); if (!trimmed) { throw new Error("Alias cannot be empty."); } if (!/^[A-Za-z0-9_.:-]+$/.test(trimmed)) { throw new Error("Alias must use letters, numbers, dots, underscores, colons, or dashes."); } return trimmed; } export function resolveKnownAgentId(params: { cfg: OpenClawConfig; rawAgentId?: string | null; }): string | undefined { const raw = params.rawAgentId?.trim(); if (!raw) { return undefined; } const agentId = normalizeAgentId(raw); const knownAgents = listAgentIds(params.cfg); if (!knownAgents.includes(agentId)) { throw new Error( `Unknown agent id "${raw}". Use "${formatCliCommand("openclaw agents list")}" to see configured agents.`, ); } return agentId; } export { modelKey }; export { DEFAULT_MODEL, DEFAULT_PROVIDER }; /** * Model key format: "provider/model" * * The model key is displayed in `/model status` and used to reference models. * When using `/model `, use the exact format shown (e.g., "openrouter/moonshotai/kimi-k2"). * * For providers with hierarchical model IDs (e.g., OpenRouter), the model ID may include * sub-providers (e.g., "moonshotai/kimi-k2"), resulting in a key like "openrouter/moonshotai/kimi-k2". */