@dianshuv/copilot-api 0.11.4 → 0.12.0
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 +9 -49
- package/dist/main.mjs +50 -666
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -46,11 +46,9 @@ make down
|
|
|
46
46
|
| Command | Description |
|
|
47
47
|
|---------|-------------|
|
|
48
48
|
| `start` | Start the API server (handles auth if needed) |
|
|
49
|
-
| `
|
|
49
|
+
| `login` | Run GitHub authentication flow only |
|
|
50
50
|
| `logout` | Remove stored GitHub token |
|
|
51
|
-
| `check-usage` | Show Copilot usage and quota |
|
|
52
51
|
| `debug` | Display diagnostic information |
|
|
53
|
-
| `patch-claude` | Patch Claude Code's context window limit |
|
|
54
52
|
|
|
55
53
|
### Start Command Options
|
|
56
54
|
|
|
@@ -60,45 +58,25 @@ make down
|
|
|
60
58
|
| `--host`, `-H` | Host/interface to bind to (`0.0.0.0` = all interfaces) | `127.0.0.1` |
|
|
61
59
|
| `--verbose`, `-v` | Enable verbose logging | false |
|
|
62
60
|
| `--account-type`, `-a` | Account type (individual, business, enterprise) | individual |
|
|
63
|
-
| `--manual` | Manual request approval mode | false |
|
|
64
61
|
| `--no-rate-limit` | Disable adaptive rate limiting | false |
|
|
65
|
-
| `--
|
|
66
|
-
| `--request-interval` | Seconds between requests in rate-limited mode | 10 |
|
|
67
|
-
| `--recovery-timeout` | Minutes before attempting recovery | 10 |
|
|
68
|
-
| `--consecutive-successes` | Successes needed to exit rate-limited mode | 5 |
|
|
69
|
-
| `--github-token`, `-g` | Provide GitHub token directly | none |
|
|
70
|
-
| `--claude-code`, `-c` | Generate Claude Code launch command | false |
|
|
71
|
-
| `--show-token` | Show tokens on fetch/refresh | false |
|
|
72
|
-
| `--show-all-models` | Show all upstream models including the hardcoded blacklist (see "Hidden Models" below) | false |
|
|
73
|
-
| `--proxy-env` | Use proxy from environment | false |
|
|
74
|
-
| `--no-history` | Disable request history UI at `/history` | false |
|
|
75
|
-
| `--history-limit` | Max history entries in memory | 1000 |
|
|
76
|
-
| `--no-auto-truncate` | Disable auto-truncate when exceeding token limits | false |
|
|
77
|
-
| `--compress-tool-results` | Compress old tool results before truncating | false |
|
|
78
|
-
| `--redirect-anthropic` | Force Anthropic through OpenAI translation | false |
|
|
79
|
-
| `--strip-server-tools` | Strip server-side tools from Anthropic requests | false |
|
|
80
|
-
| `--context-editing` | Context editing mode: off, clear-thinking, clear-tooluse, clear-both | off |
|
|
81
|
-
| `--timezone-offset` | Timezone offset in hours from UTC for log timestamps (e.g., +8, -5, 0) | +8 |
|
|
62
|
+
| `--github-token`, `-g` | Provide GitHub token directly (or `GH_TOKEN` env) | none |
|
|
82
63
|
| `--posthog-key` | PostHog API key for token usage analytics (opt-in) | none |
|
|
83
64
|
| `--api-key` | Proxy API key for inbound authentication (see [Authentication](#authentication)). Empty = disabled | none |
|
|
84
65
|
|
|
85
66
|
### Hidden Models
|
|
86
67
|
|
|
87
68
|
By default the proxy hides 30 stale / duplicate / unused upstream model ids from
|
|
88
|
-
its **listing surfaces** (`/v1/models
|
|
89
|
-
`--claude-code` interactive prompts). Pass `--show-all-models` to see the full
|
|
90
|
-
upstream catalogue.
|
|
69
|
+
its **listing surfaces** (`/v1/models` and the startup banner).
|
|
91
70
|
|
|
92
71
|
**Important**: this is a *display* filter only. The remaining POST endpoints
|
|
93
72
|
(`/v1/chat/completions`, `/v1/messages`, `/v1/responses`) do **not** reject
|
|
94
73
|
hidden ids — an explicit `POST` with a hidden id is still forwarded to
|
|
95
|
-
upstream verbatim
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
`/models` catalogue still returns them.)
|
|
74
|
+
upstream verbatim. (The embedding and Gemini ids in the list have no callable
|
|
75
|
+
endpoint in this proxy at all; they're hidden purely to denoise the
|
|
76
|
+
`/v1/models` listing — the upstream `/models` catalogue still returns them.)
|
|
99
77
|
|
|
100
|
-
To change the blacklist, edit
|
|
101
|
-
fork uses `/publish`).
|
|
78
|
+
To see the full catalogue or change the blacklist, edit
|
|
79
|
+
`src/lib/hidden-models.ts` and re-publish (this fork uses `/publish`).
|
|
102
80
|
|
|
103
81
|
Currently hidden (grouped):
|
|
104
82
|
|
|
@@ -111,15 +89,6 @@ Currently hidden (grouped):
|
|
|
111
89
|
- **Older / variant Claude** — `claude-opus-4.5`, `claude-opus-4.6`, `claude-opus-4.7-high`, `claude-opus-4.7-xhigh`, `claude-sonnet-4.5`
|
|
112
90
|
- **Special-purpose** — `mai-code-1-flash-internal`, `trajectory-compaction`
|
|
113
91
|
|
|
114
|
-
### Patch-Claude Command Options
|
|
115
|
-
|
|
116
|
-
| Option | Description | Default |
|
|
117
|
-
|--------|-------------|---------|
|
|
118
|
-
| `--limit`, `-l` | Context window limit in tokens | 128000 |
|
|
119
|
-
| `--restore`, `-r` | Restore original 200k limit | false |
|
|
120
|
-
| `--path`, `-p` | Path to Claude Code cli.js | auto-detect |
|
|
121
|
-
| `--status`, `-s` | Show current patch status | false |
|
|
122
|
-
|
|
123
92
|
## API Endpoints
|
|
124
93
|
|
|
125
94
|
### OpenAI Compatible
|
|
@@ -155,7 +124,6 @@ When enabled (default), auto-truncate automatically compacts conversation histor
|
|
|
155
124
|
|
|
156
125
|
- **Token-based truncation**: Uses the model's `max_context_window_tokens` from the Copilot API to determine when truncation is needed. A 2% safety margin is applied.
|
|
157
126
|
- **No preset byte limit**: There is no hardcoded request body size limit. If the Copilot API returns a 413 (Request Entity Too Large), the proxy dynamically learns the byte limit and applies it to subsequent requests.
|
|
158
|
-
- **Smart compression**: With `--compress-tool-results`, old tool results are compressed before removing messages, preserving more conversation context.
|
|
159
127
|
- **Orphan filtering**: After truncation, orphaned tool results (without matching tool calls) are automatically removed.
|
|
160
128
|
|
|
161
129
|
## Authentication
|
|
@@ -244,9 +212,7 @@ Per client:
|
|
|
244
212
|
- **Claude Code** (`.claude/settings.json`, see [below](#using-with-claude-code)):
|
|
245
213
|
change `ANTHROPIC_AUTH_TOKEN` from `"dummy"` to your real key. Claude Code
|
|
246
214
|
talks to the proxy's `/v1/messages` (Anthropic) surface; the auth gate accepts
|
|
247
|
-
the token from either header.
|
|
248
|
-
placeholder on purpose (so the secret never lands in your clipboard / shell
|
|
249
|
-
history) and prints a reminder to replace it when auth is on.
|
|
215
|
+
the token from either header.
|
|
250
216
|
- **Raw OpenAI SDK** → `Authorization: Bearer <key>`; **raw Anthropic SDK** →
|
|
251
217
|
`x-api-key: <key>`.
|
|
252
218
|
|
|
@@ -308,12 +274,6 @@ Create `.claude/settings.json` in your project:
|
|
|
308
274
|
}
|
|
309
275
|
```
|
|
310
276
|
|
|
311
|
-
Or use the interactive setup:
|
|
312
|
-
|
|
313
|
-
```sh
|
|
314
|
-
bun run start --claude-code
|
|
315
|
-
```
|
|
316
|
-
|
|
317
277
|
## Upstream Project
|
|
318
278
|
|
|
319
279
|
For the original project documentation, features, and updates, see: [ericc-ch/copilot-api](https://github.com/ericc-ch/copilot-api)
|
package/dist/main.mjs
CHANGED
|
@@ -3,16 +3,12 @@ import { defineCommand, runMain } from "citty";
|
|
|
3
3
|
import consola from "consola";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import os from "node:os";
|
|
6
|
-
import path
|
|
6
|
+
import path from "node:path";
|
|
7
7
|
import { getProxyForUrl } from "proxy-from-env";
|
|
8
8
|
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
9
9
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
10
|
-
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
11
|
-
import clipboard from "clipboardy";
|
|
12
10
|
import { serve } from "srvx";
|
|
13
11
|
import { PostHog } from "posthog-node";
|
|
14
|
-
import { execSync } from "node:child_process";
|
|
15
|
-
import process$1 from "node:process";
|
|
16
12
|
import pc from "picocolors";
|
|
17
13
|
import { Hono } from "hono";
|
|
18
14
|
import { cors } from "hono/cors";
|
|
@@ -782,45 +778,6 @@ async function logUser() {
|
|
|
782
778
|
consola.info(`Logged in as ${user.login}`);
|
|
783
779
|
}
|
|
784
780
|
|
|
785
|
-
//#endregion
|
|
786
|
-
//#region src/auth.ts
|
|
787
|
-
async function runAuth(options) {
|
|
788
|
-
if (options.verbose) {
|
|
789
|
-
consola.level = 5;
|
|
790
|
-
consola.info("Verbose logging enabled");
|
|
791
|
-
}
|
|
792
|
-
state.showToken = options.showToken;
|
|
793
|
-
initProxyFromEnv();
|
|
794
|
-
await ensurePaths();
|
|
795
|
-
await setupGitHubToken({ force: true });
|
|
796
|
-
consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH);
|
|
797
|
-
}
|
|
798
|
-
const auth = defineCommand({
|
|
799
|
-
meta: {
|
|
800
|
-
name: "auth",
|
|
801
|
-
description: "Run GitHub auth flow without running the server"
|
|
802
|
-
},
|
|
803
|
-
args: {
|
|
804
|
-
verbose: {
|
|
805
|
-
alias: "v",
|
|
806
|
-
type: "boolean",
|
|
807
|
-
default: false,
|
|
808
|
-
description: "Enable verbose logging"
|
|
809
|
-
},
|
|
810
|
-
"show-token": {
|
|
811
|
-
type: "boolean",
|
|
812
|
-
default: false,
|
|
813
|
-
description: "Show GitHub token on auth"
|
|
814
|
-
}
|
|
815
|
-
},
|
|
816
|
-
run({ args }) {
|
|
817
|
-
return runAuth({
|
|
818
|
-
verbose: args.verbose,
|
|
819
|
-
showToken: args["show-token"]
|
|
820
|
-
});
|
|
821
|
-
}
|
|
822
|
-
});
|
|
823
|
-
|
|
824
781
|
//#endregion
|
|
825
782
|
//#region src/services/github/get-copilot-usage.ts
|
|
826
783
|
const getCopilotUsage = async () => {
|
|
@@ -829,43 +786,6 @@ const getCopilotUsage = async () => {
|
|
|
829
786
|
return await response.json();
|
|
830
787
|
};
|
|
831
788
|
|
|
832
|
-
//#endregion
|
|
833
|
-
//#region src/check-usage.ts
|
|
834
|
-
const checkUsage = defineCommand({
|
|
835
|
-
meta: {
|
|
836
|
-
name: "check-usage",
|
|
837
|
-
description: "Show current GitHub Copilot usage/quota information"
|
|
838
|
-
},
|
|
839
|
-
async run() {
|
|
840
|
-
initProxyFromEnv();
|
|
841
|
-
await ensurePaths();
|
|
842
|
-
await setupGitHubToken();
|
|
843
|
-
try {
|
|
844
|
-
const usage = await getCopilotUsage();
|
|
845
|
-
const premium = usage.quota_snapshots.premium_interactions;
|
|
846
|
-
const premiumTotal = premium.entitlement;
|
|
847
|
-
const premiumUsed = premiumTotal - premium.remaining;
|
|
848
|
-
const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0;
|
|
849
|
-
const premiumPercentRemaining = premium.percent_remaining;
|
|
850
|
-
function summarizeQuota(name, snap) {
|
|
851
|
-
if (!snap) return `${name}: N/A`;
|
|
852
|
-
const total = snap.entitlement;
|
|
853
|
-
const used = total - snap.remaining;
|
|
854
|
-
const percentUsed = total > 0 ? used / total * 100 : 0;
|
|
855
|
-
const percentRemaining = snap.percent_remaining;
|
|
856
|
-
return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
|
|
857
|
-
}
|
|
858
|
-
const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`;
|
|
859
|
-
const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat);
|
|
860
|
-
const completionsLine = summarizeQuota("Completions", usage.quota_snapshots.completions);
|
|
861
|
-
consola.box(`Copilot Usage (plan: ${usage.copilot_plan})\nQuota resets: ${usage.quota_reset_date}\n\nQuotas:\n ${premiumLine}\n ${chatLine}\n ${completionsLine}`);
|
|
862
|
-
} catch (err) {
|
|
863
|
-
consola.error("Failed to fetch Copilot usage:", err);
|
|
864
|
-
process.exit(1);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
});
|
|
868
|
-
|
|
869
789
|
//#endregion
|
|
870
790
|
//#region src/debug.ts
|
|
871
791
|
async function getPackageVersion() {
|
|
@@ -1008,6 +928,45 @@ const debug = defineCommand({
|
|
|
1008
928
|
}
|
|
1009
929
|
});
|
|
1010
930
|
|
|
931
|
+
//#endregion
|
|
932
|
+
//#region src/login.ts
|
|
933
|
+
async function runLogin(options) {
|
|
934
|
+
if (options.verbose) {
|
|
935
|
+
consola.level = 5;
|
|
936
|
+
consola.info("Verbose logging enabled");
|
|
937
|
+
}
|
|
938
|
+
state.showToken = options.showToken;
|
|
939
|
+
initProxyFromEnv();
|
|
940
|
+
await ensurePaths();
|
|
941
|
+
await setupGitHubToken({ force: true });
|
|
942
|
+
consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH);
|
|
943
|
+
}
|
|
944
|
+
const login = defineCommand({
|
|
945
|
+
meta: {
|
|
946
|
+
name: "login",
|
|
947
|
+
description: "Run GitHub auth flow without running the server"
|
|
948
|
+
},
|
|
949
|
+
args: {
|
|
950
|
+
verbose: {
|
|
951
|
+
alias: "v",
|
|
952
|
+
type: "boolean",
|
|
953
|
+
default: false,
|
|
954
|
+
description: "Enable verbose logging"
|
|
955
|
+
},
|
|
956
|
+
"show-token": {
|
|
957
|
+
type: "boolean",
|
|
958
|
+
default: false,
|
|
959
|
+
description: "Show GitHub token on auth"
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
run({ args }) {
|
|
963
|
+
return runLogin({
|
|
964
|
+
verbose: args.verbose,
|
|
965
|
+
showToken: args["show-token"]
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
|
|
1011
970
|
//#endregion
|
|
1012
971
|
//#region src/logout.ts
|
|
1013
972
|
async function runLogout() {
|
|
@@ -1032,323 +991,9 @@ const logout = defineCommand({
|
|
|
1032
991
|
}
|
|
1033
992
|
});
|
|
1034
993
|
|
|
1035
|
-
//#endregion
|
|
1036
|
-
//#region src/patch-claude-code.ts
|
|
1037
|
-
const SUPPORTED_VERSIONS = {
|
|
1038
|
-
v2a: {
|
|
1039
|
-
min: "2.0.0",
|
|
1040
|
-
max: "2.1.10"
|
|
1041
|
-
},
|
|
1042
|
-
v2b: { min: "2.1.11" }
|
|
1043
|
-
};
|
|
1044
|
-
const PATTERNS = {
|
|
1045
|
-
funcOriginal: /function HR\(A\)\{if\(A\.includes\("\[1m\]"\)\)return 1e6;return 200000\}/,
|
|
1046
|
-
funcPatched: /function HR\(A\)\{if\(A\.includes\("\[1m\]"\)\)return 1e6;return \d+\}/,
|
|
1047
|
-
variable: /var ([A-Za-z_$]\w*)=(\d+)(?=,\w+=20000,)/
|
|
1048
|
-
};
|
|
1049
|
-
/**
|
|
1050
|
-
* Parse semver version string to comparable parts
|
|
1051
|
-
*/
|
|
1052
|
-
function parseVersion(version) {
|
|
1053
|
-
return version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Compare two semver versions
|
|
1057
|
-
* Returns: -1 if a < b, 0 if a == b, 1 if a > b
|
|
1058
|
-
*/
|
|
1059
|
-
function compareVersions(a, b) {
|
|
1060
|
-
const partsA = parseVersion(a);
|
|
1061
|
-
const partsB = parseVersion(b);
|
|
1062
|
-
const len = Math.max(partsA.length, partsB.length);
|
|
1063
|
-
for (let i = 0; i < len; i++) {
|
|
1064
|
-
const numA = partsA[i] || 0;
|
|
1065
|
-
const numB = partsB[i] || 0;
|
|
1066
|
-
if (numA < numB) return -1;
|
|
1067
|
-
if (numA > numB) return 1;
|
|
1068
|
-
}
|
|
1069
|
-
return 0;
|
|
1070
|
-
}
|
|
1071
|
-
function getPatternTypeForVersion(version) {
|
|
1072
|
-
if (compareVersions(version, SUPPORTED_VERSIONS.v2a.min) >= 0 && compareVersions(version, SUPPORTED_VERSIONS.v2a.max) <= 0) return "func";
|
|
1073
|
-
if (compareVersions(version, SUPPORTED_VERSIONS.v2b.min) >= 0) return "variable";
|
|
1074
|
-
return null;
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Get supported version range string for error messages
|
|
1078
|
-
*/
|
|
1079
|
-
function getSupportedRangeString() {
|
|
1080
|
-
return `${SUPPORTED_VERSIONS.v2a.min}-${SUPPORTED_VERSIONS.v2a.max}, ${SUPPORTED_VERSIONS.v2b.min}+`;
|
|
1081
|
-
}
|
|
1082
|
-
/**
|
|
1083
|
-
* Get Claude Code version from package.json
|
|
1084
|
-
*/
|
|
1085
|
-
function getClaudeCodeVersion(cliPath) {
|
|
1086
|
-
try {
|
|
1087
|
-
const packageJsonPath = join(dirname(cliPath), "package.json");
|
|
1088
|
-
if (!existsSync(packageJsonPath)) return null;
|
|
1089
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
1090
|
-
if (typeof packageJson === "object" && packageJson !== null && "version" in packageJson && typeof packageJson.version === "string") return packageJson.version;
|
|
1091
|
-
return null;
|
|
1092
|
-
} catch {
|
|
1093
|
-
return null;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
/**
|
|
1097
|
-
* Search volta tools directory for Claude Code
|
|
1098
|
-
*/
|
|
1099
|
-
function findInVoltaTools(voltaHome) {
|
|
1100
|
-
const paths = [];
|
|
1101
|
-
const packagesPath = join(voltaHome, "tools", "image", "packages", "@anthropic-ai", "claude-code", "lib", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
|
|
1102
|
-
if (existsSync(packagesPath)) paths.push(packagesPath);
|
|
1103
|
-
const toolsDir = join(voltaHome, "tools", "image", "node");
|
|
1104
|
-
if (existsSync(toolsDir)) try {
|
|
1105
|
-
for (const version of readdirSync(toolsDir)) {
|
|
1106
|
-
const claudePath = join(toolsDir, version, "lib", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
|
|
1107
|
-
if (existsSync(claudePath)) paths.push(claudePath);
|
|
1108
|
-
}
|
|
1109
|
-
} catch {}
|
|
1110
|
-
return paths;
|
|
1111
|
-
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Find all Claude Code CLI paths by checking common locations
|
|
1114
|
-
*/
|
|
1115
|
-
function findAllClaudeCodePaths() {
|
|
1116
|
-
const possiblePaths = [];
|
|
1117
|
-
const home = process.env.HOME || "";
|
|
1118
|
-
const voltaHome = process.env.VOLTA_HOME || join(home, ".volta");
|
|
1119
|
-
if (existsSync(voltaHome)) possiblePaths.push(...findInVoltaTools(voltaHome));
|
|
1120
|
-
const npmPrefix = process.env.npm_config_prefix;
|
|
1121
|
-
if (npmPrefix) possiblePaths.push(join(npmPrefix, "lib", "node_modules", "@anthropic-ai", "claude-code", "cli.js"));
|
|
1122
|
-
const globalPaths = [
|
|
1123
|
-
join(home, ".npm-global", "lib", "node_modules"),
|
|
1124
|
-
"/usr/local/lib/node_modules",
|
|
1125
|
-
"/usr/lib/node_modules"
|
|
1126
|
-
];
|
|
1127
|
-
for (const base of globalPaths) possiblePaths.push(join(base, "@anthropic-ai", "claude-code", "cli.js"));
|
|
1128
|
-
const bunGlobal = join(home, ".bun", "install", "global");
|
|
1129
|
-
if (existsSync(bunGlobal)) possiblePaths.push(join(bunGlobal, "node_modules", "@anthropic-ai", "claude-code", "cli.js"));
|
|
1130
|
-
return [...new Set(possiblePaths.filter((p) => existsSync(p)))];
|
|
1131
|
-
}
|
|
1132
|
-
/**
|
|
1133
|
-
* Get installation info for a CLI path
|
|
1134
|
-
*/
|
|
1135
|
-
function getInstallationInfo(cliPath) {
|
|
1136
|
-
return {
|
|
1137
|
-
path: cliPath,
|
|
1138
|
-
version: getClaudeCodeVersion(cliPath),
|
|
1139
|
-
limit: getCurrentLimit(readFileSync(cliPath, "utf8"))
|
|
1140
|
-
};
|
|
1141
|
-
}
|
|
1142
|
-
function getCurrentLimitInfo(content) {
|
|
1143
|
-
const varMatch = content.match(PATTERNS.variable);
|
|
1144
|
-
if (varMatch) return {
|
|
1145
|
-
limit: Number.parseInt(varMatch[2], 10),
|
|
1146
|
-
varName: varMatch[1]
|
|
1147
|
-
};
|
|
1148
|
-
const funcMatch = content.match(PATTERNS.funcPatched);
|
|
1149
|
-
if (funcMatch) {
|
|
1150
|
-
const limitMatch = funcMatch[0].match(/return (\d+)\}$/);
|
|
1151
|
-
return limitMatch ? { limit: Number.parseInt(limitMatch[1], 10) } : null;
|
|
1152
|
-
}
|
|
1153
|
-
return null;
|
|
1154
|
-
}
|
|
1155
|
-
/**
|
|
1156
|
-
* Get current context limit from Claude Code (legacy wrapper)
|
|
1157
|
-
*/
|
|
1158
|
-
function getCurrentLimit(content) {
|
|
1159
|
-
return getCurrentLimitInfo(content)?.limit ?? null;
|
|
1160
|
-
}
|
|
1161
|
-
/**
|
|
1162
|
-
* Check if Claude Code version is supported for patching
|
|
1163
|
-
*/
|
|
1164
|
-
function checkVersionSupport(cliPath) {
|
|
1165
|
-
const version = getClaudeCodeVersion(cliPath);
|
|
1166
|
-
if (!version) return {
|
|
1167
|
-
supported: false,
|
|
1168
|
-
version: null,
|
|
1169
|
-
patternType: null,
|
|
1170
|
-
error: "Could not detect Claude Code version"
|
|
1171
|
-
};
|
|
1172
|
-
const patternType = getPatternTypeForVersion(version);
|
|
1173
|
-
if (!patternType) return {
|
|
1174
|
-
supported: false,
|
|
1175
|
-
version,
|
|
1176
|
-
patternType: null,
|
|
1177
|
-
error: `Version ${version} is not supported. Supported: ${getSupportedRangeString()}`
|
|
1178
|
-
};
|
|
1179
|
-
return {
|
|
1180
|
-
supported: true,
|
|
1181
|
-
version,
|
|
1182
|
-
patternType
|
|
1183
|
-
};
|
|
1184
|
-
}
|
|
1185
|
-
/**
|
|
1186
|
-
* Patch Claude Code to use a different context limit
|
|
1187
|
-
*/
|
|
1188
|
-
function patchClaudeCode(cliPath, newLimit) {
|
|
1189
|
-
const content = readFileSync(cliPath, "utf8");
|
|
1190
|
-
const versionCheck = checkVersionSupport(cliPath);
|
|
1191
|
-
if (!versionCheck.supported) {
|
|
1192
|
-
consola.error(versionCheck.error);
|
|
1193
|
-
return "failed";
|
|
1194
|
-
}
|
|
1195
|
-
consola.info(`Claude Code version: ${versionCheck.version}`);
|
|
1196
|
-
const limitInfo = getCurrentLimitInfo(content);
|
|
1197
|
-
if (limitInfo?.limit === newLimit) return "already_patched";
|
|
1198
|
-
let newContent;
|
|
1199
|
-
if (versionCheck.patternType === "variable") {
|
|
1200
|
-
if (!limitInfo?.varName) {
|
|
1201
|
-
consola.error("Could not detect variable name for patching");
|
|
1202
|
-
return "failed";
|
|
1203
|
-
}
|
|
1204
|
-
newContent = content.replace(PATTERNS.variable, `var ${limitInfo.varName}=${newLimit}`);
|
|
1205
|
-
} else {
|
|
1206
|
-
const replacement = `function HR(A){if(A.includes("[1m]"))return 1e6;return ${newLimit}}`;
|
|
1207
|
-
const pattern = PATTERNS.funcOriginal.test(content) ? PATTERNS.funcOriginal : PATTERNS.funcPatched;
|
|
1208
|
-
newContent = content.replace(pattern, replacement);
|
|
1209
|
-
}
|
|
1210
|
-
writeFileSync(cliPath, newContent);
|
|
1211
|
-
return "success";
|
|
1212
|
-
}
|
|
1213
|
-
/**
|
|
1214
|
-
* Restore Claude Code to original 200k limit
|
|
1215
|
-
*/
|
|
1216
|
-
function restoreClaudeCode(cliPath) {
|
|
1217
|
-
const content = readFileSync(cliPath, "utf8");
|
|
1218
|
-
const versionCheck = checkVersionSupport(cliPath);
|
|
1219
|
-
if (!versionCheck.supported) {
|
|
1220
|
-
consola.error(versionCheck.error);
|
|
1221
|
-
return false;
|
|
1222
|
-
}
|
|
1223
|
-
consola.info(`Claude Code version: ${versionCheck.version}`);
|
|
1224
|
-
const limitInfo = getCurrentLimitInfo(content);
|
|
1225
|
-
if (limitInfo?.limit === 2e5) {
|
|
1226
|
-
consola.info("Already at original 200000 limit");
|
|
1227
|
-
return true;
|
|
1228
|
-
}
|
|
1229
|
-
let newContent;
|
|
1230
|
-
if (versionCheck.patternType === "variable") {
|
|
1231
|
-
if (!limitInfo?.varName) {
|
|
1232
|
-
consola.error("Could not detect variable name for restoring");
|
|
1233
|
-
return false;
|
|
1234
|
-
}
|
|
1235
|
-
newContent = content.replace(PATTERNS.variable, `var ${limitInfo.varName}=200000`);
|
|
1236
|
-
} else newContent = content.replace(PATTERNS.funcPatched, "function HR(A){if(A.includes(\"[1m]\"))return 1e6;return 200000}");
|
|
1237
|
-
writeFileSync(cliPath, newContent);
|
|
1238
|
-
return true;
|
|
1239
|
-
}
|
|
1240
|
-
function showStatus(cliPath, currentLimit) {
|
|
1241
|
-
const version = getClaudeCodeVersion(cliPath);
|
|
1242
|
-
if (version) consola.info(`Claude Code version: ${version}`);
|
|
1243
|
-
if (currentLimit === null) {
|
|
1244
|
-
consola.warn("Could not detect current limit - CLI may have been updated");
|
|
1245
|
-
consola.info("Look for a variable like 'var XXX=200000' followed by ',YYY=20000,' in cli.js");
|
|
1246
|
-
} else if (currentLimit === 2e5) consola.info("Status: Original (200k context window)");
|
|
1247
|
-
else consola.info(`Status: Patched (${currentLimit} context window)`);
|
|
1248
|
-
}
|
|
1249
|
-
const patchClaude = defineCommand({
|
|
1250
|
-
meta: {
|
|
1251
|
-
name: "patch-claude",
|
|
1252
|
-
description: "Patch Claude Code's context window limit to match Copilot's limits"
|
|
1253
|
-
},
|
|
1254
|
-
args: {
|
|
1255
|
-
limit: {
|
|
1256
|
-
alias: "l",
|
|
1257
|
-
type: "string",
|
|
1258
|
-
default: "128000",
|
|
1259
|
-
description: "Context window limit in tokens (default: 128000 for Copilot)"
|
|
1260
|
-
},
|
|
1261
|
-
restore: {
|
|
1262
|
-
alias: "r",
|
|
1263
|
-
type: "boolean",
|
|
1264
|
-
default: false,
|
|
1265
|
-
description: "Restore original 200k limit"
|
|
1266
|
-
},
|
|
1267
|
-
path: {
|
|
1268
|
-
alias: "p",
|
|
1269
|
-
type: "string",
|
|
1270
|
-
description: "Path to Claude Code cli.js (auto-detected if not specified)"
|
|
1271
|
-
},
|
|
1272
|
-
status: {
|
|
1273
|
-
alias: "s",
|
|
1274
|
-
type: "boolean",
|
|
1275
|
-
default: false,
|
|
1276
|
-
description: "Show current patch status without modifying"
|
|
1277
|
-
}
|
|
1278
|
-
},
|
|
1279
|
-
async run({ args }) {
|
|
1280
|
-
let cliPath;
|
|
1281
|
-
if (args.path) {
|
|
1282
|
-
cliPath = args.path;
|
|
1283
|
-
if (!existsSync(cliPath)) {
|
|
1284
|
-
consola.error(`File not found: ${cliPath}`);
|
|
1285
|
-
process.exit(1);
|
|
1286
|
-
}
|
|
1287
|
-
} else {
|
|
1288
|
-
const installations = findAllClaudeCodePaths();
|
|
1289
|
-
if (installations.length === 0) {
|
|
1290
|
-
consola.error("Could not find Claude Code installation");
|
|
1291
|
-
consola.info("Searched in: volta, npm global, bun global");
|
|
1292
|
-
consola.info("Use --path to specify the path to cli.js manually");
|
|
1293
|
-
process.exit(1);
|
|
1294
|
-
}
|
|
1295
|
-
if (installations.length === 1) cliPath = installations[0];
|
|
1296
|
-
else {
|
|
1297
|
-
consola.info(`Found ${installations.length} Claude Code installations:`);
|
|
1298
|
-
const options = installations.map((path) => {
|
|
1299
|
-
const info = getInstallationInfo(path);
|
|
1300
|
-
let status = "unknown";
|
|
1301
|
-
if (info.limit === 2e5) status = "original";
|
|
1302
|
-
else if (info.limit) status = `patched: ${info.limit}`;
|
|
1303
|
-
return {
|
|
1304
|
-
label: `v${info.version ?? "?"} (${status}) - ${path}`,
|
|
1305
|
-
value: path
|
|
1306
|
-
};
|
|
1307
|
-
});
|
|
1308
|
-
const selected = await consola.prompt("Select installation to patch:", {
|
|
1309
|
-
type: "select",
|
|
1310
|
-
options
|
|
1311
|
-
});
|
|
1312
|
-
if (typeof selected === "symbol") process.exit(0);
|
|
1313
|
-
cliPath = selected;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
consola.info(`Claude Code path: ${cliPath}`);
|
|
1317
|
-
const currentLimit = getCurrentLimit(readFileSync(cliPath, "utf8"));
|
|
1318
|
-
if (args.status) {
|
|
1319
|
-
showStatus(cliPath, currentLimit);
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
if (args.restore) {
|
|
1323
|
-
if (restoreClaudeCode(cliPath)) consola.success("Restored to original 200k limit");
|
|
1324
|
-
else {
|
|
1325
|
-
consola.error("Failed to restore - pattern not found");
|
|
1326
|
-
consola.info("Claude Code may have been updated to a new version");
|
|
1327
|
-
process.exit(1);
|
|
1328
|
-
}
|
|
1329
|
-
return;
|
|
1330
|
-
}
|
|
1331
|
-
const limit = Number.parseInt(args.limit, 10);
|
|
1332
|
-
if (Number.isNaN(limit) || limit < 1e3) {
|
|
1333
|
-
consola.error("Invalid limit value. Must be a number >= 1000");
|
|
1334
|
-
process.exit(1);
|
|
1335
|
-
}
|
|
1336
|
-
const result = patchClaudeCode(cliPath, limit);
|
|
1337
|
-
if (result === "success") {
|
|
1338
|
-
consola.success(`Patched context window: ${currentLimit ?? 2e5} → ${limit}`);
|
|
1339
|
-
consola.info("Note: You may need to re-run this after Claude Code updates");
|
|
1340
|
-
} else if (result === "already_patched") consola.success(`Already patched with limit ${limit}`);
|
|
1341
|
-
else {
|
|
1342
|
-
consola.error("Failed to patch - pattern not found");
|
|
1343
|
-
consola.info("Claude Code may have been updated to a new version");
|
|
1344
|
-
process.exit(1);
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
});
|
|
1348
|
-
|
|
1349
994
|
//#endregion
|
|
1350
995
|
//#region package.json
|
|
1351
|
-
var version = "0.
|
|
996
|
+
var version = "0.12.0";
|
|
1352
997
|
|
|
1353
998
|
//#endregion
|
|
1354
999
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -1872,37 +1517,6 @@ function buildStartupAuthLines(params) {
|
|
|
1872
1517
|
return [source === "none" ? `Inbound auth: 认证关闭 (no proxy API key configured)` : `Inbound auth: 认证开启 (source: ${source})`, `Binding to: ${bindAddress}`];
|
|
1873
1518
|
}
|
|
1874
1519
|
/**
|
|
1875
|
-
* The env var Claude Code reads for its inbound credential, and the placeholder
|
|
1876
|
-
* value the `--claude-code` setup always embeds for it. Exported so the
|
|
1877
|
-
* generated env script (src/start.ts) and the auth hint below reference the SAME
|
|
1878
|
-
* literals — changing the placeholder or the var name in one place can't silently
|
|
1879
|
-
* desync the other (the hint would otherwise keep naming a string the generated
|
|
1880
|
-
* command no longer contains).
|
|
1881
|
-
*/
|
|
1882
|
-
const CLAUDE_CODE_AUTH_TOKEN_ENV = "ANTHROPIC_AUTH_TOKEN";
|
|
1883
|
-
const CLAUDE_CODE_AUTH_TOKEN_PLACEHOLDER = "dummy";
|
|
1884
|
-
/**
|
|
1885
|
-
* Build the auth-aware hint lines for the `--claude-code` setup (Issue 05).
|
|
1886
|
-
*
|
|
1887
|
-
* The generated env script ALWAYS sets `ANTHROPIC_AUTH_TOKEN="dummy"` — a real
|
|
1888
|
-
* key is deliberately never embedded, so the secret can't land in the clipboard
|
|
1889
|
-
* or shell history. When inbound auth is ON, that placeholder won't authenticate
|
|
1890
|
-
* against this proxy, so the operator must replace it. This builder returns the
|
|
1891
|
-
* visible hint that tells them which variable to change:
|
|
1892
|
-
* - auth OFF (`source === "none"`) → no hint (today's behavior, unchanged).
|
|
1893
|
-
* - auth ON (`flag` / `env`) → a one-line hint naming
|
|
1894
|
-
* `ANTHROPIC_AUTH_TOKEN` as the field to set to the proxy API key value.
|
|
1895
|
-
*
|
|
1896
|
-
* Like {@link buildStartupAuthLines}, the key value is **never** an input here —
|
|
1897
|
-
* the builder only knows the `source` tag — so it is structurally impossible for
|
|
1898
|
-
* the secret to leak into the hint. Pure (tag in → strings out) so the copy is
|
|
1899
|
-
* pinned by unit tests.
|
|
1900
|
-
*/
|
|
1901
|
-
function buildClaudeCodeAuthHint(source) {
|
|
1902
|
-
if (source === "none") return [];
|
|
1903
|
-
return [`Inbound auth is ON: replace ${CLAUDE_CODE_AUTH_TOKEN_ENV}="${CLAUDE_CODE_AUTH_TOKEN_PLACEHOLDER}" with your proxy API key value before using Claude Code.`];
|
|
1904
|
-
}
|
|
1905
|
-
/**
|
|
1906
1520
|
* Configure the proxy API key on global state from a raw configured value.
|
|
1907
1521
|
*
|
|
1908
1522
|
* The value is trimmed; a trimmed-empty value (or `undefined`) is treated as
|
|
@@ -2841,58 +2455,6 @@ async function shutdownPostHog() {
|
|
|
2841
2455
|
}
|
|
2842
2456
|
}
|
|
2843
2457
|
|
|
2844
|
-
//#endregion
|
|
2845
|
-
//#region src/lib/shell.ts
|
|
2846
|
-
function getShell() {
|
|
2847
|
-
const { platform, ppid, env } = process$1;
|
|
2848
|
-
if (platform === "win32") {
|
|
2849
|
-
try {
|
|
2850
|
-
if (execSync(`wmic process get ParentProcessId,Name | findstr "${ppid}"`, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell";
|
|
2851
|
-
} catch {
|
|
2852
|
-
return "cmd";
|
|
2853
|
-
}
|
|
2854
|
-
return "cmd";
|
|
2855
|
-
} else {
|
|
2856
|
-
const shellPath = env.SHELL;
|
|
2857
|
-
if (shellPath) {
|
|
2858
|
-
if (shellPath.endsWith("zsh")) return "zsh";
|
|
2859
|
-
if (shellPath.endsWith("fish")) return "fish";
|
|
2860
|
-
if (shellPath.endsWith("bash")) return "bash";
|
|
2861
|
-
}
|
|
2862
|
-
return "sh";
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
2865
|
-
/**
|
|
2866
|
-
* Generates a copy-pasteable script to set multiple environment variables
|
|
2867
|
-
* and run a subsequent command.
|
|
2868
|
-
* @param {EnvVars} envVars - An object of environment variables to set.
|
|
2869
|
-
* @param {string} commandToRun - The command to run after setting the variables.
|
|
2870
|
-
* @returns {string} The formatted script string.
|
|
2871
|
-
*/
|
|
2872
|
-
function generateEnvScript(envVars, commandToRun = "") {
|
|
2873
|
-
const shell = getShell();
|
|
2874
|
-
const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
|
|
2875
|
-
let commandBlock;
|
|
2876
|
-
switch (shell) {
|
|
2877
|
-
case "powershell":
|
|
2878
|
-
commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = "${value.replaceAll("\"", "`\"")}"`).join("; ");
|
|
2879
|
-
break;
|
|
2880
|
-
case "cmd":
|
|
2881
|
-
commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & ");
|
|
2882
|
-
break;
|
|
2883
|
-
case "fish":
|
|
2884
|
-
commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} "${value.replaceAll("\"", String.raw`\"`)}"`).join("; ");
|
|
2885
|
-
break;
|
|
2886
|
-
default: {
|
|
2887
|
-
const assignments = filteredEnvVars.map(([key, value]) => `${key}="${value.replaceAll("\"", String.raw`\"`)}"`).join(" ");
|
|
2888
|
-
commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
|
|
2889
|
-
break;
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : " && "}${commandToRun}`;
|
|
2893
|
-
return commandBlock || commandToRun;
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
2458
|
//#endregion
|
|
2897
2459
|
//#region src/lib/shutdown.ts
|
|
2898
2460
|
const DRAIN_POLL_INTERVAL_MS = 500;
|
|
@@ -9925,7 +9487,6 @@ function formatModelInfo(model) {
|
|
|
9925
9487
|
async function runServer(options) {
|
|
9926
9488
|
consola.info(`copilot-api v${version}`);
|
|
9927
9489
|
configureProxyApiKey(options.apiKey);
|
|
9928
|
-
if (options.proxyEnv) initProxyFromEnv();
|
|
9929
9490
|
if (options.verbose) {
|
|
9930
9491
|
consola.level = 5;
|
|
9931
9492
|
consola.info("Verbose logging enabled");
|
|
@@ -9939,34 +9500,11 @@ async function runServer(options) {
|
|
|
9939
9500
|
process.exit(1);
|
|
9940
9501
|
}
|
|
9941
9502
|
if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`);
|
|
9942
|
-
|
|
9943
|
-
state.showToken = options.showToken;
|
|
9944
|
-
state.showAllModels = options.showAllModels;
|
|
9945
|
-
if (options.showAllModels) consola.warn("--show-all-models: hidden model blacklist is BYPASSED for this run");
|
|
9946
|
-
state.autoTruncate = options.autoTruncate;
|
|
9947
|
-
state.compressToolResults = options.compressToolResults;
|
|
9948
|
-
state.redirectAnthropic = options.redirectAnthropic;
|
|
9949
|
-
state.stripServerTools = options.stripServerTools;
|
|
9950
|
-
state.contextEditingMode = options.contextEditing;
|
|
9951
|
-
state.timezoneOffset = options.timezoneOffset;
|
|
9952
|
-
if (options.rateLimit) initAdaptiveRateLimiter({
|
|
9953
|
-
baseRetryIntervalSeconds: options.retryInterval,
|
|
9954
|
-
requestIntervalSeconds: options.requestInterval,
|
|
9955
|
-
recoveryTimeoutMinutes: options.recoveryTimeout,
|
|
9956
|
-
consecutiveSuccessesForRecovery: options.consecutiveSuccesses
|
|
9957
|
-
});
|
|
9503
|
+
if (options.rateLimit) initAdaptiveRateLimiter();
|
|
9958
9504
|
else consola.info("Rate limiting disabled");
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
if (options.stripServerTools) consola.info("Server-side tools will be stripped from requests");
|
|
9963
|
-
if (options.contextEditing !== "off") consola.info(`Context editing mode: ${options.contextEditing}`);
|
|
9964
|
-
initHistory(options.history, options.historyLimit);
|
|
9965
|
-
if (options.history) {
|
|
9966
|
-
const limitText = options.historyLimit === 0 ? "unlimited" : `max ${options.historyLimit}`;
|
|
9967
|
-
consola.info(`History recording enabled (${limitText} entries)`);
|
|
9968
|
-
startMemoryPressureMonitor();
|
|
9969
|
-
}
|
|
9505
|
+
initHistory(true, 1e3);
|
|
9506
|
+
consola.info("History recording enabled (max 1000 entries)");
|
|
9507
|
+
startMemoryPressureMonitor();
|
|
9970
9508
|
if (options.posthogKey) {
|
|
9971
9509
|
initPostHog(options.posthogKey);
|
|
9972
9510
|
if (isPostHogEnabled()) consola.info("PostHog analytics enabled");
|
|
@@ -9993,42 +9531,10 @@ async function runServer(options) {
|
|
|
9993
9531
|
process.exit(1);
|
|
9994
9532
|
}
|
|
9995
9533
|
const visibleModels = allModels.filter((m) => !isHiddenModel(m.id, state.showAllModels));
|
|
9996
|
-
if (visibleModels.length === 0) consola.warn("All upstream models are filtered by the hardcoded blacklist. /v1/models will return an empty list, but explicit POSTs with a hidden id still pass through to upstream.
|
|
9534
|
+
if (visibleModels.length === 0) consola.warn("All upstream models are filtered by the hardcoded blacklist. /v1/models will return an empty list, but explicit POSTs with a hidden id still pass through to upstream. Edit src/lib/hidden-models.ts to change the blacklist.");
|
|
9997
9535
|
else consola.info(`Available models:\n${visibleModels.map((m) => formatModelInfo(m)).join("\n")}`);
|
|
9998
9536
|
const serverUrl = `http://${resolveClientHost(options.host, void 0)}:${options.port}`;
|
|
9999
|
-
|
|
10000
|
-
if (visibleModels.length === 0) {
|
|
10001
|
-
consola.error("--claude-code interactive setup needs at least one visible model. Restart with --show-all-models or update src/lib/hidden-models.ts.");
|
|
10002
|
-
process.exit(1);
|
|
10003
|
-
}
|
|
10004
|
-
const selectedModel = await consola.prompt("Select a model to use with Claude Code", {
|
|
10005
|
-
type: "select",
|
|
10006
|
-
options: visibleModels.map((model) => model.id)
|
|
10007
|
-
});
|
|
10008
|
-
const selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", {
|
|
10009
|
-
type: "select",
|
|
10010
|
-
options: visibleModels.map((model) => model.id)
|
|
10011
|
-
});
|
|
10012
|
-
const command = generateEnvScript({
|
|
10013
|
-
ANTHROPIC_BASE_URL: serverUrl,
|
|
10014
|
-
[CLAUDE_CODE_AUTH_TOKEN_ENV]: CLAUDE_CODE_AUTH_TOKEN_PLACEHOLDER,
|
|
10015
|
-
ANTHROPIC_MODEL: selectedModel,
|
|
10016
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
|
|
10017
|
-
ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
|
|
10018
|
-
ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,
|
|
10019
|
-
DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
|
|
10020
|
-
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
10021
|
-
}, "claude");
|
|
10022
|
-
try {
|
|
10023
|
-
clipboard.writeSync(command);
|
|
10024
|
-
consola.success("Copied Claude Code command to clipboard!");
|
|
10025
|
-
} catch {
|
|
10026
|
-
consola.warn("Failed to copy to clipboard. Here is the Claude Code command:");
|
|
10027
|
-
consola.log(command);
|
|
10028
|
-
}
|
|
10029
|
-
for (const line of buildClaudeCodeAuthHint(options.apiKeySource)) consola.warn(line);
|
|
10030
|
-
}
|
|
10031
|
-
consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage${options.history ? `\n📜 History UI: ${serverUrl}/history` : ""}`);
|
|
9537
|
+
consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage\n📜 History UI: ${serverUrl}/history`);
|
|
10032
9538
|
for (const line of buildStartupAuthLines({
|
|
10033
9539
|
source: options.apiKeySource,
|
|
10034
9540
|
bindAddress: resolveBindAddress(options.host, void 0)
|
|
@@ -10049,23 +9555,6 @@ async function runServer(options) {
|
|
|
10049
9555
|
} }
|
|
10050
9556
|
}));
|
|
10051
9557
|
}
|
|
10052
|
-
function parseTimezoneOffset(value) {
|
|
10053
|
-
if (typeof value !== "string") return 8;
|
|
10054
|
-
const n = Number(value);
|
|
10055
|
-
if (!Number.isFinite(n)) return 8;
|
|
10056
|
-
return n;
|
|
10057
|
-
}
|
|
10058
|
-
const validContextEditingModes = [
|
|
10059
|
-
"off",
|
|
10060
|
-
"clear-thinking",
|
|
10061
|
-
"clear-tooluse",
|
|
10062
|
-
"clear-both"
|
|
10063
|
-
];
|
|
10064
|
-
function parseContextEditing(value) {
|
|
10065
|
-
if (validContextEditingModes.includes(value)) return value;
|
|
10066
|
-
consola.warn(`Invalid context editing mode: "${value}", using "off". Valid: ${validContextEditingModes.join(", ")}`);
|
|
10067
|
-
return "off";
|
|
10068
|
-
}
|
|
10069
9558
|
const start = defineCommand({
|
|
10070
9559
|
meta: {
|
|
10071
9560
|
name: "start",
|
|
@@ -10095,101 +9584,15 @@ const start = defineCommand({
|
|
|
10095
9584
|
default: "individual",
|
|
10096
9585
|
description: "Account type to use (individual, business, enterprise)"
|
|
10097
9586
|
},
|
|
10098
|
-
manual: {
|
|
10099
|
-
type: "boolean",
|
|
10100
|
-
default: false,
|
|
10101
|
-
description: "Enable manual request approval"
|
|
10102
|
-
},
|
|
10103
9587
|
"no-rate-limit": {
|
|
10104
9588
|
type: "boolean",
|
|
10105
9589
|
default: false,
|
|
10106
9590
|
description: "Disable adaptive rate limiting"
|
|
10107
9591
|
},
|
|
10108
|
-
"retry-interval": {
|
|
10109
|
-
type: "string",
|
|
10110
|
-
default: "10",
|
|
10111
|
-
description: "Seconds to wait before retrying after rate limit error (default: 10)"
|
|
10112
|
-
},
|
|
10113
|
-
"request-interval": {
|
|
10114
|
-
type: "string",
|
|
10115
|
-
default: "10",
|
|
10116
|
-
description: "Seconds between requests in rate-limited mode (default: 10)"
|
|
10117
|
-
},
|
|
10118
|
-
"recovery-timeout": {
|
|
10119
|
-
type: "string",
|
|
10120
|
-
default: "10",
|
|
10121
|
-
description: "Minutes before attempting to recover from rate-limited mode (default: 10)"
|
|
10122
|
-
},
|
|
10123
|
-
"consecutive-successes": {
|
|
10124
|
-
type: "string",
|
|
10125
|
-
default: "5",
|
|
10126
|
-
description: "Number of consecutive successes needed to recover from rate-limited mode (default: 5)"
|
|
10127
|
-
},
|
|
10128
9592
|
"github-token": {
|
|
10129
9593
|
alias: "g",
|
|
10130
9594
|
type: "string",
|
|
10131
|
-
description: "Provide GitHub token directly (must be generated using the `
|
|
10132
|
-
},
|
|
10133
|
-
"claude-code": {
|
|
10134
|
-
alias: "c",
|
|
10135
|
-
type: "boolean",
|
|
10136
|
-
default: false,
|
|
10137
|
-
description: "Generate a command to launch Claude Code with Copilot API config"
|
|
10138
|
-
},
|
|
10139
|
-
"show-token": {
|
|
10140
|
-
type: "boolean",
|
|
10141
|
-
default: false,
|
|
10142
|
-
description: "Show GitHub and Copilot tokens on fetch and refresh"
|
|
10143
|
-
},
|
|
10144
|
-
"show-all-models": {
|
|
10145
|
-
type: "boolean",
|
|
10146
|
-
default: false,
|
|
10147
|
-
description: "Show ALL upstream models, including the hardcoded blacklist (default: false, blacklist filtered from listings)"
|
|
10148
|
-
},
|
|
10149
|
-
"proxy-env": {
|
|
10150
|
-
type: "boolean",
|
|
10151
|
-
default: false,
|
|
10152
|
-
description: "Initialize proxy from environment variables"
|
|
10153
|
-
},
|
|
10154
|
-
"no-history": {
|
|
10155
|
-
type: "boolean",
|
|
10156
|
-
default: false,
|
|
10157
|
-
description: "Disable request history recording and Web UI"
|
|
10158
|
-
},
|
|
10159
|
-
"history-limit": {
|
|
10160
|
-
type: "string",
|
|
10161
|
-
default: "1000",
|
|
10162
|
-
description: "Maximum number of history entries to keep in memory (0 = unlimited)"
|
|
10163
|
-
},
|
|
10164
|
-
"no-auto-truncate": {
|
|
10165
|
-
type: "boolean",
|
|
10166
|
-
default: false,
|
|
10167
|
-
description: "Disable automatic conversation history truncation when exceeding limits"
|
|
10168
|
-
},
|
|
10169
|
-
"compress-tool-results": {
|
|
10170
|
-
type: "boolean",
|
|
10171
|
-
default: false,
|
|
10172
|
-
description: "Compress old tool_result content before truncating messages (may lose context details)"
|
|
10173
|
-
},
|
|
10174
|
-
"redirect-anthropic": {
|
|
10175
|
-
type: "boolean",
|
|
10176
|
-
default: false,
|
|
10177
|
-
description: "Redirect Anthropic models through OpenAI translation (instead of direct API)"
|
|
10178
|
-
},
|
|
10179
|
-
"strip-server-tools": {
|
|
10180
|
-
type: "boolean",
|
|
10181
|
-
default: false,
|
|
10182
|
-
description: "Strip Anthropic server-side tools (web_search, etc.) from requests"
|
|
10183
|
-
},
|
|
10184
|
-
"context-editing": {
|
|
10185
|
-
type: "string",
|
|
10186
|
-
default: "off",
|
|
10187
|
-
description: "Context editing mode: off, clear-thinking, clear-tooluse, clear-both"
|
|
10188
|
-
},
|
|
10189
|
-
"timezone-offset": {
|
|
10190
|
-
type: "string",
|
|
10191
|
-
default: "+8",
|
|
10192
|
-
description: "Timezone offset in hours from UTC for log timestamps (e.g., +8, -5, 0)"
|
|
9595
|
+
description: "Provide GitHub token directly (must be generated using the `login` subcommand). Falls back to the GH_TOKEN env var if the flag is omitted — prefer the env for automation since argv is visible via /proc/<pid>/cmdline."
|
|
10193
9596
|
},
|
|
10194
9597
|
"posthog-key": {
|
|
10195
9598
|
type: "string",
|
|
@@ -10210,25 +9613,8 @@ const start = defineCommand({
|
|
|
10210
9613
|
host: resolveBindHost(args.host, process.env.HOST),
|
|
10211
9614
|
verbose: args.verbose,
|
|
10212
9615
|
accountType: args["account-type"],
|
|
10213
|
-
manual: args.manual,
|
|
10214
9616
|
rateLimit: !args["no-rate-limit"],
|
|
10215
|
-
retryInterval: Number.parseInt(args["retry-interval"], 10),
|
|
10216
|
-
requestInterval: Number.parseInt(args["request-interval"], 10),
|
|
10217
|
-
recoveryTimeout: Number.parseInt(args["recovery-timeout"], 10),
|
|
10218
|
-
consecutiveSuccesses: Number.parseInt(args["consecutive-successes"], 10),
|
|
10219
9617
|
githubToken: args["github-token"] || process.env.GH_TOKEN,
|
|
10220
|
-
claudeCode: args["claude-code"],
|
|
10221
|
-
showToken: args["show-token"],
|
|
10222
|
-
showAllModels: args["show-all-models"],
|
|
10223
|
-
proxyEnv: args["proxy-env"],
|
|
10224
|
-
history: !args["no-history"],
|
|
10225
|
-
historyLimit: Number.parseInt(args["history-limit"], 10),
|
|
10226
|
-
autoTruncate: !args["no-auto-truncate"],
|
|
10227
|
-
compressToolResults: args["compress-tool-results"],
|
|
10228
|
-
redirectAnthropic: args["redirect-anthropic"],
|
|
10229
|
-
stripServerTools: args["strip-server-tools"],
|
|
10230
|
-
contextEditing: parseContextEditing(args["context-editing"]),
|
|
10231
|
-
timezoneOffset: parseTimezoneOffset(args["timezone-offset"]),
|
|
10232
9618
|
posthogKey: args["posthog-key"],
|
|
10233
9619
|
apiKey: resolvedApiKey.key,
|
|
10234
9620
|
apiKeySource: resolvedApiKey.source
|
|
@@ -10245,12 +9631,10 @@ await runMain(defineCommand({
|
|
|
10245
9631
|
description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools."
|
|
10246
9632
|
},
|
|
10247
9633
|
subCommands: {
|
|
10248
|
-
|
|
9634
|
+
login,
|
|
10249
9635
|
logout,
|
|
10250
9636
|
start,
|
|
10251
|
-
|
|
10252
|
-
debug,
|
|
10253
|
-
"patch-claude": patchClaude
|
|
9637
|
+
debug
|
|
10254
9638
|
}
|
|
10255
9639
|
}));
|
|
10256
9640
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dianshuv/copilot-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
|
|
5
5
|
"author": "dianshuv",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
38
38
|
"citty": "^0.2.0",
|
|
39
|
-
"clipboardy": "^5.1.0",
|
|
40
39
|
"consola": "^3.4.2",
|
|
41
40
|
"fetch-event-stream": "^0.1.6",
|
|
42
41
|
"gpt-tokenizer": "^3.4.0",
|