import { describe, expect, it } from "bun:test"; import { classifyError } from "../src/agent/types.ts"; describe("classifyError", () => { it("classifies 402 as credits_exhausted", () => { const err = Object.assign(new Error("Payment Required"), { statusCode: 402 }); const result = classifyError(err); expect(result.code).toBe("credits_exhausted"); expect(result.retryable).toBe(false); expect(result.message).toContain("out of credits"); }); it("classifies 429 as rate_limited and retryable", () => { const err = Object.assign(new Error("Too Many Requests"), { statusCode: 429 }); const result = classifyError(err); expect(result.code).toBe("rate_limited"); expect(result.retryable).toBe(true); }); it("classifies 500 as provider_error and retryable", () => { const err = Object.assign(new Error("Internal Server Error"), { statusCode: 500 }); const result = classifyError(err); expect(result.code).toBe("provider_error"); expect(result.retryable).toBe(true); }); it("classifies 401 as auth_error", () => { const err = Object.assign(new Error("Unauthorized"), { statusCode: 401 }); const result = classifyError(err); expect(result.code).toBe("auth_error"); expect(result.retryable).toBe(false); }); it("classifies 403 as auth_error", () => { const err = Object.assign(new Error("Forbidden"), { statusCode: 403 }); const result = classifyError(err); expect(result.code).toBe("auth_error"); expect(result.retryable).toBe(false); }); it("classifies 408 as timeout and retryable", () => { const err = Object.assign(new Error("Request Timeout"), { statusCode: 408 }); const result = classifyError(err); expect(result.code).toBe("timeout"); expect(result.retryable).toBe(true); }); it("unwraps NoOutputGeneratedError to classify the cause", () => { const cause = Object.assign(new Error("Payment Required"), { statusCode: 402 }); class NoOutputGeneratedError extends Error { constructor(msg: string, opts?: ErrorOptions) { super(msg, opts); } } const wrapper = new NoOutputGeneratedError("No output generated. Check the stream for errors.", { cause }); const result = classifyError(wrapper); expect(result.code).toBe("credits_exhausted"); expect(result.retryable).toBe(false); }); it("unwraps NoOutputGeneratedError via Symbol marker", () => { const cause = Object.assign(new Error("Too Many Requests"), { statusCode: 429 }); const wrapper = Object.assign( new Error("No output generated."), { [Symbol.for("vercel.ai.error.AI_NoOutputGeneratedError")]: true, cause }, ); const result = classifyError(wrapper); expect(result.code).toBe("rate_limited"); expect(result.retryable).toBe(true); }); it("falls back to unknown for generic errors", () => { const err = new Error("Something went wrong"); const result = classifyError(err); expect(result.code).toBe("unknown"); expect(result.retryable).toBe(false); expect(result.message).toBe("Something went wrong"); }); it("handles non-Error values", () => { const result = classifyError("string error"); expect(result.code).toBe("unknown"); expect(result.message).toBe("string error"); }); it("classifies NoOutputGeneratedError (no cause)", () => { class NoOutputGeneratedError extends Error { constructor() { super("No output generated."); } } const result = classifyError(new NoOutputGeneratedError()); expect(result.code).toBe("no_output"); expect(result.retryable).toBe(true); }); it("classifies MissingToolResultsError", () => { class MissingToolResultsError extends Error { toolCallIds: string[]; constructor() { super("Tool results are missing for tool calls abc, def."); this.toolCallIds = ["abc", "def"]; } } const result = classifyError(new MissingToolResultsError()); expect(result.code).toBe("context_corrupt"); expect(result.retryable).toBe(false); }); });