@theokit/sdk 4.12.2 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.13.1
4
+
5
+ ### Patch Changes
6
+
7
+ - M44 adversarial-review fixes for the model catalog: (B1) the provider registry + model-info index now live on `globalThis` via `Symbol.for` so every bundle copy (`dist/index.js`, `dist/models.js` — tsup bundles entries separately) shares the SAME state — previously `refreshModelCatalog` from `@theokit/sdk/models` patched a bundle-local index invisible to capability/pricing lookups and saw an empty registry (a published-artifact-only defect the src-level tests could not catch); (H2) a live models-dev patch is now a per-field MERGE that preserves theokit extension fields (`cache_control`, overlay `structured_output`) instead of a wholesale replace that wiped them; (H3) the runtime refresh gained the models.dev↔theokit id mapping (google↔google-gemini, zai↔zhipu, togetherai↔together, fireworks-ai↔fireworks, amazon-bedrock↔bedrock, google-vertex↔vertex) resolving against catalog entries (id + aliases) with a WARN for skipped unknowns — Google/Z.AI/Together/Fireworks now actually refresh; (M4) `refreshModelCatalog` self-initializes provider registration so the `/models` subpath works standalone; (M5) pricing provenance is honest post-refresh (`catalog-models-dev` vs `catalog-vendored`); (M6) the anthropic builtin defaults (opus-4-7 / sonnet-4-6 / haiku-4-5) now carry `cache_control: true` in the vendored catalog; (L8) the models-dev cache honors `THEOKIT_HOME`; (L9) a missing/corrupt vendored catalog degrades with WARN instead of throwing from `resolveModelCapabilities`, cache patch errors never delete a valid cache, and refresh never rejects; (L10) the pricing step-5 fallback also tries the date-stripped id; (L11) falsy `THEOKIT_DISABLE_MODELS_FETCH` values (`0`/`false`/empty) no longer disable the fetch.
8
+
9
+ ## 4.13.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Model catalog enrichment (agent-builder M44): the vendored `provider-catalog.json` now carries OPTIONAL per-model data (`models` block — models.dev shape verbatim: `cost{input,output,cache_read,cache_write}` USD-per-1M, `limit{context,input,output}`, `modalities`, `tool_call`/`reasoning`/`structured_output`/`cache_control`, `release_date`, `status`), loaded into an internal model-info index keyed `provider/model` (entry id + aliases). Fully additive: entries without `models` behave byte-identically, `ProviderProfile` is untouched, the 10 builtins + all 43 catalog entries keep resolving, and a malformed model sub-entry drops that model with WARN keeping the provider. DRY reconciliation: `resolveModelCapabilities` is now catalog-backed (the hand-curated EXACT map migrated into the catalog and was deleted — parity-tested over the full old-map snapshot), and `getPricingEntry` gains a step-5 catalog fallback on total LiteLLM miss (provenance `pricingVersion:"catalog-vendored"`; the LiteLLM snapshot keeps absolute precedence — and the new drift advisory caught a real stale rate: `openai/o3` corrected 10/40 → 2/8). New on `@theokit/sdk/models`: `getModelInfo(modelId)` (the enriched per-model view) and `refreshModelCatalog({url?, force?})` — an EXPLICIT opt-in models.dev refresh with a 1h-TTL atomic disk cache under `~/.theokit/cache/models-dev/`, kill-switch `THEOKIT_DISABLE_MODELS_FETCH`, and the vendored catalog as offline fallback; startup and requests never touch the network. Mechanism adapted from OpenCode's models.dev consumption (MIT); see NOTICE. Maintenance: `scripts/refresh-catalog.mjs` regenerates the curated vendored subset (30 models, +36KB).
14
+
3
15
  ## 4.12.2
4
16
 
5
17
  ### Patch Changes
package/dist/cron.cjs CHANGED
@@ -570,10 +570,10 @@ function buildToolPrompt(prompt) {
570
570
  Respond by calling the \`output\` tool with the structured answer that matches the schema.`;
571
571
  }
572
572
  function setupStructuredOutput(schema, maxRetries) {
573
- const z9 = requireZod();
573
+ const z10 = requireZod();
574
574
  const jsonSchema = toJsonSchema(schema, { unrepresentable: "any" });
575
575
  return {
576
- z: z9,
576
+ z: z10,
577
577
  jsonSchema,
578
578
  maxRetries: maxRetries ?? 1,
579
579
  initialUsage: { inputTokens: 0, outputTokens: 0 }
@@ -10504,6 +10504,178 @@ function applyToolResultGuard(parts, opts) {
10504
10504
  )
10505
10505
  );
10506
10506
  }
10507
+ var MODALITIES = ["text", "audio", "image", "video", "pdf"];
10508
+ var costSchema = zod.z.object({
10509
+ /** USD per 1M tokens (models.dev convention). */
10510
+ input: zod.z.number().nonnegative(),
10511
+ output: zod.z.number().nonnegative(),
10512
+ cache_read: zod.z.number().nonnegative().optional(),
10513
+ cache_write: zod.z.number().nonnegative().optional()
10514
+ }).loose();
10515
+ var limitSchema = zod.z.object({
10516
+ context: zod.z.number().positive(),
10517
+ input: zod.z.number().positive().optional(),
10518
+ output: zod.z.number().positive().optional()
10519
+ }).loose();
10520
+ var modalitiesSchema = zod.z.object({
10521
+ input: zod.z.array(zod.z.enum(MODALITIES)).optional(),
10522
+ output: zod.z.array(zod.z.enum(MODALITIES)).optional()
10523
+ }).loose();
10524
+ var catalogModelSchema = zod.z.object({
10525
+ name: zod.z.string().optional(),
10526
+ release_date: zod.z.string().optional(),
10527
+ attachment: zod.z.boolean().optional(),
10528
+ reasoning: zod.z.boolean().optional(),
10529
+ temperature: zod.z.boolean().optional(),
10530
+ tool_call: zod.z.boolean().optional(),
10531
+ /** theokit extension — maps to ModelCapabilities.supportsStructuredOutput. */
10532
+ structured_output: zod.z.boolean().optional(),
10533
+ /** theokit extension — maps to ModelCapabilities.supportsCacheControl. */
10534
+ cache_control: zod.z.boolean().optional(),
10535
+ cost: costSchema.optional(),
10536
+ limit: limitSchema.optional(),
10537
+ modalities: modalitiesSchema.optional(),
10538
+ status: zod.z.enum(["alpha", "beta", "deprecated"]).optional()
10539
+ }).loose();
10540
+
10541
+ // src/internal/providers/registry.ts
10542
+ function globalSingleton(key, create) {
10543
+ const g = globalThis;
10544
+ const sym = Symbol.for(key);
10545
+ if (g[sym] === void 0) g[sym] = create();
10546
+ return g[sym];
10547
+ }
10548
+ var REGISTRY = globalSingleton(
10549
+ "theokit-sdk.providers.registry",
10550
+ () => /* @__PURE__ */ new Map()
10551
+ );
10552
+ var ALIASES = globalSingleton("theokit-sdk.providers.aliases", () => /* @__PURE__ */ new Map());
10553
+ function registerProvider(profile) {
10554
+ if (REGISTRY.has(profile.name)) {
10555
+ process.stderr.write(`[theokit-sdk] Provider "${profile.name}" overridden by user plugin.
10556
+ `);
10557
+ }
10558
+ REGISTRY.set(profile.name, profile);
10559
+ for (const alias of profile.aliases ?? []) {
10560
+ const previous = ALIASES.get(alias);
10561
+ if (previous !== void 0 && previous !== profile.name) {
10562
+ process.stderr.write(
10563
+ `[theokit-sdk] Alias "${alias}" collision: was "${previous}", now "${profile.name}".
10564
+ `
10565
+ );
10566
+ }
10567
+ ALIASES.set(alias, profile.name);
10568
+ }
10569
+ }
10570
+ function getProviderProfile(name) {
10571
+ const canonical = ALIASES.get(name) ?? name;
10572
+ return REGISTRY.get(canonical);
10573
+ }
10574
+
10575
+ // src/internal/providers/catalog-loader.ts
10576
+ 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('cron.cjs', document.baseURI).href))));
10577
+ function globalSingleton2(key, create) {
10578
+ const g = globalThis;
10579
+ const sym = Symbol.for(key);
10580
+ if (g[sym] === void 0) g[sym] = create();
10581
+ return g[sym];
10582
+ }
10583
+ var modelInfoIndex = globalSingleton2(
10584
+ "theokit-sdk.providers.model-info-index",
10585
+ () => /* @__PURE__ */ new Map()
10586
+ );
10587
+ var patchedModelKeys = globalSingleton2(
10588
+ "theokit-sdk.providers.model-info-patched",
10589
+ () => /* @__PURE__ */ new Set()
10590
+ );
10591
+ var indexState = globalSingleton2("theokit-sdk.providers.model-info-loaded", () => ({
10592
+ loaded: false
10593
+ }));
10594
+ function getCatalogModelInfo(key) {
10595
+ ensureModelIndexLoaded();
10596
+ return modelInfoIndex.get(key);
10597
+ }
10598
+ function isPatchedModelKey(key) {
10599
+ return patchedModelKeys.has(key);
10600
+ }
10601
+ function ensureModelIndexLoaded() {
10602
+ if (indexState.loaded) return;
10603
+ indexState.loaded = true;
10604
+ try {
10605
+ const catalog = loadProviderCatalog();
10606
+ for (const entry of Object.values(catalog)) {
10607
+ indexEntryModels(entry);
10608
+ }
10609
+ } catch (err) {
10610
+ process.stderr.write(
10611
+ `[theokit-sdk] WARN: provider catalog unavailable (${err.message}) \u2014 per-model data disabled
10612
+ `
10613
+ );
10614
+ }
10615
+ }
10616
+ function indexEntryModels(entry) {
10617
+ if (entry.models === void 0 || typeof entry.models !== "object") return;
10618
+ for (const [modelId, raw] of Object.entries(entry.models)) {
10619
+ const parsed = catalogModelSchema.safeParse(raw);
10620
+ if (!parsed.success) {
10621
+ process.stderr.write(
10622
+ `[theokit-sdk] WARN: Skipping malformed catalog model "${entry.id}/${modelId}": ${parsed.error.issues[0]?.message ?? "invalid"}
10623
+ `
10624
+ );
10625
+ continue;
10626
+ }
10627
+ modelInfoIndex.set(`${entry.id}/${modelId}`, parsed.data);
10628
+ for (const alias of entry.aliases ?? []) {
10629
+ const key = `${alias}/${modelId}`;
10630
+ if (!modelInfoIndex.has(key)) modelInfoIndex.set(key, parsed.data);
10631
+ }
10632
+ }
10633
+ }
10634
+ function validateEntry(raw) {
10635
+ if (typeof raw.id !== "string" || typeof raw.displayName !== "string" || typeof raw.apiMode !== "string" || typeof raw.authType !== "string" || typeof raw.baseUrl !== "string" || !Array.isArray(raw.envVars) || !Array.isArray(raw.fallbackModels) || raw.capabilities == null || typeof raw.capabilities !== "object") {
10636
+ return null;
10637
+ }
10638
+ return raw;
10639
+ }
10640
+ function loadProviderCatalog(opts) {
10641
+ const catalogPath = path.join(__dirname_resolved, "provider-catalog.json");
10642
+ const rawText = fs.readFileSync(catalogPath, "utf-8");
10643
+ let entries = JSON.parse(rawText);
10644
+ const result = {};
10645
+ for (const raw of entries) {
10646
+ const validated = validateEntry(raw);
10647
+ if (validated === null) {
10648
+ process.stderr.write(
10649
+ `[theokit-sdk] WARN: Skipping malformed catalog entry: ${JSON.stringify(raw).slice(0, 100)}
10650
+ `
10651
+ );
10652
+ continue;
10653
+ }
10654
+ result[validated.id] = validated;
10655
+ }
10656
+ return result;
10657
+ }
10658
+ function registerCatalogProviders(opts) {
10659
+ const catalog = loadProviderCatalog();
10660
+ for (const entry of Object.values(catalog)) {
10661
+ if (getProviderProfile(entry.id) !== void 0) continue;
10662
+ if (entry.aliases?.some((a) => getProviderProfile(a) !== void 0)) continue;
10663
+ const profile = {
10664
+ name: entry.id,
10665
+ apiMode: entry.apiMode,
10666
+ authType: entry.authType,
10667
+ baseUrl: entry.baseUrl,
10668
+ envVars: entry.envVars,
10669
+ fallbackModels: entry.fallbackModels,
10670
+ displayName: entry.displayName,
10671
+ aliases: entry.aliases,
10672
+ modelsUrl: entry.modelsUrl,
10673
+ hostname: entry.hostname,
10674
+ extraHeaders: entry.extraHeaders
10675
+ };
10676
+ registerProvider(profile);
10677
+ }
10678
+ }
10507
10679
 
10508
10680
  // src/internal/budget/pricing-data.json
10509
10681
  var pricing_data_default = {
@@ -10596,9 +10768,9 @@ var pricing_data_default = {
10596
10768
  cacheRead: 0.025
10597
10769
  },
10598
10770
  "openai/o3": {
10599
- input: 10,
10600
- output: 40,
10601
- cacheRead: 2.5
10771
+ input: 2,
10772
+ output: 8,
10773
+ cacheRead: 0.5
10602
10774
  },
10603
10775
  "openai/o3-mini": {
10604
10776
  input: 1.1,
@@ -10686,6 +10858,30 @@ function getPricingEntry(opts) {
10686
10858
  if (found2 !== void 0) return buildEntry(stripped, found2);
10687
10859
  }
10688
10860
  }
10861
+ const catalogEntry = catalogCostFallback(opts.provider, cleaned);
10862
+ if (catalogEntry !== void 0) return catalogEntry;
10863
+ return void 0;
10864
+ }
10865
+ function catalogCostFallback(provider, cleanedModel) {
10866
+ const stripped = stripDateSuffix(cleanedModel);
10867
+ const candidates = [`${provider}/${cleanedModel}`, cleanedModel];
10868
+ if (stripped !== cleanedModel) candidates.push(`${provider}/${stripped}`, stripped);
10869
+ for (const key of candidates) {
10870
+ const info = getCatalogModelInfo(key);
10871
+ const cost = info?.cost;
10872
+ if (cost === void 0) continue;
10873
+ const [prov, ...modelParts] = key.split("/");
10874
+ return {
10875
+ provider: prov ?? provider,
10876
+ model: modelParts.join("/") || cleanedModel,
10877
+ inputCostPerMillion: cost.input,
10878
+ outputCostPerMillion: cost.output,
10879
+ ...cost.cache_read !== void 0 ? { cacheReadCostPerMillion: cost.cache_read } : {},
10880
+ ...cost.cache_write !== void 0 ? { cacheWriteCostPerMillion: cost.cache_write } : {},
10881
+ // M44 M5 fix — honest provenance: a key patched by the LIVE models-dev source is not "vendored".
10882
+ pricingVersion: isPatchedModelKey(key) ? "catalog-models-dev" : "catalog-vendored"
10883
+ };
10884
+ }
10689
10885
  return void 0;
10690
10886
  }
10691
10887
 
@@ -11202,79 +11398,6 @@ function abortError(signal) {
11202
11398
  // src/internal/llm/router.ts
11203
11399
  init_errors();
11204
11400
 
11205
- // src/internal/providers/registry.ts
11206
- var REGISTRY = /* @__PURE__ */ new Map();
11207
- var ALIASES = /* @__PURE__ */ new Map();
11208
- function registerProvider(profile) {
11209
- if (REGISTRY.has(profile.name)) {
11210
- process.stderr.write(`[theokit-sdk] Provider "${profile.name}" overridden by user plugin.
11211
- `);
11212
- }
11213
- REGISTRY.set(profile.name, profile);
11214
- for (const alias of profile.aliases ?? []) {
11215
- const previous = ALIASES.get(alias);
11216
- if (previous !== void 0 && previous !== profile.name) {
11217
- process.stderr.write(
11218
- `[theokit-sdk] Alias "${alias}" collision: was "${previous}", now "${profile.name}".
11219
- `
11220
- );
11221
- }
11222
- ALIASES.set(alias, profile.name);
11223
- }
11224
- }
11225
- function getProviderProfile(name) {
11226
- const canonical = ALIASES.get(name) ?? name;
11227
- return REGISTRY.get(canonical);
11228
- }
11229
-
11230
- // src/internal/providers/catalog-loader.ts
11231
- 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('cron.cjs', document.baseURI).href))));
11232
- function validateEntry(raw) {
11233
- if (typeof raw.id !== "string" || typeof raw.displayName !== "string" || typeof raw.apiMode !== "string" || typeof raw.authType !== "string" || typeof raw.baseUrl !== "string" || !Array.isArray(raw.envVars) || !Array.isArray(raw.fallbackModels) || raw.capabilities == null || typeof raw.capabilities !== "object") {
11234
- return null;
11235
- }
11236
- return raw;
11237
- }
11238
- function loadProviderCatalog(opts) {
11239
- const catalogPath = path.join(__dirname_resolved, "provider-catalog.json");
11240
- const rawText = fs.readFileSync(catalogPath, "utf-8");
11241
- let entries = JSON.parse(rawText);
11242
- const result = {};
11243
- for (const raw of entries) {
11244
- const validated = validateEntry(raw);
11245
- if (validated === null) {
11246
- process.stderr.write(
11247
- `[theokit-sdk] WARN: Skipping malformed catalog entry: ${JSON.stringify(raw).slice(0, 100)}
11248
- `
11249
- );
11250
- continue;
11251
- }
11252
- result[validated.id] = validated;
11253
- }
11254
- return result;
11255
- }
11256
- function registerCatalogProviders(opts) {
11257
- const catalog = loadProviderCatalog();
11258
- for (const entry of Object.values(catalog)) {
11259
- if (getProviderProfile(entry.id) !== void 0) continue;
11260
- if (entry.aliases?.some((a) => getProviderProfile(a) !== void 0)) continue;
11261
- const profile = {
11262
- name: entry.id,
11263
- apiMode: entry.apiMode,
11264
- authType: entry.authType,
11265
- baseUrl: entry.baseUrl,
11266
- envVars: entry.envVars,
11267
- fallbackModels: entry.fallbackModels,
11268
- displayName: entry.displayName,
11269
- aliases: entry.aliases,
11270
- modelsUrl: entry.modelsUrl,
11271
- hostname: entry.hostname,
11272
- extraHeaders: entry.extraHeaders
11273
- };
11274
- registerProvider(profile);
11275
- }
11276
- }
11277
-
11278
11401
  // src/internal/providers/builtin/anthropic.ts
11279
11402
  var ANTHROPIC = {
11280
11403
  name: "anthropic",