@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.
Files changed (58) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/types/auth-broker/client.d.ts +6 -2
  3. package/dist/types/auth-broker/remote-store.d.ts +14 -2
  4. package/dist/types/auth-broker/types.d.ts +6 -0
  5. package/dist/types/auth-broker/wire-schemas.d.ts +19 -0
  6. package/dist/types/auth-gateway/server.d.ts +39 -5
  7. package/dist/types/auth-gateway/types.d.ts +16 -2
  8. package/dist/types/auth-storage.d.ts +92 -24
  9. package/dist/types/provider-models/openai-compat.d.ts +1 -0
  10. package/dist/types/providers/register-builtins.d.ts +12 -12
  11. package/dist/types/stream.d.ts +2 -1
  12. package/dist/types/types.d.ts +22 -2
  13. package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
  14. package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
  15. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  16. package/dist/types/utils/oauth/types.d.ts +1 -1
  17. package/dist/types/utils/retry.d.ts +2 -0
  18. package/package.json +3 -3
  19. package/src/auth-broker/client.ts +41 -13
  20. package/src/auth-broker/redact.ts +25 -1
  21. package/src/auth-broker/remote-store.ts +374 -115
  22. package/src/auth-broker/server.ts +131 -91
  23. package/src/auth-broker/types.ts +6 -0
  24. package/src/auth-broker/wire-schemas.ts +6 -0
  25. package/src/auth-gateway/server.ts +447 -79
  26. package/src/auth-gateway/types.ts +28 -2
  27. package/src/auth-storage.ts +658 -154
  28. package/src/cli.ts +1 -0
  29. package/src/models.json +1023 -0
  30. package/src/provider-models/descriptors.ts +3 -1
  31. package/src/provider-models/openai-compat.ts +41 -1
  32. package/src/providers/anthropic.ts +7 -1
  33. package/src/providers/azure-openai-responses.ts +4 -1
  34. package/src/providers/cursor.ts +256 -101
  35. package/src/providers/gitlab-duo.ts +18 -1
  36. package/src/providers/google-gemini-cli.ts +3 -0
  37. package/src/providers/google-shared.ts +3 -0
  38. package/src/providers/kiro-codewhisperer.ts +24 -8
  39. package/src/providers/ollama.ts +3 -0
  40. package/src/providers/openai-codex-responses.ts +20 -6
  41. package/src/providers/openai-completions.ts +11 -1
  42. package/src/providers/openai-responses.ts +10 -1
  43. package/src/providers/pi-native-client.ts +1 -0
  44. package/src/providers/pi-native-server.ts +24 -0
  45. package/src/providers/register-builtins.d.ts +12 -12
  46. package/src/providers/register-builtins.ts +16 -3
  47. package/src/stream.d.ts +2 -1
  48. package/src/stream.ts +175 -67
  49. package/src/types.d.ts +22 -2
  50. package/src/types.ts +27 -1
  51. package/src/utils/oauth/api-key-login.ts +13 -2
  52. package/src/utils/oauth/api-key-validation.ts +242 -41
  53. package/src/utils/oauth/commandcode.ts +17 -0
  54. package/src/utils/oauth/index.ts +20 -5
  55. package/src/utils/oauth/types.d.ts +1 -1
  56. package/src/utils/oauth/types.ts +1 -0
  57. package/src/utils/retry.d.ts +2 -0
  58. 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
- _model: Model<"kiro-codewhisperer-stream">,
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(msg: Context["messages"][number], systemPrompt?: string): WireHistoryMessage {
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(msg: Context["messages"][number], systemPrompt?: string): WireUserMessage {
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
 
@@ -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 (hasNetworkErrorFinishReason(chunk) && !options?.fallbackManaged) {
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
- { provider: model.provider, signal: requestSignal, fallbackManaged: options?.fallbackManaged },
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();
@@ -37,6 +37,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream";
37
37
  const NON_WIRE_KEYS = new Set<keyof SimpleStreamOptions>([
38
38
  "signal",
39
39
  "apiKey",
40
+ "onStreamCreated",
40
41
  "fetch",
41
42
  "onPayload",
42
43
  "onResponse",
@@ -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">) => EventStreamImpl;
48
- export declare const streamAzureOpenAIResponses: (model: Model<"azure-openai-responses">, context: Context, options: OptionsForApi<"azure-openai-responses">) => EventStreamImpl;
49
- export declare const streamGoogle: (model: Model<"google-generative-ai">, context: Context, options: OptionsForApi<"google-generative-ai">) => EventStreamImpl;
50
- export declare const streamGoogleGeminiCli: (model: Model<"google-gemini-cli">, context: Context, options: OptionsForApi<"google-gemini-cli">) => EventStreamImpl;
51
- export declare const streamGoogleVertex: (model: Model<"google-vertex">, context: Context, options: OptionsForApi<"google-vertex">) => EventStreamImpl;
52
- export declare const streamOpenAICodexResponses: (model: Model<"openai-codex-responses">, context: Context, options: OptionsForApi<"openai-codex-responses">) => EventStreamImpl;
53
- export declare const streamOpenAICompletions: (model: Model<"openai-completions">, context: Context, options: OptionsForApi<"openai-completions">) => EventStreamImpl;
54
- export declare const streamOpenAIResponses: (model: Model<"openai-responses">, context: Context, options: OptionsForApi<"openai-responses">) => EventStreamImpl;
55
- export declare const streamCursor: (model: Model<"cursor-agent">, context: Context, options: OptionsForApi<"cursor-agent">) => EventStreamImpl;
56
- export declare const streamOllama: (model: Model<"ollama-chat">, context: Context, options: OptionsForApi<"ollama-chat">) => EventStreamImpl;
57
- export declare const streamBedrock: (model: Model<"bedrock-converse-stream">, context: Context, options: OptionsForApi<"bedrock-converse-stream">) => EventStreamImpl;
58
- export declare const streamKiroCodeWhisperer: (model: Model<"kiro-codewhisperer-stream">, context: Context, options: OptionsForApi<"kiro-codewhisperer-stream">) => EventStreamImpl;
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
- ): (model: Model<TApi>, context: Context, options: OptionsForApi<TApi>) => EventStreamImpl {
341
- return (model, context, options) => {
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>): AssistantMessageEventStream;
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>;