@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/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((resolve) => setTimeout(resolve, ms));
26
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
26
27
  }
27
- return new Promise((resolve, reject) => {
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
- resolve();
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 MAX_TOOL_OUTPUT = 1e6;
306
- var TOOL_TIMEOUT_MS = 6e4;
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
- if (!ctx.llm) {
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 ctx.llm.complete(this.systemPrompt, input.data, ctx.signal);
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
- runTool(toolDef, call, signal) {
385
- return new Promise((resolve) => {
386
- const args = [...toolDef.command];
387
- const cmd = args.shift();
388
- if (!cmd) {
389
- resolve(`Error: tool "${toolDef.name}" has an empty command`);
390
- return;
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
- const params = toolDef.parameters ?? [];
393
- for (let index = 0; index < params.length; index++) {
394
- const param = params[index];
395
- if (!param) {
396
- continue;
397
- }
398
- const value = call.arguments[param.name];
399
- if (value === void 0) {
400
- continue;
401
- }
402
- if (param.required && index === 0) {
403
- args.push(String(value));
404
- } else {
405
- args.push(`--${param.name}`, String(value));
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 child = spawn(cmd, args, {
409
- cwd: this.skillDir,
410
- stdio: ["pipe", "pipe", "pipe"],
411
- timeout: TOOL_TIMEOUT_MS,
412
- killSignal: "SIGKILL",
413
- signal
414
- });
415
- let stdout = "";
416
- let stderr = "";
417
- const stdoutDecoder = new StringDecoder("utf8");
418
- const stderrDecoder = new StringDecoder("utf8");
419
- child.stdout?.on("data", (data) => {
420
- if (stdout.length < MAX_TOOL_OUTPUT) {
421
- stdout += stdoutDecoder.write(data);
422
- if (stdout.length > MAX_TOOL_OUTPUT) {
423
- stdout = stdout.slice(0, MAX_TOOL_OUTPUT);
424
- }
425
- }
426
- });
427
- child.stderr?.on("data", (data) => {
428
- if (stderr.length < MAX_TOOL_OUTPUT) {
429
- stderr += stderrDecoder.write(data);
430
- if (stderr.length > MAX_TOOL_OUTPUT) {
431
- stderr = stderr.slice(0, MAX_TOOL_OUTPUT);
432
- }
433
- }
434
- });
435
- child.on("close", (code) => {
436
- stdout += stdoutDecoder.end();
437
- stderr += stderrDecoder.end();
438
- if (code === 0) {
439
- resolve(stdout.trim());
440
- return;
441
- }
442
- this.logger.debug?.(
443
- { tool: toolDef.name, code, stderrLen: stderr.length },
444
- "skill tool exited non-zero"
445
- );
446
- resolve(`Error (exit ${code}): ${stderr.trim() || stdout.trim()}`);
447
- });
448
- child.on("error", (err) => {
449
- resolve(`Error: ${err.message}`);
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