feat(FN-3758): align web fetch test coverage and documentation

Aligns web fetch tool test coverage across multiple test suites and updates the agents documentation, with a patch changeset for `@runfusion/fusion`.

Fusion-Task-Id: FN-3758
This commit is contained in:
Fusion
2026-05-08 18:47:58 -07:00
committed by gsxdsm
parent eed16abacb
commit a79e91d3aa
18 changed files with 633 additions and 72 deletions

View File

@@ -20,6 +20,7 @@ import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
import { Type, type Static } from "@mariozechner/pi-ai";
import type { AgentReflectionService } from "./agent-reflection.js";
import { createLogger } from "./logger.js";
import { fetchWebContent, WebFetchError } from "./web-fetch.js";
// ── Tool parameter schemas (canonical definitions) ────────────────────────
@@ -130,6 +131,13 @@ export const memoryGetParams = Type.Object({
lineCount: Type.Optional(Type.Number({ description: "Number of lines to read (default: 120, max: 400)" })),
});
export const webFetchParams = Type.Object({
url: Type.String({ description: "URL to fetch (http/https only)" }),
prompt: Type.Optional(Type.String({ description: "Optional extraction hint for downstream summarization" })),
timeoutMs: Type.Optional(Type.Number({ description: "Request timeout in milliseconds" })),
maxBytes: Type.Optional(Type.Number({ description: "Maximum content bytes to return" })),
});
export const researchRunParams = Type.Object({
query: Type.String({ description: "Research question or topic to investigate" }),
wait_for_completion: Type.Optional(Type.Boolean({ description: "Wait for completion in this call (default: false)" })),
@@ -921,6 +929,60 @@ export function createMemoryAppendTool(rootDir: string, settings?: MemoryToolSet
};
}
export function createWebFetchTool(options?: { allowPrivateHosts?: boolean }): ToolDefinition {
return {
name: "fn_web_fetch",
label: "WebFetch",
description: "Fetch and extract readable text from a URL (lightweight HTTP fetch, no JS rendering).",
parameters: webFetchParams,
execute: async (_id: string, params: Static<typeof webFetchParams>) => {
try {
const result = await fetchWebContent(params.url, {
timeoutMs: params.timeoutMs,
maxBytes: params.maxBytes,
allowPrivateHosts: options?.allowPrivateHosts ?? false,
});
const sections = [
`URL: ${result.finalUrl}`,
`Status: ${result.status}`,
`Content-Type: ${result.contentType}`,
params.prompt ? `Prompt: ${params.prompt}` : undefined,
result.title ? `Title: ${result.title}` : undefined,
"",
result.content,
result.truncated ? "\n[truncated to maxBytes]" : "",
].filter(Boolean);
return {
content: [{ type: "text" as const, text: sections.join("\n") }],
details: {
finalUrl: result.finalUrl,
status: result.status,
contentType: result.contentType,
title: result.title,
truncated: result.truncated,
bytesRead: result.bytesRead,
prompt: params.prompt,
},
};
} catch (error) {
if (error instanceof WebFetchError) {
return {
content: [{ type: "text" as const, text: `ERROR [${error.code}]: ${error.message}` }],
details: { code: error.code, message: error.message },
isError: true,
};
}
const message = error instanceof Error ? error.message : String(error);
return {
content: [{ type: "text" as const, text: `ERROR [network-error]: ${message}` }],
details: { code: "network-error", message },
isError: true,
};
}
},
};
}
export function createMemoryTools(rootDir: string, settings?: MemoryToolSettings, options?: MemoryToolOptions): ToolDefinition[] {
if (settings?.memoryEnabled === false) {
return [];