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.
This commit is contained in:
apale7 2026-04-17 09:22:09 +08:00
parent c06afaecea
commit 4264ada4f8
3 changed files with 75 additions and 2 deletions

View File

@ -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": {}
}

View File

@ -74,9 +74,9 @@ func newAliasListCmd() *cobra.Command {
} }
fmt.Fprintf(cmd.OutOrStdout(), "%s [%s]\n", a.Alias, state) fmt.Fprintf(cmd.OutOrStdout(), "%s [%s]\n", a.Alias, state)
for i, t := range a.Targets { for i, t := range a.Targets {
mark := " " mark := "x"
if !t.Enabled { if !t.Enabled {
mark = "x" mark = " "
} }
fmt.Fprintf(cmd.OutOrStdout(), " [%s] %d. %s/%s\n", mark, i+1, t.Provider, t.Model) fmt.Fprintf(cmd.OutOrStdout(), " [%s] %d. %s/%s\n", mark, i+1, t.Provider, t.Model)
} }

View File

@ -150,12 +150,17 @@ func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) {
http.Error(w, "missing model field", http.StatusBadRequest) http.Error(w, "missing model field", http.StatusBadRequest)
return 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) alias := s.cfg.FindAlias(aliasName)
if alias == nil { 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) http.Error(w, fmt.Sprintf("alias %q not found", aliasName), http.StatusNotFound)
return return
} }
if !alias.Enabled { 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) http.Error(w, fmt.Sprintf("alias %q is disabled", aliasName), http.StatusNotFound)
return return
} }
@ -166,6 +171,7 @@ func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) {
} }
} }
if len(targets) == 0 { 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) http.Error(w, fmt.Sprintf("alias %q has no enabled targets", aliasName), http.StatusFailedDependency)
return return
} }
@ -178,6 +184,7 @@ func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) {
failoverCount++ failoverCount++
continue 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 // rewrite payload.model for this upstream
cloned := cloneMap(payload) cloned := cloneMap(payload)
cloned["model"] = t.Model cloned["model"] = t.Model
@ -248,6 +255,7 @@ func (s *Server) tryOnce(
} }
if resp.StatusCode >= 400 { if resp.StatusCode >= 400 {
// non-retryable: forward status + body to client // 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) s.writeDebugHeaders(w, aliasName, provider.ID, target.Model, attempt, failoverCount)
copyResponseHeaders(w.Header(), resp.Header) copyResponseHeaders(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode) w.WriteHeader(resp.StatusCode)
@ -256,6 +264,7 @@ func (s *Server) tryOnce(
} }
// 2xx: start streaming pass-through. From this point no failover is allowed. // 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) s.writeDebugHeaders(w, aliasName, provider.ID, target.Model, attempt, failoverCount)
copyResponseHeaders(w.Header(), resp.Header) copyResponseHeaders(w.Header(), resp.Header)
w.WriteHeader(resp.StatusCode) 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. // writeDebugHeaders sets the X-OPS-* debug headers before WriteHeader.
func (s *Server) writeDebugHeaders(w http.ResponseWriter, alias, provider, remoteModel string, attempt, failoverCount int) { func (s *Server) writeDebugHeaders(w http.ResponseWriter, alias, provider, remoteModel string, attempt, failoverCount int) {
h := w.Header() h := w.Header()