diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6b7b74c..fea3454 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.3.0" + ".": "1.0.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 211f6e3..f35ed26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.0.0 (2025-08-20) + +Full Changelog: [v0.3.0...v1.0.0](https://github.com/browser-use/browser-use-python/compare/v0.3.0...v1.0.0) + ## 0.3.0 (2025-08-20) Full Changelog: [v0.2.0...v0.3.0](https://github.com/browser-use/browser-use-python/compare/v0.2.0...v0.3.0) diff --git a/README.md b/README.md index 894765b..091fe42 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,28 @@ -# Browser Use Python API library +Browser Use Python - -[![PyPI version](https://img.shields.io/pypi/v/browser-use-sdk.svg?label=pypi%20(stable))](https://pypi.org/project/browser-use-sdk/) - -The Browser Use Python library provides convenient access to the Browser Use REST API from any Python 3.8+ -application. The library includes type definitions for all request params and response fields, -and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). - -It is generated with [Stainless](https://www.stainless.com/). - -## Documentation - -The REST API documentation can be found on [docs.browser-use.com](https://docs.browser-use.com/cloud/). The full API of this library can be found in [api.md](api.md). - -## Installation +[![PyPI version]()](https://pypi.org/project/browser-use-sdk/) ```sh -# install from PyPI pip install browser-use-sdk ``` -## Usage +## Quick Start -The full API of this library can be found in [api.md](api.md). +> Get your API Key at [Browser Use Cloud](https://cloud.browser-use.com)! ```python -import os from browser_use_sdk import BrowserUse -client = BrowserUse( - api_key=os.environ.get("BROWSER_USE_API_KEY"), # This is the default and can be omitted -) +client = BrowserUse() -task = client.tasks.create( - task="Search for the top 10 Hacker News posts and return the title and url.", +run = client.tasks.run( + task="Search for the top 10 Hacker News posts and return the title and url." ) -print(task.id) + +run.done_output ``` -While you can provide an `api_key` keyword argument, -we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `BROWSER_USE_API_KEY="My API Key"` to your `.env` file -so that your API Key is not stored in source control. +> The full API of this library can be found in [api.md](api.md). ## Async usage @@ -58,10 +39,10 @@ client = AsyncBrowserUse( async def main() -> None: - task = await client.tasks.create( + task = await client.tasks.run( task="Search for the top 10 Hacker News posts and return the title and url.", ) - print(task.id) + print(task.done_output) asyncio.run(main()) @@ -93,38 +74,80 @@ async def main() -> None: api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: - task = await client.tasks.create( + task = await client.tasks.run( task="Search for the top 10 Hacker News posts and return the title and url.", ) - print(task.id) + print(task.done_output) + + +asyncio.run(main()) +``` + +## Structured Output with Pydantic + +Browser Use Python SDK provides first class support for Pydantic models. + +```py +class HackerNewsPost(BaseModel): + title: str + url: str + +class SearchResult(BaseModel): + posts: List[HackerNewsPost] + +async def main() -> None: + result = await client.tasks.run( + task=""" + Find top 10 Hacker News articles and return the title and url. + """, + structured_output_json=SearchResult, + ) + if structured_result.parsed_output is not None: + print("Top HackerNews Posts:") + for post in structured_result.parsed_output.posts: + print(f" - {post.title} - {post.url}") asyncio.run(main()) ``` -## Using types +## Streaming Updates with Async Iterators -Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: +```py +class HackerNewsPost(BaseModel): + title: str + url: str -- Serializing back into JSON, `model.to_json()` -- Converting to a dictionary, `model.to_dict()` +class SearchResult(BaseModel): + posts: List[HackerNewsPost] -Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. -## Nested params +async def main() -> None: + task = await client.tasks.create( + task=""" + Find top 10 Hacker News articles and return the title and url. + """, + structured_output_json=SearchResult, + ) -Nested parameters are dictionaries, typed using `TypedDict`, for example: + async for update in client.tasks.stream(structured_task.id, structured_output_json=SearchResult): + if len(update.steps) > 0: + last_step = update.steps[-1] + print(f"{update.status}: {last_step.url} ({last_step.next_goal})") + else: + print(f"{update.status}") -```python -from browser_use_sdk import BrowserUse + if update.status == "finished": + if update.parsed_output is None: + print("No output...") + else: + print("Top HackerNews Posts:") + for post in update.parsed_output.posts: + print(f" - {post.title} - {post.url}") -client = BrowserUse() + break -task = client.tasks.create( - task="x", - agent_settings={}, -) -print(task.agent_settings) +asyncio.run(main()) ``` ## Handling errors diff --git a/assets/cloud-banner-python.png b/assets/cloud-banner-python.png new file mode 100644 index 0000000..77e2aee Binary files /dev/null and b/assets/cloud-banner-python.png differ diff --git a/examples/async_create.py b/examples/async_create.py index 311dd01..9cfac77 100755 --- a/examples/async_create.py +++ b/examples/async_create.py @@ -16,7 +16,8 @@ async def create_regular_task() -> None: res = await client.tasks.create( task=""" Find top 10 Hacker News articles and return the title and url. - """ + """, + agent_settings={"llm": "gemini-2.5-flash"}, ) print(f"Regular Task ID: {res.id}") @@ -35,6 +36,7 @@ class SearchResult(BaseModel): task=""" Find top 10 Hacker News articles and return the title and url. """, + agent_settings={"llm": "gpt-4.1"}, structured_output_json=SearchResult, ) diff --git a/examples/async_retrieve.py b/examples/async_retrieve.py index 60e46c0..1868e7e 100755 --- a/examples/async_retrieve.py +++ b/examples/async_retrieve.py @@ -22,7 +22,8 @@ async def retrieve_regular_task() -> None: regular_task = await client.tasks.create( task=""" Find top 10 Hacker News articles and return the title and url. - """ + """, + agent_settings={"llm": "gemini-2.5-flash"}, ) print(f"Regular Task ID: {regular_task.id}") @@ -58,6 +59,7 @@ class SearchResult(BaseModel): task=""" Find top 10 Hacker News articles and return the title and url. """, + agent_settings={"llm": "gpt-4.1"}, structured_output_json=SearchResult, ) diff --git a/examples/async_run.py b/examples/async_run.py index 3f12f9d..ecb1d11 100755 --- a/examples/async_run.py +++ b/examples/async_run.py @@ -16,7 +16,8 @@ async def run_regular_task() -> None: regular_result = await client.tasks.run( task=""" Find top 10 Hacker News articles and return the title and url. - """ + """, + agent_settings={"llm": "gemini-2.5-flash"}, ) print(f"Regular Task ID: {regular_result.id}") @@ -39,6 +40,7 @@ class SearchResult(BaseModel): task=""" Find top 10 Hacker News articles and return the title and url. """, + agent_settings={"llm": "gpt-4.1"}, structured_output_json=SearchResult, ) diff --git a/examples/async_stream.py b/examples/async_stream.py index 0dc923d..772b781 100755 --- a/examples/async_stream.py +++ b/examples/async_stream.py @@ -48,6 +48,7 @@ class SearchResult(BaseModel): task=""" Find top 10 Hacker News articles and return the title and url. """, + agent_settings={"llm": "gpt-4.1"}, structured_output_json=SearchResult, ) diff --git a/examples/create.py b/examples/create.py index 35da8f6..b0af2db 100755 --- a/examples/create.py +++ b/examples/create.py @@ -15,7 +15,8 @@ def create_regular_task() -> None: res = client.tasks.create( task=""" Find top 10 Hacker News articles and return the title and url. - """ + """, + agent_settings={"llm": "gemini-2.5-flash"}, ) print(res.id) @@ -37,6 +38,7 @@ class SearchResult(BaseModel): task=""" Find top 10 Hacker News articles and return the title and url. """, + agent_settings={"llm": "gpt-4.1"}, structured_output_json=SearchResult, ) diff --git a/examples/retrieve.py b/examples/retrieve.py index 6569a5f..e2c4e47 100755 --- a/examples/retrieve.py +++ b/examples/retrieve.py @@ -22,7 +22,8 @@ def retrieve_regular_task() -> None: regular_task = client.tasks.create( task=""" Find top 10 Hacker News articles and return the title and url. - """ + """, + agent_settings={"llm": "gemini-2.5-flash"}, ) print(f"Task ID: {regular_task.id}") @@ -61,6 +62,7 @@ class SearchResult(BaseModel): task=""" Find top 10 Hacker News articles and return the title and url. """, + agent_settings={"llm": "gpt-4.1"}, structured_output_json=SearchResult, ) diff --git a/examples/run.py b/examples/run.py index dfe95ce..14b0c00 100755 --- a/examples/run.py +++ b/examples/run.py @@ -15,7 +15,8 @@ def run_regular_task() -> None: regular_result = client.tasks.run( task=""" Find top 10 Hacker News articles and return the title and url. - """ + """, + agent_settings={"llm": "gemini-2.5-flash"}, ) print(f"Task ID: {regular_result.id}") @@ -41,6 +42,7 @@ class SearchResult(BaseModel): task=""" Find top 10 Hacker News articles and return the title and url. """, + agent_settings={"llm": "gpt-4.1"}, structured_output_json=SearchResult, ) diff --git a/examples/stream.py b/examples/stream.py index eea579b..e017260 100755 --- a/examples/stream.py +++ b/examples/stream.py @@ -31,7 +31,10 @@ def stream_regular_task() -> None: for action in last_step.actions: print(f" - {action}") - print("Done") + if res.status == "finished": + print(res.done_output) + + print("Regular: DONE") stream_regular_task() @@ -50,6 +53,7 @@ class SearchResult(BaseModel): task=""" Find top 10 Hacker News articles and return the title and url. """, + agent_settings={"llm": "gpt-4.1"}, structured_output_json=SearchResult, ) diff --git a/pyproject.toml b/pyproject.toml index 7c9b988..f4106db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "browser-use-sdk" -version = "0.3.0" +version = "1.0.0" description = "The official Python library for the browser-use API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/browser_use_sdk/_version.py b/src/browser_use_sdk/_version.py index 4e9d259..486d1c9 100644 --- a/src/browser_use_sdk/_version.py +++ b/src/browser_use_sdk/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "browser_use_sdk" -__version__ = "0.3.0" # x-release-please-version +__version__ = "1.0.0" # x-release-please-version diff --git a/src/browser_use_sdk/resources/tasks.py b/src/browser_use_sdk/resources/tasks.py index 5cf36c8..8e96d22 100644 --- a/src/browser_use_sdk/resources/tasks.py +++ b/src/browser_use_sdk/resources/tasks.py @@ -1307,8 +1307,16 @@ async def _watch( if res.status == "finished": break - - await asyncio.sleep(interval) + if res.status == "paused": + break + if res.status == "stopped": + break + if res.status == "started": + await asyncio.sleep(interval) + else: + raise ValueError( + f"Expected one of 'finished', 'paused', 'stopped', or 'started' but received {res.status!r}" + ) async def update( self,