Skip to content

Commit 0cbe606

Browse files
committed
Added x-language and minimal agent example
1 parent 73014e5 commit 0cbe606

File tree

3 files changed

+74
-1
lines changed

3 files changed

+74
-1
lines changed

examples/agent_execute.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""
2+
Minimal example using the Sessions Agent Execute endpoint.
3+
4+
Required environment variables:
5+
- BROWSERBASE_API_KEY
6+
- BROWSERBASE_PROJECT_ID
7+
- MODEL_API_KEY
8+
9+
Optional:
10+
- STAGEHAND_MODEL (defaults to "openai/gpt-5-nano")
11+
12+
Run from the repo root:
13+
`PYTHONPATH=src .venv/bin/python examples/agent_execute_minimal.py`
14+
"""
15+
16+
import os
17+
import json
18+
19+
from stagehand import Stagehand
20+
from stagehand import APIResponseValidationError
21+
22+
23+
def main() -> None:
24+
model_name = os.environ.get("STAGEHAND_MODEL", "openai/gpt-5-nano")
25+
26+
# Enable strict response validation so we fail fast if the API response
27+
# doesn't match the expected schema (instead of silently constructing models
28+
# with missing fields set to None).
29+
with Stagehand(_strict_response_validation=True) as client:
30+
try:
31+
session = client.sessions.start(model_name=model_name)
32+
except APIResponseValidationError as e:
33+
print("Session start response failed schema validation.")
34+
print(f"Base URL: {client.base_url!r}")
35+
print(f"HTTP status: {e.response.status_code}")
36+
print("Raw response text:")
37+
print(e.response.text)
38+
print("Parsed response body:")
39+
print(e.body)
40+
raise
41+
session_id = session.data.session_id
42+
if not session_id:
43+
raise RuntimeError(f"Expected a session ID from /sessions/start but received {session.to_dict()!r}")
44+
45+
try:
46+
client.sessions.navigate(
47+
id=session_id,
48+
url="https://news.ycombinator.com",
49+
options={"wait_until": "domcontentloaded"},
50+
)
51+
52+
result = client.sessions.execute(
53+
id=session_id,
54+
agent_config={"model": model_name},
55+
execute_options={
56+
"instruction": "Go to Hacker News and return the titles of the first 3 articles.",
57+
"max_steps": 5,
58+
},
59+
)
60+
61+
print("Agent message:", result.data.result.message)
62+
print("\nFull result:")
63+
print(json.dumps(result.data.result.to_dict(), indent=2, default=str))
64+
finally:
65+
# Only attempt cleanup if a valid session ID was created.
66+
if session_id:
67+
client.sessions.end(id=session_id)
68+
69+
70+
if __name__ == "__main__":
71+
main()

src/stagehand/_client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ def _llm_model_api_key_auth(self) -> dict[str, str]:
170170
def default_headers(self) -> dict[str, str | Omit]:
171171
return {
172172
**super().default_headers,
173+
"x-language": "python",
173174
"X-Stainless-Async": "false",
174175
**self._custom_headers,
175176
}
@@ -387,6 +388,7 @@ def _llm_model_api_key_auth(self) -> dict[str, str]:
387388
def default_headers(self) -> dict[str, str | Omit]:
388389
return {
389390
**super().default_headers,
391+
"x-language": "python",
390392
"X-Stainless-Async": f"async:{get_async_library()}",
391393
**self._custom_headers,
392394
}

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)