Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions internal/agent/compact.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,11 @@ func (a *Agent) summaryRequest(region []provider.Message, instructions string) p
Tools: schemas,
MaxTokens: a.summaryOutputBudget(),
Temperature: provider.OptionalTemperature(a.temperature),
// The compaction prompt explicitly asks for a direct briefing with no
// thinking, and the summary budget must not be consumed by reasoning on
// thinking models. Force reasoning off so compaction works even when the
// user has a reasoning effort configured (#9613).
EffortOverride: "none",
}
}

Expand Down
8 changes: 7 additions & 1 deletion internal/provider/responses/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,13 @@ func (c *client) buildRequestBody(req provider.Request) (map[string]any, bool, [
messages := provider.SanitizeToolPairing(provider.ModelMessages(req.Messages))
body := map[string]any{"model": c.model, "stream": true}

effort := strings.ToLower(strings.TrimSpace(c.effort))
// A per-request EffortOverride (e.g. summary/compaction forcing thinking
// off) wins over the client's configured effort; otherwise client effort
// reaches every request it builds (#9613).
effort := strings.ToLower(strings.TrimSpace(req.EffortOverride))
if effort == "" {
effort = strings.ToLower(strings.TrimSpace(c.effort))
}
if c.vendor == "deepseek" && (strings.EqualFold(strings.TrimSpace(c.model), "deepseek-v4-flash") || strings.EqualFold(strings.TrimSpace(c.model), "deepseek-v4-pro")) {
if effort == "medium" || effort == "xhigh" {
effort = "high"
Expand Down
24 changes: 24 additions & 0 deletions internal/provider/responses/responses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,30 @@ func TestDeepSeekV4ResponsesEffortAliasesNormalizeToHigh(t *testing.T) {
}
}

// TestEffortOverrideWinsOverClientEffort covers #9613: a per-request
// EffortOverride (used by compaction summary requests to force thinking off)
// must override the client's configured effort in the serialized body, while a
// request without an override still inherits the configured effort.
func TestEffortOverrideWinsOverClientEffort(t *testing.T) {
message := []provider.Message{{Role: provider.RoleUser, Content: "hi"}}

// No override -> client effort (high) reaches the wire unchanged.
inherited := New(Config{Name: "deepseek", BaseURL: "https://api.deepseek.com", Model: "deepseek-v4-pro", Effort: "high"}).(*client)
body, _, _ := inherited.buildRequestBody(provider.Request{Messages: message})
if got, _ := body["reasoning"].(map[string]any)["effort"].(string); got != "high" {
t.Fatalf("no override inherited effort = %q, want high", got)
}

// Override "none" wins even when the client effort is high.
for _, clientEffort := range []string{"high", ""} {
overridden := New(Config{Name: "deepseek", BaseURL: "https://api.deepseek.com", Model: "deepseek-v4-pro", Effort: clientEffort}).(*client)
b, _, _ := overridden.buildRequestBody(provider.Request{Messages: message, EffortOverride: "none"})
if got, _ := b["reasoning"].(map[string]any)["effort"].(string); got != "none" {
t.Fatalf("override none with client effort %q serialized as %q, want none", clientEffort, got)
}
}
}

func TestRequestSerializesExplicitMaxOutputTokens(t *testing.T) {
client := New(Config{Name: "responses", BaseURL: "https://example.com", Model: "model"}).(*client)
body, _, _ := client.buildRequestBody(provider.Request{
Expand Down
Loading