@oh-my-pi/pi-ai 13.17.5 → 13.17.6
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/CHANGELOG.md +7 -1
- package/package.json +2 -2
- package/src/providers/anthropic.ts +46 -30
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.17.6] - 2026-04-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed Anthropic first-event timeouts to exclude stream connection setup from the watchdog, preserve timeout-specific retry classification after local aborts, and reset retry state cleanly between attempts
|
|
10
|
+
|
|
5
11
|
## [13.17.5] - 2026-04-01
|
|
6
12
|
### Changed
|
|
7
13
|
|
|
@@ -1959,4 +1965,4 @@ _Dedicated to Peter's shoulder ([@steipete](https://twitter.com/steipete))_
|
|
|
1959
1965
|
|
|
1960
1966
|
## [0.9.4] - 2025-11-26
|
|
1961
1967
|
|
|
1962
|
-
Initial release with multi-provider LLM support.
|
|
1968
|
+
Initial release with multi-provider LLM support.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "13.17.
|
|
4
|
+
"version": "13.17.6",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@aws-sdk/client-bedrock-runtime": "^3",
|
|
42
42
|
"@bufbuild/protobuf": "^2.11",
|
|
43
43
|
"@google/genai": "^1.43",
|
|
44
|
-
"@oh-my-pi/pi-utils": "13.17.
|
|
44
|
+
"@oh-my-pi/pi-utils": "13.17.6",
|
|
45
45
|
"@sinclair/typebox": "^0.34",
|
|
46
46
|
"@smithy/node-http-handler": "^4.4",
|
|
47
47
|
"ajv": "^8.18",
|
|
@@ -29,11 +29,13 @@ import type {
|
|
|
29
29
|
Tool,
|
|
30
30
|
ToolCall,
|
|
31
31
|
ToolResultMessage,
|
|
32
|
+
Usage,
|
|
32
33
|
} from "../types";
|
|
33
34
|
import { isAnthropicOAuthToken, normalizeToolCallId, resolveCacheRetention } from "../utils";
|
|
35
|
+
import { createAbortSourceTracker } from "../utils/abort";
|
|
34
36
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
35
37
|
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
36
|
-
import { getStreamFirstEventTimeoutMs,
|
|
38
|
+
import { createFirstEventWatchdog, getStreamFirstEventTimeoutMs, markFirstStreamEvent } from "../utils/idle-iterator";
|
|
37
39
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
38
40
|
import {
|
|
39
41
|
buildCopilotDynamicHeaders,
|
|
@@ -581,6 +583,18 @@ export function isProviderRetryableError(error: unknown): boolean {
|
|
|
581
583
|
);
|
|
582
584
|
}
|
|
583
585
|
|
|
586
|
+
function createEmptyUsage(premiumRequests?: number): Usage {
|
|
587
|
+
return {
|
|
588
|
+
input: 0,
|
|
589
|
+
output: 0,
|
|
590
|
+
cacheRead: 0,
|
|
591
|
+
cacheWrite: 0,
|
|
592
|
+
totalTokens: 0,
|
|
593
|
+
...(premiumRequests === undefined ? {} : { premiumRequests }),
|
|
594
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
584
598
|
export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
585
599
|
model: Model<"anthropic-messages">,
|
|
586
600
|
context: Context,
|
|
@@ -608,18 +622,12 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
608
622
|
api: model.api as Api,
|
|
609
623
|
provider: model.provider,
|
|
610
624
|
model: model.id,
|
|
611
|
-
usage:
|
|
612
|
-
input: 0,
|
|
613
|
-
output: 0,
|
|
614
|
-
cacheRead: 0,
|
|
615
|
-
cacheWrite: 0,
|
|
616
|
-
totalTokens: 0,
|
|
617
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
618
|
-
},
|
|
625
|
+
usage: createEmptyUsage(copilotDynamicHeaders?.premiumRequests),
|
|
619
626
|
stopReason: "stop",
|
|
620
627
|
timestamp: Date.now(),
|
|
621
628
|
};
|
|
622
629
|
let rawRequestDump: RawHttpRequestDump | undefined;
|
|
630
|
+
let activeAbortTracker = createAbortSourceTracker(options?.signal);
|
|
623
631
|
|
|
624
632
|
try {
|
|
625
633
|
let client: Anthropic;
|
|
@@ -675,22 +683,20 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
675
683
|
let providerRetryAttempt = 0;
|
|
676
684
|
let started = false;
|
|
677
685
|
do {
|
|
678
|
-
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
686
|
+
activeAbortTracker = createAbortSourceTracker(options?.signal);
|
|
687
|
+
const firstEventTimeoutAbortError = new Error(
|
|
688
|
+
"Anthropic stream timed out while waiting for the first event",
|
|
689
|
+
);
|
|
690
|
+
const { requestSignal } = activeAbortTracker;
|
|
682
691
|
const anthropicStream = client.messages.stream({ ...params, stream: true }, { signal: requestSignal });
|
|
683
|
-
if (copilotDynamicHeaders && output.usage.premiumRequests === undefined) {
|
|
684
|
-
output.usage.premiumRequests = copilotDynamicHeaders.premiumRequests;
|
|
685
|
-
}
|
|
686
692
|
|
|
687
693
|
try {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
+
await anthropicStream.withResponse();
|
|
695
|
+
const firstEventWatchdog = createFirstEventWatchdog(getStreamFirstEventTimeoutMs(), () =>
|
|
696
|
+
activeAbortTracker.abortLocally(firstEventTimeoutAbortError),
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
for await (const event of markFirstStreamEvent(anthropicStream, firstEventWatchdog)) {
|
|
694
700
|
started = true;
|
|
695
701
|
if (event.type === "message_start") {
|
|
696
702
|
output.responseId = event.message.id;
|
|
@@ -857,7 +863,11 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
857
863
|
}
|
|
858
864
|
}
|
|
859
865
|
|
|
860
|
-
|
|
866
|
+
const firstEventTimeoutError = activeAbortTracker.getLocalAbortReason();
|
|
867
|
+
if (firstEventTimeoutError) {
|
|
868
|
+
throw firstEventTimeoutError;
|
|
869
|
+
}
|
|
870
|
+
if (activeAbortTracker.wasCallerAbort()) {
|
|
861
871
|
throw new Error("Request was aborted");
|
|
862
872
|
}
|
|
863
873
|
|
|
@@ -866,23 +876,28 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
866
876
|
}
|
|
867
877
|
break; // Stream completed successfully
|
|
868
878
|
} catch (streamError) {
|
|
879
|
+
const streamFailure = activeAbortTracker.getLocalAbortReason() ?? streamError;
|
|
869
880
|
// Transient stream parse errors (truncated JSON) are retryable even after content
|
|
870
881
|
// has started streaming, since the partial response is unusable anyway.
|
|
871
882
|
// Rate-limit/overload errors are only retried before content starts.
|
|
872
|
-
const isTransient = isTransientStreamParseError(
|
|
883
|
+
const isTransient = isTransientStreamParseError(streamFailure);
|
|
873
884
|
if (
|
|
874
|
-
|
|
885
|
+
activeAbortTracker.wasCallerAbort() ||
|
|
875
886
|
providerRetryAttempt >= PROVIDER_MAX_RETRIES ||
|
|
876
887
|
(!isTransient && firstTokenTime !== undefined) ||
|
|
877
|
-
(!isTransient && !isProviderRetryableError(
|
|
888
|
+
(!isTransient && !isProviderRetryableError(streamFailure))
|
|
878
889
|
) {
|
|
879
|
-
throw
|
|
890
|
+
throw streamFailure;
|
|
880
891
|
}
|
|
881
892
|
providerRetryAttempt++;
|
|
882
893
|
const delayMs = PROVIDER_BASE_DELAY_MS * 2 ** (providerRetryAttempt - 1);
|
|
883
894
|
await abortableSleep(delayMs, options?.signal);
|
|
884
895
|
// Reset output state for clean retry
|
|
885
896
|
output.content.length = 0;
|
|
897
|
+
output.responseId = undefined;
|
|
898
|
+
output.errorMessage = undefined;
|
|
899
|
+
output.providerPayload = undefined;
|
|
900
|
+
output.usage = createEmptyUsage(copilotDynamicHeaders?.premiumRequests);
|
|
886
901
|
output.stopReason = "stop";
|
|
887
902
|
firstTokenTime = undefined;
|
|
888
903
|
started = false;
|
|
@@ -894,9 +909,10 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
894
909
|
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
895
910
|
stream.end();
|
|
896
911
|
} catch (error) {
|
|
897
|
-
for (const block of output.content) delete (block as
|
|
898
|
-
|
|
899
|
-
output.
|
|
912
|
+
for (const block of output.content) delete (block as { index?: number }).index;
|
|
913
|
+
const firstEventTimeoutError = activeAbortTracker.getLocalAbortReason();
|
|
914
|
+
output.stopReason = activeAbortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
915
|
+
output.errorMessage = firstEventTimeoutError?.message ?? (await finalizeErrorMessage(error, rawRequestDump));
|
|
900
916
|
output.duration = Date.now() - startTime;
|
|
901
917
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
902
918
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|