@dexto/tools-builtins 1.6.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/LICENSE +44 -0
- package/dist/builtin-tools-factory.cjs +95 -0
- package/dist/builtin-tools-factory.d.cts +19 -0
- package/dist/builtin-tools-factory.d.ts +17 -0
- package/dist/builtin-tools-factory.d.ts.map +1 -0
- package/dist/builtin-tools-factory.js +69 -0
- package/dist/builtin-tools-factory.test.cjs +26 -0
- package/dist/builtin-tools-factory.test.d.cts +2 -0
- package/dist/builtin-tools-factory.test.d.ts +2 -0
- package/dist/builtin-tools-factory.test.d.ts.map +1 -0
- package/dist/builtin-tools-factory.test.js +25 -0
- package/dist/implementations/ask-user-tool.cjs +65 -0
- package/dist/implementations/ask-user-tool.d.cts +46 -0
- package/dist/implementations/ask-user-tool.d.ts +45 -0
- package/dist/implementations/ask-user-tool.d.ts.map +1 -0
- package/dist/implementations/ask-user-tool.js +41 -0
- package/dist/implementations/delegate-to-url-tool.cjs +183 -0
- package/dist/implementations/delegate-to-url-tool.d.cts +27 -0
- package/dist/implementations/delegate-to-url-tool.d.ts +26 -0
- package/dist/implementations/delegate-to-url-tool.d.ts.map +1 -0
- package/dist/implementations/delegate-to-url-tool.js +159 -0
- package/dist/implementations/delegate-to-url-tool.test.cjs +75 -0
- package/dist/implementations/delegate-to-url-tool.test.d.cts +2 -0
- package/dist/implementations/delegate-to-url-tool.test.d.ts +2 -0
- package/dist/implementations/delegate-to-url-tool.test.d.ts.map +1 -0
- package/dist/implementations/delegate-to-url-tool.test.js +74 -0
- package/dist/implementations/exa-code-search-tool.cjs +57 -0
- package/dist/implementations/exa-code-search-tool.d.cts +21 -0
- package/dist/implementations/exa-code-search-tool.d.ts +20 -0
- package/dist/implementations/exa-code-search-tool.d.ts.map +1 -0
- package/dist/implementations/exa-code-search-tool.js +33 -0
- package/dist/implementations/exa-mcp.cjs +98 -0
- package/dist/implementations/exa-mcp.d.cts +24 -0
- package/dist/implementations/exa-mcp.d.ts +23 -0
- package/dist/implementations/exa-mcp.d.ts.map +1 -0
- package/dist/implementations/exa-mcp.js +74 -0
- package/dist/implementations/exa-tools.test.cjs +91 -0
- package/dist/implementations/exa-tools.test.d.cts +2 -0
- package/dist/implementations/exa-tools.test.d.ts +2 -0
- package/dist/implementations/exa-tools.test.d.ts.map +1 -0
- package/dist/implementations/exa-tools.test.js +90 -0
- package/dist/implementations/exa-web-search-tool.cjs +63 -0
- package/dist/implementations/exa-web-search-tool.d.cts +30 -0
- package/dist/implementations/exa-web-search-tool.d.ts +29 -0
- package/dist/implementations/exa-web-search-tool.d.ts.map +1 -0
- package/dist/implementations/exa-web-search-tool.js +39 -0
- package/dist/implementations/get-resource-tool.cjs +121 -0
- package/dist/implementations/get-resource-tool.d.cts +22 -0
- package/dist/implementations/get-resource-tool.d.ts +21 -0
- package/dist/implementations/get-resource-tool.d.ts.map +1 -0
- package/dist/implementations/get-resource-tool.js +97 -0
- package/dist/implementations/http-request-tool.cjs +327 -0
- package/dist/implementations/http-request-tool.d.cts +31 -0
- package/dist/implementations/http-request-tool.d.ts +30 -0
- package/dist/implementations/http-request-tool.d.ts.map +1 -0
- package/dist/implementations/http-request-tool.js +303 -0
- package/dist/implementations/invoke-skill-tool.cjs +134 -0
- package/dist/implementations/invoke-skill-tool.d.cts +26 -0
- package/dist/implementations/invoke-skill-tool.d.ts +25 -0
- package/dist/implementations/invoke-skill-tool.d.ts.map +1 -0
- package/dist/implementations/invoke-skill-tool.js +110 -0
- package/dist/implementations/list-resources-tool.cjs +99 -0
- package/dist/implementations/list-resources-tool.d.cts +26 -0
- package/dist/implementations/list-resources-tool.d.ts +25 -0
- package/dist/implementations/list-resources-tool.d.ts.map +1 -0
- package/dist/implementations/list-resources-tool.js +75 -0
- package/dist/implementations/sleep-tool.cjs +45 -0
- package/dist/implementations/sleep-tool.d.cts +16 -0
- package/dist/implementations/sleep-tool.d.ts +15 -0
- package/dist/implementations/sleep-tool.d.ts.map +1 -0
- package/dist/implementations/sleep-tool.js +21 -0
- package/dist/index.cjs +33 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/package.json +38 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DextoRuntimeError, ErrorScope, ErrorType, defineTool } from "@dexto/core";
|
|
3
|
+
import { promises as dns } from "node:dns";
|
|
4
|
+
import { isIP } from "node:net";
|
|
5
|
+
import { TextDecoder } from "node:util";
|
|
6
|
+
import { Agent } from "undici";
|
|
7
|
+
const HttpRequestInputSchema = z.object({
|
|
8
|
+
url: z.string().url().describe("Absolute URL to request"),
|
|
9
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).default("GET").describe("HTTP method to use"),
|
|
10
|
+
headers: z.record(z.string()).optional().describe("Optional request headers (string values only)"),
|
|
11
|
+
query: z.record(z.string()).optional().describe("Optional query parameters to append to the URL"),
|
|
12
|
+
body: z.union([z.string(), z.record(z.unknown()), z.array(z.unknown())]).optional().describe("Optional request body (string or JSON-serializable value)"),
|
|
13
|
+
timeoutMs: z.number().int().positive().optional().default(3e4).describe("Request timeout in milliseconds (default: 30000)")
|
|
14
|
+
}).strict();
|
|
15
|
+
function isJsonContentType(contentType) {
|
|
16
|
+
return Boolean(contentType && contentType.toLowerCase().includes("application/json"));
|
|
17
|
+
}
|
|
18
|
+
function safeJsonParse(text) {
|
|
19
|
+
if (!text.trim()) {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(text);
|
|
24
|
+
} catch {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set(["localhost"]);
|
|
29
|
+
const MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
30
|
+
const SAFE_DISPATCHER = createSafeDispatcher();
|
|
31
|
+
function isPrivateIpv4(ip) {
|
|
32
|
+
const parts = ip.split(".").map((part) => Number(part));
|
|
33
|
+
if (parts.length !== 4 || parts.some((part) => Number.isNaN(part))) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const a = parts[0];
|
|
37
|
+
const b = parts[1];
|
|
38
|
+
const c = parts[2];
|
|
39
|
+
const d = parts[3];
|
|
40
|
+
if (a === void 0 || b === void 0 || c === void 0 || d === void 0) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (a === 0) return true;
|
|
44
|
+
if (a === 10) return true;
|
|
45
|
+
if (a === 127) return true;
|
|
46
|
+
if (a === 169 && b === 254) return true;
|
|
47
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
48
|
+
if (a === 192 && b === 168) return true;
|
|
49
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
50
|
+
if (a === 192 && b === 0 && c === 0) return true;
|
|
51
|
+
if (a === 192 && b === 0 && c === 2) return true;
|
|
52
|
+
if (a === 198 && b === 51 && c === 100) return true;
|
|
53
|
+
if (a === 203 && b === 0 && c === 113) return true;
|
|
54
|
+
if (a === 198 && (b === 18 || b === 19)) return true;
|
|
55
|
+
if (a >= 224 && a <= 239) return true;
|
|
56
|
+
if (a >= 240) return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function isPrivateIpv6(ip) {
|
|
60
|
+
const normalized = ip.toLowerCase();
|
|
61
|
+
if (normalized === "::" || normalized === "::1") return true;
|
|
62
|
+
if (normalized.startsWith("fe80:")) return true;
|
|
63
|
+
if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
|
|
64
|
+
if (normalized.startsWith("::ffff:")) {
|
|
65
|
+
const ipv4Part = normalized.slice("::ffff:".length);
|
|
66
|
+
return isPrivateIpv4(ipv4Part);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
function isPrivateAddress(ip) {
|
|
71
|
+
const version = isIP(ip);
|
|
72
|
+
if (version === 4) return isPrivateIpv4(ip);
|
|
73
|
+
if (version === 6) return isPrivateIpv6(ip);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
function createSafeDispatcher() {
|
|
77
|
+
const lookup = (hostname, _options, callback) => {
|
|
78
|
+
void (async () => {
|
|
79
|
+
try {
|
|
80
|
+
if (BLOCKED_HOSTNAMES.has(hostname) || hostname.endsWith(".localhost")) {
|
|
81
|
+
callback(
|
|
82
|
+
new DextoRuntimeError(
|
|
83
|
+
"HTTP_REQUEST_UNSAFE_TARGET",
|
|
84
|
+
ErrorScope.TOOLS,
|
|
85
|
+
ErrorType.FORBIDDEN,
|
|
86
|
+
`Blocked request to local hostname: ${hostname}`
|
|
87
|
+
),
|
|
88
|
+
"",
|
|
89
|
+
0
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const records = await dns.lookup(hostname, {
|
|
94
|
+
all: true,
|
|
95
|
+
verbatim: true
|
|
96
|
+
});
|
|
97
|
+
if (!records.length) {
|
|
98
|
+
callback(
|
|
99
|
+
new DextoRuntimeError(
|
|
100
|
+
"HTTP_REQUEST_DNS_FAILED",
|
|
101
|
+
ErrorScope.TOOLS,
|
|
102
|
+
ErrorType.THIRD_PARTY,
|
|
103
|
+
`Failed to resolve hostname: ${hostname}`
|
|
104
|
+
),
|
|
105
|
+
"",
|
|
106
|
+
0
|
|
107
|
+
);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (const record of records) {
|
|
111
|
+
if (isPrivateAddress(record.address)) {
|
|
112
|
+
callback(
|
|
113
|
+
new DextoRuntimeError(
|
|
114
|
+
"HTTP_REQUEST_UNSAFE_TARGET",
|
|
115
|
+
ErrorScope.TOOLS,
|
|
116
|
+
ErrorType.FORBIDDEN,
|
|
117
|
+
`Blocked request to private address: ${record.address}`
|
|
118
|
+
),
|
|
119
|
+
"",
|
|
120
|
+
0
|
|
121
|
+
);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const selected = records[0];
|
|
126
|
+
callback(
|
|
127
|
+
null,
|
|
128
|
+
selected.address,
|
|
129
|
+
selected.family ?? (isIP(selected.address) === 6 ? 6 : 4)
|
|
130
|
+
);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const err = error instanceof DextoRuntimeError ? error : new DextoRuntimeError(
|
|
133
|
+
"HTTP_REQUEST_DNS_FAILED",
|
|
134
|
+
ErrorScope.TOOLS,
|
|
135
|
+
ErrorType.THIRD_PARTY,
|
|
136
|
+
`Failed to resolve hostname: ${hostname}`
|
|
137
|
+
);
|
|
138
|
+
callback(err, "", 0);
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
141
|
+
};
|
|
142
|
+
return new Agent({ connect: { lookup } });
|
|
143
|
+
}
|
|
144
|
+
async function assertSafeUrl(requestUrl) {
|
|
145
|
+
if (!["http:", "https:"].includes(requestUrl.protocol)) {
|
|
146
|
+
throw new DextoRuntimeError(
|
|
147
|
+
"HTTP_REQUEST_UNSUPPORTED_PROTOCOL",
|
|
148
|
+
ErrorScope.TOOLS,
|
|
149
|
+
ErrorType.USER,
|
|
150
|
+
`Unsupported URL protocol: ${requestUrl.protocol}`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const hostname = requestUrl.hostname.trim();
|
|
154
|
+
if (!hostname) {
|
|
155
|
+
throw new DextoRuntimeError(
|
|
156
|
+
"HTTP_REQUEST_INVALID_TARGET",
|
|
157
|
+
ErrorScope.TOOLS,
|
|
158
|
+
ErrorType.USER,
|
|
159
|
+
"Request URL hostname is required"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (BLOCKED_HOSTNAMES.has(hostname) || hostname.endsWith(".localhost")) {
|
|
163
|
+
throw new DextoRuntimeError(
|
|
164
|
+
"HTTP_REQUEST_UNSAFE_TARGET",
|
|
165
|
+
ErrorScope.TOOLS,
|
|
166
|
+
ErrorType.FORBIDDEN,
|
|
167
|
+
`Blocked request to local hostname: ${hostname}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (isPrivateAddress(hostname)) {
|
|
171
|
+
throw new DextoRuntimeError(
|
|
172
|
+
"HTTP_REQUEST_UNSAFE_TARGET",
|
|
173
|
+
ErrorScope.TOOLS,
|
|
174
|
+
ErrorType.FORBIDDEN,
|
|
175
|
+
`Blocked request to private address: ${hostname}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function readResponseTextWithLimit(response) {
|
|
180
|
+
const contentLength = response.headers.get("content-length");
|
|
181
|
+
if (contentLength) {
|
|
182
|
+
const parsed = Number.parseInt(contentLength, 10);
|
|
183
|
+
if (!Number.isNaN(parsed) && parsed > MAX_RESPONSE_BYTES) {
|
|
184
|
+
throw new DextoRuntimeError(
|
|
185
|
+
"HTTP_REQUEST_RESPONSE_TOO_LARGE",
|
|
186
|
+
ErrorScope.TOOLS,
|
|
187
|
+
ErrorType.THIRD_PARTY,
|
|
188
|
+
`Response too large: ${parsed} bytes exceeds ${MAX_RESPONSE_BYTES} byte limit`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!response.body) {
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
const reader = response.body.getReader();
|
|
196
|
+
const decoder = new TextDecoder();
|
|
197
|
+
let total = 0;
|
|
198
|
+
let result = "";
|
|
199
|
+
while (true) {
|
|
200
|
+
const { done, value } = await reader.read();
|
|
201
|
+
if (done) break;
|
|
202
|
+
if (!value) continue;
|
|
203
|
+
total += value.length;
|
|
204
|
+
if (total > MAX_RESPONSE_BYTES) {
|
|
205
|
+
await reader.cancel();
|
|
206
|
+
throw new DextoRuntimeError(
|
|
207
|
+
"HTTP_REQUEST_RESPONSE_TOO_LARGE",
|
|
208
|
+
ErrorScope.TOOLS,
|
|
209
|
+
ErrorType.THIRD_PARTY,
|
|
210
|
+
`Response too large: exceeded ${MAX_RESPONSE_BYTES} byte limit`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
result += decoder.decode(value, { stream: true });
|
|
214
|
+
}
|
|
215
|
+
result += decoder.decode();
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
function createHttpRequestTool() {
|
|
219
|
+
return defineTool({
|
|
220
|
+
id: "http_request",
|
|
221
|
+
displayName: "Fetch",
|
|
222
|
+
description: "Make a direct HTTP request using fetch. Supports method, headers, query params, JSON bodies, and timeouts. Returns status, headers, raw body text, and parsed JSON when available.",
|
|
223
|
+
inputSchema: HttpRequestInputSchema,
|
|
224
|
+
async execute(input, _context) {
|
|
225
|
+
const { url, method, headers, query, body, timeoutMs } = input;
|
|
226
|
+
const requestUrl = new URL(url);
|
|
227
|
+
if (query) {
|
|
228
|
+
for (const [key, value] of Object.entries(query)) {
|
|
229
|
+
requestUrl.searchParams.set(key, value);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
await assertSafeUrl(requestUrl);
|
|
233
|
+
const requestHeaders = headers ? { ...headers } : {};
|
|
234
|
+
let requestBody;
|
|
235
|
+
if (body !== void 0) {
|
|
236
|
+
if (typeof body === "string") {
|
|
237
|
+
requestBody = body;
|
|
238
|
+
} else {
|
|
239
|
+
requestBody = JSON.stringify(body);
|
|
240
|
+
const hasContentType = Object.keys(requestHeaders).some(
|
|
241
|
+
(key) => key.toLowerCase() === "content-type"
|
|
242
|
+
);
|
|
243
|
+
if (!hasContentType) {
|
|
244
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const controller = new AbortController();
|
|
249
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
250
|
+
try {
|
|
251
|
+
const requestInit = {
|
|
252
|
+
method,
|
|
253
|
+
headers: requestHeaders,
|
|
254
|
+
signal: controller.signal
|
|
255
|
+
};
|
|
256
|
+
requestInit.dispatcher = SAFE_DISPATCHER;
|
|
257
|
+
if (requestBody !== void 0) {
|
|
258
|
+
requestInit.body = requestBody;
|
|
259
|
+
}
|
|
260
|
+
const response = await fetch(requestUrl.toString(), requestInit);
|
|
261
|
+
const responseText = await readResponseTextWithLimit(response);
|
|
262
|
+
const contentType = response.headers.get("content-type");
|
|
263
|
+
const json = isJsonContentType(contentType) ? safeJsonParse(responseText) : void 0;
|
|
264
|
+
const responseHeaders = {};
|
|
265
|
+
response.headers.forEach((value, key) => {
|
|
266
|
+
responseHeaders[key] = value;
|
|
267
|
+
});
|
|
268
|
+
const payload = {
|
|
269
|
+
ok: response.ok,
|
|
270
|
+
status: response.status,
|
|
271
|
+
statusText: response.statusText,
|
|
272
|
+
headers: responseHeaders,
|
|
273
|
+
body: responseText,
|
|
274
|
+
...json !== void 0 ? { json } : {}
|
|
275
|
+
};
|
|
276
|
+
return payload;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
if (error instanceof DextoRuntimeError) {
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
282
|
+
throw new DextoRuntimeError(
|
|
283
|
+
"HTTP_REQUEST_TIMEOUT",
|
|
284
|
+
ErrorScope.TOOLS,
|
|
285
|
+
ErrorType.TIMEOUT,
|
|
286
|
+
`HTTP request timed out after ${timeoutMs}ms`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
throw new DextoRuntimeError(
|
|
290
|
+
"HTTP_REQUEST_FAILED",
|
|
291
|
+
ErrorScope.TOOLS,
|
|
292
|
+
ErrorType.THIRD_PARTY,
|
|
293
|
+
`HTTP request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
294
|
+
);
|
|
295
|
+
} finally {
|
|
296
|
+
clearTimeout(timeoutId);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
export {
|
|
302
|
+
createHttpRequestTool
|
|
303
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var invoke_skill_tool_exports = {};
|
|
20
|
+
__export(invoke_skill_tool_exports, {
|
|
21
|
+
createInvokeSkillTool: () => createInvokeSkillTool
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(invoke_skill_tool_exports);
|
|
24
|
+
var import_zod = require("zod");
|
|
25
|
+
var import_core = require("@dexto/core");
|
|
26
|
+
const InvokeSkillInputSchema = import_zod.z.object({
|
|
27
|
+
skill: import_zod.z.string().min(1, "Skill name is required").describe(
|
|
28
|
+
'The name of the skill to invoke (e.g., "plugin-name:skill-name" or "skill-name")'
|
|
29
|
+
),
|
|
30
|
+
args: import_zod.z.record(import_zod.z.string()).optional().describe("Optional arguments to pass to the skill"),
|
|
31
|
+
taskContext: import_zod.z.string().optional().describe(
|
|
32
|
+
"Context about what task this skill should accomplish. Recommended for forked skills to provide context since they run in isolation without conversation history."
|
|
33
|
+
)
|
|
34
|
+
}).strict();
|
|
35
|
+
function createInvokeSkillTool() {
|
|
36
|
+
return (0, import_core.defineTool)({
|
|
37
|
+
id: "invoke_skill",
|
|
38
|
+
displayName: "Skill",
|
|
39
|
+
description: buildToolDescription(),
|
|
40
|
+
inputSchema: InvokeSkillInputSchema,
|
|
41
|
+
async execute(input, context) {
|
|
42
|
+
const { skill, args, taskContext } = input;
|
|
43
|
+
const promptManager = context.services?.prompts;
|
|
44
|
+
if (!promptManager) {
|
|
45
|
+
throw import_core.ToolError.configInvalid(
|
|
46
|
+
"invoke_skill requires ToolExecutionContext.services.prompts"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const autoInvocable = await promptManager.listAutoInvocablePrompts();
|
|
50
|
+
let skillKey;
|
|
51
|
+
for (const key of Object.keys(autoInvocable)) {
|
|
52
|
+
const info = autoInvocable[key];
|
|
53
|
+
if (!info) continue;
|
|
54
|
+
if (key === skill || info.displayName === skill || info.commandName === skill || info.name === skill) {
|
|
55
|
+
skillKey = key;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!skillKey) {
|
|
60
|
+
return {
|
|
61
|
+
error: `Skill '${skill}' not found or not available for model invocation. Use a skill from the available list.`,
|
|
62
|
+
availableSkills: Object.keys(autoInvocable)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const promptDef = await promptManager.getPromptDefinition(skillKey);
|
|
66
|
+
const promptResult = await promptManager.getPrompt(skillKey, args);
|
|
67
|
+
const flattened = (0, import_core.flattenPromptResult)(promptResult);
|
|
68
|
+
const content = flattened.text;
|
|
69
|
+
if (promptDef?.context === "fork") {
|
|
70
|
+
const taskForker = context.services?.taskForker;
|
|
71
|
+
if (!taskForker) {
|
|
72
|
+
return {
|
|
73
|
+
error: `Skill '${skill}' requires fork execution (context: fork), but agent spawning is not available.`,
|
|
74
|
+
skill: skillKey
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const instructions = taskContext ? `## Task Context
|
|
78
|
+
${taskContext}
|
|
79
|
+
|
|
80
|
+
## Skill Instructions
|
|
81
|
+
${content}` : content;
|
|
82
|
+
const forkOptions = {
|
|
83
|
+
task: `Skill: ${skill}`,
|
|
84
|
+
instructions,
|
|
85
|
+
autoApprove: true
|
|
86
|
+
};
|
|
87
|
+
if (promptDef.agent) {
|
|
88
|
+
forkOptions.agentId = promptDef.agent;
|
|
89
|
+
}
|
|
90
|
+
if (context.toolCallId) {
|
|
91
|
+
forkOptions.toolCallId = context.toolCallId;
|
|
92
|
+
}
|
|
93
|
+
if (context.sessionId) {
|
|
94
|
+
forkOptions.sessionId = context.sessionId;
|
|
95
|
+
}
|
|
96
|
+
const result = await taskForker.fork(forkOptions);
|
|
97
|
+
if (result.success) {
|
|
98
|
+
return result.response ?? "Task completed successfully.";
|
|
99
|
+
}
|
|
100
|
+
return `Error: ${result.error ?? "Unknown error during forked execution"}`;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
skill: skillKey,
|
|
104
|
+
content,
|
|
105
|
+
instructions: "Follow the instructions in the skill content above to complete the task."
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function buildToolDescription() {
|
|
111
|
+
return `Invoke a skill to load and execute specialized instructions for a task. Skills are predefined prompts that guide how to handle specific scenarios.
|
|
112
|
+
|
|
113
|
+
When to use:
|
|
114
|
+
- When you recognize a task that matches an available skill
|
|
115
|
+
- When you need specialized guidance for a complex operation
|
|
116
|
+
- When the user references a skill by name
|
|
117
|
+
|
|
118
|
+
Parameters:
|
|
119
|
+
- skill: The name of the skill to invoke
|
|
120
|
+
- args: Optional arguments to pass to the skill (e.g., for $ARGUMENTS substitution)
|
|
121
|
+
- taskContext: Context about what you're trying to accomplish (important for forked skills that run in isolation)
|
|
122
|
+
|
|
123
|
+
Execution modes:
|
|
124
|
+
- **Inline skills**: Return instructions for you to follow in the current conversation
|
|
125
|
+
- **Fork skills**: Automatically execute in an isolated subagent and return the result (no additional tool calls needed)
|
|
126
|
+
|
|
127
|
+
Fork skills run in complete isolation without access to conversation history. They're useful for tasks that should run independently.
|
|
128
|
+
|
|
129
|
+
Available skills are listed in your system prompt. Use the skill name exactly as shown.`;
|
|
130
|
+
}
|
|
131
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
132
|
+
0 && (module.exports = {
|
|
133
|
+
createInvokeSkillTool
|
|
134
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Tool } from '@dexto/core';
|
|
3
|
+
|
|
4
|
+
declare const InvokeSkillInputSchema: z.ZodObject<{
|
|
5
|
+
skill: z.ZodString;
|
|
6
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
7
|
+
taskContext: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, "strict", z.ZodTypeAny, {
|
|
9
|
+
skill: string;
|
|
10
|
+
args?: Record<string, string> | undefined;
|
|
11
|
+
taskContext?: string | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
skill: string;
|
|
14
|
+
args?: Record<string, string> | undefined;
|
|
15
|
+
taskContext?: string | undefined;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Create the `invoke_skill` tool.
|
|
19
|
+
*
|
|
20
|
+
* Loads an auto-invocable prompt (“skill”) via the PromptManager and returns its content, or
|
|
21
|
+
* forks the skill into a sub-agent when the skill is marked as `context: fork`.
|
|
22
|
+
* Requires `ToolExecutionContext.services.prompts` and, for forked skills, `services.taskForker`.
|
|
23
|
+
*/
|
|
24
|
+
declare function createInvokeSkillTool(): Tool<typeof InvokeSkillInputSchema>;
|
|
25
|
+
|
|
26
|
+
export { createInvokeSkillTool };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Tool } from '@dexto/core';
|
|
3
|
+
declare const InvokeSkillInputSchema: z.ZodObject<{
|
|
4
|
+
skill: z.ZodString;
|
|
5
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
6
|
+
taskContext: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, "strict", z.ZodTypeAny, {
|
|
8
|
+
skill: string;
|
|
9
|
+
args?: Record<string, string> | undefined;
|
|
10
|
+
taskContext?: string | undefined;
|
|
11
|
+
}, {
|
|
12
|
+
skill: string;
|
|
13
|
+
args?: Record<string, string> | undefined;
|
|
14
|
+
taskContext?: string | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Create the `invoke_skill` tool.
|
|
18
|
+
*
|
|
19
|
+
* Loads an auto-invocable prompt (“skill”) via the PromptManager and returns its content, or
|
|
20
|
+
* forks the skill into a sub-agent when the skill is marked as `context: fork`.
|
|
21
|
+
* Requires `ToolExecutionContext.services.prompts` and, for forked skills, `services.taskForker`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createInvokeSkillTool(): Tool<typeof InvokeSkillInputSchema>;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=invoke-skill-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoke-skill-tool.d.ts","sourceRoot":"","sources":["../../src/implementations/invoke-skill-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAmB,IAAI,EAAwB,MAAM,aAAa,CAAC;AAE/E,QAAA,MAAM,sBAAsB;;;;;;;;;;;;EAgBf,CAAC;AAEd;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAAC,OAAO,sBAAsB,CAAC,CA0F3E"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ToolError, defineTool, flattenPromptResult } from "@dexto/core";
|
|
3
|
+
const InvokeSkillInputSchema = z.object({
|
|
4
|
+
skill: z.string().min(1, "Skill name is required").describe(
|
|
5
|
+
'The name of the skill to invoke (e.g., "plugin-name:skill-name" or "skill-name")'
|
|
6
|
+
),
|
|
7
|
+
args: z.record(z.string()).optional().describe("Optional arguments to pass to the skill"),
|
|
8
|
+
taskContext: z.string().optional().describe(
|
|
9
|
+
"Context about what task this skill should accomplish. Recommended for forked skills to provide context since they run in isolation without conversation history."
|
|
10
|
+
)
|
|
11
|
+
}).strict();
|
|
12
|
+
function createInvokeSkillTool() {
|
|
13
|
+
return defineTool({
|
|
14
|
+
id: "invoke_skill",
|
|
15
|
+
displayName: "Skill",
|
|
16
|
+
description: buildToolDescription(),
|
|
17
|
+
inputSchema: InvokeSkillInputSchema,
|
|
18
|
+
async execute(input, context) {
|
|
19
|
+
const { skill, args, taskContext } = input;
|
|
20
|
+
const promptManager = context.services?.prompts;
|
|
21
|
+
if (!promptManager) {
|
|
22
|
+
throw ToolError.configInvalid(
|
|
23
|
+
"invoke_skill requires ToolExecutionContext.services.prompts"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const autoInvocable = await promptManager.listAutoInvocablePrompts();
|
|
27
|
+
let skillKey;
|
|
28
|
+
for (const key of Object.keys(autoInvocable)) {
|
|
29
|
+
const info = autoInvocable[key];
|
|
30
|
+
if (!info) continue;
|
|
31
|
+
if (key === skill || info.displayName === skill || info.commandName === skill || info.name === skill) {
|
|
32
|
+
skillKey = key;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!skillKey) {
|
|
37
|
+
return {
|
|
38
|
+
error: `Skill '${skill}' not found or not available for model invocation. Use a skill from the available list.`,
|
|
39
|
+
availableSkills: Object.keys(autoInvocable)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const promptDef = await promptManager.getPromptDefinition(skillKey);
|
|
43
|
+
const promptResult = await promptManager.getPrompt(skillKey, args);
|
|
44
|
+
const flattened = flattenPromptResult(promptResult);
|
|
45
|
+
const content = flattened.text;
|
|
46
|
+
if (promptDef?.context === "fork") {
|
|
47
|
+
const taskForker = context.services?.taskForker;
|
|
48
|
+
if (!taskForker) {
|
|
49
|
+
return {
|
|
50
|
+
error: `Skill '${skill}' requires fork execution (context: fork), but agent spawning is not available.`,
|
|
51
|
+
skill: skillKey
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const instructions = taskContext ? `## Task Context
|
|
55
|
+
${taskContext}
|
|
56
|
+
|
|
57
|
+
## Skill Instructions
|
|
58
|
+
${content}` : content;
|
|
59
|
+
const forkOptions = {
|
|
60
|
+
task: `Skill: ${skill}`,
|
|
61
|
+
instructions,
|
|
62
|
+
autoApprove: true
|
|
63
|
+
};
|
|
64
|
+
if (promptDef.agent) {
|
|
65
|
+
forkOptions.agentId = promptDef.agent;
|
|
66
|
+
}
|
|
67
|
+
if (context.toolCallId) {
|
|
68
|
+
forkOptions.toolCallId = context.toolCallId;
|
|
69
|
+
}
|
|
70
|
+
if (context.sessionId) {
|
|
71
|
+
forkOptions.sessionId = context.sessionId;
|
|
72
|
+
}
|
|
73
|
+
const result = await taskForker.fork(forkOptions);
|
|
74
|
+
if (result.success) {
|
|
75
|
+
return result.response ?? "Task completed successfully.";
|
|
76
|
+
}
|
|
77
|
+
return `Error: ${result.error ?? "Unknown error during forked execution"}`;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
skill: skillKey,
|
|
81
|
+
content,
|
|
82
|
+
instructions: "Follow the instructions in the skill content above to complete the task."
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function buildToolDescription() {
|
|
88
|
+
return `Invoke a skill to load and execute specialized instructions for a task. Skills are predefined prompts that guide how to handle specific scenarios.
|
|
89
|
+
|
|
90
|
+
When to use:
|
|
91
|
+
- When you recognize a task that matches an available skill
|
|
92
|
+
- When you need specialized guidance for a complex operation
|
|
93
|
+
- When the user references a skill by name
|
|
94
|
+
|
|
95
|
+
Parameters:
|
|
96
|
+
- skill: The name of the skill to invoke
|
|
97
|
+
- args: Optional arguments to pass to the skill (e.g., for $ARGUMENTS substitution)
|
|
98
|
+
- taskContext: Context about what you're trying to accomplish (important for forked skills that run in isolation)
|
|
99
|
+
|
|
100
|
+
Execution modes:
|
|
101
|
+
- **Inline skills**: Return instructions for you to follow in the current conversation
|
|
102
|
+
- **Fork skills**: Automatically execute in an isolated subagent and return the result (no additional tool calls needed)
|
|
103
|
+
|
|
104
|
+
Fork skills run in complete isolation without access to conversation history. They're useful for tasks that should run independently.
|
|
105
|
+
|
|
106
|
+
Available skills are listed in your system prompt. Use the skill name exactly as shown.`;
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
createInvokeSkillTool
|
|
110
|
+
};
|