refactor(aibridge): clean up keypool and provider error handling (#25609)

## Description

Cleans up how key pool errors are represented and how they get turned into HTTP responses. Consolidates two error types into a single type with a kind tag, and gives the response helpers in both providers consistent names.

## Changes

- Replaced the keypool sentinel and transient error struct with one error type that carries a kind and a retry-after duration.
- Updated `KeyFailoverConfig.BuildKeyPoolResponse` to take the typed key pool error, so each provider can shape the exhaustion response in its own format.
- Removed the per-provider `MarkKey` callback from `KeyFailoverConfig` since providers can rely on the shared `MarkKeyOnStatus` helper.
- Renamed the response-error helpers so OpenAI and Anthropic use the same naming.

Related to: https://linear.app/codercom/issue/AIGOV-334/aibridge-follow-ups-from-key-failover-prs

> [!NOTE]
> Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira
This commit is contained in:
Susana Ferreira
2026-05-25 18:58:29 +01:00
committed by GitHub
parent 5d178ada9f
commit 22109a54ad
20 changed files with 428 additions and 522 deletions
+7 -10
View File
@@ -1,7 +1,6 @@
package provider
import (
"context"
"fmt"
"io"
"net/http"
@@ -173,8 +172,8 @@ func (p *Anthropic) CreateInterceptor(_ http.ResponseWriter, r *http.Request, tr
// Centralized: use the first key as a placeholder hint.
// TODO(ssncferreira): record the actually-used key in
// the interception record to reflect failover.
if k, err := cfg.KeyPool.Walker().Next(); err == nil {
credSecret = k.Value()
if key, keyPoolErr := cfg.KeyPool.Walker().Next(); keyPoolErr == nil {
credSecret = key.Value()
}
}
@@ -222,20 +221,18 @@ func (p *Anthropic) InjectAuthHeader(headers *http.Header) {
}
func (p *Anthropic) KeyFailoverConfig(logger slog.Logger) keypool.KeyFailoverConfig {
name := p.Name()
return keypool.KeyFailoverConfig{
Pool: p.cfg.KeyPool,
Pool: p.cfg.KeyPool,
ProviderName: p.Name(),
Logger: logger,
IsBYOK: func(r *http.Request) bool {
return r.Header.Get("X-Api-Key") != "" || r.Header.Get("Authorization") != ""
},
InjectAuthKey: func(h *http.Header, key string) {
h.Set("X-Api-Key", key)
},
MarkKey: func(ctx context.Context, key *keypool.Key, resp *http.Response) bool {
return keypool.MarkKeyOnStatus(ctx, key, resp, logger, name)
},
BuildExhaustedResponse: func(err error) *http.Response {
return messages.ProcessKeyPoolError(err).ToResponse()
BuildKeyPoolResponse: func(keyPoolErr *keypool.Error) *http.Response {
return messages.ResponseErrorFromKeyPool(keyPoolErr).ToResponse()
},
}
}
+7 -10
View File
@@ -1,7 +1,6 @@
package provider
import (
"context"
"encoding/json"
"fmt"
"io"
@@ -146,8 +145,8 @@ func (p *OpenAI) CreateInterceptor(_ http.ResponseWriter, r *http.Request, trace
// Centralized: use the first key as a placeholder hint.
// TODO(ssncferreira): record the actually-used key in
// the interception record to reflect failover.
if k, err := cfg.KeyPool.Walker().Next(); err == nil {
credSecret = k.Value()
if key, keyPoolErr := cfg.KeyPool.Walker().Next(); keyPoolErr == nil {
credSecret = key.Value()
}
}
cred := intercept.NewCredentialInfo(credKind, credSecret)
@@ -221,20 +220,18 @@ func (p *OpenAI) InjectAuthHeader(headers *http.Header) {
}
func (p *OpenAI) KeyFailoverConfig(logger slog.Logger) keypool.KeyFailoverConfig {
name := p.Name()
return keypool.KeyFailoverConfig{
Pool: p.cfg.KeyPool,
Pool: p.cfg.KeyPool,
ProviderName: p.Name(),
Logger: logger,
IsBYOK: func(r *http.Request) bool {
return r.Header.Get("Authorization") != ""
},
InjectAuthKey: func(h *http.Header, key string) {
h.Set("Authorization", "Bearer "+key)
},
MarkKey: func(ctx context.Context, key *keypool.Key, resp *http.Response) bool {
return keypool.MarkKeyOnStatus(ctx, key, resp, logger, name)
},
BuildExhaustedResponse: func(err error) *http.Response {
return chatcompletions.ProcessKeyPoolError(err).ToResponse()
BuildKeyPoolResponse: func(keyPoolErr *keypool.Error) *http.Response {
return intercept.ResponseErrorFromKeyPool(keyPoolErr).ToResponse()
},
}
}