@linxiraos/pi-ai 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (639) hide show
  1. package/CHANGELOG.md +5066 -0
  2. package/README.md +1195 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth/sqlite-credential-store.d.ts +117 -0
  5. package/dist/types/auth-broker/client.d.ts +93 -0
  6. package/dist/types/auth-broker/discover.d.ts +39 -0
  7. package/dist/types/auth-broker/index.d.ts +7 -0
  8. package/dist/types/auth-broker/refresher.d.ts +25 -0
  9. package/dist/types/auth-broker/remote-store.d.ts +136 -0
  10. package/dist/types/auth-broker/server.d.ts +48 -0
  11. package/dist/types/auth-broker/snapshot-cache.d.ts +17 -0
  12. package/dist/types/auth-broker/types.d.ts +152 -0
  13. package/dist/types/auth-broker/wire-schema-resource.d.ts +53 -0
  14. package/dist/types/auth-broker/wire-schemas.d.ts +5 -0
  15. package/dist/types/auth-gateway/http.d.ts +56 -0
  16. package/dist/types/auth-gateway/index.d.ts +3 -0
  17. package/dist/types/auth-gateway/server.d.ts +36 -0
  18. package/dist/types/auth-gateway/types.d.ts +127 -0
  19. package/dist/types/auth-retry.d.ts +150 -0
  20. package/dist/types/auth-storage.d.ts +1258 -0
  21. package/dist/types/dialect/anthropic.d.ts +15 -0
  22. package/dist/types/dialect/catalog.d.ts +3 -0
  23. package/dist/types/dialect/coercion.d.ts +23 -0
  24. package/dist/types/dialect/deepseek.d.ts +14 -0
  25. package/dist/types/dialect/demotion.d.ts +30 -0
  26. package/dist/types/dialect/examples.d.ts +18 -0
  27. package/dist/types/dialect/factory.d.ts +3 -0
  28. package/dist/types/dialect/fenced-thinking.d.ts +53 -0
  29. package/dist/types/dialect/gemini.d.ts +17 -0
  30. package/dist/types/dialect/gemma.d.ts +15 -0
  31. package/dist/types/dialect/glm.d.ts +9 -0
  32. package/dist/types/dialect/harmony.d.ts +8 -0
  33. package/dist/types/dialect/hermes.d.ts +9 -0
  34. package/dist/types/dialect/history.d.ts +3 -0
  35. package/dist/types/dialect/index.d.ts +11 -0
  36. package/dist/types/dialect/inventory.d.ts +9 -0
  37. package/dist/types/dialect/kimi.d.ts +14 -0
  38. package/dist/types/dialect/minimax.d.ts +3 -0
  39. package/dist/types/dialect/owned-stream.d.ts +4 -0
  40. package/dist/types/dialect/qwen3.d.ts +9 -0
  41. package/dist/types/dialect/rendering.d.ts +54 -0
  42. package/dist/types/dialect/thinking.d.ts +6 -0
  43. package/dist/types/dialect/types.d.ts +68 -0
  44. package/dist/types/dialect/xml.d.ts +9 -0
  45. package/dist/types/error/abort.d.ts +14 -0
  46. package/dist/types/error/auth-classify.d.ts +20 -0
  47. package/dist/types/error/auth.d.ts +27 -0
  48. package/dist/types/error/aws.d.ts +27 -0
  49. package/dist/types/error/classes.d.ts +106 -0
  50. package/dist/types/error/finalize.d.ts +39 -0
  51. package/dist/types/error/flags.d.ts +94 -0
  52. package/dist/types/error/format.d.ts +20 -0
  53. package/dist/types/error/gateway.d.ts +20 -0
  54. package/dist/types/error/index.d.ts +14 -0
  55. package/dist/types/error/oauth.d.ts +43 -0
  56. package/dist/types/error/provider.d.ts +42 -0
  57. package/dist/types/error/rate-limit.d.ts +80 -0
  58. package/dist/types/error/retryable.d.ts +27 -0
  59. package/dist/types/error/validation.d.ts +32 -0
  60. package/dist/types/index.d.ts +51 -0
  61. package/dist/types/provider-details.d.ts +24 -0
  62. package/dist/types/providers/amazon-bedrock.d.ts +39 -0
  63. package/dist/types/providers/anthropic-client.d.ts +106 -0
  64. package/dist/types/providers/anthropic-messages-server-schema.d.ts +937 -0
  65. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  66. package/dist/types/providers/anthropic-wire.d.ts +345 -0
  67. package/dist/types/providers/anthropic.d.ts +261 -0
  68. package/dist/types/providers/aws-credentials.d.ts +48 -0
  69. package/dist/types/providers/aws-eventstream.d.ts +39 -0
  70. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  71. package/dist/types/providers/azure-openai-responses.d.ts +16 -0
  72. package/dist/types/providers/bedrock-mantle.d.ts +13 -0
  73. package/dist/types/providers/claude-code-fingerprint.d.ts +19 -0
  74. package/dist/types/providers/cowork-fetch.d.ts +3 -0
  75. package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
  76. package/dist/types/providers/cursor-pi-args.d.ts +105 -0
  77. package/dist/types/providers/cursor.d.ts +221 -0
  78. package/dist/types/providers/devin.d.ts +12 -0
  79. package/dist/types/providers/error-message.d.ts +25 -0
  80. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  81. package/dist/types/providers/gitlab-duo-workflow.d.ts +254 -0
  82. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  83. package/dist/types/providers/google-auth.d.ts +24 -0
  84. package/dist/types/providers/google-gemini-cli.d.ts +120 -0
  85. package/dist/types/providers/google-shared.d.ts +203 -0
  86. package/dist/types/providers/google-types.d.ts +155 -0
  87. package/dist/types/providers/google-vertex.d.ts +7 -0
  88. package/dist/types/providers/google.d.ts +4 -0
  89. package/dist/types/providers/grammar.d.ts +1 -0
  90. package/dist/types/providers/kimi.d.ts +27 -0
  91. package/dist/types/providers/mock.d.ts +179 -0
  92. package/dist/types/providers/ollama.d.ts +8 -0
  93. package/dist/types/providers/openai-anthropic-shim.d.ts +35 -0
  94. package/dist/types/providers/openai-chat-server-schema.d.ts +1311 -0
  95. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  96. package/dist/types/providers/openai-chat-wire.d.ts +669 -0
  97. package/dist/types/providers/openai-codex/request-transformer.d.ts +109 -0
  98. package/dist/types/providers/openai-codex/response-handler.d.ts +26 -0
  99. package/dist/types/providers/openai-codex-responses.d.ts +238 -0
  100. package/dist/types/providers/openai-completions.d.ts +48 -0
  101. package/dist/types/providers/openai-reasoning-fallback.d.ts +25 -0
  102. package/dist/types/providers/openai-responses-server-schema.d.ts +1314 -0
  103. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  104. package/dist/types/providers/openai-responses-wire.d.ts +6099 -0
  105. package/dist/types/providers/openai-responses.d.ts +131 -0
  106. package/dist/types/providers/openai-shared.d.ts +624 -0
  107. package/dist/types/providers/pi-native-client.d.ts +13 -0
  108. package/dist/types/providers/pi-native-server.d.ts +69 -0
  109. package/dist/types/providers/register-builtins.d.ts +37 -0
  110. package/dist/types/providers/synthetic.d.ts +26 -0
  111. package/dist/types/providers/transform-messages.d.ts +32 -0
  112. package/dist/types/providers/vision-guard.d.ts +20 -0
  113. package/dist/types/registry/aiand.d.ts +7 -0
  114. package/dist/types/registry/aimlapi.d.ts +4 -0
  115. package/dist/types/registry/alibaba-coding-plan.d.ts +8 -0
  116. package/dist/types/registry/alibaba-token-plan.d.ts +18 -0
  117. package/dist/types/registry/amazon-bedrock.d.ts +5 -0
  118. package/dist/types/registry/anthropic.d.ts +10 -0
  119. package/dist/types/registry/api-key-login.d.ts +42 -0
  120. package/dist/types/registry/api-key-validation.d.ts +43 -0
  121. package/dist/types/registry/aws.d.ts +13 -0
  122. package/dist/types/registry/azure.d.ts +4 -0
  123. package/dist/types/registry/baseten.d.ts +7 -0
  124. package/dist/types/registry/bedrock-mantle.d.ts +22 -0
  125. package/dist/types/registry/cerebras.d.ts +7 -0
  126. package/dist/types/registry/cloudflare-ai-gateway.d.ts +13 -0
  127. package/dist/types/registry/coreweave.d.ts +7 -0
  128. package/dist/types/registry/cursor.d.ts +7 -0
  129. package/dist/types/registry/deepseek.d.ts +8 -0
  130. package/dist/types/registry/derived.d.ts +5 -0
  131. package/dist/types/registry/devin.d.ts +8 -0
  132. package/dist/types/registry/exa.d.ts +8 -0
  133. package/dist/types/registry/firepass.d.ts +16 -0
  134. package/dist/types/registry/fireworks.d.ts +7 -0
  135. package/dist/types/registry/github-copilot.d.ts +7 -0
  136. package/dist/types/registry/gitlab-duo-workflow.d.ts +10 -0
  137. package/dist/types/registry/gitlab-duo.d.ts +9 -0
  138. package/dist/types/registry/gmi-cloud.d.ts +7 -0
  139. package/dist/types/registry/google-antigravity.d.ts +9 -0
  140. package/dist/types/registry/google-gemini-cli.d.ts +9 -0
  141. package/dist/types/registry/google-vertex.d.ts +5 -0
  142. package/dist/types/registry/google.d.ts +4 -0
  143. package/dist/types/registry/groq.d.ts +4 -0
  144. package/dist/types/registry/huggingface.d.ts +7 -0
  145. package/dist/types/registry/index.d.ts +4 -0
  146. package/dist/types/registry/kagi.d.ts +14 -0
  147. package/dist/types/registry/kilo.d.ts +7 -0
  148. package/dist/types/registry/kimi-code.d.ts +7 -0
  149. package/dist/types/registry/litellm.d.ts +13 -0
  150. package/dist/types/registry/llama-cpp.d.ts +8 -0
  151. package/dist/types/registry/lm-studio.d.ts +8 -0
  152. package/dist/types/registry/meta.d.ts +7 -0
  153. package/dist/types/registry/minimax-code-cn.d.ts +6 -0
  154. package/dist/types/registry/minimax-code.d.ts +6 -0
  155. package/dist/types/registry/minimax.d.ts +4 -0
  156. package/dist/types/registry/mistral.d.ts +4 -0
  157. package/dist/types/registry/moonshot.d.ts +7 -0
  158. package/dist/types/registry/nanogpt.d.ts +7 -0
  159. package/dist/types/registry/novita.d.ts +6 -0
  160. package/dist/types/registry/nvidia.d.ts +7 -0
  161. package/dist/types/registry/oauth/anthropic-constants.d.ts +12 -0
  162. package/dist/types/registry/oauth/anthropic.d.ts +24 -0
  163. package/dist/types/registry/oauth/callback-server.d.ts +74 -0
  164. package/dist/types/registry/oauth/cursor.d.ts +16 -0
  165. package/dist/types/registry/oauth/device-code.d.ts +25 -0
  166. package/dist/types/registry/oauth/devin.d.ts +5 -0
  167. package/dist/types/registry/oauth/github-copilot.d.ts +30 -0
  168. package/dist/types/registry/oauth/gitlab-duo-workflow.d.ts +6 -0
  169. package/dist/types/registry/oauth/gitlab-duo.d.ts +3 -0
  170. package/dist/types/registry/oauth/google-antigravity.d.ts +11 -0
  171. package/dist/types/registry/oauth/google-gemini-cli.d.ts +22 -0
  172. package/dist/types/registry/oauth/google-oauth-shared.d.ts +56 -0
  173. package/dist/types/registry/oauth/index.d.ts +45 -0
  174. package/dist/types/registry/oauth/kimi.d.ts +21 -0
  175. package/dist/types/registry/oauth/minimax-code.d.ts +27 -0
  176. package/dist/types/registry/oauth/openai-codex.d.ts +33 -0
  177. package/dist/types/registry/oauth/opencode.d.ts +18 -0
  178. package/dist/types/registry/oauth/perplexity.d.ts +9 -0
  179. package/dist/types/registry/oauth/pkce.d.ts +8 -0
  180. package/dist/types/registry/oauth/types.d.ts +87 -0
  181. package/dist/types/registry/oauth/wafer.d.ts +1 -0
  182. package/dist/types/registry/oauth/xai-oauth.d.ts +46 -0
  183. package/dist/types/registry/oauth/xiaomi.d.ts +25 -0
  184. package/dist/types/registry/oauth/zai.d.ts +25 -0
  185. package/dist/types/registry/ollama-cloud.d.ts +7 -0
  186. package/dist/types/registry/ollama.d.ts +12 -0
  187. package/dist/types/registry/openai-codex-device.d.ts +8 -0
  188. package/dist/types/registry/openai-codex.d.ts +9 -0
  189. package/dist/types/registry/openai.d.ts +4 -0
  190. package/dist/types/registry/opencode-go.d.ts +6 -0
  191. package/dist/types/registry/opencode-zen.d.ts +6 -0
  192. package/dist/types/registry/openrouter.d.ts +13 -0
  193. package/dist/types/registry/parallel.d.ts +14 -0
  194. package/dist/types/registry/perplexity.d.ts +7 -0
  195. package/dist/types/registry/qianfan.d.ts +7 -0
  196. package/dist/types/registry/qwen-portal.d.ts +7 -0
  197. package/dist/types/registry/registry.d.ts +367 -0
  198. package/dist/types/registry/sakana.d.ts +7 -0
  199. package/dist/types/registry/siliconflow-cn.d.ts +7 -0
  200. package/dist/types/registry/siliconflow.d.ts +7 -0
  201. package/dist/types/registry/synthetic.d.ts +6 -0
  202. package/dist/types/registry/tavily.d.ts +14 -0
  203. package/dist/types/registry/together.d.ts +6 -0
  204. package/dist/types/registry/types.d.ts +75 -0
  205. package/dist/types/registry/umans.d.ts +7 -0
  206. package/dist/types/registry/venice.d.ts +13 -0
  207. package/dist/types/registry/vercel-ai-gateway.d.ts +7 -0
  208. package/dist/types/registry/vllm.d.ts +7 -0
  209. package/dist/types/registry/wafer-serverless.d.ts +6 -0
  210. package/dist/types/registry/xai-oauth.d.ts +7 -0
  211. package/dist/types/registry/xai.d.ts +7 -0
  212. package/dist/types/registry/xiaomi-token-plan-ams.d.ts +6 -0
  213. package/dist/types/registry/xiaomi-token-plan-cn.d.ts +6 -0
  214. package/dist/types/registry/xiaomi-token-plan-sgp.d.ts +6 -0
  215. package/dist/types/registry/xiaomi.d.ts +6 -0
  216. package/dist/types/registry/zai.d.ts +15 -0
  217. package/dist/types/registry/zenmux.d.ts +7 -0
  218. package/dist/types/registry/zhipu-coding-plan.d.ts +7 -0
  219. package/dist/types/stream.d.ts +46 -0
  220. package/dist/types/types.d.ts +1064 -0
  221. package/dist/types/usage/alibaba-token-plan.d.ts +3 -0
  222. package/dist/types/usage/claude.d.ts +4 -0
  223. package/dist/types/usage/cursor.d.ts +4 -0
  224. package/dist/types/usage/gemini.d.ts +2 -0
  225. package/dist/types/usage/github-copilot.d.ts +7 -0
  226. package/dist/types/usage/google-antigravity.d.ts +15 -0
  227. package/dist/types/usage/kimi.d.ts +2 -0
  228. package/dist/types/usage/minimax-code.d.ts +3 -0
  229. package/dist/types/usage/ollama.d.ts +5 -0
  230. package/dist/types/usage/openai-codex-base-url.d.ts +18 -0
  231. package/dist/types/usage/openai-codex-reset.d.ts +88 -0
  232. package/dist/types/usage/openai-codex.d.ts +10 -0
  233. package/dist/types/usage/opencode-go.d.ts +2 -0
  234. package/dist/types/usage/shared.d.ts +1 -0
  235. package/dist/types/usage/synthetic.d.ts +2 -0
  236. package/dist/types/usage/umans.d.ts +2 -0
  237. package/dist/types/usage/xai-oauth.d.ts +12 -0
  238. package/dist/types/usage/zai.d.ts +3 -0
  239. package/dist/types/usage.d.ts +527 -0
  240. package/dist/types/utils/abort.d.ts +25 -0
  241. package/dist/types/utils/anthropic-auth.d.ts +35 -0
  242. package/dist/types/utils/aws-profile.d.ts +17 -0
  243. package/dist/types/utils/block-symbols.d.ts +62 -0
  244. package/dist/types/utils/deterministic-id.d.ts +16 -0
  245. package/dist/types/utils/empty-completion-retry.d.ts +21 -0
  246. package/dist/types/utils/event-stream.d.ts +39 -0
  247. package/dist/types/utils/foundry.d.ts +1 -0
  248. package/dist/types/utils/google-validation.d.ts +2 -0
  249. package/dist/types/utils/harmony-leak.d.ts +135 -0
  250. package/dist/types/utils/http-inspector.d.ts +49 -0
  251. package/dist/types/utils/idle-iterator.d.ts +149 -0
  252. package/dist/types/utils/leaked-thinking-stream.d.ts +33 -0
  253. package/dist/types/utils/openai-http.d.ts +48 -0
  254. package/dist/types/utils/openrouter-headers.d.ts +1 -0
  255. package/dist/types/utils/parse-bind.d.ts +23 -0
  256. package/dist/types/utils/provider-response.d.ts +3 -0
  257. package/dist/types/utils/proxy.d.ts +39 -0
  258. package/dist/types/utils/request-debug.d.ts +29 -0
  259. package/dist/types/utils/retry-after.d.ts +4 -0
  260. package/dist/types/utils/retry.d.ts +14 -0
  261. package/dist/types/utils/schema/adapt.d.ts +24 -0
  262. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  263. package/dist/types/utils/schema/dereference.d.ts +11 -0
  264. package/dist/types/utils/schema/draft.d.ts +10 -0
  265. package/dist/types/utils/schema/equality.d.ts +4 -0
  266. package/dist/types/utils/schema/fields.d.ts +54 -0
  267. package/dist/types/utils/schema/index.d.ts +14 -0
  268. package/dist/types/utils/schema/json-schema-validator.d.ts +20 -0
  269. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  270. package/dist/types/utils/schema/normalize.d.ts +153 -0
  271. package/dist/types/utils/schema/spill.d.ts +8 -0
  272. package/dist/types/utils/schema/stamps.d.ts +17 -0
  273. package/dist/types/utils/schema/strict-tool-validation.d.ts +16 -0
  274. package/dist/types/utils/schema/types.d.ts +4 -0
  275. package/dist/types/utils/schema/typescript.d.ts +24 -0
  276. package/dist/types/utils/schema/wire.d.ts +52 -0
  277. package/dist/types/utils/sdk-stream-timeout.d.ts +33 -0
  278. package/dist/types/utils/sse-debug.d.ts +5 -0
  279. package/dist/types/utils/stream-markup-healing.d.ts +87 -0
  280. package/dist/types/utils/thinking-loop.d.ts +102 -0
  281. package/dist/types/utils/tool-call-loop-guard.d.ts +26 -0
  282. package/dist/types/utils/tool-choice.d.ts +52 -0
  283. package/dist/types/utils/validation.d.ts +28 -0
  284. package/dist/types/utils.d.ts +57 -0
  285. package/package.json +138 -0
  286. package/src/api-registry.ts +109 -0
  287. package/src/auth/sqlite-credential-store.ts +2066 -0
  288. package/src/auth-broker/client.ts +471 -0
  289. package/src/auth-broker/discover.ts +310 -0
  290. package/src/auth-broker/index.ts +7 -0
  291. package/src/auth-broker/refresher.ts +117 -0
  292. package/src/auth-broker/remote-store.ts +1332 -0
  293. package/src/auth-broker/server.ts +898 -0
  294. package/src/auth-broker/snapshot-cache.ts +200 -0
  295. package/src/auth-broker/types.ts +193 -0
  296. package/src/auth-broker/wire-schema-resource.ts +487 -0
  297. package/src/auth-broker/wire-schemas.ts +43 -0
  298. package/src/auth-gateway/http.ts +227 -0
  299. package/src/auth-gateway/index.ts +3 -0
  300. package/src/auth-gateway/server.ts +836 -0
  301. package/src/auth-gateway/types.ts +153 -0
  302. package/src/auth-retry.ts +401 -0
  303. package/src/auth-storage.ts +6540 -0
  304. package/src/dialect/anthropic.md +31 -0
  305. package/src/dialect/anthropic.ts +608 -0
  306. package/src/dialect/catalog.ts +29 -0
  307. package/src/dialect/coercion.ts +136 -0
  308. package/src/dialect/deepseek.md +24 -0
  309. package/src/dialect/deepseek.ts +609 -0
  310. package/src/dialect/demotion.ts +40 -0
  311. package/src/dialect/examples.ts +71 -0
  312. package/src/dialect/factory.ts +34 -0
  313. package/src/dialect/fenced-thinking.ts +184 -0
  314. package/src/dialect/gemini.md +44 -0
  315. package/src/dialect/gemini.ts +583 -0
  316. package/src/dialect/gemma.md +33 -0
  317. package/src/dialect/gemma.ts +387 -0
  318. package/src/dialect/glm.md +32 -0
  319. package/src/dialect/glm.ts +579 -0
  320. package/src/dialect/harmony.md +31 -0
  321. package/src/dialect/harmony.ts +345 -0
  322. package/src/dialect/hermes.md +25 -0
  323. package/src/dialect/hermes.ts +206 -0
  324. package/src/dialect/history.ts +81 -0
  325. package/src/dialect/index.ts +15 -0
  326. package/src/dialect/inventory.ts +30 -0
  327. package/src/dialect/kimi.md +24 -0
  328. package/src/dialect/kimi.ts +340 -0
  329. package/src/dialect/minimax.md +31 -0
  330. package/src/dialect/minimax.ts +95 -0
  331. package/src/dialect/owned-stream.ts +481 -0
  332. package/src/dialect/prompt-template.md +12 -0
  333. package/src/dialect/qwen3.md +28 -0
  334. package/src/dialect/qwen3.ts +240 -0
  335. package/src/dialect/rendering.ts +304 -0
  336. package/src/dialect/thinking.ts +292 -0
  337. package/src/dialect/types.ts +56 -0
  338. package/src/dialect/xml.md +22 -0
  339. package/src/dialect/xml.ts +90 -0
  340. package/src/error/abort.ts +18 -0
  341. package/src/error/auth-classify.ts +47 -0
  342. package/src/error/auth.ts +48 -0
  343. package/src/error/aws.ts +35 -0
  344. package/src/error/classes.ts +281 -0
  345. package/src/error/finalize.ts +69 -0
  346. package/src/error/flags.ts +602 -0
  347. package/src/error/format.ts +45 -0
  348. package/src/error/gateway.ts +96 -0
  349. package/src/error/index.ts +14 -0
  350. package/src/error/oauth.ts +58 -0
  351. package/src/error/provider.ts +63 -0
  352. package/src/error/rate-limit.ts +303 -0
  353. package/src/error/retryable.ts +70 -0
  354. package/src/error/validation.ts +44 -0
  355. package/src/index.ts +51 -0
  356. package/src/provider-details.ts +90 -0
  357. package/src/providers/amazon-bedrock.ts +1064 -0
  358. package/src/providers/anthropic-client.ts +317 -0
  359. package/src/providers/anthropic-messages-server-schema.ts +252 -0
  360. package/src/providers/anthropic-messages-server.ts +818 -0
  361. package/src/providers/anthropic-wire.ts +359 -0
  362. package/src/providers/anthropic.ts +4539 -0
  363. package/src/providers/aws-credentials.ts +772 -0
  364. package/src/providers/aws-eventstream.ts +181 -0
  365. package/src/providers/aws-sigv4.ts +218 -0
  366. package/src/providers/azure-openai-responses.ts +438 -0
  367. package/src/providers/bedrock-mantle.ts +110 -0
  368. package/src/providers/claude-code-fingerprint.ts +20 -0
  369. package/src/providers/cowork-fetch.ts +201 -0
  370. package/src/providers/cursor/exec-modern.ts +496 -0
  371. package/src/providers/cursor/proto/agent.proto +4533 -0
  372. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  373. package/src/providers/cursor/proto/buf.yaml +17 -0
  374. package/src/providers/cursor-pi-args.ts +165 -0
  375. package/src/providers/cursor.ts +4689 -0
  376. package/src/providers/devin/proto/buf/validate/validate.proto +468 -0
  377. package/src/providers/devin/proto/buf.gen.yaml +33 -0
  378. package/src/providers/devin/proto/buf.yaml +17 -0
  379. package/src/providers/devin/proto/cel/expr/checked.proto +103 -0
  380. package/src/providers/devin/proto/cel/expr/eval.proto +38 -0
  381. package/src/providers/devin/proto/cel/expr/explain.proto +15 -0
  382. package/src/providers/devin/proto/cel/expr/syntax.proto +113 -0
  383. package/src/providers/devin/proto/cel/expr/value.proto +41 -0
  384. package/src/providers/devin/proto/connectext/grpc/status/v1/status.proto +11 -0
  385. package/src/providers/devin/proto/errorspb/errors.proto +56 -0
  386. package/src/providers/devin/proto/errorspb/hintdetail.proto +7 -0
  387. package/src/providers/devin/proto/errorspb/markers.proto +10 -0
  388. package/src/providers/devin/proto/errorspb/tags.proto +12 -0
  389. package/src/providers/devin/proto/errorspb/testing.proto +6 -0
  390. package/src/providers/devin/proto/exa/analytics_pb/analytics.proto +188 -0
  391. package/src/providers/devin/proto/exa/api_server_pb/api_server.proto +2461 -0
  392. package/src/providers/devin/proto/exa/auth_pb/auth.proto +19 -0
  393. package/src/providers/devin/proto/exa/auto_cascade_common_pb/auto_cascade_common.proto +79 -0
  394. package/src/providers/devin/proto/exa/browser_preview_pb/browser_preview.proto +32 -0
  395. package/src/providers/devin/proto/exa/bug_checker_pb/bug_checker.proto +22 -0
  396. package/src/providers/devin/proto/exa/cascade_plugins_pb/cascade_plugins.proto +262 -0
  397. package/src/providers/devin/proto/exa/chat_client_server_pb/chat_client_server.proto +57 -0
  398. package/src/providers/devin/proto/exa/chat_pb/chat.proto +449 -0
  399. package/src/providers/devin/proto/exa/code_edit/code_edit_pb/code_edit.proto +186 -0
  400. package/src/providers/devin/proto/exa/codeium_common_pb/codeium_common.proto +4157 -0
  401. package/src/providers/devin/proto/exa/context_module_pb/context_module.proto +175 -0
  402. package/src/providers/devin/proto/exa/cortex_pb/cortex.proto +3268 -0
  403. package/src/providers/devin/proto/exa/dev_pb/dev.proto +26 -0
  404. package/src/providers/devin/proto/exa/diff_action_pb/diff_action.proto +75 -0
  405. package/src/providers/devin/proto/exa/eval/pr_eval/datasets_pb/datasets.proto +103 -0
  406. package/src/providers/devin/proto/exa/eval_pb/eval.proto +1315 -0
  407. package/src/providers/devin/proto/exa/extension_server_pb/extension_server.proto +556 -0
  408. package/src/providers/devin/proto/exa/file_system_provider_pb/file_system_provider.proto +75 -0
  409. package/src/providers/devin/proto/exa/index_pb/index.proto +461 -0
  410. package/src/providers/devin/proto/exa/knowledge_base_pb/knowledge_base.proto +144 -0
  411. package/src/providers/devin/proto/exa/language_server_pb/language_server.proto +2385 -0
  412. package/src/providers/devin/proto/exa/model_management_pb/model_management.proto +186 -0
  413. package/src/providers/devin/proto/exa/opensearch_clients_pb/opensearch_clients.proto +503 -0
  414. package/src/providers/devin/proto/exa/product_analytics_pb/product_analytics.proto +37 -0
  415. package/src/providers/devin/proto/exa/prompt_pb/prompt.proto +92 -0
  416. package/src/providers/devin/proto/exa/reactive_component_pb/reactive_component.proto +96 -0
  417. package/src/providers/devin/proto/exa/seat_management_pb/seat_management.proto +2680 -0
  418. package/src/providers/devin/proto/exa/tokenizer_pb/tokenizer.proto +37 -0
  419. package/src/providers/devin/proto/exa/trainer_pb/config.proto +647 -0
  420. package/src/providers/devin/proto/exa/tree_sitter/language_data_pb/language_data.proto +14 -0
  421. package/src/providers/devin/proto/exa/trust_pb/trust.proto +157 -0
  422. package/src/providers/devin/proto/exa/user_analytics_pb/user_analytics.proto +519 -0
  423. package/src/providers/devin/proto/google.golang.org/appengine/internal/base/api_base.proto +28 -0
  424. package/src/providers/devin/proto/google.golang.org/appengine/internal/datastore/datastore_v3.proto +484 -0
  425. package/src/providers/devin/proto/google.golang.org/appengine/internal/log/log_service.proto +136 -0
  426. package/src/providers/devin/proto/google.golang.org/appengine/internal/remote_api/remote_api.proto +42 -0
  427. package/src/providers/devin/proto/google.golang.org/appengine/internal/urlfetch/urlfetch_service.proto +61 -0
  428. package/src/providers/devin/proto/grpc/binlog/v1/binarylog.proto +84 -0
  429. package/src/providers/devin/proto/io/prometheus/client/metrics.proto +98 -0
  430. package/src/providers/devin.ts +679 -0
  431. package/src/providers/error-message.ts +23 -0
  432. package/src/providers/github-copilot-headers.ts +141 -0
  433. package/src/providers/gitlab-duo-workflow-chatml-note.md +1 -0
  434. package/src/providers/gitlab-duo-workflow.ts +3135 -0
  435. package/src/providers/gitlab-duo.ts +399 -0
  436. package/src/providers/google-auth.ts +330 -0
  437. package/src/providers/google-gemini-cli.ts +1370 -0
  438. package/src/providers/google-shared.ts +1122 -0
  439. package/src/providers/google-types.ts +180 -0
  440. package/src/providers/google-vertex.ts +135 -0
  441. package/src/providers/google.ts +47 -0
  442. package/src/providers/grammar.ts +70 -0
  443. package/src/providers/kimi.ts +51 -0
  444. package/src/providers/mock.ts +514 -0
  445. package/src/providers/ollama.ts +776 -0
  446. package/src/providers/openai-anthropic-shim.ts +166 -0
  447. package/src/providers/openai-chat-server-schema.ts +243 -0
  448. package/src/providers/openai-chat-server.ts +752 -0
  449. package/src/providers/openai-chat-wire.ts +859 -0
  450. package/src/providers/openai-codex/request-transformer.ts +491 -0
  451. package/src/providers/openai-codex/response-handler.ts +102 -0
  452. package/src/providers/openai-codex-responses.ts +4716 -0
  453. package/src/providers/openai-completions.ts +2389 -0
  454. package/src/providers/openai-reasoning-fallback.ts +269 -0
  455. package/src/providers/openai-responses-server-schema.ts +397 -0
  456. package/src/providers/openai-responses-server.ts +1466 -0
  457. package/src/providers/openai-responses-wire.ts +6416 -0
  458. package/src/providers/openai-responses.ts +1393 -0
  459. package/src/providers/openai-shared.ts +3500 -0
  460. package/src/providers/pi-native-client.ts +275 -0
  461. package/src/providers/pi-native-server.ts +245 -0
  462. package/src/providers/register-builtins.ts +503 -0
  463. package/src/providers/synthetic.ts +50 -0
  464. package/src/providers/transform-messages.ts +1083 -0
  465. package/src/providers/vision-guard.ts +54 -0
  466. package/src/registry/aiand.ts +22 -0
  467. package/src/registry/aimlapi.ts +6 -0
  468. package/src/registry/alibaba-coding-plan.ts +104 -0
  469. package/src/registry/alibaba-token-plan.ts +125 -0
  470. package/src/registry/amazon-bedrock.ts +22 -0
  471. package/src/registry/anthropic.ts +26 -0
  472. package/src/registry/api-key-login.ts +115 -0
  473. package/src/registry/api-key-validation.ts +145 -0
  474. package/src/registry/aws.ts +57 -0
  475. package/src/registry/azure.ts +6 -0
  476. package/src/registry/baseten.ts +22 -0
  477. package/src/registry/bedrock-mantle.ts +34 -0
  478. package/src/registry/cerebras.ts +23 -0
  479. package/src/registry/cloudflare-ai-gateway.ts +45 -0
  480. package/src/registry/coreweave.ts +40 -0
  481. package/src/registry/cursor.ts +20 -0
  482. package/src/registry/deepseek.ts +46 -0
  483. package/src/registry/derived.ts +9 -0
  484. package/src/registry/devin.ts +15 -0
  485. package/src/registry/exa.ts +19 -0
  486. package/src/registry/firepass.ts +32 -0
  487. package/src/registry/fireworks.ts +28 -0
  488. package/src/registry/github-copilot.ts +22 -0
  489. package/src/registry/gitlab-duo-workflow.ts +20 -0
  490. package/src/registry/gitlab-duo.ts +19 -0
  491. package/src/registry/gmi-cloud.ts +22 -0
  492. package/src/registry/google-antigravity.ts +22 -0
  493. package/src/registry/google-gemini-cli.ts +22 -0
  494. package/src/registry/google-vertex.ts +38 -0
  495. package/src/registry/google.ts +6 -0
  496. package/src/registry/groq.ts +6 -0
  497. package/src/registry/huggingface.ts +29 -0
  498. package/src/registry/index.ts +4 -0
  499. package/src/registry/kagi.ts +46 -0
  500. package/src/registry/kilo.ts +114 -0
  501. package/src/registry/kimi-code.ts +17 -0
  502. package/src/registry/litellm.ts +45 -0
  503. package/src/registry/llama-cpp.ts +35 -0
  504. package/src/registry/lm-studio.ts +31 -0
  505. package/src/registry/meta.ts +22 -0
  506. package/src/registry/minimax-code-cn.ts +12 -0
  507. package/src/registry/minimax-code.ts +12 -0
  508. package/src/registry/minimax.ts +6 -0
  509. package/src/registry/mistral.ts +6 -0
  510. package/src/registry/moonshot.ts +28 -0
  511. package/src/registry/nanogpt.ts +22 -0
  512. package/src/registry/novita.ts +25 -0
  513. package/src/registry/nvidia.ts +61 -0
  514. package/src/registry/oauth/anthropic-constants.ts +12 -0
  515. package/src/registry/oauth/anthropic.ts +346 -0
  516. package/src/registry/oauth/callback-server.ts +438 -0
  517. package/src/registry/oauth/cursor.ts +187 -0
  518. package/src/registry/oauth/device-code.ts +92 -0
  519. package/src/registry/oauth/devin.ts +124 -0
  520. package/src/registry/oauth/github-copilot.ts +369 -0
  521. package/src/registry/oauth/gitlab-duo-workflow.ts +146 -0
  522. package/src/registry/oauth/gitlab-duo.ts +222 -0
  523. package/src/registry/oauth/google-antigravity.ts +225 -0
  524. package/src/registry/oauth/google-gemini-cli.ts +297 -0
  525. package/src/registry/oauth/google-oauth-shared.ts +211 -0
  526. package/src/registry/oauth/index.ts +187 -0
  527. package/src/registry/oauth/kimi.ts +297 -0
  528. package/src/registry/oauth/minimax-code.ts +53 -0
  529. package/src/registry/oauth/oauth.html +317 -0
  530. package/src/registry/oauth/openai-codex.ts +384 -0
  531. package/src/registry/oauth/opencode.ts +50 -0
  532. package/src/registry/oauth/perplexity.ts +228 -0
  533. package/src/registry/oauth/pkce.ts +18 -0
  534. package/src/registry/oauth/types.ts +96 -0
  535. package/src/registry/oauth/wafer.ts +24 -0
  536. package/src/registry/oauth/xai-oauth.ts +559 -0
  537. package/src/registry/oauth/xiaomi.ts +211 -0
  538. package/src/registry/oauth/zai.ts +285 -0
  539. package/src/registry/ollama-cloud.ts +36 -0
  540. package/src/registry/ollama.ts +43 -0
  541. package/src/registry/openai-codex-device.ts +18 -0
  542. package/src/registry/openai-codex.ts +19 -0
  543. package/src/registry/openai.ts +6 -0
  544. package/src/registry/opencode-go.ts +12 -0
  545. package/src/registry/opencode-zen.ts +12 -0
  546. package/src/registry/openrouter.ts +28 -0
  547. package/src/registry/parallel.ts +45 -0
  548. package/src/registry/perplexity.ts +13 -0
  549. package/src/registry/qianfan.ts +27 -0
  550. package/src/registry/qwen-portal.ts +50 -0
  551. package/src/registry/registry.ts +182 -0
  552. package/src/registry/sakana.ts +22 -0
  553. package/src/registry/siliconflow-cn.ts +22 -0
  554. package/src/registry/siliconflow.ts +22 -0
  555. package/src/registry/synthetic.ts +21 -0
  556. package/src/registry/tavily.ts +45 -0
  557. package/src/registry/together.ts +22 -0
  558. package/src/registry/types.ts +86 -0
  559. package/src/registry/umans.ts +23 -0
  560. package/src/registry/venice.ts +33 -0
  561. package/src/registry/vercel-ai-gateway.ts +38 -0
  562. package/src/registry/vllm.ts +34 -0
  563. package/src/registry/wafer-serverless.ts +12 -0
  564. package/src/registry/xai-oauth.ts +17 -0
  565. package/src/registry/xai.ts +22 -0
  566. package/src/registry/xiaomi-token-plan-ams.ts +12 -0
  567. package/src/registry/xiaomi-token-plan-cn.ts +12 -0
  568. package/src/registry/xiaomi-token-plan-sgp.ts +12 -0
  569. package/src/registry/xiaomi.ts +12 -0
  570. package/src/registry/zai.ts +41 -0
  571. package/src/registry/zenmux.ts +22 -0
  572. package/src/registry/zhipu-coding-plan.ts +27 -0
  573. package/src/stream.ts +1944 -0
  574. package/src/types.ts +1243 -0
  575. package/src/usage/alibaba-token-plan.ts +230 -0
  576. package/src/usage/claude.ts +830 -0
  577. package/src/usage/cursor.ts +335 -0
  578. package/src/usage/gemini.ts +258 -0
  579. package/src/usage/github-copilot.ts +424 -0
  580. package/src/usage/google-antigravity.ts +497 -0
  581. package/src/usage/kimi.ts +277 -0
  582. package/src/usage/minimax-code.ts +291 -0
  583. package/src/usage/ollama.ts +41 -0
  584. package/src/usage/openai-codex-base-url.ts +35 -0
  585. package/src/usage/openai-codex-reset.ts +205 -0
  586. package/src/usage/openai-codex.ts +627 -0
  587. package/src/usage/opencode-go.ts +89 -0
  588. package/src/usage/shared.ts +10 -0
  589. package/src/usage/synthetic.ts +180 -0
  590. package/src/usage/umans.ts +192 -0
  591. package/src/usage/xai-oauth.ts +414 -0
  592. package/src/usage/zai.ts +370 -0
  593. package/src/usage.ts +411 -0
  594. package/src/utils/abort.ts +67 -0
  595. package/src/utils/anthropic-auth.ts +93 -0
  596. package/src/utils/aws-profile.ts +88 -0
  597. package/src/utils/block-symbols.ts +78 -0
  598. package/src/utils/deterministic-id.ts +20 -0
  599. package/src/utils/empty-completion-retry.ts +161 -0
  600. package/src/utils/event-stream.ts +202 -0
  601. package/src/utils/foundry.ts +8 -0
  602. package/src/utils/google-validation.ts +25 -0
  603. package/src/utils/harmony-leak.ts +500 -0
  604. package/src/utils/http-inspector.ts +196 -0
  605. package/src/utils/idle-iterator.ts +531 -0
  606. package/src/utils/leaked-thinking-stream.ts +483 -0
  607. package/src/utils/openai-http.ts +119 -0
  608. package/src/utils/openrouter-headers.ts +12 -0
  609. package/src/utils/parse-bind.ts +56 -0
  610. package/src/utils/provider-response.ts +30 -0
  611. package/src/utils/proxy.ts +314 -0
  612. package/src/utils/request-debug.ts +351 -0
  613. package/src/utils/retry-after.ts +121 -0
  614. package/src/utils/retry.ts +77 -0
  615. package/src/utils/schema/CONSTRAINTS.md +168 -0
  616. package/src/utils/schema/adapt.ts +36 -0
  617. package/src/utils/schema/compatibility.ts +435 -0
  618. package/src/utils/schema/dereference.ts +98 -0
  619. package/src/utils/schema/draft.ts +341 -0
  620. package/src/utils/schema/equality.ts +97 -0
  621. package/src/utils/schema/fields.ts +210 -0
  622. package/src/utils/schema/index.ts +14 -0
  623. package/src/utils/schema/json-schema-validator.ts +595 -0
  624. package/src/utils/schema/meta-validator.ts +167 -0
  625. package/src/utils/schema/normalize.ts +2314 -0
  626. package/src/utils/schema/spill.ts +43 -0
  627. package/src/utils/schema/stamps.ts +109 -0
  628. package/src/utils/schema/strict-tool-validation.ts +117 -0
  629. package/src/utils/schema/types.ts +10 -0
  630. package/src/utils/schema/typescript.ts +212 -0
  631. package/src/utils/schema/wire.ts +662 -0
  632. package/src/utils/sdk-stream-timeout.ts +43 -0
  633. package/src/utils/sse-debug.ts +18 -0
  634. package/src/utils/stream-markup-healing.ts +247 -0
  635. package/src/utils/thinking-loop.ts +552 -0
  636. package/src/utils/tool-call-loop-guard.ts +107 -0
  637. package/src/utils/tool-choice.ts +101 -0
  638. package/src/utils/validation.ts +1932 -0
  639. package/src/utils.ts +492 -0
@@ -0,0 +1,836 @@
1
+ /**
2
+ * omp auth-gateway HTTP server.
3
+ *
4
+ * Accepts any provider-format request (OpenAI chat-completions, Anthropic
5
+ * messages, OpenAI Responses) and dispatches through pi-ai's `streamSimple()`
6
+ * — which handles credential injection, anthropic-beta headers, codex
7
+ * websocket transport, and all the per-provider intricacies. The gateway is
8
+ * pure protocol translation: foreign wire → omp Context → pi-ai stream() →
9
+ * omp events → foreign wire.
10
+ *
11
+ * Endpoints:
12
+ * GET /healthz → unauth; ok + version
13
+ * GET /v1/usage → aggregated provider usage (5-min per-credential cache via AuthStorage)
14
+ * GET /v1/credentials/check → per-credential auth probe (diagnose 401s in a multi-account pool)
15
+ * GET /v1/models → list known models from the registry
16
+ * POST /v1/chat/completions → OpenAI chat-completions in/out
17
+ * POST /v1/messages → Anthropic messages in/out
18
+ * POST /v1/responses → OpenAI Responses in/out
19
+ */
20
+
21
+ import { Effort } from "@linxiraos/pi-catalog/effort";
22
+ import { extractHttpStatusFromError, extractRetryHint, logger } from "@linxiraos/pi-utils";
23
+ import type { ApiKeyResolver } from "../auth-retry";
24
+ import type { AuthStorage } from "../auth-storage";
25
+ import * as AIError from "../error";
26
+ import { classifyGatewayError } from "../error/gateway";
27
+ import { isUsageLimitOutcome } from "../error/rate-limit";
28
+ import * as anthropicMessages from "../providers/anthropic-messages-server";
29
+ import * as openaiChat from "../providers/openai-chat-server";
30
+ import * as openaiResponses from "../providers/openai-responses-server";
31
+ import * as piNative from "../providers/pi-native-server";
32
+ import { completeSimple, streamSimple } from "../stream";
33
+ import type { Api, AssistantMessageEventStream, Context, Model, SimpleStreamOptions } from "../types";
34
+ import { deterministicUuid } from "../utils/deterministic-id";
35
+ import { parseBind } from "../utils/parse-bind";
36
+ import {
37
+ captureRequestHeaders,
38
+ corsHeaders,
39
+ gatewayResponseHeaders,
40
+ isAuthorized,
41
+ json,
42
+ resolvePeer,
43
+ withCors,
44
+ } from "./http";
45
+ import type {
46
+ AuthGatewayServerHandle,
47
+ AuthGatewayServerOptions,
48
+ AuthGatewayFormatModule as FormatModule,
49
+ AuthGatewayParsedRequest as ParsedFormatRequest,
50
+ } from "./types";
51
+ import { DEFAULT_AUTH_GATEWAY_BIND } from "./types";
52
+
53
+ // ParsedFormatRequest / ParsedFormatOptions / FormatModule come from ./types.
54
+
55
+ export type ModelResolver = (modelId: string) => Model<Api> | undefined;
56
+
57
+ export interface AuthGatewayBootOptions extends AuthGatewayServerOptions {
58
+ /** Source of credentials. Caller wires this to a broker-backed AuthStorage. */
59
+ storage: AuthStorage;
60
+ /**
61
+ * Resolve a client-requested model id to a pi-ai Model. Caller supplies
62
+ * this from a ModelRegistry (lives in `coding-agent` to avoid an inverse
63
+ * dependency in `pi-ai`).
64
+ */
65
+ resolveModel: ModelResolver;
66
+ /** Optional supplier for `/v1/models` listing. Returns the full model array. */
67
+ listModels?: () => Iterable<Model<Api>>;
68
+ }
69
+
70
+ // `parseBind` lives in ../utils/parse-bind so the gateway and broker can't
71
+ // drift on accepted inputs (e.g. empty hostname, IPv6 brackets).
72
+
73
+ const FORMAT_ROUTES: Record<string, { module: FormatModule; label: string }> = {
74
+ "/v1/chat/completions": { module: openaiChat, label: "openai-chat" },
75
+ "/v1/messages": { module: anthropicMessages, label: "anthropic-messages" },
76
+ "/v1/responses": { module: openaiResponses, label: "openai-responses" },
77
+ };
78
+
79
+ // (passthrough fast-path removed — it bypassed pi-ai provider logic, in
80
+ // particular the Anthropic Claude-Code OAuth system-prompt prefix injection.
81
+ // Every request now takes the translate path so credential-specific request
82
+ // shaping always applies.)
83
+
84
+ // Options the caller's wire format may carry but the resolved provider can't
85
+ // honour are dropped silently in `buildStreamOptions`. We used to 400 here
86
+ // (`Unsupported option: temperature for openai-codex-responses`), but every
87
+ // realistic client (llm-git, openai SDK, anthropic SDK) bakes some of these
88
+ // defaults in without knowing which model they'll resolve to. Failing loudly
89
+ // just turned that into per-call config hell. Silent strip is what the
90
+ // upstream provider would do anyway when it ignores extra fields.
91
+
92
+ /**
93
+ * Derive a stable cache identity from the parts of the request that don't
94
+ * change turn-to-turn within a logical conversation: model id, system prompt,
95
+ * tool definitions, and the first message (the conversation seed). Codex-class
96
+ * backends only cache prefixes when an explicit `prompt_cache_key` is set;
97
+ * without one, two requests with the same prefix but different trailing
98
+ * messages don't coalesce. This bridges Anthropic-style clients (which signal
99
+ * caching via `cache_control` markers rather than an opaque key) to Codex's
100
+ * keyed model so cross-protocol caching "just works".
101
+ *
102
+ * Including the first message scopes the key to one logical conversation:
103
+ * two different chats with the same system prompt no longer share a cache
104
+ * bucket and can't trample each other's prefix-tree entries.
105
+ *
106
+ * Anthropic-backed requests ignore `sessionId`; the key is harmless there.
107
+ */
108
+ function deriveSessionId(modelId: string, context: Context): string {
109
+ const parts: string[] = [modelId];
110
+ if (context.systemPrompt && context.systemPrompt.length > 0) {
111
+ parts.push(context.systemPrompt.join("\n\n"));
112
+ }
113
+ if (context.tools && context.tools.length > 0) {
114
+ parts.push(JSON.stringify(context.tools));
115
+ }
116
+ const first = context.messages?.[0];
117
+ if (first) {
118
+ // Strip timestamp / provider metadata so the hash is stable across turns
119
+ // of the same conversation (omp re-stamps every parsed Message). role +
120
+ // content is what's actually on the wire.
121
+ parts.push(JSON.stringify({ role: first.role, content: first.content }));
122
+ }
123
+ const seed = parts.join("\u0000");
124
+ // The 36-char UUID flows through unchanged:
125
+ // `normalizeOpenAIPromptCacheKey` accepts ≤64 chars verbatim.
126
+ return deterministicUuid(seed);
127
+ }
128
+
129
+ function buildStreamOptions(parsed: ParsedFormatRequest, api: Api, signal: AbortSignal): SimpleStreamOptions {
130
+ const opts: SimpleStreamOptions = { signal };
131
+ const { options } = parsed;
132
+ // Codex backend rejects every sampling control with
133
+ // `Unsupported parameter: …` (#3117). Strip the full set for that one
134
+ // provider; everything else is harmless to forward — `streamSimple` ignores
135
+ // what the underlying provider doesn't honour.
136
+ const isCodex = api === "openai-codex-responses";
137
+ if (options.maxOutputTokens !== undefined) opts.maxTokens = options.maxOutputTokens;
138
+ if (options.temperature !== undefined && !isCodex) opts.temperature = options.temperature;
139
+ if (options.topP !== undefined && !isCodex) opts.topP = options.topP;
140
+ if (options.topK !== undefined && !isCodex) opts.topK = options.topK;
141
+ if (options.minP !== undefined && !isCodex) opts.minP = options.minP;
142
+ if (options.stopSequences !== undefined && !isCodex) opts.stopSequences = options.stopSequences;
143
+ if (options.presencePenalty !== undefined && !isCodex) opts.presencePenalty = options.presencePenalty;
144
+ if (options.frequencyPenalty !== undefined && !isCodex) opts.frequencyPenalty = options.frequencyPenalty;
145
+ if (options.repetitionPenalty !== undefined && !isCodex) opts.repetitionPenalty = options.repetitionPenalty;
146
+ if (options.metadata !== undefined) opts.metadata = options.metadata;
147
+ if (options.headers !== undefined) opts.headers = { ...(opts.headers ?? {}), ...options.headers };
148
+ if (options.toolChoice !== undefined) {
149
+ opts.toolChoice =
150
+ typeof options.toolChoice !== "object"
151
+ ? options.toolChoice
152
+ : "type" in options.toolChoice
153
+ ? options.toolChoice
154
+ : { type: "tool", name: options.toolChoice.name };
155
+ }
156
+ if (options.reasoning !== undefined) opts.reasoning = options.reasoning;
157
+ if (options.disableReasoning !== undefined) opts.disableReasoning = options.disableReasoning;
158
+ if (options.hideThinkingSummary !== undefined) opts.hideThinkingSummary = options.hideThinkingSummary;
159
+ if (options.taskBudget !== undefined) opts.taskBudget = options.taskBudget;
160
+ if (options.serviceTier !== undefined) opts.serviceTier = options.serviceTier;
161
+ if (options.cacheRetention !== undefined) opts.cacheRetention = options.cacheRetention;
162
+ if (options.include !== undefined) opts.include = options.include;
163
+ // Client-supplied `prompt_cache_key` wins; otherwise derive a stable
164
+ // key from the model + system + tools so prefix caching engages on
165
+ // Codex-class backends across turns of the same logical conversation.
166
+ const promptCacheKey = options.promptCacheKey ?? deriveSessionId(parsed.modelId, parsed.context);
167
+ opts.promptCacheKey = promptCacheKey;
168
+ opts.sessionId = promptCacheKey;
169
+ if (options.thinkingBudgets) {
170
+ opts.thinkingBudgets = { ...(opts.thinkingBudgets ?? {}), ...options.thinkingBudgets };
171
+ }
172
+ if (options.explicitThinkingBudgetTokens !== undefined) {
173
+ // Mirror Rust's `resolve_thinking_budget`: explicit budget pins onto
174
+ // whichever effort the client requested (or High when unspecified) and
175
+ // ALSO sets the effort so providers that gate on `reasoning` actually
176
+ // surface the budget.
177
+ const effort = options.reasoning ?? Effort.High;
178
+ opts.thinkingBudgets = {
179
+ ...(opts.thinkingBudgets ?? {}),
180
+ [effort]: options.explicitThinkingBudgetTokens,
181
+ };
182
+ opts.reasoning ??= effort;
183
+ }
184
+ // Fields that don't yet have a matching pi-ai `SimpleStreamOptions` slot.
185
+ // Surfaced once in debug logs so they show up when wiring a new provider,
186
+ // but NEVER widened into `options.extra` — every consumer would have to
187
+ // re-implement the typed parse to read them back out.
188
+ // TODO(pi-ai): land first-class fields and replace these blocks.
189
+ if (
190
+ options.parallelToolCalls !== undefined ||
191
+ options.previousResponseId !== undefined ||
192
+ options.seed !== undefined ||
193
+ options.logitBias !== undefined ||
194
+ options.user !== undefined ||
195
+ options.responseFormat !== undefined
196
+ ) {
197
+ logger.debug("auth-gateway dropped unsupported typed options", {
198
+ api,
199
+ parallelToolCalls: options.parallelToolCalls,
200
+ previousResponseId: options.previousResponseId,
201
+ seed: options.seed,
202
+ hasLogitBias: options.logitBias !== undefined,
203
+ user: options.user,
204
+ hasResponseFormat: options.responseFormat !== undefined,
205
+ });
206
+ }
207
+ return opts;
208
+ }
209
+
210
+ /**
211
+ * Hook fired by {@link streamSimple} when the upstream request fails in a
212
+ * way that's rotatable — today that's HTTP 401 (credential is bad) and
213
+ * usage-limit phrasing matched by {@link isUsageLimitError} (Codex's
214
+ * `usage_limit_reached`, Anthropic's `usage_limit_reached`, Google's
215
+ * `resource_exhausted`, …). The two cases need different storage actions:
216
+ *
217
+ * - **usage-limit** → {@link AuthStorage.markUsageLimitReached}. Marks just
218
+ * the current session's credential as temporarily blocked (honouring
219
+ * `retry-after` / `resets_at` hints when present) and returns `true` only
220
+ * when a sibling credential is still available. Burning the credential
221
+ * with `invalidateCredentialMatching` here would orphan accounts whose
222
+ * reset window is several hours away — exactly the bug this helper exists
223
+ * to avoid.
224
+ * - **auth-failure** → {@link AuthStorage.invalidateCredentialMatching}.
225
+ * Suspect/delete the row so it doesn't get re-picked next request.
226
+ *
227
+ * In both branches we return the next `getApiKey` result (sticky on the
228
+ * same `sessionId`) so streamSimple can transparently retry the pre-emit
229
+ * failure with a fresh credential. Returning `undefined` aborts the retry
230
+ * and surfaces the original error to the caller.
231
+ */
232
+ async function refreshGatewayApiKeyAfterAuthError(
233
+ storage: AuthStorage,
234
+ model: Model<Api>,
235
+ sessionId: string,
236
+ provider: string,
237
+ oldKey: string,
238
+ error: unknown,
239
+ signal: AbortSignal,
240
+ format: string,
241
+ peer: string,
242
+ ): Promise<string | undefined> {
243
+ const message = error instanceof Error ? error.message : String(error);
244
+ const status = extractHttpStatusFromError(error);
245
+ if (AIError.isUsageLimit(error) || isUsageLimitOutcome(status, message)) {
246
+ const retryAfterMs = extractRetryHint(undefined, message);
247
+ const { switched, retryAtMs } = await storage.markUsageLimitReached(provider, sessionId, {
248
+ retryAfterMs,
249
+ baseUrl: model.baseUrl,
250
+ modelId: model.id,
251
+ apiKey: oldKey,
252
+ signal,
253
+ });
254
+ logger.debug("auth-gateway retrying provider request after usage-limit block", {
255
+ format,
256
+ provider,
257
+ peer,
258
+ switched,
259
+ retryAfterMs,
260
+ retryAtMs,
261
+ error: message,
262
+ });
263
+ if (!switched) return undefined;
264
+ return storage.getApiKey(provider, sessionId, { modelId: model.id, signal });
265
+ }
266
+ await storage.invalidateCredentialMatching(provider, oldKey, { sessionId, signal });
267
+ logger.debug("auth-gateway retrying provider request after credential invalidation", {
268
+ format,
269
+ provider,
270
+ peer,
271
+ error: message,
272
+ });
273
+ return storage.getApiKey(provider, sessionId, { modelId: model.id, signal });
274
+ }
275
+
276
+ /**
277
+ * Build the {@link ApiKeyResolver} handed to `streamSimple` for a gateway
278
+ * request. Drives the central a/b/c auth-retry policy server-side:
279
+ *
280
+ * - initial resolve → the credential already resolved for this request.
281
+ * - step (b) `!lastChance` → force-refresh the SAME session-sticky credential
282
+ * (a peer/broker may have rotated its token out from under our cached copy).
283
+ * - step (c) `lastChance` → {@link refreshGatewayApiKeyAfterAuthError} switches
284
+ * to a sibling (usage-limit block vs credential invalidation by error class).
285
+ *
286
+ * `lastKey` tracks the most recent bearer so the switch step invalidates the
287
+ * credential that actually failed.
288
+ */
289
+ function buildGatewayApiKeyResolver(
290
+ storage: AuthStorage,
291
+ model: Model<Api>,
292
+ sessionId: string,
293
+ initialKey: string,
294
+ requestSignal: AbortSignal,
295
+ format: string,
296
+ peer: string,
297
+ ): ApiKeyResolver {
298
+ let lastKey = initialKey;
299
+ return async ({ lastChance, error, signal }) => {
300
+ const sig = signal ?? requestSignal;
301
+ if (error === undefined) {
302
+ lastKey = initialKey;
303
+ return initialKey;
304
+ }
305
+ if (!lastChance) {
306
+ const refreshed = await storage.getApiKey(model.provider, sessionId, {
307
+ modelId: model.id,
308
+ signal: sig,
309
+ forceRefresh: true,
310
+ });
311
+ lastKey = refreshed ?? lastKey;
312
+ return refreshed;
313
+ }
314
+ const next = await refreshGatewayApiKeyAfterAuthError(
315
+ storage,
316
+ model,
317
+ sessionId,
318
+ model.provider,
319
+ lastKey,
320
+ error,
321
+ sig,
322
+ format,
323
+ peer,
324
+ );
325
+ lastKey = next ?? lastKey;
326
+ return next;
327
+ };
328
+ }
329
+
330
+ function clientClosedResponse(route: { module: FormatModule }): Response {
331
+ return route.module.formatError(499, "request_aborted", "client closed request");
332
+ }
333
+
334
+ function mirrorRequestAbort(req: Request): AbortController {
335
+ const controller = new AbortController();
336
+ if (req.signal.aborted) {
337
+ controller.abort(req.signal.reason);
338
+ } else {
339
+ req.signal.addEventListener("abort", () => controller.abort(req.signal.reason), { once: true });
340
+ }
341
+ return controller;
342
+ }
343
+
344
+ // (handlePassthrough removed — see note above.)
345
+
346
+ async function handleFormatEndpoint(
347
+ route: { module: FormatModule; label: string },
348
+ bootOpts: AuthGatewayBootOptions,
349
+ req: Request,
350
+ peer: string,
351
+ ): Promise<Response> {
352
+ const startedAt = performance.now();
353
+ const requestId = crypto.randomUUID();
354
+ const controller = mirrorRequestAbort(req);
355
+ if (controller.signal.aborted) return clientClosedResponse(route);
356
+
357
+ let body: unknown;
358
+ try {
359
+ body = await req.json();
360
+ } catch (error) {
361
+ if (controller.signal.aborted) return clientClosedResponse(route);
362
+ return route.module.formatError(400, "invalid_request_error", `Invalid JSON body: ${String(error)}`);
363
+ }
364
+ if (controller.signal.aborted) return clientClosedResponse(route);
365
+
366
+ // All three supported wire formats put the model id on a top-level `model`
367
+ // field. Read it without running the full strict schema so the route can
368
+ // produce a coherent error envelope when the model id is missing.
369
+ const modelId =
370
+ typeof body === "object" && body !== null && typeof (body as { model?: unknown }).model === "string"
371
+ ? (body as { model: string }).model
372
+ : undefined;
373
+ if (!modelId) {
374
+ return route.module.formatError(400, "invalid_request_error", "Missing top-level `model` field");
375
+ }
376
+
377
+ const model = bootOpts.resolveModel(modelId);
378
+ if (!model) {
379
+ return route.module.formatError(404, "invalid_request_error", `Unknown model: ${modelId}`);
380
+ }
381
+
382
+ // Parse the wire-format request BEFORE resolving the credential so we
383
+ // have a stable per-conversation `sessionId` to thread into AuthStorage.
384
+ // Sticky-credential tracking and `markUsageLimitReached` both key off
385
+ // this id; without it `getApiKey` would re-roundrobin every request
386
+ // and `markUsageLimitReached` would no-op (it can only mark the
387
+ // credential it last handed out to that session).
388
+ let parsed: ParsedFormatRequest;
389
+ try {
390
+ parsed = route.module.parseRequest(body, req.headers);
391
+ } catch (error) {
392
+ if (controller.signal.aborted) return clientClosedResponse(route);
393
+ const message = error instanceof Error ? error.message : String(error);
394
+ return route.module.formatError(400, "invalid_request_error", message);
395
+ }
396
+ // Merge gateway-captured passthrough headers under the parser's own
397
+ // captures. Parsers that set `options.headers` themselves win (they may
398
+ // have stripped or normalized values); the gateway's allow-list fills in
399
+ // anything they didn't touch.
400
+ {
401
+ const captured = captureRequestHeaders(req.headers);
402
+ parsed.options.headers = { ...captured, ...(parsed.options.headers ?? {}) };
403
+ }
404
+ if (controller.signal.aborted) return clientClosedResponse(route);
405
+
406
+ // Sticky credential id: honour the client's `prompt_cache_key` when
407
+ // supplied (so external session ids align), otherwise derive from
408
+ // modelId + system + tools + first message. Mirrored into
409
+ // streamOpts.sessionId / promptCacheKey by `buildStreamOptions`.
410
+ const sessionId = parsed.options.promptCacheKey ?? deriveSessionId(parsed.modelId, parsed.context);
411
+ parsed.options.promptCacheKey ??= sessionId;
412
+
413
+ // pi-ai's stream() does NOT consult AuthStorage — the caller (us) is
414
+ // expected to resolve the credential and pass it as `options.apiKey`.
415
+ // For OAuth providers this returns the access token (refreshed via the
416
+ // broker override on AuthStorage when needed).
417
+ let apiKey: string | undefined;
418
+ try {
419
+ apiKey = await bootOpts.storage.getApiKey(model.provider, sessionId, {
420
+ modelId: model.id,
421
+ signal: controller.signal,
422
+ });
423
+ } catch (error) {
424
+ if (controller.signal.aborted) return clientClosedResponse(route);
425
+ const classified = classifyGatewayError(error);
426
+ logger.warn("auth-gateway getApiKey threw", { provider: model.provider, peer, error: classified.message });
427
+ return route.module.formatError(classified.status, classified.type, classified.message);
428
+ }
429
+ if (controller.signal.aborted) return clientClosedResponse(route);
430
+ if (!apiKey) {
431
+ return route.module.formatError(
432
+ 401,
433
+ "authentication_error",
434
+ `No credential available for provider ${model.provider}`,
435
+ );
436
+ }
437
+
438
+ const streamOpts = buildStreamOptions(parsed, model.api, controller.signal);
439
+ streamOpts.apiKey = buildGatewayApiKeyResolver(
440
+ bootOpts.storage,
441
+ model,
442
+ sessionId,
443
+ apiKey,
444
+ controller.signal,
445
+ route.label,
446
+ peer,
447
+ );
448
+
449
+ logger.info("auth-gateway request", {
450
+ requestId,
451
+ format: route.label,
452
+ model: parsed.modelId,
453
+ resolvedProvider: model.provider,
454
+ resolvedModel: model.id,
455
+ stream: parsed.stream,
456
+ peer,
457
+ });
458
+
459
+ if (!parsed.stream) {
460
+ try {
461
+ if (controller.signal.aborted) return clientClosedResponse(route);
462
+ const message = await completeSimple(model, parsed.context, streamOpts);
463
+ if (message.stopReason === "aborted" || message.stopReason === "error") {
464
+ const errorMessage =
465
+ message.errorMessage ??
466
+ (message.stopReason === "aborted" ? "Request was aborted" : "Upstream request failed");
467
+ logger.warn("auth-gateway non-streaming failed", {
468
+ format: route.label,
469
+ reason: message.stopReason,
470
+ error: errorMessage,
471
+ peer,
472
+ });
473
+ if (message.stopReason === "aborted") {
474
+ return route.module.formatError(499, "request_aborted", errorMessage);
475
+ }
476
+ const classified = classifyGatewayError(errorMessage);
477
+ return route.module.formatError(classified.status, classified.type, errorMessage);
478
+ }
479
+ return json(
480
+ 200,
481
+ route.module.encodeResponse(message, parsed.modelId),
482
+ gatewayResponseHeaders(model, { requestId, message, startedAt }),
483
+ );
484
+ } catch (error) {
485
+ if (controller.signal.aborted) return clientClosedResponse(route);
486
+ const classified = classifyGatewayError(error);
487
+ logger.warn("auth-gateway non-streaming aborted", {
488
+ format: route.label,
489
+ error: classified.message,
490
+ peer,
491
+ });
492
+ return route.module.formatError(classified.status, classified.type, classified.message);
493
+ }
494
+ }
495
+
496
+ let events: AssistantMessageEventStream;
497
+ try {
498
+ if (controller.signal.aborted) return clientClosedResponse(route);
499
+ events = streamSimple(model, parsed.context, streamOpts);
500
+ } catch (error) {
501
+ const classified = classifyGatewayError(error);
502
+ logger.warn("auth-gateway streamSimple threw", { format: route.label, error: classified.message, peer });
503
+ return route.module.formatError(classified.status, classified.type, classified.message);
504
+ }
505
+ if (controller.signal.aborted) return clientClosedResponse(route);
506
+
507
+ const sseStream = route.module.encodeStream(events, parsed.modelId, parsed.options, {
508
+ signal: controller.signal,
509
+ onCancel: reason => {
510
+ if (!controller.signal.aborted) {
511
+ controller.abort(reason instanceof Error ? reason : new Error("client closed request"));
512
+ }
513
+ },
514
+ });
515
+ return new Response(sseStream, {
516
+ status: 200,
517
+ headers: {
518
+ ...gatewayResponseHeaders(model, { requestId }),
519
+ "Content-Type": "text/event-stream; charset=utf-8",
520
+ "Cache-Control": "no-cache",
521
+ Connection: "keep-alive",
522
+ // Disable proxy buffering (nginx and ingress controllers honor this).
523
+ // Without it the SSE stream gets held until the buffer flushes, which
524
+ // stalls the long-thinking-budget calls we exist to support.
525
+ "X-Accel-Buffering": "no",
526
+ },
527
+ });
528
+ }
529
+
530
+ /**
531
+ * Pi-native fast path: `POST /v1/pi/stream`. Accepts the canonical pi-ai
532
+ * `Context` directly (no wire-format round-trip) and emits a bandwidth-shrunk
533
+ * event stream matching `pi-agent`'s `streamProxy`. Skips the OpenAI /
534
+ * Anthropic / Responses translation layers — those exist to bridge foreign
535
+ * SDKs (llm-git, anthropic-sdk, openai-sdk), and bridging back to pi-native
536
+ * just to bridge forward again is wasted work.
537
+ *
538
+ * Every other gateway concern (bearer auth, model resolve, credential fetch,
539
+ * abort mirroring, codex temperature/topP strip, prefix-cache key derivation,
540
+ * Claude-Code OAuth shaping inside `streamSimple`) still applies — only
541
+ * `parseRequest`/`encodeResponse`/`encodeStream` differ from the format-endpoint
542
+ * path.
543
+ */
544
+ async function handlePiNative(bootOpts: AuthGatewayBootOptions, req: Request, peer: string): Promise<Response> {
545
+ const startedAt = performance.now();
546
+ const requestId = crypto.randomUUID();
547
+ const controller = mirrorRequestAbort(req);
548
+ const aborted = (): Response => piNative.formatError(499, "request_aborted", "client closed request");
549
+ if (controller.signal.aborted) return aborted();
550
+
551
+ let body: unknown;
552
+ try {
553
+ body = await req.json();
554
+ } catch (error) {
555
+ if (controller.signal.aborted) return aborted();
556
+ return piNative.formatError(400, "invalid_request_error", `Invalid JSON body: ${String(error)}`);
557
+ }
558
+ if (controller.signal.aborted) return aborted();
559
+
560
+ let parsed: piNative.PiNativeParsedRequest;
561
+ try {
562
+ parsed = piNative.parseRequest(body, req.headers);
563
+ } catch (error) {
564
+ if (controller.signal.aborted) return aborted();
565
+ const message = error instanceof Error ? error.message : String(error);
566
+ return piNative.formatError(400, "invalid_request_error", message);
567
+ }
568
+
569
+ const model = bootOpts.resolveModel(parsed.modelId);
570
+ if (!model) {
571
+ return piNative.formatError(404, "invalid_request_error", `Unknown model: ${parsed.modelId}`);
572
+ }
573
+ // Pi-native already parsed `streamOpts.sessionId` (when set by the
574
+ // client); fall back to the derived key so credential-stickiness lines
575
+ // up with cache-prefix stickiness — same identity used for both means
576
+ // the next turn of this conversation reuses the same credential until
577
+ // it hits a usage cap, then markUsageLimitReached can hand off.
578
+ const sessionId = parsed.options.sessionId ?? deriveSessionId(parsed.modelId, parsed.context);
579
+ parsed.options.sessionId ??= sessionId;
580
+
581
+ let apiKey: string | undefined;
582
+ try {
583
+ apiKey = await bootOpts.storage.getApiKey(model.provider, sessionId, {
584
+ modelId: model.id,
585
+ signal: controller.signal,
586
+ });
587
+ } catch (error) {
588
+ if (controller.signal.aborted) return aborted();
589
+ const classified = classifyGatewayError(error);
590
+ logger.warn("auth-gateway getApiKey threw", { provider: model.provider, peer, error: classified.message });
591
+ return piNative.formatError(classified.status, classified.type, classified.message);
592
+ }
593
+ if (controller.signal.aborted) return aborted();
594
+ if (!apiKey) {
595
+ return piNative.formatError(
596
+ 401,
597
+ "authentication_error",
598
+ `No credential available for provider ${model.provider}`,
599
+ );
600
+ }
601
+
602
+ // Build the SimpleStreamOptions actually handed to `streamSimple`. We
603
+ // trust the client's options (already allow-listed by `parseRequest`) and
604
+ // only inject server-controlled fields. The codex sampling strip mirrors
605
+ // `buildStreamOptions` — Codex rejects every one with a 400 (#3117).
606
+ const streamOpts: SimpleStreamOptions = { ...parsed.options, apiKey, signal: controller.signal };
607
+ streamOpts.apiKey = buildGatewayApiKeyResolver(
608
+ bootOpts.storage,
609
+ model,
610
+ sessionId,
611
+ apiKey,
612
+ controller.signal,
613
+ "pi-native",
614
+ peer,
615
+ );
616
+ if (model.api === "openai-codex-responses") {
617
+ delete streamOpts.temperature;
618
+ delete streamOpts.topP;
619
+ delete streamOpts.topK;
620
+ delete streamOpts.minP;
621
+ delete streamOpts.stopSequences;
622
+ delete streamOpts.presencePenalty;
623
+ delete streamOpts.frequencyPenalty;
624
+ delete streamOpts.repetitionPenalty;
625
+ }
626
+ // Merge gateway-captured passthrough headers under the client's own
627
+ // headers — the client's values win when they collide.
628
+ const captured = captureRequestHeaders(req.headers);
629
+ streamOpts.headers = { ...captured, ...(streamOpts.headers ?? {}) };
630
+ streamOpts.sessionId ??= sessionId;
631
+
632
+ logger.info("auth-gateway request", {
633
+ requestId,
634
+ format: "pi-native",
635
+ model: parsed.modelId,
636
+ resolvedProvider: model.provider,
637
+ resolvedModel: model.id,
638
+ stream: parsed.stream,
639
+ peer,
640
+ });
641
+
642
+ if (!parsed.stream) {
643
+ try {
644
+ if (controller.signal.aborted) return aborted();
645
+ const message = await completeSimple(model, parsed.context, streamOpts);
646
+ if (message.stopReason === "aborted" || message.stopReason === "error") {
647
+ const errorMessage =
648
+ message.errorMessage ??
649
+ (message.stopReason === "aborted" ? "Request was aborted" : "Upstream request failed");
650
+ logger.warn("auth-gateway non-streaming failed", {
651
+ format: "pi-native",
652
+ reason: message.stopReason,
653
+ error: errorMessage,
654
+ peer,
655
+ });
656
+ if (message.stopReason === "aborted") {
657
+ return piNative.formatError(499, "request_aborted", errorMessage);
658
+ }
659
+ const classified = classifyGatewayError(errorMessage);
660
+ return piNative.formatError(classified.status, classified.type, errorMessage);
661
+ }
662
+ return json(200, { message }, gatewayResponseHeaders(model, { requestId, message, startedAt }));
663
+ } catch (error) {
664
+ if (controller.signal.aborted) return aborted();
665
+ const classified = classifyGatewayError(error);
666
+ logger.warn("auth-gateway non-streaming aborted", { format: "pi-native", error: classified.message, peer });
667
+ return piNative.formatError(classified.status, classified.type, classified.message);
668
+ }
669
+ }
670
+
671
+ let events: AssistantMessageEventStream;
672
+ try {
673
+ if (controller.signal.aborted) return aborted();
674
+ events = streamSimple(model, parsed.context, streamOpts);
675
+ } catch (error) {
676
+ const classified = classifyGatewayError(error);
677
+ logger.warn("auth-gateway streamSimple threw", { format: "pi-native", error: classified.message, peer });
678
+ return piNative.formatError(classified.status, classified.type, classified.message);
679
+ }
680
+ if (controller.signal.aborted) return aborted();
681
+
682
+ const sseStream = piNative.encodeStream(events, parsed.modelId, parsed.options, {
683
+ signal: controller.signal,
684
+ onCancel: reason => {
685
+ if (!controller.signal.aborted) {
686
+ controller.abort(reason instanceof Error ? reason : new Error("client closed request"));
687
+ }
688
+ },
689
+ });
690
+ return new Response(sseStream, {
691
+ status: 200,
692
+ headers: {
693
+ ...gatewayResponseHeaders(model, { requestId }),
694
+ "Content-Type": "text/event-stream; charset=utf-8",
695
+ "Cache-Control": "no-cache",
696
+ Connection: "keep-alive",
697
+ "X-Accel-Buffering": "no",
698
+ },
699
+ });
700
+ }
701
+
702
+ /**
703
+ * Snapshot of `GET /v1/usage` — `fetchUsageReports` already caches reports at
704
+ * a 5-minute per-credential TTL (with jitter, plus last-good fallback on
705
+ * failure) inside `AuthStorage`, so this handler is a thin wrapper that
706
+ * surfaces the same data to HTTP callers (notably the macOS usage widget).
707
+ */
708
+ async function handleUsage(storage: AuthStorage, signal: AbortSignal): Promise<Response> {
709
+ const reports = (await storage.fetchUsageReports?.({ signal })) ?? [];
710
+ // Drop the heavy provider-specific `raw` payload — UI consumers only need
711
+ // `limits` + `metadata`. Match the broker's `/v1/usage` shape so a single
712
+ // client struct (Swift widget, llm-git, ...) works against either endpoint.
713
+ const trimmed = reports.map(({ raw: _raw, ...rest }) => rest);
714
+ return json(200, { generatedAt: Date.now(), reports: trimmed });
715
+ }
716
+
717
+ /**
718
+ * Per-credential health probe surfaced on `GET /v1/credentials/check`. Tells
719
+ * the caller exactly which row in their broker is producing 401s — the
720
+ * aggregate `/v1/usage` endpoint silently drops failed credentials, which is
721
+ * the wrong shape when you're diagnosing auth.
722
+ *
723
+ * The probe is sequential (one credential at a time) to avoid synchronized
724
+ * N-account fan-out tripping per-IP rate limits on provider `/usage`
725
+ * endpoints. For multi-account pools that's the difference between getting
726
+ * a clean diagnosis and getting a 429 storm.
727
+ */
728
+ async function handleCredentialsCheck(storage: AuthStorage, signal: AbortSignal): Promise<Response> {
729
+ const credentials = await storage.checkCredentials({ signal });
730
+ return json(200, { generatedAt: Date.now(), credentials });
731
+ }
732
+
733
+ function handleModelsList(opts: AuthGatewayBootOptions): Response {
734
+ const seen = new Set<string>();
735
+ const data: Array<{ id: string; object: "model"; owned_by: string; api: Api }> = [];
736
+ for (const model of opts.listModels?.() ?? []) {
737
+ const id = `${model.provider}/${model.id}`;
738
+ if (seen.has(id)) continue;
739
+ seen.add(id);
740
+ data.push({
741
+ id,
742
+ object: "model",
743
+ owned_by: model.provider,
744
+ api: model.api,
745
+ });
746
+ }
747
+ return json(200, { object: "list", data });
748
+ }
749
+
750
+ export function startAuthGateway(opts: AuthGatewayBootOptions): AuthGatewayServerHandle {
751
+ const bind = parseBind(opts.bind ?? DEFAULT_AUTH_GATEWAY_BIND);
752
+ const tokens = new Set<string>(opts.bearerTokens);
753
+ const version = opts.version;
754
+
755
+ const server = Bun.serve({
756
+ hostname: bind.hostname,
757
+ port: bind.port,
758
+ fetch: async (req): Promise<Response> => {
759
+ const url = new URL(req.url);
760
+ const pathname = url.pathname;
761
+ const peer = resolvePeer(req);
762
+ // CORS preflight is always answered without auth — browsers send
763
+ // preflights pre-authentication and a 401 here breaks the actual
764
+ // request before the bearer is ever attached.
765
+ if (req.method === "OPTIONS") {
766
+ return new Response(null, { status: 204, headers: corsHeaders(req) });
767
+ }
768
+ try {
769
+ if (req.method === "GET" && pathname === "/healthz") {
770
+ return withCors(json(200, { ok: true, version }), req);
771
+ }
772
+ if (!isAuthorized(req, tokens)) {
773
+ logger.info("auth-gateway request unauthorized", { method: req.method, path: pathname, peer });
774
+ return withCors(json(401, { error: "unauthorized" }), req);
775
+ }
776
+
777
+ // Aggregated usage — backed by AuthStorage's 5-min per-credential cache.
778
+ // Same shape as the broker's `/v1/usage`, so widget/llm-git speak to either with the
779
+ // same client struct.
780
+ if (req.method === "GET" && pathname === "/v1/usage") {
781
+ return withCors(await handleUsage(opts.storage, req.signal), req);
782
+ }
783
+
784
+ // Per-credential auth probe — diagnoses which row in a multi-account
785
+ // pool is producing 401s. Aggregated `/v1/usage` silently drops failed
786
+ // credentials, so we need a separate endpoint that captures errors.
787
+ if (req.method === "GET" && pathname === "/v1/credentials/check") {
788
+ return withCors(await handleCredentialsCheck(opts.storage, req.signal), req);
789
+ }
790
+
791
+ // Provider-format dispatch.
792
+ const formatRoute = FORMAT_ROUTES[pathname];
793
+ if (formatRoute && req.method === "POST") {
794
+ return withCors(await handleFormatEndpoint(formatRoute, opts, req, peer), req);
795
+ }
796
+
797
+ // Pi-native fast path. Same auth + provider plumbing as the
798
+ // foreign-wire routes, just without the wire-format translation.
799
+ if (req.method === "POST" && pathname === "/v1/pi/stream") {
800
+ return withCors(await handlePiNative(opts, req, peer), req);
801
+ }
802
+
803
+ // Model catalog.
804
+ if (req.method === "GET" && pathname === "/v1/models") {
805
+ return withCors(handleModelsList(opts), req);
806
+ }
807
+
808
+ // Route-table miss: no format module to defer to, so we emit a
809
+ // plain JSON 404 rather than guessing at a protocol-specific envelope.
810
+ return withCors(json(404, { error: `No route: ${req.method} ${pathname}` }), req);
811
+ } catch (error) {
812
+ logger.error("auth-gateway handler crashed", {
813
+ method: req.method,
814
+ path: pathname,
815
+ peer,
816
+ error: String(error),
817
+ });
818
+ return withCors(json(500, { error: "internal error" }), req);
819
+ }
820
+ },
821
+ // Max-out Bun's idle timeout. Long thinking-budget calls can sit idle
822
+ // for minutes before the first token arrives; the default kills them.
823
+ idleTimeout: 255,
824
+ });
825
+
826
+ const boundHost = server.hostname ?? bind.hostname;
827
+ const boundPort = server.port ?? bind.port;
828
+ return {
829
+ url: `http://${boundHost}:${boundPort}`,
830
+ port: boundPort,
831
+ hostname: boundHost,
832
+ close: async () => {
833
+ server.stop(true);
834
+ },
835
+ };
836
+ }