feat: make systemDefaultModel optional for OpenCode fallback (#1136)
- Remove mandatory model requirement from plugin initialization - Allow OpenCode to use its built-in model fallback when user doesn't specify - Update model-resolver to handle undefined systemDefaultModel - Remove throw errors in config-handler, utils, atlas, delegate-task - Add tests for optional model scenarios Closes #1129 Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
This commit is contained in:
parent
c9b86b7815
commit
3ee519c7b0
28
bun.lock
28
bun.lock
@ -27,13 +27,13 @@
|
|||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"oh-my-opencode-darwin-arm64": "3.0.1",
|
"oh-my-opencode-darwin-arm64": "3.1.0",
|
||||||
"oh-my-opencode-darwin-x64": "3.0.1",
|
"oh-my-opencode-darwin-x64": "3.1.0",
|
||||||
"oh-my-opencode-linux-arm64": "3.0.1",
|
"oh-my-opencode-linux-arm64": "3.1.0",
|
||||||
"oh-my-opencode-linux-arm64-musl": "3.0.1",
|
"oh-my-opencode-linux-arm64-musl": "3.1.0",
|
||||||
"oh-my-opencode-linux-x64": "3.0.1",
|
"oh-my-opencode-linux-x64": "3.1.0",
|
||||||
"oh-my-opencode-linux-x64-musl": "3.0.1",
|
"oh-my-opencode-linux-x64-musl": "3.1.0",
|
||||||
"oh-my-opencode-windows-x64": "3.0.1",
|
"oh-my-opencode-windows-x64": "3.1.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -225,19 +225,19 @@
|
|||||||
|
|
||||||
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||||
|
|
||||||
"oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.0.1", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-LRcLVi6DsmGh3ICFeN4yVJ0KinvCM5jotd2z7tZQ74n0sziHO7grjK1CmJaPV9eCv0clatoK5xfFCeEJ3FvXYg=="],
|
"oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.1.0", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-8j7XI+n1bz7xIg35Zpjqp1AqoIoFWuVZdYyI9vTAZ0b6ta/mIlNOWPLAbFyEHfKelA9g3Xa+4sYnKPSxU5dQoA=="],
|
||||||
|
|
||||||
"oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.0.1", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-ZaC0ZBe5M2f2aMncNsAMu9IZ3MjSPfNVcfUTCgJkp03db8lLPsajgjeG3556Er72hxignDPsEbrLkJBNlsDbAA=="],
|
"oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.1.0", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-Kd/3KpnF07cw+qBAyLwA0y8tp3S0X8b8HWH55WGlVp6m4gvQ432kKgDum/jat1vqP/3J8hm4P/sly5ibY5gMqw=="],
|
||||||
|
|
||||||
"oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.0.1", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-pcOvV6Y2GSwKr0exDndeB2BtFt297XhJFQgrq1cbeEJawoRONDRp7LNSpjwILSQpQ7YkkYnO2bIczBmxI5llNA=="],
|
"oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.1.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-qy/QohHGM6eSQjHVEgibsDauUvlAgYPw5xrQqa9cVLo1hL4KMIhb+i4wGAxCK2p84rG2bfC2m8+IfZUxhhwcTg=="],
|
||||||
|
|
||||||
"oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.0.1", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-7kXKaVbgFnOMSaw+j4JbZNs7O7mkvCekcfWPwh/9I/0WD21/n4PbAGl01ePhRoQh+u9MC6t8FH046hEjL2sk1g=="],
|
"oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.1.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-HIO7zj3M5QAYOfgvFM7Djeuen9kdZD4RA51wzXcXiPj1FPAuBNAW9N7lTEGYBSgObgwX+vXnC3HwLSF7nqkw8w=="],
|
||||||
|
|
||||||
"oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.0.1", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-1BOV1EnKa5BErhZmWiddnbriHwm1KFrPr+0BUCDdFX/d/hrMAJTo1733zaEnvKuXzvrdHSp/VznXheeUI1VjkA=="],
|
"oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.1.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-zcKaibnEhvbReiTsqbg+dog/Z3pnBx4v6R3AR5nVhGBO27hRSAXgA/fviYyE5bWD591WB7Pqwduf0t854ilKjw=="],
|
||||||
|
|
||||||
"oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.0.1", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-ASyTVatvU1nNJ0mk9o+A/GjybT5vOdgU172ystzCsnQ+12Mnv68GgaeMu/UFJgJNaZmKdhyUAP9XhnOKvEDBGQ=="],
|
"oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.1.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-xmtHEyAhY93Djg5qEauvMqSF0x3tf8pzOGdKB6CuZmhCG69fZXk/dEwPrO0vKbOeGMV/T4K6HAg1+8Ue1N1ZaQ=="],
|
||||||
|
|
||||||
"oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.0.1", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-QIuA564mVpwzCprhhAoyd8TSw0Rt2VM6M9y7H0fOoC/UjXuU+d7wIuUNuqUUMVaUnMedkctTZop0X0i2Q+Bvhg=="],
|
"oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.1.0", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-pDgHd0mGWWVsiO0fT8C7bi6CziOXU38g+k2dWlGm1YXCMzyrrWZZCF7oIp+EzJB02saSCF/oJ2f1/uj/VPeLMA=="],
|
||||||
|
|
||||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||||
|
|
||||||
|
|||||||
@ -523,9 +523,6 @@ function buildDynamicOrchestratorPrompt(ctx?: OrchestratorContext): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
|
export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
|
||||||
if (!ctx.model) {
|
|
||||||
throw new Error("createAtlasAgent requires a model in context")
|
|
||||||
}
|
|
||||||
const restrictions = createAgentToolRestrictions([
|
const restrictions = createAgentToolRestrictions([
|
||||||
"task",
|
"task",
|
||||||
"call_omo_agent",
|
"call_omo_agent",
|
||||||
@ -534,7 +531,7 @@ export function createAtlasAgent(ctx: OrchestratorContext): AgentConfig {
|
|||||||
description:
|
description:
|
||||||
"Orchestrates work via delegate_task() to complete ALL tasks in a todo list until fully done",
|
"Orchestrates work via delegate_task() to complete ALL tasks in a todo list until fully done",
|
||||||
mode: "primary" as const,
|
mode: "primary" as const,
|
||||||
model: ctx.model,
|
...(ctx.model ? { model: ctx.model } : {}),
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
prompt: buildDynamicOrchestratorPrompt(ctx),
|
prompt: buildDynamicOrchestratorPrompt(ctx),
|
||||||
thinking: { type: "enabled", budgetTokens: 32000 },
|
thinking: { type: "enabled", budgetTokens: 32000 },
|
||||||
|
|||||||
@ -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", () => {
|
describe("buildAgent with category and skills", () => {
|
||||||
const { buildAgent } = require("./utils")
|
const { buildAgent } = require("./utils")
|
||||||
const TEST_MODEL = "anthropic/claude-opus-4-5"
|
const TEST_MODEL = "anthropic/claude-opus-4-5"
|
||||||
|
|||||||
@ -151,10 +151,6 @@ export async function createBuiltinAgents(
|
|||||||
client?: any,
|
client?: any,
|
||||||
browserProvider?: BrowserAutomationProvider
|
browserProvider?: BrowserAutomationProvider
|
||||||
): Promise<Record<string, AgentConfig>> {
|
): Promise<Record<string, AgentConfig>> {
|
||||||
if (!systemDefaultModel) {
|
|
||||||
throw new Error("createBuiltinAgents requires systemDefaultModel")
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectedProviders = readConnectedProvidersCache()
|
const connectedProviders = readConnectedProvidersCache()
|
||||||
const availableModels = client
|
const availableModels = client
|
||||||
? await fetchAvailableModels(client, { connectedProviders: connectedProviders ?? undefined })
|
? await fetchAvailableModels(client, { connectedProviders: connectedProviders ?? undefined })
|
||||||
@ -201,13 +197,14 @@ export async function createBuiltinAgents(
|
|||||||
const override = findCaseInsensitive(agentOverrides, agentName)
|
const override = findCaseInsensitive(agentOverrides, agentName)
|
||||||
const requirement = AGENT_MODEL_REQUIREMENTS[agentName]
|
const requirement = AGENT_MODEL_REQUIREMENTS[agentName]
|
||||||
|
|
||||||
// Use resolver to determine model
|
const resolution = resolveModelWithFallback({
|
||||||
const { model, variant: resolvedVariant } = resolveModelWithFallback({
|
|
||||||
userModel: override?.model,
|
userModel: override?.model,
|
||||||
fallbackChain: requirement?.fallbackChain,
|
fallbackChain: requirement?.fallbackChain,
|
||||||
availableModels,
|
availableModels,
|
||||||
systemDefaultModel,
|
systemDefaultModel,
|
||||||
})
|
})
|
||||||
|
if (!resolution) continue
|
||||||
|
const { model, variant: resolvedVariant } = resolution
|
||||||
|
|
||||||
let config = buildAgent(source, model, mergedCategories, gitMasterConfig, browserProvider)
|
let config = buildAgent(source, model, mergedCategories, gitMasterConfig, browserProvider)
|
||||||
|
|
||||||
@ -243,72 +240,76 @@ export async function createBuiltinAgents(
|
|||||||
const sisyphusOverride = agentOverrides["sisyphus"]
|
const sisyphusOverride = agentOverrides["sisyphus"]
|
||||||
const sisyphusRequirement = AGENT_MODEL_REQUIREMENTS["sisyphus"]
|
const sisyphusRequirement = AGENT_MODEL_REQUIREMENTS["sisyphus"]
|
||||||
|
|
||||||
// Use resolver to determine model
|
const sisyphusResolution = resolveModelWithFallback({
|
||||||
const { model: sisyphusModel, variant: sisyphusResolvedVariant } = resolveModelWithFallback({
|
|
||||||
userModel: sisyphusOverride?.model,
|
userModel: sisyphusOverride?.model,
|
||||||
fallbackChain: sisyphusRequirement?.fallbackChain,
|
fallbackChain: sisyphusRequirement?.fallbackChain,
|
||||||
availableModels,
|
availableModels,
|
||||||
systemDefaultModel,
|
systemDefaultModel,
|
||||||
})
|
})
|
||||||
|
|
||||||
let sisyphusConfig = createSisyphusAgent(
|
if (sisyphusResolution) {
|
||||||
sisyphusModel,
|
const { model: sisyphusModel, variant: sisyphusResolvedVariant } = sisyphusResolution
|
||||||
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 (directory && sisyphusConfig.prompt) {
|
let sisyphusConfig = createSisyphusAgent(
|
||||||
const envContext = createEnvContext()
|
sisyphusModel,
|
||||||
sisyphusConfig = { ...sisyphusConfig, prompt: sisyphusConfig.prompt + envContext }
|
availableAgents,
|
||||||
}
|
undefined,
|
||||||
|
availableSkills,
|
||||||
|
availableCategories
|
||||||
|
)
|
||||||
|
|
||||||
|
if (sisyphusOverride?.variant) {
|
||||||
|
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusOverride.variant }
|
||||||
|
} else if (sisyphusResolvedVariant) {
|
||||||
|
sisyphusConfig = { ...sisyphusConfig, variant: sisyphusResolvedVariant }
|
||||||
|
}
|
||||||
|
|
||||||
if (sisyphusOverride) {
|
if (directory && sisyphusConfig.prompt) {
|
||||||
sisyphusConfig = mergeAgentConfig(sisyphusConfig, sisyphusOverride)
|
const envContext = createEnvContext()
|
||||||
}
|
sisyphusConfig = { ...sisyphusConfig, prompt: sisyphusConfig.prompt + envContext }
|
||||||
|
}
|
||||||
|
|
||||||
result["sisyphus"] = sisyphusConfig
|
if (sisyphusOverride) {
|
||||||
|
sisyphusConfig = mergeAgentConfig(sisyphusConfig, sisyphusOverride)
|
||||||
|
}
|
||||||
|
|
||||||
|
result["sisyphus"] = sisyphusConfig
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disabledAgents.includes("atlas")) {
|
if (!disabledAgents.includes("atlas")) {
|
||||||
const orchestratorOverride = agentOverrides["atlas"]
|
const orchestratorOverride = agentOverrides["atlas"]
|
||||||
const atlasRequirement = AGENT_MODEL_REQUIREMENTS["atlas"]
|
const atlasRequirement = AGENT_MODEL_REQUIREMENTS["atlas"]
|
||||||
|
|
||||||
// Use resolver to determine model
|
const atlasResolution = resolveModelWithFallback({
|
||||||
const { model: atlasModel, variant: atlasResolvedVariant } = resolveModelWithFallback({
|
|
||||||
userModel: orchestratorOverride?.model,
|
userModel: orchestratorOverride?.model,
|
||||||
fallbackChain: atlasRequirement?.fallbackChain,
|
fallbackChain: atlasRequirement?.fallbackChain,
|
||||||
availableModels,
|
availableModels,
|
||||||
systemDefaultModel,
|
systemDefaultModel,
|
||||||
})
|
})
|
||||||
|
|
||||||
let orchestratorConfig = createAtlasAgent({
|
if (atlasResolution) {
|
||||||
model: atlasModel,
|
const { model: atlasModel, variant: atlasResolvedVariant } = atlasResolution
|
||||||
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 (orchestratorOverride) {
|
let orchestratorConfig = createAtlasAgent({
|
||||||
orchestratorConfig = mergeAgentConfig(orchestratorConfig, orchestratorOverride)
|
model: atlasModel,
|
||||||
}
|
availableAgents,
|
||||||
|
availableSkills,
|
||||||
|
userCategories: categories,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (orchestratorOverride?.variant) {
|
||||||
|
orchestratorConfig = { ...orchestratorConfig, variant: orchestratorOverride.variant }
|
||||||
|
} else if (atlasResolvedVariant) {
|
||||||
|
orchestratorConfig = { ...orchestratorConfig, variant: atlasResolvedVariant }
|
||||||
|
}
|
||||||
|
|
||||||
result["atlas"] = orchestratorConfig
|
if (orchestratorOverride) {
|
||||||
|
orchestratorConfig = mergeAgentConfig(orchestratorConfig, orchestratorOverride)
|
||||||
|
}
|
||||||
|
|
||||||
|
result["atlas"] = orchestratorConfig
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@ -105,41 +105,6 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
|
|||||||
log(`Plugin load errors`, { errors: pluginComponents.errors });
|
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
|
// Migrate disabled_agents from old names to new names
|
||||||
const migratedDisabledAgents = (pluginConfig.disabled_agents ?? []).map(agent => {
|
const migratedDisabledAgents = (pluginConfig.disabled_agents ?? []).map(agent => {
|
||||||
return AGENT_NAME_MAP[agent.toLowerCase()] ?? AGENT_NAME_MAP[agent] ?? agent
|
return AGENT_NAME_MAP[agent.toLowerCase()] ?? AGENT_NAME_MAP[agent] ?? agent
|
||||||
|
|||||||
@ -128,8 +128,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||||
expect(result.source).toBe("override")
|
expect(result!.source).toBe("override")
|
||||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via override", { model: "anthropic/claude-opus-4-5" })
|
expect(logSpy).toHaveBeenCalledWith("Model resolved via override", { model: "anthropic/claude-opus-4-5" })
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -148,8 +148,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("custom/my-model")
|
expect(result!.model).toBe("custom/my-model")
|
||||||
expect(result.source).toBe("override")
|
expect(result!.source).toBe("override")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("whitespace-only userModel is treated as not provided", () => {
|
test("whitespace-only userModel is treated as not provided", () => {
|
||||||
@ -167,7 +167,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.source).not.toBe("override")
|
expect(result!.source).not.toBe("override")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("empty string userModel is treated as not provided", () => {
|
test("empty string userModel is treated as not provided", () => {
|
||||||
@ -185,7 +185,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.source).not.toBe("override")
|
expect(result!.source).not.toBe("override")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -204,8 +204,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("github-copilot/claude-opus-4-5-preview")
|
expect(result!.model).toBe("github-copilot/claude-opus-4-5-preview")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
expect(logSpy).toHaveBeenCalledWith("Model resolved via fallback chain (availability confirmed)", {
|
expect(logSpy).toHaveBeenCalledWith("Model resolved via fallback chain (availability confirmed)", {
|
||||||
provider: "github-copilot",
|
provider: "github-copilot",
|
||||||
model: "claude-opus-4-5",
|
model: "claude-opus-4-5",
|
||||||
@ -228,8 +228,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("openai/gpt-5.2")
|
expect(result!.model).toBe("openai/gpt-5.2")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("tries next provider when first provider has no match", () => {
|
test("tries next provider when first provider has no match", () => {
|
||||||
@ -246,8 +246,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("opencode/gpt-5-nano")
|
expect(result!.model).toBe("opencode/gpt-5-nano")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("uses fuzzy matching within provider", () => {
|
test("uses fuzzy matching within provider", () => {
|
||||||
@ -264,8 +264,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("skips fallback chain when not provided", () => {
|
test("skips fallback chain when not provided", () => {
|
||||||
@ -279,7 +279,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.source).toBe("system-default")
|
expect(result!.source).toBe("system-default")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("skips fallback chain when empty", () => {
|
test("skips fallback chain when empty", () => {
|
||||||
@ -294,7 +294,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.source).toBe("system-default")
|
expect(result!.source).toBe("system-default")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("case-insensitive fuzzy matching", () => {
|
test("case-insensitive fuzzy matching", () => {
|
||||||
@ -311,8 +311,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -331,8 +331,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("google/gemini-3-pro")
|
expect(result!.model).toBe("google/gemini-3-pro")
|
||||||
expect(result.source).toBe("system-default")
|
expect(result!.source).toBe("system-default")
|
||||||
expect(logSpy).toHaveBeenCalledWith("No available model found in fallback chain, falling through to system default")
|
expect(logSpy).toHaveBeenCalledWith("No available model found in fallback chain, falling through to system default")
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -350,8 +350,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then - should use first fallback entry, not system default
|
// #then - should use first fallback entry, not system default
|
||||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns system default when fallbackChain is not provided", () => {
|
test("returns system default when fallbackChain is not provided", () => {
|
||||||
@ -365,8 +365,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("google/gemini-3-pro")
|
expect(result!.model).toBe("google/gemini-3-pro")
|
||||||
expect(result.source).toBe("system-default")
|
expect(result!.source).toBe("system-default")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -386,8 +386,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("anthropic/claude-opus-4-5")
|
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("tries all providers in first entry before moving to second entry", () => {
|
test("tries all providers in first entry before moving to second entry", () => {
|
||||||
@ -405,8 +405,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("google/gemini-3-pro")
|
expect(result!.model).toBe("google/gemini-3-pro")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("returns first matching entry even if later entries have better matches", () => {
|
test("returns first matching entry even if later entries have better matches", () => {
|
||||||
@ -427,8 +427,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("openai/gpt-5.2")
|
expect(result!.model).toBe("openai/gpt-5.2")
|
||||||
expect(result.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("falls through to system default when none match availability", () => {
|
test("falls through to system default when none match availability", () => {
|
||||||
@ -447,8 +447,8 @@ describe("resolveModelWithFallback", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(result.model).toBe("system/default")
|
expect(result!.model).toBe("system/default")
|
||||||
expect(result.source).toBe("system-default")
|
expect(result!.source).toBe("system-default")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -462,11 +462,81 @@ describe("resolveModelWithFallback", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// #when
|
// #when
|
||||||
const result: ModelResolutionResult = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(typeof result.model).toBe("string")
|
expect(result).toBeDefined()
|
||||||
expect(["override", "provider-fallback", "system-default"]).toContain(result.source)
|
expect(typeof result!.model).toBe("string")
|
||||||
|
expect(["override", "provider-fallback", "system-default"]).toContain(result!.source)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Optional systemDefaultModel", () => {
|
||||||
|
test("returns undefined when systemDefaultModel is undefined and no fallback found", () => {
|
||||||
|
// #given
|
||||||
|
const input: ExtendedModelResolutionInput = {
|
||||||
|
fallbackChain: [
|
||||||
|
{ providers: ["anthropic"], model: "nonexistent-model" },
|
||||||
|
],
|
||||||
|
availableModels: new Set(["openai/gpt-5.2"]),
|
||||||
|
systemDefaultModel: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns undefined when no fallbackChain and systemDefaultModel is undefined", () => {
|
||||||
|
// #given
|
||||||
|
const input: ExtendedModelResolutionInput = {
|
||||||
|
availableModels: new Set(["openai/gpt-5.2"]),
|
||||||
|
systemDefaultModel: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("still returns override when userModel provided even if systemDefaultModel undefined", () => {
|
||||||
|
// #given
|
||||||
|
const input: ExtendedModelResolutionInput = {
|
||||||
|
userModel: "anthropic/claude-opus-4-5",
|
||||||
|
availableModels: new Set(),
|
||||||
|
systemDefaultModel: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBeDefined()
|
||||||
|
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||||
|
expect(result!.source).toBe("override")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("still returns fallback match when systemDefaultModel undefined", () => {
|
||||||
|
// #given
|
||||||
|
const input: ExtendedModelResolutionInput = {
|
||||||
|
fallbackChain: [
|
||||||
|
{ providers: ["anthropic"], model: "claude-opus-4-5" },
|
||||||
|
],
|
||||||
|
availableModels: new Set(["anthropic/claude-opus-4-5"]),
|
||||||
|
systemDefaultModel: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// #when
|
||||||
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
|
// #then
|
||||||
|
expect(result).toBeDefined()
|
||||||
|
expect(result!.model).toBe("anthropic/claude-opus-4-5")
|
||||||
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { readConnectedProvidersCache } from "./connected-providers-cache"
|
|||||||
export type ModelResolutionInput = {
|
export type ModelResolutionInput = {
|
||||||
userModel?: string
|
userModel?: string
|
||||||
inheritedModel?: string
|
inheritedModel?: string
|
||||||
systemDefault: string
|
systemDefault?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelSource =
|
export type ModelSource =
|
||||||
@ -24,7 +24,7 @@ export type ExtendedModelResolutionInput = {
|
|||||||
userModel?: string
|
userModel?: string
|
||||||
fallbackChain?: FallbackEntry[]
|
fallbackChain?: FallbackEntry[]
|
||||||
availableModels: Set<string>
|
availableModels: Set<string>
|
||||||
systemDefaultModel: string
|
systemDefaultModel?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeModel(model?: string): string | undefined {
|
function normalizeModel(model?: string): string | undefined {
|
||||||
@ -32,7 +32,7 @@ function normalizeModel(model?: string): string | undefined {
|
|||||||
return trimmed || undefined
|
return trimmed || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveModel(input: ModelResolutionInput): string {
|
export function resolveModel(input: ModelResolutionInput): string | undefined {
|
||||||
return (
|
return (
|
||||||
normalizeModel(input.userModel) ??
|
normalizeModel(input.userModel) ??
|
||||||
normalizeModel(input.inheritedModel) ??
|
normalizeModel(input.inheritedModel) ??
|
||||||
@ -42,7 +42,7 @@ export function resolveModel(input: ModelResolutionInput): string {
|
|||||||
|
|
||||||
export function resolveModelWithFallback(
|
export function resolveModelWithFallback(
|
||||||
input: ExtendedModelResolutionInput,
|
input: ExtendedModelResolutionInput,
|
||||||
): ModelResolutionResult {
|
): ModelResolutionResult | undefined {
|
||||||
const { userModel, fallbackChain, availableModels, systemDefaultModel } = input
|
const { userModel, fallbackChain, availableModels, systemDefaultModel } = input
|
||||||
|
|
||||||
// Step 1: Override
|
// Step 1: Override
|
||||||
@ -92,7 +92,12 @@ export function resolveModelWithFallback(
|
|||||||
log("No available model found in fallback chain, falling through to system default")
|
log("No available model found in fallback chain, falling through to system default")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: System default
|
// Step 3: System default (if provided)
|
||||||
|
if (systemDefaultModel === undefined) {
|
||||||
|
log("No model resolved - systemDefaultModel not configured")
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
log("Model resolved via system default", { model: systemDefaultModel })
|
log("Model resolved via system default", { model: systemDefaultModel })
|
||||||
return { model: systemDefaultModel, source: "system-default" }
|
return { model: systemDefaultModel, source: "system-default" }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,11 +78,11 @@ describe("sisyphus-task", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("category delegation config validation", () => {
|
describe("category delegation config validation", () => {
|
||||||
test("returns error when systemDefaultModel is not configured", async () => {
|
test("proceeds without error when systemDefaultModel is undefined", async () => {
|
||||||
// #given a mock client with no model in config
|
// #given a mock client with no model in config
|
||||||
const { createDelegateTask } = require("./tools")
|
const { createDelegateTask } = require("./tools")
|
||||||
|
|
||||||
const mockManager = { launch: async () => ({}) }
|
const mockManager = { launch: async () => ({ id: "task-123" }) }
|
||||||
const mockClient = {
|
const mockClient = {
|
||||||
app: { agents: async () => ({ data: [] }) },
|
app: { agents: async () => ({ data: [] }) },
|
||||||
config: { get: async () => ({}) }, // No model configured
|
config: { get: async () => ({}) }, // No model configured
|
||||||
@ -111,14 +111,14 @@ describe("sisyphus-task", () => {
|
|||||||
description: "Test task",
|
description: "Test task",
|
||||||
prompt: "Do something",
|
prompt: "Do something",
|
||||||
category: "ultrabrain",
|
category: "ultrabrain",
|
||||||
run_in_background: false,
|
run_in_background: true,
|
||||||
load_skills: ["git-master"],
|
load_skills: [],
|
||||||
},
|
},
|
||||||
toolContext
|
toolContext
|
||||||
)
|
)
|
||||||
|
|
||||||
// #then returns descriptive error message
|
// #then proceeds without error - uses fallback chain
|
||||||
expect(result).toContain("oh-my-opencode requires a default model")
|
expect(result).not.toContain("oh-my-opencode requires a default model")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -115,9 +115,9 @@ export function resolveCategoryConfig(
|
|||||||
options: {
|
options: {
|
||||||
userCategories?: CategoriesConfig
|
userCategories?: CategoriesConfig
|
||||||
inheritedModel?: string
|
inheritedModel?: string
|
||||||
systemDefaultModel: string
|
systemDefaultModel?: string
|
||||||
}
|
}
|
||||||
): { config: CategoryConfig; promptAppend: string; model: string } | null {
|
): { config: CategoryConfig; promptAppend: string; model: string | undefined } | null {
|
||||||
const { userCategories, inheritedModel, systemDefaultModel } = options
|
const { userCategories, inheritedModel, systemDefaultModel } = options
|
||||||
const defaultConfig = DEFAULT_CATEGORIES[categoryName]
|
const defaultConfig = DEFAULT_CATEGORIES[categoryName]
|
||||||
const userConfig = userCategories?.[categoryName]
|
const userConfig = userCategories?.[categoryName]
|
||||||
@ -497,17 +497,6 @@ To continue this session: session_id="${args.session_id}"`
|
|||||||
let modelInfo: ModelFallbackInfo | undefined
|
let modelInfo: ModelFallbackInfo | undefined
|
||||||
|
|
||||||
if (args.category) {
|
if (args.category) {
|
||||||
// Guard: require system default model for category delegation
|
|
||||||
if (!systemDefaultModel) {
|
|
||||||
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null })
|
|
||||||
return (
|
|
||||||
'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)'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectedProviders = readConnectedProvidersCache()
|
const connectedProviders = readConnectedProvidersCache()
|
||||||
const availableModels = await fetchAvailableModels(client, {
|
const availableModels = await fetchAvailableModels(client, {
|
||||||
connectedProviders: connectedProviders ?? undefined
|
connectedProviders: connectedProviders ?? undefined
|
||||||
@ -523,55 +512,60 @@ To continue this session: session_id="${args.session_id}"`
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requirement = CATEGORY_MODEL_REQUIREMENTS[args.category]
|
const requirement = CATEGORY_MODEL_REQUIREMENTS[args.category]
|
||||||
let actualModel: string
|
let actualModel: string | undefined
|
||||||
|
|
||||||
if (!requirement) {
|
if (!requirement) {
|
||||||
actualModel = resolved.model
|
actualModel = resolved.model
|
||||||
modelInfo = { model: actualModel, type: "system-default", source: "system-default" }
|
if (actualModel) {
|
||||||
|
modelInfo = { model: actualModel, type: "system-default", source: "system-default" }
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const { model: resolvedModel, source, variant: resolvedVariant } = resolveModelWithFallback({
|
const resolution = resolveModelWithFallback({
|
||||||
userModel: userCategories?.[args.category]?.model ?? sisyphusJuniorModel,
|
userModel: userCategories?.[args.category]?.model ?? sisyphusJuniorModel,
|
||||||
fallbackChain: requirement.fallbackChain,
|
fallbackChain: requirement.fallbackChain,
|
||||||
availableModels,
|
availableModels,
|
||||||
systemDefaultModel,
|
systemDefaultModel,
|
||||||
})
|
})
|
||||||
|
|
||||||
actualModel = resolvedModel
|
if (resolution) {
|
||||||
|
const { model: resolvedModel, source, variant: resolvedVariant } = resolution
|
||||||
|
actualModel = resolvedModel
|
||||||
|
|
||||||
if (!parseModelString(actualModel)) {
|
if (!parseModelString(actualModel)) {
|
||||||
return `Invalid model format "${actualModel}". Expected "provider/model" format (e.g., "anthropic/claude-sonnet-4-5").`
|
return `Invalid model format "${actualModel}". Expected "provider/model" format (e.g., "anthropic/claude-sonnet-4-5").`
|
||||||
|
}
|
||||||
|
|
||||||
|
let type: "user-defined" | "inherited" | "category-default" | "system-default"
|
||||||
|
switch (source) {
|
||||||
|
case "override":
|
||||||
|
type = "user-defined"
|
||||||
|
break
|
||||||
|
case "provider-fallback":
|
||||||
|
type = "category-default"
|
||||||
|
break
|
||||||
|
case "system-default":
|
||||||
|
type = "system-default"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
modelInfo = { model: actualModel, type, source }
|
||||||
|
|
||||||
|
const parsedModel = parseModelString(actualModel)
|
||||||
|
const variantToUse = userCategories?.[args.category]?.variant ?? resolvedVariant
|
||||||
|
categoryModel = parsedModel
|
||||||
|
? (variantToUse ? { ...parsedModel, variant: variantToUse } : parsedModel)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
let type: "user-defined" | "inherited" | "category-default" | "system-default"
|
|
||||||
switch (source) {
|
|
||||||
case "override":
|
|
||||||
type = "user-defined"
|
|
||||||
break
|
|
||||||
case "provider-fallback":
|
|
||||||
type = "category-default"
|
|
||||||
break
|
|
||||||
case "system-default":
|
|
||||||
type = "system-default"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
modelInfo = { model: actualModel, type, source }
|
|
||||||
|
|
||||||
const parsedModel = parseModelString(actualModel)
|
|
||||||
const variantToUse = userCategories?.[args.category]?.variant ?? resolvedVariant
|
|
||||||
categoryModel = parsedModel
|
|
||||||
? (variantToUse ? { ...parsedModel, variant: variantToUse } : parsedModel)
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
|
|
||||||
agentToUse = SISYPHUS_JUNIOR_AGENT
|
agentToUse = SISYPHUS_JUNIOR_AGENT
|
||||||
if (!categoryModel) {
|
if (!categoryModel && actualModel) {
|
||||||
const parsedModel = parseModelString(actualModel)
|
const parsedModel = parseModelString(actualModel)
|
||||||
categoryModel = parsedModel ?? undefined
|
categoryModel = parsedModel ?? undefined
|
||||||
}
|
}
|
||||||
categoryPromptAppend = resolved.promptAppend || undefined
|
categoryPromptAppend = resolved.promptAppend || undefined
|
||||||
|
|
||||||
const isUnstableAgent = resolved.config.is_unstable_agent === true || actualModel.toLowerCase().includes("gemini")
|
const isUnstableAgent = resolved.config.is_unstable_agent === true || (actualModel?.toLowerCase().includes("gemini") ?? false)
|
||||||
// Handle both boolean false and string "false" due to potential serialization
|
// Handle both boolean false and string "false" due to potential serialization
|
||||||
const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false" as unknown as boolean
|
const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false" as unknown as boolean
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user