summaryrefslogtreecommitdiff
path: root/packages/core/src/tools/web-fetch.ts
diff options
context:
space:
mode:
authorjoshualitt <[email protected]>2025-08-13 12:27:09 -0700
committerGitHub <[email protected]>2025-08-13 19:27:09 +0000
commitc0c0e9b7a0a768515181e6ae7defe9f06199ea18 (patch)
treebb25174f46c5ab54d2f4d1bcd5d6ca98478ce72a /packages/core/src/tools/web-fetch.ts
parent904f4623b6945345d5845649e98f554671b1edfb (diff)
feat(core): Migrate read_many_files, shell, and web_fetch. (#6167)
Diffstat (limited to 'packages/core/src/tools/web-fetch.ts')
-rw-r--r--packages/core/src/tools/web-fetch.ts181
1 files changed, 92 insertions, 89 deletions
diff --git a/packages/core/src/tools/web-fetch.ts b/packages/core/src/tools/web-fetch.ts
index bf8d1968..909fc548 100644
--- a/packages/core/src/tools/web-fetch.ts
+++ b/packages/core/src/tools/web-fetch.ts
@@ -6,14 +6,16 @@
import { SchemaValidator } from '../utils/schemaValidator.js';
import {
- BaseTool,
- ToolResult,
+ BaseDeclarativeTool,
+ BaseToolInvocation,
+ Kind,
ToolCallConfirmationDetails,
ToolConfirmationOutcome,
- Kind,
+ ToolInvocation,
+ ToolResult,
} from './tools.js';
import { getErrorMessage } from '../utils/errors.js';
-import { Config, ApprovalMode } from '../config/config.js';
+import { ApprovalMode, Config } from '../config/config.js';
import { getResponseText } from '../utils/generateContentResponseUtilities.js';
import { fetchWithTimeout, isPrivateIp } from '../utils/fetch.js';
import { convert } from 'html-to-text';
@@ -59,41 +61,19 @@ export interface WebFetchToolParams {
prompt: string;
}
-/**
- * Implementation of the WebFetch tool logic
- */
-export class WebFetchTool extends BaseTool<WebFetchToolParams, ToolResult> {
- static readonly Name: string = 'web_fetch';
-
- constructor(private readonly config: Config) {
- super(
- WebFetchTool.Name,
- 'WebFetch',
- "Processes content from URL(s), including local and private network addresses (e.g., localhost), embedded in a prompt. Include up to 20 URLs and instructions (e.g., summarize, extract specific data) directly in the 'prompt' parameter.",
- Kind.Fetch,
- {
- properties: {
- prompt: {
- description:
- 'A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). Must contain as least one URL starting with http:// or https://.',
- type: 'string',
- },
- },
- required: ['prompt'],
- type: 'object',
- },
- );
- const proxy = config.getProxy();
- if (proxy) {
- setGlobalDispatcher(new ProxyAgent(proxy as string));
- }
+class WebFetchToolInvocation extends BaseToolInvocation<
+ WebFetchToolParams,
+ ToolResult
+> {
+ constructor(
+ private readonly config: Config,
+ params: WebFetchToolParams,
+ ) {
+ super(params);
}
- private async executeFallback(
- params: WebFetchToolParams,
- signal: AbortSignal,
- ): Promise<ToolResult> {
- const urls = extractUrls(params.prompt);
+ private async executeFallback(signal: AbortSignal): Promise<ToolResult> {
+ const urls = extractUrls(this.params.prompt);
if (urls.length === 0) {
return {
llmContent: 'Error: No URL found in the prompt for fallback.',
@@ -127,13 +107,14 @@ export class WebFetchTool extends BaseTool<WebFetchToolParams, ToolResult> {
}).substring(0, MAX_CONTENT_LENGTH);
const geminiClient = this.config.getGeminiClient();
- const fallbackPrompt = `The user requested the following: "${params.prompt}".
+ const fallbackPrompt = `The user requested the following: "${this.params.prompt}".
-I was unable to access the URL directly. Instead, I have fetched the raw content of the page. Please use the following content to answer the user's request. Do not attempt to access the URL again.
+I was unable to access the URL directly. Instead, I have fetched the raw content of the page. Please use the following content to answer the request. Do not attempt to access the URL again.
---
${textContent}
----`;
+---
+`;
const result = await geminiClient.generateContent(
[{ role: 'user', parts: [{ text: fallbackPrompt }] }],
{},
@@ -154,49 +135,22 @@ ${textContent}
}
}
- validateParams(params: WebFetchToolParams): string | null {
- const errors = SchemaValidator.validate(
- this.schema.parametersJsonSchema,
- params,
- );
- if (errors) {
- return errors;
- }
- if (!params.prompt || params.prompt.trim() === '') {
- return "The 'prompt' parameter cannot be empty and must contain URL(s) and instructions.";
- }
- if (
- !params.prompt.includes('http://') &&
- !params.prompt.includes('https://')
- ) {
- return "The 'prompt' must contain at least one valid URL (starting with http:// or https://).";
- }
- return null;
- }
-
- getDescription(params: WebFetchToolParams): string {
+ getDescription(): string {
const displayPrompt =
- params.prompt.length > 100
- ? params.prompt.substring(0, 97) + '...'
- : params.prompt;
+ this.params.prompt.length > 100
+ ? this.params.prompt.substring(0, 97) + '...'
+ : this.params.prompt;
return `Processing URLs and instructions from prompt: "${displayPrompt}"`;
}
- async shouldConfirmExecute(
- params: WebFetchToolParams,
- ): Promise<ToolCallConfirmationDetails | false> {
+ async shouldConfirmExecute(): Promise<ToolCallConfirmationDetails | false> {
if (this.config.getApprovalMode() === ApprovalMode.AUTO_EDIT) {
return false;
}
- const validationError = this.validateParams(params);
- if (validationError) {
- return false;
- }
-
// Perform GitHub URL conversion here to differentiate between user-provided
// URL and the actual URL to be fetched.
- const urls = extractUrls(params.prompt).map((url) => {
+ const urls = extractUrls(this.params.prompt).map((url) => {
if (url.includes('github.com') && url.includes('/blob/')) {
return url
.replace('github.com', 'raw.githubusercontent.com')
@@ -208,7 +162,7 @@ ${textContent}
const confirmationDetails: ToolCallConfirmationDetails = {
type: 'info',
title: `Confirm Web Fetch`,
- prompt: params.prompt,
+ prompt: this.params.prompt,
urls,
onConfirm: async (outcome: ToolConfirmationOutcome) => {
if (outcome === ToolConfirmationOutcome.ProceedAlways) {
@@ -219,25 +173,14 @@ ${textContent}
return confirmationDetails;
}
- async execute(
- params: WebFetchToolParams,
- signal: AbortSignal,
- ): Promise<ToolResult> {
- const validationError = this.validateParams(params);
- if (validationError) {
- return {
- llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`,
- returnDisplay: validationError,
- };
- }
-
- const userPrompt = params.prompt;
+ async execute(signal: AbortSignal): Promise<ToolResult> {
+ const userPrompt = this.params.prompt;
const urls = extractUrls(userPrompt);
const url = urls[0];
const isPrivate = isPrivateIp(url);
if (isPrivate) {
- return this.executeFallback(params, signal);
+ return this.executeFallback(signal);
}
const geminiClient = this.config.getGeminiClient();
@@ -295,7 +238,7 @@ ${textContent}
}
if (processingError) {
- return this.executeFallback(params, signal);
+ return this.executeFallback(signal);
}
const sourceListFormatted: string[] = [];
@@ -360,3 +303,63 @@ ${sourceListFormatted.join('\n')}`;
}
}
}
+
+/**
+ * Implementation of the WebFetch tool logic
+ */
+export class WebFetchTool extends BaseDeclarativeTool<
+ WebFetchToolParams,
+ ToolResult
+> {
+ static readonly Name: string = 'web_fetch';
+
+ constructor(private readonly config: Config) {
+ super(
+ WebFetchTool.Name,
+ 'WebFetch',
+ "Processes content from URL(s), including local and private network addresses (e.g., localhost), embedded in a prompt. Include up to 20 URLs and instructions (e.g., summarize, extract specific data) directly in the 'prompt' parameter.",
+ Kind.Fetch,
+ {
+ properties: {
+ prompt: {
+ description:
+ 'A comprehensive prompt that includes the URL(s) (up to 20) to fetch and specific instructions on how to process their content (e.g., "Summarize https://example.com/article and extract key points from https://another.com/data"). Must contain as least one URL starting with http:// or https://.',
+ type: 'string',
+ },
+ },
+ required: ['prompt'],
+ type: 'object',
+ },
+ );
+ const proxy = config.getProxy();
+ if (proxy) {
+ setGlobalDispatcher(new ProxyAgent(proxy as string));
+ }
+ }
+
+ protected validateToolParams(params: WebFetchToolParams): string | null {
+ const errors = SchemaValidator.validate(
+ this.schema.parametersJsonSchema,
+ params,
+ );
+ if (errors) {
+ return errors;
+ }
+ if (!params.prompt || params.prompt.trim() === '') {
+ return "The 'prompt' parameter cannot be empty and must contain URL(s) and instructions.";
+ }
+ if (
+ !params.prompt.includes('http://') &&
+ !params.prompt.includes('https://')
+ ) {
+ return "The 'prompt' must contain at least one valid URL (starting with http:// or https://).";
+ }
+ return null;
+ }
+
+ protected createInvocation(
+ params: WebFetchToolParams,
+ ): ToolInvocation<WebFetchToolParams, ToolResult> {
+ return new WebFetchToolInvocation(this.config, params);
+ }
+}