Skip to content

Commit ffe9149

Browse files
christsoclaude
andcommitted
feat: auto-detect model provider and apply appropriate options
When overriding an agent to use a different model provider, the agent now automatically gets provider-appropriate reasoning options: - GPT models: `reasoningEffort`, `textVerbosity` - Anthropic models: `thinking` with `budgetTokens` Changes: - Add `AgentFactory` type and `isGptModel` helper to types.ts - Add `createSisyphusAgent` factory to sisyphus.ts - Add `createOracleAgent` factory to oracle.ts - Update utils.ts to use factories when model overrides are detected - Add unit tests for factory behavior Closes #144 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent e54a65d commit ffe9149

File tree

5 files changed

+157
-32
lines changed

5 files changed

+157
-32
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.
@@ -451,16 +454,22 @@ If the user's approach seems problematic:
451454
452455
`
453456

454-
export const sisyphusAgent: AgentConfig = {
455-
description:
456-
"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.",
457-
mode: "primary",
458-
model: "anthropic/claude-opus-4-5",
459-
thinking: {
460-
type: "enabled",
461-
budgetTokens: 32000,
462-
},
463-
maxTokens: 64000,
464-
prompt: SISYPHUS_SYSTEM_PROMPT,
465-
color: "#00CED1",
457+
export function createSisyphusAgent(model: string = DEFAULT_MODEL): AgentConfig {
458+
const base = {
459+
description:
460+
"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.",
461+
mode: "primary" as const,
462+
model,
463+
maxTokens: 64000,
464+
prompt: SISYPHUS_SYSTEM_PROMPT,
465+
color: "#00CED1",
466+
}
467+
468+
if (isGptModel(model)) {
469+
return { ...base, reasoningEffort: "medium" }
470+
}
471+
472+
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } }
466473
}
474+
475+
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: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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 { sisyphusAgent, createSisyphusAgent } from "./sisyphus"
4+
import { oracleAgent, createOracleAgent } from "./oracle"
55
import { librarianAgent } from "./librarian"
66
import { exploreAgent } from "./explore"
77
import { frontendUiUxEngineerAgent } from "./frontend-ui-ux-engineer"
@@ -19,6 +19,11 @@ const allBuiltinAgents: Record<BuiltinAgentName, AgentConfig> = {
1919
"multimodal-looker": multimodalLookerAgent,
2020
}
2121

22+
const agentFactories: Partial<Record<BuiltinAgentName, AgentFactory>> = {
23+
Sisyphus: createSisyphusAgent,
24+
oracle: createOracleAgent,
25+
}
26+
2227
export function createEnvContext(directory: string): string {
2328
const now = new Date()
2429
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
@@ -85,16 +90,21 @@ export function createBuiltinAgents(
8590
}
8691

8792
const override = agentOverrides[agentName]
88-
89-
if (agentName === "Sisyphus" && systemDefaultModel && !override?.model) {
90-
finalConfig = {
91-
...finalConfig,
92-
model: systemDefaultModel,
93+
const factory = agentFactories[agentName]
94+
const finalModel = override?.model ?? (agentName === "Sisyphus" ? systemDefaultModel : undefined) ?? finalConfig.model
95+
const modelChanged = factory && finalModel && finalModel !== config.model
96+
97+
if (modelChanged) {
98+
finalConfig = factory(finalModel)
99+
if ((agentName === "Sisyphus" || agentName === "librarian") && directory && finalConfig.prompt) {
100+
const envContext = createEnvContext(directory)
101+
finalConfig = { ...finalConfig, prompt: finalConfig.prompt + envContext }
93102
}
94103
}
95104

96105
if (override) {
97-
result[name] = mergeAgentConfig(finalConfig, override)
106+
const { model: _, ...restOverride } = override
107+
result[name] = mergeAgentConfig(finalConfig, restOverride)
98108
} else {
99109
result[name] = finalConfig
100110
}

0 commit comments

Comments
 (0)