feat(error): decode the provider's real reason from the structured payload#1469
feat(error): decode the provider's real reason from the structured payload#1469Astro-Han wants to merge 1 commit into
Conversation
…yload The session error card and the toast formatter each sniffed JSON out of a display string with their own brace-hunting (`unwrap` in session-turn, ad-hoc parsing in `formatServerError`). That loses the provider's real reason: a DeepSeek 402 surfaced as a generic message instead of "Insufficient Balance". Add one shared decoder, `@opencode-ai/ui/util/server-error`'s `decodeServerErrorText`, that reads the structured response body (real JSON, via `JSON.parse` — no brace-hunting) and falls back to the already -extracted message. Route both the session-turn error card and the app toast formatter through it, and delete the old `unwrap`/`record` helpers. This is the frontend decode foundation. Per-kind copy and card visuals are the design-gated follow-up and are intentionally not here — the decoder turns a payload into the best plain text already present, inventing none. The provider's reason is passthrough, not localized: the i18n parity test now asserts a provider error renders verbatim in every locale (not mapped to a translation key) rather than only checking zh-vs-en key alignment. Verification: - bun test: ui server-error (6) + app parity/server-errors (11) green - tsgo --noEmit clean in packages/ui and packages/app - eslint clean on changed src files - new focused snap target `error-card-decode`: real `Card variant="error"` + real decoder over representative payloads; grid reviewed — decoded reasons (incl. a `type:` prefix and a long multi-line reason) wrap cleanly in the error card, no overflow Claude-Session: https://claude.ai/code/session_015bW9JQSkuB156gkNQdxCzi
|
Warning Review limit reached
More reviews will be available in 32 minutes and 35 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Suggested priority: P2 (includes user-path files (packages/app/src/i18n/parity.test.ts, packages/app/src/utils/server-errors.ts)).
P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.
Summary
Add one shared frontend decoder —
@opencode-ai/ui/util/server-error'sdecodeServerErrorText— that turns a server / assistant error payload into the best plain text already present on it: it reads the structured response body (real JSON, viaJSON.parse) for the provider's real reason, then falls back to the already-extracted message. Both the session-turn error card and the app toast formatter (formatServerError) now route through it, and the old per-site brace-hunting (unwrap/recordinsession-turn.tsx) is deleted.This is the frontend decode foundation of the classify → passthrough → render chain. Per-kind copy and card visuals are the design-gated follow-up and are intentionally not here — the decoder invents no copy.
Why
A DeepSeek direct 402 ("Insufficient Balance") was shown to users as a generic message because two display sites each sniffed JSON out of a display string with their own brittle brace-hunting, losing the provider's real reason. Consolidating to one structured decoder lets the UI surface the real reason once the backend passes it through (the classification + passthrough land in the sibling PRs of this chain). It also removes a duplicated, fragile parsing path.
The provider's reason is passthrough, not localized copy. The i18n parity test is upgraded accordingly: instead of only checking zh-vs-en key alignment, it now asserts a provider error renders verbatim in every locale (never mapped to a translation key).
Related Issue
Part of #1123 (classify-then-render umbrella); classification spine #1105. This PR is the frontend render piece; the backend classification/passthrough are the sibling PRs (#1466, #1468).
Human Review Status
Pending
Review Focus
decodeServerErrorTextdecode order: structuredresponseBody→fromStructuredBody(message)→ rawmessage→undefined. Confirm a clean message still passes through verbatim and a non-payload value returnsundefinedso callers keep their own fallback.session-turn.tsx: theerrorTextmemo now delegates to the decoder;unwrap/recordare removed. TheString(msg)branch is only reachable for a non-stringmessage(the decoder already handles strings).formatServerError: the decoder is consulted after the typed-error checks (ConfigInvalid / ProviderModelNotFound) and before theError/string fallbacks, so typed errors keep their tailored copy.Risk Notes
devand does not include it. The decoder is therefore exercised by unit tests and a fixture-mounted snap with synthetic post-passthrough payloads.Card); no Electron/IPC/platform/packaging surface touched.docs/, not committed).How To Verify
Screenshots or Recordings
Visible surface: the session-turn error card (
Card variant="error" class="error-card"). Checked via the new focused snap targeterror-card-decode(packages/app/e2e/snap/error-card-decode.snap.ts+ fixture), which mounts the realCard+ real decoder over five representative payloads. Grid reviewed locally atdocs/design/preview/screenshots/error-card-decode.png(local-only artifact): the402 structured bodycard showsunknown_error: Insufficient Balance,json-in-messageshowsrate limited(no JSON blob), and the long reason wraps across three lines with the error accent bar intact.Checklist
bug,enhancement,task,documentation.app,ui,platform,harness,ci.P0,P1,P2,P3.Pending,Approved by @<reviewer>, orNot required: <reason>.dev, and my PR title and commit messages use Conventional Commits in English.https://claude.ai/code/session_015bW9JQSkuB156gkNQdxCzi