Spaces:
Sleeping
Sleeping
| /** | |
| * 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); | |
| }); | |
| }); | |