import { createBuiltinAgents } from "../agents"; import { createSisyphusJuniorAgentWithOverrides } from "../agents/sisyphus-junior"; import { loadUserCommands, loadProjectCommands, loadOpencodeGlobalCommands, loadOpencodeProjectCommands, } from "../features/claude-code-command-loader"; import { loadBuiltinCommands } from "../features/builtin-commands"; import { loadUserSkills, loadProjectSkills, loadOpencodeGlobalSkills, loadOpencodeProjectSkills, } from "../features/opencode-skill-loader"; import { loadUserAgents, loadProjectAgents, } from "../features/claude-code-agent-loader"; import { loadMcpConfigs } from "../features/claude-code-mcp-loader"; import { loadAllPluginComponents } from "../features/claude-code-plugin-loader"; import { createBuiltinMcps } from "../mcp"; import type { OhMyOpenCodeConfig } from "../config"; import { log } from "../shared"; import { migrateAgentConfig } from "../shared/permission-compat"; import { PROMETHEUS_SYSTEM_PROMPT, PROMETHEUS_PERMISSION } from "../agents/prometheus-prompt"; import { DEFAULT_CATEGORIES } from "../tools/sisyphus-task/constants"; import type { ModelCacheState } from "../plugin-state"; import type { CategoryConfig } from "../config/schema"; export interface ConfigHandlerDeps { ctx: { directory: string }; pluginConfig: OhMyOpenCodeConfig; modelCacheState: ModelCacheState; } export function resolveCategoryConfig( categoryName: string, userCategories?: Record ): CategoryConfig | undefined { return userCategories?.[categoryName] ?? DEFAULT_CATEGORIES[categoryName]; } export function createConfigHandler(deps: ConfigHandlerDeps) { const { ctx, pluginConfig, modelCacheState } = deps; return async (config: Record) => { type ProviderConfig = { options?: { headers?: Record }; models?: Record; }; const providers = config.provider as | Record | undefined; const anthropicBeta = providers?.anthropic?.options?.headers?.["anthropic-beta"]; modelCacheState.anthropicContext1MEnabled = anthropicBeta?.includes("context-1m") ?? false; if (providers) { for (const [providerID, providerConfig] of Object.entries(providers)) { const models = providerConfig?.models; if (models) { for (const [modelID, modelConfig] of Object.entries(models)) { const contextLimit = modelConfig?.limit?.context; if (contextLimit) { modelCacheState.modelContextLimitsCache.set( `${providerID}/${modelID}`, contextLimit ); } } } } } const pluginComponents = (pluginConfig.claude_code?.plugins ?? true) ? await loadAllPluginComponents({ enabledPluginsOverride: pluginConfig.claude_code?.plugins_override, }) : { commands: {}, skills: {}, agents: {}, mcpServers: {}, hooksConfigs: [], plugins: [], errors: [], }; if (pluginComponents.plugins.length > 0) { log(`Loaded ${pluginComponents.plugins.length} Claude Code plugins`, { plugins: pluginComponents.plugins.map((p) => `${p.name}@${p.version}`), }); } if (pluginComponents.errors.length > 0) { log(`Plugin load errors`, { errors: pluginComponents.errors }); } const builtinAgents = createBuiltinAgents( pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory, config.model as string | undefined, pluginConfig.categories ); // Claude Code agents: Do NOT apply permission migration // Claude Code uses whitelist-based tools format which is semantically different // from OpenCode's denylist-based permission system const userAgents = (pluginConfig.claude_code?.agents ?? true) ? loadUserAgents() : {}; const projectAgents = (pluginConfig.claude_code?.agents ?? true) ? loadProjectAgents() : {}; // Plugin agents: Apply permission migration for compatibility const rawPluginAgents = pluginComponents.agents; const pluginAgents = Object.fromEntries( Object.entries(rawPluginAgents).map(([k, v]) => [ k, v ? migrateAgentConfig(v as Record) : v, ]) ); const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true; const builderEnabled = pluginConfig.sisyphus_agent?.default_builder_enabled ?? false; const plannerEnabled = pluginConfig.sisyphus_agent?.planner_enabled ?? true; const replacePlan = pluginConfig.sisyphus_agent?.replace_plan ?? true; type AgentConfig = Record< string, Record | undefined > & { build?: Record; plan?: Record; explore?: { tools?: Record }; librarian?: { tools?: Record }; "multimodal-looker"?: { tools?: Record }; "orchestrator-sisyphus"?: { tools?: Record }; }; const configAgent = config.agent as AgentConfig | undefined; if (isSisyphusEnabled && builtinAgents.Sisyphus) { (config as { default_agent?: string }).default_agent = "Sisyphus"; const agentConfig: Record = { Sisyphus: builtinAgents.Sisyphus, }; agentConfig["Sisyphus-Junior"] = createSisyphusJuniorAgentWithOverrides( pluginConfig.agents?.["Sisyphus-Junior"] ); if (builderEnabled) { const { name: _buildName, ...buildConfigWithoutName } = configAgent?.build ?? {}; const migratedBuildConfig = migrateAgentConfig( buildConfigWithoutName as Record ); const openCodeBuilderOverride = pluginConfig.agents?.["OpenCode-Builder"]; const openCodeBuilderBase = { ...migratedBuildConfig, description: `${configAgent?.build?.description ?? "Build agent"} (OpenCode default)`, }; agentConfig["OpenCode-Builder"] = openCodeBuilderOverride ? { ...openCodeBuilderBase, ...openCodeBuilderOverride } : openCodeBuilderBase; } if (plannerEnabled) { const { name: _planName, mode: _planMode, ...planConfigWithoutName } = configAgent?.plan ?? {}; const migratedPlanConfig = migrateAgentConfig( planConfigWithoutName as Record ); const prometheusOverride = pluginConfig.agents?.["Prometheus (Planner)"] as | (Record & { category?: string; model?: string }) | undefined; const defaultModel = config.model as string | undefined; // Resolve full category config (model, temperature, top_p, tools, etc.) // Apply all category properties when category is specified, but explicit // overrides (model, temperature, etc.) will take precedence during merge const categoryConfig = prometheusOverride?.category ? resolveCategoryConfig( prometheusOverride.category, pluginConfig.categories ) : undefined; const prometheusBase = { model: prometheusOverride?.model ?? categoryConfig?.model ?? defaultModel ?? "anthropic/claude-opus-4-5", mode: "primary" as const, prompt: PROMETHEUS_SYSTEM_PROMPT, permission: PROMETHEUS_PERMISSION, description: `${configAgent?.plan?.description ?? "Plan agent"} (Prometheus - OhMyOpenCode)`, color: (configAgent?.plan?.color as string) ?? "#FF6347", // Apply category properties (temperature, top_p, tools, etc.) ...(categoryConfig?.temperature !== undefined ? { temperature: categoryConfig.temperature } : {}), ...(categoryConfig?.top_p !== undefined ? { top_p: categoryConfig.top_p } : {}), ...(categoryConfig?.maxTokens !== undefined ? { maxTokens: categoryConfig.maxTokens } : {}), ...(categoryConfig?.tools ? { tools: categoryConfig.tools } : {}), ...(categoryConfig?.thinking ? { thinking: categoryConfig.thinking } : {}), ...(categoryConfig?.reasoningEffort !== undefined ? { reasoningEffort: categoryConfig.reasoningEffort } : {}), ...(categoryConfig?.textVerbosity !== undefined ? { textVerbosity: categoryConfig.textVerbosity } : {}), }; agentConfig["Prometheus (Planner)"] = prometheusOverride ? { ...prometheusBase, ...prometheusOverride } : prometheusBase; } const filteredConfigAgents = configAgent ? Object.fromEntries( Object.entries(configAgent) .filter(([key]) => { if (key === "build") return false; if (key === "plan" && replacePlan) return false; return true; }) .map(([key, value]) => [ key, value ? migrateAgentConfig(value as Record) : value, ]) ) : {}; const migratedBuild = configAgent?.build ? migrateAgentConfig(configAgent.build as Record) : {}; const planDemoteConfig = replacePlan ? { mode: "subagent" as const, hidden: true } : undefined; config.agent = { ...agentConfig, ...Object.fromEntries( Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus") ), ...userAgents, ...projectAgents, ...pluginAgents, ...filteredConfigAgents, build: { ...migratedBuild, mode: "subagent", hidden: true }, ...(planDemoteConfig ? { plan: planDemoteConfig } : {}), }; } else { config.agent = { ...builtinAgents, ...userAgents, ...projectAgents, ...pluginAgents, ...configAgent, }; } const agentResult = config.agent as AgentConfig; config.tools = { ...(config.tools as Record), "grep_app_*": false, call_omo_agent: false, }; if (agentResult.explore) { agentResult.explore.tools = { ...agentResult.explore.tools, call_omo_agent: false, }; } if (agentResult.librarian) { agentResult.librarian.tools = { ...agentResult.librarian.tools, call_omo_agent: false, "grep_app_*": true, }; } if (agentResult["multimodal-looker"]) { agentResult["multimodal-looker"].tools = { ...agentResult["multimodal-looker"].tools, task: false, call_omo_agent: false, look_at: false, }; } if (agentResult["orchestrator-sisyphus"]) { agentResult["orchestrator-sisyphus"].tools = { ...agentResult["orchestrator-sisyphus"].tools, task: false, call_omo_agent: false, }; } config.permission = { ...(config.permission as Record), webfetch: "allow", external_directory: "allow", }; const mcpResult = (pluginConfig.claude_code?.mcp ?? true) ? await loadMcpConfigs() : { servers: {} }; config.mcp = { ...(config.mcp as Record), ...createBuiltinMcps(pluginConfig.disabled_mcps), ...mcpResult.servers, ...pluginComponents.mcpServers, }; const builtinCommands = loadBuiltinCommands(pluginConfig.disabled_commands); const systemCommands = (config.command as Record) ?? {}; // Parallel loading of all commands and skills for faster startup const includeClaudeCommands = pluginConfig.claude_code?.commands ?? true; const includeClaudeSkills = pluginConfig.claude_code?.skills ?? true; const [ userCommands, projectCommands, opencodeGlobalCommands, opencodeProjectCommands, userSkills, projectSkills, opencodeGlobalSkills, opencodeProjectSkills, ] = await Promise.all([ includeClaudeCommands ? loadUserCommands() : Promise.resolve({}), includeClaudeCommands ? loadProjectCommands() : Promise.resolve({}), loadOpencodeGlobalCommands(), loadOpencodeProjectCommands(), includeClaudeSkills ? loadUserSkills() : Promise.resolve({}), includeClaudeSkills ? loadProjectSkills() : Promise.resolve({}), loadOpencodeGlobalSkills(), loadOpencodeProjectSkills(), ]); config.command = { ...builtinCommands, ...userCommands, ...userSkills, ...opencodeGlobalCommands, ...opencodeGlobalSkills, ...systemCommands, ...projectCommands, ...projectSkills, ...opencodeProjectCommands, ...opencodeProjectSkills, ...pluginComponents.commands, ...pluginComponents.skills, }; }; }