@code-yeongyu/senpi-ai 2026.9.30 → 2026.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +189 -19
- package/dist/api/anthropic-messages.js +168 -32
- package/dist/api/azure-openai-responses.js +23 -10
- package/dist/api/bedrock-converse-stream.js +12 -4
- package/dist/api/cloudflare-workers-ai-system-one.d.ts +4 -0
- package/dist/api/cloudflare-workers-ai-system-one.js +43 -0
- package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts +3 -0
- package/dist/api/cloudflare-workers-ai-system-one.lazy.js +4 -0
- package/dist/api/cloudflare.d.ts +2 -0
- package/dist/api/cloudflare.js +2 -0
- package/dist/api/context-room.d.ts +2 -2
- package/dist/api/cursor-agent.js +19 -10
- package/dist/api/devin-agent/request.d.ts +9 -9
- package/dist/api/devin-agent/request.js +14 -10
- package/dist/api/google-generative-ai.js +20 -80
- package/dist/api/google-shared.d.ts +13 -4
- package/dist/api/google-shared.js +54 -4
- package/dist/api/google-vertex.js +19 -62
- package/dist/api/llama-cpp-classify.d.ts +33 -0
- package/dist/api/llama-cpp-classify.js +365 -0
- package/dist/api/llama-cpp-classify.lazy.d.ts +3 -0
- package/dist/api/llama-cpp-classify.lazy.js +4 -0
- package/dist/api/mistral-conversations.d.ts +1 -1
- package/dist/api/mistral-conversations.js +36 -29
- package/dist/api/openai-codex-responses.d.ts +1 -1
- package/dist/api/openai-codex-responses.js +72 -37
- package/dist/api/openai-completions.d.ts +3 -2
- package/dist/api/openai-completions.js +76 -50
- package/dist/api/openai-images-params.d.ts +2 -2
- package/dist/api/openai-images.d.ts +1 -1
- package/dist/api/openai-responses-shared.d.ts +31 -8
- package/dist/api/openai-responses-shared.js +115 -27
- package/dist/api/openai-responses.d.ts +1 -1
- package/dist/api/openai-responses.js +38 -33
- package/dist/api/openrouter-images.d.ts +2 -1
- package/dist/api/openrouter-images.js +1 -0
- package/dist/api/pi-messages.d.ts +3 -3
- package/dist/api/pi-messages.js +3 -2
- package/dist/api/simple-options.d.ts +2 -2
- package/dist/api/simple-options.js +1 -0
- package/dist/api/system-one-shared.d.ts +23 -0
- package/dist/api/system-one-shared.js +183 -0
- package/dist/api/transform-messages.js +5 -2
- package/dist/api/typesafe-system-one.d.ts +4 -0
- package/dist/api/typesafe-system-one.js +19 -0
- package/dist/api/typesafe-system-one.lazy.d.ts +3 -0
- package/dist/api/typesafe-system-one.lazy.js +4 -0
- package/dist/api-registry.d.ts +3 -3
- package/dist/auth/helpers.js +1 -1
- package/dist/auth/oauth/anthropic-callback-listener.js +1 -1
- package/dist/auth/oauth/callback-server.d.ts +55 -0
- package/dist/auth/oauth/callback-server.js +146 -0
- package/dist/auth/oauth/chatgpt-subscription.d.ts +1 -1
- package/dist/auth/oauth/chatgpt-subscription.js +20 -124
- package/dist/auth/oauth/devin-callback.js +1 -1
- package/dist/auth/oauth/load.d.ts +4 -0
- package/dist/auth/oauth/load.js +10 -0
- package/dist/auth/oauth/meta.d.ts +17 -0
- package/dist/auth/oauth/meta.js +190 -0
- package/dist/auth/oauth/openai-chatgpt.d.ts +9 -0
- package/dist/auth/oauth/openai-chatgpt.js +266 -0
- package/dist/auth/oauth/openrouter.d.ts +1 -1
- package/dist/auth/oauth/openrouter.js +19 -138
- package/dist/auth/oauth/radius.d.ts +1 -1
- package/dist/auth/oauth/radius.js +21 -89
- package/dist/auth/resolve.d.ts +3 -8
- package/dist/auth/resolve.js +3 -18
- package/dist/auth/types.d.ts +10 -1
- package/dist/bun-oauth.js +4 -0
- package/dist/cli.js +3 -1
- package/dist/compat.js +17 -14
- package/dist/env-api-keys.js +2 -0
- package/dist/image-models.d.ts +19 -8
- package/dist/image-models.js +14 -13
- package/dist/images-api-registry.d.ts +8 -8
- package/dist/images.d.ts +7 -2
- package/dist/images.js +5 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/model-catalog.d.ts +27 -9
- package/dist/model-catalog.js +24 -2
- package/dist/model.d.ts +13 -13
- package/dist/models-store.d.ts +3 -2
- package/dist/models.d.ts +106 -36
- package/dist/models.generated.d.ts +142 -42
- package/dist/models.generated.js +142 -42
- package/dist/models.js +174 -36
- package/dist/providers/alibaba-token-plan.models.d.ts +4 -2
- package/dist/providers/alibaba-token-plan.models.js +4 -2
- package/dist/providers/all.d.ts +72 -19
- package/dist/providers/all.js +28 -22
- package/dist/providers/amazon-bedrock.models.d.ts +4 -2
- package/dist/providers/amazon-bedrock.models.js +4 -2
- package/dist/providers/ant-ling.models.d.ts +4 -2
- package/dist/providers/ant-ling.models.js +4 -2
- package/dist/providers/anthropic.models.d.ts +4 -2
- package/dist/providers/anthropic.models.js +4 -2
- package/dist/providers/azure-openai-responses.models.d.ts +4 -2
- package/dist/providers/azure-openai-responses.models.js +4 -2
- package/dist/providers/bai.models.d.ts +4 -2
- package/dist/providers/bai.models.js +4 -2
- package/dist/providers/baseten.models.d.ts +4 -2
- package/dist/providers/baseten.models.js +4 -2
- package/dist/providers/cerebras.models.d.ts +4 -2
- package/dist/providers/cerebras.models.js +4 -2
- package/dist/providers/chatgpt-subscription.models.d.ts +4 -2
- package/dist/providers/chatgpt-subscription.models.js +4 -2
- package/dist/providers/cloudflare-ai-gateway.models.d.ts +4 -2
- package/dist/providers/cloudflare-ai-gateway.models.js +4 -2
- package/dist/providers/cloudflare-stream.d.ts +6 -2
- package/dist/providers/cloudflare-stream.js +6 -0
- package/dist/providers/cloudflare-workers-ai.js +10 -3
- package/dist/providers/cloudflare-workers-ai.models.d.ts +4 -2
- package/dist/providers/cloudflare-workers-ai.models.js +4 -2
- package/dist/providers/data/.manifest.json +1 -1
- package/dist/providers/data/alibaba-token-plan.json +1 -1
- package/dist/providers/data/amazon-bedrock.json +1 -1
- package/dist/providers/data/ant-ling.json +1 -1
- package/dist/providers/data/anthropic.json +1 -1
- package/dist/providers/data/azure-openai-responses.json +1 -1
- package/dist/providers/data/bai.json +1 -1
- package/dist/providers/data/baseten.json +1 -1
- package/dist/providers/data/cerebras.json +1 -1
- package/dist/providers/data/chatgpt-subscription.json +1 -1
- package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/dist/providers/data/deepseek.json +1 -1
- package/dist/providers/data/fireworks.json +1 -1
- package/dist/providers/data/github-copilot.json +1 -1
- package/dist/providers/data/google-vertex.json +1 -1
- package/dist/providers/data/google.json +1 -1
- package/dist/providers/data/groq.json +1 -1
- package/dist/providers/data/huggingface.json +1 -1
- package/dist/providers/data/meta.json +1 -0
- package/dist/providers/data/minimax-cn.json +1 -1
- package/dist/providers/data/minimax.json +1 -1
- package/dist/providers/data/mistral.json +1 -1
- package/dist/providers/data/moonshotai-cn.json +1 -1
- package/dist/providers/data/moonshotai.json +1 -1
- package/dist/providers/data/nvidia.json +1 -1
- package/dist/providers/data/openai.json +1 -1
- package/dist/providers/data/opencode-go.json +1 -1
- package/dist/providers/data/opencode.json +1 -1
- package/dist/providers/data/opengateway.json +1 -1
- package/dist/providers/data/openrouter.json +1 -1
- package/dist/providers/data/qwen-token-plan-cn.json +1 -1
- package/dist/providers/data/qwen-token-plan-individual.json +1 -1
- package/dist/providers/data/qwen-token-plan.json +1 -1
- package/dist/providers/data/radius.json +1 -0
- package/dist/providers/data/together.json +1 -1
- package/dist/providers/data/typesafe.json +1 -0
- package/dist/providers/data/venice.json +1 -1
- package/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/dist/providers/data/xai.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
- package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
- package/dist/providers/data/xiaomi.json +1 -1
- package/dist/providers/data/zai-coding-cn.json +1 -1
- package/dist/providers/data/zai.json +1 -1
- package/dist/providers/deepseek.models.d.ts +4 -2
- package/dist/providers/deepseek.models.js +4 -2
- package/dist/providers/faux.d.ts +7 -2
- package/dist/providers/faux.js +31 -22
- package/dist/providers/fireworks.models.d.ts +4 -2
- package/dist/providers/fireworks.models.js +4 -2
- package/dist/providers/github-copilot.models.d.ts +4 -2
- package/dist/providers/github-copilot.models.js +4 -2
- package/dist/providers/google-vertex.models.d.ts +4 -2
- package/dist/providers/google-vertex.models.js +4 -2
- package/dist/providers/google.models.d.ts +4 -2
- package/dist/providers/google.models.js +4 -2
- package/dist/providers/groq.models.d.ts +4 -2
- package/dist/providers/groq.models.js +4 -2
- package/dist/providers/huggingface.models.d.ts +4 -2
- package/dist/providers/huggingface.models.js +4 -2
- package/dist/providers/images/register-builtins.d.ts +2 -2
- package/dist/providers/kimi-coding.models.d.ts +54 -7
- package/dist/providers/kimi-coding.models.js +34 -7
- package/dist/providers/meta.d.ts +3 -0
- package/dist/providers/meta.js +24 -0
- package/dist/providers/meta.models.d.ts +6 -0
- package/dist/providers/meta.models.js +8 -0
- package/dist/providers/minimax-cn.models.d.ts +4 -2
- package/dist/providers/minimax-cn.models.js +4 -2
- package/dist/providers/minimax.models.d.ts +4 -2
- package/dist/providers/minimax.models.js +4 -2
- package/dist/providers/mistral.models.d.ts +4 -2
- package/dist/providers/mistral.models.js +4 -2
- package/dist/providers/moonshotai-cn.models.d.ts +4 -2
- package/dist/providers/moonshotai-cn.models.js +4 -2
- package/dist/providers/moonshotai.models.d.ts +4 -2
- package/dist/providers/moonshotai.models.js +4 -2
- package/dist/providers/nvidia.models.d.ts +4 -2
- package/dist/providers/nvidia.models.js +4 -2
- package/dist/providers/openai.js +4 -2
- package/dist/providers/openai.models.d.ts +4 -2
- package/dist/providers/openai.models.js +4 -2
- package/dist/providers/opencode-go.models.d.ts +4 -2
- package/dist/providers/opencode-go.models.js +4 -2
- package/dist/providers/opencode.d.ts +3 -1
- package/dist/providers/opencode.js +5 -2
- package/dist/providers/opencode.models.d.ts +4 -2
- package/dist/providers/opencode.models.js +4 -2
- package/dist/providers/opengateway.models.d.ts +4 -2
- package/dist/providers/opengateway.models.js +4 -2
- package/dist/providers/openrouter.js +11 -2
- package/dist/providers/openrouter.models.d.ts +4 -2
- package/dist/providers/openrouter.models.js +4 -2
- package/dist/providers/qwen-token-plan-cn.models.d.ts +4 -2
- package/dist/providers/qwen-token-plan-cn.models.js +4 -2
- package/dist/providers/qwen-token-plan-individual.models.d.ts +4 -2
- package/dist/providers/qwen-token-plan-individual.models.js +4 -2
- package/dist/providers/qwen-token-plan.models.d.ts +4 -2
- package/dist/providers/qwen-token-plan.models.js +4 -2
- package/dist/providers/radius.js +19 -5
- package/dist/providers/radius.models.d.ts +6 -0
- package/dist/providers/radius.models.js +8 -0
- package/dist/providers/together.models.d.ts +4 -2
- package/dist/providers/together.models.js +4 -2
- package/dist/providers/typesafe.d.ts +3 -0
- package/dist/providers/typesafe.js +18 -0
- package/dist/providers/typesafe.models.d.ts +6 -0
- package/dist/providers/typesafe.models.js +8 -0
- package/dist/providers/venice.models.d.ts +4 -2
- package/dist/providers/venice.models.js +4 -2
- package/dist/providers/vercel-ai-gateway.js +5 -2
- package/dist/providers/vercel-ai-gateway.models.d.ts +4 -2
- package/dist/providers/vercel-ai-gateway.models.js +4 -2
- package/dist/providers/xai.models.d.ts +4 -2
- package/dist/providers/xai.models.js +4 -2
- package/dist/providers/xiaomi-token-plan-ams.models.d.ts +4 -2
- package/dist/providers/xiaomi-token-plan-ams.models.js +4 -2
- package/dist/providers/xiaomi-token-plan-cn.models.d.ts +4 -2
- package/dist/providers/xiaomi-token-plan-cn.models.js +4 -2
- package/dist/providers/xiaomi-token-plan-sgp.models.d.ts +4 -2
- package/dist/providers/xiaomi-token-plan-sgp.models.js +4 -2
- package/dist/providers/xiaomi.models.d.ts +4 -2
- package/dist/providers/xiaomi.models.js +4 -2
- package/dist/providers/zai-coding-cn.models.d.ts +4 -2
- package/dist/providers/zai-coding-cn.models.js +4 -2
- package/dist/providers/zai.models.d.ts +4 -2
- package/dist/providers/zai.models.js +4 -2
- package/dist/tool-call-middleware/context-transformer.d.ts +8 -5
- package/dist/tool-call-middleware/context-transformer.js +44 -15
- package/dist/types.d.ts +262 -28
- package/dist/utils/diagnostics.d.ts +3 -2
- package/dist/utils/estimate.d.ts +2 -2
- package/dist/utils/estimate.js +22 -30
- package/dist/utils/headers.d.ts +1 -1
- package/dist/utils/headers.js +10 -8
- package/dist/utils/model-operations.d.ts +11 -0
- package/dist/utils/model-operations.js +47 -0
- package/dist/utils/models-error.d.ts +8 -0
- package/dist/utils/models-error.js +18 -0
- package/dist/utils/overflow.d.ts +1 -0
- package/dist/utils/overflow.js +11 -5
- package/dist/utils/prompt-cache-ttl.js +10 -3
- package/dist/utils/retry.js +9 -0
- package/dist/utils/text.d.ts +9 -1
- package/dist/utils/text.js +26 -0
- package/dist/utils/transcript.d.ts +85 -0
- package/dist/utils/transcript.js +205 -0
- package/package.json +3 -4
- package/dist/image-models.generated.d.ts +0 -925
- package/dist/image-models.generated.js +0 -927
- package/dist/images-models.d.ts +0 -95
- package/dist/images-models.js +0 -141
- package/dist/providers/openai-images.d.ts +0 -3
- package/dist/providers/openai-images.js +0 -16
- package/dist/providers/openrouter-images.d.ts +0 -3
- package/dist/providers/openrouter-images.js +0 -22
- /package/dist/{auth/oauth → utils}/oauth-page.d.ts +0 -0
- /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
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
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
|
-
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
if (result?.code) {
|
|
375
|
-
code = result.code;
|
|
282
|
+
let code;
|
|
283
|
+
if (result.type === "callback") {
|
|
284
|
+
code = result.value;
|
|
376
285
|
}
|
|
377
|
-
else
|
|
378
|
-
const parsed = parseAuthorizationInput(
|
|
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
|
-
|
|
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>;
|
package/dist/auth/oauth/load.js
CHANGED
|
@@ -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
|