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
+53
View File
@@ -11037,6 +11037,59 @@ func TestUpdateAIBridgeInterceptionEnded(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "sk-u...byok", updated.CredentialHint)
})
t.Run("ErrorRecorded", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
user := dbgen.User(t, db, database.User{})
intc, err := db.InsertAIBridgeInterception(ctx, database.InsertAIBridgeInterceptionParams{
ID: uuid.New(),
InitiatorID: user.ID,
Metadata: json.RawMessage("{}"),
CredentialKind: database.CredentialKindCentralized,
})
require.NoError(t, err)
require.False(t, intc.ErrorType.Valid)
require.False(t, intc.ErrorMessage.Valid)
updated, err := db.UpdateAIBridgeInterceptionEnded(ctx, database.UpdateAIBridgeInterceptionEndedParams{
ID: intc.ID,
EndedAt: time.Now(),
ErrorType: database.NullAIBridgeInterceptionErrorType{
AIBridgeInterceptionErrorType: database.AibridgeInterceptionErrorTypeOverloaded,
Valid: true,
},
ErrorMessage: sql.NullString{String: "upstream overloaded", Valid: true},
})
require.NoError(t, err)
require.True(t, updated.ErrorType.Valid)
require.Equal(t, database.AibridgeInterceptionErrorTypeOverloaded, updated.ErrorType.AIBridgeInterceptionErrorType)
require.True(t, updated.ErrorMessage.Valid)
require.Equal(t, "upstream overloaded", updated.ErrorMessage.String)
})
t.Run("NoErrorLeavesColumnsNull", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
user := dbgen.User(t, db, database.User{})
intc, err := db.InsertAIBridgeInterception(ctx, database.InsertAIBridgeInterceptionParams{
ID: uuid.New(),
InitiatorID: user.ID,
Metadata: json.RawMessage("{}"),
CredentialKind: database.CredentialKindCentralized,
})
require.NoError(t, err)
updated, err := db.UpdateAIBridgeInterceptionEnded(ctx, database.UpdateAIBridgeInterceptionEndedParams{
ID: intc.ID,
EndedAt: time.Now(),
})
require.NoError(t, err)
require.False(t, updated.ErrorType.Valid)
require.False(t, updated.ErrorMessage.Valid)
})
}
func TestAIBridgeInterceptionAgentFirewallColumns(t *testing.T) {