@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/dist/models.cjs CHANGED
@@ -1,5 +1,190 @@
1
1
  'use strict';
2
2
 
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var url = require('url');
6
+ var zod = require('zod');
7
+ var crypto = require('crypto');
8
+ var os = require('os');
9
+
10
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
11
+ // src/internal/providers/catalog-loader.ts
12
+ var MODALITIES = ["text", "audio", "image", "video", "pdf"];
13
+ var costSchema = zod.z.object({
14
+ /** USD per 1M tokens (models.dev convention). */
15
+ input: zod.z.number().nonnegative(),
16
+ output: zod.z.number().nonnegative(),
17
+ cache_read: zod.z.number().nonnegative().optional(),
18
+ cache_write: zod.z.number().nonnegative().optional()
19
+ }).loose();
20
+ var limitSchema = zod.z.object({
21
+ context: zod.z.number().positive(),
22
+ input: zod.z.number().positive().optional(),
23
+ output: zod.z.number().positive().optional()
24
+ }).loose();
25
+ var modalitiesSchema = zod.z.object({
26
+ input: zod.z.array(zod.z.enum(MODALITIES)).optional(),
27
+ output: zod.z.array(zod.z.enum(MODALITIES)).optional()
28
+ }).loose();
29
+ var catalogModelSchema = zod.z.object({
30
+ name: zod.z.string().optional(),
31
+ release_date: zod.z.string().optional(),
32
+ attachment: zod.z.boolean().optional(),
33
+ reasoning: zod.z.boolean().optional(),
34
+ temperature: zod.z.boolean().optional(),
35
+ tool_call: zod.z.boolean().optional(),
36
+ /** theokit extension — maps to ModelCapabilities.supportsStructuredOutput. */
37
+ structured_output: zod.z.boolean().optional(),
38
+ /** theokit extension — maps to ModelCapabilities.supportsCacheControl. */
39
+ cache_control: zod.z.boolean().optional(),
40
+ cost: costSchema.optional(),
41
+ limit: limitSchema.optional(),
42
+ modalities: modalitiesSchema.optional(),
43
+ status: zod.z.enum(["alpha", "beta", "deprecated"]).optional()
44
+ }).loose();
45
+
46
+ // src/internal/providers/registry.ts
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
+ }
75
+ function getProviderProfile(name) {
76
+ const canonical = ALIASES.get(name) ?? name;
77
+ return REGISTRY.get(canonical);
78
+ }
79
+
80
+ // src/internal/providers/catalog-loader.ts
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))));
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
+ }));
99
+ function getCatalogModelInfo(key) {
100
+ ensureModelIndexLoaded();
101
+ return modelInfoIndex.get(key);
102
+ }
103
+ function patchModelInfo(key, model) {
104
+ ensureModelIndexLoaded();
105
+ const existing = modelInfoIndex.get(key);
106
+ modelInfoIndex.set(key, existing === void 0 ? model : { ...existing, ...model });
107
+ patchedModelKeys.add(key);
108
+ }
109
+ function ensureModelIndexLoaded() {
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
+ );
122
+ }
123
+ }
124
+ function indexEntryModels(entry) {
125
+ if (entry.models === void 0 || typeof entry.models !== "object") return;
126
+ for (const [modelId, raw] of Object.entries(entry.models)) {
127
+ const parsed = catalogModelSchema.safeParse(raw);
128
+ if (!parsed.success) {
129
+ process.stderr.write(
130
+ `[theokit-sdk] WARN: Skipping malformed catalog model "${entry.id}/${modelId}": ${parsed.error.issues[0]?.message ?? "invalid"}
131
+ `
132
+ );
133
+ continue;
134
+ }
135
+ modelInfoIndex.set(`${entry.id}/${modelId}`, parsed.data);
136
+ for (const alias of entry.aliases ?? []) {
137
+ const key = `${alias}/${modelId}`;
138
+ if (!modelInfoIndex.has(key)) modelInfoIndex.set(key, parsed.data);
139
+ }
140
+ }
141
+ }
142
+ function validateEntry(raw) {
143
+ 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") {
144
+ return null;
145
+ }
146
+ return raw;
147
+ }
148
+ function loadProviderCatalog(opts) {
149
+ const catalogPath = path.join(__dirname_resolved, "provider-catalog.json");
150
+ const rawText = fs.readFileSync(catalogPath, "utf-8");
151
+ let entries = JSON.parse(rawText);
152
+ const result = {};
153
+ for (const raw of entries) {
154
+ const validated = validateEntry(raw);
155
+ if (validated === null) {
156
+ process.stderr.write(
157
+ `[theokit-sdk] WARN: Skipping malformed catalog entry: ${JSON.stringify(raw).slice(0, 100)}
158
+ `
159
+ );
160
+ continue;
161
+ }
162
+ result[validated.id] = validated;
163
+ }
164
+ return result;
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
+ }
187
+
3
188
  // src/internal/llm/model-capabilities.ts
4
189
  var CONSERVATIVE_DEFAULTS = {
5
190
  supportsVision: false,
@@ -9,268 +194,25 @@ var CONSERVATIVE_DEFAULTS = {
9
194
  maxContextTokens: 4096,
10
195
  maxOutputTokens: 4096
11
196
  };
12
- var EXACT = /* @__PURE__ */ new Map([
13
- // OpenAI family
14
- [
15
- "openai/gpt-4o",
16
- {
17
- supportsVision: true,
18
- supportsStructuredOutput: true,
19
- supportsToolUse: true,
20
- supportsCacheControl: false,
21
- maxContextTokens: 128e3,
22
- maxOutputTokens: 16384
23
- }
24
- ],
25
- [
26
- "openai/gpt-4o-mini",
27
- {
28
- supportsVision: true,
29
- supportsStructuredOutput: true,
30
- supportsToolUse: true,
31
- supportsCacheControl: false,
32
- maxContextTokens: 128e3,
33
- maxOutputTokens: 16384
34
- }
35
- ],
36
- [
37
- "openai/gpt-4-turbo",
38
- {
39
- supportsVision: true,
40
- supportsStructuredOutput: false,
41
- supportsToolUse: true,
42
- supportsCacheControl: false,
43
- maxContextTokens: 128e3,
44
- maxOutputTokens: 4096
45
- }
46
- ],
47
- [
48
- "openai/o1",
49
- {
50
- supportsVision: false,
51
- supportsStructuredOutput: true,
52
- supportsToolUse: true,
53
- supportsCacheControl: false,
54
- maxContextTokens: 2e5,
55
- maxOutputTokens: 1e5
56
- }
57
- ],
58
- [
59
- "openai/o3",
60
- {
61
- supportsVision: false,
62
- supportsStructuredOutput: true,
63
- supportsToolUse: true,
64
- supportsCacheControl: false,
65
- maxContextTokens: 2e5,
66
- maxOutputTokens: 1e5
67
- }
68
- ],
69
- [
70
- // GPT-4.1 — 1M-context flagship; multimodal + structured output (RADAR #92.a).
71
- "openai/gpt-4.1",
72
- {
73
- supportsVision: true,
74
- supportsStructuredOutput: true,
75
- supportsToolUse: true,
76
- supportsCacheControl: false,
77
- maxContextTokens: 1047576,
78
- maxOutputTokens: 32768
79
- }
80
- ],
81
- // Anthropic family
82
- [
83
- "anthropic/claude-opus-4",
84
- {
85
- supportsVision: true,
86
- supportsStructuredOutput: false,
87
- supportsToolUse: true,
88
- supportsCacheControl: true,
89
- maxContextTokens: 2e5,
90
- maxOutputTokens: 32e3
91
- }
92
- ],
93
- [
94
- "anthropic/claude-sonnet-4",
95
- {
96
- supportsVision: true,
97
- supportsStructuredOutput: false,
98
- supportsToolUse: true,
99
- supportsCacheControl: true,
100
- maxContextTokens: 2e5,
101
- maxOutputTokens: 16e3
102
- }
103
- ],
104
- [
105
- "anthropic/claude-3-5-sonnet",
106
- {
107
- supportsVision: true,
108
- supportsStructuredOutput: false,
109
- supportsToolUse: true,
110
- supportsCacheControl: true,
111
- maxContextTokens: 2e5,
112
- maxOutputTokens: 8192
113
- }
114
- ],
115
- [
116
- "anthropic/claude-3-5-sonnet-latest",
117
- {
118
- supportsVision: true,
119
- supportsStructuredOutput: false,
120
- supportsToolUse: true,
121
- supportsCacheControl: true,
122
- maxContextTokens: 2e5,
123
- maxOutputTokens: 8192
124
- }
125
- ],
126
- [
127
- "anthropic/claude-3-5-haiku-latest",
128
- {
129
- supportsVision: false,
130
- supportsStructuredOutput: false,
131
- supportsToolUse: true,
132
- supportsCacheControl: true,
133
- maxContextTokens: 2e5,
134
- maxOutputTokens: 8192
135
- }
136
- ],
137
- [
138
- "anthropic/claude-3-haiku",
139
- {
140
- supportsVision: true,
141
- supportsStructuredOutput: false,
142
- supportsToolUse: true,
143
- supportsCacheControl: true,
144
- maxContextTokens: 2e5,
145
- maxOutputTokens: 4096
146
- }
147
- ],
148
- [
149
- "anthropic/claude-3-opus",
150
- {
151
- supportsVision: true,
152
- supportsStructuredOutput: false,
153
- supportsToolUse: true,
154
- supportsCacheControl: true,
155
- maxContextTokens: 2e5,
156
- maxOutputTokens: 4096
157
- }
158
- ],
159
- // Dot-form OpenRouter slugs theocode uses (RADAR #92.a). These are the same
160
- // models as their dash-form siblings above; capability parity is intentional.
161
- // Without these entries the dotted slugs fall through to the 4096 default
162
- // (`anthropic/claude-3.5-sonnet` ≠ `anthropic/claude-3-5-sonnet`).
163
- [
164
- "anthropic/claude-opus-4.1",
165
- {
166
- supportsVision: true,
167
- supportsStructuredOutput: false,
168
- supportsToolUse: true,
169
- supportsCacheControl: true,
170
- maxContextTokens: 2e5,
171
- maxOutputTokens: 32e3
172
- }
173
- ],
174
- [
175
- "anthropic/claude-sonnet-4.5",
176
- {
177
- supportsVision: true,
178
- supportsStructuredOutput: false,
179
- supportsToolUse: true,
180
- supportsCacheControl: true,
181
- maxContextTokens: 2e5,
182
- maxOutputTokens: 16e3
183
- }
184
- ],
185
- [
186
- "anthropic/claude-3.5-sonnet",
187
- {
188
- supportsVision: true,
189
- supportsStructuredOutput: false,
190
- supportsToolUse: true,
191
- supportsCacheControl: true,
192
- maxContextTokens: 2e5,
193
- maxOutputTokens: 8192
194
- }
195
- ],
196
- // Cheap OpenRouter slugs (RADAR #92.a) — previously fell to the 4096
197
- // CONSERVATIVE default. toolUse on; vision/structuredOutput only for Gemini.
198
- [
199
- "qwen/qwen3-coder-30b-a3b-instruct",
200
- {
201
- supportsVision: false,
202
- supportsStructuredOutput: false,
203
- supportsToolUse: true,
204
- supportsCacheControl: false,
205
- maxContextTokens: 16e4,
206
- maxOutputTokens: 8e3
207
- }
208
- ],
209
- [
210
- "deepseek/deepseek-v4-flash",
211
- {
212
- supportsVision: false,
213
- supportsStructuredOutput: false,
214
- supportsToolUse: true,
215
- supportsCacheControl: false,
216
- maxContextTokens: 1048576,
217
- maxOutputTokens: 8e3
218
- }
219
- ],
220
- [
221
- "deepseek/deepseek-v3.2",
222
- {
223
- supportsVision: false,
224
- supportsStructuredOutput: false,
225
- supportsToolUse: true,
226
- supportsCacheControl: false,
227
- maxContextTokens: 131072,
228
- maxOutputTokens: 8e3
229
- }
230
- ],
231
- [
232
- "z-ai/glm-4.7-flash",
233
- {
234
- supportsVision: false,
235
- supportsStructuredOutput: false,
236
- supportsToolUse: true,
237
- supportsCacheControl: false,
238
- maxContextTokens: 202752,
239
- maxOutputTokens: 8e3
240
- }
241
- ],
242
- [
243
- "google/gemini-2.5-flash-lite",
244
- {
245
- supportsVision: true,
246
- supportsStructuredOutput: true,
247
- supportsToolUse: true,
248
- supportsCacheControl: false,
249
- maxContextTokens: 1048576,
250
- maxOutputTokens: 8e3
251
- }
252
- ],
253
- [
254
- "google/gemini-2.5-pro",
255
- {
256
- supportsVision: true,
257
- supportsStructuredOutput: true,
258
- supportsToolUse: true,
259
- supportsCacheControl: false,
260
- maxContextTokens: 1048576,
261
- maxOutputTokens: 8e3
262
- }
263
- ]
264
- ]);
265
197
  var ROUTING_PREFIXES = ["openrouter/", "vertex/", "bedrock/"];
198
+ function capsFromCatalog(m) {
199
+ return {
200
+ supportsVision: m.modalities?.input?.includes("image") ?? m.attachment ?? false,
201
+ supportsStructuredOutput: m.structured_output ?? false,
202
+ supportsToolUse: m.tool_call ?? false,
203
+ supportsCacheControl: m.cache_control ?? false,
204
+ maxContextTokens: m.limit?.context ?? CONSERVATIVE_DEFAULTS.maxContextTokens,
205
+ maxOutputTokens: m.limit?.output ?? CONSERVATIVE_DEFAULTS.maxOutputTokens
206
+ };
207
+ }
266
208
  function resolveModelCapabilities(modelId) {
267
209
  const bare = stripVariantSuffix(stripRoutingPrefix(modelId));
268
- const exact = EXACT.get(bare);
269
- if (exact !== void 0) return exact;
210
+ const fromIndex = getCatalogModelInfo(bare);
211
+ if (fromIndex !== void 0) return capsFromCatalog(fromIndex);
270
212
  const withVendor = inferVendorPrefix(bare);
271
213
  if (withVendor !== bare) {
272
- const vendored = EXACT.get(withVendor);
273
- if (vendored !== void 0) return vendored;
214
+ const vendored = getCatalogModelInfo(withVendor);
215
+ if (vendored !== void 0) return capsFromCatalog(vendored);
274
216
  }
275
217
  return CONSERVATIVE_DEFAULTS;
276
218
  }
@@ -342,8 +284,763 @@ function toModelOption(modelId) {
342
284
  };
343
285
  }
344
286
 
287
+ // src/errors.ts
288
+ var TheokitAgentError = class extends Error {
289
+ name = "TheokitAgentError";
290
+ isRetryable;
291
+ code;
292
+ protoErrorCode;
293
+ metadata;
294
+ constructor(message, options = {}) {
295
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
296
+ this.isRetryable = options.isRetryable ?? false;
297
+ if (options.code !== void 0) this.code = options.code;
298
+ if (options.protoErrorCode !== void 0) this.protoErrorCode = options.protoErrorCode;
299
+ if (options.metadata !== void 0) this.metadata = options.metadata;
300
+ }
301
+ };
302
+ var ConfigurationError = class extends TheokitAgentError {
303
+ name = "ConfigurationError";
304
+ constructor(message, options = {}) {
305
+ super(message, { ...options, isRetryable: false });
306
+ }
307
+ };
308
+ function isTransientError(err) {
309
+ return err instanceof TheokitAgentError && err.isRetryable === true;
310
+ }
311
+
312
+ // src/internal/runtime/retry/with-retry.ts
313
+ function defaultSleep(ms, signal) {
314
+ return new Promise((resolve, reject) => {
315
+ if (signal?.aborted) {
316
+ reject(signal.reason instanceof Error ? signal.reason : new Error("withRetry: aborted"));
317
+ return;
318
+ }
319
+ const timer = setTimeout(() => {
320
+ signal?.removeEventListener("abort", onAbort);
321
+ resolve();
322
+ }, ms);
323
+ function onAbort() {
324
+ clearTimeout(timer);
325
+ reject(signal?.reason instanceof Error ? signal.reason : new Error("withRetry: aborted"));
326
+ }
327
+ signal?.addEventListener("abort", onAbort, { once: true });
328
+ });
329
+ }
330
+ function resolveRetryOptions(options) {
331
+ const retries = options?.retries ?? 3;
332
+ if (!Number.isInteger(retries) || retries < 0) {
333
+ throw new ConfigurationError(
334
+ `withRetry: retries must be a non-negative integer, got ${retries}`,
335
+ { code: "invalid_retry_config" }
336
+ );
337
+ }
338
+ return {
339
+ retries,
340
+ isRetryable: options?.isRetryable ?? isTransientError,
341
+ initialDelayMs: options?.initialDelayMs ?? 100,
342
+ maxDelayMs: options?.maxDelayMs ?? 3e4,
343
+ backoffMultiplier: options?.backoffMultiplier ?? 2,
344
+ rng: options?.rng ?? Math.random,
345
+ sleep: options?.sleep ?? defaultSleep,
346
+ signal: options?.signal
347
+ };
348
+ }
349
+ function backoffMs(cfg, attempt) {
350
+ const ceiling = Math.min(cfg.maxDelayMs, cfg.initialDelayMs * cfg.backoffMultiplier ** attempt);
351
+ return Math.floor(cfg.rng() * ceiling);
352
+ }
353
+ async function withRetry(fn, options) {
354
+ const cfg = resolveRetryOptions(options);
355
+ let attempt = 0;
356
+ for (; ; ) {
357
+ try {
358
+ return await fn();
359
+ } catch (err) {
360
+ if (attempt >= cfg.retries || !cfg.isRetryable(err)) throw err;
361
+ await cfg.sleep(backoffMs(cfg, attempt), cfg.signal);
362
+ attempt += 1;
363
+ }
364
+ }
365
+ }
366
+
367
+ // src/retry.ts
368
+ var Retry = class {
369
+ constructor() {
370
+ }
371
+ static create(fn, options) {
372
+ return withRetry(fn, options);
373
+ }
374
+ };
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
+
865
+ // src/internal/providers/catalog-source-models-dev.ts
866
+ var DEFAULT_URL = "https://models.dev/api.json";
867
+ var TTL_MS = 60 * 60 * 1e3;
868
+ var FETCH_TIMEOUT_MS = 1e4;
869
+ function cachePathFor(url) {
870
+ const base = process.env.THEOKIT_HOME?.trim() || path.join(os.homedir(), ".theokit");
871
+ const dir = path.join(base, "cache", "models-dev");
872
+ if (url === DEFAULT_URL) return path.join(dir, "api.json");
873
+ const hash = crypto.createHash("sha256").update(url).digest("hex").slice(0, 12);
874
+ return path.join(dir, `api-${hash}.json`);
875
+ }
876
+ function writeCacheAtomic(path$1, body) {
877
+ fs.mkdirSync(path.dirname(path$1), { recursive: true });
878
+ const tmp = `${path$1}.tmp-${crypto.randomBytes(6).toString("hex")}`;
879
+ try {
880
+ fs.writeFileSync(tmp, body);
881
+ fs.renameSync(tmp, path$1);
882
+ } catch (err) {
883
+ try {
884
+ fs.unlinkSync(tmp);
885
+ } catch {
886
+ }
887
+ throw err;
888
+ }
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
+ }
921
+ function patchIndexFromApiJson(raw) {
922
+ if (typeof raw !== "object" || raw === null) return 0;
923
+ let patched = 0;
924
+ const skipped = [];
925
+ for (const [providerId, provider] of Object.entries(raw)) {
926
+ const models = provider?.models;
927
+ if (models === void 0 || typeof models !== "object") continue;
928
+ const keys = resolvePatchKeys(providerId);
929
+ if (keys === void 0) {
930
+ skipped.push(providerId);
931
+ continue;
932
+ }
933
+ for (const [modelId, rawModel] of Object.entries(models)) {
934
+ const parsed = catalogModelSchema.safeParse(rawModel);
935
+ if (!parsed.success) continue;
936
+ for (const key of keys) patchModelInfo(`${key}/${modelId}`, parsed.data);
937
+ patched++;
938
+ }
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
+ }
946
+ return patched;
947
+ }
948
+ function loadCacheIntoIndex(url = DEFAULT_URL) {
949
+ const path = cachePathFor(url);
950
+ let body;
951
+ try {
952
+ body = fs.readFileSync(path, "utf-8");
953
+ } catch {
954
+ return 0;
955
+ }
956
+ let parsed;
957
+ try {
958
+ parsed = JSON.parse(body);
959
+ } catch {
960
+ try {
961
+ fs.unlinkSync(path);
962
+ } catch {
963
+ }
964
+ process.stderr.write(`[theokit-sdk] WARN: corrupt models-dev cache deleted (${path})
965
+ `);
966
+ return 0;
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
+ }
977
+ }
978
+ async function refreshModelCatalog(opts = {}) {
979
+ registerBuiltins();
980
+ const kill = process.env.THEOKIT_DISABLE_MODELS_FETCH;
981
+ if (kill !== void 0 && kill !== "" && kill !== "0" && kill.toLowerCase() !== "false") {
982
+ return { source: "skipped", models: 0 };
983
+ }
984
+ const url = opts.url ?? process.env.THEOKIT_MODELS_URL ?? DEFAULT_URL;
985
+ const path = cachePathFor(url);
986
+ const now = opts.deps?.now ?? (() => Date.now());
987
+ if (opts.force !== true) {
988
+ try {
989
+ const age = now() - fs.statSync(path).mtimeMs;
990
+ if (age < TTL_MS) {
991
+ return { source: "cache", models: loadCacheIntoIndex(url) };
992
+ }
993
+ } catch {
994
+ }
995
+ }
996
+ const fetchImpl = opts.deps?.fetch ?? fetch;
997
+ let body;
998
+ try {
999
+ const res = await Retry.create(
1000
+ async () => {
1001
+ const r = await fetchImpl(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
1002
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
1003
+ return r;
1004
+ },
1005
+ // 2 transient retries with backoff (OpenCode does the same); every error here is worth one more try —
1006
+ // the whole call is already fail-closed at the caller.
1007
+ { retries: 2, isRetryable: () => true, initialDelayMs: 200 }
1008
+ );
1009
+ body = await res.text();
1010
+ JSON.parse(body);
1011
+ } catch (err) {
1012
+ process.stderr.write(
1013
+ `[theokit-sdk] WARN: models-dev refresh failed (${err.message}) \u2014 serving existing data
1014
+ `
1015
+ );
1016
+ return { source: "cache", models: loadCacheIntoIndex(url) };
1017
+ }
1018
+ try {
1019
+ writeCacheAtomic(path, body);
1020
+ } catch (err) {
1021
+ process.stderr.write(
1022
+ `[theokit-sdk] WARN: models-dev cache write failed (${err.message})
1023
+ `
1024
+ );
1025
+ }
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
+ }
1035
+ }
1036
+ function getModelInfo(modelId) {
1037
+ return getCatalogModelInfo(modelId);
1038
+ }
1039
+
1040
+ exports.getModelInfo = getModelInfo;
345
1041
  exports.humanizeModelName = humanizeModelName;
346
1042
  exports.parseModelId = parseModelId;
1043
+ exports.refreshModelCatalog = refreshModelCatalog;
347
1044
  exports.resolveModelCapabilities = resolveModelCapabilities;
348
1045
  exports.toModelOption = toModelOption;
349
1046
  //# sourceMappingURL=models.cjs.map