1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
import { SchemaValidator } from '../utils/schemaValidator.js';
import { BaseTool, ToolResult } from './tools.js';
import { ToolCallConfirmationDetails } from '../ui/types.js'; // Added for shouldConfirmExecute
import { getErrorMessage } from '../utils/errors.js';
/**
* Parameters for the WebFetch tool
*/
export interface WebFetchToolParams {
/**
* The URL to fetch content from.
*/
url: string;
}
/**
* Implementation of the WebFetch tool that reads content from a URL.
*/
export class WebFetchTool extends BaseTool<WebFetchToolParams, ToolResult> {
static readonly Name: string = 'web_fetch';
/**
* Creates a new instance of the WebFetchTool
*/
constructor() {
super(
WebFetchTool.Name,
'WebFetch',
'Fetches text content from a given URL. Handles potential network errors and non-success HTTP status codes.',
{
properties: {
url: {
description:
"The URL to fetch. Must be an absolute URL (e.g., 'https://example.com/file.txt').",
type: 'string',
},
},
required: ['url'],
type: 'object',
},
);
// No rootDirectory needed for web fetching
}
/**
* Validates the parameters for the WebFetch tool
* @param params Parameters to validate
* @returns An error message string if invalid, null otherwise
*/
invalidParams(params: WebFetchToolParams): string | null {
// 1. Validate against the basic schema first
if (
this.schema.parameters &&
!SchemaValidator.validate(
this.schema.parameters as Record<string, unknown>,
params,
)
) {
return 'Parameters failed schema validation.';
}
// 2. Validate the URL format and protocol
try {
const parsedUrl = new URL(params.url);
// Ensure it's an HTTP or HTTPS URL
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return `Invalid URL protocol: "${parsedUrl.protocol}". Only 'http:' and 'https:' are supported.`;
}
} catch {
// The URL constructor throws if the format is invalid
return `Invalid URL format: "${params.url}". Please provide a valid absolute URL (e.g., 'https://example.com').`;
}
// If all checks pass, the parameters are valid
return null;
}
/**
* Gets a description of the web fetch operation.
* @param params Parameters for the web fetch.
* @returns A string describing the operation.
*/
getDescription(params: WebFetchToolParams): string {
// Shorten long URLs for display
const displayUrl =
params.url.length > 80 ? params.url.substring(0, 77) + '...' : params.url;
return `Fetching content from ${displayUrl}`;
}
/**
* Determines if the tool should prompt for confirmation before execution.
* Web fetches are generally safe, so default to false.
* @param params Parameters for the tool execution
* @returns Whether execute should be confirmed.
*/
async shouldConfirmExecute(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
params: WebFetchToolParams,
): Promise<ToolCallConfirmationDetails | false> {
// Could add logic here to confirm based on domain, etc. if needed
return Promise.resolve(false);
}
/**
* Fetches content from the specified URL.
* @param params Parameters for the web fetch operation.
* @returns Result with the fetched content or an error message.
*/
async execute(params: WebFetchToolParams): Promise<ToolResult> {
const validationError = this.invalidParams(params);
if (validationError) {
return {
llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`,
returnDisplay: `**Error:** Invalid parameters. ${validationError}`,
};
}
const url = params.url;
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'GeminiCode-CLI/1.0',
},
signal: AbortSignal.timeout(15000), // 15 seconds timeout
});
if (!response.ok) {
// fetch doesn't throw on bad HTTP status codes (4xx, 5xx)
const errorText = `Failed to fetch data from ${url}. Status: ${response.status} ${response.statusText}`;
return {
llmContent: `Error: ${errorText}`,
returnDisplay: `**Error:** ${errorText}`,
};
}
// Assuming the response is text. Add checks for content-type if needed.
const data = await response.text();
let llmContent = '';
// Truncate very large responses for the LLM context
const MAX_LLM_CONTENT_LENGTH = 100000;
if (data) {
llmContent = `Fetched data from ${url}:\n\n${
data.length > MAX_LLM_CONTENT_LENGTH
? data.substring(0, MAX_LLM_CONTENT_LENGTH) +
'\n... [Content truncated]'
: data
}`;
} else {
llmContent = `No data fetched from ${url}. Status: ${response.status}`;
}
return {
llmContent,
returnDisplay: `Fetched content from ${url}`, // Simple display message
};
} catch (error: unknown) {
// This catches network errors (DNS resolution, connection refused, etc.)
// and errors from the URL constructor if somehow bypassed validation (unlikely)
const errorMessage = `Failed to fetch data from ${url}. Error: ${getErrorMessage(error)}`;
return {
llmContent: `Error: ${errorMessage}`,
returnDisplay: `**Error:** ${errorMessage}`,
};
}
}
}
|