feat(ai): add max thinking level
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user