mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
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
154 lines
4.0 KiB
Go
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")
|
|
}
|