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()