aisubs 0.3.9 → 0.4.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.
@@ -13,6 +13,10 @@ const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
13
13
  const RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
14
14
  const MODELS_URL = "https://chatgpt.com/backend-api/codex/models";
15
15
  const OPENAI_MODELS_URL = "https://developers.openai.com/api/docs/models";
16
+ const CODEX_RELEASE_URL = "https://registry.npmjs.org/@openai/codex/latest";
17
+ /** Used only when the published Codex release cannot be looked up. */
18
+ const FALLBACK_COMPATIBILITY_VERSION = "0.154.0";
19
+ const COMPATIBILITY_VERSION_TTL_MS = 60 * 60_000;
16
20
  const EXPIRY_SKEW_MS = 5 * 60_000;
17
21
  const BROWSER_LOGIN_TIMEOUT_MS = 10 * 60_000;
18
22
  const BROWSER_CALLBACK_PORTS = [1455, 1457];
@@ -197,8 +201,43 @@ async function normalizeChatGptRequest(request) {
197
201
  }
198
202
  export function chatGptProvider(options = {}) {
199
203
  const clientId = options.clientId ?? DEFAULT_CLIENT_ID;
200
- const compatibilityVersion = options.compatibilityVersion ?? "0.154.0";
201
204
  const fetcher = options.fetch ?? globalThis.fetch;
205
+ // The models endpoint hides every model whose minimal Codex client version is
206
+ // newer than `client_version`, so a pinned version silently freezes the catalog.
207
+ // Track the published Codex release instead.
208
+ let versionCache;
209
+ let versionRequest;
210
+ const compatibilityVersion = async () => {
211
+ if (options.compatibilityVersion)
212
+ return options.compatibilityVersion;
213
+ if (versionCache && Date.now() < versionCache.expiresAt)
214
+ return versionCache.value;
215
+ versionRequest ??= (async () => {
216
+ let value = versionCache?.value ?? FALLBACK_COMPATIBILITY_VERSION;
217
+ try {
218
+ const response = await fetcher(CODEX_RELEASE_URL, {
219
+ headers: { accept: "application/json" },
220
+ // Shared by concurrent callers, so it gets its own deadline, not theirs.
221
+ signal: AbortSignal.timeout(5_000),
222
+ });
223
+ if (!response.ok)
224
+ throw new Error(`Codex version lookup failed: ${response.status}`);
225
+ const raw = await response.json();
226
+ const version = isRecord(raw) ? stringValue(raw.version) : undefined;
227
+ if (!version || !/^\d+\.\d+\.\d+/.test(version))
228
+ throw new Error("Codex version lookup returned no release version");
229
+ value = version;
230
+ }
231
+ catch {
232
+ // Keep the last known (or fallback) version; retry after the TTL.
233
+ }
234
+ versionCache = { value, expiresAt: Date.now() + COMPATIBILITY_VERSION_TTL_MS };
235
+ return value;
236
+ })().finally(() => {
237
+ versionRequest = undefined;
238
+ });
239
+ return versionRequest;
240
+ };
202
241
  // Routing tokens belong to one account/session/turn, never to the whole provider.
203
242
  const turnStates = new Map();
204
243
  const turnKey = (request, accountId) => {
@@ -473,7 +512,7 @@ export function chatGptProvider(options = {}) {
473
512
  return bearerRequest(request, credential, {
474
513
  "chatgpt-account-id": accountId,
475
514
  originator: "aisubs",
476
- "user-agent": `aisubs/${compatibilityVersion}`,
515
+ "user-agent": `aisubs/${options.compatibilityVersion ?? versionCache?.value ?? FALLBACK_COMPATIBILITY_VERSION}`,
477
516
  ...(state && state.expiresAt > Date.now() && !request.headers.has("x-codex-turn-state")
478
517
  ? { "x-codex-turn-state": state.token }
479
518
  : {}),
@@ -511,7 +550,7 @@ export function chatGptProvider(options = {}) {
511
550
  },
512
551
  async getModels({ fetch, signal }) {
513
552
  const url = new URL(MODELS_URL);
514
- url.searchParams.set("client_version", compatibilityVersion);
553
+ url.searchParams.set("client_version", await compatibilityVersion());
515
554
  const response = await fetch(url, { headers: { accept: "application/json" }, signal });
516
555
  const raw = await responseJson(response, "ChatGPT models");
517
556
  const models = Array.isArray(raw.models) ? raw.models : [];
@@ -2,6 +2,7 @@ import { randomBytes } from "node:crypto";
2
2
  import { bearerRequest, isRecord, numberValue, requireAllowedHost, responseJson, stringArray, stringValue, } from "../utils.js";
3
3
  const API_HOST = "opencode.ai";
4
4
  const API_KEY_LIFETIME_MS = 365 * 24 * 60 * 60_000;
5
+ const COMPATIBILITY_VERSION_TTL_MS = 60 * 60_000;
5
6
  const ID_PATTERN = /^(ses|msg)_[0-9a-f]{12}[0-9A-Za-z]{14}$/;
6
7
  const ID_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
7
8
  function openCodeUsageMeter(id, label, raw) {
@@ -38,15 +39,44 @@ export function parseOpenCodeGoUsage(raw) {
38
39
  }
39
40
  function openCodeProvider(config, options = {}) {
40
41
  const fetcher = options.fetch ?? globalThis.fetch;
42
+ let versionCache;
43
+ let versionRequest;
41
44
  const compatibilityVersion = async () => {
42
- const response = await fetcher("https://api.github.com/repos/anomalyco/opencode/releases/latest", { headers: { accept: "application/vnd.github+json", "user-agent": "aisubs" } });
43
- if (!response.ok)
44
- throw new Error(`OpenCode version lookup failed: ${response.status}`);
45
- const raw = await response.json();
46
- const version = isRecord(raw) ? stringValue(raw.tag_name)?.replace(/^v/, "") : undefined;
47
- if (!version)
48
- throw new Error("OpenCode version lookup returned no release version");
49
- return version;
45
+ if (versionCache && Date.now() < versionCache.expiresAt) {
46
+ if (versionCache.value)
47
+ return versionCache.value;
48
+ throw versionCache.error;
49
+ }
50
+ if (versionRequest)
51
+ return versionRequest;
52
+ const staleVersion = versionCache?.value;
53
+ versionRequest = (async () => {
54
+ try {
55
+ const response = await fetcher("https://api.github.com/repos/anomalyco/opencode/releases/latest", { headers: { accept: "application/vnd.github+json", "user-agent": "aisubs" } });
56
+ if (!response.ok)
57
+ throw new Error(`OpenCode version lookup failed: ${response.status}`);
58
+ const raw = await response.json();
59
+ const version = isRecord(raw) ? stringValue(raw.tag_name)?.replace(/^v/, "") : undefined;
60
+ if (!version)
61
+ throw new Error("OpenCode version lookup returned no release version");
62
+ versionCache = { value: version, expiresAt: Date.now() + COMPATIBILITY_VERSION_TTL_MS };
63
+ return version;
64
+ }
65
+ catch (error) {
66
+ versionCache = {
67
+ value: staleVersion,
68
+ error,
69
+ expiresAt: Date.now() + COMPATIBILITY_VERSION_TTL_MS,
70
+ };
71
+ if (staleVersion)
72
+ return staleVersion;
73
+ throw error;
74
+ }
75
+ finally {
76
+ versionRequest = undefined;
77
+ }
78
+ })();
79
+ return versionRequest;
50
80
  };
51
81
  const routingIds = new Map();
52
82
  let lastTimestamp = 0;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type ProviderId = "chatgpt" | "claude" | "copilot" | "grok" | (string & {});
1
+ export type ProviderId = "chatgpt" | "copilot" | "grok" | (string & {});
2
2
  export type LoginMode = "browser" | "device" | "external-token" | "external-cli" | "api-key";
3
3
  export interface OAuthCredential {
4
4
  accessToken: string;
@@ -3,7 +3,6 @@ import { join } from "node:path";
3
3
  import { createSubscriptionAuth } from "aisubs";
4
4
  import { SqliteCredentialStore } from "aisubs/node";
5
5
  import { chatGptProvider } from "aisubs/providers/chatgpt";
6
- import { claudeProvider } from "aisubs/providers/claude";
7
6
  import { copilotProvider } from "aisubs/providers/copilot";
8
7
  import { grokProvider } from "aisubs/providers/grok";
9
8
  import { openCodeGoProvider, openCodeZenProvider } from "aisubs/providers/opencode";
@@ -12,7 +11,6 @@ const provider = process.argv[2] ?? "chatgpt";
12
11
  const accountKey = process.argv[3] ?? "default";
13
12
  const providers = [
14
13
  chatGptProvider(),
15
- claudeProvider(),
16
14
  copilotProvider(),
17
15
  grokProvider(),
18
16
  openCodeGoProvider(),
@@ -4,7 +4,6 @@ import { createSubscriptionAuth } from "aisubs";
4
4
  import { createSubscriptionAuthServer } from "aisubs/http";
5
5
  import { SqliteApiKeyStore, SqliteCredentialStore } from "aisubs/node";
6
6
  import { chatGptProvider } from "aisubs/providers/chatgpt";
7
- import { claudeProvider } from "aisubs/providers/claude";
8
7
  import { copilotProvider } from "aisubs/providers/copilot";
9
8
  import { grokProvider } from "aisubs/providers/grok";
10
9
  import { openCodeGoProvider, openCodeZenProvider } from "aisubs/providers/opencode";
@@ -16,7 +15,6 @@ const auth = createSubscriptionAuth({
16
15
  store: new SqliteCredentialStore(database),
17
16
  providers: [
18
17
  chatGptProvider(),
19
- claudeProvider(),
20
18
  copilotProvider(),
21
19
  grokProvider(),
22
20
  openCodeGoProvider(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aisubs",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "description": "Connect AI provider accounts and use those subscriptions from any local tool or as api.",
5
5
  "keywords": [
6
6
  "ai",
@@ -8,7 +8,6 @@
8
8
  "ai-subscription-to-api",
9
9
  "ai-subscriptions",
10
10
  "ai-subtoapi",
11
- "anthropic",
12
11
  "chatgpt",
13
12
  "github-copilot",
14
13
  "grok",
@@ -67,10 +66,6 @@
67
66
  "types": "./dist/providers/chatgpt.d.ts",
68
67
  "import": "./dist/providers/chatgpt.js"
69
68
  },
70
- "./providers/claude": {
71
- "types": "./dist/providers/claude.d.ts",
72
- "import": "./dist/providers/claude.js"
73
- },
74
69
  "./providers/copilot": {
75
70
  "types": "./dist/providers/copilot.d.ts",
76
71
  "import": "./dist/providers/copilot.js"
@@ -11,9 +11,7 @@ const key = (
11
11
  ).trim();
12
12
  const output = process.env.CODEX_CATALOG ?? join(home, ".codex", "aisubs-catalog.json");
13
13
  const codexConfig = process.env.CODEX_CONFIG ?? join(home, ".codex", "config.toml");
14
- const providers = (
15
- process.env.AISUBS_PROVIDERS ?? "chatgpt,claude,copilot,grok,opencode-go,opencode-zen"
16
- )
14
+ const providers = (process.env.AISUBS_PROVIDERS ?? "chatgpt,copilot,grok,opencode-go,opencode-zen")
17
15
  .split(",")
18
16
  .map((value) => value.trim())
19
17
  .filter(Boolean);
@@ -1,2 +0,0 @@
1
- /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
2
- @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-sky-300:oklch(82.8% .111 230.318);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-fuchsia-300:oklch(83.3% .145 321.434);--color-zinc-50:oklch(98.5% 0 none);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-black:900;--tracking-tight:-.025em;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-x-\[14px\]{inset-inline:14px}.top-0{top:0}.top-3{top:calc(var(--spacing) * 3)}.top-\[14px\]{top:14px}.right-2\.5{right:calc(var(--spacing) * 2.5)}.right-3{right:calc(var(--spacing) * 3)}.z-20{z-index:20}.col-span-full{grid-column:1/-1}.col-start-1{grid-column-start:1}.col-start-2{grid-column-start:2}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.m-0{margin:0}.m-\[-18px\]{margin:-18px}.mx-auto{margin-inline:auto}.my-3{margin-block:calc(var(--spacing) * 3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-2\.5{margin-top:calc(var(--spacing) * 2.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-\[18px\]{margin-top:18px}.mt-auto{margin-top:auto}.mt-px{margin-top:1px}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-\[18px\]{margin-bottom:18px}.mb-\[22px\]{margin-bottom:22px}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-\[30px\]{width:30px;height:30px}.size-\[38px\]{width:38px;height:38px}.size-\[54px\]{width:54px;height:54px}.size-\[58px\]{width:58px;height:58px}.h-3{height:calc(var(--spacing) * 3)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-44{height:calc(var(--spacing) * 44)}.h-\[3px\]{height:3px}.h-\[30px\]{height:30px}.h-\[34px\]{height:34px}.h-dvh{height:100dvh}.h-full{height:100%}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-\[92dvh\]{max-height:92dvh}.max-h-\[510px\]{max-height:510px}.max-h-none{max-height:none}.min-h-6{min-height:calc(var(--spacing) * 6)}.min-h-7{min-height:calc(var(--spacing) * 7)}.min-h-8{min-height:calc(var(--spacing) * 8)}.min-h-9{min-height:calc(var(--spacing) * 9)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-12{min-height:calc(var(--spacing) * 12)}.min-h-72{min-height:calc(var(--spacing) * 72)}.min-h-\[43px\]{min-height:43px}.min-h-\[54px\]{min-height:54px}.min-h-\[58px\]{min-height:58px}.min-h-\[60px\]{min-height:60px}.min-h-\[390px\]{min-height:390px}.min-h-\[420px\]{min-height:420px}.min-h-dvh{min-height:100dvh}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-\[330px\]{max-width:330px}.max-w-\[390px\]{max-width:390px}.max-w-\[420px\]{max-width:420px}.max-w-\[520px\]{max-width:520px}.max-w-\[560px\]{max-width:560px}.max-w-\[630px\]{max-width:630px}.max-w-\[700px\]{max-width:700px}.max-w-\[900px\]{max-width:900px}.max-w-\[1180px\]{max-width:1180px}.max-w-none{max-width:none}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-pointer{cursor:pointer}.list-none{list-style-type:none}.appearance-none{appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[1fr_auto\]{grid-template-columns:1fr auto}.grid-cols-\[minmax\(0\,1fr\)_auto\]{grid-template-columns:minmax(0,1fr) auto}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-7{gap:calc(var(--spacing) * 7)}.gap-\[18px\]{gap:18px}.gap-px{gap:1px}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-zinc-200>:not(:last-child)){border-color:var(--color-zinc-200)}.justify-self-end{justify-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[10px\]{border-radius:10px}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-amber-600\/40{border-color:#dd740066}@supports (color:color-mix(in lab, red, red)){.border-amber-600\/40{border-color:color-mix(in oklab, var(--color-amber-600) 40%, transparent)}}.border-blue-600\/40{border-color:#155dfc66}@supports (color:color-mix(in lab, red, red)){.border-blue-600\/40{border-color:color-mix(in oklab, var(--color-blue-600) 40%, transparent)}}.border-emerald-500\/30{border-color:#00bb7f4d}@supports (color:color-mix(in lab, red, red)){.border-emerald-500\/30{border-color:color-mix(in oklab, var(--color-emerald-500) 30%, transparent)}}.border-emerald-500\/40{border-color:#00bb7f66}@supports (color:color-mix(in lab, red, red)){.border-emerald-500\/40{border-color:color-mix(in oklab, var(--color-emerald-500) 40%, transparent)}}.border-emerald-600\/40{border-color:#00976766}@supports (color:color-mix(in lab, red, red)){.border-emerald-600\/40{border-color:color-mix(in oklab, var(--color-emerald-600) 40%, transparent)}}.border-orange-500\/40{border-color:#fe6e0066}@supports (color:color-mix(in lab, red, red)){.border-orange-500\/40{border-color:color-mix(in oklab, var(--color-orange-500) 40%, transparent)}}.border-red-500\/30{border-color:#fb2c364d}@supports (color:color-mix(in lab, red, red)){.border-red-500\/30{border-color:color-mix(in oklab, var(--color-red-500) 30%, transparent)}}.border-red-600\/40{border-color:#e4001466}@supports (color:color-mix(in lab, red, red)){.border-red-600\/40{border-color:color-mix(in oklab, var(--color-red-600) 40%, transparent)}}.border-stone-400\/40{border-color:#a6a09b66}@supports (color:color-mix(in lab, red, red)){.border-stone-400\/40{border-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent)}}.border-transparent{border-color:#0000}.border-violet-500\/40{border-color:#8d54ff66}@supports (color:color-mix(in lab, red, red)){.border-violet-500\/40{border-color:color-mix(in oklab, var(--color-violet-500) 40%, transparent)}}.border-white\/5{border-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.border-white\/5{border-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.border-white\/15{border-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.border-white\/15{border-color:color-mix(in oklab, var(--color-white) 15%, transparent)}}.border-white\/20{border-color:#fff3}@supports (color:color-mix(in lab, red, red)){.border-white\/20{border-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-200\/80{border-color:#e4e4e7cc}@supports (color:color-mix(in lab, red, red)){.border-zinc-200\/80{border-color:color-mix(in oklab, var(--color-zinc-200) 80%, transparent)}}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-400\/40{border-color:#9f9fa966}@supports (color:color-mix(in lab, red, red)){.border-zinc-400\/40{border-color:color-mix(in oklab, var(--color-zinc-400) 40%, transparent)}}.border-zinc-700{border-color:var(--color-zinc-700)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-500\/10{background-color:#f99c001a}@supports (color:color-mix(in lab, red, red)){.bg-amber-500\/10{background-color:color-mix(in oklab, var(--color-amber-500) 10%, transparent)}}.bg-blue-500\/10{background-color:#3080ff1a}@supports (color:color-mix(in lab, red, red)){.bg-blue-500\/10{background-color:color-mix(in oklab, var(--color-blue-500) 10%, transparent)}}.bg-current{background-color:currentColor}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-emerald-500\/10{background-color:#00bb7f1a}@supports (color:color-mix(in lab, red, red)){.bg-emerald-500\/10{background-color:color-mix(in oklab, var(--color-emerald-500) 10%, transparent)}}.bg-orange-500\/10{background-color:#fe6e001a}@supports (color:color-mix(in lab, red, red)){.bg-orange-500\/10{background-color:color-mix(in oklab, var(--color-orange-500) 10%, transparent)}}.bg-red-500\/10{background-color:#fb2c361a}@supports (color:color-mix(in lab, red, red)){.bg-red-500\/10{background-color:color-mix(in oklab, var(--color-red-500) 10%, transparent)}}.bg-red-600{background-color:var(--color-red-600)}.bg-stone-500\/10{background-color:#79716b1a}@supports (color:color-mix(in lab, red, red)){.bg-stone-500\/10{background-color:color-mix(in oklab, var(--color-stone-500) 10%, transparent)}}.bg-transparent{background-color:#0000}.bg-violet-500\/10{background-color:#8d54ff1a}@supports (color:color-mix(in lab, red, red)){.bg-violet-500\/10{background-color:color-mix(in oklab, var(--color-violet-500) 10%, transparent)}}.bg-white{background-color:var(--color-white)}.bg-white\/5{background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.bg-white\/5{background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.bg-white\/80{background-color:#fffc}@supports (color:color-mix(in lab, red, red)){.bg-white\/80{background-color:color-mix(in oklab, var(--color-white) 80%, transparent)}}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-100\/50{background-color:#f4f4f580}@supports (color:color-mix(in lab, red, red)){.bg-zinc-100\/50{background-color:color-mix(in oklab, var(--color-zinc-100) 50%, transparent)}}.bg-zinc-100\/70{background-color:#f4f4f5b3}@supports (color:color-mix(in lab, red, red)){.bg-zinc-100\/70{background-color:color-mix(in oklab, var(--color-zinc-100) 70%, transparent)}}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-300{background-color:var(--color-zinc-300)}.bg-zinc-500\/10{background-color:#71717b1a}@supports (color:color-mix(in lab, red, red)){.bg-zinc-500\/10{background-color:color-mix(in oklab, var(--color-zinc-500) 10%, transparent)}}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.\[mask-image\:url\(\'\/logos\/anthropic\.svg\'\)\]{-webkit-mask-image:url(/logos/anthropic.svg);mask-image:url(/logos/anthropic.svg)}.\[mask-image\:url\(\'\/logos\/github-copilot\.svg\'\)\]{-webkit-mask-image:url(/logos/github-copilot.svg);mask-image:url(/logos/github-copilot.svg)}.\[mask-image\:url\(\'\/logos\/openai\.svg\'\)\]{-webkit-mask-image:url(/logos/openai.svg);mask-image:url(/logos/openai.svg)}.\[mask-image\:url\(\'\/logos\/xai\.svg\'\)\]{-webkit-mask-image:url(/logos/xai.svg);mask-image:url(/logos/xai.svg)}.\[mask-size\:contain\]{-webkit-mask-size:contain;mask-size:contain}.\[mask-position\:center\]{-webkit-mask-position:50%;mask-position:50%}.\[mask-repeat\:no-repeat\]{-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.p-0{padding:0}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-3\.5{padding:calc(var(--spacing) * 3.5)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-9{padding:calc(var(--spacing) * 9)}.p-\[18px\]{padding:18px}.p-\[22px\]{padding:22px}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-\[18px\]{padding-inline:18px}.py-0{padding-block:0}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pt-24{padding-top:calc(var(--spacing) * 24)}.pt-\[62px\]{padding-top:62px}.pt-\[106px\]{padding-top:106px}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-\[18px\]{padding-right:18px}.pb-12{padding-bottom:calc(var(--spacing) * 12)}.pl-1{padding-left:var(--spacing)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-\[19px\]{font-size:19px}.text-\[clamp\(26px\,3vw\,36px\)\]{font-size:clamp(26px,3vw,36px)}.text-\[clamp\(28px\,3vw\,36px\)\]{font-size:clamp(28px,3vw,36px)}.leading-\[1\.05\]{--tw-leading:1.05;line-height:1.05}.leading-\[1\.7\]{--tw-leading:1.7;line-height:1.7}.leading-\[1\.08\]{--tw-leading:1.08;line-height:1.08}.leading-none{--tw-leading:1;line-height:1}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[-0\.05em\]{--tw-tracking:-.05em;letter-spacing:-.05em}.tracking-\[-0\.045em\]{--tw-tracking:-.045em;letter-spacing:-.045em}.tracking-\[0\.1em\]{--tw-tracking:.1em;letter-spacing:.1em}.tracking-\[0\.05em\]{--tw-tracking:.05em;letter-spacing:.05em}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.wrap-break-word{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.\[tab-size\:2\]{tab-size:2}.text-amber-300{color:var(--color-amber-300)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-blue-700{color:var(--color-blue-700)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-300\/70{color:#5ee9b5b3}@supports (color:color-mix(in lab, red, red)){.text-emerald-300\/70{color:color-mix(in oklab, var(--color-emerald-300) 70%, transparent)}}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-fuchsia-300{color:var(--color-fuchsia-300)}.text-inherit{color:inherit}.text-orange-600{color:var(--color-orange-600)}.text-red-300{color:var(--color-red-300)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-sky-300{color:var(--color-sky-300)}.text-violet-600{color:var(--color-violet-600)}.text-white{color:var(--color-white)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.uppercase{text-transform:uppercase}.not-italic{font-style:normal}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-xl{--tw-backdrop-blur:blur(var(--blur-xl));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.group-open\:rotate-90:is(:where(.group):is([open],:popover-open,:open) *){rotate:90deg}.placeholder\:text-zinc-500::placeholder{color:var(--color-zinc-500)}.backdrop\:bg-zinc-950\/35::backdrop{background-color:#09090b59}@supports (color:color-mix(in lab, red, red)){.backdrop\:bg-zinc-950\/35::backdrop{background-color:color-mix(in oklab, var(--color-zinc-950) 35%, transparent)}}.backdrop\:backdrop-blur-sm::backdrop{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.open\:grid:is([open],:popover-open,:open){display:grid}.open\:place-items-center:is([open],:popover-open,:open){place-items:center}@media (hover:hover){.hover\:-translate-y-0\.5:hover{--tw-translate-y:calc(var(--spacing) * -.5);translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:-translate-y-px:hover{--tw-translate-y:-1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.hover\:border-red-500\/50:hover{border-color:#fb2c3680}@supports (color:color-mix(in lab, red, red)){.hover\:border-red-500\/50:hover{border-color:color-mix(in oklab, var(--color-red-500) 50%, transparent)}}.hover\:border-white\/30:hover{border-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.hover\:border-white\/30:hover{border-color:color-mix(in oklab, var(--color-white) 30%, transparent)}}.hover\:border-zinc-200:hover{border-color:var(--color-zinc-200)}.hover\:border-zinc-300:hover{border-color:var(--color-zinc-300)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-zinc-100:hover{background-color:var(--color-zinc-100)}.hover\:bg-zinc-700:hover{background-color:var(--color-zinc-700)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-white:hover{color:var(--color-white)}.hover\:text-zinc-50:hover{color:var(--color-zinc-50)}.hover\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\:text-zinc-950:hover{color:var(--color-zinc-950)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}}.focus\:border-zinc-500:focus{border-color:var(--color-zinc-500)}.focus\:ring-3:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-zinc-300\/50:focus{--tw-ring-color:#d4d4d880}@supports (color:color-mix(in lab, red, red)){.focus\:ring-zinc-300\/50:focus{--tw-ring-color:color-mix(in oklab, var(--color-zinc-300) 50%, transparent)}}.active\:translate-y-px:active{--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.aria-pressed\:bg-zinc-900[aria-pressed=true]{background-color:var(--color-zinc-900)}.aria-pressed\:text-zinc-50[aria-pressed=true]{color:var(--color-zinc-50)}.aria-selected\:border-zinc-100[aria-selected=true]{border-color:var(--color-zinc-100)}.aria-selected\:text-zinc-50[aria-selected=true]{color:var(--color-zinc-50)}@media (width>=40rem){.sm\:inset-x-\[18px\]{inset-inline:18px}.sm\:top-4{top:calc(var(--spacing) * 4)}.sm\:col-auto{grid-column:auto}.sm\:row-auto{grid-row:auto}.sm\:mb-7{margin-bottom:calc(var(--spacing) * 7)}.sm\:flex{display:flex}.sm\:inline{display:inline}.sm\:max-h-\[calc\(100dvh-48px\)\]{max-height:calc(100dvh - 48px)}.sm\:w-\[220px\]{width:220px}.sm\:w-auto{width:auto}.sm\:min-w-\[190px\]{min-width:190px}.sm\:flex-none{flex:none}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-\[130px_1fr\]{grid-template-columns:130px 1fr}.sm\:grid-cols-\[minmax\(0\,1fr\)_160px\]{grid-template-columns:minmax(0,1fr) 160px}.sm\:grid-cols-\[minmax\(0\,1fr\)_auto_auto_auto\]{grid-template-columns:minmax(0,1fr) auto auto auto}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:gap-5{gap:calc(var(--spacing) * 5)}.sm\:rounded-xl{border-radius:var(--radius-xl)}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:pt-\[22px\]{padding-top:22px}.sm\:pr-\[116px\]{padding-right:116px}.sm\:pb-16{padding-bottom:calc(var(--spacing) * 16)}}@media (width>=48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-\[minmax\(220px\,1fr\)_minmax\(240px\,380px\)_auto\]{grid-template-columns:minmax(220px,1fr) minmax(240px,380px) auto}.md\:items-center{align-items:center}.md\:justify-end{justify-content:flex-end}.md\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.md\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}}@media (width>=64rem){.lg\:grid-cols-\[minmax\(250px\,\.36fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(250px,.36fr) minmax(0,1fr)}}.dark\:block:where(.dark,.dark *){display:block}.dark\:hidden:where(.dark,.dark *){display:none}:where(.dark\:divide-zinc-700:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-zinc-700)}:where(.dark\:divide-zinc-800:where(.dark,.dark *)>:not(:last-child)){border-color:var(--color-zinc-800)}.dark\:border-stone-500\/40:where(.dark,.dark *){border-color:#79716b66}@supports (color:color-mix(in lab, red, red)){.dark\:border-stone-500\/40:where(.dark,.dark *){border-color:color-mix(in oklab, var(--color-stone-500) 40%, transparent)}}.dark\:border-white\/20:where(.dark,.dark *){border-color:#fff3}@supports (color:color-mix(in lab, red, red)){.dark\:border-white\/20:where(.dark,.dark *){border-color:color-mix(in oklab, var(--color-white) 20%, transparent)}}.dark\:border-zinc-700:where(.dark,.dark *){border-color:var(--color-zinc-700)}.dark\:border-zinc-800:where(.dark,.dark *){border-color:var(--color-zinc-800)}.dark\:bg-stone-400\/10:where(.dark,.dark *){background-color:#a6a09b1a}@supports (color:color-mix(in lab, red, red)){.dark\:bg-stone-400\/10:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-stone-400) 10%, transparent)}}.dark\:bg-white\/5:where(.dark,.dark *){background-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-white\/5:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-white) 5%, transparent)}}.dark\:bg-zinc-100:where(.dark,.dark *){background-color:var(--color-zinc-100)}.dark\:bg-zinc-700:where(.dark,.dark *){background-color:var(--color-zinc-700)}.dark\:bg-zinc-800:where(.dark,.dark *){background-color:var(--color-zinc-800)}.dark\:bg-zinc-800\/40:where(.dark,.dark *){background-color:#27272a66}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-800\/40:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-zinc-800) 40%, transparent)}}.dark\:bg-zinc-800\/50:where(.dark,.dark *){background-color:#27272a80}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-800\/50:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-zinc-800) 50%, transparent)}}.dark\:bg-zinc-900:where(.dark,.dark *){background-color:var(--color-zinc-900)}.dark\:bg-zinc-900\/80:where(.dark,.dark *){background-color:#18181bcc}@supports (color:color-mix(in lab, red, red)){.dark\:bg-zinc-900\/80:where(.dark,.dark *){background-color:color-mix(in oklab, var(--color-zinc-900) 80%, transparent)}}.dark\:bg-zinc-950:where(.dark,.dark *){background-color:var(--color-zinc-950)}.dark\:text-amber-300:where(.dark,.dark *){color:var(--color-amber-300)}.dark\:text-blue-300:where(.dark,.dark *){color:var(--color-blue-300)}.dark\:text-emerald-200:where(.dark,.dark *){color:var(--color-emerald-200)}.dark\:text-emerald-300:where(.dark,.dark *){color:var(--color-emerald-300)}.dark\:text-orange-300:where(.dark,.dark *){color:var(--color-orange-300)}.dark\:text-red-200:where(.dark,.dark *){color:var(--color-red-200)}.dark\:text-red-300:where(.dark,.dark *){color:var(--color-red-300)}.dark\:text-red-400:where(.dark,.dark *){color:var(--color-red-400)}.dark\:text-violet-300:where(.dark,.dark *){color:var(--color-violet-300)}.dark\:text-zinc-50:where(.dark,.dark *){color:var(--color-zinc-50)}.dark\:text-zinc-100:where(.dark,.dark *){color:var(--color-zinc-100)}.dark\:text-zinc-200:where(.dark,.dark *){color:var(--color-zinc-200)}.dark\:text-zinc-300:where(.dark,.dark *){color:var(--color-zinc-300)}.dark\:text-zinc-400:where(.dark,.dark *){color:var(--color-zinc-400)}.dark\:text-zinc-900:where(.dark,.dark *){color:var(--color-zinc-900)}.dark\:invert-0:where(.dark,.dark *){--tw-invert:invert(0%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.dark\:placeholder\:text-zinc-400:where(.dark,.dark *)::placeholder{color:var(--color-zinc-400)}@media (hover:hover){.dark\:hover\:border-zinc-600:where(.dark,.dark *):hover{border-color:var(--color-zinc-600)}.dark\:hover\:border-zinc-700:where(.dark,.dark *):hover{border-color:var(--color-zinc-700)}.dark\:hover\:bg-zinc-300:where(.dark,.dark *):hover{background-color:var(--color-zinc-300)}.dark\:hover\:bg-zinc-800:where(.dark,.dark *):hover{background-color:var(--color-zinc-800)}.dark\:hover\:text-red-400:where(.dark,.dark *):hover{color:var(--color-red-400)}.dark\:hover\:text-zinc-50:where(.dark,.dark *):hover{color:var(--color-zinc-50)}}.dark\:focus\:border-zinc-500:where(.dark,.dark *):focus{border-color:var(--color-zinc-500)}.dark\:focus\:ring-zinc-700:where(.dark,.dark *):focus{--tw-ring-color:var(--color-zinc-700)}.dark\:aria-pressed\:bg-zinc-100:where(.dark,.dark *)[aria-pressed=true]{background-color:var(--color-zinc-100)}.dark\:aria-pressed\:text-zinc-900:where(.dark,.dark *)[aria-pressed=true]{color:var(--color-zinc-900)}.\[\&_dd\]\:m-0 dd{margin:0}.\[\&_dd\]\:text-\[10px\] dd{font-size:10px}.\[\&_dd\]\:text-\[11px\] dd{font-size:11px}.\[\&_dd\]\:font-semibold dd{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.\[\&_dd\]\:wrap-break-word dd{overflow-wrap:break-word}.\[\&_dt\]\:mb-1 dt{margin-bottom:var(--spacing)}.\[\&_dt\]\:text-\[9px\] dt{font-size:9px}.\[\&_dt\]\:font-bold dt{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.\[\&_dt\]\:tracking-\[0\.05em\] dt{--tw-tracking:.05em;letter-spacing:.05em}.\[\&_dt\]\:text-zinc-500 dt{color:var(--color-zinc-500)}.\[\&_dt\]\:uppercase dt{text-transform:uppercase}.dark\:\[\&_dt\]\:text-zinc-400:where(.dark,.dark *) dt{color:var(--color-zinc-400)}.\[\&\:\:-webkit-details-marker\]\:hidden::-webkit-details-marker{display:none}.\[\&\>div\]\:min-w-0>div{min-width:0}.\[\&\>div\]\:border-t>div{border-top-style:var(--tw-border-style);border-top-width:1px}.\[\&\>div\]\:border-zinc-200>div{border-color:var(--color-zinc-200)}.\[\&\>div\]\:p-4>div{padding:calc(var(--spacing) * 4)}.dark\:\[\&\>div\]\:border-zinc-800:where(.dark,.dark *)>div{border-color:var(--color-zinc-800)}@media (width>=40rem){.\[\&\>div\:nth-child\(-n\+2\)\]\:sm\:border-t-0>div:nth-child(-n+2){border-top-style:var(--tw-border-style);border-top-width:0}.\[\&\>div\:nth-child\(even\)\]\:sm\:border-l>div:nth-child(2n){border-left-style:var(--tw-border-style);border-left-width:1px}.\[\&\>div\:nth-child\(even\)\]\:sm\:border-zinc-200>div:nth-child(2n){border-color:var(--color-zinc-200)}.dark\:\[\&\>div\:nth-child\(even\)\]\:sm\:border-zinc-800:where(.dark,.dark *)>div:nth-child(2n){border-color:var(--color-zinc-800)}.\[\&\>div\:nth-child\(n\+3\)\]\:sm\:border-t>div:nth-child(n+3){border-top-style:var(--tw-border-style);border-top-width:1px}.\[\&\>div\:nth-child\(n\+3\)\]\:sm\:border-zinc-200>div:nth-child(n+3){border-color:var(--color-zinc-200)}.dark\:\[\&\>div\:nth-child\(n\+3\)\]\:sm\:border-zinc-800:where(.dark,.dark *)>div:nth-child(n+3){border-color:var(--color-zinc-800)}.\[\&\>div\:nth-child\(odd\)\]\:sm\:border-r>div:nth-child(odd){border-right-style:var(--tw-border-style);border-right-width:1px}}}body:has(dialog[open]){overflow:hidden}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition:none!important;animation:none!important}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
@@ -1,7 +0,0 @@
1
- import type { ProviderAdapter } from "../types.js";
2
- export interface ClaudeProviderOptions {
3
- clientId?: string;
4
- compatibilityVersion?: string;
5
- fetch?: typeof globalThis.fetch;
6
- }
7
- export declare function claudeProvider(options?: ClaudeProviderOptions): ProviderAdapter;
@@ -1,291 +0,0 @@
1
- import { createHash, randomBytes } from "node:crypto";
2
- import { createServer } from "node:http";
3
- import { bearerRequest, isRecord, numberValue, requireAllowedHost, responseJson, stringValue, } from "../utils.js";
4
- const AUTHORIZE_URL = "https://claude.ai/oauth/authorize";
5
- const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
6
- const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
7
- const SCOPE = "user:inference user:profile user:sessions:claude_code user:mcp_servers user:file_upload";
8
- const EXPIRY_SKEW_MS = 5 * 60_000;
9
- const LOGIN_TIMEOUT_MS = 10 * 60_000;
10
- const CLAUDE_CODE_SYSTEM = "You are Claude Code, Anthropic's official CLI for Claude.";
11
- class ClaudeTokenError extends Error {
12
- status;
13
- code;
14
- constructor(status, code) {
15
- super(`Claude token exchange failed (${code ?? status})`);
16
- this.status = status;
17
- this.code = code;
18
- }
19
- }
20
- function planLabel(value) {
21
- const plan = stringValue(value);
22
- return plan
23
- ?.split(/[_-]/)
24
- .filter(Boolean)
25
- .map((part) => part[0]?.toUpperCase() + part.slice(1))
26
- .join(" ");
27
- }
28
- function credentialFromTokens(raw, previous) {
29
- const accessToken = stringValue(raw.access_token);
30
- const refreshToken = stringValue(raw.refresh_token) ?? previous?.refreshToken;
31
- if (!accessToken || !refreshToken)
32
- throw new Error("Claude token response is incomplete");
33
- const organization = isRecord(raw.organization) ? raw.organization : {};
34
- const expiresAt = numberValue(raw.expires_at);
35
- const expiresIn = numberValue(raw.expires_in);
36
- return {
37
- accessToken,
38
- refreshToken,
39
- expiresAt: expiresAt && expiresAt > Date.now()
40
- ? expiresAt - EXPIRY_SKEW_MS
41
- : Date.now() + Math.max(60, expiresIn ?? 8 * 60 * 60) * 1000 - EXPIRY_SKEW_MS,
42
- account: {
43
- id: stringValue(organization.uuid) ?? stringValue(organization.id) ?? previous?.account?.id,
44
- label: stringValue(organization.name) ?? previous?.account?.label,
45
- email: stringValue(raw.email) ?? previous?.account?.email,
46
- plan: planLabel(raw.subscription_type) ?? previous?.account?.plan,
47
- },
48
- };
49
- }
50
- export function claudeProvider(options = {}) {
51
- const clientId = options.clientId ?? CLIENT_ID;
52
- const compatibilityVersion = options.compatibilityVersion ?? "2.1.231";
53
- const fetcher = options.fetch ?? globalThis.fetch;
54
- async function exchange(code, verifier, redirectUri, state, signal) {
55
- const response = await fetcher(TOKEN_URL, {
56
- method: "POST",
57
- headers: { accept: "application/json", "content-type": "application/x-www-form-urlencoded" },
58
- body: new URLSearchParams({
59
- grant_type: "authorization_code",
60
- code,
61
- client_id: clientId,
62
- redirect_uri: redirectUri,
63
- code_verifier: verifier,
64
- state,
65
- }),
66
- signal,
67
- });
68
- if (!response.ok) {
69
- const raw = await response.json().catch(() => null);
70
- const record = isRecord(raw) ? raw : {};
71
- throw new ClaudeTokenError(response.status, stringValue(record.error));
72
- }
73
- return credentialFromTokens(await responseJson(response, "Claude token exchange"));
74
- }
75
- return {
76
- id: "claude",
77
- name: "Claude",
78
- description: "Claude Pro, Max, Team, or Enterprise subscription access with browser sign-in.",
79
- homepage: "https://claude.ai",
80
- allowedHosts: ["api.anthropic.com"],
81
- proxyBaseUrl: "https://api.anthropic.com/v1",
82
- loginModes: ["browser"],
83
- async startLogin(signal) {
84
- const verifier = randomBytes(32).toString("base64url");
85
- const challenge = createHash("sha256").update(verifier).digest("base64url");
86
- const state = randomBytes(32).toString("base64url");
87
- let finish;
88
- let timer;
89
- const server = createServer(async (request, response) => {
90
- const callback = new URL(request.url ?? "/", "http://localhost");
91
- if (callback.pathname !== "/callback") {
92
- response.writeHead(404).end();
93
- return;
94
- }
95
- if (callback.searchParams.get("state") !== state) {
96
- response.writeHead(400, {
97
- "content-type": "text/plain; charset=utf-8",
98
- "cache-control": "no-store",
99
- });
100
- response.end("Claude sign-in failed. You can close this tab and return to AI Subs.");
101
- return finish(new Error("Claude OAuth state did not match"));
102
- }
103
- const error = callback.searchParams.get("error");
104
- const code = callback.searchParams.get("code");
105
- if (error || !code) {
106
- response.writeHead(400, {
107
- "content-type": "text/plain; charset=utf-8",
108
- "cache-control": "no-store",
109
- });
110
- response.end("Claude sign-in failed. You can close this tab and return to AI Subs.");
111
- return finish(new Error(error ?? "Claude authorization code is missing"));
112
- }
113
- try {
114
- const address = server.address();
115
- if (!address || typeof address === "string")
116
- throw new Error("Claude callback is unavailable");
117
- const credential = await exchange(code, verifier, `http://localhost:${address.port}/callback`, state, signal);
118
- response.writeHead(200, {
119
- "content-type": "text/plain; charset=utf-8",
120
- "cache-control": "no-store",
121
- });
122
- response.end("Claude connected. You can close this tab and return to AI Subs.");
123
- finish(undefined, credential);
124
- }
125
- catch (cause) {
126
- response.writeHead(400, {
127
- "content-type": "text/plain; charset=utf-8",
128
- "cache-control": "no-store",
129
- });
130
- response.end("Claude sign-in failed. You can close this tab and return to AI Subs.");
131
- finish(cause);
132
- }
133
- });
134
- const complete = new Promise((resolve, reject) => {
135
- finish = (error, credential) => {
136
- if (timer)
137
- clearTimeout(timer);
138
- signal.removeEventListener("abort", abort);
139
- if (server.listening)
140
- server.close();
141
- if (error)
142
- reject(error);
143
- else
144
- resolve(credential);
145
- };
146
- });
147
- let settled = false;
148
- const originalFinish = finish;
149
- finish = (error, credential) => {
150
- if (settled)
151
- return;
152
- settled = true;
153
- originalFinish(error, credential);
154
- };
155
- const abort = () => finish(new Error("Claude browser authorization cancelled"));
156
- signal.addEventListener("abort", abort, { once: true });
157
- await new Promise((resolve, reject) => {
158
- server.once("error", reject);
159
- server.listen(0, "127.0.0.1", () => {
160
- server.off("error", reject);
161
- resolve();
162
- });
163
- });
164
- const address = server.address();
165
- if (!address || typeof address === "string")
166
- throw new Error("Unable to start Claude callback");
167
- const redirectUri = `http://localhost:${address.port}/callback`;
168
- timer = setTimeout(() => finish(new Error("Claude browser authorization timed out")), LOGIN_TIMEOUT_MS);
169
- timer.unref();
170
- const authorization = new URL(AUTHORIZE_URL);
171
- authorization.search = new URLSearchParams({
172
- code: "true",
173
- client_id: clientId,
174
- response_type: "code",
175
- redirect_uri: redirectUri,
176
- scope: SCOPE,
177
- code_challenge: challenge,
178
- code_challenge_method: "S256",
179
- state,
180
- }).toString();
181
- return {
182
- prompt: {
183
- mode: "browser",
184
- authorizationUri: authorization.toString(),
185
- expiresAt: Date.now() + LOGIN_TIMEOUT_MS,
186
- },
187
- complete,
188
- };
189
- },
190
- async refresh(credential, signal) {
191
- if (!credential.refreshToken)
192
- throw new Error("Claude refresh token is missing");
193
- const response = await fetcher(TOKEN_URL, {
194
- method: "POST",
195
- headers: {
196
- accept: "application/json",
197
- "content-type": "application/x-www-form-urlencoded",
198
- },
199
- body: new URLSearchParams({
200
- grant_type: "refresh_token",
201
- client_id: clientId,
202
- refresh_token: credential.refreshToken,
203
- }),
204
- signal,
205
- });
206
- if (!response.ok) {
207
- const raw = await response.json().catch(() => null);
208
- const record = isRecord(raw) ? raw : {};
209
- throw new ClaudeTokenError(response.status, stringValue(record.error));
210
- }
211
- return credentialFromTokens(await responseJson(response, "Claude token refresh"), credential);
212
- },
213
- async authorize(request, credential) {
214
- requireAllowedHost(request, ["api.anthropic.com"]);
215
- const raw = request.method === "POST" && new URL(request.url).pathname.endsWith("/messages")
216
- ? await request
217
- .clone()
218
- .json()
219
- .catch(() => null)
220
- : null;
221
- const beta = new Set([
222
- "claude-code-20250219",
223
- "oauth-2025-04-20",
224
- ...(request.headers.get("anthropic-beta")?.split(",") ?? []),
225
- ]);
226
- const authorized = bearerRequest(request, credential, {
227
- "anthropic-version": "2023-06-01",
228
- "anthropic-beta": [...beta]
229
- .map((value) => value.trim())
230
- .filter(Boolean)
231
- .join(","),
232
- "user-agent": `claude-cli/${compatibilityVersion}`,
233
- "x-app": "cli",
234
- });
235
- if (!isRecord(raw))
236
- return authorized;
237
- const system = raw.system;
238
- if (typeof system === "string" && system.startsWith(CLAUDE_CODE_SYSTEM))
239
- return authorized;
240
- if (Array.isArray(system) &&
241
- isRecord(system[0]) &&
242
- stringValue(system[0].text) === CLAUDE_CODE_SYSTEM) {
243
- return authorized;
244
- }
245
- const headers = new Headers(authorized.headers);
246
- headers.delete("content-length");
247
- const identity = { type: "text", text: CLAUDE_CODE_SYSTEM };
248
- const body = {
249
- ...raw,
250
- system: Array.isArray(system)
251
- ? [identity, ...system]
252
- : typeof system === "string"
253
- ? `${CLAUDE_CODE_SYSTEM}\n\n${system}`
254
- : [identity],
255
- };
256
- return new Request(authorized, { method: "POST", body: JSON.stringify(body), headers });
257
- },
258
- async getModels({ fetch, signal }) {
259
- const raw = await responseJson(await fetch("https://api.anthropic.com/v1/models", {
260
- headers: { accept: "application/json" },
261
- signal,
262
- }), "Claude models");
263
- const values = Array.isArray(raw.data) ? raw.data : [];
264
- return values.flatMap((value) => {
265
- if (!isRecord(value))
266
- return [];
267
- const id = stringValue(value.id);
268
- return id
269
- ? [
270
- {
271
- id,
272
- name: stringValue(value.display_name) ?? stringValue(value.name),
273
- description: stringValue(value.description),
274
- contextWindow: numberValue(value.context_window),
275
- maxOutputTokens: numberValue(value.max_output_tokens),
276
- inputModalities: ["text", "image", "document"],
277
- endpoints: ["messages"],
278
- supportsToolCall: true,
279
- available: true,
280
- selectable: true,
281
- },
282
- ]
283
- : [];
284
- });
285
- },
286
- isPermanentRefreshError(error) {
287
- return (error instanceof ClaudeTokenError &&
288
- (error.status === 401 || error.status === 403 || error.code === "invalid_grant"));
289
- },
290
- };
291
- }