@providerkit/core 0.24.0 → 0.25.1

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 (48) hide show
  1. package/README.md +14 -0
  2. package/dist/auth/device.d.ts +3 -2
  3. package/dist/auth/device.d.ts.map +1 -1
  4. package/dist/auth/device.js +5 -2
  5. package/dist/auth/device.js.map +1 -1
  6. package/dist/auth/flows.d.ts +5 -1
  7. package/dist/auth/flows.d.ts.map +1 -1
  8. package/dist/auth/flows.js +237 -0
  9. package/dist/auth/flows.js.map +1 -1
  10. package/dist/auth/oauth.d.ts +4 -2
  11. package/dist/auth/oauth.d.ts.map +1 -1
  12. package/dist/auth/oauth.js +4 -3
  13. package/dist/auth/oauth.js.map +1 -1
  14. package/dist/auth/with-token.js +5 -3
  15. package/dist/auth/with-token.js.map +1 -1
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +3 -0
  18. package/dist/errors.js.map +1 -1
  19. package/dist/presets.d.ts +53 -0
  20. package/dist/presets.d.ts.map +1 -1
  21. package/dist/presets.js +100 -0
  22. package/dist/presets.js.map +1 -1
  23. package/dist/providers/factory.d.ts +16 -1
  24. package/dist/providers/factory.d.ts.map +1 -1
  25. package/dist/providers/factory.js +176 -18
  26. package/dist/providers/factory.js.map +1 -1
  27. package/dist/providers/gemini.d.ts +23 -5
  28. package/dist/providers/gemini.d.ts.map +1 -1
  29. package/dist/providers/gemini.js +247 -119
  30. package/dist/providers/gemini.js.map +1 -1
  31. package/dist/providers/openai.d.ts.map +1 -1
  32. package/dist/providers/openai.js +4 -3
  33. package/dist/providers/openai.js.map +1 -1
  34. package/dist/schema.d.ts +27 -9
  35. package/dist/schema.d.ts.map +1 -1
  36. package/dist/schema.js +197 -62
  37. package/dist/schema.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/auth/device.ts +8 -4
  40. package/src/auth/flows.ts +288 -1
  41. package/src/auth/oauth.ts +8 -3
  42. package/src/auth/with-token.ts +5 -3
  43. package/src/errors.ts +3 -0
  44. package/src/presets.ts +100 -0
  45. package/src/providers/factory.ts +233 -20
  46. package/src/providers/gemini.ts +280 -124
  47. package/src/providers/openai.ts +5 -3
  48. package/src/schema.ts +200 -72
package/src/auth/flows.ts CHANGED
@@ -40,7 +40,14 @@ export interface AuthFlow {
40
40
  }
41
41
 
42
42
  export type AuthFlowId =
43
- "chatgpt" | "kimi-plan" | "grok" | "github-copilot" | "meta" | "openrouter";
43
+ | "chatgpt"
44
+ | "kimi-plan"
45
+ | "grok"
46
+ | "github-copilot"
47
+ | "meta"
48
+ | "openrouter"
49
+ | "opencode-zen"
50
+ | "antigravity";
44
51
 
45
52
  export function createAuthFlow(id: AuthFlowId, host: AuthHost): AuthFlow {
46
53
  switch (id) {
@@ -66,6 +73,10 @@ export function createAuthFlow(id: AuthFlowId, host: AuthHost): AuthFlow {
66
73
  );
67
74
  case "openrouter":
68
75
  return openrouterFlow(host);
76
+ case "opencode-zen":
77
+ return deviceFlow(host, OPENCODE_ZEN_DEVICE, opencodeZenCredential, opencodeZenRefresh(host));
78
+ case "antigravity":
79
+ return antigravityFlow(host);
69
80
  }
70
81
  }
71
82
 
@@ -518,6 +529,282 @@ function openrouterFlow(host: AuthHost): AuthFlow {
518
529
  };
519
530
  }
520
531
 
532
+ // ── OpenCode Console (Zen / Go): device code ──────────────────────────────
533
+ // Matches `opencode auth login opencode` in OpenCode v2 (`packages/core/src/plugin/provider/opencode.ts`)
534
+ // and `cc-proxy auth login opencode-zen --device`. The Console returns a relative
535
+ // `verification_uri_complete` (`/console/device?user_code=…&client_id=opencode-cli`),
536
+ // which `requestDeviceCode` resolves against `deviceUrl`.
537
+
538
+ const OPENCODE_ZEN_DEVICE: DeviceEndpoint = {
539
+ clientId: "opencode-cli",
540
+ deviceUrl: "https://opencode.ai/console/auth/device/code",
541
+ tokenUrl: "https://opencode.ai/console/auth/device/token",
542
+ extra: { supports_org_scope: true },
543
+ encode: "json",
544
+ };
545
+
546
+ function opencodeZenCredential(
547
+ body: Record<string, unknown>,
548
+ fallbackRefresh?: string,
549
+ ): Credential {
550
+ const accessToken = str(body.access_token);
551
+ if (!accessToken) {
552
+ throw authError("token_incomplete", "OpenCode Console's reply has no access token.");
553
+ }
554
+ const refreshToken = str(body.refresh_token) ?? fallbackRefresh ?? "";
555
+ const expiresIn = num(body.expires_in);
556
+ const expiresAt =
557
+ refreshToken && expiresIn !== undefined
558
+ ? Date.now() + expiresIn * 1000 - REFRESH_SKEW_MS
559
+ : NEVER;
560
+ const account = str(body.org_id) ?? accountFromToken(accessToken, "email", "sub");
561
+ return {
562
+ accessToken,
563
+ refreshToken,
564
+ expiresAt,
565
+ ...(account ? { account } : {}),
566
+ };
567
+ }
568
+
569
+ function opencodeZenRefresh(host: AuthHost): AuthFlow["refresh"] {
570
+ return async (credential) => {
571
+ if (!credential.refreshToken) return credential;
572
+ const body = await postToken(
573
+ host,
574
+ OPENCODE_ZEN_DEVICE.tokenUrl,
575
+ {
576
+ client_id: OPENCODE_ZEN_DEVICE.clientId,
577
+ grant_type: "refresh_token",
578
+ refresh_token: credential.refreshToken,
579
+ },
580
+ { encode: "json" },
581
+ );
582
+ const fresh = opencodeZenCredential(body, credential.refreshToken);
583
+ return credential.account && !fresh.account ? { ...fresh, account: credential.account } : fresh;
584
+ };
585
+ }
586
+
587
+ // ── Google Antigravity (Cloud Code Assist): PKCE + project discovery ──────
588
+ // Mirrors `cc-proxy/src/providers/antigravity.rs` and `opencodex/src/oauth/google-antigravity.ts`.
589
+ // Signs in with Google OAuth PKCE using the public Antigravity desktop client,
590
+ // then discovers the Cloud Code Assist `projectId` via `loadCodeAssist` (with
591
+ // `onboardUser` fallback for accounts without a project yet).
592
+
593
+ const ANTIGRAVITY = {
594
+ clientId: [
595
+ "1071006060591-",
596
+ "tmhssin2h21lcre235vtolojh4g403ep",
597
+ ".apps.google",
598
+ "usercontent.com",
599
+ ].join(""),
600
+ clientSecret: ["GOCS", "PX-", "K58FWR486LdL", "J1mLB8sXC4z6qDAf"].join(""),
601
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
602
+ tokenUrl: "https://oauth2.googleapis.com/token",
603
+ redirectUri: "http://127.0.0.1:51121/callback",
604
+ prodApi: "https://cloudcode-pa.googleapis.com",
605
+ dailyApi: "https://daily-cloudcode-pa.googleapis.com",
606
+ apiVersion: "v1internal",
607
+ ideUserAgent:
608
+ "antigravity/ide/2.5.5 (os_type=windows; arch=amd64; aidev_client; auth_method=oauth)",
609
+ scopes: [
610
+ "https://www.googleapis.com/auth/cloud-platform",
611
+ "https://www.googleapis.com/auth/userinfo.email",
612
+ "https://www.googleapis.com/auth/userinfo.profile",
613
+ "https://www.googleapis.com/auth/cclog",
614
+ "https://www.googleapis.com/auth/experimentsandconfigs",
615
+ ].join(" "),
616
+ onboardAttempts: 5,
617
+ onboardPollMs: 2_000,
618
+ } as const;
619
+
620
+ /** The authorize URL to open for Google Antigravity. Exported so tests can pin the exact params. */
621
+ export function antigravityAuthorizeUrl(challenge: string, state: string): string {
622
+ const params = new URLSearchParams({
623
+ response_type: "code",
624
+ client_id: ANTIGRAVITY.clientId,
625
+ redirect_uri: ANTIGRAVITY.redirectUri,
626
+ scope: ANTIGRAVITY.scopes,
627
+ code_challenge: challenge,
628
+ code_challenge_method: "S256",
629
+ access_type: "offline",
630
+ prompt: "consent",
631
+ state,
632
+ });
633
+ return `${ANTIGRAVITY.authorizeUrl}?${params.toString()}`;
634
+ }
635
+
636
+ function extractAntigravityProjectId(data: Record<string, unknown>): string | undefined {
637
+ for (const key of ["cloudaicompanionProject", "projectId", "project"]) {
638
+ const val = data[key];
639
+ const direct = str(val);
640
+ if (direct) return direct;
641
+ const nested = str(asRecord(val).id);
642
+ if (nested) return nested;
643
+ }
644
+ return undefined;
645
+ }
646
+
647
+ /** Discover the Cloud Code Assist project for an Antigravity access token. */
648
+ export async function discoverAntigravityProject(
649
+ host: AuthHost,
650
+ accessToken: string,
651
+ signal?: AbortSignal,
652
+ ): Promise<string | undefined> {
653
+ const headers = {
654
+ Authorization: `Bearer ${accessToken}`,
655
+ Accept: "application/json",
656
+ "Content-Type": "application/json",
657
+ "User-Agent": ANTIGRAVITY.ideUserAgent,
658
+ };
659
+ try {
660
+ const loadRes = await authFetch(
661
+ host,
662
+ `${ANTIGRAVITY.prodApi}/${ANTIGRAVITY.apiVersion}:loadCodeAssist`,
663
+ {
664
+ method: "POST",
665
+ headers,
666
+ body: JSON.stringify({ metadata: { ideType: "ANTIGRAVITY" } }),
667
+ ...(signal ? { signal } : {}),
668
+ },
669
+ );
670
+ if (loadRes.ok) {
671
+ const payload = asRecord(parseJson(await loadRes.text().catch(() => "")));
672
+ const found = extractAntigravityProjectId(payload);
673
+ if (found) return found;
674
+ }
675
+ } catch (err) {
676
+ if (signal?.aborted) throw err;
677
+ }
678
+
679
+ for (let attempt = 0; attempt < ANTIGRAVITY.onboardAttempts; attempt++) {
680
+ if (signal?.aborted) throw signal.reason ?? new Error("Aborted");
681
+ try {
682
+ const onboardRes = await authFetch(
683
+ host,
684
+ `${ANTIGRAVITY.dailyApi}/${ANTIGRAVITY.apiVersion}:onboardUser`,
685
+ {
686
+ method: "POST",
687
+ headers,
688
+ body: JSON.stringify({
689
+ tier_id: "free-tier",
690
+ metadata: {
691
+ ide_type: "ANTIGRAVITY",
692
+ ide_name: "antigravity",
693
+ ide_version: "2.5.5",
694
+ },
695
+ }),
696
+ ...(signal ? { signal } : {}),
697
+ },
698
+ );
699
+ if (onboardRes.ok) {
700
+ const payload = asRecord(parseJson(await onboardRes.text().catch(() => "")));
701
+ if (payload.done === true) {
702
+ const found = extractAntigravityProjectId(asRecord(payload.response));
703
+ if (found) return found;
704
+ }
705
+ } else if (onboardRes.status !== 429 && onboardRes.status < 500) {
706
+ return undefined;
707
+ }
708
+ } catch (err) {
709
+ if (signal?.aborted) throw err;
710
+ }
711
+ if (attempt + 1 < ANTIGRAVITY.onboardAttempts) {
712
+ await waitMs(ANTIGRAVITY.onboardPollMs, signal);
713
+ }
714
+ }
715
+ return undefined;
716
+ }
717
+
718
+ function waitMs(ms: number, signal?: AbortSignal): Promise<void> {
719
+ return new Promise((resolve, reject) => {
720
+ if (signal?.aborted) {
721
+ reject(signal.reason ?? new Error("Aborted"));
722
+ return;
723
+ }
724
+ const timer = setTimeout(() => {
725
+ signal?.removeEventListener("abort", onAbort);
726
+ resolve();
727
+ }, ms);
728
+ const onAbort = () => {
729
+ clearTimeout(timer);
730
+ reject(signal?.reason ?? new Error("Aborted"));
731
+ };
732
+ signal?.addEventListener("abort", onAbort, { once: true });
733
+ });
734
+ }
735
+
736
+ function antigravityFlow(host: AuthHost): AuthFlow {
737
+ const tokenRequest = (params: Record<string, string>) =>
738
+ postToken(
739
+ host,
740
+ ANTIGRAVITY.tokenUrl,
741
+ {
742
+ client_id: ANTIGRAVITY.clientId,
743
+ client_secret: ANTIGRAVITY.clientSecret,
744
+ ...params,
745
+ },
746
+ { encode: "form" },
747
+ );
748
+
749
+ return {
750
+ async signIn(signal, onPrompt) {
751
+ const { verifier, challenge } = await generatePKCE();
752
+ const state = randomState();
753
+ const authorizeUrl = antigravityAuthorizeUrl(challenge, state);
754
+ onPrompt({ url: authorizeUrl });
755
+ const code = await host.captureRedirect({
756
+ authorizeUrl,
757
+ redirectUri: ANTIGRAVITY.redirectUri,
758
+ state,
759
+ signal,
760
+ });
761
+ const body = await tokenRequest({
762
+ grant_type: "authorization_code",
763
+ code,
764
+ redirect_uri: ANTIGRAVITY.redirectUri,
765
+ code_verifier: verifier,
766
+ });
767
+ const credential = antigravityCredential(body);
768
+ const projectId = await discoverAntigravityProject(host, credential.accessToken, signal);
769
+ if (!projectId) {
770
+ throw authError(
771
+ "setup_required",
772
+ "Antigravity login could not discover a Cloud Code Assist project for this Google account.",
773
+ );
774
+ }
775
+ return { ...credential, projectId };
776
+ },
777
+ async refresh(credential) {
778
+ const body = await tokenRequest({
779
+ grant_type: "refresh_token",
780
+ refresh_token: credential.refreshToken,
781
+ });
782
+ const fresh = antigravityCredential(body, credential.refreshToken, credential.account);
783
+ const projectId =
784
+ credential.projectId ??
785
+ (await discoverAntigravityProject(host, fresh.accessToken).catch(() => undefined));
786
+ return projectId ? { ...fresh, projectId } : fresh;
787
+ },
788
+ };
789
+ }
790
+
791
+ function antigravityCredential(
792
+ body: Record<string, unknown>,
793
+ fallbackRefresh?: string,
794
+ fallbackAccount?: string,
795
+ ): Credential {
796
+ const credential = toCredential(
797
+ { ...body, expires_in: num(body.expires_in) ?? GROK_DEFAULT_LIFETIME_SEC },
798
+ fallbackRefresh,
799
+ );
800
+ const idToken = str(body.id_token);
801
+ const account =
802
+ accountFromToken(idToken, "email") ??
803
+ accountFromToken(credential.accessToken, "email") ??
804
+ fallbackAccount;
805
+ return account ? { ...credential, account } : credential;
806
+ }
807
+
521
808
  function parseJson(text: string): unknown {
522
809
  try {
523
810
  return JSON.parse(text);
package/src/auth/oauth.ts CHANGED
@@ -42,6 +42,8 @@ export interface Credential {
42
42
  account?: string;
43
43
  /** ChatGPT only: send it as the `ChatGPT-Account-Id` header. */
44
44
  chatgptAccountId?: string;
45
+ /** Google Antigravity only: the Cloud Code Assist project id for the envelope. */
46
+ projectId?: string;
45
47
  /** Set when the vendor gives each account its own host (Copilot). */
46
48
  baseUrl?: string;
47
49
  }
@@ -137,17 +139,19 @@ function toBase64Url(bytes: Uint8Array): string {
137
139
  export async function postToken(
138
140
  host: AuthHost,
139
141
  url: string,
140
- params: Record<string, string>,
142
+ params: Record<string, string | boolean>,
141
143
  opts: { encode: "json" | "form"; allowErrorBody?: boolean },
142
144
  ): Promise<Record<string, unknown>> {
143
145
  const json = opts.encode === "json";
146
+ const formEntries = () =>
147
+ Object.entries(params).map(([k, v]): [string, string] => [k, String(v)]);
144
148
  const res = await authFetch(host, url, {
145
149
  method: "POST",
146
150
  headers: {
147
151
  "Content-Type": json ? "application/json" : "application/x-www-form-urlencoded",
148
152
  Accept: "application/json",
149
153
  },
150
- body: json ? JSON.stringify(params) : new URLSearchParams(params).toString(),
154
+ body: json ? JSON.stringify(params) : new URLSearchParams(formEntries()).toString(),
151
155
  });
152
156
  const text = await res.text().catch(() => "");
153
157
  const record = asRecord(safeJson(text));
@@ -237,10 +241,11 @@ export function accountFromToken(
237
241
  export function webUrl(
238
242
  value: string | undefined,
239
243
  protocols = ["https:", "http:"],
244
+ base?: string,
240
245
  ): string | undefined {
241
246
  if (!value) return undefined;
242
247
  try {
243
- const url = new URL(value);
248
+ const url = base ? new URL(value, base) : new URL(value);
244
249
  return protocols.includes(url.protocol) ? url.href : undefined;
245
250
  } catch {
246
251
  return undefined;
@@ -71,12 +71,14 @@ export function withToken<C extends Credential>(
71
71
  }
72
72
 
73
73
  function authOf(credential: Credential): TokenAuth {
74
+ const headers: Record<string, string> = {
75
+ ...(credential.chatgptAccountId ? { "ChatGPT-Account-Id": credential.chatgptAccountId } : {}),
76
+ ...(credential.projectId ? { "x-antigravity-project": credential.projectId } : {}),
77
+ };
74
78
  return {
75
79
  apiKey: credential.accessToken,
76
80
  ...(credential.baseUrl ? { baseUrl: credential.baseUrl } : {}),
77
- ...(credential.chatgptAccountId
78
- ? { headers: { "ChatGPT-Account-Id": credential.chatgptAccountId } }
79
- : {}),
81
+ ...(Object.keys(headers).length > 0 ? { headers } : {}),
80
82
  };
81
83
  }
82
84
 
package/src/errors.ts CHANGED
@@ -349,6 +349,9 @@ const CONTEXT_PATTERNS: readonly RegExp[] = [
349
349
  // shipped the same two patterns for the same reason (0.1.51).
350
350
  /model_max_prompt_tokens_exceeded/i,
351
351
  /prompt token count of \d+ exceeds the limit/i,
352
+ // Gemini: "The input token count exceeds the maximum number of tokens allowed 1048576."
353
+ // The generic pattern below stops at "maximum number", so this read as `invalid`.
354
+ /input token count exceeds the maximum number of tokens/i,
352
355
  // Scoped to the thing that overflowed: a bare "exceeds the maximum" also
353
356
  // covers image counts and per-minute token rates, which compaction can't fix.
354
357
  /exceeds? the (?:model'?s? )?maximum (?:input |prompt |context )?(?:tokens?|length|context)/i,
package/src/presets.ts CHANGED
@@ -431,6 +431,82 @@ export const PROVIDER_PRESETS = {
431
431
  // privacy setting, and answers 500 without it.
432
432
  rotation: ["mimo-v2.6-flash", "mimo-v2.5", "glm-5.3-flash", "qwen3.8-flash", "longcat-2.0"],
433
433
  },
434
+ /**
435
+ * OpenCode Zen: free and pay-as-you-go models on `https://opencode.ai/zen/v1`,
436
+ * matching cc-proxy's `opencode-zen` provider.
437
+ *
438
+ * Free models (`big-pickle` and every `-free` id) lead the list and the default
439
+ * rotation. Zen's free tier requires the OpenCode CLI headers, a `ses_` session
440
+ * id and a `read` tool on the request; the factory adds those automatically and
441
+ * retries a free model anonymously if a stale key answers 401.
442
+ *
443
+ * `routes` picks each model's wire from its id: `claude-` and Qwen (except
444
+ * `qwen3.8-max`) on Anthropic `/v1/messages`, `gpt-`, `grok-` and `muse-` on
445
+ * `/v1/responses`, `gemini-` on `/v1/models/<id>:streamGenerateContent?alt=sse`,
446
+ * and everything else on `/v1/chat/completions`.
447
+ */
448
+ "opencode-zen": {
449
+ shape: "openai",
450
+ baseUrl: "https://opencode.ai/zen/v1",
451
+ auth: "bearer",
452
+ defaultModel: "minimax-m3.1-flash-free",
453
+ models: [
454
+ "minimax-m3.1-flash-free",
455
+ "mimo-v2.6-flash-free",
456
+ "deepseek-v4.1-flash-free",
457
+ "glm-5.3-flash-free",
458
+ "big-pickle",
459
+ "mimo-v2.5-free",
460
+ "nemotron-3.5-super-vl-free",
461
+ "nemotron-3.5-lightning-free",
462
+ "longcat-2.5-preview-free",
463
+ "beam-free",
464
+ "ll-alpha-free",
465
+ "exo-free",
466
+ "nemotron-3-ultra-free",
467
+ "claude-sonnet-5-5",
468
+ "claude-opus-5-5",
469
+ "claude-fable-5-1",
470
+ "claude-haiku-5-5",
471
+ "gpt-6-sol",
472
+ "gpt-5.6-luna",
473
+ "gemini-4-argon",
474
+ "gemini-3.8-flash",
475
+ "grok-4.7",
476
+ "glm-5.3-flash",
477
+ "deepseek-v4.1-flash",
478
+ "kimi-k3",
479
+ "minimax-m3",
480
+ "qwen3.8-flash",
481
+ ],
482
+ sessionHeader: "x-opencode-session",
483
+ routes: [
484
+ {
485
+ prefixes: ["claude-", "qwen3.8-flash", "qwen3.7", "qwen3.6", "qwen3.5"],
486
+ shape: "anthropic",
487
+ baseUrl: "https://opencode.ai/zen",
488
+ auth: "key",
489
+ },
490
+ {
491
+ prefixes: ["gpt-", "grok-", "muse-"],
492
+ shape: "responses",
493
+ baseUrl: "https://opencode.ai/zen",
494
+ },
495
+ {
496
+ prefixes: ["gemini-"],
497
+ shape: "gemini",
498
+ baseUrl: "https://opencode.ai/zen/v1",
499
+ auth: "key",
500
+ },
501
+ ],
502
+ rotation: [
503
+ "minimax-m3.1-flash-free",
504
+ "mimo-v2.6-flash-free",
505
+ "deepseek-v4.1-flash-free",
506
+ "big-pickle",
507
+ "glm-5.3-flash-free",
508
+ ],
509
+ },
434
510
  kimi: {
435
511
  shape: "anthropic",
436
512
  baseUrl: "https://api.kimi.ai/coding",
@@ -617,6 +693,30 @@ export const PROVIDER_PRESETS = {
617
693
  },
618
694
  initiatorHeader: true,
619
695
  },
696
+ /**
697
+ * Google Antigravity (Cloud Code Assist) OAuth, matching cc-proxy's
698
+ * `antigravity` provider. Sign in with `createAuthFlow("antigravity", host)`
699
+ * from `@providerkit/core/auth`, which discovers the Cloud Code Assist
700
+ * `projectId` and stores it on the credential; `withToken` and `createRegistry`
701
+ * forward it automatically.
702
+ */
703
+ antigravity: {
704
+ shape: "gemini",
705
+ baseUrl: "https://daily-cloudcode-pa.googleapis.com",
706
+ path: "/v1internal:streamGenerateContent?alt=sse",
707
+ auth: "oauth",
708
+ defaultModel: "gemini-4-argon",
709
+ models: [
710
+ "gemini-4-argon",
711
+ "gemini-3.8-flash",
712
+ "gemini-3.7-flash",
713
+ "gemini-3.1-pro",
714
+ "claude-opus-5-5",
715
+ "claude-sonnet-5-5",
716
+ "claude-sonnet-4-6",
717
+ "claude-opus-4-6-thinking",
718
+ ],
719
+ },
620
720
  } as const satisfies Record<string, ProviderPreset>;
621
721
 
622
722
  export type ProviderPresetId = keyof typeof PROVIDER_PRESETS;