aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmal.com>2026-05-02 16:36:50 +0530
committeramankumarsingh77 <amankumarsingh7702@gmal.com>2026-05-02 16:36:50 +0530
commitb7fd72a6aa065df6335d1b4710c07bb4fd67615e (patch)
tree4786b3297e7a4feae27abf27c7734b7661db6b4f
parent96934b772378b57aed243919d3c1d6ad6e96df1f (diff)
feat(domains): migrate http_get (timeout covers body) and get_network_log (deprecation note)
-rw-r--r--src/domains/network.ts80
-rw-r--r--src/registry.ts3
-rw-r--r--src/tools.ts179
3 files changed, 83 insertions, 179 deletions
diff --git a/src/domains/network.ts b/src/domains/network.ts
new file mode 100644
index 0000000..312f39a
--- /dev/null
+++ b/src/domains/network.ts
@@ -0,0 +1,80 @@
+import { Type } from "typebox";
+import { type Result, err, ok } from "../util/result";
+import { defineBrowserTool, type ToolErr, type ToolOk } from "../util/tool";
+import { applyTruncation } from "../util/truncate";
+
+const HttpGetArgs = Type.Object({
+ url: Type.String({ description: "URL to GET" }),
+ headers: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Optional HTTP headers" })),
+ timeout: Type.Optional(
+ Type.Number({ default: 20, minimum: 1, maximum: 120, description: "Total seconds (covers headers AND body)" }),
+ ),
+});
+
+export const httpGetTool = defineBrowserTool({
+ name: "browser_http_get",
+ label: "Browser HTTP GET",
+ description:
+ "Fetch a URL outside the browser (faster than browser_navigate for APIs and static pages). Timeout covers headers AND body read.",
+ promptSnippet: "HTTP GET (outside browser; for APIs/static pages)",
+ promptGuidelines: [
+ "Faster than navigate+execute_js for JSON/HTML APIs.",
+ "No JS rendering — for SPAs use browser_navigate.",
+ ],
+ parameters: HttpGetArgs,
+ ensureAlive: false,
+ async handler(args): Promise<Result<ToolOk, ToolErr>> {
+ const ac = new AbortController();
+ const timer = setTimeout(() => ac.abort(), (args.timeout ?? 20) * 1000);
+ try {
+ const fetchInit: RequestInit = { signal: ac.signal };
+ if (args.headers !== undefined) fetchInit.headers = args.headers;
+ const res = await fetch(args.url, fetchInit);
+ const body = await res.text();
+ clearTimeout(timer);
+ const ct = res.headers.get("content-type") ?? "";
+ const truncated = await applyTruncation(body, "http");
+ return ok({
+ text: `HTTP ${res.status} ${ct}\n${truncated.text}`,
+ details: truncated.fullOutputPath !== undefined
+ ? { status: res.status, contentType: ct, length: body.length, fullOutputPath: truncated.fullOutputPath, wasTruncated: truncated.wasTruncated }
+ : { status: res.status, contentType: ct, length: body.length, wasTruncated: truncated.wasTruncated },
+ });
+ } catch (e) {
+ clearTimeout(timer);
+ const msg = e instanceof Error ? e.message : String(e);
+ const kind: ToolErr["kind"] = msg.toLowerCase().includes("abort") ? "timeout" : "io_error";
+ return err({ kind, message: msg });
+ }
+ },
+});
+
+const NetLogArgs = Type.Object({
+ eventTypes: Type.Optional(
+ Type.Array(Type.String(), { description: "CDP event names to filter (e.g., ['Network.requestWillBeSent'])" }),
+ ),
+ limit: Type.Optional(Type.Integer({ default: 50, minimum: 1, maximum: 500 })),
+});
+
+export const getNetworkLogTool = defineBrowserTool({
+ name: "browser_get_network_log",
+ label: "Browser Get Network Log",
+ description:
+ "Read buffered Network.* CDP events. NOTE: in v0.3 the event stream is consumed internally by the session manager; this tool is a placeholder. Use browser_execute_js with PerformanceObserver as a workaround.",
+ promptSnippet: "Get buffered network events (DEPRECATED in v0.3 — see description)",
+ promptGuidelines: [
+ "v0.3 routes CDP events through an internal AsyncIterable; a synchronous drain API isn't exposed yet.",
+ "For now, use browser_execute_js with PerformanceObserver, performance.getEntries(), or fetch interceptors.",
+ ],
+ parameters: NetLogArgs,
+ async handler(args): Promise<Result<ToolOk, ToolErr>> {
+ return ok({
+ text: "browser_get_network_log: in v0.3 the CDP event stream is consumed by the session; use browser_execute_js with PerformanceObserver or performance.getEntries() to inspect network activity.",
+ details: {
+ deprecated: true,
+ eventTypes: args.eventTypes ?? null,
+ limit: args.limit ?? null,
+ },
+ });
+ },
+});
diff --git a/src/registry.ts b/src/registry.ts
index 06ec74b..892d656 100644
--- a/src/registry.ts
+++ b/src/registry.ts
@@ -13,6 +13,7 @@ import { listTabsTool, currentTabTool, switchTabTool, newTabTool } from "./domai
import { uploadFileTool, downloadTool, printToPdfTool } from "./domains/files";
import { viewportResizeTool } from "./domains/viewport";
import { dragAndDropTool } from "./domains/drag";
+import { httpGetTool, getNetworkLogTool } from "./domains/network";
const TOOLS: ReadonlyArray<AnyBrowserToolDefinition> = [
clickTool,
@@ -39,6 +40,8 @@ const TOOLS: ReadonlyArray<AnyBrowserToolDefinition> = [
printToPdfTool,
viewportResizeTool,
dragAndDropTool,
+ httpGetTool,
+ getNetworkLogTool,
];
export const registerAllTools = (pi: ExtensionAPI, client: BrowserClient): void => {
diff --git a/src/tools.ts b/src/tools.ts
index f665287..b1d27a9 100644
--- a/src/tools.ts
+++ b/src/tools.ts
@@ -168,185 +168,6 @@ export function registerTools(pi: ExtensionAPI, daemon: BrowserDaemon): void {
});
// ═══════════════════════════════════════════════════════════════════════════
- // browser_http_get
- // ═══════════════════════════════════════════════════════════════════════════
- pi.registerTool({
- name: "browser_http_get",
- label: "Browser HTTP GET",
- description:
- "Make a direct HTTP GET request (no browser). Use for APIs and static pages — much faster than browser navigation for data retrieval.",
- promptSnippet: "Make a direct HTTP GET request (outside browser, for APIs)",
- promptGuidelines: [
- "Use browser_http_get for API calls and static page retrieval — it bypasses the browser entirely and is 10-50x faster.",
- "Use browser_http_get with ThreadPoolExecutor for bulk data retrieval from multiple URLs.",
- "Do NOT use browser_http_get for pages that require JavaScript rendering — use browser_navigate + browser_execute_js instead.",
- ],
- parameters: Type.Object({
- url: Type.String({ description: "URL to fetch" }),
- headers: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Optional HTTP headers" })),
- timeout: Type.Optional(Type.Number({ description: "Timeout in seconds. Default: 20" })),
- }),
- async execute(_id, params) {
- try {
- const controller = new AbortController();
- const timer = setTimeout(() => controller.abort(), (params.timeout || 20) * 1000);
-
- const response = await fetch(params.url, {
- headers: {
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
- Accept: "text/html,application/json,*/*",
- ...params.headers,
- },
- signal: controller.signal,
- });
-
- clearTimeout(timer);
-
- const body = await response.text();
- const contentType = response.headers.get("content-type") || "unknown";
-
- // Build header (always shown) and truncate body separately
- const headerText = `HTTP ${response.status} ${response.statusText}\nContent-Type: ${contentType}\nLength: ${body.length} chars\n\n`;
- const { text: truncatedBody, fullOutputPath, wasTruncated } = await applyTruncation(
- body,
- "http",
- );
-
- return {
- content: [{
- type: "text" as const,
- text: headerText + truncatedBody,
- }],
- details: {
- status: response.status,
- contentType,
- length: body.length,
- fullOutputPath,
- wasTruncated,
- },
- };
- } catch (err) {
- return {
- isError: true,
- content: [{ type: "text" as const, text: `HTTP request failed: ${err instanceof Error ? err.message : String(err)}` }],
- details: undefined,
- };
- }
- },
- renderResult(result, { expanded }, theme) {
- const details = result.details as {
- status?: number;
- contentType?: string;
- length?: number;
- wasTruncated?: boolean;
- fullOutputPath?: string;
- } | undefined;
-
- const status = details?.status ?? 0;
- const ct = details?.contentType ?? "unknown";
- const len = details?.length ?? 0;
- const wasTruncated = details?.wasTruncated ?? false;
-
- if (expanded) {
- // Expanded (CTRL+O): show full response body
- const content = result.content[0];
- const raw = content?.type === "text" ? content.text : "";
- return new Text(raw || "(empty response)", 0, 0);
- }
-
- // Collapsed (default): compact HTTP response summary
- const shortCt = ct.split(";")[0] || ct; // drop charset etc.
- const truncNote = wasTruncated ? " (truncated)" : "";
- const summary = `🌐 HTTP ${status}, ${shortCt}, ${formatSize(len)}${truncNote}`;
-
- return new Text(
- theme
- ? theme.fg(status >= 200 && status < 300 ? "success" : status >= 400 ? "error" : "warning", summary)
- : summary,
- 0,
- 0,
- );
- },
- });
-
- // ═══════════════════════════════════════════════════════════════════════════
- // browser_get_network_log
- // ═══════════════════════════════════════════════════════════════════════════
- pi.registerTool({
- name: "browser_get_network_log",
- label: "Browser Get Network Log",
- description:
- "Get buffered network events (Network.requestWillBeSent, Network.responseReceived) " +
- "from the current page. Useful for inspecting XHR/fetch calls the page makes, " +
- "finding API endpoints, or debugging network issues.",
- promptSnippet: "Get buffered network request/response events",
- promptGuidelines: [
- "Use browser_get_network_log to find API endpoints the page calls — often 10x faster than DOM scraping.",
- "Network events are buffered since the last call to this tool — call it after performing actions that trigger XHR/fetch.",
- "Filter by eventType to focus on specific events: 'Network.requestWillBeSent' (URLs, headers) or 'Network.responseReceived' (status, response headers).",
- "Combine with browser_http_get to replay captured API calls.",
- ],
- parameters: Type.Object({
- eventTypes: Type.Optional(
- Type.Array(Type.String(), {
- description:
- "Network event types to filter. Default: ['Network.requestWillBeSent', 'Network.responseReceived']",
- }),
- ),
- limit: Type.Optional(Type.Number({ description: "Max events to return. Default: 50" })),
- }),
- async execute(_id, params) {
- try {
- await daemon.ensureAlive();
- const events = await daemon.getNetworkLog(
- params.eventTypes,
- params.limit,
- );
-
- if (events.length === 0) {
- return {
- content: [{ type: "text" as const, text: "No buffered network events. Perform actions that trigger XHR/fetch calls first." }],
- details: undefined,
- };
- }
-
- const lines = events.map((e) => {
- const p = e.params as Record<string, unknown>;
- if (e.method === "Network.requestWillBeSent") {
- const req = (p.request as Record<string, unknown>) || {};
- return [
- `→ ${req.method || "GET"} ${req.url || p.documentURL || "?"}`,
- ` Type: ${p.type || "?"}`,
- ].join("\n");
- }
- if (e.method === "Network.responseReceived") {
- const resp = (p.response as Record<string, unknown>) || {};
- return [
- `← ${resp.status || "?"} ${resp.statusText || ""} ${resp.url || p.url || "?"}`,
- ` MIME: ${resp.mimeType || "?"}`,
- ].join("\n");
- }
- return `${e.method}: ${JSON.stringify(p).slice(0, 200)}`;
- });
-
- const raw = `${events.length} network event(s):\n\n${lines.join("\n\n")}`;
- const { text, fullOutputPath } = await applyTruncation(raw, "net");
-
- return {
- content: [{ type: "text" as const, text }],
- details: { count: events.length, fullOutputPath },
- };
- } catch (err) {
- return {
- isError: true,
- content: [{ type: "text" as const, text: `Network log failed: ${err instanceof Error ? err.message : String(err)}` }],
- details: undefined,
- };
- }
- },
- });
-
- // ═══════════════════════════════════════════════════════════════════════════
// browser_run_script
// ═══════════════════════════════════════════════════════════════════════════
pi.registerTool({