From 514cf93e8ff8b97bfde171a83cf9abf211193e7f Mon Sep 17 00:00:00 2001 From: sid-rl Date: Fri, 10 Apr 2026 11:43:57 -0700 Subject: [PATCH 1/5] docs: restructure sphinx docs with async-first API reference and full type coverage (#782) --- docs/api/agent.rst | 16 +++++ docs/api/axon.rst | 21 ++++++ docs/api/benchmark.rst | 18 +++++ docs/api/benchmark_run.rst | 17 +++++ docs/api/blueprint.rst | 17 +++++ docs/api/core.rst | 33 +++++++++ docs/api/devbox.rst | 18 +++++ docs/api/execution.rst | 17 +++++ docs/api/execution_result.rst | 17 +++++ docs/api/gateway_config.rst | 17 +++++ docs/api/index.rst | 51 ++++++++++++++ docs/api/mcp_config.rst | 17 +++++ docs/api/network_policy.rst | 17 +++++ docs/api/scenario.rst | 17 +++++ docs/api/scenario_builder.rst | 18 +++++ docs/api/scenario_run.rst | 17 +++++ docs/api/scorer.rst | 17 +++++ docs/api/secret.rst | 17 +++++ docs/api/snapshot.rst | 17 +++++ docs/api/storage_object.rst | 17 +++++ docs/{sdk => api}/types.rst | 101 ++++++++++++++++++++++++---- docs/conf.py | 34 ++++++++++ docs/index.rst | 50 ++++++-------- docs/sdk/async/agent.rst | 7 -- docs/sdk/async/blueprint.rst | 8 --- docs/sdk/async/core.rst | 8 --- docs/sdk/async/devbox.rst | 7 -- docs/sdk/async/execution.rst | 8 --- docs/sdk/async/execution_result.rst | 8 --- docs/sdk/async/index.rst | 17 ----- docs/sdk/async/scorer.rst | 9 --- docs/sdk/async/snapshot.rst | 8 --- docs/sdk/async/storage_object.rst | 8 --- docs/sdk/sync/agent.rst | 7 -- docs/sdk/sync/blueprint.rst | 8 --- docs/sdk/sync/core.rst | 8 --- docs/sdk/sync/devbox.rst | 7 -- docs/sdk/sync/execution.rst | 8 --- docs/sdk/sync/execution_result.rst | 8 --- docs/sdk/sync/index.rst | 18 ----- docs/sdk/sync/scorer.rst | 9 --- docs/sdk/sync/snapshot.rst | 8 --- docs/sdk/sync/storage_object.rst | 8 --- uv.lock | 2 +- 44 files changed, 541 insertions(+), 219 deletions(-) create mode 100644 docs/api/agent.rst create mode 100644 docs/api/axon.rst create mode 100644 docs/api/benchmark.rst create mode 100644 docs/api/benchmark_run.rst create mode 100644 docs/api/blueprint.rst create mode 100644 docs/api/core.rst create mode 100644 docs/api/devbox.rst create mode 100644 docs/api/execution.rst create mode 100644 docs/api/execution_result.rst create mode 100644 docs/api/gateway_config.rst create mode 100644 docs/api/index.rst create mode 100644 docs/api/mcp_config.rst create mode 100644 docs/api/network_policy.rst create mode 100644 docs/api/scenario.rst create mode 100644 docs/api/scenario_builder.rst create mode 100644 docs/api/scenario_run.rst create mode 100644 docs/api/scorer.rst create mode 100644 docs/api/secret.rst create mode 100644 docs/api/snapshot.rst create mode 100644 docs/api/storage_object.rst rename docs/{sdk => api}/types.rst (53%) delete mode 100644 docs/sdk/async/agent.rst delete mode 100644 docs/sdk/async/blueprint.rst delete mode 100644 docs/sdk/async/core.rst delete mode 100644 docs/sdk/async/devbox.rst delete mode 100644 docs/sdk/async/execution.rst delete mode 100644 docs/sdk/async/execution_result.rst delete mode 100644 docs/sdk/async/index.rst delete mode 100644 docs/sdk/async/scorer.rst delete mode 100644 docs/sdk/async/snapshot.rst delete mode 100644 docs/sdk/async/storage_object.rst delete mode 100644 docs/sdk/sync/agent.rst delete mode 100644 docs/sdk/sync/blueprint.rst delete mode 100644 docs/sdk/sync/core.rst delete mode 100644 docs/sdk/sync/devbox.rst delete mode 100644 docs/sdk/sync/execution.rst delete mode 100644 docs/sdk/sync/execution_result.rst delete mode 100644 docs/sdk/sync/index.rst delete mode 100644 docs/sdk/sync/scorer.rst delete mode 100644 docs/sdk/sync/snapshot.rst delete mode 100644 docs/sdk/sync/storage_object.rst diff --git a/docs/api/agent.rst b/docs/api/agent.rst new file mode 100644 index 000000000..465b6f66b --- /dev/null +++ b/docs/api/agent.rst @@ -0,0 +1,16 @@ +Agent +===== + +The ``Agent`` class represents an agent resource in the Runloop platform. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_agent + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.agent + :members: diff --git a/docs/api/axon.rst b/docs/api/axon.rst new file mode 100644 index 000000000..c47da18c9 --- /dev/null +++ b/docs/api/axon.rst @@ -0,0 +1,21 @@ +Axon +==== + +.. note:: + + Axon APIs are in beta and may change. + +The ``Axon`` class provides event communication channels with support for +publish/subscribe messaging, server-sent events (SSE) streaming, and SQL operations. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_axon + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.axon + :members: diff --git a/docs/api/benchmark.rst b/docs/api/benchmark.rst new file mode 100644 index 000000000..4ad8fe9c3 --- /dev/null +++ b/docs/api/benchmark.rst @@ -0,0 +1,18 @@ +Benchmark +========= + +The ``Benchmark`` class manages benchmarks that group multiple scenarios for +systematic evaluation. Use benchmarks to run and compare agent performance +across a suite of scenarios. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_benchmark + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.benchmark + :members: diff --git a/docs/api/benchmark_run.rst b/docs/api/benchmark_run.rst new file mode 100644 index 000000000..a1143cda9 --- /dev/null +++ b/docs/api/benchmark_run.rst @@ -0,0 +1,17 @@ +Benchmark Run +============= + +The ``BenchmarkRun`` class represents a running instance of a benchmark. +Use it to track progress, list scenario runs, and manage the benchmark lifecycle. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_benchmark_run + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.benchmark_run + :members: diff --git a/docs/api/blueprint.rst b/docs/api/blueprint.rst new file mode 100644 index 000000000..561a2d948 --- /dev/null +++ b/docs/api/blueprint.rst @@ -0,0 +1,17 @@ +Blueprint +========= + +The ``Blueprint`` class represents a reusable devbox configuration built from a Dockerfile +and optional setup commands. Use blueprints to create pre-configured devbox environments. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_blueprint + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.blueprint + :members: diff --git a/docs/api/core.rst b/docs/api/core.rst new file mode 100644 index 000000000..0121648ea --- /dev/null +++ b/docs/api/core.rst @@ -0,0 +1,33 @@ +Core Module +=========== + +The core module provides the main SDK entry points and operation manager classes. +Use :class:`~runloop_api_client.sdk.async_.AsyncRunloopSDK` for async/await code or +:class:`~runloop_api_client.sdk.sync.RunloopSDK` for synchronous code. + +Asynchronous API +---------------- + +.. autoclass:: runloop_api_client.sdk.async_.AsyncRunloopSDK + +.. automodule:: runloop_api_client.sdk.async_ + +Synchronous API +--------------- + +.. autoclass:: runloop_api_client.sdk.sync.RunloopSDK + +.. automodule:: runloop_api_client.sdk.sync + +Base REST Client +---------------- + +The SDK wraps the generated REST client. The ``api`` attribute on +:class:`~runloop_api_client.sdk.async_.AsyncRunloopSDK` / +:class:`~runloop_api_client.sdk.sync.RunloopSDK` provides direct access. + +.. autoclass:: runloop_api_client.AsyncRunloop + :no-members: + +.. autoclass:: runloop_api_client.Runloop + :no-members: diff --git a/docs/api/devbox.rst b/docs/api/devbox.rst new file mode 100644 index 000000000..b38dcd6e3 --- /dev/null +++ b/docs/api/devbox.rst @@ -0,0 +1,18 @@ +Devbox +====== + +The ``Devbox`` class provides methods for managing and interacting with a devbox instance, +including command execution, file operations, networking, and lifecycle management. +Devboxes support context manager usage for automatic cleanup. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_devbox + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.devbox + :members: diff --git a/docs/api/execution.rst b/docs/api/execution.rst new file mode 100644 index 000000000..a788f5e82 --- /dev/null +++ b/docs/api/execution.rst @@ -0,0 +1,17 @@ +Execution +========= + +The ``Execution`` class represents an in-progress asynchronous command execution on a devbox. +Use it to track, wait for, or cancel long-running commands started with ``exec_async()``. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_execution + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.execution + :members: diff --git a/docs/api/execution_result.rst b/docs/api/execution_result.rst new file mode 100644 index 000000000..36a230c2e --- /dev/null +++ b/docs/api/execution_result.rst @@ -0,0 +1,17 @@ +Execution Result +================ + +The ``ExecutionResult`` class wraps the output of a completed command execution, +providing access to stdout, stderr, exit codes, and output parsing utilities. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_execution_result + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.execution_result + :members: diff --git a/docs/api/gateway_config.rst b/docs/api/gateway_config.rst new file mode 100644 index 000000000..75600378b --- /dev/null +++ b/docs/api/gateway_config.rst @@ -0,0 +1,17 @@ +Gateway Config +============== + +The ``GatewayConfig`` class manages API gateway configurations that proxy +requests through devboxes with credential management and authentication. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_gateway_config + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.gateway_config + :members: diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 000000000..ae4cf429c --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,51 @@ +API Reference +============= + +The Runloop Python SDK provides both synchronous and asynchronous variants for every resource. +Each page below documents both variants together. The async API mirrors the sync API exactly, +with ``await`` required on all methods. + +.. toctree:: + :maxdepth: 1 + :caption: Compute & Execution + + core + devbox + execution + execution_result + blueprint + snapshot + storage_object + +.. toctree:: + :maxdepth: 1 + :caption: Agents & Evaluation + + agent + scorer + scenario + scenario_builder + scenario_run + benchmark + benchmark_run + +.. toctree:: + :maxdepth: 1 + :caption: Communication + + axon + +.. toctree:: + :maxdepth: 1 + :caption: Security & Configuration + + secret + mcp_config + gateway_config + network_policy + +.. toctree:: + :maxdepth: 1 + :caption: Type Reference + + types diff --git a/docs/api/mcp_config.rst b/docs/api/mcp_config.rst new file mode 100644 index 000000000..000101aa0 --- /dev/null +++ b/docs/api/mcp_config.rst @@ -0,0 +1,17 @@ +MCP Config +========== + +The ``McpConfig`` class manages Model Context Protocol (MCP) server configurations +that can be attached to devboxes, defining which MCP servers and tools are available. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_mcp_config + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.mcp_config + :members: diff --git a/docs/api/network_policy.rst b/docs/api/network_policy.rst new file mode 100644 index 000000000..d98c755b5 --- /dev/null +++ b/docs/api/network_policy.rst @@ -0,0 +1,17 @@ +Network Policy +============== + +The ``NetworkPolicy`` class manages network access policies for devboxes, +controlling which hostnames and services a devbox can communicate with. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_network_policy + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.network_policy + :members: diff --git a/docs/api/scenario.rst b/docs/api/scenario.rst new file mode 100644 index 000000000..d6053d48c --- /dev/null +++ b/docs/api/scenario.rst @@ -0,0 +1,17 @@ +Scenario +======== + +The ``Scenario`` class represents a scenario for evaluating agent performance. +Scenarios define environments, problem statements, and scoring criteria. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_scenario + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.scenario + :members: diff --git a/docs/api/scenario_builder.rst b/docs/api/scenario_builder.rst new file mode 100644 index 000000000..3bf0efeb0 --- /dev/null +++ b/docs/api/scenario_builder.rst @@ -0,0 +1,18 @@ +Scenario Builder +================ + +The ``ScenarioBuilder`` class provides a fluent API for constructing scenarios. +Chain methods to configure the environment, problem statement, and scoring criteria +before building or pushing the scenario. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_scenario_builder + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.scenario_builder + :members: diff --git a/docs/api/scenario_run.rst b/docs/api/scenario_run.rst new file mode 100644 index 000000000..3b3d98f76 --- /dev/null +++ b/docs/api/scenario_run.rst @@ -0,0 +1,17 @@ +Scenario Run +============ + +The ``ScenarioRun`` class represents a running instance of a scenario. +Use it to manage the run lifecycle, retrieve scores, and download logs. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_scenario_run + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.scenario_run + :members: diff --git a/docs/api/scorer.rst b/docs/api/scorer.rst new file mode 100644 index 000000000..dcbb51ba1 --- /dev/null +++ b/docs/api/scorer.rst @@ -0,0 +1,17 @@ +Scorer +====== + +The ``Scorer`` class defines custom scoring logic for evaluating scenario outputs. +Scorers produce a score in the range [0.0, 1.0] for scenario runs. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_scorer + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.scorer + :members: diff --git a/docs/api/secret.rst b/docs/api/secret.rst new file mode 100644 index 000000000..641e0b9ed --- /dev/null +++ b/docs/api/secret.rst @@ -0,0 +1,17 @@ +Secret +====== + +The ``Secret`` class manages encrypted key-value pairs that can be injected +into devbox environments as environment variables. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_secret + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.secret + :members: diff --git a/docs/api/snapshot.rst b/docs/api/snapshot.rst new file mode 100644 index 000000000..1226dc250 --- /dev/null +++ b/docs/api/snapshot.rst @@ -0,0 +1,17 @@ +Snapshot +======== + +The ``Snapshot`` class represents a point-in-time disk snapshot of a devbox. +Use snapshots to save and restore devbox state. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_snapshot + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.snapshot + :members: diff --git a/docs/api/storage_object.rst b/docs/api/storage_object.rst new file mode 100644 index 000000000..fd86df3f3 --- /dev/null +++ b/docs/api/storage_object.rst @@ -0,0 +1,17 @@ +Storage Object +============== + +The ``StorageObject`` class manages file storage objects that can be uploaded, downloaded, +and mounted into devboxes. + +Asynchronous API +---------------- + +.. automodule:: runloop_api_client.sdk.async_storage_object + :members: + +Synchronous API +--------------- + +.. automodule:: runloop_api_client.sdk.storage_object + :members: diff --git a/docs/sdk/types.rst b/docs/api/types.rst similarity index 53% rename from docs/sdk/types.rst rename to docs/api/types.rst index ab6abafee..e436002ca 100644 --- a/docs/sdk/types.rst +++ b/docs/api/types.rst @@ -50,7 +50,7 @@ File Operation Parameters Network Tunnel Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~ -.. autotypeddict:: runloop_api_client.sdk._types.SDKDevboxCreateTunnelParams +.. autotypeddict:: runloop_api_client.sdk._types.SDKDevboxEnableTunnelParams .. autotypeddict:: runloop_api_client.sdk._types.SDKDevboxRemoveTunnelParams @@ -78,6 +78,15 @@ These TypeDicts define parameters for storage object creation, listing, and down .. autotypeddict:: runloop_api_client.sdk._types.SDKObjectDownloadParams +Agent Parameters +---------------- + +These TypeDicts define parameters for agent creation and listing. + +.. autotypeddict:: runloop_api_client.sdk._types.SDKAgentCreateParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKAgentListParams + Scorer Parameters ----------------- @@ -89,6 +98,84 @@ These TypeDicts define parameters for scorer creation, listing, and updating. .. autotypeddict:: runloop_api_client.sdk._types.SDKScorerUpdateParams +Axon Parameters +--------------- + +These TypeDicts define parameters for axon creation, listing, publishing, and SQL operations. + +.. autotypeddict:: runloop_api_client.sdk._types.SDKAxonCreateParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKAxonListParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKAxonPublishParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKAxonSqlQueryParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKAxonSqlBatchParams + +Scenario Parameters +------------------- + +These TypeDicts define parameters for scenario listing, updating, and running. + +.. autotypeddict:: runloop_api_client.sdk._types.SDKScenarioListParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKScenarioUpdateParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKScenarioRunParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKScenarioRunAsyncParams + +Benchmark Parameters +-------------------- + +These TypeDicts define parameters for benchmark creation, listing, updating, and running. + +.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkCreateParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkListParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkUpdateParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkStartRunParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkListRunsParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKBenchmarkRunListScenarioRunsParams + +Network Policy Parameters +------------------------- + +These TypeDicts define parameters for network policy creation, listing, and updating. + +.. autotypeddict:: runloop_api_client.sdk._types.SDKNetworkPolicyCreateParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKNetworkPolicyListParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKNetworkPolicyUpdateParams + +MCP Config Parameters +--------------------- + +These TypeDicts define parameters for MCP (Model Context Protocol) configuration creation, listing, and updating. + +.. autotypeddict:: runloop_api_client.sdk._types.SDKMcpConfigCreateParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKMcpConfigListParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKMcpConfigUpdateParams + +Gateway Config Parameters +------------------------- + +These TypeDicts define parameters for gateway configuration creation, listing, and updating. + +.. autotypeddict:: runloop_api_client.sdk._types.SDKGatewayConfigCreateParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKGatewayConfigListParams + +.. autotypeddict:: runloop_api_client.sdk._types.SDKGatewayConfigUpdateParams + Core Request Options -------------------- @@ -109,14 +196,4 @@ These TypeDicts define options for timeouts, idempotency, polling, and other low Base API Type Reference ----------------------- -.. automodule:: runloop_api_client.types.shared_params - :members: - :undoc-members: - :imported-members: - :member-order: groupwise - -.. automodule:: runloop_api_client.types - :members: - :undoc-members: - :imported-members: - :member-order: groupwise \ No newline at end of file +.. auto-all-types:: diff --git a/docs/conf.py b/docs/conf.py index 3bdd4d06a..d5a897e66 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,8 +3,11 @@ # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html +from __future__ import annotations + import os import sys +from typing import Any # Add the src directory to the path so we can import the package sys.path.insert(0, os.path.abspath("../src")) @@ -54,6 +57,37 @@ autodoc_typehints = "description" autodoc_typehints_description_target = "documented" + +def _inject_type_submodules(_app: Any, docname: str, source: list[str]) -> None: + """Auto-generate automodule directives for all type submodules. + + Replaces the ``.. auto-all-types::`` placeholder in types.rst with + automodule directives for every submodule in runloop_api_client.types. + This ensures all types (including file-local helper types like Lifecycle + and BuildContext) get documented at their actual module path, making + cross-references resolve without any path rewriting. + """ + if docname != "api/types": + return + import pkgutil + + import runloop_api_client.types as types_pkg + + directives: list[str] = [] + for _, modname, ispkg in sorted( + pkgutil.walk_packages(types_pkg.__path__, types_pkg.__name__ + "."), + key=lambda x: x[1], + ): + if ispkg: + continue + directives.append(f".. automodule:: {modname}\n :members:\n :undoc-members:\n") + source[0] = source[0].replace(".. auto-all-types::", "\n".join(directives)) + + +def setup(app: Any) -> None: + app.connect("source-read", _inject_type_submodules) + + # Intersphinx mapping intersphinx_mapping = { "python": ("https://docs.python.org/3", None), diff --git a/docs/index.rst b/docs/index.rst index 99e32b84e..bae150e1f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,16 +2,8 @@ Runloop Python SDK Documentation ================================== The Runloop Python SDK provides a Pythonic, object-oriented interface for managing -devboxes, blueprints, snapshots, and storage objects. The SDK offers both synchronous -and asynchronous variants to match your runtime requirements. - -.. toctree:: - :maxdepth: 1 - :caption: Contents: - - sdk/async/index - sdk/sync/index - sdk/types +devboxes, blueprints, snapshots, storage objects, scenarios, benchmarks, and more. +The SDK offers both asynchronous and synchronous variants with identical interfaces. Installation ------------ @@ -25,23 +17,6 @@ Install the SDK using pip: Quick Start ----------- -Synchronous Example -~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from runloop_api_client import RunloopSDK - - runloop = RunloopSDK() - - # Create a ready-to-use devbox - with runloop.devbox.create(name="my-devbox") as devbox: - result = devbox.cmd.exec("echo 'Hello from Runloop!'") - print(result.stdout()) - -Asynchronous Example -~~~~~~~~~~~~~~~~~~~~ - .. code-block:: python import asyncio @@ -49,17 +24,34 @@ Asynchronous Example async def main(): runloop = AsyncRunloopSDK() - + async with await runloop.devbox.create(name="my-devbox") as devbox: result = await devbox.cmd.exec("echo 'Hello from Runloop!'") print(await result.stdout()) asyncio.run(main()) +A synchronous variant is also available: + +.. code-block:: python + + from runloop_api_client import RunloopSDK + + runloop = RunloopSDK() + + with runloop.devbox.create(name="my-devbox") as devbox: + result = devbox.cmd.exec("echo 'Hello from Runloop!'") + print(result.stdout()) + +.. toctree:: + :maxdepth: 2 + :caption: API Reference + + api/index + Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/docs/sdk/async/agent.rst b/docs/sdk/async/agent.rst deleted file mode 100644 index 724b23bd5..000000000 --- a/docs/sdk/async/agent.rst +++ /dev/null @@ -1,7 +0,0 @@ -Agent -====== - -The ``AsyncAgent`` class provides asynchronous methods for managing and interacting with stored Agents. - -.. automodule:: runloop_api_client.sdk.async_agent - :members: diff --git a/docs/sdk/async/blueprint.rst b/docs/sdk/async/blueprint.rst deleted file mode 100644 index c28afd382..000000000 --- a/docs/sdk/async/blueprint.rst +++ /dev/null @@ -1,8 +0,0 @@ -Blueprint -========= - -The ``AsyncBlueprint`` class provides asynchronous methods for managing devbox blueprints. - -.. automodule:: runloop_api_client.sdk.async_blueprint - :members: - diff --git a/docs/sdk/async/core.rst b/docs/sdk/async/core.rst deleted file mode 100644 index 96fc11a80..000000000 --- a/docs/sdk/async/core.rst +++ /dev/null @@ -1,8 +0,0 @@ -Core Module -=========== - -The core asynchronous SDK module provides the main entry point and async operation classes. - -.. autoclass:: runloop_api_client.sdk.async_.AsyncRunloopSDK - -.. automodule:: runloop_api_client.sdk.async_ \ No newline at end of file diff --git a/docs/sdk/async/devbox.rst b/docs/sdk/async/devbox.rst deleted file mode 100644 index 3698daa17..000000000 --- a/docs/sdk/async/devbox.rst +++ /dev/null @@ -1,7 +0,0 @@ -Devbox -====== - -The ``AsyncDevbox`` class provides asynchronous methods for managing and interacting with a devbox instance. - -.. automodule:: runloop_api_client.sdk.async_devbox - :members: \ No newline at end of file diff --git a/docs/sdk/async/execution.rst b/docs/sdk/async/execution.rst deleted file mode 100644 index 988688094..000000000 --- a/docs/sdk/async/execution.rst +++ /dev/null @@ -1,8 +0,0 @@ -Execution -========= - -The ``AsyncExecution`` class represents an asynchronous command execution in progress. - -.. automodule:: runloop_api_client.sdk.async_execution - :members: - diff --git a/docs/sdk/async/execution_result.rst b/docs/sdk/async/execution_result.rst deleted file mode 100644 index e2f6f3ad1..000000000 --- a/docs/sdk/async/execution_result.rst +++ /dev/null @@ -1,8 +0,0 @@ -Execution Result -================ - -The ``AsyncExecutionResult`` class represents the result of a completed command execution. - -.. automodule:: runloop_api_client.sdk.async_execution_result - :members: - diff --git a/docs/sdk/async/index.rst b/docs/sdk/async/index.rst deleted file mode 100644 index df9ad2585..000000000 --- a/docs/sdk/async/index.rst +++ /dev/null @@ -1,17 +0,0 @@ -Asynchronous SDK -================ - -The asynchronous SDK provides a non-blocking interface for managing devboxes, blueprints, snapshots, and storage objects. Use this variant when working with async/await Python code. - -.. toctree:: - :maxdepth: 2 - - core - devbox - execution - execution_result - blueprint - snapshot - storage_object - scorer - agent diff --git a/docs/sdk/async/scorer.rst b/docs/sdk/async/scorer.rst deleted file mode 100644 index 7564092d8..000000000 --- a/docs/sdk/async/scorer.rst +++ /dev/null @@ -1,9 +0,0 @@ -Scorer -====== - -The ``AsyncScorer`` class provides asynchronous methods for managing custom scenario scorers. - -.. automodule:: runloop_api_client.sdk.async_scorer - :members: - - diff --git a/docs/sdk/async/snapshot.rst b/docs/sdk/async/snapshot.rst deleted file mode 100644 index 75826695a..000000000 --- a/docs/sdk/async/snapshot.rst +++ /dev/null @@ -1,8 +0,0 @@ -Snapshot -======== - -The ``AsyncSnapshot`` class provides asynchronous methods for managing devbox snapshots. - -.. automodule:: runloop_api_client.sdk.async_snapshot - :members: - diff --git a/docs/sdk/async/storage_object.rst b/docs/sdk/async/storage_object.rst deleted file mode 100644 index bc7f79b35..000000000 --- a/docs/sdk/async/storage_object.rst +++ /dev/null @@ -1,8 +0,0 @@ -Storage Object -============== - -The ``AsyncStorageObject`` class provides asynchronous methods for managing storage objects. - -.. automodule:: runloop_api_client.sdk.async_storage_object - :members: - diff --git a/docs/sdk/sync/agent.rst b/docs/sdk/sync/agent.rst deleted file mode 100644 index 207c4b611..000000000 --- a/docs/sdk/sync/agent.rst +++ /dev/null @@ -1,7 +0,0 @@ -Agent -====== - -The ``Agent`` class provides synchronous methods for managing and interacting with stored Agents. - -.. automodule:: runloop_api_client.sdk.agent - :members: diff --git a/docs/sdk/sync/blueprint.rst b/docs/sdk/sync/blueprint.rst deleted file mode 100644 index c7e250285..000000000 --- a/docs/sdk/sync/blueprint.rst +++ /dev/null @@ -1,8 +0,0 @@ -Blueprint -========= - -The ``Blueprint`` class provides synchronous methods for managing devbox blueprints. - -.. automodule:: runloop_api_client.sdk.blueprint - :members: - diff --git a/docs/sdk/sync/core.rst b/docs/sdk/sync/core.rst deleted file mode 100644 index 66af97ba2..000000000 --- a/docs/sdk/sync/core.rst +++ /dev/null @@ -1,8 +0,0 @@ -Core Module -=========== - -The core synchronous SDK module provides the main entry point and operation classes. - -.. autoclass:: runloop_api_client.sdk.sync.RunloopSDK - -.. automodule:: runloop_api_client.sdk.sync \ No newline at end of file diff --git a/docs/sdk/sync/devbox.rst b/docs/sdk/sync/devbox.rst deleted file mode 100644 index 19875fc8c..000000000 --- a/docs/sdk/sync/devbox.rst +++ /dev/null @@ -1,7 +0,0 @@ -Devbox -====== - -The ``Devbox`` class provides synchronous methods for managing and interacting with a devbox instance. - -.. automodule:: runloop_api_client.sdk.devbox - :members: \ No newline at end of file diff --git a/docs/sdk/sync/execution.rst b/docs/sdk/sync/execution.rst deleted file mode 100644 index 8159eae1e..000000000 --- a/docs/sdk/sync/execution.rst +++ /dev/null @@ -1,8 +0,0 @@ -Execution -========= - -The ``Execution`` class represents an asynchronous command execution in progress. - -.. automodule:: runloop_api_client.sdk.execution - :members: - diff --git a/docs/sdk/sync/execution_result.rst b/docs/sdk/sync/execution_result.rst deleted file mode 100644 index 92496683a..000000000 --- a/docs/sdk/sync/execution_result.rst +++ /dev/null @@ -1,8 +0,0 @@ -Execution Result -================ - -The ``ExecutionResult`` class represents the result of a completed command execution. - -.. automodule:: runloop_api_client.sdk.execution_result - :members: - diff --git a/docs/sdk/sync/index.rst b/docs/sdk/sync/index.rst deleted file mode 100644 index 6e77b9d7d..000000000 --- a/docs/sdk/sync/index.rst +++ /dev/null @@ -1,18 +0,0 @@ -Synchronous SDK -=============== - -The synchronous SDK provides a blocking interface for managing devboxes, blueprints, snapshots, and storage objects. Use this variant when working in synchronous Python code. - -.. toctree:: - :maxdepth: 2 - - core - devbox - execution - execution_result - blueprint - snapshot - storage_object - scorer - agent - diff --git a/docs/sdk/sync/scorer.rst b/docs/sdk/sync/scorer.rst deleted file mode 100644 index 09b98dfb2..000000000 --- a/docs/sdk/sync/scorer.rst +++ /dev/null @@ -1,9 +0,0 @@ -Scorer -====== - -The ``Scorer`` class provides synchronous methods for managing custom scenario scorers. - -.. automodule:: runloop_api_client.sdk.scorer - :members: - - diff --git a/docs/sdk/sync/snapshot.rst b/docs/sdk/sync/snapshot.rst deleted file mode 100644 index 6b0bb358d..000000000 --- a/docs/sdk/sync/snapshot.rst +++ /dev/null @@ -1,8 +0,0 @@ -Snapshot -======== - -The ``Snapshot`` class provides synchronous methods for managing devbox snapshots. - -.. automodule:: runloop_api_client.sdk.snapshot - :members: - diff --git a/docs/sdk/sync/storage_object.rst b/docs/sdk/sync/storage_object.rst deleted file mode 100644 index 97b4bb77d..000000000 --- a/docs/sdk/sync/storage_object.rst +++ /dev/null @@ -1,8 +0,0 @@ -Storage Object -============== - -The ``StorageObject`` class provides synchronous methods for managing storage objects. - -.. automodule:: runloop_api_client.sdk.storage_object - :members: - diff --git a/uv.lock b/uv.lock index e2e199a71..15f6eb6a7 100644 --- a/uv.lock +++ b/uv.lock @@ -2386,7 +2386,7 @@ wheels = [ [[package]] name = "runloop-api-client" -version = "1.14.0" +version = "1.18.1" source = { editable = "." } dependencies = [ { name = "anyio" }, From 6a99c569914d93e07087ed5003ee7c77f2ab88ff Mon Sep 17 00:00:00 2001 From: sid-rl Date: Fri, 10 Apr 2026 12:03:13 -0700 Subject: [PATCH 2/5] docs: added Async vs Sync tabs, removed top-level class descriptions (#783) --- docs/api/agent.rst | 16 +++++++--------- docs/api/axon.rst | 21 +++++++-------------- docs/api/benchmark.rst | 18 +++++++----------- docs/api/benchmark_run.rst | 17 +++++++---------- docs/api/blueprint.rst | 17 +++++++---------- docs/api/core.rst | 18 +++++++----------- docs/api/devbox.rst | 18 +++++++----------- docs/api/execution.rst | 17 +++++++---------- docs/api/execution_result.rst | 17 +++++++---------- docs/api/gateway_config.rst | 17 +++++++---------- docs/api/mcp_config.rst | 17 +++++++---------- docs/api/network_policy.rst | 17 +++++++---------- docs/api/scenario.rst | 17 +++++++---------- docs/api/scenario_builder.rst | 18 +++++++----------- docs/api/scenario_run.rst | 17 +++++++---------- docs/api/scorer.rst | 17 +++++++---------- docs/api/secret.rst | 17 +++++++---------- docs/api/snapshot.rst | 17 +++++++---------- docs/api/storage_object.rst | 17 +++++++---------- docs/conf.py | 1 + 20 files changed, 134 insertions(+), 197 deletions(-) diff --git a/docs/api/agent.rst b/docs/api/agent.rst index 465b6f66b..5c163f290 100644 --- a/docs/api/agent.rst +++ b/docs/api/agent.rst @@ -1,16 +1,14 @@ Agent ===== -The ``Agent`` class represents an agent resource in the Runloop platform. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_agent - :members: + .. automodule:: runloop_api_client.sdk.async_agent + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.agent - :members: + .. automodule:: runloop_api_client.sdk.agent + :members: diff --git a/docs/api/axon.rst b/docs/api/axon.rst index c47da18c9..7bcbbcd13 100644 --- a/docs/api/axon.rst +++ b/docs/api/axon.rst @@ -1,21 +1,14 @@ Axon ==== -.. note:: +.. tabs:: - Axon APIs are in beta and may change. + .. tab:: Async -The ``Axon`` class provides event communication channels with support for -publish/subscribe messaging, server-sent events (SSE) streaming, and SQL operations. + .. automodule:: runloop_api_client.sdk.async_axon + :members: -Asynchronous API ----------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.async_axon - :members: - -Synchronous API ---------------- - -.. automodule:: runloop_api_client.sdk.axon - :members: + .. automodule:: runloop_api_client.sdk.axon + :members: diff --git a/docs/api/benchmark.rst b/docs/api/benchmark.rst index 4ad8fe9c3..3c61f8885 100644 --- a/docs/api/benchmark.rst +++ b/docs/api/benchmark.rst @@ -1,18 +1,14 @@ Benchmark ========= -The ``Benchmark`` class manages benchmarks that group multiple scenarios for -systematic evaluation. Use benchmarks to run and compare agent performance -across a suite of scenarios. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_benchmark - :members: + .. automodule:: runloop_api_client.sdk.async_benchmark + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.benchmark - :members: + .. automodule:: runloop_api_client.sdk.benchmark + :members: diff --git a/docs/api/benchmark_run.rst b/docs/api/benchmark_run.rst index a1143cda9..585788569 100644 --- a/docs/api/benchmark_run.rst +++ b/docs/api/benchmark_run.rst @@ -1,17 +1,14 @@ Benchmark Run ============= -The ``BenchmarkRun`` class represents a running instance of a benchmark. -Use it to track progress, list scenario runs, and manage the benchmark lifecycle. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_benchmark_run - :members: + .. automodule:: runloop_api_client.sdk.async_benchmark_run + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.benchmark_run - :members: + .. automodule:: runloop_api_client.sdk.benchmark_run + :members: diff --git a/docs/api/blueprint.rst b/docs/api/blueprint.rst index 561a2d948..368db83bc 100644 --- a/docs/api/blueprint.rst +++ b/docs/api/blueprint.rst @@ -1,17 +1,14 @@ Blueprint ========= -The ``Blueprint`` class represents a reusable devbox configuration built from a Dockerfile -and optional setup commands. Use blueprints to create pre-configured devbox environments. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_blueprint - :members: + .. automodule:: runloop_api_client.sdk.async_blueprint + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.blueprint - :members: + .. automodule:: runloop_api_client.sdk.blueprint + :members: diff --git a/docs/api/core.rst b/docs/api/core.rst index 0121648ea..7f2eca847 100644 --- a/docs/api/core.rst +++ b/docs/api/core.rst @@ -1,23 +1,19 @@ Core Module =========== -The core module provides the main SDK entry points and operation manager classes. -Use :class:`~runloop_api_client.sdk.async_.AsyncRunloopSDK` for async/await code or -:class:`~runloop_api_client.sdk.sync.RunloopSDK` for synchronous code. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. autoclass:: runloop_api_client.sdk.async_.AsyncRunloopSDK + .. autoclass:: runloop_api_client.sdk.async_.AsyncRunloopSDK -.. automodule:: runloop_api_client.sdk.async_ + .. automodule:: runloop_api_client.sdk.async_ -Synchronous API ---------------- + .. tab:: Sync -.. autoclass:: runloop_api_client.sdk.sync.RunloopSDK + .. autoclass:: runloop_api_client.sdk.sync.RunloopSDK -.. automodule:: runloop_api_client.sdk.sync + .. automodule:: runloop_api_client.sdk.sync Base REST Client ---------------- diff --git a/docs/api/devbox.rst b/docs/api/devbox.rst index b38dcd6e3..777f80a69 100644 --- a/docs/api/devbox.rst +++ b/docs/api/devbox.rst @@ -1,18 +1,14 @@ Devbox ====== -The ``Devbox`` class provides methods for managing and interacting with a devbox instance, -including command execution, file operations, networking, and lifecycle management. -Devboxes support context manager usage for automatic cleanup. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_devbox - :members: + .. automodule:: runloop_api_client.sdk.async_devbox + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.devbox - :members: + .. automodule:: runloop_api_client.sdk.devbox + :members: diff --git a/docs/api/execution.rst b/docs/api/execution.rst index a788f5e82..b4a69a0a4 100644 --- a/docs/api/execution.rst +++ b/docs/api/execution.rst @@ -1,17 +1,14 @@ Execution ========= -The ``Execution`` class represents an in-progress asynchronous command execution on a devbox. -Use it to track, wait for, or cancel long-running commands started with ``exec_async()``. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_execution - :members: + .. automodule:: runloop_api_client.sdk.async_execution + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.execution - :members: + .. automodule:: runloop_api_client.sdk.execution + :members: diff --git a/docs/api/execution_result.rst b/docs/api/execution_result.rst index 36a230c2e..14fa31a5b 100644 --- a/docs/api/execution_result.rst +++ b/docs/api/execution_result.rst @@ -1,17 +1,14 @@ Execution Result ================ -The ``ExecutionResult`` class wraps the output of a completed command execution, -providing access to stdout, stderr, exit codes, and output parsing utilities. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_execution_result - :members: + .. automodule:: runloop_api_client.sdk.async_execution_result + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.execution_result - :members: + .. automodule:: runloop_api_client.sdk.execution_result + :members: diff --git a/docs/api/gateway_config.rst b/docs/api/gateway_config.rst index 75600378b..d14c128c6 100644 --- a/docs/api/gateway_config.rst +++ b/docs/api/gateway_config.rst @@ -1,17 +1,14 @@ Gateway Config ============== -The ``GatewayConfig`` class manages API gateway configurations that proxy -requests through devboxes with credential management and authentication. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_gateway_config - :members: + .. automodule:: runloop_api_client.sdk.async_gateway_config + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.gateway_config - :members: + .. automodule:: runloop_api_client.sdk.gateway_config + :members: diff --git a/docs/api/mcp_config.rst b/docs/api/mcp_config.rst index 000101aa0..8348041bc 100644 --- a/docs/api/mcp_config.rst +++ b/docs/api/mcp_config.rst @@ -1,17 +1,14 @@ MCP Config ========== -The ``McpConfig`` class manages Model Context Protocol (MCP) server configurations -that can be attached to devboxes, defining which MCP servers and tools are available. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_mcp_config - :members: + .. automodule:: runloop_api_client.sdk.async_mcp_config + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.mcp_config - :members: + .. automodule:: runloop_api_client.sdk.mcp_config + :members: diff --git a/docs/api/network_policy.rst b/docs/api/network_policy.rst index d98c755b5..7be288420 100644 --- a/docs/api/network_policy.rst +++ b/docs/api/network_policy.rst @@ -1,17 +1,14 @@ Network Policy ============== -The ``NetworkPolicy`` class manages network access policies for devboxes, -controlling which hostnames and services a devbox can communicate with. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_network_policy - :members: + .. automodule:: runloop_api_client.sdk.async_network_policy + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.network_policy - :members: + .. automodule:: runloop_api_client.sdk.network_policy + :members: diff --git a/docs/api/scenario.rst b/docs/api/scenario.rst index d6053d48c..7a3dc9497 100644 --- a/docs/api/scenario.rst +++ b/docs/api/scenario.rst @@ -1,17 +1,14 @@ Scenario ======== -The ``Scenario`` class represents a scenario for evaluating agent performance. -Scenarios define environments, problem statements, and scoring criteria. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_scenario - :members: + .. automodule:: runloop_api_client.sdk.async_scenario + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.scenario - :members: + .. automodule:: runloop_api_client.sdk.scenario + :members: diff --git a/docs/api/scenario_builder.rst b/docs/api/scenario_builder.rst index 3bf0efeb0..8bd21aad7 100644 --- a/docs/api/scenario_builder.rst +++ b/docs/api/scenario_builder.rst @@ -1,18 +1,14 @@ Scenario Builder ================ -The ``ScenarioBuilder`` class provides a fluent API for constructing scenarios. -Chain methods to configure the environment, problem statement, and scoring criteria -before building or pushing the scenario. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_scenario_builder - :members: + .. automodule:: runloop_api_client.sdk.async_scenario_builder + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.scenario_builder - :members: + .. automodule:: runloop_api_client.sdk.scenario_builder + :members: diff --git a/docs/api/scenario_run.rst b/docs/api/scenario_run.rst index 3b3d98f76..c98cec393 100644 --- a/docs/api/scenario_run.rst +++ b/docs/api/scenario_run.rst @@ -1,17 +1,14 @@ Scenario Run ============ -The ``ScenarioRun`` class represents a running instance of a scenario. -Use it to manage the run lifecycle, retrieve scores, and download logs. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_scenario_run - :members: + .. automodule:: runloop_api_client.sdk.async_scenario_run + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.scenario_run - :members: + .. automodule:: runloop_api_client.sdk.scenario_run + :members: diff --git a/docs/api/scorer.rst b/docs/api/scorer.rst index dcbb51ba1..4caec75de 100644 --- a/docs/api/scorer.rst +++ b/docs/api/scorer.rst @@ -1,17 +1,14 @@ Scorer ====== -The ``Scorer`` class defines custom scoring logic for evaluating scenario outputs. -Scorers produce a score in the range [0.0, 1.0] for scenario runs. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_scorer - :members: + .. automodule:: runloop_api_client.sdk.async_scorer + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.scorer - :members: + .. automodule:: runloop_api_client.sdk.scorer + :members: diff --git a/docs/api/secret.rst b/docs/api/secret.rst index 641e0b9ed..e04f1dfb9 100644 --- a/docs/api/secret.rst +++ b/docs/api/secret.rst @@ -1,17 +1,14 @@ Secret ====== -The ``Secret`` class manages encrypted key-value pairs that can be injected -into devbox environments as environment variables. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_secret - :members: + .. automodule:: runloop_api_client.sdk.async_secret + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.secret - :members: + .. automodule:: runloop_api_client.sdk.secret + :members: diff --git a/docs/api/snapshot.rst b/docs/api/snapshot.rst index 1226dc250..7b8436d30 100644 --- a/docs/api/snapshot.rst +++ b/docs/api/snapshot.rst @@ -1,17 +1,14 @@ Snapshot ======== -The ``Snapshot`` class represents a point-in-time disk snapshot of a devbox. -Use snapshots to save and restore devbox state. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_snapshot - :members: + .. automodule:: runloop_api_client.sdk.async_snapshot + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.snapshot - :members: + .. automodule:: runloop_api_client.sdk.snapshot + :members: diff --git a/docs/api/storage_object.rst b/docs/api/storage_object.rst index fd86df3f3..a78f26de1 100644 --- a/docs/api/storage_object.rst +++ b/docs/api/storage_object.rst @@ -1,17 +1,14 @@ Storage Object ============== -The ``StorageObject`` class manages file storage objects that can be uploaded, downloaded, -and mounted into devboxes. +.. tabs:: -Asynchronous API ----------------- + .. tab:: Async -.. automodule:: runloop_api_client.sdk.async_storage_object - :members: + .. automodule:: runloop_api_client.sdk.async_storage_object + :members: -Synchronous API ---------------- + .. tab:: Sync -.. automodule:: runloop_api_client.sdk.storage_object - :members: + .. automodule:: runloop_api_client.sdk.storage_object + :members: diff --git a/docs/conf.py b/docs/conf.py index d5a897e66..42c04c554 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,6 +29,7 @@ "sphinx.ext.viewcode", "sphinx_toolbox.more_autodoc.autotypeddict", "sphinx_autodoc_typehints", + "sphinx_tabs.tabs", ] templates_path = ["_templates"] From f04a5069c33fceb7a4d3db634bfd77ab789314dc Mon Sep 17 00:00:00 2001 From: sid-rl Date: Fri, 10 Apr 2026 15:30:04 -0700 Subject: [PATCH 3/5] docs: show full field descriptions for SDK parameter types (#784) --- .github/workflows/ci.yml | 2 +- docs/conf.py | 88 +++++++++++++++++++++++++++++++++++++--- pyproject.toml | 1 + uv.lock | 2 + 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71eb10127..10b9a3a85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: version: '0.10.2' - name: Install dependencies - run: uv sync --all-extras + run: uv sync --all-extras --group docs - name: Run lints run: ./scripts/lint diff --git a/docs/conf.py b/docs/conf.py index 42c04c554..4739753c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,7 +7,15 @@ import os import sys -from typing import Any +import pkgutil +from typing import get_type_hints +from typing_extensions import override + +from sphinx.errors import PycodeError +from sphinx.pycode import ModuleAnalyzer +from sphinx.application import Sphinx +from sphinx.ext.autodoc import Documenter +from sphinx_toolbox.more_autodoc.autotypeddict import TypedDictDocumenter # Add the src directory to the path so we can import the package sys.path.insert(0, os.path.abspath("../src")) @@ -59,7 +67,76 @@ autodoc_typehints_description_target = "documented" -def _inject_type_submodules(_app: Any, docname: str, source: list[str]) -> None: +# -- Autodocumenter extensions ----------------------------------------------- + + +def _collect_field_docstrings(cls: type) -> dict[str, list[str]]: + """Collect field docstrings from cls and all TypedDict ancestors via __orig_bases__.""" + result: dict[str, list[str]] = {} + # Parents first — child docstrings overwrite via later assignment + for base in getattr(cls, "__orig_bases__", ()): + origin = getattr(base, "__origin__", base) + if isinstance(origin, type) and origin is not cls: + result.update(_collect_field_docstrings(origin)) + try: + attr_docs = ModuleAnalyzer.for_module(cls.__module__).find_attr_docs() + for (_, attr_name), doc_lines in attr_docs.items(): + result[attr_name] = doc_lines + except PycodeError: + pass + return result + + +class _InheritedDocsTypedDictDocumenter(TypedDictDocumenter): + """TypedDictDocumenter that collects field docstrings from parent TypedDicts. + + Upstream only scans self.object.__module__ for field docstrings, so + inherited descriptions are lost. This subclass traverses __orig_bases__. + Upstream bug: https://github.com/sphinx-doc/sphinx/issues/9290 + Patching sphinx_toolbox 4.1.2. + """ + + @override + def sort_members( + self, + documenters: list[tuple[Documenter, bool]], + order: str, + ) -> list[tuple[Documenter, bool]]: + # Skip TypedDictDocumenter.sort_members (returns [] after adding + # lines with wrong docstrings). Call ClassDocumenter.sort_members + # to get the properly sorted documenters list. + documenters = super(TypedDictDocumenter, self).sort_members(documenters, order) + docstrings = _collect_field_docstrings(self.object) + required_keys: list[str] = [] + optional_keys: list[str] = [] + types = get_type_hints(self.object) + + for d in documenters: + name = d[0].name.split(".")[-1] + if name in self.object.__required_keys__: + required_keys.append(name) + elif name in self.object.__optional_keys__: + optional_keys.append(name) + + sourcename = self.get_sourcename() + if required_keys: + self.add_line("", sourcename) + self.add_line(":Required Keys:", sourcename) + self.document_keys(required_keys, types, docstrings) # pyright: ignore[reportUnknownMemberType] + self.add_line("", sourcename) + if optional_keys: + self.add_line("", sourcename) + self.add_line(":Optional Keys:", sourcename) + self.document_keys(optional_keys, types, docstrings) # pyright: ignore[reportUnknownMemberType] + self.add_line("", sourcename) + + return [] + + +# -- Dynamic type documentation ---------------------------------------------- + + +def _inject_type_submodules(_app: Sphinx, docname: str, source: list[str]) -> None: """Auto-generate automodule directives for all type submodules. Replaces the ``.. auto-all-types::`` placeholder in types.rst with @@ -70,8 +147,6 @@ def _inject_type_submodules(_app: Any, docname: str, source: list[str]) -> None: """ if docname != "api/types": return - import pkgutil - import runloop_api_client.types as types_pkg directives: list[str] = [] @@ -85,8 +160,9 @@ def _inject_type_submodules(_app: Any, docname: str, source: list[str]) -> None: source[0] = source[0].replace(".. auto-all-types::", "\n".join(directives)) -def setup(app: Any) -> None: - app.connect("source-read", _inject_type_submodules) +def setup(app: Sphinx) -> None: + app.add_autodocumenter(_InheritedDocsTypedDictDocumenter, override=True) + app.connect("source-read", _inject_type_submodules) # pyright: ignore[reportUnknownMemberType] # Intersphinx mapping diff --git a/pyproject.toml b/pyproject.toml index 52d3431d2..2c3dbdb53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ docs = [ "furo>=2025.9.25", "sphinx>=7.4.7", "sphinx-autodoc-typehints>=2.3.0", + "sphinx-tabs>=3.4.0", "sphinx-toolbox>=4.0.0", ] diff --git a/uv.lock b/uv.lock index 15f6eb6a7..7f63794ea 100644 --- a/uv.lock +++ b/uv.lock @@ -2435,6 +2435,7 @@ docs = [ { name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinx-autodoc-typehints", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, { name = "sphinx-autodoc-typehints", version = "3.9.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-tabs" }, { name = "sphinx-toolbox" }, ] @@ -2474,6 +2475,7 @@ docs = [ { name = "furo", specifier = ">=2025.9.25" }, { name = "sphinx", specifier = ">=7.4.7" }, { name = "sphinx-autodoc-typehints", specifier = ">=2.3.0" }, + { name = "sphinx-tabs", specifier = ">=3.4.0" }, { name = "sphinx-toolbox", specifier = ">=4.0.0" }, ] From f4b942b3329e98a4ea5182c04d6db90fd0b70308 Mon Sep 17 00:00:00 2001 From: james-rl Date: Mon, 13 Apr 2026 13:19:49 -0700 Subject: [PATCH 4/5] docs: add snapshot, suspend & resume example (#764) --- EXAMPLES.md | 32 +++++++ README.md | 2 +- examples/devbox_snapshots.py | 172 +++++++++++++++++++++++++++++++++++ examples/registry.py | 8 ++ llms.txt | 1 + 5 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 examples/devbox_snapshots.py diff --git a/EXAMPLES.md b/EXAMPLES.md index b419b5eb5..5c3203791 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -10,6 +10,7 @@ Runnable examples live in [`examples/`](./examples). - [Blueprint with Build Context](#blueprint-with-build-context) - [Devbox From Blueprint (Run Command, Shutdown)](#devbox-from-blueprint-lifecycle) - [Devbox Snapshot and Resume](#devbox-snapshot-resume) +- [Devbox Snapshots (Suspend, Resume, Restore, Delete)](#devbox-snapshots) - [Devbox Tunnel (HTTP Server Access)](#devbox-tunnel) - [MCP Hub + Claude Code + GitHub](#mcp-github-tools) - [Secrets with Devbox and Agent Gateway](#secrets-with-devbox) @@ -106,6 +107,37 @@ uv run pytest -m smoketest tests/smoketests/examples/ **Source:** [`examples/devbox_snapshot_resume.py`](./examples/devbox_snapshot_resume.py) + +## Devbox Snapshots (Suspend, Resume, Restore, Delete) + +**Use case:** Upload a file to a devbox, preserve it across suspend and resume, create a disk snapshot, restore multiple devboxes from that snapshot, mutate each copy independently, and delete the snapshot when finished. + +**Tags:** `devbox`, `snapshot`, `suspend`, `resume`, `files`, `cleanup` + +### Workflow +- Create a source devbox +- Upload a file and mutate it into a shared baseline +- Suspend and resume the source devbox +- Create a disk snapshot from the resumed devbox +- Restore two additional devboxes from the same snapshot baseline +- Mutate the same file differently in each devbox to prove isolation +- Shutdown the devboxes and delete the snapshot + +### Prerequisites +- `RUNLOOP_API_KEY` + +### Run +```sh +uv run python -m examples.devbox_snapshots +``` + +### Test +```sh +uv run pytest -m smoketest tests/smoketests/examples/ +``` + +**Source:** [`examples/devbox_snapshots.py`](./examples/devbox_snapshots.py) + ## Devbox Tunnel (HTTP Server Access) diff --git a/README.md b/README.md index 09aeb9016..d4625b271 100644 --- a/README.md +++ b/README.md @@ -288,7 +288,7 @@ Error codes are as follows: Certain errors are automatically retried 5 times by default, with a short exponential backoff. Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, -429 Rate Limit, and >=500 Internal errors are all retried by default for GET requests. For POST requests, only +429 Rate Limit, and >=500 Internal errors are all retried by default for GET requests. For POST requests, only 429 errors will be retried. You can use the `max_retries` option to configure or disable retry settings: diff --git a/examples/devbox_snapshots.py b/examples/devbox_snapshots.py new file mode 100644 index 000000000..a33c37747 --- /dev/null +++ b/examples/devbox_snapshots.py @@ -0,0 +1,172 @@ +#!/usr/bin/env -S uv run python +""" +--- +title: Devbox Snapshots (Suspend, Resume, Restore, Delete) +slug: devbox-snapshots +use_case: Upload a file to a devbox, preserve it across suspend and resume, create a disk snapshot, restore multiple devboxes from that snapshot, mutate each copy independently, and delete the snapshot when finished. +workflow: + - Create a source devbox + - Upload a file and mutate it into a shared baseline + - Suspend and resume the source devbox + - Create a disk snapshot from the resumed devbox + - Restore two additional devboxes from the same snapshot baseline + - Mutate the same file differently in each devbox to prove isolation + - Shutdown the devboxes and delete the snapshot +tags: + - devbox + - snapshot + - suspend + - resume + - files + - cleanup +prerequisites: + - RUNLOOP_API_KEY +run: uv run python -m examples.devbox_snapshots +test: uv run pytest -m smoketest tests/smoketests/examples/ +--- +""" + +from __future__ import annotations + +import tempfile +from pathlib import Path + +from runloop_api_client import AsyncRunloopSDK +from runloop_api_client.lib.polling import PollingConfig + +from ._harness import run_as_cli, unique_name, wrap_recipe +from .example_types import ExampleCheck, RecipeOutput, RecipeContext + +FILE_PATH = "/tmp/snapshot-demo.txt" +POLLING_CONFIG = PollingConfig(timeout_seconds=120.0, interval_seconds=5.0) + + +async def recipe(ctx: RecipeContext) -> RecipeOutput: + """Demonstrate suspend/resume and shared snapshot restoration with isolated mutations.""" + cleanup = ctx.cleanup + sdk = AsyncRunloopSDK() + + resources_created: list[str] = [] + + uploaded_contents = "uploaded-from-local-file" + baseline_contents = "baseline-after-upload-and-mutation" + source_contents = "source-devbox-after-isolated-mutation" + clone_a_contents = "clone-a-after-isolated-mutation" + clone_b_contents = "clone-b-after-isolated-mutation" + + with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as tmp_file: + tmp_file.write(uploaded_contents) + local_file_path = Path(tmp_file.name) + cleanup.add("local-file:snapshot-demo", lambda: local_file_path.unlink(missing_ok=True)) + + source_devbox = await sdk.devbox.create( + name=unique_name("snapshot-source"), + launch_parameters={ + "resource_size_request": "X_SMALL", + "keep_alive_time_seconds": 60 * 5, + }, + ) + cleanup.add(f"devbox:{source_devbox.id}", source_devbox.shutdown) + resources_created.append(f"devbox:{source_devbox.id}") + + await source_devbox.file.upload(path=FILE_PATH, file=local_file_path) + uploaded_readback = await source_devbox.file.read(file_path=FILE_PATH) + + await source_devbox.file.write(file_path=FILE_PATH, contents=baseline_contents) + + await source_devbox.suspend() + suspended_info = await source_devbox.await_suspended(polling_config=POLLING_CONFIG) + + resumed_info = await source_devbox.resume(polling_config=POLLING_CONFIG) + resumed_readback = await source_devbox.file.read(file_path=FILE_PATH) + + snapshot = await source_devbox.snapshot_disk( + name=unique_name("snapshot-baseline"), + commit_message="Capture the shared baseline after suspend and resume.", + polling_config=POLLING_CONFIG, + ) + cleanup.add(f"snapshot:{snapshot.id}", snapshot.delete) + resources_created.append(f"snapshot:{snapshot.id}") + + clone_a = await snapshot.create_devbox( + name=unique_name("snapshot-clone-a"), + launch_parameters={ + "resource_size_request": "X_SMALL", + "keep_alive_time_seconds": 60 * 5, + }, + ) + cleanup.add(f"devbox:{clone_a.id}", clone_a.shutdown) + resources_created.append(f"devbox:{clone_a.id}") + + # clone_a used snapshot.create_devbox(); clone_b uses sdk.devbox.create_from_snapshot() + # to demonstrate both entry points for restoring a devbox from a snapshot. + clone_b = await sdk.devbox.create_from_snapshot( + snapshot.id, + name=unique_name("snapshot-clone-b"), + launch_parameters={ + "resource_size_request": "X_SMALL", + "keep_alive_time_seconds": 60 * 5, + }, + ) + cleanup.add(f"devbox:{clone_b.id}", clone_b.shutdown) + resources_created.append(f"devbox:{clone_b.id}") + + clone_a_baseline_readback = await clone_a.file.read(file_path=FILE_PATH) + clone_b_baseline_readback = await clone_b.file.read(file_path=FILE_PATH) + + await source_devbox.file.write(file_path=FILE_PATH, contents=source_contents) + await clone_a.file.write(file_path=FILE_PATH, contents=clone_a_contents) + await clone_b.file.write(file_path=FILE_PATH, contents=clone_b_contents) + + source_isolated_readback = await source_devbox.file.read(file_path=FILE_PATH) + clone_a_isolated_readback = await clone_a.file.read(file_path=FILE_PATH) + clone_b_isolated_readback = await clone_b.file.read(file_path=FILE_PATH) + + return RecipeOutput( + resources_created=resources_created, + checks=[ + ExampleCheck( + name="uploaded file is readable on the source devbox", + passed=uploaded_readback == uploaded_contents, + details=uploaded_readback, + ), + ExampleCheck( + name="suspend reaches the suspended state", + passed=suspended_info.status == "suspended", + details=f"status={suspended_info.status}", + ), + ExampleCheck( + name="resume preserves the baseline file contents", + passed=resumed_info.status == "running" and resumed_readback == baseline_contents, + details=f"status={resumed_info.status}, contents={resumed_readback}", + ), + ExampleCheck( + name="multiple devboxes can use the same snapshot baseline", + passed=( + clone_a_baseline_readback == baseline_contents and clone_b_baseline_readback == baseline_contents + ), + details=(f"clone_a={clone_a_baseline_readback}, clone_b={clone_b_baseline_readback}"), + ), + ExampleCheck( + name="devboxes diverge after isolated mutations", + passed=( + source_isolated_readback == source_contents + and clone_a_isolated_readback == clone_a_contents + and clone_b_isolated_readback == clone_b_contents + ), + details=( + "source=" + f"{source_isolated_readback}, " + f"clone_a={clone_a_isolated_readback}, " + f"clone_b={clone_b_isolated_readback}" + ), + ), + ], + ) + + +run_devbox_snapshots_example = wrap_recipe(recipe) + + +if __name__ == "__main__": + run_as_cli(run_devbox_snapshots_example) diff --git a/examples/registry.py b/examples/registry.py index 7362e2612..bc6ca94ef 100644 --- a/examples/registry.py +++ b/examples/registry.py @@ -9,6 +9,7 @@ from .devbox_tunnel import run_devbox_tunnel_example from .example_types import ExampleResult +from .devbox_snapshots import run_devbox_snapshots_example from .mcp_github_tools import run_mcp_github_tools_example from .secrets_with_devbox import run_secrets_with_devbox_example from .devbox_snapshot_resume import run_devbox_snapshot_resume_example @@ -39,6 +40,13 @@ "required_env": ["RUNLOOP_API_KEY"], "run": run_devbox_snapshot_resume_example, }, + { + "slug": "devbox-snapshots", + "title": "Devbox Snapshots (Suspend, Resume, Restore, Delete)", + "file_name": "devbox_snapshots.py", + "required_env": ["RUNLOOP_API_KEY"], + "run": run_devbox_snapshots_example, + }, { "slug": "devbox-tunnel", "title": "Devbox Tunnel (HTTP Server Access)", diff --git a/llms.txt b/llms.txt index fe07b3bac..1b8f74f65 100644 --- a/llms.txt +++ b/llms.txt @@ -11,6 +11,7 @@ ## Core Patterns - [Devbox lifecycle example](examples/devbox_from_blueprint_lifecycle.py): Create blueprint, launch devbox, run commands, cleanup +- [Devbox snapshots example](examples/devbox_snapshots.py): Suspend and resume a devbox, create a shared snapshot baseline, restore multiple devboxes, verify isolation, cleanup - [Devbox snapshot and resume example](examples/devbox_snapshot_resume.py): Snapshot disk, resume from snapshot, verify state isolation - [MCP GitHub example](examples/mcp_github_tools.py): MCP Hub integration with Claude Code - [Secrets with Devbox example](examples/secrets_with_devbox.py): Inject a normal secret for app runtime use, protect upstream credentials with agent gateway, verify both behaviors, cleanup From eb3ad2c5b9e85a0511924ff6d4c03f88f5cf8f8c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:32:45 +0000 Subject: [PATCH 5/5] release: 1.19.0 (#785) Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- .stats.yml | 8 +- CHANGELOG.md | 23 ++ api.md | 38 ++- pyproject.toml | 2 +- src/runloop_api_client/_client.py | 76 +++++ src/runloop_api_client/_utils/_utils.py | 5 +- src/runloop_api_client/_version.py | 2 +- src/runloop_api_client/resources/__init__.py | 28 ++ src/runloop_api_client/resources/agents.py | 297 +++++++++++++++++- src/runloop_api_client/resources/apikeys.py | 197 ++++++++++++ .../resources/restricted_keys.py | 202 ++++++++++++ src/runloop_api_client/types/__init__.py | 8 + .../types/agent_devbox_counts_view.py | 24 ++ .../types/agent_list_public_params.py | 30 ++ .../types/api_key_created_view.py | 17 + .../types/apikey_create_params.py | 14 + .../types/restricted_key_create_params.py | 18 ++ .../types/restricted_key_created_view.py | 20 ++ .../types/scope_entry_view.py | 26 ++ .../types/scope_entry_view_param.py | 23 ++ .../types/shared/launch_parameters.py | 3 + .../types/shared_params/launch_parameters.py | 3 + tests/api_resources/test_agents.py | 205 +++++++++++- tests/api_resources/test_apikeys.py | 90 ++++++ tests/api_resources/test_benchmarks.py | 10 +- tests/api_resources/test_blueprints.py | 30 +- tests/api_resources/test_devboxes.py | 10 +- tests/api_resources/test_restricted_keys.py | 102 ++++++ tests/api_resources/test_scenarios.py | 30 +- tests/test_extract_files.py | 9 + 31 files changed, 1524 insertions(+), 28 deletions(-) create mode 100644 src/runloop_api_client/resources/apikeys.py create mode 100644 src/runloop_api_client/resources/restricted_keys.py create mode 100644 src/runloop_api_client/types/agent_devbox_counts_view.py create mode 100644 src/runloop_api_client/types/agent_list_public_params.py create mode 100644 src/runloop_api_client/types/api_key_created_view.py create mode 100644 src/runloop_api_client/types/apikey_create_params.py create mode 100644 src/runloop_api_client/types/restricted_key_create_params.py create mode 100644 src/runloop_api_client/types/restricted_key_created_view.py create mode 100644 src/runloop_api_client/types/scope_entry_view.py create mode 100644 src/runloop_api_client/types/scope_entry_view_param.py create mode 100644 tests/api_resources/test_apikeys.py create mode 100644 tests/api_resources/test_restricted_keys.py diff --git a/.release-please-manifest.json b/.release-please-manifest.json index aa06f8634..de44c40d8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.18.1" + ".": "1.19.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 7c298b7e4..e948713b6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 110 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f0eb12cf4df4fa3046bd88aae4966062eb6e9703768a07a0136da2f26a2ebd56.yml -openapi_spec_hash: cdbd63a8162f1e987e937042cbd60eea -config_hash: 526cf0707adc54c690fc687a1c6db728 +configured_endpoints: 115 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-5b536a11a713dd4e47b270c130368dbfdf1f30282f262c160cd0411fcd291806.yml +openapi_spec_hash: f94d993a7f34461ebde7d0186e8e3c3a +config_hash: 12de9459ff629b6a3072a75b236b7b70 diff --git a/CHANGELOG.md b/CHANGELOG.md index e01266433..afa8cdb3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 1.19.0 (2026-04-13) + +Full Changelog: [v1.18.1...v1.19.0](https://github.com/runloopai/api-client-python/compare/v1.18.1...v1.19.0) + +### Features + +* Add wake on axon event as an API primitive ([#8681](https://github.com/runloopai/api-client-python/issues/8681)) ([255099a](https://github.com/runloopai/api-client-python/commit/255099a940a7892f3c66e6534517e3545d7daccb)) +* Allow API keys to create API and restricted keys ([#8663](https://github.com/runloopai/api-client-python/issues/8663)) ([2e03b55](https://github.com/runloopai/api-client-python/commit/2e03b55638a83a8354481f47a30910e94ff57fc3)) + + +### Bug Fixes + +* add missing agent API paths to stainless config ([#8699](https://github.com/runloopai/api-client-python/issues/8699)) ([4f6fe60](https://github.com/runloopai/api-client-python/commit/4f6fe60a63fd31c049e54febb81801031dce2325)) +* ensure file data are only sent as 1 parameter ([f7ca2cc](https://github.com/runloopai/api-client-python/commit/f7ca2cc280f1e7a4ab73f331be8fe3e33ae1c611)) + + +### Documentation + +* add snapshot, suspend & resume example ([#764](https://github.com/runloopai/api-client-python/issues/764)) ([f4b942b](https://github.com/runloopai/api-client-python/commit/f4b942b3329e98a4ea5182c04d6db90fd0b70308)) +* added Async vs Sync tabs, removed top-level class descriptions ([#783](https://github.com/runloopai/api-client-python/issues/783)) ([6a99c56](https://github.com/runloopai/api-client-python/commit/6a99c569914d93e07087ed5003ee7c77f2ab88ff)) +* restructure sphinx docs with async-first API reference and full type coverage ([#782](https://github.com/runloopai/api-client-python/issues/782)) ([514cf93](https://github.com/runloopai/api-client-python/commit/514cf93e8ff8b97bfde171a83cf9abf211193e7f)) +* show full field descriptions for SDK parameter types ([#784](https://github.com/runloopai/api-client-python/issues/784)) ([f04a506](https://github.com/runloopai/api-client-python/commit/f04a5069c33fceb7a4d3db634bfd77ab789314dc)) + ## 1.18.1 (2026-04-10) Full Changelog: [v1.17.0...v1.18.1](https://github.com/runloopai/api-client-python/compare/v1.17.0...v1.18.1) diff --git a/api.md b/api.md index fd95076a1..1d97dc90c 100644 --- a/api.md +++ b/api.md @@ -79,7 +79,12 @@ Methods: Types: ```python -from runloop_api_client.types import AgentCreateParameters, AgentListView, AgentView +from runloop_api_client.types import ( + AgentCreateParameters, + AgentDevboxCountsView, + AgentListView, + AgentView, +) ``` Methods: @@ -87,6 +92,9 @@ Methods: - client.agents.create(\*\*params) -> AgentView - client.agents.retrieve(id) -> AgentView - client.agents.list(\*\*params) -> SyncAgentsCursorIDPage[AgentView] +- client.agents.delete(id) -> object +- client.agents.devbox_counts() -> AgentDevboxCountsView +- client.agents.list_public(\*\*params) -> SyncAgentsCursorIDPage[AgentView] # Axons @@ -439,3 +447,31 @@ Methods: - client.mcp_configs.update(id, \*\*params) -> McpConfigView - client.mcp_configs.list(\*\*params) -> SyncMcpConfigsCursorIDPage[McpConfigView] - client.mcp_configs.delete(id) -> McpConfigView + +# Apikeys + +Types: + +```python +from runloop_api_client.types import APIKeyCreatedView, APIKeyCreateParameters +``` + +Methods: + +- client.apikeys.create(\*\*params) -> APIKeyCreatedView + +# RestrictedKeys + +Types: + +```python +from runloop_api_client.types import ( + RestrictedKeyCreatedView, + RestrictedKeyCreateParameters, + ScopeEntryView, +) +``` + +Methods: + +- client.restricted_keys.create(\*\*params) -> RestrictedKeyCreatedView diff --git a/pyproject.toml b/pyproject.toml index 2c3dbdb53..64ca9ee03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.18.1" +version = "1.19.0" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" diff --git a/src/runloop_api_client/_client.py b/src/runloop_api_client/_client.py index 28e1e6f94..61db3a474 100644 --- a/src/runloop_api_client/_client.py +++ b/src/runloop_api_client/_client.py @@ -34,6 +34,7 @@ from .resources import ( axons, agents, + apikeys, objects, secrets, devboxes, @@ -44,9 +45,11 @@ benchmark_jobs, benchmark_runs, gateway_configs, + restricted_keys, network_policies, ) from .resources.agents import AgentsResource, AsyncAgentsResource + from .resources.apikeys import ApikeysResource, AsyncApikeysResource from .resources.objects import ObjectsResource, AsyncObjectsResource from .resources.secrets import SecretsResource, AsyncSecretsResource from .resources.benchmarks import BenchmarksResource, AsyncBenchmarksResource @@ -56,6 +59,7 @@ from .resources.benchmark_jobs import BenchmarkJobsResource, AsyncBenchmarkJobsResource from .resources.benchmark_runs import BenchmarkRunsResource, AsyncBenchmarkRunsResource from .resources.gateway_configs import GatewayConfigsResource, AsyncGatewayConfigsResource + from .resources.restricted_keys import RestrictedKeysResource, AsyncRestrictedKeysResource from .resources.network_policies import NetworkPoliciesResource, AsyncNetworkPoliciesResource from .resources.devboxes.devboxes import DevboxesResource, AsyncDevboxesResource from .resources.scenarios.scenarios import ScenariosResource, AsyncScenariosResource @@ -198,6 +202,18 @@ def mcp_configs(self) -> McpConfigsResource: return McpConfigsResource(self) + @cached_property + def apikeys(self) -> ApikeysResource: + from .resources.apikeys import ApikeysResource + + return ApikeysResource(self) + + @cached_property + def restricted_keys(self) -> RestrictedKeysResource: + from .resources.restricted_keys import RestrictedKeysResource + + return RestrictedKeysResource(self) + @cached_property def with_raw_response(self) -> RunloopWithRawResponse: return RunloopWithRawResponse(self) @@ -446,6 +462,18 @@ def mcp_configs(self) -> AsyncMcpConfigsResource: return AsyncMcpConfigsResource(self) + @cached_property + def apikeys(self) -> AsyncApikeysResource: + from .resources.apikeys import AsyncApikeysResource + + return AsyncApikeysResource(self) + + @cached_property + def restricted_keys(self) -> AsyncRestrictedKeysResource: + from .resources.restricted_keys import AsyncRestrictedKeysResource + + return AsyncRestrictedKeysResource(self) + @cached_property def with_raw_response(self) -> AsyncRunloopWithRawResponse: return AsyncRunloopWithRawResponse(self) @@ -643,6 +671,18 @@ def mcp_configs(self) -> mcp_configs.McpConfigsResourceWithRawResponse: return McpConfigsResourceWithRawResponse(self._client.mcp_configs) + @cached_property + def apikeys(self) -> apikeys.ApikeysResourceWithRawResponse: + from .resources.apikeys import ApikeysResourceWithRawResponse + + return ApikeysResourceWithRawResponse(self._client.apikeys) + + @cached_property + def restricted_keys(self) -> restricted_keys.RestrictedKeysResourceWithRawResponse: + from .resources.restricted_keys import RestrictedKeysResourceWithRawResponse + + return RestrictedKeysResourceWithRawResponse(self._client.restricted_keys) + class AsyncRunloopWithRawResponse: _client: AsyncRunloop @@ -728,6 +768,18 @@ def mcp_configs(self) -> mcp_configs.AsyncMcpConfigsResourceWithRawResponse: return AsyncMcpConfigsResourceWithRawResponse(self._client.mcp_configs) + @cached_property + def apikeys(self) -> apikeys.AsyncApikeysResourceWithRawResponse: + from .resources.apikeys import AsyncApikeysResourceWithRawResponse + + return AsyncApikeysResourceWithRawResponse(self._client.apikeys) + + @cached_property + def restricted_keys(self) -> restricted_keys.AsyncRestrictedKeysResourceWithRawResponse: + from .resources.restricted_keys import AsyncRestrictedKeysResourceWithRawResponse + + return AsyncRestrictedKeysResourceWithRawResponse(self._client.restricted_keys) + class RunloopWithStreamedResponse: _client: Runloop @@ -813,6 +865,18 @@ def mcp_configs(self) -> mcp_configs.McpConfigsResourceWithStreamingResponse: return McpConfigsResourceWithStreamingResponse(self._client.mcp_configs) + @cached_property + def apikeys(self) -> apikeys.ApikeysResourceWithStreamingResponse: + from .resources.apikeys import ApikeysResourceWithStreamingResponse + + return ApikeysResourceWithStreamingResponse(self._client.apikeys) + + @cached_property + def restricted_keys(self) -> restricted_keys.RestrictedKeysResourceWithStreamingResponse: + from .resources.restricted_keys import RestrictedKeysResourceWithStreamingResponse + + return RestrictedKeysResourceWithStreamingResponse(self._client.restricted_keys) + class AsyncRunloopWithStreamedResponse: _client: AsyncRunloop @@ -898,6 +962,18 @@ def mcp_configs(self) -> mcp_configs.AsyncMcpConfigsResourceWithStreamingRespons return AsyncMcpConfigsResourceWithStreamingResponse(self._client.mcp_configs) + @cached_property + def apikeys(self) -> apikeys.AsyncApikeysResourceWithStreamingResponse: + from .resources.apikeys import AsyncApikeysResourceWithStreamingResponse + + return AsyncApikeysResourceWithStreamingResponse(self._client.apikeys) + + @cached_property + def restricted_keys(self) -> restricted_keys.AsyncRestrictedKeysResourceWithStreamingResponse: + from .resources.restricted_keys import AsyncRestrictedKeysResourceWithStreamingResponse + + return AsyncRestrictedKeysResourceWithStreamingResponse(self._client.restricted_keys) + Client = Runloop diff --git a/src/runloop_api_client/_utils/_utils.py b/src/runloop_api_client/_utils/_utils.py index eec7f4a1f..63b8cd602 100644 --- a/src/runloop_api_client/_utils/_utils.py +++ b/src/runloop_api_client/_utils/_utils.py @@ -86,8 +86,9 @@ def _extract_items( index += 1 if is_dict(obj): try: - # We are at the last entry in the path so we must remove the field - if (len(path)) == index: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): item = obj.pop(key) else: item = obj[key] diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index 4ca4fa13d..63342c137 100644 --- a/src/runloop_api_client/_version.py +++ b/src/runloop_api_client/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "runloop_api_client" -__version__ = "1.18.1" # x-release-please-version +__version__ = "1.19.0" # x-release-please-version diff --git a/src/runloop_api_client/resources/__init__.py b/src/runloop_api_client/resources/__init__.py index 292f6d666..dd27ba9e4 100644 --- a/src/runloop_api_client/resources/__init__.py +++ b/src/runloop_api_client/resources/__init__.py @@ -16,6 +16,14 @@ AgentsResourceWithStreamingResponse, AsyncAgentsResourceWithStreamingResponse, ) +from .apikeys import ( + ApikeysResource, + AsyncApikeysResource, + ApikeysResourceWithRawResponse, + AsyncApikeysResourceWithRawResponse, + ApikeysResourceWithStreamingResponse, + AsyncApikeysResourceWithStreamingResponse, +) from .objects import ( ObjectsResource, AsyncObjectsResource, @@ -96,6 +104,14 @@ GatewayConfigsResourceWithStreamingResponse, AsyncGatewayConfigsResourceWithStreamingResponse, ) +from .restricted_keys import ( + RestrictedKeysResource, + AsyncRestrictedKeysResource, + RestrictedKeysResourceWithRawResponse, + AsyncRestrictedKeysResourceWithRawResponse, + RestrictedKeysResourceWithStreamingResponse, + AsyncRestrictedKeysResourceWithStreamingResponse, +) from .network_policies import ( NetworkPoliciesResource, AsyncNetworkPoliciesResource, @@ -184,4 +200,16 @@ "AsyncMcpConfigsResourceWithRawResponse", "McpConfigsResourceWithStreamingResponse", "AsyncMcpConfigsResourceWithStreamingResponse", + "ApikeysResource", + "AsyncApikeysResource", + "ApikeysResourceWithRawResponse", + "AsyncApikeysResourceWithRawResponse", + "ApikeysResourceWithStreamingResponse", + "AsyncApikeysResourceWithStreamingResponse", + "RestrictedKeysResource", + "AsyncRestrictedKeysResource", + "RestrictedKeysResourceWithRawResponse", + "AsyncRestrictedKeysResourceWithRawResponse", + "RestrictedKeysResourceWithStreamingResponse", + "AsyncRestrictedKeysResourceWithStreamingResponse", ] diff --git a/src/runloop_api_client/resources/agents.py b/src/runloop_api_client/resources/agents.py index 082d5725c..6febe22f0 100644 --- a/src/runloop_api_client/resources/agents.py +++ b/src/runloop_api_client/resources/agents.py @@ -6,7 +6,7 @@ import httpx -from ..types import agent_list_params, agent_create_params +from ..types import agent_list_params, agent_create_params, agent_list_public_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property @@ -20,6 +20,7 @@ from ..pagination import SyncAgentsCursorIDPage, AsyncAgentsCursorIDPage from .._base_client import AsyncPaginator, make_request_options from ..types.agent_view import AgentView +from ..types.agent_devbox_counts_view import AgentDevboxCountsView from ..types.shared_params.agent_source import AgentSource __all__ = ["AgentsResource", "AsyncAgentsResource"] @@ -202,6 +203,135 @@ def list( model=AgentView, ) + def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> object: + """Delete an Agent by its unique identifier. + + The Agent will be permanently removed. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/v1/agents/{id}/delete", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=object, + ) + + def devbox_counts( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AgentDevboxCountsView: + """Returns devbox counts grouped by agent name. + + This endpoint efficiently + aggregates devbox counts for all agents in a single request, avoiding N+1 query + patterns. + """ + return self._get( + "/v1/agents/devbox_counts", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentDevboxCountsView, + ) + + def list_public( + self, + *, + include_total_count: bool | Omit = omit, + limit: int | Omit = omit, + name: str | Omit = omit, + search: str | Omit = omit, + starting_after: str | Omit = omit, + version: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> SyncAgentsCursorIDPage[AgentView]: + """ + List all public Agents with pagination support. + + Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + + limit: The limit of items to return. Default is 20. Max is 5000. + + name: Filter agents by name (partial match supported). + + search: Search by agent ID or name. + + starting_after: Load the next page of data starting after the item with the given ID. + + version: Filter by version. Use 'latest' to get the most recently created agent. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/agents/list_public", + page=SyncAgentsCursorIDPage[AgentView], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include_total_count": include_total_count, + "limit": limit, + "name": name, + "search": search, + "starting_after": starting_after, + "version": version, + }, + agent_list_public_params.AgentListPublicParams, + ), + ), + model=AgentView, + ) + class AsyncAgentsResource(AsyncAPIResource): @cached_property @@ -380,6 +510,135 @@ def list( model=AgentView, ) + async def delete( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> object: + """Delete an Agent by its unique identifier. + + The Agent will be permanently removed. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/v1/agents/{id}/delete", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=object, + ) + + async def devbox_counts( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AgentDevboxCountsView: + """Returns devbox counts grouped by agent name. + + This endpoint efficiently + aggregates devbox counts for all agents in a single request, avoiding N+1 query + patterns. + """ + return await self._get( + "/v1/agents/devbox_counts", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AgentDevboxCountsView, + ) + + def list_public( + self, + *, + include_total_count: bool | Omit = omit, + limit: int | Omit = omit, + name: str | Omit = omit, + search: str | Omit = omit, + starting_after: str | Omit = omit, + version: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AsyncPaginator[AgentView, AsyncAgentsCursorIDPage[AgentView]]: + """ + List all public Agents with pagination support. + + Args: + include_total_count: If true (default), includes total_count in the response. Set to false to skip + the count query for better performance on large datasets. + + limit: The limit of items to return. Default is 20. Max is 5000. + + name: Filter agents by name (partial match supported). + + search: Search by agent ID or name. + + starting_after: Load the next page of data starting after the item with the given ID. + + version: Filter by version. Use 'latest' to get the most recently created agent. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/agents/list_public", + page=AsyncAgentsCursorIDPage[AgentView], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "include_total_count": include_total_count, + "limit": limit, + "name": name, + "search": search, + "starting_after": starting_after, + "version": version, + }, + agent_list_public_params.AgentListPublicParams, + ), + ), + model=AgentView, + ) + class AgentsResourceWithRawResponse: def __init__(self, agents: AgentsResource) -> None: @@ -394,6 +653,15 @@ def __init__(self, agents: AgentsResource) -> None: self.list = to_raw_response_wrapper( agents.list, ) + self.delete = to_raw_response_wrapper( + agents.delete, + ) + self.devbox_counts = to_raw_response_wrapper( + agents.devbox_counts, + ) + self.list_public = to_raw_response_wrapper( + agents.list_public, + ) class AsyncAgentsResourceWithRawResponse: @@ -409,6 +677,15 @@ def __init__(self, agents: AsyncAgentsResource) -> None: self.list = async_to_raw_response_wrapper( agents.list, ) + self.delete = async_to_raw_response_wrapper( + agents.delete, + ) + self.devbox_counts = async_to_raw_response_wrapper( + agents.devbox_counts, + ) + self.list_public = async_to_raw_response_wrapper( + agents.list_public, + ) class AgentsResourceWithStreamingResponse: @@ -424,6 +701,15 @@ def __init__(self, agents: AgentsResource) -> None: self.list = to_streamed_response_wrapper( agents.list, ) + self.delete = to_streamed_response_wrapper( + agents.delete, + ) + self.devbox_counts = to_streamed_response_wrapper( + agents.devbox_counts, + ) + self.list_public = to_streamed_response_wrapper( + agents.list_public, + ) class AsyncAgentsResourceWithStreamingResponse: @@ -439,3 +725,12 @@ def __init__(self, agents: AsyncAgentsResource) -> None: self.list = async_to_streamed_response_wrapper( agents.list, ) + self.delete = async_to_streamed_response_wrapper( + agents.delete, + ) + self.devbox_counts = async_to_streamed_response_wrapper( + agents.devbox_counts, + ) + self.list_public = async_to_streamed_response_wrapper( + agents.list_public, + ) diff --git a/src/runloop_api_client/resources/apikeys.py b/src/runloop_api_client/resources/apikeys.py new file mode 100644 index 000000000..fcc565ac0 --- /dev/null +++ b/src/runloop_api_client/resources/apikeys.py @@ -0,0 +1,197 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ..types import apikey_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.api_key_created_view import APIKeyCreatedView + +__all__ = ["ApikeysResource", "AsyncApikeysResource"] + + +class ApikeysResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ApikeysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers + """ + return ApikeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ApikeysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response + """ + return ApikeysResourceWithStreamingResponse(self) + + def create( + self, + *, + expires_at_ms: Optional[int] | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> APIKeyCreatedView: + """Create a new API key for the authenticated account. + + Use a standard API key (ak*) + or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write scope. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return self._post( + "/v1/apikeys", + body=maybe_transform( + { + "expires_at_ms": expires_at_ms, + "name": name, + }, + apikey_create_params.ApikeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=APIKeyCreatedView, + ) + + +class AsyncApikeysResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncApikeysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers + """ + return AsyncApikeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncApikeysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response + """ + return AsyncApikeysResourceWithStreamingResponse(self) + + async def create( + self, + *, + expires_at_ms: Optional[int] | Omit = omit, + name: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> APIKeyCreatedView: + """Create a new API key for the authenticated account. + + Use a standard API key (ak*) + or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write scope. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/v1/apikeys", + body=await async_maybe_transform( + { + "expires_at_ms": expires_at_ms, + "name": name, + }, + apikey_create_params.ApikeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=APIKeyCreatedView, + ) + + +class ApikeysResourceWithRawResponse: + def __init__(self, apikeys: ApikeysResource) -> None: + self._apikeys = apikeys + + self.create = to_raw_response_wrapper( + apikeys.create, + ) + + +class AsyncApikeysResourceWithRawResponse: + def __init__(self, apikeys: AsyncApikeysResource) -> None: + self._apikeys = apikeys + + self.create = async_to_raw_response_wrapper( + apikeys.create, + ) + + +class ApikeysResourceWithStreamingResponse: + def __init__(self, apikeys: ApikeysResource) -> None: + self._apikeys = apikeys + + self.create = to_streamed_response_wrapper( + apikeys.create, + ) + + +class AsyncApikeysResourceWithStreamingResponse: + def __init__(self, apikeys: AsyncApikeysResource) -> None: + self._apikeys = apikeys + + self.create = async_to_streamed_response_wrapper( + apikeys.create, + ) diff --git a/src/runloop_api_client/resources/restricted_keys.py b/src/runloop_api_client/resources/restricted_keys.py new file mode 100644 index 000000000..6e71dfedb --- /dev/null +++ b/src/runloop_api_client/resources/restricted_keys.py @@ -0,0 +1,202 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional + +import httpx + +from ..types import restricted_key_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.scope_entry_view_param import ScopeEntryViewParam +from ..types.restricted_key_created_view import RestrictedKeyCreatedView + +__all__ = ["RestrictedKeysResource", "AsyncRestrictedKeysResource"] + + +class RestrictedKeysResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> RestrictedKeysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers + """ + return RestrictedKeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> RestrictedKeysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response + """ + return RestrictedKeysResourceWithStreamingResponse(self) + + def create( + self, + *, + expires_at_ms: Optional[int] | Omit = omit, + name: str | Omit = omit, + scopes: Iterable[ScopeEntryViewParam] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RestrictedKeyCreatedView: + """Create a restricted API key with specific resource scopes. + + Use a standard API + key (ak*) or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write scope. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return self._post( + "/v1/restricted_keys", + body=maybe_transform( + { + "expires_at_ms": expires_at_ms, + "name": name, + "scopes": scopes, + }, + restricted_key_create_params.RestrictedKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=RestrictedKeyCreatedView, + ) + + +class AsyncRestrictedKeysResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncRestrictedKeysResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/runloopai/api-client-python#accessing-raw-response-data-eg-headers + """ + return AsyncRestrictedKeysResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncRestrictedKeysResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/runloopai/api-client-python#with_streaming_response + """ + return AsyncRestrictedKeysResourceWithStreamingResponse(self) + + async def create( + self, + *, + expires_at_ms: Optional[int] | Omit = omit, + name: str | Omit = omit, + scopes: Iterable[ScopeEntryViewParam] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> RestrictedKeyCreatedView: + """Create a restricted API key with specific resource scopes. + + Use a standard API + key (ak*) or a restricted key (rk*) with RESOURCE_TYPE_ACCOUNT write scope. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/v1/restricted_keys", + body=await async_maybe_transform( + { + "expires_at_ms": expires_at_ms, + "name": name, + "scopes": scopes, + }, + restricted_key_create_params.RestrictedKeyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=RestrictedKeyCreatedView, + ) + + +class RestrictedKeysResourceWithRawResponse: + def __init__(self, restricted_keys: RestrictedKeysResource) -> None: + self._restricted_keys = restricted_keys + + self.create = to_raw_response_wrapper( + restricted_keys.create, + ) + + +class AsyncRestrictedKeysResourceWithRawResponse: + def __init__(self, restricted_keys: AsyncRestrictedKeysResource) -> None: + self._restricted_keys = restricted_keys + + self.create = async_to_raw_response_wrapper( + restricted_keys.create, + ) + + +class RestrictedKeysResourceWithStreamingResponse: + def __init__(self, restricted_keys: RestrictedKeysResource) -> None: + self._restricted_keys = restricted_keys + + self.create = to_streamed_response_wrapper( + restricted_keys.create, + ) + + +class AsyncRestrictedKeysResourceWithStreamingResponse: + def __init__(self, restricted_keys: AsyncRestrictedKeysResource) -> None: + self._restricted_keys = restricted_keys + + self.create = async_to_streamed_response_wrapper( + restricted_keys.create, + ) diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index da2236119..01130880d 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -30,6 +30,7 @@ from .axon_list_params import AxonListParams as AxonListParams from .devbox_list_view import DevboxListView as DevboxListView from .object_list_view import ObjectListView as ObjectListView +from .scope_entry_view import ScopeEntryView as ScopeEntryView from .scoring_contract import ScoringContract as ScoringContract from .scoring_function import ScoringFunction as ScoringFunction from .secret_list_view import SecretListView as SecretListView @@ -49,6 +50,8 @@ from .input_context_param import InputContextParam as InputContextParam from .network_policy_view import NetworkPolicyView as NetworkPolicyView from .publish_result_view import PublishResultView as PublishResultView +from .api_key_created_view import APIKeyCreatedView as APIKeyCreatedView +from .apikey_create_params import ApikeyCreateParams as ApikeyCreateParams from .devbox_create_params import DevboxCreateParams as DevboxCreateParams from .devbox_snapshot_view import DevboxSnapshotView as DevboxSnapshotView from .devbox_update_params import DevboxUpdateParams as DevboxUpdateParams @@ -68,6 +71,7 @@ from .scenario_create_params import ScenarioCreateParams as ScenarioCreateParams from .scenario_run_list_view import ScenarioRunListView as ScenarioRunListView from .scenario_update_params import ScenarioUpdateParams as ScenarioUpdateParams +from .scope_entry_view_param import ScopeEntryViewParam as ScopeEntryViewParam from .scoring_contract_param import ScoringContractParam as ScoringContractParam from .scoring_function_param import ScoringFunctionParam as ScoringFunctionParam from .benchmark_create_params import BenchmarkCreateParams as BenchmarkCreateParams @@ -76,6 +80,8 @@ from .benchmark_update_params import BenchmarkUpdateParams as BenchmarkUpdateParams from .blueprint_create_params import BlueprintCreateParams as BlueprintCreateParams from .inspection_source_param import InspectionSourceParam as InspectionSourceParam +from .agent_devbox_counts_view import AgentDevboxCountsView as AgentDevboxCountsView +from .agent_list_public_params import AgentListPublicParams as AgentListPublicParams from .blueprint_preview_params import BlueprintPreviewParams as BlueprintPreviewParams from .gateway_config_list_view import GatewayConfigListView as GatewayConfigListView from .mcp_config_create_params import McpConfigCreateParams as McpConfigCreateParams @@ -103,6 +109,7 @@ from .devbox_enable_tunnel_params import DevboxEnableTunnelParams as DevboxEnableTunnelParams from .devbox_execute_async_params import DevboxExecuteAsyncParams as DevboxExecuteAsyncParams from .devbox_snapshot_disk_params import DevboxSnapshotDiskParams as DevboxSnapshotDiskParams +from .restricted_key_created_view import RestrictedKeyCreatedView as RestrictedKeyCreatedView from .scenario_list_public_params import ScenarioListPublicParams as ScenarioListPublicParams from .benchmark_definitions_params import BenchmarkDefinitionsParams as BenchmarkDefinitionsParams from .benchmark_list_public_params import BenchmarkListPublicParams as BenchmarkListPublicParams @@ -112,6 +119,7 @@ from .gateway_config_update_params import GatewayConfigUpdateParams as GatewayConfigUpdateParams from .network_policy_create_params import NetworkPolicyCreateParams as NetworkPolicyCreateParams from .network_policy_update_params import NetworkPolicyUpdateParams as NetworkPolicyUpdateParams +from .restricted_key_create_params import RestrictedKeyCreateParams as RestrictedKeyCreateParams from .scoring_contract_result_view import ScoringContractResultView as ScoringContractResultView from .scoring_function_result_view import ScoringFunctionResultView as ScoringFunctionResultView from .scenario_definition_list_view import ScenarioDefinitionListView as ScenarioDefinitionListView diff --git a/src/runloop_api_client/types/agent_devbox_counts_view.py b/src/runloop_api_client/types/agent_devbox_counts_view.py new file mode 100644 index 000000000..af9006d1e --- /dev/null +++ b/src/runloop_api_client/types/agent_devbox_counts_view.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict + +from .._models import BaseModel + +__all__ = ["AgentDevboxCountsView"] + + +class AgentDevboxCountsView(BaseModel): + """Devbox counts grouped by agent name. + + Used to efficiently fetch devbox counts for multiple agents in a single request. + """ + + counts: Dict[str, int] + """Map of agent name to devbox count. + + Each key is an agent name, and the value is the count of devboxes associated + with that agent. + """ + + total_count: int + """Total count of devboxes across all agents in the result.""" diff --git a/src/runloop_api_client/types/agent_list_public_params.py b/src/runloop_api_client/types/agent_list_public_params.py new file mode 100644 index 000000000..a0e53d503 --- /dev/null +++ b/src/runloop_api_client/types/agent_list_public_params.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["AgentListPublicParams"] + + +class AgentListPublicParams(TypedDict, total=False): + include_total_count: bool + """If true (default), includes total_count in the response. + + Set to false to skip the count query for better performance on large datasets. + """ + + limit: int + """The limit of items to return. Default is 20. Max is 5000.""" + + name: str + """Filter agents by name (partial match supported).""" + + search: str + """Search by agent ID or name.""" + + starting_after: str + """Load the next page of data starting after the item with the given ID.""" + + version: str + """Filter by version. Use 'latest' to get the most recently created agent.""" diff --git a/src/runloop_api_client/types/api_key_created_view.py b/src/runloop_api_client/types/api_key_created_view.py new file mode 100644 index 000000000..f48902c64 --- /dev/null +++ b/src/runloop_api_client/types/api_key_created_view.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["APIKeyCreatedView"] + + +class APIKeyCreatedView(BaseModel): + id: Optional[str] = None + + expires_at_ms: Optional[int] = None + + key_secret: Optional[str] = None + + name: Optional[str] = None diff --git a/src/runloop_api_client/types/apikey_create_params.py b/src/runloop_api_client/types/apikey_create_params.py new file mode 100644 index 000000000..106427199 --- /dev/null +++ b/src/runloop_api_client/types/apikey_create_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["ApikeyCreateParams"] + + +class ApikeyCreateParams(TypedDict, total=False): + expires_at_ms: Optional[int] + + name: str diff --git a/src/runloop_api_client/types/restricted_key_create_params.py b/src/runloop_api_client/types/restricted_key_create_params.py new file mode 100644 index 000000000..420e65a4e --- /dev/null +++ b/src/runloop_api_client/types/restricted_key_create_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import TypedDict + +from .scope_entry_view_param import ScopeEntryViewParam + +__all__ = ["RestrictedKeyCreateParams"] + + +class RestrictedKeyCreateParams(TypedDict, total=False): + expires_at_ms: Optional[int] + + name: str + + scopes: Iterable[ScopeEntryViewParam] diff --git a/src/runloop_api_client/types/restricted_key_created_view.py b/src/runloop_api_client/types/restricted_key_created_view.py new file mode 100644 index 000000000..dab3875db --- /dev/null +++ b/src/runloop_api_client/types/restricted_key_created_view.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel +from .scope_entry_view import ScopeEntryView + +__all__ = ["RestrictedKeyCreatedView"] + + +class RestrictedKeyCreatedView(BaseModel): + id: Optional[str] = None + + expires_at_ms: Optional[int] = None + + key_secret: Optional[str] = None + + name: Optional[str] = None + + scopes: Optional[List[ScopeEntryView]] = None diff --git a/src/runloop_api_client/types/scope_entry_view.py b/src/runloop_api_client/types/scope_entry_view.py new file mode 100644 index 000000000..1a0903895 --- /dev/null +++ b/src/runloop_api_client/types/scope_entry_view.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["ScopeEntryView"] + + +class ScopeEntryView(BaseModel): + access_level: Optional[Literal["ACCESS_LEVEL_NONE", "ACCESS_LEVEL_READ", "ACCESS_LEVEL_WRITE"]] = None + + resource_type: Optional[ + Literal[ + "RESOURCE_TYPE_DEVBOXES", + "RESOURCE_TYPE_BLUEPRINTS", + "RESOURCE_TYPE_SNAPSHOTS", + "RESOURCE_TYPE_BENCHMARKS", + "RESOURCE_TYPE_SCENARIOS", + "RESOURCE_TYPE_REPO_CONNECTIONS", + "RESOURCE_TYPE_AGENTS", + "RESOURCE_TYPE_OBJECTS", + "RESOURCE_TYPE_ACCOUNT", + ] + ] = None diff --git a/src/runloop_api_client/types/scope_entry_view_param.py b/src/runloop_api_client/types/scope_entry_view_param.py new file mode 100644 index 000000000..5379b838a --- /dev/null +++ b/src/runloop_api_client/types/scope_entry_view_param.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["ScopeEntryViewParam"] + + +class ScopeEntryViewParam(TypedDict, total=False): + access_level: Literal["ACCESS_LEVEL_NONE", "ACCESS_LEVEL_READ", "ACCESS_LEVEL_WRITE"] + + resource_type: Literal[ + "RESOURCE_TYPE_DEVBOXES", + "RESOURCE_TYPE_BLUEPRINTS", + "RESOURCE_TYPE_SNAPSHOTS", + "RESOURCE_TYPE_BENCHMARKS", + "RESOURCE_TYPE_SCENARIOS", + "RESOURCE_TYPE_REPO_CONNECTIONS", + "RESOURCE_TYPE_AGENTS", + "RESOURCE_TYPE_OBJECTS", + "RESOURCE_TYPE_ACCOUNT", + ] diff --git a/src/runloop_api_client/types/shared/launch_parameters.py b/src/runloop_api_client/types/shared/launch_parameters.py index a1b52ec03..6e74af438 100644 --- a/src/runloop_api_client/types/shared/launch_parameters.py +++ b/src/runloop_api_client/types/shared/launch_parameters.py @@ -12,6 +12,9 @@ class LifecycleResumeTriggers(BaseModel): """Triggers that can resume a suspended Devbox.""" + axon_event: Optional[bool] = None + """When true, axon events targeting a suspended Devbox will trigger a resume.""" + http: Optional[bool] = None """When true, HTTP traffic to a suspended Devbox via tunnel will trigger a resume.""" diff --git a/src/runloop_api_client/types/shared_params/launch_parameters.py b/src/runloop_api_client/types/shared_params/launch_parameters.py index 3906163bf..44ced4761 100644 --- a/src/runloop_api_client/types/shared_params/launch_parameters.py +++ b/src/runloop_api_client/types/shared_params/launch_parameters.py @@ -14,6 +14,9 @@ class LifecycleResumeTriggers(TypedDict, total=False): """Triggers that can resume a suspended Devbox.""" + axon_event: Optional[bool] + """When true, axon events targeting a suspended Devbox will trigger a resume.""" + http: Optional[bool] """When true, HTTP traffic to a suspended Devbox via tunnel will trigger a resume.""" diff --git a/tests/api_resources/test_agents.py b/tests/api_resources/test_agents.py index a6304d70d..fb602d148 100644 --- a/tests/api_resources/test_agents.py +++ b/tests/api_resources/test_agents.py @@ -9,7 +9,10 @@ from tests.utils import assert_matches_type from runloop_api_client import Runloop, AsyncRunloop -from runloop_api_client.types import AgentView +from runloop_api_client.types import ( + AgentView, + AgentDevboxCountsView, +) from runloop_api_client.pagination import SyncAgentsCursorIDPage, AsyncAgentsCursorIDPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -158,6 +161,106 @@ def test_streaming_response_list(self, client: Runloop) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_delete(self, client: Runloop) -> None: + agent = client.agents.delete( + "id", + ) + assert_matches_type(object, agent, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Runloop) -> None: + response = client.agents.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = response.parse() + assert_matches_type(object, agent, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Runloop) -> None: + with client.agents.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = response.parse() + assert_matches_type(object, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.agents.with_raw_response.delete( + "", + ) + + @parametrize + def test_method_devbox_counts(self, client: Runloop) -> None: + agent = client.agents.devbox_counts() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + @parametrize + def test_raw_response_devbox_counts(self, client: Runloop) -> None: + response = client.agents.with_raw_response.devbox_counts() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = response.parse() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + @parametrize + def test_streaming_response_devbox_counts(self, client: Runloop) -> None: + with client.agents.with_streaming_response.devbox_counts() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = response.parse() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_list_public(self, client: Runloop) -> None: + agent = client.agents.list_public() + assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + def test_method_list_public_with_all_params(self, client: Runloop) -> None: + agent = client.agents.list_public( + include_total_count=True, + limit=0, + name="name", + search="search", + starting_after="starting_after", + version="version", + ) + assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + def test_raw_response_list_public(self, client: Runloop) -> None: + response = client.agents.with_raw_response.list_public() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = response.parse() + assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + def test_streaming_response_list_public(self, client: Runloop) -> None: + with client.agents.with_streaming_response.list_public() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = response.parse() + assert_matches_type(SyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncAgents: parametrize = pytest.mark.parametrize( @@ -303,3 +406,103 @@ async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_delete(self, async_client: AsyncRunloop) -> None: + agent = await async_client.agents.delete( + "id", + ) + assert_matches_type(object, agent, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncRunloop) -> None: + response = await async_client.agents.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = await response.parse() + assert_matches_type(object, agent, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncRunloop) -> None: + async with async_client.agents.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = await response.parse() + assert_matches_type(object, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.agents.with_raw_response.delete( + "", + ) + + @parametrize + async def test_method_devbox_counts(self, async_client: AsyncRunloop) -> None: + agent = await async_client.agents.devbox_counts() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + @parametrize + async def test_raw_response_devbox_counts(self, async_client: AsyncRunloop) -> None: + response = await async_client.agents.with_raw_response.devbox_counts() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = await response.parse() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + @parametrize + async def test_streaming_response_devbox_counts(self, async_client: AsyncRunloop) -> None: + async with async_client.agents.with_streaming_response.devbox_counts() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = await response.parse() + assert_matches_type(AgentDevboxCountsView, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_list_public(self, async_client: AsyncRunloop) -> None: + agent = await async_client.agents.list_public() + assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + async def test_method_list_public_with_all_params(self, async_client: AsyncRunloop) -> None: + agent = await async_client.agents.list_public( + include_total_count=True, + limit=0, + name="name", + search="search", + starting_after="starting_after", + version="version", + ) + assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + async def test_raw_response_list_public(self, async_client: AsyncRunloop) -> None: + response = await async_client.agents.with_raw_response.list_public() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = await response.parse() + assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + @parametrize + async def test_streaming_response_list_public(self, async_client: AsyncRunloop) -> None: + async with async_client.agents.with_streaming_response.list_public() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = await response.parse() + assert_matches_type(AsyncAgentsCursorIDPage[AgentView], agent, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_apikeys.py b/tests/api_resources/test_apikeys.py new file mode 100644 index 000000000..e96ff6111 --- /dev/null +++ b/tests/api_resources/test_apikeys.py @@ -0,0 +1,90 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from tests.utils import assert_matches_type +from runloop_api_client import Runloop, AsyncRunloop +from runloop_api_client.types import APIKeyCreatedView + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestApikeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Runloop) -> None: + apikey = client.apikeys.create() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Runloop) -> None: + apikey = client.apikeys.create( + expires_at_ms=0, + name="name", + ) + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Runloop) -> None: + response = client.apikeys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apikey = response.parse() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Runloop) -> None: + with client.apikeys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apikey = response.parse() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncApikeys: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncRunloop) -> None: + apikey = await async_client.apikeys.create() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None: + apikey = await async_client.apikeys.create( + expires_at_ms=0, + name="name", + ) + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: + response = await async_client.apikeys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + apikey = await response.parse() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None: + async with async_client.apikeys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + apikey = await response.parse() + assert_matches_type(APIKeyCreatedView, apikey, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_benchmarks.py b/tests/api_resources/test_benchmarks.py index 924deb133..7612b34f2 100644 --- a/tests/api_resources/test_benchmarks.py +++ b/tests/api_resources/test_benchmarks.py @@ -304,7 +304,10 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -686,7 +689,10 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], diff --git a/tests/api_resources/test_blueprints.py b/tests/api_resources/test_blueprints.py index 89afc9e4b..dd3e19fcb 100644 --- a/tests/api_resources/test_blueprints.py +++ b/tests/api_resources/test_blueprints.py @@ -70,7 +70,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -292,7 +295,10 @@ def test_method_create_from_inspection_with_all_params(self, client: Runloop) -> "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -458,7 +464,10 @@ def test_method_preview_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -567,7 +576,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -789,7 +801,10 @@ async def test_method_create_from_inspection_with_all_params(self, async_client: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -955,7 +970,10 @@ async def test_method_preview_with_all_params(self, async_client: AsyncRunloop) "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index ec948f719..920ff437a 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -90,7 +90,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -1714,7 +1717,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], diff --git a/tests/api_resources/test_restricted_keys.py b/tests/api_resources/test_restricted_keys.py new file mode 100644 index 000000000..5e2bc4fed --- /dev/null +++ b/tests/api_resources/test_restricted_keys.py @@ -0,0 +1,102 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from tests.utils import assert_matches_type +from runloop_api_client import Runloop, AsyncRunloop +from runloop_api_client.types import RestrictedKeyCreatedView + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestRestrictedKeys: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_create(self, client: Runloop) -> None: + restricted_key = client.restricted_keys.create() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + def test_method_create_with_all_params(self, client: Runloop) -> None: + restricted_key = client.restricted_keys.create( + expires_at_ms=0, + name="name", + scopes=[ + { + "access_level": "ACCESS_LEVEL_NONE", + "resource_type": "RESOURCE_TYPE_DEVBOXES", + } + ], + ) + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + def test_raw_response_create(self, client: Runloop) -> None: + response = client.restricted_keys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + restricted_key = response.parse() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + def test_streaming_response_create(self, client: Runloop) -> None: + with client.restricted_keys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + restricted_key = response.parse() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncRestrictedKeys: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_create(self, async_client: AsyncRunloop) -> None: + restricted_key = await async_client.restricted_keys.create() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncRunloop) -> None: + restricted_key = await async_client.restricted_keys.create( + expires_at_ms=0, + name="name", + scopes=[ + { + "access_level": "ACCESS_LEVEL_NONE", + "resource_type": "RESOURCE_TYPE_DEVBOXES", + } + ], + ) + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + async def test_raw_response_create(self, async_client: AsyncRunloop) -> None: + response = await async_client.restricted_keys.with_raw_response.create() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + restricted_key = await response.parse() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + @parametrize + async def test_streaming_response_create(self, async_client: AsyncRunloop) -> None: + async with async_client.restricted_keys.with_streaming_response.create() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + restricted_key = await response.parse() + assert_matches_type(RestrictedKeyCreatedView, restricted_key, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py index 748d153c8..9dd8c3e63 100644 --- a/tests/api_resources/test_scenarios.py +++ b/tests/api_resources/test_scenarios.py @@ -83,7 +83,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -225,7 +228,10 @@ def test_method_update_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -440,7 +446,10 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -555,7 +564,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -697,7 +709,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], @@ -912,7 +927,10 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "idle_time_seconds": 0, "on_idle": "shutdown", }, - "resume_triggers": {"http": True}, + "resume_triggers": { + "axon_event": True, + "http": True, + }, }, "network_policy_id": "network_policy_id", "required_services": ["string"], diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index 41498b836..a76b07d19 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -35,6 +35,15 @@ def test_multiple_files() -> None: assert query == {"documents": [{}, {}]} +def test_top_level_file_array() -> None: + query = {"files": [b"file one", b"file two"], "title": "hello"} + assert extract_files(query, paths=[["files", ""]]) == [ + ("files[]", b"file one"), + ("files[]", b"file two"), + ] + assert query == {"title": "hello"} + + @pytest.mark.parametrize( "query,paths,expected", [