From b1deaf8dd9173852c64efd6975d91c0e9089cd93 Mon Sep 17 00:00:00 2001 From: moguangyu5-design Date: Tue, 21 Jul 2026 23:09:16 +0800 Subject: [PATCH] fix(config): inherit provider token defaults (#105807) Co-authored-by: Peter Steinberger Co-authored-by: moguangyu5-design --- src/config/defaults.ts | 24 +++++++++- src/config/model-alias-defaults.test.ts | 60 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 9eb4f707ef32..976ffccdb217 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -186,6 +186,15 @@ export function applyModelDefaults( continue; } const providerApi = normalizedProvider.api; + const providerContextWindow = isPositiveNumber(normalizedProvider.contextWindow) + ? normalizedProvider.contextWindow + : undefined; + const providerContextTokens = isPositiveNumber(normalizedProvider.contextTokens) + ? normalizedProvider.contextTokens + : undefined; + const providerMaxTokens = isPositiveNumber(normalizedProvider.maxTokens) + ? normalizedProvider.maxTokens + : undefined; const nextProvider = normalizedProvider; if (nextProvider !== provider) { mutated = true; @@ -226,12 +235,22 @@ export function applyModelDefaults( const contextWindow = isPositiveNumber(raw.contextWindow) ? raw.contextWindow - : DEFAULT_CONTEXT_TOKENS; + : (providerContextWindow ?? DEFAULT_CONTEXT_TOKENS); if (raw.contextWindow !== contextWindow) { modelMutated = true; } - const defaultMaxTokens = Math.min(DEFAULT_MODEL_MAX_TOKENS, contextWindow); + const contextTokens = isPositiveNumber(raw.contextTokens) + ? raw.contextTokens + : providerContextTokens; + if (raw.contextTokens !== contextTokens) { + modelMutated = true; + } + + const defaultMaxTokens = Math.min( + providerMaxTokens ?? DEFAULT_MODEL_MAX_TOKENS, + contextWindow, + ); const rawMaxTokens = isPositiveNumber(raw.maxTokens) ? raw.maxTokens : defaultMaxTokens; const maxTokens = resolveNormalizedProviderModelMaxTokens({ providerId, @@ -257,6 +276,7 @@ export function applyModelDefaults( input, cost, contextWindow, + contextTokens, maxTokens, api, }) as ModelDefinitionConfig; diff --git a/src/config/model-alias-defaults.test.ts b/src/config/model-alias-defaults.test.ts index da13088e1294..40f259c72597 100644 --- a/src/config/model-alias-defaults.test.ts +++ b/src/config/model-alias-defaults.test.ts @@ -95,6 +95,34 @@ describe("applyModelDefaults", () => { } satisfies OpenClawConfig; } + function buildProviderTokenDefaultsConfig(params: { + provider: { contextWindow?: number; contextTokens?: number; maxTokens?: number }; + model?: { contextWindow?: number; contextTokens?: number; maxTokens?: number }; + }) { + return { + models: { + providers: { + myproxy: { + baseUrl: "https://proxy.example/v1", + apiKey: "sk-test", + api: "openai-completions", + ...params.provider, + models: [ + { + id: "gpt-5.4", + name: "GPT-5.4", + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + ...params.model, + }, + ], + }, + }, + }, + } as never; + } + function buildCustomProviderManifestRegistry() { return { plugins: [ @@ -439,6 +467,38 @@ describe("applyModelDefaults", () => { expect(model?.maxTokens).toBe(32768); }); + it.each([ + { + name: "inherits provider defaults", + provider: { contextWindow: 50_000, contextTokens: 32_000, maxTokens: 4_096 }, + model: undefined, + expected: { contextWindow: 50_000, contextTokens: 32_000, maxTokens: 4_096 }, + }, + { + name: "keeps model overrides", + provider: { contextWindow: 50_000, contextTokens: 32_000, maxTokens: 4_096 }, + model: { contextWindow: 10_000, contextTokens: 8_000, maxTokens: 2_048 }, + expected: { contextWindow: 10_000, contextTokens: 8_000, maxTokens: 2_048 }, + }, + { + name: "clamps inherited maxTokens to the inherited contextWindow", + provider: { contextWindow: 4_096, maxTokens: 8_192 }, + model: undefined, + expected: { contextWindow: 4_096, contextTokens: undefined, maxTokens: 4_096 }, + }, + ])("$name", ({ provider, model, expected }) => { + const cfg = buildProviderTokenDefaultsConfig({ provider, model }); + + const next = applyModelDefaults(cfg); + const resolved = next.models?.providers?.myproxy?.models?.[0]; + + expect({ + contextWindow: resolved?.contextWindow, + contextTokens: resolved?.contextTokens, + maxTokens: resolved?.maxTokens, + }).toEqual(expected); + }); + it("normalizes stale mistral maxTokens that matched the full context window", () => { const cfg = buildMistralProviderConfig();