/** * Model Configuration & Registry * * Defines the full model catalog and provides model lookup, fallback chains, * and availability checking. Implements the model tier system from squad.agent.md. * * @module config/models */ import type { StorageProvider } from '../storage/index.js'; import type { ModelId, ModelTier } from '../runtime/config.js'; import type { SquadReasoningEffort, SquadContextTier } from '../adapter/types.js'; /** * Per-token pricing in USD. */ export interface ModelPricing { /** Cost per input token in USD */ inputPerToken: number; /** Cost per output token in USD */ outputPerToken: number; } /** * GitHub Copilot billing cost-ceiling category (`model_picker_category`). * * This is a COST axis and is intentionally SEPARATE from {@link ModelTier} * (the quality axis). A model may be standard-tier (quality) yet * powerful-category (cost) — e.g. `gpt-5.4`. Do not conflate the two. * Source: GitHub Copilot models API `model_picker_category` (canonical), * with the public `github/docs` models-and-pricing.yml as fallback. */ export type GitHubModelCategory = 'lightweight' | 'versatile' | 'powerful'; /** * Ordering of GitHub billing cost-ceiling categories, cheapest → most costly. * "Within ceiling" ⇔ `CATEGORY_ORDER[model] <= CATEGORY_ORDER[maxCategory]`. */ export declare const CATEGORY_ORDER: Record; /** * Persistent (config-level) cost policy. * * This is the COST-CEILING axis, kept deliberately separate from the quality * {@link ModelTier} axis (issue #1080 / #1183). It intentionally does NOT * carry per-token pricing or an `included`/zero-credit flag — both were dropped * as unsourceable/stale-prone (NG2/NG3). */ export interface CostPolicyConfig { /** * Maximum GitHub billing category permitted for automatic model selection. * When undefined the policy is a no-op (passthrough). */ maxCategory?: GitHubModelCategory; } /** * Per-session cost policy override, supplied at spawn time. Takes precedence * over the persistent {@link CostPolicyConfig}. */ export interface SessionCostPolicyOverride { maxCategory?: GitHubModelCategory; } /** * The action a cost policy took while finalizing a resolved model. * - `none`: model was within ceiling (or chain merely pruned). * - `downgraded-to-ceiling`: an implicit over-ceiling pick was replaced. * - `warn-allow-explicit`: an explicit over-ceiling pick was honored + warned. * - `no-compliant-model`: fail-closed — no in-ceiling model exists anywhere. */ export type CostPolicyAction = 'none' | 'downgraded-to-ceiling' | 'warn-allow-explicit' | 'no-compliant-model'; /** * Outcome of applying a cost policy to a resolved model. Surfaced (not * swallowed — this was the #1089 bug) so the lifecycle can emit `warning`. */ export interface CostPolicyOutcome { action: CostPolicyAction; originalModel: string; finalModel: string; /** Human-readable warning to surface via EventBus/log; present when action ≠ 'none'. */ warning?: string; } /** * Model capability information. */ export interface ModelInfo { /** Model identifier */ id: ModelId; /** Model tier (quality axis) */ tier: ModelTier; /** Provider (anthropic, openai, google) */ provider: 'anthropic' | 'openai' | 'google'; /** Model family */ family: 'claude' | 'gpt' | 'gemini'; /** * GitHub Copilot billing cost-ceiling category (cost axis). * Separate from {@link tier}; optional so out-of-catalog IDs still pass through. */ githubCategory?: GitHubModelCategory; /** Supports vision/multimodal input */ vision?: boolean; /** Typical use cases */ useCases?: string[]; /** Relative cost heuristic (1-10 scale, 10 = most expensive; not USD) */ cost?: number; /** Relative speed (1-10 scale, 10 = fastest) */ speed?: number; /** Uncached input/output token pricing in USD (if known) */ pricing?: ModelPricing; } /** * Full model catalog. * * Restricted to model IDs verified reachable from the GitHub Copilot CLI * surface (the `copilot-cli` integration subset, 17 enabled models, verified * 2026-07-13). Each entry carries an optional {@link ModelInfo.githubCategory} * (cost axis) sourced from the models API `model_picker_category`, kept * separate from {@link ModelInfo.tier} (quality axis). * * Notes: * - No hardcoded per-token pricing is added for models whose pricing is not * already known; pricing is sourced out-of-band and is intentionally absent * on newer entries rather than guessed. * - Out-of-catalog IDs still pass through the selector (0-cost estimate + * default chain); this catalog drives routing quality/cost, not correctness. * * Refs: #1080, #1183. */ export declare const MODEL_CATALOG: ModelInfo[]; /** * Default fallback chains per tier — real, CLI-reachable IDs ordered by preference. * Preferred model order follows the tier routing policy (GPT-first for premium * and standard, then provider fallbacks). */ export declare const DEFAULT_FALLBACK_CHAINS: Record; /** * Model registry for lookups and availability checking. */ export declare class ModelRegistry { private catalog; private tierIndex; private providerIndex; constructor(catalog?: ModelInfo[]); /** * Gets model information by ID. * * @param id - Model identifier * @returns Model info if found, null otherwise */ getModelInfo(id: ModelId): ModelInfo | null; /** * Checks if a model is available in the catalog. * * @param id - Model identifier * @returns True if model exists in catalog */ isModelAvailable(id: ModelId): boolean; /** * Gets all models for a specific tier. * * @param tier - Model tier * @returns Array of models in that tier */ getModelsByTier(tier: ModelTier): ModelInfo[]; /** * Gets all models from a specific provider. * * @param provider - Provider name * @returns Array of models from that provider */ getModelsByProvider(provider: string): ModelInfo[]; /** * Gets the fallback chain for a specific tier. * * @param tier - Model tier * @param preferSameProvider - If true, prefer models from same provider * @param currentModel - Current model (for provider preference) * @returns Ordered array of fallback model IDs */ getFallbackChain(tier: ModelTier, preferSameProvider?: boolean, currentModel?: ModelId): ModelId[]; /** * Gets the next fallback model in the chain. * * @param currentModel - Current model that failed * @param tier - Model tier * @param attemptedModels - Models already attempted * @returns Next fallback model ID, or null if chain exhausted */ getNextFallback(currentModel: ModelId, tier: ModelTier, attemptedModels?: Set): ModelId | null; /** * Gets model recommendations based on use case. * * @param useCase - Desired use case * @param tier - Optional tier constraint * @returns Recommended models sorted by relevance */ getRecommendedModels(useCase: string, tier?: ModelTier): ModelInfo[]; /** * Gets all model IDs in the catalog. * * @returns Array of all model IDs */ getAllModelIds(): ModelId[]; /** * Gets catalog statistics. * * @returns Catalog stats */ getStats(): { total: number; byTier: Record; byProvider: Record; }; } /** * Default model registry instance. */ export declare const defaultRegistry: ModelRegistry; /** * Gets model information by ID (convenience function). */ export declare function getModelInfo(id: ModelId): ModelInfo | null; /** * Gets fallback chain for a tier (convenience function). */ export declare function getFallbackChain(tier: ModelTier): ModelId[]; /** * Checks if model is available (convenience function). */ export declare function isModelAvailable(id: ModelId): boolean; /** * Estimate the cost of a model invocation based on token counts and * the SDK's built-in pricing table. * * @returns Estimated uncached input/output cost in USD, or 0 if pricing is unavailable. */ export declare function estimateCost(model: string, inputTokens: number, outputTokens: number): number; /** * Economy mode model map: normal model → cheaper alternative. * * Applied at Layer 3 (task-aware auto) and Layer 4 (default) when * economy mode is active. Layers 0–2 (explicit preferences) are * never substituted — the user's explicit choice always wins. * * Table source: issue #500 */ export declare const ECONOMY_MODEL_MAP: Record; /** * Applies economy mode substitution to a model ID. * Returns the cheaper economy alternative, or the original if no mapping exists. */ export declare function applyEconomyMode(model: string): string; /** * Shape of model preference fields within `.squad/config.json`. */ export interface ModelPreferenceConfig { defaultModel?: string; agentModelOverrides?: Record; economyMode?: boolean; defaultReasoningEffort?: string; agentReasoningEffortOverrides?: Record; defaultContextTier?: string; agentContextTierOverrides?: Record; } /** * Reads the economy mode setting from `.squad/config.json`. * * @param squadDir - Path to the `.squad/` directory * @returns True if economyMode is enabled, false otherwise */ export declare function readEconomyMode(squadDir: string, storage?: StorageProvider): boolean; /** * Writes the economy mode setting to `.squad/config.json`. * Merges with existing config — does not overwrite other fields. * * @param squadDir - Path to the `.squad/` directory * @param enabled - Whether economy mode should be enabled */ export declare function writeEconomyMode(squadDir: string, enabled: boolean, storage?: StorageProvider): void; /** * Reads the persistent model preference from `.squad/config.json`. * * @param squadDir - Path to the `.squad/` directory * @returns The defaultModel string if set, or null */ export declare function readModelPreference(squadDir: string, storage?: StorageProvider): string | null; /** * Reads per-agent model overrides from `.squad/config.json`. * * @param squadDir - Path to the `.squad/` directory * @returns Record of agent name → model ID, or empty object */ export declare function readAgentModelOverrides(squadDir: string, storage?: StorageProvider): Record; /** * Writes a persistent model preference to `.squad/config.json`. * Merges with existing config — does not overwrite other fields. * * @param squadDir - Path to the `.squad/` directory * @param model - Model ID to persist, or null to clear */ export declare function writeModelPreference(squadDir: string, model: string | null, storage?: StorageProvider): void; /** * Writes per-agent model overrides to `.squad/config.json`. * Merges with existing config — does not overwrite other fields. * * @param squadDir - Path to the `.squad/` directory * @param overrides - Record of agent name → model ID, or null to clear */ export declare function writeAgentModelOverrides(squadDir: string, overrides: Record | null, storage?: StorageProvider): void; /** * Valid reasoning effort levels, ordered from lowest to highest. * "auto" is a permitted stored sentinel that resolvers treat as "not set". * Canonical runtime list — import this instead of duplicating. The `satisfies` * clause keeps it in lock-step with the canonical {@link SquadReasoningEffort} type. */ export declare const VALID_REASONING_EFFORTS: readonly ["low", "medium", "high", "xhigh", "max"]; /** Canonical reasoning-effort union (alias of {@link SquadReasoningEffort}). */ export type ValidReasoningEffort = SquadReasoningEffort; /** * Clamp a requested reasoning effort to the highest level supported by the model. * * If the model does not support reasoning effort at all (empty or missing * supportedEfforts), returns undefined. If the requested effort is within * the model's supported range, returns it unchanged. Otherwise, returns the * highest effort the model supports. * * Deliberate: when the requested effort is *below* the model's minimum * supported level (e.g. "low" against a model that only supports ["high"]), * it is clamped UP to that minimum so a reasoning-capable model always * receives a valid level rather than undefined. This upward clamp is * intentional — see the clampReasoningEffort tests in model-preference.test.ts. * * @param requested - The reasoning effort the user/charter requested * @param supportedEfforts - The model's supportedReasoningEfforts from listModels() * @returns The clamped effort, or undefined if the model doesn't support reasoning effort */ export declare function clampReasoningEffort(requested: string | undefined, supportedEfforts: string[] | undefined): string | undefined; /** * Reads the persistent reasoning effort preference from `.squad/config.json`. * * @param squadDir - Path to the `.squad/` directory * @returns The defaultReasoningEffort string if set, or null */ export declare function readReasoningEffort(squadDir: string, storage?: StorageProvider): string | null; /** * Reads per-agent reasoning effort overrides from `.squad/config.json`. * * @param squadDir - Path to the `.squad/` directory * @returns Record of agent name → reasoning effort, or empty object */ export declare function readAgentReasoningEffortOverrides(squadDir: string, storage?: StorageProvider): Record; /** * Writes a persistent reasoning effort preference to `.squad/config.json`. * Merges with existing config — does not overwrite other fields. * * @param squadDir - Path to the `.squad/` directory * @param effort - Reasoning effort to persist, or null to clear */ export declare function writeReasoningEffort(squadDir: string, effort: string | null, storage?: StorageProvider): void; /** * Writes per-agent reasoning effort overrides to `.squad/config.json`. * Merges with existing config — does not overwrite other fields. * * @param squadDir - Path to the `.squad/` directory * @param overrides - Record of agent name → reasoning effort, or null to clear */ export declare function writeAgentReasoningEffortOverrides(squadDir: string, overrides: Record | null, storage?: StorageProvider): void; /** * Resolves the effective reasoning effort for an agent spawn. * Uses a layered priority system matching the model resolution pattern: * Layer 0a: Per-agent persistent override (.squad/config.json agentReasoningEffortOverrides) * Layer 0b: Global persistent config (.squad/config.json defaultReasoningEffort) * Layer 1: Spawn-time override (caller-provided) * Layer 2: Charter preference (agent's ## Model → **Reasoning Effort:** field) * Layer 3: Default (undefined — let SDK/API decide) * * The value "auto" at any layer is treated as "not set" and falls through. * * When `supportedEfforts` is provided (from the model's capabilities via * listModels()), the resolved effort is clamped to the highest level the * model supports. This prevents API errors when a user requests e.g. * "xhigh" on a model that only supports up to "high". * * @param options - Resolution inputs * @returns Resolved reasoning effort string, or undefined if unset */ export declare function resolveReasoningEffort(options: { agentName?: string; squadDir?: string; spawnOverride?: string | null; charterPreference?: string | null; /** Model's supportedReasoningEfforts from listModels(). When provided, clamps the result. */ supportedEfforts?: string[]; storage?: StorageProvider; }): string | undefined; /** * Valid context tiers. * "auto" is a permitted stored sentinel that resolvers treat as "not set". * Canonical runtime list — import this instead of duplicating. The `satisfies` * clause keeps it in lock-step with the canonical {@link SquadContextTier} type. * * Unlike reasoning effort there is no ranked scale: this is a two-value enum * ("default" = the model's standard window, "long_context" = its extended/1M * window). Clamping is therefore membership-based, not rank-based. */ export declare const VALID_CONTEXT_TIERS: readonly ["default", "long_context"]; /** Canonical context-tier union (alias of {@link SquadContextTier}). */ export type ValidContextTier = SquadContextTier; /** * Clamp a requested context tier to what the model actually supports. * * Semantics deliberately differ from {@link clampReasoningEffort}: context tier * is a two-value enum, not a ranked scale, so there is nothing to "clamp down" * to a nearest lower level. The rules are: * - Nothing requested (undefined/null/empty) → undefined (let the runtime decide). * - Unknown / invalid tier string → the model default (or "default"). This is * the "unknown treated as default" rule from issue #1446. * - Model capabilities unknown (no supportedTiers) → trust the valid request. * - Requested tier supported → return it unchanged. * - Requested tier unsupported (e.g. "long_context" on a model without a * long-context window) → clamp to the model's default tier, or "default". * * @param requested - The context tier the user/charter requested * @param supportedTiers - The model's supportedContextTiers from listModels() * @param modelDefault - The model's defaultContextTier from listModels() * @returns The clamped tier, or undefined if nothing was requested */ export declare function clampContextTier(requested: string | undefined, supportedTiers: string[] | undefined, modelDefault?: string): string | undefined; /** * Reads the persistent context tier preference from `.squad/config.json`. * * @param squadDir - Path to the `.squad/` directory * @returns The defaultContextTier string if set, or null */ export declare function readContextTier(squadDir: string, storage?: StorageProvider): string | null; /** * Reads per-agent context tier overrides from `.squad/config.json`. * * @param squadDir - Path to the `.squad/` directory * @returns Record of agent name → context tier, or empty object */ export declare function readAgentContextTierOverrides(squadDir: string, storage?: StorageProvider): Record; /** * Writes a persistent context tier preference to `.squad/config.json`. * Merges with existing config — does not overwrite other fields. * * @param squadDir - Path to the `.squad/` directory * @param tier - Context tier to persist, or null to clear */ export declare function writeContextTier(squadDir: string, tier: string | null, storage?: StorageProvider): void; /** * Writes per-agent context tier overrides to `.squad/config.json`. * Merges with existing config — does not overwrite other fields. * * @param squadDir - Path to the `.squad/` directory * @param overrides - Record of agent name → context tier, or null to clear */ export declare function writeAgentContextTierOverrides(squadDir: string, overrides: Record | null, storage?: StorageProvider): void; /** * Resolves the effective context tier for an agent spawn. * Uses a layered priority system matching the reasoning-effort resolution pattern: * Layer 0a: Per-agent persistent override (.squad/config.json agentContextTierOverrides) * Layer 0b: Global persistent config (.squad/config.json defaultContextTier) * Layer 1: Spawn-time override (caller-provided) * Layer 2: Charter preference (agent's ## Model → **Context Tier:** field) * Layer 3: Default (undefined — let SDK/runtime decide) * * The value "auto" at any layer is treated as "not set" and falls through. * * When `supportedContextTiers` is provided (from the model's capabilities via * listModels()), the resolved tier is clamped to what the model supports. This * prevents errors when a user requests "long_context" on a model that only * exposes a default window. * * @param options - Resolution inputs * @returns Resolved context tier string, or undefined if unset */ export declare function resolveContextTier(options: { agentName?: string; squadDir?: string; spawnOverride?: string | null; charterPreference?: string | null; /** Model's supportedContextTiers from listModels(). When provided, clamps the result. */ supportedContextTiers?: string[]; /** Model's defaultContextTier from listModels(). Used as the clamp fallback. */ defaultContextTier?: string; storage?: StorageProvider; }): string | undefined; /** * Resolves the effective model for an agent spawn using the 5-layer hierarchy: * Layer 0: Persistent config (.squad/config.json defaultModel) * Layer 1: Session-wide user directive ("always use opus") * Layer 2: Charter preference (agent's ## Model section) * Layer 3: Task-aware auto-selection (code → sonnet, docs → haiku) * Layer 4: Default (gpt-5.6-luna) * * Per-agent overrides from config.json take priority over the global defaultModel. * * Economy mode modifier: when active (via economyMode option or config.json), * shifts model selection at Layer 3 and Layer 4 to cheaper alternatives per * ECONOMY_MODEL_MAP. Layers 0–2 (explicit user preferences) are never overridden. * * @param options - Resolution inputs * @returns Resolved model ID */ export declare function resolveModel(options: { agentName?: string; squadDir?: string; sessionDirective?: string | null; charterPreference?: string | null; taskModel?: string | null; /** When true, apply economy mode substitution at Layer 3/4. Overrides config. */ economyMode?: boolean; /** Storage provider for config file access. */ storage?: StorageProvider; }): string; //# sourceMappingURL=models.d.ts.map