copilot-api-plus 1.2.18 → 1.2.20

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 { 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";
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-B9daQhPM.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-9T4XDHX4.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { defineCommand, runMain } from "citty";
6
6
  import consola from "consola";
@@ -1351,6 +1351,16 @@ accountRoutes.post("/", async (c) => {
1351
1351
  if (!body.githubToken || !body.label) return c.json({ error: "githubToken and label are required" }, 400);
1352
1352
  const account = await accountManager.addAccount(body.githubToken, body.label, body.accountType);
1353
1353
  if (body.proxy) {
1354
+ try {
1355
+ const proxyUrl = new URL(body.proxy);
1356
+ if (![
1357
+ "http:",
1358
+ "https:",
1359
+ "socks5:"
1360
+ ].includes(proxyUrl.protocol)) return c.json({ error: "proxy must use http://, https://, or socks5:// protocol" }, 400);
1361
+ } catch {
1362
+ return c.json({ error: "proxy must be a valid URL" }, 400);
1363
+ }
1354
1364
  account.proxy = body.proxy;
1355
1365
  await accountManager.saveAccounts();
1356
1366
  }
@@ -1393,6 +1403,33 @@ accountRoutes.put("/:id/status", async (c) => {
1393
1403
  return c.json({ error: "Failed to update account status" }, 500);
1394
1404
  }
1395
1405
  });
1406
+ accountRoutes.put("/:id/proxy", async (c) => {
1407
+ try {
1408
+ const id = c.req.param("id");
1409
+ const body = await c.req.json();
1410
+ const account = accountManager.getAccountById(id);
1411
+ if (!account) return c.json({ error: "Account not found" }, 404);
1412
+ if (body.proxy) {
1413
+ try {
1414
+ const proxyUrl = new URL(body.proxy);
1415
+ if (![
1416
+ "http:",
1417
+ "https:",
1418
+ "socks5:"
1419
+ ].includes(proxyUrl.protocol)) return c.json({ error: "proxy must use http://, https://, or socks5:// protocol" }, 400);
1420
+ } catch {
1421
+ return c.json({ error: "proxy must be a valid URL" }, 400);
1422
+ }
1423
+ account.proxy = body.proxy;
1424
+ } else account.proxy = void 0;
1425
+ await accountManager.saveAccounts();
1426
+ return c.json({ account: sanitiseAccount(account) });
1427
+ } catch (error) {
1428
+ consola.warn(`Error updating account proxy: ${rootCause(error)}`);
1429
+ consola.debug("Error updating account proxy:", error);
1430
+ return c.json({ error: "Failed to update account proxy" }, 500);
1431
+ }
1432
+ });
1396
1433
  accountRoutes.post("/:id/refresh", async (c) => {
1397
1434
  try {
1398
1435
  const id = c.req.param("id");
@@ -1630,27 +1667,6 @@ async function checkRateLimit(state) {
1630
1667
  * ~120s to start streaming, so we give a generous timeout for headers.
1631
1668
  */
1632
1669
  const FETCH_TIMEOUT_MS = 12e4;
1633
- /**
1634
- * Retry delays in ms. After the first failure the connection pool is reset
1635
- * (see `resetConnections`), so retries use fresh sockets.
1636
- *
1637
- * We only allow 1 retry (2 total attempts) to minimize credit waste.
1638
- * Timeout errors are NOT retried at all — they indicate the request likely
1639
- * reached Copilot (consuming a credit) and the upstream is slow or the
1640
- * proxy killed the connection mid-flight.
1641
- */
1642
- const RETRY_DELAYS = [2e3];
1643
- /**
1644
- * Timeout for retry attempts (waiting for response headers only).
1645
- * Response headers typically arrive within 3–5 s, even on slow models.
1646
- * 30 s is generous enough for a fresh socket to connect and receive
1647
- * headers, while still failing fast when the upstream is truly down.
1648
- *
1649
- * NOTE: This does NOT affect the SSE streaming phase — once headers
1650
- * arrive, the timeout is cleared and the stream runs until completion
1651
- * or interruption.
1652
- */
1653
- const RETRY_TIMEOUT_MS = 3e4;
1654
1670
  /** Minimum interval (ms) between requests on the same account. */
1655
1671
  const MIN_SAME_ACCOUNT_INTERVAL_MS = 1e3;
1656
1672
  /** Random jitter range (ms) added when switching between accounts. */
@@ -1682,45 +1698,36 @@ async function fetchWithTimeout(url, init, { timeoutMs = FETCH_TIMEOUT_MS, accou
1682
1698
  }
1683
1699
  }
1684
1700
  /**
1685
- * Retry loop for fetch: retries on network errors with exponential back-off.
1701
+ * Single-attempt fetch with connection pool reset on network errors.
1702
+ *
1703
+ * Retries are intentionally disabled — each Copilot request consumes a
1704
+ * credit, and the caller (e.g. Claude Code) already retries at the
1705
+ * application level. Our retry + caller retry created a request cascade
1706
+ * that caused account bans (367 requests in 52 minutes).
1686
1707
  *
1687
- * Returns `{ response }` on success.
1688
- * Throws the last network error if all retries are exhausted.
1708
+ * On network failure (NOT timeout), the pooled connections are destroyed
1709
+ * so that the caller's next attempt gets a fresh socket instantly.
1689
1710
  */
1690
1711
  async function fetchWithRetry(url, buildInit, { accountId, accountProxy } = {}) {
1691
- let lastError;
1692
- const maxAttempts = RETRY_DELAYS.length + 1;
1693
- for (let attempt = 0; attempt < maxAttempts; attempt++) try {
1694
- const timeout = attempt === 0 ? FETCH_TIMEOUT_MS : RETRY_TIMEOUT_MS;
1712
+ try {
1695
1713
  return await fetchWithTimeout(url, buildInit(), {
1696
- timeoutMs: timeout,
1714
+ timeoutMs: FETCH_TIMEOUT_MS,
1697
1715
  accountId,
1698
1716
  accountProxy
1699
1717
  });
1700
1718
  } catch (error) {
1701
- lastError = error;
1702
- const msg = error instanceof Error ? error.message : String(error);
1703
- if (msg.includes("timed out")) {
1704
- consola.warn(`Request timed out on attempt ${attempt + 1}/${maxAttempts} — not retrying (credit likely consumed):`, msg);
1705
- break;
1706
- }
1707
- if (attempt === 0) if (accountId) resetAccountConnections(accountId);
1719
+ if (!(error instanceof Error ? error.message : String(error)).includes("timed out")) if (accountId) resetAccountConnections(accountId);
1708
1720
  else resetConnections();
1709
- if (attempt < maxAttempts - 1) {
1710
- const delay = RETRY_DELAYS[attempt];
1711
- consola.warn(`Network error on attempt ${attempt + 1}/${maxAttempts}, retrying in ${delay}ms:`, error instanceof Error ? error.message : error);
1712
- await new Promise((r) => setTimeout(r, delay));
1713
- }
1721
+ throw error;
1714
1722
  }
1715
- throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Network request failed");
1716
1723
  }
1717
1724
  /**
1718
1725
  * Wraps an AsyncGenerator so that `releaseSlot` is called when the generator
1719
1726
  * finishes (return or throw), not when the outer function returns.
1720
1727
  * Also tracks active streams for the proxy-tunnel keepalive mechanism.
1721
1728
  */
1722
- async function* wrapGeneratorWithRelease(gen, releaseSlot) {
1723
- notifyStreamStart();
1729
+ async function* wrapGeneratorWithRelease(gen, releaseSlot, accountInfo) {
1730
+ notifyStreamStart(accountInfo);
1724
1731
  let streamError = false;
1725
1732
  try {
1726
1733
  yield* gen;
@@ -1728,9 +1735,10 @@ async function* wrapGeneratorWithRelease(gen, releaseSlot) {
1728
1735
  streamError = true;
1729
1736
  throw error;
1730
1737
  } finally {
1731
- notifyStreamEnd();
1738
+ notifyStreamEnd(accountInfo);
1732
1739
  releaseSlot();
1733
- if (streamError) resetConnections();
1740
+ if (streamError) if (accountInfo?.accountId) resetAccountConnections(accountInfo.accountId);
1741
+ else resetConnections();
1734
1742
  }
1735
1743
  }
1736
1744
  /**
@@ -1818,7 +1826,10 @@ const createChatCompletions = async (payload) => {
1818
1826
  const releaseSlot = await modelRouter.acquireSlot(resolvedModel);
1819
1827
  try {
1820
1828
  const result = await dispatchRequest(thinkingPayload);
1821
- if (Symbol.asyncIterator in result) return wrapGeneratorWithRelease(result, releaseSlot);
1829
+ if (Symbol.asyncIterator in result) {
1830
+ const accountInfo = result.__accountInfo;
1831
+ return wrapGeneratorWithRelease(result, releaseSlot, accountInfo);
1832
+ }
1822
1833
  releaseSlot();
1823
1834
  return result;
1824
1835
  } catch (error) {
@@ -1827,14 +1838,14 @@ const createChatCompletions = async (payload) => {
1827
1838
  if (wasInjected && errMsg.includes("Unrecognized request argument")) {
1828
1839
  reasoningUnsupportedModels.add(resolvedModel);
1829
1840
  consola.info(`Model "${resolvedModel}" does not support reasoning_effort — disabled for future requests`);
1830
- return retryWithoutReasoning(routedPayload, releaseSlot);
1841
+ return retryWithModifiedPayload(routedPayload, releaseSlot);
1831
1842
  }
1832
1843
  if (errMsg.includes("is not supported by model")) {
1833
1844
  const currentEffort = thinkingPayload.reasoning_effort;
1834
1845
  if (currentEffort && currentEffort !== "medium" && currentEffort !== "low") {
1835
1846
  reasoningEffortCap.set(resolvedModel, "medium");
1836
1847
  consola.info(`Model "${resolvedModel}" rejected reasoning_effort="${currentEffort}" — downgrading to "medium" for future requests`);
1837
- return retryWithDowngradedReasoning({
1848
+ return retryWithModifiedPayload({
1838
1849
  ...routedPayload,
1839
1850
  reasoning_effort: "medium"
1840
1851
  }, releaseSlot);
@@ -1846,28 +1857,17 @@ const createChatCompletions = async (payload) => {
1846
1857
  }
1847
1858
  };
1848
1859
  /**
1849
- * Retry a request without reasoning_effort after the model rejected it.
1860
+ * Retry a request after modifying the payload (e.g. stripping or
1861
+ * downgrading reasoning_effort).
1850
1862
  * Handles slot release for both streaming and non-streaming responses.
1851
1863
  */
1852
- async function retryWithoutReasoning(payload, releaseSlot) {
1864
+ async function retryWithModifiedPayload(payload, releaseSlot) {
1853
1865
  try {
1854
1866
  const result = await dispatchRequest(payload);
1855
- if (Symbol.asyncIterator in result) return wrapGeneratorWithRelease(result, releaseSlot);
1856
- releaseSlot();
1857
- return result;
1858
- } catch (retryError) {
1859
- releaseSlot();
1860
- throw retryError;
1861
- }
1862
- }
1863
- /**
1864
- * Retry a request with a downgraded reasoning_effort after the model
1865
- * rejected the higher value (e.g. "high" → "medium").
1866
- */
1867
- async function retryWithDowngradedReasoning(payload, releaseSlot) {
1868
- try {
1869
- const result = await dispatchRequest(payload);
1870
- if (Symbol.asyncIterator in result) return wrapGeneratorWithRelease(result, releaseSlot);
1867
+ if (Symbol.asyncIterator in result) {
1868
+ const accountInfo = result.__accountInfo;
1869
+ return wrapGeneratorWithRelease(result, releaseSlot, accountInfo);
1870
+ }
1871
1871
  releaseSlot();
1872
1872
  return result;
1873
1873
  } catch (retryError) {
@@ -1883,7 +1883,7 @@ function dispatchRequest(payload) {
1883
1883
  }
1884
1884
  async function createWithSingleAccount(payload) {
1885
1885
  if (!state.copilotToken) throw new Error("Copilot token not found");
1886
- const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
1886
+ const enableVision = payload.messages.some((msg) => typeof msg.content !== "string" && msg.content?.some((part) => part.type === "image_url"));
1887
1887
  const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
1888
1888
  const buildHeaders = () => ({
1889
1889
  ...copilotHeaders(state, enableVision),
@@ -1939,7 +1939,7 @@ async function tryRefreshAndRetry(account, payload, tokenSource) {
1939
1939
  try {
1940
1940
  await accountManager.refreshAccountToken(account);
1941
1941
  tokenSource.copilotToken = account.copilotToken;
1942
- const result = await doFetch(payload, tokenSource);
1942
+ const result = await doFetch(payload, tokenSource, account.id);
1943
1943
  accountManager.markAccountSuccess(account.id);
1944
1944
  return result;
1945
1945
  } catch {
@@ -2014,6 +2014,10 @@ async function createWithMultiAccount(payload) {
2014
2014
  const result = await doFetch(payload, tokenSource, account.id);
2015
2015
  account.lastRequestAt = Date.now();
2016
2016
  accountManager.markAccountSuccess(account.id);
2017
+ if (Symbol.asyncIterator in result) result.__accountInfo = {
2018
+ accountId: account.id,
2019
+ accountProxy: account.proxy
2020
+ };
2017
2021
  return result;
2018
2022
  } catch (error) {
2019
2023
  lastError = error;
@@ -2038,7 +2042,7 @@ async function createWithMultiAccount(payload) {
2038
2042
  * construction / retry / error‐surfacing logic in one place.
2039
2043
  */
2040
2044
  async function doFetch(payload, source, accountId) {
2041
- const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
2045
+ const enableVision = payload.messages.some((msg) => typeof msg.content !== "string" && msg.content?.some((part) => part.type === "image_url"));
2042
2046
  const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
2043
2047
  const buildHeaders = () => ({
2044
2048
  ...copilotHeaders(source, enableVision),
@@ -3083,7 +3087,7 @@ async function validateGitHubToken(token) {
3083
3087
  state.githubToken = token;
3084
3088
  consola.info("Using provided GitHub token");
3085
3089
  try {
3086
- const { getGitHubUser } = await import("./get-user-HaNtbtZZ.js");
3090
+ const { getGitHubUser } = await import("./get-user-q-uqgjND.js");
3087
3091
  const user = await getGitHubUser();
3088
3092
  consola.info(`Logged in as ${user.login}`);
3089
3093
  } catch (error) {
@@ -3134,10 +3138,10 @@ async function runServer(options) {
3134
3138
  try {
3135
3139
  await setupCopilotToken();
3136
3140
  } catch (error) {
3137
- const { HTTPError } = await import("./error-Ci5sfamJ.js");
3141
+ const { HTTPError } = await import("./error-oHPe3O3W.js");
3138
3142
  if (error instanceof HTTPError && error.response.status === 401) {
3139
3143
  consola.error("Failed to get Copilot token - GitHub token may be invalid or Copilot access revoked");
3140
- const { clearGithubToken } = await import("./token-BlHBeJfE.js");
3144
+ const { clearGithubToken } = await import("./token-byWuxeZE.js");
3141
3145
  await clearGithubToken();
3142
3146
  consola.info("Please restart to re-authenticate");
3143
3147
  }