From c80a74c5f4c6ec18404e24ff0c6b1698d9f9b27f Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Tue, 3 Mar 2026 00:31:12 +0900 Subject: [PATCH] fix(model-resolution): normalize model format and remove dead config flag --- src/shared/model-resolution-pipeline.test.ts | 25 +++++++++++++++++++ src/shared/model-resolution-pipeline.ts | 3 +-- .../delegate-task/subagent-resolver.test.ts | 22 ++++++++++++++++ src/tools/delegate-task/subagent-resolver.ts | 15 ++++++++--- 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/shared/model-resolution-pipeline.test.ts diff --git a/src/shared/model-resolution-pipeline.test.ts b/src/shared/model-resolution-pipeline.test.ts new file mode 100644 index 00000000..a08ecc85 --- /dev/null +++ b/src/shared/model-resolution-pipeline.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, test } from "bun:test" +import { resolveModelPipeline } from "./model-resolution-pipeline" + +describe("resolveModelPipeline", () => { + test("does not return unused explicit user config metadata in override result", () => { + // given + const result = resolveModelPipeline({ + intent: { + userModel: "openai/gpt-5.3-codex", + }, + constraints: { + availableModels: new Set(), + }, + }) + + // when + const hasExplicitUserConfigField = result + ? Object.prototype.hasOwnProperty.call(result, "explicitUserConfig") + : false + + // then + expect(result).toEqual({ model: "openai/gpt-5.3-codex", provenance: "override" }) + expect(hasExplicitUserConfigField).toBe(false) + }) +}) diff --git a/src/shared/model-resolution-pipeline.ts b/src/shared/model-resolution-pipeline.ts index 8eb12e5c..c51cad37 100644 --- a/src/shared/model-resolution-pipeline.ts +++ b/src/shared/model-resolution-pipeline.ts @@ -34,7 +34,6 @@ export type ModelResolutionResult = { variant?: string attempted?: string[] reason?: string - explicitUserConfig?: boolean } @@ -56,7 +55,7 @@ export function resolveModelPipeline( const normalizedUserModel = normalizeModel(intent?.userModel) if (normalizedUserModel) { log("Model resolved via config override", { model: normalizedUserModel }) - return { model: normalizedUserModel, provenance: "override", explicitUserConfig: true } + return { model: normalizedUserModel, provenance: "override" } } const normalizedCategoryDefault = normalizeModel(intent?.categoryDefaultModel) diff --git a/src/tools/delegate-task/subagent-resolver.test.ts b/src/tools/delegate-task/subagent-resolver.test.ts index 8482c6cf..b1c03d73 100644 --- a/src/tools/delegate-task/subagent-resolver.test.ts +++ b/src/tools/delegate-task/subagent-resolver.test.ts @@ -4,6 +4,7 @@ import { resolveSubagentExecution } from "./subagent-resolver" import type { DelegateTaskArgs } from "./types" import type { ExecutorContext } from "./executor-types" import * as logger from "../../shared/logger" +import * as connectedProvidersCache from "../../shared/connected-providers-cache" function createBaseArgs(overrides?: Partial): DelegateTaskArgs { return { @@ -79,4 +80,25 @@ describe("resolveSubagentExecution", () => { error: "network timeout", }) }) + + test("normalizes matched agent model string before returning categoryModel", async () => { + //#given + const cacheSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue({ + models: { openai: ["grok-3"] }, + connected: ["openai"], + updatedAt: "2026-03-03T00:00:00.000Z", + }) + const args = createBaseArgs({ subagent_type: "oracle" }) + const executorCtx = createExecutorContext(async () => ([ + { name: "oracle", mode: "subagent", model: "openai/gpt-5.3-codex" }, + ])) + + //#when + const result = await resolveSubagentExecution(args, executorCtx, "sisyphus", "deep") + + //#then + expect(result.error).toBeUndefined() + expect(result.categoryModel).toEqual({ providerID: "openai", modelID: "gpt-5.3-codex" }) + cacheSpy.mockRestore() + }) }) diff --git a/src/tools/delegate-task/subagent-resolver.ts b/src/tools/delegate-task/subagent-resolver.ts index 1d0e65db..907cd5c7 100644 --- a/src/tools/delegate-task/subagent-resolver.ts +++ b/src/tools/delegate-task/subagent-resolver.ts @@ -51,7 +51,11 @@ Create the work plan directly - that's your job as the planning agent.`, try { const agentsResult = await client.app.agents() - type AgentInfo = { name: string; mode?: "subagent" | "primary" | "all"; model?: { providerID: string; modelID: string } } + type AgentInfo = { + name: string + mode?: "subagent" | "primary" | "all" + model?: string | { providerID: string; modelID: string } + } const agents = normalizeSDKResponse(agentsResult, [] as AgentInfo[], { preferResponseOnMissingData: true, }) @@ -99,7 +103,9 @@ Create the work plan directly - that's your job as the planning agent.`, if (agentOverride?.model || agentRequirement || matchedAgent.model) { const availableModels = await getAvailableModelsForDelegateTask(client) - const normalizedMatchedModel = normalizeModelFormat(matchedAgent.model as Parameters[0]) + const normalizedMatchedModel = matchedAgent.model + ? normalizeModelFormat(matchedAgent.model) + : undefined const matchedAgentModelStr = normalizedMatchedModel ? `${normalizedMatchedModel.providerID}/${normalizedMatchedModel.modelID}` : undefined @@ -122,7 +128,10 @@ Create the work plan directly - that's your job as the planning agent.`, } if (!categoryModel && matchedAgent.model) { - categoryModel = matchedAgent.model + const normalizedMatchedModel = normalizeModelFormat(matchedAgent.model) + if (normalizedMatchedModel) { + categoryModel = normalizedMatchedModel + } } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error)