Skip to content

Commit f6cb76e

Browse files
committed
Integrate BrowserUse Search
1 parent 07c08cd commit f6cb76e

File tree

5 files changed

+269
-84
lines changed

5 files changed

+269
-84
lines changed

typescript/agent/.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
AI_GATEWAY_API_KEY=""
1+
#
2+
AI_GATEWAY_API_KEY=""
3+
4+
#
5+
BROWSER_USE_API_KEY=""

typescript/agent/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@radix-ui/react-tooltip": "^1.2.8",
2121
"@radix-ui/react-use-controllable-state": "^1.2.2",
2222
"ai": "^5.0.13",
23+
"browser-use-sdk": "0.1.1",
2324
"class-variance-authority": "^0.7.1",
2425
"clsx": "^2.1.1",
2526
"embla-carousel-react": "^8.6.0",
@@ -36,7 +37,7 @@
3637
"remark-math": "^6.0.0",
3738
"tailwind-merge": "^3.3.1",
3839
"use-stick-to-bottom": "^1.1.1",
39-
"zod": "^3.25.76"
40+
"zod": "^4"
4041
},
4142
"devDependencies": {
4243
"@eslint/eslintrc": "^3",

typescript/agent/pnpm-lock.yaml

Lines changed: 35 additions & 23 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

typescript/agent/src/app/api/chat/route.ts

Lines changed: 154 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,165 @@
1-
import { streamText, UIMessage, convertToModelMessages } from "ai";
1+
import {
2+
streamText,
3+
UIMessage,
4+
convertToModelMessages,
5+
tool,
6+
ToolSet,
7+
InferUITools,
8+
UIDataTypes,
9+
stepCountIs,
10+
} from "ai";
11+
import { z } from "zod";
12+
import { BrowserUse } from "browser-use-sdk";
213

3-
// Allow streaming responses up to 30 seconds
4-
export const maxDuration = 30;
14+
// Allow streaming responses up to 5 minutes
15+
export const maxDuration = 300;
16+
17+
const bu = new BrowserUse({
18+
apiKey: process.env.BROWSER_USE_API_KEY,
19+
});
20+
21+
type TaskStatus =
22+
| {
23+
status: "running";
24+
25+
lastStep: BrowserUse.Tasks.TaskView.Step;
26+
liveUrl: string | null;
27+
}
28+
| {
29+
status: "done";
30+
31+
output: string;
32+
liveUrl: string;
33+
34+
sessionId: string;
35+
};
36+
37+
const tools = {
38+
runTask: tool({
39+
description: "Run a task in a web browser.",
40+
inputSchema: z.object({
41+
task: z.string(),
42+
}),
43+
async *execute({ task }) {
44+
// Create Task
45+
const rsp = await bu.tasks.create({ task: task });
46+
47+
poll: do {
48+
// Wait for Task to Finish
49+
const status = (await bu.tasks.retrieve(rsp.id, { statusOnly: false })) as BrowserUse.Tasks.TaskView;
50+
51+
switch (status.status) {
52+
case "started":
53+
case "paused":
54+
case "stopped":
55+
if (status.steps == null || status.steps.length === 0) {
56+
break;
57+
}
58+
59+
const lastStep = status.steps[status.steps.length - 1];
60+
61+
yield {
62+
status: "running",
63+
lastStep: lastStep,
64+
liveUrl: status.sessionLiveUrl ? status.sessionLiveUrl : null,
65+
} satisfies TaskStatus;
66+
67+
await new Promise((resolve) => setTimeout(resolve, 1000));
68+
69+
break;
70+
71+
case "finished":
72+
if (status.sessionLiveUrl == null) {
73+
break;
74+
}
75+
76+
yield {
77+
status: "done",
78+
output: status.doneOutput,
79+
liveUrl: status.sessionLiveUrl,
80+
sessionId: status.sessionId,
81+
} satisfies TaskStatus;
82+
83+
break poll;
84+
85+
default:
86+
throw new Error(`Unknown status: ${status.status}`);
87+
}
88+
} while (true);
89+
},
90+
}),
91+
continueTask: tool({
92+
description: "Continue a task in a web browser.",
93+
inputSchema: z.object({
94+
sessionId: z.string(),
95+
task: z.string(),
96+
}),
97+
async *execute({ sessionId, task }) {
98+
// Create Task
99+
const rsp = await bu.tasks.create({ task: task, browserSettings: { sessionId: sessionId } });
100+
101+
poll: do {
102+
// Wait for Task to Finish
103+
const status = (await bu.tasks.retrieve(rsp.id, { statusOnly: false })) as BrowserUse.Tasks.TaskView;
104+
105+
switch (status.status) {
106+
case "started":
107+
case "paused":
108+
case "stopped":
109+
if (status.steps == null || status.steps.length === 0) {
110+
break;
111+
}
112+
113+
const lastStep = status.steps[status.steps.length - 1];
114+
115+
yield {
116+
status: "running",
117+
lastStep: lastStep,
118+
liveUrl: status.sessionLiveUrl ? status.sessionLiveUrl : null,
119+
} satisfies TaskStatus;
120+
121+
await new Promise((resolve) => setTimeout(resolve, 1000));
122+
123+
break;
124+
125+
case "finished":
126+
if (status.sessionLiveUrl == null) {
127+
break;
128+
}
129+
130+
yield {
131+
status: "done",
132+
output: status.doneOutput,
133+
liveUrl: status.sessionLiveUrl,
134+
sessionId: status.sessionId,
135+
} satisfies TaskStatus;
136+
137+
break poll;
138+
139+
default:
140+
throw new Error(`Unknown status: ${status.status}`);
141+
}
142+
} while (true);
143+
},
144+
}),
145+
} satisfies ToolSet;
146+
147+
export type ChatTools = InferUITools<typeof tools>;
148+
149+
export type ChatMessage = UIMessage<never, UIDataTypes, ChatTools>;
150+
151+
// ROUTE
5152

6153
export async function POST(req: Request) {
7-
const {
8-
messages,
9-
model,
10-
webSearch,
11-
}: { messages: UIMessage[]; model: string; webSearch: boolean } =
12-
await req.json();
154+
const { messages, model, webSearch }: { messages: UIMessage[]; model: string; webSearch: boolean } = await req.json();
13155

14156
const result = streamText({
15157
model: webSearch ? "perplexity/sonar" : model,
16158
messages: convertToModelMessages(messages),
17159
system:
18-
"You are a helpful assistant that can answer questions and help with tasks",
160+
"You are a helpful assistant that can answer questions and help with tasks. You can use the tools provided to you to help you answer questions and help with tasks.",
161+
tools: tools,
162+
stopWhen: stepCountIs(15),
19163
});
20164

21165
// send sources and reasoning back to the client

0 commit comments

Comments
 (0)