@ashx-j/lunr 0.2.9 → 0.2.10
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/builtin-extensions/ashxj-spinners.d.ts +1 -4
- package/dist/builtin-extensions/ashxj-spinners.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-spinners.js +3 -29
- package/dist/builtin-extensions/ashxj-spinners.js.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.js +5 -4
- package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +5 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +2 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js +2 -2
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +4 -6
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +26 -18
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.js +3 -1
- package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
- package/dist/catalog/models.json +1 -1
- package/dist/catalog/providers/nvidia.json +1 -1
- package/dist/catalog/providers/opencode-go.json +1 -1
- package/dist/catalog/providers/opencode.json +1 -1
- package/dist/catalog/providers/openrouter.json +1 -1
- package/dist/catalog/providers/qwen-cloud-cn.json +1 -1
- package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
- package/dist/catalog/providers/xai.json +1 -1
- package/dist/catalog/publication.json +3 -3
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +9 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/customize.d.ts +1 -0
- package/dist/core/customize.d.ts.map +1 -1
- package/dist/core/customize.js +3 -0
- package/dist/core/customize.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +2 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +9 -6
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +2 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +2 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +2 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +13 -3
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +10 -2
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +5 -3
- package/dist/core/tools/write.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts +1 -0
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +11 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.d.ts +1 -0
- package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-tail.js +2 -6
- package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +6 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +75 -16
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +15 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +139 -34
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/terminal-setup.md +2 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/core/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAK/D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAO5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAWtF,OAAO,EAAoD,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAEtH,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AASD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,+CAA+C;IAC/C,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACnF;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AA0HD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAoKhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from \"node:path\";\nimport type { AgentTool } from \"@ashx-j/lunr-agent\";\nimport type { Api, ImageContent, Model, TextContent } from \"@ashx-j/lunr-ai\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { getReadmePath } from \"../../config.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { processImage } from \"../../utils/image-process.ts\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../../utils/mime.ts\";\nimport { formatPathRelativeToCwdOrAbsolute } from \"../../utils/paths.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { resolveReadPathAsync, resolveToCwd } from \"./path-utils.ts\";\nimport {\n\tformatGroupedCall,\n\tgetTextOutput,\n\trenderToolFileName,\n\treplaceTabs,\n\tstr,\n\ttoolStatusDotFromContext,\n} from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from \"./truncate.ts\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport interface ReadToolDetails {\n\ttruncation?: TruncationResult;\n}\n\ninterface CompactReadClassification {\n\tkind: \"docs\" | \"resource\" | \"skill\";\n\tlabel: string;\n}\n\nconst COMPACT_RESOURCE_FILE_NAMES = new Set([\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"]);\n\n/**\n * Pluggable operations for the read tool.\n * Override these to delegate file reading to remote systems (for example SSH).\n */\nexport interface ReadOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Check if file is readable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n\t/** Detect image MIME type, return null or undefined for non-images */\n\tdetectImageMimeType?: (absolutePath: string) => Promise<string | null | undefined>;\n}\n\nconst defaultReadOperations: ReadOperations = {\n\treadFile: (path) => fsReadFile(path),\n\taccess: (path) => fsAccess(path, constants.R_OK),\n\tdetectImageMimeType: detectSupportedImageMimeTypeFromFile,\n};\n\nexport interface ReadToolOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n\t/** Custom operations for file reading. Default: local filesystem */\n\toperations?: ReadOperations;\n}\n\ntype ReadRenderArgs = { path?: string; file_path?: string; offset?: number; limit?: number };\n\nfunction formatReadLineRange(args: ReadRenderArgs | undefined, theme: Theme): string {\n\tif (args?.offset === undefined && args?.limit === undefined) return \"\";\n\tconst startLine = args.offset ?? 1;\n\tconst endLine = args.limit !== undefined ? startLine + args.limit - 1 : \"\";\n\treturn theme.fg(\"warning\", `:${startLine}${endLine ? `-${endLine}` : \"\"}`);\n}\n\nfunction formatReadDetail(\n\targs: ReadRenderArgs | undefined,\n\ttheme: Theme,\n\tcwd: string,\n\texpanded = false,\n): string {\n\tconst pathDisplay = renderToolFileName(str(args?.file_path ?? args?.path), theme, cwd);\n\tconst range = expanded ? formatReadLineRange(args, theme) : \"\";\n\treturn `${pathDisplay}${range}`;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction getNonVisionImageNote(model: Model<Api> | undefined): string | undefined {\n\tif (!model || model.input.includes(\"image\")) {\n\t\treturn undefined;\n\t}\n\treturn \"[Current model does not support images. The image will be omitted from this request.]\";\n}\n\nfunction toPosixPath(filePath: string): string {\n\treturn filePath.split(sep).join(\"/\");\n}\n\nfunction getPiDocsClassification(absolutePath: string): CompactReadClassification | undefined {\n\tconst packageRoot = dirname(getReadmePath());\n\tconst relativePath = relative(resolvePath(packageRoot), resolvePath(absolutePath));\n\tif (\n\t\trelativePath === \"\" ||\n\t\trelativePath === \"..\" ||\n\t\trelativePath.startsWith(`..${sep}`) ||\n\t\tisAbsolute(relativePath)\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst label = toPosixPath(relativePath);\n\tif (label === \"README.md\" || label.startsWith(\"docs/\") || label.startsWith(\"examples/\")) {\n\t\treturn { kind: \"docs\", label };\n\t}\n\treturn undefined;\n}\n\nfunction getCompactReadClassification(\n\targs: ReadRenderArgs | undefined,\n\tcwd: string,\n): CompactReadClassification | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tif (!rawPath) return undefined;\n\n\tconst absolutePath = resolveToCwd(rawPath, cwd);\n\tconst fileName = basename(absolutePath);\n\tif (fileName === \"SKILL.md\") {\n\t\treturn { kind: \"skill\", label: basename(dirname(absolutePath)) || fileName };\n\t}\n\n\tconst docsClassification = getPiDocsClassification(absolutePath);\n\tif (docsClassification) return docsClassification;\n\n\tif (COMPACT_RESOURCE_FILE_NAMES.has(fileName)) {\n\t\treturn { kind: \"resource\", label: formatPathRelativeToCwdOrAbsolute(absolutePath, cwd) };\n\t}\n\n\treturn undefined;\n}\n\nfunction formatReadResult(\n\targs: ReadRenderArgs | undefined,\n\tresult: { content: (TextContent | ImageContent)[]; details?: ReadToolDetails },\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n\t_cwd: string,\n\tisError: boolean,\n): string {\n\tif (!options.expanded && !isError) {\n\t\treturn \"\";\n\t}\n\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst output = getTextOutput(result, showImages);\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tconst renderedLines = lang ? highlightCode(replaceTabs(output), lang) : output.split(\"\\n\");\n\tconst lines = trimTrailingEmptyLines(renderedLines);\n\tconst maxLines = options.expanded ? lines.length : 10;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => (lang ? replaceTabs(line) : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t}\n\n\tconst truncation = result.details?.truncation;\n\tif (truncation?.truncated) {\n\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`)}`;\n\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`)}`;\n\t\t} else {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`)}`;\n\t\t}\n\t}\n\treturn text;\n}\n\nexport function createReadToolDefinition(\n\tcwd: string,\n\toptions?: ReadToolOptions,\n): ToolDefinition<typeof readSchema, ReadToolDetails | undefined> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tconst ops = options?.operations ?? defaultReadOperations;\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tpromptSnippet: \"Read file contents\",\n\t\tpromptGuidelines: [\"Use read to examine files instead of cat or sed.\"],\n\t\tparameters: readSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, offset, limit }: { path: string; offset?: number; limit?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\tctx?,\n\t\t) {\n\t\t\treturn new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(\n\t\t\t\t(resolve, reject) => {\n\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t};\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst absolutePath = await resolveReadPathAsync(path, cwd);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\t// Check if file exists and is readable.\n\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tconst mimeType = ops.detectImageMimeType ? await ops.detectImageMimeType(absolutePath) : undefined;\n\t\t\t\t\t\t\tlet content: (TextContent | ImageContent)[];\n\t\t\t\t\t\t\tlet details: ReadToolDetails | undefined;\n\t\t\t\t\t\t\tconst nonVisionImageNote = getNonVisionImageNote(ctx?.model);\n\t\t\t\t\t\t\tif (mimeType) {\n\t\t\t\t\t\t\t\t// Read image as binary.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst processed = await processImage(buffer, mimeType, { autoResizeImages });\n\t\t\t\t\t\t\t\tif (!processed.ok) {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${mimeType}]\\n${processed.message}`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: textNote }];\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${processed.mimeType}]`;\n\t\t\t\t\t\t\t\t\tif (processed.hints.length > 0) textNote += `\\n${processed.hints.join(\"\\n\")}`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t\t{ type: \"text\", text: textNote },\n\t\t\t\t\t\t\t\t\t\t{ type: \"image\", data: processed.data, mimeType: processed.mimeType },\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Read text content.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst textContent = buffer.toString(\"utf-8\");\n\t\t\t\t\t\t\t\tconst allLines = textContent.split(\"\\n\");\n\t\t\t\t\t\t\t\tconst totalFileLines = allLines.length;\n\t\t\t\t\t\t\t\t// Apply offset if specified. Convert from 1-indexed input to 0-indexed array access.\n\t\t\t\t\t\t\t\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\t\t\t\t\t\t\t\tconst startLineDisplay = startLine + 1;\n\t\t\t\t\t\t\t\t// Check if offset is out of bounds.\n\t\t\t\t\t\t\t\tif (startLine >= allLines.length) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet selectedContent: string;\n\t\t\t\t\t\t\t\tlet userLimitedLines: number | undefined;\n\t\t\t\t\t\t\t\t// If limit is specified by the user, honor it first. Otherwise truncateHead decides.\n\t\t\t\t\t\t\t\tif (limit !== undefined) {\n\t\t\t\t\t\t\t\t\tconst endLine = Math.min(startLine + limit, allLines.length);\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine, endLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t\tuserLimitedLines = endLine - startLine;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Apply truncation, respecting both line and byte limits.\n\t\t\t\t\t\t\t\tconst truncation = truncateHead(selectedContent);\n\t\t\t\t\t\t\t\tlet outputText: string;\n\t\t\t\t\t\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t\t\t\t\t\t// First line alone exceeds the byte limit. Point the model at a bash fallback.\n\t\t\t\t\t\t\t\t\tconst firstLineSize = formatSize(Buffer.byteLength(allLines[startLine], \"utf-8\"));\n\t\t\t\t\t\t\t\t\toutputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (truncation.truncated) {\n\t\t\t\t\t\t\t\t\t// Truncation occurred. Build an actionable continuation notice.\n\t\t\t\t\t\t\t\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\t\t\t\t\t\t\t\tconst nextOffset = endLineDisplay + 1;\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {\n\t\t\t\t\t\t\t\t\t// User-specified limit stopped early, but the file still has more content.\n\t\t\t\t\t\t\t\t\tconst remaining = allLines.length - (startLine + userLimitedLines);\n\t\t\t\t\t\t\t\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\t\t\t\t\t\t\t\toutputText = `${truncation.content}\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// No truncation and no remaining user-limited content.\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: outputText }];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tresolve({ content, details });\n\t\t\t\t\t\t} catch (error: any) {\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tif (!aborted) reject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst compact = !context.isPartial && !context.expanded && !context.isError;\n\t\t\tconst classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;\n\t\t\tlet title = theme.fg(\"toolTitle\", theme.bold(\"read\"));\n\t\t\tlet detail: string;\n\t\t\tif (classification?.kind === \"skill\") {\n\t\t\t\ttitle = theme.fg(\"customMessageLabel\", `\\x1b[1m[skill]\\x1b[22m`);\n\t\t\t\tdetail = theme.fg(\"customMessageText\", classification.label);\n\t\t\t} else if (classification) {\n\t\t\t\ttitle = theme.fg(\"toolTitle\", theme.bold(`read ${classification.kind}`));\n\t\t\t\tdetail = theme.fg(\"accent\", classification.label);\n\t\t\t} else {\n\t\t\t\tdetail = formatReadDetail(args, theme, context.cwd, context.expanded);\n\t\t\t}\n\t\t\ttext.setText(\n\t\t\t\tformatGroupedCall({\n\t\t\t\t\trole: context.groupRole ?? \"singleton\",\n\t\t\t\t\tcompact,\n\t\t\t\t\tdot: toolStatusDotFromContext(context, theme),\n\t\t\t\t\ttitle,\n\t\t\t\t\tdetail,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\t// lunr: compact-by-default — finished, successful, non-expanded calls\n\t\t\t// render header-only (ctrl+o reveals the full result). Continuation\n\t\t\t// notices stay in execute() output for the model, not in TUI chrome.\n\t\t\tif (!options.isPartial && !options.expanded && !context.isError) {\n\t\t\t\ttext.setText(\"\");\n\t\t\t\treturn text;\n\t\t\t}\n\t\t\ttext.setText(\n\t\t\t\tformatReadResult(context.args, result, options, theme, context.showImages, context.cwd, context.isError),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {\n\treturn wrapToolDefinition(createReadToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/core/tools/read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAK/D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAO5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAYtF,OAAO,EAAoD,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AAEtH,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AASD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B,qCAAqC;IACrC,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,+CAA+C;IAC/C,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACnF;AAQD,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;CAC5B;AA0HD,wBAAgB,wBAAwB,CACvC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,eAAe,GACvB,cAAc,CAAC,OAAO,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC,CAqKhE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CAEnG","sourcesContent":["import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from \"node:path\";\nimport type { AgentTool } from \"@ashx-j/lunr-agent\";\nimport type { Api, ImageContent, Model, TextContent } from \"@ashx-j/lunr-ai\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { getReadmePath } from \"../../config.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { processImage } from \"../../utils/image-process.ts\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../../utils/mime.ts\";\nimport { formatPathRelativeToCwdOrAbsolute } from \"../../utils/paths.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { resolveReadPathAsync, resolveToCwd } from \"./path-utils.ts\";\nimport {\n\tformatGroupedCall,\n\tgetTextOutput,\n\trenderToolFileName,\n\treplaceTabs,\n\tstr,\n\ttoolGroupTree,\n\ttoolStatusDotFromContext,\n} from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from \"./truncate.ts\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport interface ReadToolDetails {\n\ttruncation?: TruncationResult;\n}\n\ninterface CompactReadClassification {\n\tkind: \"docs\" | \"resource\" | \"skill\";\n\tlabel: string;\n}\n\nconst COMPACT_RESOURCE_FILE_NAMES = new Set([\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"]);\n\n/**\n * Pluggable operations for the read tool.\n * Override these to delegate file reading to remote systems (for example SSH).\n */\nexport interface ReadOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Check if file is readable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n\t/** Detect image MIME type, return null or undefined for non-images */\n\tdetectImageMimeType?: (absolutePath: string) => Promise<string | null | undefined>;\n}\n\nconst defaultReadOperations: ReadOperations = {\n\treadFile: (path) => fsReadFile(path),\n\taccess: (path) => fsAccess(path, constants.R_OK),\n\tdetectImageMimeType: detectSupportedImageMimeTypeFromFile,\n};\n\nexport interface ReadToolOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n\t/** Custom operations for file reading. Default: local filesystem */\n\toperations?: ReadOperations;\n}\n\ntype ReadRenderArgs = { path?: string; file_path?: string; offset?: number; limit?: number };\n\nfunction formatReadLineRange(args: ReadRenderArgs | undefined, theme: Theme): string {\n\tif (args?.offset === undefined && args?.limit === undefined) return \"\";\n\tconst startLine = args.offset ?? 1;\n\tconst endLine = args.limit !== undefined ? startLine + args.limit - 1 : \"\";\n\treturn theme.fg(\"warning\", `:${startLine}${endLine ? `-${endLine}` : \"\"}`);\n}\n\nfunction formatReadDetail(\n\targs: ReadRenderArgs | undefined,\n\ttheme: Theme,\n\tcwd: string,\n\texpanded = false,\n): string {\n\tconst pathDisplay = renderToolFileName(str(args?.file_path ?? args?.path), theme, cwd);\n\tconst range = expanded ? formatReadLineRange(args, theme) : \"\";\n\treturn `${pathDisplay}${range}`;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction getNonVisionImageNote(model: Model<Api> | undefined): string | undefined {\n\tif (!model || model.input.includes(\"image\")) {\n\t\treturn undefined;\n\t}\n\treturn \"[Current model does not support images. The image will be omitted from this request.]\";\n}\n\nfunction toPosixPath(filePath: string): string {\n\treturn filePath.split(sep).join(\"/\");\n}\n\nfunction getPiDocsClassification(absolutePath: string): CompactReadClassification | undefined {\n\tconst packageRoot = dirname(getReadmePath());\n\tconst relativePath = relative(resolvePath(packageRoot), resolvePath(absolutePath));\n\tif (\n\t\trelativePath === \"\" ||\n\t\trelativePath === \"..\" ||\n\t\trelativePath.startsWith(`..${sep}`) ||\n\t\tisAbsolute(relativePath)\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst label = toPosixPath(relativePath);\n\tif (label === \"README.md\" || label.startsWith(\"docs/\") || label.startsWith(\"examples/\")) {\n\t\treturn { kind: \"docs\", label };\n\t}\n\treturn undefined;\n}\n\nfunction getCompactReadClassification(\n\targs: ReadRenderArgs | undefined,\n\tcwd: string,\n): CompactReadClassification | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tif (!rawPath) return undefined;\n\n\tconst absolutePath = resolveToCwd(rawPath, cwd);\n\tconst fileName = basename(absolutePath);\n\tif (fileName === \"SKILL.md\") {\n\t\treturn { kind: \"skill\", label: basename(dirname(absolutePath)) || fileName };\n\t}\n\n\tconst docsClassification = getPiDocsClassification(absolutePath);\n\tif (docsClassification) return docsClassification;\n\n\tif (COMPACT_RESOURCE_FILE_NAMES.has(fileName)) {\n\t\treturn { kind: \"resource\", label: formatPathRelativeToCwdOrAbsolute(absolutePath, cwd) };\n\t}\n\n\treturn undefined;\n}\n\nfunction formatReadResult(\n\targs: ReadRenderArgs | undefined,\n\tresult: { content: (TextContent | ImageContent)[]; details?: ReadToolDetails },\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n\t_cwd: string,\n\tisError: boolean,\n): string {\n\tif (!options.expanded && !isError) {\n\t\treturn \"\";\n\t}\n\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst output = getTextOutput(result, showImages);\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tconst renderedLines = lang ? highlightCode(replaceTabs(output), lang) : output.split(\"\\n\");\n\tconst lines = trimTrailingEmptyLines(renderedLines);\n\tconst maxLines = options.expanded ? lines.length : 10;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => (lang ? replaceTabs(line) : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t}\n\n\tconst truncation = result.details?.truncation;\n\tif (truncation?.truncated) {\n\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`)}`;\n\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`)}`;\n\t\t} else {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`)}`;\n\t\t}\n\t}\n\treturn text;\n}\n\nexport function createReadToolDefinition(\n\tcwd: string,\n\toptions?: ReadToolOptions,\n): ToolDefinition<typeof readSchema, ReadToolDetails | undefined> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tconst ops = options?.operations ?? defaultReadOperations;\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tpromptSnippet: \"Read file contents\",\n\t\tpromptGuidelines: [\"Use read to examine files instead of cat or sed.\"],\n\t\tparameters: readSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, offset, limit }: { path: string; offset?: number; limit?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\tctx?,\n\t\t) {\n\t\t\treturn new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(\n\t\t\t\t(resolve, reject) => {\n\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t};\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst absolutePath = await resolveReadPathAsync(path, cwd);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\t// Check if file exists and is readable.\n\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tconst mimeType = ops.detectImageMimeType ? await ops.detectImageMimeType(absolutePath) : undefined;\n\t\t\t\t\t\t\tlet content: (TextContent | ImageContent)[];\n\t\t\t\t\t\t\tlet details: ReadToolDetails | undefined;\n\t\t\t\t\t\t\tconst nonVisionImageNote = getNonVisionImageNote(ctx?.model);\n\t\t\t\t\t\t\tif (mimeType) {\n\t\t\t\t\t\t\t\t// Read image as binary.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst processed = await processImage(buffer, mimeType, { autoResizeImages });\n\t\t\t\t\t\t\t\tif (!processed.ok) {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${mimeType}]\\n${processed.message}`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: textNote }];\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${processed.mimeType}]`;\n\t\t\t\t\t\t\t\t\tif (processed.hints.length > 0) textNote += `\\n${processed.hints.join(\"\\n\")}`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t\t{ type: \"text\", text: textNote },\n\t\t\t\t\t\t\t\t\t\t{ type: \"image\", data: processed.data, mimeType: processed.mimeType },\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Read text content.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst textContent = buffer.toString(\"utf-8\");\n\t\t\t\t\t\t\t\tconst allLines = textContent.split(\"\\n\");\n\t\t\t\t\t\t\t\tconst totalFileLines = allLines.length;\n\t\t\t\t\t\t\t\t// Apply offset if specified. Convert from 1-indexed input to 0-indexed array access.\n\t\t\t\t\t\t\t\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\t\t\t\t\t\t\t\tconst startLineDisplay = startLine + 1;\n\t\t\t\t\t\t\t\t// Check if offset is out of bounds.\n\t\t\t\t\t\t\t\tif (startLine >= allLines.length) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet selectedContent: string;\n\t\t\t\t\t\t\t\tlet userLimitedLines: number | undefined;\n\t\t\t\t\t\t\t\t// If limit is specified by the user, honor it first. Otherwise truncateHead decides.\n\t\t\t\t\t\t\t\tif (limit !== undefined) {\n\t\t\t\t\t\t\t\t\tconst endLine = Math.min(startLine + limit, allLines.length);\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine, endLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t\tuserLimitedLines = endLine - startLine;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Apply truncation, respecting both line and byte limits.\n\t\t\t\t\t\t\t\tconst truncation = truncateHead(selectedContent);\n\t\t\t\t\t\t\t\tlet outputText: string;\n\t\t\t\t\t\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t\t\t\t\t\t// First line alone exceeds the byte limit. Point the model at a bash fallback.\n\t\t\t\t\t\t\t\t\tconst firstLineSize = formatSize(Buffer.byteLength(allLines[startLine], \"utf-8\"));\n\t\t\t\t\t\t\t\t\toutputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (truncation.truncated) {\n\t\t\t\t\t\t\t\t\t// Truncation occurred. Build an actionable continuation notice.\n\t\t\t\t\t\t\t\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\t\t\t\t\t\t\t\tconst nextOffset = endLineDisplay + 1;\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {\n\t\t\t\t\t\t\t\t\t// User-specified limit stopped early, but the file still has more content.\n\t\t\t\t\t\t\t\t\tconst remaining = allLines.length - (startLine + userLimitedLines);\n\t\t\t\t\t\t\t\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\t\t\t\t\t\t\t\toutputText = `${truncation.content}\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// No truncation and no remaining user-limited content.\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: outputText }];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tresolve({ content, details });\n\t\t\t\t\t\t} catch (error: any) {\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tif (!aborted) reject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst compact = !context.isPartial && !context.expanded && !context.isError;\n\t\t\tconst classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;\n\t\t\tlet title = theme.fg(\"toolTitle\", theme.bold(\"read\"));\n\t\t\tlet detail: string;\n\t\t\tif (classification?.kind === \"skill\") {\n\t\t\t\ttitle = theme.fg(\"customMessageLabel\", `\\x1b[1m[skill]\\x1b[22m`);\n\t\t\t\tdetail = theme.fg(\"customMessageText\", classification.label);\n\t\t\t} else if (classification) {\n\t\t\t\ttitle = theme.fg(\"toolTitle\", theme.bold(`read ${classification.kind}`));\n\t\t\t\tdetail = theme.fg(\"accent\", classification.label);\n\t\t\t} else {\n\t\t\t\tdetail = formatReadDetail(args, theme, context.cwd, context.expanded);\n\t\t\t}\n\t\t\ttext.setText(\n\t\t\t\tformatGroupedCall({\n\t\t\t\t\trole: context.groupRole ?? \"singleton\",\n\t\t\t\t\tcompact,\n\t\t\t\t\ttree: toolGroupTree(context),\n\t\t\t\t\tdot: toolStatusDotFromContext(context, theme),\n\t\t\t\t\ttitle,\n\t\t\t\t\tdetail,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\t// lunr: compact-by-default — finished, successful, non-expanded calls\n\t\t\t// render header-only (ctrl+o reveals the full result). Continuation\n\t\t\t// notices stay in execute() output for the model, not in TUI chrome.\n\t\t\tif (!options.isPartial && !options.expanded && !context.isError) {\n\t\t\t\ttext.setText(\"\");\n\t\t\t\treturn text;\n\t\t\t}\n\t\t\ttext.setText(\n\t\t\t\tformatReadResult(context.args, result, options, theme, context.showImages, context.cwd, context.isError),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {\n\treturn wrapToolDefinition(createReadToolDefinition(cwd, options));\n}\n"]}
|
package/dist/core/tools/read.js
CHANGED
|
@@ -10,7 +10,7 @@ import { processImage } from "../../utils/image-process.js";
|
|
|
10
10
|
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
11
11
|
import { formatPathRelativeToCwdOrAbsolute } from "../../utils/paths.js";
|
|
12
12
|
import { resolveReadPathAsync, resolveToCwd } from "./path-utils.js";
|
|
13
|
-
import { formatGroupedCall, getTextOutput, renderToolFileName, replaceTabs, str, toolStatusDotFromContext, } from "./render-utils.js";
|
|
13
|
+
import { formatGroupedCall, getTextOutput, renderToolFileName, replaceTabs, str, toolGroupTree, toolStatusDotFromContext, } from "./render-utils.js";
|
|
14
14
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
15
15
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead } from "./truncate.js";
|
|
16
16
|
const readSchema = Type.Object({
|
|
@@ -262,6 +262,7 @@ export function createReadToolDefinition(cwd, options) {
|
|
|
262
262
|
text.setText(formatGroupedCall({
|
|
263
263
|
role: context.groupRole ?? "singleton",
|
|
264
264
|
compact,
|
|
265
|
+
tree: toolGroupTree(context),
|
|
265
266
|
dot: toolStatusDotFromContext(context, theme),
|
|
266
267
|
title,
|
|
267
268
|
detail,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../../src/core/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from \"node:path\";\nimport type { AgentTool } from \"@ashx-j/lunr-agent\";\nimport type { Api, ImageContent, Model, TextContent } from \"@ashx-j/lunr-ai\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { getReadmePath } from \"../../config.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { processImage } from \"../../utils/image-process.ts\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../../utils/mime.ts\";\nimport { formatPathRelativeToCwdOrAbsolute } from \"../../utils/paths.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { resolveReadPathAsync, resolveToCwd } from \"./path-utils.ts\";\nimport {\n\tformatGroupedCall,\n\tgetTextOutput,\n\trenderToolFileName,\n\treplaceTabs,\n\tstr,\n\ttoolStatusDotFromContext,\n} from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from \"./truncate.ts\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport interface ReadToolDetails {\n\ttruncation?: TruncationResult;\n}\n\ninterface CompactReadClassification {\n\tkind: \"docs\" | \"resource\" | \"skill\";\n\tlabel: string;\n}\n\nconst COMPACT_RESOURCE_FILE_NAMES = new Set([\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"]);\n\n/**\n * Pluggable operations for the read tool.\n * Override these to delegate file reading to remote systems (for example SSH).\n */\nexport interface ReadOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Check if file is readable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n\t/** Detect image MIME type, return null or undefined for non-images */\n\tdetectImageMimeType?: (absolutePath: string) => Promise<string | null | undefined>;\n}\n\nconst defaultReadOperations: ReadOperations = {\n\treadFile: (path) => fsReadFile(path),\n\taccess: (path) => fsAccess(path, constants.R_OK),\n\tdetectImageMimeType: detectSupportedImageMimeTypeFromFile,\n};\n\nexport interface ReadToolOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n\t/** Custom operations for file reading. Default: local filesystem */\n\toperations?: ReadOperations;\n}\n\ntype ReadRenderArgs = { path?: string; file_path?: string; offset?: number; limit?: number };\n\nfunction formatReadLineRange(args: ReadRenderArgs | undefined, theme: Theme): string {\n\tif (args?.offset === undefined && args?.limit === undefined) return \"\";\n\tconst startLine = args.offset ?? 1;\n\tconst endLine = args.limit !== undefined ? startLine + args.limit - 1 : \"\";\n\treturn theme.fg(\"warning\", `:${startLine}${endLine ? `-${endLine}` : \"\"}`);\n}\n\nfunction formatReadDetail(\n\targs: ReadRenderArgs | undefined,\n\ttheme: Theme,\n\tcwd: string,\n\texpanded = false,\n): string {\n\tconst pathDisplay = renderToolFileName(str(args?.file_path ?? args?.path), theme, cwd);\n\tconst range = expanded ? formatReadLineRange(args, theme) : \"\";\n\treturn `${pathDisplay}${range}`;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction getNonVisionImageNote(model: Model<Api> | undefined): string | undefined {\n\tif (!model || model.input.includes(\"image\")) {\n\t\treturn undefined;\n\t}\n\treturn \"[Current model does not support images. The image will be omitted from this request.]\";\n}\n\nfunction toPosixPath(filePath: string): string {\n\treturn filePath.split(sep).join(\"/\");\n}\n\nfunction getPiDocsClassification(absolutePath: string): CompactReadClassification | undefined {\n\tconst packageRoot = dirname(getReadmePath());\n\tconst relativePath = relative(resolvePath(packageRoot), resolvePath(absolutePath));\n\tif (\n\t\trelativePath === \"\" ||\n\t\trelativePath === \"..\" ||\n\t\trelativePath.startsWith(`..${sep}`) ||\n\t\tisAbsolute(relativePath)\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst label = toPosixPath(relativePath);\n\tif (label === \"README.md\" || label.startsWith(\"docs/\") || label.startsWith(\"examples/\")) {\n\t\treturn { kind: \"docs\", label };\n\t}\n\treturn undefined;\n}\n\nfunction getCompactReadClassification(\n\targs: ReadRenderArgs | undefined,\n\tcwd: string,\n): CompactReadClassification | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tif (!rawPath) return undefined;\n\n\tconst absolutePath = resolveToCwd(rawPath, cwd);\n\tconst fileName = basename(absolutePath);\n\tif (fileName === \"SKILL.md\") {\n\t\treturn { kind: \"skill\", label: basename(dirname(absolutePath)) || fileName };\n\t}\n\n\tconst docsClassification = getPiDocsClassification(absolutePath);\n\tif (docsClassification) return docsClassification;\n\n\tif (COMPACT_RESOURCE_FILE_NAMES.has(fileName)) {\n\t\treturn { kind: \"resource\", label: formatPathRelativeToCwdOrAbsolute(absolutePath, cwd) };\n\t}\n\n\treturn undefined;\n}\n\nfunction formatReadResult(\n\targs: ReadRenderArgs | undefined,\n\tresult: { content: (TextContent | ImageContent)[]; details?: ReadToolDetails },\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n\t_cwd: string,\n\tisError: boolean,\n): string {\n\tif (!options.expanded && !isError) {\n\t\treturn \"\";\n\t}\n\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst output = getTextOutput(result, showImages);\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tconst renderedLines = lang ? highlightCode(replaceTabs(output), lang) : output.split(\"\\n\");\n\tconst lines = trimTrailingEmptyLines(renderedLines);\n\tconst maxLines = options.expanded ? lines.length : 10;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => (lang ? replaceTabs(line) : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t}\n\n\tconst truncation = result.details?.truncation;\n\tif (truncation?.truncated) {\n\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`)}`;\n\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`)}`;\n\t\t} else {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`)}`;\n\t\t}\n\t}\n\treturn text;\n}\n\nexport function createReadToolDefinition(\n\tcwd: string,\n\toptions?: ReadToolOptions,\n): ToolDefinition<typeof readSchema, ReadToolDetails | undefined> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tconst ops = options?.operations ?? defaultReadOperations;\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tpromptSnippet: \"Read file contents\",\n\t\tpromptGuidelines: [\"Use read to examine files instead of cat or sed.\"],\n\t\tparameters: readSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, offset, limit }: { path: string; offset?: number; limit?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\tctx?,\n\t\t) {\n\t\t\treturn new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(\n\t\t\t\t(resolve, reject) => {\n\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t};\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst absolutePath = await resolveReadPathAsync(path, cwd);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\t// Check if file exists and is readable.\n\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tconst mimeType = ops.detectImageMimeType ? await ops.detectImageMimeType(absolutePath) : undefined;\n\t\t\t\t\t\t\tlet content: (TextContent | ImageContent)[];\n\t\t\t\t\t\t\tlet details: ReadToolDetails | undefined;\n\t\t\t\t\t\t\tconst nonVisionImageNote = getNonVisionImageNote(ctx?.model);\n\t\t\t\t\t\t\tif (mimeType) {\n\t\t\t\t\t\t\t\t// Read image as binary.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst processed = await processImage(buffer, mimeType, { autoResizeImages });\n\t\t\t\t\t\t\t\tif (!processed.ok) {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${mimeType}]\\n${processed.message}`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: textNote }];\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${processed.mimeType}]`;\n\t\t\t\t\t\t\t\t\tif (processed.hints.length > 0) textNote += `\\n${processed.hints.join(\"\\n\")}`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t\t{ type: \"text\", text: textNote },\n\t\t\t\t\t\t\t\t\t\t{ type: \"image\", data: processed.data, mimeType: processed.mimeType },\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Read text content.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst textContent = buffer.toString(\"utf-8\");\n\t\t\t\t\t\t\t\tconst allLines = textContent.split(\"\\n\");\n\t\t\t\t\t\t\t\tconst totalFileLines = allLines.length;\n\t\t\t\t\t\t\t\t// Apply offset if specified. Convert from 1-indexed input to 0-indexed array access.\n\t\t\t\t\t\t\t\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\t\t\t\t\t\t\t\tconst startLineDisplay = startLine + 1;\n\t\t\t\t\t\t\t\t// Check if offset is out of bounds.\n\t\t\t\t\t\t\t\tif (startLine >= allLines.length) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet selectedContent: string;\n\t\t\t\t\t\t\t\tlet userLimitedLines: number | undefined;\n\t\t\t\t\t\t\t\t// If limit is specified by the user, honor it first. Otherwise truncateHead decides.\n\t\t\t\t\t\t\t\tif (limit !== undefined) {\n\t\t\t\t\t\t\t\t\tconst endLine = Math.min(startLine + limit, allLines.length);\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine, endLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t\tuserLimitedLines = endLine - startLine;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Apply truncation, respecting both line and byte limits.\n\t\t\t\t\t\t\t\tconst truncation = truncateHead(selectedContent);\n\t\t\t\t\t\t\t\tlet outputText: string;\n\t\t\t\t\t\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t\t\t\t\t\t// First line alone exceeds the byte limit. Point the model at a bash fallback.\n\t\t\t\t\t\t\t\t\tconst firstLineSize = formatSize(Buffer.byteLength(allLines[startLine], \"utf-8\"));\n\t\t\t\t\t\t\t\t\toutputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (truncation.truncated) {\n\t\t\t\t\t\t\t\t\t// Truncation occurred. Build an actionable continuation notice.\n\t\t\t\t\t\t\t\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\t\t\t\t\t\t\t\tconst nextOffset = endLineDisplay + 1;\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {\n\t\t\t\t\t\t\t\t\t// User-specified limit stopped early, but the file still has more content.\n\t\t\t\t\t\t\t\t\tconst remaining = allLines.length - (startLine + userLimitedLines);\n\t\t\t\t\t\t\t\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\t\t\t\t\t\t\t\toutputText = `${truncation.content}\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// No truncation and no remaining user-limited content.\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: outputText }];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tresolve({ content, details });\n\t\t\t\t\t\t} catch (error: any) {\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tif (!aborted) reject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst compact = !context.isPartial && !context.expanded && !context.isError;\n\t\t\tconst classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;\n\t\t\tlet title = theme.fg(\"toolTitle\", theme.bold(\"read\"));\n\t\t\tlet detail: string;\n\t\t\tif (classification?.kind === \"skill\") {\n\t\t\t\ttitle = theme.fg(\"customMessageLabel\", `\\x1b[1m[skill]\\x1b[22m`);\n\t\t\t\tdetail = theme.fg(\"customMessageText\", classification.label);\n\t\t\t} else if (classification) {\n\t\t\t\ttitle = theme.fg(\"toolTitle\", theme.bold(`read ${classification.kind}`));\n\t\t\t\tdetail = theme.fg(\"accent\", classification.label);\n\t\t\t} else {\n\t\t\t\tdetail = formatReadDetail(args, theme, context.cwd, context.expanded);\n\t\t\t}\n\t\t\ttext.setText(\n\t\t\t\tformatGroupedCall({\n\t\t\t\t\trole: context.groupRole ?? \"singleton\",\n\t\t\t\t\tcompact,\n\t\t\t\t\tdot: toolStatusDotFromContext(context, theme),\n\t\t\t\t\ttitle,\n\t\t\t\t\tdetail,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\t// lunr: compact-by-default — finished, successful, non-expanded calls\n\t\t\t// render header-only (ctrl+o reveals the full result). Continuation\n\t\t\t// notices stay in execute() output for the model, not in TUI chrome.\n\t\t\tif (!options.isPartial && !options.expanded && !context.isError) {\n\t\t\t\ttext.setText(\"\");\n\t\t\t\treturn text;\n\t\t\t}\n\t\t\ttext.setText(\n\t\t\t\tformatReadResult(context.args, result, options, theme, context.showImages, context.cwd, context.isError),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {\n\treturn wrapToolDefinition(createReadToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../../src/core/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import { basename, dirname, isAbsolute, relative, resolve as resolvePath, sep } from \"node:path\";\nimport type { AgentTool } from \"@ashx-j/lunr-agent\";\nimport type { Api, ImageContent, Model, TextContent } from \"@ashx-j/lunr-ai\";\nimport { Text } from \"@ashx-j/lunr-tui\";\nimport { constants } from \"fs\";\nimport { access as fsAccess, readFile as fsReadFile } from \"fs/promises\";\nimport { type Static, Type } from \"typebox\";\nimport { getReadmePath } from \"../../config.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { processImage } from \"../../utils/image-process.ts\";\nimport { detectSupportedImageMimeTypeFromFile } from \"../../utils/mime.ts\";\nimport { formatPathRelativeToCwdOrAbsolute } from \"../../utils/paths.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { resolveReadPathAsync, resolveToCwd } from \"./path-utils.ts\";\nimport {\n\tformatGroupedCall,\n\tgetTextOutput,\n\trenderToolFileName,\n\treplaceTabs,\n\tstr,\n\ttoolGroupTree,\n\ttoolStatusDotFromContext,\n} from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from \"./truncate.ts\";\n\nconst readSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nexport type ReadToolInput = Static<typeof readSchema>;\n\nexport interface ReadToolDetails {\n\ttruncation?: TruncationResult;\n}\n\ninterface CompactReadClassification {\n\tkind: \"docs\" | \"resource\" | \"skill\";\n\tlabel: string;\n}\n\nconst COMPACT_RESOURCE_FILE_NAMES = new Set([\"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"]);\n\n/**\n * Pluggable operations for the read tool.\n * Override these to delegate file reading to remote systems (for example SSH).\n */\nexport interface ReadOperations {\n\t/** Read file contents as a Buffer */\n\treadFile: (absolutePath: string) => Promise<Buffer>;\n\t/** Check if file is readable (throw if not) */\n\taccess: (absolutePath: string) => Promise<void>;\n\t/** Detect image MIME type, return null or undefined for non-images */\n\tdetectImageMimeType?: (absolutePath: string) => Promise<string | null | undefined>;\n}\n\nconst defaultReadOperations: ReadOperations = {\n\treadFile: (path) => fsReadFile(path),\n\taccess: (path) => fsAccess(path, constants.R_OK),\n\tdetectImageMimeType: detectSupportedImageMimeTypeFromFile,\n};\n\nexport interface ReadToolOptions {\n\t/** Whether to auto-resize images to 2000x2000 max. Default: true */\n\tautoResizeImages?: boolean;\n\t/** Custom operations for file reading. Default: local filesystem */\n\toperations?: ReadOperations;\n}\n\ntype ReadRenderArgs = { path?: string; file_path?: string; offset?: number; limit?: number };\n\nfunction formatReadLineRange(args: ReadRenderArgs | undefined, theme: Theme): string {\n\tif (args?.offset === undefined && args?.limit === undefined) return \"\";\n\tconst startLine = args.offset ?? 1;\n\tconst endLine = args.limit !== undefined ? startLine + args.limit - 1 : \"\";\n\treturn theme.fg(\"warning\", `:${startLine}${endLine ? `-${endLine}` : \"\"}`);\n}\n\nfunction formatReadDetail(\n\targs: ReadRenderArgs | undefined,\n\ttheme: Theme,\n\tcwd: string,\n\texpanded = false,\n): string {\n\tconst pathDisplay = renderToolFileName(str(args?.file_path ?? args?.path), theme, cwd);\n\tconst range = expanded ? formatReadLineRange(args, theme) : \"\";\n\treturn `${pathDisplay}${range}`;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction getNonVisionImageNote(model: Model<Api> | undefined): string | undefined {\n\tif (!model || model.input.includes(\"image\")) {\n\t\treturn undefined;\n\t}\n\treturn \"[Current model does not support images. The image will be omitted from this request.]\";\n}\n\nfunction toPosixPath(filePath: string): string {\n\treturn filePath.split(sep).join(\"/\");\n}\n\nfunction getPiDocsClassification(absolutePath: string): CompactReadClassification | undefined {\n\tconst packageRoot = dirname(getReadmePath());\n\tconst relativePath = relative(resolvePath(packageRoot), resolvePath(absolutePath));\n\tif (\n\t\trelativePath === \"\" ||\n\t\trelativePath === \"..\" ||\n\t\trelativePath.startsWith(`..${sep}`) ||\n\t\tisAbsolute(relativePath)\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst label = toPosixPath(relativePath);\n\tif (label === \"README.md\" || label.startsWith(\"docs/\") || label.startsWith(\"examples/\")) {\n\t\treturn { kind: \"docs\", label };\n\t}\n\treturn undefined;\n}\n\nfunction getCompactReadClassification(\n\targs: ReadRenderArgs | undefined,\n\tcwd: string,\n): CompactReadClassification | undefined {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tif (!rawPath) return undefined;\n\n\tconst absolutePath = resolveToCwd(rawPath, cwd);\n\tconst fileName = basename(absolutePath);\n\tif (fileName === \"SKILL.md\") {\n\t\treturn { kind: \"skill\", label: basename(dirname(absolutePath)) || fileName };\n\t}\n\n\tconst docsClassification = getPiDocsClassification(absolutePath);\n\tif (docsClassification) return docsClassification;\n\n\tif (COMPACT_RESOURCE_FILE_NAMES.has(fileName)) {\n\t\treturn { kind: \"resource\", label: formatPathRelativeToCwdOrAbsolute(absolutePath, cwd) };\n\t}\n\n\treturn undefined;\n}\n\nfunction formatReadResult(\n\targs: ReadRenderArgs | undefined,\n\tresult: { content: (TextContent | ImageContent)[]; details?: ReadToolDetails },\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n\t_cwd: string,\n\tisError: boolean,\n): string {\n\tif (!options.expanded && !isError) {\n\t\treturn \"\";\n\t}\n\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst output = getTextOutput(result, showImages);\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tconst renderedLines = lang ? highlightCode(replaceTabs(output), lang) : output.split(\"\\n\");\n\tconst lines = trimTrailingEmptyLines(renderedLines);\n\tconst maxLines = options.expanded ? lines.length : 10;\n\tconst displayLines = lines.slice(0, maxLines);\n\tconst remaining = lines.length - maxLines;\n\tlet text = `\\n${displayLines.map((line) => (lang ? replaceTabs(line) : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\tif (remaining > 0) {\n\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t}\n\n\tconst truncation = result.details?.truncation;\n\tif (truncation?.truncated) {\n\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`)}`;\n\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`)}`;\n\t\t} else {\n\t\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`)}`;\n\t\t}\n\t}\n\treturn text;\n}\n\nexport function createReadToolDefinition(\n\tcwd: string,\n\toptions?: ReadToolOptions,\n): ToolDefinition<typeof readSchema, ReadToolDetails | undefined> {\n\tconst autoResizeImages = options?.autoResizeImages ?? true;\n\tconst ops = options?.operations ?? defaultReadOperations;\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp, bmp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files. When you need the full file, continue with offset until complete.`,\n\t\tpromptSnippet: \"Read file contents\",\n\t\tpromptGuidelines: [\"Use read to examine files instead of cat or sed.\"],\n\t\tparameters: readSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, offset, limit }: { path: string; offset?: number; limit?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\tctx?,\n\t\t) {\n\t\t\treturn new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(\n\t\t\t\t(resolve, reject) => {\n\t\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\t};\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst absolutePath = await resolveReadPathAsync(path, cwd);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\t// Check if file exists and is readable.\n\t\t\t\t\t\t\tawait ops.access(absolutePath);\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tconst mimeType = ops.detectImageMimeType ? await ops.detectImageMimeType(absolutePath) : undefined;\n\t\t\t\t\t\t\tlet content: (TextContent | ImageContent)[];\n\t\t\t\t\t\t\tlet details: ReadToolDetails | undefined;\n\t\t\t\t\t\t\tconst nonVisionImageNote = getNonVisionImageNote(ctx?.model);\n\t\t\t\t\t\t\tif (mimeType) {\n\t\t\t\t\t\t\t\t// Read image as binary.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst processed = await processImage(buffer, mimeType, { autoResizeImages });\n\t\t\t\t\t\t\t\tif (!processed.ok) {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${mimeType}]\\n${processed.message}`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: textNote }];\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tlet textNote = `Read image file [${processed.mimeType}]`;\n\t\t\t\t\t\t\t\t\tif (processed.hints.length > 0) textNote += `\\n${processed.hints.join(\"\\n\")}`;\n\t\t\t\t\t\t\t\t\tif (nonVisionImageNote) textNote += `\\n${nonVisionImageNote}`;\n\t\t\t\t\t\t\t\t\tcontent = [\n\t\t\t\t\t\t\t\t\t\t{ type: \"text\", text: textNote },\n\t\t\t\t\t\t\t\t\t\t{ type: \"image\", data: processed.data, mimeType: processed.mimeType },\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// Read text content.\n\t\t\t\t\t\t\t\tconst buffer = await ops.readFile(absolutePath);\n\t\t\t\t\t\t\t\tconst textContent = buffer.toString(\"utf-8\");\n\t\t\t\t\t\t\t\tconst allLines = textContent.split(\"\\n\");\n\t\t\t\t\t\t\t\tconst totalFileLines = allLines.length;\n\t\t\t\t\t\t\t\t// Apply offset if specified. Convert from 1-indexed input to 0-indexed array access.\n\t\t\t\t\t\t\t\tconst startLine = offset ? Math.max(0, offset - 1) : 0;\n\t\t\t\t\t\t\t\tconst startLineDisplay = startLine + 1;\n\t\t\t\t\t\t\t\t// Check if offset is out of bounds.\n\t\t\t\t\t\t\t\tif (startLine >= allLines.length) {\n\t\t\t\t\t\t\t\t\tthrow new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tlet selectedContent: string;\n\t\t\t\t\t\t\t\tlet userLimitedLines: number | undefined;\n\t\t\t\t\t\t\t\t// If limit is specified by the user, honor it first. Otherwise truncateHead decides.\n\t\t\t\t\t\t\t\tif (limit !== undefined) {\n\t\t\t\t\t\t\t\t\tconst endLine = Math.min(startLine + limit, allLines.length);\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine, endLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t\tuserLimitedLines = endLine - startLine;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tselectedContent = allLines.slice(startLine).join(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Apply truncation, respecting both line and byte limits.\n\t\t\t\t\t\t\t\tconst truncation = truncateHead(selectedContent);\n\t\t\t\t\t\t\t\tlet outputText: string;\n\t\t\t\t\t\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t\t\t\t\t\t// First line alone exceeds the byte limit. Point the model at a bash fallback.\n\t\t\t\t\t\t\t\t\tconst firstLineSize = formatSize(Buffer.byteLength(allLines[startLine], \"utf-8\"));\n\t\t\t\t\t\t\t\t\toutputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (truncation.truncated) {\n\t\t\t\t\t\t\t\t\t// Truncation occurred. Build an actionable continuation notice.\n\t\t\t\t\t\t\t\t\tconst endLineDisplay = startLineDisplay + truncation.outputLines - 1;\n\t\t\t\t\t\t\t\t\tconst nextOffset = endLineDisplay + 1;\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tdetails = { truncation };\n\t\t\t\t\t\t\t\t} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {\n\t\t\t\t\t\t\t\t\t// User-specified limit stopped early, but the file still has more content.\n\t\t\t\t\t\t\t\t\tconst remaining = allLines.length - (startLine + userLimitedLines);\n\t\t\t\t\t\t\t\t\tconst nextOffset = startLine + userLimitedLines + 1;\n\t\t\t\t\t\t\t\t\toutputText = `${truncation.content}\\n\\n[${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t// No truncation and no remaining user-limited content.\n\t\t\t\t\t\t\t\t\toutputText = truncation.content;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontent = [{ type: \"text\", text: outputText }];\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (aborted) return;\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tresolve({ content, details });\n\t\t\t\t\t\t} catch (error: any) {\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t\tif (!aborted) reject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t})();\n\t\t\t\t},\n\t\t\t);\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\tconst compact = !context.isPartial && !context.expanded && !context.isError;\n\t\t\tconst classification = !context.expanded ? getCompactReadClassification(args, context.cwd) : undefined;\n\t\t\tlet title = theme.fg(\"toolTitle\", theme.bold(\"read\"));\n\t\t\tlet detail: string;\n\t\t\tif (classification?.kind === \"skill\") {\n\t\t\t\ttitle = theme.fg(\"customMessageLabel\", `\\x1b[1m[skill]\\x1b[22m`);\n\t\t\t\tdetail = theme.fg(\"customMessageText\", classification.label);\n\t\t\t} else if (classification) {\n\t\t\t\ttitle = theme.fg(\"toolTitle\", theme.bold(`read ${classification.kind}`));\n\t\t\t\tdetail = theme.fg(\"accent\", classification.label);\n\t\t\t} else {\n\t\t\t\tdetail = formatReadDetail(args, theme, context.cwd, context.expanded);\n\t\t\t}\n\t\t\ttext.setText(\n\t\t\t\tformatGroupedCall({\n\t\t\t\t\trole: context.groupRole ?? \"singleton\",\n\t\t\t\t\tcompact,\n\t\t\t\t\ttree: toolGroupTree(context),\n\t\t\t\t\tdot: toolStatusDotFromContext(context, theme),\n\t\t\t\t\ttitle,\n\t\t\t\t\tdetail,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\t// lunr: compact-by-default — finished, successful, non-expanded calls\n\t\t\t// render header-only (ctrl+o reveals the full result). Continuation\n\t\t\t// notices stay in execute() output for the model, not in TUI chrome.\n\t\t\tif (!options.isPartial && !options.expanded && !context.isError) {\n\t\t\t\ttext.setText(\"\");\n\t\t\t\treturn text;\n\t\t\t}\n\t\t\ttext.setText(\n\t\t\t\tformatReadResult(context.args, result, options, theme, context.showImages, context.cwd, context.isError),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createReadTool(cwd: string, options?: ReadToolOptions): AgentTool<typeof readSchema> {\n\treturn wrapToolDefinition(createReadToolDefinition(cwd, options));\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ImageContent, TextContent } from "@ashx-j/lunr-ai";
|
|
2
|
-
import type { ToolGroupRole } from "../extensions/types.ts";
|
|
3
2
|
import type { Theme } from "../../modes/interactive/theme/theme.ts";
|
|
3
|
+
import type { ToolGroupRole } from "../extensions/types.ts";
|
|
4
4
|
export declare function shortenPath(path: unknown): string;
|
|
5
5
|
export declare function linkPath(styledText: string, rawPath: string, cwd: string): string;
|
|
6
6
|
export declare function str(value: unknown): string | null;
|
|
@@ -32,11 +32,21 @@ export declare function toolGroupRole(continuation: boolean, followed: boolean):
|
|
|
32
32
|
* └─ usage-service.ts
|
|
33
33
|
* ```
|
|
34
34
|
*
|
|
35
|
-
* Singletons
|
|
35
|
+
* Singletons and expanded rows keep `● title detail` on one line.
|
|
36
|
+
* Collapsed errors stay in the same-name tree; the error body is hoisted
|
|
37
|
+
* under the last leaf so it does not split the file list. Grouped still-running
|
|
38
|
+
* cards share the same tree as finished ones. `compact` is header-only body
|
|
39
|
+
* hiding at the caller; pass `tree` for chrome. `isError` is accepted so
|
|
40
|
+
* callers can keep passing it, but it does not drop tree chrome.
|
|
36
41
|
*/
|
|
42
|
+
export declare function toolGroupTree(context: {
|
|
43
|
+
expanded?: boolean;
|
|
44
|
+
isError?: boolean;
|
|
45
|
+
}): boolean;
|
|
37
46
|
export declare function formatGroupedCall(opts: {
|
|
38
47
|
role: ToolGroupRole;
|
|
39
|
-
compact
|
|
48
|
+
compact?: boolean;
|
|
49
|
+
tree?: boolean;
|
|
40
50
|
dot: string;
|
|
41
51
|
title: string;
|
|
42
52
|
detail?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-utils.d.ts","sourceRoot":"","sources":["../../../src/core/tools/render-utils.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEvE,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"render-utils.d.ts","sourceRoot":"","sources":["../../../src/core/tools/render-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport { basename } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { ImageContent, TextContent } from \"@ashx-j/lunr-ai\";\nimport { getCapabilities, getImageDimensions, hyperlink, imageFallback } from \"@ashx-j/lunr-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { stripAnsi } from \"../../utils/ansi.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\nimport type { ToolGroupRole } from \"../extensions/types.ts\";\n\nexport function shortenPath(path: unknown): string {\n\tif (typeof path !== \"string\") return \"\";\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nexport function linkPath(styledText: string, rawPath: string, cwd: string): string {\n\tif (!getCapabilities().hyperlinks) return styledText;\n\tconst absolutePath = resolvePath(rawPath, cwd);\n\treturn hyperlink(styledText, pathToFileURL(absolutePath).href);\n}\n\nexport function str(value: unknown): string | null {\n\tif (typeof value === \"string\") return value;\n\tif (value == null) return \"\";\n\treturn null;\n}\n\nexport function replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport function normalizeDisplayText(text: string): string {\n\treturn text.replace(/\\r/g, \"\");\n}\n\nexport function getTextOutput(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> } | undefined,\n\tshowImages: boolean,\n): string {\n\tif (!result) return \"\";\n\n\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\tconst imageBlocks = result.content.filter((c) => c.type === \"image\");\n\n\tlet output = textBlocks.map((c) => sanitizeBinaryOutput(stripAnsi(c.text || \"\")).replace(/\\r/g, \"\")).join(\"\\n\");\n\n\tconst caps = getCapabilities();\n\tif (imageBlocks.length > 0 && (!caps.images || !showImages)) {\n\t\tconst imageIndicators = imageBlocks\n\t\t\t.map((img) => {\n\t\t\t\tconst mimeType = img.mimeType ?? \"image/unknown\";\n\t\t\t\tconst dims =\n\t\t\t\t\timg.data && img.mimeType ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\treturn imageFallback(mimeType, dims);\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t}\n\n\treturn output;\n}\n\nexport type ToolRenderResultLike<TDetails> = {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails: TDetails;\n};\n\nexport function invalidArgText(theme: Theme): string {\n\treturn theme.fg(\"error\", \"[invalid arg]\");\n}\n\n// lunr: compact-by-default — tool execute() paths append truncation/limit notices\n// as a trailing \"\\n\\n[...]\" block. Compact (header-only) results blank the body,\n// so this extracts that notice to keep it visible without the full output.\nexport function extractTrailingNotice(text: string): string | undefined {\n\tconst trimmed = text.trimEnd();\n\tif (!trimmed.endsWith(\"]\")) return undefined;\n\tconst start = trimmed.lastIndexOf(\"\\n\\n[\");\n\tif (start === -1) return undefined;\n\treturn trimmed.slice(start + 2);\n}\n\nexport type ToolStatusDotState = \"pending\" | \"success\" | \"error\";\n\nexport type { ToolGroupRole };\n\nexport function toolGroupRole(continuation: boolean, followed: boolean): ToolGroupRole {\n\tif (!continuation && followed) return \"first\";\n\tif (continuation && followed) return \"middle\";\n\tif (continuation && !followed) return \"last\";\n\treturn \"singleton\";\n}\n\n/**\n * Quieter same-name chrome: the verb is printed once, then files hang off a tree.\n *\n * ```\n * ● read\n * ├─ resolve.ts\n * └─ usage-service.ts\n * ```\n *\n * Singletons and expanded rows keep `● title detail` on one line.\n * Collapsed errors stay in the same-name tree; the error body is hoisted\n * under the last leaf so it does not split the file list. Grouped still-running\n * cards share the same tree as finished ones. `compact` is header-only body\n * hiding at the caller; pass `tree` for chrome. `isError` is accepted so\n * callers can keep passing it, but it does not drop tree chrome.\n */\nexport function toolGroupTree(context: { expanded?: boolean; isError?: boolean }): boolean {\n\treturn !context.expanded;\n}\n\nexport function formatGroupedCall(opts: {\n\trole: ToolGroupRole;\n\tcompact?: boolean;\n\ttree?: boolean;\n\tdot: string;\n\ttitle: string;\n\tdetail?: string;\n}): string {\n\tconst detail = opts.detail?.trim() ? opts.detail : \"\";\n\tconst useTree = (opts.tree ?? opts.compact === true) && opts.role !== \"singleton\" && detail.length > 0;\n\tif (!useTree) {\n\t\treturn detail ? `${opts.dot} ${opts.title} ${detail}` : `${opts.dot} ${opts.title}`;\n\t}\n\tconst branch = opts.role === \"last\" ? \"└─\" : \"├─\";\n\tif (opts.role === \"first\") {\n\t\treturn `${opts.dot} ${opts.title}\\n ${branch} ${detail}`;\n\t}\n\treturn ` ${branch} ${detail}`;\n}\n\n/**\n * Colored status dot used as the sole state indicator on tool titles (moon theme:\n * backgrounds stay neutral). Pending = muted, success = green, error = red.\n */\nexport function toolStatusDot(state: ToolStatusDotState, theme: Theme): string {\n\treturn theme.fg(state === \"pending\" ? \"muted\" : state, \"●\");\n}\n\n/** Derive the status dot from a render context (partial → pending, error → error, else success). */\nexport function toolStatusDotFromContext(context: { isPartial: boolean; isError: boolean }, theme: Theme): string {\n\treturn toolStatusDot(context.isPartial ? \"pending\" : context.isError ? \"error\" : \"success\", theme);\n}\n\nexport function renderToolPath(\n\trawPath: string | null,\n\ttheme: Theme,\n\tcwd: string,\n\toptions?: { emptyFallback?: string },\n): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath || options?.emptyFallback;\n\tif (!value) return theme.fg(\"toolOutput\", \"...\");\n\treturn linkPath(theme.fg(\"accent\", shortenPath(value)), value, cwd);\n}\n\n/** Basename-only path for compact tool headers. Still hyperlinked to the absolute file. */\nexport function renderToolFileName(rawPath: string | null, theme: Theme, cwd: string): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tif (!rawPath) return theme.fg(\"toolOutput\", \"...\");\n\tconst name = basename(rawPath) || rawPath;\n\treturn linkPath(theme.fg(\"accent\", name), rawPath, cwd);\n}\n\n/**\n * Collapsed grep/find path. Omits cwd (`.` / empty) so the header stays one line.\n * Other paths are basename-only and hyperlinked.\n */\nexport function renderCollapsedSearchPath(rawPath: string | null, theme: Theme, cwd: string): string | undefined {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath.trim();\n\tif (!value || value === \".\" || value === \"./\") return undefined;\n\treturn renderToolFileName(value, theme, cwd);\n}\n"]}
|
|
@@ -85,11 +85,19 @@ export function toolGroupRole(continuation, followed) {
|
|
|
85
85
|
* └─ usage-service.ts
|
|
86
86
|
* ```
|
|
87
87
|
*
|
|
88
|
-
* Singletons
|
|
88
|
+
* Singletons and expanded rows keep `● title detail` on one line.
|
|
89
|
+
* Collapsed errors stay in the same-name tree; the error body is hoisted
|
|
90
|
+
* under the last leaf so it does not split the file list. Grouped still-running
|
|
91
|
+
* cards share the same tree as finished ones. `compact` is header-only body
|
|
92
|
+
* hiding at the caller; pass `tree` for chrome. `isError` is accepted so
|
|
93
|
+
* callers can keep passing it, but it does not drop tree chrome.
|
|
89
94
|
*/
|
|
95
|
+
export function toolGroupTree(context) {
|
|
96
|
+
return !context.expanded;
|
|
97
|
+
}
|
|
90
98
|
export function formatGroupedCall(opts) {
|
|
91
99
|
const detail = opts.detail?.trim() ? opts.detail : "";
|
|
92
|
-
const useTree = opts.compact && opts.role !== "singleton" && detail.length > 0;
|
|
100
|
+
const useTree = (opts.tree ?? opts.compact === true) && opts.role !== "singleton" && detail.length > 0;
|
|
93
101
|
if (!useTree) {
|
|
94
102
|
return detail ? `${opts.dot} ${opts.title} ${detail}` : `${opts.dot} ${opts.title}`;
|
|
95
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render-utils.js","sourceRoot":"","sources":["../../../src/core/tools/render-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport { basename } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { ImageContent, TextContent } from \"@ashx-j/lunr-ai\";\nimport { getCapabilities, getImageDimensions, hyperlink, imageFallback } from \"@ashx-j/lunr-tui\";\nimport type { ToolGroupRole } from \"../extensions/types.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { stripAnsi } from \"../../utils/ansi.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\n\nexport function shortenPath(path: unknown): string {\n\tif (typeof path !== \"string\") return \"\";\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nexport function linkPath(styledText: string, rawPath: string, cwd: string): string {\n\tif (!getCapabilities().hyperlinks) return styledText;\n\tconst absolutePath = resolvePath(rawPath, cwd);\n\treturn hyperlink(styledText, pathToFileURL(absolutePath).href);\n}\n\nexport function str(value: unknown): string | null {\n\tif (typeof value === \"string\") return value;\n\tif (value == null) return \"\";\n\treturn null;\n}\n\nexport function replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport function normalizeDisplayText(text: string): string {\n\treturn text.replace(/\\r/g, \"\");\n}\n\nexport function getTextOutput(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> } | undefined,\n\tshowImages: boolean,\n): string {\n\tif (!result) return \"\";\n\n\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\tconst imageBlocks = result.content.filter((c) => c.type === \"image\");\n\n\tlet output = textBlocks.map((c) => sanitizeBinaryOutput(stripAnsi(c.text || \"\")).replace(/\\r/g, \"\")).join(\"\\n\");\n\n\tconst caps = getCapabilities();\n\tif (imageBlocks.length > 0 && (!caps.images || !showImages)) {\n\t\tconst imageIndicators = imageBlocks\n\t\t\t.map((img) => {\n\t\t\t\tconst mimeType = img.mimeType ?? \"image/unknown\";\n\t\t\t\tconst dims =\n\t\t\t\t\timg.data && img.mimeType ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\treturn imageFallback(mimeType, dims);\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t}\n\n\treturn output;\n}\n\nexport type ToolRenderResultLike<TDetails> = {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails: TDetails;\n};\n\nexport function invalidArgText(theme: Theme): string {\n\treturn theme.fg(\"error\", \"[invalid arg]\");\n}\n\n// lunr: compact-by-default — tool execute() paths append truncation/limit notices\n// as a trailing \"\\n\\n[...]\" block. Compact (header-only) results blank the body,\n// so this extracts that notice to keep it visible without the full output.\nexport function extractTrailingNotice(text: string): string | undefined {\n\tconst trimmed = text.trimEnd();\n\tif (!trimmed.endsWith(\"]\")) return undefined;\n\tconst start = trimmed.lastIndexOf(\"\\n\\n[\");\n\tif (start === -1) return undefined;\n\treturn trimmed.slice(start + 2);\n}\n\nexport type ToolStatusDotState = \"pending\" | \"success\" | \"error\";\n\nexport type { ToolGroupRole };\n\nexport function toolGroupRole(continuation: boolean, followed: boolean): ToolGroupRole {\n\tif (!continuation && followed) return \"first\";\n\tif (continuation && followed) return \"middle\";\n\tif (continuation && !followed) return \"last\";\n\treturn \"singleton\";\n}\n\n/**\n * Quieter same-name chrome: the verb is printed once, then files hang off a tree.\n *\n * ```\n * ● read\n * ├─ resolve.ts\n * └─ usage-service.ts\n * ```\n *\n * Singletons, streaming, errors, and expanded rows keep `● title detail` on one line.\n */\nexport function formatGroupedCall(opts: {\n\trole: ToolGroupRole;\n\tcompact: boolean;\n\tdot: string;\n\ttitle: string;\n\tdetail?: string;\n}): string {\n\tconst detail = opts.detail?.trim() ? opts.detail : \"\";\n\tconst useTree = opts.compact && opts.role !== \"singleton\" && detail.length > 0;\n\tif (!useTree) {\n\t\treturn detail ? `${opts.dot} ${opts.title} ${detail}` : `${opts.dot} ${opts.title}`;\n\t}\n\tconst branch = opts.role === \"last\" ? \"└─\" : \"├─\";\n\tif (opts.role === \"first\") {\n\t\treturn `${opts.dot} ${opts.title}\\n ${branch} ${detail}`;\n\t}\n\treturn ` ${branch} ${detail}`;\n}\n\n/**\n * Colored status dot used as the sole state indicator on tool titles (moon theme:\n * backgrounds stay neutral). Pending = muted, success = green, error = red.\n */\nexport function toolStatusDot(state: ToolStatusDotState, theme: Theme): string {\n\treturn theme.fg(state === \"pending\" ? \"muted\" : state, \"●\");\n}\n\n/** Derive the status dot from a render context (partial → pending, error → error, else success). */\nexport function toolStatusDotFromContext(context: { isPartial: boolean; isError: boolean }, theme: Theme): string {\n\treturn toolStatusDot(context.isPartial ? \"pending\" : context.isError ? \"error\" : \"success\", theme);\n}\n\nexport function renderToolPath(\n\trawPath: string | null,\n\ttheme: Theme,\n\tcwd: string,\n\toptions?: { emptyFallback?: string },\n): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath || options?.emptyFallback;\n\tif (!value) return theme.fg(\"toolOutput\", \"...\");\n\treturn linkPath(theme.fg(\"accent\", shortenPath(value)), value, cwd);\n}\n\n/** Basename-only path for compact tool headers. Still hyperlinked to the absolute file. */\nexport function renderToolFileName(rawPath: string | null, theme: Theme, cwd: string): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tif (!rawPath) return theme.fg(\"toolOutput\", \"...\");\n\tconst name = basename(rawPath) || rawPath;\n\treturn linkPath(theme.fg(\"accent\", name), rawPath, cwd);\n}\n\n/**\n * Collapsed grep/find path. Omits cwd (`.` / empty) so the header stays one line.\n * Other paths are basename-only and hyperlinked.\n */\nexport function renderCollapsedSearchPath(rawPath: string | null, theme: Theme, cwd: string): string | undefined {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath.trim();\n\tif (!value || value === \".\" || value === \"./\") return undefined;\n\treturn renderToolFileName(value, theme, cwd);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"render-utils.js","sourceRoot":"","sources":["../../../src/core/tools/render-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport { basename } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport type { ImageContent, TextContent } from \"@ashx-j/lunr-ai\";\nimport { getCapabilities, getImageDimensions, hyperlink, imageFallback } from \"@ashx-j/lunr-tui\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { stripAnsi } from \"../../utils/ansi.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\nimport type { ToolGroupRole } from \"../extensions/types.ts\";\n\nexport function shortenPath(path: unknown): string {\n\tif (typeof path !== \"string\") return \"\";\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn `~${path.slice(home.length)}`;\n\t}\n\treturn path;\n}\n\nexport function linkPath(styledText: string, rawPath: string, cwd: string): string {\n\tif (!getCapabilities().hyperlinks) return styledText;\n\tconst absolutePath = resolvePath(rawPath, cwd);\n\treturn hyperlink(styledText, pathToFileURL(absolutePath).href);\n}\n\nexport function str(value: unknown): string | null {\n\tif (typeof value === \"string\") return value;\n\tif (value == null) return \"\";\n\treturn null;\n}\n\nexport function replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport function normalizeDisplayText(text: string): string {\n\treturn text.replace(/\\r/g, \"\");\n}\n\nexport function getTextOutput(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }> } | undefined,\n\tshowImages: boolean,\n): string {\n\tif (!result) return \"\";\n\n\tconst textBlocks = result.content.filter((c) => c.type === \"text\");\n\tconst imageBlocks = result.content.filter((c) => c.type === \"image\");\n\n\tlet output = textBlocks.map((c) => sanitizeBinaryOutput(stripAnsi(c.text || \"\")).replace(/\\r/g, \"\")).join(\"\\n\");\n\n\tconst caps = getCapabilities();\n\tif (imageBlocks.length > 0 && (!caps.images || !showImages)) {\n\t\tconst imageIndicators = imageBlocks\n\t\t\t.map((img) => {\n\t\t\t\tconst mimeType = img.mimeType ?? \"image/unknown\";\n\t\t\t\tconst dims =\n\t\t\t\t\timg.data && img.mimeType ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\treturn imageFallback(mimeType, dims);\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t}\n\n\treturn output;\n}\n\nexport type ToolRenderResultLike<TDetails> = {\n\tcontent: (TextContent | ImageContent)[];\n\tdetails: TDetails;\n};\n\nexport function invalidArgText(theme: Theme): string {\n\treturn theme.fg(\"error\", \"[invalid arg]\");\n}\n\n// lunr: compact-by-default — tool execute() paths append truncation/limit notices\n// as a trailing \"\\n\\n[...]\" block. Compact (header-only) results blank the body,\n// so this extracts that notice to keep it visible without the full output.\nexport function extractTrailingNotice(text: string): string | undefined {\n\tconst trimmed = text.trimEnd();\n\tif (!trimmed.endsWith(\"]\")) return undefined;\n\tconst start = trimmed.lastIndexOf(\"\\n\\n[\");\n\tif (start === -1) return undefined;\n\treturn trimmed.slice(start + 2);\n}\n\nexport type ToolStatusDotState = \"pending\" | \"success\" | \"error\";\n\nexport type { ToolGroupRole };\n\nexport function toolGroupRole(continuation: boolean, followed: boolean): ToolGroupRole {\n\tif (!continuation && followed) return \"first\";\n\tif (continuation && followed) return \"middle\";\n\tif (continuation && !followed) return \"last\";\n\treturn \"singleton\";\n}\n\n/**\n * Quieter same-name chrome: the verb is printed once, then files hang off a tree.\n *\n * ```\n * ● read\n * ├─ resolve.ts\n * └─ usage-service.ts\n * ```\n *\n * Singletons and expanded rows keep `● title detail` on one line.\n * Collapsed errors stay in the same-name tree; the error body is hoisted\n * under the last leaf so it does not split the file list. Grouped still-running\n * cards share the same tree as finished ones. `compact` is header-only body\n * hiding at the caller; pass `tree` for chrome. `isError` is accepted so\n * callers can keep passing it, but it does not drop tree chrome.\n */\nexport function toolGroupTree(context: { expanded?: boolean; isError?: boolean }): boolean {\n\treturn !context.expanded;\n}\n\nexport function formatGroupedCall(opts: {\n\trole: ToolGroupRole;\n\tcompact?: boolean;\n\ttree?: boolean;\n\tdot: string;\n\ttitle: string;\n\tdetail?: string;\n}): string {\n\tconst detail = opts.detail?.trim() ? opts.detail : \"\";\n\tconst useTree = (opts.tree ?? opts.compact === true) && opts.role !== \"singleton\" && detail.length > 0;\n\tif (!useTree) {\n\t\treturn detail ? `${opts.dot} ${opts.title} ${detail}` : `${opts.dot} ${opts.title}`;\n\t}\n\tconst branch = opts.role === \"last\" ? \"└─\" : \"├─\";\n\tif (opts.role === \"first\") {\n\t\treturn `${opts.dot} ${opts.title}\\n ${branch} ${detail}`;\n\t}\n\treturn ` ${branch} ${detail}`;\n}\n\n/**\n * Colored status dot used as the sole state indicator on tool titles (moon theme:\n * backgrounds stay neutral). Pending = muted, success = green, error = red.\n */\nexport function toolStatusDot(state: ToolStatusDotState, theme: Theme): string {\n\treturn theme.fg(state === \"pending\" ? \"muted\" : state, \"●\");\n}\n\n/** Derive the status dot from a render context (partial → pending, error → error, else success). */\nexport function toolStatusDotFromContext(context: { isPartial: boolean; isError: boolean }, theme: Theme): string {\n\treturn toolStatusDot(context.isPartial ? \"pending\" : context.isError ? \"error\" : \"success\", theme);\n}\n\nexport function renderToolPath(\n\trawPath: string | null,\n\ttheme: Theme,\n\tcwd: string,\n\toptions?: { emptyFallback?: string },\n): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath || options?.emptyFallback;\n\tif (!value) return theme.fg(\"toolOutput\", \"...\");\n\treturn linkPath(theme.fg(\"accent\", shortenPath(value)), value, cwd);\n}\n\n/** Basename-only path for compact tool headers. Still hyperlinked to the absolute file. */\nexport function renderToolFileName(rawPath: string | null, theme: Theme, cwd: string): string {\n\tif (rawPath === null) return invalidArgText(theme);\n\tif (!rawPath) return theme.fg(\"toolOutput\", \"...\");\n\tconst name = basename(rawPath) || rawPath;\n\treturn linkPath(theme.fg(\"accent\", name), rawPath, cwd);\n}\n\n/**\n * Collapsed grep/find path. Omits cwd (`.` / empty) so the header stays one line.\n * Other paths are basename-only and hyperlinked.\n */\nexport function renderCollapsedSearchPath(rawPath: string | null, theme: Theme, cwd: string): string | undefined {\n\tif (rawPath === null) return invalidArgText(theme);\n\tconst value = rawPath.trim();\n\tif (!value || value === \".\" || value === \"./\") return undefined;\n\treturn renderToolFileName(value, theme, cwd);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/core/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAI/D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AActF,QAAA,MAAM,WAAW;;;EAGf,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAExD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,mCAAmC;IACnC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAOD,MAAM,WAAW,gBAAgB;IAChC,oEAAoE;IACpE,UAAU,CAAC,EAAE,eAAe,CAAC;CAC7B;AAgJD,wBAAgB,yBAAyB,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,gBAAgB,GACxB,cAAc,CAAC,OAAO,WAAW,EAAE,SAAS,CAAC,CAkG/C;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC,OAAO,WAAW,CAAC,CAEtG","sourcesContent":["import type { AgentTool } from \"@ashx-j/lunr-agent\";\nimport { Container, Text } from \"@ashx-j/lunr-tui\";\nimport { mkdir as fsMkdir, writeFile as fsWriteFile } from \"fs/promises\";\nimport { dirname } from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport {\n\tnormalizeDisplayText,\n\tformatGroupedCall,\n\trenderToolFileName,\n\trenderToolPath,\n\treplaceTabs,\n\tstr,\n\ttoolStatusDotFromContext,\n} from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport type WriteToolInput = Static<typeof writeSchema>;\n\n/**\n * Pluggable operations for the write tool.\n * Override these to delegate file writing to remote systems (for example SSH).\n */\nexport interface WriteOperations {\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Create directory recursively */\n\tmkdir: (dir: string) => Promise<void>;\n}\n\nconst defaultWriteOperations: WriteOperations = {\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\tmkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => {}),\n};\n\nexport interface WriteToolOptions {\n\t/** Custom operations for file writing. Default: local filesystem */\n\toperations?: WriteOperations;\n}\n\ntype WriteHighlightCache = {\n\trawPath: string | null;\n\tlang: string;\n\trawContent: string;\n\tnormalizedLines: string[];\n\thighlightedLines: string[];\n};\n\nclass WriteCallRenderComponent extends Text {\n\tcache?: WriteHighlightCache;\n\n\tconstructor() {\n\t\tsuper(\"\", 0, 0);\n\t}\n}\n\nconst WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50;\n\nfunction highlightSingleLine(line: string, lang: string): string {\n\tconst highlighted = highlightCode(line, lang);\n\treturn highlighted[0] ?? \"\";\n}\n\nfunction refreshWriteHighlightPrefix(cache: WriteHighlightCache): void {\n\tconst prefixCount = Math.min(WRITE_PARTIAL_FULL_HIGHLIGHT_LINES, cache.normalizedLines.length);\n\tif (prefixCount === 0) return;\n\tconst prefixSource = cache.normalizedLines.slice(0, prefixCount).join(\"\\n\");\n\tconst prefixHighlighted = highlightCode(prefixSource, cache.lang);\n\tfor (let i = 0; i < prefixCount; i++) {\n\t\tcache.highlightedLines[i] =\n\t\t\tprefixHighlighted[i] ?? highlightSingleLine(cache.normalizedLines[i] ?? \"\", cache.lang);\n\t}\n}\n\nfunction rebuildWriteHighlightCacheFull(rawPath: string | null, fileContent: string): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tconst displayContent = normalizeDisplayText(fileContent);\n\tconst normalized = replaceTabs(displayContent);\n\treturn {\n\t\trawPath,\n\t\tlang,\n\t\trawContent: fileContent,\n\t\tnormalizedLines: normalized.split(\"\\n\"),\n\t\thighlightedLines: highlightCode(normalized, lang),\n\t};\n}\n\nfunction updateWriteHighlightCacheIncremental(\n\tcache: WriteHighlightCache | undefined,\n\trawPath: string | null,\n\tfileContent: string,\n): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tif (!cache) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (cache.lang !== lang || cache.rawPath !== rawPath) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (!fileContent.startsWith(cache.rawContent)) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (fileContent.length === cache.rawContent.length) return cache;\n\n\tconst deltaRaw = fileContent.slice(cache.rawContent.length);\n\tconst deltaDisplay = normalizeDisplayText(deltaRaw);\n\tconst deltaNormalized = replaceTabs(deltaDisplay);\n\tcache.rawContent = fileContent;\n\tif (cache.normalizedLines.length === 0) {\n\t\tcache.normalizedLines.push(\"\");\n\t\tcache.highlightedLines.push(\"\");\n\t}\n\n\tconst segments = deltaNormalized.split(\"\\n\");\n\tconst lastIndex = cache.normalizedLines.length - 1;\n\tcache.normalizedLines[lastIndex] += segments[0];\n\tcache.highlightedLines[lastIndex] = highlightSingleLine(cache.normalizedLines[lastIndex], cache.lang);\n\tfor (let i = 1; i < segments.length; i++) {\n\t\tcache.normalizedLines.push(segments[i]);\n\t\tcache.highlightedLines.push(highlightSingleLine(segments[i], cache.lang));\n\t}\n\trefreshWriteHighlightPrefix(cache);\n\treturn cache;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction formatWriteCall(\n\targs: { path?: string; file_path?: string; content?: string } | undefined,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tcache: WriteHighlightCache | undefined,\n\tcwd: string,\n\t// lunr: compact-by-default — finished, successful, non-expanded calls render\n\t// header-only; the content preview stays while streaming and when expanded.\n\tcompact = false,\n): string {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst fileContent = str(args?.content);\n\tconst pathDisplay = compact ? renderToolFileName(rawPath, theme, cwd) : renderToolPath(rawPath, theme, cwd);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"write\"))} ${pathDisplay}`;\n\n\tif (fileContent === null) {\n\t\ttext += `\\n\\n${theme.fg(\"error\", \"[invalid content arg - expected string]\")}`;\n\t} else if (fileContent && !compact) {\n\t\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\t\tconst renderedLines = lang\n\t\t\t? (cache?.highlightedLines ?? highlightCode(replaceTabs(normalizeDisplayText(fileContent)), lang))\n\t\t\t: normalizeDisplayText(fileContent).split(\"\\n\");\n\t\tconst lines = trimTrailingEmptyLines(renderedLines);\n\t\tconst totalLines = lines.length;\n\t\tconst maxLines = options.expanded ? lines.length : 10;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n\\n${displayLines.map((line) => (lang ? line : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines, ${totalLines} total,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\t}\n\t}\n\n\treturn text;\n}\n\nfunction formatWriteResult(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError?: boolean },\n\ttheme: Theme,\n): string | undefined {\n\tif (!result.isError) {\n\t\treturn undefined;\n\t}\n\tconst output = result.content\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text || \"\")\n\t\t.join(\"\\n\");\n\tif (!output) {\n\t\treturn undefined;\n\t}\n\treturn `\\n${theme.fg(\"error\", output)}`;\n}\n\nexport function createWriteToolDefinition(\n\tcwd: string,\n\toptions?: WriteToolOptions,\n): ToolDefinition<typeof writeSchema, undefined> {\n\tconst ops = options?.operations ?? defaultWriteOperations;\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tpromptSnippet: \"Create or overwrite files\",\n\t\tpromptGuidelines: [\"Use write only for new files or complete rewrites.\"],\n\t\tparameters: writeSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, content }: { path: string; content: string },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\t\t\tconst dir = dirname(absolutePath);\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\t// Do not reject from an abort event listener here: that would release the\n\t\t\t\t// mutation queue while an in-flight filesystem operation may still finish.\n\t\t\t\t// Checking signal.aborted after each await observes the same aborts while\n\t\t\t\t// keeping the queue locked until the current operation has settled.\n\t\t\t\tconst throwIfAborted = (): void => {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t};\n\n\t\t\t\tthrowIfAborted();\n\t\t\t\t// Create parent directories if needed.\n\t\t\t\tawait ops.mkdir(dir);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Write the file contents.\n\t\t\t\tawait ops.writeFile(absolutePath, content);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst renderArgs = args as { path?: string; file_path?: string; content?: string } | undefined;\n\t\t\tconst rawPath = str(renderArgs?.file_path ?? renderArgs?.path);\n\t\t\tconst fileContent = str(renderArgs?.content);\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as WriteCallRenderComponent | undefined) ?? new WriteCallRenderComponent();\n\t\t\tif (fileContent !== null) {\n\t\t\t\tcomponent.cache = context.argsComplete\n\t\t\t\t\t? rebuildWriteHighlightCacheFull(rawPath, fileContent)\n\t\t\t\t\t: updateWriteHighlightCacheIncremental(component.cache, rawPath, fileContent);\n\t\t\t} else {\n\t\t\t\tcomponent.cache = undefined;\n\t\t\t}\n\t\t\tconst compact = !context.isPartial && !context.expanded && !context.isError;\n\t\t\tif (compact) {\n\t\t\t\tconst pathDisplay = renderToolFileName(rawPath, theme, context.cwd);\n\t\t\t\tlet detail = pathDisplay;\n\t\t\t\tif (fileContent === null) {\n\t\t\t\t\tdetail += `\\n\\n${theme.fg(\"error\", \"[invalid content arg - expected string]\")}`;\n\t\t\t\t}\n\t\t\t\tcomponent.setText(\n\t\t\t\t\tformatGroupedCall({\n\t\t\t\t\t\trole: context.groupRole ?? \"singleton\",\n\t\t\t\t\t\tcompact: true,\n\t\t\t\t\t\tdot: toolStatusDotFromContext(context, theme),\n\t\t\t\t\t\ttitle: theme.fg(\"toolTitle\", theme.bold(\"write\")),\n\t\t\t\t\t\tdetail,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tcomponent.setText(\n\t\t\t\t\t`${toolStatusDotFromContext(context, theme)} ${formatWriteCall(\n\t\t\t\t\t\trenderArgs,\n\t\t\t\t\t\t{ expanded: context.expanded, isPartial: context.isPartial },\n\t\t\t\t\t\ttheme,\n\t\t\t\t\t\tcomponent.cache,\n\t\t\t\t\t\tcontext.cwd,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn component;\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst output = formatWriteResult({ ...result, isError: context.isError }, theme);\n\t\t\tif (!output) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {\n\treturn wrapToolDefinition(createWriteToolDefinition(cwd, options));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../../src/core/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAI/D,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,wBAAwB,CAAC;AAetF,QAAA,MAAM,WAAW;;;EAGf,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,WAAW,CAAC,CAAC;AAExD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,8BAA8B;IAC9B,SAAS,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,mCAAmC;IACnC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAOD,MAAM,WAAW,gBAAgB;IAChC,oEAAoE;IACpE,UAAU,CAAC,EAAE,eAAe,CAAC;CAC7B;AAgJD,wBAAgB,yBAAyB,CACxC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,gBAAgB,GACxB,cAAc,CAAC,OAAO,WAAW,EAAE,SAAS,CAAC,CAoG/C;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC,OAAO,WAAW,CAAC,CAEtG","sourcesContent":["import type { AgentTool } from \"@ashx-j/lunr-agent\";\nimport { Container, Text } from \"@ashx-j/lunr-tui\";\nimport { mkdir as fsMkdir, writeFile as fsWriteFile } from \"fs/promises\";\nimport { dirname } from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getLanguageFromPath, highlightCode, type Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { withFileMutationQueue } from \"./file-mutation-queue.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport {\n\tformatGroupedCall,\n\tnormalizeDisplayText,\n\trenderToolFileName,\n\trenderToolPath,\n\treplaceTabs,\n\tstr,\n\ttoolGroupTree,\n\ttoolStatusDotFromContext,\n} from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\n\nconst writeSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport type WriteToolInput = Static<typeof writeSchema>;\n\n/**\n * Pluggable operations for the write tool.\n * Override these to delegate file writing to remote systems (for example SSH).\n */\nexport interface WriteOperations {\n\t/** Write content to a file */\n\twriteFile: (absolutePath: string, content: string) => Promise<void>;\n\t/** Create directory recursively */\n\tmkdir: (dir: string) => Promise<void>;\n}\n\nconst defaultWriteOperations: WriteOperations = {\n\twriteFile: (path, content) => fsWriteFile(path, content, \"utf-8\"),\n\tmkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => {}),\n};\n\nexport interface WriteToolOptions {\n\t/** Custom operations for file writing. Default: local filesystem */\n\toperations?: WriteOperations;\n}\n\ntype WriteHighlightCache = {\n\trawPath: string | null;\n\tlang: string;\n\trawContent: string;\n\tnormalizedLines: string[];\n\thighlightedLines: string[];\n};\n\nclass WriteCallRenderComponent extends Text {\n\tcache?: WriteHighlightCache;\n\n\tconstructor() {\n\t\tsuper(\"\", 0, 0);\n\t}\n}\n\nconst WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50;\n\nfunction highlightSingleLine(line: string, lang: string): string {\n\tconst highlighted = highlightCode(line, lang);\n\treturn highlighted[0] ?? \"\";\n}\n\nfunction refreshWriteHighlightPrefix(cache: WriteHighlightCache): void {\n\tconst prefixCount = Math.min(WRITE_PARTIAL_FULL_HIGHLIGHT_LINES, cache.normalizedLines.length);\n\tif (prefixCount === 0) return;\n\tconst prefixSource = cache.normalizedLines.slice(0, prefixCount).join(\"\\n\");\n\tconst prefixHighlighted = highlightCode(prefixSource, cache.lang);\n\tfor (let i = 0; i < prefixCount; i++) {\n\t\tcache.highlightedLines[i] =\n\t\t\tprefixHighlighted[i] ?? highlightSingleLine(cache.normalizedLines[i] ?? \"\", cache.lang);\n\t}\n}\n\nfunction rebuildWriteHighlightCacheFull(rawPath: string | null, fileContent: string): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tconst displayContent = normalizeDisplayText(fileContent);\n\tconst normalized = replaceTabs(displayContent);\n\treturn {\n\t\trawPath,\n\t\tlang,\n\t\trawContent: fileContent,\n\t\tnormalizedLines: normalized.split(\"\\n\"),\n\t\thighlightedLines: highlightCode(normalized, lang),\n\t};\n}\n\nfunction updateWriteHighlightCacheIncremental(\n\tcache: WriteHighlightCache | undefined,\n\trawPath: string | null,\n\tfileContent: string,\n): WriteHighlightCache | undefined {\n\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\tif (!lang) return undefined;\n\tif (!cache) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (cache.lang !== lang || cache.rawPath !== rawPath) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (!fileContent.startsWith(cache.rawContent)) return rebuildWriteHighlightCacheFull(rawPath, fileContent);\n\tif (fileContent.length === cache.rawContent.length) return cache;\n\n\tconst deltaRaw = fileContent.slice(cache.rawContent.length);\n\tconst deltaDisplay = normalizeDisplayText(deltaRaw);\n\tconst deltaNormalized = replaceTabs(deltaDisplay);\n\tcache.rawContent = fileContent;\n\tif (cache.normalizedLines.length === 0) {\n\t\tcache.normalizedLines.push(\"\");\n\t\tcache.highlightedLines.push(\"\");\n\t}\n\n\tconst segments = deltaNormalized.split(\"\\n\");\n\tconst lastIndex = cache.normalizedLines.length - 1;\n\tcache.normalizedLines[lastIndex] += segments[0];\n\tcache.highlightedLines[lastIndex] = highlightSingleLine(cache.normalizedLines[lastIndex], cache.lang);\n\tfor (let i = 1; i < segments.length; i++) {\n\t\tcache.normalizedLines.push(segments[i]);\n\t\tcache.highlightedLines.push(highlightSingleLine(segments[i], cache.lang));\n\t}\n\trefreshWriteHighlightPrefix(cache);\n\treturn cache;\n}\n\nfunction trimTrailingEmptyLines(lines: string[]): string[] {\n\tlet end = lines.length;\n\twhile (end > 0 && lines[end - 1] === \"\") {\n\t\tend--;\n\t}\n\treturn lines.slice(0, end);\n}\n\nfunction formatWriteCall(\n\targs: { path?: string; file_path?: string; content?: string } | undefined,\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tcache: WriteHighlightCache | undefined,\n\tcwd: string,\n\t// lunr: compact-by-default — finished, successful, non-expanded calls render\n\t// header-only; the content preview stays while streaming and when expanded.\n\tcompact = false,\n): string {\n\tconst rawPath = str(args?.file_path ?? args?.path);\n\tconst fileContent = str(args?.content);\n\tconst pathDisplay = compact ? renderToolFileName(rawPath, theme, cwd) : renderToolPath(rawPath, theme, cwd);\n\tlet text = `${theme.fg(\"toolTitle\", theme.bold(\"write\"))} ${pathDisplay}`;\n\n\tif (fileContent === null) {\n\t\ttext += `\\n\\n${theme.fg(\"error\", \"[invalid content arg - expected string]\")}`;\n\t} else if (fileContent && !compact) {\n\t\tconst lang = rawPath ? getLanguageFromPath(rawPath) : undefined;\n\t\tconst renderedLines = lang\n\t\t\t? (cache?.highlightedLines ?? highlightCode(replaceTabs(normalizeDisplayText(fileContent)), lang))\n\t\t\t: normalizeDisplayText(fileContent).split(\"\\n\");\n\t\tconst lines = trimTrailingEmptyLines(renderedLines);\n\t\tconst totalLines = lines.length;\n\t\tconst maxLines = options.expanded ? lines.length : 10;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n\\n${displayLines.map((line) => (lang ? line : theme.fg(\"toolOutput\", replaceTabs(line)))).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines, ${totalLines} total,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\t}\n\t}\n\n\treturn text;\n}\n\nfunction formatWriteResult(\n\tresult: { content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>; isError?: boolean },\n\ttheme: Theme,\n): string | undefined {\n\tif (!result.isError) {\n\t\treturn undefined;\n\t}\n\tconst output = result.content\n\t\t.filter((c) => c.type === \"text\")\n\t\t.map((c) => c.text || \"\")\n\t\t.join(\"\\n\");\n\tif (!output) {\n\t\treturn undefined;\n\t}\n\treturn `\\n${theme.fg(\"error\", output)}`;\n}\n\nexport function createWriteToolDefinition(\n\tcwd: string,\n\toptions?: WriteToolOptions,\n): ToolDefinition<typeof writeSchema, undefined> {\n\tconst ops = options?.operations ?? defaultWriteOperations;\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tpromptSnippet: \"Create or overwrite files\",\n\t\tpromptGuidelines: [\"Use write only for new files or complete rewrites.\"],\n\t\tparameters: writeSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{ path, content }: { path: string; content: string },\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\tconst absolutePath = resolveToCwd(path, cwd);\n\t\t\tconst dir = dirname(absolutePath);\n\t\t\treturn withFileMutationQueue(absolutePath, async () => {\n\t\t\t\t// Do not reject from an abort event listener here: that would release the\n\t\t\t\t// mutation queue while an in-flight filesystem operation may still finish.\n\t\t\t\t// Checking signal.aborted after each await observes the same aborts while\n\t\t\t\t// keeping the queue locked until the current operation has settled.\n\t\t\t\tconst throwIfAborted = (): void => {\n\t\t\t\t\tif (signal?.aborted) throw new Error(\"Operation aborted\");\n\t\t\t\t};\n\n\t\t\t\tthrowIfAborted();\n\t\t\t\t// Create parent directories if needed.\n\t\t\t\tawait ops.mkdir(dir);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\t// Write the file contents.\n\t\t\t\tawait ops.writeFile(absolutePath, content);\n\t\t\t\tthrowIfAborted();\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst renderArgs = args as { path?: string; file_path?: string; content?: string } | undefined;\n\t\t\tconst rawPath = str(renderArgs?.file_path ?? renderArgs?.path);\n\t\t\tconst fileContent = str(renderArgs?.content);\n\t\t\tconst component =\n\t\t\t\t(context.lastComponent as WriteCallRenderComponent | undefined) ?? new WriteCallRenderComponent();\n\t\t\tif (fileContent !== null) {\n\t\t\t\tcomponent.cache = context.argsComplete\n\t\t\t\t\t? rebuildWriteHighlightCacheFull(rawPath, fileContent)\n\t\t\t\t\t: updateWriteHighlightCacheIncremental(component.cache, rawPath, fileContent);\n\t\t\t} else {\n\t\t\t\tcomponent.cache = undefined;\n\t\t\t}\n\t\t\tconst compact = !context.isPartial && !context.expanded && !context.isError;\n\t\t\tconst tree = toolGroupTree(context);\n\t\t\tif (compact || (tree && context.groupRole && context.groupRole !== \"singleton\")) {\n\t\t\t\tconst pathDisplay = renderToolFileName(rawPath, theme, context.cwd);\n\t\t\t\tlet detail = pathDisplay;\n\t\t\t\tif (fileContent === null) {\n\t\t\t\t\tdetail += `\\n\\n${theme.fg(\"error\", \"[invalid content arg - expected string]\")}`;\n\t\t\t\t}\n\t\t\t\tcomponent.setText(\n\t\t\t\t\tformatGroupedCall({\n\t\t\t\t\t\trole: context.groupRole ?? \"singleton\",\n\t\t\t\t\t\tcompact,\n\t\t\t\t\t\ttree,\n\t\t\t\t\t\tdot: toolStatusDotFromContext(context, theme),\n\t\t\t\t\t\ttitle: theme.fg(\"toolTitle\", theme.bold(\"write\")),\n\t\t\t\t\t\tdetail,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tcomponent.setText(\n\t\t\t\t\t`${toolStatusDotFromContext(context, theme)} ${formatWriteCall(\n\t\t\t\t\t\trenderArgs,\n\t\t\t\t\t\t{ expanded: context.expanded, isPartial: context.isPartial },\n\t\t\t\t\t\ttheme,\n\t\t\t\t\t\tcomponent.cache,\n\t\t\t\t\t\tcontext.cwd,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn component;\n\t\t},\n\t\trenderResult(result, _options, theme, context) {\n\t\t\tconst output = formatWriteResult({ ...result, isError: context.isError }, theme);\n\t\t\tif (!output) {\n\t\t\t\tconst component = (context.lastComponent as Container | undefined) ?? new Container();\n\t\t\t\tcomponent.clear();\n\t\t\t\treturn component;\n\t\t\t}\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(output);\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createWriteTool(cwd: string, options?: WriteToolOptions): AgentTool<typeof writeSchema> {\n\treturn wrapToolDefinition(createWriteToolDefinition(cwd, options));\n}\n"]}
|
package/dist/core/tools/write.js
CHANGED
|
@@ -6,7 +6,7 @@ import { keyHint } from "../../modes/interactive/components/keybinding-hints.js"
|
|
|
6
6
|
import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
|
|
7
7
|
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
8
8
|
import { resolveToCwd } from "./path-utils.js";
|
|
9
|
-
import {
|
|
9
|
+
import { formatGroupedCall, normalizeDisplayText, renderToolFileName, renderToolPath, replaceTabs, str, toolGroupTree, toolStatusDotFromContext, } from "./render-utils.js";
|
|
10
10
|
import { wrapToolDefinition } from "./tool-definition-wrapper.js";
|
|
11
11
|
const writeSchema = Type.Object({
|
|
12
12
|
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
@@ -179,7 +179,8 @@ export function createWriteToolDefinition(cwd, options) {
|
|
|
179
179
|
component.cache = undefined;
|
|
180
180
|
}
|
|
181
181
|
const compact = !context.isPartial && !context.expanded && !context.isError;
|
|
182
|
-
|
|
182
|
+
const tree = toolGroupTree(context);
|
|
183
|
+
if (compact || (tree && context.groupRole && context.groupRole !== "singleton")) {
|
|
183
184
|
const pathDisplay = renderToolFileName(rawPath, theme, context.cwd);
|
|
184
185
|
let detail = pathDisplay;
|
|
185
186
|
if (fileContent === null) {
|
|
@@ -187,7 +188,8 @@ export function createWriteToolDefinition(cwd, options) {
|
|
|
187
188
|
}
|
|
188
189
|
component.setText(formatGroupedCall({
|
|
189
190
|
role: context.groupRole ?? "singleton",
|
|
190
|
-
compact
|
|
191
|
+
compact,
|
|
192
|
+
tree,
|
|
191
193
|
dot: toolStatusDotFromContext(context, theme),
|
|
192
194
|
title: theme.fg("toolTitle", theme.bold("write")),
|
|
193
195
|
detail,
|