Enable Claude extended thinking by default, drop the settings field

Fixed 8192 budget; the provider profile key stays a hidden override (0 = off).
Per-turn composer control is future work.
This commit is contained in:
Rohit C Prasad
2026-07-23 07:57:04 -07:00
committed by Rohit P
parent 2968254713
commit ba99978e03
6 changed files with 41 additions and 33 deletions
+6
View File
@@ -35,6 +35,12 @@ from .capabilities import capabilities_for
# Required by the Messages API; a ceiling, not a spend target. # Required by the Messages API; a ceiling, not a spend target.
DEFAULT_MAX_TOKENS = 16000 DEFAULT_MAX_TOKENS = 16000
# Extended thinking is ON by default (owner call 2026-07-23: no user-facing setting —
# most users wouldn't know what a budget is; a per-turn composer control is future work).
# The provider profile's `thinking_budget` remains a hidden override: a number replaces
# the default, 0 disables thinking entirely.
DEFAULT_THINKING_BUDGET = 8192
# Anthropic stop_reason → the engine's OpenAI-shaped finish_reason vocabulary. # Anthropic stop_reason → the engine's OpenAI-shaped finish_reason vocabulary.
_STOP_REASON_MAP = { _STOP_REASON_MAP = {
"end_turn": "stop", "end_turn": "stop",
+7 -8
View File
@@ -106,11 +106,15 @@ def _build_openai(profile: dict[str, Any], secrets: Any) -> ProviderClient:
def _build_anthropic(profile: dict[str, Any], secrets: Any) -> ProviderClient: def _build_anthropic(profile: dict[str, Any], secrets: Any) -> ProviderClient:
# Key resolution stays in AnthropicProvider/resolve_api_key (explicit → env → SecretStore), # Key resolution stays in AnthropicProvider/resolve_api_key (explicit → env → SecretStore),
# deferred to first call so the provider can be built before a key exists. # deferred to first call so the provider can be built before a key exists.
# thinking_budget: hidden profile override — absent/invalid → the default (ON),
# explicit 0 → off (see DEFAULT_THINKING_BUDGET).
from .anthropic_provider import DEFAULT_THINKING_BUDGET
api_key = ((profile or {}).get("api_key") or "").strip() or None api_key = ((profile or {}).get("api_key") or "").strip() or None
try: try:
thinking_budget = int(str((profile or {}).get("thinking_budget") or "").strip()) thinking_budget = int(str((profile or {}).get("thinking_budget") or "").strip())
except ValueError: except ValueError:
thinking_budget = 0 thinking_budget = DEFAULT_THINKING_BUDGET
return AnthropicProvider( return AnthropicProvider(
api_key=api_key, secrets=secrets, thinking_budget=thinking_budget api_key=api_key, secrets=secrets, thinking_budget=thinking_budget
) )
@@ -225,13 +229,8 @@ DESCRIPTORS: list[ProviderDescriptor] = [
secret=True, secret=True,
placeholder="sk-ant-…", placeholder="sk-ant-…",
), ),
ProviderField( # No thinking_budget field (owner call 2026-07-23): extended thinking is
"thinking_budget", # on by default; the profile key stays a hidden override (0 = off).
"Extended thinking budget (tokens, optional)",
required=False,
placeholder="e.g. 8192 — blank = off",
help="Turns on Claude's extended thinking for every request, with this token budget. The thought process shows in the transcript.",
),
], ],
build=_build_anthropic, build=_build_anthropic,
recommended_model="claude-fable-5", recommended_model="claude-fable-5",
+1 -1
View File
@@ -328,7 +328,7 @@ const PROVIDERS = [
// openai: configured + used (drives the "Last used" sub-line and the status dot). // openai: configured + used (drives the "Last used" sub-line and the status dot).
{ name: "openai", title: "OpenAI", needs_key: true, fields: [{ key: "api_key", label: "OpenAI API key", secret: true, required: true, help: "", placeholder: "sk-…" }], configured: true, values: {}, suggested_models: ["gpt-5.5"], key_set_at: "2026-06-12", last_used_at: Math.floor(Date.now() / 1000) - 7200 }, { name: "openai", title: "OpenAI", needs_key: true, fields: [{ key: "api_key", label: "OpenAI API key", secret: true, required: true, help: "", placeholder: "sk-…" }], configured: true, values: {}, suggested_models: ["gpt-5.5"], key_set_at: "2026-06-12", last_used_at: Math.floor(Date.now() / 1000) - 7200 },
// anthropic: configured but never used ("Not used yet"). // anthropic: configured but never used ("Not used yet").
{ name: "anthropic", title: "Claude (Anthropic)", needs_key: true, fields: [{ key: "api_key", label: "API key", secret: true, required: true, help: "", placeholder: "sk-…" }, { key: "thinking_budget", label: "Extended thinking budget (tokens, optional)", secret: false, required: false, help: "Turns on Claude's extended thinking for every request.", placeholder: "e.g. 8192 — blank = off" }], configured: true, values: {}, suggested_models: ["claude-opus-4-8"], key_set_at: null, last_used_at: null }, { name: "anthropic", title: "Claude (Anthropic)", needs_key: true, fields: [{ key: "api_key", label: "API key", secret: true, required: true, help: "", placeholder: "sk-…" }], configured: true, values: {}, suggested_models: ["claude-opus-4-8"], key_set_at: null, last_used_at: null },
// zai: an OpenAI-compatible vendor — unconfigured, with a prefilled editable endpoint + blurb. // zai: an OpenAI-compatible vendor — unconfigured, with a prefilled editable endpoint + blurb.
{ name: "zai", title: "Z AI (GLM)", needs_key: true, blurb: "Uses Z AI's OpenAI-compatible API — the endpoint is prefilled, just add your key.", fields: [{ key: "api_key", label: "Z AI API key", secret: true, required: true, help: "", placeholder: "" }, { key: "base_url", label: "Endpoint", secret: false, required: false, help: "Prefilled with Z AI's international endpoint.", placeholder: "https://api.z.ai/api/paas/v4", default: "https://api.z.ai/api/paas/v4" }], configured: false, values: {}, suggested_models: ["glm-5.2"], key_set_at: null, last_used_at: null }, { name: "zai", title: "Z AI (GLM)", needs_key: true, blurb: "Uses Z AI's OpenAI-compatible API — the endpoint is prefilled, just add your key.", fields: [{ key: "api_key", label: "Z AI API key", secret: true, required: true, help: "", placeholder: "" }, { key: "base_url", label: "Endpoint", secret: false, required: false, help: "Prefilled with Z AI's international endpoint.", placeholder: "https://api.z.ai/api/paas/v4", default: "https://api.z.ai/api/paas/v4" }], configured: false, values: {}, suggested_models: ["glm-5.2"], key_set_at: null, last_used_at: null },
// ollama: keyless local provider — "configured" without proving anything runs; the // ollama: keyless local provider — "configured" without proving anything runs; the
+11 -10
View File
@@ -53,20 +53,21 @@ test("a configured provider's form opens with the saved state, no plaintext key"
await expect(page.getByTestId("set-field-api_key")).toHaveAttribute("placeholder", "••••••••"); await expect(page.getByTestId("set-field-api_key")).toHaveAttribute("placeholder", "••••••••");
}); });
test("non-secret extras blur-save on a configured provider (thinking budget)", async ({ test("non-secret fields blur-save on a configured provider (ollama endpoint)", async ({
page, page,
}) => { }) => {
// Owner-hit 2026-07-23: typed a thinking budget, left Settings, value silently never // Owner-hit 2026-07-23 (as the thinking-budget field, since folded into a default):
// saved — the Test button was the form's only save path. Blur now saves extras. // the Test button was the form's only save path — typing into a non-secret field and
// leaving Settings silently discarded it. Blur now saves.
await openModels(page); await openModels(page);
await page.getByTestId("set-provider-anthropic").click(); await page.getByTestId("set-provider-ollama").click();
const budget = page.getByTestId("set-field-thinking_budget"); const endpoint = page.getByTestId("set-field-base_url");
await budget.fill("8192"); await endpoint.fill("http://127.0.0.1:9999");
await budget.blur(); await endpoint.blur();
await expect(page.getByTestId("set-field-saved-thinking_budget")).toBeVisible(); await expect(page.getByTestId("set-field-saved-base_url")).toBeVisible();
// Leave and come back: the value survived (served from the provider's stored values). // Leave and come back: the value survived (served from the provider's stored values).
await page.getByTestId("set-back").click(); await page.getByTestId("set-back").click();
await page.getByTestId("set-provider-anthropic").click(); await page.getByTestId("set-provider-ollama").click();
await expect(page.getByTestId("set-field-thinking_budget")).toHaveValue("8192"); await expect(page.getByTestId("set-field-base_url")).toHaveValue("http://127.0.0.1:9999");
}); });
+11
View File
@@ -623,3 +623,14 @@ def test_convert_replays_thinking_blocks_ahead_of_tool_use():
assistant = msgs[1]["content"] assistant = msgs[1]["content"]
assert assistant[0] == {"type": "thinking", "thinking": "plan", "signature": "S"} assert assistant[0] == {"type": "thinking", "thinking": "plan", "signature": "S"}
assert assistant[1]["type"] == "tool_use" assert assistant[1]["type"] == "tool_use"
def test_thinking_defaults_on_with_hidden_profile_override():
"""No user-facing setting (owner call 2026-07-23): thinking is ON by default; the
profile's thinking_budget stays a hidden override, 0 = off."""
from coworker.providers.anthropic_provider import DEFAULT_THINKING_BUDGET
from coworker.providers.registry import build_provider_client
assert build_provider_client("anthropic", {}, None).thinking_budget == DEFAULT_THINKING_BUDGET
assert build_provider_client("anthropic", {"thinking_budget": "2048"}, None).thinking_budget == 2048
assert build_provider_client("anthropic", {"thinking_budget": "0"}, None).thinking_budget == 0
+5 -14
View File
@@ -769,22 +769,13 @@ def test_google_one_click_paused_but_manual_alive(tmp_path):
def test_set_provider_persists_extra_fields(tmp_path): def test_set_provider_persists_extra_fields(tmp_path):
"""Non-secret descriptor extras (anthropic thinking_budget) round-trip: saved into the """Non-secret descriptor extras (ollama's endpoint) round-trip: saved into the
profile, echoed by get_providers for form prefill, cleared by an empty save.""" profile, echoed by get_providers for form prefill, cleared by an empty save."""
manager = SessionManager(workspace=tmp_path, provider=ScriptedProvider([])) manager = SessionManager(workspace=tmp_path, provider=ScriptedProvider([]))
assert manager.set_provider( assert manager.set_provider("ollama", {"base_url": "http://127.0.0.1:9999"})["ok"]
"anthropic", {"api_key": "sk-ant-test", "thinking_budget": "8192"}
)["ok"]
providers = {p["name"]: p for p in manager.get_providers()} providers = {p["name"]: p for p in manager.get_providers()}
assert providers["anthropic"]["values"]["thinking_budget"] == "8192" assert providers["ollama"]["values"]["base_url"] == "http://127.0.0.1:9999"
from coworker.providers.registry import build_provider_client manager.set_provider("ollama", {"base_url": ""})
built = build_provider_client(
"anthropic", manager.secrets.get("provider:anthropic"), manager.secrets
)
assert built.thinking_budget == 8192
manager.set_provider("anthropic", {"thinking_budget": ""})
providers = {p["name"]: p for p in manager.get_providers()} providers = {p["name"]: p for p in manager.get_providers()}
assert "thinking_budget" not in providers["anthropic"]["values"] assert "base_url" not in providers["ollama"]["values"]