diff options
| author | amankumarsingh77 <amankumarsingh7702@gmal.com> | 2026-05-02 16:24:23 +0530 |
|---|---|---|
| committer | amankumarsingh77 <amankumarsingh7702@gmal.com> | 2026-05-02 16:24:23 +0530 |
| commit | 3dfc4f1150b80bb9a5e332999f7440b6255c4ec0 (patch) | |
| tree | f1c69c08b2327a1ab5adcdd5764be4a6e987340c /src/tools.ts | |
| parent | 6708da54b1805756908dea84bf65fe73ab03426a (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.ts | 289 |
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 // ═══════════════════════════════════════════════════════════════════════════ |
