@oh-my-pi/pi-coding-agent 13.17.6 → 13.18.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 +22 -0
- package/package.json +7 -7
- package/src/cli/args.ts +0 -1
- package/src/config/settings-schema.ts +1 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +0 -1
- package/src/prompts/agents/designer.md +1 -1
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +1 -1
- package/src/prompts/agents/oracle.md +1 -1
- package/src/prompts/agents/plan.md +1 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/tools/read.md +27 -18
- package/src/session/artifacts.ts +2 -2
- package/src/task/index.ts +1 -1
- package/src/tools/fetch.ts +173 -98
- package/src/tools/index.ts +0 -4
- package/src/tools/path-utils.ts +12 -1
- package/src/tools/read.ts +61 -6
- package/src/tools/renderers.ts +0 -2
- package/src/prompts/tools/fetch.md +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.18.0] - 2026-04-02
|
|
6
|
+
### Breaking Changes
|
|
7
|
+
|
|
8
|
+
- Removed standalone `fetch` tool; URL fetching is now integrated into the `read` tool
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added URL reading capability to `read` tool with support for web pages, GitHub issues, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, technical blogs, RSS/Atom feeds, and JSON endpoints
|
|
13
|
+
- Added `offset` and `limit` parameter support for paginating cached URL fetch results
|
|
14
|
+
- Added URL caching mechanism to avoid redundant network requests when reading the same URL multiple times
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Renamed `fetch.enabled` setting to `Read URLs` with updated description to reflect integration with read tool
|
|
19
|
+
- Updated `read` tool to accept `timeout` and `raw` parameters for URL handling
|
|
20
|
+
- Updated `read` tool to support `file://` URLs for local file paths
|
|
21
|
+
- Removed `fetch` tool from agent tool lists (explore, librarian, oracle, plan, reviewer agents)
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Fixed `read` tool to properly handle `file://` URL scheme by converting to filesystem paths
|
|
26
|
+
|
|
5
27
|
## [13.17.5] - 2026-04-01
|
|
6
28
|
### Added
|
|
7
29
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.
|
|
4
|
+
"version": "13.18.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
44
44
|
"@mozilla/readability": "^0.6",
|
|
45
|
-
"@oh-my-pi/omp-stats": "13.
|
|
46
|
-
"@oh-my-pi/pi-agent-core": "13.
|
|
47
|
-
"@oh-my-pi/pi-ai": "13.
|
|
48
|
-
"@oh-my-pi/pi-natives": "13.
|
|
49
|
-
"@oh-my-pi/pi-tui": "13.
|
|
50
|
-
"@oh-my-pi/pi-utils": "13.
|
|
45
|
+
"@oh-my-pi/omp-stats": "13.18.0",
|
|
46
|
+
"@oh-my-pi/pi-agent-core": "13.18.0",
|
|
47
|
+
"@oh-my-pi/pi-ai": "13.18.0",
|
|
48
|
+
"@oh-my-pi/pi-natives": "13.18.0",
|
|
49
|
+
"@oh-my-pi/pi-tui": "13.18.0",
|
|
50
|
+
"@oh-my-pi/pi-utils": "13.18.0",
|
|
51
51
|
"@sinclair/typebox": "^0.34",
|
|
52
52
|
"@xterm/headless": "^6.0",
|
|
53
53
|
"ajv": "^8.18",
|
package/src/cli/args.ts
CHANGED
|
@@ -262,7 +262,6 @@ ${chalk.bold("Available Tools (default-enabled unless noted):")}
|
|
|
262
262
|
browser - Browser automation (Puppeteer)
|
|
263
263
|
task - Launch sub-agents for parallel tasks
|
|
264
264
|
todo_write - Manage todo/task lists
|
|
265
|
-
fetch - Fetch and process URLs
|
|
266
265
|
web_search - Search the web
|
|
267
266
|
ask - Ask user questions (interactive mode only)
|
|
268
267
|
|
|
@@ -1207,7 +1207,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1207
1207
|
"fetch.enabled": {
|
|
1208
1208
|
type: "boolean",
|
|
1209
1209
|
default: true,
|
|
1210
|
-
ui: { tab: "tools", label: "
|
|
1210
|
+
ui: { tab: "tools", label: "Read URLs", description: "Allow the read tool to fetch and process URLs" },
|
|
1211
1211
|
},
|
|
1212
1212
|
|
|
1213
1213
|
"github.enabled": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the internal URL routing system.
|
|
3
3
|
*
|
|
4
|
-
* Internal URLs (agent://, artifact://, memory://, skill://, rule://, mcp://, pi://, local://) are resolved by tools like
|
|
4
|
+
* Internal URLs (agent://, artifact://, memory://, skill://, rule://, mcp://, pi://, local://) are resolved by tools like read,
|
|
5
5
|
* providing access to agent outputs and server resources without exposing filesystem paths.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: designer
|
|
3
3
|
description: UI/UX specialist for design implementation, review, visual refinement
|
|
4
4
|
spawns: explore
|
|
5
|
-
model: google-gemini-cli/gemini-3-pro, gemini-3-pro, gemini-3, pi/default
|
|
5
|
+
model: google-gemini-cli/gemini-3.1-pro, google-gemini-cli/gemini-3-pro, gemini-3.1-pro, gemini-3-1-pro, gemini-3-pro, gemini-3, pi/default
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are an expert UI/UX designer implementing and reviewing UI designs.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: librarian
|
|
3
3
|
description: Researches external libraries and APIs by reading source code. Returns definitive, source-verified answers.
|
|
4
|
-
tools: read, grep, find, bash, lsp, web_search,
|
|
4
|
+
tools: read, grep, find, bash, lsp, web_search, ast_grep
|
|
5
5
|
model: pi/smol
|
|
6
6
|
thinking-level: minimal
|
|
7
7
|
output:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oracle
|
|
3
3
|
description: Deep reasoning advisor for debugging dead ends, architecture decisions, and second opinions. Read-only.
|
|
4
|
-
tools: read, grep, find, bash, lsp,
|
|
4
|
+
tools: read, grep, find, bash, lsp, web_search, ast_grep
|
|
5
5
|
spawns: explore
|
|
6
6
|
model: pi/slow
|
|
7
7
|
thinking-level: high
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plan
|
|
3
3
|
description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, or tasks completable in <5 tool calls.
|
|
4
|
-
tools: read, grep, find, bash, lsp,
|
|
4
|
+
tools: read, grep, find, bash, lsp, web_search, ast_grep
|
|
5
5
|
spawns: explore
|
|
6
6
|
model: pi/plan, pi/slow
|
|
7
7
|
thinking-level: high
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: reviewer
|
|
3
3
|
description: "Code review specialist for quality/security analysis"
|
|
4
|
-
tools: read, grep, find, bash, lsp,
|
|
4
|
+
tools: read, grep, find, bash, lsp, web_search, ast_grep, report_finding
|
|
5
5
|
spawns: explore
|
|
6
6
|
model: pi/slow
|
|
7
7
|
thinking-level: high
|
|
@@ -1,31 +1,40 @@
|
|
|
1
|
-
Reads
|
|
1
|
+
Reads the content at the specified path or URL.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
-
|
|
4
|
+
The `read` tool is a multi-purpose tool that can be used to inspect all kinds of files and URLs.
|
|
5
|
+
- You **MUST** parallelize reads when exploring related files
|
|
6
|
+
|
|
7
|
+
# Filesystem
|
|
8
|
+
- Reads up to {{DEFAULT_LIMIT}} lines by default
|
|
5
9
|
- Use `offset` and `limit` for large files; max {{DEFAULT_MAX_LINES}} lines per call
|
|
6
10
|
{{#if IS_HASHLINE_MODE}}
|
|
7
|
-
-
|
|
11
|
+
- If reading from FS, result will be prefixed with anchors: `41#ZZ:def alpha():`
|
|
8
12
|
{{else}}
|
|
9
|
-
{{#if IS_LINE_NUMBER_MODE}}
|
|
10
|
-
-
|
|
11
|
-
{{/if}}
|
|
13
|
+
{{#if IS_LINE_NUMBER_MODE}}
|
|
14
|
+
- If reading from FS, result will be prefixed with line numbers: `41:def alpha():`
|
|
15
|
+
{{/if}}
|
|
12
16
|
{{/if}}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
# Inspection
|
|
19
|
+
When used with a PDF, Word, PowerPoint, Excel, RTF, EPUB, or Jupyter notebook file, the tool will return the extracted text.
|
|
20
|
+
It can also be used to inspect images.
|
|
21
|
+
|
|
22
|
+
# Directories & Archives
|
|
23
|
+
When used against a directory, or an archive root, the tool will return a list of directory entries within.
|
|
24
|
+
- Formats: `.tar`, `.tar.gz`, `.tgz`, and `.zip`.
|
|
16
25
|
- Use `archive.ext:path/inside/archive` to read or list archive contents
|
|
17
|
-
- Parallelize reads when exploring related files
|
|
18
|
-
</instruction>
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
27
|
+
# URLs
|
|
28
|
+
- Extract information from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, technical blogs, RSS/Atom feeds, JSON endpoints
|
|
29
|
+
- `raw: true` for untouched HTML or debugging
|
|
30
|
+
- `timeout` to override the default request timeout
|
|
31
|
+
</instruction>
|
|
24
32
|
|
|
25
33
|
<critical>
|
|
26
34
|
- You **MUST** use `read` instead of bash for ALL file reading: `cat`, `head`, `tail`, `less`, `more` are FORBIDDEN.
|
|
27
|
-
- You **MUST** use `read
|
|
28
|
-
- You **MUST** use `read
|
|
29
|
-
- You **MUST** always include the `path` parameter
|
|
35
|
+
- You **MUST** use `read` instead of `ls` for directory listings.
|
|
36
|
+
- You **MUST** use `read` instead of shelling out to `tar` or `unzip` for supported archive reads.
|
|
37
|
+
- You **MUST** always include the `path` parameter, NEVER call `read` with empty arguments `{}`.
|
|
30
38
|
- When reading specific line ranges, use `offset` and `limit`: `read(path="file", offset=50, limit=100)` not `cat -n file | sed`.
|
|
39
|
+
- You **MAY** use `offset` and `limit` with URL reads; the tool will paginate the cached fetched output.
|
|
31
40
|
</critical>
|
package/src/session/artifacts.ts
CHANGED
|
@@ -75,7 +75,7 @@ export class ArtifactManager {
|
|
|
75
75
|
/**
|
|
76
76
|
* Allocate a new artifact path and ID without writing content.
|
|
77
77
|
*
|
|
78
|
-
* @param toolType Tool name for file extension (e.g., "bash", "
|
|
78
|
+
* @param toolType Tool name for file extension (e.g., "bash", "read")
|
|
79
79
|
*/
|
|
80
80
|
async allocatePath(toolType: string): Promise<{ id: string; path: string }> {
|
|
81
81
|
await this.#ensureDir();
|
|
@@ -88,7 +88,7 @@ export class ArtifactManager {
|
|
|
88
88
|
* Save content as an artifact and return the artifact ID.
|
|
89
89
|
*
|
|
90
90
|
* @param content Full content to save
|
|
91
|
-
* @param toolType Tool name for file extension (e.g., "bash", "
|
|
91
|
+
* @param toolType Tool name for file extension (e.g., "bash", "read")
|
|
92
92
|
* @returns Artifact ID (numeric string)
|
|
93
93
|
*/
|
|
94
94
|
async save(content: string, toolType: string): Promise<string> {
|
package/src/task/index.ts
CHANGED
|
@@ -496,7 +496,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
496
496
|
}
|
|
497
497
|
|
|
498
498
|
const planModeState = this.session.getPlanModeState?.();
|
|
499
|
-
const planModeTools = ["read", "grep", "find", "ls", "lsp", "
|
|
499
|
+
const planModeTools = ["read", "grep", "find", "ls", "lsp", "web_search"];
|
|
500
500
|
const effectiveAgent: typeof agent = planModeState?.enabled
|
|
501
501
|
? {
|
|
502
502
|
...agent,
|
package/src/tools/fetch.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
1
2
|
import * as path from "node:path";
|
|
2
|
-
import type {
|
|
3
|
+
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
3
4
|
import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
|
4
5
|
import { htmlToMarkdown } from "@oh-my-pi/pi-natives";
|
|
5
6
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { ptree, truncate } from "@oh-my-pi/pi-utils";
|
|
7
|
-
import { type Static, Type } from "@sinclair/typebox";
|
|
8
8
|
import { parseHTML } from "linkedom";
|
|
9
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
10
9
|
import type { Settings } from "../config/settings";
|
|
11
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
11
|
import { type Theme, theme } from "../modes/theme/theme";
|
|
13
|
-
import
|
|
12
|
+
import type { ToolSession } from "../sdk";
|
|
14
13
|
import { DEFAULT_MAX_BYTES, truncateHead } from "../session/streaming-output";
|
|
15
14
|
import { renderStatusLine } from "../tui";
|
|
16
15
|
import { CachedOutputBlock } from "../tui/output-block";
|
|
@@ -21,7 +20,6 @@ import { specialHandlers } from "../web/scrapers";
|
|
|
21
20
|
import type { RenderResult } from "../web/scrapers/types";
|
|
22
21
|
import { finalizeOutput, loadPage, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
|
|
23
22
|
import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
|
|
24
|
-
import type { ToolSession } from ".";
|
|
25
23
|
import { applyListLimit } from "./list-limit";
|
|
26
24
|
import { formatStyledArtifactReference, type OutputMeta } from "./output-meta";
|
|
27
25
|
import { formatExpandHint, getDomain } from "./render-utils";
|
|
@@ -135,6 +133,10 @@ function normalizeUrl(url: string): string {
|
|
|
135
133
|
return url;
|
|
136
134
|
}
|
|
137
135
|
|
|
136
|
+
export function isReadableUrlPath(value: string): boolean {
|
|
137
|
+
return /^https?:\/\//i.test(value) || /^www\./i.test(value);
|
|
138
|
+
}
|
|
139
|
+
|
|
138
140
|
/**
|
|
139
141
|
* Normalize MIME type (lowercase, strip charset/params)
|
|
140
142
|
*/
|
|
@@ -1082,13 +1084,8 @@ async function renderUrl(
|
|
|
1082
1084
|
// Tool Definition
|
|
1083
1085
|
// =============================================================================
|
|
1084
1086
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 20)" })),
|
|
1088
|
-
raw: Type.Optional(Type.Boolean({ description: "Return raw HTML without transforms" })),
|
|
1089
|
-
});
|
|
1090
|
-
|
|
1091
|
-
export interface FetchToolDetails {
|
|
1087
|
+
export interface ReadUrlToolDetails {
|
|
1088
|
+
kind: "url";
|
|
1092
1089
|
url: string;
|
|
1093
1090
|
finalUrl: string;
|
|
1094
1091
|
contentType: string;
|
|
@@ -1098,96 +1095,180 @@ export interface FetchToolDetails {
|
|
|
1098
1095
|
meta?: OutputMeta;
|
|
1099
1096
|
}
|
|
1100
1097
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1098
|
+
interface ReadUrlCacheEntry {
|
|
1099
|
+
artifactId?: string;
|
|
1100
|
+
details: ReadUrlToolDetails;
|
|
1101
|
+
image?: FetchImagePayload;
|
|
1102
|
+
output: string;
|
|
1103
|
+
}
|
|
1107
1104
|
|
|
1108
|
-
|
|
1109
|
-
this.description = renderPromptTemplate(fetchDescription);
|
|
1110
|
-
}
|
|
1105
|
+
const readUrlCache = new Map<string, ReadUrlCacheEntry>();
|
|
1111
1106
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
_onUpdate?: AgentToolUpdateCallback<FetchToolDetails>,
|
|
1117
|
-
_context?: AgentToolContext,
|
|
1118
|
-
): Promise<AgentToolResult<FetchToolDetails>> {
|
|
1119
|
-
const { url, timeout: rawTimeout = 20, raw = false } = params;
|
|
1107
|
+
function getReadUrlCacheKey(session: ToolSession, requestedUrl: string, raw: boolean): string {
|
|
1108
|
+
const scope = session.getSessionFile() ?? session.cwd;
|
|
1109
|
+
return `${scope}::${raw ? "raw" : "rendered"}::${normalizeUrl(requestedUrl)}`;
|
|
1110
|
+
}
|
|
1120
1111
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1112
|
+
async function readArtifactOutput(session: ToolSession, artifactId: string): Promise<string | null> {
|
|
1113
|
+
const artifactsDir = session.getArtifactsDir?.();
|
|
1114
|
+
if (!artifactsDir) return null;
|
|
1123
1115
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1116
|
+
try {
|
|
1117
|
+
const files = await fs.readdir(artifactsDir);
|
|
1118
|
+
const match = files.find(file => file.startsWith(`${artifactId}.`));
|
|
1119
|
+
if (!match) return null;
|
|
1120
|
+
return await Bun.file(path.join(artifactsDir, match)).text();
|
|
1121
|
+
} catch {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
async function materializeReadUrlCacheEntry(
|
|
1127
|
+
session: ToolSession,
|
|
1128
|
+
entry: ReadUrlCacheEntry,
|
|
1129
|
+
): Promise<ReadUrlCacheEntry | null> {
|
|
1130
|
+
if (entry.artifactId) {
|
|
1131
|
+
const artifactOutput = await readArtifactOutput(session, entry.artifactId);
|
|
1132
|
+
if (artifactOutput !== null) {
|
|
1133
|
+
return { ...entry, output: artifactOutput };
|
|
1126
1134
|
}
|
|
1135
|
+
}
|
|
1127
1136
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
maxBytes: DEFAULT_MAX_BYTES,
|
|
1131
|
-
maxLines: FETCH_DEFAULT_MAX_LINES,
|
|
1132
|
-
});
|
|
1133
|
-
const needsArtifact = truncation.truncated;
|
|
1134
|
-
let artifactId: string | undefined;
|
|
1135
|
-
|
|
1136
|
-
const buildOutput = (content: string): string => {
|
|
1137
|
-
let output = "";
|
|
1138
|
-
output += `URL: ${result.finalUrl}\n`;
|
|
1139
|
-
output += `Content-Type: ${result.contentType}\n`;
|
|
1140
|
-
output += `Method: ${result.method}\n`;
|
|
1141
|
-
if (result.notes.length > 0) {
|
|
1142
|
-
output += `Notes: ${result.notes.join("; ")}\n`;
|
|
1143
|
-
}
|
|
1144
|
-
output += `\n---\n\n`;
|
|
1145
|
-
output += content;
|
|
1146
|
-
return output;
|
|
1147
|
-
};
|
|
1137
|
+
return entry.output.length > 0 ? entry : null;
|
|
1138
|
+
}
|
|
1148
1139
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}
|
|
1140
|
+
async function persistReadUrlArtifact(session: ToolSession, output: string): Promise<string | undefined> {
|
|
1141
|
+
const { path: artifactPath, id } = (await session.allocateOutputArtifact?.("read")) ?? {};
|
|
1142
|
+
if (!artifactPath) return undefined;
|
|
1143
|
+
await Bun.write(artifactPath, output);
|
|
1144
|
+
return id;
|
|
1145
|
+
}
|
|
1156
1146
|
|
|
1157
|
-
|
|
1147
|
+
async function ensureReadUrlCacheArtifact(session: ToolSession, entry: ReadUrlCacheEntry): Promise<ReadUrlCacheEntry> {
|
|
1148
|
+
if (entry.artifactId) return entry;
|
|
1149
|
+
const artifactId = await persistReadUrlArtifact(session, entry.output);
|
|
1150
|
+
return artifactId ? { ...entry, artifactId } : entry;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function cacheReadUrlEntry(session: ToolSession, requestedUrl: string, raw: boolean, entry: ReadUrlCacheEntry): void {
|
|
1154
|
+
readUrlCache.set(getReadUrlCacheKey(session, requestedUrl, raw), entry);
|
|
1155
|
+
readUrlCache.set(getReadUrlCacheKey(session, entry.details.finalUrl, raw), entry);
|
|
1156
|
+
}
|
|
1158
1157
|
|
|
1159
|
-
|
|
1158
|
+
async function buildReadUrlCacheEntry(
|
|
1159
|
+
session: ToolSession,
|
|
1160
|
+
params: { path: string; timeout?: number; raw?: boolean },
|
|
1161
|
+
signal?: AbortSignal,
|
|
1162
|
+
options?: { ensureArtifact?: boolean },
|
|
1163
|
+
): Promise<ReadUrlCacheEntry> {
|
|
1164
|
+
const { path: url, timeout: rawTimeout = 20, raw = false } = params;
|
|
1165
|
+
|
|
1166
|
+
const effectiveTimeout = clampTimeout("fetch", rawTimeout);
|
|
1167
|
+
|
|
1168
|
+
if (signal?.aborted) {
|
|
1169
|
+
throw new ToolAbortError();
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const result = await renderUrl(url, effectiveTimeout, raw, session.settings, signal);
|
|
1173
|
+
const output = buildUrlReadOutput(result, result.content);
|
|
1174
|
+
const artifactId = options?.ensureArtifact ? await persistReadUrlArtifact(session, output) : undefined;
|
|
1175
|
+
|
|
1176
|
+
return {
|
|
1177
|
+
artifactId,
|
|
1178
|
+
details: {
|
|
1179
|
+
kind: "url",
|
|
1160
1180
|
url: result.url,
|
|
1161
1181
|
finalUrl: result.finalUrl,
|
|
1162
1182
|
contentType: result.contentType,
|
|
1163
1183
|
method: result.method,
|
|
1164
|
-
truncated: Boolean(result.truncated
|
|
1184
|
+
truncated: Boolean(result.truncated),
|
|
1165
1185
|
notes: result.notes,
|
|
1166
|
-
}
|
|
1186
|
+
},
|
|
1187
|
+
image: result.image,
|
|
1188
|
+
output,
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1167
1191
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1192
|
+
export async function loadReadUrlCacheEntry(
|
|
1193
|
+
session: ToolSession,
|
|
1194
|
+
params: { path: string; timeout?: number; raw?: boolean },
|
|
1195
|
+
signal?: AbortSignal,
|
|
1196
|
+
options?: { ensureArtifact?: boolean; preferCached?: boolean },
|
|
1197
|
+
): Promise<ReadUrlCacheEntry> {
|
|
1198
|
+
const raw = params.raw ?? false;
|
|
1199
|
+
const cached = readUrlCache.get(getReadUrlCacheKey(session, params.path, raw));
|
|
1200
|
+
if (options?.preferCached && cached) {
|
|
1201
|
+
const prepared = options.ensureArtifact ? await ensureReadUrlCacheArtifact(session, cached) : cached;
|
|
1202
|
+
const materialized = await materializeReadUrlCacheEntry(session, prepared);
|
|
1203
|
+
if (materialized) {
|
|
1204
|
+
cacheReadUrlEntry(session, params.path, raw, materialized);
|
|
1205
|
+
return materialized;
|
|
1171
1206
|
}
|
|
1207
|
+
}
|
|
1172
1208
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
const totalBytes = Math.max(outputBytes + 1, MAX_OUTPUT_CHARS + 1);
|
|
1180
|
-
const totalLines = outputLines + 1;
|
|
1181
|
-
resultBuilder.truncationFromText(result.content, {
|
|
1182
|
-
direction: "tail",
|
|
1183
|
-
totalLines,
|
|
1184
|
-
totalBytes,
|
|
1185
|
-
maxBytes: MAX_OUTPUT_CHARS,
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1209
|
+
const fresh = await buildReadUrlCacheEntry(session, params, signal, {
|
|
1210
|
+
ensureArtifact: options?.ensureArtifact,
|
|
1211
|
+
});
|
|
1212
|
+
cacheReadUrlEntry(session, params.path, raw, fresh);
|
|
1213
|
+
return fresh;
|
|
1214
|
+
}
|
|
1188
1215
|
|
|
1189
|
-
|
|
1216
|
+
function buildUrlReadOutput(result: FetchRenderResult, content: string): string {
|
|
1217
|
+
let output = "";
|
|
1218
|
+
output += `URL: ${result.finalUrl}\n`;
|
|
1219
|
+
output += `Content-Type: ${result.contentType}\n`;
|
|
1220
|
+
output += `Method: ${result.method}\n`;
|
|
1221
|
+
if (result.notes.length > 0) {
|
|
1222
|
+
output += `Notes: ${result.notes.join("; ")}\n`;
|
|
1190
1223
|
}
|
|
1224
|
+
output += `\n---\n\n`;
|
|
1225
|
+
output += content;
|
|
1226
|
+
return output;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
export async function executeReadUrl(
|
|
1230
|
+
session: ToolSession,
|
|
1231
|
+
params: { path: string; timeout?: number; raw?: boolean },
|
|
1232
|
+
signal?: AbortSignal,
|
|
1233
|
+
): Promise<AgentToolResult<ReadUrlToolDetails>> {
|
|
1234
|
+
let cacheEntry = await loadReadUrlCacheEntry(session, params, signal, { preferCached: true });
|
|
1235
|
+
const truncation = truncateHead(cacheEntry.output, {
|
|
1236
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
1237
|
+
maxLines: FETCH_DEFAULT_MAX_LINES,
|
|
1238
|
+
});
|
|
1239
|
+
const needsArtifact = truncation.truncated;
|
|
1240
|
+
if (needsArtifact && !cacheEntry.artifactId) {
|
|
1241
|
+
cacheEntry = await ensureReadUrlCacheArtifact(session, cacheEntry);
|
|
1242
|
+
cacheReadUrlEntry(session, params.path, params.raw ?? false, cacheEntry);
|
|
1243
|
+
}
|
|
1244
|
+
const output = needsArtifact ? truncation.content : cacheEntry.output;
|
|
1245
|
+
const details: ReadUrlToolDetails = {
|
|
1246
|
+
...cacheEntry.details,
|
|
1247
|
+
truncated: Boolean(cacheEntry.details.truncated || needsArtifact),
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
const contentBlocks: Array<TextContent | ImageContent> = [{ type: "text", text: output }];
|
|
1251
|
+
if (cacheEntry.image) {
|
|
1252
|
+
contentBlocks.push({ type: "image", data: cacheEntry.image.data, mimeType: cacheEntry.image.mimeType });
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const resultBuilder = toolResult(details).content(contentBlocks).sourceUrl(details.finalUrl);
|
|
1256
|
+
if (needsArtifact) {
|
|
1257
|
+
resultBuilder.truncation(truncation, { direction: "head", artifactId: cacheEntry.artifactId });
|
|
1258
|
+
} else if (cacheEntry.details.truncated) {
|
|
1259
|
+
const outputLines = cacheEntry.output.split("\n").length;
|
|
1260
|
+
const outputBytes = Buffer.byteLength(cacheEntry.output, "utf-8");
|
|
1261
|
+
const totalBytes = Math.max(outputBytes + 1, MAX_OUTPUT_CHARS + 1);
|
|
1262
|
+
const totalLines = outputLines + 1;
|
|
1263
|
+
resultBuilder.truncationFromText(cacheEntry.output, {
|
|
1264
|
+
direction: "tail",
|
|
1265
|
+
totalLines,
|
|
1266
|
+
totalBytes,
|
|
1267
|
+
maxBytes: MAX_OUTPUT_CHARS,
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
return resultBuilder.done();
|
|
1191
1272
|
}
|
|
1192
1273
|
|
|
1193
1274
|
// =============================================================================
|
|
@@ -1199,26 +1280,26 @@ function countNonEmptyLines(text: string): number {
|
|
|
1199
1280
|
return text.split("\n").filter(l => l.trim()).length;
|
|
1200
1281
|
}
|
|
1201
1282
|
|
|
1202
|
-
/** Render
|
|
1203
|
-
export function
|
|
1204
|
-
args: { url?: string; timeout?: number; raw?: boolean },
|
|
1283
|
+
/** Render URL read call (URL preview) */
|
|
1284
|
+
export function renderReadUrlCall(
|
|
1285
|
+
args: { path?: string; url?: string; timeout?: number; raw?: boolean },
|
|
1205
1286
|
_options: RenderResultOptions,
|
|
1206
1287
|
uiTheme: Theme = theme,
|
|
1207
1288
|
): Component {
|
|
1208
|
-
const url = args.url ?? "";
|
|
1289
|
+
const url = args.path ?? args.url ?? "";
|
|
1209
1290
|
const domain = getDomain(url);
|
|
1210
1291
|
const path = truncate(url.replace(/^https?:\/\/[^/]+/, ""), 50, "\u2026");
|
|
1211
1292
|
const description = `${domain}${path ? ` ${path}` : ""}`.trim();
|
|
1212
1293
|
const meta: string[] = [];
|
|
1213
1294
|
if (args.raw) meta.push("raw");
|
|
1214
1295
|
if (args.timeout !== undefined) meta.push(`timeout:${args.timeout}s`);
|
|
1215
|
-
const text = renderStatusLine({ icon: "pending", title: "
|
|
1296
|
+
const text = renderStatusLine({ icon: "pending", title: "Read", description, meta }, uiTheme);
|
|
1216
1297
|
return new Text(text, 0, 0);
|
|
1217
1298
|
}
|
|
1218
1299
|
|
|
1219
|
-
/** Render
|
|
1220
|
-
export function
|
|
1221
|
-
result: { content: Array<{ type: string; text?: string }>; details?:
|
|
1300
|
+
/** Render URL read result with tree-based layout */
|
|
1301
|
+
export function renderReadUrlResult(
|
|
1302
|
+
result: { content: Array<{ type: string; text?: string }>; details?: ReadUrlToolDetails },
|
|
1222
1303
|
options: RenderResultOptions,
|
|
1223
1304
|
uiTheme: Theme = theme,
|
|
1224
1305
|
): Component {
|
|
@@ -1238,7 +1319,7 @@ export function renderFetchResult(
|
|
|
1238
1319
|
const header = renderStatusLine(
|
|
1239
1320
|
{
|
|
1240
1321
|
icon: truncated ? "warning" : "success",
|
|
1241
|
-
title: "
|
|
1322
|
+
title: "Read",
|
|
1242
1323
|
description: `${domain}${path ? ` ${path}` : ""}`,
|
|
1243
1324
|
},
|
|
1244
1325
|
uiTheme,
|
|
@@ -1316,9 +1397,3 @@ export function renderFetchResult(
|
|
|
1316
1397
|
},
|
|
1317
1398
|
};
|
|
1318
1399
|
}
|
|
1319
|
-
|
|
1320
|
-
export const fetchToolRenderer = {
|
|
1321
|
-
renderCall: renderFetchCall,
|
|
1322
|
-
renderResult: renderFetchResult,
|
|
1323
|
-
mergeCallAndResult: true,
|
|
1324
|
-
};
|
package/src/tools/index.ts
CHANGED
|
@@ -26,7 +26,6 @@ import { CalculatorTool } from "./calculator";
|
|
|
26
26
|
import { CancelJobTool } from "./cancel-job";
|
|
27
27
|
import { type CheckpointState, CheckpointTool, RewindTool } from "./checkpoint";
|
|
28
28
|
import { ExitPlanModeTool } from "./exit-plan-mode";
|
|
29
|
-
import { FetchTool } from "./fetch";
|
|
30
29
|
import { FindTool } from "./find";
|
|
31
30
|
import {
|
|
32
31
|
GhIssueViewTool,
|
|
@@ -73,7 +72,6 @@ export * from "./calculator";
|
|
|
73
72
|
export * from "./cancel-job";
|
|
74
73
|
export * from "./checkpoint";
|
|
75
74
|
export * from "./exit-plan-mode";
|
|
76
|
-
export * from "./fetch";
|
|
77
75
|
export * from "./find";
|
|
78
76
|
export * from "./gemini-image";
|
|
79
77
|
export * from "./gh";
|
|
@@ -219,7 +217,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
219
217
|
cancel_job: CancelJobTool.createIf,
|
|
220
218
|
await: AwaitTool.createIf,
|
|
221
219
|
todo_write: s => new TodoWriteTool(s),
|
|
222
|
-
fetch: s => new FetchTool(s),
|
|
223
220
|
web_search: s => new SearchTool(s),
|
|
224
221
|
search_tool_bm25: SearchToolBm25Tool.createIf,
|
|
225
222
|
write: s => new WriteTool(s),
|
|
@@ -358,7 +355,6 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
358
355
|
if (name === "render_mermaid") return session.settings.get("renderMermaid.enabled");
|
|
359
356
|
if (name === "notebook") return session.settings.get("notebook.enabled");
|
|
360
357
|
if (name === "inspect_image") return session.settings.get("inspect_image.enabled");
|
|
361
|
-
if (name === "fetch") return session.settings.get("fetch.enabled");
|
|
362
358
|
if (name === "web_search") return session.settings.get("web_search.enabled");
|
|
363
359
|
if (name === "search_tool_bm25") return session.settings.get("mcp.discoveryMode");
|
|
364
360
|
if (name === "lsp") return session.settings.get("lsp.enabled");
|
package/src/tools/path-utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import * as url from "node:url";
|
|
4
5
|
|
|
5
6
|
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
6
7
|
const NARROW_NO_BREAK_SPACE = "\u202F";
|
|
@@ -82,6 +83,16 @@ function normalizeAtPrefix(filePath: string): string {
|
|
|
82
83
|
return filePath;
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
function stripFileUrl(filePath: string): string {
|
|
87
|
+
if (!filePath.toLowerCase().startsWith("file://")) return filePath;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
return url.fileURLToPath(filePath);
|
|
91
|
+
} catch {
|
|
92
|
+
return filePath;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
export function expandTilde(filePath: string, home?: string): string {
|
|
86
97
|
const h = home ?? os.homedir();
|
|
87
98
|
if (filePath === "~") return h;
|
|
@@ -95,7 +106,7 @@ export function expandTilde(filePath: string, home?: string): string {
|
|
|
95
106
|
}
|
|
96
107
|
|
|
97
108
|
export function expandPath(filePath: string): string {
|
|
98
|
-
const normalized = normalizeUnicodeSpaces(normalizeAtPrefix(filePath));
|
|
109
|
+
const normalized = stripFileUrl(normalizeUnicodeSpaces(normalizeAtPrefix(filePath)));
|
|
99
110
|
return expandTilde(normalized);
|
|
100
111
|
}
|
|
101
112
|
|
package/src/tools/read.ts
CHANGED
|
@@ -35,9 +35,17 @@ import {
|
|
|
35
35
|
import { convertFileWithMarkit } from "../utils/markit";
|
|
36
36
|
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
|
|
37
37
|
import { type ArchiveReader, openArchive, parseArchivePathCandidates } from "./archive-reader";
|
|
38
|
+
import {
|
|
39
|
+
executeReadUrl,
|
|
40
|
+
isReadableUrlPath,
|
|
41
|
+
loadReadUrlCacheEntry,
|
|
42
|
+
type ReadUrlToolDetails,
|
|
43
|
+
renderReadUrlCall,
|
|
44
|
+
renderReadUrlResult,
|
|
45
|
+
} from "./fetch";
|
|
38
46
|
import { applyListLimit } from "./list-limit";
|
|
39
47
|
import { formatFullOutputReference, formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
40
|
-
import { resolveReadPath } from "./path-utils";
|
|
48
|
+
import { expandPath, resolveReadPath } from "./path-utils";
|
|
41
49
|
import { formatAge, formatBytes, shortenPath, wrapBrackets } from "./render-utils";
|
|
42
50
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
43
51
|
import { toolResult } from "./tool-result";
|
|
@@ -345,18 +353,26 @@ function prependSuffixResolutionNotice(text: string, suffixResolution?: { from:
|
|
|
345
353
|
}
|
|
346
354
|
|
|
347
355
|
const readSchema = Type.Object({
|
|
348
|
-
path: Type.String({ description: "Path
|
|
349
|
-
offset: Type.Optional(Type.Number({ description: "Line number to start
|
|
350
|
-
limit: Type.Optional(Type.Number({ description: "Maximum number of lines
|
|
356
|
+
path: Type.String({ description: "Path or URL to read" }),
|
|
357
|
+
offset: Type.Optional(Type.Number({ description: "Line number to start from (1-indexed)" })),
|
|
358
|
+
limit: Type.Optional(Type.Number({ description: "Maximum number of lines" })),
|
|
359
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 20)" })),
|
|
360
|
+
raw: Type.Optional(Type.Boolean({ description: "If set, returns raw content without transformations" })),
|
|
351
361
|
});
|
|
352
362
|
|
|
353
363
|
export type ReadToolInput = Static<typeof readSchema>;
|
|
354
364
|
|
|
355
365
|
export interface ReadToolDetails {
|
|
366
|
+
kind?: "file" | "url";
|
|
356
367
|
truncation?: TruncationResult;
|
|
357
368
|
isDirectory?: boolean;
|
|
358
369
|
resolvedPath?: string;
|
|
359
370
|
suffixResolution?: { from: string; to: string };
|
|
371
|
+
url?: string;
|
|
372
|
+
finalUrl?: string;
|
|
373
|
+
contentType?: string;
|
|
374
|
+
method?: string;
|
|
375
|
+
notes?: string[];
|
|
360
376
|
meta?: OutputMeta;
|
|
361
377
|
}
|
|
362
378
|
|
|
@@ -451,6 +467,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
451
467
|
options: {
|
|
452
468
|
details?: ReadToolDetails;
|
|
453
469
|
sourcePath?: string;
|
|
470
|
+
sourceUrl?: string;
|
|
454
471
|
sourceInternal?: string;
|
|
455
472
|
entityLabel: string;
|
|
456
473
|
},
|
|
@@ -466,6 +483,9 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
466
483
|
if (options.sourcePath) {
|
|
467
484
|
resultBuilder.sourcePath(options.sourcePath);
|
|
468
485
|
}
|
|
486
|
+
if (options.sourceUrl) {
|
|
487
|
+
resultBuilder.sourceUrl(options.sourceUrl);
|
|
488
|
+
}
|
|
469
489
|
if (options.sourceInternal) {
|
|
470
490
|
resultBuilder.sourceInternal(options.sourceInternal);
|
|
471
491
|
}
|
|
@@ -651,9 +671,11 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
651
671
|
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
652
672
|
_toolContext?: AgentToolContext,
|
|
653
673
|
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
654
|
-
|
|
655
|
-
|
|
674
|
+
let { path: readPath, offset, limit, timeout, raw } = params;
|
|
656
675
|
const displayMode = resolveFileDisplayMode(this.session);
|
|
676
|
+
if (readPath.startsWith("file://")) {
|
|
677
|
+
readPath = expandPath(readPath);
|
|
678
|
+
}
|
|
657
679
|
|
|
658
680
|
// Handle internal URLs (agent://, artifact://, memory://, skill://, rule://, local://, mcp://)
|
|
659
681
|
const internalRouter = this.session.internalRouter;
|
|
@@ -661,6 +683,24 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
661
683
|
return this.#handleInternalUrl(readPath, offset, limit);
|
|
662
684
|
}
|
|
663
685
|
|
|
686
|
+
if (isReadableUrlPath(readPath)) {
|
|
687
|
+
if (!this.session.settings.get("fetch.enabled")) {
|
|
688
|
+
throw new ToolError("URL reads are disabled by settings.");
|
|
689
|
+
}
|
|
690
|
+
if (offset !== undefined || limit !== undefined) {
|
|
691
|
+
const cached = await loadReadUrlCacheEntry(this.session, { path: readPath, timeout, raw }, signal, {
|
|
692
|
+
ensureArtifact: true,
|
|
693
|
+
preferCached: true,
|
|
694
|
+
});
|
|
695
|
+
return this.#buildInMemoryTextResult(cached.output, offset, limit, {
|
|
696
|
+
details: { ...cached.details },
|
|
697
|
+
sourceUrl: cached.details.finalUrl,
|
|
698
|
+
entityLabel: "URL output",
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
return executeReadUrl(this.session, { path: readPath, timeout, raw }, signal);
|
|
702
|
+
}
|
|
703
|
+
|
|
664
704
|
const archivePath = await this.#resolveArchiveReadPath(readPath, signal);
|
|
665
705
|
if (archivePath) {
|
|
666
706
|
return this.#readArchive(readPath, offset, limit, archivePath, signal);
|
|
@@ -1128,10 +1168,16 @@ interface ReadRenderArgs {
|
|
|
1128
1168
|
file_path?: string;
|
|
1129
1169
|
offset?: number;
|
|
1130
1170
|
limit?: number;
|
|
1171
|
+
timeout?: number;
|
|
1172
|
+
raw?: boolean;
|
|
1131
1173
|
}
|
|
1132
1174
|
|
|
1133
1175
|
export const readToolRenderer = {
|
|
1134
1176
|
renderCall(args: ReadRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
1177
|
+
if (isReadableUrlPath(args.file_path || args.path || "")) {
|
|
1178
|
+
return renderReadUrlCall(args, _options, uiTheme);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1135
1181
|
const rawPath = args.file_path || args.path || "";
|
|
1136
1182
|
const filePath = shortenPath(rawPath);
|
|
1137
1183
|
const offset = args.offset;
|
|
@@ -1154,6 +1200,15 @@ export const readToolRenderer = {
|
|
|
1154
1200
|
uiTheme: Theme,
|
|
1155
1201
|
args?: ReadRenderArgs,
|
|
1156
1202
|
): Component {
|
|
1203
|
+
const urlDetails = result.details as ReadUrlToolDetails | undefined;
|
|
1204
|
+
if (urlDetails?.kind === "url" || isReadableUrlPath(args?.file_path || args?.path || "")) {
|
|
1205
|
+
return renderReadUrlResult(
|
|
1206
|
+
result as { content: Array<{ type: string; text?: string }>; details?: ReadUrlToolDetails },
|
|
1207
|
+
_options,
|
|
1208
|
+
uiTheme,
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1157
1212
|
const details = result.details;
|
|
1158
1213
|
const contentText = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1159
1214
|
const imageContent = result.content?.find(c => c.type === "image");
|
package/src/tools/renderers.ts
CHANGED
|
@@ -15,7 +15,6 @@ import { astEditToolRenderer } from "./ast-edit";
|
|
|
15
15
|
import { astGrepToolRenderer } from "./ast-grep";
|
|
16
16
|
import { bashToolRenderer } from "./bash";
|
|
17
17
|
import { calculatorToolRenderer } from "./calculator";
|
|
18
|
-
import { fetchToolRenderer } from "./fetch";
|
|
19
18
|
import { findToolRenderer } from "./find";
|
|
20
19
|
import { ghRunWatchToolRenderer } from "./gh-renderer";
|
|
21
20
|
import { grepToolRenderer } from "./grep";
|
|
@@ -61,7 +60,6 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
61
60
|
ssh: sshToolRenderer as ToolRenderer,
|
|
62
61
|
task: taskToolRenderer as ToolRenderer,
|
|
63
62
|
todo_write: todoWriteToolRenderer as ToolRenderer,
|
|
64
|
-
fetch: fetchToolRenderer as ToolRenderer,
|
|
65
63
|
gh_run_watch: ghRunWatchToolRenderer as ToolRenderer,
|
|
66
64
|
web_search: webSearchToolRenderer as ToolRenderer,
|
|
67
65
|
write: writeToolRenderer as ToolRenderer,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Retrieves content from a URL and returns it in a clean, readable format.
|
|
2
|
-
|
|
3
|
-
<instruction>
|
|
4
|
-
- Extract information from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, technical blogs, RSS/Atom feeds, JSON endpoints
|
|
5
|
-
- Read PDF or DOCX files hosted at a URL
|
|
6
|
-
- Use `raw: true` for untouched HTML or debugging
|
|
7
|
-
</instruction>
|
|
8
|
-
|
|
9
|
-
<output>
|
|
10
|
-
Returns processed, readable content. HTML transformed to remove boilerplate. PDF/DOCX converted to text. JSON returned formatted. With `raw: true`, returns untransformed HTML.
|
|
11
|
-
</output>
|