aboutsummaryrefslogtreecommitdiff
path: root/src/tools.ts
diff options
context:
space:
mode:
authoramankumarsingh77 <amankumarsingh7702@gmal.com>2026-05-02 16:24:23 +0530
committeramankumarsingh77 <amankumarsingh7702@gmal.com>2026-05-02 16:24:23 +0530
commit3dfc4f1150b80bb9a5e332999f7440b6255c4ec0 (patch)
treef1c69c08b2327a1ab5adcdd5764be4a6e987340c /src/tools.ts
parent6708da54b1805756908dea84bf65fe73ab03426a (diff)
feat(domains): migrate navigate (NavOutcome) and open_urls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src/tools.ts')
-rw-r--r--src/tools.ts289
1 files changed, 0 insertions, 289 deletions
diff --git a/src/tools.ts b/src/tools.ts
index 3ea9179..3b5d639 100644
--- a/src/tools.ts
+++ b/src/tools.ts
@@ -97,79 +97,6 @@ async function applyTruncation(
export function registerTools(pi: ExtensionAPI, daemon: BrowserDaemon): void {
// ═══════════════════════════════════════════════════════════════════════════
- // browser_navigate
- // ═══════════════════════════════════════════════════════════════════════════
- pi.registerTool({
- name: "browser_navigate",
- label: "Browser Navigate",
- description:
- "Navigate to a URL. Creates a new tab on first call (safe), navigates current tab on subsequent calls.",
- promptSnippet: "Navigate to a URL (creates new tab on first call)",
- promptGuidelines: [
- "Use browser_navigate to go to URLs. The first call creates a new tab so it doesn't clobber the user's active tab.",
- "Use browser_wait_for_load after browser_navigate to wait for the page to finish loading.",
- "For extracting data from a page you already navigated to, use browser_execute_js or browser_http_get (faster for APIs).",
- ],
- parameters: Type.Object({
- url: Type.String({ description: "Full URL to navigate to (e.g. https://github.com)" }),
- }),
- async execute(_id, params) {
- try {
- await daemon.ensureAlive();
-
- // Determine if we need a new tab: create one on first-ever call,
- // or if the current tab is an internal Chrome page. Otherwise
- // navigate the current tab in-place.
- let targetId: string;
- let isNewTab = false;
- try {
- const tabs = await daemon.listTabs(false);
- if (tabs.length === 0) {
- // No real tabs exist — create one
- targetId = await daemon.newTab(params.url);
- isNewTab = true;
- } else {
- // Navigate the current tab in-place
- await daemon.cdp("Page.navigate", { url: params.url });
- const tab = await daemon.currentTab();
- targetId = tab.targetId;
- }
- } catch {
- // Fallback: create a new tab
- targetId = await daemon.newTab(params.url);
- isNewTab = true;
- }
-
- const info = await daemon.getPageInfo();
- if ("dialog" in info) {
- return {
- content: [{ type: "text" as const, text: `Navigated to ${params.url}\nTarget: ${targetId}\n⚠️ A dialog is open: ${info.dialog.type} — "${info.dialog.message}"\nUse browser_handle_dialog to dismiss or accept it.` }],
- details: undefined,
- };
- }
-
- const prefix = isNewTab ? "New tab: " : "";
- return {
- content: [{
- type: "text" as const,
- text: `${prefix}Navigated to: ${info.url}\nTitle: ${info.title}\nViewport: ${info.width}x${info.height}\nTarget: ${targetId}`,
- }],
- details: undefined,
- };
- } catch (err) {
- return {
- isError: true,
- content: [{ type: "text" as const, text: `Navigation failed: ${err instanceof Error ? err.message : String(err)}` }],
- details: undefined,
- };
- }
- },
- renderCall(args, theme) {
- return new Text(`🌐 Navigate to ${(args as { url: string }).url}`, 0, 0);
- },
- });
-
- // ═══════════════════════════════════════════════════════════════════════════
// browser_go_back
// ═══════════════════════════════════════════════════════════════════════════
pi.registerTool({
@@ -936,222 +863,6 @@ export function registerTools(pi: ExtensionAPI, daemon: BrowserDaemon): void {
},
});
- // ═══════════════════════════════════════════════════════════════════════
- // browser_open_urls
- // ═══════════════════════════════════════════════════════════════════════
- pi.registerTool({
- name: "browser_open_urls",
- label: "Browser Open URLs",
- description:
- "Open multiple URLs in new browser tabs in parallel. Useful after web_search to open citation links for visual inspection. Creates real Chrome tabs with full JS rendering support — identical to browser_new_tab but for many URLs at once.",
- promptSnippet: "Open multiple URLs in new tabs (parallel, full JS rendering)",
- promptGuidelines: [
- "Use browser_open_urls to open multiple URLs in parallel — much faster than calling browser_new_tab repeatedly.",
- "Use browser_open_urls after web_search to open citation links from the synthesized answer.",
- "Each URL opens in a real Chrome tab with full JS rendering — same as browser_new_tab.",
- "After opening, use browser_list_tabs to see all tabs, browser_switch_tab to switch, and browser_screenshot / browser_execute_js to interact.",
- "Use browser_wait_for_load on a tab before extracting data from SPAs.",
- ],
- parameters: Type.Object({
- urls: Type.Array(Type.String(), { description: "Array of URLs to open in new tabs" }),
- }),
- async execute(_id, params, _signal, onUpdate) {
- await daemon.ensureAlive();
-
- const total = params.urls.length;
- const results: Array<{
- url: string;
- targetId: string;
- ok: boolean;
- error?: string;
- }> = [];
-
- // ═══════════════════════════════════════════════════════════════════
- // Phase 1: Create all tabs in parallel.
- // Target.createTarget is a browser-level CDP call — no session
- // needed, no shared state mutated. Safe to parallelize.
- // ═══════════════════════════════════════════════════════════════════
- const createPromises = params.urls.map(async (url: string) => {
- try {
- const result = (await daemon.cdp("Target.createTarget", {
- url: "about:blank",
- })) as { targetId: string };
- return { url, targetId: result.targetId, ok: true as const };
- } catch (err) {
- return {
- url,
- targetId: "",
- ok: false as const,
- error: err instanceof Error ? err.message : String(err),
- details: undefined,
- };
- }
- });
-
- const created = await Promise.all(createPromises);
-
- // ═══════════════════════════════════════════════════════════════════
- // Phase 2: Attach a CDP session to each tab and navigate.
- // Each tab gets its own sessionId passed explicitly to cdp(),
- // avoiding the shared daemon._sessionId. Safe to parallelize.
- // ═══════════════════════════════════════════════════════════════════
- let completed = 0;
- const attachAndNav = created
- .filter((t) => t.ok)
- .map(async (tab) => {
- try {
- // Attach to target to get a session for this tab
- const attachResult = (await daemon.cdp(
- "Target.attachToTarget",
- { targetId: tab.targetId, flatten: true },
- )) as { sessionId: string };
- const sid = attachResult.sessionId;
-
- // Enable the Page domain so navigation events fire
- await daemon.cdp("Page.enable", {}, sid);
-
- // Navigate to the URL using the tab's own session
- await daemon.cdp("Page.navigate", { url: tab.url }, sid);
-
- return { ...tab, ok: true as const, targetId: tab.targetId };
- } catch (err) {
- return {
- ...tab,
- ok: false as const,
- error: err instanceof Error ? err.message : String(err),
- details: undefined,
- };
- } finally {
- completed++;
- // Stream progress so the TUI shows live per-URL status
- const okCount = created.filter((t) => t.ok).length;
- onUpdate?.({
- content: [
- {
- type: "text" as const,
- text: `Opening URLs… ${completed}/${okCount} navigated`,
- },
- ],
- details: undefined,
- });
- }
- });
-
- // Also collect failures from Phase 1
- const failures = created.filter((t) => !t.ok);
-
- const settled = await Promise.all(attachAndNav);
- results.push(...settled, ...failures);
-
- // ═══════════════════════════════════════════════════════════════════
- // Phase 3: Activate the last successfully opened tab so the user
- // sees something meaningful in the foreground.
- // ═══════════════════════════════════════════════════════════════════
- const lastOk = [...settled].reverse().find((r) => r.ok);
- if (lastOk) {
- try {
- await daemon.cdp("Target.activateTarget", {
- targetId: lastOk.targetId,
- });
- } catch {
- // best-effort — the tabs are still open
- }
- }
-
- // Build result text
- const okTabs = results.filter((r) => r.ok);
- const failedTabs = results.filter((r) => !r.ok);
-
- const lines: string[] = [];
- if (okTabs.length > 0) {
- lines.push(
- `✅ ${okTabs.length}/${total} tabs opened:`,
- ...okTabs.map(
- (t, i) => ` [${i}] ${t.url} → ${t.targetId}`,
- ),
- );
- }
- if (failedTabs.length > 0) {
- lines.push(
- `❌ ${failedTabs.length}/${total} failed:`,
- ...failedTabs.map(
- (t) => ` ${t.url}: ${t.error || "unknown error"}`,
- ),
- );
- }
-
- const isTotalFailure = okTabs.length === 0;
- const fullOutput = lines.join("\n");
- const { text, fullOutputPath } = await applyTruncation(fullOutput, "urls");
-
- return {
- isError: isTotalFailure,
- content: [{ type: "text" as const, text }],
- details: { tabs: results, fullOutputPath },
- };
- },
- renderCall(args, _theme) {
- const { urls } = args as { urls: string[] };
- return new Text(
- `🌐 Opening ${urls.length} URL${urls.length !== 1 ? "s" : ""}…`,
- 0,
- 0,
- );
- },
- renderResult(result, { expanded }, theme) {
- const details = result.details as {
- tabs?: Array<{
- url: string;
- targetId: string;
- ok: boolean;
- error?: string;
- }>;
- fullOutputPath?: string;
- } | undefined;
-
- const tabs = details?.tabs || [];
- const ok = tabs.filter((t) => t.ok).length;
- const fail = tabs.filter((t) => !t.ok).length;
- const total = tabs.length;
-
- if (expanded) {
- // Expanded (CTRL+O): show the full URL→targetId mapping
- const content = result.content[0];
- const raw = content?.type === "text" ? content.text : "";
- // Strip the truncation footer for cleaner display, show it separately
- const truncIdx = raw.indexOf("\n\n[Output truncated:");
- const displayText = truncIdx >= 0 ? raw.slice(0, truncIdx) : raw;
-
- let expandedText = displayText || "(no output)";
- if (details?.fullOutputPath && truncIdx < 0) {
- // Truncated but we stripped the footer — add a compact note
- expandedText += `\n\n${theme ? theme.fg("dim", `Full output: ${details.fullOutputPath}`) : `Full output: ${details.fullOutputPath}`}`;
- } else if (details?.fullOutputPath) {
- expandedText += `\n${theme ? theme.fg("dim", `Full output: ${details.fullOutputPath}`) : `Full output: ${details.fullOutputPath}`}`;
- }
- return new Text(expandedText, 0, 0);
- }
-
- // Collapsed (default): compact summary
- let text: string;
- if (total === 0) {
- text = "No URLs provided.";
- } else if (fail === 0) {
- text = `✅ ${ok} tab${ok !== 1 ? "s" : ""} opened`;
- } else if (ok === 0) {
- text = `❌ All ${fail} URL${fail !== 1 ? "s" : ""} failed`;
- } else {
- text = `⚠️ ${ok} opened, ${fail} failed`;
- }
-
- return new Text(
- theme ? theme.fg(fail === 0 ? "success" : ok === 0 ? "error" : "warning", text) : text,
- 0,
- 0,
- );
- },
- });
-
// ═══════════════════════════════════════════════════════════════════════════
// browser_run_script
// ═══════════════════════════════════════════════════════════════════════════