SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
13 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
3.5 KB · 61 lines typescript
Raw Blame History
1// Robust Anthropic error handling (TypeScript SDK): typed errors, retry matrix, request ids, streaming errors.2// STATUS: LIVE_VERIFIED 2026-09-18 (deliberate 404 + 400 on claude-haiku-4-5-20251001; nothing billed).3// Run: node --env-file=.env --experimental-strip-types examples/shared/errors/anthropic_error_handling.ts4import Anthropic from "@anthropic-ai/sdk";5import { appendFileSync } from "node:fs";67const client = new Anthropic({ maxRetries: 0, timeout: 60_000 }); // production: keep default maxRetries 2 (408/409/429/5xx + connection errors, honors retry-after)8const MODEL = "claude-haiku-4-5-20251001";9const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));10const log = (status: number, note: string) =>11  appendFileSync("reports/live-requests.jsonl", JSON.stringify({ ts: new Date().toISOString().replace(/\.\d+Z$/, "Z"), provider: "anthropic", method: "POST", path: "/v1/messages", status, est_cost_usd: 0, note }) + "\n");1213function isFatal(e: Anthropic.APIError): boolean {14  return e instanceof Anthropic.BadRequestError || e instanceof Anthropic.AuthenticationError || e instanceof Anthropic.PermissionDeniedError ||15    e instanceof Anthropic.NotFoundError || e instanceof Anthropic.UnprocessableEntityError;16}1718async function withBackoff<T>(fn: () => Promise<T>, attempts = 4, base = 500, cap = 8000): Promise<T> {19  for (let i = 0; ; i++) {20    try {21      return await fn();22    } catch (e) {23      if (!(e instanceof Anthropic.APIError)) throw e;24      const shouldRetry = e.headers?.get?.("x-should-retry");          // live: "false" on every 4xx25      const retryAfter = e.headers?.get?.("retry-after");26      if (isFatal(e) || shouldRetry === "false" || i === attempts - 1) throw e;27      const delay = retryAfter ? Number(retryAfter) * 1000 : Math.min(cap, base * 2 ** i) * (1 + Math.random() * 0.25);28      console.log(`  retryable ${e.status} ${e.name} (request-id ${e.requestID}); sleeping ${Math.round(delay)}ms`);29      await sleep(delay);30    }31  }32}3334async function demo(label: string, fn: () => Promise<unknown>) {35  try {36    console.log(`${label}: ok ->`, await fn());37  } catch (e) {38    if (e instanceof Anthropic.APIError) {39      const err = (e.error as any)?.error ?? {};40      console.log(`${label}: HTTP ${e.status} ${e.constructor.name} type=${err.type} msg=${JSON.stringify(err.message)} request_id=${e.requestID}`);41      log(e.status ?? 0, `error-handling demo ${label}`);42    } else if (e instanceof Anthropic.APIConnectionError) {43      console.log(`${label}: connection problem`, e.name, e.message);44    } else throw e;45  }46}4748await demo("404 unknown model", () => client.messages.create({ model: "claude-does-not-exist", max_tokens: 8, messages: [{ role: "user", content: "Reply with OK." }] }));49await demo("400 bad temperature", () => client.messages.create({ model: MODEL, max_tokens: 8, temperature: 5, messages: [{ role: "user", content: "Reply with OK." }] }));5051// Streaming: `error` events after HTTP 200 surface as thrown APIError / 'error' event on MessageStream.52try {53  const stream = client.messages.stream({ model: MODEL, max_tokens: 8, messages: [{ role: "user", content: "Reply with OK." }] });54  stream.on("error", (err) => console.log("stream error event:", err.name));55  const final = await stream.finalMessage();56  console.log("stream ok:", final.content[0]?.type === "text" ? final.content[0].text.trim() : "", "| stop_reason:", final.stop_reason);57  log(200, "error-handling demo stream");58} catch (e) {59  console.log("stream failed:", (e as Error).name, (e as Error).message);60}61