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.
package/dist/index.js CHANGED
@@ -1,29 +1,87 @@
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);
1
+ import {
2
+ AbortException,
3
+ AbstractGadget,
4
+ BudgetPricingUnavailableError,
5
+ CHARS_PER_TOKEN,
6
+ DEFAULT_GADGET_OUTPUT_LIMIT,
7
+ DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT,
8
+ DEFAULT_HINTS,
9
+ DEFAULT_MCP_COMMAND_ALLOWLIST,
10
+ DEFAULT_PROMPTS,
11
+ FALLBACK_CONTEXT_WINDOW,
12
+ GADGET_ARG_PREFIX,
13
+ GADGET_END_PREFIX,
14
+ GADGET_START_PREFIX,
15
+ HumanInputRequiredException,
16
+ JsonSchemaConversionError,
17
+ LLMMessageBuilder,
18
+ McpClient,
19
+ McpConnectError,
20
+ McpError,
21
+ McpLifecycle,
22
+ McpToolCallError,
23
+ McpUntrustedCommandError,
24
+ TaskCompletionSignal,
25
+ TimeoutException,
26
+ __esm,
27
+ __export,
28
+ __require,
29
+ __toCommonJS,
30
+ assertCommandAllowed,
31
+ audioFromBase64,
32
+ audioFromBuffer,
33
+ createGadget,
34
+ createLogger,
35
+ createMediaOutput,
36
+ defaultLogger,
37
+ detectAudioMimeType,
38
+ detectImageMimeType,
39
+ extractMessageText,
40
+ gadgetError,
41
+ gadgetSuccess,
42
+ getErrorMessage,
43
+ imageFromBase64,
44
+ imageFromBuffer,
45
+ imageFromUrl,
46
+ init_allowlist,
47
+ init_client,
48
+ init_constants,
49
+ init_create_gadget,
50
+ init_errors,
51
+ init_exceptions,
52
+ init_gadget,
53
+ init_helpers,
54
+ init_input_content,
55
+ init_json_schema_to_zod,
56
+ init_lifecycle,
57
+ init_logger,
58
+ init_messages,
59
+ init_prompt_config,
60
+ init_schema_to_json,
61
+ init_schema_validator,
62
+ init_tool_adapter,
63
+ isAudioPart,
64
+ isDataUrl,
65
+ isImagePart,
66
+ isTextPart,
67
+ jsonSchemaToZod,
68
+ mcpToolToGadget,
69
+ normalizeMessageContent,
70
+ parseDataUrl,
71
+ resolveHintTemplate,
72
+ resolvePromptTemplate,
73
+ resolveRulesTemplate,
74
+ resultWithAudio,
75
+ resultWithFile,
76
+ resultWithImage,
77
+ resultWithImages,
78
+ resultWithMedia,
79
+ schemaToJSONSchema,
80
+ text,
81
+ toBase64,
82
+ validateGadgetSchema,
83
+ withErrorHandling
84
+ } from "./chunk-HM7PUGPA.js";
27
85
 
28
86
  // src/core/execution-tree-aggregator.ts
29
87
  var ExecutionTreeAggregator;
@@ -803,679 +861,6 @@ var init_execution_tree = __esm({
803
861
  }
804
862
  });
805
863
 
806
- // src/core/constants.ts
807
- 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;
808
- var init_constants = __esm({
809
- "src/core/constants.ts"() {
810
- "use strict";
811
- GADGET_START_PREFIX = "!!!GADGET_START:";
812
- GADGET_END_PREFIX = "!!!GADGET_END";
813
- GADGET_ARG_PREFIX = "!!!ARG:";
814
- DEFAULT_GADGET_OUTPUT_LIMIT = true;
815
- DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT = 15;
816
- CHARS_PER_TOKEN = 2;
817
- FALLBACK_CONTEXT_WINDOW = 128e3;
818
- }
819
- });
820
-
821
- // src/core/input-content.ts
822
- function isTextPart(part) {
823
- return part.type === "text";
824
- }
825
- function isImagePart(part) {
826
- return part.type === "image";
827
- }
828
- function isAudioPart(part) {
829
- return part.type === "audio";
830
- }
831
- function text(content) {
832
- return { type: "text", text: content };
833
- }
834
- function imageFromBase64(data, mediaType) {
835
- return {
836
- type: "image",
837
- source: { type: "base64", mediaType, data }
838
- };
839
- }
840
- function imageFromUrl(url) {
841
- return {
842
- type: "image",
843
- source: { type: "url", url }
844
- };
845
- }
846
- function detectImageMimeType(data) {
847
- const bytes = data instanceof Buffer ? data : Buffer.from(data);
848
- for (const { bytes: magic, mimeType } of IMAGE_MAGIC_BYTES) {
849
- if (bytes.length >= magic.length) {
850
- let matches = true;
851
- for (let i = 0; i < magic.length; i++) {
852
- if (bytes[i] !== magic[i]) {
853
- matches = false;
854
- break;
855
- }
856
- }
857
- if (matches) {
858
- if (mimeType === "image/webp") {
859
- if (bytes.length >= 12) {
860
- const webpMarker = bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80;
861
- if (!webpMarker) continue;
862
- }
863
- }
864
- return mimeType;
865
- }
866
- }
867
- }
868
- return null;
869
- }
870
- function detectAudioMimeType(data) {
871
- const bytes = data instanceof Buffer ? data : Buffer.from(data);
872
- for (const { bytes: magic, mimeType } of AUDIO_MAGIC_BYTES) {
873
- if (bytes.length >= magic.length) {
874
- let matches = true;
875
- for (let i = 0; i < magic.length; i++) {
876
- if (bytes[i] !== magic[i]) {
877
- matches = false;
878
- break;
879
- }
880
- }
881
- if (matches) {
882
- if (mimeType === "audio/wav") {
883
- if (bytes.length >= 12) {
884
- const waveMarker = bytes[8] === 87 && bytes[9] === 65 && bytes[10] === 86 && bytes[11] === 69;
885
- if (!waveMarker) continue;
886
- }
887
- }
888
- return mimeType;
889
- }
890
- }
891
- }
892
- return null;
893
- }
894
- function toBase64(data) {
895
- if (typeof data === "string") {
896
- return data;
897
- }
898
- return Buffer.from(data).toString("base64");
899
- }
900
- function imageFromBuffer(buffer, mediaType) {
901
- const detectedType = mediaType ?? detectImageMimeType(buffer);
902
- if (!detectedType) {
903
- throw new Error(
904
- "Could not detect image MIME type. Please provide the mediaType parameter explicitly."
905
- );
906
- }
907
- return {
908
- type: "image",
909
- source: {
910
- type: "base64",
911
- mediaType: detectedType,
912
- data: toBase64(buffer)
913
- }
914
- };
915
- }
916
- function audioFromBase64(data, mediaType) {
917
- return {
918
- type: "audio",
919
- source: { type: "base64", mediaType, data }
920
- };
921
- }
922
- function audioFromBuffer(buffer, mediaType) {
923
- const detectedType = mediaType ?? detectAudioMimeType(buffer);
924
- if (!detectedType) {
925
- throw new Error(
926
- "Could not detect audio MIME type. Please provide the mediaType parameter explicitly."
927
- );
928
- }
929
- return {
930
- type: "audio",
931
- source: {
932
- type: "base64",
933
- mediaType: detectedType,
934
- data: toBase64(buffer)
935
- }
936
- };
937
- }
938
- function isDataUrl(input) {
939
- return input.startsWith("data:");
940
- }
941
- function parseDataUrl(url) {
942
- const match = url.match(/^data:([^;]+);base64,(.+)$/);
943
- if (!match) return null;
944
- return { mimeType: match[1], data: match[2] };
945
- }
946
- var IMAGE_MAGIC_BYTES, AUDIO_MAGIC_BYTES;
947
- var init_input_content = __esm({
948
- "src/core/input-content.ts"() {
949
- "use strict";
950
- IMAGE_MAGIC_BYTES = [
951
- { bytes: [255, 216, 255], mimeType: "image/jpeg" },
952
- { bytes: [137, 80, 78, 71], mimeType: "image/png" },
953
- { bytes: [71, 73, 70, 56], mimeType: "image/gif" },
954
- // WebP starts with RIFF....WEBP
955
- { bytes: [82, 73, 70, 70], mimeType: "image/webp" }
956
- ];
957
- AUDIO_MAGIC_BYTES = [
958
- // MP3 frame sync
959
- { bytes: [255, 251], mimeType: "audio/mp3" },
960
- { bytes: [255, 250], mimeType: "audio/mp3" },
961
- // ID3 tag (MP3)
962
- { bytes: [73, 68, 51], mimeType: "audio/mp3" },
963
- // OGG
964
- { bytes: [79, 103, 103, 83], mimeType: "audio/ogg" },
965
- // WAV (RIFF)
966
- { bytes: [82, 73, 70, 70], mimeType: "audio/wav" },
967
- // WebM
968
- { bytes: [26, 69, 223, 163], mimeType: "audio/webm" },
969
- // FLAC (fLaC)
970
- { bytes: [102, 76, 97, 67], mimeType: "audio/flac" }
971
- ];
972
- }
973
- });
974
-
975
- // src/core/prompt-config.ts
976
- function resolvePromptTemplate(template, defaultValue, context) {
977
- const resolved = template ?? defaultValue;
978
- return typeof resolved === "function" ? resolved(context) : resolved;
979
- }
980
- function resolveRulesTemplate(rules, context) {
981
- const resolved = rules ?? DEFAULT_PROMPTS.rules;
982
- if (Array.isArray(resolved)) {
983
- return resolved;
984
- }
985
- if (typeof resolved === "function") {
986
- const result = resolved(context);
987
- return Array.isArray(result) ? result : [result];
988
- }
989
- return [resolved];
990
- }
991
- function resolveHintTemplate(template, defaultValue, context) {
992
- const resolved = template ?? defaultValue;
993
- if (typeof resolved === "function") {
994
- return resolved(context);
995
- }
996
- return resolved.replace(/\{iteration\}/g, String(context.iteration)).replace(/\{maxIterations\}/g, String(context.maxIterations)).replace(/\{remaining\}/g, String(context.remaining));
997
- }
998
- var DEFAULT_HINTS, DEFAULT_PROMPTS;
999
- var init_prompt_config = __esm({
1000
- "src/core/prompt-config.ts"() {
1001
- "use strict";
1002
- DEFAULT_HINTS = {
1003
- parallelGadgetsHint: "Tip: You can call multiple gadgets in a single response for efficiency.",
1004
- iterationProgressHint: "[Iteration {iteration}/{maxIterations}] Plan your actions accordingly."
1005
- };
1006
- DEFAULT_PROMPTS = {
1007
- mainInstruction: [
1008
- "\u26A0\uFE0F CRITICAL: RESPOND ONLY WITH GADGET INVOCATIONS",
1009
- "DO NOT use function calling or tool calling",
1010
- "You must output the exact text markers shown below in plain text.",
1011
- "EACH MARKER MUST START WITH A NEWLINE."
1012
- ].join("\n"),
1013
- criticalUsage: "INVOKE gadgets using the markers - do not describe what you want to do.",
1014
- formatDescription: (ctx) => `Parameters using ${ctx.argPrefix}name markers (value on next line(s), no escaping needed)`,
1015
- rules: () => [
1016
- "Output ONLY plain text with the exact markers - never use function/tool calling",
1017
- "You can invoke multiple gadgets in a single response",
1018
- "Gadgets without dependencies execute immediately (in parallel if multiple)",
1019
- "Use :invocation_id:dep1,dep2 syntax when a gadget needs results from prior gadgets",
1020
- "If any dependency fails, dependent gadgets are automatically skipped"
1021
- ],
1022
- customExamples: null
1023
- };
1024
- }
1025
- });
1026
-
1027
- // src/core/messages.ts
1028
- function normalizeMessageContent(content) {
1029
- if (typeof content === "string") {
1030
- return [{ type: "text", text: content }];
1031
- }
1032
- return content;
1033
- }
1034
- function extractMessageText(content) {
1035
- if (typeof content === "string") {
1036
- return content;
1037
- }
1038
- return content.filter((part) => part.type === "text").map((part) => part.text).join("");
1039
- }
1040
- var LLMMessageBuilder;
1041
- var init_messages = __esm({
1042
- "src/core/messages.ts"() {
1043
- "use strict";
1044
- init_constants();
1045
- init_input_content();
1046
- init_prompt_config();
1047
- LLMMessageBuilder = class {
1048
- messages = [];
1049
- startPrefix = GADGET_START_PREFIX;
1050
- endPrefix = GADGET_END_PREFIX;
1051
- argPrefix = GADGET_ARG_PREFIX;
1052
- promptConfig;
1053
- constructor(promptConfig) {
1054
- this.promptConfig = promptConfig ?? {};
1055
- }
1056
- /**
1057
- * Set custom prefixes for gadget markers.
1058
- * Used to configure history builder to match system prompt markers.
1059
- */
1060
- withPrefixes(startPrefix, endPrefix, argPrefix) {
1061
- this.startPrefix = startPrefix;
1062
- this.endPrefix = endPrefix;
1063
- if (argPrefix) {
1064
- this.argPrefix = argPrefix;
1065
- }
1066
- return this;
1067
- }
1068
- addSystem(content, metadata) {
1069
- this.messages.push({ role: "system", content, metadata });
1070
- return this;
1071
- }
1072
- addGadgets(gadgets, options) {
1073
- if (options?.startPrefix) {
1074
- this.startPrefix = options.startPrefix;
1075
- }
1076
- if (options?.endPrefix) {
1077
- this.endPrefix = options.endPrefix;
1078
- }
1079
- if (options?.argPrefix) {
1080
- this.argPrefix = options.argPrefix;
1081
- }
1082
- const context = {
1083
- startPrefix: this.startPrefix,
1084
- endPrefix: this.endPrefix,
1085
- argPrefix: this.argPrefix,
1086
- gadgetCount: gadgets.length,
1087
- gadgetNames: gadgets.map((g) => g.name ?? g.constructor.name)
1088
- };
1089
- const parts = [];
1090
- const mainInstruction = resolvePromptTemplate(
1091
- this.promptConfig.mainInstruction,
1092
- DEFAULT_PROMPTS.mainInstruction,
1093
- context
1094
- );
1095
- parts.push(mainInstruction);
1096
- parts.push(this.buildGadgetsSection(gadgets));
1097
- parts.push(this.buildUsageSection(context));
1098
- this.messages.push({ role: "system", content: parts.join("") });
1099
- return this;
1100
- }
1101
- buildGadgetsSection(gadgets) {
1102
- const parts = [];
1103
- parts.push("\n\nAVAILABLE GADGETS");
1104
- parts.push("\n=================\n");
1105
- for (const gadget of gadgets) {
1106
- const gadgetName = gadget.name ?? gadget.constructor.name;
1107
- const instruction = gadget.getInstruction({
1108
- argPrefix: this.argPrefix,
1109
- startPrefix: this.startPrefix,
1110
- endPrefix: this.endPrefix
1111
- });
1112
- const schemaMarker = "\n\nInput Schema (BLOCK):";
1113
- const schemaIndex = instruction.indexOf(schemaMarker);
1114
- const description = (schemaIndex !== -1 ? instruction.substring(0, schemaIndex) : instruction).trim();
1115
- const schema = schemaIndex !== -1 ? instruction.substring(schemaIndex + schemaMarker.length).trim() : "";
1116
- parts.push(`
1117
- GADGET: ${gadgetName}`);
1118
- parts.push(`
1119
- ${description}`);
1120
- if (schema) {
1121
- parts.push(`
1122
-
1123
- PARAMETERS (BLOCK):
1124
- ${schema}`);
1125
- }
1126
- parts.push("\n\n---");
1127
- }
1128
- return parts.join("");
1129
- }
1130
- buildUsageSection(context) {
1131
- const parts = [];
1132
- const formatDescription = resolvePromptTemplate(
1133
- this.promptConfig.formatDescription,
1134
- DEFAULT_PROMPTS.formatDescription,
1135
- context
1136
- );
1137
- parts.push("\n\nHOW TO INVOKE GADGETS");
1138
- parts.push("\n=====================\n");
1139
- const criticalUsage = resolvePromptTemplate(
1140
- this.promptConfig.criticalUsage,
1141
- DEFAULT_PROMPTS.criticalUsage,
1142
- context
1143
- );
1144
- parts.push(`
1145
- CRITICAL: ${criticalUsage}
1146
- `);
1147
- parts.push("\nFORMAT:");
1148
- parts.push(`
1149
- 1. Start marker: ${this.startPrefix}gadget_name`);
1150
- parts.push(`
1151
- With ID: ${this.startPrefix}gadget_name:my_id`);
1152
- parts.push(`
1153
- With dependencies: ${this.startPrefix}gadget_name:my_id:dep1,dep2`);
1154
- parts.push(`
1155
- 2. ${formatDescription}`);
1156
- parts.push(`
1157
- 3. End marker: ${this.endPrefix}`);
1158
- parts.push(this.buildExamplesSection(context));
1159
- parts.push(this.buildRulesSection(context));
1160
- parts.push("\n");
1161
- return parts.join("");
1162
- }
1163
- buildExamplesSection(context) {
1164
- if (this.promptConfig.customExamples) {
1165
- return this.promptConfig.customExamples(context);
1166
- }
1167
- const parts = [];
1168
- const singleExample = `${this.startPrefix}translate
1169
- ${this.argPrefix}from
1170
- English
1171
- ${this.argPrefix}to
1172
- Polish
1173
- ${this.argPrefix}content
1174
- Paris is the capital of France: a beautiful city.
1175
- ${this.endPrefix}`;
1176
- parts.push(`
1177
-
1178
- EXAMPLE (Single Gadget):
1179
-
1180
- ${singleExample}`);
1181
- const multipleExample = `${this.startPrefix}translate
1182
- ${this.argPrefix}from
1183
- English
1184
- ${this.argPrefix}to
1185
- Polish
1186
- ${this.argPrefix}content
1187
- Paris is the capital of France: a beautiful city.
1188
- ${this.endPrefix}
1189
- ${this.startPrefix}analyze
1190
- ${this.argPrefix}type
1191
- economic_analysis
1192
- ${this.argPrefix}matter
1193
- Polish Economy
1194
- ${this.argPrefix}question
1195
- Analyze the following:
1196
- - Polish arms exports 2025
1197
- - Economic implications
1198
- ${this.endPrefix}`;
1199
- parts.push(`
1200
-
1201
- EXAMPLE (Multiple Gadgets):
1202
-
1203
- ${multipleExample}`);
1204
- const dependencyExample = `${this.startPrefix}fetch_data:fetch_1
1205
- ${this.argPrefix}url
1206
- https://api.example.com/users
1207
- ${this.endPrefix}
1208
- ${this.startPrefix}fetch_data:fetch_2
1209
- ${this.argPrefix}url
1210
- https://api.example.com/orders
1211
- ${this.endPrefix}
1212
- ${this.startPrefix}merge_data:merge_1:fetch_1,fetch_2
1213
- ${this.argPrefix}format
1214
- json
1215
- ${this.endPrefix}`;
1216
- parts.push(`
1217
-
1218
- EXAMPLE (With Dependencies):
1219
- merge_1 waits for fetch_1 AND fetch_2 to complete.
1220
- If either fails, merge_1 is automatically skipped.
1221
-
1222
- ${dependencyExample}`);
1223
- parts.push(`
1224
-
1225
- BLOCK FORMAT SYNTAX:
1226
- Block format uses ${this.argPrefix}name markers. Values are captured verbatim until the next marker.
1227
-
1228
- ${this.argPrefix}filename
1229
- calculator.ts
1230
- ${this.argPrefix}code
1231
- class Calculator {
1232
- private history: string[] = [];
1233
-
1234
- add(a: number, b: number): number {
1235
- const result = a + b;
1236
- this.history.push(\`\${a} + \${b} = \${result}\`);
1237
- return result;
1238
- }
1239
- }
1240
-
1241
- BLOCK FORMAT RULES:
1242
- - Each parameter starts with ${this.argPrefix}parameterName on its own line
1243
- - The value starts on the NEXT line after the marker
1244
- - Value ends when the next ${this.argPrefix} or ${this.endPrefix} appears
1245
- - NO escaping needed - write values exactly as they should appear
1246
- - Perfect for code, JSON, markdown, or any content with special characters
1247
-
1248
- NESTED OBJECTS (use / separator):
1249
- ${this.argPrefix}config/timeout
1250
- 30
1251
- ${this.argPrefix}config/retries
1252
- 3
1253
- Produces: { "config": { "timeout": "30", "retries": "3" } }
1254
-
1255
- ARRAYS (use numeric indices):
1256
- ${this.argPrefix}items/0
1257
- first
1258
- ${this.argPrefix}items/1
1259
- second
1260
- Produces: { "items": ["first", "second"] }`);
1261
- return parts.join("");
1262
- }
1263
- buildRulesSection(context) {
1264
- const parts = [];
1265
- parts.push("\n\nRULES:");
1266
- const rules = resolveRulesTemplate(this.promptConfig.rules, context);
1267
- for (const rule of rules) {
1268
- parts.push(`
1269
- - ${rule}`);
1270
- }
1271
- return parts.join("");
1272
- }
1273
- /**
1274
- * Add a user message.
1275
- * Content can be a string (text only) or an array of content parts (multimodal).
1276
- *
1277
- * @param content - Message content
1278
- * @param metadata - Optional metadata
1279
- *
1280
- * @example
1281
- * ```typescript
1282
- * // Text only
1283
- * builder.addUser("Hello!");
1284
- *
1285
- * // Multimodal
1286
- * builder.addUser([
1287
- * text("What's in this image?"),
1288
- * imageFromBuffer(imageData),
1289
- * ]);
1290
- * ```
1291
- */
1292
- addUser(content, metadata) {
1293
- this.messages.push({ role: "user", content, metadata });
1294
- return this;
1295
- }
1296
- addAssistant(content, metadata) {
1297
- this.messages.push({ role: "assistant", content, metadata });
1298
- return this;
1299
- }
1300
- /**
1301
- * Add a user message with an image attachment.
1302
- *
1303
- * @param textContent - Text prompt
1304
- * @param imageData - Image data (Buffer, Uint8Array, or base64 string)
1305
- * @param mimeType - Optional MIME type (auto-detected if not provided)
1306
- *
1307
- * @example
1308
- * ```typescript
1309
- * builder.addUserWithImage(
1310
- * "What's in this image?",
1311
- * await fs.readFile("photo.jpg"),
1312
- * "image/jpeg" // Optional - auto-detected
1313
- * );
1314
- * ```
1315
- */
1316
- addUserWithImage(textContent, imageData, mimeType) {
1317
- const imageBuffer = typeof imageData === "string" ? Buffer.from(imageData, "base64") : imageData;
1318
- const detectedMime = mimeType ?? detectImageMimeType(imageBuffer);
1319
- if (!detectedMime) {
1320
- throw new Error(
1321
- "Could not detect image MIME type. Please provide the mimeType parameter explicitly."
1322
- );
1323
- }
1324
- const content = [
1325
- text(textContent),
1326
- {
1327
- type: "image",
1328
- source: {
1329
- type: "base64",
1330
- mediaType: detectedMime,
1331
- data: toBase64(imageBuffer)
1332
- }
1333
- }
1334
- ];
1335
- this.messages.push({ role: "user", content });
1336
- return this;
1337
- }
1338
- /**
1339
- * Add a user message with an image URL (OpenAI only).
1340
- *
1341
- * @param textContent - Text prompt
1342
- * @param imageUrl - URL to the image
1343
- *
1344
- * @example
1345
- * ```typescript
1346
- * builder.addUserWithImageUrl(
1347
- * "What's in this image?",
1348
- * "https://example.com/image.jpg"
1349
- * );
1350
- * ```
1351
- */
1352
- addUserWithImageUrl(textContent, imageUrl) {
1353
- const content = [text(textContent), imageFromUrl(imageUrl)];
1354
- this.messages.push({ role: "user", content });
1355
- return this;
1356
- }
1357
- /**
1358
- * Add a user message with an audio attachment (Gemini only).
1359
- *
1360
- * @param textContent - Text prompt
1361
- * @param audioData - Audio data (Buffer, Uint8Array, or base64 string)
1362
- * @param mimeType - Optional MIME type (auto-detected if not provided)
1363
- *
1364
- * @example
1365
- * ```typescript
1366
- * builder.addUserWithAudio(
1367
- * "Transcribe this audio",
1368
- * await fs.readFile("recording.mp3"),
1369
- * "audio/mp3" // Optional - auto-detected
1370
- * );
1371
- * ```
1372
- */
1373
- addUserWithAudio(textContent, audioData, mimeType) {
1374
- const audioBuffer = typeof audioData === "string" ? Buffer.from(audioData, "base64") : audioData;
1375
- const content = [text(textContent), audioFromBuffer(audioBuffer, mimeType)];
1376
- this.messages.push({ role: "user", content });
1377
- return this;
1378
- }
1379
- /**
1380
- * Add a user message with multiple content parts.
1381
- * Provides full flexibility for complex multimodal messages.
1382
- *
1383
- * @param parts - Array of content parts
1384
- *
1385
- * @example
1386
- * ```typescript
1387
- * builder.addUserMultimodal([
1388
- * text("Compare these images:"),
1389
- * imageFromBuffer(image1),
1390
- * imageFromBuffer(image2),
1391
- * ]);
1392
- * ```
1393
- */
1394
- addUserMultimodal(parts) {
1395
- this.messages.push({ role: "user", content: parts });
1396
- return this;
1397
- }
1398
- /**
1399
- * Record a gadget execution result in the message history.
1400
- * Creates an assistant message with the gadget invocation and a user message with the result.
1401
- *
1402
- * The invocationId is shown to the LLM so it can reference previous calls when building dependencies.
1403
- *
1404
- * @param gadget - Name of the gadget that was executed
1405
- * @param parameters - Parameters that were passed to the gadget
1406
- * @param result - Text result from the gadget execution
1407
- * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
1408
- * @param media - Optional media outputs from the gadget
1409
- * @param mediaIds - Optional IDs for the media outputs
1410
- * @param storedMedia - Optional stored media info including file paths
1411
- */
1412
- addGadgetCallResult(gadget, parameters, result, invocationId, media, mediaIds, storedMedia) {
1413
- const paramStr = this.formatBlockParameters(parameters, "");
1414
- this.messages.push({
1415
- role: "assistant",
1416
- content: `${this.startPrefix}${gadget}:${invocationId}
1417
- ${paramStr}
1418
- ${this.endPrefix}`
1419
- });
1420
- if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
1421
- const idRefs = media.map((m, i) => {
1422
- const path3 = storedMedia?.[i]?.path;
1423
- const pathInfo = path3 ? ` \u2192 saved to: ${path3}` : "";
1424
- return `[Media: ${mediaIds[i]} (${m.kind})${pathInfo}]`;
1425
- }).join("\n");
1426
- const textWithIds = `Result (${invocationId}): ${result}
1427
- ${idRefs}`;
1428
- const parts = [text(textWithIds)];
1429
- for (const item of media) {
1430
- if (item.kind === "image") {
1431
- parts.push(imageFromBase64(item.data, item.mimeType));
1432
- } else if (item.kind === "audio") {
1433
- parts.push(audioFromBase64(item.data, item.mimeType));
1434
- }
1435
- }
1436
- this.messages.push({ role: "user", content: parts });
1437
- } else {
1438
- this.messages.push({
1439
- role: "user",
1440
- content: `Result (${invocationId}): ${result}`
1441
- });
1442
- }
1443
- return this;
1444
- }
1445
- /**
1446
- * Format parameters as Block format with JSON Pointer paths.
1447
- * Uses the configured argPrefix for consistency with system prompt.
1448
- */
1449
- formatBlockParameters(params, prefix) {
1450
- const lines = [];
1451
- for (const [key, value] of Object.entries(params)) {
1452
- const fullPath = prefix ? `${prefix}/${key}` : key;
1453
- if (Array.isArray(value)) {
1454
- value.forEach((item, index) => {
1455
- const itemPath = `${fullPath}/${index}`;
1456
- if (typeof item === "object" && item !== null) {
1457
- lines.push(this.formatBlockParameters(item, itemPath));
1458
- } else {
1459
- lines.push(`${this.argPrefix}${itemPath}`);
1460
- lines.push(String(item));
1461
- }
1462
- });
1463
- } else if (typeof value === "object" && value !== null) {
1464
- lines.push(this.formatBlockParameters(value, fullPath));
1465
- } else {
1466
- lines.push(`${this.argPrefix}${fullPath}`);
1467
- lines.push(String(value));
1468
- }
1469
- }
1470
- return lines.join("\n");
1471
- }
1472
- build() {
1473
- return [...this.messages];
1474
- }
1475
- };
1476
- }
1477
- });
1478
-
1479
864
  // src/core/model-shortcuts.ts
1480
865
  function isKnownModelPattern(model) {
1481
866
  const normalized = model.toLowerCase();
@@ -2178,56 +1563,6 @@ var init_retry = __esm({
2178
1563
  }
2179
1564
  });
2180
1565
 
2181
- // src/gadgets/exceptions.ts
2182
- var TaskCompletionSignal, HumanInputRequiredException, TimeoutException, AbortException, BudgetPricingUnavailableError;
2183
- var init_exceptions = __esm({
2184
- "src/gadgets/exceptions.ts"() {
2185
- "use strict";
2186
- TaskCompletionSignal = class extends Error {
2187
- constructor(message) {
2188
- super(message ?? "Agent loop terminated by gadget");
2189
- this.name = "TaskCompletionSignal";
2190
- }
2191
- };
2192
- HumanInputRequiredException = class extends Error {
2193
- question;
2194
- constructor(question) {
2195
- super(`Human input required: ${question}`);
2196
- this.name = "HumanInputRequiredException";
2197
- this.question = question;
2198
- }
2199
- };
2200
- TimeoutException = class extends Error {
2201
- timeoutMs;
2202
- gadgetName;
2203
- constructor(gadgetName, timeoutMs) {
2204
- super(`Gadget '${gadgetName}' execution exceeded timeout of ${timeoutMs}ms`);
2205
- this.name = "TimeoutException";
2206
- this.gadgetName = gadgetName;
2207
- this.timeoutMs = timeoutMs;
2208
- }
2209
- };
2210
- AbortException = class extends Error {
2211
- constructor(message) {
2212
- super(message || "Gadget execution was aborted");
2213
- this.name = "AbortException";
2214
- }
2215
- };
2216
- BudgetPricingUnavailableError = class extends Error {
2217
- model;
2218
- budget;
2219
- constructor(model, budget) {
2220
- super(
2221
- `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.`
2222
- );
2223
- this.name = "BudgetPricingUnavailableError";
2224
- this.model = model;
2225
- this.budget = budget;
2226
- }
2227
- };
2228
- }
2229
- });
2230
-
2231
1566
  // src/gadgets/media-store.ts
2232
1567
  import { randomBytes } from "crypto";
2233
1568
  import { mkdir, rm, writeFile } from "fs/promises";
@@ -2404,151 +1739,31 @@ var init_media_store = __esm({
2404
1739
  * Check if a media ID exists.
2405
1740
  */
2406
1741
  has(id) {
2407
- return this.items.has(id);
2408
- }
2409
- /**
2410
- * Clear in-memory store without deleting files.
2411
- * Resets the counter but leaves files on disk.
2412
- */
2413
- clear() {
2414
- this.items.clear();
2415
- this.counter = 0;
2416
- }
2417
- /**
2418
- * Delete all stored files and clear memory.
2419
- * Removes the entire session directory.
2420
- */
2421
- async cleanup() {
2422
- if (this.initialized) {
2423
- try {
2424
- await rm(this.outputDir, { recursive: true, force: true });
2425
- } catch {
2426
- }
2427
- this.initialized = false;
2428
- }
2429
- this.clear();
2430
- }
2431
- };
2432
- }
2433
- });
2434
-
2435
- // src/logging/logger.ts
2436
- import { createWriteStream, mkdirSync } from "fs";
2437
- import { dirname } from "path";
2438
- import { Logger } from "tslog";
2439
- function parseLogLevel(value) {
2440
- if (!value) {
2441
- return void 0;
2442
- }
2443
- const normalized = value.trim().toLowerCase();
2444
- if (normalized === "") {
2445
- return void 0;
2446
- }
2447
- const numericLevel = Number(normalized);
2448
- if (Number.isFinite(numericLevel)) {
2449
- return Math.max(0, Math.min(6, Math.floor(numericLevel)));
2450
- }
2451
- return LEVEL_NAME_TO_ID[normalized];
2452
- }
2453
- function parseEnvBoolean(value) {
2454
- if (!value) return void 0;
2455
- const normalized = value.trim().toLowerCase();
2456
- if (normalized === "true" || normalized === "1") return true;
2457
- if (normalized === "false" || normalized === "0") return false;
2458
- return void 0;
2459
- }
2460
- function stripAnsi(str) {
2461
- return str.replace(/\x1b\[[0-9;]*m/g, "");
2462
- }
2463
- function createLogger(options = {}) {
2464
- const envMinLevel = parseLogLevel(process.env.LLMIST_LOG_LEVEL);
2465
- const envLogFile = process.env.LLMIST_LOG_FILE?.trim() ?? "";
2466
- const envLogReset = parseEnvBoolean(process.env.LLMIST_LOG_RESET);
2467
- const minLevel = options.minLevel ?? envMinLevel ?? 4;
2468
- const defaultType = options.type ?? "pretty";
2469
- const name = options.name ?? "llmist";
2470
- const logReset = options.logReset ?? envLogReset ?? false;
2471
- const envLogTee = parseEnvBoolean(process.env.LLMIST_LOG_TEE);
2472
- const teeToConsole = options.teeToConsole ?? envLogTee ?? false;
2473
- if (envLogFile && (!logFileInitialized || sharedLogFilePath !== envLogFile)) {
2474
- try {
2475
- if (sharedLogFileStream) {
2476
- sharedLogFileStream.end();
2477
- sharedLogFileStream = void 0;
2478
- }
2479
- mkdirSync(dirname(envLogFile), { recursive: true });
2480
- const flags = logReset ? "w" : "a";
2481
- sharedLogFileStream = createWriteStream(envLogFile, { flags });
2482
- sharedLogFilePath = envLogFile;
2483
- logFileInitialized = true;
2484
- writeErrorCount = 0;
2485
- writeErrorReported = false;
2486
- sharedLogFileStream.on("error", (error) => {
2487
- writeErrorCount++;
2488
- if (!writeErrorReported) {
2489
- console.error(`[llmist] Log file write error: ${error.message}`);
2490
- writeErrorReported = true;
2491
- }
2492
- if (writeErrorCount >= MAX_WRITE_ERRORS_BEFORE_DISABLE) {
2493
- console.error(
2494
- `[llmist] Too many log file errors (${writeErrorCount}), disabling file logging`
2495
- );
2496
- sharedLogFileStream?.end();
2497
- sharedLogFileStream = void 0;
2498
- }
2499
- });
2500
- } catch (error) {
2501
- console.error("Failed to initialize LLMIST_LOG_FILE output:", error);
2502
- }
2503
- }
2504
- const useFileLogging = Boolean(sharedLogFileStream);
2505
- const logger2 = new Logger({
2506
- name,
2507
- minLevel,
2508
- type: useFileLogging ? "pretty" : defaultType,
2509
- // Hide log position for file logging and non-pretty types
2510
- hideLogPositionForProduction: useFileLogging || defaultType !== "pretty",
2511
- prettyLogTemplate: LOG_TEMPLATE,
2512
- // Use overwrite to redirect tslog's formatted output to file instead of console
2513
- overwrite: useFileLogging ? {
2514
- transportFormatted: (logMetaMarkup, logArgs, _logErrors) => {
2515
- const args = logArgs.map(
2516
- (arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
2517
- );
2518
- if (sharedLogFileStream) {
2519
- const meta = stripAnsi(logMetaMarkup);
2520
- const fileArgs = args.map((a) => stripAnsi(a));
2521
- sharedLogFileStream.write(`${meta}${fileArgs.join(" ")}
2522
- `);
2523
- }
2524
- if (teeToConsole) {
2525
- process.stdout.write(`${logMetaMarkup}${args.join(" ")}
2526
- `);
1742
+ return this.items.has(id);
1743
+ }
1744
+ /**
1745
+ * Clear in-memory store without deleting files.
1746
+ * Resets the counter but leaves files on disk.
1747
+ */
1748
+ clear() {
1749
+ this.items.clear();
1750
+ this.counter = 0;
1751
+ }
1752
+ /**
1753
+ * Delete all stored files and clear memory.
1754
+ * Removes the entire session directory.
1755
+ */
1756
+ async cleanup() {
1757
+ if (this.initialized) {
1758
+ try {
1759
+ await rm(this.outputDir, { recursive: true, force: true });
1760
+ } catch {
1761
+ }
1762
+ this.initialized = false;
2527
1763
  }
1764
+ this.clear();
2528
1765
  }
2529
- } : void 0
2530
- });
2531
- return logger2;
2532
- }
2533
- var LEVEL_NAME_TO_ID, sharedLogFilePath, sharedLogFileStream, logFileInitialized, writeErrorCount, writeErrorReported, MAX_WRITE_ERRORS_BEFORE_DISABLE, LOG_TEMPLATE, defaultLogger;
2534
- var init_logger = __esm({
2535
- "src/logging/logger.ts"() {
2536
- "use strict";
2537
- LEVEL_NAME_TO_ID = {
2538
- silly: 0,
2539
- trace: 1,
2540
- debug: 2,
2541
- info: 3,
2542
- warn: 4,
2543
- error: 5,
2544
- fatal: 6
2545
1766
  };
2546
- logFileInitialized = false;
2547
- writeErrorCount = 0;
2548
- writeErrorReported = false;
2549
- MAX_WRITE_ERRORS_BEFORE_DISABLE = 5;
2550
- LOG_TEMPLATE = "{{yyyy}}-{{mm}}-{{dd}} {{hh}}:{{MM}}:{{ss}}:{{ms}} {{logLevelName}} [{{name}}] ";
2551
- defaultLogger = createLogger();
2552
1767
  }
2553
1768
  });
2554
1769
 
@@ -3066,6 +2281,16 @@ var init_conversation_manager = __esm({
3066
2281
  getBaseMessages() {
3067
2282
  return [...this.baseMessages, ...this.initialMessages];
3068
2283
  }
2284
+ /**
2285
+ * Replace the base (system + gadget catalog) messages.
2286
+ *
2287
+ * Used when async setup (e.g. MCP server connect-and-list) discovers
2288
+ * additional gadgets after the agent was constructed. Conversation history
2289
+ * is preserved; only the leading system block is swapped.
2290
+ */
2291
+ replaceBaseMessages(newBase) {
2292
+ this.baseMessages = newBase;
2293
+ }
3069
2294
  replaceHistory(newHistory) {
3070
2295
  this.historyBuilder = new LLMMessageBuilder();
3071
2296
  if (this.startPrefix && this.endPrefix) {
@@ -4028,632 +3253,52 @@ var init_llm_call_lifecycle = __esm({
4028
3253
  * to preserve the original Agent behavior: the `afterLLMCall` controller was never
4029
3254
  * invoked for interrupted calls in the pre-extraction code. Skipping it here prevents
4030
3255
  * side effects (e.g., `append_messages` mutating conversation state) during cleanup
4031
- * of an already-exited run loop.
4032
- */
4033
- async processAfterLLMCallController(iteration, llmOptions, result, gadgetCallCount) {
4034
- let finalMessage = result.finalMessage;
4035
- if (!this.hooks.controllers?.afterLLMCall || result.finishReason === "interrupted") {
4036
- return finalMessage;
4037
- }
4038
- const context = {
4039
- iteration,
4040
- maxIterations: this.maxIterations,
4041
- budget: this.budget,
4042
- totalCost: this.tree.getTotalCost(),
4043
- options: llmOptions,
4044
- finishReason: result.finishReason,
4045
- usage: result.usage,
4046
- finalMessage: result.finalMessage,
4047
- gadgetCallCount,
4048
- logger: this.logger
4049
- };
4050
- const action = await this.hooks.controllers.afterLLMCall(context);
4051
- validateAfterLLMCallAction(action);
4052
- if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
4053
- finalMessage = action.modifiedMessage;
4054
- }
4055
- if (action.action === "append_messages" || action.action === "append_and_modify") {
4056
- for (const msg of action.messages) {
4057
- if (msg.role === "user") {
4058
- this.conversation.addUserMessage(msg.content);
4059
- } else if (msg.role === "assistant") {
4060
- this.conversation.addAssistantMessage(extractMessageText(msg.content));
4061
- } else if (msg.role === "system") {
4062
- this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4063
- }
4064
- }
4065
- }
4066
- return finalMessage;
4067
- }
4068
- };
4069
- }
4070
- });
4071
-
4072
- // src/gadgets/schema-to-json.ts
4073
- import * as z from "zod";
4074
- function schemaToJSONSchema(schema, options) {
4075
- const jsonSchema = z.toJSONSchema(schema, options ?? { target: "draft-7" });
4076
- const mismatches = detectDescriptionMismatch(schema, jsonSchema);
4077
- if (mismatches.length > 0) {
4078
- defaultLogger.warn(
4079
- `Zod instance mismatch detected: ${mismatches.length} description(s) lost. For best results, use: import { z } from "llmist"`
4080
- );
4081
- return mergeDescriptions(schema, jsonSchema);
4082
- }
4083
- return jsonSchema;
4084
- }
4085
- function detectDescriptionMismatch(schema, jsonSchema) {
4086
- const mismatches = [];
4087
- function checkSchema(zodSchema, json, path3) {
4088
- if (!zodSchema || typeof zodSchema !== "object") return;
4089
- const def = zodSchema._def;
4090
- const jsonObj = json;
4091
- if (def?.description && !jsonObj?.description) {
4092
- mismatches.push(path3 || "root");
4093
- }
4094
- if (def?.typeName === "ZodObject" && def?.shape) {
4095
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
4096
- for (const [key, fieldSchema] of Object.entries(shape)) {
4097
- const properties = jsonObj?.properties;
4098
- const jsonProp = properties?.[key];
4099
- checkSchema(fieldSchema, jsonProp, path3 ? `${path3}.${key}` : key);
4100
- }
4101
- }
4102
- if (def?.typeName === "ZodArray" && def?.type) {
4103
- checkSchema(def.type, jsonObj?.items, path3 ? `${path3}[]` : "[]");
4104
- }
4105
- if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
4106
- checkSchema(def.innerType, json, path3);
4107
- }
4108
- if (def?.typeName === "ZodDefault" && def?.innerType) {
4109
- checkSchema(def.innerType, json, path3);
4110
- }
4111
- }
4112
- checkSchema(schema, jsonSchema, "");
4113
- return mismatches;
4114
- }
4115
- function mergeDescriptions(schema, jsonSchema) {
4116
- function merge(zodSchema, json) {
4117
- if (!json || typeof json !== "object") return json;
4118
- const def = zodSchema._def;
4119
- const jsonObj = json;
4120
- const merged = { ...jsonObj };
4121
- if (def?.description && !jsonObj.description) {
4122
- merged.description = def.description;
4123
- }
4124
- if (def?.typeName === "ZodObject" && def?.shape && jsonObj.properties) {
4125
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
4126
- const properties = jsonObj.properties;
4127
- merged.properties = { ...properties };
4128
- for (const [key, fieldSchema] of Object.entries(shape)) {
4129
- if (properties[key]) {
4130
- merged.properties[key] = merge(fieldSchema, properties[key]);
4131
- }
4132
- }
4133
- }
4134
- if (def?.typeName === "ZodArray" && def?.type && jsonObj.items) {
4135
- merged.items = merge(def.type, jsonObj.items);
4136
- }
4137
- if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
4138
- return merge(def.innerType, json);
4139
- }
4140
- if (def?.typeName === "ZodDefault" && def?.innerType) {
4141
- return merge(def.innerType, json);
4142
- }
4143
- return merged;
4144
- }
4145
- return merge(schema, jsonSchema);
4146
- }
4147
- var init_schema_to_json = __esm({
4148
- "src/gadgets/schema-to-json.ts"() {
4149
- "use strict";
4150
- init_logger();
4151
- }
4152
- });
4153
-
4154
- // src/gadgets/schema-validator.ts
4155
- import * as z2 from "zod";
4156
- function validateGadgetSchema(schema, gadgetName) {
4157
- let jsonSchema;
4158
- try {
4159
- jsonSchema = z2.toJSONSchema(schema, { target: "draft-7" });
4160
- } catch (error) {
4161
- const errorMessage = error instanceof Error ? error.message : String(error);
4162
- throw new Error(
4163
- `Gadget "${gadgetName}" has a schema that cannot be serialized to JSON Schema.
4164
- This usually happens with unsupported patterns like:
4165
- - z.record() - use z.object({}).passthrough() instead
4166
- - Complex transforms or custom refinements
4167
- - Circular references
4168
-
4169
- Original error: ${errorMessage}
4170
-
4171
- Only use schema patterns that Zod v4's native toJSONSchema() supports.`
4172
- );
4173
- }
4174
- const issues = findUnknownTypes(jsonSchema);
4175
- if (issues.length > 0) {
4176
- const fieldList = issues.join(", ");
4177
- throw new Error(
4178
- `Gadget "${gadgetName}" uses z.unknown() which produces incomplete schemas.
4179
- Problematic fields: ${fieldList}
4180
-
4181
- z.unknown() doesn't generate type information in JSON Schema, making it unclear
4182
- to the LLM what data structure to provide.
4183
-
4184
- Suggestions:
4185
- - Use z.object({}).passthrough() for flexible objects
4186
- - Use z.record(z.string()) for key-value objects with string values
4187
- - Define specific structure if possible
4188
-
4189
- Example fixes:
4190
- // \u274C Bad
4191
- content: z.unknown()
4192
-
4193
- // \u2705 Good
4194
- content: z.object({}).passthrough() // for flexible objects
4195
- content: z.record(z.string()) // for key-value objects
4196
- content: z.array(z.string()) // for arrays of strings
4197
- `
4198
- );
4199
- }
4200
- }
4201
- function findUnknownTypes(schema, path3 = []) {
4202
- const issues = [];
4203
- if (!schema || typeof schema !== "object") {
4204
- return issues;
4205
- }
4206
- if (schema.definitions) {
4207
- for (const defSchema of Object.values(schema.definitions)) {
4208
- issues.push(...findUnknownTypes(defSchema, []));
4209
- }
4210
- }
4211
- if (schema.properties) {
4212
- for (const [propName, propSchema] of Object.entries(schema.properties)) {
4213
- const propPath = [...path3, propName];
4214
- if (hasNoType(propSchema)) {
4215
- issues.push(propPath.join(".") || propName);
4216
- }
4217
- issues.push(...findUnknownTypes(propSchema, propPath));
4218
- }
4219
- }
4220
- if (schema.items) {
4221
- const itemPath = [...path3, "[]"];
4222
- if (hasNoType(schema.items)) {
4223
- issues.push(itemPath.join("."));
4224
- }
4225
- issues.push(...findUnknownTypes(schema.items, itemPath));
4226
- }
4227
- if (schema.anyOf) {
4228
- schema.anyOf.forEach((subSchema, index) => {
4229
- issues.push(...findUnknownTypes(subSchema, [...path3, `anyOf[${index}]`]));
4230
- });
4231
- }
4232
- if (schema.oneOf) {
4233
- schema.oneOf.forEach((subSchema, index) => {
4234
- issues.push(...findUnknownTypes(subSchema, [...path3, `oneOf[${index}]`]));
4235
- });
4236
- }
4237
- if (schema.allOf) {
4238
- schema.allOf.forEach((subSchema, index) => {
4239
- issues.push(...findUnknownTypes(subSchema, [...path3, `allOf[${index}]`]));
4240
- });
4241
- }
4242
- return issues;
4243
- }
4244
- function hasNoType(prop) {
4245
- if (!prop || typeof prop !== "object") {
4246
- return false;
4247
- }
4248
- const hasType = prop.type !== void 0;
4249
- const hasRef = prop.$ref !== void 0;
4250
- const hasUnion = prop.anyOf !== void 0 || prop.oneOf !== void 0 || prop.allOf !== void 0;
4251
- if (hasType || hasRef || hasUnion) {
4252
- return false;
4253
- }
4254
- const keys = Object.keys(prop);
4255
- const metadataKeys = ["description", "title", "default", "examples"];
4256
- const hasOnlyMetadata = keys.every((key) => metadataKeys.includes(key));
4257
- return hasOnlyMetadata || keys.length === 0;
4258
- }
4259
- var init_schema_validator = __esm({
4260
- "src/gadgets/schema-validator.ts"() {
4261
- "use strict";
4262
- }
4263
- });
4264
-
4265
- // src/gadgets/gadget.ts
4266
- function formatParamsForBlockExample(params, prefix = "", argPrefix = GADGET_ARG_PREFIX) {
4267
- const lines = [];
4268
- for (const [key, value] of Object.entries(params)) {
4269
- const fullPath = prefix ? `${prefix}/${key}` : key;
4270
- if (Array.isArray(value)) {
4271
- value.forEach((item, index) => {
4272
- const itemPath = `${fullPath}/${index}`;
4273
- if (typeof item === "object" && item !== null) {
4274
- lines.push(
4275
- formatParamsForBlockExample(item, itemPath, argPrefix)
4276
- );
4277
- } else {
4278
- lines.push(`${argPrefix}${itemPath}`);
4279
- lines.push(String(item));
4280
- }
4281
- });
4282
- } else if (typeof value === "object" && value !== null) {
4283
- lines.push(
4284
- formatParamsForBlockExample(value, fullPath, argPrefix)
4285
- );
4286
- } else {
4287
- lines.push(`${argPrefix}${fullPath}`);
4288
- lines.push(String(value));
4289
- }
4290
- }
4291
- return lines.join("\n");
4292
- }
4293
- function formatParamLine(key, propObj, isRequired, indent = "") {
4294
- const type = propObj.type;
4295
- const description = propObj.description;
4296
- const enumValues = propObj.enum;
4297
- let line = `${indent}- ${key}`;
4298
- if (type === "array") {
4299
- const items = propObj.items;
4300
- const itemType = items?.type || "any";
4301
- line += ` (array of ${itemType})`;
4302
- } else if (type === "object" && propObj.properties) {
4303
- line += " (object)";
4304
- } else {
4305
- line += ` (${type})`;
4306
- }
4307
- if (isRequired && indent !== "") {
4308
- line += " [required]";
4309
- }
4310
- if (description) {
4311
- line += `: ${description}`;
4312
- }
4313
- if (enumValues) {
4314
- line += ` - one of: ${enumValues.map((v) => `"${v}"`).join(", ")}`;
4315
- }
4316
- return line;
4317
- }
4318
- function formatSchemaAsPlainText(schema, indent = "", atRoot = true) {
4319
- const lines = [];
4320
- const properties = schema.properties || {};
4321
- const required = schema.required || [];
4322
- if (atRoot && indent === "") {
4323
- const requiredProps = [];
4324
- const optionalProps = [];
4325
- for (const [key, prop] of Object.entries(properties)) {
4326
- if (required.includes(key)) {
4327
- requiredProps.push([key, prop]);
4328
- } else {
4329
- optionalProps.push([key, prop]);
4330
- }
4331
- }
4332
- const reqCount = requiredProps.length;
4333
- const optCount = optionalProps.length;
4334
- if (reqCount > 0 || optCount > 0) {
4335
- const parts = [];
4336
- if (reqCount > 0) parts.push(`${reqCount} required`);
4337
- if (optCount > 0) parts.push(`${optCount} optional`);
4338
- lines.push(parts.join(", "));
4339
- lines.push("");
4340
- }
4341
- if (reqCount > 0) {
4342
- lines.push("REQUIRED Parameters:");
4343
- for (const [key, prop] of requiredProps) {
4344
- lines.push(formatParamLine(key, prop, true, ""));
4345
- const propObj = prop;
4346
- if (propObj.type === "object" && propObj.properties) {
4347
- lines.push(formatSchemaAsPlainText(propObj, " ", false));
4348
- }
4349
- }
4350
- }
4351
- if (optCount > 0) {
4352
- if (reqCount > 0) lines.push("");
4353
- lines.push("OPTIONAL Parameters:");
4354
- for (const [key, prop] of optionalProps) {
4355
- lines.push(formatParamLine(key, prop, false, ""));
4356
- const propObj = prop;
4357
- if (propObj.type === "object" && propObj.properties) {
4358
- lines.push(formatSchemaAsPlainText(propObj, " ", false));
4359
- }
4360
- }
4361
- }
4362
- return lines.join("\n");
4363
- }
4364
- for (const [key, prop] of Object.entries(properties)) {
4365
- const isRequired = required.includes(key);
4366
- lines.push(formatParamLine(key, prop, isRequired, indent));
4367
- const propObj = prop;
4368
- if (propObj.type === "object" && propObj.properties) {
4369
- lines.push(formatSchemaAsPlainText(propObj, indent + " ", false));
4370
- }
4371
- }
4372
- return lines.join("\n");
4373
- }
4374
- var AbstractGadget;
4375
- var init_gadget = __esm({
4376
- "src/gadgets/gadget.ts"() {
4377
- "use strict";
4378
- init_constants();
4379
- init_exceptions();
4380
- init_schema_to_json();
4381
- init_schema_validator();
4382
- AbstractGadget = class {
4383
- /**
4384
- * The name of the gadget. Used for identification when LLM calls it.
4385
- * If not provided, defaults to the class name.
4386
- */
4387
- name;
4388
- /**
4389
- * Optional Zod schema describing the expected input payload. When provided,
4390
- * it will be validated before execution and transformed into a JSON Schema
4391
- * representation that is surfaced to the LLM as part of the instructions.
4392
- */
4393
- parameterSchema;
4394
- /**
4395
- * Optional timeout in milliseconds for gadget execution.
4396
- * If execution exceeds this timeout, a TimeoutException will be thrown.
4397
- * If not set, the global defaultGadgetTimeoutMs from runtime options will be used.
4398
- * Set to 0 or undefined to disable timeout for this gadget.
4399
- */
4400
- timeoutMs;
4401
- /**
4402
- * Optional usage examples to help LLMs understand proper invocation.
4403
- * Examples are rendered in getInstruction() alongside the schema.
4404
- *
4405
- * Note: Uses broader `unknown` type to allow typed examples from subclasses
4406
- * while maintaining runtime compatibility.
4407
- */
4408
- examples;
4409
- /**
4410
- * Maximum number of concurrent executions allowed for this gadget.
4411
- * Use this to prevent race conditions in gadgets that modify shared state.
4412
- *
4413
- * - `1` = Sequential execution (only one instance runs at a time)
4414
- * - `0` or `undefined` = Unlimited concurrency (default)
4415
- * - `N > 1` = At most N concurrent executions
4416
- *
4417
- * This property sets a safety floor: external configuration (SubagentConfig)
4418
- * can only make concurrency MORE restrictive, never less. For example, if
4419
- * a gadget declares `maxConcurrent: 1`, external config cannot override it
4420
- * to allow parallel execution.
4421
- *
4422
- * @example
4423
- * ```typescript
4424
- * // File writer that must run sequentially to avoid race conditions
4425
- * class WriteFile extends Gadget({
4426
- * description: 'Writes content to a file',
4427
- * schema: z.object({ path: z.string(), content: z.string() }),
4428
- * maxConcurrent: 1, // Sequential - prevents race conditions
4429
- * }) {
4430
- * execute(params: this['params']) { ... }
4431
- * }
4432
- * ```
4433
- */
4434
- maxConcurrent;
4435
- /**
4436
- * If true, this gadget must execute alone — no other gadgets in the same
4437
- * LLM response can run in parallel. When an exclusive gadget arrives and
4438
- * other gadgets are already in-flight, it is deferred until they complete.
4439
- *
4440
- * Use for gadgets that terminate the agent loop (e.g., Finish), where
4441
- * sibling tool results must be visible to the LLM before the loop ends.
4442
- *
4443
- * This is a safety floor: external config cannot weaken it.
4444
- */
4445
- exclusive;
4446
- /**
4447
- * Throws an AbortException if the execution has been aborted.
4448
- *
4449
- * Call this at key checkpoints in long-running gadgets to allow early exit
4450
- * when the gadget has been cancelled (e.g., due to timeout). This enables
4451
- * resource cleanup and prevents unnecessary work after cancellation.
4452
- *
4453
- * @param ctx - The execution context containing the abort signal
4454
- * @throws AbortException if ctx.signal.aborted is true
4455
- *
4456
- * @example
4457
- * ```typescript
4458
- * class DataProcessor extends Gadget({
4459
- * description: 'Processes data in multiple steps',
4460
- * schema: z.object({ items: z.array(z.string()) }),
4461
- * }) {
4462
- * async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
4463
- * const results: string[] = [];
4464
- *
4465
- * for (const item of params.items) {
4466
- * // Check before each expensive operation
4467
- * this.throwIfAborted(ctx);
4468
- *
4469
- * results.push(await this.processItem(item));
4470
- * }
4471
- *
4472
- * return results.join(', ');
4473
- * }
4474
- * }
4475
- * ```
4476
- */
4477
- throwIfAborted(ctx) {
4478
- if (ctx?.signal?.aborted) {
4479
- throw new AbortException();
4480
- }
4481
- }
4482
- /**
4483
- * Register a cleanup function to run when execution is aborted (timeout or cancellation).
4484
- * The cleanup function is called immediately if the signal is already aborted.
4485
- * Errors thrown by the cleanup function are silently ignored.
4486
- *
4487
- * Use this to clean up resources like browser instances, database connections,
4488
- * or child processes when the gadget is cancelled due to timeout.
4489
- *
4490
- * @param ctx - The execution context containing the abort signal
4491
- * @param cleanup - Function to run on abort (can be sync or async)
4492
- *
4493
- * @example
4494
- * ```typescript
4495
- * class BrowserGadget extends Gadget({
4496
- * description: 'Fetches web page content',
4497
- * schema: z.object({ url: z.string() }),
4498
- * }) {
4499
- * async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
4500
- * const browser = await chromium.launch();
4501
- * this.onAbort(ctx, () => browser.close());
4502
- *
4503
- * const page = await browser.newPage();
4504
- * this.onAbort(ctx, () => page.close());
4505
- *
4506
- * await page.goto(params.url);
4507
- * const content = await page.content();
4508
- *
4509
- * await browser.close();
4510
- * return content;
4511
- * }
4512
- * }
4513
- * ```
4514
- */
4515
- onAbort(ctx, cleanup) {
4516
- if (!ctx?.signal) return;
4517
- const safeCleanup = () => {
4518
- try {
4519
- const result = cleanup();
4520
- if (result && typeof result === "object" && "catch" in result) {
4521
- result.catch(() => {
4522
- });
4523
- }
4524
- } catch {
4525
- }
4526
- };
4527
- if (ctx.signal.aborted) {
4528
- safeCleanup();
4529
- return;
4530
- }
4531
- ctx.signal.addEventListener("abort", safeCleanup, { once: true });
4532
- }
4533
- /**
4534
- * Create an AbortController linked to the execution context's signal.
4535
- * When the parent signal aborts, the returned controller also aborts with the same reason.
4536
- *
4537
- * Useful for passing abort signals to child operations like fetch() while still
4538
- * being able to abort them independently if needed.
4539
- *
4540
- * @param ctx - The execution context containing the parent abort signal
4541
- * @returns A new AbortController linked to the parent signal
4542
- *
4543
- * @example
4544
- * ```typescript
4545
- * class FetchGadget extends Gadget({
4546
- * description: 'Fetches data from URL',
4547
- * schema: z.object({ url: z.string() }),
4548
- * }) {
4549
- * async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
4550
- * const controller = this.createLinkedAbortController(ctx);
4551
- *
4552
- * // fetch() will automatically abort when parent times out
4553
- * const response = await fetch(params.url, { signal: controller.signal });
4554
- * return response.text();
4555
- * }
4556
- * }
4557
- * ```
4558
- */
4559
- createLinkedAbortController(ctx) {
4560
- const controller = new AbortController();
4561
- if (ctx?.signal) {
4562
- if (ctx.signal.aborted) {
4563
- controller.abort(ctx.signal.reason);
4564
- } else {
4565
- ctx.signal.addEventListener(
4566
- "abort",
4567
- () => {
4568
- controller.abort(ctx.signal.reason);
4569
- },
4570
- { once: true }
4571
- );
4572
- }
4573
- }
4574
- return controller;
4575
- }
4576
- /**
4577
- * Generate instruction text for the LLM.
4578
- * Combines name, description, and parameter schema into a formatted instruction.
4579
- *
4580
- * @param optionsOrArgPrefix - Optional custom prefixes for examples, or just argPrefix string for backwards compatibility
4581
- * @returns Formatted instruction string
3256
+ * of an already-exited run loop.
4582
3257
  */
4583
- getInstruction(optionsOrArgPrefix) {
4584
- const options = typeof optionsOrArgPrefix === "string" ? { argPrefix: optionsOrArgPrefix } : optionsOrArgPrefix;
4585
- const parts = [];
4586
- parts.push(this.description);
4587
- if (this.parameterSchema) {
4588
- const gadgetName = this.name ?? this.constructor.name;
4589
- validateGadgetSchema(this.parameterSchema, gadgetName);
4590
- const jsonSchema = schemaToJSONSchema(this.parameterSchema, {
4591
- target: "draft-7"
4592
- });
4593
- parts.push("\n\nParameters:");
4594
- parts.push(formatSchemaAsPlainText(jsonSchema));
4595
- }
4596
- if (this.examples && this.examples.length > 0) {
4597
- parts.push("\n\nExamples:");
4598
- const effectiveArgPrefix = options?.argPrefix ?? GADGET_ARG_PREFIX;
4599
- const effectiveStartPrefix = options?.startPrefix ?? GADGET_START_PREFIX;
4600
- const effectiveEndPrefix = options?.endPrefix ?? GADGET_END_PREFIX;
4601
- const gadgetName = this.name || this.constructor.name;
4602
- this.examples.forEach((example, index) => {
4603
- if (index > 0) {
4604
- parts.push("");
4605
- parts.push("---");
4606
- parts.push("");
4607
- }
4608
- if (example.comment) {
4609
- parts.push(`# ${example.comment}`);
4610
- }
4611
- parts.push(`${effectiveStartPrefix}${gadgetName}`);
4612
- parts.push(
4613
- formatParamsForBlockExample(
4614
- example.params,
4615
- "",
4616
- effectiveArgPrefix
4617
- )
4618
- );
4619
- parts.push(effectiveEndPrefix);
4620
- if (example.output !== void 0) {
4621
- parts.push("");
4622
- parts.push("Expected Output:");
4623
- parts.push(example.output);
3258
+ async processAfterLLMCallController(iteration, llmOptions, result, gadgetCallCount) {
3259
+ let finalMessage = result.finalMessage;
3260
+ if (!this.hooks.controllers?.afterLLMCall || result.finishReason === "interrupted") {
3261
+ return finalMessage;
3262
+ }
3263
+ const context = {
3264
+ iteration,
3265
+ maxIterations: this.maxIterations,
3266
+ budget: this.budget,
3267
+ totalCost: this.tree.getTotalCost(),
3268
+ options: llmOptions,
3269
+ finishReason: result.finishReason,
3270
+ usage: result.usage,
3271
+ finalMessage: result.finalMessage,
3272
+ gadgetCallCount,
3273
+ logger: this.logger
3274
+ };
3275
+ const action = await this.hooks.controllers.afterLLMCall(context);
3276
+ validateAfterLLMCallAction(action);
3277
+ if (action.action === "modify_and_continue" || action.action === "append_and_modify") {
3278
+ finalMessage = action.modifiedMessage;
3279
+ }
3280
+ if (action.action === "append_messages" || action.action === "append_and_modify") {
3281
+ for (const msg of action.messages) {
3282
+ if (msg.role === "user") {
3283
+ this.conversation.addUserMessage(msg.content);
3284
+ } else if (msg.role === "assistant") {
3285
+ this.conversation.addAssistantMessage(extractMessageText(msg.content));
3286
+ } else if (msg.role === "system") {
3287
+ this.conversation.addUserMessage(`[System] ${extractMessageText(msg.content)}`);
4624
3288
  }
4625
- });
3289
+ }
4626
3290
  }
4627
- return parts.join("\n");
3291
+ return finalMessage;
4628
3292
  }
4629
3293
  };
4630
3294
  }
4631
3295
  });
4632
3296
 
4633
- // src/gadgets/create-gadget.ts
4634
- function createGadget(config) {
4635
- class DynamicGadget extends AbstractGadget {
4636
- name = config.name;
4637
- description = config.description;
4638
- parameterSchema = config.schema;
4639
- timeoutMs = config.timeoutMs;
4640
- examples = config.examples;
4641
- maxConcurrent = config.maxConcurrent;
4642
- execute(params, ctx) {
4643
- return config.execute(params, ctx);
4644
- }
4645
- }
4646
- return new DynamicGadget();
4647
- }
4648
- var init_create_gadget = __esm({
4649
- "src/gadgets/create-gadget.ts"() {
4650
- "use strict";
4651
- init_gadget();
4652
- }
4653
- });
4654
-
4655
3297
  // src/gadgets/output-viewer.ts
4656
- import { z as z3 } from "zod";
3298
+ import { z } from "zod";
3299
+ function pluralize(count, singular, plural = `${singular}s`) {
3300
+ return count === 1 ? singular : plural;
3301
+ }
4657
3302
  function applyPattern(lines, pattern) {
4658
3303
  const regex = new RegExp(pattern.regex);
4659
3304
  if (!pattern.include) {
@@ -4678,80 +3323,169 @@ function applyPatterns(lines, patterns) {
4678
3323
  }
4679
3324
  return result;
4680
3325
  }
4681
- function applyLineLimit(lines, limit) {
3326
+ function parseLimitWindow(limit) {
4682
3327
  const trimmed = limit.trim();
4683
3328
  if (trimmed.endsWith("-") && !trimmed.startsWith("-")) {
4684
3329
  const n = parseInt(trimmed.slice(0, -1), 10);
4685
- if (!isNaN(n) && n > 0) {
4686
- return lines.slice(0, n);
3330
+ if (!Number.isNaN(n) && n > 0) {
3331
+ return { kind: "first", count: n };
4687
3332
  }
4688
3333
  }
4689
3334
  if (trimmed.startsWith("-") && !trimmed.includes("-", 1)) {
4690
3335
  const n = parseInt(trimmed, 10);
4691
- if (!isNaN(n) && n < 0) {
4692
- return lines.slice(n);
3336
+ if (!Number.isNaN(n) && n < 0) {
3337
+ return { kind: "last", count: Math.abs(n) };
4693
3338
  }
4694
3339
  }
4695
3340
  const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
4696
3341
  if (rangeMatch) {
4697
3342
  const start = parseInt(rangeMatch[1], 10);
4698
3343
  const end = parseInt(rangeMatch[2], 10);
4699
- if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start) {
4700
- return lines.slice(start - 1, end);
3344
+ if (!Number.isNaN(start) && !Number.isNaN(end) && start > 0 && end >= start) {
3345
+ return { kind: "range", start, end };
3346
+ }
3347
+ }
3348
+ return null;
3349
+ }
3350
+ function applyLineLimit(lines, limit) {
3351
+ const window = parseLimitWindow(limit);
3352
+ if (!window) return lines;
3353
+ switch (window.kind) {
3354
+ case "first":
3355
+ return lines.slice(0, window.count);
3356
+ case "last":
3357
+ return lines.slice(-window.count);
3358
+ case "range":
3359
+ return lines.slice(window.start - 1, window.end);
3360
+ }
3361
+ }
3362
+ function applyCharacterLimit(content, limit, maxOutputChars) {
3363
+ const total = content.length;
3364
+ if (total === 0) {
3365
+ return { text: "", start: 0, end: 0, total: 0, truncatedBySize: false, hasMoreAfter: false };
3366
+ }
3367
+ let startIndex = 0;
3368
+ let endExclusive = total;
3369
+ const window = limit ? parseLimitWindow(limit) : null;
3370
+ if (window) {
3371
+ switch (window.kind) {
3372
+ case "first":
3373
+ endExclusive = Math.min(window.count, total);
3374
+ break;
3375
+ case "last":
3376
+ startIndex = Math.max(0, total - window.count);
3377
+ break;
3378
+ case "range":
3379
+ startIndex = Math.min(window.start - 1, total);
3380
+ endExclusive = Math.min(window.end, total);
3381
+ break;
3382
+ }
3383
+ }
3384
+ let text3 = content.slice(startIndex, endExclusive);
3385
+ let truncatedBySize = false;
3386
+ if (text3.length > maxOutputChars) {
3387
+ text3 = window?.kind === "last" ? text3.slice(-maxOutputChars) : text3.slice(0, maxOutputChars);
3388
+ if (window?.kind === "last") {
3389
+ startIndex = endExclusive - text3.length;
4701
3390
  }
3391
+ truncatedBySize = true;
4702
3392
  }
4703
- return lines;
3393
+ return {
3394
+ text: text3,
3395
+ start: text3.length === 0 ? 0 : startIndex + 1,
3396
+ end: text3.length === 0 ? 0 : startIndex + text3.length,
3397
+ total,
3398
+ truncatedBySize,
3399
+ hasMoreAfter: startIndex + text3.length < total
3400
+ };
3401
+ }
3402
+ function buildCharacterRangeHint(start, total) {
3403
+ if (total <= 0 || start > total) return null;
3404
+ const end = Math.min(total, start + CHARACTER_HINT_WINDOW - 1);
3405
+ return `${start}-${end}`;
3406
+ }
3407
+ function buildCharacterModeSuggestion(stored, opts = {}) {
3408
+ const hint = buildCharacterRangeHint(opts.start ?? 1, stored.charCount);
3409
+ const action = opts.removePatterns ? "Remove patterns and then try" : "Try";
3410
+ const lineLabel = pluralize(stored.lineCount, "line");
3411
+ return `This output is dense (${stored.lineCount.toLocaleString()} ${lineLabel}; longest line ${stored.maxLineLength.toLocaleString()} chars). ${action} mode: "character"` + (hint ? `, limit: "${hint}"` : "") + ".";
3412
+ }
3413
+ function shouldSuggestCharacterMode(stored, maxOutputChars = DEFAULT_MAX_OUTPUT_CHARS) {
3414
+ return stored.lineCount <= 3 && (stored.maxLineLength > maxOutputChars || stored.maxLineLength >= DENSE_LINE_THRESHOLD);
4704
3415
  }
4705
3416
  function createGadgetOutputViewer(store, maxOutputChars = DEFAULT_MAX_OUTPUT_CHARS) {
4706
3417
  return createGadget({
4707
3418
  name: "GadgetOutputViewer",
4708
- description: "View stored output from gadgets that returned too much data. Use patterns to filter lines (like grep) and limit to control output size. Patterns are applied first in order, then the limit is applied to the result.",
4709
- schema: z3.object({
4710
- id: z3.string().describe("ID of the stored output (from the truncation message)"),
4711
- patterns: z3.array(patternSchema).optional().describe(
4712
- "Filter patterns applied in order (like piping through grep). Each pattern can include or exclude lines with optional before/after context."
3419
+ description: 'View stored output from gadgets that returned too much data. Use mode "line" for grep-like filtering and mode "character" for raw chunked browsing when the output is dense or effectively single-line. Patterns work only in line mode.',
3420
+ schema: z.object({
3421
+ id: z.string().describe("ID of the stored output (from the truncation message)"),
3422
+ mode: z.enum(["line", "character"]).default("line").describe(
3423
+ 'Browse by "line" (supports patterns) or by "character" (raw windows for dense output).'
4713
3424
  ),
4714
- limit: z3.string().optional().describe(
4715
- "Line range to return after filtering. Formats: '100-' (first 100), '-25' (last 25), '50-100' (lines 50-100)"
3425
+ patterns: z.array(patternSchema).optional().describe(
3426
+ 'Line-mode filter patterns applied in order (like piping through grep). Not supported in mode "character".'
3427
+ ),
3428
+ limit: z.string().optional().describe(
3429
+ `Pagination window. In mode "line" it is a line range; in mode "character" it is a character range. Formats: "100-" (first 100), "-25" (last 25), "50-100" (inclusive range).`
4716
3430
  )
4717
3431
  }),
4718
3432
  examples: [
4719
3433
  {
4720
3434
  comment: "View first 50 lines of stored output",
4721
- params: { id: "Search_abc12345", limit: "50-" }
3435
+ params: { id: "Search_abc12345", mode: "line", limit: "50-" }
4722
3436
  },
4723
3437
  {
4724
3438
  comment: "Filter for error lines with context",
4725
3439
  params: {
4726
3440
  id: "Search_abc12345",
3441
+ mode: "line",
4727
3442
  patterns: [{ regex: "error|Error|ERROR", include: true, before: 2, after: 5 }]
4728
3443
  }
4729
3444
  },
4730
3445
  {
4731
- comment: "Exclude blank lines, then show first 100",
3446
+ comment: "Exclude blank lines, then show first 100 lines",
4732
3447
  params: {
4733
3448
  id: "Search_abc12345",
3449
+ mode: "line",
4734
3450
  patterns: [{ regex: "^\\s*$", include: false, before: 0, after: 0 }],
4735
3451
  limit: "100-"
4736
3452
  }
4737
3453
  },
4738
3454
  {
4739
- comment: "Chain filters: find TODOs, exclude tests, limit to 50 lines",
3455
+ comment: "Browse the raw output by character window when line mode is too dense",
4740
3456
  params: {
4741
3457
  id: "Search_abc12345",
4742
- patterns: [
4743
- { regex: "TODO", include: true, before: 1, after: 1 },
4744
- { regex: "test|spec", include: false, before: 0, after: 0 }
4745
- ],
4746
- limit: "50-"
3458
+ mode: "character",
3459
+ limit: "1-2000"
4747
3460
  }
4748
3461
  }
4749
3462
  ],
4750
- execute: ({ id, patterns, limit }) => {
3463
+ execute: ({ id, mode, patterns, limit }) => {
4751
3464
  const stored = store.get(id);
4752
3465
  if (!stored) {
4753
3466
  return `Error: No stored output with id "${id}". Available IDs: ${store.getIds().join(", ") || "(none)"}`;
4754
3467
  }
3468
+ const suggestCharacterMode = shouldSuggestCharacterMode(stored, maxOutputChars);
3469
+ if (mode === "character") {
3470
+ if (patterns && patterns.length > 0) {
3471
+ return 'Error: patterns are only supported in mode "line". Remove patterns or switch back to mode: "line".';
3472
+ }
3473
+ const window = applyCharacterLimit(stored.content, limit, maxOutputChars);
3474
+ if (window.total === 0) {
3475
+ return "[Mode: character | Output is empty]";
3476
+ }
3477
+ const header2 = [
3478
+ `[Mode: character | Showing chars ${window.start.toLocaleString()}-${window.end.toLocaleString()} of ${window.total.toLocaleString()}${window.truncatedBySize ? " (truncated due to viewer size limit)" : ""}]`
3479
+ ];
3480
+ if (window.hasMoreAfter) {
3481
+ const nextRange = buildCharacterRangeHint(window.end + 1, window.total);
3482
+ if (nextRange) {
3483
+ header2.push(`[Next chunk: mode: "character", limit: "${nextRange}"]`);
3484
+ }
3485
+ }
3486
+ return `${header2.join("\n")}
3487
+ ${window.text}`;
3488
+ }
4755
3489
  let lines = stored.content.split("\n");
4756
3490
  if (patterns && patterns.length > 0) {
4757
3491
  lines = applyPatterns(
@@ -4767,54 +3501,76 @@ function createGadgetOutputViewer(store, maxOutputChars = DEFAULT_MAX_OUTPUT_CHA
4767
3501
  if (limit) {
4768
3502
  lines = applyLineLimit(lines, limit);
4769
3503
  }
4770
- let output = lines.join("\n");
4771
3504
  const totalLines = stored.lineCount;
3505
+ const totalLineLabel = pluralize(totalLines, "line");
4772
3506
  const returnedLines = lines.length;
4773
3507
  if (returnedLines === 0) {
4774
- return `No lines matched the filters. Original output had ${totalLines} lines.`;
3508
+ const base = `No lines matched the filters. Original output had ${totalLines.toLocaleString()} lines.`;
3509
+ if (!suggestCharacterMode) return base;
3510
+ return `${base} ${buildCharacterModeSuggestion(stored, {
3511
+ removePatterns: Boolean(patterns && patterns.length > 0)
3512
+ })}`;
4775
3513
  }
3514
+ let output = lines.join("\n");
4776
3515
  let truncatedBySize = false;
4777
3516
  let linesIncluded = returnedLines;
3517
+ let clippedFirstLine = false;
4778
3518
  if (output.length > maxOutputChars) {
4779
3519
  truncatedBySize = true;
4780
3520
  let truncatedOutput = "";
4781
3521
  linesIncluded = 0;
4782
3522
  for (const line of lines) {
4783
- if (truncatedOutput.length + line.length + 1 > maxOutputChars) break;
4784
- truncatedOutput += line + "\n";
3523
+ const addition = linesIncluded === 0 ? line : `
3524
+ ${line}`;
3525
+ if (truncatedOutput.length + addition.length > maxOutputChars) break;
3526
+ truncatedOutput += addition;
4785
3527
  linesIncluded++;
4786
3528
  }
3529
+ if (linesIncluded === 0) {
3530
+ clippedFirstLine = true;
3531
+ linesIncluded = 1;
3532
+ truncatedOutput = lines[0].slice(0, maxOutputChars);
3533
+ }
4787
3534
  output = truncatedOutput;
4788
3535
  }
4789
3536
  let header;
4790
- if (truncatedBySize) {
3537
+ if (clippedFirstLine) {
3538
+ header = `[Mode: line | Showing 1 partial line of ${totalLines.toLocaleString()} ${totalLineLabel} (the selected line exceeds the viewer size limit)]
3539
+ `;
3540
+ } else if (truncatedBySize) {
4791
3541
  const remainingLines = returnedLines - linesIncluded;
4792
- header = `[Showing ${linesIncluded} of ${totalLines} lines (truncated due to size limit)]
4793
- [... ${remainingLines.toLocaleString()} more lines. Use limit parameter to paginate, e.g., limit: "${linesIncluded + 1}-${linesIncluded + 200}"]
3542
+ header = `[Mode: line | Showing ${linesIncluded.toLocaleString()} of ${totalLines.toLocaleString()} ${totalLineLabel} (truncated due to size limit)]
3543
+ [... ${remainingLines.toLocaleString()} more ${pluralize(remainingLines, "line")}. Use limit parameter to paginate, e.g., limit: "${linesIncluded + 1}-${linesIncluded + 200}"]
4794
3544
  `;
4795
3545
  } else if (returnedLines < totalLines) {
4796
- header = `[Showing ${returnedLines} of ${totalLines} lines]
3546
+ header = `[Mode: line | Showing ${returnedLines.toLocaleString()} of ${totalLines.toLocaleString()} ${totalLineLabel}]
4797
3547
  `;
4798
3548
  } else {
4799
- header = `[Showing all ${totalLines} lines]
3549
+ header = `[Mode: line | Showing all ${totalLines.toLocaleString()} ${totalLineLabel}]
4800
3550
  `;
4801
3551
  }
4802
- return header + output;
3552
+ const footer = suggestCharacterMode || clippedFirstLine ? `
3553
+ [Tip: ${buildCharacterModeSuggestion(stored, {
3554
+ removePatterns: Boolean(patterns && patterns.length > 0)
3555
+ })}]` : "";
3556
+ return header + output + footer;
4803
3557
  }
4804
3558
  });
4805
3559
  }
4806
- var patternSchema, DEFAULT_MAX_OUTPUT_CHARS;
3560
+ var DEFAULT_MAX_OUTPUT_CHARS, CHARACTER_HINT_WINDOW, DENSE_LINE_THRESHOLD, patternSchema;
4807
3561
  var init_output_viewer = __esm({
4808
3562
  "src/gadgets/output-viewer.ts"() {
4809
3563
  "use strict";
4810
3564
  init_create_gadget();
4811
- patternSchema = z3.object({
4812
- regex: z3.string().describe("Regular expression to match"),
4813
- include: z3.boolean().default(true).describe("true = keep matching lines, false = exclude matching lines"),
4814
- before: z3.number().int().min(0).default(0).describe("Context lines before each match (like grep -B)"),
4815
- after: z3.number().int().min(0).default(0).describe("Context lines after each match (like grep -A)")
4816
- });
4817
3565
  DEFAULT_MAX_OUTPUT_CHARS = 76800;
3566
+ CHARACTER_HINT_WINDOW = 2e3;
3567
+ DENSE_LINE_THRESHOLD = 4e3;
3568
+ patternSchema = z.object({
3569
+ regex: z.string().describe("Regular expression to match"),
3570
+ include: z.boolean().default(true).describe("true = keep matching lines, false = exclude matching lines"),
3571
+ before: z.number().int().min(0).default(0).describe("Context lines before each match (like grep -B)"),
3572
+ after: z.number().int().min(0).default(0).describe("Context lines after each match (like grep -A)")
3573
+ });
4818
3574
  }
4819
3575
  });
4820
3576
 
@@ -4836,12 +3592,15 @@ var init_gadget_output_store = __esm({
4836
3592
  store(gadgetName, content) {
4837
3593
  const id = this.generateId(gadgetName);
4838
3594
  const encoder = new TextEncoder();
3595
+ const lines = content.split("\n");
4839
3596
  const stored = {
4840
3597
  id,
4841
3598
  gadgetName,
4842
3599
  content,
3600
+ charCount: content.length,
4843
3601
  byteSize: encoder.encode(content).length,
4844
- lineCount: content.split("\n").length,
3602
+ lineCount: lines.length,
3603
+ maxLineLength: lines.reduce((max, line) => Math.max(max, line.length), 0),
4845
3604
  timestamp: /* @__PURE__ */ new Date()
4846
3605
  };
4847
3606
  this.outputs.set(id, stored);
@@ -4945,16 +3704,20 @@ var init_output_limit_manager = __esm({
4945
3704
  }
4946
3705
  if (result.length > this.charLimit) {
4947
3706
  const id = this.outputStore.store(ctx.gadgetName, result);
4948
- const lines = result.split("\n").length;
4949
- const bytes = new TextEncoder().encode(result).length;
3707
+ const stored = this.outputStore.get(id);
3708
+ const lines = stored?.lineCount ?? result.split("\n").length;
3709
+ const bytes = stored?.byteSize ?? new TextEncoder().encode(result).length;
3710
+ const denseSuggestion = stored && shouldSuggestCharacterMode(stored, this.charLimit) ? ` ${buildCharacterModeSuggestion(stored)}` : "";
4950
3711
  this.logger.info("Gadget output exceeded limit, stored for browsing", {
4951
3712
  gadgetName: ctx.gadgetName,
4952
3713
  outputId: id,
4953
3714
  bytes,
4954
3715
  lines,
3716
+ charCount: stored?.charCount,
3717
+ maxLineLength: stored?.maxLineLength,
4955
3718
  charLimit: this.charLimit
4956
3719
  });
4957
- return `[Gadget "${ctx.gadgetName}" returned too much data: ${bytes.toLocaleString()} bytes, ${lines.toLocaleString()} lines. Use GadgetOutputViewer with id "${id}" to read it]`;
3720
+ return `[Gadget "${ctx.gadgetName}" returned too much data: ${bytes.toLocaleString()} bytes, ${lines.toLocaleString()} lines. Use GadgetOutputViewer with id "${id}" to read it.]` + denseSuggestion;
4958
3721
  }
4959
3722
  return result;
4960
3723
  };
@@ -5340,7 +4103,7 @@ var init_activation = __esm({
5340
4103
  });
5341
4104
 
5342
4105
  // src/skills/load-skill-gadget.ts
5343
- import { z as z4 } from "zod";
4106
+ import { z as z2 } from "zod";
5344
4107
  function createLoadSkillGadget(registry) {
5345
4108
  const summaries = registry.getMetadataSummaries();
5346
4109
  const skillNames = registry.getModelInvocable().map((s) => s.name);
@@ -5352,9 +4115,9 @@ function createLoadSkillGadget(registry) {
5352
4115
  return createGadget({
5353
4116
  name: LOAD_SKILL_GADGET_NAME,
5354
4117
  description,
5355
- schema: z4.object({
5356
- skill: z4.enum(skillNames).describe("Name of the skill to load"),
5357
- arguments: z4.string().optional().describe("Arguments for the skill (e.g., a filename, issue number, or search query)")
4118
+ schema: z2.object({
4119
+ skill: z2.enum(skillNames).describe("Name of the skill to load"),
4120
+ arguments: z2.string().optional().describe("Arguments for the skill (e.g., a filename, issue number, or search query)")
5358
4121
  }),
5359
4122
  execute: async ({ skill: skillName, arguments: args }) => {
5360
4123
  const skill = registry.get(skillName);
@@ -12789,7 +11552,7 @@ __export(client_exports, {
12789
11552
  LLMist: () => LLMist
12790
11553
  });
12791
11554
  var LLMist;
12792
- var init_client = __esm({
11555
+ var init_client2 = __esm({
12793
11556
  "src/core/client.ts"() {
12794
11557
  "use strict";
12795
11558
  init_builder();
@@ -13103,6 +11866,7 @@ var init_builder = __esm({
13103
11866
  subagents;
13104
11867
  policies;
13105
11868
  skills;
11869
+ mcp;
13106
11870
  constructor(client) {
13107
11871
  this.core = { client, initialMessages: [] };
13108
11872
  this.gadgets = { gadgets: [] };
@@ -13110,6 +11874,7 @@ var init_builder = __esm({
13110
11874
  this.subagents = {};
13111
11875
  this.policies = {};
13112
11876
  this.skills = { preActivated: [], skillDirs: [] };
11877
+ this.mcp = { servers: [] };
13113
11878
  }
13114
11879
  /** Set the model to use. Supports aliases like "sonnet", "flash". */
13115
11880
  withModel(model) {
@@ -13156,6 +11921,45 @@ var init_builder = __esm({
13156
11921
  this.gadgets.gadgets.push(...gadgets);
13157
11922
  return this;
13158
11923
  }
11924
+ /**
11925
+ * Attach a Model Context Protocol (MCP) server.
11926
+ *
11927
+ * The agent connects to the server lazily at the start of `run()`,
11928
+ * discovers its tools, and registers them as native gadgets so the LLM
11929
+ * can call them through the standard streaming block format.
11930
+ *
11931
+ * Calling this multiple times accumulates servers. Tools across servers
11932
+ * are merged into a single registry; in plan 1, conflicting tool names
11933
+ * raise a registration warning. Plan 2 introduces deterministic
11934
+ * `<server>__<tool>` prefixing for collisions.
11935
+ *
11936
+ * STDIO commands are gated by an allowlist (see allowlist.ts) — pass
11937
+ * `trust: true` on the spec to opt in for non-allowlisted binaries.
11938
+ *
11939
+ * Zero-overhead invariant: if you never call this method, the MCP
11940
+ * runtime module is never loaded. Agents without MCP pay nothing.
11941
+ *
11942
+ * @example
11943
+ * ```typescript
11944
+ * const agent = LLMist.createAgent()
11945
+ * .withModel("sonnet")
11946
+ * .withMcpServer({
11947
+ * name: "filesystem",
11948
+ * transport: "stdio",
11949
+ * command: "npx",
11950
+ * args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
11951
+ * })
11952
+ * .ask("list files in /tmp");
11953
+ * ```
11954
+ */
11955
+ withMcpServer(spec) {
11956
+ this.mcp.servers.push(spec);
11957
+ return this;
11958
+ }
11959
+ /** Inspect the configured MCP server specs. Useful for tests. */
11960
+ getMcpServerSpecs() {
11961
+ return this.mcp.servers;
11962
+ }
13159
11963
  /** Add conversation history messages. */
13160
11964
  withHistory(messages) {
13161
11965
  this.core.initialMessages.push(...normalizeHistory(messages));
@@ -13417,7 +12221,7 @@ ${resolved}`);
13417
12221
  }
13418
12222
  buildAgentOptions(userPrompt) {
13419
12223
  if (!this.core.client) {
13420
- const { LLMist: LLMistClass } = (init_client(), __toCommonJS(client_exports));
12224
+ const { LLMist: LLMistClass } = (init_client2(), __toCommonJS(client_exports));
13421
12225
  this.core.client = new LLMistClass();
13422
12226
  }
13423
12227
  const registry = GadgetRegistry.from(this.gadgets.gadgets);
@@ -13476,7 +12280,8 @@ ${preActivatedBlock}` : preActivatedBlock;
13476
12280
  parentObservers: this.subagents.parentObservers
13477
12281
  },
13478
12282
  sharedRateLimitTracker: this.subagents.sharedRateLimitTracker,
13479
- sharedRetryConfig: this.retry.sharedRetryConfig
12283
+ sharedRetryConfig: this.retry.sharedRetryConfig,
12284
+ mcpSpecs: this.mcp.servers.length > 0 ? [...this.mcp.servers] : void 0
13480
12285
  };
13481
12286
  }
13482
12287
  /** Create agent and start with a user prompt. */
@@ -14400,7 +13205,7 @@ var init_typed_gadget = __esm({
14400
13205
 
14401
13206
  // src/gadgets/executor.ts
14402
13207
  import equal from "fast-deep-equal";
14403
- import { z as z5 } from "zod";
13208
+ import { z as z3 } from "zod";
14404
13209
  function getHostExportsInternal() {
14405
13210
  return {
14406
13211
  AgentBuilder,
@@ -14408,7 +13213,7 @@ function getHostExportsInternal() {
14408
13213
  createGadget,
14409
13214
  ExecutionTree,
14410
13215
  LLMist,
14411
- z: z5
13216
+ z: z3
14412
13217
  };
14413
13218
  }
14414
13219
  var GadgetExecutor;
@@ -14417,7 +13222,7 @@ var init_executor = __esm({
14417
13222
  "use strict";
14418
13223
  init_builder();
14419
13224
  init_hook_utils();
14420
- init_client();
13225
+ init_client2();
14421
13226
  init_constants();
14422
13227
  init_execution_tree();
14423
13228
  init_logger();
@@ -16548,6 +15353,10 @@ var init_agent = __esm({
16548
15353
  streamProcessorFactory;
16549
15354
  // LLM call lifecycle helper (encapsulates prepareLLMCall, completeLLMCall, notifyLLMError)
16550
15355
  llmCallLifecycle;
15356
+ // MCP integration — populated only when mcpSpecs were provided.
15357
+ mcpSpecs;
15358
+ mcpLifecycle = null;
15359
+ mcpDiscoveredPrompts = [];
16551
15360
  /**
16552
15361
  * Creates a new Agent instance.
16553
15362
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -16619,6 +15428,7 @@ var init_agent = __esm({
16619
15428
  );
16620
15429
  }
16621
15430
  this.signal = options.signal;
15431
+ this.mcpSpecs = options.mcpSpecs ?? [];
16622
15432
  this.reasoning = options.reasoning;
16623
15433
  this.caching = options.caching;
16624
15434
  this.retryConfig = options.sharedRetryConfig ?? resolveRetryConfig(options.retryConfig);
@@ -16878,6 +15688,18 @@ var init_agent = __esm({
16878
15688
  );
16879
15689
  }
16880
15690
  const unsubscribeBridge = bridgeTreeToHooks(this.tree, this.hooks, this.logger);
15691
+ if (this.mcpSpecs.length > 0) {
15692
+ const { setupMcpServers } = await import("./runtime-GKQ6QIQP.js");
15693
+ this.mcpLifecycle = await setupMcpServers({
15694
+ specs: this.mcpSpecs,
15695
+ registry: this.registry,
15696
+ conversation: this.conversation,
15697
+ prefixConfig: this.prefixConfig,
15698
+ systemPrompt: this.conversation.getBaseMessages()[0]?.role === "system" ? this.conversation.getBaseMessages()[0].content : void 0,
15699
+ logger: this.logger,
15700
+ onPromptDiscovered: (skill) => this.mcpDiscoveredPrompts.push(skill)
15701
+ });
15702
+ }
16881
15703
  let currentIteration = 0;
16882
15704
  this.logger.info("Starting agent loop", {
16883
15705
  model: this.model,
@@ -17077,6 +15899,14 @@ var init_agent = __esm({
17077
15899
  }
17078
15900
  }
17079
15901
  unsubscribeBridge();
15902
+ if (this.mcpLifecycle) {
15903
+ try {
15904
+ await this.mcpLifecycle.closeAll();
15905
+ } catch (err) {
15906
+ this.logger.debug("MCP lifecycle teardown error (suppressed):", err);
15907
+ }
15908
+ this.mcpLifecycle = null;
15909
+ }
17080
15910
  }
17081
15911
  }
17082
15912
  /**
@@ -17268,7 +16098,7 @@ init_builder();
17268
16098
  init_event_handlers();
17269
16099
  init_file_logging();
17270
16100
  init_hook_presets();
17271
- import { z as z6 } from "zod";
16101
+ import { z as z4 } from "zod";
17272
16102
 
17273
16103
  // src/agent/compaction/index.ts
17274
16104
  init_config();
@@ -17381,7 +16211,7 @@ function createHints(config) {
17381
16211
  init_stream_processor();
17382
16212
 
17383
16213
  // src/index.ts
17384
- init_client();
16214
+ init_client2();
17385
16215
  init_constants();
17386
16216
 
17387
16217
  // src/core/errors.ts
@@ -17446,140 +16276,254 @@ init_create_gadget();
17446
16276
  init_exceptions();
17447
16277
  init_executor();
17448
16278
  init_gadget();
16279
+ init_helpers();
16280
+ init_output_viewer();
16281
+ init_parser2();
16282
+ init_registry();
16283
+ init_typed_gadget();
17449
16284
 
17450
- // src/gadgets/helpers.ts
17451
- init_input_content();
17452
- function gadgetSuccess(data = {}) {
17453
- return JSON.stringify({ success: true, ...data });
17454
- }
17455
- function gadgetError(message, details) {
17456
- return JSON.stringify({ error: message, ...details });
17457
- }
17458
- function getErrorMessage(error) {
17459
- return error instanceof Error ? error.message : String(error);
17460
- }
17461
- function withErrorHandling(execute) {
17462
- return async (params, ctx) => {
17463
- try {
17464
- return await execute(params, ctx);
17465
- } catch (error) {
17466
- return gadgetError(getErrorMessage(error));
17467
- }
17468
- };
17469
- }
17470
- function createMediaOutput(kind, data, mimeType, options) {
17471
- const buffer = data instanceof Buffer ? data : Buffer.from(data);
16285
+ // src/mcp/index.ts
16286
+ init_allowlist();
16287
+ init_client();
16288
+ init_errors();
16289
+
16290
+ // src/mcp/gadget-exporter.ts
16291
+ init_schema_to_json();
16292
+
16293
+ // src/gadgets/validation.ts
16294
+ function validateAndApplyDefaults(schema, params) {
16295
+ const result = schema.safeParse(params);
16296
+ if (result.success) {
16297
+ return {
16298
+ success: true,
16299
+ data: result.data
16300
+ };
16301
+ }
16302
+ const issues = result.error.issues.map((issue) => ({
16303
+ path: issue.path.join(".") || "root",
16304
+ message: issue.message
16305
+ }));
16306
+ const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
17472
16307
  return {
17473
- kind,
17474
- data: buffer.toString("base64"),
17475
- mimeType,
17476
- description: options?.description,
17477
- metadata: options?.metadata,
17478
- fileName: options?.fileName
16308
+ success: false,
16309
+ error: formattedError,
16310
+ issues
17479
16311
  };
17480
16312
  }
17481
- function resultWithMedia(result, media, cost) {
17482
- if (media.length === 0) {
17483
- throw new Error("resultWithMedia: media array cannot be empty");
16313
+ function validateGadgetParams(gadget, params) {
16314
+ if (!gadget.parameterSchema) {
16315
+ return {
16316
+ success: true,
16317
+ data: params
16318
+ };
17484
16319
  }
17485
- return {
17486
- result,
17487
- media,
17488
- cost
17489
- };
16320
+ return validateAndApplyDefaults(gadget.parameterSchema, params);
17490
16321
  }
17491
- function resultWithImage(result, imageData, options) {
17492
- const buffer = imageData instanceof Buffer ? imageData : Buffer.from(imageData);
17493
- const mimeType = options?.mimeType ?? detectImageMimeType(buffer);
17494
- if (!mimeType) {
17495
- throw new Error(
17496
- "Could not detect image MIME type. Please provide mimeType explicitly in options."
17497
- );
16322
+
16323
+ // src/mcp/gadget-exporter.ts
16324
+ function gadgetToMcpTool(gadget) {
16325
+ const description = gadget.description && gadget.description.length > 0 ? gadget.description : `Native llmist gadget "${gadget.name ?? "unnamed"}"`;
16326
+ let inputSchema;
16327
+ if (gadget.parameterSchema) {
16328
+ inputSchema = schemaToJSONSchema(gadget.parameterSchema);
16329
+ } else {
16330
+ inputSchema = { type: "object", properties: {} };
17498
16331
  }
17499
16332
  return {
17500
- result,
17501
- media: [
17502
- {
17503
- kind: "image",
17504
- data: buffer.toString("base64"),
17505
- mimeType,
17506
- description: options?.description,
17507
- metadata: options?.metadata,
17508
- fileName: options?.fileName
17509
- }
17510
- ],
17511
- cost: options?.cost
16333
+ name: gadget.name ?? "unnamed-gadget",
16334
+ description,
16335
+ inputSchema
17512
16336
  };
17513
16337
  }
17514
- function resultWithImages(result, images, cost) {
17515
- if (images.length === 0) {
17516
- throw new Error("resultWithImages: images array cannot be empty");
17517
- }
17518
- const media = images.map((img, index) => {
17519
- const buffer = img.data instanceof Buffer ? img.data : Buffer.from(img.data);
17520
- const mimeType = img.mimeType ?? detectImageMimeType(buffer);
17521
- if (!mimeType) {
17522
- throw new Error(
17523
- `Could not detect MIME type for image at index ${index}. Please provide mimeType explicitly.`
17524
- );
16338
+ function gadgetResultToMcpContent(ret) {
16339
+ if (typeof ret === "string") {
16340
+ return [{ type: "text", text: ret }];
16341
+ }
16342
+ if (ret && typeof ret === "object" && "result" in ret) {
16343
+ const r = ret;
16344
+ const blocks = [];
16345
+ if (typeof r.result === "string") {
16346
+ blocks.push({ type: "text", text: r.result });
16347
+ } else {
16348
+ blocks.push({ type: "text", text: JSON.stringify(r.result) });
16349
+ }
16350
+ if (r.media) {
16351
+ for (const m of r.media) {
16352
+ if (m.kind === "image" || m.kind === "audio") {
16353
+ blocks.push({
16354
+ type: m.kind,
16355
+ data: m.data,
16356
+ mimeType: m.mimeType
16357
+ });
16358
+ }
16359
+ }
16360
+ }
16361
+ return blocks;
16362
+ }
16363
+ return [{ type: "text", text: JSON.stringify(ret) }];
16364
+ }
16365
+ async function runGadgetForMcp(gadget, rawParams) {
16366
+ if (gadget.parameterSchema) {
16367
+ const validation = validateAndApplyDefaults(
16368
+ gadget.parameterSchema,
16369
+ rawParams ?? {}
16370
+ );
16371
+ if (!validation.success) {
16372
+ return {
16373
+ isError: true,
16374
+ content: [
16375
+ {
16376
+ type: "text",
16377
+ text: `Invalid arguments for gadget "${gadget.name}": ${validation.error}`
16378
+ }
16379
+ ]
16380
+ };
17525
16381
  }
16382
+ rawParams = validation.data;
16383
+ }
16384
+ try {
16385
+ const result = await gadget.execute(rawParams);
17526
16386
  return {
17527
- kind: "image",
17528
- data: buffer.toString("base64"),
17529
- mimeType,
17530
- description: img.description,
17531
- metadata: img.metadata,
17532
- fileName: img.fileName
16387
+ content: gadgetResultToMcpContent(result)
17533
16388
  };
17534
- });
17535
- return { result, media, cost };
16389
+ } catch (err) {
16390
+ return {
16391
+ isError: true,
16392
+ content: [
16393
+ {
16394
+ type: "text",
16395
+ text: `Gadget "${gadget.name}" failed: ${err.message}`
16396
+ }
16397
+ ]
16398
+ };
16399
+ }
17536
16400
  }
17537
- function resultWithAudio(result, audioData, options) {
17538
- const buffer = audioData instanceof Buffer ? audioData : Buffer.from(audioData);
17539
- const mimeType = options?.mimeType ?? detectAudioMimeType(buffer);
17540
- if (!mimeType) {
17541
- throw new Error(
17542
- "Could not detect audio MIME type. Please provide mimeType explicitly in options."
17543
- );
16401
+
16402
+ // src/mcp/index.ts
16403
+ init_json_schema_to_zod();
16404
+ init_lifecycle();
16405
+
16406
+ // src/mcp/skill-exporter.ts
16407
+ function skillToMcpPrompt(skill) {
16408
+ const description = skill.description && skill.description.length > 0 ? skill.description : `Native llmist skill "${skill.name}"`;
16409
+ const args = [];
16410
+ if (skill.metadata.argumentHint) {
16411
+ args.push({
16412
+ name: "arguments",
16413
+ description: skill.metadata.argumentHint,
16414
+ required: false
16415
+ });
17544
16416
  }
17545
- const metadata = options?.durationMs ? { durationMs: options.durationMs } : void 0;
17546
16417
  return {
17547
- result,
17548
- media: [
16418
+ name: skill.name,
16419
+ description,
16420
+ ...args.length > 0 ? { arguments: args } : {}
16421
+ };
16422
+ }
16423
+ async function renderSkillForMcpPrompt(skill, args) {
16424
+ const argString = typeof args.arguments === "string" ? args.arguments : Object.values(args).filter((v) => typeof v === "string").join(" ");
16425
+ const activation = await skill.activate({
16426
+ arguments: argString || void 0
16427
+ });
16428
+ return {
16429
+ description: skill.description,
16430
+ messages: [
17549
16431
  {
17550
- kind: "audio",
17551
- data: buffer.toString("base64"),
17552
- mimeType,
17553
- description: options?.description,
17554
- metadata,
17555
- fileName: options?.fileName
16432
+ role: "user",
16433
+ content: { type: "text", text: activation.resolvedInstructions }
17556
16434
  }
17557
- ],
17558
- cost: options?.cost
16435
+ ]
17559
16436
  };
17560
16437
  }
17561
- function resultWithFile(result, fileData, mimeType, options) {
17562
- const buffer = fileData instanceof Buffer ? fileData : Buffer.from(fileData);
16438
+
16439
+ // src/mcp/server.ts
16440
+ var DEFAULT_SERVER_INFO = { name: "llmist", version: "0.0.0" };
16441
+ function createMcpServer(opts) {
16442
+ const { gadgets, skills } = opts;
16443
+ const hasTools = gadgets.getAll().length > 0;
16444
+ const hasPrompts = !!skills && skills.size > 0;
16445
+ const capabilities = {};
16446
+ if (hasTools) capabilities.tools = {};
16447
+ if (hasPrompts) capabilities.prompts = {};
16448
+ let sdkServer = null;
16449
+ let running = false;
16450
+ async function ensureServer() {
16451
+ if (sdkServer) return sdkServer;
16452
+ const [serverMod, typesMod] = await Promise.all([
16453
+ import("@modelcontextprotocol/sdk/server/index.js"),
16454
+ import("@modelcontextprotocol/sdk/types.js")
16455
+ ]);
16456
+ const ServerClass = serverMod.Server;
16457
+ const server = new ServerClass(opts.serverInfo ?? DEFAULT_SERVER_INFO, {
16458
+ capabilities
16459
+ });
16460
+ if (hasTools) {
16461
+ server.setRequestHandler(typesMod.ListToolsRequestSchema, async () => ({
16462
+ tools: gadgets.getAll().map(gadgetToMcpTool)
16463
+ }));
16464
+ server.setRequestHandler(
16465
+ typesMod.CallToolRequestSchema,
16466
+ async (req) => {
16467
+ const gadget = gadgets.get(req.params.name);
16468
+ if (!gadget) {
16469
+ return {
16470
+ isError: true,
16471
+ content: [
16472
+ {
16473
+ type: "text",
16474
+ text: `Unknown tool "${req.params.name}". Call tools/list first.`
16475
+ }
16476
+ ]
16477
+ };
16478
+ }
16479
+ return runGadgetForMcp(gadget, req.params.arguments ?? {});
16480
+ }
16481
+ );
16482
+ }
16483
+ if (hasPrompts && skills) {
16484
+ server.setRequestHandler(typesMod.ListPromptsRequestSchema, async () => ({
16485
+ prompts: Array.from(skills.getAll()).map(skillToMcpPrompt)
16486
+ }));
16487
+ server.setRequestHandler(
16488
+ typesMod.GetPromptRequestSchema,
16489
+ async (req) => {
16490
+ const skill = skills.get(req.params.name);
16491
+ if (!skill) {
16492
+ throw new Error(`Unknown prompt "${req.params.name}"`);
16493
+ }
16494
+ const result = await renderSkillForMcpPrompt(skill, req.params.arguments ?? {});
16495
+ return result;
16496
+ }
16497
+ );
16498
+ }
16499
+ sdkServer = server;
16500
+ return server;
16501
+ }
17563
16502
  return {
17564
- result,
17565
- media: [
17566
- {
17567
- kind: "file",
17568
- data: buffer.toString("base64"),
17569
- mimeType,
17570
- description: options?.description,
17571
- fileName: options?.fileName
16503
+ get running() {
16504
+ return running;
16505
+ },
16506
+ async connect(transport) {
16507
+ const server = await ensureServer();
16508
+ await server.connect(transport);
16509
+ running = true;
16510
+ },
16511
+ async stop() {
16512
+ if (!sdkServer) return;
16513
+ try {
16514
+ await sdkServer.close();
16515
+ } catch {
17572
16516
  }
17573
- ],
17574
- cost: options?.cost
16517
+ sdkServer = null;
16518
+ running = false;
16519
+ }
17575
16520
  };
17576
16521
  }
17577
16522
 
16523
+ // src/mcp/index.ts
16524
+ init_tool_adapter();
16525
+
17578
16526
  // src/index.ts
17579
- init_output_viewer();
17580
- init_parser2();
17581
- init_registry();
17582
- init_typed_gadget();
17583
16527
  init_constants2();
17584
16528
 
17585
16529
  // src/utils/config-resolver.ts
@@ -17688,38 +16632,6 @@ function hasHostExports(ctx) {
17688
16632
  init_media_store();
17689
16633
  init_schema_to_json();
17690
16634
  init_schema_validator();
17691
-
17692
- // src/gadgets/validation.ts
17693
- function validateAndApplyDefaults(schema, params) {
17694
- const result = schema.safeParse(params);
17695
- if (result.success) {
17696
- return {
17697
- success: true,
17698
- data: result.data
17699
- };
17700
- }
17701
- const issues = result.error.issues.map((issue) => ({
17702
- path: issue.path.join(".") || "root",
17703
- message: issue.message
17704
- }));
17705
- const formattedError = `Invalid parameters: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`;
17706
- return {
17707
- success: false,
17708
- error: formattedError,
17709
- issues
17710
- };
17711
- }
17712
- function validateGadgetParams(gadget, params) {
17713
- if (!gadget.parameterSchema) {
17714
- return {
17715
- success: true,
17716
- data: params
17717
- };
17718
- }
17719
- return validateAndApplyDefaults(gadget.parameterSchema, params);
17720
- }
17721
-
17722
- // src/index.ts
17723
16635
  init_logger();
17724
16636
 
17725
16637
  // src/package/manifest.ts
@@ -18017,6 +16929,7 @@ export {
18017
16929
  ConversationManager,
18018
16930
  DEFAULT_COMPACTION_CONFIG,
18019
16931
  DEFAULT_HINTS,
16932
+ DEFAULT_MCP_COMMAND_ALLOWLIST,
18020
16933
  DEFAULT_PROMPTS,
18021
16934
  DEFAULT_RATE_LIMIT_CONFIG,
18022
16935
  DEFAULT_RETRY_CONFIG,
@@ -18036,10 +16949,17 @@ export {
18036
16949
  HuggingFaceProvider,
18037
16950
  HumanInputRequiredException,
18038
16951
  HybridStrategy,
16952
+ JsonSchemaConversionError,
18039
16953
  LLMMessageBuilder,
18040
16954
  LLMist,
18041
16955
  LOAD_SKILL_GADGET_NAME,
18042
16956
  MODEL_ALIASES,
16957
+ McpClient,
16958
+ McpConnectError,
16959
+ McpError,
16960
+ McpLifecycle,
16961
+ McpToolCallError,
16962
+ McpUntrustedCommandError,
18043
16963
  MediaStore,
18044
16964
  ModelIdentifierParser,
18045
16965
  ModelRegistry,
@@ -18055,6 +16975,7 @@ export {
18055
16975
  SummarizationStrategy,
18056
16976
  TaskCompletionSignal,
18057
16977
  TimeoutException,
16978
+ assertCommandAllowed,
18058
16979
  audioFromBase64,
18059
16980
  audioFromBuffer,
18060
16981
  collectEvents,
@@ -18069,6 +16990,7 @@ export {
18069
16990
  createHuggingFaceProviderFromEnv,
18070
16991
  createLoadSkillGadget,
18071
16992
  createLogger,
16993
+ createMcpServer,
18072
16994
  createMediaOutput,
18073
16995
  createOpenAIProviderFromEnv,
18074
16996
  createOpenRouterProviderFromEnv,
@@ -18091,7 +17013,9 @@ export {
18091
17013
  formatLLMError,
18092
17014
  formatLlmRequest,
18093
17015
  gadgetError,
17016
+ gadgetResultToMcpContent,
18094
17017
  gadgetSuccess,
17018
+ gadgetToMcpTool,
18095
17019
  getErrorMessage,
18096
17020
  getHostExports2 as getHostExports,
18097
17021
  getModelId,
@@ -18119,9 +17043,11 @@ export {
18119
17043
  isSubagentEvent,
18120
17044
  isTextPart,
18121
17045
  iterationProgressHint,
17046
+ jsonSchemaToZod,
18122
17047
  listPresets,
18123
17048
  listSubagents,
18124
17049
  loadSkillsFromDirectory,
17050
+ mcpToolToGadget,
18125
17051
  normalizeMessageContent,
18126
17052
  parallelGadgetHint,
18127
17053
  parseDataUrl,
@@ -18132,6 +17058,7 @@ export {
18132
17058
  parseSkillContent,
18133
17059
  parseSkillFile,
18134
17060
  randomDelay,
17061
+ renderSkillForMcpPrompt,
18135
17062
  resetFileLoggingState,
18136
17063
  resolveConfig,
18137
17064
  resolveHintTemplate,
@@ -18149,9 +17076,11 @@ export {
18149
17076
  resultWithImage,
18150
17077
  resultWithImages,
18151
17078
  resultWithMedia,
17079
+ runGadgetForMcp,
18152
17080
  runWithHandlers,
18153
17081
  scanResources,
18154
17082
  schemaToJSONSchema,
17083
+ skillToMcpPrompt,
18155
17084
  stream,
18156
17085
  stripProviderPrefix,
18157
17086
  substituteArguments,
@@ -18167,6 +17096,6 @@ export {
18167
17096
  withErrorHandling,
18168
17097
  withRetry,
18169
17098
  withTimeout,
18170
- z6 as z
17099
+ z4 as z
18171
17100
  };
18172
17101
  //# sourceMappingURL=index.js.map