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:
parent
c06afaecea
commit
4264ada4f8
53
.trellis/tasks/04-17-ops-mvp-4xx-review/task.json
Normal file
53
.trellis/tasks/04-17-ops-mvp-4xx-review/task.json
Normal 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": {}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user