@dianshuv/copilot-api 0.11.3 → 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 +186 -31
- 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
|
|
@@ -7392,6 +7392,81 @@ function supportsDirectAnthropicApi(modelId) {
|
|
|
7392
7392
|
return resolveAnthropicModelForDirectPath(modelId) !== void 0;
|
|
7393
7393
|
}
|
|
7394
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
|
+
|
|
7395
7470
|
//#endregion
|
|
7396
7471
|
//#region src/routes/messages/message-utils.ts
|
|
7397
7472
|
function convertAnthropicMessages(messages) {
|
|
@@ -8599,27 +8674,66 @@ async function handleDirectAnthropicCompletion(c, anthropicPayload, ctx, initiat
|
|
|
8599
8674
|
} else if (state.autoTruncate && !selectedModel) consola.debug(`[Anthropic] Model '${anthropicPayload.model}' not found, skipping auto-truncate`);
|
|
8600
8675
|
if (state.manualApprove) await awaitApproval();
|
|
8601
8676
|
const clientAnthropicBetaHeader = c.req.header("anthropic-beta");
|
|
8677
|
+
const isStreaming = anthropicPayload.stream === true;
|
|
8602
8678
|
try {
|
|
8603
|
-
const
|
|
8679
|
+
const settled = settle(executeWithAdaptiveRateLimit(() => createAnthropicMessages(effectivePayload, {
|
|
8604
8680
|
initiator: initiatorOverride,
|
|
8605
8681
|
injectContext1mBeta: needsContext1mBeta,
|
|
8606
8682
|
errorModelIdOverride: needsContext1mBeta ? originalModelId : void 0,
|
|
8607
8683
|
clientAnthropicBetaHeader
|
|
8608
|
-
}));
|
|
8609
|
-
|
|
8610
|
-
if (
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
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
|
+
});
|
|
8619
8700
|
});
|
|
8620
|
-
}
|
|
8701
|
+
}
|
|
8702
|
+
return handleDirectAnthropicNonStreamingResponse(c, recoverLeakedToolCallsInResponse(response, toolNameSet(effectivePayload.tools)), ctx, truncateResult, effectivePayload);
|
|
8621
8703
|
}
|
|
8622
|
-
|
|
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
|
+
});
|
|
8623
8737
|
} catch (error) {
|
|
8624
8738
|
if (error instanceof HTTPError && error.status === 413) logPayloadSizeInfoAnthropic(effectivePayload, selectedModel);
|
|
8625
8739
|
recordErrorResponse(ctx, anthropicPayload.model, error, "messages", anthropicPayload.stream ?? false);
|
|
@@ -8864,30 +8978,71 @@ async function handleTranslatedCompletion(c, anthropicPayload, ctx, initiatorOve
|
|
|
8864
8978
|
if (autoTruncateConfig.tokenLimitCacheKeyOverride !== void 0) errorModelIdOverride = autoTruncateConfig.tokenLimitCacheKeyOverride;
|
|
8865
8979
|
else if (needsContext1mBeta && selectedModel) errorModelIdOverride = selectedModel.id;
|
|
8866
8980
|
else if (hasOneMillionSuffix) errorModelIdOverride = originalModelId;
|
|
8981
|
+
const isStreaming = anthropicPayload.stream === true;
|
|
8867
8982
|
try {
|
|
8868
|
-
const
|
|
8983
|
+
const settled = settle(executeWithAdaptiveRateLimit(() => createChatCompletions(openAIPayload, {
|
|
8869
8984
|
initiator: initiatorOverride,
|
|
8870
8985
|
resolvedModel: selectedModel,
|
|
8871
8986
|
anthropicBeta,
|
|
8872
8987
|
errorModelIdOverride
|
|
8873
|
-
}));
|
|
8874
|
-
|
|
8875
|
-
if (
|
|
8876
|
-
|
|
8877
|
-
response,
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
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)");
|
|
8883
9014
|
updateTrackerStatus(ctx.trackingId, "streaming");
|
|
8884
9015
|
return streamSSE(c, async (stream) => {
|
|
8885
|
-
await
|
|
9016
|
+
await runStreamWithKeepalive({
|
|
8886
9017
|
stream,
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
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
|
+
}
|
|
8891
9046
|
});
|
|
8892
9047
|
});
|
|
8893
9048
|
} catch (error) {
|