@vib-rato/ai 0.16.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 (525) hide show
  1. package/CHANGELOG.md +3354 -0
  2. package/README.md +1194 -0
  3. package/dist/types/adapter-internals/provider-safety-stop.d.ts +45 -0
  4. package/dist/types/api-registry.d.ts +30 -0
  5. package/dist/types/auth-broker/client.d.ts +80 -0
  6. package/dist/types/auth-broker/index.d.ts +5 -0
  7. package/dist/types/auth-broker/redact.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 +153 -0
  10. package/dist/types/auth-broker/server.d.ts +32 -0
  11. package/dist/types/auth-broker/types.d.ts +132 -0
  12. package/dist/types/auth-broker/wire-schemas.d.ts +555 -0
  13. package/dist/types/auth-gateway/http.d.ts +40 -0
  14. package/dist/types/auth-gateway/index.d.ts +3 -0
  15. package/dist/types/auth-gateway/server.d.ts +70 -0
  16. package/dist/types/auth-gateway/types.d.ts +129 -0
  17. package/dist/types/auth-storage.d.ts +1074 -0
  18. package/dist/types/cli.d.ts +2 -0
  19. package/dist/types/codex-tools.d.ts +4 -0
  20. package/dist/types/context-cap-policy.d.ts +68 -0
  21. package/dist/types/core.d.ts +35 -0
  22. package/dist/types/index.d.ts +55 -0
  23. package/dist/types/model-cache.d.ts +24 -0
  24. package/dist/types/model-manager.d.ts +77 -0
  25. package/dist/types/model-pricing.d.ts +3 -0
  26. package/dist/types/model-retirements.d.ts +6 -0
  27. package/dist/types/model-thinking.d.ts +100 -0
  28. package/dist/types/models.d.ts +21 -0
  29. package/dist/types/openai-completions-compat.d.ts +34 -0
  30. package/dist/types/provider-details.d.ts +24 -0
  31. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  32. package/dist/types/provider-models/descriptors.d.ts +48 -0
  33. package/dist/types/provider-models/google.d.ts +20 -0
  34. package/dist/types/provider-models/index.d.ts +5 -0
  35. package/dist/types/provider-models/ollama.d.ts +7 -0
  36. package/dist/types/provider-models/openai-compat.d.ts +293 -0
  37. package/dist/types/provider-models/special.d.ts +29 -0
  38. package/dist/types/providers/amazon-bedrock.d.ts +60 -0
  39. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  40. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  41. package/dist/types/providers/anthropic.d.ts +280 -0
  42. package/dist/types/providers/aws-credential-config.d.ts +19 -0
  43. package/dist/types/providers/aws-credentials.d.ts +43 -0
  44. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  45. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  46. package/dist/types/providers/azure-openai-responses.d.ts +22 -0
  47. package/dist/types/providers/composer-discipline.d.ts +32 -0
  48. package/dist/types/providers/cursor/client-version.d.ts +10 -0
  49. package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
  50. package/dist/types/providers/cursor/gen/agent_pb.d.ts +16769 -0
  51. package/dist/types/providers/cursor-pi-args.d.ts +119 -0
  52. package/dist/types/providers/cursor.d.ts +72 -0
  53. package/dist/types/providers/dashscope-token-plan-headers.d.ts +57 -0
  54. package/dist/types/providers/error-message.d.ts +27 -0
  55. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  56. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  57. package/dist/types/providers/google-auth.d.ts +26 -0
  58. package/dist/types/providers/google-gemini-cli.d.ts +75 -0
  59. package/dist/types/providers/google-gemini-headers.d.ts +43 -0
  60. package/dist/types/providers/google-shared.d.ts +183 -0
  61. package/dist/types/providers/google-types.d.ts +138 -0
  62. package/dist/types/providers/google-vertex.d.ts +11 -0
  63. package/dist/types/providers/google.d.ts +4 -0
  64. package/dist/types/providers/grammar.d.ts +1 -0
  65. package/dist/types/providers/kimi.d.ts +27 -0
  66. package/dist/types/providers/kiro-api-key.d.ts +50 -0
  67. package/dist/types/providers/kiro-codewhisperer.d.ts +11 -0
  68. package/dist/types/providers/mock.d.ts +189 -0
  69. package/dist/types/providers/ollama.d.ts +41 -0
  70. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  71. package/dist/types/providers/openai-bounded-rate-limits.d.ts +3 -0
  72. package/dist/types/providers/openai-chat-server-schema.d.ts +1733 -0
  73. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  74. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  75. package/dist/types/providers/openai-codex/request-transformer.d.ts +50 -0
  76. package/dist/types/providers/openai-codex/response-handler.d.ts +18 -0
  77. package/dist/types/providers/openai-codex-responses.d.ts +71 -0
  78. package/dist/types/providers/openai-completions-compat.d.ts +6 -0
  79. package/dist/types/providers/openai-completions.d.ts +35 -0
  80. package/dist/types/providers/openai-opencodex-responses.d.ts +10 -0
  81. package/dist/types/providers/openai-request-transform.d.ts +4 -0
  82. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  83. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  84. package/dist/types/providers/openai-responses-shared.d.ts +106 -0
  85. package/dist/types/providers/openai-responses.d.ts +37 -0
  86. package/dist/types/providers/pi-native-client.d.ts +29 -0
  87. package/dist/types/providers/pi-native-server.d.ts +60 -0
  88. package/dist/types/providers/register-builtins.d.ts +59 -0
  89. package/dist/types/providers/synthetic.d.ts +26 -0
  90. package/dist/types/providers/transform-messages.d.ts +33 -0
  91. package/dist/types/providers/vision-guard.d.ts +8 -0
  92. package/dist/types/rate-limit-utils.d.ts +19 -0
  93. package/dist/types/stream.d.ts +45 -0
  94. package/dist/types/types.d.ts +1073 -0
  95. package/dist/types/usage/claude.d.ts +3 -0
  96. package/dist/types/usage/gemini.d.ts +2 -0
  97. package/dist/types/usage/github-copilot.d.ts +7 -0
  98. package/dist/types/usage/google-antigravity.d.ts +2 -0
  99. package/dist/types/usage/grok-cli.d.ts +17 -0
  100. package/dist/types/usage/kimi.d.ts +4 -0
  101. package/dist/types/usage/minimax-code.d.ts +2 -0
  102. package/dist/types/usage/openai-codex.d.ts +3 -0
  103. package/dist/types/usage/shared.d.ts +1 -0
  104. package/dist/types/usage/zai.d.ts +2 -0
  105. package/dist/types/usage.d.ts +264 -0
  106. package/dist/types/utils/abort.d.ts +19 -0
  107. package/dist/types/utils/anthropic-auth.d.ts +39 -0
  108. package/dist/types/utils/block-symbols.d.ts +6 -0
  109. package/dist/types/utils/discovery/antigravity.d.ts +67 -0
  110. package/dist/types/utils/discovery/codex.d.ts +38 -0
  111. package/dist/types/utils/discovery/cursor.d.ts +49 -0
  112. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  113. package/dist/types/utils/discovery/index.d.ts +4 -0
  114. package/dist/types/utils/discovery/openai-compatible.d.ts +81 -0
  115. package/dist/types/utils/event-stream.d.ts +41 -0
  116. package/dist/types/utils/fallback-transport.d.ts +110 -0
  117. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  118. package/dist/types/utils/foundry.d.ts +11 -0
  119. package/dist/types/utils/h2-fetch.d.ts +22 -0
  120. package/dist/types/utils/http-inspector.d.ts +59 -0
  121. package/dist/types/utils/idle-iterator.d.ts +122 -0
  122. package/dist/types/utils/json-parse.d.ts +98 -0
  123. package/dist/types/utils/oauth/alibaba-token-plan.d.ts +19 -0
  124. package/dist/types/utils/oauth/anthropic.d.ts +41 -0
  125. package/dist/types/utils/oauth/api-key-login.d.ts +38 -0
  126. package/dist/types/utils/oauth/api-key-validation.d.ts +39 -0
  127. package/dist/types/utils/oauth/bizrouter.d.ts +1 -0
  128. package/dist/types/utils/oauth/callback-server.d.ts +80 -0
  129. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  130. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  131. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  132. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  133. package/dist/types/utils/oauth/deepinfra.d.ts +1 -0
  134. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  135. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  136. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  137. package/dist/types/utils/oauth/fugu.d.ts +1 -0
  138. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  139. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  140. package/dist/types/utils/oauth/glm-zcode.d.ts +71 -0
  141. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  142. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  143. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  144. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  145. package/dist/types/utils/oauth/index.d.ts +39 -0
  146. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  147. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  148. package/dist/types/utils/oauth/kimi.d.ts +17 -0
  149. package/dist/types/utils/oauth/kiro.d.ts +71 -0
  150. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  151. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  152. package/dist/types/utils/oauth/mara.d.ts +1 -0
  153. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  154. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  155. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  156. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  157. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  158. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  159. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  160. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  161. package/dist/types/utils/oauth/opengateway.d.ts +1 -0
  162. package/dist/types/utils/oauth/openrouter.d.ts +1 -0
  163. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  164. package/dist/types/utils/oauth/perplexity.d.ts +4 -0
  165. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  166. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  167. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  168. package/dist/types/utils/oauth/sglang.d.ts +16 -0
  169. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  170. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  171. package/dist/types/utils/oauth/together.d.ts +1 -0
  172. package/dist/types/utils/oauth/types.d.ts +56 -0
  173. package/dist/types/utils/oauth/venice.d.ts +18 -0
  174. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  175. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  176. package/dist/types/utils/oauth/xai.d.ts +30 -0
  177. package/dist/types/utils/oauth/xiaomi.d.ts +25 -0
  178. package/dist/types/utils/oauth/zai.d.ts +18 -0
  179. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  180. package/dist/types/utils/overflow.d.ts +14 -0
  181. package/dist/types/utils/parse-bind.d.ts +26 -0
  182. package/dist/types/utils/provider-response.d.ts +6 -0
  183. package/dist/types/utils/provider-safety-stop.d.ts +8 -0
  184. package/dist/types/utils/proxy.d.ts +7 -0
  185. package/dist/types/utils/retry-after.d.ts +3 -0
  186. package/dist/types/utils/retry-budget.d.ts +1 -0
  187. package/dist/types/utils/retry.d.ts +29 -0
  188. package/dist/types/utils/schema/adapt.d.ts +24 -0
  189. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  190. package/dist/types/utils/schema/dereference.d.ts +11 -0
  191. package/dist/types/utils/schema/draft.d.ts +10 -0
  192. package/dist/types/utils/schema/equality.d.ts +4 -0
  193. package/dist/types/utils/schema/fields.d.ts +49 -0
  194. package/dist/types/utils/schema/index.d.ts +14 -0
  195. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  196. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  197. package/dist/types/utils/schema/normalize.d.ts +93 -0
  198. package/dist/types/utils/schema/root-combinator.d.ts +12 -0
  199. package/dist/types/utils/schema/spill.d.ts +8 -0
  200. package/dist/types/utils/schema/stamps.d.ts +25 -0
  201. package/dist/types/utils/schema/types.d.ts +4 -0
  202. package/dist/types/utils/schema/wire.d.ts +54 -0
  203. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  204. package/dist/types/utils/sse-debug.d.ts +10 -0
  205. package/dist/types/utils/tool-call-healing.d.ts +80 -0
  206. package/dist/types/utils/tool-choice-capability.d.ts +57 -0
  207. package/dist/types/utils/tool-choice.d.ts +50 -0
  208. package/dist/types/utils/validation.d.ts +17 -0
  209. package/dist/types/utils.d.ts +117 -0
  210. package/package.json +152 -0
  211. package/src/adapter-internals/provider-safety-stop.d.ts +45 -0
  212. package/src/adapter-internals/provider-safety-stop.ts +156 -0
  213. package/src/api-registry.d.ts +30 -0
  214. package/src/api-registry.ts +96 -0
  215. package/src/auth-broker/client.ts +444 -0
  216. package/src/auth-broker/index.ts +5 -0
  217. package/src/auth-broker/redact.ts +39 -0
  218. package/src/auth-broker/refresher.ts +130 -0
  219. package/src/auth-broker/remote-store.ts +1576 -0
  220. package/src/auth-broker/server.ts +764 -0
  221. package/src/auth-broker/types.ts +164 -0
  222. package/src/auth-broker/wire-schemas.ts +261 -0
  223. package/src/auth-gateway/http.ts +198 -0
  224. package/src/auth-gateway/index.ts +3 -0
  225. package/src/auth-gateway/server.ts +1315 -0
  226. package/src/auth-gateway/types.ts +160 -0
  227. package/src/auth-storage.ts +7312 -0
  228. package/src/cli.ts +269 -0
  229. package/src/codex-tools.d.ts +4 -0
  230. package/src/codex-tools.ts +24 -0
  231. package/src/context-cap-policy.d.ts +68 -0
  232. package/src/context-cap-policy.ts +123 -0
  233. package/src/core.ts +44 -0
  234. package/src/index.ts +61 -0
  235. package/src/model-cache.ts +236 -0
  236. package/src/model-manager.ts +744 -0
  237. package/src/model-pricing.d.ts +3 -0
  238. package/src/model-pricing.ts +68 -0
  239. package/src/model-retirements.d.ts +6 -0
  240. package/src/model-retirements.ts +19 -0
  241. package/src/model-thinking.d.ts +100 -0
  242. package/src/model-thinking.ts +1054 -0
  243. package/src/models.d.ts +21 -0
  244. package/src/models.json +94672 -0
  245. package/src/models.json.d.ts +9 -0
  246. package/src/models.ts +126 -0
  247. package/src/openai-completions-compat.d.ts +34 -0
  248. package/src/openai-completions-compat.ts +383 -0
  249. package/src/prompts/composer-bash-policy-recovery.md +1 -0
  250. package/src/prompts/cursor-composer-bash-policy-recovery.md +1 -0
  251. package/src/prompts/cursor-composer-edit-discipline.md +7 -0
  252. package/src/prompts/turn-aborted-guidance.md +4 -0
  253. package/src/provider-details.ts +90 -0
  254. package/src/provider-models/bundled-references.ts +38 -0
  255. package/src/provider-models/descriptors.ts +392 -0
  256. package/src/provider-models/google.ts +92 -0
  257. package/src/provider-models/index.ts +5 -0
  258. package/src/provider-models/ollama.ts +159 -0
  259. package/src/provider-models/openai-compat.ts +2920 -0
  260. package/src/provider-models/special.ts +185 -0
  261. package/src/providers/amazon-bedrock.d.ts +60 -0
  262. package/src/providers/amazon-bedrock.ts +939 -0
  263. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  264. package/src/providers/anthropic-messages-server.ts +839 -0
  265. package/src/providers/anthropic.d.ts +280 -0
  266. package/src/providers/anthropic.ts +4421 -0
  267. package/src/providers/aws-credential-config.d.ts +19 -0
  268. package/src/providers/aws-credential-config.ts +179 -0
  269. package/src/providers/aws-credentials.d.ts +43 -0
  270. package/src/providers/aws-credentials.ts +457 -0
  271. package/src/providers/aws-eventstream.d.ts +38 -0
  272. package/src/providers/aws-eventstream.ts +185 -0
  273. package/src/providers/aws-sigv4.d.ts +55 -0
  274. package/src/providers/aws-sigv4.ts +218 -0
  275. package/src/providers/azure-openai-responses.d.ts +22 -0
  276. package/src/providers/azure-openai-responses.ts +447 -0
  277. package/src/providers/composer-discipline.d.ts +32 -0
  278. package/src/providers/composer-discipline.ts +95 -0
  279. package/src/providers/cursor/client-version.d.ts +10 -0
  280. package/src/providers/cursor/client-version.ts +10 -0
  281. package/src/providers/cursor/exec-modern.d.ts +98 -0
  282. package/src/providers/cursor/exec-modern.ts +497 -0
  283. package/src/providers/cursor/gen/agent_pb.d.ts +16769 -0
  284. package/src/providers/cursor/gen/agent_pb.ts +19780 -0
  285. package/src/providers/cursor/proto/agent.proto +4533 -0
  286. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  287. package/src/providers/cursor/proto/buf.yaml +17 -0
  288. package/src/providers/cursor-pi-args.d.ts +119 -0
  289. package/src/providers/cursor-pi-args.ts +187 -0
  290. package/src/providers/cursor.d.ts +72 -0
  291. package/src/providers/cursor.ts +3396 -0
  292. package/src/providers/dashscope-token-plan-headers.d.ts +57 -0
  293. package/src/providers/dashscope-token-plan-headers.ts +84 -0
  294. package/src/providers/error-message.d.ts +27 -0
  295. package/src/providers/error-message.ts +21 -0
  296. package/src/providers/github-copilot-headers.d.ts +40 -0
  297. package/src/providers/github-copilot-headers.ts +140 -0
  298. package/src/providers/gitlab-duo.d.ts +27 -0
  299. package/src/providers/gitlab-duo.ts +393 -0
  300. package/src/providers/google-auth.d.ts +26 -0
  301. package/src/providers/google-auth.ts +262 -0
  302. package/src/providers/google-gemini-cli.d.ts +75 -0
  303. package/src/providers/google-gemini-cli.ts +969 -0
  304. package/src/providers/google-gemini-headers.d.ts +43 -0
  305. package/src/providers/google-gemini-headers.ts +100 -0
  306. package/src/providers/google-shared.d.ts +183 -0
  307. package/src/providers/google-shared.ts +1105 -0
  308. package/src/providers/google-types.d.ts +138 -0
  309. package/src/providers/google-types.ts +167 -0
  310. package/src/providers/google-vertex.d.ts +11 -0
  311. package/src/providers/google-vertex.ts +124 -0
  312. package/src/providers/google.d.ts +4 -0
  313. package/src/providers/google.ts +41 -0
  314. package/src/providers/grammar.d.ts +1 -0
  315. package/src/providers/grammar.ts +70 -0
  316. package/src/providers/kimi.d.ts +27 -0
  317. package/src/providers/kimi.ts +52 -0
  318. package/src/providers/kiro-api-key.d.ts +50 -0
  319. package/src/providers/kiro-api-key.ts +786 -0
  320. package/src/providers/kiro-codewhisperer.d.ts +11 -0
  321. package/src/providers/kiro-codewhisperer.ts +600 -0
  322. package/src/providers/mock.ts +526 -0
  323. package/src/providers/ollama.d.ts +41 -0
  324. package/src/providers/ollama.ts +645 -0
  325. package/src/providers/openai-anthropic-shim.d.ts +31 -0
  326. package/src/providers/openai-anthropic-shim.ts +156 -0
  327. package/src/providers/openai-bounded-rate-limits.d.ts +3 -0
  328. package/src/providers/openai-bounded-rate-limits.ts +57 -0
  329. package/src/providers/openai-chat-server-schema.ts +254 -0
  330. package/src/providers/openai-chat-server.ts +724 -0
  331. package/src/providers/openai-codex/constants.d.ts +26 -0
  332. package/src/providers/openai-codex/constants.ts +43 -0
  333. package/src/providers/openai-codex/request-transformer.d.ts +50 -0
  334. package/src/providers/openai-codex/request-transformer.ts +219 -0
  335. package/src/providers/openai-codex/response-handler.d.ts +18 -0
  336. package/src/providers/openai-codex/response-handler.ts +111 -0
  337. package/src/providers/openai-codex-responses.d.ts +71 -0
  338. package/src/providers/openai-codex-responses.ts +3288 -0
  339. package/src/providers/openai-completions-compat.d.ts +6 -0
  340. package/src/providers/openai-completions-compat.ts +6 -0
  341. package/src/providers/openai-completions.d.ts +35 -0
  342. package/src/providers/openai-completions.ts +2294 -0
  343. package/src/providers/openai-opencodex-responses.ts +174 -0
  344. package/src/providers/openai-request-transform.d.ts +4 -0
  345. package/src/providers/openai-request-transform.ts +136 -0
  346. package/src/providers/openai-responses-server-schema.ts +290 -0
  347. package/src/providers/openai-responses-server.ts +1268 -0
  348. package/src/providers/openai-responses-shared.d.ts +106 -0
  349. package/src/providers/openai-responses-shared.ts +1253 -0
  350. package/src/providers/openai-responses.d.ts +37 -0
  351. package/src/providers/openai-responses.ts +989 -0
  352. package/src/providers/pi-native-client.d.ts +29 -0
  353. package/src/providers/pi-native-client.ts +243 -0
  354. package/src/providers/pi-native-server.ts +488 -0
  355. package/src/providers/register-builtins.d.ts +59 -0
  356. package/src/providers/register-builtins.ts +544 -0
  357. package/src/providers/synthetic.d.ts +26 -0
  358. package/src/providers/synthetic.ts +50 -0
  359. package/src/providers/transform-messages.d.ts +33 -0
  360. package/src/providers/transform-messages.ts +408 -0
  361. package/src/providers/vision-guard.d.ts +8 -0
  362. package/src/providers/vision-guard.ts +31 -0
  363. package/src/rate-limit-utils.d.ts +19 -0
  364. package/src/rate-limit-utils.ts +102 -0
  365. package/src/stream.d.ts +45 -0
  366. package/src/stream.ts +1306 -0
  367. package/src/types.d.ts +1073 -0
  368. package/src/types.ts +1305 -0
  369. package/src/usage/claude.ts +449 -0
  370. package/src/usage/gemini.ts +250 -0
  371. package/src/usage/github-copilot.ts +421 -0
  372. package/src/usage/google-antigravity.ts +201 -0
  373. package/src/usage/grok-cli.ts +259 -0
  374. package/src/usage/kimi.ts +285 -0
  375. package/src/usage/minimax-code.ts +31 -0
  376. package/src/usage/openai-codex.ts +503 -0
  377. package/src/usage/shared.ts +10 -0
  378. package/src/usage/zai.ts +247 -0
  379. package/src/usage.ts +190 -0
  380. package/src/utils/abort.d.ts +19 -0
  381. package/src/utils/abort.ts +51 -0
  382. package/src/utils/anthropic-auth.ts +95 -0
  383. package/src/utils/block-symbols.d.ts +6 -0
  384. package/src/utils/block-symbols.ts +11 -0
  385. package/src/utils/discovery/antigravity.ts +275 -0
  386. package/src/utils/discovery/codex.ts +362 -0
  387. package/src/utils/discovery/cursor.ts +388 -0
  388. package/src/utils/discovery/gemini.ts +248 -0
  389. package/src/utils/discovery/index.ts +4 -0
  390. package/src/utils/discovery/openai-compatible.ts +379 -0
  391. package/src/utils/event-stream.d.ts +41 -0
  392. package/src/utils/event-stream.ts +269 -0
  393. package/src/utils/fallback-transport.d.ts +110 -0
  394. package/src/utils/fallback-transport.ts +411 -0
  395. package/src/utils/fireworks-model-id.d.ts +10 -0
  396. package/src/utils/fireworks-model-id.ts +30 -0
  397. package/src/utils/foundry.d.ts +11 -0
  398. package/src/utils/foundry.ts +18 -0
  399. package/src/utils/h2-fetch.ts +60 -0
  400. package/src/utils/http-inspector.d.ts +59 -0
  401. package/src/utils/http-inspector.ts +380 -0
  402. package/src/utils/idle-iterator.d.ts +122 -0
  403. package/src/utils/idle-iterator.ts +410 -0
  404. package/src/utils/json-parse.d.ts +98 -0
  405. package/src/utils/json-parse.ts +607 -0
  406. package/src/utils/oauth/alibaba-token-plan.ts +60 -0
  407. package/src/utils/oauth/anthropic.ts +233 -0
  408. package/src/utils/oauth/api-key-login.ts +98 -0
  409. package/src/utils/oauth/api-key-validation.ts +344 -0
  410. package/src/utils/oauth/bizrouter.ts +15 -0
  411. package/src/utils/oauth/callback-server.d.ts +80 -0
  412. package/src/utils/oauth/callback-server.ts +359 -0
  413. package/src/utils/oauth/cerebras.ts +16 -0
  414. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  415. package/src/utils/oauth/commandcode.ts +17 -0
  416. package/src/utils/oauth/cursor.ts +157 -0
  417. package/src/utils/oauth/deepinfra.ts +15 -0
  418. package/src/utils/oauth/deepseek.ts +53 -0
  419. package/src/utils/oauth/firepass.ts +24 -0
  420. package/src/utils/oauth/fireworks.ts +15 -0
  421. package/src/utils/oauth/fugu.ts +15 -0
  422. package/src/utils/oauth/github-copilot.d.ts +38 -0
  423. package/src/utils/oauth/github-copilot.ts +362 -0
  424. package/src/utils/oauth/gitlab-duo.ts +123 -0
  425. package/src/utils/oauth/glm-zcode.d.ts +71 -0
  426. package/src/utils/oauth/glm-zcode.ts +433 -0
  427. package/src/utils/oauth/google-antigravity.ts +200 -0
  428. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  429. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  430. package/src/utils/oauth/huggingface.ts +62 -0
  431. package/src/utils/oauth/index.ts +558 -0
  432. package/src/utils/oauth/kagi.ts +47 -0
  433. package/src/utils/oauth/kilo.ts +87 -0
  434. package/src/utils/oauth/kimi.d.ts +17 -0
  435. package/src/utils/oauth/kimi.ts +275 -0
  436. package/src/utils/oauth/kiro.ts +448 -0
  437. package/src/utils/oauth/litellm.ts +47 -0
  438. package/src/utils/oauth/lm-studio.ts +38 -0
  439. package/src/utils/oauth/mara.ts +16 -0
  440. package/src/utils/oauth/minimax-code.ts +78 -0
  441. package/src/utils/oauth/moonshot.ts +16 -0
  442. package/src/utils/oauth/nanogpt.ts +15 -0
  443. package/src/utils/oauth/nvidia.ts +70 -0
  444. package/src/utils/oauth/oauth.html +199 -0
  445. package/src/utils/oauth/ollama-cloud.ts +28 -0
  446. package/src/utils/oauth/ollama.ts +47 -0
  447. package/src/utils/oauth/openai-codex.ts +299 -0
  448. package/src/utils/oauth/opencode.ts +49 -0
  449. package/src/utils/oauth/opengateway.ts +15 -0
  450. package/src/utils/oauth/openrouter.ts +16 -0
  451. package/src/utils/oauth/parallel.ts +46 -0
  452. package/src/utils/oauth/perplexity.ts +225 -0
  453. package/src/utils/oauth/pkce.ts +18 -0
  454. package/src/utils/oauth/qianfan.ts +58 -0
  455. package/src/utils/oauth/qwen-portal.ts +60 -0
  456. package/src/utils/oauth/sglang.ts +42 -0
  457. package/src/utils/oauth/synthetic.ts +15 -0
  458. package/src/utils/oauth/tavily.ts +46 -0
  459. package/src/utils/oauth/together.ts +16 -0
  460. package/src/utils/oauth/types.d.ts +56 -0
  461. package/src/utils/oauth/types.ts +122 -0
  462. package/src/utils/oauth/venice.ts +59 -0
  463. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  464. package/src/utils/oauth/vllm.ts +42 -0
  465. package/src/utils/oauth/xai.ts +246 -0
  466. package/src/utils/oauth/xiaomi.ts +199 -0
  467. package/src/utils/oauth/zai.ts +60 -0
  468. package/src/utils/oauth/zenmux.ts +15 -0
  469. package/src/utils/overflow.ts +275 -0
  470. package/src/utils/parse-bind.ts +81 -0
  471. package/src/utils/provider-response.d.ts +6 -0
  472. package/src/utils/provider-response.ts +30 -0
  473. package/src/utils/provider-safety-stop.ts +8 -0
  474. package/src/utils/proxy.d.ts +7 -0
  475. package/src/utils/proxy.ts +652 -0
  476. package/src/utils/retry-after.d.ts +3 -0
  477. package/src/utils/retry-after.ts +110 -0
  478. package/src/utils/retry-budget.d.ts +1 -0
  479. package/src/utils/retry-budget.ts +4 -0
  480. package/src/utils/retry.d.ts +29 -0
  481. package/src/utils/retry.ts +67 -0
  482. package/src/utils/schema/CONSTRAINTS.md +164 -0
  483. package/src/utils/schema/adapt.d.ts +24 -0
  484. package/src/utils/schema/adapt.ts +36 -0
  485. package/src/utils/schema/compatibility.d.ts +30 -0
  486. package/src/utils/schema/compatibility.ts +435 -0
  487. package/src/utils/schema/dereference.d.ts +11 -0
  488. package/src/utils/schema/dereference.ts +98 -0
  489. package/src/utils/schema/draft.d.ts +10 -0
  490. package/src/utils/schema/draft.ts +341 -0
  491. package/src/utils/schema/equality.d.ts +4 -0
  492. package/src/utils/schema/equality.ts +97 -0
  493. package/src/utils/schema/fields.d.ts +49 -0
  494. package/src/utils/schema/fields.ts +190 -0
  495. package/src/utils/schema/index.d.ts +14 -0
  496. package/src/utils/schema/index.ts +14 -0
  497. package/src/utils/schema/json-schema-validator.d.ts +12 -0
  498. package/src/utils/schema/json-schema-validator.ts +577 -0
  499. package/src/utils/schema/meta-validator.d.ts +2 -0
  500. package/src/utils/schema/meta-validator.ts +167 -0
  501. package/src/utils/schema/normalize.d.ts +93 -0
  502. package/src/utils/schema/normalize.ts +1588 -0
  503. package/src/utils/schema/root-combinator.d.ts +12 -0
  504. package/src/utils/schema/root-combinator.ts +143 -0
  505. package/src/utils/schema/spill.d.ts +8 -0
  506. package/src/utils/schema/spill.ts +43 -0
  507. package/src/utils/schema/stamps.d.ts +25 -0
  508. package/src/utils/schema/stamps.ts +97 -0
  509. package/src/utils/schema/types.d.ts +4 -0
  510. package/src/utils/schema/types.ts +11 -0
  511. package/src/utils/schema/wire.d.ts +54 -0
  512. package/src/utils/schema/wire.ts +213 -0
  513. package/src/utils/schema/zod-decontaminate.d.ts +31 -0
  514. package/src/utils/schema/zod-decontaminate.ts +331 -0
  515. package/src/utils/sse-debug.d.ts +10 -0
  516. package/src/utils/sse-debug.ts +289 -0
  517. package/src/utils/tool-call-healing.d.ts +80 -0
  518. package/src/utils/tool-call-healing.ts +298 -0
  519. package/src/utils/tool-choice-capability.d.ts +57 -0
  520. package/src/utils/tool-choice-capability.ts +633 -0
  521. package/src/utils/tool-choice.d.ts +50 -0
  522. package/src/utils/tool-choice.ts +99 -0
  523. package/src/utils/validation.ts +1080 -0
  524. package/src/utils.d.ts +117 -0
  525. package/src/utils.ts +523 -0
@@ -0,0 +1,764 @@
1
+ /**
2
+ * Auth broker HTTP server.
3
+ *
4
+ * Wraps an {@link AuthStorage} (backed by a SQLite store on the broker host)
5
+ * and exposes a minimal REST API for snapshot pulls and explicit refresh /
6
+ * disable operations. Background refresh of expiring credentials lives in
7
+ * {@link AuthBrokerRefresher}.
8
+ *
9
+ * Transport security is delegated to the operator (Tailscale / Wireguard);
10
+ * the server only checks a bearer token against an allow-list per request.
11
+ */
12
+ import * as crypto from "node:crypto";
13
+ import { logger } from "@vib-rato/utils";
14
+ import { timingSafeEqual } from "../auth-gateway/http";
15
+ import type { AuthStorage } from "../auth-storage";
16
+ import type { Provider } from "../types";
17
+ import { assertAuthenticatedOrLoopback, parseBind } from "../utils/parse-bind";
18
+ import { AUTH_BROKER_EPOCH_HEADER } from "./client";
19
+ import { cleanReason } from "./redact";
20
+ import { AuthBrokerRefresher, type AuthBrokerRefresherSchedule } from "./refresher";
21
+ import type {
22
+ CredentialDisableResponse,
23
+ CredentialIfAbsentUploadResponse,
24
+ CredentialMetadataResponse,
25
+ CredentialRefreshRequest,
26
+ CredentialRefreshResponse,
27
+ CredentialUploadResponse,
28
+ HealthzResponse,
29
+ RefresherSchedule,
30
+ SnapshotEntry,
31
+ SnapshotResponse,
32
+ SnapshotStreamSnapshotEvent,
33
+ } from "./types";
34
+ import {
35
+ DEFAULT_AUTH_BROKER_BIND,
36
+ DEFAULT_REFRESH_INTERVAL_MS,
37
+ DEFAULT_REFRESH_SKEW_MS,
38
+ DEFAULT_SERVER_IDLE_TIMEOUT_S,
39
+ DEFAULT_STREAM_KEEPALIVE_MS,
40
+ } from "./types";
41
+ import {
42
+ credentialDisableRequestSchema,
43
+ credentialRefreshRequestSchema,
44
+ credentialUploadRequestSchema,
45
+ } from "./wire-schemas";
46
+
47
+ export interface AuthBrokerServerOptions {
48
+ /** Underlying credential storage (wraps the local SQLite store on the broker). */
49
+ storage: AuthStorage;
50
+ /** Listen address; accepts `host:port` or just `port`. */
51
+ bind?: string;
52
+ /** Accept any of these bearer tokens. Empty disables auth (loopback only). */
53
+ bearerTokens: string[];
54
+ /** Broker version string surfaced on `/v1/healthz`. */
55
+ version?: string;
56
+ /** Refresh credentials expiring within this window. Default 5 min. */
57
+ refreshSkewMs?: number;
58
+ /** Background refresh cadence. Default 60s. */
59
+ refreshIntervalMs?: number;
60
+ /** Disable the background refresher (e.g. for tests). */
61
+ disableRefresher?: boolean;
62
+ /**
63
+ * Override SSE keepalive cadence in milliseconds for `/v1/snapshot/stream`.
64
+ * Internal-only — tests use a short interval so they can assert heartbeats
65
+ * without long sleeps. Default {@link DEFAULT_STREAM_KEEPALIVE_MS}.
66
+ */
67
+ streamKeepaliveMs?: number;
68
+ }
69
+
70
+ export interface AuthBrokerServerHandle {
71
+ /** Bound URL (`http://host:port`). */
72
+ url: string;
73
+ port: number;
74
+ hostname: string;
75
+ close(): Promise<void>;
76
+ }
77
+
78
+ function json(status: number, body: unknown, headers?: Record<string, string>): Response {
79
+ return new Response(JSON.stringify(body), {
80
+ status,
81
+ headers: { "Content-Type": "application/json", ...(headers ?? {}) },
82
+ });
83
+ }
84
+
85
+ function empty(status: number, headers?: Record<string, string>): Response {
86
+ return new Response(null, { status, headers });
87
+ }
88
+
89
+ function isAuthorized(req: Request, tokens: ReadonlySet<string>): boolean {
90
+ if (tokens.size === 0) return true;
91
+ const header = req.headers.get("authorization");
92
+ if (!header) return false;
93
+ const match = header.match(/^Bearer\s+(.+)$/i);
94
+ if (!match) return false;
95
+ const presented = new TextEncoder().encode(match[1].trim());
96
+ let ok = false;
97
+ for (const token of tokens) {
98
+ const expected = new TextEncoder().encode(token);
99
+ if (timingSafeEqual(presented, expected)) ok = true;
100
+ }
101
+ return ok;
102
+ }
103
+
104
+ /**
105
+ * Parse + validate a JSON request body against a Zod schema. Returns a
106
+ * `Response` (400) on parse/validation failure so handlers can early-return.
107
+ * When `allowEmpty` is set, an empty request body is validated against `{}`.
108
+ */
109
+ async function parseBody<T>(
110
+ req: Request,
111
+ schema: { safeParse(input: unknown): { success: true; data: T } | { success: false; error: { message: string } } },
112
+ options: { allowEmpty?: boolean } = {},
113
+ ): Promise<{ ok: true; data: T } | { ok: false; response: Response }> {
114
+ let raw: string;
115
+ try {
116
+ raw = await req.text();
117
+ } catch (error) {
118
+ return {
119
+ ok: false,
120
+ response: json(400, {
121
+ error: `Invalid request body: ${cleanReason(error) ?? "request body could not be read"}`,
122
+ }),
123
+ };
124
+ }
125
+ if (raw.length === 0 && !options.allowEmpty) {
126
+ return { ok: false, response: json(400, { error: "Request body required" }) };
127
+ }
128
+ let parsed: unknown;
129
+ try {
130
+ parsed = raw.length === 0 ? {} : JSON.parse(raw);
131
+ } catch (error) {
132
+ return {
133
+ ok: false,
134
+ response: json(400, {
135
+ error: `Invalid JSON body: ${cleanReason(error) ?? "request body could not be parsed"}`,
136
+ }),
137
+ };
138
+ }
139
+ const result = schema.safeParse(parsed);
140
+ if (!result.success) {
141
+ return { ok: false, response: json(400, { error: result.error.message }) };
142
+ }
143
+ return { ok: true, data: result.data };
144
+ }
145
+
146
+ const REFRESH_ROUTE = /^\/v1\/credential\/(\d+)\/refresh$/;
147
+ const DISABLE_ROUTE = /^\/v1\/credential\/(\d+)\/disable$/;
148
+
149
+ const MAX_SNAPSHOT_WAIT_MS = 30_000;
150
+ const DISABLED_NEXT_SWEEP_IN_MS = Number.MAX_SAFE_INTEGER;
151
+ const BROKER_EPOCH_SEQUENCE_CACHE_KEY = "auth-broker:epoch-sequence";
152
+
153
+ function snapshotHeaders(epoch: string, generation: number, includeEpoch: boolean): Record<string, string> {
154
+ return {
155
+ ETag: `"${includeEpoch ? `${epoch}:` : ""}${generation}"`,
156
+ "Cache-Control": "no-store",
157
+ };
158
+ }
159
+
160
+ function parseSnapshotTag(header: string | null): { epoch?: string; generation: number } | undefined {
161
+ if (!header) return undefined;
162
+ let value = header.trim();
163
+ if (value.startsWith("W/")) value = value.slice(2).trim();
164
+ if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
165
+ value = value.slice(1, -1);
166
+ }
167
+ const separator = value.lastIndexOf(":");
168
+ const epoch = separator > 0 ? value.slice(0, separator) : undefined;
169
+ const generation = Number(separator > 0 ? value.slice(separator + 1) : value);
170
+ if (!Number.isInteger(generation) || generation < 0) return undefined;
171
+ return { epoch, generation };
172
+ }
173
+
174
+ function parseWaitMs(url: URL): number {
175
+ const raw = url.searchParams.get("wait");
176
+ if (raw === null) return 0;
177
+ const parsed = Number(raw);
178
+ if (!Number.isFinite(parsed)) return 0;
179
+ return Math.max(0, Math.min(MAX_SNAPSHOT_WAIT_MS, Math.trunc(parsed)));
180
+ }
181
+
182
+ function delayResult(ms: number): { promise: Promise<"timeout">; cancel: () => void } {
183
+ const done = Promise.withResolvers<"timeout">();
184
+ const timer = setTimeout(() => done.resolve("timeout"), ms);
185
+ timer.unref?.();
186
+ return {
187
+ promise: done.promise,
188
+ cancel: () => clearTimeout(timer),
189
+ };
190
+ }
191
+
192
+ class GenerationGate {
193
+ readonly #storage: AuthStorage;
194
+ readonly #unsubscribe: () => void;
195
+ #waiters: Map<number, Set<() => void>> = new Map();
196
+
197
+ constructor(storage: AuthStorage) {
198
+ this.#storage = storage;
199
+ this.#unsubscribe = storage.onGenerationChanged(generation => this.#wake(generation));
200
+ }
201
+
202
+ waitForChange(afterGeneration: number, signal: AbortSignal): Promise<"changed" | "aborted"> {
203
+ if (this.#storage.getGeneration() !== afterGeneration) return Promise.resolve("changed");
204
+ if (signal.aborted) return Promise.resolve("aborted");
205
+
206
+ const done = Promise.withResolvers<"changed" | "aborted">();
207
+ let settled = false;
208
+ const waiters = this.#waiters.get(afterGeneration) ?? new Set<() => void>();
209
+ this.#waiters.set(afterGeneration, waiters);
210
+
211
+ const cleanup = (): void => {
212
+ signal.removeEventListener("abort", onAbort);
213
+ waiters.delete(resolveChanged);
214
+ if (waiters.size === 0) this.#waiters.delete(afterGeneration);
215
+ };
216
+ const settle = (result: "changed" | "aborted"): void => {
217
+ if (settled) return;
218
+ settled = true;
219
+ cleanup();
220
+ done.resolve(result);
221
+ };
222
+ const resolveChanged = (): void => settle("changed");
223
+ const onAbort = (): void => settle("aborted");
224
+
225
+ waiters.add(resolveChanged);
226
+ signal.addEventListener("abort", onAbort, { once: true });
227
+ return done.promise;
228
+ }
229
+
230
+ close(): void {
231
+ this.#unsubscribe();
232
+ for (const waiters of this.#waiters.values()) {
233
+ for (const resolve of waiters) resolve();
234
+ }
235
+ this.#waiters.clear();
236
+ }
237
+
238
+ #wake(generation: number): void {
239
+ for (const [waitingFor, waiters] of [...this.#waiters]) {
240
+ if (generation <= waitingFor) continue;
241
+ for (const resolve of [...waiters]) resolve();
242
+ }
243
+ }
244
+ }
245
+
246
+ function resolveRefresherSchedule(
247
+ refresher: AuthBrokerRefresher | undefined,
248
+ serverNowMs: number,
249
+ ): { wire: RefresherSchedule; nextSweepAt: number } {
250
+ if (!refresher) {
251
+ return {
252
+ wire: {
253
+ enabled: false,
254
+ intervalMs: 0,
255
+ skewMs: 0,
256
+ nextSweepInMs: DISABLED_NEXT_SWEEP_IN_MS,
257
+ },
258
+ nextSweepAt: DISABLED_NEXT_SWEEP_IN_MS,
259
+ };
260
+ }
261
+ const schedule: AuthBrokerRefresherSchedule = refresher.getSchedule();
262
+ return {
263
+ wire: {
264
+ enabled: schedule.enabled,
265
+ intervalMs: schedule.intervalMs,
266
+ skewMs: schedule.skewMs,
267
+ nextSweepInMs: Math.max(0, schedule.nextSweepAt - serverNowMs),
268
+ },
269
+ nextSweepAt: schedule.nextSweepAt,
270
+ };
271
+ }
272
+
273
+ function computeRotatesInMs(
274
+ entry: { credential: { type: string; expires?: number } },
275
+ schedule: RefresherSchedule,
276
+ nextSweepAt: number,
277
+ serverNowMs: number,
278
+ ): number | null {
279
+ if (!schedule.enabled || entry.credential.type !== "oauth") return null;
280
+ const expires = entry.credential.expires;
281
+ if (typeof expires !== "number" || !Number.isFinite(expires)) return null;
282
+ if (!Number.isFinite(nextSweepAt) || !Number.isFinite(schedule.intervalMs) || schedule.intervalMs <= 0) return null;
283
+
284
+ const dueAt = expires - schedule.skewMs;
285
+ const eligibleAt = Math.max(serverNowMs, dueAt);
286
+ if (dueAt <= serverNowMs && nextSweepAt <= serverNowMs) return 0;
287
+ if (nextSweepAt >= eligibleAt) return Math.max(0, nextSweepAt - serverNowMs);
288
+ const steps = Math.ceil((eligibleAt - nextSweepAt) / schedule.intervalMs);
289
+ const rotatesAt = nextSweepAt + steps * schedule.intervalMs;
290
+ return Math.max(0, rotatesAt - serverNowMs);
291
+ }
292
+
293
+ function buildSnapshot(
294
+ storage: AuthStorage,
295
+ refresher: AuthBrokerRefresher | undefined,
296
+ epoch: string,
297
+ includeEpoch: boolean,
298
+ ): SnapshotResponse {
299
+ const serverNowMs = Date.now();
300
+ const base = storage.exportSnapshot();
301
+ const { wire, nextSweepAt } = resolveRefresherSchedule(refresher, serverNowMs);
302
+ const credentials: SnapshotEntry[] = base.credentials.map(entry => ({
303
+ ...entry,
304
+ rotatesInMs: computeRotatesInMs(entry, wire, nextSweepAt, serverNowMs),
305
+ }));
306
+ return {
307
+ ...(includeEpoch ? { epoch } : {}),
308
+ generation: base.generation,
309
+ generatedAt: base.generatedAt,
310
+ serverNowMs,
311
+ refresher: wire,
312
+ credentials,
313
+ };
314
+ }
315
+
316
+ /** Build the payload-free credential inventory projection for the metadata route. */
317
+ function buildCredentialMetadata(
318
+ storage: AuthStorage,
319
+ epoch: string,
320
+ includeEpoch: boolean,
321
+ ): CredentialMetadataResponse {
322
+ const credentials = storage.listCredentialInventory().map(record => ({
323
+ id: record.id,
324
+ provider: record.provider,
325
+ type: record.credentialKind,
326
+ identity: record.identityLabel,
327
+ disabledCause: record.disabledCause === null ? null : "disabled via auth-broker",
328
+ }));
329
+ return {
330
+ ...(includeEpoch ? { epoch } : {}),
331
+ generation: storage.getGeneration(),
332
+ generatedAt: Date.now(),
333
+ credentials,
334
+ };
335
+ }
336
+
337
+ async function serveSnapshot(
338
+ req: Request,
339
+ url: URL,
340
+ storage: AuthStorage,
341
+ gate: GenerationGate,
342
+ refresher: AuthBrokerRefresher | undefined,
343
+ epoch: string,
344
+ peer: string,
345
+ ): Promise<Response> {
346
+ await storage.reload();
347
+ let currentGeneration = storage.getGeneration();
348
+ const clientTag = parseSnapshotTag(req.headers.get("if-none-match"));
349
+ const includeEpoch = req.headers.get(AUTH_BROKER_EPOCH_HEADER) === "1";
350
+ const clientGeneration = clientTag?.generation;
351
+ const waitMs = parseWaitMs(url);
352
+ const legacyConditional = clientTag !== undefined && !includeEpoch;
353
+
354
+ if (
355
+ legacyConditional ||
356
+ clientGeneration === undefined ||
357
+ (includeEpoch && clientTag?.epoch !== epoch) ||
358
+ currentGeneration !== clientGeneration ||
359
+ waitMs <= 0
360
+ ) {
361
+ const body = buildSnapshot(storage, refresher, epoch, includeEpoch);
362
+ logger.info("auth-broker snapshot served", {
363
+ peer,
364
+ credentials: body.credentials.length,
365
+ generation: body.generation,
366
+ });
367
+ return json(200, body, snapshotHeaders(epoch, body.generation, includeEpoch));
368
+ }
369
+
370
+ const delay = delayResult(waitMs);
371
+ const waitController = new AbortController();
372
+ const waitSignal = AbortSignal.any([req.signal, waitController.signal]);
373
+ const result = await Promise.race([gate.waitForChange(clientGeneration, waitSignal), delay.promise]);
374
+ delay.cancel();
375
+ waitController.abort();
376
+ if (result === "aborted" || req.signal.aborted)
377
+ return empty(499, snapshotHeaders(epoch, currentGeneration, includeEpoch));
378
+
379
+ await storage.reload();
380
+ currentGeneration = storage.getGeneration();
381
+ if (currentGeneration !== clientGeneration || (includeEpoch && clientTag?.epoch !== epoch)) {
382
+ const body = buildSnapshot(storage, refresher, epoch, includeEpoch);
383
+ logger.info("auth-broker snapshot long-poll changed", {
384
+ peer,
385
+ credentials: body.credentials.length,
386
+ generation: body.generation,
387
+ });
388
+ return json(200, body, snapshotHeaders(epoch, body.generation, includeEpoch));
389
+ }
390
+
391
+ logger.info("auth-broker snapshot long-poll unchanged", { peer, generation: currentGeneration });
392
+ return empty(304, snapshotHeaders(epoch, currentGeneration, includeEpoch));
393
+ }
394
+
395
+ function sseEvent(event: string, body: unknown): string {
396
+ return `event: ${event}\ndata: ${JSON.stringify(body)}\n\n`;
397
+ }
398
+
399
+ function serveSnapshotStream(
400
+ req: Request,
401
+ storage: AuthStorage,
402
+ refresher: AuthBrokerRefresher | undefined,
403
+ epoch: string,
404
+ peer: string,
405
+ keepaliveMs: number,
406
+ ): Response {
407
+ const encoder = new TextEncoder();
408
+ const includeEpoch = req.headers.get(AUTH_BROKER_EPOCH_HEADER) === "1";
409
+ const openedAt = Date.now();
410
+ let controller: ReadableStreamDefaultController<Uint8Array> | null = null;
411
+ let unsubscribe: (() => void) | null = null;
412
+ let keepaliveTimer: NodeJS.Timeout | undefined;
413
+ let abortHandler: (() => void) | null = null;
414
+ let processing = false;
415
+ let pendingBumps = 0;
416
+ let initializing = true;
417
+ let closed = false;
418
+ let lastGeneration = -1;
419
+
420
+ const cleanup = (): void => {
421
+ if (closed) return;
422
+ closed = true;
423
+ if (keepaliveTimer !== undefined) {
424
+ clearInterval(keepaliveTimer);
425
+ keepaliveTimer = undefined;
426
+ }
427
+ if (unsubscribe) {
428
+ unsubscribe();
429
+ unsubscribe = null;
430
+ }
431
+ if (abortHandler) {
432
+ req.signal.removeEventListener("abort", abortHandler);
433
+ abortHandler = null;
434
+ }
435
+ try {
436
+ controller?.close();
437
+ } catch {
438
+ // Already closed by Bun on client disconnect; harmless.
439
+ }
440
+ logger.info("auth-broker stream closed", { peer, durationMs: Date.now() - openedAt });
441
+ };
442
+
443
+ const write = (chunk: string): boolean => {
444
+ if (closed || !controller) return false;
445
+ try {
446
+ controller.enqueue(encoder.encode(chunk));
447
+ return true;
448
+ } catch (err) {
449
+ logger.debug("auth-broker stream enqueue failed", { peer, error: String(err) });
450
+ cleanup();
451
+ return false;
452
+ }
453
+ };
454
+
455
+ const processGenerationBump = async (): Promise<void> => {
456
+ if (closed) return;
457
+ if (processing) {
458
+ pendingBumps += 1;
459
+ return;
460
+ }
461
+ processing = true;
462
+ try {
463
+ do {
464
+ pendingBumps = 0;
465
+ try {
466
+ await storage.reload();
467
+ } catch (error) {
468
+ logger.warn("auth-broker stream generation reload failed", {
469
+ peer,
470
+ error: cleanReason(error) ?? "credential snapshot reload failed",
471
+ });
472
+ cleanup();
473
+ return;
474
+ }
475
+ if (closed) return;
476
+ const snapshot = buildSnapshot(storage, refresher, epoch, includeEpoch);
477
+ // Generation must move forward; a duplicate listener firing without a
478
+ // real bump is a no-op below (fingerprints unchanged).
479
+ if (snapshot.generation < lastGeneration) {
480
+ logger.warn("auth-broker stream generation went backwards", {
481
+ peer,
482
+ previous: lastGeneration,
483
+ current: snapshot.generation,
484
+ });
485
+ }
486
+ lastGeneration = snapshot.generation;
487
+ const snapshotEvent: SnapshotStreamSnapshotEvent = { kind: "snapshot", ...snapshot };
488
+ if (!write(sseEvent("snapshot", snapshotEvent))) return;
489
+ logger.debug("auth-broker stream snapshot", {
490
+ peer,
491
+ credentialCount: snapshot.credentials.length,
492
+ generation: snapshot.generation,
493
+ });
494
+ } while (pendingBumps > 0 && !closed);
495
+ } finally {
496
+ processing = false;
497
+ }
498
+ };
499
+
500
+ const stream = new ReadableStream<Uint8Array>({
501
+ async start(c) {
502
+ controller = c;
503
+ abortHandler = (): void => cleanup();
504
+ req.signal.addEventListener("abort", abortHandler, { once: true });
505
+ unsubscribe = storage.onGenerationChanged(() => {
506
+ if (initializing) {
507
+ pendingBumps += 1;
508
+ return;
509
+ }
510
+ void processGenerationBump();
511
+ });
512
+ try {
513
+ await storage.reload();
514
+ if (closed || req.signal.aborted) {
515
+ cleanup();
516
+ return;
517
+ }
518
+ const initial = buildSnapshot(storage, refresher, epoch, includeEpoch);
519
+ lastGeneration = initial.generation;
520
+ const initialEvent: SnapshotStreamSnapshotEvent = { kind: "snapshot", ...initial };
521
+ if (!write(sseEvent("snapshot", initialEvent))) return;
522
+ initializing = false;
523
+ keepaliveTimer = setInterval(() => {
524
+ write(": keepalive\n\n");
525
+ }, keepaliveMs);
526
+ keepaliveTimer.unref?.();
527
+ if (pendingBumps > 0) void processGenerationBump();
528
+ logger.info("auth-broker stream opened", { peer, generation: initial.generation });
529
+ } catch (error) {
530
+ logger.warn("auth-broker stream initialization failed", {
531
+ peer,
532
+ error: cleanReason(error) ?? "credential snapshot initialization failed",
533
+ });
534
+ cleanup();
535
+ }
536
+ },
537
+ cancel() {
538
+ cleanup();
539
+ },
540
+ });
541
+
542
+ return new Response(stream, {
543
+ status: 200,
544
+ headers: {
545
+ "Content-Type": "text/event-stream; charset=utf-8",
546
+ "Cache-Control": "no-cache",
547
+ Connection: "keep-alive",
548
+ "X-Accel-Buffering": "no",
549
+ },
550
+ });
551
+ }
552
+
553
+ /** Boot the broker. Caller owns lifecycle; `handle.close()` to stop. */
554
+ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServerHandle {
555
+ const bind = parseBind(opts.bind ?? DEFAULT_AUTH_BROKER_BIND);
556
+ const tokens = new Set<string>(opts.bearerTokens);
557
+ assertAuthenticatedOrLoopback(bind, tokens.size, "auth-broker");
558
+ const version = opts.version;
559
+ const streamKeepaliveMs = opts.streamKeepaliveMs ?? DEFAULT_STREAM_KEEPALIVE_MS;
560
+ let epochSequence: number;
561
+ try {
562
+ epochSequence = opts.storage.allocateMonotonicSequence(
563
+ BROKER_EPOCH_SEQUENCE_CACHE_KEY,
564
+ Math.floor(Date.now() / 1000) + 315_360_000,
565
+ );
566
+ } catch (error) {
567
+ throw new Error(
568
+ `Auth broker storage must support atomic monotonic sequence allocation: ${cleanReason(error) ?? "unsupported storage"}`,
569
+ );
570
+ }
571
+ const epoch = `${epochSequence}-${crypto.randomUUID()}`;
572
+
573
+ const refresher = opts.disableRefresher
574
+ ? undefined
575
+ : new AuthBrokerRefresher({
576
+ storage: opts.storage,
577
+ refreshSkewMs: opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS,
578
+ refreshIntervalMs: opts.refreshIntervalMs ?? DEFAULT_REFRESH_INTERVAL_MS,
579
+ });
580
+ refresher?.start();
581
+ const generationGate = new GenerationGate(opts.storage);
582
+
583
+ const server = Bun.serve({
584
+ hostname: bind.hostname,
585
+ port: bind.port,
586
+ idleTimeout: DEFAULT_SERVER_IDLE_TIMEOUT_S,
587
+ fetch: async (req): Promise<Response> => {
588
+ const url = new URL(req.url);
589
+ const pathname = url.pathname;
590
+ const peer =
591
+ req.headers.get("x-forwarded-for")?.split(",")[0].trim() || req.headers.get("x-real-ip") || "unknown";
592
+ try {
593
+ if (req.method === "GET" && pathname === "/v1/healthz") {
594
+ const body: HealthzResponse = { ok: true, version };
595
+ return json(200, body);
596
+ }
597
+ if (!isAuthorized(req, tokens)) {
598
+ logger.info("auth-broker request unauthorized", { method: req.method, path: pathname, peer });
599
+ return json(401, { error: "unauthorized" });
600
+ }
601
+ if (req.method === "GET" && pathname === "/v1/credentials/metadata") {
602
+ await opts.storage.reload();
603
+ const body = buildCredentialMetadata(
604
+ opts.storage,
605
+ epoch,
606
+ req.headers.get(AUTH_BROKER_EPOCH_HEADER) === "1",
607
+ );
608
+ logger.info("auth-broker credential metadata served", {
609
+ generation: body.generation,
610
+ status: "ok",
611
+ });
612
+ return json(200, body);
613
+ }
614
+ if (req.method === "GET" && pathname === "/v1/snapshot/stream") {
615
+ return serveSnapshotStream(req, opts.storage, refresher, epoch, peer, streamKeepaliveMs);
616
+ }
617
+ if (req.method === "GET" && pathname === "/v1/snapshot") {
618
+ return serveSnapshot(req, url, opts.storage, generationGate, refresher, epoch, peer);
619
+ }
620
+ if (req.method === "GET" && (pathname === "/v1/usage" || pathname === "/v1/usage/scoped")) {
621
+ try {
622
+ // AuthStorage caches usage reports internally with a 5-minute per-credential
623
+ // TTL (USAGE_REPORT_TTL_MS) so back-to-back widget polls re-use the
624
+ // last fetch instead of hitting provider endpoints repeatedly.
625
+ // `req.signal` propagates HTTP-client disconnects all the way to the
626
+ // per-caller cancel without touching the shared upstream fetch.
627
+ const scopedProvider = url.searchParams.get("provider") as Provider | null;
628
+ if (pathname === "/v1/usage/scoped" && !scopedProvider) {
629
+ return json(400, { error: "provider is required" });
630
+ }
631
+ const reports =
632
+ (await opts.storage.fetchUsageReports?.({
633
+ provider: scopedProvider ?? undefined,
634
+ signal: req.signal,
635
+ })) ?? [];
636
+ // Drop the `raw` field — it's the provider-specific upstream body,
637
+ // large and unstable. Everything UI-relevant lives in `limits` and
638
+ // `metadata`.
639
+ const trimmed = reports.map(({ raw: _raw, ...rest }) => rest);
640
+ logger.info("auth-broker usage served", { peer, reports: trimmed.length });
641
+ return json(200, { generatedAt: Date.now(), reports: trimmed });
642
+ } catch (error) {
643
+ const message = cleanReason(error) ?? "Usage fetch failed";
644
+ logger.warn("auth-broker usage fetch failed", { peer, error: message });
645
+ return json(502, { error: message });
646
+ }
647
+ }
648
+ const refreshMatch = req.method === "POST" ? pathname.match(REFRESH_ROUTE) : null;
649
+ if (refreshMatch) {
650
+ const id = Number.parseInt(refreshMatch[1], 10);
651
+ try {
652
+ const parsed = await parseBody(req, credentialRefreshRequestSchema, { allowEmpty: true });
653
+ if (!parsed.ok) return parsed.response;
654
+ const refreshRequest: CredentialRefreshRequest = parsed.data;
655
+ const entry = await opts.storage.refreshCredentialById(id, req.signal, refreshRequest);
656
+ const body: CredentialRefreshResponse = { entry };
657
+ logger.info("auth-broker credential refreshed", {
658
+ id,
659
+ provider: entry.provider,
660
+ peer,
661
+ expires: entry.credential.type === "oauth" ? entry.credential.expires : undefined,
662
+ });
663
+ return json(200, body);
664
+ } catch (error) {
665
+ const message = cleanReason(error) ?? "Credential refresh failed";
666
+ logger.warn("auth-broker refresh failed", { id, peer, error: message });
667
+ const status = message.startsWith("No credential with id") ? 404 : 500;
668
+ return json(status, { error: message });
669
+ }
670
+ }
671
+ const disableMatch = req.method === "POST" ? pathname.match(DISABLE_ROUTE) : null;
672
+ if (disableMatch) {
673
+ const id = Number.parseInt(disableMatch[1], 10);
674
+ const parsed = await parseBody(req, credentialDisableRequestSchema, { allowEmpty: true });
675
+ if (!parsed.ok) return parsed.response;
676
+ const cause = "disabled via auth-broker";
677
+ const ok =
678
+ parsed.data.expectedRevision === undefined
679
+ ? opts.storage.disableCredentialById(id, cause)
680
+ : opts.storage.disableCredentialByIdIfRevision(id, parsed.data.expectedRevision, cause);
681
+ if (!ok) {
682
+ logger.info("auth-broker disable miss", { id, peer });
683
+ return json(404, { error: `No credential with id=${id}` });
684
+ }
685
+ logger.info("auth-broker credential disabled", { id, peer });
686
+ const response: CredentialDisableResponse = { ok: true };
687
+ return json(200, response);
688
+ }
689
+ if (req.method === "POST" && pathname === "/v1/credential/if-absent") {
690
+ const parsed = await parseBody(req, credentialUploadRequestSchema);
691
+ if (!parsed.ok) return parsed.response;
692
+ const { provider, credential } = parsed.data;
693
+ try {
694
+ const result = await opts.storage.importCredentialIfAbsent(provider, credential);
695
+ logger.info("auth-broker credential import-if-absent", {
696
+ provider,
697
+ type: credential.type,
698
+ inserted: result.inserted,
699
+ reason: result.reason,
700
+ providerTotal: result.entries.length,
701
+ peer,
702
+ });
703
+ const response: CredentialIfAbsentUploadResponse = {
704
+ inserted: result.inserted,
705
+ reason: result.reason,
706
+ entries: result.entries,
707
+ };
708
+ return json(200, response);
709
+ } catch (error) {
710
+ const message = cleanReason(error) ?? "Credential import failed";
711
+ logger.warn("auth-broker upload-if-absent failed", { provider, type: credential.type, peer });
712
+ return json(500, { error: message });
713
+ }
714
+ }
715
+ if (req.method === "POST" && pathname === "/v1/credential") {
716
+ const parsed = await parseBody(req, credentialUploadRequestSchema);
717
+ if (!parsed.ok) return parsed.response;
718
+ const { provider, credential } = parsed.data;
719
+ try {
720
+ const entries = opts.storage.upsertCredential(provider, credential);
721
+ const identity =
722
+ credential.type === "oauth"
723
+ ? (credential.email ?? credential.accountId ?? credential.projectId ?? "(no identity)")
724
+ : "(api key)";
725
+ logger.info("auth-broker credential upserted", {
726
+ provider,
727
+ type: credential.type,
728
+ identity,
729
+ peer,
730
+ providerTotal: entries.length,
731
+ });
732
+ const response: CredentialUploadResponse = { entries };
733
+ return json(200, response);
734
+ } catch (error) {
735
+ const message = cleanReason(error) ?? "Credential upload failed";
736
+ logger.warn("auth-broker upload failed", { provider, peer, error: message });
737
+ return json(500, { error: message });
738
+ }
739
+ }
740
+ return json(404, { error: `No route: ${req.method} ${pathname}` });
741
+ } catch (error) {
742
+ logger.error("auth-broker handler crashed", {
743
+ method: req.method,
744
+ path: pathname,
745
+ error: cleanReason(error) ?? "internal error",
746
+ });
747
+ return json(500, { error: "internal error" });
748
+ }
749
+ },
750
+ });
751
+
752
+ const boundHost = server.hostname ?? bind.hostname;
753
+ const boundPort = server.port ?? bind.port;
754
+ return {
755
+ url: `http://${boundHost}:${boundPort}`,
756
+ port: boundPort,
757
+ hostname: boundHost,
758
+ close: async () => {
759
+ refresher?.stop();
760
+ generationGate.close();
761
+ server.stop(true);
762
+ },
763
+ };
764
+ }