@gajae-code/ai 0.15.4 → 0.15.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 +27 -1
- package/dist/types/auth-broker/client.d.ts +6 -2
- package/dist/types/auth-broker/remote-store.d.ts +14 -2
- package/dist/types/auth-broker/types.d.ts +6 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +19 -0
- package/dist/types/auth-gateway/server.d.ts +39 -5
- package/dist/types/auth-gateway/types.d.ts +16 -2
- package/dist/types/auth-storage.d.ts +116 -34
- package/dist/types/provider-models/openai-compat.d.ts +1 -0
- package/dist/types/provider-models/special.d.ts +2 -1
- package/dist/types/providers/kiro-api-key.d.ts +50 -0
- package/dist/types/providers/kiro-codewhisperer.d.ts +3 -0
- package/dist/types/providers/register-builtins.d.ts +12 -12
- package/dist/types/stream.d.ts +2 -1
- package/dist/types/types.d.ts +35 -24
- package/dist/types/utils/fallback-transport.d.ts +7 -0
- package/dist/types/utils/json-parse.d.ts +5 -3
- package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
- package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
- package/dist/types/utils/oauth/commandcode.d.ts +1 -0
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/dist/types/utils/retry.d.ts +2 -0
- package/dist/types/utils/tool-call-healing.d.ts +4 -4
- package/package.json +3 -3
- package/src/auth-broker/client.ts +41 -13
- package/src/auth-broker/redact.ts +25 -1
- package/src/auth-broker/remote-store.ts +374 -115
- package/src/auth-broker/server.ts +131 -91
- package/src/auth-broker/types.ts +6 -0
- package/src/auth-broker/wire-schemas.ts +6 -0
- package/src/auth-gateway/server.ts +447 -79
- package/src/auth-gateway/types.ts +28 -2
- package/src/auth-storage.ts +742 -157
- package/src/cli.ts +1 -0
- package/src/model-thinking.ts +16 -0
- package/src/models.json +1054 -0
- package/src/models.ts +9 -1
- package/src/provider-models/descriptors.ts +3 -1
- package/src/provider-models/openai-compat.ts +41 -1
- package/src/provider-models/special.ts +15 -3
- package/src/providers/anthropic.ts +7 -1
- package/src/providers/azure-openai-responses.ts +4 -1
- package/src/providers/cursor.ts +256 -101
- package/src/providers/gitlab-duo.ts +18 -1
- package/src/providers/google-gemini-cli.ts +3 -0
- package/src/providers/google-shared.ts +3 -0
- package/src/providers/kiro-api-key.d.ts +50 -0
- package/src/providers/kiro-api-key.ts +786 -0
- package/src/providers/kiro-codewhisperer.d.ts +3 -0
- package/src/providers/kiro-codewhisperer.ts +34 -9
- package/src/providers/ollama.ts +3 -0
- package/src/providers/openai-codex-responses.ts +24 -6
- package/src/providers/openai-completions.ts +11 -1
- package/src/providers/openai-responses-shared.ts +23 -2
- package/src/providers/openai-responses.ts +10 -1
- package/src/providers/pi-native-client.ts +1 -0
- package/src/providers/pi-native-server.ts +24 -0
- package/src/providers/register-builtins.d.ts +12 -12
- package/src/providers/register-builtins.ts +16 -3
- package/src/stream.d.ts +2 -1
- package/src/stream.ts +180 -70
- package/src/types.d.ts +35 -24
- package/src/types.ts +40 -23
- package/src/utils/fallback-transport.d.ts +7 -0
- package/src/utils/fallback-transport.ts +21 -4
- package/src/utils/json-parse.d.ts +5 -3
- package/src/utils/json-parse.ts +6 -6
- package/src/utils/oauth/api-key-login.ts +13 -2
- package/src/utils/oauth/api-key-validation.ts +242 -41
- package/src/utils/oauth/commandcode.ts +17 -0
- package/src/utils/oauth/index.ts +20 -5
- package/src/utils/oauth/types.d.ts +1 -1
- package/src/utils/oauth/types.ts +1 -0
- package/src/utils/retry.d.ts +2 -0
- package/src/utils/retry.ts +15 -2
- package/src/utils/tool-call-healing.d.ts +4 -4
- package/src/utils/tool-call-healing.ts +4 -4
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { Effort } from "../model-thinking";
|
|
1
2
|
import type { StreamFunction, StreamOptions } from "../types";
|
|
2
3
|
export interface KiroCodeWhispererOptions extends StreamOptions {
|
|
4
|
+
/** Effort level for Kiro API-key reasoning. */
|
|
5
|
+
reasoning?: Effort | boolean;
|
|
3
6
|
/** AWS region for the CodeWhisperer streaming endpoint. */
|
|
4
7
|
region?: string;
|
|
5
8
|
/** Profile ARN for enterprise IAM Identity Center accounts. */
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* not from any AGPL reference implementation.
|
|
12
12
|
*/
|
|
13
13
|
import { $credentialEnv, $env, extractHttpStatusFromError } from "@gajae-code/utils";
|
|
14
|
+
import type { Effort } from "../model-thinking";
|
|
14
15
|
import type {
|
|
15
16
|
Api,
|
|
16
17
|
AssistantMessage,
|
|
@@ -28,12 +29,15 @@ import { transportFailureFacts } from "../utils/fallback-transport";
|
|
|
28
29
|
import { withHttpStatus } from "../utils/http-inspector";
|
|
29
30
|
import { captureUnicodeEscapeEvidence } from "../utils/json-parse";
|
|
30
31
|
import { decodeEventStream } from "./aws-eventstream";
|
|
32
|
+
import { isKiroApiKey, streamKiroApiKey, toKiroModelId } from "./kiro-api-key";
|
|
31
33
|
|
|
32
34
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
35
|
// Provider options
|
|
34
36
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
37
|
|
|
36
38
|
export interface KiroCodeWhispererOptions extends StreamOptions {
|
|
39
|
+
/** Effort level for Kiro API-key reasoning. */
|
|
40
|
+
reasoning?: Effort | boolean;
|
|
37
41
|
/** AWS region for the CodeWhisperer streaming endpoint. */
|
|
38
42
|
region?: string;
|
|
39
43
|
/** Profile ARN for enterprise IAM Identity Center accounts. */
|
|
@@ -63,6 +67,7 @@ interface WireToolResult {
|
|
|
63
67
|
interface WireUserMessage {
|
|
64
68
|
userInputMessage: {
|
|
65
69
|
content: string;
|
|
70
|
+
modelId?: string;
|
|
66
71
|
userInputMessageContext?: {
|
|
67
72
|
tools?: { tools: WireToolSpec[] };
|
|
68
73
|
toolResults?: { toolResults: WireToolResult[][] };
|
|
@@ -143,6 +148,11 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
143
148
|
context: Context,
|
|
144
149
|
options: KiroCodeWhispererOptions,
|
|
145
150
|
): AssistantMessageEventStream => {
|
|
151
|
+
const token = resolveBearerToken(options.apiKey);
|
|
152
|
+
if (isKiroApiKey(token)) {
|
|
153
|
+
return streamKiroApiKey(model, context, { ...options, apiKey: token });
|
|
154
|
+
}
|
|
155
|
+
|
|
146
156
|
const stream = new AssistantMessageEventStream();
|
|
147
157
|
|
|
148
158
|
(async () => {
|
|
@@ -175,7 +185,7 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
175
185
|
const bearerToken = resolveBearerToken(options.apiKey);
|
|
176
186
|
if (!bearerToken) {
|
|
177
187
|
throw new Error(
|
|
178
|
-
"No Kiro credentials found.
|
|
188
|
+
"No Kiro credentials found. Set KIRO_API_KEY (ksk_ from https://app.kiro.dev/settings/api-keys) or run 'gjc auth-broker login kiro'.",
|
|
179
189
|
);
|
|
180
190
|
}
|
|
181
191
|
|
|
@@ -344,7 +354,7 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
344
354
|
|
|
345
355
|
function buildConversationState(
|
|
346
356
|
context: Context,
|
|
347
|
-
|
|
357
|
+
model: Model<"kiro-codewhisperer-stream">,
|
|
348
358
|
options: KiroCodeWhispererOptions,
|
|
349
359
|
): ConversationState {
|
|
350
360
|
const messages = context.messages;
|
|
@@ -352,18 +362,24 @@ function buildConversationState(
|
|
|
352
362
|
throw new Error("Kiro CodeWhisperer requires at least one message");
|
|
353
363
|
}
|
|
354
364
|
|
|
365
|
+
// Normalize the local dashed selector/wire id (e.g. "claude-haiku-4-5") to
|
|
366
|
+
// the canonical dotted upstream Kiro model id (e.g. "claude-haiku-4.5"),
|
|
367
|
+
// matching the sibling ksk_ API-key transport (kiro-api-key.ts) so both
|
|
368
|
+
// auth methods send the same wire form for the same catalog entry.
|
|
369
|
+
const modelId = toKiroModelId(model.wireModelId || model.id);
|
|
370
|
+
|
|
355
371
|
// Build history from all messages except the last
|
|
356
372
|
const history: WireHistoryMessage[] = [];
|
|
357
373
|
const systemPrompt = context.systemPrompt?.join("\n") ?? "";
|
|
358
374
|
|
|
359
375
|
for (let i = 0; i < messages.length - 1; i++) {
|
|
360
376
|
const msg = messages[i];
|
|
361
|
-
history.push(convertToWireMessage(msg, i === 0 ? systemPrompt : undefined));
|
|
377
|
+
history.push(convertToWireMessage(msg, modelId, i === 0 ? systemPrompt : undefined));
|
|
362
378
|
}
|
|
363
379
|
|
|
364
380
|
// Convert the last message as currentMessage
|
|
365
381
|
const lastMsg = messages[messages.length - 1];
|
|
366
|
-
const currentMessage = convertToWireUserMessage(lastMsg, systemPrompt);
|
|
382
|
+
const currentMessage = convertToWireUserMessage(lastMsg, modelId, systemPrompt);
|
|
367
383
|
|
|
368
384
|
// Add tools to the current message context
|
|
369
385
|
if (context.tools && context.tools.length > 0) {
|
|
@@ -383,12 +399,16 @@ function buildConversationState(
|
|
|
383
399
|
};
|
|
384
400
|
}
|
|
385
401
|
|
|
386
|
-
function convertToWireMessage(
|
|
402
|
+
function convertToWireMessage(
|
|
403
|
+
msg: Context["messages"][number],
|
|
404
|
+
modelId: string,
|
|
405
|
+
systemPrompt?: string,
|
|
406
|
+
): WireHistoryMessage {
|
|
387
407
|
if (msg.role === "user") {
|
|
388
|
-
return convertToWireUserMessage(msg, systemPrompt);
|
|
408
|
+
return convertToWireUserMessage(msg, modelId, systemPrompt);
|
|
389
409
|
}
|
|
390
410
|
if (msg.role === "toolResult") {
|
|
391
|
-
return convertToWireUserMessage(msg, systemPrompt);
|
|
411
|
+
return convertToWireUserMessage(msg, modelId, systemPrompt);
|
|
392
412
|
}
|
|
393
413
|
// assistant → assistant response
|
|
394
414
|
const textParts: string[] = [];
|
|
@@ -408,7 +428,11 @@ function convertToWireMessage(msg: Context["messages"][number], systemPrompt?: s
|
|
|
408
428
|
};
|
|
409
429
|
}
|
|
410
430
|
|
|
411
|
-
function convertToWireUserMessage(
|
|
431
|
+
function convertToWireUserMessage(
|
|
432
|
+
msg: Context["messages"][number],
|
|
433
|
+
modelId: string,
|
|
434
|
+
systemPrompt?: string,
|
|
435
|
+
): WireUserMessage {
|
|
412
436
|
let content = extractTextContent(msg);
|
|
413
437
|
if (systemPrompt) {
|
|
414
438
|
content = `${systemPrompt}\n\n${content}`;
|
|
@@ -417,6 +441,7 @@ function convertToWireUserMessage(msg: Context["messages"][number], systemPrompt
|
|
|
417
441
|
const userMsg: WireUserMessage = {
|
|
418
442
|
userInputMessage: {
|
|
419
443
|
content,
|
|
444
|
+
modelId,
|
|
420
445
|
},
|
|
421
446
|
};
|
|
422
447
|
|
|
@@ -541,7 +566,7 @@ function handleToolUseEvent(
|
|
|
541
566
|
|
|
542
567
|
function resolveBearerToken(apiKey: string | undefined): string | undefined {
|
|
543
568
|
if (!apiKey) {
|
|
544
|
-
return $credentialEnv("AWS_BEARER_TOKEN_KIRO") ?? undefined;
|
|
569
|
+
return $credentialEnv("KIRO_API_KEY") ?? $credentialEnv("AWS_BEARER_TOKEN_KIRO") ?? undefined;
|
|
545
570
|
}
|
|
546
571
|
|
|
547
572
|
// Structured API key (from getOAuthApiKey) contains the access token as JSON
|
package/src/providers/ollama.ts
CHANGED
|
@@ -419,6 +419,7 @@ export const streamOllama: StreamFunction<"ollama-chat"> = (
|
|
|
419
419
|
url: `${baseUrl}/api/chat`,
|
|
420
420
|
body,
|
|
421
421
|
};
|
|
422
|
+
options?.onStreamCreated?.();
|
|
422
423
|
let response = await fetchWithRetry(`${baseUrl}/api/chat`, {
|
|
423
424
|
method: "POST",
|
|
424
425
|
headers: {
|
|
@@ -441,6 +442,7 @@ export const streamOllama: StreamFunction<"ollama-chat"> = (
|
|
|
441
442
|
if (
|
|
442
443
|
firstTokenTime === undefined &&
|
|
443
444
|
!options.fallbackManaged &&
|
|
445
|
+
!options.disableProviderRetries &&
|
|
444
446
|
isForcedToolChoiceUnsupportedError(error, true)
|
|
445
447
|
) {
|
|
446
448
|
markToolChoiceIncapability(model, "auto", error.message);
|
|
@@ -457,6 +459,7 @@ export const streamOllama: StreamFunction<"ollama-chat"> = (
|
|
|
457
459
|
body = { ...body };
|
|
458
460
|
delete (body as { tool_choice?: unknown }).tool_choice;
|
|
459
461
|
rawRequestDump = { ...rawRequestDump, body };
|
|
462
|
+
options?.onStreamCreated?.();
|
|
460
463
|
response = await fetchWithRetry(`${baseUrl}/api/chat`, {
|
|
461
464
|
method: "POST",
|
|
462
465
|
headers: {
|
|
@@ -844,6 +844,7 @@ async function openInitialCodexEventStream(
|
|
|
844
844
|
if (websocketState && shouldUseCodexWebSocket(model, websocketState, options?.preferWebsockets)) {
|
|
845
845
|
const websocketRetryBudget = getCodexWebSocketRetryBudget(options);
|
|
846
846
|
let websocketRetries = 0;
|
|
847
|
+
let lastWebsocketError: Error | undefined;
|
|
847
848
|
while (true) {
|
|
848
849
|
try {
|
|
849
850
|
return await openCodexWebSocketTransport(
|
|
@@ -855,6 +856,7 @@ async function openInitialCodexEventStream(
|
|
|
855
856
|
);
|
|
856
857
|
} catch (error) {
|
|
857
858
|
const websocketError = error instanceof Error ? error : new Error(String(error));
|
|
859
|
+
lastWebsocketError = websocketError;
|
|
858
860
|
const isFatal = isCodexWebSocketFatalError(websocketError);
|
|
859
861
|
const activateFallback = isFatal || websocketRetries >= websocketRetryBudget;
|
|
860
862
|
recordCodexWebSocketFailure(websocketState, activateFallback);
|
|
@@ -875,6 +877,9 @@ async function openInitialCodexEventStream(
|
|
|
875
877
|
break;
|
|
876
878
|
}
|
|
877
879
|
}
|
|
880
|
+
if (options?.fallbackManaged || options?.disableProviderRetries) {
|
|
881
|
+
throw lastWebsocketError ?? new Error("Codex websocket transport failed");
|
|
882
|
+
}
|
|
878
883
|
}
|
|
879
884
|
return openCodexSseTransport(model, requestContext, requestSetup, options, websocketState, transformedBody);
|
|
880
885
|
}
|
|
@@ -1571,6 +1576,8 @@ function handleOutputItemDone(
|
|
|
1571
1576
|
arguments: terminalArguments as Record<string, unknown>,
|
|
1572
1577
|
};
|
|
1573
1578
|
captureUnicodeEscapeEvidence(toolCall, item.arguments);
|
|
1579
|
+
delete runtime.currentBlock.escapedNonAsciiArguments;
|
|
1580
|
+
delete runtime.currentBlock.escapedUnicodeArgumentEvidence;
|
|
1574
1581
|
Object.assign(runtime.currentBlock, toolCall);
|
|
1575
1582
|
captureUnicodeEscapeEvidence(runtime.currentBlock, item.arguments);
|
|
1576
1583
|
delete (runtime.currentBlock as { partialJson?: string }).partialJson;
|
|
@@ -1625,6 +1632,8 @@ function handleOutputItemDone(
|
|
|
1625
1632
|
arguments: { input: terminalInput },
|
|
1626
1633
|
customWireName: item.name,
|
|
1627
1634
|
};
|
|
1635
|
+
delete runtime.currentBlock.escapedNonAsciiArguments;
|
|
1636
|
+
delete runtime.currentBlock.escapedUnicodeArgumentEvidence;
|
|
1628
1637
|
Object.assign(runtime.currentBlock, toolCall);
|
|
1629
1638
|
delete (runtime.currentBlock as { partialJson?: string }).partialJson;
|
|
1630
1639
|
delete (runtime.currentBlock as { doneInput?: string }).doneInput;
|
|
@@ -1735,6 +1744,7 @@ async function tryRetryWithoutForcedToolChoice(
|
|
|
1735
1744
|
): Promise<boolean> {
|
|
1736
1745
|
if (
|
|
1737
1746
|
context.options?.fallbackManaged ||
|
|
1747
|
+
context.options?.disableProviderRetries ||
|
|
1738
1748
|
runtime.toolChoiceFallbackAttempted ||
|
|
1739
1749
|
context.output.content.length > 0 ||
|
|
1740
1750
|
context.firstTokenTime !== undefined ||
|
|
@@ -1838,7 +1848,8 @@ async function tryReconnectCodexWebSocketOnConnectionLimit(
|
|
|
1838
1848
|
!websocketState ||
|
|
1839
1849
|
runtime.transport !== "websocket" ||
|
|
1840
1850
|
context.options?.signal?.aborted ||
|
|
1841
|
-
context.options?.fallbackManaged
|
|
1851
|
+
context.options?.fallbackManaged ||
|
|
1852
|
+
context.options?.disableProviderRetries
|
|
1842
1853
|
) {
|
|
1843
1854
|
return false;
|
|
1844
1855
|
}
|
|
@@ -1901,6 +1912,7 @@ async function tryRecoverCodexPreviousResponseNotFound(
|
|
|
1901
1912
|
runtime.previousResponseRecoveryAttempted ||
|
|
1902
1913
|
!websocketState ||
|
|
1903
1914
|
context.options?.fallbackManaged ||
|
|
1915
|
+
context.options?.disableProviderRetries ||
|
|
1904
1916
|
runtime.transport !== "websocket" ||
|
|
1905
1917
|
context.output.content.length > 0 ||
|
|
1906
1918
|
context.options?.signal?.aborted ||
|
|
@@ -1941,7 +1953,8 @@ async function tryReplayWebsocketFailureOverSse(
|
|
|
1941
1953
|
runtime.canSafelyReplayWebsocketOverSse &&
|
|
1942
1954
|
!runtime.sawTerminalEvent &&
|
|
1943
1955
|
!context.options?.signal?.aborted &&
|
|
1944
|
-
!context.options?.fallbackManaged
|
|
1956
|
+
!context.options?.fallbackManaged &&
|
|
1957
|
+
!context.options?.disableProviderRetries;
|
|
1945
1958
|
if (!canReplay) return false;
|
|
1946
1959
|
|
|
1947
1960
|
const state = websocketState;
|
|
@@ -1995,7 +2008,8 @@ async function tryRetryCodexProviderError(
|
|
|
1995
2008
|
context.output.content.length > 0 ||
|
|
1996
2009
|
runtime.providerRetryAttempt >= resolveRetryBudget(context.options?.streamMaxRetries, CODEX_MAX_RETRIES) ||
|
|
1997
2010
|
context.options?.signal?.aborted ||
|
|
1998
|
-
context.options?.fallbackManaged
|
|
2011
|
+
context.options?.fallbackManaged ||
|
|
2012
|
+
context.options?.disableProviderRetries
|
|
1999
2013
|
) {
|
|
2000
2014
|
return false;
|
|
2001
2015
|
}
|
|
@@ -2115,7 +2129,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|
|
2115
2129
|
try {
|
|
2116
2130
|
initialTransport = await openInitialCodexEventStream(model, streamOptions, requestSetup, requestContext);
|
|
2117
2131
|
} catch (error) {
|
|
2118
|
-
if (streamOptions.fallbackManaged) throw error;
|
|
2132
|
+
if (streamOptions.fallbackManaged || streamOptions.disableProviderRetries) throw error;
|
|
2119
2133
|
initialTransport = await retryCodexInitialTransportWithoutToolChoice(
|
|
2120
2134
|
model,
|
|
2121
2135
|
streamOptions,
|
|
@@ -2629,6 +2643,7 @@ class CodexWebSocketConnection {
|
|
|
2629
2643
|
signal?: AbortSignal,
|
|
2630
2644
|
firstEventTimeoutMs?: number,
|
|
2631
2645
|
idleTimeoutMs = this.#idleTimeoutMs,
|
|
2646
|
+
onStreamCreated?: () => void,
|
|
2632
2647
|
): AsyncGenerator<Record<string, unknown>> {
|
|
2633
2648
|
if (!this.#socket || this.#socket.readyState !== WebSocket.OPEN) {
|
|
2634
2649
|
throw createCodexWebSocketTransportError("websocket connection is unavailable");
|
|
@@ -2650,6 +2665,7 @@ class CodexWebSocketConnection {
|
|
|
2650
2665
|
}
|
|
2651
2666
|
|
|
2652
2667
|
try {
|
|
2668
|
+
onStreamCreated?.();
|
|
2653
2669
|
this.#socket.send(JSON.stringify(request));
|
|
2654
2670
|
let sawFirstProgress = false;
|
|
2655
2671
|
const startedAt = Date.now();
|
|
@@ -2791,7 +2807,7 @@ async function openCodexSseEventStream(
|
|
|
2791
2807
|
signal?: AbortSignal,
|
|
2792
2808
|
onSseEvent?: OpenAICodexResponsesOptions["onSseEvent"],
|
|
2793
2809
|
fetchOverride?: FetchImpl,
|
|
2794
|
-
options?: Pick<OpenAICodexResponsesOptions, "requestMaxRetries">,
|
|
2810
|
+
options?: Pick<OpenAICodexResponsesOptions, "requestMaxRetries" | "onStreamCreated">,
|
|
2795
2811
|
): Promise<AsyncGenerator<Record<string, unknown>>> {
|
|
2796
2812
|
const headers = createCodexHeaders(requestHeaders, accountId, apiKey, sessionId, "sse", state);
|
|
2797
2813
|
logCodexDebug("codex request", {
|
|
@@ -2801,6 +2817,7 @@ async function openCodexSseEventStream(
|
|
|
2801
2817
|
sentTurnStateHeader: headers.has(X_CODEX_TURN_STATE_HEADER),
|
|
2802
2818
|
sentModelsEtagHeader: headers.has(X_MODELS_ETAG_HEADER),
|
|
2803
2819
|
});
|
|
2820
|
+
options?.onStreamCreated?.();
|
|
2804
2821
|
const response = await fetchWithRetry(url, {
|
|
2805
2822
|
method: "POST",
|
|
2806
2823
|
headers,
|
|
@@ -2841,7 +2858,7 @@ async function openCodexWebSocketEventStream(
|
|
|
2841
2858
|
request: Record<string, unknown>,
|
|
2842
2859
|
state: CodexWebSocketSessionState,
|
|
2843
2860
|
signal?: AbortSignal,
|
|
2844
|
-
options?: Pick<OpenAICodexResponsesOptions, "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs">,
|
|
2861
|
+
options?: Pick<OpenAICodexResponsesOptions, "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "onStreamCreated">,
|
|
2845
2862
|
firstEventTimeoutMs?: number,
|
|
2846
2863
|
): Promise<AsyncGenerator<Record<string, unknown>>> {
|
|
2847
2864
|
const connection = await getOrCreateCodexWebSocketConnection(state, url, headers, signal, options);
|
|
@@ -2850,6 +2867,7 @@ async function openCodexWebSocketEventStream(
|
|
|
2850
2867
|
signal,
|
|
2851
2868
|
firstEventTimeoutMs,
|
|
2852
2869
|
getCodexWebSocketIdleTimeoutMs(options?.streamIdleTimeoutMs),
|
|
2870
|
+
options?.onStreamCreated,
|
|
2853
2871
|
);
|
|
2854
2872
|
}
|
|
2855
2873
|
|
|
@@ -636,6 +636,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
636
636
|
headers: requestHeaders,
|
|
637
637
|
body: params,
|
|
638
638
|
};
|
|
639
|
+
options?.onStreamCreated?.();
|
|
639
640
|
const { data, response, request_id } = await client.chat.completions
|
|
640
641
|
.create(params as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming, { signal: requestSignal })
|
|
641
642
|
.withResponse();
|
|
@@ -648,6 +649,8 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
648
649
|
provider: model.provider,
|
|
649
650
|
signal: requestSignal,
|
|
650
651
|
fallbackManaged: options?.fallbackManaged,
|
|
652
|
+
requestMaxRetries: options?.requestMaxRetries,
|
|
653
|
+
disableProviderRetries: options?.disableProviderRetries,
|
|
651
654
|
});
|
|
652
655
|
} catch (error) {
|
|
653
656
|
const capturedErrorResponse = getCapturedErrorResponse();
|
|
@@ -656,6 +659,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
656
659
|
);
|
|
657
660
|
if (
|
|
658
661
|
!options?.fallbackManaged &&
|
|
662
|
+
!options?.disableProviderRetries &&
|
|
659
663
|
firstTokenTime === undefined &&
|
|
660
664
|
isForcedToolChoiceUnsupportedError(error, sentForcedToolChoice)
|
|
661
665
|
) {
|
|
@@ -675,6 +679,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
675
679
|
openaiStream = await createCompletionsStream();
|
|
676
680
|
} else if (
|
|
677
681
|
!options?.fallbackManaged &&
|
|
682
|
+
!options?.disableProviderRetries &&
|
|
678
683
|
isOpenRouterAnthropicModel(model) &&
|
|
679
684
|
!disableStrictTools &&
|
|
680
685
|
isCompiledGrammarTooLargeStrictError(error, capturedErrorResponse)
|
|
@@ -689,6 +694,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
689
694
|
} else {
|
|
690
695
|
if (
|
|
691
696
|
options?.fallbackManaged ||
|
|
697
|
+
options?.disableProviderRetries ||
|
|
692
698
|
!shouldRetryWithoutStrictTools(error, capturedErrorResponse, appliedToolStrictMode, context.tools)
|
|
693
699
|
) {
|
|
694
700
|
throw error;
|
|
@@ -733,7 +739,11 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
733
739
|
continue;
|
|
734
740
|
}
|
|
735
741
|
replaySafeChunks.push(chunk);
|
|
736
|
-
if (
|
|
742
|
+
if (
|
|
743
|
+
hasNetworkErrorFinishReason(chunk) &&
|
|
744
|
+
!options?.fallbackManaged &&
|
|
745
|
+
!options?.disableProviderRetries
|
|
746
|
+
) {
|
|
737
747
|
retryNetworkError = true;
|
|
738
748
|
break;
|
|
739
749
|
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../types";
|
|
32
32
|
import { normalizeResponsesToolCallId, sanitizeJsonStrings } from "../utils";
|
|
33
33
|
import type { AssistantMessageEventStream } from "../utils/event-stream";
|
|
34
|
+
import { SERVER_OVERLOADED_PROVIDER_CODE } from "../utils/fallback-transport";
|
|
34
35
|
import { captureUnicodeEscapeEvidence, isCompleteJson, parseStreamingJson } from "../utils/json-parse";
|
|
35
36
|
import { areJsonValuesEqual } from "../utils/schema";
|
|
36
37
|
import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER, partitionVisionContent } from "./vision-guard";
|
|
@@ -981,7 +982,10 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
981
982
|
: typeof statusDetailsReason === "string" && statusDetailsReason.length > 0
|
|
982
983
|
? `status_details: ${statusDetailsReason}`
|
|
983
984
|
: "Unknown error (no error details in response)";
|
|
984
|
-
|
|
985
|
+
// A `cancelled` response is not a capacity rejection and may already
|
|
986
|
+
// have produced observable work, so only the `failed` status carries
|
|
987
|
+
// the typed code onward.
|
|
988
|
+
throw createResponsesFailedError(message, response.status === "failed" ? error?.code : undefined);
|
|
985
989
|
}
|
|
986
990
|
// A response cut short for length (`incomplete`) may have stopped
|
|
987
991
|
// mid-tool-call. Any tool-call item still tracked in `items` never
|
|
@@ -1003,11 +1007,28 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
1003
1007
|
: details?.reason
|
|
1004
1008
|
? `incomplete: ${details.reason}`
|
|
1005
1009
|
: "Unknown error (no error details in response)";
|
|
1006
|
-
throw
|
|
1010
|
+
throw createResponsesFailedError(message, error?.code);
|
|
1007
1011
|
}
|
|
1008
1012
|
}
|
|
1009
1013
|
}
|
|
1010
1014
|
|
|
1015
|
+
/**
|
|
1016
|
+
* A terminal failure envelope arrives inside an HTTP 200 stream — as
|
|
1017
|
+
* `response.failed`, or as `response.completed` with a `failed` response status
|
|
1018
|
+
* — so the typed `error.code` is the only structured evidence the transport can
|
|
1019
|
+
* keep. Both shapes carry the same structured failure and are typed identically.
|
|
1020
|
+
* Exactly OpenAI's capacity-overload code is carried through as transport facts,
|
|
1021
|
+
* matched case-sensitively; every other failure stays a plain error, so an
|
|
1022
|
+
* untyped, cased, or malformed code can never reach a typed retry admission. The
|
|
1023
|
+
* display message is unchanged either way.
|
|
1024
|
+
*/
|
|
1025
|
+
function createResponsesFailedError(message: string, code: string | undefined): Error {
|
|
1026
|
+
if (code !== SERVER_OVERLOADED_PROVIDER_CODE) return new Error(message);
|
|
1027
|
+
const error = new Error(message) as Error & { openaiErrorCode?: string };
|
|
1028
|
+
error.openaiErrorCode = SERVER_OVERLOADED_PROVIDER_CODE;
|
|
1029
|
+
return error;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1011
1032
|
/**
|
|
1012
1033
|
* Whether two raw JSON argument strings decode to the same value. Used to tell a
|
|
1013
1034
|
* relay's re-serialization of the same tool arguments apart from a genuine
|
|
@@ -402,16 +402,24 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
402
402
|
};
|
|
403
403
|
const openaiStream = await callWithCopilotModelRetry(
|
|
404
404
|
async () => {
|
|
405
|
+
options?.onStreamCreated?.();
|
|
405
406
|
const { data, response, request_id } = await client.responses
|
|
406
407
|
.create(params, { signal: requestSignal })
|
|
407
408
|
.withResponse();
|
|
408
409
|
await notifyProviderResponse(options, response, model, request_id);
|
|
409
410
|
return data;
|
|
410
411
|
},
|
|
411
|
-
{
|
|
412
|
+
{
|
|
413
|
+
provider: model.provider,
|
|
414
|
+
signal: requestSignal,
|
|
415
|
+
fallbackManaged: options?.fallbackManaged,
|
|
416
|
+
requestMaxRetries: options?.requestMaxRetries,
|
|
417
|
+
disableProviderRetries: options?.disableProviderRetries,
|
|
418
|
+
},
|
|
412
419
|
).catch(async error => {
|
|
413
420
|
if (
|
|
414
421
|
options?.fallbackManaged ||
|
|
422
|
+
options?.disableProviderRetries ||
|
|
415
423
|
!isForcedToolChoiceUnsupportedError(error, isForcedOpenAIResponsesToolChoice(params.tool_choice))
|
|
416
424
|
) {
|
|
417
425
|
throw error;
|
|
@@ -431,6 +439,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
431
439
|
});
|
|
432
440
|
delete params.tool_choice;
|
|
433
441
|
if (rawRequestDump) rawRequestDump.body = params;
|
|
442
|
+
options?.onStreamCreated?.();
|
|
434
443
|
const { data, response, request_id } = await client.responses
|
|
435
444
|
.create(params, { signal: requestSignal })
|
|
436
445
|
.withResponse();
|
|
@@ -78,6 +78,21 @@ const ALLOWED_OPTION_KEYS: ReadonlySet<keyof SimpleStreamOptions> = new Set([
|
|
|
78
78
|
"preferWebsockets",
|
|
79
79
|
] as const satisfies readonly (keyof SimpleStreamOptions)[]);
|
|
80
80
|
|
|
81
|
+
const CREDENTIAL_HEADER_NAMES = new Set([
|
|
82
|
+
"authorization",
|
|
83
|
+
"proxy-authorization",
|
|
84
|
+
"cookie",
|
|
85
|
+
"set-cookie",
|
|
86
|
+
"api-key",
|
|
87
|
+
"x-api-key",
|
|
88
|
+
"x-goog-api-key",
|
|
89
|
+
"anthropic-api-key",
|
|
90
|
+
"x-auth-token",
|
|
91
|
+
"x-access-token",
|
|
92
|
+
"x-api-token",
|
|
93
|
+
"x-client-secret",
|
|
94
|
+
]);
|
|
95
|
+
|
|
81
96
|
// ---------------------------------------------------------------------------
|
|
82
97
|
// parseRequest
|
|
83
98
|
// ---------------------------------------------------------------------------
|
|
@@ -132,6 +147,15 @@ export function parseRequest(body: unknown, _headers?: Headers): PiNativeParsedR
|
|
|
132
147
|
for (const [k, v] of Object.entries(rawOpts)) {
|
|
133
148
|
if (v === undefined || v === null) continue;
|
|
134
149
|
if (!ALLOWED_OPTION_KEYS.has(k as keyof SimpleStreamOptions)) continue;
|
|
150
|
+
if (k === "headers" && typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
151
|
+
const safeHeaders: Record<string, string> = {};
|
|
152
|
+
for (const [name, value] of Object.entries(v)) {
|
|
153
|
+
if (CREDENTIAL_HEADER_NAMES.has(name.toLowerCase())) continue;
|
|
154
|
+
if (typeof value === "string") safeHeaders[name] = value;
|
|
155
|
+
}
|
|
156
|
+
optsBag[k] = safeHeaders;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
135
159
|
optsBag[k] = v;
|
|
136
160
|
}
|
|
137
161
|
}
|
|
@@ -44,16 +44,16 @@ export declare function resolveLazyStreamFirstEventFallbackMs(provider: string,
|
|
|
44
44
|
export declare const PROVIDER_RUNTIME_DESCRIPTORS: readonly ProviderRuntimeDescriptor<Api, unknown>[];
|
|
45
45
|
/** Return the lazy descriptor for a built-in API, if one is registered. */
|
|
46
46
|
export declare function getProviderRuntimeDescriptor<TApi extends Api>(api: TApi): ProviderRuntimeDescriptor<TApi, unknown> | undefined;
|
|
47
|
-
export declare const streamAnthropic: (model: Model<"anthropic-messages">, context: Context, options: OptionsForApi<"anthropic-messages"
|
|
48
|
-
export declare const streamAzureOpenAIResponses: (model: Model<"azure-openai-responses">, context: Context, options: OptionsForApi<"azure-openai-responses"
|
|
49
|
-
export declare const streamGoogle: (model: Model<"google-generative-ai">, context: Context, options: OptionsForApi<"google-generative-ai"
|
|
50
|
-
export declare const streamGoogleGeminiCli: (model: Model<"google-gemini-cli">, context: Context, options: OptionsForApi<"google-gemini-cli"
|
|
51
|
-
export declare const streamGoogleVertex: (model: Model<"google-vertex">, context: Context, options: OptionsForApi<"google-vertex"
|
|
52
|
-
export declare const streamOpenAICodexResponses: (model: Model<"openai-codex-responses">, context: Context, options: OptionsForApi<"openai-codex-responses"
|
|
53
|
-
export declare const streamOpenAICompletions: (model: Model<"openai-completions">, context: Context, options: OptionsForApi<"openai-completions"
|
|
54
|
-
export declare const streamOpenAIResponses: (model: Model<"openai-responses">, context: Context, options: OptionsForApi<"openai-responses"
|
|
55
|
-
export declare const streamCursor: (model: Model<"cursor-agent">, context: Context, options: OptionsForApi<"cursor-agent"
|
|
56
|
-
export declare const streamOllama: (model: Model<"ollama-chat">, context: Context, options: OptionsForApi<"ollama-chat"
|
|
57
|
-
export declare const streamBedrock: (model: Model<"bedrock-converse-stream">, context: Context, options: OptionsForApi<"bedrock-converse-stream"
|
|
58
|
-
export declare const streamKiroCodeWhisperer: (model: Model<"kiro-codewhisperer-stream">, context: Context, options: OptionsForApi<"kiro-codewhisperer-stream"
|
|
47
|
+
export declare const streamAnthropic: (model: Model<"anthropic-messages">, context: Context, options: OptionsForApi<"anthropic-messages">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
48
|
+
export declare const streamAzureOpenAIResponses: (model: Model<"azure-openai-responses">, context: Context, options: OptionsForApi<"azure-openai-responses">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
49
|
+
export declare const streamGoogle: (model: Model<"google-generative-ai">, context: Context, options: OptionsForApi<"google-generative-ai">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
50
|
+
export declare const streamGoogleGeminiCli: (model: Model<"google-gemini-cli">, context: Context, options: OptionsForApi<"google-gemini-cli">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
51
|
+
export declare const streamGoogleVertex: (model: Model<"google-vertex">, context: Context, options: OptionsForApi<"google-vertex">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
52
|
+
export declare const streamOpenAICodexResponses: (model: Model<"openai-codex-responses">, context: Context, options: OptionsForApi<"openai-codex-responses">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
53
|
+
export declare const streamOpenAICompletions: (model: Model<"openai-completions">, context: Context, options: OptionsForApi<"openai-completions">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
54
|
+
export declare const streamOpenAIResponses: (model: Model<"openai-responses">, context: Context, options: OptionsForApi<"openai-responses">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
55
|
+
export declare const streamCursor: (model: Model<"cursor-agent">, context: Context, options: OptionsForApi<"cursor-agent">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
56
|
+
export declare const streamOllama: (model: Model<"ollama-chat">, context: Context, options: OptionsForApi<"ollama-chat">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
57
|
+
export declare const streamBedrock: (model: Model<"bedrock-converse-stream">, context: Context, options: OptionsForApi<"bedrock-converse-stream">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
58
|
+
export declare const streamKiroCodeWhisperer: (model: Model<"kiro-codewhisperer-stream">, context: Context, options: OptionsForApi<"kiro-codewhisperer-stream">, onStreamCreated?: () => void) => EventStreamImpl;
|
|
59
59
|
export {};
|
|
@@ -252,10 +252,17 @@ function forwardStream<TApi extends Api>(
|
|
|
252
252
|
model: Model<TApi>,
|
|
253
253
|
options: OptionsForApi<TApi>,
|
|
254
254
|
abortTracker: AbortSourceTracker,
|
|
255
|
+
onStreamCreated?: () => void,
|
|
255
256
|
limits?: LazyStreamLimits,
|
|
256
257
|
): void {
|
|
257
258
|
(async () => {
|
|
258
259
|
try {
|
|
260
|
+
let admitted = false;
|
|
261
|
+
const markAdmission = (): void => {
|
|
262
|
+
if (admitted) return;
|
|
263
|
+
admitted = true;
|
|
264
|
+
onStreamCreated?.();
|
|
265
|
+
};
|
|
259
266
|
let watchedSource = source;
|
|
260
267
|
if (!limits?.providerOwnsWatchdog) {
|
|
261
268
|
const idleTimeoutMs = options.streamIdleTimeoutMs ?? getStreamIdleTimeoutMs(limits?.defaultIdleTimeoutMs);
|
|
@@ -286,6 +293,7 @@ function forwardStream<TApi extends Api>(
|
|
|
286
293
|
}
|
|
287
294
|
|
|
288
295
|
for await (const event of watchedSource) {
|
|
296
|
+
if (event.type !== "start") markAdmission();
|
|
289
297
|
target.push(event);
|
|
290
298
|
}
|
|
291
299
|
if (hasFinalResult(source)) {
|
|
@@ -337,8 +345,13 @@ function createLazyLoadErrorMessage<TApi extends Api>(
|
|
|
337
345
|
function createLazyStream<TApi extends Api>(
|
|
338
346
|
loadModule: () => Promise<LazyProviderModule<TApi>>,
|
|
339
347
|
limits?: LazyStreamLimits,
|
|
340
|
-
): (
|
|
341
|
-
|
|
348
|
+
): (
|
|
349
|
+
model: Model<TApi>,
|
|
350
|
+
context: Context,
|
|
351
|
+
options: OptionsForApi<TApi>,
|
|
352
|
+
onStreamCreated?: () => void,
|
|
353
|
+
) => EventStreamImpl {
|
|
354
|
+
return (model, context, options, onStreamCreated) => {
|
|
342
355
|
let abortTracker: AbortSourceTracker | undefined;
|
|
343
356
|
const outer = new EventStreamImpl(() =>
|
|
344
357
|
abortTracker?.abortLocally(new Error("Provider stream consumer stopped before completion")),
|
|
@@ -350,7 +363,7 @@ function createLazyStream<TApi extends Api>(
|
|
|
350
363
|
abortTracker = createAbortSourceTracker(streamOptions.signal);
|
|
351
364
|
const providerOptions = { ...streamOptions, signal: abortTracker.requestSignal } as OptionsForApi<TApi>;
|
|
352
365
|
const inner = module.stream(model, context, providerOptions);
|
|
353
|
-
forwardStream(outer, inner, model, streamOptions, abortTracker, limits);
|
|
366
|
+
forwardStream(outer, inner, model, streamOptions, abortTracker, onStreamCreated, limits);
|
|
354
367
|
})
|
|
355
368
|
.catch(error => {
|
|
356
369
|
const message = createLazyLoadErrorMessage(model, error);
|
package/src/stream.d.ts
CHANGED
|
@@ -29,12 +29,13 @@ export declare function listProvidersWithEnvKey(): string[];
|
|
|
29
29
|
* handling, so callers can append it unconditionally.
|
|
30
30
|
*/
|
|
31
31
|
export declare function formatProviderCredentialHint(provider: string): string;
|
|
32
|
+
export declare function streamFromLazyImport(createInner: () => Promise<AssistantMessageEventStream>, signal?: AbortSignal, onStreamCreated?: () => void): AssistantMessageEventStream;
|
|
32
33
|
/**
|
|
33
34
|
* Build an actionable "missing API key" error for a provider, used by the
|
|
34
35
|
* low-level `stream`/`complete` entry points (#755).
|
|
35
36
|
*/
|
|
36
37
|
export declare function formatMissingApiKeyError(provider: string): string;
|
|
37
|
-
export declare function stream<TApi extends Api>(model: Model<TApi>, context: Context, options?: OptionsForApi<TApi
|
|
38
|
+
export declare function stream<TApi extends Api>(model: Model<TApi>, context: Context, options?: OptionsForApi<TApi>, onStreamCreated?: () => void): AssistantMessageEventStream;
|
|
38
39
|
export declare function complete<TApi extends Api>(model: Model<TApi>, context: Context, options?: OptionsForApi<TApi>): Promise<AssistantMessage>;
|
|
39
40
|
export declare function streamSimple<TApi extends Api>(model: Model<TApi>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream;
|
|
40
41
|
export declare function completeSimple<TApi extends Api>(model: Model<TApi>, context: Context, options?: SimpleStreamOptions): Promise<AssistantMessage>;
|