@gajae-code/ai 0.2.3 → 0.2.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/CHANGELOG.md +6 -0
- package/dist/types/providers/anthropic.d.ts +1 -0
- package/dist/types/types.d.ts +10 -0
- package/dist/types/utils/retry-budget.d.ts +1 -0
- package/package.json +2 -2
- package/src/providers/amazon-bedrock.ts +2 -0
- package/src/providers/anthropic.ts +9 -4
- package/src/providers/azure-openai-responses.ts +2 -1
- package/src/providers/google-gemini-cli.ts +5 -3
- package/src/providers/ollama.ts +2 -0
- package/src/providers/openai-codex-responses.ts +17 -9
- package/src/providers/openai-completions.ts +4 -1
- package/src/providers/openai-responses.ts +4 -1
- package/src/stream.ts +2 -0
- package/src/types.ts +10 -0
- package/src/utils/retry-budget.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.4] - 2026-06-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added configurable provider request and stream retry budgets so hosts can bound transient upstream/server retry behavior separately from session-level retries.
|
|
10
|
+
|
|
5
11
|
## [0.2.2] - 2026-05-31
|
|
6
12
|
|
|
7
13
|
### Fixed
|
package/dist/types/types.d.ts
CHANGED
|
@@ -178,6 +178,16 @@ export interface StreamOptions {
|
|
|
178
178
|
* Default: 60000 (60 seconds). Set to 0 to disable the cap.
|
|
179
179
|
*/
|
|
180
180
|
maxRetryDelayMs?: number;
|
|
181
|
+
/**
|
|
182
|
+
* Maximum provider request retries for transports/SDKs that retry before a stream is established.
|
|
183
|
+
* Counts retries only, not the initial attempt. Providers keep their built-in default when unset.
|
|
184
|
+
*/
|
|
185
|
+
requestMaxRetries?: number;
|
|
186
|
+
/**
|
|
187
|
+
* Maximum provider stream replay retries after a replay-safe transient stream failure.
|
|
188
|
+
* Counts retries only, not the initial stream attempt. Providers keep their built-in default when unset.
|
|
189
|
+
*/
|
|
190
|
+
streamMaxRetries?: number;
|
|
181
191
|
/**
|
|
182
192
|
* Optional metadata to include in API requests.
|
|
183
193
|
* Providers extract the fields they understand and ignore the rest.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveRetryBudget(value: number | undefined, fallback: number): number;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/ai",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@anthropic-ai/sdk": "^0.94.0",
|
|
45
45
|
"@bufbuild/protobuf": "^2.12.0",
|
|
46
|
-
"@gajae-code/utils": "0.2.
|
|
46
|
+
"@gajae-code/utils": "0.2.4",
|
|
47
47
|
"openai": "^6.36.0",
|
|
48
48
|
"partial-json": "^0.1.7",
|
|
49
49
|
"zod": "4.4.3"
|
|
@@ -31,6 +31,7 @@ import { normalizeToolCallId, resolveCacheRetention } from "../utils";
|
|
|
31
31
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
32
32
|
import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
|
|
33
33
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
34
|
+
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
34
35
|
import { toolWireSchema } from "../utils/schema/wire";
|
|
35
36
|
import { resolveAwsCredentials } from "./aws-credentials";
|
|
36
37
|
import { decodeEventStream } from "./aws-eventstream";
|
|
@@ -259,6 +260,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
|
|
|
259
260
|
headers: requestHeaders,
|
|
260
261
|
body,
|
|
261
262
|
signal: options.signal,
|
|
263
|
+
maxAttempts: resolveRetryBudget(options.requestMaxRetries, 4) + 1,
|
|
262
264
|
});
|
|
263
265
|
|
|
264
266
|
if (!response.ok) {
|
|
@@ -61,6 +61,7 @@ import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse";
|
|
|
61
61
|
import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
|
|
62
62
|
import { notifyProviderResponse } from "../utils/provider-response";
|
|
63
63
|
import { isCopilotTransientModelError } from "../utils/retry";
|
|
64
|
+
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
64
65
|
import { COMBINATOR_KEYS, NO_STRICT, toolWireSchema } from "../utils/schema";
|
|
65
66
|
import { spillToDescription } from "../utils/schema/spill";
|
|
66
67
|
import { notifyRawSseEvent, wrapFetchForSseDebug } from "../utils/sse-debug";
|
|
@@ -183,12 +184,14 @@ export function buildAnthropicHeaders(options: AnthropicHeaderOptions): Record<s
|
|
|
183
184
|
"User-Agent": userAgent,
|
|
184
185
|
};
|
|
185
186
|
} else if (!isAnthropicApiBaseUrl(options.baseUrl)) {
|
|
187
|
+
const incomingUserAgent = getHeaderCaseInsensitive(options.modelHeaders, "User-Agent");
|
|
186
188
|
return {
|
|
187
189
|
...modelHeaders,
|
|
188
190
|
Accept: acceptHeader,
|
|
189
191
|
Authorization: `Bearer ${options.apiKey}`,
|
|
190
192
|
...sharedHeaders,
|
|
191
193
|
"Anthropic-Beta": betaHeader,
|
|
194
|
+
...(incomingUserAgent ? { "User-Agent": incomingUserAgent } : {}),
|
|
192
195
|
};
|
|
193
196
|
} else {
|
|
194
197
|
return {
|
|
@@ -626,6 +629,7 @@ export type AnthropicClientOptionsArgs = {
|
|
|
626
629
|
hasTools?: boolean;
|
|
627
630
|
onSseEvent?: AnthropicOptions["onSseEvent"];
|
|
628
631
|
fetch?: FetchImpl;
|
|
632
|
+
requestMaxRetries?: number;
|
|
629
633
|
};
|
|
630
634
|
|
|
631
635
|
export type AnthropicClientOptionsResult = {
|
|
@@ -1057,6 +1061,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
1057
1061
|
hasTools: !!context.tools?.length,
|
|
1058
1062
|
onSseEvent: options?.onSseEvent,
|
|
1059
1063
|
fetch: options?.fetch,
|
|
1064
|
+
requestMaxRetries: options?.requestMaxRetries,
|
|
1060
1065
|
});
|
|
1061
1066
|
client = created.client;
|
|
1062
1067
|
isOAuthToken = created.isOAuthToken;
|
|
@@ -1445,7 +1450,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
1445
1450
|
firstTokenTime === undefined && isProviderRetryableError(streamFailure, model.provider);
|
|
1446
1451
|
if (
|
|
1447
1452
|
activeAbortTracker.wasCallerAbort() ||
|
|
1448
|
-
providerRetryAttempt >= PROVIDER_MAX_RETRIES ||
|
|
1453
|
+
providerRetryAttempt >= resolveRetryBudget(options?.streamMaxRetries, PROVIDER_MAX_RETRIES) ||
|
|
1449
1454
|
(!canRetryTransientEnvelopeFailure && !canRetryProviderFailure)
|
|
1450
1455
|
) {
|
|
1451
1456
|
throw streamFailure;
|
|
@@ -1604,7 +1609,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
|
|
|
1604
1609
|
apiKey: null,
|
|
1605
1610
|
authToken: copilotApiKey,
|
|
1606
1611
|
baseURL: baseUrl,
|
|
1607
|
-
maxRetries: 5,
|
|
1612
|
+
maxRetries: resolveRetryBudget(args.requestMaxRetries, 5),
|
|
1608
1613
|
dangerouslyAllowBrowser: true,
|
|
1609
1614
|
defaultHeaders,
|
|
1610
1615
|
logLevel: ANTHROPIC_SDK_LOG_LEVEL,
|
|
@@ -1637,7 +1642,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
|
|
|
1637
1642
|
apiKey: null,
|
|
1638
1643
|
authToken: null,
|
|
1639
1644
|
baseURL: baseUrl,
|
|
1640
|
-
maxRetries: 5,
|
|
1645
|
+
maxRetries: resolveRetryBudget(args.requestMaxRetries, 5),
|
|
1641
1646
|
dangerouslyAllowBrowser: true,
|
|
1642
1647
|
defaultHeaders,
|
|
1643
1648
|
logLevel: ANTHROPIC_SDK_LOG_LEVEL,
|
|
@@ -1650,7 +1655,7 @@ export function buildAnthropicClientOptions(args: AnthropicClientOptionsArgs): A
|
|
|
1650
1655
|
apiKey: oauthToken ? null : apiKey,
|
|
1651
1656
|
authToken: oauthToken ? apiKey : undefined,
|
|
1652
1657
|
baseURL: baseUrl,
|
|
1653
|
-
maxRetries: 5,
|
|
1658
|
+
maxRetries: resolveRetryBudget(args.requestMaxRetries, 5),
|
|
1654
1659
|
dangerouslyAllowBrowser: true,
|
|
1655
1660
|
defaultHeaders,
|
|
1656
1661
|
logLevel: ANTHROPIC_SDK_LOG_LEVEL,
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
getStreamFirstEventTimeoutMs,
|
|
27
27
|
iterateWithIdleTimeout,
|
|
28
28
|
} from "../utils/idle-iterator";
|
|
29
|
+
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
29
30
|
import { sanitizeSchemaForOpenAIResponses, toolWireSchema } from "../utils/schema";
|
|
30
31
|
import { wrapFetchForSseDebug } from "../utils/sse-debug";
|
|
31
32
|
import { mapToOpenAIResponsesToolChoice } from "../utils/tool-choice";
|
|
@@ -249,7 +250,7 @@ function createClient(model: Model<"azure-openai-responses">, apiKey: string, op
|
|
|
249
250
|
apiKey,
|
|
250
251
|
apiVersion,
|
|
251
252
|
dangerouslyAllowBrowser: true,
|
|
252
|
-
maxRetries: 5,
|
|
253
|
+
maxRetries: resolveRetryBudget(options?.requestMaxRetries, 5),
|
|
253
254
|
defaultHeaders: headers,
|
|
254
255
|
baseURL: baseUrl,
|
|
255
256
|
fetch: onSseEvent ? wrapFetchForSseDebug(baseFetch, event => onSseEvent(event, model)) : baseFetch,
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
import { normalizeSystemPrompts } from "../utils";
|
|
22
22
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
23
23
|
import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
|
|
24
|
+
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
24
25
|
// Refresh is the sole responsibility of AuthStorage (broker-aware, single-flighted);
|
|
25
26
|
// the stream provider trusts the access token threaded through `options.apiKey`.
|
|
26
27
|
import { normalizeSchemaForCCA } from "../utils/schema";
|
|
@@ -340,7 +341,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
340
341
|
headers: requestHeaders,
|
|
341
342
|
body: requestBodyJson,
|
|
342
343
|
signal: options?.signal,
|
|
343
|
-
maxAttempts: MAX_RETRIES + 1,
|
|
344
|
+
maxAttempts: resolveRetryBudget(options?.requestMaxRetries, MAX_RETRIES) + 1,
|
|
344
345
|
defaultDelayMs: attempt => BASE_DELAY_MS * 2 ** attempt,
|
|
345
346
|
maxDelayMs: options?.maxRetryDelayMs ?? RATE_LIMIT_BUDGET_MS,
|
|
346
347
|
fetch: options?.fetch,
|
|
@@ -509,7 +510,8 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
509
510
|
let receivedContent = false;
|
|
510
511
|
let currentResponse = response;
|
|
511
512
|
|
|
512
|
-
|
|
513
|
+
const emptyStreamRetryBudget = resolveRetryBudget(options?.streamMaxRetries, MAX_EMPTY_STREAM_RETRIES);
|
|
514
|
+
for (let emptyAttempt = 0; emptyAttempt <= emptyStreamRetryBudget; emptyAttempt++) {
|
|
513
515
|
if (options?.signal?.aborted) {
|
|
514
516
|
throw new Error("Request was aborted");
|
|
515
517
|
}
|
|
@@ -549,7 +551,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
|
|
|
549
551
|
break;
|
|
550
552
|
}
|
|
551
553
|
|
|
552
|
-
if (emptyAttempt <
|
|
554
|
+
if (emptyAttempt < emptyStreamRetryBudget) {
|
|
553
555
|
resetOutput();
|
|
554
556
|
}
|
|
555
557
|
}
|
package/src/providers/ollama.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { normalizeSystemPrompts } from "../utils";
|
|
|
18
18
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
19
19
|
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
20
20
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
21
|
+
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
21
22
|
import { toolWireSchema } from "../utils/schema/wire";
|
|
22
23
|
import { transformMessages } from "./transform-messages";
|
|
23
24
|
|
|
@@ -402,6 +403,7 @@ export const streamOllama: StreamFunction<"ollama-chat"> = (
|
|
|
402
403
|
},
|
|
403
404
|
body: JSON.stringify(body),
|
|
404
405
|
signal: options.signal,
|
|
406
|
+
maxAttempts: resolveRetryBudget(options.requestMaxRetries, 4) + 1,
|
|
405
407
|
defaultDelayMs: OLLAMA_RETRY_DELAYS_MS,
|
|
406
408
|
fetch: options.fetch,
|
|
407
409
|
});
|
|
@@ -49,6 +49,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
|
49
49
|
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
50
50
|
import { getOpenAIStreamIdleTimeoutMs, iterateWithIdleTimeout } from "../utils/idle-iterator";
|
|
51
51
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
52
|
+
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
52
53
|
import { adaptSchemaForStrict, NO_STRICT, sanitizeSchemaForOpenAIResponses, toolWireSchema } from "../utils/schema";
|
|
53
54
|
import { compactGrammarDefinition } from "./grammar";
|
|
54
55
|
import { CODEX_BASE_URL, getCodexAccountId, OPENAI_HEADER_VALUES, OPENAI_HEADERS } from "./openai-codex/constants";
|
|
@@ -227,7 +228,10 @@ function isCodexWebSocketEnvEnabled(): boolean {
|
|
|
227
228
|
return $flag("PI_CODEX_WEBSOCKET");
|
|
228
229
|
}
|
|
229
230
|
|
|
230
|
-
function getCodexWebSocketRetryBudget(): number {
|
|
231
|
+
function getCodexWebSocketRetryBudget(options?: Pick<OpenAICodexResponsesOptions, "streamMaxRetries">): number {
|
|
232
|
+
if (options?.streamMaxRetries !== undefined) {
|
|
233
|
+
return resolveRetryBudget(options.streamMaxRetries, CODEX_WEBSOCKET_RETRY_BUDGET);
|
|
234
|
+
}
|
|
231
235
|
return parseCodexNonNegativeInteger($env.PI_CODEX_WEBSOCKET_RETRY_BUDGET, CODEX_WEBSOCKET_RETRY_BUDGET);
|
|
232
236
|
}
|
|
233
237
|
|
|
@@ -641,7 +645,7 @@ async function openInitialCodexEventStream(
|
|
|
641
645
|
}> {
|
|
642
646
|
const { transformedBody, websocketState } = requestContext;
|
|
643
647
|
if (websocketState && shouldUseCodexWebSocket(model, websocketState, options?.preferWebsockets)) {
|
|
644
|
-
const websocketRetryBudget = getCodexWebSocketRetryBudget();
|
|
648
|
+
const websocketRetryBudget = getCodexWebSocketRetryBudget(options);
|
|
645
649
|
let websocketRetries = 0;
|
|
646
650
|
while (true) {
|
|
647
651
|
try {
|
|
@@ -707,7 +711,7 @@ async function openCodexWebSocketTransport(
|
|
|
707
711
|
sentModelsEtagHeader: websocketHeaders.has(X_MODELS_ETAG_HEADER),
|
|
708
712
|
requestType: websocketRequest.type,
|
|
709
713
|
retry,
|
|
710
|
-
retryBudget: getCodexWebSocketRetryBudget(),
|
|
714
|
+
retryBudget: getCodexWebSocketRetryBudget(options),
|
|
711
715
|
});
|
|
712
716
|
const eventStream = await openCodexWebSocketEventStream(
|
|
713
717
|
toWebSocketUrl(requestContext.url),
|
|
@@ -744,6 +748,7 @@ async function openCodexSseTransport(
|
|
|
744
748
|
requestSetup.requestSignal,
|
|
745
749
|
event => options?.onSseEvent?.(event, model),
|
|
746
750
|
options?.fetch,
|
|
751
|
+
options,
|
|
747
752
|
),
|
|
748
753
|
);
|
|
749
754
|
return { eventStream, requestBodyForState: structuredCloneJSON(body), transport: "sse" };
|
|
@@ -1348,7 +1353,7 @@ async function tryRecoverCodexPreviousResponseNotFound(
|
|
|
1348
1353
|
runtime.transport !== "websocket" ||
|
|
1349
1354
|
context.output.content.length > 0 ||
|
|
1350
1355
|
context.options?.signal?.aborted ||
|
|
1351
|
-
runtime.providerRetryAttempt >= CODEX_MAX_RETRIES
|
|
1356
|
+
runtime.providerRetryAttempt >= resolveRetryBudget(context.options?.streamMaxRetries, CODEX_MAX_RETRIES)
|
|
1352
1357
|
) {
|
|
1353
1358
|
return false;
|
|
1354
1359
|
}
|
|
@@ -1390,12 +1395,14 @@ async function tryReplayWebsocketFailureOverSse(
|
|
|
1390
1395
|
const replayingBufferedOutputOverSse = context.output.content.length > 0;
|
|
1391
1396
|
const isFatal = isCodexWebSocketFatalError(streamError);
|
|
1392
1397
|
const activateFallback =
|
|
1393
|
-
replayingBufferedOutputOverSse ||
|
|
1398
|
+
replayingBufferedOutputOverSse ||
|
|
1399
|
+
isFatal ||
|
|
1400
|
+
runtime.websocketStreamRetries >= getCodexWebSocketRetryBudget(context.options);
|
|
1394
1401
|
recordCodexWebSocketFailure(state, activateFallback);
|
|
1395
1402
|
logCodexDebug("codex websocket stream fallback", {
|
|
1396
1403
|
error: streamError.message,
|
|
1397
1404
|
retry: runtime.websocketStreamRetries,
|
|
1398
|
-
retryBudget: getCodexWebSocketRetryBudget(),
|
|
1405
|
+
retryBudget: getCodexWebSocketRetryBudget(context.options),
|
|
1399
1406
|
activated: activateFallback,
|
|
1400
1407
|
fatal: isFatal,
|
|
1401
1408
|
replayedBufferedOutput: replayingBufferedOutputOverSse,
|
|
@@ -1431,7 +1438,7 @@ async function tryRetryCodexProviderError(
|
|
|
1431
1438
|
if (
|
|
1432
1439
|
!isRetryableCodexProviderError(error) ||
|
|
1433
1440
|
context.output.content.length > 0 ||
|
|
1434
|
-
runtime.providerRetryAttempt >= CODEX_MAX_RETRIES ||
|
|
1441
|
+
runtime.providerRetryAttempt >= resolveRetryBudget(context.options?.streamMaxRetries, CODEX_MAX_RETRIES) ||
|
|
1435
1442
|
context.options?.signal?.aborted
|
|
1436
1443
|
) {
|
|
1437
1444
|
return false;
|
|
@@ -1447,7 +1454,7 @@ async function tryRetryCodexProviderError(
|
|
|
1447
1454
|
logCodexDebug("retrying codex provider stream error", {
|
|
1448
1455
|
error: error instanceof Error ? error.message : String(error),
|
|
1449
1456
|
retry: runtime.providerRetryAttempt,
|
|
1450
|
-
retryBudget: CODEX_MAX_RETRIES,
|
|
1457
|
+
retryBudget: resolveRetryBudget(context.options?.streamMaxRetries, CODEX_MAX_RETRIES),
|
|
1451
1458
|
transport: runtime.transport,
|
|
1452
1459
|
});
|
|
1453
1460
|
|
|
@@ -2187,6 +2194,7 @@ async function openCodexSseEventStream(
|
|
|
2187
2194
|
signal?: AbortSignal,
|
|
2188
2195
|
onSseEvent?: OpenAICodexResponsesOptions["onSseEvent"],
|
|
2189
2196
|
fetchOverride?: FetchImpl,
|
|
2197
|
+
options?: Pick<OpenAICodexResponsesOptions, "requestMaxRetries">,
|
|
2190
2198
|
): Promise<AsyncGenerator<Record<string, unknown>>> {
|
|
2191
2199
|
const headers = createCodexHeaders(requestHeaders, accountId, apiKey, sessionId, "sse", state);
|
|
2192
2200
|
logCodexDebug("codex request", {
|
|
@@ -2201,7 +2209,7 @@ async function openCodexSseEventStream(
|
|
|
2201
2209
|
headers,
|
|
2202
2210
|
body: JSON.stringify(body),
|
|
2203
2211
|
signal,
|
|
2204
|
-
maxAttempts: CODEX_MAX_RETRIES + 1,
|
|
2212
|
+
maxAttempts: resolveRetryBudget(options?.requestMaxRetries, CODEX_MAX_RETRIES) + 1,
|
|
2205
2213
|
defaultDelayMs: attempt => CODEX_RETRY_DELAY_MS * (attempt + 1),
|
|
2206
2214
|
maxDelayMs: CODEX_RATE_LIMIT_BUDGET_MS,
|
|
2207
2215
|
fetch: fetchOverride,
|
|
@@ -56,6 +56,7 @@ import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
|
|
|
56
56
|
import { getKimiCommonHeaders } from "../utils/oauth/kimi";
|
|
57
57
|
import { notifyProviderResponse } from "../utils/provider-response";
|
|
58
58
|
import { callWithCopilotModelRetry } from "../utils/retry";
|
|
59
|
+
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
59
60
|
import { adaptSchemaForStrict, NO_STRICT, toolWireSchema } from "../utils/schema";
|
|
60
61
|
import { wrapFetchForSseDebug } from "../utils/sse-debug";
|
|
61
62
|
import { type HealedToolCall, modelMayLeakKimiToolCalls, ToolCallHealer } from "../utils/tool-call-healing";
|
|
@@ -444,6 +445,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
444
445
|
options?.fetch,
|
|
445
446
|
options?.streamFirstEventTimeoutMs,
|
|
446
447
|
options?.authCredentialType,
|
|
448
|
+
options?.requestMaxRetries,
|
|
447
449
|
);
|
|
448
450
|
const premiumRequestsTotal = copilotPremiumRequests;
|
|
449
451
|
getCapturedErrorResponse = captureErrorResponse;
|
|
@@ -920,6 +922,7 @@ async function createClient(
|
|
|
920
922
|
fetchOverride?: FetchImpl,
|
|
921
923
|
streamFirstEventTimeoutOverride?: number,
|
|
922
924
|
authCredentialType?: OpenAICompletionsOptions["authCredentialType"],
|
|
925
|
+
requestMaxRetries?: number,
|
|
923
926
|
): Promise<{
|
|
924
927
|
client: OpenAI;
|
|
925
928
|
copilotPremiumRequests: number | undefined;
|
|
@@ -1051,7 +1054,7 @@ async function createClient(
|
|
|
1051
1054
|
apiKey,
|
|
1052
1055
|
baseURL: baseUrl,
|
|
1053
1056
|
dangerouslyAllowBrowser: true,
|
|
1054
|
-
maxRetries: 5,
|
|
1057
|
+
maxRetries: resolveRetryBudget(requestMaxRetries, 5),
|
|
1055
1058
|
defaultHeaders: headers,
|
|
1056
1059
|
defaultQuery: azureDefaultQuery,
|
|
1057
1060
|
fetch: debugFetch,
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
|
|
43
43
|
import { notifyProviderResponse } from "../utils/provider-response";
|
|
44
44
|
import { callWithCopilotModelRetry } from "../utils/retry";
|
|
45
|
+
import { resolveRetryBudget } from "../utils/retry-budget";
|
|
45
46
|
import { adaptSchemaForStrict, NO_STRICT, sanitizeSchemaForOpenAIResponses, toolWireSchema } from "../utils/schema";
|
|
46
47
|
import { wrapFetchForSseDebug } from "../utils/sse-debug";
|
|
47
48
|
import { mapToOpenAIResponsesToolChoice, type OpenAIResponsesToolChoice } from "../utils/tool-choice";
|
|
@@ -253,6 +254,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
253
254
|
options?.onSseEvent,
|
|
254
255
|
options?.fetch,
|
|
255
256
|
options?.authCredentialType,
|
|
257
|
+
options?.requestMaxRetries,
|
|
256
258
|
);
|
|
257
259
|
const premiumRequestsTotal = copilotPremiumRequests;
|
|
258
260
|
const providerSessionState = getOpenAIResponsesProviderSessionState(model, options?.providerSessionState);
|
|
@@ -354,6 +356,7 @@ function createClient(
|
|
|
354
356
|
onSseEvent?: OpenAIResponsesOptions["onSseEvent"],
|
|
355
357
|
fetchOverride?: FetchImpl,
|
|
356
358
|
authCredentialType?: OpenAIResponsesOptions["authCredentialType"],
|
|
359
|
+
requestMaxRetries?: number,
|
|
357
360
|
): {
|
|
358
361
|
client: OpenAI;
|
|
359
362
|
copilotPremiumRequests: number | undefined;
|
|
@@ -410,7 +413,7 @@ function createClient(
|
|
|
410
413
|
apiKey,
|
|
411
414
|
baseURL: baseUrl,
|
|
412
415
|
dangerouslyAllowBrowser: true,
|
|
413
|
-
maxRetries: 5,
|
|
416
|
+
maxRetries: resolveRetryBudget(requestMaxRetries, 5),
|
|
414
417
|
defaultHeaders: headers,
|
|
415
418
|
fetch: onSseEvent
|
|
416
419
|
? wrapFetchForSseDebug(transformedFetch, event => onSseEvent(event, model))
|
package/src/stream.ts
CHANGED
|
@@ -563,6 +563,8 @@ function mapOptionsForApi<TApi extends Api>(
|
|
|
563
563
|
headers: options?.headers,
|
|
564
564
|
initiatorOverride: options?.initiatorOverride,
|
|
565
565
|
maxRetryDelayMs: options?.maxRetryDelayMs,
|
|
566
|
+
requestMaxRetries: options?.requestMaxRetries,
|
|
567
|
+
streamMaxRetries: options?.streamMaxRetries,
|
|
566
568
|
metadata: options?.metadata,
|
|
567
569
|
sessionId: options?.sessionId,
|
|
568
570
|
providerSessionState: options?.providerSessionState,
|
package/src/types.ts
CHANGED
|
@@ -310,6 +310,16 @@ export interface StreamOptions {
|
|
|
310
310
|
* Default: 60000 (60 seconds). Set to 0 to disable the cap.
|
|
311
311
|
*/
|
|
312
312
|
maxRetryDelayMs?: number;
|
|
313
|
+
/**
|
|
314
|
+
* Maximum provider request retries for transports/SDKs that retry before a stream is established.
|
|
315
|
+
* Counts retries only, not the initial attempt. Providers keep their built-in default when unset.
|
|
316
|
+
*/
|
|
317
|
+
requestMaxRetries?: number;
|
|
318
|
+
/**
|
|
319
|
+
* Maximum provider stream replay retries after a replay-safe transient stream failure.
|
|
320
|
+
* Counts retries only, not the initial stream attempt. Providers keep their built-in default when unset.
|
|
321
|
+
*/
|
|
322
|
+
streamMaxRetries?: number;
|
|
313
323
|
/**
|
|
314
324
|
* Optional metadata to include in API requests.
|
|
315
325
|
* Providers extract the fields they understand and ignore the rest.
|