Skip to content

Commit fbd00db

Browse files
committed
r2 uploads
1 parent ab9a15c commit fbd00db

File tree

7 files changed

+236
-19
lines changed

7 files changed

+236
-19
lines changed

lib/asyncio.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from functools import partial
2+
3+
import asyncer
4+
5+
syncify = partial(asyncer.syncify, raise_sync_error=False)
6+
asyncify = partial(asyncer.asyncify, abandon_on_cancel=True)

lib/browser.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import logging
2+
from pathlib import Path
23
from os import environ as env
34

4-
from browser_use import BrowserProfile
5+
from browser_use import Agent, BrowserProfile
56
from kernel import AsyncKernel, KernelContext
67
from kernel.types import BrowserCreateParams
78

@@ -14,6 +15,12 @@
1415
logger = logging.getLogger(__name__)
1516

1617

18+
def downloaded_files(agent: Agent) -> list[Path]:
19+
if downloads_path := agent.browser_profile.downloads_path:
20+
return list(Path(downloads_path).glob("*"))
21+
return []
22+
23+
1724
async def create_browser(ctx: KernelContext, request: BrowserAgentRequest):
1825
invocation_id = ctx.invocation_id
1926
headless = request.headless

lib/models.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from pathlib import Path
2-
from browser_use import Agent, AgentHistoryList
1+
import typing as t
2+
3+
from browser_use.agent.views import AgentHistoryList
34
from pydantic import BaseModel
45

56
from lib.ai import ModelProvider
@@ -24,21 +25,20 @@ class BrowserAgentRequest(BaseModel):
2425

2526
class BrowserAgentResponse(BaseModel):
2627
session: str
28+
success: bool
2729
duration: float
2830
result: str
29-
files: list[str]
30-
errors: list[str]
31+
downloads: dict[str, str]
3132

3233
@classmethod
33-
def build(cls, session: str, agent: Agent, history: AgentHistoryList):
34-
files: list[str] = []
35-
if downloads_path := agent.browser_profile.downloads_path:
36-
files = [str(path) for path in Path(downloads_path).glob("*")]
37-
34+
def from_run(
35+
cls,
36+
run: AgentHistoryList,
37+
**kwargs,
38+
) -> t.Self:
3839
return cls(
39-
session=session,
40-
duration=history.total_duration_seconds(),
41-
result=history.final_result(),
42-
files=files,
43-
errors=list(filter(bool, history.errors())),
40+
success=run.is_successful(),
41+
duration=run.total_duration_seconds(),
42+
result=run.final_result(),
43+
**kwargs,
4444
)

lib/storage.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import asyncio
2+
import typing as t
3+
from os import environ as env
4+
from pathlib import Path
5+
6+
import boto3
7+
import orjson
8+
from botocore.client import Config
9+
10+
from lib.asyncio import asyncify
11+
12+
BUCKET = env.get("R2_S3_BUCKET", "browser-agent")
13+
PRESIGNED_URL_EXPIRES_IN = 24 * 60 * 60 # 12 hours
14+
15+
client = boto3.client(
16+
service_name="s3",
17+
endpoint_url=env["R2_S3_ENDPOINT_URL"],
18+
aws_access_key_id=env["R2_S3_ACCESS_KEY_ID"],
19+
aws_secret_access_key=env["R2_S3_SECRET_ACCESS_KEY"],
20+
region_name="auto",
21+
config=Config(signature_version="s3v4"),
22+
)
23+
24+
25+
@asyncify
26+
def upload_file(file: Path | str, key: str):
27+
client.upload_file(
28+
Bucket=BUCKET,
29+
File=str(file),
30+
Key=key,
31+
)
32+
return client.generate_presigned_url(
33+
"get_object",
34+
Params={"Bucket": BUCKET, "Key": key},
35+
ExpiresIn=PRESIGNED_URL_EXPIRES_IN,
36+
)
37+
38+
39+
@asyncify
40+
def upload_json(data: t.Any, key: str) -> str:
41+
client.put_object(
42+
Bucket=BUCKET,
43+
Key=key,
44+
Body=orjson.dumps(data, option=orjson.OPT_INDENT_2),
45+
)
46+
return client.generate_presigned_url(
47+
"get_object",
48+
Params={"Bucket": BUCKET, "Key": key},
49+
ExpiresIn=PRESIGNED_URL_EXPIRES_IN,
50+
)
51+
52+
53+
async def upload_files(
54+
dir: str,
55+
files: list[Path | str],
56+
) -> dict[str, str]:
57+
keys = [f"{dir}/{Path(f).name}" for f in files]
58+
urls = await asyncio.gather(*[upload_file(f, k) for f, k in zip(files, keys)])
59+
return dict(zip(keys, urls))

main.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import asyncio
12
import logging
23

34
from browser_use import Agent
45
from kernel import App, KernelContext
56
from zenbase_llml import llml
67

78
from lib.ai import AGENT_INSTRUCTIONS, ChatFactory
8-
from lib.browser import create_browser
9+
from lib.browser import create_browser, downloaded_files
910
from lib.models import BrowserAgentRequest, BrowserAgentResponse
11+
from lib.storage import upload_files, upload_json
1012

1113
logger = logging.getLogger(__name__)
1214

@@ -31,7 +33,7 @@ async def perform(ctx: KernelContext, params: dict):
3133
"input": request.input,
3234
"notes": """
3335
Your browser will automatically:
34-
1. Download the PDF file upon viewing it. Just wait for it.
36+
1. Download the PDF file upon viewing it. Just wait for it. You do not need to read the PDF.
3537
2. Solve CAPTCHAs or similar tests. Just wait for it.
3638
""",
3739
}
@@ -44,7 +46,16 @@ async def perform(ctx: KernelContext, params: dict):
4446
flash_mode=request.flash,
4547
)
4648

47-
history = await agent.run(max_steps=request.max_steps)
49+
trajectory = await agent.run(max_steps=request.max_steps)
4850

49-
response = BrowserAgentResponse.build(session, agent, history)
51+
(downloads,) = await asyncio.gather(
52+
upload_files(dir=session, files=downloaded_files(agent)),
53+
upload_json(trajectory.model_dump(), key=f"{session}/trajectory.json"),
54+
)
55+
56+
response = BrowserAgentResponse.from_run(
57+
trajectory,
58+
session=session,
59+
downloads=downloads,
60+
)
5061
return response.model_dump()

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
88
"anyio>=4.10.0",
9+
"asyncer>=0.0.8",
10+
"boto3>=1.40.25",
911
"browser-use>=0.7.2",
1012
"kernel>=0.11.0",
13+
"orjson>=3.11.3",
1114
"pydantic>=2.10.6",
1215
"sorcery>=0.2.2",
1316
"zenbase-llml>=0.4.0",

0 commit comments

Comments
 (0)