From c41d6fd912f89657392b96fd01d5c8ecdb7b6062 Mon Sep 17 00:00:00 2001 From: MoerAI Date: Mon, 23 Mar 2026 20:39:47 +0900 Subject: [PATCH] fix(delegate-task): trust user-configured category models without fuzzy validation (fixes #2740) --- src/tools/delegate-task/categories.ts | 5 ++++- src/tools/delegate-task/category-resolver.ts | 1 + src/tools/delegate-task/model-selection.test.ts | 10 ++++++++++ src/tools/delegate-task/model-selection.ts | 9 +++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/tools/delegate-task/categories.ts b/src/tools/delegate-task/categories.ts index af6f6410f..ffee9aeb7 100644 --- a/src/tools/delegate-task/categories.ts +++ b/src/tools/delegate-task/categories.ts @@ -2,6 +2,7 @@ import type { CategoryConfig, CategoriesConfig } from "../../config/schema" import { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS } from "./constants" import { resolveModel } from "../../shared/model-resolver" import { isModelAvailable } from "../../shared/model-availability" +import { normalizeModel } from "../../shared/model-normalization" import { CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements" import { log } from "../../shared/logger" @@ -16,6 +17,7 @@ export interface ResolveCategoryConfigResult { config: CategoryConfig promptAppend: string model: string | undefined + isUserConfiguredModel: boolean } /** @@ -56,6 +58,7 @@ export function resolveCategoryConfig( inheritedModel: defaultConfig?.model, // Category's built-in model takes precedence over system default systemDefault: systemDefaultModel, }) + const isUserConfiguredModel = normalizeModel(userConfig?.model) !== undefined const config: CategoryConfig = { ...defaultConfig, ...userConfig, @@ -70,5 +73,5 @@ export function resolveCategoryConfig( : userConfig.prompt_append } - return { config, promptAppend, model } + return { config, promptAppend, model, isUserConfiguredModel } } diff --git a/src/tools/delegate-task/category-resolver.ts b/src/tools/delegate-task/category-resolver.ts index f6c13a825..a70f635c0 100644 --- a/src/tools/delegate-task/category-resolver.ts +++ b/src/tools/delegate-task/category-resolver.ts @@ -110,6 +110,7 @@ Available categories: ${allCategoryNames}`, userModel: explicitCategoryModel ?? overrideModel, userFallbackModels: normalizedConfiguredFallbackModels, categoryDefaultModel: resolved.model, + isUserConfiguredCategoryModel: resolved.isUserConfiguredModel, fallbackChain: requirement.fallbackChain, availableModels, systemDefaultModel, diff --git a/src/tools/delegate-task/model-selection.test.ts b/src/tools/delegate-task/model-selection.test.ts index 2f9b9c196..4324db931 100644 --- a/src/tools/delegate-task/model-selection.test.ts +++ b/src/tools/delegate-task/model-selection.test.ts @@ -102,6 +102,16 @@ describe("resolveModelForDelegateTask", () => { expect(result).toEqual({ model: "anthropic/claude-sonnet-4-6" }) }) + + test("#then trusts user-configured category model without fuzzy validation", () => { + const result = resolveModelForDelegateTask({ + categoryDefaultModel: "new-api-openai/gpt-5.4-high", + isUserConfiguredCategoryModel: true, + availableModels: new Set(["openai/gpt-5.4"]), + }) + + expect(result).toEqual({ model: "new-api-openai/gpt-5.4-high" }) + }) }) describe("#when user fallback models include variant syntax", () => { diff --git a/src/tools/delegate-task/model-selection.ts b/src/tools/delegate-task/model-selection.ts index 14b069ad7..79fbec9f5 100644 --- a/src/tools/delegate-task/model-selection.ts +++ b/src/tools/delegate-task/model-selection.ts @@ -3,6 +3,7 @@ import { normalizeModel } from "../../shared/model-normalization" import { fuzzyMatchModel } from "../../shared/model-availability" import { transformModelForProvider } from "../../shared/provider-model-id-transform" import { hasConnectedProvidersCache, hasProviderModelsCache } from "../../shared/connected-providers-cache" +import { log } from "../../shared/logger" import { parseModelString, parseVariantFromModelID } from "./model-string-parser" function isExplicitHighModel(model: string): boolean { @@ -48,6 +49,7 @@ export function resolveModelForDelegateTask(input: { userModel?: string userFallbackModels?: string[] categoryDefaultModel?: string + isUserConfiguredCategoryModel?: boolean fallbackChain?: FallbackEntry[] availableModels: Set systemDefaultModel?: string @@ -67,6 +69,13 @@ export function resolveModelForDelegateTask(input: { const explicitHighBaseModel = categoryDefault ? getExplicitHighBaseModel(categoryDefault) : null const explicitHighModel = explicitHighBaseModel ? categoryDefault : undefined if (categoryDefault) { + if (input.isUserConfiguredCategoryModel) { + log("[resolveModelForDelegateTask] using user-configured category model (bypass validation)", { + categoryDefaultModel: categoryDefault, + }) + return { model: categoryDefault } + } + if (input.availableModels.size === 0) { return { model: categoryDefault } }