Skip to content

Commit 4bd597f

Browse files
Improve error reporting for API call failures
- Add formatError() and formatApiCallError() helpers - Unwrap RetryError to expose the underlying APICallError - Show full response body (pretty-printed JSON or raw text) - Indent multi-line error output consistently Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 864c334 commit 4bd597f

1 file changed

Lines changed: 33 additions & 2 deletions

File tree

index.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env bun
22
import { generateText, streamText, tool, stepCountIs } from "ai";
3+
import { AISDKError, APICallError } from "@ai-sdk/provider";
4+
import { RetryError } from "ai";
35
import { createOpenAI } from "@ai-sdk/openai";
46
import { createAnthropic } from "@ai-sdk/anthropic";
57
import { z } from "zod";
@@ -173,6 +175,34 @@ async function runStreamTools(model: ReturnType<typeof createModel>): Promise<st
173175
return `tool_calls=${toolCalls.length} text="${text}"`;
174176
}
175177

178+
// ─── Error formatting ─────────────────────────────────────────────────────────
179+
180+
function formatApiCallError(err: APICallError): string {
181+
const status = err.statusCode ? `HTTP ${err.statusCode}` : "HTTP error";
182+
if (!err.responseBody?.trim()) return `${status}: ${err.message}`;
183+
184+
let body: string;
185+
try {
186+
body = JSON.stringify(JSON.parse(err.responseBody), null, 2);
187+
} catch {
188+
body = err.responseBody.trim();
189+
}
190+
return `${status}: ${err.message}\n${body}`;
191+
}
192+
193+
function formatError(err: unknown): string {
194+
if (RetryError.isInstance(err)) {
195+
const last = (err as RetryError).lastError;
196+
if (APICallError.isInstance(last)) return formatApiCallError(last);
197+
if (AISDKError.isInstance(last)) return `${last.name}: ${last.message}`;
198+
if (last instanceof Error) return last.message;
199+
}
200+
if (APICallError.isInstance(err)) return formatApiCallError(err);
201+
if (AISDKError.isInstance(err)) return `${err.name}: ${err.message}`;
202+
if (err instanceof Error) return err.message;
203+
return String(err);
204+
}
205+
176206
// ─── Runner ───────────────────────────────────────────────────────────────────
177207

178208
async function runTest(name: TestName, model: ReturnType<typeof createModel>): Promise<TestResult> {
@@ -195,7 +225,7 @@ async function runTest(name: TestName, model: ReturnType<typeof createModel>): P
195225
}
196226
return { name, passed: true, output, durationMs: Date.now() - start };
197227
} catch (err) {
198-
const error = err instanceof Error ? err.message : String(err);
228+
const error = formatError(err);
199229
return { name, passed: false, error, durationMs: Date.now() - start };
200230
}
201231
}
@@ -230,7 +260,8 @@ for (const testName of config.tests) {
230260
if (result.output) console.log(` ${DIM}${result.output}${RESET}`);
231261
} else {
232262
console.log(`${RED}✗ failed${RESET} ${DIM}(${result.durationMs}ms)${RESET}`);
233-
console.log(` ${RED}${result.error}${RESET}`);
263+
const indented = result.error!.split("\n").map((l) => ` ${l}`).join("\n");
264+
console.log(`${RED}${indented}${RESET}`);
234265
}
235266
}
236267

0 commit comments

Comments
 (0)