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/main.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { _ as GITHUB_BASE_URL, a as PATHS, b as copilotHeaders, c as forwardError, d as findModel, f as isNullish, h as state, l as cacheModels, m as sleep, o as ensurePaths, p as rootCause, r as getCopilotUsage, s as HTTPError, t as accountManager, u as cacheVSCodeVersion, v as GITHUB_CLIENT_ID, x as standardHeaders, y as copilotBaseUrl } from "./account-manager-DmXXcFBW.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-BRQK8jBj.js";
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
- return await fetch(url, {
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(), timeout);
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) resetConnections();
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
- const result = await doFetch(payload, tokenSource);
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-DHr540ak.js");
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-Cc8bY0ph.js");
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-M99mSdhH.js");
3140
+ const { clearGithubToken } = await import("./token-BlHBeJfE.js");
3246
3141
  await clearGithubToken();
3247
3142
  consola.info("Please restart to re-authenticate");
3248
3143
  }