diff --git a/src/agents/context.test.ts b/src/agents/context.test.ts new file mode 100644 index 00000000000..41111b4bb41 --- /dev/null +++ b/src/agents/context.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; +import { applyConfiguredContextWindows } from "./context.js"; + +describe("applyConfiguredContextWindows", () => { + it("overrides discovered cache values with explicit models.providers contextWindow", () => { + const cache = new Map([["anthropic/claude-opus-4-6", 1_000_000]]); + applyConfiguredContextWindows({ + cache, + modelsConfig: { + providers: { + openrouter: { + models: [{ id: "anthropic/claude-opus-4-6", contextWindow: 200_000 }], + }, + }, + }, + }); + + expect(cache.get("anthropic/claude-opus-4-6")).toBe(200_000); + }); + + it("adds config-only model context windows and ignores invalid entries", () => { + const cache = new Map(); + applyConfiguredContextWindows({ + cache, + modelsConfig: { + providers: { + openrouter: { + models: [ + { id: "custom/model", contextWindow: 150_000 }, + { id: "bad/model", contextWindow: 0 }, + { id: "", contextWindow: 300_000 }, + ], + }, + }, + }, + }); + + expect(cache.get("custom/model")).toBe(150_000); + expect(cache.has("bad/model")).toBe(false); + }); +}); diff --git a/src/agents/context.ts b/src/agents/context.ts index b3683e235f2..c919dbf9095 100644 --- a/src/agents/context.ts +++ b/src/agents/context.ts @@ -6,13 +6,52 @@ import { resolveOpenClawAgentDir } from "./agent-paths.js"; import { ensureOpenClawModelsJson } from "./models-config.js"; type ModelEntry = { id: string; contextWindow?: number }; +type ConfigModelEntry = { id?: string; contextWindow?: number }; +type ProviderConfigEntry = { models?: ConfigModelEntry[] }; +type ModelsConfig = { providers?: Record }; + +export function applyConfiguredContextWindows(params: { + cache: Map; + modelsConfig: ModelsConfig | undefined; +}) { + const providers = params.modelsConfig?.providers; + if (!providers || typeof providers !== "object") { + return; + } + for (const provider of Object.values(providers)) { + if (!Array.isArray(provider?.models)) { + continue; + } + for (const model of provider.models) { + const modelId = typeof model?.id === "string" ? model.id : undefined; + const contextWindow = + typeof model?.contextWindow === "number" ? model.contextWindow : undefined; + if (!modelId || !contextWindow || contextWindow <= 0) { + continue; + } + params.cache.set(modelId, contextWindow); + } + } +} const MODEL_CACHE = new Map(); const loadPromise = (async () => { + let cfg: ReturnType | undefined; + try { + cfg = loadConfig(); + } catch { + // If config can't be loaded, leave cache empty. + return; + } + + try { + await ensureOpenClawModelsJson(cfg); + } catch { + // Continue with best-effort discovery/overrides. + } + try { const { discoverAuthStorage, discoverModels } = await import("./pi-model-discovery.js"); - const cfg = loadConfig(); - await ensureOpenClawModelsJson(cfg); const agentDir = resolveOpenClawAgentDir(); const authStorage = discoverAuthStorage(agentDir); const modelRegistry = discoverModels(authStorage, agentDir); @@ -26,9 +65,16 @@ const loadPromise = (async () => { } } } catch { - // If pi-ai isn't available, leave cache empty; lookup will fall back. + // If model discovery fails, continue with config overrides only. } -})(); + + applyConfiguredContextWindows({ + cache: MODEL_CACHE, + modelsConfig: cfg.models as ModelsConfig | undefined, + }); +})().catch(() => { + // Keep lookup best-effort. +}); export function lookupContextTokens(modelId?: string): number | undefined { if (!modelId) {