@elisym/cli 0.11.5 → 0.12.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/dist/index.js +162 -38
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { readFileSync, readdirSync, statSync, renameSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join, resolve, basename, relative, sep } from 'node:path';
|
|
4
4
|
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, USDC_SOLANA_DEVNET, ElisymClient, MediaService, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, LIMITS, calculateProtocolFee, BoundedSet, KIND_JOB_FEEDBACK, NATIVE_SOL } from '@elisym/sdk';
|
|
5
|
-
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
5
|
+
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
6
6
|
import { isAddress, createSolanaRpc, address } from '@solana/kit';
|
|
7
7
|
import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools';
|
|
8
8
|
import YAML from 'yaml';
|
|
9
9
|
import { Command } from 'commander';
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
|
-
import { LlmHealthMonitor,
|
|
11
|
+
import { LlmHealthMonitor, startLlmRecovery, createFreeLlmLimiterSet, ScriptBillingExhaustedError, FREE_LLM_GLOBAL_KEY, freeLlmCustomerKey } from '@elisym/sdk/llm-health';
|
|
12
12
|
import { lookup } from 'node:dns/promises';
|
|
13
13
|
import { Socket } from 'node:net';
|
|
14
14
|
import pino from 'pino';
|
|
@@ -341,7 +341,7 @@ var init_anthropic = __esm({
|
|
|
341
341
|
// src/llm/providers/openai-compatible.ts
|
|
342
342
|
function createOpenAICompatibleProvider(config) {
|
|
343
343
|
const billingMarkers = [...DEFAULT_BILLING_MARKERS, ...config.extraBillingMarkers ?? []];
|
|
344
|
-
function
|
|
344
|
+
function bodyLooksLikeBilling4(body) {
|
|
345
345
|
const lower = body.toLowerCase();
|
|
346
346
|
return billingMarkers.some((marker) => lower.includes(marker));
|
|
347
347
|
}
|
|
@@ -423,7 +423,7 @@ function createOpenAICompatibleProvider(config) {
|
|
|
423
423
|
if (response.status === 402) {
|
|
424
424
|
return { ok: false, reason: "billing", status: response.status, body };
|
|
425
425
|
}
|
|
426
|
-
if ((response.status === 400 || response.status === 429) &&
|
|
426
|
+
if ((response.status === 400 || response.status === 429) && bodyLooksLikeBilling4(body)) {
|
|
427
427
|
return { ok: false, reason: "billing", status: response.status, body };
|
|
428
428
|
}
|
|
429
429
|
return {
|
|
@@ -1163,6 +1163,7 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
1163
1163
|
}
|
|
1164
1164
|
const llmApiKeys = Object.fromEntries(collectedKeys);
|
|
1165
1165
|
await writeYamlInitial(created.dir, yaml);
|
|
1166
|
+
await writeExampleSkillTemplate(created.dir);
|
|
1166
1167
|
await writeSecrets(
|
|
1167
1168
|
created.dir,
|
|
1168
1169
|
{
|
|
@@ -1967,6 +1968,29 @@ function resolveJobAsset(tags, skills) {
|
|
|
1967
1968
|
const skill = skills.route(tags);
|
|
1968
1969
|
return skill?.asset ?? NATIVE_SOL;
|
|
1969
1970
|
}
|
|
1971
|
+
var BILLING_BODY_MARKERS3 = ["credit balance", "billing", "insufficient", "insufficient_quota"];
|
|
1972
|
+
function bodyLooksLikeBilling3(body) {
|
|
1973
|
+
const lower = body.toLowerCase();
|
|
1974
|
+
return BILLING_BODY_MARKERS3.some((marker) => lower.includes(marker));
|
|
1975
|
+
}
|
|
1976
|
+
function resolveHealthPair(skill) {
|
|
1977
|
+
if (!skill) {
|
|
1978
|
+
return null;
|
|
1979
|
+
}
|
|
1980
|
+
if (skill.mode === "llm" && skill.resolvedTriple) {
|
|
1981
|
+
return {
|
|
1982
|
+
provider: skill.resolvedTriple.provider,
|
|
1983
|
+
model: skill.resolvedTriple.model
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1986
|
+
if (skill.mode !== "llm" && skill.llmOverride?.provider && skill.llmOverride?.model) {
|
|
1987
|
+
return {
|
|
1988
|
+
provider: skill.llmOverride.provider,
|
|
1989
|
+
model: skill.llmOverride.model
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
return null;
|
|
1993
|
+
}
|
|
1970
1994
|
var RATE_LIMIT_WINDOW_MS = 10 * 60 * 1e3;
|
|
1971
1995
|
var MAX_JOBS_PER_CUSTOMER = 20;
|
|
1972
1996
|
var GLOBAL_MAX_JOBS_PER_WINDOW = 200;
|
|
@@ -2012,6 +2036,76 @@ var AgentRuntime = class {
|
|
|
2012
2036
|
* the operator's API key.
|
|
2013
2037
|
*/
|
|
2014
2038
|
freeLlmLimiters = createFreeLlmLimiterSet();
|
|
2039
|
+
/**
|
|
2040
|
+
* Inspect an error thrown by `skill.execute()` and, when it carries a
|
|
2041
|
+
* billing/invalid signal from the LLM provider, flip the matching
|
|
2042
|
+
* (provider, model) pair to unhealthy via the health monitor. The next
|
|
2043
|
+
* job hitting the same pair is then refused at the preflight gate
|
|
2044
|
+
* before payment, so customers don't keep paying for jobs that will
|
|
2045
|
+
* fail. Recovery happens through the lazy recovery loop.
|
|
2046
|
+
*
|
|
2047
|
+
* Two error shapes are recognized:
|
|
2048
|
+
*
|
|
2049
|
+
* - `ScriptBillingExhaustedError` - thrown by SDK script skills when
|
|
2050
|
+
* the spawned process exits with `SCRIPT_EXIT_BILLING_EXHAUSTED`.
|
|
2051
|
+
* Pair comes from `skill.llmOverride` (operator-declared).
|
|
2052
|
+
*
|
|
2053
|
+
* - LLM provider HTTP error from `mode: 'llm'` - bare `Error` whose
|
|
2054
|
+
* message starts with "<Provider> API error: <status> <body>" (the
|
|
2055
|
+
* format every CLI provider currently uses). We classify on status:
|
|
2056
|
+
* 402 / 401 / 403 -> mark unhealthy. Body markers (`credit
|
|
2057
|
+
* balance`, `billing`, `insufficient`) catch the 400-on-billing case
|
|
2058
|
+
* Anthropic returns and the 429+`insufficient_quota` case OpenAI
|
|
2059
|
+
* and the openai-compatible providers (xAI/Google/DeepSeek) return.
|
|
2060
|
+
* Pair comes from `skill.resolvedTriple`.
|
|
2061
|
+
*
|
|
2062
|
+
* Anything else is a transient/skill error and does NOT touch health
|
|
2063
|
+
* state - the recovery loop should not be poisoned by skill bugs.
|
|
2064
|
+
*/
|
|
2065
|
+
markHealthFromExecuteError(skill, err, log, jobId) {
|
|
2066
|
+
if (!this.healthMonitor) {
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
const tag = `[${jobId.slice(0, 8)}]`;
|
|
2070
|
+
if (err instanceof ScriptBillingExhaustedError) {
|
|
2071
|
+
const provider = skill.llmOverride?.provider;
|
|
2072
|
+
const model = skill.llmOverride?.model;
|
|
2073
|
+
if (!provider || !model) {
|
|
2074
|
+
log(
|
|
2075
|
+
`${tag} Script returned exit ${err.exitCode} (billing-exhausted) but skill "${skill.name}" did not declare provider/model in SKILL.md - cannot gate future jobs.`
|
|
2076
|
+
);
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
log(
|
|
2080
|
+
`${tag} Script signaled billing-exhausted (exit ${err.exitCode}). Marking ${provider}/${model} unhealthy; future jobs against this pair will be refused until recovery probe succeeds.`
|
|
2081
|
+
);
|
|
2082
|
+
this.healthMonitor.markUnhealthyFromJob(provider, model, "billing", err.message);
|
|
2083
|
+
return;
|
|
2084
|
+
}
|
|
2085
|
+
if (skill.mode === "llm" && skill.resolvedTriple) {
|
|
2086
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2087
|
+
const match = /API error:\s*(\d{3})\b\s*(.*)/i.exec(message);
|
|
2088
|
+
if (!match) {
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
const status = Number(match[1]);
|
|
2092
|
+
const body = (match[2] ?? "").slice(0, 200);
|
|
2093
|
+
const isBillingStatus = status === 402;
|
|
2094
|
+
const isAuthStatus = status === 401 || status === 403;
|
|
2095
|
+
const isBilling400 = status === 400 && bodyLooksLikeBilling3(body);
|
|
2096
|
+
const isBilling429 = status === 429 && bodyLooksLikeBilling3(body);
|
|
2097
|
+
if (!isBillingStatus && !isAuthStatus && !isBilling400 && !isBilling429) {
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
const reason = isAuthStatus ? "invalid" : "billing";
|
|
2101
|
+
const provider = skill.resolvedTriple.provider;
|
|
2102
|
+
const model = skill.resolvedTriple.model;
|
|
2103
|
+
log(
|
|
2104
|
+
`${tag} LLM provider returned HTTP ${status} (${reason}). Marking ${provider}/${model} unhealthy; future jobs against this pair will be refused until recovery probe succeeds.`
|
|
2105
|
+
);
|
|
2106
|
+
this.healthMonitor.markUnhealthyFromJob(provider, model, reason, body);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2015
2109
|
/** Fetch on-chain protocol config (fee, treasury). Always fetches fresh to avoid stale treasury. */
|
|
2016
2110
|
async fetchProtocolConfig() {
|
|
2017
2111
|
if (this.config.network !== "devnet") {
|
|
@@ -2192,12 +2286,10 @@ var AgentRuntime = class {
|
|
|
2192
2286
|
throw new Error(`Input too long: ${job.input.length} chars (max ${LIMITS.MAX_INPUT_LENGTH})`);
|
|
2193
2287
|
}
|
|
2194
2288
|
const matched = this.skills.route(job.tags);
|
|
2195
|
-
|
|
2289
|
+
const healthPair = resolveHealthPair(matched);
|
|
2290
|
+
if (this.healthMonitor && healthPair) {
|
|
2196
2291
|
try {
|
|
2197
|
-
await this.healthMonitor.assertReady(
|
|
2198
|
-
matched.resolvedTriple.provider,
|
|
2199
|
-
matched.resolvedTriple.model
|
|
2200
|
-
);
|
|
2292
|
+
await this.healthMonitor.assertReady(healthPair.provider, healthPair.model);
|
|
2201
2293
|
} catch (err) {
|
|
2202
2294
|
const detail = err instanceof Error ? err.message : String(err);
|
|
2203
2295
|
log(`[${job.jobId.slice(0, 8)}] LLM health gate refused job: ${detail}`);
|
|
@@ -2244,15 +2336,21 @@ var AgentRuntime = class {
|
|
|
2244
2336
|
throw new Error("No skill matched for tags: " + job.tags.join(", "));
|
|
2245
2337
|
}
|
|
2246
2338
|
log(`[${job.jobId.slice(0, 8)}] Executing skill: ${skill.name}`);
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2339
|
+
let output;
|
|
2340
|
+
try {
|
|
2341
|
+
output = await skill.execute(
|
|
2342
|
+
{
|
|
2343
|
+
data: job.input,
|
|
2344
|
+
inputType: job.inputType,
|
|
2345
|
+
tags: job.tags,
|
|
2346
|
+
jobId: job.jobId
|
|
2347
|
+
},
|
|
2348
|
+
{ ...this.skillCtx, signal }
|
|
2349
|
+
);
|
|
2350
|
+
} catch (err) {
|
|
2351
|
+
this.markHealthFromExecuteError(skill, err, log, job.jobId);
|
|
2352
|
+
throw err;
|
|
2353
|
+
}
|
|
2256
2354
|
this.ledger.markExecuted(job.jobId, output.data);
|
|
2257
2355
|
log(`[${job.jobId.slice(0, 8)}] Skill completed, delivering result`);
|
|
2258
2356
|
const eventId = await this.transport.deliverResult(job, output.data, netAmount);
|
|
@@ -2638,6 +2736,7 @@ var StaticFileSkill = class {
|
|
|
2638
2736
|
image;
|
|
2639
2737
|
imageFile;
|
|
2640
2738
|
dir;
|
|
2739
|
+
llmOverride;
|
|
2641
2740
|
inner;
|
|
2642
2741
|
constructor(params) {
|
|
2643
2742
|
this.name = params.name;
|
|
@@ -2648,6 +2747,7 @@ var StaticFileSkill = class {
|
|
|
2648
2747
|
this.image = params.image;
|
|
2649
2748
|
this.imageFile = params.imageFile;
|
|
2650
2749
|
this.dir = params.dir;
|
|
2750
|
+
this.llmOverride = params.llmOverride;
|
|
2651
2751
|
this.inner = new StaticFileSkill$1({
|
|
2652
2752
|
name: params.name,
|
|
2653
2753
|
description: params.description,
|
|
@@ -2673,6 +2773,7 @@ var StaticScriptSkill = class {
|
|
|
2673
2773
|
image;
|
|
2674
2774
|
imageFile;
|
|
2675
2775
|
dir;
|
|
2776
|
+
llmOverride;
|
|
2676
2777
|
inner;
|
|
2677
2778
|
constructor(params) {
|
|
2678
2779
|
this.name = params.name;
|
|
@@ -2683,6 +2784,7 @@ var StaticScriptSkill = class {
|
|
|
2683
2784
|
this.image = params.image;
|
|
2684
2785
|
this.imageFile = params.imageFile;
|
|
2685
2786
|
this.dir = params.dir;
|
|
2787
|
+
this.llmOverride = params.llmOverride;
|
|
2686
2788
|
this.inner = new StaticScriptSkill$1({
|
|
2687
2789
|
name: params.name,
|
|
2688
2790
|
description: params.description,
|
|
@@ -2711,6 +2813,7 @@ var DynamicScriptSkill = class {
|
|
|
2711
2813
|
image;
|
|
2712
2814
|
imageFile;
|
|
2713
2815
|
dir;
|
|
2816
|
+
llmOverride;
|
|
2714
2817
|
inner;
|
|
2715
2818
|
constructor(params) {
|
|
2716
2819
|
this.name = params.name;
|
|
@@ -2721,6 +2824,7 @@ var DynamicScriptSkill = class {
|
|
|
2721
2824
|
this.image = params.image;
|
|
2722
2825
|
this.imageFile = params.imageFile;
|
|
2723
2826
|
this.dir = params.dir;
|
|
2827
|
+
this.llmOverride = params.llmOverride;
|
|
2724
2828
|
this.inner = new DynamicScriptSkill$1({
|
|
2725
2829
|
name: params.name,
|
|
2726
2830
|
description: params.description,
|
|
@@ -2846,7 +2950,8 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
2846
2950
|
outputFilePath,
|
|
2847
2951
|
image: parsed.image,
|
|
2848
2952
|
imageFile: parsed.imageFile,
|
|
2849
|
-
dir: entryPath
|
|
2953
|
+
dir: entryPath,
|
|
2954
|
+
llmOverride: parsed.llmOverride
|
|
2850
2955
|
});
|
|
2851
2956
|
break;
|
|
2852
2957
|
}
|
|
@@ -2874,7 +2979,8 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
2874
2979
|
scriptEnv,
|
|
2875
2980
|
image: parsed.image,
|
|
2876
2981
|
imageFile: parsed.imageFile,
|
|
2877
|
-
dir: entryPath
|
|
2982
|
+
dir: entryPath,
|
|
2983
|
+
llmOverride: parsed.llmOverride
|
|
2878
2984
|
});
|
|
2879
2985
|
break;
|
|
2880
2986
|
}
|
|
@@ -3292,12 +3398,16 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3292
3398
|
process.exit(1);
|
|
3293
3399
|
}
|
|
3294
3400
|
const llmSkills = allSkills.filter((skill) => skill.mode === "llm");
|
|
3401
|
+
const scriptDepSkills = allSkills.filter(
|
|
3402
|
+
(skill) => skill.mode !== "llm" && skill.llmOverride?.provider && skill.llmOverride?.model
|
|
3403
|
+
);
|
|
3404
|
+
const monitorPairs = /* @__PURE__ */ new Map();
|
|
3295
3405
|
const triplesByKey = /* @__PURE__ */ new Map();
|
|
3296
3406
|
const dependentSkillsByProvider = /* @__PURE__ */ new Map();
|
|
3297
3407
|
let agentDefaultCacheKey;
|
|
3298
3408
|
const llmClientCache = /* @__PURE__ */ new Map();
|
|
3299
3409
|
const healthMonitor = new LlmHealthMonitor();
|
|
3300
|
-
if (llmSkills.length > 0) {
|
|
3410
|
+
if (llmSkills.length > 0 || scriptDepSkills.length > 0) {
|
|
3301
3411
|
const resolutionErrors = [];
|
|
3302
3412
|
for (const skill of llmSkills) {
|
|
3303
3413
|
const skillMdPath = join(skillsDir, skill.name, "SKILL.md");
|
|
@@ -3311,6 +3421,10 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3311
3421
|
}
|
|
3312
3422
|
const cacheKey = cacheKeyFor(result);
|
|
3313
3423
|
triplesByKey.set(cacheKey, result);
|
|
3424
|
+
monitorPairs.set(`${result.provider}::${result.model}`, {
|
|
3425
|
+
provider: result.provider,
|
|
3426
|
+
model: result.model
|
|
3427
|
+
});
|
|
3314
3428
|
skill.resolvedTriple = {
|
|
3315
3429
|
provider: result.provider,
|
|
3316
3430
|
model: result.model,
|
|
@@ -3320,6 +3434,16 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3320
3434
|
list.push(skill.name);
|
|
3321
3435
|
dependentSkillsByProvider.set(result.provider, list);
|
|
3322
3436
|
}
|
|
3437
|
+
for (const skill of scriptDepSkills) {
|
|
3438
|
+
const provider = skill.llmOverride?.provider;
|
|
3439
|
+
const model = skill.llmOverride?.model;
|
|
3440
|
+
monitorPairs.set(`${provider}::${model}`, { provider, model });
|
|
3441
|
+
const list = dependentSkillsByProvider.get(provider) ?? [];
|
|
3442
|
+
if (!list.includes(skill.name)) {
|
|
3443
|
+
list.push(skill.name);
|
|
3444
|
+
}
|
|
3445
|
+
dependentSkillsByProvider.set(provider, list);
|
|
3446
|
+
}
|
|
3323
3447
|
if (resolutionErrors.length > 0) {
|
|
3324
3448
|
for (const message of resolutionErrors) {
|
|
3325
3449
|
console.error(` ! ${message}`);
|
|
@@ -3359,27 +3483,27 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3359
3483
|
console.error("");
|
|
3360
3484
|
process.exit(1);
|
|
3361
3485
|
}
|
|
3362
|
-
for (const [,
|
|
3363
|
-
const apiKey = keyByProvider.get(
|
|
3486
|
+
for (const [, pair] of monitorPairs) {
|
|
3487
|
+
const apiKey = keyByProvider.get(pair.provider);
|
|
3364
3488
|
if (!apiKey) {
|
|
3365
3489
|
continue;
|
|
3366
3490
|
}
|
|
3367
|
-
const envVar = getLlmProvider(
|
|
3368
|
-
process.stdout.write(` Verifying ${
|
|
3369
|
-
const verification = await verifyLlmApiKeyDeep(
|
|
3370
|
-
const descriptor = getLlmProvider(
|
|
3371
|
-
const verifyFn = async (signal) => descriptor ? descriptor.verifyKeyDeep(apiKey,
|
|
3491
|
+
const envVar = getLlmProvider(pair.provider)?.envVar ?? `${pair.provider.toUpperCase()}_API_KEY`;
|
|
3492
|
+
process.stdout.write(` Verifying ${pair.provider} ${pair.model}... `);
|
|
3493
|
+
const verification = await verifyLlmApiKeyDeep(pair.provider, apiKey, pair.model);
|
|
3494
|
+
const descriptor = getLlmProvider(pair.provider);
|
|
3495
|
+
const verifyFn = async (signal) => descriptor ? descriptor.verifyKeyDeep(apiKey, pair.model, signal) : { ok: false, reason: "unavailable", error: "no descriptor" };
|
|
3372
3496
|
healthMonitor.register({
|
|
3373
|
-
provider:
|
|
3374
|
-
model:
|
|
3497
|
+
provider: pair.provider,
|
|
3498
|
+
model: pair.model,
|
|
3375
3499
|
verifyFn
|
|
3376
3500
|
});
|
|
3377
|
-
healthMonitor.seed(
|
|
3501
|
+
healthMonitor.seed(pair.provider, pair.model, verification);
|
|
3378
3502
|
if (verification.ok) {
|
|
3379
3503
|
console.log("ok");
|
|
3380
3504
|
} else if (verification.reason === "invalid") {
|
|
3381
3505
|
console.log("INVALID");
|
|
3382
|
-
console.error(` ! ${
|
|
3506
|
+
console.error(` ! ${pair.provider} rejected the API key (HTTP ${verification.status}).`);
|
|
3383
3507
|
console.error(
|
|
3384
3508
|
` Update it via \`npx @elisym/cli profile ${agentName}\` or set ${envVar} to a valid key.
|
|
3385
3509
|
`
|
|
@@ -3389,7 +3513,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3389
3513
|
console.log("BILLING");
|
|
3390
3514
|
const detail = verification.body ? ` ${verification.body.slice(0, 200)}` : "";
|
|
3391
3515
|
console.error(
|
|
3392
|
-
` ! ${
|
|
3516
|
+
` ! ${pair.provider} reports a billing/quota issue for ${pair.model}.${detail}`
|
|
3393
3517
|
);
|
|
3394
3518
|
console.error(
|
|
3395
3519
|
` Top up the account at the provider console, or set ${envVar} to a key on a funded org.
|
|
@@ -3399,7 +3523,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3399
3523
|
} else {
|
|
3400
3524
|
console.log("unavailable");
|
|
3401
3525
|
console.warn(
|
|
3402
|
-
` ! Could not verify ${
|
|
3526
|
+
` ! Could not verify ${pair.provider} ${pair.model} (${verification.error}). Continuing - jobs will fail if the key is invalid.
|
|
3403
3527
|
`
|
|
3404
3528
|
);
|
|
3405
3529
|
}
|
|
@@ -3421,7 +3545,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3421
3545
|
llmClientCache.set(cacheKey, createLlmClient(config));
|
|
3422
3546
|
}
|
|
3423
3547
|
} else {
|
|
3424
|
-
console.log(" No LLM skills loaded; skipping LLM key check.\n");
|
|
3548
|
+
console.log(" No LLM-dependent skills loaded; skipping LLM key check.\n");
|
|
3425
3549
|
}
|
|
3426
3550
|
const agentDefaultClient = agentDefaultCacheKey !== void 0 ? llmClientCache.get(agentDefaultCacheKey) : void 0;
|
|
3427
3551
|
const getLlm = (override) => {
|
|
@@ -3618,12 +3742,12 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3618
3742
|
logger
|
|
3619
3743
|
});
|
|
3620
3744
|
let llmHeartbeat;
|
|
3621
|
-
if (
|
|
3622
|
-
llmHeartbeat =
|
|
3745
|
+
if (monitorPairs.size > 0) {
|
|
3746
|
+
llmHeartbeat = startLlmRecovery({
|
|
3623
3747
|
monitor: healthMonitor,
|
|
3624
3748
|
log: diagLog
|
|
3625
3749
|
});
|
|
3626
|
-
diagLog("LLM health monitor armed (
|
|
3750
|
+
diagLog("LLM health monitor armed (lazy recovery, 5min interval).");
|
|
3627
3751
|
}
|
|
3628
3752
|
const runtime = new AgentRuntime(
|
|
3629
3753
|
transport,
|