@gamaze/hicortex 0.7.1 → 0.10.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/README.md +72 -39
- package/dist/claude-md.d.ts +9 -21
- package/dist/claude-md.js +9 -241
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +29 -11
- package/dist/consolidate.js +0 -7
- package/dist/db.js +24 -0
- package/dist/embedder.d.ts +11 -0
- package/dist/embedder.js +27 -0
- package/dist/extensions.d.ts +41 -88
- package/dist/extensions.js +36 -61
- package/dist/features.d.ts +21 -25
- package/dist/features.js +47 -83
- package/dist/hermes-transcript-reader.d.ts +27 -0
- package/dist/hermes-transcript-reader.js +134 -0
- package/dist/index.d.ts +16 -4
- package/dist/index.js +252 -344
- package/dist/init.d.ts +41 -1
- package/dist/init.js +545 -190
- package/dist/lesson-selection.d.ts +62 -0
- package/dist/lesson-selection.js +159 -0
- package/dist/lessons-context.d.ts +17 -0
- package/dist/lessons-context.js +96 -0
- package/dist/llm.d.ts +42 -29
- package/dist/llm.js +89 -270
- package/dist/mcp-server.d.ts +0 -1
- package/dist/mcp-server.js +404 -86
- package/dist/nightly.d.ts +9 -6
- package/dist/nightly.js +197 -357
- package/dist/oc-transcript-reader.d.ts +20 -0
- package/dist/oc-transcript-reader.js +61 -0
- package/dist/pi-transcript-reader.d.ts +1 -0
- package/dist/status.js +22 -2
- package/dist/storage.d.ts +7 -1
- package/dist/storage.js +28 -7
- package/dist/transcript-reader.d.ts +19 -0
- package/dist/transcript-reader.js +17 -3
- package/dist/types.d.ts +10 -0
- package/dist/uninstall.js +31 -1
- package/hermes-plugin/hicortex/README.md +77 -0
- package/hermes-plugin/hicortex/__init__.py +17 -0
- package/hermes-plugin/hicortex/client.py +162 -0
- package/hermes-plugin/hicortex/config.py +105 -0
- package/hermes-plugin/hicortex/plugin.yaml +12 -0
- package/hermes-plugin/hicortex/provider.py +432 -0
- package/openclaw.plugin.json +17 -44
- package/package.json +7 -5
- package/dist/pro-loader.d.ts +0 -33
- package/dist/pro-loader.js +0 -187
package/dist/llm.js
CHANGED
|
@@ -2,76 +2,40 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Multi-provider LLM client for consolidation and distillation.
|
|
4
4
|
*
|
|
5
|
-
* Resolution
|
|
6
|
-
* 1.
|
|
7
|
-
* 2.
|
|
8
|
-
*
|
|
9
|
-
* 4. Fallback: Ollama at http://localhost:11434
|
|
5
|
+
* Resolution (resolveExplicitLlmConfig):
|
|
6
|
+
* 1. Explicit config-file overrides (llmBaseUrl + llmApiKey + llmModel)
|
|
7
|
+
* 2. Hicortex-specific env vars (HICORTEX_LLM_BASE_URL + HICORTEX_LLM_API_KEY + HICORTEX_LLM_MODEL)
|
|
8
|
+
* Returns null when nothing explicit is set — no silent defaults.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 3. OPENAI_API_KEY → gpt-5.4-nano
|
|
15
|
-
* 4. GOOGLE_API_KEY → gemini-2.5-flash
|
|
16
|
-
* 5. Claude CLI fallback (uses subscription, no API key needed)
|
|
17
|
-
* 6. Fallback: Ollama at http://localhost:11434
|
|
10
|
+
* Explicit backends (handled by call sites before resolveExplicitLlmConfig):
|
|
11
|
+
* - llmBackend: "claude-cli" → claudeCliConfig()
|
|
12
|
+
* - llmBackend: "ollama" → explicit ollama LlmConfig
|
|
18
13
|
*
|
|
19
14
|
* Supports any OpenAI-compatible endpoint plus first-class support for
|
|
20
15
|
* OpenAI, Anthropic, Google, Ollama, OpenRouter, and Claude CLI.
|
|
21
16
|
*/
|
|
22
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.LlmClient = exports.RateLimitError = void 0;
|
|
24
|
-
exports.
|
|
25
|
-
exports.resolveLlmConfigForCC = resolveLlmConfigForCC;
|
|
18
|
+
exports.LlmClient = exports.RateLimitError = exports.resolveLlmConfigForCC = void 0;
|
|
19
|
+
exports.resolveExplicitLlmConfig = resolveExplicitLlmConfig;
|
|
26
20
|
exports.findClaudeBinary = findClaudeBinary;
|
|
27
21
|
exports.claudeCliConfig = claudeCliConfig;
|
|
28
22
|
exports.probeOllama = probeOllama;
|
|
29
23
|
exports.probeOllamaModel = probeOllamaModel;
|
|
30
|
-
exports.
|
|
31
|
-
const node_fs_1 = require("node:fs");
|
|
32
|
-
const node_path_1 = require("node:path");
|
|
33
|
-
const node_os_1 = require("node:os");
|
|
24
|
+
exports.resolveDistillFallback = resolveDistillFallback;
|
|
34
25
|
/**
|
|
35
|
-
* Resolve LLM configuration from
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
reflectModel: pluginConfig.reflectModel ?? pluginConfig.llmModel ?? "qwen3.5:cloud",
|
|
46
|
-
provider,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
// 2. OpenClaw config file
|
|
50
|
-
const ocConfig = readOpenClawConfig();
|
|
51
|
-
if (ocConfig) {
|
|
52
|
-
return ocConfig;
|
|
53
|
-
}
|
|
54
|
-
// 3. Environment variables
|
|
55
|
-
const envConfig = resolveFromEnv();
|
|
56
|
-
if (envConfig) {
|
|
57
|
-
return envConfig;
|
|
58
|
-
}
|
|
59
|
-
// 4. Fallback: Ollama
|
|
60
|
-
return {
|
|
61
|
-
baseUrl: "http://localhost:11434",
|
|
62
|
-
apiKey: "",
|
|
63
|
-
model: "qwen3.5:4b",
|
|
64
|
-
reflectModel: "qwen3.5:cloud",
|
|
65
|
-
provider: "ollama",
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Resolve LLM configuration for Claude Code (no OC config file).
|
|
70
|
-
* Uses env vars only — CC users always have ANTHROPIC_API_KEY.
|
|
71
|
-
* Defaults to Haiku for distillation/scoring (~$0.50/mo).
|
|
26
|
+
* Resolve LLM configuration from explicit config-file overrides or
|
|
27
|
+
* Hicortex-specific env vars only. Returns null when nothing explicit is set.
|
|
28
|
+
*
|
|
29
|
+
* Call sites (mcp-server, nightly) handle the named backends (claude-cli,
|
|
30
|
+
* ollama) before reaching this function. Only call this for the "other
|
|
31
|
+
* provider" / API-key case where no named backend is in config.json.
|
|
32
|
+
*
|
|
33
|
+
* NO implicit fallbacks: ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY
|
|
34
|
+
* alone in the environment do NOT configure an LLM — the user must have
|
|
35
|
+
* chosen a provider via `npx @gamaze/hicortex init`.
|
|
72
36
|
*/
|
|
73
|
-
function
|
|
74
|
-
// 1. Explicit overrides (
|
|
37
|
+
function resolveExplicitLlmConfig(overrides) {
|
|
38
|
+
// 1. Explicit config-file overrides (both baseUrl and apiKey required)
|
|
75
39
|
if (overrides?.llmBaseUrl && overrides?.llmApiKey) {
|
|
76
40
|
const provider = detectProvider(overrides.llmBaseUrl);
|
|
77
41
|
return {
|
|
@@ -82,7 +46,7 @@ function resolveLlmConfigForCC(overrides) {
|
|
|
82
46
|
provider,
|
|
83
47
|
};
|
|
84
48
|
}
|
|
85
|
-
// 2. Hicortex-specific env vars
|
|
49
|
+
// 2. Hicortex-specific env vars (both base URL and API key required)
|
|
86
50
|
const hcBaseUrl = process.env.HICORTEX_LLM_BASE_URL;
|
|
87
51
|
const hcApiKey = process.env.HICORTEX_LLM_API_KEY;
|
|
88
52
|
const hcModel = process.env.HICORTEX_LLM_MODEL;
|
|
@@ -96,52 +60,14 @@ function resolveLlmConfigForCC(overrides) {
|
|
|
96
60
|
provider,
|
|
97
61
|
};
|
|
98
62
|
}
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
if (anthropicKey) {
|
|
102
|
-
return {
|
|
103
|
-
baseUrl: process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
|
|
104
|
-
apiKey: anthropicKey,
|
|
105
|
-
model: "claude-haiku-4-5-20251001",
|
|
106
|
-
reflectModel: "claude-sonnet-4-6",
|
|
107
|
-
provider: "anthropic",
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
const openaiKey = process.env.OPENAI_API_KEY;
|
|
111
|
-
if (openaiKey) {
|
|
112
|
-
const baseUrl = process.env.OPENAI_BASE_URL ?? "https://api.openai.com";
|
|
113
|
-
return {
|
|
114
|
-
baseUrl,
|
|
115
|
-
apiKey: openaiKey,
|
|
116
|
-
model: "gpt-5.4-nano",
|
|
117
|
-
reflectModel: "gpt-5.4-nano",
|
|
118
|
-
provider: detectProvider(baseUrl),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
const googleKey = process.env.GOOGLE_API_KEY;
|
|
122
|
-
if (googleKey) {
|
|
123
|
-
return {
|
|
124
|
-
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
125
|
-
apiKey: googleKey,
|
|
126
|
-
model: "gemini-2.5-flash",
|
|
127
|
-
reflectModel: "gemini-2.5-flash",
|
|
128
|
-
provider: "google",
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
// 4. Claude CLI fallback (subscription users)
|
|
132
|
-
const claudePath = findClaudeBinary();
|
|
133
|
-
if (claudePath) {
|
|
134
|
-
return claudeCliConfig(claudePath);
|
|
135
|
-
}
|
|
136
|
-
// 5. Ollama fallback (truly last resort)
|
|
137
|
-
return {
|
|
138
|
-
baseUrl: "http://localhost:11434",
|
|
139
|
-
apiKey: "",
|
|
140
|
-
model: "qwen3.5:4b",
|
|
141
|
-
reflectModel: "qwen3.5:4b",
|
|
142
|
-
provider: "ollama",
|
|
143
|
-
};
|
|
63
|
+
// Nothing explicit — caller decides what to do (recall-only mode, loud warning, etc.)
|
|
64
|
+
return null;
|
|
144
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* @deprecated Use resolveExplicitLlmConfig. This alias exists only to ease
|
|
68
|
+
* the transition for any lingering call sites — remove after 0.10.0 ships.
|
|
69
|
+
*/
|
|
70
|
+
exports.resolveLlmConfigForCC = resolveExplicitLlmConfig;
|
|
145
71
|
function detectProvider(url) {
|
|
146
72
|
const u = url.toLowerCase();
|
|
147
73
|
if (u.includes("ollama") || u.includes(":11434"))
|
|
@@ -154,153 +80,6 @@ function detectProvider(url) {
|
|
|
154
80
|
return "google";
|
|
155
81
|
return "openai";
|
|
156
82
|
}
|
|
157
|
-
function readOpenClawConfig() {
|
|
158
|
-
try {
|
|
159
|
-
const configPath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "openclaw.json");
|
|
160
|
-
const raw = (0, node_fs_1.readFileSync)(configPath, "utf-8");
|
|
161
|
-
const config = JSON.parse(raw);
|
|
162
|
-
const primary = config?.agents?.defaults?.model?.primary;
|
|
163
|
-
if (!primary)
|
|
164
|
-
return null;
|
|
165
|
-
// primary format is "provider/model" (e.g. "openai/gpt-5", "anthropic/claude-sonnet-4-6")
|
|
166
|
-
if (typeof primary === "string" && (primary.includes("/") || primary.includes(":"))) {
|
|
167
|
-
const sep = primary.includes("/") ? "/" : ":";
|
|
168
|
-
const [providerHint, ...rest] = primary.split(sep);
|
|
169
|
-
const model = rest.join(sep);
|
|
170
|
-
// Accept any provider name — if it's in our URL map, we know it
|
|
171
|
-
const hint = providerHint.toLowerCase();
|
|
172
|
-
const provider = (hint in PROVIDER_BASE_URLS ? hint : "openai");
|
|
173
|
-
// Resolve base URL: OC config → per-agent models.json → built-in defaults
|
|
174
|
-
const baseUrl = readOcProviderBaseUrl(config, providerHint) ??
|
|
175
|
-
getDefaultUrlForProvider(provider);
|
|
176
|
-
// Resolve API key: OC auth-profiles.json → env vars
|
|
177
|
-
const apiKey = readOcAuthKey(providerHint) ??
|
|
178
|
-
getEnvKeyForProvider(provider);
|
|
179
|
-
if (!apiKey && provider !== "ollama")
|
|
180
|
-
return null;
|
|
181
|
-
return {
|
|
182
|
-
baseUrl,
|
|
183
|
-
apiKey: apiKey ?? "",
|
|
184
|
-
model,
|
|
185
|
-
reflectModel: model,
|
|
186
|
-
provider,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// Config file doesn't exist or is invalid
|
|
192
|
-
}
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Read provider base URL from openclaw.json → models.providers.<name>.baseUrl
|
|
197
|
-
*/
|
|
198
|
-
function readOcProviderBaseUrl(config, provider) {
|
|
199
|
-
const providerConfig = config?.models?.providers?.[provider];
|
|
200
|
-
if (providerConfig?.baseUrl)
|
|
201
|
-
return providerConfig.baseUrl;
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Read API key from OC's per-agent auth-profiles.json.
|
|
206
|
-
* Scans all agent dirs for a matching provider profile.
|
|
207
|
-
*/
|
|
208
|
-
function readOcAuthKey(provider) {
|
|
209
|
-
try {
|
|
210
|
-
const { readdirSync } = require("node:fs");
|
|
211
|
-
const agentsDir = (0, node_path_1.join)((0, node_os_1.homedir)(), ".openclaw", "agents");
|
|
212
|
-
const agents = readdirSync(agentsDir);
|
|
213
|
-
for (const agentId of agents) {
|
|
214
|
-
try {
|
|
215
|
-
const authPath = (0, node_path_1.join)(agentsDir, agentId, "agent", "auth-profiles.json");
|
|
216
|
-
const raw = (0, node_fs_1.readFileSync)(authPath, "utf-8");
|
|
217
|
-
const auth = JSON.parse(raw);
|
|
218
|
-
const profiles = auth?.profiles ?? {};
|
|
219
|
-
// Look for a profile matching the provider (e.g. "openai:default")
|
|
220
|
-
for (const [profileId, profile] of Object.entries(profiles)) {
|
|
221
|
-
const p = profile;
|
|
222
|
-
if (p?.provider === provider ||
|
|
223
|
-
profileId.startsWith(`${provider}:`)) {
|
|
224
|
-
if (p?.key)
|
|
225
|
-
return p.key;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch {
|
|
230
|
-
// Skip agents without auth
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
// No agents dir
|
|
236
|
-
}
|
|
237
|
-
return undefined;
|
|
238
|
-
}
|
|
239
|
-
function resolveFromEnv() {
|
|
240
|
-
const openaiKey = process.env.OPENAI_API_KEY;
|
|
241
|
-
const openaiBaseUrl = process.env.OPENAI_BASE_URL;
|
|
242
|
-
if (openaiKey) {
|
|
243
|
-
const baseUrl = openaiBaseUrl ?? "https://api.openai.com";
|
|
244
|
-
const provider = detectProvider(baseUrl);
|
|
245
|
-
return {
|
|
246
|
-
baseUrl,
|
|
247
|
-
apiKey: openaiKey,
|
|
248
|
-
model: process.env.OPENAI_MODEL ?? "gpt-5.4-nano",
|
|
249
|
-
reflectModel: process.env.OPENAI_MODEL ?? "gpt-5.4-nano",
|
|
250
|
-
provider,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
254
|
-
if (anthropicKey) {
|
|
255
|
-
return {
|
|
256
|
-
baseUrl: process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com",
|
|
257
|
-
apiKey: anthropicKey,
|
|
258
|
-
model: "claude-sonnet-4-6",
|
|
259
|
-
reflectModel: "claude-sonnet-4-6",
|
|
260
|
-
provider: "anthropic",
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
const googleKey = process.env.GOOGLE_API_KEY;
|
|
264
|
-
if (googleKey) {
|
|
265
|
-
return {
|
|
266
|
-
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
267
|
-
apiKey: googleKey,
|
|
268
|
-
model: "gemini-2.5-flash",
|
|
269
|
-
reflectModel: "gemini-2.5-flash",
|
|
270
|
-
provider: "google",
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
function getEnvKeyForProvider(provider) {
|
|
276
|
-
switch (provider) {
|
|
277
|
-
case "openai":
|
|
278
|
-
return process.env.OPENAI_API_KEY;
|
|
279
|
-
case "anthropic":
|
|
280
|
-
return process.env.ANTHROPIC_API_KEY;
|
|
281
|
-
case "google":
|
|
282
|
-
return process.env.GOOGLE_API_KEY;
|
|
283
|
-
default:
|
|
284
|
-
return undefined;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Default base URLs for first-class supported providers.
|
|
289
|
-
*
|
|
290
|
-
* For any other provider, set llmBaseUrl explicitly in your config or use
|
|
291
|
-
* an OpenAI-compatible endpoint. The detectProvider() function will treat
|
|
292
|
-
* unknown URLs as openai-compatible by default.
|
|
293
|
-
*/
|
|
294
|
-
const PROVIDER_BASE_URLS = {
|
|
295
|
-
openai: "https://api.openai.com/v1",
|
|
296
|
-
anthropic: "https://api.anthropic.com",
|
|
297
|
-
google: "https://generativelanguage.googleapis.com/v1beta",
|
|
298
|
-
ollama: "http://localhost:11434",
|
|
299
|
-
openrouter: "https://openrouter.ai/api",
|
|
300
|
-
};
|
|
301
|
-
function getDefaultUrlForProvider(provider) {
|
|
302
|
-
return PROVIDER_BASE_URLS[provider.toLowerCase()] ?? "https://api.openai.com/v1";
|
|
303
|
-
}
|
|
304
83
|
/**
|
|
305
84
|
* Find the claude CLI binary. Returns the full path or null.
|
|
306
85
|
*/
|
|
@@ -377,7 +156,7 @@ async function probeOllama(baseUrl = "http://localhost:11434") {
|
|
|
377
156
|
* - `ok: false, reason: "unreachable"` — network failure or non-2xx.
|
|
378
157
|
* - `ok: false, reason: "model_missing"` — endpoint is up but the
|
|
379
158
|
* model isn't listed (the exact case that caused data loss when
|
|
380
|
-
*
|
|
159
|
+
* a remote Ollama box didn't have the distill model loaded).
|
|
381
160
|
*
|
|
382
161
|
* Matches on exact name OR name prefix ("qwen3.5:35b" matches "qwen3.5:35b-a3b").
|
|
383
162
|
*/
|
|
@@ -398,25 +177,65 @@ async function probeOllamaModel(baseUrl, modelName) {
|
|
|
398
177
|
}
|
|
399
178
|
}
|
|
400
179
|
/**
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
180
|
+
* Resolve the distillation endpoint before a /distill request.
|
|
181
|
+
*
|
|
182
|
+
* @param config LlmConfig (mutated in "local" mode when fallback is used)
|
|
183
|
+
* @param mode
|
|
184
|
+
* "strict" (default) — when a separate distillBaseUrl is configured and its
|
|
185
|
+
* Ollama probe fails, return "abort" immediately WITHOUT mutating config.
|
|
186
|
+
* The session is not distilled now; the nightly watermark is not advanced,
|
|
187
|
+
* so the session is re-shipped on the next run (harness stores retain raw
|
|
188
|
+
* for 30–90 days — the retry IS the queue). Prefer this to producing
|
|
189
|
+
* low-quality memories from a weak fallback model.
|
|
190
|
+
* "local" — legacy 0.9.0 behaviour: fall back to the base endpoint (local
|
|
191
|
+
* Ollama or API provider) when the remote is down. Mutates config IN PLACE
|
|
192
|
+
* to repoint distill* at the fallback.
|
|
193
|
+
*
|
|
194
|
+
* Returns:
|
|
195
|
+
* "ok" — remote distill endpoint healthy, or no separate endpoint set
|
|
196
|
+
* "fellback" — ("local" mode only) remote down; distill redirected to base
|
|
197
|
+
* "abort" — remote down and fallback not allowed (strict) or both down (local)
|
|
404
198
|
*/
|
|
405
|
-
async function
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
199
|
+
async function resolveDistillFallback(config, mode = "strict") {
|
|
200
|
+
const distillProvider = config.distillProvider ?? config.provider;
|
|
201
|
+
// Only a remote Ollama distill endpoint can go unreachable mid-run; API
|
|
202
|
+
// providers are cloud-reachable and need no fallback.
|
|
203
|
+
if (!config.distillBaseUrl || distillProvider !== "ollama")
|
|
204
|
+
return "ok";
|
|
205
|
+
const distillModel = config.distillModel ?? config.model;
|
|
206
|
+
const remote = await probeOllamaModel(config.distillBaseUrl, distillModel);
|
|
207
|
+
if (remote.ok)
|
|
208
|
+
return "ok";
|
|
209
|
+
const reason = remote.reason === "unreachable"
|
|
210
|
+
? `remote distill endpoint unreachable (${config.distillBaseUrl})`
|
|
211
|
+
: `remote distill model not loaded (${distillModel} on ${config.distillBaseUrl})`;
|
|
212
|
+
if (mode === "strict") {
|
|
213
|
+
// Do not mutate config. Log once and let the caller return 503 so the
|
|
214
|
+
// nightly watermark stays put and the session is retried next run.
|
|
215
|
+
console.error(`[hicortex] ABORT: ${reason} — session will be retried next run`);
|
|
216
|
+
return "abort";
|
|
217
|
+
}
|
|
218
|
+
// "local" mode: fall back to the base endpoint.
|
|
219
|
+
// If the base is Ollama, verify it is actually up before committing;
|
|
220
|
+
// if the base is an API provider, it is cloud-reachable.
|
|
221
|
+
if (config.provider === "ollama") {
|
|
222
|
+
const local = await probeOllamaModel(config.baseUrl, config.model);
|
|
223
|
+
if (!local.ok) {
|
|
224
|
+
console.error(`[hicortex] ABORT: ${reason}, and local fallback (${config.model} on ${config.baseUrl}) also unavailable — retry next run`);
|
|
225
|
+
return "abort";
|
|
226
|
+
}
|
|
227
|
+
config.distillBaseUrl = config.baseUrl;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// Base is an API provider — route distill through it (no separate baseUrl).
|
|
231
|
+
config.distillBaseUrl = undefined;
|
|
232
|
+
}
|
|
233
|
+
config.distillModel = config.model;
|
|
234
|
+
config.distillProvider = config.provider;
|
|
235
|
+
config.distillApiKey = config.apiKey;
|
|
236
|
+
console.warn(`[hicortex] ${reason} — falling back to base endpoint for distillation ` +
|
|
237
|
+
`(${config.provider}/${config.model}). Lower quality, but capture continues.`);
|
|
238
|
+
return "fellback";
|
|
420
239
|
}
|
|
421
240
|
// ---------------------------------------------------------------------------
|
|
422
241
|
// LLM Client class
|