@dianshuv/copilot-api 0.5.0 → 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 +154 -21
- 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";
|
|
@@ -1035,7 +1036,7 @@ const patchClaude = defineCommand({
|
|
|
1035
1036
|
|
|
1036
1037
|
//#endregion
|
|
1037
1038
|
//#region package.json
|
|
1038
|
-
var version = "0.
|
|
1039
|
+
var version = "0.6.0";
|
|
1039
1040
|
|
|
1040
1041
|
//#endregion
|
|
1041
1042
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -1947,6 +1948,55 @@ function exportHistory(format = "json") {
|
|
|
1947
1948
|
return [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
|
|
1948
1949
|
}
|
|
1949
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
|
+
|
|
1950
2000
|
//#endregion
|
|
1951
2001
|
//#region src/lib/proxy.ts
|
|
1952
2002
|
/**
|
|
@@ -2157,7 +2207,7 @@ async function gracefulShutdown(signal, deps) {
|
|
|
2157
2207
|
try {
|
|
2158
2208
|
if (await drainActiveRequests(gracefulWaitMs, tracker, drainOpts) === "drained") {
|
|
2159
2209
|
consola.info("All requests completed naturally");
|
|
2160
|
-
finalize(tracker);
|
|
2210
|
+
await finalize(tracker);
|
|
2161
2211
|
return;
|
|
2162
2212
|
}
|
|
2163
2213
|
} catch (error) {
|
|
@@ -2169,7 +2219,7 @@ async function gracefulShutdown(signal, deps) {
|
|
|
2169
2219
|
try {
|
|
2170
2220
|
if (await drainActiveRequests(abortWaitMs, tracker, drainOpts) === "drained") {
|
|
2171
2221
|
consola.info("All requests completed after abort signal");
|
|
2172
|
-
finalize(tracker);
|
|
2222
|
+
await finalize(tracker);
|
|
2173
2223
|
return;
|
|
2174
2224
|
}
|
|
2175
2225
|
} catch (error) {
|
|
@@ -2183,13 +2233,15 @@ async function gracefulShutdown(signal, deps) {
|
|
|
2183
2233
|
consola.error("Error force-closing server:", error);
|
|
2184
2234
|
}
|
|
2185
2235
|
}
|
|
2186
|
-
finalize(tracker);
|
|
2236
|
+
await finalize(tracker);
|
|
2187
2237
|
} else {
|
|
2238
|
+
await shutdownPostHog();
|
|
2188
2239
|
consola.info("Shutdown complete");
|
|
2189
2240
|
shutdownResolve?.();
|
|
2190
2241
|
}
|
|
2191
2242
|
}
|
|
2192
|
-
function finalize(tracker) {
|
|
2243
|
+
async function finalize(tracker) {
|
|
2244
|
+
await shutdownPostHog();
|
|
2193
2245
|
tracker.destroy();
|
|
2194
2246
|
consola.info("Shutdown complete");
|
|
2195
2247
|
shutdownResolve?.();
|
|
@@ -3503,8 +3555,8 @@ function recordErrorResponse(ctx, model, error) {
|
|
|
3503
3555
|
content: null
|
|
3504
3556
|
}, Date.now() - ctx.startTime);
|
|
3505
3557
|
}
|
|
3506
|
-
/** Complete TUI tracking */
|
|
3507
|
-
function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs, reasoningTokens) {
|
|
3558
|
+
/** Complete TUI tracking and send PostHog analytics */
|
|
3559
|
+
function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs, reasoningTokens, analytics) {
|
|
3508
3560
|
if (!trackingId) return;
|
|
3509
3561
|
requestTracker.updateRequest(trackingId, {
|
|
3510
3562
|
inputTokens,
|
|
@@ -3517,6 +3569,17 @@ function completeTracking(trackingId, inputTokens, outputTokens, queueWaitMs, re
|
|
|
3517
3569
|
outputTokens,
|
|
3518
3570
|
reasoningTokens
|
|
3519
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
|
|
3582
|
+
});
|
|
3520
3583
|
}
|
|
3521
3584
|
/** Fail TUI tracking */
|
|
3522
3585
|
function failTracking(trackingId, error) {
|
|
@@ -3685,7 +3748,7 @@ async function executeRequest(opts) {
|
|
|
3685
3748
|
try {
|
|
3686
3749
|
const { result: response, queueWaitMs } = await executeWithAdaptiveRateLimit(() => createChatCompletions(payload));
|
|
3687
3750
|
ctx.queueWaitMs = queueWaitMs;
|
|
3688
|
-
if (isNonStreaming(response)) return handleNonStreamingResponse$1(c, response, ctx);
|
|
3751
|
+
if (isNonStreaming(response)) return handleNonStreamingResponse$1(c, response, ctx, payload);
|
|
3689
3752
|
consola.debug("Streaming response");
|
|
3690
3753
|
updateTrackerStatus(trackingId, "streaming");
|
|
3691
3754
|
return streamSSE(c, async (stream) => {
|
|
@@ -3712,7 +3775,7 @@ async function logTokenCount(payload, selectedModel) {
|
|
|
3712
3775
|
consola.debug("Failed to calculate token count:", error);
|
|
3713
3776
|
}
|
|
3714
3777
|
}
|
|
3715
|
-
function handleNonStreamingResponse$1(c, originalResponse, ctx) {
|
|
3778
|
+
function handleNonStreamingResponse$1(c, originalResponse, ctx, payload) {
|
|
3716
3779
|
consola.debug("Non-streaming response:", JSON.stringify(originalResponse));
|
|
3717
3780
|
let response = originalResponse;
|
|
3718
3781
|
if (state.verbose && ctx.truncateResult?.wasCompacted && response.choices[0]?.message.content) {
|
|
@@ -3731,6 +3794,7 @@ function handleNonStreamingResponse$1(c, originalResponse, ctx) {
|
|
|
3731
3794
|
const choice = response.choices[0];
|
|
3732
3795
|
const usage = response.usage;
|
|
3733
3796
|
const reasoningTokens = getReasoningTokensFromOpenAIUsage(usage);
|
|
3797
|
+
const durationMs = Date.now() - ctx.startTime;
|
|
3734
3798
|
recordResponse(ctx.historyId, {
|
|
3735
3799
|
success: true,
|
|
3736
3800
|
model: response.model,
|
|
@@ -3742,13 +3806,24 @@ function handleNonStreamingResponse$1(c, originalResponse, ctx) {
|
|
|
3742
3806
|
stop_reason: choice.finish_reason,
|
|
3743
3807
|
content: buildResponseContent(choice),
|
|
3744
3808
|
toolCalls: extractToolCalls(choice)
|
|
3745
|
-
},
|
|
3809
|
+
}, durationMs);
|
|
3746
3810
|
if (ctx.trackingId && usage) requestTracker.updateRequest(ctx.trackingId, {
|
|
3747
3811
|
inputTokens: usage.prompt_tokens,
|
|
3748
3812
|
outputTokens: usage.completion_tokens,
|
|
3749
3813
|
queueWaitMs: ctx.queueWaitMs,
|
|
3750
3814
|
reasoningTokens
|
|
3751
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
|
|
3826
|
+
});
|
|
3752
3827
|
return c.json(response);
|
|
3753
3828
|
}
|
|
3754
3829
|
function buildResponseContent(choice) {
|
|
@@ -3815,7 +3890,13 @@ async function handleStreamingResponse$1(opts) {
|
|
|
3815
3890
|
await stream.writeSSE(chunk);
|
|
3816
3891
|
}
|
|
3817
3892
|
recordStreamSuccess(acc, payload.model, ctx);
|
|
3818
|
-
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs, acc.reasoningTokens
|
|
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
|
+
});
|
|
3819
3900
|
} catch (error) {
|
|
3820
3901
|
recordStreamError({
|
|
3821
3902
|
acc,
|
|
@@ -6849,7 +6930,7 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiat
|
|
|
6849
6930
|
});
|
|
6850
6931
|
});
|
|
6851
6932
|
}
|
|
6852
|
-
return handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateResult);
|
|
6933
|
+
return handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateResult, effectivePayload);
|
|
6853
6934
|
} catch (error) {
|
|
6854
6935
|
if (error instanceof HTTPError && error.status === 413) logPayloadSizeInfoAnthropic(effectivePayload, selectedModel);
|
|
6855
6936
|
recordErrorResponse(ctx, anthropicPayload.model, error);
|
|
@@ -6874,7 +6955,7 @@ function logPayloadSizeInfoAnthropic(payload, model) {
|
|
|
6874
6955
|
/**
|
|
6875
6956
|
* Handle non-streaming direct Anthropic response
|
|
6876
6957
|
*/
|
|
6877
|
-
function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateResult) {
|
|
6958
|
+
function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateResult, payload) {
|
|
6878
6959
|
consola.debug("Non-streaming response from Copilot (direct Anthropic):", JSON.stringify(response).slice(-400));
|
|
6879
6960
|
recordResponse(ctx.historyId, {
|
|
6880
6961
|
success: true,
|
|
@@ -6910,6 +6991,16 @@ function handleDirectAnthropicNonStreamingResponse(c, response, ctx, truncateRes
|
|
|
6910
6991
|
outputTokens: response.usage.output_tokens,
|
|
6911
6992
|
queueWaitMs: ctx.queueWaitMs
|
|
6912
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
|
+
});
|
|
6913
7004
|
let finalResponse = response;
|
|
6914
7005
|
if (state.verbose && truncateResult?.wasCompacted) finalResponse = prependMarkerToAnthropicResponse$1(response, createTruncationMarker$1(truncateResult));
|
|
6915
7006
|
return c.json(finalResponse);
|
|
@@ -6963,7 +7054,13 @@ async function handleDirectAnthropicStreamingResponse(opts) {
|
|
|
6963
7054
|
});
|
|
6964
7055
|
}
|
|
6965
7056
|
recordAnthropicStreamingResponse(acc, anthropicPayload.model, ctx);
|
|
6966
|
-
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs
|
|
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
|
+
});
|
|
6967
7064
|
} catch (error) {
|
|
6968
7065
|
consola.error("Direct Anthropic stream error:", error);
|
|
6969
7066
|
recordStreamError({
|
|
@@ -7046,7 +7143,8 @@ async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOve
|
|
|
7046
7143
|
c,
|
|
7047
7144
|
response,
|
|
7048
7145
|
toolNameMapping,
|
|
7049
|
-
ctx
|
|
7146
|
+
ctx,
|
|
7147
|
+
anthropicPayload
|
|
7050
7148
|
});
|
|
7051
7149
|
consola.debug("Streaming response from Copilot");
|
|
7052
7150
|
updateTrackerStatus(ctx.trackingId, "streaming");
|
|
@@ -7066,7 +7164,7 @@ async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOve
|
|
|
7066
7164
|
}
|
|
7067
7165
|
}
|
|
7068
7166
|
function handleNonStreamingResponse(opts) {
|
|
7069
|
-
const { c, response, toolNameMapping, ctx } = opts;
|
|
7167
|
+
const { c, response, toolNameMapping, ctx, anthropicPayload } = opts;
|
|
7070
7168
|
consola.debug("Non-streaming response from Copilot:", JSON.stringify(response).slice(-400));
|
|
7071
7169
|
let anthropicResponse = translateToAnthropic(response, toolNameMapping);
|
|
7072
7170
|
consola.debug("Translated Anthropic response:", JSON.stringify(anthropicResponse));
|
|
@@ -7102,6 +7200,16 @@ function handleNonStreamingResponse(opts) {
|
|
|
7102
7200
|
outputTokens: anthropicResponse.usage.output_tokens,
|
|
7103
7201
|
queueWaitMs: ctx.queueWaitMs
|
|
7104
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
|
+
});
|
|
7105
7213
|
return c.json(anthropicResponse);
|
|
7106
7214
|
}
|
|
7107
7215
|
function prependMarkerToAnthropicResponse(response, marker) {
|
|
@@ -7147,7 +7255,13 @@ async function handleStreamingResponse(opts) {
|
|
|
7147
7255
|
checkRepetition
|
|
7148
7256
|
});
|
|
7149
7257
|
recordAnthropicStreamingResponse(acc, anthropicPayload.model, ctx);
|
|
7150
|
-
completeTracking(ctx.trackingId, acc.inputTokens, acc.outputTokens, ctx.queueWaitMs
|
|
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
|
+
});
|
|
7151
7265
|
} catch (error) {
|
|
7152
7266
|
consola.error("Stream error:", error);
|
|
7153
7267
|
recordStreamError({
|
|
@@ -7670,7 +7784,12 @@ const handleResponses = async (c) => {
|
|
|
7670
7784
|
if (finalResult) {
|
|
7671
7785
|
recordResponseResult(finalResult, model, historyId, startTime);
|
|
7672
7786
|
const usage = finalResult.usage;
|
|
7673
|
-
completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, queueWaitMs, usage?.output_tokens_details?.reasoning_tokens
|
|
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
|
+
});
|
|
7674
7793
|
} else if (streamErrorMessage) {
|
|
7675
7794
|
recordResponse(historyId, {
|
|
7676
7795
|
success: false,
|
|
@@ -7699,7 +7818,12 @@ const handleResponses = async (c) => {
|
|
|
7699
7818
|
const result = response;
|
|
7700
7819
|
const usage = result.usage;
|
|
7701
7820
|
recordResponseResult(result, model, historyId, startTime);
|
|
7702
|
-
completeTracking(trackingId, usage?.input_tokens ?? 0, usage?.output_tokens ?? 0, ctx.queueWaitMs, usage?.output_tokens_details?.reasoning_tokens
|
|
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
|
+
});
|
|
7703
7827
|
consola.debug("Forwarding native Responses result:", JSON.stringify(result).slice(-400));
|
|
7704
7828
|
return c.json(result);
|
|
7705
7829
|
} catch (error) {
|
|
@@ -7890,6 +8014,10 @@ async function runServer(options) {
|
|
|
7890
8014
|
const limitText = options.historyLimit === 0 ? "unlimited" : `max ${options.historyLimit}`;
|
|
7891
8015
|
consola.info(`History recording enabled (${limitText} entries)`);
|
|
7892
8016
|
}
|
|
8017
|
+
if (options.posthogKey) {
|
|
8018
|
+
initPostHog(options.posthogKey);
|
|
8019
|
+
if (isPostHogEnabled()) consola.info("PostHog analytics enabled");
|
|
8020
|
+
}
|
|
7893
8021
|
initTui({ enabled: true });
|
|
7894
8022
|
initRequestContextManager(state.staleRequestMaxAge).startReaper();
|
|
7895
8023
|
await ensurePaths();
|
|
@@ -8064,6 +8192,10 @@ const start = defineCommand({
|
|
|
8064
8192
|
type: "string",
|
|
8065
8193
|
default: "+8",
|
|
8066
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)"
|
|
8067
8199
|
}
|
|
8068
8200
|
},
|
|
8069
8201
|
run({ args }) {
|
|
@@ -8088,7 +8220,8 @@ const start = defineCommand({
|
|
|
8088
8220
|
compressToolResults: args["compress-tool-results"],
|
|
8089
8221
|
redirectAnthropic: args["redirect-anthropic"],
|
|
8090
8222
|
rewriteAnthropicTools: !args["no-rewrite-anthropic-tools"],
|
|
8091
|
-
timezoneOffset: parseTimezoneOffset(args["timezone-offset"])
|
|
8223
|
+
timezoneOffset: parseTimezoneOffset(args["timezone-offset"]),
|
|
8224
|
+
posthogKey: args["posthog-key"]
|
|
8092
8225
|
});
|
|
8093
8226
|
}
|
|
8094
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",
|