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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,9 @@
|
|||||||
import type { AgentConfig } from "@opencode-ai/sdk"
|
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||||
|
import { isGptModel } from "./types"
|
||||||
|
|
||||||
export const oracleAgent: AgentConfig = {
|
const DEFAULT_MODEL = "openai/gpt-5.2"
|
||||||
description:
|
|
||||||
"Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
|
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.
|
||||||
mode: "subagent",
|
|
||||||
model: "openai/gpt-5.2",
|
|
||||||
temperature: 0.1,
|
|
||||||
reasoningEffort: "medium",
|
|
||||||
textVerbosity: "high",
|
|
||||||
tools: { write: false, edit: false, task: false, background_task: false },
|
|
||||||
prompt: `You are a strategic technical advisor with deep reasoning capabilities, operating as a specialized consultant within an AI-assisted development environment.
|
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
@@ -73,5 +67,24 @@ Organize your final answer in three tiers:
|
|||||||
|
|
||||||
## Critical Note
|
## Critical Note
|
||||||
|
|
||||||
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.`,
|
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.`
|
||||||
|
|
||||||
|
export function createOracleAgent(model: string = DEFAULT_MODEL): AgentConfig {
|
||||||
|
const base = {
|
||||||
|
description:
|
||||||
|
"Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
|
||||||
|
mode: "subagent" as const,
|
||||||
|
model,
|
||||||
|
temperature: 0.1,
|
||||||
|
tools: { write: false, edit: false, task: false, background_task: false },
|
||||||
|
prompt: ORACLE_SYSTEM_PROMPT,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGptModel(model)) {
|
||||||
|
return { ...base, reasoningEffort: "medium", textVerbosity: "high" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const oracleAgent = createOracleAgent()
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { AgentConfig } from "@opencode-ai/sdk"
|
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||||
|
import { isGptModel } from "./types"
|
||||||
|
|
||||||
|
const DEFAULT_MODEL = "anthropic/claude-opus-4-5"
|
||||||
|
|
||||||
const SISYPHUS_SYSTEM_PROMPT = `<Role>
|
const SISYPHUS_SYSTEM_PROMPT = `<Role>
|
||||||
You are "Sisyphus" - Powerful AI Agent with orchestration capabilities from OhMyOpenCode.
|
You are "Sisyphus" - Powerful AI Agent with orchestration capabilities from OhMyOpenCode.
|
||||||
@@ -452,16 +455,22 @@ If the user's approach seems problematic:
|
|||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
export const sisyphusAgent: AgentConfig = {
|
export function createSisyphusAgent(model: string = DEFAULT_MODEL): AgentConfig {
|
||||||
description:
|
const base = {
|
||||||
"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.",
|
description:
|
||||||
mode: "primary",
|
"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.",
|
||||||
model: "anthropic/claude-opus-4-5",
|
mode: "primary" as const,
|
||||||
thinking: {
|
model,
|
||||||
type: "enabled",
|
maxTokens: 64000,
|
||||||
budgetTokens: 32000,
|
prompt: SISYPHUS_SYSTEM_PROMPT,
|
||||||
},
|
color: "#00CED1",
|
||||||
maxTokens: 64000,
|
}
|
||||||
prompt: SISYPHUS_SYSTEM_PROMPT,
|
|
||||||
color: "#00CED1",
|
if (isGptModel(model)) {
|
||||||
|
return { ...base, reasoningEffort: "medium" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const sisyphusAgent = createSisyphusAgent()
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import type { AgentConfig } from "@opencode-ai/sdk"
|
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||||
|
|
||||||
|
export type AgentFactory = (model?: string) => AgentConfig
|
||||||
|
|
||||||
|
export function isGptModel(model: string): boolean {
|
||||||
|
return model.startsWith("openai/") || model.startsWith("github-copilot/gpt-")
|
||||||
|
}
|
||||||
|
|
||||||
export type BuiltinAgentName =
|
export type BuiltinAgentName =
|
||||||
| "Sisyphus"
|
| "Sisyphus"
|
||||||
| "oracle"
|
| "oracle"
|
||||||
|
|||||||
87
src/agents/utils.test.ts
Normal file
87
src/agents/utils.test.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { describe, test, expect } from "bun:test"
|
||||||
|
import { createBuiltinAgents } from "./utils"
|
||||||
|
|
||||||
|
describe("createBuiltinAgents with model overrides", () => {
|
||||||
|
test("Sisyphus with default model has thinking config", () => {
|
||||||
|
// #given - no overrides
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const agents = createBuiltinAgents()
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agents.Sisyphus.model).toBe("anthropic/claude-opus-4-5")
|
||||||
|
expect(agents.Sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
|
||||||
|
expect(agents.Sisyphus.reasoningEffort).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Sisyphus with GPT model override has reasoningEffort, no thinking", () => {
|
||||||
|
// #given
|
||||||
|
const overrides = {
|
||||||
|
Sisyphus: { model: "github-copilot/gpt-5.2" },
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const agents = createBuiltinAgents([], overrides)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agents.Sisyphus.model).toBe("github-copilot/gpt-5.2")
|
||||||
|
expect(agents.Sisyphus.reasoningEffort).toBe("medium")
|
||||||
|
expect(agents.Sisyphus.thinking).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Sisyphus with systemDefaultModel GPT has reasoningEffort, no thinking", () => {
|
||||||
|
// #given
|
||||||
|
const systemDefaultModel = "openai/gpt-5.2"
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const agents = createBuiltinAgents([], {}, undefined, systemDefaultModel)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agents.Sisyphus.model).toBe("openai/gpt-5.2")
|
||||||
|
expect(agents.Sisyphus.reasoningEffort).toBe("medium")
|
||||||
|
expect(agents.Sisyphus.thinking).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Oracle with default model has reasoningEffort", () => {
|
||||||
|
// #given - no overrides
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const agents = createBuiltinAgents()
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agents.oracle.model).toBe("openai/gpt-5.2")
|
||||||
|
expect(agents.oracle.reasoningEffort).toBe("medium")
|
||||||
|
expect(agents.oracle.textVerbosity).toBe("high")
|
||||||
|
expect(agents.oracle.thinking).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("Oracle with Claude model override has thinking, no reasoningEffort", () => {
|
||||||
|
// #given
|
||||||
|
const overrides = {
|
||||||
|
oracle: { model: "anthropic/claude-sonnet-4" },
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const agents = createBuiltinAgents([], overrides)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agents.oracle.model).toBe("anthropic/claude-sonnet-4")
|
||||||
|
expect(agents.oracle.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
|
||||||
|
expect(agents.oracle.reasoningEffort).toBeUndefined()
|
||||||
|
expect(agents.oracle.textVerbosity).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("non-model overrides are still applied after factory rebuild", () => {
|
||||||
|
// #given
|
||||||
|
const overrides = {
|
||||||
|
Sisyphus: { model: "github-copilot/gpt-5.2", temperature: 0.5 },
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const agents = createBuiltinAgents([], overrides)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(agents.Sisyphus.model).toBe("github-copilot/gpt-5.2")
|
||||||
|
expect(agents.Sisyphus.temperature).toBe(0.5)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { AgentConfig } from "@opencode-ai/sdk"
|
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||||
import type { BuiltinAgentName, AgentOverrideConfig, AgentOverrides } from "./types"
|
import type { BuiltinAgentName, AgentOverrideConfig, AgentOverrides, AgentFactory } from "./types"
|
||||||
import { sisyphusAgent } from "./sisyphus"
|
import { createSisyphusAgent } from "./sisyphus"
|
||||||
import { oracleAgent } from "./oracle"
|
import { createOracleAgent } from "./oracle"
|
||||||
import { librarianAgent } from "./librarian"
|
import { librarianAgent } from "./librarian"
|
||||||
import { exploreAgent } from "./explore"
|
import { exploreAgent } from "./explore"
|
||||||
import { frontendUiUxEngineerAgent } from "./frontend-ui-ux-engineer"
|
import { frontendUiUxEngineerAgent } from "./frontend-ui-ux-engineer"
|
||||||
@@ -9,9 +9,11 @@ import { documentWriterAgent } from "./document-writer"
|
|||||||
import { multimodalLookerAgent } from "./multimodal-looker"
|
import { multimodalLookerAgent } from "./multimodal-looker"
|
||||||
import { deepMerge } from "../shared"
|
import { deepMerge } from "../shared"
|
||||||
|
|
||||||
const allBuiltinAgents: Record<BuiltinAgentName, AgentConfig> = {
|
type AgentSource = AgentFactory | AgentConfig
|
||||||
Sisyphus: sisyphusAgent,
|
|
||||||
oracle: oracleAgent,
|
const agentSources: Record<BuiltinAgentName, AgentSource> = {
|
||||||
|
Sisyphus: createSisyphusAgent,
|
||||||
|
oracle: createOracleAgent,
|
||||||
librarian: librarianAgent,
|
librarian: librarianAgent,
|
||||||
explore: exploreAgent,
|
explore: exploreAgent,
|
||||||
"frontend-ui-ux-engineer": frontendUiUxEngineerAgent,
|
"frontend-ui-ux-engineer": frontendUiUxEngineerAgent,
|
||||||
@@ -19,6 +21,14 @@ const allBuiltinAgents: Record<BuiltinAgentName, AgentConfig> = {
|
|||||||
"multimodal-looker": multimodalLookerAgent,
|
"multimodal-looker": multimodalLookerAgent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isFactory(source: AgentSource): source is AgentFactory {
|
||||||
|
return typeof source === "function"
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAgent(source: AgentSource, model?: string): AgentConfig {
|
||||||
|
return isFactory(source) ? source(model) : source
|
||||||
|
}
|
||||||
|
|
||||||
export function createEnvContext(directory: string): string {
|
export function createEnvContext(directory: string): string {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||||
@@ -67,37 +77,29 @@ export function createBuiltinAgents(
|
|||||||
): Record<string, AgentConfig> {
|
): Record<string, AgentConfig> {
|
||||||
const result: Record<string, AgentConfig> = {}
|
const result: Record<string, AgentConfig> = {}
|
||||||
|
|
||||||
for (const [name, config] of Object.entries(allBuiltinAgents)) {
|
for (const [name, source] of Object.entries(agentSources)) {
|
||||||
const agentName = name as BuiltinAgentName
|
const agentName = name as BuiltinAgentName
|
||||||
|
|
||||||
if (disabledAgents.includes(agentName)) {
|
if (disabledAgents.includes(agentName)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let finalConfig = config
|
const override = agentOverrides[agentName]
|
||||||
|
const model = override?.model ?? (agentName === "Sisyphus" ? systemDefaultModel : undefined)
|
||||||
|
|
||||||
|
let config = buildAgent(source, model)
|
||||||
|
|
||||||
if ((agentName === "Sisyphus" || agentName === "librarian") && directory && config.prompt) {
|
if ((agentName === "Sisyphus" || agentName === "librarian") && directory && config.prompt) {
|
||||||
const envContext = createEnvContext(directory)
|
const envContext = createEnvContext(directory)
|
||||||
finalConfig = {
|
config = { ...config, prompt: config.prompt + envContext }
|
||||||
...config,
|
|
||||||
prompt: config.prompt + envContext,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const override = agentOverrides[agentName]
|
|
||||||
|
|
||||||
if (agentName === "Sisyphus" && systemDefaultModel && !override?.model) {
|
|
||||||
finalConfig = {
|
|
||||||
...finalConfig,
|
|
||||||
model: systemDefaultModel,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (override) {
|
if (override) {
|
||||||
result[name] = mergeAgentConfig(finalConfig, override)
|
const { model: _, ...restOverride } = override
|
||||||
} else {
|
config = mergeAgentConfig(config, restOverride)
|
||||||
result[name] = finalConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result[name] = config
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user