Files
oh-my-pi/packages/ai/test/anthropic-alignment.test.ts
T
can1357 a238205244 fix(ai): updated Anthropic request headers, tool prefixes, and token clamping
- Added OAuth request fingerprinting headers, including anthropic-client-platform/version.
- Lowered Anthropic header keys to lowercase and updated beta checks to `anthropic-beta`.
- Switched non-built-in tool names from `proxy_` to `_` while leaving built-ins unprefixed.
- Clamped max_tokens to 64,000 using `Math.min` and preserved passthrough for lower models.
2026-06-08 12:08:42 +02:00

1592 lines
51 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import * as tls from "node:tls";
import { Effort } from "@oh-my-pi/pi-ai";
import {
applyClaudeToolPrefix,
buildAnthropicClientOptions,
buildAnthropicHeaders,
buildAnthropicSystemBlocks,
claudeAgentSdkVersion,
claudeCodeSystemInstruction,
claudeCodeVersion,
claudeToolPrefix,
generateClaudeCloakingUserId,
isClaudeCloakingUserId,
mapStainlessArch,
mapStainlessOs,
streamAnthropic,
stripClaudeToolPrefix,
} from "@oh-my-pi/pi-ai/providers/anthropic";
import { getEnvApiKey } from "@oh-my-pi/pi-ai/stream";
import type { Context, Model, TJsonSchema, TokenTaskBudget, Tool } from "@oh-my-pi/pi-ai/types";
import * as z from "zod/v4";
import { withEnv } from "./helpers";
const ANTHROPIC_MODEL: Model<"anthropic-messages"> = {
id: "claude-sonnet-4-5",
name: "Claude Sonnet 4.5",
api: "anthropic-messages",
provider: "anthropic",
baseUrl: "https://api.anthropic.com",
reasoning: true,
input: ["text", "image"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200_000,
maxTokens: 8_192,
};
const CLOUDFLARE_ANTHROPIC_MODEL: Model<"anthropic-messages"> = {
...ANTHROPIC_MODEL,
id: "anthropic/claude-sonnet-4-5",
name: "Claude Sonnet 4.5 via Cloudflare",
provider: "cloudflare-ai-gateway",
baseUrl: "https://gateway.ai.cloudflare.com/v1/account/gateway/anthropic",
};
function createAbortedSignal(): AbortSignal {
const controller = new AbortController();
controller.abort();
return controller.signal;
}
type CaptureAnthropicOptions = {
isOAuth?: boolean;
metadata?: { user_id?: string };
thinkingEnabled?: boolean;
reasoning?: Effort;
temperature?: number;
topP?: number;
topK?: number;
taskBudget?: TokenTaskBudget;
toolChoice?: "auto" | "any" | "none" | { type: "tool"; name: string };
thinkingDisplay?: "summarized" | "omitted";
sessionId?: string;
};
function captureAnthropicPayload(
model: Model<"anthropic-messages">,
context: Context,
options?: CaptureAnthropicOptions,
): Promise<unknown> {
const { promise, resolve } = Promise.withResolvers<unknown>();
streamAnthropic(model, context, {
apiKey: "sk-ant-oat-test",
isOAuth: options?.isOAuth ?? true,
signal: createAbortedSignal(),
metadata: options?.metadata,
thinkingEnabled: options?.thinkingEnabled,
reasoning: options?.reasoning,
temperature: options?.temperature,
topP: options?.topP,
topK: options?.topK,
taskBudget: options?.taskBudget,
toolChoice: options?.toolChoice,
thinkingDisplay: options?.thinkingDisplay,
sessionId: options?.sessionId,
onPayload: payload => resolve(payload),
});
return promise;
}
function expectClaudeMetadataUserId(userId: string | undefined, expectedSessionId?: string): void {
expect(typeof userId).toBe("string");
const parsed = JSON.parse(userId ?? "{}") as {
device_id?: unknown;
account_uuid?: unknown;
session_id?: unknown;
};
expect(typeof parsed.device_id).toBe("string");
if (typeof parsed.device_id === "string") {
expect(parsed.device_id).toMatch(/^[0-9a-f]{64}$/);
}
if (parsed.account_uuid !== undefined) {
expect(parsed.account_uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
}
if (expectedSessionId) {
expect(parsed.session_id).toBe(expectedSessionId);
} else {
expect(typeof parsed.session_id).toBe("string");
if (typeof parsed.session_id === "string") {
expect(parsed.session_id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
}
}
}
describe("Anthropic request fingerprint alignment", () => {
it("maps Stainless OS and arch values from explicit inputs", () => {
expect(mapStainlessOs("darwin")).toBe("MacOS");
expect(mapStainlessOs("windows")).toBe("Windows");
expect(mapStainlessOs("linux")).toBe("Linux");
expect(mapStainlessOs("freebsd")).toBe("FreeBSD");
expect(mapStainlessOs("solaris")).toBe("Other::solaris");
expect(mapStainlessArch("x64")).toBe("x64");
expect(mapStainlessArch("amd64")).toBe("x64");
expect(mapStainlessArch("arm64")).toBe("arm64");
expect(mapStainlessArch("386")).toBe("x86");
expect(mapStainlessArch("x86")).toBe("x86");
expect(mapStainlessArch("sparc64")).toBe("other::sparc64");
});
it("uses runtime Stainless OS and arch mappings in Anthropic headers", () => {
const headers = buildAnthropicHeaders({
apiKey: "sk-ant-oat-test",
isOAuth: true,
stream: true,
});
expect(headers["X-Stainless-OS"]).toBe(mapStainlessOs(process.platform));
expect(headers["X-Stainless-Arch"]).toBe(mapStainlessArch(process.arch));
});
it("matches Claude Code OAuth header defaults", () => {
const sessionId = "167ec5b4-e711-4169-879f-84fa52679d9c";
const headers = buildAnthropicHeaders({
apiKey: "sk-ant-oat-test",
isOAuth: true,
stream: true,
claudeCodeSessionId: sessionId,
});
expect(headers.Accept).toBe("application/json");
expect(headers["User-Agent"]).toBe(
`claude-cli/${claudeCodeVersion} (external, local-agent, agent-sdk/${claudeAgentSdkVersion})`,
);
expect(headers["X-Claude-Code-Session-Id"]).toBe(sessionId);
expect(headers["x-client-request-id"]).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
});
it("sends redact-thinking beta only when thinking display is omitted", () => {
const baseArgs = {
model: ANTHROPIC_MODEL,
apiKey: "sk-ant-oat-test",
stream: true,
interleavedThinking: true,
hasTools: true,
thinkingEnabled: true,
} as const;
const visible = buildAnthropicClientOptions(baseArgs);
expect(visible.defaultHeaders["anthropic-beta"]).not.toContain("redact-thinking-2026-02-12");
const hidden = buildAnthropicClientOptions({ ...baseArgs, thinkingDisplay: "omitted" });
expect(hidden.defaultHeaders["anthropic-beta"]).toContain("redact-thinking-2026-02-12");
const hiddenUtility = buildAnthropicClientOptions({
...baseArgs,
hasTools: false,
thinkingEnabled: false,
thinkingDisplay: "omitted",
});
expect(hiddenUtility.defaultHeaders["anthropic-beta"]).toContain("redact-thinking-2026-02-12");
});
it("matches CC system-block layout: billing and instruction uncached, context cached in order", () => {
// We mimic Claude Code's billing+instruction system layout but do NOT emit
// the `scope: "global"` field that CC attaches to its middle breakpoint —
// `prompt-caching-scope-2026-01-05` only works against canonical
// `api.anthropic.com`, and third-party Anthropic-compatible proxies
// (z.ai, openrouter, g0i, …) reject the unknown field outright.
const blocks = buildAnthropicSystemBlocks(["Stay concise."], {
includeClaudeCodeInstruction: true,
extraInstructions: ["Use citations when possible"],
cacheControl: { type: "ephemeral" },
});
expect(blocks).toHaveLength(4);
expect(blocks?.[0].text).toStartWith("x-anthropic-billing-header:");
expect(blocks?.[0].cache_control).toBeUndefined();
expect(blocks?.[1].text).toBe(claudeCodeSystemInstruction);
expect(blocks?.[1].cache_control).toBeUndefined();
expect(blocks?.[2]).toEqual({
type: "text",
text: "Use citations when possible",
cache_control: { type: "ephemeral" },
});
expect(blocks?.[3]).toEqual({
type: "text",
text: "Stay concise.",
cache_control: { type: "ephemeral" },
});
});
it("caches Claude Code context and the last user block in OAuth request payloads", async () => {
const payload = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
})) as {
system?: Array<{ text?: string; cache_control?: unknown }>;
messages?: Array<{ content?: Array<{ cache_control?: unknown }> | string }>;
};
expect(payload.system?.[0]?.text).toStartWith("x-anthropic-billing-header:");
expect(payload.system?.[0]?.cache_control).toBeUndefined();
expect(payload.system?.[1]?.text).toBe(claudeCodeSystemInstruction);
expect(payload.system?.[1]?.cache_control).toBeUndefined();
expect(payload.system?.[2]?.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
const content = payload.messages?.[0]?.content;
expect(Array.isArray(content)).toBe(true);
expect(Array.isArray(content) ? content[0]?.cache_control : undefined).toEqual({
type: "ephemeral",
ttl: "1h",
});
});
it("clamps requested max_tokens to Claude Code's 64k cap when the model ceiling is higher", async () => {
const payload = (await captureAnthropicPayload(
{ ...ANTHROPIC_MODEL, id: "claude-opus-4-8", name: "Claude Opus 4.8", maxTokens: 128_000 },
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
)) as { max_tokens?: number };
expect(payload.max_tokens).toBe(64_000);
});
it("leaves max_tokens untouched when the model ceiling is below the 64k cap", async () => {
const payload = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
})) as { max_tokens?: number };
expect(payload.max_tokens).toBe(8_192);
});
it("billing-header fingerprint uses first user message, not leading developer message", async () => {
const userText = "Hello from user with enough chars padding here";
// Conversation with only a user message.
const payloadUserOnly = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Be helpful."],
messages: [{ role: "user", content: userText, timestamp: Date.now() }],
})) as { system?: Array<{ type: string; text?: string }> };
// Conversation prefixed with a developer message before the same user message.
const payloadWithDev = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Be helpful."],
messages: [
{ role: "developer", content: "developer instruction text", timestamp: Date.now() },
{ role: "user", content: userText, timestamp: Date.now() },
],
})) as { system?: Array<{ type: string; text?: string }> };
const billingUserOnly = payloadUserOnly.system?.[0].text ?? "";
const billingWithDev = payloadWithDev.system?.[0].text ?? "";
// Both payloads must carry a billing header.
expect(billingUserOnly).toStartWith("x-anthropic-billing-header:");
expect(billingWithDev).toStartWith("x-anthropic-billing-header:");
// The cc_version suffix (fingerprint) must be identical — developer message must not affect it.
const extractSuffix = (header: string) => header.match(/cc_version=[^.]+\.([a-f0-9]{3})/)?.[1];
expect(extractSuffix(billingWithDev)).toBe(extractSuffix(billingUserOnly));
});
it("places the automatic Anthropic cache breakpoint on the last ordered system prompt", async () => {
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["stable system", "stable durable context"],
messages: [{ role: "user", content: "variable context", timestamp: Date.now() }],
},
{ isOAuth: false },
)) as { system?: Array<{ type: string; text?: string; cache_control?: unknown }> };
expect(payload.system).toEqual([
{ type: "text", text: "stable system" },
{ type: "text", text: "stable durable context", cache_control: { type: "ephemeral" } },
]);
});
it("uses Bearer auth for non-Anthropic API bases with api-key credentials", () => {
const headers = buildAnthropicHeaders({
apiKey: "sk-ant-api-test",
baseUrl: "https://proxy.example.com",
stream: true,
});
expect(headers.Authorization).toBe("Bearer sk-ant-api-test");
expect(headers["X-Api-Key"]).toBeUndefined();
});
it("forwards only prefix-matching Claude Code User-Agent values", () => {
const forwardedHeaders = buildAnthropicHeaders({
apiKey: "sk-ant-oat-test",
isOAuth: true,
stream: true,
modelHeaders: { "User-Agent": "claude-cli/2.1.63 (external, cli)" },
});
expect(forwardedHeaders["User-Agent"]).toBe("claude-cli/2.1.63 (external, cli)");
// Test variant without slash
const forwardedNoSlashHeaders = buildAnthropicHeaders({
apiKey: "sk-ant-oat-test",
isOAuth: true,
stream: true,
modelHeaders: { "User-Agent": "claude-cli-dev" },
});
expect(forwardedNoSlashHeaders["User-Agent"]).toBe("claude-cli-dev");
const normalizedHeaders = buildAnthropicHeaders({
apiKey: "sk-ant-oat-test",
isOAuth: true,
stream: true,
modelHeaders: { "User-Agent": "curl/8.7.1" },
});
expect(normalizedHeaders["User-Agent"]).toBe(
`claude-cli/${claudeCodeVersion} (external, local-agent, agent-sdk/${claudeAgentSdkVersion})`,
);
const embeddedClaudeCliHeaders = buildAnthropicHeaders({
apiKey: "sk-ant-oat-test",
isOAuth: true,
stream: true,
modelHeaders: { "User-Agent": "my-client claude-cli/2.1.63" },
});
expect(embeddedClaudeCliHeaders["User-Agent"]).toBe(
`claude-cli/${claudeCodeVersion} (external, local-agent, agent-sdk/${claudeAgentSdkVersion})`,
);
});
it("skips Claude Code instruction injection for claude-3-5-haiku models", async () => {
const payload = (await captureAnthropicPayload(
{ ...ANTHROPIC_MODEL, id: "claude-3-5-haiku", name: "Claude 3.5 Haiku" },
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
)) as { system?: Array<{ type: string; text?: string }> };
expect(Array.isArray(payload.system)).toBe(true);
const systemBlocks = payload.system ?? [];
expect(systemBlocks.some(block => block.text?.startsWith("x-anthropic-billing-header:"))).toBe(false);
expect(systemBlocks[0]?.text).toBe("Stay concise.");
});
it("accepts uppercase hex in the user hash segment", () => {
const userId =
"user_ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCD_account_12345678-1234-1234-1234-1234567890ab_session_abcdefab-cdef-abcd-efab-cdefabcdef12";
expect(isClaudeCloakingUserId(userId)).toBe(true);
});
it("generates cloaking-compatible user IDs", () => {
const userId = generateClaudeCloakingUserId();
expect(isClaudeCloakingUserId(userId)).toBe(true);
});
it("injects Claude Code JSON metadata.user_id for OAuth requests when missing", async () => {
const payload = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
})) as { metadata?: { user_id?: string } };
expectClaudeMetadataUserId(payload.metadata?.user_id);
});
it("derives generated OAuth device_id deterministically across sessions", async () => {
const first = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ sessionId: "167ec5b4-e711-4169-879f-84fa52679d9c" },
)) as { metadata?: { user_id?: string } };
const second = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi again", timestamp: Date.now() }],
},
{ sessionId: "abcdefab-cdef-abcd-efab-cdefabcdef12" },
)) as { metadata?: { user_id?: string } };
const firstUserId = JSON.parse(first.metadata?.user_id ?? "{}") as { device_id?: string };
const secondUserId = JSON.parse(second.metadata?.user_id ?? "{}") as { device_id?: string };
expect(firstUserId.device_id).toBe(secondUserId.device_id);
});
it("uses the explicit session id for generated OAuth metadata", async () => {
const sessionId = "167ec5b4-e711-4169-879f-84fa52679d9c";
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ sessionId },
)) as { metadata?: { user_id?: string } };
expectClaudeMetadataUserId(payload.metadata?.user_id, sessionId);
});
it("does not inject metadata.user_id for non-OAuth requests without caller metadata", async () => {
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ isOAuth: false },
)) as { metadata?: { user_id?: string } };
expect(payload.metadata).toBeUndefined();
});
it("preserves valid caller metadata.user_id for OAuth requests", async () => {
const userId = generateClaudeCloakingUserId();
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ metadata: { user_id: userId } },
)) as { metadata?: { user_id?: string } };
expect(payload.metadata?.user_id).toBe(userId);
});
it("mirrors JSON metadata session_id into the Claude Code session header", async () => {
const sessionId = "167ec5b4-e711-4169-879f-84fa52679d9c";
const userId = JSON.stringify({
device_id: "a".repeat(64),
account_uuid: "12345678-1234-1234-1234-1234567890ab",
session_id: sessionId,
});
const { promise, resolve } = Promise.withResolvers<{
sessionHeader: string | null;
url: string;
accept: string | null;
}>();
const controller = new AbortController();
const fakeFetch = async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {
const headers = new Headers(init?.headers);
resolve({
sessionHeader: headers.get("X-Claude-Code-Session-Id"),
url: input instanceof Request ? input.url : String(input),
accept: headers.get("Accept"),
});
controller.abort();
return new Response('event: message_stop\ndata: {"type":"message_stop"}\n\n', {
status: 200,
headers: { "content-type": "text/event-stream" },
});
};
streamAnthropic(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{
apiKey: "sk-ant-oat-test",
isOAuth: true,
metadata: { user_id: userId },
signal: controller.signal,
fetch: fakeFetch,
},
);
expect(await promise).toEqual({
sessionHeader: sessionId,
url: "https://api.anthropic.com/v1/messages?beta=true",
accept: "application/json",
});
});
it("preserves real Claude Code JSON-format metadata.user_id for OAuth requests", async () => {
// Matches the shape produced by services/api/claude.ts → getAPIMetadata in
// the Claude Code source: { device_id, account_uuid, session_id, ...extra }.
const userId = JSON.stringify({
device_id: "a".repeat(64),
account_uuid: "12345678-1234-1234-1234-1234567890ab",
session_id: "abcdefab-cdef-abcd-efab-cdefabcdef12",
});
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ metadata: { user_id: userId } },
)) as { metadata?: { user_id?: string } };
expect(payload.metadata?.user_id).toBe(userId);
});
it("preserves a minimal { session_id } JSON metadata.user_id for OAuth requests", async () => {
const userId = JSON.stringify({ session_id: "0190fb1e-0000-7000-8000-000000000001" });
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ metadata: { user_id: userId } },
)) as { metadata?: { user_id?: string } };
expect(payload.metadata?.user_id).toBe(userId);
});
it("replaces JSON metadata.user_id missing session_id for OAuth requests", async () => {
const userId = JSON.stringify({ device_id: "x".repeat(64) });
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ metadata: { user_id: userId } },
)) as { metadata?: { user_id?: string } };
expect(payload.metadata?.user_id).not.toBe(userId);
expectClaudeMetadataUserId(payload.metadata?.user_id);
});
it("replaces invalid caller metadata.user_id for OAuth requests", async () => {
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ metadata: { user_id: "invalid-user-id" } },
)) as { metadata?: { user_id?: string } };
expect(payload.metadata?.user_id).not.toBe("invalid-user-id");
expectClaudeMetadataUserId(payload.metadata?.user_id);
});
it("adds additionalProperties false to Anthropic tool object schemas", async () => {
const originalNestedSchema = {
type: "object",
properties: {
path: { type: "string" },
},
patternProperties: {
"^x-": { type: "string" },
},
required: ["path"],
};
const tools: Tool[] = [
{
name: "edit_file",
description: "edit files",
parameters: {
type: "object",
properties: {
target: originalNestedSchema,
operations: {
type: "array",
items: {
type: "object",
properties: { content: { type: "string" } },
required: ["content"],
},
},
env: {
type: "object",
patternProperties: {
"^[A-Za-z_][A-Za-z0-9_]*$": { type: "string" },
},
propertyNames: {
type: "string",
pattern: "^[A-Za-z_][A-Za-z0-9_]*$",
},
},
},
required: ["target"],
} as TJsonSchema,
},
];
const payload = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
})) as {
tools?: Array<{
input_schema?: {
additionalProperties?: boolean;
properties?: Record<string, unknown>;
required?: string[];
};
}>;
};
const inputSchema = payload.tools?.[0]?.input_schema;
const properties = inputSchema?.properties as Record<string, Record<string, unknown>>;
const target = properties.target as { additionalProperties?: boolean; patternProperties?: unknown };
const operations = properties.operations as {
type?: string;
items?: { additionalProperties?: boolean; required?: string[] };
};
const env = properties.env as {
additionalProperties?: boolean;
patternProperties?: unknown;
propertyNames?: unknown;
};
expect(inputSchema?.additionalProperties).toBe(false);
expect(inputSchema?.required).toEqual(["target"]);
expect(target.additionalProperties).toBe(false);
expect(operations.type).toBe("array");
expect(operations.items?.additionalProperties).toBe(false);
expect(operations.items?.required).toEqual(["content"]);
expect(target).not.toHaveProperty("patternProperties");
expect(env.additionalProperties).toBe(false);
expect(env).not.toHaveProperty("patternProperties");
expect(env).not.toHaveProperty("propertyNames");
expect(inputSchema?.properties).toHaveProperty("target");
expect(originalNestedSchema).not.toHaveProperty("additionalProperties");
expect(originalNestedSchema).toHaveProperty("patternProperties");
});
it("preserves explicit additionalProperties schemas and true for open record fields", async () => {
// Mirrors open record-style shapes: Zod's `z.record(z.string(), z.unknown())`
// emits `additionalProperties: {}`, typed maps use a schema, and the yield
// fallback uses `additionalProperties: true`. Each must remain open after
// unsupported key-schema keywords are stripped.
const tools: Tool[] = [
{
name: "resolve",
description: "resolve a pending action",
parameters: {
type: "object",
properties: {
action: { type: "string" },
extra: {
type: "object",
propertyNames: { type: "string" },
additionalProperties: {},
},
extraTyped: {
type: "object",
additionalProperties: { type: "string" },
},
extraLoose: {
type: "object",
additionalProperties: true,
},
},
required: ["action"],
} as TJsonSchema,
},
];
const payload = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
})) as {
tools?: Array<{
input_schema?: {
additionalProperties?: boolean;
properties?: Record<string, unknown>;
};
}>;
};
const inputSchema = payload.tools?.[0]?.input_schema;
const properties = inputSchema?.properties as Record<string, Record<string, unknown>>;
const extra = properties.extra as { additionalProperties?: unknown; propertyNames?: unknown };
const extraTyped = properties.extraTyped as { additionalProperties?: unknown };
const extraLoose = properties.extraLoose as { additionalProperties?: unknown };
expect(inputSchema?.additionalProperties).toBe(false);
// The unsupported `propertyNames` keyword is still stripped …
expect(extra).not.toHaveProperty("propertyNames");
// … but the explicit open-map schema survives (normalized to `true` per
// JSON Schema 2020-12 §4.3.1 — `{}` and `true` are equivalent).
expect(extra.additionalProperties).toBe(true);
// A typed value schema is preserved verbatim (and would be recursed into
// if it were an object — covered separately).
expect(extraTyped.additionalProperties).toEqual({ type: "string" });
expect(extraLoose.additionalProperties).toBe(true);
});
it("removes Anthropic-unsupported array item count constraints", async () => {
const tools: Tool[] = [
{
name: "edit_file",
description: "edit files",
parameters: {
type: "object",
properties: {
sub: {
type: "array",
items: { type: "string" },
minItems: 2,
maxItems: 2,
},
nonEmpty: {
type: "array",
items: { type: "string" },
minItems: 1,
},
},
required: ["sub"],
} as TJsonSchema,
},
];
const payload = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
})) as {
tools?: Array<{
input_schema?: {
properties?: Record<string, unknown>;
};
}>;
};
const properties = payload.tools?.[0]?.input_schema?.properties as Record<string, Record<string, unknown>>;
expect(properties.sub).not.toHaveProperty("minItems");
expect(properties.sub).not.toHaveProperty("maxItems");
expect(properties.nonEmpty.minItems).toBe(1);
});
it("strips minItems from object-typed property schemas (Anthropic rejects them)", async () => {
const tools: Tool[] = [
{
name: "weird",
description: "nested object with stray minItems",
parameters: {
type: "object",
properties: {
block: {
type: "object",
properties: { a: { type: "string" } },
required: ["a"],
minItems: 1,
},
},
required: ["block"],
} as TJsonSchema,
},
];
const payload = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
})) as {
tools?: Array<{
input_schema?: { properties?: Record<string, unknown> };
}>;
};
const block = payload.tools?.[0]?.input_schema?.properties?.block as Record<string, unknown> | undefined;
expect(block?.type).toBe("object");
expect(block).not.toHaveProperty("minItems");
});
it("keeps OAuth tool names behind the proxy prefix with eager streaming and strict flags", async () => {
const tools: Tool[] = [
{
name: "bash",
description: "run commands",
strict: true,
parameters: {
type: "object",
properties: { command: { type: "string" } },
required: ["command"],
} as TJsonSchema,
},
];
const payload = (await captureAnthropicPayload(ANTHROPIC_MODEL, {
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
})) as {
tools?: Array<{ name?: string; strict?: boolean; eager_input_streaming?: boolean; cache_control?: unknown }>;
};
expect(payload.tools?.[0]?.name).toBe(`${claudeToolPrefix}bash`);
expect(payload.tools?.[0]?.strict).toBe(true);
expect(payload.tools?.[0]?.eager_input_streaming).toBe(true);
expect(payload.tools?.[0]?.cache_control).toBeUndefined();
});
it("marks only the Anthropic strict allowlist strict", async () => {
const tools: Tool[] = [
...(["bash", "python", "edit", "find"] as const).map(name => ({
name,
description: `${name} tool`,
strict: true,
parameters: {
type: "object",
properties: { requiredValue: { type: "string" } },
required: ["requiredValue"],
} as TJsonSchema,
})),
...(["write", "grep", "read", "task", "todo", "web_search", "ast_grep"] as const).map(name => ({
name,
description: `${name} tool`,
strict: true,
parameters: {
type: "object",
properties: { requiredValue: { type: "string" } },
required: ["requiredValue"],
} as TJsonSchema,
})),
];
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
},
{ isOAuth: false },
)) as {
tools?: Array<{ name?: string; strict?: boolean; input_schema?: { required?: string[] } }>;
};
const strictNames = (payload.tools ?? []).filter(tool => tool.strict === true).map(tool => tool.name);
expect(strictNames).toEqual(["bash", "python", "edit", "find"]);
expect(payload.tools?.find(tool => tool.name === "bash")?.input_schema?.required).toEqual(["requiredValue"]);
});
it("marks regular two-field Zod object tools strict", async () => {
const tools: Tool[] = [
{
name: "bash",
description: "bash tool",
strict: true,
parameters: z.object({
command: z.string(),
cwd: z.string(),
}),
},
];
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
},
{ isOAuth: false },
)) as {
tools?: Array<{
name?: string;
strict?: boolean;
input_schema?: { properties?: Record<string, unknown>; required?: string[] };
}>;
};
const bashTool = payload.tools?.find(tool => tool.name === "bash");
expect(bashTool?.strict).toBe(true);
expect(Object.keys(bashTool?.input_schema?.properties ?? {})).toEqual(["command", "cwd"]);
expect(bashTool?.input_schema?.required).toEqual(["command", "cwd"]);
});
it("does not mark allowlisted Anthropic tools strict when schemas contain open object maps", async () => {
const tools: Tool[] = [
{
name: "bash",
description: "bash tool",
strict: true,
parameters: {
type: "object",
properties: {
command: { type: "string" },
env: {
type: "object",
additionalProperties: { type: "string" },
},
},
required: ["command"],
} as TJsonSchema,
},
{
name: "python",
description: "python tool",
strict: true,
parameters: {
type: "object",
properties: { requiredValue: { type: "string" } },
required: ["requiredValue"],
} as TJsonSchema,
},
];
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
},
{ isOAuth: false },
)) as {
tools?: Array<{
name?: string;
strict?: boolean;
input_schema?: { properties?: Record<string, unknown>; required?: string[] };
}>;
};
const bashTool = payload.tools?.find(tool => tool.name === "bash");
const pythonTool = payload.tools?.find(tool => tool.name === "python");
const env = bashTool?.input_schema?.properties?.env as { additionalProperties?: unknown } | undefined;
expect(bashTool?.strict).toBeUndefined();
expect(env?.additionalProperties).toEqual({ type: "string" });
expect(pythonTool?.strict).toBe(true);
expect(pythonTool?.input_schema?.required).toEqual(["requiredValue"]);
});
it("honors strict=false and skips non-allowlisted Anthropic tools", async () => {
const tools: Tool[] = [
{
name: "bash",
description: "bash tool",
strict: false,
parameters: {
type: "object",
properties: { requiredValue: { type: "string" } },
required: ["requiredValue"],
} as TJsonSchema,
},
{
name: "python",
description: "python tool",
strict: true,
parameters: {
type: "object",
properties: { requiredValue: { type: "string" } },
required: ["requiredValue"],
} as TJsonSchema,
},
{
name: "write",
description: "write tool",
parameters: {
type: "object",
properties: { requiredValue: { type: "string" } },
required: ["requiredValue"],
} as TJsonSchema,
},
{
name: "grep",
description: "grep tool",
strict: true,
parameters: {
type: "object",
properties: { requiredValue: { type: "string" } },
required: ["requiredValue"],
} as TJsonSchema,
},
];
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
tools,
},
{ isOAuth: false },
)) as { tools?: Array<{ name?: string; strict?: boolean }> };
const strictNames = (payload.tools ?? []).filter(tool => tool.strict === true).map(tool => tool.name);
expect(strictNames).toEqual(["python"]);
});
it("adds legacy fine-grained tool-streaming beta only for tool requests on incompatible models", () => {
const incompatibleModel: Model<"anthropic-messages"> = {
...ANTHROPIC_MODEL,
compat: { supportsEagerToolInputStreaming: false },
};
const withoutTools = buildAnthropicClientOptions({
model: incompatibleModel,
apiKey: "sk-ant-api-test",
extraBetas: [],
stream: true,
interleavedThinking: false,
hasTools: false,
});
const withCompatibleTools = buildAnthropicClientOptions({
model: ANTHROPIC_MODEL,
apiKey: "sk-ant-api-test",
extraBetas: [],
stream: true,
interleavedThinking: false,
hasTools: true,
});
const withIncompatibleTools = buildAnthropicClientOptions({
model: incompatibleModel,
apiKey: "sk-ant-api-test",
extraBetas: [],
stream: true,
interleavedThinking: false,
hasTools: true,
});
expect(withoutTools.defaultHeaders["anthropic-beta"]).not.toContain("fine-grained-tool-streaming-2025-05-14");
expect(withCompatibleTools.defaultHeaders["anthropic-beta"]).not.toContain(
"fine-grained-tool-streaming-2025-05-14",
);
expect(withIncompatibleTools.defaultHeaders["anthropic-beta"]).toContain(
"fine-grained-tool-streaming-2025-05-14",
);
});
it("uses Cloudflare AI Gateway authorization without Anthropic credential headers", () => {
const options = buildAnthropicClientOptions({
model: CLOUDFLARE_ANTHROPIC_MODEL,
apiKey: "cf-gateway-token",
extraBetas: [],
stream: true,
interleavedThinking: false,
dynamicHeaders: {},
});
expect(options.baseURL).toBe("https://gateway.ai.cloudflare.com/v1/account/gateway/anthropic");
expect(options.apiKey).toBeNull();
expect(options.authToken).toBeNull();
expect(options.defaultHeaders["cf-aig-authorization"]).toBe("Bearer cf-gateway-token");
expect(options.defaultHeaders.Authorization).toBeUndefined();
expect(options.defaultHeaders["X-Api-Key"]).toBeUndefined();
});
it("keeps Cloudflare gateway auth authoritative over caller-supplied auth headers", () => {
const options = buildAnthropicClientOptions({
model: {
...CLOUDFLARE_ANTHROPIC_MODEL,
headers: {
Authorization: "Bearer anthropic-oauth",
"X-Api-Key": "sk-ant-api-leak",
"cf-aig-authorization": "Bearer stale-token",
},
},
apiKey: "cf-gateway-token",
extraBetas: [],
stream: true,
interleavedThinking: false,
dynamicHeaders: {},
});
expect(options.defaultHeaders["cf-aig-authorization"]).toBe("Bearer cf-gateway-token");
expect(options.defaultHeaders.Authorization).toBeUndefined();
expect(options.defaultHeaders["X-Api-Key"]).toBeUndefined();
});
it("applies Claude Code TLS profile for direct Anthropic transport", () => {
const options = buildAnthropicClientOptions({
model: ANTHROPIC_MODEL,
apiKey: "sk-ant-oat-test",
extraBetas: [],
stream: true,
interleavedThinking: false,
dynamicHeaders: {},
});
const tlsOptions = (
options.fetchOptions as
| {
tls?: {
rejectUnauthorized?: boolean;
serverName?: string;
ciphers?: string;
};
}
| undefined
)?.tls;
expect(tlsOptions).toBeDefined();
expect(tlsOptions?.rejectUnauthorized).toBe(true);
expect(tlsOptions?.serverName).toBe("api.anthropic.com");
expect(tlsOptions?.ciphers).toBe(tls.DEFAULT_CIPHERS);
});
it("uses Foundry base URL, Bearer auth, and custom headers when enabled", async () => {
await withEnv(
{
CLAUDE_CODE_USE_FOUNDRY: "true",
FOUNDRY_BASE_URL: "https://foundry.example.com/anthropic/",
ANTHROPIC_CUSTOM_HEADERS: "user-id: alice, x-route: engineering",
},
() => {
const options = buildAnthropicClientOptions({
model: ANTHROPIC_MODEL,
apiKey: "foundry-token",
extraBetas: [],
stream: true,
interleavedThinking: false,
dynamicHeaders: {},
});
expect(options.baseURL).toBe("https://foundry.example.com/anthropic");
expect(options.defaultHeaders.Authorization).toBe("Bearer foundry-token");
expect(options.defaultHeaders["X-Api-Key"]).toBeUndefined();
expect(options.defaultHeaders["user-id"]).toBe("alice");
expect(options.defaultHeaders["x-route"]).toBe("engineering");
},
);
});
it("forwards ANTHROPIC_CUSTOM_HEADERS to an enterprise gateway base URL without Foundry mode", async () => {
const gatewayModel: Model<"anthropic-messages"> = {
...ANTHROPIC_MODEL,
baseUrl: "https://gateway.example.com",
};
await withEnv(
{
CLAUDE_CODE_USE_FOUNDRY: undefined,
FOUNDRY_BASE_URL: undefined,
ANTHROPIC_BASE_URL: undefined,
ANTHROPIC_CUSTOM_HEADERS: "X-Gateway-Key: secret",
},
() => {
const options = buildAnthropicClientOptions({
model: gatewayModel,
apiKey: "sk-ant-api-test",
extraBetas: [],
stream: true,
interleavedThinking: false,
dynamicHeaders: {},
});
expect(options.defaultHeaders["X-Gateway-Key"]).toBe("secret");
},
);
});
it("omits ANTHROPIC_CUSTOM_HEADERS when neither Foundry mode nor a custom base URL is configured", async () => {
await withEnv(
{
CLAUDE_CODE_USE_FOUNDRY: undefined,
FOUNDRY_BASE_URL: undefined,
ANTHROPIC_BASE_URL: undefined,
ANTHROPIC_CUSTOM_HEADERS: "X-Gateway-Key: secret",
},
() => {
const options = buildAnthropicClientOptions({
model: ANTHROPIC_MODEL,
apiKey: "sk-ant-api-test",
extraBetas: [],
stream: true,
interleavedThinking: false,
dynamicHeaders: {},
});
expect(options.defaultHeaders["X-Gateway-Key"]).toBeUndefined();
},
);
});
it("loads Foundry mTLS and CA material from file paths", async () => {
const tmpDir = path.join(os.tmpdir(), `pi-ai-foundry-${Date.now()}-${Math.random().toString(16).slice(2)}`);
fs.mkdirSync(tmpDir, { recursive: true });
const caPath = path.join(tmpDir, "ca.pem");
const certPath = path.join(tmpDir, "client-cert.pem");
const keyPath = path.join(tmpDir, "client-key.pem");
fs.writeFileSync(caPath, "-----BEGIN CERTIFICATE-----\nCA\n-----END CERTIFICATE-----\n", "utf8");
fs.writeFileSync(certPath, "-----BEGIN CERTIFICATE-----\nCERT\n-----END CERTIFICATE-----\n", "utf8");
fs.writeFileSync(keyPath, "-----BEGIN PRIVATE KEY-----\nKEY\n-----END PRIVATE KEY-----\n", "utf8");
try {
await withEnv(
{
CLAUDE_CODE_USE_FOUNDRY: "1",
FOUNDRY_BASE_URL: "https://foundry.example.com",
NODE_EXTRA_CA_CERTS: caPath,
CLAUDE_CODE_CLIENT_CERT: certPath,
CLAUDE_CODE_CLIENT_KEY: keyPath,
},
() => {
const options = buildAnthropicClientOptions({
model: ANTHROPIC_MODEL,
apiKey: "foundry-token",
extraBetas: [],
stream: true,
interleavedThinking: false,
dynamicHeaders: {},
});
const tlsOptions = (
options.fetchOptions as
| {
tls?: {
serverName?: string;
ca?: string | string[];
cert?: string;
key?: string;
};
}
| undefined
)?.tls;
expect(tlsOptions?.serverName).toBe("foundry.example.com");
expect(Array.isArray(tlsOptions?.ca)).toBe(true);
const caValues = (tlsOptions?.ca ?? []) as string[];
expect(caValues.length).toBeGreaterThanOrEqual(tls.rootCertificates.length + 1);
expect(caValues.slice(0, tls.rootCertificates.length)).toEqual([...tls.rootCertificates]);
expect(caValues.at(-1)).toContain("BEGIN CERTIFICATE");
expect(tlsOptions?.cert).toContain("BEGIN CERTIFICATE");
expect(tlsOptions?.key).toContain("BEGIN PRIVATE KEY");
},
);
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
});
it("throws when Foundry mTLS cert/key pair is incomplete", async () => {
await withEnv(
{
CLAUDE_CODE_USE_FOUNDRY: "true",
FOUNDRY_BASE_URL: "https://foundry.example.com",
CLAUDE_CODE_CLIENT_CERT: "-----BEGIN CERTIFICATE-----\nCERT\n-----END CERTIFICATE-----\n",
CLAUDE_CODE_CLIENT_KEY: undefined,
},
() => {
expect(() =>
buildAnthropicClientOptions({
model: ANTHROPIC_MODEL,
apiKey: "foundry-token",
extraBetas: [],
stream: true,
interleavedThinking: false,
dynamicHeaders: {},
}),
).toThrow("Both CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY must be set for mTLS.");
},
);
});
it("resolves Anthropic Foundry API key when Foundry mode is enabled", async () => {
await withEnv(
{
CLAUDE_CODE_USE_FOUNDRY: "true",
ANTHROPIC_FOUNDRY_API_KEY: "foundry-env-token",
ANTHROPIC_OAUTH_TOKEN: "sk-ant-oat-should-not-win",
ANTHROPIC_API_KEY: "sk-ant-api-should-not-win",
},
() => {
expect(getEnvApiKey("anthropic")).toBe("foundry-env-token");
},
);
});
it("sends temperature for Anthropic requests without enabled thinking", async () => {
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ temperature: 0.2 },
)) as { temperature?: number; thinking?: { type?: string } };
expect(payload.temperature).toBe(0.2);
expect(payload.thinking).toBeUndefined();
});
it("sends disabled thinking for reasoning models when thinking is explicitly disabled", async () => {
const payload = (await captureAnthropicPayload(
ANTHROPIC_MODEL,
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{ thinkingEnabled: false },
)) as { thinking?: { type?: string } };
expect(payload.thinking).toEqual({ type: "disabled" });
});
it("drops temperature and sampling params for Opus 4.7 without enabled thinking", async () => {
const payload = (await captureAnthropicPayload(
{ ...ANTHROPIC_MODEL, id: "claude-opus-4-7", name: "Claude Opus 4.7" },
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{
temperature: 0.2,
topP: 0.3,
topK: 4,
},
)) as {
temperature?: number;
top_p?: number;
top_k?: number;
thinking?: { type?: string };
};
expect(payload.temperature).toBeUndefined();
expect(payload.top_p).toBeUndefined();
expect(payload.top_k).toBeUndefined();
expect(payload.thinking).toBeUndefined();
});
it("drops sampling params and keeps summarized adaptive thinking for OAuth Opus 4.7+", async () => {
const payload = (await captureAnthropicPayload(
{
...ANTHROPIC_MODEL,
id: "claude-opus-4-7",
name: "Claude Opus 4.7",
thinking: {
mode: "anthropic-adaptive",
minLevel: Effort.Minimal,
maxLevel: Effort.XHigh,
},
},
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{
thinkingEnabled: true,
reasoning: Effort.High,
temperature: 0.2,
topP: 0.3,
topK: 4,
},
)) as {
temperature?: number;
top_p?: number;
top_k?: number;
thinking?: { type?: string; display?: string };
context_management?: { edits?: Array<{ type?: string; keep?: string | number }> };
output_config?: { effort?: string };
};
expect(payload.temperature).toBeUndefined();
expect(payload.top_p).toBeUndefined();
expect(payload.top_k).toBeUndefined();
expect(payload.thinking).toEqual({ type: "adaptive", display: "summarized" });
expect(payload.context_management).toEqual({
edits: [{ type: "clear_thinking_20251015", keep: "all" }],
});
expect(payload.output_config).toEqual({ effort: "xhigh" });
const maxPayload = (await captureAnthropicPayload(
{
...ANTHROPIC_MODEL,
id: "claude-opus-4-7",
name: "Claude Opus 4.7",
thinking: {
mode: "anthropic-adaptive",
minLevel: Effort.Minimal,
maxLevel: Effort.XHigh,
},
},
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{
thinkingEnabled: true,
reasoning: Effort.XHigh,
},
)) as {
thinking?: { type?: string; display?: string };
output_config?: { effort?: string };
};
expect(maxPayload.thinking).toEqual({ type: "adaptive", display: "summarized" });
expect(maxPayload.output_config).toEqual({ effort: "max" });
});
it("keeps summarized adaptive thinking by default for API-key Opus 4.7+ requests", async () => {
const payload = (await captureAnthropicPayload(
{
...ANTHROPIC_MODEL,
id: "claude-opus-4-7",
name: "Claude Opus 4.7",
thinking: {
mode: "anthropic-adaptive",
minLevel: Effort.Minimal,
maxLevel: Effort.XHigh,
},
},
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Hi", timestamp: Date.now() }],
},
{
isOAuth: false,
thinkingEnabled: true,
reasoning: Effort.High,
},
)) as {
thinking?: { type?: string; display?: string };
context_management?: unknown;
output_config?: { effort?: string };
};
expect(payload.thinking).toEqual({ type: "adaptive", display: "summarized" });
expect(payload.context_management).toBeUndefined();
expect(payload.output_config).toEqual({ effort: "xhigh" });
});
it("sends task budgets through Anthropic output_config without dropping adaptive effort", async () => {
const payload = (await captureAnthropicPayload(
{
...ANTHROPIC_MODEL,
id: "claude-opus-4-7",
name: "Claude Opus 4.7",
thinking: {
mode: "anthropic-adaptive",
minLevel: Effort.Minimal,
maxLevel: Effort.XHigh,
},
},
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Review this repo", timestamp: Date.now() }],
},
{
thinkingEnabled: true,
reasoning: Effort.High,
taskBudget: { type: "tokens", total: 64_000, remaining: 48_000 },
},
)) as {
output_config?: {
effort?: string;
task_budget?: TokenTaskBudget;
};
};
expect(payload.output_config).toEqual({
effort: "xhigh",
task_budget: { type: "tokens", total: 64_000, remaining: 48_000 },
});
});
it("preserves task budget when forced tool choice disables thinking", async () => {
const payload = (await captureAnthropicPayload(
{
...ANTHROPIC_MODEL,
id: "claude-opus-4-7",
name: "Claude Opus 4.7",
thinking: {
mode: "anthropic-adaptive",
minLevel: Effort.Minimal,
maxLevel: Effort.XHigh,
},
},
{
systemPrompt: ["Stay concise."],
messages: [{ role: "user", content: "Use the tool", timestamp: Date.now() }],
tools: [
{
name: "lookup",
description: "Lookup a value",
parameters: { type: "object", properties: {}, additionalProperties: false },
},
],
},
{
thinkingEnabled: true,
reasoning: Effort.High,
taskBudget: { type: "tokens", total: 64_000 },
toolChoice: "any",
},
)) as {
thinking?: unknown;
output_config?: {
effort?: string;
task_budget?: TokenTaskBudget;
};
};
expect(payload.thinking).toBeUndefined();
expect(payload.output_config).toEqual({
task_budget: { type: "tokens", total: 64_000 },
});
});
it("treats tool prefix helpers as no-ops when prefix is empty string", () => {
// Directly verify the codec's identity behaviour: builtins pass through apply unchanged.
// (Empty-prefix path is exercised by the builtin guard below; the contract is
// roundtrip fidelity, not knowledge of the literal prefix string.)
const name = "Read";
expect(stripClaudeToolPrefix(applyClaudeToolPrefix(name))).toBe(name);
});
it("does not prefix built-in Anthropic tool names", () => {
expect(applyClaudeToolPrefix("web_search")).toBe("web_search");
expect(applyClaudeToolPrefix("CODE_EXECUTION")).toBe("CODE_EXECUTION");
expect(applyClaudeToolPrefix("Text_Editor")).toBe("Text_Editor");
expect(applyClaudeToolPrefix("computer")).toBe("computer");
});
it("prefixes custom tool names and roundtrips cleanly", () => {
const name = "Read";
const prefixed = applyClaudeToolPrefix(name);
expect(prefixed).toBe(`${claudeToolPrefix}${name}`);
expect(applyClaudeToolPrefix(prefixed)).toBe(prefixed); // idempotent
expect(stripClaudeToolPrefix(prefixed)).toBe(name); // roundtrip
});
});
describe("cch attestation", () => {
it("wrapFetchForCch: replaces cch=00000 with correct XXHash64 in outgoing request body", async () => {
const { promise: bodyPromise, resolve: bodyResolve } = Promise.withResolvers<string>();
const controller = new AbortController();
const fakeFetch = async (_input: string | URL | Request, init?: RequestInit): Promise<Response> => {
const raw = init?.body;
const body = typeof raw === "string" ? raw : new TextDecoder().decode(raw as Uint8Array);
bodyResolve(body);
controller.abort();
return new Response('event: message_stop\ndata: {"type":"message_stop"}\n\n', {
status: 200,
headers: { "content-type": "text/event-stream" },
});
};
streamAnthropic(
ANTHROPIC_MODEL,
{
systemPrompt: ["Be helpful."],
messages: [{ role: "user", content: "hello", timestamp: Date.now() }],
},
{ apiKey: "sk-ant-oat-test", isOAuth: true, signal: controller.signal, fetch: fakeFetch },
);
const capturedBody = await bodyPromise;
// The placeholder must have been replaced before the request was sent.
expect(capturedBody).toContain("cch=");
expect(capturedBody).not.toContain("cch=00000");
const m = capturedBody.match(/cch=([0-9a-f]{5})/);
expect(m).not.toBeNull();
// Self-consistency: hashing the body with the placeholder restored must reproduce the embedded cch.
const CCH_SEED = 0x4d659218e32a3268n;
const withPlaceholder = capturedBody.replace(/cch=[0-9a-f]{5}/, "cch=00000");
const h = Bun.hash.xxHash64(new TextEncoder().encode(withPlaceholder), CCH_SEED);
expect(m![1]).toBe((h & 0xfffffn).toString(16).padStart(5, "0"));
});
it("derives cch from low-20-bits of XXHash64(body, seed) — external reference values", () => {
// Each body contains "cch=00000" as the Bun HTTP layer sees it before patching.
// Expected low-20-bit hashes precomputed with the Python xxhash reference.
const CCH_SEED = 0x4d659218e32a3268n;
const enc = new TextEncoder();
const cases: [string, string][] = [
["cch=00000", "a47f7"],
['{"messages":[],"cch=00000","x":1}', "3073d"],
["x-anthropic-billing-header: cc_version=2.1.158; cc_entrypoint=cli; cch=00000;", "f2b0b"],
];
for (const [body, expected] of cases) {
const h = Bun.hash.xxHash64(enc.encode(body), CCH_SEED);
expect((h & 0xfffffn).toString(16).padStart(5, "0")).toBe(expected);
}
});
});