From f589346ca6efe69ffca2dd78cf6b25fc12f08f81 Mon Sep 17 00:00:00 2001 From: Chris Owen Date: Mon, 11 May 2026 12:28:26 -0400 Subject: [PATCH] Fix OpenCode Bedrock provider regression In v0.0.7 the OpenCode config writer started emitting the selected Aperture provider ID for every backend. That changed Bedrock from the built-in OpenCode provider ID used in v0.0.6 (amazon-bedrock) to a generic custom provider entry. OpenCode wires its Bedrock-specific handling to the built-in amazon-bedrock provider ID. When aperture-cli stopped using that ID, Bedrock launches no longer followed the same path as v0.0.6 and requests could fail with invalid security token errors when using OpenCode through Aperture. Restore the built-in amazon-bedrock provider ID for Bedrock configs only, keep the model names aligned with that provider, and update the OpenCode config test coverage to assert the provider ID and model naming behavior explicitly. Verified with go test ./internal/clients/opencode. --- internal/clients/opencode/opencode_test.go | 18 ++++++++++----- internal/clients/opencode/sdk.go | 26 ++++++++++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/internal/clients/opencode/opencode_test.go b/internal/clients/opencode/opencode_test.go index d078b4f..f334f67 100644 --- a/internal/clients/opencode/opencode_test.go +++ b/internal/clients/opencode/opencode_test.go @@ -66,6 +66,7 @@ func TestWriteProviderConfig(t *testing.T) { tests := []struct { name string provider config.ProviderInfo + wantID string wantNPM string wantOptions map[string]string }{ @@ -76,6 +77,7 @@ func TestWriteProviderConfig(t *testing.T) { Models: []string{"claude-sonnet-4-5", "claude-haiku-4-5"}, Compatibility: map[string]bool{"anthropic_messages": true}, }, + wantID: "anthropic", wantNPM: "@ai-sdk/anthropic", wantOptions: map[string]string{ "baseURL": testHost + "/v1", @@ -89,6 +91,7 @@ func TestWriteProviderConfig(t *testing.T) { Models: []string{"us.anthropic.claude-opus-4-7"}, Compatibility: map[string]bool{"bedrock_converse": true}, }, + wantID: "amazon-bedrock", wantNPM: "@ai-sdk/amazon-bedrock", wantOptions: map[string]string{ "region": "us-east-1", @@ -105,6 +108,7 @@ func TestWriteProviderConfig(t *testing.T) { "google_raw_predict": true, }, }, + wantID: "vertex", wantNPM: "@ai-sdk/google-vertex", wantOptions: map[string]string{ "apiKey": "not-required", @@ -121,6 +125,7 @@ func TestWriteProviderConfig(t *testing.T) { "openai_responses": true, }, }, + wantID: "openai", wantNPM: "@ai-sdk/openai", wantOptions: map[string]string{ "baseURL": testHost + "/v1", @@ -134,6 +139,7 @@ func TestWriteProviderConfig(t *testing.T) { Models: []string{"qwen/qwen3-235b-a22b-2507"}, Compatibility: map[string]bool{"openai_chat": true}, }, + wantID: "openrouter", wantNPM: "@ai-sdk/openai-compatible", wantOptions: map[string]string{ "baseURL": testHost + "/v1", @@ -165,9 +171,9 @@ func TestWriteProviderConfig(t *testing.T) { if err := json.Unmarshal(data, &cfg); err != nil { t.Fatalf("json: %v", err) } - prov, ok := cfg.Provider[tt.provider.ID] + prov, ok := cfg.Provider[tt.wantID] if !ok { - t.Fatalf("provider %q missing from config", tt.provider.ID) + t.Fatalf("provider %q missing from config", tt.wantID) } if prov.NPM != tt.wantNPM { t.Errorf("npm = %q, want %q", prov.NPM, tt.wantNPM) @@ -185,14 +191,14 @@ func TestWriteProviderConfig(t *testing.T) { t.Errorf("models len = %d, want %d", len(prov.Models), len(tt.provider.Models)) } for _, m := range tt.provider.Models { - fqn := tt.provider.ID + "/" + m - entry, ok := prov.Models[fqn] + name := configuredModelName(tt.wantID, tt.provider.ID, m) + entry, ok := prov.Models[name] if !ok { - t.Errorf("model %q missing from config", fqn) + t.Errorf("model %q missing from config", name) continue } if entry["id"] != m { - t.Errorf("model %q id = %q, want %q", fqn, entry["id"], m) + t.Errorf("model %q id = %q, want %q", name, entry["id"], m) } } diff --git a/internal/clients/opencode/sdk.go b/internal/clients/opencode/sdk.go index 6b17fd8..4c524b6 100644 --- a/internal/clients/opencode/sdk.go +++ b/internal/clients/opencode/sdk.go @@ -78,19 +78,20 @@ func pickSDK(compat map[string]bool, apertureHost string) (npm string, options m // chosen one) mapped to the SDK picked from its compatibility map. func writeProviderConfig(apertureHost string, p config.ProviderInfo) (string, func(), error) { npm, options := pickSDK(p.Compatibility, apertureHost) + providerID := configProviderID(p) models := make(map[string]opencodeModelEntry, len(p.Models)) whitelist := make([]string, 0, len(p.Models)) for _, m := range p.Models { - fqn := p.ID + "/" + m - models[fqn] = opencodeModelEntry{ID: m, Name: fqn} - whitelist = append(whitelist, fqn) + name := configuredModelName(providerID, p.ID, m) + models[name] = opencodeModelEntry{ID: m, Name: name} + whitelist = append(whitelist, name) } cfg := opencodeConfig{ Schema: "https://opencode.ai/config.json", Provider: map[string]opencodeProvider{ - p.ID: { + providerID: { NPM: npm, Name: "Aperture (" + p.ID + ")", Options: options, @@ -119,3 +120,20 @@ func writeProviderConfig(apertureHost string, p config.ProviderInfo) (string, fu } return path, func() { os.Remove(path) }, nil } + +func configProviderID(p config.ProviderInfo) string { + if p.Compatibility["bedrock_model_invoke"] || p.Compatibility["bedrock_converse"] { + // OpenCode has special Bedrock handling wired to its built-in + // "amazon-bedrock" provider ID. v0.0.6 used that path; v0.0.7 regressed + // by emitting the selected Aperture provider ID instead. + return "amazon-bedrock" + } + return p.ID +} + +func configuredModelName(providerID, originalProviderID, model string) string { + if providerID == "amazon-bedrock" { + return model + } + return originalProviderID + "/" + model +}