/** * agentLoopS42.test.ts — Integration tests S42: CapabilityRouter + TaskClassifier wire-in * * Copertura (12 test): * 1. classifyTask + route: task semplice → light tier → groq (velocità) * 2. classifyTask + route: task premium → high reasoning → gemini o openai * 3. classifyTask + route: code_generation → code=true → provider con code_score alto * 4. classifyTask + route: search_and_report → requires_tools=true → provider con tools=true * 5. route: prefer_free=true → esclude provider non free se disponibili free * 6. _setRouterDeps: isAvailable filtra provider senza token * 7. _setRouterDeps: solo groq disponibile → route ritorna groq * 8. route con tutti i provider unavailable → null (no crash) * 9. classifyTask: debug → modelTier standard, code=true nel capReq * 10. classifyTask: full_app → premium, needsPlan=true * 11. preferredModel map: groq → llama-3.3-70b-versatile * 12. preferredModel map: gemini → gemini-2.5-flash * * DI: _setTestDeps / _resetDeps + _setTestMatcher / _resetMatcher * Zero vi.mock su moduli ESM — pattern DI puro * SESSIONE: S42 — CapabilityRouter + TaskClassifier wire-in agentLoop */ import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { classifyTask, _setTestMatcher, _resetMatcher, } from "@/lib/taskClassifier"; import { route, scoreProfile, CAPABILITY_PROFILES, _setTestDeps, _resetDeps, } from "@/lib/capabilityRouter"; import type { CapabilityRequest } from "@/lib/capabilityRouter"; // ─── Helpers ────────────────────────────────────────────────────────────────── /** Costruisce un CapabilityRequest dal TaskClassification (rispecchia la logica wire-in) */ function buildCapReq(msg: string): CapabilityRequest { const cls = classifyTask(msg); return { reasoning_depth: ( cls.modelTier === "premium" ? "high" : cls.modelTier === "standard" ? "medium" : "low" ) as "low" | "medium" | "high", code: cls.type === "code_generation" || cls.type === "code_fix" || cls.type === "refactor" || cls.type === "debug", requires_tools: cls.type === "search_and_report" || cls.type === "debug", prefer_free: true, speed: cls.complexity <= 3, }; } /** Mappa provider → modello hint (rispecchia la logica wire-in) */ function mapPreferredModel(provider: string): string | undefined { switch (provider) { case "groq": return "llama-3.3-70b-versatile"; case "gemini": return "gemini-2.5-flash"; // gemini-2.0-flash deprecato marzo 2026 case "openai": return "gpt-4o-mini"; case "openrouter": return "moonshotai/kimi-k2.6:free"; // S306: deepseek-v4-flash non più free default: return undefined; } } // ─── Setup / Teardown ───────────────────────────────────────────────────────── beforeEach(() => { _setTestDeps({ isAvailable: () => true }); }); afterEach(() => { _resetDeps(); _resetMatcher(); }); // ─── Test suite ─────────────────────────────────────────────────────────────── describe("S42: TaskClassifier + CapabilityRouter integration", () => { it("1. task semplice → light tier → provider veloce scelto (speed=true)", () => { const cls = classifyTask("ciao"); expect(cls.modelTier).toBe("light"); expect(cls.complexity).toBeLessThanOrEqual(3); const req = buildCapReq("ciao"); expect(req.speed).toBe(true); expect(req.reasoning_depth).toBe("low"); const result = route(req); expect(result).not.toBeNull(); // Con speed=true groq ha lo score più alto (speed_score=10) expect(result!.provider).toBe("groq"); }); it("2. task premium (full_app) → reasoning high → provider con reasoning_score>=9", () => { const cls = classifyTask("crea un'app completa da zero con dashboard e autenticazione"); expect(cls.modelTier).toBe("premium"); const req = buildCapReq("crea un'app completa da zero con dashboard e autenticazione"); expect(req.reasoning_depth).toBe("high"); const result = route(req); expect(result).not.toBeNull(); expect(result!.profile.reasoning_score).toBeGreaterThanOrEqual(9); expect(["gemini", "openai"]).toContain(result!.provider); }); it("3. code_generation → code=true nel capReq → provider con code_score alto", () => { const cls = classifyTask("scrivi una funzione TypeScript per validare email"); expect(cls.type).toBe("code_generation"); const req = buildCapReq("scrivi una funzione TypeScript per validare email"); expect(req.code).toBe(true); const result = route(req); expect(result).not.toBeNull(); // I provider con code_score alto (openai=9, gemini=8, openrouter=8) devono essere preferiti expect(result!.profile.code_score).toBeGreaterThanOrEqual(6); }); it("4. search_and_report → requires_tools=true → provider con tools=true", () => { const cls = classifyTask("cerca informazioni su TypeScript 5.9"); expect(cls.type).toBe("search_and_report"); const req = buildCapReq("cerca informazioni su TypeScript 5.9"); expect(req.requires_tools).toBe(true); const result = route(req); expect(result).not.toBeNull(); // Deve scegliere un provider con tools=true expect(result!.profile.tools).toBe(true); }); it("5. prefer_free=true → esclude provider non free se ci sono free disponibili", () => { // Rende disponibili solo free + together (non free) _setTestDeps({ isAvailable: (id) => ["gemini", "groq", "together"].includes(id) }); const result = route({ prefer_free: true, speed: true }); expect(result).not.toBeNull(); // Deve preferire groq (free + speed 10/10) su together (non free) expect(result!.profile.free).toBe(true); }); it("6. _setRouterDeps: filtra provider senza token (solo gemini disponibile)", () => { _setTestDeps({ isAvailable: (id) => id === "gemini" }); const result = route({ reasoning_depth: "high" }); expect(result).not.toBeNull(); expect(result!.provider).toBe("gemini"); }); it("7. _setRouterDeps: solo groq disponibile → ritorna groq", () => { _setTestDeps({ isAvailable: (id) => id === "groq" }); const result = route({ speed: true }); expect(result).not.toBeNull(); expect(result!.provider).toBe("groq"); }); it("8. tutti i provider unavailable → route ritorna null (no crash)", () => { _setTestDeps({ isAvailable: () => false }); const result = route({ reasoning_depth: "high" }); expect(result).toBeNull(); }); it("9. debug task → modelTier standard, code=true, requires_tools=true nel capReq", () => { // "non funziona" matcha il pattern debug const msg = "la funzione non funziona e restituisce undefined"; const cls = classifyTask(msg); expect(cls.type).toBe("debug"); expect(cls.modelTier).toBe("standard"); const req = buildCapReq(msg); expect(req.code).toBe(true); expect(req.requires_tools).toBe(true); expect(req.reasoning_depth).toBe("medium"); }); it("10. full_app → premium, needsPlan=true, multiFile=true", () => { const cls = classifyTask("build a complete app from scratch with auth and dashboard"); expect(cls.modelTier).toBe("premium"); expect(cls.needsPlan).toBe(true); expect(cls.multiFile).toBe(true); }); it("11. mapPreferredModel: groq → llama-3.3-70b-versatile", () => { expect(mapPreferredModel("groq")).toBe("llama-3.3-70b-versatile"); }); it("12. mapPreferredModel: gemini → gemini-2.5-flash, openai → gpt-4o-mini, sconosciuto → undefined", () => { expect(mapPreferredModel("gemini")).toBe("gemini-2.5-flash"); expect(mapPreferredModel("openai")).toBe("gpt-4o-mini"); expect(mapPreferredModel("openrouter")).toBe("moonshotai/kimi-k2.6:free"); // S306 expect(mapPreferredModel("huggingface")).toBeUndefined(); }); }); // ─── scoreProfile sanity checks per wire-in ────────────────────────────────── describe("S42: scoreProfile sanity per wire-in", () => { it("browser-llm con requires_tools=true ha score molto negativo (bloccato dal router)", () => { const profile = CAPABILITY_PROFILES["browser-llm"]; const score = scoreProfile(profile, { requires_tools: true }); expect(score).toBeLessThan(-50); }); it("gemini ha score positivo per reasoning_depth=high", () => { const profile = CAPABILITY_PROFILES["gemini"]; const score = scoreProfile(profile, { reasoning_depth: "high" }); expect(score).toBeGreaterThan(0); }); it("groq ha score più alto di gemini per speed=true (speed_score 10 vs 7)", () => { const groqScore = scoreProfile(CAPABILITY_PROFILES["groq"], { speed: true }); const geminiScore = scoreProfile(CAPABILITY_PROFILES["gemini"], { speed: true }); expect(groqScore).toBeGreaterThan(geminiScore); }); });