copilot-api-plus 1.0.13 → 1.0.15
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 +45 -8
- package/dist/{auth-Cm_0h9bp.js → auth-CsslwaJY.js} +137 -11
- package/dist/auth-CsslwaJY.js.map +1 -0
- package/dist/auth-DqWzJajV.js +4 -0
- package/dist/{get-models-Dq2ZDU9m.js → get-models-BOgYoUAJ.js} +2 -2
- package/dist/{get-models-Bbb8B5jI.js → get-models-DZR1TS4C.js} +2 -2
- package/dist/{get-models-Bbb8B5jI.js.map → get-models-DZR1TS4C.js.map} +1 -1
- package/dist/{get-user-BFf5xJXg.js → get-user-BzIEATcF.js} +6 -3
- package/dist/get-user-BzIEATcF.js.map +1 -0
- package/dist/get-user-CsQCc3Qx.js +5 -0
- package/dist/main.js +609 -119
- package/dist/main.js.map +1 -1
- package/dist/state-CcLGr8VN.js.map +1 -1
- package/dist/{token-B8crDDoA.js → token-BUmQ_BcM.js} +15 -5
- package/dist/token-BUmQ_BcM.js.map +1 -0
- package/dist/{token-DS09XjQ5.js → token-DhdRMuOy.js} +2 -2
- package/package.json +1 -1
- package/dist/auth-Cm_0h9bp.js.map +0 -1
- package/dist/auth-D2wtETTq.js +0 -4
- package/dist/get-user-BFf5xJXg.js.map +0 -1
- package/dist/get-user-hpkh0FzZ.js +0 -5
- package/dist/token-B8crDDoA.js.map +0 -1
package/README.md
CHANGED
|
@@ -169,20 +169,55 @@ npx copilot-api-plus@latest logout --zen
|
|
|
169
169
|
#### 前置要求
|
|
170
170
|
- Google 账户
|
|
171
171
|
|
|
172
|
-
####
|
|
172
|
+
#### 认证方式
|
|
173
|
+
|
|
174
|
+
**方式一:API Key(推荐 - 最简单)**
|
|
175
|
+
|
|
176
|
+
1. 访问 https://aistudio.google.com/apikey 获取 API Key
|
|
177
|
+
2. 使用环境变量启动:
|
|
173
178
|
|
|
174
179
|
```bash
|
|
180
|
+
# Linux/macOS
|
|
181
|
+
GEMINI_API_KEY=your_api_key npx copilot-api-plus@latest start --antigravity
|
|
182
|
+
|
|
183
|
+
# Windows PowerShell
|
|
184
|
+
$env:GEMINI_API_KEY = "your_api_key"
|
|
185
|
+
npx copilot-api-plus@latest start --antigravity
|
|
186
|
+
|
|
187
|
+
# Windows CMD
|
|
188
|
+
set GEMINI_API_KEY=your_api_key
|
|
175
189
|
npx copilot-api-plus@latest start --antigravity
|
|
176
190
|
```
|
|
177
191
|
|
|
178
|
-
|
|
192
|
+
**方式二:OAuth 网页登录(推荐)**
|
|
179
193
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
194
|
+
```bash
|
|
195
|
+
npx copilot-api-plus@latest start --antigravity
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
首次运行会提示选择登录方式:
|
|
199
|
+
- **Web(推荐)**:自动打开浏览器完成 Google 登录,授权后自动捕获回调
|
|
200
|
+
- **Manual**:手动复制回调 URL 到终端
|
|
201
|
+
|
|
202
|
+
**方式三:自定义 OAuth 凭证**
|
|
203
|
+
|
|
204
|
+
如果遇到 `invalid_client` 错误,可以创建自己的 OAuth 应用:
|
|
205
|
+
|
|
206
|
+
1. 访问 https://console.cloud.google.com/apis/credentials
|
|
207
|
+
2. 创建 OAuth 2.0 客户端 ID(选择"桌面应用"类型)
|
|
208
|
+
3. 添加重定向 URI:`http://localhost:8046/callback`
|
|
209
|
+
4. 使用环境变量或命令行参数:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# 环境变量方式
|
|
213
|
+
ANTIGRAVITY_CLIENT_ID=your_client_id ANTIGRAVITY_CLIENT_SECRET=your_secret \
|
|
214
|
+
npx copilot-api-plus@latest start --antigravity
|
|
215
|
+
|
|
216
|
+
# 命令行参数方式
|
|
217
|
+
npx copilot-api-plus@latest start --antigravity \
|
|
218
|
+
--antigravity-client-id your_client_id \
|
|
219
|
+
--antigravity-client-secret your_secret
|
|
220
|
+
```
|
|
186
221
|
|
|
187
222
|
#### 可用模型
|
|
188
223
|
|
|
@@ -485,6 +520,8 @@ curl http://localhost:4141/v1/messages \
|
|
|
485
520
|
| `--zen` | `-z` | false | 启用 OpenCode Zen 模式 |
|
|
486
521
|
| `--zen-api-key` | - | - | Zen API Key |
|
|
487
522
|
| `--antigravity` | - | false | 启用 Google Antigravity 模式 |
|
|
523
|
+
| `--antigravity-client-id` | - | - | Antigravity OAuth Client ID |
|
|
524
|
+
| `--antigravity-client-secret` | - | - | Antigravity OAuth Client Secret |
|
|
488
525
|
| `--rate-limit` | `-r` | - | 请求间隔(秒) |
|
|
489
526
|
| `--wait` | `-w` | false | 达到限制时等待而非报错 |
|
|
490
527
|
| `--manual` | - | false | 手动审批每个请求 |
|
|
@@ -3,10 +3,39 @@ import consola from "consola";
|
|
|
3
3
|
|
|
4
4
|
//#region src/services/antigravity/auth.ts
|
|
5
5
|
const ANTIGRAVITY_AUTH_FILENAME = "antigravity-accounts.json";
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const GEMINI_API_KEY = process.env.GEMINI_API_KEY || "";
|
|
7
|
+
/**
|
|
8
|
+
* Check if API Key authentication is available
|
|
9
|
+
*/
|
|
10
|
+
function hasApiKey() {
|
|
11
|
+
return GEMINI_API_KEY.length > 0;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get API Key for authentication
|
|
15
|
+
*/
|
|
16
|
+
function getApiKey() {
|
|
17
|
+
return GEMINI_API_KEY || null;
|
|
18
|
+
}
|
|
19
|
+
const DEFAULT_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
20
|
+
const DEFAULT_CLIENT_SECRET = "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";
|
|
21
|
+
let GOOGLE_CLIENT_ID = process.env.ANTIGRAVITY_CLIENT_ID || DEFAULT_CLIENT_ID;
|
|
22
|
+
let GOOGLE_CLIENT_SECRET = process.env.ANTIGRAVITY_CLIENT_SECRET || DEFAULT_CLIENT_SECRET;
|
|
9
23
|
const GOOGLE_REDIRECT_URI = "http://localhost:8046/callback";
|
|
24
|
+
const OAUTH_SCOPES = [
|
|
25
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
26
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
27
|
+
"https://www.googleapis.com/auth/userinfo.profile",
|
|
28
|
+
"https://www.googleapis.com/auth/cclog",
|
|
29
|
+
"https://www.googleapis.com/auth/experimentsandconfigs"
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Set OAuth credentials from CLI arguments
|
|
33
|
+
* This overrides environment variables and defaults
|
|
34
|
+
*/
|
|
35
|
+
function setOAuthCredentials(clientId, clientSecret) {
|
|
36
|
+
GOOGLE_CLIENT_ID = clientId;
|
|
37
|
+
GOOGLE_CLIENT_SECRET = clientSecret;
|
|
38
|
+
}
|
|
10
39
|
/**
|
|
11
40
|
* Get the path to the Antigravity auth file
|
|
12
41
|
*/
|
|
@@ -185,9 +214,10 @@ function getOAuthUrl() {
|
|
|
185
214
|
client_id: GOOGLE_CLIENT_ID,
|
|
186
215
|
redirect_uri: GOOGLE_REDIRECT_URI,
|
|
187
216
|
response_type: "code",
|
|
188
|
-
scope: "
|
|
217
|
+
scope: OAUTH_SCOPES.join(" "),
|
|
189
218
|
access_type: "offline",
|
|
190
|
-
prompt: "consent"
|
|
219
|
+
prompt: "consent",
|
|
220
|
+
include_granted_scopes: "true"
|
|
191
221
|
}).toString()}`;
|
|
192
222
|
}
|
|
193
223
|
/**
|
|
@@ -226,9 +256,45 @@ async function exchangeCodeForTokens(code) {
|
|
|
226
256
|
}
|
|
227
257
|
}
|
|
228
258
|
/**
|
|
229
|
-
*
|
|
259
|
+
* Start a local OAuth callback server and wait for authorization
|
|
260
|
+
* This provides a seamless web-based login experience
|
|
230
261
|
*/
|
|
231
|
-
async function
|
|
262
|
+
async function startOAuthCallbackServer() {
|
|
263
|
+
return new Promise((resolve, reject) => {
|
|
264
|
+
const server = Bun.serve({
|
|
265
|
+
port: 8046,
|
|
266
|
+
async fetch(req) {
|
|
267
|
+
const url = new URL(req.url);
|
|
268
|
+
if (url.pathname === "/callback") {
|
|
269
|
+
const code = url.searchParams.get("code");
|
|
270
|
+
const error = url.searchParams.get("error");
|
|
271
|
+
if (error) {
|
|
272
|
+
setTimeout(() => server.stop(), 100);
|
|
273
|
+
reject(/* @__PURE__ */ new Error(`OAuth error: ${error}`));
|
|
274
|
+
return new Response(`<html><body><h1>Authorization Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`, { headers: { "Content-Type": "text/html" } });
|
|
275
|
+
}
|
|
276
|
+
if (code) {
|
|
277
|
+
setTimeout(() => server.stop(), 100);
|
|
278
|
+
resolve(code);
|
|
279
|
+
return new Response(`<html><body><h1>Authorization Successful!</h1><p>You can close this window and return to the terminal.</p></body></html>`, { headers: { "Content-Type": "text/html" } });
|
|
280
|
+
}
|
|
281
|
+
return new Response("Missing authorization code", { status: 400 });
|
|
282
|
+
}
|
|
283
|
+
return new Response("Not found", { status: 404 });
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
consola.info(`OAuth callback server started on http://localhost:8046`);
|
|
287
|
+
setTimeout(() => {
|
|
288
|
+
server.stop();
|
|
289
|
+
reject(/* @__PURE__ */ new Error("OAuth timeout - no callback received within 5 minutes"));
|
|
290
|
+
}, 300 * 1e3);
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Setup Antigravity with web-based OAuth flow
|
|
295
|
+
* Opens browser for login and automatically captures the callback
|
|
296
|
+
*/
|
|
297
|
+
async function setupAntigravityWeb() {
|
|
232
298
|
const auth = await loadAntigravityAuth();
|
|
233
299
|
if (auth && auth.accounts.length > 0) {
|
|
234
300
|
const enabledCount = auth.accounts.filter((a) => a.enable).length;
|
|
@@ -239,8 +305,51 @@ async function setupAntigravity() {
|
|
|
239
305
|
})) return;
|
|
240
306
|
}
|
|
241
307
|
consola.info("");
|
|
242
|
-
consola.info("Google Antigravity OAuth Setup");
|
|
243
|
-
consola.info("
|
|
308
|
+
consola.info("Google Antigravity OAuth Setup (Web Flow)");
|
|
309
|
+
consola.info("=========================================");
|
|
310
|
+
consola.info("");
|
|
311
|
+
const oauthUrl = getOAuthUrl();
|
|
312
|
+
consola.info("Opening browser for Google login...");
|
|
313
|
+
consola.info(`If browser doesn't open, visit: ${oauthUrl}`);
|
|
314
|
+
consola.info("");
|
|
315
|
+
try {
|
|
316
|
+
const { exec } = await import("node:child_process");
|
|
317
|
+
const platform = process.platform;
|
|
318
|
+
if (platform === "win32") exec(`start "" "${oauthUrl}"`);
|
|
319
|
+
else if (platform === "darwin") exec(`open "${oauthUrl}"`);
|
|
320
|
+
else exec(`xdg-open "${oauthUrl}"`);
|
|
321
|
+
} catch {
|
|
322
|
+
consola.warn("Could not open browser automatically");
|
|
323
|
+
}
|
|
324
|
+
consola.info("Waiting for authorization...");
|
|
325
|
+
try {
|
|
326
|
+
const code = await startOAuthCallbackServer();
|
|
327
|
+
consola.info("Authorization code received, exchanging for tokens...");
|
|
328
|
+
const account = await exchangeCodeForTokens(code);
|
|
329
|
+
if (!account) throw new Error("Failed to exchange authorization code");
|
|
330
|
+
await addAntigravityAccount(account);
|
|
331
|
+
consola.success("Antigravity account added successfully!");
|
|
332
|
+
} catch (error) {
|
|
333
|
+
consola.error("OAuth flow failed:", error);
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Setup Antigravity interactively (manual URL copy method)
|
|
339
|
+
*/
|
|
340
|
+
async function setupAntigravityManual() {
|
|
341
|
+
const auth = await loadAntigravityAuth();
|
|
342
|
+
if (auth && auth.accounts.length > 0) {
|
|
343
|
+
const enabledCount = auth.accounts.filter((a) => a.enable).length;
|
|
344
|
+
consola.info(`Found ${auth.accounts.length} Antigravity accounts (${enabledCount} enabled)`);
|
|
345
|
+
if (!await consola.prompt("Add another account?", {
|
|
346
|
+
type: "confirm",
|
|
347
|
+
initial: false
|
|
348
|
+
})) return;
|
|
349
|
+
}
|
|
350
|
+
consola.info("");
|
|
351
|
+
consola.info("Google Antigravity OAuth Setup (Manual)");
|
|
352
|
+
consola.info("=======================================");
|
|
244
353
|
consola.info("");
|
|
245
354
|
consola.info("You need to authorize with Google to use Antigravity API.");
|
|
246
355
|
consola.info("Please follow these steps:");
|
|
@@ -262,7 +371,24 @@ async function setupAntigravity() {
|
|
|
262
371
|
await addAntigravityAccount(account);
|
|
263
372
|
consola.success("Antigravity account added successfully!");
|
|
264
373
|
}
|
|
374
|
+
/**
|
|
375
|
+
* Setup Antigravity interactively
|
|
376
|
+
* Offers choice between web-based and manual OAuth flow
|
|
377
|
+
*/
|
|
378
|
+
async function setupAntigravity() {
|
|
379
|
+
if (await consola.prompt("Choose OAuth login method:", {
|
|
380
|
+
type: "select",
|
|
381
|
+
options: [{
|
|
382
|
+
value: "web",
|
|
383
|
+
label: "Web (auto-open browser, recommended)"
|
|
384
|
+
}, {
|
|
385
|
+
value: "manual",
|
|
386
|
+
label: "Manual (copy/paste callback URL)"
|
|
387
|
+
}]
|
|
388
|
+
}) === "web") await setupAntigravityWeb();
|
|
389
|
+
else await setupAntigravityManual();
|
|
390
|
+
}
|
|
265
391
|
|
|
266
392
|
//#endregion
|
|
267
|
-
export { addAntigravityAccount, clearAntigravityAuth, disableCurrentAccount, exchangeCodeForTokens, generateRandomProjectId, getAntigravityAuthPath, getCurrentAccount, getOAuthUrl, getValidAccessToken, isTokenExpired, loadAntigravityAuth, refreshAccessToken, rotateAccount, saveAntigravityAuth, setupAntigravity };
|
|
268
|
-
//# sourceMappingURL=auth-
|
|
393
|
+
export { addAntigravityAccount, clearAntigravityAuth, disableCurrentAccount, exchangeCodeForTokens, generateRandomProjectId, getAntigravityAuthPath, getApiKey, getCurrentAccount, getOAuthUrl, getValidAccessToken, hasApiKey, isTokenExpired, loadAntigravityAuth, refreshAccessToken, rotateAccount, saveAntigravityAuth, setOAuthCredentials, setupAntigravity, setupAntigravityManual, setupAntigravityWeb };
|
|
394
|
+
//# sourceMappingURL=auth-CsslwaJY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-CsslwaJY.js","names":[],"sources":["../src/services/antigravity/auth.ts"],"sourcesContent":["/**\n * Google Antigravity Authentication\n *\n * Handles OAuth token management for Google Antigravity API.\n * Supports multiple accounts with auto-rotation and token refresh.\n */\n\n/* eslint-disable @typescript-eslint/no-unsafe-assignment */\n/* eslint-disable @typescript-eslint/no-unnecessary-condition */\n\nimport consola from \"consola\"\n\nimport { PATHS, ensurePaths } from \"~/lib/paths\"\n\nexport interface AntigravityAccount {\n access_token: string\n refresh_token: string\n expires_in: number\n timestamp: number\n enable: boolean\n project_id?: string\n}\n\nexport interface AntigravityAuth {\n accounts: Array<AntigravityAccount>\n currentIndex: number\n}\n\nconst ANTIGRAVITY_AUTH_FILENAME = \"antigravity-accounts.json\"\n\n// ============================================\n// Authentication Methods (choose one):\n// ============================================\n//\n// Method 1: API Key (Recommended - Simplest)\n// Set environment variable: GEMINI_API_KEY\n// Get your API key from: https://aistudio.google.com/apikey\n//\n// Method 2: OAuth Credentials\n// Set environment variables:\n// ANTIGRAVITY_CLIENT_ID - Google OAuth Client ID\n// ANTIGRAVITY_CLIENT_SECRET - Google OAuth Client Secret\n//\n// If you get \"invalid_client\" error, create your own OAuth app:\n// 1. Go to https://console.cloud.google.com/apis/credentials\n// 2. Create OAuth 2.0 Client ID (Desktop app or Web app type)\n// 3. Add redirect URI: http://localhost:8046/callback\n// 4. Set environment variables with your credentials\n// ============================================\n\n// API Key authentication (simplest method)\nconst GEMINI_API_KEY = process.env.GEMINI_API_KEY || \"\"\n\n/**\n * Check if API Key authentication is available\n */\nexport function hasApiKey(): boolean {\n return GEMINI_API_KEY.length > 0\n}\n\n/**\n * Get API Key for authentication\n */\nexport function getApiKey(): string | null {\n return GEMINI_API_KEY || null\n}\n\n// Default OAuth credentials (from reference projects: gcli2api, antigravity2api-nodejs, Antigravity-Manager)\nconst DEFAULT_CLIENT_ID = \"1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com\"\nconst DEFAULT_CLIENT_SECRET = \"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\"\n\n// OAuth credentials - can be set via env, CLI, or defaults\nlet GOOGLE_CLIENT_ID =\n process.env.ANTIGRAVITY_CLIENT_ID || DEFAULT_CLIENT_ID\nlet GOOGLE_CLIENT_SECRET =\n process.env.ANTIGRAVITY_CLIENT_SECRET || DEFAULT_CLIENT_SECRET\nconst GOOGLE_REDIRECT_URI = \"http://localhost:8046/callback\"\n\n// OAuth scopes required for Antigravity API access\nconst OAUTH_SCOPES = [\n \"https://www.googleapis.com/auth/cloud-platform\",\n \"https://www.googleapis.com/auth/userinfo.email\",\n \"https://www.googleapis.com/auth/userinfo.profile\",\n \"https://www.googleapis.com/auth/cclog\",\n \"https://www.googleapis.com/auth/experimentsandconfigs\",\n]\n\n/**\n * Set OAuth credentials from CLI arguments\n * This overrides environment variables and defaults\n */\nexport function setOAuthCredentials(clientId: string, clientSecret: string): void {\n GOOGLE_CLIENT_ID = clientId\n GOOGLE_CLIENT_SECRET = clientSecret\n}\n\n/**\n * Get the path to the Antigravity auth file\n */\nexport function getAntigravityAuthPath(): string {\n return `${PATHS.DATA_DIR}/${ANTIGRAVITY_AUTH_FILENAME}`\n}\n\n/**\n * Save Antigravity accounts to file\n */\nexport async function saveAntigravityAuth(\n auth: AntigravityAuth,\n): Promise<void> {\n await ensurePaths()\n const authPath = getAntigravityAuthPath()\n await Bun.write(authPath, JSON.stringify(auth, null, 2))\n consola.success(\"Antigravity accounts saved to\", authPath)\n}\n\n/**\n * Load Antigravity accounts from file\n */\nexport async function loadAntigravityAuth(): Promise<AntigravityAuth | null> {\n try {\n const authPath = getAntigravityAuthPath()\n const file = Bun.file(authPath)\n\n if (!(await file.exists())) {\n return null\n }\n\n const content = await file.text()\n const data = JSON.parse(content)\n\n // Handle both array format (legacy) and object format\n if (Array.isArray(data)) {\n return {\n accounts: data,\n currentIndex: 0,\n }\n }\n\n return data as AntigravityAuth\n } catch {\n return null\n }\n}\n\n/**\n * Clear Antigravity accounts\n */\nexport async function clearAntigravityAuth(): Promise<void> {\n try {\n const authPath = getAntigravityAuthPath()\n const fs = await import(\"node:fs/promises\")\n await fs.unlink(authPath)\n consola.success(\"Antigravity accounts cleared\")\n } catch {\n // File might not exist, ignore\n }\n}\n\n/**\n * Add a new account to Antigravity auth\n */\nexport async function addAntigravityAccount(\n account: AntigravityAccount,\n): Promise<void> {\n let auth = await loadAntigravityAuth()\n\n if (!auth) {\n auth = {\n accounts: [],\n currentIndex: 0,\n }\n }\n\n auth.accounts.push(account)\n await saveAntigravityAuth(auth)\n consola.success(\"Added new Antigravity account\")\n}\n\n/**\n * Get the current active account\n */\nexport async function getCurrentAccount(): Promise<AntigravityAccount | null> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return null\n }\n\n // Find enabled account starting from current index\n const enabledAccounts = auth.accounts.filter((a) => a.enable)\n\n if (enabledAccounts.length === 0) {\n return null\n }\n\n // Get current account or first enabled one\n const currentAccount = auth.accounts[auth.currentIndex]\n if (currentAccount && currentAccount.enable) {\n return currentAccount\n }\n\n return enabledAccounts[0]\n}\n\n/**\n * Rotate to the next account\n */\nexport async function rotateAccount(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length <= 1) {\n return\n }\n\n // Find next enabled account\n let nextIndex = (auth.currentIndex + 1) % auth.accounts.length\n let attempts = 0\n\n while (!auth.accounts[nextIndex].enable && attempts < auth.accounts.length) {\n nextIndex = (nextIndex + 1) % auth.accounts.length\n attempts++\n }\n\n auth.currentIndex = nextIndex\n await saveAntigravityAuth(auth)\n consola.info(`Rotated to account ${nextIndex}`)\n}\n\n/**\n * Disable current account\n */\nexport async function disableCurrentAccount(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return\n }\n\n auth.accounts[auth.currentIndex].enable = false\n await saveAntigravityAuth(auth)\n consola.warn(`Disabled account ${auth.currentIndex}`)\n\n // Rotate to next account\n await rotateAccount()\n}\n\n/**\n * Refresh access token using refresh token\n */\nexport async function refreshAccessToken(\n account: AntigravityAccount,\n): Promise<AntigravityAccount | null> {\n try {\n const response = await fetch(\"https://oauth2.googleapis.com/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n refresh_token: account.refresh_token,\n grant_type: \"refresh_token\",\n }),\n })\n\n if (!response.ok) {\n const error = await response.text()\n consola.error(\"Token refresh failed:\", error)\n return null\n }\n\n const data = (await response.json()) as {\n access_token: string\n expires_in: number\n }\n\n return {\n ...account,\n access_token: data.access_token,\n expires_in: data.expires_in,\n timestamp: Date.now(),\n }\n } catch (error) {\n consola.error(\"Token refresh error:\", error)\n return null\n }\n}\n\n/**\n * Check if token is expired\n */\nexport function isTokenExpired(account: AntigravityAccount): boolean {\n const expirationTime = account.timestamp + account.expires_in * 1000\n // Refresh 5 minutes before expiration\n return Date.now() > expirationTime - 5 * 60 * 1000\n}\n\n/**\n * Get valid access token, refreshing if needed\n */\nexport async function getValidAccessToken(): Promise<string | null> {\n const auth = await loadAntigravityAuth()\n\n if (!auth || auth.accounts.length === 0) {\n return null\n }\n\n let account = auth.accounts[auth.currentIndex]\n\n if (!account || !account.enable) {\n const enabledAccount = auth.accounts.find((a) => a.enable)\n if (!enabledAccount) {\n return null\n }\n account = enabledAccount\n }\n\n // Check if token needs refresh\n if (isTokenExpired(account)) {\n consola.info(\"Access token expired, refreshing...\")\n const refreshedAccount = await refreshAccessToken(account)\n\n if (!refreshedAccount) {\n consola.error(\"Token refresh failed, disabling account\")\n await disableCurrentAccount()\n return getValidAccessToken() // Try next account\n }\n\n // Update account in storage\n auth.accounts[auth.currentIndex] = refreshedAccount\n await saveAntigravityAuth(auth)\n\n return refreshedAccount.access_token\n }\n\n return account.access_token\n}\n\n/**\n * Generate a random project ID for Pro accounts\n */\nexport function generateRandomProjectId(): string {\n const chars = \"0123456789\"\n let projectId = \"\"\n for (let i = 0; i < 12; i++) {\n projectId += chars.charAt(Math.floor(Math.random() * chars.length))\n }\n return projectId\n}\n\n/**\n * Get OAuth authorization URL\n */\nexport function getOAuthUrl(): string {\n const params = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n redirect_uri: GOOGLE_REDIRECT_URI,\n response_type: \"code\",\n scope: OAUTH_SCOPES.join(\" \"),\n access_type: \"offline\",\n prompt: \"consent\",\n include_granted_scopes: \"true\",\n })\n\n return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`\n}\n\n/**\n * Exchange authorization code for tokens\n */\nexport async function exchangeCodeForTokens(\n code: string,\n): Promise<AntigravityAccount | null> {\n try {\n const response = await fetch(\"https://oauth2.googleapis.com/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n code,\n redirect_uri: GOOGLE_REDIRECT_URI,\n grant_type: \"authorization_code\",\n }),\n })\n\n if (!response.ok) {\n const error = await response.text()\n consola.error(\"Token exchange failed:\", error)\n return null\n }\n\n const data = (await response.json()) as {\n access_token: string\n refresh_token: string\n expires_in: number\n }\n\n return {\n access_token: data.access_token,\n refresh_token: data.refresh_token,\n expires_in: data.expires_in,\n timestamp: Date.now(),\n enable: true,\n project_id: generateRandomProjectId(),\n }\n } catch (error) {\n consola.error(\"Token exchange error:\", error)\n return null\n }\n}\n\n/**\n * Start a local OAuth callback server and wait for authorization\n * This provides a seamless web-based login experience\n */\nasync function startOAuthCallbackServer(): Promise<string> {\n return new Promise((resolve, reject) => {\n const server = Bun.serve({\n port: 8046,\n async fetch(req) {\n const url = new URL(req.url)\n\n if (url.pathname === \"/callback\") {\n const code = url.searchParams.get(\"code\")\n const error = url.searchParams.get(\"error\")\n\n if (error) {\n // Close server after a short delay\n setTimeout(() => server.stop(), 100)\n reject(new Error(`OAuth error: ${error}`))\n return new Response(\n `<html><body><h1>Authorization Failed</h1><p>Error: ${error}</p><p>You can close this window.</p></body></html>`,\n { headers: { \"Content-Type\": \"text/html\" } }\n )\n }\n\n if (code) {\n // Close server after a short delay\n setTimeout(() => server.stop(), 100)\n resolve(code)\n return new Response(\n `<html><body><h1>Authorization Successful!</h1><p>You can close this window and return to the terminal.</p></body></html>`,\n { headers: { \"Content-Type\": \"text/html\" } }\n )\n }\n\n return new Response(\"Missing authorization code\", { status: 400 })\n }\n\n return new Response(\"Not found\", { status: 404 })\n },\n })\n\n consola.info(`OAuth callback server started on http://localhost:8046`)\n\n // Timeout after 5 minutes\n setTimeout(() => {\n server.stop()\n reject(new Error(\"OAuth timeout - no callback received within 5 minutes\"))\n }, 5 * 60 * 1000)\n })\n}\n\n/**\n * Setup Antigravity with web-based OAuth flow\n * Opens browser for login and automatically captures the callback\n */\nexport async function setupAntigravityWeb(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (auth && auth.accounts.length > 0) {\n const enabledCount = auth.accounts.filter((a) => a.enable).length\n consola.info(\n `Found ${auth.accounts.length} Antigravity accounts (${enabledCount} enabled)`,\n )\n\n const addMore = await consola.prompt(\"Add another account?\", {\n type: \"confirm\",\n initial: false,\n })\n\n if (!addMore) {\n return\n }\n }\n\n consola.info(\"\")\n consola.info(\"Google Antigravity OAuth Setup (Web Flow)\")\n consola.info(\"=========================================\")\n consola.info(\"\")\n\n const oauthUrl = getOAuthUrl()\n consola.info(\"Opening browser for Google login...\")\n consola.info(`If browser doesn't open, visit: ${oauthUrl}`)\n consola.info(\"\")\n\n // Try to open browser automatically\n try {\n const { exec } = await import(\"node:child_process\")\n const platform = process.platform\n\n if (platform === \"win32\") {\n exec(`start \"\" \"${oauthUrl}\"`)\n } else if (platform === \"darwin\") {\n exec(`open \"${oauthUrl}\"`)\n } else {\n exec(`xdg-open \"${oauthUrl}\"`)\n }\n } catch {\n consola.warn(\"Could not open browser automatically\")\n }\n\n consola.info(\"Waiting for authorization...\")\n\n try {\n const code = await startOAuthCallbackServer()\n\n consola.info(\"Authorization code received, exchanging for tokens...\")\n\n const account = await exchangeCodeForTokens(code)\n\n if (!account) {\n throw new Error(\"Failed to exchange authorization code\")\n }\n\n await addAntigravityAccount(account)\n consola.success(\"Antigravity account added successfully!\")\n } catch (error) {\n consola.error(\"OAuth flow failed:\", error)\n throw error\n }\n}\n\n/**\n * Setup Antigravity interactively (manual URL copy method)\n */\nexport async function setupAntigravityManual(): Promise<void> {\n const auth = await loadAntigravityAuth()\n\n if (auth && auth.accounts.length > 0) {\n const enabledCount = auth.accounts.filter((a) => a.enable).length\n consola.info(\n `Found ${auth.accounts.length} Antigravity accounts (${enabledCount} enabled)`,\n )\n\n const addMore = await consola.prompt(\"Add another account?\", {\n type: \"confirm\",\n initial: false,\n })\n\n if (!addMore) {\n return\n }\n }\n\n consola.info(\"\")\n consola.info(\"Google Antigravity OAuth Setup (Manual)\")\n consola.info(\"=======================================\")\n consola.info(\"\")\n consola.info(\"You need to authorize with Google to use Antigravity API.\")\n consola.info(\"Please follow these steps:\")\n consola.info(\"\")\n consola.info(\"1. Open this URL in your browser:\")\n consola.info(` ${getOAuthUrl()}`)\n consola.info(\"\")\n consola.info(\"2. Complete the Google sign-in process\")\n consola.info(\"3. After authorization, you'll be redirected to a callback URL\")\n consola.info(\"4. Copy the full callback URL and paste it below\")\n consola.info(\"\")\n\n const callbackUrl = await consola.prompt(\"Enter the callback URL:\", {\n type: \"text\",\n })\n\n if (!callbackUrl || typeof callbackUrl !== \"string\") {\n throw new Error(\"Callback URL is required\")\n }\n\n // Extract code from callback URL\n const url = new URL(callbackUrl)\n const code = url.searchParams.get(\"code\")\n\n if (!code) {\n throw new Error(\"Authorization code not found in URL\")\n }\n\n consola.info(\"Exchanging authorization code for tokens...\")\n\n const account = await exchangeCodeForTokens(code)\n\n if (!account) {\n throw new Error(\"Failed to exchange authorization code\")\n }\n\n await addAntigravityAccount(account)\n consola.success(\"Antigravity account added successfully!\")\n}\n\n/**\n * Setup Antigravity interactively\n * Offers choice between web-based and manual OAuth flow\n */\nexport async function setupAntigravity(): Promise<void> {\n const method = await consola.prompt(\"Choose OAuth login method:\", {\n type: \"select\",\n options: [\n { value: \"web\", label: \"Web (auto-open browser, recommended)\" },\n { value: \"manual\", label: \"Manual (copy/paste callback URL)\" },\n ],\n })\n\n if (method === \"web\") {\n await setupAntigravityWeb()\n } else {\n await setupAntigravityManual()\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "./paths-Ch0ixSo2.js";
|
|
2
|
+
import { addAntigravityAccount, clearAntigravityAuth, disableCurrentAccount, exchangeCodeForTokens, generateRandomProjectId, getAntigravityAuthPath, getApiKey, getCurrentAccount, getOAuthUrl, getValidAccessToken, hasApiKey, isTokenExpired, loadAntigravityAuth, refreshAccessToken, rotateAccount, saveAntigravityAuth, setOAuthCredentials, setupAntigravity, setupAntigravityManual, setupAntigravityWeb } from "./auth-CsslwaJY.js";
|
|
3
|
+
|
|
4
|
+
export { getApiKey, getCurrentAccount, hasApiKey, loadAntigravityAuth, setOAuthCredentials, setupAntigravity };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./paths-Ch0ixSo2.js";
|
|
2
|
-
import "./auth-
|
|
3
|
-
import { getAntigravityModels, isThinkingModel } from "./get-models-
|
|
2
|
+
import "./auth-CsslwaJY.js";
|
|
3
|
+
import { getAntigravityModels, isThinkingModel } from "./get-models-DZR1TS4C.js";
|
|
4
4
|
|
|
5
5
|
export { getAntigravityModels };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getValidAccessToken } from "./auth-
|
|
1
|
+
import { getValidAccessToken } from "./auth-CsslwaJY.js";
|
|
2
2
|
import consola from "consola";
|
|
3
3
|
|
|
4
4
|
//#region src/services/antigravity/get-models.ts
|
|
@@ -215,4 +215,4 @@ function isThinkingModel(modelId) {
|
|
|
215
215
|
|
|
216
216
|
//#endregion
|
|
217
217
|
export { getAntigravityModels, isThinkingModel };
|
|
218
|
-
//# sourceMappingURL=get-models-
|
|
218
|
+
//# sourceMappingURL=get-models-DZR1TS4C.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-models-
|
|
1
|
+
{"version":3,"file":"get-models-DZR1TS4C.js","names":["FALLBACK_MODELS: Array<AntigravityModel>","cachedModels: Array<AntigravityModel> | null","cacheTimestamp: number","models: Array<AntigravityModel>"],"sources":["../src/services/antigravity/get-models.ts"],"sourcesContent":["/**\n * Google Antigravity Models\n *\n * Provides list of available models from Antigravity.\n * Based on: https://github.com/liuw1535/antigravity2api-nodejs\n */\n\n/* eslint-disable require-atomic-updates */\n\nimport consola from \"consola\"\n\nimport { getValidAccessToken } from \"./auth\"\n\n// Antigravity API endpoints\nconst ANTIGRAVITY_API_HOST = \"daily-cloudcode-pa.sandbox.googleapis.com\"\nconst ANTIGRAVITY_MODELS_URL = `https://${ANTIGRAVITY_API_HOST}/v1internal:fetchAvailableModels`\nconst ANTIGRAVITY_USER_AGENT = \"antigravity/1.11.3 windows/amd64\"\n\nexport interface AntigravityModel {\n id: string\n object: string\n created: number\n owned_by: string\n}\n\nexport interface AntigravityModelsResponse {\n object: string\n data: Array<AntigravityModel>\n}\n\n/**\n * Fallback Antigravity models when API is unavailable\n * Based on antigravity2api-nodejs model list\n */\nconst FALLBACK_MODELS: Array<AntigravityModel> = [\n // Gemini models\n {\n id: \"gemini-2.5-pro-exp-03-25\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.5-pro-preview-05-06\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.0-flash-exp\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.0-flash-001\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.0-flash-thinking-exp-1219\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.0-flash-thinking-exp\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.0-flash-thinking-exp-01-21\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-2.0-pro-exp-02-05\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-1.5-pro\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-1.5-flash\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-1.5-flash-8b\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"gemini-exp-1206\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n {\n id: \"learnlm-1.5-pro-experimental\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"google\",\n },\n\n // Claude models (via Antigravity)\n {\n id: \"claude-opus-4-5\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n {\n id: \"claude-sonnet-4-5\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n {\n id: \"claude-3-5-sonnet-20241022\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n {\n id: \"claude-3-5-haiku-20241022\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n {\n id: \"claude-3-opus-20240229\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n {\n id: \"claude-3-sonnet-20240229\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n {\n id: \"claude-3-haiku-20240307\",\n object: \"model\",\n created: 1700000000,\n owned_by: \"anthropic\",\n },\n]\n\n// Cache for fetched models\nlet cachedModels: Array<AntigravityModel> | null = null\nlet cacheTimestamp: number = 0\nconst CACHE_TTL = 5 * 60 * 1000 // 5 minutes\n\n/**\n * Fetch models from Antigravity API\n */\nasync function fetchModelsFromApi(): Promise<Array<AntigravityModel> | null> {\n const accessToken = await getValidAccessToken()\n\n if (!accessToken) {\n consola.debug(\"No access token available, using fallback models\")\n return null\n }\n\n try {\n const response = await fetch(ANTIGRAVITY_MODELS_URL, {\n method: \"POST\",\n headers: {\n Host: ANTIGRAVITY_API_HOST,\n \"User-Agent\": ANTIGRAVITY_USER_AGENT,\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n \"Accept-Encoding\": \"gzip\",\n },\n body: JSON.stringify({}),\n })\n\n if (!response.ok) {\n consola.warn(`Failed to fetch Antigravity models: ${response.status}`)\n return null\n }\n\n const data = (await response.json()) as {\n models?: Array<{\n name: string\n quotaInfo?: {\n remainingFraction?: number\n resetTime?: string\n }\n }>\n }\n\n if (!data.models || !Array.isArray(data.models)) {\n return null\n }\n\n // Convert to OpenAI format\n const models: Array<AntigravityModel> = data.models\n .filter((m) => {\n // Filter out models with no remaining quota\n const remaining = m.quotaInfo?.remainingFraction ?? 1\n return remaining > 0\n })\n .map((m) => {\n // Extract model ID from name (e.g., \"models/gemini-2.0-flash\" -> \"gemini-2.0-flash\")\n const modelId = m.name.replace(\"models/\", \"\")\n const isGoogle =\n modelId.startsWith(\"gemini\") || modelId.startsWith(\"learnlm\")\n\n return {\n id: modelId,\n object: \"model\",\n created: 1700000000,\n owned_by: isGoogle ? \"google\" : \"anthropic\",\n }\n })\n\n consola.debug(`Fetched ${models.length} models from Antigravity API`)\n return models\n } catch (error) {\n consola.warn(\"Error fetching Antigravity models:\", error)\n return null\n }\n}\n\n/**\n * Get available Antigravity models\n */\nexport async function getAntigravityModels(): Promise<AntigravityModelsResponse> {\n // Check cache\n if (cachedModels && Date.now() - cacheTimestamp < CACHE_TTL) {\n consola.debug(`Returning ${cachedModels.length} cached Antigravity models`)\n return {\n object: \"list\",\n data: cachedModels,\n }\n }\n\n // Try to fetch from API\n const apiModels = await fetchModelsFromApi()\n\n if (apiModels && apiModels.length > 0) {\n cachedModels = apiModels\n cacheTimestamp = Date.now()\n\n return {\n object: \"list\",\n data: apiModels,\n }\n }\n\n // Use fallback models\n consola.debug(\n `Returning ${FALLBACK_MODELS.length} fallback Antigravity models`,\n )\n\n return {\n object: \"list\",\n data: FALLBACK_MODELS,\n }\n}\n\n/**\n * Check if a model is a Claude model\n */\nexport function isClaudeModel(modelId: string): boolean {\n return modelId.startsWith(\"claude-\")\n}\n\n/**\n * Check if a model is a thinking/reasoning model\n */\nexport function isThinkingModel(modelId: string): boolean {\n return modelId.includes(\"thinking\")\n}\n\n/**\n * Check if a model is an image generation model\n */\nexport function isImageModel(modelId: string): boolean {\n return modelId.includes(\"image\")\n}\n"],"mappings":"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"}
|
|
@@ -10,8 +10,11 @@ const standardHeaders = () => ({
|
|
|
10
10
|
const COPILOT_VERSION = "0.26.7";
|
|
11
11
|
const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
|
|
12
12
|
const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
|
|
13
|
-
const API_VERSION = "2025-
|
|
14
|
-
const copilotBaseUrl = (state$1) =>
|
|
13
|
+
const API_VERSION = "2025-05-01";
|
|
14
|
+
const copilotBaseUrl = (state$1) => {
|
|
15
|
+
if (state$1.copilotApiEndpoint) return state$1.copilotApiEndpoint;
|
|
16
|
+
return state$1.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state$1.accountType}.githubcopilot.com`;
|
|
17
|
+
};
|
|
15
18
|
const copilotHeaders = (state$1, vision = false) => {
|
|
16
19
|
const headers = {
|
|
17
20
|
Authorization: `Bearer ${state$1.copilotToken}`,
|
|
@@ -55,4 +58,4 @@ async function getGitHubUser() {
|
|
|
55
58
|
|
|
56
59
|
//#endregion
|
|
57
60
|
export { GITHUB_API_BASE_URL, GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, copilotBaseUrl, copilotHeaders, getGitHubUser, githubHeaders, standardHeaders };
|
|
58
|
-
//# sourceMappingURL=get-user-
|
|
61
|
+
//# sourceMappingURL=get-user-BzIEATcF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-user-BzIEATcF.js","names":["state","headers: Record<string, string>"],"sources":["../src/lib/api-config.ts","../src/services/github/get-user.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\"\n\nimport type { State } from \"./state\"\n\nexport const standardHeaders = () => ({\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n})\n\nconst COPILOT_VERSION = \"0.26.7\"\nconst EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`\nconst USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`\n\n// Updated to match latest Zed implementation - 2025-05-01 returns Claude models\nconst API_VERSION = \"2025-05-01\"\n\n// Use the API endpoint from token response if available, otherwise fall back to default\nexport const copilotBaseUrl = (state: State) => {\n if (state.copilotApiEndpoint) {\n return state.copilotApiEndpoint\n }\n return state.accountType === \"individual\"\n ? \"https://api.githubcopilot.com\"\n : `https://api.${state.accountType}.githubcopilot.com`\n}\nexport const copilotHeaders = (state: State, vision: boolean = false) => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${state.copilotToken}`,\n \"content-type\": standardHeaders()[\"content-type\"],\n \"copilot-integration-id\": \"vscode-chat\",\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"openai-intent\": \"conversation-panel\",\n \"x-github-api-version\": API_VERSION,\n \"x-request-id\": randomUUID(),\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n }\n\n if (vision) headers[\"copilot-vision-request\"] = \"true\"\n\n return headers\n}\n\nexport const GITHUB_API_BASE_URL = \"https://api.github.com\"\nexport const githubHeaders = (state: State) => ({\n ...standardHeaders(),\n authorization: `token ${state.githubToken}`,\n \"editor-version\": `vscode/${state.vsCodeVersion}`,\n \"editor-plugin-version\": EDITOR_PLUGIN_VERSION,\n \"user-agent\": USER_AGENT,\n \"x-github-api-version\": API_VERSION,\n \"x-vscode-user-agent-library-version\": \"electron-fetch\",\n})\n\nexport const GITHUB_BASE_URL = \"https://github.com\"\nexport const GITHUB_CLIENT_ID = \"Iv1.b507a08c87ecfe98\"\nexport const GITHUB_APP_SCOPES = [\"read:user\"].join(\" \")\n","import { GITHUB_API_BASE_URL, standardHeaders } from \"~/lib/api-config\"\nimport { HTTPError } from \"~/lib/error\"\nimport { state } from \"~/lib/state\"\n\nexport async function getGitHubUser() {\n const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {\n headers: {\n authorization: `token ${state.githubToken}`,\n ...standardHeaders(),\n },\n })\n\n if (!response.ok) throw new HTTPError(\"Failed to get GitHub user\", response)\n\n return (await response.json()) as GithubUserResponse\n}\n\n// Trimmed for the sake of simplicity\ninterface GithubUserResponse {\n login: string\n}\n"],"mappings":";;;;;AAIA,MAAa,yBAAyB;CACpC,gBAAgB;CAChB,QAAQ;CACT;AAED,MAAM,kBAAkB;AACxB,MAAM,wBAAwB,gBAAgB;AAC9C,MAAM,aAAa,qBAAqB;AAGxC,MAAM,cAAc;AAGpB,MAAa,kBAAkB,YAAiB;AAC9C,KAAIA,QAAM,mBACR,QAAOA,QAAM;AAEf,QAAOA,QAAM,gBAAgB,eACzB,kCACA,eAAeA,QAAM,YAAY;;AAEvC,MAAa,kBAAkB,SAAc,SAAkB,UAAU;CACvE,MAAMC,UAAkC;EACtC,eAAe,UAAUD,QAAM;EAC/B,gBAAgB,iBAAiB,CAAC;EAClC,0BAA0B;EAC1B,kBAAkB,UAAUA,QAAM;EAClC,yBAAyB;EACzB,cAAc;EACd,iBAAiB;EACjB,wBAAwB;EACxB,gBAAgB,YAAY;EAC5B,uCAAuC;EACxC;AAED,KAAI,OAAQ,SAAQ,4BAA4B;AAEhD,QAAO;;AAGT,MAAa,sBAAsB;AACnC,MAAa,iBAAiB,aAAkB;CAC9C,GAAG,iBAAiB;CACpB,eAAe,SAASA,QAAM;CAC9B,kBAAkB,UAAUA,QAAM;CAClC,yBAAyB;CACzB,cAAc;CACd,wBAAwB;CACxB,uCAAuC;CACxC;AAED,MAAa,kBAAkB;AAC/B,MAAa,mBAAmB;AAChC,MAAa,oBAAoB,CAAC,YAAY,CAAC,KAAK,IAAI;;;;ACrDxD,eAAsB,gBAAgB;CACpC,MAAM,WAAW,MAAM,MAAM,GAAG,oBAAoB,QAAQ,EAC1D,SAAS;EACP,eAAe,SAAS,MAAM;EAC9B,GAAG,iBAAiB;EACrB,EACF,CAAC;AAEF,KAAI,CAAC,SAAS,GAAI,OAAM,IAAI,UAAU,6BAA6B,SAAS;AAE5E,QAAQ,MAAM,SAAS,MAAM"}
|