@elisym/cli 0.11.5 → 0.13.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 +2 -0
- package/dist/index.js +183 -40
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -127,6 +127,8 @@ my-project/
|
|
|
127
127
|
|
|
128
128
|
Skills are defined in `SKILL.md` files inside `./skills/<skill-name>/`. Each file has YAML frontmatter (between `---` delimiters) that describes the skill, followed by a markdown body that becomes the LLM system prompt.
|
|
129
129
|
|
|
130
|
+
> Canonical reference for every frontmatter field, execution mode, the LLM health-monitor contract, and the script exit-code convention lives in [`SKILLS.md`](./SKILLS.md). The summary below covers the common cases; consult `SKILLS.md` for the full schema.
|
|
131
|
+
|
|
130
132
|
```markdown
|
|
131
133
|
---
|
|
132
134
|
name: youtube-summary
|
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,36 @@ 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
|
+
var AGENT_UNAVAILABLE_MESSAGE = "Agent temporarily unavailable";
|
|
1973
|
+
var AgentUnavailableError = class extends Error {
|
|
1974
|
+
constructor() {
|
|
1975
|
+
super(AGENT_UNAVAILABLE_MESSAGE);
|
|
1976
|
+
this.name = "AgentUnavailableError";
|
|
1977
|
+
}
|
|
1978
|
+
};
|
|
1979
|
+
function bodyLooksLikeBilling3(body) {
|
|
1980
|
+
const lower = body.toLowerCase();
|
|
1981
|
+
return BILLING_BODY_MARKERS3.some((marker) => lower.includes(marker));
|
|
1982
|
+
}
|
|
1983
|
+
function resolveHealthPair(skill) {
|
|
1984
|
+
if (!skill) {
|
|
1985
|
+
return null;
|
|
1986
|
+
}
|
|
1987
|
+
if (skill.mode === "llm" && skill.resolvedTriple) {
|
|
1988
|
+
return {
|
|
1989
|
+
provider: skill.resolvedTriple.provider,
|
|
1990
|
+
model: skill.resolvedTriple.model
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
if (skill.mode !== "llm" && skill.llmOverride?.provider && skill.llmOverride?.model) {
|
|
1994
|
+
return {
|
|
1995
|
+
provider: skill.llmOverride.provider,
|
|
1996
|
+
model: skill.llmOverride.model
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
return null;
|
|
2000
|
+
}
|
|
1970
2001
|
var RATE_LIMIT_WINDOW_MS = 10 * 60 * 1e3;
|
|
1971
2002
|
var MAX_JOBS_PER_CUSTOMER = 20;
|
|
1972
2003
|
var GLOBAL_MAX_JOBS_PER_WINDOW = 200;
|
|
@@ -2012,6 +2043,78 @@ var AgentRuntime = class {
|
|
|
2012
2043
|
* the operator's API key.
|
|
2013
2044
|
*/
|
|
2014
2045
|
freeLlmLimiters = createFreeLlmLimiterSet();
|
|
2046
|
+
/**
|
|
2047
|
+
* Inspect an error thrown by `skill.execute()` and, when it carries a
|
|
2048
|
+
* billing/invalid signal from the LLM provider, flip the matching
|
|
2049
|
+
* (provider, model) pair to unhealthy via the health monitor. The next
|
|
2050
|
+
* job hitting the same pair is then refused at the preflight gate
|
|
2051
|
+
* before payment, so customers don't keep paying for jobs that will
|
|
2052
|
+
* fail. Recovery happens through the lazy recovery loop.
|
|
2053
|
+
*
|
|
2054
|
+
* Two error shapes are recognized:
|
|
2055
|
+
*
|
|
2056
|
+
* - `ScriptBillingExhaustedError` - thrown by SDK script skills when
|
|
2057
|
+
* the spawned process exits with `SCRIPT_EXIT_BILLING_EXHAUSTED`.
|
|
2058
|
+
* Pair comes from `skill.llmOverride` (operator-declared).
|
|
2059
|
+
*
|
|
2060
|
+
* - LLM provider HTTP error from `mode: 'llm'` - bare `Error` whose
|
|
2061
|
+
* message starts with "<Provider> API error: <status> <body>" (the
|
|
2062
|
+
* format every CLI provider currently uses). We classify on status:
|
|
2063
|
+
* 402 / 401 / 403 -> mark unhealthy. Body markers (`credit
|
|
2064
|
+
* balance`, `billing`, `insufficient`) catch the 400-on-billing case
|
|
2065
|
+
* Anthropic returns and the 429+`insufficient_quota` case OpenAI
|
|
2066
|
+
* and the openai-compatible providers (xAI/Google/DeepSeek) return.
|
|
2067
|
+
* Pair comes from `skill.resolvedTriple`.
|
|
2068
|
+
*
|
|
2069
|
+
* Anything else is a transient/skill error and does NOT touch health
|
|
2070
|
+
* state - the recovery loop should not be poisoned by skill bugs.
|
|
2071
|
+
*/
|
|
2072
|
+
markHealthFromExecuteError(skill, err, log, jobId) {
|
|
2073
|
+
if (!this.healthMonitor) {
|
|
2074
|
+
return false;
|
|
2075
|
+
}
|
|
2076
|
+
const tag = `[${jobId.slice(0, 8)}]`;
|
|
2077
|
+
if (err instanceof ScriptBillingExhaustedError) {
|
|
2078
|
+
const provider = skill.llmOverride?.provider;
|
|
2079
|
+
const model = skill.llmOverride?.model;
|
|
2080
|
+
if (!provider || !model) {
|
|
2081
|
+
log(
|
|
2082
|
+
`${tag} Script returned exit ${err.exitCode} (billing-exhausted) but skill "${skill.name}" did not declare provider/model in SKILL.md - cannot gate future jobs.`
|
|
2083
|
+
);
|
|
2084
|
+
return false;
|
|
2085
|
+
}
|
|
2086
|
+
log(
|
|
2087
|
+
`${tag} Script signaled billing-exhausted (exit ${err.exitCode}). Marking ${provider}/${model} unhealthy; future jobs against this pair will be refused until recovery probe succeeds.`
|
|
2088
|
+
);
|
|
2089
|
+
this.healthMonitor.markUnhealthyFromJob(provider, model, "billing", err.message);
|
|
2090
|
+
return true;
|
|
2091
|
+
}
|
|
2092
|
+
if (skill.mode === "llm" && skill.resolvedTriple) {
|
|
2093
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2094
|
+
const match = /API error:\s*(\d{3})\b\s*(.*)/i.exec(message);
|
|
2095
|
+
if (!match) {
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
const status = Number(match[1]);
|
|
2099
|
+
const body = (match[2] ?? "").slice(0, 200);
|
|
2100
|
+
const isBillingStatus = status === 402;
|
|
2101
|
+
const isAuthStatus = status === 401 || status === 403;
|
|
2102
|
+
const isBilling400 = status === 400 && bodyLooksLikeBilling3(body);
|
|
2103
|
+
const isBilling429 = status === 429 && bodyLooksLikeBilling3(body);
|
|
2104
|
+
if (!isBillingStatus && !isAuthStatus && !isBilling400 && !isBilling429) {
|
|
2105
|
+
return false;
|
|
2106
|
+
}
|
|
2107
|
+
const reason = isAuthStatus ? "invalid" : "billing";
|
|
2108
|
+
const provider = skill.resolvedTriple.provider;
|
|
2109
|
+
const model = skill.resolvedTriple.model;
|
|
2110
|
+
log(
|
|
2111
|
+
`${tag} LLM provider returned HTTP ${status} (${reason}). Marking ${provider}/${model} unhealthy; future jobs against this pair will be refused until recovery probe succeeds.`
|
|
2112
|
+
);
|
|
2113
|
+
this.healthMonitor.markUnhealthyFromJob(provider, model, reason, body);
|
|
2114
|
+
return true;
|
|
2115
|
+
}
|
|
2116
|
+
return false;
|
|
2117
|
+
}
|
|
2015
2118
|
/** Fetch on-chain protocol config (fee, treasury). Always fetches fresh to avoid stale treasury. */
|
|
2016
2119
|
async fetchProtocolConfig() {
|
|
2017
2120
|
if (this.config.network !== "devnet") {
|
|
@@ -2177,7 +2280,14 @@ var AgentRuntime = class {
|
|
|
2177
2280
|
this.ledger.markFailed(job.jobId);
|
|
2178
2281
|
}
|
|
2179
2282
|
this.callbacks.onJobError?.(job.jobId, e.message);
|
|
2180
|
-
|
|
2283
|
+
let safeMessage;
|
|
2284
|
+
if (e instanceof AgentUnavailableError) {
|
|
2285
|
+
safeMessage = e.message;
|
|
2286
|
+
} else if (e.message?.includes("API")) {
|
|
2287
|
+
safeMessage = "Internal processing error";
|
|
2288
|
+
} else {
|
|
2289
|
+
safeMessage = e.message ?? "Unknown error";
|
|
2290
|
+
}
|
|
2181
2291
|
await this.transport.sendFeedback(job, { type: "error", message: safeMessage }).catch(() => {
|
|
2182
2292
|
});
|
|
2183
2293
|
} finally {
|
|
@@ -2192,18 +2302,16 @@ var AgentRuntime = class {
|
|
|
2192
2302
|
throw new Error(`Input too long: ${job.input.length} chars (max ${LIMITS.MAX_INPUT_LENGTH})`);
|
|
2193
2303
|
}
|
|
2194
2304
|
const matched = this.skills.route(job.tags);
|
|
2195
|
-
|
|
2305
|
+
const healthPair = resolveHealthPair(matched);
|
|
2306
|
+
if (this.healthMonitor && healthPair) {
|
|
2196
2307
|
try {
|
|
2197
|
-
await this.healthMonitor.assertReady(
|
|
2198
|
-
matched.resolvedTriple.provider,
|
|
2199
|
-
matched.resolvedTriple.model
|
|
2200
|
-
);
|
|
2308
|
+
await this.healthMonitor.assertReady(healthPair.provider, healthPair.model);
|
|
2201
2309
|
} catch (err) {
|
|
2202
2310
|
const detail = err instanceof Error ? err.message : String(err);
|
|
2203
2311
|
log(`[${job.jobId.slice(0, 8)}] LLM health gate refused job: ${detail}`);
|
|
2204
2312
|
await this.transport.sendFeedback(job, {
|
|
2205
2313
|
type: "error",
|
|
2206
|
-
message:
|
|
2314
|
+
message: AGENT_UNAVAILABLE_MESSAGE
|
|
2207
2315
|
}).catch(() => {
|
|
2208
2316
|
});
|
|
2209
2317
|
return;
|
|
@@ -2244,15 +2352,24 @@ var AgentRuntime = class {
|
|
|
2244
2352
|
throw new Error("No skill matched for tags: " + job.tags.join(", "));
|
|
2245
2353
|
}
|
|
2246
2354
|
log(`[${job.jobId.slice(0, 8)}] Executing skill: ${skill.name}`);
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2355
|
+
let output;
|
|
2356
|
+
try {
|
|
2357
|
+
output = await skill.execute(
|
|
2358
|
+
{
|
|
2359
|
+
data: job.input,
|
|
2360
|
+
inputType: job.inputType,
|
|
2361
|
+
tags: job.tags,
|
|
2362
|
+
jobId: job.jobId
|
|
2363
|
+
},
|
|
2364
|
+
{ ...this.skillCtx, signal }
|
|
2365
|
+
);
|
|
2366
|
+
} catch (err) {
|
|
2367
|
+
const flippedToUnhealthy = this.markHealthFromExecuteError(skill, err, log, job.jobId);
|
|
2368
|
+
if (flippedToUnhealthy) {
|
|
2369
|
+
throw new AgentUnavailableError();
|
|
2370
|
+
}
|
|
2371
|
+
throw err;
|
|
2372
|
+
}
|
|
2256
2373
|
this.ledger.markExecuted(job.jobId, output.data);
|
|
2257
2374
|
log(`[${job.jobId.slice(0, 8)}] Skill completed, delivering result`);
|
|
2258
2375
|
const eventId = await this.transport.deliverResult(job, output.data, netAmount);
|
|
@@ -2638,6 +2755,7 @@ var StaticFileSkill = class {
|
|
|
2638
2755
|
image;
|
|
2639
2756
|
imageFile;
|
|
2640
2757
|
dir;
|
|
2758
|
+
llmOverride;
|
|
2641
2759
|
inner;
|
|
2642
2760
|
constructor(params) {
|
|
2643
2761
|
this.name = params.name;
|
|
@@ -2648,6 +2766,7 @@ var StaticFileSkill = class {
|
|
|
2648
2766
|
this.image = params.image;
|
|
2649
2767
|
this.imageFile = params.imageFile;
|
|
2650
2768
|
this.dir = params.dir;
|
|
2769
|
+
this.llmOverride = params.llmOverride;
|
|
2651
2770
|
this.inner = new StaticFileSkill$1({
|
|
2652
2771
|
name: params.name,
|
|
2653
2772
|
description: params.description,
|
|
@@ -2673,6 +2792,7 @@ var StaticScriptSkill = class {
|
|
|
2673
2792
|
image;
|
|
2674
2793
|
imageFile;
|
|
2675
2794
|
dir;
|
|
2795
|
+
llmOverride;
|
|
2676
2796
|
inner;
|
|
2677
2797
|
constructor(params) {
|
|
2678
2798
|
this.name = params.name;
|
|
@@ -2683,6 +2803,7 @@ var StaticScriptSkill = class {
|
|
|
2683
2803
|
this.image = params.image;
|
|
2684
2804
|
this.imageFile = params.imageFile;
|
|
2685
2805
|
this.dir = params.dir;
|
|
2806
|
+
this.llmOverride = params.llmOverride;
|
|
2686
2807
|
this.inner = new StaticScriptSkill$1({
|
|
2687
2808
|
name: params.name,
|
|
2688
2809
|
description: params.description,
|
|
@@ -2711,6 +2832,7 @@ var DynamicScriptSkill = class {
|
|
|
2711
2832
|
image;
|
|
2712
2833
|
imageFile;
|
|
2713
2834
|
dir;
|
|
2835
|
+
llmOverride;
|
|
2714
2836
|
inner;
|
|
2715
2837
|
constructor(params) {
|
|
2716
2838
|
this.name = params.name;
|
|
@@ -2721,6 +2843,7 @@ var DynamicScriptSkill = class {
|
|
|
2721
2843
|
this.image = params.image;
|
|
2722
2844
|
this.imageFile = params.imageFile;
|
|
2723
2845
|
this.dir = params.dir;
|
|
2846
|
+
this.llmOverride = params.llmOverride;
|
|
2724
2847
|
this.inner = new DynamicScriptSkill$1({
|
|
2725
2848
|
name: params.name,
|
|
2726
2849
|
description: params.description,
|
|
@@ -2846,7 +2969,8 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
2846
2969
|
outputFilePath,
|
|
2847
2970
|
image: parsed.image,
|
|
2848
2971
|
imageFile: parsed.imageFile,
|
|
2849
|
-
dir: entryPath
|
|
2972
|
+
dir: entryPath,
|
|
2973
|
+
llmOverride: parsed.llmOverride
|
|
2850
2974
|
});
|
|
2851
2975
|
break;
|
|
2852
2976
|
}
|
|
@@ -2874,7 +2998,8 @@ function buildCliSkill(parsed, entryPath, scriptEnv) {
|
|
|
2874
2998
|
scriptEnv,
|
|
2875
2999
|
image: parsed.image,
|
|
2876
3000
|
imageFile: parsed.imageFile,
|
|
2877
|
-
dir: entryPath
|
|
3001
|
+
dir: entryPath,
|
|
3002
|
+
llmOverride: parsed.llmOverride
|
|
2878
3003
|
});
|
|
2879
3004
|
break;
|
|
2880
3005
|
}
|
|
@@ -3292,12 +3417,16 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3292
3417
|
process.exit(1);
|
|
3293
3418
|
}
|
|
3294
3419
|
const llmSkills = allSkills.filter((skill) => skill.mode === "llm");
|
|
3420
|
+
const scriptDepSkills = allSkills.filter(
|
|
3421
|
+
(skill) => skill.mode !== "llm" && skill.llmOverride?.provider && skill.llmOverride?.model
|
|
3422
|
+
);
|
|
3423
|
+
const monitorPairs = /* @__PURE__ */ new Map();
|
|
3295
3424
|
const triplesByKey = /* @__PURE__ */ new Map();
|
|
3296
3425
|
const dependentSkillsByProvider = /* @__PURE__ */ new Map();
|
|
3297
3426
|
let agentDefaultCacheKey;
|
|
3298
3427
|
const llmClientCache = /* @__PURE__ */ new Map();
|
|
3299
3428
|
const healthMonitor = new LlmHealthMonitor();
|
|
3300
|
-
if (llmSkills.length > 0) {
|
|
3429
|
+
if (llmSkills.length > 0 || scriptDepSkills.length > 0) {
|
|
3301
3430
|
const resolutionErrors = [];
|
|
3302
3431
|
for (const skill of llmSkills) {
|
|
3303
3432
|
const skillMdPath = join(skillsDir, skill.name, "SKILL.md");
|
|
@@ -3311,6 +3440,10 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3311
3440
|
}
|
|
3312
3441
|
const cacheKey = cacheKeyFor(result);
|
|
3313
3442
|
triplesByKey.set(cacheKey, result);
|
|
3443
|
+
monitorPairs.set(`${result.provider}::${result.model}`, {
|
|
3444
|
+
provider: result.provider,
|
|
3445
|
+
model: result.model
|
|
3446
|
+
});
|
|
3314
3447
|
skill.resolvedTriple = {
|
|
3315
3448
|
provider: result.provider,
|
|
3316
3449
|
model: result.model,
|
|
@@ -3320,6 +3453,16 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3320
3453
|
list.push(skill.name);
|
|
3321
3454
|
dependentSkillsByProvider.set(result.provider, list);
|
|
3322
3455
|
}
|
|
3456
|
+
for (const skill of scriptDepSkills) {
|
|
3457
|
+
const provider = skill.llmOverride?.provider;
|
|
3458
|
+
const model = skill.llmOverride?.model;
|
|
3459
|
+
monitorPairs.set(`${provider}::${model}`, { provider, model });
|
|
3460
|
+
const list = dependentSkillsByProvider.get(provider) ?? [];
|
|
3461
|
+
if (!list.includes(skill.name)) {
|
|
3462
|
+
list.push(skill.name);
|
|
3463
|
+
}
|
|
3464
|
+
dependentSkillsByProvider.set(provider, list);
|
|
3465
|
+
}
|
|
3323
3466
|
if (resolutionErrors.length > 0) {
|
|
3324
3467
|
for (const message of resolutionErrors) {
|
|
3325
3468
|
console.error(` ! ${message}`);
|
|
@@ -3359,27 +3502,27 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3359
3502
|
console.error("");
|
|
3360
3503
|
process.exit(1);
|
|
3361
3504
|
}
|
|
3362
|
-
for (const [,
|
|
3363
|
-
const apiKey = keyByProvider.get(
|
|
3505
|
+
for (const [, pair] of monitorPairs) {
|
|
3506
|
+
const apiKey = keyByProvider.get(pair.provider);
|
|
3364
3507
|
if (!apiKey) {
|
|
3365
3508
|
continue;
|
|
3366
3509
|
}
|
|
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,
|
|
3510
|
+
const envVar = getLlmProvider(pair.provider)?.envVar ?? `${pair.provider.toUpperCase()}_API_KEY`;
|
|
3511
|
+
process.stdout.write(` Verifying ${pair.provider} ${pair.model}... `);
|
|
3512
|
+
const verification = await verifyLlmApiKeyDeep(pair.provider, apiKey, pair.model);
|
|
3513
|
+
const descriptor = getLlmProvider(pair.provider);
|
|
3514
|
+
const verifyFn = async (signal) => descriptor ? descriptor.verifyKeyDeep(apiKey, pair.model, signal) : { ok: false, reason: "unavailable", error: "no descriptor" };
|
|
3372
3515
|
healthMonitor.register({
|
|
3373
|
-
provider:
|
|
3374
|
-
model:
|
|
3516
|
+
provider: pair.provider,
|
|
3517
|
+
model: pair.model,
|
|
3375
3518
|
verifyFn
|
|
3376
3519
|
});
|
|
3377
|
-
healthMonitor.seed(
|
|
3520
|
+
healthMonitor.seed(pair.provider, pair.model, verification);
|
|
3378
3521
|
if (verification.ok) {
|
|
3379
3522
|
console.log("ok");
|
|
3380
3523
|
} else if (verification.reason === "invalid") {
|
|
3381
3524
|
console.log("INVALID");
|
|
3382
|
-
console.error(` ! ${
|
|
3525
|
+
console.error(` ! ${pair.provider} rejected the API key (HTTP ${verification.status}).`);
|
|
3383
3526
|
console.error(
|
|
3384
3527
|
` Update it via \`npx @elisym/cli profile ${agentName}\` or set ${envVar} to a valid key.
|
|
3385
3528
|
`
|
|
@@ -3389,7 +3532,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3389
3532
|
console.log("BILLING");
|
|
3390
3533
|
const detail = verification.body ? ` ${verification.body.slice(0, 200)}` : "";
|
|
3391
3534
|
console.error(
|
|
3392
|
-
` ! ${
|
|
3535
|
+
` ! ${pair.provider} reports a billing/quota issue for ${pair.model}.${detail}`
|
|
3393
3536
|
);
|
|
3394
3537
|
console.error(
|
|
3395
3538
|
` Top up the account at the provider console, or set ${envVar} to a key on a funded org.
|
|
@@ -3399,7 +3542,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3399
3542
|
} else {
|
|
3400
3543
|
console.log("unavailable");
|
|
3401
3544
|
console.warn(
|
|
3402
|
-
` ! Could not verify ${
|
|
3545
|
+
` ! Could not verify ${pair.provider} ${pair.model} (${verification.error}). Continuing - jobs will fail if the key is invalid.
|
|
3403
3546
|
`
|
|
3404
3547
|
);
|
|
3405
3548
|
}
|
|
@@ -3421,7 +3564,7 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3421
3564
|
llmClientCache.set(cacheKey, createLlmClient(config));
|
|
3422
3565
|
}
|
|
3423
3566
|
} else {
|
|
3424
|
-
console.log(" No LLM skills loaded; skipping LLM key check.\n");
|
|
3567
|
+
console.log(" No LLM-dependent skills loaded; skipping LLM key check.\n");
|
|
3425
3568
|
}
|
|
3426
3569
|
const agentDefaultClient = agentDefaultCacheKey !== void 0 ? llmClientCache.get(agentDefaultCacheKey) : void 0;
|
|
3427
3570
|
const getLlm = (override) => {
|
|
@@ -3618,12 +3761,12 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
3618
3761
|
logger
|
|
3619
3762
|
});
|
|
3620
3763
|
let llmHeartbeat;
|
|
3621
|
-
if (
|
|
3622
|
-
llmHeartbeat =
|
|
3764
|
+
if (monitorPairs.size > 0) {
|
|
3765
|
+
llmHeartbeat = startLlmRecovery({
|
|
3623
3766
|
monitor: healthMonitor,
|
|
3624
3767
|
log: diagLog
|
|
3625
3768
|
});
|
|
3626
|
-
diagLog("LLM health monitor armed (
|
|
3769
|
+
diagLog("LLM health monitor armed (lazy recovery, 5min interval).");
|
|
3627
3770
|
}
|
|
3628
3771
|
const runtime = new AgentRuntime(
|
|
3629
3772
|
transport,
|