From 4264ada4f8cdb1def11fffaeb319ff07f7406763 Mon Sep 17 00:00:00 2001 From: apale7 <1092377056@qq.com> Date: Fri, 17 Apr 2026 09:22:09 +0800 Subject: [PATCH] fix: improve ops alias routing Accept ops-prefixed alias names in the local proxy so OpenCode requests do not fail on model lookup. Add clearer proxy diagnostics and fix alias list target state output to simplify ops troubleshooting. --- .../tasks/04-17-ops-mvp-4xx-review/task.json | 53 +++++++++++++++++++ internal/cli/alias.go | 4 +- internal/proxy/server.go | 20 +++++++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 .trellis/tasks/04-17-ops-mvp-4xx-review/task.json diff --git a/.trellis/tasks/04-17-ops-mvp-4xx-review/task.json b/.trellis/tasks/04-17-ops-mvp-4xx-review/task.json new file mode 100644 index 0000000..3b5415d --- /dev/null +++ b/.trellis/tasks/04-17-ops-mvp-4xx-review/task.json @@ -0,0 +1,53 @@ +{ + "id": "ops-mvp-4xx-review", + "name": "ops-mvp-4xx-review", + "title": "Review and fix OPS MVP 4xx failures", + "description": "", + "status": "planning", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "Apale", + "assignee": "Apale", + "createdAt": "2026-04-17", + "completedAt": null, + "branch": null, + "base_branch": "master", + "worktree_path": null, + "current_phase": 0, + "next_action": [ + { + "phase": 1, + "action": "brainstorm" + }, + { + "phase": 2, + "action": "research" + }, + { + "phase": 3, + "action": "implement" + }, + { + "phase": 4, + "action": "check" + }, + { + "phase": 5, + "action": "update-spec" + }, + { + "phase": 6, + "action": "record-session" + } + ], + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/internal/cli/alias.go b/internal/cli/alias.go index de04c4b..3ab449f 100644 --- a/internal/cli/alias.go +++ b/internal/cli/alias.go @@ -74,9 +74,9 @@ func newAliasListCmd() *cobra.Command { } fmt.Fprintf(cmd.OutOrStdout(), "%s [%s]\n", a.Alias, state) for i, t := range a.Targets { - mark := " " + mark := "x" if !t.Enabled { - mark = "x" + mark = " " } fmt.Fprintf(cmd.OutOrStdout(), " [%s] %d. %s/%s\n", mark, i+1, t.Provider, t.Model) } diff --git a/internal/proxy/server.go b/internal/proxy/server.go index 30ece2a..06a0e0a 100644 --- a/internal/proxy/server.go +++ b/internal/proxy/server.go @@ -150,12 +150,17 @@ func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) { http.Error(w, "missing model field", http.StatusBadRequest) return } + rawModel := aliasName + aliasName = normalizeAliasName(aliasName) + s.logger.Printf("req=%d incoming model=%q alias=%q stream=%v", reqID, rawModel, aliasName, payload["stream"]) alias := s.cfg.FindAlias(aliasName) if alias == nil { + s.logger.Printf("req=%d alias lookup failed for model=%q alias=%q", reqID, rawModel, aliasName) http.Error(w, fmt.Sprintf("alias %q not found", aliasName), http.StatusNotFound) return } if !alias.Enabled { + s.logger.Printf("req=%d alias=%q disabled", reqID, aliasName) http.Error(w, fmt.Sprintf("alias %q is disabled", aliasName), http.StatusNotFound) return } @@ -166,6 +171,7 @@ func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) { } } if len(targets) == 0 { + s.logger.Printf("req=%d alias=%q has no enabled targets", reqID, aliasName) http.Error(w, fmt.Sprintf("alias %q has no enabled targets", aliasName), http.StatusFailedDependency) return } @@ -178,6 +184,7 @@ func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) { failoverCount++ continue } + s.logger.Printf("req=%d alias=%s attempt=%d provider=%s remote_model=%s failovers=%d", reqID, aliasName, attempt+1, p.ID, t.Model, failoverCount) // rewrite payload.model for this upstream cloned := cloneMap(payload) cloned["model"] = t.Model @@ -248,6 +255,7 @@ func (s *Server) tryOnce( } if resp.StatusCode >= 400 { // non-retryable: forward status + body to client + s.logger.Printf("alias=%s attempt=%d provider=%s remote_model=%s upstream_status=%d", aliasName, attempt, provider.ID, target.Model, resp.StatusCode) s.writeDebugHeaders(w, aliasName, provider.ID, target.Model, attempt, failoverCount) copyResponseHeaders(w.Header(), resp.Header) w.WriteHeader(resp.StatusCode) @@ -256,6 +264,7 @@ func (s *Server) tryOnce( } // 2xx: start streaming pass-through. From this point no failover is allowed. + s.logger.Printf("alias=%s attempt=%d provider=%s remote_model=%s upstream_status=%d", aliasName, attempt, provider.ID, target.Model, resp.StatusCode) s.writeDebugHeaders(w, aliasName, provider.ID, target.Model, attempt, failoverCount) copyResponseHeaders(w.Header(), resp.Header) w.WriteHeader(resp.StatusCode) @@ -281,6 +290,17 @@ func (s *Server) tryOnce( } } +func normalizeAliasName(model string) string { + const prefix = "ops/" + if strings.HasPrefix(model, prefix) { + trimmed := strings.TrimPrefix(model, prefix) + if trimmed != "" { + return trimmed + } + } + return model +} + // writeDebugHeaders sets the X-OPS-* debug headers before WriteHeader. func (s *Server) writeDebugHeaders(w http.ResponseWriter, alias, provider, remoteModel string, attempt, failoverCount int) { h := w.Header()