@downcity/plugins 1.0.248 → 1.0.253

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/bin/image/ImagePlugin.d.ts +1 -1
  2. package/bin/image/ImagePlugin.d.ts.map +1 -1
  3. package/bin/image/ImagePlugin.js +23 -8
  4. package/bin/image/ImagePlugin.js.map +1 -1
  5. package/bin/image/runtime/ImageResultStorage.d.ts +14 -0
  6. package/bin/image/runtime/ImageResultStorage.d.ts.map +1 -0
  7. package/bin/image/runtime/ImageResultStorage.js +167 -0
  8. package/bin/image/runtime/ImageResultStorage.js.map +1 -0
  9. package/bin/image/types/ImagePlugin.d.ts +22 -0
  10. package/bin/image/types/ImagePlugin.d.ts.map +1 -1
  11. package/bin/web/Plugin.d.ts +25 -30
  12. package/bin/web/Plugin.d.ts.map +1 -1
  13. package/bin/web/Plugin.js +254 -110
  14. package/bin/web/Plugin.js.map +1 -1
  15. package/bin/web/providers/ComputerUseBrowserProviderAdapter.d.ts +35 -0
  16. package/bin/web/providers/ComputerUseBrowserProviderAdapter.d.ts.map +1 -0
  17. package/bin/web/providers/ComputerUseBrowserProviderAdapter.js +62 -0
  18. package/bin/web/providers/ComputerUseBrowserProviderAdapter.js.map +1 -0
  19. package/bin/web/providers/PlaywrightBrowserProvider.d.ts +43 -0
  20. package/bin/web/providers/PlaywrightBrowserProvider.d.ts.map +1 -0
  21. package/bin/web/providers/PlaywrightBrowserProvider.js +184 -0
  22. package/bin/web/providers/PlaywrightBrowserProvider.js.map +1 -0
  23. package/bin/web/providers/SemanticBrowserProviderAdapter.d.ts +39 -0
  24. package/bin/web/providers/SemanticBrowserProviderAdapter.d.ts.map +1 -0
  25. package/bin/web/providers/SemanticBrowserProviderAdapter.js +61 -0
  26. package/bin/web/providers/SemanticBrowserProviderAdapter.js.map +1 -0
  27. package/bin/web/types/ComputerUseBrowserProviderAdapter.d.ts +43 -0
  28. package/bin/web/types/ComputerUseBrowserProviderAdapter.d.ts.map +1 -0
  29. package/bin/web/types/ComputerUseBrowserProviderAdapter.js +8 -0
  30. package/bin/web/types/ComputerUseBrowserProviderAdapter.js.map +1 -0
  31. package/bin/web/types/PlaywrightBrowserProvider.d.ts +17 -0
  32. package/bin/web/types/PlaywrightBrowserProvider.d.ts.map +1 -0
  33. package/bin/web/types/PlaywrightBrowserProvider.js +7 -0
  34. package/bin/web/types/PlaywrightBrowserProvider.js.map +1 -0
  35. package/bin/web/types/SemanticBrowserProviderAdapter.d.ts +19 -0
  36. package/bin/web/types/SemanticBrowserProviderAdapter.d.ts.map +1 -0
  37. package/bin/web/types/SemanticBrowserProviderAdapter.js +8 -0
  38. package/bin/web/types/SemanticBrowserProviderAdapter.js.map +1 -0
  39. package/bin/web/types/WebPlugin.d.ts +213 -63
  40. package/bin/web/types/WebPlugin.d.ts.map +1 -1
  41. package/bin/web/types/WebPlugin.js +23 -11
  42. package/bin/web/types/WebPlugin.js.map +1 -1
  43. package/bin/web.d.ts +9 -3
  44. package/bin/web.d.ts.map +1 -1
  45. package/bin/web.js +5 -2
  46. package/bin/web.js.map +1 -1
  47. package/package.json +4 -3
  48. package/scripts/web-plugin.test.mjs +195 -57
  49. package/src/image/ImagePlugin.ts +23 -8
  50. package/src/image/runtime/ImageResultStorage.ts +198 -0
  51. package/src/image/types/ImagePlugin.ts +24 -0
  52. package/src/web/Plugin.ts +289 -125
  53. package/src/web/providers/ComputerUseBrowserProviderAdapter.ts +93 -0
  54. package/src/web/providers/PlaywrightBrowserProvider.ts +239 -0
  55. package/src/web/providers/SemanticBrowserProviderAdapter.ts +92 -0
  56. package/src/web/types/ComputerUseBrowserProviderAdapter.ts +58 -0
  57. package/src/web/types/PlaywrightBrowserProvider.ts +17 -0
  58. package/src/web/types/SemanticBrowserProviderAdapter.ts +30 -0
  59. package/src/web/types/WebPlugin.ts +236 -63
  60. package/src/web.ts +30 -8
  61. package/bin/web/PROMPT.d.ts +0 -7
  62. package/bin/web/PROMPT.d.ts.map +0 -1
  63. package/bin/web/PROMPT.js +0 -8
  64. package/bin/web/PROMPT.js.map +0 -1
  65. package/bin/web/WebPromptAssets.d.ts +0 -12
  66. package/bin/web/WebPromptAssets.d.ts.map +0 -1
  67. package/bin/web/WebPromptAssets.js +0 -13
  68. package/bin/web/WebPromptAssets.js.map +0 -1
  69. package/bin/web/runtime/Prompt.d.ts +0 -14
  70. package/bin/web/runtime/Prompt.d.ts.map +0 -1
  71. package/bin/web/runtime/Prompt.js +0 -77
  72. package/bin/web/runtime/Prompt.js.map +0 -1
  73. package/src/web/PROMPT.ts +0 -9
  74. package/src/web/PROMPT.ts.txt +0 -61
  75. package/src/web/WebPromptAssets.ts +0 -14
  76. package/src/web/runtime/Prompt.ts +0 -99
@@ -0,0 +1,239 @@
1
+ /**
2
+ * 基于 Playwright/CDP 的浏览器 provider。
3
+ *
4
+ * 关键点(中文)
5
+ * - provider 只连接外部已启动的 CDP 浏览器,不负责安装或启动 Chrome。
6
+ * - 每个 session 拥有独立 Page;连接与 session 生命周期统一由 provider 回收。
7
+ * - action 只暴露确定性 DOM 操作,AI 语义动作由后续 Stagehand adapter 承担。
8
+ */
9
+
10
+ import { randomUUID } from "node:crypto";
11
+ import { chromium } from "playwright-core";
12
+ import type { Browser, BrowserContext, Page } from "playwright-core";
13
+ import type {
14
+ BrowserActInput,
15
+ BrowserCloseSessionInput,
16
+ BrowserCreateSessionInput,
17
+ BrowserExtractInput,
18
+ BrowserExtractResult,
19
+ BrowserObservation,
20
+ BrowserObserveInput,
21
+ BrowserProvider,
22
+ } from "@/web/types/WebPlugin.js";
23
+ import type { PlaywrightBrowserProviderOptions } from "@/web/types/PlaywrightBrowserProvider.js";
24
+
25
+ const DEFAULT_URL = "about:blank";
26
+ const DEFAULT_TIMEOUT_MS = 30_000;
27
+ const DEFAULT_MAX_CHARS = 12_000;
28
+ const MAX_WAIT_MS = 60_000;
29
+
30
+ /** provider 内部持有的浏览器 session。 */
31
+ interface PlaywrightBrowserSession {
32
+ /** session 唯一标识。 */
33
+ session_id: string;
34
+ /** session 所在的浏览器上下文。 */
35
+ context: BrowserContext;
36
+ /** session 唯一拥有的页面。 */
37
+ page: Page;
38
+ }
39
+
40
+ /** 把字符上限归一化到安全范围。 */
41
+ function normalize_max_chars(value: number | undefined, fallback: number): number {
42
+ if (!Number.isFinite(value)) return fallback;
43
+ return Math.min(100_000, Math.max(1, Math.floor(value ?? fallback)));
44
+ }
45
+
46
+ /** 把超时归一化到安全范围。 */
47
+ function normalize_timeout_ms(value: number | undefined): number {
48
+ if (!Number.isFinite(value)) return DEFAULT_TIMEOUT_MS;
49
+ return Math.min(MAX_WAIT_MS, Math.max(1_000, Math.floor(value ?? DEFAULT_TIMEOUT_MS)));
50
+ }
51
+
52
+ /** 把错误转换成稳定文本。 */
53
+ function describe_error(error: unknown): string {
54
+ return error instanceof Error ? error.message : String(error);
55
+ }
56
+
57
+ /** 基于 Playwright/CDP 的浏览器 provider。 */
58
+ export class PlaywrightBrowserProvider implements BrowserProvider {
59
+ /** provider 稳定名称。 */
60
+ readonly name = "playwright-cdp";
61
+
62
+ /** 构造参数。 */
63
+ private readonly options: Required<PlaywrightBrowserProviderOptions>;
64
+
65
+ /** 当前 CDP 浏览器连接。 */
66
+ private browser: Browser | null = null;
67
+
68
+ /** 当前 provider 拥有的 session。 */
69
+ private readonly sessions = new Map<string, PlaywrightBrowserSession>();
70
+
71
+ /** provider 是否已释放。 */
72
+ private disposed = false;
73
+
74
+ constructor(options: PlaywrightBrowserProviderOptions) {
75
+ const cdp_url = String(options.cdp_url || "").trim();
76
+ if (!cdp_url) {
77
+ throw new TypeError("PlaywrightBrowserProvider requires cdp_url");
78
+ }
79
+ this.options = {
80
+ cdp_url,
81
+ default_url: String(options.default_url || DEFAULT_URL),
82
+ timeout_ms: normalize_timeout_ms(options.timeout_ms),
83
+ max_observation_chars: normalize_max_chars(
84
+ options.max_observation_chars,
85
+ DEFAULT_MAX_CHARS,
86
+ ),
87
+ };
88
+ }
89
+
90
+ /** 创建一个新的页面 session。 */
91
+ async create_session(
92
+ input: BrowserCreateSessionInput,
93
+ ): Promise<BrowserObservation> {
94
+ const browser = await this.get_browser();
95
+ const context = browser.contexts()[0] ?? await browser.newContext();
96
+ const page = await context.newPage();
97
+ page.setDefaultTimeout(this.options.timeout_ms);
98
+ page.setDefaultNavigationTimeout(this.options.timeout_ms);
99
+ const session_id = randomUUID();
100
+ const session = { session_id, context, page };
101
+ this.sessions.set(session_id, session);
102
+
103
+ try {
104
+ const url = String(input.url || this.options.default_url).trim();
105
+ if (url && url !== DEFAULT_URL) {
106
+ await page.goto(url, { waitUntil: "domcontentloaded" });
107
+ }
108
+ return await this.read_observation(session, input.include_screenshot);
109
+ } catch (error) {
110
+ await this.close_session({ session_id }).catch(() => undefined);
111
+ throw new Error(`Browser session creation failed: ${describe_error(error)}`);
112
+ }
113
+ }
114
+
115
+ /** 读取当前页面状态。 */
116
+ async observe(input: BrowserObserveInput): Promise<BrowserObservation> {
117
+ return await this.read_observation(
118
+ this.require_session(input.session_id),
119
+ input.include_screenshot,
120
+ );
121
+ }
122
+
123
+ /** 执行确定性浏览器动作。 */
124
+ async act(input: BrowserActInput): Promise<BrowserObservation> {
125
+ const session = this.require_session(input.session_id);
126
+ const { page } = session;
127
+ const action = input.action;
128
+
129
+ if (action.type === "goto") {
130
+ await page.goto(action.url, { waitUntil: "domcontentloaded" });
131
+ } else if (action.type === "click") {
132
+ await page.locator(action.selector).click();
133
+ } else if (action.type === "fill") {
134
+ await page.locator(action.selector).fill(action.value);
135
+ } else if (action.type === "press") {
136
+ await page.locator(action.selector).press(action.key);
137
+ } else if (action.type === "scroll") {
138
+ await page.evaluate(
139
+ ([x, y]) => globalThis.scrollBy(x, y),
140
+ [action.x ?? 0, action.y ?? 0] as [number, number],
141
+ );
142
+ } else {
143
+ const milliseconds = Math.min(
144
+ MAX_WAIT_MS,
145
+ Math.max(0, Math.floor(action.milliseconds ?? 500)),
146
+ );
147
+ await page.waitForTimeout(milliseconds);
148
+ }
149
+
150
+ return await this.read_observation(session, input.include_screenshot);
151
+ }
152
+
153
+ /** 从当前页面抽取文本。 */
154
+ async extract(input: BrowserExtractInput): Promise<BrowserExtractResult> {
155
+ const session = this.require_session(input.session_id);
156
+ const locator = input.selector
157
+ ? session.page.locator(input.selector)
158
+ : session.page.locator("body");
159
+ const content = (await locator.allTextContents()).join("\n");
160
+ const max_chars = normalize_max_chars(
161
+ input.max_chars,
162
+ this.options.max_observation_chars,
163
+ );
164
+ return {
165
+ provider: this.name,
166
+ session_id: session.session_id,
167
+ url: session.page.url(),
168
+ content: content.slice(0, max_chars),
169
+ };
170
+ }
171
+
172
+ /** 关闭指定 session 拥有的页面。 */
173
+ async close_session(input: BrowserCloseSessionInput): Promise<void> {
174
+ const session = this.sessions.get(input.session_id);
175
+ if (!session) return;
176
+ this.sessions.delete(input.session_id);
177
+ await session.page.close().catch(() => undefined);
178
+ }
179
+
180
+ /** 关闭所有页面和 CDP 连接。 */
181
+ async dispose(): Promise<void> {
182
+ if (this.disposed) return;
183
+ this.disposed = true;
184
+ const session_ids = [...this.sessions.keys()];
185
+ await Promise.all(
186
+ session_ids.map(async (session_id) => {
187
+ await this.close_session({ session_id });
188
+ }),
189
+ );
190
+ const browser = this.browser;
191
+ this.browser = null;
192
+ await browser?.close().catch(() => undefined);
193
+ }
194
+
195
+ /** 懒连接 CDP 浏览器。 */
196
+ private async get_browser(): Promise<Browser> {
197
+ if (this.disposed) {
198
+ throw new Error("PlaywrightBrowserProvider is disposed");
199
+ }
200
+ if (this.browser?.isConnected()) return this.browser;
201
+ this.browser = await chromium.connectOverCDP(this.options.cdp_url, {
202
+ timeout: this.options.timeout_ms,
203
+ });
204
+ this.browser.on("disconnected", () => {
205
+ this.browser = null;
206
+ this.sessions.clear();
207
+ });
208
+ return this.browser;
209
+ }
210
+
211
+ /** 读取 session,不存在时抛出稳定错误。 */
212
+ private require_session(session_id: string): PlaywrightBrowserSession {
213
+ const session = this.sessions.get(session_id);
214
+ if (!session) {
215
+ throw new Error(`Browser session not found: ${session_id}`);
216
+ }
217
+ return session;
218
+ }
219
+
220
+ /** 生成当前页面的模型友好观察结果。 */
221
+ private async read_observation(
222
+ session: PlaywrightBrowserSession,
223
+ include_screenshot: boolean | undefined,
224
+ ): Promise<BrowserObservation> {
225
+ const page = session.page;
226
+ const text = await page.locator("body").innerText().catch(() => "");
227
+ const screenshot_data_url = include_screenshot
228
+ ? `data:image/png;base64,${(await page.screenshot({ type: "png" })).toString("base64")}`
229
+ : undefined;
230
+ return {
231
+ provider: this.name,
232
+ session_id: session.session_id,
233
+ url: page.url(),
234
+ title: await page.title().catch(() => ""),
235
+ text: text.slice(0, this.options.max_observation_chars),
236
+ screenshot_data_url: screenshot_data_url ?? null,
237
+ };
238
+ }
239
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * 在现有 BrowserProvider 上组合语义动作能力。
3
+ *
4
+ * 关键点(中文)
5
+ * - 基础 provider 仍是 session 和浏览器资源的唯一拥有者。
6
+ * - adapter 只解释自然语言动作和抽取,不复制 session 状态。
7
+ * - Stagehand、Computer Use 与其他模型可以共享同一个公开协议。
8
+ */
9
+
10
+ import type {
11
+ BrowserActInput,
12
+ BrowserCloseSessionInput,
13
+ BrowserCreateSessionInput,
14
+ BrowserExtractInput,
15
+ BrowserObservation,
16
+ BrowserObserveInput,
17
+ BrowserProvider,
18
+ BrowserSemanticActInput,
19
+ BrowserSemanticExtractInput,
20
+ BrowserExtractResult,
21
+ } from "@/web/types/WebPlugin.js";
22
+ import type { SemanticBrowserProviderAdapterOptions } from "@/web/types/SemanticBrowserProviderAdapter.js";
23
+
24
+ /** 为基础浏览器组合语义动作回调。 */
25
+ export class SemanticBrowserProviderAdapter implements BrowserProvider {
26
+ /** adapter 稳定名称。 */
27
+ readonly name: string;
28
+
29
+ /** 拥有 session 的基础浏览器 provider。 */
30
+ private readonly browser: BrowserProvider;
31
+
32
+ /** 自然语言动作回调。 */
33
+ private readonly semantic_act_callback: SemanticBrowserProviderAdapterOptions["semantic_act"];
34
+
35
+ /** 自然语言抽取回调。 */
36
+ private readonly semantic_extract_callback: SemanticBrowserProviderAdapterOptions["semantic_extract"];
37
+
38
+ constructor(options: SemanticBrowserProviderAdapterOptions) {
39
+ const name = String(options.name || "").trim();
40
+ if (!name) throw new TypeError("SemanticBrowserProviderAdapter requires name");
41
+ this.name = name;
42
+ this.browser = options.browser;
43
+ this.semantic_act_callback = options.semantic_act;
44
+ this.semantic_extract_callback = options.semantic_extract;
45
+ }
46
+
47
+ /** 委托基础 provider 创建 session。 */
48
+ async create_session(
49
+ input: BrowserCreateSessionInput,
50
+ ): Promise<BrowserObservation> {
51
+ return await this.browser.create_session(input);
52
+ }
53
+
54
+ /** 委托基础 provider 观察页面。 */
55
+ async observe(input: BrowserObserveInput): Promise<BrowserObservation> {
56
+ return await this.browser.observe(input);
57
+ }
58
+
59
+ /** 委托基础 provider 执行确定性动作。 */
60
+ async act(input: BrowserActInput): Promise<BrowserObservation> {
61
+ return await this.browser.act(input);
62
+ }
63
+
64
+ /** 执行自然语言浏览器动作。 */
65
+ async semantic_act(
66
+ input: BrowserSemanticActInput,
67
+ ): Promise<BrowserObservation> {
68
+ return await this.semantic_act_callback(input);
69
+ }
70
+
71
+ /** 委托基础 provider 抽取确定性文本。 */
72
+ async extract(input: BrowserExtractInput): Promise<BrowserExtractResult> {
73
+ return await this.browser.extract(input);
74
+ }
75
+
76
+ /** 执行自然语言页面抽取。 */
77
+ async semantic_extract(
78
+ input: BrowserSemanticExtractInput,
79
+ ): Promise<BrowserExtractResult> {
80
+ return await this.semantic_extract_callback(input);
81
+ }
82
+
83
+ /** 委托基础 provider 关闭 session。 */
84
+ async close_session(input: BrowserCloseSessionInput): Promise<void> {
85
+ await this.browser.close_session(input);
86
+ }
87
+
88
+ /** 委托基础 provider 释放全部浏览器资源。 */
89
+ async dispose(): Promise<void> {
90
+ await this.browser.dispose();
91
+ }
92
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Computer Use 浏览器 adapter 类型。
3
+ *
4
+ * 关键点(中文):模型只通过 run 回调决定下一步动作,浏览器 session 仍由基础
5
+ * BrowserProvider 拥有;OpenAI、Anthropic、Gemini 或自建模型都可以实现该回调。
6
+ */
7
+
8
+ import type {
9
+ BrowserActInput,
10
+ BrowserCloseSessionInput,
11
+ BrowserCreateSessionInput,
12
+ BrowserExtractInput,
13
+ BrowserExtractResult,
14
+ BrowserObservation,
15
+ BrowserObserveInput,
16
+ BrowserProvider,
17
+ BrowserSemanticActInput,
18
+ } from "@/web/types/WebPlugin.js";
19
+
20
+ /** Computer Use 模型回调收到的输入。 */
21
+ export interface ComputerUseBrowserRunInput {
22
+ /** 用户或 Agent 给出的高层目标。 */
23
+ instruction: string;
24
+ /** 当前浏览器 session 标识。 */
25
+ session_id: string;
26
+ /** 当前页面截图和文本观察结果。 */
27
+ observation: BrowserObservation;
28
+ }
29
+
30
+ /** Computer Use adapter 构造参数。 */
31
+ export interface ComputerUseBrowserProviderAdapterOptions {
32
+ /** adapter 稳定名称,例如 `openai-computer-use`。 */
33
+ name: string;
34
+ /** 拥有浏览器连接和 session 的基础 provider。 */
35
+ browser: BrowserProvider;
36
+ /** 执行一次 Computer Use 模型循环的回调。 */
37
+ run: (
38
+ input: ComputerUseBrowserRunInput,
39
+ ) => Promise<BrowserObservation> | BrowserObservation;
40
+ }
41
+
42
+ /** Computer Use adapter 的内部委托接口。 */
43
+ export type ComputerUseBrowserProviderMethods = {
44
+ /** 创建浏览器 session。 */
45
+ create_session(input: BrowserCreateSessionInput): Promise<BrowserObservation>;
46
+ /** 观察浏览器页面。 */
47
+ observe(input: BrowserObserveInput): Promise<BrowserObservation>;
48
+ /** 执行确定性浏览器动作。 */
49
+ act(input: BrowserActInput): Promise<BrowserObservation>;
50
+ /** 抽取页面文本。 */
51
+ extract(input: BrowserExtractInput): Promise<BrowserExtractResult>;
52
+ /** 关闭浏览器 session。 */
53
+ close_session(input: BrowserCloseSessionInput): Promise<void>;
54
+ /** 释放浏览器资源。 */
55
+ dispose(): Promise<void>;
56
+ /** 执行 Computer Use 语义动作。 */
57
+ semantic_act(input: BrowserSemanticActInput): Promise<BrowserObservation>;
58
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * PlaywrightBrowserProvider 构造参数类型。
3
+ *
4
+ * 关键点(中文):只描述连接策略,不把 Playwright 运行时对象放入公开 action 协议。
5
+ */
6
+
7
+ /** Playwright CDP 浏览器 provider 构造参数。 */
8
+ export interface PlaywrightBrowserProviderOptions {
9
+ /** Chrome DevTools Protocol 地址,例如 `http://127.0.0.1:9222`。 */
10
+ cdp_url: string;
11
+ /** 创建 session 后未提供 URL 时打开的默认页面。 */
12
+ default_url?: string;
13
+ /** 页面导航、点击和填充的默认超时毫秒数。 */
14
+ timeout_ms?: number;
15
+ /** 页面观察默认返回的最大字符数。 */
16
+ max_observation_chars?: number;
17
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 语义浏览器 adapter 构造参数。
3
+ *
4
+ * 关键点(中文):Stagehand、Computer Use 或其他模型只需要实现两个回调,
5
+ * 浏览器 session 和确定性动作仍由基础 BrowserProvider 统一拥有。
6
+ */
7
+
8
+ import type {
9
+ BrowserObservation,
10
+ BrowserProvider,
11
+ BrowserSemanticActInput,
12
+ BrowserSemanticExtractInput,
13
+ BrowserExtractResult,
14
+ } from "@/web/types/WebPlugin.js";
15
+
16
+ /** 语义浏览器 adapter 构造参数。 */
17
+ export interface SemanticBrowserProviderAdapterOptions {
18
+ /** adapter 稳定名称,例如 `stagehand` 或 `computer-use`。 */
19
+ name: string;
20
+ /** 拥有浏览器连接和 session 的基础 provider。 */
21
+ browser: BrowserProvider;
22
+ /** 执行自然语言浏览器动作的回调。 */
23
+ semantic_act: (
24
+ input: BrowserSemanticActInput,
25
+ ) => Promise<BrowserObservation> | BrowserObservation;
26
+ /** 执行自然语言页面抽取的回调。 */
27
+ semantic_extract: (
28
+ input: BrowserSemanticExtractInput,
29
+ ) => Promise<BrowserExtractResult> | BrowserExtractResult;
30
+ }