Skip to content

Commit fb4c3f9

Browse files
feat: Include replay endpoint in stainless spec so SDK clients can get run metrics
1 parent 64847ef commit fb4c3f9

3 files changed

Lines changed: 208 additions & 4 deletions

File tree

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 7
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-1c405024b4a17886e921871250a445362463b7ae6070cacc3d1927d59036730c.yml
3-
openapi_spec_hash: c4ea3735257a48ed105002eb7aaa095a
4-
config_hash: 85d56c7c196269badbd0b4a9dfb28d45
1+
configured_endpoints: 8
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-089c8670f1d7c2e9fa8e5c97010db7c24b8f162eb7cfe76ffa41d70fa46efe2f.yml
3+
openapi_spec_hash: 7a226aee8f3f2ab16febbe6bb35e1657
4+
config_hash: 8e4ed6629c178aa0c8aaf575cb07c544

src/stagehand/resources/sessions.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from ..types.session_act_response import SessionActResponse
3232
from ..types.session_end_response import SessionEndResponse
3333
from ..types.session_start_response import SessionStartResponse
34+
from ..types.session_replay_response import SessionReplayResponse
3435
from ..types.session_execute_response import SessionExecuteResponse
3536
from ..types.session_extract_response import SessionExtractResponse
3637
from ..types.session_observe_response import SessionObserveResponse
@@ -862,6 +863,50 @@ def observe(
862863
stream_cls=Stream[StreamEvent],
863864
)
864865

866+
def replay(
867+
self,
868+
id: str,
869+
*,
870+
x_stream_response: Literal["true", "false"] | Omit = omit,
871+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
872+
# The extra values given here take precedence over values defined on the client or passed to this method.
873+
extra_headers: Headers | None = None,
874+
extra_query: Query | None = None,
875+
extra_body: Body | None = None,
876+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
877+
) -> SessionReplayResponse:
878+
"""
879+
Retrieves replay metrics for a session.
880+
881+
Args:
882+
id: Unique session identifier
883+
884+
x_stream_response: Whether to stream the response via SSE
885+
886+
extra_headers: Send extra headers
887+
888+
extra_query: Add additional query parameters to the request
889+
890+
extra_body: Add additional JSON properties to the request
891+
892+
timeout: Override the client-level default timeout for this request, in seconds
893+
"""
894+
if not id:
895+
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
896+
extra_headers = {
897+
**strip_not_given(
898+
{"x-stream-response": str(x_stream_response) if is_given(x_stream_response) else not_given}
899+
),
900+
**(extra_headers or {}),
901+
}
902+
return self._get(
903+
f"/v1/sessions/{id}/replay",
904+
options=make_request_options(
905+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
906+
),
907+
cast_to=SessionReplayResponse,
908+
)
909+
865910
def start(
866911
self,
867912
*,
@@ -1770,6 +1815,50 @@ async def observe(
17701815
stream_cls=AsyncStream[StreamEvent],
17711816
)
17721817

1818+
async def replay(
1819+
self,
1820+
id: str,
1821+
*,
1822+
x_stream_response: Literal["true", "false"] | Omit = omit,
1823+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
1824+
# The extra values given here take precedence over values defined on the client or passed to this method.
1825+
extra_headers: Headers | None = None,
1826+
extra_query: Query | None = None,
1827+
extra_body: Body | None = None,
1828+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
1829+
) -> SessionReplayResponse:
1830+
"""
1831+
Retrieves replay metrics for a session.
1832+
1833+
Args:
1834+
id: Unique session identifier
1835+
1836+
x_stream_response: Whether to stream the response via SSE
1837+
1838+
extra_headers: Send extra headers
1839+
1840+
extra_query: Add additional query parameters to the request
1841+
1842+
extra_body: Add additional JSON properties to the request
1843+
1844+
timeout: Override the client-level default timeout for this request, in seconds
1845+
"""
1846+
if not id:
1847+
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
1848+
extra_headers = {
1849+
**strip_not_given(
1850+
{"x-stream-response": str(x_stream_response) if is_given(x_stream_response) else not_given}
1851+
),
1852+
**(extra_headers or {}),
1853+
}
1854+
return await self._get(
1855+
f"/v1/sessions/{id}/replay",
1856+
options=make_request_options(
1857+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
1858+
),
1859+
cast_to=SessionReplayResponse,
1860+
)
1861+
17731862
async def start(
17741863
self,
17751864
*,
@@ -1877,6 +1966,9 @@ def __init__(self, sessions: SessionsResource) -> None:
18771966
self.observe = to_raw_response_wrapper(
18781967
sessions.observe,
18791968
)
1969+
self.replay = to_raw_response_wrapper(
1970+
sessions.replay,
1971+
)
18801972
self.start = to_raw_response_wrapper(
18811973
sessions.start,
18821974
)
@@ -1904,6 +1996,9 @@ def __init__(self, sessions: AsyncSessionsResource) -> None:
19041996
self.observe = async_to_raw_response_wrapper(
19051997
sessions.observe,
19061998
)
1999+
self.replay = async_to_raw_response_wrapper(
2000+
sessions.replay,
2001+
)
19072002
self.start = async_to_raw_response_wrapper(
19082003
sessions.start,
19092004
)
@@ -1931,6 +2026,9 @@ def __init__(self, sessions: SessionsResource) -> None:
19312026
self.observe = to_streamed_response_wrapper(
19322027
sessions.observe,
19332028
)
2029+
self.replay = to_streamed_response_wrapper(
2030+
sessions.replay,
2031+
)
19342032
self.start = to_streamed_response_wrapper(
19352033
sessions.start,
19362034
)
@@ -1958,6 +2056,9 @@ def __init__(self, sessions: AsyncSessionsResource) -> None:
19582056
self.observe = async_to_streamed_response_wrapper(
19592057
sessions.observe,
19602058
)
2059+
self.replay = async_to_streamed_response_wrapper(
2060+
sessions.replay,
2061+
)
19612062
self.start = async_to_streamed_response_wrapper(
19622063
sessions.start,
19632064
)

tests/api_resources/test_sessions.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
SessionActResponse,
1414
SessionEndResponse,
1515
SessionStartResponse,
16+
SessionReplayResponse,
1617
SessionExecuteResponse,
1718
SessionExtractResponse,
1819
SessionObserveResponse,
@@ -677,6 +678,57 @@ def test_path_params_observe_overload_2(self, client: Stagehand) -> None:
677678
stream_response=True,
678679
)
679680

681+
@pytest.mark.skip(reason="Prism tests are disabled")
682+
@parametrize
683+
def test_method_replay(self, client: Stagehand) -> None:
684+
session = client.sessions.replay(
685+
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
686+
)
687+
assert_matches_type(SessionReplayResponse, session, path=["response"])
688+
689+
@pytest.mark.skip(reason="Prism tests are disabled")
690+
@parametrize
691+
def test_method_replay_with_all_params(self, client: Stagehand) -> None:
692+
session = client.sessions.replay(
693+
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
694+
x_stream_response="true",
695+
)
696+
assert_matches_type(SessionReplayResponse, session, path=["response"])
697+
698+
@pytest.mark.skip(reason="Prism tests are disabled")
699+
@parametrize
700+
def test_raw_response_replay(self, client: Stagehand) -> None:
701+
response = client.sessions.with_raw_response.replay(
702+
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
703+
)
704+
705+
assert response.is_closed is True
706+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
707+
session = response.parse()
708+
assert_matches_type(SessionReplayResponse, session, path=["response"])
709+
710+
@pytest.mark.skip(reason="Prism tests are disabled")
711+
@parametrize
712+
def test_streaming_response_replay(self, client: Stagehand) -> None:
713+
with client.sessions.with_streaming_response.replay(
714+
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
715+
) as response:
716+
assert not response.is_closed
717+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
718+
719+
session = response.parse()
720+
assert_matches_type(SessionReplayResponse, session, path=["response"])
721+
722+
assert cast(Any, response.is_closed) is True
723+
724+
@pytest.mark.skip(reason="Prism tests are disabled")
725+
@parametrize
726+
def test_path_params_replay(self, client: Stagehand) -> None:
727+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
728+
client.sessions.with_raw_response.replay(
729+
id="",
730+
)
731+
680732
@pytest.mark.skip(reason="Prism tests are disabled")
681733
@parametrize
682734
def test_method_start(self, client: Stagehand) -> None:
@@ -1457,6 +1509,57 @@ async def test_path_params_observe_overload_2(self, async_client: AsyncStagehand
14571509
stream_response=True,
14581510
)
14591511

1512+
@pytest.mark.skip(reason="Prism tests are disabled")
1513+
@parametrize
1514+
async def test_method_replay(self, async_client: AsyncStagehand) -> None:
1515+
session = await async_client.sessions.replay(
1516+
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
1517+
)
1518+
assert_matches_type(SessionReplayResponse, session, path=["response"])
1519+
1520+
@pytest.mark.skip(reason="Prism tests are disabled")
1521+
@parametrize
1522+
async def test_method_replay_with_all_params(self, async_client: AsyncStagehand) -> None:
1523+
session = await async_client.sessions.replay(
1524+
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
1525+
x_stream_response="true",
1526+
)
1527+
assert_matches_type(SessionReplayResponse, session, path=["response"])
1528+
1529+
@pytest.mark.skip(reason="Prism tests are disabled")
1530+
@parametrize
1531+
async def test_raw_response_replay(self, async_client: AsyncStagehand) -> None:
1532+
response = await async_client.sessions.with_raw_response.replay(
1533+
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
1534+
)
1535+
1536+
assert response.is_closed is True
1537+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
1538+
session = await response.parse()
1539+
assert_matches_type(SessionReplayResponse, session, path=["response"])
1540+
1541+
@pytest.mark.skip(reason="Prism tests are disabled")
1542+
@parametrize
1543+
async def test_streaming_response_replay(self, async_client: AsyncStagehand) -> None:
1544+
async with async_client.sessions.with_streaming_response.replay(
1545+
id="c4dbf3a9-9a58-4b22-8a1c-9f20f9f9e123",
1546+
) as response:
1547+
assert not response.is_closed
1548+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
1549+
1550+
session = await response.parse()
1551+
assert_matches_type(SessionReplayResponse, session, path=["response"])
1552+
1553+
assert cast(Any, response.is_closed) is True
1554+
1555+
@pytest.mark.skip(reason="Prism tests are disabled")
1556+
@parametrize
1557+
async def test_path_params_replay(self, async_client: AsyncStagehand) -> None:
1558+
with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"):
1559+
await async_client.sessions.with_raw_response.replay(
1560+
id="",
1561+
)
1562+
14601563
@pytest.mark.skip(reason="Prism tests are disabled")
14611564
@parametrize
14621565
async def test_method_start(self, async_client: AsyncStagehand) -> None:

0 commit comments

Comments
 (0)