fix(cli): preserve installer fallback chains in generated config (#3144)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -98,6 +98,41 @@ describe("generateOmoConfig - model fallback system", () => {
|
|||||||
expect((result.agents as Record<string, { model: string }>)['multimodal-looker'].model).toBe("openai/gpt-5.4")
|
expect((result.agents as Record<string, { model: string }>)['multimodal-looker'].model).toBe("openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("adds fallback_models when multiple providers are available", () => {
|
||||||
|
//#given
|
||||||
|
const config: InstallConfig = {
|
||||||
|
hasClaude: true,
|
||||||
|
isMax20: false,
|
||||||
|
hasOpenAI: true,
|
||||||
|
hasGemini: false,
|
||||||
|
hasCopilot: false,
|
||||||
|
hasOpencodeZen: false,
|
||||||
|
hasZaiCodingPlan: false,
|
||||||
|
hasKimiForCoding: false,
|
||||||
|
hasOpencodeGo: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
//#when
|
||||||
|
const result = generateOmoConfig(config)
|
||||||
|
const agents = result.agents as Record<string, {
|
||||||
|
model: string
|
||||||
|
variant?: string
|
||||||
|
fallback_models?: Array<{ model: string; variant?: string }>
|
||||||
|
}>
|
||||||
|
const categories = result.categories as Record<string, {
|
||||||
|
model: string
|
||||||
|
variant?: string
|
||||||
|
fallback_models?: Array<{ model: string; variant?: string }>
|
||||||
|
}>
|
||||||
|
|
||||||
|
//#then
|
||||||
|
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6")
|
||||||
|
expect(agents.sisyphus.fallback_models?.map((entry) => entry.model)).toContain("openai/gpt-5.4")
|
||||||
|
expect(agents.sisyphus.fallback_models?.find((entry) => entry.model === "openai/gpt-5.4")?.variant).toBe("medium")
|
||||||
|
expect(categories.deep.model).toBe("openai/gpt-5.4")
|
||||||
|
expect(categories.deep.fallback_models?.map((entry) => entry.model)).toContain("anthropic/claude-opus-4-6")
|
||||||
|
})
|
||||||
|
|
||||||
test("uses haiku for explore when Claude max20", () => {
|
test("uses haiku for explore when Claude max20", () => {
|
||||||
//#given
|
//#given
|
||||||
const config: InstallConfig = {
|
const config: InstallConfig = {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { FallbackModelObject } from "../config/schema/fallback-models"
|
||||||
|
|
||||||
export interface ProviderAvailability {
|
export interface ProviderAvailability {
|
||||||
native: {
|
native: {
|
||||||
claude: boolean
|
claude: boolean
|
||||||
@@ -15,11 +17,13 @@ kimiForCoding: boolean
|
|||||||
export interface AgentConfig {
|
export interface AgentConfig {
|
||||||
model: string
|
model: string
|
||||||
variant?: string
|
variant?: string
|
||||||
|
fallback_models?: FallbackModelObject[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryConfig {
|
export interface CategoryConfig {
|
||||||
model: string
|
model: string
|
||||||
variant?: string
|
variant?: string
|
||||||
|
fallback_models?: FallbackModelObject[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GeneratedOmoConfig {
|
export interface GeneratedOmoConfig {
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import {
|
|||||||
CLI_AGENT_MODEL_REQUIREMENTS,
|
CLI_AGENT_MODEL_REQUIREMENTS,
|
||||||
CLI_CATEGORY_MODEL_REQUIREMENTS,
|
CLI_CATEGORY_MODEL_REQUIREMENTS,
|
||||||
} from "./model-fallback-requirements"
|
} from "./model-fallback-requirements"
|
||||||
|
import type { FallbackModelObject } from "../config/schema/fallback-models"
|
||||||
|
import type { FallbackEntry } from "../shared/model-requirements"
|
||||||
import type { InstallConfig } from "./types"
|
import type { InstallConfig } from "./types"
|
||||||
|
|
||||||
import type { AgentConfig, CategoryConfig, GeneratedOmoConfig } from "./model-fallback-types"
|
import type { AgentConfig, CategoryConfig, GeneratedOmoConfig } from "./model-fallback-types"
|
||||||
import { applyOpenAiOnlyModelCatalog, isOpenAiOnlyAvailability } from "./openai-only-model-catalog"
|
import { applyOpenAiOnlyModelCatalog, isOpenAiOnlyAvailability } from "./openai-only-model-catalog"
|
||||||
import { toProviderAvailability } from "./provider-availability"
|
import { isProviderAvailable, toProviderAvailability } from "./provider-availability"
|
||||||
import {
|
import {
|
||||||
getSisyphusFallbackChain,
|
getSisyphusFallbackChain,
|
||||||
isAnyFallbackEntryAvailable,
|
isAnyFallbackEntryAvailable,
|
||||||
@@ -14,6 +16,7 @@ import {
|
|||||||
isRequiredProviderAvailable,
|
isRequiredProviderAvailable,
|
||||||
resolveModelFromChain,
|
resolveModelFromChain,
|
||||||
} from "./fallback-chain-resolution"
|
} from "./fallback-chain-resolution"
|
||||||
|
import { transformModelForProvider } from "./provider-model-id-transform"
|
||||||
|
|
||||||
export type { GeneratedOmoConfig } from "./model-fallback-types"
|
export type { GeneratedOmoConfig } from "./model-fallback-types"
|
||||||
|
|
||||||
@@ -22,6 +25,44 @@ const ZAI_MODEL = "zai-coding-plan/glm-4.7"
|
|||||||
const ULTIMATE_FALLBACK = "opencode/gpt-5-nano"
|
const ULTIMATE_FALLBACK = "opencode/gpt-5-nano"
|
||||||
const SCHEMA_URL = "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json"
|
const SCHEMA_URL = "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json"
|
||||||
|
|
||||||
|
function toFallbackModelObject(entry: FallbackEntry, provider: string): FallbackModelObject {
|
||||||
|
return {
|
||||||
|
model: `${provider}/${transformModelForProvider(provider, entry.model)}`,
|
||||||
|
...(entry.variant ? { variant: entry.variant } : {}),
|
||||||
|
...(entry.reasoningEffort ? { reasoningEffort: entry.reasoningEffort as FallbackModelObject["reasoningEffort"] } : {}),
|
||||||
|
...(entry.temperature !== undefined ? { temperature: entry.temperature } : {}),
|
||||||
|
...(entry.top_p !== undefined ? { top_p: entry.top_p } : {}),
|
||||||
|
...(entry.maxTokens !== undefined ? { maxTokens: entry.maxTokens } : {}),
|
||||||
|
...(entry.thinking ? { thinking: entry.thinking } : {}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachFallbackModels<T extends AgentConfig | CategoryConfig>(
|
||||||
|
config: T,
|
||||||
|
fallbackChain: FallbackEntry[],
|
||||||
|
availability: ReturnType<typeof toProviderAvailability>,
|
||||||
|
): T {
|
||||||
|
const expandedFallbacks = fallbackChain.flatMap((entry) =>
|
||||||
|
entry.providers
|
||||||
|
.filter((provider) => isProviderAvailable(provider, availability))
|
||||||
|
.map((provider) => toFallbackModelObject(entry, provider))
|
||||||
|
)
|
||||||
|
const primaryIndex = expandedFallbacks.findIndex((entry) => entry.model === config.model)
|
||||||
|
if (primaryIndex === -1) {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackModels = expandedFallbacks.slice(primaryIndex + 1)
|
||||||
|
if (fallbackModels.length === 0) {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
fallback_models: fallbackModels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
||||||
@@ -85,7 +126,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
|||||||
const resolved = resolveModelFromChain(fallbackChain, avail)
|
const resolved = resolveModelFromChain(fallbackChain, avail)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
const variant = resolved.variant ?? req.variant
|
const variant = resolved.variant ?? req.variant
|
||||||
agents[role] = variant ? { model: resolved.model, variant } : { model: resolved.model }
|
const agentConfig = variant ? { model: resolved.model, variant } : { model: resolved.model }
|
||||||
|
agents[role] = attachFallbackModels(agentConfig, fallbackChain, avail)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -100,7 +142,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
|||||||
const resolved = resolveModelFromChain(req.fallbackChain, avail)
|
const resolved = resolveModelFromChain(req.fallbackChain, avail)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
const variant = resolved.variant ?? req.variant
|
const variant = resolved.variant ?? req.variant
|
||||||
agents[role] = variant ? { model: resolved.model, variant } : { model: resolved.model }
|
const agentConfig = variant ? { model: resolved.model, variant } : { model: resolved.model }
|
||||||
|
agents[role] = attachFallbackModels(agentConfig, req.fallbackChain, avail)
|
||||||
} else {
|
} else {
|
||||||
agents[role] = { model: ULTIMATE_FALLBACK }
|
agents[role] = { model: ULTIMATE_FALLBACK }
|
||||||
}
|
}
|
||||||
@@ -123,7 +166,8 @@ export function generateModelConfig(config: InstallConfig): GeneratedOmoConfig {
|
|||||||
const resolved = resolveModelFromChain(fallbackChain, avail)
|
const resolved = resolveModelFromChain(fallbackChain, avail)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
const variant = resolved.variant ?? req.variant
|
const variant = resolved.variant ?? req.variant
|
||||||
categories[cat] = variant ? { model: resolved.model, variant } : { model: resolved.model }
|
const categoryConfig = variant ? { model: resolved.model, variant } : { model: resolved.model }
|
||||||
|
categories[cat] = attachFallbackModels(categoryConfig, fallbackChain, avail)
|
||||||
} else {
|
} else {
|
||||||
categories[cat] = { model: ULTIMATE_FALLBACK }
|
categories[cat] = { model: ULTIMATE_FALLBACK }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user