Archived
CrossRelay stage 2: Codex /v1/responses relay, config.toml manager, Codex page
This commit is contained in:
1 parent
404704171d
commit
5112f7ca06
30 files changed
+3539
-72
No files matched your search
+162
-17
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.rutua.cn/kate522/CrossRelay/internal/claudecfg"
|
||||
"gitea.rutua.cn/kate522/CrossRelay/internal/codexcfg"
|
||||
"gitea.rutua.cn/kate522/CrossRelay/internal/config"
|
||||
)
|
||||
|
||||
@@ -29,12 +30,22 @@ func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
"models": p.Models,
|
||||
}
|
||||
}
|
||||
var activeCodex any
|
||||
if p := s.store.ActiveCodex(); p != nil {
|
||||
activeCodex = map[string]any{
|
||||
"id": p.ID,
|
||||
"name": p.Name,
|
||||
"base_url": p.BaseURL,
|
||||
"models": p.Models,
|
||||
}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"running": true,
|
||||
"version": Version,
|
||||
"port": s.port,
|
||||
"uptime_sec": int(time.Since(s.started).Seconds()),
|
||||
"active_claude_provider": active,
|
||||
"active_codex_provider": activeCodex,
|
||||
"stats": s.snapshotStats(),
|
||||
"language": cfg.Language,
|
||||
"port_mode": cfg.Gateway.PortMode,
|
||||
@@ -84,10 +95,33 @@ func (s *Server) handleDeleteProvider(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"deleted": id})
|
||||
}
|
||||
|
||||
// handleApply activates a provider and writes it into Claude Code's
|
||||
// settings.json.
|
||||
// applyInput selects which client configuration receives the provider.
|
||||
type applyInput struct {
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
// handleApply activates a provider and writes it into the target
|
||||
// client's configuration. The body is optional; an absent or empty
|
||||
// target means Claude Code, keeping older clients working.
|
||||
func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
var in applyInput
|
||||
if r.Body != nil {
|
||||
_ = decodeJSON(r, &in, 1<<20) // an empty body leaves the zero value
|
||||
}
|
||||
switch in.Target {
|
||||
case "", "claude":
|
||||
s.applyClaude(w, id)
|
||||
case "codex":
|
||||
s.applyCodex(w, id)
|
||||
default:
|
||||
writeAPIError(w, http.StatusBadRequest, `target must be "claude" or "codex"`)
|
||||
}
|
||||
}
|
||||
|
||||
// applyClaude activates a provider and writes it into Claude Code's
|
||||
// settings.json.
|
||||
func (s *Server) applyClaude(w http.ResponseWriter, id string) {
|
||||
p, err := s.store.ActivateClaude(id)
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusNotFound, err.Error())
|
||||
@@ -109,18 +143,84 @@ func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"provider": p,
|
||||
"target": "claude",
|
||||
"env": settings.Env,
|
||||
})
|
||||
}
|
||||
|
||||
// handleRestore puts back the most recent backup of settings.json.
|
||||
// applyCodex activates a provider and writes it into Codex's
|
||||
// config.toml.
|
||||
func (s *Server) applyCodex(w http.ResponseWriter, id string) {
|
||||
p, err := s.store.ActivateCodex(id)
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
mgr, err := s.codexManager()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
injection, err := mgr.Apply(codexcfg.ApplyOptions{
|
||||
BaseURL: fmt.Sprintf("http://127.0.0.1:%d", s.port),
|
||||
Token: s.store.Config().RelayToken,
|
||||
})
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// Remember the provider the user had before the first apply so
|
||||
// restore can put it back instead of dropping the key.
|
||||
if injection.PriorProvider != "" && s.store.Config().CodexPriorProvider == "" {
|
||||
if err := s.store.SetCodexPriorProvider(injection.PriorProvider); err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"provider": p,
|
||||
"target": "codex",
|
||||
"injection": injection,
|
||||
})
|
||||
}
|
||||
|
||||
// restoreInput selects the client and, optionally, one specific backup
|
||||
// file to roll back to.
|
||||
type restoreInput struct {
|
||||
Target string `json:"target"`
|
||||
Backup string `json:"backup,omitempty"`
|
||||
}
|
||||
|
||||
// handleRestore puts a client's configuration back. Claude restores
|
||||
// the newest backup (or the named one); Codex removes the managed
|
||||
// keys, or rolls back to the named backup.
|
||||
func (s *Server) handleRestore(w http.ResponseWriter, r *http.Request) {
|
||||
var in restoreInput
|
||||
if r.Body != nil {
|
||||
_ = decodeJSON(r, &in, 1<<20)
|
||||
}
|
||||
switch in.Target {
|
||||
case "", "claude":
|
||||
s.restoreClaude(w, in.Backup)
|
||||
case "codex":
|
||||
s.restoreCodex(w, in.Backup)
|
||||
default:
|
||||
writeAPIError(w, http.StatusBadRequest, `target must be "claude" or "codex"`)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) restoreClaude(w http.ResponseWriter, backup string) {
|
||||
mgr, err := s.claudeManager()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
name, err := mgr.Restore()
|
||||
var name string
|
||||
if backup != "" {
|
||||
name, err = mgr.RestoreFrom(backup)
|
||||
} else {
|
||||
name, err = mgr.Restore()
|
||||
}
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
@@ -129,21 +229,66 @@ func (s *Server) handleRestore(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"restored_from": name})
|
||||
}
|
||||
|
||||
func (s *Server) restoreCodex(w http.ResponseWriter, backup string) {
|
||||
mgr, err := s.codexManager()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if backup != "" {
|
||||
name, err := mgr.RestoreBackup(backup)
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
_ = s.store.ClearActiveCodex()
|
||||
writeJSON(w, http.StatusOK, map[string]any{"restored_from": name})
|
||||
return
|
||||
}
|
||||
if err := mgr.RemoveManaged(s.store.Config().CodexPriorProvider); err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
_ = s.store.ClearActiveCodex()
|
||||
_ = s.store.SetCodexPriorProvider("")
|
||||
writeJSON(w, http.StatusOK, map[string]any{"restored_from": ""})
|
||||
}
|
||||
|
||||
func (s *Server) handleBackups(w http.ResponseWriter, r *http.Request) {
|
||||
mgr, err := s.claudeManager()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
switch target := r.URL.Query().Get("target"); target {
|
||||
case "", "claude":
|
||||
mgr, err := s.claudeManager()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
items, err := mgr.Backups()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if items == nil {
|
||||
items = []claudecfg.BackupInfo{}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"items": items})
|
||||
case "codex":
|
||||
mgr, err := s.codexManager()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
items, err := mgr.Backups()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if items == nil {
|
||||
items = []codexcfg.BackupInfo{}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"items": items})
|
||||
default:
|
||||
writeAPIError(w, http.StatusBadRequest, `target must be "claude" or "codex"`)
|
||||
}
|
||||
items, err := mgr.Backups()
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
if items == nil {
|
||||
items = []claudecfg.BackupInfo{}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"items": items})
|
||||
}
|
||||
|
||||
func (s *Server) handleGetSettings(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in new issue
Block a user