@theokit/sdk 4.13.0 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{cron-C7catiH8.d.ts → cron-DY0uC1ng.d.ts} +7 -0
  3. package/dist/{cron-DK1WVZnI.d.cts → cron-ElTj1cSH.d.cts} +7 -0
  4. package/dist/cron.cjs +183 -20
  5. package/dist/cron.cjs.map +1 -1
  6. package/dist/cron.d.cts +1 -1
  7. package/dist/cron.d.ts +1 -1
  8. package/dist/cron.js +183 -20
  9. package/dist/cron.js.map +1 -1
  10. package/dist/eval.cjs +183 -20
  11. package/dist/eval.cjs.map +1 -1
  12. package/dist/eval.js +183 -20
  13. package/dist/eval.js.map +1 -1
  14. package/dist/index.cjs +183 -20
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +2 -2
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.js +183 -20
  19. package/dist/index.js.map +1 -1
  20. package/dist/internal/llm/openai.d.ts +1 -0
  21. package/dist/internal/providers/builtin/cerebras.d.ts +5 -0
  22. package/dist/internal/providers/builtin/cohere.d.ts +6 -0
  23. package/dist/internal/providers/builtin/deepinfra.d.ts +2 -0
  24. package/dist/internal/providers/builtin/google.d.ts +6 -0
  25. package/dist/internal/providers/builtin/groq.d.ts +2 -0
  26. package/dist/internal/providers/builtin/mistral.d.ts +2 -0
  27. package/dist/internal/providers/builtin/openai-compatible.d.ts +1 -0
  28. package/dist/internal/providers/builtin/perplexity.d.ts +5 -0
  29. package/dist/internal/providers/builtin/together.d.ts +2 -0
  30. package/dist/internal/providers/builtin/xai.d.ts +2 -0
  31. package/dist/models.cjs +753 -17
  32. package/dist/models.cjs.map +1 -1
  33. package/dist/models.js +754 -18
  34. package/dist/models.js.map +1 -1
  35. package/dist/provider-catalog.json +473 -146
  36. package/dist/types/provider-profile.d.ts +7 -0
  37. package/package.json +1 -1
package/dist/models.js CHANGED
@@ -1,4 +1,4 @@
1
- import { statSync, readFileSync, unlinkSync, mkdirSync, writeFileSync, renameSync } from 'fs';
1
+ import { statSync, readFileSync, unlinkSync, mkdirSync, writeFileSync, renameSync, chmodSync, openSync, fsyncSync, closeSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { z } from 'zod';
@@ -41,8 +41,34 @@ var catalogModelSchema = z.object({
41
41
  }).loose();
42
42
 
43
43
  // src/internal/providers/registry.ts
44
- var REGISTRY = /* @__PURE__ */ new Map();
45
- var ALIASES = /* @__PURE__ */ new Map();
44
+ function globalSingleton(key, create) {
45
+ const g = globalThis;
46
+ const sym = Symbol.for(key);
47
+ if (g[sym] === void 0) g[sym] = create();
48
+ return g[sym];
49
+ }
50
+ var REGISTRY = globalSingleton(
51
+ "theokit-sdk.providers.registry",
52
+ () => /* @__PURE__ */ new Map()
53
+ );
54
+ var ALIASES = globalSingleton("theokit-sdk.providers.aliases", () => /* @__PURE__ */ new Map());
55
+ function registerProvider(profile) {
56
+ if (REGISTRY.has(profile.name)) {
57
+ process.stderr.write(`[theokit-sdk] Provider "${profile.name}" overridden by user plugin.
58
+ `);
59
+ }
60
+ REGISTRY.set(profile.name, profile);
61
+ for (const alias of profile.aliases ?? []) {
62
+ const previous = ALIASES.get(alias);
63
+ if (previous !== void 0 && previous !== profile.name) {
64
+ process.stderr.write(
65
+ `[theokit-sdk] Alias "${alias}" collision: was "${previous}", now "${profile.name}".
66
+ `
67
+ );
68
+ }
69
+ ALIASES.set(alias, profile.name);
70
+ }
71
+ }
46
72
  function getProviderProfile(name) {
47
73
  const canonical = ALIASES.get(name) ?? name;
48
74
  return REGISTRY.get(canonical);
@@ -50,22 +76,46 @@ function getProviderProfile(name) {
50
76
 
51
77
  // src/internal/providers/catalog-loader.ts
52
78
  var __dirname_resolved = dirname(fileURLToPath(import.meta.url));
53
- var modelInfoIndex = /* @__PURE__ */ new Map();
79
+ function globalSingleton2(key, create) {
80
+ const g = globalThis;
81
+ const sym = Symbol.for(key);
82
+ if (g[sym] === void 0) g[sym] = create();
83
+ return g[sym];
84
+ }
85
+ var modelInfoIndex = globalSingleton2(
86
+ "theokit-sdk.providers.model-info-index",
87
+ () => /* @__PURE__ */ new Map()
88
+ );
89
+ var patchedModelKeys = globalSingleton2(
90
+ "theokit-sdk.providers.model-info-patched",
91
+ () => /* @__PURE__ */ new Set()
92
+ );
93
+ var indexState = globalSingleton2("theokit-sdk.providers.model-info-loaded", () => ({
94
+ loaded: false
95
+ }));
54
96
  function getCatalogModelInfo(key) {
55
97
  ensureModelIndexLoaded();
56
98
  return modelInfoIndex.get(key);
57
99
  }
58
100
  function patchModelInfo(key, model) {
59
101
  ensureModelIndexLoaded();
60
- modelInfoIndex.set(key, model);
102
+ const existing = modelInfoIndex.get(key);
103
+ modelInfoIndex.set(key, existing === void 0 ? model : { ...existing, ...model });
104
+ patchedModelKeys.add(key);
61
105
  }
62
- var _modelIndexLoaded = false;
63
106
  function ensureModelIndexLoaded() {
64
- if (_modelIndexLoaded) return;
65
- _modelIndexLoaded = true;
66
- const catalog = loadProviderCatalog();
67
- for (const entry of Object.values(catalog)) {
68
- indexEntryModels(entry);
107
+ if (indexState.loaded) return;
108
+ indexState.loaded = true;
109
+ try {
110
+ const catalog = loadProviderCatalog();
111
+ for (const entry of Object.values(catalog)) {
112
+ indexEntryModels(entry);
113
+ }
114
+ } catch (err) {
115
+ process.stderr.write(
116
+ `[theokit-sdk] WARN: provider catalog unavailable (${err.message}) \u2014 per-model data disabled
117
+ `
118
+ );
69
119
  }
70
120
  }
71
121
  function indexEntryModels(entry) {
@@ -110,6 +160,27 @@ function loadProviderCatalog(opts) {
110
160
  }
111
161
  return result;
112
162
  }
163
+ function registerCatalogProviders(opts) {
164
+ const catalog = loadProviderCatalog();
165
+ for (const entry of Object.values(catalog)) {
166
+ if (getProviderProfile(entry.id) !== void 0) continue;
167
+ if (entry.aliases?.some((a) => getProviderProfile(a) !== void 0)) continue;
168
+ const profile = {
169
+ name: entry.id,
170
+ apiMode: entry.apiMode,
171
+ authType: entry.authType,
172
+ baseUrl: entry.baseUrl,
173
+ envVars: entry.envVars,
174
+ fallbackModels: entry.fallbackModels,
175
+ displayName: entry.displayName,
176
+ aliases: entry.aliases,
177
+ modelsUrl: entry.modelsUrl,
178
+ hostname: entry.hostname,
179
+ extraHeaders: entry.extraHeaders
180
+ };
181
+ registerProvider(profile);
182
+ }
183
+ }
113
184
 
114
185
  // src/internal/llm/model-capabilities.ts
115
186
  var CONSERVATIVE_DEFAULTS = {
@@ -299,12 +370,616 @@ var Retry = class {
299
370
  }
300
371
  };
301
372
 
373
+ // src/internal/providers/builtin/anthropic.ts
374
+ var ANTHROPIC = {
375
+ name: "anthropic",
376
+ apiMode: "anthropic_messages",
377
+ envVars: ["ANTHROPIC_API_KEY"],
378
+ authType: "api_key",
379
+ baseUrl: "https://api.anthropic.com",
380
+ modelsUrl: "https://api.anthropic.com/v1/models",
381
+ hostname: "api.anthropic.com",
382
+ fallbackModels: ["claude-opus-4-7", "claude-sonnet-4-6", "claude-haiku-4-5-20251001"],
383
+ // M45 — the Anthropic beta-features header (interleaved thinking + fine-grained tool streaming). An
384
+ // Anthropic API constant, adapted verbatim from OpenCode's anthropic plugin (MIT © 2025 opencode). The
385
+ // SANCTIONED behavior delta of M45 (ADR D4) — consumed by the anthropic transport's extraHeaders wiring.
386
+ extraHeaders: {
387
+ "anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14"
388
+ }
389
+ };
390
+
391
+ // src/internal/providers/builtin/bedrock.ts
392
+ var BEDROCK = {
393
+ name: "bedrock",
394
+ apiMode: "bedrock_anthropic",
395
+ envVars: ["AWS_BEARER_TOKEN_BEDROCK"],
396
+ authType: "aws_bearer",
397
+ baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
398
+ modelsUrl: void 0,
399
+ hostname: "bedrock-runtime.amazonaws.com",
400
+ fallbackModels: [
401
+ "bedrock/us.anthropic.claude-sonnet-4-5-v1:0",
402
+ "bedrock/us.anthropic.claude-opus-4-7-v1:0",
403
+ "bedrock/global.anthropic.claude-haiku-4-5-v1:0"
404
+ ]
405
+ };
406
+
407
+ // src/internal/providers/builtin/openai-compatible.ts
408
+ function openAiCompatibleProfile(p) {
409
+ return {
410
+ apiMode: "chat_completions",
411
+ authType: "api_key",
412
+ fallbackModels: [],
413
+ ...p
414
+ };
415
+ }
416
+
417
+ // src/internal/providers/builtin/cerebras.ts
418
+ var CEREBRAS = openAiCompatibleProfile({
419
+ name: "cerebras",
420
+ baseUrl: "https://api.cerebras.ai/v1",
421
+ envVars: ["CEREBRAS_API_KEY"],
422
+ fallbackModels: ["gpt-oss-120b", "zai-glm-4.7"],
423
+ extraHeaders: { "X-Cerebras-3rd-Party-Integration": "theokit" },
424
+ hostname: "api.cerebras.ai"
425
+ });
426
+
427
+ // src/internal/providers/builtin/cohere.ts
428
+ var COHERE = openAiCompatibleProfile({
429
+ name: "cohere",
430
+ baseUrl: "https://api.cohere.ai/compatibility/v1",
431
+ envVars: ["COHERE_API_KEY", "CO_API_KEY"],
432
+ fallbackModels: ["command-a-03-2025"],
433
+ hostname: "api.cohere.ai"
434
+ });
435
+
436
+ // src/internal/providers/builtin/deepinfra.ts
437
+ var DEEPINFRA = openAiCompatibleProfile({
438
+ name: "deepinfra",
439
+ baseUrl: "https://api.deepinfra.com/v1/openai",
440
+ envVars: ["DEEPINFRA_API_KEY"],
441
+ fallbackModels: ["Qwen/Qwen3-32B"],
442
+ hostname: "api.deepinfra.com"
443
+ });
444
+
445
+ // src/internal/providers/builtin/google.ts
446
+ var GOOGLE = openAiCompatibleProfile({
447
+ name: "google",
448
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
449
+ envVars: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
450
+ fallbackModels: ["gemini-2.5-pro", "gemini-2.5-flash"],
451
+ hostname: "generativelanguage.googleapis.com"
452
+ });
453
+
454
+ // src/internal/providers/builtin/groq.ts
455
+ var GROQ = openAiCompatibleProfile({
456
+ name: "groq",
457
+ baseUrl: "https://api.groq.com/openai/v1",
458
+ envVars: ["GROQ_API_KEY"],
459
+ fallbackModels: ["llama-3.3-70b-versatile", "llama-3.1-8b-instant"],
460
+ hostname: "api.groq.com"
461
+ });
462
+
463
+ // src/internal/providers/builtin/mistral.ts
464
+ var MISTRAL = openAiCompatibleProfile({
465
+ name: "mistral",
466
+ baseUrl: "https://api.mistral.ai/v1",
467
+ envVars: ["MISTRAL_API_KEY"],
468
+ fallbackModels: ["mistral-large-latest", "codestral-latest"],
469
+ hostname: "api.mistral.ai"
470
+ });
471
+
472
+ // src/internal/providers/builtin/perplexity.ts
473
+ var PERPLEXITY = openAiCompatibleProfile({
474
+ name: "perplexity",
475
+ baseUrl: "https://api.perplexity.ai",
476
+ chatCompletionsPath: "/chat/completions",
477
+ envVars: ["PERPLEXITY_API_KEY"],
478
+ fallbackModels: ["sonar-pro", "sonar"],
479
+ hostname: "api.perplexity.ai"
480
+ });
481
+
482
+ // src/internal/providers/builtin/together.ts
483
+ var TOGETHER = openAiCompatibleProfile({
484
+ name: "together",
485
+ aliases: ["togetherai"],
486
+ baseUrl: "https://api.together.xyz/v1",
487
+ envVars: ["TOGETHER_API_KEY"],
488
+ fallbackModels: ["Qwen/Qwen2.5-7B-Instruct-Turbo"],
489
+ hostname: "api.together.xyz"
490
+ });
491
+
492
+ // src/internal/providers/builtin/xai.ts
493
+ var XAI = openAiCompatibleProfile({
494
+ name: "xai",
495
+ aliases: ["grok"],
496
+ baseUrl: "https://api.x.ai/v1",
497
+ envVars: ["XAI_API_KEY"],
498
+ fallbackModels: ["grok-4.5", "grok-4.3"],
499
+ hostname: "api.x.ai"
500
+ });
501
+
502
+ // src/internal/providers/builtin/gemini.ts
503
+ var GEMINI = {
504
+ name: "gemini",
505
+ apiMode: "chat_completions",
506
+ envVars: ["OPENROUTER_API_KEY"],
507
+ authType: "api_key",
508
+ baseUrl: "https://openrouter.ai/api",
509
+ hostname: "openrouter.ai",
510
+ fallbackModels: ["google/gemini-2.0-flash-001"]
511
+ };
512
+
513
+ // src/internal/providers/builtin/llamacpp.ts
514
+ var LLAMACPP = {
515
+ name: "llamacpp",
516
+ aliases: ["llama-cpp", "llama.cpp"],
517
+ apiMode: "chat_completions",
518
+ envVars: ["LLAMACPP_API_KEY"],
519
+ authType: "none",
520
+ baseUrl: "http://localhost:8080",
521
+ modelsUrl: "http://localhost:8080/v1/models",
522
+ hostname: "localhost",
523
+ fallbackModels: ["loaded-model"]
524
+ };
525
+
526
+ // src/internal/providers/builtin/lmstudio.ts
527
+ var LMSTUDIO = {
528
+ name: "lmstudio",
529
+ aliases: ["lm-studio", "lm_studio"],
530
+ apiMode: "chat_completions",
531
+ envVars: ["LMSTUDIO_API_KEY"],
532
+ authType: "none",
533
+ baseUrl: "http://localhost:1234",
534
+ modelsUrl: "http://localhost:1234/v1/models",
535
+ hostname: "localhost",
536
+ fallbackModels: ["loaded-model"]
537
+ };
538
+
539
+ // src/internal/providers/builtin/ollama.ts
540
+ var OLLAMA = {
541
+ name: "ollama",
542
+ apiMode: "chat_completions",
543
+ envVars: ["OLLAMA_API_KEY"],
544
+ authType: "none",
545
+ baseUrl: "http://localhost:11434",
546
+ modelsUrl: "http://localhost:11434/v1/models",
547
+ hostname: "localhost",
548
+ fallbackModels: ["llama3.2", "qwen2.5", "mistral"]
549
+ };
550
+
551
+ // src/internal/providers/builtin/openai.ts
552
+ var OPENAI = {
553
+ name: "openai",
554
+ apiMode: "chat_completions",
555
+ envVars: ["OPENAI_API_KEY"],
556
+ authType: "api_key",
557
+ baseUrl: "https://api.openai.com",
558
+ modelsUrl: "https://api.openai.com/v1/models",
559
+ hostname: "api.openai.com",
560
+ fallbackModels: ["gpt-4o", "gpt-4o-mini"]
561
+ };
562
+ function credentialHome(config, env = {}) {
563
+ const override = config.homeEnvVar !== void 0 ? env[config.homeEnvVar]?.trim() : void 0;
564
+ return override !== void 0 && override.length > 0 ? override : join(config.home, config.dirName);
565
+ }
566
+ function authFilePath(config, env = {}) {
567
+ return join(credentialHome(config, env), config.fileName);
568
+ }
569
+ var CredentialError = class extends Error {
570
+ constructor(message) {
571
+ super(message);
572
+ this.name = "CredentialError";
573
+ }
574
+ };
575
+ var apiFileSchema = z.object({
576
+ type: z.literal("api").optional(),
577
+ provider: z.string().min(1).optional(),
578
+ api_key: z.string()
579
+ }).strict();
580
+ var oauthFileSchema = z.object({
581
+ type: z.literal("oauth"),
582
+ provider: z.string().min(1),
583
+ access: z.string().min(1),
584
+ refresh: z.string().min(1),
585
+ expires: z.number(),
586
+ account_id: z.string().optional()
587
+ }).strict();
588
+ var fileSchema = z.union([oauthFileSchema, apiFileSchema]);
589
+ function assertSecureModes(dirPath, path) {
590
+ const dirMode = statSync(dirPath).mode & 511;
591
+ if ((dirMode & 18) !== 0) {
592
+ throw new CredentialError(
593
+ `${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}`
594
+ );
595
+ }
596
+ const mode = statSync(path).mode & 511;
597
+ if ((mode & 63) !== 0) {
598
+ throw new CredentialError(
599
+ `${path} is readable by other users (mode ${mode.toString(8)}). A credential file must not be. Fix it with: chmod 600 ${path}`
600
+ );
601
+ }
602
+ }
603
+ function describeUnionError(parsed, err, path) {
604
+ const looksOAuth = typeof parsed === "object" && parsed !== null && parsed.type === "oauth";
605
+ const specific = looksOAuth ? oauthFileSchema.safeParse(parsed) : apiFileSchema.safeParse(parsed);
606
+ let issue;
607
+ if (!specific.success) {
608
+ issue = specific.error.issues[0];
609
+ } else if (err instanceof z.ZodError) {
610
+ issue = err.issues[0];
611
+ }
612
+ return new CredentialError(
613
+ `${path}: ${issue?.message ?? String(err)} [${issue?.path.join(".") || "root"}]`
614
+ );
615
+ }
616
+ function parseStoredFile(raw, path) {
617
+ let parsed;
618
+ try {
619
+ parsed = JSON.parse(raw);
620
+ } catch {
621
+ throw new CredentialError(
622
+ `${path} is not valid JSON. Expected: {"provider": "<name>", "api_key": "..."}`
623
+ );
624
+ }
625
+ try {
626
+ return fileSchema.parse(parsed);
627
+ } catch (err) {
628
+ throw describeUnionError(parsed, err, path);
629
+ }
630
+ }
631
+ function readAuthFile(config, env = {}) {
632
+ const path = authFilePath(config, env);
633
+ let raw;
634
+ try {
635
+ raw = readFileSync(path, "utf8");
636
+ } catch (err) {
637
+ if (err.code === "ENOENT") return void 0;
638
+ throw new CredentialError(`cannot read ${path}: ${err.message}`);
639
+ }
640
+ assertSecureModes(credentialHome(config, env), path);
641
+ return parseStoredFile(raw, path);
642
+ }
643
+ function readStoredOAuth(config, env = {}) {
644
+ const stored = readAuthFile(config, env);
645
+ return stored !== void 0 && stored.type === "oauth" ? stored : void 0;
646
+ }
647
+ function isOAuthWrite(c) {
648
+ return "type" in c && c.type === "oauth";
649
+ }
650
+ function buildStorePayload(cred) {
651
+ if (isOAuthWrite(cred)) {
652
+ if (cred.access.length === 0 || cred.refresh.length === 0) {
653
+ throw new CredentialError(
654
+ "refusing to write an oauth credential with an empty access/refresh token"
655
+ );
656
+ }
657
+ return {
658
+ type: "oauth",
659
+ provider: cred.provider,
660
+ access: cred.access,
661
+ refresh: cred.refresh,
662
+ expires: cred.expires,
663
+ ...cred.account_id !== void 0 ? { account_id: cred.account_id } : {}
664
+ };
665
+ }
666
+ if (typeof cred.apiKey !== "string" || cred.apiKey.length === 0) {
667
+ throw new CredentialError("refusing to write an empty API key");
668
+ }
669
+ return { provider: cred.provider, api_key: cred.apiKey };
670
+ }
671
+ function writeCredential(cred, config, env = {}) {
672
+ const payload = buildStorePayload(cred);
673
+ const dir = credentialHome(config, env);
674
+ mkdirSync(dir, { recursive: true, mode: 448 });
675
+ chmodSync(dir, 448);
676
+ const path = authFilePath(config, env);
677
+ const tmp = `${path}.tmp-${randomBytes(8).toString("hex")}`;
678
+ try {
679
+ const fd = openSync(tmp, "wx", 384);
680
+ try {
681
+ writeFileSync(fd, `${JSON.stringify(payload, null, 2)}
682
+ `);
683
+ fsyncSync(fd);
684
+ } finally {
685
+ closeSync(fd);
686
+ }
687
+ chmodSync(tmp, 384);
688
+ renameSync(tmp, path);
689
+ } catch (err) {
690
+ try {
691
+ unlinkSync(tmp);
692
+ } catch {
693
+ }
694
+ throw new CredentialError(`cannot write ${path}: ${err.message}`);
695
+ }
696
+ return path;
697
+ }
698
+
699
+ // src/server/auth/errors.ts
700
+ var AuthCallbackError = class extends Error {
701
+ name = "AuthCallbackError";
702
+ code;
703
+ constructor(code, message) {
704
+ super(message ?? `OAuth callback error: ${code}`);
705
+ this.code = code;
706
+ }
707
+ };
708
+
709
+ // src/internal/auth/oauth-engine.ts
710
+ var REFRESH_SKEW_MS = 6e4;
711
+ function parseTokenResponse(body, now) {
712
+ const b = body;
713
+ if (typeof b.access_token !== "string" || b.access_token.length === 0) {
714
+ throw new AuthCallbackError(
715
+ "oauth_token_exchange_failed",
716
+ "token response had no access_token"
717
+ );
718
+ }
719
+ if (typeof b.refresh_token !== "string" || b.refresh_token.length === 0) {
720
+ throw new AuthCallbackError(
721
+ "oauth_token_exchange_failed",
722
+ "token response had no refresh_token"
723
+ );
724
+ }
725
+ const expiresIn = typeof b.expires_in === "number" ? b.expires_in : 3600;
726
+ return {
727
+ access: b.access_token,
728
+ refresh: b.refresh_token,
729
+ expires: now + expiresIn * 1e3,
730
+ ...typeof b.account_id === "string" ? { accountId: b.account_id } : {}
731
+ };
732
+ }
733
+ async function postGrant(config, form, deps) {
734
+ let res;
735
+ try {
736
+ res = await deps.fetch(config.tokenEndpoint, {
737
+ method: "POST",
738
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
739
+ body: new URLSearchParams(form).toString()
740
+ });
741
+ } catch (err) {
742
+ throw new AuthCallbackError(
743
+ "oauth_token_exchange_failed",
744
+ `token endpoint request failed: ${err.message}`
745
+ );
746
+ }
747
+ if (!res.ok) {
748
+ throw new AuthCallbackError(
749
+ "oauth_token_exchange_failed",
750
+ `token endpoint returned HTTP ${res.status}`
751
+ );
752
+ }
753
+ let json;
754
+ try {
755
+ json = await res.json();
756
+ } catch {
757
+ throw new AuthCallbackError("oauth_token_exchange_failed", "token response was not valid JSON");
758
+ }
759
+ return parseTokenResponse(json, deps.now());
760
+ }
761
+ function refreshOAuthTokens(config, refresh, deps) {
762
+ return postGrant(
763
+ config,
764
+ { grant_type: "refresh_token", refresh_token: refresh, client_id: config.clientId },
765
+ deps
766
+ );
767
+ }
768
+ function persistOAuthTokens(provider, tokens, store, env = {}) {
769
+ return writeCredential(
770
+ {
771
+ type: "oauth",
772
+ provider,
773
+ access: tokens.access,
774
+ refresh: tokens.refresh,
775
+ expires: tokens.expires,
776
+ ...tokens.accountId !== void 0 ? { account_id: tokens.accountId } : {}
777
+ },
778
+ store,
779
+ env
780
+ );
781
+ }
782
+ var inFlightRefresh = /* @__PURE__ */ new Map();
783
+ async function ensureFreshCredential(resolved, opts, deps) {
784
+ if (resolved.kind !== "oauth") return resolved;
785
+ const now = deps.now();
786
+ if (resolved.expiresAt !== void 0 && resolved.expiresAt > now + REFRESH_SKEW_MS) {
787
+ return resolved;
788
+ }
789
+ const env = opts.env ?? {};
790
+ const path = authFilePath(opts.store, env);
791
+ let refresh = inFlightRefresh.get(path);
792
+ if (refresh === void 0) {
793
+ refresh = (async () => {
794
+ const stored = readStoredOAuth(opts.store, env);
795
+ if (stored === void 0) {
796
+ throw new AuthCallbackError(
797
+ "oauth_token_exchange_failed",
798
+ "no stored oauth credential to refresh"
799
+ );
800
+ }
801
+ const fresh2 = await refreshOAuthTokens(opts.config, stored.refresh, deps);
802
+ const merged = {
803
+ ...fresh2,
804
+ accountId: fresh2.accountId ?? stored.account_id
805
+ };
806
+ persistOAuthTokens(resolved.provider, merged, opts.store, env);
807
+ return merged;
808
+ })();
809
+ inFlightRefresh.set(path, refresh);
810
+ refresh.finally(() => inFlightRefresh.delete(path)).catch(() => {
811
+ });
812
+ }
813
+ const fresh = await refresh;
814
+ return {
815
+ kind: "oauth",
816
+ provider: resolved.provider,
817
+ apiKey: fresh.access,
818
+ source: resolved.source,
819
+ inferred: false,
820
+ expiresAt: fresh.expires
821
+ };
822
+ }
823
+
824
+ // src/internal/auth/resolve-credential.ts
825
+ async function resolveOAuth(stored, path, opts, env) {
826
+ if (stored.provider !== opts.provider) return void 0;
827
+ const base = {
828
+ kind: "oauth",
829
+ provider: opts.provider,
830
+ apiKey: stored.access,
831
+ source: path,
832
+ inferred: false,
833
+ expiresAt: stored.expires
834
+ };
835
+ if (opts.oauth === void 0) return base;
836
+ const deps = {
837
+ fetch: opts.deps?.fetch ?? fetch,
838
+ now: opts.deps?.now ?? (() => Date.now())
839
+ };
840
+ return ensureFreshCredential(base, { config: opts.oauth, store: opts.store, env }, deps);
841
+ }
842
+ async function resolveCredential(opts) {
843
+ const env = opts.env ?? {};
844
+ const stored = readAuthFile(opts.store, env);
845
+ if (stored === void 0) return void 0;
846
+ const path = authFilePath(opts.store, env);
847
+ if (stored.type === "oauth") {
848
+ return resolveOAuth(stored, path, opts, env);
849
+ }
850
+ if (stored.api_key.length === 0) return void 0;
851
+ if (stored.provider !== opts.provider) return void 0;
852
+ return {
853
+ kind: "api",
854
+ provider: opts.provider,
855
+ apiKey: stored.api_key,
856
+ source: path,
857
+ inferred: false
858
+ };
859
+ }
860
+
861
+ // src/internal/providers/builtin/openai-chatgpt.ts
862
+ var DEFAULT_STORE = {
863
+ home: homedir(),
864
+ dirName: ".theokit",
865
+ fileName: "auth.json",
866
+ homeEnvVar: "THEOKIT_AUTH_HOME"
867
+ };
868
+ var OPENAI_OAUTH_CONFIG = {
869
+ provider: "openai",
870
+ authorizeEndpoint: "https://auth.openai.com/oauth/authorize",
871
+ tokenEndpoint: "https://auth.openai.com/oauth/token",
872
+ clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
873
+ scopes: ["openid", "profile", "email", "offline_access"],
874
+ redirectUri: "https://auth.openai.com/deviceauth/callback"
875
+ };
876
+ function codexFetch() {
877
+ return (async (input, init) => {
878
+ const env = process.env;
879
+ const resolved = await resolveCredential({
880
+ provider: "openai",
881
+ store: DEFAULT_STORE,
882
+ oauth: OPENAI_OAUTH_CONFIG,
883
+ env
884
+ });
885
+ if (resolved === void 0) {
886
+ throw new Error(
887
+ 'openai-chatgpt: no ChatGPT credential found \u2014 run the OpenAI device login (e.g. "/login openai") first.'
888
+ );
889
+ }
890
+ const accountId = readStoredOAuth(DEFAULT_STORE, env)?.account_id;
891
+ const headers = new Headers(init?.headers);
892
+ headers.set("authorization", `Bearer ${resolved.apiKey}`);
893
+ if (accountId !== void 0) headers.set("ChatGPT-Account-Id", accountId);
894
+ return fetch(input, { ...init, headers });
895
+ });
896
+ }
897
+ var OPENAI_CHATGPT = {
898
+ name: "openai-chatgpt",
899
+ apiMode: "responses_api",
900
+ authType: "oauth_device_code",
901
+ baseUrl: "https://chatgpt.com/backend-api/codex",
902
+ envVars: [],
903
+ fallbackModels: [
904
+ "openai-chatgpt/gpt-5.4",
905
+ "openai-chatgpt/gpt-5.4-mini",
906
+ "openai-chatgpt/gpt-5.5"
907
+ ],
908
+ extraHeaders: { originator: "codex_cli_rs" },
909
+ transform: {
910
+ // Only `fetch` (async) can await the credential refresh; `headers` is sync and cannot.
911
+ fetch: () => codexFetch()
912
+ }
913
+ };
914
+
915
+ // src/internal/providers/builtin/openrouter.ts
916
+ var OPENROUTER = {
917
+ name: "openrouter",
918
+ apiMode: "chat_completions",
919
+ aliases: ["or"],
920
+ // Ordered fallback (EC-10): OPENROUTER_API_KEY preferred, OPENAI_API_KEY as compat.
921
+ envVars: ["OPENROUTER_API_KEY", "OPENAI_API_KEY"],
922
+ authType: "api_key",
923
+ baseUrl: "https://openrouter.ai/api",
924
+ modelsUrl: "https://openrouter.ai/api/v1/models",
925
+ hostname: "openrouter.ai",
926
+ // M45 — models refreshed (claude-3-haiku retired upstream).
927
+ fallbackModels: ["openai/gpt-4o-mini", "anthropic/claude-haiku-4-5"],
928
+ // M45 — OpenRouter app-attribution headers (mechanism from OpenCode's openrouter plugin, MIT; VALUES are
929
+ // theokit's own — copying another app's referer would misattribute traffic).
930
+ extraHeaders: { "HTTP-Referer": "https://usetheo.dev", "X-Title": "theokit" }
931
+ };
932
+
933
+ // src/internal/providers/builtin/vertex.ts
934
+ var VERTEX = {
935
+ name: "vertex",
936
+ apiMode: "anthropic_messages",
937
+ // sub-dispatched in selectTransport by profile.name
938
+ envVars: ["GOOGLE_APPLICATION_CREDENTIALS"],
939
+ authType: "gcp_oauth",
940
+ baseUrl: "https://us-central1-aiplatform.googleapis.com",
941
+ modelsUrl: void 0,
942
+ hostname: "aiplatform.googleapis.com",
943
+ fallbackModels: [
944
+ "vertex/anthropic/claude-sonnet-4-5@20250929",
945
+ "vertex/google/gemini-2.0-flash-001"
946
+ ]
947
+ };
948
+
949
+ // src/internal/providers/builtin/index.ts
950
+ var registered = false;
951
+ function registerBuiltins() {
952
+ if (registered) return;
953
+ registered = true;
954
+ registerProvider(ANTHROPIC);
955
+ registerProvider(OPENAI);
956
+ registerProvider(OPENAI_CHATGPT);
957
+ registerProvider(OPENROUTER);
958
+ registerProvider(GEMINI);
959
+ registerProvider(OLLAMA);
960
+ registerProvider(LMSTUDIO);
961
+ registerProvider(LLAMACPP);
962
+ registerProvider(BEDROCK);
963
+ registerProvider(VERTEX);
964
+ registerProvider(GOOGLE);
965
+ registerProvider(MISTRAL);
966
+ registerProvider(GROQ);
967
+ registerProvider(COHERE);
968
+ registerProvider(DEEPINFRA);
969
+ registerProvider(TOGETHER);
970
+ registerProvider(XAI);
971
+ registerProvider(PERPLEXITY);
972
+ registerProvider(CEREBRAS);
973
+ registerCatalogProviders();
974
+ }
975
+
302
976
  // src/internal/providers/catalog-source-models-dev.ts
303
977
  var DEFAULT_URL = "https://models.dev/api.json";
304
978
  var TTL_MS = 60 * 60 * 1e3;
305
979
  var FETCH_TIMEOUT_MS = 1e4;
306
980
  function cachePathFor(url) {
307
- const dir = join(homedir(), ".theokit", "cache", "models-dev");
981
+ const base = process.env.THEOKIT_HOME?.trim() || join(homedir(), ".theokit");
982
+ const dir = join(base, "cache", "models-dev");
308
983
  if (url === DEFAULT_URL) return join(dir, "api.json");
309
984
  const hash = createHash("sha256").update(url).digest("hex").slice(0, 12);
310
985
  return join(dir, `api-${hash}.json`);
@@ -323,21 +998,62 @@ function writeCacheAtomic(path, body) {
323
998
  throw err;
324
999
  }
325
1000
  }
1001
+ var MODELS_DEV_ID_MAP = {
1002
+ google: "google-gemini",
1003
+ zai: "zhipu",
1004
+ togetherai: "together",
1005
+ "fireworks-ai": "fireworks",
1006
+ "amazon-bedrock": "bedrock",
1007
+ "google-vertex": "vertex"
1008
+ };
1009
+ var _catalogTargets;
1010
+ function catalogTargets() {
1011
+ if (_catalogTargets !== void 0) return _catalogTargets;
1012
+ _catalogTargets = /* @__PURE__ */ new Map();
1013
+ try {
1014
+ for (const entry of Object.values(loadProviderCatalog())) {
1015
+ const keys = [entry.id, ...entry.aliases ?? []];
1016
+ for (const k of keys) {
1017
+ if (!_catalogTargets.has(k)) _catalogTargets.set(k, { keys });
1018
+ }
1019
+ }
1020
+ } catch {
1021
+ }
1022
+ return _catalogTargets;
1023
+ }
1024
+ function resolvePatchKeys(externalId) {
1025
+ const mapped = MODELS_DEV_ID_MAP[externalId] ?? externalId;
1026
+ const fromCatalog = catalogTargets().get(mapped);
1027
+ if (fromCatalog !== void 0) return fromCatalog.keys;
1028
+ const profile = getProviderProfile(mapped);
1029
+ if (profile !== void 0) return [profile.name, ...profile.aliases ?? []];
1030
+ return void 0;
1031
+ }
326
1032
  function patchIndexFromApiJson(raw) {
327
1033
  if (typeof raw !== "object" || raw === null) return 0;
328
1034
  let patched = 0;
1035
+ const skipped = [];
329
1036
  for (const [providerId, provider] of Object.entries(raw)) {
330
1037
  const models = provider?.models;
331
1038
  if (models === void 0 || typeof models !== "object") continue;
332
- const profile = getProviderProfile(providerId);
333
- if (profile === void 0) continue;
1039
+ const keys = resolvePatchKeys(providerId);
1040
+ if (keys === void 0) {
1041
+ skipped.push(providerId);
1042
+ continue;
1043
+ }
334
1044
  for (const [modelId, rawModel] of Object.entries(models)) {
335
1045
  const parsed = catalogModelSchema.safeParse(rawModel);
336
1046
  if (!parsed.success) continue;
337
- patchModelInfo(`${profile.name}/${modelId}`, parsed.data);
1047
+ for (const key of keys) patchModelInfo(`${key}/${modelId}`, parsed.data);
338
1048
  patched++;
339
1049
  }
340
1050
  }
1051
+ if (skipped.length > 0) {
1052
+ process.stderr.write(
1053
+ `[theokit-sdk] WARN: models-dev refresh skipped ${skipped.length} unknown provider(s) (e.g. ${skipped.slice(0, 3).join(", ")})
1054
+ `
1055
+ );
1056
+ }
341
1057
  return patched;
342
1058
  }
343
1059
  function loadCacheIntoIndex(url = DEFAULT_URL) {
@@ -348,8 +1064,9 @@ function loadCacheIntoIndex(url = DEFAULT_URL) {
348
1064
  } catch {
349
1065
  return 0;
350
1066
  }
1067
+ let parsed;
351
1068
  try {
352
- return patchIndexFromApiJson(JSON.parse(body));
1069
+ parsed = JSON.parse(body);
353
1070
  } catch {
354
1071
  try {
355
1072
  unlinkSync(path);
@@ -359,9 +1076,20 @@ function loadCacheIntoIndex(url = DEFAULT_URL) {
359
1076
  `);
360
1077
  return 0;
361
1078
  }
1079
+ try {
1080
+ return patchIndexFromApiJson(parsed);
1081
+ } catch (err) {
1082
+ process.stderr.write(
1083
+ `[theokit-sdk] WARN: models-dev cache patch failed (${err.message})
1084
+ `
1085
+ );
1086
+ return 0;
1087
+ }
362
1088
  }
363
1089
  async function refreshModelCatalog(opts = {}) {
364
- if (process.env.THEOKIT_DISABLE_MODELS_FETCH !== void 0) {
1090
+ registerBuiltins();
1091
+ const kill = process.env.THEOKIT_DISABLE_MODELS_FETCH;
1092
+ if (kill !== void 0 && kill !== "" && kill !== "0" && kill.toLowerCase() !== "false") {
365
1093
  return { source: "skipped", models: 0 };
366
1094
  }
367
1095
  const url = opts.url ?? process.env.THEOKIT_MODELS_URL ?? DEFAULT_URL;
@@ -406,7 +1134,15 @@ async function refreshModelCatalog(opts = {}) {
406
1134
  `
407
1135
  );
408
1136
  }
409
- return { source: "network", models: patchIndexFromApiJson(JSON.parse(body)) };
1137
+ try {
1138
+ return { source: "network", models: patchIndexFromApiJson(JSON.parse(body)) };
1139
+ } catch (err) {
1140
+ process.stderr.write(
1141
+ `[theokit-sdk] WARN: models-dev patch failed (${err.message}) \u2014 serving existing data
1142
+ `
1143
+ );
1144
+ return { source: "cache", models: 0 };
1145
+ }
410
1146
  }
411
1147
  function getModelInfo(modelId) {
412
1148
  return getCatalogModelInfo(modelId);