Skip to content

feat(errors): expose upstream provider errors as structured JSON - #489

Open
Destynova2 wants to merge 1 commit into
mainfrom
fix/structured-upstream-error
Open

feat(errors): expose upstream provider errors as structured JSON#489
Destynova2 wants to merge 1 commit into
mainfrom
fix/structured-upstream-error

Conversation

@Destynova2

Copy link
Copy Markdown
Contributor

Fidelity gap (from the competitive analysis)

The analysis flags "provider error detail lost" — Envoy turning a structured
provider error into an empty HTTP 500.

grob is already ahead: it forwards the verbatim upstream status and the
provider body (error.rs ProviderUpstream). But the body reached the client
only as a message string, often prefixed ("anthropic API error: {…}"), so a
client had to re-parse a blob to read the provider's own error.type/code.

Change

into_response now extracts the first embedded JSON object/array from the body
and attaches it as error.upstream_error, structured, alongside the existing
message / provider / upstream_status:

{
  "error": {
    "type": "error",
    "message": "anthropic API error: {…}",
    "provider": "anthropic",
    "upstream_status": 429,
    "upstream_error": { "type": "error", "error": { "type": "rate_limit_error", "message": "slow down" } }
  }
}

Additive and backward compatible — clients reading message are unaffected;
agents that branch on the provider's error type now can, without string-parsing.

Tests

  • extract_leading_json_handles_prefix_pure_and_none — prefixed body, pure JSON, trailing junk, and no-JSON.
  • provider_upstream_forwards_status_and_structured_error — 429 forwarded verbatim + parseable upstream_error.
  • cargo test --lib server::error — 42 passed.

Part of the fidelity sweep (feeds #484).

🤖 Generated with Claude Code

On an upstream failure grob already forwards the verbatim HTTP status and the
provider's body (better than gateways that flatten a structured error into an
empty 500). But the body reached the client only as a `message` string, often
prefixed ("anthropic API error: {…}"), so a client could not read the provider's
own `error.type`/`code` without re-parsing a blob.

`into_response` now extracts the first embedded JSON object/array from the body
and attaches it as `error.upstream_error`, alongside the existing string
`message`, `provider` and `upstream_status`. Additive and backward compatible —
clients reading `message` are unaffected; agents that branch on the provider's
error type now can.

Tested: `extract_leading_json` (prefix / pure / trailing-junk / none) and a 429
ProviderUpstream response asserting status 429 + parseable `upstream_error`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Destynova2
Destynova2 enabled auto-merge (squash) July 28, 2026 21:07
Destynova2 added a commit that referenced this pull request Aug 4, 2026
The concrete artifact from the fidelity sweep: a per-path ×
per-dimension table
of what grob preserves, translates, cannot carry (API-limited), or
intentionally
drops between the Anthropic canonical format and each provider wire
format.

Makes each loss explicit rather than silent — the analysis's core
critique is
that a translation loss looks like `HTTP 200`. Records this cycle's
fixes and
flags the unverified rows honestly.

### Findings

| Content | Anthropic | OpenAI Chat | Responses (Codex) | Gemini |
|---|---|---|---|---|
| Text / image (message) | native | ✅ | ✅ (#476) | ✅ |
| Tool call | native | ✅ | ✅ | ✅ |
| **Tool-result image** | native | API-limited | **✅ #488** |
API-limited (unverified) |
| Provider error | — | structured `upstream_error` (#489) | same | same
|

The Responses tool-result-image fix (#488) was verified live; the
Chat/Gemini
equivalents are API-shaped losses (string-only tool content) marked
unverified
because grob has no standing test credential for those backends.

Feeds the per-release conformance-suite work (#484).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant