llmist 17.2.1 → 17.3.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.d.cts CHANGED
@@ -5821,10 +5821,14 @@ interface StoredOutput {
5821
5821
  gadgetName: string;
5822
5822
  /** Full output content */
5823
5823
  content: string;
5824
+ /** Total character count of the stored content */
5825
+ charCount: number;
5824
5826
  /** Size in bytes */
5825
5827
  byteSize: number;
5826
5828
  /** Number of lines */
5827
5829
  lineCount: number;
5830
+ /** Length of the longest line in characters */
5831
+ maxLineLength: number;
5828
5832
  /** When the output was stored */
5829
5833
  timestamp: Date;
5830
5834
  }
@@ -8564,19 +8568,9 @@ declare function resultWithFile(result: string, fileData: Buffer | Uint8Array, m
8564
8568
  /**
8565
8569
  * Create a GadgetOutputViewer gadget instance bound to a specific output store.
8566
8570
  *
8567
- * This is a factory function because the gadget needs access to the output store,
8568
- * which is created per-agent-run.
8569
- *
8570
8571
  * @param store - The GadgetOutputStore to read outputs from
8571
8572
  * @param maxOutputChars - Maximum characters to return (default: 76,800 = ~19k tokens)
8572
8573
  * @returns A GadgetOutputViewer gadget instance
8573
- *
8574
- * @example
8575
- * ```typescript
8576
- * const store = new GadgetOutputStore();
8577
- * const viewer = createGadgetOutputViewer(store, 76_800);
8578
- * registry.register("GadgetOutputViewer", viewer);
8579
- * ```
8580
8574
  */
8581
8575
  declare function createGadgetOutputViewer(store: GadgetOutputStore, maxOutputChars?: number): AbstractGadget;
8582
8576
 
package/dist/index.d.ts CHANGED
@@ -5821,10 +5821,14 @@ interface StoredOutput {
5821
5821
  gadgetName: string;
5822
5822
  /** Full output content */
5823
5823
  content: string;
5824
+ /** Total character count of the stored content */
5825
+ charCount: number;
5824
5826
  /** Size in bytes */
5825
5827
  byteSize: number;
5826
5828
  /** Number of lines */
5827
5829
  lineCount: number;
5830
+ /** Length of the longest line in characters */
5831
+ maxLineLength: number;
5828
5832
  /** When the output was stored */
5829
5833
  timestamp: Date;
5830
5834
  }
@@ -8564,19 +8568,9 @@ declare function resultWithFile(result: string, fileData: Buffer | Uint8Array, m
8564
8568
  /**
8565
8569
  * Create a GadgetOutputViewer gadget instance bound to a specific output store.
8566
8570
  *
8567
- * This is a factory function because the gadget needs access to the output store,
8568
- * which is created per-agent-run.
8569
- *
8570
8571
  * @param store - The GadgetOutputStore to read outputs from
8571
8572
  * @param maxOutputChars - Maximum characters to return (default: 76,800 = ~19k tokens)
8572
8573
  * @returns A GadgetOutputViewer gadget instance
8573
- *
8574
- * @example
8575
- * ```typescript
8576
- * const store = new GadgetOutputStore();
8577
- * const viewer = createGadgetOutputViewer(store, 76_800);
8578
- * registry.register("GadgetOutputViewer", viewer);
8579
- * ```
8580
8574
  */
8581
8575
  declare function createGadgetOutputViewer(store: GadgetOutputStore, maxOutputChars?: number): AbstractGadget;
8582
8576
 
package/dist/index.js CHANGED
@@ -4654,6 +4654,9 @@ var init_create_gadget = __esm({
4654
4654
 
4655
4655
  // src/gadgets/output-viewer.ts
4656
4656
  import { z as z3 } from "zod";
4657
+ function pluralize(count, singular, plural = `${singular}s`) {
4658
+ return count === 1 ? singular : plural;
4659
+ }
4657
4660
  function applyPattern(lines, pattern) {
4658
4661
  const regex = new RegExp(pattern.regex);
4659
4662
  if (!pattern.include) {
@@ -4678,80 +4681,169 @@ function applyPatterns(lines, patterns) {
4678
4681
  }
4679
4682
  return result;
4680
4683
  }
4681
- function applyLineLimit(lines, limit) {
4684
+ function parseLimitWindow(limit) {
4682
4685
  const trimmed = limit.trim();
4683
4686
  if (trimmed.endsWith("-") && !trimmed.startsWith("-")) {
4684
4687
  const n = parseInt(trimmed.slice(0, -1), 10);
4685
- if (!isNaN(n) && n > 0) {
4686
- return lines.slice(0, n);
4688
+ if (!Number.isNaN(n) && n > 0) {
4689
+ return { kind: "first", count: n };
4687
4690
  }
4688
4691
  }
4689
4692
  if (trimmed.startsWith("-") && !trimmed.includes("-", 1)) {
4690
4693
  const n = parseInt(trimmed, 10);
4691
- if (!isNaN(n) && n < 0) {
4692
- return lines.slice(n);
4694
+ if (!Number.isNaN(n) && n < 0) {
4695
+ return { kind: "last", count: Math.abs(n) };
4693
4696
  }
4694
4697
  }
4695
4698
  const rangeMatch = trimmed.match(/^(\d+)-(\d+)$/);
4696
4699
  if (rangeMatch) {
4697
4700
  const start = parseInt(rangeMatch[1], 10);
4698
4701
  const end = parseInt(rangeMatch[2], 10);
4699
- if (!isNaN(start) && !isNaN(end) && start > 0 && end >= start) {
4700
- return lines.slice(start - 1, end);
4702
+ if (!Number.isNaN(start) && !Number.isNaN(end) && start > 0 && end >= start) {
4703
+ return { kind: "range", start, end };
4701
4704
  }
4702
4705
  }
4703
- return lines;
4706
+ return null;
4707
+ }
4708
+ function applyLineLimit(lines, limit) {
4709
+ const window = parseLimitWindow(limit);
4710
+ if (!window) return lines;
4711
+ switch (window.kind) {
4712
+ case "first":
4713
+ return lines.slice(0, window.count);
4714
+ case "last":
4715
+ return lines.slice(-window.count);
4716
+ case "range":
4717
+ return lines.slice(window.start - 1, window.end);
4718
+ }
4719
+ }
4720
+ function applyCharacterLimit(content, limit, maxOutputChars) {
4721
+ const total = content.length;
4722
+ if (total === 0) {
4723
+ return { text: "", start: 0, end: 0, total: 0, truncatedBySize: false, hasMoreAfter: false };
4724
+ }
4725
+ let startIndex = 0;
4726
+ let endExclusive = total;
4727
+ const window = limit ? parseLimitWindow(limit) : null;
4728
+ if (window) {
4729
+ switch (window.kind) {
4730
+ case "first":
4731
+ endExclusive = Math.min(window.count, total);
4732
+ break;
4733
+ case "last":
4734
+ startIndex = Math.max(0, total - window.count);
4735
+ break;
4736
+ case "range":
4737
+ startIndex = Math.min(window.start - 1, total);
4738
+ endExclusive = Math.min(window.end, total);
4739
+ break;
4740
+ }
4741
+ }
4742
+ let text3 = content.slice(startIndex, endExclusive);
4743
+ let truncatedBySize = false;
4744
+ if (text3.length > maxOutputChars) {
4745
+ text3 = window?.kind === "last" ? text3.slice(-maxOutputChars) : text3.slice(0, maxOutputChars);
4746
+ if (window?.kind === "last") {
4747
+ startIndex = endExclusive - text3.length;
4748
+ }
4749
+ truncatedBySize = true;
4750
+ }
4751
+ return {
4752
+ text: text3,
4753
+ start: text3.length === 0 ? 0 : startIndex + 1,
4754
+ end: text3.length === 0 ? 0 : startIndex + text3.length,
4755
+ total,
4756
+ truncatedBySize,
4757
+ hasMoreAfter: startIndex + text3.length < total
4758
+ };
4759
+ }
4760
+ function buildCharacterRangeHint(start, total) {
4761
+ if (total <= 0 || start > total) return null;
4762
+ const end = Math.min(total, start + CHARACTER_HINT_WINDOW - 1);
4763
+ return `${start}-${end}`;
4764
+ }
4765
+ function buildCharacterModeSuggestion(stored, opts = {}) {
4766
+ const hint = buildCharacterRangeHint(opts.start ?? 1, stored.charCount);
4767
+ const action = opts.removePatterns ? "Remove patterns and then try" : "Try";
4768
+ const lineLabel = pluralize(stored.lineCount, "line");
4769
+ return `This output is dense (${stored.lineCount.toLocaleString()} ${lineLabel}; longest line ${stored.maxLineLength.toLocaleString()} chars). ${action} mode: "character"` + (hint ? `, limit: "${hint}"` : "") + ".";
4770
+ }
4771
+ function shouldSuggestCharacterMode(stored, maxOutputChars = DEFAULT_MAX_OUTPUT_CHARS) {
4772
+ return stored.lineCount <= 3 && (stored.maxLineLength > maxOutputChars || stored.maxLineLength >= DENSE_LINE_THRESHOLD);
4704
4773
  }
4705
4774
  function createGadgetOutputViewer(store, maxOutputChars = DEFAULT_MAX_OUTPUT_CHARS) {
4706
4775
  return createGadget({
4707
4776
  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.",
4777
+ 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.',
4709
4778
  schema: z3.object({
4710
4779
  id: z3.string().describe("ID of the stored output (from the truncation message)"),
4780
+ mode: z3.enum(["line", "character"]).default("line").describe(
4781
+ 'Browse by "line" (supports patterns) or by "character" (raw windows for dense output).'
4782
+ ),
4711
4783
  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."
4784
+ 'Line-mode filter patterns applied in order (like piping through grep). Not supported in mode "character".'
4713
4785
  ),
4714
4786
  limit: z3.string().optional().describe(
4715
- "Line range to return after filtering. Formats: '100-' (first 100), '-25' (last 25), '50-100' (lines 50-100)"
4787
+ `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
4788
  )
4717
4789
  }),
4718
4790
  examples: [
4719
4791
  {
4720
4792
  comment: "View first 50 lines of stored output",
4721
- params: { id: "Search_abc12345", limit: "50-" }
4793
+ params: { id: "Search_abc12345", mode: "line", limit: "50-" }
4722
4794
  },
4723
4795
  {
4724
4796
  comment: "Filter for error lines with context",
4725
4797
  params: {
4726
4798
  id: "Search_abc12345",
4799
+ mode: "line",
4727
4800
  patterns: [{ regex: "error|Error|ERROR", include: true, before: 2, after: 5 }]
4728
4801
  }
4729
4802
  },
4730
4803
  {
4731
- comment: "Exclude blank lines, then show first 100",
4804
+ comment: "Exclude blank lines, then show first 100 lines",
4732
4805
  params: {
4733
4806
  id: "Search_abc12345",
4807
+ mode: "line",
4734
4808
  patterns: [{ regex: "^\\s*$", include: false, before: 0, after: 0 }],
4735
4809
  limit: "100-"
4736
4810
  }
4737
4811
  },
4738
4812
  {
4739
- comment: "Chain filters: find TODOs, exclude tests, limit to 50 lines",
4813
+ comment: "Browse the raw output by character window when line mode is too dense",
4740
4814
  params: {
4741
4815
  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-"
4816
+ mode: "character",
4817
+ limit: "1-2000"
4747
4818
  }
4748
4819
  }
4749
4820
  ],
4750
- execute: ({ id, patterns, limit }) => {
4821
+ execute: ({ id, mode, patterns, limit }) => {
4751
4822
  const stored = store.get(id);
4752
4823
  if (!stored) {
4753
4824
  return `Error: No stored output with id "${id}". Available IDs: ${store.getIds().join(", ") || "(none)"}`;
4754
4825
  }
4826
+ const suggestCharacterMode = shouldSuggestCharacterMode(stored, maxOutputChars);
4827
+ if (mode === "character") {
4828
+ if (patterns && patterns.length > 0) {
4829
+ return 'Error: patterns are only supported in mode "line". Remove patterns or switch back to mode: "line".';
4830
+ }
4831
+ const window = applyCharacterLimit(stored.content, limit, maxOutputChars);
4832
+ if (window.total === 0) {
4833
+ return "[Mode: character | Output is empty]";
4834
+ }
4835
+ const header2 = [
4836
+ `[Mode: character | Showing chars ${window.start.toLocaleString()}-${window.end.toLocaleString()} of ${window.total.toLocaleString()}${window.truncatedBySize ? " (truncated due to viewer size limit)" : ""}]`
4837
+ ];
4838
+ if (window.hasMoreAfter) {
4839
+ const nextRange = buildCharacterRangeHint(window.end + 1, window.total);
4840
+ if (nextRange) {
4841
+ header2.push(`[Next chunk: mode: "character", limit: "${nextRange}"]`);
4842
+ }
4843
+ }
4844
+ return `${header2.join("\n")}
4845
+ ${window.text}`;
4846
+ }
4755
4847
  let lines = stored.content.split("\n");
4756
4848
  if (patterns && patterns.length > 0) {
4757
4849
  lines = applyPatterns(
@@ -4767,54 +4859,76 @@ function createGadgetOutputViewer(store, maxOutputChars = DEFAULT_MAX_OUTPUT_CHA
4767
4859
  if (limit) {
4768
4860
  lines = applyLineLimit(lines, limit);
4769
4861
  }
4770
- let output = lines.join("\n");
4771
4862
  const totalLines = stored.lineCount;
4863
+ const totalLineLabel = pluralize(totalLines, "line");
4772
4864
  const returnedLines = lines.length;
4773
4865
  if (returnedLines === 0) {
4774
- return `No lines matched the filters. Original output had ${totalLines} lines.`;
4866
+ const base = `No lines matched the filters. Original output had ${totalLines.toLocaleString()} lines.`;
4867
+ if (!suggestCharacterMode) return base;
4868
+ return `${base} ${buildCharacterModeSuggestion(stored, {
4869
+ removePatterns: Boolean(patterns && patterns.length > 0)
4870
+ })}`;
4775
4871
  }
4872
+ let output = lines.join("\n");
4776
4873
  let truncatedBySize = false;
4777
4874
  let linesIncluded = returnedLines;
4875
+ let clippedFirstLine = false;
4778
4876
  if (output.length > maxOutputChars) {
4779
4877
  truncatedBySize = true;
4780
4878
  let truncatedOutput = "";
4781
4879
  linesIncluded = 0;
4782
4880
  for (const line of lines) {
4783
- if (truncatedOutput.length + line.length + 1 > maxOutputChars) break;
4784
- truncatedOutput += line + "\n";
4881
+ const addition = linesIncluded === 0 ? line : `
4882
+ ${line}`;
4883
+ if (truncatedOutput.length + addition.length > maxOutputChars) break;
4884
+ truncatedOutput += addition;
4785
4885
  linesIncluded++;
4786
4886
  }
4887
+ if (linesIncluded === 0) {
4888
+ clippedFirstLine = true;
4889
+ linesIncluded = 1;
4890
+ truncatedOutput = lines[0].slice(0, maxOutputChars);
4891
+ }
4787
4892
  output = truncatedOutput;
4788
4893
  }
4789
4894
  let header;
4790
- if (truncatedBySize) {
4895
+ if (clippedFirstLine) {
4896
+ header = `[Mode: line | Showing 1 partial line of ${totalLines.toLocaleString()} ${totalLineLabel} (the selected line exceeds the viewer size limit)]
4897
+ `;
4898
+ } else if (truncatedBySize) {
4791
4899
  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}"]
4900
+ header = `[Mode: line | Showing ${linesIncluded.toLocaleString()} of ${totalLines.toLocaleString()} ${totalLineLabel} (truncated due to size limit)]
4901
+ [... ${remainingLines.toLocaleString()} more ${pluralize(remainingLines, "line")}. Use limit parameter to paginate, e.g., limit: "${linesIncluded + 1}-${linesIncluded + 200}"]
4794
4902
  `;
4795
4903
  } else if (returnedLines < totalLines) {
4796
- header = `[Showing ${returnedLines} of ${totalLines} lines]
4904
+ header = `[Mode: line | Showing ${returnedLines.toLocaleString()} of ${totalLines.toLocaleString()} ${totalLineLabel}]
4797
4905
  `;
4798
4906
  } else {
4799
- header = `[Showing all ${totalLines} lines]
4907
+ header = `[Mode: line | Showing all ${totalLines.toLocaleString()} ${totalLineLabel}]
4800
4908
  `;
4801
4909
  }
4802
- return header + output;
4910
+ const footer = suggestCharacterMode || clippedFirstLine ? `
4911
+ [Tip: ${buildCharacterModeSuggestion(stored, {
4912
+ removePatterns: Boolean(patterns && patterns.length > 0)
4913
+ })}]` : "";
4914
+ return header + output + footer;
4803
4915
  }
4804
4916
  });
4805
4917
  }
4806
- var patternSchema, DEFAULT_MAX_OUTPUT_CHARS;
4918
+ var DEFAULT_MAX_OUTPUT_CHARS, CHARACTER_HINT_WINDOW, DENSE_LINE_THRESHOLD, patternSchema;
4807
4919
  var init_output_viewer = __esm({
4808
4920
  "src/gadgets/output-viewer.ts"() {
4809
4921
  "use strict";
4810
4922
  init_create_gadget();
4923
+ DEFAULT_MAX_OUTPUT_CHARS = 76800;
4924
+ CHARACTER_HINT_WINDOW = 2e3;
4925
+ DENSE_LINE_THRESHOLD = 4e3;
4811
4926
  patternSchema = z3.object({
4812
4927
  regex: z3.string().describe("Regular expression to match"),
4813
4928
  include: z3.boolean().default(true).describe("true = keep matching lines, false = exclude matching lines"),
4814
4929
  before: z3.number().int().min(0).default(0).describe("Context lines before each match (like grep -B)"),
4815
4930
  after: z3.number().int().min(0).default(0).describe("Context lines after each match (like grep -A)")
4816
4931
  });
4817
- DEFAULT_MAX_OUTPUT_CHARS = 76800;
4818
4932
  }
4819
4933
  });
4820
4934
 
@@ -4836,12 +4950,15 @@ var init_gadget_output_store = __esm({
4836
4950
  store(gadgetName, content) {
4837
4951
  const id = this.generateId(gadgetName);
4838
4952
  const encoder = new TextEncoder();
4953
+ const lines = content.split("\n");
4839
4954
  const stored = {
4840
4955
  id,
4841
4956
  gadgetName,
4842
4957
  content,
4958
+ charCount: content.length,
4843
4959
  byteSize: encoder.encode(content).length,
4844
- lineCount: content.split("\n").length,
4960
+ lineCount: lines.length,
4961
+ maxLineLength: lines.reduce((max, line) => Math.max(max, line.length), 0),
4845
4962
  timestamp: /* @__PURE__ */ new Date()
4846
4963
  };
4847
4964
  this.outputs.set(id, stored);
@@ -4945,16 +5062,20 @@ var init_output_limit_manager = __esm({
4945
5062
  }
4946
5063
  if (result.length > this.charLimit) {
4947
5064
  const id = this.outputStore.store(ctx.gadgetName, result);
4948
- const lines = result.split("\n").length;
4949
- const bytes = new TextEncoder().encode(result).length;
5065
+ const stored = this.outputStore.get(id);
5066
+ const lines = stored?.lineCount ?? result.split("\n").length;
5067
+ const bytes = stored?.byteSize ?? new TextEncoder().encode(result).length;
5068
+ const denseSuggestion = stored && shouldSuggestCharacterMode(stored, this.charLimit) ? ` ${buildCharacterModeSuggestion(stored)}` : "";
4950
5069
  this.logger.info("Gadget output exceeded limit, stored for browsing", {
4951
5070
  gadgetName: ctx.gadgetName,
4952
5071
  outputId: id,
4953
5072
  bytes,
4954
5073
  lines,
5074
+ charCount: stored?.charCount,
5075
+ maxLineLength: stored?.maxLineLength,
4955
5076
  charLimit: this.charLimit
4956
5077
  });
4957
- return `[Gadget "${ctx.gadgetName}" returned too much data: ${bytes.toLocaleString()} bytes, ${lines.toLocaleString()} lines. Use GadgetOutputViewer with id "${id}" to read it]`;
5078
+ return `[Gadget "${ctx.gadgetName}" returned too much data: ${bytes.toLocaleString()} bytes, ${lines.toLocaleString()} lines. Use GadgetOutputViewer with id "${id}" to read it.]` + denseSuggestion;
4958
5079
  }
4959
5080
  return result;
4960
5081
  };