fix(coderd/x/chatd): show correct provider and clean detail for Bedrock errors (#26338)

## Problem
Bedrock errors (routed through aibridge) showed the wrong provider
("Anthropic ...")
and a doubly-wrapped detail string instead of the clean message.

## Fix (chatd only)
- **Provider label:** thread the configured provider to error
classification via
`GenerateAssistantOptions.ErrorProvider`. Transport provider still
drives prompt
  prep, sanitization, and metric labels (unchanged).
- **Detail:** unwrap the SDK transport wrapper (`METHOD "URL": NNN
{body}`) to surface
the inner message; handles top-level `{"message":...}` and nested
`{"error":{"message":...}}`.

## Notes
- Surfacing a top-level `message` now applies to all providers
(intentional; nested wins when both present).
- The advisor path keeps the transport label; accepted as-is (it returns
`err.Error()`, not the classification).
- Fixes display in chatd only; the wrapper originates in aibridge (out
of scope here).

🤖 Generated by Coder Agents.
This commit is contained in:
Cian Johnston
2026-06-16 11:14:37 +01:00
committed by GitHub
parent 12d7ad6100
commit 21a2652343
7 changed files with 293 additions and 34 deletions
+17 -5
View File
@@ -1,6 +1,7 @@
package chatloop
import (
"cmp"
"context"
"database/sql"
"encoding/base64"
@@ -210,7 +211,13 @@ type RunOptions struct {
// GenerateAssistantOptions configures one assistant model call.
type GenerateAssistantOptions struct {
Model fantasy.LanguageModel
Model fantasy.LanguageModel
// ErrorProvider labels user-facing errors with the configured provider
// identity (e.g. "bedrock"). It differs from Model.Provider(), which
// reflects the fantasy transport client and is "anthropic" for Bedrock
// routed through aibridge. Metrics and prompt preparation keep using
// Model.Provider(). When empty, Model.Provider() is used.
ErrorProvider string
Messages []fantasy.Message
Tools []fantasy.AgentTool
ActiveTools []string
@@ -344,6 +351,11 @@ func GenerateAssistant(ctx context.Context, opts GenerateAssistantOptions) (Assi
provider := opts.Model.Provider()
modelName := opts.Model.Model()
// errorProvider labels user-facing errors with the configured provider;
// see GenerateAssistantOptions.ErrorProvider. The transport provider is
// kept for prompt preparation, Anthropic history sanitization, and the
// metric labels below.
errorProvider := cmp.Or(opts.ErrorProvider, provider)
runOpts := RunOptions{
Model: opts.Model,
Logger: opts.Logger,
@@ -382,8 +394,8 @@ func GenerateAssistant(ctx context.Context, opts GenerateAssistantOptions) (Assi
opts.Metrics,
)
if streamErr != nil {
wrappedErr := wrapProviderStreamError(provider, streamErr)
classified := chaterror.Classify(wrappedErr).WithProvider(provider)
wrappedErr := wrapProviderStreamError(errorProvider, streamErr)
classified := chaterror.Classify(wrappedErr).WithProvider(errorProvider)
if classified.Retryable {
opts.Metrics.RecordStreamRetry(provider, modelName, classified)
}
@@ -396,8 +408,8 @@ func GenerateAssistant(ctx context.Context, opts GenerateAssistantOptions) (Assi
if errors.Is(err, ErrInterrupted) {
return AssistantOutcome{}, ErrInterrupted
}
wrappedErr := wrapProviderStreamError(provider, err)
classified := chaterror.Classify(wrappedErr).WithProvider(provider)
wrappedErr := wrapProviderStreamError(errorProvider, err)
classified := chaterror.Classify(wrappedErr).WithProvider(errorProvider)
if classified.Retryable {
opts.Metrics.RecordStreamRetry(provider, modelName, classified)
}