Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .fernignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Specify files that shouldn't be modified by Fern
src/wrapper/
test/wrapper/
src/index.ts
examples/
.vscode/

.gitignore
.gitignore
jest.config.mjs
76 changes: 64 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,78 @@ for (const post of result.parsed.posts) {

### Streaming Agent Updates

> You can use the `stream` method to get the latest step taken on every change.

```ts
const task = await browseruse.tasks.createTask({
task: "Search for the top 10 Hacker News posts and return the title and url.",
schema: TaskOutput,
});

for await (const msg of task.stream()) {
switch (msg.status) {
case "started":
case "paused":
case "stopped":
console.log(`running: ${msg}`);
break;
for await (const step of task.stream()) {
console.log(step);
}

case "finished":
console.log(`done:`);
const result = await task.complete();

for (const post of msg.parsed.posts) {
console.log(`${post.title} - ${post.url}`);
}
for (const post of result.parsed.posts) {
console.log(`${post.title} - ${post.url}`);
}
```

### Watching Agent Updates

> You can use the `watch` method to get the latest update on every change.

```ts
const task = await browseruse.tasks.createTask({
task: "Search for the top 10 Hacker News posts and return the title and url.",
schema: TaskOutput,
});

for await (const update of task.watch()) {
console.log(update);

if (update.data.status === "finished") {
for (const post of update.data.parsed.posts) {
console.log(`${post.title} - ${post.url}`);
}
}
}
```

## Webhook Verification

> We encourage you to use the SDK functions that verify and parse webhook events.

```ts
import { verifyWebhookEventSignature, type WebhookAgentTaskStatusUpdatePayload } from "browser-use-sdk";

export async function POST(req: Request) {
const signature = req.headers["x-browser-use-signature"] as string;
const timestamp = req.headers["x-browser-use-timestamp"] as string;

const event = await verifyWebhookEventSignature(
{
body,
signature,
timestamp,
},
{
secret: SECRET_KEY,
},
);

if (!event.ok) {
return;
}

switch (event.event.type) {
case "agent.task.status_update":
break;
case "test":
break;
default:
break;
}
}
Expand Down
6 changes: 3 additions & 3 deletions examples/retrieve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ env();
// gets API Key from environment variable BROWSER_USE_API_KEY
const browseruse = new BrowserUseClient({
apiKey: process.env.BROWSER_USE_API_KEY!,
environment: "https://api.browser-use.com/api/v2",
});

// Basic ---------------------------------------------------------------------
Expand All @@ -23,7 +22,8 @@ async function basic() {
// Create Task
const rsp = await browseruse.tasks.createTask({
task: "What's the weather in SF and what's the temperature?",
agent: { llm: "gemini-2.5-flash" },
llm: "gemini-2.5-flash",
schema: TaskOutput,
});

poll: do {
Expand Down Expand Up @@ -67,7 +67,7 @@ async function structured() {
const rsp = await browseruse.tasks.createTask({
task: "What's the weather in SF and what's the temperature?",
schema: TaskOutput,
agent: { llm: "gpt-4.1" },
llm: "gpt-4.1",
});

poll: do {
Expand Down
3 changes: 1 addition & 2 deletions examples/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ env();
// gets API Key from environment variable BROWSER_USE_API_KEY
const browseruse = new BrowserUseClient({
apiKey: process.env.BROWSER_USE_API_KEY!,
environment: "https://api.browser-use.com/api/v2",
});

// Basic ---------------------------------------------------------------------
Expand Down Expand Up @@ -48,7 +47,7 @@ async function structured() {
const rsp = await browseruse.tasks.createTask({
task: "Search for the top 10 Hacker News posts and return the title and url!",
schema: TaskOutput,
agent: { llm: "gpt-4.1" },
llm: "gpt-4.1",
});

const result = await rsp.complete();
Expand Down
34 changes: 28 additions & 6 deletions examples/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ env();

const browseruse = new BrowserUseClient({
apiKey: process.env.BROWSER_USE_API_KEY!,
environment: "https://api.browser-use.com/api/v2",
});

// Basic ---------------------------------------------------------------------
Expand All @@ -19,16 +18,39 @@ async function basic() {
console.log("Basic: Creating task and starting stream...");

const task = await browseruse.tasks.createTask({
task: "What's the weather in SF and what's the temperature?",
agent: { llm: "gemini-2.5-flash" },
task: "What's the weather and temperature in SF, NY, and LA?",
llm: "gemini-2.5-flash",
});

for await (const msg of task.stream()) {
console.log(msg);
console.log(`task.id: ${task.id}`);

const counter = { current: 0 };

for await (const step of task.stream()) {
console.log(`STREAM 1: ${step.number}`);

counter.current++;

if (counter.current === 2) {
break;
}
}

for await (const step of task.stream()) {
counter.current++;

console.log(`STREAM 2: ${step.number}`);
}

const result = await task.complete();

if (counter.current <= result.steps.length || counter.current !== result.steps.length + 2) {
console.log(`counter.current: ${counter.current}, result.steps.length: ${result.steps.length}`);
throw new Error(
"Basic: Stream does not run as expected! Each step should be relogged whenever stream restarts!",
);
}

console.log("Basic: Stream completed");
console.log(result.output);
}
Expand All @@ -51,8 +73,8 @@ async function structured() {

const task = await browseruse.tasks.createTask({
task: "Extract top 10 Hacker News posts and return the title, url, and score",
llm: "gpt-4.1",
schema: TaskOutput,
agent: { llm: "gpt-4.1" },
});

for await (const msg of task.stream()) {
Expand Down
5 changes: 2 additions & 3 deletions examples/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ env();
// gets API Key from environment variable BROWSER_USE_API_KEY
const browseruse = new BrowserUseClient({
apiKey: process.env.BROWSER_USE_API_KEY!,
environment: "https://api.browser-use.com/api/v2",
});

// Basic ---------------------------------------------------------------------
Expand All @@ -20,7 +19,7 @@ async function basic() {

const task = await browseruse.tasks.createTask({
task: "What's the weather in SF and what's the temperature?",
agent: { llm: "gemini-2.5-flash" },
llm: "gemini-2.5-flash",
});

for await (const msg of task.watch()) {
Expand Down Expand Up @@ -53,7 +52,7 @@ async function structured() {
const task = await browseruse.tasks.createTask({
task: "Extract top 10 Hacker News posts and return the title, url, and score",
schema: TaskOutput,
agent: { llm: "gpt-4.1" },
llm: "gpt-4.1",
});

for await (const msg of task.watch()) {
Expand Down
10 changes: 8 additions & 2 deletions jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default {
"^(\.{1,2}/.*)\.js$": "$1",
},
roots: ["<rootDir>/tests"],
testPathIgnorePatterns: ["\.browser\.(spec|test)\.[jt]sx?$", "/tests/wire/"],
testPathIgnorePatterns: ["\.browser\.(spec|test)\.[jt]sx?$", "/tests/wire/", "/tests/wrapper"],
setupFilesAfterEnv: [],
},
{
Expand All @@ -25,7 +25,6 @@ export default {
testMatch: ["<rootDir>/tests/unit/**/?(*.)+(browser).(spec|test).[jt]s?(x)"],
setupFilesAfterEnv: [],
},
,
{
displayName: "wire",
preset: "ts-jest",
Expand All @@ -36,6 +35,13 @@ export default {
roots: ["<rootDir>/tests/wire"],
setupFilesAfterEnv: ["<rootDir>/tests/mock-server/setup.ts"],
},
{
displayName: "wrapper",
preset: "ts-jest",
testEnvironment: "node",
moduleNameMapper: {},
roots: ["<rootDir>/tests/wrapper"],
},
],
workerThreads: false,
passWithNoTests: true,
Expand Down
7 changes: 5 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export * as BrowserUse from "./api/index.js";
export { BrowserUseError, BrowserUseTimeoutError } from "./errors/index.js";
export { BrowserUseClient } from "./Client.js";
export { BrowserUseEnvironment } from "./environments.js";
export { BrowserUseError, BrowserUseTimeoutError } from "./errors/index.js";
export { BrowserUseClient } from "./wrapper/BrowserUseClient.js";
export type { WrappedTaskFnsWithoutSchema, WrappedTaskFnsWithSchema } from "./wrapper/lib/parse.js";
export { createWebhookSignature, verifyWebhookEventSignature } from "./wrapper/lib/webhooks.js";
export type { Webhook, WebhookAgentTaskStatusUpdatePayload, WebhookTestPayload } from "./wrapper/lib/webhooks.js";
37 changes: 19 additions & 18 deletions src/wrapper/lib/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,25 @@ export function wrapCreateTaskResponse(
} while (true);
}

/**
* Streams the steps of the task and closes when the task is finished.
*
* @description Logs each step of the task exactly once. If you start the stream again, it will log the steps again.
*/
async function* stream(
config?: { interval?: number },
options?: RequestOptions,
): AsyncGenerator<BrowserUse.TaskStepView> {
const steps: { total: number } = { total: 0 };

for await (const msg of _watch(response.id, config, options)) {
for (let i = steps.total; i < msg.data.steps.length; i++) {
yield msg.data.steps[i] satisfies BrowserUse.TaskStepView;
}
steps.total = msg.data.steps.length;
}
}

function watch<T extends ZodType>(
schema: T,
config?: PollConfig,
Expand Down Expand Up @@ -252,24 +271,6 @@ export function wrapCreateTaskResponse(
throw new Error("Task did not finish");
}

async function* stream(
config?: { interval?: number },
options?: RequestOptions,
): AsyncGenerator<BrowserUse.TaskStepView> {
const step: { current: number } = { current: 0 };

const interval = config?.interval ?? 2000;

for await (const msg of _watch(response.id, { interval }, options)) {
if (msg.data.steps.length > step.current) {
step.current = msg.data.steps.length;

const lastStepIdx = msg.data.steps.length - 1;
yield msg.data.steps[lastStepIdx] satisfies BrowserUse.TaskStepView;
}
}
}

// NOTE: Finally, we return the wrapped task response.

if (schema == null) {
Expand Down
Loading