@code-yeongyu/senpi-ai 2026.10.30-2 → 2026.10.31-2

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.
Files changed (49) hide show
  1. package/dist/api/anthropic-messages.d.ts +13 -3
  2. package/dist/api/anthropic-messages.js +62 -185
  3. package/dist/api/anthropic-system-blocks.d.ts +12 -0
  4. package/dist/api/anthropic-system-blocks.js +35 -0
  5. package/dist/api/openai-completions.js +19 -2
  6. package/dist/auth/helpers.d.ts +1 -0
  7. package/dist/auth/helpers.js +1 -0
  8. package/dist/auth/oauth/load.d.ts +0 -2
  9. package/dist/auth/oauth/load.js +0 -5
  10. package/dist/auth/pool/slots.d.ts +11 -1
  11. package/dist/auth/pool/slots.js +63 -1
  12. package/dist/auth/types.d.ts +7 -0
  13. package/dist/bun-oauth.js +0 -2
  14. package/dist/compat/extension-oauth-types.d.ts +1 -0
  15. package/dist/models.js +1 -1
  16. package/dist/oauth.d.ts +1 -1
  17. package/dist/oauth.js +1 -1
  18. package/dist/providers/anthropic.js +2 -1
  19. package/dist/providers/chatgpt-subscription.js +2 -0
  20. package/dist/providers/data/.manifest.json +1 -1
  21. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  22. package/dist/providers/data/opengateway.json +1 -1
  23. package/dist/providers/data/openrouter.json +1 -1
  24. package/dist/providers/data/venice.json +1 -1
  25. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  26. package/dist/types.d.ts +16 -0
  27. package/dist/utils/chatgpt-subscription-auth.d.ts +7 -0
  28. package/dist/utils/chatgpt-subscription-auth.js +21 -4
  29. package/dist/utils/prompt-cache-ttl.d.ts +2 -1
  30. package/dist/utils/prompt-cache-ttl.js +2 -0
  31. package/dist/utils/retry-hint.d.ts +3 -1
  32. package/dist/utils/retry-hint.js +80 -2
  33. package/dist/utils/tool-schema-compat.js +35 -0
  34. package/dist/utils/transcript.d.ts +1 -1
  35. package/dist/utils/transcript.js +12 -2
  36. package/dist/utils/validation.js +111 -4
  37. package/package.json +2 -2
  38. package/dist/auth/oauth/anthropic-callback-listener.d.ts +0 -40
  39. package/dist/auth/oauth/anthropic-callback-listener.js +0 -138
  40. package/dist/auth/oauth/anthropic.d.ts +0 -13
  41. package/dist/auth/oauth/anthropic.js +0 -285
  42. package/dist/auth/oauth/authorization-input.d.ts +0 -6
  43. package/dist/auth/oauth/authorization-input.js +0 -29
  44. package/dist/auth/oauth/error-details.d.ts +0 -3
  45. package/dist/auth/oauth/error-details.js +0 -20
  46. package/dist/utils/claude-code-version-cache.d.ts +0 -12
  47. package/dist/utils/claude-code-version-cache.js +0 -60
  48. package/dist/utils/claude-code-version.d.ts +0 -78
  49. package/dist/utils/claude-code-version.js +0 -203
@@ -1,9 +1,7 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
2
  import type { MessageCreateParamsNonStreaming } from "@anthropic-ai/sdk/resources/beta/messages/messages.js";
3
- import type { AnthropicRefusalFallback, Context, Model, SimpleStreamOptions, StreamFunction, StreamOptions, Tool } from "../types.ts";
3
+ import type { AnthropicRefusalFallback, Context, Model, SimpleStreamOptions, StreamFunction, StreamOptions } from "../types.ts";
4
4
  export { getAnthropicCompat } from "../utils/prompt-cache-ttl.ts";
5
- export declare const toClaudeCodeName: (name: string) => string;
6
- export declare const fromClaudeCodeName: (name: string, tools?: Tool[]) => string;
7
5
  export type AnthropicEffort = "low" | "medium" | "high" | "xhigh" | "max";
8
6
  export type AnthropicThinkingDisplay = "summarized" | "omitted";
9
7
  export interface AnthropicOptions extends StreamOptions {
@@ -72,6 +70,18 @@ export interface AnthropicOptions extends StreamOptions {
72
70
  */
73
71
  client?: Anthropic;
74
72
  }
73
+ /**
74
+ * A Claude OAuth token (`sk-ant-oat...`) offered as an API key. senpi no longer sends such a token
75
+ * to the Messages API while presenting itself as Claude Code (senpi#5131); the subscription path is
76
+ * the `anthropic-subscription` provider, which runs the official Claude Code CLI.
77
+ */
78
+ export declare class ClaudeOAuthTokenRefusedError extends Error {
79
+ readonly code = "claude_oauth_token_refused";
80
+ readonly provider: string;
81
+ readonly modelId: string;
82
+ constructor(provider: string, modelId: string);
83
+ }
84
+ export declare function isClaudeOAuthTokenRefusedError(error: unknown): error is ClaudeOAuthTokenRefusedError;
75
85
  export declare const stream: StreamFunction<"anthropic-messages", AnthropicOptions>;
76
86
  export declare const streamSimple: StreamFunction<"anthropic-messages", SimpleStreamOptions>;
77
87
  export declare function buildAnthropicWarmPromptCacheParams(model: Model<"anthropic-messages">, context: Context, options?: AnthropicOptions): MessageCreateParamsNonStreaming;
@@ -6,7 +6,6 @@ import { registerSessionResourceCleanup } from "../session-resources.js";
6
6
  import { isVideoMimeType } from "../types.js";
7
7
  import { combineAbortSignals } from "../utils/abort-signals.js";
8
8
  import { pushAssistantProgress } from "../utils/assistant-capture.js";
9
- import { claudeCodeVersionTooOldHint, getClaudeCodeVersion, isClaudeCodeVersionTooOldError, recoverClaudeCodeVersion, } from "../utils/claude-code-version.js";
10
9
  import { splitDeferredTools } from "../utils/deferred-tools.js";
11
10
  import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
12
11
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
@@ -23,6 +22,7 @@ import { retryProviderRequest } from "../utils/provider-retry.js";
23
22
  import { appendRetryAfterMsMarker, extract429RetryAfterMs } from "../utils/retry-hint.js";
24
23
  import { normalizeAnthropicRetryFailure } from "../utils/retry-profile/failure.js";
25
24
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
25
+ import { anthropicSystemTextBlocks } from "./anthropic-system-blocks.js";
26
26
  import { applyServerFallbackAbort, applyServerFallbackContinuation, parseServerFallbackReceipt, parseStickyFallbackReceipt, } from "../utils/server-fallback-receipt.js";
27
27
  import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
28
28
  import { normalizeToolCallId } from "../utils/tool-call-id.js";
@@ -72,49 +72,6 @@ function getCacheControl(model, cacheRetention, env) {
72
72
  cacheControl: { type: "ephemeral", ...(ttl && { ttl }) },
73
73
  };
74
74
  }
75
- // Stealth mode: Mimic Claude Code's identity and tool naming exactly.
76
- // The bundled Claude Code version and the floor of the advertised `claude-cli/<version>`
77
- // (see utils/claude-code-version.ts). Keep this exact declaration: a downstream installer
78
- // (oh-my-openagent) rewrites it byte-for-byte in the installed dist and bundle.
79
- const claudeCodeVersion = "2.1.306";
80
- // Claude Code 2.x tool names (canonical casing)
81
- // Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md
82
- // To update: https://github.com/badlogic/cchistory
83
- const claudeCodeTools = [
84
- "Read",
85
- "Write",
86
- "Edit",
87
- "Bash",
88
- "Grep",
89
- "Glob",
90
- "AskUserQuestion",
91
- "EnterPlanMode",
92
- "ExitPlanMode",
93
- "KillShell",
94
- "NotebookEdit",
95
- "Skill",
96
- "Task",
97
- "TaskOutput",
98
- "TodoWrite",
99
- "WebFetch",
100
- "WebSearch",
101
- ];
102
- const ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));
103
- const ccToolAliases = new Map([["ask_user_question", "AskUserQuestion"]]);
104
- // Convert tool name to CC canonical casing if it matches (case-insensitive)
105
- export const toClaudeCodeName = (name) => ccToolAliases.get(name) ?? ccToolLookup.get(name.toLowerCase()) ?? name;
106
- export const fromClaudeCodeName = (name, tools) => {
107
- if (tools && tools.length > 0) {
108
- const alias = [...ccToolAliases].find(([, wireName]) => wireName === name);
109
- if (alias && tools.some((tool) => tool.name === alias[0]))
110
- return alias[0];
111
- const lowerName = name.toLowerCase();
112
- const matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName);
113
- if (matchedTool)
114
- return matchedTool.name;
115
- }
116
- return name;
117
- };
118
75
  function convertContentBlocks(content) {
119
76
  // If only text blocks, return as concatenated string for simplicity
120
77
  const hasImages = content.some((c) => c.type === "image");
@@ -322,6 +279,28 @@ function assertRequestAuth(provider, apiKey, headers) {
322
279
  if (!hasRequestAuth(apiKey, headers))
323
280
  throw new Error(`No API key for provider: ${provider}`);
324
281
  }
282
+ const CLAUDE_OAUTH_TOKEN_MARKER = "sk-ant-oat";
283
+ /**
284
+ * A Claude OAuth token (`sk-ant-oat...`) offered as an API key. senpi no longer sends such a token
285
+ * to the Messages API while presenting itself as Claude Code (senpi#5131); the subscription path is
286
+ * the `anthropic-subscription` provider, which runs the official Claude Code CLI.
287
+ */
288
+ export class ClaudeOAuthTokenRefusedError extends Error {
289
+ constructor(provider, modelId) {
290
+ super(`${provider}/${modelId}: a Claude OAuth token (${CLAUDE_OAUTH_TOKEN_MARKER}...) is not accepted as an API key; senpi does not send Claude subscription tokens to the Anthropic API directly. For a Claude Pro/Max subscription sign in with /login anthropic-subscription (or set CLAUDE_CODE_OAUTH_TOKEN) and use anthropic-subscription/${modelId}; otherwise use an Anthropic API key.`);
291
+ this.code = "claude_oauth_token_refused";
292
+ this.name = "ClaudeOAuthTokenRefusedError";
293
+ this.provider = provider;
294
+ this.modelId = modelId;
295
+ }
296
+ }
297
+ export function isClaudeOAuthTokenRefusedError(error) {
298
+ return error instanceof ClaudeOAuthTokenRefusedError;
299
+ }
300
+ function assertNotClaudeOAuthToken(model, apiKey) {
301
+ if (apiKey?.includes(CLAUDE_OAUTH_TOKEN_MARKER))
302
+ throw new ClaudeOAuthTokenRefusedError(model.provider, model.id);
303
+ }
325
304
  /**
326
305
  * Anthropic SDK client that never runs the SDK's own credential chain
327
306
  * (ANTHROPIC_PROFILE config files, federation env vars). Without this, every
@@ -409,7 +388,7 @@ function userMessageClosesServerTurn(message) {
409
388
  return message.content.trim().length > 0;
410
389
  return message.content.some((block) => (block.type === "text" && block.text.trim().length > 0) || block.type === "image");
411
390
  }
412
- function collectProviderNativeToolPairing(messages, model, deferredToolNames, normalizeToolName, discardedFallbackToolCallIds) {
391
+ function collectProviderNativeToolPairing(messages, model, deferredToolNames, discardedFallbackToolCallIds) {
413
392
  const resolvedUseIds = new Set();
414
393
  const validResultIds = new Set();
415
394
  // Uses whose turn can still be resumed. A same-model assistant resets the
@@ -461,10 +440,9 @@ function collectProviderNativeToolPairing(messages, model, deferredToolNames, no
461
440
  // and the pending use resumable.
462
441
  let emitsReferences = false;
463
442
  for (const name of message.addedToolNames ?? []) {
464
- const normalizedName = normalizeToolName(name);
465
- if (!deferredToolNames.has(normalizedName) || loadedReferenceNames.has(normalizedName))
443
+ if (!deferredToolNames.has(name) || loadedReferenceNames.has(name))
466
444
  continue;
467
- loadedReferenceNames.add(normalizedName);
445
+ loadedReferenceNames.add(name);
468
446
  emitsReferences = true;
469
447
  }
470
448
  if (emitsReferences)
@@ -925,7 +903,6 @@ async function* iterateAnthropicEvents(response, signal, onTransportActivity) {
925
903
  export const stream = (model, context, options) => {
926
904
  const stream = new AssistantMessageEventStream({ captureBlocks: true });
927
905
  const normalizedContext = resolveTranscript(context, supportsMidConvoSystemMessages(model));
928
- const currentTools = getCurrentTools(normalizedContext.messages);
929
906
  (async () => {
930
907
  const providerThinkingLevel = managedEffortForRequest(model, options);
931
908
  const output = {
@@ -952,8 +929,6 @@ export const stream = (model, context, options) => {
952
929
  const requestSignal = combinedAbort.signal;
953
930
  try {
954
931
  let client;
955
- let isOAuth = false;
956
- let advertisedClaudeCodeVersion;
957
932
  let usageModel = model;
958
933
  let inputTransformations;
959
934
  let openClient;
@@ -966,6 +941,7 @@ export const stream = (model, context, options) => {
966
941
  const federation = getAnthropicFederation(model, apiKey, optionsHeaders, options?.env);
967
942
  if (!federation)
968
943
  assertRequestAuth(model.provider, apiKey, optionsHeaders);
944
+ assertNotClaudeOAuthToken(model, apiKey);
969
945
  let copilotDynamicHeaders;
970
946
  if (model.provider === "github-copilot") {
971
947
  const hasImages = hasCopilotVisionInput(normalizedContext.messages);
@@ -977,37 +953,12 @@ export const stream = (model, context, options) => {
977
953
  const cacheRetention = resolveCacheRetention(options?.cacheRetention ?? model.cacheRetention, options?.env, "short");
978
954
  const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
979
955
  openClient = () => {
980
- const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, normalizedContext), options?.refusalFallbacks !== undefined && configuredBetaHeader(model, options) !== null, optionsHeaders, getAnthropicCompat(model).supportsGzipRequestBody
956
+ client = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, normalizedContext), options?.refusalFallbacks !== undefined && configuredBetaHeader(model, options) !== null, optionsHeaders, getAnthropicCompat(model).supportsGzipRequestBody
981
957
  ? withGzipRequestBodies(options?.fetch)
982
958
  : options?.fetch, copilotDynamicHeaders, cacheSessionId, options?.env, federation);
983
- client = created.client;
984
- isOAuth = created.isOAuthToken;
985
- advertisedClaudeCodeVersion = created.claudeCodeVersion;
986
959
  };
987
960
  openClient();
988
961
  }
989
- // One retry per request: a `claude_code_version_too_old` 400 names the version Anthropic
990
- // wants, so the fingerprint is raised and the same request goes out again with it.
991
- let claudeCodeVersionRetried = false;
992
- const retryWithNewerClaudeCode = async (error) => {
993
- if (claudeCodeVersionRetried ||
994
- openClient === undefined ||
995
- advertisedClaudeCodeVersion === undefined ||
996
- !isClaudeCodeVersionTooOldError(error)) {
997
- return false;
998
- }
999
- const next = await recoverClaudeCodeVersion(error, claudeCodeVersion, advertisedClaudeCodeVersion, options?.env);
1000
- if (next === undefined)
1001
- return false;
1002
- claudeCodeVersionRetried = true;
1003
- openClient();
1004
- return true;
1005
- };
1006
- const noteTooOldClaudeCode = (error) => {
1007
- if (advertisedClaudeCodeVersion !== undefined && isClaudeCodeVersionTooOldError(error)) {
1008
- error.message = `${error.message}\n${claudeCodeVersionTooOldHint(advertisedClaudeCodeVersion)}`;
1009
- }
1010
- };
1011
962
  const fallbackKey = unsignedThinkingFallbackKey(model, options?.sessionId);
1012
963
  let unsignedThinkingReplay = fallbackKey && unsignedThinkingTextReplayFallbacks.has(fallbackKey)
1013
964
  ? "text"
@@ -1024,7 +975,7 @@ export const stream = (model, context, options) => {
1024
975
  }
1025
976
  let lastSentParams;
1026
977
  const createRequest = async () => {
1027
- let params = buildParams(model, normalizedContext, isOAuth, options, unsignedThinkingReplay);
978
+ let params = buildParams(model, normalizedContext, options, unsignedThinkingReplay);
1028
979
  const nextParams = await options?.onPayload?.(params, model);
1029
980
  if (nextParams !== undefined) {
1030
981
  params = nextParams;
@@ -1109,16 +1060,6 @@ export const stream = (model, context, options) => {
1109
1060
  }
1110
1061
  if (markNewlyExpiredSearches(error))
1111
1062
  return createRequestDemotingExpiredSearches();
1112
- if (await retryWithNewerClaudeCode(error)) {
1113
- try {
1114
- return await createRequest();
1115
- }
1116
- catch (retryError) {
1117
- noteTooOldClaudeCode(retryError);
1118
- throw retryError;
1119
- }
1120
- }
1121
- noteTooOldClaudeCode(error);
1122
1063
  throw error;
1123
1064
  }
1124
1065
  }, {
@@ -1231,9 +1172,7 @@ export const stream = (model, context, options) => {
1231
1172
  const block = {
1232
1173
  type: "toolCall",
1233
1174
  id: event.content_block.id,
1234
- name: isOAuth
1235
- ? fromClaudeCodeName(event.content_block.name, currentTools)
1236
- : event.content_block.name,
1175
+ name: event.content_block.name,
1237
1176
  arguments: isRecord(event.content_block.input)
1238
1177
  ? event.content_block.input
1239
1178
  : {},
@@ -1593,6 +1532,7 @@ export const streamSimple = (model, context, options) => {
1593
1532
  if (!getAnthropicFederation(model, apiKey, optionsHeaders, options?.env)) {
1594
1533
  assertRequestAuth(model.provider, apiKey, optionsHeaders);
1595
1534
  }
1535
+ assertNotClaudeOAuthToken(model, apiKey);
1596
1536
  const base = {
1597
1537
  ...buildBaseOptions(model, context, options, options?.apiKey),
1598
1538
  toolChoice: options?.toolChoice,
@@ -1624,9 +1564,6 @@ export const streamSimple = (model, context, options) => {
1624
1564
  thinkingBudgetTokens: Math.min(adjusted.thinkingBudget, Math.max(0, maxTokens - 1024)),
1625
1565
  });
1626
1566
  };
1627
- function isOAuthToken(apiKey) {
1628
- return apiKey.includes("sk-ant-oat");
1629
- }
1630
1567
  function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, useServerSideFallbackBeta, optionsHeaders, fetch, dynamicHeaders, sessionId, env, federation) {
1631
1568
  // Adaptive thinking models have interleaved thinking built in, so skip the beta header.
1632
1569
  const needsInterleavedBeta = interleavedThinking && !supportsAdaptiveThinking(model);
@@ -1656,7 +1593,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
1656
1593
  ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
1657
1594
  }, model.headers, optionsHeaders)),
1658
1595
  });
1659
- return { client, isOAuthToken: false };
1596
+ return client;
1660
1597
  }
1661
1598
  // Copilot: Bearer auth, selective betas.
1662
1599
  if (model.provider === "github-copilot") {
@@ -1672,26 +1609,7 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
1672
1609
  ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
1673
1610
  }, model.headers, dynamicHeaders, optionsHeaders)),
1674
1611
  });
1675
- return { client, isOAuthToken: false };
1676
- }
1677
- // OAuth: Bearer auth, Claude Code identity headers
1678
- if (apiKey && isOAuthToken(apiKey)) {
1679
- const advertisedClaudeCodeVersion = getClaudeCodeVersion(claudeCodeVersion, env);
1680
- const client = new PiAnthropic({
1681
- apiKey: null,
1682
- authToken: apiKey,
1683
- baseURL: model.baseUrl,
1684
- dangerouslyAllowBrowser: true,
1685
- fetch,
1686
- defaultHeaders: sanitizeAdaptiveThinkingHeaders(model, mergeClientHeaders(model, {
1687
- accept: "application/json",
1688
- "anthropic-dangerous-direct-browser-access": "true",
1689
- "anthropic-beta": ["claude-code-20250219", "oauth-2025-04-20", ...betaFeatures].join(","),
1690
- "user-agent": `claude-cli/${advertisedClaudeCodeVersion}`,
1691
- "x-app": "cli",
1692
- }, model.headers, optionsHeaders)),
1693
- });
1694
- return { client, isOAuthToken: true, claudeCodeVersion: advertisedClaudeCodeVersion };
1612
+ return client;
1695
1613
  }
1696
1614
  // API key, header-owned auth, or workload identity federation.
1697
1615
  const affinityCompat = getAnthropicCompat(model);
@@ -1720,9 +1638,9 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
1720
1638
  });
1721
1639
  federationClient = { key, fetch, client };
1722
1640
  }
1723
- return { client: federationClient.client.withOptions({ defaultHeaders }), isOAuthToken: false };
1641
+ return federationClient.client.withOptions({ defaultHeaders });
1724
1642
  }
1725
- const client = new PiAnthropic({
1643
+ return new PiAnthropic({
1726
1644
  apiKey: apiKey ?? null,
1727
1645
  authToken: null,
1728
1646
  baseURL: model.baseUrl,
@@ -1730,13 +1648,12 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
1730
1648
  fetch,
1731
1649
  defaultHeaders,
1732
1650
  });
1733
- return { client, isOAuthToken: false };
1734
1651
  }
1735
1652
  export function buildAnthropicWarmPromptCacheParams(model, context, options) {
1736
1653
  // Public fork entry (warmPromptCache, cache-keepalive): accepts a raw Context and builds
1737
1654
  // the same transcript the turn's stream() resolves, so the warmed prefix matches.
1738
1655
  const transcript = resolveTranscript(normalizeContext(context), supportsMidConvoSystemMessages(model));
1739
- const params = buildParams(model, transcript, false, {
1656
+ const params = buildParams(model, transcript, {
1740
1657
  ...options,
1741
1658
  maxTokens: 0,
1742
1659
  thinkingEnabled: undefined,
@@ -1758,14 +1675,12 @@ function configuredBetaHeader(model, options) {
1758
1675
  }
1759
1676
  return configured;
1760
1677
  }
1761
- function getBetaFeatures(model, context, isOAuthToken, shape, options) {
1678
+ function getBetaFeatures(model, context, shape, options) {
1762
1679
  const configured = configuredBetaHeader(model, options);
1763
1680
  if (configured === null)
1764
1681
  return [];
1765
1682
  // Betas the request body depends on: the API rejects the request without them.
1766
1683
  const required = [];
1767
- if (isOAuthToken)
1768
- required.push("claude-code-20250219", "oauth-2025-04-20");
1769
1684
  if (shape.midConvoEffort)
1770
1685
  required.push(MID_CONVERSATION_OUTPUT_CONFIG_BETA);
1771
1686
  if (shape.thinkingBinding)
@@ -1800,7 +1715,7 @@ function defaultOptionalBetas(model, context, options) {
1800
1715
  features.push(SERVER_SIDE_FALLBACK_BETA);
1801
1716
  return features;
1802
1717
  }
1803
- function buildParams(model, context, isOAuthToken, options, unsignedThinkingReplay = getAnthropicCompat(model).unsignedThinkingReplay) {
1718
+ function buildParams(model, context, options, unsignedThinkingReplay = getAnthropicCompat(model).unsignedThinkingReplay) {
1804
1719
  const compat = getAnthropicCompat(model);
1805
1720
  const { cacheControl } = getCacheControl(model, options?.cacheRetention ?? model.cacheRetention, options?.env);
1806
1721
  const initialSystemMessage = getInitialSystemMessage(context.messages);
@@ -1815,7 +1730,7 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
1815
1730
  // old boundary (read) and the new one (written for the next step) are marked. Two checkpoints fit
1816
1731
  // only when the system block holds one, which then also covers the tools before it (senpi#3242).
1817
1732
  const stablePrefixMarkers = conversationMessages.filter((message) => message.role === "assistant" && message.promptCacheBreakpoint === true).length;
1818
- const systemHoldsCheckpoint = isOAuthToken || initialSystemText.length > 0;
1733
+ const systemHoldsCheckpoint = initialSystemText.length > 0;
1819
1734
  const stablePrefixCheckpointLimit = stablePrefixMarkers >= 2 && systemHoldsCheckpoint ? 2 : Math.min(1, stablePrefixMarkers);
1820
1735
  // Native tool changes reference tools by name, so a redefined name cannot be expressed,
1821
1736
  // and Anthropic rejects a tool list where every tool is deferred, so there must be an
@@ -1823,18 +1738,14 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
1823
1738
  const initialTools = initialSystemMessage?.toolsAdded ?? [];
1824
1739
  // A configured `anthropic-beta: null` asks for no betas at all, so the request takes a shape that needs none:
1825
1740
  // the current tool list instead of native tool changes, top-level effort instead of per-message markers, and
1826
- // no server-side fallbacks (senpi#2957). OAuth still needs its identity betas; getBetaFeatures names them.
1741
+ // no server-side fallbacks (senpi#2957).
1827
1742
  const betasSuppressed = configuredBetaHeader(model, options) === null;
1828
- if (betasSuppressed && isOAuthToken) {
1829
- throw new Error(`The anthropic-beta header is set to null, but ${model.provider}/${model.id} is called with an OAuth token, which needs the claude-code-20250219 and oauth-2025-04-20 betas. Remove the null, or list the betas you want instead; senpi adds the ones a request needs.`);
1830
- }
1831
1743
  const midConvoEffort = model.compat?.supportsMidConvoEffort === true && !betasSuppressed;
1832
1744
  const nativeToolChanges = !betasSuppressed &&
1833
1745
  supportsMidConvoSystemMessages(model) &&
1834
1746
  supportsMidConvoToolChanges(model) &&
1835
1747
  initialTools.length > 0 &&
1836
1748
  !hasToolRedefinitions(context.messages);
1837
- const normalizeToolName = isOAuthToken ? toClaudeCodeName : (name) => name;
1838
1749
  // A marker on a discarded pre-fallback result must not defer its tool:
1839
1750
  // convertMessages drops that result, so no tool_reference would replay to
1840
1751
  // load the deferred definition.
@@ -1845,23 +1756,23 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
1845
1756
  // `defer_loading` and loaded by a `tool_reference`. Native tool changes own deferral otherwise.
1846
1757
  const toolPlacement = nativeToolChanges
1847
1758
  ? { immediate: [], deferred: new Map() }
1848
- : splitDeferredTools({ tools: getCurrentTools(context.messages), messages: partitionMessages }, compat.supportsToolReferences, normalizeToolName);
1759
+ : splitDeferredTools({ tools: getCurrentTools(context.messages), messages: partitionMessages }, compat.supportsToolReferences);
1849
1760
  let immediateTools = toolPlacement.immediate;
1850
1761
  let deferredTools = [...toolPlacement.deferred.values()];
1851
1762
  if (immediateTools.length === 0 && deferredTools.length > 0) {
1852
1763
  immediateTools = deferredTools;
1853
1764
  deferredTools = [];
1854
1765
  }
1855
- const deferredToolNames = new Set(deferredTools.map((tool) => normalizeToolName(tool.name)));
1766
+ const deferredToolNames = new Set(deferredTools.map((tool) => tool.name));
1856
1767
  const activeEffort = managedEffortForRequest(model, options) ?? "high";
1857
1768
  const sendFallbacks = options?.refusalFallbacks !== undefined && !betasSuppressed;
1858
- const betaFeatures = getBetaFeatures(model, context, isOAuthToken, {
1769
+ const betaFeatures = getBetaFeatures(model, context, {
1859
1770
  midConvoEffort,
1860
1771
  thinkingBinding: midConvoEffort && options?.thinkingEnabled !== false,
1861
1772
  nativeToolChanges,
1862
1773
  refusalFallbacks: sendFallbacks,
1863
1774
  }, options);
1864
- const convertedMessages = convertMessages(conversationMessages, model, isOAuthToken, cacheControl, unsignedThinkingReplay, deferredToolNames, normalizeToolName, midConvoEffort ? model.provider : undefined, nativeToolChanges, stablePrefixCheckpointLimit);
1775
+ const convertedMessages = convertMessages(conversationMessages, model, cacheControl, unsignedThinkingReplay, deferredToolNames, midConvoEffort ? model.provider : undefined, nativeToolChanges, stablePrefixCheckpointLimit);
1865
1776
  const messages = midConvoEffort
1866
1777
  ? insertThinkingLevelMessages(convertedMessages, activeEffort)
1867
1778
  : convertedMessages.messages;
@@ -1887,32 +1798,8 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
1887
1798
  stream: true,
1888
1799
  ...(betaFeatures.length > 0 ? { betas: betaFeatures } : {}),
1889
1800
  };
1890
- // For OAuth tokens, we MUST include Claude Code identity
1891
- if (isOAuthToken) {
1892
- params.system = [
1893
- {
1894
- type: "text",
1895
- text: "You are Claude Code, Anthropic's official CLI for Claude.",
1896
- ...(!initialSystemText && cacheControl ? { cache_control: cacheControl } : {}),
1897
- },
1898
- ];
1899
- if (initialSystemText) {
1900
- params.system.push({
1901
- type: "text",
1902
- text: sanitizeSurrogates(initialSystemText),
1903
- ...(cacheControl ? { cache_control: cacheControl } : {}),
1904
- });
1905
- }
1906
- }
1907
- else if (initialSystemText) {
1908
- // Add cache control to system prompt for non-OAuth tokens
1909
- params.system = [
1910
- {
1911
- type: "text",
1912
- text: sanitizeSurrogates(initialSystemText),
1913
- ...(cacheControl ? { cache_control: cacheControl } : {}),
1914
- },
1915
- ];
1801
+ if (initialSystemText) {
1802
+ params.system = anthropicSystemTextBlocks(initialSystemText, initialSystemMessage?.stablePrefix, cacheControl);
1916
1803
  }
1917
1804
  // Temperature is incompatible with extended thinking and unsupported on Claude Opus 4.7+.
1918
1805
  if (options?.temperature !== undefined && !options?.thinkingEnabled && compat.supportsTemperature) {
@@ -1932,15 +1819,15 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
1932
1819
  const initialNames = new Set(initialTools.map((tool) => tool.name));
1933
1820
  const laterTools = getDeclaredTools(context.messages).filter((tool) => !initialNames.has(tool.name));
1934
1821
  params.tools = [
1935
- ...convertTools(initialTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
1822
+ ...convertTools(initialTools, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
1936
1823
  DEFERRED_TOOL_PLACEHOLDER,
1937
- ...convertTools(laterTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, undefined, true),
1824
+ ...convertTools(laterTools, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, undefined, true),
1938
1825
  ];
1939
1826
  }
1940
1827
  else if (immediateTools.length > 0 || deferredTools.length > 0) {
1941
1828
  params.tools = [
1942
- ...convertTools(immediateTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
1943
- ...convertTools(deferredTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, undefined, true),
1829
+ ...convertTools(immediateTools, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
1830
+ ...convertTools(deferredTools, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, undefined, true),
1944
1831
  ];
1945
1832
  }
1946
1833
  // Managed effort models always use adaptive thinking so prefix mismatches can
@@ -2061,17 +1948,13 @@ function finalAssistantTurnStartsWithToolUse(messages) {
2061
1948
  }
2062
1949
  return false;
2063
1950
  }
2064
- function convertToolResult(msg, isOAuthToken, deferredToolNames, loadedToolNames, normalizeToolName) {
1951
+ function convertToolResult(msg, deferredToolNames, loadedToolNames) {
2065
1952
  const references = [];
2066
1953
  for (const name of msg.addedToolNames ?? []) {
2067
- const normalizedName = normalizeToolName(name);
2068
- if (!deferredToolNames.has(normalizedName) || loadedToolNames.has(normalizedName))
1954
+ if (!deferredToolNames.has(name) || loadedToolNames.has(name))
2069
1955
  continue;
2070
- loadedToolNames.add(normalizedName);
2071
- references.push({
2072
- type: "tool_reference",
2073
- tool_name: isOAuthToken ? toClaudeCodeName(name) : name,
2074
- });
1956
+ loadedToolNames.add(name);
1957
+ references.push({ type: "tool_reference", tool_name: name });
2075
1958
  }
2076
1959
  // The video block variant is not in the SDK's ContentBlockParam union, so the
2077
1960
  // converted array is cast through the same escape hatch as tool_reference blocks.
@@ -2091,7 +1974,7 @@ function convertToolResult(msg, isOAuthToken, deferredToolNames, loadedToolNames
2091
1974
  : convertedContent,
2092
1975
  };
2093
1976
  }
2094
- function convertMessages(transformedMessages, model, isOAuthToken, cacheControl, unsignedThinkingReplay = "text", deferredToolNames = new Set(), normalizeToolName = (name) => name, managedProvider, nativeToolChanges = false, stablePrefixCheckpointLimit = 0) {
1977
+ function convertMessages(transformedMessages, model, cacheControl, unsignedThinkingReplay = "text", deferredToolNames = new Set(), managedProvider, nativeToolChanges = false, stablePrefixCheckpointLimit = 0) {
2095
1978
  const params = [];
2096
1979
  const assistantLevels = new Map();
2097
1980
  const loadedToolNames = new Set();
@@ -2110,7 +1993,7 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
2110
1993
  // assistant turn below; drop their tool_results in lockstep so none dangle.
2111
1994
  const discardedFallbackToolCallIds = collectDiscardedFallbackToolCallIds(transformedMessages, model);
2112
1995
  const rejectsNativeWebSearchReplay = !getAnthropicCompat(model).supportsWebSearch;
2113
- const providerNativeToolPairing = collectProviderNativeToolPairing(transformedMessages, model, deferredToolNames, normalizeToolName, discardedFallbackToolCallIds);
1996
+ const providerNativeToolPairing = collectProviderNativeToolPairing(transformedMessages, model, deferredToolNames, discardedFallbackToolCallIds);
2114
1997
  for (let i = 0; i < transformedMessages.length; i++) {
2115
1998
  const msg = transformedMessages[i];
2116
1999
  if (msg.role === "system") {
@@ -2122,16 +2005,10 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
2122
2005
  blocks.push({ type: "text", text: sanitizeSurrogates(text) });
2123
2006
  if (nativeToolChanges) {
2124
2007
  for (const tool of msg.toolsRemoved ?? []) {
2125
- blocks.push({
2126
- type: "tool_removal",
2127
- tool: { type: "tool_reference", name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name },
2128
- });
2008
+ blocks.push({ type: "tool_removal", tool: { type: "tool_reference", name: tool.name } });
2129
2009
  }
2130
2010
  for (const tool of msg.toolsAdded ?? []) {
2131
- blocks.push({
2132
- type: "tool_addition",
2133
- tool: { type: "tool_reference", name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name },
2134
- });
2011
+ blocks.push({ type: "tool_addition", tool: { type: "tool_reference", name: tool.name } });
2135
2012
  }
2136
2013
  }
2137
2014
  if (blocks.length > 0)
@@ -2262,7 +2139,7 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
2262
2139
  blocks.push({
2263
2140
  type: "tool_use",
2264
2141
  id: block.id,
2265
- name: isOAuthToken ? toClaudeCodeName(block.name) : block.name,
2142
+ name: block.name,
2266
2143
  input: block.arguments ?? {},
2267
2144
  });
2268
2145
  }
@@ -2307,7 +2184,7 @@ function convertMessages(transformedMessages, model, isOAuthToken, cacheControl,
2307
2184
  while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
2308
2185
  const nextMsg = transformedMessages[j]; // We know it's a toolResult
2309
2186
  if (!discardedFallbackToolCallIds.has(nextMsg.toolCallId)) {
2310
- const converted = convertToolResult(nextMsg, isOAuthToken, deferredToolNames, loadedToolNames, normalizeToolName);
2187
+ const converted = convertToolResult(nextMsg, deferredToolNames, loadedToolNames);
2311
2188
  toolResults.push(converted.toolResult);
2312
2189
  siblingContent.push(...converted.siblingContent);
2313
2190
  }
@@ -2399,7 +2276,7 @@ const isAnthropicStrictUnsupportedKeyword = (key, value) => {
2399
2276
  return typeof value !== "string" || !ANTHROPIC_STRICT_STRING_FORMATS.has(value);
2400
2277
  return false;
2401
2278
  };
2402
- function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl, deferLoading = false) {
2279
+ function convertTools(tools, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl, deferLoading = false) {
2403
2280
  if (!tools)
2404
2281
  return [];
2405
2282
  return tools.map((tool, index) => {
@@ -2420,7 +2297,7 @@ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supp
2420
2297
  }
2421
2298
  : legacyInputSchema;
2422
2299
  return {
2423
- name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
2300
+ name: tool.name,
2424
2301
  description: tool.description,
2425
2302
  ...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),
2426
2303
  ...(strict === true ? { strict: true } : {}),
@@ -0,0 +1,12 @@
1
+ import type { BetaCacheControlEphemeral, BetaTextBlockParam as TextBlockParam } from "@anthropic-ai/sdk/resources/beta/messages/messages.js";
2
+ /**
3
+ * The system prompt as Anthropic `system` text blocks (senpi#4447).
4
+ *
5
+ * With a stable prefix, the prompt goes out as two blocks whose concatenation is the prompt: the
6
+ * part that is the same across sessions carries the system cache breakpoint, the part that changes
7
+ * between sessions (memory, MCP instructions) carries none. Another session then reads tools and the
8
+ * stable part from cache even when its tail differs, and the request keeps the same number of
9
+ * breakpoints. The later conversation breakpoint still caches the tail within a session.
10
+ */
11
+ export declare function anthropicSystemTextBlocks(text: string, stablePrefix: string | undefined, cacheControl: BetaCacheControlEphemeral | undefined): TextBlockParam[];
12
+ //# sourceMappingURL=anthropic-system-blocks.d.ts.map
@@ -0,0 +1,35 @@
1
+ import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
2
+ const HAS_VISIBLE_TEXT = /\S/;
3
+ /**
4
+ * The system prompt as Anthropic `system` text blocks (senpi#4447).
5
+ *
6
+ * With a stable prefix, the prompt goes out as two blocks whose concatenation is the prompt: the
7
+ * part that is the same across sessions carries the system cache breakpoint, the part that changes
8
+ * between sessions (memory, MCP instructions) carries none. Another session then reads tools and the
9
+ * stable part from cache even when its tail differs, and the request keeps the same number of
10
+ * breakpoints. The later conversation breakpoint still caches the tail within a session.
11
+ */
12
+ export function anthropicSystemTextBlocks(text, stablePrefix, cacheControl) {
13
+ const marker = cacheControl ? { cache_control: cacheControl } : {};
14
+ const boundary = splitPoint(text, stablePrefix);
15
+ if (boundary === undefined)
16
+ return [{ type: "text", text: sanitizeSurrogates(text), ...marker }];
17
+ return [
18
+ { type: "text", text: sanitizeSurrogates(text.slice(0, boundary)), ...marker },
19
+ { type: "text", text: sanitizeSurrogates(text.slice(boundary)) },
20
+ ];
21
+ }
22
+ /**
23
+ * The split offset, kept off a surrogate pair. Undefined when the text no longer starts with the prefix
24
+ * (a later rewrite of the prompt) or when either side would have no visible text.
25
+ */
26
+ function splitPoint(text, stablePrefix) {
27
+ if (stablePrefix === undefined || stablePrefix.length >= text.length || !text.startsWith(stablePrefix))
28
+ return undefined;
29
+ const code = text.charCodeAt(stablePrefix.length - 1);
30
+ const boundary = code >= 0xd800 && code <= 0xdbff ? stablePrefix.length - 1 : stablePrefix.length;
31
+ const stable = text.slice(0, boundary);
32
+ const rest = text.slice(boundary);
33
+ return HAS_VISIBLE_TEXT.test(stable) && HAS_VISIBLE_TEXT.test(rest) ? boundary : undefined;
34
+ }
35
+ //# sourceMappingURL=anthropic-system-blocks.js.map
@@ -1500,7 +1500,24 @@ export function convertMessages(model, context, compat, options = {}) {
1500
1500
  return params;
1501
1501
  }
1502
1502
  function convertTools(tools, compat) {
1503
- return tools.map((tool) => {
1503
+ const strictByTool = tools.map((tool) => tool.freeform || resolveGrammarConstrainedSampling(tool, compat.supportsOpenAIGrammarTools)
1504
+ ? undefined
1505
+ : resolveJsonSchemaStrictSampling(tool, compat.supportsStrictMode !== false));
1506
+ if (compat.mixedStrictTools === false) {
1507
+ const nonStrictTool = tools.find((tool, index) => !tool.freeform &&
1508
+ !resolveGrammarConstrainedSampling(tool, compat.supportsOpenAIGrammarTools) &&
1509
+ strictByTool[index] !== true);
1510
+ if (nonStrictTool) {
1511
+ const requiredTool = tools.find((tool) => tool.constrainedSampling &&
1512
+ tool.constrainedSampling.type === "json_schema" &&
1513
+ tool.constrainedSampling.strict === "require");
1514
+ if (requiredTool) {
1515
+ throw new Error(`Tool "${requiredTool.name}" requires JSON-schema constrained sampling, but tool "${nonStrictTool.name}" cannot be strict and this provider rejects mixed strict and non-strict tools.`);
1516
+ }
1517
+ strictByTool.fill(undefined);
1518
+ }
1519
+ }
1520
+ return tools.map((tool, index) => {
1504
1521
  const grammar = resolveGrammarConstrainedSampling(tool, compat.supportsOpenAIGrammarTools);
1505
1522
  if (grammar) {
1506
1523
  return {
@@ -1521,7 +1538,7 @@ function convertTools(tools, compat) {
1521
1538
  if (tool.freeform) {
1522
1539
  throw new Error("Freeform tools cannot be sent to OpenAI Chat Completions; use Responses API");
1523
1540
  }
1524
- const strict = resolveJsonSchemaStrictSampling(tool, compat.supportsStrictMode !== false);
1541
+ const strict = strictByTool[index];
1525
1542
  const schemaParameters = getJsonSchemaToolParameters(tool, strict);
1526
1543
  const normalizedParameters = compat.toolSchemaFlavor === "moonshot-mfjs"
1527
1544
  ? normalizeToolParametersForMoonshot(schemaParameters)
@@ -18,6 +18,7 @@ export declare function lazyOAuth(input: {
18
18
  isSubscription?: boolean;
19
19
  loginLabel?: string;
20
20
  rejectedTokenStatuses?: readonly number[];
21
+ accountIdentity?: OAuthAuth["accountIdentity"];
21
22
  load: () => Promise<OAuthAuth>;
22
23
  }): OAuthAuth;
23
24
  //# sourceMappingURL=helpers.d.ts.map