mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 19:47:10 +08:00
AnthropicAdapter.Stream built thinking config (buildAnthropicThinkingConfig)
and wrote it into body only inside the `if len(body)==0` normal-construction
block. The RequestBodyOverride branch skipped it entirely — a disabled effort
on the override path left whatever thinking config the override body carried,
violating user intent and diverging from openai.go, where
applyOpenAIThinkingDisable runs unconditionally after both branches.
Also, on the normal path, disabled only wrote thinking:{type:disabled} but
left a stale output_config (set by a prior adaptive turn or by
AnthropicExtraParams) in place — an explicitly disabled request could still
carry output_config.effort=high, a contradictory payload.
Fix:
- Extract applyAnthropicThinkingConfig(body, req), called unconditionally
after the override/normal block, mirroring openai.go:1860.
- disabled: force thinking:{type:"disabled"}, delete output_config, set
thinking_disabled_provider_param=thinking.type knob.
- adaptive (AnthropicThinkingEffort non-empty): write
thinking:{type:adaptive,display:summarized} + output_config.
- empty effort: no-op.
- buildAnthropicThinkingConfig retained for stable-message-count / message
normalization signals inside the normal block.
Tests: anthropic_thinking_disable_test.go covers all three branches, alias
normalization (off→disabled), disabled overriding existing adaptive config,
and the override-path symmetry scenario.
142 lines
4.6 KiB
Go
142 lines
4.6 KiB
Go
package modeladapter
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// TestApplyAnthropicThinkingConfig covers the three effort branches on a body
|
|
// built by the normal construction path, plus the override path symmetry that
|
|
// was the original bug (thinking config was only applied inside the
|
|
// `if len(body)==0` block, so RequestBodyOverride skipped it entirely).
|
|
func TestApplyAnthropicThinkingConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
body map[string]any
|
|
thinkingEffort string
|
|
adaptiveEffort string
|
|
wantThinking any
|
|
wantOutputCfg bool // expect output_config key present
|
|
}{
|
|
{
|
|
name: "disabled_writes_disabled_and_drops_output_config",
|
|
body: map[string]any{"model": "m", "output_config": map[string]any{"effort": "high"}},
|
|
thinkingEffort: "disabled",
|
|
wantThinking: map[string]any{"type": "disabled"},
|
|
wantOutputCfg: false,
|
|
},
|
|
{
|
|
name: "adaptive_writes_adaptive_and_output_config",
|
|
body: map[string]any{"model": "m"},
|
|
adaptiveEffort: "high",
|
|
wantThinking: map[string]any{"type": "adaptive", "display": "summarized"},
|
|
wantOutputCfg: true,
|
|
},
|
|
{
|
|
name: "empty_effort_no_op",
|
|
body: map[string]any{"model": "m"},
|
|
wantThinking: nil,
|
|
wantOutputCfg: false,
|
|
},
|
|
{
|
|
name: "disabled_overrides_existing_adaptive_thinking",
|
|
body: map[string]any{"thinking": map[string]any{"type": "adaptive", "display": "summarized"}, "output_config": map[string]any{"effort": "high"}},
|
|
thinkingEffort: "disabled",
|
|
wantThinking: map[string]any{"type": "disabled"},
|
|
wantOutputCfg: false,
|
|
},
|
|
{
|
|
name: "disabled_normalizes_aliases",
|
|
body: map[string]any{"model": "m"},
|
|
thinkingEffort: "off",
|
|
wantThinking: map[string]any{"type": "disabled"},
|
|
wantOutputCfg: false,
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
req := StreamRequest{
|
|
ThinkingEffort: tc.thinkingEffort,
|
|
AnthropicThinkingEffort: tc.adaptiveEffort,
|
|
RequestKnobs: map[string]any{},
|
|
}
|
|
applyAnthropicThinkingConfig(tc.body, req)
|
|
|
|
gotThinking, hasThinking := tc.body["thinking"]
|
|
if tc.wantThinking == nil {
|
|
if hasThinking {
|
|
t.Fatalf("expected no thinking key, got %v", gotThinking)
|
|
}
|
|
} else {
|
|
if !hasThinking {
|
|
t.Fatalf("expected thinking=%v, key absent", tc.wantThinking)
|
|
}
|
|
if !mapEqual(gotThinking, tc.wantThinking) {
|
|
t.Fatalf("thinking mismatch\nwant: %v\ngot: %v", tc.wantThinking, gotThinking)
|
|
}
|
|
}
|
|
|
|
_, hasOutputCfg := tc.body["output_config"]
|
|
if hasOutputCfg != tc.wantOutputCfg {
|
|
t.Fatalf("output_config presence=%v, want %v", hasOutputCfg, tc.wantOutputCfg)
|
|
}
|
|
|
|
if tc.thinkingEffort == "disabled" || tc.thinkingEffort == "off" {
|
|
gotKnob := req.RequestKnobs["thinking_disabled_provider_param"]
|
|
if gotKnob != "thinking.type" {
|
|
t.Fatalf("knob thinking_disabled_provider_param=%v, want thinking.type", gotKnob)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestApplyAnthropicThinkingConfigOverridePath simulates the RequestBodyOverride
|
|
// branch: the adapter receives a body that did NOT go through the normal
|
|
// construction (no tools/messages/system written by the adapter). Before the
|
|
// fix, thinking config was skipped entirely on this path. Now
|
|
// applyAnthropicThinkingConfig runs unconditionally and disables thinking.
|
|
func TestApplyAnthropicThinkingConfigOverridePath(t *testing.T) {
|
|
overrideBody := map[string]any{
|
|
"model": "claude-x",
|
|
"messages": []any{map[string]any{"role": "user", "content": "hi"}},
|
|
"thinking": map[string]any{"type": "adaptive", "display": "summarized"},
|
|
"output_config": map[string]any{"effort": "high"},
|
|
"stream": true,
|
|
}
|
|
req := StreamRequest{
|
|
ThinkingEffort: "disabled",
|
|
RequestKnobs: map[string]any{},
|
|
}
|
|
|
|
applyAnthropicThinkingConfig(overrideBody, req)
|
|
|
|
gotThinking := overrideBody["thinking"]
|
|
if !mapEqual(gotThinking, map[string]any{"type": "disabled"}) {
|
|
t.Fatalf("override path: thinking not forced to disabled, got %v", gotThinking)
|
|
}
|
|
if _, hasOutputCfg := overrideBody["output_config"]; hasOutputCfg {
|
|
t.Fatalf("override path: output_config should be dropped on disabled, still present")
|
|
}
|
|
if gotKnob := req.RequestKnobs["thinking_disabled_provider_param"]; gotKnob != "thinking.type" {
|
|
t.Fatalf("override path: knob=%v, want thinking.type", gotKnob)
|
|
}
|
|
}
|
|
|
|
func mapEqual(a, b any) bool {
|
|
ma, okA := a.(map[string]any)
|
|
mb, okB := b.(map[string]any)
|
|
if !okA || !okB {
|
|
return a == b
|
|
}
|
|
if len(ma) != len(mb) {
|
|
return false
|
|
}
|
|
for k, va := range ma {
|
|
vb, ok := mb[k]
|
|
if !ok || !mapEqual(va, vb) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|