@dianshuv/copilot-api 0.11.2 → 0.11.4
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/dist/main.mjs +191 -34
- package/package.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -1348,7 +1348,7 @@ const patchClaude = defineCommand({
|
|
|
1348
1348
|
|
|
1349
1349
|
//#endregion
|
|
1350
1350
|
//#region package.json
|
|
1351
|
-
var version = "0.11.
|
|
1351
|
+
var version = "0.11.4";
|
|
1352
1352
|
|
|
1353
1353
|
//#endregion
|
|
1354
1354
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -7324,8 +7324,10 @@ function stripServerToolsFromPayload(tools) {
|
|
|
7324
7324
|
}
|
|
7325
7325
|
/**
|
|
7326
7326
|
* Effective 1M context window. Two ways a request lands on this size:
|
|
7327
|
-
* (a) base model id + context-1m-2025-08-07 beta header (e.g. claude-opus-4.8
|
|
7328
|
-
*
|
|
7327
|
+
* (a) base model id + context-1m-2025-08-07 beta header (e.g. claude-opus-4.8,
|
|
7328
|
+
* claude-opus-4.7),
|
|
7329
|
+
* (b) a distinct upstream "-1m-internal" model id (a historical convention; no
|
|
7330
|
+
* model currently on Copilot uses it, but the resolver still probes for it).
|
|
7329
7331
|
*/
|
|
7330
7332
|
const ONE_MILLION_CONTEXT_WINDOW_TOKENS = 1e6;
|
|
7331
7333
|
/**
|
|
@@ -7390,6 +7392,81 @@ function supportsDirectAnthropicApi(modelId) {
|
|
|
7390
7392
|
return resolveAnthropicModelForDirectPath(modelId) !== void 0;
|
|
7391
7393
|
}
|
|
7392
7394
|
|
|
7395
|
+
//#endregion
|
|
7396
|
+
//#region src/lib/stream-keepalive.ts
|
|
7397
|
+
/** SSE comment line used as a keepalive heartbeat. */
|
|
7398
|
+
const SSE_PING = ": ping\n\n";
|
|
7399
|
+
/**
|
|
7400
|
+
* Grace period before opening a keepalive stream. Normal upstream responses
|
|
7401
|
+
* resolve sub-second (response headers arrive immediately, the body is not
|
|
7402
|
+
* buffered); only a request queued behind the rate limiter takes >=10s. 3s
|
|
7403
|
+
* cleanly separates the two — a request still pending after 3s is queued.
|
|
7404
|
+
*/
|
|
7405
|
+
const RATE_LIMIT_GRACE_MS = 3e3;
|
|
7406
|
+
/**
|
|
7407
|
+
* Interval between keepalive pings once the stream is open. Frequent enough to
|
|
7408
|
+
* prove liveness and defeat idle-connection drops, far below Claude Code's
|
|
7409
|
+
* default 10-minute request timeout.
|
|
7410
|
+
*/
|
|
7411
|
+
const KEEPALIVE_PING_INTERVAL_MS = 5e3;
|
|
7412
|
+
/**
|
|
7413
|
+
* Capture a promise's outcome as a value. The returned promise never rejects,
|
|
7414
|
+
* so it can be raced against a timer and awaited again later without producing
|
|
7415
|
+
* an unhandled rejection or a second pending chain on the original promise.
|
|
7416
|
+
*/
|
|
7417
|
+
function settle(promise) {
|
|
7418
|
+
return promise.then((value) => ({
|
|
7419
|
+
kind: "done",
|
|
7420
|
+
value
|
|
7421
|
+
}), (error) => ({
|
|
7422
|
+
kind: "error",
|
|
7423
|
+
error
|
|
7424
|
+
}));
|
|
7425
|
+
}
|
|
7426
|
+
/**
|
|
7427
|
+
* Race an already-settled promise against a grace timer. Resolves with the
|
|
7428
|
+
* settled outcome if it arrives within `graceMs`, otherwise `{ kind: "timeout" }`.
|
|
7429
|
+
* The underlying work keeps running — await the SAME `settled` promise afterward
|
|
7430
|
+
* to obtain its eventual result.
|
|
7431
|
+
*/
|
|
7432
|
+
async function raceWithGrace(settled, graceMs) {
|
|
7433
|
+
let timer;
|
|
7434
|
+
const timeout = new Promise((resolve) => {
|
|
7435
|
+
timer = setTimeout(() => resolve({ kind: "timeout" }), graceMs);
|
|
7436
|
+
});
|
|
7437
|
+
const result = await Promise.race([settled, timeout]);
|
|
7438
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
7439
|
+
return result;
|
|
7440
|
+
}
|
|
7441
|
+
/**
|
|
7442
|
+
* Drive an open SSE stream while the upstream request is still queued: emit a
|
|
7443
|
+
* ping immediately, then every `pingIntervalMs`, until `settled` resolves —
|
|
7444
|
+
* then hand off to `onResponse` (success) or `onError` (failure). Pings stop on
|
|
7445
|
+
* client abort, on resolution, and never write to an aborted stream.
|
|
7446
|
+
*/
|
|
7447
|
+
async function runStreamWithKeepalive(opts) {
|
|
7448
|
+
const { stream, settled, pingIntervalMs, onResponse, onError } = opts;
|
|
7449
|
+
let pinging = true;
|
|
7450
|
+
const sendPing = () => {
|
|
7451
|
+
if (pinging && !stream.aborted) stream.write(SSE_PING);
|
|
7452
|
+
};
|
|
7453
|
+
sendPing();
|
|
7454
|
+
const interval = setInterval(sendPing, pingIntervalMs);
|
|
7455
|
+
stream.onAbort(() => {
|
|
7456
|
+
pinging = false;
|
|
7457
|
+
clearInterval(interval);
|
|
7458
|
+
});
|
|
7459
|
+
let result;
|
|
7460
|
+
try {
|
|
7461
|
+
result = await settled;
|
|
7462
|
+
} finally {
|
|
7463
|
+
pinging = false;
|
|
7464
|
+
clearInterval(interval);
|
|
7465
|
+
}
|
|
7466
|
+
if (result.kind === "done") await onResponse(result.value);
|
|
7467
|
+
else if (result.kind === "error") await onError(result.error);
|
|
7468
|
+
}
|
|
7469
|
+
|
|
7393
7470
|
//#endregion
|
|
7394
7471
|
//#region src/routes/messages/message-utils.ts
|
|
7395
7472
|
function convertAnthropicMessages(messages) {
|
|
@@ -7677,7 +7754,7 @@ function translateModelName(model) {
|
|
|
7677
7754
|
if (/^claude-sonnet-4-\d+$/.test(model)) return "claude-sonnet-4";
|
|
7678
7755
|
if (model === "claude-opus-4-8-1m") return "claude-opus-4.8";
|
|
7679
7756
|
if (model === "claude-opus-4-8") return "claude-opus-4.8";
|
|
7680
|
-
if (model === "claude-opus-4-7-1m") return "claude-opus-4.7
|
|
7757
|
+
if (model === "claude-opus-4-7-1m") return "claude-opus-4.7";
|
|
7681
7758
|
if (/^claude-opus-4-7$/.test(model)) return "claude-opus-4.7";
|
|
7682
7759
|
if (model === "claude-opus-4-6-1m") return "claude-opus-4.6-1m";
|
|
7683
7760
|
if (/^claude-opus-4-6$/.test(model)) return "claude-opus-4.6";
|
|
@@ -8597,27 +8674,66 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiat
|
|
|
8597
8674
|
} else if (state.autoTruncate && !selectedModel) consola.debug(`[Anthropic] Model '${anthropicPayload.model}' not found, skipping auto-truncate`);
|
|
8598
8675
|
if (state.manualApprove) await awaitApproval();
|
|
8599
8676
|
const clientAnthropicBetaHeader = c.req.header("anthropic-beta");
|
|
8677
|
+
const isStreaming = anthropicPayload.stream === true;
|
|
8600
8678
|
try {
|
|
8601
|
-
const
|
|
8679
|
+
const settled = settle(executeWithAdaptiveRateLimit(() => createAnthropicMessages(effectivePayload, {
|
|
8602
8680
|
initiator: initiatorOverride,
|
|
8603
8681
|
injectContext1mBeta: needsContext1mBeta,
|
|
8604
8682
|
errorModelIdOverride: needsContext1mBeta ? originalModelId : void 0,
|
|
8605
8683
|
clientAnthropicBetaHeader
|
|
8606
|
-
}));
|
|
8607
|
-
|
|
8608
|
-
if (
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8684
|
+
})));
|
|
8685
|
+
const raced = isStreaming ? await raceWithGrace(settled, RATE_LIMIT_GRACE_MS) : await settled;
|
|
8686
|
+
if (raced.kind === "error") throw raced.error;
|
|
8687
|
+
if (raced.kind === "done") {
|
|
8688
|
+
const { result: response, queueWaitMs } = raced.value;
|
|
8689
|
+
ctx.queueWaitMs = queueWaitMs;
|
|
8690
|
+
if (Symbol.asyncIterator in response) {
|
|
8691
|
+
consola.debug("Streaming response from Copilot (direct Anthropic)");
|
|
8692
|
+
updateTrackerStatus(ctx.trackingId, "streaming");
|
|
8693
|
+
return streamSSE(c, async (stream) => {
|
|
8694
|
+
await handleDirectAnthropicStreamingResponse({
|
|
8695
|
+
stream,
|
|
8696
|
+
response,
|
|
8697
|
+
anthropicPayload: effectivePayload,
|
|
8698
|
+
ctx
|
|
8699
|
+
});
|
|
8617
8700
|
});
|
|
8618
|
-
}
|
|
8701
|
+
}
|
|
8702
|
+
return handleDirectAnthropicNonStreamingResponse(c, recoverLeakedToolCallsInResponse(response, toolNameSet(effectivePayload.tools)), ctx, truncateResult, effectivePayload);
|
|
8619
8703
|
}
|
|
8620
|
-
|
|
8704
|
+
consola.debug("[RateLimiter] Request queued past grace; opening keepalive stream (direct Anthropic)");
|
|
8705
|
+
updateTrackerStatus(ctx.trackingId, "streaming");
|
|
8706
|
+
return streamSSE(c, async (stream) => {
|
|
8707
|
+
await runStreamWithKeepalive({
|
|
8708
|
+
stream,
|
|
8709
|
+
settled,
|
|
8710
|
+
pingIntervalMs: KEEPALIVE_PING_INTERVAL_MS,
|
|
8711
|
+
onResponse: async ({ result: response, queueWaitMs }) => {
|
|
8712
|
+
ctx.queueWaitMs = queueWaitMs;
|
|
8713
|
+
await handleDirectAnthropicStreamingResponse({
|
|
8714
|
+
stream,
|
|
8715
|
+
response,
|
|
8716
|
+
anthropicPayload: effectivePayload,
|
|
8717
|
+
ctx
|
|
8718
|
+
});
|
|
8719
|
+
},
|
|
8720
|
+
onError: async (error) => {
|
|
8721
|
+
recordStreamError({
|
|
8722
|
+
acc: createAnthropicStreamAccumulator(),
|
|
8723
|
+
fallbackModel: anthropicPayload.model,
|
|
8724
|
+
ctx,
|
|
8725
|
+
error,
|
|
8726
|
+
endpoint: "messages"
|
|
8727
|
+
});
|
|
8728
|
+
failTracking(ctx.trackingId, error);
|
|
8729
|
+
const errorEvent = translateErrorToAnthropicErrorEvent(error);
|
|
8730
|
+
await stream.writeSSE({
|
|
8731
|
+
event: errorEvent.type,
|
|
8732
|
+
data: JSON.stringify(errorEvent)
|
|
8733
|
+
});
|
|
8734
|
+
}
|
|
8735
|
+
});
|
|
8736
|
+
});
|
|
8621
8737
|
} catch (error) {
|
|
8622
8738
|
if (error instanceof HTTPError && error.status === 413) logPayloadSizeInfoAnthropic(effectivePayload, selectedModel);
|
|
8623
8739
|
recordErrorResponse(ctx, anthropicPayload.model, error, "messages", anthropicPayload.stream ?? false);
|
|
@@ -8862,30 +8978,71 @@ async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOve
|
|
|
8862
8978
|
if (autoTruncateConfig.tokenLimitCacheKeyOverride !== void 0) errorModelIdOverride = autoTruncateConfig.tokenLimitCacheKeyOverride;
|
|
8863
8979
|
else if (needsContext1mBeta && selectedModel) errorModelIdOverride = selectedModel.id;
|
|
8864
8980
|
else if (hasOneMillionSuffix) errorModelIdOverride = originalModelId;
|
|
8981
|
+
const isStreaming = anthropicPayload.stream === true;
|
|
8865
8982
|
try {
|
|
8866
|
-
const
|
|
8983
|
+
const settled = settle(executeWithAdaptiveRateLimit(() => createChatCompletions(openAIPayload, {
|
|
8867
8984
|
initiator: initiatorOverride,
|
|
8868
8985
|
resolvedModel: selectedModel,
|
|
8869
8986
|
anthropicBeta,
|
|
8870
8987
|
errorModelIdOverride
|
|
8871
|
-
}));
|
|
8872
|
-
|
|
8873
|
-
if (
|
|
8874
|
-
|
|
8875
|
-
response,
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8988
|
+
})));
|
|
8989
|
+
const raced = isStreaming ? await raceWithGrace(settled, RATE_LIMIT_GRACE_MS) : await settled;
|
|
8990
|
+
if (raced.kind === "error") throw raced.error;
|
|
8991
|
+
if (raced.kind === "done") {
|
|
8992
|
+
const { result: response, queueWaitMs } = raced.value;
|
|
8993
|
+
ctx.queueWaitMs = queueWaitMs;
|
|
8994
|
+
if (isNonStreaming(response)) return handleNonStreamingResponse({
|
|
8995
|
+
c,
|
|
8996
|
+
response,
|
|
8997
|
+
toolNameMapping,
|
|
8998
|
+
ctx,
|
|
8999
|
+
anthropicPayload
|
|
9000
|
+
});
|
|
9001
|
+
consola.debug("Streaming response from Copilot");
|
|
9002
|
+
updateTrackerStatus(ctx.trackingId, "streaming");
|
|
9003
|
+
return streamSSE(c, async (stream) => {
|
|
9004
|
+
await handleStreamingResponse({
|
|
9005
|
+
stream,
|
|
9006
|
+
response,
|
|
9007
|
+
toolNameMapping,
|
|
9008
|
+
anthropicPayload,
|
|
9009
|
+
ctx
|
|
9010
|
+
});
|
|
9011
|
+
});
|
|
9012
|
+
}
|
|
9013
|
+
consola.debug("[RateLimiter] Request queued past grace; opening keepalive stream (translated)");
|
|
8881
9014
|
updateTrackerStatus(ctx.trackingId, "streaming");
|
|
8882
9015
|
return streamSSE(c, async (stream) => {
|
|
8883
|
-
await
|
|
9016
|
+
await runStreamWithKeepalive({
|
|
8884
9017
|
stream,
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
9018
|
+
settled,
|
|
9019
|
+
pingIntervalMs: KEEPALIVE_PING_INTERVAL_MS,
|
|
9020
|
+
onResponse: async ({ result: response, queueWaitMs }) => {
|
|
9021
|
+
ctx.queueWaitMs = queueWaitMs;
|
|
9022
|
+
if (isNonStreaming(response)) return;
|
|
9023
|
+
await handleStreamingResponse({
|
|
9024
|
+
stream,
|
|
9025
|
+
response,
|
|
9026
|
+
toolNameMapping,
|
|
9027
|
+
anthropicPayload,
|
|
9028
|
+
ctx
|
|
9029
|
+
});
|
|
9030
|
+
},
|
|
9031
|
+
onError: async (error) => {
|
|
9032
|
+
recordStreamError({
|
|
9033
|
+
acc: createAnthropicStreamAccumulator(),
|
|
9034
|
+
fallbackModel: anthropicPayload.model,
|
|
9035
|
+
ctx,
|
|
9036
|
+
error,
|
|
9037
|
+
endpoint: "messages"
|
|
9038
|
+
});
|
|
9039
|
+
failTracking(ctx.trackingId, error);
|
|
9040
|
+
const errorEvent = translateErrorToAnthropicErrorEvent(error);
|
|
9041
|
+
await stream.writeSSE({
|
|
9042
|
+
event: errorEvent.type,
|
|
9043
|
+
data: JSON.stringify(errorEvent)
|
|
9044
|
+
});
|
|
9045
|
+
}
|
|
8889
9046
|
});
|
|
8890
9047
|
});
|
|
8891
9048
|
} catch (error) {
|