@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,647 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { resolvePromptCacheKey } from "../auth-gateway/http";
3
+ /**
4
+ * Parsed inbound OpenAI chat-completions request, ready to feed into aery-ai
5
+ * `stream(model, context, options)`.
6
+ */
7
+ import type { AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
8
+ import type {
9
+ AssistantMessage,
10
+ AssistantMessageEventStream,
11
+ Context,
12
+ ImageContent,
13
+ Message,
14
+ ResolvedServiceTier,
15
+ StopReason,
16
+ TextContent,
17
+ Tool,
18
+ ToolCall,
19
+ ToolResultMessage,
20
+ TSchema,
21
+ } from "../types";
22
+ import {
23
+ type OpenAIChatContentPart,
24
+ type OpenAIChatMessage,
25
+ type OpenAIChatTool,
26
+ type OpenAIChatToolCall,
27
+ type OpenAIChatToolChoice,
28
+ openaiChatRequestSchema,
29
+ } from "./openai-chat-server-schema";
30
+
31
+ export type { ParsedRequest };
32
+
33
+ type ReasoningEffort = NonNullable<ParsedRequest["options"]["reasoning"]>;
34
+
35
+ function isReasoningEffort(value: unknown): value is ReasoningEffort {
36
+ return value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh";
37
+ }
38
+
39
+ function isServiceTier(value: unknown): value is ResolvedServiceTier {
40
+ return value === "auto" || value === "default" || value === "flex" || value === "scale" || value === "priority";
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // parseRequest
45
+ // ---------------------------------------------------------------------------
46
+
47
+ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
48
+ // Header capture is centralized in `auth-gateway/server.ts` (allow-listed
49
+ // headers like openai-organization/openai-project/openai-beta/x-stainless-*
50
+ // land on `options.headers` automatically). We consult `headers` here too
51
+ // for `resolvePromptCacheKey` to pull a cache identity out of inbound
52
+ // vendor-neutral headers when the body doesn't carry one.
53
+ const parsed = openaiChatRequestSchema.safeParse(body);
54
+ if (!parsed.success) {
55
+ throw new Error(`openai-chat: ${parsed.error.message}`);
56
+ }
57
+ const data = parsed.data;
58
+
59
+ const now = Date.now();
60
+ const systemParts: string[] = [];
61
+ const messages: Message[] = [];
62
+ // Map of `tool_call_id` → function name, populated as we walk assistant
63
+ // turns. The OpenAI wire spec drops `name` from `role:"tool"` messages,
64
+ // but downstream providers (notably Google: `functionResponse.name` is
65
+ // required) need it. We back-resolve from the matching call. If the
66
+ // client did send a wire `name` we still prefer that (forward-compat).
67
+ const toolNamesById = new Map<string, string>();
68
+
69
+ for (const m of data.messages as OpenAIChatMessage[]) {
70
+ switch (m.role) {
71
+ case "system": {
72
+ const text = stringifyContent(m.content);
73
+ if (text.length > 0) systemParts.push(text);
74
+ break;
75
+ }
76
+ case "developer":
77
+ messages.push({ role: "developer", content: parseUserLikeContent(m.content), timestamp: now });
78
+ break;
79
+ case "user":
80
+ messages.push({ role: "user", content: parseUserLikeContent(m.content), timestamp: now });
81
+ break;
82
+ case "assistant":
83
+ if (m.tool_calls) {
84
+ for (const raw of m.tool_calls) {
85
+ if (raw.type !== undefined && raw.type !== "function") continue;
86
+ const fn = (raw as { function?: { name?: string } }).function;
87
+ if (raw.id && fn?.name) toolNamesById.set(raw.id, fn.name);
88
+ }
89
+ }
90
+ messages.push(
91
+ buildAssistantMessage(
92
+ (m.content ?? undefined) as string | OpenAIChatContentPart[] | undefined,
93
+ m.tool_calls,
94
+ data.model,
95
+ now,
96
+ ),
97
+ );
98
+ break;
99
+ case "tool": {
100
+ // Prefer the wire `name` when present; otherwise back-resolve from
101
+ // the assistant `tool_calls` map. Falls through to "" only when no
102
+ // prior call shares this id, which is the well-known broken case.
103
+ const wireName = (m as { name?: string }).name;
104
+ const resolvedName = wireName ?? (m.tool_call_id ? toolNamesById.get(m.tool_call_id) : undefined);
105
+ pushToolResultMessages(messages, m.content, m.tool_call_id, resolvedName, now);
106
+ break;
107
+ }
108
+ case "function": {
109
+ // Legacy `function` role (pre-tools API): the message carries the tool's
110
+ // name on `name` and its output on `content`. Translate to a canonical
111
+ // `toolResult` with a synthetic id (no original id on the wire).
112
+ const fn = m as { role: "function"; name: string; content: string | null };
113
+ pushToolResultMessages(messages, fn.content ?? "", undefined, fn.name, now);
114
+ break;
115
+ }
116
+ }
117
+ }
118
+
119
+ const tools = data.tools ? buildTools(data.tools as OpenAIChatTool[]) : undefined;
120
+
121
+ const context: Context = {
122
+ messages,
123
+ ...(systemParts.length > 0 ? { systemPrompt: [systemParts.join("\n\n")] } : {}),
124
+ ...(tools ? { tools } : {}),
125
+ };
126
+
127
+ // Prefer max_completion_tokens (newer) over max_tokens.
128
+ const maxOutputTokens = data.max_completion_tokens ?? data.max_tokens;
129
+ const stopSequences = normalizeStop(data.stop);
130
+ // Schema accepts the Anthropic-style {type:'tool', name} variant that the SDK
131
+ // union doesn't model; the normalizer collapses it to a plain name lookup.
132
+ const toolChoice = normalizeToolChoice(data.tool_choice as Parameters<typeof normalizeToolChoice>[0]);
133
+ const includeStreamingUsage = data.stream_options?.include_usage === true;
134
+
135
+ // `includeStreamingUsage` is the one genuinely-opaque flag — the streaming
136
+ // encoder reads it later off `options.extra`. Everything else now lives on
137
+ // a typed field; `extra` stays undefined when only typed values are set.
138
+ const extra: Record<string, unknown> = {};
139
+ let hasExtra = false;
140
+ if (includeStreamingUsage) {
141
+ extra.includeStreamingUsage = true;
142
+ hasExtra = true;
143
+ }
144
+
145
+ const options: ParsedRequest["options"] = {};
146
+ if (maxOutputTokens !== undefined) options.maxOutputTokens = maxOutputTokens;
147
+ if (data.temperature !== undefined) options.temperature = data.temperature;
148
+ if (data.top_p !== undefined) options.topP = data.top_p;
149
+ if (stopSequences) options.stopSequences = stopSequences;
150
+ if (toolChoice !== undefined) options.toolChoice = toolChoice;
151
+ if (data.presence_penalty !== undefined) options.presencePenalty = data.presence_penalty;
152
+ if (data.frequency_penalty !== undefined) options.frequencyPenalty = data.frequency_penalty;
153
+ if (data.seed !== undefined) options.seed = data.seed;
154
+ if (data.logit_bias !== undefined) options.logitBias = data.logit_bias;
155
+ if (data.user !== undefined) options.user = data.user;
156
+ if (data.response_format !== undefined) options.responseFormat = data.response_format;
157
+ if (data.parallel_tool_calls !== undefined) options.parallelToolCalls = data.parallel_tool_calls;
158
+ if (data.reasoning_effort !== undefined && isReasoningEffort(data.reasoning_effort)) {
159
+ options.reasoning = data.reasoning_effort;
160
+ }
161
+ if (data.service_tier !== undefined && isServiceTier(data.service_tier)) {
162
+ options.serviceTier = data.service_tier;
163
+ }
164
+ if (data.metadata !== undefined) options.metadata = data.metadata;
165
+ const cacheKey = resolvePromptCacheKey(body, headers);
166
+ if (cacheKey !== undefined) options.promptCacheKey = cacheKey;
167
+ if (hasExtra) options.extra = extra;
168
+
169
+ return {
170
+ modelId: data.model,
171
+ context,
172
+ stream: data.stream === true,
173
+ options,
174
+ };
175
+ }
176
+
177
+ function stringifyContent(content: string | OpenAIChatContentPart[] | undefined): string {
178
+ if (content === undefined) return "";
179
+ if (typeof content === "string") return content;
180
+ const out: string[] = [];
181
+ for (const part of content) {
182
+ if (part.type === "text") out.push(part.text);
183
+ }
184
+ return out.join("");
185
+ }
186
+
187
+ function parseUserLikeContent(
188
+ content: string | OpenAIChatContentPart[] | undefined,
189
+ ): string | (TextContent | ImageContent)[] {
190
+ if (content === undefined) return "";
191
+ if (typeof content === "string") return content;
192
+ const parts: (TextContent | ImageContent)[] = [];
193
+ for (const part of content) {
194
+ if (part.type === "text") {
195
+ parts.push({ type: "text", text: part.text });
196
+ continue;
197
+ }
198
+ if (part.type !== "image_url") continue;
199
+ // input_audio / file / refusal / unknown-type parts are accepted by the
200
+ // schema for forward-compat but dropped here — aery-ai's canonical user
201
+ // content only models text and image today.
202
+ const url = typeof part.image_url === "string" ? part.image_url : part.image_url.url;
203
+ const decoded = decodeDataUri(url);
204
+ if (decoded) {
205
+ parts.push({ type: "image", data: decoded.data, mimeType: decoded.mimeType });
206
+ } else {
207
+ // No image fetcher available in the gateway; surface as a text placeholder so
208
+ // downstream providers still receive a coherent message.
209
+ parts.push({ type: "text", text: `[image: ${url}]` });
210
+ }
211
+ }
212
+ return parts;
213
+ }
214
+
215
+ function decodeDataUri(url: string): { data: string; mimeType: string } | undefined {
216
+ if (!url.startsWith("data:")) return undefined;
217
+ const comma = url.indexOf(",");
218
+ if (comma < 0) return undefined;
219
+ const header = url.slice(5, comma);
220
+ const payload = url.slice(comma + 1);
221
+ const isBase64 = header.endsWith(";base64");
222
+ const mimeType = (isBase64 ? header.slice(0, -";base64".length) : header) || "application/octet-stream";
223
+ const data = isBase64 ? payload : Buffer.from(decodeURIComponent(payload), "utf8").toString("base64");
224
+ return { data, mimeType };
225
+ }
226
+
227
+ function buildAssistantMessage(
228
+ content: string | OpenAIChatContentPart[] | undefined,
229
+ toolCalls: OpenAIChatToolCall[] | undefined,
230
+ modelId: string,
231
+ now: number,
232
+ ): AssistantMessage {
233
+ const parts: AssistantMessage["content"] = [];
234
+ const text = stringifyContent(content);
235
+ if (text.length > 0) parts.push({ type: "text", text });
236
+ if (toolCalls) {
237
+ for (const raw of toolCalls) {
238
+ // Schema only accepts type:"function" (or omitted); narrow the SDK
239
+ // union here so the custom-tool variant doesn't trip TS.
240
+ if (raw.type !== undefined && raw.type !== "function") continue;
241
+ const fn = (raw as { function: { name: string; arguments: string } }).function;
242
+ const argsStr = fn.arguments;
243
+ let args: Record<string, unknown> = {};
244
+ if (argsStr.length > 0) {
245
+ try {
246
+ const v: unknown = JSON.parse(argsStr);
247
+ args =
248
+ v && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>) : { __raw: argsStr };
249
+ } catch {
250
+ args = { __raw: argsStr };
251
+ }
252
+ }
253
+ const call: ToolCall = { type: "toolCall", id: raw.id, name: fn.name, arguments: args };
254
+ parts.push(call);
255
+ }
256
+ }
257
+ return {
258
+ role: "assistant",
259
+ content: parts,
260
+ api: "openai-completions",
261
+ provider: "openai",
262
+ model: modelId,
263
+ usage: {
264
+ input: 0,
265
+ output: 0,
266
+ cacheRead: 0,
267
+ cacheWrite: 0,
268
+ totalTokens: 0,
269
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
270
+ },
271
+ stopReason: "stop",
272
+ timestamp: now,
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Walk a wire `tool` (or legacy `function`) message into canonical messages.
278
+ * Tool-result content may carry images alongside text; aery-ai's
279
+ * `ToolResultMessage` accepts both, but most downstream providers ignore
280
+ * images on tool results. To mirror Rust's `encode_messages` behavior we
281
+ * keep text inside the tool-result message and hoist any image parts into a
282
+ * follow-up `user` message so they still reach the model.
283
+ */
284
+ function pushToolResultMessages(
285
+ messages: Message[],
286
+ content: string | OpenAIChatContentPart[] | undefined | null,
287
+ toolCallId: string | undefined,
288
+ toolName: string | undefined,
289
+ now: number,
290
+ ): void {
291
+ const textParts: TextContent[] = [];
292
+ const imageParts: ImageContent[] = [];
293
+
294
+ if (typeof content === "string") {
295
+ if (content.length > 0) textParts.push({ type: "text", text: content });
296
+ } else if (Array.isArray(content)) {
297
+ for (const part of content) {
298
+ if (part.type === "text") {
299
+ textParts.push({ type: "text", text: part.text });
300
+ continue;
301
+ }
302
+ if (part.type !== "image_url") continue;
303
+ const url = typeof part.image_url === "string" ? part.image_url : part.image_url.url;
304
+ const decoded = decodeDataUri(url);
305
+ if (decoded) {
306
+ imageParts.push({ type: "image", data: decoded.data, mimeType: decoded.mimeType });
307
+ } else {
308
+ // No fetcher available; degrade gracefully to a text placeholder.
309
+ textParts.push({ type: "text", text: `[image: ${url}]` });
310
+ }
311
+ }
312
+ }
313
+
314
+ const toolMsg: ToolResultMessage = {
315
+ role: "toolResult",
316
+ toolCallId: toolCallId ?? "",
317
+ // OpenAI's `tool` role omits the tool name on the wire; the legacy
318
+ // `function` role supplies it. Downstream providers tolerate empty.
319
+ toolName: toolName ?? "",
320
+ content: textParts.length > 0 ? textParts : [{ type: "text", text: "" }],
321
+ isError: false,
322
+ timestamp: now,
323
+ };
324
+ messages.push(toolMsg);
325
+
326
+ if (imageParts.length > 0) {
327
+ messages.push({
328
+ role: "user",
329
+ content: imageParts,
330
+ timestamp: now,
331
+ });
332
+ }
333
+ }
334
+
335
+ function buildTools(tools: OpenAIChatTool[]): Tool[] | undefined {
336
+ if (tools.length === 0) return undefined;
337
+ const out: Tool[] = [];
338
+ for (const t of tools) {
339
+ if (t.type !== "function") continue;
340
+ out.push({
341
+ name: t.function.name,
342
+ description: t.function.description ?? "",
343
+ parameters: (t.function.parameters ?? {}) as Record<string, unknown> as TSchema,
344
+ });
345
+ }
346
+ return out;
347
+ }
348
+
349
+ function normalizeStop(value: string | string[] | undefined): string[] | undefined {
350
+ if (value === undefined) return undefined;
351
+ if (typeof value === "string") return [value];
352
+ return value.length > 0 ? value : undefined;
353
+ }
354
+
355
+ function normalizeToolChoice(value: OpenAIChatToolChoice | undefined): ParsedRequest["options"]["toolChoice"] {
356
+ if (value === undefined) return undefined;
357
+ if (value === "auto" || value === "none" || value === "required") return value;
358
+ if (typeof value === "object" && value !== null) {
359
+ // OpenAI canonical: { type: 'function', function: { name } }
360
+ if ("function" in value && value.function) return { name: value.function.name };
361
+ // Anthropic-style passthrough (schema-allowed): { type: 'tool', name }
362
+ const anthropicLike = value as unknown as { type?: string; name?: string };
363
+ if (anthropicLike.type === "tool" && typeof anthropicLike.name === "string") {
364
+ return { name: anthropicLike.name };
365
+ }
366
+ }
367
+ return undefined;
368
+ }
369
+
370
+ // ---------------------------------------------------------------------------
371
+ // encodeResponse (non-streaming)
372
+ // ---------------------------------------------------------------------------
373
+
374
+ export function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown> {
375
+ const { text, reasoning, toolCalls } = flattenAssistant(message);
376
+
377
+ const responseMessage: Record<string, unknown> = {
378
+ role: "assistant",
379
+ content: text.length > 0 ? text : null,
380
+ // aery-ai does not surface real refusals yet; emit `null` so SDKs that
381
+ // probe `.refusal` see the documented field shape rather than missing.
382
+ refusal: null,
383
+ };
384
+ if (reasoning.length > 0) {
385
+ // DeepSeek-style / o-series reasoning channel.
386
+ responseMessage.reasoning_content = reasoning;
387
+ }
388
+ if (toolCalls.length > 0) {
389
+ responseMessage.tool_calls = toolCalls.map(tc => ({
390
+ id: tc.id,
391
+ type: "function",
392
+ function: { name: tc.name, arguments: stringifyArgs(tc.arguments) },
393
+ }));
394
+ }
395
+
396
+ return {
397
+ id: makeId(),
398
+ object: "chat.completion",
399
+ created: Math.floor(Date.now() / 1000),
400
+ model: requestedModelId,
401
+ // Real OpenAI always emits this key, even when the value is null. Mirror
402
+ // the contract so probing SDKs do not throw on a missing field.
403
+ system_fingerprint: null,
404
+ choices: [
405
+ {
406
+ index: 0,
407
+ message: responseMessage,
408
+ finish_reason: mapFinishReason(message.stopReason, toolCalls.length > 0),
409
+ logprobs: null,
410
+ },
411
+ ],
412
+ usage: buildUsage(message),
413
+ };
414
+ }
415
+
416
+ function buildUsage(message: AssistantMessage): Record<string, unknown> {
417
+ const promptTokens = message.usage.input + message.usage.cacheRead + message.usage.cacheWrite;
418
+ const usage: Record<string, unknown> = {
419
+ prompt_tokens: promptTokens,
420
+ completion_tokens: message.usage.output,
421
+ total_tokens: promptTokens + message.usage.output,
422
+ prompt_tokens_details: { cached_tokens: message.usage.cacheRead },
423
+ };
424
+ if (message.usage.reasoningTokens !== undefined) {
425
+ usage.completion_tokens_details = { reasoning_tokens: message.usage.reasoningTokens };
426
+ }
427
+ return usage;
428
+ }
429
+
430
+ function flattenAssistant(message: AssistantMessage): {
431
+ text: string;
432
+ reasoning: string;
433
+ toolCalls: ToolCall[];
434
+ } {
435
+ let text = "";
436
+ let reasoning = "";
437
+ const toolCalls: ToolCall[] = [];
438
+ for (const part of message.content) {
439
+ switch (part.type) {
440
+ case "text":
441
+ text += part.text;
442
+ break;
443
+ case "thinking":
444
+ reasoning += part.thinking;
445
+ break;
446
+ case "redactedThinking":
447
+ // Opaque blob — surface verbatim on the reasoning channel so the
448
+ // concatenation round-trips through clients that just echo it.
449
+ reasoning += part.data;
450
+ break;
451
+ case "toolCall":
452
+ toolCalls.push(part);
453
+ break;
454
+ }
455
+ }
456
+ return { text, reasoning, toolCalls };
457
+ }
458
+
459
+ function isOnlyRaw(args: Record<string, unknown>): boolean {
460
+ for (const k in args) {
461
+ if (k !== "__raw") return false;
462
+ }
463
+ return true;
464
+ }
465
+
466
+ function stringifyArgs(args: Record<string, unknown>): string {
467
+ // `__raw` is our fallback marker for un-parseable inbound args; preserve it verbatim on the way out.
468
+ if (typeof args.__raw === "string" && isOnlyRaw(args)) return args.__raw;
469
+ try {
470
+ return JSON.stringify(args);
471
+ } catch {
472
+ return "{}";
473
+ }
474
+ }
475
+
476
+ function mapFinishReason(reason: StopReason, hasToolCalls: boolean): string {
477
+ if (reason === "toolUse" || (hasToolCalls && reason === "stop")) return "tool_calls";
478
+ if (reason === "length") return "length";
479
+ // aery-ai's StopReason does not currently carry a content-filter signal;
480
+ // when it does, map it to "content_filter" here.
481
+ return "stop";
482
+ }
483
+
484
+ function makeId(): string {
485
+ return `chatcmpl-${randomUUID()}`;
486
+ }
487
+
488
+ // ---------------------------------------------------------------------------
489
+ // encodeStream (SSE)
490
+ // ---------------------------------------------------------------------------
491
+
492
+ export function encodeStream(
493
+ events: AssistantMessageEventStream,
494
+ requestedModelId: string,
495
+ options?: ParsedRequest["options"],
496
+ ): ReadableStream<Uint8Array> {
497
+ const encoder = new TextEncoder();
498
+ const id = makeId();
499
+ const created = Math.floor(Date.now() / 1000);
500
+ const includeUsage = options?.extra?.includeStreamingUsage === true;
501
+
502
+ const baseChunk = (delta: Record<string, unknown>, finishReason: string | null) => ({
503
+ id,
504
+ object: "chat.completion.chunk",
505
+ created,
506
+ model: requestedModelId,
507
+ system_fingerprint: null,
508
+ choices: [{ index: 0, delta, finish_reason: finishReason, logprobs: null }],
509
+ ...(includeUsage ? { usage: null } : {}),
510
+ });
511
+
512
+ const writeSse = (controller: ReadableStreamDefaultController<Uint8Array>, payload: unknown): void => {
513
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(payload)}\n\n`));
514
+ };
515
+
516
+ const writeUsage = (controller: ReadableStreamDefaultController<Uint8Array>, message: AssistantMessage): void => {
517
+ writeSse(controller, {
518
+ id,
519
+ object: "chat.completion.chunk",
520
+ created,
521
+ model: requestedModelId,
522
+ system_fingerprint: null,
523
+ choices: [],
524
+ usage: buildUsage(message),
525
+ });
526
+ };
527
+
528
+ return new ReadableStream<Uint8Array>({
529
+ async start(controller) {
530
+ // contentIndex (from aery-ai events) -> tool_calls index on the wire.
531
+ const toolIndexByContentIndex = new Map<number, number>();
532
+ let nextToolIndex = 0;
533
+ let hasToolCalls = false;
534
+ let finishReason: string = "stop";
535
+
536
+ try {
537
+ // Initial role chunk.
538
+ writeSse(controller, baseChunk({ role: "assistant" }, null));
539
+
540
+ for await (const event of events) {
541
+ switch (event.type) {
542
+ case "text_delta":
543
+ if (event.delta.length > 0) {
544
+ writeSse(controller, baseChunk({ content: event.delta }, null));
545
+ }
546
+ break;
547
+
548
+ case "thinking_delta":
549
+ // DeepSeek-style / o-series reasoning channel. Clients that don't
550
+ // understand it ignore the unknown delta key.
551
+ if (event.delta.length > 0) {
552
+ writeSse(controller, baseChunk({ reasoning_content: event.delta }, null));
553
+ }
554
+ break;
555
+
556
+ case "toolcall_start": {
557
+ hasToolCalls = true;
558
+ const idx = nextToolIndex++;
559
+ toolIndexByContentIndex.set(event.contentIndex, idx);
560
+ const partial = event.partial.content[event.contentIndex];
561
+ const call = partial && partial.type === "toolCall" ? partial : undefined;
562
+ writeSse(
563
+ controller,
564
+ baseChunk(
565
+ {
566
+ tool_calls: [
567
+ {
568
+ index: idx,
569
+ id: call?.id ?? "",
570
+ type: "function",
571
+ function: { name: call?.name ?? "", arguments: "" },
572
+ },
573
+ ],
574
+ },
575
+ null,
576
+ ),
577
+ );
578
+ break;
579
+ }
580
+
581
+ case "toolcall_delta": {
582
+ const idx = toolIndexByContentIndex.get(event.contentIndex);
583
+ if (idx === undefined) break;
584
+ writeSse(
585
+ controller,
586
+ baseChunk({ tool_calls: [{ index: idx, function: { arguments: event.delta } }] }, null),
587
+ );
588
+ break;
589
+ }
590
+
591
+ case "done":
592
+ finishReason =
593
+ event.reason === "toolUse"
594
+ ? "tool_calls"
595
+ : event.reason === "length"
596
+ ? "length"
597
+ : hasToolCalls
598
+ ? "tool_calls"
599
+ : "stop";
600
+ writeSse(controller, baseChunk({}, finishReason));
601
+ if (includeUsage) writeUsage(controller, event.message);
602
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
603
+ controller.close();
604
+ return;
605
+
606
+ case "error": {
607
+ const msg = event.error.errorMessage ?? "stream error";
608
+ writeSse(controller, { error: { message: msg, type: "upstream_error" } });
609
+ controller.close();
610
+ return;
611
+ }
612
+
613
+ // Drop start / *_start / *_end — chat-completions wire only
614
+ // surfaces deltas and the terminal finish_reason.
615
+ default:
616
+ break;
617
+ }
618
+ }
619
+
620
+ // Stream ended without a terminal `done` (defensive). Close gracefully.
621
+ writeSse(controller, baseChunk({}, hasToolCalls ? "tool_calls" : "stop"));
622
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
623
+ controller.close();
624
+ } catch (err) {
625
+ const msg = err instanceof Error ? err.message : String(err);
626
+ writeSse(controller, { error: { message: msg, type: "upstream_error" } });
627
+ controller.close();
628
+ }
629
+ },
630
+ });
631
+ }
632
+
633
+ // ---------------------------------------------------------------------------
634
+ // formatError
635
+ // ---------------------------------------------------------------------------
636
+
637
+ /**
638
+ * OpenAI chat-completions error envelope:
639
+ * `{ error: { message, type } }`
640
+ * Matches the shape the official SDK auto-parses into `APIError`.
641
+ */
642
+ export function formatError(status: number, type: string, message: string): Response {
643
+ return new Response(JSON.stringify({ error: { message, type } }), {
644
+ status,
645
+ headers: { "Content-Type": "application/json" },
646
+ });
647
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Constants for OpenAI Codex (ChatGPT OAuth) backend
3
+ */
4
+
5
+ export const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
6
+
7
+ export const OPENAI_HEADERS = {
8
+ BETA: "OpenAI-Beta",
9
+ ACCOUNT_ID: "chatgpt-account-id",
10
+ ORIGINATOR: "originator",
11
+ SESSION_ID: "session_id",
12
+ CONVERSATION_ID: "conversation_id",
13
+ } as const;
14
+
15
+ export const OPENAI_HEADER_VALUES = {
16
+ BETA_RESPONSES: "responses=experimental",
17
+ BETA_RESPONSES_WEBSOCKETS_V2: "responses_websockets=2026-02-06",
18
+ ORIGINATOR_CODEX: "aery",
19
+ } as const;
20
+
21
+ export const URL_PATHS = {
22
+ RESPONSES: "/responses",
23
+ CODEX_RESPONSES: "/codex/responses",
24
+ } as const;
25
+
26
+ export const JWT_CLAIM_PATH = "https://api.openai.com/auth" as const;
27
+
28
+ /**
29
+ * Extract account ID from a Codex JWT access token.
30
+ * Returns undefined if the token is not a valid Codex JWT.
31
+ */
32
+ export function getCodexAccountId(accessToken: string): string | undefined {
33
+ try {
34
+ const parts = accessToken.split(".");
35
+ if (parts.length !== 3) return undefined;
36
+ const decoded = Buffer.from(parts[1] ?? "", "base64").toString("utf-8");
37
+ const payload = JSON.parse(decoded) as Record<string, unknown>;
38
+ const auth = payload[JWT_CLAIM_PATH] as { chatgpt_account_id?: string } | undefined;
39
+ return auth?.chatgpt_account_id ?? undefined;
40
+ } catch {
41
+ return undefined;
42
+ }
43
+ }