diff --git a/src/tts/provider-error-utils.test.ts b/src/tts/provider-error-utils.test.ts deleted file mode 100644 index c57623172b2..00000000000 --- a/src/tts/provider-error-utils.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - assertOkOrThrowProviderError, - extractProviderErrorDetail, - extractProviderRequestId, -} from "./provider-error-utils.js"; - -describe("provider error utils", () => { - it("formats nested provider error details with request ids", async () => { - const response = new Response( - JSON.stringify({ - detail: { - message: "Quota exceeded", - status: "quota_exceeded", - }, - }), - { - status: 429, - headers: { "x-request-id": "req_123" }, - }, - ); - - await expect(assertOkOrThrowProviderError(response, "Provider API error")).rejects.toThrow( - "Provider API error (429): Quota exceeded [code=quota_exceeded] [request_id=req_123]", - ); - }); - - it("reads string error fields and fallback request id headers", async () => { - const response = new Response(JSON.stringify({ error: "Invalid API key" }), { - status: 401, - headers: { "request-id": "fallback_req" }, - }); - - expect(await extractProviderErrorDetail(response)).toBe("Invalid API key"); - expect(extractProviderRequestId(response)).toBe("fallback_req"); - }); -}); diff --git a/src/tts/provider-error-utils.ts b/src/tts/provider-error-utils.ts deleted file mode 100644 index 5d99dc453e7..00000000000 --- a/src/tts/provider-error-utils.ts +++ /dev/null @@ -1,152 +0,0 @@ -export { asFiniteNumber } from "../shared/number-coercion.js"; -import { normalizeOptionalString as trimToUndefined } from "../shared/string-coerce.js"; -export { normalizeOptionalString as trimToUndefined } from "../shared/string-coerce.js"; - -export function asBoolean(value: unknown): boolean | undefined { - return typeof value === "boolean" ? value : undefined; -} - -export function asObject(value: unknown): Record | undefined { - return typeof value === "object" && value !== null && !Array.isArray(value) - ? (value as Record) - : undefined; -} - -export function truncateErrorDetail(detail: string, limit = 220): string { - return detail.length <= limit ? detail : `${detail.slice(0, limit - 1)}…`; -} - -export async function readResponseTextLimited( - response: Response, - limitBytes = 16 * 1024, -): Promise { - if (limitBytes <= 0) { - return ""; - } - const reader = response.body?.getReader(); - if (!reader) { - return ""; - } - - const decoder = new TextDecoder(); - let total = 0; - let text = ""; - let reachedLimit = false; - - try { - while (true) { - const { value, done } = await reader.read(); - if (done) { - break; - } - if (!value || value.byteLength === 0) { - continue; - } - const remaining = limitBytes - total; - if (remaining <= 0) { - reachedLimit = true; - break; - } - const chunk = value.byteLength > remaining ? value.subarray(0, remaining) : value; - total += chunk.byteLength; - text += decoder.decode(chunk, { stream: true }); - if (total >= limitBytes) { - reachedLimit = true; - break; - } - } - text += decoder.decode(); - } finally { - if (reachedLimit) { - await reader.cancel().catch(() => {}); - } - } - - return text; -} - -export function formatProviderErrorPayload(payload: unknown): string | undefined { - const root = asObject(payload); - const detailObject = asObject(root?.detail); - const subject = asObject(root?.error) ?? detailObject ?? root; - if (!subject) { - return undefined; - } - const message = - trimToUndefined(subject.message) ?? - trimToUndefined(subject.detail) ?? - trimToUndefined(root?.message) ?? - trimToUndefined(root?.error) ?? - trimToUndefined(root?.detail); - const type = trimToUndefined(subject.type); - const code = trimToUndefined(subject.code) ?? trimToUndefined(subject.status); - const metadata = [type ? `type=${type}` : undefined, code ? `code=${code}` : undefined] - .filter((value): value is string => Boolean(value)) - .join(", "); - if (message && metadata) { - return `${truncateErrorDetail(message)} [${metadata}]`; - } - if (message) { - return truncateErrorDetail(message); - } - if (metadata) { - return `[${metadata}]`; - } - return undefined; -} - -export async function extractProviderErrorDetail(response: Response): Promise { - const rawBody = trimToUndefined(await readResponseTextLimited(response)); - if (!rawBody) { - return undefined; - } - try { - return formatProviderErrorPayload(JSON.parse(rawBody)) ?? truncateErrorDetail(rawBody); - } catch { - return truncateErrorDetail(rawBody); - } -} - -export function extractProviderRequestId(response: Response): string | undefined { - return ( - trimToUndefined(response.headers.get("x-request-id")) ?? - trimToUndefined(response.headers.get("request-id")) - ); -} - -export function formatProviderHttpErrorMessage(params: { - label: string; - status: number; - detail?: string; - requestId?: string; -}): string { - const { label, status, detail, requestId } = params; - return ( - `${label} (${status})` + - (detail ? `: ${detail}` : "") + - (requestId ? ` [request_id=${requestId}]` : "") - ); -} - -export async function createProviderHttpError(response: Response, label: string): Promise { - const detail = await extractProviderErrorDetail(response); - const requestId = extractProviderRequestId(response); - return new Error( - formatProviderHttpErrorMessage({ - label, - status: response.status, - detail, - requestId, - }), - ); -} - -export async function assertOkOrThrowProviderError( - response: Response, - label: string, -): Promise { - if (response.ok) { - return; - } - throw await createProviderHttpError(response, label); -}