@zhachory1/mewrite-ai 0.65.3

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 (238) hide show
  1. package/README.md +1330 -0
  2. package/dist/api-registry.d.ts +20 -0
  3. package/dist/api-registry.d.ts.map +1 -0
  4. package/dist/api-registry.js +44 -0
  5. package/dist/api-registry.js.map +1 -0
  6. package/dist/bedrock-provider.d.ts +5 -0
  7. package/dist/bedrock-provider.d.ts.map +1 -0
  8. package/dist/bedrock-provider.js +6 -0
  9. package/dist/bedrock-provider.js.map +1 -0
  10. package/dist/cache/__tests__/compaction.test.d.ts +2 -0
  11. package/dist/cache/__tests__/compaction.test.d.ts.map +1 -0
  12. package/dist/cache/__tests__/compaction.test.js +100 -0
  13. package/dist/cache/__tests__/compaction.test.js.map +1 -0
  14. package/dist/cache/__tests__/layers.test.d.ts +2 -0
  15. package/dist/cache/__tests__/layers.test.d.ts.map +1 -0
  16. package/dist/cache/__tests__/layers.test.js +71 -0
  17. package/dist/cache/__tests__/layers.test.js.map +1 -0
  18. package/dist/cache/__tests__/policy.test.d.ts +2 -0
  19. package/dist/cache/__tests__/policy.test.d.ts.map +1 -0
  20. package/dist/cache/__tests__/policy.test.js +79 -0
  21. package/dist/cache/__tests__/policy.test.js.map +1 -0
  22. package/dist/cache/__tests__/tool-serializer.test.d.ts +2 -0
  23. package/dist/cache/__tests__/tool-serializer.test.d.ts.map +1 -0
  24. package/dist/cache/__tests__/tool-serializer.test.js +64 -0
  25. package/dist/cache/__tests__/tool-serializer.test.js.map +1 -0
  26. package/dist/cache/compaction.d.ts +30 -0
  27. package/dist/cache/compaction.d.ts.map +1 -0
  28. package/dist/cache/compaction.js +50 -0
  29. package/dist/cache/compaction.js.map +1 -0
  30. package/dist/cache/index.d.ts +5 -0
  31. package/dist/cache/index.d.ts.map +1 -0
  32. package/dist/cache/index.js +5 -0
  33. package/dist/cache/index.js.map +1 -0
  34. package/dist/cache/layers.d.ts +17 -0
  35. package/dist/cache/layers.d.ts.map +1 -0
  36. package/dist/cache/layers.js +48 -0
  37. package/dist/cache/layers.js.map +1 -0
  38. package/dist/cache/policy.d.ts +40 -0
  39. package/dist/cache/policy.d.ts.map +1 -0
  40. package/dist/cache/policy.js +50 -0
  41. package/dist/cache/policy.js.map +1 -0
  42. package/dist/cache/tool-serializer.d.ts +8 -0
  43. package/dist/cache/tool-serializer.d.ts.map +1 -0
  44. package/dist/cache/tool-serializer.js +35 -0
  45. package/dist/cache/tool-serializer.js.map +1 -0
  46. package/dist/cli.d.ts +3 -0
  47. package/dist/cli.d.ts.map +1 -0
  48. package/dist/cli.js +116 -0
  49. package/dist/cli.js.map +1 -0
  50. package/dist/env-api-keys.d.ts +27 -0
  51. package/dist/env-api-keys.d.ts.map +1 -0
  52. package/dist/env-api-keys.js +223 -0
  53. package/dist/env-api-keys.js.map +1 -0
  54. package/dist/index.d.ts +32 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +21 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/models.d.ts +34 -0
  59. package/dist/models.d.ts.map +1 -0
  60. package/dist/models.generated.d.ts +14370 -0
  61. package/dist/models.generated.d.ts.map +1 -0
  62. package/dist/models.generated.js +14181 -0
  63. package/dist/models.generated.js.map +1 -0
  64. package/dist/models.js +140 -0
  65. package/dist/models.js.map +1 -0
  66. package/dist/oauth.d.ts +2 -0
  67. package/dist/oauth.d.ts.map +1 -0
  68. package/dist/oauth.js +2 -0
  69. package/dist/oauth.js.map +1 -0
  70. package/dist/providers/amazon-bedrock.d.ts +20 -0
  71. package/dist/providers/amazon-bedrock.d.ts.map +1 -0
  72. package/dist/providers/amazon-bedrock.js +651 -0
  73. package/dist/providers/amazon-bedrock.js.map +1 -0
  74. package/dist/providers/anthropic-capabilities.d.ts +65 -0
  75. package/dist/providers/anthropic-capabilities.d.ts.map +1 -0
  76. package/dist/providers/anthropic-capabilities.js +155 -0
  77. package/dist/providers/anthropic-capabilities.js.map +1 -0
  78. package/dist/providers/anthropic-discovery.d.ts +49 -0
  79. package/dist/providers/anthropic-discovery.d.ts.map +1 -0
  80. package/dist/providers/anthropic-discovery.js +218 -0
  81. package/dist/providers/anthropic-discovery.js.map +1 -0
  82. package/dist/providers/anthropic.d.ts +40 -0
  83. package/dist/providers/anthropic.d.ts.map +1 -0
  84. package/dist/providers/anthropic.js +766 -0
  85. package/dist/providers/anthropic.js.map +1 -0
  86. package/dist/providers/azure-openai-responses.d.ts +15 -0
  87. package/dist/providers/azure-openai-responses.d.ts.map +1 -0
  88. package/dist/providers/azure-openai-responses.js +176 -0
  89. package/dist/providers/azure-openai-responses.js.map +1 -0
  90. package/dist/providers/faux.d.ts +56 -0
  91. package/dist/providers/faux.d.ts.map +1 -0
  92. package/dist/providers/faux.js +367 -0
  93. package/dist/providers/faux.js.map +1 -0
  94. package/dist/providers/github-copilot-headers.d.ts +8 -0
  95. package/dist/providers/github-copilot-headers.d.ts.map +1 -0
  96. package/dist/providers/github-copilot-headers.js +29 -0
  97. package/dist/providers/github-copilot-headers.js.map +1 -0
  98. package/dist/providers/google-gemini-cli.d.ts +74 -0
  99. package/dist/providers/google-gemini-cli.d.ts.map +1 -0
  100. package/dist/providers/google-gemini-cli.js +776 -0
  101. package/dist/providers/google-gemini-cli.js.map +1 -0
  102. package/dist/providers/google-shared.d.ts +65 -0
  103. package/dist/providers/google-shared.d.ts.map +1 -0
  104. package/dist/providers/google-shared.js +312 -0
  105. package/dist/providers/google-shared.js.map +1 -0
  106. package/dist/providers/google-vertex.d.ts +15 -0
  107. package/dist/providers/google-vertex.d.ts.map +1 -0
  108. package/dist/providers/google-vertex.js +419 -0
  109. package/dist/providers/google-vertex.js.map +1 -0
  110. package/dist/providers/google.d.ts +13 -0
  111. package/dist/providers/google.d.ts.map +1 -0
  112. package/dist/providers/google.js +374 -0
  113. package/dist/providers/google.js.map +1 -0
  114. package/dist/providers/mistral.d.ts +22 -0
  115. package/dist/providers/mistral.d.ts.map +1 -0
  116. package/dist/providers/mistral.js +501 -0
  117. package/dist/providers/mistral.js.map +1 -0
  118. package/dist/providers/openai-codex-responses.d.ts +9 -0
  119. package/dist/providers/openai-codex-responses.d.ts.map +1 -0
  120. package/dist/providers/openai-codex-responses.js +741 -0
  121. package/dist/providers/openai-codex-responses.js.map +1 -0
  122. package/dist/providers/openai-completions.d.ts +15 -0
  123. package/dist/providers/openai-completions.d.ts.map +1 -0
  124. package/dist/providers/openai-completions.js +753 -0
  125. package/dist/providers/openai-completions.js.map +1 -0
  126. package/dist/providers/openai-responses-shared.d.ts +17 -0
  127. package/dist/providers/openai-responses-shared.d.ts.map +1 -0
  128. package/dist/providers/openai-responses-shared.js +470 -0
  129. package/dist/providers/openai-responses-shared.js.map +1 -0
  130. package/dist/providers/openai-responses.d.ts +13 -0
  131. package/dist/providers/openai-responses.d.ts.map +1 -0
  132. package/dist/providers/openai-responses.js +190 -0
  133. package/dist/providers/openai-responses.js.map +1 -0
  134. package/dist/providers/register-builtins.d.ts +38 -0
  135. package/dist/providers/register-builtins.d.ts.map +1 -0
  136. package/dist/providers/register-builtins.js +261 -0
  137. package/dist/providers/register-builtins.js.map +1 -0
  138. package/dist/providers/simple-options.d.ts +8 -0
  139. package/dist/providers/simple-options.d.ts.map +1 -0
  140. package/dist/providers/simple-options.js +35 -0
  141. package/dist/providers/simple-options.js.map +1 -0
  142. package/dist/providers/transform-messages.d.ts +8 -0
  143. package/dist/providers/transform-messages.d.ts.map +1 -0
  144. package/dist/providers/transform-messages.js +155 -0
  145. package/dist/providers/transform-messages.js.map +1 -0
  146. package/dist/registry/fetcher.d.ts +26 -0
  147. package/dist/registry/fetcher.d.ts.map +1 -0
  148. package/dist/registry/fetcher.js +69 -0
  149. package/dist/registry/fetcher.js.map +1 -0
  150. package/dist/registry/index.d.ts +8 -0
  151. package/dist/registry/index.d.ts.map +1 -0
  152. package/dist/registry/index.js +8 -0
  153. package/dist/registry/index.js.map +1 -0
  154. package/dist/registry/loader.d.ts +38 -0
  155. package/dist/registry/loader.d.ts.map +1 -0
  156. package/dist/registry/loader.js +90 -0
  157. package/dist/registry/loader.js.map +1 -0
  158. package/dist/registry/merger.d.ts +21 -0
  159. package/dist/registry/merger.d.ts.map +1 -0
  160. package/dist/registry/merger.js +90 -0
  161. package/dist/registry/merger.js.map +1 -0
  162. package/dist/registry/schema.d.ts +130 -0
  163. package/dist/registry/schema.d.ts.map +1 -0
  164. package/dist/registry/schema.js +103 -0
  165. package/dist/registry/schema.js.map +1 -0
  166. package/dist/stream.d.ts +8 -0
  167. package/dist/stream.d.ts.map +1 -0
  168. package/dist/stream.js +27 -0
  169. package/dist/stream.js.map +1 -0
  170. package/dist/types.d.ts +296 -0
  171. package/dist/types.d.ts.map +1 -0
  172. package/dist/types.js +2 -0
  173. package/dist/types.js.map +1 -0
  174. package/dist/utils/event-stream.d.ts +21 -0
  175. package/dist/utils/event-stream.d.ts.map +1 -0
  176. package/dist/utils/event-stream.js +81 -0
  177. package/dist/utils/event-stream.js.map +1 -0
  178. package/dist/utils/hash.d.ts +3 -0
  179. package/dist/utils/hash.d.ts.map +1 -0
  180. package/dist/utils/hash.js +14 -0
  181. package/dist/utils/hash.js.map +1 -0
  182. package/dist/utils/json-parse.d.ts +9 -0
  183. package/dist/utils/json-parse.d.ts.map +1 -0
  184. package/dist/utils/json-parse.js +29 -0
  185. package/dist/utils/json-parse.js.map +1 -0
  186. package/dist/utils/oauth/anthropic.d.ts +25 -0
  187. package/dist/utils/oauth/anthropic.d.ts.map +1 -0
  188. package/dist/utils/oauth/anthropic.js +336 -0
  189. package/dist/utils/oauth/anthropic.js.map +1 -0
  190. package/dist/utils/oauth/github-copilot.d.ts +30 -0
  191. package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
  192. package/dist/utils/oauth/github-copilot.js +293 -0
  193. package/dist/utils/oauth/github-copilot.js.map +1 -0
  194. package/dist/utils/oauth/google-antigravity.d.ts +26 -0
  195. package/dist/utils/oauth/google-antigravity.d.ts.map +1 -0
  196. package/dist/utils/oauth/google-antigravity.js +376 -0
  197. package/dist/utils/oauth/google-antigravity.js.map +1 -0
  198. package/dist/utils/oauth/google-gemini-cli.d.ts +26 -0
  199. package/dist/utils/oauth/google-gemini-cli.d.ts.map +1 -0
  200. package/dist/utils/oauth/google-gemini-cli.js +482 -0
  201. package/dist/utils/oauth/google-gemini-cli.js.map +1 -0
  202. package/dist/utils/oauth/index.d.ts +61 -0
  203. package/dist/utils/oauth/index.d.ts.map +1 -0
  204. package/dist/utils/oauth/index.js +131 -0
  205. package/dist/utils/oauth/index.js.map +1 -0
  206. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  207. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  208. package/dist/utils/oauth/oauth-page.js +105 -0
  209. package/dist/utils/oauth/oauth-page.js.map +1 -0
  210. package/dist/utils/oauth/openai-codex.d.ts +34 -0
  211. package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
  212. package/dist/utils/oauth/openai-codex.js +374 -0
  213. package/dist/utils/oauth/openai-codex.js.map +1 -0
  214. package/dist/utils/oauth/pkce.d.ts +13 -0
  215. package/dist/utils/oauth/pkce.d.ts.map +1 -0
  216. package/dist/utils/oauth/pkce.js +31 -0
  217. package/dist/utils/oauth/pkce.js.map +1 -0
  218. package/dist/utils/oauth/types.d.ts +49 -0
  219. package/dist/utils/oauth/types.d.ts.map +1 -0
  220. package/dist/utils/oauth/types.js +2 -0
  221. package/dist/utils/oauth/types.js.map +1 -0
  222. package/dist/utils/overflow.d.ts +53 -0
  223. package/dist/utils/overflow.d.ts.map +1 -0
  224. package/dist/utils/overflow.js +132 -0
  225. package/dist/utils/overflow.js.map +1 -0
  226. package/dist/utils/sanitize-unicode.d.ts +22 -0
  227. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  228. package/dist/utils/sanitize-unicode.js +26 -0
  229. package/dist/utils/sanitize-unicode.js.map +1 -0
  230. package/dist/utils/typebox-helpers.d.ts +17 -0
  231. package/dist/utils/typebox-helpers.d.ts.map +1 -0
  232. package/dist/utils/typebox-helpers.js +21 -0
  233. package/dist/utils/typebox-helpers.js.map +1 -0
  234. package/dist/utils/validation.d.ts +18 -0
  235. package/dist/utils/validation.d.ts.map +1 -0
  236. package/dist/utils/validation.js +80 -0
  237. package/dist/utils/validation.js.map +1 -0
  238. package/package.json +129 -0
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Model-capability discovery for Anthropic-family models.
3
+ *
4
+ * Two backends, dispatched by provider:
5
+ *
6
+ * - github-copilot: GET {baseUrl}/models — OpenAI-style listing with rich
7
+ * `capabilities.supports` (adaptive_thinking, reasoning_effort, limits.
8
+ * max_context_window_tokens). The 1M context tier on Copilot is exposed
9
+ * as separate model ids (for example claude-opus-4.6-1m) rather than
10
+ * via a beta header; the bare id (claude-opus-4.6) is hard-capped at
11
+ * 200k regardless of any beta header (which the relay rejects).
12
+ * Discovery surfaces every id the response returns so the user can
13
+ * select the right one in the picker.
14
+ *
15
+ * - anthropic: GET {baseUrl}/v1/models — Anthropic-native with
16
+ * `capabilities.thinking.types.adaptive.supported`,
17
+ * `capabilities.effort.{max,xhigh}.supported`, `max_input_tokens`.
18
+ * The 1M context tier is gated by `anthropic-beta: context-1m-2025-08-07`
19
+ * on the same id; we detect it by making a second probe with that
20
+ * header and observing whether `max_input_tokens` rises.
21
+ *
22
+ * - others (amazon-bedrock, google-vertex, etc.): no equivalent
23
+ * discovery endpoint; the static fallback table in
24
+ * anthropic-capabilities.ts is used.
25
+ *
26
+ * Discovery runs at most once per (provider, baseUrl, apiKey-hash) per
27
+ * process. Concurrent calls join the same in-flight promise. Failures are
28
+ * silent — we leave the static fallback in place and try again next
29
+ * process. The discovery layer never throws back to the caller.
30
+ *
31
+ * The discovered registry entries (new model ids, corrected contextWindow)
32
+ * are also pushed into the in-memory model registry via
33
+ * `mergeDiscoveredModels` so the model picker reflects reality.
34
+ */
35
+ import { CONTEXT_1M_BETA_HEADER, setDiscoveredCapabilities, } from "./anthropic-capabilities.js";
36
+ // ----------------------------------------------------------------------------
37
+ // Discovery state (per process)
38
+ // ----------------------------------------------------------------------------
39
+ const discoveryPromises = new Map();
40
+ function discoveryKey(provider, baseUrl) {
41
+ return `${provider}::${baseUrl}`;
42
+ }
43
+ /**
44
+ * Trigger discovery for a provider+baseUrl. Returns a Promise that resolves
45
+ * once the capability cache has been populated (or the attempt has failed).
46
+ * Subsequent calls with the same (provider, baseUrl) join the in-flight
47
+ * promise.
48
+ *
49
+ * Never throws. On any error the static fallback remains in place.
50
+ */
51
+ export function discoverAnthropicCapabilities(provider, baseUrl, apiKey, extraHeaders) {
52
+ const key = discoveryKey(provider, baseUrl);
53
+ const existing = discoveryPromises.get(key);
54
+ if (existing)
55
+ return existing;
56
+ const p = runDiscovery(provider, baseUrl, apiKey, extraHeaders).catch(() => {
57
+ // Swallow — discovery is best-effort; static table remains in place.
58
+ });
59
+ discoveryPromises.set(key, p);
60
+ return p;
61
+ }
62
+ /** Test-only: clear discovery memo so tests can re-run. */
63
+ export function _clearDiscoveryStateForTests() {
64
+ discoveryPromises.clear();
65
+ }
66
+ // ----------------------------------------------------------------------------
67
+ // Dispatcher
68
+ // ----------------------------------------------------------------------------
69
+ async function runDiscovery(provider, baseUrl, apiKey, extraHeaders) {
70
+ if (provider === "github-copilot") {
71
+ await discoverCopilot(baseUrl, apiKey, extraHeaders);
72
+ return;
73
+ }
74
+ if (provider === "anthropic") {
75
+ await discoverAnthropicNative(baseUrl, apiKey, extraHeaders);
76
+ return;
77
+ }
78
+ // No discovery endpoint for other providers (Bedrock, Vertex, etc.).
79
+ }
80
+ async function discoverCopilot(baseUrl, apiKey, extraHeaders) {
81
+ const res = await fetch(`${baseUrl}/models`, {
82
+ method: "GET",
83
+ headers: {
84
+ Authorization: `Bearer ${apiKey}`,
85
+ "User-Agent": "GitHubCopilotChat/0.35.0",
86
+ "Editor-Version": "vscode/1.107.0",
87
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
88
+ "Copilot-Integration-Id": "vscode-chat",
89
+ ...extraHeaders,
90
+ },
91
+ });
92
+ if (!res.ok)
93
+ return;
94
+ const body = (await res.json());
95
+ const entries = body?.data ?? [];
96
+ const discoveredModels = [];
97
+ for (const entry of entries) {
98
+ if (entry.vendor !== "Anthropic")
99
+ continue;
100
+ if (!entry.supported_endpoints?.includes("/v1/messages"))
101
+ continue;
102
+ const caps = entry.capabilities ?? {};
103
+ const sup = caps.supports ?? {};
104
+ const lim = caps.limits ?? {};
105
+ const adaptive = sup.adaptive_thinking === true;
106
+ const efforts = sup.reasoning_effort ?? [];
107
+ const xhighEffort = efforts.includes("max") || efforts.includes("xhigh");
108
+ setDiscoveredCapabilities("github-copilot", entry.id, {
109
+ thinkingSchema: adaptive ? "adaptive" : "legacy",
110
+ xhighEffort: xhighEffort || undefined,
111
+ contextWindow: lim.max_context_window_tokens,
112
+ });
113
+ // Build a registry entry so previously-unknown ids (e.g.
114
+ // claude-opus-4.6-1m) show up in the model picker.
115
+ discoveredModels.push({
116
+ id: entry.id,
117
+ name: entry.name || entry.id,
118
+ api: "anthropic-messages",
119
+ provider: "github-copilot",
120
+ baseUrl,
121
+ headers: {
122
+ "User-Agent": "GitHubCopilotChat/0.35.0",
123
+ "Editor-Version": "vscode/1.107.0",
124
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
125
+ "Copilot-Integration-Id": "vscode-chat",
126
+ },
127
+ reasoning: adaptive,
128
+ input: sup.vision ? ["text", "image"] : ["text"],
129
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
130
+ contextWindow: lim.max_context_window_tokens ?? 128000,
131
+ maxTokens: lim.max_output_tokens ?? 8192,
132
+ });
133
+ }
134
+ mergeDiscoveredModels("github-copilot", discoveredModels);
135
+ }
136
+ async function discoverAnthropicNative(baseUrl, apiKey, extraHeaders) {
137
+ const baseHeaders = {
138
+ "x-api-key": apiKey,
139
+ "anthropic-version": "2023-06-01",
140
+ ...extraHeaders,
141
+ };
142
+ // First, capability map from the bare /v1/models endpoint.
143
+ const baseEntries = await fetchAnthropicModels(baseUrl, baseHeaders);
144
+ if (!baseEntries)
145
+ return;
146
+ // Then, retry with the context-1m beta and compare max_input_tokens.
147
+ // Models whose max_input_tokens grows with the beta need it to unlock
148
+ // the larger window; others reject the beta or ignore it.
149
+ const betaEntries = await fetchAnthropicModels(baseUrl, {
150
+ ...baseHeaders,
151
+ "anthropic-beta": CONTEXT_1M_BETA_HEADER,
152
+ });
153
+ const betaWindowById = new Map();
154
+ if (betaEntries) {
155
+ for (const entry of betaEntries) {
156
+ if (typeof entry.max_input_tokens === "number") {
157
+ betaWindowById.set(entry.id, entry.max_input_tokens);
158
+ }
159
+ }
160
+ }
161
+ for (const entry of baseEntries) {
162
+ const c = entry.capabilities ?? {};
163
+ const thinking = c.thinking ?? {};
164
+ const types = thinking.types ?? {};
165
+ const effort = c.effort ?? {};
166
+ const adaptive = types.adaptive?.supported === true;
167
+ const xhighEffort = effort.max?.supported === true || effort.xhigh?.supported === true;
168
+ const baseWindow = entry.max_input_tokens ?? 0;
169
+ const betaWindow = betaWindowById.get(entry.id) ?? 0;
170
+ const wantsBeta = betaWindow > baseWindow;
171
+ const caps = {
172
+ thinkingSchema: adaptive ? "adaptive" : "legacy",
173
+ };
174
+ if (xhighEffort)
175
+ caps.xhighEffort = true;
176
+ if (wantsBeta) {
177
+ caps.contextBeta = CONTEXT_1M_BETA_HEADER;
178
+ caps.contextWindow = betaWindow;
179
+ }
180
+ else if (baseWindow > 0) {
181
+ caps.contextWindow = baseWindow;
182
+ }
183
+ setDiscoveredCapabilities("anthropic", entry.id, caps);
184
+ }
185
+ }
186
+ async function fetchAnthropicModels(baseUrl, headers) {
187
+ const url = `${baseUrl.replace(/\/$/, "")}/v1/models?limit=1000`;
188
+ const res = await fetch(url, { method: "GET", headers });
189
+ if (!res.ok)
190
+ return null;
191
+ const body = (await res.json());
192
+ return body?.data ?? null;
193
+ }
194
+ // ----------------------------------------------------------------------------
195
+ // Registry merge
196
+ // ----------------------------------------------------------------------------
197
+ /**
198
+ * Push freshly-discovered models into the in-memory registry via the hook
199
+ * wired by models.ts. New ids are inserted; existing ids have their
200
+ * capability-related fields refreshed.
201
+ */
202
+ function mergeDiscoveredModels(provider, fresh) {
203
+ for (const m of fresh) {
204
+ registerModel(provider, m);
205
+ }
206
+ }
207
+ // Registry mutation is provided by models.ts at module load time to avoid a
208
+ // circular import. Until it is wired, discovery still populates the
209
+ // capability cache but cannot publish new model ids into the registry.
210
+ const registryHookHolder = { fn: () => { } };
211
+ function registerModel(provider, model) {
212
+ registryHookHolder.fn(provider, model);
213
+ }
214
+ /** Not for external use: called by models.ts at module load to wire the registry hook. */
215
+ export function _setRegistryHook(hook) {
216
+ registryHookHolder.fn = hook;
217
+ }
218
+ //# sourceMappingURL=anthropic-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-discovery.js","sourceRoot":"","sources":["../../src/providers/anthropic-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model-capability discovery for Anthropic-family models.\n *\n * Two backends, dispatched by provider:\n *\n * - github-copilot: GET {baseUrl}/models — OpenAI-style listing with rich\n * `capabilities.supports` (adaptive_thinking, reasoning_effort, limits.\n * max_context_window_tokens). The 1M context tier on Copilot is exposed\n * as separate model ids (for example claude-opus-4.6-1m) rather than\n * via a beta header; the bare id (claude-opus-4.6) is hard-capped at\n * 200k regardless of any beta header (which the relay rejects).\n * Discovery surfaces every id the response returns so the user can\n * select the right one in the picker.\n *\n * - anthropic: GET {baseUrl}/v1/models — Anthropic-native with\n * `capabilities.thinking.types.adaptive.supported`,\n * `capabilities.effort.{max,xhigh}.supported`, `max_input_tokens`.\n * The 1M context tier is gated by `anthropic-beta: context-1m-2025-08-07`\n * on the same id; we detect it by making a second probe with that\n * header and observing whether `max_input_tokens` rises.\n *\n * - others (amazon-bedrock, google-vertex, etc.): no equivalent\n * discovery endpoint; the static fallback table in\n * anthropic-capabilities.ts is used.\n *\n * Discovery runs at most once per (provider, baseUrl, apiKey-hash) per\n * process. Concurrent calls join the same in-flight promise. Failures are\n * silent — we leave the static fallback in place and try again next\n * process. The discovery layer never throws back to the caller.\n *\n * The discovered registry entries (new model ids, corrected contextWindow)\n * are also pushed into the in-memory model registry via\n * `mergeDiscoveredModels` so the model picker reflects reality.\n */\n\nimport type { Api, Model } from \"../types.js\";\nimport {\n\ttype AnthropicModelCapabilities,\n\tCONTEXT_1M_BETA_HEADER,\n\tsetDiscoveredCapabilities,\n} from \"./anthropic-capabilities.js\";\n\n// ----------------------------------------------------------------------------\n// Discovery state (per process)\n// ----------------------------------------------------------------------------\n\nconst discoveryPromises = new Map<string, Promise<void>>();\n\nfunction discoveryKey(provider: string, baseUrl: string): string {\n\treturn `${provider}::${baseUrl}`;\n}\n\n/**\n * Trigger discovery for a provider+baseUrl. Returns a Promise that resolves\n * once the capability cache has been populated (or the attempt has failed).\n * Subsequent calls with the same (provider, baseUrl) join the in-flight\n * promise.\n *\n * Never throws. On any error the static fallback remains in place.\n */\nexport function discoverAnthropicCapabilities(\n\tprovider: string,\n\tbaseUrl: string,\n\tapiKey: string,\n\textraHeaders?: Record<string, string>,\n): Promise<void> {\n\tconst key = discoveryKey(provider, baseUrl);\n\tconst existing = discoveryPromises.get(key);\n\tif (existing) return existing;\n\n\tconst p = runDiscovery(provider, baseUrl, apiKey, extraHeaders).catch(() => {\n\t\t// Swallow — discovery is best-effort; static table remains in place.\n\t});\n\tdiscoveryPromises.set(key, p);\n\treturn p;\n}\n\n/** Test-only: clear discovery memo so tests can re-run. */\nexport function _clearDiscoveryStateForTests(): void {\n\tdiscoveryPromises.clear();\n}\n\n// ----------------------------------------------------------------------------\n// Dispatcher\n// ----------------------------------------------------------------------------\n\nasync function runDiscovery(\n\tprovider: string,\n\tbaseUrl: string,\n\tapiKey: string,\n\textraHeaders?: Record<string, string>,\n): Promise<void> {\n\tif (provider === \"github-copilot\") {\n\t\tawait discoverCopilot(baseUrl, apiKey, extraHeaders);\n\t\treturn;\n\t}\n\tif (provider === \"anthropic\") {\n\t\tawait discoverAnthropicNative(baseUrl, apiKey, extraHeaders);\n\t\treturn;\n\t}\n\t// No discovery endpoint for other providers (Bedrock, Vertex, etc.).\n}\n\n// ----------------------------------------------------------------------------\n// GitHub Copilot: GET {baseUrl}/models\n// ----------------------------------------------------------------------------\n\ninterface CopilotModelEntry {\n\tid: string;\n\tname?: string;\n\tvendor?: string;\n\tsupported_endpoints?: string[];\n\tmodel_picker_enabled?: boolean;\n\tcapabilities?: {\n\t\tfamily?: string;\n\t\ttype?: string;\n\t\tsupports?: {\n\t\t\tadaptive_thinking?: boolean;\n\t\t\treasoning_effort?: string[];\n\t\t\tstreaming?: boolean;\n\t\t\ttool_calls?: boolean;\n\t\t\tvision?: boolean;\n\t\t};\n\t\tlimits?: {\n\t\t\tmax_context_window_tokens?: number;\n\t\t\tmax_output_tokens?: number;\n\t\t\tmax_prompt_tokens?: number;\n\t\t};\n\t};\n}\n\nasync function discoverCopilot(baseUrl: string, apiKey: string, extraHeaders?: Record<string, string>): Promise<void> {\n\tconst res = await fetch(`${baseUrl}/models`, {\n\t\tmethod: \"GET\",\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${apiKey}`,\n\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\t\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\t\t\"Copilot-Integration-Id\": \"vscode-chat\",\n\t\t\t...extraHeaders,\n\t\t},\n\t});\n\tif (!res.ok) return;\n\tconst body = (await res.json()) as { data?: CopilotModelEntry[] };\n\tconst entries = body?.data ?? [];\n\n\tconst discoveredModels: Model<\"anthropic-messages\">[] = [];\n\n\tfor (const entry of entries) {\n\t\tif (entry.vendor !== \"Anthropic\") continue;\n\t\tif (!entry.supported_endpoints?.includes(\"/v1/messages\")) continue;\n\n\t\tconst caps = entry.capabilities ?? {};\n\t\tconst sup = caps.supports ?? {};\n\t\tconst lim = caps.limits ?? {};\n\n\t\tconst adaptive = sup.adaptive_thinking === true;\n\t\tconst efforts = sup.reasoning_effort ?? [];\n\t\tconst xhighEffort = efforts.includes(\"max\") || efforts.includes(\"xhigh\");\n\n\t\tsetDiscoveredCapabilities(\"github-copilot\", entry.id, {\n\t\t\tthinkingSchema: adaptive ? \"adaptive\" : \"legacy\",\n\t\t\txhighEffort: xhighEffort || undefined,\n\t\t\tcontextWindow: lim.max_context_window_tokens,\n\t\t});\n\n\t\t// Build a registry entry so previously-unknown ids (e.g.\n\t\t// claude-opus-4.6-1m) show up in the model picker.\n\t\tdiscoveredModels.push({\n\t\t\tid: entry.id,\n\t\t\tname: entry.name || entry.id,\n\t\t\tapi: \"anthropic-messages\",\n\t\t\tprovider: \"github-copilot\",\n\t\t\tbaseUrl,\n\t\t\theaders: {\n\t\t\t\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\t\t\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\t\t\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\t\t\t\"Copilot-Integration-Id\": \"vscode-chat\",\n\t\t\t},\n\t\t\treasoning: adaptive,\n\t\t\tinput: sup.vision ? [\"text\", \"image\"] : [\"text\"],\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n\t\t\tcontextWindow: lim.max_context_window_tokens ?? 128000,\n\t\t\tmaxTokens: lim.max_output_tokens ?? 8192,\n\t\t});\n\t}\n\n\tmergeDiscoveredModels(\"github-copilot\", discoveredModels);\n}\n\n// ----------------------------------------------------------------------------\n// Direct Anthropic: GET {baseUrl}/v1/models\n// ----------------------------------------------------------------------------\n\ninterface AnthropicCapabilitySupport {\n\tsupported?: boolean;\n}\ninterface AnthropicModelEntry {\n\tid: string;\n\tdisplay_name?: string;\n\tmax_input_tokens?: number;\n\tmax_tokens?: number;\n\tcapabilities?: {\n\t\tthinking?: {\n\t\t\tsupported?: boolean;\n\t\t\ttypes?: {\n\t\t\t\tadaptive?: AnthropicCapabilitySupport;\n\t\t\t\tenabled?: AnthropicCapabilitySupport;\n\t\t\t};\n\t\t};\n\t\teffort?: {\n\t\t\tsupported?: boolean;\n\t\t\tlow?: AnthropicCapabilitySupport;\n\t\t\tmedium?: AnthropicCapabilitySupport;\n\t\t\thigh?: AnthropicCapabilitySupport;\n\t\t\tmax?: AnthropicCapabilitySupport;\n\t\t\txhigh?: AnthropicCapabilitySupport;\n\t\t};\n\t\timage_input?: AnthropicCapabilitySupport;\n\t};\n}\n\nasync function discoverAnthropicNative(\n\tbaseUrl: string,\n\tapiKey: string,\n\textraHeaders?: Record<string, string>,\n): Promise<void> {\n\tconst baseHeaders: Record<string, string> = {\n\t\t\"x-api-key\": apiKey,\n\t\t\"anthropic-version\": \"2023-06-01\",\n\t\t...extraHeaders,\n\t};\n\n\t// First, capability map from the bare /v1/models endpoint.\n\tconst baseEntries = await fetchAnthropicModels(baseUrl, baseHeaders);\n\tif (!baseEntries) return;\n\n\t// Then, retry with the context-1m beta and compare max_input_tokens.\n\t// Models whose max_input_tokens grows with the beta need it to unlock\n\t// the larger window; others reject the beta or ignore it.\n\tconst betaEntries = await fetchAnthropicModels(baseUrl, {\n\t\t...baseHeaders,\n\t\t\"anthropic-beta\": CONTEXT_1M_BETA_HEADER,\n\t});\n\tconst betaWindowById = new Map<string, number>();\n\tif (betaEntries) {\n\t\tfor (const entry of betaEntries) {\n\t\t\tif (typeof entry.max_input_tokens === \"number\") {\n\t\t\t\tbetaWindowById.set(entry.id, entry.max_input_tokens);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const entry of baseEntries) {\n\t\tconst c = entry.capabilities ?? {};\n\t\tconst thinking = c.thinking ?? {};\n\t\tconst types = thinking.types ?? {};\n\t\tconst effort = c.effort ?? {};\n\n\t\tconst adaptive = types.adaptive?.supported === true;\n\t\tconst xhighEffort = effort.max?.supported === true || effort.xhigh?.supported === true;\n\n\t\tconst baseWindow = entry.max_input_tokens ?? 0;\n\t\tconst betaWindow = betaWindowById.get(entry.id) ?? 0;\n\t\tconst wantsBeta = betaWindow > baseWindow;\n\n\t\tconst caps: AnthropicModelCapabilities = {\n\t\t\tthinkingSchema: adaptive ? \"adaptive\" : \"legacy\",\n\t\t};\n\t\tif (xhighEffort) caps.xhighEffort = true;\n\t\tif (wantsBeta) {\n\t\t\tcaps.contextBeta = CONTEXT_1M_BETA_HEADER;\n\t\t\tcaps.contextWindow = betaWindow;\n\t\t} else if (baseWindow > 0) {\n\t\t\tcaps.contextWindow = baseWindow;\n\t\t}\n\n\t\tsetDiscoveredCapabilities(\"anthropic\", entry.id, caps);\n\t}\n}\n\nasync function fetchAnthropicModels(\n\tbaseUrl: string,\n\theaders: Record<string, string>,\n): Promise<AnthropicModelEntry[] | null> {\n\tconst url = `${baseUrl.replace(/\\/$/, \"\")}/v1/models?limit=1000`;\n\tconst res = await fetch(url, { method: \"GET\", headers });\n\tif (!res.ok) return null;\n\tconst body = (await res.json()) as { data?: AnthropicModelEntry[] };\n\treturn body?.data ?? null;\n}\n\n// ----------------------------------------------------------------------------\n// Registry merge\n// ----------------------------------------------------------------------------\n\n/**\n * Push freshly-discovered models into the in-memory registry via the hook\n * wired by models.ts. New ids are inserted; existing ids have their\n * capability-related fields refreshed.\n */\nfunction mergeDiscoveredModels(provider: string, fresh: Model<Api>[]): void {\n\tfor (const m of fresh) {\n\t\tregisterModel(provider, m);\n\t}\n}\n\n// Registry mutation is provided by models.ts at module load time to avoid a\n// circular import. Until it is wired, discovery still populates the\n// capability cache but cannot publish new model ids into the registry.\nconst registryHookHolder: { fn: (provider: string, model: Model<Api>) => void } = { fn: () => {} };\n\nfunction registerModel(provider: string, model: Model<Api>): void {\n\tregistryHookHolder.fn(provider, model);\n}\n\n/** Not for external use: called by models.ts at module load to wire the registry hook. */\nexport function _setRegistryHook(hook: (provider: string, model: Model<Api>) => void): void {\n\tregistryHookHolder.fn = hook;\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ import type { SimpleStreamOptions, StreamFunction, StreamOptions } from "../types.js";
3
+ export type AnthropicEffort = "low" | "medium" | "high" | "max";
4
+ export interface AnthropicOptions extends StreamOptions {
5
+ /**
6
+ * Enable extended thinking.
7
+ * For Opus 4.6 and Sonnet 4.6: uses adaptive thinking (model decides when/how much to think).
8
+ * For older models: uses budget-based thinking with thinkingBudgetTokens.
9
+ */
10
+ thinkingEnabled?: boolean;
11
+ /**
12
+ * Token budget for extended thinking (older models only).
13
+ * Ignored for Opus 4.6 and Sonnet 4.6, which use adaptive thinking.
14
+ */
15
+ thinkingBudgetTokens?: number;
16
+ /**
17
+ * Effort level for adaptive thinking (Opus 4.6 and Sonnet 4.6).
18
+ * Controls how much thinking Claude allocates:
19
+ * - "max": Always thinks with no constraints (Opus 4.6 only)
20
+ * - "high": Always thinks, deep reasoning (default)
21
+ * - "medium": Moderate thinking, may skip for simple queries
22
+ * - "low": Minimal thinking, skips for simple tasks
23
+ * Ignored for older models.
24
+ */
25
+ effort?: AnthropicEffort;
26
+ interleavedThinking?: boolean;
27
+ toolChoice?: "auto" | "any" | "none" | {
28
+ type: "tool";
29
+ name: string;
30
+ };
31
+ /**
32
+ * Pre-built Anthropic client instance. When provided, skips internal client
33
+ * construction entirely. Use this to inject alternative SDK clients such as
34
+ * `AnthropicVertex` that shares the same messaging API.
35
+ */
36
+ client?: Anthropic;
37
+ }
38
+ export declare const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOptions>;
39
+ export declare const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleStreamOptions>;
40
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAQ1C,OAAO,KAAK,EAQX,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAmIrB,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAEhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAYD,eAAO,MAAM,eAAe,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA2PlF,CAAC;AA2BF,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAuC3F,CAAC","sourcesContent":["import Anthropic from \"@anthropic-ai/sdk\";\nimport type {\n\tContentBlockParam,\n\tMessageCreateParamsStreaming,\n\tMessageParam,\n} from \"@anthropic-ai/sdk/resources/messages.js\";\nimport { getEnvApiKey } from \"../env-api-keys.js\";\nimport { calculateCost } from \"../models.js\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tCacheRetention,\n\tContext,\n\tImageContent,\n\tMessage,\n\tModel,\n\tSimpleStreamOptions,\n\tStopReason,\n\tStreamFunction,\n\tStreamOptions,\n\tTextContent,\n\tThinkingContent,\n\tTool,\n\tToolCall,\n\tToolResultMessage,\n} from \"../types.js\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.js\";\nimport { parseStreamingJson } from \"../utils/json-parse.js\";\nimport { sanitizeSurrogates } from \"../utils/sanitize-unicode.js\";\n\nimport { getAnthropicCapabilities, supportsAdaptiveThinking } from \"./anthropic-capabilities.js\";\nimport { discoverAnthropicCapabilities } from \"./anthropic-discovery.js\";\nimport { buildCopilotDynamicHeaders, hasCopilotVisionInput } from \"./github-copilot-headers.js\";\nimport { adjustMaxTokensForThinking, buildBaseOptions } from \"./simple-options.js\";\nimport { transformMessages } from \"./transform-messages.js\";\n\n/**\n * Resolve cache retention preference.\n * Defaults to \"short\" and uses PI_CACHE_RETENTION for backward compatibility.\n */\nfunction resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention {\n\tif (cacheRetention) {\n\t\treturn cacheRetention;\n\t}\n\tif (typeof process !== \"undefined\" && process.env.PI_CACHE_RETENTION === \"long\") {\n\t\treturn \"long\";\n\t}\n\treturn \"short\";\n}\n\nfunction getCacheControl(\n\tbaseUrl: string,\n\tcacheRetention?: CacheRetention,\n): { retention: CacheRetention; cacheControl?: { type: \"ephemeral\"; ttl?: \"1h\" } } {\n\tconst retention = resolveCacheRetention(cacheRetention);\n\tif (retention === \"none\") {\n\t\treturn { retention };\n\t}\n\tconst ttl = retention === \"long\" && baseUrl.includes(\"api.anthropic.com\") ? \"1h\" : undefined;\n\treturn {\n\t\tretention,\n\t\tcacheControl: { type: \"ephemeral\", ...(ttl && { ttl }) },\n\t};\n}\n\n// Stealth mode: Mimic Claude Code's tool naming exactly\nconst claudeCodeVersion = \"2.1.75\";\n\n// Claude Code 2.x tool names (canonical casing)\n// Source: https://cchistory.mariozechner.at/data/prompts-2.1.11.md\n// To update: https://github.com/badlogic/cchistory\nconst claudeCodeTools = [\n\t\"Read\",\n\t\"Write\",\n\t\"Edit\",\n\t\"Bash\",\n\t\"Grep\",\n\t\"Glob\",\n\t\"AskUserQuestion\",\n\t\"EnterPlanMode\",\n\t\"ExitPlanMode\",\n\t\"KillShell\",\n\t\"NotebookEdit\",\n\t\"Skill\",\n\t\"Task\",\n\t\"TaskOutput\",\n\t\"TodoWrite\",\n\t\"WebFetch\",\n\t\"WebSearch\",\n];\n\nconst ccToolLookup = new Map(claudeCodeTools.map((t) => [t.toLowerCase(), t]));\n\n// Convert tool name to CC canonical casing if it matches (case-insensitive)\nconst toClaudeCodeName = (name: string) => ccToolLookup.get(name.toLowerCase()) ?? name;\nconst fromClaudeCodeName = (name: string, tools?: Tool[]) => {\n\tif (tools && tools.length > 0) {\n\t\tconst lowerName = name.toLowerCase();\n\t\tconst matchedTool = tools.find((tool) => tool.name.toLowerCase() === lowerName);\n\t\tif (matchedTool) return matchedTool.name;\n\t}\n\treturn name;\n};\n\n/**\n * Convert content blocks to Anthropic API format\n */\nfunction convertContentBlocks(content: (TextContent | ImageContent)[]):\n\t| string\n\t| Array<\n\t\t\t| { type: \"text\"; text: string }\n\t\t\t| {\n\t\t\t\t\ttype: \"image\";\n\t\t\t\t\tsource: {\n\t\t\t\t\t\ttype: \"base64\";\n\t\t\t\t\t\tmedia_type: \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\";\n\t\t\t\t\t\tdata: string;\n\t\t\t\t\t};\n\t\t\t }\n\t > {\n\t// If only text blocks, return as concatenated string for simplicity\n\tconst hasImages = content.some((c) => c.type === \"image\");\n\tif (!hasImages) {\n\t\treturn sanitizeSurrogates(content.map((c) => (c as TextContent).text).join(\"\\n\"));\n\t}\n\n\t// If we have images, convert to content block array\n\tconst blocks = content.map((block) => {\n\t\tif (block.type === \"text\") {\n\t\t\treturn {\n\t\t\t\ttype: \"text\" as const,\n\t\t\t\ttext: sanitizeSurrogates(block.text),\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\ttype: \"image\" as const,\n\t\t\tsource: {\n\t\t\t\ttype: \"base64\" as const,\n\t\t\t\tmedia_type: block.mimeType as \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\",\n\t\t\t\tdata: block.data,\n\t\t\t},\n\t\t};\n\t});\n\n\t// If only images (no text), add placeholder text block\n\tconst hasText = blocks.some((b) => b.type === \"text\");\n\tif (!hasText) {\n\t\tblocks.unshift({\n\t\t\ttype: \"text\" as const,\n\t\t\ttext: \"(see attached image)\",\n\t\t});\n\t}\n\n\treturn blocks;\n}\n\nexport type AnthropicEffort = \"low\" | \"medium\" | \"high\" | \"max\";\n\nexport interface AnthropicOptions extends StreamOptions {\n\t/**\n\t * Enable extended thinking.\n\t * For Opus 4.6 and Sonnet 4.6: uses adaptive thinking (model decides when/how much to think).\n\t * For older models: uses budget-based thinking with thinkingBudgetTokens.\n\t */\n\tthinkingEnabled?: boolean;\n\t/**\n\t * Token budget for extended thinking (older models only).\n\t * Ignored for Opus 4.6 and Sonnet 4.6, which use adaptive thinking.\n\t */\n\tthinkingBudgetTokens?: number;\n\t/**\n\t * Effort level for adaptive thinking (Opus 4.6 and Sonnet 4.6).\n\t * Controls how much thinking Claude allocates:\n\t * - \"max\": Always thinks with no constraints (Opus 4.6 only)\n\t * - \"high\": Always thinks, deep reasoning (default)\n\t * - \"medium\": Moderate thinking, may skip for simple queries\n\t * - \"low\": Minimal thinking, skips for simple tasks\n\t * Ignored for older models.\n\t */\n\teffort?: AnthropicEffort;\n\tinterleavedThinking?: boolean;\n\ttoolChoice?: \"auto\" | \"any\" | \"none\" | { type: \"tool\"; name: string };\n\t/**\n\t * Pre-built Anthropic client instance. When provided, skips internal client\n\t * construction entirely. Use this to inject alternative SDK clients such as\n\t * `AnthropicVertex` that shares the same messaging API.\n\t */\n\tclient?: Anthropic;\n}\n\nfunction mergeHeaders(...headerSources: (Record<string, string> | undefined)[]): Record<string, string> {\n\tconst merged: Record<string, string> = {};\n\tfor (const headers of headerSources) {\n\t\tif (headers) {\n\t\t\tObject.assign(merged, headers);\n\t\t}\n\t}\n\treturn merged;\n}\n\nexport const streamAnthropic: StreamFunction<\"anthropic-messages\", AnthropicOptions> = (\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\toptions?: AnthropicOptions,\n): AssistantMessageEventStream => {\n\tconst stream = new AssistantMessageEventStream();\n\n\t(async () => {\n\t\tconst output: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: model.api as Api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\ttotalTokens: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t},\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\n\t\ttry {\n\t\t\tlet client: Anthropic;\n\t\t\tlet isOAuth: boolean;\n\n\t\t\tif (options?.client) {\n\t\t\t\tclient = options.client;\n\t\t\t\tisOAuth = false;\n\t\t\t} else {\n\t\t\t\tconst apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? \"\";\n\n\t\t\t\t// Discover real per-account capabilities before building the\n\t\t\t\t// request so the first call uses the right thinking schema,\n\t\t\t\t// reasoning_effort ceiling, and context window. Memoized per\n\t\t\t\t// (provider, baseUrl) for the rest of the process; subsequent\n\t\t\t\t// calls resolve from cache immediately.\n\t\t\t\tif (apiKey) {\n\t\t\t\t\tawait discoverAnthropicCapabilities(model.provider, model.baseUrl, apiKey);\n\t\t\t\t}\n\n\t\t\t\tlet copilotDynamicHeaders: Record<string, string> | undefined;\n\t\t\t\tif (model.provider === \"github-copilot\") {\n\t\t\t\t\tconst hasImages = hasCopilotVisionInput(context.messages);\n\t\t\t\t\tcopilotDynamicHeaders = buildCopilotDynamicHeaders({\n\t\t\t\t\t\tmessages: context.messages,\n\t\t\t\t\t\thasImages,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst created = createClient(\n\t\t\t\t\tmodel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\toptions?.interleavedThinking ?? true,\n\t\t\t\t\toptions?.headers,\n\t\t\t\t\tcopilotDynamicHeaders,\n\t\t\t\t);\n\t\t\t\tclient = created.client;\n\t\t\t\tisOAuth = created.isOAuthToken;\n\t\t\t}\n\t\t\tlet params = buildParams(model, context, isOAuth, options);\n\t\t\tconst nextParams = await options?.onPayload?.(params, model);\n\t\t\tif (nextParams !== undefined) {\n\t\t\t\tparams = nextParams as MessageCreateParamsStreaming;\n\t\t\t}\n\t\t\tconst anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: options?.signal });\n\t\t\tstream.push({ type: \"start\", partial: output });\n\n\t\t\ttype Block = (ThinkingContent | TextContent | (ToolCall & { partialJson: string })) & { index: number };\n\t\t\tconst blocks = output.content as Block[];\n\n\t\t\tfor await (const event of anthropicStream) {\n\t\t\t\tif (event.type === \"message_start\") {\n\t\t\t\t\toutput.responseId = event.message.id;\n\t\t\t\t\t// Capture initial token usage from message_start event\n\t\t\t\t\t// This ensures we have input token counts even if the stream is aborted early\n\t\t\t\t\toutput.usage.input = event.message.usage.input_tokens || 0;\n\t\t\t\t\toutput.usage.output = event.message.usage.output_tokens || 0;\n\t\t\t\t\toutput.usage.cacheRead = event.message.usage.cache_read_input_tokens || 0;\n\t\t\t\t\toutput.usage.cacheWrite = event.message.usage.cache_creation_input_tokens || 0;\n\t\t\t\t\t// Anthropic doesn't provide total_tokens, compute from components\n\t\t\t\t\toutput.usage.totalTokens =\n\t\t\t\t\t\toutput.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;\n\t\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\t} else if (event.type === \"content_block_start\") {\n\t\t\t\t\tif (event.content_block.type === \"text\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"text_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"thinking\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\tthinking: \"\",\n\t\t\t\t\t\t\tthinkingSignature: \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"redacted_thinking\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\tthinking: \"[Reasoning redacted]\",\n\t\t\t\t\t\t\tthinkingSignature: event.content_block.data,\n\t\t\t\t\t\t\tredacted: true,\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t} else if (event.content_block.type === \"tool_use\") {\n\t\t\t\t\t\tconst block: Block = {\n\t\t\t\t\t\t\ttype: \"toolCall\",\n\t\t\t\t\t\t\tid: event.content_block.id,\n\t\t\t\t\t\t\tname: isOAuth\n\t\t\t\t\t\t\t\t? fromClaudeCodeName(event.content_block.name, context.tools)\n\t\t\t\t\t\t\t\t: event.content_block.name,\n\t\t\t\t\t\t\targuments: (event.content_block.input as Record<string, any>) ?? {},\n\t\t\t\t\t\t\tpartialJson: \"\",\n\t\t\t\t\t\t\tindex: event.index,\n\t\t\t\t\t\t};\n\t\t\t\t\t\toutput.content.push(block);\n\t\t\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: output.content.length - 1, partial: output });\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"content_block_delta\") {\n\t\t\t\t\tif (event.delta.type === \"text_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"text\") {\n\t\t\t\t\t\t\tblock.text += event.delta.text;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.text,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"thinking_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"thinking\") {\n\t\t\t\t\t\t\tblock.thinking += event.delta.thinking;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.thinking,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"input_json_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"toolCall\") {\n\t\t\t\t\t\t\tblock.partialJson += event.delta.partial_json;\n\t\t\t\t\t\t\tblock.arguments = parseStreamingJson(block.partialJson);\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"toolcall_delta\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tdelta: event.delta.partial_json,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (event.delta.type === \"signature_delta\") {\n\t\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\t\tif (block && block.type === \"thinking\") {\n\t\t\t\t\t\t\tblock.thinkingSignature = block.thinkingSignature || \"\";\n\t\t\t\t\t\t\tblock.thinkingSignature += event.delta.signature;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"content_block_stop\") {\n\t\t\t\t\tconst index = blocks.findIndex((b) => b.index === event.index);\n\t\t\t\t\tconst block = blocks[index];\n\t\t\t\t\tif (block) {\n\t\t\t\t\t\tdelete (block as any).index;\n\t\t\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tcontent: block.text,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\tcontent: block.thinking,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\t\t\tblock.arguments = parseStreamingJson(block.partialJson);\n\t\t\t\t\t\t\tdelete (block as any).partialJson;\n\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\ttype: \"toolcall_end\",\n\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\ttoolCall: block,\n\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (event.type === \"message_delta\") {\n\t\t\t\t\tif (event.delta.stop_reason) {\n\t\t\t\t\t\toutput.stopReason = mapStopReason(event.delta.stop_reason);\n\t\t\t\t\t}\n\t\t\t\t\t// Only update usage fields if present (not null).\n\t\t\t\t\t// Preserves input_tokens from message_start when proxies omit it in message_delta.\n\t\t\t\t\tif (event.usage.input_tokens != null) {\n\t\t\t\t\t\toutput.usage.input = event.usage.input_tokens;\n\t\t\t\t\t}\n\t\t\t\t\tif (event.usage.output_tokens != null) {\n\t\t\t\t\t\toutput.usage.output = event.usage.output_tokens;\n\t\t\t\t\t}\n\t\t\t\t\tif (event.usage.cache_read_input_tokens != null) {\n\t\t\t\t\t\toutput.usage.cacheRead = event.usage.cache_read_input_tokens;\n\t\t\t\t\t}\n\t\t\t\t\tif (event.usage.cache_creation_input_tokens != null) {\n\t\t\t\t\t\toutput.usage.cacheWrite = event.usage.cache_creation_input_tokens;\n\t\t\t\t\t}\n\t\t\t\t\t// Anthropic doesn't provide total_tokens, compute from components\n\t\t\t\t\toutput.usage.totalTokens =\n\t\t\t\t\t\toutput.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;\n\t\t\t\t\tcalculateCost(model, output.usage);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (options?.signal?.aborted) {\n\t\t\t\tthrow new Error(\"Request was aborted\");\n\t\t\t}\n\n\t\t\tif (output.stopReason === \"aborted\" || output.stopReason === \"error\") {\n\t\t\t\tthrow new Error(\"An unknown error occurred\");\n\t\t\t}\n\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason, message: output });\n\t\t\tstream.end();\n\t\t} catch (error) {\n\t\t\tfor (const block of output.content) delete (block as any).index;\n\t\t\toutput.stopReason = options?.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\toutput.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t\tstream.end();\n\t\t}\n\t})();\n\n\treturn stream;\n};\n\n/**\n * Map ThinkingLevel to Anthropic effort levels for adaptive thinking.\n * Note: effort \"max\" is only valid on models whose capability table\n * entry sets `xhighEffort: true` (today: Opus 4.6 / 4.7).\n */\nfunction mapThinkingLevelToEffort(\n\tlevel: SimpleStreamOptions[\"reasoning\"],\n\tmodel: Model<\"anthropic-messages\">,\n): AnthropicEffort {\n\tswitch (level) {\n\t\tcase \"minimal\":\n\t\t\treturn \"low\";\n\t\tcase \"low\":\n\t\t\treturn \"low\";\n\t\tcase \"medium\":\n\t\t\treturn \"medium\";\n\t\tcase \"high\":\n\t\t\treturn \"high\";\n\t\tcase \"xhigh\":\n\t\t\treturn getAnthropicCapabilities(model.id, model.provider).xhighEffort ? \"max\" : \"high\";\n\t\tdefault:\n\t\t\treturn \"high\";\n\t}\n}\n\nexport const streamSimpleAnthropic: StreamFunction<\"anthropic-messages\", SimpleStreamOptions> = (\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\toptions?: SimpleStreamOptions,\n): AssistantMessageEventStream => {\n\tconst apiKey = options?.apiKey || getEnvApiKey(model.provider);\n\tif (!apiKey) {\n\t\tthrow new Error(`No API key for provider: ${model.provider}`);\n\t}\n\n\tconst base = buildBaseOptions(model, options, apiKey);\n\tif (!options?.reasoning) {\n\t\treturn streamAnthropic(model, context, { ...base, thinkingEnabled: false } satisfies AnthropicOptions);\n\t}\n\n\t// For Opus 4.6 and Sonnet 4.6: use adaptive thinking with effort level\n\t// For older models: use budget-based thinking\n\tif (supportsAdaptiveThinking(model.id, model.provider)) {\n\t\tconst effort = mapThinkingLevelToEffort(options.reasoning, model);\n\t\treturn streamAnthropic(model, context, {\n\t\t\t...base,\n\t\t\tthinkingEnabled: true,\n\t\t\teffort,\n\t\t} satisfies AnthropicOptions);\n\t}\n\n\tconst adjusted = adjustMaxTokensForThinking(\n\t\tbase.maxTokens || 0,\n\t\tmodel.maxTokens,\n\t\toptions.reasoning,\n\t\toptions.thinkingBudgets,\n\t);\n\n\treturn streamAnthropic(model, context, {\n\t\t...base,\n\t\tmaxTokens: adjusted.maxTokens,\n\t\tthinkingEnabled: true,\n\t\tthinkingBudgetTokens: adjusted.thinkingBudget,\n\t} satisfies AnthropicOptions);\n};\n\nfunction isOAuthToken(apiKey: string): boolean {\n\treturn apiKey.includes(\"sk-ant-oat\");\n}\n\nfunction createClient(\n\tmodel: Model<\"anthropic-messages\">,\n\tapiKey: string,\n\tinterleavedThinking: boolean,\n\toptionsHeaders?: Record<string, string>,\n\tdynamicHeaders?: Record<string, string>,\n): { client: Anthropic; isOAuthToken: boolean } {\n\t// Adaptive thinking models (Opus 4.6, Sonnet 4.6) have interleaved thinking built-in.\n\t// The beta header is deprecated on Opus 4.6 and redundant on Sonnet 4.6, so skip it.\n\tconst needsInterleavedBeta = interleavedThinking && !supportsAdaptiveThinking(model.id, model.provider);\n\n\t// Opt into per-model capability betas (e.g. 1M context window).\n\tconst caps = getAnthropicCapabilities(model.id, model.provider);\n\tconst extraBetas: string[] = [];\n\tif (caps.contextBeta) {\n\t\textraBetas.push(caps.contextBeta);\n\t}\n\n\t// Copilot: Bearer auth, selective betas (no fine-grained-tool-streaming)\n\tif (model.provider === \"github-copilot\") {\n\t\tconst betaFeatures: string[] = [];\n\t\tif (needsInterleavedBeta) {\n\t\t\tbetaFeatures.push(\"interleaved-thinking-2025-05-14\");\n\t\t}\n\t\tbetaFeatures.push(...extraBetas);\n\n\t\tconst client = new Anthropic({\n\t\t\tapiKey: null,\n\t\t\tauthToken: apiKey,\n\t\t\tbaseURL: model.baseUrl,\n\t\t\tdangerouslyAllowBrowser: true,\n\t\t\tdefaultHeaders: mergeHeaders(\n\t\t\t\t{\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t\t\t...(betaFeatures.length > 0 ? { \"anthropic-beta\": betaFeatures.join(\",\") } : {}),\n\t\t\t\t},\n\t\t\t\tmodel.headers,\n\t\t\t\tdynamicHeaders,\n\t\t\t\toptionsHeaders,\n\t\t\t),\n\t\t});\n\n\t\treturn { client, isOAuthToken: false };\n\t}\n\n\tconst betaFeatures = [\"fine-grained-tool-streaming-2025-05-14\"];\n\tif (needsInterleavedBeta) {\n\t\tbetaFeatures.push(\"interleaved-thinking-2025-05-14\");\n\t}\n\tbetaFeatures.push(...extraBetas);\n\n\t// OAuth: Bearer auth, Claude Code identity headers\n\tif (isOAuthToken(apiKey)) {\n\t\tconst client = new Anthropic({\n\t\t\tapiKey: null,\n\t\t\tauthToken: apiKey,\n\t\t\tbaseURL: model.baseUrl,\n\t\t\tdangerouslyAllowBrowser: true,\n\t\t\tdefaultHeaders: mergeHeaders(\n\t\t\t\t{\n\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t\t\t\"anthropic-beta\": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(\",\")}`,\n\t\t\t\t\t\"user-agent\": `claude-cli/${claudeCodeVersion}`,\n\t\t\t\t\t\"x-app\": \"cli\",\n\t\t\t\t},\n\t\t\t\tmodel.headers,\n\t\t\t\toptionsHeaders,\n\t\t\t),\n\t\t});\n\n\t\treturn { client, isOAuthToken: true };\n\t}\n\n\t// API key auth\n\tconst client = new Anthropic({\n\t\tapiKey,\n\t\tbaseURL: model.baseUrl,\n\t\tdangerouslyAllowBrowser: true,\n\t\tdefaultHeaders: mergeHeaders(\n\t\t\t{\n\t\t\t\taccept: \"application/json\",\n\t\t\t\t\"anthropic-dangerous-direct-browser-access\": \"true\",\n\t\t\t\t\"anthropic-beta\": betaFeatures.join(\",\"),\n\t\t\t},\n\t\t\tmodel.headers,\n\t\t\toptionsHeaders,\n\t\t),\n\t});\n\n\treturn { client, isOAuthToken: false };\n}\n\nfunction buildParams(\n\tmodel: Model<\"anthropic-messages\">,\n\tcontext: Context,\n\tisOAuthToken: boolean,\n\toptions?: AnthropicOptions,\n): MessageCreateParamsStreaming {\n\tconst { cacheControl } = getCacheControl(model.baseUrl, options?.cacheRetention);\n\tconst params: MessageCreateParamsStreaming = {\n\t\tmodel: model.id,\n\t\tmessages: convertMessages(context.messages, model, isOAuthToken, cacheControl),\n\t\tmax_tokens: options?.maxTokens || (model.maxTokens / 3) | 0,\n\t\tstream: true,\n\t};\n\n\t// For OAuth tokens, we MUST include Claude Code identity\n\tif (isOAuthToken) {\n\t\tparams.system = [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: \"You are Claude Code, Anthropic's official CLI for Claude.\",\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t},\n\t\t];\n\t\tif (context.systemPrompt) {\n\t\t\tparams.system.push({\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: sanitizeSurrogates(context.systemPrompt),\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t});\n\t\t}\n\t} else if (context.systemPrompt) {\n\t\t// Add cache control to system prompt for non-OAuth tokens\n\t\tparams.system = [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: sanitizeSurrogates(context.systemPrompt),\n\t\t\t\t...(cacheControl ? { cache_control: cacheControl } : {}),\n\t\t\t},\n\t\t];\n\t}\n\n\t// Temperature is incompatible with extended thinking (adaptive or budget-based).\n\tif (options?.temperature !== undefined && !options?.thinkingEnabled) {\n\t\tparams.temperature = options.temperature;\n\t}\n\n\tif (context.tools) {\n\t\tparams.tools = convertTools(context.tools, isOAuthToken);\n\t\t// Experimental, env-gated (#42 cache probe): anchor a cache breakpoint on the\n\t\t// last tool so [system+tools] is a named stable segment, independent of the\n\t\t// rolling-tail breakpoint. Default OFF — no behavior change. Used only to\n\t\t// measure whether this beats Anthropic's automatic prefix-matching.\n\t\tif (cacheControl && process.env.CAVE_TOOLS_CACHE_BREAKPOINT === \"1\" && params.tools.length > 0) {\n\t\t\t(params.tools[params.tools.length - 1] as { cache_control?: typeof cacheControl }).cache_control =\n\t\t\t\tcacheControl;\n\t\t}\n\t}\n\n\t// Configure thinking mode: adaptive (Opus 4.6 and Sonnet 4.6),\n\t// budget-based (older models), or explicitly disabled.\n\tif (model.reasoning) {\n\t\tif (options?.thinkingEnabled) {\n\t\t\tif (supportsAdaptiveThinking(model.id, model.provider)) {\n\t\t\t\t// Adaptive thinking: Claude decides when and how much to think\n\t\t\t\tparams.thinking = { type: \"adaptive\" };\n\t\t\t\tif (options.effort) {\n\t\t\t\t\tparams.output_config = { effort: options.effort };\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Budget-based thinking for older models\n\t\t\t\tparams.thinking = {\n\t\t\t\t\ttype: \"enabled\",\n\t\t\t\t\tbudget_tokens: options.thinkingBudgetTokens || 1024,\n\t\t\t\t};\n\t\t\t}\n\t\t} else if (options?.thinkingEnabled === false) {\n\t\t\tparams.thinking = { type: \"disabled\" };\n\t\t}\n\t}\n\n\tif (options?.metadata) {\n\t\tconst userId = options.metadata.user_id;\n\t\tif (typeof userId === \"string\") {\n\t\t\tparams.metadata = { user_id: userId };\n\t\t}\n\t}\n\n\tif (options?.toolChoice) {\n\t\tif (typeof options.toolChoice === \"string\") {\n\t\t\tparams.tool_choice = { type: options.toolChoice };\n\t\t} else {\n\t\t\tparams.tool_choice = options.toolChoice;\n\t\t}\n\t}\n\n\treturn params;\n}\n\n// Normalize tool call IDs to match Anthropic's required pattern and length\nfunction normalizeToolCallId(id: string): string {\n\treturn id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n}\n\nfunction convertMessages(\n\tmessages: Message[],\n\tmodel: Model<\"anthropic-messages\">,\n\tisOAuthToken: boolean,\n\tcacheControl?: { type: \"ephemeral\"; ttl?: \"1h\" },\n): MessageParam[] {\n\tconst params: MessageParam[] = [];\n\n\t// Transform messages for cross-provider compatibility\n\tconst transformedMessages = transformMessages(messages, model, normalizeToolCallId);\n\n\tfor (let i = 0; i < transformedMessages.length; i++) {\n\t\tconst msg = transformedMessages[i];\n\n\t\tif (msg.role === \"user\") {\n\t\t\tif (typeof msg.content === \"string\") {\n\t\t\t\tif (msg.content.trim().length > 0) {\n\t\t\t\t\tparams.push({\n\t\t\t\t\t\trole: \"user\",\n\t\t\t\t\t\tcontent: sanitizeSurrogates(msg.content),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst blocks: ContentBlockParam[] = msg.content.map((item) => {\n\t\t\t\t\tif (item.type === \"text\") {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: sanitizeSurrogates(item.text),\n\t\t\t\t\t\t};\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"image\",\n\t\t\t\t\t\t\tsource: {\n\t\t\t\t\t\t\t\ttype: \"base64\",\n\t\t\t\t\t\t\t\tmedia_type: item.mimeType as \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\",\n\t\t\t\t\t\t\t\tdata: item.data,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tlet filteredBlocks = !model?.input.includes(\"image\") ? blocks.filter((b) => b.type !== \"image\") : blocks;\n\t\t\t\tfilteredBlocks = filteredBlocks.filter((b) => {\n\t\t\t\t\tif (b.type === \"text\") {\n\t\t\t\t\t\treturn b.text.trim().length > 0;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t});\n\t\t\t\tif (filteredBlocks.length === 0) continue;\n\t\t\t\tparams.push({\n\t\t\t\t\trole: \"user\",\n\t\t\t\t\tcontent: filteredBlocks,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (msg.role === \"assistant\") {\n\t\t\tconst blocks: ContentBlockParam[] = [];\n\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (block.text.trim().length === 0) continue;\n\t\t\t\t\tblocks.push({\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: sanitizeSurrogates(block.text),\n\t\t\t\t\t});\n\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\t// Redacted thinking: pass the opaque payload back as redacted_thinking\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\ttype: \"redacted_thinking\",\n\t\t\t\t\t\t\tdata: block.thinkingSignature!,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (block.thinking.trim().length === 0) continue;\n\t\t\t\t\t// If thinking signature is missing/empty (e.g., from aborted stream),\n\t\t\t\t\t// convert to plain text block without <thinking> tags to avoid API rejection\n\t\t\t\t\t// and prevent Claude from mimicking the tags in responses\n\t\t\t\t\tif (!block.thinkingSignature || block.thinkingSignature.trim().length === 0) {\n\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: sanitizeSurrogates(block.thinking),\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\tblocks.push({\n\t\t\t\t\t\t\ttype: \"thinking\",\n\t\t\t\t\t\t\tthinking: sanitizeSurrogates(block.thinking),\n\t\t\t\t\t\t\tsignature: block.thinkingSignature,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\tblocks.push({\n\t\t\t\t\t\ttype: \"tool_use\",\n\t\t\t\t\t\tid: block.id,\n\t\t\t\t\t\tname: isOAuthToken ? toClaudeCodeName(block.name) : block.name,\n\t\t\t\t\t\tinput: block.arguments ?? {},\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (blocks.length === 0) continue;\n\t\t\tparams.push({\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: blocks,\n\t\t\t});\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t// Collect all consecutive toolResult messages, needed for z.ai Anthropic endpoint\n\t\t\tconst toolResults: ContentBlockParam[] = [];\n\n\t\t\t// Add the current tool result\n\t\t\ttoolResults.push({\n\t\t\t\ttype: \"tool_result\",\n\t\t\t\ttool_use_id: msg.toolCallId,\n\t\t\t\tcontent: convertContentBlocks(msg.content),\n\t\t\t\tis_error: msg.isError,\n\t\t\t});\n\n\t\t\t// Look ahead for consecutive toolResult messages\n\t\t\tlet j = i + 1;\n\t\t\twhile (j < transformedMessages.length && transformedMessages[j].role === \"toolResult\") {\n\t\t\t\tconst nextMsg = transformedMessages[j] as ToolResultMessage; // We know it's a toolResult\n\t\t\t\ttoolResults.push({\n\t\t\t\t\ttype: \"tool_result\",\n\t\t\t\t\ttool_use_id: nextMsg.toolCallId,\n\t\t\t\t\tcontent: convertContentBlocks(nextMsg.content),\n\t\t\t\t\tis_error: nextMsg.isError,\n\t\t\t\t});\n\t\t\t\tj++;\n\t\t\t}\n\n\t\t\t// Skip the messages we've already processed\n\t\t\ti = j - 1;\n\n\t\t\t// Add a single user message with all tool results\n\t\t\tparams.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent: toolResults,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Add cache_control to the last user message to cache conversation history\n\tif (cacheControl && params.length > 0) {\n\t\tconst lastMessage = params[params.length - 1];\n\t\tif (lastMessage.role === \"user\") {\n\t\t\tif (Array.isArray(lastMessage.content)) {\n\t\t\t\tconst lastBlock = lastMessage.content[lastMessage.content.length - 1];\n\t\t\t\tif (\n\t\t\t\t\tlastBlock &&\n\t\t\t\t\t(lastBlock.type === \"text\" || lastBlock.type === \"image\" || lastBlock.type === \"tool_result\")\n\t\t\t\t) {\n\t\t\t\t\t(lastBlock as any).cache_control = cacheControl;\n\t\t\t\t}\n\t\t\t} else if (typeof lastMessage.content === \"string\") {\n\t\t\t\tlastMessage.content = [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: lastMessage.content,\n\t\t\t\t\t\tcache_control: cacheControl,\n\t\t\t\t\t},\n\t\t\t\t] as any;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn params;\n}\n\nfunction convertTools(tools: Tool[], isOAuthToken: boolean): Anthropic.Messages.Tool[] {\n\tif (!tools) return [];\n\n\treturn tools.map((tool) => {\n\t\tconst jsonSchema = tool.parameters as any; // TypeBox already generates JSON Schema\n\n\t\treturn {\n\t\t\tname: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tinput_schema: {\n\t\t\t\ttype: \"object\" as const,\n\t\t\t\tproperties: jsonSchema.properties || {},\n\t\t\t\trequired: jsonSchema.required || [],\n\t\t\t},\n\t\t};\n\t});\n}\n\nfunction mapStopReason(reason: Anthropic.Messages.StopReason | string): StopReason {\n\tswitch (reason) {\n\t\tcase \"end_turn\":\n\t\t\treturn \"stop\";\n\t\tcase \"max_tokens\":\n\t\t\treturn \"length\";\n\t\tcase \"tool_use\":\n\t\t\treturn \"toolUse\";\n\t\tcase \"refusal\":\n\t\t\treturn \"error\";\n\t\tcase \"pause_turn\": // Stop is good enough -> resubmit\n\t\t\treturn \"stop\";\n\t\tcase \"stop_sequence\":\n\t\t\treturn \"stop\"; // We don't supply stop sequences, so this should never happen\n\t\tcase \"sensitive\": // Content flagged by safety filters (not yet in SDK types)\n\t\t\treturn \"error\";\n\t\tdefault:\n\t\t\t// Handle unknown stop reasons gracefully (API may add new values)\n\t\t\tthrow new Error(`Unhandled stop reason: ${reason}`);\n\t}\n}\n"]}