@elisym/sdk 0.12.4 → 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/dist/agent-store.cjs +16 -22
- package/dist/agent-store.cjs.map +1 -1
- package/dist/agent-store.d.cts +7 -3
- package/dist/agent-store.d.ts +7 -3
- package/dist/agent-store.js +16 -22
- package/dist/agent-store.js.map +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/node.cjs.map +1 -1
- package/dist/node.js.map +1 -1
- package/dist/skills.cjs +472 -93
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.d.cts +235 -3
- package/dist/skills.d.ts +235 -3
- package/dist/skills.js +465 -94
- package/dist/skills.js.map +1 -1
- package/package.json +1 -1
package/dist/skills.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { StringDecoder } from 'node:string_decoder';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { dirname, resolve, relative, sep, join } from 'node:path';
|
|
3
5
|
import { readdirSync, statSync, readFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
6
|
import YAML from 'yaml';
|
|
6
7
|
import Decimal from 'decimal.js-light';
|
|
7
8
|
|
|
@@ -22,9 +23,9 @@ function sleepWithSignal(ms, signal) {
|
|
|
22
23
|
return Promise.reject(createAbortError());
|
|
23
24
|
}
|
|
24
25
|
if (!signal) {
|
|
25
|
-
return new Promise((
|
|
26
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
26
27
|
}
|
|
27
|
-
return new Promise((
|
|
28
|
+
return new Promise((resolve2, reject) => {
|
|
28
29
|
const cleanup = () => {
|
|
29
30
|
clearTimeout(timer);
|
|
30
31
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -35,7 +36,7 @@ function sleepWithSignal(ms, signal) {
|
|
|
35
36
|
};
|
|
36
37
|
const timer = setTimeout(() => {
|
|
37
38
|
cleanup();
|
|
38
|
-
|
|
39
|
+
resolve2();
|
|
39
40
|
}, ms);
|
|
40
41
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
41
42
|
});
|
|
@@ -302,14 +303,62 @@ function createLlmClient(config) {
|
|
|
302
303
|
}
|
|
303
304
|
return createAnthropicClient(config);
|
|
304
305
|
}
|
|
305
|
-
var
|
|
306
|
-
var
|
|
306
|
+
var MAX_SCRIPT_OUTPUT = 1e6;
|
|
307
|
+
var DEFAULT_SCRIPT_TIMEOUT_MS = 6e4;
|
|
308
|
+
function runScript(cmd, args, opts) {
|
|
309
|
+
return new Promise((resolveResult) => {
|
|
310
|
+
const maxOutput = opts.maxOutput ?? MAX_SCRIPT_OUTPUT;
|
|
311
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS;
|
|
312
|
+
const child = spawn(cmd, args, {
|
|
313
|
+
cwd: opts.cwd,
|
|
314
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
315
|
+
timeout: timeoutMs,
|
|
316
|
+
killSignal: "SIGKILL",
|
|
317
|
+
signal: opts.signal
|
|
318
|
+
});
|
|
319
|
+
let stdout = "";
|
|
320
|
+
let stderr = "";
|
|
321
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
322
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
323
|
+
child.stdout?.on("data", (data) => {
|
|
324
|
+
if (stdout.length < maxOutput) {
|
|
325
|
+
stdout += stdoutDecoder.write(data);
|
|
326
|
+
if (stdout.length > maxOutput) {
|
|
327
|
+
stdout = stdout.slice(0, maxOutput);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
child.stderr?.on("data", (data) => {
|
|
332
|
+
if (stderr.length < maxOutput) {
|
|
333
|
+
stderr += stderrDecoder.write(data);
|
|
334
|
+
if (stderr.length > maxOutput) {
|
|
335
|
+
stderr = stderr.slice(0, maxOutput);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
child.on("close", (code) => {
|
|
340
|
+
stdout += stdoutDecoder.end();
|
|
341
|
+
stderr += stderrDecoder.end();
|
|
342
|
+
resolveResult({ stdout, stderr, code });
|
|
343
|
+
});
|
|
344
|
+
child.on("error", (err) => {
|
|
345
|
+
resolveResult({ stdout, stderr, code: null, spawnError: err });
|
|
346
|
+
});
|
|
347
|
+
if (child.stdin) {
|
|
348
|
+
child.stdin.on("error", () => {
|
|
349
|
+
});
|
|
350
|
+
child.stdin.end(opts.stdin ?? "");
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
}
|
|
307
354
|
var ScriptSkill = class {
|
|
308
355
|
name;
|
|
309
356
|
description;
|
|
310
357
|
capabilities;
|
|
311
358
|
priceSubunits;
|
|
312
359
|
asset;
|
|
360
|
+
mode = "llm";
|
|
361
|
+
llmOverride;
|
|
313
362
|
image;
|
|
314
363
|
imageFile;
|
|
315
364
|
skillDir;
|
|
@@ -323,6 +372,7 @@ var ScriptSkill = class {
|
|
|
323
372
|
this.capabilities = params.capabilities;
|
|
324
373
|
this.priceSubunits = params.priceSubunits;
|
|
325
374
|
this.asset = params.asset;
|
|
375
|
+
this.llmOverride = params.llmOverride;
|
|
326
376
|
this.image = params.image;
|
|
327
377
|
this.imageFile = params.imageFile;
|
|
328
378
|
this.skillDir = params.skillDir;
|
|
@@ -332,11 +382,9 @@ var ScriptSkill = class {
|
|
|
332
382
|
this.logger = params.logger ?? {};
|
|
333
383
|
}
|
|
334
384
|
async execute(input, ctx) {
|
|
335
|
-
|
|
336
|
-
throw new Error("LLM client not configured for skill runtime");
|
|
337
|
-
}
|
|
385
|
+
const llm = this.resolveLlmClient(ctx);
|
|
338
386
|
if (this.tools.length === 0) {
|
|
339
|
-
const result = await
|
|
387
|
+
const result = await llm.complete(this.systemPrompt, input.data, ctx.signal);
|
|
340
388
|
return { data: result };
|
|
341
389
|
}
|
|
342
390
|
const toolDefs = this.tools.map((tool) => ({
|
|
@@ -349,7 +397,6 @@ var ScriptSkill = class {
|
|
|
349
397
|
}))
|
|
350
398
|
}));
|
|
351
399
|
const messages = [{ role: "user", content: input.data }];
|
|
352
|
-
const llm = ctx.llm;
|
|
353
400
|
for (let round = 0; round < this.maxToolRounds; round++) {
|
|
354
401
|
if (ctx.signal?.aborted) {
|
|
355
402
|
throw new Error("Job aborted");
|
|
@@ -381,76 +428,191 @@ var ScriptSkill = class {
|
|
|
381
428
|
}
|
|
382
429
|
throw new Error(`Max tool rounds (${this.maxToolRounds}) exceeded`);
|
|
383
430
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
431
|
+
/**
|
|
432
|
+
* Resolve the LLM client for this skill from the runtime context.
|
|
433
|
+
*
|
|
434
|
+
* Contract:
|
|
435
|
+
* - When `llmOverride` is set, `ctx.getLlm` MUST be wired. Falling back to
|
|
436
|
+
* `ctx.llm` (the agent default) would silently use the wrong configuration
|
|
437
|
+
* for max-tokens-only overrides.
|
|
438
|
+
* - When no override is set, prefer `ctx.getLlm()` (returns the agent
|
|
439
|
+
* default), then fall back to `ctx.llm` for legacy callers that wire only
|
|
440
|
+
* a single client.
|
|
441
|
+
*/
|
|
442
|
+
resolveLlmClient(ctx) {
|
|
443
|
+
let client;
|
|
444
|
+
if (this.llmOverride) {
|
|
445
|
+
client = ctx.getLlm?.(this.llmOverride);
|
|
446
|
+
if (!client) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Skill "${this.name}" requires ctx.getLlm to be configured (llmOverride is set)`
|
|
449
|
+
);
|
|
391
450
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
451
|
+
return client;
|
|
452
|
+
}
|
|
453
|
+
client = ctx.getLlm?.() ?? ctx.llm;
|
|
454
|
+
if (!client) {
|
|
455
|
+
throw new Error("LLM client not configured for skill runtime");
|
|
456
|
+
}
|
|
457
|
+
return client;
|
|
458
|
+
}
|
|
459
|
+
async runTool(toolDef, call, signal) {
|
|
460
|
+
const args = [...toolDef.command];
|
|
461
|
+
const cmd = args.shift();
|
|
462
|
+
if (!cmd) {
|
|
463
|
+
return `Error: tool "${toolDef.name}" has an empty command`;
|
|
464
|
+
}
|
|
465
|
+
const params = toolDef.parameters ?? [];
|
|
466
|
+
for (let index = 0; index < params.length; index++) {
|
|
467
|
+
const param = params[index];
|
|
468
|
+
if (!param) {
|
|
469
|
+
continue;
|
|
407
470
|
}
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
471
|
+
const value = call.arguments[param.name];
|
|
472
|
+
if (value === void 0) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (param.required && index === 0) {
|
|
476
|
+
args.push(String(value));
|
|
477
|
+
} else {
|
|
478
|
+
args.push(`--${param.name}`, String(value));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
const result = await runScript(cmd, args, { cwd: this.skillDir, signal });
|
|
482
|
+
if (result.spawnError) {
|
|
483
|
+
return `Error: ${result.spawnError.message}`;
|
|
484
|
+
}
|
|
485
|
+
if (result.code === 0) {
|
|
486
|
+
return result.stdout.trim();
|
|
487
|
+
}
|
|
488
|
+
this.logger.debug?.(
|
|
489
|
+
{ tool: toolDef.name, code: result.code, stderrLen: result.stderr.length },
|
|
490
|
+
"skill tool exited non-zero"
|
|
491
|
+
);
|
|
492
|
+
return `Error (exit ${result.code}): ${result.stderr.trim() || result.stdout.trim()}`;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
var MAX_STATIC_FILE_SIZE = 256 * 1024;
|
|
496
|
+
var StaticFileSkill = class {
|
|
497
|
+
name;
|
|
498
|
+
description;
|
|
499
|
+
capabilities;
|
|
500
|
+
priceSubunits;
|
|
501
|
+
asset;
|
|
502
|
+
mode = "static-file";
|
|
503
|
+
image;
|
|
504
|
+
imageFile;
|
|
505
|
+
outputFilePath;
|
|
506
|
+
constructor(params) {
|
|
507
|
+
this.name = params.name;
|
|
508
|
+
this.description = params.description;
|
|
509
|
+
this.capabilities = params.capabilities;
|
|
510
|
+
this.priceSubunits = params.priceSubunits;
|
|
511
|
+
this.asset = params.asset;
|
|
512
|
+
this.image = params.image;
|
|
513
|
+
this.imageFile = params.imageFile;
|
|
514
|
+
this.outputFilePath = params.outputFilePath;
|
|
515
|
+
}
|
|
516
|
+
async execute(_input, _ctx) {
|
|
517
|
+
const buffer = await readFile(this.outputFilePath);
|
|
518
|
+
if (buffer.length > MAX_STATIC_FILE_SIZE) {
|
|
519
|
+
throw new Error(
|
|
520
|
+
`static-file output exceeds ${MAX_STATIC_FILE_SIZE} bytes (got ${buffer.length})`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
return { data: buffer.toString("utf-8") };
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
var StaticScriptSkill = class {
|
|
527
|
+
name;
|
|
528
|
+
description;
|
|
529
|
+
capabilities;
|
|
530
|
+
priceSubunits;
|
|
531
|
+
asset;
|
|
532
|
+
mode = "static-script";
|
|
533
|
+
image;
|
|
534
|
+
imageFile;
|
|
535
|
+
scriptPath;
|
|
536
|
+
scriptArgs;
|
|
537
|
+
scriptTimeoutMs;
|
|
538
|
+
constructor(params) {
|
|
539
|
+
this.name = params.name;
|
|
540
|
+
this.description = params.description;
|
|
541
|
+
this.capabilities = params.capabilities;
|
|
542
|
+
this.priceSubunits = params.priceSubunits;
|
|
543
|
+
this.asset = params.asset;
|
|
544
|
+
this.image = params.image;
|
|
545
|
+
this.imageFile = params.imageFile;
|
|
546
|
+
this.scriptPath = params.scriptPath;
|
|
547
|
+
this.scriptArgs = params.scriptArgs;
|
|
548
|
+
this.scriptTimeoutMs = params.scriptTimeoutMs;
|
|
549
|
+
}
|
|
550
|
+
async execute(_input, ctx) {
|
|
551
|
+
const result = await runScript(this.scriptPath, this.scriptArgs, {
|
|
552
|
+
cwd: dirname(this.scriptPath),
|
|
553
|
+
signal: ctx.signal,
|
|
554
|
+
timeoutMs: this.scriptTimeoutMs
|
|
451
555
|
});
|
|
556
|
+
if (result.spawnError) {
|
|
557
|
+
throw new Error(`script spawn failed: ${result.spawnError.message}`);
|
|
558
|
+
}
|
|
559
|
+
if (result.code !== 0) {
|
|
560
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "(no output)";
|
|
561
|
+
throw new Error(`script failed (exit ${result.code}): ${detail}`);
|
|
562
|
+
}
|
|
563
|
+
return { data: result.stdout.trim() };
|
|
452
564
|
}
|
|
453
565
|
};
|
|
566
|
+
var DynamicScriptSkill = class {
|
|
567
|
+
name;
|
|
568
|
+
description;
|
|
569
|
+
capabilities;
|
|
570
|
+
priceSubunits;
|
|
571
|
+
asset;
|
|
572
|
+
mode = "dynamic-script";
|
|
573
|
+
image;
|
|
574
|
+
imageFile;
|
|
575
|
+
scriptPath;
|
|
576
|
+
scriptArgs;
|
|
577
|
+
scriptTimeoutMs;
|
|
578
|
+
constructor(params) {
|
|
579
|
+
this.name = params.name;
|
|
580
|
+
this.description = params.description;
|
|
581
|
+
this.capabilities = params.capabilities;
|
|
582
|
+
this.priceSubunits = params.priceSubunits;
|
|
583
|
+
this.asset = params.asset;
|
|
584
|
+
this.image = params.image;
|
|
585
|
+
this.imageFile = params.imageFile;
|
|
586
|
+
this.scriptPath = params.scriptPath;
|
|
587
|
+
this.scriptArgs = params.scriptArgs;
|
|
588
|
+
this.scriptTimeoutMs = params.scriptTimeoutMs;
|
|
589
|
+
}
|
|
590
|
+
async execute(input, ctx) {
|
|
591
|
+
const result = await runScript(this.scriptPath, this.scriptArgs, {
|
|
592
|
+
cwd: dirname(this.scriptPath),
|
|
593
|
+
stdin: input.data,
|
|
594
|
+
signal: ctx.signal,
|
|
595
|
+
timeoutMs: this.scriptTimeoutMs
|
|
596
|
+
});
|
|
597
|
+
if (result.spawnError) {
|
|
598
|
+
throw new Error(`script spawn failed: ${result.spawnError.message}`);
|
|
599
|
+
}
|
|
600
|
+
if (result.code !== 0) {
|
|
601
|
+
const detail = result.stderr.trim() || result.stdout.trim() || "(no output)";
|
|
602
|
+
throw new Error(`script failed (exit ${result.code}): ${detail}`);
|
|
603
|
+
}
|
|
604
|
+
return { data: result.stdout.trim() };
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
function resolveInsidePath(rootDir, value) {
|
|
608
|
+
const root = resolve(rootDir);
|
|
609
|
+
const candidate = resolve(root, value);
|
|
610
|
+
const rel = relative(root, candidate);
|
|
611
|
+
if (rel === "" || rel.startsWith("..") || rel.includes(`..${sep}`)) {
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
return candidate;
|
|
615
|
+
}
|
|
454
616
|
var LAMPORTS_PER_SOL = 1e9;
|
|
455
617
|
var NATIVE_SOL = {
|
|
456
618
|
chain: "solana",
|
|
@@ -519,7 +681,15 @@ function parseAssetAmount(asset, human) {
|
|
|
519
681
|
Decimal.clone({ toExpNeg: -100, toExpPos: 100, precision: 50 });
|
|
520
682
|
|
|
521
683
|
// src/skills/loader.ts
|
|
684
|
+
var VALID_PROVIDERS = ["anthropic", "openai"];
|
|
685
|
+
var MAX_TOKENS_LIMIT = 2e5;
|
|
522
686
|
var DEFAULT_MAX_TOOL_ROUNDS = 10;
|
|
687
|
+
var VALID_MODES = [
|
|
688
|
+
"llm",
|
|
689
|
+
"static-file",
|
|
690
|
+
"static-script",
|
|
691
|
+
"dynamic-script"
|
|
692
|
+
];
|
|
523
693
|
function solToLamports(sol) {
|
|
524
694
|
const asNumber = typeof sol === "string" ? Number(sol) : sol;
|
|
525
695
|
if (!Number.isFinite(asNumber) || asNumber < 0) {
|
|
@@ -636,6 +806,86 @@ function validateTool(raw, skillName, index) {
|
|
|
636
806
|
parameters
|
|
637
807
|
};
|
|
638
808
|
}
|
|
809
|
+
function validateMode(skillName, raw) {
|
|
810
|
+
if (raw === void 0 || raw === null) {
|
|
811
|
+
return "llm";
|
|
812
|
+
}
|
|
813
|
+
if (typeof raw !== "string") {
|
|
814
|
+
throw new Error(`SKILL.md "${skillName}": "mode" must be a string`);
|
|
815
|
+
}
|
|
816
|
+
if (!VALID_MODES.includes(raw)) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
`SKILL.md "${skillName}": invalid mode "${raw}". Allowed: ${VALID_MODES.join(", ")}`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
return raw;
|
|
822
|
+
}
|
|
823
|
+
function validateScriptArgs(skillName, raw) {
|
|
824
|
+
if (raw === void 0 || raw === null) {
|
|
825
|
+
return [];
|
|
826
|
+
}
|
|
827
|
+
if (!Array.isArray(raw)) {
|
|
828
|
+
throw new Error(`SKILL.md "${skillName}": "script_args" must be an array of strings`);
|
|
829
|
+
}
|
|
830
|
+
for (const part of raw) {
|
|
831
|
+
if (typeof part !== "string") {
|
|
832
|
+
throw new Error(`SKILL.md "${skillName}": "script_args" entries must be strings`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return raw;
|
|
836
|
+
}
|
|
837
|
+
function validateLlmOverride(skillName, frontmatter, mode) {
|
|
838
|
+
const hasProvider = frontmatter.provider !== void 0 && frontmatter.provider !== null;
|
|
839
|
+
const hasModel = frontmatter.model !== void 0 && frontmatter.model !== null;
|
|
840
|
+
const hasMaxTokens = frontmatter.max_tokens !== void 0 && frontmatter.max_tokens !== null;
|
|
841
|
+
if (!hasProvider && !hasModel && !hasMaxTokens) {
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
if (mode !== "llm") {
|
|
845
|
+
throw new Error(
|
|
846
|
+
`SKILL.md "${skillName}": "provider"/"model"/"max_tokens" are only valid in mode 'llm' (got '${mode}')`
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (hasProvider !== hasModel) {
|
|
850
|
+
throw new Error(
|
|
851
|
+
`SKILL.md "${skillName}": "provider" and "model" must be set together (declare both, or neither)`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
const override = {};
|
|
855
|
+
if (hasProvider && hasModel) {
|
|
856
|
+
if (typeof frontmatter.provider !== "string") {
|
|
857
|
+
throw new Error(`SKILL.md "${skillName}": "provider" must be a string`);
|
|
858
|
+
}
|
|
859
|
+
if (!VALID_PROVIDERS.includes(frontmatter.provider)) {
|
|
860
|
+
throw new Error(
|
|
861
|
+
`SKILL.md "${skillName}": invalid provider "${frontmatter.provider}". Allowed: ${VALID_PROVIDERS.join(", ")}`
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
if (typeof frontmatter.model !== "string" || frontmatter.model.length === 0) {
|
|
865
|
+
throw new Error(`SKILL.md "${skillName}": "model" must be a non-empty string`);
|
|
866
|
+
}
|
|
867
|
+
override.provider = frontmatter.provider;
|
|
868
|
+
override.model = frontmatter.model;
|
|
869
|
+
}
|
|
870
|
+
if (hasMaxTokens) {
|
|
871
|
+
if (typeof frontmatter.max_tokens !== "number" || !Number.isInteger(frontmatter.max_tokens) || frontmatter.max_tokens <= 0 || frontmatter.max_tokens > MAX_TOKENS_LIMIT) {
|
|
872
|
+
throw new Error(
|
|
873
|
+
`SKILL.md "${skillName}": "max_tokens" must be a positive integer <= ${MAX_TOKENS_LIMIT}`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
override.maxTokens = frontmatter.max_tokens;
|
|
877
|
+
}
|
|
878
|
+
return override;
|
|
879
|
+
}
|
|
880
|
+
function validateScriptTimeoutMs(skillName, raw) {
|
|
881
|
+
if (raw === void 0 || raw === null) {
|
|
882
|
+
return void 0;
|
|
883
|
+
}
|
|
884
|
+
if (typeof raw !== "number" || !Number.isInteger(raw) || raw <= 0) {
|
|
885
|
+
throw new Error(`SKILL.md "${skillName}": "script_timeout_ms" must be a positive integer`);
|
|
886
|
+
}
|
|
887
|
+
return raw;
|
|
888
|
+
}
|
|
639
889
|
function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
|
|
640
890
|
if (typeof frontmatter.name !== "string" || frontmatter.name.length === 0) {
|
|
641
891
|
throw new Error('SKILL.md: missing or invalid "name" field');
|
|
@@ -687,8 +937,14 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
|
|
|
687
937
|
);
|
|
688
938
|
}
|
|
689
939
|
}
|
|
940
|
+
const mode = validateMode(frontmatter.name, frontmatter.mode);
|
|
690
941
|
const tools = [];
|
|
691
942
|
if (frontmatter.tools !== void 0) {
|
|
943
|
+
if (mode !== "llm") {
|
|
944
|
+
throw new Error(
|
|
945
|
+
`SKILL.md "${frontmatter.name}": "tools" is only valid in mode 'llm' (got '${mode}')`
|
|
946
|
+
);
|
|
947
|
+
}
|
|
692
948
|
if (!Array.isArray(frontmatter.tools)) {
|
|
693
949
|
throw new Error(`SKILL.md "${frontmatter.name}": "tools" must be an array`);
|
|
694
950
|
}
|
|
@@ -698,6 +954,11 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
|
|
|
698
954
|
}
|
|
699
955
|
let maxToolRounds = DEFAULT_MAX_TOOL_ROUNDS;
|
|
700
956
|
if (frontmatter.max_tool_rounds !== void 0) {
|
|
957
|
+
if (mode !== "llm") {
|
|
958
|
+
throw new Error(
|
|
959
|
+
`SKILL.md "${frontmatter.name}": "max_tool_rounds" is only valid in mode 'llm' (got '${mode}')`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
701
962
|
if (typeof frontmatter.max_tool_rounds !== "number" || !Number.isInteger(frontmatter.max_tool_rounds) || frontmatter.max_tool_rounds <= 0) {
|
|
702
963
|
throw new Error(
|
|
703
964
|
`SKILL.md "${frontmatter.name}": "max_tool_rounds" must be a positive integer`
|
|
@@ -705,21 +966,146 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
|
|
|
705
966
|
}
|
|
706
967
|
maxToolRounds = frontmatter.max_tool_rounds;
|
|
707
968
|
}
|
|
969
|
+
let outputFile;
|
|
970
|
+
let script;
|
|
971
|
+
let scriptArgs = [];
|
|
972
|
+
let scriptTimeoutMs;
|
|
973
|
+
if (mode === "static-file") {
|
|
974
|
+
if (typeof frontmatter.output_file !== "string" || frontmatter.output_file.length === 0) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
`SKILL.md "${frontmatter.name}": mode 'static-file' requires "output_file" (string)`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
if (frontmatter.script !== void 0) {
|
|
980
|
+
throw new Error(
|
|
981
|
+
`SKILL.md "${frontmatter.name}": "script" is not valid in mode 'static-file'`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
outputFile = frontmatter.output_file;
|
|
985
|
+
} else if (mode === "static-script" || mode === "dynamic-script") {
|
|
986
|
+
if (typeof frontmatter.script !== "string" || frontmatter.script.length === 0) {
|
|
987
|
+
throw new Error(`SKILL.md "${frontmatter.name}": mode '${mode}' requires "script" (string)`);
|
|
988
|
+
}
|
|
989
|
+
if (frontmatter.output_file !== void 0) {
|
|
990
|
+
throw new Error(
|
|
991
|
+
`SKILL.md "${frontmatter.name}": "output_file" is only valid in mode 'static-file'`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
script = frontmatter.script;
|
|
995
|
+
scriptArgs = validateScriptArgs(frontmatter.name, frontmatter.script_args);
|
|
996
|
+
scriptTimeoutMs = validateScriptTimeoutMs(frontmatter.name, frontmatter.script_timeout_ms);
|
|
997
|
+
} else {
|
|
998
|
+
if (frontmatter.output_file !== void 0) {
|
|
999
|
+
throw new Error(
|
|
1000
|
+
`SKILL.md "${frontmatter.name}": "output_file" is only valid in mode 'static-file'`
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
if (frontmatter.script !== void 0) {
|
|
1004
|
+
throw new Error(
|
|
1005
|
+
`SKILL.md "${frontmatter.name}": "script" is only valid in script modes (static-script, dynamic-script)`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
if (frontmatter.script_args !== void 0) {
|
|
1009
|
+
throw new Error(
|
|
1010
|
+
`SKILL.md "${frontmatter.name}": "script_args" is only valid in script modes`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
if (frontmatter.script_timeout_ms !== void 0) {
|
|
1014
|
+
throw new Error(
|
|
1015
|
+
`SKILL.md "${frontmatter.name}": "script_timeout_ms" is only valid in script modes`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
708
1019
|
const image = typeof frontmatter.image === "string" ? frontmatter.image : void 0;
|
|
709
1020
|
const imageFile = typeof frontmatter.image_file === "string" ? frontmatter.image_file : void 0;
|
|
1021
|
+
const llmOverride = validateLlmOverride(frontmatter.name, frontmatter, mode);
|
|
710
1022
|
return {
|
|
711
1023
|
name: frontmatter.name,
|
|
712
1024
|
description: frontmatter.description,
|
|
713
1025
|
capabilities,
|
|
714
1026
|
priceSubunits,
|
|
715
1027
|
asset,
|
|
1028
|
+
mode,
|
|
716
1029
|
systemPrompt,
|
|
717
1030
|
tools,
|
|
718
1031
|
maxToolRounds,
|
|
1032
|
+
llmOverride,
|
|
719
1033
|
image,
|
|
720
|
-
imageFile
|
|
1034
|
+
imageFile,
|
|
1035
|
+
outputFile,
|
|
1036
|
+
script,
|
|
1037
|
+
scriptArgs,
|
|
1038
|
+
scriptTimeoutMs
|
|
721
1039
|
};
|
|
722
1040
|
}
|
|
1041
|
+
function buildSkillFromParsed(parsed, skillDir, logger) {
|
|
1042
|
+
switch (parsed.mode) {
|
|
1043
|
+
case "llm":
|
|
1044
|
+
return new ScriptSkill({
|
|
1045
|
+
name: parsed.name,
|
|
1046
|
+
description: parsed.description,
|
|
1047
|
+
capabilities: parsed.capabilities,
|
|
1048
|
+
priceSubunits: parsed.priceSubunits,
|
|
1049
|
+
asset: parsed.asset,
|
|
1050
|
+
skillDir,
|
|
1051
|
+
systemPrompt: parsed.systemPrompt,
|
|
1052
|
+
tools: parsed.tools,
|
|
1053
|
+
maxToolRounds: parsed.maxToolRounds,
|
|
1054
|
+
llmOverride: parsed.llmOverride,
|
|
1055
|
+
image: parsed.image,
|
|
1056
|
+
imageFile: parsed.imageFile,
|
|
1057
|
+
logger
|
|
1058
|
+
});
|
|
1059
|
+
case "static-file": {
|
|
1060
|
+
if (parsed.outputFile === void 0) {
|
|
1061
|
+
throw new Error(
|
|
1062
|
+
`SKILL.md "${parsed.name}": internal error - outputFile missing for mode 'static-file'`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
const outputFilePath = resolveInsidePath(skillDir, parsed.outputFile);
|
|
1066
|
+
if (!outputFilePath) {
|
|
1067
|
+
throw new Error(
|
|
1068
|
+
`SKILL.md "${parsed.name}": "output_file" must stay inside the skill directory`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
return new StaticFileSkill({
|
|
1072
|
+
name: parsed.name,
|
|
1073
|
+
description: parsed.description,
|
|
1074
|
+
capabilities: parsed.capabilities,
|
|
1075
|
+
priceSubunits: parsed.priceSubunits,
|
|
1076
|
+
asset: parsed.asset,
|
|
1077
|
+
outputFilePath,
|
|
1078
|
+
image: parsed.image,
|
|
1079
|
+
imageFile: parsed.imageFile
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
case "static-script":
|
|
1083
|
+
case "dynamic-script": {
|
|
1084
|
+
if (parsed.script === void 0) {
|
|
1085
|
+
throw new Error(
|
|
1086
|
+
`SKILL.md "${parsed.name}": internal error - script missing for mode '${parsed.mode}'`
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
const scriptPath = resolveInsidePath(skillDir, parsed.script);
|
|
1090
|
+
if (!scriptPath) {
|
|
1091
|
+
throw new Error(`SKILL.md "${parsed.name}": "script" must stay inside the skill directory`);
|
|
1092
|
+
}
|
|
1093
|
+
const Ctor = parsed.mode === "static-script" ? StaticScriptSkill : DynamicScriptSkill;
|
|
1094
|
+
return new Ctor({
|
|
1095
|
+
name: parsed.name,
|
|
1096
|
+
description: parsed.description,
|
|
1097
|
+
capabilities: parsed.capabilities,
|
|
1098
|
+
priceSubunits: parsed.priceSubunits,
|
|
1099
|
+
asset: parsed.asset,
|
|
1100
|
+
scriptPath,
|
|
1101
|
+
scriptArgs: parsed.scriptArgs,
|
|
1102
|
+
scriptTimeoutMs: parsed.scriptTimeoutMs ?? DEFAULT_SCRIPT_TIMEOUT_MS,
|
|
1103
|
+
image: parsed.image,
|
|
1104
|
+
imageFile: parsed.imageFile
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
723
1109
|
function loadSkillsFromDir(skillsDir, options = {}) {
|
|
724
1110
|
const logger = options.logger ?? {};
|
|
725
1111
|
const skills = [];
|
|
@@ -744,22 +1130,7 @@ function loadSkillsFromDir(skillsDir, options = {}) {
|
|
|
744
1130
|
const content = readFileSync(skillMdPath, "utf-8");
|
|
745
1131
|
const { frontmatter, systemPrompt } = parseSkillMd(content);
|
|
746
1132
|
const parsed = validateSkillFrontmatter(frontmatter, systemPrompt, options);
|
|
747
|
-
skills.push(
|
|
748
|
-
new ScriptSkill({
|
|
749
|
-
name: parsed.name,
|
|
750
|
-
description: parsed.description,
|
|
751
|
-
capabilities: parsed.capabilities,
|
|
752
|
-
priceSubunits: parsed.priceSubunits,
|
|
753
|
-
asset: parsed.asset,
|
|
754
|
-
skillDir: entryPath,
|
|
755
|
-
systemPrompt: parsed.systemPrompt,
|
|
756
|
-
tools: parsed.tools,
|
|
757
|
-
maxToolRounds: parsed.maxToolRounds,
|
|
758
|
-
image: parsed.image,
|
|
759
|
-
imageFile: parsed.imageFile,
|
|
760
|
-
logger
|
|
761
|
-
})
|
|
762
|
-
);
|
|
1133
|
+
skills.push(buildSkillFromParsed(parsed, entryPath, logger));
|
|
763
1134
|
} catch (error) {
|
|
764
1135
|
const message = error instanceof Error ? error.message : String(error);
|
|
765
1136
|
logger.warn?.({ dir: entry, err: message }, "skipping malformed skill directory");
|
|
@@ -768,6 +1139,6 @@ function loadSkillsFromDir(skillsDir, options = {}) {
|
|
|
768
1139
|
return skills;
|
|
769
1140
|
}
|
|
770
1141
|
|
|
771
|
-
export { DEFAULT_MAX_TOOL_ROUNDS, ScriptSkill, createAnthropicClient, createLlmClient, createOpenAIClient, loadSkillsFromDir, parseSkillMd, validateSkillFrontmatter };
|
|
1142
|
+
export { DEFAULT_MAX_TOOL_ROUNDS, DEFAULT_SCRIPT_TIMEOUT_MS, DynamicScriptSkill, MAX_SCRIPT_OUTPUT, MAX_STATIC_FILE_SIZE, ScriptSkill, StaticFileSkill, StaticScriptSkill, createAnthropicClient, createLlmClient, createOpenAIClient, loadSkillsFromDir, parseSkillMd, resolveInsidePath, runScript, validateSkillFrontmatter };
|
|
772
1143
|
//# sourceMappingURL=skills.js.map
|
|
773
1144
|
//# sourceMappingURL=skills.js.map
|