@gajae-code/ai 0.1.1

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 (349) hide show
  1. package/CHANGELOG.md +2644 -0
  2. package/README.md +1181 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth-broker/client.d.ts +66 -0
  5. package/dist/types/auth-broker/index.d.ts +5 -0
  6. package/dist/types/auth-broker/refresher.d.ts +25 -0
  7. package/dist/types/auth-broker/remote-store.d.ts +96 -0
  8. package/dist/types/auth-broker/server.d.ts +32 -0
  9. package/dist/types/auth-broker/types.d.ts +105 -0
  10. package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
  11. package/dist/types/auth-gateway/http.d.ts +39 -0
  12. package/dist/types/auth-gateway/index.d.ts +3 -0
  13. package/dist/types/auth-gateway/server.d.ts +17 -0
  14. package/dist/types/auth-gateway/types.d.ts +115 -0
  15. package/dist/types/auth-storage.d.ts +641 -0
  16. package/dist/types/cli.d.ts +2 -0
  17. package/dist/types/index.d.ts +49 -0
  18. package/dist/types/model-cache.d.ts +17 -0
  19. package/dist/types/model-manager.d.ts +62 -0
  20. package/dist/types/model-thinking.d.ts +71 -0
  21. package/dist/types/models.d.ts +12 -0
  22. package/dist/types/provider-details.d.ts +24 -0
  23. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  24. package/dist/types/provider-models/descriptors.d.ts +48 -0
  25. package/dist/types/provider-models/google.d.ts +20 -0
  26. package/dist/types/provider-models/index.d.ts +5 -0
  27. package/dist/types/provider-models/ollama.d.ts +7 -0
  28. package/dist/types/provider-models/openai-compat.d.ts +237 -0
  29. package/dist/types/provider-models/special.d.ts +16 -0
  30. package/dist/types/providers/amazon-bedrock.d.ts +36 -0
  31. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  32. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  33. package/dist/types/providers/anthropic.d.ts +188 -0
  34. package/dist/types/providers/aws-credentials.d.ts +43 -0
  35. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  36. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  37. package/dist/types/providers/azure-openai-responses.d.ts +15 -0
  38. package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
  39. package/dist/types/providers/cursor.d.ts +42 -0
  40. package/dist/types/providers/error-message.d.ts +27 -0
  41. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  42. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  43. package/dist/types/providers/google-auth.d.ts +24 -0
  44. package/dist/types/providers/google-gemini-cli.d.ts +72 -0
  45. package/dist/types/providers/google-gemini-headers.d.ts +18 -0
  46. package/dist/types/providers/google-shared.d.ts +163 -0
  47. package/dist/types/providers/google-types.d.ts +138 -0
  48. package/dist/types/providers/google-vertex.d.ts +7 -0
  49. package/dist/types/providers/google.d.ts +4 -0
  50. package/dist/types/providers/grammar.d.ts +1 -0
  51. package/dist/types/providers/kimi.d.ts +27 -0
  52. package/dist/types/providers/mock.d.ts +175 -0
  53. package/dist/types/providers/ollama.d.ts +6 -0
  54. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  55. package/dist/types/providers/openai-chat-server-schema.d.ts +814 -0
  56. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  57. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  58. package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
  59. package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
  60. package/dist/types/providers/openai-codex-responses.d.ts +67 -0
  61. package/dist/types/providers/openai-completions-compat.d.ts +25 -0
  62. package/dist/types/providers/openai-completions.d.ts +33 -0
  63. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  64. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  65. package/dist/types/providers/openai-responses-shared.d.ts +89 -0
  66. package/dist/types/providers/openai-responses.d.ts +32 -0
  67. package/dist/types/providers/pi-native-client.d.ts +13 -0
  68. package/dist/types/providers/pi-native-server.d.ts +68 -0
  69. package/dist/types/providers/register-builtins.d.ts +31 -0
  70. package/dist/types/providers/synthetic.d.ts +26 -0
  71. package/dist/types/providers/transform-messages.d.ts +12 -0
  72. package/dist/types/providers/vision-guard.d.ts +8 -0
  73. package/dist/types/rate-limit-utils.d.ts +19 -0
  74. package/dist/types/stream.d.ts +24 -0
  75. package/dist/types/types.d.ts +746 -0
  76. package/dist/types/usage/claude.d.ts +3 -0
  77. package/dist/types/usage/gemini.d.ts +2 -0
  78. package/dist/types/usage/github-copilot.d.ts +7 -0
  79. package/dist/types/usage/google-antigravity.d.ts +2 -0
  80. package/dist/types/usage/kimi.d.ts +2 -0
  81. package/dist/types/usage/minimax-code.d.ts +2 -0
  82. package/dist/types/usage/openai-codex.d.ts +3 -0
  83. package/dist/types/usage/shared.d.ts +1 -0
  84. package/dist/types/usage/zai.d.ts +2 -0
  85. package/dist/types/usage.d.ts +258 -0
  86. package/dist/types/utils/abort.d.ts +19 -0
  87. package/dist/types/utils/anthropic-auth.d.ts +31 -0
  88. package/dist/types/utils/discovery/antigravity.d.ts +61 -0
  89. package/dist/types/utils/discovery/codex.d.ts +38 -0
  90. package/dist/types/utils/discovery/cursor.d.ts +23 -0
  91. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  92. package/dist/types/utils/discovery/index.d.ts +4 -0
  93. package/dist/types/utils/discovery/openai-compatible.d.ts +72 -0
  94. package/dist/types/utils/event-stream.d.ts +28 -0
  95. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  96. package/dist/types/utils/foundry.d.ts +1 -0
  97. package/dist/types/utils/h2-fetch.d.ts +22 -0
  98. package/dist/types/utils/http-inspector.d.ts +31 -0
  99. package/dist/types/utils/idle-iterator.d.ts +67 -0
  100. package/dist/types/utils/json-parse.d.ts +10 -0
  101. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
  102. package/dist/types/utils/oauth/anthropic.d.ts +22 -0
  103. package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
  104. package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
  105. package/dist/types/utils/oauth/callback-server.d.ts +57 -0
  106. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  107. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  108. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  109. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  110. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  111. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  112. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  113. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  114. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  115. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  116. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  117. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  118. package/dist/types/utils/oauth/index.d.ts +38 -0
  119. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  120. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  121. package/dist/types/utils/oauth/kimi.d.ts +21 -0
  122. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  123. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  124. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  125. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  126. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  127. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  128. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  129. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  130. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  131. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  132. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  133. package/dist/types/utils/oauth/perplexity.d.ts +9 -0
  134. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  135. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  136. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  137. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  138. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  139. package/dist/types/utils/oauth/together.d.ts +1 -0
  140. package/dist/types/utils/oauth/types.d.ts +44 -0
  141. package/dist/types/utils/oauth/venice.d.ts +18 -0
  142. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  143. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  144. package/dist/types/utils/oauth/xiaomi.d.ts +19 -0
  145. package/dist/types/utils/oauth/zai.d.ts +18 -0
  146. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  147. package/dist/types/utils/overflow.d.ts +54 -0
  148. package/dist/types/utils/parse-bind.d.ts +23 -0
  149. package/dist/types/utils/provider-response.d.ts +3 -0
  150. package/dist/types/utils/retry-after.d.ts +3 -0
  151. package/dist/types/utils/retry.d.ts +26 -0
  152. package/dist/types/utils/schema/adapt.d.ts +24 -0
  153. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  154. package/dist/types/utils/schema/dereference.d.ts +11 -0
  155. package/dist/types/utils/schema/draft.d.ts +10 -0
  156. package/dist/types/utils/schema/equality.d.ts +4 -0
  157. package/dist/types/utils/schema/fields.d.ts +49 -0
  158. package/dist/types/utils/schema/index.d.ts +13 -0
  159. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  160. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  161. package/dist/types/utils/schema/normalize.d.ts +93 -0
  162. package/dist/types/utils/schema/spill.d.ts +8 -0
  163. package/dist/types/utils/schema/stamps.d.ts +25 -0
  164. package/dist/types/utils/schema/types.d.ts +4 -0
  165. package/dist/types/utils/schema/wire.d.ts +54 -0
  166. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  167. package/dist/types/utils/sse-debug.d.ts +10 -0
  168. package/dist/types/utils/tool-call-healing.d.ts +71 -0
  169. package/dist/types/utils/tool-choice.d.ts +50 -0
  170. package/dist/types/utils/validation.d.ts +17 -0
  171. package/dist/types/utils.d.ts +28 -0
  172. package/package.json +146 -0
  173. package/src/api-registry.ts +96 -0
  174. package/src/auth-broker/client.ts +358 -0
  175. package/src/auth-broker/index.ts +5 -0
  176. package/src/auth-broker/refresher.ts +127 -0
  177. package/src/auth-broker/remote-store.ts +623 -0
  178. package/src/auth-broker/server.ts +644 -0
  179. package/src/auth-broker/types.ts +127 -0
  180. package/src/auth-broker/wire-schemas.ts +200 -0
  181. package/src/auth-gateway/http.ts +194 -0
  182. package/src/auth-gateway/index.ts +3 -0
  183. package/src/auth-gateway/server.ts +717 -0
  184. package/src/auth-gateway/types.ts +134 -0
  185. package/src/auth-storage.ts +4104 -0
  186. package/src/cli.ts +262 -0
  187. package/src/index.ts +54 -0
  188. package/src/model-cache.ts +129 -0
  189. package/src/model-manager.ts +450 -0
  190. package/src/model-thinking.ts +691 -0
  191. package/src/models.json +73853 -0
  192. package/src/models.json.d.ts +9 -0
  193. package/src/models.ts +56 -0
  194. package/src/prompts/turn-aborted-guidance.md +4 -0
  195. package/src/provider-details.ts +90 -0
  196. package/src/provider-models/bundled-references.ts +38 -0
  197. package/src/provider-models/descriptors.ts +308 -0
  198. package/src/provider-models/google.ts +91 -0
  199. package/src/provider-models/index.ts +5 -0
  200. package/src/provider-models/ollama.ts +153 -0
  201. package/src/provider-models/openai-compat.ts +2275 -0
  202. package/src/provider-models/special.ts +67 -0
  203. package/src/providers/amazon-bedrock.ts +849 -0
  204. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  205. package/src/providers/anthropic-messages-server.ts +677 -0
  206. package/src/providers/anthropic.ts +2696 -0
  207. package/src/providers/aws-credentials.ts +501 -0
  208. package/src/providers/aws-eventstream.ts +185 -0
  209. package/src/providers/aws-sigv4.ts +218 -0
  210. package/src/providers/azure-openai-responses.ts +337 -0
  211. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  212. package/src/providers/cursor/proto/agent.proto +3526 -0
  213. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  214. package/src/providers/cursor/proto/buf.yaml +17 -0
  215. package/src/providers/cursor.ts +2561 -0
  216. package/src/providers/error-message.ts +21 -0
  217. package/src/providers/github-copilot-headers.ts +140 -0
  218. package/src/providers/gitlab-duo.ts +372 -0
  219. package/src/providers/google-auth.ts +252 -0
  220. package/src/providers/google-gemini-cli.ts +795 -0
  221. package/src/providers/google-gemini-headers.ts +41 -0
  222. package/src/providers/google-shared.ts +902 -0
  223. package/src/providers/google-types.ts +167 -0
  224. package/src/providers/google-vertex.ts +88 -0
  225. package/src/providers/google.ts +41 -0
  226. package/src/providers/grammar.ts +70 -0
  227. package/src/providers/kimi.ts +52 -0
  228. package/src/providers/mock.ts +500 -0
  229. package/src/providers/ollama.ts +544 -0
  230. package/src/providers/openai-anthropic-shim.ts +138 -0
  231. package/src/providers/openai-chat-server-schema.ts +243 -0
  232. package/src/providers/openai-chat-server.ts +628 -0
  233. package/src/providers/openai-codex/constants.ts +43 -0
  234. package/src/providers/openai-codex/request-transformer.ts +161 -0
  235. package/src/providers/openai-codex/response-handler.ts +81 -0
  236. package/src/providers/openai-codex-responses.ts +2598 -0
  237. package/src/providers/openai-completions-compat.ts +279 -0
  238. package/src/providers/openai-completions.ts +1853 -0
  239. package/src/providers/openai-responses-server-schema.ts +290 -0
  240. package/src/providers/openai-responses-server.ts +1183 -0
  241. package/src/providers/openai-responses-shared.ts +800 -0
  242. package/src/providers/openai-responses.ts +621 -0
  243. package/src/providers/pi-native-client.ts +228 -0
  244. package/src/providers/pi-native-server.ts +210 -0
  245. package/src/providers/register-builtins.ts +412 -0
  246. package/src/providers/synthetic.ts +50 -0
  247. package/src/providers/transform-messages.ts +309 -0
  248. package/src/providers/vision-guard.ts +31 -0
  249. package/src/rate-limit-utils.ts +84 -0
  250. package/src/stream.ts +895 -0
  251. package/src/types.ts +884 -0
  252. package/src/usage/claude.ts +431 -0
  253. package/src/usage/gemini.ts +250 -0
  254. package/src/usage/github-copilot.ts +421 -0
  255. package/src/usage/google-antigravity.ts +201 -0
  256. package/src/usage/kimi.ts +271 -0
  257. package/src/usage/minimax-code.ts +31 -0
  258. package/src/usage/openai-codex.ts +503 -0
  259. package/src/usage/shared.ts +10 -0
  260. package/src/usage/zai.ts +247 -0
  261. package/src/usage.ts +183 -0
  262. package/src/utils/abort.ts +51 -0
  263. package/src/utils/anthropic-auth.ts +87 -0
  264. package/src/utils/discovery/antigravity.ts +261 -0
  265. package/src/utils/discovery/codex.ts +371 -0
  266. package/src/utils/discovery/cursor.ts +306 -0
  267. package/src/utils/discovery/gemini.ts +248 -0
  268. package/src/utils/discovery/index.ts +4 -0
  269. package/src/utils/discovery/openai-compatible.ts +224 -0
  270. package/src/utils/event-stream.ts +142 -0
  271. package/src/utils/fireworks-model-id.ts +30 -0
  272. package/src/utils/foundry.ts +8 -0
  273. package/src/utils/h2-fetch.ts +60 -0
  274. package/src/utils/http-inspector.ts +176 -0
  275. package/src/utils/idle-iterator.ts +250 -0
  276. package/src/utils/json-parse.ts +148 -0
  277. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  278. package/src/utils/oauth/anthropic.ts +200 -0
  279. package/src/utils/oauth/api-key-login.ts +87 -0
  280. package/src/utils/oauth/api-key-validation.ts +92 -0
  281. package/src/utils/oauth/callback-server.ts +276 -0
  282. package/src/utils/oauth/cerebras.ts +16 -0
  283. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  284. package/src/utils/oauth/cursor.ts +157 -0
  285. package/src/utils/oauth/deepseek.ts +53 -0
  286. package/src/utils/oauth/firepass.ts +24 -0
  287. package/src/utils/oauth/fireworks.ts +15 -0
  288. package/src/utils/oauth/github-copilot.ts +362 -0
  289. package/src/utils/oauth/gitlab-duo.ts +123 -0
  290. package/src/utils/oauth/google-antigravity.ts +200 -0
  291. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  292. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  293. package/src/utils/oauth/huggingface.ts +62 -0
  294. package/src/utils/oauth/index.ts +444 -0
  295. package/src/utils/oauth/kagi.ts +47 -0
  296. package/src/utils/oauth/kilo.ts +87 -0
  297. package/src/utils/oauth/kimi.ts +254 -0
  298. package/src/utils/oauth/litellm.ts +47 -0
  299. package/src/utils/oauth/lm-studio.ts +38 -0
  300. package/src/utils/oauth/minimax-code.ts +78 -0
  301. package/src/utils/oauth/moonshot.ts +16 -0
  302. package/src/utils/oauth/nanogpt.ts +15 -0
  303. package/src/utils/oauth/nvidia.ts +70 -0
  304. package/src/utils/oauth/oauth.html +199 -0
  305. package/src/utils/oauth/ollama-cloud.ts +28 -0
  306. package/src/utils/oauth/ollama.ts +47 -0
  307. package/src/utils/oauth/openai-codex.ts +299 -0
  308. package/src/utils/oauth/opencode.ts +49 -0
  309. package/src/utils/oauth/parallel.ts +46 -0
  310. package/src/utils/oauth/perplexity.ts +206 -0
  311. package/src/utils/oauth/pkce.ts +18 -0
  312. package/src/utils/oauth/qianfan.ts +58 -0
  313. package/src/utils/oauth/qwen-portal.ts +60 -0
  314. package/src/utils/oauth/synthetic.ts +16 -0
  315. package/src/utils/oauth/tavily.ts +46 -0
  316. package/src/utils/oauth/together.ts +16 -0
  317. package/src/utils/oauth/types.ts +94 -0
  318. package/src/utils/oauth/venice.ts +59 -0
  319. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  320. package/src/utils/oauth/vllm.ts +40 -0
  321. package/src/utils/oauth/xiaomi.ts +137 -0
  322. package/src/utils/oauth/zai.ts +60 -0
  323. package/src/utils/oauth/zenmux.ts +15 -0
  324. package/src/utils/overflow.ts +137 -0
  325. package/src/utils/parse-bind.ts +54 -0
  326. package/src/utils/provider-response.ts +30 -0
  327. package/src/utils/retry-after.ts +110 -0
  328. package/src/utils/retry.ts +54 -0
  329. package/src/utils/schema/CONSTRAINTS.md +164 -0
  330. package/src/utils/schema/adapt.ts +36 -0
  331. package/src/utils/schema/compatibility.ts +435 -0
  332. package/src/utils/schema/dereference.ts +98 -0
  333. package/src/utils/schema/draft.ts +341 -0
  334. package/src/utils/schema/equality.ts +97 -0
  335. package/src/utils/schema/fields.ts +190 -0
  336. package/src/utils/schema/index.ts +13 -0
  337. package/src/utils/schema/json-schema-validator.ts +577 -0
  338. package/src/utils/schema/meta-validator.ts +167 -0
  339. package/src/utils/schema/normalize.ts +1588 -0
  340. package/src/utils/schema/spill.ts +43 -0
  341. package/src/utils/schema/stamps.ts +97 -0
  342. package/src/utils/schema/types.ts +11 -0
  343. package/src/utils/schema/wire.ts +213 -0
  344. package/src/utils/schema/zod-decontaminate.ts +331 -0
  345. package/src/utils/sse-debug.ts +289 -0
  346. package/src/utils/tool-call-healing.ts +271 -0
  347. package/src/utils/tool-choice.ts +99 -0
  348. package/src/utils/validation.ts +1019 -0
  349. package/src/utils.ts +166 -0
@@ -0,0 +1,250 @@
1
+ import { $env } from "@gajae-code/utils";
2
+
3
+ const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 120_000;
4
+ const DEFAULT_STREAM_FIRST_EVENT_TIMEOUT_MS = 100_000;
5
+
6
+ function normalizeIdleTimeoutMs(value: string | undefined, fallback: number): number | undefined {
7
+ if (value === undefined) return fallback;
8
+ const parsed = Number(value);
9
+ if (!Number.isFinite(parsed)) return fallback;
10
+ if (parsed <= 0) return undefined;
11
+ return Math.trunc(parsed);
12
+ }
13
+
14
+ /**
15
+ * Returns the idle timeout used for provider streaming transports.
16
+ *
17
+ * `PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` is accepted as a backward-compatible alias.
18
+ * Set `PI_STREAM_IDLE_TIMEOUT_MS=0` to disable the watchdog.
19
+ *
20
+ * Providers that legitimately stream much slower than the global default can pass
21
+ * `fallbackMs` to widen the floor used when neither env var nor caller option is set.
22
+ * Caller options still take precedence; env overrides still trump the fallback.
23
+ */
24
+ export function getStreamIdleTimeoutMs(fallbackMs: number = DEFAULT_STREAM_IDLE_TIMEOUT_MS): number | undefined {
25
+ return normalizeIdleTimeoutMs($env.PI_STREAM_IDLE_TIMEOUT_MS ?? $env.PI_OPENAI_STREAM_IDLE_TIMEOUT_MS, fallbackMs);
26
+ }
27
+
28
+ /**
29
+ * Returns the idle timeout used for OpenAI-family streaming transports.
30
+ *
31
+ * Set `PI_OPENAI_STREAM_IDLE_TIMEOUT_MS=0` to disable the watchdog.
32
+ */
33
+ export function getOpenAIStreamIdleTimeoutMs(): number | undefined {
34
+ return normalizeIdleTimeoutMs(
35
+ $env.PI_OPENAI_STREAM_IDLE_TIMEOUT_MS ?? $env.PI_STREAM_IDLE_TIMEOUT_MS,
36
+ DEFAULT_STREAM_IDLE_TIMEOUT_MS,
37
+ );
38
+ }
39
+
40
+ /**
41
+ * Returns the timeout used while waiting for the first stream event.
42
+ * The first token can legitimately take longer than later inter-event gaps,
43
+ * so the default never undershoots the steady-state idle timeout.
44
+ *
45
+ * Set `PI_STREAM_FIRST_EVENT_TIMEOUT_MS=0` to disable the watchdog.
46
+ *
47
+ * Providers whose first response can legitimately take longer (heavy reasoning,
48
+ * slow cold-start proxies) can pass `fallbackMs` to widen the floor used when
49
+ * neither env var nor caller option is set. Caller options still take precedence;
50
+ * env overrides still trump the fallback.
51
+ */
52
+ export function getStreamFirstEventTimeoutMs(
53
+ idleTimeoutMs?: number,
54
+ fallbackMs: number = DEFAULT_STREAM_FIRST_EVENT_TIMEOUT_MS,
55
+ ): number | undefined {
56
+ const fallback = idleTimeoutMs === undefined ? fallbackMs : Math.max(fallbackMs, idleTimeoutMs);
57
+ return normalizeIdleTimeoutMs($env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS, fallback);
58
+ }
59
+
60
+ export type Watchdog = NodeJS.Timeout | undefined;
61
+
62
+ const dummyWatchdog = setTimeout(() => {}, 1);
63
+ clearTimeout(dummyWatchdog);
64
+
65
+ /**
66
+ * Starts a watchdog that aborts a request if no first stream event arrives in time.
67
+ * Call `markFirstEventReceived()` as soon as the first event is observed.
68
+ */
69
+ export function createWatchdog(timeoutMs: number | undefined, onTimeout: () => void): Watchdog {
70
+ if (timeoutMs !== undefined && timeoutMs > 0) {
71
+ return setTimeout(onTimeout, timeoutMs);
72
+ }
73
+ return undefined;
74
+ }
75
+
76
+ export interface IdleTimeoutIteratorOptions {
77
+ watchdog?: Watchdog;
78
+ idleTimeoutMs?: number;
79
+ firstItemTimeoutMs?: number;
80
+ errorMessage: string;
81
+ firstItemErrorMessage?: string;
82
+ onIdle?: () => void;
83
+ onFirstItemTimeout?: () => void;
84
+ /**
85
+ * Optional semantic-progress predicate. Non-progress items are still yielded,
86
+ * but they do not reset the idle deadline. This prevents provider
87
+ * keepalive/no-op events from keeping a stalled tool call alive forever.
88
+ */
89
+ isProgressItem?: (item: unknown) => boolean;
90
+ /**
91
+ * Cancel iteration as soon as this signal aborts. Required for caller-driven
92
+ * cancellation (ESC) when the underlying transport does not surface signal
93
+ * aborts to the iterator (HTTP/2 proxies, native sockets, mocked fetch).
94
+ * Without this, the consumer sleeps on iterator.next() until the idle/first
95
+ * -event watchdog fires — observable as the issue #912 "Working… forever"
96
+ * symptom on the github-copilot provider.
97
+ */
98
+ abortSignal?: AbortSignal;
99
+ }
100
+
101
+ /**
102
+ * Yields items from an async iterable while enforcing a maximum idle gap between items.
103
+ *
104
+ * The first item may use a shorter timeout so stuck requests can be aborted and retried
105
+ * before any user-visible content has streamed.
106
+ */
107
+ export async function* iterateWithIdleTimeout<T>(
108
+ iterable: AsyncIterable<T>,
109
+ options: IdleTimeoutIteratorOptions,
110
+ ): AsyncGenerator<T> {
111
+ let watchdog = options.watchdog;
112
+ const firstItemTimeoutMs = options.firstItemTimeoutMs ?? options.idleTimeoutMs;
113
+ const abortSignal = options.abortSignal;
114
+ const iterator = iterable[Symbol.asyncIterator]();
115
+
116
+ const closeIterator = (): void => {
117
+ const returnPromise = iterator.return?.();
118
+ if (returnPromise) {
119
+ void returnPromise.catch(() => {});
120
+ }
121
+ };
122
+
123
+ if (abortSignal?.aborted) {
124
+ closeIterator();
125
+ throw abortReason(abortSignal);
126
+ }
127
+
128
+ const withRacy = <T>(promise: Promise<T>) =>
129
+ promise.then(
130
+ result => ({ kind: "next" as const, result }),
131
+ error => ({ kind: "error" as const, error }),
132
+ );
133
+
134
+ let awaitingFirstItem = true;
135
+ const markFirstItemReceived = () => {
136
+ watchdog && clearTimeout(watchdog);
137
+ watchdog = undefined;
138
+ awaitingFirstItem = false;
139
+ };
140
+ const isProgressItem = (item: T): boolean => {
141
+ if (!options.isProgressItem) return true;
142
+ try {
143
+ return options.isProgressItem(item);
144
+ } catch {
145
+ return true;
146
+ }
147
+ };
148
+ let lastProgressAt = Date.now();
149
+
150
+ const noTimeoutEnforced =
151
+ (firstItemTimeoutMs === undefined || firstItemTimeoutMs <= 0) &&
152
+ (options.idleTimeoutMs === undefined || options.idleTimeoutMs <= 0);
153
+
154
+ while (true) {
155
+ let activeTimeoutMs: number | undefined;
156
+ if (awaitingFirstItem) {
157
+ activeTimeoutMs = firstItemTimeoutMs;
158
+ } else if (options.idleTimeoutMs !== undefined && options.idleTimeoutMs > 0) {
159
+ activeTimeoutMs = options.idleTimeoutMs - (Date.now() - lastProgressAt);
160
+ if (activeTimeoutMs <= 0) {
161
+ options.onIdle?.();
162
+ closeIterator();
163
+ throw new Error(options.errorMessage);
164
+ }
165
+ }
166
+
167
+ const nextResultPromise = withRacy(iterator.next());
168
+
169
+ const racers: Array<
170
+ Promise<
171
+ | { kind: "next"; result: IteratorResult<T> }
172
+ | { kind: "error"; error: unknown }
173
+ | { kind: "timeout" }
174
+ | { kind: "abort" }
175
+ >
176
+ > = [nextResultPromise];
177
+
178
+ let timer: NodeJS.Timeout | undefined;
179
+ let resolveTimeout: ((value: { kind: "timeout" }) => void) | undefined;
180
+ const enforceTimeout = !noTimeoutEnforced && activeTimeoutMs !== undefined && activeTimeoutMs > 0;
181
+ if (enforceTimeout) {
182
+ const { promise, resolve } = Promise.withResolvers<{ kind: "timeout" }>();
183
+ resolveTimeout = resolve;
184
+ timer = setTimeout(() => resolve({ kind: "timeout" }), activeTimeoutMs);
185
+ racers.push(promise);
186
+ }
187
+
188
+ let abortListener: (() => void) | undefined;
189
+ let resolveAbort: ((value: { kind: "abort" }) => void) | undefined;
190
+ if (abortSignal) {
191
+ const { promise, resolve } = Promise.withResolvers<{ kind: "abort" }>();
192
+ resolveAbort = resolve;
193
+ abortListener = () => resolve({ kind: "abort" });
194
+ abortSignal.addEventListener("abort", abortListener, { once: true });
195
+ racers.push(promise);
196
+ }
197
+
198
+ try {
199
+ const outcome = await Promise.race(racers);
200
+ if (outcome.kind === "abort") {
201
+ closeIterator();
202
+ throw abortReason(abortSignal!);
203
+ }
204
+ if (outcome.kind === "timeout") {
205
+ if (!awaitingFirstItem) {
206
+ options.onIdle?.();
207
+ } else {
208
+ options.onFirstItemTimeout?.();
209
+ }
210
+ closeIterator();
211
+ throw new Error(
212
+ !awaitingFirstItem ? options.errorMessage : (options.firstItemErrorMessage ?? options.errorMessage),
213
+ );
214
+ }
215
+ if (outcome.kind === "error") {
216
+ throw outcome.error;
217
+ }
218
+ if (outcome.result.done) {
219
+ markFirstItemReceived();
220
+ return;
221
+ }
222
+ const item = outcome.result.value;
223
+ // Non-progress items (e.g. provider keepalives, synthetic `start` events that
224
+ // arrive before the model has produced any tokens) MUST NOT flip us out of
225
+ // `awaitingFirstItem`. Otherwise the next iteration switches from the (longer)
226
+ // first-item watchdog to the (shorter) idle watchdog while we're still waiting
227
+ // on the model's first real output.
228
+ if (isProgressItem(item)) {
229
+ markFirstItemReceived();
230
+ lastProgressAt = Date.now();
231
+ }
232
+ yield item;
233
+ } finally {
234
+ if (timer !== undefined) clearTimeout(timer);
235
+ // Resolve dangling promises so the racers don't leak (Promise.race is one-shot).
236
+ resolveTimeout?.({ kind: "timeout" });
237
+ if (abortListener && abortSignal) {
238
+ abortSignal.removeEventListener("abort", abortListener);
239
+ }
240
+ resolveAbort?.({ kind: "abort" });
241
+ }
242
+ }
243
+ }
244
+
245
+ function abortReason(signal: AbortSignal): Error {
246
+ const reason = signal.reason;
247
+ if (reason instanceof Error) return reason;
248
+ if (typeof reason === "string") return new Error(reason);
249
+ return new Error("Request was aborted");
250
+ }
@@ -0,0 +1,148 @@
1
+ import { parse as partialParse } from "partial-json";
2
+
3
+ const QUOTE = 0x22;
4
+ const BACKSLASH = 0x5c;
5
+ const U = 0x75;
6
+
7
+ // Valid chars after `\`: " \ / b f n r t u
8
+ const VALID_ESCAPE_CHAR = new Uint8Array(128);
9
+ for (const ch of '"\\/bfnrtu') VALID_ESCAPE_CHAR[ch.charCodeAt(0)] = 1;
10
+
11
+ const CONTROL_ESCAPES: readonly string[] = (() => {
12
+ const e: string[] = [];
13
+ e[0x08] = "\\b";
14
+ e[0x09] = "\\t";
15
+ e[0x0a] = "\\n";
16
+ e[0x0c] = "\\f";
17
+ e[0x0d] = "\\r";
18
+ for (let cp = 0; cp <= 0x1f; cp++) {
19
+ e[cp] ??= `\\u${cp.toString(16).padStart(4, "0")}`;
20
+ }
21
+ return e;
22
+ })();
23
+
24
+ function isHexDigit(cp: number): boolean {
25
+ return (cp >= 0x30 && cp <= 0x39) || ((cp | 0x20) >= 0x61 && (cp | 0x20) <= 0x66);
26
+ }
27
+
28
+ export function repairJson(json: string): string {
29
+ const len = json.length;
30
+ const parts: string[] = [];
31
+ let lastEmit = 0;
32
+ let inString = false;
33
+ let i = 0;
34
+
35
+ while (i < len) {
36
+ if (!inString) {
37
+ // Fast scan: skip to next quote.
38
+ while (i < len && json.charCodeAt(i) !== QUOTE) i++;
39
+ if (i >= len) break;
40
+ inString = true;
41
+ i++;
42
+ continue;
43
+ }
44
+
45
+ // Fast scan inside string: advance past chars that need no handling.
46
+ while (i < len) {
47
+ const cp = json.charCodeAt(i);
48
+ if (cp < 0x20 || cp === QUOTE || cp === BACKSLASH) break;
49
+ i++;
50
+ }
51
+ if (i >= len) break;
52
+
53
+ const cp = json.charCodeAt(i);
54
+
55
+ if (cp === QUOTE) {
56
+ inString = false;
57
+ i++;
58
+ continue;
59
+ }
60
+
61
+ if (cp === BACKSLASH) {
62
+ // Need at least one char after the backslash; treat EOI as invalid escape.
63
+ if (i + 1 >= len) {
64
+ parts.push(json.slice(lastEmit, i), "\\\\");
65
+ lastEmit = i + 1;
66
+ i++;
67
+ continue;
68
+ }
69
+
70
+ const nextCp = json.charCodeAt(i + 1);
71
+
72
+ if (nextCp === U) {
73
+ // Need full \uXXXX, all four digits, all hex.
74
+ if (
75
+ i + 5 < len &&
76
+ isHexDigit(json.charCodeAt(i + 2)) &&
77
+ isHexDigit(json.charCodeAt(i + 3)) &&
78
+ isHexDigit(json.charCodeAt(i + 4)) &&
79
+ isHexDigit(json.charCodeAt(i + 5))
80
+ ) {
81
+ i += 6;
82
+ continue;
83
+ }
84
+ // Truncated or non-hex \u — escape the backslash, re-process the rest.
85
+ parts.push(json.slice(lastEmit, i), "\\\\");
86
+ lastEmit = i + 1;
87
+ i++;
88
+ continue;
89
+ }
90
+
91
+ if (nextCp < 128 && VALID_ESCAPE_CHAR[nextCp] === 1) {
92
+ i += 2;
93
+ continue;
94
+ }
95
+
96
+ parts.push(json.slice(lastEmit, i), "\\\\");
97
+ lastEmit = i + 1;
98
+ i++;
99
+ continue;
100
+ }
101
+
102
+ // Control character (cp < 0x20).
103
+ parts.push(json.slice(lastEmit, i), CONTROL_ESCAPES[cp]);
104
+ lastEmit = i + 1;
105
+ i++;
106
+ }
107
+
108
+ if (!parts.length) return json;
109
+ if (lastEmit < len) parts.push(json.slice(lastEmit));
110
+ return parts.join("");
111
+ }
112
+
113
+ export function parseJsonWithRepair<T>(json: string): T {
114
+ try {
115
+ return JSON.parse(json) as T;
116
+ } catch (error) {
117
+ const repairedJson = repairJson(json);
118
+ if (repairedJson !== json) {
119
+ return JSON.parse(repairedJson) as T;
120
+ }
121
+ throw error;
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Attempts to parse potentially incomplete JSON during streaming.
127
+ * Always returns a valid object, even if the JSON is incomplete.
128
+ *
129
+ * @param partialJson The partial JSON string from streaming
130
+ * @returns Parsed object or empty object if parsing fails
131
+ */
132
+ export function parseStreamingJson<T = Record<string, unknown>>(partialJson: string | undefined): T {
133
+ partialJson = partialJson?.trimStart();
134
+ if (!partialJson) {
135
+ return {} as T;
136
+ }
137
+ try {
138
+ return JSON.parse(partialJson) as T;
139
+ } catch {
140
+ partialJson = repairJson(partialJson);
141
+ try {
142
+ return (partialParse(partialJson) ?? {}) as T;
143
+ } catch {
144
+ // If all parsing fails, return empty object
145
+ return {} as T;
146
+ }
147
+ }
148
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Alibaba Coding Plan login flow.
3
+ *
4
+ * Alibaba Coding Plan provides OpenAI-compatible models via https://coding-intl.dashscope.aliyuncs.com/v1.
5
+ *
6
+ * This is not OAuth - it's a simple API key flow:
7
+ * 1. Open browser to Alibaba Cloud DashScope API key settings
8
+ * 2. User copies their API key
9
+ * 3. User pastes the API key into the CLI
10
+ */
11
+
12
+ import { validateOpenAICompatibleApiKey } from "./api-key-validation";
13
+ import type { OAuthController } from "./types";
14
+
15
+ const AUTH_URL = "https://modelstudio.console.alibabacloud.com/";
16
+ const API_BASE_URL = "https://coding-intl.dashscope.aliyuncs.com/v1";
17
+ const VALIDATION_MODEL = "qwen3.5-plus";
18
+
19
+ /**
20
+ * Login to Alibaba Coding Plan.
21
+ *
22
+ * Opens browser to API keys page, prompts user to paste their API key.
23
+ * Returns the API key directly (not OAuthCredentials - this isn't OAuth).
24
+ */
25
+ export async function loginAlibabaCodingPlan(options: OAuthController): Promise<string> {
26
+ if (!options.onPrompt) {
27
+ throw new Error("Alibaba Coding Plan login requires onPrompt callback");
28
+ }
29
+
30
+ options.onAuth?.({
31
+ url: AUTH_URL,
32
+ instructions: "Copy your API key from the Alibaba Cloud DashScope console",
33
+ });
34
+
35
+ const apiKey = await options.onPrompt({
36
+ message: "Paste your Alibaba Coding Plan API key",
37
+ placeholder: "sk-...",
38
+ });
39
+
40
+ if (options.signal?.aborted) {
41
+ throw new Error("Login cancelled");
42
+ }
43
+
44
+ const trimmed = apiKey.trim();
45
+ if (!trimmed) {
46
+ throw new Error("API key is required");
47
+ }
48
+
49
+ options.onProgress?.("Validating API key...");
50
+ await validateOpenAICompatibleApiKey({
51
+ provider: "Alibaba Coding Plan",
52
+ apiKey: trimmed,
53
+ baseUrl: API_BASE_URL,
54
+ model: VALIDATION_MODEL,
55
+ signal: options.signal,
56
+ });
57
+
58
+ return trimmed;
59
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Anthropic OAuth flow (Anthropic model Pro/Max)
3
+ */
4
+ import { OAuthCallbackFlow } from "./callback-server";
5
+ import { generatePKCE } from "./pkce";
6
+ import type { OAuthController, OAuthCredentials } from "./types";
7
+
8
+ const decode = (s: string) => atob(s);
9
+ const CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
10
+ const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
11
+ const TOKEN_URL = "https://api.anthropic.com/v1/oauth/token";
12
+ const CALLBACK_PORT = 54545;
13
+ const CALLBACK_PATH = "/callback";
14
+ const SCOPES = "org:create_api_key user:profile user:inference";
15
+
16
+ function formatErrorDetails(error: unknown): string {
17
+ if (error instanceof Error) {
18
+ const details: string[] = [`${error.name}: ${error.message}`];
19
+ const errorWithCode = error as Error & { code?: string; errno?: number | string; cause?: unknown };
20
+ if (errorWithCode.code) details.push(`code=${errorWithCode.code}`);
21
+ if (typeof errorWithCode.errno !== "undefined") details.push(`errno=${String(errorWithCode.errno)}`);
22
+ if (typeof error.cause !== "undefined") {
23
+ details.push(`cause=${formatErrorDetails(error.cause)}`);
24
+ }
25
+ if (error.stack) {
26
+ details.push(`stack=${error.stack}`);
27
+ }
28
+ return details.join("; ");
29
+ }
30
+ return String(error);
31
+ }
32
+
33
+ async function postJson(url: string, body: Record<string, string | number>): Promise<string> {
34
+ const response = await fetch(url, {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ Accept: "application/json",
39
+ },
40
+ body: JSON.stringify(body),
41
+ signal: AbortSignal.timeout(30_000),
42
+ });
43
+
44
+ const responseBody = await response.text();
45
+ if (!response.ok) {
46
+ throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);
47
+ }
48
+ return responseBody;
49
+ }
50
+
51
+ /**
52
+ * Decoded shape of Anthropic's `/v1/oauth/token` response (both
53
+ * `authorization_code` exchange and `refresh_token` refresh return the same
54
+ * envelope). The `account` block is inlined alongside the tokens, so we can
55
+ * surface `accountId` / `email` on {@link OAuthCredentials} without a separate
56
+ * `/api/oauth/profile` round-trip.
57
+ */
58
+ interface AnthropicTokenResponse {
59
+ access_token: string;
60
+ refresh_token: string;
61
+ expires_in: number;
62
+ account?: { uuid?: string; email_address?: string };
63
+ }
64
+
65
+ function parseOAuthTokenResponse(responseBody: string, operation: string): AnthropicTokenResponse {
66
+ try {
67
+ return JSON.parse(responseBody) as AnthropicTokenResponse;
68
+ } catch (error) {
69
+ throw new Error(
70
+ `Anthropic ${operation} returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,
71
+ );
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Lift the OAuth response's `account: { uuid, email_address }` block onto
77
+ * {@link OAuthCredentials} so downstream identity propagation (e.g.
78
+ * `metadata.user_id.account_uuid`, usage tracking) works without a separate
79
+ * `/api/oauth/profile` round-trip. Returns `undefined` for either field when
80
+ * the response omits it or carries a non-string / empty value.
81
+ */
82
+ function extractAccountFromTokenResponse(data: AnthropicTokenResponse): {
83
+ accountId?: string;
84
+ email?: string;
85
+ } {
86
+ const accountUuid = data.account?.uuid;
87
+ const emailAddress = data.account?.email_address;
88
+ return {
89
+ accountId: typeof accountUuid === "string" && accountUuid.length > 0 ? accountUuid : undefined,
90
+ email: typeof emailAddress === "string" && emailAddress.length > 0 ? emailAddress : undefined,
91
+ };
92
+ }
93
+
94
+ export class AnthropicOAuthFlow extends OAuthCallbackFlow {
95
+ #verifier: string = "";
96
+ #challenge: string = "";
97
+
98
+ constructor(ctrl: OAuthController) {
99
+ super(ctrl, CALLBACK_PORT, CALLBACK_PATH);
100
+ }
101
+
102
+ async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
103
+ const pkce = await generatePKCE();
104
+ this.#verifier = pkce.verifier;
105
+ this.#challenge = pkce.challenge;
106
+
107
+ const authParams = new URLSearchParams({
108
+ code: "true",
109
+ client_id: CLIENT_ID,
110
+ response_type: "code",
111
+ redirect_uri: redirectUri,
112
+ scope: SCOPES,
113
+ code_challenge: this.#challenge,
114
+ code_challenge_method: "S256",
115
+ state,
116
+ });
117
+ const url = `${AUTHORIZE_URL}?${authParams.toString()}`;
118
+
119
+ return {
120
+ url,
121
+ instructions:
122
+ "Complete login in your browser. If the browser cannot reach this machine, paste the final redirect URL or authorization code when prompted.",
123
+ };
124
+ }
125
+
126
+ async exchangeToken(code: string, state: string, redirectUri: string): Promise<OAuthCredentials> {
127
+ let exchangeCode = code;
128
+ let exchangeState = state;
129
+ const codeFragmentIndex = code.indexOf("#");
130
+ if (codeFragmentIndex >= 0) {
131
+ exchangeCode = code.slice(0, codeFragmentIndex);
132
+ const codeFragmentState = code.slice(codeFragmentIndex + 1);
133
+ if (codeFragmentState.length > 0) {
134
+ exchangeState = codeFragmentState;
135
+ }
136
+ }
137
+
138
+ let responseBody: string;
139
+ try {
140
+ responseBody = await postJson(TOKEN_URL, {
141
+ grant_type: "authorization_code",
142
+ client_id: CLIENT_ID,
143
+ code: exchangeCode,
144
+ state: exchangeState,
145
+ redirect_uri: redirectUri,
146
+ code_verifier: this.#verifier,
147
+ });
148
+ } catch (error) {
149
+ throw new Error(
150
+ `Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`,
151
+ );
152
+ }
153
+
154
+ const tokenData = parseOAuthTokenResponse(responseBody, "token exchange");
155
+ const { accountId, email } = extractAccountFromTokenResponse(tokenData);
156
+
157
+ return {
158
+ refresh: tokenData.refresh_token,
159
+ access: tokenData.access_token,
160
+ expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,
161
+ accountId,
162
+ email,
163
+ };
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Login with Anthropic OAuth
169
+ */
170
+ export async function loginAnthropic(ctrl: OAuthController): Promise<OAuthCredentials> {
171
+ const flow = new AnthropicOAuthFlow(ctrl);
172
+ return flow.login();
173
+ }
174
+
175
+ /**
176
+ * Refresh Anthropic OAuth token
177
+ */
178
+ export async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {
179
+ let responseBody: string;
180
+ try {
181
+ responseBody = await postJson(TOKEN_URL, {
182
+ grant_type: "refresh_token",
183
+ client_id: CLIENT_ID,
184
+ refresh_token: refreshToken,
185
+ });
186
+ } catch (error) {
187
+ throw new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`);
188
+ }
189
+
190
+ const data = parseOAuthTokenResponse(responseBody, "token refresh");
191
+ const { accountId, email } = extractAccountFromTokenResponse(data);
192
+
193
+ return {
194
+ refresh: data.refresh_token || refreshToken,
195
+ access: data.access_token,
196
+ expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
197
+ accountId,
198
+ email,
199
+ };
200
+ }