feat(config): support object-style fallback_models with per-model settings

Add support for object-style entries in fallback_models arrays, enabling
per-model configuration of variant, reasoningEffort, temperature, top_p,
maxTokens, and thinking settings.

- Zod schema for FallbackModelObject with full validation
- normalizeFallbackModels() and flattenToFallbackModelStrings() utilities
- Provider-agnostic model resolution pipeline with fallback chain
- Session prompt params state management
- Fallback chain construction with prefix-match lookup
- Integration across delegate-task, background-agent, and plugin layers
This commit is contained in:
Ravi Tharuma
2026-03-18 14:21:27 +01:00
parent 7761e48dca
commit a77a16c494
34 changed files with 1686 additions and 126 deletions
+39 -3
View File
@@ -1,6 +1,8 @@
import type { FallbackEntry } from "./model-requirements"
import type { FallbackModelObject } from "../config/schema/fallback-models"
import { normalizeModel } from "./model-normalization"
import { resolveModelPipeline } from "./model-resolution-pipeline"
import { KNOWN_VARIANTS } from "./known-variants"
export type ModelResolutionInput = {
userModel?: string
@@ -61,11 +63,45 @@ export function resolveModelWithFallback(
}
/**
* Normalizes fallback_models config (which can be string or string[]) to string[]
* Centralized helper to avoid duplicated normalization logic
* Normalizes fallback_models config to a mixed array.
* Accepts string, string[], or mixed arrays of strings and FallbackModelObject entries.
*/
export function normalizeFallbackModels(models: string | string[] | undefined): string[] | undefined {
export function normalizeFallbackModels(
models: string | (string | FallbackModelObject)[] | undefined,
): (string | FallbackModelObject)[] | undefined {
if (!models) return undefined
if (typeof models === "string") return [models]
return models
}
/**
* Extracts plain model strings from a mixed fallback models array.
* Object entries are flattened to "model" or "model(variant)" strings.
* Use this when consumers need string[] (e.g., resolveModelForDelegateTask).
*/
export function flattenToFallbackModelStrings(
models: (string | FallbackModelObject)[] | undefined,
): string[] | undefined {
if (!models) return undefined
return models.map((entry) => {
if (typeof entry === "string") return entry
const variant = entry.variant
if (variant) {
// Strip any supported inline variant syntax before appending explicit override.
// Supports both parenthesized and space-suffix forms so we don't emit
// invalid strings like "provider/model high(low)".
const model = entry.model
.replace(/\([^()]+\)\s*$/, "")
.replace(/\s+([a-z][a-z0-9_-]*)\s*$/i, (match, suffix) => {
const normalized = String(suffix).toLowerCase()
return KNOWN_VARIANTS.has(normalized)
? ""
: match
})
.trim()
return `${model}(${variant})`
}
// No explicit variant — preserve model string as-is (including any inline variant)
return entry.model
})
}