From 7f1b835240574dc517d424dca84251f86c4b1276 Mon Sep 17 00:00:00 2001 From: Ben Navetta Date: Wed, 7 Jan 2026 11:05:23 +0000 Subject: [PATCH 01/11] docs(dev): Add WARP.md file --- WARP.md | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 WARP.md diff --git a/WARP.md b/WARP.md new file mode 100644 index 0000000..1cf8a11 --- /dev/null +++ b/WARP.md @@ -0,0 +1,119 @@ +# WARP.md + +This file provides guidance to WARP (warp.dev) when working with code in this repository. + +## Repository Overview + +This is the official Python SDK for the Warp API, providing convenient access to the Warp API REST API. The SDK is **generated code** created using [Stainless](https://www.stainless.com/) from an OpenAPI specification. Most files are auto-generated, with exceptions for `src/warp_agent_sdk/lib/` and `examples/` directories which are manually maintained. + +## Development Commands + +### Setup +```bash +# Bootstrap the development environment (installs uv, Python, and dependencies) +./scripts/bootstrap +``` + +### Testing +```bash +# Run full test suite (tests with both Pydantic v1 and v2, multiple Python versions) +./scripts/test + +# Run specific tests +uv run pytest tests/test_client.py + +# Tests require a mock Prism server running on port 4010 +# The test script will automatically start one if not running +# To manually start: ./scripts/mock --daemon +``` + +### Linting and Type Checking +```bash +# Run all linters (ruff, pyright, mypy) +./scripts/lint + +# Run with auto-fix +./scripts/lint --fix + +# Format code +./scripts/format +``` + +### Building +```bash +# Build distribution packages (.tar.gz and .whl) +uv build +``` + +## Code Architecture + +### Generated vs Manual Code + +**Generated code** (DO NOT manually edit - changes will be overwritten): +- `src/warp_agent_sdk/_client.py` - Main client classes (WarpAPI, AsyncWarpAPI) +- `src/warp_agent_sdk/resources/` - API resource classes +- `src/warp_agent_sdk/types/` - Type definitions and models +- Most utility files in `src/warp_agent_sdk/_utils/` + +**Manual code** (safe to edit): +- `src/warp_agent_sdk/lib/` - Custom library code +- `examples/` - Example scripts +- `tests/` - Test files + +### Core Components + +**Client Architecture**: +- `WarpAPI` (sync) and `AsyncWarpAPI` (async) are the main entry points +- Both inherit from `SyncAPIClient` and `AsyncAPIClient` base classes +- Support for both `httpx` (default) and `aiohttp` (optional) HTTP backends +- API key authentication via `Authorization: Bearer` header +- Default base URL: `https://app.warp.dev/api/v1` + +**Resource Structure**: +- Resources are organized hierarchically (e.g., `client.agent.tasks.retrieve()`) +- Each resource has sync/async variants and raw/streaming response wrappers +- Main resource: `AgentResource` with `run()` method and nested `TasksResource` + +**Type System**: +- Uses Pydantic models for request/response validation +- TypedDict for nested parameters +- Custom types: `NotGiven`, `Omit` for optional parameters +- Supports both Pydantic v1 and v2 + +## Environment Variables + +- `WARP_API_KEY` - API key for authentication (required) +- `WARP_API_BASE_URL` - Override default base URL +- `WARP_API_LOG` - Enable logging (`info` or `debug`) +- `TEST_API_BASE_URL` - Use custom API endpoint for tests + +## Testing Conventions + +- Tests use `pytest` with `pytest-asyncio` for async tests +- Both sync and async variants must be tested +- Tests run against a mock Prism server based on OpenAPI spec +- Tests run with both Pydantic v1 and v2 on Python 3.9 and 3.14+ +- Use `respx` for mocking HTTP requests in tests + +## Development Workflow + +1. **Making changes**: + - Only edit files in `lib/` and `examples/` directories + - Other changes should be made to the OpenAPI spec and regenerated + +2. **Adding examples**: + - Create executable Python scripts in `examples/` + - Use shebang: `#!/usr/bin/env -S uv run python` + - Make executable: `chmod +x examples/.py` + +3. **Code quality**: + - Run `./scripts/format` before committing + - Ensure `./scripts/lint` passes + - Run `./scripts/test` to verify changes + +## Package Management + +- Uses [uv](https://docs.astral.sh/uv/) for fast, reliable dependency management +- `pyproject.toml` defines project metadata and dependencies +- `uv.lock` pins exact versions for reproducibility +- `requirements-dev.lock` exported for pip compatibility From c64e6c44b6d02a63609d02f4976a7977dc3b0045 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 07:31:05 +0000 Subject: [PATCH 02/11] feat(client): add support for binary request streaming --- src/warp_agent_sdk/_base_client.py | 145 ++++++++++++++++++++-- src/warp_agent_sdk/_models.py | 17 ++- src/warp_agent_sdk/_types.py | 9 ++ tests/test_client.py | 187 ++++++++++++++++++++++++++++- 4 files changed, 344 insertions(+), 14 deletions(-) diff --git a/src/warp_agent_sdk/_base_client.py b/src/warp_agent_sdk/_base_client.py index 41099fd..e0f9736 100644 --- a/src/warp_agent_sdk/_base_client.py +++ b/src/warp_agent_sdk/_base_client.py @@ -9,6 +9,7 @@ import inspect import logging import platform +import warnings import email.utils from types import TracebackType from random import random @@ -51,9 +52,11 @@ ResponseT, AnyMapping, PostParser, + BinaryTypes, RequestFiles, HttpxSendArgs, RequestOptions, + AsyncBinaryTypes, HttpxRequestFiles, ModelBuilderProtocol, not_given, @@ -477,8 +480,19 @@ def _build_request( retries_taken: int = 0, ) -> httpx.Request: if log.isEnabledFor(logging.DEBUG): - log.debug("Request options: %s", model_dump(options, exclude_unset=True)) - + log.debug( + "Request options: %s", + model_dump( + options, + exclude_unset=True, + # Pydantic v1 can't dump every type we support in content, so we exclude it for now. + exclude={ + "content", + } + if PYDANTIC_V1 + else {}, + ), + ) kwargs: dict[str, Any] = {} json_data = options.json_data @@ -532,7 +546,13 @@ def _build_request( is_body_allowed = options.method.lower() != "get" if is_body_allowed: - if isinstance(json_data, bytes): + if options.content is not None and json_data is not None: + raise TypeError("Passing both `content` and `json_data` is not supported") + if options.content is not None and files is not None: + raise TypeError("Passing both `content` and `files` is not supported") + if options.content is not None: + kwargs["content"] = options.content + elif isinstance(json_data, bytes): kwargs["content"] = json_data else: kwargs["json"] = json_data if is_given(json_data) else None @@ -1194,6 +1214,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[False] = False, @@ -1206,6 +1227,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: Literal[True], @@ -1219,6 +1241,7 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool, @@ -1231,13 +1254,25 @@ def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, files: RequestFiles | None = None, stream: bool = False, stream_cls: type[_StreamT] | None = None, ) -> ResponseT | _StreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) @@ -1247,11 +1282,23 @@ def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=to_httpx_files(files), **options + method="patch", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1261,11 +1308,23 @@ def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=to_httpx_files(files), **options ) return self.request(cast_to, opts) @@ -1275,9 +1334,19 @@ def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: BinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return self.request(cast_to, opts) def get_api_list( @@ -1717,6 +1786,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[False] = False, @@ -1729,6 +1799,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: Literal[True], @@ -1742,6 +1813,7 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool, @@ -1754,13 +1826,25 @@ async def post( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, stream: bool = False, stream_cls: type[_AsyncStreamT] | None = None, ) -> ResponseT | _AsyncStreamT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="post", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="post", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls) @@ -1770,11 +1854,28 @@ async def patch( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="patch", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="patch", + url=path, + json_data=body, + content=content, + files=await async_to_httpx_files(files), + **options, ) return await self.request(cast_to, opts) @@ -1784,11 +1885,23 @@ async def put( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, files: RequestFiles | None = None, options: RequestOptions = {}, ) -> ResponseT: + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if files is not None and content is not None: + raise TypeError("Passing both `files` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) opts = FinalRequestOptions.construct( - method="put", url=path, json_data=body, files=await async_to_httpx_files(files), **options + method="put", url=path, json_data=body, content=content, files=await async_to_httpx_files(files), **options ) return await self.request(cast_to, opts) @@ -1798,9 +1911,19 @@ async def delete( *, cast_to: Type[ResponseT], body: Body | None = None, + content: AsyncBinaryTypes | None = None, options: RequestOptions = {}, ) -> ResponseT: - opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, **options) + if body is not None and content is not None: + raise TypeError("Passing both `body` and `content` is not supported") + if isinstance(body, bytes): + warnings.warn( + "Passing raw bytes as `body` is deprecated and will be removed in a future version. " + "Please pass raw bytes via the `content` parameter instead.", + DeprecationWarning, + stacklevel=2, + ) + opts = FinalRequestOptions.construct(method="delete", url=path, json_data=body, content=content, **options) return await self.request(cast_to, opts) def get_api_list( diff --git a/src/warp_agent_sdk/_models.py b/src/warp_agent_sdk/_models.py index ca9500b..29070e0 100644 --- a/src/warp_agent_sdk/_models.py +++ b/src/warp_agent_sdk/_models.py @@ -3,7 +3,20 @@ import os import inspect import weakref -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast +from typing import ( + IO, + TYPE_CHECKING, + Any, + Type, + Union, + Generic, + TypeVar, + Callable, + Iterable, + Optional, + AsyncIterable, + cast, +) from datetime import date, datetime from typing_extensions import ( List, @@ -787,6 +800,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): timeout: float | Timeout | None files: HttpxRequestFiles | None idempotency_key: str + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] json_data: Body extra_json: AnyMapping follow_redirects: bool @@ -805,6 +819,7 @@ class FinalRequestOptions(pydantic.BaseModel): post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() follow_redirects: Union[bool, None] = None + content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. json_data: Union[Body, None] = None diff --git a/src/warp_agent_sdk/_types.py b/src/warp_agent_sdk/_types.py index 6c063c0..39b1522 100644 --- a/src/warp_agent_sdk/_types.py +++ b/src/warp_agent_sdk/_types.py @@ -13,9 +13,11 @@ Mapping, TypeVar, Callable, + Iterable, Iterator, Optional, Sequence, + AsyncIterable, ) from typing_extensions import ( Set, @@ -56,6 +58,13 @@ else: Base64FileInput = Union[IO[bytes], PathLike] FileContent = Union[IO[bytes], bytes, PathLike] # PathLike is not subscriptable in Python 3.8. + + +# Used for sending raw binary data / streaming data in request bodies +# e.g. for file uploads without multipart encoding +BinaryTypes = Union[bytes, bytearray, IO[bytes], Iterable[bytes]] +AsyncBinaryTypes = Union[bytes, bytearray, IO[bytes], AsyncIterable[bytes]] + FileTypes = Union[ # file (or bytes) FileContent, diff --git a/tests/test_client.py b/tests/test_client.py index 7e28add..92ea2da 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -8,10 +8,11 @@ import json import asyncio import inspect +import dataclasses import tracemalloc -from typing import Any, Union, cast +from typing import Any, Union, TypeVar, Callable, Iterable, Iterator, Optional, Coroutine, cast from unittest import mock -from typing_extensions import Literal +from typing_extensions import Literal, AsyncIterator, override import httpx import pytest @@ -36,6 +37,7 @@ from .utils import update_env +T = TypeVar("T") base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") api_key = "My API Key" @@ -50,6 +52,57 @@ def _low_retry_timeout(*_args: Any, **_kwargs: Any) -> float: return 0.1 +def mirror_request_content(request: httpx.Request) -> httpx.Response: + return httpx.Response(200, content=request.content) + + +# note: we can't use the httpx.MockTransport class as it consumes the request +# body itself, which means we can't test that the body is read lazily +class MockTransport(httpx.BaseTransport, httpx.AsyncBaseTransport): + def __init__( + self, + handler: Callable[[httpx.Request], httpx.Response] + | Callable[[httpx.Request], Coroutine[Any, Any, httpx.Response]], + ) -> None: + self.handler = handler + + @override + def handle_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert not inspect.iscoroutinefunction(self.handler), "handler must not be a coroutine function" + assert inspect.isfunction(self.handler), "handler must be a function" + return self.handler(request) + + @override + async def handle_async_request( + self, + request: httpx.Request, + ) -> httpx.Response: + assert inspect.iscoroutinefunction(self.handler), "handler must be a coroutine function" + return await self.handler(request) + + +@dataclasses.dataclass +class Counter: + value: int = 0 + + +def _make_sync_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> Iterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + +async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] = None) -> AsyncIterator[T]: + for item in iterable: + if counter: + counter.value += 1 + yield item + + def _get_open_connections(client: WarpAPI | AsyncWarpAPI) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -500,6 +553,70 @@ def test_multipart_repeating_array(self, client: WarpAPI) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload(self, respx_mock: MockRouter, client: WarpAPI) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + def test_binary_content_upload_with_iterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_sync_iterator([file_content], counter=counter) + + def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=request.read()) + + with WarpAPI( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.Client(transport=MockTransport(handler=mock_handler)), + ) as client: + response = client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: WarpAPI) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + @pytest.mark.respx(base_url=base_url) def test_basic_union_response(self, respx_mock: MockRouter, client: WarpAPI) -> None: class Model1(BaseModel): @@ -1323,6 +1440,72 @@ def test_multipart_repeating_array(self, async_client: AsyncWarpAPI) -> None: b"", ] + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncWarpAPI) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + response = await async_client.post( + "/upload", + content=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + + async def test_binary_content_upload_with_asynciterator(self) -> None: + file_content = b"Hello, this is a test file." + counter = Counter() + iterator = _make_async_iterator([file_content], counter=counter) + + async def mock_handler(request: httpx.Request) -> httpx.Response: + assert counter.value == 0, "the request body should not have been read" + return httpx.Response(200, content=await request.aread()) + + async with AsyncWarpAPI( + base_url=base_url, + api_key=api_key, + _strict_response_validation=True, + http_client=httpx.AsyncClient(transport=MockTransport(handler=mock_handler)), + ) as client: + response = await client.post( + "/upload", + content=iterator, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + assert counter.value == 1 + + @pytest.mark.respx(base_url=base_url) + async def test_binary_content_upload_with_body_is_deprecated( + self, respx_mock: MockRouter, async_client: AsyncWarpAPI + ) -> None: + respx_mock.post("/upload").mock(side_effect=mirror_request_content) + + file_content = b"Hello, this is a test file." + + with pytest.deprecated_call( + match="Passing raw bytes as `body` is deprecated and will be removed in a future version. Please pass raw bytes via the `content` parameter instead." + ): + response = await async_client.post( + "/upload", + body=file_content, + cast_to=httpx.Response, + options={"headers": {"Content-Type": "application/octet-stream"}}, + ) + + assert response.status_code == 200 + assert response.request.headers["Content-Type"] == "application/octet-stream" + assert response.content == file_content + @pytest.mark.respx(base_url=base_url) async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncWarpAPI) -> None: class Model1(BaseModel): From fd0a90fae32b8a7e352217e3672e63b9b4dbc594 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 18:29:04 +0000 Subject: [PATCH 03/11] chore(internal): update `actions/checkout` version --- .github/workflows/ci.yml | 6 +++--- .github/workflows/publish-pypi.yml | 2 +- .github/workflows/release-doctor.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a266ab6..82d2167 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/warp-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v5 @@ -41,7 +41,7 @@ jobs: id-token: write runs-on: ${{ github.repository == 'stainless-sdks/warp-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v5 @@ -75,7 +75,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/warp-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v5 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 7cc7fc0..0913bfa 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install uv uses: astral-sh/setup-uv@v5 diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 39d6102..dbc24df 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'warpdotdev/warp-sdk-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check release environment run: | From 71adb45481ca3248d892e62ff767b9ea011e7f21 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 23:39:47 +0000 Subject: [PATCH 04/11] feat(api): created at filter in list view --- .stats.yml | 4 ++-- src/warp_agent_sdk/resources/agent/agent.py | 8 ++++++++ src/warp_agent_sdk/types/agent/task_item.py | 5 ++++- src/warp_agent_sdk/types/agent_run_params.py | 3 +++ src/warp_agent_sdk/types/ambient_agent_config.py | 4 ++-- src/warp_agent_sdk/types/ambient_agent_config_param.py | 4 ++-- tests/api_resources/test_agent.py | 2 ++ 7 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4fd2e98..ad775c5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-c4c5f89f67a73e4d17377d2b96fc201a63cd5458cbebaa23e78f92b59b90cc5b.yml -openapi_spec_hash: 931c6189a4fc4ee320963646b1b7edbe +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-678afb4efd7b655ded420e66621722701b018bcfce2423ec9beb4e575108c05c.yml +openapi_spec_hash: 4f8537526fc20ec33facbc86e1abd571 config_hash: 9c6b93a5f4b658b946f0ab7fcfedbaa3 diff --git a/src/warp_agent_sdk/resources/agent/agent.py b/src/warp_agent_sdk/resources/agent/agent.py index f3635b9..bf378e5 100644 --- a/src/warp_agent_sdk/resources/agent/agent.py +++ b/src/warp_agent_sdk/resources/agent/agent.py @@ -59,6 +59,7 @@ def run( *, prompt: str, config: AmbientAgentConfigParam | Omit = omit, + team: bool | Omit = omit, title: 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. @@ -77,6 +78,8 @@ def run( config: Configuration for an ambient agent task + team: Make the task visible to all team members, not only the calling user + title: Custom title for the task (auto-generated if not provided) extra_headers: Send extra headers @@ -93,6 +96,7 @@ def run( { "prompt": prompt, "config": config, + "team": team, "title": title, }, agent_run_params.AgentRunParams, @@ -133,6 +137,7 @@ async def run( *, prompt: str, config: AmbientAgentConfigParam | Omit = omit, + team: bool | Omit = omit, title: 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. @@ -151,6 +156,8 @@ async def run( config: Configuration for an ambient agent task + team: Make the task visible to all team members, not only the calling user + title: Custom title for the task (auto-generated if not provided) extra_headers: Send extra headers @@ -167,6 +174,7 @@ async def run( { "prompt": prompt, "config": config, + "team": team, "title": title, }, agent_run_params.AgentRunParams, diff --git a/src/warp_agent_sdk/types/agent/task_item.py b/src/warp_agent_sdk/types/agent/task_item.py index d33c538..b76b132 100644 --- a/src/warp_agent_sdk/types/agent/task_item.py +++ b/src/warp_agent_sdk/types/agent/task_item.py @@ -67,10 +67,13 @@ class TaskItem(BaseModel): """Source that created the task: - LINEAR: Created from Linear integration - - API: Created via the public API + - API: Created via the Warp API - SLACK: Created from Slack integration - LOCAL: Created from local CLI/app - SCHEDULED_AGENT: Created by a scheduled agent """ + started_at: Optional[datetime] = None + """Timestamp when the agent started working on the task (RFC3339)""" + status_message: Optional[StatusMessage] = None diff --git a/src/warp_agent_sdk/types/agent_run_params.py b/src/warp_agent_sdk/types/agent_run_params.py index 4220f53..92e51d7 100644 --- a/src/warp_agent_sdk/types/agent_run_params.py +++ b/src/warp_agent_sdk/types/agent_run_params.py @@ -16,5 +16,8 @@ class AgentRunParams(TypedDict, total=False): config: AmbientAgentConfigParam """Configuration for an ambient agent task""" + team: bool + """Make the task visible to all team members, not only the calling user""" + title: str """Custom title for the task (auto-generated if not provided)""" diff --git a/src/warp_agent_sdk/types/ambient_agent_config.py b/src/warp_agent_sdk/types/ambient_agent_config.py index ae3c545..fb80060 100644 --- a/src/warp_agent_sdk/types/ambient_agent_config.py +++ b/src/warp_agent_sdk/types/ambient_agent_config.py @@ -41,13 +41,13 @@ class AmbientAgentConfig(BaseModel): """Custom base prompt for the agent""" environment_id: Optional[str] = None - """UID of a CloudEnvironment GSO to use""" + """UID of the environment to run the task in""" mcp_servers: Optional[Dict[str, McpServers]] = None """Map of MCP server configurations by name""" api_model_id: Optional[str] = FieldInfo(alias="model_id", default=None) - """LLM model to use (uses workspace default if not specified)""" + """LLM model to use (uses team default if not specified)""" name: Optional[str] = None """Config name for searchability and traceability""" diff --git a/src/warp_agent_sdk/types/ambient_agent_config_param.py b/src/warp_agent_sdk/types/ambient_agent_config_param.py index 28bcb82..32d0ddc 100644 --- a/src/warp_agent_sdk/types/ambient_agent_config_param.py +++ b/src/warp_agent_sdk/types/ambient_agent_config_param.py @@ -42,13 +42,13 @@ class AmbientAgentConfigParam(TypedDict, total=False): """Custom base prompt for the agent""" environment_id: str - """UID of a CloudEnvironment GSO to use""" + """UID of the environment to run the task in""" mcp_servers: Dict[str, McpServers] """Map of MCP server configurations by name""" model_id: str - """LLM model to use (uses workspace default if not specified)""" + """LLM model to use (uses team default if not specified)""" name: str """Config name for searchability and traceability""" diff --git a/tests/api_resources/test_agent.py b/tests/api_resources/test_agent.py index fcb0851..f873025 100644 --- a/tests/api_resources/test_agent.py +++ b/tests/api_resources/test_agent.py @@ -46,6 +46,7 @@ def test_method_run_with_all_params(self, client: WarpAPI) -> None: "model_id": "model_id", "name": "name", }, + team=True, title="title", ) assert_matches_type(AgentRunResponse, agent, path=["response"]) @@ -111,6 +112,7 @@ async def test_method_run_with_all_params(self, async_client: AsyncWarpAPI) -> N "model_id": "model_id", "name": "name", }, + team=True, title="title", ) assert_matches_type(AgentRunResponse, agent, path=["response"]) From fe8c5b3f2da07a415ecd21d0f1ae5985e45f1a4d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 19:38:18 +0000 Subject: [PATCH 05/11] feat(api)!: catch up openapi, rename tasks -> runs --- .stats.yml | 6 +- README.md | 8 +- api.md | 8 +- .../resources/agent/__init__.py | 28 ++-- src/warp_agent_sdk/resources/agent/agent.py | 54 +++---- .../resources/agent/{tasks.py => runs.py} | 148 +++++++++--------- src/warp_agent_sdk/types/agent/__init__.py | 10 +- src/warp_agent_sdk/types/agent/run_item.py | 110 +++++++++++++ ...task_list_params.py => run_list_params.py} | 22 +-- ..._list_response.py => run_list_response.py} | 8 +- ...task_source_type.py => run_source_type.py} | 4 +- .../agent/{task_state.py => run_state.py} | 4 +- src/warp_agent_sdk/types/agent/task_item.py | 79 ---------- src/warp_agent_sdk/types/agent_run_params.py | 6 +- .../types/agent_run_response.py | 24 +-- .../types/ambient_agent_config.py | 4 +- .../types/ambient_agent_config_param.py | 4 +- .../agent/{test_tasks.py => test_runs.py} | 98 ++++++------ 18 files changed, 328 insertions(+), 297 deletions(-) rename src/warp_agent_sdk/resources/agent/{tasks.py => runs.py} (70%) create mode 100644 src/warp_agent_sdk/types/agent/run_item.py rename src/warp_agent_sdk/types/agent/{task_list_params.py => run_list_params.py} (63%) rename src/warp_agent_sdk/types/agent/{task_list_response.py => run_list_response.py} (73%) rename src/warp_agent_sdk/types/agent/{task_source_type.py => run_source_type.py} (53%) rename src/warp_agent_sdk/types/agent/{task_state.py => run_state.py} (52%) delete mode 100644 src/warp_agent_sdk/types/agent/task_item.py rename tests/api_resources/agent/{test_tasks.py => test_runs.py} (67%) diff --git a/.stats.yml b/.stats.yml index ad775c5..b964fdf 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-678afb4efd7b655ded420e66621722701b018bcfce2423ec9beb4e575108c05c.yml -openapi_spec_hash: 4f8537526fc20ec33facbc86e1abd571 -config_hash: 9c6b93a5f4b658b946f0ab7fcfedbaa3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-24029c9a3bb61d8ed8807686035c52060f97505d57ad827ae4debdec426ea0a0.yml +openapi_spec_hash: ffe58e3dd2d1c5c1552af6c0330e6fb1 +config_hash: 386210f0e52fc8dd00b78e25011b9980 diff --git a/README.md b/README.md index dafd2a7..22c4697 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ client = WarpAPI( response = client.agent.run( prompt="Fix the bug in auth.go", ) -print(response.task_id) +print(response.run_id) ``` ### Using environments and configuration @@ -119,7 +119,7 @@ async def main() -> None: response = await client.agent.run( prompt="Fix the bug in auth.go", ) - print(response.task_id) + print(response.run_id) asyncio.run(main()) @@ -155,7 +155,7 @@ async def main() -> None: response = await client.agent.run( prompt="Fix the bug in auth.go", ) - print(response.task_id) + print(response.run_id) asyncio.run(main()) @@ -321,7 +321,7 @@ response = client.agent.with_raw_response.run( print(response.headers.get('X-My-Header')) agent = response.parse() # get the object that `agent.run()` would have returned -print(agent.task_id) +print(agent.run_id) ``` These methods return an [`APIResponse`](https://github.com/warpdotdev/warp-sdk-python/tree/main/src/warp_agent_sdk/_response.py) object. diff --git a/api.md b/api.md index f438daa..b840946 100644 --- a/api.md +++ b/api.md @@ -10,15 +10,15 @@ Methods: - client.agent.run(\*\*params) -> AgentRunResponse -## Tasks +## Runs Types: ```python -from warp_agent_sdk.types.agent import TaskItem, TaskSourceType, TaskState, TaskListResponse +from warp_agent_sdk.types.agent import RunItem, RunSourceType, RunState, RunListResponse ``` Methods: -- client.agent.tasks.retrieve(task_id) -> TaskItem -- client.agent.tasks.list(\*\*params) -> TaskListResponse +- client.agent.runs.retrieve(run_id) -> RunItem +- client.agent.runs.list(\*\*params) -> RunListResponse diff --git a/src/warp_agent_sdk/resources/agent/__init__.py b/src/warp_agent_sdk/resources/agent/__init__.py index 481bf57..71feab7 100644 --- a/src/warp_agent_sdk/resources/agent/__init__.py +++ b/src/warp_agent_sdk/resources/agent/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .runs import ( + RunsResource, + AsyncRunsResource, + RunsResourceWithRawResponse, + AsyncRunsResourceWithRawResponse, + RunsResourceWithStreamingResponse, + AsyncRunsResourceWithStreamingResponse, +) from .agent import ( AgentResource, AsyncAgentResource, @@ -8,22 +16,14 @@ AgentResourceWithStreamingResponse, AsyncAgentResourceWithStreamingResponse, ) -from .tasks import ( - TasksResource, - AsyncTasksResource, - TasksResourceWithRawResponse, - AsyncTasksResourceWithRawResponse, - TasksResourceWithStreamingResponse, - AsyncTasksResourceWithStreamingResponse, -) __all__ = [ - "TasksResource", - "AsyncTasksResource", - "TasksResourceWithRawResponse", - "AsyncTasksResourceWithRawResponse", - "TasksResourceWithStreamingResponse", - "AsyncTasksResourceWithStreamingResponse", + "RunsResource", + "AsyncRunsResource", + "RunsResourceWithRawResponse", + "AsyncRunsResourceWithRawResponse", + "RunsResourceWithStreamingResponse", + "AsyncRunsResourceWithStreamingResponse", "AgentResource", "AsyncAgentResource", "AgentResourceWithRawResponse", diff --git a/src/warp_agent_sdk/resources/agent/agent.py b/src/warp_agent_sdk/resources/agent/agent.py index bf378e5..7914f7a 100644 --- a/src/warp_agent_sdk/resources/agent/agent.py +++ b/src/warp_agent_sdk/resources/agent/agent.py @@ -4,13 +4,13 @@ import httpx -from .tasks import ( - TasksResource, - AsyncTasksResource, - TasksResourceWithRawResponse, - AsyncTasksResourceWithRawResponse, - TasksResourceWithStreamingResponse, - AsyncTasksResourceWithStreamingResponse, +from .runs import ( + RunsResource, + AsyncRunsResource, + RunsResourceWithRawResponse, + AsyncRunsResourceWithRawResponse, + RunsResourceWithStreamingResponse, + AsyncRunsResourceWithStreamingResponse, ) from ...types import agent_run_params from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given @@ -32,8 +32,8 @@ class AgentResource(SyncAPIResource): @cached_property - def tasks(self) -> TasksResource: - return TasksResource(self._client) + def runs(self) -> RunsResource: + return RunsResource(self._client) @cached_property def with_raw_response(self) -> AgentResourceWithRawResponse: @@ -71,16 +71,16 @@ def run( """Spawn an ambient agent with a prompt and optional configuration. The agent will - be queued for execution and assigned a unique task ID. + be queued for execution and assigned a unique run ID. Args: prompt: The prompt/instruction for the agent to execute - config: Configuration for an ambient agent task + config: Configuration for an ambient agent run - team: Make the task visible to all team members, not only the calling user + team: Make the run visible to all team members, not only the calling user - title: Custom title for the task (auto-generated if not provided) + title: Custom title for the run (auto-generated if not provided) extra_headers: Send extra headers @@ -110,8 +110,8 @@ def run( class AsyncAgentResource(AsyncAPIResource): @cached_property - def tasks(self) -> AsyncTasksResource: - return AsyncTasksResource(self._client) + def runs(self) -> AsyncRunsResource: + return AsyncRunsResource(self._client) @cached_property def with_raw_response(self) -> AsyncAgentResourceWithRawResponse: @@ -149,16 +149,16 @@ async def run( """Spawn an ambient agent with a prompt and optional configuration. The agent will - be queued for execution and assigned a unique task ID. + be queued for execution and assigned a unique run ID. Args: prompt: The prompt/instruction for the agent to execute - config: Configuration for an ambient agent task + config: Configuration for an ambient agent run - team: Make the task visible to all team members, not only the calling user + team: Make the run visible to all team members, not only the calling user - title: Custom title for the task (auto-generated if not provided) + title: Custom title for the run (auto-generated if not provided) extra_headers: Send extra headers @@ -195,8 +195,8 @@ def __init__(self, agent: AgentResource) -> None: ) @cached_property - def tasks(self) -> TasksResourceWithRawResponse: - return TasksResourceWithRawResponse(self._agent.tasks) + def runs(self) -> RunsResourceWithRawResponse: + return RunsResourceWithRawResponse(self._agent.runs) class AsyncAgentResourceWithRawResponse: @@ -208,8 +208,8 @@ def __init__(self, agent: AsyncAgentResource) -> None: ) @cached_property - def tasks(self) -> AsyncTasksResourceWithRawResponse: - return AsyncTasksResourceWithRawResponse(self._agent.tasks) + def runs(self) -> AsyncRunsResourceWithRawResponse: + return AsyncRunsResourceWithRawResponse(self._agent.runs) class AgentResourceWithStreamingResponse: @@ -221,8 +221,8 @@ def __init__(self, agent: AgentResource) -> None: ) @cached_property - def tasks(self) -> TasksResourceWithStreamingResponse: - return TasksResourceWithStreamingResponse(self._agent.tasks) + def runs(self) -> RunsResourceWithStreamingResponse: + return RunsResourceWithStreamingResponse(self._agent.runs) class AsyncAgentResourceWithStreamingResponse: @@ -234,5 +234,5 @@ def __init__(self, agent: AsyncAgentResource) -> None: ) @cached_property - def tasks(self) -> AsyncTasksResourceWithStreamingResponse: - return AsyncTasksResourceWithStreamingResponse(self._agent.tasks) + def runs(self) -> AsyncRunsResourceWithStreamingResponse: + return AsyncRunsResourceWithStreamingResponse(self._agent.runs) diff --git a/src/warp_agent_sdk/resources/agent/tasks.py b/src/warp_agent_sdk/resources/agent/runs.py similarity index 70% rename from src/warp_agent_sdk/resources/agent/tasks.py rename to src/warp_agent_sdk/resources/agent/runs.py index ba77527..c1c0162 100644 --- a/src/warp_agent_sdk/resources/agent/tasks.py +++ b/src/warp_agent_sdk/resources/agent/runs.py @@ -17,39 +17,39 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ...types.agent import TaskSourceType, task_list_params +from ...types.agent import RunSourceType, run_list_params from ..._base_client import make_request_options -from ...types.agent.task_item import TaskItem -from ...types.agent.task_state import TaskState -from ...types.agent.task_source_type import TaskSourceType -from ...types.agent.task_list_response import TaskListResponse +from ...types.agent.run_item import RunItem +from ...types.agent.run_state import RunState +from ...types.agent.run_source_type import RunSourceType +from ...types.agent.run_list_response import RunListResponse -__all__ = ["TasksResource", "AsyncTasksResource"] +__all__ = ["RunsResource", "AsyncRunsResource"] -class TasksResource(SyncAPIResource): +class RunsResource(SyncAPIResource): @cached_property - def with_raw_response(self) -> TasksResourceWithRawResponse: + def with_raw_response(self) -> RunsResourceWithRawResponse: """ 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/warpdotdev/warp-sdk-python#accessing-raw-response-data-eg-headers """ - return TasksResourceWithRawResponse(self) + return RunsResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> TasksResourceWithStreamingResponse: + def with_streaming_response(self) -> RunsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. For more information, see https://www.github.com/warpdotdev/warp-sdk-python#with_streaming_response """ - return TasksResourceWithStreamingResponse(self) + return RunsResourceWithStreamingResponse(self) def retrieve( self, - task_id: str, + run_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. @@ -57,9 +57,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> TaskItem: + ) -> RunItem: """ - Retrieve detailed information about a specific agent task, including the full + Retrieve detailed information about a specific agent run, including the full prompt, session link, and resolved configuration. Args: @@ -71,14 +71,14 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not task_id: - raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") return self._get( - f"/agent/tasks/{task_id}", + f"/agent/runs/{run_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=TaskItem, + cast_to=RunItem, ) def list( @@ -91,16 +91,16 @@ def list( cursor: str | Omit = omit, limit: int | Omit = omit, model_id: str | Omit = omit, - source: TaskSourceType | Omit = omit, - state: List[TaskState] | Omit = omit, + source: RunSourceType | Omit = omit, + state: List[RunState] | 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, - ) -> TaskListResponse: - """Retrieve a paginated list of agent tasks with optional filtering. + ) -> RunListResponse: + """Retrieve a paginated list of agent runs with optional filtering. Results are ordered by creation time (newest first). @@ -108,21 +108,21 @@ def list( Args: config_name: Filter by agent config name - created_after: Filter tasks created after this timestamp (RFC3339 format) + created_after: Filter runs created after this timestamp (RFC3339 format) - created_before: Filter tasks created before this timestamp (RFC3339 format) + created_before: Filter runs created before this timestamp (RFC3339 format) creator: Filter by creator UID (user or service account) cursor: Pagination cursor from previous response - limit: Maximum number of tasks to return + limit: Maximum number of runs to return model_id: Filter by model ID - source: Filter by task source type + source: Filter by run source type - state: Filter by task state. Can be specified multiple times to match any of the given + state: Filter by run state. Can be specified multiple times to match any of the given states. extra_headers: Send extra headers @@ -134,7 +134,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get( - "/agent/tasks", + "/agent/runs", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -152,36 +152,36 @@ def list( "source": source, "state": state, }, - task_list_params.TaskListParams, + run_list_params.RunListParams, ), ), - cast_to=TaskListResponse, + cast_to=RunListResponse, ) -class AsyncTasksResource(AsyncAPIResource): +class AsyncRunsResource(AsyncAPIResource): @cached_property - def with_raw_response(self) -> AsyncTasksResourceWithRawResponse: + def with_raw_response(self) -> AsyncRunsResourceWithRawResponse: """ 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/warpdotdev/warp-sdk-python#accessing-raw-response-data-eg-headers """ - return AsyncTasksResourceWithRawResponse(self) + return AsyncRunsResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> AsyncTasksResourceWithStreamingResponse: + def with_streaming_response(self) -> AsyncRunsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. For more information, see https://www.github.com/warpdotdev/warp-sdk-python#with_streaming_response """ - return AsyncTasksResourceWithStreamingResponse(self) + return AsyncRunsResourceWithStreamingResponse(self) async def retrieve( self, - task_id: str, + run_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. @@ -189,9 +189,9 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> TaskItem: + ) -> RunItem: """ - Retrieve detailed information about a specific agent task, including the full + Retrieve detailed information about a specific agent run, including the full prompt, session link, and resolved configuration. Args: @@ -203,14 +203,14 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not task_id: - raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") return await self._get( - f"/agent/tasks/{task_id}", + f"/agent/runs/{run_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=TaskItem, + cast_to=RunItem, ) async def list( @@ -223,16 +223,16 @@ async def list( cursor: str | Omit = omit, limit: int | Omit = omit, model_id: str | Omit = omit, - source: TaskSourceType | Omit = omit, - state: List[TaskState] | Omit = omit, + source: RunSourceType | Omit = omit, + state: List[RunState] | 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, - ) -> TaskListResponse: - """Retrieve a paginated list of agent tasks with optional filtering. + ) -> RunListResponse: + """Retrieve a paginated list of agent runs with optional filtering. Results are ordered by creation time (newest first). @@ -240,21 +240,21 @@ async def list( Args: config_name: Filter by agent config name - created_after: Filter tasks created after this timestamp (RFC3339 format) + created_after: Filter runs created after this timestamp (RFC3339 format) - created_before: Filter tasks created before this timestamp (RFC3339 format) + created_before: Filter runs created before this timestamp (RFC3339 format) creator: Filter by creator UID (user or service account) cursor: Pagination cursor from previous response - limit: Maximum number of tasks to return + limit: Maximum number of runs to return model_id: Filter by model ID - source: Filter by task source type + source: Filter by run source type - state: Filter by task state. Can be specified multiple times to match any of the given + state: Filter by run state. Can be specified multiple times to match any of the given states. extra_headers: Send extra headers @@ -266,7 +266,7 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ return await self._get( - "/agent/tasks", + "/agent/runs", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -284,56 +284,56 @@ async def list( "source": source, "state": state, }, - task_list_params.TaskListParams, + run_list_params.RunListParams, ), ), - cast_to=TaskListResponse, + cast_to=RunListResponse, ) -class TasksResourceWithRawResponse: - def __init__(self, tasks: TasksResource) -> None: - self._tasks = tasks +class RunsResourceWithRawResponse: + def __init__(self, runs: RunsResource) -> None: + self._runs = runs self.retrieve = to_raw_response_wrapper( - tasks.retrieve, + runs.retrieve, ) self.list = to_raw_response_wrapper( - tasks.list, + runs.list, ) -class AsyncTasksResourceWithRawResponse: - def __init__(self, tasks: AsyncTasksResource) -> None: - self._tasks = tasks +class AsyncRunsResourceWithRawResponse: + def __init__(self, runs: AsyncRunsResource) -> None: + self._runs = runs self.retrieve = async_to_raw_response_wrapper( - tasks.retrieve, + runs.retrieve, ) self.list = async_to_raw_response_wrapper( - tasks.list, + runs.list, ) -class TasksResourceWithStreamingResponse: - def __init__(self, tasks: TasksResource) -> None: - self._tasks = tasks +class RunsResourceWithStreamingResponse: + def __init__(self, runs: RunsResource) -> None: + self._runs = runs self.retrieve = to_streamed_response_wrapper( - tasks.retrieve, + runs.retrieve, ) self.list = to_streamed_response_wrapper( - tasks.list, + runs.list, ) -class AsyncTasksResourceWithStreamingResponse: - def __init__(self, tasks: AsyncTasksResource) -> None: - self._tasks = tasks +class AsyncRunsResourceWithStreamingResponse: + def __init__(self, runs: AsyncRunsResource) -> None: + self._runs = runs self.retrieve = async_to_streamed_response_wrapper( - tasks.retrieve, + runs.retrieve, ) self.list = async_to_streamed_response_wrapper( - tasks.list, + runs.list, ) diff --git a/src/warp_agent_sdk/types/agent/__init__.py b/src/warp_agent_sdk/types/agent/__init__.py index bbde35a..ee07ed6 100644 --- a/src/warp_agent_sdk/types/agent/__init__.py +++ b/src/warp_agent_sdk/types/agent/__init__.py @@ -2,8 +2,8 @@ from __future__ import annotations -from .task_item import TaskItem as TaskItem -from .task_state import TaskState as TaskState -from .task_list_params import TaskListParams as TaskListParams -from .task_source_type import TaskSourceType as TaskSourceType -from .task_list_response import TaskListResponse as TaskListResponse +from .run_item import RunItem as RunItem +from .run_state import RunState as RunState +from .run_list_params import RunListParams as RunListParams +from .run_source_type import RunSourceType as RunSourceType +from .run_list_response import RunListResponse as RunListResponse diff --git a/src/warp_agent_sdk/types/agent/run_item.py b/src/warp_agent_sdk/types/agent/run_item.py new file mode 100644 index 0000000..103e3a6 --- /dev/null +++ b/src/warp_agent_sdk/types/agent/run_item.py @@ -0,0 +1,110 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from ..._models import BaseModel +from .run_state import RunState +from .run_source_type import RunSourceType +from ..ambient_agent_config import AmbientAgentConfig + +__all__ = ["RunItem", "Creator", "RequestUsage", "StatusMessage"] + + +class Creator(BaseModel): + display_name: Optional[str] = None + """Display name of the creator""" + + photo_url: Optional[str] = None + """URL to the creator's photo""" + + type: Optional[Literal["user", "service_account"]] = None + """Type of the creator principal""" + + uid: Optional[str] = None + """Unique identifier of the creator""" + + +class RequestUsage(BaseModel): + """Resource usage information for the run""" + + compute_cost: Optional[float] = None + """Cost of compute resources for the run""" + + inference_cost: Optional[float] = None + """Cost of LLM inference for the run""" + + +class StatusMessage(BaseModel): + message: Optional[str] = None + """Human-readable status message""" + + +class RunItem(BaseModel): + created_at: datetime + """Timestamp when the run was created (RFC3339)""" + + prompt: str + """The prompt/instruction for the agent""" + + run_id: str + """Unique identifier for the run""" + + state: RunState + """Current state of the run: + + - QUEUED: Run is waiting to be picked up + - PENDING: Run is being prepared + - CLAIMED: Run has been claimed by a worker + - INPROGRESS: Run is actively being executed + - SUCCEEDED: Run completed successfully + - FAILED: Run failed + """ + + task_id: str + """Unique identifier for the task (typically matches run_id). + + Deprecated - use run_id instead. + """ + + title: str + """Human-readable title for the run""" + + updated_at: datetime + """Timestamp when the run was last updated (RFC3339)""" + + agent_config: Optional[AmbientAgentConfig] = None + """Configuration for an ambient agent run""" + + conversation_id: Optional[str] = None + """UUID of the conversation associated with the run""" + + creator: Optional[Creator] = None + + is_sandbox_running: Optional[bool] = None + """Whether the sandbox environment is currently running""" + + request_usage: Optional[RequestUsage] = None + """Resource usage information for the run""" + + session_id: Optional[str] = None + """UUID of the shared session (if available)""" + + session_link: Optional[str] = None + """URL to view the agent session""" + + source: Optional[RunSourceType] = None + """Source that created the run: + + - LINEAR: Created from Linear integration + - API: Created via the Warp API + - SLACK: Created from Slack integration + - LOCAL: Created from local CLI/app + - SCHEDULED_AGENT: Created by a scheduled agent + """ + + started_at: Optional[datetime] = None + """Timestamp when the agent started working on the run (RFC3339)""" + + status_message: Optional[StatusMessage] = None diff --git a/src/warp_agent_sdk/types/agent/task_list_params.py b/src/warp_agent_sdk/types/agent/run_list_params.py similarity index 63% rename from src/warp_agent_sdk/types/agent/task_list_params.py rename to src/warp_agent_sdk/types/agent/run_list_params.py index 5a4e28e..d7ef57c 100644 --- a/src/warp_agent_sdk/types/agent/task_list_params.py +++ b/src/warp_agent_sdk/types/agent/run_list_params.py @@ -7,21 +7,21 @@ from typing_extensions import Annotated, TypedDict from ..._utils import PropertyInfo -from .task_state import TaskState -from .task_source_type import TaskSourceType +from .run_state import RunState +from .run_source_type import RunSourceType -__all__ = ["TaskListParams"] +__all__ = ["RunListParams"] -class TaskListParams(TypedDict, total=False): +class RunListParams(TypedDict, total=False): config_name: str """Filter by agent config name""" created_after: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """Filter tasks created after this timestamp (RFC3339 format)""" + """Filter runs created after this timestamp (RFC3339 format)""" created_before: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """Filter tasks created before this timestamp (RFC3339 format)""" + """Filter runs created before this timestamp (RFC3339 format)""" creator: str """Filter by creator UID (user or service account)""" @@ -30,16 +30,16 @@ class TaskListParams(TypedDict, total=False): """Pagination cursor from previous response""" limit: int - """Maximum number of tasks to return""" + """Maximum number of runs to return""" model_id: str """Filter by model ID""" - source: TaskSourceType - """Filter by task source type""" + source: RunSourceType + """Filter by run source type""" - state: List[TaskState] - """Filter by task state. + state: List[RunState] + """Filter by run state. Can be specified multiple times to match any of the given states. """ diff --git a/src/warp_agent_sdk/types/agent/task_list_response.py b/src/warp_agent_sdk/types/agent/run_list_response.py similarity index 73% rename from src/warp_agent_sdk/types/agent/task_list_response.py rename to src/warp_agent_sdk/types/agent/run_list_response.py index 64bc2e9..db1beb9 100644 --- a/src/warp_agent_sdk/types/agent/task_list_response.py +++ b/src/warp_agent_sdk/types/agent/run_list_response.py @@ -2,10 +2,10 @@ from typing import List, Optional +from .run_item import RunItem from ..._models import BaseModel -from .task_item import TaskItem -__all__ = ["TaskListResponse", "PageInfo"] +__all__ = ["RunListResponse", "PageInfo"] class PageInfo(BaseModel): @@ -16,7 +16,7 @@ class PageInfo(BaseModel): """Opaque cursor for fetching the next page""" -class TaskListResponse(BaseModel): +class RunListResponse(BaseModel): page_info: PageInfo - tasks: List[TaskItem] + runs: List[RunItem] diff --git a/src/warp_agent_sdk/types/agent/task_source_type.py b/src/warp_agent_sdk/types/agent/run_source_type.py similarity index 53% rename from src/warp_agent_sdk/types/agent/task_source_type.py rename to src/warp_agent_sdk/types/agent/run_source_type.py index 702ab70..c1a4b52 100644 --- a/src/warp_agent_sdk/types/agent/task_source_type.py +++ b/src/warp_agent_sdk/types/agent/run_source_type.py @@ -2,6 +2,6 @@ from typing_extensions import Literal, TypeAlias -__all__ = ["TaskSourceType"] +__all__ = ["RunSourceType"] -TaskSourceType: TypeAlias = Literal["LINEAR", "API", "SLACK", "LOCAL", "SCHEDULED_AGENT"] +RunSourceType: TypeAlias = Literal["LINEAR", "API", "SLACK", "LOCAL", "SCHEDULED_AGENT"] diff --git a/src/warp_agent_sdk/types/agent/task_state.py b/src/warp_agent_sdk/types/agent/run_state.py similarity index 52% rename from src/warp_agent_sdk/types/agent/task_state.py rename to src/warp_agent_sdk/types/agent/run_state.py index 8584e25..af072aa 100644 --- a/src/warp_agent_sdk/types/agent/task_state.py +++ b/src/warp_agent_sdk/types/agent/run_state.py @@ -2,6 +2,6 @@ from typing_extensions import Literal, TypeAlias -__all__ = ["TaskState"] +__all__ = ["RunState"] -TaskState: TypeAlias = Literal["QUEUED", "PENDING", "CLAIMED", "INPROGRESS", "SUCCEEDED", "FAILED"] +RunState: TypeAlias = Literal["QUEUED", "PENDING", "CLAIMED", "INPROGRESS", "SUCCEEDED", "FAILED"] diff --git a/src/warp_agent_sdk/types/agent/task_item.py b/src/warp_agent_sdk/types/agent/task_item.py deleted file mode 100644 index b76b132..0000000 --- a/src/warp_agent_sdk/types/agent/task_item.py +++ /dev/null @@ -1,79 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from datetime import datetime -from typing_extensions import Literal - -from ..._models import BaseModel -from .task_state import TaskState -from .task_source_type import TaskSourceType -from ..ambient_agent_config import AmbientAgentConfig - -__all__ = ["TaskItem", "Creator", "StatusMessage"] - - -class Creator(BaseModel): - type: Optional[Literal["user", "service_account"]] = None - """Type of the creator principal""" - - uid: Optional[str] = None - """Unique identifier of the creator""" - - -class StatusMessage(BaseModel): - message: Optional[str] = None - """Human-readable status message""" - - -class TaskItem(BaseModel): - created_at: datetime - """Timestamp when the task was created (RFC3339)""" - - prompt: str - """The prompt/instruction for the agent""" - - state: TaskState - """Current state of the task: - - - QUEUED: Task is waiting to be picked up - - PENDING: Task is being prepared - - CLAIMED: Task has been claimed by a worker - - INPROGRESS: Task is actively being executed - - SUCCEEDED: Task completed successfully - - FAILED: Task failed - """ - - task_id: str - """Unique identifier for the task""" - - title: str - """Human-readable title for the task""" - - updated_at: datetime - """Timestamp when the task was last updated (RFC3339)""" - - agent_config: Optional[AmbientAgentConfig] = None - """Configuration for an ambient agent task""" - - creator: Optional[Creator] = None - - session_id: Optional[str] = None - """UUID of the shared session (if available)""" - - session_link: Optional[str] = None - """URL to view the agent session""" - - source: Optional[TaskSourceType] = None - """Source that created the task: - - - LINEAR: Created from Linear integration - - API: Created via the Warp API - - SLACK: Created from Slack integration - - LOCAL: Created from local CLI/app - - SCHEDULED_AGENT: Created by a scheduled agent - """ - - started_at: Optional[datetime] = None - """Timestamp when the agent started working on the task (RFC3339)""" - - status_message: Optional[StatusMessage] = None diff --git a/src/warp_agent_sdk/types/agent_run_params.py b/src/warp_agent_sdk/types/agent_run_params.py index 92e51d7..3761f66 100644 --- a/src/warp_agent_sdk/types/agent_run_params.py +++ b/src/warp_agent_sdk/types/agent_run_params.py @@ -14,10 +14,10 @@ class AgentRunParams(TypedDict, total=False): """The prompt/instruction for the agent to execute""" config: AmbientAgentConfigParam - """Configuration for an ambient agent task""" + """Configuration for an ambient agent run""" team: bool - """Make the task visible to all team members, not only the calling user""" + """Make the run visible to all team members, not only the calling user""" title: str - """Custom title for the task (auto-generated if not provided)""" + """Custom title for the run (auto-generated if not provided)""" diff --git a/src/warp_agent_sdk/types/agent_run_response.py b/src/warp_agent_sdk/types/agent_run_response.py index 878eb89..5134eac 100644 --- a/src/warp_agent_sdk/types/agent_run_response.py +++ b/src/warp_agent_sdk/types/agent_run_response.py @@ -1,22 +1,22 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .._models import BaseModel -from .agent.task_state import TaskState +from .agent.run_state import RunState __all__ = ["AgentRunResponse"] class AgentRunResponse(BaseModel): - state: TaskState - """Current state of the task: + run_id: str + """Unique identifier for the created run""" - - QUEUED: Task is waiting to be picked up - - PENDING: Task is being prepared - - CLAIMED: Task has been claimed by a worker - - INPROGRESS: Task is actively being executed - - SUCCEEDED: Task completed successfully - - FAILED: Task failed - """ + state: RunState + """Current state of the run: - task_id: str - """Unique identifier for the created task""" + - QUEUED: Run is waiting to be picked up + - PENDING: Run is being prepared + - CLAIMED: Run has been claimed by a worker + - INPROGRESS: Run is actively being executed + - SUCCEEDED: Run completed successfully + - FAILED: Run failed + """ diff --git a/src/warp_agent_sdk/types/ambient_agent_config.py b/src/warp_agent_sdk/types/ambient_agent_config.py index fb80060..32d67ff 100644 --- a/src/warp_agent_sdk/types/ambient_agent_config.py +++ b/src/warp_agent_sdk/types/ambient_agent_config.py @@ -35,13 +35,13 @@ class McpServers(BaseModel): class AmbientAgentConfig(BaseModel): - """Configuration for an ambient agent task""" + """Configuration for an ambient agent run""" base_prompt: Optional[str] = None """Custom base prompt for the agent""" environment_id: Optional[str] = None - """UID of the environment to run the task in""" + """UID of the environment to run the agent in""" mcp_servers: Optional[Dict[str, McpServers]] = None """Map of MCP server configurations by name""" diff --git a/src/warp_agent_sdk/types/ambient_agent_config_param.py b/src/warp_agent_sdk/types/ambient_agent_config_param.py index 32d0ddc..7a9e91c 100644 --- a/src/warp_agent_sdk/types/ambient_agent_config_param.py +++ b/src/warp_agent_sdk/types/ambient_agent_config_param.py @@ -36,13 +36,13 @@ class McpServers(TypedDict, total=False): class AmbientAgentConfigParam(TypedDict, total=False): - """Configuration for an ambient agent task""" + """Configuration for an ambient agent run""" base_prompt: str """Custom base prompt for the agent""" environment_id: str - """UID of the environment to run the task in""" + """UID of the environment to run the agent in""" mcp_servers: Dict[str, McpServers] """Map of MCP server configurations by name""" diff --git a/tests/api_resources/agent/test_tasks.py b/tests/api_resources/agent/test_runs.py similarity index 67% rename from tests/api_resources/agent/test_tasks.py rename to tests/api_resources/agent/test_runs.py index f5e4d52..d17cd50 100644 --- a/tests/api_resources/agent/test_tasks.py +++ b/tests/api_resources/agent/test_runs.py @@ -10,66 +10,66 @@ from tests.utils import assert_matches_type from warp_agent_sdk import WarpAPI, AsyncWarpAPI from warp_agent_sdk._utils import parse_datetime -from warp_agent_sdk.types.agent import TaskItem, TaskListResponse +from warp_agent_sdk.types.agent import RunItem, RunListResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -class TestTasks: +class TestRuns: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_retrieve(self, client: WarpAPI) -> None: - task = client.agent.tasks.retrieve( - "taskId", + run = client.agent.runs.retrieve( + "runId", ) - assert_matches_type(TaskItem, task, path=["response"]) + assert_matches_type(RunItem, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_retrieve(self, client: WarpAPI) -> None: - response = client.agent.tasks.with_raw_response.retrieve( - "taskId", + response = client.agent.runs.with_raw_response.retrieve( + "runId", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - task = response.parse() - assert_matches_type(TaskItem, task, path=["response"]) + run = response.parse() + assert_matches_type(RunItem, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_retrieve(self, client: WarpAPI) -> None: - with client.agent.tasks.with_streaming_response.retrieve( - "taskId", + with client.agent.runs.with_streaming_response.retrieve( + "runId", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - task = response.parse() - assert_matches_type(TaskItem, task, path=["response"]) + run = response.parse() + assert_matches_type(RunItem, run, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_path_params_retrieve(self, client: WarpAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_id` but received ''"): - client.agent.tasks.with_raw_response.retrieve( + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.agent.runs.with_raw_response.retrieve( "", ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: WarpAPI) -> None: - task = client.agent.tasks.list() - assert_matches_type(TaskListResponse, task, path=["response"]) + run = client.agent.runs.list() + assert_matches_type(RunListResponse, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_with_all_params(self, client: WarpAPI) -> None: - task = client.agent.tasks.list( + run = client.agent.runs.list( config_name="config_name", created_after=parse_datetime("2019-12-27T18:11:19.117Z"), created_before=parse_datetime("2019-12-27T18:11:19.117Z"), @@ -80,32 +80,32 @@ def test_method_list_with_all_params(self, client: WarpAPI) -> None: source="LINEAR", state=["QUEUED"], ) - assert_matches_type(TaskListResponse, task, path=["response"]) + assert_matches_type(RunListResponse, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_list(self, client: WarpAPI) -> None: - response = client.agent.tasks.with_raw_response.list() + response = client.agent.runs.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - task = response.parse() - assert_matches_type(TaskListResponse, task, path=["response"]) + run = response.parse() + assert_matches_type(RunListResponse, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_list(self, client: WarpAPI) -> None: - with client.agent.tasks.with_streaming_response.list() as response: + with client.agent.runs.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - task = response.parse() - assert_matches_type(TaskListResponse, task, path=["response"]) + run = response.parse() + assert_matches_type(RunListResponse, run, path=["response"]) assert cast(Any, response.is_closed) is True -class TestAsyncTasks: +class TestAsyncRuns: parametrize = pytest.mark.parametrize( "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) @@ -113,55 +113,55 @@ class TestAsyncTasks: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncWarpAPI) -> None: - task = await async_client.agent.tasks.retrieve( - "taskId", + run = await async_client.agent.runs.retrieve( + "runId", ) - assert_matches_type(TaskItem, task, path=["response"]) + assert_matches_type(RunItem, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_retrieve(self, async_client: AsyncWarpAPI) -> None: - response = await async_client.agent.tasks.with_raw_response.retrieve( - "taskId", + response = await async_client.agent.runs.with_raw_response.retrieve( + "runId", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - task = await response.parse() - assert_matches_type(TaskItem, task, path=["response"]) + run = await response.parse() + assert_matches_type(RunItem, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_retrieve(self, async_client: AsyncWarpAPI) -> None: - async with async_client.agent.tasks.with_streaming_response.retrieve( - "taskId", + async with async_client.agent.runs.with_streaming_response.retrieve( + "runId", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - task = await response.parse() - assert_matches_type(TaskItem, task, path=["response"]) + run = await response.parse() + assert_matches_type(RunItem, run, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_path_params_retrieve(self, async_client: AsyncWarpAPI) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_id` but received ''"): - await async_client.agent.tasks.with_raw_response.retrieve( + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.agent.runs.with_raw_response.retrieve( "", ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncWarpAPI) -> None: - task = await async_client.agent.tasks.list() - assert_matches_type(TaskListResponse, task, path=["response"]) + run = await async_client.agent.runs.list() + assert_matches_type(RunListResponse, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWarpAPI) -> None: - task = await async_client.agent.tasks.list( + run = await async_client.agent.runs.list( config_name="config_name", created_after=parse_datetime("2019-12-27T18:11:19.117Z"), created_before=parse_datetime("2019-12-27T18:11:19.117Z"), @@ -172,26 +172,26 @@ async def test_method_list_with_all_params(self, async_client: AsyncWarpAPI) -> source="LINEAR", state=["QUEUED"], ) - assert_matches_type(TaskListResponse, task, path=["response"]) + assert_matches_type(RunListResponse, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncWarpAPI) -> None: - response = await async_client.agent.tasks.with_raw_response.list() + response = await async_client.agent.runs.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - task = await response.parse() - assert_matches_type(TaskListResponse, task, path=["response"]) + run = await response.parse() + assert_matches_type(RunListResponse, run, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncWarpAPI) -> None: - async with async_client.agent.tasks.with_streaming_response.list() as response: + async with async_client.agent.runs.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - task = await response.parse() - assert_matches_type(TaskListResponse, task, path=["response"]) + run = await response.parse() + assert_matches_type(RunListResponse, run, path=["response"]) assert cast(Any, response.is_closed) is True From 713ae7d03ee5f04eeac42b9a213d96ccc91d06d7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:09:59 +0000 Subject: [PATCH 06/11] chore(ci): upgrade `actions/github-script` --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82d2167..3e89dae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: - name: Get GitHub OIDC Token if: github.repository == 'stainless-sdks/warp-api-python' id: github-oidc - uses: actions/github-script@v6 + uses: actions/github-script@v8 with: script: core.setOutput('github_token', await core.getIDToken()); From 4aa12253c3cd40bbd8460dfe6602fed5080554a4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:14:51 +0000 Subject: [PATCH 07/11] feat(api): add artifacts, worker_host, and new source types --- .stats.yml | 6 +-- api.md | 8 ++- src/warp_agent_sdk/resources/agent/runs.py | 8 +++ src/warp_agent_sdk/types/agent/__init__.py | 1 + .../types/agent/artifact_item.py | 54 +++++++++++++++++++ src/warp_agent_sdk/types/agent/run_item.py | 8 ++- .../types/agent/run_list_params.py | 3 ++ .../types/agent/run_source_type.py | 2 +- .../types/ambient_agent_config.py | 6 +++ .../types/ambient_agent_config_param.py | 6 +++ tests/api_resources/agent/test_runs.py | 2 + tests/api_resources/test_agent.py | 2 + 12 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 src/warp_agent_sdk/types/agent/artifact_item.py diff --git a/.stats.yml b/.stats.yml index b964fdf..468b580 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-24029c9a3bb61d8ed8807686035c52060f97505d57ad827ae4debdec426ea0a0.yml -openapi_spec_hash: ffe58e3dd2d1c5c1552af6c0330e6fb1 -config_hash: 386210f0e52fc8dd00b78e25011b9980 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-48ffcddda2e2febbe761bbb643e04c4d260c5b6ba6378297f1e7af9793dedaa5.yml +openapi_spec_hash: 1058ed679fc1f8a91d4f4c3136445dbc +config_hash: efa7e85b4732b72432e1b5828d2b6b9c diff --git a/api.md b/api.md index b840946..b2a484a 100644 --- a/api.md +++ b/api.md @@ -15,7 +15,13 @@ Methods: Types: ```python -from warp_agent_sdk.types.agent import RunItem, RunSourceType, RunState, RunListResponse +from warp_agent_sdk.types.agent import ( + ArtifactItem, + RunItem, + RunSourceType, + RunState, + RunListResponse, +) ``` Methods: diff --git a/src/warp_agent_sdk/resources/agent/runs.py b/src/warp_agent_sdk/resources/agent/runs.py index c1c0162..0f059c0 100644 --- a/src/warp_agent_sdk/resources/agent/runs.py +++ b/src/warp_agent_sdk/resources/agent/runs.py @@ -89,6 +89,7 @@ def list( created_before: Union[str, datetime] | Omit = omit, creator: str | Omit = omit, cursor: str | Omit = omit, + environment_id: str | Omit = omit, limit: int | Omit = omit, model_id: str | Omit = omit, source: RunSourceType | Omit = omit, @@ -116,6 +117,8 @@ def list( cursor: Pagination cursor from previous response + environment_id: Filter runs by environment ID + limit: Maximum number of runs to return model_id: Filter by model ID @@ -147,6 +150,7 @@ def list( "created_before": created_before, "creator": creator, "cursor": cursor, + "environment_id": environment_id, "limit": limit, "model_id": model_id, "source": source, @@ -221,6 +225,7 @@ async def list( created_before: Union[str, datetime] | Omit = omit, creator: str | Omit = omit, cursor: str | Omit = omit, + environment_id: str | Omit = omit, limit: int | Omit = omit, model_id: str | Omit = omit, source: RunSourceType | Omit = omit, @@ -248,6 +253,8 @@ async def list( cursor: Pagination cursor from previous response + environment_id: Filter runs by environment ID + limit: Maximum number of runs to return model_id: Filter by model ID @@ -279,6 +286,7 @@ async def list( "created_before": created_before, "creator": creator, "cursor": cursor, + "environment_id": environment_id, "limit": limit, "model_id": model_id, "source": source, diff --git a/src/warp_agent_sdk/types/agent/__init__.py b/src/warp_agent_sdk/types/agent/__init__.py index ee07ed6..2d7860c 100644 --- a/src/warp_agent_sdk/types/agent/__init__.py +++ b/src/warp_agent_sdk/types/agent/__init__.py @@ -4,6 +4,7 @@ from .run_item import RunItem as RunItem from .run_state import RunState as RunState +from .artifact_item import ArtifactItem as ArtifactItem from .run_list_params import RunListParams as RunListParams from .run_source_type import RunSourceType as RunSourceType from .run_list_response import RunListResponse as RunListResponse diff --git a/src/warp_agent_sdk/types/agent/artifact_item.py b/src/warp_agent_sdk/types/agent/artifact_item.py new file mode 100644 index 0000000..aab426d --- /dev/null +++ b/src/warp_agent_sdk/types/agent/artifact_item.py @@ -0,0 +1,54 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from ..._utils import PropertyInfo +from ..._models import BaseModel + +__all__ = ["ArtifactItem", "PlanArtifact", "PlanArtifactPlan", "PullRequestArtifact", "PullRequestArtifactPullRequest"] + + +class PlanArtifactPlan(BaseModel): + document_uid: str + """Unique identifier for the plan document""" + + notebook_uid: Optional[str] = None + """Unique identifier for the associated notebook""" + + title: Optional[str] = None + """Title of the plan""" + + +class PlanArtifact(BaseModel): + artifact_type: Literal["plan"] + """Type of the artifact""" + + created_at: datetime + """Timestamp when the artifact was created (RFC3339)""" + + plan: PlanArtifactPlan + + +class PullRequestArtifactPullRequest(BaseModel): + branch: str + """Branch name for the pull request""" + + url: str + """URL of the pull request""" + + +class PullRequestArtifact(BaseModel): + artifact_type: Literal["pull_request"] + """Type of the artifact""" + + created_at: datetime + """Timestamp when the artifact was created (RFC3339)""" + + pull_request: PullRequestArtifactPullRequest + + +ArtifactItem: TypeAlias = Annotated[ + Union[PlanArtifact, PullRequestArtifact], PropertyInfo(discriminator="artifact_type") +] diff --git a/src/warp_agent_sdk/types/agent/run_item.py b/src/warp_agent_sdk/types/agent/run_item.py index 103e3a6..c925ffb 100644 --- a/src/warp_agent_sdk/types/agent/run_item.py +++ b/src/warp_agent_sdk/types/agent/run_item.py @@ -1,11 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Optional from datetime import datetime from typing_extensions import Literal from ..._models import BaseModel from .run_state import RunState +from .artifact_item import ArtifactItem from .run_source_type import RunSourceType from ..ambient_agent_config import AmbientAgentConfig @@ -77,6 +78,9 @@ class RunItem(BaseModel): agent_config: Optional[AmbientAgentConfig] = None """Configuration for an ambient agent run""" + artifacts: Optional[List[ArtifactItem]] = None + """Artifacts created during the run (plans, pull requests, etc.)""" + conversation_id: Optional[str] = None """UUID of the conversation associated with the run""" @@ -102,6 +106,8 @@ class RunItem(BaseModel): - SLACK: Created from Slack integration - LOCAL: Created from local CLI/app - SCHEDULED_AGENT: Created by a scheduled agent + - WEB_APP: Created from the Warp web app + - GITHUB_ACTION: Created from a GitHub action """ started_at: Optional[datetime] = None diff --git a/src/warp_agent_sdk/types/agent/run_list_params.py b/src/warp_agent_sdk/types/agent/run_list_params.py index d7ef57c..23d6a75 100644 --- a/src/warp_agent_sdk/types/agent/run_list_params.py +++ b/src/warp_agent_sdk/types/agent/run_list_params.py @@ -29,6 +29,9 @@ class RunListParams(TypedDict, total=False): cursor: str """Pagination cursor from previous response""" + environment_id: Annotated[str, PropertyInfo(alias="environmentId")] + """Filter runs by environment ID""" + limit: int """Maximum number of runs to return""" diff --git a/src/warp_agent_sdk/types/agent/run_source_type.py b/src/warp_agent_sdk/types/agent/run_source_type.py index c1a4b52..70eedb6 100644 --- a/src/warp_agent_sdk/types/agent/run_source_type.py +++ b/src/warp_agent_sdk/types/agent/run_source_type.py @@ -4,4 +4,4 @@ __all__ = ["RunSourceType"] -RunSourceType: TypeAlias = Literal["LINEAR", "API", "SLACK", "LOCAL", "SCHEDULED_AGENT"] +RunSourceType: TypeAlias = Literal["LINEAR", "API", "SLACK", "LOCAL", "SCHEDULED_AGENT", "WEB_APP", "GITHUB_ACTION"] diff --git a/src/warp_agent_sdk/types/ambient_agent_config.py b/src/warp_agent_sdk/types/ambient_agent_config.py index 32d67ff..80db2ba 100644 --- a/src/warp_agent_sdk/types/ambient_agent_config.py +++ b/src/warp_agent_sdk/types/ambient_agent_config.py @@ -51,3 +51,9 @@ class AmbientAgentConfig(BaseModel): name: Optional[str] = None """Config name for searchability and traceability""" + + worker_host: Optional[str] = None + """ + Self-hosted worker ID that should execute this task. If not specified or set to + "warp", the task runs on Warp-hosted workers. + """ diff --git a/src/warp_agent_sdk/types/ambient_agent_config_param.py b/src/warp_agent_sdk/types/ambient_agent_config_param.py index 7a9e91c..5c8e711 100644 --- a/src/warp_agent_sdk/types/ambient_agent_config_param.py +++ b/src/warp_agent_sdk/types/ambient_agent_config_param.py @@ -52,3 +52,9 @@ class AmbientAgentConfigParam(TypedDict, total=False): name: str """Config name for searchability and traceability""" + + worker_host: str + """ + Self-hosted worker ID that should execute this task. If not specified or set to + "warp", the task runs on Warp-hosted workers. + """ diff --git a/tests/api_resources/agent/test_runs.py b/tests/api_resources/agent/test_runs.py index d17cd50..94cf8b8 100644 --- a/tests/api_resources/agent/test_runs.py +++ b/tests/api_resources/agent/test_runs.py @@ -75,6 +75,7 @@ def test_method_list_with_all_params(self, client: WarpAPI) -> None: created_before=parse_datetime("2019-12-27T18:11:19.117Z"), creator="creator", cursor="cursor", + environment_id="environmentId", limit=1, model_id="model_id", source="LINEAR", @@ -167,6 +168,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWarpAPI) -> created_before=parse_datetime("2019-12-27T18:11:19.117Z"), creator="creator", cursor="cursor", + environment_id="environmentId", limit=1, model_id="model_id", source="LINEAR", diff --git a/tests/api_resources/test_agent.py b/tests/api_resources/test_agent.py index f873025..84eeddf 100644 --- a/tests/api_resources/test_agent.py +++ b/tests/api_resources/test_agent.py @@ -45,6 +45,7 @@ def test_method_run_with_all_params(self, client: WarpAPI) -> None: }, "model_id": "model_id", "name": "name", + "worker_host": "worker_host", }, team=True, title="title", @@ -111,6 +112,7 @@ async def test_method_run_with_all_params(self, async_client: AsyncWarpAPI) -> N }, "model_id": "model_id", "name": "name", + "worker_host": "worker_host", }, team=True, title="title", From 68f4b61fd83b1f5576b72ad6ea3a28cd58e06a68 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:55:29 +0000 Subject: [PATCH 08/11] feat(client): add custom JSON encoder for extended type support --- src/warp_agent_sdk/_base_client.py | 7 +- src/warp_agent_sdk/_compat.py | 6 +- src/warp_agent_sdk/_utils/_json.py | 35 ++++++++ tests/test_utils/test_json.py | 126 +++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 src/warp_agent_sdk/_utils/_json.py create mode 100644 tests/test_utils/test_json.py diff --git a/src/warp_agent_sdk/_base_client.py b/src/warp_agent_sdk/_base_client.py index e0f9736..61d4df8 100644 --- a/src/warp_agent_sdk/_base_client.py +++ b/src/warp_agent_sdk/_base_client.py @@ -86,6 +86,7 @@ APIConnectionError, APIResponseValidationError, ) +from ._utils._json import openapi_dumps log: logging.Logger = logging.getLogger(__name__) @@ -554,8 +555,10 @@ def _build_request( kwargs["content"] = options.content elif isinstance(json_data, bytes): kwargs["content"] = json_data - else: - kwargs["json"] = json_data if is_given(json_data) else None + elif not files: + # Don't set content when JSON is sent as multipart/form-data, + # since httpx's content param overrides other body arguments + kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None kwargs["files"] = files else: headers.pop("Content-Type", None) diff --git a/src/warp_agent_sdk/_compat.py b/src/warp_agent_sdk/_compat.py index bdef67f..786ff42 100644 --- a/src/warp_agent_sdk/_compat.py +++ b/src/warp_agent_sdk/_compat.py @@ -139,6 +139,7 @@ def model_dump( exclude_defaults: bool = False, warnings: bool = True, mode: Literal["json", "python"] = "python", + by_alias: bool | None = None, ) -> dict[str, Any]: if (not PYDANTIC_V1) or hasattr(model, "model_dump"): return model.model_dump( @@ -148,13 +149,12 @@ def model_dump( exclude_defaults=exclude_defaults, # warnings are not supported in Pydantic v1 warnings=True if PYDANTIC_V1 else warnings, + by_alias=by_alias, ) return cast( "dict[str, Any]", model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast] - exclude=exclude, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, + exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias) ), ) diff --git a/src/warp_agent_sdk/_utils/_json.py b/src/warp_agent_sdk/_utils/_json.py new file mode 100644 index 0000000..6058421 --- /dev/null +++ b/src/warp_agent_sdk/_utils/_json.py @@ -0,0 +1,35 @@ +import json +from typing import Any +from datetime import datetime +from typing_extensions import override + +import pydantic + +from .._compat import model_dump + + +def openapi_dumps(obj: Any) -> bytes: + """ + Serialize an object to UTF-8 encoded JSON bytes. + + Extends the standard json.dumps with support for additional types + commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc. + """ + return json.dumps( + obj, + cls=_CustomEncoder, + # Uses the same defaults as httpx's JSON serialization + ensure_ascii=False, + separators=(",", ":"), + allow_nan=False, + ).encode() + + +class _CustomEncoder(json.JSONEncoder): + @override + def default(self, o: Any) -> Any: + if isinstance(o, datetime): + return o.isoformat() + if isinstance(o, pydantic.BaseModel): + return model_dump(o, exclude_unset=True, mode="json", by_alias=True) + return super().default(o) diff --git a/tests/test_utils/test_json.py b/tests/test_utils/test_json.py new file mode 100644 index 0000000..354d1d8 --- /dev/null +++ b/tests/test_utils/test_json.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import datetime +from typing import Union + +import pydantic + +from warp_agent_sdk import _compat +from warp_agent_sdk._utils._json import openapi_dumps + + +class TestOpenapiDumps: + def test_basic(self) -> None: + data = {"key": "value", "number": 42} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"key":"value","number":42}' + + def test_datetime_serialization(self) -> None: + dt = datetime.datetime(2023, 1, 1, 12, 0, 0) + data = {"datetime": dt} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"datetime":"2023-01-01T12:00:00"}' + + def test_pydantic_model_serialization(self) -> None: + class User(pydantic.BaseModel): + first_name: str + last_name: str + age: int + + model_instance = User(first_name="John", last_name="Kramer", age=83) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"first_name":"John","last_name":"Kramer","age":83}}' + + def test_pydantic_model_with_default_values(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + score: int = 0 + + model_instance = User(name="Alice") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Alice"}}' + + def test_pydantic_model_with_default_values_overridden(self) -> None: + class User(pydantic.BaseModel): + name: str + role: str = "user" + active: bool = True + + model_instance = User(name="Bob", role="admin", active=False) + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Bob","role":"admin","active":false}}' + + def test_pydantic_model_with_alias(self) -> None: + class User(pydantic.BaseModel): + first_name: str = pydantic.Field(alias="firstName") + last_name: str = pydantic.Field(alias="lastName") + + model_instance = User(firstName="John", lastName="Doe") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"firstName":"John","lastName":"Doe"}}' + + def test_pydantic_model_with_alias_and_default(self) -> None: + class User(pydantic.BaseModel): + user_name: str = pydantic.Field(alias="userName") + user_role: str = pydantic.Field(default="member", alias="userRole") + is_active: bool = pydantic.Field(default=True, alias="isActive") + + model_instance = User(userName="charlie") + data = {"model": model_instance} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"charlie"}}' + + model_with_overrides = User(userName="diana", userRole="admin", isActive=False) + data = {"model": model_with_overrides} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"userName":"diana","userRole":"admin","isActive":false}}' + + def test_pydantic_model_with_nested_models_and_defaults(self) -> None: + class Address(pydantic.BaseModel): + street: str + city: str = "Unknown" + + class User(pydantic.BaseModel): + name: str + address: Address + verified: bool = False + + if _compat.PYDANTIC_V1: + # to handle forward references in Pydantic v1 + User.update_forward_refs(**locals()) # type: ignore[reportDeprecated] + + address = Address(street="123 Main St") + user = User(name="Diana", address=address) + data = {"user": user} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"user":{"name":"Diana","address":{"street":"123 Main St"}}}' + + address_with_city = Address(street="456 Oak Ave", city="Boston") + user_verified = User(name="Eve", address=address_with_city, verified=True) + data = {"user": user_verified} + json_bytes = openapi_dumps(data) + assert ( + json_bytes == b'{"user":{"name":"Eve","address":{"street":"456 Oak Ave","city":"Boston"},"verified":true}}' + ) + + def test_pydantic_model_with_optional_fields(self) -> None: + class User(pydantic.BaseModel): + name: str + email: Union[str, None] + phone: Union[str, None] + + model_with_none = User(name="Eve", email=None, phone=None) + data = {"model": model_with_none} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Eve","email":null,"phone":null}}' + + model_with_values = User(name="Frank", email="frank@example.com", phone=None) + data = {"model": model_with_values} + json_bytes = openapi_dumps(data) + assert json_bytes == b'{"model":{"name":"Frank","email":"frank@example.com","phone":null}}' From 1927318b5b2a4115b3f1ec2be7db86f1e8e0d321 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 19:26:03 +0000 Subject: [PATCH 09/11] feat(api): add schedules, agent list, skill-spec --- .stats.yml | 8 +- api.md | 34 +- .../resources/agent/__init__.py | 14 + src/warp_agent_sdk/resources/agent/agent.py | 125 ++- src/warp_agent_sdk/resources/agent/runs.py | 82 ++ .../resources/agent/schedules.py | 742 ++++++++++++++++++ src/warp_agent_sdk/types/__init__.py | 6 + src/warp_agent_sdk/types/agent/__init__.py | 6 + .../types/agent/run_cancel_response.py | 7 + src/warp_agent_sdk/types/agent/run_item.py | 20 +- .../types/agent/schedule_create_params.py | 35 + .../types/agent/schedule_delete_response.py | 10 + .../types/agent/schedule_list_response.py | 13 + .../types/agent/schedule_update_params.py | 26 + .../types/agent/scheduled_agent_item.py | 63 ++ src/warp_agent_sdk/types/agent_list_params.py | 15 + .../types/agent_list_response.py | 58 ++ .../types/ambient_agent_config.py | 40 +- .../types/ambient_agent_config_param.py | 39 +- .../types/cloud_environment_config.py | 34 + src/warp_agent_sdk/types/mcp_server_config.py | 32 + .../types/mcp_server_config_param.py | 35 + src/warp_agent_sdk/types/run_creator_info.py | 22 + tests/api_resources/agent/test_runs.py | 84 ++ tests/api_resources/agent/test_schedules.py | 736 +++++++++++++++++ tests/api_resources/test_agent.py | 79 +- 26 files changed, 2285 insertions(+), 80 deletions(-) create mode 100644 src/warp_agent_sdk/resources/agent/schedules.py create mode 100644 src/warp_agent_sdk/types/agent/run_cancel_response.py create mode 100644 src/warp_agent_sdk/types/agent/schedule_create_params.py create mode 100644 src/warp_agent_sdk/types/agent/schedule_delete_response.py create mode 100644 src/warp_agent_sdk/types/agent/schedule_list_response.py create mode 100644 src/warp_agent_sdk/types/agent/schedule_update_params.py create mode 100644 src/warp_agent_sdk/types/agent/scheduled_agent_item.py create mode 100644 src/warp_agent_sdk/types/agent_list_params.py create mode 100644 src/warp_agent_sdk/types/agent_list_response.py create mode 100644 src/warp_agent_sdk/types/cloud_environment_config.py create mode 100644 src/warp_agent_sdk/types/mcp_server_config.py create mode 100644 src/warp_agent_sdk/types/mcp_server_config_param.py create mode 100644 src/warp_agent_sdk/types/run_creator_info.py create mode 100644 tests/api_resources/agent/test_schedules.py diff --git a/.stats.yml b/.stats.yml index 468b580..7759d9b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 3 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-48ffcddda2e2febbe761bbb643e04c4d260c5b6ba6378297f1e7af9793dedaa5.yml -openapi_spec_hash: 1058ed679fc1f8a91d4f4c3136445dbc -config_hash: efa7e85b4732b72432e1b5828d2b6b9c +configured_endpoints: 12 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-3ee19d97da1711b8dfcba85f38c3c345fa96aaf78ea7f025e1ae490d17e97517.yml +openapi_spec_hash: 2d03e7a248b1be5bd5600b62912cdab8 +config_hash: 9db55bfa03e8dd5e251342bd7491408c diff --git a/api.md b/api.md index b2a484a..59b2005 100644 --- a/api.md +++ b/api.md @@ -3,11 +3,19 @@ Types: ```python -from warp_agent_sdk.types import AmbientAgentConfig, AgentRunResponse +from warp_agent_sdk.types import ( + AmbientAgentConfig, + CloudEnvironmentConfig, + McpServerConfig, + RunCreatorInfo, + AgentListResponse, + AgentRunResponse, +) ``` Methods: +- client.agent.list(\*\*params) -> AgentListResponse - client.agent.run(\*\*params) -> AgentRunResponse ## Runs @@ -21,6 +29,7 @@ from warp_agent_sdk.types.agent import ( RunSourceType, RunState, RunListResponse, + RunCancelResponse, ) ``` @@ -28,3 +37,26 @@ Methods: - client.agent.runs.retrieve(run_id) -> RunItem - client.agent.runs.list(\*\*params) -> RunListResponse +- client.agent.runs.cancel(run_id) -> str + +## Schedules + +Types: + +```python +from warp_agent_sdk.types.agent import ( + ScheduledAgentItem, + ScheduleListResponse, + ScheduleDeleteResponse, +) +``` + +Methods: + +- client.agent.schedules.create(\*\*params) -> ScheduledAgentItem +- client.agent.schedules.retrieve(schedule_id) -> ScheduledAgentItem +- client.agent.schedules.update(schedule_id, \*\*params) -> ScheduledAgentItem +- client.agent.schedules.list() -> ScheduleListResponse +- client.agent.schedules.delete(schedule_id) -> ScheduleDeleteResponse +- client.agent.schedules.pause(schedule_id) -> ScheduledAgentItem +- client.agent.schedules.resume(schedule_id) -> ScheduledAgentItem diff --git a/src/warp_agent_sdk/resources/agent/__init__.py b/src/warp_agent_sdk/resources/agent/__init__.py index 71feab7..84fc8a7 100644 --- a/src/warp_agent_sdk/resources/agent/__init__.py +++ b/src/warp_agent_sdk/resources/agent/__init__.py @@ -16,6 +16,14 @@ AgentResourceWithStreamingResponse, AsyncAgentResourceWithStreamingResponse, ) +from .schedules import ( + SchedulesResource, + AsyncSchedulesResource, + SchedulesResourceWithRawResponse, + AsyncSchedulesResourceWithRawResponse, + SchedulesResourceWithStreamingResponse, + AsyncSchedulesResourceWithStreamingResponse, +) __all__ = [ "RunsResource", @@ -24,6 +32,12 @@ "AsyncRunsResourceWithRawResponse", "RunsResourceWithStreamingResponse", "AsyncRunsResourceWithStreamingResponse", + "SchedulesResource", + "AsyncSchedulesResource", + "SchedulesResourceWithRawResponse", + "AsyncSchedulesResourceWithRawResponse", + "SchedulesResourceWithStreamingResponse", + "AsyncSchedulesResourceWithStreamingResponse", "AgentResource", "AsyncAgentResource", "AgentResourceWithRawResponse", diff --git a/src/warp_agent_sdk/resources/agent/agent.py b/src/warp_agent_sdk/resources/agent/agent.py index 7914f7a..52ba8f8 100644 --- a/src/warp_agent_sdk/resources/agent/agent.py +++ b/src/warp_agent_sdk/resources/agent/agent.py @@ -12,10 +12,18 @@ RunsResourceWithStreamingResponse, AsyncRunsResourceWithStreamingResponse, ) -from ...types import agent_run_params +from ...types import agent_run_params, agent_list_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 .schedules import ( + SchedulesResource, + AsyncSchedulesResource, + SchedulesResourceWithRawResponse, + AsyncSchedulesResourceWithRawResponse, + SchedulesResourceWithStreamingResponse, + AsyncSchedulesResourceWithStreamingResponse, +) from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( to_raw_response_wrapper, @@ -25,6 +33,7 @@ ) from ..._base_client import make_request_options from ...types.agent_run_response import AgentRunResponse +from ...types.agent_list_response import AgentListResponse from ...types.ambient_agent_config_param import AmbientAgentConfigParam __all__ = ["AgentResource", "AsyncAgentResource"] @@ -35,6 +44,10 @@ class AgentResource(SyncAPIResource): def runs(self) -> RunsResource: return RunsResource(self._client) + @cached_property + def schedules(self) -> SchedulesResource: + return SchedulesResource(self._client) + @cached_property def with_raw_response(self) -> AgentResourceWithRawResponse: """ @@ -54,6 +67,45 @@ def with_streaming_response(self) -> AgentResourceWithStreamingResponse: """ return AgentResourceWithStreamingResponse(self) + def list( + self, + *, + repo: 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, + ) -> AgentListResponse: + """ + Retrieve a list of available agents (skills) that can be used to run tasks. + Agents are discovered from environments or a specific repository. + + Args: + repo: Optional repository specification to list agents from (format: "owner/repo"). If + not provided, lists agents from all accessible environments. + + 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( + "/agent", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"repo": repo}, agent_list_params.AgentListParams), + ), + cast_to=AgentListResponse, + ) + def run( self, *, @@ -113,6 +165,10 @@ class AsyncAgentResource(AsyncAPIResource): def runs(self) -> AsyncRunsResource: return AsyncRunsResource(self._client) + @cached_property + def schedules(self) -> AsyncSchedulesResource: + return AsyncSchedulesResource(self._client) + @cached_property def with_raw_response(self) -> AsyncAgentResourceWithRawResponse: """ @@ -132,6 +188,45 @@ def with_streaming_response(self) -> AsyncAgentResourceWithStreamingResponse: """ return AsyncAgentResourceWithStreamingResponse(self) + async def list( + self, + *, + repo: 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, + ) -> AgentListResponse: + """ + Retrieve a list of available agents (skills) that can be used to run tasks. + Agents are discovered from environments or a specific repository. + + Args: + repo: Optional repository specification to list agents from (format: "owner/repo"). If + not provided, lists agents from all accessible environments. + + 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 await self._get( + "/agent", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"repo": repo}, agent_list_params.AgentListParams), + ), + cast_to=AgentListResponse, + ) + async def run( self, *, @@ -190,6 +285,9 @@ class AgentResourceWithRawResponse: def __init__(self, agent: AgentResource) -> None: self._agent = agent + self.list = to_raw_response_wrapper( + agent.list, + ) self.run = to_raw_response_wrapper( agent.run, ) @@ -198,11 +296,18 @@ def __init__(self, agent: AgentResource) -> None: def runs(self) -> RunsResourceWithRawResponse: return RunsResourceWithRawResponse(self._agent.runs) + @cached_property + def schedules(self) -> SchedulesResourceWithRawResponse: + return SchedulesResourceWithRawResponse(self._agent.schedules) + class AsyncAgentResourceWithRawResponse: def __init__(self, agent: AsyncAgentResource) -> None: self._agent = agent + self.list = async_to_raw_response_wrapper( + agent.list, + ) self.run = async_to_raw_response_wrapper( agent.run, ) @@ -211,11 +316,18 @@ def __init__(self, agent: AsyncAgentResource) -> None: def runs(self) -> AsyncRunsResourceWithRawResponse: return AsyncRunsResourceWithRawResponse(self._agent.runs) + @cached_property + def schedules(self) -> AsyncSchedulesResourceWithRawResponse: + return AsyncSchedulesResourceWithRawResponse(self._agent.schedules) + class AgentResourceWithStreamingResponse: def __init__(self, agent: AgentResource) -> None: self._agent = agent + self.list = to_streamed_response_wrapper( + agent.list, + ) self.run = to_streamed_response_wrapper( agent.run, ) @@ -224,11 +336,18 @@ def __init__(self, agent: AgentResource) -> None: def runs(self) -> RunsResourceWithStreamingResponse: return RunsResourceWithStreamingResponse(self._agent.runs) + @cached_property + def schedules(self) -> SchedulesResourceWithStreamingResponse: + return SchedulesResourceWithStreamingResponse(self._agent.schedules) + class AsyncAgentResourceWithStreamingResponse: def __init__(self, agent: AsyncAgentResource) -> None: self._agent = agent + self.list = async_to_streamed_response_wrapper( + agent.list, + ) self.run = async_to_streamed_response_wrapper( agent.run, ) @@ -236,3 +355,7 @@ def __init__(self, agent: AsyncAgentResource) -> None: @cached_property def runs(self) -> AsyncRunsResourceWithStreamingResponse: return AsyncRunsResourceWithStreamingResponse(self._agent.runs) + + @cached_property + def schedules(self) -> AsyncSchedulesResourceWithStreamingResponse: + return AsyncSchedulesResourceWithStreamingResponse(self._agent.schedules) diff --git a/src/warp_agent_sdk/resources/agent/runs.py b/src/warp_agent_sdk/resources/agent/runs.py index 0f059c0..a00a979 100644 --- a/src/warp_agent_sdk/resources/agent/runs.py +++ b/src/warp_agent_sdk/resources/agent/runs.py @@ -162,6 +162,41 @@ def list( cast_to=RunListResponse, ) + def cancel( + self, + run_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, + ) -> str: + """Cancel an agent run that is currently queued or in progress. + + Once cancelled, the + run will transition to a failed state. + + 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 + """ + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return self._post( + f"/agent/runs/{run_id}/cancel", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=str, + ) + class AsyncRunsResource(AsyncAPIResource): @cached_property @@ -298,6 +333,41 @@ async def list( cast_to=RunListResponse, ) + async def cancel( + self, + run_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, + ) -> str: + """Cancel an agent run that is currently queued or in progress. + + Once cancelled, the + run will transition to a failed state. + + 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 + """ + if not run_id: + raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}") + return await self._post( + f"/agent/runs/{run_id}/cancel", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=str, + ) + class RunsResourceWithRawResponse: def __init__(self, runs: RunsResource) -> None: @@ -309,6 +379,9 @@ def __init__(self, runs: RunsResource) -> None: self.list = to_raw_response_wrapper( runs.list, ) + self.cancel = to_raw_response_wrapper( + runs.cancel, + ) class AsyncRunsResourceWithRawResponse: @@ -321,6 +394,9 @@ def __init__(self, runs: AsyncRunsResource) -> None: self.list = async_to_raw_response_wrapper( runs.list, ) + self.cancel = async_to_raw_response_wrapper( + runs.cancel, + ) class RunsResourceWithStreamingResponse: @@ -333,6 +409,9 @@ def __init__(self, runs: RunsResource) -> None: self.list = to_streamed_response_wrapper( runs.list, ) + self.cancel = to_streamed_response_wrapper( + runs.cancel, + ) class AsyncRunsResourceWithStreamingResponse: @@ -345,3 +424,6 @@ def __init__(self, runs: AsyncRunsResource) -> None: self.list = async_to_streamed_response_wrapper( runs.list, ) + self.cancel = async_to_streamed_response_wrapper( + runs.cancel, + ) diff --git a/src/warp_agent_sdk/resources/agent/schedules.py b/src/warp_agent_sdk/resources/agent/schedules.py new file mode 100644 index 0000000..6237dde --- /dev/null +++ b/src/warp_agent_sdk/resources/agent/schedules.py @@ -0,0 +1,742 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +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 ...types.agent import schedule_create_params, schedule_update_params +from ..._base_client import make_request_options +from ...types.agent.scheduled_agent_item import ScheduledAgentItem +from ...types.ambient_agent_config_param import AmbientAgentConfigParam +from ...types.agent.schedule_list_response import ScheduleListResponse +from ...types.agent.schedule_delete_response import ScheduleDeleteResponse + +__all__ = ["SchedulesResource", "AsyncSchedulesResource"] + + +class SchedulesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SchedulesResourceWithRawResponse: + """ + 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/warpdotdev/warp-sdk-python#accessing-raw-response-data-eg-headers + """ + return SchedulesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SchedulesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/warpdotdev/warp-sdk-python#with_streaming_response + """ + return SchedulesResourceWithStreamingResponse(self) + + def create( + self, + *, + cron_schedule: str, + name: str, + prompt: str, + agent_config: AmbientAgentConfigParam | Omit = omit, + enabled: bool | Omit = omit, + team: bool | 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, + ) -> ScheduledAgentItem: + """Create a new scheduled agent that runs on a cron schedule. + + The agent will be + triggered automatically based on the cron expression. + + Args: + cron_schedule: Cron expression defining when the agent runs (e.g., "0 9 \\** \\** \\**" for daily at + 9am UTC) + + name: Human-readable name for the schedule + + prompt: The prompt/instruction for the agent to execute + + agent_config: Configuration for an ambient agent run + + enabled: Whether the schedule should be active immediately + + team: Whether to create a team-owned schedule. Defaults to true for users on a single + team. + + 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._post( + "/agent/schedules", + body=maybe_transform( + { + "cron_schedule": cron_schedule, + "name": name, + "prompt": prompt, + "agent_config": agent_config, + "enabled": enabled, + "team": team, + }, + schedule_create_params.ScheduleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + def retrieve( + self, + schedule_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, + ) -> ScheduledAgentItem: + """ + Retrieve detailed information about a specific scheduled agent, including its + configuration, history, and next scheduled run time. + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return self._get( + f"/agent/schedules/{schedule_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + def update( + self, + schedule_id: str, + *, + cron_schedule: str, + enabled: bool, + name: str, + prompt: str, + agent_config: AmbientAgentConfigParam | 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, + ) -> ScheduledAgentItem: + """Update an existing scheduled agent's configuration. + + All fields except + agent_config are required. + + Args: + cron_schedule: Cron expression defining when the agent runs + + enabled: Whether the schedule should be active + + name: Human-readable name for the schedule + + prompt: The prompt/instruction for the agent to execute + + agent_config: Configuration for an ambient agent run + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return self._put( + f"/agent/schedules/{schedule_id}", + body=maybe_transform( + { + "cron_schedule": cron_schedule, + "enabled": enabled, + "name": name, + "prompt": prompt, + "agent_config": agent_config, + }, + schedule_update_params.ScheduleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + def list( + 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, + ) -> ScheduleListResponse: + """Retrieve all scheduled agents accessible to the authenticated user. + + Results are + sorted alphabetically by name. + """ + return self._get( + "/agent/schedules", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduleListResponse, + ) + + def delete( + self, + schedule_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, + ) -> ScheduleDeleteResponse: + """Delete a scheduled agent. + + This will stop all future scheduled runs. + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return self._delete( + f"/agent/schedules/{schedule_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduleDeleteResponse, + ) + + def pause( + self, + schedule_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, + ) -> ScheduledAgentItem: + """Pause a scheduled agent. + + The agent will not run until resumed. This sets the + enabled flag to false. + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return self._post( + f"/agent/schedules/{schedule_id}/pause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + def resume( + self, + schedule_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, + ) -> ScheduledAgentItem: + """Resume a paused scheduled agent. + + The agent will start running according to its + cron schedule. This sets the enabled flag to true. + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return self._post( + f"/agent/schedules/{schedule_id}/resume", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + +class AsyncSchedulesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSchedulesResourceWithRawResponse: + """ + 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/warpdotdev/warp-sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncSchedulesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSchedulesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/warpdotdev/warp-sdk-python#with_streaming_response + """ + return AsyncSchedulesResourceWithStreamingResponse(self) + + async def create( + self, + *, + cron_schedule: str, + name: str, + prompt: str, + agent_config: AmbientAgentConfigParam | Omit = omit, + enabled: bool | Omit = omit, + team: bool | 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, + ) -> ScheduledAgentItem: + """Create a new scheduled agent that runs on a cron schedule. + + The agent will be + triggered automatically based on the cron expression. + + Args: + cron_schedule: Cron expression defining when the agent runs (e.g., "0 9 \\** \\** \\**" for daily at + 9am UTC) + + name: Human-readable name for the schedule + + prompt: The prompt/instruction for the agent to execute + + agent_config: Configuration for an ambient agent run + + enabled: Whether the schedule should be active immediately + + team: Whether to create a team-owned schedule. Defaults to true for users on a single + team. + + 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 await self._post( + "/agent/schedules", + body=await async_maybe_transform( + { + "cron_schedule": cron_schedule, + "name": name, + "prompt": prompt, + "agent_config": agent_config, + "enabled": enabled, + "team": team, + }, + schedule_create_params.ScheduleCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + async def retrieve( + self, + schedule_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, + ) -> ScheduledAgentItem: + """ + Retrieve detailed information about a specific scheduled agent, including its + configuration, history, and next scheduled run time. + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return await self._get( + f"/agent/schedules/{schedule_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + async def update( + self, + schedule_id: str, + *, + cron_schedule: str, + enabled: bool, + name: str, + prompt: str, + agent_config: AmbientAgentConfigParam | 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, + ) -> ScheduledAgentItem: + """Update an existing scheduled agent's configuration. + + All fields except + agent_config are required. + + Args: + cron_schedule: Cron expression defining when the agent runs + + enabled: Whether the schedule should be active + + name: Human-readable name for the schedule + + prompt: The prompt/instruction for the agent to execute + + agent_config: Configuration for an ambient agent run + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return await self._put( + f"/agent/schedules/{schedule_id}", + body=await async_maybe_transform( + { + "cron_schedule": cron_schedule, + "enabled": enabled, + "name": name, + "prompt": prompt, + "agent_config": agent_config, + }, + schedule_update_params.ScheduleUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + async def list( + 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, + ) -> ScheduleListResponse: + """Retrieve all scheduled agents accessible to the authenticated user. + + Results are + sorted alphabetically by name. + """ + return await self._get( + "/agent/schedules", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduleListResponse, + ) + + async def delete( + self, + schedule_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, + ) -> ScheduleDeleteResponse: + """Delete a scheduled agent. + + This will stop all future scheduled runs. + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return await self._delete( + f"/agent/schedules/{schedule_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduleDeleteResponse, + ) + + async def pause( + self, + schedule_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, + ) -> ScheduledAgentItem: + """Pause a scheduled agent. + + The agent will not run until resumed. This sets the + enabled flag to false. + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return await self._post( + f"/agent/schedules/{schedule_id}/pause", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + async def resume( + self, + schedule_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, + ) -> ScheduledAgentItem: + """Resume a paused scheduled agent. + + The agent will start running according to its + cron schedule. This sets the enabled flag to true. + + 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 + """ + if not schedule_id: + raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}") + return await self._post( + f"/agent/schedules/{schedule_id}/resume", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ScheduledAgentItem, + ) + + +class SchedulesResourceWithRawResponse: + def __init__(self, schedules: SchedulesResource) -> None: + self._schedules = schedules + + self.create = to_raw_response_wrapper( + schedules.create, + ) + self.retrieve = to_raw_response_wrapper( + schedules.retrieve, + ) + self.update = to_raw_response_wrapper( + schedules.update, + ) + self.list = to_raw_response_wrapper( + schedules.list, + ) + self.delete = to_raw_response_wrapper( + schedules.delete, + ) + self.pause = to_raw_response_wrapper( + schedules.pause, + ) + self.resume = to_raw_response_wrapper( + schedules.resume, + ) + + +class AsyncSchedulesResourceWithRawResponse: + def __init__(self, schedules: AsyncSchedulesResource) -> None: + self._schedules = schedules + + self.create = async_to_raw_response_wrapper( + schedules.create, + ) + self.retrieve = async_to_raw_response_wrapper( + schedules.retrieve, + ) + self.update = async_to_raw_response_wrapper( + schedules.update, + ) + self.list = async_to_raw_response_wrapper( + schedules.list, + ) + self.delete = async_to_raw_response_wrapper( + schedules.delete, + ) + self.pause = async_to_raw_response_wrapper( + schedules.pause, + ) + self.resume = async_to_raw_response_wrapper( + schedules.resume, + ) + + +class SchedulesResourceWithStreamingResponse: + def __init__(self, schedules: SchedulesResource) -> None: + self._schedules = schedules + + self.create = to_streamed_response_wrapper( + schedules.create, + ) + self.retrieve = to_streamed_response_wrapper( + schedules.retrieve, + ) + self.update = to_streamed_response_wrapper( + schedules.update, + ) + self.list = to_streamed_response_wrapper( + schedules.list, + ) + self.delete = to_streamed_response_wrapper( + schedules.delete, + ) + self.pause = to_streamed_response_wrapper( + schedules.pause, + ) + self.resume = to_streamed_response_wrapper( + schedules.resume, + ) + + +class AsyncSchedulesResourceWithStreamingResponse: + def __init__(self, schedules: AsyncSchedulesResource) -> None: + self._schedules = schedules + + self.create = async_to_streamed_response_wrapper( + schedules.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + schedules.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + schedules.update, + ) + self.list = async_to_streamed_response_wrapper( + schedules.list, + ) + self.delete = async_to_streamed_response_wrapper( + schedules.delete, + ) + self.pause = async_to_streamed_response_wrapper( + schedules.pause, + ) + self.resume = async_to_streamed_response_wrapper( + schedules.resume, + ) diff --git a/src/warp_agent_sdk/types/__init__.py b/src/warp_agent_sdk/types/__init__.py index 7175dea..c827ee7 100644 --- a/src/warp_agent_sdk/types/__init__.py +++ b/src/warp_agent_sdk/types/__init__.py @@ -3,6 +3,12 @@ from __future__ import annotations from .agent_run_params import AgentRunParams as AgentRunParams +from .run_creator_info import RunCreatorInfo as RunCreatorInfo +from .agent_list_params import AgentListParams as AgentListParams +from .mcp_server_config import McpServerConfig as McpServerConfig from .agent_run_response import AgentRunResponse as AgentRunResponse +from .agent_list_response import AgentListResponse as AgentListResponse from .ambient_agent_config import AmbientAgentConfig as AmbientAgentConfig +from .mcp_server_config_param import McpServerConfigParam as McpServerConfigParam +from .cloud_environment_config import CloudEnvironmentConfig as CloudEnvironmentConfig from .ambient_agent_config_param import AmbientAgentConfigParam as AmbientAgentConfigParam diff --git a/src/warp_agent_sdk/types/agent/__init__.py b/src/warp_agent_sdk/types/agent/__init__.py index 2d7860c..9aad9f6 100644 --- a/src/warp_agent_sdk/types/agent/__init__.py +++ b/src/warp_agent_sdk/types/agent/__init__.py @@ -8,3 +8,9 @@ from .run_list_params import RunListParams as RunListParams from .run_source_type import RunSourceType as RunSourceType from .run_list_response import RunListResponse as RunListResponse +from .run_cancel_response import RunCancelResponse as RunCancelResponse +from .scheduled_agent_item import ScheduledAgentItem as ScheduledAgentItem +from .schedule_create_params import ScheduleCreateParams as ScheduleCreateParams +from .schedule_list_response import ScheduleListResponse as ScheduleListResponse +from .schedule_update_params import ScheduleUpdateParams as ScheduleUpdateParams +from .schedule_delete_response import ScheduleDeleteResponse as ScheduleDeleteResponse diff --git a/src/warp_agent_sdk/types/agent/run_cancel_response.py b/src/warp_agent_sdk/types/agent/run_cancel_response.py new file mode 100644 index 0000000..b002815 --- /dev/null +++ b/src/warp_agent_sdk/types/agent/run_cancel_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["RunCancelResponse"] + +RunCancelResponse: TypeAlias = str diff --git a/src/warp_agent_sdk/types/agent/run_item.py b/src/warp_agent_sdk/types/agent/run_item.py index c925ffb..a8f310a 100644 --- a/src/warp_agent_sdk/types/agent/run_item.py +++ b/src/warp_agent_sdk/types/agent/run_item.py @@ -2,29 +2,15 @@ from typing import List, Optional from datetime import datetime -from typing_extensions import Literal from ..._models import BaseModel from .run_state import RunState from .artifact_item import ArtifactItem from .run_source_type import RunSourceType +from ..run_creator_info import RunCreatorInfo from ..ambient_agent_config import AmbientAgentConfig -__all__ = ["RunItem", "Creator", "RequestUsage", "StatusMessage"] - - -class Creator(BaseModel): - display_name: Optional[str] = None - """Display name of the creator""" - - photo_url: Optional[str] = None - """URL to the creator's photo""" - - type: Optional[Literal["user", "service_account"]] = None - """Type of the creator principal""" - - uid: Optional[str] = None - """Unique identifier of the creator""" +__all__ = ["RunItem", "RequestUsage", "StatusMessage"] class RequestUsage(BaseModel): @@ -84,7 +70,7 @@ class RunItem(BaseModel): conversation_id: Optional[str] = None """UUID of the conversation associated with the run""" - creator: Optional[Creator] = None + creator: Optional[RunCreatorInfo] = None is_sandbox_running: Optional[bool] = None """Whether the sandbox environment is currently running""" diff --git a/src/warp_agent_sdk/types/agent/schedule_create_params.py b/src/warp_agent_sdk/types/agent/schedule_create_params.py new file mode 100644 index 0000000..0c3a5d6 --- /dev/null +++ b/src/warp_agent_sdk/types/agent/schedule_create_params.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ..ambient_agent_config_param import AmbientAgentConfigParam + +__all__ = ["ScheduleCreateParams"] + + +class ScheduleCreateParams(TypedDict, total=False): + cron_schedule: Required[str] + """ + Cron expression defining when the agent runs (e.g., "0 9 \\** \\** \\**" for daily at + 9am UTC) + """ + + name: Required[str] + """Human-readable name for the schedule""" + + prompt: Required[str] + """The prompt/instruction for the agent to execute""" + + agent_config: AmbientAgentConfigParam + """Configuration for an ambient agent run""" + + enabled: bool + """Whether the schedule should be active immediately""" + + team: bool + """ + Whether to create a team-owned schedule. Defaults to true for users on a single + team. + """ diff --git a/src/warp_agent_sdk/types/agent/schedule_delete_response.py b/src/warp_agent_sdk/types/agent/schedule_delete_response.py new file mode 100644 index 0000000..af8e5fd --- /dev/null +++ b/src/warp_agent_sdk/types/agent/schedule_delete_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["ScheduleDeleteResponse"] + + +class ScheduleDeleteResponse(BaseModel): + success: bool + """Whether the deletion was successful""" diff --git a/src/warp_agent_sdk/types/agent/schedule_list_response.py b/src/warp_agent_sdk/types/agent/schedule_list_response.py new file mode 100644 index 0000000..e0983ef --- /dev/null +++ b/src/warp_agent_sdk/types/agent/schedule_list_response.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 .scheduled_agent_item import ScheduledAgentItem + +__all__ = ["ScheduleListResponse"] + + +class ScheduleListResponse(BaseModel): + schedules: List[ScheduledAgentItem] + """List of scheduled agents""" diff --git a/src/warp_agent_sdk/types/agent/schedule_update_params.py b/src/warp_agent_sdk/types/agent/schedule_update_params.py new file mode 100644 index 0000000..111463b --- /dev/null +++ b/src/warp_agent_sdk/types/agent/schedule_update_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ..ambient_agent_config_param import AmbientAgentConfigParam + +__all__ = ["ScheduleUpdateParams"] + + +class ScheduleUpdateParams(TypedDict, total=False): + cron_schedule: Required[str] + """Cron expression defining when the agent runs""" + + enabled: Required[bool] + """Whether the schedule should be active""" + + name: Required[str] + """Human-readable name for the schedule""" + + prompt: Required[str] + """The prompt/instruction for the agent to execute""" + + agent_config: AmbientAgentConfigParam + """Configuration for an ambient agent run""" diff --git a/src/warp_agent_sdk/types/agent/scheduled_agent_item.py b/src/warp_agent_sdk/types/agent/scheduled_agent_item.py new file mode 100644 index 0000000..d629420 --- /dev/null +++ b/src/warp_agent_sdk/types/agent/scheduled_agent_item.py @@ -0,0 +1,63 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ..._models import BaseModel +from ..run_creator_info import RunCreatorInfo +from ..ambient_agent_config import AmbientAgentConfig +from ..cloud_environment_config import CloudEnvironmentConfig + +__all__ = ["ScheduledAgentItem", "History"] + + +class History(BaseModel): + """Scheduler-derived history metadata for a scheduled agent""" + + last_ran: Optional[datetime] = None + """Timestamp of the last successful run (RFC3339)""" + + next_run: Optional[datetime] = None + """Timestamp of the next scheduled run (RFC3339)""" + + +class ScheduledAgentItem(BaseModel): + id: str + """Unique identifier for the scheduled agent""" + + created_at: datetime + """Timestamp when the schedule was created (RFC3339)""" + + cron_schedule: str + """ + Cron expression defining when the agent runs (e.g., "0 9 \\** \\** \\**" for daily at + 9am UTC) + """ + + enabled: bool + """Whether the schedule is currently active""" + + name: str + """Human-readable name for the schedule""" + + prompt: str + """The prompt/instruction for the agent to execute""" + + updated_at: datetime + """Timestamp when the schedule was last updated (RFC3339)""" + + agent_config: Optional[AmbientAgentConfig] = None + """Configuration for an ambient agent run""" + + created_by: Optional[RunCreatorInfo] = None + + environment: Optional[CloudEnvironmentConfig] = None + """Configuration for a cloud environment used by scheduled agents""" + + history: Optional[History] = None + """Scheduler-derived history metadata for a scheduled agent""" + + last_spawn_error: Optional[str] = None + """Error message from the last failed spawn attempt, if any""" + + updated_by: Optional[RunCreatorInfo] = None diff --git a/src/warp_agent_sdk/types/agent_list_params.py b/src/warp_agent_sdk/types/agent_list_params.py new file mode 100644 index 0000000..75ffda7 --- /dev/null +++ b/src/warp_agent_sdk/types/agent_list_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_extensions import TypedDict + +__all__ = ["AgentListParams"] + + +class AgentListParams(TypedDict, total=False): + repo: str + """ + Optional repository specification to list agents from (format: "owner/repo"). If + not provided, lists agents from all accessible environments. + """ diff --git a/src/warp_agent_sdk/types/agent_list_response.py b/src/warp_agent_sdk/types/agent_list_response.py new file mode 100644 index 0000000..3a733a1 --- /dev/null +++ b/src/warp_agent_sdk/types/agent_list_response.py @@ -0,0 +1,58 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel + +__all__ = ["AgentListResponse", "Agent", "AgentVariant", "AgentVariantEnvironment", "AgentVariantSource"] + + +class AgentVariantEnvironment(BaseModel): + name: str + """Human-readable name of the environment""" + + uid: str + """Unique identifier for the environment""" + + +class AgentVariantSource(BaseModel): + name: str + """GitHub repository name""" + + owner: str + """GitHub repository owner""" + + skill_path: str + """Path to the skill definition file within the repository""" + + +class AgentVariant(BaseModel): + id: str + """ + Stable identifier for this skill variant. Format: "{owner}/{repo}:{skill_path}" + Example: "warpdotdev/warp-server:.claude/skills/deploy/SKILL.md" + """ + + base_prompt: str + """Base prompt/instructions for the agent""" + + description: str + """Description of the agent variant""" + + environments: List[AgentVariantEnvironment] + """Environments where this agent variant is available""" + + source: AgentVariantSource + + +class Agent(BaseModel): + name: str + """Human-readable name of the agent""" + + variants: List[AgentVariant] + """Available variants of this agent""" + + +class AgentListResponse(BaseModel): + agents: List[Agent] + """List of available agents""" diff --git a/src/warp_agent_sdk/types/ambient_agent_config.py b/src/warp_agent_sdk/types/ambient_agent_config.py index 80db2ba..6c18018 100644 --- a/src/warp_agent_sdk/types/ambient_agent_config.py +++ b/src/warp_agent_sdk/types/ambient_agent_config.py @@ -1,37 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional +from typing import Dict, Optional from pydantic import Field as FieldInfo from .._models import BaseModel +from .mcp_server_config import McpServerConfig -__all__ = ["AmbientAgentConfig", "McpServers"] - - -class McpServers(BaseModel): - """Configuration for an MCP server. - - Must have exactly one of: warp_id, command, or url. - """ - - args: Optional[List[str]] = None - """Stdio transport - command arguments""" - - command: Optional[str] = None - """Stdio transport - command to run""" - - env: Optional[Dict[str, str]] = None - """Environment variables for the server""" - - headers: Optional[Dict[str, str]] = None - """HTTP headers for SSE/HTTP transport""" - - url: Optional[str] = None - """SSE/HTTP transport - server URL""" - - warp_id: Optional[str] = None - """Reference to a Warp shared MCP server by UUID""" +__all__ = ["AmbientAgentConfig"] class AmbientAgentConfig(BaseModel): @@ -43,7 +19,7 @@ class AmbientAgentConfig(BaseModel): environment_id: Optional[str] = None """UID of the environment to run the agent in""" - mcp_servers: Optional[Dict[str, McpServers]] = None + mcp_servers: Optional[Dict[str, McpServerConfig]] = None """Map of MCP server configurations by name""" api_model_id: Optional[str] = FieldInfo(alias="model_id", default=None) @@ -52,6 +28,14 @@ class AmbientAgentConfig(BaseModel): name: Optional[str] = None """Config name for searchability and traceability""" + skill_spec: Optional[str] = None + """ + Skill specification identifying which agent skill to use. Format: + "{owner}/{repo}:{skill_path}" Example: + "warpdotdev/warp-server:.claude/skills/deploy/SKILL.md" Use the list agents + endpoint to discover available skills. + """ + worker_host: Optional[str] = None """ Self-hosted worker ID that should execute this task. If not specified or set to diff --git a/src/warp_agent_sdk/types/ambient_agent_config_param.py b/src/warp_agent_sdk/types/ambient_agent_config_param.py index 5c8e711..9aeedf0 100644 --- a/src/warp_agent_sdk/types/ambient_agent_config_param.py +++ b/src/warp_agent_sdk/types/ambient_agent_config_param.py @@ -5,34 +5,9 @@ from typing import Dict from typing_extensions import TypedDict -from .._types import SequenceNotStr +from .mcp_server_config_param import McpServerConfigParam -__all__ = ["AmbientAgentConfigParam", "McpServers"] - - -class McpServers(TypedDict, total=False): - """Configuration for an MCP server. - - Must have exactly one of: warp_id, command, or url. - """ - - args: SequenceNotStr[str] - """Stdio transport - command arguments""" - - command: str - """Stdio transport - command to run""" - - env: Dict[str, str] - """Environment variables for the server""" - - headers: Dict[str, str] - """HTTP headers for SSE/HTTP transport""" - - url: str - """SSE/HTTP transport - server URL""" - - warp_id: str - """Reference to a Warp shared MCP server by UUID""" +__all__ = ["AmbientAgentConfigParam"] class AmbientAgentConfigParam(TypedDict, total=False): @@ -44,7 +19,7 @@ class AmbientAgentConfigParam(TypedDict, total=False): environment_id: str """UID of the environment to run the agent in""" - mcp_servers: Dict[str, McpServers] + mcp_servers: Dict[str, McpServerConfigParam] """Map of MCP server configurations by name""" model_id: str @@ -53,6 +28,14 @@ class AmbientAgentConfigParam(TypedDict, total=False): name: str """Config name for searchability and traceability""" + skill_spec: str + """ + Skill specification identifying which agent skill to use. Format: + "{owner}/{repo}:{skill_path}" Example: + "warpdotdev/warp-server:.claude/skills/deploy/SKILL.md" Use the list agents + endpoint to discover available skills. + """ + worker_host: str """ Self-hosted worker ID that should execute this task. If not specified or set to diff --git a/src/warp_agent_sdk/types/cloud_environment_config.py b/src/warp_agent_sdk/types/cloud_environment_config.py new file mode 100644 index 0000000..732c645 --- /dev/null +++ b/src/warp_agent_sdk/types/cloud_environment_config.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from .._models import BaseModel + +__all__ = ["CloudEnvironmentConfig", "GitHubRepo"] + + +class GitHubRepo(BaseModel): + owner: str + """GitHub repository owner (user or organization)""" + + repo: str + """GitHub repository name""" + + +class CloudEnvironmentConfig(BaseModel): + """Configuration for a cloud environment used by scheduled agents""" + + description: Optional[str] = None + """Optional description of the environment""" + + docker_image: Optional[str] = None + """Docker image to use (e.g., "ubuntu:latest" or "registry/repo:tag")""" + + github_repos: Optional[List[GitHubRepo]] = None + """List of GitHub repositories to clone into the environment""" + + name: Optional[str] = None + """Human-readable name for the environment""" + + setup_commands: Optional[List[str]] = None + """Shell commands to run during environment setup""" diff --git a/src/warp_agent_sdk/types/mcp_server_config.py b/src/warp_agent_sdk/types/mcp_server_config.py new file mode 100644 index 0000000..c9b7134 --- /dev/null +++ b/src/warp_agent_sdk/types/mcp_server_config.py @@ -0,0 +1,32 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from .._models import BaseModel + +__all__ = ["McpServerConfig"] + + +class McpServerConfig(BaseModel): + """Configuration for an MCP server. + + Must have exactly one of: warp_id, command, or url. + """ + + args: Optional[List[str]] = None + """Stdio transport - command arguments""" + + command: Optional[str] = None + """Stdio transport - command to run""" + + env: Optional[Dict[str, str]] = None + """Environment variables for the server""" + + headers: Optional[Dict[str, str]] = None + """HTTP headers for SSE/HTTP transport""" + + url: Optional[str] = None + """SSE/HTTP transport - server URL""" + + warp_id: Optional[str] = None + """Reference to a Warp shared MCP server by UUID""" diff --git a/src/warp_agent_sdk/types/mcp_server_config_param.py b/src/warp_agent_sdk/types/mcp_server_config_param.py new file mode 100644 index 0000000..63a63aa --- /dev/null +++ b/src/warp_agent_sdk/types/mcp_server_config_param.py @@ -0,0 +1,35 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict +from typing_extensions import TypedDict + +from .._types import SequenceNotStr + +__all__ = ["McpServerConfigParam"] + + +class McpServerConfigParam(TypedDict, total=False): + """Configuration for an MCP server. + + Must have exactly one of: warp_id, command, or url. + """ + + args: SequenceNotStr[str] + """Stdio transport - command arguments""" + + command: str + """Stdio transport - command to run""" + + env: Dict[str, str] + """Environment variables for the server""" + + headers: Dict[str, str] + """HTTP headers for SSE/HTTP transport""" + + url: str + """SSE/HTTP transport - server URL""" + + warp_id: str + """Reference to a Warp shared MCP server by UUID""" diff --git a/src/warp_agent_sdk/types/run_creator_info.py b/src/warp_agent_sdk/types/run_creator_info.py new file mode 100644 index 0000000..952ca60 --- /dev/null +++ b/src/warp_agent_sdk/types/run_creator_info.py @@ -0,0 +1,22 @@ +# 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__ = ["RunCreatorInfo"] + + +class RunCreatorInfo(BaseModel): + display_name: Optional[str] = None + """Display name of the creator""" + + photo_url: Optional[str] = None + """URL to the creator's photo""" + + type: Optional[Literal["user", "service_account"]] = None + """Type of the creator principal""" + + uid: Optional[str] = None + """Unique identifier of the creator""" diff --git a/tests/api_resources/agent/test_runs.py b/tests/api_resources/agent/test_runs.py index 94cf8b8..c5eb4b4 100644 --- a/tests/api_resources/agent/test_runs.py +++ b/tests/api_resources/agent/test_runs.py @@ -105,6 +105,48 @@ def test_streaming_response_list(self, client: WarpAPI) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_cancel(self, client: WarpAPI) -> None: + run = client.agent.runs.cancel( + "runId", + ) + assert_matches_type(str, run, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_cancel(self, client: WarpAPI) -> None: + response = client.agent.runs.with_raw_response.cancel( + "runId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = response.parse() + assert_matches_type(str, run, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_cancel(self, client: WarpAPI) -> None: + with client.agent.runs.with_streaming_response.cancel( + "runId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = response.parse() + assert_matches_type(str, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_cancel(self, client: WarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + client.agent.runs.with_raw_response.cancel( + "", + ) + class TestAsyncRuns: parametrize = pytest.mark.parametrize( @@ -197,3 +239,45 @@ async def test_streaming_response_list(self, async_client: AsyncWarpAPI) -> None assert_matches_type(RunListResponse, run, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_cancel(self, async_client: AsyncWarpAPI) -> None: + run = await async_client.agent.runs.cancel( + "runId", + ) + assert_matches_type(str, run, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_cancel(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.runs.with_raw_response.cancel( + "runId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + run = await response.parse() + assert_matches_type(str, run, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_cancel(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.runs.with_streaming_response.cancel( + "runId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + run = await response.parse() + assert_matches_type(str, run, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_cancel(self, async_client: AsyncWarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"): + await async_client.agent.runs.with_raw_response.cancel( + "", + ) diff --git a/tests/api_resources/agent/test_schedules.py b/tests/api_resources/agent/test_schedules.py new file mode 100644 index 0000000..d8a8a61 --- /dev/null +++ b/tests/api_resources/agent/test_schedules.py @@ -0,0 +1,736 @@ +# 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 warp_agent_sdk import WarpAPI, AsyncWarpAPI +from warp_agent_sdk.types.agent import ( + ScheduledAgentItem, + ScheduleListResponse, + ScheduleDeleteResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSchedules: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.create( + cron_schedule="0 9 * * *", + name="Daily Code Review", + prompt="Review open pull requests and provide feedback", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.create( + cron_schedule="0 9 * * *", + name="Daily Code Review", + prompt="Review open pull requests and provide feedback", + agent_config={ + "base_prompt": "base_prompt", + "environment_id": "environment_id", + "mcp_servers": { + "foo": { + "args": ["string"], + "command": "command", + "env": {"foo": "string"}, + "headers": {"foo": "string"}, + "url": "https://example.com", + "warp_id": "warp_id", + } + }, + "model_id": "model_id", + "name": "name", + "skill_spec": "skill_spec", + "worker_host": "worker_host", + }, + enabled=True, + team=True, + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: WarpAPI) -> None: + response = client.agent.schedules.with_raw_response.create( + cron_schedule="0 9 * * *", + name="Daily Code Review", + prompt="Review open pull requests and provide feedback", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: WarpAPI) -> None: + with client.agent.schedules.with_streaming_response.create( + cron_schedule="0 9 * * *", + name="Daily Code Review", + prompt="Review open pull requests and provide feedback", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_retrieve(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.retrieve( + "scheduleId", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: WarpAPI) -> None: + response = client.agent.schedules.with_raw_response.retrieve( + "scheduleId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: WarpAPI) -> None: + with client.agent.schedules.with_streaming_response.retrieve( + "scheduleId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: WarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + client.agent.schedules.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.update( + schedule_id="scheduleId", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.update( + schedule_id="scheduleId", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + agent_config={ + "base_prompt": "base_prompt", + "environment_id": "environment_id", + "mcp_servers": { + "foo": { + "args": ["string"], + "command": "command", + "env": {"foo": "string"}, + "headers": {"foo": "string"}, + "url": "https://example.com", + "warp_id": "warp_id", + } + }, + "model_id": "model_id", + "name": "name", + "skill_spec": "skill_spec", + "worker_host": "worker_host", + }, + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_update(self, client: WarpAPI) -> None: + response = client.agent.schedules.with_raw_response.update( + schedule_id="scheduleId", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_update(self, client: WarpAPI) -> None: + with client.agent.schedules.with_streaming_response.update( + schedule_id="scheduleId", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_update(self, client: WarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + client.agent.schedules.with_raw_response.update( + schedule_id="", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.list() + assert_matches_type(ScheduleListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: WarpAPI) -> None: + response = client.agent.schedules.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert_matches_type(ScheduleListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: WarpAPI) -> None: + with client.agent.schedules.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert_matches_type(ScheduleListResponse, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_delete(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.delete( + "scheduleId", + ) + assert_matches_type(ScheduleDeleteResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_delete(self, client: WarpAPI) -> None: + response = client.agent.schedules.with_raw_response.delete( + "scheduleId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert_matches_type(ScheduleDeleteResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: WarpAPI) -> None: + with client.agent.schedules.with_streaming_response.delete( + "scheduleId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert_matches_type(ScheduleDeleteResponse, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_delete(self, client: WarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + client.agent.schedules.with_raw_response.delete( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_pause(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.pause( + "scheduleId", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_pause(self, client: WarpAPI) -> None: + response = client.agent.schedules.with_raw_response.pause( + "scheduleId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_pause(self, client: WarpAPI) -> None: + with client.agent.schedules.with_streaming_response.pause( + "scheduleId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_pause(self, client: WarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + client.agent.schedules.with_raw_response.pause( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_resume(self, client: WarpAPI) -> None: + schedule = client.agent.schedules.resume( + "scheduleId", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_resume(self, client: WarpAPI) -> None: + response = client.agent.schedules.with_raw_response.resume( + "scheduleId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_resume(self, client: WarpAPI) -> None: + with client.agent.schedules.with_streaming_response.resume( + "scheduleId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_resume(self, client: WarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + client.agent.schedules.with_raw_response.resume( + "", + ) + + +class TestAsyncSchedules: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.create( + cron_schedule="0 9 * * *", + name="Daily Code Review", + prompt="Review open pull requests and provide feedback", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.create( + cron_schedule="0 9 * * *", + name="Daily Code Review", + prompt="Review open pull requests and provide feedback", + agent_config={ + "base_prompt": "base_prompt", + "environment_id": "environment_id", + "mcp_servers": { + "foo": { + "args": ["string"], + "command": "command", + "env": {"foo": "string"}, + "headers": {"foo": "string"}, + "url": "https://example.com", + "warp_id": "warp_id", + } + }, + "model_id": "model_id", + "name": "name", + "skill_spec": "skill_spec", + "worker_host": "worker_host", + }, + enabled=True, + team=True, + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.schedules.with_raw_response.create( + cron_schedule="0 9 * * *", + name="Daily Code Review", + prompt="Review open pull requests and provide feedback", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.schedules.with_streaming_response.create( + cron_schedule="0 9 * * *", + name="Daily Code Review", + prompt="Review open pull requests and provide feedback", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.retrieve( + "scheduleId", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.schedules.with_raw_response.retrieve( + "scheduleId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.schedules.with_streaming_response.retrieve( + "scheduleId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncWarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + await async_client.agent.schedules.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.update( + schedule_id="scheduleId", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.update( + schedule_id="scheduleId", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + agent_config={ + "base_prompt": "base_prompt", + "environment_id": "environment_id", + "mcp_servers": { + "foo": { + "args": ["string"], + "command": "command", + "env": {"foo": "string"}, + "headers": {"foo": "string"}, + "url": "https://example.com", + "warp_id": "warp_id", + } + }, + "model_id": "model_id", + "name": "name", + "skill_spec": "skill_spec", + "worker_host": "worker_host", + }, + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.schedules.with_raw_response.update( + schedule_id="scheduleId", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.schedules.with_streaming_response.update( + schedule_id="scheduleId", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncWarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + await async_client.agent.schedules.with_raw_response.update( + schedule_id="", + cron_schedule="cron_schedule", + enabled=True, + name="name", + prompt="prompt", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.list() + assert_matches_type(ScheduleListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.schedules.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert_matches_type(ScheduleListResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.schedules.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert_matches_type(ScheduleListResponse, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.delete( + "scheduleId", + ) + assert_matches_type(ScheduleDeleteResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.schedules.with_raw_response.delete( + "scheduleId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert_matches_type(ScheduleDeleteResponse, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.schedules.with_streaming_response.delete( + "scheduleId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert_matches_type(ScheduleDeleteResponse, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_delete(self, async_client: AsyncWarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + await async_client.agent.schedules.with_raw_response.delete( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_pause(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.pause( + "scheduleId", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_pause(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.schedules.with_raw_response.pause( + "scheduleId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_pause(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.schedules.with_streaming_response.pause( + "scheduleId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_pause(self, async_client: AsyncWarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + await async_client.agent.schedules.with_raw_response.pause( + "", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_resume(self, async_client: AsyncWarpAPI) -> None: + schedule = await async_client.agent.schedules.resume( + "scheduleId", + ) + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_resume(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.schedules.with_raw_response.resume( + "scheduleId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_resume(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.schedules.with_streaming_response.resume( + "scheduleId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + schedule = await response.parse() + assert_matches_type(ScheduledAgentItem, schedule, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_resume(self, async_client: AsyncWarpAPI) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"): + await async_client.agent.schedules.with_raw_response.resume( + "", + ) diff --git a/tests/api_resources/test_agent.py b/tests/api_resources/test_agent.py index 84eeddf..ad91f17 100644 --- a/tests/api_resources/test_agent.py +++ b/tests/api_resources/test_agent.py @@ -9,7 +9,10 @@ from tests.utils import assert_matches_type from warp_agent_sdk import WarpAPI, AsyncWarpAPI -from warp_agent_sdk.types import AgentRunResponse +from warp_agent_sdk.types import ( + AgentRunResponse, + AgentListResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +20,42 @@ class TestAgent: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list(self, client: WarpAPI) -> None: + agent = client.agent.list() + assert_matches_type(AgentListResponse, agent, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: WarpAPI) -> None: + agent = client.agent.list( + repo="repo", + ) + assert_matches_type(AgentListResponse, agent, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_list(self, client: WarpAPI) -> None: + response = client.agent.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = response.parse() + assert_matches_type(AgentListResponse, agent, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_list(self, client: WarpAPI) -> None: + with client.agent.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = response.parse() + assert_matches_type(AgentListResponse, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_run(self, client: WarpAPI) -> None: @@ -45,6 +84,7 @@ def test_method_run_with_all_params(self, client: WarpAPI) -> None: }, "model_id": "model_id", "name": "name", + "skill_spec": "skill_spec", "worker_host": "worker_host", }, team=True, @@ -84,6 +124,42 @@ class TestAsyncAgent: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWarpAPI) -> None: + agent = await async_client.agent.list() + assert_matches_type(AgentListResponse, agent, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWarpAPI) -> None: + agent = await async_client.agent.list( + repo="repo", + ) + assert_matches_type(AgentListResponse, agent, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWarpAPI) -> None: + response = await async_client.agent.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + agent = await response.parse() + assert_matches_type(AgentListResponse, agent, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWarpAPI) -> None: + async with async_client.agent.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + agent = await response.parse() + assert_matches_type(AgentListResponse, agent, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_run(self, async_client: AsyncWarpAPI) -> None: @@ -112,6 +188,7 @@ async def test_method_run_with_all_params(self, async_client: AsyncWarpAPI) -> N }, "model_id": "model_id", "name": "name", + "skill_spec": "skill_spec", "worker_host": "worker_host", }, team=True, From 8105304447af1bb4a28bf629cf116063621abfb5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:49:29 +0000 Subject: [PATCH 10/11] feat(api): add models for agent skill, user profile --- .stats.yml | 2 +- api.md | 3 +- src/warp_agent_sdk/types/__init__.py | 3 +- src/warp_agent_sdk/types/agent/run_item.py | 4 +- .../types/agent/scheduled_agent_item.py | 6 +-- .../types/agent_list_response.py | 51 ++---------------- src/warp_agent_sdk/types/agent_skill.py | 53 +++++++++++++++++++ .../{run_creator_info.py => user_profile.py} | 4 +- 8 files changed, 68 insertions(+), 58 deletions(-) create mode 100644 src/warp_agent_sdk/types/agent_skill.py rename src/warp_agent_sdk/types/{run_creator_info.py => user_profile.py} (89%) diff --git a/.stats.yml b/.stats.yml index 7759d9b..885b622 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-3ee19d97da1711b8dfcba85f38c3c345fa96aaf78ea7f025e1ae490d17e97517.yml openapi_spec_hash: 2d03e7a248b1be5bd5600b62912cdab8 -config_hash: 9db55bfa03e8dd5e251342bd7491408c +config_hash: 07820b17df23cbea39cb77fa05292538 diff --git a/api.md b/api.md index 59b2005..ff6ba6f 100644 --- a/api.md +++ b/api.md @@ -4,10 +4,11 @@ Types: ```python from warp_agent_sdk.types import ( + AgentSkill, AmbientAgentConfig, CloudEnvironmentConfig, McpServerConfig, - RunCreatorInfo, + UserProfile, AgentListResponse, AgentRunResponse, ) diff --git a/src/warp_agent_sdk/types/__init__.py b/src/warp_agent_sdk/types/__init__.py index c827ee7..a69bb94 100644 --- a/src/warp_agent_sdk/types/__init__.py +++ b/src/warp_agent_sdk/types/__init__.py @@ -2,8 +2,9 @@ from __future__ import annotations +from .agent_skill import AgentSkill as AgentSkill +from .user_profile import UserProfile as UserProfile from .agent_run_params import AgentRunParams as AgentRunParams -from .run_creator_info import RunCreatorInfo as RunCreatorInfo from .agent_list_params import AgentListParams as AgentListParams from .mcp_server_config import McpServerConfig as McpServerConfig from .agent_run_response import AgentRunResponse as AgentRunResponse diff --git a/src/warp_agent_sdk/types/agent/run_item.py b/src/warp_agent_sdk/types/agent/run_item.py index a8f310a..1bdb698 100644 --- a/src/warp_agent_sdk/types/agent/run_item.py +++ b/src/warp_agent_sdk/types/agent/run_item.py @@ -5,9 +5,9 @@ from ..._models import BaseModel from .run_state import RunState +from ..user_profile import UserProfile from .artifact_item import ArtifactItem from .run_source_type import RunSourceType -from ..run_creator_info import RunCreatorInfo from ..ambient_agent_config import AmbientAgentConfig __all__ = ["RunItem", "RequestUsage", "StatusMessage"] @@ -70,7 +70,7 @@ class RunItem(BaseModel): conversation_id: Optional[str] = None """UUID of the conversation associated with the run""" - creator: Optional[RunCreatorInfo] = None + creator: Optional[UserProfile] = None is_sandbox_running: Optional[bool] = None """Whether the sandbox environment is currently running""" diff --git a/src/warp_agent_sdk/types/agent/scheduled_agent_item.py b/src/warp_agent_sdk/types/agent/scheduled_agent_item.py index d629420..141ea42 100644 --- a/src/warp_agent_sdk/types/agent/scheduled_agent_item.py +++ b/src/warp_agent_sdk/types/agent/scheduled_agent_item.py @@ -4,7 +4,7 @@ from datetime import datetime from ..._models import BaseModel -from ..run_creator_info import RunCreatorInfo +from ..user_profile import UserProfile from ..ambient_agent_config import AmbientAgentConfig from ..cloud_environment_config import CloudEnvironmentConfig @@ -49,7 +49,7 @@ class ScheduledAgentItem(BaseModel): agent_config: Optional[AmbientAgentConfig] = None """Configuration for an ambient agent run""" - created_by: Optional[RunCreatorInfo] = None + created_by: Optional[UserProfile] = None environment: Optional[CloudEnvironmentConfig] = None """Configuration for a cloud environment used by scheduled agents""" @@ -60,4 +60,4 @@ class ScheduledAgentItem(BaseModel): last_spawn_error: Optional[str] = None """Error message from the last failed spawn attempt, if any""" - updated_by: Optional[RunCreatorInfo] = None + updated_by: Optional[UserProfile] = None diff --git a/src/warp_agent_sdk/types/agent_list_response.py b/src/warp_agent_sdk/types/agent_list_response.py index 3a733a1..b32e870 100644 --- a/src/warp_agent_sdk/types/agent_list_response.py +++ b/src/warp_agent_sdk/types/agent_list_response.py @@ -3,56 +3,11 @@ from typing import List from .._models import BaseModel +from .agent_skill import AgentSkill -__all__ = ["AgentListResponse", "Agent", "AgentVariant", "AgentVariantEnvironment", "AgentVariantSource"] - - -class AgentVariantEnvironment(BaseModel): - name: str - """Human-readable name of the environment""" - - uid: str - """Unique identifier for the environment""" - - -class AgentVariantSource(BaseModel): - name: str - """GitHub repository name""" - - owner: str - """GitHub repository owner""" - - skill_path: str - """Path to the skill definition file within the repository""" - - -class AgentVariant(BaseModel): - id: str - """ - Stable identifier for this skill variant. Format: "{owner}/{repo}:{skill_path}" - Example: "warpdotdev/warp-server:.claude/skills/deploy/SKILL.md" - """ - - base_prompt: str - """Base prompt/instructions for the agent""" - - description: str - """Description of the agent variant""" - - environments: List[AgentVariantEnvironment] - """Environments where this agent variant is available""" - - source: AgentVariantSource - - -class Agent(BaseModel): - name: str - """Human-readable name of the agent""" - - variants: List[AgentVariant] - """Available variants of this agent""" +__all__ = ["AgentListResponse"] class AgentListResponse(BaseModel): - agents: List[Agent] + agents: List[AgentSkill] """List of available agents""" diff --git a/src/warp_agent_sdk/types/agent_skill.py b/src/warp_agent_sdk/types/agent_skill.py new file mode 100644 index 0000000..a7ff256 --- /dev/null +++ b/src/warp_agent_sdk/types/agent_skill.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from .._models import BaseModel + +__all__ = ["AgentSkill", "Variant", "VariantEnvironment", "VariantSource"] + + +class VariantEnvironment(BaseModel): + name: str + """Human-readable name of the environment""" + + uid: str + """Unique identifier for the environment""" + + +class VariantSource(BaseModel): + name: str + """GitHub repository name""" + + owner: str + """GitHub repository owner""" + + skill_path: str + """Path to the skill definition file within the repository""" + + +class Variant(BaseModel): + id: str + """ + Stable identifier for this skill variant. Format: "{owner}/{repo}:{skill_path}" + Example: "warpdotdev/warp-server:.claude/skills/deploy/SKILL.md" + """ + + base_prompt: str + """Base prompt/instructions for the agent""" + + description: str + """Description of the agent variant""" + + environments: List[VariantEnvironment] + """Environments where this agent variant is available""" + + source: VariantSource + + +class AgentSkill(BaseModel): + name: str + """Human-readable name of the agent""" + + variants: List[Variant] + """Available variants of this agent""" diff --git a/src/warp_agent_sdk/types/run_creator_info.py b/src/warp_agent_sdk/types/user_profile.py similarity index 89% rename from src/warp_agent_sdk/types/run_creator_info.py rename to src/warp_agent_sdk/types/user_profile.py index 952ca60..ed7bb01 100644 --- a/src/warp_agent_sdk/types/run_creator_info.py +++ b/src/warp_agent_sdk/types/user_profile.py @@ -5,10 +5,10 @@ from .._models import BaseModel -__all__ = ["RunCreatorInfo"] +__all__ = ["UserProfile"] -class RunCreatorInfo(BaseModel): +class UserProfile(BaseModel): display_name: Optional[str] = None """Display name of the creator""" From 570d2689c19a1cfb5ed540b9ec20090161f4f8e8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Feb 2026 00:49:46 +0000 Subject: [PATCH 11/11] release: 0.4.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/warp_agent_sdk/_version.py | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6b7b74c..da59f99 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.3.0" + ".": "0.4.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b3df9a5..ed8c988 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## 0.4.0 (2026-02-03) + +Full Changelog: [v0.3.0...v0.4.0](https://github.com/warpdotdev/warp-sdk-python/compare/v0.3.0...v0.4.0) + +### ⚠ BREAKING CHANGES + +* **api:** catch up openapi, rename tasks -> runs + +### Features + +* **api:** add artifacts, worker_host, and new source types ([4aa1225](https://github.com/warpdotdev/warp-sdk-python/commit/4aa12253c3cd40bbd8460dfe6602fed5080554a4)) +* **api:** add models for agent skill, user profile ([8105304](https://github.com/warpdotdev/warp-sdk-python/commit/8105304447af1bb4a28bf629cf116063621abfb5)) +* **api:** add schedules, agent list, skill-spec ([1927318](https://github.com/warpdotdev/warp-sdk-python/commit/1927318b5b2a4115b3f1ec2be7db86f1e8e0d321)) +* **api:** catch up openapi, rename tasks -> runs ([fe8c5b3](https://github.com/warpdotdev/warp-sdk-python/commit/fe8c5b3f2da07a415ecd21d0f1ae5985e45f1a4d)) +* **api:** created at filter in list view ([71adb45](https://github.com/warpdotdev/warp-sdk-python/commit/71adb45481ca3248d892e62ff767b9ea011e7f21)) +* **client:** add custom JSON encoder for extended type support ([68f4b61](https://github.com/warpdotdev/warp-sdk-python/commit/68f4b61fd83b1f5576b72ad6ea3a28cd58e06a68)) +* **client:** add support for binary request streaming ([c64e6c4](https://github.com/warpdotdev/warp-sdk-python/commit/c64e6c44b6d02a63609d02f4976a7977dc3b0045)) + + +### Chores + +* **ci:** upgrade `actions/github-script` ([713ae7d](https://github.com/warpdotdev/warp-sdk-python/commit/713ae7d03ee5f04eeac42b9a213d96ccc91d06d7)) +* **internal:** update `actions/checkout` version ([fd0a90f](https://github.com/warpdotdev/warp-sdk-python/commit/fd0a90fae32b8a7e352217e3672e63b9b4dbc594)) + + +### Documentation + +* **dev:** Add WARP.md file ([7f1b835](https://github.com/warpdotdev/warp-sdk-python/commit/7f1b835240574dc517d424dca84251f86c4b1276)) + ## 0.3.0 (2026-01-05) Full Changelog: [v0.2.1...v0.3.0](https://github.com/warpdotdev/warp-sdk-python/compare/v0.2.1...v0.3.0) diff --git a/pyproject.toml b/pyproject.toml index f514cde..1f0ef39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "warp-agent-sdk" -version = "0.3.0" +version = "0.4.0" description = "The official Python library for the warp-api API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/warp_agent_sdk/_version.py b/src/warp_agent_sdk/_version.py index 3f8939a..62e4ed0 100644 --- a/src/warp_agent_sdk/_version.py +++ b/src/warp_agent_sdk/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "warp_agent_sdk" -__version__ = "0.3.0" # x-release-please-version +__version__ = "0.4.0" # x-release-please-version