@psnext/slingcli 2.4.20260601-1 → 2.5.20260602-2
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/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler/package.json +4 -4
- package/node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js +12 -1
- package/node_modules/@aws-sdk/credential-provider-node/dist-es/runtime/memoize-chain.js +12 -1
- package/node_modules/@aws-sdk/credential-provider-node/package.json +1 -1
- package/node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +81 -18
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +14 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +197 -190
- package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +84 -21
- package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +24 -14
- package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google.js +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/openrouter.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/mistral.js +2 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex-responses.js +130 -62
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +18 -12
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses-shared.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +5 -9
- package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/stream.js +14 -2
- package/node_modules/@earendil-works/pi-ai/dist/utils/abort-signals.js +34 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/device-code.js +8 -7
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/github-copilot.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/index.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +179 -79
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +27 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +9 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session-services.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +29 -5
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +21 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/footer-data-provider.js +29 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +65 -13
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/resolve-config-value.js +134 -11
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +12 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +167 -96
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +14 -9
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +0 -3
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/edit.js +7 -10
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/ls.js +5 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +6 -7
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/render-utils.js +17 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/write.js +5 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +69 -16
- package/node_modules/@earendil-works/pi-coding-agent/dist/migrations.js +118 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +1 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +2 -4
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +61 -6
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/print-mode.js +1 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +4 -1
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/deprecation.js +13 -0
- package/node_modules/@earendil-works/pi-coding-agent/dist/utils/json.js +7 -0
- package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +71 -56
- package/node_modules/@earendil-works/pi-coding-agent/package.json +7 -7
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +9 -53
- package/node_modules/@earendil-works/pi-tui/dist/components/input.js +6 -54
- package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +34 -7
- package/node_modules/@earendil-works/pi-tui/dist/terminal.js +172 -37
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +166 -22
- package/node_modules/@earendil-works/pi-tui/dist/utils.js +8 -3
- package/node_modules/@earendil-works/pi-tui/dist/word-navigation.js +96 -0
- package/node_modules/@earendil-works/pi-tui/package.json +2 -2
- package/node_modules/@mariozechner/clipboard/Cargo.toml +3 -3
- package/node_modules/@mariozechner/clipboard/index.d.ts +34 -20
- package/node_modules/@mariozechner/clipboard/index.js +546 -257
- package/node_modules/@mariozechner/clipboard/package.json +14 -14
- package/node_modules/@mariozechner/clipboard/src/lib.rs +4 -9
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/clipboard.linux-x64-gnu.node +0 -0
- package/node_modules/@mariozechner/clipboard-linux-x64-gnu/package.json +2 -2
- package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.browser.js +39 -22
- package/node_modules/@smithy/core/dist-cjs/submodules/retry/index.js +39 -22
- package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/configurations.js +19 -6
- package/node_modules/@smithy/core/dist-es/submodules/retry/middleware-retry/retryMiddleware.js +4 -5
- package/node_modules/@smithy/core/dist-es/submodules/retry/service-error-classification/constants.js +1 -1
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/ConfiguredRetryStrategy.js +4 -5
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/DefaultRetryToken.js +3 -0
- package/node_modules/@smithy/core/dist-es/submodules/retry/util-retry/StandardRetryStrategy.js +9 -5
- package/node_modules/@smithy/core/dist-types/submodules/retry/middleware-retry/configurations.d.ts +4 -1
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/ConfiguredRetryStrategy.d.ts +1 -2
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/DefaultRetryToken.d.ts +3 -0
- package/node_modules/@smithy/core/dist-types/submodules/retry/util-retry/StandardRetryStrategy.d.ts +1 -1
- package/node_modules/@smithy/core/package.json +2 -2
- package/node_modules/@smithy/credential-provider-imds/package.json +3 -3
- package/node_modules/@smithy/fetch-http-handler/package.json +4 -4
- package/node_modules/@smithy/signature-v4/package.json +3 -3
- package/node_modules/@smithy/types/dist-types/retry.d.ts +25 -0
- package/node_modules/@smithy/types/package.json +1 -1
- package/package.json +6 -6
- package/slingshot/index.js +231 -230
|
@@ -15,9 +15,9 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
|
|
|
15
15
|
_os = m;
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
import { getEnvApiKey } from "../env-api-keys.js";
|
|
19
18
|
import { clampThinkingLevel } from "../models.js";
|
|
20
19
|
import { registerSessionResourceCleanup } from "../session-resources.js";
|
|
20
|
+
import { combineAbortSignals } from "../utils/abort-signals.js";
|
|
21
21
|
import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
|
|
22
22
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
23
23
|
import { headersToRecord } from "../utils/headers.js";
|
|
@@ -32,6 +32,8 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
|
32
32
|
const DEFAULT_MAX_RETRIES = 0;
|
|
33
33
|
const BASE_DELAY_MS = 1000;
|
|
34
34
|
const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
|
|
35
|
+
const DEFAULT_SSE_HEADER_TIMEOUT_MS = 10_000;
|
|
36
|
+
const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;
|
|
35
37
|
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
|
36
38
|
const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
|
|
37
39
|
const CODEX_RESPONSE_STATUSES = new Set([
|
|
@@ -96,6 +98,27 @@ function sleep(ms, signal) {
|
|
|
96
98
|
});
|
|
97
99
|
});
|
|
98
100
|
}
|
|
101
|
+
function normalizeTimeoutMs(value) {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
105
|
+
throw new Error(`Invalid timeoutMs: ${String(value)}`);
|
|
106
|
+
}
|
|
107
|
+
return Math.floor(value);
|
|
108
|
+
}
|
|
109
|
+
function createSSEHeaderTimeout() {
|
|
110
|
+
const controller = new AbortController();
|
|
111
|
+
let error;
|
|
112
|
+
const timeout = setTimeout(() => {
|
|
113
|
+
error = new Error(`Codex SSE response headers timed out after ${DEFAULT_SSE_HEADER_TIMEOUT_MS}ms`);
|
|
114
|
+
controller.abort(error);
|
|
115
|
+
}, DEFAULT_SSE_HEADER_TIMEOUT_MS);
|
|
116
|
+
return {
|
|
117
|
+
signal: controller.signal,
|
|
118
|
+
clear: () => clearTimeout(timeout),
|
|
119
|
+
error: () => error,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
99
122
|
// ============================================================================
|
|
100
123
|
// Main Stream Function
|
|
101
124
|
// ============================================================================
|
|
@@ -120,11 +143,11 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
120
143
|
timestamp: Date.now(),
|
|
121
144
|
};
|
|
122
145
|
try {
|
|
123
|
-
const apiKey = options?.apiKey
|
|
146
|
+
const apiKey = options?.apiKey;
|
|
124
147
|
if (!apiKey) {
|
|
125
148
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
126
149
|
}
|
|
127
|
-
const accountId = extractAccountId(apiKey)
|
|
150
|
+
const accountId = extractAccountId(apiKey);
|
|
128
151
|
let body = buildRequestBody(model, context, options);
|
|
129
152
|
const nextBody = await options?.onPayload?.(body, model);
|
|
130
153
|
if (nextBody !== undefined) {
|
|
@@ -134,6 +157,8 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
134
157
|
const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, options?.sessionId);
|
|
135
158
|
const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
|
|
136
159
|
const bodyJson = JSON.stringify(body);
|
|
160
|
+
const idleTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);
|
|
161
|
+
const websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);
|
|
137
162
|
const transport = options?.transport || "auto";
|
|
138
163
|
const websocketDisabledForSession = transport !== "sse" && isWebSocketSseFallbackActive(options?.sessionId);
|
|
139
164
|
if (websocketDisabledForSession) {
|
|
@@ -144,7 +169,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
144
169
|
try {
|
|
145
170
|
await processWebSocketStream(resolveCodexWebSocketUrl(model.baseUrl), body, websocketHeaders, output, stream, model, () => {
|
|
146
171
|
websocketStarted = true;
|
|
147
|
-
}, options);
|
|
172
|
+
}, idleTimeoutMs, websocketConnectTimeoutMs, options);
|
|
148
173
|
if (options?.signal?.aborted) {
|
|
149
174
|
throw new Error("Request was aborted");
|
|
150
175
|
}
|
|
@@ -184,12 +209,24 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
184
209
|
throw new Error("Request was aborted");
|
|
185
210
|
}
|
|
186
211
|
try {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
const headerTimeout = createSSEHeaderTimeout();
|
|
213
|
+
const combinedSignal = combineAbortSignals([options?.signal, headerTimeout.signal]);
|
|
214
|
+
try {
|
|
215
|
+
response = await fetch(resolveCodexUrl(model.baseUrl), {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: sseHeaders,
|
|
218
|
+
body: bodyJson,
|
|
219
|
+
signal: combinedSignal.signal,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
const timeoutError = headerTimeout.error();
|
|
224
|
+
throw timeoutError && !options?.signal?.aborted ? timeoutError : error;
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
combinedSignal.cleanup();
|
|
228
|
+
headerTimeout.clear();
|
|
229
|
+
}
|
|
193
230
|
await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
|
|
194
231
|
if (response.ok) {
|
|
195
232
|
break;
|
|
@@ -257,7 +294,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
|
|
|
257
294
|
return stream;
|
|
258
295
|
};
|
|
259
296
|
export const streamSimpleOpenAICodexResponses = (model, context, options) => {
|
|
260
|
-
const apiKey = options?.apiKey
|
|
297
|
+
const apiKey = options?.apiKey;
|
|
261
298
|
if (!apiKey) {
|
|
262
299
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
263
300
|
}
|
|
@@ -282,7 +319,7 @@ function buildRequestBody(model, context, options) {
|
|
|
282
319
|
stream: true,
|
|
283
320
|
instructions: context.systemPrompt || "You are a helpful assistant.",
|
|
284
321
|
input: messages,
|
|
285
|
-
text: { verbosity: options?.textVerbosity || "
|
|
322
|
+
text: { verbosity: options?.textVerbosity || "low" },
|
|
286
323
|
include: ["reasoning.encrypted_content"],
|
|
287
324
|
prompt_cache_key: clampOpenAIPromptCacheKey(options?.sessionId),
|
|
288
325
|
tool_choice: "auto",
|
|
@@ -339,7 +376,11 @@ function resolveCodexServiceTier(responseServiceTier, requestServiceTier) {
|
|
|
339
376
|
function resolveCodexUrl(baseUrl) {
|
|
340
377
|
const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;
|
|
341
378
|
const normalized = raw.replace(/\/+$/, "");
|
|
342
|
-
|
|
379
|
+
if (normalized.endsWith("/codex/responses"))
|
|
380
|
+
return normalized;
|
|
381
|
+
if (normalized.endsWith("/codex"))
|
|
382
|
+
return `${normalized}/responses`;
|
|
383
|
+
return `${normalized}/codex/responses`;
|
|
343
384
|
}
|
|
344
385
|
function resolveCodexWebSocketUrl(baseUrl) {
|
|
345
386
|
const url = new URL(resolveCodexUrl(baseUrl));
|
|
@@ -353,7 +394,7 @@ function resolveCodexWebSocketUrl(baseUrl) {
|
|
|
353
394
|
// Response Processing
|
|
354
395
|
// ============================================================================
|
|
355
396
|
async function processStream(response, output, stream, model, options) {
|
|
356
|
-
await processResponsesStream(mapCodexEvents(parseSSE(response)), output, stream, model, {
|
|
397
|
+
await processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal)), output, stream, model, {
|
|
357
398
|
serviceTier: options?.serviceTier,
|
|
358
399
|
resolveServiceTier: resolveCodexServiceTier,
|
|
359
400
|
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
|
|
@@ -420,15 +461,25 @@ function normalizeCodexStatus(status) {
|
|
|
420
461
|
// ============================================================================
|
|
421
462
|
// SSE Parsing
|
|
422
463
|
// ============================================================================
|
|
423
|
-
async function* parseSSE(response) {
|
|
464
|
+
async function* parseSSE(response, signal) {
|
|
424
465
|
if (!response.body)
|
|
425
466
|
return;
|
|
426
467
|
const reader = response.body.getReader();
|
|
427
468
|
const decoder = new TextDecoder();
|
|
428
469
|
let buffer = "";
|
|
470
|
+
const onAbort = () => {
|
|
471
|
+
void reader.cancel().catch(() => { });
|
|
472
|
+
};
|
|
473
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
429
474
|
try {
|
|
430
475
|
while (true) {
|
|
476
|
+
if (signal?.aborted) {
|
|
477
|
+
throw new Error("Request was aborted");
|
|
478
|
+
}
|
|
431
479
|
const { done, value } = await reader.read();
|
|
480
|
+
if (signal?.aborted) {
|
|
481
|
+
throw new Error("Request was aborted");
|
|
482
|
+
}
|
|
432
483
|
if (done)
|
|
433
484
|
break;
|
|
434
485
|
buffer += decoder.decode(value, { stream: true });
|
|
@@ -459,6 +510,7 @@ async function* parseSSE(response) {
|
|
|
459
510
|
}
|
|
460
511
|
}
|
|
461
512
|
finally {
|
|
513
|
+
signal?.removeEventListener("abort", onAbort);
|
|
462
514
|
try {
|
|
463
515
|
await reader.cancel();
|
|
464
516
|
}
|
|
@@ -615,7 +667,7 @@ function scheduleSessionWebSocketExpiry(sessionId, entry) {
|
|
|
615
667
|
websocketSessionCache.delete(sessionId);
|
|
616
668
|
}, SESSION_WEBSOCKET_CACHE_TTL_MS);
|
|
617
669
|
}
|
|
618
|
-
async function connectWebSocket(url, headers, signal) {
|
|
670
|
+
async function connectWebSocket(url, headers, signal, connectTimeoutMs = DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS) {
|
|
619
671
|
const WebSocketCtor = await getWebSocketConstructor();
|
|
620
672
|
if (!WebSocketCtor) {
|
|
621
673
|
throw new Error("WebSocket transport is not available in this runtime");
|
|
@@ -624,6 +676,7 @@ async function connectWebSocket(url, headers, signal) {
|
|
|
624
676
|
delete wsHeaders["OpenAI-Beta"];
|
|
625
677
|
return new Promise((resolve, reject) => {
|
|
626
678
|
let settled = false;
|
|
679
|
+
let timeout;
|
|
627
680
|
let socket;
|
|
628
681
|
try {
|
|
629
682
|
socket = new WebSocketCtor(url, { headers: wsHeaders });
|
|
@@ -632,62 +685,63 @@ async function connectWebSocket(url, headers, signal) {
|
|
|
632
685
|
reject(error instanceof Error ? error : new Error(String(error)));
|
|
633
686
|
return;
|
|
634
687
|
}
|
|
635
|
-
const
|
|
636
|
-
if (
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
688
|
+
const cleanup = () => {
|
|
689
|
+
if (timeout) {
|
|
690
|
+
clearTimeout(timeout);
|
|
691
|
+
timeout = undefined;
|
|
692
|
+
}
|
|
693
|
+
socket.removeEventListener("open", onOpen);
|
|
694
|
+
socket.removeEventListener("error", onError);
|
|
695
|
+
socket.removeEventListener("close", onClose);
|
|
696
|
+
signal?.removeEventListener("abort", onAbort);
|
|
641
697
|
};
|
|
642
|
-
const
|
|
643
|
-
const error = extractWebSocketError(event);
|
|
698
|
+
const fail = (error, closeReason) => {
|
|
644
699
|
if (settled)
|
|
645
700
|
return;
|
|
646
701
|
settled = true;
|
|
647
702
|
cleanup();
|
|
703
|
+
if (closeReason) {
|
|
704
|
+
closeWebSocketSilently(socket, 1000, closeReason);
|
|
705
|
+
}
|
|
648
706
|
reject(error);
|
|
649
707
|
};
|
|
650
|
-
const
|
|
651
|
-
const error = extractWebSocketCloseError(event);
|
|
708
|
+
const onOpen = () => {
|
|
652
709
|
if (settled)
|
|
653
710
|
return;
|
|
654
711
|
settled = true;
|
|
655
712
|
cleanup();
|
|
656
|
-
|
|
713
|
+
resolve(socket);
|
|
657
714
|
};
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
return;
|
|
661
|
-
settled = true;
|
|
662
|
-
cleanup();
|
|
663
|
-
socket.close(1000, "aborted");
|
|
664
|
-
reject(new Error("Request was aborted"));
|
|
715
|
+
const onError = (event) => {
|
|
716
|
+
fail(extractWebSocketError(event));
|
|
665
717
|
};
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
718
|
+
const onClose = (event) => {
|
|
719
|
+
fail(extractWebSocketCloseError(event));
|
|
720
|
+
};
|
|
721
|
+
const onAbort = () => {
|
|
722
|
+
fail(new Error("Request was aborted"), "aborted");
|
|
671
723
|
};
|
|
672
724
|
socket.addEventListener("open", onOpen);
|
|
673
725
|
socket.addEventListener("error", onError);
|
|
674
726
|
socket.addEventListener("close", onClose);
|
|
675
727
|
signal?.addEventListener("abort", onAbort);
|
|
728
|
+
if (connectTimeoutMs > 0) {
|
|
729
|
+
timeout = setTimeout(() => {
|
|
730
|
+
fail(new Error(`WebSocket connect timeout after ${connectTimeoutMs}ms`), "connect_timeout");
|
|
731
|
+
}, connectTimeoutMs);
|
|
732
|
+
}
|
|
733
|
+
if (signal?.aborted) {
|
|
734
|
+
onAbort();
|
|
735
|
+
}
|
|
676
736
|
});
|
|
677
737
|
}
|
|
678
|
-
async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
738
|
+
async function acquireWebSocket(url, headers, sessionId, signal, connectTimeoutMs) {
|
|
679
739
|
if (!sessionId) {
|
|
680
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
740
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
681
741
|
return {
|
|
682
742
|
socket,
|
|
683
743
|
reused: false,
|
|
684
|
-
release: (
|
|
685
|
-
if (keep === false) {
|
|
686
|
-
closeWebSocketSilently(socket);
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
closeWebSocketSilently(socket);
|
|
690
|
-
},
|
|
744
|
+
release: () => closeWebSocketSilently(socket),
|
|
691
745
|
};
|
|
692
746
|
}
|
|
693
747
|
const cached = websocketSessionCache.get(sessionId);
|
|
@@ -714,7 +768,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
714
768
|
};
|
|
715
769
|
}
|
|
716
770
|
if (cached.busy) {
|
|
717
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
771
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
718
772
|
return {
|
|
719
773
|
socket,
|
|
720
774
|
reused: false,
|
|
@@ -728,7 +782,7 @@ async function acquireWebSocket(url, headers, sessionId, signal) {
|
|
|
728
782
|
websocketSessionCache.delete(sessionId);
|
|
729
783
|
}
|
|
730
784
|
}
|
|
731
|
-
const socket = await connectWebSocket(url, headers, signal);
|
|
785
|
+
const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs);
|
|
732
786
|
const entry = { socket, busy: true };
|
|
733
787
|
websocketSessionCache.set(sessionId, entry);
|
|
734
788
|
return {
|
|
@@ -804,7 +858,7 @@ async function decodeWebSocketData(data) {
|
|
|
804
858
|
}
|
|
805
859
|
return null;
|
|
806
860
|
}
|
|
807
|
-
async function* parseWebSocket(socket, signal) {
|
|
861
|
+
async function* parseWebSocket(socket, signal, idleTimeoutMs) {
|
|
808
862
|
const queue = [];
|
|
809
863
|
let pending = null;
|
|
810
864
|
let done = false;
|
|
@@ -882,8 +936,23 @@ async function* parseWebSocket(socket, signal) {
|
|
|
882
936
|
}
|
|
883
937
|
if (done)
|
|
884
938
|
break;
|
|
885
|
-
|
|
939
|
+
let timeout;
|
|
940
|
+
await new Promise((resolve, reject) => {
|
|
886
941
|
pending = resolve;
|
|
942
|
+
if (idleTimeoutMs !== undefined && idleTimeoutMs > 0) {
|
|
943
|
+
timeout = setTimeout(() => {
|
|
944
|
+
const error = new Error(`WebSocket idle timeout after ${idleTimeoutMs}ms`);
|
|
945
|
+
failed = error;
|
|
946
|
+
done = true;
|
|
947
|
+
pending = null;
|
|
948
|
+
closeWebSocketSilently(socket, 1000, "idle_timeout");
|
|
949
|
+
reject(error);
|
|
950
|
+
}, idleTimeoutMs);
|
|
951
|
+
}
|
|
952
|
+
}).finally(() => {
|
|
953
|
+
if (timeout) {
|
|
954
|
+
clearTimeout(timeout);
|
|
955
|
+
}
|
|
887
956
|
});
|
|
888
957
|
}
|
|
889
958
|
if (failed) {
|
|
@@ -952,8 +1021,8 @@ async function* startWebSocketOutputOnFirstEvent(events, output, stream, onStart
|
|
|
952
1021
|
yield event;
|
|
953
1022
|
}
|
|
954
1023
|
}
|
|
955
|
-
async function processWebSocketStream(url, body, headers, output, stream, model, onStart, options) {
|
|
956
|
-
const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal);
|
|
1024
|
+
async function processWebSocketStream(url, body, headers, output, stream, model, onStart, idleTimeoutMs, websocketConnectTimeoutMs, options) {
|
|
1025
|
+
const { socket, entry, reused, release } = await acquireWebSocket(url, headers, options?.sessionId, options?.signal, websocketConnectTimeoutMs);
|
|
957
1026
|
let keepConnection = true;
|
|
958
1027
|
const useCachedContext = options?.transport === "websocket-cached" || options?.transport === "auto";
|
|
959
1028
|
// ChatGPT Codex Responses rejects `store: true` ("Store must be set to false").
|
|
@@ -985,7 +1054,7 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
|
|
|
985
1054
|
}
|
|
986
1055
|
try {
|
|
987
1056
|
socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
|
|
988
|
-
await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal)), output, stream, onStart), output, stream, model, {
|
|
1057
|
+
await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs)), output, stream, onStart), output, stream, model, {
|
|
989
1058
|
serviceTier: options?.serviceTier,
|
|
990
1059
|
resolveServiceTier: resolveCodexServiceTier,
|
|
991
1060
|
applyServiceTierPricing: (usage, serviceTier) => applyServiceTierPricing(usage, serviceTier, model),
|
|
@@ -1048,13 +1117,15 @@ function extractAccountId(token) {
|
|
|
1048
1117
|
try {
|
|
1049
1118
|
const parts = token.split(".");
|
|
1050
1119
|
if (parts.length !== 3)
|
|
1051
|
-
|
|
1120
|
+
throw new Error("Invalid token");
|
|
1052
1121
|
const payload = JSON.parse(atob(parts[1]));
|
|
1053
1122
|
const accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;
|
|
1054
|
-
|
|
1123
|
+
if (!accountId)
|
|
1124
|
+
throw new Error("No account ID in token");
|
|
1125
|
+
return accountId;
|
|
1055
1126
|
}
|
|
1056
1127
|
catch {
|
|
1057
|
-
|
|
1128
|
+
throw new Error("Failed to extract accountId from token");
|
|
1058
1129
|
}
|
|
1059
1130
|
}
|
|
1060
1131
|
function createCodexRequestId() {
|
|
@@ -1069,10 +1140,7 @@ function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token)
|
|
|
1069
1140
|
headers.set(key, value);
|
|
1070
1141
|
}
|
|
1071
1142
|
headers.set("Authorization", `Bearer ${token}`);
|
|
1072
|
-
|
|
1073
|
-
if (accountId) {
|
|
1074
|
-
headers.set("chatgpt-account-id", accountId);
|
|
1075
|
-
}
|
|
1143
|
+
headers.set("chatgpt-account-id", accountId);
|
|
1076
1144
|
headers.set("originator", "pi");
|
|
1077
1145
|
const userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "pi (browser)";
|
|
1078
1146
|
headers.set("User-Agent", userAgent);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import { getEnvApiKey } from "../env-api-keys.js";
|
|
3
2
|
import { calculateCost, clampThinkingLevel } from "../models.js";
|
|
4
3
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
5
4
|
import { headersToRecord } from "../utils/headers.js";
|
|
@@ -70,7 +69,10 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
70
69
|
timestamp: Date.now(),
|
|
71
70
|
};
|
|
72
71
|
try {
|
|
73
|
-
const apiKey = options?.apiKey
|
|
72
|
+
const apiKey = options?.apiKey;
|
|
73
|
+
if (!apiKey) {
|
|
74
|
+
throw new Error(`No API key for provider: ${model.provider}`);
|
|
75
|
+
}
|
|
74
76
|
const compat = getCompat(model);
|
|
75
77
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
76
78
|
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
@@ -335,7 +337,7 @@ export const streamOpenAICompletions = (model, context, options) => {
|
|
|
335
337
|
return stream;
|
|
336
338
|
};
|
|
337
339
|
export const streamSimpleOpenAICompletions = (model, context, options) => {
|
|
338
|
-
const apiKey = options?.apiKey
|
|
340
|
+
const apiKey = options?.apiKey;
|
|
339
341
|
if (!apiKey) {
|
|
340
342
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
341
343
|
}
|
|
@@ -350,12 +352,6 @@ export const streamSimpleOpenAICompletions = (model, context, options) => {
|
|
|
350
352
|
});
|
|
351
353
|
};
|
|
352
354
|
function createClient(model, context, apiKey, optionsHeaders, sessionId, compat = getCompat(model)) {
|
|
353
|
-
if (!apiKey) {
|
|
354
|
-
if (!process.env.OPENAI_API_KEY) {
|
|
355
|
-
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.");
|
|
356
|
-
}
|
|
357
|
-
apiKey = process.env.OPENAI_API_KEY;
|
|
358
|
-
}
|
|
359
355
|
const headers = { ...model.headers };
|
|
360
356
|
if (model.provider === "github-copilot") {
|
|
361
357
|
const hasImages = hasCopilotVisionInput(context.messages);
|
|
@@ -448,7 +444,7 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
448
444
|
}
|
|
449
445
|
else if (compat.thinkingFormat === "deepseek" && model.reasoning) {
|
|
450
446
|
params.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
|
|
451
|
-
if (options?.reasoningEffort) {
|
|
447
|
+
if (options?.reasoningEffort && compat.supportsReasoningEffort) {
|
|
452
448
|
params.reasoning_effort =
|
|
453
449
|
model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
|
|
454
450
|
}
|
|
@@ -472,6 +468,15 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
|
|
|
472
468
|
togetherParams.reasoning_effort = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
|
|
473
469
|
}
|
|
474
470
|
}
|
|
471
|
+
else if (compat.thinkingFormat === "string-thinking" && model.reasoning) {
|
|
472
|
+
const stringThinkingParams = params;
|
|
473
|
+
if (options?.reasoningEffort) {
|
|
474
|
+
stringThinkingParams.thinking = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
|
|
475
|
+
}
|
|
476
|
+
else if (model.thinkingLevelMap?.off !== null) {
|
|
477
|
+
stringThinkingParams.thinking = model.thinkingLevelMap?.off ?? "none";
|
|
478
|
+
}
|
|
479
|
+
}
|
|
475
480
|
else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) {
|
|
476
481
|
// OpenAI-style reasoning_effort
|
|
477
482
|
params.reasoning_effort = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
|
|
@@ -871,6 +876,7 @@ function detectCompat(model) {
|
|
|
871
876
|
const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
|
|
872
877
|
const isTogether = provider === "together" || baseUrl.includes("api.together.ai") || baseUrl.includes("api.together.xyz");
|
|
873
878
|
const isMoonshot = provider === "moonshotai" || provider === "moonshotai-cn" || baseUrl.includes("api.moonshot.");
|
|
879
|
+
const isOpenRouter = provider === "openrouter" || baseUrl.includes("openrouter.ai");
|
|
874
880
|
const isCloudflareWorkersAI = provider === "cloudflare-workers-ai" || baseUrl.includes("api.cloudflare.com");
|
|
875
881
|
const isCloudflareAiGateway = provider === "cloudflare-ai-gateway" || baseUrl.includes("gateway.ai.cloudflare.com");
|
|
876
882
|
const isNonStandard = provider === "cerebras" ||
|
|
@@ -892,7 +898,7 @@ function detectCompat(model) {
|
|
|
892
898
|
const cacheControlFormat = provider === "openrouter" && model.id.startsWith("anthropic/") ? "anthropic" : undefined;
|
|
893
899
|
return {
|
|
894
900
|
supportsStore: !isNonStandard,
|
|
895
|
-
supportsDeveloperRole: !isNonStandard,
|
|
901
|
+
supportsDeveloperRole: !isNonStandard && !isOpenRouter,
|
|
896
902
|
supportsReasoningEffort: !isGrok && !isZai && !isMoonshot && !isTogether && !isCloudflareAiGateway,
|
|
897
903
|
supportsUsageInStreaming: true,
|
|
898
904
|
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
|
|
@@ -906,7 +912,7 @@ function detectCompat(model) {
|
|
|
906
912
|
? "zai"
|
|
907
913
|
: isTogether
|
|
908
914
|
? "together"
|
|
909
|
-
:
|
|
915
|
+
: isOpenRouter
|
|
910
916
|
? "openrouter"
|
|
911
917
|
: "openai",
|
|
912
918
|
openRouterRouting: {},
|
|
@@ -106,6 +106,7 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
|
|
|
106
106
|
const isDifferentModel = assistantMsg.model !== model.id &&
|
|
107
107
|
assistantMsg.provider === model.provider &&
|
|
108
108
|
assistantMsg.api === model.api;
|
|
109
|
+
let textBlockIndex = 0;
|
|
109
110
|
for (const block of msg.content) {
|
|
110
111
|
if (block.type === "thinking") {
|
|
111
112
|
if (block.thinkingSignature) {
|
|
@@ -116,10 +117,12 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
|
|
|
116
117
|
else if (block.type === "text") {
|
|
117
118
|
const textBlock = block;
|
|
118
119
|
const parsedSignature = parseTextSignature(textBlock.textSignature);
|
|
120
|
+
const fallbackMessageId = textBlockIndex === 0 ? `msg_pi_${msgIndex}` : `msg_pi_${msgIndex}_${textBlockIndex}`;
|
|
121
|
+
textBlockIndex++;
|
|
119
122
|
// OpenAI requires id to be max 64 characters
|
|
120
123
|
let msgId = parsedSignature?.id;
|
|
121
124
|
if (!msgId) {
|
|
122
|
-
msgId =
|
|
125
|
+
msgId = fallbackMessageId;
|
|
123
126
|
}
|
|
124
127
|
else if (msgId.length > 64) {
|
|
125
128
|
msgId = `msg_${shortHash(msgId)}`;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import { getEnvApiKey } from "../env-api-keys.js";
|
|
3
2
|
import { clampThinkingLevel } from "../models.js";
|
|
4
3
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
5
4
|
import { headersToRecord } from "../utils/headers.js";
|
|
@@ -73,7 +72,10 @@ export const streamOpenAIResponses = (model, context, options) => {
|
|
|
73
72
|
};
|
|
74
73
|
try {
|
|
75
74
|
// Create OpenAI client
|
|
76
|
-
const apiKey = options?.apiKey
|
|
75
|
+
const apiKey = options?.apiKey;
|
|
76
|
+
if (!apiKey) {
|
|
77
|
+
throw new Error(`No API key for provider: ${model.provider}`);
|
|
78
|
+
}
|
|
77
79
|
const cacheRetention = resolveCacheRetention(options?.cacheRetention);
|
|
78
80
|
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
|
79
81
|
const client = createClient(model, context, apiKey, options?.headers, cacheSessionId);
|
|
@@ -118,7 +120,7 @@ export const streamOpenAIResponses = (model, context, options) => {
|
|
|
118
120
|
return stream;
|
|
119
121
|
};
|
|
120
122
|
export const streamSimpleOpenAIResponses = (model, context, options) => {
|
|
121
|
-
const apiKey = options?.apiKey
|
|
123
|
+
const apiKey = options?.apiKey;
|
|
122
124
|
if (!apiKey) {
|
|
123
125
|
throw new Error(`No API key for provider: ${model.provider}`);
|
|
124
126
|
}
|
|
@@ -131,12 +133,6 @@ export const streamSimpleOpenAIResponses = (model, context, options) => {
|
|
|
131
133
|
});
|
|
132
134
|
};
|
|
133
135
|
function createClient(model, context, apiKey, optionsHeaders, sessionId) {
|
|
134
|
-
if (!apiKey) {
|
|
135
|
-
if (!process.env.OPENAI_API_KEY) {
|
|
136
|
-
throw new Error("OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.");
|
|
137
|
-
}
|
|
138
|
-
apiKey = process.env.OPENAI_API_KEY;
|
|
139
|
-
}
|
|
140
136
|
const compat = getCompat(model);
|
|
141
137
|
const headers = { ...model.headers };
|
|
142
138
|
if (model.provider === "github-copilot") {
|
|
@@ -11,6 +11,7 @@ export function buildBaseOptions(_model, options, apiKey) {
|
|
|
11
11
|
onPayload: options?.onPayload,
|
|
12
12
|
onResponse: options?.onResponse,
|
|
13
13
|
timeoutMs: options?.timeoutMs,
|
|
14
|
+
websocketConnectTimeoutMs: options?.websocketConnectTimeoutMs,
|
|
14
15
|
maxRetries: options?.maxRetries,
|
|
15
16
|
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
16
17
|
metadata: options?.metadata,
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import "./providers/register-builtins.js";
|
|
2
2
|
import { getApiProvider } from "./api-registry.js";
|
|
3
|
+
import { getEnvApiKey } from "./env-api-keys.js";
|
|
3
4
|
export { getEnvApiKey } from "./env-api-keys.js";
|
|
5
|
+
function hasExplicitApiKey(apiKey) {
|
|
6
|
+
return typeof apiKey === "string" && apiKey.trim().length > 0;
|
|
7
|
+
}
|
|
8
|
+
function withEnvApiKey(model, options) {
|
|
9
|
+
if (hasExplicitApiKey(options?.apiKey))
|
|
10
|
+
return options;
|
|
11
|
+
const apiKey = getEnvApiKey(model.provider);
|
|
12
|
+
if (!apiKey)
|
|
13
|
+
return options;
|
|
14
|
+
return { ...options, apiKey };
|
|
15
|
+
}
|
|
4
16
|
function resolveApiProvider(api) {
|
|
5
17
|
const provider = getApiProvider(api);
|
|
6
18
|
if (!provider) {
|
|
@@ -10,7 +22,7 @@ function resolveApiProvider(api) {
|
|
|
10
22
|
}
|
|
11
23
|
export function stream(model, context, options) {
|
|
12
24
|
const provider = resolveApiProvider(model.api);
|
|
13
|
-
return provider.stream(model, context, options);
|
|
25
|
+
return provider.stream(model, context, withEnvApiKey(model, options));
|
|
14
26
|
}
|
|
15
27
|
export async function complete(model, context, options) {
|
|
16
28
|
const s = stream(model, context, options);
|
|
@@ -18,7 +30,7 @@ export async function complete(model, context, options) {
|
|
|
18
30
|
}
|
|
19
31
|
export function streamSimple(model, context, options) {
|
|
20
32
|
const provider = resolveApiProvider(model.api);
|
|
21
|
-
return provider.streamSimple(model, context, options);
|
|
33
|
+
return provider.streamSimple(model, context, withEnvApiKey(model, options));
|
|
22
34
|
}
|
|
23
35
|
export async function completeSimple(model, context, options) {
|
|
24
36
|
const s = streamSimple(model, context, options);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function combineAbortSignals(signals) {
|
|
2
|
+
const activeSignals = signals.filter((signal) => signal !== undefined);
|
|
3
|
+
if (activeSignals.length === 0) {
|
|
4
|
+
return { cleanup: () => { } };
|
|
5
|
+
}
|
|
6
|
+
if (activeSignals.length === 1) {
|
|
7
|
+
return { signal: activeSignals[0], cleanup: () => { } };
|
|
8
|
+
}
|
|
9
|
+
const controller = new AbortController();
|
|
10
|
+
const listeners = [];
|
|
11
|
+
const abort = (signal) => {
|
|
12
|
+
if (!controller.signal.aborted) {
|
|
13
|
+
controller.abort(signal.reason);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
for (const signal of activeSignals) {
|
|
17
|
+
if (signal.aborted) {
|
|
18
|
+
abort(signal);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const listener = () => abort(signal);
|
|
22
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
23
|
+
listeners.push({ signal, listener });
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
cleanup: () => {
|
|
28
|
+
for (const { signal, listener } of listeners) {
|
|
29
|
+
signal.removeEventListener("abort", listener);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=abort-signals.js.map
|
|
@@ -33,22 +33,23 @@ export async function pollOAuthDeviceCodeFlow(options) {
|
|
|
33
33
|
if (options.signal?.aborted) {
|
|
34
34
|
throw new Error(CANCEL_MESSAGE);
|
|
35
35
|
}
|
|
36
|
-
const remainingMs = deadline - Date.now();
|
|
37
|
-
await abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);
|
|
38
36
|
const result = await options.poll();
|
|
39
37
|
if (result.status === "complete") {
|
|
40
|
-
return result.
|
|
38
|
+
return result.value;
|
|
41
39
|
}
|
|
42
|
-
if (result.status === "
|
|
43
|
-
|
|
40
|
+
if (result.status === "failed") {
|
|
41
|
+
throw new Error(result.message);
|
|
44
42
|
}
|
|
45
43
|
if (result.status === "slow_down") {
|
|
46
44
|
slowDownResponses += 1;
|
|
47
45
|
// RFC 8628 section 3.5: apply this increase to this and all subsequent requests.
|
|
48
46
|
intervalMs = Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INTERVAL_INCREMENT_MS);
|
|
49
|
-
continue;
|
|
50
47
|
}
|
|
51
|
-
|
|
48
|
+
const remainingMs = deadline - Date.now();
|
|
49
|
+
if (remainingMs <= 0) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
await abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);
|
|
52
53
|
}
|
|
53
54
|
throw new Error(slowDownResponses > 0 ? SLOW_DOWN_TIMEOUT_MESSAGE : TIMEOUT_MESSAGE);
|
|
54
55
|
}
|