Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.4] - 2026-06-05

### Added

- Added a public provider error fixture corpus for OpenAI, Anthropic, Gemini and
transport-level failures, covering SDK-like objects, parsed fetch responses
and expected normalized outputs.
- Added fixture-driven regression tests and published `fixtures/` in the npm
package.

## [0.1.3] - 2026-06-04

### Changed
Expand Down Expand Up @@ -44,4 +54,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
pair arrays.
- Zero runtime dependencies; ESM + CJS builds with type declarations.

[0.1.4]: https://github.com/slegarraga/llm-errors/releases/tag/v0.1.4
[0.1.3]: https://github.com/slegarraga/llm-errors/releases/tag/v0.1.3
[0.1.2]: https://github.com/slegarraga/llm-errors/releases/tag/v0.1.2
[0.1.1]: https://github.com/slegarraga/llm-errors/releases/tag/v0.1.1
[0.1.0]: https://github.com/slegarraga/llm-errors/releases/tag/v0.1.0
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ try {
npm install llm-errors
```

## Fixture corpus

The npm package includes a public fixture corpus under
[`fixtures/`](./fixtures/README.md). It pairs raw SDK-like, fetch-like and
transport-level provider errors with the normalized output expected from
`normalizeError`.

These fixtures are useful for downstream regression tests when you want to
verify provider-portable retry and error handling without importing OpenAI,
Anthropic or Gemini SDKs.

## API

### `normalizeError(error, options?) => NormalizedError`
Expand Down
25 changes: 25 additions & 0 deletions fixtures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Provider Error Fixtures

This corpus contains redacted, synthetic examples of provider error shapes that
`llm-errors` supports. The files are intentionally small JSON fixtures so they
can be reused by downstream test suites without importing any provider SDK.

## Layout

- `cases/` contains raw SDK-like, fetch-like and transport-level inputs.
- `expected/` contains the normalized output for the matching case path.

For example, `cases/openai/sdk-rate-limit.json` is paired with
`expected/openai/sdk-rate-limit.json`.

## Scope

The corpus covers:

- OpenAI SDK `APIError`-style objects and parsed fetch responses.
- Anthropic SDK envelopes and parsed fetch responses.
- Gemini / Google RPC error envelopes.
- Transport failures such as Node timeout codes and browser abort errors.

The fixtures are not recordings of private traffic and do not contain API keys,
request IDs, account IDs or user content.
17 changes: 17 additions & 0 deletions fixtures/cases/anthropic/fetch-context-length.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Anthropic parsed fetch response prompt too long",
"shape": "anthropic-fetch-parsed-body",
"error": {
"status": 400,
"headers": {
"anthropic-version": "2023-06-01"
},
"body": {
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "prompt is too long: 250000 tokens > 200000 maximum"
}
}
}
}
18 changes: 18 additions & 0 deletions fixtures/cases/anthropic/fetch-rate-limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "Anthropic parsed fetch response rate limit",
"shape": "anthropic-fetch-parsed-body",
"error": {
"status": 429,
"headers": {
"anthropic-ratelimit-requests-limit": "50",
"retry-after": "30"
},
"body": {
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "rate limit exceeded"
}
}
}
}
17 changes: 17 additions & 0 deletions fixtures/cases/anthropic/sdk-overloaded-529.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Anthropic SDK overloaded response",
"shape": "anthropic-sdk-apierror",
"error": {
"status": 529,
"headers": {
"anthropic-version": "2023-06-01"
},
"error": {
"type": "error",
"error": {
"type": "overloaded_error",
"message": "Overloaded"
}
}
}
}
19 changes: 19 additions & 0 deletions fixtures/cases/gemini/fetch-unavailable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Gemini parsed fetch response unavailable",
"shape": "google-rpc-fetch-parsed-body",
"error": {
"response": {
"status": 503,
"headers": {
"retry-after": "4"
},
"data": {
"error": {
"code": 503,
"message": "The model is overloaded. Please try again later.",
"status": "UNAVAILABLE"
}
}
}
}
}
11 changes: 11 additions & 0 deletions fixtures/cases/gemini/rpc-permission-denied.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "Gemini Google RPC permission denied",
"shape": "google-rpc-error-envelope",
"error": {
"error": {
"code": 403,
"message": "The caller does not have permission.",
"status": "PERMISSION_DENIED"
}
}
}
17 changes: 17 additions & 0 deletions fixtures/cases/gemini/rpc-resource-exhausted.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Gemini Google RPC resource exhausted",
"shape": "google-rpc-error-envelope",
"error": {
"error": {
"code": 429,
"message": "Resource has been exhausted.",
"status": "RESOURCE_EXHAUSTED",
"details": [
{
"@type": "type.googleapis.com/google.rpc.RetryInfo",
"retryDelay": "17s"
}
]
}
}
}
8 changes: 8 additions & 0 deletions fixtures/cases/network/abort-error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "Browser fetch abort",
"shape": "dom-abort-error",
"error": {
"name": "AbortError",
"message": "The operation was aborted."
}
}
9 changes: 9 additions & 0 deletions fixtures/cases/network/node-timeout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "Node transport timeout",
"shape": "node-error-code",
"error": {
"name": "Error",
"message": "connect ETIMEDOUT",
"code": "ETIMEDOUT"
}
}
18 changes: 18 additions & 0 deletions fixtures/cases/openai/fetch-context-length.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "OpenAI parsed fetch response context length",
"shape": "openai-fetch-parsed-body",
"error": {
"status": 400,
"headers": {
"openai-processing-ms": "42"
},
"body": {
"error": {
"message": "This model's maximum context length is 128000 tokens.",
"type": "invalid_request_error",
"code": "context_length_exceeded",
"param": "messages"
}
}
}
}
13 changes: 13 additions & 0 deletions fixtures/cases/openai/sdk-insufficient-quota.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "OpenAI SDK APIError insufficient quota",
"shape": "openai-sdk-apierror",
"error": {
"status": 429,
"error": {
"message": "You exceeded your current quota.",
"type": "insufficient_quota",
"code": "insufficient_quota",
"param": null
}
}
}
18 changes: 18 additions & 0 deletions fixtures/cases/openai/sdk-rate-limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "OpenAI SDK APIError rate limit",
"shape": "openai-sdk-apierror",
"error": {
"status": 429,
"headers": {
"openai-version": "2020-10-01",
"retry-after-ms": "1250",
"retry-after": "2"
},
"error": {
"message": "Rate limit reached for requests",
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"param": null
}
}
}
8 changes: 8 additions & 0 deletions fixtures/expected/anthropic/fetch-context-length.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"provider": "anthropic",
"category": "context_length_exceeded",
"message": "prompt is too long: 250000 tokens > 200000 maximum",
"status": 400,
"code": "invalid_request_error",
"retryable": false
}
9 changes: 9 additions & 0 deletions fixtures/expected/anthropic/fetch-rate-limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"provider": "anthropic",
"category": "rate_limit",
"message": "rate limit exceeded",
"status": 429,
"code": "rate_limit_error",
"retryable": true,
"retryAfterMs": 30000
}
8 changes: 8 additions & 0 deletions fixtures/expected/anthropic/sdk-overloaded-529.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"provider": "anthropic",
"category": "overloaded",
"message": "Overloaded",
"status": 529,
"code": "overloaded_error",
"retryable": true
}
9 changes: 9 additions & 0 deletions fixtures/expected/gemini/fetch-unavailable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"provider": "gemini",
"category": "overloaded",
"message": "The model is overloaded. Please try again later.",
"status": 503,
"code": "UNAVAILABLE",
"retryable": true,
"retryAfterMs": 4000
}
8 changes: 8 additions & 0 deletions fixtures/expected/gemini/rpc-permission-denied.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"provider": "gemini",
"category": "permission",
"message": "The caller does not have permission.",
"status": 403,
"code": "PERMISSION_DENIED",
"retryable": false
}
9 changes: 9 additions & 0 deletions fixtures/expected/gemini/rpc-resource-exhausted.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"provider": "gemini",
"category": "rate_limit",
"message": "Resource has been exhausted.",
"status": 429,
"code": "RESOURCE_EXHAUSTED",
"retryable": true,
"retryAfterMs": 17000
}
7 changes: 7 additions & 0 deletions fixtures/expected/network/abort-error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"provider": "unknown",
"category": "timeout",
"message": "The operation was aborted.",
"code": "AbortError",
"retryable": true
}
7 changes: 7 additions & 0 deletions fixtures/expected/network/node-timeout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"provider": "unknown",
"category": "timeout",
"message": "connect ETIMEDOUT",
"code": "ETIMEDOUT",
"retryable": true
}
8 changes: 8 additions & 0 deletions fixtures/expected/openai/fetch-context-length.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"provider": "openai",
"category": "context_length_exceeded",
"message": "This model's maximum context length is 128000 tokens.",
"status": 400,
"code": "context_length_exceeded",
"retryable": false
}
8 changes: 8 additions & 0 deletions fixtures/expected/openai/sdk-insufficient-quota.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"provider": "openai",
"category": "insufficient_quota",
"message": "You exceeded your current quota.",
"status": 429,
"code": "insufficient_quota",
"retryable": false
}
9 changes: 9 additions & 0 deletions fixtures/expected/openai/sdk-rate-limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"provider": "openai",
"category": "rate_limit",
"message": "Rate limit reached for requests",
"status": 429,
"code": "rate_limit_exceeded",
"retryable": true,
"retryAfterMs": 1250
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "llm-errors",
"version": "0.1.3",
"version": "0.1.4",
"description": "Normalize OpenAI, Anthropic and Gemini API errors into one shape: category, retryable flag and Retry-After delay. Zero dependencies.",
"keywords": [
"openai",
Expand Down Expand Up @@ -46,6 +46,7 @@
},
"files": [
"dist",
"fixtures",
"README.md",
"LICENSE",
"CHANGELOG.md"
Expand Down
Loading