@lenylvt/pi-ai 0.64.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.
- package/README.md +203 -0
- package/dist/api-registry.d.ts +20 -0
- package/dist/api-registry.d.ts.map +1 -0
- package/dist/api-registry.js +44 -0
- package/dist/api-registry.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +119 -0
- package/dist/cli.js.map +1 -0
- package/dist/env-api-keys.d.ts +7 -0
- package/dist/env-api-keys.d.ts.map +1 -0
- package/dist/env-api-keys.js +13 -0
- package/dist/env-api-keys.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +24 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.generated.d.ts +2332 -0
- package/dist/models.generated.d.ts.map +1 -0
- package/dist/models.generated.js +2186 -0
- package/dist/models.generated.js.map +1 -0
- package/dist/models.js +60 -0
- package/dist/models.js.map +1 -0
- package/dist/oauth.d.ts +2 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +2 -0
- package/dist/oauth.js.map +1 -0
- package/dist/providers/anthropic.d.ts +40 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +749 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/faux.d.ts +56 -0
- package/dist/providers/faux.d.ts.map +1 -0
- package/dist/providers/faux.js +367 -0
- package/dist/providers/faux.js.map +1 -0
- package/dist/providers/github-copilot-headers.d.ts +8 -0
- package/dist/providers/github-copilot-headers.d.ts.map +1 -0
- package/dist/providers/github-copilot-headers.js +29 -0
- package/dist/providers/github-copilot-headers.js.map +1 -0
- package/dist/providers/openai-codex-responses.d.ts +9 -0
- package/dist/providers/openai-codex-responses.d.ts.map +1 -0
- package/dist/providers/openai-codex-responses.js +741 -0
- package/dist/providers/openai-codex-responses.js.map +1 -0
- package/dist/providers/openai-completions.d.ts +15 -0
- package/dist/providers/openai-completions.d.ts.map +1 -0
- package/dist/providers/openai-completions.js +687 -0
- package/dist/providers/openai-completions.js.map +1 -0
- package/dist/providers/openai-responses-shared.d.ts +17 -0
- package/dist/providers/openai-responses-shared.d.ts.map +1 -0
- package/dist/providers/openai-responses-shared.js +458 -0
- package/dist/providers/openai-responses-shared.js.map +1 -0
- package/dist/providers/openai-responses.d.ts +13 -0
- package/dist/providers/openai-responses.d.ts.map +1 -0
- package/dist/providers/openai-responses.js +190 -0
- package/dist/providers/openai-responses.js.map +1 -0
- package/dist/providers/register-builtins.d.ts +16 -0
- package/dist/providers/register-builtins.d.ts.map +1 -0
- package/dist/providers/register-builtins.js +140 -0
- package/dist/providers/register-builtins.js.map +1 -0
- package/dist/providers/simple-options.d.ts +8 -0
- package/dist/providers/simple-options.d.ts.map +1 -0
- package/dist/providers/simple-options.js +35 -0
- package/dist/providers/simple-options.js.map +1 -0
- package/dist/providers/transform-messages.d.ts +8 -0
- package/dist/providers/transform-messages.d.ts.map +1 -0
- package/dist/providers/transform-messages.js +155 -0
- package/dist/providers/transform-messages.js.map +1 -0
- package/dist/stream.d.ts +8 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +27 -0
- package/dist/stream.js.map +1 -0
- package/dist/types.d.ts +283 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/event-stream.d.ts +21 -0
- package/dist/utils/event-stream.d.ts.map +1 -0
- package/dist/utils/event-stream.js +81 -0
- package/dist/utils/event-stream.js.map +1 -0
- package/dist/utils/hash.d.ts +3 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/json-parse.d.ts +9 -0
- package/dist/utils/json-parse.d.ts.map +1 -0
- package/dist/utils/json-parse.js +29 -0
- package/dist/utils/json-parse.js.map +1 -0
- package/dist/utils/oauth/anthropic.d.ts +25 -0
- package/dist/utils/oauth/anthropic.d.ts.map +1 -0
- package/dist/utils/oauth/anthropic.js +335 -0
- package/dist/utils/oauth/anthropic.js.map +1 -0
- package/dist/utils/oauth/github-copilot.d.ts +30 -0
- package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
- package/dist/utils/oauth/github-copilot.js +292 -0
- package/dist/utils/oauth/github-copilot.js.map +1 -0
- package/dist/utils/oauth/index.d.ts +36 -0
- package/dist/utils/oauth/index.d.ts.map +1 -0
- package/dist/utils/oauth/index.js +92 -0
- package/dist/utils/oauth/index.js.map +1 -0
- package/dist/utils/oauth/oauth-page.d.ts +3 -0
- package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
- package/dist/utils/oauth/oauth-page.js +105 -0
- package/dist/utils/oauth/oauth-page.js.map +1 -0
- package/dist/utils/oauth/openai-codex.d.ts +34 -0
- package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
- package/dist/utils/oauth/openai-codex.js +373 -0
- package/dist/utils/oauth/openai-codex.js.map +1 -0
- package/dist/utils/oauth/pkce.d.ts +13 -0
- package/dist/utils/oauth/pkce.d.ts.map +1 -0
- package/dist/utils/oauth/pkce.js +31 -0
- package/dist/utils/oauth/pkce.js.map +1 -0
- package/dist/utils/oauth/types.d.ts +47 -0
- package/dist/utils/oauth/types.d.ts.map +1 -0
- package/dist/utils/oauth/types.js +2 -0
- package/dist/utils/oauth/types.js.map +1 -0
- package/dist/utils/overflow.d.ts +53 -0
- package/dist/utils/overflow.d.ts.map +1 -0
- package/dist/utils/overflow.js +119 -0
- package/dist/utils/overflow.js.map +1 -0
- package/dist/utils/sanitize-unicode.d.ts +22 -0
- package/dist/utils/sanitize-unicode.d.ts.map +1 -0
- package/dist/utils/sanitize-unicode.js +26 -0
- package/dist/utils/sanitize-unicode.js.map +1 -0
- package/dist/utils/typebox-helpers.d.ts +17 -0
- package/dist/utils/typebox-helpers.d.ts.map +1 -0
- package/dist/utils/typebox-helpers.js +21 -0
- package/dist/utils/typebox-helpers.js.map +1 -0
- package/dist/utils/validation.d.ts +18 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +80 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +89 -0
- package/src/api-registry.ts +98 -0
- package/src/cli.ts +136 -0
- package/src/env-api-keys.ts +22 -0
- package/src/index.ts +29 -0
- package/src/models.generated.ts +2188 -0
- package/src/models.ts +82 -0
- package/src/oauth.ts +1 -0
- package/src/providers/anthropic.ts +905 -0
- package/src/providers/faux.ts +498 -0
- package/src/providers/github-copilot-headers.ts +37 -0
- package/src/providers/openai-codex-responses.ts +929 -0
- package/src/providers/openai-completions.ts +811 -0
- package/src/providers/openai-responses-shared.ts +513 -0
- package/src/providers/openai-responses.ts +251 -0
- package/src/providers/register-builtins.ts +232 -0
- package/src/providers/simple-options.ts +46 -0
- package/src/providers/transform-messages.ts +172 -0
- package/src/stream.ts +59 -0
- package/src/types.ts +294 -0
- package/src/utils/event-stream.ts +87 -0
- package/src/utils/hash.ts +13 -0
- package/src/utils/json-parse.ts +28 -0
- package/src/utils/oauth/anthropic.ts +402 -0
- package/src/utils/oauth/github-copilot.ts +396 -0
- package/src/utils/oauth/index.ts +123 -0
- package/src/utils/oauth/oauth-page.ts +109 -0
- package/src/utils/oauth/openai-codex.ts +450 -0
- package/src/utils/oauth/pkce.ts +34 -0
- package/src/utils/oauth/types.ts +59 -0
- package/src/utils/overflow.ts +125 -0
- package/src/utils/sanitize-unicode.ts +25 -0
- package/src/utils/typebox-helpers.ts +24 -0
- package/src/utils/validation.ts +93 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic OAuth flow (Claude Pro/Max)
|
|
3
|
+
*
|
|
4
|
+
* NOTE: This module uses Node.js http.createServer for the OAuth callback server.
|
|
5
|
+
* It is only intended for CLI use, not browser environments.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Server } from "node:http";
|
|
9
|
+
import { oauthErrorHtml, oauthSuccessHtml } from "./oauth-page.js";
|
|
10
|
+
import { generatePKCE } from "./pkce.js";
|
|
11
|
+
import type { OAuthCredentials, OAuthLoginCallbacks, OAuthPrompt, OAuthProviderInterface } from "./types.js";
|
|
12
|
+
|
|
13
|
+
type CallbackServerInfo = {
|
|
14
|
+
server: Server;
|
|
15
|
+
redirectUri: string;
|
|
16
|
+
cancelWait: () => void;
|
|
17
|
+
waitForCode: () => Promise<{ code: string; state: string } | null>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type NodeApis = {
|
|
21
|
+
createServer: typeof import("node:http").createServer;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let nodeApis: NodeApis | null = null;
|
|
25
|
+
let nodeApisPromise: Promise<NodeApis> | null = null;
|
|
26
|
+
|
|
27
|
+
const decode = (s: string) => atob(s);
|
|
28
|
+
const CLIENT_ID = decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
29
|
+
const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
|
|
30
|
+
const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
31
|
+
const CALLBACK_HOST = "127.0.0.1";
|
|
32
|
+
const CALLBACK_PORT = 53692;
|
|
33
|
+
const CALLBACK_PATH = "/callback";
|
|
34
|
+
const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
|
|
35
|
+
const SCOPES =
|
|
36
|
+
"org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
37
|
+
async function getNodeApis(): Promise<NodeApis> {
|
|
38
|
+
if (nodeApis) return nodeApis;
|
|
39
|
+
if (!nodeApisPromise) {
|
|
40
|
+
if (typeof process === "undefined" || (!process.versions?.node && !process.versions?.bun)) {
|
|
41
|
+
throw new Error("Anthropic OAuth is only available in Node.js environments");
|
|
42
|
+
}
|
|
43
|
+
nodeApisPromise = import("node:http").then((httpModule) => ({
|
|
44
|
+
createServer: httpModule.createServer,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
nodeApis = await nodeApisPromise;
|
|
48
|
+
return nodeApis;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseAuthorizationInput(input: string): { code?: string; state?: string } {
|
|
52
|
+
const value = input.trim();
|
|
53
|
+
if (!value) return {};
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const url = new URL(value);
|
|
57
|
+
return {
|
|
58
|
+
code: url.searchParams.get("code") ?? undefined,
|
|
59
|
+
state: url.searchParams.get("state") ?? undefined,
|
|
60
|
+
};
|
|
61
|
+
} catch {
|
|
62
|
+
// not a URL
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (value.includes("#")) {
|
|
66
|
+
const [code, state] = value.split("#", 2);
|
|
67
|
+
return { code, state };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (value.includes("code=")) {
|
|
71
|
+
const params = new URLSearchParams(value);
|
|
72
|
+
return {
|
|
73
|
+
code: params.get("code") ?? undefined,
|
|
74
|
+
state: params.get("state") ?? undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { code: value };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatErrorDetails(error: unknown): string {
|
|
82
|
+
if (error instanceof Error) {
|
|
83
|
+
const details: string[] = [`${error.name}: ${error.message}`];
|
|
84
|
+
const errorWithCode = error as Error & { code?: string; errno?: number | string; cause?: unknown };
|
|
85
|
+
if (errorWithCode.code) details.push(`code=${errorWithCode.code}`);
|
|
86
|
+
if (typeof errorWithCode.errno !== "undefined") details.push(`errno=${String(errorWithCode.errno)}`);
|
|
87
|
+
if (typeof error.cause !== "undefined") {
|
|
88
|
+
details.push(`cause=${formatErrorDetails(error.cause)}`);
|
|
89
|
+
}
|
|
90
|
+
if (error.stack) {
|
|
91
|
+
details.push(`stack=${error.stack}`);
|
|
92
|
+
}
|
|
93
|
+
return details.join("; ");
|
|
94
|
+
}
|
|
95
|
+
return String(error);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function startCallbackServer(expectedState: string): Promise<CallbackServerInfo> {
|
|
99
|
+
const { createServer } = await getNodeApis();
|
|
100
|
+
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
let settleWait: ((value: { code: string; state: string } | null) => void) | undefined;
|
|
103
|
+
const waitForCodePromise = new Promise<{ code: string; state: string } | null>((resolveWait) => {
|
|
104
|
+
let settled = false;
|
|
105
|
+
settleWait = (value) => {
|
|
106
|
+
if (settled) return;
|
|
107
|
+
settled = true;
|
|
108
|
+
resolveWait(value);
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const server = createServer((req, res) => {
|
|
113
|
+
try {
|
|
114
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
115
|
+
if (url.pathname !== CALLBACK_PATH) {
|
|
116
|
+
res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
|
|
117
|
+
res.end(oauthErrorHtml("Callback route not found."));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const code = url.searchParams.get("code");
|
|
122
|
+
const state = url.searchParams.get("state");
|
|
123
|
+
const error = url.searchParams.get("error");
|
|
124
|
+
|
|
125
|
+
if (error) {
|
|
126
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
127
|
+
res.end(oauthErrorHtml("Anthropic authentication did not complete.", `Error: ${error}`));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!code || !state) {
|
|
132
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
133
|
+
res.end(oauthErrorHtml("Missing code or state parameter."));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (state !== expectedState) {
|
|
138
|
+
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
139
|
+
res.end(oauthErrorHtml("State mismatch."));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
144
|
+
res.end(oauthSuccessHtml("Anthropic authentication completed. You can close this window."));
|
|
145
|
+
settleWait?.({ code, state });
|
|
146
|
+
} catch {
|
|
147
|
+
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
148
|
+
res.end("Internal error");
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
server.on("error", (err) => {
|
|
153
|
+
reject(err);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
|
|
157
|
+
resolve({
|
|
158
|
+
server,
|
|
159
|
+
redirectUri: REDIRECT_URI,
|
|
160
|
+
cancelWait: () => {
|
|
161
|
+
settleWait?.(null);
|
|
162
|
+
},
|
|
163
|
+
waitForCode: () => waitForCodePromise,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function postJson(url: string, body: Record<string, string | number>): Promise<string> {
|
|
170
|
+
const response = await fetch(url, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
Accept: "application/json",
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify(body),
|
|
177
|
+
signal: AbortSignal.timeout(30_000),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const responseBody = await response.text();
|
|
181
|
+
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return responseBody;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function exchangeAuthorizationCode(
|
|
190
|
+
code: string,
|
|
191
|
+
state: string,
|
|
192
|
+
verifier: string,
|
|
193
|
+
redirectUri: string,
|
|
194
|
+
): Promise<OAuthCredentials> {
|
|
195
|
+
let responseBody: string;
|
|
196
|
+
try {
|
|
197
|
+
responseBody = await postJson(TOKEN_URL, {
|
|
198
|
+
grant_type: "authorization_code",
|
|
199
|
+
client_id: CLIENT_ID,
|
|
200
|
+
code,
|
|
201
|
+
state,
|
|
202
|
+
redirect_uri: redirectUri,
|
|
203
|
+
code_verifier: verifier,
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let tokenData: { access_token: string; refresh_token: string; expires_in: number };
|
|
212
|
+
try {
|
|
213
|
+
tokenData = JSON.parse(responseBody) as { access_token: string; refresh_token: string; expires_in: number };
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
refresh: tokenData.refresh_token,
|
|
222
|
+
access: tokenData.access_token,
|
|
223
|
+
expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Login with Anthropic OAuth (authorization code + PKCE)
|
|
229
|
+
*/
|
|
230
|
+
export async function loginAnthropic(options: {
|
|
231
|
+
onAuth: (info: { url: string; instructions?: string }) => void;
|
|
232
|
+
onPrompt: (prompt: OAuthPrompt) => Promise<string>;
|
|
233
|
+
onProgress?: (message: string) => void;
|
|
234
|
+
onManualCodeInput?: () => Promise<string>;
|
|
235
|
+
}): Promise<OAuthCredentials> {
|
|
236
|
+
const { verifier, challenge } = await generatePKCE();
|
|
237
|
+
const server = await startCallbackServer(verifier);
|
|
238
|
+
|
|
239
|
+
let code: string | undefined;
|
|
240
|
+
let state: string | undefined;
|
|
241
|
+
let redirectUriForExchange = REDIRECT_URI;
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const authParams = new URLSearchParams({
|
|
245
|
+
code: "true",
|
|
246
|
+
client_id: CLIENT_ID,
|
|
247
|
+
response_type: "code",
|
|
248
|
+
redirect_uri: REDIRECT_URI,
|
|
249
|
+
scope: SCOPES,
|
|
250
|
+
code_challenge: challenge,
|
|
251
|
+
code_challenge_method: "S256",
|
|
252
|
+
state: verifier,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
options.onAuth({
|
|
256
|
+
url: `${AUTHORIZE_URL}?${authParams.toString()}`,
|
|
257
|
+
instructions:
|
|
258
|
+
"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.",
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (options.onManualCodeInput) {
|
|
262
|
+
let manualInput: string | undefined;
|
|
263
|
+
let manualError: Error | undefined;
|
|
264
|
+
const manualPromise = options
|
|
265
|
+
.onManualCodeInput()
|
|
266
|
+
.then((input) => {
|
|
267
|
+
manualInput = input;
|
|
268
|
+
server.cancelWait();
|
|
269
|
+
})
|
|
270
|
+
.catch((err) => {
|
|
271
|
+
manualError = err instanceof Error ? err : new Error(String(err));
|
|
272
|
+
server.cancelWait();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
const result = await server.waitForCode();
|
|
276
|
+
|
|
277
|
+
if (manualError) {
|
|
278
|
+
throw manualError;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (result?.code) {
|
|
282
|
+
code = result.code;
|
|
283
|
+
state = result.state;
|
|
284
|
+
redirectUriForExchange = REDIRECT_URI;
|
|
285
|
+
} else if (manualInput) {
|
|
286
|
+
const parsed = parseAuthorizationInput(manualInput);
|
|
287
|
+
if (parsed.state && parsed.state !== verifier) {
|
|
288
|
+
throw new Error("OAuth state mismatch");
|
|
289
|
+
}
|
|
290
|
+
code = parsed.code;
|
|
291
|
+
state = parsed.state ?? verifier;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!code) {
|
|
295
|
+
await manualPromise;
|
|
296
|
+
if (manualError) {
|
|
297
|
+
throw manualError;
|
|
298
|
+
}
|
|
299
|
+
if (manualInput) {
|
|
300
|
+
const parsed = parseAuthorizationInput(manualInput);
|
|
301
|
+
if (parsed.state && parsed.state !== verifier) {
|
|
302
|
+
throw new Error("OAuth state mismatch");
|
|
303
|
+
}
|
|
304
|
+
code = parsed.code;
|
|
305
|
+
state = parsed.state ?? verifier;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
const result = await server.waitForCode();
|
|
310
|
+
if (result?.code) {
|
|
311
|
+
code = result.code;
|
|
312
|
+
state = result.state;
|
|
313
|
+
redirectUriForExchange = REDIRECT_URI;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!code) {
|
|
318
|
+
const input = await options.onPrompt({
|
|
319
|
+
message: "Paste the authorization code or full redirect URL:",
|
|
320
|
+
placeholder: REDIRECT_URI,
|
|
321
|
+
});
|
|
322
|
+
const parsed = parseAuthorizationInput(input);
|
|
323
|
+
if (parsed.state && parsed.state !== verifier) {
|
|
324
|
+
throw new Error("OAuth state mismatch");
|
|
325
|
+
}
|
|
326
|
+
code = parsed.code;
|
|
327
|
+
state = parsed.state ?? verifier;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (!code) {
|
|
331
|
+
throw new Error("Missing authorization code");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!state) {
|
|
335
|
+
throw new Error("Missing OAuth state");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
options.onProgress?.("Exchanging authorization code for tokens...");
|
|
339
|
+
return exchangeAuthorizationCode(code, state, verifier, redirectUriForExchange);
|
|
340
|
+
} finally {
|
|
341
|
+
server.server.close();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Refresh Anthropic OAuth token
|
|
347
|
+
*/
|
|
348
|
+
export async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {
|
|
349
|
+
let responseBody: string;
|
|
350
|
+
try {
|
|
351
|
+
responseBody = await postJson(TOKEN_URL, {
|
|
352
|
+
grant_type: "refresh_token",
|
|
353
|
+
client_id: CLIENT_ID,
|
|
354
|
+
refresh_token: refreshToken,
|
|
355
|
+
});
|
|
356
|
+
} catch (error) {
|
|
357
|
+
throw new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let data: { access_token: string; refresh_token: string; expires_in: number; scope?: string };
|
|
361
|
+
try {
|
|
362
|
+
data = JSON.parse(responseBody) as {
|
|
363
|
+
access_token: string;
|
|
364
|
+
refresh_token: string;
|
|
365
|
+
expires_in: number;
|
|
366
|
+
scope?: string;
|
|
367
|
+
};
|
|
368
|
+
} catch (error) {
|
|
369
|
+
throw new Error(
|
|
370
|
+
`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`,
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
refresh: data.refresh_token,
|
|
376
|
+
access: data.access_token,
|
|
377
|
+
expires: Date.now() + data.expires_in * 1000 - 5 * 60 * 1000,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export const anthropicOAuthProvider: OAuthProviderInterface = {
|
|
382
|
+
id: "anthropic",
|
|
383
|
+
name: "Anthropic (Claude Pro/Max)",
|
|
384
|
+
usesCallbackServer: true,
|
|
385
|
+
|
|
386
|
+
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
387
|
+
return loginAnthropic({
|
|
388
|
+
onAuth: callbacks.onAuth,
|
|
389
|
+
onPrompt: callbacks.onPrompt,
|
|
390
|
+
onProgress: callbacks.onProgress,
|
|
391
|
+
onManualCodeInput: callbacks.onManualCodeInput,
|
|
392
|
+
});
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
|
396
|
+
return refreshAnthropicToken(credentials.refresh);
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
getApiKey(credentials: OAuthCredentials): string {
|
|
400
|
+
return credentials.access;
|
|
401
|
+
},
|
|
402
|
+
};
|