Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/backends/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ async function buildExecutionPlan(
gitHubToken: string | undefined,
isGitHubAck: boolean,
engineId: string,
engine: AgentEngine,
): Promise<
Omit<AgentExecutionPlan, 'progressReporter'> & {
reviewSidecarPath?: string;
Expand Down Expand Up @@ -173,7 +174,7 @@ async function buildExecutionPlan(
const {
systemPrompt,
taskPrompt: taskPromptOverride,
model,
model: rawModel,
maxIterations,
contextFiles,
} = await resolveModelConfig({
Expand All @@ -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
Expand Down Expand Up @@ -412,6 +416,7 @@ async function resolvePartialExecutionPlan(
gitHubToken,
isGitHubAck,
engine.definition.id,
engine,
);

const partialInput = gitHubToken
Expand Down
9 changes: 9 additions & 0 deletions src/backends/claude-code/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@ export class ClaudeCodeEngine implements AgentEngine {
return true;
}

resolveModel(cascadeModel: string): string {
return resolveClaudeModel(cascadeModel);
}

async execute(input: AgentExecutionPlan): Promise<AgentEngineResult> {
const startTime = Date.now();
const systemPrompt = buildSystemPrompt(input.systemPrompt, input.availableTools);
Expand All @@ -464,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', {
Expand Down
9 changes: 9 additions & 0 deletions src/backends/codex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,10 @@ export class CodexEngine implements AgentEngine {
return true;
}

resolveModel(cascadeModel: string): string {
return resolveCodexModel(cascadeModel);
}

async execute(input: AgentExecutionPlan): Promise<AgentEngineResult> {
const startTime = Date.now();
const systemPrompt = buildSystemPrompt(input.systemPrompt, input.availableTools);
Expand All @@ -488,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);
Expand Down
9 changes: 9 additions & 0 deletions src/backends/opencode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,9 +801,18 @@ export class OpenCodeEngine implements AgentEngine {
return true;
}

resolveModel(cascadeModel: string): string {
return resolveOpenCodeModel(cascadeModel);
}

async execute(input: AgentExecutionPlan): Promise<AgentEngineResult> {
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(
Expand Down
6 changes: 6 additions & 0 deletions src/backends/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,10 @@ export interface AgentEngine {

execute(input: AgentExecutionPlan): Promise<AgentEngineResult>;
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;
}
Loading