feat(ai): add max thinking level

This commit is contained in:
Mario Zechner
2026-07-09 22:30:53 +02:00
parent 8973ae28ab
commit fbdd46389c
61 changed files with 441 additions and 168 deletions
@@ -19,7 +19,7 @@ import {
import { getModel } from "../src/compat.ts";
import type { AssistantMessage, Context, Message, Model, Tool, ToolResultMessage, Transport } from "../src/types.ts";
type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
interface Args {
turns: number;
@@ -58,7 +58,14 @@ function parseArgs(argv: string[]): Args {
break;
case "--reasoning": {
const value = required(argv[++i], arg);
if (value !== "minimal" && value !== "low" && value !== "medium" && value !== "high" && value !== "xhigh") {
if (
value !== "minimal" &&
value !== "low" &&
value !== "medium" &&
value !== "high" &&
value !== "xhigh" &&
value !== "max"
) {
throw new Error(`Invalid --reasoning: ${value}`);
}
reasoning = value;
@@ -90,7 +97,7 @@ function printHelp(): void {
Options:
--turns <n> Number of user turns. Default: ${DEFAULT_TURNS}
--transport <mode> sse | websocket | websocket-cached | auto. Default: websocket-cached
--reasoning <level> minimal | low | medium | high | xhigh. Default: low
--reasoning <level> minimal | low | medium | high | xhigh | max. Default: low
--max-tokens <n> Max output tokens per model request. Default: ${DEFAULT_MAX_TOKENS}
--session-id <id> Session id for websocket/cache state
`);
@@ -57,12 +57,14 @@ describe("Copilot Claude via Anthropic Messages", () => {
it("applies Copilot-specific adaptive thinking effort overrides", () => {
const opus47 = getModel("github-copilot", "claude-opus-4.7");
expect(opus47.thinkingLevelMap).toMatchObject({ minimal: "low", xhigh: "xhigh" });
expect(opus47.thinkingLevelMap).toMatchObject({ minimal: "low", xhigh: "xhigh", max: "max" });
expect(getSupportedThinkingLevels(opus47)).toContain("xhigh");
expect(getSupportedThinkingLevels(opus47)).toContain("max");
const sonnet46 = getModel("github-copilot", "claude-sonnet-4.6");
expect(sonnet46.thinkingLevelMap).toMatchObject({ minimal: "low", xhigh: "max" });
expect(getSupportedThinkingLevels(sonnet46)).toContain("xhigh");
expect(sonnet46.thinkingLevelMap).toMatchObject({ minimal: "low", max: "max" });
expect(getSupportedThinkingLevels(sonnet46)).toContain("max");
expect(getSupportedThinkingLevels(sonnet46)).not.toContain("xhigh");
});
it("uses Bearer auth, Copilot headers, and valid Anthropic Messages payload", async () => {
+89
View File
@@ -0,0 +1,89 @@
import { describe, expect, it } from "vitest";
import { streamSimple as streamSimpleOpenAICodexResponses } from "../src/api/openai-codex-responses.ts";
import { clampThinkingLevel, getModel, getSupportedThinkingLevels } from "../src/compat.ts";
import type { Context, Model } from "../src/types.ts";
function mockToken(): string {
const payload = Buffer.from(
JSON.stringify({ "https://api.openai.com/auth": { chatgpt_account_id: "acc_test" } }),
"utf8",
).toString("base64");
return `aaa.${payload}.bbb`;
}
describe("max thinking level", () => {
it("is opt-in for ordinary reasoning models", () => {
const model: Model<"openai-completions"> = {
id: "ordinary-reasoning",
name: "Ordinary Reasoning",
api: "openai-completions",
provider: "test",
baseUrl: "https://example.com/v1",
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096,
};
expect(getSupportedThinkingLevels(model)).toEqual(["off", "minimal", "low", "medium", "high"]);
expect(clampThinkingLevel(model, "max")).toBe("high");
});
it.each(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"] as const)(
"exposes xhigh and max for openai-codex/%s",
(modelId) => {
const model = getModel("openai-codex", modelId);
expect(model).toBeDefined();
expect(model?.thinkingLevelMap).toMatchObject({ xhigh: "xhigh", max: "max" });
expect(getSupportedThinkingLevels(model!)).toEqual([
"off",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max",
]);
},
);
it("supports a hole between high and max", () => {
const model: Model<"openai-completions"> = {
id: "high-and-max",
name: "High and Max",
api: "openai-completions",
provider: "test",
baseUrl: "https://example.com/v1",
reasoning: true,
thinkingLevelMap: { xhigh: null, max: "max" },
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 128000,
maxTokens: 4096,
};
expect(getSupportedThinkingLevels(model)).toEqual(["off", "minimal", "low", "medium", "high", "max"]);
expect(clampThinkingLevel(model, "xhigh")).toBe("max");
});
it("sends max to the Codex Responses API", async () => {
const model = getModel("openai-codex", "gpt-5.6-sol")!;
const context: Context = {
systemPrompt: "You are a helpful assistant.",
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
};
let payload: unknown;
await streamSimpleOpenAICodexResponses(model, context, {
apiKey: mockToken(),
reasoning: "max",
onPayload: (request) => {
payload = request;
throw new Error("payload captured");
},
}).result();
expect(payload).toMatchObject({ reasoning: { effort: "max", summary: "auto" } });
});
});
@@ -305,7 +305,7 @@ describe("openai-completions tool_choice", () => {
low: "high",
medium: "high",
high: "high",
xhigh: "max",
max: "max",
});
}
});
@@ -316,7 +316,7 @@ describe("openai-completions tool_choice", () => {
{ reasoning: "low", effort: "high" },
{ reasoning: "medium", effort: "high" },
{ reasoning: "high", effort: "high" },
{ reasoning: "xhigh", effort: "max" },
{ reasoning: "max", effort: "max" },
] as const;
for (const testCase of cases) {
+38 -16
View File
@@ -2,35 +2,47 @@ import { describe, expect, it } from "vitest";
import { getModel, getSupportedThinkingLevels } from "../src/compat.ts";
describe("getSupportedThinkingLevels", () => {
it("includes xhigh for Anthropic Opus 4.6 on anthropic-messages API", () => {
it("includes max but not xhigh for Anthropic Opus 4.6 on anthropic-messages API", () => {
const model = getModel("anthropic", "claude-opus-4-6");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
expect(getSupportedThinkingLevels(model!)).toContain("max");
expect(getSupportedThinkingLevels(model!)).not.toContain("xhigh");
});
it("includes xhigh for Anthropic Opus 4.8 on anthropic-messages API", () => {
it("includes xhigh and max for Anthropic Opus 4.8 on anthropic-messages API", () => {
const model = getModel("anthropic", "claude-opus-4-8");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
expect(getSupportedThinkingLevels(model!)).toContain("max");
});
it("includes xhigh for Anthropic Opus 4.8 on anthropic-messages API", () => {
const model = getModel("anthropic", "claude-opus-4-8");
it("includes max but not xhigh for Anthropic Sonnet 4.6 on anthropic-messages API", () => {
const model = getModel("anthropic", "claude-sonnet-4-6");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("max");
expect(getSupportedThinkingLevels(model!)).not.toContain("xhigh");
});
it("includes xhigh and max for Anthropic Sonnet 5 on anthropic-messages API", () => {
const model = getModel("anthropic", "claude-sonnet-5");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
expect(getSupportedThinkingLevels(model!)).toContain("max");
});
it("includes xhigh but not off for Anthropic Claude Fable 5 on anthropic-messages API", () => {
it("includes xhigh and max but not off for Anthropic Claude Fable 5 on anthropic-messages API", () => {
const model = getModel("anthropic", "claude-fable-5");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
expect(getSupportedThinkingLevels(model!)).toContain("max");
expect(getSupportedThinkingLevels(model!)).not.toContain("off");
});
it("does not include xhigh for Claude Sonnet 4.5", () => {
it("does not include xhigh or max for Claude Sonnet 4.5", () => {
const model = getModel("anthropic", "claude-sonnet-4-5");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).not.toContain("xhigh");
expect(getSupportedThinkingLevels(model!)).not.toContain("max");
});
it.each(["gpt-5.4", "gpt-5.5", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] as const)(
@@ -43,11 +55,19 @@ describe("getSupportedThinkingLevels", () => {
);
it.each(["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] as const)(
"includes xhigh for OpenAI %s models",
"includes xhigh and max for OpenAI %s models",
(modelId) => {
const model = getModel("openai", modelId);
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
expect(getSupportedThinkingLevels(model!)).toEqual([
"off",
"minimal",
"low",
"medium",
"high",
"xhigh",
"max",
]);
},
);
@@ -63,16 +83,16 @@ describe("getSupportedThinkingLevels", () => {
expect(getSupportedThinkingLevels(model!)).toEqual(["medium", "high", "xhigh"]);
});
it("includes only high/xhigh plus off for DeepSeek V4 Flash on the DeepSeek provider", () => {
it("includes only high/max plus off for DeepSeek V4 Flash on the DeepSeek provider", () => {
const model = getModel("deepseek", "deepseek-v4-flash");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toEqual(["off", "high", "xhigh"]);
expect(getSupportedThinkingLevels(model!)).toEqual(["off", "high", "max"]);
});
it("includes only high/xhigh plus off for DeepSeek V4 Flash on opencode-go", () => {
it("includes only high/max plus off for DeepSeek V4 Flash on opencode-go", () => {
const model = getModel("opencode-go", "deepseek-v4-flash");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toEqual(["off", "high", "xhigh"]);
expect(getSupportedThinkingLevels(model!)).toEqual(["off", "high", "max"]);
});
it("includes only high plus off for OpenCode Go Kimi K2.6", () => {
@@ -102,16 +122,18 @@ describe("getSupportedThinkingLevels", () => {
expect(getSupportedThinkingLevels(model!)).toEqual(["off", "high", "xhigh"]);
});
it("includes xhigh for OpenRouter Opus 4.6 (openai-completions API)", () => {
it("includes max but not xhigh for OpenRouter Opus 4.6 (openai-completions API)", () => {
const model = getModel("openrouter", "anthropic/claude-opus-4.6");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
expect(getSupportedThinkingLevels(model!)).toContain("max");
expect(getSupportedThinkingLevels(model!)).not.toContain("xhigh");
});
it("includes xhigh but not off for Bedrock Claude Fable 5", () => {
it("includes xhigh and max but not off for Bedrock Claude Fable 5", () => {
const model = getModel("amazon-bedrock", "global.anthropic.claude-fable-5");
expect(model).toBeDefined();
expect(getSupportedThinkingLevels(model!)).toContain("xhigh");
expect(getSupportedThinkingLevels(model!)).toContain("max");
expect(getSupportedThinkingLevels(model!)).not.toContain("off");
});
});