aboutsummaryrefslogtreecommitdiff
path: root/src/domains/network.ts
blob: 1377486cad8c0f9c12230aa8cc20d9ab5cf6a125 (plain)
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
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 — use browser_execute_js with PerformanceObserver instead)",
  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,
      },
    });
  },
});