@oh-my-pi/pi-ai 15.10.3 → 15.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.10.4] - 2026-06-08
6
+ ### Added
7
+
8
+ - Added `anthropic-client-platform` (`desktop_app`) and `anthropic-client-version` (`1.11187.4`) headers to the Anthropic request fingerprint for OAuth sessions
9
+
10
+ ### Changed
11
+
12
+ - Changed non-built-in tool names sent to Anthropic from `proxy_` prefixing to `_` prefixing (for example `bash` to `_bash`) while built-in tool names remain unchanged
13
+ - Updated the Anthropic OAuth stealth fingerprint to track Claude Code 2.1.165: `claudeCodeVersion` bumped to `2.1.165` (flows into both the `cc_version` billing header and the `claude-cli/<version>` user-agent), `claudeCodeSystemInstruction` changed to `"You are a Claude agent, built on Anthropic's Claude Agent SDK."`, and the billing-header `cc_entrypoint` changed from `cli` to `local-agent`.
14
+ - Clamped the Anthropic request `max_tokens` to `Math.min(CLAUDE_CODE_MAX_OUTPUT_TOKENS, options.maxTokens || model.maxTokens)` (64k) so OAuth requests match Claude Code's requested output cap instead of sending the model's full ceiling (e.g. 128k for Opus 4.8).
15
+
5
16
  ## [15.10.3] - 2026-06-08
6
17
 
7
18
  ### Removed
@@ -26,9 +26,12 @@ type AnthropicCacheControl = NonNullable<TextBlockParam["cache_control"]>;
26
26
  */
27
27
  export declare function clearAnthropicFastModeFallback(providerSessionState: Map<string, ProviderSessionState> | undefined): void;
28
28
  export declare function isAnthropicFastModeUnsupportedError(error: unknown): boolean;
29
- export declare const claudeCodeVersion = "2.1.160";
29
+ export declare const claudeCodeVersion = "2.1.165";
30
+ export declare const claudeAgentSdkVersion = "0.3.165";
31
+ export declare const claudeClientVersion = "1.11187.4";
30
32
  export declare const claudeToolPrefix: string;
31
- export declare const claudeCodeSystemInstruction = "You are Claude Code, Anthropic's official CLI for Claude.";
33
+ export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
34
+ export declare const CLAUDE_CODE_MAX_OUTPUT_TOKENS = 64000;
32
35
  export declare function mapStainlessOs(platform: string): "MacOS" | "Windows" | "Linux" | "FreeBSD" | `Other::${string}`;
33
36
  export declare function mapStainlessArch(arch: string): "x64" | "arm64" | "x86" | `other::${string}`;
34
37
  export declare const claudeCodeHeaders: {
@@ -40,11 +43,13 @@ export declare const claudeCodeHeaders: {
40
43
  "X-Stainless-Arch": "arm64" | "x64" | "x86" | `other::${string}`;
41
44
  "X-Stainless-OS": "FreeBSD" | "Linux" | "MacOS" | "Windows" | `Other::${string}`;
42
45
  "X-Stainless-Timeout": string;
46
+ "anthropic-client-platform": string;
47
+ "anthropic-client-version": string;
43
48
  };
44
49
  export declare function isClaudeCloakingUserId(userId: string): boolean;
45
50
  export declare function generateClaudeCloakingUserId(): string;
46
- export declare const applyClaudeToolPrefix: (name: string, prefixOverride?: string) => string;
47
- export declare const stripClaudeToolPrefix: (name: string, prefixOverride?: string) => string;
51
+ export declare const applyClaudeToolPrefix: (name: string) => string;
52
+ export declare const stripClaudeToolPrefix: (name: string) => string;
48
53
  export type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max";
49
54
  export type AnthropicThinkingDisplay = "summarized" | "omitted";
50
55
  export interface AnthropicOptions extends StreamOptions {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "15.10.3",
4
+ "version": "15.10.4",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@bufbuild/protobuf": "^2.12.0",
42
- "@oh-my-pi/pi-utils": "15.10.3",
42
+ "@oh-my-pi/pi-utils": "15.10.4",
43
43
  "openai": "^6.39.0",
44
44
  "partial-json": "^0.1.7",
45
45
  "zod": "4.4.3"
@@ -196,9 +196,9 @@ const sharedHeaders = {
196
196
  "Accept-Encoding": "gzip, deflate, br, zstd",
197
197
  Connection: "keep-alive",
198
198
  "Content-Type": "application/json",
199
- "Anthropic-Version": "2023-06-01",
200
- "Anthropic-Dangerous-Direct-Browser-Access": "true",
201
- "X-App": "cli",
199
+ "anthropic-version": "2023-06-01",
200
+ "anthropic-dangerous-direct-browser-access": "true",
201
+ "x-app": "cli",
202
202
  };
203
203
 
204
204
  export function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record<string, string> {
@@ -216,7 +216,7 @@ export function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record<s
216
216
  ...modelHeaders,
217
217
  Accept: acceptHeader,
218
218
  ...sharedHeaders,
219
- "Anthropic-Beta": betaHeader,
219
+ "anthropic-beta": betaHeader,
220
220
  "cf-aig-authorization": `Bearer ${options.apiKey}`,
221
221
  };
222
222
  }
@@ -225,14 +225,14 @@ export function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record<s
225
225
  const incomingUserAgent = getHeaderCaseInsensitive(options.modelHeaders, "User-Agent");
226
226
  const userAgent = isClaudeCodeClientUserAgent(incomingUserAgent)
227
227
  ? incomingUserAgent
228
- : `claude-cli/${claudeCodeVersion} (external, cli)`;
228
+ : `claude-cli/${claudeCodeVersion} (external, local-agent, agent-sdk/${claudeAgentSdkVersion})`;
229
229
  return {
230
230
  ...modelHeaders,
231
231
  ...claudeCodeHeaders,
232
232
  Accept: acceptHeader,
233
233
  Authorization: `Bearer ${options.apiKey}`,
234
234
  ...sharedHeaders,
235
- "Anthropic-Beta": betaHeader,
235
+ "anthropic-beta": betaHeader,
236
236
  ...(options.claudeCodeSessionId ? { "X-Claude-Code-Session-Id": options.claudeCodeSessionId } : {}),
237
237
  "x-client-request-id": nodeCrypto.randomUUID(),
238
238
  "User-Agent": userAgent,
@@ -243,14 +243,14 @@ export function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record<s
243
243
  Accept: acceptHeader,
244
244
  Authorization: `Bearer ${options.apiKey}`,
245
245
  ...sharedHeaders,
246
- "Anthropic-Beta": betaHeader,
246
+ "anthropic-beta": betaHeader,
247
247
  };
248
248
  } else {
249
249
  return {
250
250
  ...modelHeaders,
251
251
  Accept: acceptHeader,
252
252
  ...sharedHeaders,
253
- "Anthropic-Beta": betaHeader,
253
+ "anthropic-beta": betaHeader,
254
254
  "X-Api-Key": options.apiKey,
255
255
  };
256
256
  }
@@ -260,12 +260,6 @@ type AnthropicCacheControl = NonNullable<TextBlockParam["cache_control"]>;
260
260
 
261
261
  type AnthropicOutputConfig = NonNullable<MessageCreateParamsStreaming["output_config"]>;
262
262
 
263
- function getAnthropicOutputConfig(params: MessageCreateParamsStreaming): AnthropicOutputConfig {
264
- const outputConfig = params.output_config ?? {};
265
- params.output_config = outputConfig;
266
- return outputConfig;
267
- }
268
-
269
263
  const ANTHROPIC_STOP_SEQUENCES_MAX = 4;
270
264
  let warnedStopSequencesTrim = false;
271
265
 
@@ -398,9 +392,14 @@ function getCacheControl(
398
392
  }
399
393
 
400
394
  // Stealth mode: mimic Claude Code's request fingerprint.
401
- export const claudeCodeVersion = "2.1.160";
402
- export const claudeToolPrefix: string = "proxy_";
403
- export const claudeCodeSystemInstruction = "You are Claude Code, Anthropic's official CLI for Claude.";
395
+ export const claudeCodeVersion = "2.1.165";
396
+ export const claudeAgentSdkVersion = "0.3.165";
397
+ export const claudeClientVersion = "1.11187.4";
398
+ export const claudeToolPrefix: string = "_";
399
+ export const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
400
+ // Claude Code caps requested output at 64k tokens even when the model ceiling is
401
+ // higher (e.g. Opus 4.8 supports 128k); clamp to match the wire fingerprint.
402
+ export const CLAUDE_CODE_MAX_OUTPUT_TOKENS = 64000;
404
403
 
405
404
  export function mapStainlessOs(platform: string): "MacOS" | "Windows" | "Linux" | "FreeBSD" | `Other::${string}` {
406
405
  switch (platform.toLowerCase()) {
@@ -443,7 +442,9 @@ export const claudeCodeHeaders = {
443
442
  "X-Stainless-Lang": "js",
444
443
  "X-Stainless-Arch": mapStainlessArch(process.arch),
445
444
  "X-Stainless-OS": mapStainlessOs(process.platform),
446
- "X-Stainless-Timeout": "600",
445
+ "X-Stainless-Timeout": "900",
446
+ "anthropic-client-platform": "desktop_app",
447
+ "anthropic-client-version": claudeClientVersion,
447
448
  };
448
449
 
449
450
  const enforcedHeaderKeys = new Set(
@@ -453,11 +454,11 @@ const enforcedHeaderKeys = new Set(
453
454
  "Accept-Encoding",
454
455
  "Connection",
455
456
  "Content-Type",
456
- "Anthropic-Version",
457
- "Anthropic-Dangerous-Direct-Browser-Access",
458
- "Anthropic-Beta",
457
+ "anthropic-version",
458
+ "anthropic-dangerous-direct-browser-access",
459
+ "anthropic-beta",
459
460
  "User-Agent",
460
- "X-App",
461
+ "x-app",
461
462
  "Authorization",
462
463
  "X-Api-Key",
463
464
  "X-Claude-Code-Session-Id",
@@ -480,7 +481,7 @@ function createClaudeBillingHeader(firstUserMessageText: string): string {
480
481
  .slice(0, 3);
481
482
  // cch=00000: placeholder replaced with the real attestation hash by wrapFetchForCch
482
483
  // before the request hits the wire (see below).
483
- return `${CLAUDE_BILLING_HEADER_PREFIX} cc_version=${claudeCodeVersion}.${versionSuffix}; cc_entrypoint=cli; ${CCH_PLACEHOLDER_STR};`;
484
+ return `${CLAUDE_BILLING_HEADER_PREFIX} cc_version=${claudeCodeVersion}.${versionSuffix}; cc_entrypoint=local-agent; ${CCH_PLACEHOLDER_STR};`;
484
485
  }
485
486
 
486
487
  // cch attestation: XXHash64(body_with_placeholder, seed) low-20-bits, 5 hex chars.
@@ -620,19 +621,17 @@ function resolveAnthropicMetadataUserId(
620
621
  return generateClaudeJsonUserId(sessionId);
621
622
  }
622
623
  const ANTHROPIC_BUILTIN_TOOL_NAMES = new Set(["web_search", "code_execution", "text_editor", "computer"]);
623
- export const applyClaudeToolPrefix = (name: string, prefixOverride: string = claudeToolPrefix) => {
624
- if (!prefixOverride) return name;
624
+ export const applyClaudeToolPrefix = (name: string): string => {
625
+ if (!claudeToolPrefix) return name;
625
626
  if (ANTHROPIC_BUILTIN_TOOL_NAMES.has(name.toLowerCase())) return name;
626
- const prefix = prefixOverride.toLowerCase();
627
- if (name.toLowerCase().startsWith(prefix)) return name;
628
- return `${prefixOverride}${name}`;
627
+ if (name.toLowerCase().startsWith(claudeToolPrefix.toLowerCase())) return name;
628
+ return `${claudeToolPrefix}${name}`;
629
629
  };
630
630
 
631
- export const stripClaudeToolPrefix = (name: string, prefixOverride: string = claudeToolPrefix) => {
632
- if (!prefixOverride) return name;
633
- const prefix = prefixOverride.toLowerCase();
634
- if (!name.toLowerCase().startsWith(prefix)) return name;
635
- return name.slice(prefixOverride.length);
631
+ export const stripClaudeToolPrefix = (name: string): string => {
632
+ if (!claudeToolPrefix) return name;
633
+ if (!name.toLowerCase().startsWith(claudeToolPrefix.toLowerCase())) return name;
634
+ return name.slice(claudeToolPrefix.length);
636
635
  };
637
636
 
638
637
  const ANTHROPIC_MANY_IMAGE_THRESHOLD = 20;
@@ -2285,13 +2284,6 @@ function extractClaudeCodeFirstUserMessageText(messages: readonly Message[]): st
2285
2284
  return "";
2286
2285
  }
2287
2286
 
2288
- function applyClaudeCodeContextManagement(params: MessageCreateParamsStreaming, isOAuthToken: boolean): void {
2289
- if (!isOAuthToken || params.thinking?.type !== "adaptive") return;
2290
- params.context_management = {
2291
- edits: [{ type: "clear_thinking_20251015", keep: "all" }],
2292
- };
2293
- }
2294
-
2295
2287
  function buildParams(
2296
2288
  model: Model<"anthropic-messages">,
2297
2289
  baseUrl: string,
@@ -2301,58 +2293,41 @@ function buildParams(
2301
2293
  disableStrictTools = false,
2302
2294
  ): MessageCreateParamsStreaming {
2303
2295
  const { cacheControl } = getCacheControl(model, baseUrl, options?.cacheRetention, isOAuthToken);
2304
- const params: MessageCreateParamsStreaming = {
2305
- model: model.id,
2306
- messages: convertAnthropicMessages(context.messages, model, isOAuthToken),
2307
- max_tokens: options?.maxTokens || model.maxTokens,
2308
- stream: true,
2309
- };
2310
- if (options?.temperature !== undefined && !options?.thinkingEnabled) {
2311
- params.temperature = options.temperature;
2312
- }
2313
-
2314
- if (options?.topP !== undefined) {
2315
- params.top_p = options.topP;
2316
- }
2317
- if (options?.topK !== undefined) {
2318
- params.top_k = options.topK;
2319
- }
2320
- if (options?.stopSequences?.length) {
2321
- const seqs = options.stopSequences;
2322
- if (seqs.length > ANTHROPIC_STOP_SEQUENCES_MAX && !warnedStopSequencesTrim) {
2323
- warnedStopSequencesTrim = true;
2324
- logger.warn("anthropic: stop_sequences exceeds 4; extra entries dropped", {
2325
- received: seqs.length,
2326
- kept: ANTHROPIC_STOP_SEQUENCES_MAX,
2327
- });
2328
- }
2329
- params.stop_sequences =
2330
- seqs.length > ANTHROPIC_STOP_SEQUENCES_MAX ? seqs.slice(0, ANTHROPIC_STOP_SEQUENCES_MAX) : seqs;
2331
- }
2332
2296
 
2333
- // Opus 4.7+ rejects non-default sampling parameters with 400 error.
2334
- if (hasOpus47ApiRestrictions(model.id)) {
2335
- delete params.top_p;
2336
- delete params.top_k;
2337
- delete params.temperature;
2338
- }
2297
+ // Pre-compute system blocks so they occupy the right slot in the serialized body.
2298
+ const shouldInjectClaudeCodeInstruction = isOAuthToken && !model.id.startsWith("claude-3-5-haiku");
2299
+ const firstUserMessageText = shouldInjectClaudeCodeInstruction
2300
+ ? extractClaudeCodeFirstUserMessageText(context.messages)
2301
+ : "";
2302
+ const systemBlocks = buildAnthropicSystemBlocks(context.systemPrompt, {
2303
+ includeClaudeCodeInstruction: shouldInjectClaudeCodeInstruction,
2304
+ firstUserMessageText,
2305
+ });
2339
2306
 
2307
+ // Pre-compute tools.
2308
+ let tools: ReturnType<typeof convertTools> | undefined;
2340
2309
  if (context.tools) {
2341
- params.tools = convertTools(
2310
+ tools = convertTools(
2342
2311
  context.tools,
2343
2312
  isOAuthToken,
2344
2313
  disableStrictTools || model.provider === "github-copilot",
2345
2314
  getAnthropicCompat(model).supportsEagerToolInputStreaming,
2346
2315
  );
2347
2316
  } else if (isOAuthToken) {
2348
- params.tools = [];
2317
+ tools = [];
2349
2318
  }
2350
2319
 
2320
+ // Pre-compute metadata.
2321
+ const metadataUserId = resolveAnthropicMetadataUserId(options?.metadata?.user_id, isOAuthToken, options?.sessionId);
2322
+ const metadata = metadataUserId ? { user_id: metadataUserId } : undefined;
2323
+
2324
+ // Pre-compute thinking + output_config effort.
2325
+ let thinking: MessageCreateParamsStreaming["thinking"] | undefined;
2326
+ let outputConfigEffort: AnthropicEffort | undefined;
2351
2327
  if (model.reasoning) {
2352
2328
  if (options?.thinkingEnabled) {
2353
2329
  const mode = model.thinking?.mode;
2354
2330
  const effort = resolveAnthropicAdaptiveEffort(model, options);
2355
-
2356
2331
  const compat = getAnthropicCompat(model);
2357
2332
  if (mode === "anthropic-adaptive" && !compat.disableAdaptiveThinking) {
2358
2333
  const adaptive: { type: "adaptive"; display?: AnthropicThinkingDisplay } = { type: "adaptive" };
@@ -2362,31 +2337,70 @@ function buildParams(
2362
2337
  if (options.thinkingDisplay !== undefined || supportsAdaptiveThinkingDisplay(model.id)) {
2363
2338
  adaptive.display = options.thinkingDisplay ?? "summarized";
2364
2339
  }
2365
- params.thinking = adaptive;
2366
- if (effort) {
2367
- getAnthropicOutputConfig(params).effort = effort;
2368
- }
2340
+ thinking = adaptive;
2341
+ if (effort) outputConfigEffort = effort;
2369
2342
  } else {
2370
- params.thinking = {
2343
+ thinking = {
2371
2344
  type: "enabled",
2372
2345
  budget_tokens: options.thinkingBudgetTokens || 1024,
2373
2346
  display: options.thinkingDisplay ?? "summarized",
2374
2347
  };
2375
- if (mode === "anthropic-budget-effort" && effort) {
2376
- getAnthropicOutputConfig(params).effort = effort;
2377
- }
2348
+ if (mode === "anthropic-budget-effort" && effort) outputConfigEffort = effort;
2378
2349
  }
2379
2350
  } else if (options?.thinkingEnabled === false) {
2380
- params.thinking = { type: "disabled" };
2351
+ thinking = { type: "disabled" };
2381
2352
  }
2382
2353
  }
2383
2354
 
2384
- if (options?.taskBudget) {
2385
- getAnthropicOutputConfig(params).task_budget = options.taskBudget;
2355
+ // Pre-compute context_management (depends on thinking).
2356
+ const contextManagement =
2357
+ isOAuthToken && thinking?.type === "adaptive"
2358
+ ? { edits: [{ type: "clear_thinking_20251015" as const, keep: "all" as const }] }
2359
+ : undefined;
2360
+
2361
+ // Pre-compute output_config.
2362
+ const outputConfigEntries: AnthropicOutputConfig = {};
2363
+ if (outputConfigEffort) outputConfigEntries.effort = outputConfigEffort;
2364
+ if (options?.taskBudget) outputConfigEntries.task_budget = options.taskBudget;
2365
+ const outputConfig = Object.keys(outputConfigEntries).length ? outputConfigEntries : undefined;
2366
+
2367
+ // Build params in the canonical field order: model → messages → system → tools →
2368
+ // metadata → max_tokens → thinking → context_management → output_config → stream.
2369
+ const params: MessageCreateParamsStreaming = {
2370
+ model: model.id,
2371
+ messages: convertAnthropicMessages(context.messages, model, isOAuthToken),
2372
+ ...(systemBlocks && { system: systemBlocks }),
2373
+ ...(tools !== undefined && { tools }),
2374
+ ...(metadata && { metadata }),
2375
+ max_tokens: Math.min(CLAUDE_CODE_MAX_OUTPUT_TOKENS, options?.maxTokens || model.maxTokens),
2376
+ ...(thinking && { thinking }),
2377
+ ...(contextManagement && { context_management: contextManagement }),
2378
+ ...(outputConfig && { output_config: outputConfig }),
2379
+ stream: true,
2380
+ };
2381
+
2382
+ // Opus 4.7+ rejects non-default sampling parameters with 400 error.
2383
+ const allowSamplingParams = !hasOpus47ApiRestrictions(model.id);
2384
+ if (allowSamplingParams && options?.temperature !== undefined && !options?.thinkingEnabled) {
2385
+ params.temperature = options.temperature;
2386
2386
  }
2387
- const metadataUserId = resolveAnthropicMetadataUserId(options?.metadata?.user_id, isOAuthToken, options?.sessionId);
2388
- if (metadataUserId) {
2389
- params.metadata = { user_id: metadataUserId };
2387
+ if (allowSamplingParams && options?.topP !== undefined) {
2388
+ params.top_p = options.topP;
2389
+ }
2390
+ if (allowSamplingParams && options?.topK !== undefined) {
2391
+ params.top_k = options.topK;
2392
+ }
2393
+ if (options?.stopSequences?.length) {
2394
+ const seqs = options.stopSequences;
2395
+ if (seqs.length > ANTHROPIC_STOP_SEQUENCES_MAX && !warnedStopSequencesTrim) {
2396
+ warnedStopSequencesTrim = true;
2397
+ logger.warn("anthropic: stop_sequences exceeds 4; extra entries dropped", {
2398
+ received: seqs.length,
2399
+ kept: ANTHROPIC_STOP_SEQUENCES_MAX,
2400
+ });
2401
+ }
2402
+ params.stop_sequences =
2403
+ seqs.length > ANTHROPIC_STOP_SEQUENCES_MAX ? seqs.slice(0, ANTHROPIC_STOP_SEQUENCES_MAX) : seqs;
2390
2404
  }
2391
2405
 
2392
2406
  if (resolveServiceTier(options?.serviceTier, model.provider) === "priority") {
@@ -2403,19 +2417,7 @@ function buildParams(
2403
2417
  }
2404
2418
  }
2405
2419
 
2406
- const shouldInjectClaudeCodeInstruction = isOAuthToken && !model.id.startsWith("claude-3-5-haiku");
2407
- const firstUserMessageText = shouldInjectClaudeCodeInstruction
2408
- ? extractClaudeCodeFirstUserMessageText(context.messages)
2409
- : "";
2410
- const systemBlocks = buildAnthropicSystemBlocks(context.systemPrompt, {
2411
- includeClaudeCodeInstruction: shouldInjectClaudeCodeInstruction,
2412
- firstUserMessageText,
2413
- });
2414
- if (systemBlocks) {
2415
- params.system = systemBlocks;
2416
- }
2417
2420
  disableThinkingIfToolChoiceForced(params);
2418
- applyClaudeCodeContextManagement(params, isOAuthToken);
2419
2421
  ensureMaxTokensForThinking(params, model);
2420
2422
  applyPromptCaching(params, cacheControl);
2421
2423
  enforceCacheControlLimit(params, 4);