返回 DeepSeek-Reasonix
hostCalls.ts
根目录 / desktop / electron / src / main / browser / hostCalls.ts
1 import type { BrowserNavigateTarget } from "../../shared/ipc.js";
2 import type { HostCallTable } from "../hostCalls.js";
3 import { bool, num, str, strList, type Params } from "../params.js";
4 import type { ActionExecutor, ActResult } from "./actions.js";
5 import type { DocumentRegistry } from "./documents.js";
6 import type { DownloadTracker, HostDownload } from "./downloads.js";
7 import { takenOver } from "./errors.js";
8 import type { GrantRegistry } from "./grants.js";
9 import { captureScreenshot, type ScreenshotDeps, type ScreenshotResult } from "./screenshot.js";
10 import { takeSnapshot, type SnapshotResult } from "./snapshot.js";
11 import type { BrowserSurfaceManager, BrowserTab } from "./surfaceManager.js";
12
13 export interface HostBrowserTab {
14 id: string;
15 url: string;
16 title: string;
17 loading: boolean;
18 temporary: boolean;
19 }
20
21 export interface BrowserHostDeps {
22 surfaces: BrowserSurfaceManager;
23 grants: GrantRegistry;
24 documents: DocumentRegistry;
25 actions: ActionExecutor;
26 downloads: DownloadTracker;
27 snapshot?(tab: BrowserTab, selector: string): Promise<SnapshotResult>;
28 screenshot?(tab: BrowserTab, request: { ref: string; fullPage: boolean; directory: string }): Promise<ScreenshotResult>;
29 screenshotDeps?: ScreenshotDeps;
30 }
31
32 export function hostTab(surfaces: BrowserSurfaceManager, tab: BrowserTab): HostBrowserTab {
33 const view = surfaces.view(tab);
34 return { id: view.id, url: view.url, title: view.title, loading: view.loading, temporary: view.temporary };
35 }
36
37 // Every call after grant re-verifies the grant and that the browser tab
38 // belongs to the grant's task; reads and writes both refuse a tab the user
39 // is operating (-32011).
40 export function buildBrowserHostCalls(deps: BrowserHostDeps): HostCallTable {
41 const { surfaces, grants, documents, downloads } = deps;
42 const boundTab = (params: Params): BrowserTab => {
43 const tab = surfaces.get(str(params, "tabId"));
44 grants.verifyTab(str(params, "grantId"), tab?.taskId);
45 return tab as BrowserTab;
46 };
47 const agentTab = (params: Params): BrowserTab => {
48 const tab = boundTab(params);
49 if (tab.mode !== "agent") throw takenOver(`tab ${tab.id} is in human mode`);
50 return tab;
51 };
52 const snapshot = deps.snapshot ?? ((tab, selector) => takeSnapshot(tab.view.page, tab.id, tab.epoch, selector, documents));
53 const screenshot = deps.screenshot ?? ((tab, request) => {
54 const token = documents.currentToken(tab.id);
55 const binding = token ? documents.lookup(token) ?? null : null;
56 return captureScreenshot(tab.view.page, binding, tab.view.page.getZoomFactor() || 1, request, deps.screenshotDeps);
57 });
58
59 return {
60 "host/browser.grant": (params) => {
61 grants.install({ grantId: str(params, "grantId"), taskId: str(params, "tabId"), sessionId: str(params, "sessionId") });
62 return {};
63 },
64 "host/browser.revoke": (params) => {
65 const grant = grants.revoke(str(params, "grantId"));
66 if (grant) for (const tab of surfaces.tabsForTask(grant.taskId)) documents.invalidateTab(tab.id);
67 return {};
68 },
69 "host/browser.tabs.list": (params) => {
70 const grant = grants.verify(str(params, "grantId"));
71 return { tabs: surfaces.tabsForTask(grant.taskId).map((tab) => hostTab(surfaces, tab)) };
72 },
73 "host/browser.tabs.open": async (params) => {
74 const grant = grants.verify(str(params, "grantId"));
75 const tab = await surfaces.open(str(params, "url"), { taskId: grant.taskId, temporary: bool(params, "temporary") });
76 return hostTab(surfaces, tab);
77 },
78 "host/browser.tabs.navigate": async (params) => {
79 const tab = agentTab(params);
80 const action = str(params, "action");
81 const target: BrowserNavigateTarget = action === "back" || action === "forward" || action === "reload" ? { action } : { url: str(params, "url") };
82 await surfaces.navigate(tab.id, target);
83 return hostTab(surfaces, tab);
84 },
85 "host/browser.tabs.close": (params) => {
86 const tab = boundTab(params);
87 documents.invalidateTab(tab.id);
88 downloads.forgetTab(tab.id);
89 surfaces.close(tab.id);
90 return {};
91 },
92 "host/browser.snapshot": (params) => snapshot(agentTab(params), str(params, "selector")),
93 "host/browser.act": (params): Promise<ActResult> => {
94 const tab = boundTab(params);
95 const grantId = str(params, "grantId");
96 const directory = str(params, "directory");
97 if (directory !== "") downloads.setTaskDirectory(tab.taskId, directory);
98 return deps.actions.act(
99 tab,
100 {
101 operationId: str(params, "operationId"),
102 tabId: tab.id,
103 documentToken: str(params, "documentToken"),
104 action: str(params, "action"),
105 ref: str(params, "ref"),
106 text: str(params, "text"),
107 keys: str(params, "keys"),
108 options: strList(params, "options"),
109 files: strList(params, "files"),
110 submit: bool(params, "submit"),
111 deltaX: num(params, "deltaX"),
112 deltaY: num(params, "deltaY"),
113 },
114 () => grants.verifyTab(grantId, surfaces.get(tab.id)?.taskId),
115 );
116 },
117 "host/browser.screenshot": (params) => {
118 const tab = agentTab(params);
119 const directory = str(params, "directory");
120 if (directory !== "") downloads.setTaskDirectory(tab.taskId, directory);
121 return screenshot(tab, { ref: str(params, "ref"), fullPage: bool(params, "fullPage"), directory });
122 },
123 "host/browser.downloads": async (params): Promise<{ downloads: HostDownload[] }> => {
124 const tab = boundTab(params);
125 return { downloads: await downloads.wait(tab.id, num(params, "waitForMs")) };
126 },
127 };
128 }
129
129 lines TYPESCRIPT