@sonnechasser/ntrp 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/index.js +1986 -596
- package/dist/index.js.map +1 -1
- package/dist/investigation/verbosity-cli.js +897 -202
- package/dist/investigation/verbosity-cli.js.map +1 -1
- package/dist/mcp/server.js +922 -222
- package/dist/mcp/server.js.map +1 -1
- package/dist/whimsy/time-bank-smoke.js +1929 -501
- package/dist/whimsy/time-bank-smoke.js.map +1 -1
- package/package.json +2 -1
package/dist/mcp/server.js
CHANGED
|
@@ -4002,10 +4002,198 @@ var init_health_score = __esm({
|
|
|
4002
4002
|
}
|
|
4003
4003
|
});
|
|
4004
4004
|
|
|
4005
|
+
// src/ai/llm/providers.ts
|
|
4006
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
4007
|
+
import { join as join7 } from "path";
|
|
4008
|
+
function providersPath() {
|
|
4009
|
+
return join7(ntrpHome(), "providers.json");
|
|
4010
|
+
}
|
|
4011
|
+
function loadCustomProviders() {
|
|
4012
|
+
if (cachedEntries) return cachedEntries;
|
|
4013
|
+
const path = providersPath();
|
|
4014
|
+
if (!existsSync7(path)) {
|
|
4015
|
+
cachedEntries = [];
|
|
4016
|
+
return cachedEntries;
|
|
4017
|
+
}
|
|
4018
|
+
try {
|
|
4019
|
+
const parsed = JSON.parse(readFileSync6(path, "utf-8"));
|
|
4020
|
+
cachedEntries = Array.isArray(parsed.providers) ? parsed.providers : [];
|
|
4021
|
+
} catch {
|
|
4022
|
+
cachedEntries = [];
|
|
4023
|
+
}
|
|
4024
|
+
return cachedEntries;
|
|
4025
|
+
}
|
|
4026
|
+
function customEntryToSpec(entry) {
|
|
4027
|
+
return {
|
|
4028
|
+
id: entry.id,
|
|
4029
|
+
label: entry.label ?? entry.id,
|
|
4030
|
+
api: "openai-compat",
|
|
4031
|
+
base_url: entry.base_url.replace(/\/+$/, ""),
|
|
4032
|
+
key_prefixes: [],
|
|
4033
|
+
shared_prefixes: [],
|
|
4034
|
+
key_config_name: keyConfigNameFor(entry.id),
|
|
4035
|
+
requires_key: entry.requires_key ?? false,
|
|
4036
|
+
custom: true
|
|
4037
|
+
};
|
|
4038
|
+
}
|
|
4039
|
+
function keyConfigNameFor(providerId) {
|
|
4040
|
+
return providerId === "anthropic" ? "api-key" : `${providerId}-api-key`;
|
|
4041
|
+
}
|
|
4042
|
+
function listProviderSpecs() {
|
|
4043
|
+
const customs = loadCustomProviders();
|
|
4044
|
+
const customById = new Map(customs.map((e) => [e.id, e]));
|
|
4045
|
+
const specs = BUILTIN_SPECS.map((spec) => {
|
|
4046
|
+
const override = customById.get(spec.id);
|
|
4047
|
+
if (override?.base_url) {
|
|
4048
|
+
return { ...spec, base_url: override.base_url.replace(/\/+$/, "") };
|
|
4049
|
+
}
|
|
4050
|
+
return spec;
|
|
4051
|
+
});
|
|
4052
|
+
for (const entry of customs) {
|
|
4053
|
+
if (!BUILTIN_SPECS.some((s) => s.id === entry.id)) {
|
|
4054
|
+
specs.push(customEntryToSpec(entry));
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
return specs;
|
|
4058
|
+
}
|
|
4059
|
+
function getProviderSpec(id) {
|
|
4060
|
+
return listProviderSpecs().find((s) => s.id === id);
|
|
4061
|
+
}
|
|
4062
|
+
function isEndpointEnabled(id) {
|
|
4063
|
+
const entry = loadCustomProviders().find((e) => e.id === id);
|
|
4064
|
+
return !!entry && entry.enabled !== false;
|
|
4065
|
+
}
|
|
4066
|
+
function modelsUrl(spec) {
|
|
4067
|
+
if (spec.api === "anthropic") return `${spec.base_url}/v1/models?limit=100`;
|
|
4068
|
+
return `${spec.base_url}/models`;
|
|
4069
|
+
}
|
|
4070
|
+
var BUILTIN_SPECS, cachedEntries;
|
|
4071
|
+
var init_providers = __esm({
|
|
4072
|
+
"src/ai/llm/providers.ts"() {
|
|
4073
|
+
"use strict";
|
|
4074
|
+
init_store();
|
|
4075
|
+
BUILTIN_SPECS = [
|
|
4076
|
+
{
|
|
4077
|
+
id: "anthropic",
|
|
4078
|
+
label: "Anthropic",
|
|
4079
|
+
api: "anthropic",
|
|
4080
|
+
base_url: "https://api.anthropic.com",
|
|
4081
|
+
key_prefixes: ["sk-ant-"],
|
|
4082
|
+
shared_prefixes: [],
|
|
4083
|
+
key_config_name: "api-key",
|
|
4084
|
+
requires_key: true
|
|
4085
|
+
},
|
|
4086
|
+
{
|
|
4087
|
+
id: "openai",
|
|
4088
|
+
label: "OpenAI",
|
|
4089
|
+
api: "openai-compat",
|
|
4090
|
+
base_url: "https://api.openai.com/v1",
|
|
4091
|
+
key_prefixes: ["sk-proj-", "sk-svcacct-", "sk-admin-"],
|
|
4092
|
+
shared_prefixes: ["sk-"],
|
|
4093
|
+
key_config_name: "openai-api-key",
|
|
4094
|
+
env_var: "OPENAI_API_KEY",
|
|
4095
|
+
requires_key: true
|
|
4096
|
+
},
|
|
4097
|
+
{
|
|
4098
|
+
id: "google",
|
|
4099
|
+
label: "Google Gemini",
|
|
4100
|
+
api: "openai-compat",
|
|
4101
|
+
base_url: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
4102
|
+
key_prefixes: ["AIza"],
|
|
4103
|
+
shared_prefixes: [],
|
|
4104
|
+
key_config_name: "google-api-key",
|
|
4105
|
+
requires_key: true
|
|
4106
|
+
},
|
|
4107
|
+
{
|
|
4108
|
+
id: "groq",
|
|
4109
|
+
label: "Groq",
|
|
4110
|
+
api: "openai-compat",
|
|
4111
|
+
base_url: "https://api.groq.com/openai/v1",
|
|
4112
|
+
key_prefixes: ["gsk_"],
|
|
4113
|
+
shared_prefixes: [],
|
|
4114
|
+
key_config_name: "groq-api-key",
|
|
4115
|
+
requires_key: true
|
|
4116
|
+
},
|
|
4117
|
+
{
|
|
4118
|
+
id: "mistral",
|
|
4119
|
+
label: "Mistral",
|
|
4120
|
+
api: "openai-compat",
|
|
4121
|
+
base_url: "https://api.mistral.ai/v1",
|
|
4122
|
+
key_prefixes: [],
|
|
4123
|
+
shared_prefixes: [],
|
|
4124
|
+
key_config_name: "mistral-api-key",
|
|
4125
|
+
requires_key: true
|
|
4126
|
+
},
|
|
4127
|
+
{
|
|
4128
|
+
id: "deepseek",
|
|
4129
|
+
label: "DeepSeek",
|
|
4130
|
+
api: "openai-compat",
|
|
4131
|
+
base_url: "https://api.deepseek.com/v1",
|
|
4132
|
+
key_prefixes: [],
|
|
4133
|
+
shared_prefixes: ["sk-"],
|
|
4134
|
+
key_config_name: "deepseek-api-key",
|
|
4135
|
+
requires_key: true
|
|
4136
|
+
},
|
|
4137
|
+
{
|
|
4138
|
+
id: "xai",
|
|
4139
|
+
label: "xAI",
|
|
4140
|
+
api: "openai-compat",
|
|
4141
|
+
base_url: "https://api.x.ai/v1",
|
|
4142
|
+
key_prefixes: ["xai-"],
|
|
4143
|
+
shared_prefixes: [],
|
|
4144
|
+
key_config_name: "xai-api-key",
|
|
4145
|
+
requires_key: true
|
|
4146
|
+
},
|
|
4147
|
+
{
|
|
4148
|
+
id: "openrouter",
|
|
4149
|
+
label: "OpenRouter",
|
|
4150
|
+
api: "openai-compat",
|
|
4151
|
+
base_url: "https://openrouter.ai/api/v1",
|
|
4152
|
+
key_prefixes: ["sk-or-"],
|
|
4153
|
+
shared_prefixes: [],
|
|
4154
|
+
key_config_name: "openrouter-api-key",
|
|
4155
|
+
requires_key: true
|
|
4156
|
+
},
|
|
4157
|
+
{
|
|
4158
|
+
id: "together",
|
|
4159
|
+
label: "Together AI",
|
|
4160
|
+
api: "openai-compat",
|
|
4161
|
+
base_url: "https://api.together.xyz/v1",
|
|
4162
|
+
key_prefixes: [],
|
|
4163
|
+
shared_prefixes: [],
|
|
4164
|
+
key_config_name: "together-api-key",
|
|
4165
|
+
requires_key: true
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
id: "fireworks",
|
|
4169
|
+
label: "Fireworks AI",
|
|
4170
|
+
api: "openai-compat",
|
|
4171
|
+
base_url: "https://api.fireworks.ai/inference/v1",
|
|
4172
|
+
key_prefixes: ["fw_"],
|
|
4173
|
+
shared_prefixes: [],
|
|
4174
|
+
key_config_name: "fireworks-api-key",
|
|
4175
|
+
requires_key: true
|
|
4176
|
+
},
|
|
4177
|
+
{
|
|
4178
|
+
id: "ollama",
|
|
4179
|
+
label: "Ollama (local)",
|
|
4180
|
+
api: "openai-compat",
|
|
4181
|
+
base_url: "http://localhost:11434/v1",
|
|
4182
|
+
key_prefixes: [],
|
|
4183
|
+
shared_prefixes: [],
|
|
4184
|
+
key_config_name: "ollama-api-key",
|
|
4185
|
+
requires_key: false
|
|
4186
|
+
}
|
|
4187
|
+
];
|
|
4188
|
+
cachedEntries = null;
|
|
4189
|
+
}
|
|
4190
|
+
});
|
|
4191
|
+
|
|
4005
4192
|
// src/config/llm-config.ts
|
|
4006
4193
|
function parseProvider(raw) {
|
|
4007
|
-
if (raw
|
|
4008
|
-
|
|
4194
|
+
if (!raw?.trim()) return void 0;
|
|
4195
|
+
const id = raw.trim();
|
|
4196
|
+
return getProviderSpec(id) ? id : void 0;
|
|
4009
4197
|
}
|
|
4010
4198
|
function parseTier(raw) {
|
|
4011
4199
|
if (raw === "high" || raw === "medium" || raw === "low") return raw;
|
|
@@ -4013,7 +4201,7 @@ function parseTier(raw) {
|
|
|
4013
4201
|
}
|
|
4014
4202
|
function parseFailoverOrder(raw) {
|
|
4015
4203
|
if (!raw?.trim()) return ["openai"];
|
|
4016
|
-
return raw.split(",").map((s) => s.trim()).filter((s) => s
|
|
4204
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => !!s && !!getProviderSpec(s));
|
|
4017
4205
|
}
|
|
4018
4206
|
function parseAutoFailover(raw) {
|
|
4019
4207
|
if (!raw) return false;
|
|
@@ -4028,30 +4216,42 @@ function getOpenAiApiKey() {
|
|
|
4028
4216
|
if (fromConfig) return fromConfig;
|
|
4029
4217
|
return process.env.OPENAI_API_KEY?.trim() || void 0;
|
|
4030
4218
|
}
|
|
4219
|
+
function getProviderApiKey(provider) {
|
|
4220
|
+
const spec = getProviderSpec(provider);
|
|
4221
|
+
if (!spec) return void 0;
|
|
4222
|
+
const record = loadConfig();
|
|
4223
|
+
const fromConfig = record[spec.key_config_name]?.trim();
|
|
4224
|
+
if (fromConfig) return fromConfig;
|
|
4225
|
+
if (spec.env_var) {
|
|
4226
|
+
const fromEnv = process.env[spec.env_var]?.trim();
|
|
4227
|
+
if (fromEnv) return fromEnv;
|
|
4228
|
+
}
|
|
4229
|
+
return void 0;
|
|
4230
|
+
}
|
|
4031
4231
|
function hasProviderKey(provider) {
|
|
4032
|
-
|
|
4033
|
-
|
|
4232
|
+
const spec = getProviderSpec(provider);
|
|
4233
|
+
if (!spec) return false;
|
|
4234
|
+
if (!spec.requires_key) return isEndpointEnabled(spec.id) || !!getProviderApiKey(provider);
|
|
4235
|
+
return !!getProviderApiKey(provider);
|
|
4034
4236
|
}
|
|
4035
4237
|
function getAvailableProviders() {
|
|
4036
|
-
|
|
4037
|
-
if (hasProviderKey("anthropic")) out.push("anthropic");
|
|
4038
|
-
if (hasProviderKey("openai")) out.push("openai");
|
|
4039
|
-
return out;
|
|
4238
|
+
return listProviderSpecs().filter((s) => hasProviderKey(s.id)).map((s) => s.id);
|
|
4040
4239
|
}
|
|
4041
4240
|
function hasAnyLlmProvider() {
|
|
4042
4241
|
return getAvailableProviders().length > 0;
|
|
4043
4242
|
}
|
|
4243
|
+
function hasKeylessConfiguredProvider() {
|
|
4244
|
+
return listProviderSpecs().some((s) => !s.requires_key && hasProviderKey(s.id));
|
|
4245
|
+
}
|
|
4044
4246
|
function applyLazyMigration(config) {
|
|
4045
4247
|
if (migrated) return;
|
|
4046
4248
|
migrated = true;
|
|
4047
4249
|
let changed = false;
|
|
4048
4250
|
const record = config;
|
|
4049
4251
|
if (!record["llm-primary"]) {
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
} else if (record["openai-api-key"] || process.env.OPENAI_API_KEY) {
|
|
4054
|
-
record["llm-primary"] = "openai";
|
|
4252
|
+
const available = getAvailableProviders();
|
|
4253
|
+
if (available.length > 0) {
|
|
4254
|
+
record["llm-primary"] = available[0];
|
|
4055
4255
|
changed = true;
|
|
4056
4256
|
}
|
|
4057
4257
|
}
|
|
@@ -4091,20 +4291,20 @@ function loadLlmConfig() {
|
|
|
4091
4291
|
openaiKey: getOpenAiApiKey()
|
|
4092
4292
|
};
|
|
4093
4293
|
}
|
|
4094
|
-
function getProviderApiKey(provider) {
|
|
4095
|
-
if (provider === "anthropic") return getAnthropicApiKey();
|
|
4096
|
-
return getOpenAiApiKey();
|
|
4097
|
-
}
|
|
4098
4294
|
function getInvestigationApiKey(provider) {
|
|
4099
4295
|
if (provider === "anthropic") {
|
|
4100
4296
|
return process.env.NTRP_INVESTIGATION_API_KEY?.trim() || getAnthropicApiKey();
|
|
4101
4297
|
}
|
|
4102
|
-
|
|
4298
|
+
if (provider === "openai") {
|
|
4299
|
+
return process.env.NTRP_INVESTIGATION_OPENAI_KEY?.trim() || getOpenAiApiKey();
|
|
4300
|
+
}
|
|
4301
|
+
return getProviderApiKey(provider);
|
|
4103
4302
|
}
|
|
4104
4303
|
var migrated;
|
|
4105
4304
|
var init_llm_config = __esm({
|
|
4106
4305
|
"src/config/llm-config.ts"() {
|
|
4107
4306
|
"use strict";
|
|
4307
|
+
init_providers();
|
|
4108
4308
|
init_store();
|
|
4109
4309
|
migrated = false;
|
|
4110
4310
|
}
|
|
@@ -4122,7 +4322,13 @@ function resolvePrimaryApiKey(ctx) {
|
|
|
4122
4322
|
if (isInvestigationMode(ctx)) {
|
|
4123
4323
|
return getInvestigationApiKey(primary) ?? getInvestigationApiKey("anthropic") ?? getInvestigationApiKey("openai");
|
|
4124
4324
|
}
|
|
4125
|
-
|
|
4325
|
+
const primaryKey = getProviderApiKey(primary);
|
|
4326
|
+
if (primaryKey) return primaryKey;
|
|
4327
|
+
for (const provider of getAvailableProviders()) {
|
|
4328
|
+
const key = getProviderApiKey(provider);
|
|
4329
|
+
if (key) return key;
|
|
4330
|
+
}
|
|
4331
|
+
return void 0;
|
|
4126
4332
|
}
|
|
4127
4333
|
function canUseReplAi(ctx) {
|
|
4128
4334
|
if (!ctx) return false;
|
|
@@ -4133,25 +4339,21 @@ function canUseReplAi(ctx) {
|
|
|
4133
4339
|
}
|
|
4134
4340
|
function assertReplAi(ctx) {
|
|
4135
4341
|
if (!ctx) {
|
|
4136
|
-
throw new Error(
|
|
4137
|
-
"AI features require stored API keys. Run `ntrp`, then /config set api-key or /config set openai-api-key."
|
|
4138
|
-
);
|
|
4342
|
+
throw new Error(`AI features require stored API keys. Run \`ntrp\`, then /connect.`);
|
|
4139
4343
|
}
|
|
4140
4344
|
if (!canUseReplAi(ctx)) {
|
|
4141
4345
|
if (!hasAnyLlmProvider()) {
|
|
4142
|
-
throw new Error(
|
|
4143
|
-
"No LLM API key configured. Run: /config set api-key (Anthropic) and/or /config set openai-api-key"
|
|
4144
|
-
);
|
|
4346
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
4145
4347
|
}
|
|
4146
4348
|
throw new Error(
|
|
4147
4349
|
"AI features run only in the interactive REPL or headless mode with stored keys."
|
|
4148
4350
|
);
|
|
4149
4351
|
}
|
|
4150
4352
|
const key = resolvePrimaryApiKey(ctx);
|
|
4151
|
-
if (!key) {
|
|
4152
|
-
throw new Error(
|
|
4353
|
+
if (!key && !hasKeylessConfiguredProvider()) {
|
|
4354
|
+
throw new Error(NO_KEY_MESSAGE);
|
|
4153
4355
|
}
|
|
4154
|
-
return key;
|
|
4356
|
+
return key ?? "";
|
|
4155
4357
|
}
|
|
4156
4358
|
function hasEnvApiKeyHint() {
|
|
4157
4359
|
return !!(process.env.ANTHROPIC_API_KEY ?? process.env.NTRP_API_KEY ?? process.env.OPENAI_API_KEY);
|
|
@@ -4164,10 +4366,12 @@ function describeLlmReadiness() {
|
|
|
4164
4366
|
openai: providers.includes("openai")
|
|
4165
4367
|
};
|
|
4166
4368
|
}
|
|
4369
|
+
var NO_KEY_MESSAGE;
|
|
4167
4370
|
var init_gate = __esm({
|
|
4168
4371
|
"src/ai/llm/gate.ts"() {
|
|
4169
4372
|
"use strict";
|
|
4170
4373
|
init_llm_config();
|
|
4374
|
+
NO_KEY_MESSAGE = "No LLM API key configured. Run /connect and paste any provider's key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
4171
4375
|
}
|
|
4172
4376
|
});
|
|
4173
4377
|
|
|
@@ -4212,6 +4416,12 @@ var init_types = __esm({
|
|
|
4212
4416
|
});
|
|
4213
4417
|
|
|
4214
4418
|
// src/ai/llm/errors.ts
|
|
4419
|
+
function isToolsUnsupportedMessage(message) {
|
|
4420
|
+
const msg = message.toLowerCase();
|
|
4421
|
+
const mentionsTools = msg.includes("tool") || msg.includes("function");
|
|
4422
|
+
const mentionsUnsupported = msg.includes("not support") || msg.includes("unsupported") || msg.includes("no support") || msg.includes("not available") || msg.includes("not enabled");
|
|
4423
|
+
return mentionsTools && mentionsUnsupported;
|
|
4424
|
+
}
|
|
4215
4425
|
function mapAnthropicError(err, provider) {
|
|
4216
4426
|
const e = err;
|
|
4217
4427
|
const status = e.status;
|
|
@@ -4229,6 +4439,9 @@ function mapAnthropicError(err, provider) {
|
|
|
4229
4439
|
if (status === 503) {
|
|
4230
4440
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
4231
4441
|
}
|
|
4442
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
4443
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
4444
|
+
}
|
|
4232
4445
|
if (status === 404 || message.toLowerCase().includes("model")) {
|
|
4233
4446
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
4234
4447
|
}
|
|
@@ -4237,6 +4450,11 @@ function mapAnthropicError(err, provider) {
|
|
|
4237
4450
|
}
|
|
4238
4451
|
return new LlmError("UNKNOWN", message, provider, status);
|
|
4239
4452
|
}
|
|
4453
|
+
function isModelNotFoundMessage(message) {
|
|
4454
|
+
const msg = message.toLowerCase();
|
|
4455
|
+
if (!msg.includes("model")) return false;
|
|
4456
|
+
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");
|
|
4457
|
+
}
|
|
4240
4458
|
function mapOpenAiError(err, provider) {
|
|
4241
4459
|
const e = err;
|
|
4242
4460
|
const status = e.status;
|
|
@@ -4251,7 +4469,10 @@ function mapOpenAiError(err, provider) {
|
|
|
4251
4469
|
if (status === 503 || code === "server_error") {
|
|
4252
4470
|
return new LlmError("OVERLOADED", message, provider, status);
|
|
4253
4471
|
}
|
|
4254
|
-
if (
|
|
4472
|
+
if (isToolsUnsupportedMessage(message)) {
|
|
4473
|
+
return new LlmError("TOOLS_UNSUPPORTED", message, provider, status);
|
|
4474
|
+
}
|
|
4475
|
+
if (status === 404 || code === "model_not_found" || code === "model_decommissioned" || isModelNotFoundMessage(message)) {
|
|
4255
4476
|
return new LlmError("MODEL_NOT_FOUND", message, provider, status);
|
|
4256
4477
|
}
|
|
4257
4478
|
if (code === "context_length_exceeded") {
|
|
@@ -4387,8 +4608,15 @@ var init_anthropic = __esm({
|
|
|
4387
4608
|
}
|
|
4388
4609
|
});
|
|
4389
4610
|
|
|
4390
|
-
// src/ai/llm/adapters/openai.ts
|
|
4611
|
+
// src/ai/llm/adapters/openai-compat.ts
|
|
4391
4612
|
import OpenAI from "openai";
|
|
4613
|
+
function makeClient(apiKey, baseUrl) {
|
|
4614
|
+
return new OpenAI({
|
|
4615
|
+
// Keyless endpoints (Ollama) still need a non-empty string for the SDK.
|
|
4616
|
+
apiKey: apiKey || "local",
|
|
4617
|
+
...baseUrl ? { baseURL: baseUrl } : {}
|
|
4618
|
+
});
|
|
4619
|
+
}
|
|
4392
4620
|
function toOpenAiTools(tools2) {
|
|
4393
4621
|
return tools2.map((t) => ({
|
|
4394
4622
|
type: "function",
|
|
@@ -4457,9 +4685,8 @@ function parseResponse2(message) {
|
|
|
4457
4685
|
assistant_message: { role: "assistant", content: text, tool_calls }
|
|
4458
4686
|
};
|
|
4459
4687
|
}
|
|
4460
|
-
async function
|
|
4461
|
-
const
|
|
4462
|
-
const client = new OpenAI({ apiKey });
|
|
4688
|
+
async function openaiCompatComplete(provider, baseUrl, apiKey, model, req) {
|
|
4689
|
+
const client = makeClient(apiKey, baseUrl);
|
|
4463
4690
|
try {
|
|
4464
4691
|
const response = await client.chat.completions.create({
|
|
4465
4692
|
model,
|
|
@@ -4469,7 +4696,7 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
4469
4696
|
});
|
|
4470
4697
|
const choice = response.choices[0];
|
|
4471
4698
|
if (!choice?.message) {
|
|
4472
|
-
throw new Error(
|
|
4699
|
+
throw new Error(`${provider} returned no message`);
|
|
4473
4700
|
}
|
|
4474
4701
|
const parsed = parseResponse2(choice.message);
|
|
4475
4702
|
if (response.usage) {
|
|
@@ -4480,15 +4707,11 @@ async function openaiComplete(apiKey, model, req) {
|
|
|
4480
4707
|
}
|
|
4481
4708
|
return parsed;
|
|
4482
4709
|
} catch (err) {
|
|
4483
|
-
if (err instanceof OpenAI.APIError) {
|
|
4484
|
-
throw mapOpenAiError(err, provider);
|
|
4485
|
-
}
|
|
4486
4710
|
throw mapOpenAiError(err, provider);
|
|
4487
4711
|
}
|
|
4488
4712
|
}
|
|
4489
|
-
async function*
|
|
4490
|
-
const
|
|
4491
|
-
const client = new OpenAI({ apiKey });
|
|
4713
|
+
async function* openaiCompatStream(provider, baseUrl, apiKey, model, req) {
|
|
4714
|
+
const client = makeClient(apiKey, baseUrl);
|
|
4492
4715
|
try {
|
|
4493
4716
|
const stream = await client.chat.completions.create({
|
|
4494
4717
|
model,
|
|
@@ -4501,64 +4724,118 @@ async function* openaiStream(apiKey, model, req) {
|
|
|
4501
4724
|
if (delta) yield { type: "text_delta", text: delta };
|
|
4502
4725
|
}
|
|
4503
4726
|
} catch (err) {
|
|
4504
|
-
if (err instanceof OpenAI.APIError) {
|
|
4505
|
-
throw mapOpenAiError(err, provider);
|
|
4506
|
-
}
|
|
4507
4727
|
throw mapOpenAiError(err, provider);
|
|
4508
4728
|
}
|
|
4509
4729
|
}
|
|
4510
|
-
var
|
|
4511
|
-
"src/ai/llm/adapters/openai.ts"() {
|
|
4730
|
+
var init_openai_compat = __esm({
|
|
4731
|
+
"src/ai/llm/adapters/openai-compat.ts"() {
|
|
4512
4732
|
"use strict";
|
|
4513
4733
|
init_errors();
|
|
4514
4734
|
}
|
|
4515
4735
|
});
|
|
4516
4736
|
|
|
4517
|
-
// src/ai/llm/
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
if (entry.status === "active") return entry.id;
|
|
4530
|
-
if (!entry.successor_id) {
|
|
4531
|
-
const fallback = cheapestActiveInTier(entry.provider, entry.tier);
|
|
4532
|
-
return fallback?.id ?? current;
|
|
4533
|
-
}
|
|
4534
|
-
current = entry.successor_id;
|
|
4737
|
+
// src/ai/llm/models-cache.ts
|
|
4738
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
4739
|
+
import { join as join8 } from "path";
|
|
4740
|
+
function cachePath() {
|
|
4741
|
+
return join8(ntrpHome(), "models.json");
|
|
4742
|
+
}
|
|
4743
|
+
function loadFile() {
|
|
4744
|
+
if (cached) return cached;
|
|
4745
|
+
const path = cachePath();
|
|
4746
|
+
if (!existsSync8(path)) {
|
|
4747
|
+
cached = { version: 1, providers: {} };
|
|
4748
|
+
return cached;
|
|
4535
4749
|
}
|
|
4536
|
-
|
|
4750
|
+
try {
|
|
4751
|
+
const parsed = JSON.parse(readFileSync7(path, "utf-8"));
|
|
4752
|
+
cached = { version: 1, providers: parsed.providers ?? {} };
|
|
4753
|
+
} catch {
|
|
4754
|
+
cached = { version: 1, providers: {} };
|
|
4755
|
+
}
|
|
4756
|
+
return cached;
|
|
4757
|
+
}
|
|
4758
|
+
function saveFile(file) {
|
|
4759
|
+
writeFileSync7(cachePath(), JSON.stringify(file, null, 2) + "\n");
|
|
4760
|
+
cached = file;
|
|
4761
|
+
}
|
|
4762
|
+
function getProviderModels(provider) {
|
|
4763
|
+
return loadFile().providers[provider];
|
|
4764
|
+
}
|
|
4765
|
+
function setProviderModels(provider, entry) {
|
|
4766
|
+
const file = loadFile();
|
|
4767
|
+
file.providers[provider] = entry;
|
|
4768
|
+
saveFile(file);
|
|
4769
|
+
}
|
|
4770
|
+
function getCachedTierModel(provider, tier) {
|
|
4771
|
+
return getProviderModels(provider)?.tier_stack?.[tier];
|
|
4772
|
+
}
|
|
4773
|
+
function findCachedModel(provider, modelId) {
|
|
4774
|
+
return getProviderModels(provider)?.models.find((m) => m.id === modelId);
|
|
4775
|
+
}
|
|
4776
|
+
function cachedModelProvider(modelId) {
|
|
4777
|
+
const file = loadFile();
|
|
4778
|
+
for (const [provider, entry] of Object.entries(file.providers)) {
|
|
4779
|
+
if (entry.models.some((m) => m.id === modelId)) return provider;
|
|
4780
|
+
}
|
|
4781
|
+
return void 0;
|
|
4537
4782
|
}
|
|
4538
|
-
function
|
|
4783
|
+
function markModelNoTools(provider, modelId) {
|
|
4784
|
+
const file = loadFile();
|
|
4785
|
+
const entry = file.providers[provider];
|
|
4786
|
+
if (!entry) return;
|
|
4787
|
+
const noTools = new Set(entry.quirks?.no_tools ?? []);
|
|
4788
|
+
if (noTools.has(modelId)) return;
|
|
4789
|
+
noTools.add(modelId);
|
|
4790
|
+
entry.quirks = { ...entry.quirks, no_tools: [...noTools] };
|
|
4791
|
+
saveFile(file);
|
|
4792
|
+
}
|
|
4793
|
+
function modelHasNoToolsQuirk(provider, modelId) {
|
|
4794
|
+
return !!getProviderModels(provider)?.quirks?.no_tools?.includes(modelId);
|
|
4795
|
+
}
|
|
4796
|
+
function isProviderCacheStale(provider, ttlMs = CACHE_TTL_MS) {
|
|
4797
|
+
const entry = getProviderModels(provider);
|
|
4798
|
+
if (!entry) return true;
|
|
4799
|
+
const fetched = Date.parse(entry.fetched_at);
|
|
4800
|
+
if (Number.isNaN(fetched)) return true;
|
|
4801
|
+
return Date.now() - fetched > ttlMs;
|
|
4802
|
+
}
|
|
4803
|
+
var CACHE_TTL_MS, cached;
|
|
4804
|
+
var init_models_cache = __esm({
|
|
4805
|
+
"src/ai/llm/models-cache.ts"() {
|
|
4806
|
+
"use strict";
|
|
4807
|
+
init_store();
|
|
4808
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
4809
|
+
cached = null;
|
|
4810
|
+
}
|
|
4811
|
+
});
|
|
4812
|
+
|
|
4813
|
+
// src/ai/llm/catalog.ts
|
|
4814
|
+
function catalogTierDefault(provider, tier) {
|
|
4539
4815
|
const candidates = ENTRIES.filter(
|
|
4540
4816
|
(e) => e.provider === provider && e.tier === tier && e.status === "active"
|
|
4541
4817
|
);
|
|
4542
4818
|
if (candidates.length === 0) return void 0;
|
|
4543
4819
|
return candidates.sort((a, b) => a.relative_cost - b.relative_cost)[0];
|
|
4544
4820
|
}
|
|
4545
|
-
function
|
|
4546
|
-
|
|
4547
|
-
if (!entry) {
|
|
4548
|
-
throw new Error(`No active ${tier}-tier model for provider ${provider} in catalog`);
|
|
4549
|
-
}
|
|
4550
|
-
return entry;
|
|
4821
|
+
function modelProviderHint(modelId) {
|
|
4822
|
+
return cachedModelProvider(modelId) ?? byId.get(modelId)?.provider;
|
|
4551
4823
|
}
|
|
4552
|
-
function
|
|
4553
|
-
if (override)
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4824
|
+
function overrideForProvider(override, provider, activeProvider) {
|
|
4825
|
+
if (!override) return void 0;
|
|
4826
|
+
const hint = modelProviderHint(override);
|
|
4827
|
+
if (hint) return hint === provider ? override : void 0;
|
|
4828
|
+
return provider === activeProvider ? override : void 0;
|
|
4829
|
+
}
|
|
4830
|
+
function resolveModelSafe(provider, tier, override) {
|
|
4831
|
+
if (override) return override;
|
|
4832
|
+
const discovered = getCachedTierModel(provider, tier);
|
|
4833
|
+
if (discovered) return discovered;
|
|
4834
|
+
return catalogTierDefault(provider, tier)?.id;
|
|
4560
4835
|
}
|
|
4561
4836
|
function formatModelLabel(provider, modelId) {
|
|
4837
|
+
const cachedName = findCachedModel(provider, modelId)?.display_name;
|
|
4838
|
+
if (cachedName) return `${provider}/${cachedName}`;
|
|
4562
4839
|
const entry = byId.get(modelId);
|
|
4563
4840
|
return entry ? `${provider}/${entry.display_name}` : `${provider}/${modelId}`;
|
|
4564
4841
|
}
|
|
@@ -4566,6 +4843,7 @@ var ENTRIES, byId;
|
|
|
4566
4843
|
var init_catalog = __esm({
|
|
4567
4844
|
"src/ai/llm/catalog.ts"() {
|
|
4568
4845
|
"use strict";
|
|
4846
|
+
init_models_cache();
|
|
4569
4847
|
ENTRIES = [
|
|
4570
4848
|
{
|
|
4571
4849
|
id: "claude-opus-4-6",
|
|
@@ -4638,6 +4916,292 @@ var init_catalog = __esm({
|
|
|
4638
4916
|
}
|
|
4639
4917
|
});
|
|
4640
4918
|
|
|
4919
|
+
// src/ai/llm/http.ts
|
|
4920
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
4921
|
+
function fixtureResponse(url, headers) {
|
|
4922
|
+
try {
|
|
4923
|
+
const raw = readFileSync8(process.env.NTRP_LLM_HTTP_FIXTURE, "utf-8");
|
|
4924
|
+
const entries = JSON.parse(raw);
|
|
4925
|
+
const headerValues = Object.values(headers).join(" ");
|
|
4926
|
+
for (const entry of entries) {
|
|
4927
|
+
if (!url.includes(entry.url_includes)) continue;
|
|
4928
|
+
if (entry.auth_includes && !headerValues.includes(entry.auth_includes)) continue;
|
|
4929
|
+
return { status: entry.status, ok: entry.status >= 200 && entry.status < 300, body: entry.body };
|
|
4930
|
+
}
|
|
4931
|
+
} catch {
|
|
4932
|
+
}
|
|
4933
|
+
return { status: 0, ok: false, body: void 0 };
|
|
4934
|
+
}
|
|
4935
|
+
async function llmHttpGetJson(url, headers, timeoutMs = 6e3) {
|
|
4936
|
+
if (process.env.NTRP_LLM_HTTP_FIXTURE) {
|
|
4937
|
+
return fixtureResponse(url, headers);
|
|
4938
|
+
}
|
|
4939
|
+
const controller = new AbortController();
|
|
4940
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
4941
|
+
try {
|
|
4942
|
+
const res = await fetch(url, { method: "GET", headers, signal: controller.signal });
|
|
4943
|
+
let body;
|
|
4944
|
+
try {
|
|
4945
|
+
body = await res.json();
|
|
4946
|
+
} catch {
|
|
4947
|
+
body = void 0;
|
|
4948
|
+
}
|
|
4949
|
+
return { status: res.status, ok: res.ok, body };
|
|
4950
|
+
} catch {
|
|
4951
|
+
return { status: 0, ok: false, body: void 0 };
|
|
4952
|
+
} finally {
|
|
4953
|
+
clearTimeout(timer);
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
var init_http = __esm({
|
|
4957
|
+
"src/ai/llm/http.ts"() {
|
|
4958
|
+
"use strict";
|
|
4959
|
+
}
|
|
4960
|
+
});
|
|
4961
|
+
|
|
4962
|
+
// src/ai/llm/ranking.ts
|
|
4963
|
+
function compareModels(a, b) {
|
|
4964
|
+
const createdA = a.created ?? 0;
|
|
4965
|
+
const createdB = b.created ?? 0;
|
|
4966
|
+
if (createdA !== createdB) return createdB - createdA;
|
|
4967
|
+
const versionA = extractVersion(a.id);
|
|
4968
|
+
const versionB = extractVersion(b.id);
|
|
4969
|
+
if (versionA !== versionB) return versionB - versionA;
|
|
4970
|
+
if (a.id.length !== b.id.length) return a.id.length - b.id.length;
|
|
4971
|
+
return a.id.localeCompare(b.id);
|
|
4972
|
+
}
|
|
4973
|
+
function extractVersion(id) {
|
|
4974
|
+
const match = id.match(/(\d+(?:\.\d+)?)/);
|
|
4975
|
+
return match ? Number(match[1]) : 0;
|
|
4976
|
+
}
|
|
4977
|
+
function pickByPatterns(models, patterns) {
|
|
4978
|
+
for (const pattern of patterns) {
|
|
4979
|
+
const matches = models.filter((m) => pattern.test(m.id));
|
|
4980
|
+
if (matches.length > 0) return [...matches].sort(compareModels)[0];
|
|
4981
|
+
}
|
|
4982
|
+
return void 0;
|
|
4983
|
+
}
|
|
4984
|
+
function genericBucket(model) {
|
|
4985
|
+
if (GENERIC_HIGH.test(model.id)) return "high";
|
|
4986
|
+
if (GENERIC_LOW.test(model.id)) return "low";
|
|
4987
|
+
return "medium";
|
|
4988
|
+
}
|
|
4989
|
+
function genericPick(models, tier) {
|
|
4990
|
+
const bucket = models.filter((m) => genericBucket(m) === tier);
|
|
4991
|
+
if (bucket.length > 0) return [...bucket].sort(compareModels)[0];
|
|
4992
|
+
return void 0;
|
|
4993
|
+
}
|
|
4994
|
+
function rankModels(providerId, models) {
|
|
4995
|
+
if (models.length === 0) return null;
|
|
4996
|
+
const preferences = PROVIDER_PREFERENCES[providerId];
|
|
4997
|
+
const picks = {};
|
|
4998
|
+
for (const tier of ["high", "medium", "low"]) {
|
|
4999
|
+
const preferred = preferences ? pickByPatterns(models, preferences[tier]) : void 0;
|
|
5000
|
+
const generic = preferred ?? genericPick(models, tier);
|
|
5001
|
+
if (generic) picks[tier] = generic.id;
|
|
5002
|
+
}
|
|
5003
|
+
const anyModel = [...models].sort(compareModels)[0].id;
|
|
5004
|
+
const high = picks.high ?? picks.medium ?? picks.low ?? anyModel;
|
|
5005
|
+
const medium = picks.medium ?? picks.high ?? picks.low ?? anyModel;
|
|
5006
|
+
const low = picks.low ?? picks.medium ?? picks.high ?? anyModel;
|
|
5007
|
+
return { high, medium, low };
|
|
5008
|
+
}
|
|
5009
|
+
var PROVIDER_PREFERENCES, GENERIC_LOW, GENERIC_HIGH;
|
|
5010
|
+
var init_ranking = __esm({
|
|
5011
|
+
"src/ai/llm/ranking.ts"() {
|
|
5012
|
+
"use strict";
|
|
5013
|
+
PROVIDER_PREFERENCES = {
|
|
5014
|
+
anthropic: {
|
|
5015
|
+
high: [/^claude-opus/i, /^claude-sonnet/i],
|
|
5016
|
+
medium: [/^claude-sonnet/i, /^claude-haiku/i],
|
|
5017
|
+
low: [/^claude-haiku/i, /^claude-sonnet/i]
|
|
5018
|
+
},
|
|
5019
|
+
openai: {
|
|
5020
|
+
high: [/^gpt-5(?!.*(mini|nano|chat))/i, /^gpt-4\.1(?!.*(mini|nano))/i, /^gpt-4o(?!.*mini)/i, /^o3(?!.*mini)/i],
|
|
5021
|
+
medium: [/^gpt-5.*mini/i, /^gpt-4\.1-mini/i, /^gpt-4o-mini/i, /^o4-mini/i],
|
|
5022
|
+
low: [/^gpt-5.*nano/i, /^gpt-4\.1-nano/i, /^gpt-4o-mini/i]
|
|
5023
|
+
},
|
|
5024
|
+
google: {
|
|
5025
|
+
high: [/^gemini-[\d.]+-pro/i, /^gemini-[\d.]+-flash(?!-lite)/i],
|
|
5026
|
+
medium: [/^gemini-[\d.]+-flash(?!-lite|-8b)/i, /^gemini-[\d.]+-pro/i],
|
|
5027
|
+
low: [/^gemini-[\d.]+-flash-lite/i, /flash-8b/i, /^gemini-[\d.]+-flash(?!-lite)/i]
|
|
5028
|
+
},
|
|
5029
|
+
groq: {
|
|
5030
|
+
high: [/llama-3\.3-70b/i, /gpt-oss-120b/i, /70b/i, /deepseek-r1/i],
|
|
5031
|
+
medium: [/llama-3\.1-8b-instant/i, /gpt-oss-20b/i, /llama.*8b/i],
|
|
5032
|
+
low: [/8b-instant/i, /llama.*8b/i, /gemma/i]
|
|
5033
|
+
},
|
|
5034
|
+
deepseek: {
|
|
5035
|
+
high: [/reasoner/i, /chat/i],
|
|
5036
|
+
medium: [/chat/i],
|
|
5037
|
+
low: [/chat/i]
|
|
5038
|
+
},
|
|
5039
|
+
mistral: {
|
|
5040
|
+
high: [/large/i, /medium/i],
|
|
5041
|
+
medium: [/medium/i, /^mistral-small/i],
|
|
5042
|
+
low: [/ministral/i, /small/i, /tiny/i]
|
|
5043
|
+
},
|
|
5044
|
+
xai: {
|
|
5045
|
+
high: [/^grok-\d+(?!.*(mini|fast))/i, /^grok(?!.*(mini|fast))/i],
|
|
5046
|
+
medium: [/^grok.*mini(?!.*fast)/i, /^grok.*fast/i],
|
|
5047
|
+
low: [/^grok.*mini.*fast/i, /^grok.*mini/i]
|
|
5048
|
+
},
|
|
5049
|
+
openrouter: {
|
|
5050
|
+
high: [/^openrouter\/auto$/i, /claude.*opus/i, /^openai\/gpt-5(?!.*(mini|nano))/i, /gemini.*pro/i],
|
|
5051
|
+
medium: [/claude.*sonnet/i, /gpt-5.*mini/i, /gpt-4\.1-mini/i, /gemini.*flash(?!-lite)/i],
|
|
5052
|
+
low: [/claude.*haiku/i, /nano/i, /flash-lite/i, /mini/i]
|
|
5053
|
+
}
|
|
5054
|
+
};
|
|
5055
|
+
GENERIC_LOW = /(mini|nano|lite|tiny|micro|small|haiku|instant|flash|turbo|\b0?\.?5b\b|\b[1-8]b\b)/i;
|
|
5056
|
+
GENERIC_HIGH = /(opus|ultra|large|max\b|\bpro\b|405b|253b|235b|120b|72b|70b|reason|-r1\b|think|deep)/i;
|
|
5057
|
+
}
|
|
5058
|
+
});
|
|
5059
|
+
|
|
5060
|
+
// src/ai/llm/discovery.ts
|
|
5061
|
+
function authHeaders(spec, apiKey) {
|
|
5062
|
+
if (spec.api === "anthropic") {
|
|
5063
|
+
return { "x-api-key": apiKey ?? "", "anthropic-version": "2023-06-01" };
|
|
5064
|
+
}
|
|
5065
|
+
return apiKey ? { Authorization: `Bearer ${apiKey}` } : {};
|
|
5066
|
+
}
|
|
5067
|
+
function normalizeItem(spec, item) {
|
|
5068
|
+
let id = item.id ?? "";
|
|
5069
|
+
if (!id) return null;
|
|
5070
|
+
if (id.startsWith("models/")) id = id.slice("models/".length);
|
|
5071
|
+
const model = { id };
|
|
5072
|
+
const display = item.display_name ?? item.name;
|
|
5073
|
+
if (display && display !== id) model.display_name = display;
|
|
5074
|
+
if (typeof item.created === "number") model.created = item.created;
|
|
5075
|
+
else if (item.created_at) {
|
|
5076
|
+
const parsed = Date.parse(item.created_at);
|
|
5077
|
+
if (!Number.isNaN(parsed)) model.created = Math.floor(parsed / 1e3);
|
|
5078
|
+
}
|
|
5079
|
+
if (typeof item.context_length === "number") model.context_length = item.context_length;
|
|
5080
|
+
if (Array.isArray(item.supported_parameters)) {
|
|
5081
|
+
model.supports_tools = item.supported_parameters.includes("tools");
|
|
5082
|
+
}
|
|
5083
|
+
return model;
|
|
5084
|
+
}
|
|
5085
|
+
async function fetchProviderModels(spec, apiKey, timeoutMs = 6e3) {
|
|
5086
|
+
const headers = authHeaders(spec, apiKey);
|
|
5087
|
+
if (spec.api === "anthropic") {
|
|
5088
|
+
const models2 = [];
|
|
5089
|
+
let url = modelsUrl(spec);
|
|
5090
|
+
for (let page = 0; page < 5 && url; page++) {
|
|
5091
|
+
const res2 = await llmHttpGetJson(url, headers, timeoutMs);
|
|
5092
|
+
if (!res2.ok) return models2.length > 0 ? { ok: true, models: models2 } : { ok: false, status: res2.status };
|
|
5093
|
+
const body2 = res2.body;
|
|
5094
|
+
for (const item of body2?.data ?? []) {
|
|
5095
|
+
const model = normalizeItem(spec, item);
|
|
5096
|
+
if (model) models2.push(model);
|
|
5097
|
+
}
|
|
5098
|
+
url = body2?.has_more && body2.last_id ? `${spec.base_url}/v1/models?limit=100&after_id=${encodeURIComponent(body2.last_id)}` : null;
|
|
5099
|
+
}
|
|
5100
|
+
return { ok: true, models: models2 };
|
|
5101
|
+
}
|
|
5102
|
+
const res = await llmHttpGetJson(modelsUrl(spec), headers, timeoutMs);
|
|
5103
|
+
if (!res.ok) return { ok: false, status: res.status };
|
|
5104
|
+
const body = res.body;
|
|
5105
|
+
const list = Array.isArray(body) ? body : body?.data ?? [];
|
|
5106
|
+
const models = [];
|
|
5107
|
+
for (const item of list) {
|
|
5108
|
+
const model = normalizeItem(spec, item);
|
|
5109
|
+
if (model) models.push(model);
|
|
5110
|
+
}
|
|
5111
|
+
return { ok: true, models };
|
|
5112
|
+
}
|
|
5113
|
+
function filterChatModels(spec, models) {
|
|
5114
|
+
const extra = PROVIDER_EXCLUDE[spec.id];
|
|
5115
|
+
return models.filter((m) => !NON_CHAT.test(m.id) && !(extra && extra.test(m.id)));
|
|
5116
|
+
}
|
|
5117
|
+
function storeDiscoveredModels(providerId, rawModels) {
|
|
5118
|
+
const spec = getProviderSpec(providerId);
|
|
5119
|
+
if (!spec || rawModels.length === 0) return null;
|
|
5120
|
+
const chat = filterChatModels(spec, rawModels);
|
|
5121
|
+
const usable = chat.length > 0 ? chat : rawModels;
|
|
5122
|
+
const stack = rankModels(providerId, usable);
|
|
5123
|
+
if (!stack) return null;
|
|
5124
|
+
const prior = getProviderModels(providerId);
|
|
5125
|
+
const entry = {
|
|
5126
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5127
|
+
models: usable,
|
|
5128
|
+
tier_stack: stack,
|
|
5129
|
+
...prior?.quirks ? { quirks: prior.quirks } : {}
|
|
5130
|
+
};
|
|
5131
|
+
setProviderModels(providerId, entry);
|
|
5132
|
+
return entry;
|
|
5133
|
+
}
|
|
5134
|
+
async function refreshProviderModels(providerId, opts = {}) {
|
|
5135
|
+
const spec = getProviderSpec(providerId);
|
|
5136
|
+
if (!spec) return null;
|
|
5137
|
+
if (!opts.force && !isProviderCacheStale(providerId)) {
|
|
5138
|
+
return getProviderModels(providerId) ?? null;
|
|
5139
|
+
}
|
|
5140
|
+
const apiKey = opts.apiKey ?? getProviderApiKey(providerId);
|
|
5141
|
+
if (spec.requires_key && !apiKey) return null;
|
|
5142
|
+
const result = await fetchProviderModels(spec, apiKey);
|
|
5143
|
+
if (!result.ok) return null;
|
|
5144
|
+
return storeDiscoveredModels(providerId, result.models);
|
|
5145
|
+
}
|
|
5146
|
+
function rerankExcluding(providerId, deadModelId) {
|
|
5147
|
+
const prior = getProviderModels(providerId);
|
|
5148
|
+
if (!prior) return null;
|
|
5149
|
+
const survivors = prior.models.filter((m) => m.id !== deadModelId);
|
|
5150
|
+
const stack = rankModels(providerId, survivors);
|
|
5151
|
+
if (!stack) return null;
|
|
5152
|
+
const entry = { ...prior, models: survivors, tier_stack: stack };
|
|
5153
|
+
setProviderModels(providerId, entry);
|
|
5154
|
+
return entry;
|
|
5155
|
+
}
|
|
5156
|
+
var NON_CHAT, PROVIDER_EXCLUDE;
|
|
5157
|
+
var init_discovery = __esm({
|
|
5158
|
+
"src/ai/llm/discovery.ts"() {
|
|
5159
|
+
"use strict";
|
|
5160
|
+
init_llm_config();
|
|
5161
|
+
init_http();
|
|
5162
|
+
init_models_cache();
|
|
5163
|
+
init_providers();
|
|
5164
|
+
init_ranking();
|
|
5165
|
+
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;
|
|
5166
|
+
PROVIDER_EXCLUDE = {
|
|
5167
|
+
openai: /(chatgpt|-search|deep-research|-pro\b|computer-use|codex-mini|-instruct\b)/i
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
});
|
|
5171
|
+
|
|
5172
|
+
// src/ai/llm/heal.ts
|
|
5173
|
+
async function healModelNotFound(opts) {
|
|
5174
|
+
const { provider, tier, deadModel } = opts;
|
|
5175
|
+
const refreshed = await refreshProviderModels(provider, { apiKey: opts.apiKey, force: true });
|
|
5176
|
+
let candidate = refreshed?.tier_stack?.[tier] ?? getCachedTierModel(provider, tier);
|
|
5177
|
+
if (!candidate || candidate === deadModel) {
|
|
5178
|
+
const reranked = rerankExcluding(provider, deadModel);
|
|
5179
|
+
candidate = reranked?.tier_stack?.[tier];
|
|
5180
|
+
}
|
|
5181
|
+
if (!candidate || candidate === deadModel) return null;
|
|
5182
|
+
clearDeadOverride(deadModel, opts.ctx);
|
|
5183
|
+
return {
|
|
5184
|
+
model: candidate,
|
|
5185
|
+
notice: `model ${deadModel} is no longer available \u2014 switched to ${candidate}`
|
|
5186
|
+
};
|
|
5187
|
+
}
|
|
5188
|
+
function clearDeadOverride(deadModel, ctx) {
|
|
5189
|
+
if (getConfigValue("llm-model-override")?.trim() === deadModel) {
|
|
5190
|
+
deleteConfigValue("llm-model-override");
|
|
5191
|
+
}
|
|
5192
|
+
if (ctx?.llm?.modelOverride === deadModel) {
|
|
5193
|
+
ctx.llm.modelOverride = void 0;
|
|
5194
|
+
}
|
|
5195
|
+
}
|
|
5196
|
+
var init_heal = __esm({
|
|
5197
|
+
"src/ai/llm/heal.ts"() {
|
|
5198
|
+
"use strict";
|
|
5199
|
+
init_store();
|
|
5200
|
+
init_discovery();
|
|
5201
|
+
init_models_cache();
|
|
5202
|
+
}
|
|
5203
|
+
});
|
|
5204
|
+
|
|
4641
5205
|
// src/ai/llm/surfaces.ts
|
|
4642
5206
|
function tierForSurface(surface, userTier) {
|
|
4643
5207
|
const spec = SURFACE_SPECS[surface];
|
|
@@ -4682,7 +5246,7 @@ function resolveActiveProvider(ctx) {
|
|
|
4682
5246
|
if (session && hasProviderKey(session)) return session;
|
|
4683
5247
|
const cfg = loadLlmConfig();
|
|
4684
5248
|
if (hasProviderKey(cfg.primary)) return cfg.primary;
|
|
4685
|
-
const available =
|
|
5249
|
+
const available = getAvailableProviders();
|
|
4686
5250
|
if (available.length > 0) return available[0];
|
|
4687
5251
|
return cfg.primary;
|
|
4688
5252
|
}
|
|
@@ -4708,8 +5272,8 @@ function resolveProviderOrder(ctx) {
|
|
|
4708
5272
|
for (const p of cfg.failoverOrder) {
|
|
4709
5273
|
if (p !== active && hasProviderKey(p) && !order.includes(p)) order.push(p);
|
|
4710
5274
|
}
|
|
4711
|
-
for (const p of
|
|
4712
|
-
if (p !== active &&
|
|
5275
|
+
for (const p of getAvailableProviders()) {
|
|
5276
|
+
if (p !== active && !order.includes(p)) order.push(p);
|
|
4713
5277
|
}
|
|
4714
5278
|
return order;
|
|
4715
5279
|
}
|
|
@@ -4723,23 +5287,16 @@ var init_session_state = __esm({
|
|
|
4723
5287
|
});
|
|
4724
5288
|
|
|
4725
5289
|
// src/ai/llm/resolver.ts
|
|
4726
|
-
function getProviderOrder(config, ctx) {
|
|
4727
|
-
void config;
|
|
4728
|
-
return resolveProviderOrder(ctx);
|
|
4729
|
-
}
|
|
4730
5290
|
function resolveCompletionContext(surface, opts = {}) {
|
|
4731
5291
|
const activeProvider = resolveActiveProvider(opts.ctx);
|
|
4732
5292
|
const tier = opts.tier ?? resolveEffectiveTier(opts.ctx, surface);
|
|
4733
5293
|
const override = opts.modelOverride ?? resolveEffectiveModelOverride(opts.ctx);
|
|
4734
5294
|
const providerOrder = resolveProviderOrder(opts.ctx);
|
|
4735
5295
|
const modelByProvider = {};
|
|
4736
|
-
for (const provider of providerOrder) {
|
|
4737
|
-
const providerOverride =
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
if (!modelByProvider[activeProvider]) {
|
|
4741
|
-
const activeOverride = override && getCatalogEntry(override)?.provider === activeProvider ? override : void 0;
|
|
4742
|
-
modelByProvider[activeProvider] = resolveModel(activeProvider, tier, activeOverride);
|
|
5296
|
+
for (const provider of /* @__PURE__ */ new Set([...providerOrder, activeProvider])) {
|
|
5297
|
+
const providerOverride = overrideForProvider(override, provider, activeProvider);
|
|
5298
|
+
const model = resolveModelSafe(provider, tier, providerOverride);
|
|
5299
|
+
if (model) modelByProvider[provider] = model;
|
|
4743
5300
|
}
|
|
4744
5301
|
return {
|
|
4745
5302
|
providerOrder,
|
|
@@ -4765,70 +5322,142 @@ var init_resolver = __esm({
|
|
|
4765
5322
|
|
|
4766
5323
|
// src/ai/llm/failover.ts
|
|
4767
5324
|
async function completeOnProvider(provider, model, apiKey, req) {
|
|
4768
|
-
|
|
4769
|
-
|
|
5325
|
+
const spec = getProviderSpec(provider);
|
|
5326
|
+
if (!spec) {
|
|
5327
|
+
throw new LlmError("UNKNOWN", `Unknown provider "${provider}" \u2014 run /connect to register it.`, provider);
|
|
5328
|
+
}
|
|
5329
|
+
if (spec.api === "anthropic") {
|
|
5330
|
+
return anthropicComplete(apiKey ?? "", model, req);
|
|
5331
|
+
}
|
|
5332
|
+
return openaiCompatComplete(provider, spec.base_url, apiKey, model, req);
|
|
5333
|
+
}
|
|
5334
|
+
function usableKey(provider, ctx) {
|
|
5335
|
+
const spec = getProviderSpec(provider);
|
|
5336
|
+
if (!spec) return { ok: false };
|
|
5337
|
+
const apiKey = getApiKeyForProvider(provider, ctx);
|
|
5338
|
+
if (spec.requires_key && !apiKey) return { ok: false };
|
|
5339
|
+
return { ok: true, apiKey };
|
|
5340
|
+
}
|
|
5341
|
+
async function resolveModelWithDiscovery(provider, cfg, opts) {
|
|
5342
|
+
const known = cfg.modelByProvider[provider];
|
|
5343
|
+
if (known) return known;
|
|
5344
|
+
const override = overrideForProvider(opts.modelOverride, provider, cfg.activeProvider);
|
|
5345
|
+
const direct = resolveModelSafe(provider, cfg.tier, override);
|
|
5346
|
+
if (direct) return direct;
|
|
5347
|
+
await refreshProviderModels(provider, { apiKey: opts.apiKey, force: true }).catch(() => null);
|
|
5348
|
+
return resolveModelSafe(provider, cfg.tier, override);
|
|
5349
|
+
}
|
|
5350
|
+
function stripTools(req) {
|
|
5351
|
+
const { tools: _tools, ...rest } = req;
|
|
5352
|
+
return rest;
|
|
4770
5353
|
}
|
|
4771
5354
|
async function completeWithFailover(req, opts = {}) {
|
|
4772
|
-
const
|
|
5355
|
+
const cfg = resolveCompletionContext(req.surface, {
|
|
4773
5356
|
max_tokens: req.max_tokens,
|
|
4774
5357
|
tier: opts.tier,
|
|
4775
5358
|
modelOverride: opts.modelOverride,
|
|
4776
5359
|
ctx: opts.ctx
|
|
4777
5360
|
});
|
|
4778
|
-
const providers =
|
|
5361
|
+
const providers = cfg.providerOrder;
|
|
4779
5362
|
if (providers.length === 0) {
|
|
4780
|
-
throw new Error(
|
|
5363
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
4781
5364
|
}
|
|
5365
|
+
const notices = [];
|
|
4782
5366
|
let lastError;
|
|
4783
5367
|
let failoverFrom;
|
|
5368
|
+
const buildMeta = (provider, model, response) => ({
|
|
5369
|
+
provider_used: provider,
|
|
5370
|
+
model_used: model,
|
|
5371
|
+
...response.token_usage ?? {},
|
|
5372
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
5373
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
5374
|
+
});
|
|
4784
5375
|
for (let i = 0; i < providers.length; i++) {
|
|
4785
5376
|
const provider = providers[i];
|
|
4786
|
-
const
|
|
4787
|
-
if (!
|
|
4788
|
-
let model =
|
|
5377
|
+
const key = usableKey(provider, opts.ctx);
|
|
5378
|
+
if (!key.ok) continue;
|
|
5379
|
+
let model = await resolveModelWithDiscovery(provider, cfg, {
|
|
5380
|
+
modelOverride: opts.modelOverride,
|
|
5381
|
+
apiKey: key.apiKey
|
|
5382
|
+
});
|
|
5383
|
+
if (!model) {
|
|
5384
|
+
lastError = new LlmError(
|
|
5385
|
+
"MODEL_NOT_FOUND",
|
|
5386
|
+
`No models known for provider "${provider}". Run /connect or /model refresh.`,
|
|
5387
|
+
provider
|
|
5388
|
+
);
|
|
5389
|
+
continue;
|
|
5390
|
+
}
|
|
5391
|
+
let effectiveReq = req;
|
|
5392
|
+
if (req.tools?.length && modelHasNoToolsQuirk(provider, model)) {
|
|
5393
|
+
effectiveReq = stripTools(req);
|
|
5394
|
+
notices.push(`${model} doesn't support tool calling \u2014 answering without live data tools`);
|
|
5395
|
+
}
|
|
4789
5396
|
try {
|
|
4790
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
4791
|
-
const meta =
|
|
4792
|
-
provider_used: provider,
|
|
4793
|
-
model_used: model,
|
|
4794
|
-
...response.token_usage ?? {},
|
|
4795
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
4796
|
-
};
|
|
5397
|
+
const response = await completeOnProvider(provider, model, key.apiKey, effectiveReq);
|
|
5398
|
+
const meta = buildMeta(provider, model, response);
|
|
4797
5399
|
recordLlmUsage(response.token_usage);
|
|
4798
5400
|
return { response, meta };
|
|
4799
5401
|
} catch (err) {
|
|
4800
|
-
|
|
5402
|
+
let llmErr = err;
|
|
4801
5403
|
if (llmErr.name !== "LlmError") throw err;
|
|
4802
5404
|
lastError = llmErr;
|
|
4803
|
-
if (llmErr.code === "
|
|
4804
|
-
|
|
5405
|
+
if (llmErr.code === "TOOLS_UNSUPPORTED" && effectiveReq.tools?.length) {
|
|
5406
|
+
markModelNoTools(provider, model);
|
|
5407
|
+
notices.push(`${model} doesn't support tool calling \u2014 retrying without live data tools`);
|
|
4805
5408
|
try {
|
|
4806
|
-
const response = await completeOnProvider(provider, model, apiKey,
|
|
4807
|
-
const meta =
|
|
4808
|
-
provider_used: provider,
|
|
4809
|
-
model_used: model,
|
|
4810
|
-
...response.token_usage ?? {},
|
|
4811
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
4812
|
-
};
|
|
5409
|
+
const response = await completeOnProvider(provider, model, key.apiKey, stripTools(effectiveReq));
|
|
5410
|
+
const meta = buildMeta(provider, model, response);
|
|
4813
5411
|
recordLlmUsage(response.token_usage);
|
|
4814
5412
|
return { response, meta };
|
|
4815
5413
|
} catch (retryErr) {
|
|
4816
5414
|
const retryLlm = retryErr;
|
|
4817
|
-
if (retryLlm.name
|
|
4818
|
-
|
|
5415
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
5416
|
+
lastError = retryLlm;
|
|
5417
|
+
llmErr = retryLlm;
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
if (llmErr.code === "MODEL_NOT_FOUND") {
|
|
5421
|
+
const healed = await healModelNotFound({
|
|
5422
|
+
provider,
|
|
5423
|
+
tier: cfg.tier,
|
|
5424
|
+
deadModel: model,
|
|
5425
|
+
apiKey: key.apiKey,
|
|
5426
|
+
ctx: opts.ctx
|
|
5427
|
+
}).catch(() => null);
|
|
5428
|
+
if (healed) {
|
|
5429
|
+
notices.push(healed.notice);
|
|
5430
|
+
model = healed.model;
|
|
5431
|
+
let retryReq = req;
|
|
5432
|
+
if (req.tools?.length && modelHasNoToolsQuirk(provider, model)) {
|
|
5433
|
+
retryReq = stripTools(req);
|
|
5434
|
+
notices.push(`${model} doesn't support tool calling \u2014 answering without live data tools`);
|
|
5435
|
+
}
|
|
5436
|
+
try {
|
|
5437
|
+
const response = await completeOnProvider(provider, model, key.apiKey, retryReq);
|
|
5438
|
+
const meta = buildMeta(provider, model, response);
|
|
5439
|
+
recordLlmUsage(response.token_usage);
|
|
5440
|
+
return { response, meta };
|
|
5441
|
+
} catch (retryErr) {
|
|
5442
|
+
const retryLlm = retryErr;
|
|
5443
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
5444
|
+
lastError = retryLlm;
|
|
5445
|
+
llmErr = retryLlm;
|
|
5446
|
+
}
|
|
4819
5447
|
}
|
|
4820
5448
|
}
|
|
4821
5449
|
if (!isFailoverEligible(llmErr.code)) throw llmErr;
|
|
4822
5450
|
const next = providers[i + 1];
|
|
4823
5451
|
if (next) {
|
|
4824
5452
|
failoverFrom = failoverFrom ?? provider;
|
|
5453
|
+
notices.push(`${provider} unavailable (${llmErr.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
4825
5454
|
opts.onFailover?.(provider, next, llmErr.code);
|
|
4826
5455
|
continue;
|
|
4827
5456
|
}
|
|
4828
5457
|
throw llmErr;
|
|
4829
5458
|
}
|
|
4830
5459
|
}
|
|
4831
|
-
throw lastError ?? new Error(
|
|
5460
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
4832
5461
|
}
|
|
4833
5462
|
async function* streamWithFailover(req, opts = {}) {
|
|
4834
5463
|
const cfg = resolveCompletionContext(req.surface, {
|
|
@@ -4837,23 +5466,47 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
4837
5466
|
modelOverride: opts.modelOverride,
|
|
4838
5467
|
ctx: opts.ctx
|
|
4839
5468
|
});
|
|
4840
|
-
const providers =
|
|
5469
|
+
const providers = cfg.providerOrder;
|
|
4841
5470
|
if (providers.length === 0) {
|
|
4842
|
-
throw new Error(
|
|
5471
|
+
throw new Error(NO_PROVIDER_MESSAGE);
|
|
4843
5472
|
}
|
|
5473
|
+
const notices = [];
|
|
4844
5474
|
let lastError;
|
|
4845
5475
|
let failoverFrom;
|
|
5476
|
+
async function* streamOnProvider(provider, model, apiKey) {
|
|
5477
|
+
const spec = getProviderSpec(provider);
|
|
5478
|
+
if (!spec) {
|
|
5479
|
+
throw new LlmError("UNKNOWN", `Unknown provider "${provider}" \u2014 run /connect to register it.`, provider);
|
|
5480
|
+
}
|
|
5481
|
+
if (spec.api === "anthropic") {
|
|
5482
|
+
yield* anthropicStream(apiKey ?? "", model, req);
|
|
5483
|
+
return;
|
|
5484
|
+
}
|
|
5485
|
+
yield* openaiCompatStream(provider, spec.base_url, apiKey, model, req);
|
|
5486
|
+
}
|
|
4846
5487
|
for (let i = 0; i < providers.length; i++) {
|
|
4847
5488
|
const provider = providers[i];
|
|
4848
|
-
const
|
|
4849
|
-
if (!
|
|
4850
|
-
|
|
4851
|
-
|
|
5489
|
+
const key = usableKey(provider, opts.ctx);
|
|
5490
|
+
if (!key.ok) continue;
|
|
5491
|
+
let model = await resolveModelWithDiscovery(provider, cfg, {
|
|
5492
|
+
modelOverride: opts.modelOverride,
|
|
5493
|
+
apiKey: key.apiKey
|
|
5494
|
+
});
|
|
5495
|
+
if (!model) {
|
|
5496
|
+
lastError = new LlmError(
|
|
5497
|
+
"MODEL_NOT_FOUND",
|
|
5498
|
+
`No models known for provider "${provider}". Run /connect or /model refresh.`,
|
|
5499
|
+
provider
|
|
5500
|
+
);
|
|
5501
|
+
continue;
|
|
5502
|
+
}
|
|
5503
|
+
let yieldedAny = false;
|
|
5504
|
+
const attempt = async function* (attemptModel) {
|
|
4852
5505
|
let fullText = "";
|
|
4853
|
-
const
|
|
4854
|
-
for await (const event of streamFn(apiKey, model, req)) {
|
|
5506
|
+
for await (const event of streamOnProvider(provider, attemptModel, key.apiKey)) {
|
|
4855
5507
|
if (event.type === "text_delta") {
|
|
4856
5508
|
fullText += event.text;
|
|
5509
|
+
yieldedAny = true;
|
|
4857
5510
|
yield event;
|
|
4858
5511
|
}
|
|
4859
5512
|
}
|
|
@@ -4861,10 +5514,11 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
4861
5514
|
recordLlmUsage({ input_tokens: 0, output_tokens: estimatedOut });
|
|
4862
5515
|
const meta = {
|
|
4863
5516
|
provider_used: provider,
|
|
4864
|
-
model_used:
|
|
5517
|
+
model_used: attemptModel,
|
|
4865
5518
|
input_tokens: 0,
|
|
4866
5519
|
output_tokens: estimatedOut,
|
|
4867
|
-
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
|
|
5520
|
+
...failoverFrom ? { failover: true, failover_from: failoverFrom } : {},
|
|
5521
|
+
...notices.length > 0 ? { notices: [...notices] } : {}
|
|
4868
5522
|
};
|
|
4869
5523
|
yield {
|
|
4870
5524
|
type: "done",
|
|
@@ -4876,52 +5530,86 @@ async function* streamWithFailover(req, opts = {}) {
|
|
|
4876
5530
|
},
|
|
4877
5531
|
meta
|
|
4878
5532
|
};
|
|
5533
|
+
};
|
|
5534
|
+
try {
|
|
5535
|
+
yield* attempt(model);
|
|
4879
5536
|
return;
|
|
4880
5537
|
} catch (err) {
|
|
4881
5538
|
const llmErr = err;
|
|
4882
5539
|
if (llmErr.name !== "LlmError") throw err;
|
|
4883
5540
|
lastError = llmErr;
|
|
4884
|
-
if (
|
|
5541
|
+
if (yieldedAny) throw llmErr;
|
|
5542
|
+
if (llmErr.code === "MODEL_NOT_FOUND") {
|
|
5543
|
+
const healed = await healModelNotFound({
|
|
5544
|
+
provider,
|
|
5545
|
+
tier: cfg.tier,
|
|
5546
|
+
deadModel: model,
|
|
5547
|
+
apiKey: key.apiKey,
|
|
5548
|
+
ctx: opts.ctx
|
|
5549
|
+
}).catch(() => null);
|
|
5550
|
+
if (healed) {
|
|
5551
|
+
notices.push(healed.notice);
|
|
5552
|
+
model = healed.model;
|
|
5553
|
+
try {
|
|
5554
|
+
yield* attempt(model);
|
|
5555
|
+
return;
|
|
5556
|
+
} catch (retryErr) {
|
|
5557
|
+
const retryLlm = retryErr;
|
|
5558
|
+
if (retryLlm.name !== "LlmError") throw retryErr;
|
|
5559
|
+
lastError = retryLlm;
|
|
5560
|
+
if (yieldedAny) throw retryLlm;
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
if (!isFailoverEligible(lastError.code)) throw lastError;
|
|
4885
5565
|
const next = providers[i + 1];
|
|
4886
5566
|
if (next) {
|
|
4887
5567
|
failoverFrom = failoverFrom ?? provider;
|
|
4888
|
-
|
|
5568
|
+
notices.push(`${provider} unavailable (${lastError.code.toLowerCase()}) \u2014 trying ${next}`);
|
|
5569
|
+
opts.onFailover?.(provider, next, lastError.code);
|
|
4889
5570
|
continue;
|
|
4890
5571
|
}
|
|
4891
|
-
throw
|
|
5572
|
+
throw lastError;
|
|
4892
5573
|
}
|
|
4893
5574
|
}
|
|
4894
|
-
throw lastError ?? new Error(
|
|
5575
|
+
throw lastError ?? new Error(NO_PROVIDER_MESSAGE);
|
|
4895
5576
|
}
|
|
5577
|
+
var NO_PROVIDER_MESSAGE;
|
|
4896
5578
|
var init_failover = __esm({
|
|
4897
5579
|
"src/ai/llm/failover.ts"() {
|
|
4898
5580
|
"use strict";
|
|
4899
5581
|
init_usage_stats();
|
|
4900
5582
|
init_anthropic();
|
|
4901
|
-
|
|
5583
|
+
init_openai_compat();
|
|
4902
5584
|
init_catalog();
|
|
5585
|
+
init_discovery();
|
|
4903
5586
|
init_errors();
|
|
5587
|
+
init_heal();
|
|
5588
|
+
init_models_cache();
|
|
5589
|
+
init_providers();
|
|
5590
|
+
init_types();
|
|
4904
5591
|
init_resolver();
|
|
5592
|
+
NO_PROVIDER_MESSAGE = "No LLM provider configured. Run /connect and paste any API key (Anthropic, OpenAI, Groq, Gemini, ...).";
|
|
4905
5593
|
}
|
|
4906
5594
|
});
|
|
4907
5595
|
|
|
4908
5596
|
// src/config/profile.ts
|
|
4909
|
-
import { readFileSync as
|
|
4910
|
-
import { join as
|
|
5597
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
5598
|
+
import { join as join9 } from "path";
|
|
4911
5599
|
function profilePath() {
|
|
4912
5600
|
return PROFILE_PATH;
|
|
4913
5601
|
}
|
|
4914
5602
|
function profileExists() {
|
|
4915
|
-
return
|
|
5603
|
+
return existsSync9(PROFILE_PATH);
|
|
4916
5604
|
}
|
|
4917
5605
|
function isProfileConfigured(profile = loadProfile()) {
|
|
4918
5606
|
if (!profile) return false;
|
|
4919
5607
|
return profile.company_name.trim().length > 0;
|
|
4920
5608
|
}
|
|
4921
5609
|
function loadProfile() {
|
|
4922
|
-
if (!
|
|
5610
|
+
if (!existsSync9(PROFILE_PATH)) return null;
|
|
4923
5611
|
try {
|
|
4924
|
-
const parsed = JSON.parse(
|
|
5612
|
+
const parsed = JSON.parse(readFileSync9(PROFILE_PATH, "utf-8"));
|
|
4925
5613
|
if (!parsed || typeof parsed !== "object") return null;
|
|
4926
5614
|
return parsed;
|
|
4927
5615
|
} catch {
|
|
@@ -4934,21 +5622,21 @@ var init_profile = __esm({
|
|
|
4934
5622
|
"use strict";
|
|
4935
5623
|
init_store();
|
|
4936
5624
|
NTRP_DIR3 = ntrpHome();
|
|
4937
|
-
PROFILE_PATH =
|
|
5625
|
+
PROFILE_PATH = join9(NTRP_DIR3, "profile.json");
|
|
4938
5626
|
}
|
|
4939
5627
|
});
|
|
4940
5628
|
|
|
4941
5629
|
// src/data/playbook.ts
|
|
4942
|
-
import { existsSync as
|
|
4943
|
-
import { join as
|
|
5630
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10, appendFileSync } from "fs";
|
|
5631
|
+
import { join as join10 } from "path";
|
|
4944
5632
|
function playsPath() {
|
|
4945
|
-
return
|
|
5633
|
+
return join10(getMemoryDir(), PLAYS_FILE);
|
|
4946
5634
|
}
|
|
4947
5635
|
function getCustomPlays() {
|
|
4948
5636
|
const path = playsPath();
|
|
4949
|
-
if (!
|
|
5637
|
+
if (!existsSync10(path)) return [];
|
|
4950
5638
|
const out = [];
|
|
4951
|
-
for (const line of
|
|
5639
|
+
for (const line of readFileSync10(path, "utf-8").split("\n")) {
|
|
4952
5640
|
const trimmed = line.trim();
|
|
4953
5641
|
if (!trimmed) continue;
|
|
4954
5642
|
try {
|
|
@@ -5395,7 +6083,8 @@ async function* streamFindings(input, ctx) {
|
|
|
5395
6083
|
findings,
|
|
5396
6084
|
model_used: meta.model_used,
|
|
5397
6085
|
provider_used: meta.provider_used,
|
|
5398
|
-
raw_prompt: userMessage
|
|
6086
|
+
raw_prompt: userMessage,
|
|
6087
|
+
usage: meta
|
|
5399
6088
|
};
|
|
5400
6089
|
}
|
|
5401
6090
|
}
|
|
@@ -5696,9 +6385,9 @@ var init_tool_schemas = __esm({
|
|
|
5696
6385
|
});
|
|
5697
6386
|
|
|
5698
6387
|
// src/ai/privacy.ts
|
|
5699
|
-
import { existsSync as
|
|
6388
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, appendFileSync as appendFileSync2 } from "fs";
|
|
5700
6389
|
import { homedir as homedir3 } from "os";
|
|
5701
|
-
import { join as
|
|
6390
|
+
import { join as join11 } from "path";
|
|
5702
6391
|
function stripPII(obj) {
|
|
5703
6392
|
if (obj === null || obj === void 0) return obj;
|
|
5704
6393
|
if (typeof obj !== "object") return obj;
|
|
@@ -5713,14 +6402,14 @@ function stripPII(obj) {
|
|
|
5713
6402
|
return out;
|
|
5714
6403
|
}
|
|
5715
6404
|
function ensureAuditDir() {
|
|
5716
|
-
if (!
|
|
6405
|
+
if (!existsSync11(AUDIT_DIR)) {
|
|
5717
6406
|
mkdirSync7(AUDIT_DIR, { recursive: true });
|
|
5718
6407
|
}
|
|
5719
6408
|
}
|
|
5720
6409
|
function logToolCall(entry) {
|
|
5721
6410
|
ensureAuditDir();
|
|
5722
6411
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5723
|
-
const path =
|
|
6412
|
+
const path = join11(AUDIT_DIR, `agentic-${date}.jsonl`);
|
|
5724
6413
|
appendFileSync2(path, JSON.stringify(entry) + "\n");
|
|
5725
6414
|
}
|
|
5726
6415
|
var PII_FIELDS, AUDIT_DIR;
|
|
@@ -5744,7 +6433,7 @@ var init_privacy = __esm({
|
|
|
5744
6433
|
"raw_data",
|
|
5745
6434
|
"metadata"
|
|
5746
6435
|
]);
|
|
5747
|
-
AUDIT_DIR =
|
|
6436
|
+
AUDIT_DIR = join11(homedir3(), ".ntrp", "audit");
|
|
5748
6437
|
}
|
|
5749
6438
|
});
|
|
5750
6439
|
|
|
@@ -7982,7 +8671,7 @@ async function runMetricsAnalysis(options = {}) {
|
|
|
7982
8671
|
if (options.findings) {
|
|
7983
8672
|
if (!canUseReplAi(options.ctx)) {
|
|
7984
8673
|
throw new Error(
|
|
7985
|
-
"AI metrics findings require stored API keys. Run `ntrp`,
|
|
8674
|
+
"AI metrics findings require stored API keys. Run `ntrp`, then /connect (any provider key), then /metrics --findings."
|
|
7986
8675
|
);
|
|
7987
8676
|
}
|
|
7988
8677
|
options.onProgress?.("findings");
|
|
@@ -8337,6 +9026,9 @@ function formatLlmAttribution(meta) {
|
|
|
8337
9026
|
return line;
|
|
8338
9027
|
}
|
|
8339
9028
|
function printLlmAttribution(meta) {
|
|
9029
|
+
for (const notice of meta.notices ?? []) {
|
|
9030
|
+
console.log(chalk8.dim(` ${notice}`));
|
|
9031
|
+
}
|
|
8340
9032
|
const line = formatLlmAttribution(meta);
|
|
8341
9033
|
if (line) console.log(chalk8.dim(` ${line}`));
|
|
8342
9034
|
}
|
|
@@ -8609,6 +9301,7 @@ async function renderDiagnoseStream(options) {
|
|
|
8609
9301
|
let modelUsed = "";
|
|
8610
9302
|
let providerUsed;
|
|
8611
9303
|
let failover;
|
|
9304
|
+
let notices;
|
|
8612
9305
|
let rawPrompt = "";
|
|
8613
9306
|
try {
|
|
8614
9307
|
for await (const event of runFindings(fullResult)) {
|
|
@@ -8624,6 +9317,7 @@ async function renderDiagnoseStream(options) {
|
|
|
8624
9317
|
modelUsed = event.model_used;
|
|
8625
9318
|
providerUsed = event.provider_used;
|
|
8626
9319
|
failover = event.failover;
|
|
9320
|
+
notices = event.usage?.notices;
|
|
8627
9321
|
rawPrompt = event.raw_prompt;
|
|
8628
9322
|
}
|
|
8629
9323
|
}
|
|
@@ -8647,7 +9341,8 @@ async function renderDiagnoseStream(options) {
|
|
|
8647
9341
|
printLlmAttribution({
|
|
8648
9342
|
model_used: modelUsed,
|
|
8649
9343
|
provider_used: providerUsed,
|
|
8650
|
-
failover
|
|
9344
|
+
failover,
|
|
9345
|
+
notices
|
|
8651
9346
|
});
|
|
8652
9347
|
} catch (err) {
|
|
8653
9348
|
findingsSpinner.fail(deep ? "Agentic investigation failed" : "AI findings failed");
|
|
@@ -9070,7 +9765,7 @@ async function handler(args, ctx) {
|
|
|
9070
9765
|
console.log();
|
|
9071
9766
|
console.log(" " + chalk11.red("AI findings run only in the interactive REPL."));
|
|
9072
9767
|
console.log(" " + chalk11.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
|
|
9073
|
-
console.log(" " + chalk11.dim("Start with ") + paint("accent", "ntrp") + chalk11.dim(",
|
|
9768
|
+
console.log(" " + chalk11.dim("Start with ") + paint("accent", "ntrp") + chalk11.dim(", run ") + paint("accent", "/connect") + chalk11.dim(" (any provider key), then /diagnose --findings."));
|
|
9074
9769
|
console.log();
|
|
9075
9770
|
return;
|
|
9076
9771
|
}
|
|
@@ -12075,18 +12770,18 @@ var init_generator = __esm({
|
|
|
12075
12770
|
});
|
|
12076
12771
|
|
|
12077
12772
|
// src/demo/taxonomy-cache.ts
|
|
12078
|
-
import { readFileSync as
|
|
12773
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync9, existsSync as existsSync12, mkdirSync as mkdirSync8, unlinkSync as unlinkSync3 } from "fs";
|
|
12079
12774
|
import { homedir as homedir4 } from "os";
|
|
12080
|
-
import { join as
|
|
12775
|
+
import { join as join12 } from "path";
|
|
12081
12776
|
function ensureDir5() {
|
|
12082
|
-
if (!
|
|
12777
|
+
if (!existsSync12(NTRP_DIR4)) {
|
|
12083
12778
|
mkdirSync8(NTRP_DIR4, { recursive: true });
|
|
12084
12779
|
}
|
|
12085
12780
|
}
|
|
12086
12781
|
function loadCachedTaxonomy(profile) {
|
|
12087
|
-
if (!
|
|
12782
|
+
if (!existsSync12(TAXONOMY_PATH)) return null;
|
|
12088
12783
|
try {
|
|
12089
|
-
const parsed = JSON.parse(
|
|
12784
|
+
const parsed = JSON.parse(readFileSync11(TAXONOMY_PATH, "utf-8"));
|
|
12090
12785
|
if (!parsed || typeof parsed !== "object") return null;
|
|
12091
12786
|
if (parsed.profile_updated_at !== profile.updated_at) return null;
|
|
12092
12787
|
return parsed;
|
|
@@ -12096,14 +12791,14 @@ function loadCachedTaxonomy(profile) {
|
|
|
12096
12791
|
}
|
|
12097
12792
|
function saveCachedTaxonomy(taxonomy) {
|
|
12098
12793
|
ensureDir5();
|
|
12099
|
-
|
|
12794
|
+
writeFileSync9(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
|
|
12100
12795
|
}
|
|
12101
12796
|
var NTRP_DIR4, TAXONOMY_PATH;
|
|
12102
12797
|
var init_taxonomy_cache = __esm({
|
|
12103
12798
|
"src/demo/taxonomy-cache.ts"() {
|
|
12104
12799
|
"use strict";
|
|
12105
|
-
NTRP_DIR4 =
|
|
12106
|
-
TAXONOMY_PATH =
|
|
12800
|
+
NTRP_DIR4 = join12(homedir4(), ".ntrp");
|
|
12801
|
+
TAXONOMY_PATH = join12(NTRP_DIR4, "demo-taxonomy.json");
|
|
12107
12802
|
}
|
|
12108
12803
|
});
|
|
12109
12804
|
|
|
@@ -12432,8 +13127,8 @@ function markFailure(ctx) {
|
|
|
12432
13127
|
}
|
|
12433
13128
|
async function loadOrBuildTaxonomy(profile, forceRegen, ctx) {
|
|
12434
13129
|
if (!forceRegen) {
|
|
12435
|
-
const
|
|
12436
|
-
if (
|
|
13130
|
+
const cached2 = loadCachedTaxonomy(profile);
|
|
13131
|
+
if (cached2) return cached2;
|
|
12437
13132
|
}
|
|
12438
13133
|
const spinnerText = forceRegen ? "Rebuilding market taxonomy\u2026" : "Researching your market taxonomy\u2026";
|
|
12439
13134
|
const spinner = ora4({ text: spinnerText, discardStdin: false }).start();
|
|
@@ -12536,7 +13231,7 @@ __export(ingest_exports, {
|
|
|
12536
13231
|
});
|
|
12537
13232
|
import chalk15 from "chalk";
|
|
12538
13233
|
import ora5 from "ora";
|
|
12539
|
-
import { readFileSync as
|
|
13234
|
+
import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
|
|
12540
13235
|
import { basename as basename2 } from "path";
|
|
12541
13236
|
async function handler3(args, ctx) {
|
|
12542
13237
|
const { positional, flags } = parseArgs(args, [
|
|
@@ -12560,7 +13255,7 @@ async function handler3(args, ctx) {
|
|
|
12560
13255
|
console.error(chalk15.dim(" /ingest --demo [--scenario <name>]"));
|
|
12561
13256
|
process.exit(1);
|
|
12562
13257
|
}
|
|
12563
|
-
if (!
|
|
13258
|
+
if (!existsSync13(file)) {
|
|
12564
13259
|
console.error(chalk15.red(` File not found: ${file}`));
|
|
12565
13260
|
process.exit(1);
|
|
12566
13261
|
}
|
|
@@ -12578,7 +13273,7 @@ async function handler3(args, ctx) {
|
|
|
12578
13273
|
try {
|
|
12579
13274
|
await initSchema();
|
|
12580
13275
|
spinner.text = "Parsing CSV...";
|
|
12581
|
-
const content =
|
|
13276
|
+
const content = readFileSync12(file, "utf-8");
|
|
12582
13277
|
const { rows, headers } = parseCSV(content);
|
|
12583
13278
|
if (rows.length === 0) {
|
|
12584
13279
|
spinner.fail("CSV is empty");
|
|
@@ -12694,7 +13389,7 @@ __export(ingest_chat_exports, {
|
|
|
12694
13389
|
loadDemoFromChat: () => loadDemoFromChat,
|
|
12695
13390
|
looksLikeFilePath: () => looksLikeFilePath
|
|
12696
13391
|
});
|
|
12697
|
-
import { existsSync as
|
|
13392
|
+
import { existsSync as existsSync14 } from "fs";
|
|
12698
13393
|
import { basename as basename3, resolve as resolve4 } from "path";
|
|
12699
13394
|
import { homedir as homedir5 } from "os";
|
|
12700
13395
|
import chalk16 from "chalk";
|
|
@@ -12714,11 +13409,11 @@ function extractFilePath(input) {
|
|
|
12714
13409
|
const m = trimmed.match(re);
|
|
12715
13410
|
if (m?.[1]) {
|
|
12716
13411
|
const p = expandPath(m[1]);
|
|
12717
|
-
if (
|
|
13412
|
+
if (existsSync14(p)) return p;
|
|
12718
13413
|
}
|
|
12719
13414
|
if (!m?.[1] && re.test(trimmed) && trimmed.toLowerCase().endsWith(".csv")) {
|
|
12720
13415
|
const p = expandPath(trimmed.replace(/^["']|["']$/g, ""));
|
|
12721
|
-
if (
|
|
13416
|
+
if (existsSync14(p)) return p;
|
|
12722
13417
|
}
|
|
12723
13418
|
}
|
|
12724
13419
|
return null;
|
|
@@ -12748,12 +13443,12 @@ async function ingestFromChat(ctx, filePath) {
|
|
|
12748
13443
|
}
|
|
12749
13444
|
const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
|
|
12750
13445
|
const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
|
|
12751
|
-
const { readFileSync:
|
|
13446
|
+
const { readFileSync: readFileSync18 } = await import("fs");
|
|
12752
13447
|
const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
|
|
12753
13448
|
const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
|
|
12754
13449
|
let headerCheckFailed = false;
|
|
12755
13450
|
try {
|
|
12756
|
-
const raw =
|
|
13451
|
+
const raw = readFileSync18(filePath, "utf-8");
|
|
12757
13452
|
const { headers } = parseCSV2(raw);
|
|
12758
13453
|
const detected = detectEntityType2(headers, "unknown");
|
|
12759
13454
|
if (!detected) headerCheckFailed = true;
|
|
@@ -13892,7 +14587,7 @@ async function runDiagnosis(options = {}) {
|
|
|
13892
14587
|
if (options.findings) {
|
|
13893
14588
|
if (!canUseReplAi(options.ctx)) {
|
|
13894
14589
|
throw new Error(
|
|
13895
|
-
"AI findings require stored API keys. Run `ntrp`, then /
|
|
14590
|
+
"AI findings require stored API keys. Run `ntrp`, then /connect (any provider key), and use /diagnose --findings."
|
|
13896
14591
|
);
|
|
13897
14592
|
}
|
|
13898
14593
|
if (options.deep) {
|
|
@@ -14100,24 +14795,24 @@ JSON SHAPE:
|
|
|
14100
14795
|
|
|
14101
14796
|
// src/strategies/readers.ts
|
|
14102
14797
|
import { createHash } from "crypto";
|
|
14103
|
-
import { existsSync as
|
|
14798
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
14104
14799
|
import { extname, resolve as resolve5 } from "path";
|
|
14105
14800
|
import { parse as parseYaml } from "yaml";
|
|
14106
14801
|
import { PDFParse } from "pdf-parse";
|
|
14107
14802
|
async function readStrategyFile(pathOrDash) {
|
|
14108
14803
|
if (pathOrDash === "-") {
|
|
14109
|
-
const text2 =
|
|
14804
|
+
const text2 = readFileSync13(0, "utf-8");
|
|
14110
14805
|
return createDocument("stdin", null, text2, {});
|
|
14111
14806
|
}
|
|
14112
14807
|
const sourcePath = resolve5(pathOrDash);
|
|
14113
|
-
if (!
|
|
14808
|
+
if (!existsSync15(sourcePath)) {
|
|
14114
14809
|
throw new NtrpError("strategy_file_not_found", `Strategy file not found: ${pathOrDash}`, 2 /* Usage */);
|
|
14115
14810
|
}
|
|
14116
14811
|
const ext = extname(sourcePath).toLowerCase();
|
|
14117
14812
|
if (ext === ".pdf") {
|
|
14118
14813
|
return readPdf(sourcePath);
|
|
14119
14814
|
}
|
|
14120
|
-
const text =
|
|
14815
|
+
const text = readFileSync13(sourcePath, "utf-8");
|
|
14121
14816
|
if (ext === ".yaml" || ext === ".yml") {
|
|
14122
14817
|
const structured = parseStructuredYaml(text);
|
|
14123
14818
|
return createDocument("yaml", sourcePath, text, structured);
|
|
@@ -14132,7 +14827,7 @@ function readStrategyText(text) {
|
|
|
14132
14827
|
return createDocument("text", null, text, {});
|
|
14133
14828
|
}
|
|
14134
14829
|
async function readPdf(sourcePath) {
|
|
14135
|
-
const data =
|
|
14830
|
+
const data = readFileSync13(sourcePath);
|
|
14136
14831
|
const parser = new PDFParse({ data });
|
|
14137
14832
|
try {
|
|
14138
14833
|
const result = await parser.getText();
|
|
@@ -14179,15 +14874,15 @@ var init_readers = __esm({
|
|
|
14179
14874
|
});
|
|
14180
14875
|
|
|
14181
14876
|
// src/strategies/library.ts
|
|
14182
|
-
import { writeFileSync as
|
|
14183
|
-
import { join as
|
|
14877
|
+
import { writeFileSync as writeFileSync10 } from "fs";
|
|
14878
|
+
import { join as join13 } from "path";
|
|
14184
14879
|
import { stringify as stringifyYaml } from "yaml";
|
|
14185
14880
|
function strategyLibraryPath(slug) {
|
|
14186
|
-
return
|
|
14881
|
+
return join13(getStrategiesDir(), `${slug}.md`);
|
|
14187
14882
|
}
|
|
14188
14883
|
function writeStrategyMarkdown(strategy) {
|
|
14189
14884
|
const path = strategyLibraryPath(strategy.slug);
|
|
14190
|
-
|
|
14885
|
+
writeFileSync10(path, renderStrategyMarkdown(strategy), "utf-8");
|
|
14191
14886
|
return path;
|
|
14192
14887
|
}
|
|
14193
14888
|
function renderStrategyMarkdown(strategy) {
|
|
@@ -14261,7 +14956,7 @@ var init_library = __esm({
|
|
|
14261
14956
|
// src/strategies/connectors.ts
|
|
14262
14957
|
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
14263
14958
|
import { homedir as homedir6 } from "os";
|
|
14264
|
-
import { basename as basename4, extname as extname2, join as
|
|
14959
|
+
import { basename as basename4, extname as extname2, join as join14, relative, resolve as resolve6, sep as sep2 } from "path";
|
|
14265
14960
|
function createLocalFolderConnector(options) {
|
|
14266
14961
|
const rootPath = resolveUserPath(options.rootPath);
|
|
14267
14962
|
const name = options.name ?? (basename4(rootPath) || "local");
|
|
@@ -14304,7 +14999,7 @@ function createLocalFolderConnector(options) {
|
|
|
14304
14999
|
}
|
|
14305
15000
|
function walkLocalFolder(rootPath, currentPath, refs, opts) {
|
|
14306
15001
|
for (const entry of readdirSync2(currentPath, { withFileTypes: true })) {
|
|
14307
|
-
const absolutePath =
|
|
15002
|
+
const absolutePath = join14(currentPath, entry.name);
|
|
14308
15003
|
const relativePath = normalizePath(relative(rootPath, absolutePath));
|
|
14309
15004
|
if (entry.isDirectory()) {
|
|
14310
15005
|
if (shouldSkipDirectory(entry.name) || matchesAny(relativePath, opts.excludePatterns)) continue;
|
|
@@ -14368,7 +15063,7 @@ function normalizePath(path) {
|
|
|
14368
15063
|
}
|
|
14369
15064
|
function resolveUserPath(path) {
|
|
14370
15065
|
if (path === "~") return homedir6();
|
|
14371
|
-
if (path.startsWith("~/")) return
|
|
15066
|
+
if (path.startsWith("~/")) return join14(homedir6(), path.slice(2));
|
|
14372
15067
|
return resolve6(path);
|
|
14373
15068
|
}
|
|
14374
15069
|
var DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDED_DIRS;
|
|
@@ -14613,8 +15308,8 @@ async function callProvider(texts) {
|
|
|
14613
15308
|
async function embedText(text) {
|
|
14614
15309
|
const key = text.trim();
|
|
14615
15310
|
if (!key) return null;
|
|
14616
|
-
const
|
|
14617
|
-
if (
|
|
15311
|
+
const cached2 = cache.get(key);
|
|
15312
|
+
if (cached2) return cached2;
|
|
14618
15313
|
const result = await callProvider([key]);
|
|
14619
15314
|
const vec = result?.[0] ?? null;
|
|
14620
15315
|
if (vec) cache.set(key, vec);
|
|
@@ -14624,8 +15319,8 @@ async function embedItems(items) {
|
|
|
14624
15319
|
const needing = [];
|
|
14625
15320
|
const out = items.map((it, index) => {
|
|
14626
15321
|
if (it.embedding && it.embedding.length > 0) return { ...it };
|
|
14627
|
-
const
|
|
14628
|
-
if (
|
|
15322
|
+
const cached2 = cache.get(it.text.trim());
|
|
15323
|
+
if (cached2) return { ...it, embedding: cached2 };
|
|
14629
15324
|
needing.push({ index, text: it.text });
|
|
14630
15325
|
return { ...it };
|
|
14631
15326
|
});
|
|
@@ -14784,17 +15479,17 @@ var init_retrieval = __esm({
|
|
|
14784
15479
|
});
|
|
14785
15480
|
|
|
14786
15481
|
// src/memory/knowledge.ts
|
|
14787
|
-
import { existsSync as
|
|
14788
|
-
import { join as
|
|
15482
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
|
|
15483
|
+
import { join as join17 } from "path";
|
|
14789
15484
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
14790
15485
|
function knowledgePath() {
|
|
14791
|
-
return
|
|
15486
|
+
return join17(getMemoryDir(), KNOWLEDGE_FILE);
|
|
14792
15487
|
}
|
|
14793
15488
|
function loadKnowledgeChunks() {
|
|
14794
15489
|
const path = knowledgePath();
|
|
14795
|
-
if (!
|
|
15490
|
+
if (!existsSync16(path)) return [];
|
|
14796
15491
|
const out = [];
|
|
14797
|
-
for (const line of
|
|
15492
|
+
for (const line of readFileSync14(path, "utf-8").split("\n")) {
|
|
14798
15493
|
const trimmed = line.trim();
|
|
14799
15494
|
if (!trimmed) continue;
|
|
14800
15495
|
try {
|
|
@@ -14827,17 +15522,17 @@ __export(store_exports2, {
|
|
|
14827
15522
|
rewriteJsonl: () => rewriteJsonl,
|
|
14828
15523
|
scrubText: () => scrubText
|
|
14829
15524
|
});
|
|
14830
|
-
import { existsSync as
|
|
14831
|
-
import { join as
|
|
15525
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync12 } from "fs";
|
|
15526
|
+
import { join as join18 } from "path";
|
|
14832
15527
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
14833
15528
|
function memPath(file) {
|
|
14834
|
-
return
|
|
15529
|
+
return join18(getMemoryDir(), file);
|
|
14835
15530
|
}
|
|
14836
15531
|
function readJsonl(file) {
|
|
14837
15532
|
const path = memPath(file);
|
|
14838
|
-
if (!
|
|
15533
|
+
if (!existsSync17(path)) return [];
|
|
14839
15534
|
const out = [];
|
|
14840
|
-
for (const line of
|
|
15535
|
+
for (const line of readFileSync15(path, "utf-8").split("\n")) {
|
|
14841
15536
|
const trimmed = line.trim();
|
|
14842
15537
|
if (!trimmed) continue;
|
|
14843
15538
|
try {
|
|
@@ -14855,7 +15550,7 @@ function appendJsonl(file, obj) {
|
|
|
14855
15550
|
}
|
|
14856
15551
|
function rewriteJsonl(file, rows) {
|
|
14857
15552
|
try {
|
|
14858
|
-
|
|
15553
|
+
writeFileSync12(memPath(file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : ""));
|
|
14859
15554
|
} catch {
|
|
14860
15555
|
}
|
|
14861
15556
|
}
|
|
@@ -14917,7 +15612,7 @@ function loadWinSnippets() {
|
|
|
14917
15612
|
const out = [];
|
|
14918
15613
|
for (const name of readdirSync4(dir)) {
|
|
14919
15614
|
if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
|
|
14920
|
-
const raw =
|
|
15615
|
+
const raw = readFileSync15(join18(dir, name), "utf-8");
|
|
14921
15616
|
const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
|
|
14922
15617
|
const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
14923
15618
|
out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
|
|
@@ -15091,7 +15786,7 @@ init_session_analysis();
|
|
|
15091
15786
|
init_queries();
|
|
15092
15787
|
init_diagnosis();
|
|
15093
15788
|
init_strategy();
|
|
15094
|
-
import { join as
|
|
15789
|
+
import { join as join16 } from "path";
|
|
15095
15790
|
|
|
15096
15791
|
// src/services/publish.ts
|
|
15097
15792
|
init_queries();
|
|
@@ -15256,8 +15951,8 @@ function buildSections(input) {
|
|
|
15256
15951
|
|
|
15257
15952
|
// src/repositories/markdown.ts
|
|
15258
15953
|
init_formatters();
|
|
15259
|
-
import { mkdirSync as mkdirSync9, writeFileSync as
|
|
15260
|
-
import { basename as basename5, dirname as dirname2, join as
|
|
15954
|
+
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
|
|
15955
|
+
import { basename as basename5, dirname as dirname2, join as join15, resolve as resolve7 } from "path";
|
|
15261
15956
|
import { stringify as stringifyYaml2 } from "yaml";
|
|
15262
15957
|
var markdownRepositoryAdapter = {
|
|
15263
15958
|
kind: "markdown",
|
|
@@ -15282,9 +15977,9 @@ var markdownRepositoryAdapter = {
|
|
|
15282
15977
|
mkdirSync9(root, { recursive: true });
|
|
15283
15978
|
const written = [];
|
|
15284
15979
|
for (const file of files) {
|
|
15285
|
-
const absolutePath =
|
|
15980
|
+
const absolutePath = join15(root, file.relativePath);
|
|
15286
15981
|
mkdirSync9(dirname2(absolutePath), { recursive: true });
|
|
15287
|
-
|
|
15982
|
+
writeFileSync11(absolutePath, file.contents, "utf-8");
|
|
15288
15983
|
written.push(absolutePath);
|
|
15289
15984
|
}
|
|
15290
15985
|
return {
|
|
@@ -15600,7 +16295,7 @@ async function runSmokeProtocol(_input, ctx) {
|
|
|
15600
16295
|
});
|
|
15601
16296
|
const proposalResult = await proposeRepositoryExport({
|
|
15602
16297
|
target: "markdown",
|
|
15603
|
-
directory:
|
|
16298
|
+
directory: join16(getExportsDir(), "repository-smoke"),
|
|
15604
16299
|
source: "smoke_protocol",
|
|
15605
16300
|
modelOrFixture: "smoke-protocol-v1"
|
|
15606
16301
|
});
|
|
@@ -15764,11 +16459,12 @@ async function runAsk(question, ctx) {
|
|
|
15764
16459
|
|
|
15765
16460
|
// src/services/setup.ts
|
|
15766
16461
|
init_repl_api();
|
|
16462
|
+
init_providers();
|
|
15767
16463
|
init_llm_config();
|
|
15768
16464
|
init_store();
|
|
15769
16465
|
init_profile();
|
|
15770
|
-
import { existsSync as
|
|
15771
|
-
import { join as
|
|
16466
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "fs";
|
|
16467
|
+
import { join as join19 } from "path";
|
|
15772
16468
|
|
|
15773
16469
|
// src/license/verify.ts
|
|
15774
16470
|
init_store();
|
|
@@ -15965,8 +16661,8 @@ function setupCheck() {
|
|
|
15965
16661
|
let writable = false;
|
|
15966
16662
|
try {
|
|
15967
16663
|
mkdirSync10(home, { recursive: true });
|
|
15968
|
-
const probe =
|
|
15969
|
-
|
|
16664
|
+
const probe = join19(home, ".write-check");
|
|
16665
|
+
writeFileSync13(probe, "ok\n");
|
|
15970
16666
|
writable = true;
|
|
15971
16667
|
} catch {
|
|
15972
16668
|
writable = false;
|
|
@@ -15991,7 +16687,8 @@ function setupCheck() {
|
|
|
15991
16687
|
tier: llmCfg.tier,
|
|
15992
16688
|
auto_failover: llmCfg.autoFailover,
|
|
15993
16689
|
anthropic: llmReady.anthropic,
|
|
15994
|
-
openai: llmReady.openai
|
|
16690
|
+
openai: llmReady.openai,
|
|
16691
|
+
providers: llmReady.providers
|
|
15995
16692
|
}
|
|
15996
16693
|
},
|
|
15997
16694
|
license: {
|
|
@@ -16010,18 +16707,18 @@ init_serialize();
|
|
|
16010
16707
|
init_errors2();
|
|
16011
16708
|
|
|
16012
16709
|
// src/version.ts
|
|
16013
|
-
import { existsSync as
|
|
16014
|
-
import { dirname as dirname3, join as
|
|
16710
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
16711
|
+
import { dirname as dirname3, join as join20 } from "path";
|
|
16015
16712
|
import { fileURLToPath } from "url";
|
|
16016
16713
|
var cachedVersion;
|
|
16017
16714
|
function getInstalledVersion() {
|
|
16018
16715
|
if (cachedVersion) return cachedVersion;
|
|
16019
16716
|
const start = dirname3(fileURLToPath(import.meta.url));
|
|
16020
16717
|
for (const rel of ["../package.json", "../../package.json"]) {
|
|
16021
|
-
const path =
|
|
16022
|
-
if (!
|
|
16718
|
+
const path = join20(start, rel);
|
|
16719
|
+
if (!existsSync19(path)) continue;
|
|
16023
16720
|
try {
|
|
16024
|
-
const pkg = JSON.parse(
|
|
16721
|
+
const pkg = JSON.parse(readFileSync17(path, "utf-8"));
|
|
16025
16722
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
16026
16723
|
cachedVersion = pkg.version;
|
|
16027
16724
|
return cachedVersion;
|
|
@@ -16097,10 +16794,13 @@ async function callTool(name, input) {
|
|
|
16097
16794
|
case "ntrp_setup_check":
|
|
16098
16795
|
return toolContent(setupCheck());
|
|
16099
16796
|
case "ntrp_diagnose": {
|
|
16797
|
+
const wantsFindings = Boolean(input.findings || input.deep);
|
|
16100
16798
|
const result = await runDiagnosis({
|
|
16101
|
-
findings:
|
|
16799
|
+
findings: wantsFindings,
|
|
16102
16800
|
deep: Boolean(input.deep),
|
|
16103
|
-
segment: typeof input.segment === "string" ? input.segment : void 0
|
|
16801
|
+
segment: typeof input.segment === "string" ? input.segment : void 0,
|
|
16802
|
+
// Without a ctx, canUseReplAi() rejects AI findings even with stored keys.
|
|
16803
|
+
...wantsFindings ? { ctx: await initHeadlessAgentContext() } : {}
|
|
16104
16804
|
});
|
|
16105
16805
|
return toolContent({
|
|
16106
16806
|
health: serializeHealth(result.health),
|