Milestones 2.1.2 and 2.1.3 implement OpenAI HTTP calls, but provider failures must surface to clients as the stable IBEX error envelope defined in Phase 1 (M1.2.3), not raw OpenAI JSON or opaque 502s. Without a single mapping layer: - OpenAI 429 responses may omit `Retry-After` in the IBEX envelope.
Milestone 2.1.5 — Provider Error Mapping and Stable Envelope
Status: Completed
Goal: 2.1 — LLM provider abstraction and OpenAI forwarding
Phase: 2 — Single Provider End-to-End
Estimated effort: 1–2 days
ADR required: None (extends ADR-0013 envelope and ADR-0026 client policy)
Why This Milestone Exists
Milestones 2.1.2 and 2.1.3 implement OpenAI HTTP calls, but provider failures must surface to clients as the stable IBEX error envelope defined in Phase 1 (M1.2.3), not raw OpenAI JSON or opaque 502s. Without a single mapping layer:
- OpenAI 429 responses may omit
Retry-Afterin the IBEX envelope. - OpenAI 401 (invalid API key) could leak as 401 to the caller, implying the client's PAT is wrong.
- Transient 503s from OpenAI may be retried by the client when the proxy already exhausted retries.
This milestone centralises provider → IBEX error translation in packages/provider (MapProviderError / MapError) used by both streaming and non-streaming paths.
Non-Goals
- Mapping errors from non-OpenAI providers (Phase 4) — same
ProviderErrorpath applies when adapters register - Custom per-org error messages (operator UI — Phase 3)
- Retrying after the response has started streaming to the client
- Inventing mid-stream SSE error frames (contradicts ADR-0027 verbatim forward)
Branch
feature/m2-1-5-provider-error-mapping
PR Title
feat(proxy): centralise provider error mapping to stable envelope (m2.1.5)
Prerequisites
- 2.1.2 merged (non-streaming client exists)
- 2.1.3 merged (streaming path exists)
- 1.2.3 merged (error envelope)
Deliverables
1. MapProviderError / MapError
type MapInput struct {
ProviderName string
StatusCode int
RetryAfter time.Duration
TransportErr error
SafeMessage string
}
func MapProviderError(in MapInput) *apierror.Error
func MapError(err error) (mapped *apierror.Error, write bool)apierror.WriteHTTP applies the envelope and Retry-After. Never exposes provider API keys or raw ProviderBody in the returned error. ProviderName is for structured logs only.
2. Status code mapping table (canonical)
| Provider status / condition | IBEX code | HTTP status | Notes |
|---|---|---|---|
| 400 | INVALID_REQUEST | 400 | Sanitized safe provider message subset |
| 401, 403, 404, other 4xx (except 429) | PROVIDER_UNAVAILABLE | 503 | Bad provider key — not client PAT |
| 429 | RATE_LIMITED | 429 | Forward Retry-After when present |
| 500, 502, 503, 504 | PROVIDER_UNAVAILABLE | 503 | After client retries exhausted |
| Client timeout | PROVIDER_TIMEOUT | 504 | |
| Network / TLS error | PROVIDER_UNAVAILABLE | 503 |
3. Streaming behaviour
- If provider returns non-2xx before first byte to client: map via
MapErrorand return JSON envelope (no SSE). - If error occurs after streaming started: graceful SSE close only — no JSON swap, no injected
data: [DONE]or terminal error event (ADR-0027 verbatim dual-write; accumulatorComplete()==false).
4. Wire into handlers
- Non-streaming handler uses
MapErrorexclusively — no inline status switches. - Streaming forwarder delegates to the same mapper for pre-stream failures.
Testing Requirements
TestMapProviderError_OpenAI429: 429 + Retry-After 30s →RATE_LIMITEDTestMapProviderError_OpenAI401: 401 →PROVIDER_UNAVAILABLE/ 503 (not 401)TestMapProviderError_Timeout: context deadline →PROVIDER_TIMEOUT/ 504TestUnit_Streaming_PreStreamError: provider 503 before bytes → JSON envelope, not SSETestUnit_Streaming_ProviderErrorMidStream: mid-stream failure → partial SSE, no[DONE]
Acceptance Criteria
- All provider errors from OpenAI client paths use
MapError/MapProviderError - No provider API key or response content in logs or client payloads
- OpenAI 429 returns IBEX
RATE_LIMITEDwithRetry-Afterwhen header present - OpenAI 401 returns 503
PROVIDER_UNAVAILABLE(client PAT semantics unchanged) - Unit tests cover full mapping table
Risks
| Risk | Likelihood | Mitigation |
|---|---|---|
| OpenAI error JSON shape changes | Medium | Map by HTTP status first; sanitize short message only |
| Double-retry storms (proxy + client) | Medium | Document that proxy retries are exhaustive for 429/5xx |
| Mid-stream errors confuse SDK clients | Low | Follow ADR-0027 — no invented SSE error frames |
Last updated on