refactor(providers): move defaults and error policy into plugins

This commit is contained in:
Peter Steinberger
2026-04-04 07:42:09 +01:00
parent e34f42559f
commit b167ad052c
17 changed files with 542 additions and 226 deletions

View File

@@ -1,7 +1,8 @@
import { DEFAULT_CONTEXT_TOKENS } from "../agents/defaults.js";
import { normalizeProviderId, parseModelRef } from "../agents/model-selection.js";
import { normalizeProviderId } from "../agents/model-selection.js";
import { normalizeProviderSpecificConfig } from "../agents/models-config.providers.policy.js";
import { applyProviderConfigDefaultsWithPlugin } from "../plugins/provider-runtime.js";
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
import { resolveAgentModelPrimaryValue } from "./model-input.js";
import {
LEGACY_TALK_PROVIDER_ID,
normalizeTalkConfig,
@@ -16,8 +17,6 @@ type WarnState = { warned: boolean };
let defaultWarnState: WarnState = { warned: false };
type AnthropicAuthDefaultsMode = "api_key" | "oauth";
const DEFAULT_MODEL_ALIASES: Readonly<Record<string, string>> = {
// Anthropic (pi-ai catalog uses "latest" ids without date suffix)
opus: "anthropic/claude-opus-4-6",
@@ -54,16 +53,6 @@ const MISTRAL_SAFE_MAX_TOKENS_BY_MODEL = {
type ModelDefinitionLike = Partial<ModelDefinitionConfig> &
Pick<ModelDefinitionConfig, "id" | "name">;
function resolveDefaultProviderApi(
providerId: string,
providerApi: ModelDefinitionConfig["api"] | undefined,
): ModelDefinitionConfig["api"] | undefined {
if (providerApi) {
return providerApi;
}
return normalizeProviderId(providerId) === "anthropic" ? "anthropic-messages" : undefined;
}
function isPositiveNumber(value: unknown): value is number {
return typeof value === "number" && Number.isFinite(value) && value > 0;
}
@@ -98,58 +87,6 @@ export function resolveNormalizedProviderModelMaxTokens(params: {
return Math.min(safeMaxTokens, params.contextWindow);
}
function resolveAnthropicDefaultAuthMode(cfg: OpenClawConfig): AnthropicAuthDefaultsMode | null {
const profiles = cfg.auth?.profiles ?? {};
const anthropicProfiles = Object.entries(profiles).filter(
([, profile]) => profile?.provider === "anthropic",
);
const order = cfg.auth?.order?.anthropic ?? [];
for (const profileId of order) {
const entry = profiles[profileId];
if (!entry || entry.provider !== "anthropic") {
continue;
}
if (entry.mode === "api_key") {
return "api_key";
}
if (entry.mode === "oauth" || entry.mode === "token") {
return "oauth";
}
}
const hasApiKey = anthropicProfiles.some(([, profile]) => profile?.mode === "api_key");
const hasOauth = anthropicProfiles.some(
([, profile]) => profile?.mode === "oauth" || profile?.mode === "token",
);
if (hasApiKey && !hasOauth) {
return "api_key";
}
if (hasOauth && !hasApiKey) {
return "oauth";
}
if (process.env.ANTHROPIC_OAUTH_TOKEN?.trim()) {
return "oauth";
}
if (process.env.ANTHROPIC_API_KEY?.trim()) {
return "api_key";
}
return null;
}
function resolvePrimaryModelRef(raw?: string): string | null {
if (!raw || typeof raw !== "string") {
return null;
}
const trimmed = raw.trim();
if (!trimmed) {
return null;
}
const aliasKey = trimmed.toLowerCase();
return DEFAULT_MODEL_ALIASES[aliasKey] ?? trimmed;
}
export type SessionDefaultsOptions = {
warn?: (message: string) => void;
warnState?: WarnState;
@@ -242,15 +179,19 @@ export function applyModelDefaults(cfg: OpenClawConfig): OpenClawConfig {
if (providerConfig) {
const nextProviders = { ...providerConfig };
for (const [providerId, provider] of Object.entries(providerConfig)) {
const models = provider.models;
const normalizedProvider = normalizeProviderSpecificConfig(providerId, provider);
const models = normalizedProvider.models;
if (!Array.isArray(models) || models.length === 0) {
if (normalizedProvider !== provider) {
nextProviders[providerId] = normalizedProvider;
mutated = true;
}
continue;
}
const providerApi = resolveDefaultProviderApi(providerId, provider.api);
let nextProvider = provider;
if (providerApi && provider.api !== providerApi) {
const providerApi = normalizedProvider.api;
let nextProvider = normalizedProvider;
if (nextProvider !== provider) {
mutated = true;
nextProvider = { ...nextProvider, api: providerApi };
}
let providerMutated = false;
const nextModels = models.map((model) => {
@@ -434,105 +375,16 @@ export function applyLoggingDefaults(cfg: OpenClawConfig): OpenClawConfig {
}
export function applyContextPruningDefaults(cfg: OpenClawConfig): OpenClawConfig {
const defaults = cfg.agents?.defaults;
if (!defaults) {
return cfg;
}
const authMode = resolveAnthropicDefaultAuthMode(cfg);
if (!authMode) {
return cfg;
}
let mutated = false;
const nextDefaults = { ...defaults };
const contextPruning = defaults.contextPruning ?? {};
const heartbeat = defaults.heartbeat ?? {};
if (defaults.contextPruning?.mode === undefined) {
nextDefaults.contextPruning = {
...contextPruning,
mode: "cache-ttl",
ttl: defaults.contextPruning?.ttl ?? "1h",
};
mutated = true;
}
if (defaults.heartbeat?.every === undefined) {
nextDefaults.heartbeat = {
...heartbeat,
every: authMode === "oauth" ? "1h" : "30m",
};
mutated = true;
}
if (authMode === "api_key") {
const nextModels = defaults.models ? { ...defaults.models } : {};
let modelsMutated = false;
const isAnthropicCacheRetentionTarget = (
parsed: { provider: string; model: string } | null | undefined,
): parsed is { provider: string; model: string } =>
Boolean(
parsed &&
(parsed.provider === "anthropic" ||
(parsed.provider === "amazon-bedrock" &&
parsed.model.toLowerCase().includes("anthropic.claude"))),
);
for (const [key, entry] of Object.entries(nextModels)) {
const parsed = parseModelRef(key, "anthropic");
if (!isAnthropicCacheRetentionTarget(parsed)) {
continue;
}
const current = entry ?? {};
const params = (current as { params?: Record<string, unknown> }).params ?? {};
if (typeof params.cacheRetention === "string") {
continue;
}
nextModels[key] = {
...(current as Record<string, unknown>),
params: { ...params, cacheRetention: "short" },
};
modelsMutated = true;
}
const primary = resolvePrimaryModelRef(
resolveAgentModelPrimaryValue(defaults.model) ?? undefined,
);
if (primary) {
const parsedPrimary = parseModelRef(primary, "anthropic");
if (isAnthropicCacheRetentionTarget(parsedPrimary)) {
const key = `${parsedPrimary.provider}/${parsedPrimary.model}`;
const entry = nextModels[key];
const current = entry ?? {};
const params = (current as { params?: Record<string, unknown> }).params ?? {};
if (typeof params.cacheRetention !== "string") {
nextModels[key] = {
...(current as Record<string, unknown>),
params: { ...params, cacheRetention: "short" },
};
modelsMutated = true;
}
}
}
if (modelsMutated) {
nextDefaults.models = nextModels;
mutated = true;
}
}
if (!mutated) {
return cfg;
}
return {
...cfg,
agents: {
...cfg.agents,
defaults: nextDefaults,
},
};
return (
applyProviderConfigDefaultsWithPlugin({
provider: "anthropic",
context: {
provider: "anthropic",
config: cfg,
env: process.env,
},
}) ?? cfg
);
}
export function applyCompactionDefaults(cfg: OpenClawConfig): OpenClawConfig {