@browser_use/pi 0.1.0
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/.env.example +6 -0
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/dist/agent.d.ts +15 -0
- package/dist/agent.js +381 -0
- package/dist/agent.js.map +1 -0
- package/dist/browser.d.ts +52 -0
- package/dist/browser.js +264 -0
- package/dist/browser.js.map +1 -0
- package/dist/cdp.d.ts +39 -0
- package/dist/cdp.js +291 -0
- package/dist/cdp.js.map +1 -0
- package/dist/context.d.ts +24 -0
- package/dist/context.js +143 -0
- package/dist/context.js.map +1 -0
- package/dist/control.d.ts +18 -0
- package/dist/control.js +84 -0
- package/dist/control.js.map +1 -0
- package/dist/events.d.ts +40 -0
- package/dist/events.js +79 -0
- package/dist/events.js.map +1 -0
- package/dist/highlight.d.ts +3 -0
- package/dist/highlight.js +102 -0
- package/dist/highlight.js.map +1 -0
- package/dist/history.d.ts +22 -0
- package/dist/history.js +126 -0
- package/dist/history.js.map +1 -0
- package/dist/images.d.ts +14 -0
- package/dist/images.js +104 -0
- package/dist/images.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +422 -0
- package/dist/index.js.map +1 -0
- package/dist/model-stream.d.ts +3 -0
- package/dist/model-stream.js +78 -0
- package/dist/model-stream.js.map +1 -0
- package/dist/observer.d.ts +16 -0
- package/dist/observer.js +56 -0
- package/dist/observer.js.map +1 -0
- package/dist/page.d.ts +58 -0
- package/dist/page.js +189 -0
- package/dist/page.js.map +1 -0
- package/dist/policy.d.ts +18 -0
- package/dist/policy.js +195 -0
- package/dist/policy.js.map +1 -0
- package/dist/prompt.d.ts +1 -0
- package/dist/prompt.js +41 -0
- package/dist/prompt.js.map +1 -0
- package/dist/protocol.d.ts +70 -0
- package/dist/protocol.js +7 -0
- package/dist/protocol.js.map +1 -0
- package/dist/recording.d.ts +44 -0
- package/dist/recording.js +120 -0
- package/dist/recording.js.map +1 -0
- package/dist/research-tools.d.ts +3 -0
- package/dist/research-tools.js +67 -0
- package/dist/research-tools.js.map +1 -0
- package/dist/runtime.d.ts +39 -0
- package/dist/runtime.js +268 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +251 -0
- package/dist/server.js.map +1 -0
- package/dist/telemetry.d.ts +3 -0
- package/dist/telemetry.js +41 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/video.d.ts +18 -0
- package/dist/video.js +177 -0
- package/dist/video.js.map +1 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +329 -0
- package/dist/worker.js.map +1 -0
- package/examples/README.md +58 -0
- package/examples/apply-to-job.ts +57 -0
- package/examples/ehr.ts +53 -0
- package/examples/extract.ts +50 -0
- package/examples/form.ts +36 -0
- package/examples/onepassword.ts +66 -0
- package/examples/qa.ts +72 -0
- package/examples/research.ts +35 -0
- package/examples/stripe-link.ts +166 -0
- package/package.json +66 -0
package/.env.example
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Browser Use contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/browser-use/browser-use-pi/main/docs/public/banner.webp" alt="A white arch above the clouds" width="100%" />
|
|
2
|
+
|
|
3
|
+
# Browser Use Pi
|
|
4
|
+
|
|
5
|
+
**Browser Use, built on Pi Mono. In TypeScript.**
|
|
6
|
+
|
|
7
|
+
Pi Mono + a persistent V8 REPL + raw CDP. The agent writes JavaScript, controls Chrome, and builds the helpers it needs as it goes.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Your task → Pi Mono → persistent V8 REPL → raw CDP → Chrome
|
|
11
|
+
↑ │
|
|
12
|
+
└──────── AX tree + screenshots ──────┘
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The programmability of Browser Harness, with sessions, saved logins, streaming and typed results. One SDK you can put in your app.
|
|
16
|
+
|
|
17
|
+
## Start
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm install @browser_use/pi
|
|
21
|
+
# or: pnpm add @browser_use/pi
|
|
22
|
+
# or: bun add @browser_use/pi
|
|
23
|
+
export OPENROUTER_API_KEY=...
|
|
24
|
+
export BROWSER_USE_API_KEY=...
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Browser, BrowserUse } from '@browser_use/pi';
|
|
29
|
+
|
|
30
|
+
const agent = await BrowserUse.create({
|
|
31
|
+
model: 'openrouter/openai/gpt-5.6-luna',
|
|
32
|
+
reasoning: 'xhigh',
|
|
33
|
+
browser: Browser.cloud({ apiKey: process.env.BROWSER_USE_API_KEY! }),
|
|
34
|
+
workspace: './work',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const result = await agent.run('Find the top story on Hacker News.');
|
|
39
|
+
console.log(result.status, result.text);
|
|
40
|
+
await agent.followUp('Summarize the comments.');
|
|
41
|
+
} finally {
|
|
42
|
+
await agent.close();
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Save as `agent.ts`. Run with `node agent.ts` or `bun agent.ts`. Uses [Browser Use Cloud](docs/sessions.md); no local Chrome installation needed.
|
|
47
|
+
|
|
48
|
+
**Node 22.19+ · Bun 1.3.14+**. Bun also needs Node for the V8 worker.
|
|
49
|
+
|
|
50
|
+
## Keep building
|
|
51
|
+
|
|
52
|
+
- **Models:** upstream Pi's model catalog and transports, with custom providers supported. Model capabilities and provider access vary. [Models](docs/models.md)
|
|
53
|
+
- **Sessions:** follow-ups, saved logins, persistent workspaces, cloud browsers or your own Chrome. [Sessions](docs/sessions.md)
|
|
54
|
+
- **Control:** streaming, hooks, typed results and compaction. Cap steps, time or cost and keep partial work. [API](docs/api.md)
|
|
55
|
+
- **Show the work:** interaction highlights, recordings and GIF exports. [Examples](examples/README.md)
|
|
56
|
+
|
|
57
|
+
[Quickstart](docs/quickstart.md) · [Browser primitives](docs/browser.md) · [Python](docs/python.md) · [Historical benchmarks](docs/benchmarks.md)
|
|
58
|
+
|
|
59
|
+
JavaScript runs in a killable worker with filesystem and network access. Use an isolated machine for untrusted tasks. Anonymous run counters are enabled; disable with `telemetry: false` or `DO_NOT_TRACK=1`. [Telemetry](docs/api.md#telemetry)
|
|
60
|
+
|
|
61
|
+
## Develop
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
npm test
|
|
65
|
+
npm run check
|
|
66
|
+
npm run docs:build
|
|
67
|
+
npm run test:python
|
|
68
|
+
```
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type AgentMessage, type StreamFn } from '@earendil-works/pi-agent-core';
|
|
2
|
+
import type { Model, Api, Usage } from '@earendil-works/pi-ai';
|
|
3
|
+
import { type TSchema } from 'typebox';
|
|
4
|
+
import { type BrowserRuntime } from './runtime.js';
|
|
5
|
+
import type { BrowserUseOptions, RunOptions, RunResult } from './types.js';
|
|
6
|
+
import { type RunControl } from './control.js';
|
|
7
|
+
export declare const zeroUsage: () => Usage;
|
|
8
|
+
export declare function runAgent(runtime: BrowserRuntime, model: Model<Api>, config: BrowserUseOptions & {
|
|
9
|
+
streamFn: StreamFn;
|
|
10
|
+
}, workspace: string, task: string, schema: TSchema, options: RunOptions, session?: {
|
|
11
|
+
messages: AgentMessage[];
|
|
12
|
+
control: RunControl;
|
|
13
|
+
save: (messages: AgentMessage[]) => void;
|
|
14
|
+
eventsPath?: string;
|
|
15
|
+
}): Promise<RunResult<unknown>>;
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { Agent, } from '@earendil-works/pi-agent-core';
|
|
2
|
+
import { Type } from 'typebox';
|
|
3
|
+
import { Check, Errors } from 'typebox/value';
|
|
4
|
+
import { CellError } from './runtime.js';
|
|
5
|
+
import { Observer } from './observer.js';
|
|
6
|
+
import { RunContext } from './context.js';
|
|
7
|
+
import { deadlineStream } from './model-stream.js';
|
|
8
|
+
import { researchTools } from './research-tools.js';
|
|
9
|
+
import { redact } from './history.js';
|
|
10
|
+
import { SYSTEM_PROMPT } from './prompt.js';
|
|
11
|
+
import { positiveInteger } from './protocol.js';
|
|
12
|
+
import { bounded } from './control.js';
|
|
13
|
+
export const zeroUsage = () => ({
|
|
14
|
+
input: 0,
|
|
15
|
+
output: 0,
|
|
16
|
+
cacheRead: 0,
|
|
17
|
+
cacheWrite: 0,
|
|
18
|
+
totalTokens: 0,
|
|
19
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
20
|
+
});
|
|
21
|
+
function sumUsage(messages) {
|
|
22
|
+
const result = zeroUsage();
|
|
23
|
+
for (const message of messages) {
|
|
24
|
+
if (message.role !== 'assistant')
|
|
25
|
+
continue;
|
|
26
|
+
for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'])
|
|
27
|
+
result[key] += message.usage[key];
|
|
28
|
+
for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'total'])
|
|
29
|
+
result.cost[key] += message.usage.cost[key];
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
export async function runAgent(runtime, model, config, workspace, task, schema, options, session) {
|
|
34
|
+
const maxSteps = positiveInteger('maxSteps', options.maxSteps ?? 40);
|
|
35
|
+
const timeoutMs = positiveInteger('timeoutMs', options.timeoutMs ?? 300_000);
|
|
36
|
+
const maxContextChars = positiveInteger('maxContextChars', options.maxContextChars ?? 240_000);
|
|
37
|
+
if (options.maxCostUsd !== undefined &&
|
|
38
|
+
(!Number.isFinite(options.maxCostUsd) || options.maxCostUsd <= 0)) {
|
|
39
|
+
throw new Error('maxCostUsd must be a finite positive number.');
|
|
40
|
+
}
|
|
41
|
+
if (options.compaction !== undefined && typeof options.compaction !== 'boolean')
|
|
42
|
+
throw new Error('compaction must be boolean.');
|
|
43
|
+
runtime.beginRun();
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
const previousMessages = session?.messages.length ?? 0;
|
|
46
|
+
const hookTimeout = config.hookTimeoutMs ?? 30_000;
|
|
47
|
+
let steps = 0;
|
|
48
|
+
let finishRepairs = 0;
|
|
49
|
+
let providerRetries = 0;
|
|
50
|
+
let retriedUsage = zeroUsage();
|
|
51
|
+
let finalizing = false;
|
|
52
|
+
let compactionFailed = false;
|
|
53
|
+
const warnings = [];
|
|
54
|
+
const context = new RunContext(model, deadlineStream((selected, request, settings) => config.streamFn(selected, redact(request, config.redact ?? []), settings), config.compactionTimeoutMs ?? 120_000), workspace, maxContextChars, options.compaction !== false, config.redact ?? []);
|
|
55
|
+
let completion;
|
|
56
|
+
let stopped;
|
|
57
|
+
const codeParameters = Type.Object({ code: Type.String({ minLength: 1 }) });
|
|
58
|
+
const cellFailures = new Map();
|
|
59
|
+
const javascript = {
|
|
60
|
+
name: 'javascript',
|
|
61
|
+
label: 'Browser JavaScript',
|
|
62
|
+
description: `Execute JavaScript in a persistent Node REPL with raw CDP. Cell deadline: ${config.cellTimeoutMs ?? 30_000} ms. Checkpoint small batches before the deadline. Use screenshot() for native images.`,
|
|
63
|
+
parameters: codeParameters,
|
|
64
|
+
executionMode: 'sequential',
|
|
65
|
+
replay: 'never',
|
|
66
|
+
execute: async (_id, params, signal) => {
|
|
67
|
+
let result;
|
|
68
|
+
try {
|
|
69
|
+
result = await runtime.execute(params.code, config.cellTimeoutMs ?? 30_000, signal);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (!(error instanceof CellError))
|
|
73
|
+
throw error;
|
|
74
|
+
// Pi converts thrown tools to text-only errors. Restore their native evidence
|
|
75
|
+
// in afterToolCall while retaining Pi's error flag and application hook control.
|
|
76
|
+
cellFailures.set(_id, error);
|
|
77
|
+
throw new Error(`${error.message}\nState reset: ${error.stateReset}. Actions may already have happened.\nPartial output: ${error.result.text}\nCaptured output file: ${error.result.outputFile ?? '(none)'}`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: 'text', text: result.text }, ...result.images],
|
|
81
|
+
details: {
|
|
82
|
+
outputFile: result.outputFile,
|
|
83
|
+
targetId: result.targetId,
|
|
84
|
+
observationTargetId: result.observationTargetId,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const acceptResult = async (output, signal) => {
|
|
90
|
+
if (!Check(schema, output))
|
|
91
|
+
throw new Error(`Final result does not match schema: ${JSON.stringify(Errors(schema, output).slice(0, 5)).slice(0, 2000)}`);
|
|
92
|
+
if (config.validateResult) {
|
|
93
|
+
const feedback = await bounded(() => config.validateResult(output, signal), hookTimeout, signal);
|
|
94
|
+
if (feedback)
|
|
95
|
+
throw new Error(`Result rejected: ${feedback}`);
|
|
96
|
+
}
|
|
97
|
+
signal?.throwIfAborted();
|
|
98
|
+
const text = typeof output === 'string' ? output : JSON.stringify(output);
|
|
99
|
+
if (text === undefined)
|
|
100
|
+
throw new Error('The final result must be JSON serializable.');
|
|
101
|
+
completion = { output, text };
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: 'text', text: 'Result accepted.' }],
|
|
104
|
+
details: {},
|
|
105
|
+
terminate: true,
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
const resultParameters = Type.Object({ result: schema });
|
|
109
|
+
const finish = {
|
|
110
|
+
name: 'finish',
|
|
111
|
+
label: 'Deliver result',
|
|
112
|
+
description: 'Submit the verified final result, matching this schema. For data already in JavaScript, prefer finish_from_js to avoid rewriting it. This ends the task.',
|
|
113
|
+
parameters: resultParameters,
|
|
114
|
+
executionMode: 'sequential',
|
|
115
|
+
execute: async (_id, params, signal) => acceptResult(params.result, signal),
|
|
116
|
+
};
|
|
117
|
+
const expressionParameters = Type.Object({ expression: Type.String({ minLength: 1 }) });
|
|
118
|
+
const finishFromJs = {
|
|
119
|
+
name: 'finish_from_js',
|
|
120
|
+
label: 'Deliver JavaScript value',
|
|
121
|
+
description: 'Deliver an existing value from the persistent REPL without printing or rewriting it. Expression must match the result schema of finish. For a string result use JSON.stringify(records) or an existing text variable. Evaluated once; ends the task after validation.',
|
|
122
|
+
parameters: expressionParameters,
|
|
123
|
+
executionMode: 'sequential',
|
|
124
|
+
replay: 'never',
|
|
125
|
+
execute: async (_id, params, signal) => acceptResult(await runtime.readResult(params.expression, config.cellTimeoutMs ?? 30_000, signal), signal),
|
|
126
|
+
};
|
|
127
|
+
const checkBudgets = (messages, systemPrompt) => {
|
|
128
|
+
if (steps >= maxSteps)
|
|
129
|
+
stopped = 'max_steps';
|
|
130
|
+
if (options.maxCostUsd !== undefined &&
|
|
131
|
+
sumUsage(messages.slice(previousMessages)).cost.total +
|
|
132
|
+
retriedUsage.cost.total +
|
|
133
|
+
context.usage.reduce((sum, usage) => sum + usage.cost.total, 0) >=
|
|
134
|
+
options.maxCostUsd)
|
|
135
|
+
stopped = 'cost_limit';
|
|
136
|
+
if (options.compaction === false && !context.fits(messages, systemPrompt))
|
|
137
|
+
stopped = 'context_limit';
|
|
138
|
+
return stopped !== undefined;
|
|
139
|
+
};
|
|
140
|
+
const observer = options.observe
|
|
141
|
+
? new Observer(options.observe, positiveInteger('observerTimeoutMs', options.observerTimeoutMs ?? 3000))
|
|
142
|
+
: undefined;
|
|
143
|
+
const journalGuidance = session?.eventsPath
|
|
144
|
+
? `Run journal (JSON path): ${JSON.stringify(session.eventsPath)}\nRead relevant JSONL events to recover original tool execution times and observations. Event timestamps are not simultaneous page snapshots; never replace missing capture times with report time.\n`
|
|
145
|
+
: '';
|
|
146
|
+
const agent = new Agent({
|
|
147
|
+
streamFn: deadlineStream((selected, request, settings) => config.streamFn(selected, redact(request, config.redact ?? []), {
|
|
148
|
+
...settings,
|
|
149
|
+
maxTokens: Math.min(selected.maxTokens, 32768, Math.floor(selected.contextWindow * 0.15)),
|
|
150
|
+
}), config.modelTimeoutMs ?? 300_000),
|
|
151
|
+
initialState: {
|
|
152
|
+
model,
|
|
153
|
+
messages: session?.messages ?? [],
|
|
154
|
+
systemPrompt: `${SYSTEM_PROMPT}\nWorkspace directory (JSON string): ${JSON.stringify(workspace)}. Relative file-tool paths and the JavaScript working directory start here. Save deliverables inside this directory; files outside it are not included by BrowserUse.files(). Use relative paths or the exact workspace value, not a guessed parent directory.\n${journalGuidance}${config.sensitiveData ? `Named secrets (values withheld): ${JSON.stringify(Object.fromEntries(Object.entries(config.sensitiveData).map(([name, entry]) => [name, entry.domains])))}. Use await fillSecret(name, backendNodeId, page) on an input found in the AX tree. Never read back, print or save credentials.\n` : ''}${config.instructions ?? ''}`,
|
|
155
|
+
thinkingLevel: config.reasoning ?? 'medium',
|
|
156
|
+
tools: [
|
|
157
|
+
javascript,
|
|
158
|
+
finish,
|
|
159
|
+
finishFromJs,
|
|
160
|
+
...(config.researchTools ? researchTools(workspace, config.cellTimeoutMs ?? 30_000) : []),
|
|
161
|
+
...(config.tools ?? []),
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
toolExecution: 'sequential',
|
|
165
|
+
transformContext: async (messages, signal) => {
|
|
166
|
+
try {
|
|
167
|
+
if (!compactionFailed)
|
|
168
|
+
await context.prepare(messages, agent.state.systemPrompt, signal);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
compactionFailed = true;
|
|
172
|
+
warnings.push(`Compaction unavailable: ${String(error)}`);
|
|
173
|
+
}
|
|
174
|
+
signal?.throwIfAborted();
|
|
175
|
+
if (!context.fits(messages, agent.state.systemPrompt)) {
|
|
176
|
+
stopped = 'context_limit';
|
|
177
|
+
throw new Error('Context limit reached; checkpoint files and transcript retained.');
|
|
178
|
+
}
|
|
179
|
+
if (options.maxCostUsd !== undefined &&
|
|
180
|
+
sumUsage(messages.slice(previousMessages)).cost.total +
|
|
181
|
+
retriedUsage.cost.total +
|
|
182
|
+
context.usage.reduce((sum, u) => sum + u.cost.total, 0) >=
|
|
183
|
+
options.maxCostUsd) {
|
|
184
|
+
stopped = 'cost_limit';
|
|
185
|
+
throw new Error('Cost limit reached during context preparation.');
|
|
186
|
+
}
|
|
187
|
+
return context.project(messages);
|
|
188
|
+
},
|
|
189
|
+
prepareNextTurnWithContext: ({ context: current }) => {
|
|
190
|
+
if (!finalizing &&
|
|
191
|
+
((maxSteps >= 2 && steps >= maxSteps - 1) ||
|
|
192
|
+
Date.now() - start >= timeoutMs * 0.9 ||
|
|
193
|
+
(options.maxCostUsd !== undefined &&
|
|
194
|
+
sumUsage(current.messages.slice(previousMessages)).cost.total +
|
|
195
|
+
retriedUsage.cost.total +
|
|
196
|
+
context.usage.reduce((sum, usage) => sum + usage.cost.total, 0) >=
|
|
197
|
+
options.maxCostUsd * 0.9))) {
|
|
198
|
+
finalizing = true;
|
|
199
|
+
current.messages.push({
|
|
200
|
+
role: 'user',
|
|
201
|
+
content: 'Budget nearly exhausted. Deliver the verified result now with finish/finish_from_js. Reference saved files; explicitly list missing evidence. Do not perform more browser actions.',
|
|
202
|
+
timestamp: Date.now(),
|
|
203
|
+
});
|
|
204
|
+
agent.state.tools = [finish, finishFromJs];
|
|
205
|
+
return { context: { ...current, tools: [finish, finishFromJs] } };
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
},
|
|
209
|
+
beforeToolCall: async (call, signal) => {
|
|
210
|
+
await session?.control.checkpoint(signal);
|
|
211
|
+
if (completion || stopped || signal?.aborted)
|
|
212
|
+
return { block: true, reason: 'The run has ended.', terminate: true };
|
|
213
|
+
return config.beforeToolCall
|
|
214
|
+
? bounded(() => config.beforeToolCall(call, signal), hookTimeout, signal)
|
|
215
|
+
: undefined;
|
|
216
|
+
},
|
|
217
|
+
afterToolCall: async (call, signal) => {
|
|
218
|
+
const failure = cellFailures.get(call.toolCall.id);
|
|
219
|
+
cellFailures.delete(call.toolCall.id);
|
|
220
|
+
const evidence = failure
|
|
221
|
+
? {
|
|
222
|
+
content: [...call.result.content, ...failure.result.images],
|
|
223
|
+
details: {
|
|
224
|
+
outputFile: failure.result.outputFile,
|
|
225
|
+
targetId: failure.result.targetId,
|
|
226
|
+
observationTargetId: failure.result.observationTargetId,
|
|
227
|
+
stateReset: failure.stateReset,
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
: undefined;
|
|
231
|
+
// Completion validation belongs in validateResult, not a post-effect override.
|
|
232
|
+
if (!config.afterToolCall ||
|
|
233
|
+
call.toolCall.name === 'finish' ||
|
|
234
|
+
call.toolCall.name === 'finish_from_js')
|
|
235
|
+
return evidence;
|
|
236
|
+
const observed = evidence ? { ...call, result: { ...call.result, ...evidence } } : call;
|
|
237
|
+
const override = await bounded(() => config.afterToolCall(observed, signal), hookTimeout, signal);
|
|
238
|
+
return { ...evidence, ...override };
|
|
239
|
+
},
|
|
240
|
+
shouldStopAfterTurn: ({ context }) => {
|
|
241
|
+
if (completion || stopped)
|
|
242
|
+
return true;
|
|
243
|
+
return checkBudgets(context.messages, context.systemPrompt) || finishRepairs > 0;
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
if (session)
|
|
247
|
+
session.control.steer = (text) => agent.steer({ role: 'user', content: text, timestamp: Date.now() });
|
|
248
|
+
agent.subscribe((event) => {
|
|
249
|
+
if (event.type === 'turn_start')
|
|
250
|
+
steps++;
|
|
251
|
+
});
|
|
252
|
+
if (options.onEvent)
|
|
253
|
+
agent.subscribe((event, signal) => bounded(() => options.onEvent(event, signal), hookTimeout, signal));
|
|
254
|
+
agent.subscribe((event) => {
|
|
255
|
+
if (event.type === 'tool_execution_end')
|
|
256
|
+
observer?.push(event);
|
|
257
|
+
});
|
|
258
|
+
const cancel = () => {
|
|
259
|
+
stopped = 'cancelled';
|
|
260
|
+
agent.abort();
|
|
261
|
+
};
|
|
262
|
+
options.signal?.addEventListener('abort', cancel, { once: true });
|
|
263
|
+
const timer = setTimeout(() => {
|
|
264
|
+
stopped = 'timeout';
|
|
265
|
+
agent.abort();
|
|
266
|
+
}, timeoutMs);
|
|
267
|
+
let error;
|
|
268
|
+
try {
|
|
269
|
+
if (options.signal?.aborted)
|
|
270
|
+
stopped = 'cancelled';
|
|
271
|
+
else if (!context.fits([...agent.state.messages, { role: 'user', content: task, timestamp: Date.now() }], agent.state.systemPrompt) &&
|
|
272
|
+
agent.state.messages.length === 0)
|
|
273
|
+
stopped = 'context_limit';
|
|
274
|
+
else if (!checkBudgets([...agent.state.messages, { role: 'user', content: task, timestamp: Date.now() }], agent.state.systemPrompt)) {
|
|
275
|
+
await agent.prompt(task);
|
|
276
|
+
const failed = agent.state.messages.at(-1);
|
|
277
|
+
if (failed?.role === 'assistant' &&
|
|
278
|
+
failed.stopReason === 'error' &&
|
|
279
|
+
(/^OpenAI API error \(5\d{2}\): /.test(failed.errorMessage ?? '') ||
|
|
280
|
+
failed.errorMessage === 'Connection error.' ||
|
|
281
|
+
/stream ended before a terminal|Model stream exceeded|terminated|ECONNRESET|socket hang up|Unable to verify model access right now\. Please retry\./i.test(failed.errorMessage ?? '') ||
|
|
282
|
+
/^(?:(?:server_error|unknown): )?Sorry, something went wrong\.$/.test(failed.errorMessage ?? '') ||
|
|
283
|
+
failed.errorMessage?.startsWith('An error occurred while processing your request. You can retry your request,')) &&
|
|
284
|
+
!checkBudgets(agent.state.messages, agent.state.systemPrompt) &&
|
|
285
|
+
!options.signal?.aborted) {
|
|
286
|
+
// Pi never executes tool calls from an error response. Retry inference once, not actions.
|
|
287
|
+
providerRetries = 1;
|
|
288
|
+
retriedUsage = failed.usage;
|
|
289
|
+
agent.state.messages = agent.state.messages.slice(0, -1);
|
|
290
|
+
await agent.continue();
|
|
291
|
+
}
|
|
292
|
+
const ending = agent.state.messages.findLast((m) => m.role === 'assistant');
|
|
293
|
+
// One delivery-only repair. The original timer, transcript and budgets remain in force.
|
|
294
|
+
if (!completion && !stopped && ending?.role === 'assistant' && ending.stopReason === 'stop') {
|
|
295
|
+
const repair = {
|
|
296
|
+
role: 'user',
|
|
297
|
+
content: [
|
|
298
|
+
{
|
|
299
|
+
type: 'text',
|
|
300
|
+
text: 'The run ended without a validated delivery. Use finish_from_js for an existing result or finish for a concise answer. Preserve all verified records; report missing evidence honestly. Do not repeat browser actions. This is the single delivery repair turn.',
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
timestamp: Date.now(),
|
|
304
|
+
};
|
|
305
|
+
if (!checkBudgets([...agent.state.messages, repair], agent.state.systemPrompt)) {
|
|
306
|
+
finishRepairs = 1;
|
|
307
|
+
agent.state.tools = [finish, finishFromJs];
|
|
308
|
+
await agent.prompt(repair);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch (cause) {
|
|
314
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
315
|
+
stopped ??= 'error';
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
clearTimeout(timer);
|
|
319
|
+
await observer?.close(stopped === 'cancelled' || stopped === 'timeout');
|
|
320
|
+
warnings.push(...(observer?.warnings ?? []));
|
|
321
|
+
options.signal?.removeEventListener('abort', cancel);
|
|
322
|
+
session?.save(context.project(agent.state.messages));
|
|
323
|
+
session?.control.finish();
|
|
324
|
+
}
|
|
325
|
+
const last = agent.state.messages.slice(previousMessages).findLast((m) => m.role === 'assistant');
|
|
326
|
+
// A failed delivery repair must not erase useful text from the original ending.
|
|
327
|
+
const text = agent.state.messages
|
|
328
|
+
.slice(previousMessages)
|
|
329
|
+
.filter((m) => m.role === 'assistant')
|
|
330
|
+
.map((m) => m.content
|
|
331
|
+
.filter((c) => c.type === 'text')
|
|
332
|
+
.map((c) => c.text)
|
|
333
|
+
.join('\n'))
|
|
334
|
+
.findLast((value) => value.trim().length > 0) ?? '';
|
|
335
|
+
const usage = sumUsage(agent.state.messages.slice(previousMessages));
|
|
336
|
+
for (const extra of [retriedUsage, ...context.usage]) {
|
|
337
|
+
for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'])
|
|
338
|
+
usage[key] += extra[key];
|
|
339
|
+
for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'total'])
|
|
340
|
+
usage.cost[key] += extra.cost[key];
|
|
341
|
+
}
|
|
342
|
+
const metrics = {
|
|
343
|
+
steps,
|
|
344
|
+
finishRepairs,
|
|
345
|
+
durationMs: Date.now() - start,
|
|
346
|
+
usage,
|
|
347
|
+
compactions: context.compactions,
|
|
348
|
+
providerRetries,
|
|
349
|
+
...(warnings.length ? { warnings } : {}),
|
|
350
|
+
workspace,
|
|
351
|
+
model: config.model,
|
|
352
|
+
};
|
|
353
|
+
if (completion && !stopped)
|
|
354
|
+
return { ...metrics, status: 'completed', ...completion };
|
|
355
|
+
if (last?.role === 'assistant' &&
|
|
356
|
+
(last.stopReason === 'error' || last.stopReason === 'aborted')) {
|
|
357
|
+
stopped ??= 'error';
|
|
358
|
+
error ??= last.errorMessage ?? 'Model request failed.';
|
|
359
|
+
}
|
|
360
|
+
const observation = agent.state.messages
|
|
361
|
+
.slice(previousMessages)
|
|
362
|
+
.findLast((m) => m.role === 'toolResult');
|
|
363
|
+
const fallback = observation?.role === 'toolResult'
|
|
364
|
+
? observation.content
|
|
365
|
+
.filter((c) => c.type === 'text')
|
|
366
|
+
.map((c) => c.text)
|
|
367
|
+
.join('\n')
|
|
368
|
+
: '';
|
|
369
|
+
return {
|
|
370
|
+
...metrics,
|
|
371
|
+
status: stopped ?? 'incomplete',
|
|
372
|
+
text: text || (fallback ? `Last tool observation (unfinished):\n${fallback}` : ''),
|
|
373
|
+
...(completion
|
|
374
|
+
? { partial: { value: completion.output } }
|
|
375
|
+
: runtime.partial
|
|
376
|
+
? { partial: structuredClone(runtime.partial) }
|
|
377
|
+
: {}),
|
|
378
|
+
...(error ? { error } : {}),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Agent,\n type AgentMessage,\n type AgentTool,\n type StreamFn,\n} from '@earendil-works/pi-agent-core';\nimport type { Model, Api, Usage } from '@earendil-works/pi-ai';\nimport { Type, type TSchema } from 'typebox';\nimport { Check, Errors } from 'typebox/value';\nimport { CellError, type BrowserRuntime } from './runtime.js';\nimport { Observer } from './observer.js';\nimport { RunContext } from './context.js';\nimport { deadlineStream } from './model-stream.js';\nimport { researchTools } from './research-tools.js';\nimport type { BrowserUseOptions, RunOptions, RunResult, StopReason } from './types.js';\nimport { redact } from './history.js';\nimport { SYSTEM_PROMPT } from './prompt.js';\nimport { positiveInteger } from './protocol.js';\nimport { bounded, type RunControl } from './control.js';\n\nexport const zeroUsage = (): Usage => ({\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n});\nfunction sumUsage(messages: AgentMessage[]): Usage {\n const result = zeroUsage();\n for (const message of messages) {\n if (message.role !== 'assistant') continue;\n for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'] as const)\n result[key] += message.usage[key];\n for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'total'] as const)\n result.cost[key] += message.usage.cost[key];\n }\n return result;\n}\n\nexport async function runAgent(\n runtime: BrowserRuntime,\n model: Model<Api>,\n config: BrowserUseOptions & { streamFn: StreamFn },\n workspace: string,\n task: string,\n schema: TSchema,\n options: RunOptions,\n session?: {\n messages: AgentMessage[];\n control: RunControl;\n save: (messages: AgentMessage[]) => void;\n eventsPath?: string;\n },\n): Promise<RunResult<unknown>> {\n const maxSteps = positiveInteger('maxSteps', options.maxSteps ?? 40);\n const timeoutMs = positiveInteger('timeoutMs', options.timeoutMs ?? 300_000);\n const maxContextChars = positiveInteger('maxContextChars', options.maxContextChars ?? 240_000);\n if (\n options.maxCostUsd !== undefined &&\n (!Number.isFinite(options.maxCostUsd) || options.maxCostUsd <= 0)\n ) {\n throw new Error('maxCostUsd must be a finite positive number.');\n }\n if (options.compaction !== undefined && typeof options.compaction !== 'boolean')\n throw new Error('compaction must be boolean.');\n runtime.beginRun();\n const start = Date.now();\n const previousMessages = session?.messages.length ?? 0;\n const hookTimeout = config.hookTimeoutMs ?? 30_000;\n let steps = 0;\n let finishRepairs = 0;\n let providerRetries = 0;\n let retriedUsage = zeroUsage();\n let finalizing = false;\n let compactionFailed = false;\n const warnings: string[] = [];\n const context = new RunContext(\n model,\n deadlineStream(\n (selected, request, settings) =>\n config.streamFn(selected, redact(request, config.redact ?? []), settings),\n config.compactionTimeoutMs ?? 120_000,\n ),\n workspace,\n maxContextChars,\n options.compaction !== false,\n config.redact ?? [],\n );\n let completion: { output: unknown; text: string } | undefined;\n let stopped: StopReason | undefined;\n const codeParameters = Type.Object({ code: Type.String({ minLength: 1 }) });\n const cellFailures = new Map<string, CellError>();\n const javascript: AgentTool<typeof codeParameters> = {\n name: 'javascript',\n label: 'Browser JavaScript',\n description: `Execute JavaScript in a persistent Node REPL with raw CDP. Cell deadline: ${config.cellTimeoutMs ?? 30_000} ms. Checkpoint small batches before the deadline. Use screenshot() for native images.`,\n parameters: codeParameters,\n executionMode: 'sequential',\n replay: 'never',\n execute: async (_id, params: { code: string }, signal) => {\n let result;\n try {\n result = await runtime.execute(params.code, config.cellTimeoutMs ?? 30_000, signal);\n } catch (error) {\n if (!(error instanceof CellError)) throw error;\n // Pi converts thrown tools to text-only errors. Restore their native evidence\n // in afterToolCall while retaining Pi's error flag and application hook control.\n cellFailures.set(_id, error);\n throw new Error(\n `${error.message}\\nState reset: ${error.stateReset}. Actions may already have happened.\\nPartial output: ${error.result.text}\\nCaptured output file: ${error.result.outputFile ?? '(none)'}`,\n );\n }\n return {\n content: [{ type: 'text', text: result.text }, ...result.images],\n details: {\n outputFile: result.outputFile,\n targetId: result.targetId,\n observationTargetId: result.observationTargetId,\n },\n };\n },\n };\n const acceptResult = async (output: unknown, signal?: AbortSignal) => {\n if (!Check(schema, output))\n throw new Error(\n `Final result does not match schema: ${JSON.stringify(Errors(schema, output).slice(0, 5)).slice(0, 2000)}`,\n );\n if (config.validateResult) {\n const feedback = await bounded(\n () => config.validateResult!(output, signal),\n hookTimeout,\n signal,\n );\n if (feedback) throw new Error(`Result rejected: ${feedback}`);\n }\n signal?.throwIfAborted();\n const text = typeof output === 'string' ? output : JSON.stringify(output);\n if (text === undefined) throw new Error('The final result must be JSON serializable.');\n completion = { output, text };\n return {\n content: [{ type: 'text' as const, text: 'Result accepted.' }],\n details: {},\n terminate: true,\n };\n };\n const resultParameters = Type.Object({ result: schema });\n const finish: AgentTool<typeof resultParameters> = {\n name: 'finish',\n label: 'Deliver result',\n description:\n 'Submit the verified final result, matching this schema. For data already in JavaScript, prefer finish_from_js to avoid rewriting it. This ends the task.',\n parameters: resultParameters,\n executionMode: 'sequential',\n execute: async (_id, params: { result: unknown }, signal) =>\n acceptResult(params.result, signal),\n };\n const expressionParameters = Type.Object({ expression: Type.String({ minLength: 1 }) });\n const finishFromJs: AgentTool<typeof expressionParameters> = {\n name: 'finish_from_js',\n label: 'Deliver JavaScript value',\n description:\n 'Deliver an existing value from the persistent REPL without printing or rewriting it. Expression must match the result schema of finish. For a string result use JSON.stringify(records) or an existing text variable. Evaluated once; ends the task after validation.',\n parameters: expressionParameters,\n executionMode: 'sequential',\n replay: 'never',\n execute: async (_id, params: { expression: string }, signal) =>\n acceptResult(\n await runtime.readResult(params.expression, config.cellTimeoutMs ?? 30_000, signal),\n signal,\n ),\n };\n const checkBudgets = (messages: AgentMessage[], systemPrompt: string) => {\n if (steps >= maxSteps) stopped = 'max_steps';\n if (\n options.maxCostUsd !== undefined &&\n sumUsage(messages.slice(previousMessages)).cost.total +\n retriedUsage.cost.total +\n context.usage.reduce((sum, usage) => sum + usage.cost.total, 0) >=\n options.maxCostUsd\n )\n stopped = 'cost_limit';\n if (options.compaction === false && !context.fits(messages, systemPrompt))\n stopped = 'context_limit';\n return stopped !== undefined;\n };\n const observer = options.observe\n ? new Observer(\n options.observe,\n positiveInteger('observerTimeoutMs', options.observerTimeoutMs ?? 3000),\n )\n : undefined;\n const journalGuidance = session?.eventsPath\n ? `Run journal (JSON path): ${JSON.stringify(session.eventsPath)}\\nRead relevant JSONL events to recover original tool execution times and observations. Event timestamps are not simultaneous page snapshots; never replace missing capture times with report time.\\n`\n : '';\n const agent = new Agent({\n streamFn: deadlineStream(\n (selected, request, settings) =>\n config.streamFn(selected, redact(request, config.redact ?? []), {\n ...settings,\n maxTokens: Math.min(selected.maxTokens, 32768, Math.floor(selected.contextWindow * 0.15)),\n }),\n config.modelTimeoutMs ?? 300_000,\n ),\n initialState: {\n model,\n messages: session?.messages ?? [],\n systemPrompt: `${SYSTEM_PROMPT}\\nWorkspace directory (JSON string): ${JSON.stringify(workspace)}. Relative file-tool paths and the JavaScript working directory start here. Save deliverables inside this directory; files outside it are not included by BrowserUse.files(). Use relative paths or the exact workspace value, not a guessed parent directory.\\n${journalGuidance}${config.sensitiveData ? `Named secrets (values withheld): ${JSON.stringify(Object.fromEntries(Object.entries(config.sensitiveData).map(([name, entry]) => [name, entry.domains])))}. Use await fillSecret(name, backendNodeId, page) on an input found in the AX tree. Never read back, print or save credentials.\\n` : ''}${config.instructions ?? ''}`,\n thinkingLevel: config.reasoning ?? 'medium',\n tools: [\n javascript,\n finish,\n finishFromJs,\n ...(config.researchTools ? researchTools(workspace, config.cellTimeoutMs ?? 30_000) : []),\n ...(config.tools ?? []),\n ],\n },\n toolExecution: 'sequential',\n transformContext: async (messages, signal) => {\n try {\n if (!compactionFailed) await context.prepare(messages, agent.state.systemPrompt, signal);\n } catch (error) {\n compactionFailed = true;\n warnings.push(`Compaction unavailable: ${String(error)}`);\n }\n signal?.throwIfAborted();\n if (!context.fits(messages, agent.state.systemPrompt)) {\n stopped = 'context_limit';\n throw new Error('Context limit reached; checkpoint files and transcript retained.');\n }\n if (\n options.maxCostUsd !== undefined &&\n sumUsage(messages.slice(previousMessages)).cost.total +\n retriedUsage.cost.total +\n context.usage.reduce((sum, u) => sum + u.cost.total, 0) >=\n options.maxCostUsd\n ) {\n stopped = 'cost_limit';\n throw new Error('Cost limit reached during context preparation.');\n }\n return context.project(messages);\n },\n prepareNextTurnWithContext: ({ context: current }) => {\n if (\n !finalizing &&\n ((maxSteps >= 2 && steps >= maxSteps - 1) ||\n Date.now() - start >= timeoutMs * 0.9 ||\n (options.maxCostUsd !== undefined &&\n sumUsage(current.messages.slice(previousMessages)).cost.total +\n retriedUsage.cost.total +\n context.usage.reduce((sum, usage) => sum + usage.cost.total, 0) >=\n options.maxCostUsd * 0.9))\n ) {\n finalizing = true;\n current.messages.push({\n role: 'user',\n content:\n 'Budget nearly exhausted. Deliver the verified result now with finish/finish_from_js. Reference saved files; explicitly list missing evidence. Do not perform more browser actions.',\n timestamp: Date.now(),\n });\n agent.state.tools = [finish, finishFromJs];\n return { context: { ...current, tools: [finish, finishFromJs] } };\n }\n return undefined;\n },\n beforeToolCall: async (call, signal) => {\n await session?.control.checkpoint(signal);\n if (completion || stopped || signal?.aborted)\n return { block: true, reason: 'The run has ended.', terminate: true };\n return config.beforeToolCall\n ? bounded(() => config.beforeToolCall!(call, signal), hookTimeout, signal)\n : undefined;\n },\n afterToolCall: async (call, signal) => {\n const failure = cellFailures.get(call.toolCall.id);\n cellFailures.delete(call.toolCall.id);\n const evidence = failure\n ? {\n content: [...call.result.content, ...failure.result.images],\n details: {\n outputFile: failure.result.outputFile,\n targetId: failure.result.targetId,\n observationTargetId: failure.result.observationTargetId,\n stateReset: failure.stateReset,\n },\n }\n : undefined;\n // Completion validation belongs in validateResult, not a post-effect override.\n if (\n !config.afterToolCall ||\n call.toolCall.name === 'finish' ||\n call.toolCall.name === 'finish_from_js'\n )\n return evidence;\n const observed = evidence ? { ...call, result: { ...call.result, ...evidence } } : call;\n const override = await bounded(\n () => config.afterToolCall!(observed, signal),\n hookTimeout,\n signal,\n );\n return { ...evidence, ...override };\n },\n shouldStopAfterTurn: ({ context }) => {\n if (completion || stopped) return true;\n return checkBudgets(context.messages, context.systemPrompt) || finishRepairs > 0;\n },\n });\n if (session)\n session.control.steer = (text) =>\n agent.steer({ role: 'user', content: text, timestamp: Date.now() });\n agent.subscribe((event) => {\n if (event.type === 'turn_start') steps++;\n });\n if (options.onEvent)\n agent.subscribe((event, signal) =>\n bounded(() => options.onEvent!(event, signal), hookTimeout, signal),\n );\n agent.subscribe((event) => {\n if (event.type === 'tool_execution_end') observer?.push(event);\n });\n const cancel = () => {\n stopped = 'cancelled';\n agent.abort();\n };\n options.signal?.addEventListener('abort', cancel, { once: true });\n const timer = setTimeout(() => {\n stopped = 'timeout';\n agent.abort();\n }, timeoutMs);\n let error: string | undefined;\n try {\n if (options.signal?.aborted) stopped = 'cancelled';\n else if (\n !context.fits(\n [...agent.state.messages, { role: 'user', content: task, timestamp: Date.now() }],\n agent.state.systemPrompt,\n ) &&\n agent.state.messages.length === 0\n )\n stopped = 'context_limit';\n else if (\n !checkBudgets(\n [...agent.state.messages, { role: 'user', content: task, timestamp: Date.now() }],\n agent.state.systemPrompt,\n )\n ) {\n await agent.prompt(task);\n const failed = agent.state.messages.at(-1);\n if (\n failed?.role === 'assistant' &&\n failed.stopReason === 'error' &&\n (/^OpenAI API error \\(5\\d{2}\\): /.test(failed.errorMessage ?? '') ||\n failed.errorMessage === 'Connection error.' ||\n /stream ended before a terminal|Model stream exceeded|terminated|ECONNRESET|socket hang up|Unable to verify model access right now\\. Please retry\\./i.test(\n failed.errorMessage ?? '',\n ) ||\n /^(?:(?:server_error|unknown): )?Sorry, something went wrong\\.$/.test(\n failed.errorMessage ?? '',\n ) ||\n failed.errorMessage?.startsWith(\n 'An error occurred while processing your request. You can retry your request,',\n )) &&\n !checkBudgets(agent.state.messages, agent.state.systemPrompt) &&\n !options.signal?.aborted\n ) {\n // Pi never executes tool calls from an error response. Retry inference once, not actions.\n providerRetries = 1;\n retriedUsage = failed.usage;\n agent.state.messages = agent.state.messages.slice(0, -1);\n await agent.continue();\n }\n const ending = agent.state.messages.findLast((m) => m.role === 'assistant');\n // One delivery-only repair. The original timer, transcript and budgets remain in force.\n if (!completion && !stopped && ending?.role === 'assistant' && ending.stopReason === 'stop') {\n const repair: AgentMessage = {\n role: 'user',\n content: [\n {\n type: 'text',\n text: 'The run ended without a validated delivery. Use finish_from_js for an existing result or finish for a concise answer. Preserve all verified records; report missing evidence honestly. Do not repeat browser actions. This is the single delivery repair turn.',\n },\n ],\n timestamp: Date.now(),\n };\n if (!checkBudgets([...agent.state.messages, repair], agent.state.systemPrompt)) {\n finishRepairs = 1;\n agent.state.tools = [finish, finishFromJs];\n await agent.prompt(repair);\n }\n }\n }\n } catch (cause) {\n error = cause instanceof Error ? cause.message : String(cause);\n stopped ??= 'error';\n } finally {\n clearTimeout(timer);\n await observer?.close(stopped === 'cancelled' || stopped === 'timeout');\n warnings.push(...(observer?.warnings ?? []));\n options.signal?.removeEventListener('abort', cancel);\n session?.save(context.project(agent.state.messages));\n session?.control.finish();\n }\n const last = agent.state.messages.slice(previousMessages).findLast((m) => m.role === 'assistant');\n // A failed delivery repair must not erase useful text from the original ending.\n const text =\n agent.state.messages\n .slice(previousMessages)\n .filter((m) => m.role === 'assistant')\n .map((m) =>\n m.content\n .filter((c) => c.type === 'text')\n .map((c) => c.text)\n .join('\\n'),\n )\n .findLast((value) => value.trim().length > 0) ?? '';\n const usage = sumUsage(agent.state.messages.slice(previousMessages));\n for (const extra of [retriedUsage, ...context.usage]) {\n for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens'] as const)\n usage[key] += extra[key];\n for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'total'] as const)\n usage.cost[key] += extra.cost[key];\n }\n const metrics = {\n steps,\n finishRepairs,\n durationMs: Date.now() - start,\n usage,\n compactions: context.compactions,\n providerRetries,\n ...(warnings.length ? { warnings } : {}),\n workspace,\n model: config.model,\n };\n if (completion && !stopped) return { ...metrics, status: 'completed', ...completion };\n if (\n last?.role === 'assistant' &&\n (last.stopReason === 'error' || last.stopReason === 'aborted')\n ) {\n stopped ??= 'error';\n error ??= last.errorMessage ?? 'Model request failed.';\n }\n const observation = agent.state.messages\n .slice(previousMessages)\n .findLast((m) => m.role === 'toolResult');\n const fallback =\n observation?.role === 'toolResult'\n ? observation.content\n .filter((c) => c.type === 'text')\n .map((c) => c.text)\n .join('\\n')\n : '';\n return {\n ...metrics,\n status: stopped ?? 'incomplete',\n text: text || (fallback ? `Last tool observation (unfinished):\\n${fallback}` : ''),\n ...(completion\n ? { partial: { value: completion.output } }\n : runtime.partial\n ? { partial: structuredClone(runtime.partial) }\n : {}),\n ...(error ? { error } : {}),\n };\n}\n"]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface LocalBrowserOptions {
|
|
2
|
+
headless?: boolean;
|
|
3
|
+
channel?: 'chrome' | 'msedge';
|
|
4
|
+
executablePath?: string;
|
|
5
|
+
profileDir?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CloudBrowserOptions {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
profileId?: string;
|
|
10
|
+
timeoutMinutes?: number;
|
|
11
|
+
proxyCountryCode?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ChromeBrowserOptions {
|
|
14
|
+
cdpUrl?: string;
|
|
15
|
+
/** User-data root containing DevToolsActivePort, not its Default subdirectory. */
|
|
16
|
+
profileDir?: string;
|
|
17
|
+
targetId?: string;
|
|
18
|
+
/** macOS only: accept Chrome’s exact remote-debugging sheet while connecting. */
|
|
19
|
+
approveConnection?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type BrowserOptions = ({
|
|
22
|
+
kind: 'cloud';
|
|
23
|
+
} & CloudBrowserOptions) | ({
|
|
24
|
+
kind: 'chromium';
|
|
25
|
+
} & LocalBrowserOptions) | ({
|
|
26
|
+
kind: 'chrome';
|
|
27
|
+
} & ChromeBrowserOptions) | ({
|
|
28
|
+
kind?: never;
|
|
29
|
+
cdpUrl: string;
|
|
30
|
+
targetId?: string;
|
|
31
|
+
} & {
|
|
32
|
+
[K in keyof LocalBrowserOptions]?: never;
|
|
33
|
+
}) | ({
|
|
34
|
+
kind?: never;
|
|
35
|
+
cdpUrl?: never;
|
|
36
|
+
targetId?: never;
|
|
37
|
+
} & LocalBrowserOptions);
|
|
38
|
+
/** Declarative browser choices. BrowserUse owns the resulting connection lifecycle. */
|
|
39
|
+
export declare const Browser: {
|
|
40
|
+
cloud: (options: CloudBrowserOptions) => BrowserOptions;
|
|
41
|
+
chromium: (options?: LocalBrowserOptions) => BrowserOptions;
|
|
42
|
+
chrome: (options?: ChromeBrowserOptions) => BrowserOptions;
|
|
43
|
+
};
|
|
44
|
+
/** Same profile discovery convention as Browser Harness on macOS, Linux and Windows. */
|
|
45
|
+
export declare function chromeProfileDirs(platform?: NodeJS.Platform, home?: string, local?: string | undefined): string[];
|
|
46
|
+
/** Local Chrome has an isolated temporary profile; external Chrome always belongs to the caller. */
|
|
47
|
+
export declare function openBrowser(options?: BrowserOptions): Promise<{
|
|
48
|
+
endpoint: string;
|
|
49
|
+
close: () => Promise<void>;
|
|
50
|
+
}>;
|
|
51
|
+
/** Narrow AX action from Browser Harness. Never enables debugging or grants Accessibility. */
|
|
52
|
+
export declare function approveChromeConnection(signal: AbortSignal): Promise<string>;
|