From b4e57462618094a71ce83b6ef7b031c1d04cd256 Mon Sep 17 00:00:00 2001 From: Cascade Bot Date: Sat, 14 Mar 2026 19:03:27 +0000 Subject: [PATCH 1/2] feat(backends): add resolveModel() to AgentEngine interface --- src/backends/adapter.ts | 7 ++++++- src/backends/claude-code/index.ts | 4 ++++ src/backends/codex/index.ts | 4 ++++ src/backends/opencode/index.ts | 4 ++++ src/backends/types.ts | 6 ++++++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/backends/adapter.ts b/src/backends/adapter.ts index 0efaeda2..3f329003 100644 --- a/src/backends/adapter.ts +++ b/src/backends/adapter.ts @@ -133,6 +133,7 @@ async function buildExecutionPlan( gitHubToken: string | undefined, isGitHubAck: boolean, engineId: string, + engine: AgentEngine, ): Promise< Omit & { reviewSidecarPath?: string; @@ -173,7 +174,7 @@ async function buildExecutionPlan( const { systemPrompt, taskPrompt: taskPromptOverride, - model, + model: rawModel, maxIterations, contextFiles, } = await resolveModelConfig({ @@ -186,6 +187,9 @@ async function buildExecutionPlan( agentInput: input, }); + // Allow the engine to resolve/validate the model string (e.g. strip provider prefix) + const model = engine.resolveModel ? engine.resolveModel(rawModel) : rawModel; + const profile = await getAgentProfile(agentType); // Use profile to fetch agent-specific context injections @@ -412,6 +416,7 @@ async function resolvePartialExecutionPlan( gitHubToken, isGitHubAck, engine.definition.id, + engine, ); const partialInput = gitHubToken diff --git a/src/backends/claude-code/index.ts b/src/backends/claude-code/index.ts index 4e34c1b4..5292b124 100644 --- a/src/backends/claude-code/index.ts +++ b/src/backends/claude-code/index.ts @@ -456,6 +456,10 @@ export class ClaudeCodeEngine implements AgentEngine { return true; } + resolveModel(cascadeModel: string): string { + return resolveClaudeModel(cascadeModel); + } + async execute(input: AgentExecutionPlan): Promise { const startTime = Date.now(); const systemPrompt = buildSystemPrompt(input.systemPrompt, input.availableTools); diff --git a/src/backends/codex/index.ts b/src/backends/codex/index.ts index 117db9b9..9d1defbd 100644 --- a/src/backends/codex/index.ts +++ b/src/backends/codex/index.ts @@ -480,6 +480,10 @@ export class CodexEngine implements AgentEngine { return true; } + resolveModel(cascadeModel: string): string { + return resolveCodexModel(cascadeModel); + } + async execute(input: AgentExecutionPlan): Promise { const startTime = Date.now(); const systemPrompt = buildSystemPrompt(input.systemPrompt, input.availableTools); diff --git a/src/backends/opencode/index.ts b/src/backends/opencode/index.ts index f07a742b..4921986f 100644 --- a/src/backends/opencode/index.ts +++ b/src/backends/opencode/index.ts @@ -801,6 +801,10 @@ export class OpenCodeEngine implements AgentEngine { return true; } + resolveModel(cascadeModel: string): string { + return resolveOpenCodeModel(cascadeModel); + } + async execute(input: AgentExecutionPlan): Promise { const settings = resolveOpenCodeSettings(input.project); const agent = 'build' as const; diff --git a/src/backends/types.ts b/src/backends/types.ts index 9b510102..b8bef31e 100644 --- a/src/backends/types.ts +++ b/src/backends/types.ts @@ -147,4 +147,10 @@ export interface AgentEngine { execute(input: AgentExecutionPlan): Promise; supportsAgentType(agentType: string): boolean; + /** + * Optionally resolve a CASCADE model string to the engine-specific model identifier. + * Engines that need model validation (e.g., Claude Code, Codex) implement this method. + * Engines that pass the model through unchanged (e.g., LLMist) do not need to implement it. + */ + resolveModel?(cascadeModel: string): string; } From 2179e0f3e618a989c80e0ed2e82baf5879670802 Mon Sep 17 00:00:00 2001 From: Cascade Bot Date: Sat, 14 Mar 2026 19:15:22 +0000 Subject: [PATCH 2/2] docs(backends): comment double model resolution for backward compat Add explanatory comments to the resolve*Model() calls in each engine's execute() method clarifying that the redundancy is intentional. These calls remain for backward compatibility when execute() is invoked directly without going through the adapter's pre-resolution step. All three resolve functions are idempotent so the double call is safe. Co-Authored-By: Claude Opus 4.6 --- src/backends/claude-code/index.ts | 5 +++++ src/backends/codex/index.ts | 5 +++++ src/backends/opencode/index.ts | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/backends/claude-code/index.ts b/src/backends/claude-code/index.ts index 5292b124..2ff9e22f 100644 --- a/src/backends/claude-code/index.ts +++ b/src/backends/claude-code/index.ts @@ -468,6 +468,11 @@ export class ClaudeCodeEngine implements AgentEngine { input.contextInjections, input.repoDir, ); + // Resolve model again here for backward compatibility: execute() may be called + // directly (e.g. in tests) without going through the adapter, so we cannot rely + // solely on the adapter's engine.resolveModel() pre-resolution. Since + // resolveClaudeModel() is idempotent, calling it twice via the normal adapter path + // is safe. const model = resolveClaudeModel(input.model); input.logWriter('INFO', 'Starting Claude Code SDK execution', { diff --git a/src/backends/codex/index.ts b/src/backends/codex/index.ts index 9d1defbd..67123619 100644 --- a/src/backends/codex/index.ts +++ b/src/backends/codex/index.ts @@ -492,6 +492,11 @@ export class CodexEngine implements AgentEngine { input.contextInjections, input.repoDir, ); + // Resolve model again here for backward compatibility: execute() may be called + // directly (e.g. in tests) without going through the adapter, so we cannot rely + // solely on the adapter's engine.resolveModel() pre-resolution. Since + // resolveCodexModel() is idempotent, calling it twice via the normal adapter path + // is safe. const model = resolveCodexModel(input.model); const settings = resolveCodexSettings(input.project, input.nativeToolCapabilities); assertHeadlessCodexSettings(settings); diff --git a/src/backends/opencode/index.ts b/src/backends/opencode/index.ts index 4921986f..971c74b1 100644 --- a/src/backends/opencode/index.ts +++ b/src/backends/opencode/index.ts @@ -808,6 +808,11 @@ export class OpenCodeEngine implements AgentEngine { async execute(input: AgentExecutionPlan): Promise { const settings = resolveOpenCodeSettings(input.project); const agent = 'build' as const; + // Resolve model again here for backward compatibility: execute() may be called + // directly (e.g. in tests) without going through the adapter, so we cannot rely + // solely on the adapter's engine.resolveModel() pre-resolution. Since + // resolveOpenCodeModel() is idempotent, calling it twice via the normal adapter path + // is safe. const model = resolveOpenCodeModel(input.model); const config = buildConfig(input, model, settings); const { prompt: taskPrompt, hasOffloadedContext } = await buildTaskPrompt(