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
|
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { SchemaValidator } from '../utils/schemaValidator.js';
import { BaseTool, ToolResult } from './tools.js';
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 logic (moved from CLI)
*/
export class WebFetchLogic extends BaseTool<WebFetchToolParams, ToolResult> {
static readonly Name: string = 'web_fetch';
constructor() {
super(
WebFetchLogic.Name,
'', // Display name handled by CLI wrapper
'', // Description handled by CLI wrapper
{
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',
},
);
}
validateParams(params: WebFetchToolParams): string | null {
if (
this.schema.parameters &&
!SchemaValidator.validate(
this.schema.parameters as Record<string, unknown>,
params,
)
) {
return 'Parameters failed schema validation.';
}
try {
const parsedUrl = new URL(params.url);
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return `Invalid URL protocol: "${parsedUrl.protocol}". Only 'http:' and 'https:' are supported.`;
}
} catch {
return `Invalid URL format: "${params.url}". Please provide a valid absolute URL (e.g., 'https://example.com').`;
}
return null;
}
getDescription(params: WebFetchToolParams): string {
const displayUrl =
params.url.length > 80 ? params.url.substring(0, 77) + '...' : params.url;
return `Fetching content from ${displayUrl}`;
}
// Removed shouldConfirmExecute - handled by CLI
async execute(params: WebFetchToolParams): Promise<ToolResult> {
const validationError = this.validateParams(params);
if (validationError) {
return {
llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`,
returnDisplay: `Error: ${validationError}`,
};
}
const url = params.url;
try {
const response = await fetch(url, {
headers: {
// Identify the client making the request
'User-Agent': 'GeminiCode-ServerLogic/1.0',
},
signal: AbortSignal.timeout(15000), // Use AbortSignal for timeout
});
if (!response.ok) {
const errorText = `Failed to fetch data from ${url}. Status: ${response.status} ${response.statusText}`;
return {
llmContent: `Error: ${errorText}`,
returnDisplay: `Error: ${errorText}`,
};
}
// Basic check for text-based content types
const contentType = response.headers.get('content-type') || '';
if (
!contentType.includes('text/') &&
!contentType.includes('json') &&
!contentType.includes('xml')
) {
const errorText = `Unsupported content type: ${contentType} from ${url}`;
return {
llmContent: `Error: ${errorText}`,
returnDisplay: `Error: ${errorText}`,
};
}
const data = await response.text();
const MAX_LLM_CONTENT_LENGTH = 200000; // Truncate large responses
const truncatedData =
data.length > MAX_LLM_CONTENT_LENGTH
? data.substring(0, MAX_LLM_CONTENT_LENGTH) +
'\n... [Content truncated]'
: data;
const llmContent = data
? `Fetched data from ${url}:\n\n${truncatedData}`
: `No text data fetched from ${url}. Status: ${response.status}`; // Adjusted message for clarity
return {
llmContent,
returnDisplay: `Fetched content from ${url}`,
};
} catch (error: unknown) {
const errorMessage = `Failed to fetch data from ${url}. Error: ${getErrorMessage(error)}`;
return {
llmContent: `Error: ${errorMessage}`,
returnDisplay: `Error: ${errorMessage}`,
};
}
}
}
|