summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-30 23:54:41 +0900
committerAdam Malczewski <[email protected]>2026-05-30 23:54:41 +0900
commit83832e201b71b35beada967d5872c532364949c4 (patch)
tree14af5017e13e795d653bbfedac891bf56eb6be92
parent667abdd14f9e387a94d232276843525ae72d3d9d (diff)
downloaddispatch-83832e201b71b35beada967d5872c532364949c4.tar.gz
dispatch-83832e201b71b35beada967d5872c532364949c4.zip
fix(agent): stream thinking for all adaptive Claude models, not just Opus 4.7
Extended thinking was gated on a hardcoded `model === "claude-opus-4-7"` check, so newer/other adaptive models (Opus 4.8, Opus/Sonnet 4.6) fell into the classic `thinking: { type: "enabled" }` branch. Adaptive models default thinking display to "omitted", so no thinking was streamed — the UI showed nothing for Claude while DeepSeek (a separate openai-compatible path) worked. Replace the string check with a pure helper `anthropicThinkingProviderOptions` that mirrors opencode's transform.ts detection: - adaptive (`type: "adaptive"`) for Opus 4.7+ (version-parsed) and Opus/Sonnet 4.6 (id substring; handles dash and dot forms); - `display: "summarized"` ONLY for Opus 4.7+ (they default to omitted and must be forced); Opus/Sonnet 4.6 stream thinking without it; - all other Claude models keep classic `enabled` + budgetTokens. Pure function (no provider/streamText/network), unit-tested directly: Opus 4.8 (the reported bug), Opus 4.7, Sonnet/Opus 4.6, Opus 4.5 + dated Sonnet (enabled), a future Opus 4.9 (proves version-parse), and effort->budget mapping.
-rw-r--r--packages/core/src/agent/agent.tsbin54519 -> 56551 bytes
-rw-r--r--packages/core/tests/agent/agent.test.ts63
2 files changed, 62 insertions, 1 deletions
diff --git a/packages/core/src/agent/agent.ts b/packages/core/src/agent/agent.ts
index 4638301..d4bb761 100644
--- a/packages/core/src/agent/agent.ts
+++ b/packages/core/src/agent/agent.ts
Binary files differ
diff --git a/packages/core/tests/agent/agent.test.ts b/packages/core/tests/agent/agent.test.ts
index a12ea07..7560443 100644
--- a/packages/core/tests/agent/agent.test.ts
+++ b/packages/core/tests/agent/agent.test.ts
@@ -30,7 +30,7 @@ vi.mock("@ai-sdk/openai-compatible", () => ({
})),
}));
-const { Agent } = await import("../../src/agent/agent.js");
+const { Agent, anthropicThinkingProviderOptions } = await import("../../src/agent/agent.js");
const { streamText } = await import("ai");
function makeConfig(overrides: Partial<AgentConfig> = {}): AgentConfig {
@@ -1392,3 +1392,64 @@ describe("Agent", () => {
expect(events.some((e) => e.type === "usage")).toBe(false);
});
});
+
+describe("anthropicThinkingProviderOptions — adaptive-thinking model detection", () => {
+ // Pure function: no provider construction, no streamText, no network I/O.
+ // Mirrors opencode's transform.ts detection — Opus 4.7+ AND Opus/Sonnet 4.6
+ // are adaptive; only Opus 4.7+ needs display:"summarized" to surface thinking.
+
+ it("Opus 4.8 → adaptive + display:summarized (the reported bug)", () => {
+ expect(anthropicThinkingProviderOptions("claude-opus-4-8", "max")).toEqual({
+ thinking: { type: "adaptive", display: "summarized" },
+ effort: "max",
+ });
+ });
+
+ it("Opus 4.7 → adaptive + display:summarized (dash and dot id forms)", () => {
+ const expected = { thinking: { type: "adaptive", display: "summarized" }, effort: "high" };
+ expect(anthropicThinkingProviderOptions("claude-opus-4-7", "high")).toEqual(expected);
+ expect(anthropicThinkingProviderOptions("claude-opus-4.7", "high")).toEqual(expected);
+ });
+
+ it("Sonnet 4.6 → adaptive WITHOUT display (dash and dot id forms)", () => {
+ const expected = { thinking: { type: "adaptive" }, effort: "medium" };
+ expect(anthropicThinkingProviderOptions("claude-sonnet-4-6", "medium")).toEqual(expected);
+ expect(anthropicThinkingProviderOptions("claude-sonnet-4.6", "medium")).toEqual(expected);
+ });
+
+ it("Opus 4.6 → adaptive WITHOUT display", () => {
+ expect(anthropicThinkingProviderOptions("claude-opus-4-6", "high")).toEqual({
+ thinking: { type: "adaptive" },
+ effort: "high",
+ });
+ });
+
+ it("older Claude (Opus 4.5, dated Sonnet) → classic enabled thinking", () => {
+ expect(anthropicThinkingProviderOptions("claude-opus-4-5", "max")).toEqual({
+ thinking: { type: "enabled", budgetTokens: 31999 },
+ });
+ expect(anthropicThinkingProviderOptions("claude-sonnet-4-20250514", "high")).toEqual({
+ thinking: { type: "enabled", budgetTokens: 16000 },
+ });
+ });
+
+ it("uses a version parse, not a hardcoded string (future Opus 4.9 is adaptive)", () => {
+ expect(anthropicThinkingProviderOptions("claude-opus-4-9", "high")).toEqual({
+ thinking: { type: "adaptive", display: "summarized" },
+ effort: "high",
+ });
+ });
+
+ it("maps reasoning effort → budgetTokens for enabled (non-adaptive) models", () => {
+ const budget = (e: "low" | "medium" | "high" | "max") => {
+ const opts = anthropicThinkingProviderOptions("claude-3-7-sonnet", e) as {
+ thinking: { type: "enabled"; budgetTokens: number };
+ };
+ return opts.thinking.budgetTokens;
+ };
+ expect(budget("low")).toBe(2000);
+ expect(budget("medium")).toBe(5000);
+ expect(budget("high")).toBe(16000);
+ expect(budget("max")).toBe(31999);
+ });
+});