@dexto/core 1.1.4
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/LICENSE +44 -0
- package/dist/chunk-D62MHQBE.js +2203 -0
- package/dist/chunk-F2QFAECT.js +87 -0
- package/dist/chunk-FCJVTIBV.js +535 -0
- package/dist/chunk-J6AXCN3H.js +1268 -0
- package/dist/chunk-MVKLS3LM.js +43 -0
- package/dist/chunk-PI6XFMEW.js +141 -0
- package/dist/chunk-TPERKLLN.js +75 -0
- package/dist/chunk-XFQLRBHE.js +122 -0
- package/dist/errors-ZZ4Z3FKB.js +10 -0
- package/dist/index.browser.cjs +250 -0
- package/dist/index.browser.d.cts +379 -0
- package/dist/index.browser.d.ts +379 -0
- package/dist/index.browser.js +217 -0
- package/dist/index.cjs +15283 -0
- package/dist/index.d.cts +6842 -0
- package/dist/index.d.ts +6842 -0
- package/dist/index.js +9914 -0
- package/dist/loader-HBNEYPQZ.js +20 -0
- package/dist/path-TP7WBDED.js +21 -0
- package/dist/postgres-backend-WMWS7RAT.js +216 -0
- package/dist/redis-backend-BNLN3XHX.js +169 -0
- package/dist/registry-Z4DFXODW.js +14 -0
- package/dist/sqlite-backend-AR6XNK2Q.js +248 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1268 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DextoValidationError
|
|
3
|
+
} from "./chunk-XFQLRBHE.js";
|
|
4
|
+
import {
|
|
5
|
+
getDextoGlobalPath,
|
|
6
|
+
logger
|
|
7
|
+
} from "./chunk-D62MHQBE.js";
|
|
8
|
+
import {
|
|
9
|
+
DextoRuntimeError
|
|
10
|
+
} from "./chunk-TPERKLLN.js";
|
|
11
|
+
import {
|
|
12
|
+
init_esm_shims
|
|
13
|
+
} from "./chunk-MVKLS3LM.js";
|
|
14
|
+
|
|
15
|
+
// src/preferences/loader.ts
|
|
16
|
+
init_esm_shims();
|
|
17
|
+
import { existsSync } from "fs";
|
|
18
|
+
import { promises as fs } from "fs";
|
|
19
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
20
|
+
|
|
21
|
+
// src/preferences/schemas.ts
|
|
22
|
+
init_esm_shims();
|
|
23
|
+
import { z as z2 } from "zod";
|
|
24
|
+
|
|
25
|
+
// src/llm/registry.ts
|
|
26
|
+
init_esm_shims();
|
|
27
|
+
|
|
28
|
+
// src/llm/errors.ts
|
|
29
|
+
init_esm_shims();
|
|
30
|
+
|
|
31
|
+
// src/llm/error-codes.ts
|
|
32
|
+
init_esm_shims();
|
|
33
|
+
var LLMErrorCode = /* @__PURE__ */ ((LLMErrorCode2) => {
|
|
34
|
+
LLMErrorCode2["API_KEY_MISSING"] = "llm_api_key_missing";
|
|
35
|
+
LLMErrorCode2["API_KEY_INVALID"] = "llm_api_key_invalid";
|
|
36
|
+
LLMErrorCode2["API_KEY_CANDIDATE_MISSING"] = "llm_api_key_candidate_missing";
|
|
37
|
+
LLMErrorCode2["BASE_URL_MISSING"] = "llm_base_url_missing";
|
|
38
|
+
LLMErrorCode2["BASE_URL_INVALID"] = "llm_base_url_invalid";
|
|
39
|
+
LLMErrorCode2["MODEL_INCOMPATIBLE"] = "llm_model_incompatible";
|
|
40
|
+
LLMErrorCode2["MODEL_UNKNOWN"] = "llm_model_unknown";
|
|
41
|
+
LLMErrorCode2["PROVIDER_UNSUPPORTED"] = "llm_provider_unsupported";
|
|
42
|
+
LLMErrorCode2["ROUTER_UNSUPPORTED"] = "llm_router_unsupported";
|
|
43
|
+
LLMErrorCode2["INPUT_FILE_UNSUPPORTED"] = "llm_input_file_unsupported";
|
|
44
|
+
LLMErrorCode2["INPUT_IMAGE_UNSUPPORTED"] = "llm_input_image_unsupported";
|
|
45
|
+
LLMErrorCode2["INPUT_TEXT_INVALID"] = "llm_input_text_invalid";
|
|
46
|
+
LLMErrorCode2["TOKENS_EXCEEDED"] = "llm_tokens_exceeded";
|
|
47
|
+
LLMErrorCode2["RATE_LIMIT_EXCEEDED"] = "llm_rate_limit_exceeded";
|
|
48
|
+
LLMErrorCode2["SWITCH_FAILED"] = "llm_switch_failed";
|
|
49
|
+
LLMErrorCode2["GENERATION_FAILED"] = "llm_generation_failed";
|
|
50
|
+
LLMErrorCode2["SWITCH_INPUT_MISSING"] = "llm_switch_input_missing";
|
|
51
|
+
LLMErrorCode2["REQUEST_INVALID_SCHEMA"] = "llm_request_invalid_schema";
|
|
52
|
+
return LLMErrorCode2;
|
|
53
|
+
})(LLMErrorCode || {});
|
|
54
|
+
|
|
55
|
+
// src/llm/errors.ts
|
|
56
|
+
var LLMError = class {
|
|
57
|
+
// Runtime model/provider lookup errors
|
|
58
|
+
static unknownModel(provider, model) {
|
|
59
|
+
return new DextoRuntimeError(
|
|
60
|
+
"llm_model_unknown" /* MODEL_UNKNOWN */,
|
|
61
|
+
"llm" /* LLM */,
|
|
62
|
+
"user" /* USER */,
|
|
63
|
+
`Unknown model '${model}' for provider '${provider}'`,
|
|
64
|
+
{ provider, model }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
static baseUrlMissing(provider) {
|
|
68
|
+
return new DextoRuntimeError(
|
|
69
|
+
"llm_base_url_missing" /* BASE_URL_MISSING */,
|
|
70
|
+
"llm" /* LLM */,
|
|
71
|
+
"user" /* USER */,
|
|
72
|
+
`Provider '${provider}' requires a baseURL (set config.baseURL or OPENAI_BASE_URL environment variable)`,
|
|
73
|
+
{ provider }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
static modelProviderUnknown(model) {
|
|
77
|
+
const availableProviders = getSupportedProviders();
|
|
78
|
+
return new DextoRuntimeError(
|
|
79
|
+
"llm_model_unknown" /* MODEL_UNKNOWN */,
|
|
80
|
+
"llm" /* LLM */,
|
|
81
|
+
"user" /* USER */,
|
|
82
|
+
`Unknown model '${model}' - could not infer provider. Available providers: ${availableProviders.join(", ")}`,
|
|
83
|
+
{ model, availableProviders },
|
|
84
|
+
"Specify the provider explicitly or use a recognized model name"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
static unsupportedRouter(router, provider) {
|
|
88
|
+
const supportedRouters = getSupportedRoutersForProvider(provider).map((r) => r);
|
|
89
|
+
return new DextoRuntimeError(
|
|
90
|
+
"llm_router_unsupported" /* ROUTER_UNSUPPORTED */,
|
|
91
|
+
"llm" /* LLM */,
|
|
92
|
+
"user" /* USER */,
|
|
93
|
+
`Router '${router}' not supported for provider '${provider}'. Supported: ${supportedRouters.join(", ")}`,
|
|
94
|
+
{ router, provider, supportedRouters }
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
// Runtime service errors
|
|
98
|
+
static rateLimitExceeded(provider, retryAfter) {
|
|
99
|
+
return new DextoRuntimeError(
|
|
100
|
+
"llm_rate_limit_exceeded" /* RATE_LIMIT_EXCEEDED */,
|
|
101
|
+
"llm" /* LLM */,
|
|
102
|
+
"rate_limit" /* RATE_LIMIT */,
|
|
103
|
+
`Rate limit exceeded for ${provider}`,
|
|
104
|
+
{
|
|
105
|
+
details: { provider, retryAfter },
|
|
106
|
+
recovery: retryAfter ? `Wait ${retryAfter} seconds before retrying` : "Wait before retrying or upgrade your plan"
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
// Runtime operation errors
|
|
111
|
+
static generationFailed(error, provider, model) {
|
|
112
|
+
return new DextoRuntimeError(
|
|
113
|
+
"llm_generation_failed" /* GENERATION_FAILED */,
|
|
114
|
+
"llm" /* LLM */,
|
|
115
|
+
"third_party" /* THIRD_PARTY */,
|
|
116
|
+
`Generation failed: ${error}`,
|
|
117
|
+
{ details: { error, provider, model } }
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
// Switch operation errors (runtime checks not covered by Zod)
|
|
121
|
+
static switchInputMissing() {
|
|
122
|
+
return new DextoRuntimeError(
|
|
123
|
+
"llm_switch_input_missing" /* SWITCH_INPUT_MISSING */,
|
|
124
|
+
"llm" /* LLM */,
|
|
125
|
+
"user" /* USER */,
|
|
126
|
+
"At least model or provider must be specified for LLM switch",
|
|
127
|
+
{},
|
|
128
|
+
"Provide either a model name, provider, or both"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/llm/types.ts
|
|
134
|
+
init_esm_shims();
|
|
135
|
+
var LLM_PROVIDERS = [
|
|
136
|
+
"openai",
|
|
137
|
+
"openai-compatible",
|
|
138
|
+
"anthropic",
|
|
139
|
+
"google",
|
|
140
|
+
"groq",
|
|
141
|
+
"xai",
|
|
142
|
+
"cohere"
|
|
143
|
+
];
|
|
144
|
+
var LLM_ROUTERS = ["vercel", "in-built"];
|
|
145
|
+
var SUPPORTED_FILE_TYPES = ["pdf", "image", "audio"];
|
|
146
|
+
|
|
147
|
+
// src/llm/registry.ts
|
|
148
|
+
var MIME_TYPE_TO_FILE_TYPE = {
|
|
149
|
+
"application/pdf": "pdf",
|
|
150
|
+
"audio/mp3": "audio",
|
|
151
|
+
"audio/mpeg": "audio",
|
|
152
|
+
"audio/wav": "audio",
|
|
153
|
+
"audio/x-wav": "audio",
|
|
154
|
+
"audio/wave": "audio",
|
|
155
|
+
"audio/webm": "audio",
|
|
156
|
+
"audio/ogg": "audio",
|
|
157
|
+
"audio/m4a": "audio",
|
|
158
|
+
"audio/aac": "audio",
|
|
159
|
+
// Common image MIME types
|
|
160
|
+
"image/jpeg": "image",
|
|
161
|
+
"image/jpg": "image",
|
|
162
|
+
"image/png": "image",
|
|
163
|
+
"image/webp": "image",
|
|
164
|
+
"image/gif": "image"
|
|
165
|
+
};
|
|
166
|
+
function getAllowedMimeTypes() {
|
|
167
|
+
return Object.keys(MIME_TYPE_TO_FILE_TYPE);
|
|
168
|
+
}
|
|
169
|
+
var DEFAULT_MAX_INPUT_TOKENS = 128e3;
|
|
170
|
+
var LLM_REGISTRY = {
|
|
171
|
+
openai: {
|
|
172
|
+
models: [
|
|
173
|
+
{
|
|
174
|
+
name: "gpt-5",
|
|
175
|
+
displayName: "GPT-5",
|
|
176
|
+
maxInputTokens: 4e5,
|
|
177
|
+
supportedFileTypes: ["pdf", "image"],
|
|
178
|
+
pricing: {
|
|
179
|
+
inputPerM: 1.25,
|
|
180
|
+
outputPerM: 10,
|
|
181
|
+
cacheReadPerM: 0.125,
|
|
182
|
+
currency: "USD",
|
|
183
|
+
unit: "per_million_tokens"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "gpt-5-mini",
|
|
188
|
+
displayName: "GPT-5 Mini",
|
|
189
|
+
maxInputTokens: 4e5,
|
|
190
|
+
supportedFileTypes: ["pdf", "image"],
|
|
191
|
+
pricing: {
|
|
192
|
+
inputPerM: 0.25,
|
|
193
|
+
outputPerM: 2,
|
|
194
|
+
cacheReadPerM: 0.025,
|
|
195
|
+
currency: "USD",
|
|
196
|
+
unit: "per_million_tokens"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "gpt-5-nano",
|
|
201
|
+
displayName: "GPT-5 Nano",
|
|
202
|
+
maxInputTokens: 4e5,
|
|
203
|
+
supportedFileTypes: ["pdf", "image"],
|
|
204
|
+
pricing: {
|
|
205
|
+
inputPerM: 0.05,
|
|
206
|
+
outputPerM: 0.4,
|
|
207
|
+
cacheReadPerM: 5e-3,
|
|
208
|
+
currency: "USD",
|
|
209
|
+
unit: "per_million_tokens"
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "gpt-4.1",
|
|
214
|
+
displayName: "GPT-4.1",
|
|
215
|
+
maxInputTokens: 1048576,
|
|
216
|
+
supportedFileTypes: ["pdf", "image"],
|
|
217
|
+
pricing: {
|
|
218
|
+
inputPerM: 2,
|
|
219
|
+
outputPerM: 8,
|
|
220
|
+
cacheReadPerM: 0.5,
|
|
221
|
+
currency: "USD",
|
|
222
|
+
unit: "per_million_tokens"
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "gpt-4.1-mini",
|
|
227
|
+
displayName: "GPT-4.1 Mini",
|
|
228
|
+
maxInputTokens: 1048576,
|
|
229
|
+
default: true,
|
|
230
|
+
supportedFileTypes: ["pdf", "image"],
|
|
231
|
+
pricing: {
|
|
232
|
+
inputPerM: 0.4,
|
|
233
|
+
outputPerM: 1.6,
|
|
234
|
+
cacheReadPerM: 0.1,
|
|
235
|
+
currency: "USD",
|
|
236
|
+
unit: "per_million_tokens"
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "gpt-4.1-nano",
|
|
241
|
+
displayName: "GPT-4.1 Nano",
|
|
242
|
+
maxInputTokens: 1048576,
|
|
243
|
+
supportedFileTypes: ["pdf", "image"],
|
|
244
|
+
pricing: {
|
|
245
|
+
inputPerM: 0.1,
|
|
246
|
+
outputPerM: 0.4,
|
|
247
|
+
cacheReadPerM: 0.025,
|
|
248
|
+
currency: "USD",
|
|
249
|
+
unit: "per_million_tokens"
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "gpt-4o",
|
|
254
|
+
displayName: "GPT-4o",
|
|
255
|
+
maxInputTokens: 128e3,
|
|
256
|
+
supportedFileTypes: ["pdf", "image"],
|
|
257
|
+
pricing: {
|
|
258
|
+
inputPerM: 2.5,
|
|
259
|
+
outputPerM: 10,
|
|
260
|
+
cacheReadPerM: 1.25,
|
|
261
|
+
currency: "USD",
|
|
262
|
+
unit: "per_million_tokens"
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "gpt-4o-mini",
|
|
267
|
+
displayName: "GPT-4o Mini",
|
|
268
|
+
maxInputTokens: 128e3,
|
|
269
|
+
supportedFileTypes: ["pdf", "image"],
|
|
270
|
+
pricing: {
|
|
271
|
+
inputPerM: 0.15,
|
|
272
|
+
outputPerM: 0.6,
|
|
273
|
+
cacheReadPerM: 0.075,
|
|
274
|
+
currency: "USD",
|
|
275
|
+
unit: "per_million_tokens"
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "gpt-4o-audio-preview",
|
|
280
|
+
displayName: "GPT-4o Audio Preview",
|
|
281
|
+
maxInputTokens: 128e3,
|
|
282
|
+
supportedFileTypes: ["audio"],
|
|
283
|
+
pricing: {
|
|
284
|
+
inputPerM: 2.5,
|
|
285
|
+
outputPerM: 10,
|
|
286
|
+
currency: "USD",
|
|
287
|
+
unit: "per_million_tokens"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "o4-mini",
|
|
292
|
+
displayName: "O4 Mini",
|
|
293
|
+
maxInputTokens: 2e5,
|
|
294
|
+
supportedFileTypes: ["pdf", "image"],
|
|
295
|
+
pricing: {
|
|
296
|
+
inputPerM: 1.1,
|
|
297
|
+
outputPerM: 4.4,
|
|
298
|
+
cacheReadPerM: 0.275,
|
|
299
|
+
currency: "USD",
|
|
300
|
+
unit: "per_million_tokens"
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "o3",
|
|
305
|
+
displayName: "O3",
|
|
306
|
+
maxInputTokens: 2e5,
|
|
307
|
+
supportedFileTypes: ["pdf", "image"],
|
|
308
|
+
pricing: {
|
|
309
|
+
inputPerM: 2,
|
|
310
|
+
outputPerM: 8,
|
|
311
|
+
cacheReadPerM: 0.5,
|
|
312
|
+
currency: "USD",
|
|
313
|
+
unit: "per_million_tokens"
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "o3-mini",
|
|
318
|
+
displayName: "O3 Mini",
|
|
319
|
+
maxInputTokens: 2e5,
|
|
320
|
+
supportedFileTypes: [],
|
|
321
|
+
pricing: {
|
|
322
|
+
inputPerM: 1.1,
|
|
323
|
+
outputPerM: 4.4,
|
|
324
|
+
cacheReadPerM: 0.55,
|
|
325
|
+
currency: "USD",
|
|
326
|
+
unit: "per_million_tokens"
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "o1",
|
|
331
|
+
displayName: "O1",
|
|
332
|
+
maxInputTokens: 2e5,
|
|
333
|
+
supportedFileTypes: ["pdf", "image"],
|
|
334
|
+
pricing: {
|
|
335
|
+
inputPerM: 15,
|
|
336
|
+
outputPerM: 60,
|
|
337
|
+
cacheReadPerM: 7.5,
|
|
338
|
+
currency: "USD",
|
|
339
|
+
unit: "per_million_tokens"
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
],
|
|
343
|
+
supportedRouters: ["vercel", "in-built"],
|
|
344
|
+
baseURLSupport: "none",
|
|
345
|
+
supportedFileTypes: []
|
|
346
|
+
// No defaults - models must explicitly specify support
|
|
347
|
+
},
|
|
348
|
+
"openai-compatible": {
|
|
349
|
+
models: [],
|
|
350
|
+
// Empty - accepts any model name for custom endpoints
|
|
351
|
+
supportedRouters: ["vercel", "in-built"],
|
|
352
|
+
baseURLSupport: "required",
|
|
353
|
+
supportedFileTypes: ["pdf", "image", "audio"]
|
|
354
|
+
// Allow all types for custom endpoints - user assumes responsibility for model capabilities
|
|
355
|
+
},
|
|
356
|
+
anthropic: {
|
|
357
|
+
models: [
|
|
358
|
+
{
|
|
359
|
+
name: "claude-opus-4-1-20250805",
|
|
360
|
+
displayName: "Claude 4.1 Opus",
|
|
361
|
+
maxInputTokens: 2e5,
|
|
362
|
+
supportedFileTypes: ["pdf", "image"],
|
|
363
|
+
pricing: {
|
|
364
|
+
inputPerM: 15,
|
|
365
|
+
outputPerM: 75,
|
|
366
|
+
cacheWritePerM: 18.75,
|
|
367
|
+
cacheReadPerM: 1.5,
|
|
368
|
+
currency: "USD",
|
|
369
|
+
unit: "per_million_tokens"
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "claude-4-opus-20250514",
|
|
374
|
+
displayName: "Claude 4 Opus",
|
|
375
|
+
maxInputTokens: 2e5,
|
|
376
|
+
supportedFileTypes: ["pdf", "image"],
|
|
377
|
+
pricing: {
|
|
378
|
+
inputPerM: 15,
|
|
379
|
+
outputPerM: 75,
|
|
380
|
+
cacheWritePerM: 18.75,
|
|
381
|
+
cacheReadPerM: 1.5,
|
|
382
|
+
currency: "USD",
|
|
383
|
+
unit: "per_million_tokens"
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "claude-4-sonnet-20250514",
|
|
388
|
+
displayName: "Claude 4 Sonnet",
|
|
389
|
+
maxInputTokens: 2e5,
|
|
390
|
+
default: true,
|
|
391
|
+
supportedFileTypes: ["pdf", "image"],
|
|
392
|
+
pricing: {
|
|
393
|
+
inputPerM: 3,
|
|
394
|
+
outputPerM: 15,
|
|
395
|
+
cacheWritePerM: 3.75,
|
|
396
|
+
cacheReadPerM: 0.3,
|
|
397
|
+
currency: "USD",
|
|
398
|
+
unit: "per_million_tokens"
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: "claude-3-7-sonnet-20250219",
|
|
403
|
+
displayName: "Claude 3.7 Sonnet",
|
|
404
|
+
maxInputTokens: 2e5,
|
|
405
|
+
supportedFileTypes: ["pdf", "image"],
|
|
406
|
+
pricing: {
|
|
407
|
+
inputPerM: 3,
|
|
408
|
+
outputPerM: 15,
|
|
409
|
+
cacheWritePerM: 3.75,
|
|
410
|
+
cacheReadPerM: 0.3,
|
|
411
|
+
currency: "USD",
|
|
412
|
+
unit: "per_million_tokens"
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: "claude-3-5-sonnet-20240620",
|
|
417
|
+
displayName: "Claude 3.5 Sonnet",
|
|
418
|
+
maxInputTokens: 2e5,
|
|
419
|
+
supportedFileTypes: ["pdf", "image"],
|
|
420
|
+
pricing: {
|
|
421
|
+
inputPerM: 3,
|
|
422
|
+
outputPerM: 15,
|
|
423
|
+
cacheWritePerM: 3.75,
|
|
424
|
+
cacheReadPerM: 0.3,
|
|
425
|
+
currency: "USD",
|
|
426
|
+
unit: "per_million_tokens"
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: "claude-3-5-haiku-20241022",
|
|
431
|
+
displayName: "Claude 3.5 Haiku",
|
|
432
|
+
maxInputTokens: 2e5,
|
|
433
|
+
supportedFileTypes: ["pdf", "image"],
|
|
434
|
+
pricing: {
|
|
435
|
+
inputPerM: 0.8,
|
|
436
|
+
outputPerM: 4,
|
|
437
|
+
cacheWritePerM: 1,
|
|
438
|
+
cacheReadPerM: 0.08,
|
|
439
|
+
currency: "USD",
|
|
440
|
+
unit: "per_million_tokens"
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
],
|
|
444
|
+
supportedRouters: ["vercel", "in-built"],
|
|
445
|
+
baseURLSupport: "none",
|
|
446
|
+
supportedFileTypes: []
|
|
447
|
+
// No defaults - models must explicitly specify support
|
|
448
|
+
},
|
|
449
|
+
google: {
|
|
450
|
+
models: [
|
|
451
|
+
{
|
|
452
|
+
name: "gemini-2.5-pro",
|
|
453
|
+
displayName: "Gemini 2.5 Pro",
|
|
454
|
+
maxInputTokens: 1048576,
|
|
455
|
+
default: true,
|
|
456
|
+
supportedFileTypes: ["pdf", "image", "audio"],
|
|
457
|
+
pricing: {
|
|
458
|
+
inputPerM: 1.25,
|
|
459
|
+
outputPerM: 10,
|
|
460
|
+
cacheReadPerM: 0.31,
|
|
461
|
+
currency: "USD",
|
|
462
|
+
unit: "per_million_tokens"
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: "gemini-2.5-flash",
|
|
467
|
+
displayName: "Gemini 2.5 Flash",
|
|
468
|
+
maxInputTokens: 1048576,
|
|
469
|
+
supportedFileTypes: ["pdf", "image", "audio"],
|
|
470
|
+
pricing: {
|
|
471
|
+
inputPerM: 0.3,
|
|
472
|
+
outputPerM: 2.5,
|
|
473
|
+
currency: "USD",
|
|
474
|
+
unit: "per_million_tokens"
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
name: "gemini-2.5-flash-lite",
|
|
479
|
+
displayName: "Gemini 2.5 Flash Lite",
|
|
480
|
+
maxInputTokens: 1048576,
|
|
481
|
+
supportedFileTypes: ["pdf", "image", "audio"],
|
|
482
|
+
pricing: {
|
|
483
|
+
inputPerM: 0.1,
|
|
484
|
+
outputPerM: 0.4,
|
|
485
|
+
cacheReadPerM: 0.025,
|
|
486
|
+
currency: "USD",
|
|
487
|
+
unit: "per_million_tokens"
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "gemini-2.0-flash",
|
|
492
|
+
displayName: "Gemini 2.0 Flash",
|
|
493
|
+
maxInputTokens: 1048576,
|
|
494
|
+
supportedFileTypes: ["pdf", "image", "audio"],
|
|
495
|
+
pricing: {
|
|
496
|
+
inputPerM: 0.15,
|
|
497
|
+
outputPerM: 0.6,
|
|
498
|
+
cacheReadPerM: 0.025,
|
|
499
|
+
cacheWritePerM: 1,
|
|
500
|
+
currency: "USD",
|
|
501
|
+
unit: "per_million_tokens"
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: "gemini-2.0-flash-lite",
|
|
506
|
+
displayName: "Gemini 2.0 Flash Lite",
|
|
507
|
+
maxInputTokens: 1048576,
|
|
508
|
+
supportedFileTypes: ["pdf", "image", "audio"],
|
|
509
|
+
pricing: {
|
|
510
|
+
inputPerM: 0.075,
|
|
511
|
+
outputPerM: 0.3,
|
|
512
|
+
currency: "USD",
|
|
513
|
+
unit: "per_million_tokens"
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
],
|
|
517
|
+
supportedRouters: ["vercel"],
|
|
518
|
+
baseURLSupport: "none",
|
|
519
|
+
supportedFileTypes: []
|
|
520
|
+
// No defaults - models must explicitly specify support
|
|
521
|
+
},
|
|
522
|
+
// https://console.groq.com/docs/models
|
|
523
|
+
groq: {
|
|
524
|
+
models: [
|
|
525
|
+
{
|
|
526
|
+
name: "gemma-2-9b-it",
|
|
527
|
+
displayName: "Gemma 2 9B Instruct",
|
|
528
|
+
maxInputTokens: 8192,
|
|
529
|
+
supportedFileTypes: [],
|
|
530
|
+
pricing: {
|
|
531
|
+
inputPerM: 0.2,
|
|
532
|
+
outputPerM: 0.2,
|
|
533
|
+
currency: "USD",
|
|
534
|
+
unit: "per_million_tokens"
|
|
535
|
+
}
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
name: "openai/gpt-oss-20b",
|
|
539
|
+
displayName: "GPT OSS 20B 128k",
|
|
540
|
+
maxInputTokens: 128e3,
|
|
541
|
+
supportedFileTypes: [],
|
|
542
|
+
pricing: {
|
|
543
|
+
inputPerM: 0.1,
|
|
544
|
+
outputPerM: 0.5,
|
|
545
|
+
currency: "USD",
|
|
546
|
+
unit: "per_million_tokens"
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
name: "openai/gpt-oss-120b",
|
|
551
|
+
displayName: "GPT OSS 120B 128k",
|
|
552
|
+
maxInputTokens: 128e3,
|
|
553
|
+
supportedFileTypes: [],
|
|
554
|
+
pricing: {
|
|
555
|
+
inputPerM: 0.15,
|
|
556
|
+
outputPerM: 0.75,
|
|
557
|
+
currency: "USD",
|
|
558
|
+
unit: "per_million_tokens"
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
name: "moonshotai/kimi-k2-instruct",
|
|
563
|
+
displayName: "Kimi K2 1T 128k",
|
|
564
|
+
maxInputTokens: 128e3,
|
|
565
|
+
supportedFileTypes: [],
|
|
566
|
+
pricing: {
|
|
567
|
+
inputPerM: 1,
|
|
568
|
+
outputPerM: 3,
|
|
569
|
+
currency: "USD",
|
|
570
|
+
unit: "per_million_tokens"
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "meta-llama/llama-4-scout-17b-16e-instruct",
|
|
575
|
+
displayName: "Llama 4 Scout (17Bx16E) 128k",
|
|
576
|
+
maxInputTokens: 128e3,
|
|
577
|
+
supportedFileTypes: [],
|
|
578
|
+
pricing: {
|
|
579
|
+
inputPerM: 0.11,
|
|
580
|
+
outputPerM: 0.34,
|
|
581
|
+
currency: "USD",
|
|
582
|
+
unit: "per_million_tokens"
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
name: "meta-llama/llama-4-maverick-17b-128e-instruct",
|
|
587
|
+
displayName: "Llama 4 Maverick (17Bx128E) 128k",
|
|
588
|
+
maxInputTokens: 128e3,
|
|
589
|
+
supportedFileTypes: [],
|
|
590
|
+
pricing: {
|
|
591
|
+
inputPerM: 0.2,
|
|
592
|
+
outputPerM: 0.6,
|
|
593
|
+
currency: "USD",
|
|
594
|
+
unit: "per_million_tokens"
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
name: "deepseek-r1-distill-llama-70b",
|
|
599
|
+
displayName: "DeepSeek R1 Distill Llama 70B 128k",
|
|
600
|
+
maxInputTokens: 128e3,
|
|
601
|
+
supportedFileTypes: [],
|
|
602
|
+
pricing: {
|
|
603
|
+
inputPerM: 0.75,
|
|
604
|
+
outputPerM: 0.9,
|
|
605
|
+
currency: "USD",
|
|
606
|
+
unit: "per_million_tokens"
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
name: "qwen/qwen3-32b",
|
|
611
|
+
displayName: "Qwen3 32B 131k",
|
|
612
|
+
maxInputTokens: 131e3,
|
|
613
|
+
supportedFileTypes: [],
|
|
614
|
+
pricing: {
|
|
615
|
+
inputPerM: 0.29,
|
|
616
|
+
outputPerM: 0.59,
|
|
617
|
+
currency: "USD",
|
|
618
|
+
unit: "per_million_tokens"
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
name: "llama-3.3-70b-versatile",
|
|
623
|
+
displayName: "Llama 3.3 70B Versatile",
|
|
624
|
+
maxInputTokens: 128e3,
|
|
625
|
+
default: true,
|
|
626
|
+
supportedFileTypes: [],
|
|
627
|
+
pricing: {
|
|
628
|
+
inputPerM: 0.59,
|
|
629
|
+
outputPerM: 0.79,
|
|
630
|
+
currency: "USD",
|
|
631
|
+
unit: "per_million_tokens"
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
],
|
|
635
|
+
supportedRouters: ["vercel"],
|
|
636
|
+
baseURLSupport: "none",
|
|
637
|
+
supportedFileTypes: []
|
|
638
|
+
// Groq currently doesn't support file uploads
|
|
639
|
+
},
|
|
640
|
+
// https://docs.x.ai/docs/models
|
|
641
|
+
// Note: XAI API only supports image uploads (JPG/PNG up to 20MB), not PDFs
|
|
642
|
+
xai: {
|
|
643
|
+
models: [
|
|
644
|
+
{
|
|
645
|
+
name: "grok-4",
|
|
646
|
+
displayName: "Grok 4",
|
|
647
|
+
maxInputTokens: 256e3,
|
|
648
|
+
default: true,
|
|
649
|
+
supportedFileTypes: ["image"],
|
|
650
|
+
pricing: {
|
|
651
|
+
inputPerM: 3,
|
|
652
|
+
outputPerM: 15,
|
|
653
|
+
cacheReadPerM: 0.75,
|
|
654
|
+
currency: "USD",
|
|
655
|
+
unit: "per_million_tokens"
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
name: "grok-3",
|
|
660
|
+
displayName: "Grok 3",
|
|
661
|
+
maxInputTokens: 131072,
|
|
662
|
+
supportedFileTypes: ["image"],
|
|
663
|
+
pricing: {
|
|
664
|
+
inputPerM: 3,
|
|
665
|
+
outputPerM: 15,
|
|
666
|
+
cacheReadPerM: 0.75,
|
|
667
|
+
currency: "USD",
|
|
668
|
+
unit: "per_million_tokens"
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: "grok-3-mini",
|
|
673
|
+
displayName: "Grok 3 Mini",
|
|
674
|
+
maxInputTokens: 131072,
|
|
675
|
+
supportedFileTypes: ["image"],
|
|
676
|
+
pricing: {
|
|
677
|
+
inputPerM: 0.3,
|
|
678
|
+
outputPerM: 0.5,
|
|
679
|
+
cacheReadPerM: 0.075,
|
|
680
|
+
currency: "USD",
|
|
681
|
+
unit: "per_million_tokens"
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
name: "grok-code-fast-1",
|
|
686
|
+
displayName: "Grok Code Fast",
|
|
687
|
+
maxInputTokens: 131072,
|
|
688
|
+
supportedFileTypes: [],
|
|
689
|
+
pricing: {
|
|
690
|
+
inputPerM: 0.2,
|
|
691
|
+
outputPerM: 1.5,
|
|
692
|
+
cacheReadPerM: 0.02,
|
|
693
|
+
currency: "USD",
|
|
694
|
+
unit: "per_million_tokens"
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
],
|
|
698
|
+
supportedRouters: ["vercel"],
|
|
699
|
+
baseURLSupport: "none",
|
|
700
|
+
supportedFileTypes: []
|
|
701
|
+
// XAI currently doesn't support file uploads
|
|
702
|
+
},
|
|
703
|
+
// https://docs.cohere.com/reference/models
|
|
704
|
+
cohere: {
|
|
705
|
+
models: [
|
|
706
|
+
{
|
|
707
|
+
name: "command-a-03-2025",
|
|
708
|
+
displayName: "Command A (03-2025)",
|
|
709
|
+
maxInputTokens: 256e3,
|
|
710
|
+
default: true,
|
|
711
|
+
supportedFileTypes: [],
|
|
712
|
+
pricing: {
|
|
713
|
+
inputPerM: 2.5,
|
|
714
|
+
outputPerM: 10,
|
|
715
|
+
currency: "USD",
|
|
716
|
+
unit: "per_million_tokens"
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
name: "command-r-plus",
|
|
721
|
+
displayName: "Command R+",
|
|
722
|
+
maxInputTokens: 128e3,
|
|
723
|
+
supportedFileTypes: [],
|
|
724
|
+
pricing: {
|
|
725
|
+
inputPerM: 2.5,
|
|
726
|
+
outputPerM: 10,
|
|
727
|
+
currency: "USD",
|
|
728
|
+
unit: "per_million_tokens"
|
|
729
|
+
}
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
name: "command-r",
|
|
733
|
+
displayName: "Command R",
|
|
734
|
+
maxInputTokens: 128e3,
|
|
735
|
+
supportedFileTypes: [],
|
|
736
|
+
pricing: {
|
|
737
|
+
inputPerM: 0.15,
|
|
738
|
+
outputPerM: 0.6,
|
|
739
|
+
currency: "USD",
|
|
740
|
+
unit: "per_million_tokens"
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
name: "command-r7b",
|
|
745
|
+
displayName: "Command R7B",
|
|
746
|
+
maxInputTokens: 128e3,
|
|
747
|
+
supportedFileTypes: [],
|
|
748
|
+
pricing: {
|
|
749
|
+
inputPerM: 0.0375,
|
|
750
|
+
outputPerM: 0.15,
|
|
751
|
+
currency: "USD",
|
|
752
|
+
unit: "per_million_tokens"
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
],
|
|
756
|
+
supportedRouters: ["vercel"],
|
|
757
|
+
baseURLSupport: "none",
|
|
758
|
+
supportedFileTypes: []
|
|
759
|
+
// Cohere currently doesn't support file uploads
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
function getDefaultModelForProvider(provider) {
|
|
763
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
764
|
+
return providerInfo.models.find((m) => m.default)?.name || null;
|
|
765
|
+
}
|
|
766
|
+
function getSupportedProviders() {
|
|
767
|
+
return [...LLM_PROVIDERS];
|
|
768
|
+
}
|
|
769
|
+
function getSupportedModels(provider) {
|
|
770
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
771
|
+
return providerInfo.models.map((m) => m.name);
|
|
772
|
+
}
|
|
773
|
+
function getMaxInputTokensForModel(provider, model) {
|
|
774
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
775
|
+
const modelInfo = providerInfo.models.find((m) => m.name.toLowerCase() === model.toLowerCase());
|
|
776
|
+
if (!modelInfo) {
|
|
777
|
+
const supportedModels = getSupportedModels(provider).join(", ");
|
|
778
|
+
logger.error(
|
|
779
|
+
`Model '${model}' not found for provider '${provider}' in LLM registry. Supported models: ${supportedModels}`
|
|
780
|
+
);
|
|
781
|
+
throw LLMError.unknownModel(provider, model);
|
|
782
|
+
}
|
|
783
|
+
logger.debug(`Found max tokens for ${provider}/${model}: ${modelInfo.maxInputTokens}`);
|
|
784
|
+
return modelInfo.maxInputTokens;
|
|
785
|
+
}
|
|
786
|
+
function isValidProviderModel(provider, model) {
|
|
787
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
788
|
+
return providerInfo.models.some((m) => m.name.toLowerCase() === model.toLowerCase());
|
|
789
|
+
}
|
|
790
|
+
function getProviderFromModel(model) {
|
|
791
|
+
const lowerModel = model.toLowerCase();
|
|
792
|
+
for (const provider of LLM_PROVIDERS) {
|
|
793
|
+
const info = LLM_REGISTRY[provider];
|
|
794
|
+
if (info.models.some((m) => m.name.toLowerCase() === lowerModel)) {
|
|
795
|
+
return provider;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
throw LLMError.modelProviderUnknown(model);
|
|
799
|
+
}
|
|
800
|
+
function getAllSupportedModels() {
|
|
801
|
+
return Object.values(LLM_REGISTRY).flatMap((info) => info.models.map((m) => m.name));
|
|
802
|
+
}
|
|
803
|
+
function getSupportedRoutersForProvider(provider) {
|
|
804
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
805
|
+
return providerInfo.supportedRouters;
|
|
806
|
+
}
|
|
807
|
+
function supportsBaseURL(provider) {
|
|
808
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
809
|
+
return providerInfo.baseURLSupport !== "none";
|
|
810
|
+
}
|
|
811
|
+
function requiresBaseURL(provider) {
|
|
812
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
813
|
+
return providerInfo.baseURLSupport === "required";
|
|
814
|
+
}
|
|
815
|
+
function acceptsAnyModel(provider) {
|
|
816
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
817
|
+
return providerInfo.models.length === 0;
|
|
818
|
+
}
|
|
819
|
+
function getSupportedFileTypesForModel(provider, model) {
|
|
820
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
821
|
+
if (acceptsAnyModel(provider)) {
|
|
822
|
+
return providerInfo.supportedFileTypes;
|
|
823
|
+
}
|
|
824
|
+
const modelInfo = providerInfo.models.find((m) => m.name.toLowerCase() === model.toLowerCase());
|
|
825
|
+
if (!modelInfo) {
|
|
826
|
+
throw LLMError.unknownModel(provider, model);
|
|
827
|
+
}
|
|
828
|
+
return modelInfo.supportedFileTypes;
|
|
829
|
+
}
|
|
830
|
+
function modelSupportsFileType(provider, model, fileType) {
|
|
831
|
+
const supportedTypes = getSupportedFileTypesForModel(provider, model);
|
|
832
|
+
return supportedTypes.includes(fileType);
|
|
833
|
+
}
|
|
834
|
+
function validateModelFileSupport(provider, model, mimeType) {
|
|
835
|
+
const baseMimeType = mimeType.toLowerCase().split(";")[0]?.trim() || mimeType.toLowerCase();
|
|
836
|
+
const fileType = MIME_TYPE_TO_FILE_TYPE[baseMimeType];
|
|
837
|
+
if (!fileType) {
|
|
838
|
+
return {
|
|
839
|
+
isSupported: false,
|
|
840
|
+
error: `Unsupported file type: ${mimeType}`
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
try {
|
|
844
|
+
if (!modelSupportsFileType(provider, model, fileType)) {
|
|
845
|
+
return {
|
|
846
|
+
isSupported: false,
|
|
847
|
+
fileType,
|
|
848
|
+
error: `Model '${model}' (${provider}) does not support ${fileType} files`
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
isSupported: true,
|
|
853
|
+
fileType
|
|
854
|
+
};
|
|
855
|
+
} catch (error) {
|
|
856
|
+
return {
|
|
857
|
+
isSupported: false,
|
|
858
|
+
fileType,
|
|
859
|
+
error: error instanceof Error ? error.message : "Unknown error validating model file support"
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function isRouterSupportedForModel(provider, model, router) {
|
|
864
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
865
|
+
const modelInfo = providerInfo.models.find((m) => m.name.toLowerCase() === model.toLowerCase());
|
|
866
|
+
if (!modelInfo) {
|
|
867
|
+
return isRouterSupportedForProvider(provider, router);
|
|
868
|
+
}
|
|
869
|
+
if (modelInfo.supportedRouters) {
|
|
870
|
+
return modelInfo.supportedRouters.includes(router);
|
|
871
|
+
}
|
|
872
|
+
return isRouterSupportedForProvider(provider, router);
|
|
873
|
+
}
|
|
874
|
+
function getSupportedRoutersForModel(provider, model) {
|
|
875
|
+
const providerInfo = LLM_REGISTRY[provider];
|
|
876
|
+
const modelInfo = providerInfo.models.find((m) => m.name.toLowerCase() === model.toLowerCase());
|
|
877
|
+
if (!modelInfo) {
|
|
878
|
+
return getSupportedRoutersForProvider(provider);
|
|
879
|
+
}
|
|
880
|
+
if (modelInfo.supportedRouters) {
|
|
881
|
+
return modelInfo.supportedRouters;
|
|
882
|
+
}
|
|
883
|
+
return getSupportedRoutersForProvider(provider);
|
|
884
|
+
}
|
|
885
|
+
function isRouterSupportedForProvider(provider, router) {
|
|
886
|
+
const supportedRouters = getSupportedRoutersForProvider(provider);
|
|
887
|
+
return supportedRouters.includes(router);
|
|
888
|
+
}
|
|
889
|
+
function getEffectiveMaxInputTokens(config) {
|
|
890
|
+
const configuredMaxInputTokens = config.maxInputTokens;
|
|
891
|
+
if (configuredMaxInputTokens != null) {
|
|
892
|
+
if (config.baseURL) {
|
|
893
|
+
logger.debug(
|
|
894
|
+
`Using maxInputTokens from configuration (with baseURL): ${configuredMaxInputTokens}`
|
|
895
|
+
);
|
|
896
|
+
return configuredMaxInputTokens;
|
|
897
|
+
}
|
|
898
|
+
try {
|
|
899
|
+
const registryMaxInputTokens = getMaxInputTokensForModel(config.provider, config.model);
|
|
900
|
+
if (configuredMaxInputTokens > registryMaxInputTokens) {
|
|
901
|
+
logger.warn(
|
|
902
|
+
`Provided maxInputTokens (${configuredMaxInputTokens}) for ${config.provider}/${config.model} exceeds the known limit (${registryMaxInputTokens}) for model ${config.model}. Capping to registry limit.`
|
|
903
|
+
);
|
|
904
|
+
return registryMaxInputTokens;
|
|
905
|
+
} else {
|
|
906
|
+
logger.debug(
|
|
907
|
+
`Using valid maxInputTokens override from configuration: ${configuredMaxInputTokens} (Registry limit: ${registryMaxInputTokens})`
|
|
908
|
+
);
|
|
909
|
+
return configuredMaxInputTokens;
|
|
910
|
+
}
|
|
911
|
+
} catch (error) {
|
|
912
|
+
if (error instanceof DextoRuntimeError && error.code === "llm_model_unknown" /* MODEL_UNKNOWN */) {
|
|
913
|
+
logger.warn(
|
|
914
|
+
`Registry lookup failed during maxInputTokens override check for ${config.provider}/${config.model}: ${error.message}. Proceeding with the provided maxInputTokens value (${configuredMaxInputTokens}), but it might be invalid.`
|
|
915
|
+
);
|
|
916
|
+
return configuredMaxInputTokens;
|
|
917
|
+
} else {
|
|
918
|
+
logger.error(
|
|
919
|
+
`Unexpected error during registry lookup for maxInputTokens override check: ${error}`
|
|
920
|
+
);
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
if (config.baseURL) {
|
|
926
|
+
logger.warn(
|
|
927
|
+
`baseURL is set but maxInputTokens is missing. Defaulting to ${DEFAULT_MAX_INPUT_TOKENS}. Provide 'maxInputTokens' in configuration to avoid default fallback.`
|
|
928
|
+
);
|
|
929
|
+
return DEFAULT_MAX_INPUT_TOKENS;
|
|
930
|
+
}
|
|
931
|
+
if (acceptsAnyModel(config.provider)) {
|
|
932
|
+
logger.debug(
|
|
933
|
+
`Provider ${config.provider} accepts any model, defaulting to ${DEFAULT_MAX_INPUT_TOKENS} tokens`
|
|
934
|
+
);
|
|
935
|
+
return DEFAULT_MAX_INPUT_TOKENS;
|
|
936
|
+
}
|
|
937
|
+
try {
|
|
938
|
+
const registryMaxInputTokens = getMaxInputTokensForModel(config.provider, config.model);
|
|
939
|
+
logger.debug(
|
|
940
|
+
`Using maxInputTokens from registry for ${config.provider}/${config.model}: ${registryMaxInputTokens}`
|
|
941
|
+
);
|
|
942
|
+
return registryMaxInputTokens;
|
|
943
|
+
} catch (error) {
|
|
944
|
+
if (error instanceof DextoRuntimeError && error.code === "llm_model_unknown" /* MODEL_UNKNOWN */) {
|
|
945
|
+
logger.error(
|
|
946
|
+
`Registry lookup failed for ${config.provider}/${config.model}: ${error.message}. Effective maxInputTokens cannot be determined.`
|
|
947
|
+
);
|
|
948
|
+
throw LLMError.unknownModel(config.provider, config.model);
|
|
949
|
+
} else {
|
|
950
|
+
logger.error(`Unexpected error during registry lookup for maxInputTokens: ${error}`);
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// src/utils/result.ts
|
|
957
|
+
init_esm_shims();
|
|
958
|
+
import { z } from "zod";
|
|
959
|
+
var NonEmptyTrimmed = z.string().transform((s) => s.trim()).refine((s) => s.length > 0, { message: "Required" });
|
|
960
|
+
function isValidUrl(s) {
|
|
961
|
+
try {
|
|
962
|
+
const u = new URL(s);
|
|
963
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
964
|
+
} catch {
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
var OptionalURL = z.string().transform((s) => s.trim()).refine((s) => s === "" || isValidUrl(s), { message: "Invalid URL" }).transform((s) => s === "" ? void 0 : s).optional();
|
|
969
|
+
var EnvExpandedString = (env) => z.string().transform((input) => {
|
|
970
|
+
if (typeof input !== "string") return "";
|
|
971
|
+
const envToUse = env ?? process.env;
|
|
972
|
+
const out = input.replace(
|
|
973
|
+
/\$([A-Z_][A-Z0-9_]*)|\${([A-Z_][A-Z0-9_]*)}/gi,
|
|
974
|
+
(_, a, b) => envToUse[a || b] ?? ""
|
|
975
|
+
);
|
|
976
|
+
return out.trim();
|
|
977
|
+
});
|
|
978
|
+
var NonEmptyEnvExpandedString = (env) => EnvExpandedString(env).refine((s) => s.length > 0, {
|
|
979
|
+
message: "Value is required"
|
|
980
|
+
});
|
|
981
|
+
var RequiredEnvURL = (env) => EnvExpandedString(env).refine(
|
|
982
|
+
(s) => {
|
|
983
|
+
try {
|
|
984
|
+
const u = new URL(s);
|
|
985
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
986
|
+
} catch {
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
},
|
|
990
|
+
{ message: "Invalid URL" }
|
|
991
|
+
);
|
|
992
|
+
var ok = (data, issues = []) => ({
|
|
993
|
+
ok: true,
|
|
994
|
+
data,
|
|
995
|
+
issues
|
|
996
|
+
// warnings live alongside errors here
|
|
997
|
+
});
|
|
998
|
+
var fail = (issues) => ({
|
|
999
|
+
ok: false,
|
|
1000
|
+
issues
|
|
1001
|
+
});
|
|
1002
|
+
function hasErrors(issues) {
|
|
1003
|
+
return issues.some((i) => i.severity !== "warning");
|
|
1004
|
+
}
|
|
1005
|
+
function splitIssues(issues) {
|
|
1006
|
+
return {
|
|
1007
|
+
errors: issues.filter((i) => i.severity !== "warning"),
|
|
1008
|
+
warnings: issues.filter((i) => i.severity === "warning")
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
function zodToIssues(err, severity = "error") {
|
|
1012
|
+
return err.errors.map((e) => {
|
|
1013
|
+
const params = e.params || {};
|
|
1014
|
+
return {
|
|
1015
|
+
code: params.code ?? "schema_validation",
|
|
1016
|
+
message: e.message,
|
|
1017
|
+
scope: params.scope ?? "agent" /* AGENT */,
|
|
1018
|
+
// Fallback for non-custom Zod errors
|
|
1019
|
+
// Treat plain Zod schema failures as USER errors by default
|
|
1020
|
+
type: params.type ?? "user" /* USER */,
|
|
1021
|
+
path: e.path,
|
|
1022
|
+
severity,
|
|
1023
|
+
context: params
|
|
1024
|
+
};
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/preferences/error-codes.ts
|
|
1029
|
+
init_esm_shims();
|
|
1030
|
+
var PreferenceErrorCode = /* @__PURE__ */ ((PreferenceErrorCode2) => {
|
|
1031
|
+
PreferenceErrorCode2["FILE_NOT_FOUND"] = "preference_file_not_found";
|
|
1032
|
+
PreferenceErrorCode2["FILE_READ_ERROR"] = "preference_file_read_error";
|
|
1033
|
+
PreferenceErrorCode2["FILE_WRITE_ERROR"] = "preference_file_write_error";
|
|
1034
|
+
PreferenceErrorCode2["VALIDATION_ERROR"] = "preference_validation_error";
|
|
1035
|
+
PreferenceErrorCode2["MODEL_INCOMPATIBLE"] = "preference_model_incompatible";
|
|
1036
|
+
return PreferenceErrorCode2;
|
|
1037
|
+
})(PreferenceErrorCode || {});
|
|
1038
|
+
|
|
1039
|
+
// src/preferences/schemas.ts
|
|
1040
|
+
var PreferenceLLMSchema = z2.object({
|
|
1041
|
+
provider: z2.enum(LLM_PROVIDERS).describe("LLM provider (openai, anthropic, google, etc.)"),
|
|
1042
|
+
model: NonEmptyTrimmed.describe("Model name for the provider"),
|
|
1043
|
+
apiKey: z2.string().regex(
|
|
1044
|
+
/^\$[A-Z_][A-Z0-9_]*$/,
|
|
1045
|
+
"Must be environment variable reference (e.g., $OPENAI_API_KEY)"
|
|
1046
|
+
).describe("Environment variable reference for API key")
|
|
1047
|
+
}).strict().superRefine((data, ctx) => {
|
|
1048
|
+
if (!isValidProviderModel(data.provider, data.model)) {
|
|
1049
|
+
const supportedModels = getSupportedModels(data.provider);
|
|
1050
|
+
ctx.addIssue({
|
|
1051
|
+
code: z2.ZodIssueCode.custom,
|
|
1052
|
+
path: ["model"],
|
|
1053
|
+
message: `Model '${data.model}' is not supported by provider '${data.provider}'. Supported models: ${supportedModels.join(", ")}`,
|
|
1054
|
+
params: {
|
|
1055
|
+
code: "preference_model_incompatible" /* MODEL_INCOMPATIBLE */,
|
|
1056
|
+
scope: "preference" /* PREFERENCE */,
|
|
1057
|
+
type: "user" /* USER */
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
var PreferenceDefaultsSchema = z2.object({
|
|
1063
|
+
defaultAgent: z2.string().min(1).describe("Default agent name for global CLI usage (required)")
|
|
1064
|
+
}).strict();
|
|
1065
|
+
var PreferenceSetupSchema = z2.object({
|
|
1066
|
+
completed: z2.boolean().default(false).describe("Whether initial setup has been completed")
|
|
1067
|
+
}).strict();
|
|
1068
|
+
var GlobalPreferencesSchema = z2.object({
|
|
1069
|
+
llm: PreferenceLLMSchema.describe("LLM configuration preferences"),
|
|
1070
|
+
defaults: PreferenceDefaultsSchema.describe("Default behavior preferences (required)"),
|
|
1071
|
+
setup: PreferenceSetupSchema.default({ completed: false }).describe(
|
|
1072
|
+
"Setup completion tracking"
|
|
1073
|
+
)
|
|
1074
|
+
}).strict();
|
|
1075
|
+
|
|
1076
|
+
// src/preferences/constants.ts
|
|
1077
|
+
init_esm_shims();
|
|
1078
|
+
var PREFERENCES_FILE = "preferences.yml";
|
|
1079
|
+
|
|
1080
|
+
// src/preferences/errors.ts
|
|
1081
|
+
init_esm_shims();
|
|
1082
|
+
var PreferenceError = class {
|
|
1083
|
+
static fileNotFound(preferencesPath) {
|
|
1084
|
+
return new DextoRuntimeError(
|
|
1085
|
+
"preference_file_not_found" /* FILE_NOT_FOUND */,
|
|
1086
|
+
"preference" /* PREFERENCE */,
|
|
1087
|
+
"user" /* USER */,
|
|
1088
|
+
`Preferences file not found: ${preferencesPath}`,
|
|
1089
|
+
{ preferencesPath },
|
|
1090
|
+
"Run `dexto setup` to create preferences"
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
static fileReadError(preferencesPath, cause) {
|
|
1094
|
+
return new DextoRuntimeError(
|
|
1095
|
+
"preference_file_read_error" /* FILE_READ_ERROR */,
|
|
1096
|
+
"preference" /* PREFERENCE */,
|
|
1097
|
+
"system" /* SYSTEM */,
|
|
1098
|
+
`Failed to read preferences: ${cause}`,
|
|
1099
|
+
{ preferencesPath, cause },
|
|
1100
|
+
"Check file permissions and ensure the file is not corrupted"
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
static fileWriteError(preferencesPath, cause) {
|
|
1104
|
+
return new DextoRuntimeError(
|
|
1105
|
+
"preference_file_write_error" /* FILE_WRITE_ERROR */,
|
|
1106
|
+
"preference" /* PREFERENCE */,
|
|
1107
|
+
"system" /* SYSTEM */,
|
|
1108
|
+
`Failed to save preferences: ${cause}`,
|
|
1109
|
+
{ preferencesPath, cause },
|
|
1110
|
+
"Check file permissions and available disk space"
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
static validationFailed(zodError) {
|
|
1114
|
+
const issues = zodError.issues.map((issue) => ({
|
|
1115
|
+
code: "preference_validation_error" /* VALIDATION_ERROR */,
|
|
1116
|
+
message: `${issue.path.join(".")}: ${issue.message}`,
|
|
1117
|
+
scope: "preference" /* PREFERENCE */,
|
|
1118
|
+
type: "user" /* USER */,
|
|
1119
|
+
severity: "error"
|
|
1120
|
+
}));
|
|
1121
|
+
return new DextoValidationError(issues);
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
|
|
1125
|
+
// src/preferences/loader.ts
|
|
1126
|
+
async function loadGlobalPreferences() {
|
|
1127
|
+
const preferencesPath = getDextoGlobalPath(PREFERENCES_FILE);
|
|
1128
|
+
if (!existsSync(preferencesPath)) {
|
|
1129
|
+
throw PreferenceError.fileNotFound(preferencesPath);
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
const fileContent = await fs.readFile(preferencesPath, "utf-8");
|
|
1133
|
+
const rawPreferences = parseYaml(fileContent);
|
|
1134
|
+
const validation = GlobalPreferencesSchema.safeParse(rawPreferences);
|
|
1135
|
+
if (!validation.success) {
|
|
1136
|
+
throw PreferenceError.validationFailed(validation.error);
|
|
1137
|
+
}
|
|
1138
|
+
logger.debug(`Loaded global preferences from: ${preferencesPath}`);
|
|
1139
|
+
return validation.data;
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
if (error instanceof DextoValidationError || error instanceof DextoRuntimeError) {
|
|
1142
|
+
throw error;
|
|
1143
|
+
}
|
|
1144
|
+
throw PreferenceError.fileReadError(
|
|
1145
|
+
preferencesPath,
|
|
1146
|
+
error instanceof Error ? error.message : String(error)
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
async function saveGlobalPreferences(preferences) {
|
|
1151
|
+
const preferencesPath = getDextoGlobalPath(PREFERENCES_FILE);
|
|
1152
|
+
const validation = GlobalPreferencesSchema.safeParse(preferences);
|
|
1153
|
+
if (!validation.success) {
|
|
1154
|
+
throw PreferenceError.validationFailed(validation.error);
|
|
1155
|
+
}
|
|
1156
|
+
try {
|
|
1157
|
+
logger.info(`Saving global preferences to: ${preferencesPath}`);
|
|
1158
|
+
const dextoDir = getDextoGlobalPath("");
|
|
1159
|
+
await fs.mkdir(dextoDir, { recursive: true });
|
|
1160
|
+
const yamlContent = stringifyYaml(preferences, {
|
|
1161
|
+
indent: 2,
|
|
1162
|
+
lineWidth: 100,
|
|
1163
|
+
minContentWidth: 20
|
|
1164
|
+
});
|
|
1165
|
+
await fs.writeFile(preferencesPath, yamlContent, "utf-8");
|
|
1166
|
+
logger.info(
|
|
1167
|
+
`\u2713 Saved global preferences ${JSON.stringify(preferences)} to: ${preferencesPath}`
|
|
1168
|
+
);
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
throw PreferenceError.fileWriteError(
|
|
1171
|
+
preferencesPath,
|
|
1172
|
+
error instanceof Error ? error.message : String(error)
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
function globalPreferencesExist() {
|
|
1177
|
+
const preferencesPath = getDextoGlobalPath(PREFERENCES_FILE);
|
|
1178
|
+
return existsSync(preferencesPath);
|
|
1179
|
+
}
|
|
1180
|
+
function getGlobalPreferencesPath() {
|
|
1181
|
+
return getDextoGlobalPath(PREFERENCES_FILE);
|
|
1182
|
+
}
|
|
1183
|
+
function createInitialPreferences(provider, model, apiKeyVar, defaultAgent = "default-agent") {
|
|
1184
|
+
return {
|
|
1185
|
+
llm: {
|
|
1186
|
+
provider,
|
|
1187
|
+
model,
|
|
1188
|
+
apiKey: `$${apiKeyVar}`
|
|
1189
|
+
},
|
|
1190
|
+
defaults: {
|
|
1191
|
+
defaultAgent
|
|
1192
|
+
},
|
|
1193
|
+
setup: {
|
|
1194
|
+
completed: true
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
async function updateGlobalPreferences(updates) {
|
|
1199
|
+
const existing = await loadGlobalPreferences();
|
|
1200
|
+
const merged = {
|
|
1201
|
+
...existing,
|
|
1202
|
+
...updates,
|
|
1203
|
+
// LLM section requires complete replacement (high coherence - provider/model/apiKey must match)
|
|
1204
|
+
llm: updates.llm || existing.llm,
|
|
1205
|
+
// Defaults and setup sections allow partial updates (low coherence - independent fields)
|
|
1206
|
+
defaults: updates.defaults ? { ...existing.defaults, ...updates.defaults } : existing.defaults,
|
|
1207
|
+
setup: updates.setup ? { ...existing.setup, ...updates.setup } : existing.setup
|
|
1208
|
+
};
|
|
1209
|
+
const validation = GlobalPreferencesSchema.safeParse(merged);
|
|
1210
|
+
if (!validation.success) {
|
|
1211
|
+
throw PreferenceError.validationFailed(validation.error);
|
|
1212
|
+
}
|
|
1213
|
+
await saveGlobalPreferences(validation.data);
|
|
1214
|
+
return validation.data;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
export {
|
|
1218
|
+
LLMErrorCode,
|
|
1219
|
+
LLM_PROVIDERS,
|
|
1220
|
+
LLM_ROUTERS,
|
|
1221
|
+
SUPPORTED_FILE_TYPES,
|
|
1222
|
+
MIME_TYPE_TO_FILE_TYPE,
|
|
1223
|
+
getAllowedMimeTypes,
|
|
1224
|
+
DEFAULT_MAX_INPUT_TOKENS,
|
|
1225
|
+
LLM_REGISTRY,
|
|
1226
|
+
getDefaultModelForProvider,
|
|
1227
|
+
getSupportedProviders,
|
|
1228
|
+
getSupportedModels,
|
|
1229
|
+
getMaxInputTokensForModel,
|
|
1230
|
+
isValidProviderModel,
|
|
1231
|
+
getProviderFromModel,
|
|
1232
|
+
getAllSupportedModels,
|
|
1233
|
+
getSupportedRoutersForProvider,
|
|
1234
|
+
supportsBaseURL,
|
|
1235
|
+
requiresBaseURL,
|
|
1236
|
+
acceptsAnyModel,
|
|
1237
|
+
getSupportedFileTypesForModel,
|
|
1238
|
+
modelSupportsFileType,
|
|
1239
|
+
validateModelFileSupport,
|
|
1240
|
+
isRouterSupportedForModel,
|
|
1241
|
+
getSupportedRoutersForModel,
|
|
1242
|
+
isRouterSupportedForProvider,
|
|
1243
|
+
getEffectiveMaxInputTokens,
|
|
1244
|
+
LLMError,
|
|
1245
|
+
NonEmptyTrimmed,
|
|
1246
|
+
OptionalURL,
|
|
1247
|
+
EnvExpandedString,
|
|
1248
|
+
NonEmptyEnvExpandedString,
|
|
1249
|
+
RequiredEnvURL,
|
|
1250
|
+
ok,
|
|
1251
|
+
fail,
|
|
1252
|
+
hasErrors,
|
|
1253
|
+
splitIssues,
|
|
1254
|
+
zodToIssues,
|
|
1255
|
+
PreferenceErrorCode,
|
|
1256
|
+
PreferenceLLMSchema,
|
|
1257
|
+
PreferenceDefaultsSchema,
|
|
1258
|
+
PreferenceSetupSchema,
|
|
1259
|
+
GlobalPreferencesSchema,
|
|
1260
|
+
PREFERENCES_FILE,
|
|
1261
|
+
PreferenceError,
|
|
1262
|
+
loadGlobalPreferences,
|
|
1263
|
+
saveGlobalPreferences,
|
|
1264
|
+
globalPreferencesExist,
|
|
1265
|
+
getGlobalPreferencesPath,
|
|
1266
|
+
createInitialPreferences,
|
|
1267
|
+
updateGlobalPreferences
|
|
1268
|
+
};
|