mirror of
https://github.com/chenhg5/cc-connect.git
synced 2026-07-03 12:28:10 +08:00
- Add readCodexModelCatalog() that reads $CODEX_HOME/config.toml, resolves model_catalog_json path (expand ~, relative to CODEX_HOME), and parses the JSON as the primary model list - Extract shared parseCodexModelsJSON() to deduplicate JSON parsing logic between readCodexCachedModels and readCodexModelCatalog - Refactor readCodexCachedModels() to reuse resolveCodexHome(nil) - Update AvailableModels() priority: 1. model_catalog_json (new, highest) 2. provider config 3. GET /v1/models API 4. models_cache.json 5. hardcoded fallback - Add tests: TestAvailableModels_UsesModelCatalog, TestReadCodexModelCatalog_NoConfigFile
113 lines
4.1 KiB
Go
113 lines
4.1 KiB
Go
package codex
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
func TestAvailableModels_FallbackToModelsCache(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "forbidden", http.StatusForbidden)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
tmp := t.TempDir()
|
|
cache := `{
|
|
"models": [
|
|
{"slug":"gpt-5.4","description":"Latest frontier agentic coding model.","visibility":"list","supported_in_api":true},
|
|
{"slug":"gpt-5.3-codex","description":"Great for coding","visibility":"list","supported_in_api":true},
|
|
{"slug":"hidden-internal","visibility":"hidden","supported_in_api":true},
|
|
{"slug":"tool-only","visibility":"list","supported_in_api":false}
|
|
]
|
|
}`
|
|
if err := os.WriteFile(tmp+"/models_cache.json", []byte(cache), 0o600); err != nil {
|
|
t.Fatalf("write models_cache.json: %v", err)
|
|
}
|
|
|
|
t.Setenv("CODEX_HOME", tmp)
|
|
t.Setenv("OPENAI_API_KEY", "test-key")
|
|
t.Setenv("OPENAI_BASE_URL", srv.URL)
|
|
|
|
a := &Agent{activeIdx: -1}
|
|
models := a.AvailableModels(context.Background())
|
|
if len(models) != 2 {
|
|
t.Fatalf("models length = %d, want 2, models=%v", len(models), models)
|
|
}
|
|
if models[0].Name != "gpt-5.4" || models[1].Name != "gpt-5.3-codex" {
|
|
t.Fatalf("models = %v, want [gpt-5.4 gpt-5.3-codex]", models)
|
|
}
|
|
}
|
|
|
|
// TestAvailableModels_UsesModelCatalog tests that when CODEX_HOME/config.toml
|
|
// contains model_catalog_json, readCodexModelCatalog reads and parses that file
|
|
// as the highest-priority model source.
|
|
func TestAvailableModels_UsesModelCatalog(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "forbidden", http.StatusForbidden)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
tmp := t.TempDir()
|
|
|
|
config := `model_catalog_json = "model_catalog.json"
|
|
`
|
|
if err := os.WriteFile(tmp+"/config.toml", []byte(config), 0o644); err != nil {
|
|
t.Fatalf("write config.toml: %v", err)
|
|
}
|
|
|
|
// Mix of models: list-visible API-supported, hidden, non-API, and
|
|
// one with an empty slug that falls back to display_name.
|
|
catalog := `{
|
|
"models": [
|
|
{"slug":"gpt-5.4","display_name":"GPT-5.4","description":"Latest frontier agentic coding model.","visibility":"list","supported_in_api":true},
|
|
{"slug":"gpt-5.3-codex","display_name":"GPT-5.3 Codex","description":"Great for coding","visibility":"list","supported_in_api":true},
|
|
{"slug":"hidden-internal","visibility":"hidden","supported_in_api":true},
|
|
{"slug":"tool-only","visibility":"list","supported_in_api":false},
|
|
{"slug":"","display_name":"MissingSlug","visibility":"list","supported_in_api":true}
|
|
]
|
|
}`
|
|
if err := os.WriteFile(tmp+"/model_catalog.json", []byte(catalog), 0o644); err != nil {
|
|
t.Fatalf("write model_catalog.json: %v", err)
|
|
}
|
|
|
|
t.Setenv("CODEX_HOME", tmp)
|
|
t.Setenv("OPENAI_API_KEY", "test-key")
|
|
t.Setenv("OPENAI_BASE_URL", srv.URL)
|
|
|
|
a := &Agent{activeIdx: -1}
|
|
models := a.AvailableModels(context.Background())
|
|
|
|
// 3 visible+supported: gpt-5.4, gpt-5.3-codex, MissingSlug (slug empty → display_name fallback)
|
|
if len(models) != 3 {
|
|
t.Fatalf("models length = %d, want 3, models=%v", len(models), models)
|
|
}
|
|
if models[0].Name != "gpt-5.4" || models[0].Desc != "Latest frontier agentic coding model." {
|
|
t.Errorf("models[0] = %+v, want gpt-5.4 with description", models[0])
|
|
}
|
|
if models[1].Name != "gpt-5.3-codex" || models[1].Desc != "Great for coding" {
|
|
t.Errorf("models[1] = %+v, want gpt-5.3-codex with description", models[1])
|
|
}
|
|
if models[2].Name != "MissingSlug" || models[2].Desc != "" {
|
|
t.Errorf("models[2] = %+v, want MissingSlug (display_name fallback)", models[2])
|
|
}
|
|
}
|
|
|
|
// TestReadCodexModelCatalog_NoConfigFile tests graceful fallback when
|
|
// CODEX_HOME/config.toml does not exist.
|
|
func TestReadCodexModelCatalog_NoConfigFile(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
t.Setenv("CODEX_HOME", tmp)
|
|
|
|
a := &Agent{activeIdx: -1}
|
|
models := a.AvailableModels(context.Background())
|
|
|
|
// No config.toml → no model_catalog.json → no models_cache.json
|
|
// → no OPENAI_API_KEY → all the way to hardcoded fallback (6 models)
|
|
if len(models) != 6 {
|
|
t.Fatalf("expected 6 hardcoded fallback models, got %d: %v", len(models), models)
|
|
}
|
|
}
|