feat: record and expose terminal upstream interception errors (#26961)

Categorises the terminal error of a failed interception and persists it
on the interception record, then surfaces it on the AI Gateway API.

- Categorise into an enum (`bad_request`, `unauthorized`,
  `rate_limited`, `overloaded`, `server_error`, `unknown`), unwrapping
  the ResponseError envelope, the upstream Anthropic/OpenAI SDK errors,
  and key-pool exhaustion so blocking and streaming paths agree.
- Thread the type and raw message through the recorder dRPC into the
  `aibridge_interceptions` row (optional proto fields; NULL on success).
- Expose the error on the AI Gateway thread API from the root
  interception.

*This PR was produced by opencode (agent) using the `anthropic/claude-opus-4-8` model, under human direction and review.*
This commit is contained in:
Danny Kopping
2026-07-09 15:36:56 +02:00
committed by GitHub
parent 63497ee9d8
commit ef0b5585d5
34 changed files with 1212 additions and 322 deletions
+18 -6
View File
@@ -2402,21 +2402,33 @@ UPDATE aibridge_interceptions
credential_hint = CASE
WHEN credential_kind = 'centralized' THEN $2::text
ELSE credential_hint
END
END,
-- Terminal upstream error, only set when the interception failed.
-- NULL leaves the columns empty for successful interceptions.
error_type = $3::aibridge_interception_error_type,
error_message = $4::text
WHERE
id = $3::uuid
id = $5::uuid
AND ended_at IS NULL
RETURNING id, initiator_id, provider, model, started_at, metadata, ended_at, api_key_id, client, thread_parent_id, thread_root_id, client_session_id, session_id, provider_name, credential_kind, credential_hint, agent_firewall_session_id, agent_firewall_sequence_number, error_type, error_message
`
type UpdateAIBridgeInterceptionEndedParams struct {
EndedAt time.Time `db:"ended_at" json:"ended_at"`
CredentialHint string `db:"credential_hint" json:"credential_hint"`
ID uuid.UUID `db:"id" json:"id"`
EndedAt time.Time `db:"ended_at" json:"ended_at"`
CredentialHint string `db:"credential_hint" json:"credential_hint"`
ErrorType NullAIBridgeInterceptionErrorType `db:"error_type" json:"error_type"`
ErrorMessage sql.NullString `db:"error_message" json:"error_message"`
ID uuid.UUID `db:"id" json:"id"`
}
func (q *sqlQuerier) UpdateAIBridgeInterceptionEnded(ctx context.Context, arg UpdateAIBridgeInterceptionEndedParams) (AIBridgeInterception, error) {
row := q.db.QueryRowContext(ctx, updateAIBridgeInterceptionEnded, arg.EndedAt, arg.CredentialHint, arg.ID)
row := q.db.QueryRowContext(ctx, updateAIBridgeInterceptionEnded,
arg.EndedAt,
arg.CredentialHint,
arg.ErrorType,
arg.ErrorMessage,
arg.ID,
)
var i AIBridgeInterception
err := row.Scan(
&i.ID,