@elisym/sdk 0.12.5 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/skills.d.ts CHANGED
@@ -16,9 +16,28 @@ interface SkillOutput {
16
16
  data: string;
17
17
  outputMime?: string;
18
18
  }
19
+ /**
20
+ * Optional per-skill LLM override declared in SKILL.md frontmatter.
21
+ *
22
+ * Parse-time invariant (enforced by `validateLlmOverride` in the loader):
23
+ * `provider` is set iff `model` is set. `maxTokens` is independent. So
24
+ * downstream code may rely on: when `provider !== undefined`, `model` is
25
+ * also defined.
26
+ */
27
+ interface SkillLlmOverride {
28
+ provider?: 'anthropic' | 'openai';
29
+ model?: string;
30
+ maxTokens?: number;
31
+ }
19
32
  interface SkillContext {
20
- /** Required only when the routed skill has `mode === 'llm'`. */
33
+ /** Agent-default LLM client. May be undefined when every LLM skill overrides. */
21
34
  llm?: LlmClient;
35
+ /**
36
+ * Resolve the LLM client for a skill. The runtime caches clients by
37
+ * resolved (provider, model, maxTokens) triple. Callers pass their
38
+ * `llmOverride` (or undefined for the agent default).
39
+ */
40
+ getLlm?: (override?: SkillLlmOverride) => LlmClient | undefined;
22
41
  agentName: string;
23
42
  agentDescription: string;
24
43
  signal?: AbortSignal;
@@ -74,6 +93,12 @@ interface Skill {
74
93
  asset: Asset;
75
94
  /** Execution mode. Default 'llm' for back-compat. */
76
95
  mode: SkillMode;
96
+ /**
97
+ * Optional per-skill LLM config override (only set when mode === 'llm').
98
+ * Carried through from SKILL.md frontmatter so the runtime can route this
99
+ * skill to a non-default model/provider/max_tokens.
100
+ */
101
+ llmOverride?: SkillLlmOverride;
77
102
  image?: string;
78
103
  imageFile?: string;
79
104
  execute(input: SkillInput, ctx: SkillContext): Promise<SkillOutput>;
@@ -144,6 +169,8 @@ interface ScriptSkillParams {
144
169
  systemPrompt: string;
145
170
  tools: SkillToolDef[];
146
171
  maxToolRounds: number;
172
+ /** Optional per-skill LLM override (provider/model pair and/or maxTokens). */
173
+ llmOverride?: SkillLlmOverride;
147
174
  image?: string;
148
175
  imageFile?: string;
149
176
  logger?: ScriptSkillLogger;
@@ -162,6 +189,7 @@ declare class ScriptSkill implements Skill {
162
189
  priceSubunits: bigint;
163
190
  asset: Asset;
164
191
  mode: SkillMode;
192
+ readonly llmOverride?: SkillLlmOverride;
165
193
  image?: string;
166
194
  imageFile?: string;
167
195
  private skillDir;
@@ -171,6 +199,18 @@ declare class ScriptSkill implements Skill {
171
199
  private logger;
172
200
  constructor(params: ScriptSkillParams);
173
201
  execute(input: SkillInput, ctx: SkillContext): Promise<SkillOutput>;
202
+ /**
203
+ * Resolve the LLM client for this skill from the runtime context.
204
+ *
205
+ * Contract:
206
+ * - When `llmOverride` is set, `ctx.getLlm` MUST be wired. Falling back to
207
+ * `ctx.llm` (the agent default) would silently use the wrong configuration
208
+ * for max-tokens-only overrides.
209
+ * - When no override is set, prefer `ctx.getLlm()` (returns the agent
210
+ * default), then fall back to `ctx.llm` for legacy callers that wire only
211
+ * a single client.
212
+ */
213
+ private resolveLlmClient;
174
214
  private runTool;
175
215
  }
176
216
 
@@ -304,6 +344,12 @@ interface SkillFrontmatter {
304
344
  image_file?: unknown;
305
345
  tools?: unknown;
306
346
  max_tool_rounds?: unknown;
347
+ /** Optional per-skill LLM provider override ('anthropic' | 'openai'). Pairs with `model`. */
348
+ provider?: unknown;
349
+ /** Optional per-skill LLM model override. Pairs with `provider`. */
350
+ model?: unknown;
351
+ /** Optional per-skill max_tokens override. Independent of provider/model. */
352
+ max_tokens?: unknown;
307
353
  /** Execution mode. Default 'llm'. */
308
354
  mode?: unknown;
309
355
  /** Required when mode === 'static-file'. Path relative to skill dir. */
@@ -326,6 +372,12 @@ interface ParsedSkill {
326
372
  systemPrompt: string;
327
373
  tools: SkillToolDef[];
328
374
  maxToolRounds: number;
375
+ /**
376
+ * Per-skill LLM override (only present when mode === 'llm' and the SKILL.md
377
+ * declared at least one of `provider`/`model`/`max_tokens`). Parse-time
378
+ * invariant: `provider` set iff `model` set.
379
+ */
380
+ llmOverride?: SkillLlmOverride;
329
381
  image?: string;
330
382
  imageFile?: string;
331
383
  /** Set when mode === 'static-file'. */
@@ -362,4 +414,4 @@ declare function validateSkillFrontmatter(frontmatter: SkillFrontmatter, systemP
362
414
  */
363
415
  declare function loadSkillsFromDir(skillsDir: string, options?: LoadSkillsOptions): Skill[];
364
416
 
365
- export { type CompletionResult, DEFAULT_MAX_TOOL_ROUNDS, DEFAULT_SCRIPT_TIMEOUT_MS, DynamicScriptSkill, type DynamicScriptSkillParams, type LlmClient, type LlmClientConfig, type LlmProvider, type LoadSkillsOptions, type LoaderLogger, MAX_SCRIPT_OUTPUT, MAX_STATIC_FILE_SIZE, type ParsedSkill, type RunScriptOptions, type RunScriptResult, ScriptSkill, type ScriptSkillLogger, type ScriptSkillParams, type Skill, type SkillContext, type SkillFrontmatter, type SkillInput, type SkillMode, type SkillOutput, type SkillToolDef, StaticFileSkill, type StaticFileSkillParams, StaticScriptSkill, type StaticScriptSkillParams, type ToolCall, type ToolDef, type ToolResult, createAnthropicClient, createLlmClient, createOpenAIClient, loadSkillsFromDir, parseSkillMd, resolveInsidePath, runScript, validateSkillFrontmatter };
417
+ export { type CompletionResult, DEFAULT_MAX_TOOL_ROUNDS, DEFAULT_SCRIPT_TIMEOUT_MS, DynamicScriptSkill, type DynamicScriptSkillParams, type LlmClient, type LlmClientConfig, type LlmProvider, type LoadSkillsOptions, type LoaderLogger, MAX_SCRIPT_OUTPUT, MAX_STATIC_FILE_SIZE, type ParsedSkill, type RunScriptOptions, type RunScriptResult, ScriptSkill, type ScriptSkillLogger, type ScriptSkillParams, type Skill, type SkillContext, type SkillFrontmatter, type SkillInput, type SkillLlmOverride, type SkillMode, type SkillOutput, type SkillToolDef, StaticFileSkill, type StaticFileSkillParams, StaticScriptSkill, type StaticScriptSkillParams, type ToolCall, type ToolDef, type ToolResult, createAnthropicClient, createLlmClient, createOpenAIClient, loadSkillsFromDir, parseSkillMd, resolveInsidePath, runScript, validateSkillFrontmatter };
package/dist/skills.js CHANGED
@@ -358,6 +358,7 @@ var ScriptSkill = class {
358
358
  priceSubunits;
359
359
  asset;
360
360
  mode = "llm";
361
+ llmOverride;
361
362
  image;
362
363
  imageFile;
363
364
  skillDir;
@@ -371,6 +372,7 @@ var ScriptSkill = class {
371
372
  this.capabilities = params.capabilities;
372
373
  this.priceSubunits = params.priceSubunits;
373
374
  this.asset = params.asset;
375
+ this.llmOverride = params.llmOverride;
374
376
  this.image = params.image;
375
377
  this.imageFile = params.imageFile;
376
378
  this.skillDir = params.skillDir;
@@ -380,11 +382,9 @@ var ScriptSkill = class {
380
382
  this.logger = params.logger ?? {};
381
383
  }
382
384
  async execute(input, ctx) {
383
- if (!ctx.llm) {
384
- throw new Error("LLM client not configured for skill runtime");
385
- }
385
+ const llm = this.resolveLlmClient(ctx);
386
386
  if (this.tools.length === 0) {
387
- const result = await ctx.llm.complete(this.systemPrompt, input.data, ctx.signal);
387
+ const result = await llm.complete(this.systemPrompt, input.data, ctx.signal);
388
388
  return { data: result };
389
389
  }
390
390
  const toolDefs = this.tools.map((tool) => ({
@@ -397,7 +397,6 @@ var ScriptSkill = class {
397
397
  }))
398
398
  }));
399
399
  const messages = [{ role: "user", content: input.data }];
400
- const llm = ctx.llm;
401
400
  for (let round = 0; round < this.maxToolRounds; round++) {
402
401
  if (ctx.signal?.aborted) {
403
402
  throw new Error("Job aborted");
@@ -429,6 +428,34 @@ var ScriptSkill = class {
429
428
  }
430
429
  throw new Error(`Max tool rounds (${this.maxToolRounds}) exceeded`);
431
430
  }
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
+ );
450
+ }
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
+ }
432
459
  async runTool(toolDef, call, signal) {
433
460
  const args = [...toolDef.command];
434
461
  const cmd = args.shift();
@@ -654,6 +681,8 @@ function parseAssetAmount(asset, human) {
654
681
  Decimal.clone({ toExpNeg: -100, toExpPos: 100, precision: 50 });
655
682
 
656
683
  // src/skills/loader.ts
684
+ var VALID_PROVIDERS = ["anthropic", "openai"];
685
+ var MAX_TOKENS_LIMIT = 2e5;
657
686
  var DEFAULT_MAX_TOOL_ROUNDS = 10;
658
687
  var VALID_MODES = [
659
688
  "llm",
@@ -805,6 +834,49 @@ function validateScriptArgs(skillName, raw) {
805
834
  }
806
835
  return raw;
807
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
+ }
808
880
  function validateScriptTimeoutMs(skillName, raw) {
809
881
  if (raw === void 0 || raw === null) {
810
882
  return void 0;
@@ -946,6 +1018,7 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
946
1018
  }
947
1019
  const image = typeof frontmatter.image === "string" ? frontmatter.image : void 0;
948
1020
  const imageFile = typeof frontmatter.image_file === "string" ? frontmatter.image_file : void 0;
1021
+ const llmOverride = validateLlmOverride(frontmatter.name, frontmatter, mode);
949
1022
  return {
950
1023
  name: frontmatter.name,
951
1024
  description: frontmatter.description,
@@ -956,6 +1029,7 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
956
1029
  systemPrompt,
957
1030
  tools,
958
1031
  maxToolRounds,
1032
+ llmOverride,
959
1033
  image,
960
1034
  imageFile,
961
1035
  outputFile,
@@ -977,6 +1051,7 @@ function buildSkillFromParsed(parsed, skillDir, logger) {
977
1051
  systemPrompt: parsed.systemPrompt,
978
1052
  tools: parsed.tools,
979
1053
  maxToolRounds: parsed.maxToolRounds,
1054
+ llmOverride: parsed.llmOverride,
980
1055
  image: parsed.image,
981
1056
  imageFile: parsed.imageFile,
982
1057
  logger