gateway: a passed-through request's reasoning effort is fitted to the model's levels, and one asking "none" that the upstream refuses with the levels it takes is asked again at low — Qoder's permission check asks "none", which Command Code turned away

This commit is contained in:
yetone committed 2026-09-28 00:39:22 +08:00
1 parent aee0d1c221
commit 8b8a9e5a7a
2 files changed
+85

No files matched your search

+62
View File
@@ -932,10 +932,28 @@ func (s *Server) passthrough(w http.ResponseWriter, r *http.Request, p provider.
case provider.Anthropic:
body = thinkingOffUnlessAsked(body)
}
// the effort as the agent sent it, fitted to the model's levels: Qoder's
// permission check asks "none", which Command Code turns away
asked := bodyEffort(proto, body)
if e := fitEffort(asked, p.Efforts(model)); asked != "" && e != asked {
body = withBodyEffort(proto, body, e)
}
res, err := s.forward(r.Context(), p, proto, pathOf(proto), p.Prepare(body), r.Header)
if err != nil {
return writeError(w, proto, 502, p.Name+": "+err.Error()), err.Error(), true
}
if e := bodyEffort(proto, body); (e == "none" || e == "minimal") && res.StatusCode == http.StatusBadRequest {
// a model whose levels magpie doesn't know, refusing no reasoning
// with the levels it takes: asked again at the lowest of them
b, _ := io.ReadAll(io.LimitReader(res.Body, 1<<20))
res.Body.Close()
res.Body = io.NopCloser(bytes.NewReader(b))
if effortLevelsNamed.Match(b) {
if res, err = s.forward(r.Context(), p, proto, pathOf(proto), p.Prepare(withBodyEffort(proto, body, "low")), r.Header); err != nil {
return writeError(w, proto, 502, p.Name+": "+err.Error()), err.Error(), true
}
}
}
if proto == provider.Anthropic && res.StatusCode == http.StatusBadRequest {
// a model that always thinks refuses thinking turned off: asked
// again with it left to the model
@@ -1468,6 +1486,50 @@ func conversationID(in http.Header, body []byte) string {
return "magpie-" + hex.EncodeToString(sum[:12])
}
// effortLevelsNamed is an error that lists the reasoning levels a model
// takes, as one refusing "none" does: Command Code's `expected one of
// "low"|"medium"|"high"|"xhigh"|"max"`.
var effortLevelsNamed = regexp.MustCompile(`(?i)\blow\b\W+(?:medium|high)\b`)
// bodyEffort is the reasoning effort a Chat or Responses request asks for.
func bodyEffort(proto provider.Protocol, body []byte) string {
var v struct {
ReasoningEffort string `json:"reasoning_effort"`
Reasoning *struct {
Effort string `json:"effort"`
} `json:"reasoning"`
}
if json.Unmarshal(body, &v) != nil {
return ""
}
switch {
case proto == provider.Chat:
return v.ReasoningEffort
case proto == provider.Responses && v.Reasoning != nil:
return v.Reasoning.Effort
}
return ""
}
// withBodyEffort asks a Chat or Responses request for effort instead,
// keeping the rest of its reasoning settings.
func withBodyEffort(proto provider.Protocol, body []byte, effort string) []byte {
switch proto {
case provider.Chat:
return withFields(body, map[string]any{"reasoning_effort": effort})
case provider.Responses:
var v struct {
Reasoning map[string]any `json:"reasoning"`
}
if json.Unmarshal(body, &v) != nil || v.Reasoning == nil {
return body
}
v.Reasoning["effort"] = effort
return withFields(body, map[string]any{"reasoning": v.Reasoning})
}
return body
}
// withFields sets top-level fields, keeping every other field as it was.
func withFields(body []byte, fields map[string]any) []byte {
dec := json.NewDecoder(bytes.NewReader(body))
+23
View File
@@ -561,6 +561,29 @@ func TestAnthropicPassthroughModelThatAlwaysThinks(t *testing.T) {
}
}
// Qoder asks "none" for its permission checks, which Command Code turns
// away with the levels it takes: asked again at the lowest, once
func TestPassthroughEffortNoneRefused(t *testing.T) {
f := &fake{t: t, ctype: "application/json", reply: `{"id":"c1","choices":[]}`}
f.refuse = func(b []byte) (int, string) {
if bytes.Contains(b, []byte(`"reasoning_effort":"none"`)) {
return 400, `{"error":{"message":"Command Code: Invalid option: expected one of \"low\"|\"medium\"|\"high\"|\"xhigh\"|\"max\""}}`
}
return 0, ""
}
setup(t, provider.Chat, f)
code, body := post(t, "/v1/chat/completions", `{"model":"m1","messages":[{"role":"user","content":"ok?"}],"reasoning_effort":"none"}`)
if code != 200 || f.calls != 2 || !bytes.Contains(f.got, []byte(`"reasoning_effort":"low"`)) || !bytes.Contains(f.got, []byte(`"ok?"`)) {
t.Fatalf("%d %s after %d calls, last sent %s", code, body, f.calls, f.got)
}
// another 400 is the agent's to see
f.calls = 0
f.refuse = func([]byte) (int, string) { return 400, `{"error":{"message":"bad request"}}` }
if code, _ := post(t, "/v1/chat/completions", `{"model":"m1","messages":[],"reasoning_effort":"none"}`); code != 400 || f.calls != 1 {
t.Errorf("%d after %d calls", code, f.calls)
}
}
// effort without thinking asks for no reasoning on a Chat upstream
func TestAnthropicEffortWithoutThinking(t *testing.T) {
f := &fake{t: t, reply: sse(`data: {"id":"c1","choices":[{"delta":{"content":"T"},"finish_reason":"stop"}]}`, `data: [DONE]`)}