Skip to content

Commit ea91ef6

Browse files
release: 1.1.0 (#700)
* chore(smoketests): fixed smoketests and cleaned up typing/formatting (#699) * increase timeout for flaky smoketest * don't convert params from TypedDict to dict (unnecessary and undoes type hints) * docstring formatting fixes * removed shell exec tests with 'working_dir' parameter testing * feat(scenarios): added scenario class to sdk (#701) * uv lock upgrade * scenario wrapper * modified ScenarioStartRunParams to follow our previous pattern for use in the SDK * fixed redundancy and formatting in scenario and scenario run unit tests * formatting fixes * changed scenario and scenario smoketests to update existing scenarios and add metadata tag instead of creating new ones every time (workaround for lack of cleanup option) * changed scenario run methods to `run` and `run_async`, updated parameter TypedDicts appropriately * exposed all scenario update parameters appropriately using typeddict * formatting fixes * remove scenario builder smoketests * add score_and_complete and download_logs methods to scenario runs, and update docstrings to not leak internals * add unit tests for ScenarioOps and AsyncScenarioOps * update tests for scenario.update and change test name for scenario.run and run_async * add unit tests for download_logs and score_and_complete * don't pass polling_config to get_info within await_env_ready * release: 1.1.0 --------- Co-authored-by: sid-rl <siddarth@runloop.ai> Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com>
1 parent 42849d6 commit ea91ef6

31 files changed

Lines changed: 2095 additions & 212 deletions

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "1.0.0"
2+
".": "1.1.0"
33
}

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## 1.1.0 (2025-12-06)
4+
5+
Full Changelog: [v1.0.0...v1.1.0](https://github.com/runloopai/api-client-python/compare/v1.0.0...v1.1.0)
6+
7+
### Features
8+
9+
* **scenarios:** added scenario class to sdk ([#701](https://github.com/runloopai/api-client-python/issues/701)) ([121f0fc](https://github.com/runloopai/api-client-python/commit/121f0fc6efa779979f664a75e91d60c068358e21))
10+
* **SDK:** Build context helpers for the python clients and SDKs ([#684](https://github.com/runloopai/api-client-python/issues/684)) ([42849d6](https://github.com/runloopai/api-client-python/commit/42849d6834098d00234e5ab345ed0d91bac00435))
11+
12+
13+
### Chores
14+
15+
* **smoketests:** fixed smoketests and cleaned up typing/formatting ([#699](https://github.com/runloopai/api-client-python/issues/699)) ([f57aae7](https://github.com/runloopai/api-client-python/commit/f57aae71be8eb801214c012b2ec66b9414f0365e))
16+
317
## 1.0.0 (2025-12-02)
418

519
Full Changelog: [v0.69.0...v1.0.0](https://github.com/runloopai/api-client-python/compare/v0.69.0...v1.0.0)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "runloop_api_client"
3-
version = "1.0.0"
3+
version = "1.1.0"
44
description = "The official Python library for the runloop API"
55
dynamic = ["readme"]
66
license = "MIT"

src/runloop_api_client/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "runloop_api_client"
4-
__version__ = "1.0.0" # x-release-please-version
4+
__version__ = "1.1.0" # x-release-please-version

src/runloop_api_client/sdk/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,35 @@
55

66
from __future__ import annotations
77

8-
from .sync import AgentOps, DevboxOps, ScorerOps, RunloopSDK, SnapshotOps, BlueprintOps, StorageObjectOps
8+
from .sync import AgentOps, DevboxOps, ScorerOps, RunloopSDK, ScenarioOps, SnapshotOps, BlueprintOps, StorageObjectOps
99
from .agent import Agent
1010
from .async_ import (
1111
AsyncAgentOps,
1212
AsyncDevboxOps,
1313
AsyncScorerOps,
1414
AsyncRunloopSDK,
15+
AsyncScenarioOps,
1516
AsyncSnapshotOps,
1617
AsyncBlueprintOps,
1718
AsyncStorageObjectOps,
1819
)
1920
from .devbox import Devbox, NamedShell
2021
from .scorer import Scorer
22+
from .scenario import Scenario
2123
from .snapshot import Snapshot
2224
from .blueprint import Blueprint
2325
from .execution import Execution
2426
from .async_agent import AsyncAgent
2527
from .async_devbox import AsyncDevbox, AsyncNamedShell
2628
from .async_scorer import AsyncScorer
29+
from .scenario_run import ScenarioRun
30+
from .async_scenario import AsyncScenario
2731
from .async_snapshot import AsyncSnapshot
2832
from .storage_object import StorageObject
2933
from .async_blueprint import AsyncBlueprint
3034
from .async_execution import AsyncExecution
3135
from .execution_result import ExecutionResult
36+
from .async_scenario_run import AsyncScenarioRun
3237
from .async_storage_object import AsyncStorageObject
3338
from .async_execution_result import AsyncExecutionResult
3439

@@ -43,6 +48,8 @@
4348
"AsyncDevboxOps",
4449
"BlueprintOps",
4550
"AsyncBlueprintOps",
51+
"ScenarioOps",
52+
"AsyncScenarioOps",
4653
"ScorerOps",
4754
"AsyncScorerOps",
4855
"SnapshotOps",
@@ -60,6 +67,10 @@
6067
"AsyncExecutionResult",
6168
"Blueprint",
6269
"AsyncBlueprint",
70+
"Scenario",
71+
"AsyncScenario",
72+
"ScenarioRun",
73+
"AsyncScenarioRun",
6374
"Scorer",
6475
"AsyncScorer",
6576
"Snapshot",

src/runloop_api_client/sdk/_types.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@
1111
from ..types.agent_create_params import AgentCreateParams
1212
from ..types.devbox_create_params import DevboxCreateParams, DevboxBaseCreateParams
1313
from ..types.object_create_params import ObjectCreateParams
14+
from ..types.scenario_list_params import ScenarioListParams
1415
from ..types.blueprint_list_params import BlueprintListParams
1516
from ..types.object_download_params import ObjectDownloadParams
17+
from ..types.scenario_update_params import ScenarioUpdateParams
1618
from ..types.blueprint_create_params import BlueprintCreateParams
1719
from ..types.devbox_upload_file_params import DevboxUploadFileParams
20+
from ..types.scenario_start_run_params import ScenarioStartRunBaseParams
1821
from ..types.devbox_create_tunnel_params import DevboxCreateTunnelParams
1922
from ..types.devbox_download_file_params import DevboxDownloadFileParams
2023
from ..types.devbox_execute_async_params import DevboxNiceExecuteAsyncParams
@@ -167,3 +170,19 @@ class SDKAgentCreateParams(AgentCreateParams, LongRequestOptions):
167170

168171
class SDKAgentListParams(AgentListParams, BaseRequestOptions):
169172
pass
173+
174+
175+
class SDKScenarioListParams(ScenarioListParams, BaseRequestOptions):
176+
pass
177+
178+
179+
class SDKScenarioUpdateParams(ScenarioUpdateParams, LongRequestOptions):
180+
pass
181+
182+
183+
class SDKScenarioRunAsyncParams(ScenarioStartRunBaseParams, LongRequestOptions):
184+
pass
185+
186+
187+
class SDKScenarioRunParams(ScenarioStartRunBaseParams, LongPollingRequestOptions):
188+
pass

src/runloop_api_client/sdk/agent.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ class Agent:
1919
including retrieving agent information.
2020
2121
Example:
22-
>>> agent = runloop.agent.create_from_npm(
23-
... name="my-agent",
24-
... package_name="@runloop/example-agent"
25-
... )
22+
>>> agent = runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent")
2623
>>> info = agent.get_info()
2724
>>> print(info.name)
2825
"""

src/runloop_api_client/sdk/async_.py

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
SDKAgentCreateParams,
2020
SDKDevboxCreateParams,
2121
SDKObjectCreateParams,
22+
SDKScenarioListParams,
2223
SDKScorerCreateParams,
2324
SDKBlueprintListParams,
2425
SDKBlueprintCreateParams,
@@ -31,6 +32,7 @@
3132
from .async_agent import AsyncAgent
3233
from .async_devbox import AsyncDevbox
3334
from .async_scorer import AsyncScorer
35+
from .async_scenario import AsyncScenario
3436
from .async_snapshot import AsyncSnapshot
3537
from .async_blueprint import AsyncBlueprint
3638
from ..lib.context_loader import TarFilter, build_directory_tar
@@ -221,17 +223,16 @@ class AsyncBlueprintOps:
221223
... dockerfile="FROM ubuntu:22.04\\nRUN apt-get update",
222224
... )
223225
>>> blueprints = await runloop.blueprint.list()
224-
226+
225227
To use a local directory as a build context, use an object.
226228
227229
Example:
228230
>>> from datetime import timedelta
229231
>>> from runloop_api_client.types.blueprint_build_parameters import BuildContext
230-
>>>
231232
>>> runloop = AsyncRunloopSDK()
232233
>>> obj = await runloop.object_storage.upload_from_dir(
233234
... "./",
234-
... ttl=timedelta(hours=1),
235+
... ttl=timedelta(hours=1),
235236
... )
236237
>>> blueprint = await runloop.blueprint.create(
237238
... name="my-blueprint",
@@ -549,7 +550,8 @@ async def list(self, **params: Unpack[SDKScorerListParams]) -> list[AsyncScorer]
549550
"""
550551
page = await self._client.scenarios.scorers.list(**params)
551552
return [AsyncScorer(self._client, item.id) async for item in page]
552-
553+
554+
553555
class AsyncAgentOps:
554556
"""High-level async manager for creating and managing agents.
555557
@@ -559,15 +561,10 @@ class AsyncAgentOps:
559561
Example:
560562
>>> runloop = AsyncRunloopSDK()
561563
>>> # Create agent from NPM package
562-
>>> agent = await runloop.agent.create_from_npm(
563-
... name="my-agent",
564-
... package_name="@runloop/example-agent"
565-
... )
564+
>>> agent = await runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent")
566565
>>> # Create agent from Git repository
567566
>>> agent = await runloop.agent.create_from_git(
568-
... name="git-agent",
569-
... repository="https://github.com/user/agent-repo",
570-
... ref="main"
567+
... name="git-agent", repository="https://github.com/user/agent-repo", ref="main"
571568
... )
572569
>>> # List all agents
573570
>>> agents = await runloop.agent.list(limit=10)
@@ -621,7 +618,9 @@ async def create_from_npm(
621618
:raises ValueError: If 'source' is provided in params
622619
"""
623620
if "source" in params:
624-
raise ValueError("Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration")
621+
raise ValueError(
622+
"Cannot specify 'source' when using create_from_npm(); source is automatically set to npm configuration"
623+
)
625624

626625
npm_config: dict = {"package_name": package_name}
627626
if npm_version is not None:
@@ -661,7 +660,9 @@ async def create_from_pip(
661660
:raises ValueError: If 'source' is provided in params
662661
"""
663662
if "source" in params:
664-
raise ValueError("Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration")
663+
raise ValueError(
664+
"Cannot specify 'source' when using create_from_pip(); source is automatically set to pip configuration"
665+
)
665666

666667
pip_config: dict = {"package_name": package_name}
667668
if pip_version is not None:
@@ -698,7 +699,9 @@ async def create_from_git(
698699
:raises ValueError: If 'source' is provided in params
699700
"""
700701
if "source" in params:
701-
raise ValueError("Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration")
702+
raise ValueError(
703+
"Cannot specify 'source' when using create_from_git(); source is automatically set to git configuration"
704+
)
702705

703706
git_config: dict = {"repository": repository}
704707
if ref is not None:
@@ -730,7 +733,9 @@ async def create_from_object(
730733
:raises ValueError: If 'source' is provided in params
731734
"""
732735
if "source" in params:
733-
raise ValueError("Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration")
736+
raise ValueError(
737+
"Cannot specify 'source' when using create_from_object(); source is automatically set to object configuration"
738+
)
734739

735740
object_config: dict = {"object_id": object_id}
736741
if agent_setup is not None:
@@ -767,6 +772,45 @@ async def list(
767772
return [AsyncAgent(self._client, item.id, item) for item in page.agents]
768773

769774

775+
class AsyncScenarioOps:
776+
"""Manage scenarios (async). Access via ``runloop.scenario``.
777+
778+
Example:
779+
>>> runloop = AsyncRunloopSDK()
780+
>>> scenario = runloop.scenario.from_id("scn-xxx")
781+
>>> run = await scenario.run()
782+
>>> scenarios = await runloop.scenario.list()
783+
"""
784+
785+
def __init__(self, client: AsyncRunloop) -> None:
786+
"""Initialize AsyncScenarioOps.
787+
788+
:param client: AsyncRunloop client instance
789+
:type client: AsyncRunloop
790+
"""
791+
self._client = client
792+
793+
def from_id(self, scenario_id: str) -> AsyncScenario:
794+
"""Get an AsyncScenario instance for an existing scenario ID.
795+
796+
:param scenario_id: ID of the scenario
797+
:type scenario_id: str
798+
:return: AsyncScenario instance for the given ID
799+
:rtype: AsyncScenario
800+
"""
801+
return AsyncScenario(self._client, scenario_id)
802+
803+
async def list(self, **params: Unpack[SDKScenarioListParams]) -> list[AsyncScenario]:
804+
"""List all scenarios, optionally filtered by parameters.
805+
806+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScenarioListParams` for available parameters
807+
:return: List of scenarios
808+
:rtype: list[AsyncScenario]
809+
"""
810+
page = await self._client.scenarios.list(**params)
811+
return [AsyncScenario(self._client, item.id) async for item in page]
812+
813+
770814
class AsyncRunloopSDK:
771815
"""High-level asynchronous entry point for the Runloop SDK.
772816
@@ -782,6 +826,8 @@ class AsyncRunloopSDK:
782826
:vartype devbox: AsyncDevboxOps
783827
:ivar blueprint: High-level async interface for blueprint management
784828
:vartype blueprint: AsyncBlueprintOps
829+
:ivar scenario: High-level async interface for scenario management
830+
:vartype scenario: AsyncScenarioOps
785831
:ivar scorer: High-level async interface for scorer management
786832
:vartype scorer: AsyncScorerOps
787833
:ivar snapshot: High-level async interface for snapshot management
@@ -801,6 +847,7 @@ class AsyncRunloopSDK:
801847
agent: AsyncAgentOps
802848
devbox: AsyncDevboxOps
803849
blueprint: AsyncBlueprintOps
850+
scenario: AsyncScenarioOps
804851
scorer: AsyncScorerOps
805852
snapshot: AsyncSnapshotOps
806853
storage_object: AsyncStorageObjectOps
@@ -846,6 +893,7 @@ def __init__(
846893
self.agent = AsyncAgentOps(self.api)
847894
self.devbox = AsyncDevboxOps(self.api)
848895
self.blueprint = AsyncBlueprintOps(self.api)
896+
self.scenario = AsyncScenarioOps(self.api)
849897
self.scorer = AsyncScorerOps(self.api)
850898
self.snapshot = AsyncSnapshotOps(self.api)
851899
self.storage_object = AsyncStorageObjectOps(self.api)

src/runloop_api_client/sdk/async_agent.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ class AsyncAgent:
1919
including retrieving agent information.
2020
2121
Example:
22-
>>> agent = await runloop.agent.create_from_npm(
23-
... name="my-agent",
24-
... package_name="@runloop/example-agent"
25-
... )
22+
>>> agent = await runloop.agent.create_from_npm(name="my-agent", package_name="@runloop/example-agent")
2623
>>> info = await agent.get_info()
2724
>>> print(info.name)
2825
"""

src/runloop_api_client/sdk/async_devbox.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,6 @@ async def exec(
643643
>>> result = await shell.exec("npm install", stdout=lambda line: print(f"[LOG] {line}"))
644644
"""
645645
# Ensure shell_name is set and cannot be overridden by user params
646-
params = dict(params)
647646
params["shell_name"] = self._shell_name
648647
return await self._devbox.cmd.exec(command, **params)
649648

@@ -677,7 +676,6 @@ async def exec_async(
677676
... print("Task completed successfully!")
678677
"""
679678
# Ensure shell_name is set and cannot be overridden by user params
680-
params = dict(params)
681679
params["shell_name"] = self._shell_name
682680
return await self._devbox.cmd.exec_async(command, **params)
683681

0 commit comments

Comments
 (0)