summaryrefslogtreecommitdiff
path: root/packages/server/src/tools/web-fetch.ts
diff options
context:
space:
mode:
authorEvan Senter <[email protected]>2025-04-19 19:45:42 +0100
committerGitHub <[email protected]>2025-04-19 19:45:42 +0100
commit3fce6cea27d3e6129d6c06e528b62e1b11bf7094 (patch)
tree244b8e9ab94f902d65d4bda8739a6538e377ed17 /packages/server/src/tools/web-fetch.ts
parent0c9e1ef61be7db53e6e73b7208b649cd8cbed6c3 (diff)
Starting to modularize into separate cli / server packages. (#55)
* Starting to move a lot of code into packages/server * More of the massive refactor, builds and runs, some issues though. * Fixing outstanding issue with double messages. * Fixing a minor UI issue. * Fixing the build post-merge. * Running formatting. * Addressing comments.
Diffstat (limited to 'packages/server/src/tools/web-fetch.ts')
-rw-r--r--packages/server/src/tools/web-fetch.ts141
1 files changed, 141 insertions, 0 deletions
diff --git a/packages/server/src/tools/web-fetch.ts b/packages/server/src/tools/web-fetch.ts
new file mode 100644
index 00000000..29e33fbe
--- /dev/null
+++ b/packages/server/src/tools/web-fetch.ts
@@ -0,0 +1,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}`,
+ };
+ }
+ }
+}