feat(agent-bff): map agent errors to the BFF error contract#1738
feat(agent-bff): map agent errors to the BFF error contract#1738nbouliol wants to merge 10 commits into
Conversation
1 new issue
|
|
Coverage Impact ⬆️ Merging this pull request will increase total coverage on Modified Files with Diff Coverage (2)
🛟 Help
|
Add mapAgentError() translating agent-client AgentHttpError failures into the BFF error envelope, plus the complete registry of BFF-local error factories the Slice-3 endpoints emit. Fixes PRD-670 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- unmapped agent error name now returns 500 mapping_error instead of a silent status fallback - extract the unmapped-name warn out of fallbackTypeByStatus (single responsibility) - use a named constant for the validation_error type - make the name-to-type test use an explicit table instead of deriving expectations from the map under test Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
886cec1 to
d9c89e7
Compare
…mapping - fall back to the outer AgentHttpError status when the JSON:API error object omits its own status, instead of hardcoding 400 - fall back to errors[0].message when detail is absent, before the generic default message Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dies When the agent body is not a JSON error object, use AgentHttpError.responseText as the message source before the generic default, so plain-text or empty bodies still surface the agent-supplied text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JSON:API expresses errors[0].status as a string; a string status reached BffHttpError and was rejected by isSerializableError, downgrading a mapped 4xx to a generic 500. Coerce it to a number before constructing the error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An unmapped agent error name (e.g. a ValidationError subclass like MissingCollectionError, whose name is its own class name) collapsed to mapping_error 500, losing the real status and category. Fall back to the status-derived type instead and log the name at Warn so a curated mapping can be added — never a silent 500 collapse. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A plain Error whose message embeds a JSON:API body with a 5xx status bypassed the AgentHttpError 5xx normalization and was mapped to a client-error type with a 503 status. Centralize the 5xx→agent_unavailable normalization in mapJsonApiError so both the AgentHttpError and wrapped-message paths agree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
coerceStatus trusted errors[0].status whenever finite, so a JSON:API body with a non-error status (e.g. 200) inside a 404 response produced a BffHttpError with a 2xx status — an error envelope sent to clients as HTTP 200. Ignore a body status that is not >= 400 and fall back to the enclosing status instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
A) type values are untyped string where a fixed registry exists — agent-error-mapper.ts:31,41 ▎ B) What-comments against project style — agent-error-mapper.ts:90-91, 94-95 |
…p leaking transport details - Type AGENT_ERROR_TYPE_MAP/FALLBACK_TYPE_BY_STATUS values as a BffErrorType union so a typo'd type is a compile error. - Inline the STATUS_* constants to raw HTTP literals, matching the sibling error files' local precedent. - network_error now returns a generic message and logs the real transport cause at Warn, so internal hostnames/IPs never reach the client; the non-agent branch is no longer silent. - Add coverage: flat body.message priority, non-numeric status fallback, 5xx body inside a non-5xx AgentHttpError, valid JSON without errors[], and the network_error message assertion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks @ShohanRahman — all addressed in 8c1f281 (PR description updated too). Comment 1
Comment 2 A. Untyped map values — added a Build + lint clean, 400 tests pass. |
…point Clarify that semantic agent-client action errors (form validation / approval outcomes) are not transport failures and are handled by the action endpoint before the network_error fallback, so they are not mislabelled as 502. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Context
Slice-3 BFF data/action endpoints (PRD-671..674) need one shared error layer producing the BFF envelope
{ error: { type, status, message, details? } }, so consumers branch onerror.type+error.status, never on a message substring. This ships first and is a hard dependency of every Slice-3 endpoint ticket.What this adds
src/http/agent-error-mapper.ts—mapAgentError(error, { logger }): BffHttpError:AgentHttpError) →network_error(502), with a generic message (the real cause is logged, never sent to the client, to avoid leaking internal hostnames/IPs)agent_unavailable(503, normalized — including a 5xxerrors[0].statuscarried in a non-5xx response)errors[0]→ type from an explicit name registry (NotFoundError→not_found, …); status fromerrors[0].status(ignored if not>= 400, falling back to the outerAgentHttpError.status);messagefromdetail(thenmessage);detailsfromdataerrors[0].status) + aWarnlog (an unrecognized name keeps the agent's real status/category instead of collapsing to a 500; the log surfaces it so an explicit registry entry can be added)Errorwhose message is a JSON{ errors: [...] }stringsrc/http/bff-local-errors.ts— the complete registry of BFF-local factories the Slice-3 endpoints emit:unknown_*(404),*_not_allowed(403),invalid_request(400),relation_field_not_supported(422),mapping_error(500),unsupported_action_result(501).package.json— adds@forestadmin/agent-client(the committed BFF→agent transport;AgentHttpErroris the mapper input).Produced
BffHttpErrorinstances flow through the existingerror-middlewareunchanged. No barrel exports: in-package endpoints import relatively.Notes / deviations from the ticket text
The ticket and the global spec were stale on three points; the implementation follows the actual code:
AgentHttpError(notError(JSON.stringify(...))); the string case is kept as a defensive fallback.data, notmeta/source;detailsis sourced fromdata.NotFoundError, notNotFound), so an explicit registry replaces literal snake_case.Deliberate contract choices (unspecified by the spec):
network_error=502 /agent_unavailable=503.MissingCollectionError→invalid_request400), never collapsing to a generic 500, and is logged atWarnso a curated registry entry can be added. This preserves the agent's real status and category for unrecognized names.errors[0].statusis ignored in favour of the enclosing status.Tests
test/http/agent-error-mapper.test.ts— registry coverage, JSON-in-message parse, outer-status fallback, non-numeric status fallback, message/body.messagefallback, transport→502 (generic message + logged cause), 5xx→503 (both paths), non-error status ignored, unmapped-name→status-derived+Warnlog.test/http/bff-local-errors.test.ts— every factory + details.Full suite: 400/400 pass; build + lint clean.
Fixes PRD-670
🤖 Generated with Claude Code
Note
Map agent errors to the BFF error contract in agent-bff
mapAgentErrorto translate thrown agent errors intoBffHttpErrorobjects: transport failures become 502network_error, agent 5xx become 503agent_unavailable, JSON:API errors map to typed BFF errors, and flat bodies fall back to status-derived types.bff-local-errors.tswith factory functions for BFF-local errors (e.g.unknownCollection,invalidRequest,mappingError) using predefined types and status codes.@forestadmin/agent-clientas a runtime dependency forAgentHttpErrordetection.Macroscope summarized fc1d08e.