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/README.en.md +67 -9
- package/README.md +66 -8
- package/dist/{account-manager-COJx3i2R.js → account-manager-B9daQhPM.js} +62 -20
- package/dist/account-manager-B9daQhPM.js.map +1 -0
- package/dist/error-oHPe3O3W.js +2 -0
- package/dist/get-user-q-uqgjND.js +2 -0
- package/dist/main.js +79 -75
- package/dist/main.js.map +1 -1
- package/dist/{token-CswtzkcP.js → token-9T4XDHX4.js} +2 -2
- package/dist/{token-CswtzkcP.js.map → token-9T4XDHX4.js.map} +1 -1
- package/dist/token-byWuxeZE.js +3 -0
- package/package.json +1 -1
- package/dist/account-manager-COJx3i2R.js.map +0 -1
- package/dist/error-Ci5sfamJ.js +0 -2
- package/dist/get-user-HaNtbtZZ.js +0 -2
- package/dist/token-BlHBeJfE.js +0 -3
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-
|
|
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-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
|
-
*
|
|
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
|
-
*
|
|
1688
|
-
*
|
|
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
|
-
|
|
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:
|
|
1714
|
+
timeoutMs: FETCH_TIMEOUT_MS,
|
|
1697
1715
|
accountId,
|
|
1698
1716
|
accountProxy
|
|
1699
1717
|
});
|
|
1700
1718
|
} catch (error) {
|
|
1701
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1864
|
+
async function retryWithModifiedPayload(payload, releaseSlot) {
|
|
1853
1865
|
try {
|
|
1854
1866
|
const result = await dispatchRequest(payload);
|
|
1855
|
-
if (Symbol.asyncIterator in result)
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
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((
|
|
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((
|
|
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-
|
|
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-
|
|
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-
|
|
3144
|
+
const { clearGithubToken } = await import("./token-byWuxeZE.js");
|
|
3141
3145
|
await clearGithubToken();
|
|
3142
3146
|
consola.info("Please restart to re-authenticate");
|
|
3143
3147
|
}
|