@aryee337/aery-ai 0.2.27 → 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,612 @@
1
+ /**
2
+ * Streaming-safe filters for leaked chat-template tool-call and thinking markup.
3
+ *
4
+ * Hosted models sometimes leak raw template markup into visible `content` instead
5
+ * of returning structured events. One `StreamMarkupHealing` instance owns one stream
6
+ * and one grammar selected by options:
7
+ *
8
+ * - `kimi`: Kimi K2 `<|tool_calls_section_begin|>` sections.
9
+ * - `dsml`: DeepSeek `<|DSML|tool_calls>` envelopes.
10
+ * - `thinking`: plain `<think>` / `<thinking>` blocks used by MiniMax-style streams.
11
+ *
12
+ * The parser strips marker bytes, reconstructs embedded calls, emits thinking
13
+ * deltas for thinking blocks, and holds partial tags across chunk boundaries.
14
+ */
15
+
16
+ import { parseJsonWithRepair } from "./json-parse";
17
+
18
+ const KIMI_SECTION_BEGIN = "<|tool_calls_section_begin|>";
19
+ const KIMI_SECTION_END = "<|tool_calls_section_end|>";
20
+ const KIMI_CALL_BEGIN = "<|tool_call_begin|>";
21
+ const KIMI_CALL_END = "<|tool_call_end|>";
22
+ const KIMI_ARG_BEGIN = "<|tool_call_argument_begin|>";
23
+ const KIMI_TOKENS = [KIMI_SECTION_BEGIN, KIMI_SECTION_END, KIMI_CALL_BEGIN, KIMI_CALL_END, KIMI_ARG_BEGIN] as const;
24
+
25
+ /** Maximum buffered Kimi partial-token length before giving up holdback. */
26
+ const MAX_KIMI_PARTIAL_HOLD = 64;
27
+
28
+ /** Both fullwidth (U+FF5C) and ASCII pipes are observed in DeepSeek DSML leaks. */
29
+ const DSML_PIPE = "[||]";
30
+ const DSML_TOOL_CALLS_OPEN_RE = new RegExp(`<${DSML_PIPE}DSML${DSML_PIPE}tool_calls>`, "y");
31
+ const DSML_TOOL_CALLS_CLOSE_RE = new RegExp(`</${DSML_PIPE}DSML${DSML_PIPE}tool_calls>`, "y");
32
+ const DSML_INVOKE_OPEN_RE = new RegExp(`<${DSML_PIPE}DSML${DSML_PIPE}invoke\\s+name="([^"]*)"\\s*>`, "y");
33
+ const DSML_INVOKE_CLOSE_RE = new RegExp(`</${DSML_PIPE}DSML${DSML_PIPE}invoke>`, "y");
34
+ const DSML_PARAMETER_OPEN_RE = new RegExp(
35
+ `<${DSML_PIPE}DSML${DSML_PIPE}parameter\\s+name="([^"]*)"(?:\\s+string="(true|false)")?\\s*>`,
36
+ "y",
37
+ );
38
+ const DSML_PARAMETER_CLOSE_RE = new RegExp(`</${DSML_PIPE}DSML${DSML_PIPE}parameter>`, "y");
39
+
40
+ const THINK_OPEN = "<think>";
41
+ const THINK_CLOSE = "</think>";
42
+ const THINKING_OPEN = "<thinking>";
43
+ const THINKING_CLOSE = "</thinking>";
44
+
45
+ const PLAIN_THINKING_TAGS = [
46
+ { open: THINK_OPEN, close: THINK_CLOSE },
47
+ { open: THINKING_OPEN, close: THINKING_CLOSE },
48
+ ] as const;
49
+
50
+ /** Cap held-back XML tag bytes so a stray `<` in prose cannot grow unboundedly. */
51
+ const MAX_XML_PARTIAL_HOLD = 256;
52
+
53
+ /** Maximum parameter bytes to accumulate before abandoning a pathological XML call. */
54
+ const MAX_XML_PARAM_VALUE_LENGTH = 1_000_000;
55
+
56
+ export interface HealedToolCall {
57
+ readonly id: string;
58
+ readonly name: string;
59
+ readonly arguments: string;
60
+ }
61
+
62
+ export type StreamMarkupHealingPattern = "kimi" | "dsml" | "thinking";
63
+
64
+ export interface StreamMarkupHealingOptions {
65
+ readonly pattern: StreamMarkupHealingPattern;
66
+ }
67
+
68
+ export type StreamMarkupHealingEvent =
69
+ | { readonly type: "text"; readonly text: string }
70
+ | { readonly type: "thinking"; readonly thinking: string }
71
+ | { readonly type: "toolCall"; readonly call: HealedToolCall };
72
+
73
+ type XmlToolState =
74
+ | { readonly kind: "idle" }
75
+ | { readonly kind: "section" }
76
+ | { readonly kind: "invoke"; readonly name: string; readonly args: Record<string, unknown> }
77
+ | {
78
+ readonly kind: "parameter";
79
+ readonly invokeName: string;
80
+ readonly args: Record<string, unknown>;
81
+ readonly paramName: string;
82
+ readonly isString: boolean;
83
+ value: string;
84
+ };
85
+
86
+ type ThinkingTag = { readonly open: string; readonly close: string };
87
+
88
+ /**
89
+ * State machine that consumes streamed visible text and emits cleaned text,
90
+ * thinking deltas, and reconstructed tool calls.
91
+ *
92
+ * Feed only one stream channel (usually `delta.content` / `message.content`).
93
+ * Mixing reasoning and visible text into the same instance can corrupt the
94
+ * held-back partial tag buffer.
95
+ */
96
+ export class StreamMarkupHealing {
97
+ readonly #pattern: StreamMarkupHealingPattern;
98
+ #buffer = "";
99
+ #offset = 0;
100
+
101
+ #kimiInSection = false;
102
+ #kimiInCall = false;
103
+ #kimiInArgs = false;
104
+ #kimiPendingId = "";
105
+ #kimiPendingArgs = "";
106
+
107
+ #xmlState: XmlToolState = { kind: "idle" };
108
+ #thinkingCloseTag = "";
109
+ #sectionTerminated = false;
110
+ readonly #completed: HealedToolCall[] = [];
111
+
112
+ constructor(options: StreamMarkupHealingOptions) {
113
+ this.#pattern = options.pattern;
114
+ }
115
+
116
+ get pattern(): StreamMarkupHealingPattern {
117
+ return this.#pattern;
118
+ }
119
+
120
+ /**
121
+ * Feed a chunk and return visible text only. Reconstructed tool calls are
122
+ * stored for {@link drainCompleted}; thinking blocks are intentionally not
123
+ * returned by this compatibility helper. Use {@link feedEvents} when the
124
+ * caller needs ordered text/thinking/tool-call events.
125
+ */
126
+ feed(text: string): string {
127
+ let clean = "";
128
+ for (const event of this.feedEvents(text)) {
129
+ if (event.type === "text") {
130
+ clean += event.text;
131
+ } else if (event.type === "toolCall") {
132
+ this.#completed.push(event.call);
133
+ }
134
+ }
135
+ return clean;
136
+ }
137
+
138
+ /** Feed a chunk and return cleaned text/thinking/tool-call events in stream order. */
139
+ feedEvents(text: string): StreamMarkupHealingEvent[] {
140
+ if (text.length === 0) return [];
141
+ this.#compact();
142
+ this.#buffer += text;
143
+ switch (this.#pattern) {
144
+ case "kimi":
145
+ return this.#consumeKimiEvents();
146
+ case "dsml":
147
+ return this.#consumeDsmlEvents();
148
+ case "thinking":
149
+ return this.#consumePlainThinkingEvents();
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Like {@link feed}, but discards completed calls. Used when the upstream
155
+ * chunk also carries structured `tool_calls`, keeping that structured payload
156
+ * as the single source of truth.
157
+ */
158
+ consumeWithoutCalls(text: string): string {
159
+ let clean = "";
160
+ for (const event of this.feedEvents(text)) {
161
+ if (event.type === "text") clean += event.text;
162
+ }
163
+ return clean;
164
+ }
165
+
166
+ /** Drain accumulated tool calls from calls to {@link feed}. */
167
+ drainCompleted(): HealedToolCall[] {
168
+ if (this.#completed.length === 0) return [];
169
+ return this.#completed.splice(0, this.#completed.length);
170
+ }
171
+
172
+ /**
173
+ * Flush held-back stream-end fragments as ordered events. Partial tool-call
174
+ * sections/envelopes are dropped; unterminated thinking blocks are emitted as
175
+ * thinking, matching the previous MiniMax parser behavior.
176
+ */
177
+ flushEvents(): StreamMarkupHealingEvent[] {
178
+ const tail = this.#remaining();
179
+ this.#buffer = "";
180
+ this.#offset = 0;
181
+
182
+ switch (this.#pattern) {
183
+ case "kimi": {
184
+ const inTemplate = this.#kimiInCall || this.#kimiInSection;
185
+ this.#resetKimi();
186
+ return inTemplate || tail.length === 0 ? [] : [{ type: "text", text: tail }];
187
+ }
188
+ case "dsml": {
189
+ const state = this.#xmlState;
190
+ this.#xmlState = { kind: "idle" };
191
+ return state.kind !== "idle" || tail.length === 0 ? [] : [{ type: "text", text: tail }];
192
+ }
193
+ case "thinking": {
194
+ const closeTag = this.#thinkingCloseTag;
195
+ this.#thinkingCloseTag = "";
196
+ if (tail.length === 0) return [];
197
+ return closeTag ? [{ type: "thinking", thinking: tail }] : [{ type: "text", text: tail }];
198
+ }
199
+ }
200
+ }
201
+
202
+ /** Flush held-back text only. Reconstructed calls are retained for {@link drainCompleted}. */
203
+ flushPending(): string {
204
+ let clean = "";
205
+ for (const event of this.flushEvents()) {
206
+ if (event.type === "text") {
207
+ clean += event.text;
208
+ } else if (event.type === "toolCall") {
209
+ this.#completed.push(event.call);
210
+ }
211
+ }
212
+ return clean;
213
+ }
214
+
215
+ /** True once any configured tool-call section/envelope has fully closed. */
216
+ get sectionClosed(): boolean {
217
+ return this.#sectionTerminated;
218
+ }
219
+
220
+ #remaining(): string {
221
+ return this.#offset === 0 ? this.#buffer : this.#buffer.slice(this.#offset);
222
+ }
223
+
224
+ #compact(): void {
225
+ if (this.#offset === 0) return;
226
+ this.#buffer = this.#buffer.slice(this.#offset);
227
+ this.#offset = 0;
228
+ }
229
+
230
+ #consumeKimiEvents(): StreamMarkupHealingEvent[] {
231
+ const events: StreamMarkupHealingEvent[] = [];
232
+ let clean = "";
233
+ const flushClean = (): void => {
234
+ if (clean.length === 0) return;
235
+ events.push({ type: "text", text: clean });
236
+ clean = "";
237
+ };
238
+
239
+ while (this.#offset < this.#buffer.length) {
240
+ if (this.#startsWithPartialToken(KIMI_TOKENS, MAX_KIMI_PARTIAL_HOLD)) break;
241
+
242
+ if (this.#matchesToken(KIMI_SECTION_BEGIN)) {
243
+ this.#kimiInSection = true;
244
+ this.#offset += KIMI_SECTION_BEGIN.length;
245
+ continue;
246
+ }
247
+ if (this.#matchesToken(KIMI_SECTION_END)) {
248
+ this.#kimiInSection = false;
249
+ this.#sectionTerminated = true;
250
+ this.#offset += KIMI_SECTION_END.length;
251
+ continue;
252
+ }
253
+ if (this.#matchesToken(KIMI_CALL_BEGIN)) {
254
+ if (!this.#kimiInSection) {
255
+ clean += KIMI_CALL_BEGIN;
256
+ this.#offset += KIMI_CALL_BEGIN.length;
257
+ continue;
258
+ }
259
+ this.#kimiInCall = true;
260
+ this.#kimiInArgs = false;
261
+ this.#kimiPendingId = "";
262
+ this.#kimiPendingArgs = "";
263
+ this.#offset += KIMI_CALL_BEGIN.length;
264
+ continue;
265
+ }
266
+ if (this.#matchesToken(KIMI_ARG_BEGIN)) {
267
+ if (!this.#kimiInSection) {
268
+ clean += KIMI_ARG_BEGIN;
269
+ this.#offset += KIMI_ARG_BEGIN.length;
270
+ continue;
271
+ }
272
+ this.#kimiInArgs = true;
273
+ this.#offset += KIMI_ARG_BEGIN.length;
274
+ continue;
275
+ }
276
+ if (this.#matchesToken(KIMI_CALL_END)) {
277
+ if (!this.#kimiInSection || !this.#kimiInCall) {
278
+ clean += KIMI_CALL_END;
279
+ this.#offset += KIMI_CALL_END.length;
280
+ continue;
281
+ }
282
+ const call = this.#finalizeKimiCall();
283
+ flushClean();
284
+ events.push({ type: "toolCall", call });
285
+ this.#offset += KIMI_CALL_END.length;
286
+ continue;
287
+ }
288
+
289
+ const ch = this.#buffer[this.#offset]!;
290
+ this.#offset += 1;
291
+
292
+ if (this.#kimiInCall) {
293
+ if (this.#kimiInArgs) {
294
+ this.#kimiPendingArgs += ch;
295
+ } else {
296
+ this.#kimiPendingId += ch;
297
+ }
298
+ continue;
299
+ }
300
+
301
+ if (!this.#kimiInSection) clean += ch;
302
+ }
303
+
304
+ flushClean();
305
+ return events;
306
+ }
307
+
308
+ #consumeDsmlEvents(): StreamMarkupHealingEvent[] {
309
+ return this.#consumeXmlToolEvents({
310
+ getState: () => this.#xmlState,
311
+ setState: state => {
312
+ this.#xmlState = state;
313
+ },
314
+ sectionOpen: DSML_TOOL_CALLS_OPEN_RE,
315
+ sectionClose: DSML_TOOL_CALLS_CLOSE_RE,
316
+ invokeOpen: DSML_INVOKE_OPEN_RE,
317
+ invokeClose: DSML_INVOKE_CLOSE_RE,
318
+ parameterOpen: DSML_PARAMETER_OPEN_RE,
319
+ parameterClose: DSML_PARAMETER_CLOSE_RE,
320
+ coerceStringByDefault: true,
321
+ });
322
+ }
323
+
324
+ #consumePlainThinkingEvents(): StreamMarkupHealingEvent[] {
325
+ const events: StreamMarkupHealingEvent[] = [];
326
+ let clean = "";
327
+ let thinking = "";
328
+ const flushClean = (): void => {
329
+ if (clean.length === 0) return;
330
+ events.push({ type: "text", text: clean });
331
+ clean = "";
332
+ };
333
+ const flushThinking = (): void => {
334
+ if (thinking.length === 0) return;
335
+ events.push({ type: "thinking", thinking });
336
+ thinking = "";
337
+ };
338
+
339
+ while (this.#offset < this.#buffer.length) {
340
+ if (this.#thinkingCloseTag) {
341
+ if (this.#matchesToken(this.#thinkingCloseTag)) {
342
+ flushThinking();
343
+ this.#offset += this.#thinkingCloseTag.length;
344
+ this.#thinkingCloseTag = "";
345
+ continue;
346
+ }
347
+ if (this.#startsWithPartialToken([this.#thinkingCloseTag], MAX_XML_PARTIAL_HOLD)) break;
348
+ const ch = this.#buffer[this.#offset]!;
349
+ this.#offset += 1;
350
+ thinking += ch;
351
+ continue;
352
+ }
353
+
354
+ const thinkingTag = this.#tryMatchThinkingOpen(PLAIN_THINKING_TAGS);
355
+ if (thinkingTag) {
356
+ flushClean();
357
+ this.#thinkingCloseTag = thinkingTag.close;
358
+ continue;
359
+ }
360
+ if (this.#startsWithPartialThinkingOpen(PLAIN_THINKING_TAGS)) break;
361
+
362
+ const ch = this.#buffer[this.#offset]!;
363
+ this.#offset += 1;
364
+ clean += ch;
365
+ }
366
+
367
+ flushClean();
368
+ flushThinking();
369
+ return events;
370
+ }
371
+
372
+ #consumeXmlToolEvents(config: {
373
+ readonly getState: () => XmlToolState;
374
+ readonly setState: (state: XmlToolState) => void;
375
+ readonly sectionOpen: RegExp;
376
+ readonly sectionClose: RegExp;
377
+ readonly invokeOpen: RegExp;
378
+ readonly invokeClose: RegExp;
379
+ readonly parameterOpen: RegExp;
380
+ readonly parameterClose: RegExp;
381
+ readonly coerceStringByDefault: boolean;
382
+ }): StreamMarkupHealingEvent[] {
383
+ const events: StreamMarkupHealingEvent[] = [];
384
+ let clean = "";
385
+ const flushClean = (): void => {
386
+ if (clean.length === 0) return;
387
+ events.push({ type: "text", text: clean });
388
+ clean = "";
389
+ };
390
+
391
+ while (this.#offset < this.#buffer.length) {
392
+ const state = config.getState();
393
+
394
+ if (state.kind === "idle") {
395
+ if (this.#tryMatch(config.sectionOpen)) {
396
+ config.setState({ kind: "section" });
397
+ continue;
398
+ }
399
+ } else if (state.kind === "section") {
400
+ if (this.#tryMatch(config.sectionClose)) {
401
+ config.setState({ kind: "idle" });
402
+ this.#sectionTerminated = true;
403
+ continue;
404
+ }
405
+ const invokeMatch = this.#tryMatchCapture(config.invokeOpen);
406
+ if (invokeMatch) {
407
+ config.setState({ kind: "invoke", name: invokeMatch[1] ?? "", args: {} });
408
+ continue;
409
+ }
410
+ } else if (state.kind === "invoke") {
411
+ if (this.#tryMatch(config.invokeClose)) {
412
+ const call = finalizeXmlToolCall(state.name, state.args);
413
+ flushClean();
414
+ events.push({ type: "toolCall", call });
415
+ config.setState({ kind: "section" });
416
+ continue;
417
+ }
418
+ const paramMatch = this.#tryMatchCapture(config.parameterOpen);
419
+ if (paramMatch) {
420
+ const stringAttr = paramMatch[2];
421
+ config.setState({
422
+ kind: "parameter",
423
+ invokeName: state.name,
424
+ args: state.args,
425
+ paramName: paramMatch[1] ?? "",
426
+ isString: config.coerceStringByDefault ? stringAttr !== "false" : false,
427
+ value: "",
428
+ });
429
+ continue;
430
+ }
431
+ } else if (this.#tryMatch(config.parameterClose)) {
432
+ state.args[state.paramName] = coerceXmlParamValue(state.value, state.isString);
433
+ config.setState({ kind: "invoke", name: state.invokeName, args: state.args });
434
+ continue;
435
+ }
436
+
437
+ if (this.#startsWithPartialXmlTag()) break;
438
+
439
+ const ch = this.#buffer[this.#offset]!;
440
+ this.#offset += 1;
441
+ if (state.kind === "idle") {
442
+ clean += ch;
443
+ continue;
444
+ }
445
+ if (state.kind === "parameter") {
446
+ if (state.value.length >= MAX_XML_PARAM_VALUE_LENGTH) {
447
+ config.setState({ kind: "idle" });
448
+ continue;
449
+ }
450
+ state.value += ch;
451
+ }
452
+ }
453
+
454
+ flushClean();
455
+ return events;
456
+ }
457
+
458
+ #tryMatch(pattern: RegExp): boolean {
459
+ pattern.lastIndex = this.#offset;
460
+ const match = pattern.exec(this.#buffer);
461
+ if (!match) return false;
462
+ this.#offset += match[0].length;
463
+ return true;
464
+ }
465
+
466
+ #tryMatchCapture(pattern: RegExp): RegExpExecArray | undefined {
467
+ pattern.lastIndex = this.#offset;
468
+ const match = pattern.exec(this.#buffer);
469
+ if (!match) return undefined;
470
+ this.#offset += match[0].length;
471
+ return match;
472
+ }
473
+
474
+ #tryMatchThinkingOpen(tags: readonly ThinkingTag[]): ThinkingTag | undefined {
475
+ for (const tag of tags) {
476
+ if (!this.#matchesToken(tag.open)) continue;
477
+ this.#offset += tag.open.length;
478
+ return tag;
479
+ }
480
+ return undefined;
481
+ }
482
+
483
+ #matchesToken(token: string): boolean {
484
+ return this.#buffer.startsWith(token, this.#offset);
485
+ }
486
+
487
+ #startsWithPartialThinkingOpen(tags: readonly ThinkingTag[]): boolean {
488
+ for (const tag of tags) {
489
+ if (this.#startsWithPartialToken([tag.open], MAX_XML_PARTIAL_HOLD)) return true;
490
+ }
491
+ return false;
492
+ }
493
+
494
+ #startsWithPartialToken(tokens: readonly string[], maxHold: number): boolean {
495
+ const remainingLength = this.#buffer.length - this.#offset;
496
+ if (remainingLength === 0 || remainingLength > maxHold) return false;
497
+ for (const token of tokens) {
498
+ if (token.length <= remainingLength) continue;
499
+ if (this.#bufferIsPrefixOf(token, remainingLength)) return true;
500
+ }
501
+ return false;
502
+ }
503
+
504
+ #startsWithPartialXmlTag(): boolean {
505
+ if (this.#buffer[this.#offset] !== "<") return false;
506
+ const tailLength = this.#buffer.length - this.#offset;
507
+ if (tailLength > MAX_XML_PARTIAL_HOLD) return false;
508
+ for (let i = this.#offset + 1; i < this.#buffer.length; i++) {
509
+ if (this.#buffer[i] === ">") return false;
510
+ }
511
+ return true;
512
+ }
513
+
514
+ #bufferIsPrefixOf(token: string, remainingLength: number): boolean {
515
+ for (let i = 0; i < remainingLength; i++) {
516
+ if (this.#buffer[this.#offset + i] !== token[i]) return false;
517
+ }
518
+ return true;
519
+ }
520
+
521
+ #finalizeKimiCall(): HealedToolCall {
522
+ const rawId = this.#kimiPendingId.trim();
523
+ const rawArgs = this.#kimiPendingArgs.trim();
524
+ const name = normalizeKimiFunctionName(rawId);
525
+
526
+ let argsJson = rawArgs;
527
+ if (rawArgs.length > 0) {
528
+ try {
529
+ argsJson = JSON.stringify(parseJsonWithRepair<unknown>(rawArgs));
530
+ } catch {
531
+ // Leave raw; downstream parseStreamingJson absorbs the failure.
532
+ }
533
+ } else {
534
+ argsJson = "{}";
535
+ }
536
+
537
+ this.#kimiInCall = false;
538
+ this.#kimiInArgs = false;
539
+ this.#kimiPendingId = "";
540
+ this.#kimiPendingArgs = "";
541
+ return { id: generateHealedToolCallId(), name, arguments: argsJson };
542
+ }
543
+
544
+ #resetKimi(): void {
545
+ this.#kimiInSection = false;
546
+ this.#kimiInCall = false;
547
+ this.#kimiInArgs = false;
548
+ this.#kimiPendingId = "";
549
+ this.#kimiPendingArgs = "";
550
+ }
551
+ }
552
+
553
+ function normalizeKimiFunctionName(rawId: string): string {
554
+ const stripped = rawId.startsWith("functions.") ? rawId.slice("functions.".length) : rawId;
555
+ const colon = stripped.indexOf(":");
556
+ return colon >= 0 ? stripped.slice(0, colon) : stripped;
557
+ }
558
+
559
+ function finalizeXmlToolCall(name: string, args: Record<string, unknown>): HealedToolCall {
560
+ return {
561
+ id: generateHealedToolCallId(),
562
+ name: name.trim(),
563
+ arguments: JSON.stringify(args),
564
+ };
565
+ }
566
+
567
+ function coerceXmlParamValue(raw: string, isString: boolean): unknown {
568
+ if (isString) return raw;
569
+ const trimmed = raw.trim();
570
+ if (trimmed.length === 0) return raw;
571
+ try {
572
+ return parseJsonWithRepair<unknown>(trimmed);
573
+ } catch {
574
+ return raw;
575
+ }
576
+ }
577
+
578
+ function generateHealedToolCallId(): string {
579
+ return `call_${crypto.randomUUID().replace(/-/g, "").slice(0, 24)}`;
580
+ }
581
+
582
+ /** Cheap model/provider gate for Kimi-K2 chat-template token leaks. */
583
+ export function modelMayLeakKimiToolCalls(provider: string, modelId: string): boolean {
584
+ if (provider === "kimi-code" || provider === "moonshot") return true;
585
+ return /kimi[-/_.]?k2/i.test(modelId);
586
+ }
587
+
588
+ /** Cheap model/provider gate for DeepSeek DSML envelope leaks. */
589
+ export function modelMayLeakDsmlToolCalls(provider: string, modelId: string): boolean {
590
+ if (!/deepseek/i.test(modelId)) return false;
591
+ return (
592
+ provider === "ollama" ||
593
+ provider === "ollama-cloud" ||
594
+ provider === "nvidia" ||
595
+ provider === "deepseek" ||
596
+ provider === "fireworks" ||
597
+ provider === "nanogpt" ||
598
+ provider === "opencode-go" ||
599
+ provider === "openrouter"
600
+ );
601
+ }
602
+
603
+ export function getStreamMarkupHealingPattern(
604
+ provider: string,
605
+ modelId: string,
606
+ options?: { readonly parseThinkingTags?: boolean },
607
+ ): StreamMarkupHealingPattern | undefined {
608
+ if (options?.parseThinkingTags) return "thinking";
609
+ if (modelMayLeakKimiToolCalls(provider, modelId)) return "kimi";
610
+ if (modelMayLeakDsmlToolCalls(provider, modelId)) return "dsml";
611
+ return undefined;
612
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Utility functions for mapping unified ToolChoice to provider-specific formats.
3
+ */
4
+ import type { ToolChoice } from "../types";
5
+
6
+ /** OpenAI Completions API tool choice format */
7
+ export type OpenAICompletionsToolChoice =
8
+ | "auto"
9
+ | "none"
10
+ | "required"
11
+ | { type: "function"; function: { name: string } }
12
+ | undefined;
13
+
14
+ /** OpenAI Responses API tool choice format (flat structure) */
15
+ export type OpenAIResponsesToolChoice =
16
+ | "auto"
17
+ | "none"
18
+ | "required"
19
+ | { type: "function"; name: string }
20
+ | { type: "custom"; name: string }
21
+ | undefined;
22
+
23
+ /** Anthropic-compatible tool choice format */
24
+ export type AnthropicToolChoice = "auto" | "none" | "any" | { type: "tool"; name: string } | undefined;
25
+
26
+ /**
27
+ * Extract function name from unified ToolChoice.
28
+ */
29
+ function extractFunctionName(choice: ToolChoice): string | undefined {
30
+ if (typeof choice === "string") return undefined;
31
+ if (choice.type === "tool" && "name" in choice) return choice.name;
32
+ if (choice.type === "function") {
33
+ if ("function" in choice && choice.function && typeof choice.function === "object") {
34
+ return (choice.function as { name?: string }).name;
35
+ }
36
+ if ("name" in choice) return choice.name;
37
+ }
38
+ return undefined;
39
+ }
40
+
41
+ /**
42
+ * Map unified ToolChoice to OpenAI Completions API format.
43
+ * - "any" → "required"
44
+ * - { type: "tool", name } → { type: "function", function: { name } }
45
+ */
46
+ export function mapToOpenAICompletionsToolChoice(choice?: ToolChoice): OpenAICompletionsToolChoice {
47
+ if (!choice) return undefined;
48
+ if (typeof choice === "string") {
49
+ if (choice === "any") return "required";
50
+ if (choice === "auto" || choice === "none" || choice === "required") return choice;
51
+ return undefined;
52
+ }
53
+ const name = extractFunctionName(choice);
54
+ return name ? { type: "function", function: { name } } : undefined;
55
+ }
56
+
57
+ /**
58
+ * Returns true when an OpenAI-completions `tool_choice` value forces a tool
59
+ * call (`"required"` or a function-name pin), as opposed to leaving it open
60
+ * (`"auto"`, `"none"`, or unset). Accepts `unknown` because the param shape
61
+ * pulled from the OpenAI SDK (`ChatCompletionToolChoiceOption`) widens with
62
+ * each release; this check only needs the open/forced bit.
63
+ */
64
+ export function isForcedToolChoice(choice: unknown): boolean {
65
+ if (choice === undefined || choice === "auto" || choice === "none") return false;
66
+ return true;
67
+ }
68
+
69
+ /**
70
+ * Map unified ToolChoice to OpenAI Responses API format.
71
+ * - "any" → "required"
72
+ * - { type: "tool", name } → { type: "function", name } (flat structure)
73
+ */
74
+ export function mapToOpenAIResponsesToolChoice(choice?: ToolChoice): OpenAIResponsesToolChoice {
75
+ if (!choice) return undefined;
76
+ if (typeof choice === "string") {
77
+ if (choice === "any") return "required";
78
+ if (choice === "auto" || choice === "none" || choice === "required") return choice;
79
+ return undefined;
80
+ }
81
+ const name = extractFunctionName(choice);
82
+ return name ? { type: "function", name } : undefined;
83
+ }
84
+
85
+ /**
86
+ * Map unified ToolChoice to Anthropic-compatible format.
87
+ * - "required" → "any"
88
+ * - { type: "function", ... } → { type: "tool", name }
89
+ */
90
+ export function mapToAnthropicToolChoice(choice?: ToolChoice): AnthropicToolChoice {
91
+ if (!choice) return undefined;
92
+ if (typeof choice === "string") {
93
+ if (choice === "required") return "any";
94
+ if (choice === "auto" || choice === "none" || choice === "any") return choice;
95
+ return undefined;
96
+ }
97
+ const name = extractFunctionName(choice);
98
+ return name ? { type: "tool", name } : undefined;
99
+ }