@elisym/sdk 0.13.0 → 0.15.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
@@ -6,303 +6,7 @@ import { readdirSync, statSync, readFileSync } from 'node:fs';
6
6
  import YAML from 'yaml';
7
7
  import Decimal from 'decimal.js-light';
8
8
 
9
- // src/skills/llmClient.ts
10
- var LLM_TIMEOUT_MS = 12e4;
11
- var MAX_RETRIES = 2;
12
- var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
13
- var DEFAULT_MAX_TOKENS = 4096;
14
- var DEFAULT_ANTHROPIC_MODEL = "claude-haiku-4-5-20251001";
15
- var DEFAULT_OPENAI_MODEL = "gpt-4o-mini";
16
- function createAbortError() {
17
- const err = new Error("The operation was aborted");
18
- err.name = "AbortError";
19
- return err;
20
- }
21
- function sleepWithSignal(ms, signal) {
22
- if (signal?.aborted) {
23
- return Promise.reject(createAbortError());
24
- }
25
- if (!signal) {
26
- return new Promise((resolve2) => setTimeout(resolve2, ms));
27
- }
28
- return new Promise((resolve2, reject) => {
29
- const cleanup = () => {
30
- clearTimeout(timer);
31
- signal.removeEventListener("abort", onAbort);
32
- };
33
- const onAbort = () => {
34
- cleanup();
35
- reject(createAbortError());
36
- };
37
- const timer = setTimeout(() => {
38
- cleanup();
39
- resolve2();
40
- }, ms);
41
- signal.addEventListener("abort", onAbort, { once: true });
42
- });
43
- }
44
- async function fetchWithTimeout(url, init, signal) {
45
- if (signal?.aborted) {
46
- throw createAbortError();
47
- }
48
- const controller = new AbortController();
49
- const timer = setTimeout(() => controller.abort(), LLM_TIMEOUT_MS);
50
- const onAbort = () => controller.abort();
51
- signal?.addEventListener("abort", onAbort, { once: true });
52
- try {
53
- return await fetch(url, { ...init, signal: controller.signal });
54
- } finally {
55
- clearTimeout(timer);
56
- signal?.removeEventListener("abort", onAbort);
57
- }
58
- }
59
- async function fetchWithRetry(url, init, signal) {
60
- for (let attempt = 0; ; attempt++) {
61
- let response;
62
- try {
63
- response = await fetchWithTimeout(url, init, signal);
64
- } catch (error) {
65
- const name = error instanceof Error ? error.name : "";
66
- if (attempt >= MAX_RETRIES || name === "AbortError") {
67
- throw error;
68
- }
69
- await sleepWithSignal(Math.min(1e3 * 2 ** attempt, 8e3), signal);
70
- continue;
71
- }
72
- if (response.ok || attempt >= MAX_RETRIES || !RETRYABLE_STATUSES.has(response.status)) {
73
- return response;
74
- }
75
- const retryAfter = response.headers.get("retry-after");
76
- const delay = retryAfter ? Math.min(parseInt(retryAfter, 10) * 1e3 || 1e3 * 2 ** attempt, 3e4) : Math.min(1e3 * 2 ** attempt, 8e3);
77
- await response.body?.cancel().catch(() => void 0);
78
- await sleepWithSignal(delay, signal);
79
- }
80
- }
81
- var AnthropicClient = class {
82
- constructor(config) {
83
- this.config = config;
84
- }
85
- async complete(systemPrompt, userInput, signal) {
86
- const response = await fetchWithRetry(
87
- "https://api.anthropic.com/v1/messages",
88
- {
89
- method: "POST",
90
- headers: {
91
- "Content-Type": "application/json",
92
- "x-api-key": this.config.apiKey,
93
- "anthropic-version": "2023-06-01"
94
- },
95
- body: JSON.stringify({
96
- model: this.config.model,
97
- max_tokens: this.config.maxTokens,
98
- system: systemPrompt,
99
- messages: [{ role: "user", content: userInput }]
100
- })
101
- },
102
- signal
103
- );
104
- if (!response.ok) {
105
- throw new Error(`Anthropic API error: ${response.status} ${await response.text()}`);
106
- }
107
- const data = await response.json();
108
- const textBlock = data.content?.find((block) => block.type === "text");
109
- return textBlock?.text ?? "";
110
- }
111
- async completeWithTools(systemPrompt, messages, tools, signal) {
112
- const anthropicTools = tools.map((tool) => ({
113
- name: tool.name,
114
- description: tool.description,
115
- input_schema: {
116
- type: "object",
117
- properties: Object.fromEntries(
118
- tool.parameters.map((param) => [
119
- param.name,
120
- { type: "string", description: param.description }
121
- ])
122
- ),
123
- required: tool.parameters.filter((param) => param.required).map((param) => param.name)
124
- }
125
- }));
126
- const response = await fetchWithRetry(
127
- "https://api.anthropic.com/v1/messages",
128
- {
129
- method: "POST",
130
- headers: {
131
- "Content-Type": "application/json",
132
- "x-api-key": this.config.apiKey,
133
- "anthropic-version": "2023-06-01"
134
- },
135
- body: JSON.stringify({
136
- model: this.config.model,
137
- max_tokens: this.config.maxTokens,
138
- system: systemPrompt,
139
- messages,
140
- tools: anthropicTools
141
- })
142
- },
143
- signal
144
- );
145
- if (!response.ok) {
146
- throw new Error(`Anthropic API error: ${response.status} ${await response.text()}`);
147
- }
148
- const data = await response.json();
149
- const content = data.content ?? [];
150
- const toolUses = content.filter((block) => block.type === "tool_use");
151
- if (toolUses.length > 0) {
152
- const calls = toolUses.map((block) => ({
153
- id: block.id ?? "",
154
- name: block.name ?? "",
155
- arguments: block.input ?? {}
156
- }));
157
- return {
158
- type: "tool_use",
159
- calls,
160
- assistantMessage: { role: "assistant", content }
161
- };
162
- }
163
- const textBlock = content.find((block) => block.type === "text");
164
- return { type: "text", text: textBlock?.text ?? "" };
165
- }
166
- formatToolResultMessages(results) {
167
- return [
168
- {
169
- role: "user",
170
- content: results.map((result) => ({
171
- type: "tool_result",
172
- tool_use_id: result.callId,
173
- content: result.content
174
- }))
175
- }
176
- ];
177
- }
178
- };
179
- var OpenAIClient = class {
180
- constructor(config) {
181
- this.config = config;
182
- }
183
- isReasoningModel() {
184
- return /^o\d/.test(this.config.model);
185
- }
186
- async complete(systemPrompt, userInput, signal) {
187
- const reasoning = this.isReasoningModel();
188
- const response = await fetchWithRetry(
189
- "https://api.openai.com/v1/chat/completions",
190
- {
191
- method: "POST",
192
- headers: {
193
- "Content-Type": "application/json",
194
- Authorization: `Bearer ${this.config.apiKey}`
195
- },
196
- body: JSON.stringify({
197
- model: this.config.model,
198
- ...reasoning ? { max_completion_tokens: this.config.maxTokens } : { max_tokens: this.config.maxTokens },
199
- messages: [
200
- { role: reasoning ? "developer" : "system", content: systemPrompt },
201
- { role: "user", content: userInput }
202
- ]
203
- })
204
- },
205
- signal
206
- );
207
- if (!response.ok) {
208
- throw new Error(`OpenAI API error: ${response.status} ${await response.text()}`);
209
- }
210
- const data = await response.json();
211
- return data.choices?.[0]?.message?.content ?? "";
212
- }
213
- async completeWithTools(systemPrompt, messages, tools, signal) {
214
- const openaiTools = tools.map((tool) => ({
215
- type: "function",
216
- function: {
217
- name: tool.name,
218
- description: tool.description,
219
- parameters: {
220
- type: "object",
221
- properties: Object.fromEntries(
222
- tool.parameters.map((param) => [
223
- param.name,
224
- { type: "string", description: param.description }
225
- ])
226
- ),
227
- required: tool.parameters.filter((param) => param.required).map((param) => param.name)
228
- }
229
- }
230
- }));
231
- const reasoning = this.isReasoningModel();
232
- const response = await fetchWithRetry(
233
- "https://api.openai.com/v1/chat/completions",
234
- {
235
- method: "POST",
236
- headers: {
237
- "Content-Type": "application/json",
238
- Authorization: `Bearer ${this.config.apiKey}`
239
- },
240
- body: JSON.stringify({
241
- model: this.config.model,
242
- ...reasoning ? { max_completion_tokens: this.config.maxTokens } : { max_tokens: this.config.maxTokens },
243
- messages: [
244
- { role: reasoning ? "developer" : "system", content: systemPrompt },
245
- ...messages
246
- ],
247
- tools: openaiTools
248
- })
249
- },
250
- signal
251
- );
252
- if (!response.ok) {
253
- throw new Error(`OpenAI API error: ${response.status} ${await response.text()}`);
254
- }
255
- const data = await response.json();
256
- const message = data.choices?.[0]?.message;
257
- const toolCalls = message?.tool_calls ?? [];
258
- if (toolCalls.length > 0) {
259
- const calls = toolCalls.map((call) => {
260
- let args;
261
- try {
262
- args = JSON.parse(call.function?.arguments ?? "{}");
263
- } catch {
264
- args = {};
265
- }
266
- return { id: call.id ?? "", name: call.function?.name ?? "", arguments: args };
267
- });
268
- return { type: "tool_use", calls, assistantMessage: message };
269
- }
270
- return { type: "text", text: message?.content ?? "" };
271
- }
272
- formatToolResultMessages(results) {
273
- return results.map((result) => ({
274
- role: "tool",
275
- tool_call_id: result.callId,
276
- content: result.content
277
- }));
278
- }
279
- };
280
- function createAnthropicClient(config) {
281
- if (!config.apiKey) {
282
- throw new Error("ANTHROPIC_API_KEY is required for skill runtime");
283
- }
284
- return new AnthropicClient({
285
- apiKey: config.apiKey,
286
- model: config.model ?? DEFAULT_ANTHROPIC_MODEL,
287
- maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS
288
- });
289
- }
290
- function createOpenAIClient(config) {
291
- if (!config.apiKey) {
292
- throw new Error("OPENAI_API_KEY is required for skill runtime");
293
- }
294
- return new OpenAIClient({
295
- apiKey: config.apiKey,
296
- model: config.model ?? DEFAULT_OPENAI_MODEL,
297
- maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS
298
- });
299
- }
300
- function createLlmClient(config) {
301
- if (config.provider === "openai") {
302
- return createOpenAIClient(config);
303
- }
304
- return createAnthropicClient(config);
305
- }
9
+ // src/skills/scriptSkill.ts
306
10
  var MAX_SCRIPT_OUTPUT = 1e6;
307
11
  var DEFAULT_SCRIPT_TIMEOUT_MS = 6e4;
308
12
  function runScript(cmd, args, opts) {
@@ -681,7 +385,6 @@ function parseAssetAmount(asset, human) {
681
385
  Decimal.clone({ toExpNeg: -100, toExpPos: 100, precision: 50 });
682
386
 
683
387
  // src/skills/loader.ts
684
- var VALID_PROVIDERS = ["anthropic", "openai"];
685
388
  var MAX_TOKENS_LIMIT = 2e5;
686
389
  var DEFAULT_MAX_TOOL_ROUNDS = 10;
687
390
  var VALID_MODES = [
@@ -853,13 +556,8 @@ function validateLlmOverride(skillName, frontmatter, mode) {
853
556
  }
854
557
  const override = {};
855
558
  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
- );
559
+ if (typeof frontmatter.provider !== "string" || frontmatter.provider.length === 0) {
560
+ throw new Error(`SKILL.md "${skillName}": "provider" must be a non-empty string`);
863
561
  }
864
562
  if (typeof frontmatter.model !== "string" || frontmatter.model.length === 0) {
865
563
  throw new Error(`SKILL.md "${skillName}": "model" must be a non-empty string`);
@@ -877,6 +575,33 @@ function validateLlmOverride(skillName, frontmatter, mode) {
877
575
  }
878
576
  return override;
879
577
  }
578
+ var MAX_RATE_LIMIT_WINDOW_SECS = 86400;
579
+ var MAX_RATE_LIMIT_PER_WINDOW = 1e4;
580
+ function validateRateLimit(skillName, raw) {
581
+ if (raw === void 0 || raw === null) {
582
+ return void 0;
583
+ }
584
+ if (typeof raw !== "object") {
585
+ throw new Error(`SKILL.md "${skillName}": "rate_limit" must be an object`);
586
+ }
587
+ const record = raw;
588
+ const perWindowSecs = record.per_window_secs;
589
+ const maxPerWindow = record.max_per_window;
590
+ if (typeof perWindowSecs !== "number" || !Number.isInteger(perWindowSecs) || perWindowSecs < 1 || perWindowSecs > MAX_RATE_LIMIT_WINDOW_SECS) {
591
+ throw new Error(
592
+ `SKILL.md "${skillName}": "rate_limit.per_window_secs" must be an integer between 1 and ${MAX_RATE_LIMIT_WINDOW_SECS}`
593
+ );
594
+ }
595
+ if (typeof maxPerWindow !== "number" || !Number.isInteger(maxPerWindow) || maxPerWindow < 1 || maxPerWindow > MAX_RATE_LIMIT_PER_WINDOW) {
596
+ throw new Error(
597
+ `SKILL.md "${skillName}": "rate_limit.max_per_window" must be an integer between 1 and ${MAX_RATE_LIMIT_PER_WINDOW}`
598
+ );
599
+ }
600
+ return {
601
+ perWindowMs: perWindowSecs * 1e3,
602
+ maxPerWindow
603
+ };
604
+ }
880
605
  function validateScriptTimeoutMs(skillName, raw) {
881
606
  if (raw === void 0 || raw === null) {
882
607
  return void 0;
@@ -1019,6 +744,7 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
1019
744
  const image = typeof frontmatter.image === "string" ? frontmatter.image : void 0;
1020
745
  const imageFile = typeof frontmatter.image_file === "string" ? frontmatter.image_file : void 0;
1021
746
  const llmOverride = validateLlmOverride(frontmatter.name, frontmatter, mode);
747
+ const rateLimit = validateRateLimit(frontmatter.name, frontmatter.rate_limit);
1022
748
  return {
1023
749
  name: frontmatter.name,
1024
750
  description: frontmatter.description,
@@ -1035,7 +761,8 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
1035
761
  outputFile,
1036
762
  script,
1037
763
  scriptArgs,
1038
- scriptTimeoutMs
764
+ scriptTimeoutMs,
765
+ rateLimit
1039
766
  };
1040
767
  }
1041
768
  function buildSkillFromParsed(parsed, skillDir, logger) {
@@ -1139,6 +866,6 @@ function loadSkillsFromDir(skillsDir, options = {}) {
1139
866
  return skills;
1140
867
  }
1141
868
 
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 };
869
+ export { DEFAULT_MAX_TOOL_ROUNDS, DEFAULT_SCRIPT_TIMEOUT_MS, DynamicScriptSkill, MAX_SCRIPT_OUTPUT, MAX_STATIC_FILE_SIZE, ScriptSkill, StaticFileSkill, StaticScriptSkill, loadSkillsFromDir, parseSkillMd, resolveInsidePath, runScript, validateSkillFrontmatter };
1143
870
  //# sourceMappingURL=skills.js.map
1144
871
  //# sourceMappingURL=skills.js.map