@oh-my-pi/pi-ai 14.6.0 → 14.6.1
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/package.json +3 -3
- package/src/models.json +66 -8
- package/src/provider-models/openai-compat.ts +11 -5
- package/src/providers/openai-completions.ts +43 -8
- package/src/providers/openai-responses-shared.ts +14 -1
- package/src/providers/openai-responses.ts +1 -0
- package/src/providers/register-builtins.ts +1 -3
- package/src/utils/idle-iterator.ts +73 -34
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "14.6.
|
|
4
|
+
"version": "14.6.1",
|
|
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",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"@aws-sdk/credential-provider-node": "^3.972.36",
|
|
47
47
|
"@bufbuild/protobuf": "^2.12.0",
|
|
48
48
|
"@google/genai": "^1.50.1",
|
|
49
|
-
"@oh-my-pi/pi-natives": "14.6.
|
|
50
|
-
"@oh-my-pi/pi-utils": "14.6.
|
|
49
|
+
"@oh-my-pi/pi-natives": "14.6.1",
|
|
50
|
+
"@oh-my-pi/pi-utils": "14.6.1",
|
|
51
51
|
"@sinclair/typebox": "^0.34.49",
|
|
52
52
|
"@smithy/node-http-handler": "^4.6.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
package/src/models.json
CHANGED
|
@@ -28105,6 +28105,44 @@
|
|
|
28105
28105
|
"contextWindow": 222222,
|
|
28106
28106
|
"maxTokens": 8888
|
|
28107
28107
|
},
|
|
28108
|
+
"poolside/laguna-m.1": {
|
|
28109
|
+
"id": "poolside/laguna-m.1",
|
|
28110
|
+
"name": "poolside/laguna-m.1",
|
|
28111
|
+
"api": "openai-completions",
|
|
28112
|
+
"provider": "nanogpt",
|
|
28113
|
+
"baseUrl": "https://nano-gpt.com/api/v1",
|
|
28114
|
+
"reasoning": false,
|
|
28115
|
+
"input": [
|
|
28116
|
+
"text"
|
|
28117
|
+
],
|
|
28118
|
+
"cost": {
|
|
28119
|
+
"input": 0,
|
|
28120
|
+
"output": 0,
|
|
28121
|
+
"cacheRead": 0,
|
|
28122
|
+
"cacheWrite": 0
|
|
28123
|
+
},
|
|
28124
|
+
"contextWindow": 222222,
|
|
28125
|
+
"maxTokens": 8888
|
|
28126
|
+
},
|
|
28127
|
+
"poolside/laguna-xs.2": {
|
|
28128
|
+
"id": "poolside/laguna-xs.2",
|
|
28129
|
+
"name": "poolside/laguna-xs.2",
|
|
28130
|
+
"api": "openai-completions",
|
|
28131
|
+
"provider": "nanogpt",
|
|
28132
|
+
"baseUrl": "https://nano-gpt.com/api/v1",
|
|
28133
|
+
"reasoning": false,
|
|
28134
|
+
"input": [
|
|
28135
|
+
"text"
|
|
28136
|
+
],
|
|
28137
|
+
"cost": {
|
|
28138
|
+
"input": 0,
|
|
28139
|
+
"output": 0,
|
|
28140
|
+
"cacheRead": 0,
|
|
28141
|
+
"cacheWrite": 0
|
|
28142
|
+
},
|
|
28143
|
+
"contextWindow": 222222,
|
|
28144
|
+
"maxTokens": 8888
|
|
28145
|
+
},
|
|
28108
28146
|
"qvq-max": {
|
|
28109
28147
|
"id": "qvq-max",
|
|
28110
28148
|
"name": "qvq-max",
|
|
@@ -35776,9 +35814,9 @@
|
|
|
35776
35814
|
"minimax-m2.7": {
|
|
35777
35815
|
"id": "minimax-m2.7",
|
|
35778
35816
|
"name": "MiniMax M2.7",
|
|
35779
|
-
"api": "
|
|
35817
|
+
"api": "openai-completions",
|
|
35780
35818
|
"provider": "opencode-go",
|
|
35781
|
-
"baseUrl": "https://opencode.ai/zen/go",
|
|
35819
|
+
"baseUrl": "https://opencode.ai/zen/go/v1",
|
|
35782
35820
|
"reasoning": true,
|
|
35783
35821
|
"input": [
|
|
35784
35822
|
"text"
|
|
@@ -35792,7 +35830,7 @@
|
|
|
35792
35830
|
"contextWindow": 204800,
|
|
35793
35831
|
"maxTokens": 131072,
|
|
35794
35832
|
"thinking": {
|
|
35795
|
-
"mode": "
|
|
35833
|
+
"mode": "effort",
|
|
35796
35834
|
"minLevel": "minimal",
|
|
35797
35835
|
"maxLevel": "xhigh"
|
|
35798
35836
|
}
|
|
@@ -38485,7 +38523,7 @@
|
|
|
38485
38523
|
"cacheRead": 0.024999999999999998,
|
|
38486
38524
|
"cacheWrite": 0.08333333333333334
|
|
38487
38525
|
},
|
|
38488
|
-
"contextWindow":
|
|
38526
|
+
"contextWindow": 1048576,
|
|
38489
38527
|
"maxTokens": 8192
|
|
38490
38528
|
},
|
|
38491
38529
|
"google/gemini-2.0-flash-lite-001": {
|
|
@@ -42996,13 +43034,13 @@
|
|
|
42996
43034
|
"image"
|
|
42997
43035
|
],
|
|
42998
43036
|
"cost": {
|
|
42999
|
-
"input": 0.
|
|
43000
|
-
"output": 3.
|
|
43037
|
+
"input": 0.32,
|
|
43038
|
+
"output": 3.1999999999999997,
|
|
43001
43039
|
"cacheRead": 0,
|
|
43002
43040
|
"cacheWrite": 0
|
|
43003
43041
|
},
|
|
43004
|
-
"contextWindow":
|
|
43005
|
-
"maxTokens":
|
|
43042
|
+
"contextWindow": 262144,
|
|
43043
|
+
"maxTokens": 81920,
|
|
43006
43044
|
"thinking": {
|
|
43007
43045
|
"mode": "effort",
|
|
43008
43046
|
"minLevel": "minimal",
|
|
@@ -55034,6 +55072,26 @@
|
|
|
55034
55072
|
"contextWindow": 2000000,
|
|
55035
55073
|
"maxTokens": 30000
|
|
55036
55074
|
},
|
|
55075
|
+
"x-ai/grok-4.3": {
|
|
55076
|
+
"id": "x-ai/grok-4.3",
|
|
55077
|
+
"name": "xAI: Grok 4.3",
|
|
55078
|
+
"api": "openai-completions",
|
|
55079
|
+
"provider": "zenmux",
|
|
55080
|
+
"baseUrl": "https://zenmux.ai/api/v1",
|
|
55081
|
+
"reasoning": false,
|
|
55082
|
+
"input": [
|
|
55083
|
+
"text",
|
|
55084
|
+
"image"
|
|
55085
|
+
],
|
|
55086
|
+
"cost": {
|
|
55087
|
+
"input": 1.25,
|
|
55088
|
+
"output": 2.5,
|
|
55089
|
+
"cacheRead": 0.2,
|
|
55090
|
+
"cacheWrite": 0
|
|
55091
|
+
},
|
|
55092
|
+
"contextWindow": 1000000,
|
|
55093
|
+
"maxTokens": 8888
|
|
55094
|
+
},
|
|
55037
55095
|
"x-ai/grok-code-fast-1": {
|
|
55038
55096
|
"id": "x-ai/grok-code-fast-1",
|
|
55039
55097
|
"name": "Grok Code Fast 1",
|
|
@@ -1868,12 +1868,18 @@ function createOpenCodeApiResolution(
|
|
|
1868
1868
|
}
|
|
1869
1869
|
|
|
1870
1870
|
const OPENCODE_ZEN_API_RESOLUTION = createOpenCodeApiResolution("https://opencode.ai/zen");
|
|
1871
|
-
// OpenCode Go: models.dev declares qwen3.5-plus / qwen3.6-plus
|
|
1872
|
-
// `provider.npm = "@ai-sdk/anthropic"`, but
|
|
1873
|
-
//
|
|
1874
|
-
//
|
|
1875
|
-
//
|
|
1871
|
+
// OpenCode Go: models.dev declares minimax-m2.7 / qwen3.5-plus / qwen3.6-plus
|
|
1872
|
+
// with `provider.npm = "@ai-sdk/anthropic"`, but the OpenCode Go gateway only
|
|
1873
|
+
// serves them at `https://opencode.ai/zen/go/v1/chat/completions` (verified
|
|
1874
|
+
// against https://opencode.ai/zen/go/v1/models and the upstream endpoint
|
|
1875
|
+
// table at https://opencode.ai/docs/go/#endpoints — minimax-m2.5 works the
|
|
1876
|
+
// same way and lacks an `npm` field on models.dev so it already falls through
|
|
1877
|
+
// to the openai-completions default). Without this override the resolver
|
|
1878
|
+
// would POST anthropic-style requests to /v1/messages and the gateway would
|
|
1879
|
+
// return its `Page Not Found` HTML (issue #887). Override the resolver so
|
|
1880
|
+
// regenerating models.json keeps the correct routing.
|
|
1876
1881
|
const OPENCODE_GO_API_RESOLUTION = createOpenCodeApiResolution("https://opencode.ai/zen/go", {
|
|
1882
|
+
"minimax-m2.7": "openai-completions",
|
|
1877
1883
|
"qwen3.5-plus": "openai-completions",
|
|
1878
1884
|
"qwen3.6-plus": "openai-completions",
|
|
1879
1885
|
});
|
|
@@ -81,6 +81,43 @@ function normalizeMistralToolId(id: string, isMistral: boolean): string {
|
|
|
81
81
|
return normalized;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Normalize OpenAI-compatible streaming `delta.content` into plain text.
|
|
86
|
+
*
|
|
87
|
+
* Most providers stream `delta.content` as a string, but some (notably Mistral
|
|
88
|
+
* Medium 3.5 / `mistral-medium-2604`) return an array of typed content parts
|
|
89
|
+
* — e.g. `[{ type: "text", text: "Hello" }]`. Without normalization those
|
|
90
|
+
* parts get string-coerced via `text += array`, producing the literal
|
|
91
|
+
* `[object Object]` sequences observed in issue #911.
|
|
92
|
+
*
|
|
93
|
+
* Returns the joined text. Non-text parts and unknown shapes are skipped so
|
|
94
|
+
* we never emit JS object sigils as visible output.
|
|
95
|
+
*/
|
|
96
|
+
function normalizeStreamingContentText(content: unknown): string {
|
|
97
|
+
if (typeof content === "string") return content;
|
|
98
|
+
if (Array.isArray(content)) {
|
|
99
|
+
let out = "";
|
|
100
|
+
for (const part of content) {
|
|
101
|
+
if (typeof part === "string") {
|
|
102
|
+
out += part;
|
|
103
|
+
} else if (part && typeof part === "object") {
|
|
104
|
+
const obj = part as { type?: unknown; text?: unknown };
|
|
105
|
+
if ((obj.type === undefined || obj.type === "text") && typeof obj.text === "string") {
|
|
106
|
+
out += obj.text;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
if (content && typeof content === "object") {
|
|
113
|
+
const obj = content as { type?: unknown; text?: unknown };
|
|
114
|
+
if ((obj.type === undefined || obj.type === "text") && typeof obj.text === "string") {
|
|
115
|
+
return obj.text;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
|
|
84
121
|
function serializeToolArguments(value: unknown): string {
|
|
85
122
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
86
123
|
try {
|
|
@@ -537,6 +574,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
537
574
|
idleTimeoutMs,
|
|
538
575
|
errorMessage: "OpenAI completions stream stalled while waiting for the next event",
|
|
539
576
|
onIdle: () => requestAbortController.abort(),
|
|
577
|
+
abortSignal: options?.signal,
|
|
540
578
|
})) {
|
|
541
579
|
if (!chunk || typeof chunk !== "object") continue;
|
|
542
580
|
|
|
@@ -567,20 +605,17 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
567
605
|
}
|
|
568
606
|
|
|
569
607
|
if (choice.delta) {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
choice.delta.content !== undefined &&
|
|
573
|
-
choice.delta.content.length > 0
|
|
574
|
-
) {
|
|
608
|
+
const normalizedDeltaText = normalizeStreamingContentText(choice.delta.content);
|
|
609
|
+
if (normalizedDeltaText.length > 0) {
|
|
575
610
|
if (!firstTokenTime) firstTokenTime = Date.now();
|
|
576
611
|
if (parseMiniMaxThinkTags) {
|
|
577
|
-
taggedTextBuffer +=
|
|
612
|
+
taggedTextBuffer += normalizedDeltaText;
|
|
578
613
|
flushTaggedTextBuffer();
|
|
579
614
|
} else if (stripDeepseekChatTemplateTokens) {
|
|
580
|
-
deepseekStripBuffer +=
|
|
615
|
+
deepseekStripBuffer += normalizedDeltaText;
|
|
581
616
|
flushDeepseekStripBuffer(false);
|
|
582
617
|
} else {
|
|
583
|
-
appendTextDelta(
|
|
618
|
+
appendTextDelta(normalizedDeltaText);
|
|
584
619
|
}
|
|
585
620
|
}
|
|
586
621
|
|
|
@@ -540,13 +540,26 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
540
540
|
}
|
|
541
541
|
calculateCost(model, output.usage);
|
|
542
542
|
output.stopReason = mapOpenAIResponsesStopReason(response?.status);
|
|
543
|
+
if (response?.status === "failed" || response?.status === "cancelled") {
|
|
544
|
+
const error = response?.error ?? (response as any)?.status_details?.error;
|
|
545
|
+
const details = response?.incomplete_details;
|
|
546
|
+
const statusDetailsReason = (response as any)?.status_details?.reason;
|
|
547
|
+
const message = error
|
|
548
|
+
? `${error.code || "unknown"}: ${error.message || "no message"}`
|
|
549
|
+
: details?.reason
|
|
550
|
+
? `incomplete: ${details.reason}`
|
|
551
|
+
: typeof statusDetailsReason === "string" && statusDetailsReason.length > 0
|
|
552
|
+
? `status_details: ${statusDetailsReason}`
|
|
553
|
+
: "Unknown error (no error details in response)";
|
|
554
|
+
throw new Error(message);
|
|
555
|
+
}
|
|
543
556
|
if (output.content.some(block => block.type === "toolCall") && output.stopReason === "stop") {
|
|
544
557
|
output.stopReason = "toolUse";
|
|
545
558
|
}
|
|
546
559
|
} else if (event.type === "error") {
|
|
547
560
|
throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
|
|
548
561
|
} else if (event.type === "response.failed") {
|
|
549
|
-
const error = event.response?.error;
|
|
562
|
+
const error = event.response?.error ?? (event.response as any)?.status_details?.error;
|
|
550
563
|
const details = event.response?.incomplete_details;
|
|
551
564
|
const message = error
|
|
552
565
|
? `${error.code || "unknown"}: ${error.message || "no message"}`
|
|
@@ -220,6 +220,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
220
220
|
watchdog: firstEventWatchdog,
|
|
221
221
|
errorMessage: "OpenAI responses stream stalled while waiting for the next event",
|
|
222
222
|
onIdle: () => requestAbortController.abort(),
|
|
223
|
+
abortSignal: options?.signal,
|
|
223
224
|
}),
|
|
224
225
|
output,
|
|
225
226
|
stream,
|
|
@@ -132,8 +132,6 @@ interface BedrockProviderModule {
|
|
|
132
132
|
// Module-level lazy promise caches
|
|
133
133
|
// ---------------------------------------------------------------------------
|
|
134
134
|
|
|
135
|
-
const importNodeOnlyProvider = (specifier: string): Promise<unknown> => import(specifier);
|
|
136
|
-
|
|
137
135
|
let anthropicProviderModulePromise: Promise<LazyProviderModule<"anthropic-messages">> | undefined;
|
|
138
136
|
let azureOpenAIResponsesProviderModulePromise: Promise<LazyProviderModule<"azure-openai-responses">> | undefined;
|
|
139
137
|
let googleProviderModulePromise: Promise<LazyProviderModule<"google-generative-ai">> | undefined;
|
|
@@ -320,7 +318,7 @@ function loadBedrockProviderModule(): Promise<LazyProviderModule<"bedrock-conver
|
|
|
320
318
|
if (bedrockProviderModuleOverride) {
|
|
321
319
|
return Promise.resolve(bedrockProviderModuleOverride);
|
|
322
320
|
}
|
|
323
|
-
bedrockProviderModulePromise ||=
|
|
321
|
+
bedrockProviderModulePromise ||= import("./amazon-bedrock").then(module => {
|
|
324
322
|
const provider = module as BedrockProviderModule;
|
|
325
323
|
return { stream: provider.streamBedrock };
|
|
326
324
|
});
|
|
@@ -59,6 +59,15 @@ export interface IdleTimeoutIteratorOptions {
|
|
|
59
59
|
firstItemErrorMessage?: string;
|
|
60
60
|
onIdle?: () => void;
|
|
61
61
|
onFirstItemTimeout?: () => void;
|
|
62
|
+
/**
|
|
63
|
+
* Cancel iteration as soon as this signal aborts. Required for caller-driven
|
|
64
|
+
* cancellation (ESC) when the underlying transport does not surface signal
|
|
65
|
+
* aborts to the iterator (HTTP/2 proxies, native sockets, mocked fetch).
|
|
66
|
+
* Without this, the consumer sleeps on iterator.next() until the idle/first
|
|
67
|
+
* -event watchdog fires — observable as the issue #912 "Working… forever"
|
|
68
|
+
* symptom on the github-copilot provider.
|
|
69
|
+
*/
|
|
70
|
+
abortSignal?: AbortSignal;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
/**
|
|
@@ -73,19 +82,20 @@ export async function* iterateWithIdleTimeout<T>(
|
|
|
73
82
|
): AsyncGenerator<T> {
|
|
74
83
|
let watchdog = options.watchdog;
|
|
75
84
|
const firstItemTimeoutMs = options.firstItemTimeoutMs ?? options.idleTimeoutMs;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
yield item;
|
|
85
|
+
const abortSignal = options.abortSignal;
|
|
86
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
87
|
+
|
|
88
|
+
const closeIterator = (): void => {
|
|
89
|
+
const returnPromise = iterator.return?.();
|
|
90
|
+
if (returnPromise) {
|
|
91
|
+
void returnPromise.catch(() => {});
|
|
84
92
|
}
|
|
85
|
-
|
|
86
|
-
}
|
|
93
|
+
};
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
if (abortSignal?.aborted) {
|
|
96
|
+
closeIterator();
|
|
97
|
+
throw abortReason(abortSignal);
|
|
98
|
+
}
|
|
89
99
|
|
|
90
100
|
const withRacy = <T>(promise: Promise<T>) =>
|
|
91
101
|
promise.then(
|
|
@@ -98,54 +108,83 @@ export async function* iterateWithIdleTimeout<T>(
|
|
|
98
108
|
onFirst = null;
|
|
99
109
|
};
|
|
100
110
|
|
|
111
|
+
const noTimeoutEnforced =
|
|
112
|
+
(firstItemTimeoutMs === undefined || firstItemTimeoutMs <= 0) &&
|
|
113
|
+
(options.idleTimeoutMs === undefined || options.idleTimeoutMs <= 0);
|
|
114
|
+
|
|
101
115
|
while (true) {
|
|
102
116
|
const nextResultPromise = withRacy(iterator.next());
|
|
103
117
|
const activeTimeoutMs = !onFirst ? options.idleTimeoutMs : firstItemTimeoutMs;
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
const racers: Array<
|
|
120
|
+
Promise<
|
|
121
|
+
| { kind: "next"; result: IteratorResult<T> }
|
|
122
|
+
| { kind: "error"; error: unknown }
|
|
123
|
+
| { kind: "timeout" }
|
|
124
|
+
| { kind: "abort" }
|
|
125
|
+
>
|
|
126
|
+
> = [nextResultPromise];
|
|
127
|
+
|
|
128
|
+
let timer: NodeJS.Timeout | undefined;
|
|
129
|
+
let resolveTimeout: ((value: { kind: "timeout" }) => void) | undefined;
|
|
130
|
+
const enforceTimeout = !noTimeoutEnforced && activeTimeoutMs !== undefined && activeTimeoutMs > 0;
|
|
131
|
+
if (enforceTimeout) {
|
|
132
|
+
const { promise, resolve } = Promise.withResolvers<{ kind: "timeout" }>();
|
|
133
|
+
resolveTimeout = resolve;
|
|
134
|
+
timer = setTimeout(() => resolve({ kind: "timeout" }), activeTimeoutMs);
|
|
135
|
+
racers.push(promise);
|
|
116
136
|
}
|
|
117
137
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
let abortListener: (() => void) | undefined;
|
|
139
|
+
let resolveAbort: ((value: { kind: "abort" }) => void) | undefined;
|
|
140
|
+
if (abortSignal) {
|
|
141
|
+
const { promise, resolve } = Promise.withResolvers<{ kind: "abort" }>();
|
|
142
|
+
resolveAbort = resolve;
|
|
143
|
+
abortListener = () => resolve({ kind: "abort" });
|
|
144
|
+
abortSignal.addEventListener("abort", abortListener, { once: true });
|
|
145
|
+
racers.push(promise);
|
|
146
|
+
}
|
|
122
147
|
|
|
123
148
|
try {
|
|
124
|
-
const outcome = await Promise.race(
|
|
149
|
+
const outcome = await Promise.race(racers);
|
|
150
|
+
if (outcome.kind === "abort") {
|
|
151
|
+
closeIterator();
|
|
152
|
+
throw abortReason(abortSignal!);
|
|
153
|
+
}
|
|
125
154
|
if (outcome.kind === "timeout") {
|
|
126
155
|
if (!onFirst) {
|
|
127
156
|
options.onIdle?.();
|
|
128
157
|
} else {
|
|
129
158
|
options.onFirstItemTimeout?.();
|
|
130
159
|
}
|
|
131
|
-
|
|
132
|
-
if (returnPromise) {
|
|
133
|
-
void returnPromise.catch(() => {});
|
|
134
|
-
}
|
|
160
|
+
closeIterator();
|
|
135
161
|
throw new Error(!onFirst ? options.errorMessage : (options.firstItemErrorMessage ?? options.errorMessage));
|
|
136
162
|
}
|
|
137
|
-
watchdog && clearTimeout(watchdog);
|
|
138
|
-
watchdog = undefined;
|
|
139
163
|
if (outcome.kind === "error") {
|
|
140
164
|
throw outcome.error;
|
|
141
165
|
}
|
|
166
|
+
watchdog && clearTimeout(watchdog);
|
|
167
|
+
watchdog = undefined;
|
|
142
168
|
if (outcome.result.done) {
|
|
143
169
|
return;
|
|
144
170
|
}
|
|
145
171
|
onFirst?.();
|
|
146
172
|
yield outcome.result.value;
|
|
147
173
|
} finally {
|
|
148
|
-
clearTimeout(timer);
|
|
174
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
175
|
+
// Resolve dangling promises so the racers don't leak (Promise.race is one-shot).
|
|
176
|
+
resolveTimeout?.({ kind: "timeout" });
|
|
177
|
+
if (abortListener && abortSignal) {
|
|
178
|
+
abortSignal.removeEventListener("abort", abortListener);
|
|
179
|
+
}
|
|
180
|
+
resolveAbort?.({ kind: "abort" });
|
|
149
181
|
}
|
|
150
182
|
}
|
|
151
183
|
}
|
|
184
|
+
|
|
185
|
+
function abortReason(signal: AbortSignal): Error {
|
|
186
|
+
const reason = signal.reason;
|
|
187
|
+
if (reason instanceof Error) return reason;
|
|
188
|
+
if (typeof reason === "string") return new Error(reason);
|
|
189
|
+
return new Error("Request was aborted");
|
|
190
|
+
}
|