import { describe, expect, it, vi } from "vitest"; import { runAssistantAddressLaneRuntime, type AssistantAddressFollowupCarryoverLike, type AssistantAddressLaneLike } from "../src/services/assistantAddressLaneRuntimeAdapter"; function limitedLane(category: string): AssistantAddressLaneLike { return { handled: true, debug: { limited_reason_category: category } }; } function factualLane(): AssistantAddressLaneLike { return { handled: true, debug: {} }; } function unhandledLane(): AssistantAddressLaneLike { return { handled: false, debug: {} }; } describe("assistant address lane runtime adapter", () => { it("returns contextual lane immediately when preferred contextual attempt is factual", async () => { const carryover: AssistantAddressFollowupCarryoverLike = { followupContext: { scope: "ctx" } }; const runAddressLaneAttempt = vi.fn(async () => factualLane()); const result = await runAssistantAddressLaneRuntime({ userMessage: "raw question", addressInputMessage: "normalized question", carryover, shouldPreferContextualLane: true, canRetryWithRawUserMessage: true, runAddressLaneAttempt, isRetryableAddressLimitedResult: (lane) => Boolean(lane?.debug?.limited_reason_category) }); expect(result.handled).toBe(true); expect(result.selection?.messageUsed).toBe("normalized question"); expect(result.selection?.carryMeta).toBe(carryover); expect(result.retryAudit.attempted).toBe(false); expect(runAddressLaneAttempt).toHaveBeenCalledTimes(1); expect(runAddressLaneAttempt).toHaveBeenCalledWith("normalized question", carryover, null); }); it("retries with raw message after limited result and returns factual retry", async () => { const carryover: AssistantAddressFollowupCarryoverLike = { followupContext: { scope: "ctx" } }; const runAddressLaneAttempt = vi .fn() .mockResolvedValueOnce(limitedLane("empty_match")) .mockResolvedValueOnce(limitedLane("empty_match")) .mockResolvedValueOnce(factualLane()); const result = await runAssistantAddressLaneRuntime({ userMessage: "raw question", addressInputMessage: "normalized question", carryover, shouldPreferContextualLane: false, canRetryWithRawUserMessage: true, runAddressLaneAttempt, isRetryableAddressLimitedResult: (lane) => Boolean(lane?.debug?.limited_reason_category) }); expect(result.handled).toBe(true); expect(result.selection?.messageUsed).toBe("raw question"); expect(result.selection?.carryMeta).toBe(carryover); expect(result.retryAudit.attempted).toBe(true); expect(result.retryAudit.reason).toBe("limited_result_retry_with_raw_message"); expect(result.retryAudit.initial_limited_category).toBe("empty_match"); expect(result.retryAudit.retry_used_followup_context).toBe(true); expect(result.retryAudit.retry_result_category).toBe(null); expect(runAddressLaneAttempt).toHaveBeenCalledTimes(3); }); it("can retry with raw message after unsupported limited result", async () => { const carryover: AssistantAddressFollowupCarryoverLike = { followupContext: { scope: "ctx" } }; const runAddressLaneAttempt = vi .fn() .mockResolvedValueOnce(limitedLane("unsupported")) .mockResolvedValueOnce(limitedLane("unsupported")) .mockResolvedValueOnce(factualLane()); const result = await runAssistantAddressLaneRuntime({ userMessage: "что нам отгружал чепурнов? какой товар или услугу?", addressInputMessage: "Определить, что необходимо отгрузить контрагенту Чепурнов", carryover, shouldPreferContextualLane: false, canRetryWithRawUserMessage: true, runAddressLaneAttempt, isRetryableAddressLimitedResult: (lane) => ["missing_anchor", "empty_match", "unsupported"].includes( String(lane?.debug?.limited_reason_category ?? "").trim() ) }); expect(result.handled).toBe(true); expect(result.selection?.messageUsed).toBe("что нам отгружал чепурнов? какой товар или услугу?"); expect(result.retryAudit.attempted).toBe(true); expect(result.retryAudit.initial_limited_category).toBe("unsupported"); expect(runAddressLaneAttempt).toHaveBeenCalledTimes(3); }); it("returns pending limited result when retry is disabled", async () => { const runAddressLaneAttempt = vi .fn() .mockResolvedValueOnce(limitedLane("missing_anchor")) .mockResolvedValueOnce(unhandledLane()); const result = await runAssistantAddressLaneRuntime({ userMessage: "raw question", addressInputMessage: "normalized question", carryover: { followupContext: { scope: "ctx" } }, shouldPreferContextualLane: false, canRetryWithRawUserMessage: false, runAddressLaneAttempt, isRetryableAddressLimitedResult: (lane) => Boolean(lane?.debug?.limited_reason_category) }); expect(result.handled).toBe(true); expect(result.selection?.messageUsed).toBe("normalized question"); expect(result.selection?.addressLane.debug?.limited_reason_category).toBe("missing_anchor"); expect(result.retryAudit.attempted).toBe(false); }); });