@gajae-code/ai 0.15.5 → 0.16.0

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 (88) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/types/adapter-internals/aws-region.d.ts +7 -0
  3. package/dist/types/auth-broker/client.d.ts +6 -2
  4. package/dist/types/auth-broker/remote-store.d.ts +14 -2
  5. package/dist/types/auth-broker/types.d.ts +6 -0
  6. package/dist/types/auth-broker/wire-schemas.d.ts +19 -0
  7. package/dist/types/auth-gateway/server.d.ts +39 -5
  8. package/dist/types/auth-gateway/types.d.ts +16 -2
  9. package/dist/types/auth-storage.d.ts +92 -24
  10. package/dist/types/core.d.ts +1 -0
  11. package/dist/types/index.d.ts +1 -0
  12. package/dist/types/provider-models/openai-compat.d.ts +1 -0
  13. package/dist/types/providers/anthropic.d.ts +1 -1
  14. package/dist/types/providers/google-gemini-headers.d.ts +1 -1
  15. package/dist/types/providers/openai-codex-responses.d.ts +6 -0
  16. package/dist/types/providers/register-builtins.d.ts +12 -12
  17. package/dist/types/stream.d.ts +2 -1
  18. package/dist/types/types.d.ts +22 -2
  19. package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
  20. package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
  21. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  22. package/dist/types/utils/oauth/types.d.ts +1 -1
  23. package/dist/types/utils/retry.d.ts +2 -0
  24. package/dist/types/utils/schema/normalize.d.ts +0 -5
  25. package/dist/types/utils/sqlite-errors.d.ts +4 -0
  26. package/package.json +3 -3
  27. package/src/adapter-internals/aws-region.d.ts +7 -0
  28. package/src/adapter-internals/aws-region.ts +14 -0
  29. package/src/auth-broker/client.ts +41 -13
  30. package/src/auth-broker/redact.ts +25 -1
  31. package/src/auth-broker/remote-store.ts +374 -115
  32. package/src/auth-broker/server.ts +131 -91
  33. package/src/auth-broker/types.ts +6 -0
  34. package/src/auth-broker/wire-schemas.ts +6 -0
  35. package/src/auth-gateway/server.ts +447 -79
  36. package/src/auth-gateway/types.ts +28 -2
  37. package/src/auth-storage.ts +672 -168
  38. package/src/cli.ts +1 -0
  39. package/src/core.ts +1 -0
  40. package/src/index.ts +1 -0
  41. package/src/model-thinking.ts +8 -0
  42. package/src/models.json +1224 -3
  43. package/src/provider-models/descriptors.ts +3 -1
  44. package/src/provider-models/openai-compat.ts +102 -1
  45. package/src/providers/amazon-bedrock.ts +5 -1
  46. package/src/providers/anthropic.d.ts +1 -1
  47. package/src/providers/anthropic.ts +8 -2
  48. package/src/providers/aws-credentials.ts +6 -0
  49. package/src/providers/azure-openai-responses.ts +4 -1
  50. package/src/providers/cursor.ts +256 -101
  51. package/src/providers/gitlab-duo.ts +18 -1
  52. package/src/providers/google-gemini-cli.ts +3 -0
  53. package/src/providers/google-gemini-headers.d.ts +1 -1
  54. package/src/providers/google-gemini-headers.ts +1 -1
  55. package/src/providers/google-shared.ts +3 -0
  56. package/src/providers/kiro-api-key.ts +33 -8
  57. package/src/providers/kiro-codewhisperer.ts +28 -9
  58. package/src/providers/ollama.ts +3 -0
  59. package/src/providers/openai-codex-responses.d.ts +6 -0
  60. package/src/providers/openai-codex-responses.ts +37 -8
  61. package/src/providers/openai-completions.ts +11 -1
  62. package/src/providers/openai-responses.ts +10 -1
  63. package/src/providers/pi-native-client.ts +25 -1
  64. package/src/providers/pi-native-server.ts +24 -0
  65. package/src/providers/register-builtins.d.ts +12 -12
  66. package/src/providers/register-builtins.ts +16 -3
  67. package/src/stream.d.ts +2 -1
  68. package/src/stream.ts +175 -67
  69. package/src/types.d.ts +22 -2
  70. package/src/types.ts +27 -1
  71. package/src/utils/oauth/api-key-login.ts +13 -2
  72. package/src/utils/oauth/api-key-validation.ts +242 -41
  73. package/src/utils/oauth/commandcode.ts +17 -0
  74. package/src/utils/oauth/index.ts +20 -5
  75. package/src/utils/oauth/kiro.ts +91 -22
  76. package/src/utils/oauth/types.d.ts +1 -1
  77. package/src/utils/oauth/types.ts +1 -0
  78. package/src/utils/retry.d.ts +2 -0
  79. package/src/utils/retry.ts +15 -2
  80. package/src/utils/schema/dereference.ts +169 -49
  81. package/src/utils/schema/draft.ts +46 -23
  82. package/src/utils/schema/normalize.d.ts +0 -5
  83. package/src/utils/schema/normalize.ts +396 -119
  84. package/src/utils/schema/types.ts +3 -1
  85. package/src/utils/schema/zod-decontaminate.ts +83 -29
  86. package/src/utils/sqlite-errors.d.ts +4 -0
  87. package/src/utils/sqlite-errors.ts +13 -0
  88. package/src/utils/tool-choice-capability.ts +2 -3
@@ -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>;
package/src/stream.ts CHANGED
@@ -67,6 +67,7 @@ import type {
67
67
  Api,
68
68
  AssistantMessage,
69
69
  AssistantMessageEvent,
70
+ AuthRetryCredential,
70
71
  Context,
71
72
  Model,
72
73
  OptionsForApi,
@@ -115,6 +116,7 @@ const serviceProviderMap: Record<string, KeyResolver> = {
115
116
  mistral: "MISTRAL_API_KEY",
116
117
  minimax: "MINIMAX_API_KEY",
117
118
  "minimax-code": "MINIMAX_CODE_API_KEY",
119
+ "commandcode-goat": "CMD_API_KEY",
118
120
  "minimax-code-cn": "MINIMAX_CODE_CN_API_KEY",
119
121
  "opencode-go": "OPENCODE_API_KEY",
120
122
  "opencode-zen": "OPENCODE_API_KEY",
@@ -221,6 +223,7 @@ export function listProvidersWithEnvKey(): string[] {
221
223
  * Used to give OpenCode users an accurate headless auth diagnostic (#755).
222
224
  */
223
225
  const OPENCODE_SUBSCRIPTION_PROVIDERS = new Set(["opencode-go", "opencode-zen"]);
226
+ const API_KEY_LOGIN_PROVIDERS = new Set(["commandcode-goat"]);
224
227
 
225
228
  /**
226
229
  * Provider-specific credential guidance appended to "no credential" errors.
@@ -239,12 +242,16 @@ export function formatProviderCredentialHint(provider: string): string {
239
242
  const resolver = serviceProviderMap[provider];
240
243
  const envVar = typeof resolver === "string" ? resolver : undefined;
241
244
  const isOpenCodeSubscription = OPENCODE_SUBSCRIPTION_PROVIDERS.has(provider);
245
+ const isApiKeyLoginProvider = API_KEY_LOGIN_PROVIDERS.has(provider);
242
246
  const parts: string[] = [];
243
247
  if (isOpenCodeSubscription) {
244
248
  parts.push(
245
249
  "OpenCode subscriptions authenticate with an API key (created at https://opencode.ai/auth), not a separate session/OAuth token.",
246
250
  );
247
251
  }
252
+ if (isApiKeyLoginProvider) {
253
+ parts.push("Command Code GOAT uses an API key from https://commandcode.ai/studio/#api-keys.");
254
+ }
248
255
  if (provider === "jetbrains-junie") {
249
256
  parts.push(
250
257
  "JetBrains AI (Junie) authenticates with an access token generated at https://junie.jetbrains.com/cli; there is no OAuth login for this provider.",
@@ -267,10 +274,18 @@ function pipeAssistantStream(
267
274
  outer: AssistantMessageEventStream,
268
275
  inner: AssistantMessageEventStream,
269
276
  signal?: AbortSignal,
277
+ onStreamCreated?: () => void,
270
278
  ): void {
271
279
  void (async () => {
272
280
  try {
281
+ let admitted = false;
282
+ const markAdmission = (): void => {
283
+ if (admitted) return;
284
+ admitted = true;
285
+ onStreamCreated?.();
286
+ };
273
287
  for await (const event of inner) {
288
+ if (event.type !== "start") markAdmission();
274
289
  outer.push(event);
275
290
  // The inner provider stream owns abort semantics (it receives the
276
291
  // same signal), but stop forwarding as soon as the consumer
@@ -288,14 +303,16 @@ function pipeAssistantStream(
288
303
  })();
289
304
  }
290
305
 
291
- function streamFromLazyImport(
306
+ export function streamFromLazyImport(
292
307
  createInner: () => Promise<AssistantMessageEventStream>,
293
308
  signal?: AbortSignal,
309
+ onStreamCreated?: () => void,
294
310
  ): AssistantMessageEventStream {
295
311
  const outer = new AssistantMessageEventStream();
296
312
  void (async () => {
297
313
  try {
298
- pipeAssistantStream(outer, await createInner(), signal);
314
+ const inner = await createInner();
315
+ pipeAssistantStream(outer, inner, signal, onStreamCreated);
299
316
  } catch (error) {
300
317
  outer.fail(error);
301
318
  }
@@ -317,6 +334,7 @@ export function stream<TApi extends Api>(
317
334
  model: Model<TApi>,
318
335
  context: Context,
319
336
  options?: OptionsForApi<TApi>,
337
+ onStreamCreated?: () => void,
320
338
  ): AssistantMessageEventStream {
321
339
  if (!hasValidatedManagedAttempt(options)) assertManagedAttempt(options);
322
340
  if (options?.fallbackManaged) {
@@ -350,6 +368,7 @@ export function stream<TApi extends Api>(
350
368
  return streamGitLabDuo(model, context, adapterOptions);
351
369
  },
352
370
  (options as StreamOptions | undefined)?.signal,
371
+ onStreamCreated,
353
372
  );
354
373
  }
355
374
 
@@ -362,15 +381,22 @@ export function stream<TApi extends Api>(
362
381
  isProviderSafetyStopModelTrusted(model)
363
382
  ? withProviderSafetyStopAdapterInvocation(vertexOptions)
364
383
  : vertexOptions,
384
+ onStreamCreated,
365
385
  );
366
386
  } else if (model.api === "bedrock-converse-stream") {
367
387
  // Bedrock doesn't have any API keys instead it sources credentials from standard AWS env variables or from given AWS profile.
368
- return streamBedrock(model as Model<"bedrock-converse-stream">, context, (options || {}) as BedrockOptions);
388
+ return streamBedrock(
389
+ model as Model<"bedrock-converse-stream">,
390
+ context,
391
+ (options || {}) as BedrockOptions,
392
+ onStreamCreated,
393
+ );
369
394
  } else if (model.api === "kiro-codewhisperer-stream") {
370
395
  return streamKiroCodeWhisperer(
371
396
  model as Model<"kiro-codewhisperer-stream">,
372
397
  context,
373
398
  (options || {}) as KiroCodeWhispererOptions,
399
+ onStreamCreated,
374
400
  );
375
401
  }
376
402
 
@@ -387,23 +413,39 @@ export function stream<TApi extends Api>(
387
413
  switch (api) {
388
414
  case "anthropic-messages": {
389
415
  const anthropicOptions = adapterProviderOptions as AnthropicOptions;
390
- return streamAnthropic(model as Model<"anthropic-messages">, context, {
391
- ...anthropicOptions,
392
- isOAuth: anthropicOptions.isOAuth ?? model.isOAuth,
393
- });
416
+ return streamAnthropic(
417
+ model as Model<"anthropic-messages">,
418
+ context,
419
+ {
420
+ ...anthropicOptions,
421
+ isOAuth: anthropicOptions.isOAuth ?? model.isOAuth,
422
+ },
423
+ onStreamCreated,
424
+ );
394
425
  }
395
426
 
396
427
  case "openai-completions":
397
- return streamOpenAICompletions(model as Model<"openai-completions">, context, adapterProviderOptions as any);
428
+ return streamOpenAICompletions(
429
+ model as Model<"openai-completions">,
430
+ context,
431
+ adapterProviderOptions as any,
432
+ onStreamCreated,
433
+ );
398
434
 
399
435
  case "openai-responses":
400
- return streamOpenAIResponses(model as Model<"openai-responses">, context, adapterProviderOptions as any);
436
+ return streamOpenAIResponses(
437
+ model as Model<"openai-responses">,
438
+ context,
439
+ adapterProviderOptions as any,
440
+ onStreamCreated,
441
+ );
401
442
 
402
443
  case "azure-openai-responses":
403
444
  return streamAzureOpenAIResponses(
404
445
  model as Model<"azure-openai-responses">,
405
446
  context,
406
447
  adapterProviderOptions as any,
448
+ onStreamCreated,
407
449
  );
408
450
 
409
451
  case "openai-codex-responses":
@@ -411,23 +453,35 @@ export function stream<TApi extends Api>(
411
453
  model as Model<"openai-codex-responses">,
412
454
  context,
413
455
  adapterProviderOptions as any,
456
+ onStreamCreated,
414
457
  );
415
458
 
416
459
  case "google-generative-ai":
417
- return streamGoogle(model as Model<"google-generative-ai">, context, adapterProviderOptions);
460
+ return streamGoogle(model as Model<"google-generative-ai">, context, adapterProviderOptions, onStreamCreated);
418
461
 
419
462
  case "google-gemini-cli":
420
463
  return streamGoogleGeminiCli(
421
464
  model as Model<"google-gemini-cli">,
422
465
  context,
423
466
  adapterProviderOptions as GoogleGeminiCliOptions,
467
+ onStreamCreated,
424
468
  );
425
469
 
426
470
  case "ollama-chat":
427
- return streamOllama(model as Model<"ollama-chat">, context, adapterProviderOptions as OllamaChatOptions);
471
+ return streamOllama(
472
+ model as Model<"ollama-chat">,
473
+ context,
474
+ adapterProviderOptions as OllamaChatOptions,
475
+ onStreamCreated,
476
+ );
428
477
 
429
478
  case "cursor-agent":
430
- return streamCursor(model as Model<"cursor-agent">, context, adapterProviderOptions as CursorOptions);
479
+ return streamCursor(
480
+ model as Model<"cursor-agent">,
481
+ context,
482
+ adapterProviderOptions as CursorOptions,
483
+ onStreamCreated,
484
+ );
431
485
 
432
486
  default:
433
487
  throw new Error(`Unhandled API: ${api}`);
@@ -539,9 +593,19 @@ export function streamSimple<TApi extends Api>(
539
593
  ? AbortSignal.any([options.signal, consumerAbortController.signal])
540
594
  : consumerAbortController.signal;
541
595
  const onAuthError = options!.onAuthError!;
542
- const runAttempt = async (apiKey: string, captureAuthFailure: boolean): Promise<AuthRetryFailure | undefined> => {
596
+ const runAttempt = async (
597
+ apiKey: string,
598
+ captureAuthFailure: boolean,
599
+ onStreamCreated?: () => void,
600
+ ): Promise<AuthRetryFailure | undefined> => {
543
601
  const bufferedEvents: AssistantMessageEvent[] = [];
544
602
  let emittedReplayUnsafeEvent = false;
603
+ let admitted = false;
604
+ const markAdmission = (): void => {
605
+ if (admitted) return;
606
+ admitted = true;
607
+ onStreamCreated?.();
608
+ };
545
609
  const flushBuffered = (): void => {
546
610
  emitBufferedEvents(outer, bufferedEvents);
547
611
  bufferedEvents.length = 0;
@@ -552,6 +616,7 @@ export function streamSimple<TApi extends Api>(
552
616
  ...options,
553
617
  apiKey,
554
618
  onAuthError: undefined,
619
+ onStreamCreated: markAdmission,
555
620
  signal: requestSignal,
556
621
  });
557
622
  for await (const event of inner) {
@@ -587,6 +652,12 @@ export function streamSimple<TApi extends Api>(
587
652
  }
588
653
  flushBuffered();
589
654
  outer.fail(error);
655
+ } finally {
656
+ // A lazy import or a synchronous provider failure can happen before
657
+ // the admission hook is reached. Release that attempt's lease in
658
+ // the failure path without extending a successful request's lease
659
+ // through the response lifetime.
660
+ if (!admitted) markAdmission();
590
661
  }
591
662
  return undefined;
592
663
  };
@@ -600,19 +671,22 @@ export function streamSimple<TApi extends Api>(
600
671
  };
601
672
 
602
673
  void (async () => {
603
- const failure = await runAttempt(retryApiKey, true);
674
+ const failure = await runAttempt(retryApiKey, true, options?.onStreamCreated);
604
675
  if (!failure) return;
605
- let nextKey: string | undefined;
676
+ let nextCredential: string | AuthRetryCredential | undefined;
606
677
  try {
607
- nextKey = await onAuthError(model.provider, retryApiKey, failure.error);
678
+ nextCredential = await onAuthError(model.provider, retryApiKey, failure.error);
608
679
  } catch {
609
- nextKey = undefined;
680
+ nextCredential = undefined;
610
681
  }
611
- if (!nextKey || nextKey === retryApiKey) {
682
+ const retryCredential: AuthRetryCredential | undefined =
683
+ typeof nextCredential === "string" ? { apiKey: nextCredential } : nextCredential;
684
+ if (!retryCredential?.apiKey || retryCredential.apiKey === retryApiKey) {
685
+ if (retryCredential) retryCredential.onStreamCreated?.();
612
686
  emitFailure(failure);
613
687
  return;
614
688
  }
615
- await runAttempt(nextKey, false);
689
+ await runAttempt(retryCredential.apiKey, false, retryCredential.onStreamCreated);
616
690
  })();
617
691
  return outer;
618
692
  }
@@ -625,26 +699,39 @@ export function streamSimple<TApi extends Api>(
625
699
  // pi-native transport.
626
700
  const resolvedRequestMaxTokens = resolveDefaultRequestMaxTokens(model, options?.maxTokens);
627
701
  if (model.transport === "pi-native") {
628
- return streamFromLazyImport(async () => {
629
- const { streamPiNative } = await import("./providers/pi-native-client");
630
- return streamPiNative(model, context, { ...options, maxTokens: resolvedRequestMaxTokens });
631
- }, options?.signal);
702
+ return streamFromLazyImport(
703
+ async () => {
704
+ const { streamPiNative } = await import("./providers/pi-native-client");
705
+ return streamPiNative(model, context, { ...options, maxTokens: resolvedRequestMaxTokens });
706
+ },
707
+ options?.signal,
708
+ options?.onStreamCreated,
709
+ );
632
710
  }
633
711
 
634
712
  // Check custom API registry (extension-provided APIs)
635
713
  const customApiProvider = getCustomApi(model.api);
636
714
  if (customApiProvider) {
637
- return customApiProvider.streamSimple(model, context, { ...options, maxTokens: resolvedRequestMaxTokens });
715
+ const events = customApiProvider.streamSimple(model, context, {
716
+ ...options,
717
+ maxTokens: resolvedRequestMaxTokens,
718
+ });
719
+ if (!options?.onStreamCreated) return events;
720
+ const forwarded = new AssistantMessageEventStream();
721
+ pipeAssistantStream(forwarded, events, options.signal, options.onStreamCreated);
722
+ return forwarded;
638
723
  }
639
724
 
640
725
  // Vertex AI uses Application Default Credentials, not API keys
641
726
  if (model.api === "google-vertex") {
642
727
  const providerOptions = mapOptionsForApi(model, options, undefined);
643
- return stream(model, context, providerOptions);
728
+ const events = stream(model, context, providerOptions, options?.onStreamCreated);
729
+ return events;
644
730
  } else if (model.api === "bedrock-converse-stream") {
645
731
  // Bedrock doesn't have any API keys instead it sources credentials from standard AWS env variables or from given AWS profile.
646
732
  const providerOptions = mapOptionsForApi(model, options, undefined);
647
- return stream(model, context, providerOptions);
733
+ const events = stream(model, context, providerOptions, options?.onStreamCreated);
734
+ return events;
648
735
  }
649
736
 
650
737
  const apiKey = options?.apiKey || getEnvApiKey(model.provider);
@@ -658,58 +745,71 @@ export function streamSimple<TApi extends Api>(
658
745
 
659
746
  // GitLab Duo - wraps Anthropic/OpenAI behind GitLab AI Gateway direct access tokens
660
747
  if (model.provider === "gitlab-duo") {
661
- return streamFromLazyImport(async () => {
662
- const { streamGitLabDuo } = await import("./providers/gitlab-duo");
663
- return streamGitLabDuo(
664
- model,
665
- context,
666
- copyProviderSafetyStopAdapterInvocation(adapterOptions, {
667
- ...adapterOptions,
668
- apiKey,
669
- maxTokens: resolvedSpecialProviderMaxTokens,
670
- }),
671
- );
672
- }, options?.signal);
748
+ return streamFromLazyImport(
749
+ async () => {
750
+ const { streamGitLabDuo } = await import("./providers/gitlab-duo");
751
+ return streamGitLabDuo(
752
+ model,
753
+ context,
754
+ copyProviderSafetyStopAdapterInvocation(adapterOptions, {
755
+ ...adapterOptions,
756
+ apiKey,
757
+ maxTokens: resolvedSpecialProviderMaxTokens,
758
+ }),
759
+ );
760
+ },
761
+ options?.signal,
762
+ options?.onStreamCreated,
763
+ );
673
764
  }
674
765
 
675
766
  // Kimi Code - route to dedicated handler that wraps OpenAI or Anthropic API
676
767
  if (model.provider === "kimi-code") {
677
- return streamFromLazyImport(async () => {
678
- const { streamKimi } = await import("./providers/kimi");
679
- // Pass raw SimpleStreamOptions - streamKimi handles mapping internally
680
- return streamKimi(
681
- model as Model<"openai-completions">,
682
- context,
683
- copyProviderSafetyStopAdapterInvocation(adapterOptions, {
684
- ...adapterOptions,
685
- apiKey,
686
- maxTokens: resolvedSpecialProviderMaxTokens,
687
- format: options?.kimiApiFormat ?? "anthropic",
688
- }),
689
- );
690
- }, options?.signal);
768
+ return streamFromLazyImport(
769
+ async () => {
770
+ const { streamKimi } = await import("./providers/kimi");
771
+ // Pass raw SimpleStreamOptions - streamKimi handles mapping internally
772
+ return streamKimi(
773
+ model as Model<"openai-completions">,
774
+ context,
775
+ copyProviderSafetyStopAdapterInvocation(adapterOptions, {
776
+ ...adapterOptions,
777
+ apiKey,
778
+ maxTokens: resolvedSpecialProviderMaxTokens,
779
+ format: options?.kimiApiFormat ?? "anthropic",
780
+ }),
781
+ );
782
+ },
783
+ options?.signal,
784
+ options?.onStreamCreated,
785
+ );
691
786
  }
692
787
 
693
788
  // Synthetic - route to dedicated handler that wraps OpenAI or Anthropic API
694
789
  if (model.provider === "synthetic") {
695
- return streamFromLazyImport(async () => {
696
- const { streamSynthetic } = await import("./providers/synthetic");
697
- // Pass raw SimpleStreamOptions - streamSynthetic handles mapping internally
698
- return streamSynthetic(
699
- model as Model<"openai-completions">,
700
- context,
701
- copyProviderSafetyStopAdapterInvocation(adapterOptions, {
702
- ...adapterOptions,
703
- apiKey,
704
- maxTokens: resolvedSpecialProviderMaxTokens,
705
- format: options?.syntheticApiFormat ?? "openai", // Default to OpenAI format
706
- }),
707
- );
708
- }, options?.signal);
790
+ return streamFromLazyImport(
791
+ async () => {
792
+ const { streamSynthetic } = await import("./providers/synthetic");
793
+ // Pass raw SimpleStreamOptions - streamSynthetic handles mapping internally
794
+ return streamSynthetic(
795
+ model as Model<"openai-completions">,
796
+ context,
797
+ copyProviderSafetyStopAdapterInvocation(adapterOptions, {
798
+ ...adapterOptions,
799
+ apiKey,
800
+ maxTokens: resolvedSpecialProviderMaxTokens,
801
+ format: options?.syntheticApiFormat ?? "openai", // Default to OpenAI format
802
+ }),
803
+ );
804
+ },
805
+ options?.signal,
806
+ options?.onStreamCreated,
807
+ );
709
808
  }
710
809
 
711
810
  const providerOptions = mapOptionsForApi(model, options, apiKey);
712
- return stream(model, context, providerOptions);
811
+ const events = stream(model, context, providerOptions, options?.onStreamCreated);
812
+ return events;
713
813
  }
714
814
 
715
815
  export async function completeSimple<TApi extends Api>(
@@ -858,6 +958,8 @@ function mapOptionsForApi<TApi extends Api>(
858
958
  providerSessionState: options?.providerSessionState,
859
959
  onPayload: options?.onPayload,
860
960
  onResponse: options?.onResponse,
961
+ onStreamCreated: options?.onStreamCreated,
962
+ disableProviderRetries: options?.disableProviderRetries,
861
963
  onSseEvent: options?.onSseEvent,
862
964
  attemptScope: options?.attemptScope,
863
965
  execHandlers: options?.execHandlers,
@@ -1162,6 +1264,12 @@ function mapOptionsForApi<TApi extends Api>(
1162
1264
  });
1163
1265
  }
1164
1266
 
1267
+ case "kiro-codewhisperer-stream":
1268
+ return castApi<"kiro-codewhisperer-stream">({
1269
+ ...base,
1270
+ reasoning: options?.reasoning,
1271
+ });
1272
+
1165
1273
  default:
1166
1274
  throw new Error(`Unhandled API in mapOptionsForApi: ${model.api}`);
1167
1275
  }
package/src/types.d.ts CHANGED
@@ -54,7 +54,7 @@ export interface ThinkingConfig {
54
54
  /** Provider-specific transport used to encode the selected effort. */
55
55
  mode: ThinkingControlMode;
56
56
  }
57
- export declare const KNOWN_PROVIDERS: readonly ["alibaba-token-plan", "amazon-bedrock", "kiro", "azure-openai", "anthropic", "google", "google-gemini-cli", "google-antigravity", "google-vertex", "openai", "openai-codex", "opencodex", "kimi-code", "minimax-code", "minimax-code-cn", "github-copilot", "fireworks", "firepass", "fugu", "gitlab-duo", "cursor", "jetbrains-junie", "deepseek", "deepinfra", "xai", "groq", "cerebras", "openrouter", "kilo", "vercel-ai-gateway", "zai", "glm-zcode", "mistral", "minimax", "opencode-go", "opencode-zen", "opengateway", "bizrouter", "mara", "synthetic", "cloudflare-ai-gateway", "huggingface", "litellm", "moonshot", "nvidia", "nanogpt", "ollama", "ollama-cloud", "qianfan", "qwen-portal", "sglang", "together", "venice", "vllm", "xiaomi", "xiaomi-token-plan-sgp", "xiaomi-token-plan-ams", "xiaomi-token-plan-cn", "zenmux", "lm-studio", "omlx"];
57
+ export declare const KNOWN_PROVIDERS: readonly ["alibaba-token-plan", "amazon-bedrock", "kiro", "azure-openai", "anthropic", "google", "google-gemini-cli", "google-antigravity", "google-vertex", "openai", "openai-codex", "opencodex", "kimi-code", "minimax-code", "minimax-code-cn", "github-copilot", "fireworks", "firepass", "fugu", "gitlab-duo", "cursor", "jetbrains-junie", "deepseek", "deepinfra", "xai", "groq", "cerebras", "openrouter", "kilo", "vercel-ai-gateway", "zai", "glm-zcode", "mistral", "minimax", "opencode-go", "commandcode-goat", "opencode-zen", "opengateway", "bizrouter", "mara", "synthetic", "cloudflare-ai-gateway", "huggingface", "litellm", "moonshot", "nvidia", "nanogpt", "ollama", "ollama-cloud", "qianfan", "qwen-portal", "sglang", "together", "venice", "vllm", "xiaomi", "xiaomi-token-plan-sgp", "xiaomi-token-plan-ams", "xiaomi-token-plan-cn", "zenmux", "lm-studio", "omlx"];
58
58
  export type KnownProvider = (typeof KNOWN_PROVIDERS)[number];
59
59
  export declare function isKnownProvider(provider: string): provider is KnownProvider;
60
60
  export type Provider = KnownProvider | string;
@@ -155,6 +155,18 @@ export interface RawSseEvent {
155
155
  export type FetchImpl = ((input: string | URL | Request, init?: RequestInit) => Promise<Response>) & {
156
156
  preconnect?: typeof globalThis.fetch.preconnect;
157
157
  };
158
+ /**
159
+ * Credential returned by an auth retry resolver.
160
+ *
161
+ * The optional admission callback lets an authority-bearing caller retain a
162
+ * credential lease until the replacement provider request is actually
163
+ * admitted. Ordinary callers can continue returning a string from
164
+ * {@link StreamOptions.onAuthError}.
165
+ */
166
+ export interface AuthRetryCredential {
167
+ apiKey: string;
168
+ onStreamCreated?: () => void;
169
+ }
158
170
  export interface StreamOptions {
159
171
  temperature?: number;
160
172
  topP?: number;
@@ -186,7 +198,7 @@ export interface StreamOptions {
186
198
  * event has been emitted. Returning a different key retries the provider
187
199
  * request once.
188
200
  */
189
- onAuthError?: (provider: string, apiKey: string, error: unknown) => Promise<string | undefined>;
201
+ onAuthError?: (provider: string, apiKey: string, error: unknown) => Promise<string | AuthRetryCredential | undefined>;
190
202
  cacheRetention?: CacheRetention;
191
203
  /**
192
204
  * Additional headers to include in provider requests.
@@ -243,6 +255,14 @@ export interface StreamOptions {
243
255
  * The `scope` parameter carries the per-attempt identity for execution attribution.
244
256
  */
245
257
  onResponse?: (response: ProviderResponseMetadata, model?: Model<Api>, scope?: AttemptScopeRef) => void | Promise<void>;
258
+ /**
259
+ * Internal dispatch-admission hook. Providers invoke this immediately before
260
+ * submitting an outbound request; stream forwarding retains a first-response
261
+ * fallback for custom providers that do not expose a transport seam.
262
+ */
263
+ onStreamCreated?: () => void;
264
+ /** Internal authority policy: disable provider-owned retries and corrective replays. */
265
+ disableProviderRetries?: boolean;
246
266
  /**
247
267
  * Optional callback for raw Server-Sent Events as they arrive from HTTP streaming providers.
248
268
  *
package/src/types.ts CHANGED
@@ -167,6 +167,7 @@ export const KNOWN_PROVIDERS = [
167
167
  "mistral",
168
168
  "minimax",
169
169
  "opencode-go",
170
+ "commandcode-goat",
170
171
  "opencode-zen",
171
172
  "opengateway",
172
173
  "bizrouter",
@@ -353,6 +354,19 @@ export type FetchImpl = ((input: string | URL | Request, init?: RequestInit) =>
353
354
  preconnect?: typeof globalThis.fetch.preconnect;
354
355
  };
355
356
 
357
+ /**
358
+ * Credential returned by an auth retry resolver.
359
+ *
360
+ * The optional admission callback lets an authority-bearing caller retain a
361
+ * credential lease until the replacement provider request is actually
362
+ * admitted. Ordinary callers can continue returning a string from
363
+ * {@link StreamOptions.onAuthError}.
364
+ */
365
+ export interface AuthRetryCredential {
366
+ apiKey: string;
367
+ onStreamCreated?: () => void;
368
+ }
369
+
356
370
  export interface StreamOptions {
357
371
  temperature?: number;
358
372
  topP?: number;
@@ -384,7 +398,11 @@ export interface StreamOptions {
384
398
  * event has been emitted. Returning a different key retries the provider
385
399
  * request once.
386
400
  */
387
- onAuthError?: (provider: string, apiKey: string, error: unknown) => Promise<string | undefined>;
401
+ onAuthError?: (
402
+ provider: string,
403
+ apiKey: string,
404
+ error: unknown,
405
+ ) => Promise<string | AuthRetryCredential | undefined>;
388
406
  cacheRetention?: CacheRetention;
389
407
  /**
390
408
  * Additional headers to include in provider requests.
@@ -449,6 +467,14 @@ export interface StreamOptions {
449
467
  model?: Model<Api>,
450
468
  scope?: AttemptScopeRef,
451
469
  ) => void | Promise<void>;
470
+ /**
471
+ * Internal dispatch-admission hook. Providers invoke this immediately before
472
+ * submitting an outbound request; stream forwarding retains a first-response
473
+ * fallback for custom providers that do not expose a transport seam.
474
+ */
475
+ onStreamCreated?: () => void;
476
+ /** Internal authority policy: disable provider-owned retries and corrective replays. */
477
+ disableProviderRetries?: boolean;
452
478
  /**
453
479
  * Optional callback for raw Server-Sent Events as they arrive from HTTP streaming providers.
454
480
  *