@theokit/sdk 4.13.0 → 4.13.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.
package/dist/models.cjs CHANGED
@@ -44,8 +44,34 @@ var catalogModelSchema = zod.z.object({
44
44
  }).loose();
45
45
 
46
46
  // src/internal/providers/registry.ts
47
- var REGISTRY = /* @__PURE__ */ new Map();
48
- var ALIASES = /* @__PURE__ */ new Map();
47
+ function globalSingleton(key, create) {
48
+ const g = globalThis;
49
+ const sym = Symbol.for(key);
50
+ if (g[sym] === void 0) g[sym] = create();
51
+ return g[sym];
52
+ }
53
+ var REGISTRY = globalSingleton(
54
+ "theokit-sdk.providers.registry",
55
+ () => /* @__PURE__ */ new Map()
56
+ );
57
+ var ALIASES = globalSingleton("theokit-sdk.providers.aliases", () => /* @__PURE__ */ new Map());
58
+ function registerProvider(profile) {
59
+ if (REGISTRY.has(profile.name)) {
60
+ process.stderr.write(`[theokit-sdk] Provider "${profile.name}" overridden by user plugin.
61
+ `);
62
+ }
63
+ REGISTRY.set(profile.name, profile);
64
+ for (const alias of profile.aliases ?? []) {
65
+ const previous = ALIASES.get(alias);
66
+ if (previous !== void 0 && previous !== profile.name) {
67
+ process.stderr.write(
68
+ `[theokit-sdk] Alias "${alias}" collision: was "${previous}", now "${profile.name}".
69
+ `
70
+ );
71
+ }
72
+ ALIASES.set(alias, profile.name);
73
+ }
74
+ }
49
75
  function getProviderProfile(name) {
50
76
  const canonical = ALIASES.get(name) ?? name;
51
77
  return REGISTRY.get(canonical);
@@ -53,22 +79,46 @@ function getProviderProfile(name) {
53
79
 
54
80
  // src/internal/providers/catalog-loader.ts
55
81
  var __dirname_resolved = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('models.cjs', document.baseURI).href))));
56
- var modelInfoIndex = /* @__PURE__ */ new Map();
82
+ function globalSingleton2(key, create) {
83
+ const g = globalThis;
84
+ const sym = Symbol.for(key);
85
+ if (g[sym] === void 0) g[sym] = create();
86
+ return g[sym];
87
+ }
88
+ var modelInfoIndex = globalSingleton2(
89
+ "theokit-sdk.providers.model-info-index",
90
+ () => /* @__PURE__ */ new Map()
91
+ );
92
+ var patchedModelKeys = globalSingleton2(
93
+ "theokit-sdk.providers.model-info-patched",
94
+ () => /* @__PURE__ */ new Set()
95
+ );
96
+ var indexState = globalSingleton2("theokit-sdk.providers.model-info-loaded", () => ({
97
+ loaded: false
98
+ }));
57
99
  function getCatalogModelInfo(key) {
58
100
  ensureModelIndexLoaded();
59
101
  return modelInfoIndex.get(key);
60
102
  }
61
103
  function patchModelInfo(key, model) {
62
104
  ensureModelIndexLoaded();
63
- modelInfoIndex.set(key, model);
105
+ const existing = modelInfoIndex.get(key);
106
+ modelInfoIndex.set(key, existing === void 0 ? model : { ...existing, ...model });
107
+ patchedModelKeys.add(key);
64
108
  }
65
- var _modelIndexLoaded = false;
66
109
  function ensureModelIndexLoaded() {
67
- if (_modelIndexLoaded) return;
68
- _modelIndexLoaded = true;
69
- const catalog = loadProviderCatalog();
70
- for (const entry of Object.values(catalog)) {
71
- indexEntryModels(entry);
110
+ if (indexState.loaded) return;
111
+ indexState.loaded = true;
112
+ try {
113
+ const catalog = loadProviderCatalog();
114
+ for (const entry of Object.values(catalog)) {
115
+ indexEntryModels(entry);
116
+ }
117
+ } catch (err) {
118
+ process.stderr.write(
119
+ `[theokit-sdk] WARN: provider catalog unavailable (${err.message}) \u2014 per-model data disabled
120
+ `
121
+ );
72
122
  }
73
123
  }
74
124
  function indexEntryModels(entry) {
@@ -113,6 +163,27 @@ function loadProviderCatalog(opts) {
113
163
  }
114
164
  return result;
115
165
  }
166
+ function registerCatalogProviders(opts) {
167
+ const catalog = loadProviderCatalog();
168
+ for (const entry of Object.values(catalog)) {
169
+ if (getProviderProfile(entry.id) !== void 0) continue;
170
+ if (entry.aliases?.some((a) => getProviderProfile(a) !== void 0)) continue;
171
+ const profile = {
172
+ name: entry.id,
173
+ apiMode: entry.apiMode,
174
+ authType: entry.authType,
175
+ baseUrl: entry.baseUrl,
176
+ envVars: entry.envVars,
177
+ fallbackModels: entry.fallbackModels,
178
+ displayName: entry.displayName,
179
+ aliases: entry.aliases,
180
+ modelsUrl: entry.modelsUrl,
181
+ hostname: entry.hostname,
182
+ extraHeaders: entry.extraHeaders
183
+ };
184
+ registerProvider(profile);
185
+ }
186
+ }
116
187
 
117
188
  // src/internal/llm/model-capabilities.ts
118
189
  var CONSERVATIVE_DEFAULTS = {
@@ -302,12 +373,502 @@ var Retry = class {
302
373
  }
303
374
  };
304
375
 
376
+ // src/internal/providers/builtin/anthropic.ts
377
+ var ANTHROPIC = {
378
+ name: "anthropic",
379
+ apiMode: "anthropic_messages",
380
+ envVars: ["ANTHROPIC_API_KEY"],
381
+ authType: "api_key",
382
+ baseUrl: "https://api.anthropic.com",
383
+ modelsUrl: "https://api.anthropic.com/v1/models",
384
+ hostname: "api.anthropic.com",
385
+ fallbackModels: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5-20251001"]
386
+ };
387
+
388
+ // src/internal/providers/builtin/bedrock.ts
389
+ var BEDROCK = {
390
+ name: "bedrock",
391
+ apiMode: "bedrock_anthropic",
392
+ envVars: ["AWS_BEARER_TOKEN_BEDROCK"],
393
+ authType: "aws_bearer",
394
+ baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
395
+ modelsUrl: void 0,
396
+ hostname: "bedrock-runtime.amazonaws.com",
397
+ fallbackModels: [
398
+ "bedrock/us.anthropic.claude-sonnet-4-5-v1:0",
399
+ "bedrock/us.anthropic.claude-opus-4-7-v1:0",
400
+ "bedrock/global.anthropic.claude-haiku-4-5-v1:0"
401
+ ]
402
+ };
403
+
404
+ // src/internal/providers/builtin/gemini.ts
405
+ var GEMINI = {
406
+ name: "gemini",
407
+ apiMode: "chat_completions",
408
+ envVars: ["OPENROUTER_API_KEY"],
409
+ authType: "api_key",
410
+ baseUrl: "https://openrouter.ai/api",
411
+ hostname: "openrouter.ai",
412
+ fallbackModels: ["google/gemini-2.0-flash-001"]
413
+ };
414
+
415
+ // src/internal/providers/builtin/llamacpp.ts
416
+ var LLAMACPP = {
417
+ name: "llamacpp",
418
+ aliases: ["llama-cpp", "llama.cpp"],
419
+ apiMode: "chat_completions",
420
+ envVars: ["LLAMACPP_API_KEY"],
421
+ authType: "none",
422
+ baseUrl: "http://localhost:8080",
423
+ modelsUrl: "http://localhost:8080/v1/models",
424
+ hostname: "localhost",
425
+ fallbackModels: ["loaded-model"]
426
+ };
427
+
428
+ // src/internal/providers/builtin/lmstudio.ts
429
+ var LMSTUDIO = {
430
+ name: "lmstudio",
431
+ aliases: ["lm-studio", "lm_studio"],
432
+ apiMode: "chat_completions",
433
+ envVars: ["LMSTUDIO_API_KEY"],
434
+ authType: "none",
435
+ baseUrl: "http://localhost:1234",
436
+ modelsUrl: "http://localhost:1234/v1/models",
437
+ hostname: "localhost",
438
+ fallbackModels: ["loaded-model"]
439
+ };
440
+
441
+ // src/internal/providers/builtin/ollama.ts
442
+ var OLLAMA = {
443
+ name: "ollama",
444
+ apiMode: "chat_completions",
445
+ envVars: ["OLLAMA_API_KEY"],
446
+ authType: "none",
447
+ baseUrl: "http://localhost:11434",
448
+ modelsUrl: "http://localhost:11434/v1/models",
449
+ hostname: "localhost",
450
+ fallbackModels: ["llama3.2", "qwen2.5", "mistral"]
451
+ };
452
+
453
+ // src/internal/providers/builtin/openai.ts
454
+ var OPENAI = {
455
+ name: "openai",
456
+ apiMode: "chat_completions",
457
+ envVars: ["OPENAI_API_KEY"],
458
+ authType: "api_key",
459
+ baseUrl: "https://api.openai.com",
460
+ modelsUrl: "https://api.openai.com/v1/models",
461
+ hostname: "api.openai.com",
462
+ fallbackModels: ["gpt-4o", "gpt-4o-mini"]
463
+ };
464
+ function credentialHome(config, env = {}) {
465
+ const override = config.homeEnvVar !== void 0 ? env[config.homeEnvVar]?.trim() : void 0;
466
+ return override !== void 0 && override.length > 0 ? override : path.join(config.home, config.dirName);
467
+ }
468
+ function authFilePath(config, env = {}) {
469
+ return path.join(credentialHome(config, env), config.fileName);
470
+ }
471
+ var CredentialError = class extends Error {
472
+ constructor(message) {
473
+ super(message);
474
+ this.name = "CredentialError";
475
+ }
476
+ };
477
+ var apiFileSchema = zod.z.object({
478
+ type: zod.z.literal("api").optional(),
479
+ provider: zod.z.string().min(1).optional(),
480
+ api_key: zod.z.string()
481
+ }).strict();
482
+ var oauthFileSchema = zod.z.object({
483
+ type: zod.z.literal("oauth"),
484
+ provider: zod.z.string().min(1),
485
+ access: zod.z.string().min(1),
486
+ refresh: zod.z.string().min(1),
487
+ expires: zod.z.number(),
488
+ account_id: zod.z.string().optional()
489
+ }).strict();
490
+ var fileSchema = zod.z.union([oauthFileSchema, apiFileSchema]);
491
+ function assertSecureModes(dirPath, path) {
492
+ const dirMode = fs.statSync(dirPath).mode & 511;
493
+ if ((dirMode & 18) !== 0) {
494
+ throw new CredentialError(
495
+ `${dirPath} is writable by other users (mode ${dirMode.toString(8)}), so the credential file inside it can be replaced. Fix it with: chmod 700 ${dirPath}`
496
+ );
497
+ }
498
+ const mode = fs.statSync(path).mode & 511;
499
+ if ((mode & 63) !== 0) {
500
+ throw new CredentialError(
501
+ `${path} is readable by other users (mode ${mode.toString(8)}). A credential file must not be. Fix it with: chmod 600 ${path}`
502
+ );
503
+ }
504
+ }
505
+ function describeUnionError(parsed, err, path) {
506
+ const looksOAuth = typeof parsed === "object" && parsed !== null && parsed.type === "oauth";
507
+ const specific = looksOAuth ? oauthFileSchema.safeParse(parsed) : apiFileSchema.safeParse(parsed);
508
+ let issue;
509
+ if (!specific.success) {
510
+ issue = specific.error.issues[0];
511
+ } else if (err instanceof zod.z.ZodError) {
512
+ issue = err.issues[0];
513
+ }
514
+ return new CredentialError(
515
+ `${path}: ${issue?.message ?? String(err)} [${issue?.path.join(".") || "root"}]`
516
+ );
517
+ }
518
+ function parseStoredFile(raw, path) {
519
+ let parsed;
520
+ try {
521
+ parsed = JSON.parse(raw);
522
+ } catch {
523
+ throw new CredentialError(
524
+ `${path} is not valid JSON. Expected: {"provider": "<name>", "api_key": "..."}`
525
+ );
526
+ }
527
+ try {
528
+ return fileSchema.parse(parsed);
529
+ } catch (err) {
530
+ throw describeUnionError(parsed, err, path);
531
+ }
532
+ }
533
+ function readAuthFile(config, env = {}) {
534
+ const path = authFilePath(config, env);
535
+ let raw;
536
+ try {
537
+ raw = fs.readFileSync(path, "utf8");
538
+ } catch (err) {
539
+ if (err.code === "ENOENT") return void 0;
540
+ throw new CredentialError(`cannot read ${path}: ${err.message}`);
541
+ }
542
+ assertSecureModes(credentialHome(config, env), path);
543
+ return parseStoredFile(raw, path);
544
+ }
545
+ function readStoredOAuth(config, env = {}) {
546
+ const stored = readAuthFile(config, env);
547
+ return stored !== void 0 && stored.type === "oauth" ? stored : void 0;
548
+ }
549
+ function isOAuthWrite(c) {
550
+ return "type" in c && c.type === "oauth";
551
+ }
552
+ function buildStorePayload(cred) {
553
+ if (isOAuthWrite(cred)) {
554
+ if (cred.access.length === 0 || cred.refresh.length === 0) {
555
+ throw new CredentialError(
556
+ "refusing to write an oauth credential with an empty access/refresh token"
557
+ );
558
+ }
559
+ return {
560
+ type: "oauth",
561
+ provider: cred.provider,
562
+ access: cred.access,
563
+ refresh: cred.refresh,
564
+ expires: cred.expires,
565
+ ...cred.account_id !== void 0 ? { account_id: cred.account_id } : {}
566
+ };
567
+ }
568
+ if (typeof cred.apiKey !== "string" || cred.apiKey.length === 0) {
569
+ throw new CredentialError("refusing to write an empty API key");
570
+ }
571
+ return { provider: cred.provider, api_key: cred.apiKey };
572
+ }
573
+ function writeCredential(cred, config, env = {}) {
574
+ const payload = buildStorePayload(cred);
575
+ const dir = credentialHome(config, env);
576
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
577
+ fs.chmodSync(dir, 448);
578
+ const path = authFilePath(config, env);
579
+ const tmp = `${path}.tmp-${crypto.randomBytes(8).toString("hex")}`;
580
+ try {
581
+ const fd = fs.openSync(tmp, "wx", 384);
582
+ try {
583
+ fs.writeFileSync(fd, `${JSON.stringify(payload, null, 2)}
584
+ `);
585
+ fs.fsyncSync(fd);
586
+ } finally {
587
+ fs.closeSync(fd);
588
+ }
589
+ fs.chmodSync(tmp, 384);
590
+ fs.renameSync(tmp, path);
591
+ } catch (err) {
592
+ try {
593
+ fs.unlinkSync(tmp);
594
+ } catch {
595
+ }
596
+ throw new CredentialError(`cannot write ${path}: ${err.message}`);
597
+ }
598
+ return path;
599
+ }
600
+
601
+ // src/server/auth/errors.ts
602
+ var AuthCallbackError = class extends Error {
603
+ name = "AuthCallbackError";
604
+ code;
605
+ constructor(code, message) {
606
+ super(message ?? `OAuth callback error: ${code}`);
607
+ this.code = code;
608
+ }
609
+ };
610
+
611
+ // src/internal/auth/oauth-engine.ts
612
+ var REFRESH_SKEW_MS = 6e4;
613
+ function parseTokenResponse(body, now) {
614
+ const b = body;
615
+ if (typeof b.access_token !== "string" || b.access_token.length === 0) {
616
+ throw new AuthCallbackError(
617
+ "oauth_token_exchange_failed",
618
+ "token response had no access_token"
619
+ );
620
+ }
621
+ if (typeof b.refresh_token !== "string" || b.refresh_token.length === 0) {
622
+ throw new AuthCallbackError(
623
+ "oauth_token_exchange_failed",
624
+ "token response had no refresh_token"
625
+ );
626
+ }
627
+ const expiresIn = typeof b.expires_in === "number" ? b.expires_in : 3600;
628
+ return {
629
+ access: b.access_token,
630
+ refresh: b.refresh_token,
631
+ expires: now + expiresIn * 1e3,
632
+ ...typeof b.account_id === "string" ? { accountId: b.account_id } : {}
633
+ };
634
+ }
635
+ async function postGrant(config, form, deps) {
636
+ let res;
637
+ try {
638
+ res = await deps.fetch(config.tokenEndpoint, {
639
+ method: "POST",
640
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
641
+ body: new URLSearchParams(form).toString()
642
+ });
643
+ } catch (err) {
644
+ throw new AuthCallbackError(
645
+ "oauth_token_exchange_failed",
646
+ `token endpoint request failed: ${err.message}`
647
+ );
648
+ }
649
+ if (!res.ok) {
650
+ throw new AuthCallbackError(
651
+ "oauth_token_exchange_failed",
652
+ `token endpoint returned HTTP ${res.status}`
653
+ );
654
+ }
655
+ let json;
656
+ try {
657
+ json = await res.json();
658
+ } catch {
659
+ throw new AuthCallbackError("oauth_token_exchange_failed", "token response was not valid JSON");
660
+ }
661
+ return parseTokenResponse(json, deps.now());
662
+ }
663
+ function refreshOAuthTokens(config, refresh, deps) {
664
+ return postGrant(
665
+ config,
666
+ { grant_type: "refresh_token", refresh_token: refresh, client_id: config.clientId },
667
+ deps
668
+ );
669
+ }
670
+ function persistOAuthTokens(provider, tokens, store, env = {}) {
671
+ return writeCredential(
672
+ {
673
+ type: "oauth",
674
+ provider,
675
+ access: tokens.access,
676
+ refresh: tokens.refresh,
677
+ expires: tokens.expires,
678
+ ...tokens.accountId !== void 0 ? { account_id: tokens.accountId } : {}
679
+ },
680
+ store,
681
+ env
682
+ );
683
+ }
684
+ var inFlightRefresh = /* @__PURE__ */ new Map();
685
+ async function ensureFreshCredential(resolved, opts, deps) {
686
+ if (resolved.kind !== "oauth") return resolved;
687
+ const now = deps.now();
688
+ if (resolved.expiresAt !== void 0 && resolved.expiresAt > now + REFRESH_SKEW_MS) {
689
+ return resolved;
690
+ }
691
+ const env = opts.env ?? {};
692
+ const path = authFilePath(opts.store, env);
693
+ let refresh = inFlightRefresh.get(path);
694
+ if (refresh === void 0) {
695
+ refresh = (async () => {
696
+ const stored = readStoredOAuth(opts.store, env);
697
+ if (stored === void 0) {
698
+ throw new AuthCallbackError(
699
+ "oauth_token_exchange_failed",
700
+ "no stored oauth credential to refresh"
701
+ );
702
+ }
703
+ const fresh2 = await refreshOAuthTokens(opts.config, stored.refresh, deps);
704
+ const merged = {
705
+ ...fresh2,
706
+ accountId: fresh2.accountId ?? stored.account_id
707
+ };
708
+ persistOAuthTokens(resolved.provider, merged, opts.store, env);
709
+ return merged;
710
+ })();
711
+ inFlightRefresh.set(path, refresh);
712
+ refresh.finally(() => inFlightRefresh.delete(path)).catch(() => {
713
+ });
714
+ }
715
+ const fresh = await refresh;
716
+ return {
717
+ kind: "oauth",
718
+ provider: resolved.provider,
719
+ apiKey: fresh.access,
720
+ source: resolved.source,
721
+ inferred: false,
722
+ expiresAt: fresh.expires
723
+ };
724
+ }
725
+
726
+ // src/internal/auth/resolve-credential.ts
727
+ async function resolveOAuth(stored, path, opts, env) {
728
+ if (stored.provider !== opts.provider) return void 0;
729
+ const base = {
730
+ kind: "oauth",
731
+ provider: opts.provider,
732
+ apiKey: stored.access,
733
+ source: path,
734
+ inferred: false,
735
+ expiresAt: stored.expires
736
+ };
737
+ if (opts.oauth === void 0) return base;
738
+ const deps = {
739
+ fetch: opts.deps?.fetch ?? fetch,
740
+ now: opts.deps?.now ?? (() => Date.now())
741
+ };
742
+ return ensureFreshCredential(base, { config: opts.oauth, store: opts.store, env }, deps);
743
+ }
744
+ async function resolveCredential(opts) {
745
+ const env = opts.env ?? {};
746
+ const stored = readAuthFile(opts.store, env);
747
+ if (stored === void 0) return void 0;
748
+ const path = authFilePath(opts.store, env);
749
+ if (stored.type === "oauth") {
750
+ return resolveOAuth(stored, path, opts, env);
751
+ }
752
+ if (stored.api_key.length === 0) return void 0;
753
+ if (stored.provider !== opts.provider) return void 0;
754
+ return {
755
+ kind: "api",
756
+ provider: opts.provider,
757
+ apiKey: stored.api_key,
758
+ source: path,
759
+ inferred: false
760
+ };
761
+ }
762
+
763
+ // src/internal/providers/builtin/openai-chatgpt.ts
764
+ var DEFAULT_STORE = {
765
+ home: os.homedir(),
766
+ dirName: ".theokit",
767
+ fileName: "auth.json",
768
+ homeEnvVar: "THEOKIT_AUTH_HOME"
769
+ };
770
+ var OPENAI_OAUTH_CONFIG = {
771
+ provider: "openai",
772
+ authorizeEndpoint: "https://auth.openai.com/oauth/authorize",
773
+ tokenEndpoint: "https://auth.openai.com/oauth/token",
774
+ clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
775
+ scopes: ["openid", "profile", "email", "offline_access"],
776
+ redirectUri: "https://auth.openai.com/deviceauth/callback"
777
+ };
778
+ function codexFetch() {
779
+ return (async (input, init) => {
780
+ const env = process.env;
781
+ const resolved = await resolveCredential({
782
+ provider: "openai",
783
+ store: DEFAULT_STORE,
784
+ oauth: OPENAI_OAUTH_CONFIG,
785
+ env
786
+ });
787
+ if (resolved === void 0) {
788
+ throw new Error(
789
+ 'openai-chatgpt: no ChatGPT credential found \u2014 run the OpenAI device login (e.g. "/login openai") first.'
790
+ );
791
+ }
792
+ const accountId = readStoredOAuth(DEFAULT_STORE, env)?.account_id;
793
+ const headers = new Headers(init?.headers);
794
+ headers.set("authorization", `Bearer ${resolved.apiKey}`);
795
+ if (accountId !== void 0) headers.set("ChatGPT-Account-Id", accountId);
796
+ return fetch(input, { ...init, headers });
797
+ });
798
+ }
799
+ var OPENAI_CHATGPT = {
800
+ name: "openai-chatgpt",
801
+ apiMode: "responses_api",
802
+ authType: "oauth_device_code",
803
+ baseUrl: "https://chatgpt.com/backend-api/codex",
804
+ envVars: [],
805
+ fallbackModels: [
806
+ "openai-chatgpt/gpt-5.4",
807
+ "openai-chatgpt/gpt-5.4-mini",
808
+ "openai-chatgpt/gpt-5.5"
809
+ ],
810
+ extraHeaders: { originator: "codex_cli_rs" },
811
+ transform: {
812
+ // Only `fetch` (async) can await the credential refresh; `headers` is sync and cannot.
813
+ fetch: () => codexFetch()
814
+ }
815
+ };
816
+
817
+ // src/internal/providers/builtin/openrouter.ts
818
+ var OPENROUTER = {
819
+ name: "openrouter",
820
+ apiMode: "chat_completions",
821
+ aliases: ["or"],
822
+ // Ordered fallback (EC-10): OPENROUTER_API_KEY preferred, OPENAI_API_KEY as compat.
823
+ envVars: ["OPENROUTER_API_KEY", "OPENAI_API_KEY"],
824
+ authType: "api_key",
825
+ baseUrl: "https://openrouter.ai/api",
826
+ modelsUrl: "https://openrouter.ai/api/v1/models",
827
+ hostname: "openrouter.ai",
828
+ fallbackModels: ["openai/gpt-4o-mini", "anthropic/claude-3-haiku"]
829
+ };
830
+
831
+ // src/internal/providers/builtin/vertex.ts
832
+ var VERTEX = {
833
+ name: "vertex",
834
+ apiMode: "anthropic_messages",
835
+ // sub-dispatched in selectTransport by profile.name
836
+ envVars: ["GOOGLE_APPLICATION_CREDENTIALS"],
837
+ authType: "gcp_oauth",
838
+ baseUrl: "https://us-central1-aiplatform.googleapis.com",
839
+ modelsUrl: void 0,
840
+ hostname: "aiplatform.googleapis.com",
841
+ fallbackModels: [
842
+ "vertex/anthropic/claude-sonnet-4-5@20250929",
843
+ "vertex/google/gemini-2.0-flash-001"
844
+ ]
845
+ };
846
+
847
+ // src/internal/providers/builtin/index.ts
848
+ var registered = false;
849
+ function registerBuiltins() {
850
+ if (registered) return;
851
+ registered = true;
852
+ registerProvider(ANTHROPIC);
853
+ registerProvider(OPENAI);
854
+ registerProvider(OPENAI_CHATGPT);
855
+ registerProvider(OPENROUTER);
856
+ registerProvider(GEMINI);
857
+ registerProvider(OLLAMA);
858
+ registerProvider(LMSTUDIO);
859
+ registerProvider(LLAMACPP);
860
+ registerProvider(BEDROCK);
861
+ registerProvider(VERTEX);
862
+ registerCatalogProviders();
863
+ }
864
+
305
865
  // src/internal/providers/catalog-source-models-dev.ts
306
866
  var DEFAULT_URL = "https://models.dev/api.json";
307
867
  var TTL_MS = 60 * 60 * 1e3;
308
868
  var FETCH_TIMEOUT_MS = 1e4;
309
869
  function cachePathFor(url) {
310
- const dir = path.join(os.homedir(), ".theokit", "cache", "models-dev");
870
+ const base = process.env.THEOKIT_HOME?.trim() || path.join(os.homedir(), ".theokit");
871
+ const dir = path.join(base, "cache", "models-dev");
311
872
  if (url === DEFAULT_URL) return path.join(dir, "api.json");
312
873
  const hash = crypto.createHash("sha256").update(url).digest("hex").slice(0, 12);
313
874
  return path.join(dir, `api-${hash}.json`);
@@ -326,21 +887,62 @@ function writeCacheAtomic(path$1, body) {
326
887
  throw err;
327
888
  }
328
889
  }
890
+ var MODELS_DEV_ID_MAP = {
891
+ google: "google-gemini",
892
+ zai: "zhipu",
893
+ togetherai: "together",
894
+ "fireworks-ai": "fireworks",
895
+ "amazon-bedrock": "bedrock",
896
+ "google-vertex": "vertex"
897
+ };
898
+ var _catalogTargets;
899
+ function catalogTargets() {
900
+ if (_catalogTargets !== void 0) return _catalogTargets;
901
+ _catalogTargets = /* @__PURE__ */ new Map();
902
+ try {
903
+ for (const entry of Object.values(loadProviderCatalog())) {
904
+ const keys = [entry.id, ...entry.aliases ?? []];
905
+ for (const k of keys) {
906
+ if (!_catalogTargets.has(k)) _catalogTargets.set(k, { keys });
907
+ }
908
+ }
909
+ } catch {
910
+ }
911
+ return _catalogTargets;
912
+ }
913
+ function resolvePatchKeys(externalId) {
914
+ const mapped = MODELS_DEV_ID_MAP[externalId] ?? externalId;
915
+ const fromCatalog = catalogTargets().get(mapped);
916
+ if (fromCatalog !== void 0) return fromCatalog.keys;
917
+ const profile = getProviderProfile(mapped);
918
+ if (profile !== void 0) return [profile.name, ...profile.aliases ?? []];
919
+ return void 0;
920
+ }
329
921
  function patchIndexFromApiJson(raw) {
330
922
  if (typeof raw !== "object" || raw === null) return 0;
331
923
  let patched = 0;
924
+ const skipped = [];
332
925
  for (const [providerId, provider] of Object.entries(raw)) {
333
926
  const models = provider?.models;
334
927
  if (models === void 0 || typeof models !== "object") continue;
335
- const profile = getProviderProfile(providerId);
336
- if (profile === void 0) continue;
928
+ const keys = resolvePatchKeys(providerId);
929
+ if (keys === void 0) {
930
+ skipped.push(providerId);
931
+ continue;
932
+ }
337
933
  for (const [modelId, rawModel] of Object.entries(models)) {
338
934
  const parsed = catalogModelSchema.safeParse(rawModel);
339
935
  if (!parsed.success) continue;
340
- patchModelInfo(`${profile.name}/${modelId}`, parsed.data);
936
+ for (const key of keys) patchModelInfo(`${key}/${modelId}`, parsed.data);
341
937
  patched++;
342
938
  }
343
939
  }
940
+ if (skipped.length > 0) {
941
+ process.stderr.write(
942
+ `[theokit-sdk] WARN: models-dev refresh skipped ${skipped.length} unknown provider(s) (e.g. ${skipped.slice(0, 3).join(", ")})
943
+ `
944
+ );
945
+ }
344
946
  return patched;
345
947
  }
346
948
  function loadCacheIntoIndex(url = DEFAULT_URL) {
@@ -351,8 +953,9 @@ function loadCacheIntoIndex(url = DEFAULT_URL) {
351
953
  } catch {
352
954
  return 0;
353
955
  }
956
+ let parsed;
354
957
  try {
355
- return patchIndexFromApiJson(JSON.parse(body));
958
+ parsed = JSON.parse(body);
356
959
  } catch {
357
960
  try {
358
961
  fs.unlinkSync(path);
@@ -362,9 +965,20 @@ function loadCacheIntoIndex(url = DEFAULT_URL) {
362
965
  `);
363
966
  return 0;
364
967
  }
968
+ try {
969
+ return patchIndexFromApiJson(parsed);
970
+ } catch (err) {
971
+ process.stderr.write(
972
+ `[theokit-sdk] WARN: models-dev cache patch failed (${err.message})
973
+ `
974
+ );
975
+ return 0;
976
+ }
365
977
  }
366
978
  async function refreshModelCatalog(opts = {}) {
367
- if (process.env.THEOKIT_DISABLE_MODELS_FETCH !== void 0) {
979
+ registerBuiltins();
980
+ const kill = process.env.THEOKIT_DISABLE_MODELS_FETCH;
981
+ if (kill !== void 0 && kill !== "" && kill !== "0" && kill.toLowerCase() !== "false") {
368
982
  return { source: "skipped", models: 0 };
369
983
  }
370
984
  const url = opts.url ?? process.env.THEOKIT_MODELS_URL ?? DEFAULT_URL;
@@ -409,7 +1023,15 @@ async function refreshModelCatalog(opts = {}) {
409
1023
  `
410
1024
  );
411
1025
  }
412
- return { source: "network", models: patchIndexFromApiJson(JSON.parse(body)) };
1026
+ try {
1027
+ return { source: "network", models: patchIndexFromApiJson(JSON.parse(body)) };
1028
+ } catch (err) {
1029
+ process.stderr.write(
1030
+ `[theokit-sdk] WARN: models-dev patch failed (${err.message}) \u2014 serving existing data
1031
+ `
1032
+ );
1033
+ return { source: "cache", models: 0 };
1034
+ }
413
1035
  }
414
1036
  function getModelInfo(modelId) {
415
1037
  return getCatalogModelInfo(modelId);