@elisym/sdk 0.23.0 → 0.24.1

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.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { A as Asset } from './assets-C-nzSYD4.cjs';
2
- import { S as SkillRateLimit } from './types-Cdscy9kY.cjs';
2
+ import { S as SkillRateLimit } from './types-CqN9kFTn.cjs';
3
3
 
4
4
  /**
5
5
  * Shared SKILL.md runtime types. A skill is a markdown document whose
@@ -12,10 +12,29 @@ interface SkillInput {
12
12
  inputType: string;
13
13
  tags: string[];
14
14
  jobId: string;
15
+ /**
16
+ * Local path to a file input fetched out-of-band (P2P via iroh), when the job
17
+ * carried a file attachment. The runtime fetches it after payment and removes
18
+ * it after execution; the skill reads from disk rather than from `data`.
19
+ */
20
+ filePath?: string;
15
21
  }
16
22
  interface SkillOutput {
17
23
  data: string;
18
24
  outputMime?: string;
25
+ /**
26
+ * Local path to a file result. When set, the runtime seeds the file via iroh
27
+ * and the customer fetches it out-of-band; `data` carries any text note (or '').
28
+ * `outputMime` is reused as the attachment's mime.
29
+ */
30
+ filePath?: string;
31
+ /**
32
+ * Releases the resources backing `filePath` (e.g. a temp dir). The runtime
33
+ * calls it once it has seeded the file - or failed to - since seeding happens
34
+ * after `execute()` returns and the producer cannot release the file itself.
35
+ * Mirrors the input-side cleanup callback in the runtime's `resolveInputFile`.
36
+ */
37
+ cleanup?: () => Promise<void>;
19
38
  }
20
39
  /**
21
40
  * Optional per-skill LLM override declared in SKILL.md frontmatter.
@@ -342,6 +361,12 @@ interface DynamicScriptSkillParams {
342
361
  * itself reads the key from its environment.
343
362
  */
344
363
  llmOverride?: SkillLlmOverride;
364
+ /**
365
+ * MIME type the script declares for a file result (SKILL.md `output_mime`).
366
+ * Used only when the script writes a file to `ELISYM_OUTPUT_FILE`; becomes the
367
+ * iroh attachment's mime. Defaults to `application/octet-stream`.
368
+ */
369
+ outputMime?: string;
345
370
  }
346
371
  /**
347
372
  * Pipes the user's job input to the script's stdin and returns its
@@ -363,6 +388,7 @@ declare class DynamicScriptSkill implements Skill {
363
388
  private scriptArgs;
364
389
  private scriptTimeoutMs?;
365
390
  private scriptEnv?;
391
+ private outputMime?;
366
392
  constructor(params: DynamicScriptSkillParams);
367
393
  execute(input: SkillInput, ctx: SkillContext): Promise<SkillOutput>;
368
394
  }
@@ -411,6 +437,20 @@ interface SkillFrontmatter {
411
437
  script_args?: unknown;
412
438
  /** Optional override of `DEFAULT_SCRIPT_TIMEOUT_MS`. */
413
439
  script_timeout_ms?: unknown;
440
+ /**
441
+ * MIME type for a file result. Only valid in mode 'dynamic-script' (the only
442
+ * mode that can emit a file via `ELISYM_OUTPUT_FILE`). Becomes the iroh
443
+ * attachment's mime; defaults to `application/octet-stream` when omitted.
444
+ */
445
+ output_mime?: unknown;
446
+ /**
447
+ * MIME the skill expects as a file input. Only valid in mode 'dynamic-script'
448
+ * (the only mode that receives a file via `ELISYM_INPUT_FILE`). Discovery hint
449
+ * only - the runtime still content-sniffs the actual file and does not enforce
450
+ * this. Convention: `*` = any file, `image/*` = any image, `image/png` =
451
+ * exact. Its presence signals "this capability needs a file input".
452
+ */
453
+ input_mime?: unknown;
414
454
  /**
415
455
  * Optional per-skill rate limit. Applies to any skill mode. Snake-case
416
456
  * keys here match the YAML frontmatter convention; parsed into camelCase
@@ -455,6 +495,14 @@ interface ParsedSkill {
455
495
  scriptArgs: string[];
456
496
  /** Undefined => caller uses `DEFAULT_SCRIPT_TIMEOUT_MS`. */
457
497
  scriptTimeoutMs?: number;
498
+ /** MIME for a file result (mode 'dynamic-script' only). */
499
+ outputMime?: string;
500
+ /**
501
+ * MIME the skill expects as a file input (mode 'dynamic-script' only).
502
+ * Discovery hint only; not enforced at runtime. Presence signals the
503
+ * capability needs a file input (clients gate file-only flows on it).
504
+ */
505
+ inputMime?: string;
458
506
  /** Optional per-skill rate limit (any mode). */
459
507
  rateLimit?: SkillRateLimit;
460
508
  /**
package/dist/skills.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { A as Asset } from './assets-C-nzSYD4.js';
2
- import { S as SkillRateLimit } from './types-Cdscy9kY.js';
2
+ import { S as SkillRateLimit } from './types-CqN9kFTn.js';
3
3
 
4
4
  /**
5
5
  * Shared SKILL.md runtime types. A skill is a markdown document whose
@@ -12,10 +12,29 @@ interface SkillInput {
12
12
  inputType: string;
13
13
  tags: string[];
14
14
  jobId: string;
15
+ /**
16
+ * Local path to a file input fetched out-of-band (P2P via iroh), when the job
17
+ * carried a file attachment. The runtime fetches it after payment and removes
18
+ * it after execution; the skill reads from disk rather than from `data`.
19
+ */
20
+ filePath?: string;
15
21
  }
16
22
  interface SkillOutput {
17
23
  data: string;
18
24
  outputMime?: string;
25
+ /**
26
+ * Local path to a file result. When set, the runtime seeds the file via iroh
27
+ * and the customer fetches it out-of-band; `data` carries any text note (or '').
28
+ * `outputMime` is reused as the attachment's mime.
29
+ */
30
+ filePath?: string;
31
+ /**
32
+ * Releases the resources backing `filePath` (e.g. a temp dir). The runtime
33
+ * calls it once it has seeded the file - or failed to - since seeding happens
34
+ * after `execute()` returns and the producer cannot release the file itself.
35
+ * Mirrors the input-side cleanup callback in the runtime's `resolveInputFile`.
36
+ */
37
+ cleanup?: () => Promise<void>;
19
38
  }
20
39
  /**
21
40
  * Optional per-skill LLM override declared in SKILL.md frontmatter.
@@ -342,6 +361,12 @@ interface DynamicScriptSkillParams {
342
361
  * itself reads the key from its environment.
343
362
  */
344
363
  llmOverride?: SkillLlmOverride;
364
+ /**
365
+ * MIME type the script declares for a file result (SKILL.md `output_mime`).
366
+ * Used only when the script writes a file to `ELISYM_OUTPUT_FILE`; becomes the
367
+ * iroh attachment's mime. Defaults to `application/octet-stream`.
368
+ */
369
+ outputMime?: string;
345
370
  }
346
371
  /**
347
372
  * Pipes the user's job input to the script's stdin and returns its
@@ -363,6 +388,7 @@ declare class DynamicScriptSkill implements Skill {
363
388
  private scriptArgs;
364
389
  private scriptTimeoutMs?;
365
390
  private scriptEnv?;
391
+ private outputMime?;
366
392
  constructor(params: DynamicScriptSkillParams);
367
393
  execute(input: SkillInput, ctx: SkillContext): Promise<SkillOutput>;
368
394
  }
@@ -411,6 +437,20 @@ interface SkillFrontmatter {
411
437
  script_args?: unknown;
412
438
  /** Optional override of `DEFAULT_SCRIPT_TIMEOUT_MS`. */
413
439
  script_timeout_ms?: unknown;
440
+ /**
441
+ * MIME type for a file result. Only valid in mode 'dynamic-script' (the only
442
+ * mode that can emit a file via `ELISYM_OUTPUT_FILE`). Becomes the iroh
443
+ * attachment's mime; defaults to `application/octet-stream` when omitted.
444
+ */
445
+ output_mime?: unknown;
446
+ /**
447
+ * MIME the skill expects as a file input. Only valid in mode 'dynamic-script'
448
+ * (the only mode that receives a file via `ELISYM_INPUT_FILE`). Discovery hint
449
+ * only - the runtime still content-sniffs the actual file and does not enforce
450
+ * this. Convention: `*` = any file, `image/*` = any image, `image/png` =
451
+ * exact. Its presence signals "this capability needs a file input".
452
+ */
453
+ input_mime?: unknown;
414
454
  /**
415
455
  * Optional per-skill rate limit. Applies to any skill mode. Snake-case
416
456
  * keys here match the YAML frontmatter convention; parsed into camelCase
@@ -455,6 +495,14 @@ interface ParsedSkill {
455
495
  scriptArgs: string[];
456
496
  /** Undefined => caller uses `DEFAULT_SCRIPT_TIMEOUT_MS`. */
457
497
  scriptTimeoutMs?: number;
498
+ /** MIME for a file result (mode 'dynamic-script' only). */
499
+ outputMime?: string;
500
+ /**
501
+ * MIME the skill expects as a file input (mode 'dynamic-script' only).
502
+ * Discovery hint only; not enforced at runtime. Presence signals the
503
+ * capability needs a file input (clients gate file-only flows on it).
504
+ */
505
+ inputMime?: string;
458
506
  /** Optional per-skill rate limit (any mode). */
459
507
  rateLimit?: SkillRateLimit;
460
508
  /**
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
+ import { readFile, mkdtemp, stat, rm } from 'node:fs/promises';
4
+ import { dirname, join, resolve, relative, sep } from 'node:path';
5
+ import { tmpdir } from 'node:os';
5
6
  import { readdirSync, statSync, readFileSync } from 'node:fs';
6
7
  import YAML from 'yaml';
7
8
  import Decimal from 'decimal.js-light';
@@ -345,6 +346,7 @@ var DynamicScriptSkill = class {
345
346
  scriptArgs;
346
347
  scriptTimeoutMs;
347
348
  scriptEnv;
349
+ outputMime;
348
350
  constructor(params) {
349
351
  this.name = params.name;
350
352
  this.description = params.description;
@@ -358,35 +360,65 @@ var DynamicScriptSkill = class {
358
360
  this.scriptArgs = params.scriptArgs;
359
361
  this.scriptTimeoutMs = params.scriptTimeoutMs;
360
362
  this.scriptEnv = params.scriptEnv;
363
+ this.outputMime = params.outputMime;
361
364
  }
362
365
  async execute(input, ctx) {
363
- const result = await runScript(this.scriptPath, this.scriptArgs, {
364
- cwd: dirname(this.scriptPath),
365
- stdin: input.data,
366
- signal: ctx.signal,
367
- timeoutMs: this.scriptTimeoutMs,
368
- env: this.scriptEnv
369
- });
370
- if (result.spawnError) {
371
- throw new ScriptExecutionError(
372
- null,
373
- result.spawnError.message,
374
- "script could not be started"
375
- );
376
- }
377
- if (result.code === SCRIPT_EXIT_BILLING_EXHAUSTED) {
378
- throw new ScriptBillingExhaustedError(result.code, result.stdout, result.stderr);
379
- }
380
- if (result.code !== 0) {
381
- const detail = result.stderr.trim() || result.stdout.trim() || "(no output)";
382
- throw new ScriptExecutionError(result.code, detail);
366
+ const outDir = await mkdtemp(join(tmpdir(), "elisym-skill-out-"));
367
+ const outputFile = join(outDir, "output");
368
+ const env = {
369
+ ...this.scriptEnv ?? process.env,
370
+ ELISYM_OUTPUT_FILE: outputFile
371
+ };
372
+ if (input.filePath !== void 0) {
373
+ env.ELISYM_INPUT_FILE = input.filePath;
383
374
  }
384
- const output = result.stdout.trim();
385
- if (output === "") {
386
- const detail = result.stderr.trim() || "(no stderr)";
387
- throw new ScriptExecutionError(result.code, detail, "script produced empty output");
375
+ let keepOutDir = false;
376
+ try {
377
+ const result = await runScript(this.scriptPath, this.scriptArgs, {
378
+ cwd: dirname(this.scriptPath),
379
+ stdin: input.data,
380
+ signal: ctx.signal,
381
+ timeoutMs: this.scriptTimeoutMs,
382
+ env
383
+ });
384
+ if (result.spawnError) {
385
+ throw new ScriptExecutionError(
386
+ null,
387
+ result.spawnError.message,
388
+ "script could not be started"
389
+ );
390
+ }
391
+ if (result.code === SCRIPT_EXIT_BILLING_EXHAUSTED) {
392
+ throw new ScriptBillingExhaustedError(result.code, result.stdout, result.stderr);
393
+ }
394
+ if (result.code !== 0) {
395
+ const detail = result.stderr.trim() || result.stdout.trim() || "(no output)";
396
+ throw new ScriptExecutionError(result.code, detail);
397
+ }
398
+ const outputStat = await stat(outputFile).catch(() => null);
399
+ if (outputStat !== null && outputStat.isFile() && outputStat.size > 0) {
400
+ keepOutDir = true;
401
+ return {
402
+ data: result.stdout.trim(),
403
+ filePath: outputFile,
404
+ outputMime: this.outputMime ?? "application/octet-stream",
405
+ cleanup: async () => {
406
+ await rm(outDir, { recursive: true, force: true });
407
+ }
408
+ };
409
+ }
410
+ const output = result.stdout.trim();
411
+ if (output === "") {
412
+ const detail = result.stderr.trim() || "(no stderr)";
413
+ throw new ScriptExecutionError(result.code, detail, "script produced empty output");
414
+ }
415
+ return { data: output };
416
+ } finally {
417
+ if (!keepOutDir) {
418
+ await rm(outDir, { recursive: true, force: true }).catch(() => {
419
+ });
420
+ }
388
421
  }
389
- return { data: output };
390
422
  }
391
423
  };
392
424
  function resolveInsidePath(rootDir, value) {
@@ -404,6 +436,7 @@ var LIMITS = {
404
436
  // hours, so this exists only to keep `secs * 1000` within Node's setTimeout limit
405
437
  // (2_147_483_647 ms) - a larger value overflows and fires the timer immediately.
406
438
  MAX_EXECUTION_SECS: 2147483};
439
+ new TextEncoder();
407
440
  var NATIVE_SOL = {
408
441
  chain: "solana",
409
442
  token: "sol",
@@ -701,6 +734,34 @@ function validateScriptTimeoutMs(skillName, raw) {
701
734
  }
702
735
  return raw;
703
736
  }
737
+ function validateOutputMime(skillName, raw) {
738
+ if (raw === void 0 || raw === null) {
739
+ return void 0;
740
+ }
741
+ if (typeof raw !== "string" || raw.length === 0) {
742
+ throw new Error(
743
+ `SKILL.md "${skillName}": "output_mime" must be a non-empty string (e.g. "image/png")`
744
+ );
745
+ }
746
+ if (raw.length > 255) {
747
+ throw new Error(`SKILL.md "${skillName}": "output_mime" too long (max 255 chars)`);
748
+ }
749
+ return raw;
750
+ }
751
+ function validateInputMime(skillName, raw) {
752
+ if (raw === void 0 || raw === null) {
753
+ return void 0;
754
+ }
755
+ if (typeof raw !== "string" || raw.length === 0) {
756
+ throw new Error(
757
+ `SKILL.md "${skillName}": "input_mime" must be a non-empty string (e.g. "image/png" or "*")`
758
+ );
759
+ }
760
+ if (raw.length > 255) {
761
+ throw new Error(`SKILL.md "${skillName}": "input_mime" too long (max 255 chars)`);
762
+ }
763
+ return raw;
764
+ }
704
765
  function validateMaxExecutionSecs(skillName, raw) {
705
766
  if (raw === void 0 || raw === null) {
706
767
  return void 0;
@@ -801,6 +862,8 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
801
862
  let script;
802
863
  let scriptArgs = [];
803
864
  let scriptTimeoutMs;
865
+ let outputMime;
866
+ let inputMime;
804
867
  if (mode === "static-file") {
805
868
  if (typeof frontmatter.output_file !== "string" || frontmatter.output_file.length === 0) {
806
869
  throw new Error(
@@ -812,6 +875,16 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
812
875
  `SKILL.md "${frontmatter.name}": "script" is not valid in mode 'static-file'`
813
876
  );
814
877
  }
878
+ if (frontmatter.output_mime !== void 0) {
879
+ throw new Error(
880
+ `SKILL.md "${frontmatter.name}": "output_mime" is only valid in mode 'dynamic-script'`
881
+ );
882
+ }
883
+ if (frontmatter.input_mime !== void 0) {
884
+ throw new Error(
885
+ `SKILL.md "${frontmatter.name}": "input_mime" is only valid in mode 'dynamic-script'`
886
+ );
887
+ }
815
888
  outputFile = frontmatter.output_file;
816
889
  } else if (mode === "static-script" || mode === "dynamic-script") {
817
890
  if (typeof frontmatter.script !== "string" || frontmatter.script.length === 0) {
@@ -825,6 +898,21 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
825
898
  script = frontmatter.script;
826
899
  scriptArgs = validateScriptArgs(frontmatter.name, frontmatter.script_args);
827
900
  scriptTimeoutMs = validateScriptTimeoutMs(frontmatter.name, frontmatter.script_timeout_ms);
901
+ if (mode === "dynamic-script") {
902
+ outputMime = validateOutputMime(frontmatter.name, frontmatter.output_mime);
903
+ inputMime = validateInputMime(frontmatter.name, frontmatter.input_mime);
904
+ } else {
905
+ if (frontmatter.output_mime !== void 0) {
906
+ throw new Error(
907
+ `SKILL.md "${frontmatter.name}": "output_mime" is only valid in mode 'dynamic-script'`
908
+ );
909
+ }
910
+ if (frontmatter.input_mime !== void 0) {
911
+ throw new Error(
912
+ `SKILL.md "${frontmatter.name}": "input_mime" is only valid in mode 'dynamic-script'`
913
+ );
914
+ }
915
+ }
828
916
  } else {
829
917
  if (frontmatter.output_file !== void 0) {
830
918
  throw new Error(
@@ -846,6 +934,16 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
846
934
  `SKILL.md "${frontmatter.name}": "script_timeout_ms" is only valid in script modes`
847
935
  );
848
936
  }
937
+ if (frontmatter.output_mime !== void 0) {
938
+ throw new Error(
939
+ `SKILL.md "${frontmatter.name}": "output_mime" is only valid in mode 'dynamic-script'`
940
+ );
941
+ }
942
+ if (frontmatter.input_mime !== void 0) {
943
+ throw new Error(
944
+ `SKILL.md "${frontmatter.name}": "input_mime" is only valid in mode 'dynamic-script'`
945
+ );
946
+ }
849
947
  }
850
948
  const image = typeof frontmatter.image === "string" ? frontmatter.image : void 0;
851
949
  const imageFile = typeof frontmatter.image_file === "string" ? frontmatter.image_file : void 0;
@@ -872,6 +970,8 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
872
970
  script,
873
971
  scriptArgs,
874
972
  scriptTimeoutMs,
973
+ outputMime,
974
+ inputMime,
875
975
  rateLimit,
876
976
  executionTimeoutSecs
877
977
  };
@@ -937,8 +1037,7 @@ function buildSkillFromParsed(parsed, skillDir, logger) {
937
1037
  if (!scriptPath) {
938
1038
  throw new Error(`SKILL.md "${parsed.name}": "script" must stay inside the skill directory`);
939
1039
  }
940
- const Ctor = parsed.mode === "static-script" ? StaticScriptSkill : DynamicScriptSkill;
941
- return new Ctor({
1040
+ const scriptParams = {
942
1041
  name: parsed.name,
943
1042
  description: parsed.description,
944
1043
  capabilities: parsed.capabilities,
@@ -950,7 +1049,8 @@ function buildSkillFromParsed(parsed, skillDir, logger) {
950
1049
  image: parsed.image,
951
1050
  imageFile,
952
1051
  llmOverride: parsed.llmOverride
953
- });
1052
+ };
1053
+ return parsed.mode === "dynamic-script" ? new DynamicScriptSkill({ ...scriptParams, outputMime: parsed.outputMime }) : new StaticScriptSkill(scriptParams);
954
1054
  }
955
1055
  }
956
1056
  }