@elisym/cli 0.8.1 → 0.9.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 +9 -0
- package/dist/index.js +458 -76
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills-examples/README.md +17 -15
- package/skills-examples/cheap-summarizer/SKILL.md +26 -0
- package/skills-examples/static-now/SKILL.md +2 -1
- package/skills-examples/static-welcome/SKILL.md +2 -1
- package/skills-examples/uppercase-proxy/SKILL.md +2 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --no-deprecation
|
|
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
|
-
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, ElisymClient, MediaService, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, USDC_SOLANA_DEVNET, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, LIMITS, calculateProtocolFee, BoundedSet, NATIVE_SOL } from '@elisym/sdk';
|
|
4
|
+
import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, ElisymClient, MediaService, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, USDC_SOLANA_DEVNET, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, LIMITS, calculateProtocolFee, BoundedSet, KIND_JOB_FEEDBACK, NATIVE_SOL } from '@elisym/sdk';
|
|
5
5
|
import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYaml, writeSecrets, listAgents, loadAgent, agentPaths, readMediaCache, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
|
|
6
6
|
import { isAddress, createSolanaRpc, address } from '@solana/kit';
|
|
7
|
-
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
|
|
7
|
+
import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools';
|
|
8
8
|
import YAML from 'yaml';
|
|
9
9
|
import { Command } from 'commander';
|
|
10
|
-
import { isEncrypted } from '@elisym/sdk/node';
|
|
11
10
|
import { createHash } from 'node:crypto';
|
|
12
11
|
import { lookup } from 'node:dns/promises';
|
|
13
12
|
import { Socket } from 'node:net';
|
|
@@ -140,16 +139,16 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
140
139
|
yaml = result.yaml;
|
|
141
140
|
promptedApiKey = result.apiKey;
|
|
142
141
|
}
|
|
143
|
-
let
|
|
142
|
+
let defaultProviderKey;
|
|
144
143
|
if (yaml.llm) {
|
|
145
144
|
const envKey = yaml.llm.provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY;
|
|
146
145
|
if (envKey) {
|
|
147
|
-
|
|
146
|
+
defaultProviderKey = envKey;
|
|
148
147
|
console.log(
|
|
149
148
|
` Using ${yaml.llm.provider === "anthropic" ? "ANTHROPIC" : "OPENAI"}_API_KEY from environment.`
|
|
150
149
|
);
|
|
151
150
|
} else if (promptedApiKey) {
|
|
152
|
-
|
|
151
|
+
defaultProviderKey = promptedApiKey;
|
|
153
152
|
} else {
|
|
154
153
|
const { apiKey } = await inquirer.prompt([
|
|
155
154
|
{
|
|
@@ -159,7 +158,38 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
159
158
|
mask: "*"
|
|
160
159
|
}
|
|
161
160
|
]);
|
|
162
|
-
|
|
161
|
+
defaultProviderKey = apiKey || void 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
let otherProviderKey;
|
|
165
|
+
if (yaml.llm && !template) {
|
|
166
|
+
const otherProvider = yaml.llm.provider === "anthropic" ? "openai" : "anthropic";
|
|
167
|
+
const otherProviderLabel = otherProvider === "anthropic" ? "Anthropic" : "OpenAI";
|
|
168
|
+
const otherEnvVar = otherProvider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
169
|
+
const otherEnvKey = process.env[otherEnvVar];
|
|
170
|
+
const { configureOther } = await inquirer.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: "confirm",
|
|
173
|
+
name: "configureOther",
|
|
174
|
+
message: `Configure ${otherProviderLabel} API key too (for skills that override the default provider)?`,
|
|
175
|
+
default: Boolean(otherEnvKey)
|
|
176
|
+
}
|
|
177
|
+
]);
|
|
178
|
+
if (configureOther) {
|
|
179
|
+
if (otherEnvKey) {
|
|
180
|
+
otherProviderKey = otherEnvKey;
|
|
181
|
+
console.log(` Using ${otherEnvVar} from environment.`);
|
|
182
|
+
} else {
|
|
183
|
+
const { promptedOther } = await inquirer.prompt([
|
|
184
|
+
{
|
|
185
|
+
type: "password",
|
|
186
|
+
name: "promptedOther",
|
|
187
|
+
message: `${otherProviderLabel} API key:`,
|
|
188
|
+
mask: "*"
|
|
189
|
+
}
|
|
190
|
+
]);
|
|
191
|
+
otherProviderKey = promptedOther || void 0;
|
|
192
|
+
}
|
|
163
193
|
}
|
|
164
194
|
}
|
|
165
195
|
let passphrase = "";
|
|
@@ -194,12 +224,15 @@ async function cmdInit(nameArg, options = {}) {
|
|
|
194
224
|
const nostrPubkey = getPublicKey(nostrSecretBytes);
|
|
195
225
|
const nostrSecretHex = Buffer.from(nostrSecretBytes).toString("hex");
|
|
196
226
|
const created = await createAgentDir({ target, name: agentName, cwd });
|
|
227
|
+
const anthropicKey = yaml.llm?.provider === "anthropic" ? defaultProviderKey : otherProviderKey;
|
|
228
|
+
const openaiKey = yaml.llm?.provider === "openai" ? defaultProviderKey : otherProviderKey;
|
|
197
229
|
await writeYaml(created.dir, yaml);
|
|
198
230
|
await writeSecrets(
|
|
199
231
|
created.dir,
|
|
200
232
|
{
|
|
201
233
|
nostr_secret_key: nostrSecretHex,
|
|
202
|
-
|
|
234
|
+
anthropic_api_key: anthropicKey,
|
|
235
|
+
openai_api_key: openaiKey
|
|
203
236
|
},
|
|
204
237
|
passphrase || void 0
|
|
205
238
|
);
|
|
@@ -490,7 +523,7 @@ async function cmdProfile(name) {
|
|
|
490
523
|
{
|
|
491
524
|
type: "list",
|
|
492
525
|
name: "llmProvider",
|
|
493
|
-
message: "LLM provider:",
|
|
526
|
+
message: "Default LLM provider (used by skills without a `provider:` override):",
|
|
494
527
|
choices: [
|
|
495
528
|
{ name: "Anthropic (Claude)", value: "anthropic" },
|
|
496
529
|
{ name: "OpenAI (GPT)", value: "openai" }
|
|
@@ -498,16 +531,17 @@ async function cmdProfile(name) {
|
|
|
498
531
|
default: loaded.yaml.llm?.provider ?? "anthropic"
|
|
499
532
|
}
|
|
500
533
|
]);
|
|
534
|
+
const otherProvider = llmProvider === "anthropic" ? "openai" : "anthropic";
|
|
535
|
+
const defaultKeyStatus = describeKeyStatus(loaded.secrets, llmProvider);
|
|
501
536
|
const { apiKey } = await inquirer.prompt([
|
|
502
537
|
{
|
|
503
538
|
type: "password",
|
|
504
539
|
name: "apiKey",
|
|
505
|
-
message:
|
|
540
|
+
message: `${labelFor(llmProvider)} API key [${defaultKeyStatus}] (leave empty to keep current):`,
|
|
506
541
|
mask: "*"
|
|
507
542
|
}
|
|
508
543
|
]);
|
|
509
|
-
const
|
|
510
|
-
const probeKey = apiKey || currentKeyPlain;
|
|
544
|
+
const probeKey = apiKey || pickPlainKey(loaded.secrets, llmProvider);
|
|
511
545
|
console.log(" Fetching available models...");
|
|
512
546
|
const { fetchModels: fetchModels2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
513
547
|
const models = await fetchModels2(llmProvider, probeKey);
|
|
@@ -528,13 +562,37 @@ async function cmdProfile(name) {
|
|
|
528
562
|
default: loaded.yaml.llm?.max_tokens ?? 4096
|
|
529
563
|
}
|
|
530
564
|
]);
|
|
565
|
+
const otherKeyStatus = describeKeyStatus(loaded.secrets, otherProvider);
|
|
566
|
+
const { otherApiKey } = await inquirer.prompt([
|
|
567
|
+
{
|
|
568
|
+
type: "password",
|
|
569
|
+
name: "otherApiKey",
|
|
570
|
+
message: `${labelFor(otherProvider)} API key for skill overrides [${otherKeyStatus}] (leave empty to keep current):`,
|
|
571
|
+
mask: "*"
|
|
572
|
+
}
|
|
573
|
+
]);
|
|
531
574
|
const nextLlm = { provider: llmProvider, model, max_tokens: maxTokens };
|
|
532
575
|
const nextYaml = { ...loaded.yaml, llm: nextLlm };
|
|
533
576
|
await writeYaml(loaded.dir, nextYaml);
|
|
534
577
|
loaded.yaml = nextYaml;
|
|
535
|
-
if (apiKey) {
|
|
536
|
-
|
|
537
|
-
|
|
578
|
+
if (apiKey || otherApiKey) {
|
|
579
|
+
const nextSecrets = { ...loaded.secrets };
|
|
580
|
+
if (apiKey) {
|
|
581
|
+
if (llmProvider === "anthropic") {
|
|
582
|
+
nextSecrets.anthropic_api_key = apiKey;
|
|
583
|
+
} else {
|
|
584
|
+
nextSecrets.openai_api_key = apiKey;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (otherApiKey) {
|
|
588
|
+
if (otherProvider === "anthropic") {
|
|
589
|
+
nextSecrets.anthropic_api_key = otherApiKey;
|
|
590
|
+
} else {
|
|
591
|
+
nextSecrets.openai_api_key = otherApiKey;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
await writeSecrets(loaded.dir, nextSecrets, passphrase);
|
|
595
|
+
loaded.secrets = nextSecrets;
|
|
538
596
|
}
|
|
539
597
|
console.log(" LLM updated.\n");
|
|
540
598
|
}
|
|
@@ -548,6 +606,17 @@ function truncate(value, max = 40) {
|
|
|
548
606
|
}
|
|
549
607
|
return value.slice(0, max - 1) + "\u2026";
|
|
550
608
|
}
|
|
609
|
+
function labelFor(provider) {
|
|
610
|
+
return provider === "anthropic" ? "Anthropic" : "OpenAI";
|
|
611
|
+
}
|
|
612
|
+
function describeKeyStatus(secrets, provider) {
|
|
613
|
+
const perProviderField = provider === "anthropic" ? "anthropic_api_key" : "openai_api_key";
|
|
614
|
+
return secrets[perProviderField] ? "set" : "not set";
|
|
615
|
+
}
|
|
616
|
+
function pickPlainKey(secrets, provider) {
|
|
617
|
+
const perProviderField = provider === "anthropic" ? "anthropic_api_key" : "openai_api_key";
|
|
618
|
+
return secrets[perProviderField] ?? "";
|
|
619
|
+
}
|
|
551
620
|
var TCP_PROBE_TIMEOUT_MS = 3e3;
|
|
552
621
|
function parseRelayUrl(url) {
|
|
553
622
|
try {
|
|
@@ -1095,6 +1164,82 @@ var OpenAIClient = class {
|
|
|
1095
1164
|
}));
|
|
1096
1165
|
}
|
|
1097
1166
|
};
|
|
1167
|
+
|
|
1168
|
+
// src/llm/resolve.ts
|
|
1169
|
+
var DEFAULT_MAX_TOKENS = 4096;
|
|
1170
|
+
function resolveSkillLlm(input, agentDefault) {
|
|
1171
|
+
const override = input.llmOverride;
|
|
1172
|
+
const overridePairSet = override?.provider !== void 0 && override.model !== void 0;
|
|
1173
|
+
let provider;
|
|
1174
|
+
let model;
|
|
1175
|
+
if (overridePairSet) {
|
|
1176
|
+
provider = override.provider;
|
|
1177
|
+
model = override.model;
|
|
1178
|
+
} else if (agentDefault) {
|
|
1179
|
+
provider = agentDefault.provider;
|
|
1180
|
+
model = agentDefault.model;
|
|
1181
|
+
}
|
|
1182
|
+
if (!provider || !model) {
|
|
1183
|
+
return {
|
|
1184
|
+
error: `Skill "${input.skillName}" at ${input.skillMdPath}: LLM model is required - declare "provider" + "model" in the SKILL.md frontmatter or set agent-level llm via 'npx @elisym/cli profile <agent>'.`
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
const maxTokens = override?.maxTokens ?? agentDefault?.max_tokens ?? DEFAULT_MAX_TOKENS;
|
|
1188
|
+
return { provider, model, maxTokens };
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// src/llm/cache.ts
|
|
1192
|
+
function cacheKeyFor(triple) {
|
|
1193
|
+
return JSON.stringify({
|
|
1194
|
+
provider: triple.provider,
|
|
1195
|
+
model: triple.model,
|
|
1196
|
+
maxTokens: triple.maxTokens
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
function resolveTripleForOverride(override, agentDefault) {
|
|
1200
|
+
const overridePairSet = override?.provider !== void 0 && override.model !== void 0;
|
|
1201
|
+
let provider;
|
|
1202
|
+
let model;
|
|
1203
|
+
if (overridePairSet) {
|
|
1204
|
+
provider = override.provider;
|
|
1205
|
+
model = override.model;
|
|
1206
|
+
} else if (agentDefault) {
|
|
1207
|
+
provider = agentDefault.provider;
|
|
1208
|
+
model = agentDefault.model;
|
|
1209
|
+
}
|
|
1210
|
+
if (!provider || !model) {
|
|
1211
|
+
return void 0;
|
|
1212
|
+
}
|
|
1213
|
+
const maxTokens = override?.maxTokens ?? agentDefault?.max_tokens ?? DEFAULT_MAX_TOKENS;
|
|
1214
|
+
return { provider, model, maxTokens };
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// src/llm/keys.ts
|
|
1218
|
+
var ENV_VAR_FOR_PROVIDER = {
|
|
1219
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
1220
|
+
openai: "OPENAI_API_KEY"
|
|
1221
|
+
};
|
|
1222
|
+
var SECRET_FIELD_FOR_PROVIDER = {
|
|
1223
|
+
anthropic: "anthropic_api_key",
|
|
1224
|
+
openai: "openai_api_key"
|
|
1225
|
+
};
|
|
1226
|
+
function resolveProviderApiKey(input) {
|
|
1227
|
+
const { provider, secrets, dependentSkills } = input;
|
|
1228
|
+
const perProviderField = SECRET_FIELD_FOR_PROVIDER[provider];
|
|
1229
|
+
const perProviderValue = secrets[perProviderField];
|
|
1230
|
+
if (perProviderValue) {
|
|
1231
|
+
return { apiKey: perProviderValue, origin: "secrets-per-provider" };
|
|
1232
|
+
}
|
|
1233
|
+
const envVar = ENV_VAR_FOR_PROVIDER[provider];
|
|
1234
|
+
const envValue = process.env[envVar];
|
|
1235
|
+
if (envValue) {
|
|
1236
|
+
return { apiKey: envValue, origin: "env" };
|
|
1237
|
+
}
|
|
1238
|
+
const skillList = dependentSkills.length > 0 ? dependentSkills.join(", ") : "<none>";
|
|
1239
|
+
return {
|
|
1240
|
+
error: `Provider "${provider}" needs an API key (required by skill(s): ${skillList}). Set secrets.${perProviderField} via 'npx @elisym/cli profile <agent>' or export ${envVar}.`
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1098
1243
|
function resolveLevel(options) {
|
|
1099
1244
|
if (options.level) {
|
|
1100
1245
|
return options.level;
|
|
@@ -1146,6 +1291,7 @@ var payment = new SolanaPaymentStrategy();
|
|
|
1146
1291
|
var LEDGER_GC_INTERVAL_MS = 60 * 60 * 1e3;
|
|
1147
1292
|
var LEDGER_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1148
1293
|
var TOTAL_JOB_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1294
|
+
var SIG_PATH_TIMEOUT_MS = 60 * 1e3;
|
|
1149
1295
|
function resolveJobPrice(tags, skills) {
|
|
1150
1296
|
const skill = skills.route(tags);
|
|
1151
1297
|
return skill?.priceSubunits ?? 0;
|
|
@@ -1424,33 +1570,124 @@ var AgentRuntime = class {
|
|
|
1424
1570
|
chain: "solana"
|
|
1425
1571
|
});
|
|
1426
1572
|
const rpc = createSolanaRpc(getRpcUrl(this.config.network));
|
|
1427
|
-
|
|
1573
|
+
const verifyAbort = new AbortController();
|
|
1574
|
+
const onOuterAbort = () => verifyAbort.abort();
|
|
1428
1575
|
if (signal) {
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1576
|
+
if (signal.aborted) {
|
|
1577
|
+
verifyAbort.abort();
|
|
1578
|
+
} else {
|
|
1579
|
+
signal.addEventListener("abort", onOuterAbort, { once: true });
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
const deadlineMs = this.config.paymentTimeoutSecs * 1e3;
|
|
1583
|
+
const sigPathTimeoutMs = Math.min(deadlineMs, SIG_PATH_TIMEOUT_MS);
|
|
1584
|
+
let result;
|
|
1585
|
+
try {
|
|
1586
|
+
result = await new Promise((resolve3, reject) => {
|
|
1587
|
+
let settled = false;
|
|
1588
|
+
let pending = 2;
|
|
1589
|
+
let lastResult = { verified: false };
|
|
1590
|
+
const win = (verified) => {
|
|
1591
|
+
if (settled) {
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
settled = true;
|
|
1595
|
+
clearTimeout(deadline);
|
|
1596
|
+
resolve3(verified);
|
|
1597
|
+
};
|
|
1598
|
+
const lose = (verified, reason) => {
|
|
1599
|
+
if (settled) {
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
lastResult = verified;
|
|
1603
|
+
pending -= 1;
|
|
1604
|
+
log(`[${job.jobId.slice(0, 8)}] verify ${reason}`);
|
|
1605
|
+
if (pending === 0) {
|
|
1606
|
+
settled = true;
|
|
1607
|
+
clearTimeout(deadline);
|
|
1608
|
+
resolve3(lastResult);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
this.transport.waitForPaymentSignature(job.jobId, job.customerId, verifyAbort.signal, sigPathTimeoutMs).then(async (sig) => {
|
|
1612
|
+
if (settled) {
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
if (!sig) {
|
|
1616
|
+
lose({ verified: false }, "sig path: no payment-completed feedback");
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
log(`[${job.jobId.slice(0, 8)}] verify sig path: got ${sig.slice(0, 8)}...`);
|
|
1620
|
+
try {
|
|
1621
|
+
const verified = await payment.verifyPayment(rpc, request, protocolConfig, {
|
|
1622
|
+
txSignature: sig
|
|
1623
|
+
});
|
|
1624
|
+
if (verified.verified) {
|
|
1625
|
+
win(verified);
|
|
1626
|
+
} else {
|
|
1627
|
+
const reason = verified.error ?? "unknown";
|
|
1628
|
+
lose(verified, `sig path: not verified (${reason})`);
|
|
1629
|
+
}
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
lose(
|
|
1632
|
+
{ verified: false },
|
|
1633
|
+
`sig path error: ${err instanceof Error ? err.message : String(err)}`
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
}).catch(
|
|
1637
|
+
(err) => lose(
|
|
1638
|
+
{ verified: false },
|
|
1639
|
+
`sig path error: ${err instanceof Error ? err.message : String(err)}`
|
|
1640
|
+
)
|
|
1641
|
+
);
|
|
1642
|
+
payment.verifyPayment(rpc, request, protocolConfig).then((verified) => {
|
|
1643
|
+
if (verified.verified) {
|
|
1644
|
+
win(verified);
|
|
1645
|
+
} else {
|
|
1646
|
+
const reason = verified.error ?? "unknown";
|
|
1647
|
+
lose(verified, `ref path: not verified (${reason})`);
|
|
1648
|
+
}
|
|
1649
|
+
}).catch(
|
|
1650
|
+
(err) => lose(
|
|
1651
|
+
{ verified: false },
|
|
1652
|
+
`ref path error: ${err instanceof Error ? err.message : String(err)}`
|
|
1653
|
+
)
|
|
1654
|
+
);
|
|
1655
|
+
const deadline = setTimeout(() => {
|
|
1656
|
+
if (settled) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
settled = true;
|
|
1660
|
+
log(`[${job.jobId.slice(0, 8)}] verify deadline (${deadlineMs}ms) hit`);
|
|
1661
|
+
resolve3(lastResult);
|
|
1662
|
+
}, deadlineMs);
|
|
1663
|
+
if (signal?.aborted) {
|
|
1664
|
+
settled = true;
|
|
1665
|
+
clearTimeout(deadline);
|
|
1432
1666
|
const err = new Error("The operation was aborted");
|
|
1433
1667
|
err.name = "AbortError";
|
|
1434
1668
|
reject(err);
|
|
1435
|
-
};
|
|
1436
|
-
if (signal.aborted) {
|
|
1437
|
-
abortHandler();
|
|
1438
1669
|
return;
|
|
1439
1670
|
}
|
|
1440
|
-
signal
|
|
1671
|
+
signal?.addEventListener(
|
|
1672
|
+
"abort",
|
|
1673
|
+
() => {
|
|
1674
|
+
if (settled) {
|
|
1675
|
+
return;
|
|
1676
|
+
}
|
|
1677
|
+
settled = true;
|
|
1678
|
+
clearTimeout(deadline);
|
|
1679
|
+
const err = new Error("The operation was aborted");
|
|
1680
|
+
err.name = "AbortError";
|
|
1681
|
+
reject(err);
|
|
1682
|
+
},
|
|
1683
|
+
{ once: true }
|
|
1684
|
+
);
|
|
1441
1685
|
});
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
]);
|
|
1447
|
-
} finally {
|
|
1448
|
-
if (abortHandler) {
|
|
1449
|
-
signal.removeEventListener("abort", abortHandler);
|
|
1450
|
-
}
|
|
1686
|
+
} finally {
|
|
1687
|
+
verifyAbort.abort();
|
|
1688
|
+
if (signal) {
|
|
1689
|
+
signal.removeEventListener("abort", onOuterAbort);
|
|
1451
1690
|
}
|
|
1452
|
-
} else {
|
|
1453
|
-
result = await payment.verifyPayment(rpc, request, protocolConfig);
|
|
1454
1691
|
}
|
|
1455
1692
|
if (result.verified) {
|
|
1456
1693
|
return { netAmount, paymentRequest: requestJson };
|
|
@@ -1787,11 +2024,12 @@ var ScriptSkill = class {
|
|
|
1787
2024
|
priceSubunits;
|
|
1788
2025
|
asset;
|
|
1789
2026
|
mode = "llm";
|
|
2027
|
+
llmOverride;
|
|
1790
2028
|
image;
|
|
1791
2029
|
imageFile;
|
|
1792
2030
|
dir;
|
|
1793
2031
|
inner;
|
|
1794
|
-
constructor(name, description, capabilities, priceSubunits, assetOrImage, imageOrImageFile, imageFileOrDir, dirOrPrompt, promptOrTools, toolsOrRounds, rounds) {
|
|
2032
|
+
constructor(name, description, capabilities, priceSubunits, assetOrImage, imageOrImageFile, imageFileOrDir, dirOrPrompt, promptOrTools, toolsOrRounds, rounds, llmOverride) {
|
|
1795
2033
|
let asset;
|
|
1796
2034
|
let image;
|
|
1797
2035
|
let imageFile;
|
|
@@ -1821,6 +2059,7 @@ var ScriptSkill = class {
|
|
|
1821
2059
|
this.capabilities = capabilities;
|
|
1822
2060
|
this.priceSubunits = priceSubunits;
|
|
1823
2061
|
this.asset = asset;
|
|
2062
|
+
this.llmOverride = llmOverride;
|
|
1824
2063
|
this.image = image;
|
|
1825
2064
|
this.imageFile = imageFile;
|
|
1826
2065
|
this.dir = skillDir;
|
|
@@ -1834,6 +2073,7 @@ var ScriptSkill = class {
|
|
|
1834
2073
|
systemPrompt,
|
|
1835
2074
|
tools,
|
|
1836
2075
|
maxToolRounds,
|
|
2076
|
+
llmOverride,
|
|
1837
2077
|
image,
|
|
1838
2078
|
imageFile
|
|
1839
2079
|
});
|
|
@@ -1858,7 +2098,8 @@ function buildCliSkill(parsed, entryPath) {
|
|
|
1858
2098
|
entryPath,
|
|
1859
2099
|
parsed.systemPrompt,
|
|
1860
2100
|
parsed.tools,
|
|
1861
|
-
parsed.maxToolRounds
|
|
2101
|
+
parsed.maxToolRounds,
|
|
2102
|
+
parsed.llmOverride
|
|
1862
2103
|
);
|
|
1863
2104
|
case "static-file": {
|
|
1864
2105
|
if (parsed.outputFile === void 0) {
|
|
@@ -1944,7 +2185,7 @@ function loadSkillsFromDir(skillsDir) {
|
|
|
1944
2185
|
}
|
|
1945
2186
|
return skills;
|
|
1946
2187
|
}
|
|
1947
|
-
function
|
|
2188
|
+
function isEncrypted(event) {
|
|
1948
2189
|
return event.tags.some((t) => t[0] === "encrypted" && t[1] === "nip44");
|
|
1949
2190
|
}
|
|
1950
2191
|
var HEALTH_CHECK_IDLE_MS = 30 * 60 * 1e3;
|
|
@@ -1978,7 +2219,7 @@ var NostrTransport = class {
|
|
|
1978
2219
|
}
|
|
1979
2220
|
const tags = event.tags.filter((t) => t[0] === "t").map((t) => t[1]);
|
|
1980
2221
|
const bidTag = event.tags.find((t) => t[0] === "bid");
|
|
1981
|
-
const encrypted =
|
|
2222
|
+
const encrypted = isEncrypted(event);
|
|
1982
2223
|
const iTag = event.tags.find((t) => t[0] === "i");
|
|
1983
2224
|
let inputType = "text";
|
|
1984
2225
|
if (iTag?.[2]) {
|
|
@@ -1998,6 +2239,76 @@ var NostrTransport = class {
|
|
|
1998
2239
|
}
|
|
1999
2240
|
);
|
|
2000
2241
|
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Wait for the customer's `payment-completed` feedback for a specific job
|
|
2244
|
+
* and return the on-chain Solana tx signature it carries.
|
|
2245
|
+
*
|
|
2246
|
+
* The customer publishes this event right after `confirmTransaction(... 'confirmed')`
|
|
2247
|
+
* succeeds (see packages/app/app/hooks/useBuyCapability.ts), so receiving it
|
|
2248
|
+
* lets the provider verify the payment with a single targeted
|
|
2249
|
+
* `getTransaction(sig, commitment: 'confirmed')` call instead of the
|
|
2250
|
+
* heavyweight `getSignaturesForAddress(reference)` index lookup. This is
|
|
2251
|
+
* dramatically more reliable on the public devnet RPC, which throttles and
|
|
2252
|
+
* lags the address index.
|
|
2253
|
+
*
|
|
2254
|
+
* Resolves with `null` if `signal` aborts or `timeoutMs` elapses before a
|
|
2255
|
+
* valid signature arrives. The timeout exists so a customer that disappears
|
|
2256
|
+
* after job submission does not hold a `p-limit` slot for the full payment
|
|
2257
|
+
* expiry window - without it this path waits forever on the relay.
|
|
2258
|
+
*/
|
|
2259
|
+
waitForPaymentSignature(jobId, customerPubkey, signal, timeoutMs) {
|
|
2260
|
+
return new Promise((resolve3) => {
|
|
2261
|
+
let settled = false;
|
|
2262
|
+
const filter = {
|
|
2263
|
+
kinds: [KIND_JOB_FEEDBACK],
|
|
2264
|
+
"#e": [jobId],
|
|
2265
|
+
authors: [customerPubkey],
|
|
2266
|
+
"#t": ["elisym"],
|
|
2267
|
+
since: Math.floor(Date.now() / 1e3) - 5
|
|
2268
|
+
};
|
|
2269
|
+
const finish = (sig) => {
|
|
2270
|
+
if (settled) {
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
settled = true;
|
|
2274
|
+
if (timer) {
|
|
2275
|
+
clearTimeout(timer);
|
|
2276
|
+
}
|
|
2277
|
+
signal.removeEventListener("abort", onAbort);
|
|
2278
|
+
sub.close();
|
|
2279
|
+
resolve3(sig);
|
|
2280
|
+
};
|
|
2281
|
+
const sub = this.client.pool.subscribe(filter, (event) => {
|
|
2282
|
+
if (settled) {
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
if (!verifyEvent(event)) {
|
|
2286
|
+
return;
|
|
2287
|
+
}
|
|
2288
|
+
const status = event.tags.find((t) => t[0] === "status")?.[1];
|
|
2289
|
+
if (status !== "payment-completed") {
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
const txTag = event.tags.find((t) => t[0] === "tx");
|
|
2293
|
+
const sig = txTag?.[1];
|
|
2294
|
+
const chain = txTag?.[2];
|
|
2295
|
+
if (typeof sig !== "string" || sig.length === 0) {
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
if (chain !== void 0 && chain !== "solana") {
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
finish(sig);
|
|
2302
|
+
});
|
|
2303
|
+
const onAbort = () => finish(null);
|
|
2304
|
+
const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => finish(null), timeoutMs) : null;
|
|
2305
|
+
if (signal.aborted) {
|
|
2306
|
+
onAbort();
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2001
2312
|
/** Send job feedback to customer. */
|
|
2002
2313
|
async sendFeedback(job, status) {
|
|
2003
2314
|
if (status.type === "payment-required") {
|
|
@@ -2237,53 +2548,124 @@ async function cmdStart(nameArg, options = {}) {
|
|
|
2237
2548
|
);
|
|
2238
2549
|
process.exit(1);
|
|
2239
2550
|
}
|
|
2240
|
-
const
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2551
|
+
const llmSkills = allSkills.filter((skill) => skill.mode === "llm");
|
|
2552
|
+
const triplesByKey = /* @__PURE__ */ new Map();
|
|
2553
|
+
const dependentSkillsByProvider = /* @__PURE__ */ new Map();
|
|
2554
|
+
let agentDefaultCacheKey;
|
|
2555
|
+
const llmClientCache = /* @__PURE__ */ new Map();
|
|
2556
|
+
if (llmSkills.length > 0) {
|
|
2557
|
+
const resolutionErrors = [];
|
|
2558
|
+
for (const skill of llmSkills) {
|
|
2559
|
+
const skillMdPath = join(skillsDir, skill.name, "SKILL.md");
|
|
2560
|
+
const result = resolveSkillLlm(
|
|
2561
|
+
{ skillName: skill.name, skillMdPath, llmOverride: skill.llmOverride },
|
|
2562
|
+
loaded.yaml.llm
|
|
2563
|
+
);
|
|
2564
|
+
if ("error" in result) {
|
|
2565
|
+
resolutionErrors.push(result.error);
|
|
2566
|
+
continue;
|
|
2567
|
+
}
|
|
2568
|
+
const cacheKey = cacheKeyFor(result);
|
|
2569
|
+
triplesByKey.set(cacheKey, result);
|
|
2570
|
+
const list = dependentSkillsByProvider.get(result.provider) ?? [];
|
|
2571
|
+
list.push(skill.name);
|
|
2572
|
+
dependentSkillsByProvider.set(result.provider, list);
|
|
2573
|
+
}
|
|
2574
|
+
if (resolutionErrors.length > 0) {
|
|
2575
|
+
for (const message of resolutionErrors) {
|
|
2576
|
+
console.error(` ! ${message}`);
|
|
2577
|
+
}
|
|
2578
|
+
console.error("");
|
|
2245
2579
|
process.exit(1);
|
|
2246
2580
|
}
|
|
2247
|
-
if (
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2581
|
+
if (loaded.yaml.llm) {
|
|
2582
|
+
const agentDefaultTriple = {
|
|
2583
|
+
provider: loaded.yaml.llm.provider,
|
|
2584
|
+
model: loaded.yaml.llm.model,
|
|
2585
|
+
maxTokens: loaded.yaml.llm.max_tokens
|
|
2586
|
+
};
|
|
2587
|
+
const agentKey = cacheKeyFor(agentDefaultTriple);
|
|
2588
|
+
if (triplesByKey.has(agentKey)) {
|
|
2589
|
+
agentDefaultCacheKey = agentKey;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
const keyByProvider = /* @__PURE__ */ new Map();
|
|
2593
|
+
const keyErrors = [];
|
|
2594
|
+
for (const [provider, dependents] of dependentSkillsByProvider) {
|
|
2595
|
+
const keyResult = resolveProviderApiKey({
|
|
2596
|
+
provider,
|
|
2597
|
+
secrets: loaded.secrets,
|
|
2598
|
+
dependentSkills: dependents
|
|
2599
|
+
});
|
|
2600
|
+
if ("error" in keyResult) {
|
|
2601
|
+
keyErrors.push(keyResult.error);
|
|
2602
|
+
continue;
|
|
2603
|
+
}
|
|
2604
|
+
keyByProvider.set(provider, keyResult.apiKey);
|
|
2605
|
+
}
|
|
2606
|
+
if (keyErrors.length > 0) {
|
|
2607
|
+
for (const message of keyErrors) {
|
|
2608
|
+
console.error(` ! ${message}`);
|
|
2609
|
+
}
|
|
2610
|
+
console.error("");
|
|
2252
2611
|
process.exit(1);
|
|
2253
2612
|
}
|
|
2254
|
-
const
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2613
|
+
for (const provider of keyByProvider.keys()) {
|
|
2614
|
+
const apiKey = keyByProvider.get(provider);
|
|
2615
|
+
if (!apiKey) {
|
|
2616
|
+
continue;
|
|
2617
|
+
}
|
|
2618
|
+
const envVar = provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY";
|
|
2619
|
+
process.stdout.write(` Verifying ${provider} API key... `);
|
|
2620
|
+
const verification = await verifyLlmApiKey(provider, apiKey);
|
|
2621
|
+
if (verification.ok) {
|
|
2622
|
+
console.log("ok");
|
|
2623
|
+
} else if (verification.reason === "invalid") {
|
|
2624
|
+
console.log("INVALID");
|
|
2625
|
+
console.error(` ! ${provider} rejected the API key (HTTP ${verification.status}).`);
|
|
2626
|
+
console.error(
|
|
2627
|
+
` Update it via \`npx @elisym/cli profile ${agentName}\` or set ${envVar} to a valid key.
|
|
2265
2628
|
`
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2629
|
+
);
|
|
2630
|
+
process.exit(1);
|
|
2631
|
+
} else {
|
|
2632
|
+
console.log("unavailable");
|
|
2633
|
+
console.warn(
|
|
2634
|
+
` ! Could not verify ${provider} API key (${verification.error}). Continuing - jobs will fail if the key is invalid.
|
|
2272
2635
|
`
|
|
2273
|
-
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
for (const [cacheKey, triple] of triplesByKey) {
|
|
2640
|
+
const apiKey = keyByProvider.get(triple.provider);
|
|
2641
|
+
if (!apiKey) {
|
|
2642
|
+
console.error(` ! Internal error: no API key for provider "${triple.provider}".
|
|
2643
|
+
`);
|
|
2644
|
+
process.exit(1);
|
|
2645
|
+
}
|
|
2646
|
+
const config = {
|
|
2647
|
+
provider: triple.provider,
|
|
2648
|
+
apiKey,
|
|
2649
|
+
model: triple.model,
|
|
2650
|
+
maxTokens: triple.maxTokens,
|
|
2651
|
+
logUsage: true
|
|
2652
|
+
};
|
|
2653
|
+
llmClientCache.set(cacheKey, createLlmClient(config));
|
|
2274
2654
|
}
|
|
2275
|
-
llm = createLlmClient({
|
|
2276
|
-
provider: loaded.yaml.llm.provider,
|
|
2277
|
-
apiKey: loaded.secrets.llm_api_key,
|
|
2278
|
-
model: loaded.yaml.llm.model,
|
|
2279
|
-
maxTokens: loaded.yaml.llm.max_tokens,
|
|
2280
|
-
logUsage: true
|
|
2281
|
-
});
|
|
2282
2655
|
} else {
|
|
2283
2656
|
console.log(" No LLM skills loaded; skipping LLM key check.\n");
|
|
2284
2657
|
}
|
|
2658
|
+
const agentDefaultClient = agentDefaultCacheKey !== void 0 ? llmClientCache.get(agentDefaultCacheKey) : void 0;
|
|
2659
|
+
const getLlm = (override) => {
|
|
2660
|
+
const triple = resolveTripleForOverride(override, loaded.yaml.llm);
|
|
2661
|
+
if (!triple) {
|
|
2662
|
+
return void 0;
|
|
2663
|
+
}
|
|
2664
|
+
return llmClientCache.get(cacheKeyFor(triple));
|
|
2665
|
+
};
|
|
2285
2666
|
const skillCtx = {
|
|
2286
|
-
llm,
|
|
2667
|
+
llm: agentDefaultClient,
|
|
2668
|
+
getLlm,
|
|
2287
2669
|
agentName,
|
|
2288
2670
|
agentDescription: loaded.yaml.description ?? ""
|
|
2289
2671
|
};
|
|
@@ -2583,9 +2965,9 @@ async function loadAgentWithPrompt(name, cwd) {
|
|
|
2583
2965
|
try {
|
|
2584
2966
|
return await loadAgent(name, cwd, envPassphrase);
|
|
2585
2967
|
} catch (e) {
|
|
2586
|
-
const
|
|
2968
|
+
const isEncrypted2 = /encrypted secrets/i.test(e?.message ?? "");
|
|
2587
2969
|
const isWrongPassphrase = /Decryption failed/i.test(e?.message ?? "");
|
|
2588
|
-
if (!
|
|
2970
|
+
if (!isEncrypted2 && !isWrongPassphrase) {
|
|
2589
2971
|
throw e;
|
|
2590
2972
|
}
|
|
2591
2973
|
if (!process.stdin.isTTY) {
|