llmist 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,18 +45,28 @@ function parseLogLevel(value) {
45
45
  }
46
46
  return LEVEL_NAME_TO_ID[normalized];
47
47
  }
48
+ function parseEnvBoolean(value) {
49
+ if (!value) return void 0;
50
+ const normalized = value.trim().toLowerCase();
51
+ if (normalized === "true" || normalized === "1") return true;
52
+ if (normalized === "false" || normalized === "0") return false;
53
+ return void 0;
54
+ }
48
55
  function createLogger(options = {}) {
49
56
  const envMinLevel = parseLogLevel(process.env.LLMIST_LOG_LEVEL);
50
57
  const envLogFile = process.env.LLMIST_LOG_FILE?.trim() ?? "";
58
+ const envLogReset = parseEnvBoolean(process.env.LLMIST_LOG_RESET);
51
59
  const minLevel = options.minLevel ?? envMinLevel ?? 4;
52
60
  const defaultType = options.type ?? "pretty";
53
61
  const name = options.name ?? "llmist";
62
+ const logReset = options.logReset ?? envLogReset ?? false;
54
63
  let logFileStream;
55
64
  let finalType = defaultType;
56
65
  if (envLogFile) {
57
66
  try {
58
67
  (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(envLogFile), { recursive: true });
59
- logFileStream = (0, import_node_fs.createWriteStream)(envLogFile, { flags: "a" });
68
+ const flags = logReset ? "w" : "a";
69
+ logFileStream = (0, import_node_fs.createWriteStream)(envLogFile, { flags });
60
70
  finalType = "hidden";
61
71
  } catch (error) {
62
72
  console.error("Failed to initialize LLMIST_LOG_FILE output:", error);
@@ -100,12 +110,16 @@ var init_logger = __esm({
100
110
  });
101
111
 
102
112
  // src/core/constants.ts
103
- var GADGET_START_PREFIX, GADGET_END_PREFIX;
113
+ var GADGET_START_PREFIX, GADGET_END_PREFIX, DEFAULT_GADGET_OUTPUT_LIMIT, DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT, CHARS_PER_TOKEN, FALLBACK_CONTEXT_WINDOW;
104
114
  var init_constants = __esm({
105
115
  "src/core/constants.ts"() {
106
116
  "use strict";
107
117
  GADGET_START_PREFIX = "!!!GADGET_START:";
108
118
  GADGET_END_PREFIX = "!!!GADGET_END";
119
+ DEFAULT_GADGET_OUTPUT_LIMIT = true;
120
+ DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT = 15;
121
+ CHARS_PER_TOKEN = 4;
122
+ FALLBACK_CONTEXT_WINDOW = 128e3;
109
123
  }
110
124
  });
111
125
 
@@ -436,7 +450,7 @@ var init_prompt_config = __esm({
436
450
  criticalUsage: "INVOKE gadgets using the markers - do not describe what you want to do.",
437
451
  formatDescriptionYaml: "Parameters in YAML format (one per line)",
438
452
  formatDescriptionJson: "Parameters in JSON format (valid JSON object)",
439
- formatDescriptionToml: "Parameters in TOML format (key = value pairs, use triple-quotes for multiline)",
453
+ formatDescriptionToml: "Parameters in TOML format (key = value pairs, use heredoc for multiline: key = <<<EOF ... EOF)",
440
454
  rules: () => [
441
455
  "Output ONLY plain text with the exact markers - never use function/tool calling",
442
456
  "You can invoke multiple gadgets in a single response",
@@ -617,10 +631,11 @@ ${this.endPrefix}
617
631
  ${this.startPrefix}analyze
618
632
  type: economic_analysis
619
633
  matter: "Polish Economy"
620
- question: |
621
- Analyze the following:
622
- - Polish arms exports 2025
623
- - Economic implications
634
+ question: <<<EOF
635
+ Analyze the following:
636
+ - Polish arms exports 2025
637
+ - Economic implications
638
+ EOF
624
639
  ${this.endPrefix}`,
625
640
  json: `${this.startPrefix}translate
626
641
  {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
@@ -636,11 +651,11 @@ ${this.endPrefix}
636
651
  ${this.startPrefix}analyze
637
652
  type = "economic_analysis"
638
653
  matter = "Polish Economy"
639
- question = """
654
+ question = <<<EOF
640
655
  Analyze the following:
641
656
  - Polish arms exports 2025
642
657
  - Economic implications
643
- """
658
+ EOF
644
659
  ${this.endPrefix}`,
645
660
  auto: `${this.startPrefix}translate
646
661
  {"from": "English", "to": "Polish", "content": "Paris is the capital of France: a beautiful city."}
@@ -657,37 +672,38 @@ ${multipleExamples[parameterFormat]}`);
657
672
  if (parameterFormat === "yaml") {
658
673
  parts.push(`
659
674
 
660
- YAML MULTILINE SYNTAX:
661
- For string values with special characters (colons, dashes, quotes) or multiple lines,
662
- use the pipe (|) syntax. ALL content lines MUST be indented with 2 spaces:
675
+ YAML HEREDOC SYNTAX:
676
+ For string values with multiple lines, use heredoc syntax (<<<DELIMITER...DELIMITER):
663
677
 
664
- CORRECT - all lines indented:
665
- question: |
666
- Which option do you prefer?
667
- - Option A: fast processing
668
- - Option B: thorough analysis
669
- Please choose one.
678
+ filePath: "README.md"
679
+ content: <<<EOF
680
+ # Project Title
670
681
 
671
- WRONG - inconsistent indentation breaks YAML:
672
- question: |
673
- Which option do you prefer?
674
- - Option A: fast
675
- Please choose one. <-- ERROR: not indented, breaks out of the block`);
682
+ This content can contain:
683
+ - Markdown lists
684
+ - Special characters: # : -
685
+ - Multiple paragraphs
686
+ EOF
687
+
688
+ The delimiter (EOF) can be any identifier. The closing delimiter must be on its own line.
689
+ No indentation is required for the content.`);
676
690
  } else if (parameterFormat === "toml") {
677
691
  parts.push(`
678
692
 
679
- TOML MULTILINE SYNTAX:
680
- For string values with multiple lines or special characters, use triple-quotes ("""):
693
+ TOML HEREDOC SYNTAX:
694
+ For string values with multiple lines, use heredoc syntax (<<<DELIMITER...DELIMITER):
681
695
 
682
696
  filePath = "README.md"
683
- content = """
697
+ content = <<<EOF
684
698
  # Project Title
685
699
 
686
700
  This content can contain:
687
701
  - Markdown lists
688
702
  - Special characters: # : -
689
703
  - Multiple paragraphs
690
- """`);
704
+ EOF
705
+
706
+ The delimiter (EOF) can be any identifier. The closing delimiter must be on its own line.`);
691
707
  }
692
708
  return parts.join("");
693
709
  }
@@ -744,8 +760,534 @@ ${value}
744
760
  }
745
761
  return JSON.stringify(parameters);
746
762
  }
747
- build() {
748
- return [...this.messages];
763
+ build() {
764
+ return [...this.messages];
765
+ }
766
+ };
767
+ }
768
+ });
769
+
770
+ // src/gadgets/schema-to-json.ts
771
+ function schemaToJSONSchema(schema, options) {
772
+ const jsonSchema = z2.toJSONSchema(schema, options ?? { target: "draft-7" });
773
+ const mismatches = detectDescriptionMismatch(schema, jsonSchema);
774
+ if (mismatches.length > 0) {
775
+ defaultLogger.warn(
776
+ `Zod instance mismatch detected: ${mismatches.length} description(s) lost. For best results, use: import { z } from "llmist"`
777
+ );
778
+ return mergeDescriptions(schema, jsonSchema);
779
+ }
780
+ return jsonSchema;
781
+ }
782
+ function detectDescriptionMismatch(schema, jsonSchema) {
783
+ const mismatches = [];
784
+ function checkSchema(zodSchema, json, path) {
785
+ if (!zodSchema || typeof zodSchema !== "object") return;
786
+ const def = zodSchema._def;
787
+ const jsonObj = json;
788
+ if (def?.description && !jsonObj?.description) {
789
+ mismatches.push(path || "root");
790
+ }
791
+ if (def?.typeName === "ZodObject" && def?.shape) {
792
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
793
+ for (const [key, fieldSchema] of Object.entries(shape)) {
794
+ const properties = jsonObj?.properties;
795
+ const jsonProp = properties?.[key];
796
+ checkSchema(fieldSchema, jsonProp, path ? `${path}.${key}` : key);
797
+ }
798
+ }
799
+ if (def?.typeName === "ZodArray" && def?.type) {
800
+ checkSchema(def.type, jsonObj?.items, path ? `${path}[]` : "[]");
801
+ }
802
+ if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
803
+ checkSchema(def.innerType, json, path);
804
+ }
805
+ if (def?.typeName === "ZodDefault" && def?.innerType) {
806
+ checkSchema(def.innerType, json, path);
807
+ }
808
+ }
809
+ checkSchema(schema, jsonSchema, "");
810
+ return mismatches;
811
+ }
812
+ function mergeDescriptions(schema, jsonSchema) {
813
+ function merge(zodSchema, json) {
814
+ if (!json || typeof json !== "object") return json;
815
+ const def = zodSchema._def;
816
+ const jsonObj = json;
817
+ const merged = { ...jsonObj };
818
+ if (def?.description && !jsonObj.description) {
819
+ merged.description = def.description;
820
+ }
821
+ if (def?.typeName === "ZodObject" && def?.shape && jsonObj.properties) {
822
+ const shape = typeof def.shape === "function" ? def.shape() : def.shape;
823
+ const properties = jsonObj.properties;
824
+ merged.properties = { ...properties };
825
+ for (const [key, fieldSchema] of Object.entries(shape)) {
826
+ if (properties[key]) {
827
+ merged.properties[key] = merge(fieldSchema, properties[key]);
828
+ }
829
+ }
830
+ }
831
+ if (def?.typeName === "ZodArray" && def?.type && jsonObj.items) {
832
+ merged.items = merge(def.type, jsonObj.items);
833
+ }
834
+ if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
835
+ return merge(def.innerType, json);
836
+ }
837
+ if (def?.typeName === "ZodDefault" && def?.innerType) {
838
+ return merge(def.innerType, json);
839
+ }
840
+ return merged;
841
+ }
842
+ return merge(schema, jsonSchema);
843
+ }
844
+ var z2;
845
+ var init_schema_to_json = __esm({
846
+ "src/gadgets/schema-to-json.ts"() {
847
+ "use strict";
848
+ z2 = __toESM(require("zod"), 1);
849
+ init_logger();
850
+ }
851
+ });
852
+
853
+ // src/gadgets/gadget.ts
854
+ function findSafeDelimiter(content) {
855
+ const lines = content.split("\n");
856
+ for (const delimiter of HEREDOC_DELIMITERS) {
857
+ const regex = new RegExp(`^${delimiter}\\s*$`);
858
+ const isUsed = lines.some((line) => regex.test(line));
859
+ if (!isUsed) {
860
+ return delimiter;
861
+ }
862
+ }
863
+ let counter = 1;
864
+ while (counter < 1e3) {
865
+ const delimiter = `HEREDOC_${counter}`;
866
+ const regex = new RegExp(`^${delimiter}\\s*$`);
867
+ const isUsed = lines.some((line) => regex.test(line));
868
+ if (!isUsed) {
869
+ return delimiter;
870
+ }
871
+ counter++;
872
+ }
873
+ return "HEREDOC_FALLBACK";
874
+ }
875
+ function formatYamlValue(value, indent = "") {
876
+ if (typeof value === "string") {
877
+ const lines = value.split("\n");
878
+ if (lines.length === 1 && !value.includes(":") && !value.startsWith("-")) {
879
+ return value;
880
+ }
881
+ const delimiter = findSafeDelimiter(value);
882
+ return `<<<${delimiter}
883
+ ${value}
884
+ ${delimiter}`;
885
+ }
886
+ if (typeof value === "number" || typeof value === "boolean") {
887
+ return String(value);
888
+ }
889
+ if (value === null || value === void 0) {
890
+ return "null";
891
+ }
892
+ if (Array.isArray(value)) {
893
+ if (value.length === 0) return "[]";
894
+ const items = value.map((item) => `${indent}- ${formatYamlValue(item, indent + " ")}`);
895
+ return "\n" + items.join("\n");
896
+ }
897
+ if (typeof value === "object") {
898
+ const entries = Object.entries(value);
899
+ if (entries.length === 0) return "{}";
900
+ const lines = entries.map(([k, v]) => {
901
+ const formattedValue = formatYamlValue(v, indent + " ");
902
+ if (formattedValue.startsWith("\n") || formattedValue.startsWith("|")) {
903
+ return `${indent}${k}: ${formattedValue}`;
904
+ }
905
+ return `${indent}${k}: ${formattedValue}`;
906
+ });
907
+ return "\n" + lines.join("\n");
908
+ }
909
+ return yaml.dump(value).trimEnd();
910
+ }
911
+ function formatParamsAsYaml(params) {
912
+ const lines = [];
913
+ for (const [key, value] of Object.entries(params)) {
914
+ const formattedValue = formatYamlValue(value, "");
915
+ if (formattedValue.startsWith("\n")) {
916
+ lines.push(`${key}:${formattedValue}`);
917
+ } else {
918
+ lines.push(`${key}: ${formattedValue}`);
919
+ }
920
+ }
921
+ return lines.join("\n");
922
+ }
923
+ function formatTomlValue(value) {
924
+ if (typeof value === "string") {
925
+ if (value.includes("\n")) {
926
+ const delimiter = findSafeDelimiter(value);
927
+ return `<<<${delimiter}
928
+ ${value}
929
+ ${delimiter}`;
930
+ }
931
+ return JSON.stringify(value);
932
+ }
933
+ if (typeof value === "number" || typeof value === "boolean") {
934
+ return String(value);
935
+ }
936
+ if (value === null || value === void 0) {
937
+ return '""';
938
+ }
939
+ if (Array.isArray(value)) {
940
+ return JSON.stringify(value);
941
+ }
942
+ if (typeof value === "object") {
943
+ return JSON.stringify(value);
944
+ }
945
+ return JSON.stringify(value);
946
+ }
947
+ function formatParamsAsToml(params) {
948
+ const lines = [];
949
+ for (const [key, value] of Object.entries(params)) {
950
+ lines.push(`${key} = ${formatTomlValue(value)}`);
951
+ }
952
+ return lines.join("\n");
953
+ }
954
+ var yaml, HEREDOC_DELIMITERS, BaseGadget;
955
+ var init_gadget = __esm({
956
+ "src/gadgets/gadget.ts"() {
957
+ "use strict";
958
+ yaml = __toESM(require("js-yaml"), 1);
959
+ init_schema_to_json();
960
+ init_schema_validator();
961
+ HEREDOC_DELIMITERS = ["EOF", "END", "DOC", "CONTENT", "TEXT", "HEREDOC", "DATA", "BLOCK"];
962
+ BaseGadget = class {
963
+ /**
964
+ * The name of the gadget. Used for identification when LLM calls it.
965
+ * If not provided, defaults to the class name.
966
+ */
967
+ name;
968
+ /**
969
+ * Optional Zod schema describing the expected input payload. When provided,
970
+ * it will be validated before execution and transformed into a JSON Schema
971
+ * representation that is surfaced to the LLM as part of the instructions.
972
+ */
973
+ parameterSchema;
974
+ /**
975
+ * Optional timeout in milliseconds for gadget execution.
976
+ * If execution exceeds this timeout, a TimeoutException will be thrown.
977
+ * If not set, the global defaultGadgetTimeoutMs from runtime options will be used.
978
+ * Set to 0 or undefined to disable timeout for this gadget.
979
+ */
980
+ timeoutMs;
981
+ /**
982
+ * Optional usage examples to help LLMs understand proper invocation.
983
+ * Examples are rendered in getInstruction() alongside the schema.
984
+ *
985
+ * Note: Uses broader `unknown` type to allow typed examples from subclasses
986
+ * while maintaining runtime compatibility.
987
+ */
988
+ examples;
989
+ /**
990
+ * Auto-generated instruction text for the LLM.
991
+ * Combines name, description, and parameter schema into a formatted instruction.
992
+ * @deprecated Use getInstruction(format) instead for format-specific schemas
993
+ */
994
+ get instruction() {
995
+ return this.getInstruction("yaml");
996
+ }
997
+ /**
998
+ * Generate instruction text for the LLM with format-specific schema.
999
+ * Combines name, description, and parameter schema into a formatted instruction.
1000
+ *
1001
+ * @param format - Format for the schema representation ('json' | 'yaml' | 'toml' | 'auto')
1002
+ * @returns Formatted instruction string
1003
+ */
1004
+ getInstruction(format = "json") {
1005
+ const parts = [];
1006
+ parts.push(this.description);
1007
+ if (this.parameterSchema) {
1008
+ const gadgetName = this.name ?? this.constructor.name;
1009
+ validateGadgetSchema(this.parameterSchema, gadgetName);
1010
+ const jsonSchema = schemaToJSONSchema(this.parameterSchema, {
1011
+ target: "draft-7"
1012
+ });
1013
+ if (format === "json" || format === "auto") {
1014
+ parts.push("\n\nInput Schema (JSON):");
1015
+ parts.push(JSON.stringify(jsonSchema, null, 2));
1016
+ } else if (format === "toml") {
1017
+ parts.push("\n\nInput Schema (TOML):");
1018
+ parts.push(JSON.stringify(jsonSchema, null, 2));
1019
+ } else {
1020
+ const yamlSchema = yaml.dump(jsonSchema).trimEnd();
1021
+ parts.push("\n\nInput Schema (YAML):");
1022
+ parts.push(yamlSchema);
1023
+ }
1024
+ }
1025
+ if (this.examples && this.examples.length > 0) {
1026
+ parts.push("\n\nExamples:");
1027
+ this.examples.forEach((example, index) => {
1028
+ if (index > 0) {
1029
+ parts.push("");
1030
+ }
1031
+ if (example.comment) {
1032
+ parts.push(`# ${example.comment}`);
1033
+ }
1034
+ parts.push("Input:");
1035
+ if (format === "json" || format === "auto") {
1036
+ parts.push(JSON.stringify(example.params, null, 2));
1037
+ } else if (format === "toml") {
1038
+ parts.push(formatParamsAsToml(example.params));
1039
+ } else {
1040
+ parts.push(formatParamsAsYaml(example.params));
1041
+ }
1042
+ if (example.output !== void 0) {
1043
+ parts.push("Output:");
1044
+ parts.push(example.output);
1045
+ }
1046
+ });
1047
+ }
1048
+ return parts.join("\n");
1049
+ }
1050
+ };
1051
+ }
1052
+ });
1053
+
1054
+ // src/gadgets/create-gadget.ts
1055
+ function createGadget(config) {
1056
+ class DynamicGadget extends BaseGadget {
1057
+ name = config.name;
1058
+ description = config.description;
1059
+ parameterSchema = config.schema;
1060
+ timeoutMs = config.timeoutMs;
1061
+ examples = config.examples;
1062
+ execute(params) {
1063
+ return config.execute(params);
1064
+ }
1065
+ }
1066
+ return new DynamicGadget();
1067
+ }
1068
+ var init_create_gadget = __esm({
1069
+ "src/gadgets/create-gadget.ts"() {
1070
+ "use strict";
1071
+ init_gadget();
1072
+ }
1073
+ });
1074
+
1075
+ // src/gadgets/output-viewer.ts
1076
+ function applyPattern(lines, pattern) {
1077
+ const regex = new RegExp(pattern.regex);
1078
+ if (!pattern.include) {
1079
+ return lines.filter((line) => !regex.test(line));
1080
+ }
1081
+ const matchIndices = /* @__PURE__ */ new Set();
1082
+ for (let i = 0; i < lines.length; i++) {
1083
+ if (regex.test(lines[i])) {
1084
+ const start = Math.max(0, i - pattern.before);
1085
+ const end = Math.min(lines.length - 1, i + pattern.after);
1086
+ for (let j = start; j <= end; j++) {
1087
+ matchIndices.add(j);
1088
+ }
1089
+ }
1090
+ }
1091
+ return lines.filter((_, index) => matchIndices.has(index));
1092
+ }
1093
+ function applyPatterns(lines, patterns) {
1094
+ let result = lines;
1095
+ for (const pattern of patterns) {
1096
+ result = applyPattern(result, pattern);
1097
+ }
1098
+ return result;
1099
+ }
1100
+ function applyLineLimit(lines, limit) {
1101
+ const trimmed = limit.trim();
1102
+ if (trimmed.endsWith("-") && !trimmed.startsWith("-")) {
1103
+ const n = parseInt(trimmed.slice(0, -1), 10);
1104
+ if (!isNaN(n) && n > 0) {
1105
+ return lines.slice(0, n);
1106
+ }
1107
+ }
1108
+ if (trimmed.startsWith("-") && !trimmed.includes("-", 1)) {
1109
+ const n = parseInt(trimmed, 10);
1110
+ if (!isNaN(n) && n < 0) {
1111
+ return lines.slice(n);
1112
+ }
1113
+ }
1114
+ const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
1115
+ if (rangeMatch) {
1116
+ const start = parseInt(rangeMatch[1], 10);
1117
+ const end = parseInt(rangeMatch[2], 10);
1118
+ if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start) {
1119
+ return lines.slice(start - 1, end);
1120
+ }
1121
+ }
1122
+ return lines;
1123
+ }
1124
+ function createGadgetOutputViewer(store) {
1125
+ return createGadget({
1126
+ name: "GadgetOutputViewer",
1127
+ 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.",
1128
+ schema: import_zod.z.object({
1129
+ id: import_zod.z.string().describe("ID of the stored output (from the truncation message)"),
1130
+ patterns: import_zod.z.array(patternSchema).optional().describe(
1131
+ "Filter patterns applied in order (like piping through grep). Each pattern can include or exclude lines with optional before/after context."
1132
+ ),
1133
+ limit: import_zod.z.string().optional().describe(
1134
+ "Line range to return after filtering. Formats: '100-' (first 100), '-25' (last 25), '50-100' (lines 50-100)"
1135
+ )
1136
+ }),
1137
+ examples: [
1138
+ {
1139
+ comment: "View first 50 lines of stored output",
1140
+ params: { id: "Search_abc12345", limit: "50-" }
1141
+ },
1142
+ {
1143
+ comment: "Filter for error lines with context",
1144
+ params: {
1145
+ id: "Search_abc12345",
1146
+ patterns: [{ regex: "error|Error|ERROR", include: true, before: 2, after: 5 }]
1147
+ }
1148
+ },
1149
+ {
1150
+ comment: "Exclude blank lines, then show first 100",
1151
+ params: {
1152
+ id: "Search_abc12345",
1153
+ patterns: [{ regex: "^\\s*$", include: false, before: 0, after: 0 }],
1154
+ limit: "100-"
1155
+ }
1156
+ },
1157
+ {
1158
+ comment: "Chain filters: find TODOs, exclude tests, limit to 50 lines",
1159
+ params: {
1160
+ id: "Search_abc12345",
1161
+ patterns: [
1162
+ { regex: "TODO", include: true, before: 1, after: 1 },
1163
+ { regex: "test|spec", include: false, before: 0, after: 0 }
1164
+ ],
1165
+ limit: "50-"
1166
+ }
1167
+ }
1168
+ ],
1169
+ execute: ({ id, patterns, limit }) => {
1170
+ const stored = store.get(id);
1171
+ if (!stored) {
1172
+ return `Error: No stored output with id "${id}". Available IDs: ${store.getIds().join(", ") || "(none)"}`;
1173
+ }
1174
+ let lines = stored.content.split("\n");
1175
+ if (patterns && patterns.length > 0) {
1176
+ lines = applyPatterns(
1177
+ lines,
1178
+ patterns.map((p) => ({
1179
+ regex: p.regex,
1180
+ include: p.include ?? true,
1181
+ before: p.before ?? 0,
1182
+ after: p.after ?? 0
1183
+ }))
1184
+ );
1185
+ }
1186
+ if (limit) {
1187
+ lines = applyLineLimit(lines, limit);
1188
+ }
1189
+ const totalLines = stored.lineCount;
1190
+ const returnedLines = lines.length;
1191
+ if (returnedLines === 0) {
1192
+ return `No lines matched the filters. Original output had ${totalLines} lines.`;
1193
+ }
1194
+ const header = returnedLines < totalLines ? `[Showing ${returnedLines} of ${totalLines} lines]
1195
+ ` : `[Showing all ${totalLines} lines]
1196
+ `;
1197
+ return header + lines.join("\n");
1198
+ }
1199
+ });
1200
+ }
1201
+ var import_zod, patternSchema;
1202
+ var init_output_viewer = __esm({
1203
+ "src/gadgets/output-viewer.ts"() {
1204
+ "use strict";
1205
+ import_zod = require("zod");
1206
+ init_create_gadget();
1207
+ patternSchema = import_zod.z.object({
1208
+ regex: import_zod.z.string().describe("Regular expression to match"),
1209
+ include: import_zod.z.boolean().default(true).describe("true = keep matching lines, false = exclude matching lines"),
1210
+ before: import_zod.z.number().int().min(0).default(0).describe("Context lines before each match (like grep -B)"),
1211
+ after: import_zod.z.number().int().min(0).default(0).describe("Context lines after each match (like grep -A)")
1212
+ });
1213
+ }
1214
+ });
1215
+
1216
+ // src/agent/gadget-output-store.ts
1217
+ var import_node_crypto, GadgetOutputStore;
1218
+ var init_gadget_output_store = __esm({
1219
+ "src/agent/gadget-output-store.ts"() {
1220
+ "use strict";
1221
+ import_node_crypto = require("crypto");
1222
+ GadgetOutputStore = class {
1223
+ outputs = /* @__PURE__ */ new Map();
1224
+ /**
1225
+ * Store a gadget output and return its ID.
1226
+ *
1227
+ * @param gadgetName - Name of the gadget that produced the output
1228
+ * @param content - Full output content to store
1229
+ * @returns Generated ID for retrieving the output later
1230
+ */
1231
+ store(gadgetName, content) {
1232
+ const id = this.generateId(gadgetName);
1233
+ const encoder = new TextEncoder();
1234
+ const stored = {
1235
+ id,
1236
+ gadgetName,
1237
+ content,
1238
+ byteSize: encoder.encode(content).length,
1239
+ lineCount: content.split("\n").length,
1240
+ timestamp: /* @__PURE__ */ new Date()
1241
+ };
1242
+ this.outputs.set(id, stored);
1243
+ return id;
1244
+ }
1245
+ /**
1246
+ * Retrieve a stored output by ID.
1247
+ *
1248
+ * @param id - The output ID (e.g., "Search_d34db33f")
1249
+ * @returns The stored output or undefined if not found
1250
+ */
1251
+ get(id) {
1252
+ return this.outputs.get(id);
1253
+ }
1254
+ /**
1255
+ * Check if an output exists.
1256
+ *
1257
+ * @param id - The output ID to check
1258
+ * @returns True if the output exists
1259
+ */
1260
+ has(id) {
1261
+ return this.outputs.has(id);
1262
+ }
1263
+ /**
1264
+ * Get all stored output IDs.
1265
+ *
1266
+ * @returns Array of output IDs
1267
+ */
1268
+ getIds() {
1269
+ return Array.from(this.outputs.keys());
1270
+ }
1271
+ /**
1272
+ * Get the number of stored outputs.
1273
+ */
1274
+ get size() {
1275
+ return this.outputs.size;
1276
+ }
1277
+ /**
1278
+ * Clear all stored outputs.
1279
+ * Called when the agent run completes.
1280
+ */
1281
+ clear() {
1282
+ this.outputs.clear();
1283
+ }
1284
+ /**
1285
+ * Generate a unique ID for a stored output.
1286
+ * Format: {GadgetName}_{8 hex chars}
1287
+ */
1288
+ generateId(gadgetName) {
1289
+ const hex = (0, import_node_crypto.randomBytes)(4).toString("hex");
1290
+ return `${gadgetName}_${hex}`;
749
1291
  }
750
1292
  };
751
1293
  }
@@ -1258,6 +1800,25 @@ function preprocessYaml(yamlStr) {
1258
1800
  let i = 0;
1259
1801
  while (i < lines.length) {
1260
1802
  const line = lines[i];
1803
+ const heredocMatch = line.match(/^(\s*)([\w-]+):\s*<<<([A-Za-z_][A-Za-z0-9_]*)\s*$/);
1804
+ if (heredocMatch) {
1805
+ const [, indent, key, delimiter] = heredocMatch;
1806
+ const bodyLines = [];
1807
+ i++;
1808
+ const closingRegex = new RegExp(`^${delimiter}\\s*$`);
1809
+ while (i < lines.length && !closingRegex.test(lines[i])) {
1810
+ bodyLines.push(lines[i]);
1811
+ i++;
1812
+ }
1813
+ if (i < lines.length) {
1814
+ i++;
1815
+ }
1816
+ result.push(`${indent}${key}: |`);
1817
+ for (const bodyLine of bodyLines) {
1818
+ result.push(`${indent} ${bodyLine}`);
1819
+ }
1820
+ continue;
1821
+ }
1261
1822
  const match = line.match(/^(\s*)([\w-]+):\s+(.+)$/);
1262
1823
  if (match) {
1263
1824
  const [, indent, key, value] = match;
@@ -1349,11 +1910,53 @@ function preprocessYaml(yamlStr) {
1349
1910
  }
1350
1911
  return result.join("\n");
1351
1912
  }
1352
- var yaml, import_js_toml, globalInvocationCounter, StreamParser;
1913
+ function preprocessTomlHeredoc(tomlStr) {
1914
+ const lines = tomlStr.split("\n");
1915
+ const result = [];
1916
+ let i = 0;
1917
+ const heredocStartRegex = /^(\s*)([\w-]+)\s*=\s*<<<([A-Za-z_][A-Za-z0-9_]*)\s*$/;
1918
+ while (i < lines.length) {
1919
+ const line = lines[i];
1920
+ const match = line.match(heredocStartRegex);
1921
+ if (match) {
1922
+ const [, indent, key, delimiter] = match;
1923
+ const bodyLines = [];
1924
+ i++;
1925
+ const closingRegex = new RegExp(`^${delimiter}\\s*$`);
1926
+ let foundClosing = false;
1927
+ while (i < lines.length) {
1928
+ const bodyLine = lines[i];
1929
+ if (closingRegex.test(bodyLine)) {
1930
+ foundClosing = true;
1931
+ i++;
1932
+ break;
1933
+ }
1934
+ bodyLines.push(bodyLine);
1935
+ i++;
1936
+ }
1937
+ if (bodyLines.length === 0) {
1938
+ result.push(`${indent}${key} = """"""`);
1939
+ } else {
1940
+ result.push(`${indent}${key} = """`);
1941
+ for (let j = 0; j < bodyLines.length - 1; j++) {
1942
+ result.push(bodyLines[j]);
1943
+ }
1944
+ result.push(`${bodyLines[bodyLines.length - 1]}"""`);
1945
+ }
1946
+ if (!foundClosing) {
1947
+ }
1948
+ continue;
1949
+ }
1950
+ result.push(line);
1951
+ i++;
1952
+ }
1953
+ return result.join("\n");
1954
+ }
1955
+ var yaml2, import_js_toml, globalInvocationCounter, StreamParser;
1353
1956
  var init_parser = __esm({
1354
1957
  "src/gadgets/parser.ts"() {
1355
1958
  "use strict";
1356
- yaml = __toESM(require("js-yaml"), 1);
1959
+ yaml2 = __toESM(require("js-yaml"), 1);
1357
1960
  import_js_toml = require("js-toml");
1358
1961
  init_constants();
1359
1962
  globalInvocationCounter = 0;
@@ -1400,14 +2003,14 @@ var init_parser = __esm({
1400
2003
  }
1401
2004
  if (this.parameterFormat === "yaml") {
1402
2005
  try {
1403
- return { parameters: yaml.load(preprocessYaml(raw)) };
2006
+ return { parameters: yaml2.load(preprocessYaml(raw)) };
1404
2007
  } catch (error) {
1405
2008
  return { parseError: error instanceof Error ? error.message : "Failed to parse YAML" };
1406
2009
  }
1407
2010
  }
1408
2011
  if (this.parameterFormat === "toml") {
1409
2012
  try {
1410
- return { parameters: (0, import_js_toml.load)(raw) };
2013
+ return { parameters: (0, import_js_toml.load)(preprocessTomlHeredoc(raw)) };
1411
2014
  } catch (error) {
1412
2015
  return { parseError: error instanceof Error ? error.message : "Failed to parse TOML" };
1413
2016
  }
@@ -1416,10 +2019,10 @@ var init_parser = __esm({
1416
2019
  return { parameters: JSON.parse(raw) };
1417
2020
  } catch {
1418
2021
  try {
1419
- return { parameters: (0, import_js_toml.load)(raw) };
2022
+ return { parameters: (0, import_js_toml.load)(preprocessTomlHeredoc(raw)) };
1420
2023
  } catch {
1421
2024
  try {
1422
- return { parameters: yaml.load(preprocessYaml(raw)) };
2025
+ return { parameters: yaml2.load(preprocessYaml(raw)) };
1423
2026
  } catch (error) {
1424
2027
  return {
1425
2028
  parseError: error instanceof Error ? error.message : "Failed to parse as JSON, TOML, or YAML"
@@ -1941,9 +2544,12 @@ var Agent;
1941
2544
  var init_agent = __esm({
1942
2545
  "src/agent/agent.ts"() {
1943
2546
  "use strict";
2547
+ init_constants();
1944
2548
  init_messages();
1945
2549
  init_model_shortcuts();
2550
+ init_output_viewer();
1946
2551
  init_logger();
2552
+ init_gadget_output_store();
1947
2553
  init_agent_internal_key();
1948
2554
  init_conversation_manager();
1949
2555
  init_event_handlers();
@@ -1968,6 +2574,10 @@ var init_agent = __esm({
1968
2574
  defaultGadgetTimeoutMs;
1969
2575
  defaultMaxTokens;
1970
2576
  userPromptProvided;
2577
+ // Gadget output limiting
2578
+ outputStore;
2579
+ outputLimitEnabled;
2580
+ outputLimitCharLimit;
1971
2581
  /**
1972
2582
  * Creates a new Agent instance.
1973
2583
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -1983,7 +2593,6 @@ var init_agent = __esm({
1983
2593
  this.maxIterations = options.maxIterations ?? 10;
1984
2594
  this.temperature = options.temperature;
1985
2595
  this.logger = options.logger ?? createLogger({ name: "llmist:agent" });
1986
- this.hooks = options.hooks ?? {};
1987
2596
  this.registry = options.registry;
1988
2597
  this.parameterFormat = options.parameterFormat ?? "json";
1989
2598
  this.gadgetStartPrefix = options.gadgetStartPrefix;
@@ -1994,6 +2603,16 @@ var init_agent = __esm({
1994
2603
  this.shouldContinueAfterError = options.shouldContinueAfterError;
1995
2604
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
1996
2605
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
2606
+ this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
2607
+ this.outputStore = new GadgetOutputStore();
2608
+ const limitPercent = options.gadgetOutputLimitPercent ?? DEFAULT_GADGET_OUTPUT_LIMIT_PERCENT;
2609
+ const limits = this.client.modelRegistry.getModelLimits(this.model);
2610
+ const contextWindow = limits?.contextWindow ?? FALLBACK_CONTEXT_WINDOW;
2611
+ this.outputLimitCharLimit = Math.floor(contextWindow * (limitPercent / 100) * CHARS_PER_TOKEN);
2612
+ if (this.outputLimitEnabled) {
2613
+ this.registry.register("GadgetOutputViewer", createGadgetOutputViewer(this.outputStore));
2614
+ }
2615
+ this.hooks = this.mergeOutputLimiterHook(options.hooks);
1997
2616
  const baseBuilder = new LLMMessageBuilder(options.promptConfig);
1998
2617
  if (options.systemPrompt) {
1999
2618
  baseBuilder.addSystem(options.systemPrompt);
@@ -2298,6 +2917,43 @@ var init_agent = __esm({
2298
2917
  }
2299
2918
  return this.client.modelRegistry.getModelLimits(unprefixedModelId)?.maxOutputTokens;
2300
2919
  }
2920
+ /**
2921
+ * Merge the output limiter interceptor into user-provided hooks.
2922
+ * The limiter runs first, then chains to any user interceptor.
2923
+ */
2924
+ mergeOutputLimiterHook(userHooks) {
2925
+ if (!this.outputLimitEnabled) {
2926
+ return userHooks ?? {};
2927
+ }
2928
+ const limiterInterceptor = (result, ctx) => {
2929
+ if (ctx.gadgetName === "GadgetOutputViewer") {
2930
+ return result;
2931
+ }
2932
+ if (result.length > this.outputLimitCharLimit) {
2933
+ const id = this.outputStore.store(ctx.gadgetName, result);
2934
+ const lines = result.split("\n").length;
2935
+ const bytes = new TextEncoder().encode(result).length;
2936
+ this.logger.info("Gadget output exceeded limit, stored for browsing", {
2937
+ gadgetName: ctx.gadgetName,
2938
+ outputId: id,
2939
+ bytes,
2940
+ lines,
2941
+ charLimit: this.outputLimitCharLimit
2942
+ });
2943
+ return `[Gadget "${ctx.gadgetName}" returned too much data: ${bytes.toLocaleString()} bytes, ${lines.toLocaleString()} lines. Use GadgetOutputViewer with id "${id}" to read it]`;
2944
+ }
2945
+ return result;
2946
+ };
2947
+ const userInterceptor = userHooks?.interceptors?.interceptGadgetResult;
2948
+ const chainedInterceptor = userInterceptor ? (result, ctx) => userInterceptor(limiterInterceptor(result, ctx), ctx) : limiterInterceptor;
2949
+ return {
2950
+ ...userHooks,
2951
+ interceptors: {
2952
+ ...userHooks?.interceptors,
2953
+ interceptGadgetResult: chainedInterceptor
2954
+ }
2955
+ };
2956
+ }
2301
2957
  /**
2302
2958
  * Run agent with named event handlers (syntactic sugar).
2303
2959
  *
@@ -2350,6 +3006,8 @@ var init_builder = __esm({
2350
3006
  stopOnGadgetError;
2351
3007
  shouldContinueAfterError;
2352
3008
  defaultGadgetTimeoutMs;
3009
+ gadgetOutputLimit;
3010
+ gadgetOutputLimitPercent;
2353
3011
  constructor(client) {
2354
3012
  this.client = client;
2355
3013
  }
@@ -2682,6 +3340,45 @@ var init_builder = __esm({
2682
3340
  this.defaultGadgetTimeoutMs = timeoutMs;
2683
3341
  return this;
2684
3342
  }
3343
+ /**
3344
+ * Enable or disable gadget output limiting.
3345
+ *
3346
+ * When enabled, gadget outputs exceeding the configured limit are stored
3347
+ * and can be browsed using the GadgetOutputViewer gadget.
3348
+ *
3349
+ * @param enabled - Whether to enable output limiting (default: true)
3350
+ * @returns This builder for chaining
3351
+ *
3352
+ * @example
3353
+ * ```typescript
3354
+ * .withGadgetOutputLimit(false) // Disable output limiting
3355
+ * ```
3356
+ */
3357
+ withGadgetOutputLimit(enabled) {
3358
+ this.gadgetOutputLimit = enabled;
3359
+ return this;
3360
+ }
3361
+ /**
3362
+ * Set the maximum gadget output as a percentage of the model's context window.
3363
+ *
3364
+ * Outputs exceeding this limit are stored for later browsing with GadgetOutputViewer.
3365
+ *
3366
+ * @param percent - Percentage of context window (1-100, default: 15)
3367
+ * @returns This builder for chaining
3368
+ * @throws {Error} If percent is not between 1 and 100
3369
+ *
3370
+ * @example
3371
+ * ```typescript
3372
+ * .withGadgetOutputLimitPercent(25) // 25% of context window
3373
+ * ```
3374
+ */
3375
+ withGadgetOutputLimitPercent(percent) {
3376
+ if (percent < 1 || percent > 100) {
3377
+ throw new Error("Output limit percent must be between 1 and 100");
3378
+ }
3379
+ this.gadgetOutputLimitPercent = percent;
3380
+ return this;
3381
+ }
2685
3382
  /**
2686
3383
  * Build and create the agent with the given user prompt.
2687
3384
  * Returns the Agent instance ready to run.
@@ -2726,7 +3423,9 @@ var init_builder = __esm({
2726
3423
  textOnlyHandler: this.textOnlyHandler,
2727
3424
  stopOnGadgetError: this.stopOnGadgetError,
2728
3425
  shouldContinueAfterError: this.shouldContinueAfterError,
2729
- defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
3426
+ defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
3427
+ gadgetOutputLimit: this.gadgetOutputLimit,
3428
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
2730
3429
  };
2731
3430
  return new Agent(AGENT_INTERNAL_KEY, options);
2732
3431
  }
@@ -2825,7 +3524,9 @@ var init_builder = __esm({
2825
3524
  textOnlyHandler: this.textOnlyHandler,
2826
3525
  stopOnGadgetError: this.stopOnGadgetError,
2827
3526
  shouldContinueAfterError: this.shouldContinueAfterError,
2828
- defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs
3527
+ defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
3528
+ gadgetOutputLimit: this.gadgetOutputLimit,
3529
+ gadgetOutputLimitPercent: this.gadgetOutputLimitPercent
2829
3530
  };
2830
3531
  return new Agent(AGENT_INTERNAL_KEY, options);
2831
3532
  }
@@ -5266,256 +5967,8 @@ function createMockClient(options) {
5266
5967
  });
5267
5968
  }
5268
5969
 
5269
- // src/gadgets/gadget.ts
5270
- var yaml2 = __toESM(require("js-yaml"), 1);
5271
-
5272
- // src/gadgets/schema-to-json.ts
5273
- var z2 = __toESM(require("zod"), 1);
5274
- init_logger();
5275
- function schemaToJSONSchema(schema, options) {
5276
- const jsonSchema = z2.toJSONSchema(schema, options ?? { target: "draft-7" });
5277
- const mismatches = detectDescriptionMismatch(schema, jsonSchema);
5278
- if (mismatches.length > 0) {
5279
- defaultLogger.warn(
5280
- `Zod instance mismatch detected: ${mismatches.length} description(s) lost. For best results, use: import { z } from "llmist"`
5281
- );
5282
- return mergeDescriptions(schema, jsonSchema);
5283
- }
5284
- return jsonSchema;
5285
- }
5286
- function detectDescriptionMismatch(schema, jsonSchema) {
5287
- const mismatches = [];
5288
- function checkSchema(zodSchema, json, path) {
5289
- if (!zodSchema || typeof zodSchema !== "object") return;
5290
- const def = zodSchema._def;
5291
- const jsonObj = json;
5292
- if (def?.description && !jsonObj?.description) {
5293
- mismatches.push(path || "root");
5294
- }
5295
- if (def?.typeName === "ZodObject" && def?.shape) {
5296
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
5297
- for (const [key, fieldSchema] of Object.entries(shape)) {
5298
- const properties = jsonObj?.properties;
5299
- const jsonProp = properties?.[key];
5300
- checkSchema(fieldSchema, jsonProp, path ? `${path}.${key}` : key);
5301
- }
5302
- }
5303
- if (def?.typeName === "ZodArray" && def?.type) {
5304
- checkSchema(def.type, jsonObj?.items, path ? `${path}[]` : "[]");
5305
- }
5306
- if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
5307
- checkSchema(def.innerType, json, path);
5308
- }
5309
- if (def?.typeName === "ZodDefault" && def?.innerType) {
5310
- checkSchema(def.innerType, json, path);
5311
- }
5312
- }
5313
- checkSchema(schema, jsonSchema, "");
5314
- return mismatches;
5315
- }
5316
- function mergeDescriptions(schema, jsonSchema) {
5317
- function merge(zodSchema, json) {
5318
- if (!json || typeof json !== "object") return json;
5319
- const def = zodSchema._def;
5320
- const jsonObj = json;
5321
- const merged = { ...jsonObj };
5322
- if (def?.description && !jsonObj.description) {
5323
- merged.description = def.description;
5324
- }
5325
- if (def?.typeName === "ZodObject" && def?.shape && jsonObj.properties) {
5326
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
5327
- const properties = jsonObj.properties;
5328
- merged.properties = { ...properties };
5329
- for (const [key, fieldSchema] of Object.entries(shape)) {
5330
- if (properties[key]) {
5331
- merged.properties[key] = merge(fieldSchema, properties[key]);
5332
- }
5333
- }
5334
- }
5335
- if (def?.typeName === "ZodArray" && def?.type && jsonObj.items) {
5336
- merged.items = merge(def.type, jsonObj.items);
5337
- }
5338
- if ((def?.typeName === "ZodOptional" || def?.typeName === "ZodNullable") && def?.innerType) {
5339
- return merge(def.innerType, json);
5340
- }
5341
- if (def?.typeName === "ZodDefault" && def?.innerType) {
5342
- return merge(def.innerType, json);
5343
- }
5344
- return merged;
5345
- }
5346
- return merge(schema, jsonSchema);
5347
- }
5348
-
5349
- // src/gadgets/gadget.ts
5350
- init_schema_validator();
5351
- function formatYamlValue(value, indent = "") {
5352
- if (typeof value === "string") {
5353
- const lines = value.split("\n");
5354
- if (lines.length === 1 && !value.includes(":") && !value.startsWith("-")) {
5355
- return value;
5356
- }
5357
- const indentedLines = lines.map((line) => `${indent} ${line}`).join("\n");
5358
- return `|
5359
- ${indentedLines}`;
5360
- }
5361
- if (typeof value === "number" || typeof value === "boolean") {
5362
- return String(value);
5363
- }
5364
- if (value === null || value === void 0) {
5365
- return "null";
5366
- }
5367
- if (Array.isArray(value)) {
5368
- if (value.length === 0) return "[]";
5369
- const items = value.map((item) => `${indent}- ${formatYamlValue(item, indent + " ")}`);
5370
- return "\n" + items.join("\n");
5371
- }
5372
- if (typeof value === "object") {
5373
- const entries = Object.entries(value);
5374
- if (entries.length === 0) return "{}";
5375
- const lines = entries.map(([k, v]) => {
5376
- const formattedValue = formatYamlValue(v, indent + " ");
5377
- if (formattedValue.startsWith("\n") || formattedValue.startsWith("|")) {
5378
- return `${indent}${k}: ${formattedValue}`;
5379
- }
5380
- return `${indent}${k}: ${formattedValue}`;
5381
- });
5382
- return "\n" + lines.join("\n");
5383
- }
5384
- return yaml2.dump(value).trimEnd();
5385
- }
5386
- function formatParamsAsYaml(params) {
5387
- const lines = [];
5388
- for (const [key, value] of Object.entries(params)) {
5389
- const formattedValue = formatYamlValue(value, "");
5390
- if (formattedValue.startsWith("\n")) {
5391
- lines.push(`${key}:${formattedValue}`);
5392
- } else {
5393
- lines.push(`${key}: ${formattedValue}`);
5394
- }
5395
- }
5396
- return lines.join("\n");
5397
- }
5398
- function formatTomlValue(value) {
5399
- if (typeof value === "string") {
5400
- if (value.includes("\n")) {
5401
- return `"""
5402
- ${value}
5403
- """`;
5404
- }
5405
- return JSON.stringify(value);
5406
- }
5407
- if (typeof value === "number" || typeof value === "boolean") {
5408
- return String(value);
5409
- }
5410
- if (value === null || value === void 0) {
5411
- return '""';
5412
- }
5413
- if (Array.isArray(value)) {
5414
- return JSON.stringify(value);
5415
- }
5416
- if (typeof value === "object") {
5417
- return JSON.stringify(value);
5418
- }
5419
- return JSON.stringify(value);
5420
- }
5421
- function formatParamsAsToml(params) {
5422
- const lines = [];
5423
- for (const [key, value] of Object.entries(params)) {
5424
- lines.push(`${key} = ${formatTomlValue(value)}`);
5425
- }
5426
- return lines.join("\n");
5427
- }
5428
- var BaseGadget = class {
5429
- /**
5430
- * The name of the gadget. Used for identification when LLM calls it.
5431
- * If not provided, defaults to the class name.
5432
- */
5433
- name;
5434
- /**
5435
- * Optional Zod schema describing the expected input payload. When provided,
5436
- * it will be validated before execution and transformed into a JSON Schema
5437
- * representation that is surfaced to the LLM as part of the instructions.
5438
- */
5439
- parameterSchema;
5440
- /**
5441
- * Optional timeout in milliseconds for gadget execution.
5442
- * If execution exceeds this timeout, a TimeoutException will be thrown.
5443
- * If not set, the global defaultGadgetTimeoutMs from runtime options will be used.
5444
- * Set to 0 or undefined to disable timeout for this gadget.
5445
- */
5446
- timeoutMs;
5447
- /**
5448
- * Optional usage examples to help LLMs understand proper invocation.
5449
- * Examples are rendered in getInstruction() alongside the schema.
5450
- *
5451
- * Note: Uses broader `unknown` type to allow typed examples from subclasses
5452
- * while maintaining runtime compatibility.
5453
- */
5454
- examples;
5455
- /**
5456
- * Auto-generated instruction text for the LLM.
5457
- * Combines name, description, and parameter schema into a formatted instruction.
5458
- * @deprecated Use getInstruction(format) instead for format-specific schemas
5459
- */
5460
- get instruction() {
5461
- return this.getInstruction("yaml");
5462
- }
5463
- /**
5464
- * Generate instruction text for the LLM with format-specific schema.
5465
- * Combines name, description, and parameter schema into a formatted instruction.
5466
- *
5467
- * @param format - Format for the schema representation ('json' | 'yaml' | 'toml' | 'auto')
5468
- * @returns Formatted instruction string
5469
- */
5470
- getInstruction(format = "json") {
5471
- const parts = [];
5472
- parts.push(this.description);
5473
- if (this.parameterSchema) {
5474
- const gadgetName = this.name ?? this.constructor.name;
5475
- validateGadgetSchema(this.parameterSchema, gadgetName);
5476
- const jsonSchema = schemaToJSONSchema(this.parameterSchema, {
5477
- target: "draft-7"
5478
- });
5479
- if (format === "json" || format === "auto") {
5480
- parts.push("\n\nInput Schema (JSON):");
5481
- parts.push(JSON.stringify(jsonSchema, null, 2));
5482
- } else if (format === "toml") {
5483
- parts.push("\n\nInput Schema (TOML):");
5484
- parts.push(JSON.stringify(jsonSchema, null, 2));
5485
- } else {
5486
- const yamlSchema = yaml2.dump(jsonSchema).trimEnd();
5487
- parts.push("\n\nInput Schema (YAML):");
5488
- parts.push(yamlSchema);
5489
- }
5490
- }
5491
- if (this.examples && this.examples.length > 0) {
5492
- parts.push("\n\nExamples:");
5493
- this.examples.forEach((example, index) => {
5494
- if (index > 0) {
5495
- parts.push("");
5496
- }
5497
- if (example.comment) {
5498
- parts.push(`# ${example.comment}`);
5499
- }
5500
- parts.push("Input:");
5501
- if (format === "json" || format === "auto") {
5502
- parts.push(JSON.stringify(example.params, null, 2));
5503
- } else if (format === "toml") {
5504
- parts.push(formatParamsAsToml(example.params));
5505
- } else {
5506
- parts.push(formatParamsAsYaml(example.params));
5507
- }
5508
- if (example.output !== void 0) {
5509
- parts.push("Output:");
5510
- parts.push(example.output);
5511
- }
5512
- });
5513
- }
5514
- return parts.join("\n");
5515
- }
5516
- };
5517
-
5518
5970
  // src/testing/mock-gadget.ts
5971
+ init_gadget();
5519
5972
  var MockGadgetImpl = class extends BaseGadget {
5520
5973
  name;
5521
5974
  description;