22
33from __future__ import annotations
44
5- from typing import TYPE_CHECKING , Optional
5+ import os
6+ from typing import Union , Optional
7+ from functools import cached_property
68from typing_extensions import Unpack , override
79
810from ..types import ScenarioRunView
9- from ._types import BaseRequestOptions , LongRequestOptions
11+ from ._types import BaseRequestOptions , LongRequestOptions , PollingRequestOptions
1012from .._client import AsyncRunloop
11- from ..lib .polling import PollingConfig
13+ from ._helpers import filter_params
14+ from .async_devbox import AsyncDevbox
1215from ..types .scoring_contract_result_view import ScoringContractResultView
1316
14- if TYPE_CHECKING :
15- from .async_devbox import AsyncDevbox
16-
1717
1818class AsyncScenarioRun :
19- """Async wrapper around a running scenario with devbox access.
19+ """A running scenario with devbox access (async) .
2020
2121 Provides async methods for managing the scenario run lifecycle, accessing
22- the underlying devbox, and retrieving scoring results.
22+ the devbox, and retrieving scoring results. Obtain instances via
23+ ``scenario.run()`` or ``scenario.run_async()``.
2324
2425 Example:
25- >>> scenario = await sdk .scenario.from_id("scn-xxx")
26- >>> run = await scenario.run ()
26+ >>> scenario = runloop .scenario.from_id("scn-xxx")
27+ >>> run = await scenario.run_async ()
2728 >>> await run.await_env_ready()
2829 >>> devbox = run.devbox
2930 >>> # ... agent does work on the devbox ...
30- >>> await run.score()
31- >>> await run.await_scored()
32- >>> result = await run.get_score()
31+ >>> await run.score_and_await()
32+ >>> score = await run.get_score()
3333 """
3434
3535 def __init__ (self , client : AsyncRunloop , run_id : str , devbox_id : str ) -> None :
36- """Initialize the wrapper .
36+ """Create an AsyncScenarioRun instance .
3737
38- :param client: Generated AsyncRunloop client
38+ :param client: AsyncRunloop client instance
3939 :type client: AsyncRunloop
40- :param run_id: ScenarioRun ID returned by the API
40+ :param run_id: Scenario run ID
4141 :type run_id: str
4242 :param devbox_id: Devbox ID associated with this run
4343 :type devbox_id: str
@@ -68,17 +68,15 @@ def devbox_id(self) -> str:
6868 """
6969 return self ._devbox_id
7070
71- @property
72- def devbox (self ) -> " AsyncDevbox" :
73- """Return an AsyncDevbox wrapper for the underlying devbox .
71+ @cached_property
72+ def devbox (self ) -> AsyncDevbox :
73+ """The devbox instance for this scenario run .
7474
7575 Use this to interact with the devbox environment during the scenario run.
7676
77- :return: AsyncDevbox wrapper instance
77+ :return: AsyncDevbox instance
7878 :rtype: AsyncDevbox
7979 """
80- from .async_devbox import AsyncDevbox
81-
8280 return AsyncDevbox (self ._client , self ._devbox_id )
8381
8482 async def get_info (
@@ -87,7 +85,7 @@ async def get_info(
8785 ) -> ScenarioRunView :
8886 """Retrieve current scenario run status and metadata.
8987
90- :param options: Optional request configuration
88+ :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
9189 :return: Current scenario run state info
9290 :rtype: ScenarioRunView
9391 """
@@ -98,22 +96,18 @@ async def get_info(
9896
9997 async def await_env_ready (
10098 self ,
101- * ,
102- polling_config : PollingConfig | None = None ,
103- ** options : Unpack [BaseRequestOptions ],
99+ ** options : Unpack [PollingRequestOptions ],
104100 ) -> ScenarioRunView :
105101 """Wait for the scenario environment (devbox) to be ready.
106102
107103 Blocks until the devbox reaches running state.
108104
109- :param polling_config: Optional polling configuration
110- :type polling_config: PollingConfig | None
111- :param options: Optional request configuration
105+ :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
112106 :return: Scenario run state after environment is ready
113107 :rtype: ScenarioRunView
114108 """
115- await self ._client .devboxes .await_running (self ._devbox_id , polling_config = polling_config )
116- return await self .get_info (** options )
109+ await self ._client .devboxes .await_running (self ._devbox_id , polling_config = options . get ( " polling_config" ) )
110+ return await self .get_info (** filter_params ( options , PollingRequestOptions ) )
117111
118112 async def score (
119113 self ,
@@ -123,7 +117,7 @@ async def score(
123117
124118 This triggers the scoring process using the scenario's scoring contract.
125119
126- :param options: Optional long-running request configuration
120+ :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
127121 :return: Updated scenario run state
128122 :rtype: ScenarioRunView
129123 """
@@ -134,45 +128,53 @@ async def score(
134128
135129 async def await_scored (
136130 self ,
137- * ,
138- polling_config : PollingConfig | None = None ,
139- ** options : Unpack [BaseRequestOptions ],
131+ ** options : Unpack [PollingRequestOptions ],
140132 ) -> ScenarioRunView :
141133 """Wait for the scenario run to be scored.
142134
143135 Blocks until scoring is complete.
144136
145- :param polling_config: Optional polling configuration
146- :type polling_config: PollingConfig | None
147- :param options: Optional request configuration
137+ :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
148138 :return: Scored scenario run state
149139 :rtype: ScenarioRunView
150140 """
151141 return await self ._client .scenarios .runs .await_scored (
152142 self ._id ,
153- polling_config = polling_config ,
154143 ** options ,
155144 )
156145
157146 async def score_and_await (
158147 self ,
159- * ,
160- polling_config : PollingConfig | None = None ,
161- ** options : Unpack [BaseRequestOptions ],
148+ ** options : Unpack [PollingRequestOptions ],
162149 ) -> ScenarioRunView :
163150 """Submit for scoring and wait for completion.
164151
165152 Convenience method that calls score() then await_scored().
166153
167- :param polling_config: Optional polling configuration
168- :type polling_config: PollingConfig | None
169- :param options: Optional request configuration
154+ :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
170155 :return: Scored scenario run state
171156 :rtype: ScenarioRunView
172157 """
173158 return await self ._client .scenarios .runs .score_and_await (
174159 self ._id ,
175- polling_config = polling_config ,
160+ ** options ,
161+ )
162+
163+ async def score_and_complete (
164+ self ,
165+ ** options : Unpack [PollingRequestOptions ],
166+ ) -> ScenarioRunView :
167+ """Score the run, wait for scoring, then complete and shutdown.
168+
169+ Convenience method that scores the scenario run, waits for scoring to
170+ finish, then completes the run and shuts down the devbox.
171+
172+ :param options: See :typeddict:`~runloop_api_client.sdk._types.PollingRequestOptions` for available options
173+ :return: Completed scenario run state with scoring results
174+ :rtype: ScenarioRunView
175+ """
176+ return await self ._client .scenarios .runs .score_and_complete (
177+ self ._id ,
176178 ** options ,
177179 )
178180
@@ -182,7 +184,7 @@ async def complete(
182184 ) -> ScenarioRunView :
183185 """Complete the scenario run and shutdown the devbox.
184186
185- :param options: Optional long-running request configuration
187+ :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
186188 :return: Final scenario run state
187189 :rtype: ScenarioRunView
188190 """
@@ -197,7 +199,7 @@ async def cancel(
197199 ) -> ScenarioRunView :
198200 """Cancel the scenario run and shutdown the devbox.
199201
200- :param options: Optional long-running request configuration
202+ :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
201203 :return: Cancelled scenario run state
202204 :rtype: ScenarioRunView
203205 """
@@ -206,16 +208,34 @@ async def cancel(
206208 ** options ,
207209 )
208210
211+ async def download_logs (
212+ self ,
213+ file : Union [str , os .PathLike [str ]],
214+ ** options : Unpack [LongRequestOptions ],
215+ ) -> None :
216+ """Download all logs for this scenario run to a zip file.
217+
218+ Downloads a zip archive containing all logs from the scenario run's
219+ associated devbox.
220+
221+ :param file: Path where the zip file will be written
222+ :type file: str | os.PathLike[str]
223+ :param options: See :typeddict:`~runloop_api_client.sdk._types.LongRequestOptions` for available options
224+ """
225+ response = await self ._client .scenarios .runs .download_logs (self ._id , ** options )
226+ await response .write_to_file (file )
227+
209228 async def get_score (
210229 self ,
211230 ** options : Unpack [BaseRequestOptions ],
212231 ) -> Optional [ScoringContractResultView ]:
213232 """Get the scoring result for this run.
214233
215- Returns None if the run has not been scored yet.
234+ Returns None if the run has not been scored yet. Always makes an API
235+ call to retrieve the current scoring result.
216236
217- :param options: Optional request configuration
218- :return: Scoring result or None
237+ :param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
238+ :return: Scoring result or None if not yet scored
219239 :rtype: Optional[ScoringContractResultView]
220240 """
221241 info = await self .get_info (** options )
0 commit comments