@dianshuv/copilot-api 0.4.3 → 0.6.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 +2 -0
- package/dist/main.mjs +291 -96
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- **Graceful shutdown**: 4-phase shutdown sequence — stops accepting requests, waits for in-flight requests to complete, sends abort signal, then force-closes. Configurable via `--shutdown-graceful-wait` and `--shutdown-abort-wait`.
|
|
16
16
|
- **Stream repetition detection**: Detects when models get stuck in repetitive output loops using KMP-based pattern matching and logs a warning.
|
|
17
17
|
- **Stale request reaping**: Automatically force-fails requests that exceed a configurable maximum age (default 600s) to prevent resource leaks.
|
|
18
|
+
- **PostHog analytics**: Optional PostHog Cloud integration (`--posthog-key`) sends per-request token usage events for long-term trend analysis. Free tier (1M events/month) is more than sufficient for individual use.
|
|
18
19
|
|
|
19
20
|
## Quick Start
|
|
20
21
|
|
|
@@ -66,6 +67,7 @@ copilot-api start
|
|
|
66
67
|
| `--redirect-anthropic` | Force Anthropic through OpenAI translation | false |
|
|
67
68
|
| `--no-rewrite-anthropic-tools` | Don't rewrite server-side tools | false |
|
|
68
69
|
| `--timezone-offset` | Timezone offset in hours from UTC for log timestamps (e.g., +8, -5, 0) | +8 |
|
|
70
|
+
| `--posthog-key` | PostHog API key for token usage analytics (opt-in) | none |
|
|
69
71
|
|
|
70
72
|
### Patch-Claude Command Options
|
|
71
73
|
|
package/dist/main.mjs
CHANGED
|
@@ -4,11 +4,12 @@ import consola from "consola";
|
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path, { dirname, join } from "node:path";
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
9
9
|
import clipboard from "clipboardy";
|
|
10
10
|
import { serve } from "srvx";
|
|
11
11
|
import invariant from "tiny-invariant";
|
|
12
|
+
import { PostHog } from "posthog-node";
|
|
12
13
|
import { getProxyForUrl } from "proxy-from-env";
|
|
13
14
|
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
14
15
|
import { execSync } from "node:child_process";
|
|
@@ -68,7 +69,14 @@ const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
|
|
|
68
69
|
const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
|
|
69
70
|
const API_VERSION = "2025-04-01";
|
|
70
71
|
const copilotBaseUrl = (state) => state.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state.accountType}.githubcopilot.com`;
|
|
71
|
-
|
|
72
|
+
function hasHeaderKey(headers, key) {
|
|
73
|
+
const lowerKey = key.toLowerCase();
|
|
74
|
+
return Object.keys(headers).some((existingKey) => {
|
|
75
|
+
return existingKey.toLowerCase() === lowerKey;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function copilotHeaders(state, visionOrOptions) {
|
|
79
|
+
const options = typeof visionOrOptions === "boolean" ? { vision: visionOrOptions } : visionOrOptions ?? {};
|
|
72
80
|
const headers = {
|
|
73
81
|
Authorization: `Bearer ${state.copilotToken}`,
|
|
74
82
|
"content-type": standardHeaders()["content-type"],
|
|
@@ -76,14 +84,15 @@ const copilotHeaders = (state, vision = false) => {
|
|
|
76
84
|
"editor-version": `vscode/${state.vsCodeVersion}`,
|
|
77
85
|
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
|
|
78
86
|
"user-agent": USER_AGENT,
|
|
79
|
-
"openai-intent": "conversation-panel",
|
|
87
|
+
"openai-intent": options.intent ?? "conversation-panel",
|
|
80
88
|
"x-github-api-version": API_VERSION,
|
|
81
89
|
"x-request-id": randomUUID(),
|
|
82
90
|
"x-vscode-user-agent-library-version": "electron-fetch"
|
|
83
91
|
};
|
|
84
|
-
if (
|
|
92
|
+
for (const [key, value] of Object.entries(options.modelRequestHeaders ?? {})) if (!hasHeaderKey(headers, key)) headers[key] = value;
|
|
93
|
+
if (options.vision) headers["copilot-vision-request"] = "true";
|
|
85
94
|
return headers;
|
|
86
|
-
}
|
|
95
|
+
}
|
|
87
96
|
const GITHUB_API_BASE_URL = "https://api.github.com";
|
|
88
97
|
const githubHeaders = (state) => ({
|
|
89
98
|
...standardHeaders(),
|
|
@@ -208,6 +217,10 @@ function formatRateLimitError(copilotMessage) {
|
|
|
208
217
|
}
|
|
209
218
|
};
|
|
210
219
|
}
|
|
220
|
+
function truncateForLog(text, maxLen) {
|
|
221
|
+
if (text.length <= maxLen) return text;
|
|
222
|
+
return `${text.slice(0, maxLen)}...`;
|
|
223
|
+
}
|
|
211
224
|
function forwardError(c, error) {
|
|
212
225
|
if (error instanceof HTTPError) {
|
|
213
226
|
if (error.status === 413) {
|
|
@@ -246,7 +259,9 @@ function forwardError(c, error) {
|
|
|
246
259
|
consola.warn(`HTTP 429: Rate limit exceeded`);
|
|
247
260
|
return c.json(formattedError, 429);
|
|
248
261
|
}
|
|
249
|
-
|
|
262
|
+
let loggedError = errorJson;
|
|
263
|
+
if (typeof errorJson === "string") loggedError = errorJson.trimStart().startsWith("<") ? `[HTML ${errorJson.length} bytes]` : truncateForLog(errorJson, 200);
|
|
264
|
+
consola.error(`HTTP ${error.status}:`, loggedError);
|
|
250
265
|
return c.json({ error: {
|
|
251
266
|
message: error.responseText,
|
|
252
267
|
type: "error"
|
|
@@ -1021,7 +1036,7 @@ const patchClaude = defineCommand({
|
|
|
1021
1036
|
|
|
1022
1037
|
//#endregion
|
|
1023
1038
|
//#region package.json
|
|
1024
|
-
var version = "0.
|
|
1039
|
+
var version = "0.6.0";
|
|
1025
1040
|
|
|
1026
1041
|
//#endregion
|
|
1027
1042
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -1933,6 +1948,55 @@ function exportHistory(format = "json") {
|
|
|
1933
1948
|
return [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
|
|
1934
1949
|
}
|
|
1935
1950
|
|
|
1951
|
+
//#endregion
|
|
1952
|
+
//#region src/lib/posthog.ts
|
|
1953
|
+
let client = null;
|
|
1954
|
+
let distinctId = "";
|
|
1955
|
+
function initPostHog(apiKey) {
|
|
1956
|
+
if (!apiKey) return;
|
|
1957
|
+
try {
|
|
1958
|
+
client = new PostHog(apiKey, {
|
|
1959
|
+
host: "https://us.i.posthog.com",
|
|
1960
|
+
flushAt: 20,
|
|
1961
|
+
flushInterval: 1e4
|
|
1962
|
+
});
|
|
1963
|
+
distinctId = createHash("sha256").update(os.hostname() + os.userInfo().username).digest("hex");
|
|
1964
|
+
} catch (error) {
|
|
1965
|
+
consola.warn("Failed to initialize PostHog:", error instanceof Error ? error.message : error);
|
|
1966
|
+
client = null;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
function isPostHogEnabled() {
|
|
1970
|
+
return client !== null;
|
|
1971
|
+
}
|
|
1972
|
+
function captureRequest(params) {
|
|
1973
|
+
if (!client) return;
|
|
1974
|
+
const properties = {
|
|
1975
|
+
model: params.model,
|
|
1976
|
+
input_tokens: params.inputTokens,
|
|
1977
|
+
output_tokens: params.outputTokens,
|
|
1978
|
+
duration_ms: params.durationMs,
|
|
1979
|
+
success: params.success,
|
|
1980
|
+
stream: params.stream,
|
|
1981
|
+
tool_count: params.toolCount
|
|
1982
|
+
};
|
|
1983
|
+
if (params.reasoningTokens !== void 0) properties.reasoning_tokens = params.reasoningTokens;
|
|
1984
|
+
if (params.stopReason !== void 0) properties.stop_reason = params.stopReason;
|
|
1985
|
+
client.capture({
|
|
1986
|
+
distinctId,
|
|
1987
|
+
event: "copilot_api_request",
|
|
1988
|
+
properties
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
async function shutdownPostHog() {
|
|
1992
|
+
if (!client) return;
|
|
1993
|
+
try {
|
|
1994
|
+
await client.shutdown();
|
|
1995
|
+
} catch (error) {
|
|
1996
|
+
consola.warn("Failed to flush PostHog events:", error instanceof Error ? error.message : error);
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1936
2000
|
//#endregion
|
|
1937
2001
|
//#region src/lib/proxy.ts
|
|
1938
2002
|
/**
|
|
@@ -2143,7 +2207,7 @@ async function gracefulShutdown(signal, deps) {
|
|
|
2143
2207
|
try {
|
|
2144
2208
|
if (await drainActiveRequests(gracefulWaitMs, tracker, drainOpts) === "drained") {
|
|
2145
2209
|
consola.info("All requests completed naturally");
|
|
2146
|
-
finalize(tracker);
|
|
2210
|
+
await finalize(tracker);
|
|
2147
2211
|
return;
|
|
2148
2212
|
}
|
|
2149
2213
|
} catch (error) {
|
|
@@ -2155,7 +2219,7 @@ async function gracefulShutdown(signal, deps) {
|
|
|
2155
2219
|
try {
|
|
2156
2220
|
if (await drainActiveRequests(abortWaitMs, tracker, drainOpts) === "drained") {
|
|
2157
2221
|
consola.info("All requests completed after abort signal");
|
|
2158
|
-
finalize(tracker);
|
|
2222
|
+
await finalize(tracker);
|
|
2159
2223
|
return;
|
|
2160
2224
|
}
|
|
2161
2225
|
} catch (error) {
|
|
@@ -2169,13 +2233,15 @@ async function gracefulShutdown(signal, deps) {
|
|
|
2169
2233
|
consola.error("Error force-closing server:", error);
|
|
2170
2234
|
}
|
|
2171
2235
|
}
|
|
2172
|
-
finalize(tracker);
|
|
2236
|
+
await finalize(tracker);
|
|
2173
2237
|
} else {
|
|
2238
|
+
await shutdownPostHog();
|
|
2174
2239
|
consola.info("Shutdown complete");
|
|
2175
2240
|
shutdownResolve?.();
|
|
2176
2241
|
}
|
|
2177
2242
|
}
|
|
2178
|
-
function finalize(tracker) {
|
|
2243
|
+
async function finalize(tracker) {
|
|
2244
|
+
await shutdownPostHog();
|
|
2179
2245
|
tracker.destroy();
|
|
2180
2246
|
consola.info("Shutdown complete");
|
|
2181
2247
|
shutdownResolve?.();
|
|
@@ -2465,8 +2531,10 @@ var RequestTracker = class {
|
|
|
2465
2531
|
if (update.durationMs !== void 0) request.durationMs = update.durationMs;
|
|
2466
2532
|
if (update.inputTokens !== void 0) request.inputTokens = update.inputTokens;
|
|
2467
2533
|
if (update.outputTokens !== void 0) request.outputTokens = update.outputTokens;
|
|
2534
|
+
if (update.reasoningTokens !== void 0) request.reasoningTokens = update.reasoningTokens;
|
|
2468
2535
|
if (update.error !== void 0) request.error = update.error;
|
|
2469
2536
|
if (update.queuePosition !== void 0) request.queuePosition = update.queuePosition;
|
|
2537
|
+
if (update.queueWaitMs !== void 0) request.queueWaitMs = update.queueWaitMs;
|
|
2470
2538
|
this.renderer?.onRequestUpdate(id, update);
|
|
2471
2539
|
}
|
|
2472
2540
|
/**
|
|
@@ -2481,6 +2549,7 @@ var RequestTracker = class {
|
|
|
2481
2549
|
if (usage) {
|
|
2482
2550
|
request.inputTokens = usage.inputTokens;
|
|
2483
2551
|
request.outputTokens = usage.outputTokens;
|
|
2552
|
+
if (usage.reasoningTokens !== void 0) request.reasoningTokens = usage.reasoningTokens;
|
|
2484
2553
|
}
|
|
2485
2554
|
this.renderer?.onRequestComplete(request);
|
|
2486
2555
|
this.requests.delete(id);
|
|
@@ -3281,6 +3350,26 @@ function createTruncationResponseMarkerOpenAI(result) {
|
|
|
3281
3350
|
return `\n\n---\n[Auto-truncated: ${result.removedMessageCount} messages removed, ${result.originalTokens} → ${result.compactedTokens} tokens (${percentage}% reduction)]`;
|
|
3282
3351
|
}
|
|
3283
3352
|
|
|
3353
|
+
//#endregion
|
|
3354
|
+
//#region src/lib/message-sanitizer.ts
|
|
3355
|
+
const startPattern = /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\n*/;
|
|
3356
|
+
const endPatternWithNewline = /\n+<system-reminder>[\s\S]*?<\/system-reminder>\s*$/;
|
|
3357
|
+
const endPatternOnly = /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/;
|
|
3358
|
+
function removeSystemReminderTags(text) {
|
|
3359
|
+
let result = text;
|
|
3360
|
+
let prev;
|
|
3361
|
+
do {
|
|
3362
|
+
prev = result;
|
|
3363
|
+
result = result.replace(startPattern, "");
|
|
3364
|
+
} while (result !== prev);
|
|
3365
|
+
do {
|
|
3366
|
+
prev = result;
|
|
3367
|
+
result = result.replace(endPatternWithNewline, "");
|
|
3368
|
+
} while (result !== prev);
|
|
3369
|
+
result = result.replace(endPatternOnly, "");
|
|
3370
|
+
return result;
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3284
3373
|
//#endregion
|
|
3285
3374
|
//#region src/lib/repetition-detector.ts
|
|
3286
3375
|
/**
|
|
@@ -3409,7 +3498,10 @@ const createChatCompletions = async (payload, options) => {
|
|
|
3409
3498
|
const enableVision = payload.messages.some((x) => typeof x.content !== "string" && x.content?.some((x) => x.type === "image_url"));
|
|
3410
3499
|
const isAgentCall = payload.messages.some((msg) => ["assistant", "tool"].includes(msg.role));
|
|
3411
3500
|
const headers = {
|
|
3412
|
-
...copilotHeaders(state,
|
|
3501
|
+
...copilotHeaders(state, {
|
|
3502
|
+
vision: enableVision,
|
|
3503
|
+
intent: isAgentCall ? "conversation-agent" : "conversation-panel"
|
|
3504
|
+
}),
|
|
3413
3505
|
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user")
|
|
3414
3506
|
};
|
|
3415
3507
|
const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
|
|
@@ -3463,17 +3555,30 @@ function recordErrorResponse(ctx, model, error) {
|
|
|
3463
3555
|
content: null
|
|
3464
3556
|
}, Date.now() - ctx.startTime);
|
|
3465
3557
|
}
|
|
3466
|
-
/** Complete TUI tracking */
|
|
3467
|
-
function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs) {
|
|
3558
|
+
/** Complete TUI tracking and send PostHog analytics */
|
|
3559
|
+
function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs, reasoningTokens, analytics) {
|
|
3468
3560
|
if (!trackingId) return;
|
|
3469
3561
|
requestTracker.updateRequest(trackingId, {
|
|
3470
3562
|
inputTokens,
|
|
3471
3563
|
outputTokens,
|
|
3472
|
-
queueWaitMs
|
|
3564
|
+
queueWaitMs,
|
|
3565
|
+
reasoningTokens
|
|
3473
3566
|
});
|
|
3474
3567
|
requestTracker.completeRequest(trackingId, 200, {
|
|
3475
3568
|
inputTokens,
|
|
3476
|
-
outputTokens
|
|
3569
|
+
outputTokens,
|
|
3570
|
+
reasoningTokens
|
|
3571
|
+
});
|
|
3572
|
+
if (analytics) captureRequest({
|
|
3573
|
+
model: analytics.model,
|
|
3574
|
+
inputTokens,
|
|
3575
|
+
outputTokens,
|
|
3576
|
+
durationMs: analytics.durationMs,
|
|
3577
|
+
success: true,
|
|
3578
|
+
stream: analytics.stream,
|
|
3579
|
+
toolCount: analytics.toolCount ?? 0,
|
|
3580
|
+
reasoningTokens,
|
|
3581
|
+
stopReason: analytics.stopReason
|
|
3477
3582
|
});
|
|
3478
3583
|
}
|
|
3479
3584
|
/** Fail TUI tracking */
|
|
@@ -3593,6 +3698,9 @@ async function logPayloadSizeInfo(payload, model) {
|
|
|
3593
3698
|
|
|
3594
3699
|
//#endregion
|
|
3595
3700
|
//#region src/routes/chat-completions/handler.ts
|
|
3701
|
+
function getReasoningTokensFromOpenAIUsage(usage) {
|
|
3702
|
+
return usage?.completion_tokens_details?.reasoning_tokens;
|
|
3703
|
+
}
|
|
3596
3704
|
async function handleCompletion$1(c) {
|
|
3597
3705
|
const originalPayload = await c.req.json();
|
|
3598
3706
|
consola.debug("Request payload:", JSON.stringify(originalPayload).slice(-400));
|
|
@@ -3640,7 +3748,7 @@ async function executeRequest(opts) {
|
|
|
3640
3748
|
try {
|
|
3641
3749
|
const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(payload));
|
|
3642
3750
|
ctx.queueWaitMs = queueWaitMs;
|
|
3643
|
-
if (isNonStreaming(response)) return handleNonStreamingResponse$1(c, response, ctx);
|
|
3751
|
+
if (isNonStreaming(response)) return handleNonStreamingResponse$1(c, response, ctx, payload);
|
|
3644
3752
|
consola.debug("Streaming response");
|
|
3645
3753
|
updateTrackerStatus(trackingId, "streaming");
|
|
3646
3754
|
return streamSSE(c, async (stream) => {
|
|
@@ -3667,7 +3775,7 @@ async function logTokenCount(payload, selectedModel) {
|
|
|
3667
3775
|
consola.debug("Failed to calculate token count:", error);
|
|
3668
3776
|
}
|
|
3669
3777
|
}
|
|
3670
|
-
function handleNonStreamingResponse$1(c, originalResponse, ctx) {
|
|
3778
|
+
function handleNonStreamingResponse$1(c, originalResponse, ctx, payload) {
|
|
3671
3779
|
consola.debug("Non-streaming response:", JSON.stringify(originalResponse));
|
|
3672
3780
|
let response = originalResponse;
|
|
3673
3781
|
if (state.verbose && ctx.truncateResult?.wasCompacted && response.choices[0]?.message.content) {
|
|
@@ -3685,21 +3793,36 @@ function handleNonStreamingResponse$1(c, originalResponse, ctx) {
|
|
|
3685
3793
|
}
|
|
3686
3794
|
const choice = response.choices[0];
|
|
3687
3795
|
const usage = response.usage;
|
|
3796
|
+
const reasoningTokens = getReasoningTokensFromOpenAIUsage(usage);
|
|
3797
|
+
const durationMs = Date.now() - ctx.startTime;
|
|
3688
3798
|
recordResponse(ctx.historyId, {
|
|
3689
3799
|
success: true,
|
|
3690
3800
|
model: response.model,
|
|
3691
3801
|
usage: {
|
|
3692
3802
|
input_tokens: usage?.prompt_tokens ?? 0,
|
|
3693
|
-
output_tokens: usage?.completion_tokens ?? 0
|
|
3803
|
+
output_tokens: usage?.completion_tokens ?? 0,
|
|
3804
|
+
...reasoningTokens !== void 0 ? { output_tokens_details: { reasoning_tokens: reasoningTokens } } : {}
|
|
3694
3805
|
},
|
|
3695
3806
|
stop_reason: choice.finish_reason,
|
|
3696
3807
|
content: buildResponseContent(choice),
|
|
3697
3808
|
toolCalls: extractToolCalls(choice)
|
|
3698
|
-
},
|
|
3809
|
+
}, durationMs);
|
|
3699
3810
|
if (ctx.trackingId && usage) requestTracker.updateRequest(ctx.trackingId, {
|
|
3700
3811
|
inputTokens: usage.prompt_tokens,
|
|
3701
3812
|
outputTokens: usage.completion_tokens,
|
|
3702
|
-
queueWaitMs: ctx.queueWaitMs
|
|
3813
|
+
queueWaitMs: ctx.queueWaitMs,
|
|
3814
|
+
reasoningTokens
|
|
3815
|
+
});
|
|
3816
|
+
captureRequest({
|
|
3817
|
+
model: response.model,
|
|
3818
|
+
inputTokens: usage?.prompt_tokens ?? 0,
|
|
3819
|
+
outputTokens: usage?.completion_tokens ?? 0,
|
|
3820
|
+
durationMs,
|
|
3821
|
+
success: true,
|
|
3822
|
+
stream: false,
|
|
3823
|
+
toolCount: payload.tools?.length ?? 0,
|
|
3824
|
+
reasoningTokens,
|
|
3825
|
+
stopReason: choice.finish_reason
|
|
3703
3826
|
});
|
|
3704
3827
|
return c.json(response);
|
|
3705
3828
|
}
|
|
@@ -3729,6 +3852,7 @@ function createStreamAccumulator() {
|
|
|
3729
3852
|
model: "",
|
|
3730
3853
|
inputTokens: 0,
|
|
3731
3854
|
outputTokens: 0,
|
|
3855
|
+
reasoningTokens: 0,
|
|
3732
3856
|
finishReason: "",
|
|
3733
3857
|
content: "",
|
|
3734
3858
|
toolCalls: [],
|
|
@@ -3766,7 +3890,13 @@ async function handleStreamingResponse$1(opts) {
|
|
|
3766
3890
|
await stream.writeSSE(chunk);
|
|
3767
3891
|
}
|
|
3768
3892
|
recordStreamSuccess(acc, payload.model, ctx);
|
|
3769
|
-
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs
|
|
3893
|
+
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs, acc.reasoningTokens, {
|
|
3894
|
+
model: acc.model || payload.model,
|
|
3895
|
+
stream: true,
|
|
3896
|
+
durationMs: Date.now() - ctx.startTime,
|
|
3897
|
+
stopReason: acc.finishReason || void 0,
|
|
3898
|
+
toolCount: payload.tools?.length ?? 0
|
|
3899
|
+
});
|
|
3770
3900
|
} catch (error) {
|
|
3771
3901
|
recordStreamError({
|
|
3772
3902
|
acc,
|
|
@@ -3786,6 +3916,7 @@ function parseStreamChunk(chunk, acc, checkRepetition) {
|
|
|
3786
3916
|
if (parsed.usage) {
|
|
3787
3917
|
acc.inputTokens = parsed.usage.prompt_tokens;
|
|
3788
3918
|
acc.outputTokens = parsed.usage.completion_tokens;
|
|
3919
|
+
acc.reasoningTokens = getReasoningTokensFromOpenAIUsage(parsed.usage) ?? 0;
|
|
3789
3920
|
}
|
|
3790
3921
|
const choice = parsed.choices[0];
|
|
3791
3922
|
if (choice) {
|
|
@@ -3826,7 +3957,8 @@ function recordStreamSuccess(acc, fallbackModel, ctx) {
|
|
|
3826
3957
|
model: acc.model || fallbackModel,
|
|
3827
3958
|
usage: {
|
|
3828
3959
|
input_tokens: acc.inputTokens,
|
|
3829
|
-
output_tokens: acc.outputTokens
|
|
3960
|
+
output_tokens: acc.outputTokens,
|
|
3961
|
+
...acc.reasoningTokens > 0 ? { output_tokens_details: { reasoning_tokens: acc.reasoningTokens } } : {}
|
|
3830
3962
|
},
|
|
3831
3963
|
stop_reason: acc.finishReason || void 0,
|
|
3832
3964
|
content: {
|
|
@@ -3845,7 +3977,7 @@ function convertOpenAIMessages(messages) {
|
|
|
3845
3977
|
return messages.map((msg) => {
|
|
3846
3978
|
const result = {
|
|
3847
3979
|
role: msg.role,
|
|
3848
|
-
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
|
|
3980
|
+
content: typeof msg.content === "string" ? removeSystemReminderTags(msg.content) : JSON.stringify(msg.content)
|
|
3849
3981
|
};
|
|
3850
3982
|
if ("tool_calls" in msg && msg.tool_calls) result.tool_calls = msg.tool_calls.map((tc) => ({
|
|
3851
3983
|
id: tc.id,
|
|
@@ -6006,7 +6138,10 @@ async function createAnthropicMessages(payload, options) {
|
|
|
6006
6138
|
});
|
|
6007
6139
|
const isAgentCall = filteredPayload.messages.some((msg) => msg.role === "assistant");
|
|
6008
6140
|
const headers = {
|
|
6009
|
-
...copilotHeaders(state,
|
|
6141
|
+
...copilotHeaders(state, {
|
|
6142
|
+
vision: enableVision,
|
|
6143
|
+
intent: isAgentCall ? "conversation-agent" : "conversation-panel"
|
|
6144
|
+
}),
|
|
6010
6145
|
"X-Initiator": options?.initiator ?? (isAgentCall ? "agent" : "user"),
|
|
6011
6146
|
"anthropic-version": "2023-06-01"
|
|
6012
6147
|
};
|
|
@@ -6141,12 +6276,12 @@ function convertAnthropicMessages(messages) {
|
|
|
6141
6276
|
return messages.map((msg) => {
|
|
6142
6277
|
if (typeof msg.content === "string") return {
|
|
6143
6278
|
role: msg.role,
|
|
6144
|
-
content: msg.content
|
|
6279
|
+
content: removeSystemReminderTags(msg.content)
|
|
6145
6280
|
};
|
|
6146
6281
|
const content = msg.content.map((block) => {
|
|
6147
6282
|
if (block.type === "text") return {
|
|
6148
6283
|
type: "text",
|
|
6149
|
-
text: block.text
|
|
6284
|
+
text: removeSystemReminderTags(block.text)
|
|
6150
6285
|
};
|
|
6151
6286
|
if (block.type === "tool_use") return {
|
|
6152
6287
|
type: "tool_use",
|
|
@@ -6213,9 +6348,13 @@ function createAnthropicStreamAccumulator() {
|
|
|
6213
6348
|
stopReason: "",
|
|
6214
6349
|
content: "",
|
|
6215
6350
|
toolCalls: [],
|
|
6351
|
+
serverToolResults: [],
|
|
6216
6352
|
currentToolCall: null
|
|
6217
6353
|
};
|
|
6218
6354
|
}
|
|
6355
|
+
function isServerToolResultType(type) {
|
|
6356
|
+
return type !== "tool_result" && type.endsWith("_tool_result");
|
|
6357
|
+
}
|
|
6219
6358
|
function processAnthropicEvent(event, acc) {
|
|
6220
6359
|
switch (event.type) {
|
|
6221
6360
|
case "content_block_delta":
|
|
@@ -6238,11 +6377,14 @@ function handleContentBlockDelta(delta, acc) {
|
|
|
6238
6377
|
else if (delta.type === "input_json_delta" && acc.currentToolCall) acc.currentToolCall.input += delta.partial_json;
|
|
6239
6378
|
}
|
|
6240
6379
|
function handleContentBlockStart(block, acc) {
|
|
6241
|
-
if (block.type === "tool_use")
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6380
|
+
if (block.type === "tool_use") {
|
|
6381
|
+
const toolBlock = block;
|
|
6382
|
+
acc.currentToolCall = {
|
|
6383
|
+
id: toolBlock.id,
|
|
6384
|
+
name: toolBlock.name,
|
|
6385
|
+
input: ""
|
|
6386
|
+
};
|
|
6387
|
+
} else if (isServerToolResultType(block.type)) acc.serverToolResults.push(block);
|
|
6246
6388
|
}
|
|
6247
6389
|
function handleContentBlockStop(acc) {
|
|
6248
6390
|
if (acc.currentToolCall) {
|
|
@@ -6257,6 +6399,32 @@ function handleMessageDelta(delta, usage, acc) {
|
|
|
6257
6399
|
acc.outputTokens = usage.output_tokens;
|
|
6258
6400
|
}
|
|
6259
6401
|
}
|
|
6402
|
+
function recordAnthropicStreamingResponse(acc, fallbackModel, ctx) {
|
|
6403
|
+
const contentBlocks = [];
|
|
6404
|
+
if (acc.content) contentBlocks.push({
|
|
6405
|
+
type: "text",
|
|
6406
|
+
text: acc.content
|
|
6407
|
+
});
|
|
6408
|
+
for (const tc of acc.toolCalls) contentBlocks.push({
|
|
6409
|
+
type: "tool_use",
|
|
6410
|
+
...tc
|
|
6411
|
+
});
|
|
6412
|
+
for (const result of acc.serverToolResults) contentBlocks.push(result);
|
|
6413
|
+
recordResponse(ctx.historyId, {
|
|
6414
|
+
success: true,
|
|
6415
|
+
model: acc.model || fallbackModel,
|
|
6416
|
+
usage: {
|
|
6417
|
+
input_tokens: acc.inputTokens,
|
|
6418
|
+
output_tokens: acc.outputTokens
|
|
6419
|
+
},
|
|
6420
|
+
stop_reason: acc.stopReason || void 0,
|
|
6421
|
+
content: contentBlocks.length > 0 ? {
|
|
6422
|
+
role: "assistant",
|
|
6423
|
+
content: contentBlocks
|
|
6424
|
+
} : null,
|
|
6425
|
+
toolCalls: acc.toolCalls.length > 0 ? acc.toolCalls : void 0
|
|
6426
|
+
}, Date.now() - ctx.startTime);
|
|
6427
|
+
}
|
|
6260
6428
|
|
|
6261
6429
|
//#endregion
|
|
6262
6430
|
//#region src/routes/messages/non-stream-translation.ts
|
|
@@ -6762,7 +6930,7 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiat
|
|
|
6762
6930
|
});
|
|
6763
6931
|
});
|
|
6764
6932
|
}
|
|
6765
|
-
return handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateResult);
|
|
6933
|
+
return handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateResult, effectivePayload);
|
|
6766
6934
|
} catch (error) {
|
|
6767
6935
|
if (error instanceof HTTPError && error.status === 413) logPayloadSizeInfoAnthropic(effectivePayload, selectedModel);
|
|
6768
6936
|
recordErrorResponse(ctx, anthropicPayload.model, error);
|
|
@@ -6787,7 +6955,7 @@ function logPayloadSizeInfoAnthropic(payload, model) {
|
|
|
6787
6955
|
/**
|
|
6788
6956
|
* Handle non-streaming direct Anthropic response
|
|
6789
6957
|
*/
|
|
6790
|
-
function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateResult) {
|
|
6958
|
+
function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateResult, payload) {
|
|
6791
6959
|
consola.debug("Non-streaming response from Copilot (direct Anthropic):", JSON.stringify(response).slice(-400));
|
|
6792
6960
|
recordResponse(ctx.historyId, {
|
|
6793
6961
|
success: true,
|
|
@@ -6823,6 +6991,16 @@ function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateRes
|
|
|
6823
6991
|
outputTokens: response.usage.output_tokens,
|
|
6824
6992
|
queueWaitMs: ctx.queueWaitMs
|
|
6825
6993
|
});
|
|
6994
|
+
captureRequest({
|
|
6995
|
+
model: response.model,
|
|
6996
|
+
inputTokens: response.usage.input_tokens,
|
|
6997
|
+
outputTokens: response.usage.output_tokens,
|
|
6998
|
+
durationMs: Date.now() - ctx.startTime,
|
|
6999
|
+
success: true,
|
|
7000
|
+
stream: false,
|
|
7001
|
+
toolCount: payload.tools?.length ?? 0,
|
|
7002
|
+
stopReason: response.stop_reason ?? void 0
|
|
7003
|
+
});
|
|
6826
7004
|
let finalResponse = response;
|
|
6827
7005
|
if (state.verbose && truncateResult?.wasCompacted) finalResponse = prependMarkerToAnthropicResponse$1(response, createTruncationMarker$1(truncateResult));
|
|
6828
7006
|
return c.json(finalResponse);
|
|
@@ -6875,8 +7053,14 @@ async function handleDirectAnthropicStreamingResponse(opts) {
|
|
|
6875
7053
|
data: rawEvent.data
|
|
6876
7054
|
});
|
|
6877
7055
|
}
|
|
6878
|
-
|
|
6879
|
-
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs
|
|
7056
|
+
recordAnthropicStreamingResponse(acc, anthropicPayload.model, ctx);
|
|
7057
|
+
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs, void 0, {
|
|
7058
|
+
model: acc.model || anthropicPayload.model,
|
|
7059
|
+
stream: true,
|
|
7060
|
+
durationMs: Date.now() - ctx.startTime,
|
|
7061
|
+
stopReason: acc.stopReason || void 0,
|
|
7062
|
+
toolCount: anthropicPayload.tools?.length ?? 0
|
|
7063
|
+
});
|
|
6880
7064
|
} catch (error) {
|
|
6881
7065
|
consola.error("Direct Anthropic stream error:", error);
|
|
6882
7066
|
recordStreamError({
|
|
@@ -6893,31 +7077,6 @@ async function handleDirectAnthropicStreamingResponse(opts) {
|
|
|
6893
7077
|
});
|
|
6894
7078
|
}
|
|
6895
7079
|
}
|
|
6896
|
-
function recordStreamingResponse$1(acc, fallbackModel, ctx) {
|
|
6897
|
-
const contentBlocks = [];
|
|
6898
|
-
if (acc.content) contentBlocks.push({
|
|
6899
|
-
type: "text",
|
|
6900
|
-
text: acc.content
|
|
6901
|
-
});
|
|
6902
|
-
for (const tc of acc.toolCalls) contentBlocks.push({
|
|
6903
|
-
type: "tool_use",
|
|
6904
|
-
...tc
|
|
6905
|
-
});
|
|
6906
|
-
recordResponse(ctx.historyId, {
|
|
6907
|
-
success: true,
|
|
6908
|
-
model: acc.model || fallbackModel,
|
|
6909
|
-
usage: {
|
|
6910
|
-
input_tokens: acc.inputTokens,
|
|
6911
|
-
output_tokens: acc.outputTokens
|
|
6912
|
-
},
|
|
6913
|
-
stop_reason: acc.stopReason || void 0,
|
|
6914
|
-
content: contentBlocks.length > 0 ? {
|
|
6915
|
-
role: "assistant",
|
|
6916
|
-
content: contentBlocks
|
|
6917
|
-
} : null,
|
|
6918
|
-
toolCalls: acc.toolCalls.length > 0 ? acc.toolCalls : void 0
|
|
6919
|
-
}, Date.now() - ctx.startTime);
|
|
6920
|
-
}
|
|
6921
7080
|
|
|
6922
7081
|
//#endregion
|
|
6923
7082
|
//#region src/routes/messages/subagent-marker.ts
|
|
@@ -6984,7 +7143,8 @@ async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOve
|
|
|
6984
7143
|
c,
|
|
6985
7144
|
response,
|
|
6986
7145
|
toolNameMapping,
|
|
6987
|
-
ctx
|
|
7146
|
+
ctx,
|
|
7147
|
+
anthropicPayload
|
|
6988
7148
|
});
|
|
6989
7149
|
consola.debug("Streaming response from Copilot");
|
|
6990
7150
|
updateTrackerStatus(ctx.trackingId, "streaming");
|
|
@@ -7004,7 +7164,7 @@ async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOve
|
|
|
7004
7164
|
}
|
|
7005
7165
|
}
|
|
7006
7166
|
function handleNonStreamingResponse(opts) {
|
|
7007
|
-
const { c, response, toolNameMapping, ctx } = opts;
|
|
7167
|
+
const { c, response, toolNameMapping, ctx, anthropicPayload } = opts;
|
|
7008
7168
|
consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
|
|
7009
7169
|
let anthropicResponse = translateToAnthropic(response, toolNameMapping);
|
|
7010
7170
|
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
@@ -7040,6 +7200,16 @@ function handleNonStreamingResponse(opts) {
|
|
|
7040
7200
|
outputTokens: anthropicResponse.usage.output_tokens,
|
|
7041
7201
|
queueWaitMs: ctx.queueWaitMs
|
|
7042
7202
|
});
|
|
7203
|
+
captureRequest({
|
|
7204
|
+
model: anthropicResponse.model,
|
|
7205
|
+
inputTokens: anthropicResponse.usage.input_tokens,
|
|
7206
|
+
outputTokens: anthropicResponse.usage.output_tokens,
|
|
7207
|
+
durationMs: Date.now() - ctx.startTime,
|
|
7208
|
+
success: true,
|
|
7209
|
+
stream: false,
|
|
7210
|
+
toolCount: anthropicPayload.tools?.length ?? 0,
|
|
7211
|
+
stopReason: anthropicResponse.stop_reason ?? void 0
|
|
7212
|
+
});
|
|
7043
7213
|
return c.json(anthropicResponse);
|
|
7044
7214
|
}
|
|
7045
7215
|
function prependMarkerToAnthropicResponse(response, marker) {
|
|
@@ -7084,8 +7254,14 @@ async function handleStreamingResponse(opts) {
|
|
|
7084
7254
|
acc,
|
|
7085
7255
|
checkRepetition
|
|
7086
7256
|
});
|
|
7087
|
-
|
|
7088
|
-
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs
|
|
7257
|
+
recordAnthropicStreamingResponse(acc, anthropicPayload.model, ctx);
|
|
7258
|
+
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs, void 0, {
|
|
7259
|
+
model: acc.model || anthropicPayload.model,
|
|
7260
|
+
stream: true,
|
|
7261
|
+
durationMs: Date.now() - ctx.startTime,
|
|
7262
|
+
stopReason: acc.stopReason || void 0,
|
|
7263
|
+
toolCount: anthropicPayload.tools?.length ?? 0
|
|
7264
|
+
});
|
|
7089
7265
|
} catch (error) {
|
|
7090
7266
|
consola.error("Stream error:", error);
|
|
7091
7267
|
recordStreamError({
|
|
@@ -7163,31 +7339,6 @@ async function processStreamChunks(opts) {
|
|
|
7163
7339
|
}
|
|
7164
7340
|
}
|
|
7165
7341
|
}
|
|
7166
|
-
function recordStreamingResponse(acc, fallbackModel, ctx) {
|
|
7167
|
-
const contentBlocks = [];
|
|
7168
|
-
if (acc.content) contentBlocks.push({
|
|
7169
|
-
type: "text",
|
|
7170
|
-
text: acc.content
|
|
7171
|
-
});
|
|
7172
|
-
for (const tc of acc.toolCalls) contentBlocks.push({
|
|
7173
|
-
type: "tool_use",
|
|
7174
|
-
...tc
|
|
7175
|
-
});
|
|
7176
|
-
recordResponse(ctx.historyId, {
|
|
7177
|
-
success: true,
|
|
7178
|
-
model: acc.model || fallbackModel,
|
|
7179
|
-
usage: {
|
|
7180
|
-
input_tokens: acc.inputTokens,
|
|
7181
|
-
output_tokens: acc.outputTokens
|
|
7182
|
-
},
|
|
7183
|
-
stop_reason: acc.stopReason || void 0,
|
|
7184
|
-
content: contentBlocks.length > 0 ? {
|
|
7185
|
-
role: "assistant",
|
|
7186
|
-
content: contentBlocks
|
|
7187
|
-
} : null,
|
|
7188
|
-
toolCalls: acc.toolCalls.length > 0 ? acc.toolCalls : void 0
|
|
7189
|
-
}, Date.now() - ctx.startTime);
|
|
7190
|
-
}
|
|
7191
7342
|
|
|
7192
7343
|
//#endregion
|
|
7193
7344
|
//#region src/routes/messages/handler.ts
|
|
@@ -7361,7 +7512,7 @@ modelRoutes.get("/", async (c) => {
|
|
|
7361
7512
|
const createResponses = async (payload, { vision, initiator }) => {
|
|
7362
7513
|
if (!state.copilotToken) throw new Error("Copilot token not found");
|
|
7363
7514
|
const headers = {
|
|
7364
|
-
...copilotHeaders(state, vision),
|
|
7515
|
+
...copilotHeaders(state, { vision }),
|
|
7365
7516
|
"X-Initiator": initiator
|
|
7366
7517
|
};
|
|
7367
7518
|
payload.service_tier = null;
|
|
@@ -7633,7 +7784,12 @@ const handleResponses = async (c) => {
|
|
|
7633
7784
|
if (finalResult) {
|
|
7634
7785
|
recordResponseResult(finalResult, model, historyId, startTime);
|
|
7635
7786
|
const usage = finalResult.usage;
|
|
7636
|
-
completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, queueWaitMs
|
|
7787
|
+
completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, queueWaitMs, usage?.output_tokens_details?.reasoning_tokens, {
|
|
7788
|
+
model: finalResult.model || model,
|
|
7789
|
+
stream: true,
|
|
7790
|
+
durationMs: Date.now() - startTime,
|
|
7791
|
+
toolCount: tools.length
|
|
7792
|
+
});
|
|
7637
7793
|
} else if (streamErrorMessage) {
|
|
7638
7794
|
recordResponse(historyId, {
|
|
7639
7795
|
success: false,
|
|
@@ -7662,7 +7818,12 @@ const handleResponses = async (c) => {
|
|
|
7662
7818
|
const result = response;
|
|
7663
7819
|
const usage = result.usage;
|
|
7664
7820
|
recordResponseResult(result, model, historyId, startTime);
|
|
7665
|
-
completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, ctx.queueWaitMs
|
|
7821
|
+
completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, ctx.queueWaitMs, usage?.output_tokens_details?.reasoning_tokens, {
|
|
7822
|
+
model: result.model || model,
|
|
7823
|
+
stream: false,
|
|
7824
|
+
durationMs: Date.now() - startTime,
|
|
7825
|
+
toolCount: tools.length
|
|
7826
|
+
});
|
|
7666
7827
|
consola.debug("Forwarding native Responses result:", JSON.stringify(result).slice(-400));
|
|
7667
7828
|
return c.json(result);
|
|
7668
7829
|
} catch (error) {
|
|
@@ -7713,7 +7874,8 @@ function recordResponseResult(result, fallbackModel, historyId, startTime) {
|
|
|
7713
7874
|
model: result.model || fallbackModel,
|
|
7714
7875
|
usage: {
|
|
7715
7876
|
input_tokens: usage?.input_tokens ?? 0,
|
|
7716
|
-
output_tokens: usage?.output_tokens ?? 0
|
|
7877
|
+
output_tokens: usage?.output_tokens ?? 0,
|
|
7878
|
+
...usage?.output_tokens_details ? { output_tokens_details: { reasoning_tokens: usage.output_tokens_details.reasoning_tokens } } : {}
|
|
7717
7879
|
},
|
|
7718
7880
|
stop_reason: extractResponseStopReason(result),
|
|
7719
7881
|
content,
|
|
@@ -7788,6 +7950,18 @@ server.route("/history", historyRoutes);
|
|
|
7788
7950
|
|
|
7789
7951
|
//#endregion
|
|
7790
7952
|
//#region src/start.ts
|
|
7953
|
+
const VALID_ACCOUNT_TYPES = [
|
|
7954
|
+
"individual",
|
|
7955
|
+
"business",
|
|
7956
|
+
"enterprise"
|
|
7957
|
+
];
|
|
7958
|
+
function isValidAccountType(accountType) {
|
|
7959
|
+
return VALID_ACCOUNT_TYPES.includes(accountType);
|
|
7960
|
+
}
|
|
7961
|
+
function validateAccountType(accountType) {
|
|
7962
|
+
if (isValidAccountType(accountType)) return;
|
|
7963
|
+
throw new Error(`Invalid account type: "${accountType}". Available: ${VALID_ACCOUNT_TYPES.join(", ")}`);
|
|
7964
|
+
}
|
|
7791
7965
|
/** Format limit values as "Xk" or "?" if not available */
|
|
7792
7966
|
function formatLimit(value) {
|
|
7793
7967
|
return value ? `${Math.round(value / 1e3)}k` : "?";
|
|
@@ -7810,6 +7984,12 @@ async function runServer(options) {
|
|
|
7810
7984
|
state.verbose = true;
|
|
7811
7985
|
}
|
|
7812
7986
|
state.accountType = options.accountType;
|
|
7987
|
+
try {
|
|
7988
|
+
validateAccountType(state.accountType);
|
|
7989
|
+
} catch (error) {
|
|
7990
|
+
consola.error(error instanceof Error ? error.message : String(error));
|
|
7991
|
+
process.exit(1);
|
|
7992
|
+
}
|
|
7813
7993
|
if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`);
|
|
7814
7994
|
state.manualApprove = options.manual;
|
|
7815
7995
|
state.showToken = options.showToken;
|
|
@@ -7834,6 +8014,10 @@ async function runServer(options) {
|
|
|
7834
8014
|
const limitText = options.historyLimit === 0 ? "unlimited" : `max ${options.historyLimit}`;
|
|
7835
8015
|
consola.info(`History recording enabled (${limitText} entries)`);
|
|
7836
8016
|
}
|
|
8017
|
+
if (options.posthogKey) {
|
|
8018
|
+
initPostHog(options.posthogKey);
|
|
8019
|
+
if (isPostHogEnabled()) consola.info("PostHog analytics enabled");
|
|
8020
|
+
}
|
|
7837
8021
|
initTui({ enabled: true });
|
|
7838
8022
|
initRequestContextManager(state.staleRequestMaxAge).startReaper();
|
|
7839
8023
|
await ensurePaths();
|
|
@@ -7843,7 +8027,13 @@ async function runServer(options) {
|
|
|
7843
8027
|
consola.info("Using provided GitHub token");
|
|
7844
8028
|
} else await setupGitHubToken();
|
|
7845
8029
|
await setupCopilotToken();
|
|
7846
|
-
|
|
8030
|
+
try {
|
|
8031
|
+
await cacheModels();
|
|
8032
|
+
} catch (error) {
|
|
8033
|
+
consola.error(`Failed to fetch available models for account type "${state.accountType}". Check that the account type matches your Copilot plan.`);
|
|
8034
|
+
consola.error(error instanceof Error ? error.message : String(error));
|
|
8035
|
+
process.exit(1);
|
|
8036
|
+
}
|
|
7847
8037
|
consola.info(`Available models:\n${state.models?.data.map((m) => formatModelInfo(m)).join("\n")}`);
|
|
7848
8038
|
const serverUrl = `http://${options.host ?? "localhost"}:${options.port}`;
|
|
7849
8039
|
if (options.claudeCode) {
|
|
@@ -8002,6 +8192,10 @@ const start = defineCommand({
|
|
|
8002
8192
|
type: "string",
|
|
8003
8193
|
default: "+8",
|
|
8004
8194
|
description: "Timezone offset in hours from UTC for log timestamps (e.g., +8, -5, 0)"
|
|
8195
|
+
},
|
|
8196
|
+
"posthog-key": {
|
|
8197
|
+
type: "string",
|
|
8198
|
+
description: "PostHog API key for token usage analytics (opt-in, no key = disabled)"
|
|
8005
8199
|
}
|
|
8006
8200
|
},
|
|
8007
8201
|
run({ args }) {
|
|
@@ -8026,7 +8220,8 @@ const start = defineCommand({
|
|
|
8026
8220
|
compressToolResults: args["compress-tool-results"],
|
|
8027
8221
|
redirectAnthropic: args["redirect-anthropic"],
|
|
8028
8222
|
rewriteAnthropicTools: !args["no-rewrite-anthropic-tools"],
|
|
8029
|
-
timezoneOffset: parseTimezoneOffset(args["timezone-offset"])
|
|
8223
|
+
timezoneOffset: parseTimezoneOffset(args["timezone-offset"]),
|
|
8224
|
+
posthogKey: args["posthog-key"]
|
|
8030
8225
|
});
|
|
8031
8226
|
}
|
|
8032
8227
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dianshuv/copilot-api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"gpt-tokenizer": "^3.4.0",
|
|
42
42
|
"hono": "^4.11.7",
|
|
43
43
|
"picocolors": "^1.1.1",
|
|
44
|
+
"posthog-node": "^5.28.6",
|
|
44
45
|
"proxy-from-env": "^1.1.0",
|
|
45
46
|
"srvx": "^0.10.1",
|
|
46
47
|
"tiny-invariant": "^1.3.3",
|