@sonnechasser/ntrp 0.1.8 → 0.2.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/index.js +1933 -623
- package/dist/index.js.map +1 -1
- package/dist/investigation/verbosity-cli.js +903 -205
- package/dist/investigation/verbosity-cli.js.map +1 -1
- package/dist/mcp/server.js +928 -225
- package/dist/mcp/server.js.map +1 -1
- package/dist/whimsy/time-bank-smoke.js +1810 -495
- package/dist/whimsy/time-bank-smoke.js.map +1 -1
- package/package.json +2 -1
|
@@ -2954,6 +2954,12 @@ var init_types = __esm({
|
|
|
2954
2954
|
});
|
|
2955
2955
|
|
|
2956
2956
|
// src/ai/llm/errors.ts
|
|
2957
|
+
function isToolsUnsupportedMessage(message) {
|
|
2958
|
+
const msg = message.toLowerCase();
|
|
2959
|
+
const mentionsTools = msg.includes("tool") || msg.includes("function");
|
|
2960
|
+
const mentionsUnsupported = msg.includes("not support") || msg.includes("unsupported") || msg.includes("no support") || msg.includes("not available") || msg.includes("not enabled");
|
|
2961
|
+
return mentionsTools && mentionsUnsupported;
|
|
2962
|
+
}
|
|
2957
2963
|
function mapAnthropicError(err, provider) {
|
|
2958
2964
|
const e = err;
|
|
2959
2965
|
const status = e.status;
|
|
@@ -2971,6 +2977,9 @@ function mapAnthropicError(err, provider) {
|
|
|
2971
2977
|
if (status === 503) {
|
|
2972
2978
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
2973
2979
|
}
|
|
2980
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
2981
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
2982
|
+
}
|
|
2974
2983
|
if (status === 404 || message.toLowerCase().includes("model")) {
|
|
2975
2984
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
2976
2985
|
}
|
|
@@ -2979,6 +2988,11 @@ function mapAnthropicError(err, provider) {
|
|
|
2979
2988
|
}
|
|
2980
2989
|
return new LlmError("UNKNOWN", message, provider, status);
|
|
2981
2990
|
}
|
|
2991
|
+
function isModelNotFoundMessage(message) {
|
|
2992
|
+
const msg = message.toLowerCase();
|
|
2993
|
+
if (!msg.includes("model")) return false;
|
|
2994
|
+
return msg.includes("not found") || msg.includes("does not exist") || msg.includes("decommissioned") || msg.includes("deprecated") || msg.includes("retired") || msg.includes("do not have access") || msg.includes("invalid model");
|
|
2995
|
+
}
|
|
2982
2996
|
function mapOpenAiError(err, provider) {
|
|
2983
2997
|
const e = err;
|
|
2984
2998
|
const status = e.status;
|
|
@@ -2993,7 +3007,10 @@ function mapOpenAiError(err, provider) {
|
|
|
2993
3007
|
if (status === 503 || code === "server_error") {
|
|
2994
3008
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
2995
3009
|
}
|
|
2996
|
-
if (
|
|
3010
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
3011
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
3012
|
+
}
|
|
3013
|
+
if (status === 404 || code === "model_not_found" || code === "model_decommissioned" || isModelNotFoundMessage(message)) {
|
|
2997
3014
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
2998
3015
|
}
|
|
2999
3016
|
if (code === "context_length_exceeded") {
|
|
@@ -3129,8 +3146,15 @@ var init_anthropic = __esm({
|
|
|
3129
3146
|
}
|
|
3130
3147
|
});
|
|
3131
3148
|
|
|
3132
|
-
// src/ai/llm/adapters/openai.ts
|
|
3149
|
+
// src/ai/llm/adapters/openai-compat.ts
|
|
3133
3150
|
import OpenAI from "openai";
|
|
3151
|
+
function makeClient(apiKey, baseUrl) {
|
|
3152
|
+
return new OpenAI({
|
|
3153
|
+
// Keyless endpoints (Ollama) still need a non-empty string for the SDK.
|
|
3154
|
+
apiKey: apiKey || "local",
|
|
3155
|
+
...baseUrl ? { baseURL: baseUrl } : {}
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3134
3158
|
function toOpenAiTools(tools) {
|
|
3135
3159
|
return tools.map((t) => ({
|
|
3136
3160
|
type: "function",
|
|
@@ -3199,9 +3223,8 @@ function parseResponse2(message) {
|
|
|
3199
3223
|
assistant_message: { role: "assistant", content: text, tool_calls }
|
|
3200
3224
|
};
|
|
3201
3225
|
}
|
|
3202
|
-
async function
|
|
3203
|
-
const
|
|
3204
|
-
const client = new OpenAI({ apiKey });
|
|
3226
|
+
async function openaiCompatComplete(provider, baseUrl, apiKey, model, req) {
|
|
3227
|
+
const client = makeClient(apiKey, baseUrl);
|
|
3205
3228
|
try {
|
|
3206
3229
|
const response = await client.chat.completions.create({
|
|
3207
3230
|
model,
|
|
@@ -3211,7 +3234,7 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
3211
3234
|
});
|
|
3212
3235
|
const choice = response.choices[0];
|
|
3213
3236
|
if (!choice?.message) {
|
|
3214
|
-
throw new Error(
|
|
3237
|
+
throw new Error(`${provider} returned no message`);
|
|
3215
3238
|
}
|
|
3216
3239
|
const parsed = parseResponse2(choice.message);
|
|
3217
3240
|
if (response.usage) {
|
|
@@ -3222,15 +3245,11 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
3222
3245
|
}
|
|
3223
3246
|
return parsed;
|
|
3224
3247
|
} catch (err) {
|
|
3225
|
-
if (err instanceof OpenAI.APIError) {
|
|
3226
|
-
throw mapOpenAiError(err, provider);
|
|
3227
|
-
}
|
|
3228
3248
|
throw mapOpenAiError(err, provider);
|
|
3229
3249
|
}
|
|
3230
3250
|
}
|
|
3231
|
-
async function*
|
|
3232
|
-
const
|
|
3233
|
-
const client = new OpenAI({ apiKey });
|
|
3251
|
+
async function* openaiCompatStream(provider, baseUrl, apiKey, model, req) {
|
|
3252
|
+
const client = makeClient(apiKey, baseUrl);
|
|
3234
3253
|
try {
|
|
3235
3254
|
const stream = await client.chat.completions.create({
|
|
3236
3255
|
model,
|
|
@@ -3243,64 +3262,118 @@ async function* openaiStream(apiKey, model, req) {
|
|
|
3243
3262
|
if (delta) yield { type: "text_delta", text: delta };
|
|
3244
3263
|
}
|
|
3245
3264
|
} catch (err) {
|
|
3246
|
-
if (err instanceof OpenAI.APIError) {
|
|
3247
|
-
throw mapOpenAiError(err, provider);
|
|
3248
|
-
}
|
|
3249
3265
|
throw mapOpenAiError(err, provider);
|
|
3250
3266
|
}
|
|
3251
3267
|
}
|
|
3252
|
-
var
|
|
3253
|
-
"src/ai/llm/adapters/openai.ts"() {
|
|
3268
|
+
var init_openai_compat = __esm({
|
|
3269
|
+
"src/ai/llm/adapters/openai-compat.ts"() {
|
|
3254
3270
|
"use strict";
|
|
3255
3271
|
init_errors();
|
|
3256
3272
|
}
|
|
3257
3273
|
});
|
|
3258
3274
|
|
|
3259
|
-
// src/ai/llm/
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
if (entry.status === "active") return entry.id;
|
|
3272
|
-
if (!entry.successor_id) {
|
|
3273
|
-
const fallback = cheapestActiveInTier(entry.provider, entry.tier);
|
|
3274
|
-
return fallback?.id ?? current;
|
|
3275
|
-
}
|
|
3276
|
-
current = entry.successor_id;
|
|
3275
|
+
// src/ai/llm/models-cache.ts
|
|
3276
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
3277
|
+
import { join as join7 } from "path";
|
|
3278
|
+
function cachePath() {
|
|
3279
|
+
return join7(ntrpHome(), "models.json");
|
|
3280
|
+
}
|
|
3281
|
+
function loadFile() {
|
|
3282
|
+
if (cached) return cached;
|
|
3283
|
+
const path = cachePath();
|
|
3284
|
+
if (!existsSync7(path)) {
|
|
3285
|
+
cached = { version: 1, providers: {} };
|
|
3286
|
+
return cached;
|
|
3277
3287
|
}
|
|
3278
|
-
|
|
3288
|
+
try {
|
|
3289
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
3290
|
+
cached = { version: 1, providers: parsed.providers ?? {} };
|
|
3291
|
+
} catch {
|
|
3292
|
+
cached = { version: 1, providers: {} };
|
|
3293
|
+
}
|
|
3294
|
+
return cached;
|
|
3295
|
+
}
|
|
3296
|
+
function saveFile(file) {
|
|
3297
|
+
writeFileSync6(cachePath(), JSON.stringify(file, null, 2) + "\n");
|
|
3298
|
+
cached = file;
|
|
3299
|
+
}
|
|
3300
|
+
function getProviderModels(provider) {
|
|
3301
|
+
return loadFile().providers[provider];
|
|
3302
|
+
}
|
|
3303
|
+
function setProviderModels(provider, entry) {
|
|
3304
|
+
const file = loadFile();
|
|
3305
|
+
file.providers[provider] = entry;
|
|
3306
|
+
saveFile(file);
|
|
3307
|
+
}
|
|
3308
|
+
function getCachedTierModel(provider, tier) {
|
|
3309
|
+
return getProviderModels(provider)?.tier_stack?.[tier];
|
|
3310
|
+
}
|
|
3311
|
+
function findCachedModel(provider, modelId) {
|
|
3312
|
+
return getProviderModels(provider)?.models.find((m) => m.id === modelId);
|
|
3279
3313
|
}
|
|
3280
|
-
function
|
|
3314
|
+
function cachedModelProvider(modelId) {
|
|
3315
|
+
const file = loadFile();
|
|
3316
|
+
for (const [provider, entry] of Object.entries(file.providers)) {
|
|
3317
|
+
if (entry.models.some((m) => m.id === modelId)) return provider;
|
|
3318
|
+
}
|
|
3319
|
+
return void 0;
|
|
3320
|
+
}
|
|
3321
|
+
function markModelNoTools(provider, modelId) {
|
|
3322
|
+
const file = loadFile();
|
|
3323
|
+
const entry = file.providers[provider];
|
|
3324
|
+
if (!entry) return;
|
|
3325
|
+
const noTools = new Set(entry.quirks?.no_tools ?? []);
|
|
3326
|
+
if (noTools.has(modelId)) return;
|
|
3327
|
+
noTools.add(modelId);
|
|
3328
|
+
entry.quirks = { ...entry.quirks, no_tools: [...noTools] };
|
|
3329
|
+
saveFile(file);
|
|
3330
|
+
}
|
|
3331
|
+
function modelHasNoToolsQuirk(provider, modelId) {
|
|
3332
|
+
return !!getProviderModels(provider)?.quirks?.no_tools?.includes(modelId);
|
|
3333
|
+
}
|
|
3334
|
+
function isProviderCacheStale(provider, ttlMs = CACHE_TTL_MS) {
|
|
3335
|
+
const entry = getProviderModels(provider);
|
|
3336
|
+
if (!entry) return true;
|
|
3337
|
+
const fetched = Date.parse(entry.fetched_at);
|
|
3338
|
+
if (Number.isNaN(fetched)) return true;
|
|
3339
|
+
return Date.now() - fetched > ttlMs;
|
|
3340
|
+
}
|
|
3341
|
+
var CACHE_TTL_MS, cached;
|
|
3342
|
+
var init_models_cache = __esm({
|
|
3343
|
+
"src/ai/llm/models-cache.ts"() {
|
|
3344
|
+
"use strict";
|
|
3345
|
+
init_store();
|
|
3346
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3347
|
+
cached = null;
|
|
3348
|
+
}
|
|
3349
|
+
});
|
|
3350
|
+
|
|
3351
|
+
// src/ai/llm/catalog.ts
|
|
3352
|
+
function catalogTierDefault(provider, tier) {
|
|
3281
3353
|
const candidates = ENTRIES.filter(
|
|
3282
3354
|
(e) => e.provider === provider && e.tier === tier && e.status === "active"
|
|
3283
3355
|
);
|
|
3284
3356
|
if (candidates.length === 0) return void 0;
|
|
3285
3357
|
return candidates.sort((a, b) => a.relative_cost - b.relative_cost)[0];
|
|
3286
3358
|
}
|
|
3287
|
-
function
|
|
3288
|
-
|
|
3289
|
-
if (!entry) {
|
|
3290
|
-
throw new Error(`No active ${tier}-tier model for provider ${provider} in catalog`);
|
|
3291
|
-
}
|
|
3292
|
-
return entry;
|
|
3359
|
+
function modelProviderHint(modelId) {
|
|
3360
|
+
return cachedModelProvider(modelId) ?? byId.get(modelId)?.provider;
|
|
3293
3361
|
}
|
|
3294
|
-
function
|
|
3295
|
-
if (override)
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3362
|
+
function overrideForProvider(override, provider, activeProvider) {
|
|
3363
|
+
if (!override) return void 0;
|
|
3364
|
+
const hint = modelProviderHint(override);
|
|
3365
|
+
if (hint) return hint === provider ? override : void 0;
|
|
3366
|
+
return provider === activeProvider ? override : void 0;
|
|
3367
|
+
}
|
|
3368
|
+
function resolveModelSafe(provider, tier, override) {
|
|
3369
|
+
if (override) return override;
|
|
3370
|
+
const discovered = getCachedTierModel(provider, tier);
|
|
3371
|
+
if (discovered) return discovered;
|
|
3372
|
+
return catalogTierDefault(provider, tier)?.id;
|
|
3302
3373
|
}
|
|
3303
3374
|
function formatModelLabel(provider, modelId) {
|
|
3375
|
+
const cachedName = findCachedModel(provider, modelId)?.display_name;
|
|
3376
|
+
if (cachedName) return `${provider}/${cachedName}`;
|
|
3304
3377
|
const entry = byId.get(modelId);
|
|
3305
3378
|
return entry ? `${provider}/${entry.display_name}` : `${provider}/${modelId}`;
|
|
3306
3379
|
}
|
|
@@ -3308,6 +3381,7 @@ var ENTRIES, byId;
|
|
|
3308
3381
|
var init_catalog = __esm({
|
|
3309
3382
|
"src/ai/llm/catalog.ts"() {
|
|
3310
3383
|
"use strict";
|
|
3384
|
+
init_models_cache();
|
|
3311
3385
|
ENTRIES = [
|
|
3312
3386
|
{
|
|
3313
3387
|
id: "claude-opus-4-6",
|
|
@@ -3380,10 +3454,198 @@ var init_catalog = __esm({
|
|
|
3380
3454
|
}
|
|
3381
3455
|
});
|
|
3382
3456
|
|
|
3457
|
+
// src/ai/llm/providers.ts
|
|
3458
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
3459
|
+
import { join as join8 } from "path";
|
|
3460
|
+
function providersPath() {
|
|
3461
|
+
return join8(ntrpHome(), "providers.json");
|
|
3462
|
+
}
|
|
3463
|
+
function loadCustomProviders() {
|
|
3464
|
+
if (cachedEntries) return cachedEntries;
|
|
3465
|
+
const path = providersPath();
|
|
3466
|
+
if (!existsSync8(path)) {
|
|
3467
|
+
cachedEntries = [];
|
|
3468
|
+
return cachedEntries;
|
|
3469
|
+
}
|
|
3470
|
+
try {
|
|
3471
|
+
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
3472
|
+
cachedEntries = Array.isArray(parsed.providers) ? parsed.providers : [];
|
|
3473
|
+
} catch {
|
|
3474
|
+
cachedEntries = [];
|
|
3475
|
+
}
|
|
3476
|
+
return cachedEntries;
|
|
3477
|
+
}
|
|
3478
|
+
function customEntryToSpec(entry) {
|
|
3479
|
+
return {
|
|
3480
|
+
id: entry.id,
|
|
3481
|
+
label: entry.label ?? entry.id,
|
|
3482
|
+
api: "openai-compat",
|
|
3483
|
+
base_url: entry.base_url.replace(/\/+$/, ""),
|
|
3484
|
+
key_prefixes: [],
|
|
3485
|
+
shared_prefixes: [],
|
|
3486
|
+
key_config_name: keyConfigNameFor(entry.id),
|
|
3487
|
+
requires_key: entry.requires_key ?? false,
|
|
3488
|
+
custom: true
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3491
|
+
function keyConfigNameFor(providerId) {
|
|
3492
|
+
return providerId === "anthropic" ? "api-key" : `${providerId}-api-key`;
|
|
3493
|
+
}
|
|
3494
|
+
function listProviderSpecs() {
|
|
3495
|
+
const customs = loadCustomProviders();
|
|
3496
|
+
const customById = new Map(customs.map((e) => [e.id, e]));
|
|
3497
|
+
const specs = BUILTIN_SPECS.map((spec) => {
|
|
3498
|
+
const override = customById.get(spec.id);
|
|
3499
|
+
if (override?.base_url) {
|
|
3500
|
+
return { ...spec, base_url: override.base_url.replace(/\/+$/, "") };
|
|
3501
|
+
}
|
|
3502
|
+
return spec;
|
|
3503
|
+
});
|
|
3504
|
+
for (const entry of customs) {
|
|
3505
|
+
if (!BUILTIN_SPECS.some((s) => s.id === entry.id)) {
|
|
3506
|
+
specs.push(customEntryToSpec(entry));
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
return specs;
|
|
3510
|
+
}
|
|
3511
|
+
function getProviderSpec(id) {
|
|
3512
|
+
return listProviderSpecs().find((s) => s.id === id);
|
|
3513
|
+
}
|
|
3514
|
+
function isEndpointEnabled(id) {
|
|
3515
|
+
const entry = loadCustomProviders().find((e) => e.id === id);
|
|
3516
|
+
return !!entry && entry.enabled !== false;
|
|
3517
|
+
}
|
|
3518
|
+
function modelsUrl(spec) {
|
|
3519
|
+
if (spec.api === "anthropic") return `${spec.base_url}/v1/models?limit=100`;
|
|
3520
|
+
return `${spec.base_url}/models`;
|
|
3521
|
+
}
|
|
3522
|
+
var BUILTIN_SPECS, cachedEntries;
|
|
3523
|
+
var init_providers = __esm({
|
|
3524
|
+
"src/ai/llm/providers.ts"() {
|
|
3525
|
+
"use strict";
|
|
3526
|
+
init_store();
|
|
3527
|
+
BUILTIN_SPECS = [
|
|
3528
|
+
{
|
|
3529
|
+
id: "anthropic",
|
|
3530
|
+
label: "Anthropic",
|
|
3531
|
+
api: "anthropic",
|
|
3532
|
+
base_url: "https://api.anthropic.com",
|
|
3533
|
+
key_prefixes: ["sk-ant-"],
|
|
3534
|
+
shared_prefixes: [],
|
|
3535
|
+
key_config_name: "api-key",
|
|
3536
|
+
requires_key: true
|
|
3537
|
+
},
|
|
3538
|
+
{
|
|
3539
|
+
id: "openai",
|
|
3540
|
+
label: "OpenAI",
|
|
3541
|
+
api: "openai-compat",
|
|
3542
|
+
base_url: "https://api.openai.com/v1",
|
|
3543
|
+
key_prefixes: ["sk-proj-", "sk-svcacct-", "sk-admin-"],
|
|
3544
|
+
shared_prefixes: ["sk-"],
|
|
3545
|
+
key_config_name: "openai-api-key",
|
|
3546
|
+
env_var: "OPENAI_API_KEY",
|
|
3547
|
+
requires_key: true
|
|
3548
|
+
},
|
|
3549
|
+
{
|
|
3550
|
+
id: "google",
|
|
3551
|
+
label: "Google Gemini",
|
|
3552
|
+
api: "openai-compat",
|
|
3553
|
+
base_url: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
3554
|
+
key_prefixes: ["AIza"],
|
|
3555
|
+
shared_prefixes: [],
|
|
3556
|
+
key_config_name: "google-api-key",
|
|
3557
|
+
requires_key: true
|
|
3558
|
+
},
|
|
3559
|
+
{
|
|
3560
|
+
id: "groq",
|
|
3561
|
+
label: "Groq",
|
|
3562
|
+
api: "openai-compat",
|
|
3563
|
+
base_url: "https://api.groq.com/openai/v1",
|
|
3564
|
+
key_prefixes: ["gsk_"],
|
|
3565
|
+
shared_prefixes: [],
|
|
3566
|
+
key_config_name: "groq-api-key",
|
|
3567
|
+
requires_key: true
|
|
3568
|
+
},
|
|
3569
|
+
{
|
|
3570
|
+
id: "mistral",
|
|
3571
|
+
label: "Mistral",
|
|
3572
|
+
api: "openai-compat",
|
|
3573
|
+
base_url: "https://api.mistral.ai/v1",
|
|
3574
|
+
key_prefixes: [],
|
|
3575
|
+
shared_prefixes: [],
|
|
3576
|
+
key_config_name: "mistral-api-key",
|
|
3577
|
+
requires_key: true
|
|
3578
|
+
},
|
|
3579
|
+
{
|
|
3580
|
+
id: "deepseek",
|
|
3581
|
+
label: "DeepSeek",
|
|
3582
|
+
api: "openai-compat",
|
|
3583
|
+
base_url: "https://api.deepseek.com/v1",
|
|
3584
|
+
key_prefixes: [],
|
|
3585
|
+
shared_prefixes: ["sk-"],
|
|
3586
|
+
key_config_name: "deepseek-api-key",
|
|
3587
|
+
requires_key: true
|
|
3588
|
+
},
|
|
3589
|
+
{
|
|
3590
|
+
id: "xai",
|
|
3591
|
+
label: "xAI",
|
|
3592
|
+
api: "openai-compat",
|
|
3593
|
+
base_url: "https://api.x.ai/v1",
|
|
3594
|
+
key_prefixes: ["xai-"],
|
|
3595
|
+
shared_prefixes: [],
|
|
3596
|
+
key_config_name: "xai-api-key",
|
|
3597
|
+
requires_key: true
|
|
3598
|
+
},
|
|
3599
|
+
{
|
|
3600
|
+
id: "openrouter",
|
|
3601
|
+
label: "OpenRouter",
|
|
3602
|
+
api: "openai-compat",
|
|
3603
|
+
base_url: "https://openrouter.ai/api/v1",
|
|
3604
|
+
key_prefixes: ["sk-or-"],
|
|
3605
|
+
shared_prefixes: [],
|
|
3606
|
+
key_config_name: "openrouter-api-key",
|
|
3607
|
+
requires_key: true
|
|
3608
|
+
},
|
|
3609
|
+
{
|
|
3610
|
+
id: "together",
|
|
3611
|
+
label: "Together AI",
|
|
3612
|
+
api: "openai-compat",
|
|
3613
|
+
base_url: "https://api.together.xyz/v1",
|
|
3614
|
+
key_prefixes: [],
|
|
3615
|
+
shared_prefixes: [],
|
|
3616
|
+
key_config_name: "together-api-key",
|
|
3617
|
+
requires_key: true
|
|
3618
|
+
},
|
|
3619
|
+
{
|
|
3620
|
+
id: "fireworks",
|
|
3621
|
+
label: "Fireworks AI",
|
|
3622
|
+
api: "openai-compat",
|
|
3623
|
+
base_url: "https://api.fireworks.ai/inference/v1",
|
|
3624
|
+
key_prefixes: ["fw_"],
|
|
3625
|
+
shared_prefixes: [],
|
|
3626
|
+
key_config_name: "fireworks-api-key",
|
|
3627
|
+
requires_key: true
|
|
3628
|
+
},
|
|
3629
|
+
{
|
|
3630
|
+
id: "ollama",
|
|
3631
|
+
label: "Ollama (local)",
|
|
3632
|
+
api: "openai-compat",
|
|
3633
|
+
base_url: "http://localhost:11434/v1",
|
|
3634
|
+
key_prefixes: [],
|
|
3635
|
+
shared_prefixes: [],
|
|
3636
|
+
key_config_name: "ollama-api-key",
|
|
3637
|
+
requires_key: false
|
|
3638
|
+
}
|
|
3639
|
+
];
|
|
3640
|
+
cachedEntries = null;
|
|
3641
|
+
}
|
|
3642
|
+
});
|
|
3643
|
+
|
|
3383
3644
|
// src/config/llm-config.ts
|
|
3384
3645
|
function parseProvider(raw) {
|
|
3385
|
-
if (raw
|
|
3386
|
-
|
|
3646
|
+
if (!raw?.trim()) return void 0;
|
|
3647
|
+
const id = raw.trim();
|
|
3648
|
+
return getProviderSpec(id) ? id : void 0;
|
|
3387
3649
|
}
|
|
3388
3650
|
function parseTier(raw) {
|
|
3389
3651
|
if (raw === "high" || raw === "medium" || raw === "low") return raw;
|
|
@@ -3391,7 +3653,7 @@ function parseTier(raw) {
|
|
|
3391
3653
|
}
|
|
3392
3654
|
function parseFailoverOrder(raw) {
|
|
3393
3655
|
if (!raw?.trim()) return ["openai"];
|
|
3394
|
-
return raw.split(",").map((s) => s.trim()).filter((s) => s
|
|
3656
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => !!s && !!getProviderSpec(s));
|
|
3395
3657
|
}
|
|
3396
3658
|
function parseAutoFailover(raw) {
|
|
3397
3659
|
if (!raw) return false;
|
|
@@ -3406,30 +3668,42 @@ function getOpenAiApiKey() {
|
|
|
3406
3668
|
if (fromConfig) return fromConfig;
|
|
3407
3669
|
return process.env.OPENAI_API_KEY?.trim() || void 0;
|
|
3408
3670
|
}
|
|
3671
|
+
function getProviderApiKey(provider) {
|
|
3672
|
+
const spec = getProviderSpec(provider);
|
|
3673
|
+
if (!spec) return void 0;
|
|
3674
|
+
const record = loadConfig();
|
|
3675
|
+
const fromConfig = record[spec.key_config_name]?.trim();
|
|
3676
|
+
if (fromConfig) return fromConfig;
|
|
3677
|
+
if (spec.env_var) {
|
|
3678
|
+
const fromEnv = process.env[spec.env_var]?.trim();
|
|
3679
|
+
if (fromEnv) return fromEnv;
|
|
3680
|
+
}
|
|
3681
|
+
return void 0;
|
|
3682
|
+
}
|
|
3409
3683
|
function hasProviderKey(provider) {
|
|
3410
|
-
|
|
3411
|
-
|
|
3684
|
+
const spec = getProviderSpec(provider);
|
|
3685
|
+
if (!spec) return false;
|
|
3686
|
+
if (!spec.requires_key) return isEndpointEnabled(spec.id) || !!getProviderApiKey(provider);
|
|
3687
|
+
return !!getProviderApiKey(provider);
|
|
3412
3688
|
}
|
|
3413
3689
|
function getAvailableProviders() {
|
|
3414
|
-
|
|
3415
|
-
if (hasProviderKey("anthropic")) out.push("anthropic");
|
|
3416
|
-
if (hasProviderKey("openai")) out.push("openai");
|
|
3417
|
-
return out;
|
|
3690
|
+
return listProviderSpecs().filter((s) => hasProviderKey(s.id)).map((s) => s.id);
|
|
3418
3691
|
}
|
|
3419
3692
|
function hasAnyLlmProvider() {
|
|
3420
3693
|
return getAvailableProviders().length > 0;
|
|
3421
3694
|
}
|
|
3695
|
+
function hasKeylessConfiguredProvider() {
|
|
3696
|
+
return listProviderSpecs().some((s) => !s.requires_key && hasProviderKey(s.id));
|
|
3697
|
+
}
|
|
3422
3698
|
function applyLazyMigration(config) {
|
|
3423
3699
|
if (migrated) return;
|
|
3424
3700
|
migrated = true;
|
|
3425
3701
|
let changed = false;
|
|
3426
3702
|
const record = config;
|
|
3427
3703
|
if (!record["llm-primary"]) {
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
} else if (record["openai-api-key"] || process.env.OPENAI_API_KEY) {
|
|
3432
|
-
record["llm-primary"] = "openai";
|
|
3704
|
+
const available = getAvailableProviders();
|
|
3705
|
+
if (available.length > 0) {
|
|
3706
|
+
record["llm-primary"] = available[0];
|
|
3433
3707
|
changed = true;
|
|
3434
3708
|
}
|
|
3435
3709
|
}
|
|
@@ -3469,25 +3743,311 @@ function loadLlmConfig() {
|
|
|
3469
3743
|
openaiKey: getOpenAiApiKey()
|
|
3470
3744
|
};
|
|
3471
3745
|
}
|
|
3472
|
-
function getProviderApiKey(provider) {
|
|
3473
|
-
if (provider === "anthropic") return getAnthropicApiKey();
|
|
3474
|
-
return getOpenAiApiKey();
|
|
3475
|
-
}
|
|
3476
3746
|
function getInvestigationApiKey(provider) {
|
|
3477
3747
|
if (provider === "anthropic") {
|
|
3478
3748
|
return process.env.NTRP_INVESTIGATION_API_KEY?.trim() || getAnthropicApiKey();
|
|
3479
3749
|
}
|
|
3480
|
-
|
|
3750
|
+
if (provider === "openai") {
|
|
3751
|
+
return process.env.NTRP_INVESTIGATION_OPENAI_KEY?.trim() || getOpenAiApiKey();
|
|
3752
|
+
}
|
|
3753
|
+
return getProviderApiKey(provider);
|
|
3481
3754
|
}
|
|
3482
3755
|
var migrated;
|
|
3483
3756
|
var init_llm_config = __esm({
|
|
3484
3757
|
"src/config/llm-config.ts"() {
|
|
3485
3758
|
"use strict";
|
|
3759
|
+
init_providers();
|
|
3486
3760
|
init_store();
|
|
3487
3761
|
migrated = false;
|
|
3488
3762
|
}
|
|
3489
3763
|
});
|
|
3490
3764
|
|
|
3765
|
+
// src/ai/llm/http.ts
|
|
3766
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
3767
|
+
function fixtureResponse(url, headers) {
|
|
3768
|
+
try {
|
|
3769
|
+
const raw = readFileSync8(process.env.NTRP_LLM_HTTP_FIXTURE, "utf-8");
|
|
3770
|
+
const entries = JSON.parse(raw);
|
|
3771
|
+
const headerValues = Object.values(headers).join(" ");
|
|
3772
|
+
for (const entry of entries) {
|
|
3773
|
+
if (!url.includes(entry.url_includes)) continue;
|
|
3774
|
+
if (entry.auth_includes && !headerValues.includes(entry.auth_includes)) continue;
|
|
3775
|
+
return { status: entry.status, ok: entry.status >= 200 && entry.status < 300, body: entry.body };
|
|
3776
|
+
}
|
|
3777
|
+
} catch {
|
|
3778
|
+
}
|
|
3779
|
+
return { status: 0, ok: false, body: void 0 };
|
|
3780
|
+
}
|
|
3781
|
+
async function llmHttpGetJson(url, headers, timeoutMs = 6e3) {
|
|
3782
|
+
if (process.env.NTRP_LLM_HTTP_FIXTURE) {
|
|
3783
|
+
return fixtureResponse(url, headers);
|
|
3784
|
+
}
|
|
3785
|
+
const controller = new AbortController();
|
|
3786
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3787
|
+
try {
|
|
3788
|
+
const res = await fetch(url, { method: "GET", headers, signal: controller.signal });
|
|
3789
|
+
let body;
|
|
3790
|
+
try {
|
|
3791
|
+
body = await res.json();
|
|
3792
|
+
} catch {
|
|
3793
|
+
body = void 0;
|
|
3794
|
+
}
|
|
3795
|
+
return { status: res.status, ok: res.ok, body };
|
|
3796
|
+
} catch {
|
|
3797
|
+
return { status: 0, ok: false, body: void 0 };
|
|
3798
|
+
} finally {
|
|
3799
|
+
clearTimeout(timer);
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
var init_http = __esm({
|
|
3803
|
+
"src/ai/llm/http.ts"() {
|
|
3804
|
+
"use strict";
|
|
3805
|
+
}
|
|
3806
|
+
});
|
|
3807
|
+
|
|
3808
|
+
// src/ai/llm/ranking.ts
|
|
3809
|
+
function compareModels(a, b) {
|
|
3810
|
+
const createdA = a.created ?? 0;
|
|
3811
|
+
const createdB = b.created ?? 0;
|
|
3812
|
+
if (createdA !== createdB) return createdB - createdA;
|
|
3813
|
+
const versionA = extractVersion(a.id);
|
|
3814
|
+
const versionB = extractVersion(b.id);
|
|
3815
|
+
if (versionA !== versionB) return versionB - versionA;
|
|
3816
|
+
if (a.id.length !== b.id.length) return a.id.length - b.id.length;
|
|
3817
|
+
return a.id.localeCompare(b.id);
|
|
3818
|
+
}
|
|
3819
|
+
function extractVersion(id) {
|
|
3820
|
+
const match = id.match(/(\d+(?:\.\d+)?)/);
|
|
3821
|
+
return match ? Number(match[1]) : 0;
|
|
3822
|
+
}
|
|
3823
|
+
function pickByPatterns(models, patterns) {
|
|
3824
|
+
for (const pattern of patterns) {
|
|
3825
|
+
const matches = models.filter((m) => pattern.test(m.id));
|
|
3826
|
+
if (matches.length > 0) return [...matches].sort(compareModels)[0];
|
|
3827
|
+
}
|
|
3828
|
+
return void 0;
|
|
3829
|
+
}
|
|
3830
|
+
function genericBucket(model) {
|
|
3831
|
+
if (GENERIC_HIGH.test(model.id)) return "high";
|
|
3832
|
+
if (GENERIC_LOW.test(model.id)) return "low";
|
|
3833
|
+
return "medium";
|
|
3834
|
+
}
|
|
3835
|
+
function genericPick(models, tier) {
|
|
3836
|
+
const bucket = models.filter((m) => genericBucket(m) === tier);
|
|
3837
|
+
if (bucket.length > 0) return [...bucket].sort(compareModels)[0];
|
|
3838
|
+
return void 0;
|
|
3839
|
+
}
|
|
3840
|
+
function rankModels(providerId, models) {
|
|
3841
|
+
if (models.length === 0) return null;
|
|
3842
|
+
const preferences = PROVIDER_PREFERENCES[providerId];
|
|
3843
|
+
const picks = {};
|
|
3844
|
+
for (const tier of ["high", "medium", "low"]) {
|
|
3845
|
+
const preferred = preferences ? pickByPatterns(models, preferences[tier]) : void 0;
|
|
3846
|
+
const generic = preferred ?? genericPick(models, tier);
|
|
3847
|
+
if (generic) picks[tier] = generic.id;
|
|
3848
|
+
}
|
|
3849
|
+
const anyModel = [...models].sort(compareModels)[0].id;
|
|
3850
|
+
const high = picks.high ?? picks.medium ?? picks.low ?? anyModel;
|
|
3851
|
+
const medium = picks.medium ?? picks.high ?? picks.low ?? anyModel;
|
|
3852
|
+
const low = picks.low ?? picks.medium ?? picks.high ?? anyModel;
|
|
3853
|
+
return { high, medium, low };
|
|
3854
|
+
}
|
|
3855
|
+
var PROVIDER_PREFERENCES, GENERIC_LOW, GENERIC_HIGH;
|
|
3856
|
+
var init_ranking = __esm({
|
|
3857
|
+
"src/ai/llm/ranking.ts"() {
|
|
3858
|
+
"use strict";
|
|
3859
|
+
PROVIDER_PREFERENCES = {
|
|
3860
|
+
anthropic: {
|
|
3861
|
+
high: [/^claude-opus/i, /^claude-sonnet/i],
|
|
3862
|
+
medium: [/^claude-sonnet/i, /^claude-haiku/i],
|
|
3863
|
+
low: [/^claude-haiku/i, /^claude-sonnet/i]
|
|
3864
|
+
},
|
|
3865
|
+
openai: {
|
|
3866
|
+
high: [/^gpt-5(?!.*(mini|nano|chat))/i, /^gpt-4\.1(?!.*(mini|nano))/i, /^gpt-4o(?!.*mini)/i, /^o3(?!.*mini)/i],
|
|
3867
|
+
medium: [/^gpt-5.*mini/i, /^gpt-4\.1-mini/i, /^gpt-4o-mini/i, /^o4-mini/i],
|
|
3868
|
+
low: [/^gpt-5.*nano/i, /^gpt-4\.1-nano/i, /^gpt-4o-mini/i]
|
|
3869
|
+
},
|
|
3870
|
+
google: {
|
|
3871
|
+
high: [/^gemini-[\d.]+-pro/i, /^gemini-[\d.]+-flash(?!-lite)/i],
|
|
3872
|
+
medium: [/^gemini-[\d.]+-flash(?!-lite|-8b)/i, /^gemini-[\d.]+-pro/i],
|
|
3873
|
+
low: [/^gemini-[\d.]+-flash-lite/i, /flash-8b/i, /^gemini-[\d.]+-flash(?!-lite)/i]
|
|
3874
|
+
},
|
|
3875
|
+
groq: {
|
|
3876
|
+
high: [/llama-3\.3-70b/i, /gpt-oss-120b/i, /70b/i, /deepseek-r1/i],
|
|
3877
|
+
medium: [/llama-3\.1-8b-instant/i, /gpt-oss-20b/i, /llama.*8b/i],
|
|
3878
|
+
low: [/8b-instant/i, /llama.*8b/i, /gemma/i]
|
|
3879
|
+
},
|
|
3880
|
+
deepseek: {
|
|
3881
|
+
high: [/reasoner/i, /chat/i],
|
|
3882
|
+
medium: [/chat/i],
|
|
3883
|
+
low: [/chat/i]
|
|
3884
|
+
},
|
|
3885
|
+
mistral: {
|
|
3886
|
+
high: [/large/i, /medium/i],
|
|
3887
|
+
medium: [/medium/i, /^mistral-small/i],
|
|
3888
|
+
low: [/ministral/i, /small/i, /tiny/i]
|
|
3889
|
+
},
|
|
3890
|
+
xai: {
|
|
3891
|
+
high: [/^grok-\d+(?!.*(mini|fast))/i, /^grok(?!.*(mini|fast))/i],
|
|
3892
|
+
medium: [/^grok.*mini(?!.*fast)/i, /^grok.*fast/i],
|
|
3893
|
+
low: [/^grok.*mini.*fast/i, /^grok.*mini/i]
|
|
3894
|
+
},
|
|
3895
|
+
openrouter: {
|
|
3896
|
+
high: [/^openrouter\/auto$/i, /claude.*opus/i, /^openai\/gpt-5(?!.*(mini|nano))/i, /gemini.*pro/i],
|
|
3897
|
+
medium: [/claude.*sonnet/i, /gpt-5.*mini/i, /gpt-4\.1-mini/i, /gemini.*flash(?!-lite)/i],
|
|
3898
|
+
low: [/claude.*haiku/i, /nano/i, /flash-lite/i, /mini/i]
|
|
3899
|
+
}
|
|
3900
|
+
};
|
|
3901
|
+
GENERIC_LOW = /(mini|nano|lite|tiny|micro|small|haiku|instant|flash|turbo|\b0?\.?5b\b|\b[1-8]b\b)/i;
|
|
3902
|
+
GENERIC_HIGH = /(opus|ultra|large|max\b|\bpro\b|405b|253b|235b|120b|72b|70b|reason|-r1\b|think|deep)/i;
|
|
3903
|
+
}
|
|
3904
|
+
});
|
|
3905
|
+
|
|
3906
|
+
// src/ai/llm/discovery.ts
|
|
3907
|
+
function authHeaders(spec, apiKey) {
|
|
3908
|
+
if (spec.api === "anthropic") {
|
|
3909
|
+
return { "x-api-key": apiKey ?? "", "anthropic-version": "2023-06-01" };
|
|
3910
|
+
}
|
|
3911
|
+
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
|
|
3912
|
+
}
|
|
3913
|
+
function normalizeItem(spec, item) {
|
|
3914
|
+
let id = item.id ?? "";
|
|
3915
|
+
if (!id) return null;
|
|
3916
|
+
if (id.startsWith("models/")) id = id.slice("models/".length);
|
|
3917
|
+
const model = { id };
|
|
3918
|
+
const display = item.display_name ?? item.name;
|
|
3919
|
+
if (display && display !== id) model.display_name = display;
|
|
3920
|
+
if (typeof item.created === "number") model.created = item.created;
|
|
3921
|
+
else if (item.created_at) {
|
|
3922
|
+
const parsed = Date.parse(item.created_at);
|
|
3923
|
+
if (!Number.isNaN(parsed)) model.created = Math.floor(parsed / 1e3);
|
|
3924
|
+
}
|
|
3925
|
+
if (typeof item.context_length === "number") model.context_length = item.context_length;
|
|
3926
|
+
if (Array.isArray(item.supported_parameters)) {
|
|
3927
|
+
model.supports_tools = item.supported_parameters.includes("tools");
|
|
3928
|
+
}
|
|
3929
|
+
return model;
|
|
3930
|
+
}
|
|
3931
|
+
async function fetchProviderModels(spec, apiKey, timeoutMs = 6e3) {
|
|
3932
|
+
const headers = authHeaders(spec, apiKey);
|
|
3933
|
+
if (spec.api === "anthropic") {
|
|
3934
|
+
const models2 = [];
|
|
3935
|
+
let url = modelsUrl(spec);
|
|
3936
|
+
for (let page = 0; page < 5 && url; page++) {
|
|
3937
|
+
const res2 = await llmHttpGetJson(url, headers, timeoutMs);
|
|
3938
|
+
if (!res2.ok) return models2.length > 0 ? { ok: true, models: models2 } : { ok: false, status: res2.status };
|
|
3939
|
+
const body2 = res2.body;
|
|
3940
|
+
for (const item of body2?.data ?? []) {
|
|
3941
|
+
const model = normalizeItem(spec, item);
|
|
3942
|
+
if (model) models2.push(model);
|
|
3943
|
+
}
|
|
3944
|
+
url = body2?.has_more && body2.last_id ? `${spec.base_url}/v1/models?limit=100&after_id=${encodeURIComponent(body2.last_id)}` : null;
|
|
3945
|
+
}
|
|
3946
|
+
return { ok: true, models: models2 };
|
|
3947
|
+
}
|
|
3948
|
+
const res = await llmHttpGetJson(modelsUrl(spec), headers, timeoutMs);
|
|
3949
|
+
if (!res.ok) return { ok: false, status: res.status };
|
|
3950
|
+
const body = res.body;
|
|
3951
|
+
const list = Array.isArray(body) ? body : body?.data ?? [];
|
|
3952
|
+
const models = [];
|
|
3953
|
+
for (const item of list) {
|
|
3954
|
+
const model = normalizeItem(spec, item);
|
|
3955
|
+
if (model) models.push(model);
|
|
3956
|
+
}
|
|
3957
|
+
return { ok: true, models };
|
|
3958
|
+
}
|
|
3959
|
+
function filterChatModels(spec, models) {
|
|
3960
|
+
const extra = PROVIDER_EXCLUDE[spec.id];
|
|
3961
|
+
return models.filter((m) => !NON_CHAT.test(m.id) && !(extra && extra.test(m.id)));
|
|
3962
|
+
}
|
|
3963
|
+
function storeDiscoveredModels(providerId, rawModels) {
|
|
3964
|
+
const spec = getProviderSpec(providerId);
|
|
3965
|
+
if (!spec || rawModels.length === 0) return null;
|
|
3966
|
+
const chat = filterChatModels(spec, rawModels);
|
|
3967
|
+
const usable = chat.length > 0 ? chat : rawModels;
|
|
3968
|
+
const stack = rankModels(providerId, usable);
|
|
3969
|
+
if (!stack) return null;
|
|
3970
|
+
const prior = getProviderModels(providerId);
|
|
3971
|
+
const entry = {
|
|
3972
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3973
|
+
models: usable,
|
|
3974
|
+
tier_stack: stack,
|
|
3975
|
+
...prior?.quirks ? { quirks: prior.quirks } : {}
|
|
3976
|
+
};
|
|
3977
|
+
setProviderModels(providerId, entry);
|
|
3978
|
+
return entry;
|
|
3979
|
+
}
|
|
3980
|
+
async function refreshProviderModels(providerId, opts = {}) {
|
|
3981
|
+
const spec = getProviderSpec(providerId);
|
|
3982
|
+
if (!spec) return null;
|
|
3983
|
+
if (!opts.force && !isProviderCacheStale(providerId)) {
|
|
3984
|
+
return getProviderModels(providerId) ?? null;
|
|
3985
|
+
}
|
|
3986
|
+
const apiKey = opts.apiKey ?? getProviderApiKey(providerId);
|
|
3987
|
+
if (spec.requires_key && !apiKey) return null;
|
|
3988
|
+
const result = await fetchProviderModels(spec, apiKey);
|
|
3989
|
+
if (!result.ok) return null;
|
|
3990
|
+
return storeDiscoveredModels(providerId, result.models);
|
|
3991
|
+
}
|
|
3992
|
+
function rerankExcluding(providerId, deadModelId) {
|
|
3993
|
+
const prior = getProviderModels(providerId);
|
|
3994
|
+
if (!prior) return null;
|
|
3995
|
+
const survivors = prior.models.filter((m) => m.id !== deadModelId);
|
|
3996
|
+
const stack = rankModels(providerId, survivors);
|
|
3997
|
+
if (!stack) return null;
|
|
3998
|
+
const entry = { ...prior, models: survivors, tier_stack: stack };
|
|
3999
|
+
setProviderModels(providerId, entry);
|
|
4000
|
+
return entry;
|
|
4001
|
+
}
|
|
4002
|
+
var NON_CHAT, PROVIDER_EXCLUDE;
|
|
4003
|
+
var init_discovery = __esm({
|
|
4004
|
+
"src/ai/llm/discovery.ts"() {
|
|
4005
|
+
"use strict";
|
|
4006
|
+
init_llm_config();
|
|
4007
|
+
init_http();
|
|
4008
|
+
init_models_cache();
|
|
4009
|
+
init_providers();
|
|
4010
|
+
init_ranking();
|
|
4011
|
+
NON_CHAT = /(embed|embedding|whisper|tts|dall-e|davinci|babbage|curie|\bada\b|moderation|-audio|realtime|transcribe|-image|rerank|guard|voice|sora|distil-whisper)/i;
|
|
4012
|
+
PROVIDER_EXCLUDE = {
|
|
4013
|
+
openai: /(chatgpt|-search|deep-research|-pro\b|computer-use|codex-mini|-instruct\b)/i
|
|
4014
|
+
};
|
|
4015
|
+
}
|
|
4016
|
+
});
|
|
4017
|
+
|
|
4018
|
+
// src/ai/llm/heal.ts
|
|
4019
|
+
async function healModelNotFound(opts) {
|
|
4020
|
+
const { provider, tier, deadModel } = opts;
|
|
4021
|
+
const refreshed = await refreshProviderModels(provider, { apiKey: opts.apiKey, force: true });
|
|
4022
|
+
let candidate = refreshed?.tier_stack?.[tier] ?? getCachedTierModel(provider, tier);
|
|
4023
|
+
if (!candidate || candidate === deadModel) {
|
|
4024
|
+
const reranked = rerankExcluding(provider, deadModel);
|
|
4025
|
+
candidate = reranked?.tier_stack?.[tier];
|
|
4026
|
+
}
|
|
4027
|
+
if (!candidate || candidate === deadModel) return null;
|
|
4028
|
+
clearDeadOverride(deadModel, opts.ctx);
|
|
4029
|
+
return {
|
|
4030
|
+
model: candidate,
|
|
4031
|
+
notice: `model ${deadModel} is no longer available \u2014 switched to ${candidate}`
|
|
4032
|
+
};
|
|
4033
|
+
}
|
|
4034
|
+
function clearDeadOverride(deadModel, ctx) {
|
|
4035
|
+
if (getConfigValue("llm-model-override")?.trim() === deadModel) {
|
|
4036
|
+
deleteConfigValue("llm-model-override");
|
|
4037
|
+
}
|
|
4038
|
+
if (ctx?.llm?.modelOverride === deadModel) {
|
|
4039
|
+
ctx.llm.modelOverride = void 0;
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
var init_heal = __esm({
|
|
4043
|
+
"src/ai/llm/heal.ts"() {
|
|
4044
|
+
"use strict";
|
|
4045
|
+
init_store();
|
|
4046
|
+
init_discovery();
|
|
4047
|
+
init_models_cache();
|
|
4048
|
+
}
|
|
4049
|
+
});
|
|
4050
|
+
|
|
3491
4051
|
// src/ai/llm/surfaces.ts
|
|
3492
4052
|
function tierForSurface(surface, userTier) {
|
|
3493
4053
|
const spec = SURFACE_SPECS[surface];
|
|
@@ -3532,7 +4092,7 @@ function resolveActiveProvider(ctx) {
|
|
|
3532
4092
|
if (session && hasProviderKey(session)) return session;
|
|
3533
4093
|
const cfg = loadLlmConfig();
|
|
3534
4094
|
if (hasProviderKey(cfg.primary)) return cfg.primary;
|
|
3535
|
-
const available =
|
|
4095
|
+
const available = getAvailableProviders();
|
|
3536
4096
|
if (available.length > 0) return available[0];
|
|
3537
4097
|
return cfg.primary;
|
|
3538
4098
|
}
|
|
@@ -3558,8 +4118,8 @@ function resolveProviderOrder(ctx) {
|
|
|
3558
4118
|
for (const p of cfg.failoverOrder) {
|
|
3559
4119
|
if (p !== active && hasProviderKey(p) && !order.includes(p)) order.push(p);
|
|
3560
4120
|
}
|
|
3561
|
-
for (const p of
|
|
3562
|
-
if (p !== active &&
|
|
4121
|
+
for (const p of getAvailableProviders()) {
|
|
4122
|
+
if (p !== active && !order.includes(p)) order.push(p);
|
|
3563
4123
|
}
|
|
3564
4124
|
return order;
|
|
3565
4125
|
}
|
|
@@ -3573,23 +4133,16 @@ var init_session_state = __esm({
|
|
|
3573
4133
|
});
|
|
3574
4134
|
|
|
3575
4135
|
// src/ai/llm/resolver.ts
|
|
3576
|
-
function getProviderOrder(config, ctx) {
|
|
3577
|
-
void config;
|
|
3578
|
-
return resolveProviderOrder(ctx);
|
|
3579
|
-
}
|
|
3580
4136
|
function resolveCompletionContext(surface, opts = {}) {
|
|
3581
4137
|
const activeProvider = resolveActiveProvider(opts.ctx);
|
|
3582
4138
|
const tier = opts.tier ?? resolveEffectiveTier(opts.ctx, surface);
|
|
3583
4139
|
const override = opts.modelOverride ?? resolveEffectiveModelOverride(opts.ctx);
|
|
3584
4140
|
const providerOrder = resolveProviderOrder(opts.ctx);
|
|
3585
4141
|
const modelByProvider = {};
|
|
3586
|
-
for (const provider of providerOrder) {
|
|
3587
|
-
const providerOverride =
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
if (!modelByProvider[activeProvider]) {
|
|
3591
|
-
const activeOverride = override && getCatalogEntry(override)?.provider === activeProvider ? override : void 0;
|
|
3592
|
-
modelByProvider[activeProvider] = resolveModel(activeProvider, tier, activeOverride);
|
|
4142
|
+
for (const provider of /* @__PURE__ */ new Set([...providerOrder, activeProvider])) {
|
|
4143
|
+
const providerOverride = overrideForProvider(override, provider, activeProvider);
|
|
4144
|
+
const model = resolveModelSafe(provider, tier, providerOverride);
|
|
4145
|
+
if (model) modelByProvider[provider] = model;
|
|
3593
4146
|
}
|
|
3594
4147
|
return {
|
|
3595
4148
|
providerOrder,
|
|
@@ -3615,70 +4168,142 @@ var init_resolver = __esm({
|
|
|
3615
4168
|
|
|
3616
4169
|
// src/ai/llm/failover.ts
|
|
3617
4170
|
async function completeOnProvider(provider, model, apiKey, req) {
|
|
3618
|
-
|
|
3619
|
-
|
|
4171
|
+
const spec = getProviderSpec(provider);
|
|
4172
|
+
if (!spec) {
|
|
4173
|
+
throw new LlmError("UNKNOWN", `Unknown provider "${provider}" \u2014 run /connect to register it.`, provider);
|
|
4174
|
+
}
|
|
4175
|
+
if (spec.api === "anthropic") {
|
|
4176
|
+
return anthropicComplete(apiKey ?? "", model, req);
|
|
4177
|
+
}
|
|
4178
|
+
return openaiCompatComplete(provider, spec.base_url, apiKey, model, req);
|
|
4179
|
+
}
|
|
4180
|
+
function usableKey(provider, ctx) {
|
|
4181
|
+
const spec = getProviderSpec(provider);
|
|
4182
|
+
if (!spec) return { ok: false };
|
|
4183
|
+
const apiKey = getApiKeyForProvider(provider, ctx);
|
|
4184
|
+
if (spec.requires_key && !apiKey) return { ok: false };
|
|
4185
|
+
return { ok: true, apiKey };
|
|
4186
|
+
}
|
|
4187
|
+
async function resolveModelWithDiscovery(provider, cfg, opts) {
|
|
4188
|
+
const known = cfg.modelByProvider[provider];
|
|
4189
|
+
if (known) return known;
|
|
4190
|
+
const override = overrideForProvider(opts.modelOverride, provider, cfg.activeProvider);
|
|
4191
|
+
const direct = resolveModelSafe(provider, cfg.tier, override);
|
|
4192
|
+
if (direct) return direct;
|
|
4193
|
+
await refreshProviderModels(provider, { apiKey: opts.apiKey, force: true }).catch(() => null);
|
|
4194
|
+
return resolveModelSafe(provider, cfg.tier, override);
|
|
4195
|
+
}
|
|
4196
|
+
function stripTools(req) {
|
|
4197
|
+
const { tools: _tools, ...rest } = req;
|
|
4198
|
+
return rest;
|
|
3620
4199
|
}
|
|
3621
4200
|
async function completeWithFailover(req, opts = {}) {
|
|
3622
|
-
const
|
|
4201
|
+
const cfg = resolveCompletionContext(req.surface, {
|
|
3623
4202
|
max_tokens: req.max_tokens,
|
|
3624
4203
|
tier: opts.tier,
|
|
3625
4204
|
modelOverride: opts.modelOverride,
|
|
3626
4205
|
ctx: opts.ctx
|
|
3627
4206
|
});
|
|
3628
|
-
const providers =
|
|
4207
|
+
const providers = cfg.providerOrder;
|
|
3629
4208
|
if (providers.length === 0) {
|
|
3630
|
-
throw new Error(
|
|
4209
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
3631
4210
|
}
|
|
4211
|
+
const notices = [];
|
|
3632
4212
|
let lastError;
|
|
3633
4213
|
let failoverFrom;
|
|
4214
|
+
const buildMeta = (provider, model, response) => ({
|
|
4215
|
+
provider_used: provider,
|
|
4216
|
+
model_used: model,
|
|
4217
|
+
...response.token_usage ?? {},
|
|
4218
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
4219
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
4220
|
+
});
|
|
3634
4221
|
for (let i = 0; i < providers.length; i++) {
|
|
3635
4222
|
const provider = providers[i];
|
|
3636
|
-
const
|
|
3637
|
-
if (!
|
|
3638
|
-
let model =
|
|
4223
|
+
const key = usableKey(provider, opts.ctx);
|
|
4224
|
+
if (!key.ok) continue;
|
|
4225
|
+
let model = await resolveModelWithDiscovery(provider, cfg, {
|
|
4226
|
+
modelOverride: opts.modelOverride,
|
|
4227
|
+
apiKey: key.apiKey
|
|
4228
|
+
});
|
|
4229
|
+
if (!model) {
|
|
4230
|
+
lastError = new LlmError(
|
|
4231
|
+
"MODEL_NOT_FOUND",
|
|
4232
|
+
`No models known for provider "${provider}". Run /connect or /model refresh.`,
|
|
4233
|
+
provider
|
|
4234
|
+
);
|
|
4235
|
+
continue;
|
|
4236
|
+
}
|
|
4237
|
+
let effectiveReq = req;
|
|
4238
|
+
if (req.tools?.length && modelHasNoToolsQuirk(provider, model)) {
|
|
4239
|
+
effectiveReq = stripTools(req);
|
|
4240
|
+
notices.push(`${model} doesn't support tool calling \u2014 answering without live data tools`);
|
|
4241
|
+
}
|
|
3639
4242
|
try {
|
|
3640
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
3641
|
-
const meta =
|
|
3642
|
-
provider_used: provider,
|
|
3643
|
-
model_used: model,
|
|
3644
|
-
...response.token_usage ?? {},
|
|
3645
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
3646
|
-
};
|
|
4243
|
+
const response = await completeOnProvider(provider, model, key.apiKey, effectiveReq);
|
|
4244
|
+
const meta = buildMeta(provider, model, response);
|
|
3647
4245
|
recordLlmUsage(response.token_usage);
|
|
3648
4246
|
return { response, meta };
|
|
3649
4247
|
} catch (err) {
|
|
3650
|
-
|
|
4248
|
+
let llmErr = err;
|
|
3651
4249
|
if (llmErr.name !== "LlmError") throw err;
|
|
3652
4250
|
lastError = llmErr;
|
|
3653
|
-
if (llmErr.code === "
|
|
3654
|
-
|
|
4251
|
+
if (llmErr.code === "TOOLS_UNSUPPORTED" && effectiveReq.tools?.length) {
|
|
4252
|
+
markModelNoTools(provider, model);
|
|
4253
|
+
notices.push(`${model} doesn't support tool calling \u2014 retrying without live data tools`);
|
|
3655
4254
|
try {
|
|
3656
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
3657
|
-
const meta =
|
|
3658
|
-
provider_used: provider,
|
|
3659
|
-
model_used: model,
|
|
3660
|
-
...response.token_usage ?? {},
|
|
3661
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
3662
|
-
};
|
|
4255
|
+
const response = await completeOnProvider(provider, model, key.apiKey, stripTools(effectiveReq));
|
|
4256
|
+
const meta = buildMeta(provider, model, response);
|
|
3663
4257
|
recordLlmUsage(response.token_usage);
|
|
3664
4258
|
return { response, meta };
|
|
3665
4259
|
} catch (retryErr) {
|
|
3666
4260
|
const retryLlm = retryErr;
|
|
3667
|
-
if (retryLlm.name
|
|
3668
|
-
|
|
4261
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
4262
|
+
lastError = retryLlm;
|
|
4263
|
+
llmErr = retryLlm;
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
if (llmErr.code === "MODEL_NOT_FOUND") {
|
|
4267
|
+
const healed = await healModelNotFound({
|
|
4268
|
+
provider,
|
|
4269
|
+
tier: cfg.tier,
|
|
4270
|
+
deadModel: model,
|
|
4271
|
+
apiKey: key.apiKey,
|
|
4272
|
+
ctx: opts.ctx
|
|
4273
|
+
}).catch(() => null);
|
|
4274
|
+
if (healed) {
|
|
4275
|
+
notices.push(healed.notice);
|
|
4276
|
+
model = healed.model;
|
|
4277
|
+
let retryReq = req;
|
|
4278
|
+
if (req.tools?.length && modelHasNoToolsQuirk(provider, model)) {
|
|
4279
|
+
retryReq = stripTools(req);
|
|
4280
|
+
notices.push(`${model} doesn't support tool calling \u2014 answering without live data tools`);
|
|
4281
|
+
}
|
|
4282
|
+
try {
|
|
4283
|
+
const response = await completeOnProvider(provider, model, key.apiKey, retryReq);
|
|
4284
|
+
const meta = buildMeta(provider, model, response);
|
|
4285
|
+
recordLlmUsage(response.token_usage);
|
|
4286
|
+
return { response, meta };
|
|
4287
|
+
} catch (retryErr) {
|
|
4288
|
+
const retryLlm = retryErr;
|
|
4289
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
4290
|
+
lastError = retryLlm;
|
|
4291
|
+
llmErr = retryLlm;
|
|
4292
|
+
}
|
|
3669
4293
|
}
|
|
3670
4294
|
}
|
|
3671
4295
|
if (!isFailoverEligible(llmErr.code)) throw llmErr;
|
|
3672
4296
|
const next = providers[i + 1];
|
|
3673
4297
|
if (next) {
|
|
3674
4298
|
failoverFrom = failoverFrom ?? provider;
|
|
4299
|
+
notices.push(`${provider} unavailable (${llmErr.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
3675
4300
|
opts.onFailover?.(provider, next, llmErr.code);
|
|
3676
4301
|
continue;
|
|
3677
4302
|
}
|
|
3678
4303
|
throw llmErr;
|
|
3679
4304
|
}
|
|
3680
4305
|
}
|
|
3681
|
-
throw lastError ?? new Error(
|
|
4306
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
3682
4307
|
}
|
|
3683
4308
|
async function* streamWithFailover(req, opts = {}) {
|
|
3684
4309
|
const cfg = resolveCompletionContext(req.surface, {
|
|
@@ -3687,23 +4312,47 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
3687
4312
|
modelOverride: opts.modelOverride,
|
|
3688
4313
|
ctx: opts.ctx
|
|
3689
4314
|
});
|
|
3690
|
-
const providers =
|
|
4315
|
+
const providers = cfg.providerOrder;
|
|
3691
4316
|
if (providers.length === 0) {
|
|
3692
|
-
throw new Error(
|
|
4317
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
3693
4318
|
}
|
|
4319
|
+
const notices = [];
|
|
3694
4320
|
let lastError;
|
|
3695
4321
|
let failoverFrom;
|
|
4322
|
+
async function* streamOnProvider(provider, model, apiKey) {
|
|
4323
|
+
const spec = getProviderSpec(provider);
|
|
4324
|
+
if (!spec) {
|
|
4325
|
+
throw new LlmError("UNKNOWN", `Unknown provider "${provider}" \u2014 run /connect to register it.`, provider);
|
|
4326
|
+
}
|
|
4327
|
+
if (spec.api === "anthropic") {
|
|
4328
|
+
yield* anthropicStream(apiKey ?? "", model, req);
|
|
4329
|
+
return;
|
|
4330
|
+
}
|
|
4331
|
+
yield* openaiCompatStream(provider, spec.base_url, apiKey, model, req);
|
|
4332
|
+
}
|
|
3696
4333
|
for (let i = 0; i < providers.length; i++) {
|
|
3697
4334
|
const provider = providers[i];
|
|
3698
|
-
const
|
|
3699
|
-
if (!
|
|
3700
|
-
|
|
3701
|
-
|
|
4335
|
+
const key = usableKey(provider, opts.ctx);
|
|
4336
|
+
if (!key.ok) continue;
|
|
4337
|
+
let model = await resolveModelWithDiscovery(provider, cfg, {
|
|
4338
|
+
modelOverride: opts.modelOverride,
|
|
4339
|
+
apiKey: key.apiKey
|
|
4340
|
+
});
|
|
4341
|
+
if (!model) {
|
|
4342
|
+
lastError = new LlmError(
|
|
4343
|
+
"MODEL_NOT_FOUND",
|
|
4344
|
+
`No models known for provider "${provider}". Run /connect or /model refresh.`,
|
|
4345
|
+
provider
|
|
4346
|
+
);
|
|
4347
|
+
continue;
|
|
4348
|
+
}
|
|
4349
|
+
let yieldedAny = false;
|
|
4350
|
+
const attempt = async function* (attemptModel) {
|
|
3702
4351
|
let fullText = "";
|
|
3703
|
-
const
|
|
3704
|
-
for await (const event of streamFn(apiKey, model, req)) {
|
|
4352
|
+
for await (const event of streamOnProvider(provider, attemptModel, key.apiKey)) {
|
|
3705
4353
|
if (event.type === "text_delta") {
|
|
3706
4354
|
fullText += event.text;
|
|
4355
|
+
yieldedAny = true;
|
|
3707
4356
|
yield event;
|
|
3708
4357
|
}
|
|
3709
4358
|
}
|
|
@@ -3711,10 +4360,11 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
3711
4360
|
recordLlmUsage({ input_tokens: 0, output_tokens: estimatedOut });
|
|
3712
4361
|
const meta = {
|
|
3713
4362
|
provider_used: provider,
|
|
3714
|
-
model_used:
|
|
4363
|
+
model_used: attemptModel,
|
|
3715
4364
|
input_tokens: 0,
|
|
3716
4365
|
output_tokens: estimatedOut,
|
|
3717
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
4366
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
4367
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
3718
4368
|
};
|
|
3719
4369
|
yield {
|
|
3720
4370
|
type: "done",
|
|
@@ -3726,32 +4376,66 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
3726
4376
|
},
|
|
3727
4377
|
meta
|
|
3728
4378
|
};
|
|
4379
|
+
};
|
|
4380
|
+
try {
|
|
4381
|
+
yield* attempt(model);
|
|
3729
4382
|
return;
|
|
3730
4383
|
} catch (err) {
|
|
3731
4384
|
const llmErr = err;
|
|
3732
4385
|
if (llmErr.name !== "LlmError") throw err;
|
|
3733
4386
|
lastError = llmErr;
|
|
3734
|
-
if (
|
|
4387
|
+
if (yieldedAny) throw llmErr;
|
|
4388
|
+
if (llmErr.code === "MODEL_NOT_FOUND") {
|
|
4389
|
+
const healed = await healModelNotFound({
|
|
4390
|
+
provider,
|
|
4391
|
+
tier: cfg.tier,
|
|
4392
|
+
deadModel: model,
|
|
4393
|
+
apiKey: key.apiKey,
|
|
4394
|
+
ctx: opts.ctx
|
|
4395
|
+
}).catch(() => null);
|
|
4396
|
+
if (healed) {
|
|
4397
|
+
notices.push(healed.notice);
|
|
4398
|
+
model = healed.model;
|
|
4399
|
+
try {
|
|
4400
|
+
yield* attempt(model);
|
|
4401
|
+
return;
|
|
4402
|
+
} catch (retryErr) {
|
|
4403
|
+
const retryLlm = retryErr;
|
|
4404
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
4405
|
+
lastError = retryLlm;
|
|
4406
|
+
if (yieldedAny) throw retryLlm;
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
if (!isFailoverEligible(lastError.code)) throw lastError;
|
|
3735
4411
|
const next = providers[i + 1];
|
|
3736
4412
|
if (next) {
|
|
3737
4413
|
failoverFrom = failoverFrom ?? provider;
|
|
3738
|
-
|
|
4414
|
+
notices.push(`${provider} unavailable (${lastError.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
4415
|
+
opts.onFailover?.(provider, next, lastError.code);
|
|
3739
4416
|
continue;
|
|
3740
4417
|
}
|
|
3741
|
-
throw
|
|
4418
|
+
throw lastError;
|
|
3742
4419
|
}
|
|
3743
4420
|
}
|
|
3744
|
-
throw lastError ?? new Error(
|
|
4421
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
3745
4422
|
}
|
|
4423
|
+
var NO_PROVIDER_MESSAGE;
|
|
3746
4424
|
var init_failover = __esm({
|
|
3747
4425
|
"src/ai/llm/failover.ts"() {
|
|
3748
4426
|
"use strict";
|
|
3749
4427
|
init_usage_stats();
|
|
3750
4428
|
init_anthropic();
|
|
3751
|
-
|
|
4429
|
+
init_openai_compat();
|
|
3752
4430
|
init_catalog();
|
|
4431
|
+
init_discovery();
|
|
3753
4432
|
init_errors();
|
|
4433
|
+
init_heal();
|
|
4434
|
+
init_models_cache();
|
|
4435
|
+
init_providers();
|
|
4436
|
+
init_types();
|
|
3754
4437
|
init_resolver();
|
|
4438
|
+
NO_PROVIDER_MESSAGE = "No LLM provider configured. Run /connect and paste any API key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
3755
4439
|
}
|
|
3756
4440
|
});
|
|
3757
4441
|
|
|
@@ -3804,7 +4488,13 @@ function resolvePrimaryApiKey(ctx) {
|
|
|
3804
4488
|
if (isInvestigationMode(ctx)) {
|
|
3805
4489
|
return getInvestigationApiKey(primary) ?? getInvestigationApiKey("anthropic") ?? getInvestigationApiKey("openai");
|
|
3806
4490
|
}
|
|
3807
|
-
|
|
4491
|
+
const primaryKey = getProviderApiKey(primary);
|
|
4492
|
+
if (primaryKey) return primaryKey;
|
|
4493
|
+
for (const provider of getAvailableProviders()) {
|
|
4494
|
+
const key = getProviderApiKey(provider);
|
|
4495
|
+
if (key) return key;
|
|
4496
|
+
}
|
|
4497
|
+
return void 0;
|
|
3808
4498
|
}
|
|
3809
4499
|
function canUseReplAi(ctx) {
|
|
3810
4500
|
if (!ctx) return false;
|
|
@@ -3815,25 +4505,21 @@ function canUseReplAi(ctx) {
|
|
|
3815
4505
|
}
|
|
3816
4506
|
function assertReplAi(ctx) {
|
|
3817
4507
|
if (!ctx) {
|
|
3818
|
-
throw new Error(
|
|
3819
|
-
"AI features require stored API keys. Run `ntrp`, then /config set api-key or /config set openai-api-key."
|
|
3820
|
-
);
|
|
4508
|
+
throw new Error(`AI features require stored API keys. Run \`ntrp\`, then /connect.`);
|
|
3821
4509
|
}
|
|
3822
4510
|
if (!canUseReplAi(ctx)) {
|
|
3823
4511
|
if (!hasAnyLlmProvider()) {
|
|
3824
|
-
throw new Error(
|
|
3825
|
-
"No LLM API key configured. Run: /config set api-key (Anthropic) and/or /config set openai-api-key"
|
|
3826
|
-
);
|
|
4512
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
3827
4513
|
}
|
|
3828
4514
|
throw new Error(
|
|
3829
4515
|
"AI features run only in the interactive REPL or headless mode with stored keys."
|
|
3830
4516
|
);
|
|
3831
4517
|
}
|
|
3832
4518
|
const key = resolvePrimaryApiKey(ctx);
|
|
3833
|
-
if (!key) {
|
|
3834
|
-
throw new Error(
|
|
4519
|
+
if (!key && !hasKeylessConfiguredProvider()) {
|
|
4520
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
3835
4521
|
}
|
|
3836
|
-
return key;
|
|
4522
|
+
return key ?? "";
|
|
3837
4523
|
}
|
|
3838
4524
|
function hasEnvApiKeyHint() {
|
|
3839
4525
|
return !!(process.env.ANTHROPIC_API_KEY ?? process.env.NTRP_API_KEY ?? process.env.OPENAI_API_KEY);
|
|
@@ -3846,10 +4532,12 @@ function describeLlmReadiness() {
|
|
|
3846
4532
|
openai: providers.includes("openai")
|
|
3847
4533
|
};
|
|
3848
4534
|
}
|
|
4535
|
+
var NO_KEY_MESSAGE;
|
|
3849
4536
|
var init_gate = __esm({
|
|
3850
4537
|
"src/ai/llm/gate.ts"() {
|
|
3851
4538
|
"use strict";
|
|
3852
4539
|
init_llm_config();
|
|
4540
|
+
NO_KEY_MESSAGE = "No LLM API key configured. Run /connect and paste any provider's key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
3853
4541
|
}
|
|
3854
4542
|
});
|
|
3855
4543
|
|
|
@@ -4159,9 +4847,9 @@ var init_tool_schemas = __esm({
|
|
|
4159
4847
|
});
|
|
4160
4848
|
|
|
4161
4849
|
// src/ai/privacy.ts
|
|
4162
|
-
import { existsSync as
|
|
4850
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, appendFileSync } from "fs";
|
|
4163
4851
|
import { homedir as homedir3 } from "os";
|
|
4164
|
-
import { join as
|
|
4852
|
+
import { join as join9 } from "path";
|
|
4165
4853
|
function stripPII(obj) {
|
|
4166
4854
|
if (obj === null || obj === void 0) return obj;
|
|
4167
4855
|
if (typeof obj !== "object") return obj;
|
|
@@ -4176,14 +4864,14 @@ function stripPII(obj) {
|
|
|
4176
4864
|
return out;
|
|
4177
4865
|
}
|
|
4178
4866
|
function ensureAuditDir() {
|
|
4179
|
-
if (!
|
|
4867
|
+
if (!existsSync9(AUDIT_DIR)) {
|
|
4180
4868
|
mkdirSync6(AUDIT_DIR, { recursive: true });
|
|
4181
4869
|
}
|
|
4182
4870
|
}
|
|
4183
4871
|
function logToolCall(entry) {
|
|
4184
4872
|
ensureAuditDir();
|
|
4185
4873
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4186
|
-
const path =
|
|
4874
|
+
const path = join9(AUDIT_DIR, `agentic-${date}.jsonl`);
|
|
4187
4875
|
appendFileSync(path, JSON.stringify(entry) + "\n");
|
|
4188
4876
|
}
|
|
4189
4877
|
var PII_FIELDS, AUDIT_DIR;
|
|
@@ -4207,7 +4895,7 @@ var init_privacy = __esm({
|
|
|
4207
4895
|
"raw_data",
|
|
4208
4896
|
"metadata"
|
|
4209
4897
|
]);
|
|
4210
|
-
AUDIT_DIR =
|
|
4898
|
+
AUDIT_DIR = join9(homedir3(), ".ntrp", "audit");
|
|
4211
4899
|
}
|
|
4212
4900
|
});
|
|
4213
4901
|
|
|
@@ -4434,16 +5122,16 @@ var init_metrics_benchmarks = __esm({
|
|
|
4434
5122
|
});
|
|
4435
5123
|
|
|
4436
5124
|
// src/config/profile.ts
|
|
4437
|
-
import { readFileSync as
|
|
4438
|
-
import { join as
|
|
5125
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
5126
|
+
import { join as join10 } from "path";
|
|
4439
5127
|
function isProfileConfigured(profile = loadProfile()) {
|
|
4440
5128
|
if (!profile) return false;
|
|
4441
5129
|
return profile.company_name.trim().length > 0;
|
|
4442
5130
|
}
|
|
4443
5131
|
function loadProfile() {
|
|
4444
|
-
if (!
|
|
5132
|
+
if (!existsSync10(PROFILE_PATH)) return null;
|
|
4445
5133
|
try {
|
|
4446
|
-
const parsed = JSON.parse(
|
|
5134
|
+
const parsed = JSON.parse(readFileSync9(PROFILE_PATH, "utf-8"));
|
|
4447
5135
|
if (!parsed || typeof parsed !== "object") return null;
|
|
4448
5136
|
return parsed;
|
|
4449
5137
|
} catch {
|
|
@@ -4456,7 +5144,7 @@ var init_profile = __esm({
|
|
|
4456
5144
|
"use strict";
|
|
4457
5145
|
init_store();
|
|
4458
5146
|
NTRP_DIR3 = ntrpHome();
|
|
4459
|
-
PROFILE_PATH =
|
|
5147
|
+
PROFILE_PATH = join10(NTRP_DIR3, "profile.json");
|
|
4460
5148
|
}
|
|
4461
5149
|
});
|
|
4462
5150
|
|
|
@@ -7369,16 +8057,16 @@ var init_complete = __esm({
|
|
|
7369
8057
|
});
|
|
7370
8058
|
|
|
7371
8059
|
// src/data/playbook.ts
|
|
7372
|
-
import { existsSync as
|
|
7373
|
-
import { join as
|
|
8060
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, appendFileSync as appendFileSync2 } from "fs";
|
|
8061
|
+
import { join as join11 } from "path";
|
|
7374
8062
|
function playsPath() {
|
|
7375
|
-
return
|
|
8063
|
+
return join11(getMemoryDir(), PLAYS_FILE);
|
|
7376
8064
|
}
|
|
7377
8065
|
function getCustomPlays() {
|
|
7378
8066
|
const path = playsPath();
|
|
7379
|
-
if (!
|
|
8067
|
+
if (!existsSync11(path)) return [];
|
|
7380
8068
|
const out = [];
|
|
7381
|
-
for (const line of
|
|
8069
|
+
for (const line of readFileSync10(path, "utf-8").split("\n")) {
|
|
7382
8070
|
const trimmed = line.trim();
|
|
7383
8071
|
if (!trimmed) continue;
|
|
7384
8072
|
try {
|
|
@@ -7805,7 +8493,7 @@ async function runMetricsAnalysis(options = {}) {
|
|
|
7805
8493
|
if (options.findings) {
|
|
7806
8494
|
if (!canUseReplAi(options.ctx)) {
|
|
7807
8495
|
throw new Error(
|
|
7808
|
-
"AI metrics findings require stored API keys. Run `ntrp`,
|
|
8496
|
+
"AI metrics findings require stored API keys. Run `ntrp`, then /connect (any provider key), then /metrics --findings."
|
|
7809
8497
|
);
|
|
7810
8498
|
}
|
|
7811
8499
|
options.onProgress?.("findings");
|
|
@@ -8160,6 +8848,9 @@ function formatLlmAttribution(meta) {
|
|
|
8160
8848
|
return line;
|
|
8161
8849
|
}
|
|
8162
8850
|
function printLlmAttribution(meta) {
|
|
8851
|
+
for (const notice of meta.notices ?? []) {
|
|
8852
|
+
console.log(chalk8.dim(` ${notice}`));
|
|
8853
|
+
}
|
|
8163
8854
|
const line = formatLlmAttribution(meta);
|
|
8164
8855
|
if (line) console.log(chalk8.dim(` ${line}`));
|
|
8165
8856
|
}
|
|
@@ -8432,6 +9123,7 @@ async function renderDiagnoseStream(options) {
|
|
|
8432
9123
|
let modelUsed = "";
|
|
8433
9124
|
let providerUsed;
|
|
8434
9125
|
let failover;
|
|
9126
|
+
let notices;
|
|
8435
9127
|
let rawPrompt = "";
|
|
8436
9128
|
try {
|
|
8437
9129
|
for await (const event of runFindings(fullResult)) {
|
|
@@ -8447,6 +9139,7 @@ async function renderDiagnoseStream(options) {
|
|
|
8447
9139
|
modelUsed = event.model_used;
|
|
8448
9140
|
providerUsed = event.provider_used;
|
|
8449
9141
|
failover = event.failover;
|
|
9142
|
+
notices = event.usage?.notices;
|
|
8450
9143
|
rawPrompt = event.raw_prompt;
|
|
8451
9144
|
}
|
|
8452
9145
|
}
|
|
@@ -8470,7 +9163,8 @@ async function renderDiagnoseStream(options) {
|
|
|
8470
9163
|
printLlmAttribution({
|
|
8471
9164
|
model_used: modelUsed,
|
|
8472
9165
|
provider_used: providerUsed,
|
|
8473
|
-
failover
|
|
9166
|
+
failover,
|
|
9167
|
+
notices
|
|
8474
9168
|
});
|
|
8475
9169
|
} catch (err) {
|
|
8476
9170
|
findingsSpinner.fail(deep ? "Agentic investigation failed" : "AI findings failed");
|
|
@@ -8782,7 +9476,8 @@ async function* streamFindings(input, ctx) {
|
|
|
8782
9476
|
findings,
|
|
8783
9477
|
model_used: meta.model_used,
|
|
8784
9478
|
provider_used: meta.provider_used,
|
|
8785
|
-
raw_prompt: userMessage
|
|
9479
|
+
raw_prompt: userMessage,
|
|
9480
|
+
usage: meta
|
|
8786
9481
|
};
|
|
8787
9482
|
}
|
|
8788
9483
|
}
|
|
@@ -9000,7 +9695,7 @@ async function runDiagnosis(options = {}) {
|
|
|
9000
9695
|
if (options.findings) {
|
|
9001
9696
|
if (!canUseReplAi(options.ctx)) {
|
|
9002
9697
|
throw new Error(
|
|
9003
|
-
"AI findings require stored API keys. Run `ntrp`, then /
|
|
9698
|
+
"AI findings require stored API keys. Run `ntrp`, then /connect (any provider key), and use /diagnose --findings."
|
|
9004
9699
|
);
|
|
9005
9700
|
}
|
|
9006
9701
|
if (options.deep) {
|
|
@@ -9143,7 +9838,7 @@ async function handler(args, ctx) {
|
|
|
9143
9838
|
console.log();
|
|
9144
9839
|
console.log(" " + chalk11.red("AI findings run only in the interactive REPL."));
|
|
9145
9840
|
console.log(" " + chalk11.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
|
|
9146
|
-
console.log(" " + chalk11.dim("Start with ") + paint("accent", "ntrp") + chalk11.dim(",
|
|
9841
|
+
console.log(" " + chalk11.dim("Start with ") + paint("accent", "ntrp") + chalk11.dim(", run ") + paint("accent", "/connect") + chalk11.dim(" (any provider key), then /diagnose --findings."));
|
|
9147
9842
|
console.log();
|
|
9148
9843
|
return;
|
|
9149
9844
|
}
|
|
@@ -12148,18 +12843,18 @@ var init_generator = __esm({
|
|
|
12148
12843
|
});
|
|
12149
12844
|
|
|
12150
12845
|
// src/demo/taxonomy-cache.ts
|
|
12151
|
-
import { readFileSync as
|
|
12846
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync12, mkdirSync as mkdirSync8, unlinkSync as unlinkSync3 } from "fs";
|
|
12152
12847
|
import { homedir as homedir4 } from "os";
|
|
12153
|
-
import { join as
|
|
12848
|
+
import { join as join12 } from "path";
|
|
12154
12849
|
function ensureDir5() {
|
|
12155
|
-
if (!
|
|
12850
|
+
if (!existsSync12(NTRP_DIR4)) {
|
|
12156
12851
|
mkdirSync8(NTRP_DIR4, { recursive: true });
|
|
12157
12852
|
}
|
|
12158
12853
|
}
|
|
12159
12854
|
function loadCachedTaxonomy(profile) {
|
|
12160
|
-
if (!
|
|
12855
|
+
if (!existsSync12(TAXONOMY_PATH)) return null;
|
|
12161
12856
|
try {
|
|
12162
|
-
const parsed = JSON.parse(
|
|
12857
|
+
const parsed = JSON.parse(readFileSync11(TAXONOMY_PATH, "utf-8"));
|
|
12163
12858
|
if (!parsed || typeof parsed !== "object") return null;
|
|
12164
12859
|
if (parsed.profile_updated_at !== profile.updated_at) return null;
|
|
12165
12860
|
return parsed;
|
|
@@ -12169,14 +12864,14 @@ function loadCachedTaxonomy(profile) {
|
|
|
12169
12864
|
}
|
|
12170
12865
|
function saveCachedTaxonomy(taxonomy) {
|
|
12171
12866
|
ensureDir5();
|
|
12172
|
-
|
|
12867
|
+
writeFileSync9(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
|
|
12173
12868
|
}
|
|
12174
12869
|
var NTRP_DIR4, TAXONOMY_PATH;
|
|
12175
12870
|
var init_taxonomy_cache = __esm({
|
|
12176
12871
|
"src/demo/taxonomy-cache.ts"() {
|
|
12177
12872
|
"use strict";
|
|
12178
|
-
NTRP_DIR4 =
|
|
12179
|
-
TAXONOMY_PATH =
|
|
12873
|
+
NTRP_DIR4 = join12(homedir4(), ".ntrp");
|
|
12874
|
+
TAXONOMY_PATH = join12(NTRP_DIR4, "demo-taxonomy.json");
|
|
12180
12875
|
}
|
|
12181
12876
|
});
|
|
12182
12877
|
|
|
@@ -12505,8 +13200,8 @@ function markFailure(ctx) {
|
|
|
12505
13200
|
}
|
|
12506
13201
|
async function loadOrBuildTaxonomy(profile, forceRegen, ctx) {
|
|
12507
13202
|
if (!forceRegen) {
|
|
12508
|
-
const
|
|
12509
|
-
if (
|
|
13203
|
+
const cached2 = loadCachedTaxonomy(profile);
|
|
13204
|
+
if (cached2) return cached2;
|
|
12510
13205
|
}
|
|
12511
13206
|
const spinnerText = forceRegen ? "Rebuilding market taxonomy\u2026" : "Researching your market taxonomy\u2026";
|
|
12512
13207
|
const spinner = ora4({ text: spinnerText, discardStdin: false }).start();
|
|
@@ -12609,7 +13304,7 @@ __export(ingest_exports, {
|
|
|
12609
13304
|
});
|
|
12610
13305
|
import chalk15 from "chalk";
|
|
12611
13306
|
import ora5 from "ora";
|
|
12612
|
-
import { readFileSync as
|
|
13307
|
+
import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
|
|
12613
13308
|
import { basename as basename2 } from "path";
|
|
12614
13309
|
async function handler3(args, ctx) {
|
|
12615
13310
|
const { positional, flags } = parseArgs(args, [
|
|
@@ -12633,7 +13328,7 @@ async function handler3(args, ctx) {
|
|
|
12633
13328
|
console.error(chalk15.dim(" /ingest --demo [--scenario <name>]"));
|
|
12634
13329
|
process.exit(1);
|
|
12635
13330
|
}
|
|
12636
|
-
if (!
|
|
13331
|
+
if (!existsSync13(file)) {
|
|
12637
13332
|
console.error(chalk15.red(` File not found: ${file}`));
|
|
12638
13333
|
process.exit(1);
|
|
12639
13334
|
}
|
|
@@ -12651,7 +13346,7 @@ async function handler3(args, ctx) {
|
|
|
12651
13346
|
try {
|
|
12652
13347
|
await initSchema();
|
|
12653
13348
|
spinner.text = "Parsing CSV...";
|
|
12654
|
-
const content =
|
|
13349
|
+
const content = readFileSync12(file, "utf-8");
|
|
12655
13350
|
const { rows, headers } = parseCSV(content);
|
|
12656
13351
|
if (rows.length === 0) {
|
|
12657
13352
|
spinner.fail("CSV is empty");
|
|
@@ -12767,7 +13462,7 @@ __export(ingest_chat_exports, {
|
|
|
12767
13462
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
12768
13463
|
looksLikeFilePath: () => looksLikeFilePath
|
|
12769
13464
|
});
|
|
12770
|
-
import { existsSync as
|
|
13465
|
+
import { existsSync as existsSync14 } from "fs";
|
|
12771
13466
|
import { basename as basename3, resolve as resolve4 } from "path";
|
|
12772
13467
|
import { homedir as homedir5 } from "os";
|
|
12773
13468
|
import chalk16 from "chalk";
|
|
@@ -12787,11 +13482,11 @@ function extractFilePath(input) {
|
|
|
12787
13482
|
const m = trimmed.match(re);
|
|
12788
13483
|
if (m?.[1]) {
|
|
12789
13484
|
const p = expandPath(m[1]);
|
|
12790
|
-
if (
|
|
13485
|
+
if (existsSync14(p)) return p;
|
|
12791
13486
|
}
|
|
12792
13487
|
if (!m?.[1] && re.test(trimmed) && trimmed.toLowerCase().endsWith(".csv")) {
|
|
12793
13488
|
const p = expandPath(trimmed.replace(/^["']|["']$/g, ""));
|
|
12794
|
-
if (
|
|
13489
|
+
if (existsSync14(p)) return p;
|
|
12795
13490
|
}
|
|
12796
13491
|
}
|
|
12797
13492
|
return null;
|
|
@@ -12821,12 +13516,12 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
12821
13516
|
}
|
|
12822
13517
|
const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
|
|
12823
13518
|
const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
|
|
12824
|
-
const { readFileSync:
|
|
13519
|
+
const { readFileSync: readFileSync16 } = await import("fs");
|
|
12825
13520
|
const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
|
|
12826
13521
|
const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
|
|
12827
13522
|
let headerCheckFailed = false;
|
|
12828
13523
|
try {
|
|
12829
|
-
const raw =
|
|
13524
|
+
const raw = readFileSync16(filePath, "utf-8");
|
|
12830
13525
|
const { headers } = parseCSV2(raw);
|
|
12831
13526
|
const detected = detectEntityType2(headers, "unknown");
|
|
12832
13527
|
if (!detected) headerCheckFailed = true;
|
|
@@ -13194,15 +13889,18 @@ function inferHandoffTarget(input) {
|
|
|
13194
13889
|
return "plan";
|
|
13195
13890
|
}
|
|
13196
13891
|
function isShipIntent(input) {
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
);
|
|
13892
|
+
const line = input.trim();
|
|
13893
|
+
if (/\?\s*$/.test(line) || QUESTION_LEAD_RE.test(line)) return false;
|
|
13894
|
+
return SHIP_INTENT_RE.test(line);
|
|
13200
13895
|
}
|
|
13896
|
+
var QUESTION_LEAD_RE, SHIP_INTENT_RE;
|
|
13201
13897
|
var init_handoff_draft = __esm({
|
|
13202
13898
|
"src/conversation/handoff-draft.ts"() {
|
|
13203
13899
|
"use strict";
|
|
13204
13900
|
init_profile();
|
|
13205
13901
|
init_session_analysis();
|
|
13902
|
+
QUESTION_LEAD_RE = /^\s*(what|why|how|when|where|who|which|is|are|was|were|do|does|did|explain|tell me|help me understand)\b/i;
|
|
13903
|
+
SHIP_INTENT_RE = /\b(ship|export|deliver|write[- ]?up|board memo|action plan|turn (this|it|that) into|(draft|create|make|build|prepare|generate|send)\s+(me\s+)?(a\s+|the\s+)?hand[- ]?off|hand[- ]?off\s+(prompt|doc|document|plan))\b/i;
|
|
13206
13904
|
}
|
|
13207
13905
|
});
|
|
13208
13906
|
|
|
@@ -14007,8 +14705,8 @@ async function callProvider(texts) {
|
|
|
14007
14705
|
async function embedText(text) {
|
|
14008
14706
|
const key = text.trim();
|
|
14009
14707
|
if (!key) return null;
|
|
14010
|
-
const
|
|
14011
|
-
if (
|
|
14708
|
+
const cached2 = cache.get(key);
|
|
14709
|
+
if (cached2) return cached2;
|
|
14012
14710
|
const result = await callProvider([key]);
|
|
14013
14711
|
const vec = result?.[0] ?? null;
|
|
14014
14712
|
if (vec) cache.set(key, vec);
|
|
@@ -14018,8 +14716,8 @@ async function embedItems(items) {
|
|
|
14018
14716
|
const needing = [];
|
|
14019
14717
|
const out = items.map((it, index) => {
|
|
14020
14718
|
if (it.embedding && it.embedding.length > 0) return { ...it };
|
|
14021
|
-
const
|
|
14022
|
-
if (
|
|
14719
|
+
const cached2 = cache.get(it.text.trim());
|
|
14720
|
+
if (cached2) return { ...it, embedding: cached2 };
|
|
14023
14721
|
needing.push({ index, text: it.text });
|
|
14024
14722
|
return { ...it };
|
|
14025
14723
|
});
|
|
@@ -14049,24 +14747,24 @@ var init_embeddings = __esm({
|
|
|
14049
14747
|
|
|
14050
14748
|
// src/strategies/readers.ts
|
|
14051
14749
|
import { createHash } from "crypto";
|
|
14052
|
-
import { existsSync as
|
|
14750
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
14053
14751
|
import { extname, resolve as resolve5 } from "path";
|
|
14054
14752
|
import { parse as parseYaml } from "yaml";
|
|
14055
14753
|
import { PDFParse } from "pdf-parse";
|
|
14056
14754
|
async function readStrategyFile(pathOrDash) {
|
|
14057
14755
|
if (pathOrDash === "-") {
|
|
14058
|
-
const text2 =
|
|
14756
|
+
const text2 = readFileSync13(0, "utf-8");
|
|
14059
14757
|
return createDocument("stdin", null, text2, {});
|
|
14060
14758
|
}
|
|
14061
14759
|
const sourcePath = resolve5(pathOrDash);
|
|
14062
|
-
if (!
|
|
14760
|
+
if (!existsSync15(sourcePath)) {
|
|
14063
14761
|
throw new NtrpError("strategy_file_not_found", `Strategy file not found: ${pathOrDash}`, 2 /* Usage */);
|
|
14064
14762
|
}
|
|
14065
14763
|
const ext = extname(sourcePath).toLowerCase();
|
|
14066
14764
|
if (ext === ".pdf") {
|
|
14067
14765
|
return readPdf(sourcePath);
|
|
14068
14766
|
}
|
|
14069
|
-
const text =
|
|
14767
|
+
const text = readFileSync13(sourcePath, "utf-8");
|
|
14070
14768
|
if (ext === ".yaml" || ext === ".yml") {
|
|
14071
14769
|
const structured = parseStructuredYaml(text);
|
|
14072
14770
|
return createDocument("yaml", sourcePath, text, structured);
|
|
@@ -14081,7 +14779,7 @@ function readStrategyText(text) {
|
|
|
14081
14779
|
return createDocument("text", null, text, {});
|
|
14082
14780
|
}
|
|
14083
14781
|
async function readPdf(sourcePath) {
|
|
14084
|
-
const data =
|
|
14782
|
+
const data = readFileSync13(sourcePath);
|
|
14085
14783
|
const parser = new PDFParse({ data });
|
|
14086
14784
|
try {
|
|
14087
14785
|
const result = await parser.getText();
|
|
@@ -14270,15 +14968,15 @@ JSON SHAPE:
|
|
|
14270
14968
|
});
|
|
14271
14969
|
|
|
14272
14970
|
// src/strategies/library.ts
|
|
14273
|
-
import { writeFileSync as
|
|
14274
|
-
import { join as
|
|
14971
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
14972
|
+
import { join as join14 } from "path";
|
|
14275
14973
|
import { stringify as stringifyYaml } from "yaml";
|
|
14276
14974
|
function strategyLibraryPath(slug) {
|
|
14277
|
-
return
|
|
14975
|
+
return join14(getStrategiesDir(), `${slug}.md`);
|
|
14278
14976
|
}
|
|
14279
14977
|
function writeStrategyMarkdown(strategy) {
|
|
14280
14978
|
const path = strategyLibraryPath(strategy.slug);
|
|
14281
|
-
|
|
14979
|
+
writeFileSync10(path, renderStrategyMarkdown(strategy), "utf-8");
|
|
14282
14980
|
return path;
|
|
14283
14981
|
}
|
|
14284
14982
|
function renderStrategyMarkdown(strategy) {
|
|
@@ -14352,7 +15050,7 @@ var init_library = __esm({
|
|
|
14352
15050
|
// src/strategies/connectors.ts
|
|
14353
15051
|
import { readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
14354
15052
|
import { homedir as homedir6 } from "os";
|
|
14355
|
-
import { basename as basename4, extname as extname2, join as
|
|
15053
|
+
import { basename as basename4, extname as extname2, join as join15, relative, resolve as resolve6, sep as sep2 } from "path";
|
|
14356
15054
|
function createLocalFolderConnector(options) {
|
|
14357
15055
|
const rootPath = resolveUserPath(options.rootPath);
|
|
14358
15056
|
const name = options.name ?? (basename4(rootPath) || "local");
|
|
@@ -14395,7 +15093,7 @@ function createLocalFolderConnector(options) {
|
|
|
14395
15093
|
}
|
|
14396
15094
|
function walkLocalFolder(rootPath, currentPath, refs, opts) {
|
|
14397
15095
|
for (const entry of readdirSync3(currentPath, { withFileTypes: true })) {
|
|
14398
|
-
const absolutePath =
|
|
15096
|
+
const absolutePath = join15(currentPath, entry.name);
|
|
14399
15097
|
const relativePath = normalizePath(relative(rootPath, absolutePath));
|
|
14400
15098
|
if (entry.isDirectory()) {
|
|
14401
15099
|
if (shouldSkipDirectory(entry.name) || matchesAny(relativePath, opts.excludePatterns)) continue;
|
|
@@ -14459,7 +15157,7 @@ function normalizePath(path) {
|
|
|
14459
15157
|
}
|
|
14460
15158
|
function resolveUserPath(path) {
|
|
14461
15159
|
if (path === "~") return homedir6();
|
|
14462
|
-
if (path.startsWith("~/")) return
|
|
15160
|
+
if (path.startsWith("~/")) return join15(homedir6(), path.slice(2));
|
|
14463
15161
|
return resolve6(path);
|
|
14464
15162
|
}
|
|
14465
15163
|
var DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDED_DIRS;
|
|
@@ -14670,8 +15368,8 @@ init_divergence();
|
|
|
14670
15368
|
|
|
14671
15369
|
// src/memory/store.ts
|
|
14672
15370
|
init_store();
|
|
14673
|
-
import { existsSync as
|
|
14674
|
-
import { join as
|
|
15371
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync11 } from "fs";
|
|
15372
|
+
import { join as join16 } from "path";
|
|
14675
15373
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14676
15374
|
|
|
14677
15375
|
// src/memory/retrieval.ts
|
|
@@ -14801,18 +15499,18 @@ async function rankByRelevance(query, items, topK) {
|
|
|
14801
15499
|
// src/memory/knowledge.ts
|
|
14802
15500
|
init_store();
|
|
14803
15501
|
init_readers();
|
|
14804
|
-
import { existsSync as
|
|
14805
|
-
import { join as
|
|
15502
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14, appendFileSync as appendFileSync3, readdirSync as readdirSync2 } from "fs";
|
|
15503
|
+
import { join as join13 } from "path";
|
|
14806
15504
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
14807
15505
|
var KNOWLEDGE_FILE = "knowledge.jsonl";
|
|
14808
15506
|
function knowledgePath() {
|
|
14809
|
-
return
|
|
15507
|
+
return join13(getMemoryDir(), KNOWLEDGE_FILE);
|
|
14810
15508
|
}
|
|
14811
15509
|
function loadKnowledgeChunks() {
|
|
14812
15510
|
const path = knowledgePath();
|
|
14813
|
-
if (!
|
|
15511
|
+
if (!existsSync16(path)) return [];
|
|
14814
15512
|
const out = [];
|
|
14815
|
-
for (const line of
|
|
15513
|
+
for (const line of readFileSync14(path, "utf-8").split("\n")) {
|
|
14816
15514
|
const trimmed = line.trim();
|
|
14817
15515
|
if (!trimmed) continue;
|
|
14818
15516
|
try {
|
|
@@ -14827,13 +15525,13 @@ function loadKnowledgeChunks() {
|
|
|
14827
15525
|
var FACTS_FILE = "facts.jsonl";
|
|
14828
15526
|
var LEDGER_FILE = "ledger.jsonl";
|
|
14829
15527
|
function memPath(file) {
|
|
14830
|
-
return
|
|
15528
|
+
return join16(getMemoryDir(), file);
|
|
14831
15529
|
}
|
|
14832
15530
|
function readJsonl(file) {
|
|
14833
15531
|
const path = memPath(file);
|
|
14834
|
-
if (!
|
|
15532
|
+
if (!existsSync17(path)) return [];
|
|
14835
15533
|
const out = [];
|
|
14836
|
-
for (const line of
|
|
15534
|
+
for (const line of readFileSync15(path, "utf-8").split("\n")) {
|
|
14837
15535
|
const trimmed = line.trim();
|
|
14838
15536
|
if (!trimmed) continue;
|
|
14839
15537
|
try {
|
|
@@ -14868,7 +15566,7 @@ function loadWinSnippets() {
|
|
|
14868
15566
|
const out = [];
|
|
14869
15567
|
for (const name of readdirSync4(dir)) {
|
|
14870
15568
|
if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
|
|
14871
|
-
const raw =
|
|
15569
|
+
const raw = readFileSync15(join16(dir, name), "utf-8");
|
|
14872
15570
|
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
|
|
14873
15571
|
const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
14874
15572
|
out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
|