Skip to content

Commit 5826dcf

Browse files
committed
fix(agents): preserve subagent fallback-only overrides
1 parent 7c70401 commit 5826dcf

6 files changed

Lines changed: 79 additions & 41 deletions

File tree

src/agents/agent-scope.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,14 @@ describe("resolveAgentConfig", () => {
564564
fallbacks: ["google/gemini-3-pro"],
565565
},
566566
},
567+
{
568+
id: "fallback-only-subagent-model",
569+
subagents: {
570+
model: {
571+
fallbacks: [],
572+
},
573+
},
574+
},
567575
{
568576
id: "default-subagent",
569577
},
@@ -591,6 +599,9 @@ describe("resolveAgentConfig", () => {
591599
"openai-codex/gpt-5.4",
592600
"zai/glm-5",
593601
]);
602+
expect(
603+
resolveSubagentModelFallbacksOverride(cfg, "fallback-only-subagent-model"),
604+
).toStrictEqual([]);
594605
expect(resolveSubagentModelFallbacksOverride(cfg, "default-subagent")).toEqual([
595606
"openai-codex/gpt-5.4",
596607
"zai/glm-5",

src/agents/agent-scope.ts

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -172,33 +172,63 @@ function resolveSelectedModelFallbacksOverride(
172172
return Array.isArray(raw.fallbacks) ? raw.fallbacks : undefined;
173173
}
174174

175-
export function resolveSubagentModelConfigSelection(params: {
175+
export type SubagentModelConfigSelectionSource = "subagent" | "agent" | "default-subagent";
176+
177+
export type SubagentModelConfigSelectionResult = {
178+
raw: AgentModelConfig;
179+
source: SubagentModelConfigSelectionSource;
180+
};
181+
182+
export function resolveSubagentModelConfigSelectionResult(params: {
176183
cfg: OpenClawConfig;
177184
agentId?: string;
178185
agentConfigOverride?: Pick<AgentConfig, "model" | "subagents">;
179-
}): AgentModelConfig | undefined {
186+
}): SubagentModelConfigSelectionResult | undefined {
180187
const agentConfig =
181188
params.agentConfigOverride ??
182189
(params.agentId ? resolveAgentConfig(params.cfg, params.agentId) : undefined);
183-
for (const raw of [
184-
agentConfig?.subagents?.model,
185-
agentConfig?.model,
186-
params.cfg.agents?.defaults?.subagents?.model,
187-
]) {
188-
if (resolvePrimaryStringValue(raw)) {
189-
return raw;
190-
}
191-
}
192-
return undefined;
190+
const candidates: SubagentModelConfigSelectionResult[] = [
191+
...(agentConfig?.subagents?.model
192+
? [{ raw: agentConfig.subagents.model, source: "subagent" as const }]
193+
: []),
194+
...(agentConfig?.model ? [{ raw: agentConfig.model, source: "agent" as const }] : []),
195+
...(params.cfg.agents?.defaults?.subagents?.model
196+
? [
197+
{
198+
raw: params.cfg.agents.defaults.subagents.model,
199+
source: "default-subagent" as const,
200+
},
201+
]
202+
: []),
203+
];
204+
return candidates.find((candidate) => resolvePrimaryStringValue(candidate.raw));
205+
}
206+
207+
export function resolveSubagentModelConfigSelection(params: {
208+
cfg: OpenClawConfig;
209+
agentId?: string;
210+
agentConfigOverride?: Pick<AgentConfig, "model" | "subagents">;
211+
}): AgentModelConfig | undefined {
212+
return resolveSubagentModelConfigSelectionResult(params)?.raw;
193213
}
194214

195215
export function resolveSubagentModelFallbacksOverride(
196216
cfg: OpenClawConfig,
197217
agentId: string,
198218
): string[] | undefined {
199-
return resolveSelectedModelFallbacksOverride(
200-
resolveSubagentModelConfigSelection({ cfg, agentId }),
201-
);
219+
const agentConfig = resolveAgentConfig(cfg, agentId);
220+
const subagentFallbacks = resolveSelectedModelFallbacksOverride(agentConfig?.subagents?.model);
221+
if (subagentFallbacks !== undefined) {
222+
return subagentFallbacks;
223+
}
224+
const selection = resolveSubagentModelConfigSelectionResult({ cfg, agentId });
225+
if (selection?.source === "agent") {
226+
return resolveSelectedModelFallbacksOverride(agentConfig?.model);
227+
}
228+
if (selection?.source === "default-subagent") {
229+
return resolveSelectedModelFallbacksOverride(cfg.agents?.defaults?.subagents?.model);
230+
}
231+
return undefined;
202232
}
203233

204234
export function resolveFallbackAgentId(params: {

src/cron/isolated-agent.model-formatting.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,20 @@ vi.mock("./isolated-agent/run-model-selection.runtime.js", () => ({
3939
resolveAllowedModelRef: resolveAllowedModelRefMock,
4040
resolveConfiguredModelRef: resolveConfiguredModelRefMock,
4141
resolveHooksGmailModel: resolveHooksGmailModelMock,
42-
resolveSubagentModelConfigSelection: ({
42+
resolveSubagentModelConfigSelectionResult: ({
4343
cfg,
4444
agentConfigOverride,
4545
}: {
4646
cfg?: { agents?: { defaults?: { subagents?: { model?: unknown } } } };
4747
agentConfigOverride?: { model?: unknown; subagents?: { model?: unknown } };
4848
}) => {
49-
for (const raw of [
50-
agentConfigOverride?.subagents?.model,
51-
agentConfigOverride?.model,
52-
cfg?.agents?.defaults?.subagents?.model,
49+
for (const candidate of [
50+
{ raw: agentConfigOverride?.subagents?.model, source: "subagent" as const },
51+
{ raw: agentConfigOverride?.model, source: "agent" as const },
52+
{ raw: cfg?.agents?.defaults?.subagents?.model, source: "default-subagent" as const },
5353
]) {
54-
if (normalizeModelSelectionMock(raw)) {
55-
return raw;
54+
if (normalizeModelSelectionMock(candidate.raw)) {
55+
return candidate;
5656
}
5757
}
5858
return undefined;

src/cron/isolated-agent/model-selection.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
resolveAllowedModelRef,
1111
resolveConfiguredModelRef,
1212
resolveHooksGmailModel,
13-
resolveSubagentModelConfigSelection,
13+
resolveSubagentModelConfigSelectionResult,
1414
} from "./run-model-selection.runtime.js";
1515

1616
type CronSessionModelOverrides = {
@@ -83,21 +83,14 @@ export async function resolveCronModelSelection(
8383
return catalog;
8484
};
8585

86-
const subagentModelConfigSelection = resolveSubagentModelConfigSelection({
86+
const subagentModelConfigSelection = resolveSubagentModelConfigSelectionResult({
8787
cfg: params.cfg,
8888
agentId: params.agentId,
8989
agentConfigOverride: params.agentConfigOverride,
9090
});
91-
const subagentModelRaw = normalizeModelSelection(subagentModelConfigSelection);
92-
const agentSubagentModel = normalizeModelSelection(params.agentConfigOverride?.subagents?.model);
93-
const agentModel = normalizeModelSelection(params.agentConfigOverride?.model);
91+
const subagentModelRaw = normalizeModelSelection(subagentModelConfigSelection?.raw);
9492
const subagentModelSource: CronModelSelectionSource =
95-
subagentModelRaw !== undefined &&
96-
agentModel !== undefined &&
97-
(agentSubagentModel === undefined || subagentModelRaw !== agentSubagentModel) &&
98-
subagentModelRaw === agentModel
99-
? "agent"
100-
: "subagent";
93+
subagentModelConfigSelection?.source === "agent" ? "agent" : "subagent";
10194
if (subagentModelRaw) {
10295
const resolvedSubagent = resolveAllowedModelRef({
10396
cfg: params.cfgWithAgentDefaults,

src/cron/isolated-agent/run-model-selection.runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
2-
export { resolveSubagentModelConfigSelection } from "../../agents/agent-scope.js";
2+
export { resolveSubagentModelConfigSelectionResult } from "../../agents/agent-scope.js";
33
export { loadModelCatalog } from "../../agents/model-catalog.js";
44
export {
55
getModelRefStatus,

src/cron/isolated-agent/run.test-harness.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,20 +164,20 @@ vi.mock("./run-model-selection.runtime.js", () => ({
164164
resolveAllowedModelRef: resolveAllowedModelRefMock,
165165
resolveConfiguredModelRef: resolveConfiguredModelRefMock,
166166
resolveHooksGmailModel: resolveHooksGmailModelMock,
167-
resolveSubagentModelConfigSelection: ({
167+
resolveSubagentModelConfigSelectionResult: ({
168168
cfg,
169169
agentConfigOverride,
170170
}: {
171171
cfg?: { agents?: { defaults?: { subagents?: { model?: unknown } } } };
172172
agentConfigOverride?: { model?: unknown; subagents?: { model?: unknown } };
173173
}) => {
174-
for (const raw of [
175-
agentConfigOverride?.subagents?.model,
176-
agentConfigOverride?.model,
177-
cfg?.agents?.defaults?.subagents?.model,
174+
for (const candidate of [
175+
{ raw: agentConfigOverride?.subagents?.model, source: "subagent" as const },
176+
{ raw: agentConfigOverride?.model, source: "agent" as const },
177+
{ raw: cfg?.agents?.defaults?.subagents?.model, source: "default-subagent" as const },
178178
]) {
179-
if (normalizeModelSelectionForTest(raw)) {
180-
return raw;
179+
if (normalizeModelSelectionForTest(candidate.raw)) {
180+
return candidate;
181181
}
182182
}
183183
return undefined;
@@ -361,6 +361,10 @@ function resetRunConfigMocks(): void {
361361
? fallbacks.filter((entry) => typeof entry === "string")
362362
: undefined;
363363
};
364+
const subagentFallbacks = resolveOverride(agentConfig?.subagents?.model);
365+
if (subagentFallbacks !== undefined) {
366+
return subagentFallbacks;
367+
}
364368
const selectedConfig = [
365369
agentConfig?.subagents?.model,
366370
agentConfig?.model,

0 commit comments

Comments
 (0)