llmist 17.2.1 → 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.
@@ -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