botholomew 0.18.4 → 0.18.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botholomew",
3
- "version": "0.18.4",
3
+ "version": "0.18.5",
4
4
  "description": "An autonomous AI agent for knowledge work — works your task queue while you sleep.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,6 +40,7 @@ import { searchThreadsTool } from "./thread/search.ts";
40
40
  import { viewThreadTool } from "./thread/view.ts";
41
41
  import { registerTool } from "./tool.ts";
42
42
  // Util tools
43
+ import { readLargeResultTool } from "./util/read_large_result.ts";
43
44
  import { sleepTool } from "./util/sleep.ts";
44
45
  // Worker tools
45
46
  import { spawnWorkerTool } from "./worker/spawn.ts";
@@ -95,6 +96,7 @@ export function registerAllTools(): void {
95
96
 
96
97
  // Util
97
98
  registerTool(sleepTool);
99
+ registerTool(readLargeResultTool);
98
100
 
99
101
  // Worker
100
102
  registerTool(spawnWorkerTool);
@@ -0,0 +1,84 @@
1
+ import { z } from "zod";
2
+ import {
3
+ PAGE_SIZE_CHARS,
4
+ peekLargeResult,
5
+ readLargeResultPage,
6
+ } from "../../worker/large-results.ts";
7
+ import type { ToolDefinition } from "../tool.ts";
8
+
9
+ const ID_PATTERN = /^lr_\d+$/;
10
+
11
+ const inputSchema = z.object({
12
+ id: z
13
+ .string()
14
+ .regex(ID_PATTERN)
15
+ .describe(
16
+ 'Large-result id from the "Paginated for LLM" stub, e.g. "lr_1".',
17
+ ),
18
+ page: z
19
+ .number()
20
+ .int()
21
+ .min(1)
22
+ .default(1)
23
+ .describe(
24
+ `1-based page number. Each page is ~${PAGE_SIZE_CHARS} chars. Start at 1; the response includes total_pages so you know when to stop.`,
25
+ ),
26
+ });
27
+
28
+ const outputSchema = z.object({
29
+ content: z.string(),
30
+ id: z.string(),
31
+ page: z.number(),
32
+ total_pages: z.number(),
33
+ total_chars: z.number(),
34
+ is_error: z.boolean(),
35
+ error_type: z.string().optional(),
36
+ next_action_hint: z.string().optional(),
37
+ });
38
+
39
+ export const readLargeResultTool = {
40
+ name: "read_large_result",
41
+ description: `[[ bash equivalent command: sed -n '<page>p' ]] Read one page of a large tool result that was cached because its inline payload exceeded the response budget. Use the id from the "Paginated for LLM" stub. Pages are 1-based and ~${PAGE_SIZE_CHARS} chars each; loop from page=1 to total_pages.`,
42
+ group: "util",
43
+ inputSchema,
44
+ outputSchema,
45
+ execute: async (input, _ctx): Promise<z.infer<typeof outputSchema>> => {
46
+ const meta = peekLargeResult(input.id);
47
+ if (!meta) {
48
+ return {
49
+ content: "",
50
+ id: input.id,
51
+ page: input.page,
52
+ total_pages: 0,
53
+ total_chars: 0,
54
+ is_error: true,
55
+ error_type: "unknown_id",
56
+ next_action_hint:
57
+ "Large-result entries live only for the current worker tick or chat session and are cleared between worker tasks. Re-run the originating tool to regenerate the result.",
58
+ };
59
+ }
60
+
61
+ const result = readLargeResultPage(input.id, input.page);
62
+ if (!result) {
63
+ return {
64
+ content: "",
65
+ id: input.id,
66
+ page: input.page,
67
+ total_pages: meta.totalPages,
68
+ total_chars: meta.totalChars,
69
+ is_error: true,
70
+ error_type: "page_out_of_range",
71
+ next_action_hint: `page=${input.page} is past the end. Valid pages are 1–${meta.totalPages}.`,
72
+ };
73
+ }
74
+
75
+ return {
76
+ content: result.content,
77
+ id: input.id,
78
+ page: result.page,
79
+ total_pages: result.totalPages,
80
+ total_chars: meta.totalChars,
81
+ is_error: false,
82
+ };
83
+ },
84
+ } satisfies ToolDefinition<typeof inputSchema, typeof outputSchema>;
@@ -52,6 +52,19 @@ export function readLargeResultPage(
52
52
  return { content, page, totalPages: entry.totalPages };
53
53
  }
54
54
 
55
+ /** Inspect a stored result without consuming a page. Returns null if unknown. */
56
+ export function peekLargeResult(
57
+ id: string,
58
+ ): { toolName: string; totalChars: number; totalPages: number } | null {
59
+ const entry = store.get(id);
60
+ if (!entry) return null;
61
+ return {
62
+ toolName: entry.toolName,
63
+ totalChars: entry.totalChars,
64
+ totalPages: entry.totalPages,
65
+ };
66
+ }
67
+
55
68
  /** Build the inline stub that replaces the full result in the conversation */
56
69
  export function buildResultStub(
57
70
  id: string,
@@ -67,7 +80,7 @@ export function buildResultStub(
67
80
  preview,
68
81
  preview.length < content.length ? "..." : "",
69
82
  "",
70
- `Use read_large_result with id="${id}" to read page-by-page (pages 1–${totalPages}).`,
83
+ `Use read_large_result with id="${id}" and page=<n> (1–${totalPages}) to read it in ~${PAGE_SIZE_CHARS}-char pages.`,
71
84
  ].join("\n");
72
85
  }
73
86