import type { Project, Task } from '../model/types.js'; import type { Complexity } from '../model/complexity.js'; /** 角色:executor(执行改动)/ reviewer(执行后复审) */ export type ModelRole = 'executor' | 'reviewer'; /** 模型不可用时的回退链(按序取第一个 ≠ 失败模型的,同一 run 内只重试一次) */ export const MODEL_FALLBACK_CHAIN = ['claude-opus-4-8', 'claude-sonnet-4-6'] as const; /** 复杂度 → [env 覆盖变量名, 默认模型] */ const MODEL_TABLE: Record> = { executor: { easy: ['MAESTRO_MODEL_EASY', 'claude-sonnet-4-6'], medium: ['MAESTRO_MODEL_MEDIUM', 'claude-opus-4-8'], hard: ['MAESTRO_MODEL_HARD', 'claude-fable-5'], }, reviewer: { easy: ['MAESTRO_MODEL_REVIEW_EASY', 'claude-sonnet-4-6'], medium: ['MAESTRO_MODEL_REVIEW_MEDIUM', 'claude-opus-4-8'], hard: ['MAESTRO_MODEL_REVIEW_HARD', 'claude-opus-4-8'], }, }; /** * 按角色 + 复杂度选模型: * 1. project.model 设了就最优先(executor/reviewer 都用它) * 2. 否则查 env 覆盖(MAESTRO_MODEL_* / MAESTRO_MODEL_REVIEW_*) * 3. 否则按复杂度取默认 */ export function pickModel(task: Task, project: Project, role: ModelRole): string { if (project.model) return project.model; const [env, fallback] = MODEL_TABLE[role][task.complexity]; const override = process.env[env]?.trim(); return override || fallback; } /** 看板展示用:当前项目 executor 各复杂度实际会用的模型(含 project.model / env 覆盖解析) */ export function resolvedExecutorModels(project: Project): Record { const out = {} as Record; for (const c of ['easy', 'medium', 'hard'] as Complexity[]) { if (project.model) { out[c] = project.model; continue; } const [env, fallback] = MODEL_TABLE.executor[c]; out[c] = process.env[env]?.trim() || fallback; } return out; } /** * AUTO 复杂度分类器用的模型:复用 executor easy 档(claude-sonnet,省额度), * env MAESTRO_MODEL_EASY 可覆盖。单次轻量调用,不走 project.model。 */ export function pickClassifierModel(): string { const [env, fallback] = MODEL_TABLE.executor.easy; return process.env[env]?.trim() || fallback; } /** 错误信息是否像“模型不可用”(not_found / invalid / permission 等模式 + 提到 model) */ export function isModelError(msg: string): boolean { if (!/model/i.test(msg)) return false; return /not[_\s-]?found|invalid|permission|forbidden|unauthorized|unavailable|unknown|unsupported|does not exist|no access|404|403/i.test(msg); } /** 回退链里取第一个与失败模型不同的;链上全相同(不可能两项都等)则 null */ export function pickFallbackModel(failedModel: string): string | null { for (const m of MODEL_FALLBACK_CHAIN) { if (m !== failedModel) return m; } return null; }