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..baef5f7 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: 22
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/warp-bnavetta/warp-api-f74133adb6e546317af84cb055755fb9fdaa4b9d5b4285bf0e25c5da2922b737.yml
+openapi_spec_hash: 30edc04bccac2c6d4d1228807a372023
+config_hash: 44a1e8f98607a5cf03815a63bae63453
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 543cbc1..67ddfbd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,68 @@
# Changelog
+## 0.12.0 (2026-04-30)
+
+Full Changelog: [v0.11.0...v0.12.0](https://github.com/warpdotdev/oz-sdk-python/compare/v0.11.0...v0.12.0)
+
+### Features
+
+* [APP-3106] Specs: Explicit QueryMode for cloud agent run creation ([1d4e049](https://github.com/warpdotdev/oz-sdk-python/commit/1d4e049e5c64fdfe1f8eff8dbf7bc8256ed98182))
+* [REMOTE-1481] [3/N] Add executor filter to run list API ([2dbc229](https://github.com/warpdotdev/oz-sdk-python/commit/2dbc22949c6c9475a31e74cba5eadecb018277f3))
+* Accept and persist executor on schedule and integration create/update APIs ([a8500aa](https://github.com/warpdotdev/oz-sdk-python/commit/a8500aa0b3e74221cd931b01d7e7569ad191d1f3))
+* Add API support for public access on oz session link ([df940e0](https://github.com/warpdotdev/oz-sdk-python/commit/df940e09b23d2cc3d314407a8826f722b9428dca))
+* Add Codex as a third-party Oz harness. ([898e18f](https://github.com/warpdotdev/oz-sdk-python/commit/898e18f8e26a2064d3bb3bdda83c08adeb91088d))
+* Add Gemini as a third-party Oz harness. ([0128722](https://github.com/warpdotdev/oz-sdk-python/commit/0128722383f87f6d5a73fd0ecafc4206a3013342))
+* 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 ([86d389c](https://github.com/warpdotdev/oz-sdk-python/commit/86d389c610fb2b89d7fcd17ebfd06610ef2b0b90))
+* **api:** api update ([15d68a1](https://github.com/warpdotdev/oz-sdk-python/commit/15d68a1db8178fd9cb5fae456ca6f341b60fd1e7))
+* **api:** api update ([54f5cdc](https://github.com/warpdotdev/oz-sdk-python/commit/54f5cdc819015611f7214b8ac6632df1cebf857d))
+* **api:** api update ([e290d3b](https://github.com/warpdotdev/oz-sdk-python/commit/e290d3b8cd2e8bd10a713e41992e282b704c624e))
+* **api:** api update ([35565ad](https://github.com/warpdotdev/oz-sdk-python/commit/35565ada13a8a16f9117b3ca0c12279d1708d184))
+* **api:** api update ([9cf2d03](https://github.com/warpdotdev/oz-sdk-python/commit/9cf2d0352242be5c7075fe971561cc36aaaf6e77))
+* **api:** api update ([ed871aa](https://github.com/warpdotdev/oz-sdk-python/commit/ed871aac657265138112d98be757a0de48796e02))
+* **api:** api update ([a44c455](https://github.com/warpdotdev/oz-sdk-python/commit/a44c455cb949e69b05f33f38571b40d9c0caed36))
+* **api:** api update ([e24ba64](https://github.com/warpdotdev/oz-sdk-python/commit/e24ba644c9d3624019f240fd43f51079adf7d8d4))
+* **api:** api update ([e5aea21](https://github.com/warpdotdev/oz-sdk-python/commit/e5aea2192017b1758d7b32d1e855015ce29e93ca))
+* **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))
+* Apply service-account secrets at run creation ([db0e98c](https://github.com/warpdotdev/oz-sdk-python/commit/db0e98ca667b0cfe5b8ab182db5b1d4a19584113))
+* Extend service account API to support skills + secrets ([caf9c45](https://github.com/warpdotdev/oz-sdk-python/commit/caf9c4539ad1718ed194619ebf26425217265706))
+* implement server-side cloud-to-cloud handoff for agent follow-ups (REMOTE-1290) ([eb873a6](https://github.com/warpdotdev/oz-sdk-python/commit/eb873a6092aa1792ca6c23f5d27a7ee66c225ea0))
+* Inject auth secrets via ambient agent config. ([6032a0c](https://github.com/warpdotdev/oz-sdk-python/commit/6032a0ccfe2ce669ce1f815ad853b912d4ca575e))
+* Make conversation/session redirects public ([4c23bb6](https://github.com/warpdotdev/oz-sdk-python/commit/4c23bb6ebec32b5022a1731284d3e07b81a9f5ae))
+* Memory data model scaffolding. ([4ccbe03](https://github.com/warpdotdev/oz-sdk-python/commit/4ccbe0371e7bf02c454a14bc8bc2d412dcebf290))
+* support setting headers via env ([d3b4755](https://github.com/warpdotdev/oz-sdk-python/commit/d3b4755c7447d915a4e11be043b21b859f6c028d))
+* 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))
+* use correct field name format for multipart file arrays ([f785ff2](https://github.com/warpdotdev/oz-sdk-python/commit/f785ff2bafbd319574974549a73c21643cdb5cc5))
+
+
+### Performance Improvements
+
+* **client:** optimize file structure copying in multipart requests ([b8e42cc](https://github.com/warpdotdev/oz-sdk-python/commit/b8e42cc08805eef08a3462088ba0e32cf79dac8c))
+
+
+### Chores
+
+* **internal:** more robust bootstrap script ([27c80e9](https://github.com/warpdotdev/oz-sdk-python/commit/27c80e953bc19de56ea730b0c2170aa8ffbc495a))
+* **internal:** reformat pyproject.toml ([fd17af1](https://github.com/warpdotdev/oz-sdk-python/commit/fd17af10c72ed943e4023a4e9e071ac7b8db08f7))
+* 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..bd33474 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
@@ -37,6 +40,7 @@ from oz_agent_sdk.types.agent import (
RunSourceType,
RunState,
RunCancelResponse,
+ RunListHandoffAttachmentsResponse,
)
```
@@ -45,6 +49,8 @@ Methods:
- client.agent.runs.retrieve(run_id) -> RunItem
- client.agent.runs.list(\*\*params) -> SyncRunsCursorPage[RunItem]
- client.agent.runs.cancel(run_id) -> str
+- client.agent.runs.list_handoff_attachments(run_id) -> RunListHandoffAttachmentsResponse
+- client.agent.runs.submit_followup(run_id, \*\*params) -> object
## Schedules
@@ -69,6 +75,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:
@@ -80,3 +106,15 @@ from oz_agent_sdk.types.agent import SessionCheckRedirectResponse
Methods:
- client.agent.sessions.check_redirect(session_uuid) -> SessionCheckRedirectResponse
+
+## Conversations
+
+Types:
+
+```python
+from oz_agent_sdk.types.agent import ConversationCheckRedirectResponse
+```
+
+Methods:
+
+- client.agent.conversations.check_redirect(conversation_id) -> ConversationCheckRedirectResponse
diff --git a/pyproject.toml b/pyproject.toml
index 01da747..367b68a 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"
@@ -154,7 +154,7 @@ show_error_codes = true
#
# We also exclude our `tests` as mypy doesn't always infer
# types correctly and Pyright will still catch any type errors.
-exclude = ['src/oz_agent_sdk/_files.py', '_dev/.*.py', 'tests/.*']
+exclude = ["src/oz_agent_sdk/_files.py", "_dev/.*.py", "tests/.*"]
strict_equality = true
implicit_reexport = true
diff --git a/scripts/bootstrap b/scripts/bootstrap
index 4638ec6..5a23841 100755
--- a/scripts/bootstrap
+++ b/scripts/bootstrap
@@ -4,7 +4,7 @@ set -e
cd "$(dirname "$0")/.."
-if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
+if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then
brew bundle check >/dev/null 2>&1 || {
echo -n "==> Install Homebrew dependencies? (y/N): "
read -r response
diff --git a/src/oz_agent_sdk/_client.py b/src/oz_agent_sdk/_client.py
index 81603d2..949709b 100644
--- a/src/oz_agent_sdk/_client.py
+++ b/src/oz_agent_sdk/_client.py
@@ -19,7 +19,11 @@
RequestOptions,
not_given,
)
-from ._utils import is_given, get_async_library
+from ._utils import (
+ is_given,
+ is_mapping_t,
+ get_async_library,
+)
from ._compat import cached_property
from ._models import SecurityOptions
from ._version import __version__
@@ -82,6 +86,15 @@ def __init__(
if base_url is None:
base_url = f"https://app.warp.dev/api/v1"
+ custom_headers_env = os.environ.get("OZ_API_CUSTOM_HEADERS")
+ if custom_headers_env is not None:
+ parsed: dict[str, str] = {}
+ for line in custom_headers_env.split("\n"):
+ colon = line.find(":")
+ if colon >= 0:
+ parsed[line[:colon].strip()] = line[colon + 1 :].strip()
+ default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
+
super().__init__(
version=__version__,
base_url=base_url,
@@ -262,6 +275,15 @@ def __init__(
if base_url is None:
base_url = f"https://app.warp.dev/api/v1"
+ custom_headers_env = os.environ.get("OZ_API_CUSTOM_HEADERS")
+ if custom_headers_env is not None:
+ parsed: dict[str, str] = {}
+ for line in custom_headers_env.split("\n"):
+ colon = line.find(":")
+ if colon >= 0:
+ parsed[line[:colon].strip()] = line[colon + 1 :].strip()
+ default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}
+
super().__init__(
version=__version__,
base_url=base_url,
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/_qs.py b/src/oz_agent_sdk/_qs.py
index de8c99b..4127c19 100644
--- a/src/oz_agent_sdk/_qs.py
+++ b/src/oz_agent_sdk/_qs.py
@@ -2,17 +2,13 @@
from typing import Any, List, Tuple, Union, Mapping, TypeVar
from urllib.parse import parse_qs, urlencode
-from typing_extensions import Literal, get_args
+from typing_extensions import get_args
-from ._types import NotGiven, not_given
+from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
from ._utils import flatten
_T = TypeVar("_T")
-
-ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
-NestedFormat = Literal["dots", "brackets"]
-
PrimitiveData = Union[str, int, float, bool, None]
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
# https://github.com/microsoft/pyright/issues/3555
diff --git a/src/oz_agent_sdk/_types.py b/src/oz_agent_sdk/_types.py
index cbb7d7e..09db394 100644
--- a/src/oz_agent_sdk/_types.py
+++ b/src/oz_agent_sdk/_types.py
@@ -47,6 +47,9 @@
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")
+ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
+NestedFormat = Literal["dots", "brackets"]
+
# Approximates httpx internal ProxiesTypes and RequestFiles types
# while adding support for `PathLike` instances
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..199cd23 100644
--- a/src/oz_agent_sdk/_utils/_utils.py
+++ b/src/oz_agent_sdk/_utils/_utils.py
@@ -17,11 +17,11 @@
)
from pathlib import Path
from datetime import date, datetime
-from typing_extensions import TypeGuard
+from typing_extensions import TypeGuard, get_args
import sniffio
-from .._types import Omit, NotGiven, FileTypes, HeadersLike
+from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike
_T = TypeVar("_T")
_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
@@ -40,25 +40,45 @@ def extract_files(
query: Mapping[str, object],
*,
paths: Sequence[Sequence[str]],
+ array_format: ArrayFormat = "brackets",
) -> list[tuple[str, FileTypes]]:
"""Recursively extract files from the given dictionary based on specified paths.
A path may look like this ['foo', 'files', '', 'data'].
+ ``array_format`` controls how ```` segments contribute to the emitted
+ field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
+ ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).
+
Note: this mutates the given dictionary.
"""
files: list[tuple[str, FileTypes]] = []
for path in paths:
- files.extend(_extract_items(query, path, index=0, flattened_key=None))
+ files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
return files
+def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
+ if array_format == "brackets":
+ return "[]"
+ if array_format == "indices":
+ return f"[{array_index}]"
+ if array_format == "repeat" or array_format == "comma":
+ # Both repeat the bare field name for each file part; there is no
+ # meaningful way to comma-join binary parts.
+ return ""
+ raise NotImplementedError(
+ f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
+ )
+
+
def _extract_items(
obj: object,
path: Sequence[str],
*,
index: int,
flattened_key: str | None,
+ array_format: ArrayFormat,
) -> list[tuple[str, FileTypes]]:
try:
key = path[index]
@@ -75,9 +95,11 @@ def _extract_items(
if is_list(obj):
files: list[tuple[str, FileTypes]] = []
- for entry in obj:
- assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
- files.append((flattened_key + "[]", cast(FileTypes, entry)))
+ for array_index, entry in enumerate(obj):
+ suffix = _array_suffix(array_format, array_index)
+ emitted_key = (flattened_key + suffix) if flattened_key else suffix
+ assert_is_file_content(entry, key=emitted_key)
+ files.append((emitted_key, cast(FileTypes, entry)))
return files
assert_is_file_content(obj, key=flattened_key)
@@ -86,8 +108,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]
@@ -105,6 +128,7 @@ def _extract_items(
path,
index=index,
flattened_key=flattened_key,
+ array_format=array_format,
)
elif is_list(obj):
if key != "":
@@ -116,9 +140,12 @@ def _extract_items(
item,
path,
index=index,
- flattened_key=flattened_key + "[]" if flattened_key is not None else "[]",
+ flattened_key=(
+ (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
+ ),
+ array_format=array_format,
)
- for item in obj
+ for array_index, item in enumerate(obj)
]
)
@@ -176,21 +203,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/__init__.py b/src/oz_agent_sdk/resources/agent/__init__.py
index 4336e34..69e9075 100644
--- a/src/oz_agent_sdk/resources/agent/__init__.py
+++ b/src/oz_agent_sdk/resources/agent/__init__.py
@@ -32,6 +32,14 @@
SchedulesResourceWithStreamingResponse,
AsyncSchedulesResourceWithStreamingResponse,
)
+from .conversations import (
+ ConversationsResource,
+ AsyncConversationsResource,
+ ConversationsResourceWithRawResponse,
+ AsyncConversationsResourceWithRawResponse,
+ ConversationsResourceWithStreamingResponse,
+ AsyncConversationsResourceWithStreamingResponse,
+)
__all__ = [
"RunsResource",
@@ -52,6 +60,12 @@
"AsyncSessionsResourceWithRawResponse",
"SessionsResourceWithStreamingResponse",
"AsyncSessionsResourceWithStreamingResponse",
+ "ConversationsResource",
+ "AsyncConversationsResource",
+ "ConversationsResourceWithRawResponse",
+ "AsyncConversationsResourceWithRawResponse",
+ "ConversationsResourceWithStreamingResponse",
+ "AsyncConversationsResourceWithStreamingResponse",
"AgentResource",
"AsyncAgentResource",
"AgentResourceWithRawResponse",
diff --git a/src/oz_agent_sdk/resources/agent/agent.py b/src/oz_agent_sdk/resources/agent/agent.py
index b720375..ff5f385 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 (
@@ -42,11 +43,20 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
+from .conversations import (
+ ConversationsResource,
+ AsyncConversationsResource,
+ ConversationsResourceWithRawResponse,
+ AsyncConversationsResourceWithRawResponse,
+ ConversationsResourceWithStreamingResponse,
+ AsyncConversationsResourceWithStreamingResponse,
+)
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
from ...types.agent_get_artifact_response import AgentGetArtifactResponse
+from ...types.agent_list_environments_response import AgentListEnvironmentsResponse
__all__ = ["AgentResource", "AsyncAgentResource"]
@@ -64,11 +74,21 @@ 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"""
return SessionsResource(self._client)
+ @cached_property
+ def conversations(self) -> ConversationsResource:
+ """Operations for running and managing cloud agents"""
+ return ConversationsResource(self._client)
+
@cached_property
def with_raw_response(self) -> AgentResourceWithRawResponse:
"""
@@ -163,8 +183,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 +211,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,11 +357,21 @@ 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"""
return AsyncSessionsResource(self._client)
+ @cached_property
+ def conversations(self) -> AsyncConversationsResource:
+ """Operations for running and managing cloud agents"""
+ return AsyncConversationsResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncAgentResourceWithRawResponse:
"""
@@ -392,8 +466,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 +494,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 +639,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,11 +656,21 @@ 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"""
return SessionsResourceWithRawResponse(self._agent.sessions)
+ @cached_property
+ def conversations(self) -> ConversationsResourceWithRawResponse:
+ """Operations for running and managing cloud agents"""
+ return ConversationsResourceWithRawResponse(self._agent.conversations)
+
class AsyncAgentResourceWithRawResponse:
def __init__(self, agent: AsyncAgentResource) -> None:
@@ -549,6 +682,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,11 +699,21 @@ 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"""
return AsyncSessionsResourceWithRawResponse(self._agent.sessions)
+ @cached_property
+ def conversations(self) -> AsyncConversationsResourceWithRawResponse:
+ """Operations for running and managing cloud agents"""
+ return AsyncConversationsResourceWithRawResponse(self._agent.conversations)
+
class AgentResourceWithStreamingResponse:
def __init__(self, agent: AgentResource) -> None:
@@ -579,6 +725,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,11 +742,21 @@ 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"""
return SessionsResourceWithStreamingResponse(self._agent.sessions)
+ @cached_property
+ def conversations(self) -> ConversationsResourceWithStreamingResponse:
+ """Operations for running and managing cloud agents"""
+ return ConversationsResourceWithStreamingResponse(self._agent.conversations)
+
class AsyncAgentResourceWithStreamingResponse:
def __init__(self, agent: AsyncAgentResource) -> None:
@@ -609,6 +768,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,7 +785,17 @@ 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"""
return AsyncSessionsResourceWithStreamingResponse(self._agent.sessions)
+
+ @cached_property
+ def conversations(self) -> AsyncConversationsResourceWithStreamingResponse:
+ """Operations for running and managing cloud agents"""
+ return AsyncConversationsResourceWithStreamingResponse(self._agent.conversations)
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..ab7835e
--- /dev/null
+++ b/src/oz_agent_sdk/resources/agent/agent_.py
@@ -0,0 +1,494 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, 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,
+ description: Optional[str] | Omit = omit,
+ secrets: Iterable[agent_create_params.Secret] | Omit = omit,
+ skills: SequenceNotStr[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:
+ """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
+
+ description: Optional description of the agent
+
+ secrets: Optional list of secrets associated with the agent. Duplicate names within a
+ single request are rejected. Each entry is unioned into the run-time secret
+ scope when the agent executes.
+
+ skills:
+ Optional list of skill specs to associate with the agent. Format:
+ "{owner}/{repo}:{skill_path}" (e.g.,
+ "warpdotdev/warp-server:.claude/skills/deploy/SKILL.md"). Each spec is validated
+ and normalized at attach time using the team's GitHub credentials; inaccessible
+ or malformed specs are rejected.
+
+ 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,
+ "description": description,
+ "secrets": secrets,
+ "skills": skills,
+ },
+ 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,
+ *,
+ description: Optional[str] | Omit = omit,
+ name: str | Omit = omit,
+ secrets: Optional[Iterable[agent_update_params.Secret]] | Omit = omit,
+ skills: Optional[SequenceNotStr[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:
+ description: Replacement description.
+
+ Omit or pass `null` to leave unchanged, or use an empty
+ value to clear.
+
+ name: The new name for the agent
+
+ secrets: Replacement list of secrets. Omit to leave unchanged, pass an empty array to
+ clear, or pass a non-empty array to replace. Duplicate names are rejected.
+
+ skills: Replacement list of skill specs. Omit to leave unchanged, pass an empty array to
+ clear, or pass a non-empty array to replace.
+
+ 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(
+ {
+ "description": description,
+ "name": name,
+ "secrets": secrets,
+ "skills": skills,
+ },
+ 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,
+ description: Optional[str] | Omit = omit,
+ secrets: Iterable[agent_create_params.Secret] | Omit = omit,
+ skills: SequenceNotStr[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:
+ """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
+
+ description: Optional description of the agent
+
+ secrets: Optional list of secrets associated with the agent. Duplicate names within a
+ single request are rejected. Each entry is unioned into the run-time secret
+ scope when the agent executes.
+
+ skills:
+ Optional list of skill specs to associate with the agent. Format:
+ "{owner}/{repo}:{skill_path}" (e.g.,
+ "warpdotdev/warp-server:.claude/skills/deploy/SKILL.md"). Each spec is validated
+ and normalized at attach time using the team's GitHub credentials; inaccessible
+ or malformed specs are rejected.
+
+ 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,
+ "description": description,
+ "secrets": secrets,
+ "skills": skills,
+ },
+ 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,
+ *,
+ description: Optional[str] | Omit = omit,
+ name: str | Omit = omit,
+ secrets: Optional[Iterable[agent_update_params.Secret]] | Omit = omit,
+ skills: Optional[SequenceNotStr[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:
+ description: Replacement description.
+
+ Omit or pass `null` to leave unchanged, or use an empty
+ value to clear.
+
+ name: The new name for the agent
+
+ secrets: Replacement list of secrets. Omit to leave unchanged, pass an empty array to
+ clear, or pass a non-empty array to replace. Duplicate names are rejected.
+
+ skills: Replacement list of skill specs. Omit to leave unchanged, pass an empty array to
+ clear, or pass a non-empty array to replace.
+
+ 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(
+ {
+ "description": description,
+ "name": name,
+ "secrets": secrets,
+ "skills": skills,
+ },
+ 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/conversations.py b/src/oz_agent_sdk/resources/agent/conversations.py
new file mode 100644
index 0000000..2525916
--- /dev/null
+++ b/src/oz_agent_sdk/resources/agent/conversations.py
@@ -0,0 +1,190 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.agent.conversation_check_redirect_response import ConversationCheckRedirectResponse
+
+__all__ = ["ConversationsResource", "AsyncConversationsResource"]
+
+
+class ConversationsResource(SyncAPIResource):
+ """Operations for running and managing cloud agents"""
+
+ @cached_property
+ def with_raw_response(self) -> ConversationsResourceWithRawResponse:
+ """
+ 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 ConversationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ConversationsResourceWithStreamingResponse:
+ """
+ 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 ConversationsResourceWithStreamingResponse(self)
+
+ def check_redirect(
+ self,
+ conversation_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,
+ ) -> ConversationCheckRedirectResponse:
+ """Check whether a conversation should redirect to a live shared session.
+
+ Returns a
+ session_id if the underlying ambient agent task still has a live shared session,
+ or an empty object if no redirect is needed.
+
+ This endpoint is public (no authentication required) so that anonymous viewers
+ can resolve a publicly-shared conversation link before signing in. Access to the
+ underlying live session is still gated by the session-sharing service ACLs.
+
+ 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 conversation_id:
+ raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
+ return self._get(
+ path_template("/agent/conversations/{conversation_id}/redirect", conversation_id=conversation_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=ConversationCheckRedirectResponse,
+ )
+
+
+class AsyncConversationsResource(AsyncAPIResource):
+ """Operations for running and managing cloud agents"""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncConversationsResourceWithRawResponse:
+ """
+ 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 AsyncConversationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncConversationsResourceWithStreamingResponse:
+ """
+ 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 AsyncConversationsResourceWithStreamingResponse(self)
+
+ async def check_redirect(
+ self,
+ conversation_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,
+ ) -> ConversationCheckRedirectResponse:
+ """Check whether a conversation should redirect to a live shared session.
+
+ Returns a
+ session_id if the underlying ambient agent task still has a live shared session,
+ or an empty object if no redirect is needed.
+
+ This endpoint is public (no authentication required) so that anonymous viewers
+ can resolve a publicly-shared conversation link before signing in. Access to the
+ underlying live session is still gated by the session-sharing service ACLs.
+
+ 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 conversation_id:
+ raise ValueError(f"Expected a non-empty value for `conversation_id` but received {conversation_id!r}")
+ return await self._get(
+ path_template("/agent/conversations/{conversation_id}/redirect", conversation_id=conversation_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=ConversationCheckRedirectResponse,
+ )
+
+
+class ConversationsResourceWithRawResponse:
+ def __init__(self, conversations: ConversationsResource) -> None:
+ self._conversations = conversations
+
+ self.check_redirect = to_raw_response_wrapper(
+ conversations.check_redirect,
+ )
+
+
+class AsyncConversationsResourceWithRawResponse:
+ def __init__(self, conversations: AsyncConversationsResource) -> None:
+ self._conversations = conversations
+
+ self.check_redirect = async_to_raw_response_wrapper(
+ conversations.check_redirect,
+ )
+
+
+class ConversationsResourceWithStreamingResponse:
+ def __init__(self, conversations: ConversationsResource) -> None:
+ self._conversations = conversations
+
+ self.check_redirect = to_streamed_response_wrapper(
+ conversations.check_redirect,
+ )
+
+
+class AsyncConversationsResourceWithStreamingResponse:
+ def __init__(self, conversations: AsyncConversationsResource) -> None:
+ self._conversations = conversations
+
+ self.check_redirect = async_to_streamed_response_wrapper(
+ conversations.check_redirect,
+ )
diff --git a/src/oz_agent_sdk/resources/agent/runs.py b/src/oz_agent_sdk/resources/agent/runs.py
index e6e55b5..6b1ab06 100644
--- a/src/oz_agent_sdk/resources/agent/runs.py
+++ b/src/oz_agent_sdk/resources/agent/runs.py
@@ -9,7 +9,7 @@
import httpx
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform
+from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -19,11 +19,12 @@
async_to_streamed_response_wrapper,
)
from ...pagination import SyncRunsCursorPage, AsyncRunsCursorPage
-from ...types.agent import RunSourceType, run_list_params
+from ...types.agent import RunSourceType, run_list_params, run_submit_followup_params
from ..._base_client import AsyncPaginator, make_request_options
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_handoff_attachments_response import RunListHandoffAttachmentsResponse
__all__ = ["RunsResource", "AsyncRunsResource"]
@@ -87,6 +88,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,
@@ -94,6 +96,7 @@ def list(
cursor: str | Omit = omit,
environment_id: str | Omit = omit,
execution_location: Literal["LOCAL", "REMOTE"] | Omit = omit,
+ executor: str | Omit = omit,
limit: int | Omit = omit,
model_id: str | Omit = omit,
name: str | Omit = omit,
@@ -119,6 +122,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)
@@ -133,6 +139,9 @@ def list(
execution_location: Filter by where the run executed
+ executor: Filter by the user or agent that executed the run. This will often be the same
+ as the creator, but not always: users may delegate tasks to agents.
+
limit: Maximum number of runs to return
model_id: Filter by model ID
@@ -182,6 +191,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,
@@ -189,6 +199,7 @@ def list(
"cursor": cursor,
"environment_id": environment_id,
"execution_location": execution_location,
+ "executor": executor,
"limit": limit,
"model_id": model_id,
"name": name,
@@ -247,6 +258,86 @@ def cancel(
cast_to=str,
)
+ def list_handoff_attachments(
+ 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,
+ ) -> RunListHandoffAttachmentsResponse:
+ """
+ Return fresh presigned download URLs for handoff snapshot files uploaded by the
+ latest ended execution of this run. An empty list is returned when no ended
+ execution exists or no snapshot files were uploaded.
+
+ This endpoint is useful for third-party harnesses that want to download the
+ snapshot files produced by a previous execution before starting a handoff
+ execution themselves.
+
+ 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._get(
+ path_template("/agent/runs/{run_id}/handoff/attachments", run_id=run_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RunListHandoffAttachmentsResponse,
+ )
+
+ def submit_followup(
+ self,
+ run_id: str,
+ *,
+ message: 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,
+ ) -> object:
+ """Send a follow-up message to an existing run.
+
+ The server transparently routes the
+ message based on the current state of the run (still queued, actively running,
+ or ended). A 200 response means the follow-up was accepted; updated run state
+ can be observed via `GET /agent/runs/{runId}`.
+
+ Args:
+ message: The follow-up message to send to the 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 run_id:
+ raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
+ return self._post(
+ path_template("/agent/runs/{run_id}/followups", run_id=run_id),
+ body=maybe_transform({"message": message}, run_submit_followup_params.RunSubmitFollowupParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=object,
+ )
+
class AsyncRunsResource(AsyncAPIResource):
"""Operations for running and managing cloud agents"""
@@ -307,6 +398,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,
@@ -314,6 +406,7 @@ def list(
cursor: str | Omit = omit,
environment_id: str | Omit = omit,
execution_location: Literal["LOCAL", "REMOTE"] | Omit = omit,
+ executor: str | Omit = omit,
limit: int | Omit = omit,
model_id: str | Omit = omit,
name: str | Omit = omit,
@@ -339,6 +432,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)
@@ -353,6 +449,9 @@ def list(
execution_location: Filter by where the run executed
+ executor: Filter by the user or agent that executed the run. This will often be the same
+ as the creator, but not always: users may delegate tasks to agents.
+
limit: Maximum number of runs to return
model_id: Filter by model ID
@@ -402,6 +501,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,
@@ -409,6 +509,7 @@ def list(
"cursor": cursor,
"environment_id": environment_id,
"execution_location": execution_location,
+ "executor": executor,
"limit": limit,
"model_id": model_id,
"name": name,
@@ -467,6 +568,86 @@ async def cancel(
cast_to=str,
)
+ async def list_handoff_attachments(
+ 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,
+ ) -> RunListHandoffAttachmentsResponse:
+ """
+ Return fresh presigned download URLs for handoff snapshot files uploaded by the
+ latest ended execution of this run. An empty list is returned when no ended
+ execution exists or no snapshot files were uploaded.
+
+ This endpoint is useful for third-party harnesses that want to download the
+ snapshot files produced by a previous execution before starting a handoff
+ execution themselves.
+
+ 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._get(
+ path_template("/agent/runs/{run_id}/handoff/attachments", run_id=run_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RunListHandoffAttachmentsResponse,
+ )
+
+ async def submit_followup(
+ self,
+ run_id: str,
+ *,
+ message: 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,
+ ) -> object:
+ """Send a follow-up message to an existing run.
+
+ The server transparently routes the
+ message based on the current state of the run (still queued, actively running,
+ or ended). A 200 response means the follow-up was accepted; updated run state
+ can be observed via `GET /agent/runs/{runId}`.
+
+ Args:
+ message: The follow-up message to send to the 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 run_id:
+ raise ValueError(f"Expected a non-empty value for `run_id` but received {run_id!r}")
+ return await self._post(
+ path_template("/agent/runs/{run_id}/followups", run_id=run_id),
+ body=await async_maybe_transform({"message": message}, run_submit_followup_params.RunSubmitFollowupParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=object,
+ )
+
class RunsResourceWithRawResponse:
def __init__(self, runs: RunsResource) -> None:
@@ -481,6 +662,12 @@ def __init__(self, runs: RunsResource) -> None:
self.cancel = to_raw_response_wrapper(
runs.cancel,
)
+ self.list_handoff_attachments = to_raw_response_wrapper(
+ runs.list_handoff_attachments,
+ )
+ self.submit_followup = to_raw_response_wrapper(
+ runs.submit_followup,
+ )
class AsyncRunsResourceWithRawResponse:
@@ -496,6 +683,12 @@ def __init__(self, runs: AsyncRunsResource) -> None:
self.cancel = async_to_raw_response_wrapper(
runs.cancel,
)
+ self.list_handoff_attachments = async_to_raw_response_wrapper(
+ runs.list_handoff_attachments,
+ )
+ self.submit_followup = async_to_raw_response_wrapper(
+ runs.submit_followup,
+ )
class RunsResourceWithStreamingResponse:
@@ -511,6 +704,12 @@ def __init__(self, runs: RunsResource) -> None:
self.cancel = to_streamed_response_wrapper(
runs.cancel,
)
+ self.list_handoff_attachments = to_streamed_response_wrapper(
+ runs.list_handoff_attachments,
+ )
+ self.submit_followup = to_streamed_response_wrapper(
+ runs.submit_followup,
+ )
class AsyncRunsResourceWithStreamingResponse:
@@ -526,3 +725,9 @@ def __init__(self, runs: AsyncRunsResource) -> None:
self.cancel = async_to_streamed_response_wrapper(
runs.cancel,
)
+ self.list_handoff_attachments = async_to_streamed_response_wrapper(
+ runs.list_handoff_attachments,
+ )
+ self.submit_followup = async_to_streamed_response_wrapper(
+ runs.submit_followup,
+ )
diff --git a/src/oz_agent_sdk/resources/agent/schedules.py b/src/oz_agent_sdk/resources/agent/schedules.py
index f9fbac0..b29a979 100644
--- a/src/oz_agent_sdk/resources/agent/schedules.py
+++ b/src/oz_agent_sdk/resources/agent/schedules.py
@@ -52,6 +52,7 @@ def create(
cron_schedule: str,
name: str,
agent_config: AmbientAgentConfigParam | Omit = omit,
+ agent_uid: str | Omit = omit,
enabled: bool | Omit = omit,
prompt: str | Omit = omit,
team: bool | Omit = omit,
@@ -75,6 +76,9 @@ def create(
agent_config: Configuration for a cloud agent run
+ agent_uid: Agent UID to use as the execution principal for this schedule. Only valid for
+ team-owned schedules.
+
enabled: Whether the schedule should be active immediately
prompt: The prompt/instruction for the agent to execute. Required unless
@@ -98,6 +102,7 @@ def create(
"cron_schedule": cron_schedule,
"name": name,
"agent_config": agent_config,
+ "agent_uid": agent_uid,
"enabled": enabled,
"prompt": prompt,
"team": team,
@@ -152,6 +157,7 @@ def update(
enabled: bool,
name: str,
agent_config: AmbientAgentConfigParam | Omit = omit,
+ agent_uid: str | Omit = omit,
prompt: 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.
@@ -174,6 +180,9 @@ def update(
agent_config: Configuration for a cloud agent run
+ agent_uid: Agent UID to use as the execution principal for this schedule. Only valid for
+ team-owned schedules.
+
prompt: The prompt/instruction for the agent to execute. Required unless
agent_config.skill_spec is provided.
@@ -195,6 +204,7 @@ def update(
"enabled": enabled,
"name": name,
"agent_config": agent_config,
+ "agent_uid": agent_uid,
"prompt": prompt,
},
schedule_update_params.ScheduleUpdateParams,
@@ -360,6 +370,7 @@ async def create(
cron_schedule: str,
name: str,
agent_config: AmbientAgentConfigParam | Omit = omit,
+ agent_uid: str | Omit = omit,
enabled: bool | Omit = omit,
prompt: str | Omit = omit,
team: bool | Omit = omit,
@@ -383,6 +394,9 @@ async def create(
agent_config: Configuration for a cloud agent run
+ agent_uid: Agent UID to use as the execution principal for this schedule. Only valid for
+ team-owned schedules.
+
enabled: Whether the schedule should be active immediately
prompt: The prompt/instruction for the agent to execute. Required unless
@@ -406,6 +420,7 @@ async def create(
"cron_schedule": cron_schedule,
"name": name,
"agent_config": agent_config,
+ "agent_uid": agent_uid,
"enabled": enabled,
"prompt": prompt,
"team": team,
@@ -460,6 +475,7 @@ async def update(
enabled: bool,
name: str,
agent_config: AmbientAgentConfigParam | Omit = omit,
+ agent_uid: str | Omit = omit,
prompt: 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.
@@ -482,6 +498,9 @@ async def update(
agent_config: Configuration for a cloud agent run
+ agent_uid: Agent UID to use as the execution principal for this schedule. Only valid for
+ team-owned schedules.
+
prompt: The prompt/instruction for the agent to execute. Required unless
agent_config.skill_spec is provided.
@@ -503,6 +522,7 @@ async def update(
"enabled": enabled,
"name": name,
"agent_config": agent_config,
+ "agent_uid": agent_uid,
"prompt": prompt,
},
schedule_update_params.ScheduleUpdateParams,
diff --git a/src/oz_agent_sdk/resources/agent/sessions.py b/src/oz_agent_sdk/resources/agent/sessions.py
index 0beb811..d704bd6 100644
--- a/src/oz_agent_sdk/resources/agent/sessions.py
+++ b/src/oz_agent_sdk/resources/agent/sessions.py
@@ -58,6 +58,10 @@ def check_redirect(
Returns a conversation_id if the agent sandbox has finished and conversation
data is available, or an empty object if no redirect is needed.
+ This endpoint is public (no authentication required) so that anonymous viewers
+ can resolve a publicly-shared session link before signing in. Access to the
+ underlying conversation transcript is still gated by conversation link-sharing.
+
Args:
extra_headers: Send extra headers
@@ -72,7 +76,11 @@ def check_redirect(
return self._get(
path_template("/agent/sessions/{session_uuid}/redirect", session_uuid=session_uuid),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
),
cast_to=SessionCheckRedirectResponse,
)
@@ -116,6 +124,10 @@ async def check_redirect(
Returns a conversation_id if the agent sandbox has finished and conversation
data is available, or an empty object if no redirect is needed.
+ This endpoint is public (no authentication required) so that anonymous viewers
+ can resolve a publicly-shared session link before signing in. Access to the
+ underlying conversation transcript is still gated by conversation link-sharing.
+
Args:
extra_headers: Send extra headers
@@ -130,7 +142,11 @@ async def check_redirect(
return await self._get(
path_template("/agent/sessions/{session_uuid}/redirect", session_uuid=session_uuid),
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
),
cast_to=SessionCheckRedirectResponse,
)
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..07bab46 100644
--- a/src/oz_agent_sdk/types/agent/__init__.py
+++ b/src/oz_agent_sdk/types/agent/__init__.py
@@ -5,13 +5,22 @@
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
from .schedule_list_response import ScheduleListResponse as ScheduleListResponse
from .schedule_update_params import ScheduleUpdateParams as ScheduleUpdateParams
from .schedule_delete_response import ScheduleDeleteResponse as ScheduleDeleteResponse
+from .run_submit_followup_params import RunSubmitFollowupParams as RunSubmitFollowupParams
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
+from .conversation_check_redirect_response import ConversationCheckRedirectResponse as ConversationCheckRedirectResponse
+from .run_list_handoff_attachments_response import (
+ RunListHandoffAttachmentsResponse as RunListHandoffAttachmentsResponse,
+)
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..5f31e19
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/agent_create_params.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from ..._types import SequenceNotStr
+
+__all__ = ["AgentCreateParams", "Secret"]
+
+
+class AgentCreateParams(TypedDict, total=False):
+ name: Required[str]
+ """A name for the agent"""
+
+ description: Optional[str]
+ """Optional description of the agent"""
+
+ secrets: Iterable[Secret]
+ """
+ Optional list of secrets associated with the agent. Duplicate names within a
+ single request are rejected. Each entry is unioned into the run-time secret
+ scope when the agent executes.
+ """
+
+ skills: SequenceNotStr[str]
+ """
+ Optional list of skill specs to associate with the agent. Format:
+ "{owner}/{repo}:{skill_path}" (e.g.,
+ "warpdotdev/warp-server:.claude/skills/deploy/SKILL.md"). Each spec is validated
+ and normalized at attach time using the team's GitHub credentials; inaccessible
+ or malformed specs are rejected.
+ """
+
+
+class Secret(TypedDict, total=False):
+ """Reference to a managed secret by name."""
+
+ name: Required[str]
+ """Name of the managed secret."""
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..8b46c85
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/agent_response.py
@@ -0,0 +1,41 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["AgentResponse", "Secret"]
+
+
+class Secret(BaseModel):
+ """Reference to a managed secret by name."""
+
+ name: str
+ """Name of the managed secret."""
+
+
+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"""
+
+ secrets: List[Secret]
+ """Secrets that this agent may access by default."""
+
+ skills: List[str]
+ """
+ Ordered list of normalized skill specs associated with this agent. Always
+ present; empty when no skills are attached.
+ """
+
+ uid: str
+ """Unique identifier for the agent"""
+
+ description: Optional[str] = None
+ """Optional description of 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..d3cc68a
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/agent_update_params.py
@@ -0,0 +1,42 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable, Optional
+from typing_extensions import Required, TypedDict
+
+from ..._types import SequenceNotStr
+
+__all__ = ["AgentUpdateParams", "Secret"]
+
+
+class AgentUpdateParams(TypedDict, total=False):
+ description: Optional[str]
+ """Replacement description.
+
+ Omit or pass `null` to leave unchanged, or use an empty value to clear.
+ """
+
+ name: str
+ """The new name for the agent"""
+
+ secrets: Optional[Iterable[Secret]]
+ """Replacement list of secrets.
+
+ Omit to leave unchanged, pass an empty array to clear, or pass a non-empty array
+ to replace. Duplicate names are rejected.
+ """
+
+ skills: Optional[SequenceNotStr[str]]
+ """Replacement list of skill specs.
+
+ Omit to leave unchanged, pass an empty array to clear, or pass a non-empty array
+ to replace.
+ """
+
+
+class Secret(TypedDict, total=False):
+ """Reference to a managed secret by name."""
+
+ name: Required[str]
+ """Name of the managed secret."""
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/conversation_check_redirect_response.py b/src/oz_agent_sdk/types/agent/conversation_check_redirect_response.py
new file mode 100644
index 0000000..d18fa13
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/conversation_check_redirect_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from ..._models import BaseModel
+
+__all__ = ["ConversationCheckRedirectResponse"]
+
+
+class ConversationCheckRedirectResponse(BaseModel):
+ session_id: Optional[str] = None
+ """The shared session UUID to redirect to (only present when redirect is needed)"""
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..5b802d8 100644
--- a/src/oz_agent_sdk/types/agent/run_item.py
+++ b/src/oz_agent_sdk/types/agent/run_item.py
@@ -168,6 +168,8 @@ class RunItem(BaseModel):
- REMOTE: Executed by a remote/cloud worker
"""
+ executor: Optional[UserProfile] = None
+
is_sandbox_running: Optional[bool] = None
"""Whether the sandbox environment is currently running"""
@@ -215,3 +217,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_handoff_attachments_response.py b/src/oz_agent_sdk/types/agent/run_list_handoff_attachments_response.py
new file mode 100644
index 0000000..b3ac5b8
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/run_list_handoff_attachments_response.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from ..._models import BaseModel
+
+__all__ = ["RunListHandoffAttachmentsResponse", "Attachment"]
+
+
+class Attachment(BaseModel):
+ """A handoff snapshot attachment exposed for download."""
+
+ attachment_id: str
+ """Identifier for the snapshot attachment within the run."""
+
+ download_url: str
+ """Time-limited signed URL to download the snapshot attachment."""
+
+ filename: str
+ """Original filename of the snapshot attachment."""
+
+ mime_type: Optional[str] = None
+ """MIME type of the snapshot attachment, if known."""
+
+
+class RunListHandoffAttachmentsResponse(BaseModel):
+ """Response body for listing handoff snapshot attachments."""
+
+ attachments: List[Attachment]
+ """
+ Handoff snapshot attachments exposed by the latest ended execution. Empty when
+ no ended execution exists or no files were uploaded.
+ """
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..2e2d36f 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"""
@@ -35,6 +41,13 @@ class RunListParams(TypedDict, total=False):
execution_location: Literal["LOCAL", "REMOTE"]
"""Filter by where the run executed"""
+ executor: str
+ """Filter by the user or agent that executed the run.
+
+ This will often be the same as the creator, but not always: users may delegate
+ tasks to agents.
+ """
+
limit: int
"""Maximum number of runs to return"""
diff --git a/src/oz_agent_sdk/types/agent/run_submit_followup_params.py b/src/oz_agent_sdk/types/agent/run_submit_followup_params.py
new file mode 100644
index 0000000..4912cd1
--- /dev/null
+++ b/src/oz_agent_sdk/types/agent/run_submit_followup_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__ = ["RunSubmitFollowupParams"]
+
+
+class RunSubmitFollowupParams(TypedDict, total=False):
+ message: Required[str]
+ """The follow-up message to send to the run."""
diff --git a/src/oz_agent_sdk/types/agent/schedule_create_params.py b/src/oz_agent_sdk/types/agent/schedule_create_params.py
index e94a3b3..8958fd0 100644
--- a/src/oz_agent_sdk/types/agent/schedule_create_params.py
+++ b/src/oz_agent_sdk/types/agent/schedule_create_params.py
@@ -22,6 +22,12 @@ class ScheduleCreateParams(TypedDict, total=False):
agent_config: AmbientAgentConfigParam
"""Configuration for a cloud agent run"""
+ agent_uid: str
+ """
+ Agent UID to use as the execution principal for this schedule. Only valid for
+ team-owned schedules.
+ """
+
enabled: bool
"""Whether the schedule should be active immediately"""
diff --git a/src/oz_agent_sdk/types/agent/schedule_update_params.py b/src/oz_agent_sdk/types/agent/schedule_update_params.py
index 2c82454..de71dc9 100644
--- a/src/oz_agent_sdk/types/agent/schedule_update_params.py
+++ b/src/oz_agent_sdk/types/agent/schedule_update_params.py
@@ -22,6 +22,12 @@ class ScheduleUpdateParams(TypedDict, total=False):
agent_config: AmbientAgentConfigParam
"""Configuration for a cloud agent run"""
+ agent_uid: str
+ """
+ Agent UID to use as the execution principal for this schedule. Only valid for
+ team-owned schedules.
+ """
+
prompt: str
"""
The prompt/instruction for the agent to execute. Required unless
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..b90112c 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", "SessionSharing"]
class Harness(BaseModel):
@@ -17,20 +17,48 @@ 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
+ type: Optional[Literal["oz", "claude", "gemini"]] = None
"""The harness type identifier.
- oz: Warp's built-in harness (default)
- claude: Claude Code harness
+ - gemini: Gemini CLI harness
+ """
+
+
+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 SessionSharing(BaseModel):
+ """
+ Configures sharing behavior for the run's shared session.
+ When set, the worker emits `--share public:` and the bundled Warp
+ client applies an anyone-with-link ACL to the shared session once it has
+ bootstrapped. The same ACL is mirrored onto the backing conversation so
+ link viewers can read the conversation without being on the run's team.
+ Subject to the workspace-level anyone-with-link sharing setting.
+ """
+
+ public_access: Optional[Literal["VIEWER", "EDITOR"]] = None
+ """
+ Grants anyone-with-link access at the specified level to the run's shared
+ session and backing conversation.
+
+ - VIEWER: link viewers can read the session and conversation.
+ - EDITOR: link viewers can also interact with the session. Anonymous
+ (unauthenticated) reads are not supported in this release; link viewers must
+ still be authenticated Warp users.
"""
@@ -55,6 +83,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
@@ -76,6 +110,16 @@ class AmbientAgentConfig(BaseModel):
and track them via the name query parameter on GET /agent/runs.
"""
+ session_sharing: Optional[SessionSharing] = None
+ """
+ Configures sharing behavior for the run's shared session. When set, the worker
+ emits `--share public:` and the bundled Warp client applies an
+ anyone-with-link ACL to the shared session once it has bootstrapped. The same
+ ACL is mirrored onto the backing conversation so link viewers can read the
+ conversation without being on the run's team. Subject to the workspace-level
+ anyone-with-link sharing setting.
+ """
+
skill_spec: Optional[str] = None
"""
Skill specification identifying which agent skill to use. Format:
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..9031e46 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", "SessionSharing"]
class Harness(TypedDict, total=False):
@@ -16,20 +16,48 @@ 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"]
+ type: Literal["oz", "claude", "gemini"]
"""The harness type identifier.
- oz: Warp's built-in harness (default)
- claude: Claude Code harness
+ - gemini: Gemini CLI harness
+ """
+
+
+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 SessionSharing(TypedDict, total=False):
+ """
+ Configures sharing behavior for the run's shared session.
+ When set, the worker emits `--share public:` and the bundled Warp
+ client applies an anyone-with-link ACL to the shared session once it has
+ bootstrapped. The same ACL is mirrored onto the backing conversation so
+ link viewers can read the conversation without being on the run's team.
+ Subject to the workspace-level anyone-with-link sharing setting.
+ """
+
+ public_access: Literal["VIEWER", "EDITOR"]
+ """
+ Grants anyone-with-link access at the specified level to the run's shared
+ session and backing conversation.
+
+ - VIEWER: link viewers can read the session and conversation.
+ - EDITOR: link viewers can also interact with the session. Anonymous
+ (unauthenticated) reads are not supported in this release; link viewers must
+ still be authenticated Warp users.
"""
@@ -54,6 +82,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
@@ -75,6 +109,16 @@ class AmbientAgentConfigParam(TypedDict, total=False):
and track them via the name query parameter on GET /agent/runs.
"""
+ session_sharing: SessionSharing
+ """
+ Configures sharing behavior for the run's shared session. When set, the worker
+ emits `--share public:` and the bundled Warp client applies an
+ anyone-with-link ACL to the shared session once it has bootstrapped. The same
+ ACL is mirrored onto the backing conversation so link viewers can read the
+ conversation without being on the run's team. Subject to the workspace-level
+ anyone-with-link sharing setting.
+ """
+
skill_spec: str
"""
Skill specification identifying which agent skill to use. Format:
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..a23b05e
--- /dev/null
+++ b/tests/api_resources/agent/test_agent_.py
@@ -0,0 +1,365 @@
+# 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_method_create_with_all_params(self, client: OzAPI) -> None:
+ agent = client.agent.agent.create(
+ name="name",
+ description="description",
+ secrets=[{"name": "name"}],
+ skills=["string"],
+ )
+ 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",
+ description="description",
+ name="name",
+ secrets=[{"name": "name"}],
+ skills=["string"],
+ )
+ 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_method_create_with_all_params(self, async_client: AsyncOzAPI) -> None:
+ agent = await async_client.agent.agent.create(
+ name="name",
+ description="description",
+ secrets=[{"name": "name"}],
+ skills=["string"],
+ )
+ 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",
+ description="description",
+ name="name",
+ secrets=[{"name": "name"}],
+ skills=["string"],
+ )
+ 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_conversations.py b/tests/api_resources/agent/test_conversations.py
new file mode 100644
index 0000000..e1a21d4
--- /dev/null
+++ b/tests/api_resources/agent/test_conversations.py
@@ -0,0 +1,108 @@
+# 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 ConversationCheckRedirectResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestConversations:
+ 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_check_redirect(self, client: OzAPI) -> None:
+ conversation = client.agent.conversations.check_redirect(
+ "conversationId",
+ )
+ assert_matches_type(ConversationCheckRedirectResponse, conversation, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_check_redirect(self, client: OzAPI) -> None:
+ response = client.agent.conversations.with_raw_response.check_redirect(
+ "conversationId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ conversation = response.parse()
+ assert_matches_type(ConversationCheckRedirectResponse, conversation, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_check_redirect(self, client: OzAPI) -> None:
+ with client.agent.conversations.with_streaming_response.check_redirect(
+ "conversationId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ conversation = response.parse()
+ assert_matches_type(ConversationCheckRedirectResponse, conversation, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_check_redirect(self, client: OzAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"):
+ client.agent.conversations.with_raw_response.check_redirect(
+ "",
+ )
+
+
+class TestAsyncConversations:
+ 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_check_redirect(self, async_client: AsyncOzAPI) -> None:
+ conversation = await async_client.agent.conversations.check_redirect(
+ "conversationId",
+ )
+ assert_matches_type(ConversationCheckRedirectResponse, conversation, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_check_redirect(self, async_client: AsyncOzAPI) -> None:
+ response = await async_client.agent.conversations.with_raw_response.check_redirect(
+ "conversationId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ conversation = await response.parse()
+ assert_matches_type(ConversationCheckRedirectResponse, conversation, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_check_redirect(self, async_client: AsyncOzAPI) -> None:
+ async with async_client.agent.conversations.with_streaming_response.check_redirect(
+ "conversationId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ conversation = await response.parse()
+ assert_matches_type(ConversationCheckRedirectResponse, conversation, 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_check_redirect(self, async_client: AsyncOzAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `conversation_id` but received ''"):
+ await async_client.agent.conversations.with_raw_response.check_redirect(
+ "",
+ )
diff --git a/tests/api_resources/agent/test_runs.py b/tests/api_resources/agent/test_runs.py
index bf257f8..cb77f95 100644
--- a/tests/api_resources/agent/test_runs.py
+++ b/tests/api_resources/agent/test_runs.py
@@ -11,7 +11,10 @@
from oz_agent_sdk import OzAPI, AsyncOzAPI
from oz_agent_sdk._utils import parse_datetime
from oz_agent_sdk.pagination import SyncRunsCursorPage, AsyncRunsCursorPage
-from oz_agent_sdk.types.agent import RunItem
+from oz_agent_sdk.types.agent import (
+ RunItem,
+ RunListHandoffAttachmentsResponse,
+)
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -71,6 +74,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"),
@@ -78,6 +82,7 @@ def test_method_list_with_all_params(self, client: OzAPI) -> None:
cursor="cursor",
environment_id="environment_id",
execution_location="LOCAL",
+ executor="executor",
limit=1,
model_id="model_id",
name="name",
@@ -157,6 +162,94 @@ def test_path_params_cancel(self, client: OzAPI) -> None:
"",
)
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_handoff_attachments(self, client: OzAPI) -> None:
+ run = client.agent.runs.list_handoff_attachments(
+ "runId",
+ )
+ assert_matches_type(RunListHandoffAttachmentsResponse, run, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list_handoff_attachments(self, client: OzAPI) -> None:
+ response = client.agent.runs.with_raw_response.list_handoff_attachments(
+ "runId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ run = response.parse()
+ assert_matches_type(RunListHandoffAttachmentsResponse, run, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list_handoff_attachments(self, client: OzAPI) -> None:
+ with client.agent.runs.with_streaming_response.list_handoff_attachments(
+ "runId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ run = response.parse()
+ assert_matches_type(RunListHandoffAttachmentsResponse, run, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list_handoff_attachments(self, client: OzAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"):
+ client.agent.runs.with_raw_response.list_handoff_attachments(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_submit_followup(self, client: OzAPI) -> None:
+ run = client.agent.runs.submit_followup(
+ run_id="runId",
+ message="message",
+ )
+ assert_matches_type(object, run, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_submit_followup(self, client: OzAPI) -> None:
+ response = client.agent.runs.with_raw_response.submit_followup(
+ run_id="runId",
+ message="message",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ run = response.parse()
+ assert_matches_type(object, run, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_submit_followup(self, client: OzAPI) -> None:
+ with client.agent.runs.with_streaming_response.submit_followup(
+ run_id="runId",
+ message="message",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ run = response.parse()
+ assert_matches_type(object, run, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_submit_followup(self, client: OzAPI) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `run_id` but received ''"):
+ client.agent.runs.with_raw_response.submit_followup(
+ run_id="",
+ message="message",
+ )
+
class TestAsyncRuns:
parametrize = pytest.mark.parametrize(
@@ -215,6 +308,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"),
@@ -222,6 +316,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncOzAPI) -> No
cursor="cursor",
environment_id="environment_id",
execution_location="LOCAL",
+ executor="executor",
limit=1,
model_id="model_id",
name="name",
@@ -300,3 +395,91 @@ async def test_path_params_cancel(self, async_client: AsyncOzAPI) -> None:
await async_client.agent.runs.with_raw_response.cancel(
"",
)
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_handoff_attachments(self, async_client: AsyncOzAPI) -> None:
+ run = await async_client.agent.runs.list_handoff_attachments(
+ "runId",
+ )
+ assert_matches_type(RunListHandoffAttachmentsResponse, run, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list_handoff_attachments(self, async_client: AsyncOzAPI) -> None:
+ response = await async_client.agent.runs.with_raw_response.list_handoff_attachments(
+ "runId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ run = await response.parse()
+ assert_matches_type(RunListHandoffAttachmentsResponse, run, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_handoff_attachments(self, async_client: AsyncOzAPI) -> None:
+ async with async_client.agent.runs.with_streaming_response.list_handoff_attachments(
+ "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(RunListHandoffAttachmentsResponse, run, 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_list_handoff_attachments(self, async_client: AsyncOzAPI) -> 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.list_handoff_attachments(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_submit_followup(self, async_client: AsyncOzAPI) -> None:
+ run = await async_client.agent.runs.submit_followup(
+ run_id="runId",
+ message="message",
+ )
+ assert_matches_type(object, run, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_submit_followup(self, async_client: AsyncOzAPI) -> None:
+ response = await async_client.agent.runs.with_raw_response.submit_followup(
+ run_id="runId",
+ message="message",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ run = await response.parse()
+ assert_matches_type(object, run, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_submit_followup(self, async_client: AsyncOzAPI) -> None:
+ async with async_client.agent.runs.with_streaming_response.submit_followup(
+ run_id="runId",
+ message="message",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ run = await response.parse()
+ assert_matches_type(object, run, 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_submit_followup(self, async_client: AsyncOzAPI) -> 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.submit_followup(
+ run_id="",
+ message="message",
+ )
diff --git a/tests/api_resources/agent/test_schedules.py b/tests/api_resources/agent/test_schedules.py
index 9b22b45..b37e9bc 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": {
@@ -57,9 +55,11 @@ def test_method_create_with_all_params(self, client: OzAPI) -> None:
},
"model_id": "model_id",
"name": "name",
+ "session_sharing": {"public_access": "VIEWER"},
"skill_spec": "skill_spec",
"worker_host": "worker_host",
},
+ agent_uid="agent_uid",
enabled=True,
prompt="Review open pull requests and provide feedback",
team=True,
@@ -159,10 +159,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": {
@@ -176,9 +174,11 @@ def test_method_update_with_all_params(self, client: OzAPI) -> None:
},
"model_id": "model_id",
"name": "name",
+ "session_sharing": {"public_access": "VIEWER"},
"skill_spec": "skill_spec",
"worker_host": "worker_host",
},
+ agent_uid="agent_uid",
prompt="prompt",
)
assert_matches_type(ScheduledAgentItem, schedule, path=["response"])
@@ -405,10 +405,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": {
@@ -422,9 +420,11 @@ async def test_method_create_with_all_params(self, async_client: AsyncOzAPI) ->
},
"model_id": "model_id",
"name": "name",
+ "session_sharing": {"public_access": "VIEWER"},
"skill_spec": "skill_spec",
"worker_host": "worker_host",
},
+ agent_uid="agent_uid",
enabled=True,
prompt="Review open pull requests and provide feedback",
team=True,
@@ -524,10 +524,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": {
@@ -541,9 +539,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncOzAPI) ->
},
"model_id": "model_id",
"name": "name",
+ "session_sharing": {"public_access": "VIEWER"},
"skill_spec": "skill_spec",
"worker_host": "worker_host",
},
+ agent_uid="agent_uid",
prompt="prompt",
)
assert_matches_type(ScheduledAgentItem, schedule, path=["response"])
diff --git a/tests/api_resources/test_agent.py b/tests/api_resources/test_agent.py
index 70c07fb..78b54a4 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": {
@@ -141,6 +176,7 @@ def test_method_run_with_all_params(self, client: OzAPI) -> None:
},
"model_id": "model_id",
"name": "name",
+ "session_sharing": {"public_access": "VIEWER"},
"skill_spec": "skill_spec",
"worker_host": "worker_host",
},
@@ -263,6 +299,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 +357,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": {
@@ -302,6 +372,7 @@ async def test_method_run_with_all_params(self, async_client: AsyncOzAPI) -> Non
},
"model_id": "model_id",
"name": "name",
+ "session_sharing": {"public_access": "VIEWER"},
"skill_spec": "skill_spec",
"worker_host": "worker_host",
},
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..c7d7add 100644
--- a/tests/test_extract_files.py
+++ b/tests/test_extract_files.py
@@ -4,7 +4,7 @@
import pytest
-from oz_agent_sdk._types import FileTypes
+from oz_agent_sdk._types import FileTypes, ArrayFormat
from oz_agent_sdk._utils import extract_files
@@ -35,6 +35,12 @@ 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",
[
@@ -62,3 +68,24 @@ def test_ignores_incorrect_paths(
expected: list[tuple[str, FileTypes]],
) -> None:
assert extract_files(query, paths=paths) == expected
+
+
+@pytest.mark.parametrize(
+ "array_format,expected_top_level,expected_nested",
+ [
+ ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]),
+ ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]),
+ ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]),
+ ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]),
+ ],
+)
+def test_array_format_controls_file_field_names(
+ array_format: ArrayFormat,
+ expected_top_level: list[tuple[str, FileTypes]],
+ expected_nested: list[tuple[str, FileTypes]],
+) -> None:
+ top_level = {"files": [b"a", b"b"]}
+ assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level
+
+ nested = {"items": [{"file": b"a"}, {"file": b"b"}]}
+ assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested
diff --git a/tests/test_files.py b/tests/test_files.py
index a1255cc..393aa1a 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 [entry for _, entry in extracted] == [file1, file2]
+ assert original == {
+ "items": [
+ {"file": file1, "extra": 1},
+ {"file": file2, "extra": 2},
+ ],
+ "title": "example",
+ }
+ assert copied == {
+ "items": [
+ {"extra": 1},
+ {"extra": 2},
+ ],
+ "title": "example",
+ }