@bastani/pi-ai 0.9.20-alpha.2 → 0.9.20-alpha.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.
Files changed (264) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/NOTICE.md +1 -1
  3. package/README.md +28 -5
  4. package/dist/api/anthropic-messages.d.ts.map +1 -1
  5. package/dist/api/anthropic-messages.js +167 -85
  6. package/dist/api/anthropic-messages.js.map +1 -1
  7. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  8. package/dist/api/azure-openai-responses.js +17 -5
  9. package/dist/api/azure-openai-responses.js.map +1 -1
  10. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  11. package/dist/api/bedrock-converse-stream.js +15 -12
  12. package/dist/api/bedrock-converse-stream.js.map +1 -1
  13. package/dist/api/google-generative-ai.d.ts.map +1 -1
  14. package/dist/api/google-generative-ai.js +22 -80
  15. package/dist/api/google-generative-ai.js.map +1 -1
  16. package/dist/api/google-shared.d.ts +13 -4
  17. package/dist/api/google-shared.d.ts.map +1 -1
  18. package/dist/api/google-shared.js +54 -4
  19. package/dist/api/google-shared.js.map +1 -1
  20. package/dist/api/google-vertex.d.ts.map +1 -1
  21. package/dist/api/google-vertex.js +25 -65
  22. package/dist/api/google-vertex.js.map +1 -1
  23. package/dist/api/mistral-conversations.d.ts.map +1 -1
  24. package/dist/api/mistral-conversations.js +15 -11
  25. package/dist/api/mistral-conversations.js.map +1 -1
  26. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  27. package/dist/api/openai-codex-responses.js +20 -22
  28. package/dist/api/openai-codex-responses.js.map +1 -1
  29. package/dist/api/openai-completions.d.ts +6 -4
  30. package/dist/api/openai-completions.d.ts.map +1 -1
  31. package/dist/api/openai-completions.js +39 -57
  32. package/dist/api/openai-completions.js.map +1 -1
  33. package/dist/api/openai-responses-shared.d.ts +7 -5
  34. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  35. package/dist/api/openai-responses-shared.js +56 -56
  36. package/dist/api/openai-responses-shared.js.map +1 -1
  37. package/dist/api/openai-responses.d.ts.map +1 -1
  38. package/dist/api/openai-responses.js +14 -19
  39. package/dist/api/openai-responses.js.map +1 -1
  40. package/dist/api/pi-messages.d.ts +3 -3
  41. package/dist/api/pi-messages.d.ts.map +1 -1
  42. package/dist/api/pi-messages.js +2 -2
  43. package/dist/api/pi-messages.js.map +1 -1
  44. package/dist/api/simple-options.d.ts +3 -3
  45. package/dist/api/simple-options.d.ts.map +1 -1
  46. package/dist/api/simple-options.js.map +1 -1
  47. package/dist/api/transform-messages.d.ts.map +1 -1
  48. package/dist/api/transform-messages.js +21 -7
  49. package/dist/api/transform-messages.js.map +1 -1
  50. package/dist/auth/credential-store.d.ts.map +1 -1
  51. package/dist/auth/credential-store.js +3 -2
  52. package/dist/auth/credential-store.js.map +1 -1
  53. package/dist/auth/resolve.d.ts +4 -0
  54. package/dist/auth/resolve.d.ts.map +1 -1
  55. package/dist/auth/resolve.js +27 -8
  56. package/dist/auth/resolve.js.map +1 -1
  57. package/dist/compat.d.ts +3 -3
  58. package/dist/compat.d.ts.map +1 -1
  59. package/dist/compat.js +9 -6
  60. package/dist/compat.js.map +1 -1
  61. package/dist/env-api-keys.d.ts +2 -0
  62. package/dist/env-api-keys.d.ts.map +1 -1
  63. package/dist/env-api-keys.js +59 -43
  64. package/dist/env-api-keys.js.map +1 -1
  65. package/dist/image-models.generated.d.ts +30 -0
  66. package/dist/image-models.generated.d.ts.map +1 -1
  67. package/dist/image-models.generated.js +34 -4
  68. package/dist/image-models.generated.js.map +1 -1
  69. package/dist/index.d.ts +3 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +3 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/models.d.ts +5 -4
  74. package/dist/models.d.ts.map +1 -1
  75. package/dist/models.generated.d.ts +2 -0
  76. package/dist/models.generated.d.ts.map +1 -1
  77. package/dist/models.generated.js +2 -0
  78. package/dist/models.generated.js.map +1 -1
  79. package/dist/models.js +6 -3
  80. package/dist/models.js.map +1 -1
  81. package/dist/providers/amazon-bedrock.models.d.ts +1 -2
  82. package/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  83. package/dist/providers/amazon-bedrock.models.js.map +1 -1
  84. package/dist/providers/ant-ling.models.d.ts +1 -2
  85. package/dist/providers/ant-ling.models.d.ts.map +1 -1
  86. package/dist/providers/ant-ling.models.js.map +1 -1
  87. package/dist/providers/anthropic.models.d.ts +1 -2
  88. package/dist/providers/anthropic.models.d.ts.map +1 -1
  89. package/dist/providers/anthropic.models.js.map +1 -1
  90. package/dist/providers/azure-openai-responses.models.d.ts +1 -2
  91. package/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
  92. package/dist/providers/azure-openai-responses.models.js.map +1 -1
  93. package/dist/providers/baseten.models.d.ts +1 -2
  94. package/dist/providers/baseten.models.d.ts.map +1 -1
  95. package/dist/providers/baseten.models.js.map +1 -1
  96. package/dist/providers/cerebras.models.d.ts +1 -2
  97. package/dist/providers/cerebras.models.d.ts.map +1 -1
  98. package/dist/providers/cerebras.models.js.map +1 -1
  99. package/dist/providers/cloudflare-ai-gateway.models.d.ts +1 -2
  100. package/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
  101. package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  102. package/dist/providers/cloudflare-workers-ai.models.d.ts +1 -2
  103. package/dist/providers/cloudflare-workers-ai.models.d.ts.map +1 -1
  104. package/dist/providers/cloudflare-workers-ai.models.js.map +1 -1
  105. package/dist/providers/data/.manifest.json +1 -1
  106. package/dist/providers/data/amazon-bedrock.json +1 -1
  107. package/dist/providers/data/anthropic.json +1 -1
  108. package/dist/providers/data/baseten.json +1 -1
  109. package/dist/providers/data/cerebras.json +1 -1
  110. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  111. package/dist/providers/data/deepseek.json +1 -1
  112. package/dist/providers/data/fireworks.json +1 -1
  113. package/dist/providers/data/github-copilot.json +1 -1
  114. package/dist/providers/data/google-vertex.json +1 -1
  115. package/dist/providers/data/google.json +1 -1
  116. package/dist/providers/data/mistral.json +1 -1
  117. package/dist/providers/data/moonshotai-cn.json +1 -1
  118. package/dist/providers/data/moonshotai.json +1 -1
  119. package/dist/providers/data/nvidia.json +1 -1
  120. package/dist/providers/data/openai-codex.json +1 -1
  121. package/dist/providers/data/openai.json +1 -1
  122. package/dist/providers/data/opencode-go.json +1 -1
  123. package/dist/providers/data/opencode.json +1 -1
  124. package/dist/providers/data/openrouter.json +1 -1
  125. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  126. package/dist/providers/data/qwen-token-plan.json +1 -1
  127. package/dist/providers/data/radius.json +1 -0
  128. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  129. package/dist/providers/data/zai-coding-cn.json +1 -1
  130. package/dist/providers/data/zai.json +1 -1
  131. package/dist/providers/deepseek.models.d.ts +1 -2
  132. package/dist/providers/deepseek.models.d.ts.map +1 -1
  133. package/dist/providers/deepseek.models.js.map +1 -1
  134. package/dist/providers/faux.d.ts +2 -2
  135. package/dist/providers/faux.d.ts.map +1 -1
  136. package/dist/providers/faux.js +11 -11
  137. package/dist/providers/faux.js.map +1 -1
  138. package/dist/providers/fireworks.models.d.ts +1 -2
  139. package/dist/providers/fireworks.models.d.ts.map +1 -1
  140. package/dist/providers/fireworks.models.js.map +1 -1
  141. package/dist/providers/github-copilot.models.d.ts +1 -2
  142. package/dist/providers/github-copilot.models.d.ts.map +1 -1
  143. package/dist/providers/github-copilot.models.js.map +1 -1
  144. package/dist/providers/google-vertex.models.d.ts +1 -2
  145. package/dist/providers/google-vertex.models.d.ts.map +1 -1
  146. package/dist/providers/google-vertex.models.js.map +1 -1
  147. package/dist/providers/google.models.d.ts +1 -2
  148. package/dist/providers/google.models.d.ts.map +1 -1
  149. package/dist/providers/google.models.js.map +1 -1
  150. package/dist/providers/groq.models.d.ts +1 -2
  151. package/dist/providers/groq.models.d.ts.map +1 -1
  152. package/dist/providers/groq.models.js.map +1 -1
  153. package/dist/providers/huggingface.models.d.ts +1 -2
  154. package/dist/providers/huggingface.models.d.ts.map +1 -1
  155. package/dist/providers/huggingface.models.js.map +1 -1
  156. package/dist/providers/kimi-coding.models.d.ts +1 -2
  157. package/dist/providers/kimi-coding.models.d.ts.map +1 -1
  158. package/dist/providers/kimi-coding.models.js.map +1 -1
  159. package/dist/providers/minimax-cn.models.d.ts +1 -2
  160. package/dist/providers/minimax-cn.models.d.ts.map +1 -1
  161. package/dist/providers/minimax-cn.models.js.map +1 -1
  162. package/dist/providers/minimax.models.d.ts +1 -2
  163. package/dist/providers/minimax.models.d.ts.map +1 -1
  164. package/dist/providers/minimax.models.js.map +1 -1
  165. package/dist/providers/mistral.models.d.ts +1 -2
  166. package/dist/providers/mistral.models.d.ts.map +1 -1
  167. package/dist/providers/mistral.models.js.map +1 -1
  168. package/dist/providers/moonshotai-cn.models.d.ts +1 -2
  169. package/dist/providers/moonshotai-cn.models.d.ts.map +1 -1
  170. package/dist/providers/moonshotai-cn.models.js.map +1 -1
  171. package/dist/providers/moonshotai.models.d.ts +1 -2
  172. package/dist/providers/moonshotai.models.d.ts.map +1 -1
  173. package/dist/providers/moonshotai.models.js.map +1 -1
  174. package/dist/providers/nvidia.models.d.ts +1 -2
  175. package/dist/providers/nvidia.models.d.ts.map +1 -1
  176. package/dist/providers/nvidia.models.js.map +1 -1
  177. package/dist/providers/openai-codex.models.d.ts +1 -2
  178. package/dist/providers/openai-codex.models.d.ts.map +1 -1
  179. package/dist/providers/openai-codex.models.js.map +1 -1
  180. package/dist/providers/openai.models.d.ts +1 -2
  181. package/dist/providers/openai.models.d.ts.map +1 -1
  182. package/dist/providers/openai.models.js.map +1 -1
  183. package/dist/providers/opencode-go.models.d.ts +1 -2
  184. package/dist/providers/opencode-go.models.d.ts.map +1 -1
  185. package/dist/providers/opencode-go.models.js.map +1 -1
  186. package/dist/providers/opencode.models.d.ts +1 -2
  187. package/dist/providers/opencode.models.d.ts.map +1 -1
  188. package/dist/providers/opencode.models.js.map +1 -1
  189. package/dist/providers/openrouter.models.d.ts +1 -2
  190. package/dist/providers/openrouter.models.d.ts.map +1 -1
  191. package/dist/providers/openrouter.models.js.map +1 -1
  192. package/dist/providers/qwen-token-plan-cn.models.d.ts +1 -2
  193. package/dist/providers/qwen-token-plan-cn.models.d.ts.map +1 -1
  194. package/dist/providers/qwen-token-plan-cn.models.js.map +1 -1
  195. package/dist/providers/qwen-token-plan-individual.models.d.ts +1 -2
  196. package/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -1
  197. package/dist/providers/qwen-token-plan-individual.models.js.map +1 -1
  198. package/dist/providers/qwen-token-plan.models.d.ts +1 -2
  199. package/dist/providers/qwen-token-plan.models.d.ts.map +1 -1
  200. package/dist/providers/qwen-token-plan.models.js.map +1 -1
  201. package/dist/providers/radius.d.ts.map +1 -1
  202. package/dist/providers/radius.js +19 -5
  203. package/dist/providers/radius.js.map +1 -1
  204. package/dist/providers/radius.models.d.ts +3 -0
  205. package/dist/providers/radius.models.d.ts.map +1 -0
  206. package/dist/providers/radius.models.js +6 -0
  207. package/dist/providers/radius.models.js.map +1 -0
  208. package/dist/providers/together.models.d.ts +1 -2
  209. package/dist/providers/together.models.d.ts.map +1 -1
  210. package/dist/providers/together.models.js.map +1 -1
  211. package/dist/providers/vercel-ai-gateway.models.d.ts +1 -2
  212. package/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  213. package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  214. package/dist/providers/xai.models.d.ts +1 -2
  215. package/dist/providers/xai.models.d.ts.map +1 -1
  216. package/dist/providers/xai.models.js.map +1 -1
  217. package/dist/providers/xiaomi-token-plan-ams.models.d.ts +1 -2
  218. package/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
  219. package/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  220. package/dist/providers/xiaomi-token-plan-cn.models.d.ts +1 -2
  221. package/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
  222. package/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  223. package/dist/providers/xiaomi-token-plan-sgp.models.d.ts +1 -2
  224. package/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
  225. package/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  226. package/dist/providers/xiaomi.models.d.ts +1 -2
  227. package/dist/providers/xiaomi.models.d.ts.map +1 -1
  228. package/dist/providers/xiaomi.models.js.map +1 -1
  229. package/dist/providers/zai-coding-cn.models.d.ts +1 -2
  230. package/dist/providers/zai-coding-cn.models.d.ts.map +1 -1
  231. package/dist/providers/zai-coding-cn.models.js.map +1 -1
  232. package/dist/providers/zai.models.d.ts +1 -2
  233. package/dist/providers/zai.models.d.ts.map +1 -1
  234. package/dist/providers/zai.models.js.map +1 -1
  235. package/dist/types.d.ts +95 -26
  236. package/dist/types.d.ts.map +1 -1
  237. package/dist/types.js.map +1 -1
  238. package/dist/utils/diagnostics.d.ts +3 -2
  239. package/dist/utils/diagnostics.d.ts.map +1 -1
  240. package/dist/utils/diagnostics.js.map +1 -1
  241. package/dist/utils/estimate.d.ts +2 -2
  242. package/dist/utils/estimate.d.ts.map +1 -1
  243. package/dist/utils/estimate.js +8 -29
  244. package/dist/utils/estimate.js.map +1 -1
  245. package/dist/utils/overflow.d.ts +1 -0
  246. package/dist/utils/overflow.d.ts.map +1 -1
  247. package/dist/utils/overflow.js +17 -5
  248. package/dist/utils/overflow.js.map +1 -1
  249. package/dist/utils/retry.d.ts.map +1 -1
  250. package/dist/utils/retry.js +2 -0
  251. package/dist/utils/retry.js.map +1 -1
  252. package/dist/utils/text.d.ts +9 -1
  253. package/dist/utils/text.d.ts.map +1 -1
  254. package/dist/utils/text.js +26 -0
  255. package/dist/utils/text.js.map +1 -1
  256. package/dist/utils/transcript.d.ts +84 -0
  257. package/dist/utils/transcript.d.ts.map +1 -0
  258. package/dist/utils/transcript.js +204 -0
  259. package/dist/utils/transcript.js.map +1 -0
  260. package/package.json +3 -3
  261. package/dist/utils/deferred-tools.d.ts +0 -9
  262. package/dist/utils/deferred-tools.d.ts.map +0 -1
  263. package/dist/utils/deferred-tools.js +0 -36
  264. package/dist/utils/deferred-tools.js.map +0 -1
@@ -1,6 +1,5 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
2
  import { calculateCost } from "../models.js";
3
- import { splitDeferredTools } from "../utils/deferred-tools.js";
4
3
  import { appendAssistantMessageDiagnostic } from "../utils/diagnostics.js";
5
4
  import { assertSupportedDocumentMimeType } from "../utils/document-input.js";
6
5
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
@@ -11,6 +10,8 @@ import { getProviderEnvValue } from "../utils/provider-env.js";
11
10
  import { retryProviderRequest } from "../utils/provider-retry.js";
12
11
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
13
12
  import { createStreamDeadline, withStreamDeadline } from "../utils/stream-deadline.js";
13
+ import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
14
+ import { getCurrentTools, getDeclaredTools, getInitialSystemMessage, hasToolRedefinitions, resolveTranscript, } from "../utils/transcript.js";
14
15
  import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling } from "./constrained-sampling.js";
15
16
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput, preserveCopilotIntegrationHeader, } from "./github-copilot-headers.js";
16
17
  import { adjustMaxTokensForThinking, buildBaseOptions, clampMaxTokensToContext } from "./simple-options.js";
@@ -127,6 +128,20 @@ const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
127
128
  const SERVER_SIDE_FALLBACK_BETA = "server-side-fallback-2026-07-01";
128
129
  const THINKING_BINDING_CONTROLS_BETA = "thinking-binding-controls-2026-08-01";
129
130
  const MID_CONVERSATION_OUTPUT_CONFIG_BETA = "mid-conversation-output-config-2026-07-01";
131
+ const MID_CONVERSATION_TOOL_CHANGES_BETA = "mid-conversation-tool-changes-2026-07-01";
132
+ /**
133
+ * Stable deferred tool declared whenever native tool changes are in use. Anthropic adds
134
+ * hidden prompt scaffolding as soon as any tool has `defer_loading`; declaring this
135
+ * placeholder from the first request keeps that scaffolding in the cached prefix, so the
136
+ * first real late tool does not invalidate the cache (measured: full miss without it).
137
+ * It is never activated and the model cannot see it.
138
+ */
139
+ const DEFERRED_TOOL_PLACEHOLDER = {
140
+ name: "__pi_deferred_placeholder__",
141
+ description: "Reserved placeholder. Never available. Never call this.",
142
+ input_schema: { type: "object", properties: {}, required: [] },
143
+ defer_loading: true,
144
+ };
130
145
  function shouldUseServerSideFallbackBeta(model) {
131
146
  return (model.compat?.allowedFallbackModels?.length ?? 0) > 0;
132
147
  }
@@ -244,24 +259,10 @@ function getAnthropicCompat(model) {
244
259
  supportsTemperature: model.compat?.supportsTemperature ?? true,
245
260
  allowEmptySignature: model.compat?.allowEmptySignature ?? false,
246
261
  supportsStrictTools: model.compat?.supportsStrictTools ?? false,
247
- supportsToolReferences: model.compat?.supportsToolReferences ?? defaultSupportsToolReferences(model),
262
+ supportsMidConvoSystemMessages: model.compat?.supportsMidConvoSystemMessages ?? false,
263
+ supportsMidConvoToolChanges: model.compat?.supportsMidConvoToolChanges ?? false,
248
264
  };
249
265
  }
250
- /**
251
- * Default for `supportsToolReferences`: first-party Anthropic models except
252
- * Haiku (rejects client-side tool_reference blocks) and models that predate
253
- * tool search (Claude 3.x, Opus/Sonnet 4.0, Opus 4.1).
254
- */
255
- function defaultSupportsToolReferences(model) {
256
- if (model.provider !== "anthropic" || model.id.includes("haiku"))
257
- return false;
258
- const version = model.id.match(/^claude-(?:opus|sonnet|fable)-(\d+)(?:-(\d+))?(?:-|$)/);
259
- if (!version)
260
- return false;
261
- const major = Number(version[1]);
262
- const minor = version[2] && version[2].length < 8 ? Number(version[2]) : 0;
263
- return major > 4 || (major === 4 && minor >= 5);
264
- }
265
266
  function mergeHeaders(...headerSources) {
266
267
  const merged = {};
267
268
  for (const headers of headerSources) {
@@ -456,6 +457,8 @@ async function* iterateAnthropicEvents(response, signal) {
456
457
  }
457
458
  export const stream = (model, context, options) => {
458
459
  const stream = new AssistantMessageEventStream();
460
+ const normalizedContext = resolveTranscript(context, getAnthropicCompat(model).supportsMidConvoSystemMessages);
461
+ const currentTools = getCurrentTools(normalizedContext.messages);
459
462
  (async () => {
460
463
  const providerThinkingLevel = supportsMidConvoEffort(model) ? (options?.effort ?? "high") : undefined;
461
464
  const output = {
@@ -491,20 +494,23 @@ export const stream = (model, context, options) => {
491
494
  assertRequestAuth(model.provider, apiKey, options?.headers);
492
495
  let copilotDynamicHeaders;
493
496
  if (model.provider === "github-copilot") {
494
- const hasImages = hasCopilotVisionInput(context.messages);
497
+ const hasImages = hasCopilotVisionInput(normalizedContext.messages);
495
498
  copilotDynamicHeaders = preserveCopilotIntegrationHeader(model.headers, buildCopilotDynamicHeaders({
496
- messages: context.messages,
499
+ messages: normalizedContext.messages,
497
500
  hasImages,
498
501
  apiKey,
499
502
  }));
500
503
  }
501
504
  const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
502
505
  const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
503
- const created = createClient(model, apiKey, model.reasoning && options?.thinkingEnabled === true && (options.interleavedThinking ?? true), shouldUseFineGrainedToolStreamingBeta(model, context), shouldUseServerSideFallbackBeta(model), options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId);
506
+ const created = createClient(model, apiKey, model.reasoning && options?.thinkingEnabled === true && (options.interleavedThinking ?? true), shouldUseFineGrainedToolStreamingBeta(model, context), shouldUseServerSideFallbackBeta(model), options?.headers, options?.fetch, copilotDynamicHeaders, cacheSessionId, getAnthropicCompat(model).supportsMidConvoToolChanges &&
507
+ getAnthropicCompat(model).supportsMidConvoSystemMessages &&
508
+ (getInitialSystemMessage(normalizedContext.messages)?.toolsAdded?.length ?? 0) > 0 &&
509
+ !hasToolRedefinitions(normalizedContext.messages));
504
510
  client = created.client;
505
511
  isOAuth = created.isOAuthToken;
506
512
  }
507
- let params = buildParams(model, context, isOAuth, options);
513
+ let params = buildParams(model, normalizedContext, isOAuth, options);
508
514
  const nextParams = await options?.onPayload?.(params, model);
509
515
  if (nextParams !== undefined) {
510
516
  params = nextParams;
@@ -514,7 +520,7 @@ export const stream = (model, context, options) => {
514
520
  ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
515
521
  maxRetries: 0,
516
522
  };
517
- const response = await retryProviderRequest(() => client.messages.create({ ...params, stream: true }, requestOptions).asResponse(), {
523
+ const response = await retryProviderRequest(() => client.beta.messages.create({ ...params, stream: true }, requestOptions).asResponse(), {
518
524
  maxRetries: options?.maxRetries,
519
525
  maxRetryDelayMs: options?.maxRetryDelayMs,
520
526
  signal: streamDeadline.signal,
@@ -525,11 +531,13 @@ export const stream = (model, context, options) => {
525
531
  for await (const event of withStreamDeadline(iterateAnthropicEvents(response, streamDeadline.signal), streamDeadline.deadlineMs, streamDeadline.abort)) {
526
532
  if (event.type === "message_start") {
527
533
  output.responseId = event.message.id;
528
- output.model = event.message.model;
529
- const fallbackCost = output.model === model.id
534
+ const responseModel = event.message.model;
535
+ if (responseModel !== model.id)
536
+ output.responseModel = responseModel;
537
+ const fallbackCost = responseModel === model.id
530
538
  ? undefined
531
- : model.compat?.allowedFallbackModels?.find((fallback) => fallback.provider === model.provider && fallback.model === output.model)?.cost;
532
- usageModel = fallbackCost ? { ...model, id: output.model, cost: fallbackCost } : model;
539
+ : model.compat?.allowedFallbackModels?.find((fallback) => fallback.provider === model.provider && fallback.model === responseModel)?.cost;
540
+ usageModel = fallbackCost ? { ...model, id: responseModel, cost: fallbackCost } : model;
533
541
  // Capture initial token usage from message_start event
534
542
  // This ensures we have input token counts even if the stream is aborted early
535
543
  output.usage.input = event.message.usage.input_tokens || 0;
@@ -579,7 +587,7 @@ export const stream = (model, context, options) => {
579
587
  type: "toolCall",
580
588
  id: event.content_block.id,
581
589
  name: isOAuth
582
- ? fromClaudeCodeName(event.content_block.name, context.tools)
590
+ ? fromClaudeCodeName(event.content_block.name, currentTools)
583
591
  : event.content_block.name,
584
592
  arguments: event.content_block.input ?? {},
585
593
  partialJson: "",
@@ -839,7 +847,7 @@ export const streamSimple = (model, context, options) => {
839
847
  function isOAuthToken(apiKey) {
840
848
  return apiKey.includes("sk-ant-oat");
841
849
  }
842
- function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, useServerSideFallbackBeta, optionsHeaders, fetch, dynamicHeaders, sessionId) {
850
+ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, useServerSideFallbackBeta, optionsHeaders, fetch, dynamicHeaders, sessionId, useNativeToolChanges = false) {
843
851
  // Adaptive thinking models have interleaved thinking built in, so skip the beta header.
844
852
  const needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;
845
853
  const betaFeatures = [];
@@ -858,6 +866,8 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
858
866
  if (supportsMidConvoEffort(model)) {
859
867
  betaFeatures.push(MID_CONVERSATION_OUTPUT_CONFIG_BETA, THINKING_BINDING_CONTROLS_BETA);
860
868
  }
869
+ if (useNativeToolChanges)
870
+ betaFeatures.push(MID_CONVERSATION_TOOL_CHANGES_BETA);
861
871
  const uniqueBetaFeatures = [...new Set(betaFeatures)];
862
872
  // Copilot: Bearer auth, selective betas.
863
873
  if (model.provider === "github-copilot") {
@@ -915,20 +925,58 @@ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStre
915
925
  });
916
926
  return { client, isOAuthToken: false };
917
927
  }
928
+ function getNativeToolChangeBetas(model, context, isOAuth, options) {
929
+ let configured;
930
+ for (const headers of [model.headers, options?.headers]) {
931
+ for (const [name, value] of Object.entries(headers ?? {})) {
932
+ if (name.toLowerCase() === "anthropic-beta")
933
+ configured = value;
934
+ }
935
+ }
936
+ if (configured === null)
937
+ return [];
938
+ if (configured !== undefined)
939
+ return [
940
+ ...new Set(configured
941
+ .split(",")
942
+ .map((feature) => feature.trim())
943
+ .filter(Boolean)),
944
+ ];
945
+ const features = [MID_CONVERSATION_TOOL_CHANGES_BETA];
946
+ if (isOAuth)
947
+ features.push("claude-code-20250219", "oauth-2025-04-20");
948
+ if (shouldUseFineGrainedToolStreamingBeta(model, context))
949
+ features.push(FINE_GRAINED_TOOL_STREAMING_BETA);
950
+ if (model.reasoning &&
951
+ options?.thinkingEnabled &&
952
+ (options.interleavedThinking ?? true) &&
953
+ model.compat?.forceAdaptiveThinking !== true) {
954
+ features.push(INTERLEAVED_THINKING_BETA);
955
+ }
956
+ if (shouldUseServerSideFallbackBeta(model))
957
+ features.push(SERVER_SIDE_FALLBACK_BETA);
958
+ if (shouldUseThinkingBindingControlsBeta(model))
959
+ features.push(THINKING_BINDING_CONTROLS_BETA);
960
+ if (supportsMidConvoEffort(model))
961
+ features.push(MID_CONVERSATION_OUTPUT_CONFIG_BETA, THINKING_BINDING_CONTROLS_BETA);
962
+ return [...new Set(features)];
963
+ }
918
964
  function buildParams(model, context, isOAuthToken, options) {
919
965
  const { cacheControl } = getCacheControl(model, options?.cacheRetention, options?.env);
920
966
  const compat = getAnthropicCompat(model);
967
+ const initialSystemMessage = getInitialSystemMessage(context.messages);
968
+ const initialSystemText = initialSystemMessage ? getSystemMessageText(initialSystemMessage) : "";
921
969
  const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
922
- const normalizeToolName = isOAuthToken ? toClaudeCodeName : (name) => name;
923
- const toolPlacement = splitDeferredTools({ ...context, messages: transformedMessages }, compat.supportsToolReferences, normalizeToolName);
924
- let immediateTools = toolPlacement.immediate;
925
- let deferredTools = [...toolPlacement.deferred.values()];
926
- if (immediateTools.length === 0 && deferredTools.length > 0) {
927
- immediateTools = deferredTools;
928
- deferredTools = [];
929
- }
930
- const deferredToolNames = new Set(deferredTools.map((tool) => normalizeToolName(tool.name)));
931
- const converted = convertMessages(transformedMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, deferredToolNames, normalizeToolName, supportsMidConvoEffort(model) ? model.provider : undefined);
970
+ const conversationMessages = initialSystemMessage ? transformedMessages.slice(1) : transformedMessages;
971
+ // Native tool changes reference tools by name, so a redefined name cannot be expressed,
972
+ // and Anthropic rejects a tool list where every tool is deferred, so there must be an
973
+ // initial active tool to anchor the deferred ones. Otherwise the current tool list is sent.
974
+ const initialTools = initialSystemMessage?.toolsAdded ?? [];
975
+ const nativeToolChanges = compat.supportsMidConvoSystemMessages &&
976
+ compat.supportsMidConvoToolChanges &&
977
+ initialTools.length > 0 &&
978
+ !hasToolRedefinitions(context.messages);
979
+ const converted = convertMessages(conversationMessages, isOAuthToken, cacheControl, compat.allowEmptySignature, supportsMidConvoEffort(model) ? model.provider : undefined, nativeToolChanges);
932
980
  const activeEffort = options?.effort ?? "high";
933
981
  const params = {
934
982
  model: model.id,
@@ -938,6 +986,8 @@ function buildParams(model, context, isOAuthToken, options) {
938
986
  max_tokens: options?.maxTokens ?? model.maxTokens,
939
987
  stream: true,
940
988
  };
989
+ if (nativeToolChanges)
990
+ params.betas = getNativeToolChangeBetas(model, context, isOAuthToken, options);
941
991
  // For OAuth tokens, we MUST include Claude Code identity
942
992
  if (isOAuthToken) {
943
993
  params.system = [
@@ -947,20 +997,20 @@ function buildParams(model, context, isOAuthToken, options) {
947
997
  ...(cacheControl ? { cache_control: cacheControl } : {}),
948
998
  },
949
999
  ];
950
- if (context.systemPrompt) {
1000
+ if (initialSystemText) {
951
1001
  params.system.push({
952
1002
  type: "text",
953
- text: sanitizeSurrogates(context.systemPrompt),
1003
+ text: sanitizeSurrogates(initialSystemText),
954
1004
  ...(cacheControl ? { cache_control: cacheControl } : {}),
955
1005
  });
956
1006
  }
957
1007
  }
958
- else if (context.systemPrompt) {
1008
+ else if (initialSystemText) {
959
1009
  // Add cache control to system prompt for non-OAuth tokens
960
1010
  params.system = [
961
1011
  {
962
1012
  type: "text",
963
- text: sanitizeSurrogates(context.systemPrompt),
1013
+ text: sanitizeSurrogates(initialSystemText),
964
1014
  ...(cacheControl ? { cache_control: cacheControl } : {}),
965
1015
  },
966
1016
  ];
@@ -972,12 +1022,26 @@ function buildParams(model, context, isOAuthToken, options) {
972
1022
  compat.supportsTemperature) {
973
1023
  params.temperature = options.temperature;
974
1024
  }
975
- if (immediateTools.length > 0 || deferredTools.length > 0) {
1025
+ const toolCacheControl = compat.supportsCacheControlOnTools ? cacheControl : undefined;
1026
+ if (nativeToolChanges) {
1027
+ // Initial tools stay active with the cache breakpoint on the last one. Every later
1028
+ // declaration is deferred and only surfaced by its `tool_addition` block; removed
1029
+ // tools stay declared and are withdrawn by `tool_removal`. The request-level list
1030
+ // therefore only grows, keeping the cached prefix intact across tool changes.
1031
+ const initialNames = new Set(initialTools.map((tool) => tool.name));
1032
+ const laterTools = getDeclaredTools(context.messages).filter((tool) => !initialNames.has(tool.name));
976
1033
  params.tools = [
977
- ...convertTools(immediateTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, compat.supportsCacheControlOnTools ? cacheControl : undefined),
978
- ...convertTools(deferredTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, undefined, true),
1034
+ ...convertTools(initialTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl),
1035
+ DEFERRED_TOOL_PLACEHOLDER,
1036
+ ...convertTools(laterTools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools).map((tool) => ({ ...tool, defer_loading: true })),
979
1037
  ];
980
1038
  }
1039
+ else {
1040
+ const tools = getCurrentTools(context.messages);
1041
+ if (tools.length > 0) {
1042
+ params.tools = convertTools(tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsStrictTools, toolCacheControl);
1043
+ }
1044
+ }
981
1045
  // Managed effort models always use adaptive thinking. Their per-turn markers
982
1046
  // carry the actual effort; the top-level high value deliberately stays stable.
983
1047
  if (supportsMidConvoEffort(model)) {
@@ -1082,41 +1146,54 @@ function buildParams(model, context, isOAuthToken, options) {
1082
1146
  function normalizeToolCallId(id) {
1083
1147
  return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
1084
1148
  }
1085
- function convertToolResult(msg, isOAuthToken, deferredToolNames, loadedToolNames, normalizeToolName) {
1086
- const references = [];
1087
- for (const name of msg.addedToolNames ?? []) {
1088
- const normalizedName = normalizeToolName(name);
1089
- if (!deferredToolNames.has(normalizedName) || loadedToolNames.has(normalizedName))
1090
- continue;
1091
- loadedToolNames.add(normalizedName);
1092
- references.push({
1093
- type: "tool_reference",
1094
- tool_name: isOAuthToken ? toClaudeCodeName(name) : name,
1095
- });
1096
- }
1097
- const convertedContent = convertContentBlocks(msg.content);
1098
- // Anthropic rejects tool references mixed with ordinary tool-result content.
1149
+ function convertToolResult(msg) {
1099
1150
  return {
1100
- toolResult: {
1101
- type: "tool_result",
1102
- tool_use_id: msg.toolCallId,
1103
- content: references.length > 0 ? references : convertedContent,
1104
- is_error: msg.isError,
1105
- },
1106
- siblingContent: references.length === 0
1107
- ? []
1108
- : typeof convertedContent === "string"
1109
- ? [{ type: "text", text: convertedContent }]
1110
- : convertedContent,
1151
+ type: "tool_result",
1152
+ tool_use_id: msg.toolCallId,
1153
+ content: convertContentBlocks(msg.content),
1154
+ is_error: msg.isError,
1111
1155
  };
1112
1156
  }
1113
- function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowEmptySignature = false, deferredToolNames = new Set(), normalizeToolName = (name) => name, managedProvider) {
1157
+ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowEmptySignature = false, managedProvider, nativeToolChanges = false) {
1114
1158
  const params = [];
1115
1159
  const assistantLevels = new Map();
1116
- const loadedToolNames = new Set();
1160
+ // Later system messages are held back and emitted directly before the next assistant
1161
+ // message (or at the end of the transcript). Anthropic requires `tool_result` blocks to
1162
+ // immediately follow their `tool_use`, so a system message between them is rejected; this
1163
+ // also mirrors where the managed-effort system messages are inserted. As a result an
1164
+ // update placed before a user message in the transcript lands after it on the wire.
1165
+ const pendingSystemMessages = [];
1166
+ const flushPendingSystemMessages = () => {
1167
+ params.push(...pendingSystemMessages);
1168
+ pendingSystemMessages.length = 0;
1169
+ };
1117
1170
  for (let i = 0; i < transformedMessages.length; i++) {
1118
1171
  const msg = transformedMessages[i];
1119
- if (msg.role === "user") {
1172
+ if (msg.role === "system") {
1173
+ // Later system messages only reach this point when the model accepts them natively;
1174
+ // otherwise the transcript was collapsed into the leading message before conversion.
1175
+ const text = renderSystemMessageUpdate(msg);
1176
+ const blocks = [];
1177
+ if (text.length > 0)
1178
+ blocks.push({ type: "text", text: sanitizeSurrogates(text) });
1179
+ if (nativeToolChanges) {
1180
+ for (const tool of msg.toolsRemoved ?? []) {
1181
+ blocks.push({
1182
+ type: "tool_removal",
1183
+ tool: { type: "tool_reference", name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name },
1184
+ });
1185
+ }
1186
+ for (const tool of msg.toolsAdded ?? []) {
1187
+ blocks.push({
1188
+ type: "tool_addition",
1189
+ tool: { type: "tool_reference", name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name },
1190
+ });
1191
+ }
1192
+ }
1193
+ if (blocks.length > 0)
1194
+ pendingSystemMessages.push({ role: "system", content: blocks });
1195
+ }
1196
+ else if (msg.role === "user") {
1120
1197
  if (typeof msg.content === "string") {
1121
1198
  if (msg.content.trim().length > 0) {
1122
1199
  params.push({
@@ -1173,6 +1250,7 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE
1173
1250
  }
1174
1251
  }
1175
1252
  else if (msg.role === "assistant") {
1253
+ flushPendingSystemMessages();
1176
1254
  const blocks = [];
1177
1255
  for (const block of msg.content) {
1178
1256
  if (block.type === "text") {
@@ -1261,31 +1339,32 @@ function convertMessages(transformedMessages, isOAuthToken, cacheControl, allowE
1261
1339
  else if (msg.role === "toolResult") {
1262
1340
  // Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint.
1263
1341
  const toolResults = [];
1264
- const siblingContent = [];
1265
1342
  let j = i;
1266
1343
  while (j < transformedMessages.length && transformedMessages[j].role === "toolResult") {
1267
- const converted = convertToolResult(transformedMessages[j], isOAuthToken, deferredToolNames, loadedToolNames, normalizeToolName);
1268
- toolResults.push(converted.toolResult);
1269
- siblingContent.push(...converted.siblingContent);
1344
+ toolResults.push(convertToolResult(transformedMessages[j]));
1270
1345
  j++;
1271
1346
  }
1272
1347
  // Skip the messages we've already processed.
1273
1348
  i = j - 1;
1274
- // Displaced reference-bearing results must follow every tool_result block.
1275
1349
  params.push({
1276
1350
  role: "user",
1277
- content: [...toolResults, ...siblingContent],
1351
+ content: toolResults,
1278
1352
  });
1279
1353
  }
1280
1354
  }
1281
- // Add cache_control to the last user message to cache conversation history
1355
+ flushPendingSystemMessages();
1356
+ // Add cache_control to the last user or system message to cache conversation history
1282
1357
  if (cacheControl && params.length > 0) {
1283
1358
  const lastMessage = params[params.length - 1];
1284
- if (lastMessage.role === "user") {
1359
+ if (lastMessage.role === "user" || lastMessage.role === "system") {
1285
1360
  if (Array.isArray(lastMessage.content)) {
1286
1361
  const lastBlock = lastMessage.content[lastMessage.content.length - 1];
1287
1362
  if (lastBlock &&
1288
- (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "tool_result")) {
1363
+ (lastBlock.type === "text" ||
1364
+ lastBlock.type === "image" ||
1365
+ lastBlock.type === "tool_result" ||
1366
+ lastBlock.type === "tool_addition" ||
1367
+ lastBlock.type === "tool_removal")) {
1289
1368
  lastBlock.cache_control = cacheControl;
1290
1369
  }
1291
1370
  }
@@ -1318,7 +1397,7 @@ function insertThinkingLevelMessages(converted, activeEffort) {
1318
1397
  return messages;
1319
1398
  }
1320
1399
  function shouldUseFineGrainedToolStreamingBeta(model, context) {
1321
- return !!context.tools?.length && !getAnthropicCompat(model).supportsEagerToolInputStreaming;
1400
+ return getCurrentTools(context.messages).length > 0 && !getAnthropicCompat(model).supportsEagerToolInputStreaming;
1322
1401
  }
1323
1402
  function projectObjectUnionForAnthropic(schema) {
1324
1403
  if (schema.properties !== undefined || !Array.isArray(schema.anyOf) || schema.anyOf.length === 0)
@@ -1354,9 +1433,13 @@ function projectObjectUnionForAnthropic(schema) {
1354
1433
  required: (branches[0]?.required ?? []).filter((key) => branches.every((branch) => (branch.required ?? []).includes(key))),
1355
1434
  };
1356
1435
  }
1357
- function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl, deferLoading = false) {
1436
+ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supportsStrictTools, cacheControl) {
1358
1437
  if (!tools)
1359
1438
  return [];
1439
+ // OAuth canonicalization can make distinct transcript names collide. Keep the latest
1440
+ // declaration, as the pre-transcript tool serializer did, without sending duplicate names.
1441
+ if (isOAuthToken)
1442
+ tools = [...new Map(tools.map((tool) => [toClaudeCodeName(tool.name), tool])).values()];
1360
1443
  return tools.map((tool, index) => {
1361
1444
  const strict = resolveJsonSchemaStrictSampling(tool, supportsStrictTools);
1362
1445
  const parameters = getJsonSchemaToolParameters(tool, strict);
@@ -1381,7 +1464,6 @@ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, supp
1381
1464
  ...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),
1382
1465
  ...(strict === true ? { strict: true } : {}),
1383
1466
  input_schema: inputSchema,
1384
- ...(deferLoading ? { defer_loading: true } : {}),
1385
1467
  ...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),
1386
1468
  };
1387
1469
  });