import type { AgentModelEntryConfig } from "../config/types.agent-defaults.js"; import type { AgentRuntimePolicyConfig } from "../config/types.agents-shared.js"; import type { ModelDefinitionConfig, ModelProviderConfig } from "../config/types.models.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { normalizeAgentId } from "../routing/session-key.js"; import { listAgentEntries, resolveSessionAgentIds } from "./agent-scope.js"; import { normalizeProviderId } from "./provider-id.js"; export type ModelRuntimePolicySource = "model" | "provider"; export type ResolvedModelRuntimePolicy = { policy?: AgentRuntimePolicyConfig; source?: ModelRuntimePolicySource; }; type ModelEntryMatchKind = "none" | "exact" | "provider-wildcard"; function hasRuntimePolicy(value: AgentRuntimePolicyConfig | undefined): boolean { return Boolean(value?.id?.trim()); } function resolveProviderConfig( config: OpenClawConfig | undefined, provider: string | undefined, ): ModelProviderConfig | undefined { if (!config?.models?.providers || !provider?.trim()) { return undefined; } const providers = config.models.providers; const direct = providers[provider]; if (direct) { return direct; } const normalizedProvider = normalizeProviderId(provider); for (const [candidateProvider, providerConfig] of Object.entries(providers)) { if (normalizeProviderId(candidateProvider) === normalizedProvider) { return providerConfig; } } return undefined; } function normalizeModelIdForProvider( provider: string | undefined, modelId: string | undefined, ): string | undefined { const trimmed = modelId?.trim(); if (!trimmed) { return undefined; } const slash = trimmed.indexOf("/"); if (slash <= 0) { return trimmed; } const modelProvider = normalizeProviderId(trimmed.slice(0, slash)); const expectedProvider = normalizeProviderId(provider ?? ""); if (expectedProvider && modelProvider !== expectedProvider) { return undefined; } return trimmed.slice(slash + 1).trim() || undefined; } function modelEntryMatches(params: { entry: Pick; provider: string | undefined; modelId: string; }): boolean { return modelEntryMatchKind(params) === "exact"; } function modelEntryMatchKind(params: { entry: Pick; provider: string | undefined; modelId: string; }): ModelEntryMatchKind { const entryId = params.entry.id.trim(); if (entryId === params.modelId) { return "exact"; } const slash = entryId.indexOf("/"); if (slash <= 0) { return "none"; } if (normalizeProviderId(entryId.slice(0, slash)) !== normalizeProviderId(params.provider ?? "")) { return "none"; } const entryModelId = entryId.slice(slash + 1).trim(); if (entryModelId === params.modelId) { return "exact"; } if (entryModelId === "*") { return "provider-wildcard"; } return "none"; } function modelKeyMatchKind(params: { key: string; provider: string | undefined; modelId: string; }): ModelEntryMatchKind { return modelEntryMatchKind({ entry: { id: params.key }, provider: params.provider, modelId: params.modelId, }); } function modelKeyIsProviderWildcard(params: { key: string; provider: string | undefined; }): boolean { const slash = params.key.indexOf("/"); if (slash <= 0) { return false; } if ( normalizeProviderId(params.key.slice(0, slash)) !== normalizeProviderId(params.provider ?? "") ) { return false; } return params.key.slice(slash + 1).trim() === "*"; } function resolveAgentModelEntryRuntimePolicy(params: { config?: OpenClawConfig; provider?: string; modelId?: string; agentId?: string; sessionKey?: string; matchKind: Exclude; }): ResolvedModelRuntimePolicy { const modelId = normalizeModelIdForProvider(params.provider, params.modelId); if (!params.config || (!modelId && params.matchKind !== "provider-wildcard")) { return {}; } const { sessionAgentId } = resolveSessionAgentIds({ config: params.config, agentId: params.agentId, sessionKey: params.sessionKey, }); const agentEntry = listAgentEntries(params.config).find( (entry) => normalizeAgentId(entry.id) === sessionAgentId, ); const modelMaps: Array | undefined> = [ agentEntry?.models, params.config.agents?.defaults?.models, ]; for (const models of modelMaps) { for (const [key, entry] of Object.entries(models ?? {})) { const matches = modelId ? modelKeyMatchKind({ key, provider: params.provider, modelId }) === params.matchKind : modelKeyIsProviderWildcard({ key, provider: params.provider }); if (matches && hasRuntimePolicy(entry?.agentRuntime)) { return { policy: entry.agentRuntime, source: "model" }; } } } return {}; } function resolveModelConfig(params: { providerConfig?: ModelProviderConfig; provider?: string; modelId?: string; }): ModelDefinitionConfig | undefined { const modelId = normalizeModelIdForProvider(params.provider, params.modelId); if (!modelId || !Array.isArray(params.providerConfig?.models)) { return undefined; } return params.providerConfig.models.find((entry) => modelEntryMatches({ entry, provider: params.provider, modelId }), ); } export function resolveModelRuntimePolicy(params: { config?: OpenClawConfig; provider?: string; modelId?: string; agentId?: string; sessionKey?: string; }): ResolvedModelRuntimePolicy { if (process.env.OPENCLAW_BUILD_PRIVATE_QA === "1") { const forcedRuntime = process.env.OPENCLAW_QA_FORCE_RUNTIME?.trim().toLowerCase(); if (forcedRuntime === "pi" || forcedRuntime === "codex") { return { policy: { id: forcedRuntime }, source: "model" }; } } const agentModelPolicy = resolveAgentModelEntryRuntimePolicy({ ...params, matchKind: "exact" }); if (agentModelPolicy.policy) { return agentModelPolicy; } const providerConfig = resolveProviderConfig(params.config, params.provider); const modelConfig = resolveModelConfig({ providerConfig, provider: params.provider, modelId: params.modelId, }); if (hasRuntimePolicy(modelConfig?.agentRuntime)) { return { policy: modelConfig?.agentRuntime, source: "model" }; } const agentWildcardModelPolicy = resolveAgentModelEntryRuntimePolicy({ ...params, matchKind: "provider-wildcard", }); if (agentWildcardModelPolicy.policy) { return agentWildcardModelPolicy; } if (hasRuntimePolicy(providerConfig?.agentRuntime)) { return { policy: providerConfig?.agentRuntime, source: "provider" }; } return {}; }