@gajae-code/ai 0.15.5 → 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 +15 -0
- 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 +92 -24
- package/dist/types/provider-models/openai-compat.d.ts +1 -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 +22 -2
- 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/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 +658 -154
- package/src/cli.ts +1 -0
- package/src/models.json +1023 -0
- package/src/provider-models/descriptors.ts +3 -1
- package/src/provider-models/openai-compat.ts +41 -1
- 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-codewhisperer.ts +24 -8
- package/src/providers/ollama.ts +3 -0
- package/src/providers/openai-codex-responses.ts +20 -6
- package/src/providers/openai-completions.ts +11 -1
- 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 +175 -67
- package/src/types.d.ts +22 -2
- package/src/types.ts +27 -1
- 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
|
@@ -921,6 +921,7 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
|
|
|
921
921
|
options.toolChoice !== "auto" &&
|
|
922
922
|
options.toolChoice !== "none";
|
|
923
923
|
const fetchImpl = plan.fetch ?? options?.fetch ?? (globalThis.fetch.bind(globalThis) as FetchImpl);
|
|
924
|
+
options?.onStreamCreated?.();
|
|
924
925
|
let response = await fetchImpl(plan.url, {
|
|
925
926
|
method: "POST",
|
|
926
927
|
headers: { ...plan.headers, "Content-Type": "application/json", Accept: "text/event-stream" },
|
|
@@ -935,6 +936,7 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
|
|
|
935
936
|
);
|
|
936
937
|
if (
|
|
937
938
|
!options?.fallbackManaged &&
|
|
939
|
+
!options?.disableProviderRetries &&
|
|
938
940
|
firstTokenTime === undefined &&
|
|
939
941
|
isForcedToolChoiceUnsupportedError(error, true)
|
|
940
942
|
) {
|
|
@@ -957,6 +959,7 @@ export function streamGoogleGenAI<T extends "google-generative-ai" | "google-ver
|
|
|
957
959
|
params = retryParams;
|
|
958
960
|
rawRequestDump = { ...rawRequestDump, body: params };
|
|
959
961
|
wireBody = paramsToWireBody(params);
|
|
962
|
+
options?.onStreamCreated?.();
|
|
960
963
|
response = await fetchImpl(plan.url, {
|
|
961
964
|
method: "POST",
|
|
962
965
|
headers: { ...plan.headers, "Content-Type": "application/json", Accept: "text/event-stream" },
|
|
@@ -29,7 +29,7 @@ import { transportFailureFacts } from "../utils/fallback-transport";
|
|
|
29
29
|
import { withHttpStatus } from "../utils/http-inspector";
|
|
30
30
|
import { captureUnicodeEscapeEvidence } from "../utils/json-parse";
|
|
31
31
|
import { decodeEventStream } from "./aws-eventstream";
|
|
32
|
-
import { isKiroApiKey, streamKiroApiKey } from "./kiro-api-key";
|
|
32
|
+
import { isKiroApiKey, streamKiroApiKey, toKiroModelId } from "./kiro-api-key";
|
|
33
33
|
|
|
34
34
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
35
|
// Provider options
|
|
@@ -67,6 +67,7 @@ interface WireToolResult {
|
|
|
67
67
|
interface WireUserMessage {
|
|
68
68
|
userInputMessage: {
|
|
69
69
|
content: string;
|
|
70
|
+
modelId?: string;
|
|
70
71
|
userInputMessageContext?: {
|
|
71
72
|
tools?: { tools: WireToolSpec[] };
|
|
72
73
|
toolResults?: { toolResults: WireToolResult[][] };
|
|
@@ -353,7 +354,7 @@ export const streamKiroCodeWhisperer: StreamFunction<"kiro-codewhisperer-stream"
|
|
|
353
354
|
|
|
354
355
|
function buildConversationState(
|
|
355
356
|
context: Context,
|
|
356
|
-
|
|
357
|
+
model: Model<"kiro-codewhisperer-stream">,
|
|
357
358
|
options: KiroCodeWhispererOptions,
|
|
358
359
|
): ConversationState {
|
|
359
360
|
const messages = context.messages;
|
|
@@ -361,18 +362,24 @@ function buildConversationState(
|
|
|
361
362
|
throw new Error("Kiro CodeWhisperer requires at least one message");
|
|
362
363
|
}
|
|
363
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
|
+
|
|
364
371
|
// Build history from all messages except the last
|
|
365
372
|
const history: WireHistoryMessage[] = [];
|
|
366
373
|
const systemPrompt = context.systemPrompt?.join("\n") ?? "";
|
|
367
374
|
|
|
368
375
|
for (let i = 0; i < messages.length - 1; i++) {
|
|
369
376
|
const msg = messages[i];
|
|
370
|
-
history.push(convertToWireMessage(msg, i === 0 ? systemPrompt : undefined));
|
|
377
|
+
history.push(convertToWireMessage(msg, modelId, i === 0 ? systemPrompt : undefined));
|
|
371
378
|
}
|
|
372
379
|
|
|
373
380
|
// Convert the last message as currentMessage
|
|
374
381
|
const lastMsg = messages[messages.length - 1];
|
|
375
|
-
const currentMessage = convertToWireUserMessage(lastMsg, systemPrompt);
|
|
382
|
+
const currentMessage = convertToWireUserMessage(lastMsg, modelId, systemPrompt);
|
|
376
383
|
|
|
377
384
|
// Add tools to the current message context
|
|
378
385
|
if (context.tools && context.tools.length > 0) {
|
|
@@ -392,12 +399,16 @@ function buildConversationState(
|
|
|
392
399
|
};
|
|
393
400
|
}
|
|
394
401
|
|
|
395
|
-
function convertToWireMessage(
|
|
402
|
+
function convertToWireMessage(
|
|
403
|
+
msg: Context["messages"][number],
|
|
404
|
+
modelId: string,
|
|
405
|
+
systemPrompt?: string,
|
|
406
|
+
): WireHistoryMessage {
|
|
396
407
|
if (msg.role === "user") {
|
|
397
|
-
return convertToWireUserMessage(msg, systemPrompt);
|
|
408
|
+
return convertToWireUserMessage(msg, modelId, systemPrompt);
|
|
398
409
|
}
|
|
399
410
|
if (msg.role === "toolResult") {
|
|
400
|
-
return convertToWireUserMessage(msg, systemPrompt);
|
|
411
|
+
return convertToWireUserMessage(msg, modelId, systemPrompt);
|
|
401
412
|
}
|
|
402
413
|
// assistant → assistant response
|
|
403
414
|
const textParts: string[] = [];
|
|
@@ -417,7 +428,11 @@ function convertToWireMessage(msg: Context["messages"][number], systemPrompt?: s
|
|
|
417
428
|
};
|
|
418
429
|
}
|
|
419
430
|
|
|
420
|
-
function convertToWireUserMessage(
|
|
431
|
+
function convertToWireUserMessage(
|
|
432
|
+
msg: Context["messages"][number],
|
|
433
|
+
modelId: string,
|
|
434
|
+
systemPrompt?: string,
|
|
435
|
+
): WireUserMessage {
|
|
421
436
|
let content = extractTextContent(msg);
|
|
422
437
|
if (systemPrompt) {
|
|
423
438
|
content = `${systemPrompt}\n\n${content}`;
|
|
@@ -426,6 +441,7 @@ function convertToWireUserMessage(msg: Context["messages"][number], systemPrompt
|
|
|
426
441
|
const userMsg: WireUserMessage = {
|
|
427
442
|
userInputMessage: {
|
|
428
443
|
content,
|
|
444
|
+
modelId,
|
|
429
445
|
},
|
|
430
446
|
};
|
|
431
447
|
|
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
|
}
|
|
@@ -1739,6 +1744,7 @@ async function tryRetryWithoutForcedToolChoice(
|
|
|
1739
1744
|
): Promise<boolean> {
|
|
1740
1745
|
if (
|
|
1741
1746
|
context.options?.fallbackManaged ||
|
|
1747
|
+
context.options?.disableProviderRetries ||
|
|
1742
1748
|
runtime.toolChoiceFallbackAttempted ||
|
|
1743
1749
|
context.output.content.length > 0 ||
|
|
1744
1750
|
context.firstTokenTime !== undefined ||
|
|
@@ -1842,7 +1848,8 @@ async function tryReconnectCodexWebSocketOnConnectionLimit(
|
|
|
1842
1848
|
!websocketState ||
|
|
1843
1849
|
runtime.transport !== "websocket" ||
|
|
1844
1850
|
context.options?.signal?.aborted ||
|
|
1845
|
-
context.options?.fallbackManaged
|
|
1851
|
+
context.options?.fallbackManaged ||
|
|
1852
|
+
context.options?.disableProviderRetries
|
|
1846
1853
|
) {
|
|
1847
1854
|
return false;
|
|
1848
1855
|
}
|
|
@@ -1905,6 +1912,7 @@ async function tryRecoverCodexPreviousResponseNotFound(
|
|
|
1905
1912
|
runtime.previousResponseRecoveryAttempted ||
|
|
1906
1913
|
!websocketState ||
|
|
1907
1914
|
context.options?.fallbackManaged ||
|
|
1915
|
+
context.options?.disableProviderRetries ||
|
|
1908
1916
|
runtime.transport !== "websocket" ||
|
|
1909
1917
|
context.output.content.length > 0 ||
|
|
1910
1918
|
context.options?.signal?.aborted ||
|
|
@@ -1945,7 +1953,8 @@ async function tryReplayWebsocketFailureOverSse(
|
|
|
1945
1953
|
runtime.canSafelyReplayWebsocketOverSse &&
|
|
1946
1954
|
!runtime.sawTerminalEvent &&
|
|
1947
1955
|
!context.options?.signal?.aborted &&
|
|
1948
|
-
!context.options?.fallbackManaged
|
|
1956
|
+
!context.options?.fallbackManaged &&
|
|
1957
|
+
!context.options?.disableProviderRetries;
|
|
1949
1958
|
if (!canReplay) return false;
|
|
1950
1959
|
|
|
1951
1960
|
const state = websocketState;
|
|
@@ -1999,7 +2008,8 @@ async function tryRetryCodexProviderError(
|
|
|
1999
2008
|
context.output.content.length > 0 ||
|
|
2000
2009
|
runtime.providerRetryAttempt >= resolveRetryBudget(context.options?.streamMaxRetries, CODEX_MAX_RETRIES) ||
|
|
2001
2010
|
context.options?.signal?.aborted ||
|
|
2002
|
-
context.options?.fallbackManaged
|
|
2011
|
+
context.options?.fallbackManaged ||
|
|
2012
|
+
context.options?.disableProviderRetries
|
|
2003
2013
|
) {
|
|
2004
2014
|
return false;
|
|
2005
2015
|
}
|
|
@@ -2119,7 +2129,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|
|
2119
2129
|
try {
|
|
2120
2130
|
initialTransport = await openInitialCodexEventStream(model, streamOptions, requestSetup, requestContext);
|
|
2121
2131
|
} catch (error) {
|
|
2122
|
-
if (streamOptions.fallbackManaged) throw error;
|
|
2132
|
+
if (streamOptions.fallbackManaged || streamOptions.disableProviderRetries) throw error;
|
|
2123
2133
|
initialTransport = await retryCodexInitialTransportWithoutToolChoice(
|
|
2124
2134
|
model,
|
|
2125
2135
|
streamOptions,
|
|
@@ -2633,6 +2643,7 @@ class CodexWebSocketConnection {
|
|
|
2633
2643
|
signal?: AbortSignal,
|
|
2634
2644
|
firstEventTimeoutMs?: number,
|
|
2635
2645
|
idleTimeoutMs = this.#idleTimeoutMs,
|
|
2646
|
+
onStreamCreated?: () => void,
|
|
2636
2647
|
): AsyncGenerator<Record<string, unknown>> {
|
|
2637
2648
|
if (!this.#socket || this.#socket.readyState !== WebSocket.OPEN) {
|
|
2638
2649
|
throw createCodexWebSocketTransportError("websocket connection is unavailable");
|
|
@@ -2654,6 +2665,7 @@ class CodexWebSocketConnection {
|
|
|
2654
2665
|
}
|
|
2655
2666
|
|
|
2656
2667
|
try {
|
|
2668
|
+
onStreamCreated?.();
|
|
2657
2669
|
this.#socket.send(JSON.stringify(request));
|
|
2658
2670
|
let sawFirstProgress = false;
|
|
2659
2671
|
const startedAt = Date.now();
|
|
@@ -2795,7 +2807,7 @@ async function openCodexSseEventStream(
|
|
|
2795
2807
|
signal?: AbortSignal,
|
|
2796
2808
|
onSseEvent?: OpenAICodexResponsesOptions["onSseEvent"],
|
|
2797
2809
|
fetchOverride?: FetchImpl,
|
|
2798
|
-
options?: Pick<OpenAICodexResponsesOptions, "requestMaxRetries">,
|
|
2810
|
+
options?: Pick<OpenAICodexResponsesOptions, "requestMaxRetries" | "onStreamCreated">,
|
|
2799
2811
|
): Promise<AsyncGenerator<Record<string, unknown>>> {
|
|
2800
2812
|
const headers = createCodexHeaders(requestHeaders, accountId, apiKey, sessionId, "sse", state);
|
|
2801
2813
|
logCodexDebug("codex request", {
|
|
@@ -2805,6 +2817,7 @@ async function openCodexSseEventStream(
|
|
|
2805
2817
|
sentTurnStateHeader: headers.has(X_CODEX_TURN_STATE_HEADER),
|
|
2806
2818
|
sentModelsEtagHeader: headers.has(X_MODELS_ETAG_HEADER),
|
|
2807
2819
|
});
|
|
2820
|
+
options?.onStreamCreated?.();
|
|
2808
2821
|
const response = await fetchWithRetry(url, {
|
|
2809
2822
|
method: "POST",
|
|
2810
2823
|
headers,
|
|
@@ -2845,7 +2858,7 @@ async function openCodexWebSocketEventStream(
|
|
|
2845
2858
|
request: Record<string, unknown>,
|
|
2846
2859
|
state: CodexWebSocketSessionState,
|
|
2847
2860
|
signal?: AbortSignal,
|
|
2848
|
-
options?: Pick<OpenAICodexResponsesOptions, "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs">,
|
|
2861
|
+
options?: Pick<OpenAICodexResponsesOptions, "streamFirstEventTimeoutMs" | "streamIdleTimeoutMs" | "onStreamCreated">,
|
|
2849
2862
|
firstEventTimeoutMs?: number,
|
|
2850
2863
|
): Promise<AsyncGenerator<Record<string, unknown>>> {
|
|
2851
2864
|
const connection = await getOrCreateCodexWebSocketConnection(state, url, headers, signal, options);
|
|
@@ -2854,6 +2867,7 @@ async function openCodexWebSocketEventStream(
|
|
|
2854
2867
|
signal,
|
|
2855
2868
|
firstEventTimeoutMs,
|
|
2856
2869
|
getCodexWebSocketIdleTimeoutMs(options?.streamIdleTimeoutMs),
|
|
2870
|
+
options?.onStreamCreated,
|
|
2857
2871
|
);
|
|
2858
2872
|
}
|
|
2859
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
|
}
|
|
@@ -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>;
|