piclaw 0.0.20 → 0.0.21
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/.output/nitro.json +1 -1
- package/.output/public/assets/defult-D5RLDUrI.js +1 -0
- package/.output/public/assets/{dist-D-Hc5HbQ.js → dist-BH_oa-kv.js} +1 -1
- package/.output/public/assets/index-7JvURuHy.js +204 -0
- package/.output/public/assets/index-K43slwjJ.css +1 -0
- package/.output/public/index.html +11 -2
- package/.output/server/_chunks/app.mjs +138 -104
- package/.output/server/_chunks/config.mjs +4 -0
- package/.output/server/_chunks/dummy.mjs +1 -1
- package/.output/server/_chunks/logger.mjs +1 -1
- package/.output/server/_chunks/notes.mjs +1 -3
- package/.output/server/_chunks/renderer-template.mjs +1 -1
- package/.output/server/_chunks/sandbox.mjs +217 -0
- package/.output/server/_chunks/server.mjs +411 -291
- package/.output/server/_chunks/terminal.mjs +47 -8
- package/.output/server/_chunks/virtual.mjs +192 -54
- package/.output/server/_id_.delete.mjs +5 -2
- package/.output/server/_id_2.delete.mjs +8 -0
- package/.output/server/_jid_.delete.mjs +0 -1
- package/.output/server/_jid_.patch.mjs +21 -3
- package/.output/server/_jid_2.delete.mjs +0 -1
- package/.output/server/_libs/@acemir/cssom+[...].mjs +2269 -1137
- package/.output/server/_libs/@google/genai.mjs +348 -284
- package/.output/server/_libs/@mariozechner/pi-agent-core+[...].mjs +381 -2073
- package/.output/server/_libs/@mariozechner/pi-coding-agent+[...].mjs +236 -136
- package/.output/server/_libs/_.mjs +3 -2
- package/.output/server/_libs/_10.mjs +2 -4
- package/.output/server/_libs/_11.mjs +2 -4
- package/.output/server/_libs/_12.mjs +2 -3
- package/.output/server/_libs/_13.mjs +2 -3
- package/.output/server/_libs/_14.mjs +2 -4
- package/.output/server/_libs/_15.mjs +2 -4
- package/.output/server/_libs/_16.mjs +2 -3
- package/.output/server/_libs/_17.mjs +2 -4
- package/.output/server/_libs/_18.mjs +2 -2
- package/.output/server/_libs/_19.mjs +2 -2
- package/.output/server/_libs/_2.mjs +3 -3
- package/.output/server/_libs/_20.mjs +2 -2
- package/.output/server/_libs/_21.mjs +2 -2
- package/.output/server/_libs/_22.mjs +2 -2
- package/.output/server/_libs/_23.mjs +2 -2
- package/.output/server/_libs/_24.mjs +2 -2
- package/.output/server/_libs/_25.mjs +2 -2
- package/.output/server/_libs/_26.mjs +2 -2
- package/.output/server/_libs/_27.mjs +2 -2
- package/.output/server/_libs/_28.mjs +2 -2
- package/.output/server/_libs/_29.mjs +2 -2
- package/.output/server/_libs/_3.mjs +3 -3
- package/.output/server/_libs/_30.mjs +2 -2
- package/.output/server/_libs/_31.mjs +2 -2
- package/.output/server/_libs/_32.mjs +2 -2
- package/.output/server/_libs/_33.mjs +2 -2
- package/.output/server/_libs/_34.mjs +2 -2
- package/.output/server/_libs/_35.mjs +2 -2
- package/.output/server/_libs/_36.mjs +2 -2
- package/.output/server/_libs/_37.mjs +2 -2
- package/.output/server/_libs/_38.mjs +2 -2
- package/.output/server/_libs/_39.mjs +2 -2
- package/.output/server/_libs/_4.mjs +4 -3
- package/.output/server/_libs/_40.mjs +2 -2
- package/.output/server/_libs/_41.mjs +2 -2
- package/.output/server/_libs/_42.mjs +2 -2
- package/.output/server/_libs/_43.mjs +2 -2
- package/.output/server/_libs/_44.mjs +2 -2
- package/.output/server/_libs/_45.mjs +2 -2
- package/.output/server/_libs/_46.mjs +2 -2
- package/.output/server/_libs/_47.mjs +2 -2
- package/.output/server/_libs/_48.mjs +2 -2
- package/.output/server/_libs/_49.mjs +2 -2
- package/.output/server/_libs/_5.mjs +2 -3
- package/.output/server/_libs/_50.mjs +2 -2
- package/.output/server/_libs/_51.mjs +2 -2
- package/.output/server/_libs/_52.mjs +2 -2
- package/.output/server/_libs/_53.mjs +2 -2
- package/.output/server/_libs/_54.mjs +2 -2
- package/.output/server/_libs/_55.mjs +2 -2
- package/.output/server/_libs/_56.mjs +2 -2
- package/.output/server/_libs/_57.mjs +2 -2
- package/.output/server/_libs/_58.mjs +2 -2
- package/.output/server/_libs/_59.mjs +2 -2
- package/.output/server/_libs/_6.mjs +2 -3
- package/.output/server/_libs/_60.mjs +2 -2
- package/.output/server/_libs/_61.mjs +2 -2
- package/.output/server/_libs/_62.mjs +2 -2
- package/.output/server/_libs/_63.mjs +2 -2
- package/.output/server/_libs/_64.mjs +2 -2
- package/.output/server/_libs/_65.mjs +2 -2
- package/.output/server/_libs/_66.mjs +2 -2
- package/.output/server/_libs/_67.mjs +2 -2
- package/.output/server/_libs/_68.mjs +2 -2
- package/.output/server/_libs/_69.mjs +2 -2
- package/.output/server/_libs/_7.mjs +2 -5
- package/.output/server/_libs/_70.mjs +2 -2
- package/.output/server/_libs/_71.mjs +2 -2
- package/.output/server/_libs/_72.mjs +2 -2
- package/.output/server/_libs/_73.mjs +2 -2
- package/.output/server/_libs/_74.mjs +2 -2
- package/.output/server/_libs/_75.mjs +2 -2
- package/.output/server/_libs/_76.mjs +2 -2
- package/.output/server/_libs/_77.mjs +2 -2
- package/.output/server/_libs/_78.mjs +2 -2
- package/.output/server/_libs/_79.mjs +2 -2
- package/.output/server/_libs/_8.mjs +2 -3
- package/.output/server/_libs/_80.mjs +2 -2
- package/.output/server/_libs/_81.mjs +2 -2
- package/.output/server/_libs/_82.mjs +2 -2
- package/.output/server/_libs/_83.mjs +2 -2
- package/.output/server/_libs/_84.mjs +2 -2
- package/.output/server/_libs/_85.mjs +2 -2
- package/.output/server/_libs/_86.mjs +2 -2
- package/.output/server/_libs/_87.mjs +2 -2
- package/.output/server/_libs/_88.mjs +2 -2
- package/.output/server/_libs/_89.mjs +2 -2
- package/.output/server/_libs/_9.mjs +2 -4
- package/.output/server/_libs/_90.mjs +5 -2
- package/.output/server/_libs/_91.mjs +3 -2
- package/.output/server/_libs/_92.mjs +2 -2
- package/.output/server/_libs/_93.mjs +2 -2
- package/.output/server/_libs/_94.mjs +2 -2
- package/.output/server/_libs/agent-base.mjs +1 -1
- package/.output/server/_libs/cheerio+[...].mjs +1 -1
- package/.output/server/_libs/data-uri-to-buffer.mjs +2 -67
- package/.output/server/_libs/data-urls+[...].mjs +1 -1
- package/.output/server/_libs/diff.mjs +1 -1
- package/.output/server/_libs/exodus__bytes.mjs +99 -81
- package/.output/server/_libs/fetch-blob+node-domexception.mjs +1 -1
- package/.output/server/_libs/h3+rou3+srvx.mjs +34 -4
- package/.output/server/_libs/html-encoding-sniffer.mjs +1 -1
- package/.output/server/_libs/https-proxy-agent.mjs +2 -2
- package/.output/server/_libs/jsdom.mjs +1 -1
- package/.output/server/_libs/just-bash+[...].mjs +4676 -3916
- package/.output/server/_libs/mariozechner__jiti.mjs +1 -1
- package/.output/server/_libs/mariozechner__pi-ai.mjs +1472 -0
- package/.output/server/_libs/md4x.mjs +1 -1
- package/.output/server/_libs/node-fetch.mjs +14 -14
- package/.output/server/_libs/node-liblzma.mjs +1 -1
- package/.output/server/_libs/silvia-odwyer__photon-node.mjs +1 -1
- package/.output/server/_routes/api/auth/status.mjs +25 -6
- package/.output/server/_routes/api/config2.mjs +2 -0
- package/.output/server/_routes/api/files/groups.mjs +0 -1
- package/.output/server/_routes/api/groups.mjs +4 -2
- package/.output/server/_routes/api/groups2.mjs +14 -5
- package/.output/server/_routes/api/health.mjs +0 -1
- package/.output/server/_routes/api/pi/apikey.mjs +0 -1
- package/.output/server/_routes/api/pi/apikey_providers.mjs +0 -1
- package/.output/server/_routes/api/pi/commands.mjs +1 -2
- package/.output/server/_routes/api/pi/login/events.mjs +0 -1
- package/.output/server/_routes/api/pi/login/respond.mjs +0 -1
- package/.output/server/_routes/api/pi/login.mjs +0 -1
- package/.output/server/_routes/api/pi/logout.mjs +0 -1
- package/.output/server/_routes/api/pi/models.mjs +0 -1
- package/.output/server/_routes/api/pi/status.mjs +0 -1
- package/.output/server/_routes/api/sandbox.mjs +26 -0
- package/.output/server/_routes/api/sandbox2.mjs +17 -0
- package/.output/server/_routes/api/send.mjs +12 -12
- package/.output/server/_routes/api/status.mjs +0 -1
- package/.output/server/_routes/api/stop.mjs +0 -1
- package/.output/server/_routes/api/tasks2.mjs +0 -1
- package/.output/server/_routes/api/telegram/setup.mjs +0 -1
- package/.output/server/_routes/api/telegram/status.mjs +0 -1
- package/.output/server/_routes/api/terminal2.mjs +2 -1
- package/.output/server/_routes/api/tunnel/setup.mjs +0 -1
- package/.output/server/_runtime.mjs +1 -2
- package/.output/server/index.mjs +1 -1
- package/.output/server/node_modules/amdefine/amdefine.js +301 -0
- package/.output/server/node_modules/amdefine/package.json +16 -0
- package/.output/server/node_modules/compressjs/lib/BWT.js +420 -0
- package/.output/server/node_modules/compressjs/lib/BWTC.js +234 -0
- package/.output/server/node_modules/compressjs/lib/BitStream.js +108 -0
- package/.output/server/node_modules/compressjs/lib/Bzip2.js +936 -0
- package/.output/server/node_modules/compressjs/lib/CRC32.js +105 -0
- package/.output/server/node_modules/compressjs/lib/Context1Model.js +56 -0
- package/.output/server/node_modules/compressjs/lib/DefSumModel.js +152 -0
- package/.output/server/node_modules/compressjs/lib/DeflateDistanceModel.js +55 -0
- package/.output/server/node_modules/compressjs/lib/Dmc.js +197 -0
- package/.output/server/node_modules/compressjs/lib/DummyRangeCoder.js +81 -0
- package/.output/server/node_modules/compressjs/lib/FenwickModel.js +194 -0
- package/.output/server/node_modules/compressjs/lib/Huffman.js +514 -0
- package/.output/server/node_modules/compressjs/lib/HuffmanAllocator.js +227 -0
- package/.output/server/node_modules/compressjs/lib/LogDistanceModel.js +46 -0
- package/.output/server/node_modules/compressjs/lib/Lzjb.js +300 -0
- package/.output/server/node_modules/compressjs/lib/LzjbR.js +241 -0
- package/.output/server/node_modules/compressjs/lib/Lzp3.js +273 -0
- package/.output/server/node_modules/compressjs/lib/MTFModel.js +208 -0
- package/.output/server/node_modules/compressjs/lib/NoModel.js +46 -0
- package/.output/server/node_modules/compressjs/lib/PPM.js +343 -0
- package/.output/server/node_modules/compressjs/lib/RangeCoder.js +238 -0
- package/.output/server/node_modules/compressjs/lib/Simple.js +111 -0
- package/.output/server/node_modules/compressjs/lib/Stream.js +53 -0
- package/.output/server/node_modules/compressjs/lib/Util.js +324 -0
- package/.output/server/node_modules/compressjs/lib/freeze.js +14 -0
- package/.output/server/node_modules/compressjs/main.js +29 -0
- package/.output/server/node_modules/compressjs/package.json +35 -0
- package/.output/server/package.json +2 -1
- package/README.md +10 -1
- package/lib/index.d.mts +1 -0
- package/lib/index.mjs +1 -0
- package/lib/piclaw.mjs +100 -0
- package/lib/utils.mjs +96 -0
- package/package.json +16 -11
- package/.output/public/assets/defult-DtwgaiMA.js +0 -1
- package/.output/public/assets/index-B5n0eraW.css +0 -1
- package/.output/public/assets/index-DUbn6fuj.js +0 -205
- package/.output/server/_libs/@aws-crypto/crc32+[...].mjs +0 -299
- package/.output/server/_libs/@aws-sdk/client-bedrock-runtime+[...].mjs +0 -17828
- package/.output/server/_libs/@aws-sdk/credential-provider-http+[...].mjs +0 -122
- package/.output/server/_libs/@aws-sdk/credential-provider-ini+[...].mjs +0 -417
- package/.output/server/_libs/@aws-sdk/credential-provider-process+[...].mjs +0 -54
- package/.output/server/_libs/@aws-sdk/credential-provider-sso+[...].mjs +0 -1151
- package/.output/server/_libs/@aws-sdk/credential-provider-web-identity+[...].mjs +0 -50
- package/.output/server/_libs/@smithy/credential-provider-imds+[...].mjs +0 -369
- package/.output/server/_libs/@tootallnate/quickjs-emscripten+[...].mjs +0 -3011
- package/.output/server/_libs/_100.mjs +0 -2
- package/.output/server/_libs/_101.mjs +0 -2
- package/.output/server/_libs/_102.mjs +0 -2
- package/.output/server/_libs/_103.mjs +0 -5
- package/.output/server/_libs/_104.mjs +0 -3
- package/.output/server/_libs/_105.mjs +0 -2
- package/.output/server/_libs/_106.mjs +0 -3
- package/.output/server/_libs/_107.mjs +0 -2
- package/.output/server/_libs/_108.mjs +0 -2
- package/.output/server/_libs/_95.mjs +0 -2
- package/.output/server/_libs/_96.mjs +0 -2
- package/.output/server/_libs/_97.mjs +0 -2
- package/.output/server/_libs/_98.mjs +0 -2
- package/.output/server/_libs/_99.mjs +0 -2
- package/.output/server/_libs/amdefine.mjs +0 -188
- package/.output/server/_libs/ast-types.mjs +0 -2270
- package/.output/server/_libs/aws-sdk__nested-clients.mjs +0 -3141
- package/.output/server/_libs/basic-ftp.mjs +0 -1906
- package/.output/server/_libs/compressjs.mjs +0 -50
- package/.output/server/_libs/degenerator+[...].mjs +0 -9964
- package/.output/server/_libs/get-uri.mjs +0 -413
- package/.output/server/_libs/http-proxy-agent.mjs +0 -123
- package/.output/server/_libs/ip-address.mjs +0 -1423
- package/.output/server/_libs/lru-cache.mjs +0 -732
- package/.output/server/_libs/netmask.mjs +0 -139
- package/.output/server/_libs/pac-proxy-agent+[...].mjs +0 -3104
- package/.output/server/_libs/proxy-agent+proxy-from-env.mjs +0 -204
- package/.output/server/_libs/smithy__core.mjs +0 -192
- package/.output/server/node_modules/tslib/modules/index.js +0 -70
- package/.output/server/node_modules/tslib/modules/package.json +0 -3
- package/.output/server/node_modules/tslib/package.json +0 -47
- package/.output/server/node_modules/tslib/tslib.js +0 -484
- package/bin/piclaw.mjs +0 -195
|
@@ -0,0 +1,1472 @@
|
|
|
1
|
+
import { r as __exportAll } from "../_runtime.mjs";
|
|
2
|
+
import { c as getModels } from "./@mariozechner/pi-agent-core+[...].mjs";
|
|
3
|
+
/**
|
|
4
|
+
* PKCE utilities using Web Crypto API.
|
|
5
|
+
* Works in both Node.js 20+ and browsers.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Encode bytes as base64url string.
|
|
9
|
+
*/
|
|
10
|
+
function base64urlEncode(bytes) {
|
|
11
|
+
let binary = "";
|
|
12
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
13
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generate PKCE code verifier and challenge.
|
|
17
|
+
* Uses Web Crypto API for cross-platform compatibility.
|
|
18
|
+
*/
|
|
19
|
+
async function generatePKCE() {
|
|
20
|
+
const verifierBytes = new Uint8Array(32);
|
|
21
|
+
crypto.getRandomValues(verifierBytes);
|
|
22
|
+
const verifier = base64urlEncode(verifierBytes);
|
|
23
|
+
const data = new TextEncoder().encode(verifier);
|
|
24
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
25
|
+
return {
|
|
26
|
+
verifier,
|
|
27
|
+
challenge: base64urlEncode(new Uint8Array(hashBuffer))
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Anthropic OAuth flow (Claude Pro/Max)
|
|
32
|
+
*/
|
|
33
|
+
var decode$3 = (s) => atob(s);
|
|
34
|
+
var CLIENT_ID$4 = decode$3("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl");
|
|
35
|
+
var AUTHORIZE_URL$1 = "https://claude.ai/oauth/authorize";
|
|
36
|
+
var TOKEN_URL$3 = "https://console.anthropic.com/v1/oauth/token";
|
|
37
|
+
var REDIRECT_URI$3 = "https://console.anthropic.com/oauth/code/callback";
|
|
38
|
+
var SCOPES$2 = "org:create_api_key user:profile user:inference";
|
|
39
|
+
/**
|
|
40
|
+
* Login with Anthropic OAuth (device code flow)
|
|
41
|
+
*
|
|
42
|
+
* @param onAuthUrl - Callback to handle the authorization URL (e.g., open browser)
|
|
43
|
+
* @param onPromptCode - Callback to prompt user for the authorization code
|
|
44
|
+
*/
|
|
45
|
+
async function loginAnthropic(onAuthUrl, onPromptCode) {
|
|
46
|
+
const { verifier, challenge } = await generatePKCE();
|
|
47
|
+
onAuthUrl(`${AUTHORIZE_URL$1}?${new URLSearchParams({
|
|
48
|
+
code: "true",
|
|
49
|
+
client_id: CLIENT_ID$4,
|
|
50
|
+
response_type: "code",
|
|
51
|
+
redirect_uri: REDIRECT_URI$3,
|
|
52
|
+
scope: SCOPES$2,
|
|
53
|
+
code_challenge: challenge,
|
|
54
|
+
code_challenge_method: "S256",
|
|
55
|
+
state: verifier
|
|
56
|
+
}).toString()}`);
|
|
57
|
+
const splits = (await onPromptCode()).split("#");
|
|
58
|
+
const code = splits[0];
|
|
59
|
+
const state = splits[1];
|
|
60
|
+
const tokenResponse = await fetch(TOKEN_URL$3, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: { "Content-Type": "application/json" },
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
grant_type: "authorization_code",
|
|
65
|
+
client_id: CLIENT_ID$4,
|
|
66
|
+
code,
|
|
67
|
+
state,
|
|
68
|
+
redirect_uri: REDIRECT_URI$3,
|
|
69
|
+
code_verifier: verifier
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
if (!tokenResponse.ok) {
|
|
73
|
+
const error = await tokenResponse.text();
|
|
74
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
75
|
+
}
|
|
76
|
+
const tokenData = await tokenResponse.json();
|
|
77
|
+
const expiresAt = Date.now() + tokenData.expires_in * 1e3 - 300 * 1e3;
|
|
78
|
+
return {
|
|
79
|
+
refresh: tokenData.refresh_token,
|
|
80
|
+
access: tokenData.access_token,
|
|
81
|
+
expires: expiresAt
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Refresh Anthropic OAuth token
|
|
86
|
+
*/
|
|
87
|
+
async function refreshAnthropicToken(refreshToken) {
|
|
88
|
+
const response = await fetch(TOKEN_URL$3, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "Content-Type": "application/json" },
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
grant_type: "refresh_token",
|
|
93
|
+
client_id: CLIENT_ID$4,
|
|
94
|
+
refresh_token: refreshToken
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const error = await response.text();
|
|
99
|
+
throw new Error(`Anthropic token refresh failed: ${error}`);
|
|
100
|
+
}
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
return {
|
|
103
|
+
refresh: data.refresh_token,
|
|
104
|
+
access: data.access_token,
|
|
105
|
+
expires: Date.now() + data.expires_in * 1e3 - 300 * 1e3
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const anthropicOAuthProvider = {
|
|
109
|
+
id: "anthropic",
|
|
110
|
+
name: "Anthropic (Claude Pro/Max)",
|
|
111
|
+
async login(callbacks) {
|
|
112
|
+
return loginAnthropic((url) => callbacks.onAuth({ url }), () => callbacks.onPrompt({ message: "Paste the authorization code:" }));
|
|
113
|
+
},
|
|
114
|
+
async refreshToken(credentials) {
|
|
115
|
+
return refreshAnthropicToken(credentials.refresh);
|
|
116
|
+
},
|
|
117
|
+
getApiKey(credentials) {
|
|
118
|
+
return credentials.access;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* GitHub Copilot OAuth flow
|
|
123
|
+
*/
|
|
124
|
+
var decode$2 = (s) => atob(s);
|
|
125
|
+
var CLIENT_ID$3 = decode$2("SXYxLmI1MDdhMDhjODdlY2ZlOTg=");
|
|
126
|
+
var COPILOT_HEADERS = {
|
|
127
|
+
"User-Agent": "GitHubCopilotChat/0.35.0",
|
|
128
|
+
"Editor-Version": "vscode/1.107.0",
|
|
129
|
+
"Editor-Plugin-Version": "copilot-chat/0.35.0",
|
|
130
|
+
"Copilot-Integration-Id": "vscode-chat"
|
|
131
|
+
};
|
|
132
|
+
function normalizeDomain(input) {
|
|
133
|
+
const trimmed = input.trim();
|
|
134
|
+
if (!trimmed) return null;
|
|
135
|
+
try {
|
|
136
|
+
return (trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`)).hostname;
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function getUrls(domain) {
|
|
142
|
+
return {
|
|
143
|
+
deviceCodeUrl: `https://${domain}/login/device/code`,
|
|
144
|
+
accessTokenUrl: `https://${domain}/login/oauth/access_token`,
|
|
145
|
+
copilotTokenUrl: `https://api.${domain}/copilot_internal/v2/token`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Parse the proxy-ep from a Copilot token and convert to API base URL.
|
|
150
|
+
* Token format: tid=...;exp=...;proxy-ep=proxy.individual.githubcopilot.com;...
|
|
151
|
+
* Returns API URL like https://api.individual.githubcopilot.com
|
|
152
|
+
*/
|
|
153
|
+
function getBaseUrlFromToken(token) {
|
|
154
|
+
const match = token.match(/proxy-ep=([^;]+)/);
|
|
155
|
+
if (!match) return null;
|
|
156
|
+
return `https://${match[1].replace(/^proxy\./, "api.")}`;
|
|
157
|
+
}
|
|
158
|
+
function getGitHubCopilotBaseUrl(token, enterpriseDomain) {
|
|
159
|
+
if (token) {
|
|
160
|
+
const urlFromToken = getBaseUrlFromToken(token);
|
|
161
|
+
if (urlFromToken) return urlFromToken;
|
|
162
|
+
}
|
|
163
|
+
if (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;
|
|
164
|
+
return "https://api.individual.githubcopilot.com";
|
|
165
|
+
}
|
|
166
|
+
async function fetchJson(url, init) {
|
|
167
|
+
const response = await fetch(url, init);
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const text = await response.text();
|
|
170
|
+
throw new Error(`${response.status} ${response.statusText}: ${text}`);
|
|
171
|
+
}
|
|
172
|
+
return response.json();
|
|
173
|
+
}
|
|
174
|
+
async function startDeviceFlow(domain) {
|
|
175
|
+
const data = await fetchJson(getUrls(domain).deviceCodeUrl, {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: {
|
|
178
|
+
Accept: "application/json",
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
"User-Agent": "GitHubCopilotChat/0.35.0"
|
|
181
|
+
},
|
|
182
|
+
body: JSON.stringify({
|
|
183
|
+
client_id: CLIENT_ID$3,
|
|
184
|
+
scope: "read:user"
|
|
185
|
+
})
|
|
186
|
+
});
|
|
187
|
+
if (!data || typeof data !== "object") throw new Error("Invalid device code response");
|
|
188
|
+
const deviceCode = data.device_code;
|
|
189
|
+
const userCode = data.user_code;
|
|
190
|
+
const verificationUri = data.verification_uri;
|
|
191
|
+
const interval = data.interval;
|
|
192
|
+
const expiresIn = data.expires_in;
|
|
193
|
+
if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string" || typeof interval !== "number" || typeof expiresIn !== "number") throw new Error("Invalid device code response fields");
|
|
194
|
+
return {
|
|
195
|
+
device_code: deviceCode,
|
|
196
|
+
user_code: userCode,
|
|
197
|
+
verification_uri: verificationUri,
|
|
198
|
+
interval,
|
|
199
|
+
expires_in: expiresIn
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Sleep that can be interrupted by an AbortSignal
|
|
204
|
+
*/
|
|
205
|
+
function abortableSleep(ms, signal) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
if (signal?.aborted) {
|
|
208
|
+
reject(/* @__PURE__ */ new Error("Login cancelled"));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const timeout = setTimeout(resolve, ms);
|
|
212
|
+
signal?.addEventListener("abort", () => {
|
|
213
|
+
clearTimeout(timeout);
|
|
214
|
+
reject(/* @__PURE__ */ new Error("Login cancelled"));
|
|
215
|
+
}, { once: true });
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
async function pollForGitHubAccessToken(domain, deviceCode, intervalSeconds, expiresIn, signal) {
|
|
219
|
+
const urls = getUrls(domain);
|
|
220
|
+
const deadline = Date.now() + expiresIn * 1e3;
|
|
221
|
+
let intervalMs = Math.max(1e3, Math.floor(intervalSeconds * 1e3));
|
|
222
|
+
while (Date.now() < deadline) {
|
|
223
|
+
if (signal?.aborted) throw new Error("Login cancelled");
|
|
224
|
+
const raw = await fetchJson(urls.accessTokenUrl, {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: {
|
|
227
|
+
Accept: "application/json",
|
|
228
|
+
"Content-Type": "application/json",
|
|
229
|
+
"User-Agent": "GitHubCopilotChat/0.35.0"
|
|
230
|
+
},
|
|
231
|
+
body: JSON.stringify({
|
|
232
|
+
client_id: CLIENT_ID$3,
|
|
233
|
+
device_code: deviceCode,
|
|
234
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
235
|
+
})
|
|
236
|
+
});
|
|
237
|
+
if (raw && typeof raw === "object" && typeof raw.access_token === "string") return raw.access_token;
|
|
238
|
+
if (raw && typeof raw === "object" && typeof raw.error === "string") {
|
|
239
|
+
const err = raw.error;
|
|
240
|
+
if (err === "authorization_pending") {
|
|
241
|
+
await abortableSleep(intervalMs, signal);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (err === "slow_down") {
|
|
245
|
+
intervalMs += 5e3;
|
|
246
|
+
await abortableSleep(intervalMs, signal);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
throw new Error(`Device flow failed: ${err}`);
|
|
250
|
+
}
|
|
251
|
+
await abortableSleep(intervalMs, signal);
|
|
252
|
+
}
|
|
253
|
+
throw new Error("Device flow timed out");
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Refresh GitHub Copilot token
|
|
257
|
+
*/
|
|
258
|
+
async function refreshGitHubCopilotToken(refreshToken, enterpriseDomain) {
|
|
259
|
+
const raw = await fetchJson(getUrls(enterpriseDomain || "github.com").copilotTokenUrl, { headers: {
|
|
260
|
+
Accept: "application/json",
|
|
261
|
+
Authorization: `Bearer ${refreshToken}`,
|
|
262
|
+
...COPILOT_HEADERS
|
|
263
|
+
} });
|
|
264
|
+
if (!raw || typeof raw !== "object") throw new Error("Invalid Copilot token response");
|
|
265
|
+
const token = raw.token;
|
|
266
|
+
const expiresAt = raw.expires_at;
|
|
267
|
+
if (typeof token !== "string" || typeof expiresAt !== "number") throw new Error("Invalid Copilot token response fields");
|
|
268
|
+
return {
|
|
269
|
+
refresh: refreshToken,
|
|
270
|
+
access: token,
|
|
271
|
+
expires: expiresAt * 1e3 - 300 * 1e3,
|
|
272
|
+
enterpriseUrl: enterpriseDomain
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Enable a model for the user's GitHub Copilot account.
|
|
277
|
+
* This is required for some models (like Claude, Grok) before they can be used.
|
|
278
|
+
*/
|
|
279
|
+
async function enableGitHubCopilotModel(token, modelId, enterpriseDomain) {
|
|
280
|
+
const url = `${getGitHubCopilotBaseUrl(token, enterpriseDomain)}/models/${modelId}/policy`;
|
|
281
|
+
try {
|
|
282
|
+
return (await fetch(url, {
|
|
283
|
+
method: "POST",
|
|
284
|
+
headers: {
|
|
285
|
+
"Content-Type": "application/json",
|
|
286
|
+
Authorization: `Bearer ${token}`,
|
|
287
|
+
...COPILOT_HEADERS,
|
|
288
|
+
"openai-intent": "chat-policy",
|
|
289
|
+
"x-interaction-type": "chat-policy"
|
|
290
|
+
},
|
|
291
|
+
body: JSON.stringify({ state: "enabled" })
|
|
292
|
+
})).ok;
|
|
293
|
+
} catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Enable all known GitHub Copilot models that may require policy acceptance.
|
|
299
|
+
* Called after successful login to ensure all models are available.
|
|
300
|
+
*/
|
|
301
|
+
async function enableAllGitHubCopilotModels(token, enterpriseDomain, onProgress) {
|
|
302
|
+
const models = getModels("github-copilot");
|
|
303
|
+
await Promise.all(models.map(async (model) => {
|
|
304
|
+
const success = await enableGitHubCopilotModel(token, model.id, enterpriseDomain);
|
|
305
|
+
onProgress?.(model.id, success);
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Login with GitHub Copilot OAuth (device code flow)
|
|
310
|
+
*
|
|
311
|
+
* @param options.onAuth - Callback with URL and optional instructions (user code)
|
|
312
|
+
* @param options.onPrompt - Callback to prompt user for input
|
|
313
|
+
* @param options.onProgress - Optional progress callback
|
|
314
|
+
* @param options.signal - Optional AbortSignal for cancellation
|
|
315
|
+
*/
|
|
316
|
+
async function loginGitHubCopilot(options) {
|
|
317
|
+
const input = await options.onPrompt({
|
|
318
|
+
message: "GitHub Enterprise URL/domain (blank for github.com)",
|
|
319
|
+
placeholder: "company.ghe.com",
|
|
320
|
+
allowEmpty: true
|
|
321
|
+
});
|
|
322
|
+
if (options.signal?.aborted) throw new Error("Login cancelled");
|
|
323
|
+
const trimmed = input.trim();
|
|
324
|
+
const enterpriseDomain = normalizeDomain(input);
|
|
325
|
+
if (trimmed && !enterpriseDomain) throw new Error("Invalid GitHub Enterprise URL/domain");
|
|
326
|
+
const domain = enterpriseDomain || "github.com";
|
|
327
|
+
const device = await startDeviceFlow(domain);
|
|
328
|
+
options.onAuth(device.verification_uri, `Enter code: ${device.user_code}`);
|
|
329
|
+
const credentials = await refreshGitHubCopilotToken(await pollForGitHubAccessToken(domain, device.device_code, device.interval, device.expires_in, options.signal), enterpriseDomain ?? void 0);
|
|
330
|
+
options.onProgress?.("Enabling models...");
|
|
331
|
+
await enableAllGitHubCopilotModels(credentials.access, enterpriseDomain ?? void 0);
|
|
332
|
+
return credentials;
|
|
333
|
+
}
|
|
334
|
+
const githubCopilotOAuthProvider = {
|
|
335
|
+
id: "github-copilot",
|
|
336
|
+
name: "GitHub Copilot",
|
|
337
|
+
async login(callbacks) {
|
|
338
|
+
return loginGitHubCopilot({
|
|
339
|
+
onAuth: (url, instructions) => callbacks.onAuth({
|
|
340
|
+
url,
|
|
341
|
+
instructions
|
|
342
|
+
}),
|
|
343
|
+
onPrompt: callbacks.onPrompt,
|
|
344
|
+
onProgress: callbacks.onProgress,
|
|
345
|
+
signal: callbacks.signal
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
async refreshToken(credentials) {
|
|
349
|
+
const creds = credentials;
|
|
350
|
+
return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl);
|
|
351
|
+
},
|
|
352
|
+
getApiKey(credentials) {
|
|
353
|
+
return credentials.access;
|
|
354
|
+
},
|
|
355
|
+
modifyModels(models, credentials) {
|
|
356
|
+
const creds = credentials;
|
|
357
|
+
const domain = creds.enterpriseUrl ? normalizeDomain(creds.enterpriseUrl) ?? void 0 : void 0;
|
|
358
|
+
const baseUrl = getGitHubCopilotBaseUrl(creds.access, domain);
|
|
359
|
+
return models.map((m) => m.provider === "github-copilot" ? {
|
|
360
|
+
...m,
|
|
361
|
+
baseUrl
|
|
362
|
+
} : m);
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Antigravity OAuth flow (Gemini 3, Claude, GPT-OSS via Google Cloud)
|
|
367
|
+
* Uses different OAuth credentials than google-gemini-cli for access to additional models.
|
|
368
|
+
*
|
|
369
|
+
* NOTE: This module uses Node.js http.createServer for the OAuth callback.
|
|
370
|
+
* It is only intended for CLI use, not browser environments.
|
|
371
|
+
*/
|
|
372
|
+
var _createServer$1 = null;
|
|
373
|
+
var _httpImportPromise$1 = null;
|
|
374
|
+
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) _httpImportPromise$1 = import("node:http").then((m) => {
|
|
375
|
+
_createServer$1 = m.createServer;
|
|
376
|
+
});
|
|
377
|
+
var decode$1 = (s) => atob(s);
|
|
378
|
+
var CLIENT_ID$2 = decode$1("MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==");
|
|
379
|
+
var CLIENT_SECRET$1 = decode$1("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=");
|
|
380
|
+
var REDIRECT_URI$2 = "http://localhost:51121/oauth-callback";
|
|
381
|
+
var SCOPES$1 = [
|
|
382
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
383
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
384
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
385
|
+
"https://www.googleapis.com/auth/cclog",
|
|
386
|
+
"https://www.googleapis.com/auth/experimentsandconfigs"
|
|
387
|
+
];
|
|
388
|
+
var AUTH_URL$1 = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
389
|
+
var TOKEN_URL$2 = "https://oauth2.googleapis.com/token";
|
|
390
|
+
var DEFAULT_PROJECT_ID = "rising-fact-p41fc";
|
|
391
|
+
/**
|
|
392
|
+
* Start a local HTTP server to receive the OAuth callback
|
|
393
|
+
*/
|
|
394
|
+
async function getNodeCreateServer$1() {
|
|
395
|
+
if (_createServer$1) return _createServer$1;
|
|
396
|
+
if (_httpImportPromise$1) await _httpImportPromise$1;
|
|
397
|
+
if (_createServer$1) return _createServer$1;
|
|
398
|
+
throw new Error("Antigravity OAuth is only available in Node.js environments");
|
|
399
|
+
}
|
|
400
|
+
async function startCallbackServer$1() {
|
|
401
|
+
const createServer = await getNodeCreateServer$1();
|
|
402
|
+
return new Promise((resolve, reject) => {
|
|
403
|
+
let result = null;
|
|
404
|
+
let cancelled = false;
|
|
405
|
+
const server = createServer((req, res) => {
|
|
406
|
+
const url = new URL(req.url || "", `http://localhost:51121`);
|
|
407
|
+
if (url.pathname === "/oauth-callback") {
|
|
408
|
+
const code = url.searchParams.get("code");
|
|
409
|
+
const state = url.searchParams.get("state");
|
|
410
|
+
const error = url.searchParams.get("error");
|
|
411
|
+
if (error) {
|
|
412
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
413
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (code && state) {
|
|
417
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
418
|
+
res.end(`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`);
|
|
419
|
+
result = {
|
|
420
|
+
code,
|
|
421
|
+
state
|
|
422
|
+
};
|
|
423
|
+
} else {
|
|
424
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
425
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`);
|
|
426
|
+
}
|
|
427
|
+
} else {
|
|
428
|
+
res.writeHead(404);
|
|
429
|
+
res.end();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
server.on("error", (err) => {
|
|
433
|
+
reject(err);
|
|
434
|
+
});
|
|
435
|
+
server.listen(51121, "127.0.0.1", () => {
|
|
436
|
+
resolve({
|
|
437
|
+
server,
|
|
438
|
+
cancelWait: () => {
|
|
439
|
+
cancelled = true;
|
|
440
|
+
},
|
|
441
|
+
waitForCode: async () => {
|
|
442
|
+
const sleep = () => new Promise((r) => setTimeout(r, 100));
|
|
443
|
+
while (!result && !cancelled) await sleep();
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Parse redirect URL to extract code and state
|
|
452
|
+
*/
|
|
453
|
+
function parseRedirectUrl$1(input) {
|
|
454
|
+
const value = input.trim();
|
|
455
|
+
if (!value) return {};
|
|
456
|
+
try {
|
|
457
|
+
const url = new URL(value);
|
|
458
|
+
return {
|
|
459
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
460
|
+
state: url.searchParams.get("state") ?? void 0
|
|
461
|
+
};
|
|
462
|
+
} catch {
|
|
463
|
+
return {};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Discover or provision a project for the user
|
|
468
|
+
*/
|
|
469
|
+
async function discoverProject$1(accessToken, onProgress) {
|
|
470
|
+
const headers = {
|
|
471
|
+
Authorization: `Bearer ${accessToken}`,
|
|
472
|
+
"Content-Type": "application/json",
|
|
473
|
+
"User-Agent": "google-api-nodejs-client/9.15.1",
|
|
474
|
+
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
|
475
|
+
"Client-Metadata": JSON.stringify({
|
|
476
|
+
ideType: "IDE_UNSPECIFIED",
|
|
477
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
478
|
+
pluginType: "GEMINI"
|
|
479
|
+
})
|
|
480
|
+
};
|
|
481
|
+
const endpoints = ["https://cloudcode-pa.googleapis.com", "https://daily-cloudcode-pa.sandbox.googleapis.com"];
|
|
482
|
+
onProgress?.("Checking for existing project...");
|
|
483
|
+
for (const endpoint of endpoints) try {
|
|
484
|
+
const loadResponse = await fetch(`${endpoint}/v1internal:loadCodeAssist`, {
|
|
485
|
+
method: "POST",
|
|
486
|
+
headers,
|
|
487
|
+
body: JSON.stringify({ metadata: {
|
|
488
|
+
ideType: "IDE_UNSPECIFIED",
|
|
489
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
490
|
+
pluginType: "GEMINI"
|
|
491
|
+
} })
|
|
492
|
+
});
|
|
493
|
+
if (loadResponse.ok) {
|
|
494
|
+
const data = await loadResponse.json();
|
|
495
|
+
if (typeof data.cloudaicompanionProject === "string" && data.cloudaicompanionProject) return data.cloudaicompanionProject;
|
|
496
|
+
if (data.cloudaicompanionProject && typeof data.cloudaicompanionProject === "object" && data.cloudaicompanionProject.id) return data.cloudaicompanionProject.id;
|
|
497
|
+
}
|
|
498
|
+
} catch {}
|
|
499
|
+
onProgress?.("Using default project...");
|
|
500
|
+
return DEFAULT_PROJECT_ID;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Get user email from the access token
|
|
504
|
+
*/
|
|
505
|
+
async function getUserEmail$1(accessToken) {
|
|
506
|
+
try {
|
|
507
|
+
const response = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { headers: { Authorization: `Bearer ${accessToken}` } });
|
|
508
|
+
if (response.ok) return (await response.json()).email;
|
|
509
|
+
} catch {}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Refresh Antigravity token
|
|
513
|
+
*/
|
|
514
|
+
async function refreshAntigravityToken(refreshToken, projectId) {
|
|
515
|
+
const response = await fetch(TOKEN_URL$2, {
|
|
516
|
+
method: "POST",
|
|
517
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
518
|
+
body: new URLSearchParams({
|
|
519
|
+
client_id: CLIENT_ID$2,
|
|
520
|
+
client_secret: CLIENT_SECRET$1,
|
|
521
|
+
refresh_token: refreshToken,
|
|
522
|
+
grant_type: "refresh_token"
|
|
523
|
+
})
|
|
524
|
+
});
|
|
525
|
+
if (!response.ok) {
|
|
526
|
+
const error = await response.text();
|
|
527
|
+
throw new Error(`Antigravity token refresh failed: ${error}`);
|
|
528
|
+
}
|
|
529
|
+
const data = await response.json();
|
|
530
|
+
return {
|
|
531
|
+
refresh: data.refresh_token || refreshToken,
|
|
532
|
+
access: data.access_token,
|
|
533
|
+
expires: Date.now() + data.expires_in * 1e3 - 300 * 1e3,
|
|
534
|
+
projectId
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Login with Antigravity OAuth
|
|
539
|
+
*
|
|
540
|
+
* @param onAuth - Callback with URL and optional instructions
|
|
541
|
+
* @param onProgress - Optional progress callback
|
|
542
|
+
* @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
|
|
543
|
+
* Races with browser callback - whichever completes first wins.
|
|
544
|
+
*/
|
|
545
|
+
async function loginAntigravity(onAuth, onProgress, onManualCodeInput) {
|
|
546
|
+
const { verifier, challenge } = await generatePKCE();
|
|
547
|
+
onProgress?.("Starting local server for OAuth callback...");
|
|
548
|
+
const server = await startCallbackServer$1();
|
|
549
|
+
let code;
|
|
550
|
+
try {
|
|
551
|
+
onAuth({
|
|
552
|
+
url: `${AUTH_URL$1}?${new URLSearchParams({
|
|
553
|
+
client_id: CLIENT_ID$2,
|
|
554
|
+
response_type: "code",
|
|
555
|
+
redirect_uri: REDIRECT_URI$2,
|
|
556
|
+
scope: SCOPES$1.join(" "),
|
|
557
|
+
code_challenge: challenge,
|
|
558
|
+
code_challenge_method: "S256",
|
|
559
|
+
state: verifier,
|
|
560
|
+
access_type: "offline",
|
|
561
|
+
prompt: "consent"
|
|
562
|
+
}).toString()}`,
|
|
563
|
+
instructions: "Complete the sign-in in your browser."
|
|
564
|
+
});
|
|
565
|
+
onProgress?.("Waiting for OAuth callback...");
|
|
566
|
+
if (onManualCodeInput) {
|
|
567
|
+
let manualInput;
|
|
568
|
+
let manualError;
|
|
569
|
+
const manualPromise = onManualCodeInput().then((input) => {
|
|
570
|
+
manualInput = input;
|
|
571
|
+
server.cancelWait();
|
|
572
|
+
}).catch((err) => {
|
|
573
|
+
manualError = err instanceof Error ? err : new Error(String(err));
|
|
574
|
+
server.cancelWait();
|
|
575
|
+
});
|
|
576
|
+
const result = await server.waitForCode();
|
|
577
|
+
if (manualError) throw manualError;
|
|
578
|
+
if (result?.code) {
|
|
579
|
+
if (result.state !== verifier) throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
580
|
+
code = result.code;
|
|
581
|
+
} else if (manualInput) {
|
|
582
|
+
const parsed = parseRedirectUrl$1(manualInput);
|
|
583
|
+
if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
584
|
+
code = parsed.code;
|
|
585
|
+
}
|
|
586
|
+
if (!code) {
|
|
587
|
+
await manualPromise;
|
|
588
|
+
if (manualError) throw manualError;
|
|
589
|
+
if (manualInput) {
|
|
590
|
+
const parsed = parseRedirectUrl$1(manualInput);
|
|
591
|
+
if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
592
|
+
code = parsed.code;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
const result = await server.waitForCode();
|
|
597
|
+
if (result?.code) {
|
|
598
|
+
if (result.state !== verifier) throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
599
|
+
code = result.code;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (!code) throw new Error("No authorization code received");
|
|
603
|
+
onProgress?.("Exchanging authorization code for tokens...");
|
|
604
|
+
const tokenResponse = await fetch(TOKEN_URL$2, {
|
|
605
|
+
method: "POST",
|
|
606
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
607
|
+
body: new URLSearchParams({
|
|
608
|
+
client_id: CLIENT_ID$2,
|
|
609
|
+
client_secret: CLIENT_SECRET$1,
|
|
610
|
+
code,
|
|
611
|
+
grant_type: "authorization_code",
|
|
612
|
+
redirect_uri: REDIRECT_URI$2,
|
|
613
|
+
code_verifier: verifier
|
|
614
|
+
})
|
|
615
|
+
});
|
|
616
|
+
if (!tokenResponse.ok) {
|
|
617
|
+
const error = await tokenResponse.text();
|
|
618
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
619
|
+
}
|
|
620
|
+
const tokenData = await tokenResponse.json();
|
|
621
|
+
if (!tokenData.refresh_token) throw new Error("No refresh token received. Please try again.");
|
|
622
|
+
onProgress?.("Getting user info...");
|
|
623
|
+
const email = await getUserEmail$1(tokenData.access_token);
|
|
624
|
+
const projectId = await discoverProject$1(tokenData.access_token, onProgress);
|
|
625
|
+
const expiresAt = Date.now() + tokenData.expires_in * 1e3 - 300 * 1e3;
|
|
626
|
+
return {
|
|
627
|
+
refresh: tokenData.refresh_token,
|
|
628
|
+
access: tokenData.access_token,
|
|
629
|
+
expires: expiresAt,
|
|
630
|
+
projectId,
|
|
631
|
+
email
|
|
632
|
+
};
|
|
633
|
+
} finally {
|
|
634
|
+
server.server.close();
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const antigravityOAuthProvider = {
|
|
638
|
+
id: "google-antigravity",
|
|
639
|
+
name: "Antigravity (Gemini 3, Claude, GPT-OSS)",
|
|
640
|
+
usesCallbackServer: true,
|
|
641
|
+
async login(callbacks) {
|
|
642
|
+
return loginAntigravity(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);
|
|
643
|
+
},
|
|
644
|
+
async refreshToken(credentials) {
|
|
645
|
+
const creds = credentials;
|
|
646
|
+
if (!creds.projectId) throw new Error("Antigravity credentials missing projectId");
|
|
647
|
+
return refreshAntigravityToken(creds.refresh, creds.projectId);
|
|
648
|
+
},
|
|
649
|
+
getApiKey(credentials) {
|
|
650
|
+
const creds = credentials;
|
|
651
|
+
return JSON.stringify({
|
|
652
|
+
token: creds.access,
|
|
653
|
+
projectId: creds.projectId
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
/**
|
|
658
|
+
* Gemini CLI OAuth flow (Google Cloud Code Assist)
|
|
659
|
+
* Standard Gemini models only (gemini-2.0-flash, gemini-2.5-*)
|
|
660
|
+
*
|
|
661
|
+
* NOTE: This module uses Node.js http.createServer for the OAuth callback.
|
|
662
|
+
* It is only intended for CLI use, not browser environments.
|
|
663
|
+
*/
|
|
664
|
+
var _createServer = null;
|
|
665
|
+
var _httpImportPromise = null;
|
|
666
|
+
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) _httpImportPromise = import("node:http").then((m) => {
|
|
667
|
+
_createServer = m.createServer;
|
|
668
|
+
});
|
|
669
|
+
var decode = (s) => atob(s);
|
|
670
|
+
var CLIENT_ID$1 = decode("NjgxMjU1ODA5Mzk1LW9vOGZ0Mm9wcmRybnA5ZTNhcWY2YXYzaG1kaWIxMzVqLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29t");
|
|
671
|
+
var CLIENT_SECRET = decode("R09DU1BYLTR1SGdNUG0tMW83U2stZ2VWNkN1NWNsWEZzeGw=");
|
|
672
|
+
var REDIRECT_URI$1 = "http://localhost:8085/oauth2callback";
|
|
673
|
+
var SCOPES = [
|
|
674
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
675
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
676
|
+
"https://www.googleapis.com/auth/userinfo.profile"
|
|
677
|
+
];
|
|
678
|
+
var AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
679
|
+
var TOKEN_URL$1 = "https://oauth2.googleapis.com/token";
|
|
680
|
+
var CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
681
|
+
/**
|
|
682
|
+
* Start a local HTTP server to receive the OAuth callback
|
|
683
|
+
*/
|
|
684
|
+
async function getNodeCreateServer() {
|
|
685
|
+
if (_createServer) return _createServer;
|
|
686
|
+
if (_httpImportPromise) await _httpImportPromise;
|
|
687
|
+
if (_createServer) return _createServer;
|
|
688
|
+
throw new Error("Gemini CLI OAuth is only available in Node.js environments");
|
|
689
|
+
}
|
|
690
|
+
async function startCallbackServer() {
|
|
691
|
+
const createServer = await getNodeCreateServer();
|
|
692
|
+
return new Promise((resolve, reject) => {
|
|
693
|
+
let result = null;
|
|
694
|
+
let cancelled = false;
|
|
695
|
+
const server = createServer((req, res) => {
|
|
696
|
+
const url = new URL(req.url || "", `http://localhost:8085`);
|
|
697
|
+
if (url.pathname === "/oauth2callback") {
|
|
698
|
+
const code = url.searchParams.get("code");
|
|
699
|
+
const state = url.searchParams.get("state");
|
|
700
|
+
const error = url.searchParams.get("error");
|
|
701
|
+
if (error) {
|
|
702
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
703
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (code && state) {
|
|
707
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
708
|
+
res.end(`<html><body><h1>Authentication Successful</h1><p>You can close this window and return to the terminal.</p></body></html>`);
|
|
709
|
+
result = {
|
|
710
|
+
code,
|
|
711
|
+
state
|
|
712
|
+
};
|
|
713
|
+
} else {
|
|
714
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
715
|
+
res.end(`<html><body><h1>Authentication Failed</h1><p>Missing code or state parameter.</p></body></html>`);
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
res.writeHead(404);
|
|
719
|
+
res.end();
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
server.on("error", (err) => {
|
|
723
|
+
reject(err);
|
|
724
|
+
});
|
|
725
|
+
server.listen(8085, "127.0.0.1", () => {
|
|
726
|
+
resolve({
|
|
727
|
+
server,
|
|
728
|
+
cancelWait: () => {
|
|
729
|
+
cancelled = true;
|
|
730
|
+
},
|
|
731
|
+
waitForCode: async () => {
|
|
732
|
+
const sleep = () => new Promise((r) => setTimeout(r, 100));
|
|
733
|
+
while (!result && !cancelled) await sleep();
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Parse redirect URL to extract code and state
|
|
742
|
+
*/
|
|
743
|
+
function parseRedirectUrl(input) {
|
|
744
|
+
const value = input.trim();
|
|
745
|
+
if (!value) return {};
|
|
746
|
+
try {
|
|
747
|
+
const url = new URL(value);
|
|
748
|
+
return {
|
|
749
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
750
|
+
state: url.searchParams.get("state") ?? void 0
|
|
751
|
+
};
|
|
752
|
+
} catch {
|
|
753
|
+
return {};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
var TIER_FREE = "free-tier";
|
|
757
|
+
var TIER_LEGACY = "legacy-tier";
|
|
758
|
+
var TIER_STANDARD = "standard-tier";
|
|
759
|
+
/**
|
|
760
|
+
* Wait helper for onboarding retries
|
|
761
|
+
*/
|
|
762
|
+
function wait(ms) {
|
|
763
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Get default tier from allowed tiers
|
|
767
|
+
*/
|
|
768
|
+
function getDefaultTier(allowedTiers) {
|
|
769
|
+
if (!allowedTiers || allowedTiers.length === 0) return { id: TIER_LEGACY };
|
|
770
|
+
return allowedTiers.find((t) => t.isDefault) ?? { id: TIER_LEGACY };
|
|
771
|
+
}
|
|
772
|
+
function isVpcScAffectedUser(payload) {
|
|
773
|
+
if (!payload || typeof payload !== "object") return false;
|
|
774
|
+
if (!("error" in payload)) return false;
|
|
775
|
+
const error = payload.error;
|
|
776
|
+
if (!error?.details || !Array.isArray(error.details)) return false;
|
|
777
|
+
return error.details.some((detail) => detail.reason === "SECURITY_POLICY_VIOLATED");
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Poll a long-running operation until completion
|
|
781
|
+
*/
|
|
782
|
+
async function pollOperation(operationName, headers, onProgress) {
|
|
783
|
+
let attempt = 0;
|
|
784
|
+
while (true) {
|
|
785
|
+
if (attempt > 0) {
|
|
786
|
+
onProgress?.(`Waiting for project provisioning (attempt ${attempt + 1})...`);
|
|
787
|
+
await wait(5e3);
|
|
788
|
+
}
|
|
789
|
+
const response = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal/${operationName}`, {
|
|
790
|
+
method: "GET",
|
|
791
|
+
headers
|
|
792
|
+
});
|
|
793
|
+
if (!response.ok) throw new Error(`Failed to poll operation: ${response.status} ${response.statusText}`);
|
|
794
|
+
const data = await response.json();
|
|
795
|
+
if (data.done) return data;
|
|
796
|
+
attempt += 1;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Discover or provision a Google Cloud project for the user
|
|
801
|
+
*/
|
|
802
|
+
async function discoverProject(accessToken, onProgress) {
|
|
803
|
+
const envProjectId = process.env.GOOGLE_CLOUD_PROJECT || process.env.GOOGLE_CLOUD_PROJECT_ID;
|
|
804
|
+
const headers = {
|
|
805
|
+
Authorization: `Bearer ${accessToken}`,
|
|
806
|
+
"Content-Type": "application/json",
|
|
807
|
+
"User-Agent": "google-api-nodejs-client/9.15.1",
|
|
808
|
+
"X-Goog-Api-Client": "gl-node/22.17.0"
|
|
809
|
+
};
|
|
810
|
+
onProgress?.("Checking for existing Cloud Code Assist project...");
|
|
811
|
+
const loadResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:loadCodeAssist`, {
|
|
812
|
+
method: "POST",
|
|
813
|
+
headers,
|
|
814
|
+
body: JSON.stringify({
|
|
815
|
+
cloudaicompanionProject: envProjectId,
|
|
816
|
+
metadata: {
|
|
817
|
+
ideType: "IDE_UNSPECIFIED",
|
|
818
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
819
|
+
pluginType: "GEMINI",
|
|
820
|
+
duetProject: envProjectId
|
|
821
|
+
}
|
|
822
|
+
})
|
|
823
|
+
});
|
|
824
|
+
let data;
|
|
825
|
+
if (!loadResponse.ok) {
|
|
826
|
+
let errorPayload;
|
|
827
|
+
try {
|
|
828
|
+
errorPayload = await loadResponse.clone().json();
|
|
829
|
+
} catch {
|
|
830
|
+
errorPayload = void 0;
|
|
831
|
+
}
|
|
832
|
+
if (isVpcScAffectedUser(errorPayload)) data = { currentTier: { id: TIER_STANDARD } };
|
|
833
|
+
else {
|
|
834
|
+
const errorText = await loadResponse.text();
|
|
835
|
+
throw new Error(`loadCodeAssist failed: ${loadResponse.status} ${loadResponse.statusText}: ${errorText}`);
|
|
836
|
+
}
|
|
837
|
+
} else data = await loadResponse.json();
|
|
838
|
+
if (data.currentTier) {
|
|
839
|
+
if (data.cloudaicompanionProject) return data.cloudaicompanionProject;
|
|
840
|
+
if (envProjectId) return envProjectId;
|
|
841
|
+
throw new Error("This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. See https://goo.gle/gemini-cli-auth-docs#workspace-gca");
|
|
842
|
+
}
|
|
843
|
+
const tierId = getDefaultTier(data.allowedTiers)?.id ?? TIER_FREE;
|
|
844
|
+
if (tierId !== TIER_FREE && !envProjectId) throw new Error("This account requires setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. See https://goo.gle/gemini-cli-auth-docs#workspace-gca");
|
|
845
|
+
onProgress?.("Provisioning Cloud Code Assist project (this may take a moment)...");
|
|
846
|
+
const onboardBody = {
|
|
847
|
+
tierId,
|
|
848
|
+
metadata: {
|
|
849
|
+
ideType: "IDE_UNSPECIFIED",
|
|
850
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
851
|
+
pluginType: "GEMINI"
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
if (tierId !== TIER_FREE && envProjectId) {
|
|
855
|
+
onboardBody.cloudaicompanionProject = envProjectId;
|
|
856
|
+
onboardBody.metadata.duetProject = envProjectId;
|
|
857
|
+
}
|
|
858
|
+
const onboardResponse = await fetch(`${CODE_ASSIST_ENDPOINT}/v1internal:onboardUser`, {
|
|
859
|
+
method: "POST",
|
|
860
|
+
headers,
|
|
861
|
+
body: JSON.stringify(onboardBody)
|
|
862
|
+
});
|
|
863
|
+
if (!onboardResponse.ok) {
|
|
864
|
+
const errorText = await onboardResponse.text();
|
|
865
|
+
throw new Error(`onboardUser failed: ${onboardResponse.status} ${onboardResponse.statusText}: ${errorText}`);
|
|
866
|
+
}
|
|
867
|
+
let lroData = await onboardResponse.json();
|
|
868
|
+
if (!lroData.done && lroData.name) lroData = await pollOperation(lroData.name, headers, onProgress);
|
|
869
|
+
const projectId = lroData.response?.cloudaicompanionProject?.id;
|
|
870
|
+
if (projectId) return projectId;
|
|
871
|
+
if (envProjectId) return envProjectId;
|
|
872
|
+
throw new Error("Could not discover or provision a Google Cloud project. Try setting the GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_PROJECT_ID environment variable. See https://goo.gle/gemini-cli-auth-docs#workspace-gca");
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Get user email from the access token
|
|
876
|
+
*/
|
|
877
|
+
async function getUserEmail(accessToken) {
|
|
878
|
+
try {
|
|
879
|
+
const response = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", { headers: { Authorization: `Bearer ${accessToken}` } });
|
|
880
|
+
if (response.ok) return (await response.json()).email;
|
|
881
|
+
} catch {}
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Refresh Google Cloud Code Assist token
|
|
885
|
+
*/
|
|
886
|
+
async function refreshGoogleCloudToken(refreshToken, projectId) {
|
|
887
|
+
const response = await fetch(TOKEN_URL$1, {
|
|
888
|
+
method: "POST",
|
|
889
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
890
|
+
body: new URLSearchParams({
|
|
891
|
+
client_id: CLIENT_ID$1,
|
|
892
|
+
client_secret: CLIENT_SECRET,
|
|
893
|
+
refresh_token: refreshToken,
|
|
894
|
+
grant_type: "refresh_token"
|
|
895
|
+
})
|
|
896
|
+
});
|
|
897
|
+
if (!response.ok) {
|
|
898
|
+
const error = await response.text();
|
|
899
|
+
throw new Error(`Google Cloud token refresh failed: ${error}`);
|
|
900
|
+
}
|
|
901
|
+
const data = await response.json();
|
|
902
|
+
return {
|
|
903
|
+
refresh: data.refresh_token || refreshToken,
|
|
904
|
+
access: data.access_token,
|
|
905
|
+
expires: Date.now() + data.expires_in * 1e3 - 300 * 1e3,
|
|
906
|
+
projectId
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Login with Gemini CLI (Google Cloud Code Assist) OAuth
|
|
911
|
+
*
|
|
912
|
+
* @param onAuth - Callback with URL and optional instructions
|
|
913
|
+
* @param onProgress - Optional progress callback
|
|
914
|
+
* @param onManualCodeInput - Optional promise that resolves with user-pasted redirect URL.
|
|
915
|
+
* Races with browser callback - whichever completes first wins.
|
|
916
|
+
*/
|
|
917
|
+
async function loginGeminiCli(onAuth, onProgress, onManualCodeInput) {
|
|
918
|
+
const { verifier, challenge } = await generatePKCE();
|
|
919
|
+
onProgress?.("Starting local server for OAuth callback...");
|
|
920
|
+
const server = await startCallbackServer();
|
|
921
|
+
let code;
|
|
922
|
+
try {
|
|
923
|
+
onAuth({
|
|
924
|
+
url: `${AUTH_URL}?${new URLSearchParams({
|
|
925
|
+
client_id: CLIENT_ID$1,
|
|
926
|
+
response_type: "code",
|
|
927
|
+
redirect_uri: REDIRECT_URI$1,
|
|
928
|
+
scope: SCOPES.join(" "),
|
|
929
|
+
code_challenge: challenge,
|
|
930
|
+
code_challenge_method: "S256",
|
|
931
|
+
state: verifier,
|
|
932
|
+
access_type: "offline",
|
|
933
|
+
prompt: "consent"
|
|
934
|
+
}).toString()}`,
|
|
935
|
+
instructions: "Complete the sign-in in your browser."
|
|
936
|
+
});
|
|
937
|
+
onProgress?.("Waiting for OAuth callback...");
|
|
938
|
+
if (onManualCodeInput) {
|
|
939
|
+
let manualInput;
|
|
940
|
+
let manualError;
|
|
941
|
+
const manualPromise = onManualCodeInput().then((input) => {
|
|
942
|
+
manualInput = input;
|
|
943
|
+
server.cancelWait();
|
|
944
|
+
}).catch((err) => {
|
|
945
|
+
manualError = err instanceof Error ? err : new Error(String(err));
|
|
946
|
+
server.cancelWait();
|
|
947
|
+
});
|
|
948
|
+
const result = await server.waitForCode();
|
|
949
|
+
if (manualError) throw manualError;
|
|
950
|
+
if (result?.code) {
|
|
951
|
+
if (result.state !== verifier) throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
952
|
+
code = result.code;
|
|
953
|
+
} else if (manualInput) {
|
|
954
|
+
const parsed = parseRedirectUrl(manualInput);
|
|
955
|
+
if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
956
|
+
code = parsed.code;
|
|
957
|
+
}
|
|
958
|
+
if (!code) {
|
|
959
|
+
await manualPromise;
|
|
960
|
+
if (manualError) throw manualError;
|
|
961
|
+
if (manualInput) {
|
|
962
|
+
const parsed = parseRedirectUrl(manualInput);
|
|
963
|
+
if (parsed.state && parsed.state !== verifier) throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
964
|
+
code = parsed.code;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
} else {
|
|
968
|
+
const result = await server.waitForCode();
|
|
969
|
+
if (result?.code) {
|
|
970
|
+
if (result.state !== verifier) throw new Error("OAuth state mismatch - possible CSRF attack");
|
|
971
|
+
code = result.code;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (!code) throw new Error("No authorization code received");
|
|
975
|
+
onProgress?.("Exchanging authorization code for tokens...");
|
|
976
|
+
const tokenResponse = await fetch(TOKEN_URL$1, {
|
|
977
|
+
method: "POST",
|
|
978
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
979
|
+
body: new URLSearchParams({
|
|
980
|
+
client_id: CLIENT_ID$1,
|
|
981
|
+
client_secret: CLIENT_SECRET,
|
|
982
|
+
code,
|
|
983
|
+
grant_type: "authorization_code",
|
|
984
|
+
redirect_uri: REDIRECT_URI$1,
|
|
985
|
+
code_verifier: verifier
|
|
986
|
+
})
|
|
987
|
+
});
|
|
988
|
+
if (!tokenResponse.ok) {
|
|
989
|
+
const error = await tokenResponse.text();
|
|
990
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
991
|
+
}
|
|
992
|
+
const tokenData = await tokenResponse.json();
|
|
993
|
+
if (!tokenData.refresh_token) throw new Error("No refresh token received. Please try again.");
|
|
994
|
+
onProgress?.("Getting user info...");
|
|
995
|
+
const email = await getUserEmail(tokenData.access_token);
|
|
996
|
+
const projectId = await discoverProject(tokenData.access_token, onProgress);
|
|
997
|
+
const expiresAt = Date.now() + tokenData.expires_in * 1e3 - 300 * 1e3;
|
|
998
|
+
return {
|
|
999
|
+
refresh: tokenData.refresh_token,
|
|
1000
|
+
access: tokenData.access_token,
|
|
1001
|
+
expires: expiresAt,
|
|
1002
|
+
projectId,
|
|
1003
|
+
email
|
|
1004
|
+
};
|
|
1005
|
+
} finally {
|
|
1006
|
+
server.server.close();
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const geminiCliOAuthProvider = {
|
|
1010
|
+
id: "google-gemini-cli",
|
|
1011
|
+
name: "Google Cloud Code Assist (Gemini CLI)",
|
|
1012
|
+
usesCallbackServer: true,
|
|
1013
|
+
async login(callbacks) {
|
|
1014
|
+
return loginGeminiCli(callbacks.onAuth, callbacks.onProgress, callbacks.onManualCodeInput);
|
|
1015
|
+
},
|
|
1016
|
+
async refreshToken(credentials) {
|
|
1017
|
+
const creds = credentials;
|
|
1018
|
+
if (!creds.projectId) throw new Error("Google Cloud credentials missing projectId");
|
|
1019
|
+
return refreshGoogleCloudToken(creds.refresh, creds.projectId);
|
|
1020
|
+
},
|
|
1021
|
+
getApiKey(credentials) {
|
|
1022
|
+
const creds = credentials;
|
|
1023
|
+
return JSON.stringify({
|
|
1024
|
+
token: creds.access,
|
|
1025
|
+
projectId: creds.projectId
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
/**
|
|
1030
|
+
* OpenAI Codex (ChatGPT OAuth) flow
|
|
1031
|
+
*
|
|
1032
|
+
* NOTE: This module uses Node.js crypto and http for the OAuth callback.
|
|
1033
|
+
* It is only intended for CLI use, not browser environments.
|
|
1034
|
+
*/
|
|
1035
|
+
var _randomBytes = null;
|
|
1036
|
+
var _http = null;
|
|
1037
|
+
if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
|
|
1038
|
+
import("node:crypto").then((m) => {
|
|
1039
|
+
_randomBytes = m.randomBytes;
|
|
1040
|
+
});
|
|
1041
|
+
import("node:http").then((m) => {
|
|
1042
|
+
_http = m;
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
var CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
1046
|
+
var AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
|
|
1047
|
+
var TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
1048
|
+
var REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
1049
|
+
var SCOPE = "openid profile email offline_access";
|
|
1050
|
+
var JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
1051
|
+
var SUCCESS_HTML = `<!doctype html>
|
|
1052
|
+
<html lang="en">
|
|
1053
|
+
<head>
|
|
1054
|
+
<meta charset="utf-8" />
|
|
1055
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1056
|
+
<title>Authentication successful</title>
|
|
1057
|
+
</head>
|
|
1058
|
+
<body>
|
|
1059
|
+
<p>Authentication successful. Return to your terminal to continue.</p>
|
|
1060
|
+
</body>
|
|
1061
|
+
</html>`;
|
|
1062
|
+
function createState() {
|
|
1063
|
+
if (!_randomBytes) throw new Error("OpenAI Codex OAuth is only available in Node.js environments");
|
|
1064
|
+
return _randomBytes(16).toString("hex");
|
|
1065
|
+
}
|
|
1066
|
+
function parseAuthorizationInput(input) {
|
|
1067
|
+
const value = input.trim();
|
|
1068
|
+
if (!value) return {};
|
|
1069
|
+
try {
|
|
1070
|
+
const url = new URL(value);
|
|
1071
|
+
return {
|
|
1072
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
1073
|
+
state: url.searchParams.get("state") ?? void 0
|
|
1074
|
+
};
|
|
1075
|
+
} catch {}
|
|
1076
|
+
if (value.includes("#")) {
|
|
1077
|
+
const [code, state] = value.split("#", 2);
|
|
1078
|
+
return {
|
|
1079
|
+
code,
|
|
1080
|
+
state
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
if (value.includes("code=")) {
|
|
1084
|
+
const params = new URLSearchParams(value);
|
|
1085
|
+
return {
|
|
1086
|
+
code: params.get("code") ?? void 0,
|
|
1087
|
+
state: params.get("state") ?? void 0
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
return { code: value };
|
|
1091
|
+
}
|
|
1092
|
+
function decodeJwt(token) {
|
|
1093
|
+
try {
|
|
1094
|
+
const parts = token.split(".");
|
|
1095
|
+
if (parts.length !== 3) return null;
|
|
1096
|
+
const payload = parts[1] ?? "";
|
|
1097
|
+
const decoded = atob(payload);
|
|
1098
|
+
return JSON.parse(decoded);
|
|
1099
|
+
} catch {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
async function exchangeAuthorizationCode(code, verifier, redirectUri = REDIRECT_URI) {
|
|
1104
|
+
const response = await fetch(TOKEN_URL, {
|
|
1105
|
+
method: "POST",
|
|
1106
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1107
|
+
body: new URLSearchParams({
|
|
1108
|
+
grant_type: "authorization_code",
|
|
1109
|
+
client_id: CLIENT_ID,
|
|
1110
|
+
code,
|
|
1111
|
+
code_verifier: verifier,
|
|
1112
|
+
redirect_uri: redirectUri
|
|
1113
|
+
})
|
|
1114
|
+
});
|
|
1115
|
+
if (!response.ok) {
|
|
1116
|
+
const text = await response.text().catch(() => "");
|
|
1117
|
+
console.error("[openai-codex] code->token failed:", response.status, text);
|
|
1118
|
+
return { type: "failed" };
|
|
1119
|
+
}
|
|
1120
|
+
const json = await response.json();
|
|
1121
|
+
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
1122
|
+
console.error("[openai-codex] token response missing fields:", json);
|
|
1123
|
+
return { type: "failed" };
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
type: "success",
|
|
1127
|
+
access: json.access_token,
|
|
1128
|
+
refresh: json.refresh_token,
|
|
1129
|
+
expires: Date.now() + json.expires_in * 1e3
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
async function refreshAccessToken(refreshToken) {
|
|
1133
|
+
try {
|
|
1134
|
+
const response = await fetch(TOKEN_URL, {
|
|
1135
|
+
method: "POST",
|
|
1136
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1137
|
+
body: new URLSearchParams({
|
|
1138
|
+
grant_type: "refresh_token",
|
|
1139
|
+
refresh_token: refreshToken,
|
|
1140
|
+
client_id: CLIENT_ID
|
|
1141
|
+
})
|
|
1142
|
+
});
|
|
1143
|
+
if (!response.ok) {
|
|
1144
|
+
const text = await response.text().catch(() => "");
|
|
1145
|
+
console.error("[openai-codex] Token refresh failed:", response.status, text);
|
|
1146
|
+
return { type: "failed" };
|
|
1147
|
+
}
|
|
1148
|
+
const json = await response.json();
|
|
1149
|
+
if (!json.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
|
|
1150
|
+
console.error("[openai-codex] Token refresh response missing fields:", json);
|
|
1151
|
+
return { type: "failed" };
|
|
1152
|
+
}
|
|
1153
|
+
return {
|
|
1154
|
+
type: "success",
|
|
1155
|
+
access: json.access_token,
|
|
1156
|
+
refresh: json.refresh_token,
|
|
1157
|
+
expires: Date.now() + json.expires_in * 1e3
|
|
1158
|
+
};
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
console.error("[openai-codex] Token refresh error:", error);
|
|
1161
|
+
return { type: "failed" };
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
async function createAuthorizationFlow(originator = "pi") {
|
|
1165
|
+
const { verifier, challenge } = await generatePKCE();
|
|
1166
|
+
const state = createState();
|
|
1167
|
+
const url = new URL(AUTHORIZE_URL);
|
|
1168
|
+
url.searchParams.set("response_type", "code");
|
|
1169
|
+
url.searchParams.set("client_id", CLIENT_ID);
|
|
1170
|
+
url.searchParams.set("redirect_uri", REDIRECT_URI);
|
|
1171
|
+
url.searchParams.set("scope", SCOPE);
|
|
1172
|
+
url.searchParams.set("code_challenge", challenge);
|
|
1173
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
1174
|
+
url.searchParams.set("state", state);
|
|
1175
|
+
url.searchParams.set("id_token_add_organizations", "true");
|
|
1176
|
+
url.searchParams.set("codex_cli_simplified_flow", "true");
|
|
1177
|
+
url.searchParams.set("originator", originator);
|
|
1178
|
+
return {
|
|
1179
|
+
verifier,
|
|
1180
|
+
state,
|
|
1181
|
+
url: url.toString()
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
function startLocalOAuthServer(state) {
|
|
1185
|
+
if (!_http) throw new Error("OpenAI Codex OAuth is only available in Node.js environments");
|
|
1186
|
+
let lastCode = null;
|
|
1187
|
+
let cancelled = false;
|
|
1188
|
+
const server = _http.createServer((req, res) => {
|
|
1189
|
+
try {
|
|
1190
|
+
const url = new URL(req.url || "", "http://localhost");
|
|
1191
|
+
if (url.pathname !== "/auth/callback") {
|
|
1192
|
+
res.statusCode = 404;
|
|
1193
|
+
res.end("Not found");
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (url.searchParams.get("state") !== state) {
|
|
1197
|
+
res.statusCode = 400;
|
|
1198
|
+
res.end("State mismatch");
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
const code = url.searchParams.get("code");
|
|
1202
|
+
if (!code) {
|
|
1203
|
+
res.statusCode = 400;
|
|
1204
|
+
res.end("Missing authorization code");
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
res.statusCode = 200;
|
|
1208
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
1209
|
+
res.end(SUCCESS_HTML);
|
|
1210
|
+
lastCode = code;
|
|
1211
|
+
} catch {
|
|
1212
|
+
res.statusCode = 500;
|
|
1213
|
+
res.end("Internal error");
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
return new Promise((resolve) => {
|
|
1217
|
+
server.listen(1455, "127.0.0.1", () => {
|
|
1218
|
+
resolve({
|
|
1219
|
+
close: () => server.close(),
|
|
1220
|
+
cancelWait: () => {
|
|
1221
|
+
cancelled = true;
|
|
1222
|
+
},
|
|
1223
|
+
waitForCode: async () => {
|
|
1224
|
+
const sleep = () => new Promise((r) => setTimeout(r, 100));
|
|
1225
|
+
for (let i = 0; i < 600; i += 1) {
|
|
1226
|
+
if (lastCode) return { code: lastCode };
|
|
1227
|
+
if (cancelled) return null;
|
|
1228
|
+
await sleep();
|
|
1229
|
+
}
|
|
1230
|
+
return null;
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
}).on("error", (err) => {
|
|
1234
|
+
console.error("[openai-codex] Failed to bind http://127.0.0.1:1455 (", err.code, ") Falling back to manual paste.");
|
|
1235
|
+
resolve({
|
|
1236
|
+
close: () => {
|
|
1237
|
+
try {
|
|
1238
|
+
server.close();
|
|
1239
|
+
} catch {}
|
|
1240
|
+
},
|
|
1241
|
+
cancelWait: () => {},
|
|
1242
|
+
waitForCode: async () => null
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
function getAccountId(accessToken) {
|
|
1248
|
+
const accountId = (decodeJwt(accessToken)?.[JWT_CLAIM_PATH])?.chatgpt_account_id;
|
|
1249
|
+
return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Login with OpenAI Codex OAuth
|
|
1253
|
+
*
|
|
1254
|
+
* @param options.onAuth - Called with URL and instructions when auth starts
|
|
1255
|
+
* @param options.onPrompt - Called to prompt user for manual code paste (fallback if no onManualCodeInput)
|
|
1256
|
+
* @param options.onProgress - Optional progress messages
|
|
1257
|
+
* @param options.onManualCodeInput - Optional promise that resolves with user-pasted code.
|
|
1258
|
+
* Races with browser callback - whichever completes first wins.
|
|
1259
|
+
* Useful for showing paste input immediately alongside browser flow.
|
|
1260
|
+
* @param options.originator - OAuth originator parameter (defaults to "pi")
|
|
1261
|
+
*/
|
|
1262
|
+
async function loginOpenAICodex(options) {
|
|
1263
|
+
const { verifier, state, url } = await createAuthorizationFlow(options.originator);
|
|
1264
|
+
const server = await startLocalOAuthServer(state);
|
|
1265
|
+
options.onAuth({
|
|
1266
|
+
url,
|
|
1267
|
+
instructions: "A browser window should open. Complete login to finish."
|
|
1268
|
+
});
|
|
1269
|
+
let code;
|
|
1270
|
+
try {
|
|
1271
|
+
if (options.onManualCodeInput) {
|
|
1272
|
+
let manualCode;
|
|
1273
|
+
let manualError;
|
|
1274
|
+
const manualPromise = options.onManualCodeInput().then((input) => {
|
|
1275
|
+
manualCode = input;
|
|
1276
|
+
server.cancelWait();
|
|
1277
|
+
}).catch((err) => {
|
|
1278
|
+
manualError = err instanceof Error ? err : new Error(String(err));
|
|
1279
|
+
server.cancelWait();
|
|
1280
|
+
});
|
|
1281
|
+
const result = await server.waitForCode();
|
|
1282
|
+
if (manualError) throw manualError;
|
|
1283
|
+
if (result?.code) code = result.code;
|
|
1284
|
+
else if (manualCode) {
|
|
1285
|
+
const parsed = parseAuthorizationInput(manualCode);
|
|
1286
|
+
if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
|
|
1287
|
+
code = parsed.code;
|
|
1288
|
+
}
|
|
1289
|
+
if (!code) {
|
|
1290
|
+
await manualPromise;
|
|
1291
|
+
if (manualError) throw manualError;
|
|
1292
|
+
if (manualCode) {
|
|
1293
|
+
const parsed = parseAuthorizationInput(manualCode);
|
|
1294
|
+
if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
|
|
1295
|
+
code = parsed.code;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
} else {
|
|
1299
|
+
const result = await server.waitForCode();
|
|
1300
|
+
if (result?.code) code = result.code;
|
|
1301
|
+
}
|
|
1302
|
+
if (!code) {
|
|
1303
|
+
const parsed = parseAuthorizationInput(await options.onPrompt({ message: "Paste the authorization code (or full redirect URL):" }));
|
|
1304
|
+
if (parsed.state && parsed.state !== state) throw new Error("State mismatch");
|
|
1305
|
+
code = parsed.code;
|
|
1306
|
+
}
|
|
1307
|
+
if (!code) throw new Error("Missing authorization code");
|
|
1308
|
+
const tokenResult = await exchangeAuthorizationCode(code, verifier);
|
|
1309
|
+
if (tokenResult.type !== "success") throw new Error("Token exchange failed");
|
|
1310
|
+
const accountId = getAccountId(tokenResult.access);
|
|
1311
|
+
if (!accountId) throw new Error("Failed to extract accountId from token");
|
|
1312
|
+
return {
|
|
1313
|
+
access: tokenResult.access,
|
|
1314
|
+
refresh: tokenResult.refresh,
|
|
1315
|
+
expires: tokenResult.expires,
|
|
1316
|
+
accountId
|
|
1317
|
+
};
|
|
1318
|
+
} finally {
|
|
1319
|
+
server.close();
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Refresh OpenAI Codex OAuth token
|
|
1324
|
+
*/
|
|
1325
|
+
async function refreshOpenAICodexToken(refreshToken) {
|
|
1326
|
+
const result = await refreshAccessToken(refreshToken);
|
|
1327
|
+
if (result.type !== "success") throw new Error("Failed to refresh OpenAI Codex token");
|
|
1328
|
+
const accountId = getAccountId(result.access);
|
|
1329
|
+
if (!accountId) throw new Error("Failed to extract accountId from token");
|
|
1330
|
+
return {
|
|
1331
|
+
access: result.access,
|
|
1332
|
+
refresh: result.refresh,
|
|
1333
|
+
expires: result.expires,
|
|
1334
|
+
accountId
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
const openaiCodexOAuthProvider = {
|
|
1338
|
+
id: "openai-codex",
|
|
1339
|
+
name: "ChatGPT Plus/Pro (Codex Subscription)",
|
|
1340
|
+
usesCallbackServer: true,
|
|
1341
|
+
async login(callbacks) {
|
|
1342
|
+
return loginOpenAICodex({
|
|
1343
|
+
onAuth: callbacks.onAuth,
|
|
1344
|
+
onPrompt: callbacks.onPrompt,
|
|
1345
|
+
onProgress: callbacks.onProgress,
|
|
1346
|
+
onManualCodeInput: callbacks.onManualCodeInput
|
|
1347
|
+
});
|
|
1348
|
+
},
|
|
1349
|
+
async refreshToken(credentials) {
|
|
1350
|
+
return refreshOpenAICodexToken(credentials.refresh);
|
|
1351
|
+
},
|
|
1352
|
+
getApiKey(credentials) {
|
|
1353
|
+
return credentials.access;
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
var BUILT_IN_OAUTH_PROVIDERS = [
|
|
1357
|
+
anthropicOAuthProvider,
|
|
1358
|
+
githubCopilotOAuthProvider,
|
|
1359
|
+
geminiCliOAuthProvider,
|
|
1360
|
+
antigravityOAuthProvider,
|
|
1361
|
+
openaiCodexOAuthProvider
|
|
1362
|
+
];
|
|
1363
|
+
var oauthProviderRegistry = new Map(BUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]));
|
|
1364
|
+
/**
|
|
1365
|
+
* Get an OAuth provider by ID
|
|
1366
|
+
*/
|
|
1367
|
+
function getOAuthProvider(id) {
|
|
1368
|
+
return oauthProviderRegistry.get(id);
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Register a custom OAuth provider
|
|
1372
|
+
*/
|
|
1373
|
+
function registerOAuthProvider(provider) {
|
|
1374
|
+
oauthProviderRegistry.set(provider.id, provider);
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Unregister an OAuth provider.
|
|
1378
|
+
*
|
|
1379
|
+
* If the provider is built-in, restores the built-in implementation.
|
|
1380
|
+
* Custom providers are removed completely.
|
|
1381
|
+
*/
|
|
1382
|
+
function unregisterOAuthProvider(id) {
|
|
1383
|
+
const builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);
|
|
1384
|
+
if (builtInProvider) {
|
|
1385
|
+
oauthProviderRegistry.set(id, builtInProvider);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
oauthProviderRegistry.delete(id);
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Reset OAuth providers to built-ins.
|
|
1392
|
+
*/
|
|
1393
|
+
function resetOAuthProviders() {
|
|
1394
|
+
oauthProviderRegistry.clear();
|
|
1395
|
+
for (const provider of BUILT_IN_OAUTH_PROVIDERS) oauthProviderRegistry.set(provider.id, provider);
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Get all registered OAuth providers
|
|
1399
|
+
*/
|
|
1400
|
+
function getOAuthProviders() {
|
|
1401
|
+
return Array.from(oauthProviderRegistry.values());
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* @deprecated Use getOAuthProviders() which returns OAuthProviderInterface[]
|
|
1405
|
+
*/
|
|
1406
|
+
function getOAuthProviderInfoList() {
|
|
1407
|
+
return getOAuthProviders().map((p) => ({
|
|
1408
|
+
id: p.id,
|
|
1409
|
+
name: p.name,
|
|
1410
|
+
available: true
|
|
1411
|
+
}));
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Refresh token for any OAuth provider.
|
|
1415
|
+
* @deprecated Use getOAuthProvider(id).refreshToken() instead
|
|
1416
|
+
*/
|
|
1417
|
+
async function refreshOAuthToken(providerId, credentials) {
|
|
1418
|
+
const provider = getOAuthProvider(providerId);
|
|
1419
|
+
if (!provider) throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
1420
|
+
return provider.refreshToken(credentials);
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Get API key for a provider from OAuth credentials.
|
|
1424
|
+
* Automatically refreshes expired tokens.
|
|
1425
|
+
*
|
|
1426
|
+
* @returns API key string and updated credentials, or null if no credentials
|
|
1427
|
+
* @throws Error if refresh fails
|
|
1428
|
+
*/
|
|
1429
|
+
async function getOAuthApiKey(providerId, credentials) {
|
|
1430
|
+
const provider = getOAuthProvider(providerId);
|
|
1431
|
+
if (!provider) throw new Error(`Unknown OAuth provider: ${providerId}`);
|
|
1432
|
+
let creds = credentials[providerId];
|
|
1433
|
+
if (!creds) return null;
|
|
1434
|
+
if (Date.now() >= creds.expires) try {
|
|
1435
|
+
creds = await provider.refreshToken(creds);
|
|
1436
|
+
} catch (_error) {
|
|
1437
|
+
throw new Error(`Failed to refresh OAuth token for ${providerId}`);
|
|
1438
|
+
}
|
|
1439
|
+
const apiKey = provider.getApiKey(creds);
|
|
1440
|
+
return {
|
|
1441
|
+
newCredentials: creds,
|
|
1442
|
+
apiKey
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
var oauth_exports = /* @__PURE__ */ __exportAll({
|
|
1446
|
+
anthropicOAuthProvider: () => anthropicOAuthProvider,
|
|
1447
|
+
antigravityOAuthProvider: () => antigravityOAuthProvider,
|
|
1448
|
+
geminiCliOAuthProvider: () => geminiCliOAuthProvider,
|
|
1449
|
+
getGitHubCopilotBaseUrl: () => getGitHubCopilotBaseUrl,
|
|
1450
|
+
getOAuthApiKey: () => getOAuthApiKey,
|
|
1451
|
+
getOAuthProvider: () => getOAuthProvider,
|
|
1452
|
+
getOAuthProviderInfoList: () => getOAuthProviderInfoList,
|
|
1453
|
+
getOAuthProviders: () => getOAuthProviders,
|
|
1454
|
+
githubCopilotOAuthProvider: () => githubCopilotOAuthProvider,
|
|
1455
|
+
loginAnthropic: () => loginAnthropic,
|
|
1456
|
+
loginAntigravity: () => loginAntigravity,
|
|
1457
|
+
loginGeminiCli: () => loginGeminiCli,
|
|
1458
|
+
loginGitHubCopilot: () => loginGitHubCopilot,
|
|
1459
|
+
loginOpenAICodex: () => loginOpenAICodex,
|
|
1460
|
+
normalizeDomain: () => normalizeDomain,
|
|
1461
|
+
openaiCodexOAuthProvider: () => openaiCodexOAuthProvider,
|
|
1462
|
+
refreshAnthropicToken: () => refreshAnthropicToken,
|
|
1463
|
+
refreshAntigravityToken: () => refreshAntigravityToken,
|
|
1464
|
+
refreshGitHubCopilotToken: () => refreshGitHubCopilotToken,
|
|
1465
|
+
refreshGoogleCloudToken: () => refreshGoogleCloudToken,
|
|
1466
|
+
refreshOAuthToken: () => refreshOAuthToken,
|
|
1467
|
+
refreshOpenAICodexToken: () => refreshOpenAICodexToken,
|
|
1468
|
+
registerOAuthProvider: () => registerOAuthProvider,
|
|
1469
|
+
resetOAuthProviders: () => resetOAuthProviders,
|
|
1470
|
+
unregisterOAuthProvider: () => unregisterOAuthProvider
|
|
1471
|
+
});
|
|
1472
|
+
export { registerOAuthProvider as a, getOAuthProviders as i, getOAuthApiKey as n, resetOAuthProviders as o, getOAuthProvider as r, oauth_exports as t };
|