@draht/ai 2026.4.26 → 2026.6.11

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 (198) hide show
  1. package/dist/api-registry.d.ts +1 -1
  2. package/dist/api-registry.d.ts.map +1 -1
  3. package/dist/api-registry.js.map +1 -1
  4. package/dist/bedrock-provider.d.ts +2 -2
  5. package/dist/bedrock-provider.d.ts.map +1 -1
  6. package/dist/bedrock-provider.js.map +1 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +14 -0
  9. package/dist/cli.js.map +1 -1
  10. package/dist/env-api-keys.d.ts +10 -1
  11. package/dist/env-api-keys.d.ts.map +1 -1
  12. package/dist/env-api-keys.js +110 -36
  13. package/dist/env-api-keys.js.map +1 -1
  14. package/dist/image-models.d.ts +10 -0
  15. package/dist/image-models.d.ts.map +1 -0
  16. package/dist/image-models.generated.d.ts +485 -0
  17. package/dist/image-models.generated.d.ts.map +1 -0
  18. package/dist/image-models.generated.js +487 -0
  19. package/dist/image-models.generated.js.map +1 -0
  20. package/dist/image-models.js +23 -0
  21. package/dist/image-models.js.map +1 -0
  22. package/dist/images-api-registry.d.ts +14 -0
  23. package/dist/images-api-registry.d.ts.map +1 -0
  24. package/dist/images-api-registry.js +22 -0
  25. package/dist/images-api-registry.js.map +1 -0
  26. package/dist/images.d.ts +4 -0
  27. package/dist/images.d.ts.map +1 -0
  28. package/dist/images.js +14 -0
  29. package/dist/images.js.map +1 -0
  30. package/dist/index.d.ts +31 -25
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +7 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/models.d.ts +5 -8
  35. package/dist/models.d.ts.map +1 -1
  36. package/dist/models.generated.d.ts +5197 -1721
  37. package/dist/models.generated.d.ts.map +1 -1
  38. package/dist/models.generated.js +7156 -5016
  39. package/dist/models.generated.js.map +1 -1
  40. package/dist/models.js +33 -6
  41. package/dist/models.js.map +1 -1
  42. package/dist/oauth.d.ts +1 -1
  43. package/dist/oauth.d.ts.map +1 -1
  44. package/dist/oauth.js.map +1 -1
  45. package/dist/providers/amazon-bedrock.d.ts +19 -1
  46. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  47. package/dist/providers/amazon-bedrock.js +278 -89
  48. package/dist/providers/amazon-bedrock.js.map +1 -1
  49. package/dist/providers/anthropic.d.ts +37 -6
  50. package/dist/providers/anthropic.d.ts.map +1 -1
  51. package/dist/providers/anthropic.js +300 -114
  52. package/dist/providers/anthropic.js.map +1 -1
  53. package/dist/providers/azure-openai-responses.d.ts +1 -1
  54. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  55. package/dist/providers/azure-openai-responses.js +68 -21
  56. package/dist/providers/azure-openai-responses.js.map +1 -1
  57. package/dist/providers/cloudflare.d.ts +13 -0
  58. package/dist/providers/cloudflare.d.ts.map +1 -0
  59. package/dist/providers/cloudflare.js +26 -0
  60. package/dist/providers/cloudflare.js.map +1 -0
  61. package/dist/providers/faux.d.ts +1 -1
  62. package/dist/providers/faux.d.ts.map +1 -1
  63. package/dist/providers/faux.js +1 -0
  64. package/dist/providers/faux.js.map +1 -1
  65. package/dist/providers/github-copilot-headers.d.ts +1 -1
  66. package/dist/providers/github-copilot-headers.d.ts.map +1 -1
  67. package/dist/providers/github-copilot-headers.js.map +1 -1
  68. package/dist/providers/google-shared.d.ts +8 -3
  69. package/dist/providers/google-shared.d.ts.map +1 -1
  70. package/dist/providers/google-shared.js +34 -17
  71. package/dist/providers/google-shared.js.map +1 -1
  72. package/dist/providers/google-vertex.d.ts +2 -2
  73. package/dist/providers/google-vertex.d.ts.map +1 -1
  74. package/dist/providers/google-vertex.js +45 -18
  75. package/dist/providers/google-vertex.js.map +1 -1
  76. package/dist/providers/google.d.ts +2 -2
  77. package/dist/providers/google.d.ts.map +1 -1
  78. package/dist/providers/google.js +9 -6
  79. package/dist/providers/google.js.map +1 -1
  80. package/dist/providers/images/openrouter.d.ts +3 -0
  81. package/dist/providers/images/openrouter.d.ts.map +1 -0
  82. package/dist/providers/images/openrouter.js +128 -0
  83. package/dist/providers/images/openrouter.js.map +1 -0
  84. package/dist/providers/images/register-builtins.d.ts +4 -0
  85. package/dist/providers/images/register-builtins.d.ts.map +1 -0
  86. package/dist/providers/images/register-builtins.js +34 -0
  87. package/dist/providers/images/register-builtins.js.map +1 -0
  88. package/dist/providers/mistral.d.ts +4 -1
  89. package/dist/providers/mistral.d.ts.map +1 -1
  90. package/dist/providers/mistral.js +43 -10
  91. package/dist/providers/mistral.js.map +1 -1
  92. package/dist/providers/openai-codex-responses.d.ts +22 -1
  93. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  94. package/dist/providers/openai-codex-responses.js +542 -111
  95. package/dist/providers/openai-codex-responses.js.map +1 -1
  96. package/dist/providers/openai-completions.d.ts +6 -2
  97. package/dist/providers/openai-completions.d.ts.map +1 -1
  98. package/dist/providers/openai-completions.js +446 -227
  99. package/dist/providers/openai-completions.js.map +1 -1
  100. package/dist/providers/openai-prompt-cache.d.ts +3 -0
  101. package/dist/providers/openai-prompt-cache.d.ts.map +1 -0
  102. package/dist/providers/openai-prompt-cache.js +10 -0
  103. package/dist/providers/openai-prompt-cache.js.map +1 -0
  104. package/dist/providers/openai-responses-shared.d.ts +3 -2
  105. package/dist/providers/openai-responses-shared.d.ts.map +1 -1
  106. package/dist/providers/openai-responses-shared.js +41 -15
  107. package/dist/providers/openai-responses-shared.js.map +1 -1
  108. package/dist/providers/openai-responses.d.ts +1 -1
  109. package/dist/providers/openai-responses.d.ts.map +1 -1
  110. package/dist/providers/openai-responses.js +85 -40
  111. package/dist/providers/openai-responses.js.map +1 -1
  112. package/dist/providers/register-builtins.d.ts +10 -13
  113. package/dist/providers/register-builtins.d.ts.map +1 -1
  114. package/dist/providers/register-builtins.js +13 -20
  115. package/dist/providers/register-builtins.js.map +1 -1
  116. package/dist/providers/simple-options.d.ts +2 -2
  117. package/dist/providers/simple-options.d.ts.map +1 -1
  118. package/dist/providers/simple-options.js +8 -2
  119. package/dist/providers/simple-options.js.map +1 -1
  120. package/dist/providers/transform-messages.d.ts +1 -1
  121. package/dist/providers/transform-messages.d.ts.map +1 -1
  122. package/dist/providers/transform-messages.js +63 -34
  123. package/dist/providers/transform-messages.js.map +1 -1
  124. package/dist/session-resources.d.ts +4 -0
  125. package/dist/session-resources.d.ts.map +1 -0
  126. package/dist/session-resources.js +22 -0
  127. package/dist/session-resources.js.map +1 -0
  128. package/dist/stream.d.ts +3 -3
  129. package/dist/stream.d.ts.map +1 -1
  130. package/dist/stream.js +14 -2
  131. package/dist/stream.js.map +1 -1
  132. package/dist/types.d.ts +177 -14
  133. package/dist/types.d.ts.map +1 -1
  134. package/dist/types.js.map +1 -1
  135. package/dist/utils/abort-signals.d.ts +6 -0
  136. package/dist/utils/abort-signals.d.ts.map +1 -0
  137. package/dist/utils/abort-signals.js +34 -0
  138. package/dist/utils/abort-signals.js.map +1 -0
  139. package/dist/utils/diagnostics.d.ts +19 -0
  140. package/dist/utils/diagnostics.d.ts.map +1 -0
  141. package/dist/utils/diagnostics.js +25 -0
  142. package/dist/utils/diagnostics.js.map +1 -0
  143. package/dist/utils/event-stream.d.ts +3 -3
  144. package/dist/utils/event-stream.d.ts.map +1 -1
  145. package/dist/utils/event-stream.js +2 -2
  146. package/dist/utils/event-stream.js.map +1 -1
  147. package/dist/utils/headers.d.ts +2 -0
  148. package/dist/utils/headers.d.ts.map +1 -0
  149. package/dist/utils/headers.js +8 -0
  150. package/dist/utils/headers.js.map +1 -0
  151. package/dist/utils/json-parse.d.ts +8 -1
  152. package/dist/utils/json-parse.d.ts.map +1 -1
  153. package/dist/utils/json-parse.js +89 -5
  154. package/dist/utils/json-parse.js.map +1 -1
  155. package/dist/utils/node-http-proxy.d.ts +10 -0
  156. package/dist/utils/node-http-proxy.d.ts.map +1 -0
  157. package/dist/utils/node-http-proxy.js +97 -0
  158. package/dist/utils/node-http-proxy.js.map +1 -0
  159. package/dist/utils/oauth/anthropic.d.ts +1 -1
  160. package/dist/utils/oauth/anthropic.d.ts.map +1 -1
  161. package/dist/utils/oauth/anthropic.js +1 -1
  162. package/dist/utils/oauth/anthropic.js.map +1 -1
  163. package/dist/utils/oauth/device-code.d.ts +21 -0
  164. package/dist/utils/oauth/device-code.d.ts.map +1 -0
  165. package/dist/utils/oauth/device-code.js +56 -0
  166. package/dist/utils/oauth/device-code.js.map +1 -0
  167. package/dist/utils/oauth/github-copilot.d.ts +3 -3
  168. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  169. package/dist/utils/oauth/github-copilot.js +58 -70
  170. package/dist/utils/oauth/github-copilot.js.map +1 -1
  171. package/dist/utils/oauth/index.d.ts +8 -11
  172. package/dist/utils/oauth/index.d.ts.map +1 -1
  173. package/dist/utils/oauth/index.js +2 -11
  174. package/dist/utils/oauth/index.js.map +1 -1
  175. package/dist/utils/oauth/openai-codex.d.ts +11 -2
  176. package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  177. package/dist/utils/oauth/openai-codex.js +187 -73
  178. package/dist/utils/oauth/openai-codex.js.map +1 -1
  179. package/dist/utils/oauth/types.d.ts +18 -1
  180. package/dist/utils/oauth/types.d.ts.map +1 -1
  181. package/dist/utils/oauth/types.js.map +1 -1
  182. package/dist/utils/overflow.d.ts +7 -3
  183. package/dist/utils/overflow.d.ts.map +1 -1
  184. package/dist/utils/overflow.js +25 -3
  185. package/dist/utils/overflow.js.map +1 -1
  186. package/dist/utils/typebox-helpers.d.ts +1 -1
  187. package/dist/utils/typebox-helpers.d.ts.map +1 -1
  188. package/dist/utils/typebox-helpers.js +1 -1
  189. package/dist/utils/typebox-helpers.js.map +1 -1
  190. package/dist/utils/validation.d.ts +1 -1
  191. package/dist/utils/validation.d.ts.map +1 -1
  192. package/dist/utils/validation.js +242 -41
  193. package/dist/utils/validation.js.map +1 -1
  194. package/package.json +15 -16
  195. package/dist/providers/google-gemini-cli.d.ts +0 -74
  196. package/dist/providers/google-gemini-cli.d.ts.map +0 -1
  197. package/dist/providers/google-gemini-cli.js +0 -776
  198. package/dist/providers/google-gemini-cli.js.map +0 -1
@@ -1,9 +1,10 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
- import { getEnvApiKey } from "../env-api-keys.js";
3
- import { calculateCost, supportsMax } from "../models.js";
2
+ import { calculateCost } from "../models.js";
4
3
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
5
- import { parseStreamingJson } from "../utils/json-parse.js";
4
+ import { headersToRecord } from "../utils/headers.js";
5
+ import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse.js";
6
6
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
7
+ import { resolveCloudflareBaseUrl } from "./cloudflare.js";
7
8
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
8
9
  import { adjustMaxTokensForThinking, buildBaseOptions } from "./simple-options.js";
9
10
  import { transformMessages } from "./transform-messages.js";
@@ -20,58 +21,40 @@ function resolveCacheRetention(cacheRetention) {
20
21
  }
21
22
  return "short";
22
23
  }
23
- function getCacheControl(baseUrl, cacheRetention) {
24
+ function getCacheControl(model, cacheRetention) {
24
25
  const retention = resolveCacheRetention(cacheRetention);
25
26
  if (retention === "none") {
26
27
  return { retention };
27
28
  }
28
- const ttl = retention === "long" && baseUrl.includes("api.anthropic.com") ? "1h" : undefined;
29
+ const ttl = retention === "long" && getAnthropicCompat(model).supportsLongCacheRetention ? "1h" : undefined;
29
30
  return {
30
31
  retention,
31
32
  cacheControl: { type: "ephemeral", ...(ttl && { ttl }) },
32
33
  };
33
34
  }
34
- // Stealth mode: Mimic Claude Code's identity, headers, and tool naming.
35
- // Source: https://cchistory.mariozechner.at/data/prompts-2.1.100.md
35
+ // Stealth mode: Mimic Claude Code's tool naming exactly
36
+ const claudeCodeVersion = "2.1.75";
37
+ // Claude Code 2.x tool names (canonical casing)
38
+ // Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md
36
39
  // To update: https://github.com/badlogic/cchistory
37
- const claudeCodeVersion = "2.1.100";
38
- // Build hash suffix that Claude Code appends to the billing header's cc_version.
39
- // Each real release has its own hash (e.g. 2.1.100 -> "1af", 2.1.90 -> "232").
40
- // This value only needs to parse; it is not cryptographically verified server-side.
41
- const claudeCodeBuild = "1af";
42
- // First line of Claude Code's system prompt. Anthropic uses this to classify
43
- // traffic as first-party Claude Code (vs third-party SDK usage) for billing.
44
- const claudeCodeBillingHeader = `x-anthropic-billing-header: cc_version=${claudeCodeVersion}.${claudeCodeBuild}; cc_entrypoint=sdk-cli; cch=00000;`;
45
- // Identity statement that immediately follows the billing header in real Claude Code.
46
- const claudeCodeIdentity = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
47
- // Claude Code 2.1.100 tool names (canonical casing), both active and deferred
48
- // (deferred tools are loaded on-demand via ToolSearch but still use these names).
49
40
  const claudeCodeTools = [
50
- "Agent",
51
- "AskUserQuestion",
52
- "Bash",
53
- "CronCreate",
54
- "CronDelete",
55
- "CronList",
41
+ "Read",
42
+ "Write",
56
43
  "Edit",
44
+ "Bash",
45
+ "Grep",
46
+ "Glob",
47
+ "AskUserQuestion",
57
48
  "EnterPlanMode",
58
- "EnterWorktree",
59
49
  "ExitPlanMode",
60
- "ExitWorktree",
61
- "Glob",
62
- "Grep",
63
- "Monitor",
50
+ "KillShell",
64
51
  "NotebookEdit",
65
- "Read",
66
- "RemoteTrigger",
67
52
  "Skill",
53
+ "Task",
68
54
  "TaskOutput",
69
- "TaskStop",
70
55
  "TodoWrite",
71
- "ToolSearch",
72
56
  "WebFetch",
73
57
  "WebSearch",
74
- "Write",
75
58
  ];
76
59
  const ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));
77
60
  // Convert tool name to CC canonical casing if it matches (case-insensitive)
@@ -121,6 +104,21 @@ function convertContentBlocks(content) {
121
104
  }
122
105
  return blocks;
123
106
  }
107
+ const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
108
+ const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
109
+ function getAnthropicCompat(model) {
110
+ // Auto-detect session affinity and cache control support from provider
111
+ const isFireworks = model.provider === "fireworks";
112
+ const isCloudflareAiGatewayAnthropic = model.provider === "cloudflare-ai-gateway" && model.baseUrl.includes("anthropic");
113
+ return {
114
+ supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? !isFireworks,
115
+ supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? !isFireworks,
116
+ sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? !!(isFireworks || isCloudflareAiGatewayAnthropic),
117
+ supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? !isFireworks,
118
+ supportsTemperature: model.compat?.supportsTemperature ?? true,
119
+ allowEmptySignature: model.compat?.allowEmptySignature ?? false,
120
+ };
121
+ }
124
122
  function mergeHeaders(...headerSources) {
125
123
  const merged = {};
126
124
  for (const headers of headerSources) {
@@ -130,6 +128,157 @@ function mergeHeaders(...headerSources) {
130
128
  }
131
129
  return merged;
132
130
  }
131
+ const ANTHROPIC_MESSAGE_EVENTS = new Set([
132
+ "message_start",
133
+ "message_delta",
134
+ "message_stop",
135
+ "content_block_start",
136
+ "content_block_delta",
137
+ "content_block_stop",
138
+ ]);
139
+ function flushSseEvent(state) {
140
+ if (!state.event && state.data.length === 0) {
141
+ return null;
142
+ }
143
+ const event = {
144
+ event: state.event,
145
+ data: state.data.join("\n"),
146
+ raw: [...state.raw],
147
+ };
148
+ state.event = null;
149
+ state.data = [];
150
+ state.raw = [];
151
+ return event;
152
+ }
153
+ function decodeSseLine(line, state) {
154
+ if (line === "") {
155
+ return flushSseEvent(state);
156
+ }
157
+ state.raw.push(line);
158
+ if (line.startsWith(":")) {
159
+ return null;
160
+ }
161
+ const delimiterIndex = line.indexOf(":");
162
+ const fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex);
163
+ let value = delimiterIndex === -1 ? "" : line.slice(delimiterIndex + 1);
164
+ if (value.startsWith(" ")) {
165
+ value = value.slice(1);
166
+ }
167
+ if (fieldName === "event") {
168
+ state.event = value;
169
+ }
170
+ else if (fieldName === "data") {
171
+ state.data.push(value);
172
+ }
173
+ return null;
174
+ }
175
+ function nextLineBreakIndex(text) {
176
+ const carriageReturnIndex = text.indexOf("\r");
177
+ const newlineIndex = text.indexOf("\n");
178
+ if (carriageReturnIndex === -1) {
179
+ return newlineIndex;
180
+ }
181
+ if (newlineIndex === -1) {
182
+ return carriageReturnIndex;
183
+ }
184
+ return Math.min(carriageReturnIndex, newlineIndex);
185
+ }
186
+ function consumeLine(text) {
187
+ const lineBreakIndex = nextLineBreakIndex(text);
188
+ if (lineBreakIndex === -1) {
189
+ return null;
190
+ }
191
+ let nextIndex = lineBreakIndex + 1;
192
+ if (text[lineBreakIndex] === "\r" && text[nextIndex] === "\n") {
193
+ nextIndex += 1;
194
+ }
195
+ return {
196
+ line: text.slice(0, lineBreakIndex),
197
+ rest: text.slice(nextIndex),
198
+ };
199
+ }
200
+ async function* iterateSseMessages(body, signal) {
201
+ const reader = body.getReader();
202
+ const decoder = new TextDecoder();
203
+ const state = { event: null, data: [], raw: [] };
204
+ let buffer = "";
205
+ try {
206
+ while (true) {
207
+ if (signal?.aborted) {
208
+ throw new Error("Request was aborted");
209
+ }
210
+ const { value, done } = await reader.read();
211
+ if (done) {
212
+ break;
213
+ }
214
+ buffer += decoder.decode(value, { stream: true });
215
+ let consumed = consumeLine(buffer);
216
+ while (consumed) {
217
+ buffer = consumed.rest;
218
+ const event = decodeSseLine(consumed.line, state);
219
+ if (event) {
220
+ yield event;
221
+ }
222
+ consumed = consumeLine(buffer);
223
+ }
224
+ }
225
+ buffer += decoder.decode();
226
+ let consumed = consumeLine(buffer);
227
+ while (consumed) {
228
+ buffer = consumed.rest;
229
+ const event = decodeSseLine(consumed.line, state);
230
+ if (event) {
231
+ yield event;
232
+ }
233
+ consumed = consumeLine(buffer);
234
+ }
235
+ if (buffer.length > 0) {
236
+ const event = decodeSseLine(buffer, state);
237
+ if (event) {
238
+ yield event;
239
+ }
240
+ }
241
+ const trailingEvent = flushSseEvent(state);
242
+ if (trailingEvent) {
243
+ yield trailingEvent;
244
+ }
245
+ }
246
+ finally {
247
+ reader.releaseLock();
248
+ }
249
+ }
250
+ async function* iterateAnthropicEvents(response, signal) {
251
+ if (!response.body) {
252
+ throw new Error("Attempted to iterate over an Anthropic response with no body");
253
+ }
254
+ let sawMessageStart = false;
255
+ let sawMessageEnd = false;
256
+ for await (const sse of iterateSseMessages(response.body, signal)) {
257
+ if (sse.event === "error") {
258
+ throw new Error(sse.data);
259
+ }
260
+ if (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? "")) {
261
+ continue;
262
+ }
263
+ try {
264
+ const event = parseJsonWithRepair(sse.data);
265
+ if (event.type === "message_start") {
266
+ sawMessageStart = true;
267
+ }
268
+ else if (event.type === "message_stop") {
269
+ sawMessageEnd = true;
270
+ }
271
+ yield event;
272
+ }
273
+ catch (error) {
274
+ const message = error instanceof Error ? error.message : String(error);
275
+ throw new Error(`Could not parse Anthropic SSE event ${sse.event}: ${message}; data=${sse.data}; raw=${sse.raw.join("\\n")}`);
276
+ }
277
+ }
278
+ if (sawMessageStart && !sawMessageEnd) {
279
+ throw new Error("Anthropic stream ended before message_stop");
280
+ }
281
+ }
133
282
  export const streamAnthropic = (model, context, options) => {
134
283
  const stream = new AssistantMessageEventStream();
135
284
  (async () => {
@@ -158,7 +307,10 @@ export const streamAnthropic = (model, context, options) => {
158
307
  isOAuth = false;
159
308
  }
160
309
  else {
161
- const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
310
+ const apiKey = options?.apiKey;
311
+ if (!apiKey) {
312
+ throw new Error(`No API key for provider: ${model.provider}`);
313
+ }
162
314
  let copilotDynamicHeaders;
163
315
  if (model.provider === "github-copilot") {
164
316
  const hasImages = hasCopilotVisionInput(context.messages);
@@ -167,7 +319,9 @@ export const streamAnthropic = (model, context, options) => {
167
319
  hasImages,
168
320
  });
169
321
  }
170
- const created = createClient(model, apiKey, options?.interleavedThinking ?? true, options?.headers, copilotDynamicHeaders);
322
+ const cacheRetention = options?.cacheRetention ?? resolveCacheRetention();
323
+ const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
324
+ const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId);
171
325
  client = created.client;
172
326
  isOAuth = created.isOAuthToken;
173
327
  }
@@ -176,10 +330,16 @@ export const streamAnthropic = (model, context, options) => {
176
330
  if (nextParams !== undefined) {
177
331
  params = nextParams;
178
332
  }
179
- const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal });
333
+ const requestOptions = {
334
+ ...(options?.signal ? { signal: options.signal } : {}),
335
+ ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
336
+ maxRetries: options?.maxRetries ?? 0,
337
+ };
338
+ const response = await client.messages.create({ ...params, stream: true }, requestOptions).asResponse();
339
+ await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
180
340
  stream.push({ type: "start", partial: output });
181
341
  const blocks = output.content;
182
- for await (const event of anthropicStream) {
342
+ for await (const event of iterateAnthropicEvents(response, options?.signal)) {
183
343
  if (event.type === "message_start") {
184
344
  output.responseId = event.message.id;
185
345
  // Capture initial token usage from message_start event
@@ -312,6 +472,8 @@ export const streamAnthropic = (model, context, options) => {
312
472
  }
313
473
  else if (block.type === "toolCall") {
314
474
  block.arguments = parseStreamingJson(block.partialJson);
475
+ // Finalize in-place and strip the scratch buffer so replay only
476
+ // carries parsed arguments.
315
477
  delete block.partialJson;
316
478
  stream.push({
317
479
  type: "toolcall_end",
@@ -356,8 +518,11 @@ export const streamAnthropic = (model, context, options) => {
356
518
  stream.end();
357
519
  }
358
520
  catch (error) {
359
- for (const block of output.content)
521
+ for (const block of output.content) {
360
522
  delete block.index;
523
+ // partialJson is only a streaming scratch buffer; never persist it.
524
+ delete block.partialJson;
525
+ }
361
526
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
362
527
  output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
363
528
  stream.push({ type: "error", reason: output.stopReason, error: output });
@@ -366,41 +531,28 @@ export const streamAnthropic = (model, context, options) => {
366
531
  })();
367
532
  return stream;
368
533
  };
369
- /**
370
- * Check if a model supports adaptive thinking (Opus 4.6 and Sonnet 4.6)
371
- */
372
- function supportsAdaptiveThinking(modelId) {
373
- // Opus 4.6 and Sonnet 4.6 model IDs (with or without date suffix)
374
- return (modelId.includes("opus-4-6") ||
375
- modelId.includes("opus-4.6") ||
376
- modelId.includes("sonnet-4-6") ||
377
- modelId.includes("sonnet-4.6"));
378
- }
379
534
  /**
380
535
  * Map ThinkingLevel to Anthropic effort levels for adaptive thinking.
381
- * Note: effort "max" is only valid on Opus models that pass `supportsMax`.
536
+ * Note: effort "max" is only valid on Opus 4.6, while Opus 4.7+ and Fable 5 support "xhigh".
382
537
  */
383
- function mapThinkingLevelToEffort(level, modelId) {
384
- const supportsMaxEffort = supportsMax({ id: modelId });
538
+ function mapThinkingLevelToEffort(model, level) {
539
+ const mapped = level ? model.thinkingLevelMap?.[level] : undefined;
540
+ if (typeof mapped === "string")
541
+ return mapped;
385
542
  switch (level) {
386
543
  case "minimal":
387
- return "low";
388
544
  case "low":
389
545
  return "low";
390
546
  case "medium":
391
547
  return "medium";
392
548
  case "high":
393
549
  return "high";
394
- case "xhigh":
395
- return supportsMaxEffort ? "max" : "high";
396
- case "max":
397
- return supportsMaxEffort ? "max" : "high";
398
550
  default:
399
551
  return "high";
400
552
  }
401
553
  }
402
554
  export const streamSimpleAnthropic = (model, context, options) => {
403
- const apiKey = options?.apiKey || getEnvApiKey(model.provider);
555
+ const apiKey = options?.apiKey;
404
556
  if (!apiKey) {
405
557
  throw new Error(`No API key for provider: ${model.provider}`);
406
558
  }
@@ -408,17 +560,19 @@ export const streamSimpleAnthropic = (model, context, options) => {
408
560
  if (!options?.reasoning) {
409
561
  return streamAnthropic(model, context, { ...base, thinkingEnabled: false });
410
562
  }
411
- // For Opus 4.6 and Sonnet 4.6: use adaptive thinking with effort level
412
- // For older models: use budget-based thinking
413
- if (supportsAdaptiveThinking(model.id)) {
414
- const effort = mapThinkingLevelToEffort(options.reasoning, model.id);
563
+ // For models with adaptive thinking: use an effort level.
564
+ // For older models: use budget-based thinking.
565
+ if (model.compat?.forceAdaptiveThinking === true) {
566
+ const effort = mapThinkingLevelToEffort(model, options.reasoning);
415
567
  return streamAnthropic(model, context, {
416
568
  ...base,
417
569
  thinkingEnabled: true,
418
570
  effort,
419
571
  });
420
572
  }
421
- const adjusted = adjustMaxTokensForThinking(base.maxTokens || 0, model.maxTokens, options.reasoning, options.thinkingBudgets);
573
+ // Undefined means the caller did not request an output cap; let the helper use the model cap.
574
+ // Do not coerce to 0 here, or the thinking budget would become the entire max_tokens value.
575
+ const adjusted = adjustMaxTokensForThinking(base.maxTokens, model.maxTokens, options.reasoning, options.thinkingBudgets);
422
576
  return streamAnthropic(model, context, {
423
577
  ...base,
424
578
  maxTokens: adjusted.maxTokens,
@@ -429,16 +583,35 @@ export const streamSimpleAnthropic = (model, context, options) => {
429
583
  function isOAuthToken(apiKey) {
430
584
  return apiKey.includes("sk-ant-oat");
431
585
  }
432
- function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynamicHeaders) {
433
- // Adaptive thinking models (Opus 4.6, Sonnet 4.6) have interleaved thinking built-in.
434
- // The beta header is deprecated on Opus 4.6 and redundant on Sonnet 4.6, so skip it.
435
- const needsInterleavedBeta = interleavedThinking && !supportsAdaptiveThinking(model.id);
436
- // Copilot: Bearer auth, selective betas (no fine-grained-tool-streaming)
586
+ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, dynamicHeaders, sessionId) {
587
+ // Adaptive thinking models have interleaved thinking built in, so skip the beta header.
588
+ const needsInterleavedBeta = interleavedThinking && model.compat?.forceAdaptiveThinking !== true;
589
+ const betaFeatures = [];
590
+ if (useFineGrainedToolStreamingBeta) {
591
+ betaFeatures.push(FINE_GRAINED_TOOL_STREAMING_BETA);
592
+ }
593
+ if (needsInterleavedBeta) {
594
+ betaFeatures.push(INTERLEAVED_THINKING_BETA);
595
+ }
596
+ if (model.provider === "cloudflare-ai-gateway") {
597
+ const client = new Anthropic({
598
+ apiKey: null,
599
+ authToken: null,
600
+ baseURL: resolveCloudflareBaseUrl(model),
601
+ dangerouslyAllowBrowser: true,
602
+ defaultHeaders: mergeHeaders({
603
+ accept: "application/json",
604
+ "anthropic-dangerous-direct-browser-access": "true",
605
+ "cf-aig-authorization": `Bearer ${apiKey}`,
606
+ "x-api-key": null,
607
+ Authorization: null,
608
+ ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
609
+ }, model.headers, optionsHeaders),
610
+ });
611
+ return { client, isOAuthToken: false };
612
+ }
613
+ // Copilot: Bearer auth, selective betas.
437
614
  if (model.provider === "github-copilot") {
438
- const betaFeatures = [];
439
- if (needsInterleavedBeta) {
440
- betaFeatures.push("interleaved-thinking-2025-05-14");
441
- }
442
615
  const client = new Anthropic({
443
616
  apiKey: null,
444
617
  authToken: apiKey,
@@ -452,10 +625,6 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
452
625
  });
453
626
  return { client, isOAuthToken: false };
454
627
  }
455
- const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"];
456
- if (needsInterleavedBeta) {
457
- betaFeatures.push("interleaved-thinking-2025-05-14");
458
- }
459
628
  // OAuth: Bearer auth, Claude Code identity headers
460
629
  if (isOAuthToken(apiKey)) {
461
630
  const client = new Anthropic({
@@ -466,7 +635,7 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
466
635
  defaultHeaders: mergeHeaders({
467
636
  accept: "application/json",
468
637
  "anthropic-dangerous-direct-browser-access": "true",
469
- "anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}`,
638
+ "anthropic-beta": ["claude-code-20250219", "oauth-2025-04-20", ...betaFeatures].join(","),
470
639
  "user-agent": `claude-cli/${claudeCodeVersion}`,
471
640
  "x-app": "cli",
472
641
  }, model.headers, optionsHeaders),
@@ -474,36 +643,35 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
474
643
  return { client, isOAuthToken: true };
475
644
  }
476
645
  // API key auth
646
+ const sessionAffinityHeaders = sessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { "x-session-affinity": sessionId } : {};
477
647
  const client = new Anthropic({
478
648
  apiKey,
649
+ authToken: null,
479
650
  baseURL: model.baseUrl,
480
651
  dangerouslyAllowBrowser: true,
481
652
  defaultHeaders: mergeHeaders({
482
653
  accept: "application/json",
483
654
  "anthropic-dangerous-direct-browser-access": "true",
484
- "anthropic-beta": betaFeatures.join(","),
485
- }, model.headers, optionsHeaders),
655
+ ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
656
+ }, sessionAffinityHeaders, model.headers, optionsHeaders),
486
657
  });
487
658
  return { client, isOAuthToken: false };
488
659
  }
489
660
  function buildParams(model, context, isOAuthToken, options) {
490
- const { cacheControl } = getCacheControl(model.baseUrl, options?.cacheRetention);
661
+ const { cacheControl } = getCacheControl(model, options?.cacheRetention);
662
+ const compat = getAnthropicCompat(model);
491
663
  const params = {
492
664
  model: model.id,
493
- messages: convertMessages(context.messages, model, isOAuthToken, cacheControl),
494
- max_tokens: options?.maxTokens || (model.maxTokens / 3) | 0,
665
+ messages: convertMessages(context.messages, model, isOAuthToken, cacheControl, compat.allowEmptySignature),
666
+ max_tokens: options?.maxTokens ?? model.maxTokens,
495
667
  stream: true,
496
668
  };
497
- // For OAuth tokens, we MUST prepend the Claude Code billing header and identity.
498
- // Anthropic inspects the first system block to classify traffic as first-party
499
- // Claude Code (billed against the Claude subscription) vs third-party SDK usage
500
- // (billed as extra usage per token). Without these exact strings, Pro/Max/Team
501
- // subscriptions are metered as third-party.
669
+ // For OAuth tokens, we MUST include Claude Code identity
502
670
  if (isOAuthToken) {
503
671
  params.system = [
504
672
  {
505
673
  type: "text",
506
- text: `${claudeCodeBillingHeader}\n${claudeCodeIdentity}`,
674
+ text: "You are Claude Code, Anthropic's official CLI for Claude.",
507
675
  ...(cacheControl ? { cache_control: cacheControl } : {}),
508
676
  },
509
677
  ];
@@ -525,22 +693,29 @@ function buildParams(model, context, isOAuthToken, options) {
525
693
  },
526
694
  ];
527
695
  }
528
- // Temperature is incompatible with extended thinking (adaptive or budget-based).
529
- if (options?.temperature !== undefined && !options?.thinkingEnabled) {
696
+ // Temperature is incompatible with extended thinking and unsupported on Claude Opus 4.7+.
697
+ if (options?.temperature !== undefined && !options?.thinkingEnabled && compat.supportsTemperature) {
530
698
  params.temperature = options.temperature;
531
699
  }
532
- if (context.tools) {
533
- params.tools = convertTools(context.tools, isOAuthToken);
700
+ if (context.tools && context.tools.length > 0) {
701
+ const compat = getAnthropicCompat(model);
702
+ params.tools = convertTools(context.tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsCacheControlOnTools ? cacheControl : undefined);
534
703
  }
535
- // Configure thinking mode: adaptive (Opus 4.6 and Sonnet 4.6),
536
- // budget-based (older models), or explicitly disabled.
704
+ // Configure thinking mode: adaptive, budget-based, or explicitly disabled.
537
705
  if (model.reasoning) {
538
706
  if (options?.thinkingEnabled) {
539
- if (supportsAdaptiveThinking(model.id)) {
540
- // Adaptive thinking: Claude decides when and how much to think
541
- params.thinking = { type: "adaptive" };
707
+ // Default to "summarized" so Opus 4.7 and Mythos Preview behave like
708
+ // older Claude 4 models (whose API default is also "summarized").
709
+ const display = options.thinkingDisplay ?? "summarized";
710
+ if (model.compat?.forceAdaptiveThinking === true) {
711
+ // Adaptive thinking: Claude decides when and how much to think.
712
+ params.thinking = { type: "adaptive", display };
542
713
  if (options.effort) {
543
- params.output_config = { effort: options.effort };
714
+ // The Anthropic SDK types can lag newly supported effort values such as "xhigh".
715
+ params.output_config =
716
+ options.effort === "xhigh"
717
+ ? { effort: options.effort }
718
+ : { effort: options.effort };
544
719
  }
545
720
  }
546
721
  else {
@@ -548,10 +723,11 @@ function buildParams(model, context, isOAuthToken, options) {
548
723
  params.thinking = {
549
724
  type: "enabled",
550
725
  budget_tokens: options.thinkingBudgetTokens || 1024,
726
+ display,
551
727
  };
552
728
  }
553
729
  }
554
- else if (options?.thinkingEnabled === false) {
730
+ else if (options?.thinkingEnabled === false && model.thinkingLevelMap?.off !== null) {
555
731
  params.thinking = { type: "disabled" };
556
732
  }
557
733
  }
@@ -575,7 +751,7 @@ function buildParams(model, context, isOAuthToken, options) {
575
751
  function normalizeToolCallId(id) {
576
752
  return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
577
753
  }
578
- function convertMessages(messages, model, isOAuthToken, cacheControl) {
754
+ function convertMessages(messages, model, isOAuthToken, cacheControl, allowEmptySignature = false) {
579
755
  const params = [];
580
756
  // Transform messages for cross-provider compatibility
581
757
  const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
@@ -609,8 +785,7 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
609
785
  };
610
786
  }
611
787
  });
612
- let filteredBlocks = !model?.input.includes("image") ? blocks.filter((b) => b.type !== "image") : blocks;
613
- filteredBlocks = filteredBlocks.filter((b) => {
788
+ const filteredBlocks = blocks.filter((b) => {
614
789
  if (b.type === "text") {
615
790
  return b.text.trim().length > 0;
616
791
  }
@@ -647,13 +822,19 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
647
822
  if (block.thinking.trim().length === 0)
648
823
  continue;
649
824
  // If thinking signature is missing/empty (e.g., from aborted stream),
650
- // convert to plain text block without <thinking> tags to avoid API rejection
651
- // and prevent Claude from mimicking the tags in responses
825
+ // convert to plain text for Anthropic. Some compatible providers emit
826
+ // and accept empty signatures, so let marked models preserve the block.
652
827
  if (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {
653
- blocks.push({
654
- type: "text",
655
- text: sanitizeSurrogates(block.thinking),
656
- });
828
+ blocks.push(allowEmptySignature
829
+ ? {
830
+ type: "thinking",
831
+ thinking: sanitizeSurrogates(block.thinking),
832
+ signature: "",
833
+ }
834
+ : {
835
+ type: "text",
836
+ text: sanitizeSurrogates(block.thinking),
837
+ });
657
838
  }
658
839
  else {
659
840
  blocks.push({
@@ -734,19 +915,24 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
734
915
  }
735
916
  return params;
736
917
  }
737
- function convertTools(tools, isOAuthToken) {
918
+ function shouldUseFineGrainedToolStreamingBeta(model, context) {
919
+ return !!context.tools?.length && !getAnthropicCompat(model).supportsEagerToolInputStreaming;
920
+ }
921
+ function convertTools(tools, isOAuthToken, supportsEagerToolInputStreaming, cacheControl) {
738
922
  if (!tools)
739
923
  return [];
740
- return tools.map((tool) => {
741
- const jsonSchema = tool.parameters; // TypeBox already generates JSON Schema
924
+ return tools.map((tool, index) => {
925
+ const schema = tool.parameters;
742
926
  return {
743
927
  name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
744
928
  description: tool.description,
929
+ ...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),
745
930
  input_schema: {
746
931
  type: "object",
747
- properties: jsonSchema.properties || {},
748
- required: jsonSchema.required || [],
932
+ properties: schema.properties ?? {},
933
+ required: schema.required ?? [],
749
934
  },
935
+ ...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),
750
936
  };
751
937
  });
752
938
  }