@elisym/sdk 0.12.5 → 0.14.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 +29 -24
- package/dist/agent-store.cjs.map +1 -1
- package/dist/agent-store.d.cts +19 -11
- package/dist/agent-store.d.ts +19 -11
- package/dist/agent-store.js +29 -24
- package/dist/agent-store.js.map +1 -1
- package/dist/index.cjs +6 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -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 +75 -305
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.d.cts +62 -13
- package/dist/skills.d.ts +62 -13
- package/dist/skills.js +76 -303
- package/dist/skills.js.map +1 -1
- package/package.json +1 -1
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?: string;
|
|
29
|
+
model?: string;
|
|
30
|
+
maxTokens?: number;
|
|
31
|
+
}
|
|
19
32
|
interface SkillContext {
|
|
20
|
-
/**
|
|
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;
|
|
@@ -64,6 +83,14 @@ interface LlmClient {
|
|
|
64
83
|
completeWithTools(systemPrompt: string, messages: unknown[], tools: ToolDef[], signal?: AbortSignal): Promise<CompletionResult>;
|
|
65
84
|
formatToolResultMessages(results: ToolResult[]): unknown[];
|
|
66
85
|
}
|
|
86
|
+
/** Provider id. Plain string so the registry stays open to additions without type-union edits. */
|
|
87
|
+
type LlmProvider = string;
|
|
88
|
+
interface LlmClientConfig {
|
|
89
|
+
provider: LlmProvider;
|
|
90
|
+
apiKey: string;
|
|
91
|
+
model?: string;
|
|
92
|
+
maxTokens?: number;
|
|
93
|
+
}
|
|
67
94
|
interface Skill {
|
|
68
95
|
name: string;
|
|
69
96
|
description: string;
|
|
@@ -74,22 +101,17 @@ interface Skill {
|
|
|
74
101
|
asset: Asset;
|
|
75
102
|
/** Execution mode. Default 'llm' for back-compat. */
|
|
76
103
|
mode: SkillMode;
|
|
104
|
+
/**
|
|
105
|
+
* Optional per-skill LLM config override (only set when mode === 'llm').
|
|
106
|
+
* Carried through from SKILL.md frontmatter so the runtime can route this
|
|
107
|
+
* skill to a non-default model/provider/max_tokens.
|
|
108
|
+
*/
|
|
109
|
+
llmOverride?: SkillLlmOverride;
|
|
77
110
|
image?: string;
|
|
78
111
|
imageFile?: string;
|
|
79
112
|
execute(input: SkillInput, ctx: SkillContext): Promise<SkillOutput>;
|
|
80
113
|
}
|
|
81
114
|
|
|
82
|
-
type LlmProvider = 'anthropic' | 'openai';
|
|
83
|
-
interface LlmClientConfig {
|
|
84
|
-
provider: LlmProvider;
|
|
85
|
-
apiKey: string;
|
|
86
|
-
model?: string;
|
|
87
|
-
maxTokens?: number;
|
|
88
|
-
}
|
|
89
|
-
declare function createAnthropicClient(config: Omit<LlmClientConfig, 'provider'>): LlmClient;
|
|
90
|
-
declare function createOpenAIClient(config: Omit<LlmClientConfig, 'provider'>): LlmClient;
|
|
91
|
-
declare function createLlmClient(config: LlmClientConfig): LlmClient;
|
|
92
|
-
|
|
93
115
|
declare const MAX_SCRIPT_OUTPUT = 1000000;
|
|
94
116
|
declare const DEFAULT_SCRIPT_TIMEOUT_MS = 60000;
|
|
95
117
|
interface SkillToolDef {
|
|
@@ -144,6 +166,8 @@ interface ScriptSkillParams {
|
|
|
144
166
|
systemPrompt: string;
|
|
145
167
|
tools: SkillToolDef[];
|
|
146
168
|
maxToolRounds: number;
|
|
169
|
+
/** Optional per-skill LLM override (provider/model pair and/or maxTokens). */
|
|
170
|
+
llmOverride?: SkillLlmOverride;
|
|
147
171
|
image?: string;
|
|
148
172
|
imageFile?: string;
|
|
149
173
|
logger?: ScriptSkillLogger;
|
|
@@ -162,6 +186,7 @@ declare class ScriptSkill implements Skill {
|
|
|
162
186
|
priceSubunits: bigint;
|
|
163
187
|
asset: Asset;
|
|
164
188
|
mode: SkillMode;
|
|
189
|
+
readonly llmOverride?: SkillLlmOverride;
|
|
165
190
|
image?: string;
|
|
166
191
|
imageFile?: string;
|
|
167
192
|
private skillDir;
|
|
@@ -171,6 +196,18 @@ declare class ScriptSkill implements Skill {
|
|
|
171
196
|
private logger;
|
|
172
197
|
constructor(params: ScriptSkillParams);
|
|
173
198
|
execute(input: SkillInput, ctx: SkillContext): Promise<SkillOutput>;
|
|
199
|
+
/**
|
|
200
|
+
* Resolve the LLM client for this skill from the runtime context.
|
|
201
|
+
*
|
|
202
|
+
* Contract:
|
|
203
|
+
* - When `llmOverride` is set, `ctx.getLlm` MUST be wired. Falling back to
|
|
204
|
+
* `ctx.llm` (the agent default) would silently use the wrong configuration
|
|
205
|
+
* for max-tokens-only overrides.
|
|
206
|
+
* - When no override is set, prefer `ctx.getLlm()` (returns the agent
|
|
207
|
+
* default), then fall back to `ctx.llm` for legacy callers that wire only
|
|
208
|
+
* a single client.
|
|
209
|
+
*/
|
|
210
|
+
private resolveLlmClient;
|
|
174
211
|
private runTool;
|
|
175
212
|
}
|
|
176
213
|
|
|
@@ -304,6 +341,12 @@ interface SkillFrontmatter {
|
|
|
304
341
|
image_file?: unknown;
|
|
305
342
|
tools?: unknown;
|
|
306
343
|
max_tool_rounds?: unknown;
|
|
344
|
+
/** Optional per-skill LLM provider override (e.g. 'anthropic', 'openai'). Pairs with `model`. */
|
|
345
|
+
provider?: unknown;
|
|
346
|
+
/** Optional per-skill LLM model override. Pairs with `provider`. */
|
|
347
|
+
model?: unknown;
|
|
348
|
+
/** Optional per-skill max_tokens override. Independent of provider/model. */
|
|
349
|
+
max_tokens?: unknown;
|
|
307
350
|
/** Execution mode. Default 'llm'. */
|
|
308
351
|
mode?: unknown;
|
|
309
352
|
/** Required when mode === 'static-file'. Path relative to skill dir. */
|
|
@@ -326,6 +369,12 @@ interface ParsedSkill {
|
|
|
326
369
|
systemPrompt: string;
|
|
327
370
|
tools: SkillToolDef[];
|
|
328
371
|
maxToolRounds: number;
|
|
372
|
+
/**
|
|
373
|
+
* Per-skill LLM override (only present when mode === 'llm' and the SKILL.md
|
|
374
|
+
* declared at least one of `provider`/`model`/`max_tokens`). Parse-time
|
|
375
|
+
* invariant: `provider` set iff `model` set.
|
|
376
|
+
*/
|
|
377
|
+
llmOverride?: SkillLlmOverride;
|
|
329
378
|
image?: string;
|
|
330
379
|
imageFile?: string;
|
|
331
380
|
/** Set when mode === 'static-file'. */
|
|
@@ -362,4 +411,4 @@ declare function validateSkillFrontmatter(frontmatter: SkillFrontmatter, systemP
|
|
|
362
411
|
*/
|
|
363
412
|
declare function loadSkillsFromDir(skillsDir: string, options?: LoadSkillsOptions): Skill[];
|
|
364
413
|
|
|
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,
|
|
414
|
+
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, loadSkillsFromDir, parseSkillMd, resolveInsidePath, runScript, validateSkillFrontmatter };
|
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/
|
|
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) {
|
|
@@ -358,6 +62,7 @@ var ScriptSkill = class {
|
|
|
358
62
|
priceSubunits;
|
|
359
63
|
asset;
|
|
360
64
|
mode = "llm";
|
|
65
|
+
llmOverride;
|
|
361
66
|
image;
|
|
362
67
|
imageFile;
|
|
363
68
|
skillDir;
|
|
@@ -371,6 +76,7 @@ var ScriptSkill = class {
|
|
|
371
76
|
this.capabilities = params.capabilities;
|
|
372
77
|
this.priceSubunits = params.priceSubunits;
|
|
373
78
|
this.asset = params.asset;
|
|
79
|
+
this.llmOverride = params.llmOverride;
|
|
374
80
|
this.image = params.image;
|
|
375
81
|
this.imageFile = params.imageFile;
|
|
376
82
|
this.skillDir = params.skillDir;
|
|
@@ -380,11 +86,9 @@ var ScriptSkill = class {
|
|
|
380
86
|
this.logger = params.logger ?? {};
|
|
381
87
|
}
|
|
382
88
|
async execute(input, ctx) {
|
|
383
|
-
|
|
384
|
-
throw new Error("LLM client not configured for skill runtime");
|
|
385
|
-
}
|
|
89
|
+
const llm = this.resolveLlmClient(ctx);
|
|
386
90
|
if (this.tools.length === 0) {
|
|
387
|
-
const result = await
|
|
91
|
+
const result = await llm.complete(this.systemPrompt, input.data, ctx.signal);
|
|
388
92
|
return { data: result };
|
|
389
93
|
}
|
|
390
94
|
const toolDefs = this.tools.map((tool) => ({
|
|
@@ -397,7 +101,6 @@ var ScriptSkill = class {
|
|
|
397
101
|
}))
|
|
398
102
|
}));
|
|
399
103
|
const messages = [{ role: "user", content: input.data }];
|
|
400
|
-
const llm = ctx.llm;
|
|
401
104
|
for (let round = 0; round < this.maxToolRounds; round++) {
|
|
402
105
|
if (ctx.signal?.aborted) {
|
|
403
106
|
throw new Error("Job aborted");
|
|
@@ -429,6 +132,34 @@ var ScriptSkill = class {
|
|
|
429
132
|
}
|
|
430
133
|
throw new Error(`Max tool rounds (${this.maxToolRounds}) exceeded`);
|
|
431
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolve the LLM client for this skill from the runtime context.
|
|
137
|
+
*
|
|
138
|
+
* Contract:
|
|
139
|
+
* - When `llmOverride` is set, `ctx.getLlm` MUST be wired. Falling back to
|
|
140
|
+
* `ctx.llm` (the agent default) would silently use the wrong configuration
|
|
141
|
+
* for max-tokens-only overrides.
|
|
142
|
+
* - When no override is set, prefer `ctx.getLlm()` (returns the agent
|
|
143
|
+
* default), then fall back to `ctx.llm` for legacy callers that wire only
|
|
144
|
+
* a single client.
|
|
145
|
+
*/
|
|
146
|
+
resolveLlmClient(ctx) {
|
|
147
|
+
let client;
|
|
148
|
+
if (this.llmOverride) {
|
|
149
|
+
client = ctx.getLlm?.(this.llmOverride);
|
|
150
|
+
if (!client) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Skill "${this.name}" requires ctx.getLlm to be configured (llmOverride is set)`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return client;
|
|
156
|
+
}
|
|
157
|
+
client = ctx.getLlm?.() ?? ctx.llm;
|
|
158
|
+
if (!client) {
|
|
159
|
+
throw new Error("LLM client not configured for skill runtime");
|
|
160
|
+
}
|
|
161
|
+
return client;
|
|
162
|
+
}
|
|
432
163
|
async runTool(toolDef, call, signal) {
|
|
433
164
|
const args = [...toolDef.command];
|
|
434
165
|
const cmd = args.shift();
|
|
@@ -654,6 +385,7 @@ function parseAssetAmount(asset, human) {
|
|
|
654
385
|
Decimal.clone({ toExpNeg: -100, toExpPos: 100, precision: 50 });
|
|
655
386
|
|
|
656
387
|
// src/skills/loader.ts
|
|
388
|
+
var MAX_TOKENS_LIMIT = 2e5;
|
|
657
389
|
var DEFAULT_MAX_TOOL_ROUNDS = 10;
|
|
658
390
|
var VALID_MODES = [
|
|
659
391
|
"llm",
|
|
@@ -805,6 +537,44 @@ function validateScriptArgs(skillName, raw) {
|
|
|
805
537
|
}
|
|
806
538
|
return raw;
|
|
807
539
|
}
|
|
540
|
+
function validateLlmOverride(skillName, frontmatter, mode) {
|
|
541
|
+
const hasProvider = frontmatter.provider !== void 0 && frontmatter.provider !== null;
|
|
542
|
+
const hasModel = frontmatter.model !== void 0 && frontmatter.model !== null;
|
|
543
|
+
const hasMaxTokens = frontmatter.max_tokens !== void 0 && frontmatter.max_tokens !== null;
|
|
544
|
+
if (!hasProvider && !hasModel && !hasMaxTokens) {
|
|
545
|
+
return void 0;
|
|
546
|
+
}
|
|
547
|
+
if (mode !== "llm") {
|
|
548
|
+
throw new Error(
|
|
549
|
+
`SKILL.md "${skillName}": "provider"/"model"/"max_tokens" are only valid in mode 'llm' (got '${mode}')`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
if (hasProvider !== hasModel) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
`SKILL.md "${skillName}": "provider" and "model" must be set together (declare both, or neither)`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
const override = {};
|
|
558
|
+
if (hasProvider && hasModel) {
|
|
559
|
+
if (typeof frontmatter.provider !== "string" || frontmatter.provider.length === 0) {
|
|
560
|
+
throw new Error(`SKILL.md "${skillName}": "provider" must be a non-empty string`);
|
|
561
|
+
}
|
|
562
|
+
if (typeof frontmatter.model !== "string" || frontmatter.model.length === 0) {
|
|
563
|
+
throw new Error(`SKILL.md "${skillName}": "model" must be a non-empty string`);
|
|
564
|
+
}
|
|
565
|
+
override.provider = frontmatter.provider;
|
|
566
|
+
override.model = frontmatter.model;
|
|
567
|
+
}
|
|
568
|
+
if (hasMaxTokens) {
|
|
569
|
+
if (typeof frontmatter.max_tokens !== "number" || !Number.isInteger(frontmatter.max_tokens) || frontmatter.max_tokens <= 0 || frontmatter.max_tokens > MAX_TOKENS_LIMIT) {
|
|
570
|
+
throw new Error(
|
|
571
|
+
`SKILL.md "${skillName}": "max_tokens" must be a positive integer <= ${MAX_TOKENS_LIMIT}`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
override.maxTokens = frontmatter.max_tokens;
|
|
575
|
+
}
|
|
576
|
+
return override;
|
|
577
|
+
}
|
|
808
578
|
function validateScriptTimeoutMs(skillName, raw) {
|
|
809
579
|
if (raw === void 0 || raw === null) {
|
|
810
580
|
return void 0;
|
|
@@ -946,6 +716,7 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
|
|
|
946
716
|
}
|
|
947
717
|
const image = typeof frontmatter.image === "string" ? frontmatter.image : void 0;
|
|
948
718
|
const imageFile = typeof frontmatter.image_file === "string" ? frontmatter.image_file : void 0;
|
|
719
|
+
const llmOverride = validateLlmOverride(frontmatter.name, frontmatter, mode);
|
|
949
720
|
return {
|
|
950
721
|
name: frontmatter.name,
|
|
951
722
|
description: frontmatter.description,
|
|
@@ -956,6 +727,7 @@ function validateSkillFrontmatter(frontmatter, systemPrompt, options = {}) {
|
|
|
956
727
|
systemPrompt,
|
|
957
728
|
tools,
|
|
958
729
|
maxToolRounds,
|
|
730
|
+
llmOverride,
|
|
959
731
|
image,
|
|
960
732
|
imageFile,
|
|
961
733
|
outputFile,
|
|
@@ -977,6 +749,7 @@ function buildSkillFromParsed(parsed, skillDir, logger) {
|
|
|
977
749
|
systemPrompt: parsed.systemPrompt,
|
|
978
750
|
tools: parsed.tools,
|
|
979
751
|
maxToolRounds: parsed.maxToolRounds,
|
|
752
|
+
llmOverride: parsed.llmOverride,
|
|
980
753
|
image: parsed.image,
|
|
981
754
|
imageFile: parsed.imageFile,
|
|
982
755
|
logger
|
|
@@ -1064,6 +837,6 @@ function loadSkillsFromDir(skillsDir, options = {}) {
|
|
|
1064
837
|
return skills;
|
|
1065
838
|
}
|
|
1066
839
|
|
|
1067
|
-
export { DEFAULT_MAX_TOOL_ROUNDS, DEFAULT_SCRIPT_TIMEOUT_MS, DynamicScriptSkill, MAX_SCRIPT_OUTPUT, MAX_STATIC_FILE_SIZE, ScriptSkill, StaticFileSkill, StaticScriptSkill,
|
|
840
|
+
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 };
|
|
1068
841
|
//# sourceMappingURL=skills.js.map
|
|
1069
842
|
//# sourceMappingURL=skills.js.map
|