mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-05 15:19:54 +08:00
- Inspect `input` items for the latest `configuration_update` reasoning effort before falling back to top-level configuration. - Route `codex`, `xai`, and `openai-response` providers through usage-specific thinking config extraction. - Support `none`, `auto`, and explicit reasoning level modes from in-turn updates. Closes: #5714
157 lines
6.3 KiB
Go
157 lines
6.3 KiB
Go
package thinking_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
|
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/codex"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func TestExtractCodexReasoningEffortWithConfigurationUpdate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
provider string
|
|
model string
|
|
body string
|
|
wantRequestEffort string
|
|
wantTranslatedEffort string
|
|
}{
|
|
{
|
|
name: "codex extracts configuration_update effort over top-level",
|
|
provider: "codex",
|
|
model: "gpt-6-astra",
|
|
body: `{"model":"gpt-6-astra","reasoning":{"effort":"xhigh","summary":"auto"},"input":[{"type":"configuration_update","reasoning":{"effort":"low"}}]}`,
|
|
wantRequestEffort: "low",
|
|
wantTranslatedEffort: "low",
|
|
},
|
|
{
|
|
name: "openai-response extracts configuration_update effort over top-level",
|
|
provider: "openai-response",
|
|
model: "gpt-6-astra",
|
|
body: `{"model":"gpt-6-astra","reasoning":{"effort":"xhigh","summary":"auto"},"input":[{"type":"configuration_update","reasoning":{"effort":"low"}}]}`,
|
|
wantRequestEffort: "low",
|
|
wantTranslatedEffort: "low",
|
|
},
|
|
{
|
|
name: "codex picks latest configuration_update when multiple are present",
|
|
provider: "codex",
|
|
model: "gpt-6-astra",
|
|
body: `{
|
|
"model":"gpt-6-astra",
|
|
"reasoning":{"effort":"xhigh","summary":"auto"},
|
|
"input":[
|
|
{"type":"configuration_update","reasoning":{"effort":"low"}},
|
|
{"role":"user","content":"second turn"},
|
|
{"type":"configuration_update","reasoning":{"effort":"medium"}}
|
|
]
|
|
}`,
|
|
wantRequestEffort: "medium",
|
|
wantTranslatedEffort: "medium",
|
|
},
|
|
{
|
|
name: "codex falls back to top-level when configuration_update has no reasoning effort",
|
|
provider: "codex",
|
|
model: "gpt-6-astra",
|
|
body: `{"model":"gpt-6-astra","reasoning":{"effort":"xhigh","summary":"auto"},"input":[{"type":"configuration_update","tools":[]}]}`,
|
|
wantRequestEffort: "xhigh",
|
|
wantTranslatedEffort: "xhigh",
|
|
},
|
|
{
|
|
name: "codex handles configuration_update effort none",
|
|
provider: "codex",
|
|
model: "gpt-6-astra",
|
|
body: `{"model":"gpt-6-astra","reasoning":{"effort":"xhigh"},"input":[{"type":"configuration_update","reasoning":{"effort":"none"}}]}`,
|
|
wantRequestEffort: "none",
|
|
wantTranslatedEffort: "none",
|
|
},
|
|
{
|
|
name: "codex handles configuration_update effort auto",
|
|
provider: "codex",
|
|
model: "gpt-6-astra",
|
|
body: `{"model":"gpt-6-astra","reasoning":{"effort":"xhigh"},"input":[{"type":"configuration_update","reasoning":{"effort":"auto"}}]}`,
|
|
wantRequestEffort: "auto",
|
|
wantTranslatedEffort: "auto",
|
|
},
|
|
{
|
|
name: "trailing configuration_update without reasoning effort preserves earlier effort",
|
|
provider: "codex",
|
|
model: "gpt-6-astra",
|
|
body: `{
|
|
"model":"gpt-6-astra",
|
|
"reasoning":{"effort":"xhigh"},
|
|
"input":[
|
|
{"type":"configuration_update","reasoning":{"effort":"low"}},
|
|
{"type":"configuration_update","tools":[]}
|
|
]
|
|
}`,
|
|
wantRequestEffort: "low",
|
|
wantTranslatedEffort: "low",
|
|
},
|
|
{
|
|
name: "invalid json returns empty effort",
|
|
provider: "codex",
|
|
model: "gpt-6-astra",
|
|
body: `{"model":"gpt-6-astra","reasoning":{"effort":"xhigh"},"input":`,
|
|
wantRequestEffort: "",
|
|
wantTranslatedEffort: "",
|
|
},
|
|
{
|
|
name: "codex falls back to top-level when input has no configuration_update",
|
|
provider: "codex",
|
|
model: "gpt-6-astra",
|
|
body: `{"model":"gpt-6-astra","reasoning":{"effort":"xhigh"},"input":[{"role":"user","content":"hello"}]}`,
|
|
wantRequestEffort: "xhigh",
|
|
wantTranslatedEffort: "xhigh",
|
|
},
|
|
{
|
|
name: "model suffix takes precedence over configuration_update for request effort",
|
|
provider: "codex",
|
|
model: "gpt-6-astra(high)",
|
|
body: `{"model":"gpt-6-astra","reasoning":{"effort":"xhigh"},"input":[{"type":"configuration_update","reasoning":{"effort":"low"}}]}`,
|
|
wantRequestEffort: "high",
|
|
wantTranslatedEffort: "low",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
gotRequest := thinking.ExtractReasoningEffort([]byte(tc.body), tc.provider, tc.model)
|
|
if gotRequest != tc.wantRequestEffort {
|
|
t.Fatalf("ExtractReasoningEffort() = %q, want %q", gotRequest, tc.wantRequestEffort)
|
|
}
|
|
|
|
gotTranslated := thinking.ExtractTranslatedReasoningEffort([]byte(tc.body), tc.provider)
|
|
if gotTranslated != tc.wantTranslatedEffort {
|
|
t.Fatalf("ExtractTranslatedReasoningEffort() = %q, want %q", gotTranslated, tc.wantTranslatedEffort)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApplyThinkingPreservesCodexTopLevelReasoningEffortBaseline(t *testing.T) {
|
|
// Preserving request-level baseline sent upstream ensures prompt prefix cache preservation.
|
|
body := []byte(`{"model":"gpt-6-astra","reasoning":{"effort":"xhigh","summary":"auto"},"input":[{"type":"configuration_update","reasoning":{"effort":"low"}}]}`)
|
|
applied, err := thinking.ApplyThinking(body, "gpt-6-astra", "codex", "codex", "codex")
|
|
if err != nil {
|
|
t.Fatalf("ApplyThinking() error = %v", err)
|
|
}
|
|
|
|
// Verify top-level request baseline is preserved for upstream prompt caching.
|
|
if gotTopEffort := gjson.GetBytes(applied, "reasoning.effort").String(); gotTopEffort != "xhigh" {
|
|
t.Fatalf("ApplyThinking() top-level reasoning.effort = %q, want %q", gotTopEffort, "xhigh")
|
|
}
|
|
if gotSummary := gjson.GetBytes(applied, "reasoning.summary").String(); gotSummary != "auto" {
|
|
t.Fatalf("ApplyThinking() reasoning.summary = %q, want %q", gotSummary, "auto")
|
|
}
|
|
if gotInputEffort := gjson.GetBytes(applied, "input.0.reasoning.effort").String(); gotInputEffort != "low" {
|
|
t.Fatalf("ApplyThinking() input[0].reasoning.effort = %q, want %q", gotInputEffort, "low")
|
|
}
|
|
|
|
// Verify usage reporting extracts the effective in-turn effort.
|
|
effort := thinking.ExtractTranslatedReasoningEffort(applied, "codex")
|
|
if effort != "low" {
|
|
t.Fatalf("ExtractTranslatedReasoningEffort(applied) = %q, want %q", effort, "low")
|
|
}
|
|
}
|