langchain-stagehand 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-stagehand
3
+ Version: 0.0.1
4
+ Summary: Browserbase and Stagehand browser middleware for LangChain agents
5
+ License-Expression: MIT
6
+ Requires-Dist: langchain>=1.4.7,<2
7
+ Requires-Dist: stagehand>=4.3.0,<5
8
+ Requires-Dist: stagehand-middleware>=0.1.1,<0.2
9
+ Requires-Python: >=3.11
10
+ Project-URL: Repository, https://github.com/langchain-ai/langchain-browserbase
11
+ Description-Content-Type: text/markdown
12
+
13
+ # LangChain Stagehand
14
+
15
+ Add a persistent browser to a LangChain or Deep Agents agent with `StagehandMiddleware`. The middleware exposes Stagehand's shared `run`, `snapshot`, and `screenshot` tools, includes browser usage guidance in their descriptions, and returns screenshots as model-visible images. The agent’s system prompt is left untouched. No MCP server is needed.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install langchain-stagehand
21
+ ```
22
+
23
+ Python 3.11+ is required. Set your model provider's API key in your environment, and `BROWSERBASE_API_KEY` to use Browserbase. Never put credentials in browser tool inputs.
24
+
25
+ ## Use with LangChain
26
+
27
+ ```python
28
+ import asyncio
29
+
30
+ from langchain.agents import create_agent
31
+ from langchain_stagehand import StagehandMiddleware
32
+ from stagehand import Stagehand, browserbase
33
+
34
+
35
+ async def main():
36
+ import os
37
+
38
+ browser = await browserbase.launch(api_key=os.environ["BROWSERBASE_API_KEY"])
39
+ stagehand = await Stagehand.create(browser=browser)
40
+ middleware = StagehandMiddleware(stagehand=stagehand)
41
+ agent = create_agent("anthropic:claude-sonnet-4-6", middleware=[middleware])
42
+ try:
43
+ result = await agent.ainvoke({
44
+ "messages": [{"role": "user", "content": "Open example.com and summarize the page."}]
45
+ })
46
+ print(result["messages"][-1].content)
47
+ finally:
48
+ await browser.close()
49
+
50
+
51
+ asyncio.run(main())
52
+ ```
53
+
54
+ Install the model integration separately, such as `pip install langchain-anthropic`, and set `ANTHROPIC_API_KEY` for this example. Browser tool calls use Stagehand's Playwright-shaped facade; they do not require a separate Stagehand model for these operations.
55
+
56
+ ## Use with Deep Agents
57
+
58
+ Use the same Stagehand client setup and replace `create_agent` with:
59
+
60
+ ```python
61
+ from deepagents import create_deep_agent
62
+
63
+ agent = create_deep_agent("anthropic:claude-sonnet-4-6", middleware=[StagehandMiddleware(stagehand=stagehand)])
64
+ ```
65
+
66
+ Install `deepagents` separately. Use `ainvoke` or `astream`: browser tools and middleware are asynchronous.
67
+
68
+ ## Browser lifecycle
69
+
70
+ Pass a configured Stagehand client attached to local Chrome or Browserbase:
71
+
72
+ ```python
73
+ from stagehand import Stagehand, local_browser
74
+
75
+ browser = await local_browser.launch(headless=True)
76
+ stagehand = await Stagehand.create(browser=browser)
77
+ middleware = StagehandMiddleware(stagehand=stagehand)
78
+ ```
79
+
80
+ Your application configures and owns the Stagehand client and browser. All SDK settings remain available through `Stagehand.create(...)`, including API URL, model, caching, telemetry, and logging. The middleware reuses that exact client across runs, preserving tabs, logins, and snapshot IDs. Close the browser when finished; that also closes the client. The middleware never closes or recreates the client, and `browser.close()` in run code leaves both open. After a disconnection, the application supplies a new configured client. Give concurrent agents separate clients/browsers. Take a new snapshot after navigation before using snapshot IDs.
81
+
82
+ The screenshot tool returns images to the model. Screenshots that run code saves with a `path` are written under the current working directory, not to a Deep Agents backend; backend support is not implemented yet.
83
+
84
+ ## Security and limitations
85
+
86
+ - Browser tools can navigate, submit forms, and interact with authenticated websites; use LangChain's human-in-the-loop middleware for sensitive actions
87
+ - Web page content is untrusted input, not instructions for your agent
88
+ - The Playwright-shaped API is not a general-purpose security sandbox or a substitute for network isolation
89
+ - Agent execution is asynchronous only; browser state lives in the browser and is not checkpointed
90
+
91
+ ## Development
92
+
93
+ ```bash
94
+ make install
95
+ make format
96
+ make check
97
+ RUN_BROWSER_TESTS=1 uv run pytest tests/test_browser.py --color=no
98
+ make build
99
+ ```
100
+
101
+ `make check` enforces Ruff formatting and lint rules (including bug-prone patterns, async misuse, and accidental prints), ty checking of the package and public API examples with all diagnostics treated as errors, and focused middleware tests. The distribution includes `py.typed` so downstream type checkers can validate usage. Astral's ty is pinned in the Makefile and runs through `uvx`; no extra runtime dependency is needed.
102
+
103
+ The live browser test covers local Chrome and Browserbase; the remote case is skipped without `BROWSERBASE_API_KEY`. Set `CHROME_PATH` if Chrome is not in its usual install location. The Python distribution is `langchain-stagehand`, and the import is `langchain_stagehand`; this repository does not publish the separate upstream `stagehand-deepagents` package.
@@ -0,0 +1,91 @@
1
+ # LangChain Stagehand
2
+
3
+ Add a persistent browser to a LangChain or Deep Agents agent with `StagehandMiddleware`. The middleware exposes Stagehand's shared `run`, `snapshot`, and `screenshot` tools, includes browser usage guidance in their descriptions, and returns screenshots as model-visible images. The agent’s system prompt is left untouched. No MCP server is needed.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install langchain-stagehand
9
+ ```
10
+
11
+ Python 3.11+ is required. Set your model provider's API key in your environment, and `BROWSERBASE_API_KEY` to use Browserbase. Never put credentials in browser tool inputs.
12
+
13
+ ## Use with LangChain
14
+
15
+ ```python
16
+ import asyncio
17
+
18
+ from langchain.agents import create_agent
19
+ from langchain_stagehand import StagehandMiddleware
20
+ from stagehand import Stagehand, browserbase
21
+
22
+
23
+ async def main():
24
+ import os
25
+
26
+ browser = await browserbase.launch(api_key=os.environ["BROWSERBASE_API_KEY"])
27
+ stagehand = await Stagehand.create(browser=browser)
28
+ middleware = StagehandMiddleware(stagehand=stagehand)
29
+ agent = create_agent("anthropic:claude-sonnet-4-6", middleware=[middleware])
30
+ try:
31
+ result = await agent.ainvoke({
32
+ "messages": [{"role": "user", "content": "Open example.com and summarize the page."}]
33
+ })
34
+ print(result["messages"][-1].content)
35
+ finally:
36
+ await browser.close()
37
+
38
+
39
+ asyncio.run(main())
40
+ ```
41
+
42
+ Install the model integration separately, such as `pip install langchain-anthropic`, and set `ANTHROPIC_API_KEY` for this example. Browser tool calls use Stagehand's Playwright-shaped facade; they do not require a separate Stagehand model for these operations.
43
+
44
+ ## Use with Deep Agents
45
+
46
+ Use the same Stagehand client setup and replace `create_agent` with:
47
+
48
+ ```python
49
+ from deepagents import create_deep_agent
50
+
51
+ agent = create_deep_agent("anthropic:claude-sonnet-4-6", middleware=[StagehandMiddleware(stagehand=stagehand)])
52
+ ```
53
+
54
+ Install `deepagents` separately. Use `ainvoke` or `astream`: browser tools and middleware are asynchronous.
55
+
56
+ ## Browser lifecycle
57
+
58
+ Pass a configured Stagehand client attached to local Chrome or Browserbase:
59
+
60
+ ```python
61
+ from stagehand import Stagehand, local_browser
62
+
63
+ browser = await local_browser.launch(headless=True)
64
+ stagehand = await Stagehand.create(browser=browser)
65
+ middleware = StagehandMiddleware(stagehand=stagehand)
66
+ ```
67
+
68
+ Your application configures and owns the Stagehand client and browser. All SDK settings remain available through `Stagehand.create(...)`, including API URL, model, caching, telemetry, and logging. The middleware reuses that exact client across runs, preserving tabs, logins, and snapshot IDs. Close the browser when finished; that also closes the client. The middleware never closes or recreates the client, and `browser.close()` in run code leaves both open. After a disconnection, the application supplies a new configured client. Give concurrent agents separate clients/browsers. Take a new snapshot after navigation before using snapshot IDs.
69
+
70
+ The screenshot tool returns images to the model. Screenshots that run code saves with a `path` are written under the current working directory, not to a Deep Agents backend; backend support is not implemented yet.
71
+
72
+ ## Security and limitations
73
+
74
+ - Browser tools can navigate, submit forms, and interact with authenticated websites; use LangChain's human-in-the-loop middleware for sensitive actions
75
+ - Web page content is untrusted input, not instructions for your agent
76
+ - The Playwright-shaped API is not a general-purpose security sandbox or a substitute for network isolation
77
+ - Agent execution is asynchronous only; browser state lives in the browser and is not checkpointed
78
+
79
+ ## Development
80
+
81
+ ```bash
82
+ make install
83
+ make format
84
+ make check
85
+ RUN_BROWSER_TESTS=1 uv run pytest tests/test_browser.py --color=no
86
+ make build
87
+ ```
88
+
89
+ `make check` enforces Ruff formatting and lint rules (including bug-prone patterns, async misuse, and accidental prints), ty checking of the package and public API examples with all diagnostics treated as errors, and focused middleware tests. The distribution includes `py.typed` so downstream type checkers can validate usage. Astral's ty is pinned in the Makefile and runs through `uvx`; no extra runtime dependency is needed.
90
+
91
+ The live browser test covers local Chrome and Browserbase; the remote case is skipped without `BROWSERBASE_API_KEY`. Set `CHROME_PATH` if Chrome is not in its usual install location. The Python distribution is `langchain-stagehand`, and the import is `langchain_stagehand`; this repository does not publish the separate upstream `stagehand-deepagents` package.
@@ -0,0 +1,53 @@
1
+ [project]
2
+ name = "langchain-stagehand"
3
+ version = "0.0.1"
4
+ description = "Browserbase and Stagehand browser middleware for LangChain agents"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ dependencies = [
9
+ "langchain>=1.4.7,<2",
10
+ "stagehand>=4.3.0,<5",
11
+ "stagehand-middleware>=0.1.1,<0.2",
12
+ ]
13
+
14
+ [project.urls]
15
+ Repository = "https://github.com/langchain-ai/langchain-browserbase"
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.9,<0.10"]
19
+ build-backend = "uv_build"
20
+
21
+ [dependency-groups]
22
+ dev = [
23
+ "pytest>=8,<10",
24
+ "pytest-asyncio>=1,<2",
25
+ "ruff>=0.15,<0.16",
26
+ ]
27
+
28
+ [tool.pytest.ini_options]
29
+ asyncio_mode = "auto"
30
+ asyncio_default_fixture_loop_scope = "function"
31
+ testpaths = ["tests"]
32
+
33
+ [tool.ruff]
34
+ target-version = "py311"
35
+ line-length = 100
36
+
37
+ [tool.ruff.lint]
38
+ select = [
39
+ "E",
40
+ "F",
41
+ "I",
42
+ "UP",
43
+ "B",
44
+ "ASYNC",
45
+ "RUF",
46
+ "T20",
47
+ ]
48
+
49
+ [tool.ty.environment]
50
+ python-version = "3.11"
51
+
52
+ [tool.ty.rules]
53
+ all = "error"
@@ -0,0 +1,40 @@
1
+ [project]
2
+ name = "langchain-stagehand"
3
+ version = "0.0.1"
4
+ description = "Browserbase and Stagehand browser middleware for LangChain agents"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ dependencies = [
9
+ "langchain>=1.4.7,<2",
10
+ "stagehand>=4.3.0,<5",
11
+ "stagehand-middleware>=0.1.1,<0.2",
12
+ ]
13
+
14
+ [project.urls]
15
+ Repository = "https://github.com/langchain-ai/langchain-browserbase"
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.9,<0.10"]
19
+ build-backend = "uv_build"
20
+
21
+ [dependency-groups]
22
+ dev = ["pytest>=8,<10", "pytest-asyncio>=1,<2", "ruff>=0.15,<0.16"]
23
+
24
+ [tool.pytest.ini_options]
25
+ asyncio_mode = "auto"
26
+ asyncio_default_fixture_loop_scope = "function"
27
+ testpaths = ["tests"]
28
+
29
+ [tool.ruff]
30
+ target-version = "py311"
31
+ line-length = 100
32
+
33
+ [tool.ruff.lint]
34
+ select = ["E", "F", "I", "UP", "B", "ASYNC", "RUF", "T20"]
35
+
36
+ [tool.ty.environment]
37
+ python-version = "3.11"
38
+
39
+ [tool.ty.rules]
40
+ all = "error"
@@ -0,0 +1,5 @@
1
+ """Stagehand middleware for LangChain and Deep Agents."""
2
+
3
+ from .middleware import StagehandMiddleware
4
+
5
+ __all__ = ["StagehandMiddleware"]
@@ -0,0 +1,38 @@
1
+ """Typed snapshot actions accepted by Stagehand's browser facade."""
2
+
3
+ from typing import Literal, NotRequired
4
+
5
+ from typing_extensions import TypedDict
6
+
7
+
8
+ class ClickAction(TypedDict):
9
+ op: Literal["click", "hover"]
10
+ id: str
11
+
12
+
13
+ class FillAction(TypedDict):
14
+ op: Literal["fill"]
15
+ id: str
16
+ value: str
17
+
18
+
19
+ class TypeAction(TypedDict):
20
+ op: Literal["type"]
21
+ id: str
22
+ text: str
23
+ delay: NotRequired[float]
24
+
25
+
26
+ class PressAction(TypedDict):
27
+ op: Literal["press"]
28
+ id: str
29
+ key: str
30
+
31
+
32
+ class SelectAction(TypedDict):
33
+ op: Literal["select"]
34
+ id: str
35
+ values: str | list[str]
36
+
37
+
38
+ SnapshotAction = ClickAction | FillAction | TypeAction | PressAction | SelectAction
@@ -0,0 +1,88 @@
1
+ """Native LangChain middleware over Stagehand's shared browser facade."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Awaitable, Callable
7
+ from typing import Annotated, Literal, TypeVar
8
+
9
+ from langchain.agents.middleware import AgentMiddleware
10
+ from langchain_core.tools import ToolException, tool
11
+ from pydantic import Field
12
+ from stagehand import Stagehand
13
+ from stagehand_middleware.facade import (
14
+ StagehandFacadeExecutionError,
15
+ StagehandFacadeInputError,
16
+ StagehandFacadeTools,
17
+ facade_tools,
18
+ )
19
+
20
+ from .actions import SnapshotAction
21
+
22
+ T = TypeVar("T")
23
+
24
+
25
+ class StagehandMiddleware(AgentMiddleware):
26
+ """Add browser tools with `middleware=[StagehandMiddleware(stagehand=client)]`."""
27
+
28
+ def __init__(self, *, stagehand: Stagehand) -> None:
29
+ self._stagehand = stagehand
30
+ self._facade = self._open_facade()
31
+ descriptions = {spec["name"]: spec["description"] for spec in facade_tools()}
32
+
33
+ @tool(
34
+ description=descriptions["run"]
35
+ + (
36
+ " Reuse the initialized browser; do not launch another browser or browser process."
37
+ " Capture a new snapshot after navigation or stale IDs."
38
+ )
39
+ )
40
+ async def run(
41
+ *, code: str | None = None, actions: list[SnapshotAction] | None = None
42
+ ) -> str:
43
+ result = await self._call(lambda facade: facade.run(code=code, actions=actions))
44
+ return result if isinstance(result, str) else json.dumps(result, ensure_ascii=False)
45
+
46
+ @tool(
47
+ description=descriptions["snapshot"]
48
+ + (" Capture a new snapshot after navigation or stale IDs before taking actions.")
49
+ )
50
+ async def snapshot(*, includeIframes: bool = True) -> str:
51
+ return await self._call(lambda facade: facade.snapshot(include_iframes=includeIframes))
52
+
53
+ @tool(description=descriptions["screenshot"])
54
+ async def screenshot(
55
+ *,
56
+ fullPage: bool | None = None,
57
+ type: Literal["png", "jpeg"] = "png",
58
+ quality: Annotated[float | None, Field(ge=0, le=100)] = None,
59
+ ) -> list[dict[str, str]]:
60
+ image = await self._call(
61
+ lambda facade: facade.screenshot(
62
+ full_page=fullPage,
63
+ type=type,
64
+ quality=quality,
65
+ )
66
+ )
67
+ return [{"type": "image", "base64": image["data"], "mime_type": image["mimeType"]}]
68
+
69
+ self.tools = [run, snapshot, screenshot]
70
+ for browser_tool in self.tools:
71
+ browser_tool.handle_tool_error = True
72
+ browser_tool.handle_validation_error = True
73
+
74
+ def _open_facade(self) -> StagehandFacadeTools:
75
+ # The application owns the client and browser: `browser.close()` in run code
76
+ # leaves both open and swaps in a fresh facade, so later calls still work.
77
+ async def reopen() -> None:
78
+ self._facade = self._open_facade()
79
+
80
+ return StagehandFacadeTools(self._stagehand, on_close_requested=reopen)
81
+
82
+ async def _call(self, operation: Callable[[StagehandFacadeTools], Awaitable[T]]) -> T:
83
+ try:
84
+ return await operation(self._facade)
85
+ except (StagehandFacadeInputError, StagehandFacadeExecutionError) as error:
86
+ raise ToolException(str(error)) from None
87
+ except Exception as error:
88
+ raise ToolException(f"{type(error).__name__}: {error}") from None