@oh-my-pi/pi-coding-agent 1.337.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- package/src/utils/tools-manager.ts +274 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { accessSync, constants } from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import { isAbsolute, resolve as resolvePath } from "node:path";
|
|
4
|
+
|
|
5
|
+
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
6
|
+
const NARROW_NO_BREAK_SPACE = "\u202F";
|
|
7
|
+
|
|
8
|
+
function normalizeUnicodeSpaces(str: string): string {
|
|
9
|
+
return str.replace(UNICODE_SPACES, " ");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function tryMacOSScreenshotPath(filePath: string): string {
|
|
13
|
+
return filePath.replace(/ (AM|PM)\./g, `${NARROW_NO_BREAK_SPACE}$1.`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fileExists(filePath: string): boolean {
|
|
17
|
+
try {
|
|
18
|
+
accessSync(filePath, constants.F_OK);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function expandPath(filePath: string): string {
|
|
26
|
+
const normalized = normalizeUnicodeSpaces(filePath);
|
|
27
|
+
if (normalized === "~") {
|
|
28
|
+
return os.homedir();
|
|
29
|
+
}
|
|
30
|
+
if (normalized.startsWith("~/")) {
|
|
31
|
+
return os.homedir() + normalized.slice(1);
|
|
32
|
+
}
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a path relative to the given cwd.
|
|
38
|
+
* Handles ~ expansion and absolute paths.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveToCwd(filePath: string, cwd: string): string {
|
|
41
|
+
const expanded = expandPath(filePath);
|
|
42
|
+
if (isAbsolute(expanded)) {
|
|
43
|
+
return expanded;
|
|
44
|
+
}
|
|
45
|
+
return resolvePath(cwd, expanded);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveReadPath(filePath: string, cwd: string): string {
|
|
49
|
+
const resolved = resolveToCwd(filePath, cwd);
|
|
50
|
+
|
|
51
|
+
if (fileExists(resolved)) {
|
|
52
|
+
return resolved;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const macOSVariant = tryMacOSScreenshotPath(resolved);
|
|
56
|
+
if (macOSVariant !== resolved && fileExists(macOSVariant)) {
|
|
57
|
+
return macOSVariant;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { Type } from "@sinclair/typebox";
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
import { constants } from "fs";
|
|
6
|
+
import { access, readFile } from "fs/promises";
|
|
7
|
+
import { extname } from "path";
|
|
8
|
+
import { detectSupportedImageMimeTypeFromFile } from "../../utils/mime.js";
|
|
9
|
+
import { resolveReadPath } from "./path-utils.js";
|
|
10
|
+
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
|
|
11
|
+
|
|
12
|
+
// Document types convertible via markitdown
|
|
13
|
+
const CONVERTIBLE_EXTENSIONS = new Set([".pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".rtf", ".epub"]);
|
|
14
|
+
|
|
15
|
+
function convertWithMarkitdown(filePath: string): { content: string; ok: boolean; error?: string } {
|
|
16
|
+
const cmd = Bun.which("markitdown");
|
|
17
|
+
if (!cmd) {
|
|
18
|
+
return { content: "", ok: false, error: "markitdown not found" };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = spawnSync(cmd, [filePath], {
|
|
22
|
+
encoding: "utf-8",
|
|
23
|
+
timeout: 60000,
|
|
24
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (result.status === 0 && result.stdout && result.stdout.length > 0) {
|
|
28
|
+
return { content: result.stdout, ok: true };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { content: "", ok: false, error: result.stderr || "Conversion failed" };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const readSchema = Type.Object({
|
|
35
|
+
path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
|
|
36
|
+
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
37
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export interface ReadToolDetails {
|
|
41
|
+
truncation?: TruncationResult;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createReadTool(cwd: string): AgentTool<typeof readSchema> {
|
|
45
|
+
return {
|
|
46
|
+
name: "read",
|
|
47
|
+
label: "Read",
|
|
48
|
+
description: `Reads a file from the local filesystem. You can access any file directly by using this tool.
|
|
49
|
+
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
- The file_path parameter must be an absolute path, not a relative path
|
|
53
|
+
- By default, it reads up to ${DEFAULT_MAX_LINES} lines starting from the beginning of the file
|
|
54
|
+
- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
|
|
55
|
+
- Any lines longer than 500 characters will be truncated
|
|
56
|
+
- Results are returned using cat -n format, with line numbers starting at 1
|
|
57
|
+
- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.
|
|
58
|
+
- This tool can read PDF files (.pdf). PDFs are processed page by page, extracting both text and visual content for analysis.
|
|
59
|
+
- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.
|
|
60
|
+
- This tool can only read files, not directories. To read a directory, use an ls command via the bash tool.
|
|
61
|
+
- You can call multiple tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
|
|
62
|
+
- You will regularly be asked to read screenshots. If the user provides a path to a screenshot, ALWAYS use this tool to view the file at the path. This tool will work with all temporary file paths.
|
|
63
|
+
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.`,
|
|
64
|
+
parameters: readSchema,
|
|
65
|
+
execute: async (
|
|
66
|
+
_toolCallId: string,
|
|
67
|
+
{ path, offset, limit }: { path: string; offset?: number; limit?: number },
|
|
68
|
+
signal?: AbortSignal,
|
|
69
|
+
) => {
|
|
70
|
+
const absolutePath = resolveReadPath(path, cwd);
|
|
71
|
+
|
|
72
|
+
return new Promise<{ content: (TextContent | ImageContent)[]; details: ReadToolDetails | undefined }>(
|
|
73
|
+
(resolve, reject) => {
|
|
74
|
+
// Check if already aborted
|
|
75
|
+
if (signal?.aborted) {
|
|
76
|
+
reject(new Error("Operation aborted"));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let aborted = false;
|
|
81
|
+
|
|
82
|
+
// Set up abort handler
|
|
83
|
+
const onAbort = () => {
|
|
84
|
+
aborted = true;
|
|
85
|
+
reject(new Error("Operation aborted"));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (signal) {
|
|
89
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Perform the read operation
|
|
93
|
+
(async () => {
|
|
94
|
+
try {
|
|
95
|
+
// Check if file exists
|
|
96
|
+
await access(absolutePath, constants.R_OK);
|
|
97
|
+
|
|
98
|
+
// Check if aborted before reading
|
|
99
|
+
if (aborted) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);
|
|
104
|
+
const ext = extname(absolutePath).toLowerCase();
|
|
105
|
+
|
|
106
|
+
// Read the file based on type
|
|
107
|
+
let content: (TextContent | ImageContent)[];
|
|
108
|
+
let details: ReadToolDetails | undefined;
|
|
109
|
+
|
|
110
|
+
if (mimeType) {
|
|
111
|
+
// Read as image (binary)
|
|
112
|
+
const buffer = await readFile(absolutePath);
|
|
113
|
+
const base64 = buffer.toString("base64");
|
|
114
|
+
|
|
115
|
+
content = [
|
|
116
|
+
{ type: "text", text: `Read image file [${mimeType}]` },
|
|
117
|
+
{ type: "image", data: base64, mimeType },
|
|
118
|
+
];
|
|
119
|
+
} else if (CONVERTIBLE_EXTENSIONS.has(ext)) {
|
|
120
|
+
// Convert document via markitdown
|
|
121
|
+
const result = convertWithMarkitdown(absolutePath);
|
|
122
|
+
if (result.ok) {
|
|
123
|
+
// Apply truncation to converted content
|
|
124
|
+
const truncation = truncateHead(result.content);
|
|
125
|
+
let outputText = truncation.content;
|
|
126
|
+
|
|
127
|
+
if (truncation.truncated) {
|
|
128
|
+
outputText += `\n\n[Document converted via markitdown. Output truncated to $formatSize(
|
|
129
|
+
DEFAULT_MAX_BYTES,
|
|
130
|
+
)]`;
|
|
131
|
+
details = { truncation };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
content = [{ type: "text", text: outputText }];
|
|
135
|
+
} else {
|
|
136
|
+
// markitdown not available or failed
|
|
137
|
+
const errorMsg =
|
|
138
|
+
result.error === "markitdown not found"
|
|
139
|
+
? `markitdown not installed. Install with: pip install markitdown`
|
|
140
|
+
: result.error || "conversion failed";
|
|
141
|
+
content = [{ type: "text", text: `[Cannot read ${ext} file: ${errorMsg}]` }];
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// Read as text
|
|
145
|
+
const textContent = await readFile(absolutePath, "utf-8");
|
|
146
|
+
const allLines = textContent.split("\n");
|
|
147
|
+
const totalFileLines = allLines.length;
|
|
148
|
+
|
|
149
|
+
// Apply offset if specified (1-indexed to 0-indexed)
|
|
150
|
+
const startLine = offset ? Math.max(0, offset - 1) : 0;
|
|
151
|
+
const startLineDisplay = startLine + 1; // For display (1-indexed)
|
|
152
|
+
|
|
153
|
+
// Check if offset is out of bounds
|
|
154
|
+
if (startLine >= allLines.length) {
|
|
155
|
+
throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// If limit is specified by user, use it; otherwise we'll let truncateHead decide
|
|
159
|
+
let selectedContent: string;
|
|
160
|
+
let userLimitedLines: number | undefined;
|
|
161
|
+
if (limit !== undefined) {
|
|
162
|
+
const endLine = Math.min(startLine + limit, allLines.length);
|
|
163
|
+
selectedContent = allLines.slice(startLine, endLine).join("\n");
|
|
164
|
+
userLimitedLines = endLine - startLine;
|
|
165
|
+
} else {
|
|
166
|
+
selectedContent = allLines.slice(startLine).join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Apply truncation (respects both line and byte limits)
|
|
170
|
+
const truncation = truncateHead(selectedContent);
|
|
171
|
+
|
|
172
|
+
let outputText: string;
|
|
173
|
+
|
|
174
|
+
if (truncation.firstLineExceedsLimit) {
|
|
175
|
+
// First line at offset exceeds 30KB - tell model to use bash
|
|
176
|
+
const firstLineSize = formatSize(Buffer.byteLength(allLines[startLine], "utf-8"));
|
|
177
|
+
outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(
|
|
178
|
+
DEFAULT_MAX_BYTES,
|
|
179
|
+
)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;
|
|
180
|
+
details = { truncation };
|
|
181
|
+
} else if (truncation.truncated) {
|
|
182
|
+
// Truncation occurred - build actionable notice
|
|
183
|
+
const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
|
|
184
|
+
const nextOffset = endLineDisplay + 1;
|
|
185
|
+
|
|
186
|
+
outputText = truncation.content;
|
|
187
|
+
|
|
188
|
+
if (truncation.truncatedBy === "lines") {
|
|
189
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue]`;
|
|
190
|
+
} else {
|
|
191
|
+
outputText += `\n\n[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(
|
|
192
|
+
DEFAULT_MAX_BYTES,
|
|
193
|
+
)} limit). Use offset=${nextOffset} to continue]`;
|
|
194
|
+
}
|
|
195
|
+
details = { truncation };
|
|
196
|
+
} else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
|
|
197
|
+
// User specified limit, there's more content, but no truncation
|
|
198
|
+
const remaining = allLines.length - (startLine + userLimitedLines);
|
|
199
|
+
const nextOffset = startLine + userLimitedLines + 1;
|
|
200
|
+
|
|
201
|
+
outputText = truncation.content;
|
|
202
|
+
outputText += `\n\n[${remaining} more lines in file. Use offset=${nextOffset} to continue]`;
|
|
203
|
+
} else {
|
|
204
|
+
// No truncation, no user limit exceeded
|
|
205
|
+
outputText = truncation.content;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
content = [{ type: "text", text: outputText }];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check if aborted after reading
|
|
212
|
+
if (aborted) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Clean up abort handler
|
|
217
|
+
if (signal) {
|
|
218
|
+
signal.removeEventListener("abort", onAbort);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
resolve({ content, details });
|
|
222
|
+
} catch (error: any) {
|
|
223
|
+
// Clean up abort handler
|
|
224
|
+
if (signal) {
|
|
225
|
+
signal.removeEventListener("abort", onAbort);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!aborted) {
|
|
229
|
+
reject(error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
})();
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Default read tool using process.cwd() - for backwards compatibility */
|
|
240
|
+
export const readTool = createReadTool(process.cwd());
|