diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6a299d65..71eb10127 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 10 name: lint 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 + 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 @@ -38,7 +38,7 @@ jobs: run: uv run python scripts/generate_examples_md.py --check build: - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork + if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata') timeout-minutes: 10 name: build permissions: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f94eeca26..734ad79c1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.13.0" + ".": "1.13.1" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index f43fecc35..8ef9a4f85 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 122 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-32e4b2dfb75745be076697d252bd80aff21c08464750928ffe2b7dd997d0b443.yml -openapi_spec_hash: eb0ccabfcda0fb8c56b53939b56f6d80 -config_hash: c422b761c745873bce8fa5ccf03b7b98 +configured_endpoints: 124 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-c576eb67f119a7eb5815d4a3bf413c652cd7e4c257095e3b6b51967fe72fc00e.yml +openapi_spec_hash: 0a4d20adf725a121e39a3442afa34b32 +config_hash: a759c23a5a04ad26f8740acc7e094c01 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f7dfb282..4f5c89c16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 1.13.1 (2026-03-25) + +Full Changelog: [v1.13.0...v1.13.1](https://github.com/runloopai/api-client-python/compare/v1.13.0...v1.13.1) + +### Features + +* Add axon rest endpoints for raw SQL access ([#8287](https://github.com/runloopai/api-client-python/issues/8287)) ([d3135f4](https://github.com/runloopai/api-client-python/commit/d3135f4791d18c685981d3cb959568d36414ac81)) +* add BrokerMount to OpenAPI spec ([#8282](https://github.com/runloopai/api-client-python/issues/8282)) ([fc41e95](https://github.com/runloopai/api-client-python/commit/fc41e957a30f1387781d7285489372a617c50967)) +* **sdk:** add axons to OO SDK ([#766](https://github.com/runloopai/api-client-python/issues/766)) ([821cf30](https://github.com/runloopai/api-client-python/commit/821cf30a83e05c1d591eaad41485c8acf1ff3c8b)) + + +### Bug Fixes + +* **broker:** broker protocol renames for clarity / future proof ([#8285](https://github.com/runloopai/api-client-python/issues/8285)) ([4f07960](https://github.com/runloopai/api-client-python/commit/4f0796094fcc154f88bad8c243a50fbfc7c738b4)) + + +### Chores + +* **ci:** skip lint on metadata-only changes ([b276bc4](https://github.com/runloopai/api-client-python/commit/b276bc45e3549f33888df99f589473812fb153e7)) +* **tests:** bump steady to v0.19.7 ([2f380b6](https://github.com/runloopai/api-client-python/commit/2f380b6d400d6a8879313a0928eb3a9d4151d560)) + ## 1.13.0 (2026-03-24) Full Changelog: [v1.12.1...v1.13.0](https://github.com/runloopai/api-client-python/compare/v1.12.1...v1.13.0) diff --git a/api.md b/api.md index 81abd6751..239d793fb 100644 --- a/api.md +++ b/api.md @@ -5,6 +5,7 @@ from runloop_api_client.types import ( AfterIdle, AgentMount, AgentSource, + BrokerMount, CodeMountParameters, LaunchParameters, Mount, @@ -104,11 +105,33 @@ from runloop_api_client.types import ( Methods: -- client.axons.create(\*\*params) -> AxonView -- client.axons.retrieve(id) -> AxonView -- client.axons.list() -> AxonListView -- client.axons.publish(id, \*\*params) -> PublishResultView -- client.axons.subscribe_sse(id) -> AxonEventView +- client.axons.create(\*\*params) -> AxonView +- client.axons.retrieve(id) -> AxonView +- client.axons.list() -> AxonListView +- client.axons.publish(id, \*\*params) -> PublishResultView +- client.axons.subscribe_sse(id) -> AxonEventView + +## Sql + +Types: + +```python +from runloop_api_client.types.axons import ( + SqlBatchParams, + SqlBatchResultView, + SqlColumnMetaView, + SqlQueryResultView, + SqlResultMetaView, + SqlStatementParams, + SqlStepErrorView, + SqlStepResultView, +) +``` + +Methods: + +- client.axons.sql.batch(id, \*\*params) -> SqlBatchResultView +- client.axons.sql.query(id, \*\*params) -> SqlQueryResultView # Blueprints diff --git a/pyproject.toml b/pyproject.toml index 931df6d19..d4e04171a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "runloop_api_client" -version = "1.13.0" +version = "1.13.1" description = "The official Python library for the runloop API" dynamic = ["readme"] license = "MIT" diff --git a/scripts/mock b/scripts/mock index b319bdfbb..09eb49f65 100755 --- a/scripts/mock +++ b/scripts/mock @@ -22,9 +22,9 @@ echo "==> Starting mock server with URL ${URL}" # Run steady mock on the given spec if [ "$1" == "--daemon" ]; then # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stdy/cli@0.19.6 -- steady --version + npm exec --package=@stdy/cli@0.19.7 -- steady --version - npm exec --package=@stdy/cli@0.19.6 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" &> .stdy.log & + npm exec --package=@stdy/cli@0.19.7 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" &> .stdy.log & # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" @@ -48,5 +48,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stdy/cli@0.19.6 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" + npm exec --package=@stdy/cli@0.19.7 -- steady --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index bc3f79742..10fadae13 100755 --- a/scripts/test +++ b/scripts/test @@ -43,7 +43,7 @@ elif ! steady_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.6 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.19.7 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-form-array-format=comma --validator-query-array-format=comma --validator-form-object-format=brackets --validator-query-object-format=brackets${NC}" echo exit 1 diff --git a/src/runloop_api_client/_client.py b/src/runloop_api_client/_client.py index 3b5ab20db..31ef26d72 100644 --- a/src/runloop_api_client/_client.py +++ b/src/runloop_api_client/_client.py @@ -47,12 +47,12 @@ gateway_configs, network_policies, ) - from .resources.axons import AxonsResource, AsyncAxonsResource from .resources.agents import AgentsResource, AsyncAgentsResource from .resources.objects import ObjectsResource, AsyncObjectsResource from .resources.secrets import SecretsResource, AsyncSecretsResource from .resources.benchmarks import BenchmarksResource, AsyncBenchmarksResource from .resources.blueprints import BlueprintsResource, AsyncBlueprintsResource + from .resources.axons.axons import AxonsResource, AsyncAxonsResource from .resources.mcp_configs import McpConfigsResource, AsyncMcpConfigsResource from .resources.repositories import RepositoriesResource, AsyncRepositoriesResource from .resources.benchmark_jobs import BenchmarkJobsResource, AsyncBenchmarkJobsResource diff --git a/src/runloop_api_client/_version.py b/src/runloop_api_client/_version.py index 5dafd5d62..d87121d4d 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.13.0" # x-release-please-version +__version__ = "1.13.1" # x-release-please-version diff --git a/src/runloop_api_client/resources/axons/__init__.py b/src/runloop_api_client/resources/axons/__init__.py new file mode 100644 index 000000000..81c6cf070 --- /dev/null +++ b/src/runloop_api_client/resources/axons/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .sql import ( + SqlResource, + AsyncSqlResource, + SqlResourceWithRawResponse, + AsyncSqlResourceWithRawResponse, + SqlResourceWithStreamingResponse, + AsyncSqlResourceWithStreamingResponse, +) +from .axons import ( + AxonsResource, + AsyncAxonsResource, + AxonsResourceWithRawResponse, + AsyncAxonsResourceWithRawResponse, + AxonsResourceWithStreamingResponse, + AsyncAxonsResourceWithStreamingResponse, +) + +__all__ = [ + "SqlResource", + "AsyncSqlResource", + "SqlResourceWithRawResponse", + "AsyncSqlResourceWithRawResponse", + "SqlResourceWithStreamingResponse", + "AsyncSqlResourceWithStreamingResponse", + "AxonsResource", + "AsyncAxonsResource", + "AxonsResourceWithRawResponse", + "AsyncAxonsResourceWithRawResponse", + "AxonsResourceWithStreamingResponse", + "AsyncAxonsResourceWithStreamingResponse", +] diff --git a/src/runloop_api_client/resources/axons.py b/src/runloop_api_client/resources/axons/axons.py similarity index 92% rename from src/runloop_api_client/resources/axons.py rename to src/runloop_api_client/resources/axons/axons.py index f39c994a3..097fe2330 100644 --- a/src/runloop_api_client/resources/axons.py +++ b/src/runloop_api_client/resources/axons/axons.py @@ -7,28 +7,40 @@ import httpx -from ..types import axon_create_params, axon_publish_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 -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( +from .sql import ( + SqlResource, + AsyncSqlResource, + SqlResourceWithRawResponse, + AsyncSqlResourceWithRawResponse, + SqlResourceWithStreamingResponse, + AsyncSqlResourceWithStreamingResponse, +) +from ...types import axon_create_params, axon_publish_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 +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 .._streaming import Stream, AsyncStream -from .._base_client import make_request_options -from ..types.axon_view import AxonView -from ..types.axon_list_view import AxonListView -from ..types.axon_event_view import AxonEventView -from ..types.publish_result_view import PublishResultView +from ..._streaming import Stream, AsyncStream +from ..._base_client import make_request_options +from ...types.axon_view import AxonView +from ...types.axon_list_view import AxonListView +from ...types.axon_event_view import AxonEventView +from ...types.publish_result_view import PublishResultView __all__ = ["AxonsResource", "AsyncAxonsResource"] class AxonsResource(SyncAPIResource): + @cached_property + def sql(self) -> SqlResource: + return SqlResource(self._client) + @cached_property def with_raw_response(self) -> AxonsResourceWithRawResponse: """ @@ -241,6 +253,10 @@ def subscribe_sse( class AsyncAxonsResource(AsyncAPIResource): + @cached_property + def sql(self) -> AsyncSqlResource: + return AsyncSqlResource(self._client) + @cached_property def with_raw_response(self) -> AsyncAxonsResourceWithRawResponse: """ @@ -472,6 +488,10 @@ def __init__(self, axons: AxonsResource) -> None: axons.subscribe_sse, ) + @cached_property + def sql(self) -> SqlResourceWithRawResponse: + return SqlResourceWithRawResponse(self._axons.sql) + class AsyncAxonsResourceWithRawResponse: def __init__(self, axons: AsyncAxonsResource) -> None: @@ -493,6 +513,10 @@ def __init__(self, axons: AsyncAxonsResource) -> None: axons.subscribe_sse, ) + @cached_property + def sql(self) -> AsyncSqlResourceWithRawResponse: + return AsyncSqlResourceWithRawResponse(self._axons.sql) + class AxonsResourceWithStreamingResponse: def __init__(self, axons: AxonsResource) -> None: @@ -514,6 +538,10 @@ def __init__(self, axons: AxonsResource) -> None: axons.subscribe_sse, ) + @cached_property + def sql(self) -> SqlResourceWithStreamingResponse: + return SqlResourceWithStreamingResponse(self._axons.sql) + class AsyncAxonsResourceWithStreamingResponse: def __init__(self, axons: AsyncAxonsResource) -> None: @@ -534,3 +562,7 @@ def __init__(self, axons: AsyncAxonsResource) -> None: self.subscribe_sse = async_to_streamed_response_wrapper( axons.subscribe_sse, ) + + @cached_property + def sql(self) -> AsyncSqlResourceWithStreamingResponse: + return AsyncSqlResourceWithStreamingResponse(self._axons.sql) diff --git a/src/runloop_api_client/resources/axons/sql.py b/src/runloop_api_client/resources/axons/sql.py new file mode 100644 index 000000000..449b0fb0d --- /dev/null +++ b/src/runloop_api_client/resources/axons/sql.py @@ -0,0 +1,313 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable + +import httpx + +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 +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 ...types.axons import sql_batch_params, sql_query_params +from ..._base_client import make_request_options +from ...types.axons.sql_statement_params import SqlStatementParams +from ...types.axons.sql_batch_result_view import SqlBatchResultView +from ...types.axons.sql_query_result_view import SqlQueryResultView + +__all__ = ["SqlResource", "AsyncSqlResource"] + + +class SqlResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SqlResourceWithRawResponse: + """ + 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 SqlResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SqlResourceWithStreamingResponse: + """ + 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 SqlResourceWithStreamingResponse(self) + + def batch( + self, + id: str, + *, + statements: Iterable[SqlStatementParams], + # 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, + ) -> SqlBatchResultView: + """ + [Beta] Execute multiple SQL statements atomically within a single transaction + against an axon's SQLite database. + + Args: + statements: The SQL statements to execute atomically within a transaction. + + 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/axons/{id}/sql/batch", id=id), + body=maybe_transform({"statements": statements}, sql_batch_params.SqlBatchParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=SqlBatchResultView, + ) + + def query( + self, + id: str, + *, + sql: str, + params: Iterable[object] | 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, + ) -> SqlQueryResultView: + """ + [Beta] Execute a single parameterized SQL statement against an axon's SQLite + database. + + Args: + sql: SQL query with ?-style positional placeholders. + + params: Positional parameter bindings for ? placeholders. + + 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/axons/{id}/sql/query", id=id), + body=maybe_transform( + { + "sql": sql, + "params": params, + }, + sql_query_params.SqlQueryParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=SqlQueryResultView, + ) + + +class AsyncSqlResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSqlResourceWithRawResponse: + """ + 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 AsyncSqlResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSqlResourceWithStreamingResponse: + """ + 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 AsyncSqlResourceWithStreamingResponse(self) + + async def batch( + self, + id: str, + *, + statements: Iterable[SqlStatementParams], + # 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, + ) -> SqlBatchResultView: + """ + [Beta] Execute multiple SQL statements atomically within a single transaction + against an axon's SQLite database. + + Args: + statements: The SQL statements to execute atomically within a transaction. + + 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/axons/{id}/sql/batch", id=id), + body=await async_maybe_transform({"statements": statements}, sql_batch_params.SqlBatchParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=SqlBatchResultView, + ) + + async def query( + self, + id: str, + *, + sql: str, + params: Iterable[object] | 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, + ) -> SqlQueryResultView: + """ + [Beta] Execute a single parameterized SQL statement against an axon's SQLite + database. + + Args: + sql: SQL query with ?-style positional placeholders. + + params: Positional parameter bindings for ? placeholders. + + 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/axons/{id}/sql/query", id=id), + body=await async_maybe_transform( + { + "sql": sql, + "params": params, + }, + sql_query_params.SqlQueryParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=SqlQueryResultView, + ) + + +class SqlResourceWithRawResponse: + def __init__(self, sql: SqlResource) -> None: + self._sql = sql + + self.batch = to_raw_response_wrapper( + sql.batch, + ) + self.query = to_raw_response_wrapper( + sql.query, + ) + + +class AsyncSqlResourceWithRawResponse: + def __init__(self, sql: AsyncSqlResource) -> None: + self._sql = sql + + self.batch = async_to_raw_response_wrapper( + sql.batch, + ) + self.query = async_to_raw_response_wrapper( + sql.query, + ) + + +class SqlResourceWithStreamingResponse: + def __init__(self, sql: SqlResource) -> None: + self._sql = sql + + self.batch = to_streamed_response_wrapper( + sql.batch, + ) + self.query = to_streamed_response_wrapper( + sql.query, + ) + + +class AsyncSqlResourceWithStreamingResponse: + def __init__(self, sql: AsyncSqlResource) -> None: + self._sql = sql + + self.batch = async_to_streamed_response_wrapper( + sql.batch, + ) + self.query = async_to_streamed_response_wrapper( + sql.query, + ) diff --git a/src/runloop_api_client/types/__init__.py b/src/runloop_api_client/types/__init__.py index 11f82aa7a..77ac06e96 100644 --- a/src/runloop_api_client/types/__init__.py +++ b/src/runloop_api_client/types/__init__.py @@ -8,6 +8,7 @@ AgentMount as AgentMount, RunProfile as RunProfile, AgentSource as AgentSource, + BrokerMount as BrokerMount, ObjectMount as ObjectMount, LaunchParameters as LaunchParameters, CodeMountParameters as CodeMountParameters, diff --git a/src/runloop_api_client/types/axons/__init__.py b/src/runloop_api_client/types/axons/__init__.py new file mode 100644 index 000000000..8ab8cf9b1 --- /dev/null +++ b/src/runloop_api_client/types/axons/__init__.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from .sql_batch_params import SqlBatchParams as SqlBatchParams +from .sql_query_params import SqlQueryParams as SqlQueryParams +from .sql_step_error_view import SqlStepErrorView as SqlStepErrorView +from .sql_column_meta_view import SqlColumnMetaView as SqlColumnMetaView +from .sql_result_meta_view import SqlResultMetaView as SqlResultMetaView +from .sql_statement_params import SqlStatementParams as SqlStatementParams +from .sql_step_result_view import SqlStepResultView as SqlStepResultView +from .sql_batch_result_view import SqlBatchResultView as SqlBatchResultView +from .sql_query_result_view import SqlQueryResultView as SqlQueryResultView diff --git a/src/runloop_api_client/types/axons/sql_batch_params.py b/src/runloop_api_client/types/axons/sql_batch_params.py new file mode 100644 index 000000000..423599bdc --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_batch_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, TypedDict + +from .sql_statement_params import SqlStatementParams + +__all__ = ["SqlBatchParams"] + + +class SqlBatchParams(TypedDict, total=False): + statements: Required[Iterable[SqlStatementParams]] + """The SQL statements to execute atomically within a transaction.""" diff --git a/src/runloop_api_client/types/axons/sql_batch_result_view.py b/src/runloop_api_client/types/axons/sql_batch_result_view.py new file mode 100644 index 000000000..be728bc15 --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_batch_result_view.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel +from .sql_step_result_view import SqlStepResultView + +__all__ = ["SqlBatchResultView"] + + +class SqlBatchResultView(BaseModel): + results: List[SqlStepResultView] + """One result per statement, in order.""" diff --git a/src/runloop_api_client/types/axons/sql_column_meta_view.py b/src/runloop_api_client/types/axons/sql_column_meta_view.py new file mode 100644 index 000000000..37c9ff05d --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_column_meta_view.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["SqlColumnMetaView"] + + +class SqlColumnMetaView(BaseModel): + name: str + """Column name or alias.""" + + type: str + """Declared type (TEXT, INTEGER, REAL, BLOB, or empty).""" diff --git a/src/runloop_api_client/types/axons/sql_query_params.py b/src/runloop_api_client/types/axons/sql_query_params.py new file mode 100644 index 000000000..fbfc7e59e --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_query_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, TypedDict + +__all__ = ["SqlQueryParams"] + + +class SqlQueryParams(TypedDict, total=False): + sql: Required[str] + """SQL query with ?-style positional placeholders.""" + + params: Iterable[object] + """Positional parameter bindings for ? placeholders.""" diff --git a/src/runloop_api_client/types/axons/sql_query_result_view.py b/src/runloop_api_client/types/axons/sql_query_result_view.py new file mode 100644 index 000000000..8671252fa --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_query_result_view.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ..._models import BaseModel +from .sql_column_meta_view import SqlColumnMetaView +from .sql_result_meta_view import SqlResultMetaView + +__all__ = ["SqlQueryResultView"] + + +class SqlQueryResultView(BaseModel): + columns: List[SqlColumnMetaView] + """Column metadata.""" + + meta: SqlResultMetaView + """Execution metadata.""" + + rows: List[object] + """Result rows (empty for non-SELECT statements).""" diff --git a/src/runloop_api_client/types/axons/sql_result_meta_view.py b/src/runloop_api_client/types/axons/sql_result_meta_view.py new file mode 100644 index 000000000..eca0b7b2f --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_result_meta_view.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["SqlResultMetaView"] + + +class SqlResultMetaView(BaseModel): + changes: int + """Rows modified by INSERT/UPDATE/DELETE.""" + + duration_ms: float + """Execution time in milliseconds.""" + + rows_read_limit_reached: bool + """True when result was truncated at the row limit.""" diff --git a/src/runloop_api_client/types/axons/sql_statement_params.py b/src/runloop_api_client/types/axons/sql_statement_params.py new file mode 100644 index 000000000..c41c18d60 --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_statement_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, TypedDict + +__all__ = ["SqlStatementParams"] + + +class SqlStatementParams(TypedDict, total=False): + sql: Required[str] + """SQL query with ?-style positional placeholders.""" + + params: Iterable[object] + """Positional parameter bindings for ? placeholders.""" diff --git a/src/runloop_api_client/types/axons/sql_step_error_view.py b/src/runloop_api_client/types/axons/sql_step_error_view.py new file mode 100644 index 000000000..f47c0da8b --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_step_error_view.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["SqlStepErrorView"] + + +class SqlStepErrorView(BaseModel): + message: str + """Error message.""" diff --git a/src/runloop_api_client/types/axons/sql_step_result_view.py b/src/runloop_api_client/types/axons/sql_step_result_view.py new file mode 100644 index 000000000..afcef3d85 --- /dev/null +++ b/src/runloop_api_client/types/axons/sql_step_result_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 +from .sql_step_error_view import SqlStepErrorView +from .sql_query_result_view import SqlQueryResultView + +__all__ = ["SqlStepResultView"] + + +class SqlStepResultView(BaseModel): + error: Optional[SqlStepErrorView] = None + """Error on failure.""" + + success: Optional[SqlQueryResultView] = None + """Result on success.""" diff --git a/src/runloop_api_client/types/shared/__init__.py b/src/runloop_api_client/types/shared/__init__.py index ab07ef757..6f7d27818 100644 --- a/src/runloop_api_client/types/shared/__init__.py +++ b/src/runloop_api_client/types/shared/__init__.py @@ -5,6 +5,7 @@ from .agent_mount import AgentMount as AgentMount from .run_profile import RunProfile as RunProfile from .agent_source import AgentSource as AgentSource +from .broker_mount import BrokerMount as BrokerMount from .object_mount import ObjectMount as ObjectMount from .launch_parameters import LaunchParameters as LaunchParameters from .code_mount_parameters import CodeMountParameters as CodeMountParameters diff --git a/src/runloop_api_client/types/shared/broker_mount.py b/src/runloop_api_client/types/shared/broker_mount.py new file mode 100644 index 000000000..ff48078e9 --- /dev/null +++ b/src/runloop_api_client/types/shared/broker_mount.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["BrokerMount"] + + +class BrokerMount(BaseModel): + axon_id: str + """The ID of the axon event stream to mount onto the Devbox.""" + + type: Literal["broker_mount"] + + agent_binary: Optional[str] = None + """Binary to launch the agent (e.g., 'opencode'). Used by ACP broker.""" + + launch_args: Optional[List[str]] = None + """Arguments to pass to the agent command (e.g., ['acp']). Used by ACP broker.""" + + protocol: Optional[Literal["acp", "claude_json", "codex_app_server"]] = None + """The protocol used by the broker to deliver events to the agent.""" diff --git a/src/runloop_api_client/types/shared/mount.py b/src/runloop_api_client/types/shared/mount.py index 855748fc4..f283abe6b 100644 --- a/src/runloop_api_client/types/shared/mount.py +++ b/src/runloop_api_client/types/shared/mount.py @@ -6,6 +6,7 @@ from ..._utils import PropertyInfo from ..._models import BaseModel from .agent_mount import AgentMount +from .broker_mount import BrokerMount from .object_mount import ObjectMount __all__ = ["Mount", "CodeMount", "FileMount"] @@ -40,4 +41,6 @@ class FileMount(BaseModel): type: Literal["file_mount"] -Mount: TypeAlias = Annotated[Union[ObjectMount, AgentMount, CodeMount, FileMount], PropertyInfo(discriminator="type")] +Mount: TypeAlias = Annotated[ + Union[ObjectMount, AgentMount, CodeMount, FileMount, BrokerMount], PropertyInfo(discriminator="type") +] diff --git a/src/runloop_api_client/types/shared_params/__init__.py b/src/runloop_api_client/types/shared_params/__init__.py index ab07ef757..6f7d27818 100644 --- a/src/runloop_api_client/types/shared_params/__init__.py +++ b/src/runloop_api_client/types/shared_params/__init__.py @@ -5,6 +5,7 @@ from .agent_mount import AgentMount as AgentMount from .run_profile import RunProfile as RunProfile from .agent_source import AgentSource as AgentSource +from .broker_mount import BrokerMount as BrokerMount from .object_mount import ObjectMount as ObjectMount from .launch_parameters import LaunchParameters as LaunchParameters from .code_mount_parameters import CodeMountParameters as CodeMountParameters diff --git a/src/runloop_api_client/types/shared_params/broker_mount.py b/src/runloop_api_client/types/shared_params/broker_mount.py new file mode 100644 index 000000000..4a495b051 --- /dev/null +++ b/src/runloop_api_client/types/shared_params/broker_mount.py @@ -0,0 +1,26 @@ +# 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 Literal, Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["BrokerMount"] + + +class BrokerMount(TypedDict, total=False): + axon_id: Required[str] + """The ID of the axon event stream to mount onto the Devbox.""" + + type: Required[Literal["broker_mount"]] + + agent_binary: Optional[str] + """Binary to launch the agent (e.g., 'opencode'). Used by ACP broker.""" + + launch_args: Optional[SequenceNotStr[str]] + """Arguments to pass to the agent command (e.g., ['acp']). Used by ACP broker.""" + + protocol: Optional[Literal["acp", "claude_json", "codex_app_server"]] + """The protocol used by the broker to deliver events to the agent.""" diff --git a/src/runloop_api_client/types/shared_params/mount.py b/src/runloop_api_client/types/shared_params/mount.py index 8c053506c..bdc8a142c 100644 --- a/src/runloop_api_client/types/shared_params/mount.py +++ b/src/runloop_api_client/types/shared_params/mount.py @@ -6,6 +6,7 @@ from typing_extensions import Literal, Required, TypeAlias, TypedDict from .agent_mount import AgentMount +from .broker_mount import BrokerMount from .object_mount import ObjectMount __all__ = ["Mount", "CodeMount", "FileMount"] @@ -40,4 +41,4 @@ class FileMount(TypedDict, total=False): type: Required[Literal["file_mount"]] -Mount: TypeAlias = Union[ObjectMount, AgentMount, CodeMount, FileMount] +Mount: TypeAlias = Union[ObjectMount, AgentMount, CodeMount, FileMount, BrokerMount] diff --git a/tests/api_resources/axons/__init__.py b/tests/api_resources/axons/__init__.py new file mode 100644 index 000000000..fd8019a9a --- /dev/null +++ b/tests/api_resources/axons/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/axons/test_sql.py b/tests/api_resources/axons/test_sql.py new file mode 100644 index 000000000..bd2506187 --- /dev/null +++ b/tests/api_resources/axons/test_sql.py @@ -0,0 +1,210 @@ +# 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.axons import SqlBatchResultView, SqlQueryResultView + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSql: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_batch(self, client: Runloop) -> None: + sql = client.axons.sql.batch( + id="id", + statements=[{"sql": "sql"}], + ) + assert_matches_type(SqlBatchResultView, sql, path=["response"]) + + @parametrize + def test_raw_response_batch(self, client: Runloop) -> None: + response = client.axons.sql.with_raw_response.batch( + id="id", + statements=[{"sql": "sql"}], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + sql = response.parse() + assert_matches_type(SqlBatchResultView, sql, path=["response"]) + + @parametrize + def test_streaming_response_batch(self, client: Runloop) -> None: + with client.axons.sql.with_streaming_response.batch( + id="id", + statements=[{"sql": "sql"}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + sql = response.parse() + assert_matches_type(SqlBatchResultView, sql, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_batch(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.axons.sql.with_raw_response.batch( + id="", + statements=[{"sql": "sql"}], + ) + + @parametrize + def test_method_query(self, client: Runloop) -> None: + sql = client.axons.sql.query( + id="id", + sql="sql", + ) + assert_matches_type(SqlQueryResultView, sql, path=["response"]) + + @parametrize + def test_method_query_with_all_params(self, client: Runloop) -> None: + sql = client.axons.sql.query( + id="id", + sql="sql", + params=[{}], + ) + assert_matches_type(SqlQueryResultView, sql, path=["response"]) + + @parametrize + def test_raw_response_query(self, client: Runloop) -> None: + response = client.axons.sql.with_raw_response.query( + id="id", + sql="sql", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + sql = response.parse() + assert_matches_type(SqlQueryResultView, sql, path=["response"]) + + @parametrize + def test_streaming_response_query(self, client: Runloop) -> None: + with client.axons.sql.with_streaming_response.query( + id="id", + sql="sql", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + sql = response.parse() + assert_matches_type(SqlQueryResultView, sql, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_query(self, client: Runloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.axons.sql.with_raw_response.query( + id="", + sql="sql", + ) + + +class TestAsyncSql: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_batch(self, async_client: AsyncRunloop) -> None: + sql = await async_client.axons.sql.batch( + id="id", + statements=[{"sql": "sql"}], + ) + assert_matches_type(SqlBatchResultView, sql, path=["response"]) + + @parametrize + async def test_raw_response_batch(self, async_client: AsyncRunloop) -> None: + response = await async_client.axons.sql.with_raw_response.batch( + id="id", + statements=[{"sql": "sql"}], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + sql = await response.parse() + assert_matches_type(SqlBatchResultView, sql, path=["response"]) + + @parametrize + async def test_streaming_response_batch(self, async_client: AsyncRunloop) -> None: + async with async_client.axons.sql.with_streaming_response.batch( + id="id", + statements=[{"sql": "sql"}], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + sql = await response.parse() + assert_matches_type(SqlBatchResultView, sql, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_batch(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.axons.sql.with_raw_response.batch( + id="", + statements=[{"sql": "sql"}], + ) + + @parametrize + async def test_method_query(self, async_client: AsyncRunloop) -> None: + sql = await async_client.axons.sql.query( + id="id", + sql="sql", + ) + assert_matches_type(SqlQueryResultView, sql, path=["response"]) + + @parametrize + async def test_method_query_with_all_params(self, async_client: AsyncRunloop) -> None: + sql = await async_client.axons.sql.query( + id="id", + sql="sql", + params=[{}], + ) + assert_matches_type(SqlQueryResultView, sql, path=["response"]) + + @parametrize + async def test_raw_response_query(self, async_client: AsyncRunloop) -> None: + response = await async_client.axons.sql.with_raw_response.query( + id="id", + sql="sql", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + sql = await response.parse() + assert_matches_type(SqlQueryResultView, sql, path=["response"]) + + @parametrize + async def test_streaming_response_query(self, async_client: AsyncRunloop) -> None: + async with async_client.axons.sql.with_streaming_response.query( + id="id", + sql="sql", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + sql = await response.parse() + assert_matches_type(SqlQueryResultView, sql, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_query(self, async_client: AsyncRunloop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.axons.sql.with_raw_response.query( + id="", + sql="sql", + )