Skip to content

Commit 0e584d0

Browse files
committed
fix: address OpenAPI review feedback
1 parent f85e8e2 commit 0e584d0

17 files changed

Lines changed: 5070 additions & 5897 deletions

File tree

RUNBOOK.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,4 @@ Follow existing patterns in the codebase. Read before writing.
107107
- **Python SDK type safety**: All resource methods return Pydantic model instances (via `model_validate()`), NOT dicts.
108108
- **Structured output**: V2 only. Python auto-converts Pydantic models via `output_schema`. TS uses Zod schemas via `{ schema }` option.
109109
- **Polling**: `await client.run()` polls `tasks.status()` (lightweight). `for await`/`for step in client.stream()` polls full `tasks.get()` and yields new `TaskStepView` steps.
110-
- **docs/openapi dir**: `task snapshot:save` calls `task docs:sync` which requires `docs/openapi/` to exist. Create it with `mkdir -p docs/openapi` if missing (e.g., on fresh clone).
110+
- **Docs OpenAPI dirs**: `task snapshot:save` calls `task docs:sync`, which updates both `docs/openapi/` and the `docs/cloud/openapi/` files consumed by Mintlify. Both directories must exist on a fresh clone.

Taskfile.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ tasks:
358358
- cp snapshots/v2.json docs/openapi/v2.json
359359
- cp snapshots/v3.json docs/openapi/v3.json
360360
- cp snapshots/v4.json docs/openapi/v4.json
361+
- python3 -m json.tool --indent 4 snapshots/v2.json docs/cloud/openapi/v2.json
362+
- python3 -m json.tool --indent 4 snapshots/v3.json docs/cloud/openapi/v3.json
363+
- python3 -m json.tool --indent 4 snapshots/v4.json docs/cloud/openapi/v4.json
361364

362365
docs:dev:
363366
desc: Start Mintlify docs dev server

browser-use-node/src/v2/resources/browsers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export class Browsers {
2828

2929
/** Create a new browser session. */
3030
create(body: CreateBrowserBody = {}): Promise<BrowserSessionItemView> {
31+
if (body.metadata && Object.keys(body.metadata).length > 10) {
32+
throw new RangeError("metadata supports at most 10 key-value pairs");
33+
}
3134
if (body.proxyCountryCode) {
3235
body = { ...body, proxyCountryCode: body.proxyCountryCode.toLowerCase() as any };
3336
}

browser-use-node/src/v3/resources/browsers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ export class Browsers {
2727

2828
/** Create a standalone browser session. */
2929
create(body: Partial<CreateBrowserSessionRequest> = {}): Promise<BrowserSessionItemView> {
30+
if (body.metadata && Object.keys(body.metadata).length > 10) {
31+
throw new RangeError("metadata supports at most 10 key-value pairs");
32+
}
3033
return this.http.post<BrowserSessionItemView>("/browsers", body);
3134
}
3235

browser-use-node/tests/browsers.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,17 @@ describe.each([
2424
metadata: ["team", "env=test"],
2525
});
2626
});
27+
28+
it("rejects more than 10 metadata entries", () => {
29+
const http = { post: vi.fn() };
30+
const browsers = new Browsers(http as any);
31+
const metadata = Object.fromEntries(
32+
Array.from({ length: 11 }, (_, index) => [`key-${index}`, `value-${index}`]),
33+
);
34+
35+
expect(() => browsers.create({ metadata })).toThrow(
36+
"metadata supports at most 10 key-value pairs",
37+
);
38+
expect(http.post).not.toHaveBeenCalled();
39+
});
2740
});

browser-use-python/src/browser_use_sdk/v2/resources/browsers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ def _build_create_body(
2727
enable_recording: bool | None = None,
2828
**extra: Any,
2929
) -> dict[str, Any]:
30+
if metadata is not None and len(metadata) > 10:
31+
raise ValueError("metadata supports at most 10 key-value pairs")
32+
3033
body: dict[str, Any] = {}
3134
if profile_id is not None:
3235
body["profileId"] = profile_id

browser-use-python/src/browser_use_sdk/v3/resources/browsers.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
from uuid import UUID
1717

1818

19+
def _validate_metadata(metadata: dict[str, str] | None) -> None:
20+
if metadata is not None and len(metadata) > 10:
21+
raise ValueError("metadata supports at most 10 key-value pairs")
22+
23+
1924
class Browsers:
2025
def __init__(self, http: SyncHttpClient) -> None:
2126
self._http = http
@@ -35,6 +40,7 @@ def create(
3540
**extra: Any,
3641
) -> BrowserSessionItemView:
3742
"""Create a standalone browser session."""
43+
_validate_metadata(metadata)
3844
body: dict[str, Any] = {}
3945
if profile_id is not None:
4046
body["profileId"] = profile_id
@@ -144,6 +150,7 @@ async def create(
144150
**extra: Any,
145151
) -> BrowserSessionItemView:
146152
"""Create a standalone browser session."""
153+
_validate_metadata(metadata)
147154
body: dict[str, Any] = {}
148155
if profile_id is not None:
149156
body["profileId"] = profile_id

browser-use-python/tests/test_browsers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,15 @@ def test_browser_metadata_create_and_list(browser_cls: type[Any]) -> None:
5151
"agentSessionId": None,
5252
"metadata": ["team", "env=test"],
5353
}
54+
55+
56+
@pytest.mark.parametrize("browser_cls", [V2Browsers, V3Browsers])
57+
def test_browser_metadata_rejects_more_than_10_entries(browser_cls: type[Any]) -> None:
58+
http = FakeHttp()
59+
browsers = browser_cls(http)
60+
metadata = {f"key-{index}": f"value-{index}" for index in range(11)}
61+
62+
with pytest.raises(ValueError, match="metadata supports at most 10"):
63+
browsers.create(metadata=metadata)
64+
65+
assert http.calls == []

docs/cloud/openapi/v2.json

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@
192192
"Tasks"
193193
],
194194
"summary": "Create Task",
195-
"description": "Create and start a new task.\n\nYou can either:\n1. Start a new task without a sessionId (auto-creates a session with US proxy by default).\n Note: Tasks without a sessionId are one-off tasks that automatically close the session\n upon completion (keep_alive=false). Use sessionSettings to configure the auto-created\n session (e.g. proxyCountryCode, profileId, screen dimensions).\n2. Start a new task in an existing session (reuse for follow-up tasks or custom configuration)\n\nNote: Without sessionSettings, a US proxy is enabled by default. Providing sessionSettings\noverrides defaults proxy is only enabled if proxyCountryCode is set. For full control over\nsession configuration (e.g. keep_alive), create a session first via POST /sessions with your\ndesired settings, then pass that sessionId when creating tasks.",
195+
"description": "Create and start a new task.\n\nYou can either:\n1. Start a new task without a sessionId (auto-creates a session with US proxy by default).\n Note: Tasks without a sessionId are one-off tasks that automatically close the session\n upon completion (keep_alive=false). Use sessionSettings to configure the auto-created\n session (e.g. proxyCountryCode, profileId, screen dimensions).\n2. Start a new task in an existing session (reuse for follow-up tasks or custom configuration)\n\nNote: Without sessionSettings, a US proxy is enabled by default. Providing sessionSettings\noverrides defaults \u2014 proxy is only enabled if proxyCountryCode is set. For full control over\nsession configuration (e.g. keep_alive), create a session first via POST /sessions with your\ndesired settings, then pass that sessionId when creating tasks.",
196196
"operationId": "create_task_tasks_post",
197197
"security": [
198198
{
@@ -1651,14 +1651,35 @@
16511651
"Browsers"
16521652
],
16531653
"summary": "List Browser Sessions",
1654-
"description": "Get paginated list of browser sessions with optional status filtering.",
1654+
"description": "Get paginated list of browser sessions with optional status filtering.\n\nList responses intentionally omit per-session presigned recording URLs\n(`recording_url` is always `null` here). Each recording URL requires a\nsynchronous boto3 SigV4 signing call (plus an S3 HEAD) that holds the GIL\nand blocks the event loop. With page_size up to the max this CPU-pegs the\nworker and starves the DB pool, cascading into pool exhaustion across the\nfleet. Clients should fetch the recording URL on demand via\n`GET /api/v2/browsers/{id}`, which signs exactly one URL for a single\nsession. Mirrors the v3 sessions list fix (ENG-4904, PR #4621).",
16551655
"operationId": "list_browser_sessions_browsers_get",
16561656
"security": [
16571657
{
16581658
"APIKeyHeader": []
16591659
}
16601660
],
16611661
"parameters": [
1662+
{
1663+
"name": "metadata",
1664+
"in": "query",
1665+
"required": false,
1666+
"schema": {
1667+
"anyOf": [
1668+
{
1669+
"type": "array",
1670+
"items": {
1671+
"type": "string"
1672+
}
1673+
},
1674+
{
1675+
"type": "null"
1676+
}
1677+
],
1678+
"description": "Only browsers tagged with every one of these terms. `key` matches any value; `key=value` matches exactly. Repeat the param to require more than one (AND).",
1679+
"title": "Metadata"
1680+
},
1681+
"description": "Only browsers tagged with every one of these terms. `key` matches any value; `key=value` matches exactly. Repeat the param to require more than one (AND)."
1682+
},
16621683
{
16631684
"name": "pageSize",
16641685
"in": "query",
@@ -1697,6 +1718,23 @@
16971718
],
16981719
"title": "Filterby"
16991720
}
1721+
},
1722+
{
1723+
"name": "agentSessionId",
1724+
"in": "query",
1725+
"required": false,
1726+
"schema": {
1727+
"anyOf": [
1728+
{
1729+
"type": "string",
1730+
"format": "uuid"
1731+
},
1732+
{
1733+
"type": "null"
1734+
}
1735+
],
1736+
"title": "Agentsessionid"
1737+
}
17001738
}
17011739
],
17021740
"responses": {
@@ -3330,6 +3368,12 @@
33303368
"format": "uuid",
33313369
"title": "Project ID",
33323370
"description": "The ID of the project"
3371+
},
3372+
"tracingDisabled": {
3373+
"type": "boolean",
3374+
"title": "Tracing Disabled",
3375+
"description": "Whether third-party LLM tracing is disabled for this project",
3376+
"default": false
33333377
}
33343378
},
33353379
"type": "object",
@@ -3526,7 +3570,16 @@
35263570
}
35273571
],
35283572
"title": "Recording URL",
3529-
"description": "Presigned URL to download the session recording (available after session ends, if recording was enabled)"
3573+
"description": "Presigned URL to download the session recording. Only populated on `GET /api/v2/browsers/{id}`; always `null` in list responses."
3574+
},
3575+
"metadata": {
3576+
"additionalProperties": {
3577+
"type": "string"
3578+
},
3579+
"type": "object",
3580+
"title": "Metadata",
3581+
"description": "Caller-supplied labels set when the browser was created.",
3582+
"default": {}
35303583
}
35313584
},
35323585
"type": "object",
@@ -3698,7 +3751,22 @@
36983751
}
36993752
],
37003753
"title": "Recording URL",
3701-
"description": "Presigned URL to download the session recording (available after session ends, if recording was enabled)"
3754+
"description": "Presigned URL to download the session recording, if recording was enabled. Only populated on GET /api/v2/browsers/{session_id}: the upload starts when the browser stops, so it is never ready in the stop response."
3755+
},
3756+
"recordingAvailable": {
3757+
"type": "boolean",
3758+
"title": "Recording Available",
3759+
"description": "False when a recording can never appear for this session: recording was disabled, or the browser stopped long enough ago that the upload is not coming. Only ever false from proof, so a failed recording lookup leaves it true. Clients polling for `recordingUrl` must stop when this is false.",
3760+
"default": true
3761+
},
3762+
"metadata": {
3763+
"additionalProperties": {
3764+
"type": "string"
3765+
},
3766+
"type": "object",
3767+
"title": "Metadata",
3768+
"description": "Caller-supplied labels set when the browser was created.",
3769+
"default": {}
37023770
}
37033771
},
37043772
"type": "object",
@@ -3775,6 +3843,22 @@
37753843
"description": "Country code for proxy location. Defaults to US. Set to null to disable proxy.",
37763844
"default": "us"
37773845
},
3846+
"metadata": {
3847+
"anyOf": [
3848+
{
3849+
"additionalProperties": {
3850+
"type": "string"
3851+
},
3852+
"maxProperties": 10,
3853+
"type": "object"
3854+
},
3855+
{
3856+
"type": "null"
3857+
}
3858+
],
3859+
"title": "Metadata",
3860+
"description": "Labels for this browser. Up to 10 key-value pairs. Filterable on the browsers list and in the dashboard history."
3861+
},
37783862
"timeout": {
37793863
"type": "integer",
37803864
"title": "Timeout",
@@ -4165,7 +4249,7 @@
41654249
}
41664250
],
41674251
"title": "Thinking Level",
4168-
"description": "Optional model reasoning depth. Omit this field to preserve the provider default. V2 accepts disabled/low/medium/high for browser-use-llm, browser-use-2.0, gemini-2.5-flash, gemini-3-flash-preview, gemini-3.5-flash, gemini-flash-latest, gemini-flash-lite-latest, gpt-5.5, gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, claude-sonnet-5, claude-opus-4-7, claude-opus-4-8, and claude-opus-5; low/medium/high for gemini-2.5-pro, o3, and o4-mini; low/high for gemini-3-pro-preview and gemini-3.1-pro-preview; and disabled only for claude-sonnet-4-20250514, claude-sonnet-4-5-20250929, and claude-opus-4-5-20251101. Other V2 models reject an explicit level. V2 cannot configure GLM thinking or enable fixed-budget Claude thinking."
4252+
"description": "Optional model reasoning depth. Omit this field to preserve the model provider default. Supported values depend on the selected model: most supported Claude models and GPT-5.1+ models support disabled/low/medium/high; Gemini Flash models support all four (disabled maps to Gemini's minimal level); Claude Fable 5, earlier GPT-5 models, Gemini 2.5 Pro, o3/o4, and Grok support low/medium/high; Gemini 3.1 Pro supports low/high; GLM supports disabled/high. Unsupported model/level combinations are rejected. API V2 cannot configure GLM or fixed-budget Claude thinking; use API V3 or V4 for those combinations."
41694253
},
41704254
"vision": {
41714255
"anyOf": [
@@ -4183,7 +4267,7 @@
41834267
},
41844268
"systemPromptExtension": {
41854269
"type": "string",
4186-
"maxLength": 2000,
4270+
"maxLength": 10000,
41874271
"title": "System Prompt Extension",
41884272
"description": "Optional extension to the agent system prompt.",
41894273
"default": ""
@@ -5040,6 +5124,7 @@
50405124
"cf",
50415125
"cg",
50425126
"ch",
5127+
"ci",
50435128
"ck",
50445129
"cl",
50455130
"cm",
@@ -6187,6 +6272,7 @@
61876272
"enum": [
61886273
"browser-use-llm",
61896274
"browser-use-2.0",
6275+
"bu-2-0-mini-preview",
61906276
"gpt-4.1",
61916277
"gpt-4.1-mini",
61926278
"o4-mini",

0 commit comments

Comments
 (0)