Files
coder/cli/server_aibridge_internal_test.go
T
Cian Johnston 74d1eef834 feat: support non-Anthropic models on Bedrock Mantle (#28404)
Migrates bedrock-typed provider construction to the standalone Bedrock
provider introduced by #28420, then removes the legacy Bedrock behavior
from the Anthropic provider. Existing provider and integration tests are
updated to use the new type; legacy environment configuration preserves
the `anthropic` route name while reporting provider type `bedrock`.

Adds chatd model-ID inference for Bedrock providers: `anthropic.*`
models keep the Anthropic Messages client, while other models use the
OpenAI Responses client. BYOK header format follows the inferred wire
shape.

A credential-gated live integration test discovers the Mantle catalog
from `/v1/models` and probes every model through the bridge. The
`openai.gpt-oss-*`, `google.gemma-4-*`, and `xai.grok-*` families are
documented as currently unsupported because Mantle serves them only on
routes the prefix-based rules cannot reach.

Depends on #28420.

## Smoke tests

Validated against a local `develop.sh` server on August 22, 2026:

- Coder Agents passed with Anthropic over InvokeModel, and Anthropic,
OpenAI, and Mistral models over Mantle.
- Claude Code passed the InvokeModel and Mantle Anthropic flows.
- Crush passed all four flows, including Mantle OpenAI and third-party
Chat Completions.

Models tested: `us.anthropic.claude-sonnet-4-6`,
`anthropic.claude-sonnet-5`, `openai.gpt-5.6-luna`, and
`mistral.ministral-3-14b-instruct`.

> Generated by Coder Agents
2026-09-09 18:28:57 +01:00

154 lines
4.0 KiB
Go

package cli
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/aibridge"
"github.com/coder/coder/v2/coderd/aibridged/proto"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
func TestBuildProviderFromProtoSetsAPIDumpDir(t *testing.T) {
t.Parallel()
const dumpDir = "/tmp/coder-aibridge-dumps"
tests := []struct {
name string
provider *proto.AIProvider
expectedType string
}{
{
name: "OpenAI",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeOpenai),
Name: "openai",
BaseUrl: "https://api.openai.com/",
},
expectedType: aibridge.ProviderOpenAI,
},
{
name: "Anthropic",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeAnthropic),
Name: "anthropic",
BaseUrl: "https://api.anthropic.com/",
},
expectedType: aibridge.ProviderAnthropic,
},
{
name: "Copilot",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeCopilot),
Name: "copilot",
BaseUrl: "https://api.githubcopilot.com/",
},
expectedType: aibridge.ProviderCopilot,
},
{
name: "Azure",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeAzure),
Name: "azure",
BaseUrl: "https://example.openai.azure.com/",
},
expectedType: aibridge.ProviderOpenAI,
},
{
name: "Google",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeGoogle),
Name: "google",
BaseUrl: "https://generativelanguage.googleapis.com/v1beta/openai/",
},
expectedType: aibridge.ProviderOpenAI,
},
{
name: "OpenAICompat",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeOpenaiCompat),
Name: "openai-compat",
BaseUrl: "https://compat.example.com/v1/",
},
expectedType: aibridge.ProviderOpenAI,
},
{
name: "OpenRouter",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeOpenrouter),
Name: "openrouter",
BaseUrl: "https://openrouter.ai/api/v1/",
},
expectedType: aibridge.ProviderOpenAI,
},
{
name: "Vercel",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeVercel),
Name: "vercel",
BaseUrl: "https://api.v0.dev/v1/",
},
expectedType: aibridge.ProviderOpenAI,
},
{
name: "Bedrock",
provider: &proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeBedrock),
Name: "bedrock",
BaseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com/",
Bedrock: &proto.AIProviderKindBedrock{
Region: "us-east-1",
AccessKey: "AKID",
AccessKeySecret: "secret",
Model: "anthropic.claude-3-5-sonnet-20241022-v2:0",
SmallFastModel: "anthropic.claude-3-5-haiku-20241022-v1:0",
},
},
expectedType: aibridge.ProviderBedrock,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
provider, err := buildProvider(t.Context(), protoToProviderSpec(tt.provider), codersdk.AIBridgeConfig{
AllowBYOK: serpent.Bool(true),
APIDumpDir: serpent.String(dumpDir),
}, nil)
require.NoError(t, err)
assert.Equal(t, dumpDir, provider.APIDumpDir())
assert.Equal(t, tt.expectedType, provider.Type())
})
}
}
func TestBuildProviderFromProtoBedrockWithoutSettings(t *testing.T) {
t.Parallel()
_, err := buildProvider(t.Context(), protoToProviderSpec(&proto.AIProvider{
Enabled: true,
Type: string(database.AIProviderTypeBedrock),
Name: "bedrock-no-settings",
BaseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com/",
}), codersdk.AIBridgeConfig{
AllowBYOK: serpent.Bool(true),
}, nil)
require.Error(t, err)
assert.Contains(t, err.Error(), "bedrock provider has no bedrock credentials configured")
}