@earendil-works/pi-ai 0.87.1 → 0.99.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 (605) hide show
  1. package/README.md +207 -27
  2. package/dist/api/anthropic-messages.d.ts.map +1 -1
  3. package/dist/api/anthropic-messages.js +6 -0
  4. package/dist/api/anthropic-messages.js.map +1 -1
  5. package/dist/api/anthropic-messages.lazy.d.ts.map +1 -1
  6. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  7. package/dist/api/azure-openai-responses.js +6 -5
  8. package/dist/api/azure-openai-responses.js.map +1 -1
  9. package/dist/api/azure-openai-responses.lazy.d.ts.map +1 -1
  10. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  11. package/dist/api/bedrock-converse-stream.js +1 -0
  12. package/dist/api/bedrock-converse-stream.js.map +1 -1
  13. package/dist/api/bedrock-converse-stream.lazy.d.ts.map +1 -1
  14. package/dist/api/bedrock-converse-stream.lazy.js.map +1 -1
  15. package/dist/api/cloudflare-ai-binding.d.ts.map +1 -1
  16. package/dist/api/cloudflare-ai-binding.js.map +1 -1
  17. package/dist/api/cloudflare-workers-ai-system-one.d.ts +4 -0
  18. package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -0
  19. package/dist/api/cloudflare-workers-ai-system-one.js +43 -0
  20. package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -0
  21. package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts +3 -0
  22. package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts.map +1 -0
  23. package/dist/api/cloudflare-workers-ai-system-one.lazy.js +4 -0
  24. package/dist/api/cloudflare-workers-ai-system-one.lazy.js.map +1 -0
  25. package/dist/api/cloudflare.d.ts +2 -0
  26. package/dist/api/cloudflare.d.ts.map +1 -1
  27. package/dist/api/cloudflare.js +2 -0
  28. package/dist/api/cloudflare.js.map +1 -1
  29. package/dist/api/constrained-sampling.d.ts.map +1 -1
  30. package/dist/api/constrained-sampling.js.map +1 -1
  31. package/dist/api/github-copilot-headers.d.ts.map +1 -1
  32. package/dist/api/github-copilot-headers.js.map +1 -1
  33. package/dist/api/google-generative-ai.d.ts.map +1 -1
  34. package/dist/api/google-generative-ai.js +1 -0
  35. package/dist/api/google-generative-ai.js.map +1 -1
  36. package/dist/api/google-generative-ai.lazy.d.ts.map +1 -1
  37. package/dist/api/google-shared.d.ts.map +1 -1
  38. package/dist/api/google-shared.js.map +1 -1
  39. package/dist/api/google-vertex.d.ts.map +1 -1
  40. package/dist/api/google-vertex.js +1 -0
  41. package/dist/api/google-vertex.js.map +1 -1
  42. package/dist/api/google-vertex.lazy.d.ts.map +1 -1
  43. package/dist/api/lazy.d.ts.map +1 -1
  44. package/dist/api/lazy.js.map +1 -1
  45. package/dist/api/llama-cpp-classify.d.ts +33 -0
  46. package/dist/api/llama-cpp-classify.d.ts.map +1 -0
  47. package/dist/api/llama-cpp-classify.js +365 -0
  48. package/dist/api/llama-cpp-classify.js.map +1 -0
  49. package/dist/api/llama-cpp-classify.lazy.d.ts +3 -0
  50. package/dist/api/llama-cpp-classify.lazy.d.ts.map +1 -0
  51. package/dist/api/llama-cpp-classify.lazy.js +4 -0
  52. package/dist/api/llama-cpp-classify.lazy.js.map +1 -0
  53. package/dist/api/mistral-conversations.d.ts +1 -1
  54. package/dist/api/mistral-conversations.d.ts.map +1 -1
  55. package/dist/api/mistral-conversations.js +18 -17
  56. package/dist/api/mistral-conversations.js.map +1 -1
  57. package/dist/api/mistral-conversations.lazy.d.ts.map +1 -1
  58. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  59. package/dist/api/openai-codex-responses.js +20 -4
  60. package/dist/api/openai-codex-responses.js.map +1 -1
  61. package/dist/api/openai-codex-responses.lazy.d.ts.map +1 -1
  62. package/dist/api/openai-completions.d.ts.map +1 -1
  63. package/dist/api/openai-completions.js +4 -4
  64. package/dist/api/openai-completions.js.map +1 -1
  65. package/dist/api/openai-completions.lazy.d.ts.map +1 -1
  66. package/dist/api/openai-prompt-cache.d.ts.map +1 -1
  67. package/dist/api/openai-prompt-cache.js.map +1 -1
  68. package/dist/api/openai-responses-shared.d.ts +2 -1
  69. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  70. package/dist/api/openai-responses-shared.js +14 -0
  71. package/dist/api/openai-responses-shared.js.map +1 -1
  72. package/dist/api/openai-responses.d.ts.map +1 -1
  73. package/dist/api/openai-responses.js +26 -9
  74. package/dist/api/openai-responses.js.map +1 -1
  75. package/dist/api/openai-responses.lazy.d.ts.map +1 -1
  76. package/dist/api/openrouter-images.d.ts +2 -1
  77. package/dist/api/openrouter-images.d.ts.map +1 -1
  78. package/dist/api/openrouter-images.js +1 -0
  79. package/dist/api/openrouter-images.js.map +1 -1
  80. package/dist/api/openrouter-images.lazy.d.ts.map +1 -1
  81. package/dist/api/openrouter-images.lazy.js.map +1 -1
  82. package/dist/api/pi-messages.d.ts.map +1 -1
  83. package/dist/api/pi-messages.js +1 -0
  84. package/dist/api/pi-messages.js.map +1 -1
  85. package/dist/api/pi-messages.lazy.d.ts.map +1 -1
  86. package/dist/api/simple-options.d.ts.map +1 -1
  87. package/dist/api/simple-options.js +2 -4
  88. package/dist/api/simple-options.js.map +1 -1
  89. package/dist/api/system-one-shared.d.ts +23 -0
  90. package/dist/api/system-one-shared.d.ts.map +1 -0
  91. package/dist/api/system-one-shared.js +183 -0
  92. package/dist/api/system-one-shared.js.map +1 -0
  93. package/dist/api/transform-messages.d.ts.map +1 -1
  94. package/dist/api/transform-messages.js.map +1 -1
  95. package/dist/api/typesafe-system-one.d.ts +4 -0
  96. package/dist/api/typesafe-system-one.d.ts.map +1 -0
  97. package/dist/api/typesafe-system-one.js +19 -0
  98. package/dist/api/typesafe-system-one.js.map +1 -0
  99. package/dist/api/typesafe-system-one.lazy.d.ts +3 -0
  100. package/dist/api/typesafe-system-one.lazy.d.ts.map +1 -0
  101. package/dist/api/typesafe-system-one.lazy.js +4 -0
  102. package/dist/api/typesafe-system-one.lazy.js.map +1 -0
  103. package/dist/auth/context.d.ts.map +1 -1
  104. package/dist/auth/context.js.map +1 -1
  105. package/dist/auth/credential-store.d.ts.map +1 -1
  106. package/dist/auth/credential-store.js.map +1 -1
  107. package/dist/auth/helpers.d.ts.map +1 -1
  108. package/dist/auth/helpers.js +1 -1
  109. package/dist/auth/helpers.js.map +1 -1
  110. package/dist/auth/oauth/anthropic.d.ts.map +1 -1
  111. package/dist/auth/oauth/anthropic.js +19 -128
  112. package/dist/auth/oauth/anthropic.js.map +1 -1
  113. package/dist/auth/oauth/callback-server.d.ts +55 -0
  114. package/dist/auth/oauth/callback-server.d.ts.map +1 -0
  115. package/dist/auth/oauth/callback-server.js +146 -0
  116. package/dist/auth/oauth/callback-server.js.map +1 -0
  117. package/dist/auth/oauth/device-code.d.ts.map +1 -1
  118. package/dist/auth/oauth/device-code.js.map +1 -1
  119. package/dist/auth/oauth/github-copilot.d.ts.map +1 -1
  120. package/dist/auth/oauth/github-copilot.js.map +1 -1
  121. package/dist/auth/oauth/kimi-coding.d.ts.map +1 -1
  122. package/dist/auth/oauth/kimi-coding.js.map +1 -1
  123. package/dist/auth/oauth/load.d.ts +2 -0
  124. package/dist/auth/oauth/load.d.ts.map +1 -1
  125. package/dist/auth/oauth/load.js +5 -0
  126. package/dist/auth/oauth/load.js.map +1 -1
  127. package/dist/auth/oauth/meta.d.ts.map +1 -1
  128. package/dist/auth/oauth/meta.js.map +1 -1
  129. package/dist/auth/oauth/openai-chatgpt.d.ts +9 -0
  130. package/dist/auth/oauth/openai-chatgpt.d.ts.map +1 -0
  131. package/dist/auth/oauth/openai-chatgpt.js +266 -0
  132. package/dist/auth/oauth/openai-chatgpt.js.map +1 -0
  133. package/dist/auth/oauth/openai-codex.d.ts +1 -1
  134. package/dist/auth/oauth/openai-codex.d.ts.map +1 -1
  135. package/dist/auth/oauth/openai-codex.js +20 -124
  136. package/dist/auth/oauth/openai-codex.js.map +1 -1
  137. package/dist/auth/oauth/openrouter.d.ts +1 -1
  138. package/dist/auth/oauth/openrouter.d.ts.map +1 -1
  139. package/dist/auth/oauth/openrouter.js +19 -138
  140. package/dist/auth/oauth/openrouter.js.map +1 -1
  141. package/dist/auth/oauth/pkce.d.ts.map +1 -1
  142. package/dist/auth/oauth/pkce.js.map +1 -1
  143. package/dist/auth/oauth/radius.d.ts +1 -1
  144. package/dist/auth/oauth/radius.d.ts.map +1 -1
  145. package/dist/auth/oauth/radius.js +21 -89
  146. package/dist/auth/oauth/radius.js.map +1 -1
  147. package/dist/auth/oauth/xai.d.ts.map +1 -1
  148. package/dist/auth/oauth/xai.js.map +1 -1
  149. package/dist/auth/resolve.d.ts +2 -8
  150. package/dist/auth/resolve.d.ts.map +1 -1
  151. package/dist/auth/resolve.js +3 -19
  152. package/dist/auth/resolve.js.map +1 -1
  153. package/dist/auth/types.d.ts +10 -1
  154. package/dist/auth/types.d.ts.map +1 -1
  155. package/dist/auth/types.js.map +1 -1
  156. package/dist/bedrock-provider.d.ts.map +1 -1
  157. package/dist/bun-oauth.d.ts.map +1 -1
  158. package/dist/bun-oauth.js +2 -0
  159. package/dist/bun-oauth.js.map +1 -1
  160. package/dist/cli.d.ts.map +1 -1
  161. package/dist/cli.js +3 -1
  162. package/dist/cli.js.map +1 -1
  163. package/dist/compat/extension-oauth-types.d.ts.map +1 -1
  164. package/dist/compat.d.ts.map +1 -1
  165. package/dist/compat.js.map +1 -1
  166. package/dist/env-api-keys.d.ts.map +1 -1
  167. package/dist/env-api-keys.js +1 -0
  168. package/dist/env-api-keys.js.map +1 -1
  169. package/dist/image-models.d.ts +19 -8
  170. package/dist/image-models.d.ts.map +1 -1
  171. package/dist/image-models.js +14 -13
  172. package/dist/image-models.js.map +1 -1
  173. package/dist/images-api-registry.d.ts +7 -7
  174. package/dist/images-api-registry.d.ts.map +1 -1
  175. package/dist/images-api-registry.js.map +1 -1
  176. package/dist/images.d.ts +7 -2
  177. package/dist/images.d.ts.map +1 -1
  178. package/dist/images.js +5 -0
  179. package/dist/images.js.map +1 -1
  180. package/dist/index.d.ts +0 -1
  181. package/dist/index.d.ts.map +1 -1
  182. package/dist/index.js +0 -1
  183. package/dist/index.js.map +1 -1
  184. package/dist/legacy-api-aliases.d.ts.map +1 -1
  185. package/dist/model-catalog.d.ts +25 -9
  186. package/dist/model-catalog.d.ts.map +1 -1
  187. package/dist/model-catalog.js +14 -2
  188. package/dist/model-catalog.js.map +1 -1
  189. package/dist/models-store.d.ts +3 -2
  190. package/dist/models-store.d.ts.map +1 -1
  191. package/dist/models-store.js.map +1 -1
  192. package/dist/models.d.ts +97 -30
  193. package/dist/models.d.ts.map +1 -1
  194. package/dist/models.generated.d.ts +131 -41
  195. package/dist/models.generated.d.ts.map +1 -1
  196. package/dist/models.generated.js +131 -41
  197. package/dist/models.generated.js.map +1 -1
  198. package/dist/models.js +157 -30
  199. package/dist/models.js.map +1 -1
  200. package/dist/oauth.d.ts.map +1 -1
  201. package/dist/providers/all.d.ts +19 -13
  202. package/dist/providers/all.d.ts.map +1 -1
  203. package/dist/providers/all.js +25 -21
  204. package/dist/providers/all.js.map +1 -1
  205. package/dist/providers/amazon-bedrock.d.ts.map +1 -1
  206. package/dist/providers/amazon-bedrock.js.map +1 -1
  207. package/dist/providers/amazon-bedrock.models.d.ts +4 -2
  208. package/dist/providers/amazon-bedrock.models.d.ts.map +1 -1
  209. package/dist/providers/amazon-bedrock.models.js +4 -2
  210. package/dist/providers/amazon-bedrock.models.js.map +1 -1
  211. package/dist/providers/ant-ling.d.ts.map +1 -1
  212. package/dist/providers/ant-ling.js.map +1 -1
  213. package/dist/providers/ant-ling.models.d.ts +4 -2
  214. package/dist/providers/ant-ling.models.d.ts.map +1 -1
  215. package/dist/providers/ant-ling.models.js +4 -2
  216. package/dist/providers/ant-ling.models.js.map +1 -1
  217. package/dist/providers/anthropic.d.ts.map +1 -1
  218. package/dist/providers/anthropic.js.map +1 -1
  219. package/dist/providers/anthropic.models.d.ts +4 -2
  220. package/dist/providers/anthropic.models.d.ts.map +1 -1
  221. package/dist/providers/anthropic.models.js +4 -2
  222. package/dist/providers/anthropic.models.js.map +1 -1
  223. package/dist/providers/azure-openai-responses.d.ts.map +1 -1
  224. package/dist/providers/azure-openai-responses.js.map +1 -1
  225. package/dist/providers/azure-openai-responses.models.d.ts +4 -2
  226. package/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
  227. package/dist/providers/azure-openai-responses.models.js +4 -2
  228. package/dist/providers/azure-openai-responses.models.js.map +1 -1
  229. package/dist/providers/baseten.d.ts.map +1 -1
  230. package/dist/providers/baseten.js.map +1 -1
  231. package/dist/providers/baseten.models.d.ts +4 -2
  232. package/dist/providers/baseten.models.d.ts.map +1 -1
  233. package/dist/providers/baseten.models.js +4 -2
  234. package/dist/providers/baseten.models.js.map +1 -1
  235. package/dist/providers/cerebras.d.ts.map +1 -1
  236. package/dist/providers/cerebras.js.map +1 -1
  237. package/dist/providers/cerebras.models.d.ts +4 -2
  238. package/dist/providers/cerebras.models.d.ts.map +1 -1
  239. package/dist/providers/cerebras.models.js +4 -2
  240. package/dist/providers/cerebras.models.js.map +1 -1
  241. package/dist/providers/cloudflare-ai-gateway.d.ts.map +1 -1
  242. package/dist/providers/cloudflare-ai-gateway.js.map +1 -1
  243. package/dist/providers/cloudflare-ai-gateway.models.d.ts +4 -2
  244. package/dist/providers/cloudflare-ai-gateway.models.d.ts.map +1 -1
  245. package/dist/providers/cloudflare-ai-gateway.models.js +4 -2
  246. package/dist/providers/cloudflare-ai-gateway.models.js.map +1 -1
  247. package/dist/providers/cloudflare-auth.d.ts.map +1 -1
  248. package/dist/providers/cloudflare-auth.js.map +1 -1
  249. package/dist/providers/cloudflare-stream.d.ts +6 -2
  250. package/dist/providers/cloudflare-stream.d.ts.map +1 -1
  251. package/dist/providers/cloudflare-stream.js +6 -0
  252. package/dist/providers/cloudflare-stream.js.map +1 -1
  253. package/dist/providers/cloudflare-workers-ai.d.ts.map +1 -1
  254. package/dist/providers/cloudflare-workers-ai.js +10 -3
  255. package/dist/providers/cloudflare-workers-ai.js.map +1 -1
  256. package/dist/providers/cloudflare-workers-ai.models.d.ts +4 -2
  257. package/dist/providers/cloudflare-workers-ai.models.d.ts.map +1 -1
  258. package/dist/providers/cloudflare-workers-ai.models.js +4 -2
  259. package/dist/providers/cloudflare-workers-ai.models.js.map +1 -1
  260. package/dist/providers/data/.manifest.json +1 -1
  261. package/dist/providers/data/amazon-bedrock.json +1 -1
  262. package/dist/providers/data/ant-ling.json +1 -1
  263. package/dist/providers/data/anthropic.json +1 -1
  264. package/dist/providers/data/azure-openai-responses.json +1 -1
  265. package/dist/providers/data/baseten.json +1 -1
  266. package/dist/providers/data/cerebras.json +1 -1
  267. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  268. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  269. package/dist/providers/data/deepseek.json +1 -1
  270. package/dist/providers/data/fireworks.json +1 -1
  271. package/dist/providers/data/github-copilot.json +1 -1
  272. package/dist/providers/data/google-vertex.json +1 -1
  273. package/dist/providers/data/google.json +1 -1
  274. package/dist/providers/data/groq.json +1 -1
  275. package/dist/providers/data/huggingface.json +1 -1
  276. package/dist/providers/data/kimi-coding.json +1 -1
  277. package/dist/providers/data/meta.json +1 -1
  278. package/dist/providers/data/minimax-cn.json +1 -1
  279. package/dist/providers/data/minimax.json +1 -1
  280. package/dist/providers/data/mistral.json +1 -1
  281. package/dist/providers/data/moonshotai-cn.json +1 -1
  282. package/dist/providers/data/moonshotai.json +1 -1
  283. package/dist/providers/data/nvidia.json +1 -1
  284. package/dist/providers/data/openai-codex.json +1 -1
  285. package/dist/providers/data/openai.json +1 -1
  286. package/dist/providers/data/opencode-go.json +1 -1
  287. package/dist/providers/data/opencode.json +1 -1
  288. package/dist/providers/data/openrouter.json +1 -1
  289. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  290. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  291. package/dist/providers/data/qwen-token-plan.json +1 -1
  292. package/dist/providers/data/radius.json +1 -1
  293. package/dist/providers/data/together.json +1 -1
  294. package/dist/providers/data/typesafe.json +1 -0
  295. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  296. package/dist/providers/data/xai.json +1 -1
  297. package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  298. package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  299. package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  300. package/dist/providers/data/xiaomi.json +1 -1
  301. package/dist/providers/data/zai-coding-cn.json +1 -1
  302. package/dist/providers/data/zai.json +1 -1
  303. package/dist/providers/deepseek.d.ts.map +1 -1
  304. package/dist/providers/deepseek.js.map +1 -1
  305. package/dist/providers/deepseek.models.d.ts +4 -2
  306. package/dist/providers/deepseek.models.d.ts.map +1 -1
  307. package/dist/providers/deepseek.models.js +4 -2
  308. package/dist/providers/deepseek.models.js.map +1 -1
  309. package/dist/providers/faux.d.ts +2 -2
  310. package/dist/providers/faux.d.ts.map +1 -1
  311. package/dist/providers/faux.js.map +1 -1
  312. package/dist/providers/fireworks.d.ts.map +1 -1
  313. package/dist/providers/fireworks.js.map +1 -1
  314. package/dist/providers/fireworks.models.d.ts +4 -2
  315. package/dist/providers/fireworks.models.d.ts.map +1 -1
  316. package/dist/providers/fireworks.models.js +4 -2
  317. package/dist/providers/fireworks.models.js.map +1 -1
  318. package/dist/providers/github-copilot.d.ts.map +1 -1
  319. package/dist/providers/github-copilot.js.map +1 -1
  320. package/dist/providers/github-copilot.models.d.ts +4 -2
  321. package/dist/providers/github-copilot.models.d.ts.map +1 -1
  322. package/dist/providers/github-copilot.models.js +4 -2
  323. package/dist/providers/github-copilot.models.js.map +1 -1
  324. package/dist/providers/google-vertex.d.ts.map +1 -1
  325. package/dist/providers/google-vertex.js.map +1 -1
  326. package/dist/providers/google-vertex.models.d.ts +4 -2
  327. package/dist/providers/google-vertex.models.d.ts.map +1 -1
  328. package/dist/providers/google-vertex.models.js +4 -2
  329. package/dist/providers/google-vertex.models.js.map +1 -1
  330. package/dist/providers/google.d.ts.map +1 -1
  331. package/dist/providers/google.js.map +1 -1
  332. package/dist/providers/google.models.d.ts +4 -2
  333. package/dist/providers/google.models.d.ts.map +1 -1
  334. package/dist/providers/google.models.js +4 -2
  335. package/dist/providers/google.models.js.map +1 -1
  336. package/dist/providers/groq.d.ts.map +1 -1
  337. package/dist/providers/groq.js.map +1 -1
  338. package/dist/providers/groq.models.d.ts +4 -2
  339. package/dist/providers/groq.models.d.ts.map +1 -1
  340. package/dist/providers/groq.models.js +4 -2
  341. package/dist/providers/groq.models.js.map +1 -1
  342. package/dist/providers/huggingface.d.ts.map +1 -1
  343. package/dist/providers/huggingface.js.map +1 -1
  344. package/dist/providers/huggingface.models.d.ts +4 -2
  345. package/dist/providers/huggingface.models.d.ts.map +1 -1
  346. package/dist/providers/huggingface.models.js +4 -2
  347. package/dist/providers/huggingface.models.js.map +1 -1
  348. package/dist/providers/images/register-builtins.d.ts +1 -1
  349. package/dist/providers/images/register-builtins.d.ts.map +1 -1
  350. package/dist/providers/images/register-builtins.js.map +1 -1
  351. package/dist/providers/kimi-coding.d.ts.map +1 -1
  352. package/dist/providers/kimi-coding.js.map +1 -1
  353. package/dist/providers/kimi-coding.models.d.ts +4 -2
  354. package/dist/providers/kimi-coding.models.d.ts.map +1 -1
  355. package/dist/providers/kimi-coding.models.js +4 -2
  356. package/dist/providers/kimi-coding.models.js.map +1 -1
  357. package/dist/providers/meta.d.ts.map +1 -1
  358. package/dist/providers/meta.js.map +1 -1
  359. package/dist/providers/meta.models.d.ts +4 -2
  360. package/dist/providers/meta.models.d.ts.map +1 -1
  361. package/dist/providers/meta.models.js +4 -2
  362. package/dist/providers/meta.models.js.map +1 -1
  363. package/dist/providers/minimax-cn.d.ts.map +1 -1
  364. package/dist/providers/minimax-cn.js.map +1 -1
  365. package/dist/providers/minimax-cn.models.d.ts +4 -2
  366. package/dist/providers/minimax-cn.models.d.ts.map +1 -1
  367. package/dist/providers/minimax-cn.models.js +4 -2
  368. package/dist/providers/minimax-cn.models.js.map +1 -1
  369. package/dist/providers/minimax.d.ts.map +1 -1
  370. package/dist/providers/minimax.js.map +1 -1
  371. package/dist/providers/minimax.models.d.ts +4 -2
  372. package/dist/providers/minimax.models.d.ts.map +1 -1
  373. package/dist/providers/minimax.models.js +4 -2
  374. package/dist/providers/minimax.models.js.map +1 -1
  375. package/dist/providers/mistral.d.ts.map +1 -1
  376. package/dist/providers/mistral.js.map +1 -1
  377. package/dist/providers/mistral.models.d.ts +4 -2
  378. package/dist/providers/mistral.models.d.ts.map +1 -1
  379. package/dist/providers/mistral.models.js +4 -2
  380. package/dist/providers/mistral.models.js.map +1 -1
  381. package/dist/providers/moonshotai-cn.d.ts.map +1 -1
  382. package/dist/providers/moonshotai-cn.js.map +1 -1
  383. package/dist/providers/moonshotai-cn.models.d.ts +4 -2
  384. package/dist/providers/moonshotai-cn.models.d.ts.map +1 -1
  385. package/dist/providers/moonshotai-cn.models.js +4 -2
  386. package/dist/providers/moonshotai-cn.models.js.map +1 -1
  387. package/dist/providers/moonshotai.d.ts.map +1 -1
  388. package/dist/providers/moonshotai.js.map +1 -1
  389. package/dist/providers/moonshotai.models.d.ts +4 -2
  390. package/dist/providers/moonshotai.models.d.ts.map +1 -1
  391. package/dist/providers/moonshotai.models.js +4 -2
  392. package/dist/providers/moonshotai.models.js.map +1 -1
  393. package/dist/providers/nvidia.d.ts.map +1 -1
  394. package/dist/providers/nvidia.js.map +1 -1
  395. package/dist/providers/nvidia.models.d.ts +4 -2
  396. package/dist/providers/nvidia.models.d.ts.map +1 -1
  397. package/dist/providers/nvidia.models.js +4 -2
  398. package/dist/providers/nvidia.models.js.map +1 -1
  399. package/dist/providers/openai-codex.d.ts.map +1 -1
  400. package/dist/providers/openai-codex.js +1 -1
  401. package/dist/providers/openai-codex.js.map +1 -1
  402. package/dist/providers/openai-codex.models.d.ts +4 -2
  403. package/dist/providers/openai-codex.models.d.ts.map +1 -1
  404. package/dist/providers/openai-codex.models.js +4 -2
  405. package/dist/providers/openai-codex.models.js.map +1 -1
  406. package/dist/providers/openai.d.ts.map +1 -1
  407. package/dist/providers/openai.js +11 -2
  408. package/dist/providers/openai.js.map +1 -1
  409. package/dist/providers/openai.models.d.ts +4 -2
  410. package/dist/providers/openai.models.d.ts.map +1 -1
  411. package/dist/providers/openai.models.js +4 -2
  412. package/dist/providers/openai.models.js.map +1 -1
  413. package/dist/providers/opencode-go.d.ts.map +1 -1
  414. package/dist/providers/opencode-go.js.map +1 -1
  415. package/dist/providers/opencode-go.models.d.ts +4 -2
  416. package/dist/providers/opencode-go.models.d.ts.map +1 -1
  417. package/dist/providers/opencode-go.models.js +4 -2
  418. package/dist/providers/opencode-go.models.js.map +1 -1
  419. package/dist/providers/opencode-headers.d.ts.map +1 -1
  420. package/dist/providers/opencode-headers.js.map +1 -1
  421. package/dist/providers/opencode.d.ts +3 -1
  422. package/dist/providers/opencode.d.ts.map +1 -1
  423. package/dist/providers/opencode.js +5 -2
  424. package/dist/providers/opencode.js.map +1 -1
  425. package/dist/providers/opencode.models.d.ts +4 -2
  426. package/dist/providers/opencode.models.d.ts.map +1 -1
  427. package/dist/providers/opencode.models.js +4 -2
  428. package/dist/providers/opencode.models.js.map +1 -1
  429. package/dist/providers/openrouter.d.ts.map +1 -1
  430. package/dist/providers/openrouter.js +11 -2
  431. package/dist/providers/openrouter.js.map +1 -1
  432. package/dist/providers/openrouter.models.d.ts +4 -2
  433. package/dist/providers/openrouter.models.d.ts.map +1 -1
  434. package/dist/providers/openrouter.models.js +4 -2
  435. package/dist/providers/openrouter.models.js.map +1 -1
  436. package/dist/providers/qwen-token-plan-cn.d.ts.map +1 -1
  437. package/dist/providers/qwen-token-plan-cn.js.map +1 -1
  438. package/dist/providers/qwen-token-plan-cn.models.d.ts +4 -2
  439. package/dist/providers/qwen-token-plan-cn.models.d.ts.map +1 -1
  440. package/dist/providers/qwen-token-plan-cn.models.js +4 -2
  441. package/dist/providers/qwen-token-plan-cn.models.js.map +1 -1
  442. package/dist/providers/qwen-token-plan-individual.d.ts.map +1 -1
  443. package/dist/providers/qwen-token-plan-individual.js.map +1 -1
  444. package/dist/providers/qwen-token-plan-individual.models.d.ts +4 -2
  445. package/dist/providers/qwen-token-plan-individual.models.d.ts.map +1 -1
  446. package/dist/providers/qwen-token-plan-individual.models.js +4 -2
  447. package/dist/providers/qwen-token-plan-individual.models.js.map +1 -1
  448. package/dist/providers/qwen-token-plan.d.ts.map +1 -1
  449. package/dist/providers/qwen-token-plan.js.map +1 -1
  450. package/dist/providers/qwen-token-plan.models.d.ts +4 -2
  451. package/dist/providers/qwen-token-plan.models.d.ts.map +1 -1
  452. package/dist/providers/qwen-token-plan.models.js +4 -2
  453. package/dist/providers/qwen-token-plan.models.js.map +1 -1
  454. package/dist/providers/radius-config.d.ts.map +1 -1
  455. package/dist/providers/radius-config.js.map +1 -1
  456. package/dist/providers/radius.d.ts.map +1 -1
  457. package/dist/providers/radius.js.map +1 -1
  458. package/dist/providers/radius.models.d.ts +4 -2
  459. package/dist/providers/radius.models.d.ts.map +1 -1
  460. package/dist/providers/radius.models.js +4 -2
  461. package/dist/providers/radius.models.js.map +1 -1
  462. package/dist/providers/together.d.ts.map +1 -1
  463. package/dist/providers/together.js.map +1 -1
  464. package/dist/providers/together.models.d.ts +4 -2
  465. package/dist/providers/together.models.d.ts.map +1 -1
  466. package/dist/providers/together.models.js +4 -2
  467. package/dist/providers/together.models.js.map +1 -1
  468. package/dist/providers/typesafe.d.ts +3 -0
  469. package/dist/providers/typesafe.d.ts.map +1 -0
  470. package/dist/providers/typesafe.js +18 -0
  471. package/dist/providers/typesafe.js.map +1 -0
  472. package/dist/providers/typesafe.models.d.ts +6 -0
  473. package/dist/providers/typesafe.models.d.ts.map +1 -0
  474. package/dist/providers/typesafe.models.js +8 -0
  475. package/dist/providers/typesafe.models.js.map +1 -0
  476. package/dist/providers/vercel-ai-gateway.d.ts.map +1 -1
  477. package/dist/providers/vercel-ai-gateway.js +5 -2
  478. package/dist/providers/vercel-ai-gateway.js.map +1 -1
  479. package/dist/providers/vercel-ai-gateway.models.d.ts +4 -2
  480. package/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  481. package/dist/providers/vercel-ai-gateway.models.js +4 -2
  482. package/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  483. package/dist/providers/xai.d.ts.map +1 -1
  484. package/dist/providers/xai.js.map +1 -1
  485. package/dist/providers/xai.models.d.ts +4 -2
  486. package/dist/providers/xai.models.d.ts.map +1 -1
  487. package/dist/providers/xai.models.js +4 -2
  488. package/dist/providers/xai.models.js.map +1 -1
  489. package/dist/providers/xiaomi-token-plan-ams.d.ts.map +1 -1
  490. package/dist/providers/xiaomi-token-plan-ams.js.map +1 -1
  491. package/dist/providers/xiaomi-token-plan-ams.models.d.ts +4 -2
  492. package/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
  493. package/dist/providers/xiaomi-token-plan-ams.models.js +4 -2
  494. package/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  495. package/dist/providers/xiaomi-token-plan-cn.d.ts.map +1 -1
  496. package/dist/providers/xiaomi-token-plan-cn.js.map +1 -1
  497. package/dist/providers/xiaomi-token-plan-cn.models.d.ts +4 -2
  498. package/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
  499. package/dist/providers/xiaomi-token-plan-cn.models.js +4 -2
  500. package/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  501. package/dist/providers/xiaomi-token-plan-sgp.d.ts.map +1 -1
  502. package/dist/providers/xiaomi-token-plan-sgp.js.map +1 -1
  503. package/dist/providers/xiaomi-token-plan-sgp.models.d.ts +4 -2
  504. package/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
  505. package/dist/providers/xiaomi-token-plan-sgp.models.js +4 -2
  506. package/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  507. package/dist/providers/xiaomi.d.ts.map +1 -1
  508. package/dist/providers/xiaomi.js.map +1 -1
  509. package/dist/providers/xiaomi.models.d.ts +4 -2
  510. package/dist/providers/xiaomi.models.d.ts.map +1 -1
  511. package/dist/providers/xiaomi.models.js +4 -2
  512. package/dist/providers/xiaomi.models.js.map +1 -1
  513. package/dist/providers/zai-coding-cn.d.ts.map +1 -1
  514. package/dist/providers/zai-coding-cn.js.map +1 -1
  515. package/dist/providers/zai-coding-cn.models.d.ts +4 -2
  516. package/dist/providers/zai-coding-cn.models.d.ts.map +1 -1
  517. package/dist/providers/zai-coding-cn.models.js +4 -2
  518. package/dist/providers/zai-coding-cn.models.js.map +1 -1
  519. package/dist/providers/zai.d.ts.map +1 -1
  520. package/dist/providers/zai.js.map +1 -1
  521. package/dist/providers/zai.models.d.ts +4 -2
  522. package/dist/providers/zai.models.d.ts.map +1 -1
  523. package/dist/providers/zai.models.js +4 -2
  524. package/dist/providers/zai.models.js.map +1 -1
  525. package/dist/session-resources.d.ts.map +1 -1
  526. package/dist/session-resources.js.map +1 -1
  527. package/dist/types.d.ts +141 -21
  528. package/dist/types.d.ts.map +1 -1
  529. package/dist/types.js.map +1 -1
  530. package/dist/utils/abort-signals.d.ts.map +1 -1
  531. package/dist/utils/abort-signals.js.map +1 -1
  532. package/dist/utils/abort.d.ts.map +1 -1
  533. package/dist/utils/abort.js.map +1 -1
  534. package/dist/utils/assistant-message-frame.d.ts.map +1 -1
  535. package/dist/utils/assistant-message-frame.js.map +1 -1
  536. package/dist/utils/diagnostics.d.ts.map +1 -1
  537. package/dist/utils/diagnostics.js.map +1 -1
  538. package/dist/utils/error-body.d.ts.map +1 -1
  539. package/dist/utils/error-body.js.map +1 -1
  540. package/dist/utils/estimate.d.ts.map +1 -1
  541. package/dist/utils/estimate.js.map +1 -1
  542. package/dist/utils/event-stream.d.ts.map +1 -1
  543. package/dist/utils/event-stream.js.map +1 -1
  544. package/dist/utils/hash.d.ts.map +1 -1
  545. package/dist/utils/hash.js.map +1 -1
  546. package/dist/utils/headers.d.ts +1 -1
  547. package/dist/utils/headers.d.ts.map +1 -1
  548. package/dist/utils/headers.js +10 -8
  549. package/dist/utils/headers.js.map +1 -1
  550. package/dist/utils/json-parse.d.ts.map +1 -1
  551. package/dist/utils/json-parse.js.map +1 -1
  552. package/dist/utils/model-operations.d.ts +11 -0
  553. package/dist/utils/model-operations.d.ts.map +1 -0
  554. package/dist/utils/model-operations.js +47 -0
  555. package/dist/utils/model-operations.js.map +1 -0
  556. package/dist/utils/models-error.d.ts +8 -0
  557. package/dist/utils/models-error.d.ts.map +1 -0
  558. package/dist/utils/models-error.js +19 -0
  559. package/dist/utils/models-error.js.map +1 -0
  560. package/dist/utils/node-http-proxy.d.ts.map +1 -1
  561. package/dist/utils/node-http-proxy.js.map +1 -1
  562. package/dist/utils/oauth-page.d.ts.map +1 -0
  563. package/dist/utils/oauth-page.js.map +1 -0
  564. package/dist/utils/overflow.d.ts.map +1 -1
  565. package/dist/utils/overflow.js.map +1 -1
  566. package/dist/utils/pi-user-agent.d.ts.map +1 -1
  567. package/dist/utils/pi-user-agent.js.map +1 -1
  568. package/dist/utils/provider-env.d.ts.map +1 -1
  569. package/dist/utils/provider-env.js.map +1 -1
  570. package/dist/utils/provider-retry.d.ts.map +1 -1
  571. package/dist/utils/provider-retry.js.map +1 -1
  572. package/dist/utils/retry.d.ts.map +1 -1
  573. package/dist/utils/retry.js +7 -0
  574. package/dist/utils/retry.js.map +1 -1
  575. package/dist/utils/sanitize-unicode.d.ts.map +1 -1
  576. package/dist/utils/sanitize-unicode.js.map +1 -1
  577. package/dist/utils/sleep.d.ts.map +1 -1
  578. package/dist/utils/sleep.js.map +1 -1
  579. package/dist/utils/text.d.ts.map +1 -1
  580. package/dist/utils/text.js.map +1 -1
  581. package/dist/utils/transcript.d.ts.map +1 -1
  582. package/dist/utils/transcript.js.map +1 -1
  583. package/dist/utils/typebox-helpers.d.ts.map +1 -1
  584. package/dist/utils/typebox-helpers.js.map +1 -1
  585. package/dist/utils/uuid.d.ts.map +1 -1
  586. package/dist/utils/uuid.js.map +1 -1
  587. package/dist/utils/validation.d.ts.map +1 -1
  588. package/dist/utils/validation.js.map +1 -1
  589. package/package.json +5 -6
  590. package/dist/auth/oauth/oauth-page.d.ts.map +0 -1
  591. package/dist/auth/oauth/oauth-page.js.map +0 -1
  592. package/dist/image-models.generated.d.ts +0 -830
  593. package/dist/image-models.generated.d.ts.map +0 -1
  594. package/dist/image-models.generated.js +0 -832
  595. package/dist/image-models.generated.js.map +0 -1
  596. package/dist/images-models.d.ts +0 -95
  597. package/dist/images-models.d.ts.map +0 -1
  598. package/dist/images-models.js +0 -143
  599. package/dist/images-models.js.map +0 -1
  600. package/dist/providers/openrouter-images.d.ts +0 -3
  601. package/dist/providers/openrouter-images.d.ts.map +0 -1
  602. package/dist/providers/openrouter-images.js +0 -22
  603. package/dist/providers/openrouter-images.js.map +0 -1
  604. /package/dist/{auth/oauth → utils}/oauth-page.d.ts +0 -0
  605. /package/dist/{auth/oauth → utils}/oauth-page.js +0 -0
@@ -0,0 +1,266 @@
1
+ /**
2
+ * OpenAI Responses API token sharing through Sign in with ChatGPT.
3
+ *
4
+ * This public-client flow uses no client secret and sends the resulting user
5
+ * access token directly to api.openai.com.
6
+ */
7
+ import { randomBytes } from "node:crypto";
8
+ import { createServer } from "node:http";
9
+ import { oauthErrorHtml, oauthSuccessHtml } from "../../utils/oauth-page.js";
10
+ import { getProviderEnvValue } from "../../utils/provider-env.js";
11
+ import { generatePKCE } from "./pkce.js";
12
+ // every login registers a new client with this ID; OpenAI returns the issued client ID in the callback
13
+ const DYNAMIC_CLIENT_ID = "dynamic_agent_client";
14
+ const AGENT_NAME_HINT = "Pi";
15
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
16
+ const AUTHORIZE_URL = "https://auth.openai.com/api/accounts/authorize";
17
+ const TOKEN_URL = "https://auth.openai.com/api/accounts/oauth/token";
18
+ const RESOURCE = "https://api.openai.com/v1";
19
+ const CALLBACK_HOST = getProviderEnvValue("PI_OAUTH_CALLBACK_HOST") || "127.0.0.1";
20
+ const CALLBACK_PORT = 1455;
21
+ const CALLBACK_PATH = "/auth/callback";
22
+ const REDIRECT_URI = `http://127.0.0.1:${CALLBACK_PORT}${CALLBACK_PATH}`;
23
+ const DIRECT_TOKEN_SCOPE = "chatgpt.tokens.use.direct";
24
+ const SCOPE = `openid profile email offline_access resource.invoke ${DIRECT_TOKEN_SCOPE}`;
25
+ // Refresh this long before the real expiry so a request never starts with a token about to expire.
26
+ const EXPIRY_MARGIN_MS = 3 * 60 * 1000;
27
+ function randomValue() {
28
+ return randomBytes(32).toString("base64url");
29
+ }
30
+ function authorizationResultFromCallback(url, expectedState) {
31
+ const code = url.searchParams.get("code");
32
+ if (!code)
33
+ throw new Error("Missing authorization code");
34
+ const state = url.searchParams.get("state");
35
+ if (!state)
36
+ throw new Error("Missing OAuth state");
37
+ if (state !== expectedState)
38
+ throw new Error("OAuth state mismatch");
39
+ const clientId = url.searchParams.get("client_id")?.trim();
40
+ if (!clientId)
41
+ throw new Error("OpenAI OAuth registration callback did not contain an issued client ID");
42
+ return { code, clientId };
43
+ }
44
+ function authorizationResultFromManualInput(input, expectedState) {
45
+ let url;
46
+ try {
47
+ url = new URL(input.trim());
48
+ }
49
+ catch {
50
+ throw new Error("Paste the full callback URL from the browser");
51
+ }
52
+ const expected = new URL(REDIRECT_URI);
53
+ if (url.origin !== expected.origin || url.pathname !== expected.pathname) {
54
+ throw new Error(`The pasted callback URL must start with ${REDIRECT_URI}`);
55
+ }
56
+ const error = url.searchParams.get("error");
57
+ if (error)
58
+ throw new Error(`ChatGPT authorization failed: ${error}`);
59
+ return authorizationResultFromCallback(url, expectedState);
60
+ }
61
+ function sendHtml(response, status, body) {
62
+ response.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
63
+ response.end(body);
64
+ }
65
+ function startCallbackServer(expectedState) {
66
+ return new Promise((resolve, reject) => {
67
+ let resolveResult;
68
+ let rejectResult;
69
+ const result = new Promise((resolveAuthorization, rejectAuthorization) => {
70
+ resolveResult = resolveAuthorization;
71
+ rejectResult = rejectAuthorization;
72
+ });
73
+ const server = createServer((request, response) => {
74
+ try {
75
+ const url = new URL(request.url || "", REDIRECT_URI);
76
+ if (url.pathname !== CALLBACK_PATH) {
77
+ sendHtml(response, 404, oauthErrorHtml("Callback route not found."));
78
+ return;
79
+ }
80
+ const error = url.searchParams.get("error");
81
+ if (error) {
82
+ sendHtml(response, 400, oauthErrorHtml("ChatGPT was not connected.", `Error: ${error}`));
83
+ rejectResult(new Error(`ChatGPT authorization failed: ${error}`));
84
+ return;
85
+ }
86
+ let authorizationResult;
87
+ try {
88
+ authorizationResult = authorizationResultFromCallback(url, expectedState);
89
+ }
90
+ catch (error) {
91
+ const message = error instanceof Error ? error.message : "Invalid callback";
92
+ sendHtml(response, 400, oauthErrorHtml(message));
93
+ return;
94
+ }
95
+ sendHtml(response, 200, oauthSuccessHtml("ChatGPT authentication completed. You can close this window."));
96
+ resolveResult(authorizationResult);
97
+ }
98
+ catch {
99
+ sendHtml(response, 500, oauthErrorHtml("Internal error while processing the callback."));
100
+ }
101
+ });
102
+ server.once("error", reject);
103
+ server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
104
+ server.removeListener("error", reject);
105
+ server.on("error", rejectResult);
106
+ resolve({ server, result });
107
+ });
108
+ });
109
+ }
110
+ async function requestToken(body, signal) {
111
+ const response = await fetch(TOKEN_URL, {
112
+ method: "POST",
113
+ headers: {
114
+ accept: "application/json",
115
+ "content-type": "application/x-www-form-urlencoded",
116
+ },
117
+ body,
118
+ signal,
119
+ });
120
+ if (!response.ok) {
121
+ const responseBody = await response.text().catch(() => "");
122
+ throw new Error(`OpenAI OAuth token request failed (${response.status}): ${responseBody || response.statusText}`);
123
+ }
124
+ const data = await response.json();
125
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
126
+ throw new Error("OpenAI OAuth token response must be an object");
127
+ }
128
+ return data;
129
+ }
130
+ function requireTokenString(value, field) {
131
+ if (typeof value !== "string" || value.trim().length === 0) {
132
+ throw new Error(`OpenAI OAuth token response has invalid ${field}`);
133
+ }
134
+ return value;
135
+ }
136
+ function credentialFromTokenResponse(token, clientId) {
137
+ const access = requireTokenString(token.access_token, "access_token");
138
+ const refresh = requireTokenString(token.refresh_token, "refresh_token");
139
+ const scope = requireTokenString(token.scope, "scope");
140
+ if (typeof token.expires_in !== "number" || !Number.isFinite(token.expires_in) || token.expires_in <= 0) {
141
+ throw new Error("OpenAI OAuth token response has invalid expires_in");
142
+ }
143
+ const scopes = scope.trim().split(/\s+/).filter(Boolean);
144
+ if (!scopes.includes(DIRECT_TOKEN_SCOPE)) {
145
+ throw new Error(`OpenAI OAuth grant did not include ${DIRECT_TOKEN_SCOPE}`);
146
+ }
147
+ return {
148
+ type: "oauth",
149
+ access,
150
+ refresh,
151
+ expires: Date.now() + token.expires_in * 1000 - EXPIRY_MARGIN_MS,
152
+ clientId,
153
+ scopes,
154
+ };
155
+ }
156
+ async function exchangeAuthorizationCode(code, verifier, clientId, signal) {
157
+ const token = await requestToken(new URLSearchParams({
158
+ grant_type: "authorization_code",
159
+ client_id: clientId,
160
+ code,
161
+ code_verifier: verifier,
162
+ redirect_uri: REDIRECT_URI,
163
+ resource: RESOURCE,
164
+ }), signal);
165
+ // Pi does not use the ID token to identify the user or read profile data.
166
+ // Keep the presence check as part of the token-response contract.
167
+ if (typeof token.id_token !== "string" || token.id_token.trim().length === 0) {
168
+ throw new Error("OpenAI OAuth token response did not contain an ID token");
169
+ }
170
+ return credentialFromTokenResponse(token, clientId);
171
+ }
172
+ async function refreshAccessToken(credential, signal) {
173
+ const clientId = credential.clientId;
174
+ if (typeof clientId !== "string" || clientId.trim().length === 0) {
175
+ throw new Error("Stored OpenAI OAuth credential does not contain an issued client ID; reconnect ChatGPT");
176
+ }
177
+ const token = await requestToken(new URLSearchParams({
178
+ grant_type: "refresh_token",
179
+ client_id: clientId,
180
+ refresh_token: credential.refresh,
181
+ resource: RESOURCE,
182
+ }), signal);
183
+ return credentialFromTokenResponse(token, clientId);
184
+ }
185
+ /** OpenAI identifies each installation ("agent host") by a stable URI such as `urn:uuid:<uuid>`. */
186
+ function agentHostId(deviceId) {
187
+ if (!deviceId || !UUID_PATTERN.test(deviceId)) {
188
+ throw new Error("Sign in with ChatGPT requires a device ID (UUID) for this installation");
189
+ }
190
+ return `urn:uuid:${deviceId.toLowerCase()}`;
191
+ }
192
+ async function loginOpenAIChatGPT(interaction, options) {
193
+ const hostId = agentHostId(options?.getDeviceId?.());
194
+ const { verifier, challenge } = await generatePKCE();
195
+ const state = randomValue();
196
+ const nonce = randomValue();
197
+ let callback;
198
+ try {
199
+ callback = await startCallbackServer(state);
200
+ }
201
+ catch (error) {
202
+ interaction.notify({
203
+ type: "info",
204
+ message: `Could not listen on ${REDIRECT_URI}; paste the final redirect URL to continue. ${error instanceof Error ? error.message : String(error)}`,
205
+ });
206
+ }
207
+ const authorizationUrl = new URL(AUTHORIZE_URL);
208
+ authorizationUrl.search = new URLSearchParams({
209
+ client_id: DYNAMIC_CLIENT_ID,
210
+ agent_name_hint: AGENT_NAME_HINT,
211
+ ext_agent_host_id: hostId,
212
+ response_type: "code",
213
+ redirect_uri: REDIRECT_URI,
214
+ resource: RESOURCE,
215
+ scope: SCOPE,
216
+ state,
217
+ code_challenge: challenge,
218
+ code_challenge_method: "S256",
219
+ nonce,
220
+ }).toString();
221
+ interaction.notify({
222
+ type: "auth_url",
223
+ url: authorizationUrl.toString(),
224
+ instructions: "Complete sign-in in your browser. If the callback does not complete, paste the final redirect URL here.",
225
+ });
226
+ const manualAbort = new AbortController();
227
+ const manualCode = interaction
228
+ .prompt({
229
+ type: "manual_code",
230
+ message: "Complete login in your browser, or paste the final redirect URL here:",
231
+ placeholder: REDIRECT_URI,
232
+ signal: AbortSignal.any([manualAbort.signal, interaction.signal]),
233
+ })
234
+ .then((input) => authorizationResultFromManualInput(input, state));
235
+ try {
236
+ const result = await (callback ? Promise.race([callback.result, manualCode]) : manualCode);
237
+ interaction.notify({ type: "progress", message: "Exchanging authorization code for tokens..." });
238
+ return await exchangeAuthorizationCode(result.code, verifier, result.clientId, interaction.signal);
239
+ }
240
+ catch (error) {
241
+ if (interaction.signal.aborted)
242
+ throw new Error("Login cancelled");
243
+ throw error;
244
+ }
245
+ finally {
246
+ manualAbort.abort();
247
+ callback?.server.close();
248
+ // close() only stops accepting new connections. Browsers open spare connections ahead of
249
+ // time, and one that has not sent a request yet stays open and attached to this server.
250
+ // A later login in the same process starts a new server with a new state, but the browser
251
+ // may send that login's callback over the spare connection. This server would then handle
252
+ // it and reject it with "OAuth state mismatch", and the new login would never see it.
253
+ callback?.server.closeAllConnections();
254
+ }
255
+ }
256
+ export const openaiChatGPTOAuth = {
257
+ name: "OpenAI (ChatGPT subscription)",
258
+ isSubscription: true,
259
+ loginLabel: "Sign in with ChatGPT",
260
+ login: loginOpenAIChatGPT,
261
+ refresh: refreshAccessToken,
262
+ async toAuth(credential) {
263
+ return { apiKey: credential.access };
264
+ },
265
+ };
266
+ //# sourceMappingURL=openai-chatgpt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-chatgpt.js","sourceRoot":"","sources":["../../../src/auth/oauth/openai-chatgpt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OpenAI Responses API token sharing through Sign in with ChatGPT.\n *\n * This public-client flow uses no client secret and sends the resulting user\n * access token directly to api.openai.com.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createServer, type Server, type ServerResponse } from \"node:http\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"../../utils/oauth-page.ts\";\nimport { getProviderEnvValue } from \"../../utils/provider-env.ts\";\nimport type { LoginOptions, OAuthAuth, OAuthCredential, ProviderAuthInteraction } from \"../types.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\n\n// every login registers a new client with this ID; OpenAI returns the issued client ID in the callback\nconst DYNAMIC_CLIENT_ID = \"dynamic_agent_client\";\nconst AGENT_NAME_HINT = \"Pi\";\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nconst AUTHORIZE_URL = \"https://auth.openai.com/api/accounts/authorize\";\nconst TOKEN_URL = \"https://auth.openai.com/api/accounts/oauth/token\";\nconst RESOURCE = \"https://api.openai.com/v1\";\nconst CALLBACK_HOST = getProviderEnvValue(\"PI_OAUTH_CALLBACK_HOST\") || \"127.0.0.1\";\nconst CALLBACK_PORT = 1455;\nconst CALLBACK_PATH = \"/auth/callback\";\nconst REDIRECT_URI = `http://127.0.0.1:${CALLBACK_PORT}${CALLBACK_PATH}`;\nconst DIRECT_TOKEN_SCOPE = \"chatgpt.tokens.use.direct\";\nconst SCOPE = `openid profile email offline_access resource.invoke ${DIRECT_TOKEN_SCOPE}`;\n// Refresh this long before the real expiry so a request never starts with a token about to expire.\nconst EXPIRY_MARGIN_MS = 3 * 60 * 1000;\n\ntype AuthorizationResult = {\n\tcode: string;\n\tclientId: string;\n};\n\ntype CallbackServer = {\n\tserver: Server;\n\tresult: Promise<AuthorizationResult>;\n};\n\ntype TokenResponse = {\n\taccess_token?: unknown;\n\trefresh_token?: unknown;\n\texpires_in?: unknown;\n\tid_token?: unknown;\n\tscope?: unknown;\n};\n\nfunction randomValue(): string {\n\treturn randomBytes(32).toString(\"base64url\");\n}\n\nfunction authorizationResultFromCallback(url: URL, expectedState: string): AuthorizationResult {\n\tconst code = url.searchParams.get(\"code\");\n\tif (!code) throw new Error(\"Missing authorization code\");\n\tconst state = url.searchParams.get(\"state\");\n\tif (!state) throw new Error(\"Missing OAuth state\");\n\tif (state !== expectedState) throw new Error(\"OAuth state mismatch\");\n\tconst clientId = url.searchParams.get(\"client_id\")?.trim();\n\tif (!clientId) throw new Error(\"OpenAI OAuth registration callback did not contain an issued client ID\");\n\treturn { code, clientId };\n}\n\nfunction authorizationResultFromManualInput(input: string, expectedState: string): AuthorizationResult {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(input.trim());\n\t} catch {\n\t\tthrow new Error(\"Paste the full callback URL from the browser\");\n\t}\n\tconst expected = new URL(REDIRECT_URI);\n\tif (url.origin !== expected.origin || url.pathname !== expected.pathname) {\n\t\tthrow new Error(`The pasted callback URL must start with ${REDIRECT_URI}`);\n\t}\n\tconst error = url.searchParams.get(\"error\");\n\tif (error) throw new Error(`ChatGPT authorization failed: ${error}`);\n\treturn authorizationResultFromCallback(url, expectedState);\n}\n\nfunction sendHtml(response: ServerResponse, status: number, body: string): void {\n\tresponse.writeHead(status, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\tresponse.end(body);\n}\n\nfunction startCallbackServer(expectedState: string): Promise<CallbackServer> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet resolveResult!: (result: AuthorizationResult) => void;\n\t\tlet rejectResult!: (error: Error) => void;\n\t\tconst result = new Promise<AuthorizationResult>((resolveAuthorization, rejectAuthorization) => {\n\t\t\tresolveResult = resolveAuthorization;\n\t\t\trejectResult = rejectAuthorization;\n\t\t});\n\n\t\tconst server = createServer((request, response) => {\n\t\t\ttry {\n\t\t\t\tconst url = new URL(request.url || \"\", REDIRECT_URI);\n\t\t\t\tif (url.pathname !== CALLBACK_PATH) {\n\t\t\t\t\tsendHtml(response, 404, oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst error = url.searchParams.get(\"error\");\n\t\t\t\tif (error) {\n\t\t\t\t\tsendHtml(response, 400, oauthErrorHtml(\"ChatGPT was not connected.\", `Error: ${error}`));\n\t\t\t\t\trejectResult(new Error(`ChatGPT authorization failed: ${error}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet authorizationResult: AuthorizationResult;\n\t\t\t\ttry {\n\t\t\t\t\tauthorizationResult = authorizationResultFromCallback(url, expectedState);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : \"Invalid callback\";\n\t\t\t\t\tsendHtml(response, 400, oauthErrorHtml(message));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tsendHtml(response, 200, oauthSuccessHtml(\"ChatGPT authentication completed. You can close this window.\"));\n\t\t\t\tresolveResult(authorizationResult);\n\t\t\t} catch {\n\t\t\t\tsendHtml(response, 500, oauthErrorHtml(\"Internal error while processing the callback.\"));\n\t\t\t}\n\t\t});\n\n\t\tserver.once(\"error\", reject);\n\t\tserver.listen(CALLBACK_PORT, CALLBACK_HOST, () => {\n\t\t\tserver.removeListener(\"error\", reject);\n\t\t\tserver.on(\"error\", rejectResult);\n\t\t\tresolve({ server, result });\n\t\t});\n\t});\n}\n\nasync function requestToken(body: URLSearchParams, signal: AbortSignal): Promise<TokenResponse> {\n\tconst response = await fetch(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\taccept: \"application/json\",\n\t\t\t\"content-type\": \"application/x-www-form-urlencoded\",\n\t\t},\n\t\tbody,\n\t\tsignal,\n\t});\n\tif (!response.ok) {\n\t\tconst responseBody = await response.text().catch(() => \"\");\n\t\tthrow new Error(`OpenAI OAuth token request failed (${response.status}): ${responseBody || response.statusText}`);\n\t}\n\tconst data: unknown = await response.json();\n\tif (typeof data !== \"object\" || data === null || Array.isArray(data)) {\n\t\tthrow new Error(\"OpenAI OAuth token response must be an object\");\n\t}\n\treturn data as TokenResponse;\n}\n\nfunction requireTokenString(value: unknown, field: \"access_token\" | \"refresh_token\" | \"scope\"): string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) {\n\t\tthrow new Error(`OpenAI OAuth token response has invalid ${field}`);\n\t}\n\treturn value;\n}\n\nfunction credentialFromTokenResponse(token: TokenResponse, clientId: string): OAuthCredential {\n\tconst access = requireTokenString(token.access_token, \"access_token\");\n\tconst refresh = requireTokenString(token.refresh_token, \"refresh_token\");\n\tconst scope = requireTokenString(token.scope, \"scope\");\n\tif (typeof token.expires_in !== \"number\" || !Number.isFinite(token.expires_in) || token.expires_in <= 0) {\n\t\tthrow new Error(\"OpenAI OAuth token response has invalid expires_in\");\n\t}\n\tconst scopes = scope.trim().split(/\\s+/).filter(Boolean);\n\tif (!scopes.includes(DIRECT_TOKEN_SCOPE)) {\n\t\tthrow new Error(`OpenAI OAuth grant did not include ${DIRECT_TOKEN_SCOPE}`);\n\t}\n\treturn {\n\t\ttype: \"oauth\",\n\t\taccess,\n\t\trefresh,\n\t\texpires: Date.now() + token.expires_in * 1000 - EXPIRY_MARGIN_MS,\n\t\tclientId,\n\t\tscopes,\n\t};\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tverifier: string,\n\tclientId: string,\n\tsignal: AbortSignal,\n): Promise<OAuthCredential> {\n\tconst token = await requestToken(\n\t\tnew URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: clientId,\n\t\t\tcode,\n\t\t\tcode_verifier: verifier,\n\t\t\tredirect_uri: REDIRECT_URI,\n\t\t\tresource: RESOURCE,\n\t\t}),\n\t\tsignal,\n\t);\n\t// Pi does not use the ID token to identify the user or read profile data.\n\t// Keep the presence check as part of the token-response contract.\n\tif (typeof token.id_token !== \"string\" || token.id_token.trim().length === 0) {\n\t\tthrow new Error(\"OpenAI OAuth token response did not contain an ID token\");\n\t}\n\treturn credentialFromTokenResponse(token, clientId);\n}\n\nasync function refreshAccessToken(credential: OAuthCredential, signal: AbortSignal): Promise<OAuthCredential> {\n\tconst clientId = credential.clientId;\n\tif (typeof clientId !== \"string\" || clientId.trim().length === 0) {\n\t\tthrow new Error(\"Stored OpenAI OAuth credential does not contain an issued client ID; reconnect ChatGPT\");\n\t}\n\tconst token = await requestToken(\n\t\tnew URLSearchParams({\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\tclient_id: clientId,\n\t\t\trefresh_token: credential.refresh,\n\t\t\tresource: RESOURCE,\n\t\t}),\n\t\tsignal,\n\t);\n\treturn credentialFromTokenResponse(token, clientId);\n}\n\n/** OpenAI identifies each installation (\"agent host\") by a stable URI such as `urn:uuid:<uuid>`. */\nfunction agentHostId(deviceId: string | undefined): string {\n\tif (!deviceId || !UUID_PATTERN.test(deviceId)) {\n\t\tthrow new Error(\"Sign in with ChatGPT requires a device ID (UUID) for this installation\");\n\t}\n\treturn `urn:uuid:${deviceId.toLowerCase()}`;\n}\n\nasync function loginOpenAIChatGPT(\n\tinteraction: ProviderAuthInteraction,\n\toptions?: LoginOptions,\n): Promise<OAuthCredential> {\n\tconst hostId = agentHostId(options?.getDeviceId?.());\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = randomValue();\n\tconst nonce = randomValue();\n\tlet callback: CallbackServer | undefined;\n\ttry {\n\t\tcallback = await startCallbackServer(state);\n\t} catch (error) {\n\t\tinteraction.notify({\n\t\t\ttype: \"info\",\n\t\t\tmessage: `Could not listen on ${REDIRECT_URI}; paste the final redirect URL to continue. ${error instanceof Error ? error.message : String(error)}`,\n\t\t});\n\t}\n\n\tconst authorizationUrl = new URL(AUTHORIZE_URL);\n\tauthorizationUrl.search = new URLSearchParams({\n\t\tclient_id: DYNAMIC_CLIENT_ID,\n\t\tagent_name_hint: AGENT_NAME_HINT,\n\t\text_agent_host_id: hostId,\n\t\tresponse_type: \"code\",\n\t\tredirect_uri: REDIRECT_URI,\n\t\tresource: RESOURCE,\n\t\tscope: SCOPE,\n\t\tstate,\n\t\tcode_challenge: challenge,\n\t\tcode_challenge_method: \"S256\",\n\t\tnonce,\n\t}).toString();\n\tinteraction.notify({\n\t\ttype: \"auth_url\",\n\t\turl: authorizationUrl.toString(),\n\t\tinstructions:\n\t\t\t\"Complete sign-in in your browser. If the callback does not complete, paste the final redirect URL here.\",\n\t});\n\n\tconst manualAbort = new AbortController();\n\tconst manualCode = interaction\n\t\t.prompt({\n\t\t\ttype: \"manual_code\",\n\t\t\tmessage: \"Complete login in your browser, or paste the final redirect URL here:\",\n\t\t\tplaceholder: REDIRECT_URI,\n\t\t\tsignal: AbortSignal.any([manualAbort.signal, interaction.signal]),\n\t\t})\n\t\t.then((input) => authorizationResultFromManualInput(input, state));\n\n\ttry {\n\t\tconst result = await (callback ? Promise.race([callback.result, manualCode]) : manualCode);\n\t\tinteraction.notify({ type: \"progress\", message: \"Exchanging authorization code for tokens...\" });\n\t\treturn await exchangeAuthorizationCode(result.code, verifier, result.clientId, interaction.signal);\n\t} catch (error) {\n\t\tif (interaction.signal.aborted) throw new Error(\"Login cancelled\");\n\t\tthrow error;\n\t} finally {\n\t\tmanualAbort.abort();\n\t\tcallback?.server.close();\n\t\t// close() only stops accepting new connections. Browsers open spare connections ahead of\n\t\t// time, and one that has not sent a request yet stays open and attached to this server.\n\t\t// A later login in the same process starts a new server with a new state, but the browser\n\t\t// may send that login's callback over the spare connection. This server would then handle\n\t\t// it and reject it with \"OAuth state mismatch\", and the new login would never see it.\n\t\tcallback?.server.closeAllConnections();\n\t}\n}\n\nexport const openaiChatGPTOAuth: OAuthAuth = {\n\tname: \"OpenAI (ChatGPT subscription)\",\n\tisSubscription: true,\n\tloginLabel: \"Sign in with ChatGPT\",\n\tlogin: loginOpenAIChatGPT,\n\trefresh: refreshAccessToken,\n\tasync toAuth(credential) {\n\t\treturn { apiKey: credential.access };\n\t},\n};\n"]}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * OpenAI Codex (ChatGPT OAuth) flow
3
3
  *
4
- * NOTE: This module uses Node.js crypto and http for the OAuth callback.
4
+ * NOTE: This module uses Node.js crypto and http (via callback-server.ts) for the OAuth callback.
5
5
  * It is only intended for CLI use, not browser environments.
6
6
  */
7
7
  import type { OAuthAuth } from "../types.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/openai-codex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAeH,OAAO,KAAK,EAAE,SAAS,EAA4C,MAAM,aAAa,CAAC;AA8evF,eAAO,MAAM,gBAAgB,EAAE,SA6B9B,CAAC","sourcesContent":["/**\n * OpenAI Codex (ChatGPT OAuth) flow\n *\n * NOTE: This module uses Node.js crypto and http for the OAuth callback.\n * It is only intended for CLI use, not browser environments.\n */\n\n// NEVER convert to top-level imports - breaks browser/Vite builds\nlet _randomBytes: typeof import(\"node:crypto\").randomBytes | null = null;\nlet _http: typeof import(\"node:http\") | null = null;\nif (typeof process !== \"undefined\" && (process.versions?.node || process.versions?.bun)) {\n\timport(\"node:crypto\").then((m) => {\n\t\t_randomBytes = m.randomBytes;\n\t});\n\timport(\"node:http\").then((m) => {\n\t\t_http = m;\n\t});\n}\n\nimport { getProviderEnvValue } from \"../../utils/provider-env.ts\";\nimport type { OAuthAuth, OAuthCredential, ProviderAuthInteraction } from \"../types.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"./oauth-page.ts\";\nimport { generatePKCE } from \"./pkce.ts\";\n\nconst CLIENT_ID = \"app_EMoamEEZ73f0CkXaXp7hrann\";\nconst AUTH_BASE_URL = \"https://auth.openai.com\";\nconst AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;\nconst TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;\nconst REDIRECT_URI = \"http://localhost:1455/auth/callback\";\nconst DEVICE_USER_CODE_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`;\nconst DEVICE_TOKEN_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/token`;\nconst DEVICE_VERIFICATION_URI = `${AUTH_BASE_URL}/codex/device`;\nconst DEVICE_REDIRECT_URI = `${AUTH_BASE_URL}/deviceauth/callback`;\nconst DEVICE_CODE_TIMEOUT_SECONDS = 15 * 60;\nconst OPENAI_CODEX_BROWSER_LOGIN_METHOD = \"browser\";\nconst OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD = \"device_code\";\nconst SCOPE = \"openid profile email offline_access\";\nconst JWT_CLAIM_PATH = \"https://api.openai.com/auth\";\n\ntype OAuthToken = { access: string; refresh: string; expires: number };\ntype TokenOperation = \"exchange\" | \"refresh\";\n\nfunction getCallbackHost(): string {\n\treturn getProviderEnvValue(\"PI_OAUTH_CALLBACK_HOST\") || \"127.0.0.1\";\n}\n\ntype DeviceAuthInfo = {\n\tdeviceAuthId: string;\n\tuserCode: string;\n\tintervalSeconds: number;\n};\n\ntype DeviceTokenSuccess = {\n\tauthorizationCode: string;\n\tcodeVerifier: string;\n};\n\ntype JwtPayload = {\n\t[JWT_CLAIM_PATH]?: {\n\t\tchatgpt_account_id?: string;\n\t};\n\t[key: string]: unknown;\n};\n\nfunction createState(): string {\n\tif (!_randomBytes) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\treturn _randomBytes(16).toString(\"hex\");\n}\n\nfunction parseAuthorizationInput(input: string): { code?: string; state?: string } {\n\tconst value = input.trim();\n\tif (!value) return {};\n\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn {\n\t\t\tcode: url.searchParams.get(\"code\") ?? undefined,\n\t\t\tstate: url.searchParams.get(\"state\") ?? undefined,\n\t\t};\n\t} catch {\n\t\t// not a URL\n\t}\n\n\tif (value.includes(\"#\")) {\n\t\tconst [code, state] = value.split(\"#\", 2);\n\t\treturn { code, state };\n\t}\n\n\tif (value.includes(\"code=\")) {\n\t\tconst params = new URLSearchParams(value);\n\t\treturn {\n\t\t\tcode: params.get(\"code\") ?? undefined,\n\t\t\tstate: params.get(\"state\") ?? undefined,\n\t\t};\n\t}\n\n\treturn { code: value };\n}\n\nfunction decodeJwt(token: string): JwtPayload | null {\n\ttry {\n\t\tconst parts = token.split(\".\");\n\t\tif (parts.length !== 3) return null;\n\t\tconst payload = parts[1] ?? \"\";\n\t\tconst decoded = atob(payload);\n\t\treturn JSON.parse(decoded) as JwtPayload;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function fetchWithLoginCancellation(input: string, init: RequestInit): Promise<Response> {\n\ttry {\n\t\treturn await fetch(input, init);\n\t} catch (error) {\n\t\tif (init.signal?.aborted) {\n\t\t\tthrow new Error(\"Login cancelled\");\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nasync function readTokenResponse(response: Response, operation: TokenOperation): Promise<OAuthToken> {\n\tif (!response.ok) {\n\t\tconst text = await response.text().catch(() => \"\");\n\t\tthrow new Error(`OpenAI Codex token ${operation} failed (${response.status}): ${text || response.statusText}`);\n\t}\n\n\tconst rawJson = await response.json();\n\tconst json = rawJson as {\n\t\taccess_token?: string;\n\t\trefresh_token?: string;\n\t\texpires_in?: number;\n\t} | null;\n\tif (!json?.access_token || !json.refresh_token || typeof json.expires_in !== \"number\") {\n\t\tthrow new Error(`OpenAI Codex token ${operation} response missing fields: ${JSON.stringify(json)}`);\n\t}\n\n\treturn {\n\t\taccess: json.access_token,\n\t\trefresh: json.refresh_token,\n\t\texpires: Date.now() + json.expires_in * 1000,\n\t};\n}\n\nasync function exchangeAuthorizationCode(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string,\n\tsignal: AbortSignal,\n): Promise<OAuthToken> {\n\tconst response = await fetchWithLoginCancellation(TOKEN_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams({\n\t\t\tgrant_type: \"authorization_code\",\n\t\t\tclient_id: CLIENT_ID,\n\t\t\tcode,\n\t\t\tcode_verifier: verifier,\n\t\t\tredirect_uri: redirectUri,\n\t\t}),\n\t\tsignal,\n\t});\n\n\treturn readTokenResponse(response, \"exchange\");\n}\n\nasync function refreshAccessToken(refreshToken: string, signal: AbortSignal): Promise<OAuthToken> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\t\tbody: new URLSearchParams({\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t}),\n\t\t\tsignal,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(`OpenAI Codex token refresh error: ${error instanceof Error ? error.message : String(error)}`);\n\t}\n\n\treturn readTokenResponse(response, \"refresh\");\n}\n\nasync function startOpenAICodexDeviceAuth(signal: AbortSignal): Promise<DeviceAuthInfo> {\n\tconst response = await fetchWithLoginCancellation(DEVICE_USER_CODE_URL, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify({ client_id: CLIENT_ID }),\n\t\tsignal,\n\t});\n\n\tif (!response.ok) {\n\t\tif (response.status === 404) {\n\t\t\tthrow new Error(\n\t\t\t\t\"OpenAI Codex device code login is not enabled for this server. Use browser login or verify the server URL.\",\n\t\t\t);\n\t\t}\n\t\tconst responseBody = await response.text().catch(() => \"\");\n\t\tthrow new Error(\n\t\t\t`OpenAI Codex device code request failed with status ${response.status}${responseBody ? `: ${responseBody}` : \"\"}`,\n\t\t);\n\t}\n\n\tconst rawJson = await response.json();\n\tconst json = rawJson as {\n\t\tdevice_auth_id?: string;\n\t\tuser_code?: string;\n\t\tinterval?: number | string;\n\t} | null;\n\tconst intervalSeconds = typeof json?.interval === \"string\" ? Number(json.interval.trim()) : json?.interval;\n\tif (\n\t\t!json?.device_auth_id ||\n\t\t!json.user_code ||\n\t\ttypeof intervalSeconds !== \"number\" ||\n\t\t!Number.isFinite(intervalSeconds) ||\n\t\tintervalSeconds < 0\n\t) {\n\t\tthrow new Error(`Invalid OpenAI Codex device code response: ${JSON.stringify(json)}`);\n\t}\n\n\treturn {\n\t\tdeviceAuthId: json.device_auth_id,\n\t\tuserCode: json.user_code,\n\t\tintervalSeconds,\n\t};\n}\n\nasync function pollOpenAICodexDeviceAuth(device: DeviceAuthInfo, signal: AbortSignal): Promise<DeviceTokenSuccess> {\n\treturn pollOAuthDeviceCodeFlow<DeviceTokenSuccess>({\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,\n\t\tsignal,\n\t\tpoll: async () => {\n\t\t\tconst response = await fetchWithLoginCancellation(DEVICE_TOKEN_URL, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tdevice_auth_id: device.deviceAuthId,\n\t\t\t\t\tuser_code: device.userCode,\n\t\t\t\t}),\n\t\t\t\tsignal,\n\t\t\t});\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst rawJson = await response.json();\n\t\t\t\tconst json = rawJson as { authorization_code?: string; code_verifier?: string } | null;\n\t\t\t\tif (!json?.authorization_code || !json.code_verifier) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tmessage: `Invalid OpenAI Codex device auth token response: ${JSON.stringify(json)}`,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"complete\",\n\t\t\t\t\tvalue: { authorizationCode: json.authorization_code, codeVerifier: json.code_verifier },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (response.status === 403 || response.status === 404) {\n\t\t\t\treturn { status: \"pending\" };\n\t\t\t}\n\n\t\t\tconst responseBody = await response.text().catch(() => \"\");\n\t\t\tlet errorCode: unknown;\n\t\t\ttry {\n\t\t\t\tconst json = JSON.parse(responseBody) as { error?: string | { code?: string } } | null;\n\t\t\t\tconst error = json?.error;\n\t\t\t\terrorCode = typeof error === \"object\" ? error?.code : error;\n\t\t\t} catch {}\n\n\t\t\tif (errorCode === \"deviceauth_authorization_pending\") {\n\t\t\t\treturn { status: \"pending\" };\n\t\t\t}\n\t\t\tif (errorCode === \"slow_down\") {\n\t\t\t\treturn { status: \"slow_down\" };\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tstatus: \"failed\",\n\t\t\t\tmessage: `OpenAI Codex device auth failed with status ${response.status}${responseBody ? `: ${responseBody}` : \"\"}`,\n\t\t\t};\n\t\t},\n\t});\n}\n\nasync function createAuthorizationFlow(\n\toriginator: string = \"pi\",\n): Promise<{ verifier: string; state: string; url: string }> {\n\tconst { verifier, challenge } = await generatePKCE();\n\tconst state = createState();\n\n\tconst url = new URL(AUTHORIZE_URL);\n\turl.searchParams.set(\"response_type\", \"code\");\n\turl.searchParams.set(\"client_id\", CLIENT_ID);\n\turl.searchParams.set(\"redirect_uri\", REDIRECT_URI);\n\turl.searchParams.set(\"scope\", SCOPE);\n\turl.searchParams.set(\"code_challenge\", challenge);\n\turl.searchParams.set(\"code_challenge_method\", \"S256\");\n\turl.searchParams.set(\"state\", state);\n\turl.searchParams.set(\"id_token_add_organizations\", \"true\");\n\turl.searchParams.set(\"codex_cli_simplified_flow\", \"true\");\n\turl.searchParams.set(\"originator\", originator);\n\n\treturn { verifier, state, url: url.toString() };\n}\n\ntype OAuthServerInfo = {\n\tclose: () => void;\n\tcancelWait: () => void;\n\twaitForCode: () => Promise<{ code: string } | null>;\n};\n\nfunction startLocalOAuthServer(state: string): Promise<OAuthServerInfo> {\n\tif (!_http) {\n\t\tthrow new Error(\"OpenAI Codex OAuth is only available in Node.js environments\");\n\t}\n\n\tlet settleWait: ((value: { code: string } | null) => void) | undefined;\n\tconst waitForCodePromise = new Promise<{ code: string } | null>((resolve) => {\n\t\tlet settled = false;\n\t\tsettleWait = (value) => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tresolve(value);\n\t\t};\n\t});\n\n\tconst server = _http.createServer((req, res) => {\n\t\ttry {\n\t\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\t\tif (url.pathname !== \"/auth/callback\") {\n\t\t\t\tres.statusCode = 404;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (url.searchParams.get(\"state\") !== state) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"State mismatch.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst code = url.searchParams.get(\"code\");\n\t\t\tif (!code) {\n\t\t\t\tres.statusCode = 400;\n\t\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\t\tres.end(oauthErrorHtml(\"Missing authorization code.\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tres.statusCode = 200;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthSuccessHtml(\"OpenAI authentication completed. You can close this window.\"));\n\t\t\tsettleWait?.({ code });\n\t\t} catch {\n\t\t\tres.statusCode = 500;\n\t\t\tres.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n\t\t\tres.end(oauthErrorHtml(\"Internal error while processing OAuth callback.\"));\n\t\t}\n\t});\n\n\treturn new Promise((resolve) => {\n\t\tserver\n\t\t\t.listen(1455, getCallbackHost(), () => {\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => server.close(),\n\t\t\t\t\tcancelWait: () => {\n\t\t\t\t\t\tsettleWait?.(null);\n\t\t\t\t\t},\n\t\t\t\t\twaitForCode: () => waitForCodePromise,\n\t\t\t\t});\n\t\t\t})\n\t\t\t.on(\"error\", (_err: NodeJS.ErrnoException) => {\n\t\t\t\tsettleWait?.(null);\n\t\t\t\tresolve({\n\t\t\t\t\tclose: () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tserver.close();\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// ignore\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tcancelWait: () => {},\n\t\t\t\t\twaitForCode: async () => null,\n\t\t\t\t});\n\t\t\t});\n\t});\n}\n\nfunction getAccountId(accessToken: string): string | null {\n\tconst payload = decodeJwt(accessToken);\n\tconst auth = payload?.[JWT_CLAIM_PATH];\n\tconst accountId = auth?.chatgpt_account_id;\n\treturn typeof accountId === \"string\" && accountId.length > 0 ? accountId : null;\n}\n\nfunction credentialsFromToken(token: OAuthToken): OAuthCredential {\n\tconst accountId = getAccountId(token.access);\n\tif (!accountId) {\n\t\tthrow new Error(\"Failed to extract accountId from token\");\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\taccess: token.access,\n\t\trefresh: token.refresh,\n\t\texpires: token.expires,\n\t\taccountId,\n\t};\n}\n\nasync function exchangeAuthorizationCodeForCredentials(\n\tcode: string,\n\tverifier: string,\n\tredirectUri: string,\n\tsignal: AbortSignal,\n): Promise<OAuthCredential> {\n\treturn credentialsFromToken(await exchangeAuthorizationCode(code, verifier, redirectUri, signal));\n}\n\nasync function loginOpenAICodexDeviceCode(interaction: ProviderAuthInteraction): Promise<OAuthCredential> {\n\tconst device = await startOpenAICodexDeviceAuth(interaction.signal);\n\tinteraction.notify({\n\t\ttype: \"device_code\",\n\t\tuserCode: device.userCode,\n\t\tverificationUri: DEVICE_VERIFICATION_URI,\n\t\tintervalSeconds: device.intervalSeconds,\n\t\texpiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,\n\t});\n\tconst code = await pollOpenAICodexDeviceAuth(device, interaction.signal);\n\treturn exchangeAuthorizationCodeForCredentials(\n\t\tcode.authorizationCode,\n\t\tcode.codeVerifier,\n\t\tDEVICE_REDIRECT_URI,\n\t\tinteraction.signal,\n\t);\n}\n\nasync function loginOpenAICodex(interaction: ProviderAuthInteraction): Promise<OAuthCredential> {\n\tconst { verifier, state, url } = await createAuthorizationFlow();\n\tconst server = await startLocalOAuthServer(state);\n\tconst manualAbort = new AbortController();\n\tconst onAbort = () => server.cancelWait();\n\tinteraction.signal.addEventListener(\"abort\", onAbort, { once: true });\n\tif (interaction.signal.aborted) onAbort();\n\tlet code: string | undefined;\n\tlet manualCode: string | undefined;\n\tlet manualError: Error | undefined;\n\n\tinteraction.notify({\n\t\ttype: \"auth_url\",\n\t\turl,\n\t\tinstructions: \"A browser window should open. Complete login to finish.\",\n\t});\n\n\ttry {\n\t\tconst manualPromise = interaction\n\t\t\t.prompt({\n\t\t\t\ttype: \"manual_code\",\n\t\t\t\tmessage: \"Complete login in your browser, or paste the authorization code / redirect URL here:\",\n\t\t\t\tplaceholder: REDIRECT_URI,\n\t\t\t\tsignal: manualAbort.signal,\n\t\t\t})\n\t\t\t.then((input) => {\n\t\t\t\tmanualCode = input;\n\t\t\t\tserver.cancelWait();\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tmanualError = error instanceof Error ? error : new Error(String(error));\n\t\t\t\tserver.cancelWait();\n\t\t\t});\n\n\t\tconst result = await server.waitForCode();\n\t\tif (manualError) throw manualError;\n\t\tif (result?.code) {\n\t\t\tcode = result.code;\n\t\t} else if (manualCode) {\n\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\tif (parsed.state && parsed.state !== state) throw new Error(\"State mismatch\");\n\t\t\tcode = parsed.code;\n\t\t}\n\n\t\tif (!code) {\n\t\t\tawait manualPromise;\n\t\t\tif (manualError) throw manualError;\n\t\t\tif (manualCode) {\n\t\t\t\tconst parsed = parseAuthorizationInput(manualCode);\n\t\t\t\tif (parsed.state && parsed.state !== state) throw new Error(\"State mismatch\");\n\t\t\t\tcode = parsed.code;\n\t\t\t}\n\t\t}\n\n\t\tif (!code) throw new Error(\"Missing authorization code\");\n\t\treturn exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI, interaction.signal);\n\t} finally {\n\t\tinteraction.signal.removeEventListener(\"abort\", onAbort);\n\t\tmanualAbort.abort();\n\t\tserver.close();\n\t}\n}\n\n/**\n * Refresh OpenAI Codex OAuth token\n */\nasync function refreshOpenAICodexToken(refreshToken: string, signal: AbortSignal): Promise<OAuthCredential> {\n\treturn credentialsFromToken(await refreshAccessToken(refreshToken, signal));\n}\n\nexport const openaiCodexOAuth: OAuthAuth = {\n\tname: \"OpenAI (ChatGPT Plus/Pro)\",\n\tisSubscription: true,\n\n\tasync login(interaction) {\n\t\tconst method = await interaction.prompt({\n\t\t\ttype: \"select\",\n\t\t\tmessage: \"Select OpenAI Codex login method:\",\n\t\t\toptions: [\n\t\t\t\t{ id: OPENAI_CODEX_BROWSER_LOGIN_METHOD, label: \"Browser login (default)\" },\n\t\t\t\t{ id: OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, label: \"Device code login (headless)\" },\n\t\t\t],\n\t\t});\n\n\t\tif (method === OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD) {\n\t\t\treturn loginOpenAICodexDeviceCode(interaction);\n\t\t}\n\t\tif (method !== OPENAI_CODEX_BROWSER_LOGIN_METHOD) {\n\t\t\tthrow new Error(`Unknown OpenAI Codex login method: ${method}`);\n\t\t}\n\n\t\treturn loginOpenAICodex(interaction);\n\t},\n\n\trefresh: (credential, signal) => refreshOpenAICodexToken(credential.refresh, signal),\n\n\tasync toAuth(credential) {\n\t\treturn { apiKey: credential.access };\n\t},\n};\n"]}
1
+ {"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../../src/auth/oauth/openai-codex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,KAAK,EAAE,SAAS,EAA4C,MAAM,aAAa,CAAC;AAqYvF,eAAO,MAAM,gBAAgB,EAAE,SA6B9B,CAAC"}
@@ -1,23 +1,19 @@
1
1
  /**
2
2
  * OpenAI Codex (ChatGPT OAuth) flow
3
3
  *
4
- * NOTE: This module uses Node.js crypto and http for the OAuth callback.
4
+ * NOTE: This module uses Node.js crypto and http (via callback-server.ts) for the OAuth callback.
5
5
  * It is only intended for CLI use, not browser environments.
6
6
  */
7
7
  // NEVER convert to top-level imports - breaks browser/Vite builds
8
8
  let _randomBytes = null;
9
- let _http = null;
10
9
  if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
11
10
  import("node:crypto").then((m) => {
12
11
  _randomBytes = m.randomBytes;
13
12
  });
14
- import("node:http").then((m) => {
15
- _http = m;
16
- });
17
13
  }
18
14
  import { getProviderEnvValue } from "../../utils/provider-env.js";
15
+ import { startOAuthCallbackServer, waitForCallbackOrManualInput } from "./callback-server.js";
19
16
  import { pollOAuthDeviceCodeFlow } from "./device-code.js";
20
- import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.js";
21
17
  import { generatePKCE } from "./pkce.js";
22
18
  const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
23
19
  const AUTH_BASE_URL = "https://auth.openai.com";
@@ -242,81 +238,6 @@ async function createAuthorizationFlow(originator = "pi") {
242
238
  url.searchParams.set("originator", originator);
243
239
  return { verifier, state, url: url.toString() };
244
240
  }
245
- function startLocalOAuthServer(state) {
246
- if (!_http) {
247
- throw new Error("OpenAI Codex OAuth is only available in Node.js environments");
248
- }
249
- let settleWait;
250
- const waitForCodePromise = new Promise((resolve) => {
251
- let settled = false;
252
- settleWait = (value) => {
253
- if (settled)
254
- return;
255
- settled = true;
256
- resolve(value);
257
- };
258
- });
259
- const server = _http.createServer((req, res) => {
260
- try {
261
- const url = new URL(req.url || "", "http://localhost");
262
- if (url.pathname !== "/auth/callback") {
263
- res.statusCode = 404;
264
- res.setHeader("Content-Type", "text/html; charset=utf-8");
265
- res.end(oauthErrorHtml("Callback route not found."));
266
- return;
267
- }
268
- if (url.searchParams.get("state") !== state) {
269
- res.statusCode = 400;
270
- res.setHeader("Content-Type", "text/html; charset=utf-8");
271
- res.end(oauthErrorHtml("State mismatch."));
272
- return;
273
- }
274
- const code = url.searchParams.get("code");
275
- if (!code) {
276
- res.statusCode = 400;
277
- res.setHeader("Content-Type", "text/html; charset=utf-8");
278
- res.end(oauthErrorHtml("Missing authorization code."));
279
- return;
280
- }
281
- res.statusCode = 200;
282
- res.setHeader("Content-Type", "text/html; charset=utf-8");
283
- res.end(oauthSuccessHtml("OpenAI authentication completed. You can close this window."));
284
- settleWait?.({ code });
285
- }
286
- catch {
287
- res.statusCode = 500;
288
- res.setHeader("Content-Type", "text/html; charset=utf-8");
289
- res.end(oauthErrorHtml("Internal error while processing OAuth callback."));
290
- }
291
- });
292
- return new Promise((resolve) => {
293
- server
294
- .listen(1455, getCallbackHost(), () => {
295
- resolve({
296
- close: () => server.close(),
297
- cancelWait: () => {
298
- settleWait?.(null);
299
- },
300
- waitForCode: () => waitForCodePromise,
301
- });
302
- })
303
- .on("error", (_err) => {
304
- settleWait?.(null);
305
- resolve({
306
- close: () => {
307
- try {
308
- server.close();
309
- }
310
- catch {
311
- // ignore
312
- }
313
- },
314
- cancelWait: () => { },
315
- waitForCode: async () => null,
316
- });
317
- });
318
- });
319
- }
320
241
  function getAccountId(accessToken) {
321
242
  const payload = decodeJwt(accessToken);
322
243
  const auth = payload?.[JWT_CLAIM_PATH];
@@ -353,67 +274,42 @@ async function loginOpenAICodexDeviceCode(interaction) {
353
274
  }
354
275
  async function loginOpenAICodex(interaction) {
355
276
  const { verifier, state, url } = await createAuthorizationFlow();
356
- const server = await startLocalOAuthServer(state);
357
- const manualAbort = new AbortController();
358
- const onAbort = () => server.cancelWait();
359
- interaction.signal.addEventListener("abort", onAbort, { once: true });
360
- if (interaction.signal.aborted)
361
- onAbort();
362
- let code;
363
- let manualCode;
364
- let manualError;
277
+ // Port 1455 is shared with the Codex CLI; when it is taken, fall back to the pasted redirect URL.
278
+ const callback = await startOAuthCallbackServer({
279
+ providerName: "OpenAI",
280
+ host: getCallbackHost(),
281
+ port: 1455,
282
+ path: "/auth/callback",
283
+ state,
284
+ complete: async (code) => code,
285
+ signal: interaction.signal,
286
+ }).catch(() => undefined);
365
287
  interaction.notify({
366
288
  type: "auth_url",
367
289
  url,
368
290
  instructions: "A browser window should open. Complete login to finish.",
369
291
  });
370
292
  try {
371
- const manualPromise = interaction
372
- .prompt({
373
- type: "manual_code",
293
+ const result = await waitForCallbackOrManualInput(interaction, callback, {
374
294
  message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
375
295
  placeholder: REDIRECT_URI,
376
- signal: manualAbort.signal,
377
- })
378
- .then((input) => {
379
- manualCode = input;
380
- server.cancelWait();
381
- })
382
- .catch((error) => {
383
- manualError = error instanceof Error ? error : new Error(String(error));
384
- server.cancelWait();
385
296
  });
386
- const result = await server.waitForCode();
387
- if (manualError)
388
- throw manualError;
389
- if (result?.code) {
390
- code = result.code;
297
+ let code;
298
+ if (result.type === "callback") {
299
+ code = result.value;
391
300
  }
392
- else if (manualCode) {
393
- const parsed = parseAuthorizationInput(manualCode);
301
+ else {
302
+ const parsed = parseAuthorizationInput(result.input);
394
303
  if (parsed.state && parsed.state !== state)
395
304
  throw new Error("State mismatch");
396
305
  code = parsed.code;
397
306
  }
398
- if (!code) {
399
- await manualPromise;
400
- if (manualError)
401
- throw manualError;
402
- if (manualCode) {
403
- const parsed = parseAuthorizationInput(manualCode);
404
- if (parsed.state && parsed.state !== state)
405
- throw new Error("State mismatch");
406
- code = parsed.code;
407
- }
408
- }
409
307
  if (!code)
410
308
  throw new Error("Missing authorization code");
411
- return exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI, interaction.signal);
309
+ return await exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI, interaction.signal);
412
310
  }
413
311
  finally {
414
- interaction.signal.removeEventListener("abort", onAbort);
415
- manualAbort.abort();
416
- server.close();
312
+ callback?.close();
417
313
  }
418
314
  }
419
315
  /**