github-router 0.3.13 → 0.3.15
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/LICENSE +21 -21
- package/README.md +206 -206
- package/dist/main.js +251 -69
- package/dist/main.js.map +1 -1
- package/package.json +2 -5
package/dist/main.js
CHANGED
|
@@ -19,13 +19,19 @@ import { events } from "fetch-event-stream";
|
|
|
19
19
|
import clipboard from "clipboardy";
|
|
20
20
|
|
|
21
21
|
//#region src/lib/paths.ts
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
function appDir() {
|
|
23
|
+
return path.join(os.homedir(), ".local", "share", "github-router");
|
|
24
|
+
}
|
|
25
25
|
const PATHS = {
|
|
26
|
-
APP_DIR
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
get APP_DIR() {
|
|
27
|
+
return appDir();
|
|
28
|
+
},
|
|
29
|
+
get GITHUB_TOKEN_PATH() {
|
|
30
|
+
return path.join(appDir(), "github_token");
|
|
31
|
+
},
|
|
32
|
+
get ERROR_LOG_PATH() {
|
|
33
|
+
return path.join(appDir(), "error.log");
|
|
34
|
+
}
|
|
29
35
|
};
|
|
30
36
|
async function ensurePaths() {
|
|
31
37
|
await fs.mkdir(PATHS.APP_DIR, { recursive: true });
|
|
@@ -47,6 +53,7 @@ const state = {
|
|
|
47
53
|
manualApprove: false,
|
|
48
54
|
rateLimitWait: false,
|
|
49
55
|
showToken: false,
|
|
56
|
+
extendedBetas: false,
|
|
50
57
|
sessionId: randomUUID(),
|
|
51
58
|
machineId: randomBytes(32).toString("hex")
|
|
52
59
|
};
|
|
@@ -57,19 +64,21 @@ const standardHeaders = () => ({
|
|
|
57
64
|
"content-type": "application/json",
|
|
58
65
|
accept: "application/json"
|
|
59
66
|
});
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
const DEFAULT_COPILOT_VERSION = "0.43.2026033101";
|
|
68
|
+
function copilotVersion(state$1) {
|
|
69
|
+
return state$1.copilotVersion ?? DEFAULT_COPILOT_VERSION;
|
|
70
|
+
}
|
|
63
71
|
const API_VERSION = "2025-10-01";
|
|
64
72
|
const copilotBaseUrl = (state$1) => state$1.copilotApiUrl ?? "https://api.githubcopilot.com";
|
|
65
73
|
const copilotHeaders = (state$1, vision = false, integrationId = "vscode-chat") => {
|
|
74
|
+
const version = copilotVersion(state$1);
|
|
66
75
|
const headers = {
|
|
67
76
|
Authorization: `Bearer ${state$1.copilotToken}`,
|
|
68
77
|
"content-type": standardHeaders()["content-type"],
|
|
69
78
|
"copilot-integration-id": integrationId,
|
|
70
79
|
"editor-version": `vscode/${state$1.vsCodeVersion}`,
|
|
71
|
-
"editor-plugin-version":
|
|
72
|
-
"user-agent":
|
|
80
|
+
"editor-plugin-version": `copilot-chat/${version}`,
|
|
81
|
+
"user-agent": `GitHubCopilotChat/${version}`,
|
|
73
82
|
"openai-intent": "conversation-panel",
|
|
74
83
|
"x-interaction-type": "conversation-panel",
|
|
75
84
|
"x-github-api-version": API_VERSION,
|
|
@@ -86,8 +95,8 @@ const githubHeaders = (state$1) => ({
|
|
|
86
95
|
...standardHeaders(),
|
|
87
96
|
authorization: `token ${state$1.githubToken}`,
|
|
88
97
|
"editor-version": `vscode/${state$1.vsCodeVersion}`,
|
|
89
|
-
"editor-plugin-version":
|
|
90
|
-
"user-agent":
|
|
98
|
+
"editor-plugin-version": `copilot-chat/${copilotVersion(state$1)}`,
|
|
99
|
+
"user-agent": `GitHubCopilotChat/${copilotVersion(state$1)}`,
|
|
91
100
|
"x-github-api-version": API_VERSION,
|
|
92
101
|
"x-vscode-user-agent-library-version": "electron-fetch"
|
|
93
102
|
});
|
|
@@ -114,17 +123,27 @@ async function forwardError(c, error) {
|
|
|
114
123
|
} catch {
|
|
115
124
|
errorJson = void 0;
|
|
116
125
|
}
|
|
126
|
+
if (isAnthropicError(errorJson)) {
|
|
127
|
+
consola.error("HTTP error:", errorJson);
|
|
128
|
+
return c.json(errorJson, error.response.status);
|
|
129
|
+
}
|
|
117
130
|
const message = resolveErrorMessage(errorJson, errorText);
|
|
118
131
|
consola.error("HTTP error:", errorJson ?? errorText);
|
|
119
|
-
return c.json({
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
return c.json({
|
|
133
|
+
type: "error",
|
|
134
|
+
error: {
|
|
135
|
+
type: resolveErrorType(error.response.status),
|
|
136
|
+
message
|
|
137
|
+
}
|
|
138
|
+
}, error.response.status);
|
|
123
139
|
}
|
|
124
|
-
return c.json({
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
return c.json({
|
|
141
|
+
type: "error",
|
|
142
|
+
error: {
|
|
143
|
+
type: "api_error",
|
|
144
|
+
message: error instanceof Error ? error.message : String(error)
|
|
145
|
+
}
|
|
146
|
+
}, 500);
|
|
128
147
|
}
|
|
129
148
|
function resolveErrorMessage(errorJson, fallback) {
|
|
130
149
|
if (typeof errorJson !== "object" || errorJson === null) return fallback;
|
|
@@ -136,6 +155,30 @@ function resolveErrorMessage(errorJson, fallback) {
|
|
|
136
155
|
}
|
|
137
156
|
return fallback;
|
|
138
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Check if a parsed JSON body is already in Anthropic error format:
|
|
160
|
+
* { type: "error", error: { type: "...", message: "..." } }
|
|
161
|
+
*/
|
|
162
|
+
function isAnthropicError(json) {
|
|
163
|
+
if (typeof json !== "object" || json === null) return false;
|
|
164
|
+
const record = json;
|
|
165
|
+
if (record.type !== "error") return false;
|
|
166
|
+
if (typeof record.error !== "object" || record.error === null) return false;
|
|
167
|
+
const inner = record.error;
|
|
168
|
+
return typeof inner.type === "string" && typeof inner.message === "string";
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Map HTTP status to Anthropic error type.
|
|
172
|
+
*/
|
|
173
|
+
function resolveErrorType(status) {
|
|
174
|
+
if (status === 400) return "invalid_request_error";
|
|
175
|
+
if (status === 401) return "authentication_error";
|
|
176
|
+
if (status === 403) return "permission_error";
|
|
177
|
+
if (status === 404) return "not_found_error";
|
|
178
|
+
if (status === 429) return "rate_limit_error";
|
|
179
|
+
if (status === 529) return "overloaded_error";
|
|
180
|
+
return "api_error";
|
|
181
|
+
}
|
|
139
182
|
|
|
140
183
|
//#endregion
|
|
141
184
|
//#region src/services/github/get-copilot-token.ts
|
|
@@ -181,6 +224,39 @@ const getModels = async () => {
|
|
|
181
224
|
return await response.json();
|
|
182
225
|
};
|
|
183
226
|
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/services/get-copilot-version.ts
|
|
229
|
+
const FALLBACK$1 = "0.43.2026033101";
|
|
230
|
+
async function getCopilotChatVersion() {
|
|
231
|
+
const controller = new AbortController();
|
|
232
|
+
const timeout = setTimeout(() => {
|
|
233
|
+
controller.abort();
|
|
234
|
+
}, 5e3);
|
|
235
|
+
try {
|
|
236
|
+
const response = await fetch("https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery", {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: {
|
|
239
|
+
"Content-Type": "application/json",
|
|
240
|
+
Accept: "application/json;api-version=7.1-preview.1"
|
|
241
|
+
},
|
|
242
|
+
body: JSON.stringify({
|
|
243
|
+
filters: [{ criteria: [{
|
|
244
|
+
filterType: 7,
|
|
245
|
+
value: "GitHub.copilot-chat"
|
|
246
|
+
}] }],
|
|
247
|
+
flags: 914
|
|
248
|
+
}),
|
|
249
|
+
signal: controller.signal
|
|
250
|
+
});
|
|
251
|
+
if (!response.ok) return FALLBACK$1;
|
|
252
|
+
return (await response.json())?.results?.[0]?.extensions?.[0]?.versions?.[0]?.version ?? FALLBACK$1;
|
|
253
|
+
} catch {
|
|
254
|
+
return FALLBACK$1;
|
|
255
|
+
} finally {
|
|
256
|
+
clearTimeout(timeout);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
184
260
|
//#endregion
|
|
185
261
|
//#region src/services/get-vscode-version.ts
|
|
186
262
|
const FALLBACK = "1.104.3";
|
|
@@ -208,23 +284,50 @@ const sleep = (ms) => new Promise((resolve) => {
|
|
|
208
284
|
});
|
|
209
285
|
const isNullish = (value) => value === null || value === void 0;
|
|
210
286
|
/**
|
|
211
|
-
* Beta
|
|
212
|
-
*
|
|
213
|
-
* so our requests match what VS Code produces.
|
|
287
|
+
* Beta prefixes VS Code Copilot Chat v0.43 actually sends.
|
|
288
|
+
* Default mode — makes proxy traffic indistinguishable from VS Code.
|
|
214
289
|
*/
|
|
215
|
-
const
|
|
290
|
+
const VSCODE_BETA_PREFIXES = [
|
|
216
291
|
"interleaved-thinking-",
|
|
217
292
|
"context-management-",
|
|
218
|
-
"advanced-tool-use-"
|
|
219
|
-
"token-counting-"
|
|
293
|
+
"advanced-tool-use-"
|
|
220
294
|
];
|
|
221
295
|
/**
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
296
|
+
* Extended beta prefixes for Claude CLI compatibility.
|
|
297
|
+
* Enabled via --extended-betas flag. Includes all betas confirmed
|
|
298
|
+
* to work with the Copilot API.
|
|
299
|
+
*
|
|
300
|
+
* Notably absent: output-128k- (Copilot returns 400).
|
|
301
|
+
*/
|
|
302
|
+
const EXTENDED_BETA_PREFIXES = [
|
|
303
|
+
...VSCODE_BETA_PREFIXES,
|
|
304
|
+
"claude-code-",
|
|
305
|
+
"context-1m-",
|
|
306
|
+
"effort-",
|
|
307
|
+
"prompt-caching-",
|
|
308
|
+
"computer-use-",
|
|
309
|
+
"pdfs-",
|
|
310
|
+
"max-tokens-",
|
|
311
|
+
"token-counting-",
|
|
312
|
+
"compact-",
|
|
313
|
+
"structured-outputs-",
|
|
314
|
+
"fast-mode-",
|
|
315
|
+
"skills-",
|
|
316
|
+
"mcp-client-",
|
|
317
|
+
"mcp-servers-",
|
|
318
|
+
"files-api-",
|
|
319
|
+
"redact-thinking-",
|
|
320
|
+
"web-search-"
|
|
321
|
+
];
|
|
322
|
+
/**
|
|
323
|
+
* Filter an `anthropic-beta` header value, keeping only beta flags
|
|
324
|
+
* in the active whitelist. Uses extended prefixes when --extended-betas
|
|
325
|
+
* is enabled, VS Code-only prefixes otherwise.
|
|
326
|
+
* Returns the filtered comma-separated string, or undefined if nothing remains.
|
|
225
327
|
*/
|
|
226
328
|
function filterBetaHeader(value) {
|
|
227
|
-
|
|
329
|
+
const prefixes = state.extendedBetas ? EXTENDED_BETA_PREFIXES : VSCODE_BETA_PREFIXES;
|
|
330
|
+
return value.split(",").map((v) => v.trim()).filter((v) => v && prefixes.some((prefix) => v.startsWith(prefix))).join(",") || void 0;
|
|
228
331
|
}
|
|
229
332
|
/**
|
|
230
333
|
* Normalize a model ID for fuzzy comparison: lowercase, replace dots with
|
|
@@ -297,6 +400,11 @@ const cacheVSCodeVersion = async () => {
|
|
|
297
400
|
state.vsCodeVersion = response;
|
|
298
401
|
consola.info(`Using VSCode version: ${response}`);
|
|
299
402
|
};
|
|
403
|
+
const cacheCopilotVersion = async () => {
|
|
404
|
+
const version = await getCopilotChatVersion();
|
|
405
|
+
state.copilotVersion = version;
|
|
406
|
+
consola.info(`Using Copilot Chat version: ${version}`);
|
|
407
|
+
};
|
|
300
408
|
|
|
301
409
|
//#endregion
|
|
302
410
|
//#region src/services/github/poll-access-token.ts
|
|
@@ -1306,16 +1414,18 @@ embeddingRoutes.post("/", async (c) => {
|
|
|
1306
1414
|
* (anthropic-beta) so Copilot enables extended features.
|
|
1307
1415
|
*/
|
|
1308
1416
|
function buildHeaders(extraHeaders) {
|
|
1309
|
-
|
|
1417
|
+
const headers = {
|
|
1310
1418
|
...copilotHeaders(state),
|
|
1311
1419
|
accept: "application/json",
|
|
1312
|
-
"openai-intent": "
|
|
1420
|
+
"openai-intent": "messages-proxy",
|
|
1313
1421
|
"x-interaction-type": "conversation-agent",
|
|
1314
1422
|
"X-Initiator": "agent",
|
|
1315
1423
|
"anthropic-version": "2023-06-01",
|
|
1316
1424
|
"X-Interaction-Id": randomUUID(),
|
|
1317
1425
|
...extraHeaders
|
|
1318
1426
|
};
|
|
1427
|
+
delete headers["copilot-integration-id"];
|
|
1428
|
+
return headers;
|
|
1319
1429
|
}
|
|
1320
1430
|
/**
|
|
1321
1431
|
* Forward an Anthropic Messages API request to Copilot's native /v1/messages endpoint.
|
|
@@ -1437,7 +1547,7 @@ async function handleCountTokens(c) {
|
|
|
1437
1547
|
return c.json(responseBody);
|
|
1438
1548
|
}
|
|
1439
1549
|
/**
|
|
1440
|
-
* Parse the JSON body, resolve the model name, and re-serialize.
|
|
1550
|
+
* Parse the JSON body, resolve the model name, sanitize cache_control, and re-serialize.
|
|
1441
1551
|
*/
|
|
1442
1552
|
function resolveModelInBody$1(rawBody) {
|
|
1443
1553
|
let parsed;
|
|
@@ -1447,23 +1557,41 @@ function resolveModelInBody$1(rawBody) {
|
|
|
1447
1557
|
return { body: rawBody };
|
|
1448
1558
|
}
|
|
1449
1559
|
const originalModel = typeof parsed.model === "string" ? parsed.model : void 0;
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
originalModel
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
parsed.model = resolved;
|
|
1560
|
+
let modified = false;
|
|
1561
|
+
if (originalModel) {
|
|
1562
|
+
const resolved = resolveModel(originalModel);
|
|
1563
|
+
if (resolved !== originalModel) {
|
|
1564
|
+
parsed.model = resolved;
|
|
1565
|
+
modified = true;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
if (rawBody.includes("\"scope\"") && sanitizeCacheControl$1(parsed)) modified = true;
|
|
1569
|
+
const resolvedModel = typeof parsed.model === "string" ? parsed.model : originalModel;
|
|
1461
1570
|
return {
|
|
1462
|
-
body: JSON.stringify(parsed),
|
|
1571
|
+
body: modified ? JSON.stringify(parsed) : rawBody,
|
|
1463
1572
|
originalModel,
|
|
1464
|
-
resolvedModel
|
|
1573
|
+
resolvedModel
|
|
1465
1574
|
};
|
|
1466
1575
|
}
|
|
1576
|
+
function sanitizeCacheControl$1(body) {
|
|
1577
|
+
let stripped = false;
|
|
1578
|
+
function stripScope(block) {
|
|
1579
|
+
if (block.cache_control?.scope !== void 0) {
|
|
1580
|
+
delete block.cache_control.scope;
|
|
1581
|
+
if (Object.keys(block.cache_control).length === 0) delete block.cache_control;
|
|
1582
|
+
stripped = true;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
if (Array.isArray(body.system)) for (const block of body.system) stripScope(block);
|
|
1586
|
+
if (Array.isArray(body.messages)) {
|
|
1587
|
+
for (const msg of body.messages) if (Array.isArray(msg.content)) for (const block of msg.content) {
|
|
1588
|
+
stripScope(block);
|
|
1589
|
+
if (Array.isArray(block.content)) for (const nested of block.content) stripScope(nested);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (Array.isArray(body.tools)) for (const tool of body.tools) stripScope(tool);
|
|
1593
|
+
return stripped;
|
|
1594
|
+
}
|
|
1467
1595
|
|
|
1468
1596
|
//#endregion
|
|
1469
1597
|
//#region src/routes/messages/handler.ts
|
|
@@ -1609,13 +1737,18 @@ async function handleCompletion(c) {
|
|
|
1609
1737
|
streaming: true
|
|
1610
1738
|
}, selectedModel, startTime);
|
|
1611
1739
|
if (debugEnabled) consola.debug("Streaming response from Copilot /v1/messages");
|
|
1740
|
+
const streamHeaders = {
|
|
1741
|
+
"content-type": "text/event-stream",
|
|
1742
|
+
"cache-control": "no-cache",
|
|
1743
|
+
connection: "keep-alive"
|
|
1744
|
+
};
|
|
1745
|
+
const requestId = response.headers.get("x-request-id");
|
|
1746
|
+
if (requestId) streamHeaders["x-request-id"] = requestId;
|
|
1747
|
+
const reqId = response.headers.get("request-id");
|
|
1748
|
+
if (reqId) streamHeaders["request-id"] = reqId;
|
|
1612
1749
|
return new Response(response.body, {
|
|
1613
1750
|
status: response.status,
|
|
1614
|
-
headers:
|
|
1615
|
-
"content-type": "text/event-stream",
|
|
1616
|
-
"cache-control": "no-cache",
|
|
1617
|
-
connection: "keep-alive"
|
|
1618
|
-
}
|
|
1751
|
+
headers: streamHeaders
|
|
1619
1752
|
});
|
|
1620
1753
|
}
|
|
1621
1754
|
const responseBody = await response.json();
|
|
@@ -1629,11 +1762,18 @@ async function handleCompletion(c) {
|
|
|
1629
1762
|
status: response.status
|
|
1630
1763
|
}, selectedModel, startTime);
|
|
1631
1764
|
if (debugEnabled) consola.debug("Non-streaming response from Copilot /v1/messages:", JSON.stringify(responseBody).slice(0, 2e3));
|
|
1765
|
+
const xRequestId = response.headers.get("x-request-id");
|
|
1766
|
+
if (xRequestId) c.header("x-request-id", xRequestId);
|
|
1767
|
+
const requestIdHeader = response.headers.get("request-id");
|
|
1768
|
+
if (requestIdHeader) c.header("request-id", requestIdHeader);
|
|
1632
1769
|
return c.json(responseBody, response.status);
|
|
1633
1770
|
}
|
|
1634
1771
|
/**
|
|
1635
|
-
* Parse the JSON body, resolve the model name,
|
|
1636
|
-
* Returns the body string plus the original
|
|
1772
|
+
* Parse the JSON body, resolve the model name, sanitize cache_control
|
|
1773
|
+
* fields, and re-serialize. Returns the body string plus the original
|
|
1774
|
+
* and resolved model names.
|
|
1775
|
+
*
|
|
1776
|
+
* Re-serialization is skipped when no modifications are needed.
|
|
1637
1777
|
*/
|
|
1638
1778
|
function resolveModelInBody(rawBody) {
|
|
1639
1779
|
let parsed;
|
|
@@ -1643,24 +1783,50 @@ function resolveModelInBody(rawBody) {
|
|
|
1643
1783
|
return { body: rawBody };
|
|
1644
1784
|
}
|
|
1645
1785
|
const originalModel = typeof parsed.model === "string" ? parsed.model : void 0;
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
originalModel
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
parsed.model = resolved;
|
|
1786
|
+
let modified = false;
|
|
1787
|
+
if (originalModel) {
|
|
1788
|
+
const resolved = resolveModel(originalModel);
|
|
1789
|
+
if (resolved !== originalModel) {
|
|
1790
|
+
parsed.model = resolved;
|
|
1791
|
+
modified = true;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
if (rawBody.includes("\"scope\"") && sanitizeCacheControl(parsed)) modified = true;
|
|
1795
|
+
const resolvedModel = typeof parsed.model === "string" ? parsed.model : originalModel;
|
|
1657
1796
|
return {
|
|
1658
|
-
body: JSON.stringify(parsed),
|
|
1797
|
+
body: modified ? JSON.stringify(parsed) : rawBody,
|
|
1659
1798
|
originalModel,
|
|
1660
|
-
resolvedModel
|
|
1799
|
+
resolvedModel
|
|
1661
1800
|
};
|
|
1662
1801
|
}
|
|
1663
1802
|
/**
|
|
1803
|
+
* Strip the `scope` field from all `cache_control` objects in the body.
|
|
1804
|
+
* Claude CLI 2.1.88+ sends {"type":"ephemeral","scope":"global"} which
|
|
1805
|
+
* Copilot rejects. Mutates the parsed object in place.
|
|
1806
|
+
*
|
|
1807
|
+
* Covers: system blocks, message content blocks (including nested
|
|
1808
|
+
* tool_result content), and tool definitions.
|
|
1809
|
+
*/
|
|
1810
|
+
function sanitizeCacheControl(body) {
|
|
1811
|
+
let stripped = false;
|
|
1812
|
+
function stripScope(block) {
|
|
1813
|
+
if (block.cache_control?.scope !== void 0) {
|
|
1814
|
+
delete block.cache_control.scope;
|
|
1815
|
+
if (Object.keys(block.cache_control).length === 0) delete block.cache_control;
|
|
1816
|
+
stripped = true;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
if (Array.isArray(body.system)) for (const block of body.system) stripScope(block);
|
|
1820
|
+
if (Array.isArray(body.messages)) {
|
|
1821
|
+
for (const msg of body.messages) if (Array.isArray(msg.content)) for (const block of msg.content) {
|
|
1822
|
+
stripScope(block);
|
|
1823
|
+
if (Array.isArray(block.content)) for (const nested of block.content) stripScope(nested);
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
if (Array.isArray(body.tools)) for (const tool of body.tools) stripScope(tool);
|
|
1827
|
+
return stripped;
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1664
1830
|
* Apply default anthropic-beta values for Claude models when the client
|
|
1665
1831
|
* (e.g. curl) sends no beta headers. Claude CLI sends its own betas,
|
|
1666
1832
|
* so this only fires as a safety net for bare clients.
|
|
@@ -1670,7 +1836,7 @@ function applyDefaultBetas(betaHeaders, modelId) {
|
|
|
1670
1836
|
if (!modelId || !modelId.startsWith("claude-")) return betaHeaders;
|
|
1671
1837
|
return {
|
|
1672
1838
|
...betaHeaders,
|
|
1673
|
-
"anthropic-beta": ["interleaved-thinking-2025-05-14", "
|
|
1839
|
+
"anthropic-beta": ["interleaved-thinking-2025-05-14", "context-management-2025-06-27"].join(",")
|
|
1674
1840
|
};
|
|
1675
1841
|
}
|
|
1676
1842
|
|
|
@@ -2037,6 +2203,7 @@ usageRoute.get("/", async (c) => {
|
|
|
2037
2203
|
const server = new Hono();
|
|
2038
2204
|
server.use(cors());
|
|
2039
2205
|
server.get("/", (c) => c.text("Server running"));
|
|
2206
|
+
server.on("HEAD", ["/"], (c) => c.body(null, 200));
|
|
2040
2207
|
server.route("/chat/completions", completionRoutes);
|
|
2041
2208
|
server.route("/responses", responsesRoutes);
|
|
2042
2209
|
server.route("/models", modelRoutes);
|
|
@@ -2050,6 +2217,13 @@ server.route("/v1/models", modelRoutes);
|
|
|
2050
2217
|
server.route("/v1/embeddings", embeddingRoutes);
|
|
2051
2218
|
server.route("/v1/search", searchRoutes);
|
|
2052
2219
|
server.route("/v1/messages", messageRoutes);
|
|
2220
|
+
server.notFound((c) => c.json({
|
|
2221
|
+
type: "error",
|
|
2222
|
+
error: {
|
|
2223
|
+
type: "not_found_error",
|
|
2224
|
+
message: `${c.req.method} ${c.req.path} not found`
|
|
2225
|
+
}
|
|
2226
|
+
}, 404));
|
|
2053
2227
|
|
|
2054
2228
|
//#endregion
|
|
2055
2229
|
//#region src/lib/server-setup.ts
|
|
@@ -2066,9 +2240,11 @@ async function setupAndServe(options) {
|
|
|
2066
2240
|
state.rateLimitSeconds = options.rateLimit;
|
|
2067
2241
|
state.rateLimitWait = options.rateLimitWait;
|
|
2068
2242
|
state.showToken = options.showToken;
|
|
2243
|
+
state.extendedBetas = options.extendedBetas;
|
|
2069
2244
|
if (process.env.COPILOT_API_URL) state.copilotApiUrl = process.env.COPILOT_API_URL;
|
|
2070
2245
|
await ensurePaths();
|
|
2071
2246
|
await cacheVSCodeVersion();
|
|
2247
|
+
await cacheCopilotVersion();
|
|
2072
2248
|
if (options.githubToken) {
|
|
2073
2249
|
state.githubToken = options.githubToken;
|
|
2074
2250
|
consola.info("Using provided GitHub token");
|
|
@@ -2162,6 +2338,11 @@ const sharedServerArgs = {
|
|
|
2162
2338
|
type: "boolean",
|
|
2163
2339
|
default: false,
|
|
2164
2340
|
description: "Initialize proxy from environment variables"
|
|
2341
|
+
},
|
|
2342
|
+
"extended-betas": {
|
|
2343
|
+
type: "boolean",
|
|
2344
|
+
default: false,
|
|
2345
|
+
description: "Forward extended beta headers for Claude CLI compatibility (default: VS Code-only)"
|
|
2165
2346
|
}
|
|
2166
2347
|
};
|
|
2167
2348
|
const allowedAccountTypes = new Set([
|
|
@@ -2197,7 +2378,8 @@ function parseSharedArgs(args) {
|
|
|
2197
2378
|
rateLimitWait,
|
|
2198
2379
|
githubToken,
|
|
2199
2380
|
showToken: args["show-token"],
|
|
2200
|
-
proxyEnv: args["proxy-env"]
|
|
2381
|
+
proxyEnv: args["proxy-env"],
|
|
2382
|
+
extendedBetas: args["extended-betas"]
|
|
2201
2383
|
};
|
|
2202
2384
|
}
|
|
2203
2385
|
/** Build environment variables for Claude Code. */
|