CrossRelay stage 2: Codex /v1/responses relay, config.toml manager, Codex page

This commit is contained in:
kate522 committed 2026-10-18 18:35:01 +08:00
1 parent 404704171d
commit 5112f7ca06
30 files changed
+3539 -72

No files matched your search

+8 -2
View File
@@ -48,6 +48,7 @@ type Provider struct {
APIKey string `json:"api_key"`
Models Models `json:"models"`
EnabledForClaude bool `json:"enabled_for_claude"`
EnabledForCodex bool `json:"enabled_for_codex"`
CreatedAt time.Time `json:"created_at"`
}
@@ -63,8 +64,13 @@ type Config struct {
RelayToken string `json:"relay_token"`
Providers []Provider `json:"providers"`
ActiveClaudeID string `json:"active_claude_id,omitempty"`
Gateway Gateway `json:"gateway"`
Language string `json:"language"`
ActiveCodexID string `json:"active_codex_id,omitempty"`
// CodexPriorProvider remembers the model_provider value that was in
// config.toml before CrossRelay first applied, so restore can put it
// back instead of dropping the key.
CodexPriorProvider string `json:"codex_prior_provider,omitempty"`
Gateway Gateway `json:"gateway"`
Language string `json:"language"`
}
// ResolveModel maps the model name Claude Code asked for onto the model
+96
View File
@@ -128,6 +128,102 @@ func TestDeleteProviderClearsActive(t *testing.T) {
}
}
func TestActivateCodexRoundTrip(t *testing.T) {
t.Setenv("CROSSRELAY_HOME", t.TempDir())
store, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
p, err := store.AddProvider("DeepSeek", "deepseek", "https://api.deepseek.com/v1", "sk-test",
Models{Default: "deepseek-chat"})
if err != nil {
t.Fatalf("AddProvider: %v", err)
}
if _, err := store.ActivateCodex(p.ID); err != nil {
t.Fatalf("ActivateCodex: %v", err)
}
if err := store.SetCodexPriorProvider("custom"); err != nil {
t.Fatalf("SetCodexPriorProvider: %v", err)
}
reloaded, err := Load()
if err != nil {
t.Fatalf("reload: %v", err)
}
active := reloaded.ActiveCodex()
if active == nil || active.Name != "DeepSeek" {
t.Fatalf("active codex provider after reload = %+v", active)
}
if !active.EnabledForCodex {
t.Error("enabled_for_codex not persisted")
}
if reloaded.Config().CodexPriorProvider != "custom" {
t.Errorf("prior provider = %q, want custom", reloaded.Config().CodexPriorProvider)
}
if err := reloaded.ClearActiveCodex(); err != nil {
t.Fatalf("ClearActiveCodex: %v", err)
}
if reloaded.ActiveCodex() != nil {
t.Error("active codex provider survived clear")
}
if _, err := reloaded.ActivateCodex("p_missing"); err == nil {
t.Error("ActivateCodex accepted an unknown id")
}
}
func TestUpdateProviderKeepsEnabledFlags(t *testing.T) {
t.Setenv("CROSSRELAY_HOME", t.TempDir())
store, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
p, err := store.AddProvider("DeepSeek", "deepseek", "https://api.deepseek.com/v1", "sk-test",
Models{Default: "deepseek-chat"})
if err != nil {
t.Fatalf("AddProvider: %v", err)
}
if _, err := store.ActivateClaude(p.ID); err != nil {
t.Fatalf("ActivateClaude: %v", err)
}
if _, err := store.ActivateCodex(p.ID); err != nil {
t.Fatalf("ActivateCodex: %v", err)
}
updated, err := store.UpdateProvider(p.ID, "DeepSeek 2", "deepseek", "https://api.deepseek.com/v1", "",
Models{Default: "deepseek-reasoner"})
if err != nil {
t.Fatalf("UpdateProvider: %v", err)
}
if !updated.EnabledForClaude || !updated.EnabledForCodex {
t.Errorf("enabled flags lost: claude=%v codex=%v", updated.EnabledForClaude, updated.EnabledForCodex)
}
if updated.APIKey != "sk-test" {
t.Errorf("api key = %q, want the stored key kept", updated.APIKey)
}
}
func TestDeleteProviderClearsCodexActive(t *testing.T) {
t.Setenv("CROSSRELAY_HOME", t.TempDir())
store, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
p, err := store.AddProvider("DeepSeek", "deepseek", "https://api.deepseek.com/v1", "sk-test",
Models{Default: "deepseek-chat"})
if err != nil {
t.Fatalf("AddProvider: %v", err)
}
if _, err := store.ActivateCodex(p.ID); err != nil {
t.Fatalf("ActivateCodex: %v", err)
}
if err := store.DeleteProvider(p.ID); err != nil {
t.Fatalf("DeleteProvider: %v", err)
}
if store.ActiveCodex() != nil {
t.Error("deleted provider stayed active for codex")
}
}
func TestSetGatewayValidation(t *testing.T) {
t.Setenv("CROSSRELAY_HOME", t.TempDir())
store, _ := Load()
+63
View File
@@ -88,6 +88,21 @@ func (s *Store) ActiveClaude() *Provider {
return nil
}
// ActiveCodex returns the provider currently applied to Codex.
func (s *Store) ActiveCodex() *Provider {
s.mu.Lock()
defer s.mu.Unlock()
if s.cfg.ActiveCodexID == "" {
return nil
}
for i := range s.cfg.Providers {
if s.cfg.Providers[i].ID == s.cfg.ActiveCodexID {
return s.cfg.Providers[i].clone()
}
}
return nil
}
// AddProvider validates and appends a provider.
func (s *Store) AddProvider(name, preset, baseURL, apiKey string, models Models) (*Provider, error) {
s.mu.Lock()
@@ -128,6 +143,7 @@ func (s *Store) UpdateProvider(id, name, preset, baseURL, apiKey string, models
}
updated.ID = existing.ID
updated.EnabledForClaude = existing.EnabledForClaude
updated.EnabledForCodex = existing.EnabledForCodex
updated.CreatedAt = existing.CreatedAt
s.cfg.Providers[idx] = *updated
if err := s.saveLocked(); err != nil {
@@ -154,6 +170,9 @@ func (s *Store) DeleteProvider(id string) error {
if s.cfg.ActiveClaudeID == id {
s.cfg.ActiveClaudeID = ""
}
if s.cfg.ActiveCodexID == id {
s.cfg.ActiveCodexID = ""
}
return s.saveLocked()
}
@@ -192,6 +211,50 @@ func (s *Store) ClearActiveClaude() error {
return s.saveLocked()
}
// ActivateCodex marks a provider as the one written into Codex's
// config.toml and records it as active.
func (s *Store) ActivateCodex(id string) (*Provider, error) {
s.mu.Lock()
defer s.mu.Unlock()
idx := -1
for i := range s.cfg.Providers {
if s.cfg.Providers[i].ID == id {
idx = i
break
}
}
if idx < 0 {
return nil, fmt.Errorf("provider %q not found", id)
}
s.cfg.Providers[idx].EnabledForCodex = true
s.cfg.ActiveCodexID = id
if err := s.saveLocked(); err != nil {
return nil, err
}
return s.cfg.Providers[idx].clone(), nil
}
// ClearActiveCodex forgets the active Codex provider without touching
// the provider list (used after restoring the official config).
func (s *Store) ClearActiveCodex() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.cfg.ActiveCodexID == "" {
return nil
}
s.cfg.ActiveCodexID = ""
return s.saveLocked()
}
// SetCodexPriorProvider records (or clears) the model_provider value
// that config.toml carried before CrossRelay took it over.
func (s *Store) SetCodexPriorProvider(prior string) error {
s.mu.Lock()
defer s.mu.Unlock()
s.cfg.CodexPriorProvider = strings.TrimSpace(prior)
return s.saveLocked()
}
// SetGateway updates listener settings.
func (s *Store) SetGateway(g Gateway) error {
g.PortMode = strings.TrimSpace(g.PortMode)