@gajae-code/ai 0.15.4 → 0.15.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +27 -1
  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 +116 -34
  9. package/dist/types/provider-models/openai-compat.d.ts +1 -0
  10. package/dist/types/provider-models/special.d.ts +2 -1
  11. package/dist/types/providers/kiro-api-key.d.ts +50 -0
  12. package/dist/types/providers/kiro-codewhisperer.d.ts +3 -0
  13. package/dist/types/providers/register-builtins.d.ts +12 -12
  14. package/dist/types/stream.d.ts +2 -1
  15. package/dist/types/types.d.ts +35 -24
  16. package/dist/types/utils/fallback-transport.d.ts +7 -0
  17. package/dist/types/utils/json-parse.d.ts +5 -3
  18. package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
  19. package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
  20. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  21. package/dist/types/utils/oauth/types.d.ts +1 -1
  22. package/dist/types/utils/retry.d.ts +2 -0
  23. package/dist/types/utils/tool-call-healing.d.ts +4 -4
  24. package/package.json +3 -3
  25. package/src/auth-broker/client.ts +41 -13
  26. package/src/auth-broker/redact.ts +25 -1
  27. package/src/auth-broker/remote-store.ts +374 -115
  28. package/src/auth-broker/server.ts +131 -91
  29. package/src/auth-broker/types.ts +6 -0
  30. package/src/auth-broker/wire-schemas.ts +6 -0
  31. package/src/auth-gateway/server.ts +447 -79
  32. package/src/auth-gateway/types.ts +28 -2
  33. package/src/auth-storage.ts +742 -157
  34. package/src/cli.ts +1 -0
  35. package/src/model-thinking.ts +16 -0
  36. package/src/models.json +1054 -0
  37. package/src/models.ts +9 -1
  38. package/src/provider-models/descriptors.ts +3 -1
  39. package/src/provider-models/openai-compat.ts +41 -1
  40. package/src/provider-models/special.ts +15 -3
  41. package/src/providers/anthropic.ts +7 -1
  42. package/src/providers/azure-openai-responses.ts +4 -1
  43. package/src/providers/cursor.ts +256 -101
  44. package/src/providers/gitlab-duo.ts +18 -1
  45. package/src/providers/google-gemini-cli.ts +3 -0
  46. package/src/providers/google-shared.ts +3 -0
  47. package/src/providers/kiro-api-key.d.ts +50 -0
  48. package/src/providers/kiro-api-key.ts +786 -0
  49. package/src/providers/kiro-codewhisperer.d.ts +3 -0
  50. package/src/providers/kiro-codewhisperer.ts +34 -9
  51. package/src/providers/ollama.ts +3 -0
  52. package/src/providers/openai-codex-responses.ts +24 -6
  53. package/src/providers/openai-completions.ts +11 -1
  54. package/src/providers/openai-responses-shared.ts +23 -2
  55. package/src/providers/openai-responses.ts +10 -1
  56. package/src/providers/pi-native-client.ts +1 -0
  57. package/src/providers/pi-native-server.ts +24 -0
  58. package/src/providers/register-builtins.d.ts +12 -12
  59. package/src/providers/register-builtins.ts +16 -3
  60. package/src/stream.d.ts +2 -1
  61. package/src/stream.ts +180 -70
  62. package/src/types.d.ts +35 -24
  63. package/src/types.ts +40 -23
  64. package/src/utils/fallback-transport.d.ts +7 -0
  65. package/src/utils/fallback-transport.ts +21 -4
  66. package/src/utils/json-parse.d.ts +5 -3
  67. package/src/utils/json-parse.ts +6 -6
  68. package/src/utils/oauth/api-key-login.ts +13 -2
  69. package/src/utils/oauth/api-key-validation.ts +242 -41
  70. package/src/utils/oauth/commandcode.ts +17 -0
  71. package/src/utils/oauth/index.ts +20 -5
  72. package/src/utils/oauth/types.d.ts +1 -1
  73. package/src/utils/oauth/types.ts +1 -0
  74. package/src/utils/retry.d.ts +2 -0
  75. package/src/utils/retry.ts +15 -2
  76. package/src/utils/tool-call-healing.d.ts +4 -4
  77. package/src/utils/tool-call-healing.ts +4 -4
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",
@@ -131,10 +133,12 @@ const serviceProviderMap: Record<string, KeyResolver> = {
131
133
  tavily: "TAVILY_API_KEY",
132
134
  parallel: "PARALLEL_API_KEY",
133
135
  kagi: "KAGI_API_KEY",
134
- // Kiro uses AWS SSO OIDC OAuth flow; bearer token is stored as the OAuth access token.
136
+ // Kiro API keys use the ksk_ prefix; preserve the AWS bearer fallback for OAuth.
135
137
  kiro: () => {
136
- const bearerToken = $credentialEnv("AWS_BEARER_TOKEN_KIRO");
137
- if (bearerToken) return bearerToken;
138
+ const apiKey = $credentialEnv("KIRO_API_KEY");
139
+ return apiKey?.trim().startsWith("ksk_") && !/[\x00-\x1f\x7f]/.test(apiKey)
140
+ ? apiKey
141
+ : $credentialEnv("AWS_BEARER_TOKEN_KIRO");
138
142
  },
139
143
  // GitHub Copilot uses GitHub personal access token
140
144
  "github-copilot": () => $pickCredentialEnv("COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"),
@@ -219,6 +223,7 @@ export function listProvidersWithEnvKey(): string[] {
219
223
  * Used to give OpenCode users an accurate headless auth diagnostic (#755).
220
224
  */
221
225
  const OPENCODE_SUBSCRIPTION_PROVIDERS = new Set(["opencode-go", "opencode-zen"]);
226
+ const API_KEY_LOGIN_PROVIDERS = new Set(["commandcode-goat"]);
222
227
 
223
228
  /**
224
229
  * Provider-specific credential guidance appended to "no credential" errors.
@@ -237,12 +242,16 @@ export function formatProviderCredentialHint(provider: string): string {
237
242
  const resolver = serviceProviderMap[provider];
238
243
  const envVar = typeof resolver === "string" ? resolver : undefined;
239
244
  const isOpenCodeSubscription = OPENCODE_SUBSCRIPTION_PROVIDERS.has(provider);
245
+ const isApiKeyLoginProvider = API_KEY_LOGIN_PROVIDERS.has(provider);
240
246
  const parts: string[] = [];
241
247
  if (isOpenCodeSubscription) {
242
248
  parts.push(
243
249
  "OpenCode subscriptions authenticate with an API key (created at https://opencode.ai/auth), not a separate session/OAuth token.",
244
250
  );
245
251
  }
252
+ if (isApiKeyLoginProvider) {
253
+ parts.push("Command Code GOAT uses an API key from https://commandcode.ai/studio/#api-keys.");
254
+ }
246
255
  if (provider === "jetbrains-junie") {
247
256
  parts.push(
248
257
  "JetBrains AI (Junie) authenticates with an access token generated at https://junie.jetbrains.com/cli; there is no OAuth login for this provider.",
@@ -265,10 +274,18 @@ function pipeAssistantStream(
265
274
  outer: AssistantMessageEventStream,
266
275
  inner: AssistantMessageEventStream,
267
276
  signal?: AbortSignal,
277
+ onStreamCreated?: () => void,
268
278
  ): void {
269
279
  void (async () => {
270
280
  try {
281
+ let admitted = false;
282
+ const markAdmission = (): void => {
283
+ if (admitted) return;
284
+ admitted = true;
285
+ onStreamCreated?.();
286
+ };
271
287
  for await (const event of inner) {
288
+ if (event.type !== "start") markAdmission();
272
289
  outer.push(event);
273
290
  // The inner provider stream owns abort semantics (it receives the
274
291
  // same signal), but stop forwarding as soon as the consumer
@@ -286,14 +303,16 @@ function pipeAssistantStream(
286
303
  })();
287
304
  }
288
305
 
289
- function streamFromLazyImport(
306
+ export function streamFromLazyImport(
290
307
  createInner: () => Promise<AssistantMessageEventStream>,
291
308
  signal?: AbortSignal,
309
+ onStreamCreated?: () => void,
292
310
  ): AssistantMessageEventStream {
293
311
  const outer = new AssistantMessageEventStream();
294
312
  void (async () => {
295
313
  try {
296
- pipeAssistantStream(outer, await createInner(), signal);
314
+ const inner = await createInner();
315
+ pipeAssistantStream(outer, inner, signal, onStreamCreated);
297
316
  } catch (error) {
298
317
  outer.fail(error);
299
318
  }
@@ -315,6 +334,7 @@ export function stream<TApi extends Api>(
315
334
  model: Model<TApi>,
316
335
  context: Context,
317
336
  options?: OptionsForApi<TApi>,
337
+ onStreamCreated?: () => void,
318
338
  ): AssistantMessageEventStream {
319
339
  if (!hasValidatedManagedAttempt(options)) assertManagedAttempt(options);
320
340
  if (options?.fallbackManaged) {
@@ -348,6 +368,7 @@ export function stream<TApi extends Api>(
348
368
  return streamGitLabDuo(model, context, adapterOptions);
349
369
  },
350
370
  (options as StreamOptions | undefined)?.signal,
371
+ onStreamCreated,
351
372
  );
352
373
  }
353
374
 
@@ -360,15 +381,22 @@ export function stream<TApi extends Api>(
360
381
  isProviderSafetyStopModelTrusted(model)
361
382
  ? withProviderSafetyStopAdapterInvocation(vertexOptions)
362
383
  : vertexOptions,
384
+ onStreamCreated,
363
385
  );
364
386
  } else if (model.api === "bedrock-converse-stream") {
365
387
  // Bedrock doesn't have any API keys instead it sources credentials from standard AWS env variables or from given AWS profile.
366
- 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
+ );
367
394
  } else if (model.api === "kiro-codewhisperer-stream") {
368
395
  return streamKiroCodeWhisperer(
369
396
  model as Model<"kiro-codewhisperer-stream">,
370
397
  context,
371
398
  (options || {}) as KiroCodeWhispererOptions,
399
+ onStreamCreated,
372
400
  );
373
401
  }
374
402
 
@@ -385,23 +413,39 @@ export function stream<TApi extends Api>(
385
413
  switch (api) {
386
414
  case "anthropic-messages": {
387
415
  const anthropicOptions = adapterProviderOptions as AnthropicOptions;
388
- return streamAnthropic(model as Model<"anthropic-messages">, context, {
389
- ...anthropicOptions,
390
- isOAuth: anthropicOptions.isOAuth ?? model.isOAuth,
391
- });
416
+ return streamAnthropic(
417
+ model as Model<"anthropic-messages">,
418
+ context,
419
+ {
420
+ ...anthropicOptions,
421
+ isOAuth: anthropicOptions.isOAuth ?? model.isOAuth,
422
+ },
423
+ onStreamCreated,
424
+ );
392
425
  }
393
426
 
394
427
  case "openai-completions":
395
- 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
+ );
396
434
 
397
435
  case "openai-responses":
398
- 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
+ );
399
442
 
400
443
  case "azure-openai-responses":
401
444
  return streamAzureOpenAIResponses(
402
445
  model as Model<"azure-openai-responses">,
403
446
  context,
404
447
  adapterProviderOptions as any,
448
+ onStreamCreated,
405
449
  );
406
450
 
407
451
  case "openai-codex-responses":
@@ -409,23 +453,35 @@ export function stream<TApi extends Api>(
409
453
  model as Model<"openai-codex-responses">,
410
454
  context,
411
455
  adapterProviderOptions as any,
456
+ onStreamCreated,
412
457
  );
413
458
 
414
459
  case "google-generative-ai":
415
- return streamGoogle(model as Model<"google-generative-ai">, context, adapterProviderOptions);
460
+ return streamGoogle(model as Model<"google-generative-ai">, context, adapterProviderOptions, onStreamCreated);
416
461
 
417
462
  case "google-gemini-cli":
418
463
  return streamGoogleGeminiCli(
419
464
  model as Model<"google-gemini-cli">,
420
465
  context,
421
466
  adapterProviderOptions as GoogleGeminiCliOptions,
467
+ onStreamCreated,
422
468
  );
423
469
 
424
470
  case "ollama-chat":
425
- 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
+ );
426
477
 
427
478
  case "cursor-agent":
428
- 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
+ );
429
485
 
430
486
  default:
431
487
  throw new Error(`Unhandled API: ${api}`);
@@ -537,9 +593,19 @@ export function streamSimple<TApi extends Api>(
537
593
  ? AbortSignal.any([options.signal, consumerAbortController.signal])
538
594
  : consumerAbortController.signal;
539
595
  const onAuthError = options!.onAuthError!;
540
- 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> => {
541
601
  const bufferedEvents: AssistantMessageEvent[] = [];
542
602
  let emittedReplayUnsafeEvent = false;
603
+ let admitted = false;
604
+ const markAdmission = (): void => {
605
+ if (admitted) return;
606
+ admitted = true;
607
+ onStreamCreated?.();
608
+ };
543
609
  const flushBuffered = (): void => {
544
610
  emitBufferedEvents(outer, bufferedEvents);
545
611
  bufferedEvents.length = 0;
@@ -550,6 +616,7 @@ export function streamSimple<TApi extends Api>(
550
616
  ...options,
551
617
  apiKey,
552
618
  onAuthError: undefined,
619
+ onStreamCreated: markAdmission,
553
620
  signal: requestSignal,
554
621
  });
555
622
  for await (const event of inner) {
@@ -585,6 +652,12 @@ export function streamSimple<TApi extends Api>(
585
652
  }
586
653
  flushBuffered();
587
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();
588
661
  }
589
662
  return undefined;
590
663
  };
@@ -598,19 +671,22 @@ export function streamSimple<TApi extends Api>(
598
671
  };
599
672
 
600
673
  void (async () => {
601
- const failure = await runAttempt(retryApiKey, true);
674
+ const failure = await runAttempt(retryApiKey, true, options?.onStreamCreated);
602
675
  if (!failure) return;
603
- let nextKey: string | undefined;
676
+ let nextCredential: string | AuthRetryCredential | undefined;
604
677
  try {
605
- nextKey = await onAuthError(model.provider, retryApiKey, failure.error);
678
+ nextCredential = await onAuthError(model.provider, retryApiKey, failure.error);
606
679
  } catch {
607
- nextKey = undefined;
680
+ nextCredential = undefined;
608
681
  }
609
- 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?.();
610
686
  emitFailure(failure);
611
687
  return;
612
688
  }
613
- await runAttempt(nextKey, false);
689
+ await runAttempt(retryCredential.apiKey, false, retryCredential.onStreamCreated);
614
690
  })();
615
691
  return outer;
616
692
  }
@@ -623,26 +699,39 @@ export function streamSimple<TApi extends Api>(
623
699
  // pi-native transport.
624
700
  const resolvedRequestMaxTokens = resolveDefaultRequestMaxTokens(model, options?.maxTokens);
625
701
  if (model.transport === "pi-native") {
626
- return streamFromLazyImport(async () => {
627
- const { streamPiNative } = await import("./providers/pi-native-client");
628
- return streamPiNative(model, context, { ...options, maxTokens: resolvedRequestMaxTokens });
629
- }, 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
+ );
630
710
  }
631
711
 
632
712
  // Check custom API registry (extension-provided APIs)
633
713
  const customApiProvider = getCustomApi(model.api);
634
714
  if (customApiProvider) {
635
- 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;
636
723
  }
637
724
 
638
725
  // Vertex AI uses Application Default Credentials, not API keys
639
726
  if (model.api === "google-vertex") {
640
727
  const providerOptions = mapOptionsForApi(model, options, undefined);
641
- return stream(model, context, providerOptions);
728
+ const events = stream(model, context, providerOptions, options?.onStreamCreated);
729
+ return events;
642
730
  } else if (model.api === "bedrock-converse-stream") {
643
731
  // Bedrock doesn't have any API keys instead it sources credentials from standard AWS env variables or from given AWS profile.
644
732
  const providerOptions = mapOptionsForApi(model, options, undefined);
645
- return stream(model, context, providerOptions);
733
+ const events = stream(model, context, providerOptions, options?.onStreamCreated);
734
+ return events;
646
735
  }
647
736
 
648
737
  const apiKey = options?.apiKey || getEnvApiKey(model.provider);
@@ -656,58 +745,71 @@ export function streamSimple<TApi extends Api>(
656
745
 
657
746
  // GitLab Duo - wraps Anthropic/OpenAI behind GitLab AI Gateway direct access tokens
658
747
  if (model.provider === "gitlab-duo") {
659
- return streamFromLazyImport(async () => {
660
- const { streamGitLabDuo } = await import("./providers/gitlab-duo");
661
- return streamGitLabDuo(
662
- model,
663
- context,
664
- copyProviderSafetyStopAdapterInvocation(adapterOptions, {
665
- ...adapterOptions,
666
- apiKey,
667
- maxTokens: resolvedSpecialProviderMaxTokens,
668
- }),
669
- );
670
- }, 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
+ );
671
764
  }
672
765
 
673
766
  // Kimi Code - route to dedicated handler that wraps OpenAI or Anthropic API
674
767
  if (model.provider === "kimi-code") {
675
- return streamFromLazyImport(async () => {
676
- const { streamKimi } = await import("./providers/kimi");
677
- // Pass raw SimpleStreamOptions - streamKimi handles mapping internally
678
- return streamKimi(
679
- model as Model<"openai-completions">,
680
- context,
681
- copyProviderSafetyStopAdapterInvocation(adapterOptions, {
682
- ...adapterOptions,
683
- apiKey,
684
- maxTokens: resolvedSpecialProviderMaxTokens,
685
- format: options?.kimiApiFormat ?? "anthropic",
686
- }),
687
- );
688
- }, 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
+ );
689
786
  }
690
787
 
691
788
  // Synthetic - route to dedicated handler that wraps OpenAI or Anthropic API
692
789
  if (model.provider === "synthetic") {
693
- return streamFromLazyImport(async () => {
694
- const { streamSynthetic } = await import("./providers/synthetic");
695
- // Pass raw SimpleStreamOptions - streamSynthetic handles mapping internally
696
- return streamSynthetic(
697
- model as Model<"openai-completions">,
698
- context,
699
- copyProviderSafetyStopAdapterInvocation(adapterOptions, {
700
- ...adapterOptions,
701
- apiKey,
702
- maxTokens: resolvedSpecialProviderMaxTokens,
703
- format: options?.syntheticApiFormat ?? "openai", // Default to OpenAI format
704
- }),
705
- );
706
- }, 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
+ );
707
808
  }
708
809
 
709
810
  const providerOptions = mapOptionsForApi(model, options, apiKey);
710
- return stream(model, context, providerOptions);
811
+ const events = stream(model, context, providerOptions, options?.onStreamCreated);
812
+ return events;
711
813
  }
712
814
 
713
815
  export async function completeSimple<TApi extends Api>(
@@ -856,6 +958,8 @@ function mapOptionsForApi<TApi extends Api>(
856
958
  providerSessionState: options?.providerSessionState,
857
959
  onPayload: options?.onPayload,
858
960
  onResponse: options?.onResponse,
961
+ onStreamCreated: options?.onStreamCreated,
962
+ disableProviderRetries: options?.disableProviderRetries,
859
963
  onSseEvent: options?.onSseEvent,
860
964
  attemptScope: options?.attemptScope,
861
965
  execHandlers: options?.execHandlers,
@@ -1160,6 +1264,12 @@ function mapOptionsForApi<TApi extends Api>(
1160
1264
  });
1161
1265
  }
1162
1266
 
1267
+ case "kiro-codewhisperer-stream":
1268
+ return castApi<"kiro-codewhisperer-stream">({
1269
+ ...base,
1270
+ reasoning: options?.reasoning,
1271
+ });
1272
+
1163
1273
  default:
1164
1274
  throw new Error(`Unhandled API in mapOptionsForApi: ${model.api}`);
1165
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
  *
@@ -399,31 +419,22 @@ export interface ToolCall {
399
419
  */
400
420
  incompleteArgumentsReason?: "truncated" | "malformed" | "conflicting" | "ambiguous";
401
421
  /**
402
- * Set when the raw argument JSON spelled a printable character as a `\uXXXX`
403
- * escape instead of a literal character. This includes ASCII landings because
404
- * a one-nibble mutation can move an intended non-ASCII scalar below U+0080.
405
- * Such a payload parses cleanly but
406
- * is unverifiable: one mistyped hex digit decodes to a different, equally
407
- * valid character, so the text can be silently wrong with no in-band evidence.
408
- * The agent loop resamples the turn a bounded number of times and then
409
- * rejects the call instead of executing it. The single bounded exception
410
- * is a tool that enumerated its display-only fields
411
- * (`displaySafeEscapedArgFields`): when every escaped scalar corroborates
412
- * a decoded non-ASCII character inside those fields, the call executes
413
- * with a warning instead — rendered question text, never executable
414
- * content, ids, or durable metadata.
415
- * Escapes that are required (control characters) or unavoidable (lone
416
- * surrogates) never set this.
422
+ * Set by current producers when raw argument JSON carries unsafe Unicode
423
+ * data, such as malformed escape evidence or a decoded unpaired surrogate.
424
+ * Valid JSON `\uXXXX` escapes are canonical spellings of the decoded string
425
+ * and current producers do not set this flag for them.
426
+ *
427
+ * Legacy producers may still set the flag for any escaped non-ASCII spelling.
428
+ * The agent loop keeps its bounded legacy resample/display-safe behavior for
429
+ * those calls while consuming the transient evidence below.
417
430
  */
418
431
  escapedNonAsciiArguments?: boolean;
419
432
  /**
420
- * Bounded, payload-free evidence for the original raw escape positions and
421
- * process-keyed scalar/path identities. Required for the display-safe terminal exemption: decoded values
422
- * alone cannot prove that an ASCII landing such as `\u0077` was not a
423
- * one-nibble mutation of a non-ASCII escape. Presence of this evidence implies
424
- * the guarded state even if a legacy producer omitted
425
- * `escapedNonAsciiArguments`. The agent consumes and removes this transient
426
- * field before the tool-call message can become durable.
433
+ * Bounded, payload-free evidence for raw Unicode argument data. Current
434
+ * producers attach it only for unsafe data; legacy producers may attach
435
+ * non-malformed positional evidence used by the display-safe compatibility
436
+ * path. The agent consumes and removes this transient field before the
437
+ * tool-call message can become durable.
427
438
  */
428
439
  escapedUnicodeArgumentEvidence?: UnicodeEscapeEvidence;
429
440
  }