Skip to content

Commit 0e492d0

Browse files
committed
Added session create helper
1 parent 4abfc84 commit 0e492d0

File tree

9 files changed

+977
-85
lines changed

9 files changed

+977
-85
lines changed

README.md

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,43 +35,32 @@ python examples/full_example.py
3535
This example demonstrates the full Stagehand workflow: starting a session, navigating to a page, observing possible actions, acting on elements, extracting data, and running an autonomous agent.
3636

3737
```python
38-
from stagehand import Stagehand, __version__
38+
from stagehand import Stagehand
3939

4040

4141
def main() -> None:
42-
sdk_version = __version__
43-
4442
# Create client using environment variables:
4543
# BROWSERBASE_API_KEY, BROWSERBASE_PROJECT_ID, MODEL_API_KEY
4644
client = Stagehand()
4745

48-
# Start a new browser session
49-
start_response = client.sessions.start(
46+
# Start a new browser session (returns a session helper bound to a session_id)
47+
session = client.sessions.create(
5048
model_name="openai/gpt-5-nano",
51-
x_language="python",
52-
x_sdk_version=sdk_version,
5349
)
5450

55-
session_id = start_response.data.session_id
56-
print(f"Session started: {session_id}")
51+
print(f"Session started: {session.id}")
5752

5853
try:
5954
# Navigate to a webpage
60-
client.sessions.navigate(
61-
id=session_id,
55+
session.navigate(
6256
url="https://news.ycombinator.com",
6357
frame_id="", # empty string for the main frame
64-
x_language="python",
65-
x_sdk_version=sdk_version,
6658
)
6759
print("Navigated to Hacker News")
6860

6961
# Observe to find possible actions on the page
70-
observe_response = client.sessions.observe(
71-
id=session_id,
62+
observe_response = session.observe(
7263
instruction="find the link to view comments for the top post",
73-
x_language="python",
74-
x_sdk_version=sdk_version,
7564
)
7665

7766
results = observe_response.data.result
@@ -83,17 +72,13 @@ def main() -> None:
8372
action = results[0].to_dict(exclude_none=True)
8473
print("Acting on:", action.get("description"))
8574

86-
act_response = client.sessions.act(
87-
id=session_id,
75+
act_response = session.act(
8876
input=action,
89-
x_language="python",
90-
x_sdk_version=sdk_version,
9177
)
9278
print("Act completed:", act_response.data.result.message)
9379

9480
# Extract structured data from the page using a JSON schema
95-
extract_response = client.sessions.extract(
96-
id=session_id,
81+
extract_response = session.extract(
9782
instruction="extract the text of the top comment on this page",
9883
schema={
9984
"type": "object",
@@ -103,32 +88,27 @@ def main() -> None:
10388
},
10489
"required": ["commentText"],
10590
},
106-
x_language="python",
107-
x_sdk_version=sdk_version,
10891
)
10992

11093
extracted = extract_response.data.result
11194
author = extracted.get("author", "unknown") if isinstance(extracted, dict) else "unknown"
11295
print("Extracted author:", author)
11396

11497
# Run an autonomous agent to accomplish a complex task
115-
execute_response = client.sessions.execute(
116-
id=session_id,
98+
execute_response = session.execute(
11799
execute_options={
118100
"instruction": f"Find any personal website, GitHub, or LinkedIn profile for the Hacker News user '{author}'.",
119101
"max_steps": 10,
120102
},
121103
agent_config={"model": "openai/gpt-5-nano"},
122-
x_language="python",
123-
x_sdk_version=sdk_version,
124104
timeout=300.0,
125105
)
126106

127107
print("Agent completed:", execute_response.data.result.message)
128108
print("Agent success:", execute_response.data.result.success)
129109
finally:
130110
# End the browser session to clean up resources
131-
client.sessions.end(id=session_id, x_language="python", x_sdk_version=sdk_version)
111+
session.end()
132112
print("Session ended")
133113

134114

examples/act_example.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,15 @@ def main() -> None:
2929
)
3030

3131
# Start a new browser session
32-
start_response = client.sessions.start(
32+
session = client.sessions.create(
3333
model_name="openai/gpt-5-nano",
3434
)
3535

36-
session_id = start_response.data.session_id
37-
print(f"Session started: {session_id}")
36+
print(f"Session started: {session.id}")
3837

3938
try:
4039
# Navigate to example.com
41-
client.sessions.navigate(
42-
id=session_id,
40+
session.navigate(
4341
url="https://www.example.com",
4442
frame_id="", # Empty string for main frame
4543
)
@@ -48,8 +46,7 @@ def main() -> None:
4846
# Call act() with a string instruction directly
4947
# This is the key test - passing a string instead of an Action object
5048
print("\nAttempting to call act() with string input...")
51-
act_response = client.sessions.act(
52-
id=session_id,
49+
act_response = session.act(
5350
input="click the 'More information' link", # String instruction
5451
)
5552

@@ -61,13 +58,12 @@ def main() -> None:
6158
print(f"Error: {e}")
6259
print(f"Error type: {type(e).__name__}")
6360
import traceback
61+
6462
traceback.print_exc()
6563

6664
finally:
6765
# End the session to clean up resources
68-
client.sessions.end(
69-
id=session_id,
70-
)
66+
session.end()
7167
print("\nSession ended")
7268

7369

examples/agent_execute.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def main() -> None:
2727
# with missing fields set to None).
2828
with Stagehand(_strict_response_validation=True) as client:
2929
try:
30-
session = client.sessions.start(model_name=model_name)
30+
session = client.sessions.create(model_name=model_name)
3131
except APIResponseValidationError as e:
3232
print("Session start response failed schema validation.")
3333
print(f"Base URL: {client.base_url!r}")
@@ -37,19 +37,16 @@ def main() -> None:
3737
print("Parsed response body:")
3838
print(e.body)
3939
raise
40-
session_id = session.data.session_id
41-
if not session_id:
42-
raise RuntimeError(f"Expected a session ID from /sessions/start but received {session.to_dict()!r}")
40+
if not session.id:
41+
raise RuntimeError(f"Expected a session ID from /sessions/start but received {session!r}")
4342

4443
try:
45-
client.sessions.navigate(
46-
id=session_id,
44+
session.navigate(
4745
url="https://news.ycombinator.com",
4846
options={"wait_until": "domcontentloaded"},
4947
)
5048

51-
result = client.sessions.execute(
52-
id=session_id,
49+
result = session.execute(
5350
agent_config={"model": model_name},
5451
execute_options={
5552
"instruction": "Go to Hacker News and return the titles of the first 3 articles.",
@@ -61,9 +58,7 @@ def main() -> None:
6158
print("\nFull result:")
6259
print(json.dumps(result.data.result.to_dict(), indent=2, default=str))
6360
finally:
64-
# Only attempt cleanup if a valid session ID was created.
65-
if session_id:
66-
client.sessions.end(id=session_id)
61+
session.end()
6762

6863

6964
if __name__ == "__main__":

examples/full_example.py

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,23 @@ def main() -> None:
3030
model_api_key=os.environ.get("MODEL_API_KEY"),
3131
)
3232

33-
# Start a new browser session
34-
start_response = client.sessions.start(
33+
# Start a new browser session (returns a session helper bound to a session_id)
34+
session = client.sessions.create(
3535
model_name="openai/gpt-5-nano",
3636
)
3737

38-
session_id = start_response.data.session_id
39-
print(f"Session started: {session_id}")
38+
print(f"Session started: {session.id}")
4039

4140
try:
4241
# Navigate to Hacker News
43-
client.sessions.navigate(
44-
id=session_id,
42+
session.navigate(
4543
url="https://news.ycombinator.com",
4644
frame_id="", # Empty string for main frame
4745
)
4846
print("Navigated to Hacker News")
4947

5048
# Observe to find possible actions - looking for the comments link
51-
observe_response = client.sessions.observe(
52-
id=session_id,
49+
observe_response = session.observe(
5350
instruction="find the link to view comments for the top post",
5451
)
5552

@@ -65,30 +62,22 @@ def main() -> None:
6562
print(f"Acting on: {result.description}")
6663

6764
# Pass the action to Act
68-
act_response = client.sessions.act(
69-
id=session_id,
65+
act_response = session.act(
7066
input=result, # type: ignore[arg-type]
7167
)
7268
print(f"Act completed: {act_response.data.result.message}")
7369

7470
# Extract data from the page
7571
# We're now on the comments page, so extract the top comment text
76-
extract_response = client.sessions.extract(
77-
id=session_id,
72+
extract_response = session.extract(
7873
instruction="extract the text of the top comment on this page",
7974
schema={
8075
"type": "object",
8176
"properties": {
82-
"commentText": {
83-
"type": "string",
84-
"description": "The text content of the top comment"
85-
},
86-
"author": {
87-
"type": "string",
88-
"description": "The username of the comment author"
89-
}
77+
"commentText": {"type": "string", "description": "The text content of the top comment"},
78+
"author": {"type": "string", "description": "The username of the comment author"},
9079
},
91-
"required": ["commentText"]
80+
"required": ["commentText"],
9281
},
9382
)
9483

@@ -103,8 +92,7 @@ def main() -> None:
10392
# Use the Agent to find the author's profile
10493
# Execute runs an autonomous agent that can navigate and interact with pages
10594
# Use a longer timeout (5 minutes) since agent execution can take a while
106-
execute_response = client.sessions.execute( # pyright: ignore[reportArgumentType]
107-
id=session_id,
95+
execute_response = session.execute( # pyright: ignore[reportArgumentType]
10896
execute_options={
10997
"instruction": (
11098
f"Find any personal website, GitHub, LinkedIn, or other best profile URL for the Hacker News user '{author}'. "
@@ -129,9 +117,7 @@ def main() -> None:
129117

130118
finally:
131119
# End the session to clean up resources
132-
client.sessions.end(
133-
id=session_id,
134-
)
120+
session.end()
135121
print("Session ended")
136122

137123

src/stagehand/_client.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
if TYPE_CHECKING:
3636
from .resources import sessions
37-
from .resources.sessions import SessionsResource, AsyncSessionsResource
37+
from .resources.sessions_helpers import SessionsResourceWithHelpers, AsyncSessionsResourceWithHelpers
3838

3939
__all__ = [
4040
"Timeout",
@@ -179,10 +179,10 @@ def close(self) -> None:
179179
self._sea_server.close()
180180

181181
@cached_property
182-
def sessions(self) -> SessionsResource:
183-
from .resources.sessions import SessionsResource
182+
def sessions(self) -> SessionsResourceWithHelpers:
183+
from .resources.sessions_helpers import SessionsResourceWithHelpers
184184

185-
return SessionsResource(self)
185+
return SessionsResourceWithHelpers(self)
186186

187187
@cached_property
188188
def with_raw_response(self) -> StagehandWithRawResponse:
@@ -469,10 +469,10 @@ async def close(self) -> None:
469469
await self._sea_server.aclose()
470470

471471
@cached_property
472-
def sessions(self) -> AsyncSessionsResource:
473-
from .resources.sessions import AsyncSessionsResource
472+
def sessions(self) -> AsyncSessionsResourceWithHelpers:
473+
from .resources.sessions_helpers import AsyncSessionsResourceWithHelpers
474474

475-
return AsyncSessionsResource(self)
475+
return AsyncSessionsResourceWithHelpers(self)
476476

477477
@cached_property
478478
def with_raw_response(self) -> AsyncStagehandWithRawResponse:

0 commit comments

Comments
 (0)