|
1 | 1 | import { Anthropic } from "@anthropic-ai/sdk" |
2 | 2 | import OpenAI from "openai" |
3 | | -import { convertToAiSdkMessages, convertToolsForAiSdk, processAiSdkStreamPart, mapToolChoice } from "../ai-sdk" |
| 3 | +import { |
| 4 | + convertToAiSdkMessages, |
| 5 | + convertToolsForAiSdk, |
| 6 | + processAiSdkStreamPart, |
| 7 | + mapToolChoice, |
| 8 | + extractAiSdkErrorMessage, |
| 9 | + handleAiSdkError, |
| 10 | +} from "../ai-sdk" |
4 | 11 |
|
5 | 12 | vitest.mock("ai", () => ({ |
6 | 13 | tool: vitest.fn((t) => t), |
@@ -531,4 +538,110 @@ describe("AI SDK conversion utilities", () => { |
531 | 538 | expect(result).toBeUndefined() |
532 | 539 | }) |
533 | 540 | }) |
| 541 | + |
| 542 | + describe("extractAiSdkErrorMessage", () => { |
| 543 | + it("should return 'Unknown error' for null/undefined", () => { |
| 544 | + expect(extractAiSdkErrorMessage(null)).toBe("Unknown error") |
| 545 | + expect(extractAiSdkErrorMessage(undefined)).toBe("Unknown error") |
| 546 | + }) |
| 547 | + |
| 548 | + it("should extract message from AI_RetryError", () => { |
| 549 | + const retryError = { |
| 550 | + name: "AI_RetryError", |
| 551 | + message: "Failed after 3 attempts", |
| 552 | + errors: [new Error("Error 1"), new Error("Error 2"), new Error("Too Many Requests")], |
| 553 | + lastError: { message: "Too Many Requests", status: 429 }, |
| 554 | + } |
| 555 | + |
| 556 | + const result = extractAiSdkErrorMessage(retryError) |
| 557 | + expect(result).toBe("Failed after 3 attempts (429): Too Many Requests") |
| 558 | + }) |
| 559 | + |
| 560 | + it("should handle AI_RetryError without status", () => { |
| 561 | + const retryError = { |
| 562 | + name: "AI_RetryError", |
| 563 | + message: "Failed after 2 attempts", |
| 564 | + errors: [new Error("Error 1"), new Error("Connection failed")], |
| 565 | + lastError: { message: "Connection failed" }, |
| 566 | + } |
| 567 | + |
| 568 | + const result = extractAiSdkErrorMessage(retryError) |
| 569 | + expect(result).toBe("Failed after 2 attempts: Connection failed") |
| 570 | + }) |
| 571 | + |
| 572 | + it("should extract message from AI_APICallError", () => { |
| 573 | + const apiError = { |
| 574 | + name: "AI_APICallError", |
| 575 | + message: "Rate limit exceeded", |
| 576 | + status: 429, |
| 577 | + } |
| 578 | + |
| 579 | + const result = extractAiSdkErrorMessage(apiError) |
| 580 | + expect(result).toBe("API Error (429): Rate limit exceeded") |
| 581 | + }) |
| 582 | + |
| 583 | + it("should handle AI_APICallError without status", () => { |
| 584 | + const apiError = { |
| 585 | + name: "AI_APICallError", |
| 586 | + message: "Connection timeout", |
| 587 | + } |
| 588 | + |
| 589 | + const result = extractAiSdkErrorMessage(apiError) |
| 590 | + expect(result).toBe("Connection timeout") |
| 591 | + }) |
| 592 | + |
| 593 | + it("should extract message from standard Error", () => { |
| 594 | + const error = new Error("Something went wrong") |
| 595 | + expect(extractAiSdkErrorMessage(error)).toBe("Something went wrong") |
| 596 | + }) |
| 597 | + |
| 598 | + it("should convert non-Error to string", () => { |
| 599 | + expect(extractAiSdkErrorMessage("string error")).toBe("string error") |
| 600 | + expect(extractAiSdkErrorMessage({ custom: "object" })).toBe("[object Object]") |
| 601 | + }) |
| 602 | + }) |
| 603 | + |
| 604 | + describe("handleAiSdkError", () => { |
| 605 | + it("should wrap error with provider name", () => { |
| 606 | + const error = new Error("API Error") |
| 607 | + const result = handleAiSdkError(error, "Fireworks") |
| 608 | + |
| 609 | + expect(result.message).toBe("Fireworks: API Error") |
| 610 | + }) |
| 611 | + |
| 612 | + it("should preserve status code from AI_RetryError", () => { |
| 613 | + const retryError = { |
| 614 | + name: "AI_RetryError", |
| 615 | + errors: [new Error("Too Many Requests")], |
| 616 | + lastError: { message: "Too Many Requests", status: 429 }, |
| 617 | + } |
| 618 | + |
| 619 | + const result = handleAiSdkError(retryError, "Groq") |
| 620 | + |
| 621 | + expect(result.message).toContain("Groq:") |
| 622 | + expect(result.message).toContain("429") |
| 623 | + expect((result as any).status).toBe(429) |
| 624 | + }) |
| 625 | + |
| 626 | + it("should preserve status code from AI_APICallError", () => { |
| 627 | + const apiError = { |
| 628 | + name: "AI_APICallError", |
| 629 | + message: "Unauthorized", |
| 630 | + status: 401, |
| 631 | + } |
| 632 | + |
| 633 | + const result = handleAiSdkError(apiError, "DeepSeek") |
| 634 | + |
| 635 | + expect(result.message).toContain("DeepSeek:") |
| 636 | + expect(result.message).toContain("401") |
| 637 | + expect((result as any).status).toBe(401) |
| 638 | + }) |
| 639 | + |
| 640 | + it("should preserve original error as cause", () => { |
| 641 | + const originalError = new Error("Original error") |
| 642 | + const result = handleAiSdkError(originalError, "Cerebras") |
| 643 | + |
| 644 | + expect((result as any).cause).toBe(originalError) |
| 645 | + }) |
| 646 | + }) |
534 | 647 | }) |
0 commit comments