Skip to content

Commit d7bc817

Browse files
christsoclaude
andauthored
feat: auto-detect model provider and apply appropriate options (#146)
When overriding an agent's model to a different provider, the agent now automatically gets provider-appropriate reasoning options: - GPT models: `reasoningEffort`, `textVerbosity` - Anthropic models: `thinking` with `budgetTokens` ## Why utils.ts changes are required The original flow merges overrides onto pre-built agent configs: mergeAgentConfig(sisyphusAgent, { model: "gpt-5.2" }) // Result: { model: "gpt-5.2", thinking: {...} } The `thinking` config persists because it exists in the pre-built `sisyphusAgent`. GPT models ignore `thinking` and need `reasoningEffort`. The fix: call the agent factory with the resolved model, so the factory can return the correct provider-specific config: buildAgent(createSisyphusAgent, "gpt-5.2") // Result: { model: "gpt-5.2", reasoningEffort: "medium" } Closes #144 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent a9459c0 commit d7bc817

File tree

5 files changed

+164
-47
lines changed

5 files changed

+164
-47
lines changed

src/agents/oracle.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
import type { AgentConfig } from "@opencode-ai/sdk"
2+
import { isGptModel } from "./types"
23

3-
export const oracleAgent: AgentConfig = {
4-
description:
5-
"Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
6-
mode: "subagent",
7-
model: "openai/gpt-5.2",
8-
temperature: 0.1,
9-
reasoningEffort: "medium",
10-
textVerbosity: "high",
11-
tools: { write: false, edit: false, task: false, background_task: false },
12-
prompt: `You are a strategic technical advisor with deep reasoning capabilities, operating as a specialized consultant within an AI-assisted development environment.
4+
const DEFAULT_MODEL = "openai/gpt-5.2"
5+
6+
const ORACLE_SYSTEM_PROMPT = `You are a strategic technical advisor with deep reasoning capabilities, operating as a specialized consultant within an AI-assisted development environment.
137
148
## Context
159
@@ -73,5 +67,24 @@ Organize your final answer in three tiers:
7367
7468
## Critical Note
7569
76-
Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.`,
70+
Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.`
71+
72+
export function createOracleAgent(model: string = DEFAULT_MODEL): AgentConfig {
73+
const base = {
74+
description:
75+
"Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
76+
mode: "subagent" as const,
77+
model,
78+
temperature: 0.1,
79+
tools: { write: false, edit: false, task: false, background_task: false },
80+
prompt: ORACLE_SYSTEM_PROMPT,
81+
}
82+
83+
if (isGptModel(model)) {
84+
return { ...base, reasoningEffort: "medium", textVerbosity: "high" }
85+
}
86+
87+
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } }
7788
}
89+
90+
export const oracleAgent = createOracleAgent()

src/agents/sisyphus.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import type { AgentConfig } from "@opencode-ai/sdk"
2+
import { isGptModel } from "./types"
3+
4+
const DEFAULT_MODEL = "anthropic/claude-opus-4-5"
25

36
const SISYPHUS_SYSTEM_PROMPT = `<Role>
47
You are "Sisyphus" - Powerful AI Agent with orchestration capabilities from OhMyOpenCode.
@@ -452,16 +455,22 @@ If the user's approach seems problematic:
452455
453456
`
454457

455-
export const sisyphusAgent: AgentConfig = {
456-
description:
457-
"Sisyphus - Powerful AI orchestrator from OhMyOpenCode. Plans obsessively with todos, assesses search complexity before exploration, delegates strategically to specialized agents. Uses explore for internal code (parallel-friendly), librarian only for external docs, and always delegates UI work to frontend engineer.",
458-
mode: "primary",
459-
model: "anthropic/claude-opus-4-5",
460-
thinking: {
461-
type: "enabled",
462-
budgetTokens: 32000,
463-
},
464-
maxTokens: 64000,
465-
prompt: SISYPHUS_SYSTEM_PROMPT,
466-
color: "#00CED1",
458+
export function createSisyphusAgent(model: string = DEFAULT_MODEL): AgentConfig {
459+
const base = {
460+
description:
461+
"Sisyphus - Powerful AI orchestrator from OhMyOpenCode. Plans obsessively with todos, assesses search complexity before exploration, delegates strategically to specialized agents. Uses explore for internal code (parallel-friendly), librarian only for external docs, and always delegates UI work to frontend engineer.",
462+
mode: "primary" as const,
463+
model,
464+
maxTokens: 64000,
465+
prompt: SISYPHUS_SYSTEM_PROMPT,
466+
color: "#00CED1",
467+
}
468+
469+
if (isGptModel(model)) {
470+
return { ...base, reasoningEffort: "medium" }
471+
}
472+
473+
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } }
467474
}
475+
476+
export const sisyphusAgent = createSisyphusAgent()

src/agents/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import type { AgentConfig } from "@opencode-ai/sdk"
22

3+
export type AgentFactory = (model?: string) => AgentConfig
4+
5+
export function isGptModel(model: string): boolean {
6+
return model.startsWith("openai/") || model.startsWith("github-copilot/gpt-")
7+
}
8+
39
export type BuiltinAgentName =
410
| "Sisyphus"
511
| "oracle"

src/agents/utils.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { describe, test, expect } from "bun:test"
2+
import { createBuiltinAgents } from "./utils"
3+
4+
describe("createBuiltinAgents with model overrides", () => {
5+
test("Sisyphus with default model has thinking config", () => {
6+
// #given - no overrides
7+
8+
// #when
9+
const agents = createBuiltinAgents()
10+
11+
// #then
12+
expect(agents.Sisyphus.model).toBe("anthropic/claude-opus-4-5")
13+
expect(agents.Sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
14+
expect(agents.Sisyphus.reasoningEffort).toBeUndefined()
15+
})
16+
17+
test("Sisyphus with GPT model override has reasoningEffort, no thinking", () => {
18+
// #given
19+
const overrides = {
20+
Sisyphus: { model: "github-copilot/gpt-5.2" },
21+
}
22+
23+
// #when
24+
const agents = createBuiltinAgents([], overrides)
25+
26+
// #then
27+
expect(agents.Sisyphus.model).toBe("github-copilot/gpt-5.2")
28+
expect(agents.Sisyphus.reasoningEffort).toBe("medium")
29+
expect(agents.Sisyphus.thinking).toBeUndefined()
30+
})
31+
32+
test("Sisyphus with systemDefaultModel GPT has reasoningEffort, no thinking", () => {
33+
// #given
34+
const systemDefaultModel = "openai/gpt-5.2"
35+
36+
// #when
37+
const agents = createBuiltinAgents([], {}, undefined, systemDefaultModel)
38+
39+
// #then
40+
expect(agents.Sisyphus.model).toBe("openai/gpt-5.2")
41+
expect(agents.Sisyphus.reasoningEffort).toBe("medium")
42+
expect(agents.Sisyphus.thinking).toBeUndefined()
43+
})
44+
45+
test("Oracle with default model has reasoningEffort", () => {
46+
// #given - no overrides
47+
48+
// #when
49+
const agents = createBuiltinAgents()
50+
51+
// #then
52+
expect(agents.oracle.model).toBe("openai/gpt-5.2")
53+
expect(agents.oracle.reasoningEffort).toBe("medium")
54+
expect(agents.oracle.textVerbosity).toBe("high")
55+
expect(agents.oracle.thinking).toBeUndefined()
56+
})
57+
58+
test("Oracle with Claude model override has thinking, no reasoningEffort", () => {
59+
// #given
60+
const overrides = {
61+
oracle: { model: "anthropic/claude-sonnet-4" },
62+
}
63+
64+
// #when
65+
const agents = createBuiltinAgents([], overrides)
66+
67+
// #then
68+
expect(agents.oracle.model).toBe("anthropic/claude-sonnet-4")
69+
expect(agents.oracle.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
70+
expect(agents.oracle.reasoningEffort).toBeUndefined()
71+
expect(agents.oracle.textVerbosity).toBeUndefined()
72+
})
73+
74+
test("non-model overrides are still applied after factory rebuild", () => {
75+
// #given
76+
const overrides = {
77+
Sisyphus: { model: "github-copilot/gpt-5.2", temperature: 0.5 },
78+
}
79+
80+
// #when
81+
const agents = createBuiltinAgents([], overrides)
82+
83+
// #then
84+
expect(agents.Sisyphus.model).toBe("github-copilot/gpt-5.2")
85+
expect(agents.Sisyphus.temperature).toBe(0.5)
86+
})
87+
})

src/agents/utils.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
11
import type { AgentConfig } from "@opencode-ai/sdk"
2-
import type { BuiltinAgentName, AgentOverrideConfig, AgentOverrides } from "./types"
3-
import { sisyphusAgent } from "./sisyphus"
4-
import { oracleAgent } from "./oracle"
2+
import type { BuiltinAgentName, AgentOverrideConfig, AgentOverrides, AgentFactory } from "./types"
3+
import { createSisyphusAgent } from "./sisyphus"
4+
import { createOracleAgent } from "./oracle"
55
import { librarianAgent } from "./librarian"
66
import { exploreAgent } from "./explore"
77
import { frontendUiUxEngineerAgent } from "./frontend-ui-ux-engineer"
88
import { documentWriterAgent } from "./document-writer"
99
import { multimodalLookerAgent } from "./multimodal-looker"
1010
import { deepMerge } from "../shared"
1111

12-
const allBuiltinAgents: Record<BuiltinAgentName, AgentConfig> = {
13-
Sisyphus: sisyphusAgent,
14-
oracle: oracleAgent,
12+
type AgentSource = AgentFactory | AgentConfig
13+
14+
const agentSources: Record<BuiltinAgentName, AgentSource> = {
15+
Sisyphus: createSisyphusAgent,
16+
oracle: createOracleAgent,
1517
librarian: librarianAgent,
1618
explore: exploreAgent,
1719
"frontend-ui-ux-engineer": frontendUiUxEngineerAgent,
1820
"document-writer": documentWriterAgent,
1921
"multimodal-looker": multimodalLookerAgent,
2022
}
2123

24+
function isFactory(source: AgentSource): source is AgentFactory {
25+
return typeof source === "function"
26+
}
27+
28+
function buildAgent(source: AgentSource, model?: string): AgentConfig {
29+
return isFactory(source) ? source(model) : source
30+
}
31+
2232
export function createEnvContext(directory: string): string {
2333
const now = new Date()
2434
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
@@ -67,37 +77,29 @@ export function createBuiltinAgents(
6777
): Record<string, AgentConfig> {
6878
const result: Record<string, AgentConfig> = {}
6979

70-
for (const [name, config] of Object.entries(allBuiltinAgents)) {
80+
for (const [name, source] of Object.entries(agentSources)) {
7181
const agentName = name as BuiltinAgentName
7282

7383
if (disabledAgents.includes(agentName)) {
7484
continue
7585
}
7686

77-
let finalConfig = config
87+
const override = agentOverrides[agentName]
88+
const model = override?.model ?? (agentName === "Sisyphus" ? systemDefaultModel : undefined)
89+
90+
let config = buildAgent(source, model)
7891

7992
if ((agentName === "Sisyphus" || agentName === "librarian") && directory && config.prompt) {
8093
const envContext = createEnvContext(directory)
81-
finalConfig = {
82-
...config,
83-
prompt: config.prompt + envContext,
84-
}
85-
}
86-
87-
const override = agentOverrides[agentName]
88-
89-
if (agentName === "Sisyphus" && systemDefaultModel && !override?.model) {
90-
finalConfig = {
91-
...finalConfig,
92-
model: systemDefaultModel,
93-
}
94+
config = { ...config, prompt: config.prompt + envContext }
9495
}
9596

9697
if (override) {
97-
result[name] = mergeAgentConfig(finalConfig, override)
98-
} else {
99-
result[name] = finalConfig
98+
const { model: _, ...restOverride } = override
99+
config = mergeAgentConfig(config, restOverride)
100100
}
101+
102+
result[name] = config
101103
}
102104

103105
return result

0 commit comments

Comments
 (0)