diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f7014c3..a713055 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.11.0"
+ ".": "0.12.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 51b5464..fd4484e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 14
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-8f9c749573846b07a55a3131b66456f0a592838c6bfc986ab30948df66cd6f11.yml
-openapi_spec_hash: 59f1ac98ad6cf13b12c59196bcecffd7
-config_hash: 60052b2c1c0862014416821aba875574
+configured_endpoints: 19
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta%2Fwarp-api-caba3084bc4ae52a6847df96dd0f8fe0a3b2bc9801ff4d2935092ae7e2c794f7.yml
+openapi_spec_hash: d3dd12a5a9bffb132239a2a206890602
+config_hash: c5fc921cc04f541a85f92299f365eba6
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 543cbc1..2827223 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,43 @@
# Changelog
+## 0.12.0 (2026-04-17)
+
+Full Changelog: [v0.11.0...v0.12.0](https://github.com/warpdotdev/oz-sdk-python/compare/v0.11.0...v0.12.0)
+
+### Features
+
+* Add parent_run_id filter to List runs endpoint ([33cf04c](https://github.com/warpdotdev/oz-sdk-python/commit/33cf04c0777d53059f8a3c73b925b468276aba2a))
+* Add system prompt to resolve-prompt for harnesses. ([2b7afab](https://github.com/warpdotdev/oz-sdk-python/commit/2b7afabf4a54764ab162ac32f4dbe7fe4274ba72))
+* Add trigger URL to task source. ([9662dbe](https://github.com/warpdotdev/oz-sdk-python/commit/9662dbe10cc5392271b3e56c35cb8ef95ec80eed))
+* Add worker_host to AgentListSource in OpenAPI spec ([ec0982c](https://github.com/warpdotdev/oz-sdk-python/commit/ec0982ce786b2380ab89aa7298b28998c6eade46))
+* **api:** api update ([f94b38b](https://github.com/warpdotdev/oz-sdk-python/commit/f94b38b2ffaf344a198e89ad7ace1d7b8e93c300))
+* **api:** api update ([7f419fa](https://github.com/warpdotdev/oz-sdk-python/commit/7f419faa01908e1b4a613ec2bdd708aed1a05871))
+* **api:** api update ([116f06e](https://github.com/warpdotdev/oz-sdk-python/commit/116f06e5c63e8d015e7dfea47fc22ede53ebb84e))
+* **api:** api update ([66c8521](https://github.com/warpdotdev/oz-sdk-python/commit/66c852194f355cedca2b34b944c398e796f064ab))
+* **api:** api update ([ebd2c55](https://github.com/warpdotdev/oz-sdk-python/commit/ebd2c5520128722d88285aea9dd0caf3242d9a31))
+* **api:** api update ([1ffc53b](https://github.com/warpdotdev/oz-sdk-python/commit/1ffc53b4d69eeb8060faac1903f1ba25f7a4c443))
+* **api:** api update ([cf28804](https://github.com/warpdotdev/oz-sdk-python/commit/cf288045d369ed927db45ac5e5ebd994ccebb547))
+* **api:** api update ([a6d82f1](https://github.com/warpdotdev/oz-sdk-python/commit/a6d82f1a71ad8e0ff5f10c43cc81938babc1444e))
+* **api:** api update ([b9872f2](https://github.com/warpdotdev/oz-sdk-python/commit/b9872f2642191ef6e1d4d5d3542a08f9e2784b9e))
+* **api:** api update ([0ef9c8f](https://github.com/warpdotdev/oz-sdk-python/commit/0ef9c8f483269d203a3edc7b5805f48f4263eb2f))
+* Inject auth secrets via ambient agent config. ([6032a0c](https://github.com/warpdotdev/oz-sdk-python/commit/6032a0ccfe2ce669ce1f815ad853b912d4ca575e))
+* Update public API and graphql to support creating multiple service accounts and API keys ([8d537bc](https://github.com/warpdotdev/oz-sdk-python/commit/8d537bcbe75b344427bd04e5406ab066769f580a))
+
+
+### Bug Fixes
+
+* ensure file data are only sent as 1 parameter ([6720ea9](https://github.com/warpdotdev/oz-sdk-python/commit/6720ea9331a945cf2d030e20b8851a223697aab3))
+
+
+### Performance Improvements
+
+* **client:** optimize file structure copying in multipart requests ([b8e42cc](https://github.com/warpdotdev/oz-sdk-python/commit/b8e42cc08805eef08a3462088ba0e32cf79dac8c))
+
+
+### Chores
+
+* update SDK settings ([f2dd099](https://github.com/warpdotdev/oz-sdk-python/commit/f2dd099128bf4f98e304778556d55e80a4fd219c))
+
## 0.11.0 (2026-04-09)
Full Changelog: [v0.10.1...v0.11.0](https://github.com/warpdotdev/oz-sdk-python/compare/v0.10.1...v0.11.0)
diff --git a/api.md b/api.md
index 3ef6227..918ca45 100644
--- a/api.md
+++ b/api.md
@@ -7,6 +7,7 @@ from oz_agent_sdk.types import (
AgentSkill,
AmbientAgentConfig,
AwsProviderConfig,
+ CloudEnvironment,
CloudEnvironmentConfig,
Error,
ErrorCode,
@@ -16,6 +17,7 @@ from oz_agent_sdk.types import (
UserProfile,
AgentListResponse,
AgentGetArtifactResponse,
+ AgentListEnvironmentsResponse,
AgentRunResponse,
)
```
@@ -24,6 +26,7 @@ Methods:
- client.agent.list(\*\*params) -> AgentListResponse
- client.agent.get_artifact(artifact_uid) -> AgentGetArtifactResponse
+- client.agent.list_environments(\*\*params) -> AgentListEnvironmentsResponse
- client.agent.run(\*\*params) -> AgentRunResponse
## Runs
@@ -69,6 +72,26 @@ Methods:
- client.agent.schedules.pause(schedule_id) -> ScheduledAgentItem
- client.agent.schedules.resume(schedule_id) -> ScheduledAgentItem
+## Agent
+
+Types:
+
+```python
+from oz_agent_sdk.types.agent import (
+ AgentResponse,
+ CreateAgentRequest,
+ ListAgentIdentitiesResponse,
+ UpdateAgentRequest,
+)
+```
+
+Methods:
+
+- client.agent.agent.create(\*\*params) -> AgentResponse
+- client.agent.agent.update(uid, \*\*params) -> AgentResponse
+- client.agent.agent.list() -> ListAgentIdentitiesResponse
+- client.agent.agent.delete(uid) -> None
+
## Sessions
Types:
diff --git a/pyproject.toml b/pyproject.toml
index 01da747..6b28584 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "oz-agent-sdk"
-version = "0.11.0"
+version = "0.12.0"
description = "The official Python library for the oz-api API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/oz_agent_sdk/_files.py b/src/oz_agent_sdk/_files.py
index cc14c14..0fdce17 100644
--- a/src/oz_agent_sdk/_files.py
+++ b/src/oz_agent_sdk/_files.py
@@ -3,8 +3,8 @@
import io
import os
import pathlib
-from typing import overload
-from typing_extensions import TypeGuard
+from typing import Sequence, cast, overload
+from typing_extensions import TypeVar, TypeGuard
import anyio
@@ -17,7 +17,9 @@
HttpxFileContent,
HttpxRequestFiles,
)
-from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
+from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t
+
+_T = TypeVar("_T")
def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
@@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent:
return await anyio.Path(file).read_bytes()
return file
+
+
+def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
+ """Copy only the containers along the given paths.
+
+ Used to guard against mutation by extract_files without copying the entire structure.
+ Only dicts and lists that lie on a path are copied; everything else
+ is returned by reference.
+
+ For example, given paths=[["foo", "files", "file"]] and the structure:
+ {
+ "foo": {
+ "bar": {"baz": {}},
+ "files": {"file": }
+ }
+ }
+ The root dict, "foo", and "files" are copied (they lie on the path).
+ "bar" and "baz" are returned by reference (off the path).
+ """
+ return _deepcopy_with_paths(item, paths, 0)
+
+
+def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
+ if not paths:
+ return item
+ if is_mapping(item):
+ key_to_paths: dict[str, list[Sequence[str]]] = {}
+ for path in paths:
+ if index < len(path):
+ key_to_paths.setdefault(path[index], []).append(path)
+
+ # if no path continues through this mapping, it won't be mutated and copying it is redundant
+ if not key_to_paths:
+ return item
+
+ result = dict(item)
+ for key, subpaths in key_to_paths.items():
+ if key in result:
+ result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
+ return cast(_T, result)
+ if is_list(item):
+ array_paths = [path for path in paths if index < len(path) and path[index] == ""]
+
+ # if no path expects a list here, nothing will be mutated inside it - return by reference
+ if not array_paths:
+ return cast(_T, item)
+ return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
+ return item
diff --git a/src/oz_agent_sdk/_utils/__init__.py b/src/oz_agent_sdk/_utils/__init__.py
index 10cb66d..1c090e5 100644
--- a/src/oz_agent_sdk/_utils/__init__.py
+++ b/src/oz_agent_sdk/_utils/__init__.py
@@ -24,7 +24,6 @@
coerce_integer as coerce_integer,
file_from_path as file_from_path,
strip_not_given as strip_not_given,
- deepcopy_minimal as deepcopy_minimal,
get_async_library as get_async_library,
maybe_coerce_float as maybe_coerce_float,
get_required_header as get_required_header,
diff --git a/src/oz_agent_sdk/_utils/_utils.py b/src/oz_agent_sdk/_utils/_utils.py
index eec7f4a..771859f 100644
--- a/src/oz_agent_sdk/_utils/_utils.py
+++ b/src/oz_agent_sdk/_utils/_utils.py
@@ -86,8 +86,9 @@ def _extract_items(
index += 1
if is_dict(obj):
try:
- # We are at the last entry in the path so we must remove the field
- if (len(path)) == index:
+ # Remove the field if there are no more dict keys in the path,
+ # only "" traversal markers or end.
+ if all(p == "" for p in path[index:]):
item = obj.pop(key)
else:
item = obj[key]
@@ -176,21 +177,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]:
return isinstance(obj, Iterable)
-def deepcopy_minimal(item: _T) -> _T:
- """Minimal reimplementation of copy.deepcopy() that will only copy certain object types:
-
- - mappings, e.g. `dict`
- - list
-
- This is done for performance reasons.
- """
- if is_mapping(item):
- return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()})
- if is_list(item):
- return cast(_T, [deepcopy_minimal(entry) for entry in item])
- return item
-
-
# copied from https://github.com/Rapptz/RoboDanny
def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str:
size = len(seq)
diff --git a/src/oz_agent_sdk/_version.py b/src/oz_agent_sdk/_version.py
index 113b87a..b96fedf 100644
--- a/src/oz_agent_sdk/_version.py
+++ b/src/oz_agent_sdk/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "oz_agent_sdk"
-__version__ = "0.11.0" # x-release-please-version
+__version__ = "0.12.0" # x-release-please-version
diff --git a/src/oz_agent_sdk/resources/agent/agent.py b/src/oz_agent_sdk/resources/agent/agent.py
index b720375..0b12e59 100644
--- a/src/oz_agent_sdk/resources/agent/agent.py
+++ b/src/oz_agent_sdk/resources/agent/agent.py
@@ -7,6 +7,7 @@
import httpx
+from . import agent_ as agent
from .runs import (
RunsResource,
AsyncRunsResource,
@@ -15,7 +16,7 @@
RunsResourceWithStreamingResponse,
AsyncRunsResourceWithStreamingResponse,
)
-from ...types import agent_run_params, agent_list_params
+from ...types import agent_run_params, agent_list_params, agent_list_environments_params
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, async_maybe_transform
from .sessions import (
@@ -47,6 +48,7 @@
from ...types.agent_list_response import AgentListResponse
from ...types.ambient_agent_config_param import AmbientAgentConfigParam
from ...types.agent_get_artifact_response import AgentGetArtifactResponse
+from ...types.agent_list_environments_response import AgentListEnvironmentsResponse
__all__ = ["AgentResource", "AsyncAgentResource"]
@@ -64,6 +66,11 @@ def schedules(self) -> SchedulesResource:
"""Operations for creating and managing scheduled agents"""
return SchedulesResource(self._client)
+ @cached_property
+ def agent(self) -> agent.AgentResource:
+ """Operations for running and managing cloud agents"""
+ return agent.AgentResource(self._client)
+
@cached_property
def sessions(self) -> SessionsResource:
"""Operations for running and managing cloud agents"""
@@ -163,8 +170,9 @@ def get_artifact(
) -> AgentGetArtifactResponse:
"""Retrieve an artifact by its UUID.
- For supported downloadable artifacts, returns
- a time-limited signed download URL.
+ For downloadable file-like artifacts, returns
+ a time-limited signed download URL. For plan artifacts, returns the current plan
+ content inline.
Args:
extra_headers: Send extra headers
@@ -190,6 +198,49 @@ def get_artifact(
),
)
+ def list_environments(
+ self,
+ *,
+ sort_by: Literal["name", "last_updated"] | 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,
+ ) -> AgentListEnvironmentsResponse:
+ """Retrieve cloud environments accessible to the authenticated principal.
+
+ Returns
+ environments the caller owns, has been granted guest access to, or has accessed
+ via link sharing.
+
+ Args:
+ sort_by: Sort order for the returned environments.
+
+ - `name`: alphabetical by environment name
+ - `last_updated`: most recently updated first (default)
+
+ 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/environments",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform({"sort_by": sort_by}, agent_list_environments_params.AgentListEnvironmentsParams),
+ ),
+ cast_to=AgentListEnvironmentsResponse,
+ )
+
def run(
self,
*,
@@ -293,6 +344,11 @@ def schedules(self) -> AsyncSchedulesResource:
"""Operations for creating and managing scheduled agents"""
return AsyncSchedulesResource(self._client)
+ @cached_property
+ def agent(self) -> agent.AsyncAgentResource:
+ """Operations for running and managing cloud agents"""
+ return agent.AsyncAgentResource(self._client)
+
@cached_property
def sessions(self) -> AsyncSessionsResource:
"""Operations for running and managing cloud agents"""
@@ -392,8 +448,9 @@ async def get_artifact(
) -> AgentGetArtifactResponse:
"""Retrieve an artifact by its UUID.
- For supported downloadable artifacts, returns
- a time-limited signed download URL.
+ For downloadable file-like artifacts, returns
+ a time-limited signed download URL. For plan artifacts, returns the current plan
+ content inline.
Args:
extra_headers: Send extra headers
@@ -419,6 +476,51 @@ async def get_artifact(
),
)
+ async def list_environments(
+ self,
+ *,
+ sort_by: Literal["name", "last_updated"] | 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,
+ ) -> AgentListEnvironmentsResponse:
+ """Retrieve cloud environments accessible to the authenticated principal.
+
+ Returns
+ environments the caller owns, has been granted guest access to, or has accessed
+ via link sharing.
+
+ Args:
+ sort_by: Sort order for the returned environments.
+
+ - `name`: alphabetical by environment name
+ - `last_updated`: most recently updated first (default)
+
+ 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/environments",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"sort_by": sort_by}, agent_list_environments_params.AgentListEnvironmentsParams
+ ),
+ ),
+ cast_to=AgentListEnvironmentsResponse,
+ )
+
async def run(
self,
*,
@@ -519,6 +621,9 @@ def __init__(self, agent: AgentResource) -> None:
self.get_artifact = to_raw_response_wrapper(
agent.get_artifact,
)
+ self.list_environments = to_raw_response_wrapper(
+ agent.list_environments,
+ )
self.run = to_raw_response_wrapper(
agent.run,
)
@@ -533,6 +638,11 @@ def schedules(self) -> SchedulesResourceWithRawResponse:
"""Operations for creating and managing scheduled agents"""
return SchedulesResourceWithRawResponse(self._agent.schedules)
+ @cached_property
+ def agent(self) -> agent.AgentResourceWithRawResponse:
+ """Operations for running and managing cloud agents"""
+ return agent.AgentResourceWithRawResponse(self._agent.agent)
+
@cached_property
def sessions(self) -> SessionsResourceWithRawResponse:
"""Operations for running and managing cloud agents"""
@@ -549,6 +659,9 @@ def __init__(self, agent: AsyncAgentResource) -> None:
self.get_artifact = async_to_raw_response_wrapper(
agent.get_artifact,
)
+ self.list_environments = async_to_raw_response_wrapper(
+ agent.list_environments,
+ )
self.run = async_to_raw_response_wrapper(
agent.run,
)
@@ -563,6 +676,11 @@ def schedules(self) -> AsyncSchedulesResourceWithRawResponse:
"""Operations for creating and managing scheduled agents"""
return AsyncSchedulesResourceWithRawResponse(self._agent.schedules)
+ @cached_property
+ def agent(self) -> agent.AsyncAgentResourceWithRawResponse:
+ """Operations for running and managing cloud agents"""
+ return agent.AsyncAgentResourceWithRawResponse(self._agent.agent)
+
@cached_property
def sessions(self) -> AsyncSessionsResourceWithRawResponse:
"""Operations for running and managing cloud agents"""
@@ -579,6 +697,9 @@ def __init__(self, agent: AgentResource) -> None:
self.get_artifact = to_streamed_response_wrapper(
agent.get_artifact,
)
+ self.list_environments = to_streamed_response_wrapper(
+ agent.list_environments,
+ )
self.run = to_streamed_response_wrapper(
agent.run,
)
@@ -593,6 +714,11 @@ def schedules(self) -> SchedulesResourceWithStreamingResponse:
"""Operations for creating and managing scheduled agents"""
return SchedulesResourceWithStreamingResponse(self._agent.schedules)
+ @cached_property
+ def agent(self) -> agent.AgentResourceWithStreamingResponse:
+ """Operations for running and managing cloud agents"""
+ return agent.AgentResourceWithStreamingResponse(self._agent.agent)
+
@cached_property
def sessions(self) -> SessionsResourceWithStreamingResponse:
"""Operations for running and managing cloud agents"""
@@ -609,6 +735,9 @@ def __init__(self, agent: AsyncAgentResource) -> None:
self.get_artifact = async_to_streamed_response_wrapper(
agent.get_artifact,
)
+ self.list_environments = async_to_streamed_response_wrapper(
+ agent.list_environments,
+ )
self.run = async_to_streamed_response_wrapper(
agent.run,
)
@@ -623,6 +752,11 @@ def schedules(self) -> AsyncSchedulesResourceWithStreamingResponse:
"""Operations for creating and managing scheduled agents"""
return AsyncSchedulesResourceWithStreamingResponse(self._agent.schedules)
+ @cached_property
+ def agent(self) -> agent.AsyncAgentResourceWithStreamingResponse:
+ """Operations for running and managing cloud agents"""
+ return agent.AsyncAgentResourceWithStreamingResponse(self._agent.agent)
+
@cached_property
def sessions(self) -> AsyncSessionsResourceWithStreamingResponse:
"""Operations for running and managing cloud agents"""
diff --git a/src/oz_agent_sdk/resources/agent/agent_.py b/src/oz_agent_sdk/resources/agent/agent_.py
new file mode 100644
index 0000000..2c82516
--- /dev/null
+++ b/src/oz_agent_sdk/resources/agent/agent_.py
@@ -0,0 +1,402 @@
+# 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, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...types.agent import agent_create_params, agent_update_params
+from ..._base_client import make_request_options
+from ...types.agent.agent_response import AgentResponse
+from ...types.agent.list_agent_identities_response import ListAgentIdentitiesResponse
+
+__all__ = ["AgentResource", "AsyncAgentResource"]
+
+
+class AgentResource(SyncAPIResource):
+ """Operations for running and managing cloud agents"""
+
+ @cached_property
+ def with_raw_response(self) -> AgentResourceWithRawResponse:
+ """
+ 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/oz-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AgentResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AgentResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/warpdotdev/oz-sdk-python#with_streaming_response
+ """
+ return AgentResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ name: 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,
+ ) -> AgentResponse:
+ """Create a new agent for the caller's team.
+
+ Agents can be used as the execution
+ principal for team-owned runs.
+
+ Args:
+ name: A name for the agent
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/agent/identities",
+ body=maybe_transform({"name": name}, agent_create_params.AgentCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AgentResponse,
+ )
+
+ def update(
+ self,
+ uid: str,
+ *,
+ name: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AgentResponse:
+ """
+ Update an existing agent.
+
+ Args:
+ name: The new name for the agent
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not uid:
+ raise ValueError(f"Expected a non-empty value for `uid` but received {uid!r}")
+ return self._put(
+ path_template("/agent/identities/{uid}", uid=uid),
+ body=maybe_transform({"name": name}, agent_update_params.AgentUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AgentResponse,
+ )
+
+ 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,
+ ) -> ListAgentIdentitiesResponse:
+ """List all agents for the caller's team.
+
+ Each agent includes an `available` flag
+ indicating whether it is within the team's plan limit and may be used for runs.
+ """
+ return self._get(
+ "/agent/identities",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ListAgentIdentitiesResponse,
+ )
+
+ def delete(
+ self,
+ uid: 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,
+ ) -> None:
+ """Delete an agent.
+
+ All API keys associated with the agent are deleted atomically.
+
+ 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 uid:
+ raise ValueError(f"Expected a non-empty value for `uid` but received {uid!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/agent/identities/{uid}", uid=uid),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncAgentResource(AsyncAPIResource):
+ """Operations for running and managing cloud agents"""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncAgentResourceWithRawResponse:
+ """
+ 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/oz-sdk-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncAgentResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncAgentResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/warpdotdev/oz-sdk-python#with_streaming_response
+ """
+ return AsyncAgentResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ name: 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,
+ ) -> AgentResponse:
+ """Create a new agent for the caller's team.
+
+ Agents can be used as the execution
+ principal for team-owned runs.
+
+ Args:
+ name: A name for the agent
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/agent/identities",
+ body=await async_maybe_transform({"name": name}, agent_create_params.AgentCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AgentResponse,
+ )
+
+ async def update(
+ self,
+ uid: str,
+ *,
+ name: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AgentResponse:
+ """
+ Update an existing agent.
+
+ Args:
+ name: The new name for the agent
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not uid:
+ raise ValueError(f"Expected a non-empty value for `uid` but received {uid!r}")
+ return await self._put(
+ path_template("/agent/identities/{uid}", uid=uid),
+ body=await async_maybe_transform({"name": name}, agent_update_params.AgentUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AgentResponse,
+ )
+
+ 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,
+ ) -> ListAgentIdentitiesResponse:
+ """List all agents for the caller's team.
+
+ Each agent includes an `available` flag
+ indicating whether it is within the team's plan limit and may be used for runs.
+ """
+ return await self._get(
+ "/agent/identities",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ListAgentIdentitiesResponse,
+ )
+
+ async def delete(
+ self,
+ uid: 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,
+ ) -> None:
+ """Delete an agent.
+
+ All API keys associated with the agent are deleted atomically.
+
+ 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 uid:
+ raise ValueError(f"Expected a non-empty value for `uid` but received {uid!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/agent/identities/{uid}", uid=uid),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AgentResourceWithRawResponse:
+ def __init__(self, agent: AgentResource) -> None:
+ self._agent = agent
+
+ self.create = to_raw_response_wrapper(
+ agent.create,
+ )
+ self.update = to_raw_response_wrapper(
+ agent.update,
+ )
+ self.list = to_raw_response_wrapper(
+ agent.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ agent.delete,
+ )
+
+
+class AsyncAgentResourceWithRawResponse:
+ def __init__(self, agent: AsyncAgentResource) -> None:
+ self._agent = agent
+
+ self.create = async_to_raw_response_wrapper(
+ agent.create,
+ )
+ self.update = async_to_raw_response_wrapper(
+ agent.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ agent.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ agent.delete,
+ )
+
+
+class AgentResourceWithStreamingResponse:
+ def __init__(self, agent: AgentResource) -> None:
+ self._agent = agent
+
+ self.create = to_streamed_response_wrapper(
+ agent.create,
+ )
+ self.update = to_streamed_response_wrapper(
+ agent.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ agent.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ agent.delete,
+ )
+
+
+class AsyncAgentResourceWithStreamingResponse:
+ def __init__(self, agent: AsyncAgentResource) -> None:
+ self._agent = agent
+
+ self.create = async_to_streamed_response_wrapper(
+ agent.create,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ agent.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ agent.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ agent.delete,
+ )
diff --git a/src/oz_agent_sdk/resources/agent/runs.py b/src/oz_agent_sdk/resources/agent/runs.py
index e6e55b5..00c3f49 100644
--- a/src/oz_agent_sdk/resources/agent/runs.py
+++ b/src/oz_agent_sdk/resources/agent/runs.py
@@ -87,6 +87,7 @@ def retrieve(
def list(
self,
*,
+ ancestor_run_id: str | Omit = omit,
artifact_type: Literal["PLAN", "PULL_REQUEST", "SCREENSHOT", "FILE"] | Omit = omit,
created_after: Union[str, datetime] | Omit = omit,
created_before: Union[str, datetime] | Omit = omit,
@@ -119,6 +120,9 @@ def list(
to `sort_by=updated_at` and `sort_order=desc`.
Args:
+ ancestor_run_id: Filter runs by ancestor run ID. The referenced run must exist and be accessible
+ to the caller.
+
artifact_type: Filter runs by artifact type
created_after: Filter runs created after this timestamp (RFC3339 format)
@@ -182,6 +186,7 @@ def list(
timeout=timeout,
query=maybe_transform(
{
+ "ancestor_run_id": ancestor_run_id,
"artifact_type": artifact_type,
"created_after": created_after,
"created_before": created_before,
@@ -307,6 +312,7 @@ async def retrieve(
def list(
self,
*,
+ ancestor_run_id: str | Omit = omit,
artifact_type: Literal["PLAN", "PULL_REQUEST", "SCREENSHOT", "FILE"] | Omit = omit,
created_after: Union[str, datetime] | Omit = omit,
created_before: Union[str, datetime] | Omit = omit,
@@ -339,6 +345,9 @@ def list(
to `sort_by=updated_at` and `sort_order=desc`.
Args:
+ ancestor_run_id: Filter runs by ancestor run ID. The referenced run must exist and be accessible
+ to the caller.
+
artifact_type: Filter runs by artifact type
created_after: Filter runs created after this timestamp (RFC3339 format)
@@ -402,6 +411,7 @@ def list(
timeout=timeout,
query=maybe_transform(
{
+ "ancestor_run_id": ancestor_run_id,
"artifact_type": artifact_type,
"created_after": created_after,
"created_before": created_before,
diff --git a/src/oz_agent_sdk/types/__init__.py b/src/oz_agent_sdk/types/__init__.py
index 4c520a7..e27ca02 100644
--- a/src/oz_agent_sdk/types/__init__.py
+++ b/src/oz_agent_sdk/types/__init__.py
@@ -8,6 +8,7 @@
from .user_profile import UserProfile as UserProfile
from .agent_run_params import AgentRunParams as AgentRunParams
from .agent_list_params import AgentListParams as AgentListParams
+from .cloud_environment import CloudEnvironment as CloudEnvironment
from .mcp_server_config import McpServerConfig as McpServerConfig
from .agent_run_response import AgentRunResponse as AgentRunResponse
from .agent_list_response import AgentListResponse as AgentListResponse
@@ -18,3 +19,5 @@
from .cloud_environment_config import CloudEnvironmentConfig as CloudEnvironmentConfig
from .ambient_agent_config_param import AmbientAgentConfigParam as AmbientAgentConfigParam
from .agent_get_artifact_response import AgentGetArtifactResponse as AgentGetArtifactResponse
+from .agent_list_environments_params import AgentListEnvironmentsParams as AgentListEnvironmentsParams
+from .agent_list_environments_response import AgentListEnvironmentsResponse as AgentListEnvironmentsResponse
diff --git a/src/oz_agent_sdk/types/agent/__init__.py b/src/oz_agent_sdk/types/agent/__init__.py
index 1176974..ca0f9d8 100644
--- a/src/oz_agent_sdk/types/agent/__init__.py
+++ b/src/oz_agent_sdk/types/agent/__init__.py
@@ -5,8 +5,11 @@
from .run_item import RunItem as RunItem
from .run_state import RunState as RunState
from .artifact_item import ArtifactItem as ArtifactItem
+from .agent_response import AgentResponse as AgentResponse
from .run_list_params import RunListParams as RunListParams
from .run_source_type import RunSourceType as RunSourceType
+from .agent_create_params import AgentCreateParams as AgentCreateParams
+from .agent_update_params import AgentUpdateParams as AgentUpdateParams
from .run_cancel_response import RunCancelResponse as RunCancelResponse
from .scheduled_agent_item import ScheduledAgentItem as ScheduledAgentItem
from .schedule_create_params import ScheduleCreateParams as ScheduleCreateParams
@@ -14,4 +17,5 @@
from .schedule_update_params import ScheduleUpdateParams as ScheduleUpdateParams
from .schedule_delete_response import ScheduleDeleteResponse as ScheduleDeleteResponse
from .scheduled_agent_history_item import ScheduledAgentHistoryItem as ScheduledAgentHistoryItem
+from .list_agent_identities_response import ListAgentIdentitiesResponse as ListAgentIdentitiesResponse
from .session_check_redirect_response import SessionCheckRedirectResponse as SessionCheckRedirectResponse
diff --git a/src/oz_agent_sdk/types/agent/agent_create_params.py b/src/oz_agent_sdk/types/agent/agent_create_params.py
new file mode 100644
index 0000000..5f0a737
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/agent_create_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["AgentCreateParams"]
+
+
+class AgentCreateParams(TypedDict, total=False):
+ name: Required[str]
+ """A name for the agent"""
diff --git a/src/oz_agent_sdk/types/agent/agent_response.py b/src/oz_agent_sdk/types/agent/agent_response.py
new file mode 100644
index 0000000..d748157
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/agent_response.py
@@ -0,0 +1,21 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["AgentResponse"]
+
+
+class AgentResponse(BaseModel):
+ available: bool
+ """Whether this agent is within the team's plan limit and can be used for runs"""
+
+ created_at: datetime
+ """When the agent was created (RFC3339)"""
+
+ name: str
+ """Name of the agent"""
+
+ uid: str
+ """Unique identifier for the agent"""
diff --git a/src/oz_agent_sdk/types/agent/agent_update_params.py b/src/oz_agent_sdk/types/agent/agent_update_params.py
new file mode 100644
index 0000000..e5aecdd
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/agent_update_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["AgentUpdateParams"]
+
+
+class AgentUpdateParams(TypedDict, total=False):
+ name: str
+ """The new name for the agent"""
diff --git a/src/oz_agent_sdk/types/agent/artifact_item.py b/src/oz_agent_sdk/types/agent/artifact_item.py
index 299379b..437b857 100644
--- a/src/oz_agent_sdk/types/agent/artifact_item.py
+++ b/src/oz_agent_sdk/types/agent/artifact_item.py
@@ -24,12 +24,21 @@ class PlanArtifactData(BaseModel):
document_uid: str
"""Unique identifier for the plan document"""
+ artifact_uid: Optional[str] = None
+ """
+ Unique identifier for the plan artifact, usable with the artifact retrieval
+ endpoint
+ """
+
notebook_uid: Optional[str] = None
"""Unique identifier for the associated notebook"""
title: Optional[str] = None
"""Title of the plan"""
+ url: Optional[str] = None
+ """URL to open the plan in Warp Drive"""
+
class PlanArtifact(BaseModel):
artifact_type: Literal["PLAN"]
diff --git a/src/oz_agent_sdk/types/agent/list_agent_identities_response.py b/src/oz_agent_sdk/types/agent/list_agent_identities_response.py
new file mode 100644
index 0000000..d796e6f
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/list_agent_identities_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from ..._models import BaseModel
+from .agent_response import AgentResponse
+
+__all__ = ["ListAgentIdentitiesResponse"]
+
+
+class ListAgentIdentitiesResponse(BaseModel):
+ agents: List[AgentResponse]
diff --git a/src/oz_agent_sdk/types/agent/run_item.py b/src/oz_agent_sdk/types/agent/run_item.py
index d246450..61dd012 100644
--- a/src/oz_agent_sdk/types/agent/run_item.py
+++ b/src/oz_agent_sdk/types/agent/run_item.py
@@ -215,3 +215,6 @@ class RunItem(BaseModel):
For terminal error states, includes structured error code and retryability info
from the platform error catalog.
"""
+
+ trigger_url: Optional[str] = None
+ """URL to the run trigger (e.g. Slack thread, Linear issue, schedule)"""
diff --git a/src/oz_agent_sdk/types/agent/run_list_params.py b/src/oz_agent_sdk/types/agent/run_list_params.py
index 0b4e389..b53a299 100644
--- a/src/oz_agent_sdk/types/agent/run_list_params.py
+++ b/src/oz_agent_sdk/types/agent/run_list_params.py
@@ -14,6 +14,12 @@
class RunListParams(TypedDict, total=False):
+ ancestor_run_id: str
+ """Filter runs by ancestor run ID.
+
+ The referenced run must exist and be accessible to the caller.
+ """
+
artifact_type: Literal["PLAN", "PULL_REQUEST", "SCREENSHOT", "FILE"]
"""Filter runs by artifact type"""
diff --git a/src/oz_agent_sdk/types/agent_get_artifact_response.py b/src/oz_agent_sdk/types/agent_get_artifact_response.py
index 30405bc..7e835c8 100644
--- a/src/oz_agent_sdk/types/agent_get_artifact_response.py
+++ b/src/oz_agent_sdk/types/agent_get_artifact_response.py
@@ -9,6 +9,8 @@
__all__ = [
"AgentGetArtifactResponse",
+ "PlanArtifactResponse",
+ "PlanArtifactResponseData",
"ScreenshotArtifactResponse",
"ScreenshotArtifactResponseData",
"FileArtifactResponse",
@@ -16,6 +18,44 @@
]
+class PlanArtifactResponseData(BaseModel):
+ """Response data for a plan artifact, including current markdown content."""
+
+ content: str
+ """Current markdown content of the plan"""
+
+ content_type: str
+ """MIME type of the returned plan content"""
+
+ document_uid: str
+ """Unique identifier for the plan document"""
+
+ notebook_uid: str
+ """Unique identifier for the associated notebook"""
+
+ title: Optional[str] = None
+ """Current title of the plan"""
+
+ url: Optional[str] = None
+ """URL to open the plan in Warp Drive"""
+
+
+class PlanArtifactResponse(BaseModel):
+ """Response for retrieving a plan artifact."""
+
+ artifact_type: Literal["PLAN"]
+ """Type of the artifact"""
+
+ artifact_uid: str
+ """Unique identifier (UUID) for the artifact"""
+
+ created_at: datetime
+ """Timestamp when the artifact was created (RFC3339)"""
+
+ data: PlanArtifactResponseData
+ """Response data for a plan artifact, including current markdown content."""
+
+
class ScreenshotArtifactResponseData(BaseModel):
"""Response data for a screenshot artifact, including a signed download URL."""
@@ -90,5 +130,6 @@ class FileArtifactResponse(BaseModel):
AgentGetArtifactResponse: TypeAlias = Annotated[
- Union[ScreenshotArtifactResponse, FileArtifactResponse], PropertyInfo(discriminator="artifact_type")
+ Union[PlanArtifactResponse, ScreenshotArtifactResponse, FileArtifactResponse],
+ PropertyInfo(discriminator="artifact_type"),
]
diff --git a/src/oz_agent_sdk/types/agent_list_environments_params.py b/src/oz_agent_sdk/types/agent_list_environments_params.py
new file mode 100644
index 0000000..481c31d
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent_list_environments_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["AgentListEnvironmentsParams"]
+
+
+class AgentListEnvironmentsParams(TypedDict, total=False):
+ sort_by: Literal["name", "last_updated"]
+ """Sort order for the returned environments.
+
+ - `name`: alphabetical by environment name
+ - `last_updated`: most recently updated first (default)
+ """
diff --git a/src/oz_agent_sdk/types/agent_list_environments_response.py b/src/oz_agent_sdk/types/agent_list_environments_response.py
new file mode 100644
index 0000000..103845a
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent_list_environments_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 .cloud_environment import CloudEnvironment
+
+__all__ = ["AgentListEnvironmentsResponse"]
+
+
+class AgentListEnvironmentsResponse(BaseModel):
+ environments: List[CloudEnvironment]
+ """List of accessible cloud environments"""
diff --git a/src/oz_agent_sdk/types/agent_skill.py b/src/oz_agent_sdk/types/agent_skill.py
index 9b3386f..b7b9ef4 100644
--- a/src/oz_agent_sdk/types/agent_skill.py
+++ b/src/oz_agent_sdk/types/agent_skill.py
@@ -26,6 +26,13 @@ class VariantSource(BaseModel):
skill_path: str
"""Path to the skill definition file within the repository"""
+ worker_host: Optional[str] = None
+ """
+ Self-hosted worker host that reported this skill. Present only for skills
+ discovered from self-hosted workers (as opposed to skills from GitHub repos
+ linked to environments).
+ """
+
class Variant(BaseModel):
id: str
diff --git a/src/oz_agent_sdk/types/ambient_agent_config.py b/src/oz_agent_sdk/types/ambient_agent_config.py
index abd16c1..69eaf3a 100644
--- a/src/oz_agent_sdk/types/ambient_agent_config.py
+++ b/src/oz_agent_sdk/types/ambient_agent_config.py
@@ -8,7 +8,7 @@
from .._models import BaseModel
from .mcp_server_config import McpServerConfig
-__all__ = ["AmbientAgentConfig", "Harness"]
+__all__ = ["AmbientAgentConfig", "Harness", "HarnessAuthSecrets"]
class Harness(BaseModel):
@@ -17,15 +17,6 @@ class Harness(BaseModel):
Default (nil/empty) uses Warp's built-in harness.
"""
- auth_secret_name: Optional[str] = None
- """Name of a managed secret to use as the authentication credential for the
- harness.
-
- The secret must exist within the caller's personal or team scope. The
- environment variable injected into the agent is determined by the secret type
- (e.g. ANTHROPIC_API_KEY for anthropic_api_key secrets).
- """
-
type: Optional[Literal["oz", "claude"]] = None
"""The harness type identifier.
@@ -34,6 +25,20 @@ class Harness(BaseModel):
"""
+class HarnessAuthSecrets(BaseModel):
+ """
+ Authentication secrets for third-party harnesses.
+ Only the secret for the harness specified gets injected into the environment.
+ """
+
+ claude_auth_secret_name: Optional[str] = None
+ """
+ Name of a managed secret for Claude Code harness authentication. The secret must
+ exist within the caller's personal or team scope. Only applicable when harness
+ type is "claude".
+ """
+
+
class AmbientAgentConfig(BaseModel):
"""Configuration for a cloud agent run"""
@@ -55,6 +60,12 @@ class AmbientAgentConfig(BaseModel):
uses Warp's built-in harness.
"""
+ harness_auth_secrets: Optional[HarnessAuthSecrets] = None
+ """
+ Authentication secrets for third-party harnesses. Only the secret for the
+ harness specified gets injected into the environment.
+ """
+
idle_timeout_minutes: Optional[int] = None
"""
Number of minutes to keep the agent environment alive after task completion. If
diff --git a/src/oz_agent_sdk/types/ambient_agent_config_param.py b/src/oz_agent_sdk/types/ambient_agent_config_param.py
index 24da607..8ea7901 100644
--- a/src/oz_agent_sdk/types/ambient_agent_config_param.py
+++ b/src/oz_agent_sdk/types/ambient_agent_config_param.py
@@ -7,7 +7,7 @@
from .mcp_server_config_param import McpServerConfigParam
-__all__ = ["AmbientAgentConfigParam", "Harness"]
+__all__ = ["AmbientAgentConfigParam", "Harness", "HarnessAuthSecrets"]
class Harness(TypedDict, total=False):
@@ -16,15 +16,6 @@ class Harness(TypedDict, total=False):
Default (nil/empty) uses Warp's built-in harness.
"""
- auth_secret_name: str
- """Name of a managed secret to use as the authentication credential for the
- harness.
-
- The secret must exist within the caller's personal or team scope. The
- environment variable injected into the agent is determined by the secret type
- (e.g. ANTHROPIC_API_KEY for anthropic_api_key secrets).
- """
-
type: Literal["oz", "claude"]
"""The harness type identifier.
@@ -33,6 +24,20 @@ class Harness(TypedDict, total=False):
"""
+class HarnessAuthSecrets(TypedDict, total=False):
+ """
+ Authentication secrets for third-party harnesses.
+ Only the secret for the harness specified gets injected into the environment.
+ """
+
+ claude_auth_secret_name: str
+ """
+ Name of a managed secret for Claude Code harness authentication. The secret must
+ exist within the caller's personal or team scope. Only applicable when harness
+ type is "claude".
+ """
+
+
class AmbientAgentConfigParam(TypedDict, total=False):
"""Configuration for a cloud agent run"""
@@ -54,6 +59,12 @@ class AmbientAgentConfigParam(TypedDict, total=False):
uses Warp's built-in harness.
"""
+ harness_auth_secrets: HarnessAuthSecrets
+ """
+ Authentication secrets for third-party harnesses. Only the secret for the
+ harness specified gets injected into the environment.
+ """
+
idle_timeout_minutes: int
"""
Number of minutes to keep the agent environment alive after task completion. If
diff --git a/src/oz_agent_sdk/types/cloud_environment.py b/src/oz_agent_sdk/types/cloud_environment.py
new file mode 100644
index 0000000..4bde98c
--- /dev/null
+++ b/src/oz_agent_sdk/types/cloud_environment.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .scope import Scope
+from .._models import BaseModel
+from .user_profile import UserProfile
+from .agent.run_state import RunState
+from .cloud_environment_config import CloudEnvironmentConfig
+
+__all__ = ["CloudEnvironment", "LastTaskCreated"]
+
+
+class LastTaskCreated(BaseModel):
+ """Summary of the most recently created task for an environment"""
+
+ id: str
+ """Unique identifier of the task"""
+
+ created_at: datetime
+ """When the task was created (RFC3339)"""
+
+ 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
+ - BLOCKED: Run is blocked (e.g., awaiting user input or approval)
+ - ERROR: Run encountered an error
+ - CANCELLED: Run was cancelled by user
+ """
+
+ title: str
+ """Title of the task"""
+
+ updated_at: datetime
+ """When the task was last updated (RFC3339)"""
+
+ started_at: Optional[datetime] = None
+ """When the task started running (RFC3339), null if not yet started"""
+
+
+class CloudEnvironment(BaseModel):
+ """A cloud environment for running agents"""
+
+ config: CloudEnvironmentConfig
+ """Configuration for a cloud environment used by scheduled agents"""
+
+ last_updated: datetime
+ """Timestamp when the environment was last updated (RFC3339)"""
+
+ setup_failed: bool
+ """True when the most recent task failed during setup before it started running"""
+
+ uid: str
+ """Unique identifier for the environment"""
+
+ creator: Optional[UserProfile] = None
+
+ last_editor: Optional[UserProfile] = None
+
+ last_task_created: Optional[LastTaskCreated] = None
+ """Summary of the most recently created task for an environment"""
+
+ last_task_run_timestamp: Optional[datetime] = None
+ """Timestamp of the most recent task run in this environment (RFC3339)"""
+
+ scope: Optional[Scope] = None
+ """Ownership scope for a resource (team or personal)"""
diff --git a/tests/api_resources/agent/test_agent_.py b/tests/api_resources/agent/test_agent_.py
new file mode 100644
index 0000000..9bbc914
--- /dev/null
+++ b/tests/api_resources/agent/test_agent_.py
@@ -0,0 +1,337 @@
+# 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 oz_agent_sdk import OzAPI, AsyncOzAPI
+from oz_agent_sdk.types.agent import (
+ AgentResponse,
+ ListAgentIdentitiesResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestAgent:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create(self, client: OzAPI) -> None:
+ agent = client.agent.agent.create(
+ name="name",
+ )
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: OzAPI) -> None:
+ response = client.agent.agent.with_raw_response.create(
+ name="name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: OzAPI) -> None:
+ with client.agent.agent.with_streaming_response.create(
+ name="name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: OzAPI) -> None:
+ agent = client.agent.agent.update(
+ uid="uid",
+ )
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: OzAPI) -> None:
+ agent = client.agent.agent.update(
+ uid="uid",
+ name="name",
+ )
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: OzAPI) -> None:
+ response = client.agent.agent.with_raw_response.update(
+ uid="uid",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: OzAPI) -> None:
+ with client.agent.agent.with_streaming_response.update(
+ uid="uid",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: OzAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `uid` but received ''"):
+ client.agent.agent.with_raw_response.update(
+ uid="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: OzAPI) -> None:
+ agent = client.agent.agent.list()
+ assert_matches_type(ListAgentIdentitiesResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: OzAPI) -> None:
+ response = client.agent.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(ListAgentIdentitiesResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: OzAPI) -> None:
+ with client.agent.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(ListAgentIdentitiesResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: OzAPI) -> None:
+ agent = client.agent.agent.delete(
+ "uid",
+ )
+ assert agent is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: OzAPI) -> None:
+ response = client.agent.agent.with_raw_response.delete(
+ "uid",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert agent is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: OzAPI) -> None:
+ with client.agent.agent.with_streaming_response.delete(
+ "uid",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert agent is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_delete(self, client: OzAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `uid` but received ''"):
+ client.agent.agent.with_raw_response.delete(
+ "",
+ )
+
+
+class TestAsyncAgent:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncOzAPI) -> None:
+ agent = await async_client.agent.agent.create(
+ name="name",
+ )
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncOzAPI) -> None:
+ response = await async_client.agent.agent.with_raw_response.create(
+ name="name",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncOzAPI) -> None:
+ async with async_client.agent.agent.with_streaming_response.create(
+ name="name",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncOzAPI) -> None:
+ agent = await async_client.agent.agent.update(
+ uid="uid",
+ )
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncOzAPI) -> None:
+ agent = await async_client.agent.agent.update(
+ uid="uid",
+ name="name",
+ )
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncOzAPI) -> None:
+ response = await async_client.agent.agent.with_raw_response.update(
+ uid="uid",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncOzAPI) -> None:
+ async with async_client.agent.agent.with_streaming_response.update(
+ uid="uid",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert_matches_type(AgentResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncOzAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `uid` but received ''"):
+ await async_client.agent.agent.with_raw_response.update(
+ uid="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncOzAPI) -> None:
+ agent = await async_client.agent.agent.list()
+ assert_matches_type(ListAgentIdentitiesResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncOzAPI) -> None:
+ response = await async_client.agent.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(ListAgentIdentitiesResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncOzAPI) -> None:
+ async with async_client.agent.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(ListAgentIdentitiesResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncOzAPI) -> None:
+ agent = await async_client.agent.agent.delete(
+ "uid",
+ )
+ assert agent is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncOzAPI) -> None:
+ response = await async_client.agent.agent.with_raw_response.delete(
+ "uid",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert agent is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncOzAPI) -> None:
+ async with async_client.agent.agent.with_streaming_response.delete(
+ "uid",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert agent is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncOzAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `uid` but received ''"):
+ await async_client.agent.agent.with_raw_response.delete(
+ "",
+ )
diff --git a/tests/api_resources/agent/test_runs.py b/tests/api_resources/agent/test_runs.py
index bf257f8..cf95a65 100644
--- a/tests/api_resources/agent/test_runs.py
+++ b/tests/api_resources/agent/test_runs.py
@@ -71,6 +71,7 @@ def test_method_list(self, client: OzAPI) -> None:
@parametrize
def test_method_list_with_all_params(self, client: OzAPI) -> None:
run = client.agent.runs.list(
+ ancestor_run_id="ancestor_run_id",
artifact_type="PLAN",
created_after=parse_datetime("2019-12-27T18:11:19.117Z"),
created_before=parse_datetime("2019-12-27T18:11:19.117Z"),
@@ -215,6 +216,7 @@ async def test_method_list(self, async_client: AsyncOzAPI) -> None:
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncOzAPI) -> None:
run = await async_client.agent.runs.list(
+ ancestor_run_id="ancestor_run_id",
artifact_type="PLAN",
created_after=parse_datetime("2019-12-27T18:11:19.117Z"),
created_before=parse_datetime("2019-12-27T18:11:19.117Z"),
diff --git a/tests/api_resources/agent/test_schedules.py b/tests/api_resources/agent/test_schedules.py
index 9b22b45..51fbae1 100644
--- a/tests/api_resources/agent/test_schedules.py
+++ b/tests/api_resources/agent/test_schedules.py
@@ -40,10 +40,8 @@ def test_method_create_with_all_params(self, client: OzAPI) -> None:
"base_prompt": "base_prompt",
"computer_use_enabled": True,
"environment_id": "environment_id",
- "harness": {
- "auth_secret_name": "auth_secret_name",
- "type": "oz",
- },
+ "harness": {"type": "oz"},
+ "harness_auth_secrets": {"claude_auth_secret_name": "claude_auth_secret_name"},
"idle_timeout_minutes": 1,
"mcp_servers": {
"foo": {
@@ -159,10 +157,8 @@ def test_method_update_with_all_params(self, client: OzAPI) -> None:
"base_prompt": "base_prompt",
"computer_use_enabled": True,
"environment_id": "environment_id",
- "harness": {
- "auth_secret_name": "auth_secret_name",
- "type": "oz",
- },
+ "harness": {"type": "oz"},
+ "harness_auth_secrets": {"claude_auth_secret_name": "claude_auth_secret_name"},
"idle_timeout_minutes": 1,
"mcp_servers": {
"foo": {
@@ -405,10 +401,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncOzAPI) ->
"base_prompt": "base_prompt",
"computer_use_enabled": True,
"environment_id": "environment_id",
- "harness": {
- "auth_secret_name": "auth_secret_name",
- "type": "oz",
- },
+ "harness": {"type": "oz"},
+ "harness_auth_secrets": {"claude_auth_secret_name": "claude_auth_secret_name"},
"idle_timeout_minutes": 1,
"mcp_servers": {
"foo": {
@@ -524,10 +518,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncOzAPI) ->
"base_prompt": "base_prompt",
"computer_use_enabled": True,
"environment_id": "environment_id",
- "harness": {
- "auth_secret_name": "auth_secret_name",
- "type": "oz",
- },
+ "harness": {"type": "oz"},
+ "harness_auth_secrets": {"claude_auth_secret_name": "claude_auth_secret_name"},
"idle_timeout_minutes": 1,
"mcp_servers": {
"foo": {
diff --git a/tests/api_resources/test_agent.py b/tests/api_resources/test_agent.py
index 70c07fb..7f628ef 100644
--- a/tests/api_resources/test_agent.py
+++ b/tests/api_resources/test_agent.py
@@ -13,6 +13,7 @@
AgentRunResponse,
AgentListResponse,
AgentGetArtifactResponse,
+ AgentListEnvironmentsResponse,
)
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -102,6 +103,42 @@ def test_path_params_get_artifact(self, client: OzAPI) -> None:
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_environments(self, client: OzAPI) -> None:
+ agent = client.agent.list_environments()
+ assert_matches_type(AgentListEnvironmentsResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_environments_with_all_params(self, client: OzAPI) -> None:
+ agent = client.agent.list_environments(
+ sort_by="name",
+ )
+ assert_matches_type(AgentListEnvironmentsResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list_environments(self, client: OzAPI) -> None:
+ response = client.agent.with_raw_response.list_environments()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = response.parse()
+ assert_matches_type(AgentListEnvironmentsResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list_environments(self, client: OzAPI) -> None:
+ with client.agent.with_streaming_response.list_environments() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = response.parse()
+ assert_matches_type(AgentListEnvironmentsResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_method_run(self, client: OzAPI) -> None:
@@ -124,10 +161,8 @@ def test_method_run_with_all_params(self, client: OzAPI) -> None:
"base_prompt": "base_prompt",
"computer_use_enabled": True,
"environment_id": "environment_id",
- "harness": {
- "auth_secret_name": "auth_secret_name",
- "type": "oz",
- },
+ "harness": {"type": "oz"},
+ "harness_auth_secrets": {"claude_auth_secret_name": "claude_auth_secret_name"},
"idle_timeout_minutes": 1,
"mcp_servers": {
"foo": {
@@ -263,6 +298,42 @@ async def test_path_params_get_artifact(self, async_client: AsyncOzAPI) -> None:
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_environments(self, async_client: AsyncOzAPI) -> None:
+ agent = await async_client.agent.list_environments()
+ assert_matches_type(AgentListEnvironmentsResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_environments_with_all_params(self, async_client: AsyncOzAPI) -> None:
+ agent = await async_client.agent.list_environments(
+ sort_by="name",
+ )
+ assert_matches_type(AgentListEnvironmentsResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list_environments(self, async_client: AsyncOzAPI) -> None:
+ response = await async_client.agent.with_raw_response.list_environments()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ agent = await response.parse()
+ assert_matches_type(AgentListEnvironmentsResponse, agent, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_environments(self, async_client: AsyncOzAPI) -> None:
+ async with async_client.agent.with_streaming_response.list_environments() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ agent = await response.parse()
+ assert_matches_type(AgentListEnvironmentsResponse, agent, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_method_run(self, async_client: AsyncOzAPI) -> None:
@@ -285,10 +356,8 @@ async def test_method_run_with_all_params(self, async_client: AsyncOzAPI) -> Non
"base_prompt": "base_prompt",
"computer_use_enabled": True,
"environment_id": "environment_id",
- "harness": {
- "auth_secret_name": "auth_secret_name",
- "type": "oz",
- },
+ "harness": {"type": "oz"},
+ "harness_auth_secrets": {"claude_auth_secret_name": "claude_auth_secret_name"},
"idle_timeout_minutes": 1,
"mcp_servers": {
"foo": {
diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py
deleted file mode 100644
index 009b9b9..0000000
--- a/tests/test_deepcopy.py
+++ /dev/null
@@ -1,58 +0,0 @@
-from oz_agent_sdk._utils import deepcopy_minimal
-
-
-def assert_different_identities(obj1: object, obj2: object) -> None:
- assert obj1 == obj2
- assert id(obj1) != id(obj2)
-
-
-def test_simple_dict() -> None:
- obj1 = {"foo": "bar"}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
-
-
-def test_nested_dict() -> None:
- obj1 = {"foo": {"bar": True}}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1["foo"], obj2["foo"])
-
-
-def test_complex_nested_dict() -> None:
- obj1 = {"foo": {"bar": [{"hello": "world"}]}}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1["foo"], obj2["foo"])
- assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"])
- assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0])
-
-
-def test_simple_list() -> None:
- obj1 = ["a", "b", "c"]
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
-
-
-def test_nested_list() -> None:
- obj1 = ["a", [1, 2, 3]]
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert_different_identities(obj1[1], obj2[1])
-
-
-class MyObject: ...
-
-
-def test_ignores_other_types() -> None:
- # custom classes
- my_obj = MyObject()
- obj1 = {"foo": my_obj}
- obj2 = deepcopy_minimal(obj1)
- assert_different_identities(obj1, obj2)
- assert obj1["foo"] is my_obj
-
- # tuples
- obj3 = ("a", "b")
- obj4 = deepcopy_minimal(obj3)
- assert obj3 is obj4
diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py
index 95b71bd..f014004 100644
--- a/tests/test_extract_files.py
+++ b/tests/test_extract_files.py
@@ -35,6 +35,15 @@ def test_multiple_files() -> None:
assert query == {"documents": [{}, {}]}
+def test_top_level_file_array() -> None:
+ query = {"files": [b"file one", b"file two"], "title": "hello"}
+ assert extract_files(query, paths=[["files", ""]]) == [
+ ("files[]", b"file one"),
+ ("files[]", b"file two"),
+ ]
+ assert query == {"title": "hello"}
+
+
@pytest.mark.parametrize(
"query,paths,expected",
[
diff --git a/tests/test_files.py b/tests/test_files.py
index a1255cc..26486e1 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -4,7 +4,8 @@
import pytest
from dirty_equals import IsDict, IsList, IsBytes, IsTuple
-from oz_agent_sdk._files import to_httpx_files, async_to_httpx_files
+from oz_agent_sdk._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files
+from oz_agent_sdk._utils import extract_files
readme_path = Path(__file__).parent.parent.joinpath("README.md")
@@ -49,3 +50,99 @@ def test_string_not_allowed() -> None:
"file": "foo", # type: ignore
}
)
+
+
+def assert_different_identities(obj1: object, obj2: object) -> None:
+ assert obj1 == obj2
+ assert obj1 is not obj2
+
+
+class TestDeepcopyWithPaths:
+ def test_copies_top_level_dict(self) -> None:
+ original = {"file": b"data", "other": "value"}
+ result = deepcopy_with_paths(original, [["file"]])
+ assert_different_identities(result, original)
+
+ def test_file_value_is_same_reference(self) -> None:
+ file_bytes = b"contents"
+ original = {"file": file_bytes}
+ result = deepcopy_with_paths(original, [["file"]])
+ assert_different_identities(result, original)
+ assert result["file"] is file_bytes
+
+ def test_list_popped_wholesale(self) -> None:
+ files = [b"f1", b"f2"]
+ original = {"files": files, "title": "t"}
+ result = deepcopy_with_paths(original, [["files", ""]])
+ assert_different_identities(result, original)
+ result_files = result["files"]
+ assert isinstance(result_files, list)
+ assert_different_identities(result_files, files)
+
+ def test_nested_array_path_copies_list_and_elements(self) -> None:
+ elem1 = {"file": b"f1", "extra": 1}
+ elem2 = {"file": b"f2", "extra": 2}
+ original = {"items": [elem1, elem2]}
+ result = deepcopy_with_paths(original, [["items", "", "file"]])
+ assert_different_identities(result, original)
+ result_items = result["items"]
+ assert isinstance(result_items, list)
+ assert_different_identities(result_items, original["items"])
+ assert_different_identities(result_items[0], elem1)
+ assert_different_identities(result_items[1], elem2)
+
+ def test_empty_paths_returns_same_object(self) -> None:
+ original = {"foo": "bar"}
+ result = deepcopy_with_paths(original, [])
+ assert result is original
+
+ def test_multiple_paths(self) -> None:
+ f1 = b"file1"
+ f2 = b"file2"
+ original = {"a": f1, "b": f2, "c": "unchanged"}
+ result = deepcopy_with_paths(original, [["a"], ["b"]])
+ assert_different_identities(result, original)
+ assert result["a"] is f1
+ assert result["b"] is f2
+ assert result["c"] is original["c"]
+
+ def test_extract_files_does_not_mutate_original_top_level(self) -> None:
+ file_bytes = b"contents"
+ original = {"file": file_bytes, "other": "value"}
+
+ copied = deepcopy_with_paths(original, [["file"]])
+ extracted = extract_files(copied, paths=[["file"]])
+
+ assert extracted == [("file", file_bytes)]
+ assert original == {"file": file_bytes, "other": "value"}
+ assert copied == {"other": "value"}
+
+ def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None:
+ file1 = b"f1"
+ file2 = b"f2"
+ original = {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+
+ copied = deepcopy_with_paths(original, [["items", "", "file"]])
+ extracted = extract_files(copied, paths=[["items", "", "file"]])
+
+ assert extracted == [("items[][file]", file1), ("items[][file]", file2)]
+ assert original == {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+ assert copied == {
+ "items": [
+ {"extra": 1},
+ {"extra": 2},
+ ],
+ "title": "example",
+ }