@aryee337/aery-ai 0.2.28 → 0.2.29

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 (417) hide show
  1. package/CHANGELOG.md +2914 -0
  2. package/README.md +614 -813
  3. package/package.json +140 -105
  4. package/src/api-registry.ts +96 -0
  5. package/src/auth-broker/client.ts +358 -0
  6. package/src/auth-broker/index.ts +5 -0
  7. package/src/auth-broker/refresher.ts +117 -0
  8. package/src/auth-broker/remote-store.ts +623 -0
  9. package/src/auth-broker/server.ts +644 -0
  10. package/src/auth-broker/types.ts +127 -0
  11. package/src/auth-broker/wire-schemas.ts +200 -0
  12. package/src/auth-gateway/http.ts +194 -0
  13. package/src/auth-gateway/index.ts +3 -0
  14. package/src/auth-gateway/server.ts +818 -0
  15. package/src/auth-gateway/types.ts +143 -0
  16. package/src/auth-storage.ts +4422 -0
  17. package/src/index.ts +54 -0
  18. package/src/model-cache.ts +129 -0
  19. package/src/model-manager.ts +469 -0
  20. package/src/model-thinking.ts +782 -0
  21. package/src/models.json +83530 -0
  22. package/src/models.json.d.ts +9 -0
  23. package/src/models.ts +56 -0
  24. package/src/prompts/turn-aborted-guidance.md +4 -0
  25. package/src/provider-details.ts +90 -0
  26. package/src/provider-models/bundled-references.ts +38 -0
  27. package/src/provider-models/descriptors.ts +355 -0
  28. package/src/provider-models/google.ts +88 -0
  29. package/src/provider-models/index.ts +5 -0
  30. package/src/provider-models/ollama.ts +153 -0
  31. package/src/provider-models/openai-compat.ts +2817 -0
  32. package/src/provider-models/special.ts +67 -0
  33. package/src/providers/aery-native-client.ts +228 -0
  34. package/src/providers/aery-native-server.ts +212 -0
  35. package/src/providers/amazon-bedrock.ts +873 -0
  36. package/src/providers/anthropic-client.ts +318 -0
  37. package/src/providers/anthropic-messages-server-schema.ts +243 -0
  38. package/src/providers/anthropic-messages-server.ts +683 -0
  39. package/src/providers/anthropic-wire.ts +268 -0
  40. package/src/providers/anthropic.ts +3094 -0
  41. package/src/providers/aws-credentials.ts +501 -0
  42. package/src/providers/aws-eventstream.ts +185 -0
  43. package/src/providers/aws-sigv4.ts +218 -0
  44. package/src/providers/azure-openai-responses.ts +361 -0
  45. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  46. package/src/providers/cursor/proto/agent.proto +3526 -0
  47. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  48. package/src/providers/cursor/proto/buf.yaml +17 -0
  49. package/src/providers/cursor.ts +2621 -0
  50. package/src/providers/error-message.ts +21 -0
  51. package/src/providers/github-copilot-headers.ts +140 -0
  52. package/src/providers/gitlab-duo.ts +372 -0
  53. package/src/providers/google-auth.ts +252 -0
  54. package/src/providers/google-gemini-cli.ts +809 -0
  55. package/src/providers/google-gemini-headers.ts +41 -0
  56. package/src/providers/google-shared.ts +917 -0
  57. package/src/providers/google-types.ts +167 -0
  58. package/src/providers/google-vertex.ts +91 -0
  59. package/src/providers/google.ts +41 -0
  60. package/src/providers/grammar.ts +70 -0
  61. package/src/providers/kimi.ts +52 -0
  62. package/src/providers/mock.ts +496 -0
  63. package/src/providers/ollama.ts +644 -0
  64. package/src/providers/openai-anthropic-shim.ts +138 -0
  65. package/src/providers/openai-chat-server-schema.ts +252 -0
  66. package/src/providers/openai-chat-server.ts +647 -0
  67. package/src/providers/openai-codex/constants.ts +43 -0
  68. package/src/providers/openai-codex/request-transformer.ts +161 -0
  69. package/src/providers/openai-codex/response-handler.ts +81 -0
  70. package/src/providers/openai-codex-responses.ts +3018 -0
  71. package/src/providers/openai-completions-compat.ts +300 -0
  72. package/src/providers/openai-completions.ts +1979 -0
  73. package/src/providers/openai-responses-server-schema.ts +290 -0
  74. package/src/providers/openai-responses-server.ts +1183 -0
  75. package/src/providers/openai-responses-shared.ts +873 -0
  76. package/src/providers/openai-responses.ts +679 -0
  77. package/src/providers/register-builtins.ts +436 -0
  78. package/src/providers/synthetic.ts +50 -0
  79. package/src/providers/transform-messages.ts +382 -0
  80. package/src/providers/vision-guard.ts +31 -0
  81. package/src/providers/xai-responses.ts +82 -0
  82. package/src/rate-limit-utils.ts +84 -0
  83. package/src/stream.ts +1065 -0
  84. package/src/types.ts +944 -0
  85. package/src/usage/claude.ts +482 -0
  86. package/src/usage/gemini.ts +250 -0
  87. package/src/usage/github-copilot.ts +421 -0
  88. package/src/usage/google-antigravity.ts +201 -0
  89. package/src/usage/kimi.ts +271 -0
  90. package/src/usage/minimax-code.ts +31 -0
  91. package/src/usage/openai-codex.ts +503 -0
  92. package/src/usage/shared.ts +10 -0
  93. package/src/usage/zai.ts +247 -0
  94. package/src/usage.ts +185 -0
  95. package/src/utils/abort.ts +51 -0
  96. package/src/utils/abortable-iterator.ts +69 -0
  97. package/src/utils/anthropic-auth.ts +93 -0
  98. package/src/utils/discovery/antigravity.ts +261 -0
  99. package/src/utils/discovery/codex.ts +371 -0
  100. package/src/utils/discovery/cursor.ts +306 -0
  101. package/src/utils/discovery/gemini.ts +248 -0
  102. package/src/utils/discovery/index.ts +4 -0
  103. package/src/utils/discovery/openai-compatible.ts +224 -0
  104. package/src/utils/event-stream.ts +142 -0
  105. package/src/utils/fireworks-model-id.ts +30 -0
  106. package/src/utils/foundry.ts +8 -0
  107. package/src/utils/http-inspector.ts +176 -0
  108. package/src/utils/idle-iterator.ts +267 -0
  109. package/src/utils/json-parse.ts +182 -0
  110. package/src/utils/oauth/__tests__/xai-oauth.test.ts +107 -0
  111. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  112. package/src/utils/oauth/anthropic.ts +273 -0
  113. package/src/utils/oauth/api-key-login.ts +87 -0
  114. package/src/utils/oauth/api-key-validation.ts +92 -0
  115. package/src/utils/oauth/callback-server.ts +276 -0
  116. package/src/utils/oauth/cerebras.ts +16 -0
  117. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  118. package/src/utils/oauth/cursor.ts +157 -0
  119. package/src/utils/oauth/deepseek.ts +53 -0
  120. package/src/utils/oauth/firepass.ts +24 -0
  121. package/src/utils/oauth/fireworks.ts +15 -0
  122. package/src/utils/oauth/github-copilot.ts +362 -0
  123. package/src/utils/oauth/gitlab-duo.ts +123 -0
  124. package/src/utils/oauth/google-antigravity.ts +200 -0
  125. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  126. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  127. package/src/utils/oauth/huggingface.ts +62 -0
  128. package/src/utils/oauth/index.ts +484 -0
  129. package/src/utils/oauth/kagi.ts +47 -0
  130. package/src/utils/oauth/kilo.ts +87 -0
  131. package/src/utils/oauth/kimi.ts +254 -0
  132. package/src/utils/oauth/litellm.ts +47 -0
  133. package/src/utils/oauth/lm-studio.ts +38 -0
  134. package/src/utils/oauth/minimax-code.ts +78 -0
  135. package/src/utils/oauth/moonshot.ts +23 -0
  136. package/src/utils/oauth/nanogpt.ts +15 -0
  137. package/src/utils/oauth/nvidia.ts +70 -0
  138. package/src/utils/oauth/oauth.html +203 -0
  139. package/src/utils/oauth/ollama-cloud.ts +28 -0
  140. package/src/utils/oauth/ollama.ts +47 -0
  141. package/src/utils/oauth/openai-codex.ts +299 -0
  142. package/src/utils/oauth/opencode.ts +49 -0
  143. package/src/utils/oauth/openrouter.ts +20 -0
  144. package/src/utils/oauth/parallel.ts +46 -0
  145. package/src/utils/oauth/perplexity.ts +206 -0
  146. package/src/utils/oauth/pkce.ts +18 -0
  147. package/src/utils/oauth/qianfan.ts +58 -0
  148. package/src/utils/oauth/qwen-portal.ts +60 -0
  149. package/src/utils/oauth/synthetic.ts +15 -0
  150. package/src/utils/oauth/tavily.ts +46 -0
  151. package/src/utils/oauth/together.ts +16 -0
  152. package/src/utils/oauth/types.ts +99 -0
  153. package/src/utils/oauth/venice.ts +59 -0
  154. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  155. package/src/utils/oauth/vllm.ts +40 -0
  156. package/src/utils/oauth/wafer.ts +50 -0
  157. package/src/utils/oauth/xai-oauth.ts +342 -0
  158. package/src/utils/oauth/xiaomi.ts +139 -0
  159. package/src/utils/oauth/zai.ts +60 -0
  160. package/src/utils/oauth/zenmux.ts +15 -0
  161. package/src/utils/oauth/zhipu.ts +60 -0
  162. package/src/utils/overflow.ts +137 -0
  163. package/src/utils/parse-bind.ts +54 -0
  164. package/src/utils/provider-response.ts +30 -0
  165. package/src/utils/request-debug.ts +336 -0
  166. package/src/utils/retry-after.ts +110 -0
  167. package/src/utils/retry.ts +54 -0
  168. package/src/utils/schema/CONSTRAINTS.md +164 -0
  169. package/src/utils/schema/adapt.ts +36 -0
  170. package/src/utils/schema/compatibility.ts +435 -0
  171. package/src/utils/schema/dereference.ts +98 -0
  172. package/src/utils/schema/draft.ts +341 -0
  173. package/src/utils/schema/equality.ts +97 -0
  174. package/src/utils/schema/fields.ts +191 -0
  175. package/src/utils/schema/index.ts +13 -0
  176. package/src/utils/schema/json-schema-validator.ts +577 -0
  177. package/src/utils/schema/meta-validator.ts +167 -0
  178. package/src/utils/schema/normalize.ts +1588 -0
  179. package/src/utils/schema/spill.ts +43 -0
  180. package/src/utils/schema/stamps.ts +97 -0
  181. package/src/utils/schema/types.ts +10 -0
  182. package/src/utils/schema/wire.ts +293 -0
  183. package/src/utils/schema/zod-decontaminate.ts +331 -0
  184. package/src/utils/sdk-stream-timeout.ts +43 -0
  185. package/src/utils/sse-debug.ts +289 -0
  186. package/src/utils/stream-markup-healing.ts +612 -0
  187. package/src/utils/tool-choice.ts +99 -0
  188. package/src/utils/validation.ts +1024 -0
  189. package/src/utils.ts +166 -0
  190. package/dist/api-registry.d.ts +0 -20
  191. package/dist/api-registry.d.ts.map +0 -1
  192. package/dist/api-registry.js +0 -44
  193. package/dist/api-registry.js.map +0 -1
  194. package/dist/bedrock-provider.d.ts +0 -5
  195. package/dist/bedrock-provider.d.ts.map +0 -1
  196. package/dist/bedrock-provider.js +0 -6
  197. package/dist/bedrock-provider.js.map +0 -1
  198. package/dist/cli.d.ts +0 -3
  199. package/dist/cli.d.ts.map +0 -1
  200. package/dist/cli.js +0 -130
  201. package/dist/cli.js.map +0 -1
  202. package/dist/env-api-keys.d.ts +0 -18
  203. package/dist/env-api-keys.d.ts.map +0 -1
  204. package/dist/env-api-keys.js +0 -178
  205. package/dist/env-api-keys.js.map +0 -1
  206. package/dist/image-models.d.ts +0 -10
  207. package/dist/image-models.d.ts.map +0 -1
  208. package/dist/image-models.generated.d.ts +0 -440
  209. package/dist/image-models.generated.d.ts.map +0 -1
  210. package/dist/image-models.generated.js +0 -442
  211. package/dist/image-models.generated.js.map +0 -1
  212. package/dist/image-models.js +0 -23
  213. package/dist/image-models.js.map +0 -1
  214. package/dist/images-api-registry.d.ts +0 -14
  215. package/dist/images-api-registry.d.ts.map +0 -1
  216. package/dist/images-api-registry.js +0 -22
  217. package/dist/images-api-registry.js.map +0 -1
  218. package/dist/images.d.ts +0 -4
  219. package/dist/images.d.ts.map +0 -1
  220. package/dist/images.js +0 -14
  221. package/dist/images.js.map +0 -1
  222. package/dist/index.d.ts +0 -32
  223. package/dist/index.d.ts.map +0 -1
  224. package/dist/index.js +0 -20
  225. package/dist/index.js.map +0 -1
  226. package/dist/models.d.ts +0 -18
  227. package/dist/models.d.ts.map +0 -1
  228. package/dist/models.generated.d.ts +0 -17707
  229. package/dist/models.generated.d.ts.map +0 -1
  230. package/dist/models.generated.js +0 -16561
  231. package/dist/models.generated.js.map +0 -1
  232. package/dist/models.js +0 -71
  233. package/dist/models.js.map +0 -1
  234. package/dist/oauth.d.ts +0 -2
  235. package/dist/oauth.d.ts.map +0 -1
  236. package/dist/oauth.js +0 -2
  237. package/dist/oauth.js.map +0 -1
  238. package/dist/providers/aery-error-formatting.d.ts +0 -13
  239. package/dist/providers/aery-error-formatting.d.ts.map +0 -1
  240. package/dist/providers/aery-error-formatting.js +0 -112
  241. package/dist/providers/aery-error-formatting.js.map +0 -1
  242. package/dist/providers/amazon-bedrock.d.ts +0 -38
  243. package/dist/providers/amazon-bedrock.d.ts.map +0 -1
  244. package/dist/providers/amazon-bedrock.js +0 -763
  245. package/dist/providers/amazon-bedrock.js.map +0 -1
  246. package/dist/providers/anthropic.d.ts +0 -71
  247. package/dist/providers/anthropic.d.ts.map +0 -1
  248. package/dist/providers/anthropic.js +0 -949
  249. package/dist/providers/anthropic.js.map +0 -1
  250. package/dist/providers/azure-openai-responses.d.ts +0 -15
  251. package/dist/providers/azure-openai-responses.d.ts.map +0 -1
  252. package/dist/providers/azure-openai-responses.js +0 -225
  253. package/dist/providers/azure-openai-responses.js.map +0 -1
  254. package/dist/providers/cloudflare.d.ts +0 -13
  255. package/dist/providers/cloudflare.d.ts.map +0 -1
  256. package/dist/providers/cloudflare.js +0 -26
  257. package/dist/providers/cloudflare.js.map +0 -1
  258. package/dist/providers/faux.d.ts +0 -56
  259. package/dist/providers/faux.d.ts.map +0 -1
  260. package/dist/providers/faux.js +0 -368
  261. package/dist/providers/faux.js.map +0 -1
  262. package/dist/providers/github-copilot-headers.d.ts +0 -8
  263. package/dist/providers/github-copilot-headers.d.ts.map +0 -1
  264. package/dist/providers/github-copilot-headers.js +0 -29
  265. package/dist/providers/github-copilot-headers.js.map +0 -1
  266. package/dist/providers/google-gemini-cli.d.ts +0 -74
  267. package/dist/providers/google-gemini-cli.d.ts.map +0 -1
  268. package/dist/providers/google-gemini-cli.js +0 -779
  269. package/dist/providers/google-gemini-cli.js.map +0 -1
  270. package/dist/providers/google-shared.d.ts +0 -70
  271. package/dist/providers/google-shared.d.ts.map +0 -1
  272. package/dist/providers/google-shared.js +0 -329
  273. package/dist/providers/google-shared.js.map +0 -1
  274. package/dist/providers/google-vertex.d.ts +0 -15
  275. package/dist/providers/google-vertex.d.ts.map +0 -1
  276. package/dist/providers/google-vertex.js +0 -442
  277. package/dist/providers/google-vertex.js.map +0 -1
  278. package/dist/providers/google.d.ts +0 -13
  279. package/dist/providers/google.d.ts.map +0 -1
  280. package/dist/providers/google.js +0 -400
  281. package/dist/providers/google.js.map +0 -1
  282. package/dist/providers/images/openrouter.d.ts +0 -3
  283. package/dist/providers/images/openrouter.d.ts.map +0 -1
  284. package/dist/providers/images/openrouter.js +0 -129
  285. package/dist/providers/images/openrouter.js.map +0 -1
  286. package/dist/providers/images/register-builtins.d.ts +0 -4
  287. package/dist/providers/images/register-builtins.d.ts.map +0 -1
  288. package/dist/providers/images/register-builtins.js +0 -34
  289. package/dist/providers/images/register-builtins.js.map +0 -1
  290. package/dist/providers/mistral.d.ts +0 -25
  291. package/dist/providers/mistral.d.ts.map +0 -1
  292. package/dist/providers/mistral.js +0 -535
  293. package/dist/providers/mistral.js.map +0 -1
  294. package/dist/providers/openai-codex-responses.d.ts +0 -30
  295. package/dist/providers/openai-codex-responses.d.ts.map +0 -1
  296. package/dist/providers/openai-codex-responses.js +0 -1090
  297. package/dist/providers/openai-codex-responses.js.map +0 -1
  298. package/dist/providers/openai-completions.d.ts +0 -19
  299. package/dist/providers/openai-completions.d.ts.map +0 -1
  300. package/dist/providers/openai-completions.js +0 -950
  301. package/dist/providers/openai-completions.js.map +0 -1
  302. package/dist/providers/openai-prompt-cache.d.ts +0 -3
  303. package/dist/providers/openai-prompt-cache.d.ts.map +0 -1
  304. package/dist/providers/openai-prompt-cache.js +0 -10
  305. package/dist/providers/openai-prompt-cache.js.map +0 -1
  306. package/dist/providers/openai-responses-shared.d.ts +0 -18
  307. package/dist/providers/openai-responses-shared.d.ts.map +0 -1
  308. package/dist/providers/openai-responses-shared.js +0 -492
  309. package/dist/providers/openai-responses-shared.js.map +0 -1
  310. package/dist/providers/openai-responses.d.ts +0 -13
  311. package/dist/providers/openai-responses.d.ts.map +0 -1
  312. package/dist/providers/openai-responses.js +0 -237
  313. package/dist/providers/openai-responses.js.map +0 -1
  314. package/dist/providers/register-builtins.d.ts +0 -38
  315. package/dist/providers/register-builtins.d.ts.map +0 -1
  316. package/dist/providers/register-builtins.js +0 -278
  317. package/dist/providers/register-builtins.js.map +0 -1
  318. package/dist/providers/simple-options.d.ts +0 -8
  319. package/dist/providers/simple-options.d.ts.map +0 -1
  320. package/dist/providers/simple-options.js +0 -41
  321. package/dist/providers/simple-options.js.map +0 -1
  322. package/dist/providers/transform-messages.d.ts +0 -8
  323. package/dist/providers/transform-messages.d.ts.map +0 -1
  324. package/dist/providers/transform-messages.js +0 -184
  325. package/dist/providers/transform-messages.js.map +0 -1
  326. package/dist/session-resources.d.ts +0 -4
  327. package/dist/session-resources.d.ts.map +0 -1
  328. package/dist/session-resources.js +0 -22
  329. package/dist/session-resources.js.map +0 -1
  330. package/dist/stream.d.ts +0 -8
  331. package/dist/stream.d.ts.map +0 -1
  332. package/dist/stream.js +0 -27
  333. package/dist/stream.js.map +0 -1
  334. package/dist/types.d.ts +0 -498
  335. package/dist/types.d.ts.map +0 -1
  336. package/dist/types.js +0 -2
  337. package/dist/types.js.map +0 -1
  338. package/dist/utils/diagnostics.d.ts +0 -19
  339. package/dist/utils/diagnostics.d.ts.map +0 -1
  340. package/dist/utils/diagnostics.js +0 -25
  341. package/dist/utils/diagnostics.js.map +0 -1
  342. package/dist/utils/event-stream.d.ts +0 -21
  343. package/dist/utils/event-stream.d.ts.map +0 -1
  344. package/dist/utils/event-stream.js +0 -81
  345. package/dist/utils/event-stream.js.map +0 -1
  346. package/dist/utils/hash.d.ts +0 -3
  347. package/dist/utils/hash.d.ts.map +0 -1
  348. package/dist/utils/hash.js +0 -14
  349. package/dist/utils/hash.js.map +0 -1
  350. package/dist/utils/headers.d.ts +0 -2
  351. package/dist/utils/headers.d.ts.map +0 -1
  352. package/dist/utils/headers.js +0 -8
  353. package/dist/utils/headers.js.map +0 -1
  354. package/dist/utils/json-parse.d.ts +0 -16
  355. package/dist/utils/json-parse.d.ts.map +0 -1
  356. package/dist/utils/json-parse.js +0 -113
  357. package/dist/utils/json-parse.js.map +0 -1
  358. package/dist/utils/node-http-proxy.d.ts +0 -10
  359. package/dist/utils/node-http-proxy.d.ts.map +0 -1
  360. package/dist/utils/node-http-proxy.js +0 -97
  361. package/dist/utils/node-http-proxy.js.map +0 -1
  362. package/dist/utils/oauth/anthropic.d.ts +0 -25
  363. package/dist/utils/oauth/anthropic.d.ts.map +0 -1
  364. package/dist/utils/oauth/anthropic.js +0 -335
  365. package/dist/utils/oauth/anthropic.js.map +0 -1
  366. package/dist/utils/oauth/device-code.d.ts +0 -19
  367. package/dist/utils/oauth/device-code.d.ts.map +0 -1
  368. package/dist/utils/oauth/device-code.js +0 -55
  369. package/dist/utils/oauth/device-code.js.map +0 -1
  370. package/dist/utils/oauth/github-copilot.d.ts +0 -30
  371. package/dist/utils/oauth/github-copilot.d.ts.map +0 -1
  372. package/dist/utils/oauth/github-copilot.js +0 -268
  373. package/dist/utils/oauth/github-copilot.js.map +0 -1
  374. package/dist/utils/oauth/google-antigravity.d.ts +0 -26
  375. package/dist/utils/oauth/google-antigravity.d.ts.map +0 -1
  376. package/dist/utils/oauth/google-antigravity.js +0 -377
  377. package/dist/utils/oauth/google-antigravity.js.map +0 -1
  378. package/dist/utils/oauth/google-gemini-cli.d.ts +0 -26
  379. package/dist/utils/oauth/google-gemini-cli.d.ts.map +0 -1
  380. package/dist/utils/oauth/google-gemini-cli.js +0 -482
  381. package/dist/utils/oauth/google-gemini-cli.js.map +0 -1
  382. package/dist/utils/oauth/index.d.ts +0 -63
  383. package/dist/utils/oauth/index.d.ts.map +0 -1
  384. package/dist/utils/oauth/index.js +0 -131
  385. package/dist/utils/oauth/index.js.map +0 -1
  386. package/dist/utils/oauth/oauth-page.d.ts +0 -3
  387. package/dist/utils/oauth/oauth-page.d.ts.map +0 -1
  388. package/dist/utils/oauth/oauth-page.js +0 -105
  389. package/dist/utils/oauth/oauth-page.js.map +0 -1
  390. package/dist/utils/oauth/openai-codex.d.ts +0 -34
  391. package/dist/utils/oauth/openai-codex.d.ts.map +0 -1
  392. package/dist/utils/oauth/openai-codex.js +0 -385
  393. package/dist/utils/oauth/openai-codex.js.map +0 -1
  394. package/dist/utils/oauth/pkce.d.ts +0 -13
  395. package/dist/utils/oauth/pkce.d.ts.map +0 -1
  396. package/dist/utils/oauth/pkce.js +0 -31
  397. package/dist/utils/oauth/pkce.js.map +0 -1
  398. package/dist/utils/oauth/types.d.ts +0 -64
  399. package/dist/utils/oauth/types.d.ts.map +0 -1
  400. package/dist/utils/oauth/types.js +0 -2
  401. package/dist/utils/oauth/types.js.map +0 -1
  402. package/dist/utils/overflow.d.ts +0 -56
  403. package/dist/utils/overflow.d.ts.map +0 -1
  404. package/dist/utils/overflow.js +0 -151
  405. package/dist/utils/overflow.js.map +0 -1
  406. package/dist/utils/sanitize-unicode.d.ts +0 -22
  407. package/dist/utils/sanitize-unicode.d.ts.map +0 -1
  408. package/dist/utils/sanitize-unicode.js +0 -26
  409. package/dist/utils/sanitize-unicode.js.map +0 -1
  410. package/dist/utils/typebox-helpers.d.ts +0 -17
  411. package/dist/utils/typebox-helpers.d.ts.map +0 -1
  412. package/dist/utils/typebox-helpers.js +0 -21
  413. package/dist/utils/typebox-helpers.js.map +0 -1
  414. package/dist/utils/validation.d.ts +0 -18
  415. package/dist/utils/validation.d.ts.map +0 -1
  416. package/dist/utils/validation.js +0 -281
  417. package/dist/utils/validation.js.map +0 -1
@@ -0,0 +1,683 @@
1
+ import { logger } from "@aryee337/aery-utils";
2
+ import { captureRequestHeaders, resolvePromptCacheKey } from "../auth-gateway/http";
3
+ import type {
4
+ AssistantMessage,
5
+ AssistantMessageEventStream,
6
+ Message,
7
+ RedactedThinkingContent,
8
+ StopReason,
9
+ TextContent,
10
+ ThinkingContent,
11
+ Tool,
12
+ ToolCall,
13
+ ToolResultMessage,
14
+ UserMessage,
15
+ } from "../types";
16
+ import {
17
+ type AnthropicAssistantContentBlock,
18
+ type AnthropicMessage,
19
+ type AnthropicSystem,
20
+ type AnthropicTool,
21
+ type AnthropicToolChoice,
22
+ type AnthropicToolResultContent,
23
+ type AnthropicUserContentBlock,
24
+ anthropicMessagesRequestSchema,
25
+ } from "./anthropic-messages-server-schema";
26
+
27
+ /**
28
+ * Anthropic Messages API (https://docs.anthropic.com/en/api/messages) ↔ aery-ai
29
+ * gateway translation. Inbound: foreign HTTP body → aery Context. Outbound:
30
+ * aery AssistantMessage[Stream] → Anthropic-shaped JSON / SSE.
31
+ */
32
+
33
+ import type { AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
34
+
35
+ export type { ParsedRequest };
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Inbound parsing
39
+ // ---------------------------------------------------------------------------
40
+
41
+ type ImageContentPart = { type: "image"; data: string; mimeType: string };
42
+
43
+ // Dedup noise from unknown-block-type warnings. Module-scoped so the warn
44
+ // fires once per (category, type) pair across the lifetime of the process.
45
+ const WARNED_UNKNOWN_BLOCK_TYPES = new Set<string>();
46
+ function warnUnknownBlockType(category: "user" | "assistant", blockType: string): void {
47
+ const key = `${category}:${blockType}`;
48
+ if (WARNED_UNKNOWN_BLOCK_TYPES.has(key)) return;
49
+ WARNED_UNKNOWN_BLOCK_TYPES.add(key);
50
+ logger.warn("anthropic-messages: unknown content block flattened to text placeholder", {
51
+ category,
52
+ blockType,
53
+ });
54
+ }
55
+
56
+ // aery-ai's `ImageContent` only carries base64 + mimeType. When the inbound
57
+ // uses `url` or `file_id` sources we surface a text placeholder so the
58
+ // downstream provider still sees a sane history; warn once per source kind.
59
+ const WARNED_NON_BASE64_IMAGE_SOURCES = new Set<string>();
60
+ function warnNonBase64ImageSource(sourceType: string): void {
61
+ if (WARNED_NON_BASE64_IMAGE_SOURCES.has(sourceType)) return;
62
+ WARNED_NON_BASE64_IMAGE_SOURCES.add(sourceType);
63
+ logger.warn(
64
+ "anthropic-messages: image source surfaced as text placeholder (aery-ai ImageContent lacks URL channel)",
65
+ {
66
+ sourceType,
67
+ },
68
+ );
69
+ }
70
+
71
+ // Compact, log-safe stringification for unknown content blocks. Keeps the
72
+ // placeholder informative without dumping multi-KB structures into history.
73
+ function describeUnknownBlock(block: { type: string }): string {
74
+ try {
75
+ const json = JSON.stringify(block);
76
+ if (json !== undefined && json.length <= 200) return `[${block.type}: ${json}]`;
77
+ } catch {
78
+ // fall through
79
+ }
80
+ return `[${block.type}]`;
81
+ }
82
+
83
+ function buildSystemPrompt(raw: AnthropicSystem): string[] | undefined {
84
+ if (raw === undefined) return undefined;
85
+ if (typeof raw === "string") return raw.length > 0 ? [raw] : undefined;
86
+ const parts = raw.map(block => block.text).filter(text => text.length > 0);
87
+ return parts.length > 0 ? [parts.join("\n\n")] : undefined;
88
+ }
89
+
90
+ function makeUserMessage(parts: (TextContent | ImageContentPart)[], timestamp: number): UserMessage {
91
+ return {
92
+ role: "user",
93
+ content: parts.length === 1 && parts[0].type === "text" ? parts[0].text : parts,
94
+ timestamp,
95
+ };
96
+ }
97
+
98
+ function toolResultPartsFromBlocks(
99
+ content: AnthropicToolResultContent[] | string | undefined,
100
+ ): (TextContent | ImageContentPart)[] {
101
+ if (content === undefined) return [];
102
+ if (typeof content === "string") return [{ type: "text", text: content }];
103
+ const out: (TextContent | ImageContentPart)[] = [];
104
+ for (const block of content) {
105
+ if (block.type === "text") {
106
+ out.push({ type: "text", text: block.text });
107
+ continue;
108
+ }
109
+ // block.type === "image" — schema only accepts base64 sources.
110
+ if (block.source.type === "base64") {
111
+ out.push({ type: "image", data: block.source.data, mimeType: block.source.media_type });
112
+ }
113
+ }
114
+ return out;
115
+ }
116
+
117
+ function walkUserContent(
118
+ blocks: string | AnthropicUserContentBlock[],
119
+ timestamp: number,
120
+ ): (UserMessage | ToolResultMessage)[] {
121
+ const messages: (UserMessage | ToolResultMessage)[] = [];
122
+ const userParts: (TextContent | ImageContentPart)[] = [];
123
+ const flush = () => {
124
+ if (userParts.length === 0) return;
125
+ messages.push(makeUserMessage(userParts.splice(0), timestamp));
126
+ };
127
+ if (typeof blocks === "string") {
128
+ if (blocks.length > 0) userParts.push({ type: "text", text: blocks });
129
+ flush();
130
+ return messages;
131
+ }
132
+ for (const block of blocks) {
133
+ if (block.type === "text") {
134
+ userParts.push({ type: "text", text: block.text });
135
+ } else if (block.type === "image") {
136
+ // SDK's typed source covers base64+url; our schema also accepts the
137
+ // forward-compat `file` variant. Narrow against a widened shape so
138
+ // every variant is handled at runtime regardless of SDK lag.
139
+ const source = block.source as {
140
+ type: string;
141
+ data?: string;
142
+ media_type?: string;
143
+ url?: string;
144
+ file_id?: string;
145
+ };
146
+ if (source.type === "base64" && source.data && source.media_type) {
147
+ userParts.push({ type: "image", data: source.data, mimeType: source.media_type });
148
+ } else {
149
+ warnNonBase64ImageSource(source.type);
150
+ const ref =
151
+ source.type === "url" ? (source.url ?? "") : source.type === "file" ? (source.file_id ?? "") : "";
152
+ userParts.push({ type: "text", text: `[image: ${ref}]` });
153
+ }
154
+ } else if (block.type === "tool_result") {
155
+ // Anthropic permits tool_result blocks to follow plain text/image
156
+ // siblings in the same user message. aery-ai's history is a flat
157
+ // sequence of typed messages, so flush the accumulated parts as a
158
+ // separate UserMessage before emitting the ToolResultMessage.
159
+ flush();
160
+ messages.push({
161
+ role: "toolResult",
162
+ toolCallId: block.tool_use_id,
163
+ // Anthropic tool_results don't carry the tool name; downstream can rehydrate.
164
+ toolName: "",
165
+ content: toolResultPartsFromBlocks(block.content as AnthropicToolResultContent[] | string | undefined),
166
+ isError: block.is_error === true,
167
+ timestamp,
168
+ });
169
+ } else {
170
+ // Unknown variant (server_tool_use, mcp_*, document, web_search_tool_result,
171
+ // container_upload, code_execution_*, …). Flatten to a text placeholder
172
+ // so the downstream provider still gets a coherent transcript.
173
+ const unknown = block as { type: string };
174
+ warnUnknownBlockType("user", unknown.type);
175
+ userParts.push({ type: "text", text: describeUnknownBlock(unknown) });
176
+ }
177
+ }
178
+ flush();
179
+ return messages;
180
+ }
181
+
182
+ function walkAssistantContent(
183
+ blocks: string | AnthropicAssistantContentBlock[],
184
+ ): (TextContent | ThinkingContent | RedactedThinkingContent | ToolCall)[] {
185
+ const out: (TextContent | ThinkingContent | RedactedThinkingContent | ToolCall)[] = [];
186
+ if (typeof blocks === "string") {
187
+ if (blocks.length > 0) out.push({ type: "text", text: blocks });
188
+ return out;
189
+ }
190
+ for (const block of blocks) {
191
+ switch (block.type) {
192
+ case "text":
193
+ out.push({ type: "text", text: block.text });
194
+ break;
195
+ case "thinking": {
196
+ const tc: ThinkingContent = { type: "thinking", thinking: block.thinking };
197
+ if (block.signature !== undefined) tc.thinkingSignature = block.signature;
198
+ out.push(tc);
199
+ break;
200
+ }
201
+ case "redacted_thinking":
202
+ out.push({ type: "redactedThinking", data: block.data });
203
+ break;
204
+ case "tool_use":
205
+ out.push({
206
+ type: "toolCall",
207
+ id: block.id,
208
+ name: block.name,
209
+ arguments: block.input ?? {},
210
+ });
211
+ break;
212
+ default: {
213
+ // Unknown assistant variant (server_tool_use, mcp_tool_use, …).
214
+ // Flatten to a text placeholder; warn once per unknown type.
215
+ const unknown = block as { type: string };
216
+ warnUnknownBlockType("assistant", unknown.type);
217
+ out.push({ type: "text", text: describeUnknownBlock(unknown) });
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ return out;
223
+ }
224
+
225
+ function walkTools(tools: AnthropicTool[] | undefined): Tool[] | undefined {
226
+ if (!tools) return undefined;
227
+ return tools.map(tool => ({
228
+ name: tool.name,
229
+ description: tool.description ?? "",
230
+ parameters: tool.input_schema as Record<string, unknown>,
231
+ }));
232
+ }
233
+
234
+ function mapToolChoice(choice: AnthropicToolChoice | undefined): ParsedRequest["options"]["toolChoice"] {
235
+ if (!choice) return undefined;
236
+ switch (choice.type) {
237
+ case "auto":
238
+ return "auto";
239
+ case "any":
240
+ return "required";
241
+ case "none":
242
+ return "none";
243
+ case "tool":
244
+ return { name: choice.name };
245
+ }
246
+ }
247
+
248
+ type AnthropicCacheControl = { type: "ephemeral"; ttl?: "1h" | "5m" };
249
+ type HasCacheControl = { cache_control?: AnthropicCacheControl };
250
+
251
+ function readCacheControl(value: unknown): AnthropicCacheControl | undefined {
252
+ if (value === null || typeof value !== "object") return undefined;
253
+ const cc = (value as HasCacheControl).cache_control;
254
+ if (!cc || typeof cc !== "object" || cc.type !== "ephemeral") return undefined;
255
+ return cc;
256
+ }
257
+
258
+ /**
259
+ * Anthropic clients annotate caching breakpoints per block via
260
+ * `cache_control: { type: "ephemeral", ttl?: "1h"|"5m" }`. aery-ai's
261
+ * `cacheRetention` is per-request, not per-block, and its anthropic provider
262
+ * re-applies breakpoints itself on the rebuilt outbound wire. Scan every
263
+ * block once and return the strongest retention requested: any `ttl: "1h"`
264
+ * promotes the request to "long", anything else ephemeral maps to "short".
265
+ */
266
+ function deriveCacheRetention(data: {
267
+ system?: unknown;
268
+ messages: readonly unknown[];
269
+ tools?: readonly unknown[];
270
+ }): "short" | "long" | undefined {
271
+ let strongest: "short" | "long" | undefined;
272
+ const visit = (cc: AnthropicCacheControl | undefined): void => {
273
+ if (!cc) return;
274
+ if (cc.ttl === "1h") strongest = "long";
275
+ else strongest ??= "short";
276
+ };
277
+ if (Array.isArray(data.system)) {
278
+ for (const block of data.system) visit(readCacheControl(block));
279
+ }
280
+ for (const message of data.messages) {
281
+ if (message === null || typeof message !== "object") continue;
282
+ const content = (message as { content?: unknown }).content;
283
+ if (!Array.isArray(content)) continue;
284
+ for (const block of content) visit(readCacheControl(block));
285
+ }
286
+ if (data.tools) {
287
+ for (const tool of data.tools) visit(readCacheControl(tool));
288
+ }
289
+ return strongest;
290
+ }
291
+
292
+ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
293
+ const parsed = anthropicMessagesRequestSchema.safeParse(body);
294
+ if (!parsed.success) {
295
+ throw new Error(`anthropic-messages: ${parsed.error.message}`);
296
+ }
297
+ const data = parsed.data;
298
+
299
+ const now = Date.now();
300
+ const messages: Message[] = [];
301
+ for (const message of data.messages as AnthropicMessage[]) {
302
+ if (message.role === "user") {
303
+ for (const m of walkUserContent(message.content, now)) messages.push(m);
304
+ } else {
305
+ const assistant: AssistantMessage = {
306
+ role: "assistant",
307
+ content: walkAssistantContent(message.content),
308
+ api: "anthropic-messages",
309
+ provider: "anthropic",
310
+ model: data.model,
311
+ usage: emptyUsage(),
312
+ stopReason: "stop",
313
+ timestamp: now,
314
+ };
315
+ messages.push(assistant);
316
+ }
317
+ }
318
+
319
+ const options: ParsedRequest["options"] = {
320
+ maxOutputTokens: data.max_tokens,
321
+ };
322
+ if (data.temperature !== undefined) options.temperature = data.temperature;
323
+ if (data.top_p !== undefined) options.topP = data.top_p;
324
+ if (data.top_k !== undefined) options.topK = data.top_k;
325
+ if (data.stop_sequences) options.stopSequences = data.stop_sequences;
326
+ const toolChoice = mapToolChoice(data.tool_choice as AnthropicToolChoice | undefined);
327
+ if (toolChoice !== undefined) options.toolChoice = toolChoice;
328
+ // `disable_parallel_tool_use === true` means the client wants the model to
329
+ // emit at most one tool call per turn; map to aery-ai's negated boolean.
330
+ // Leave undefined when the field is absent or explicitly `false` so we
331
+ // don't override provider defaults.
332
+ if (data.tool_choice?.disable_parallel_tool_use === true) {
333
+ options.parallelToolCalls = false;
334
+ }
335
+ if (data.thinking) {
336
+ switch (data.thinking.type) {
337
+ case "enabled":
338
+ options.explicitThinkingBudgetTokens = data.thinking.budget_tokens;
339
+ break;
340
+ case "disabled":
341
+ options.disableReasoning = true;
342
+ break;
343
+ case "adaptive":
344
+ if (data.thinking.budget_tokens !== undefined) {
345
+ options.explicitThinkingBudgetTokens = data.thinking.budget_tokens;
346
+ }
347
+ break;
348
+ }
349
+ }
350
+ if (data.output_config?.task_budget) {
351
+ options.taskBudget = data.output_config.task_budget;
352
+ }
353
+ const cacheRetention = deriveCacheRetention(data);
354
+ if (cacheRetention !== undefined) options.cacheRetention = cacheRetention;
355
+ // Anthropic clients commonly send `metadata: { user_id }`; forward verbatim
356
+ // so downstream providers (and our anthropic-passthrough fast-path) can
357
+ // preserve abuse-tracking signal.
358
+ if (data.metadata !== undefined) {
359
+ options.metadata = data.metadata as Record<string, unknown>;
360
+ }
361
+ const cacheKey = resolvePromptCacheKey(body, headers);
362
+ if (cacheKey !== undefined) options.promptCacheKey = cacheKey;
363
+ // Allow-listed header capture. The gateway's `handleFormatEndpoint`
364
+ // already merges its own pre-capture under whatever the parser sets, but
365
+ // we populate here too so direct callers of `parseRequest` (tests, custom
366
+ // wrappers) see the same surface. `anthropic-version` is the most
367
+ // load-bearing — some downstream Anthropic-API targets reject requests
368
+ // missing it.
369
+ if (headers) {
370
+ const captured = captureRequestHeaders(headers);
371
+ if (Object.keys(captured).length > 0) options.headers = captured;
372
+ }
373
+
374
+ return {
375
+ modelId: data.model,
376
+ context: {
377
+ systemPrompt: buildSystemPrompt(data.system as AnthropicSystem),
378
+ messages,
379
+ tools: walkTools(data.tools as AnthropicTool[] | undefined),
380
+ },
381
+ stream: data.stream === true,
382
+ options,
383
+ };
384
+ }
385
+
386
+ function emptyUsage(): AssistantMessage["usage"] {
387
+ return {
388
+ input: 0,
389
+ output: 0,
390
+ cacheRead: 0,
391
+ cacheWrite: 0,
392
+ totalTokens: 0,
393
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
394
+ };
395
+ }
396
+
397
+ // ---------------------------------------------------------------------------
398
+ // Outbound encoding
399
+ // ---------------------------------------------------------------------------
400
+
401
+ function newMessageId(): string {
402
+ const hex = (globalThis.crypto?.randomUUID?.() ?? randomFallback()).replace(/-/g, "").slice(0, 24);
403
+ return `msg_${hex}`;
404
+ }
405
+
406
+ function randomFallback(): string {
407
+ // Sufficient for tests / environments without crypto.randomUUID
408
+ const buf = new Uint8Array(16);
409
+ for (let i = 0; i < 16; i++) buf[i] = Math.floor(Math.random() * 256);
410
+ const hex = Array.from(buf, b => b.toString(16).padStart(2, "0")).join("");
411
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
412
+ }
413
+
414
+ function mapStopReasonOut(reason: StopReason): "end_turn" | "max_tokens" | "tool_use" {
415
+ switch (reason) {
416
+ case "length":
417
+ return "max_tokens";
418
+ case "toolUse":
419
+ return "tool_use";
420
+ default:
421
+ return "end_turn";
422
+ }
423
+ }
424
+
425
+ function encodeContentBlocks(message: AssistantMessage): Record<string, unknown>[] {
426
+ const blocks: Record<string, unknown>[] = [];
427
+ for (const c of message.content) {
428
+ switch (c.type) {
429
+ case "text":
430
+ blocks.push({ type: "text", text: c.text });
431
+ break;
432
+ case "thinking": {
433
+ const b: Record<string, unknown> = { type: "thinking", thinking: c.thinking };
434
+ if (c.thinkingSignature) b.signature = c.thinkingSignature;
435
+ blocks.push(b);
436
+ break;
437
+ }
438
+ case "redactedThinking":
439
+ blocks.push({ type: "redacted_thinking", data: c.data });
440
+ break;
441
+ case "toolCall":
442
+ blocks.push({ type: "tool_use", id: c.id, name: c.name, input: c.arguments ?? {} });
443
+ break;
444
+ }
445
+ }
446
+ return blocks;
447
+ }
448
+
449
+ function encodeUsage(message: AssistantMessage): Record<string, unknown> {
450
+ return {
451
+ input_tokens: message.usage.input,
452
+ output_tokens: message.usage.output,
453
+ cache_read_input_tokens: message.usage.cacheRead,
454
+ cache_creation_input_tokens: message.usage.cacheWrite,
455
+ };
456
+ }
457
+
458
+ export function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown> {
459
+ if (message.stopReason === "error" || message.stopReason === "aborted") {
460
+ throw new Error(message.errorMessage ?? `anthropic-messages: upstream ${message.stopReason}`);
461
+ }
462
+ return {
463
+ id: message.responseId ?? newMessageId(),
464
+ type: "message",
465
+ role: "assistant",
466
+ model: requestedModelId,
467
+ content: encodeContentBlocks(message),
468
+ stop_reason: mapStopReasonOut(message.stopReason),
469
+ // TODO: surface the matched stop sequence once aery-ai's
470
+ // `AssistantMessage.stopReason` carries the matched string. Intentionally
471
+ // `null` for now (Anthropic schema allows it).
472
+ stop_sequence: null,
473
+ usage: encodeUsage(message),
474
+ };
475
+ }
476
+
477
+ // ---------------------------------------------------------------------------
478
+ // Streaming encoder
479
+ // ---------------------------------------------------------------------------
480
+
481
+ const ENCODER = new TextEncoder();
482
+
483
+ function sseFrame(event: string, data: Record<string, unknown>): Uint8Array {
484
+ return ENCODER.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
485
+ }
486
+
487
+ type BlockKind = "text" | "thinking" | "tool_use";
488
+
489
+ interface OpenBlock {
490
+ index: number;
491
+ kind: BlockKind;
492
+ }
493
+
494
+ export function encodeStream(
495
+ events: AssistantMessageEventStream,
496
+ requestedModelId: string,
497
+ ): ReadableStream<Uint8Array> {
498
+ return new ReadableStream<Uint8Array>({
499
+ async start(controller) {
500
+ const messageId = newMessageId();
501
+ let started = false;
502
+ const open = new Map<number, OpenBlock>();
503
+
504
+ const ensureStart = (partial: AssistantMessage) => {
505
+ if (started) return;
506
+ started = true;
507
+ controller.enqueue(
508
+ sseFrame("message_start", {
509
+ type: "message_start",
510
+ message: {
511
+ id: messageId,
512
+ type: "message",
513
+ role: "assistant",
514
+ model: requestedModelId,
515
+ content: [],
516
+ stop_reason: null,
517
+ // TODO: same as encodeResponse — surface matched stop sequence
518
+ // once aery-ai propagates it.
519
+ stop_sequence: null,
520
+ usage: encodeUsage(partial),
521
+ },
522
+ }),
523
+ );
524
+ };
525
+
526
+ const closeBlock = (index: number) => {
527
+ if (!open.has(index)) return;
528
+ controller.enqueue(sseFrame("content_block_stop", { type: "content_block_stop", index }));
529
+ open.delete(index);
530
+ };
531
+
532
+ try {
533
+ for await (const ev of events) {
534
+ switch (ev.type) {
535
+ case "start":
536
+ ensureStart(ev.partial);
537
+ break;
538
+ case "text_start": {
539
+ ensureStart(ev.partial);
540
+ open.set(ev.contentIndex, { index: ev.contentIndex, kind: "text" });
541
+ controller.enqueue(
542
+ sseFrame("content_block_start", {
543
+ type: "content_block_start",
544
+ index: ev.contentIndex,
545
+ content_block: { type: "text", text: "" },
546
+ }),
547
+ );
548
+ break;
549
+ }
550
+ case "text_delta":
551
+ controller.enqueue(
552
+ sseFrame("content_block_delta", {
553
+ type: "content_block_delta",
554
+ index: ev.contentIndex,
555
+ delta: { type: "text_delta", text: ev.delta },
556
+ }),
557
+ );
558
+ break;
559
+ case "text_end":
560
+ closeBlock(ev.contentIndex);
561
+ break;
562
+ case "thinking_start": {
563
+ ensureStart(ev.partial);
564
+ open.set(ev.contentIndex, { index: ev.contentIndex, kind: "thinking" });
565
+ controller.enqueue(
566
+ sseFrame("content_block_start", {
567
+ type: "content_block_start",
568
+ index: ev.contentIndex,
569
+ content_block: { type: "thinking", thinking: "" },
570
+ }),
571
+ );
572
+ break;
573
+ }
574
+ case "thinking_delta":
575
+ controller.enqueue(
576
+ sseFrame("content_block_delta", {
577
+ type: "content_block_delta",
578
+ index: ev.contentIndex,
579
+ delta: { type: "thinking_delta", thinking: ev.delta },
580
+ }),
581
+ );
582
+ break;
583
+ case "thinking_end": {
584
+ const c = ev.partial.content[ev.contentIndex];
585
+ if (c?.type === "thinking" && c.thinkingSignature) {
586
+ controller.enqueue(
587
+ sseFrame("content_block_delta", {
588
+ type: "content_block_delta",
589
+ index: ev.contentIndex,
590
+ delta: { type: "signature_delta", signature: c.thinkingSignature },
591
+ }),
592
+ );
593
+ }
594
+ closeBlock(ev.contentIndex);
595
+ break;
596
+ }
597
+ case "toolcall_start": {
598
+ ensureStart(ev.partial);
599
+ const tc = ev.partial.content[ev.contentIndex] as ToolCall | undefined;
600
+ open.set(ev.contentIndex, { index: ev.contentIndex, kind: "tool_use" });
601
+ controller.enqueue(
602
+ sseFrame("content_block_start", {
603
+ type: "content_block_start",
604
+ index: ev.contentIndex,
605
+ content_block: {
606
+ type: "tool_use",
607
+ id: tc?.id ?? "",
608
+ name: tc?.name ?? "",
609
+ input: {},
610
+ },
611
+ }),
612
+ );
613
+ break;
614
+ }
615
+ case "toolcall_delta":
616
+ controller.enqueue(
617
+ sseFrame("content_block_delta", {
618
+ type: "content_block_delta",
619
+ index: ev.contentIndex,
620
+ delta: { type: "input_json_delta", partial_json: ev.delta },
621
+ }),
622
+ );
623
+ break;
624
+ case "toolcall_end":
625
+ closeBlock(ev.contentIndex);
626
+ break;
627
+ case "done": {
628
+ for (const idx of [...open.keys()]) closeBlock(idx);
629
+ controller.enqueue(
630
+ sseFrame("message_delta", {
631
+ type: "message_delta",
632
+ // TODO: surface matched stop sequence once aery-ai
633
+ // propagates it on the `done` event.
634
+ delta: { stop_reason: mapStopReasonOut(ev.reason), stop_sequence: null },
635
+ usage: encodeUsage(ev.message),
636
+ }),
637
+ );
638
+ controller.enqueue(sseFrame("message_stop", { type: "message_stop" }));
639
+ controller.close();
640
+ return;
641
+ }
642
+ case "error": {
643
+ const msg = ev.error.errorMessage ?? "stream error";
644
+ controller.enqueue(
645
+ sseFrame("error", { type: "error", error: { type: "api_error", message: msg } }),
646
+ );
647
+ controller.close();
648
+ return;
649
+ }
650
+ }
651
+ }
652
+ // stream ended without explicit done; close gracefully
653
+ for (const idx of [...open.keys()]) closeBlock(idx);
654
+ controller.enqueue(sseFrame("message_stop", { type: "message_stop" }));
655
+ controller.close();
656
+ } catch (err) {
657
+ controller.enqueue(
658
+ sseFrame("error", {
659
+ type: "error",
660
+ error: { type: "api_error", message: err instanceof Error ? err.message : String(err) },
661
+ }),
662
+ );
663
+ controller.close();
664
+ }
665
+ },
666
+ });
667
+ }
668
+
669
+ // ---------------------------------------------------------------------------
670
+ // Error envelope
671
+ // ---------------------------------------------------------------------------
672
+
673
+ /**
674
+ * Anthropic error envelope: `{ type: "error", error: { type, message } }`.
675
+ * See https://docs.anthropic.com/en/api/errors. Returned as a `Response` so
676
+ * the gateway can hand it straight back to the client without extra wrapping.
677
+ */
678
+ export function formatError(status: number, type: string, message: string): Response {
679
+ return new Response(JSON.stringify({ type: "error", error: { type, message } }), {
680
+ status,
681
+ headers: { "Content-Type": "application/json" },
682
+ });
683
+ }