llmist 17.3.0 → 17.5.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/README.md +21 -0
- package/dist/chunk-HM7PUGPA.js +2252 -0
- package/dist/chunk-HM7PUGPA.js.map +1 -0
- package/dist/index.cjs +1416 -331
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +448 -13
- package/dist/index.d.ts +448 -13
- package/dist/index.js +486 -1678
- package/dist/index.js.map +1 -1
- package/dist/runtime-GKQ6QIQP.js +187 -0
- package/dist/runtime-GKQ6QIQP.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,2252 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
|
|
28
|
+
// src/core/constants.ts
|
|
29
|
+
var GADGET_START_PREFIX, GADGET_END_PREFIX, GADGET_ARG_PREFIX, DEFAULT_GADGET_OUTPUT_LIMIT, DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT, CHARS_PER_TOKEN, FALLBACK_CONTEXT_WINDOW;
|
|
30
|
+
var init_constants = __esm({
|
|
31
|
+
"src/core/constants.ts"() {
|
|
32
|
+
"use strict";
|
|
33
|
+
GADGET_START_PREFIX = "!!!GADGET_START:";
|
|
34
|
+
GADGET_END_PREFIX = "!!!GADGET_END";
|
|
35
|
+
GADGET_ARG_PREFIX = "!!!ARG:";
|
|
36
|
+
DEFAULT_GADGET_OUTPUT_LIMIT = true;
|
|
37
|
+
DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT = 15;
|
|
38
|
+
CHARS_PER_TOKEN = 2;
|
|
39
|
+
FALLBACK_CONTEXT_WINDOW = 128e3;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// src/core/input-content.ts
|
|
44
|
+
function isTextPart(part) {
|
|
45
|
+
return part.type === "text";
|
|
46
|
+
}
|
|
47
|
+
function isImagePart(part) {
|
|
48
|
+
return part.type === "image";
|
|
49
|
+
}
|
|
50
|
+
function isAudioPart(part) {
|
|
51
|
+
return part.type === "audio";
|
|
52
|
+
}
|
|
53
|
+
function text(content) {
|
|
54
|
+
return { type: "text", text: content };
|
|
55
|
+
}
|
|
56
|
+
function imageFromBase64(data, mediaType) {
|
|
57
|
+
return {
|
|
58
|
+
type: "image",
|
|
59
|
+
source: { type: "base64", mediaType, data }
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function imageFromUrl(url) {
|
|
63
|
+
return {
|
|
64
|
+
type: "image",
|
|
65
|
+
source: { type: "url", url }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function detectImageMimeType(data) {
|
|
69
|
+
const bytes = data instanceof Buffer ? data : Buffer.from(data);
|
|
70
|
+
for (const { bytes: magic, mimeType } of IMAGE_MAGIC_BYTES) {
|
|
71
|
+
if (bytes.length >= magic.length) {
|
|
72
|
+
let matches = true;
|
|
73
|
+
for (let i = 0; i < magic.length; i++) {
|
|
74
|
+
if (bytes[i] !== magic[i]) {
|
|
75
|
+
matches = false;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (matches) {
|
|
80
|
+
if (mimeType === "image/webp") {
|
|
81
|
+
if (bytes.length >= 12) {
|
|
82
|
+
const webpMarker = bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80;
|
|
83
|
+
if (!webpMarker) continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return mimeType;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function detectAudioMimeType(data) {
|
|
93
|
+
const bytes = data instanceof Buffer ? data : Buffer.from(data);
|
|
94
|
+
for (const { bytes: magic, mimeType } of AUDIO_MAGIC_BYTES) {
|
|
95
|
+
if (bytes.length >= magic.length) {
|
|
96
|
+
let matches = true;
|
|
97
|
+
for (let i = 0; i < magic.length; i++) {
|
|
98
|
+
if (bytes[i] !== magic[i]) {
|
|
99
|
+
matches = false;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (matches) {
|
|
104
|
+
if (mimeType === "audio/wav") {
|
|
105
|
+
if (bytes.length >= 12) {
|
|
106
|
+
const waveMarker = bytes[8] === 87 && bytes[9] === 65 && bytes[10] === 86 && bytes[11] === 69;
|
|
107
|
+
if (!waveMarker) continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return mimeType;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
function toBase64(data) {
|
|
117
|
+
if (typeof data === "string") {
|
|
118
|
+
return data;
|
|
119
|
+
}
|
|
120
|
+
return Buffer.from(data).toString("base64");
|
|
121
|
+
}
|
|
122
|
+
function imageFromBuffer(buffer, mediaType) {
|
|
123
|
+
const detectedType = mediaType ?? detectImageMimeType(buffer);
|
|
124
|
+
if (!detectedType) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
"Could not detect image MIME type. Please provide the mediaType parameter explicitly."
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
type: "image",
|
|
131
|
+
source: {
|
|
132
|
+
type: "base64",
|
|
133
|
+
mediaType: detectedType,
|
|
134
|
+
data: toBase64(buffer)
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function audioFromBase64(data, mediaType) {
|
|
139
|
+
return {
|
|
140
|
+
type: "audio",
|
|
141
|
+
source: { type: "base64", mediaType, data }
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function audioFromBuffer(buffer, mediaType) {
|
|
145
|
+
const detectedType = mediaType ?? detectAudioMimeType(buffer);
|
|
146
|
+
if (!detectedType) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"Could not detect audio MIME type. Please provide the mediaType parameter explicitly."
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
type: "audio",
|
|
153
|
+
source: {
|
|
154
|
+
type: "base64",
|
|
155
|
+
mediaType: detectedType,
|
|
156
|
+
data: toBase64(buffer)
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function isDataUrl(input) {
|
|
161
|
+
return input.startsWith("data:");
|
|
162
|
+
}
|
|
163
|
+
function parseDataUrl(url) {
|
|
164
|
+
const match = url.match(/^data:([^;]+);base64,(.+)$/);
|
|
165
|
+
if (!match) return null;
|
|
166
|
+
return { mimeType: match[1], data: match[2] };
|
|
167
|
+
}
|
|
168
|
+
var IMAGE_MAGIC_BYTES, AUDIO_MAGIC_BYTES;
|
|
169
|
+
var init_input_content = __esm({
|
|
170
|
+
"src/core/input-content.ts"() {
|
|
171
|
+
"use strict";
|
|
172
|
+
IMAGE_MAGIC_BYTES = [
|
|
173
|
+
{ bytes: [255, 216, 255], mimeType: "image/jpeg" },
|
|
174
|
+
{ bytes: [137, 80, 78, 71], mimeType: "image/png" },
|
|
175
|
+
{ bytes: [71, 73, 70, 56], mimeType: "image/gif" },
|
|
176
|
+
// WebP starts with RIFF....WEBP
|
|
177
|
+
{ bytes: [82, 73, 70, 70], mimeType: "image/webp" }
|
|
178
|
+
];
|
|
179
|
+
AUDIO_MAGIC_BYTES = [
|
|
180
|
+
// MP3 frame sync
|
|
181
|
+
{ bytes: [255, 251], mimeType: "audio/mp3" },
|
|
182
|
+
{ bytes: [255, 250], mimeType: "audio/mp3" },
|
|
183
|
+
// ID3 tag (MP3)
|
|
184
|
+
{ bytes: [73, 68, 51], mimeType: "audio/mp3" },
|
|
185
|
+
// OGG
|
|
186
|
+
{ bytes: [79, 103, 103, 83], mimeType: "audio/ogg" },
|
|
187
|
+
// WAV (RIFF)
|
|
188
|
+
{ bytes: [82, 73, 70, 70], mimeType: "audio/wav" },
|
|
189
|
+
// WebM
|
|
190
|
+
{ bytes: [26, 69, 223, 163], mimeType: "audio/webm" },
|
|
191
|
+
// FLAC (fLaC)
|
|
192
|
+
{ bytes: [102, 76, 97, 67], mimeType: "audio/flac" }
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// src/core/prompt-config.ts
|
|
198
|
+
function resolvePromptTemplate(template, defaultValue, context) {
|
|
199
|
+
const resolved = template ?? defaultValue;
|
|
200
|
+
return typeof resolved === "function" ? resolved(context) : resolved;
|
|
201
|
+
}
|
|
202
|
+
function resolveRulesTemplate(rules, context) {
|
|
203
|
+
const resolved = rules ?? DEFAULT_PROMPTS.rules;
|
|
204
|
+
if (Array.isArray(resolved)) {
|
|
205
|
+
return resolved;
|
|
206
|
+
}
|
|
207
|
+
if (typeof resolved === "function") {
|
|
208
|
+
const result = resolved(context);
|
|
209
|
+
return Array.isArray(result) ? result : [result];
|
|
210
|
+
}
|
|
211
|
+
return [resolved];
|
|
212
|
+
}
|
|
213
|
+
function resolveHintTemplate(template, defaultValue, context) {
|
|
214
|
+
const resolved = template ?? defaultValue;
|
|
215
|
+
if (typeof resolved === "function") {
|
|
216
|
+
return resolved(context);
|
|
217
|
+
}
|
|
218
|
+
return resolved.replace(/\{iteration\}/g, String(context.iteration)).replace(/\{maxIterations\}/g, String(context.maxIterations)).replace(/\{remaining\}/g, String(context.remaining));
|
|
219
|
+
}
|
|
220
|
+
var DEFAULT_HINTS, DEFAULT_PROMPTS;
|
|
221
|
+
var init_prompt_config = __esm({
|
|
222
|
+
"src/core/prompt-config.ts"() {
|
|
223
|
+
"use strict";
|
|
224
|
+
DEFAULT_HINTS = {
|
|
225
|
+
parallelGadgetsHint: "Tip: You can call multiple gadgets in a single response for efficiency.",
|
|
226
|
+
iterationProgressHint: "[Iteration {iteration}/{maxIterations}] Plan your actions accordingly."
|
|
227
|
+
};
|
|
228
|
+
DEFAULT_PROMPTS = {
|
|
229
|
+
mainInstruction: [
|
|
230
|
+
"\u26A0\uFE0F CRITICAL: RESPOND ONLY WITH GADGET INVOCATIONS",
|
|
231
|
+
"DO NOT use function calling or tool calling",
|
|
232
|
+
"You must output the exact text markers shown below in plain text.",
|
|
233
|
+
"EACH MARKER MUST START WITH A NEWLINE."
|
|
234
|
+
].join("\n"),
|
|
235
|
+
criticalUsage: "INVOKE gadgets using the markers - do not describe what you want to do.",
|
|
236
|
+
formatDescription: (ctx) => `Parameters using ${ctx.argPrefix}name markers (value on next line(s), no escaping needed)`,
|
|
237
|
+
rules: () => [
|
|
238
|
+
"Output ONLY plain text with the exact markers - never use function/tool calling",
|
|
239
|
+
"You can invoke multiple gadgets in a single response",
|
|
240
|
+
"Gadgets without dependencies execute immediately (in parallel if multiple)",
|
|
241
|
+
"Use :invocation_id:dep1,dep2 syntax when a gadget needs results from prior gadgets",
|
|
242
|
+
"If any dependency fails, dependent gadgets are automatically skipped"
|
|
243
|
+
],
|
|
244
|
+
customExamples: null
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// src/core/messages.ts
|
|
250
|
+
function normalizeMessageContent(content) {
|
|
251
|
+
if (typeof content === "string") {
|
|
252
|
+
return [{ type: "text", text: content }];
|
|
253
|
+
}
|
|
254
|
+
return content;
|
|
255
|
+
}
|
|
256
|
+
function extractMessageText(content) {
|
|
257
|
+
if (typeof content === "string") {
|
|
258
|
+
return content;
|
|
259
|
+
}
|
|
260
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
261
|
+
}
|
|
262
|
+
var LLMMessageBuilder;
|
|
263
|
+
var init_messages = __esm({
|
|
264
|
+
"src/core/messages.ts"() {
|
|
265
|
+
"use strict";
|
|
266
|
+
init_constants();
|
|
267
|
+
init_input_content();
|
|
268
|
+
init_prompt_config();
|
|
269
|
+
LLMMessageBuilder = class {
|
|
270
|
+
messages = [];
|
|
271
|
+
startPrefix = GADGET_START_PREFIX;
|
|
272
|
+
endPrefix = GADGET_END_PREFIX;
|
|
273
|
+
argPrefix = GADGET_ARG_PREFIX;
|
|
274
|
+
promptConfig;
|
|
275
|
+
constructor(promptConfig) {
|
|
276
|
+
this.promptConfig = promptConfig ?? {};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Set custom prefixes for gadget markers.
|
|
280
|
+
* Used to configure history builder to match system prompt markers.
|
|
281
|
+
*/
|
|
282
|
+
withPrefixes(startPrefix, endPrefix, argPrefix) {
|
|
283
|
+
this.startPrefix = startPrefix;
|
|
284
|
+
this.endPrefix = endPrefix;
|
|
285
|
+
if (argPrefix) {
|
|
286
|
+
this.argPrefix = argPrefix;
|
|
287
|
+
}
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
addSystem(content, metadata) {
|
|
291
|
+
this.messages.push({ role: "system", content, metadata });
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
294
|
+
addGadgets(gadgets, options) {
|
|
295
|
+
if (options?.startPrefix) {
|
|
296
|
+
this.startPrefix = options.startPrefix;
|
|
297
|
+
}
|
|
298
|
+
if (options?.endPrefix) {
|
|
299
|
+
this.endPrefix = options.endPrefix;
|
|
300
|
+
}
|
|
301
|
+
if (options?.argPrefix) {
|
|
302
|
+
this.argPrefix = options.argPrefix;
|
|
303
|
+
}
|
|
304
|
+
const context = {
|
|
305
|
+
startPrefix: this.startPrefix,
|
|
306
|
+
endPrefix: this.endPrefix,
|
|
307
|
+
argPrefix: this.argPrefix,
|
|
308
|
+
gadgetCount: gadgets.length,
|
|
309
|
+
gadgetNames: gadgets.map((g) => g.name ?? g.constructor.name)
|
|
310
|
+
};
|
|
311
|
+
const parts = [];
|
|
312
|
+
const mainInstruction = resolvePromptTemplate(
|
|
313
|
+
this.promptConfig.mainInstruction,
|
|
314
|
+
DEFAULT_PROMPTS.mainInstruction,
|
|
315
|
+
context
|
|
316
|
+
);
|
|
317
|
+
parts.push(mainInstruction);
|
|
318
|
+
parts.push(this.buildGadgetsSection(gadgets));
|
|
319
|
+
parts.push(this.buildUsageSection(context));
|
|
320
|
+
this.messages.push({ role: "system", content: parts.join("") });
|
|
321
|
+
return this;
|
|
322
|
+
}
|
|
323
|
+
buildGadgetsSection(gadgets) {
|
|
324
|
+
const parts = [];
|
|
325
|
+
parts.push("\n\nAVAILABLE GADGETS");
|
|
326
|
+
parts.push("\n=================\n");
|
|
327
|
+
for (const gadget of gadgets) {
|
|
328
|
+
const gadgetName = gadget.name ?? gadget.constructor.name;
|
|
329
|
+
const instruction = gadget.getInstruction({
|
|
330
|
+
argPrefix: this.argPrefix,
|
|
331
|
+
startPrefix: this.startPrefix,
|
|
332
|
+
endPrefix: this.endPrefix
|
|
333
|
+
});
|
|
334
|
+
const schemaMarker = "\n\nInput Schema (BLOCK):";
|
|
335
|
+
const schemaIndex = instruction.indexOf(schemaMarker);
|
|
336
|
+
const description = (schemaIndex !== -1 ? instruction.substring(0, schemaIndex) : instruction).trim();
|
|
337
|
+
const schema = schemaIndex !== -1 ? instruction.substring(schemaIndex + schemaMarker.length).trim() : "";
|
|
338
|
+
parts.push(`
|
|
339
|
+
GADGET: ${gadgetName}`);
|
|
340
|
+
parts.push(`
|
|
341
|
+
${description}`);
|
|
342
|
+
if (schema) {
|
|
343
|
+
parts.push(`
|
|
344
|
+
|
|
345
|
+
PARAMETERS (BLOCK):
|
|
346
|
+
${schema}`);
|
|
347
|
+
}
|
|
348
|
+
parts.push("\n\n---");
|
|
349
|
+
}
|
|
350
|
+
return parts.join("");
|
|
351
|
+
}
|
|
352
|
+
buildUsageSection(context) {
|
|
353
|
+
const parts = [];
|
|
354
|
+
const formatDescription = resolvePromptTemplate(
|
|
355
|
+
this.promptConfig.formatDescription,
|
|
356
|
+
DEFAULT_PROMPTS.formatDescription,
|
|
357
|
+
context
|
|
358
|
+
);
|
|
359
|
+
parts.push("\n\nHOW TO INVOKE GADGETS");
|
|
360
|
+
parts.push("\n=====================\n");
|
|
361
|
+
const criticalUsage = resolvePromptTemplate(
|
|
362
|
+
this.promptConfig.criticalUsage,
|
|
363
|
+
DEFAULT_PROMPTS.criticalUsage,
|
|
364
|
+
context
|
|
365
|
+
);
|
|
366
|
+
parts.push(`
|
|
367
|
+
CRITICAL: ${criticalUsage}
|
|
368
|
+
`);
|
|
369
|
+
parts.push("\nFORMAT:");
|
|
370
|
+
parts.push(`
|
|
371
|
+
1. Start marker: ${this.startPrefix}gadget_name`);
|
|
372
|
+
parts.push(`
|
|
373
|
+
With ID: ${this.startPrefix}gadget_name:my_id`);
|
|
374
|
+
parts.push(`
|
|
375
|
+
With dependencies: ${this.startPrefix}gadget_name:my_id:dep1,dep2`);
|
|
376
|
+
parts.push(`
|
|
377
|
+
2. ${formatDescription}`);
|
|
378
|
+
parts.push(`
|
|
379
|
+
3. End marker: ${this.endPrefix}`);
|
|
380
|
+
parts.push(this.buildExamplesSection(context));
|
|
381
|
+
parts.push(this.buildRulesSection(context));
|
|
382
|
+
parts.push("\n");
|
|
383
|
+
return parts.join("");
|
|
384
|
+
}
|
|
385
|
+
buildExamplesSection(context) {
|
|
386
|
+
if (this.promptConfig.customExamples) {
|
|
387
|
+
return this.promptConfig.customExamples(context);
|
|
388
|
+
}
|
|
389
|
+
const parts = [];
|
|
390
|
+
const singleExample = `${this.startPrefix}translate
|
|
391
|
+
${this.argPrefix}from
|
|
392
|
+
English
|
|
393
|
+
${this.argPrefix}to
|
|
394
|
+
Polish
|
|
395
|
+
${this.argPrefix}content
|
|
396
|
+
Paris is the capital of France: a beautiful city.
|
|
397
|
+
${this.endPrefix}`;
|
|
398
|
+
parts.push(`
|
|
399
|
+
|
|
400
|
+
EXAMPLE (Single Gadget):
|
|
401
|
+
|
|
402
|
+
${singleExample}`);
|
|
403
|
+
const multipleExample = `${this.startPrefix}translate
|
|
404
|
+
${this.argPrefix}from
|
|
405
|
+
English
|
|
406
|
+
${this.argPrefix}to
|
|
407
|
+
Polish
|
|
408
|
+
${this.argPrefix}content
|
|
409
|
+
Paris is the capital of France: a beautiful city.
|
|
410
|
+
${this.endPrefix}
|
|
411
|
+
${this.startPrefix}analyze
|
|
412
|
+
${this.argPrefix}type
|
|
413
|
+
economic_analysis
|
|
414
|
+
${this.argPrefix}matter
|
|
415
|
+
Polish Economy
|
|
416
|
+
${this.argPrefix}question
|
|
417
|
+
Analyze the following:
|
|
418
|
+
- Polish arms exports 2025
|
|
419
|
+
- Economic implications
|
|
420
|
+
${this.endPrefix}`;
|
|
421
|
+
parts.push(`
|
|
422
|
+
|
|
423
|
+
EXAMPLE (Multiple Gadgets):
|
|
424
|
+
|
|
425
|
+
${multipleExample}`);
|
|
426
|
+
const dependencyExample = `${this.startPrefix}fetch_data:fetch_1
|
|
427
|
+
${this.argPrefix}url
|
|
428
|
+
https://api.example.com/users
|
|
429
|
+
${this.endPrefix}
|
|
430
|
+
${this.startPrefix}fetch_data:fetch_2
|
|
431
|
+
${this.argPrefix}url
|
|
432
|
+
https://api.example.com/orders
|
|
433
|
+
${this.endPrefix}
|
|
434
|
+
${this.startPrefix}merge_data:merge_1:fetch_1,fetch_2
|
|
435
|
+
${this.argPrefix}format
|
|
436
|
+
json
|
|
437
|
+
${this.endPrefix}`;
|
|
438
|
+
parts.push(`
|
|
439
|
+
|
|
440
|
+
EXAMPLE (With Dependencies):
|
|
441
|
+
merge_1 waits for fetch_1 AND fetch_2 to complete.
|
|
442
|
+
If either fails, merge_1 is automatically skipped.
|
|
443
|
+
|
|
444
|
+
${dependencyExample}`);
|
|
445
|
+
parts.push(`
|
|
446
|
+
|
|
447
|
+
BLOCK FORMAT SYNTAX:
|
|
448
|
+
Block format uses ${this.argPrefix}name markers. Values are captured verbatim until the next marker.
|
|
449
|
+
|
|
450
|
+
${this.argPrefix}filename
|
|
451
|
+
calculator.ts
|
|
452
|
+
${this.argPrefix}code
|
|
453
|
+
class Calculator {
|
|
454
|
+
private history: string[] = [];
|
|
455
|
+
|
|
456
|
+
add(a: number, b: number): number {
|
|
457
|
+
const result = a + b;
|
|
458
|
+
this.history.push(\`\${a} + \${b} = \${result}\`);
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
BLOCK FORMAT RULES:
|
|
464
|
+
- Each parameter starts with ${this.argPrefix}parameterName on its own line
|
|
465
|
+
- The value starts on the NEXT line after the marker
|
|
466
|
+
- Value ends when the next ${this.argPrefix} or ${this.endPrefix} appears
|
|
467
|
+
- NO escaping needed - write values exactly as they should appear
|
|
468
|
+
- Perfect for code, JSON, markdown, or any content with special characters
|
|
469
|
+
|
|
470
|
+
NESTED OBJECTS (use / separator):
|
|
471
|
+
${this.argPrefix}config/timeout
|
|
472
|
+
30
|
|
473
|
+
${this.argPrefix}config/retries
|
|
474
|
+
3
|
|
475
|
+
Produces: { "config": { "timeout": "30", "retries": "3" } }
|
|
476
|
+
|
|
477
|
+
ARRAYS (use numeric indices):
|
|
478
|
+
${this.argPrefix}items/0
|
|
479
|
+
first
|
|
480
|
+
${this.argPrefix}items/1
|
|
481
|
+
second
|
|
482
|
+
Produces: { "items": ["first", "second"] }`);
|
|
483
|
+
return parts.join("");
|
|
484
|
+
}
|
|
485
|
+
buildRulesSection(context) {
|
|
486
|
+
const parts = [];
|
|
487
|
+
parts.push("\n\nRULES:");
|
|
488
|
+
const rules = resolveRulesTemplate(this.promptConfig.rules, context);
|
|
489
|
+
for (const rule of rules) {
|
|
490
|
+
parts.push(`
|
|
491
|
+
- ${rule}`);
|
|
492
|
+
}
|
|
493
|
+
return parts.join("");
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Add a user message.
|
|
497
|
+
* Content can be a string (text only) or an array of content parts (multimodal).
|
|
498
|
+
*
|
|
499
|
+
* @param content - Message content
|
|
500
|
+
* @param metadata - Optional metadata
|
|
501
|
+
*
|
|
502
|
+
* @example
|
|
503
|
+
* ```typescript
|
|
504
|
+
* // Text only
|
|
505
|
+
* builder.addUser("Hello!");
|
|
506
|
+
*
|
|
507
|
+
* // Multimodal
|
|
508
|
+
* builder.addUser([
|
|
509
|
+
* text("What's in this image?"),
|
|
510
|
+
* imageFromBuffer(imageData),
|
|
511
|
+
* ]);
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
addUser(content, metadata) {
|
|
515
|
+
this.messages.push({ role: "user", content, metadata });
|
|
516
|
+
return this;
|
|
517
|
+
}
|
|
518
|
+
addAssistant(content, metadata) {
|
|
519
|
+
this.messages.push({ role: "assistant", content, metadata });
|
|
520
|
+
return this;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Add a user message with an image attachment.
|
|
524
|
+
*
|
|
525
|
+
* @param textContent - Text prompt
|
|
526
|
+
* @param imageData - Image data (Buffer, Uint8Array, or base64 string)
|
|
527
|
+
* @param mimeType - Optional MIME type (auto-detected if not provided)
|
|
528
|
+
*
|
|
529
|
+
* @example
|
|
530
|
+
* ```typescript
|
|
531
|
+
* builder.addUserWithImage(
|
|
532
|
+
* "What's in this image?",
|
|
533
|
+
* await fs.readFile("photo.jpg"),
|
|
534
|
+
* "image/jpeg" // Optional - auto-detected
|
|
535
|
+
* );
|
|
536
|
+
* ```
|
|
537
|
+
*/
|
|
538
|
+
addUserWithImage(textContent, imageData, mimeType) {
|
|
539
|
+
const imageBuffer = typeof imageData === "string" ? Buffer.from(imageData, "base64") : imageData;
|
|
540
|
+
const detectedMime = mimeType ?? detectImageMimeType(imageBuffer);
|
|
541
|
+
if (!detectedMime) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
"Could not detect image MIME type. Please provide the mimeType parameter explicitly."
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
const content = [
|
|
547
|
+
text(textContent),
|
|
548
|
+
{
|
|
549
|
+
type: "image",
|
|
550
|
+
source: {
|
|
551
|
+
type: "base64",
|
|
552
|
+
mediaType: detectedMime,
|
|
553
|
+
data: toBase64(imageBuffer)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
];
|
|
557
|
+
this.messages.push({ role: "user", content });
|
|
558
|
+
return this;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Add a user message with an image URL (OpenAI only).
|
|
562
|
+
*
|
|
563
|
+
* @param textContent - Text prompt
|
|
564
|
+
* @param imageUrl - URL to the image
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* ```typescript
|
|
568
|
+
* builder.addUserWithImageUrl(
|
|
569
|
+
* "What's in this image?",
|
|
570
|
+
* "https://example.com/image.jpg"
|
|
571
|
+
* );
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
addUserWithImageUrl(textContent, imageUrl) {
|
|
575
|
+
const content = [text(textContent), imageFromUrl(imageUrl)];
|
|
576
|
+
this.messages.push({ role: "user", content });
|
|
577
|
+
return this;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Add a user message with an audio attachment (Gemini only).
|
|
581
|
+
*
|
|
582
|
+
* @param textContent - Text prompt
|
|
583
|
+
* @param audioData - Audio data (Buffer, Uint8Array, or base64 string)
|
|
584
|
+
* @param mimeType - Optional MIME type (auto-detected if not provided)
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```typescript
|
|
588
|
+
* builder.addUserWithAudio(
|
|
589
|
+
* "Transcribe this audio",
|
|
590
|
+
* await fs.readFile("recording.mp3"),
|
|
591
|
+
* "audio/mp3" // Optional - auto-detected
|
|
592
|
+
* );
|
|
593
|
+
* ```
|
|
594
|
+
*/
|
|
595
|
+
addUserWithAudio(textContent, audioData, mimeType) {
|
|
596
|
+
const audioBuffer = typeof audioData === "string" ? Buffer.from(audioData, "base64") : audioData;
|
|
597
|
+
const content = [text(textContent), audioFromBuffer(audioBuffer, mimeType)];
|
|
598
|
+
this.messages.push({ role: "user", content });
|
|
599
|
+
return this;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Add a user message with multiple content parts.
|
|
603
|
+
* Provides full flexibility for complex multimodal messages.
|
|
604
|
+
*
|
|
605
|
+
* @param parts - Array of content parts
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* ```typescript
|
|
609
|
+
* builder.addUserMultimodal([
|
|
610
|
+
* text("Compare these images:"),
|
|
611
|
+
* imageFromBuffer(image1),
|
|
612
|
+
* imageFromBuffer(image2),
|
|
613
|
+
* ]);
|
|
614
|
+
* ```
|
|
615
|
+
*/
|
|
616
|
+
addUserMultimodal(parts) {
|
|
617
|
+
this.messages.push({ role: "user", content: parts });
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Record a gadget execution result in the message history.
|
|
622
|
+
* Creates an assistant message with the gadget invocation and a user message with the result.
|
|
623
|
+
*
|
|
624
|
+
* The invocationId is shown to the LLM so it can reference previous calls when building dependencies.
|
|
625
|
+
*
|
|
626
|
+
* @param gadget - Name of the gadget that was executed
|
|
627
|
+
* @param parameters - Parameters that were passed to the gadget
|
|
628
|
+
* @param result - Text result from the gadget execution
|
|
629
|
+
* @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
|
|
630
|
+
* @param media - Optional media outputs from the gadget
|
|
631
|
+
* @param mediaIds - Optional IDs for the media outputs
|
|
632
|
+
* @param storedMedia - Optional stored media info including file paths
|
|
633
|
+
*/
|
|
634
|
+
addGadgetCallResult(gadget, parameters, result, invocationId, media, mediaIds, storedMedia) {
|
|
635
|
+
const paramStr = this.formatBlockParameters(parameters, "");
|
|
636
|
+
this.messages.push({
|
|
637
|
+
role: "assistant",
|
|
638
|
+
content: `${this.startPrefix}${gadget}:${invocationId}
|
|
639
|
+
${paramStr}
|
|
640
|
+
${this.endPrefix}`
|
|
641
|
+
});
|
|
642
|
+
if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
|
|
643
|
+
const idRefs = media.map((m, i) => {
|
|
644
|
+
const path2 = storedMedia?.[i]?.path;
|
|
645
|
+
const pathInfo = path2 ? ` \u2192 saved to: ${path2}` : "";
|
|
646
|
+
return `[Media: ${mediaIds[i]} (${m.kind})${pathInfo}]`;
|
|
647
|
+
}).join("\n");
|
|
648
|
+
const textWithIds = `Result (${invocationId}): ${result}
|
|
649
|
+
${idRefs}`;
|
|
650
|
+
const parts = [text(textWithIds)];
|
|
651
|
+
for (const item of media) {
|
|
652
|
+
if (item.kind === "image") {
|
|
653
|
+
parts.push(imageFromBase64(item.data, item.mimeType));
|
|
654
|
+
} else if (item.kind === "audio") {
|
|
655
|
+
parts.push(audioFromBase64(item.data, item.mimeType));
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
this.messages.push({ role: "user", content: parts });
|
|
659
|
+
} else {
|
|
660
|
+
this.messages.push({
|
|
661
|
+
role: "user",
|
|
662
|
+
content: `Result (${invocationId}): ${result}`
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Format parameters as Block format with JSON Pointer paths.
|
|
669
|
+
* Uses the configured argPrefix for consistency with system prompt.
|
|
670
|
+
*/
|
|
671
|
+
formatBlockParameters(params, prefix) {
|
|
672
|
+
const lines = [];
|
|
673
|
+
for (const [key, value] of Object.entries(params)) {
|
|
674
|
+
const fullPath = prefix ? `${prefix}/${key}` : key;
|
|
675
|
+
if (Array.isArray(value)) {
|
|
676
|
+
value.forEach((item, index) => {
|
|
677
|
+
const itemPath = `${fullPath}/${index}`;
|
|
678
|
+
if (typeof item === "object" && item !== null) {
|
|
679
|
+
lines.push(this.formatBlockParameters(item, itemPath));
|
|
680
|
+
} else {
|
|
681
|
+
lines.push(`${this.argPrefix}${itemPath}`);
|
|
682
|
+
lines.push(String(item));
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
} else if (typeof value === "object" && value !== null) {
|
|
686
|
+
lines.push(this.formatBlockParameters(value, fullPath));
|
|
687
|
+
} else {
|
|
688
|
+
lines.push(`${this.argPrefix}${fullPath}`);
|
|
689
|
+
lines.push(String(value));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return lines.join("\n");
|
|
693
|
+
}
|
|
694
|
+
build() {
|
|
695
|
+
return [...this.messages];
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// src/gadgets/exceptions.ts
|
|
702
|
+
var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException, BudgetPricingUnavailableError;
|
|
703
|
+
var init_exceptions = __esm({
|
|
704
|
+
"src/gadgets/exceptions.ts"() {
|
|
705
|
+
"use strict";
|
|
706
|
+
TaskCompletionSignal = class extends Error {
|
|
707
|
+
constructor(message) {
|
|
708
|
+
super(message ?? "Agent loop terminated by gadget");
|
|
709
|
+
this.name = "TaskCompletionSignal";
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
HumanInputRequiredException = class extends Error {
|
|
713
|
+
question;
|
|
714
|
+
constructor(question) {
|
|
715
|
+
super(`Human input required: ${question}`);
|
|
716
|
+
this.name = "HumanInputRequiredException";
|
|
717
|
+
this.question = question;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
TimeoutException = class extends Error {
|
|
721
|
+
timeoutMs;
|
|
722
|
+
gadgetName;
|
|
723
|
+
constructor(gadgetName, timeoutMs) {
|
|
724
|
+
super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
|
|
725
|
+
this.name = "TimeoutException";
|
|
726
|
+
this.gadgetName = gadgetName;
|
|
727
|
+
this.timeoutMs = timeoutMs;
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
AbortException = class extends Error {
|
|
731
|
+
constructor(message) {
|
|
732
|
+
super(message || "Gadget execution was aborted");
|
|
733
|
+
this.name = "AbortException";
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
BudgetPricingUnavailableError = class extends Error {
|
|
737
|
+
model;
|
|
738
|
+
budget;
|
|
739
|
+
constructor(model, budget) {
|
|
740
|
+
super(
|
|
741
|
+
`Budget of $${budget.toFixed(2)} was set but model "${model}" has no valid pricing information in the model registry. Either register pricing for this model via client.modelRegistry.registerModel() or remove the budget constraint.`
|
|
742
|
+
);
|
|
743
|
+
this.name = "BudgetPricingUnavailableError";
|
|
744
|
+
this.model = model;
|
|
745
|
+
this.budget = budget;
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
// src/logging/logger.ts
|
|
752
|
+
import { createWriteStream, mkdirSync } from "fs";
|
|
753
|
+
import { dirname } from "path";
|
|
754
|
+
import { Logger } from "tslog";
|
|
755
|
+
function parseLogLevel(value) {
|
|
756
|
+
if (!value) {
|
|
757
|
+
return void 0;
|
|
758
|
+
}
|
|
759
|
+
const normalized = value.trim().toLowerCase();
|
|
760
|
+
if (normalized === "") {
|
|
761
|
+
return void 0;
|
|
762
|
+
}
|
|
763
|
+
const numericLevel = Number(normalized);
|
|
764
|
+
if (Number.isFinite(numericLevel)) {
|
|
765
|
+
return Math.max(0, Math.min(6, Math.floor(numericLevel)));
|
|
766
|
+
}
|
|
767
|
+
return LEVEL_NAME_TO_ID[normalized];
|
|
768
|
+
}
|
|
769
|
+
function parseEnvBoolean(value) {
|
|
770
|
+
if (!value) return void 0;
|
|
771
|
+
const normalized = value.trim().toLowerCase();
|
|
772
|
+
if (normalized === "true" || normalized === "1") return true;
|
|
773
|
+
if (normalized === "false" || normalized === "0") return false;
|
|
774
|
+
return void 0;
|
|
775
|
+
}
|
|
776
|
+
function stripAnsi(str) {
|
|
777
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
778
|
+
}
|
|
779
|
+
function createLogger(options = {}) {
|
|
780
|
+
const envMinLevel = parseLogLevel(process.env.LLMIST_LOG_LEVEL);
|
|
781
|
+
const envLogFile = process.env.LLMIST_LOG_FILE?.trim() ?? "";
|
|
782
|
+
const envLogReset = parseEnvBoolean(process.env.LLMIST_LOG_RESET);
|
|
783
|
+
const minLevel = options.minLevel ?? envMinLevel ?? 4;
|
|
784
|
+
const defaultType = options.type ?? "pretty";
|
|
785
|
+
const name = options.name ?? "llmist";
|
|
786
|
+
const logReset = options.logReset ?? envLogReset ?? false;
|
|
787
|
+
const envLogTee = parseEnvBoolean(process.env.LLMIST_LOG_TEE);
|
|
788
|
+
const teeToConsole = options.teeToConsole ?? envLogTee ?? false;
|
|
789
|
+
if (envLogFile && (!logFileInitialized || sharedLogFilePath !== envLogFile)) {
|
|
790
|
+
try {
|
|
791
|
+
if (sharedLogFileStream) {
|
|
792
|
+
sharedLogFileStream.end();
|
|
793
|
+
sharedLogFileStream = void 0;
|
|
794
|
+
}
|
|
795
|
+
mkdirSync(dirname(envLogFile), { recursive: true });
|
|
796
|
+
const flags = logReset ? "w" : "a";
|
|
797
|
+
sharedLogFileStream = createWriteStream(envLogFile, { flags });
|
|
798
|
+
sharedLogFilePath = envLogFile;
|
|
799
|
+
logFileInitialized = true;
|
|
800
|
+
writeErrorCount = 0;
|
|
801
|
+
writeErrorReported = false;
|
|
802
|
+
sharedLogFileStream.on("error", (error) => {
|
|
803
|
+
writeErrorCount++;
|
|
804
|
+
if (!writeErrorReported) {
|
|
805
|
+
console.error(`[llmist] Log file write error: ${error.message}`);
|
|
806
|
+
writeErrorReported = true;
|
|
807
|
+
}
|
|
808
|
+
if (writeErrorCount >= MAX_WRITE_ERRORS_BEFORE_DISABLE) {
|
|
809
|
+
console.error(
|
|
810
|
+
`[llmist] Too many log file errors (${writeErrorCount}), disabling file logging`
|
|
811
|
+
);
|
|
812
|
+
sharedLogFileStream?.end();
|
|
813
|
+
sharedLogFileStream = void 0;
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
} catch (error) {
|
|
817
|
+
console.error("Failed to initialize LLMIST_LOG_FILE output:", error);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
const useFileLogging = Boolean(sharedLogFileStream);
|
|
821
|
+
const logger = new Logger({
|
|
822
|
+
name,
|
|
823
|
+
minLevel,
|
|
824
|
+
type: useFileLogging ? "pretty" : defaultType,
|
|
825
|
+
// Hide log position for file logging and non-pretty types
|
|
826
|
+
hideLogPositionForProduction: useFileLogging || defaultType !== "pretty",
|
|
827
|
+
prettyLogTemplate: LOG_TEMPLATE,
|
|
828
|
+
// Use overwrite to redirect tslog's formatted output to file instead of console
|
|
829
|
+
overwrite: useFileLogging ? {
|
|
830
|
+
transportFormatted: (logMetaMarkup, logArgs, _logErrors) => {
|
|
831
|
+
const args = logArgs.map(
|
|
832
|
+
(arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
|
|
833
|
+
);
|
|
834
|
+
if (sharedLogFileStream) {
|
|
835
|
+
const meta = stripAnsi(logMetaMarkup);
|
|
836
|
+
const fileArgs = args.map((a) => stripAnsi(a));
|
|
837
|
+
sharedLogFileStream.write(`${meta}${fileArgs.join(" ")}
|
|
838
|
+
`);
|
|
839
|
+
}
|
|
840
|
+
if (teeToConsole) {
|
|
841
|
+
process.stdout.write(`${logMetaMarkup}${args.join(" ")}
|
|
842
|
+
`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
} : void 0
|
|
846
|
+
});
|
|
847
|
+
return logger;
|
|
848
|
+
}
|
|
849
|
+
var LEVEL_NAME_TO_ID, sharedLogFilePath, sharedLogFileStream, logFileInitialized, writeErrorCount, writeErrorReported, MAX_WRITE_ERRORS_BEFORE_DISABLE, LOG_TEMPLATE, defaultLogger;
|
|
850
|
+
var init_logger = __esm({
|
|
851
|
+
"src/logging/logger.ts"() {
|
|
852
|
+
"use strict";
|
|
853
|
+
LEVEL_NAME_TO_ID = {
|
|
854
|
+
silly: 0,
|
|
855
|
+
trace: 1,
|
|
856
|
+
debug: 2,
|
|
857
|
+
info: 3,
|
|
858
|
+
warn: 4,
|
|
859
|
+
error: 5,
|
|
860
|
+
fatal: 6
|
|
861
|
+
};
|
|
862
|
+
logFileInitialized = false;
|
|
863
|
+
writeErrorCount = 0;
|
|
864
|
+
writeErrorReported = false;
|
|
865
|
+
MAX_WRITE_ERRORS_BEFORE_DISABLE = 5;
|
|
866
|
+
LOG_TEMPLATE = "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] ";
|
|
867
|
+
defaultLogger = createLogger();
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
// src/gadgets/schema-to-json.ts
|
|
872
|
+
import * as z from "zod";
|
|
873
|
+
function schemaToJSONSchema(schema, options) {
|
|
874
|
+
const jsonSchema = z.toJSONSchema(schema, options ?? { target: "draft-7" });
|
|
875
|
+
const mismatches = detectDescriptionMismatch(schema, jsonSchema);
|
|
876
|
+
if (mismatches.length > 0) {
|
|
877
|
+
defaultLogger.warn(
|
|
878
|
+
`Zod instance mismatch detected: ${mismatches.length} description(s) lost. For best results, use: import { z } from "llmist"`
|
|
879
|
+
);
|
|
880
|
+
return mergeDescriptions(schema, jsonSchema);
|
|
881
|
+
}
|
|
882
|
+
return jsonSchema;
|
|
883
|
+
}
|
|
884
|
+
function detectDescriptionMismatch(schema, jsonSchema) {
|
|
885
|
+
const mismatches = [];
|
|
886
|
+
function checkSchema(zodSchema, json, path2) {
|
|
887
|
+
if (!zodSchema || typeof zodSchema !== "object") return;
|
|
888
|
+
const def = zodSchema._def;
|
|
889
|
+
const jsonObj = json;
|
|
890
|
+
if (def?.description && !jsonObj?.description) {
|
|
891
|
+
mismatches.push(path2 || "root");
|
|
892
|
+
}
|
|
893
|
+
if (def?.typeName === "ZodObject" && def?.shape) {
|
|
894
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
895
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
896
|
+
const properties = jsonObj?.properties;
|
|
897
|
+
const jsonProp = properties?.[key];
|
|
898
|
+
checkSchema(fieldSchema, jsonProp, path2 ? `${path2}.${key}` : key);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (def?.typeName === "ZodArray" && def?.type) {
|
|
902
|
+
checkSchema(def.type, jsonObj?.items, path2 ? `${path2}[]` : "[]");
|
|
903
|
+
}
|
|
904
|
+
if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
|
|
905
|
+
checkSchema(def.innerType, json, path2);
|
|
906
|
+
}
|
|
907
|
+
if (def?.typeName === "ZodDefault" && def?.innerType) {
|
|
908
|
+
checkSchema(def.innerType, json, path2);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
checkSchema(schema, jsonSchema, "");
|
|
912
|
+
return mismatches;
|
|
913
|
+
}
|
|
914
|
+
function mergeDescriptions(schema, jsonSchema) {
|
|
915
|
+
function merge(zodSchema, json) {
|
|
916
|
+
if (!json || typeof json !== "object") return json;
|
|
917
|
+
const def = zodSchema._def;
|
|
918
|
+
const jsonObj = json;
|
|
919
|
+
const merged = { ...jsonObj };
|
|
920
|
+
if (def?.description && !jsonObj.description) {
|
|
921
|
+
merged.description = def.description;
|
|
922
|
+
}
|
|
923
|
+
if (def?.typeName === "ZodObject" && def?.shape && jsonObj.properties) {
|
|
924
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
925
|
+
const properties = jsonObj.properties;
|
|
926
|
+
merged.properties = { ...properties };
|
|
927
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
928
|
+
if (properties[key]) {
|
|
929
|
+
merged.properties[key] = merge(fieldSchema, properties[key]);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (def?.typeName === "ZodArray" && def?.type && jsonObj.items) {
|
|
934
|
+
merged.items = merge(def.type, jsonObj.items);
|
|
935
|
+
}
|
|
936
|
+
if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
|
|
937
|
+
return merge(def.innerType, json);
|
|
938
|
+
}
|
|
939
|
+
if (def?.typeName === "ZodDefault" && def?.innerType) {
|
|
940
|
+
return merge(def.innerType, json);
|
|
941
|
+
}
|
|
942
|
+
return merged;
|
|
943
|
+
}
|
|
944
|
+
return merge(schema, jsonSchema);
|
|
945
|
+
}
|
|
946
|
+
var init_schema_to_json = __esm({
|
|
947
|
+
"src/gadgets/schema-to-json.ts"() {
|
|
948
|
+
"use strict";
|
|
949
|
+
init_logger();
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
// src/gadgets/schema-validator.ts
|
|
954
|
+
import * as z2 from "zod";
|
|
955
|
+
function validateGadgetSchema(schema, gadgetName) {
|
|
956
|
+
let jsonSchema;
|
|
957
|
+
try {
|
|
958
|
+
jsonSchema = z2.toJSONSchema(schema, { target: "draft-7" });
|
|
959
|
+
} catch (error) {
|
|
960
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
961
|
+
throw new Error(
|
|
962
|
+
`Gadget "${gadgetName}" has a schema that cannot be serialized to JSON Schema.
|
|
963
|
+
This usually happens with unsupported patterns like:
|
|
964
|
+
- z.record() - use z.object({}).passthrough() instead
|
|
965
|
+
- Complex transforms or custom refinements
|
|
966
|
+
- Circular references
|
|
967
|
+
|
|
968
|
+
Original error: ${errorMessage}
|
|
969
|
+
|
|
970
|
+
Only use schema patterns that Zod v4's native toJSONSchema() supports.`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
const issues = findUnknownTypes(jsonSchema);
|
|
974
|
+
if (issues.length > 0) {
|
|
975
|
+
const fieldList = issues.join(", ");
|
|
976
|
+
throw new Error(
|
|
977
|
+
`Gadget "${gadgetName}" uses z.unknown() which produces incomplete schemas.
|
|
978
|
+
Problematic fields: ${fieldList}
|
|
979
|
+
|
|
980
|
+
z.unknown() doesn't generate type information in JSON Schema, making it unclear
|
|
981
|
+
to the LLM what data structure to provide.
|
|
982
|
+
|
|
983
|
+
Suggestions:
|
|
984
|
+
- Use z.object({}).passthrough() for flexible objects
|
|
985
|
+
- Use z.record(z.string()) for key-value objects with string values
|
|
986
|
+
- Define specific structure if possible
|
|
987
|
+
|
|
988
|
+
Example fixes:
|
|
989
|
+
// \u274C Bad
|
|
990
|
+
content: z.unknown()
|
|
991
|
+
|
|
992
|
+
// \u2705 Good
|
|
993
|
+
content: z.object({}).passthrough() // for flexible objects
|
|
994
|
+
content: z.record(z.string()) // for key-value objects
|
|
995
|
+
content: z.array(z.string()) // for arrays of strings
|
|
996
|
+
`
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function findUnknownTypes(schema, path2 = []) {
|
|
1001
|
+
const issues = [];
|
|
1002
|
+
if (!schema || typeof schema !== "object") {
|
|
1003
|
+
return issues;
|
|
1004
|
+
}
|
|
1005
|
+
if (schema.definitions) {
|
|
1006
|
+
for (const defSchema of Object.values(schema.definitions)) {
|
|
1007
|
+
issues.push(...findUnknownTypes(defSchema, []));
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
if (schema.properties) {
|
|
1011
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
1012
|
+
const propPath = [...path2, propName];
|
|
1013
|
+
if (hasNoType(propSchema)) {
|
|
1014
|
+
issues.push(propPath.join(".") || propName);
|
|
1015
|
+
}
|
|
1016
|
+
issues.push(...findUnknownTypes(propSchema, propPath));
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (schema.items) {
|
|
1020
|
+
const itemPath = [...path2, "[]"];
|
|
1021
|
+
if (hasNoType(schema.items)) {
|
|
1022
|
+
issues.push(itemPath.join("."));
|
|
1023
|
+
}
|
|
1024
|
+
issues.push(...findUnknownTypes(schema.items, itemPath));
|
|
1025
|
+
}
|
|
1026
|
+
if (schema.anyOf) {
|
|
1027
|
+
schema.anyOf.forEach((subSchema, index) => {
|
|
1028
|
+
issues.push(...findUnknownTypes(subSchema, [...path2, `anyOf[${index}]`]));
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
if (schema.oneOf) {
|
|
1032
|
+
schema.oneOf.forEach((subSchema, index) => {
|
|
1033
|
+
issues.push(...findUnknownTypes(subSchema, [...path2, `oneOf[${index}]`]));
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
if (schema.allOf) {
|
|
1037
|
+
schema.allOf.forEach((subSchema, index) => {
|
|
1038
|
+
issues.push(...findUnknownTypes(subSchema, [...path2, `allOf[${index}]`]));
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
return issues;
|
|
1042
|
+
}
|
|
1043
|
+
function hasNoType(prop) {
|
|
1044
|
+
if (!prop || typeof prop !== "object") {
|
|
1045
|
+
return false;
|
|
1046
|
+
}
|
|
1047
|
+
const hasType = prop.type !== void 0;
|
|
1048
|
+
const hasRef = prop.$ref !== void 0;
|
|
1049
|
+
const hasUnion = prop.anyOf !== void 0 || prop.oneOf !== void 0 || prop.allOf !== void 0;
|
|
1050
|
+
if (hasType || hasRef || hasUnion) {
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
const keys = Object.keys(prop);
|
|
1054
|
+
const metadataKeys = ["description", "title", "default", "examples"];
|
|
1055
|
+
const hasOnlyMetadata = keys.every((key) => metadataKeys.includes(key));
|
|
1056
|
+
return hasOnlyMetadata || keys.length === 0;
|
|
1057
|
+
}
|
|
1058
|
+
var init_schema_validator = __esm({
|
|
1059
|
+
"src/gadgets/schema-validator.ts"() {
|
|
1060
|
+
"use strict";
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
// src/gadgets/gadget.ts
|
|
1065
|
+
function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
|
|
1066
|
+
const lines = [];
|
|
1067
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1068
|
+
const fullPath = prefix ? `${prefix}/${key}` : key;
|
|
1069
|
+
if (Array.isArray(value)) {
|
|
1070
|
+
value.forEach((item, index) => {
|
|
1071
|
+
const itemPath = `${fullPath}/${index}`;
|
|
1072
|
+
if (typeof item === "object" && item !== null) {
|
|
1073
|
+
lines.push(
|
|
1074
|
+
formatParamsForBlockExample(item, itemPath, argPrefix)
|
|
1075
|
+
);
|
|
1076
|
+
} else {
|
|
1077
|
+
lines.push(`${argPrefix}${itemPath}`);
|
|
1078
|
+
lines.push(String(item));
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
} else if (typeof value === "object" && value !== null) {
|
|
1082
|
+
lines.push(
|
|
1083
|
+
formatParamsForBlockExample(value, fullPath, argPrefix)
|
|
1084
|
+
);
|
|
1085
|
+
} else {
|
|
1086
|
+
lines.push(`${argPrefix}${fullPath}`);
|
|
1087
|
+
lines.push(String(value));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return lines.join("\n");
|
|
1091
|
+
}
|
|
1092
|
+
function formatParamLine(key, propObj, isRequired, indent = "") {
|
|
1093
|
+
const type = propObj.type;
|
|
1094
|
+
const description = propObj.description;
|
|
1095
|
+
const enumValues = propObj.enum;
|
|
1096
|
+
let line = `${indent}- ${key}`;
|
|
1097
|
+
if (type === "array") {
|
|
1098
|
+
const items = propObj.items;
|
|
1099
|
+
const itemType = items?.type || "any";
|
|
1100
|
+
line += ` (array of ${itemType})`;
|
|
1101
|
+
} else if (type === "object" && propObj.properties) {
|
|
1102
|
+
line += " (object)";
|
|
1103
|
+
} else {
|
|
1104
|
+
line += ` (${type})`;
|
|
1105
|
+
}
|
|
1106
|
+
if (isRequired && indent !== "") {
|
|
1107
|
+
line += " [required]";
|
|
1108
|
+
}
|
|
1109
|
+
if (description) {
|
|
1110
|
+
line += `: ${description}`;
|
|
1111
|
+
}
|
|
1112
|
+
if (enumValues) {
|
|
1113
|
+
line += ` - one of: ${enumValues.map((v) => `"${v}"`).join(", ")}`;
|
|
1114
|
+
}
|
|
1115
|
+
return line;
|
|
1116
|
+
}
|
|
1117
|
+
function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
|
|
1118
|
+
const lines = [];
|
|
1119
|
+
const properties = schema.properties || {};
|
|
1120
|
+
const required = schema.required || [];
|
|
1121
|
+
if (atRoot && indent === "") {
|
|
1122
|
+
const requiredProps = [];
|
|
1123
|
+
const optionalProps = [];
|
|
1124
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
1125
|
+
if (required.includes(key)) {
|
|
1126
|
+
requiredProps.push([key, prop]);
|
|
1127
|
+
} else {
|
|
1128
|
+
optionalProps.push([key, prop]);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const reqCount = requiredProps.length;
|
|
1132
|
+
const optCount = optionalProps.length;
|
|
1133
|
+
if (reqCount > 0 || optCount > 0) {
|
|
1134
|
+
const parts = [];
|
|
1135
|
+
if (reqCount > 0) parts.push(`${reqCount} required`);
|
|
1136
|
+
if (optCount > 0) parts.push(`${optCount} optional`);
|
|
1137
|
+
lines.push(parts.join(", "));
|
|
1138
|
+
lines.push("");
|
|
1139
|
+
}
|
|
1140
|
+
if (reqCount > 0) {
|
|
1141
|
+
lines.push("REQUIRED Parameters:");
|
|
1142
|
+
for (const [key, prop] of requiredProps) {
|
|
1143
|
+
lines.push(formatParamLine(key, prop, true, ""));
|
|
1144
|
+
const propObj = prop;
|
|
1145
|
+
if (propObj.type === "object" && propObj.properties) {
|
|
1146
|
+
lines.push(formatSchemaAsPlainText(propObj, " ", false));
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
if (optCount > 0) {
|
|
1151
|
+
if (reqCount > 0) lines.push("");
|
|
1152
|
+
lines.push("OPTIONAL Parameters:");
|
|
1153
|
+
for (const [key, prop] of optionalProps) {
|
|
1154
|
+
lines.push(formatParamLine(key, prop, false, ""));
|
|
1155
|
+
const propObj = prop;
|
|
1156
|
+
if (propObj.type === "object" && propObj.properties) {
|
|
1157
|
+
lines.push(formatSchemaAsPlainText(propObj, " ", false));
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return lines.join("\n");
|
|
1162
|
+
}
|
|
1163
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
1164
|
+
const isRequired = required.includes(key);
|
|
1165
|
+
lines.push(formatParamLine(key, prop, isRequired, indent));
|
|
1166
|
+
const propObj = prop;
|
|
1167
|
+
if (propObj.type === "object" && propObj.properties) {
|
|
1168
|
+
lines.push(formatSchemaAsPlainText(propObj, indent + " ", false));
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return lines.join("\n");
|
|
1172
|
+
}
|
|
1173
|
+
var AbstractGadget;
|
|
1174
|
+
var init_gadget = __esm({
|
|
1175
|
+
"src/gadgets/gadget.ts"() {
|
|
1176
|
+
"use strict";
|
|
1177
|
+
init_constants();
|
|
1178
|
+
init_exceptions();
|
|
1179
|
+
init_schema_to_json();
|
|
1180
|
+
init_schema_validator();
|
|
1181
|
+
AbstractGadget = class {
|
|
1182
|
+
/**
|
|
1183
|
+
* The name of the gadget. Used for identification when LLM calls it.
|
|
1184
|
+
* If not provided, defaults to the class name.
|
|
1185
|
+
*/
|
|
1186
|
+
name;
|
|
1187
|
+
/**
|
|
1188
|
+
* Optional Zod schema describing the expected input payload. When provided,
|
|
1189
|
+
* it will be validated before execution and transformed into a JSON Schema
|
|
1190
|
+
* representation that is surfaced to the LLM as part of the instructions.
|
|
1191
|
+
*/
|
|
1192
|
+
parameterSchema;
|
|
1193
|
+
/**
|
|
1194
|
+
* Optional timeout in milliseconds for gadget execution.
|
|
1195
|
+
* If execution exceeds this timeout, a TimeoutException will be thrown.
|
|
1196
|
+
* If not set, the global defaultGadgetTimeoutMs from runtime options will be used.
|
|
1197
|
+
* Set to 0 or undefined to disable timeout for this gadget.
|
|
1198
|
+
*/
|
|
1199
|
+
timeoutMs;
|
|
1200
|
+
/**
|
|
1201
|
+
* Optional usage examples to help LLMs understand proper invocation.
|
|
1202
|
+
* Examples are rendered in getInstruction() alongside the schema.
|
|
1203
|
+
*
|
|
1204
|
+
* Note: Uses broader `unknown` type to allow typed examples from subclasses
|
|
1205
|
+
* while maintaining runtime compatibility.
|
|
1206
|
+
*/
|
|
1207
|
+
examples;
|
|
1208
|
+
/**
|
|
1209
|
+
* Maximum number of concurrent executions allowed for this gadget.
|
|
1210
|
+
* Use this to prevent race conditions in gadgets that modify shared state.
|
|
1211
|
+
*
|
|
1212
|
+
* - `1` = Sequential execution (only one instance runs at a time)
|
|
1213
|
+
* - `0` or `undefined` = Unlimited concurrency (default)
|
|
1214
|
+
* - `N > 1` = At most N concurrent executions
|
|
1215
|
+
*
|
|
1216
|
+
* This property sets a safety floor: external configuration (SubagentConfig)
|
|
1217
|
+
* can only make concurrency MORE restrictive, never less. For example, if
|
|
1218
|
+
* a gadget declares `maxConcurrent: 1`, external config cannot override it
|
|
1219
|
+
* to allow parallel execution.
|
|
1220
|
+
*
|
|
1221
|
+
* @example
|
|
1222
|
+
* ```typescript
|
|
1223
|
+
* // File writer that must run sequentially to avoid race conditions
|
|
1224
|
+
* class WriteFile extends Gadget({
|
|
1225
|
+
* description: 'Writes content to a file',
|
|
1226
|
+
* schema: z.object({ path: z.string(), content: z.string() }),
|
|
1227
|
+
* maxConcurrent: 1, // Sequential - prevents race conditions
|
|
1228
|
+
* }) {
|
|
1229
|
+
* execute(params: this['params']) { ... }
|
|
1230
|
+
* }
|
|
1231
|
+
* ```
|
|
1232
|
+
*/
|
|
1233
|
+
maxConcurrent;
|
|
1234
|
+
/**
|
|
1235
|
+
* If true, this gadget must execute alone — no other gadgets in the same
|
|
1236
|
+
* LLM response can run in parallel. When an exclusive gadget arrives and
|
|
1237
|
+
* other gadgets are already in-flight, it is deferred until they complete.
|
|
1238
|
+
*
|
|
1239
|
+
* Use for gadgets that terminate the agent loop (e.g., Finish), where
|
|
1240
|
+
* sibling tool results must be visible to the LLM before the loop ends.
|
|
1241
|
+
*
|
|
1242
|
+
* This is a safety floor: external config cannot weaken it.
|
|
1243
|
+
*/
|
|
1244
|
+
exclusive;
|
|
1245
|
+
/**
|
|
1246
|
+
* Throws an AbortException if the execution has been aborted.
|
|
1247
|
+
*
|
|
1248
|
+
* Call this at key checkpoints in long-running gadgets to allow early exit
|
|
1249
|
+
* when the gadget has been cancelled (e.g., due to timeout). This enables
|
|
1250
|
+
* resource cleanup and prevents unnecessary work after cancellation.
|
|
1251
|
+
*
|
|
1252
|
+
* @param ctx - The execution context containing the abort signal
|
|
1253
|
+
* @throws AbortException if ctx.signal.aborted is true
|
|
1254
|
+
*
|
|
1255
|
+
* @example
|
|
1256
|
+
* ```typescript
|
|
1257
|
+
* class DataProcessor extends Gadget({
|
|
1258
|
+
* description: 'Processes data in multiple steps',
|
|
1259
|
+
* schema: z.object({ items: z.array(z.string()) }),
|
|
1260
|
+
* }) {
|
|
1261
|
+
* async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
|
|
1262
|
+
* const results: string[] = [];
|
|
1263
|
+
*
|
|
1264
|
+
* for (const item of params.items) {
|
|
1265
|
+
* // Check before each expensive operation
|
|
1266
|
+
* this.throwIfAborted(ctx);
|
|
1267
|
+
*
|
|
1268
|
+
* results.push(await this.processItem(item));
|
|
1269
|
+
* }
|
|
1270
|
+
*
|
|
1271
|
+
* return results.join(', ');
|
|
1272
|
+
* }
|
|
1273
|
+
* }
|
|
1274
|
+
* ```
|
|
1275
|
+
*/
|
|
1276
|
+
throwIfAborted(ctx) {
|
|
1277
|
+
if (ctx?.signal?.aborted) {
|
|
1278
|
+
throw new AbortException();
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Register a cleanup function to run when execution is aborted (timeout or cancellation).
|
|
1283
|
+
* The cleanup function is called immediately if the signal is already aborted.
|
|
1284
|
+
* Errors thrown by the cleanup function are silently ignored.
|
|
1285
|
+
*
|
|
1286
|
+
* Use this to clean up resources like browser instances, database connections,
|
|
1287
|
+
* or child processes when the gadget is cancelled due to timeout.
|
|
1288
|
+
*
|
|
1289
|
+
* @param ctx - The execution context containing the abort signal
|
|
1290
|
+
* @param cleanup - Function to run on abort (can be sync or async)
|
|
1291
|
+
*
|
|
1292
|
+
* @example
|
|
1293
|
+
* ```typescript
|
|
1294
|
+
* class BrowserGadget extends Gadget({
|
|
1295
|
+
* description: 'Fetches web page content',
|
|
1296
|
+
* schema: z.object({ url: z.string() }),
|
|
1297
|
+
* }) {
|
|
1298
|
+
* async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
|
|
1299
|
+
* const browser = await chromium.launch();
|
|
1300
|
+
* this.onAbort(ctx, () => browser.close());
|
|
1301
|
+
*
|
|
1302
|
+
* const page = await browser.newPage();
|
|
1303
|
+
* this.onAbort(ctx, () => page.close());
|
|
1304
|
+
*
|
|
1305
|
+
* await page.goto(params.url);
|
|
1306
|
+
* const content = await page.content();
|
|
1307
|
+
*
|
|
1308
|
+
* await browser.close();
|
|
1309
|
+
* return content;
|
|
1310
|
+
* }
|
|
1311
|
+
* }
|
|
1312
|
+
* ```
|
|
1313
|
+
*/
|
|
1314
|
+
onAbort(ctx, cleanup) {
|
|
1315
|
+
if (!ctx?.signal) return;
|
|
1316
|
+
const safeCleanup = () => {
|
|
1317
|
+
try {
|
|
1318
|
+
const result = cleanup();
|
|
1319
|
+
if (result && typeof result === "object" && "catch" in result) {
|
|
1320
|
+
result.catch(() => {
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
} catch {
|
|
1324
|
+
}
|
|
1325
|
+
};
|
|
1326
|
+
if (ctx.signal.aborted) {
|
|
1327
|
+
safeCleanup();
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
ctx.signal.addEventListener("abort", safeCleanup, { once: true });
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Create an AbortController linked to the execution context's signal.
|
|
1334
|
+
* When the parent signal aborts, the returned controller also aborts with the same reason.
|
|
1335
|
+
*
|
|
1336
|
+
* Useful for passing abort signals to child operations like fetch() while still
|
|
1337
|
+
* being able to abort them independently if needed.
|
|
1338
|
+
*
|
|
1339
|
+
* @param ctx - The execution context containing the parent abort signal
|
|
1340
|
+
* @returns A new AbortController linked to the parent signal
|
|
1341
|
+
*
|
|
1342
|
+
* @example
|
|
1343
|
+
* ```typescript
|
|
1344
|
+
* class FetchGadget extends Gadget({
|
|
1345
|
+
* description: 'Fetches data from URL',
|
|
1346
|
+
* schema: z.object({ url: z.string() }),
|
|
1347
|
+
* }) {
|
|
1348
|
+
* async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
|
|
1349
|
+
* const controller = this.createLinkedAbortController(ctx);
|
|
1350
|
+
*
|
|
1351
|
+
* // fetch() will automatically abort when parent times out
|
|
1352
|
+
* const response = await fetch(params.url, { signal: controller.signal });
|
|
1353
|
+
* return response.text();
|
|
1354
|
+
* }
|
|
1355
|
+
* }
|
|
1356
|
+
* ```
|
|
1357
|
+
*/
|
|
1358
|
+
createLinkedAbortController(ctx) {
|
|
1359
|
+
const controller = new AbortController();
|
|
1360
|
+
if (ctx?.signal) {
|
|
1361
|
+
if (ctx.signal.aborted) {
|
|
1362
|
+
controller.abort(ctx.signal.reason);
|
|
1363
|
+
} else {
|
|
1364
|
+
ctx.signal.addEventListener(
|
|
1365
|
+
"abort",
|
|
1366
|
+
() => {
|
|
1367
|
+
controller.abort(ctx.signal.reason);
|
|
1368
|
+
},
|
|
1369
|
+
{ once: true }
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return controller;
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Generate instruction text for the LLM.
|
|
1377
|
+
* Combines name, description, and parameter schema into a formatted instruction.
|
|
1378
|
+
*
|
|
1379
|
+
* @param optionsOrArgPrefix - Optional custom prefixes for examples, or just argPrefix string for backwards compatibility
|
|
1380
|
+
* @returns Formatted instruction string
|
|
1381
|
+
*/
|
|
1382
|
+
getInstruction(optionsOrArgPrefix) {
|
|
1383
|
+
const options = typeof optionsOrArgPrefix === "string" ? { argPrefix: optionsOrArgPrefix } : optionsOrArgPrefix;
|
|
1384
|
+
const parts = [];
|
|
1385
|
+
parts.push(this.description);
|
|
1386
|
+
if (this.parameterSchema) {
|
|
1387
|
+
const gadgetName = this.name ?? this.constructor.name;
|
|
1388
|
+
validateGadgetSchema(this.parameterSchema, gadgetName);
|
|
1389
|
+
const jsonSchema = schemaToJSONSchema(this.parameterSchema, {
|
|
1390
|
+
target: "draft-7"
|
|
1391
|
+
});
|
|
1392
|
+
parts.push("\n\nParameters:");
|
|
1393
|
+
parts.push(formatSchemaAsPlainText(jsonSchema));
|
|
1394
|
+
}
|
|
1395
|
+
if (this.examples && this.examples.length > 0) {
|
|
1396
|
+
parts.push("\n\nExamples:");
|
|
1397
|
+
const effectiveArgPrefix = options?.argPrefix ?? GADGET_ARG_PREFIX;
|
|
1398
|
+
const effectiveStartPrefix = options?.startPrefix ?? GADGET_START_PREFIX;
|
|
1399
|
+
const effectiveEndPrefix = options?.endPrefix ?? GADGET_END_PREFIX;
|
|
1400
|
+
const gadgetName = this.name || this.constructor.name;
|
|
1401
|
+
this.examples.forEach((example, index) => {
|
|
1402
|
+
if (index > 0) {
|
|
1403
|
+
parts.push("");
|
|
1404
|
+
parts.push("---");
|
|
1405
|
+
parts.push("");
|
|
1406
|
+
}
|
|
1407
|
+
if (example.comment) {
|
|
1408
|
+
parts.push(`# ${example.comment}`);
|
|
1409
|
+
}
|
|
1410
|
+
parts.push(`${effectiveStartPrefix}${gadgetName}`);
|
|
1411
|
+
parts.push(
|
|
1412
|
+
formatParamsForBlockExample(
|
|
1413
|
+
example.params,
|
|
1414
|
+
"",
|
|
1415
|
+
effectiveArgPrefix
|
|
1416
|
+
)
|
|
1417
|
+
);
|
|
1418
|
+
parts.push(effectiveEndPrefix);
|
|
1419
|
+
if (example.output !== void 0) {
|
|
1420
|
+
parts.push("");
|
|
1421
|
+
parts.push("Expected Output:");
|
|
1422
|
+
parts.push(example.output);
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
return parts.join("\n");
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
});
|
|
1431
|
+
|
|
1432
|
+
// src/gadgets/create-gadget.ts
|
|
1433
|
+
function createGadget(config) {
|
|
1434
|
+
class DynamicGadget extends AbstractGadget {
|
|
1435
|
+
name = config.name;
|
|
1436
|
+
description = config.description;
|
|
1437
|
+
parameterSchema = config.schema;
|
|
1438
|
+
timeoutMs = config.timeoutMs;
|
|
1439
|
+
examples = config.examples;
|
|
1440
|
+
maxConcurrent = config.maxConcurrent;
|
|
1441
|
+
execute(params, ctx) {
|
|
1442
|
+
return config.execute(params, ctx);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return new DynamicGadget();
|
|
1446
|
+
}
|
|
1447
|
+
var init_create_gadget = __esm({
|
|
1448
|
+
"src/gadgets/create-gadget.ts"() {
|
|
1449
|
+
"use strict";
|
|
1450
|
+
init_gadget();
|
|
1451
|
+
}
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
// src/mcp/errors.ts
|
|
1455
|
+
var McpError, McpUntrustedCommandError, McpConnectError, McpToolCallError, McpTimeoutError, JsonSchemaConversionError;
|
|
1456
|
+
var init_errors = __esm({
|
|
1457
|
+
"src/mcp/errors.ts"() {
|
|
1458
|
+
"use strict";
|
|
1459
|
+
McpError = class extends Error {
|
|
1460
|
+
serverName;
|
|
1461
|
+
constructor(message, serverName) {
|
|
1462
|
+
super(message);
|
|
1463
|
+
this.name = "McpError";
|
|
1464
|
+
this.serverName = serverName;
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
McpUntrustedCommandError = class extends McpError {
|
|
1468
|
+
command;
|
|
1469
|
+
constructor(command, serverName) {
|
|
1470
|
+
super(
|
|
1471
|
+
`Refusing to spawn MCP stdio command "${command}" because its basename is not in the default allowlist. To opt in, set { trust: true } on the server spec (library), or trust = true in your TOML mcp.servers block, or pass --mcp-trust ${serverName ?? "<name>"} on the CLI. See https://llmist.dev/library/advanced/mcp-security/ for context (CVE-2026-30623).`,
|
|
1472
|
+
serverName
|
|
1473
|
+
);
|
|
1474
|
+
this.name = "McpUntrustedCommandError";
|
|
1475
|
+
this.command = command;
|
|
1476
|
+
}
|
|
1477
|
+
};
|
|
1478
|
+
McpConnectError = class extends McpError {
|
|
1479
|
+
cause;
|
|
1480
|
+
constructor(message, opts) {
|
|
1481
|
+
super(message, opts?.serverName);
|
|
1482
|
+
this.name = "McpConnectError";
|
|
1483
|
+
this.cause = opts?.cause;
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
McpToolCallError = class extends McpError {
|
|
1487
|
+
toolName;
|
|
1488
|
+
cause;
|
|
1489
|
+
constructor(toolName, message, opts) {
|
|
1490
|
+
super(message, opts?.serverName);
|
|
1491
|
+
this.name = "McpToolCallError";
|
|
1492
|
+
this.toolName = toolName;
|
|
1493
|
+
this.cause = opts?.cause;
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
McpTimeoutError = class extends McpError {
|
|
1497
|
+
operation;
|
|
1498
|
+
timeoutMs;
|
|
1499
|
+
constructor(operation, timeoutMs, serverName) {
|
|
1500
|
+
super(
|
|
1501
|
+
`MCP operation "${operation}" on server "${serverName ?? "<unknown>"}" timed out after ${timeoutMs}ms`,
|
|
1502
|
+
serverName
|
|
1503
|
+
);
|
|
1504
|
+
this.name = "McpTimeoutError";
|
|
1505
|
+
this.operation = operation;
|
|
1506
|
+
this.timeoutMs = timeoutMs;
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
JsonSchemaConversionError = class extends Error {
|
|
1510
|
+
schemaFragment;
|
|
1511
|
+
reason;
|
|
1512
|
+
constructor(reason, schemaFragment) {
|
|
1513
|
+
super(`JSON Schema \u2192 Zod conversion failed: ${reason}`);
|
|
1514
|
+
this.name = "JsonSchemaConversionError";
|
|
1515
|
+
this.reason = reason;
|
|
1516
|
+
this.schemaFragment = schemaFragment;
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
// src/mcp/allowlist.ts
|
|
1523
|
+
import path from "path";
|
|
1524
|
+
function assertCommandAllowed(command, trusted, customAllowlist) {
|
|
1525
|
+
if (!command || typeof command !== "string") {
|
|
1526
|
+
throw new McpUntrustedCommandError(String(command));
|
|
1527
|
+
}
|
|
1528
|
+
if (WHITESPACE_OR_META_RE.test(command)) {
|
|
1529
|
+
throw new McpUntrustedCommandError(command);
|
|
1530
|
+
}
|
|
1531
|
+
if (trusted) return;
|
|
1532
|
+
const allowlist = customAllowlist ?? DEFAULT_MCP_COMMAND_ALLOWLIST;
|
|
1533
|
+
const base = path.basename(command);
|
|
1534
|
+
if (!allowlist.has(base)) {
|
|
1535
|
+
throw new McpUntrustedCommandError(command);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
var DEFAULT_MCP_COMMAND_ALLOWLIST, WHITESPACE_OR_META_RE;
|
|
1539
|
+
var init_allowlist = __esm({
|
|
1540
|
+
"src/mcp/allowlist.ts"() {
|
|
1541
|
+
"use strict";
|
|
1542
|
+
init_errors();
|
|
1543
|
+
DEFAULT_MCP_COMMAND_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
1544
|
+
"npx",
|
|
1545
|
+
"node",
|
|
1546
|
+
"uvx",
|
|
1547
|
+
"uv",
|
|
1548
|
+
"python",
|
|
1549
|
+
"python3",
|
|
1550
|
+
"deno",
|
|
1551
|
+
"bun"
|
|
1552
|
+
]);
|
|
1553
|
+
WHITESPACE_OR_META_RE = /[\s;|&`$<>()'"\\]/;
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
|
|
1557
|
+
// src/mcp/client.ts
|
|
1558
|
+
async function loadSdk() {
|
|
1559
|
+
if (!cachedSdk) {
|
|
1560
|
+
cachedSdk = (async () => {
|
|
1561
|
+
const [client, stdio, http] = await Promise.all([
|
|
1562
|
+
import("@modelcontextprotocol/sdk/client/index.js"),
|
|
1563
|
+
import("@modelcontextprotocol/sdk/client/stdio.js"),
|
|
1564
|
+
import("@modelcontextprotocol/sdk/client/streamableHttp.js")
|
|
1565
|
+
]);
|
|
1566
|
+
return {
|
|
1567
|
+
Client: client.Client,
|
|
1568
|
+
StdioClientTransport: stdio.StdioClientTransport,
|
|
1569
|
+
StreamableHTTPClientTransport: http.StreamableHTTPClientTransport
|
|
1570
|
+
};
|
|
1571
|
+
})();
|
|
1572
|
+
}
|
|
1573
|
+
return cachedSdk;
|
|
1574
|
+
}
|
|
1575
|
+
var cachedSdk, DEFAULT_CLIENT_INFO, McpClient;
|
|
1576
|
+
var init_client = __esm({
|
|
1577
|
+
"src/mcp/client.ts"() {
|
|
1578
|
+
"use strict";
|
|
1579
|
+
init_allowlist();
|
|
1580
|
+
init_errors();
|
|
1581
|
+
cachedSdk = null;
|
|
1582
|
+
DEFAULT_CLIENT_INFO = { name: "llmist", version: "0.0.0" };
|
|
1583
|
+
McpClient = class {
|
|
1584
|
+
constructor(spec, opts) {
|
|
1585
|
+
this.spec = spec;
|
|
1586
|
+
this.injectedTransport = opts?.transport;
|
|
1587
|
+
this.clientInfo = opts?.clientInfo ?? DEFAULT_CLIENT_INFO;
|
|
1588
|
+
}
|
|
1589
|
+
sdkClient = null;
|
|
1590
|
+
spawnedPid = null;
|
|
1591
|
+
closed = false;
|
|
1592
|
+
injectedTransport;
|
|
1593
|
+
clientInfo;
|
|
1594
|
+
get serverName() {
|
|
1595
|
+
return this.spec.name;
|
|
1596
|
+
}
|
|
1597
|
+
get pid() {
|
|
1598
|
+
return this.spawnedPid;
|
|
1599
|
+
}
|
|
1600
|
+
get serverCapabilities() {
|
|
1601
|
+
if (!this.sdkClient) return null;
|
|
1602
|
+
return this.sdkClient.getServerCapabilities() ?? null;
|
|
1603
|
+
}
|
|
1604
|
+
async connect() {
|
|
1605
|
+
if (this.sdkClient) return;
|
|
1606
|
+
let transport;
|
|
1607
|
+
if (this.injectedTransport) {
|
|
1608
|
+
transport = this.injectedTransport;
|
|
1609
|
+
} else if (this.spec.transport === "stdio") {
|
|
1610
|
+
assertCommandAllowed(this.spec.command, this.spec.trust === true);
|
|
1611
|
+
const { StdioClientTransport } = await loadSdk();
|
|
1612
|
+
const stdioTransport = new StdioClientTransport({
|
|
1613
|
+
command: this.spec.command,
|
|
1614
|
+
args: this.spec.args,
|
|
1615
|
+
env: this.spec.env
|
|
1616
|
+
});
|
|
1617
|
+
transport = stdioTransport;
|
|
1618
|
+
this.spawnedPid = null;
|
|
1619
|
+
} else {
|
|
1620
|
+
const { StreamableHTTPClientTransport } = await loadSdk();
|
|
1621
|
+
let url;
|
|
1622
|
+
try {
|
|
1623
|
+
url = new URL(this.spec.url);
|
|
1624
|
+
} catch (err) {
|
|
1625
|
+
throw new McpConnectError(
|
|
1626
|
+
`MCP server "${this.spec.name}" has an invalid URL: ${err.message}`,
|
|
1627
|
+
{ serverName: this.spec.name, cause: err }
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
transport = new StreamableHTTPClientTransport(url, {
|
|
1631
|
+
requestInit: this.spec.headers ? { headers: this.spec.headers } : void 0
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
const { Client } = await loadSdk();
|
|
1635
|
+
const client = new Client(this.clientInfo, { capabilities: {} });
|
|
1636
|
+
try {
|
|
1637
|
+
await this.withTimeout(() => client.connect(transport), "connect");
|
|
1638
|
+
} catch (err) {
|
|
1639
|
+
throw new McpConnectError(
|
|
1640
|
+
`Failed to connect to MCP server "${this.spec.name}": ${err.message}`,
|
|
1641
|
+
{ serverName: this.spec.name, cause: err }
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
this.sdkClient = client;
|
|
1645
|
+
const maybePid = transport.pid;
|
|
1646
|
+
if (typeof maybePid === "number") {
|
|
1647
|
+
this.spawnedPid = maybePid;
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
async listTools() {
|
|
1651
|
+
const client = this.requireClient();
|
|
1652
|
+
const res = await this.withTimeout(() => client.listTools(), "tools/list");
|
|
1653
|
+
return res.tools.map((t) => ({
|
|
1654
|
+
name: t.name,
|
|
1655
|
+
description: t.description,
|
|
1656
|
+
inputSchema: t.inputSchema
|
|
1657
|
+
}));
|
|
1658
|
+
}
|
|
1659
|
+
async callTool(name, args) {
|
|
1660
|
+
const client = this.requireClient();
|
|
1661
|
+
try {
|
|
1662
|
+
const res = await this.withTimeout(
|
|
1663
|
+
() => client.callTool({
|
|
1664
|
+
name,
|
|
1665
|
+
arguments: args ?? {}
|
|
1666
|
+
}),
|
|
1667
|
+
`tools/call ${name}`
|
|
1668
|
+
);
|
|
1669
|
+
return {
|
|
1670
|
+
content: res.content ?? [],
|
|
1671
|
+
isError: res.isError
|
|
1672
|
+
};
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
throw new McpToolCallError(
|
|
1675
|
+
name,
|
|
1676
|
+
`MCP tool call "${name}" on server "${this.spec.name}" failed: ${err.message}`,
|
|
1677
|
+
{ serverName: this.spec.name, cause: err }
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
async listPrompts() {
|
|
1682
|
+
const client = this.requireClient();
|
|
1683
|
+
if (!client.listPrompts) {
|
|
1684
|
+
return [];
|
|
1685
|
+
}
|
|
1686
|
+
const listPrompts = client.listPrompts.bind(client);
|
|
1687
|
+
const res = await this.withTimeout(() => listPrompts(), "prompts/list");
|
|
1688
|
+
return res.prompts.map((p) => ({
|
|
1689
|
+
name: p.name,
|
|
1690
|
+
description: p.description,
|
|
1691
|
+
arguments: p.arguments
|
|
1692
|
+
}));
|
|
1693
|
+
}
|
|
1694
|
+
async getPrompt(name, args) {
|
|
1695
|
+
const client = this.requireClient();
|
|
1696
|
+
if (!client.getPrompt) {
|
|
1697
|
+
throw new McpToolCallError(name, "Server has no getPrompt method", {
|
|
1698
|
+
serverName: this.spec.name
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
const getPrompt = client.getPrompt.bind(client);
|
|
1702
|
+
try {
|
|
1703
|
+
const res = await this.withTimeout(
|
|
1704
|
+
() => getPrompt({ name, arguments: args ?? {} }),
|
|
1705
|
+
`prompts/get ${name}`
|
|
1706
|
+
);
|
|
1707
|
+
return {
|
|
1708
|
+
description: res.description,
|
|
1709
|
+
messages: res.messages.map((m) => ({
|
|
1710
|
+
role: m.role,
|
|
1711
|
+
content: m.content
|
|
1712
|
+
}))
|
|
1713
|
+
};
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
throw new McpToolCallError(
|
|
1716
|
+
name,
|
|
1717
|
+
`MCP prompts/get "${name}" on server "${this.spec.name}" failed: ${err.message}`,
|
|
1718
|
+
{ serverName: this.spec.name, cause: err }
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
async close() {
|
|
1723
|
+
if (this.closed) return;
|
|
1724
|
+
this.closed = true;
|
|
1725
|
+
if (this.sdkClient) {
|
|
1726
|
+
try {
|
|
1727
|
+
await this.sdkClient.close();
|
|
1728
|
+
} catch {
|
|
1729
|
+
}
|
|
1730
|
+
this.sdkClient = null;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
requireClient() {
|
|
1734
|
+
if (!this.sdkClient) {
|
|
1735
|
+
throw new McpConnectError(
|
|
1736
|
+
`MCP client for server "${this.spec.name}" is not connected. Call connect() first.`,
|
|
1737
|
+
{ serverName: this.spec.name }
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
return this.sdkClient;
|
|
1741
|
+
}
|
|
1742
|
+
async withTimeout(fn, operation) {
|
|
1743
|
+
const timeoutMs = this.spec.timeoutMs;
|
|
1744
|
+
if (timeoutMs === void 0 || timeoutMs <= 0) {
|
|
1745
|
+
return fn();
|
|
1746
|
+
}
|
|
1747
|
+
return new Promise((resolve, reject) => {
|
|
1748
|
+
let settled = false;
|
|
1749
|
+
const timeoutId = setTimeout(() => {
|
|
1750
|
+
if (settled) return;
|
|
1751
|
+
settled = true;
|
|
1752
|
+
reject(new McpTimeoutError(operation, timeoutMs, this.spec.name));
|
|
1753
|
+
}, timeoutMs);
|
|
1754
|
+
fn().then((result) => {
|
|
1755
|
+
if (settled) return;
|
|
1756
|
+
settled = true;
|
|
1757
|
+
clearTimeout(timeoutId);
|
|
1758
|
+
resolve(result);
|
|
1759
|
+
}).catch((err) => {
|
|
1760
|
+
if (settled) return;
|
|
1761
|
+
settled = true;
|
|
1762
|
+
clearTimeout(timeoutId);
|
|
1763
|
+
reject(err);
|
|
1764
|
+
});
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
// src/mcp/lifecycle.ts
|
|
1772
|
+
var McpLifecycle;
|
|
1773
|
+
var init_lifecycle = __esm({
|
|
1774
|
+
"src/mcp/lifecycle.ts"() {
|
|
1775
|
+
"use strict";
|
|
1776
|
+
init_logger();
|
|
1777
|
+
McpLifecycle = class {
|
|
1778
|
+
clients = [];
|
|
1779
|
+
closing = null;
|
|
1780
|
+
signalHandlersInstalled = false;
|
|
1781
|
+
sigtermHandler = null;
|
|
1782
|
+
sigintHandler = null;
|
|
1783
|
+
get size() {
|
|
1784
|
+
return this.clients.length;
|
|
1785
|
+
}
|
|
1786
|
+
register(client) {
|
|
1787
|
+
this.clients.push(client);
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* Attach SIGTERM/SIGINT handlers that close every registered client when
|
|
1791
|
+
* the parent process is asked to exit. Idempotent (double install is a
|
|
1792
|
+
* no-op) and removable via `removeSignalHandlers()`.
|
|
1793
|
+
*/
|
|
1794
|
+
installSignalHandlers() {
|
|
1795
|
+
if (this.signalHandlersInstalled) return;
|
|
1796
|
+
this.signalHandlersInstalled = true;
|
|
1797
|
+
this.sigtermHandler = () => {
|
|
1798
|
+
void this.closeAll();
|
|
1799
|
+
};
|
|
1800
|
+
this.sigintHandler = () => {
|
|
1801
|
+
void this.closeAll();
|
|
1802
|
+
};
|
|
1803
|
+
process.on("SIGTERM", this.sigtermHandler);
|
|
1804
|
+
process.on("SIGINT", this.sigintHandler);
|
|
1805
|
+
}
|
|
1806
|
+
removeSignalHandlers() {
|
|
1807
|
+
if (!this.signalHandlersInstalled) return;
|
|
1808
|
+
if (this.sigtermHandler) process.off("SIGTERM", this.sigtermHandler);
|
|
1809
|
+
if (this.sigintHandler) process.off("SIGINT", this.sigintHandler);
|
|
1810
|
+
this.sigtermHandler = null;
|
|
1811
|
+
this.sigintHandler = null;
|
|
1812
|
+
this.signalHandlersInstalled = false;
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Close every registered client in parallel. Errors from individual close()
|
|
1816
|
+
* calls are swallowed (logged via console.warn) — a teardown path must not
|
|
1817
|
+
* throw because that would mask the original reason the agent is shutting
|
|
1818
|
+
* down. Idempotent: concurrent calls all return the same in-flight promise.
|
|
1819
|
+
*/
|
|
1820
|
+
async closeAll() {
|
|
1821
|
+
if (this.closing) return this.closing;
|
|
1822
|
+
const toClose = this.clients;
|
|
1823
|
+
this.clients = [];
|
|
1824
|
+
this.closing = (async () => {
|
|
1825
|
+
const results = await Promise.allSettled(toClose.map((c) => c.close()));
|
|
1826
|
+
for (const r of results) {
|
|
1827
|
+
if (r.status === "rejected") {
|
|
1828
|
+
defaultLogger.debug("MCP client close failed during teardown:", r.reason);
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
})();
|
|
1832
|
+
try {
|
|
1833
|
+
await this.closing;
|
|
1834
|
+
} finally {
|
|
1835
|
+
this.closing = null;
|
|
1836
|
+
this.removeSignalHandlers();
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
// src/gadgets/helpers.ts
|
|
1844
|
+
function gadgetSuccess(data = {}) {
|
|
1845
|
+
return JSON.stringify({ success: true, ...data });
|
|
1846
|
+
}
|
|
1847
|
+
function gadgetError(message, details) {
|
|
1848
|
+
return JSON.stringify({ error: message, ...details });
|
|
1849
|
+
}
|
|
1850
|
+
function getErrorMessage(error) {
|
|
1851
|
+
return error instanceof Error ? error.message : String(error);
|
|
1852
|
+
}
|
|
1853
|
+
function withErrorHandling(execute) {
|
|
1854
|
+
return async (params, ctx) => {
|
|
1855
|
+
try {
|
|
1856
|
+
return await execute(params, ctx);
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
return gadgetError(getErrorMessage(error));
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
function createMediaOutput(kind, data, mimeType, options) {
|
|
1863
|
+
const buffer = data instanceof Buffer ? data : Buffer.from(data);
|
|
1864
|
+
return {
|
|
1865
|
+
kind,
|
|
1866
|
+
data: buffer.toString("base64"),
|
|
1867
|
+
mimeType,
|
|
1868
|
+
description: options?.description,
|
|
1869
|
+
metadata: options?.metadata,
|
|
1870
|
+
fileName: options?.fileName
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function resultWithMedia(result, media, cost) {
|
|
1874
|
+
if (media.length === 0) {
|
|
1875
|
+
throw new Error("resultWithMedia: media array cannot be empty");
|
|
1876
|
+
}
|
|
1877
|
+
return {
|
|
1878
|
+
result,
|
|
1879
|
+
media,
|
|
1880
|
+
cost
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
function resultWithImage(result, imageData, options) {
|
|
1884
|
+
const buffer = imageData instanceof Buffer ? imageData : Buffer.from(imageData);
|
|
1885
|
+
const mimeType = options?.mimeType ?? detectImageMimeType(buffer);
|
|
1886
|
+
if (!mimeType) {
|
|
1887
|
+
throw new Error(
|
|
1888
|
+
"Could not detect image MIME type. Please provide mimeType explicitly in options."
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
return {
|
|
1892
|
+
result,
|
|
1893
|
+
media: [
|
|
1894
|
+
{
|
|
1895
|
+
kind: "image",
|
|
1896
|
+
data: buffer.toString("base64"),
|
|
1897
|
+
mimeType,
|
|
1898
|
+
description: options?.description,
|
|
1899
|
+
metadata: options?.metadata,
|
|
1900
|
+
fileName: options?.fileName
|
|
1901
|
+
}
|
|
1902
|
+
],
|
|
1903
|
+
cost: options?.cost
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
function resultWithImages(result, images, cost) {
|
|
1907
|
+
if (images.length === 0) {
|
|
1908
|
+
throw new Error("resultWithImages: images array cannot be empty");
|
|
1909
|
+
}
|
|
1910
|
+
const media = images.map((img, index) => {
|
|
1911
|
+
const buffer = img.data instanceof Buffer ? img.data : Buffer.from(img.data);
|
|
1912
|
+
const mimeType = img.mimeType ?? detectImageMimeType(buffer);
|
|
1913
|
+
if (!mimeType) {
|
|
1914
|
+
throw new Error(
|
|
1915
|
+
`Could not detect MIME type for image at index ${index}. Please provide mimeType explicitly.`
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
return {
|
|
1919
|
+
kind: "image",
|
|
1920
|
+
data: buffer.toString("base64"),
|
|
1921
|
+
mimeType,
|
|
1922
|
+
description: img.description,
|
|
1923
|
+
metadata: img.metadata,
|
|
1924
|
+
fileName: img.fileName
|
|
1925
|
+
};
|
|
1926
|
+
});
|
|
1927
|
+
return { result, media, cost };
|
|
1928
|
+
}
|
|
1929
|
+
function resultWithAudio(result, audioData, options) {
|
|
1930
|
+
const buffer = audioData instanceof Buffer ? audioData : Buffer.from(audioData);
|
|
1931
|
+
const mimeType = options?.mimeType ?? detectAudioMimeType(buffer);
|
|
1932
|
+
if (!mimeType) {
|
|
1933
|
+
throw new Error(
|
|
1934
|
+
"Could not detect audio MIME type. Please provide mimeType explicitly in options."
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
const metadata = options?.durationMs ? { durationMs: options.durationMs } : void 0;
|
|
1938
|
+
return {
|
|
1939
|
+
result,
|
|
1940
|
+
media: [
|
|
1941
|
+
{
|
|
1942
|
+
kind: "audio",
|
|
1943
|
+
data: buffer.toString("base64"),
|
|
1944
|
+
mimeType,
|
|
1945
|
+
description: options?.description,
|
|
1946
|
+
metadata,
|
|
1947
|
+
fileName: options?.fileName
|
|
1948
|
+
}
|
|
1949
|
+
],
|
|
1950
|
+
cost: options?.cost
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
function resultWithFile(result, fileData, mimeType, options) {
|
|
1954
|
+
const buffer = fileData instanceof Buffer ? fileData : Buffer.from(fileData);
|
|
1955
|
+
return {
|
|
1956
|
+
result,
|
|
1957
|
+
media: [
|
|
1958
|
+
{
|
|
1959
|
+
kind: "file",
|
|
1960
|
+
data: buffer.toString("base64"),
|
|
1961
|
+
mimeType,
|
|
1962
|
+
description: options?.description,
|
|
1963
|
+
fileName: options?.fileName
|
|
1964
|
+
}
|
|
1965
|
+
],
|
|
1966
|
+
cost: options?.cost
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
var init_helpers = __esm({
|
|
1970
|
+
"src/gadgets/helpers.ts"() {
|
|
1971
|
+
"use strict";
|
|
1972
|
+
init_input_content();
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
// src/mcp/json-schema-to-zod.ts
|
|
1977
|
+
import { z as z3 } from "zod";
|
|
1978
|
+
function jsonSchemaToZod(schema) {
|
|
1979
|
+
if (!schema || typeof schema !== "object") {
|
|
1980
|
+
return z3.unknown();
|
|
1981
|
+
}
|
|
1982
|
+
if (schema.$ref) {
|
|
1983
|
+
throw new JsonSchemaConversionError("$ref is not supported in MCP tool schemas", schema);
|
|
1984
|
+
}
|
|
1985
|
+
if (schema.allOf) {
|
|
1986
|
+
throw new JsonSchemaConversionError(
|
|
1987
|
+
"allOf is not supported (MCP tools should use a single composed schema)",
|
|
1988
|
+
schema
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
const union = schema.oneOf ?? schema.anyOf;
|
|
1992
|
+
if (union) {
|
|
1993
|
+
if (!Array.isArray(union) || union.length < 2) {
|
|
1994
|
+
throw new JsonSchemaConversionError("oneOf/anyOf must have at least two members", schema);
|
|
1995
|
+
}
|
|
1996
|
+
const branches = union.map((m) => jsonSchemaToZod(m));
|
|
1997
|
+
return applyDecorators(z3.union(branches), schema);
|
|
1998
|
+
}
|
|
1999
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
2000
|
+
if (type === void 0 && schema.enum && Array.isArray(schema.enum)) {
|
|
2001
|
+
return applyDecorators(buildEnum(schema.enum), schema);
|
|
2002
|
+
}
|
|
2003
|
+
if (type === void 0) {
|
|
2004
|
+
return applyDecorators(z3.unknown(), schema);
|
|
2005
|
+
}
|
|
2006
|
+
switch (type) {
|
|
2007
|
+
case "string": {
|
|
2008
|
+
let s;
|
|
2009
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
2010
|
+
s = buildEnum(schema.enum);
|
|
2011
|
+
} else {
|
|
2012
|
+
s = z3.string();
|
|
2013
|
+
}
|
|
2014
|
+
return applyDecorators(s, schema);
|
|
2015
|
+
}
|
|
2016
|
+
case "number":
|
|
2017
|
+
return applyDecorators(z3.number(), schema);
|
|
2018
|
+
case "integer":
|
|
2019
|
+
return applyDecorators(z3.number().int(), schema);
|
|
2020
|
+
case "boolean":
|
|
2021
|
+
return applyDecorators(z3.boolean(), schema);
|
|
2022
|
+
case "null":
|
|
2023
|
+
return applyDecorators(z3.null(), schema);
|
|
2024
|
+
case "array": {
|
|
2025
|
+
const items = schema.items;
|
|
2026
|
+
if (Array.isArray(items)) {
|
|
2027
|
+
throw new JsonSchemaConversionError("tuple-style items arrays are not supported", schema);
|
|
2028
|
+
}
|
|
2029
|
+
const inner = items ? jsonSchemaToZod(items) : z3.unknown();
|
|
2030
|
+
return applyDecorators(z3.array(inner), schema);
|
|
2031
|
+
}
|
|
2032
|
+
case "object": {
|
|
2033
|
+
const props = schema.properties ?? {};
|
|
2034
|
+
const required = new Set(schema.required ?? []);
|
|
2035
|
+
const keys = Object.keys(props);
|
|
2036
|
+
if (keys.length === 0) {
|
|
2037
|
+
return applyDecorators(z3.record(z3.string(), z3.unknown()), schema);
|
|
2038
|
+
}
|
|
2039
|
+
const shape = {};
|
|
2040
|
+
for (const key of keys) {
|
|
2041
|
+
const inner = jsonSchemaToZod(props[key]);
|
|
2042
|
+
shape[key] = required.has(key) ? inner : inner.optional();
|
|
2043
|
+
}
|
|
2044
|
+
return applyDecorators(z3.object(shape), schema);
|
|
2045
|
+
}
|
|
2046
|
+
default:
|
|
2047
|
+
throw new JsonSchemaConversionError(`unknown JSON Schema type "${type}"`, schema);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
function buildEnum(values) {
|
|
2051
|
+
if (values.every((v) => typeof v === "string")) {
|
|
2052
|
+
const literals2 = values;
|
|
2053
|
+
if (literals2.length === 0) {
|
|
2054
|
+
throw new JsonSchemaConversionError("enum cannot be empty", values);
|
|
2055
|
+
}
|
|
2056
|
+
return z3.enum(literals2);
|
|
2057
|
+
}
|
|
2058
|
+
const literals = values.map((v) => z3.literal(v));
|
|
2059
|
+
if (literals.length === 0) {
|
|
2060
|
+
throw new JsonSchemaConversionError("enum cannot be empty", values);
|
|
2061
|
+
}
|
|
2062
|
+
if (literals.length === 1) {
|
|
2063
|
+
return literals[0];
|
|
2064
|
+
}
|
|
2065
|
+
return z3.union(literals);
|
|
2066
|
+
}
|
|
2067
|
+
function applyDecorators(base, schema) {
|
|
2068
|
+
let s = base;
|
|
2069
|
+
if (schema.nullable === true) {
|
|
2070
|
+
s = s.nullable();
|
|
2071
|
+
}
|
|
2072
|
+
if (schema.description) {
|
|
2073
|
+
s = s.describe(schema.description);
|
|
2074
|
+
}
|
|
2075
|
+
if (schema.default !== void 0) {
|
|
2076
|
+
s = s.default(schema.default);
|
|
2077
|
+
}
|
|
2078
|
+
return s;
|
|
2079
|
+
}
|
|
2080
|
+
var init_json_schema_to_zod = __esm({
|
|
2081
|
+
"src/mcp/json-schema-to-zod.ts"() {
|
|
2082
|
+
"use strict";
|
|
2083
|
+
init_errors();
|
|
2084
|
+
}
|
|
2085
|
+
});
|
|
2086
|
+
|
|
2087
|
+
// src/mcp/tool-adapter.ts
|
|
2088
|
+
import { z as z4 } from "zod";
|
|
2089
|
+
function mcpToolToGadget(tool, client, opts) {
|
|
2090
|
+
const gadgetName = (opts?.prefix ?? "") + tool.name;
|
|
2091
|
+
const schema = buildSchema(tool.inputSchema);
|
|
2092
|
+
const description = tool.description ?? `MCP tool "${tool.name}" from server "${client.serverName}"`;
|
|
2093
|
+
return createGadget({
|
|
2094
|
+
name: gadgetName,
|
|
2095
|
+
description,
|
|
2096
|
+
schema,
|
|
2097
|
+
execute: async (params) => {
|
|
2098
|
+
const result = await client.callTool(tool.name, params);
|
|
2099
|
+
return mcpResultToGadgetReturn(result, tool.name);
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
function buildSchema(inputSchema) {
|
|
2104
|
+
if (!inputSchema) {
|
|
2105
|
+
return z4.object({});
|
|
2106
|
+
}
|
|
2107
|
+
const converted = jsonSchemaToZod(inputSchema);
|
|
2108
|
+
if (!(converted instanceof z4.ZodObject) && !(converted instanceof z4.ZodRecord)) {
|
|
2109
|
+
return z4.object({}).passthrough();
|
|
2110
|
+
}
|
|
2111
|
+
return converted;
|
|
2112
|
+
}
|
|
2113
|
+
function mcpResultToGadgetReturn(result, toolName) {
|
|
2114
|
+
const blocks = result.content ?? [];
|
|
2115
|
+
const textParts = [];
|
|
2116
|
+
const media = [];
|
|
2117
|
+
for (const block of blocks) {
|
|
2118
|
+
const kind = block.type;
|
|
2119
|
+
if (kind === "text" && typeof block.text === "string") {
|
|
2120
|
+
textParts.push(block.text);
|
|
2121
|
+
} else if (kind === "image") {
|
|
2122
|
+
const b = block;
|
|
2123
|
+
media.push({ kind: "image", data: b.data, mimeType: b.mimeType });
|
|
2124
|
+
} else if (kind === "audio") {
|
|
2125
|
+
const b = block;
|
|
2126
|
+
media.push({ kind: "audio", data: b.data, mimeType: b.mimeType });
|
|
2127
|
+
} else {
|
|
2128
|
+
try {
|
|
2129
|
+
textParts.push(JSON.stringify(block));
|
|
2130
|
+
} catch {
|
|
2131
|
+
textParts.push(String(block));
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
const text2 = textParts.join("\n");
|
|
2136
|
+
if (result.isError) {
|
|
2137
|
+
throw new Error(
|
|
2138
|
+
text2 ? text2 : `MCP tool "${toolName}" returned an error result with no text content`
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
if (media.length === 0) {
|
|
2142
|
+
return text2;
|
|
2143
|
+
}
|
|
2144
|
+
if (media.length === 1 && media[0].kind === "image") {
|
|
2145
|
+
const img = media[0];
|
|
2146
|
+
return resultWithImage(text2, Buffer.from(img.data, "base64"), {
|
|
2147
|
+
mimeType: img.mimeType
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
return {
|
|
2151
|
+
result: text2,
|
|
2152
|
+
media: media.map((m) => ({
|
|
2153
|
+
kind: m.kind,
|
|
2154
|
+
data: m.data,
|
|
2155
|
+
mimeType: m.mimeType
|
|
2156
|
+
}))
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
var init_tool_adapter = __esm({
|
|
2160
|
+
"src/mcp/tool-adapter.ts"() {
|
|
2161
|
+
"use strict";
|
|
2162
|
+
init_create_gadget();
|
|
2163
|
+
init_helpers();
|
|
2164
|
+
init_json_schema_to_zod();
|
|
2165
|
+
}
|
|
2166
|
+
});
|
|
2167
|
+
|
|
2168
|
+
export {
|
|
2169
|
+
__require,
|
|
2170
|
+
__esm,
|
|
2171
|
+
__export,
|
|
2172
|
+
__toCommonJS,
|
|
2173
|
+
GADGET_START_PREFIX,
|
|
2174
|
+
GADGET_END_PREFIX,
|
|
2175
|
+
GADGET_ARG_PREFIX,
|
|
2176
|
+
DEFAULT_GADGET_OUTPUT_LIMIT,
|
|
2177
|
+
DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT,
|
|
2178
|
+
CHARS_PER_TOKEN,
|
|
2179
|
+
FALLBACK_CONTEXT_WINDOW,
|
|
2180
|
+
init_constants,
|
|
2181
|
+
isTextPart,
|
|
2182
|
+
isImagePart,
|
|
2183
|
+
isAudioPart,
|
|
2184
|
+
text,
|
|
2185
|
+
imageFromBase64,
|
|
2186
|
+
imageFromUrl,
|
|
2187
|
+
detectImageMimeType,
|
|
2188
|
+
detectAudioMimeType,
|
|
2189
|
+
toBase64,
|
|
2190
|
+
imageFromBuffer,
|
|
2191
|
+
audioFromBase64,
|
|
2192
|
+
audioFromBuffer,
|
|
2193
|
+
isDataUrl,
|
|
2194
|
+
parseDataUrl,
|
|
2195
|
+
init_input_content,
|
|
2196
|
+
DEFAULT_HINTS,
|
|
2197
|
+
DEFAULT_PROMPTS,
|
|
2198
|
+
resolvePromptTemplate,
|
|
2199
|
+
resolveRulesTemplate,
|
|
2200
|
+
resolveHintTemplate,
|
|
2201
|
+
init_prompt_config,
|
|
2202
|
+
normalizeMessageContent,
|
|
2203
|
+
extractMessageText,
|
|
2204
|
+
LLMMessageBuilder,
|
|
2205
|
+
init_messages,
|
|
2206
|
+
TaskCompletionSignal,
|
|
2207
|
+
HumanInputRequiredException,
|
|
2208
|
+
TimeoutException,
|
|
2209
|
+
AbortException,
|
|
2210
|
+
BudgetPricingUnavailableError,
|
|
2211
|
+
init_exceptions,
|
|
2212
|
+
createLogger,
|
|
2213
|
+
defaultLogger,
|
|
2214
|
+
init_logger,
|
|
2215
|
+
schemaToJSONSchema,
|
|
2216
|
+
init_schema_to_json,
|
|
2217
|
+
validateGadgetSchema,
|
|
2218
|
+
init_schema_validator,
|
|
2219
|
+
AbstractGadget,
|
|
2220
|
+
init_gadget,
|
|
2221
|
+
createGadget,
|
|
2222
|
+
init_create_gadget,
|
|
2223
|
+
McpError,
|
|
2224
|
+
McpUntrustedCommandError,
|
|
2225
|
+
McpConnectError,
|
|
2226
|
+
McpToolCallError,
|
|
2227
|
+
JsonSchemaConversionError,
|
|
2228
|
+
init_errors,
|
|
2229
|
+
DEFAULT_MCP_COMMAND_ALLOWLIST,
|
|
2230
|
+
assertCommandAllowed,
|
|
2231
|
+
init_allowlist,
|
|
2232
|
+
McpClient,
|
|
2233
|
+
init_client,
|
|
2234
|
+
McpLifecycle,
|
|
2235
|
+
init_lifecycle,
|
|
2236
|
+
gadgetSuccess,
|
|
2237
|
+
gadgetError,
|
|
2238
|
+
getErrorMessage,
|
|
2239
|
+
withErrorHandling,
|
|
2240
|
+
createMediaOutput,
|
|
2241
|
+
resultWithMedia,
|
|
2242
|
+
resultWithImage,
|
|
2243
|
+
resultWithImages,
|
|
2244
|
+
resultWithAudio,
|
|
2245
|
+
resultWithFile,
|
|
2246
|
+
init_helpers,
|
|
2247
|
+
jsonSchemaToZod,
|
|
2248
|
+
init_json_schema_to_zod,
|
|
2249
|
+
mcpToolToGadget,
|
|
2250
|
+
init_tool_adapter
|
|
2251
|
+
};
|
|
2252
|
+
//# sourceMappingURL=chunk-HM7PUGPA.js.map
|