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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist-deno
/*.tgz
.idea/

.env
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v22.12.0
41 changes: 41 additions & 0 deletions examples/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env -S npm run tsn -T

import { BrowserUse } from 'browser-use-sdk';
import { TaskView } from 'browser-use-sdk/resources';
import { spinner } from './utils';

// gets API Key from environment variable BROWSER_USE_API_KEY
const browseruse = new BrowserUse();

async function main() {
let log = 'starting';
const stop = spinner(() => log);

// Create Task
const rsp = await browseruse.tasks.create({
task: "What's the weather line in SF and what's the temperature?",
});

poll: do {
// Wait for Task to Finish
const status = (await browseruse.tasks.retrieve(rsp.id, { statusOnly: false })) as TaskView;

switch (status.status) {
case 'started':
case 'paused':
case 'stopped':
log = `agent ${status.status} - live: ${status.sessionLiveUrl}`;

await new Promise((resolve) => setTimeout(resolve, 2000));
break;

case 'finished':
stop();

console.log(status.doneOutput);
break poll;
}
} while (true);
}

main().catch(console.error);
69 changes: 69 additions & 0 deletions examples/structured-output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env -S npm run tsn -T

import { BrowserUse } from 'browser-use-sdk';
import { z } from 'zod';
import { spinner } from './utils';

// gets API Key from environment variable BROWSER_USE_API_KEY
const browseruse = new BrowserUse();

// Define Structured Output Schema
const HackerNewsResponse = z.object({
title: z.string(),
url: z.string(),
score: z.number(),
});

const TaskOutput = z.object({
posts: z.array(HackerNewsResponse),
});

async function main() {
let log = 'starting';
const stop = spinner(() => log);

// Create Task
const rsp = await browseruse.tasks.createWithStructuredOutput({
task: 'Extract top 10 Hacker News posts and return the title, url, and score',
structuredOutputJson: TaskOutput,
});

poll: do {
// Wait for Task to Finish
const status = await browseruse.tasks.retrieveWithStructuredOutput(rsp.id, {
structuredOutputJson: TaskOutput,
});

switch (status.status) {
case 'started':
case 'paused':
case 'stopped': {
const stepsCount = status.steps ? status.steps.length : 0;
const steps = `${stepsCount} steps`;
const lastGoalDescription = stepsCount > 0 ? status.steps![stepsCount - 1]!.nextGoal : undefined;
const lastGoal = lastGoalDescription ? `, last: ${lastGoalDescription}` : '';
const liveUrl = status.sessionLiveUrl ? `, live: ${status.sessionLiveUrl}` : '';

log = `agent ${status.status} (${steps}${lastGoal}${liveUrl}) `;

await new Promise((resolve) => setTimeout(resolve, 2000));

break;
}

case 'finished':
stop();

// Print Structured Output
console.log('TOP POSTS:');

for (const post of status.doneOutput!.posts) {
console.log(` - ${post.title} (${post.score}) ${post.url}`);
}

break poll;
}
} while (true);
}

main().catch(console.error);
28 changes: 28 additions & 0 deletions examples/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];

/**
* Start a spinner that updates the text every 100ms.
*
* @param renderText - A function that returns the text to display.
* @returns A function to stop the spinner.
*/
export function spinner(renderText: () => string): () => void {
let frameIndex = 0;
const interval = setInterval(() => {
const frame = SPINNER_FRAMES[frameIndex++ % SPINNER_FRAMES.length];
const text = `${frame} ${renderText()}`;
if (typeof process.stdout.clearLine === 'function') {
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
}
process.stdout.write(text);
}, 100);

return () => {
clearInterval(interval);
if (typeof process.stdout.clearLine === 'function') {
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
}
};
}
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@
"tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz",
"tsconfig-paths": "^4.0.0",
"typescript": "5.8.3",
"typescript-eslint": "8.31.1"
"typescript-eslint": "8.31.1",
"zod": "^4.0.17"
},
"peerDependencies": {
"zod": "^4.0.17"
},
"exports": {
".": {
Expand Down
58 changes: 58 additions & 0 deletions src/lib/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import z, { type ZodType } from 'zod';
import type { TaskCreateParams, TaskRetrieveParams, TaskView } from '../resources/tasks';

// RUN

export type RunTaskCreateParamsWithStructuredOutput<T extends ZodType> = Omit<
TaskCreateParams,
'structuredOutputJson'
> & {
structuredOutputJson: T;
};

export function stringifyStructuredOutput<T extends ZodType>(
req: RunTaskCreateParamsWithStructuredOutput<T>,
): TaskCreateParams {
return {
...req,
structuredOutputJson: JSON.stringify(z.toJSONSchema(req.structuredOutputJson)),
};
}

// RETRIEVE

export type GetTaskStatusParamsWithStructuredOutput<T extends ZodType> = Omit<
TaskRetrieveParams,
'statusOnly'
> & {
statusOnly?: false;
structuredOutputJson: T;
};

export type TaskViewWithStructuredOutput<T extends ZodType> = Omit<TaskView, 'doneOutput'> & {
doneOutput: z.output<T> | null;
};

export function parseStructuredTaskOutput<T extends ZodType>(
res: TaskView,
body: GetTaskStatusParamsWithStructuredOutput<T>,
): TaskViewWithStructuredOutput<T> {
try {
const parsed = JSON.parse(res.doneOutput);

const response = body.structuredOutputJson.safeParse(parsed);
if (!response.success) {
throw new Error(`Invalid structured output: ${response.error.message}`);
}

return { ...res, doneOutput: response.data };
} catch (e) {
if (e instanceof SyntaxError) {
return {
...res,
doneOutput: null,
};
}
throw e;
}
}
32 changes: 32 additions & 0 deletions src/resources/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import type { ZodType } from 'zod';

import { APIResource } from '../core/resource';
import * as TasksAPI from './tasks';
import { APIPromise } from '../core/api-promise';
import { RequestOptions } from '../internal/request-options';
import { path } from '../internal/utils/path';
import {
parseStructuredTaskOutput,
stringifyStructuredOutput,
type TaskViewWithStructuredOutput,
type GetTaskStatusParamsWithStructuredOutput,
type RunTaskCreateParamsWithStructuredOutput,
} from '../lib/parse';

export class Tasks extends APIResource {
/**
Expand All @@ -14,6 +23,15 @@ export class Tasks extends APIResource {
return this._client.post('/tasks', { body, ...options });
}

createWithStructuredOutput<T extends ZodType>(
body: RunTaskCreateParamsWithStructuredOutput<T>,
options?: RequestOptions,
): APIPromise<TaskViewWithStructuredOutput<T>> {
return this.create(stringifyStructuredOutput(body), options)._thenUnwrap((rsp) =>
parseStructuredTaskOutput(rsp as TaskView, body),
);
}

/**
* Get Task
*/
Expand All @@ -25,6 +43,20 @@ export class Tasks extends APIResource {
return this._client.get(path`/tasks/${taskID}`, { query, ...options });
}

retrieveWithStructuredOutput<T extends ZodType>(
taskID: string,
query: GetTaskStatusParamsWithStructuredOutput<T>,
options?: RequestOptions,
): APIPromise<TaskViewWithStructuredOutput<T>> {
// NOTE: We manually remove structuredOutputJson from the query object because
// it's not a valid Browser Use Cloud parameter.
const { structuredOutputJson, ...rest } = query;

return this.retrieve(taskID, rest, options)._thenUnwrap((rsp) =>
parseStructuredTaskOutput(rsp as TaskView, query),
);
}

/**
* Update Task
*/
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3498,3 +3498,8 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==

zod@^4.0.17:
version "4.0.17"
resolved "https://registry.yarnpkg.com/zod/-/zod-4.0.17.tgz#95931170715f73f7426c385c237b7477750d6c8d"
integrity sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==