phi-code-ai 0.56.3 → 0.74.0

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 (187) hide show
  1. package/README.md +258 -73
  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.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +1 -1
  7. package/dist/cli.js.map +1 -1
  8. package/dist/env-api-keys.d.ts +9 -0
  9. package/dist/env-api-keys.d.ts.map +1 -1
  10. package/dist/env-api-keys.js +96 -30
  11. package/dist/env-api-keys.js.map +1 -1
  12. package/dist/image-models.d.ts +10 -0
  13. package/dist/image-models.d.ts.map +1 -0
  14. package/dist/image-models.generated.d.ts +305 -0
  15. package/dist/image-models.generated.d.ts.map +1 -0
  16. package/dist/image-models.generated.js +307 -0
  17. package/dist/image-models.generated.js.map +1 -0
  18. package/dist/image-models.js +23 -0
  19. package/dist/image-models.js.map +1 -0
  20. package/dist/images-api-registry.d.ts +14 -0
  21. package/dist/images-api-registry.d.ts.map +1 -0
  22. package/dist/images-api-registry.js +22 -0
  23. package/dist/images-api-registry.js.map +1 -0
  24. package/dist/images.d.ts +4 -0
  25. package/dist/images.d.ts.map +1 -0
  26. package/dist/images.js +14 -0
  27. package/dist/images.js.map +1 -0
  28. package/dist/index.d.ts +20 -11
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +8 -9
  31. package/dist/index.js.map +1 -1
  32. package/dist/models.d.ts +3 -9
  33. package/dist/models.d.ts.map +1 -1
  34. package/dist/models.generated.d.ts +6525 -2231
  35. package/dist/models.generated.d.ts.map +1 -1
  36. package/dist/models.generated.js +8992 -5524
  37. package/dist/models.generated.js.map +1 -1
  38. package/dist/models.js +28 -12
  39. package/dist/models.js.map +1 -1
  40. package/dist/oauth.d.ts.map +1 -1
  41. package/dist/providers/amazon-bedrock.d.ts +23 -0
  42. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  43. package/dist/providers/amazon-bedrock.js +206 -44
  44. package/dist/providers/amazon-bedrock.js.map +1 -1
  45. package/dist/providers/anthropic.d.ts +23 -2
  46. package/dist/providers/anthropic.d.ts.map +1 -1
  47. package/dist/providers/anthropic.js +294 -63
  48. package/dist/providers/anthropic.js.map +1 -1
  49. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  50. package/dist/providers/azure-openai-responses.js +47 -23
  51. package/dist/providers/azure-openai-responses.js.map +1 -1
  52. package/dist/providers/cloudflare.d.ts +13 -0
  53. package/dist/providers/cloudflare.d.ts.map +1 -0
  54. package/dist/providers/cloudflare.js +26 -0
  55. package/dist/providers/cloudflare.js.map +1 -0
  56. package/dist/providers/faux.d.ts +56 -0
  57. package/dist/providers/faux.d.ts.map +1 -0
  58. package/dist/providers/faux.js +368 -0
  59. package/dist/providers/faux.js.map +1 -0
  60. package/dist/providers/github-copilot-headers.d.ts.map +1 -1
  61. package/dist/providers/github-copilot-headers.js.map +1 -1
  62. package/dist/providers/google-shared.d.ts +7 -2
  63. package/dist/providers/google-shared.d.ts.map +1 -1
  64. package/dist/providers/google-shared.js +53 -24
  65. package/dist/providers/google-shared.js.map +1 -1
  66. package/dist/providers/google-vertex.d.ts +1 -1
  67. package/dist/providers/google-vertex.d.ts.map +1 -1
  68. package/dist/providers/google-vertex.js +87 -16
  69. package/dist/providers/google-vertex.js.map +1 -1
  70. package/dist/providers/google.d.ts +1 -1
  71. package/dist/providers/google.d.ts.map +1 -1
  72. package/dist/providers/google.js +57 -9
  73. package/dist/providers/google.js.map +1 -1
  74. package/dist/providers/images/openrouter.d.ts +3 -0
  75. package/dist/providers/images/openrouter.d.ts.map +1 -0
  76. package/dist/providers/images/openrouter.js +129 -0
  77. package/dist/providers/images/openrouter.js.map +1 -0
  78. package/dist/providers/images/register-builtins.d.ts +4 -0
  79. package/dist/providers/images/register-builtins.d.ts.map +1 -0
  80. package/dist/providers/images/register-builtins.js +34 -0
  81. package/dist/providers/images/register-builtins.js.map +1 -0
  82. package/dist/providers/mistral.d.ts +3 -0
  83. package/dist/providers/mistral.d.ts.map +1 -1
  84. package/dist/providers/mistral.js +49 -9
  85. package/dist/providers/mistral.js.map +1 -1
  86. package/dist/providers/openai-codex-responses.d.ts +21 -0
  87. package/dist/providers/openai-codex-responses.d.ts.map +1 -1
  88. package/dist/providers/openai-codex-responses.js +443 -86
  89. package/dist/providers/openai-codex-responses.js.map +1 -1
  90. package/dist/providers/openai-completions.d.ts +5 -1
  91. package/dist/providers/openai-completions.d.ts.map +1 -1
  92. package/dist/providers/openai-completions.js +460 -225
  93. package/dist/providers/openai-completions.js.map +1 -1
  94. package/dist/providers/openai-responses-shared.d.ts +1 -0
  95. package/dist/providers/openai-responses-shared.d.ts.map +1 -1
  96. package/dist/providers/openai-responses-shared.js +95 -45
  97. package/dist/providers/openai-responses-shared.js.map +1 -1
  98. package/dist/providers/openai-responses.d.ts.map +1 -1
  99. package/dist/providers/openai-responses.js +66 -44
  100. package/dist/providers/openai-responses.js.map +1 -1
  101. package/dist/providers/register-builtins.d.ts +27 -2
  102. package/dist/providers/register-builtins.d.ts.map +1 -1
  103. package/dist/providers/register-builtins.js +157 -52
  104. package/dist/providers/register-builtins.js.map +1 -1
  105. package/dist/providers/simple-options.d.ts.map +1 -1
  106. package/dist/providers/simple-options.js +5 -1
  107. package/dist/providers/simple-options.js.map +1 -1
  108. package/dist/providers/transform-messages.d.ts.map +1 -1
  109. package/dist/providers/transform-messages.js +63 -34
  110. package/dist/providers/transform-messages.js.map +1 -1
  111. package/dist/session-resources.d.ts +4 -0
  112. package/dist/session-resources.d.ts.map +1 -0
  113. package/dist/session-resources.js +22 -0
  114. package/dist/session-resources.js.map +1 -0
  115. package/dist/stream.d.ts.map +1 -1
  116. package/dist/stream.js.map +1 -1
  117. package/dist/types.d.ts +219 -15
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/types.js.map +1 -1
  120. package/dist/utils/diagnostics.d.ts +19 -0
  121. package/dist/utils/diagnostics.d.ts.map +1 -0
  122. package/dist/utils/diagnostics.js +25 -0
  123. package/dist/utils/diagnostics.js.map +1 -0
  124. package/dist/utils/event-stream.d.ts.map +1 -1
  125. package/dist/utils/event-stream.js +7 -3
  126. package/dist/utils/event-stream.js.map +1 -1
  127. package/dist/utils/hash.d.ts.map +1 -1
  128. package/dist/utils/hash.js.map +1 -1
  129. package/dist/utils/headers.d.ts +2 -0
  130. package/dist/utils/headers.d.ts.map +1 -0
  131. package/dist/utils/headers.js +8 -0
  132. package/dist/utils/headers.js.map +1 -0
  133. package/dist/utils/json-parse.d.ts +8 -1
  134. package/dist/utils/json-parse.d.ts.map +1 -1
  135. package/dist/utils/json-parse.js +89 -5
  136. package/dist/utils/json-parse.js.map +1 -1
  137. package/dist/utils/oauth/anthropic.d.ts +14 -6
  138. package/dist/utils/oauth/anthropic.d.ts.map +1 -1
  139. package/dist/utils/oauth/anthropic.js +288 -57
  140. package/dist/utils/oauth/anthropic.js.map +1 -1
  141. package/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  142. package/dist/utils/oauth/github-copilot.js +23 -12
  143. package/dist/utils/oauth/github-copilot.js.map +1 -1
  144. package/dist/utils/oauth/index.d.ts +0 -4
  145. package/dist/utils/oauth/index.d.ts.map +1 -1
  146. package/dist/utils/oauth/index.js +0 -10
  147. package/dist/utils/oauth/index.js.map +1 -1
  148. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  149. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  150. package/dist/utils/oauth/oauth-page.js +105 -0
  151. package/dist/utils/oauth/oauth-page.js.map +1 -0
  152. package/dist/utils/oauth/openai-codex.d.ts.map +1 -1
  153. package/dist/utils/oauth/openai-codex.js +51 -46
  154. package/dist/utils/oauth/openai-codex.js.map +1 -1
  155. package/dist/utils/oauth/pkce.d.ts.map +1 -1
  156. package/dist/utils/oauth/pkce.js.map +1 -1
  157. package/dist/utils/oauth/types.d.ts +10 -0
  158. package/dist/utils/oauth/types.d.ts.map +1 -1
  159. package/dist/utils/oauth/types.js.map +1 -1
  160. package/dist/utils/overflow.d.ts +7 -3
  161. package/dist/utils/overflow.d.ts.map +1 -1
  162. package/dist/utils/overflow.js +46 -13
  163. package/dist/utils/overflow.js.map +1 -1
  164. package/dist/utils/sanitize-unicode.d.ts.map +1 -1
  165. package/dist/utils/sanitize-unicode.js.map +1 -1
  166. package/dist/utils/typebox-helpers.d.ts +1 -1
  167. package/dist/utils/typebox-helpers.d.ts.map +1 -1
  168. package/dist/utils/typebox-helpers.js +1 -1
  169. package/dist/utils/typebox-helpers.js.map +1 -1
  170. package/dist/utils/validation.d.ts.map +1 -1
  171. package/dist/utils/validation.js +247 -38
  172. package/dist/utils/validation.js.map +1 -1
  173. package/package.json +44 -14
  174. package/bedrock-provider.d.ts +0 -1
  175. package/bedrock-provider.js +0 -1
  176. package/dist/providers/google-gemini-cli.d.ts +0 -74
  177. package/dist/providers/google-gemini-cli.d.ts.map +0 -1
  178. package/dist/providers/google-gemini-cli.js +0 -754
  179. package/dist/providers/google-gemini-cli.js.map +0 -1
  180. package/dist/utils/oauth/google-antigravity.d.ts +0 -26
  181. package/dist/utils/oauth/google-antigravity.d.ts.map +0 -1
  182. package/dist/utils/oauth/google-antigravity.js +0 -373
  183. package/dist/utils/oauth/google-antigravity.js.map +0 -1
  184. package/dist/utils/oauth/google-gemini-cli.d.ts +0 -26
  185. package/dist/utils/oauth/google-gemini-cli.d.ts.map +0 -1
  186. package/dist/utils/oauth/google-gemini-cli.js +0 -478
  187. package/dist/utils/oauth/google-gemini-cli.js.map +0 -1
@@ -2,8 +2,10 @@ import Anthropic from "@anthropic-ai/sdk";
2
2
  import { getEnvApiKey } from "../env-api-keys.js";
3
3
  import { calculateCost } from "../models.js";
4
4
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
5
- import { parseStreamingJson } from "../utils/json-parse.js";
5
+ import { headersToRecord } from "../utils/headers.js";
6
+ import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse.js";
6
7
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
8
+ import { resolveCloudflareBaseUrl } from "./cloudflare.js";
7
9
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
8
10
  import { adjustMaxTokensForThinking, buildBaseOptions } from "./simple-options.js";
9
11
  import { transformMessages } from "./transform-messages.js";
@@ -20,19 +22,19 @@ function resolveCacheRetention(cacheRetention) {
20
22
  }
21
23
  return "short";
22
24
  }
23
- function getCacheControl(baseUrl, cacheRetention) {
25
+ function getCacheControl(model, cacheRetention) {
24
26
  const retention = resolveCacheRetention(cacheRetention);
25
27
  if (retention === "none") {
26
28
  return { retention };
27
29
  }
28
- const ttl = retention === "long" && baseUrl.includes("api.anthropic.com") ? "1h" : undefined;
30
+ const ttl = retention === "long" && getAnthropicCompat(model).supportsLongCacheRetention ? "1h" : undefined;
29
31
  return {
30
32
  retention,
31
33
  cacheControl: { type: "ephemeral", ...(ttl && { ttl }) },
32
34
  };
33
35
  }
34
36
  // Stealth mode: Mimic Claude Code's tool naming exactly
35
- const claudeCodeVersion = "2.1.62";
37
+ const claudeCodeVersion = "2.1.75";
36
38
  // Claude Code 2.x tool names (canonical casing)
37
39
  // Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md
38
40
  // To update: https://github.com/badlogic/cchistory
@@ -103,6 +105,19 @@ function convertContentBlocks(content) {
103
105
  }
104
106
  return blocks;
105
107
  }
108
+ const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
109
+ const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
110
+ function getAnthropicCompat(model) {
111
+ // Auto-detect session affinity and cache control support from provider
112
+ const isFireworks = model.provider === "fireworks";
113
+ const isCloudflareAiGatewayAnthropic = model.provider === "cloudflare-ai-gateway" && model.baseUrl.includes("anthropic");
114
+ return {
115
+ supportsEagerToolInputStreaming: model.compat?.supportsEagerToolInputStreaming ?? !isFireworks,
116
+ supportsLongCacheRetention: model.compat?.supportsLongCacheRetention ?? !isFireworks,
117
+ sendSessionAffinityHeaders: model.compat?.sendSessionAffinityHeaders ?? !!(isFireworks || isCloudflareAiGatewayAnthropic),
118
+ supportsCacheControlOnTools: model.compat?.supportsCacheControlOnTools ?? !isFireworks,
119
+ };
120
+ }
106
121
  function mergeHeaders(...headerSources) {
107
122
  const merged = {};
108
123
  for (const headers of headerSources) {
@@ -112,6 +127,157 @@ function mergeHeaders(...headerSources) {
112
127
  }
113
128
  return merged;
114
129
  }
130
+ const ANTHROPIC_MESSAGE_EVENTS = new Set([
131
+ "message_start",
132
+ "message_delta",
133
+ "message_stop",
134
+ "content_block_start",
135
+ "content_block_delta",
136
+ "content_block_stop",
137
+ ]);
138
+ function flushSseEvent(state) {
139
+ if (!state.event && state.data.length === 0) {
140
+ return null;
141
+ }
142
+ const event = {
143
+ event: state.event,
144
+ data: state.data.join("\n"),
145
+ raw: [...state.raw],
146
+ };
147
+ state.event = null;
148
+ state.data = [];
149
+ state.raw = [];
150
+ return event;
151
+ }
152
+ function decodeSseLine(line, state) {
153
+ if (line === "") {
154
+ return flushSseEvent(state);
155
+ }
156
+ state.raw.push(line);
157
+ if (line.startsWith(":")) {
158
+ return null;
159
+ }
160
+ const delimiterIndex = line.indexOf(":");
161
+ const fieldName = delimiterIndex === -1 ? line : line.slice(0, delimiterIndex);
162
+ let value = delimiterIndex === -1 ? "" : line.slice(delimiterIndex + 1);
163
+ if (value.startsWith(" ")) {
164
+ value = value.slice(1);
165
+ }
166
+ if (fieldName === "event") {
167
+ state.event = value;
168
+ }
169
+ else if (fieldName === "data") {
170
+ state.data.push(value);
171
+ }
172
+ return null;
173
+ }
174
+ function nextLineBreakIndex(text) {
175
+ const carriageReturnIndex = text.indexOf("\r");
176
+ const newlineIndex = text.indexOf("\n");
177
+ if (carriageReturnIndex === -1) {
178
+ return newlineIndex;
179
+ }
180
+ if (newlineIndex === -1) {
181
+ return carriageReturnIndex;
182
+ }
183
+ return Math.min(carriageReturnIndex, newlineIndex);
184
+ }
185
+ function consumeLine(text) {
186
+ const lineBreakIndex = nextLineBreakIndex(text);
187
+ if (lineBreakIndex === -1) {
188
+ return null;
189
+ }
190
+ let nextIndex = lineBreakIndex + 1;
191
+ if (text[lineBreakIndex] === "\r" && text[nextIndex] === "\n") {
192
+ nextIndex += 1;
193
+ }
194
+ return {
195
+ line: text.slice(0, lineBreakIndex),
196
+ rest: text.slice(nextIndex),
197
+ };
198
+ }
199
+ async function* iterateSseMessages(body, signal) {
200
+ const reader = body.getReader();
201
+ const decoder = new TextDecoder();
202
+ const state = { event: null, data: [], raw: [] };
203
+ let buffer = "";
204
+ try {
205
+ while (true) {
206
+ if (signal?.aborted) {
207
+ throw new Error("Request was aborted");
208
+ }
209
+ const { value, done } = await reader.read();
210
+ if (done) {
211
+ break;
212
+ }
213
+ buffer += decoder.decode(value, { stream: true });
214
+ let consumed = consumeLine(buffer);
215
+ while (consumed) {
216
+ buffer = consumed.rest;
217
+ const event = decodeSseLine(consumed.line, state);
218
+ if (event) {
219
+ yield event;
220
+ }
221
+ consumed = consumeLine(buffer);
222
+ }
223
+ }
224
+ buffer += decoder.decode();
225
+ let consumed = consumeLine(buffer);
226
+ while (consumed) {
227
+ buffer = consumed.rest;
228
+ const event = decodeSseLine(consumed.line, state);
229
+ if (event) {
230
+ yield event;
231
+ }
232
+ consumed = consumeLine(buffer);
233
+ }
234
+ if (buffer.length > 0) {
235
+ const event = decodeSseLine(buffer, state);
236
+ if (event) {
237
+ yield event;
238
+ }
239
+ }
240
+ const trailingEvent = flushSseEvent(state);
241
+ if (trailingEvent) {
242
+ yield trailingEvent;
243
+ }
244
+ }
245
+ finally {
246
+ reader.releaseLock();
247
+ }
248
+ }
249
+ async function* iterateAnthropicEvents(response, signal) {
250
+ if (!response.body) {
251
+ throw new Error("Attempted to iterate over an Anthropic response with no body");
252
+ }
253
+ let sawMessageStart = false;
254
+ let sawMessageEnd = false;
255
+ for await (const sse of iterateSseMessages(response.body, signal)) {
256
+ if (sse.event === "error") {
257
+ throw new Error(sse.data);
258
+ }
259
+ if (!ANTHROPIC_MESSAGE_EVENTS.has(sse.event ?? "")) {
260
+ continue;
261
+ }
262
+ try {
263
+ const event = parseJsonWithRepair(sse.data);
264
+ if (event.type === "message_start") {
265
+ sawMessageStart = true;
266
+ }
267
+ else if (event.type === "message_stop") {
268
+ sawMessageEnd = true;
269
+ }
270
+ yield event;
271
+ }
272
+ catch (error) {
273
+ const message = error instanceof Error ? error.message : String(error);
274
+ throw new Error(`Could not parse Anthropic SSE event ${sse.event}: ${message}; data=${sse.data}; raw=${sse.raw.join("\\n")}`);
275
+ }
276
+ }
277
+ if (sawMessageStart && !sawMessageEnd) {
278
+ throw new Error("Anthropic stream ended before message_stop");
279
+ }
280
+ }
115
281
  export const streamAnthropic = (model, context, options) => {
116
282
  const stream = new AssistantMessageEventStream();
117
283
  (async () => {
@@ -133,23 +299,45 @@ export const streamAnthropic = (model, context, options) => {
133
299
  timestamp: Date.now(),
134
300
  };
135
301
  try {
136
- const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
137
- let copilotDynamicHeaders;
138
- if (model.provider === "github-copilot") {
139
- const hasImages = hasCopilotVisionInput(context.messages);
140
- copilotDynamicHeaders = buildCopilotDynamicHeaders({
141
- messages: context.messages,
142
- hasImages,
143
- });
302
+ let client;
303
+ let isOAuth;
304
+ if (options?.client) {
305
+ client = options.client;
306
+ isOAuth = false;
307
+ }
308
+ else {
309
+ const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
310
+ let copilotDynamicHeaders;
311
+ if (model.provider === "github-copilot") {
312
+ const hasImages = hasCopilotVisionInput(context.messages);
313
+ copilotDynamicHeaders = buildCopilotDynamicHeaders({
314
+ messages: context.messages,
315
+ hasImages,
316
+ });
317
+ }
318
+ const cacheRetention = options?.cacheRetention ?? resolveCacheRetention();
319
+ const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
320
+ const created = createClient(model, apiKey, options?.interleavedThinking ?? true, shouldUseFineGrainedToolStreamingBeta(model, context), options?.headers, copilotDynamicHeaders, cacheSessionId);
321
+ client = created.client;
322
+ isOAuth = created.isOAuthToken;
323
+ }
324
+ let params = buildParams(model, context, isOAuth, options);
325
+ const nextParams = await options?.onPayload?.(params, model);
326
+ if (nextParams !== undefined) {
327
+ params = nextParams;
144
328
  }
145
- const { client, isOAuthToken } = createClient(model, apiKey, options?.interleavedThinking ?? true, options?.headers, copilotDynamicHeaders);
146
- const params = buildParams(model, context, isOAuthToken, options);
147
- options?.onPayload?.(params);
148
- const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal });
329
+ const requestOptions = {
330
+ ...(options?.signal ? { signal: options.signal } : {}),
331
+ ...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
332
+ ...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
333
+ };
334
+ const response = await client.messages.create({ ...params, stream: true }, requestOptions).asResponse();
335
+ await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
149
336
  stream.push({ type: "start", partial: output });
150
337
  const blocks = output.content;
151
- for await (const event of anthropicStream) {
338
+ for await (const event of iterateAnthropicEvents(response, options?.signal)) {
152
339
  if (event.type === "message_start") {
340
+ output.responseId = event.message.id;
153
341
  // Capture initial token usage from message_start event
154
342
  // This ensures we have input token counts even if the stream is aborted early
155
343
  output.usage.input = event.message.usage.input_tokens || 0;
@@ -196,7 +384,7 @@ export const streamAnthropic = (model, context, options) => {
196
384
  const block = {
197
385
  type: "toolCall",
198
386
  id: event.content_block.id,
199
- name: isOAuthToken
387
+ name: isOAuth
200
388
  ? fromClaudeCodeName(event.content_block.name, context.tools)
201
389
  : event.content_block.name,
202
390
  arguments: event.content_block.input ?? {},
@@ -280,6 +468,8 @@ export const streamAnthropic = (model, context, options) => {
280
468
  }
281
469
  else if (block.type === "toolCall") {
282
470
  block.arguments = parseStreamingJson(block.partialJson);
471
+ // Finalize in-place and strip the scratch buffer so replay only
472
+ // carries parsed arguments.
283
473
  delete block.partialJson;
284
474
  stream.push({
285
475
  type: "toolcall_end",
@@ -324,8 +514,11 @@ export const streamAnthropic = (model, context, options) => {
324
514
  stream.end();
325
515
  }
326
516
  catch (error) {
327
- for (const block of output.content)
517
+ for (const block of output.content) {
328
518
  delete block.index;
519
+ // partialJson is only a streaming scratch buffer; never persist it.
520
+ delete block.partialJson;
521
+ }
329
522
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
330
523
  output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
331
524
  stream.push({ type: "error", reason: output.stopReason, error: output });
@@ -335,31 +528,33 @@ export const streamAnthropic = (model, context, options) => {
335
528
  return stream;
336
529
  };
337
530
  /**
338
- * Check if a model supports adaptive thinking (Opus 4.6 and Sonnet 4.6)
531
+ * Check if a model supports adaptive thinking (Opus 4.6+, Sonnet 4.6)
339
532
  */
340
533
  function supportsAdaptiveThinking(modelId) {
341
- // Opus 4.6 and Sonnet 4.6 model IDs (with or without date suffix)
534
+ // Adaptive-thinking model IDs (with or without date suffix)
342
535
  return (modelId.includes("opus-4-6") ||
343
536
  modelId.includes("opus-4.6") ||
537
+ modelId.includes("opus-4-7") ||
538
+ modelId.includes("opus-4.7") ||
344
539
  modelId.includes("sonnet-4-6") ||
345
540
  modelId.includes("sonnet-4.6"));
346
541
  }
347
542
  /**
348
543
  * Map ThinkingLevel to Anthropic effort levels for adaptive thinking.
349
- * Note: effort "max" is only valid on Opus 4.6.
544
+ * Note: effort "max" is only valid on Opus 4.6, while Opus 4.7 supports "xhigh".
350
545
  */
351
- function mapThinkingLevelToEffort(level, modelId) {
546
+ function mapThinkingLevelToEffort(model, level) {
547
+ const mapped = level ? model.thinkingLevelMap?.[level] : undefined;
548
+ if (typeof mapped === "string")
549
+ return mapped;
352
550
  switch (level) {
353
551
  case "minimal":
354
- return "low";
355
552
  case "low":
356
553
  return "low";
357
554
  case "medium":
358
555
  return "medium";
359
556
  case "high":
360
557
  return "high";
361
- case "xhigh":
362
- return modelId.includes("opus-4-6") || modelId.includes("opus-4.6") ? "max" : "high";
363
558
  default:
364
559
  return "high";
365
560
  }
@@ -376,7 +571,7 @@ export const streamSimpleAnthropic = (model, context, options) => {
376
571
  // For Opus 4.6 and Sonnet 4.6: use adaptive thinking with effort level
377
572
  // For older models: use budget-based thinking
378
573
  if (supportsAdaptiveThinking(model.id)) {
379
- const effort = mapThinkingLevelToEffort(options.reasoning, model.id);
574
+ const effort = mapThinkingLevelToEffort(model, options.reasoning);
380
575
  return streamAnthropic(model, context, {
381
576
  ...base,
382
577
  thinkingEnabled: true,
@@ -394,16 +589,36 @@ export const streamSimpleAnthropic = (model, context, options) => {
394
589
  function isOAuthToken(apiKey) {
395
590
  return apiKey.includes("sk-ant-oat");
396
591
  }
397
- function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynamicHeaders) {
592
+ function createClient(model, apiKey, interleavedThinking, useFineGrainedToolStreamingBeta, optionsHeaders, dynamicHeaders, sessionId) {
398
593
  // Adaptive thinking models (Opus 4.6, Sonnet 4.6) have interleaved thinking built-in.
399
594
  // The beta header is deprecated on Opus 4.6 and redundant on Sonnet 4.6, so skip it.
400
595
  const needsInterleavedBeta = interleavedThinking && !supportsAdaptiveThinking(model.id);
401
- // Copilot: Bearer auth, selective betas (no fine-grained-tool-streaming)
596
+ const betaFeatures = [];
597
+ if (useFineGrainedToolStreamingBeta) {
598
+ betaFeatures.push(FINE_GRAINED_TOOL_STREAMING_BETA);
599
+ }
600
+ if (needsInterleavedBeta) {
601
+ betaFeatures.push(INTERLEAVED_THINKING_BETA);
602
+ }
603
+ if (model.provider === "cloudflare-ai-gateway") {
604
+ const client = new Anthropic({
605
+ apiKey: null,
606
+ authToken: null,
607
+ baseURL: resolveCloudflareBaseUrl(model),
608
+ dangerouslyAllowBrowser: true,
609
+ defaultHeaders: mergeHeaders({
610
+ accept: "application/json",
611
+ "anthropic-dangerous-direct-browser-access": "true",
612
+ "cf-aig-authorization": `Bearer ${apiKey}`,
613
+ "x-api-key": null,
614
+ Authorization: null,
615
+ ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
616
+ }, model.headers, optionsHeaders),
617
+ });
618
+ return { client, isOAuthToken: false };
619
+ }
620
+ // Copilot: Bearer auth, selective betas.
402
621
  if (model.provider === "github-copilot") {
403
- const betaFeatures = [];
404
- if (needsInterleavedBeta) {
405
- betaFeatures.push("interleaved-thinking-2025-05-14");
406
- }
407
622
  const client = new Anthropic({
408
623
  apiKey: null,
409
624
  authToken: apiKey,
@@ -417,10 +632,6 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
417
632
  });
418
633
  return { client, isOAuthToken: false };
419
634
  }
420
- const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"];
421
- if (needsInterleavedBeta) {
422
- betaFeatures.push("interleaved-thinking-2025-05-14");
423
- }
424
635
  // OAuth: Bearer auth, Claude Code identity headers
425
636
  if (isOAuthToken(apiKey)) {
426
637
  const client = new Anthropic({
@@ -431,7 +642,7 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
431
642
  defaultHeaders: mergeHeaders({
432
643
  accept: "application/json",
433
644
  "anthropic-dangerous-direct-browser-access": "true",
434
- "anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}`,
645
+ "anthropic-beta": ["claude-code-20250219", "oauth-2025-04-20", ...betaFeatures].join(","),
435
646
  "user-agent": `claude-cli/${claudeCodeVersion}`,
436
647
  "x-app": "cli",
437
648
  }, model.headers, optionsHeaders),
@@ -439,6 +650,7 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
439
650
  return { client, isOAuthToken: true };
440
651
  }
441
652
  // API key auth
653
+ const sessionAffinityHeaders = sessionId && getAnthropicCompat(model).sendSessionAffinityHeaders ? { "x-session-affinity": sessionId } : {};
442
654
  const client = new Anthropic({
443
655
  apiKey,
444
656
  baseURL: model.baseUrl,
@@ -446,13 +658,13 @@ function createClient(model, apiKey, interleavedThinking, optionsHeaders, dynami
446
658
  defaultHeaders: mergeHeaders({
447
659
  accept: "application/json",
448
660
  "anthropic-dangerous-direct-browser-access": "true",
449
- "anthropic-beta": betaFeatures.join(","),
450
- }, model.headers, optionsHeaders),
661
+ ...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
662
+ }, sessionAffinityHeaders, model.headers, optionsHeaders),
451
663
  });
452
664
  return { client, isOAuthToken: false };
453
665
  }
454
666
  function buildParams(model, context, isOAuthToken, options) {
455
- const { cacheControl } = getCacheControl(model.baseUrl, options?.cacheRetention);
667
+ const { cacheControl } = getCacheControl(model, options?.cacheRetention);
456
668
  const params = {
457
669
  model: model.id,
458
670
  messages: convertMessages(context.messages, model, isOAuthToken, cacheControl),
@@ -490,24 +702,39 @@ function buildParams(model, context, isOAuthToken, options) {
490
702
  if (options?.temperature !== undefined && !options?.thinkingEnabled) {
491
703
  params.temperature = options.temperature;
492
704
  }
493
- if (context.tools) {
494
- params.tools = convertTools(context.tools, isOAuthToken);
705
+ if (context.tools && context.tools.length > 0) {
706
+ const compat = getAnthropicCompat(model);
707
+ params.tools = convertTools(context.tools, isOAuthToken, compat.supportsEagerToolInputStreaming, compat.supportsCacheControlOnTools ? cacheControl : undefined);
495
708
  }
496
- // Configure thinking mode: adaptive (Opus 4.6 and Sonnet 4.6) or budget-based (older models)
497
- if (options?.thinkingEnabled && model.reasoning) {
498
- if (supportsAdaptiveThinking(model.id)) {
499
- // Adaptive thinking: Claude decides when and how much to think
500
- params.thinking = { type: "adaptive" };
501
- if (options.effort) {
502
- params.output_config = { effort: options.effort };
709
+ // Configure thinking mode: adaptive (Opus 4.6+ and Sonnet 4.6),
710
+ // budget-based (older models), or explicitly disabled.
711
+ if (model.reasoning) {
712
+ if (options?.thinkingEnabled) {
713
+ // Default to "summarized" so Opus 4.7 and Mythos Preview behave like
714
+ // older Claude 4 models (whose API default is also "summarized").
715
+ const display = options.thinkingDisplay ?? "summarized";
716
+ if (supportsAdaptiveThinking(model.id)) {
717
+ // Adaptive thinking: Claude decides when and how much to think.
718
+ params.thinking = { type: "adaptive", display };
719
+ if (options.effort) {
720
+ // The Anthropic SDK types can lag newly supported effort values such as "xhigh".
721
+ params.output_config =
722
+ options.effort === "xhigh"
723
+ ? { effort: options.effort }
724
+ : { effort: options.effort };
725
+ }
726
+ }
727
+ else {
728
+ // Budget-based thinking for older models
729
+ params.thinking = {
730
+ type: "enabled",
731
+ budget_tokens: options.thinkingBudgetTokens || 1024,
732
+ display,
733
+ };
503
734
  }
504
735
  }
505
- else {
506
- // Budget-based thinking for older models
507
- params.thinking = {
508
- type: "enabled",
509
- budget_tokens: options.thinkingBudgetTokens || 1024,
510
- };
736
+ else if (options?.thinkingEnabled === false) {
737
+ params.thinking = { type: "disabled" };
511
738
  }
512
739
  }
513
740
  if (options?.metadata) {
@@ -564,8 +791,7 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
564
791
  };
565
792
  }
566
793
  });
567
- let filteredBlocks = !model?.input.includes("image") ? blocks.filter((b) => b.type !== "image") : blocks;
568
- filteredBlocks = filteredBlocks.filter((b) => {
794
+ const filteredBlocks = blocks.filter((b) => {
569
795
  if (b.type === "text") {
570
796
  return b.text.trim().length > 0;
571
797
  }
@@ -689,19 +915,24 @@ function convertMessages(messages, model, isOAuthToken, cacheControl) {
689
915
  }
690
916
  return params;
691
917
  }
692
- 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) {
693
922
  if (!tools)
694
923
  return [];
695
- return tools.map((tool) => {
696
- const jsonSchema = tool.parameters; // TypeBox already generates JSON Schema
924
+ return tools.map((tool, index) => {
925
+ const schema = tool.parameters;
697
926
  return {
698
927
  name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
699
928
  description: tool.description,
929
+ ...(supportsEagerToolInputStreaming ? { eager_input_streaming: true } : {}),
700
930
  input_schema: {
701
931
  type: "object",
702
- properties: jsonSchema.properties || {},
703
- required: jsonSchema.required || [],
932
+ properties: schema.properties ?? {},
933
+ required: schema.required ?? [],
704
934
  },
935
+ ...(cacheControl && index === tools.length - 1 ? { cache_control: cacheControl } : {}),
705
936
  };
706
937
  });
707
938
  }