@code-yeongyu/senpi-ai 2026.9.30 → 2026.10.1-2

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 (274) hide show
  1. package/README.md +189 -19
  2. package/dist/api/anthropic-messages.js +168 -32
  3. package/dist/api/azure-openai-responses.js +23 -10
  4. package/dist/api/bedrock-converse-stream.js +12 -4
  5. package/dist/api/cloudflare-workers-ai-system-one.d.ts +4 -0
  6. package/dist/api/cloudflare-workers-ai-system-one.js +43 -0
  7. package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts +3 -0
  8. package/dist/api/cloudflare-workers-ai-system-one.lazy.js +4 -0
  9. package/dist/api/cloudflare.d.ts +2 -0
  10. package/dist/api/cloudflare.js +2 -0
  11. package/dist/api/context-room.d.ts +2 -2
  12. package/dist/api/cursor-agent.js +19 -10
  13. package/dist/api/devin-agent/request.d.ts +9 -9
  14. package/dist/api/devin-agent/request.js +14 -10
  15. package/dist/api/google-generative-ai.js +20 -80
  16. package/dist/api/google-shared.d.ts +13 -4
  17. package/dist/api/google-shared.js +54 -4
  18. package/dist/api/google-vertex.js +19 -62
  19. package/dist/api/llama-cpp-classify.d.ts +33 -0
  20. package/dist/api/llama-cpp-classify.js +365 -0
  21. package/dist/api/llama-cpp-classify.lazy.d.ts +3 -0
  22. package/dist/api/llama-cpp-classify.lazy.js +4 -0
  23. package/dist/api/mistral-conversations.d.ts +1 -1
  24. package/dist/api/mistral-conversations.js +36 -29
  25. package/dist/api/openai-codex-responses.d.ts +1 -1
  26. package/dist/api/openai-codex-responses.js +72 -37
  27. package/dist/api/openai-completions.d.ts +3 -2
  28. package/dist/api/openai-completions.js +76 -50
  29. package/dist/api/openai-images-params.d.ts +2 -2
  30. package/dist/api/openai-images.d.ts +1 -1
  31. package/dist/api/openai-responses-shared.d.ts +31 -8
  32. package/dist/api/openai-responses-shared.js +115 -27
  33. package/dist/api/openai-responses.d.ts +1 -1
  34. package/dist/api/openai-responses.js +38 -33
  35. package/dist/api/openrouter-images.d.ts +2 -1
  36. package/dist/api/openrouter-images.js +1 -0
  37. package/dist/api/pi-messages.d.ts +3 -3
  38. package/dist/api/pi-messages.js +3 -2
  39. package/dist/api/simple-options.d.ts +2 -2
  40. package/dist/api/simple-options.js +1 -0
  41. package/dist/api/system-one-shared.d.ts +23 -0
  42. package/dist/api/system-one-shared.js +183 -0
  43. package/dist/api/transform-messages.js +5 -2
  44. package/dist/api/typesafe-system-one.d.ts +4 -0
  45. package/dist/api/typesafe-system-one.js +19 -0
  46. package/dist/api/typesafe-system-one.lazy.d.ts +3 -0
  47. package/dist/api/typesafe-system-one.lazy.js +4 -0
  48. package/dist/api-registry.d.ts +3 -3
  49. package/dist/auth/helpers.js +1 -1
  50. package/dist/auth/oauth/anthropic-callback-listener.js +1 -1
  51. package/dist/auth/oauth/callback-server.d.ts +55 -0
  52. package/dist/auth/oauth/callback-server.js +146 -0
  53. package/dist/auth/oauth/chatgpt-subscription.d.ts +1 -1
  54. package/dist/auth/oauth/chatgpt-subscription.js +20 -124
  55. package/dist/auth/oauth/devin-callback.js +1 -1
  56. package/dist/auth/oauth/load.d.ts +4 -0
  57. package/dist/auth/oauth/load.js +10 -0
  58. package/dist/auth/oauth/meta.d.ts +17 -0
  59. package/dist/auth/oauth/meta.js +190 -0
  60. package/dist/auth/oauth/openai-chatgpt.d.ts +9 -0
  61. package/dist/auth/oauth/openai-chatgpt.js +266 -0
  62. package/dist/auth/oauth/openrouter.d.ts +1 -1
  63. package/dist/auth/oauth/openrouter.js +19 -138
  64. package/dist/auth/oauth/radius.d.ts +1 -1
  65. package/dist/auth/oauth/radius.js +21 -89
  66. package/dist/auth/resolve.d.ts +3 -8
  67. package/dist/auth/resolve.js +3 -18
  68. package/dist/auth/types.d.ts +10 -1
  69. package/dist/bun-oauth.js +4 -0
  70. package/dist/cli.js +3 -1
  71. package/dist/compat.js +17 -14
  72. package/dist/env-api-keys.js +2 -0
  73. package/dist/image-models.d.ts +19 -8
  74. package/dist/image-models.js +14 -13
  75. package/dist/images-api-registry.d.ts +8 -8
  76. package/dist/images.d.ts +7 -2
  77. package/dist/images.js +5 -0
  78. package/dist/index.d.ts +2 -2
  79. package/dist/index.js +2 -2
  80. package/dist/model-catalog.d.ts +27 -9
  81. package/dist/model-catalog.js +24 -2
  82. package/dist/model.d.ts +13 -13
  83. package/dist/models-store.d.ts +3 -2
  84. package/dist/models.d.ts +106 -36
  85. package/dist/models.generated.d.ts +142 -42
  86. package/dist/models.generated.js +142 -42
  87. package/dist/models.js +174 -36
  88. package/dist/providers/alibaba-token-plan.models.d.ts +4 -2
  89. package/dist/providers/alibaba-token-plan.models.js +4 -2
  90. package/dist/providers/all.d.ts +72 -19
  91. package/dist/providers/all.js +28 -22
  92. package/dist/providers/amazon-bedrock.models.d.ts +4 -2
  93. package/dist/providers/amazon-bedrock.models.js +4 -2
  94. package/dist/providers/ant-ling.models.d.ts +4 -2
  95. package/dist/providers/ant-ling.models.js +4 -2
  96. package/dist/providers/anthropic.models.d.ts +4 -2
  97. package/dist/providers/anthropic.models.js +4 -2
  98. package/dist/providers/azure-openai-responses.models.d.ts +4 -2
  99. package/dist/providers/azure-openai-responses.models.js +4 -2
  100. package/dist/providers/bai.models.d.ts +4 -2
  101. package/dist/providers/bai.models.js +4 -2
  102. package/dist/providers/baseten.models.d.ts +4 -2
  103. package/dist/providers/baseten.models.js +4 -2
  104. package/dist/providers/cerebras.models.d.ts +4 -2
  105. package/dist/providers/cerebras.models.js +4 -2
  106. package/dist/providers/chatgpt-subscription.models.d.ts +4 -2
  107. package/dist/providers/chatgpt-subscription.models.js +4 -2
  108. package/dist/providers/cloudflare-ai-gateway.models.d.ts +4 -2
  109. package/dist/providers/cloudflare-ai-gateway.models.js +4 -2
  110. package/dist/providers/cloudflare-stream.d.ts +6 -2
  111. package/dist/providers/cloudflare-stream.js +6 -0
  112. package/dist/providers/cloudflare-workers-ai.js +10 -3
  113. package/dist/providers/cloudflare-workers-ai.models.d.ts +4 -2
  114. package/dist/providers/cloudflare-workers-ai.models.js +4 -2
  115. package/dist/providers/data/.manifest.json +1 -1
  116. package/dist/providers/data/alibaba-token-plan.json +1 -1
  117. package/dist/providers/data/amazon-bedrock.json +1 -1
  118. package/dist/providers/data/ant-ling.json +1 -1
  119. package/dist/providers/data/anthropic.json +1 -1
  120. package/dist/providers/data/azure-openai-responses.json +1 -1
  121. package/dist/providers/data/bai.json +1 -1
  122. package/dist/providers/data/baseten.json +1 -1
  123. package/dist/providers/data/cerebras.json +1 -1
  124. package/dist/providers/data/chatgpt-subscription.json +1 -1
  125. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  126. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  127. package/dist/providers/data/deepseek.json +1 -1
  128. package/dist/providers/data/fireworks.json +1 -1
  129. package/dist/providers/data/github-copilot.json +1 -1
  130. package/dist/providers/data/google-vertex.json +1 -1
  131. package/dist/providers/data/google.json +1 -1
  132. package/dist/providers/data/groq.json +1 -1
  133. package/dist/providers/data/huggingface.json +1 -1
  134. package/dist/providers/data/meta.json +1 -0
  135. package/dist/providers/data/minimax-cn.json +1 -1
  136. package/dist/providers/data/minimax.json +1 -1
  137. package/dist/providers/data/mistral.json +1 -1
  138. package/dist/providers/data/moonshotai-cn.json +1 -1
  139. package/dist/providers/data/moonshotai.json +1 -1
  140. package/dist/providers/data/nvidia.json +1 -1
  141. package/dist/providers/data/openai.json +1 -1
  142. package/dist/providers/data/opencode-go.json +1 -1
  143. package/dist/providers/data/opencode.json +1 -1
  144. package/dist/providers/data/opengateway.json +1 -1
  145. package/dist/providers/data/openrouter.json +1 -1
  146. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  147. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  148. package/dist/providers/data/qwen-token-plan.json +1 -1
  149. package/dist/providers/data/radius.json +1 -0
  150. package/dist/providers/data/together.json +1 -1
  151. package/dist/providers/data/typesafe.json +1 -0
  152. package/dist/providers/data/venice.json +1 -1
  153. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  154. package/dist/providers/data/xai.json +1 -1
  155. package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  156. package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  157. package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  158. package/dist/providers/data/xiaomi.json +1 -1
  159. package/dist/providers/data/zai-coding-cn.json +1 -1
  160. package/dist/providers/data/zai.json +1 -1
  161. package/dist/providers/deepseek.models.d.ts +4 -2
  162. package/dist/providers/deepseek.models.js +4 -2
  163. package/dist/providers/faux.d.ts +7 -2
  164. package/dist/providers/faux.js +31 -22
  165. package/dist/providers/fireworks.models.d.ts +4 -2
  166. package/dist/providers/fireworks.models.js +4 -2
  167. package/dist/providers/github-copilot.models.d.ts +4 -2
  168. package/dist/providers/github-copilot.models.js +4 -2
  169. package/dist/providers/google-vertex.models.d.ts +4 -2
  170. package/dist/providers/google-vertex.models.js +4 -2
  171. package/dist/providers/google.models.d.ts +4 -2
  172. package/dist/providers/google.models.js +4 -2
  173. package/dist/providers/groq.models.d.ts +4 -2
  174. package/dist/providers/groq.models.js +4 -2
  175. package/dist/providers/huggingface.models.d.ts +4 -2
  176. package/dist/providers/huggingface.models.js +4 -2
  177. package/dist/providers/images/register-builtins.d.ts +2 -2
  178. package/dist/providers/kimi-coding.models.d.ts +54 -7
  179. package/dist/providers/kimi-coding.models.js +34 -7
  180. package/dist/providers/meta.d.ts +3 -0
  181. package/dist/providers/meta.js +24 -0
  182. package/dist/providers/meta.models.d.ts +6 -0
  183. package/dist/providers/meta.models.js +8 -0
  184. package/dist/providers/minimax-cn.models.d.ts +4 -2
  185. package/dist/providers/minimax-cn.models.js +4 -2
  186. package/dist/providers/minimax.models.d.ts +4 -2
  187. package/dist/providers/minimax.models.js +4 -2
  188. package/dist/providers/mistral.models.d.ts +4 -2
  189. package/dist/providers/mistral.models.js +4 -2
  190. package/dist/providers/moonshotai-cn.models.d.ts +4 -2
  191. package/dist/providers/moonshotai-cn.models.js +4 -2
  192. package/dist/providers/moonshotai.models.d.ts +4 -2
  193. package/dist/providers/moonshotai.models.js +4 -2
  194. package/dist/providers/nvidia.models.d.ts +4 -2
  195. package/dist/providers/nvidia.models.js +4 -2
  196. package/dist/providers/openai.js +4 -2
  197. package/dist/providers/openai.models.d.ts +4 -2
  198. package/dist/providers/openai.models.js +4 -2
  199. package/dist/providers/opencode-go.models.d.ts +4 -2
  200. package/dist/providers/opencode-go.models.js +4 -2
  201. package/dist/providers/opencode.d.ts +3 -1
  202. package/dist/providers/opencode.js +5 -2
  203. package/dist/providers/opencode.models.d.ts +4 -2
  204. package/dist/providers/opencode.models.js +4 -2
  205. package/dist/providers/opengateway.models.d.ts +4 -2
  206. package/dist/providers/opengateway.models.js +4 -2
  207. package/dist/providers/openrouter.js +11 -2
  208. package/dist/providers/openrouter.models.d.ts +4 -2
  209. package/dist/providers/openrouter.models.js +4 -2
  210. package/dist/providers/qwen-token-plan-cn.models.d.ts +4 -2
  211. package/dist/providers/qwen-token-plan-cn.models.js +4 -2
  212. package/dist/providers/qwen-token-plan-individual.models.d.ts +4 -2
  213. package/dist/providers/qwen-token-plan-individual.models.js +4 -2
  214. package/dist/providers/qwen-token-plan.models.d.ts +4 -2
  215. package/dist/providers/qwen-token-plan.models.js +4 -2
  216. package/dist/providers/radius.js +19 -5
  217. package/dist/providers/radius.models.d.ts +6 -0
  218. package/dist/providers/radius.models.js +8 -0
  219. package/dist/providers/together.models.d.ts +4 -2
  220. package/dist/providers/together.models.js +4 -2
  221. package/dist/providers/typesafe.d.ts +3 -0
  222. package/dist/providers/typesafe.js +18 -0
  223. package/dist/providers/typesafe.models.d.ts +6 -0
  224. package/dist/providers/typesafe.models.js +8 -0
  225. package/dist/providers/venice.models.d.ts +4 -2
  226. package/dist/providers/venice.models.js +4 -2
  227. package/dist/providers/vercel-ai-gateway.js +5 -2
  228. package/dist/providers/vercel-ai-gateway.models.d.ts +4 -2
  229. package/dist/providers/vercel-ai-gateway.models.js +4 -2
  230. package/dist/providers/xai.models.d.ts +4 -2
  231. package/dist/providers/xai.models.js +4 -2
  232. package/dist/providers/xiaomi-token-plan-ams.models.d.ts +4 -2
  233. package/dist/providers/xiaomi-token-plan-ams.models.js +4 -2
  234. package/dist/providers/xiaomi-token-plan-cn.models.d.ts +4 -2
  235. package/dist/providers/xiaomi-token-plan-cn.models.js +4 -2
  236. package/dist/providers/xiaomi-token-plan-sgp.models.d.ts +4 -2
  237. package/dist/providers/xiaomi-token-plan-sgp.models.js +4 -2
  238. package/dist/providers/xiaomi.models.d.ts +4 -2
  239. package/dist/providers/xiaomi.models.js +4 -2
  240. package/dist/providers/zai-coding-cn.models.d.ts +4 -2
  241. package/dist/providers/zai-coding-cn.models.js +4 -2
  242. package/dist/providers/zai.models.d.ts +4 -2
  243. package/dist/providers/zai.models.js +4 -2
  244. package/dist/tool-call-middleware/context-transformer.d.ts +8 -5
  245. package/dist/tool-call-middleware/context-transformer.js +44 -15
  246. package/dist/types.d.ts +262 -28
  247. package/dist/utils/diagnostics.d.ts +3 -2
  248. package/dist/utils/estimate.d.ts +2 -2
  249. package/dist/utils/estimate.js +22 -30
  250. package/dist/utils/headers.d.ts +1 -1
  251. package/dist/utils/headers.js +10 -8
  252. package/dist/utils/model-operations.d.ts +11 -0
  253. package/dist/utils/model-operations.js +47 -0
  254. package/dist/utils/models-error.d.ts +8 -0
  255. package/dist/utils/models-error.js +18 -0
  256. package/dist/utils/overflow.d.ts +1 -0
  257. package/dist/utils/overflow.js +11 -5
  258. package/dist/utils/prompt-cache-ttl.js +10 -3
  259. package/dist/utils/retry.js +9 -0
  260. package/dist/utils/text.d.ts +9 -1
  261. package/dist/utils/text.js +26 -0
  262. package/dist/utils/transcript.d.ts +85 -0
  263. package/dist/utils/transcript.js +205 -0
  264. package/package.json +3 -4
  265. package/dist/image-models.generated.d.ts +0 -925
  266. package/dist/image-models.generated.js +0 -927
  267. package/dist/images-models.d.ts +0 -95
  268. package/dist/images-models.js +0 -141
  269. package/dist/providers/openai-images.d.ts +0 -3
  270. package/dist/providers/openai-images.js +0 -16
  271. package/dist/providers/openrouter-images.d.ts +0 -3
  272. package/dist/providers/openrouter-images.js +0 -22
  273. /package/dist/{auth/oauth → utils}/oauth-page.d.ts +0 -0
  274. /package/dist/{auth/oauth → utils}/oauth-page.js +0 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Loopback OAuth redirect handler shared by the browser sign-in flows.
3
+ *
4
+ * NOTE: This module uses node:http. It is only reachable through the lazily
5
+ * loaded OAuth flow modules, never from browser-facing entry points.
6
+ */
7
+ import type { ProviderAuthInteraction } from "../types.ts";
8
+ export interface OAuthCallbackServerOptions<T> {
9
+ /** Provider name used on the browser page, for example `OpenAI`. */
10
+ providerName: string;
11
+ /** Address to listen on. */
12
+ host: string;
13
+ /** Port to listen on; `0` picks a free port. */
14
+ port: number;
15
+ path: string;
16
+ /** Host in `redirectUri` when it differs from `host`, for example `localhost`. */
17
+ redirectHost?: string;
18
+ /** Expected `state` parameter. Omit when the provider does not send one. */
19
+ state?: string;
20
+ /**
21
+ * Finishes the sign-in with the received code before the browser page is sent, so the page can
22
+ * show exchange failures. Pass `async (code) => code` to exchange the code later.
23
+ */
24
+ complete: (code: string) => Promise<T>;
25
+ signal?: AbortSignal;
26
+ timeoutMs?: number;
27
+ }
28
+ export interface OAuthCallbackServer<T> {
29
+ redirectUri: string;
30
+ /**
31
+ * Resolves with the result of `complete`, or `undefined` after `cancel()`. Rejects when the provider
32
+ * redirects with an error, `complete` fails, the signal aborts, or the timeout elapses.
33
+ */
34
+ wait(): Promise<T | undefined>;
35
+ /** Stop waiting for the browser unless a callback is already being completed. */
36
+ cancel(): void;
37
+ close(): void;
38
+ }
39
+ export declare function startOAuthCallbackServer<T>(options: OAuthCallbackServerOptions<T>): Promise<OAuthCallbackServer<T>>;
40
+ /**
41
+ * Wait for the browser callback, or for the user to paste the code or redirect URL when the browser
42
+ * cannot reach the loopback server (for example over SSH). Without a callback server only the manual
43
+ * prompt is used.
44
+ */
45
+ export declare function waitForCallbackOrManualInput<T>(interaction: ProviderAuthInteraction, callback: OAuthCallbackServer<T> | undefined, prompt: {
46
+ message: string;
47
+ placeholder: string;
48
+ }): Promise<{
49
+ type: "callback";
50
+ value: T;
51
+ } | {
52
+ type: "manual";
53
+ input: string;
54
+ }>;
55
+ //# sourceMappingURL=callback-server.d.ts.map
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Loopback OAuth redirect handler shared by the browser sign-in flows.
3
+ *
4
+ * NOTE: This module uses node:http. It is only reachable through the lazily
5
+ * loaded OAuth flow modules, never from browser-facing entry points.
6
+ */
7
+ import { createServer } from "node:http";
8
+ import { oauthErrorHtml, oauthSuccessHtml } from "../../utils/oauth-page.js";
9
+ function sendPage(response, status, html) {
10
+ response.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
11
+ response.end(html);
12
+ }
13
+ export async function startOAuthCallbackServer(options) {
14
+ const { providerName, signal } = options;
15
+ if (signal?.aborted)
16
+ throw new Error("Login cancelled");
17
+ let resolveWait = () => { };
18
+ let rejectWait = () => { };
19
+ const waitPromise = new Promise((resolve, reject) => {
20
+ resolveWait = resolve;
21
+ rejectWait = reject;
22
+ });
23
+ // A cancelled or closed wait may never be observed.
24
+ waitPromise.catch(() => undefined);
25
+ let claimed = false;
26
+ let settled = false;
27
+ let timer;
28
+ const onAbort = () => finish({ error: new Error("Login cancelled") });
29
+ const finish = (result) => {
30
+ if (settled)
31
+ return;
32
+ settled = true;
33
+ if (timer)
34
+ clearTimeout(timer);
35
+ signal?.removeEventListener("abort", onAbort);
36
+ if ("error" in result)
37
+ rejectWait(result.error);
38
+ else
39
+ resolveWait(result.value);
40
+ };
41
+ const server = createServer((request, response) => {
42
+ void (async () => {
43
+ const url = new URL(request.url ?? "/", "http://localhost");
44
+ if (request.method !== "GET" || url.pathname !== options.path) {
45
+ sendPage(response, 404, oauthErrorHtml("Callback route not found."));
46
+ return;
47
+ }
48
+ if (options.state !== undefined && url.searchParams.get("state") !== options.state) {
49
+ sendPage(response, 400, oauthErrorHtml("State mismatch."));
50
+ return;
51
+ }
52
+ if (claimed || settled) {
53
+ sendPage(response, 409, oauthErrorHtml("This sign-in has already been handled."));
54
+ return;
55
+ }
56
+ const error = url.searchParams.get("error");
57
+ if (error) {
58
+ const description = url.searchParams.get("error_description") ?? error;
59
+ sendPage(response, 400, oauthErrorHtml(`${providerName} authorization failed.`, description));
60
+ finish({ error: new Error(`${providerName} authorization failed: ${description}`) });
61
+ return;
62
+ }
63
+ const code = url.searchParams.get("code");
64
+ if (!code) {
65
+ sendPage(response, 400, oauthErrorHtml("Missing authorization code."));
66
+ return;
67
+ }
68
+ claimed = true;
69
+ try {
70
+ const value = await options.complete(code);
71
+ sendPage(response, 200, oauthSuccessHtml(`Signed in to ${providerName}. You may now close this page.`));
72
+ finish({ value });
73
+ }
74
+ catch (error) {
75
+ const failure = error instanceof Error ? error : new Error(String(error));
76
+ sendPage(response, 502, oauthErrorHtml(`${providerName} sign-in failed.`, failure.message));
77
+ finish({ error: failure });
78
+ }
79
+ })();
80
+ });
81
+ await new Promise((resolve, reject) => {
82
+ server.once("error", reject);
83
+ server.listen(options.port, options.host, () => {
84
+ server.off("error", reject);
85
+ resolve();
86
+ });
87
+ });
88
+ const address = server.address();
89
+ if (!address || typeof address === "string") {
90
+ server.close();
91
+ throw new Error("OAuth callback server did not bind to TCP");
92
+ }
93
+ server.on("error", (error) => finish({ error }));
94
+ signal?.addEventListener("abort", onAbort, { once: true });
95
+ if (options.timeoutMs !== undefined) {
96
+ timer = setTimeout(() => finish({ error: new Error(`${providerName} sign-in timed out`) }), options.timeoutMs);
97
+ }
98
+ const redirectHost = options.redirectHost ?? options.host;
99
+ return {
100
+ redirectUri: `http://${redirectHost.includes(":") ? `[${redirectHost}]` : redirectHost}:${address.port}${options.path}`,
101
+ wait: () => waitPromise,
102
+ cancel: () => {
103
+ if (!claimed)
104
+ finish({ value: undefined });
105
+ },
106
+ close: () => {
107
+ finish({ error: new Error("OAuth callback server closed") });
108
+ server.close();
109
+ },
110
+ };
111
+ }
112
+ /**
113
+ * Wait for the browser callback, or for the user to paste the code or redirect URL when the browser
114
+ * cannot reach the loopback server (for example over SSH). Without a callback server only the manual
115
+ * prompt is used.
116
+ */
117
+ export async function waitForCallbackOrManualInput(interaction, callback, prompt) {
118
+ const manualAbort = new AbortController();
119
+ let manualError;
120
+ const manual = interaction
121
+ .prompt({ type: "manual_code", ...prompt, signal: manualAbort.signal })
122
+ .then((input) => {
123
+ callback?.cancel();
124
+ return input;
125
+ })
126
+ .catch((error) => {
127
+ manualError = error instanceof Error ? error : new Error(String(error));
128
+ callback?.cancel();
129
+ return undefined;
130
+ });
131
+ try {
132
+ const value = await callback?.wait();
133
+ if (manualError)
134
+ throw manualError;
135
+ if (value !== undefined)
136
+ return { type: "callback", value };
137
+ const input = await manual;
138
+ if (manualError)
139
+ throw manualError;
140
+ return { type: "manual", input: input ?? "" };
141
+ }
142
+ finally {
143
+ manualAbort.abort();
144
+ }
145
+ }
146
+ //# sourceMappingURL=callback-server.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ChatGPT Subscription 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,25 +1,21 @@
1
1
  /**
2
2
  * ChatGPT Subscription 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 { extractChatGptSubscriptionAccountId } from "../../utils/chatgpt-subscription-auth.js";
19
15
  import { getProviderEnvValue } from "../../utils/provider-env.js";
20
16
  import { getWireIdentity } from "../../wire-identity.js";
17
+ import { startOAuthCallbackServer, waitForCallbackOrManualInput } from "./callback-server.js";
21
18
  import { pollOAuthDeviceCodeFlow } from "./device-code.js";
22
- import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.js";
23
19
  import { generatePKCE } from "./pkce.js";
24
20
  const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
25
21
  const AUTH_BASE_URL = "https://auth.openai.com";
@@ -230,81 +226,6 @@ async function createAuthorizationFlow(originator = getWireIdentity()) {
230
226
  url.searchParams.set("originator", originator);
231
227
  return { verifier, state, url: url.toString() };
232
228
  }
233
- function startLocalOAuthServer(state) {
234
- if (!_http) {
235
- throw new Error("ChatGPT Subscription OAuth is only available in Node.js environments");
236
- }
237
- let settleWait;
238
- const waitForCodePromise = new Promise((resolve) => {
239
- let settled = false;
240
- settleWait = (value) => {
241
- if (settled)
242
- return;
243
- settled = true;
244
- resolve(value);
245
- };
246
- });
247
- const server = _http.createServer((req, res) => {
248
- try {
249
- const url = new URL(req.url || "", "http://localhost");
250
- if (url.pathname !== "/auth/callback") {
251
- res.statusCode = 404;
252
- res.setHeader("Content-Type", "text/html; charset=utf-8");
253
- res.end(oauthErrorHtml("Callback route not found."));
254
- return;
255
- }
256
- if (url.searchParams.get("state") !== state) {
257
- res.statusCode = 400;
258
- res.setHeader("Content-Type", "text/html; charset=utf-8");
259
- res.end(oauthErrorHtml("State mismatch."));
260
- return;
261
- }
262
- const code = url.searchParams.get("code");
263
- if (!code) {
264
- res.statusCode = 400;
265
- res.setHeader("Content-Type", "text/html; charset=utf-8");
266
- res.end(oauthErrorHtml("Missing authorization code."));
267
- return;
268
- }
269
- res.statusCode = 200;
270
- res.setHeader("Content-Type", "text/html; charset=utf-8");
271
- res.end(oauthSuccessHtml("OpenAI authentication completed. You can close this window."));
272
- settleWait?.({ code });
273
- }
274
- catch {
275
- res.statusCode = 500;
276
- res.setHeader("Content-Type", "text/html; charset=utf-8");
277
- res.end(oauthErrorHtml("Internal error while processing OAuth callback."));
278
- }
279
- });
280
- return new Promise((resolve) => {
281
- server
282
- .listen(1455, getCallbackHost(), () => {
283
- resolve({
284
- close: () => server.close(),
285
- cancelWait: () => {
286
- settleWait?.(null);
287
- },
288
- waitForCode: () => waitForCodePromise,
289
- });
290
- })
291
- .on("error", (_err) => {
292
- settleWait?.(null);
293
- resolve({
294
- close: () => {
295
- try {
296
- server.close();
297
- }
298
- catch {
299
- // ignore
300
- }
301
- },
302
- cancelWait: () => { },
303
- waitForCode: async () => null,
304
- });
305
- });
306
- });
307
- }
308
229
  function getAccountId(accessToken) {
309
230
  return extractChatGptSubscriptionAccountId(accessToken) ?? null;
310
231
  }
@@ -338,67 +259,42 @@ async function loginChatGptSubscriptionDeviceCode(interaction) {
338
259
  }
339
260
  async function loginChatGptSubscription(interaction) {
340
261
  const { verifier, state, url } = await createAuthorizationFlow();
341
- const server = await startLocalOAuthServer(state);
342
- const manualAbort = new AbortController();
343
- const onAbort = () => server.cancelWait();
344
- interaction.signal.addEventListener("abort", onAbort, { once: true });
345
- if (interaction.signal.aborted)
346
- onAbort();
347
- let code;
348
- let manualCode;
349
- let manualError;
262
+ // Port 1455 is shared with the Codex CLI; when it is taken, fall back to the pasted redirect URL.
263
+ const callback = await startOAuthCallbackServer({
264
+ providerName: "OpenAI",
265
+ host: getCallbackHost(),
266
+ port: 1455,
267
+ path: "/auth/callback",
268
+ state,
269
+ complete: async (code) => code,
270
+ signal: interaction.signal,
271
+ }).catch(() => undefined);
350
272
  interaction.notify({
351
273
  type: "auth_url",
352
274
  url,
353
275
  instructions: "A browser window should open. Complete login to finish.",
354
276
  });
355
277
  try {
356
- const manualPromise = interaction
357
- .prompt({
358
- type: "manual_code",
278
+ const result = await waitForCallbackOrManualInput(interaction, callback, {
359
279
  message: "Complete login in your browser, or paste the authorization code / redirect URL here:",
360
280
  placeholder: REDIRECT_URI,
361
- signal: manualAbort.signal,
362
- })
363
- .then((input) => {
364
- manualCode = input;
365
- server.cancelWait();
366
- })
367
- .catch((error) => {
368
- manualError = error instanceof Error ? error : new Error(String(error));
369
- server.cancelWait();
370
281
  });
371
- const result = await server.waitForCode();
372
- if (manualError)
373
- throw manualError;
374
- if (result?.code) {
375
- code = result.code;
282
+ let code;
283
+ if (result.type === "callback") {
284
+ code = result.value;
376
285
  }
377
- else if (manualCode) {
378
- const parsed = parseAuthorizationInput(manualCode);
286
+ else {
287
+ const parsed = parseAuthorizationInput(result.input);
379
288
  if (parsed.state && parsed.state !== state)
380
289
  throw new Error("State mismatch");
381
290
  code = parsed.code;
382
291
  }
383
- if (!code) {
384
- await manualPromise;
385
- if (manualError)
386
- throw manualError;
387
- if (manualCode) {
388
- const parsed = parseAuthorizationInput(manualCode);
389
- if (parsed.state && parsed.state !== state)
390
- throw new Error("State mismatch");
391
- code = parsed.code;
392
- }
393
- }
394
292
  if (!code)
395
293
  throw new Error("Missing authorization code");
396
- return exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI, interaction.signal);
294
+ return await exchangeAuthorizationCodeForCredentials(code, verifier, REDIRECT_URI, interaction.signal);
397
295
  }
398
296
  finally {
399
- interaction.signal.removeEventListener("abort", onAbort);
400
- manualAbort.abort();
401
- server.close();
297
+ callback?.close();
402
298
  }
403
299
  }
404
300
  /**
@@ -7,9 +7,9 @@
7
7
  * before spending the code, and renders the shared OAuth result pages.
8
8
  */
9
9
  import { createServer } from "node:http";
10
+ import { oauthErrorHtml, oauthSuccessHtml } from "../../utils/oauth-page.js";
10
11
  import { getProviderEnvValue } from "../../utils/provider-env.js";
11
12
  import { exchangeDevinAuthorizationCode } from "./devin-token.js";
12
- import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.js";
13
13
  export const DEVIN_CALLBACK_PORT = 59653;
14
14
  export const DEVIN_CALLBACK_PATH = "/callback";
15
15
  function callbackHost() {
@@ -2,9 +2,11 @@ import type { OAuthAuth } from "../types.ts";
2
2
  type OAuthFlowLoaders = {
3
3
  anthropic: () => OAuthAuth | Promise<OAuthAuth>;
4
4
  chatgptSubscription: () => OAuthAuth | Promise<OAuthAuth>;
5
+ openaiChatGPT: () => OAuthAuth | Promise<OAuthAuth>;
5
6
  githubCopilot: () => OAuthAuth | Promise<OAuthAuth>;
6
7
  openrouter: () => OAuthAuth | Promise<OAuthAuth>;
7
8
  kimiCoding: () => OAuthAuth | Promise<OAuthAuth>;
9
+ meta: () => OAuthAuth | Promise<OAuthAuth>;
8
10
  xai: () => OAuthAuth | Promise<OAuthAuth>;
9
11
  cursor: () => OAuthAuth | Promise<OAuthAuth>;
10
12
  devin: () => OAuthAuth | Promise<OAuthAuth>;
@@ -17,9 +19,11 @@ type OAuthFlowLoaders = {
17
19
  export declare function registerBundledOAuthFlowLoaders(loaders: OAuthFlowLoaders): void;
18
20
  export declare const loadAnthropicOAuth: () => Promise<OAuthAuth>;
19
21
  export declare const loadChatGptSubscriptionOAuth: () => Promise<OAuthAuth>;
22
+ export declare const loadOpenAIChatGPTOAuth: () => Promise<OAuthAuth>;
20
23
  export declare const loadGitHubCopilotOAuth: () => Promise<OAuthAuth>;
21
24
  export declare const loadOpenRouterOAuth: () => Promise<OAuthAuth>;
22
25
  export declare const loadKimiCodingOAuth: () => Promise<OAuthAuth>;
26
+ export declare const loadMetaOAuth: () => Promise<OAuthAuth>;
23
27
  export declare const loadXaiOAuth: () => Promise<OAuthAuth>;
24
28
  export declare const loadCursorOAuth: () => Promise<OAuthAuth>;
25
29
  export declare const loadDevinOAuth: () => Promise<OAuthAuth>;
@@ -32,6 +32,11 @@ export const loadChatGptSubscriptionOAuth = async () => {
32
32
  return (await importOAuthModule("./chatgpt-subscription.ts"))
33
33
  .chatgptSubscriptionOAuth;
34
34
  };
35
+ export const loadOpenAIChatGPTOAuth = async () => {
36
+ if (bundledLoaders)
37
+ return bundledLoaders.openaiChatGPT();
38
+ return (await importOAuthModule("./openai-chatgpt.ts")).openaiChatGPTOAuth;
39
+ };
35
40
  export const loadGitHubCopilotOAuth = async () => {
36
41
  if (bundledLoaders)
37
42
  return bundledLoaders.githubCopilot();
@@ -47,6 +52,11 @@ export const loadKimiCodingOAuth = async () => {
47
52
  return bundledLoaders.kimiCoding();
48
53
  return (await importOAuthModule("./kimi-coding.ts")).kimiCodingOAuth;
49
54
  };
55
+ export const loadMetaOAuth = async () => {
56
+ if (bundledLoaders)
57
+ return bundledLoaders.meta();
58
+ return (await importOAuthModule("./meta.ts")).metaOAuth;
59
+ };
50
60
  export const loadXaiOAuth = async () => {
51
61
  if (bundledLoaders)
52
62
  return bundledLoaders.xai();
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Meta Model API OAuth flow
3
+ *
4
+ * RFC 8628 device authorization grant against https://auth.meta.com (JSON
5
+ * responses). Meta splits identity from API access: the resulting identity
6
+ * token is not accepted for inference, so it is exchanged for a Model API
7
+ * key via the Muse Code key-mint endpoint (minted keys live about a day).
8
+ * The identity token is stored as `refresh` and the minted key as `access`,
9
+ * so the standard OAuth scheduler re-mints the key when it expires with no
10
+ * bespoke renewal machinery. The identity token itself is not renewable
11
+ * (auth.meta.com answers grant_type=refresh_token with 404 and issues no
12
+ * refresh_token), so a 401/403 from mint means the session is dead and the
13
+ * user must sign in again.
14
+ */
15
+ import type { OAuthAuth } from "../types.ts";
16
+ export declare const metaOAuth: OAuthAuth;
17
+ //# sourceMappingURL=meta.d.ts.map
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Meta Model API OAuth flow
3
+ *
4
+ * RFC 8628 device authorization grant against https://auth.meta.com (JSON
5
+ * responses). Meta splits identity from API access: the resulting identity
6
+ * token is not accepted for inference, so it is exchanged for a Model API
7
+ * key via the Muse Code key-mint endpoint (minted keys live about a day).
8
+ * The identity token is stored as `refresh` and the minted key as `access`,
9
+ * so the standard OAuth scheduler re-mints the key when it expires with no
10
+ * bespoke renewal machinery. The identity token itself is not renewable
11
+ * (auth.meta.com answers grant_type=refresh_token with 404 and issues no
12
+ * refresh_token), so a 401/403 from mint means the session is dead and the
13
+ * user must sign in again.
14
+ */
15
+ import { pollOAuthDeviceCodeFlow } from "./device-code.js";
16
+ // Muse Code CLI client id.
17
+ const CLIENT_ID = "1031625952748946";
18
+ const AUTH_HOST = "https://auth.meta.com";
19
+ const DEVICE_AUTHORIZATION_URL = `${AUTH_HOST}/oidc/device/authorization/`;
20
+ const DEVICE_TOKEN_URL = `${AUTH_HOST}/oidc/device/token/`;
21
+ const API_KEY_MINT_URL = "https://api.meta.ai/muse-code/key";
22
+ const API_KEY_LIFETIME_MS = 24 * 60 * 60 * 1000;
23
+ const REQUEST_TIMEOUT_MS = 30 * 1000;
24
+ function requestSignal(signal) {
25
+ return AbortSignal.any([AbortSignal.timeout(REQUEST_TIMEOUT_MS), signal]);
26
+ }
27
+ async function readJson(response) {
28
+ try {
29
+ const json = await response.json();
30
+ return json && typeof json === "object" ? json : null;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ function errorDetail(json) {
37
+ for (const key of ["error_description", "detail", "message", "error"]) {
38
+ const value = json?.[key];
39
+ if (typeof value === "string" && value.trim())
40
+ return `: ${value.trim()}`;
41
+ }
42
+ return "";
43
+ }
44
+ /** The verification URI is opened in the user's browser; only http(s) URLs are trusted. */
45
+ function trustedHttpUrl(value) {
46
+ if (typeof value !== "string" || !value)
47
+ return null;
48
+ try {
49
+ const url = new URL(value);
50
+ if (url.protocol !== "https:" && url.protocol !== "http:")
51
+ return null;
52
+ return url.href;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ function positiveNumber(value) {
59
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
60
+ }
61
+ async function startDeviceAuthorization(signal) {
62
+ const response = await fetch(DEVICE_AUTHORIZATION_URL, {
63
+ method: "POST",
64
+ headers: {
65
+ "Content-Type": "application/x-www-form-urlencoded",
66
+ Accept: "application/json",
67
+ },
68
+ body: new URLSearchParams({ client_id: CLIENT_ID }).toString(),
69
+ signal: requestSignal(signal),
70
+ });
71
+ const json = await readJson(response);
72
+ if (!response.ok) {
73
+ throw new Error(`Meta device authorization failed with status ${response.status}${errorDetail(json)}`);
74
+ }
75
+ const deviceCode = json?.device_code;
76
+ const userCode = json?.user_code;
77
+ const verificationUri = trustedHttpUrl(json?.verification_uri_complete) ?? trustedHttpUrl(json?.verification_uri);
78
+ if (typeof deviceCode !== "string" || !deviceCode || typeof userCode !== "string" || !userCode || !verificationUri) {
79
+ throw new Error(`Invalid Meta device authorization response: ${JSON.stringify(json)}`);
80
+ }
81
+ return {
82
+ deviceCode,
83
+ userCode,
84
+ verificationUri,
85
+ intervalSeconds: positiveNumber(json?.interval),
86
+ expiresInSeconds: positiveNumber(json?.expires_in),
87
+ };
88
+ }
89
+ async function pollForIdentityToken(device, signal) {
90
+ return pollOAuthDeviceCodeFlow({
91
+ intervalSeconds: device.intervalSeconds,
92
+ expiresInSeconds: device.expiresInSeconds,
93
+ waitBeforeFirstPoll: true,
94
+ signal,
95
+ poll: async () => {
96
+ const response = await fetch(DEVICE_TOKEN_URL, {
97
+ method: "POST",
98
+ headers: {
99
+ "Content-Type": "application/x-www-form-urlencoded",
100
+ Accept: "application/json",
101
+ },
102
+ body: new URLSearchParams({
103
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
104
+ device_code: device.deviceCode,
105
+ client_id: CLIENT_ID,
106
+ }).toString(),
107
+ signal: requestSignal(signal),
108
+ });
109
+ const json = await readJson(response);
110
+ if (response.ok && typeof json?.access_token === "string" && json.access_token) {
111
+ return { status: "complete", value: json.access_token };
112
+ }
113
+ switch (json?.error) {
114
+ case "authorization_pending":
115
+ return { status: "pending" };
116
+ case "slow_down":
117
+ return { status: "slow_down", intervalSeconds: positiveNumber(json?.interval) };
118
+ case "access_denied":
119
+ return { status: "failed", message: "Meta login was denied." };
120
+ case "expired_token":
121
+ return { status: "failed", message: "Meta device authorization expired. Please restart login." };
122
+ default:
123
+ return {
124
+ status: "failed",
125
+ message: `Meta device token request failed with status ${response.status}${errorDetail(json)}`,
126
+ };
127
+ }
128
+ },
129
+ });
130
+ }
131
+ /** Exchange an identity token for a Model API key. Keys are valid for about a day. */
132
+ async function mintApiKey(identityToken, signal) {
133
+ const response = await fetch(API_KEY_MINT_URL, {
134
+ method: "POST",
135
+ headers: {
136
+ Accept: "application/json",
137
+ Authorization: `Bearer ${identityToken}`,
138
+ "Content-Type": "application/json",
139
+ "x-api-version": "1.0.0",
140
+ },
141
+ body: "{}",
142
+ signal: requestSignal(signal),
143
+ });
144
+ const json = await readJson(response);
145
+ if (response.status === 401 || response.status === 403) {
146
+ // Identity token is not renewable (see file header); only a fresh device flow helps.
147
+ throw new Error(`Meta session expired (status ${response.status}). Run \`/login meta\` to sign in again.${errorDetail(json)}`);
148
+ }
149
+ if (!response.ok) {
150
+ throw new Error(`Meta API key mint failed with status ${response.status}${errorDetail(json)}`);
151
+ }
152
+ const apiKey = json?.api_key;
153
+ if (typeof apiKey !== "string" || !apiKey) {
154
+ const actionUrl = trustedHttpUrl(json?.action_url);
155
+ throw new Error(`Meta did not issue an API key.${actionUrl ? ` Complete setup at ${actionUrl}` : ""}`);
156
+ }
157
+ return { type: "oauth", refresh: identityToken, access: apiKey, expires: Date.now() + API_KEY_LIFETIME_MS };
158
+ }
159
+ async function loginMeta(interaction) {
160
+ try {
161
+ const device = await startDeviceAuthorization(interaction.signal);
162
+ interaction.notify({
163
+ type: "device_code",
164
+ userCode: device.userCode,
165
+ verificationUri: device.verificationUri,
166
+ intervalSeconds: device.intervalSeconds,
167
+ expiresInSeconds: device.expiresInSeconds,
168
+ });
169
+ const identityToken = await pollForIdentityToken(device, interaction.signal);
170
+ interaction.notify({ type: "progress", message: "Enabling Meta Model API access..." });
171
+ return await mintApiKey(identityToken, interaction.signal);
172
+ }
173
+ catch (e) {
174
+ // An in-flight fetch rejects with a DOMException on abort; the login UI matches on this message.
175
+ if (interaction.signal.aborted)
176
+ throw new Error("Login cancelled");
177
+ throw e;
178
+ }
179
+ }
180
+ export const metaOAuth = {
181
+ name: "Meta (Muse subscription)",
182
+ isSubscription: true,
183
+ loginLabel: "Sign in with Meta",
184
+ login: loginMeta,
185
+ refresh: (credential, signal) => mintApiKey(credential.refresh, signal),
186
+ async toAuth(credential) {
187
+ return { apiKey: credential.access };
188
+ },
189
+ };
190
+ //# sourceMappingURL=meta.js.map