fix(opencode): avoid sync panic on model metadata
This commit is contained in:
parent
14061b020f
commit
887eb14eca
@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@ -165,3 +166,57 @@ func TestProviderEnableDisableCommands(t *testing.T) {
|
||||
t.Fatalf("provider after enable = %#v, want disabled=false", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpencodeSyncDoesNotPanicOnSliceModelMetadata(t *testing.T) {
|
||||
t.Setenv("OLPX_CONFIG", filepath.Join(t.TempDir(), "olpx.json"))
|
||||
configPath = ""
|
||||
|
||||
cfg, err := loadCfg()
|
||||
if err != nil {
|
||||
t.Fatalf("loadCfg: %v", err)
|
||||
}
|
||||
cfg.UpsertProvider(config.Provider{
|
||||
ID: "p1",
|
||||
BaseURL: "https://example.com/v1",
|
||||
})
|
||||
cfg.UpsertAlias(config.Alias{
|
||||
Alias: "gpt-5.4",
|
||||
Enabled: true,
|
||||
Targets: []config.Target{{Provider: "p1", Model: "up-1", Enabled: true}},
|
||||
})
|
||||
if err := cfg.Save(); err != nil {
|
||||
t.Fatalf("save config: %v", err)
|
||||
}
|
||||
|
||||
target := filepath.Join(t.TempDir(), "opencode.jsonc")
|
||||
seed := []byte("{\n \"$schema\": \"https://opencode.ai/config.json\",\n \"provider\": {\n \"olpx\": {\n \"npm\": \"@ai-sdk/openai\",\n \"name\": \"OpenCode LocalProxy CLI\",\n \"options\": {\n \"baseURL\": \"http://127.0.0.1:9982/v1\",\n \"apiKey\": \"olpx-local\",\n \"setCacheKey\": true\n },\n \"models\": {\n \"gpt-5.4\": {\n \"name\": \"custom-display-name\",\n \"tags\": [\"reasoning\", \"priority\"],\n \"variants\": [\n {\"name\": \"high\", \"effort\": \"high\"}\n ]\n }\n }\n }\n }\n}\n")
|
||||
if err := os.WriteFile(target, seed, 0o600); err != nil {
|
||||
t.Fatalf("write target config: %v", err)
|
||||
}
|
||||
|
||||
cmd := newOpencodeSyncCmd()
|
||||
var stdout bytes.Buffer
|
||||
cmd.SetOut(&stdout)
|
||||
cmd.SetErr(&stdout)
|
||||
cmd.SetArgs([]string{"--target", target})
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("opencode sync panicked with slice metadata: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatalf("execute opencode sync: %v", err)
|
||||
}
|
||||
if got := stdout.String(); got != "✓ no changes required at "+target+"\n" {
|
||||
t.Fatalf("stdout = %q", got)
|
||||
}
|
||||
data, err := os.ReadFile(target)
|
||||
if err != nil {
|
||||
t.Fatalf("read target config: %v", err)
|
||||
}
|
||||
if !bytes.Equal(data, seed) {
|
||||
t.Fatalf("sync rewrote unchanged config:\n%s", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/tidwall/jsonc"
|
||||
@ -601,25 +602,5 @@ func ImportCustomProviders(raw Raw) []ImportableProvider {
|
||||
|
||||
// mapsEqualShallow compares two string-keyed maps for structural equality.
|
||||
func mapsEqualShallow(a, b map[string]any) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for k, v := range a {
|
||||
bv, ok := b[k]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
am, amOk := v.(map[string]any)
|
||||
bm, bmOk := bv.(map[string]any)
|
||||
if amOk && bmOk {
|
||||
if !mapsEqualShallow(am, bm) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if v != bv {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return reflect.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
@ -142,6 +142,43 @@ func TestEnsureOLPXProviderPreservesExistingModelMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureOLPXProviderDoesNotPanicOnComparableMetadata(t *testing.T) {
|
||||
raw := Raw{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"provider": map[string]any{
|
||||
"olpx": map[string]any{
|
||||
"npm": "@ai-sdk/openai",
|
||||
"name": "OpenCode LocalProxy CLI",
|
||||
"options": map[string]any{
|
||||
"baseURL": "http://127.0.0.1:9982/v1",
|
||||
"apiKey": "olpx-local",
|
||||
"setCacheKey": true,
|
||||
},
|
||||
"models": map[string]any{
|
||||
"gpt-5.4": map[string]any{
|
||||
"name": "custom-display-name",
|
||||
"tags": []any{"reasoning", "priority"},
|
||||
"variants": []any{
|
||||
map[string]any{"name": "high", "effort": "high"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("EnsureOLPXProvider() panicked with slice metadata: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
changed := EnsureOLPXProvider(raw, "http://127.0.0.1:9982/v1", "olpx-local", []string{"gpt-5.4"})
|
||||
if changed {
|
||||
t.Fatal("EnsureOLPXProvider() reported change for unchanged alias metadata with slices")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateOLPXProviderAllowsCustomModelMetadata(t *testing.T) {
|
||||
raw := Raw{
|
||||
"provider": map[string]any{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user