Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions src/agents/atlas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,9 +523,6 @@ function buildDynamicOrchestratorPrompt(ctx?: OrchestratorContext): string {
}

export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
if (!ctx.model) {
throw new Error("createAtlasAgent requires a model in context")
}
const restrictions = createAgentToolRestrictions([
"task",
"call_omo_agent",
Expand All @@ -534,7 +531,7 @@ export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
description:
"Orchestrates work via delegate_task() to complete ALL tasks in a todo list until fully done",
mode: "primary" as const,
model: ctx.model,
...(ctx.model ? { model: ctx.model } : {}),
temperature: 0.1,
prompt: buildDynamicOrchestratorPrompt(ctx),
thinking: { type: "enabled", budgetTokens: 32000 },
Expand Down
24 changes: 24 additions & 0 deletions src/agents/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,30 @@ describe("createBuiltinAgents with model overrides", () => {
})
})

describe("createBuiltinAgents without systemDefaultModel", () => {
test("creates agents successfully without systemDefaultModel", async () => {
// #given - no systemDefaultModel provided

// #when
const agents = await createBuiltinAgents([], {}, undefined, undefined)

// #then - agents should still be created using fallback chain
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe("openai/gpt-5.2")
})

test("sisyphus uses fallback chain when systemDefaultModel undefined", async () => {
// #given - no systemDefaultModel

// #when
const agents = await createBuiltinAgents([], {}, undefined, undefined)

// #then - sisyphus should use its fallback chain
expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-5")
})
})

describe("buildAgent with category and skills", () => {
const { buildAgent } = require("./utils")
const TEST_MODEL = "anthropic/claude-opus-4-5"
Expand Down
99 changes: 50 additions & 49 deletions src/agents/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,6 @@ export async function createBuiltinAgents(
client?: any,
browserProvider?: BrowserAutomationProvider
): Promise<Record<string, AgentConfig>> {
if (!systemDefaultModel) {
throw new Error("createBuiltinAgents requires systemDefaultModel")
}

const connectedProviders = readConnectedProvidersCache()
const availableModels = client
? await fetchAvailableModels(client, { connectedProviders: connectedProviders ?? undefined })
Expand Down Expand Up @@ -201,13 +197,14 @@ export async function createBuiltinAgents(
const override = findCaseInsensitive(agentOverrides, agentName)
const requirement = AGENT_MODEL_REQUIREMENTS[agentName]

// Use resolver to determine model
const { model, variant: resolvedVariant } = resolveModelWithFallback({
const resolution = resolveModelWithFallback({
userModel: override?.model,
fallbackChain: requirement?.fallbackChain,
availableModels,
systemDefaultModel,
})
if (!resolution) continue
const { model, variant: resolvedVariant } = resolution

let config = buildAgent(source, model, mergedCategories, gitMasterConfig, browserProvider)

Expand Down Expand Up @@ -243,72 +240,76 @@ export async function createBuiltinAgents(
const sisyphusOverride = agentOverrides["sisyphus"]
const sisyphusRequirement = AGENT_MODEL_REQUIREMENTS["sisyphus"]

// Use resolver to determine model
const { model: sisyphusModel, variant: sisyphusResolvedVariant } = resolveModelWithFallback({
const sisyphusResolution = resolveModelWithFallback({
userModel: sisyphusOverride?.model,
fallbackChain: sisyphusRequirement?.fallbackChain,
availableModels,
systemDefaultModel,
})

let sisyphusConfig = createSisyphusAgent(
sisyphusModel,
availableAgents,
undefined,
availableSkills,
availableCategories
)

// Apply variant from override or resolved fallback chain
if (sisyphusOverride?.variant) {
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusOverride.variant }
} else if (sisyphusResolvedVariant) {
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusResolvedVariant }
}
if (sisyphusResolution) {
const { model: sisyphusModel, variant: sisyphusResolvedVariant } = sisyphusResolution

let sisyphusConfig = createSisyphusAgent(
sisyphusModel,
availableAgents,
undefined,
availableSkills,
availableCategories
)

if (sisyphusOverride?.variant) {
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusOverride.variant }
} else if (sisyphusResolvedVariant) {
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusResolvedVariant }
}

if (directory && sisyphusConfig.prompt) {
const envContext = createEnvContext()
sisyphusConfig = { ...sisyphusConfig, prompt: sisyphusConfig.prompt + envContext }
}
if (directory && sisyphusConfig.prompt) {
const envContext = createEnvContext()
sisyphusConfig = { ...sisyphusConfig, prompt: sisyphusConfig.prompt + envContext }
}

if (sisyphusOverride) {
sisyphusConfig = mergeAgentConfig(sisyphusConfig, sisyphusOverride)
}
if (sisyphusOverride) {
sisyphusConfig = mergeAgentConfig(sisyphusConfig, sisyphusOverride)
}

result["sisyphus"] = sisyphusConfig
result["sisyphus"] = sisyphusConfig
}
}

if (!disabledAgents.includes("atlas")) {
const orchestratorOverride = agentOverrides["atlas"]
const atlasRequirement = AGENT_MODEL_REQUIREMENTS["atlas"]

// Use resolver to determine model
const { model: atlasModel, variant: atlasResolvedVariant } = resolveModelWithFallback({
const atlasResolution = resolveModelWithFallback({
userModel: orchestratorOverride?.model,
fallbackChain: atlasRequirement?.fallbackChain,
availableModels,
systemDefaultModel,
})

let orchestratorConfig = createAtlasAgent({
model: atlasModel,
availableAgents,
availableSkills,
userCategories: categories,
})

// Apply variant from override or resolved fallback chain
if (orchestratorOverride?.variant) {
orchestratorConfig = { ...orchestratorConfig, variant: orchestratorOverride.variant }
} else if (atlasResolvedVariant) {
orchestratorConfig = { ...orchestratorConfig, variant: atlasResolvedVariant }
}
if (atlasResolution) {
const { model: atlasModel, variant: atlasResolvedVariant } = atlasResolution

let orchestratorConfig = createAtlasAgent({
model: atlasModel,
availableAgents,
availableSkills,
userCategories: categories,
})

if (orchestratorOverride?.variant) {
orchestratorConfig = { ...orchestratorConfig, variant: orchestratorOverride.variant }
} else if (atlasResolvedVariant) {
orchestratorConfig = { ...orchestratorConfig, variant: atlasResolvedVariant }
}

if (orchestratorOverride) {
orchestratorConfig = mergeAgentConfig(orchestratorConfig, orchestratorOverride)
}
if (orchestratorOverride) {
orchestratorConfig = mergeAgentConfig(orchestratorConfig, orchestratorOverride)
}

result["atlas"] = orchestratorConfig
result["atlas"] = orchestratorConfig
}
}

return result
Expand Down
35 changes: 0 additions & 35 deletions src/plugin-handlers/config-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,41 +105,6 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
log(`Plugin load errors`, { errors: pluginComponents.errors });
}

if (!(config.model as string | undefined)?.trim()) {
let fallbackModel: string | undefined

for (const agentConfig of Object.values(pluginConfig.agents ?? {})) {
const model = (agentConfig as { model?: string })?.model
if (model && typeof model === 'string' && model.trim()) {
fallbackModel = model.trim()
break
}
}

if (!fallbackModel) {
for (const categoryConfig of Object.values(pluginConfig.categories ?? {})) {
const model = (categoryConfig as { model?: string })?.model
if (model && typeof model === 'string' && model.trim()) {
fallbackModel = model.trim()
break
}
}
}

if (fallbackModel) {
config.model = fallbackModel
log(`No default model specified, using fallback from config: ${fallbackModel}`)
} else {
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null })
throw new Error(
'oh-my-opencode requires a default model.\n\n' +
`Add this to ${paths.configJsonc}:\n\n` +
' "model": "anthropic/claude-sonnet-4-5"\n\n' +
'(Replace with your preferred provider/model)'
)
}
}

// Migrate disabled_agents from old names to new names
const migratedDisabledAgents = (pluginConfig.disabled_agents ?? []).map(agent => {
return AGENT_NAME_MAP[agent.toLowerCase()] ?? AGENT_NAME_MAP[agent] ?? agent
Expand Down
Loading