Merge pull request #4435 from code-yeongyu/fix/issue-4429-team-mode-model-override
fix(agents): preserve user model overrides when team_mode enabled
This commit is contained in:
@@ -30,6 +30,104 @@ afterEach(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("createBuiltinAgents with model overrides", () => {
|
describe("createBuiltinAgents with model overrides", () => {
|
||||||
|
test("user config models take priority when team_mode is enabled", async () => {
|
||||||
|
// #given
|
||||||
|
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
|
||||||
|
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
|
||||||
|
const overrides = {
|
||||||
|
sisyphus: { model: "openai/gpt-5.5" },
|
||||||
|
explore: { model: "minimax-cn-coding-plan/MiniMax-M2.5-highspeed" },
|
||||||
|
atlas: { model: "google/antigravity-claude-opus-4-5-thinking" },
|
||||||
|
hephaestus: { model: "github-copilot/gpt-5.5" },
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// #when
|
||||||
|
const agentsWithTeamMode = await createBuiltinAgents(
|
||||||
|
[],
|
||||||
|
overrides,
|
||||||
|
undefined,
|
||||||
|
TEST_DEFAULT_MODEL,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agentsWithTeamMode.sisyphus.model).toBe("openai/gpt-5.5")
|
||||||
|
expect(agentsWithTeamMode.explore.model).toBe("minimax-cn-coding-plan/MiniMax-M2.5-highspeed")
|
||||||
|
expect(agentsWithTeamMode.atlas.model).toBe("google/antigravity-claude-opus-4-5-thinking")
|
||||||
|
expect(agentsWithTeamMode.hephaestus.model).toBe("github-copilot/gpt-5.5")
|
||||||
|
} finally {
|
||||||
|
providerModelsSpy.mockRestore()
|
||||||
|
fetchSpy.mockRestore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("team_mode does not change resolved models for user overrides", async () => {
|
||||||
|
// #given
|
||||||
|
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
|
||||||
|
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
|
||||||
|
const overrides = {
|
||||||
|
sisyphus: { model: "openai/gpt-5.5" },
|
||||||
|
explore: { model: "minimax-cn-coding-plan/MiniMax-M2.5-highspeed" },
|
||||||
|
atlas: { model: "google/antigravity-claude-opus-4-5-thinking" },
|
||||||
|
hephaestus: { model: "github-copilot/gpt-5.5" },
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// #when
|
||||||
|
const agentsWithoutTeamMode = await createBuiltinAgents(
|
||||||
|
[],
|
||||||
|
overrides,
|
||||||
|
undefined,
|
||||||
|
TEST_DEFAULT_MODEL,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
const agentsWithTeamMode = await createBuiltinAgents(
|
||||||
|
[],
|
||||||
|
overrides,
|
||||||
|
undefined,
|
||||||
|
TEST_DEFAULT_MODEL,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
[],
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agentsWithTeamMode.sisyphus.model).toBe(agentsWithoutTeamMode.sisyphus.model)
|
||||||
|
expect(agentsWithTeamMode.explore.model).toBe(agentsWithoutTeamMode.explore.model)
|
||||||
|
expect(agentsWithTeamMode.atlas.model).toBe(agentsWithoutTeamMode.atlas.model)
|
||||||
|
expect(agentsWithTeamMode.hephaestus.model).toBe(agentsWithoutTeamMode.hephaestus.model)
|
||||||
|
} finally {
|
||||||
|
providerModelsSpy.mockRestore()
|
||||||
|
fetchSpy.mockRestore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test("Sisyphus with default model has thinking config when all models available", async () => {
|
test("Sisyphus with default model has thinking config when all models available", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||||
|
|||||||
@@ -310,6 +310,62 @@ describe("applyAgentConfig builtin override protection", () => {
|
|||||||
expect(result.SiSyPhUs).toBeUndefined()
|
expect(result.SiSyPhUs).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("filters host config agent display-name aliases before they override resolved builtin models", async () => {
|
||||||
|
// given
|
||||||
|
createBuiltinAgentsSpy.mockResolvedValue({
|
||||||
|
sisyphus: {
|
||||||
|
name: "sisyphus",
|
||||||
|
prompt: "resolved sisyphus prompt",
|
||||||
|
mode: "primary",
|
||||||
|
model: "openai/gpt-5.5",
|
||||||
|
},
|
||||||
|
explore: {
|
||||||
|
name: "explore",
|
||||||
|
prompt: "resolved explore prompt",
|
||||||
|
mode: "subagent",
|
||||||
|
model: "minimax-cn-coding-plan/MiniMax-M2.5-highspeed",
|
||||||
|
},
|
||||||
|
atlas: builtinAtlasConfig,
|
||||||
|
})
|
||||||
|
const config = createBaseConfig()
|
||||||
|
config.agent = {
|
||||||
|
[getAgentListDisplayName("sisyphus")]: {
|
||||||
|
name: getAgentListDisplayName("sisyphus"),
|
||||||
|
prompt: "stale sisyphus prompt",
|
||||||
|
mode: "primary",
|
||||||
|
model: "anthropic/claude-opus-4-7",
|
||||||
|
},
|
||||||
|
[getAgentListDisplayName("explore")]: {
|
||||||
|
name: getAgentListDisplayName("explore"),
|
||||||
|
prompt: "stale explore prompt",
|
||||||
|
mode: "subagent",
|
||||||
|
model: "openai/gpt-5.4",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const pluginConfig = {
|
||||||
|
...createPluginConfig(),
|
||||||
|
team_mode: { enabled: true },
|
||||||
|
agents: {
|
||||||
|
sisyphus: { model: "openai/gpt-5.5" },
|
||||||
|
explore: { model: "minimax-cn-coding-plan/MiniMax-M2.5-highspeed" },
|
||||||
|
},
|
||||||
|
} as OhMyOpenCodeConfig
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = await applyAgentConfig({
|
||||||
|
config,
|
||||||
|
pluginConfig,
|
||||||
|
ctx: { directory: "/tmp" },
|
||||||
|
pluginComponents: createPluginComponents(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect((result[getAgentListDisplayName("sisyphus")] as AgentConfig).model).toBe("openai/gpt-5.5")
|
||||||
|
expect((result[getAgentListDisplayName("explore")] as AgentConfig).model).toBe(
|
||||||
|
"minimax-cn-coding-plan/MiniMax-M2.5-highspeed"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test("filters plugin agents whose key matches the builtin display-name alias", async () => {
|
test("filters plugin agents whose key matches the builtin display-name alias", async () => {
|
||||||
// given
|
// given
|
||||||
const pluginComponents = createPluginComponents()
|
const pluginComponents = createPluginComponents()
|
||||||
|
|||||||
@@ -259,24 +259,6 @@ export async function applyAgentConfig(params: {
|
|||||||
agentConfig["OpenCode-Builder"] = override ? { ...base, ...override } : base;
|
agentConfig["OpenCode-Builder"] = override ? { ...base, ...override } : base;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredConfigAgents = configAgent
|
|
||||||
? Object.fromEntries(
|
|
||||||
Object.entries(configAgent)
|
|
||||||
.filter(([key]) => {
|
|
||||||
if (key === "build") return false;
|
|
||||||
if (key === "plan" && shouldDemotePlan) return false;
|
|
||||||
if (key in builtinAgents) return false;
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.map(([key, value]) => {
|
|
||||||
if (!value) return [key, value];
|
|
||||||
const migrated = migrateAgentConfig(value as Record<string, unknown>);
|
|
||||||
if (!migrated.mode) migrated.mode = "subagent";
|
|
||||||
return [key, migrated];
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
: {};
|
|
||||||
|
|
||||||
const migratedBuild = configAgent?.build
|
const migratedBuild = configAgent?.build
|
||||||
? migrateAgentConfig(configAgent.build as Record<string, unknown>)
|
? migrateAgentConfig(configAgent.build as Record<string, unknown>)
|
||||||
: {};
|
: {};
|
||||||
@@ -292,6 +274,26 @@ export async function applyAgentConfig(params: {
|
|||||||
...Object.keys(agentConfig),
|
...Object.keys(agentConfig),
|
||||||
...Object.keys(builtinAgents),
|
...Object.keys(builtinAgents),
|
||||||
]);
|
]);
|
||||||
|
const filteredConfigAgentSource = configAgent
|
||||||
|
? filterProtectedAgentOverrides(
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(configAgent).filter(([key]) => {
|
||||||
|
if (key === "build") return false;
|
||||||
|
if (key === "plan" && shouldDemotePlan) return false;
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
protectedBuiltinAgentNames,
|
||||||
|
)
|
||||||
|
: {};
|
||||||
|
const filteredConfigAgents = Object.fromEntries(
|
||||||
|
Object.entries(filteredConfigAgentSource).map(([key, value]) => {
|
||||||
|
if (!value) return [key, value];
|
||||||
|
const migrated = migrateAgentConfig(value as Record<string, unknown>);
|
||||||
|
if (!migrated.mode) migrated.mode = "subagent";
|
||||||
|
return [key, migrated];
|
||||||
|
}),
|
||||||
|
);
|
||||||
const filteredUserAgents = filterProtectedAgentOverrides(
|
const filteredUserAgents = filterProtectedAgentOverrides(
|
||||||
userAgents,
|
userAgents,
|
||||||
protectedBuiltinAgentNames,
|
protectedBuiltinAgentNames,
|
||||||
@@ -373,16 +375,17 @@ export async function applyAgentConfig(params: {
|
|||||||
protectedBuiltinAgentNames,
|
protectedBuiltinAgentNames,
|
||||||
);
|
);
|
||||||
|
|
||||||
const defaultedConfigAgents = configAgent
|
const filteredConfigAgentSource = configAgent
|
||||||
? Object.fromEntries(
|
? filterProtectedAgentOverrides(configAgent, protectedBuiltinAgentNames)
|
||||||
Object.entries(configAgent).map(([key, value]) => {
|
|
||||||
if (!value) return [key, value];
|
|
||||||
const migrated = migrateAgentConfig(value as Record<string, unknown>);
|
|
||||||
if (!migrated.mode) migrated.mode = "subagent";
|
|
||||||
return [key, migrated];
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
: {};
|
: {};
|
||||||
|
const defaultedConfigAgents = Object.fromEntries(
|
||||||
|
Object.entries(filteredConfigAgentSource).map(([key, value]) => {
|
||||||
|
if (!value) return [key, value];
|
||||||
|
const migrated = migrateAgentConfig(value as Record<string, unknown>);
|
||||||
|
if (!migrated.mode) migrated.mode = "subagent";
|
||||||
|
return [key, migrated];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
params.config.agent = {
|
params.config.agent = {
|
||||||
...builtinAgents,
|
...builtinAgents,
|
||||||
|
|||||||
Reference in New Issue
Block a user