pi-agent-browser-native 0.2.48 → 0.2.50
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.
- package/CHANGELOG.md +27 -1
- package/README.md +21 -11
- package/dist/extensions/agent-browser/index.js +808 -0
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
- package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
- package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
- package/dist/extensions/agent-browser/lib/config-policy.js +669 -0
- package/dist/extensions/agent-browser/lib/config.js +122 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
- package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
- package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
- package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
- package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
- package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
- package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
- package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
- package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
- package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
- package/dist/extensions/agent-browser/lib/playbook.js +121 -0
- package/dist/extensions/agent-browser/lib/process.js +363 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
- package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
- package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
- package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
- package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
- package/dist/extensions/agent-browser/lib/results/network.js +73 -0
- package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +956 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
- package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
- package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
- package/dist/extensions/agent-browser/lib/results/text.js +40 -0
- package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
- package/dist/extensions/agent-browser/lib/runtime.js +855 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
- package/dist/extensions/agent-browser/lib/temp.js +498 -0
- package/dist/extensions/agent-browser/lib/web-search.js +562 -0
- package/docs/ARCHITECTURE.md +5 -5
- package/docs/COMMAND_REFERENCE.md +4 -4
- package/docs/RELEASE.md +22 -11
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +5 -4
- package/docs/TOOL_CONTRACT.md +1 -1
- package/package.json +9 -5
- package/scripts/config.mjs +14 -20
- package/scripts/doctor.mjs +8 -7
- package/extensions/agent-browser/index.ts +0 -961
- package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
- package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
- package/extensions/agent-browser/lib/bash-guard.ts +0 -205
- package/extensions/agent-browser/lib/command-policy.ts +0 -71
- package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
- package/extensions/agent-browser/lib/config-policy.js +0 -690
- package/extensions/agent-browser/lib/config.ts +0 -211
- package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
- package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
- package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
- package/extensions/agent-browser/lib/electron/launch.ts +0 -499
- package/extensions/agent-browser/lib/executable-path.ts +0 -19
- package/extensions/agent-browser/lib/fs-utils.ts +0 -18
- package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
- package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
- package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
- package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
- package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
- package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
- package/extensions/agent-browser/lib/input-modes.ts +0 -45
- package/extensions/agent-browser/lib/json-schema.ts +0 -73
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
- package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
- package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
- package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
- package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
- package/extensions/agent-browser/lib/playbook.ts +0 -142
- package/extensions/agent-browser/lib/process.ts +0 -516
- package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
- package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
- package/extensions/agent-browser/lib/results/categories.ts +0 -106
- package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
- package/extensions/agent-browser/lib/results/contracts.ts +0 -241
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
- package/extensions/agent-browser/lib/results/envelope.ts +0 -195
- package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
- package/extensions/agent-browser/lib/results/network.ts +0 -78
- package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
- package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
- package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
- package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
- package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
- package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
- package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
- package/extensions/agent-browser/lib/results/presentation.ts +0 -257
- package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
- package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
- package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
- package/extensions/agent-browser/lib/results/text.ts +0 -40
- package/extensions/agent-browser/lib/runtime.ts +0 -988
- package/extensions/agent-browser/lib/session-page-state.ts +0 -512
- package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
- package/extensions/agent-browser/lib/temp.ts +0 -577
- package/extensions/agent-browser/lib/web-search.ts +0 -728
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose: Provide the optional provider-backed `agent_browser_web_search` companion tool.
|
|
3
|
+
* Responsibilities: Define strict search input schema, resolve configured Brave/Exa credentials lazily, call the selected search API with cancellation/timeout, normalize compact results, and keep secrets out of content/details.
|
|
4
|
+
* Scope: Live web search only; browser automation remains in the `agent_browser` tool.
|
|
5
|
+
*/
|
|
6
|
+
import { JsonSchema } from "./json-schema.js";
|
|
7
|
+
import { StringEnum as localStringEnum } from "./string-enum-schema.js";
|
|
8
|
+
import { DEFAULT_WEB_SEARCH_PROVIDER, WEB_SEARCH_PROVIDERS, resolvePreferredWebSearchCredential, } from "./config.js";
|
|
9
|
+
export const AGENT_BROWSER_WEB_SEARCH_TOOL_NAME = "agent_browser_web_search";
|
|
10
|
+
export const BRAVE_SEARCH_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
|
|
11
|
+
export const EXA_SEARCH_ENDPOINT = "https://api.exa.ai/search";
|
|
12
|
+
export const DEFAULT_SEARCH_RESULT_COUNT = 5;
|
|
13
|
+
export const MAX_SEARCH_RESULT_COUNT = 10;
|
|
14
|
+
export const SEARCH_REQUEST_TIMEOUT_MS = 15_000;
|
|
15
|
+
export const EXA_DEEP_SEARCH_REQUEST_TIMEOUT_MS = 45_000;
|
|
16
|
+
export const WEB_SEARCH_MIN_REQUEST_INTERVAL_MS = 1_100;
|
|
17
|
+
export const EXA_SEARCH_TYPES = ["auto", "fast", "instant", "deep-lite", "deep", "deep-reasoning"];
|
|
18
|
+
export const WEB_SEARCH_PROVIDER_PARAM_VALUES = ["auto", ...WEB_SEARCH_PROVIDERS];
|
|
19
|
+
export function createAgentBrowserWebSearchParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
20
|
+
return Type.Object({
|
|
21
|
+
query: Type.String({
|
|
22
|
+
minLength: 1,
|
|
23
|
+
description: "Search query to run with the configured Exa or Brave web search provider.",
|
|
24
|
+
}),
|
|
25
|
+
provider: Type.Optional(StringEnum(WEB_SEARCH_PROVIDER_PARAM_VALUES, {
|
|
26
|
+
description: `Optional provider override. auto uses configured keys and preferredProvider; when both Exa and Brave are available, the default preferred provider is ${DEFAULT_WEB_SEARCH_PROVIDER}.`,
|
|
27
|
+
})),
|
|
28
|
+
searchType: Type.Optional(StringEnum(EXA_SEARCH_TYPES, {
|
|
29
|
+
description: "Optional Exa search type. Defaults to auto; ignored by Brave. Use deep/deep-reasoning only for harder research because they are slower.",
|
|
30
|
+
})),
|
|
31
|
+
count: Type.Optional(Type.Integer({
|
|
32
|
+
minimum: 1,
|
|
33
|
+
maximum: MAX_SEARCH_RESULT_COUNT,
|
|
34
|
+
description: `Number of web results to return. Defaults to ${DEFAULT_SEARCH_RESULT_COUNT}; max ${MAX_SEARCH_RESULT_COUNT}.`,
|
|
35
|
+
})),
|
|
36
|
+
offset: Type.Optional(Type.Integer({
|
|
37
|
+
minimum: 0,
|
|
38
|
+
maximum: 9,
|
|
39
|
+
description: "Zero-based result offset for pagination. Defaults to 0.",
|
|
40
|
+
})),
|
|
41
|
+
country: Type.Optional(Type.String({
|
|
42
|
+
pattern: "^[A-Za-z]{2}$",
|
|
43
|
+
description: "Optional 2-letter country code, such as US or GB.",
|
|
44
|
+
})),
|
|
45
|
+
searchLang: Type.Optional(Type.String({
|
|
46
|
+
minLength: 2,
|
|
47
|
+
maxLength: 8,
|
|
48
|
+
description: "Optional Brave search language code, such as en or en-US.",
|
|
49
|
+
})),
|
|
50
|
+
safesearch: Type.Optional(StringEnum(["off", "moderate", "strict"], {
|
|
51
|
+
description: "Optional search safety setting. Brave forwards this as safesearch; Exa maps moderate/strict to moderation=true.",
|
|
52
|
+
})),
|
|
53
|
+
freshness: Type.Optional(StringEnum(["pd", "pw", "pm", "py"], {
|
|
54
|
+
description: "Optional freshness window: pd=past day, pw=past week, pm=past month, py=past year.",
|
|
55
|
+
})),
|
|
56
|
+
}, { additionalProperties: false });
|
|
57
|
+
}
|
|
58
|
+
export const AgentBrowserWebSearchParams = createAgentBrowserWebSearchParamsSchema();
|
|
59
|
+
const HTML_ENTITY_REPLACEMENTS = {
|
|
60
|
+
amp: "&",
|
|
61
|
+
apos: "'",
|
|
62
|
+
gt: ">",
|
|
63
|
+
lt: "<",
|
|
64
|
+
nbsp: " ",
|
|
65
|
+
quot: '"',
|
|
66
|
+
};
|
|
67
|
+
const HTML_TAG_NAMES_TO_STRIP = new Set([
|
|
68
|
+
"a",
|
|
69
|
+
"abbr",
|
|
70
|
+
"address",
|
|
71
|
+
"article",
|
|
72
|
+
"aside",
|
|
73
|
+
"audio",
|
|
74
|
+
"b",
|
|
75
|
+
"base",
|
|
76
|
+
"blockquote",
|
|
77
|
+
"body",
|
|
78
|
+
"br",
|
|
79
|
+
"button",
|
|
80
|
+
"canvas",
|
|
81
|
+
"code",
|
|
82
|
+
"div",
|
|
83
|
+
"em",
|
|
84
|
+
"embed",
|
|
85
|
+
"footer",
|
|
86
|
+
"form",
|
|
87
|
+
"h1",
|
|
88
|
+
"h2",
|
|
89
|
+
"h3",
|
|
90
|
+
"h4",
|
|
91
|
+
"h5",
|
|
92
|
+
"h6",
|
|
93
|
+
"head",
|
|
94
|
+
"header",
|
|
95
|
+
"html",
|
|
96
|
+
"i",
|
|
97
|
+
"iframe",
|
|
98
|
+
"img",
|
|
99
|
+
"input",
|
|
100
|
+
"li",
|
|
101
|
+
"link",
|
|
102
|
+
"main",
|
|
103
|
+
"mark",
|
|
104
|
+
"math",
|
|
105
|
+
"meta",
|
|
106
|
+
"nav",
|
|
107
|
+
"object",
|
|
108
|
+
"ol",
|
|
109
|
+
"option",
|
|
110
|
+
"p",
|
|
111
|
+
"pre",
|
|
112
|
+
"script",
|
|
113
|
+
"section",
|
|
114
|
+
"select",
|
|
115
|
+
"source",
|
|
116
|
+
"span",
|
|
117
|
+
"strong",
|
|
118
|
+
"style",
|
|
119
|
+
"svg",
|
|
120
|
+
"table",
|
|
121
|
+
"tbody",
|
|
122
|
+
"td",
|
|
123
|
+
"textarea",
|
|
124
|
+
"tfoot",
|
|
125
|
+
"th",
|
|
126
|
+
"thead",
|
|
127
|
+
"tr",
|
|
128
|
+
"u",
|
|
129
|
+
"ul",
|
|
130
|
+
"video",
|
|
131
|
+
]);
|
|
132
|
+
function decodeHtmlEntity(entity) {
|
|
133
|
+
const named = HTML_ENTITY_REPLACEMENTS[entity.toLowerCase()];
|
|
134
|
+
if (named !== undefined)
|
|
135
|
+
return named;
|
|
136
|
+
const decimalMatch = /^#(\d+)$/.exec(entity);
|
|
137
|
+
const hexMatch = /^#x([0-9a-f]+)$/i.exec(entity);
|
|
138
|
+
const codePoint = decimalMatch ? Number.parseInt(decimalMatch[1] ?? "", 10) : hexMatch ? Number.parseInt(hexMatch[1] ?? "", 16) : undefined;
|
|
139
|
+
if (codePoint === undefined || !Number.isFinite(codePoint))
|
|
140
|
+
return `&${entity};`;
|
|
141
|
+
try {
|
|
142
|
+
return String.fromCodePoint(codePoint);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return `&${entity};`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export function decodeHtmlEntities(value) {
|
|
149
|
+
return value.replace(/&([a-z][a-z0-9]+|#\d+|#x[0-9a-f]+);/gi, (_match, entity) => decodeHtmlEntity(entity));
|
|
150
|
+
}
|
|
151
|
+
function stripDecodedHtmlTags(value) {
|
|
152
|
+
return value.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, " ").replace(/<\/?([a-z][a-z0-9-]*)(\s[^>]*)?>/gi, (match, tagName, attributes) => {
|
|
153
|
+
if (attributes || match.startsWith("</") || HTML_TAG_NAMES_TO_STRIP.has(tagName.toLowerCase()))
|
|
154
|
+
return " ";
|
|
155
|
+
return match;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
export function cleanSearchText(value, maxLength = 500) {
|
|
159
|
+
if (typeof value !== "string")
|
|
160
|
+
return undefined;
|
|
161
|
+
const cleaned = stripDecodedHtmlTags(decodeHtmlEntities(value.replace(/<[^>]*>/g, " ")))
|
|
162
|
+
.replace(/\s+/g, " ")
|
|
163
|
+
.trim();
|
|
164
|
+
if (!cleaned)
|
|
165
|
+
return undefined;
|
|
166
|
+
if (cleaned.length <= maxLength)
|
|
167
|
+
return cleaned;
|
|
168
|
+
return `${cleaned.slice(0, Math.max(0, maxLength - 1)).trimEnd()}…`;
|
|
169
|
+
}
|
|
170
|
+
export function normalizeSearchUrl(value) {
|
|
171
|
+
if (typeof value !== "string")
|
|
172
|
+
return undefined;
|
|
173
|
+
try {
|
|
174
|
+
const url = new URL(value);
|
|
175
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
176
|
+
return undefined;
|
|
177
|
+
return url.toString();
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function getHostname(url) {
|
|
184
|
+
try {
|
|
185
|
+
return new URL(url).hostname;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function normalizeHighlightList(value) {
|
|
192
|
+
if (!Array.isArray(value))
|
|
193
|
+
return undefined;
|
|
194
|
+
const highlights = value
|
|
195
|
+
.map((entry) => cleanSearchText(entry, 320))
|
|
196
|
+
.filter((entry) => Boolean(entry))
|
|
197
|
+
.slice(0, 3);
|
|
198
|
+
return highlights.length > 0 ? highlights : undefined;
|
|
199
|
+
}
|
|
200
|
+
export function normalizeBraveSearchResult(result) {
|
|
201
|
+
const title = cleanSearchText(result.title, 180);
|
|
202
|
+
const url = normalizeSearchUrl(result.url);
|
|
203
|
+
if (!title || !url)
|
|
204
|
+
return undefined;
|
|
205
|
+
return {
|
|
206
|
+
title,
|
|
207
|
+
url,
|
|
208
|
+
description: cleanSearchText(result.description, 320),
|
|
209
|
+
source: cleanSearchText(result.profile?.name, 120) ?? cleanSearchText(result.meta_url?.hostname, 120),
|
|
210
|
+
age: cleanSearchText(result.age, 80),
|
|
211
|
+
language: cleanSearchText(result.language, 40),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export function normalizeExaSearchResult(result) {
|
|
215
|
+
const title = cleanSearchText(result.title, 180);
|
|
216
|
+
const url = normalizeSearchUrl(result.url);
|
|
217
|
+
if (!title || !url)
|
|
218
|
+
return undefined;
|
|
219
|
+
const highlights = normalizeHighlightList(result.highlights);
|
|
220
|
+
return {
|
|
221
|
+
title,
|
|
222
|
+
url,
|
|
223
|
+
description: cleanSearchText(result.summary, 320) ?? highlights?.[0] ?? cleanSearchText(result.text, 320),
|
|
224
|
+
highlights,
|
|
225
|
+
source: cleanSearchText(result.author, 120) ?? cleanSearchText(getHostname(url), 120),
|
|
226
|
+
age: cleanSearchText(result.publishedDate, 80),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function getProviderLabel(provider) {
|
|
230
|
+
return provider === "exa" ? "Exa" : "Brave";
|
|
231
|
+
}
|
|
232
|
+
export function formatSearchResults(provider, query, results) {
|
|
233
|
+
const providerLabel = getProviderLabel(provider);
|
|
234
|
+
if (results.length === 0) {
|
|
235
|
+
return `No ${providerLabel} web results found for: ${query}`;
|
|
236
|
+
}
|
|
237
|
+
const lines = [`${providerLabel} web search results for: ${query}`, ""];
|
|
238
|
+
results.forEach((result, index) => {
|
|
239
|
+
lines.push(`${index + 1}. ${result.title}`);
|
|
240
|
+
lines.push(` URL: ${result.url}`);
|
|
241
|
+
if (result.source)
|
|
242
|
+
lines.push(` Source: ${result.source}`);
|
|
243
|
+
if (result.age)
|
|
244
|
+
lines.push(` Age: ${result.age}`);
|
|
245
|
+
if (result.description)
|
|
246
|
+
lines.push(` Summary: ${result.description}`);
|
|
247
|
+
if (result.highlights && result.highlights.length > 1) {
|
|
248
|
+
lines.push(" Highlights:");
|
|
249
|
+
for (const highlight of result.highlights)
|
|
250
|
+
lines.push(` - ${highlight}`);
|
|
251
|
+
}
|
|
252
|
+
lines.push("");
|
|
253
|
+
});
|
|
254
|
+
return lines.join("\n").trimEnd();
|
|
255
|
+
}
|
|
256
|
+
export function buildBraveSearchUrl(params) {
|
|
257
|
+
const url = new URL(BRAVE_SEARCH_ENDPOINT);
|
|
258
|
+
url.searchParams.set("q", params.query);
|
|
259
|
+
url.searchParams.set("count", String(params.count));
|
|
260
|
+
url.searchParams.set("offset", String(params.offset));
|
|
261
|
+
if (params.country)
|
|
262
|
+
url.searchParams.set("country", params.country.toUpperCase());
|
|
263
|
+
if (params.searchLang)
|
|
264
|
+
url.searchParams.set("search_lang", params.searchLang);
|
|
265
|
+
if (params.safesearch)
|
|
266
|
+
url.searchParams.set("safesearch", params.safesearch);
|
|
267
|
+
if (params.freshness)
|
|
268
|
+
url.searchParams.set("freshness", params.freshness);
|
|
269
|
+
return url;
|
|
270
|
+
}
|
|
271
|
+
const FRESHNESS_DAYS = {
|
|
272
|
+
pd: 1,
|
|
273
|
+
pw: 7,
|
|
274
|
+
pm: 31,
|
|
275
|
+
py: 365,
|
|
276
|
+
};
|
|
277
|
+
function getStartPublishedDate(freshness, now) {
|
|
278
|
+
if (!freshness)
|
|
279
|
+
return undefined;
|
|
280
|
+
const days = FRESHNESS_DAYS[freshness];
|
|
281
|
+
return new Date(now().getTime() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
282
|
+
}
|
|
283
|
+
export function buildExaSearchRequestBody(params, now = () => new Date()) {
|
|
284
|
+
const body = {
|
|
285
|
+
query: params.query,
|
|
286
|
+
type: params.searchType ?? "auto",
|
|
287
|
+
numResults: Math.min(params.count + params.offset, 100),
|
|
288
|
+
contents: { highlights: true },
|
|
289
|
+
};
|
|
290
|
+
if (params.country)
|
|
291
|
+
body.userLocation = params.country.toUpperCase();
|
|
292
|
+
if (params.safesearch && params.safesearch !== "off")
|
|
293
|
+
body.moderation = true;
|
|
294
|
+
const startPublishedDate = getStartPublishedDate(params.freshness, now);
|
|
295
|
+
if (startPublishedDate)
|
|
296
|
+
body.startPublishedDate = startPublishedDate;
|
|
297
|
+
return body;
|
|
298
|
+
}
|
|
299
|
+
function redactSearchSecret(text, apiKey) {
|
|
300
|
+
return apiKey ? text.split(apiKey).join("[REDACTED]") : text;
|
|
301
|
+
}
|
|
302
|
+
function sleepWithAbort(ms, signal) {
|
|
303
|
+
if (ms <= 0)
|
|
304
|
+
return Promise.resolve();
|
|
305
|
+
if (signal?.aborted)
|
|
306
|
+
return Promise.reject(signal.reason ?? new Error("Web search cancelled"));
|
|
307
|
+
return new Promise((resolve, reject) => {
|
|
308
|
+
const cleanup = () => signal?.removeEventListener("abort", abort);
|
|
309
|
+
const timeout = setTimeout(() => {
|
|
310
|
+
cleanup();
|
|
311
|
+
resolve();
|
|
312
|
+
}, ms);
|
|
313
|
+
const abort = () => {
|
|
314
|
+
clearTimeout(timeout);
|
|
315
|
+
cleanup();
|
|
316
|
+
reject(signal?.reason ?? new Error("Web search cancelled"));
|
|
317
|
+
};
|
|
318
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
export class WebSearchRequestGate {
|
|
322
|
+
now;
|
|
323
|
+
sleep;
|
|
324
|
+
lastRequestStartedAt = 0;
|
|
325
|
+
tail = Promise.resolve();
|
|
326
|
+
constructor(now = Date.now, sleep = sleepWithAbort) {
|
|
327
|
+
this.now = now;
|
|
328
|
+
this.sleep = sleep;
|
|
329
|
+
}
|
|
330
|
+
run(signal, task) {
|
|
331
|
+
const runTask = async () => {
|
|
332
|
+
const elapsedMs = this.lastRequestStartedAt === 0 ? WEB_SEARCH_MIN_REQUEST_INTERVAL_MS : this.now() - this.lastRequestStartedAt;
|
|
333
|
+
const waitMs = Math.max(0, WEB_SEARCH_MIN_REQUEST_INTERVAL_MS - elapsedMs);
|
|
334
|
+
if (waitMs > 0)
|
|
335
|
+
await this.sleep(waitMs, signal);
|
|
336
|
+
if (signal?.aborted)
|
|
337
|
+
throw signal.reason ?? new Error("Web search cancelled");
|
|
338
|
+
this.lastRequestStartedAt = this.now();
|
|
339
|
+
return task();
|
|
340
|
+
};
|
|
341
|
+
const result = this.tail.then(runTask, runTask);
|
|
342
|
+
this.tail = result.catch(() => undefined);
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function formatSearchHttpError(provider, status, statusText, body, apiKey) {
|
|
347
|
+
const providerLabel = getProviderLabel(provider);
|
|
348
|
+
const errorPreview = cleanSearchText(redactSearchSecret(body, apiKey), 300);
|
|
349
|
+
if (status === 429) {
|
|
350
|
+
const preview = errorPreview ? ` Upstream details: ${redactSearchSecret(errorPreview, apiKey)}` : "";
|
|
351
|
+
return `${providerLabel} search rate limit exceeded (HTTP 429). Do not issue parallel or repeated agent_browser_web_search calls; use one high-signal query, inspect those results, then wait before retrying or ask the user to adjust their ${providerLabel} API plan/limits.${preview}`;
|
|
352
|
+
}
|
|
353
|
+
return `${providerLabel} search failed with HTTP ${status}: ${errorPreview ? redactSearchSecret(errorPreview, apiKey) : statusText}`;
|
|
354
|
+
}
|
|
355
|
+
async function fetchSearchJson(options) {
|
|
356
|
+
if (options.signal?.aborted) {
|
|
357
|
+
throw options.signal.reason ?? new Error(options.cancelMessage);
|
|
358
|
+
}
|
|
359
|
+
const controller = new AbortController();
|
|
360
|
+
const timeout = setTimeout(() => controller.abort(new Error(options.timeoutMessage)), options.timeoutMs);
|
|
361
|
+
const abort = () => controller.abort(options.signal?.reason ?? new Error(options.cancelMessage));
|
|
362
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
363
|
+
try {
|
|
364
|
+
const response = await fetch(options.request, {
|
|
365
|
+
...(options.init ?? {}),
|
|
366
|
+
signal: controller.signal,
|
|
367
|
+
});
|
|
368
|
+
const text = await response.text();
|
|
369
|
+
if (!response.ok) {
|
|
370
|
+
throw new Error(formatSearchHttpError(options.provider, response.status, response.statusText, text, options.apiKey));
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
return JSON.parse(text);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
throw new Error(`${options.invalidJsonMessage}: ${error instanceof Error ? error.message : String(error)}`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
clearTimeout(timeout);
|
|
381
|
+
options.signal?.removeEventListener("abort", abort);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
export async function fetchBraveSearchJson(url, apiKey, signal) {
|
|
385
|
+
return fetchSearchJson({
|
|
386
|
+
apiKey,
|
|
387
|
+
cancelMessage: "Brave search cancelled",
|
|
388
|
+
init: {
|
|
389
|
+
headers: {
|
|
390
|
+
Accept: "application/json",
|
|
391
|
+
"X-Subscription-Token": apiKey,
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
invalidJsonMessage: "Brave search returned invalid JSON",
|
|
395
|
+
provider: "brave",
|
|
396
|
+
request: url,
|
|
397
|
+
signal,
|
|
398
|
+
timeoutMessage: "Brave search timed out",
|
|
399
|
+
timeoutMs: SEARCH_REQUEST_TIMEOUT_MS,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function getExaRequestTimeoutMs(searchType) {
|
|
403
|
+
return searchType?.startsWith("deep") ? EXA_DEEP_SEARCH_REQUEST_TIMEOUT_MS : SEARCH_REQUEST_TIMEOUT_MS;
|
|
404
|
+
}
|
|
405
|
+
export async function fetchExaSearchJson(body, apiKey, signal, timeoutMs = SEARCH_REQUEST_TIMEOUT_MS) {
|
|
406
|
+
return fetchSearchJson({
|
|
407
|
+
apiKey,
|
|
408
|
+
cancelMessage: "Exa search cancelled",
|
|
409
|
+
init: {
|
|
410
|
+
body: JSON.stringify(body),
|
|
411
|
+
headers: {
|
|
412
|
+
Accept: "application/json",
|
|
413
|
+
"Content-Type": "application/json",
|
|
414
|
+
"x-api-key": apiKey,
|
|
415
|
+
},
|
|
416
|
+
method: "POST",
|
|
417
|
+
},
|
|
418
|
+
invalidJsonMessage: "Exa search returned invalid JSON",
|
|
419
|
+
provider: "exa",
|
|
420
|
+
request: EXA_SEARCH_ENDPOINT,
|
|
421
|
+
signal,
|
|
422
|
+
timeoutMessage: "Exa search timed out",
|
|
423
|
+
timeoutMs,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
const BRAVE_WEB_SEARCH_ADAPTER = {
|
|
427
|
+
provider: "brave",
|
|
428
|
+
buildRequest(params) {
|
|
429
|
+
return buildBraveSearchUrl({
|
|
430
|
+
query: params.query,
|
|
431
|
+
count: params.count,
|
|
432
|
+
offset: params.offset,
|
|
433
|
+
country: params.country,
|
|
434
|
+
searchLang: params.searchLang,
|
|
435
|
+
safesearch: params.safesearch,
|
|
436
|
+
freshness: params.freshness,
|
|
437
|
+
});
|
|
438
|
+
},
|
|
439
|
+
fetchJson(request, apiKey, signal) {
|
|
440
|
+
return fetchBraveSearchJson(request, apiKey, signal);
|
|
441
|
+
},
|
|
442
|
+
normalizeResponse(response, params) {
|
|
443
|
+
return {
|
|
444
|
+
results: (response.web?.results ?? [])
|
|
445
|
+
.map(normalizeBraveSearchResult)
|
|
446
|
+
.filter((result) => Boolean(result)),
|
|
447
|
+
returnedQuery: cleanSearchText(response.query?.altered, 300) ?? cleanSearchText(response.query?.original, 300) ?? params.query,
|
|
448
|
+
};
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
const EXA_WEB_SEARCH_ADAPTER = {
|
|
452
|
+
provider: "exa",
|
|
453
|
+
buildRequest(params) {
|
|
454
|
+
const searchType = params.searchType ?? "auto";
|
|
455
|
+
return {
|
|
456
|
+
body: buildExaSearchRequestBody({
|
|
457
|
+
query: params.query,
|
|
458
|
+
count: params.count,
|
|
459
|
+
offset: params.offset,
|
|
460
|
+
country: params.country,
|
|
461
|
+
safesearch: params.safesearch,
|
|
462
|
+
freshness: params.freshness,
|
|
463
|
+
searchType,
|
|
464
|
+
}),
|
|
465
|
+
timeoutMs: getExaRequestTimeoutMs(searchType),
|
|
466
|
+
};
|
|
467
|
+
},
|
|
468
|
+
fetchJson(request, apiKey, signal) {
|
|
469
|
+
return fetchExaSearchJson(request.body, apiKey, signal, request.timeoutMs);
|
|
470
|
+
},
|
|
471
|
+
normalizeResponse(response, params) {
|
|
472
|
+
const searchType = params.searchType ?? "auto";
|
|
473
|
+
return {
|
|
474
|
+
extraDetails: {
|
|
475
|
+
requestId: cleanSearchText(response.requestId, 120),
|
|
476
|
+
searchType: cleanSearchText(response.searchType, 80) ?? searchType,
|
|
477
|
+
},
|
|
478
|
+
results: (response.results ?? [])
|
|
479
|
+
.map(normalizeExaSearchResult)
|
|
480
|
+
.filter((result) => Boolean(result))
|
|
481
|
+
.slice(params.offset, params.offset + params.count),
|
|
482
|
+
returnedQuery: params.query,
|
|
483
|
+
};
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
export const WEB_SEARCH_PROVIDER_ADAPTERS = {
|
|
487
|
+
exa: EXA_WEB_SEARCH_ADAPTER,
|
|
488
|
+
brave: BRAVE_WEB_SEARCH_ADAPTER,
|
|
489
|
+
};
|
|
490
|
+
export function getWebSearchProviderAdapter(provider) {
|
|
491
|
+
return WEB_SEARCH_PROVIDER_ADAPTERS[provider];
|
|
492
|
+
}
|
|
493
|
+
function buildMissingCredentialError(provider) {
|
|
494
|
+
if (provider === "brave")
|
|
495
|
+
return "agent_browser_web_search provider brave was requested but no BRAVE_API_KEY/config credential resolved.";
|
|
496
|
+
if (provider === "exa")
|
|
497
|
+
return "agent_browser_web_search provider exa was requested but no EXA_API_KEY/config credential resolved.";
|
|
498
|
+
return "No Exa or Brave web search credential resolved. Configure webSearch.exaApiKey or webSearch.braveApiKey, or load EXA_API_KEY/BRAVE_API_KEY in the runtime environment.";
|
|
499
|
+
}
|
|
500
|
+
export function createAgentBrowserWebSearchTool(configState, options = {}) {
|
|
501
|
+
const requestGate = new WebSearchRequestGate();
|
|
502
|
+
return {
|
|
503
|
+
name: AGENT_BROWSER_WEB_SEARCH_TOOL_NAME,
|
|
504
|
+
label: "Agent Browser Web Search",
|
|
505
|
+
description: `Search the web with Exa or Brave when configured. Returns up to ${MAX_SEARCH_RESULT_COUNT} concise web results.`,
|
|
506
|
+
promptSnippet: "Search the live web with Exa or Brave for current or external information.",
|
|
507
|
+
promptGuidelines: [
|
|
508
|
+
"Use agent_browser_web_search when live web search would help answer the task, find current external information, or discover candidate URLs for agent_browser.",
|
|
509
|
+
"agent_browser_web_search chooses Exa or Brave from configured keys; when both are available, Exa is preferred by default unless webSearch.preferredProvider says otherwise. Use provider only when the user/config calls for a specific provider.",
|
|
510
|
+
"Prefer agent_browser_web_search over opening a search engine results page with agent_browser when a quick result list is enough; use agent_browser for interaction, DOM, screenshots, or auth.",
|
|
511
|
+
"Do not issue parallel or repeated agent_browser_web_search calls; use one high-signal query, inspect the results, then only run a focused follow-up if needed. If the provider returns HTTP 429, stop searching and tell the user the API plan/rate limit needs time or a plan change.",
|
|
512
|
+
"After using agent_browser_web_search, cite result URLs in the final answer when web evidence informed the answer.",
|
|
513
|
+
],
|
|
514
|
+
parameters: AgentBrowserWebSearchParams,
|
|
515
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
516
|
+
const runtimeConfigState = ctx ? options.loadConfigState?.(ctx) ?? configState : configState;
|
|
517
|
+
if (runtimeConfigState.errors.length > 0) {
|
|
518
|
+
throw new Error(`agent_browser_web_search config is invalid: ${runtimeConfigState.errors.join("; ")}`);
|
|
519
|
+
}
|
|
520
|
+
if (!runtimeConfigState.webSearchEnabled) {
|
|
521
|
+
throw new Error("agent_browser_web_search is disabled by pi-agent-browser-native config.");
|
|
522
|
+
}
|
|
523
|
+
const requestedProvider = params.provider ?? "auto";
|
|
524
|
+
const resolved = await resolvePreferredWebSearchCredential(runtimeConfigState, { provider: requestedProvider, signal });
|
|
525
|
+
if (!resolved)
|
|
526
|
+
throw new Error(buildMissingCredentialError(requestedProvider));
|
|
527
|
+
const query = params.query.trim();
|
|
528
|
+
if (!query)
|
|
529
|
+
throw new Error("query must not be blank");
|
|
530
|
+
const count = Math.min(Math.max(params.count ?? DEFAULT_SEARCH_RESULT_COUNT, 1), MAX_SEARCH_RESULT_COUNT);
|
|
531
|
+
const offset = Math.max(params.offset ?? 0, 0);
|
|
532
|
+
const adapter = getWebSearchProviderAdapter(resolved.provider);
|
|
533
|
+
const executionParams = {
|
|
534
|
+
country: params.country,
|
|
535
|
+
count,
|
|
536
|
+
freshness: params.freshness,
|
|
537
|
+
offset,
|
|
538
|
+
query,
|
|
539
|
+
safesearch: params.safesearch,
|
|
540
|
+
searchLang: params.searchLang,
|
|
541
|
+
searchType: params.searchType ?? "auto",
|
|
542
|
+
};
|
|
543
|
+
const request = adapter.buildRequest(executionParams);
|
|
544
|
+
const data = await requestGate.run(signal, () => adapter.fetchJson(request, resolved.credential.value, signal));
|
|
545
|
+
const normalized = adapter.normalizeResponse(data, executionParams);
|
|
546
|
+
const details = {
|
|
547
|
+
provider: adapter.provider,
|
|
548
|
+
query,
|
|
549
|
+
returnedQuery: normalized.returnedQuery,
|
|
550
|
+
count,
|
|
551
|
+
offset,
|
|
552
|
+
...normalized.extraDetails,
|
|
553
|
+
fetchedAt: new Date().toISOString(),
|
|
554
|
+
results: normalized.results,
|
|
555
|
+
};
|
|
556
|
+
return {
|
|
557
|
+
content: [{ type: "text", text: formatSearchResults(adapter.provider, normalized.returnedQuery, normalized.results) }],
|
|
558
|
+
details,
|
|
559
|
+
};
|
|
560
|
+
},
|
|
561
|
+
};
|
|
562
|
+
}
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -23,7 +23,7 @@ V1 exposes one native browser tool:
|
|
|
23
23
|
|
|
24
24
|
It may also expose one optional companion tool:
|
|
25
25
|
|
|
26
|
-
- `agent_browser_web_search`,
|
|
26
|
+
- `agent_browser_web_search`, available when an Exa or Brave Search credential source is configured or resolvable from startup config or trusted session config and runtime `webSearch.enabled` is not false
|
|
27
27
|
|
|
28
28
|
Why:
|
|
29
29
|
- keeps browser automation centered on `agent_browser`
|
|
@@ -68,11 +68,11 @@ Pi docs use `settings.json` for package/resource loading and filtering, not arbi
|
|
|
68
68
|
- project-local: `.pi/config/pi-agent-browser-native/config.json`
|
|
69
69
|
- explicit override: `PI_AGENT_BROWSER_CONFIG=/path/to/config.json`
|
|
70
70
|
|
|
71
|
-
Config layers merge in that order: global, project, override. The shared policy module (`extensions/agent-browser/lib/config-policy.js`) owns provider descriptors, environment variable names, config keys,
|
|
71
|
+
Config layers merge in that order: global, project, override. The shared policy module (`extensions/agent-browser/lib/config-policy.js`) owns provider descriptors, environment variable names, config keys, credential source parsing, developer-trusted project layer inclusion, layer validation/merge, redacted status projection, and credential summaries for both runtime config loading and the package config helper. Under Pi 0.79+, globally installed or CLI-loaded extensions are developer-trusted code, so this extension reads `.pi/config/pi-agent-browser-native/config.json` by default and skips that project layer when Pi reports the project is untrusted or when launched with `--no-approve`. Global config and explicit `PI_AGENT_BROWSER_CONFIG` overrides remain available either way. The config reader accepts v1 fields for `webSearch.enabled`, `webSearch.preferredProvider`, `webSearch.exaApiKey`, `webSearch.braveApiKey`, and conservative browser defaults such as `browser.defaultProfile` and `browser.executablePath`. Web-search key fields follow Pi model/provider-style value resolution from any loaded layer: literal values, `$ENV_VAR` / `${ENV_VAR}` interpolation, escapes (`$$`, `$!`), and leading `!command` resolved at request time. `EXA_API_KEY` and `BRAVE_API_KEY` remain environment fallbacks when no config credential source exists for that provider. Browser default values keep their source scope; prompt guidance is emitted from the highest-priority loaded layer, including project config when Pi trust/loading allows it.
|
|
72
72
|
|
|
73
|
-
`agent_browser_web_search`
|
|
73
|
+
`agent_browser_web_search` availability is conditional. Startup registration uses global, override, and environment fallback config without reading project-local config before Pi trust context exists; trusted project config can register the companion tool on `session_start`, and every execution reloads the final session config so `webSearch.enabled: false` still prevents a request even if a startup credential made the tool visible. A global disable is the normal user default and can still be overridden by project config or `PI_AGENT_BROWSER_CONFIG`; a project disable applies to one repo; an explicit `PI_AGENT_BROWSER_CONFIG` file with `webSearch.enabled: false` is the highest-priority hard disable for that run. Literal and env-backed sources must resolve before they make the tool available; command-backed sources are considered configured without running the command until tool execution, so secret managers do not slow startup or prompt unexpectedly. The tool resolves the selected key lazily, chooses Exa or Brave from available credentials (preferring Exa by default unless `webSearch.preferredProvider` says otherwise), then follows one provider-agnostic execution path through provider adapters for request building, HTTP JSON fetch, response normalization, and provider-specific detail fields. It calls Exa `/search` with highlights or Brave Search and returns compact result details without exposing keys.
|
|
74
74
|
|
|
75
|
-
Browser default config is intentionally
|
|
75
|
+
Browser default config is intentionally advisory. It can add prompt guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables, but current releases do not auto-inject `--profile` or `--executable-path` into every launch. Loaded project config can provide the same guidance as global and override config; Pi/project trust decides whether that project layer is loaded. Automatic launch-default mutation would affect privacy, browser state, and host executable choice, so it needs a separate explicit design and test pass.
|
|
76
76
|
|
|
77
77
|
### Prompt guidance budget
|
|
78
78
|
|
|
@@ -199,7 +199,7 @@ This keeps the product centered on native tool usage instead of auxiliary skill
|
|
|
199
199
|
### `pi-agent-browser-native` owns
|
|
200
200
|
|
|
201
201
|
- tool registration and schema (including the optional `semanticAction` compilation path to upstream `find` or `select`)
|
|
202
|
-
- subprocess execution and JSON parsing through
|
|
202
|
+
- subprocess execution and JSON parsing through `buildAgentBrowserProcessEnv` in `extensions/agent-browser/lib/process.ts`: copies the parent process environment so user-approved provider credentials and other runtime variables reach upstream, then applies wrapper overrides such as the managed socket directory and clamped default operation timeout
|
|
203
203
|
- clear missing-binary errors
|
|
204
204
|
- compact result summaries, including presentation-time redaction: stateful browser-context commands (`auth`, `cookies`, `storage`, `dialog`, `frame`, `state`) use field-aware value redaction and compact formatters, while other structured upstream JSON (for example `network`, `diff`, `trace` / `profiler` / `record`, `console` / `errors` / `highlight` / `inspect` / `clipboard`, `stream`, `dashboard`, and `chat`) is passed through `redactPresentationData` in `extensions/agent-browser/lib/results/presentation.ts` so model-facing `details.data` and batch roll-ups stay compact and do not echo bearer tokens, proxy passwords, or similar fields verbatim; `redactInvocationArgs` in `extensions/agent-browser/lib/runtime.ts` masks trailing values for sensitive global flags such as `--body`, `--headers`, `--password`, and `--proxy`, preserves positional rules for `cookies set` and `storage local|session set`, and nested `batch` steps use the same argv and error-body scrubbing before echoing commands or errors
|
|
205
205
|
- bounded machine-readable outcome metadata on tool `details` (`resultCategory`, `successCategory`, `failureCategory`, optional `nextActions`, optional `pageChangeSummary` with per-step summaries on `batch`, optional `artifactVerification` with the same shape on each successful `batchSteps[]` row) so agents can branch without parsing prose; enums, classifier precedence, and generic follow-up payloads are implemented under `extensions/agent-browser/lib/results/` in focused modules (`contracts.ts` for shared types, `categories.ts` for `classifyAgentBrowserSuccessCategory` / `classifyAgentBrowserFailureCategory` / `buildAgentBrowserResultCategoryDetails`, `action-recommendations.ts` for `buildAgentBrowserNextActions`, `next-actions.ts` for the `AgentBrowserNextAction` shape and merge helpers, `recovery-actions.ts` for recovery id registries and `buildRecoveryNextActions`, `network.ts` for `classifyNetworkRequestFailure` / `summarizeNetworkFailures`, and related helpers; `shared.ts` in that directory is a **re-export-only** barrel so historical `./results/shared.js` imports keep working without growing a monolith). Per-session tab target, `refSnapshot` alignment, invalidation, and tab pinning observations flow through `extensions/agent-browser/lib/session-page-state.ts` from `extensions/agent-browser/index.ts`. Compact page-change summaries and artifact verification rollups are built in `extensions/agent-browser/lib/results/presentation.ts` (`buildPageChangeSummary`, `buildArtifactVerificationSummary`), and the human contract lives in [`TOOL_CONTRACT.md`](TOOL_CONTRACT.md#details). Real Pi custom tools otherwise only mark a row failed when `execute` throws, so the extension also registers `pi.on("tool_result", …)` and patches `agent_browser` results whose `details.resultCategory` is `failure` to set `isError: true`. Prose results also receive a short category notice, while caller-requested `--json` results with parseable JSON content keep that text unchanged so JSONL transcripts, UI affordances, and the machine-readable contract stay aligned for wrapper-side reclassifications such as `qa-failure` (`buildAgentBrowserToolResultPatch` in `extensions/agent-browser/lib/pi-tool-rendering.ts`; transcript semantics in the same contract doc)
|
|
@@ -748,7 +748,7 @@ npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-conf
|
|
|
748
748
|
npm exec --yes --package pi-agent-browser-native@latest -- pi-agent-browser-config browser executable set "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
749
749
|
```
|
|
750
750
|
|
|
751
|
-
The optional `agent_browser_web_search` tool is
|
|
751
|
+
The optional `agent_browser_web_search` tool is available when Exa or Brave credentials are visible from startup config or trusted session config and the runtime config has not set `webSearch.enabled` to `false`. It is a separate custom tool, not an `agent_browser` input mode, and does not launch a browser. Use it when current/live external web information would help; use `agent_browser` for browser interaction, screenshots, authenticated/profile pages, and DOM inspection. Disable scope is explicit: `web-search disable --global` sets the normal user default, `web-search disable --project` disables it for one repo, and a `PI_AGENT_BROWSER_CONFIG` override containing `{ "version": 1, "webSearch": { "enabled": false } }` wins over both for a hard per-run disable. Loaded config may use plaintext, custom env aliases, interpolation literals, malformed-or-late-bound `$` values, and command-backed web-search keys; the resolved secret reaches the provider request while model-facing tool output and status text stay redacted. `web-search set-key`, `set-command`, and `clear` require `--provider`; `set-env` infers Exa/Brave from `EXA_API_KEY` or `BRAVE_API_KEY` unless you pass `--provider`. For Exa, the tool defaults to `searchType: "auto"` with `contents.highlights: true`; use `fast`, `instant`, `deep-lite`, `deep`, or `deep-reasoning` only when the task needs that latency/depth tradeoff.
|
|
752
752
|
|
|
753
753
|
Example config:
|
|
754
754
|
|
|
@@ -771,7 +771,7 @@ Example config:
|
|
|
771
771
|
}
|
|
772
772
|
```
|
|
773
773
|
|
|
774
|
-
Browser default config is conservative: it adds agent guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables; current releases do not auto-inject `--profile` or `--executable-path` for every launch. Configure profile/executable guidance globally
|
|
774
|
+
Browser default config is conservative: it adds agent guidance for signed-in/account-specific tasks and alternate Chromium-compatible executables; current releases do not auto-inject `--profile` or `--executable-path` for every launch. Configure profile/executable guidance globally, in trusted project config, or through `PI_AGENT_BROWSER_CONFIG`. Ask the agent to run `profiles` and `doctor` when profile resolution fails, then use the reported Chrome profile directory name, a full profile/user-data directory path if upstream accepts one, or the configured `browser.executablePath` with top-level `sessionMode: "fresh"`.
|
|
775
775
|
|
|
776
776
|
## Important global flags, config, and environment
|
|
777
777
|
|
|
@@ -819,7 +819,7 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
|
|
|
819
819
|
- `--confirm-interactive`: interactive confirmations; auto-denies when stdin is not a TTY. Environment: `AGENT_BROWSER_CONFIRM_INTERACTIVE`.
|
|
820
820
|
- `-p, --provider <name>`: provider such as `ios`, `browserbase`, `kernel`, `browseruse`, `browserless`, or `agentcore`. Environment: `AGENT_BROWSER_PROVIDER`.
|
|
821
821
|
- `--device <name>`: iOS device name. Environment: `AGENT_BROWSER_IOS_DEVICE`.
|
|
822
|
-
- Provider-specific iOS examples from upstream include `agent-browser -p ios device list`, `agent-browser -p ios swipe up`, and `agent-browser -p ios tap @e1`; in pi, pass those tokens through `args` rather than bash. iOS requires external Xcode/Appium setup, and cloud providers (`browserbase`, `kernel`, `browseruse`, `browserless`, `agentcore`) require their upstream accounts, credentials, and provider-specific environment variables. Common
|
|
822
|
+
- Provider-specific iOS examples from upstream include `agent-browser -p ios device list`, `agent-browser -p ios swipe up`, and `agent-browser -p ios tap @e1`; in pi, pass those tokens through `args` rather than bash. iOS requires external Xcode/Appium setup, and cloud providers (`browserbase`, `kernel`, `browseruse`, `browserless`, `agentcore`) require their upstream accounts, credentials, and provider-specific environment variables. Common provider variables include `BROWSERBASE_API_KEY`, `BROWSERBASE_PROJECT_ID`, `BROWSERLESS_API_KEY`, `BROWSERLESS_API_URL`, `BROWSERLESS_BROWSER_TYPE`, `BROWSERLESS_STEALTH`, `BROWSERLESS_TTL`, `BROWSER_USE_API_KEY`, `KERNEL_API_KEY`, `KERNEL_HEADLESS`, `KERNEL_STEALTH`, `KERNEL_TIMEOUT_SECONDS`, `KERNEL_PROFILE_NAME`, `AGENTCORE_API_KEY`, `AGENTCORE_REGION`, `AGENTCORE_BROWSER_ID`, `AGENTCORE_PROFILE_ID`, `AGENTCORE_SESSION_TIMEOUT`, plus AWS names used by AgentCore such as `AWS_PROFILE`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION`, and `AWS_DEFAULT_REGION`. The wrapper forwards parent environment variables plus provider flags and stays thin; it does not emulate provider setup or cloud browser behavior.
|
|
823
823
|
- `--model <name>`: AI model for `chat`. Environment: `AI_GATEWAY_MODEL`.
|
|
824
824
|
- `-v, --verbose`: show tool commands and raw output.
|
|
825
825
|
- `-q, --quiet`: show only AI text responses.
|
|
@@ -837,7 +837,7 @@ Browser default config is conservative: it adds agent guidance for signed-in/acc
|
|
|
837
837
|
|
|
838
838
|
Use `--config <path>` to load a specific config file. Boolean flags accept optional `true` or `false` values, such as `--headed false`, to override config. Browser extensions from user and project configs are merged rather than replaced.
|
|
839
839
|
|
|
840
|
-
Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGENT_BROWSER_STREAM_PORT`, `AGENT_BROWSER_IDLE_TIMEOUT_MS`, `AGENT_BROWSER_ENCRYPTION_KEY`, `AGENT_BROWSER_STATE_EXPIRE_DAYS`, `AGENT_BROWSER_IOS_DEVICE`, `AGENT_BROWSER_IOS_UDID`, `AI_GATEWAY_URL`, `AI_GATEWAY_API_KEY`,
|
|
840
|
+
Other useful environment variables include `AGENT_BROWSER_DEFAULT_TIMEOUT`, `AGENT_BROWSER_STREAM_PORT`, `AGENT_BROWSER_IDLE_TIMEOUT_MS`, `AGENT_BROWSER_ENCRYPTION_KEY`, `AGENT_BROWSER_STATE_EXPIRE_DAYS`, `AGENT_BROWSER_IOS_DEVICE`, `AGENT_BROWSER_IOS_UDID`, `AI_GATEWAY_URL`, `AI_GATEWAY_API_KEY`, provider credential names, and AWS credential names when using AgentCore. The upstream child receives the parent environment plus wrapper overrides such as the managed socket directory and clamped default operation timeout (`buildAgentBrowserProcessEnv` in `extensions/agent-browser/lib/process.ts`). Model-facing output still redacts recognized secret values.
|
|
841
841
|
|
|
842
842
|
## Wrapper-specific behavior worth knowing
|
|
843
843
|
|