Commit Graph
4 Commits
Author SHA1 Message Date
Go Expert Coding agent 3d44b7cd40 refactor: use typed StatusError for retry metadata, providers wrap errors at Recv()
Addresses review feedback from rumpl and simonferquel on PR #2096.

The previous approach extracted Retry-After metadata in a dispatcher function
that type-switched on provider SDK error types (`*anthropic.Error`,
`*openai.Error`). This kept all providers coupled to the modelerrors package
and duplicated parsing logic.

New design:

1. `modelerrors.StatusError` — a thin typed wrapper carrying:
   - StatusCode int
   - RetryAfter time.Duration  (parsed from Retry-After header)
   - Err error                 (original SDK error, accessible via Unwrap)

2. `modelerrors.WrapHTTPError(statusCode, resp, err)` — wraps any provider
   error in StatusError; parses Retry-After from resp.Header if non-nil.

3. `modelerrors.ParseRetryAfterHeader(value)` — shared RFC 7231 §7.1.3
   parser (integer seconds + HTTP-date). No SDK imports.

4. `modelerrors.ClassifyModelError(err)` — single-arg; uses errors.As to
   find *StatusError in the chain; falls back to message/pattern matching for
   providers not yet wrapped (e.g. Bedrock).

5. Three new wrap.go files (one per provider family), each a single function:
   - pkg/model/provider/anthropic/wrap.go  — wrapAnthropicError(*anthropic.Error)
   - pkg/model/provider/oaistream/wrap.go  — WrapOpenAIError(*openai.Error) (exported)
   - pkg/model/provider/gemini/wrap.go     — wrapGeminiError(*genai.APIError)

6. Wrap sites — one-line change at Recv() in each adapter:
   - anthropic/adapter.go, beta_adapter.go
   - oaistream/adapter.go
   - openai/response_stream.go (reuses oaistream.WrapOpenAIError)
   - gemini/adapter.go

No changes to fallback.go logic — ClassifyModelError signature stays
`(err error)` and retry/rate-limit decisions are unchanged.

Assisted-By: docker-agent
2026-03-13 11:07:50 +00:00
Go Expert Coding agent 7c22ff16f9 fix: make HTTP 429 retryable when no fallback model, respect Retry-After header
When a model returns HTTP 429 (Too Many Requests):
- If fallback models are configured: skip to next fallback immediately (existing behavior preserved)
- If no fallbacks are configured: retry the same model using the Retry-After header duration (capped at 60s), falling back to exponential backoff if the header is absent

HTTP 500 was already handled as retryable via IsRetryableStatusCode — no change needed.

New additions to pkg/modelerrors:
- ExtractRetryAfter(err): extracts Retry-After duration from *anthropic.Error and *openai.Error responses
- parseRetryAfterHeader(value): parses integer-seconds or HTTP-date Retry-After values
- ClassifyModelError(err): unified classifier returning (retryable, rateLimited bool, retryAfter time.Duration)
- MaxRetryAfterWait constant (60s cap)

pkg/runtime/fallback.go changes:
- Replace both IsRetryableModelError call sites with ClassifyModelError
- Add handleModelError helper to avoid duplicating classification logic
- 429 with fallbacks → break to next model (unchanged behavior)
- 429 without fallbacks → sleep(min(retryAfter, 60s) or backoff), then continue

Fixes #2095

Assisted-By: docker-agent
2026-03-13 10:17:35 +00:00
David Gageot 943d3b0606 This can be retried
Signed-off-by: David Gageot <david.gageot@docker.com>
2026-03-12 17:17:54 +01:00
David Gageot 6068592017 refactor: split runtime.go and extract pkg/modelerrors
Break up the oversized runtime.go (2510 lines) by extracting cohesive
groups of functions into dedicated files, and move pure error
classification utilities into a new reusable package.

New files in pkg/runtime:
- model_picker.go: model change/revert tool handlers
- agent_delegation.go: sub-agent dispatch, task transfer, handoffs

New package pkg/modelerrors:
- ContextOverflowError type and detection
- HTTP status code extraction (Anthropic, Gemini, OpenAI)
- Retryable error classification
- Exponential backoff with jitter
- User-friendly error formatting

Assisted-By: docker-agent
2026-03-09 17:32:50 +01:00