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", () => {
|
||||
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 () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
|
||||
@@ -310,6 +310,62 @@ describe("applyAgentConfig builtin override protection", () => {
|
||||
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 () => {
|
||||
// given
|
||||
const pluginComponents = createPluginComponents()
|
||||
|
||||
@@ -259,24 +259,6 @@ export async function applyAgentConfig(params: {
|
||||
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
|
||||
? migrateAgentConfig(configAgent.build as Record<string, unknown>)
|
||||
: {};
|
||||
@@ -292,6 +274,26 @@ export async function applyAgentConfig(params: {
|
||||
...Object.keys(agentConfig),
|
||||
...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(
|
||||
userAgents,
|
||||
protectedBuiltinAgentNames,
|
||||
@@ -373,16 +375,17 @@ export async function applyAgentConfig(params: {
|
||||
protectedBuiltinAgentNames,
|
||||
);
|
||||
|
||||
const defaultedConfigAgents = configAgent
|
||||
? Object.fromEntries(
|
||||
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 filteredConfigAgentSource = configAgent
|
||||
? filterProtectedAgentOverrides(configAgent, protectedBuiltinAgentNames)
|
||||
: {};
|
||||
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 = {
|
||||
...builtinAgents,
|
||||
|
||||
Reference in New Issue
Block a user