diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10b9a3a85..3db832c9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/runloop-python' && 'depot-ubuntu-24.04' || 'ubuntu-slim' }} if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') steps: - - uses: actions/checkout@v6 + - uses: runloopai/checkout@main - name: Install uv uses: runloopai/setup-uv@main @@ -46,7 +46,7 @@ jobs: id-token: write runs-on: ${{ github.repository == 'stainless-sdks/runloop-python' && 'depot-ubuntu-24.04' || 'ubuntu-slim' }} steps: - - uses: actions/checkout@v6 + - uses: runloopai/checkout@main - name: Install uv uses: runloopai/setup-uv@main @@ -64,7 +64,7 @@ jobs: github.repository == 'stainless-sdks/runloop-python' && !startsWith(github.ref, 'refs/heads/stl/') id: github-oidc - uses: actions/github-script@v8 + uses: runloopai/github-script@main with: script: core.setOutput('github_token', await core.getIDToken()); @@ -84,7 +84,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/runloop-python' && 'depot-ubuntu-24.04' || 'ubuntu-slim' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v6 + - uses: runloopai/checkout@main - name: Install uv uses: runloopai/setup-uv@main diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 7ec40f7d4..7f148e54b 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-slim steps: - - uses: actions/checkout@v6 + - uses: runloopai/checkout@main - name: Install uv uses: runloopai/setup-uv@main diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 364178039..4ec1877db 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'runloopai/api-client-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v6 + - uses: runloopai/checkout@main - name: Check release environment run: | diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ba231b076..397c4203e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.21.0" + ".": "1.22.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 75a9a5bb0..fe2d09b74 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 119 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-6ec03cfc156036a0758aebc523b469f3007de431312c536c434efe795e1892e7.yml -openapi_spec_hash: 092761a0209e0950cfd8f262a981adb1 -config_hash: 06faf3176c48bf2b258730ce50809f0f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai/runloop-2c6498f804f6a88977521c2b4b8700d8992235a01ffc965193d87acf8021683b.yml +openapi_spec_hash: cf32b83c81c8aa76f03dda8123b228b5 +config_hash: 444e00951b440bf92e7548b2807584a4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 07431324c..dc89e8046 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 1.22.0 (2026-05-20) + +Full Changelog: [v1.21.0...v1.22.0](https://github.com/runloopai/api-client-python/compare/v1.21.0...v1.22.0) + +### Features + +* add reflex initiator type, hidden param ([#9350](https://github.com/runloopai/api-client-python/issues/9350)) ([5922abf](https://github.com/runloopai/api-client-python/commit/5922abfd135589b229cb64ea23750d34fede857f)) +* **api:** expose lifecycle_hooks on LaunchParameters lifecycle ([#9115](https://github.com/runloopai/api-client-python/issues/9115)) ([c9c7c37](https://github.com/runloopai/api-client-python/commit/c9c7c37c2a38f91142b0b28d1e5cbc183b8ee53e)) + + +### Bug Fixes + +* **mux:** strip internal stub note from PTY OpenAPI descriptions ([#9315](https://github.com/runloopai/api-client-python/issues/9315)) ([214629e](https://github.com/runloopai/api-client-python/commit/214629e9281c38ff75b5769e13b4b977a346bb04)) + + +### Chores + +* Update stainless.yml, bump AGENTS.md to keep this updated ([#9268](https://github.com/runloopai/api-client-python/issues/9268)) ([5d86ef5](https://github.com/runloopai/api-client-python/commit/5d86ef5240920ba4b6de9e59456aea3c0971e3ef)) + ## 1.21.0 (2026-05-13) Full Changelog: [v1.20.3...v1.21.0](https://github.com/runloopai/api-client-python/compare/v1.20.3...v1.21.0) diff --git a/api.md b/api.md index 2f48b034e..30535e6f1 100644 --- a/api.md +++ b/api.md @@ -8,8 +8,11 @@ from runloop_api_client.types import ( BrokerMount, CodeMountParameters, LaunchParameters, + LifecycleConfiguration, + LifecycleHooks, Mount, ObjectMount, + ResumeTriggers, RunProfile, ) ``` diff --git a/pyproject.toml b/pyproject.toml index c1f57fbd6..1b48f1988 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.21.0" +version = "1.22.0" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index e94914292..27a4b2948 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.21.0" # x-release-please-version +__version__ = "1.22.0" # x-release-please-version diff --git a/src/runloop_api_client/resources/pty.py b/src/runloop_api_client/resources/pty.py index dc1a837b5..09158e0cc 100644 --- a/src/runloop_api_client/resources/pty.py +++ b/src/runloop_api_client/resources/pty.py @@ -68,22 +68,18 @@ def connect( effect. The response returns a PtyConnectView containing connect_url (a server-relative path to the WebSocket data plane), idle_ttl_seconds (how long this session is retained after the last client disconnects), and the resulting - cols/rows. The interactive byte stream itself is intentionally not modeled in - OpenAPI; see the controller-level documentation for the WebSocket close-code - conventions. The single-attach contract is enforced when a client opens the - WebSocket data plane, not on this bootstrap call: bootstrap always succeeds for - a valid session_name, even if another client is currently attached. Rejection of - a second concurrent attach happens at WebSocket upgrade time. If the active - client disconnects, the session is preserved for the idle TTL so a later connect - using the same session_name resumes the same shell. After the TTL expires, after - an explicit close control action, or after the underlying Devbox lifecycle - replaces the PTY process (such as through suspend/resume), a later request with - the same session_name creates a fresh PTY session without the previous shell - state. - - Documentation note: this operation is published from mux strictly as an OpenAPI - contract stub for the PTY service control plane. It is not evidence that mux - itself serves the interactive PTY transport. + cols/rows. The interactive terminal byte stream is exchanged over the WebSocket + data plane and is not modeled in this OpenAPI contract; clients should connect + to connect_url and exchange raw binary frames for terminal I/O. The + single-attach contract is enforced when a client opens the WebSocket data plane, + not on this bootstrap call: bootstrap always succeeds for a valid session_name, + even if another client is currently attached. Rejection of a second concurrent + attach happens at WebSocket upgrade time. If the active client disconnects, the + session is preserved for the idle TTL so a later connect using the same + session_name resumes the same shell. After the TTL expires, after an explicit + close control action, or after the underlying Devbox lifecycle replaces the PTY + process (such as through suspend/resume), a later request with the same + session_name creates a fresh PTY session without the previous shell state. Args: cols: Optional initial terminal width in character cells (1..=1000). Defaults to 80 @@ -158,10 +154,6 @@ def control( from the server's session cache. A subsequent connect with the same session_name will create a fresh PTY session. - Documentation note: this operation is published from mux strictly as an OpenAPI - contract stub for the PTY service control plane. It is not evidence that mux - itself serves the interactive PTY transport. - Args: extra_headers: Send extra headers @@ -241,22 +233,18 @@ async def connect( effect. The response returns a PtyConnectView containing connect_url (a server-relative path to the WebSocket data plane), idle_ttl_seconds (how long this session is retained after the last client disconnects), and the resulting - cols/rows. The interactive byte stream itself is intentionally not modeled in - OpenAPI; see the controller-level documentation for the WebSocket close-code - conventions. The single-attach contract is enforced when a client opens the - WebSocket data plane, not on this bootstrap call: bootstrap always succeeds for - a valid session_name, even if another client is currently attached. Rejection of - a second concurrent attach happens at WebSocket upgrade time. If the active - client disconnects, the session is preserved for the idle TTL so a later connect - using the same session_name resumes the same shell. After the TTL expires, after - an explicit close control action, or after the underlying Devbox lifecycle - replaces the PTY process (such as through suspend/resume), a later request with - the same session_name creates a fresh PTY session without the previous shell - state. - - Documentation note: this operation is published from mux strictly as an OpenAPI - contract stub for the PTY service control plane. It is not evidence that mux - itself serves the interactive PTY transport. + cols/rows. The interactive terminal byte stream is exchanged over the WebSocket + data plane and is not modeled in this OpenAPI contract; clients should connect + to connect_url and exchange raw binary frames for terminal I/O. The + single-attach contract is enforced when a client opens the WebSocket data plane, + not on this bootstrap call: bootstrap always succeeds for a valid session_name, + even if another client is currently attached. Rejection of a second concurrent + attach happens at WebSocket upgrade time. If the active client disconnects, the + session is preserved for the idle TTL so a later connect using the same + session_name resumes the same shell. After the TTL expires, after an explicit + close control action, or after the underlying Devbox lifecycle replaces the PTY + process (such as through suspend/resume), a later request with the same + session_name creates a fresh PTY session without the previous shell state. Args: cols: Optional initial terminal width in character cells (1..=1000). Defaults to 80 @@ -331,10 +319,6 @@ async def control( from the server's session cache. A subsequent connect with the same session_name will create a fresh PTY session. - Documentation note: this operation is published from mux strictly as an OpenAPI - contract stub for the PTY service control plane. It is not evidence that mux - itself serves the interactive PTY transport. - Args: extra_headers: Send extra headers diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index b28ef2790..428a5a7a2 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -10,8 +10,11 @@ AgentSource as AgentSource, BrokerMount as BrokerMount, ObjectMount as ObjectMount, + LifecycleHooks as LifecycleHooks, + ResumeTriggers as ResumeTriggers, LaunchParameters as LaunchParameters, CodeMountParameters as CodeMountParameters, + LifecycleConfiguration as LifecycleConfiguration, ) from .axon_view import AxonView as AxonView from .agent_view import AgentView as AgentView diff --git a/src/runloop_api_client/types/devbox_view.py b/src/runloop_api_client/types/devbox_view.py index 068f180ad..83ffa8bec 100644 --- a/src/runloop_api_client/types/devbox_view.py +++ b/src/runloop_api_client/types/devbox_view.py @@ -100,7 +100,7 @@ class DevboxView(BaseModel): initiator_id: Optional[str] = None """The ID of the initiator that created the Devbox.""" - initiator_type: Optional[Literal["unknown", "api", "scenario", "scoring_validation"]] = None + initiator_type: Optional[Literal["unknown", "api", "scenario", "scoring_validation", "reflex"]] = None """The type of initiator that created the Devbox.""" mcp_specs: Optional[Dict[str, McpSpecs]] = None diff --git a/src/runloop_api_client/types/shared/__init__.py b/src/runloop_api_client/types/shared/__init__.py index 6f7d27818..5463ca25c 100644 --- a/src/runloop_api_client/types/shared/__init__.py +++ b/src/runloop_api_client/types/shared/__init__.py @@ -7,5 +7,8 @@ from .agent_source import AgentSource as AgentSource from .broker_mount import BrokerMount as BrokerMount from .object_mount import ObjectMount as ObjectMount +from .lifecycle_hooks import LifecycleHooks as LifecycleHooks +from .resume_triggers import ResumeTriggers as ResumeTriggers from .launch_parameters import LaunchParameters as LaunchParameters from .code_mount_parameters import CodeMountParameters as CodeMountParameters +from .lifecycle_configuration import LifecycleConfiguration as LifecycleConfiguration diff --git a/src/runloop_api_client/types/shared/launch_parameters.py b/src/runloop_api_client/types/shared/launch_parameters.py index 6e74af438..0f9b1a896 100644 --- a/src/runloop_api_client/types/shared/launch_parameters.py +++ b/src/runloop_api_client/types/shared/launch_parameters.py @@ -5,35 +5,9 @@ from ..._models import BaseModel from .after_idle import AfterIdle +from .lifecycle_configuration import LifecycleConfiguration -__all__ = ["LaunchParameters", "Lifecycle", "LifecycleResumeTriggers", "UserParameters"] - - -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.""" - - -class Lifecycle(BaseModel): - """Lifecycle configuration for idle and resume behavior. - - Configure idle policy via lifecycle.after_idle (if both this and the top-level after_idle are set, they must match) and resume triggers via lifecycle.resume_triggers. - """ - - after_idle: Optional[AfterIdle] = None - """Configure Devbox lifecycle based on idle activity. - - If both this and the top-level after_idle are set, they must have the same - value. Prefer this field for new integrations. - """ - - resume_triggers: Optional[LifecycleResumeTriggers] = None - """Triggers that can resume a suspended Devbox.""" +__all__ = ["LaunchParameters", "UserParameters"] class UserParameters(BaseModel): @@ -89,12 +63,13 @@ class LaunchParameters(BaseModel): launch_commands: Optional[List[str]] = None """Set of commands to be run at launch time, before the entrypoint process is run.""" - lifecycle: Optional[Lifecycle] = None + lifecycle: Optional[LifecycleConfiguration] = None """Lifecycle configuration for idle and resume behavior. Configure idle policy via lifecycle.after_idle (if both this and the top-level - after_idle are set, they must match) and resume triggers via - lifecycle.resume_triggers. + after_idle are set, they must match), resume triggers via + lifecycle.resume_triggers, and optional lifecycle hooks via + lifecycle.lifecycle_hooks. """ network_policy_id: Optional[str] = None diff --git a/src/runloop_api_client/types/shared/lifecycle_configuration.py b/src/runloop_api_client/types/shared/lifecycle_configuration.py new file mode 100644 index 000000000..4cbbb302f --- /dev/null +++ b/src/runloop_api_client/types/shared/lifecycle_configuration.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .after_idle import AfterIdle +from .lifecycle_hooks import LifecycleHooks +from .resume_triggers import ResumeTriggers + +__all__ = ["LifecycleConfiguration"] + + +class LifecycleConfiguration(BaseModel): + """Lifecycle configuration for Devbox idle and resume behavior. + + Configure idle policy via after_idle, resume triggers via resume_triggers, and optional lifecycle hooks via lifecycle_hooks. + """ + + after_idle: Optional[AfterIdle] = None + """Configure Devbox lifecycle based on idle activity. + + If both this and the top-level after_idle are set, they must have the same + value. Prefer this field for new integrations. + """ + + lifecycle_hooks: Optional[LifecycleHooks] = None + """Optional lifecycle hooks. + + suspend_commands run through the suspend path before the Devbox suspends; see + launch_commands for work on every startup. + """ + + resume_triggers: Optional[ResumeTriggers] = None + """Triggers that can resume a suspended Devbox.""" diff --git a/src/runloop_api_client/types/shared/lifecycle_hooks.py b/src/runloop_api_client/types/shared/lifecycle_hooks.py new file mode 100644 index 000000000..4a2a049c1 --- /dev/null +++ b/src/runloop_api_client/types/shared/lifecycle_hooks.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel + +__all__ = ["LifecycleHooks"] + + +class LifecycleHooks(BaseModel): + """Lifecycle hooks for Devbox suspend. + + suspend_commands run sequentially as the configured Devbox user through the rage/vmagent suspend path before the Devbox suspends; failures are logged but do not block suspending. The suspend_deadline_ms budget defaults to 30000 ms, may not exceed 60000 ms, and covers broker drain plus suspend_commands. If the deadline is exceeded, suspend work is abandoned, the timeout is logged, and the Devbox still proceeds to suspend by shutting down vmagent and killing the VM. Resume hooks and resume deadline settings are persistence/internal only and hidden from the public API reference. launch_commands still run on every startup, including after resume. + """ + + suspend_commands: Optional[List[str]] = None + """Commands to run through the suspend path before the Devbox suspends (e.g. + + cleanup, quiesce daemons). + """ + + suspend_deadline_ms: Optional[int] = None + """Deadline in milliseconds for broker drain and suspend_commands during suspend. + + Defaults to 30000 ms and may not exceed 60000 ms. If exceeded, suspend work is + abandoned, the timeout is logged, and the Devbox still proceeds to suspend by + shutting down vmagent and killing the VM. + """ diff --git a/src/runloop_api_client/types/shared/resume_triggers.py b/src/runloop_api_client/types/shared/resume_triggers.py new file mode 100644 index 000000000..e2f3a4b3f --- /dev/null +++ b/src/runloop_api_client/types/shared/resume_triggers.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__ = ["ResumeTriggers"] + + +class ResumeTriggers(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/__init__.py b/src/runloop_api_client/types/shared_params/__init__.py index 6f7d27818..5463ca25c 100644 --- a/src/runloop_api_client/types/shared_params/__init__.py +++ b/src/runloop_api_client/types/shared_params/__init__.py @@ -7,5 +7,8 @@ from .agent_source import AgentSource as AgentSource from .broker_mount import BrokerMount as BrokerMount from .object_mount import ObjectMount as ObjectMount +from .lifecycle_hooks import LifecycleHooks as LifecycleHooks +from .resume_triggers import ResumeTriggers as ResumeTriggers from .launch_parameters import LaunchParameters as LaunchParameters from .code_mount_parameters import CodeMountParameters as CodeMountParameters +from .lifecycle_configuration import LifecycleConfiguration as LifecycleConfiguration 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 44ced4761..2a371c83b 100644 --- a/src/runloop_api_client/types/shared_params/launch_parameters.py +++ b/src/runloop_api_client/types/shared_params/launch_parameters.py @@ -7,35 +7,9 @@ from ..._types import SequenceNotStr from .after_idle import AfterIdle +from .lifecycle_configuration import LifecycleConfiguration -__all__ = ["LaunchParameters", "Lifecycle", "LifecycleResumeTriggers", "UserParameters"] - - -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.""" - - -class Lifecycle(TypedDict, total=False): - """Lifecycle configuration for idle and resume behavior. - - Configure idle policy via lifecycle.after_idle (if both this and the top-level after_idle are set, they must match) and resume triggers via lifecycle.resume_triggers. - """ - - after_idle: Optional[AfterIdle] - """Configure Devbox lifecycle based on idle activity. - - If both this and the top-level after_idle are set, they must have the same - value. Prefer this field for new integrations. - """ - - resume_triggers: Optional[LifecycleResumeTriggers] - """Triggers that can resume a suspended Devbox.""" +__all__ = ["LaunchParameters", "UserParameters"] class UserParameters(TypedDict, total=False): @@ -91,12 +65,13 @@ class LaunchParameters(TypedDict, total=False): launch_commands: Optional[SequenceNotStr[str]] """Set of commands to be run at launch time, before the entrypoint process is run.""" - lifecycle: Optional[Lifecycle] + lifecycle: Optional[LifecycleConfiguration] """Lifecycle configuration for idle and resume behavior. Configure idle policy via lifecycle.after_idle (if both this and the top-level - after_idle are set, they must match) and resume triggers via - lifecycle.resume_triggers. + after_idle are set, they must match), resume triggers via + lifecycle.resume_triggers, and optional lifecycle hooks via + lifecycle.lifecycle_hooks. """ network_policy_id: Optional[str] diff --git a/src/runloop_api_client/types/shared_params/lifecycle_configuration.py b/src/runloop_api_client/types/shared_params/lifecycle_configuration.py new file mode 100644 index 000000000..0b53cfbcc --- /dev/null +++ b/src/runloop_api_client/types/shared_params/lifecycle_configuration.py @@ -0,0 +1,36 @@ +# 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 + +from .after_idle import AfterIdle +from .lifecycle_hooks import LifecycleHooks +from .resume_triggers import ResumeTriggers + +__all__ = ["LifecycleConfiguration"] + + +class LifecycleConfiguration(TypedDict, total=False): + """Lifecycle configuration for Devbox idle and resume behavior. + + Configure idle policy via after_idle, resume triggers via resume_triggers, and optional lifecycle hooks via lifecycle_hooks. + """ + + after_idle: Optional[AfterIdle] + """Configure Devbox lifecycle based on idle activity. + + If both this and the top-level after_idle are set, they must have the same + value. Prefer this field for new integrations. + """ + + lifecycle_hooks: Optional[LifecycleHooks] + """Optional lifecycle hooks. + + suspend_commands run through the suspend path before the Devbox suspends; see + launch_commands for work on every startup. + """ + + resume_triggers: Optional[ResumeTriggers] + """Triggers that can resume a suspended Devbox.""" diff --git a/src/runloop_api_client/types/shared_params/lifecycle_hooks.py b/src/runloop_api_client/types/shared_params/lifecycle_hooks.py new file mode 100644 index 000000000..28b172e6f --- /dev/null +++ b/src/runloop_api_client/types/shared_params/lifecycle_hooks.py @@ -0,0 +1,31 @@ +# 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 + +from ..._types import SequenceNotStr + +__all__ = ["LifecycleHooks"] + + +class LifecycleHooks(TypedDict, total=False): + """Lifecycle hooks for Devbox suspend. + + suspend_commands run sequentially as the configured Devbox user through the rage/vmagent suspend path before the Devbox suspends; failures are logged but do not block suspending. The suspend_deadline_ms budget defaults to 30000 ms, may not exceed 60000 ms, and covers broker drain plus suspend_commands. If the deadline is exceeded, suspend work is abandoned, the timeout is logged, and the Devbox still proceeds to suspend by shutting down vmagent and killing the VM. Resume hooks and resume deadline settings are persistence/internal only and hidden from the public API reference. launch_commands still run on every startup, including after resume. + """ + + suspend_commands: Optional[SequenceNotStr[str]] + """Commands to run through the suspend path before the Devbox suspends (e.g. + + cleanup, quiesce daemons). + """ + + suspend_deadline_ms: Optional[int] + """Deadline in milliseconds for broker drain and suspend_commands during suspend. + + Defaults to 30000 ms and may not exceed 60000 ms. If exceeded, suspend work is + abandoned, the timeout is logged, and the Devbox still proceeds to suspend by + shutting down vmagent and killing the VM. + """ diff --git a/src/runloop_api_client/types/shared_params/resume_triggers.py b/src/runloop_api_client/types/shared_params/resume_triggers.py new file mode 100644 index 000000000..02a89031a --- /dev/null +++ b/src/runloop_api_client/types/shared_params/resume_triggers.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 Optional +from typing_extensions import TypedDict + +__all__ = ["ResumeTriggers"] + + +class ResumeTriggers(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_benchmarks.py b/tests/api_resources/test_benchmarks.py index 7612b34f2..0c1dd130f 100644 --- a/tests/api_resources/test_benchmarks.py +++ b/tests/api_resources/test_benchmarks.py @@ -304,6 +304,10 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -689,6 +693,10 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, diff --git a/tests/api_resources/test_blueprints.py b/tests/api_resources/test_blueprints.py index dd3e19fcb..acec702ab 100644 --- a/tests/api_resources/test_blueprints.py +++ b/tests/api_resources/test_blueprints.py @@ -70,6 +70,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -295,6 +299,10 @@ def test_method_create_from_inspection_with_all_params(self, client: Runloop) -> "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -464,6 +472,10 @@ def test_method_preview_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -576,6 +588,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -801,6 +817,10 @@ async def test_method_create_from_inspection_with_all_params(self, async_client: "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -970,6 +990,10 @@ async def test_method_preview_with_all_params(self, async_client: AsyncRunloop) "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, diff --git a/tests/api_resources/test_devboxes.py b/tests/api_resources/test_devboxes.py index 97f51b915..dae981685 100644 --- a/tests/api_resources/test_devboxes.py +++ b/tests/api_resources/test_devboxes.py @@ -91,6 +91,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -1756,6 +1760,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, diff --git a/tests/api_resources/test_scenarios.py b/tests/api_resources/test_scenarios.py index 9dd8c3e63..518618462 100644 --- a/tests/api_resources/test_scenarios.py +++ b/tests/api_resources/test_scenarios.py @@ -83,6 +83,10 @@ def test_method_create_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -228,6 +232,10 @@ def test_method_update_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -446,6 +454,10 @@ def test_method_start_run_with_all_params(self, client: Runloop) -> None: "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -564,6 +576,10 @@ async def test_method_create_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -709,6 +725,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncRunloop) - "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True, @@ -927,6 +947,10 @@ async def test_method_start_run_with_all_params(self, async_client: AsyncRunloop "idle_time_seconds": 0, "on_idle": "shutdown", }, + "lifecycle_hooks": { + "suspend_commands": ["string"], + "suspend_deadline_ms": 0, + }, "resume_triggers": { "axon_event": True, "http": True,