copilot-api-plus 1.2.17 → 1.2.18
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/dist/{account-manager-DmXXcFBW.js → account-manager-COJx3i2R.js} +255 -5
- package/dist/account-manager-COJx3i2R.js.map +1 -0
- package/dist/error-Ci5sfamJ.js +2 -0
- package/dist/get-user-HaNtbtZZ.js +2 -0
- package/dist/main.js +50 -155
- package/dist/main.js.map +1 -1
- package/dist/token-BlHBeJfE.js +3 -0
- package/dist/{token-BRQK8jBj.js → token-CswtzkcP.js} +2 -2
- package/dist/{token-BRQK8jBj.js.map → token-CswtzkcP.js.map} +1 -1
- package/package.json +1 -1
- package/dist/account-manager-DmXXcFBW.js.map +0 -1
- package/dist/error-Cc8bY0ph.js +0 -2
- package/dist/get-user-DHr540ak.js +0 -2
- package/dist/token-M99mSdhH.js +0 -3
package/dist/main.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { a as stopCopilotTokenRefresh, i as setupGitHubToken, n as refreshCopilotToken, o as pollAccessToken, r as setupCopilotToken, s as getDeviceCode, t as clearGithubToken } from "./token-
|
|
2
|
+
import { C as GITHUB_BASE_URL, D as standardHeaders, E as copilotHeaders, T as copilotBaseUrl, _ as findModel, a as getAccountDispatcher, b as sleep, c as notifyStreamStart, d as PATHS, f as ensurePaths, g as cacheVSCodeVersion, h as cacheModels, l as resetAccountConnections, m as forwardError, o as initProxyFromEnv, p as HTTPError, r as getCopilotUsage, s as notifyStreamEnd, t as accountManager, u as resetConnections, v as isNullish, w as GITHUB_CLIENT_ID, x as state, y as rootCause } from "./account-manager-COJx3i2R.js";
|
|
3
|
+
import { a as stopCopilotTokenRefresh, i as setupGitHubToken, n as refreshCopilotToken, o as pollAccessToken, r as setupCopilotToken, s as getDeviceCode, t as clearGithubToken } from "./token-CswtzkcP.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
6
6
|
import consola from "consola";
|
|
@@ -8,8 +8,6 @@ import { timingSafeEqual } from "node:crypto";
|
|
|
8
8
|
import fs from "node:fs/promises";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import path from "node:path";
|
|
11
|
-
import { getProxyForUrl } from "proxy-from-env";
|
|
12
|
-
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
13
11
|
import * as p from "@clack/prompts";
|
|
14
12
|
import clipboard from "clipboardy";
|
|
15
13
|
import { serve } from "srvx";
|
|
@@ -119,144 +117,6 @@ async function applyProxyConfig() {
|
|
|
119
117
|
return true;
|
|
120
118
|
}
|
|
121
119
|
//#endregion
|
|
122
|
-
//#region src/lib/proxy.ts
|
|
123
|
-
const agentOptions = {
|
|
124
|
-
keepAliveTimeout: 3e5,
|
|
125
|
-
keepAliveMaxTimeout: 6e5,
|
|
126
|
-
allowH2: true,
|
|
127
|
-
connect: {
|
|
128
|
-
timeout: 15e3,
|
|
129
|
-
keepAlive: true,
|
|
130
|
-
keepAliveInitialDelay: 15e3
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
let direct;
|
|
134
|
-
let proxies = /* @__PURE__ */ new Map();
|
|
135
|
-
/** Whether a proxy is actually configured and in use. */
|
|
136
|
-
let proxyActive = false;
|
|
137
|
-
/**
|
|
138
|
-
* Many proxy nodes (especially third-party VPN/airport services) kill
|
|
139
|
-
* CONNECT tunnels that are idle for ~60 s. During long model thinking
|
|
140
|
-
* phases the SSE stream carries no data, which looks "idle" to the proxy.
|
|
141
|
-
*
|
|
142
|
-
* This keepalive sends a tiny HEAD request to the Copilot API every 45 s
|
|
143
|
-
* through the same proxy. The encrypted packets flowing through the
|
|
144
|
-
* CONNECT tunnel reset the proxy's idle timer, keeping the tunnel alive.
|
|
145
|
-
*
|
|
146
|
-
* The keepalive is active ONLY while there are SSE streams in flight
|
|
147
|
-
* (tracked via `streamCount`). When no streams are active it stops to
|
|
148
|
-
* avoid unnecessary traffic.
|
|
149
|
-
*/
|
|
150
|
-
let keepaliveTimer;
|
|
151
|
-
let streamCount = 0;
|
|
152
|
-
const KEEPALIVE_INTERVAL_MS = 45e3;
|
|
153
|
-
const KEEPALIVE_URL = "https://api.individual.githubcopilot.com/";
|
|
154
|
-
function startKeepalive() {
|
|
155
|
-
if (keepaliveTimer) return;
|
|
156
|
-
keepaliveTimer = setInterval(() => {
|
|
157
|
-
fetch(KEEPALIVE_URL, { method: "HEAD" }).catch(() => {});
|
|
158
|
-
consola.debug("Proxy keepalive ping sent");
|
|
159
|
-
}, KEEPALIVE_INTERVAL_MS);
|
|
160
|
-
keepaliveTimer.unref();
|
|
161
|
-
consola.debug("Proxy keepalive started (45 s interval)");
|
|
162
|
-
}
|
|
163
|
-
function stopKeepalive() {
|
|
164
|
-
if (keepaliveTimer) {
|
|
165
|
-
clearInterval(keepaliveTimer);
|
|
166
|
-
keepaliveTimer = void 0;
|
|
167
|
-
consola.debug("Proxy keepalive stopped (no active streams)");
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Call when an SSE stream starts. Activates the proxy-tunnel keepalive
|
|
172
|
-
* if this is the first active stream and a proxy is configured.
|
|
173
|
-
*/
|
|
174
|
-
function notifyStreamStart() {
|
|
175
|
-
if (!proxyActive) return;
|
|
176
|
-
streamCount++;
|
|
177
|
-
if (streamCount === 1) startKeepalive();
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Call when an SSE stream ends (success or error). Stops the keepalive
|
|
181
|
-
* once no streams are active.
|
|
182
|
-
*/
|
|
183
|
-
function notifyStreamEnd() {
|
|
184
|
-
if (!proxyActive) return;
|
|
185
|
-
streamCount = Math.max(0, streamCount - 1);
|
|
186
|
-
if (streamCount === 0) stopKeepalive();
|
|
187
|
-
}
|
|
188
|
-
function initProxyFromEnv() {
|
|
189
|
-
if (typeof Bun !== "undefined") return;
|
|
190
|
-
try {
|
|
191
|
-
direct = new Agent(agentOptions);
|
|
192
|
-
proxies = /* @__PURE__ */ new Map();
|
|
193
|
-
setGlobalDispatcher({
|
|
194
|
-
dispatch(options, handler) {
|
|
195
|
-
try {
|
|
196
|
-
const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin;
|
|
197
|
-
const raw = getProxyForUrl(origin.toString());
|
|
198
|
-
const proxyUrl = raw && raw.length > 0 ? raw : void 0;
|
|
199
|
-
if (!proxyUrl) {
|
|
200
|
-
consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
|
|
201
|
-
return direct.dispatch(options, handler);
|
|
202
|
-
}
|
|
203
|
-
let agent = proxies.get(proxyUrl);
|
|
204
|
-
if (!agent) {
|
|
205
|
-
agent = new ProxyAgent({
|
|
206
|
-
uri: proxyUrl,
|
|
207
|
-
...agentOptions
|
|
208
|
-
});
|
|
209
|
-
proxies.set(proxyUrl, agent);
|
|
210
|
-
}
|
|
211
|
-
let label = proxyUrl;
|
|
212
|
-
try {
|
|
213
|
-
const u = new URL(proxyUrl);
|
|
214
|
-
label = `${u.protocol}//${u.host}`;
|
|
215
|
-
} catch {}
|
|
216
|
-
consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`);
|
|
217
|
-
return agent.dispatch(options, handler);
|
|
218
|
-
} catch {
|
|
219
|
-
return direct.dispatch(options, handler);
|
|
220
|
-
}
|
|
221
|
-
},
|
|
222
|
-
close() {
|
|
223
|
-
for (const agent of proxies.values()) agent.close();
|
|
224
|
-
return direct.close();
|
|
225
|
-
},
|
|
226
|
-
destroy() {
|
|
227
|
-
for (const agent of proxies.values()) agent.destroy();
|
|
228
|
-
return direct.destroy();
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
proxyActive = true;
|
|
232
|
-
consola.debug("HTTP proxy configured from environment (per-URL)");
|
|
233
|
-
} catch (err) {
|
|
234
|
-
consola.debug("Proxy setup skipped:", err);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Destroy all pooled connections (direct + proxy agents) and replace them
|
|
239
|
-
* with fresh instances. The global dispatcher's `dispatch` method captures
|
|
240
|
-
* `direct` and `proxies` by reference, so subsequent requests automatically
|
|
241
|
-
* use the new agents — no need to call `setGlobalDispatcher` again.
|
|
242
|
-
*
|
|
243
|
-
* Call this after a network error to discard stale/half-closed sockets that
|
|
244
|
-
* would otherwise cause every retry to wait ~60 s before timing out.
|
|
245
|
-
*
|
|
246
|
-
* Under the Bun runtime (which doesn't use undici) this is a no-op.
|
|
247
|
-
*/
|
|
248
|
-
function resetConnections() {
|
|
249
|
-
if (typeof Bun !== "undefined") return;
|
|
250
|
-
if (!direct) return;
|
|
251
|
-
const oldDirect = direct;
|
|
252
|
-
const oldProxies = proxies;
|
|
253
|
-
direct = new Agent(agentOptions);
|
|
254
|
-
proxies = /* @__PURE__ */ new Map();
|
|
255
|
-
oldDirect.close().catch(() => {});
|
|
256
|
-
for (const agent of oldProxies.values()) agent.close().catch(() => {});
|
|
257
|
-
consola.debug("Connection pool reset — stale sockets cleared");
|
|
258
|
-
}
|
|
259
|
-
//#endregion
|
|
260
120
|
//#region src/account.ts
|
|
261
121
|
const addAccount = defineCommand({
|
|
262
122
|
meta: {
|
|
@@ -1490,6 +1350,10 @@ accountRoutes.post("/", async (c) => {
|
|
|
1490
1350
|
const body = await c.req.json();
|
|
1491
1351
|
if (!body.githubToken || !body.label) return c.json({ error: "githubToken and label are required" }, 400);
|
|
1492
1352
|
const account = await accountManager.addAccount(body.githubToken, body.label, body.accountType);
|
|
1353
|
+
if (body.proxy) {
|
|
1354
|
+
account.proxy = body.proxy;
|
|
1355
|
+
await accountManager.saveAccounts();
|
|
1356
|
+
}
|
|
1493
1357
|
return c.json({ account: sanitiseAccount(account) }, 201);
|
|
1494
1358
|
} catch (error) {
|
|
1495
1359
|
consola.warn(`Error adding account: ${rootCause(error)}`);
|
|
@@ -1787,20 +1651,29 @@ const RETRY_DELAYS = [2e3];
|
|
|
1787
1651
|
* or interruption.
|
|
1788
1652
|
*/
|
|
1789
1653
|
const RETRY_TIMEOUT_MS = 3e4;
|
|
1654
|
+
/** Minimum interval (ms) between requests on the same account. */
|
|
1655
|
+
const MIN_SAME_ACCOUNT_INTERVAL_MS = 1e3;
|
|
1656
|
+
/** Random jitter range (ms) added when switching between accounts. */
|
|
1657
|
+
const ACCOUNT_SWITCH_JITTER_MIN_MS = 1e3;
|
|
1658
|
+
const ACCOUNT_SWITCH_JITTER_MAX_MS = 5e3;
|
|
1659
|
+
/** Track the last-used account ID to detect account switches. */
|
|
1660
|
+
let lastUsedAccountId;
|
|
1790
1661
|
/**
|
|
1791
1662
|
* Wrapper around `fetch()` that aborts if the server doesn't respond within
|
|
1792
1663
|
* `timeoutMs`. The timeout only covers the period until the response headers
|
|
1793
1664
|
* arrive – once the body starts streaming, the timeout is cleared so that
|
|
1794
1665
|
* long SSE responses are not interrupted.
|
|
1795
1666
|
*/
|
|
1796
|
-
async function fetchWithTimeout(url, init, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
1667
|
+
async function fetchWithTimeout(url, init, { timeoutMs = FETCH_TIMEOUT_MS, accountId, accountProxy } = {}) {
|
|
1797
1668
|
const controller = new AbortController();
|
|
1798
1669
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1799
1670
|
try {
|
|
1800
|
-
|
|
1671
|
+
const fetchOptions = {
|
|
1801
1672
|
...init,
|
|
1802
1673
|
signal: controller.signal
|
|
1803
|
-
}
|
|
1674
|
+
};
|
|
1675
|
+
if (accountId) fetchOptions.dispatcher = getAccountDispatcher(accountId, accountProxy);
|
|
1676
|
+
return await fetch(url, fetchOptions);
|
|
1804
1677
|
} catch (error) {
|
|
1805
1678
|
if (error instanceof DOMException && error.name === "AbortError") throw new Error(`Request timed out after ${timeoutMs}ms`);
|
|
1806
1679
|
throw error;
|
|
@@ -1814,12 +1687,16 @@ async function fetchWithTimeout(url, init, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
|
1814
1687
|
* Returns `{ response }` on success.
|
|
1815
1688
|
* Throws the last network error if all retries are exhausted.
|
|
1816
1689
|
*/
|
|
1817
|
-
async function fetchWithRetry(url, buildInit) {
|
|
1690
|
+
async function fetchWithRetry(url, buildInit, { accountId, accountProxy } = {}) {
|
|
1818
1691
|
let lastError;
|
|
1819
1692
|
const maxAttempts = RETRY_DELAYS.length + 1;
|
|
1820
1693
|
for (let attempt = 0; attempt < maxAttempts; attempt++) try {
|
|
1821
1694
|
const timeout = attempt === 0 ? FETCH_TIMEOUT_MS : RETRY_TIMEOUT_MS;
|
|
1822
|
-
return await fetchWithTimeout(url, buildInit(),
|
|
1695
|
+
return await fetchWithTimeout(url, buildInit(), {
|
|
1696
|
+
timeoutMs: timeout,
|
|
1697
|
+
accountId,
|
|
1698
|
+
accountProxy
|
|
1699
|
+
});
|
|
1823
1700
|
} catch (error) {
|
|
1824
1701
|
lastError = error;
|
|
1825
1702
|
const msg = error instanceof Error ? error.message : String(error);
|
|
@@ -1827,7 +1704,8 @@ async function fetchWithRetry(url, buildInit) {
|
|
|
1827
1704
|
consola.warn(`Request timed out on attempt ${attempt + 1}/${maxAttempts} — not retrying (credit likely consumed):`, msg);
|
|
1828
1705
|
break;
|
|
1829
1706
|
}
|
|
1830
|
-
if (attempt === 0)
|
|
1707
|
+
if (attempt === 0) if (accountId) resetAccountConnections(accountId);
|
|
1708
|
+
else resetConnections();
|
|
1831
1709
|
if (attempt < maxAttempts - 1) {
|
|
1832
1710
|
const delay = RETRY_DELAYS[attempt];
|
|
1833
1711
|
consola.warn(`Network error on attempt ${attempt + 1}/${maxAttempts}, retrying in ${delay}ms:`, error instanceof Error ? error.message : error);
|
|
@@ -2117,10 +1995,24 @@ async function createWithMultiAccount(payload) {
|
|
|
2117
1995
|
copilotApiEndpoint: account.copilotApiEndpoint,
|
|
2118
1996
|
accountType: account.accountType,
|
|
2119
1997
|
githubToken: account.githubToken,
|
|
2120
|
-
vsCodeVersion: state.vsCodeVersion
|
|
1998
|
+
vsCodeVersion: state.vsCodeVersion,
|
|
1999
|
+
machineId: account.machineId,
|
|
2000
|
+
sessionId: account.sessionId,
|
|
2001
|
+
proxy: account.proxy
|
|
2121
2002
|
};
|
|
2122
2003
|
try {
|
|
2123
|
-
|
|
2004
|
+
if (account.lastRequestAt) {
|
|
2005
|
+
const elapsed = Date.now() - account.lastRequestAt;
|
|
2006
|
+
if (elapsed < MIN_SAME_ACCOUNT_INTERVAL_MS) await new Promise((r) => setTimeout(r, MIN_SAME_ACCOUNT_INTERVAL_MS - elapsed));
|
|
2007
|
+
}
|
|
2008
|
+
if (lastUsedAccountId && lastUsedAccountId !== account.id) {
|
|
2009
|
+
const jitter = ACCOUNT_SWITCH_JITTER_MIN_MS + Math.random() * (ACCOUNT_SWITCH_JITTER_MAX_MS - ACCOUNT_SWITCH_JITTER_MIN_MS);
|
|
2010
|
+
consola.debug(`Account switch jitter: ${Math.round(jitter)}ms (${lastUsedAccountId.slice(0, 8)} → ${account.id.slice(0, 8)})`);
|
|
2011
|
+
await new Promise((r) => setTimeout(r, jitter));
|
|
2012
|
+
}
|
|
2013
|
+
lastUsedAccountId = account.id;
|
|
2014
|
+
const result = await doFetch(payload, tokenSource, account.id);
|
|
2015
|
+
account.lastRequestAt = Date.now();
|
|
2124
2016
|
accountManager.markAccountSuccess(account.id);
|
|
2125
2017
|
return result;
|
|
2126
2018
|
} catch (error) {
|
|
@@ -2145,7 +2037,7 @@ async function createWithMultiAccount(payload) {
|
|
|
2145
2037
|
* call it with different `TokenSource` objects while keeping all the header
|
|
2146
2038
|
* construction / retry / error‐surfacing logic in one place.
|
|
2147
2039
|
*/
|
|
2148
|
-
async function doFetch(payload, source) {
|
|
2040
|
+
async function doFetch(payload, source, accountId) {
|
|
2149
2041
|
const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
|
|
2150
2042
|
const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
|
|
2151
2043
|
const buildHeaders = () => ({
|
|
@@ -2166,7 +2058,10 @@ async function doFetch(payload, source) {
|
|
|
2166
2058
|
method: "POST",
|
|
2167
2059
|
headers: buildHeaders(),
|
|
2168
2060
|
body: bodyString
|
|
2169
|
-
})
|
|
2061
|
+
}), {
|
|
2062
|
+
accountId,
|
|
2063
|
+
accountProxy: source.proxy
|
|
2064
|
+
});
|
|
2170
2065
|
if (!response.ok) {
|
|
2171
2066
|
const errorBody = await response.text();
|
|
2172
2067
|
if (response.status === 400) consola.warn(`400: ${errorBody}`);
|
|
@@ -3188,7 +3083,7 @@ async function validateGitHubToken(token) {
|
|
|
3188
3083
|
state.githubToken = token;
|
|
3189
3084
|
consola.info("Using provided GitHub token");
|
|
3190
3085
|
try {
|
|
3191
|
-
const { getGitHubUser } = await import("./get-user-
|
|
3086
|
+
const { getGitHubUser } = await import("./get-user-HaNtbtZZ.js");
|
|
3192
3087
|
const user = await getGitHubUser();
|
|
3193
3088
|
consola.info(`Logged in as ${user.login}`);
|
|
3194
3089
|
} catch (error) {
|
|
@@ -3239,10 +3134,10 @@ async function runServer(options) {
|
|
|
3239
3134
|
try {
|
|
3240
3135
|
await setupCopilotToken();
|
|
3241
3136
|
} catch (error) {
|
|
3242
|
-
const { HTTPError } = await import("./error-
|
|
3137
|
+
const { HTTPError } = await import("./error-Ci5sfamJ.js");
|
|
3243
3138
|
if (error instanceof HTTPError && error.response.status === 401) {
|
|
3244
3139
|
consola.error("Failed to get Copilot token - GitHub token may be invalid or Copilot access revoked");
|
|
3245
|
-
const { clearGithubToken } = await import("./token-
|
|
3140
|
+
const { clearGithubToken } = await import("./token-BlHBeJfE.js");
|
|
3246
3141
|
await clearGithubToken();
|
|
3247
3142
|
consola.info("Please restart to re-authenticate");
|
|
3248
3143
|
}
|