diff --git a/CHANGELOG.md b/CHANGELOG.md index 26aa6b9..e216a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ existing `0.1.0` release; earlier development prereleases are not listed. ## [Unreleased] +### Added + +- Forward resource search for Environments, Skills, Vaults, and Files, with PAT Identity scope in the query. +- Managed resource search for Agents, Sessions, Deployments, Environments, Skills, Vaults, Files, and Memory Stores, including typed metadata and resource filters. +- Automatic Search beta headers and POST body cursor pagination that preserves filters and request options across pages. + ## [0.11.0] - 2026-10-27 ### Changed diff --git a/README.md b/README.md index 6c937de..0b33304 100644 --- a/README.md +++ b/README.md @@ -818,3 +818,53 @@ Released under the [MIT License](LICENSE). ## Forward Webhooks Manage webhook endpoints and verify signed event deliveries. See [Webhook usage and receiver examples](docs/webhooks.md). + +## Resource search + +Search uses `POST //search` with typed filters in the JSON body. +The SDK automatically includes `x-qoder-beta: search-2026-08-31`, preserving +other effective beta flags. Calling Search with no filters sends `{}`. + +- Forward: Environments, Skills, Vaults, and Files. +- Managed: Agents, Sessions, Deployments, Environments, Skills, Vaults, Files, + and Memory Stores. + +Metadata filters are exact string matches combined with AND. Each resource also +supports its documented filters, such as name, creation time, Session status, +or Skill source/display title. See the official +[Forward search](https://docs.qoder.com/cloud-agents/api/forward/environments/search) +and [Managed Session search](https://docs.qoder.com/cloud-agents/api/sessions/search) +contracts. This searches QCA resources; it does not perform a web search. + +Forward's `identity_id` selector is sent only in the URL query. Set it when a PAT +should search an Identity's resources; omit it with all SATs, whose scope comes +from the token. Search cursors are opaque: automatic pagination puts `next_page` +back into the JSON body's `page` while preserving filters, ownership, and request +options. Existing List methods retain their GET/query pagination behavior. + +```python +# client is a Forward client. +for environment in client.environments.search( + metadata={"team": "core"}, + name="dev", + identity_id="idn_example", # PAT only; omit for SAT. + limit=20, +): + print(environment.id) + +# managed_client is a Managed client. Time filters accept datetime or RFC 3339 strings. +page = managed_client.sessions.search( + metadata={"ticket": "A-123"}, + statuses=["idle", "running"], + created_at_gte="2026-01-01T00:00:00Z", +) + +# async_client is an AsyncForward client. +async for environment in async_client.environments.search(metadata={"team": "core"}): + print(environment.id) +``` + +Synchronous searches return `SyncPage`; asynchronous searches can be awaited for +an `AsyncPage` or directly iterated with `async for`. Use `get_next_page()` for +manual pagination (await it on an async page). `*SearchParams` TypedDicts are +exported from `qca.forward.types` and `qca.managed.types`. diff --git a/docs/api/reference.md b/docs/api/reference.md index 6b21f93..de22e6a 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -1700,6 +1700,10 @@ class BasePage(BaseModel, Generic[T]) #### next\_page + + +#### total + #### has\_next\_page @@ -3807,6 +3811,36 @@ class Environments(SyncAPIResource) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/environments.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Environment] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/environments.py) + +POST /environments/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. +identity_id is a query selector for PAT callers; omit it with any SAT. + #### list @@ -3959,6 +3993,36 @@ class AsyncEnvironments(AsyncAPIResource) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/environments.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Environment] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/environments.py) + +POST /environments/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. +identity_id is a query selector for PAT callers; omit it with any SAT. + #### list @@ -4115,6 +4179,34 @@ class Files(SyncAPIResource) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/files.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + scope_id: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[FileMetadata] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/files.py) + +POST /files/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. +identity_id is a query selector for PAT callers; omit it with any SAT. + #### list @@ -4243,6 +4335,34 @@ class AsyncFiles(AsyncAPIResource) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/files.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + scope_id: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[FileMetadata] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/files.py) + +POST /files/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. +identity_id is a query selector for PAT callers; omit it with any SAT. + #### list @@ -8518,6 +8638,34 @@ def versions() -> Versions [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/skills/skills.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + source: Union[Literal["custom", "qoder"], NotGiven] = NOT_GIVEN, + display_title: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Skill] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/skills/skills.py) + +POST /skills/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. +identity_id is a query selector for PAT callers; omit it with any SAT. + #### list @@ -8665,6 +8813,34 @@ def versions() -> AsyncVersions [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/skills/skills.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + source: Union[Literal["custom", "qoder"], NotGiven] = NOT_GIVEN, + display_title: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Skill] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/skills/skills.py) + +POST /skills/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. +identity_id is a query selector for PAT callers; omit it with any SAT. + #### list @@ -9838,6 +10014,34 @@ def credentials() -> Credentials [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/vaults/vaults.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Vault] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/vaults/vaults.py) + +POST /vaults/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. +identity_id is a query selector for PAT callers; omit it with any SAT. + #### list @@ -9950,6 +10154,34 @@ def credentials() -> AsyncCredentials [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/vaults/vaults.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Vault] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/vaults/vaults.py) + +POST /vaults/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. +identity_id is a query selector for PAT callers; omit it with any SAT. + #### list @@ -12502,6 +12734,52 @@ Select identity ownership with a PAT. Omit when using a SAT. #### before\_id + + +# qca.forward.types.environment\_search\_params + + + +## EnvironmentSearchParams + +```python +class EnvironmentSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/environment_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### name + + + +#### include\_archived + + + +#### created\_at\_gte + + + +#### created\_at\_lte + + + +#### identity\_id + # qca.forward.types.environment\_update\_params @@ -12684,6 +12962,44 @@ class FileMetadata(BaseModel) #### identity\_id + + +# qca.forward.types.file\_search\_params + + + +## FileSearchParams + +```python +class FileSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/file_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### name + + + +#### scope\_id + + + +#### identity\_id + # qca.forward.types.file\_upload\_params @@ -17294,6 +17610,44 @@ Select identity ownership with a PAT. Omit when using a SAT. #### include\_content + + +# qca.forward.types.skill\_search\_params + + + +## SkillSearchParams + +```python +class SkillSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/skill_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### source + + + +#### display\_title + + + +#### identity\_id + # qca.forward.types.skill\_update\_params @@ -18496,6 +18850,44 @@ Select identity ownership with a PAT. Omit when using a SAT. #### name + + +# qca.forward.types.vault\_search\_params + + + +## VaultSearchParams + +```python +class VaultSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/vault_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### include\_archived + + + +#### name + + + +#### identity\_id + # qca.forward.types.webhook\_endpoint @@ -19144,6 +19536,36 @@ def update( POST /agents/{agent_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Agent] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/agents/agents.py) + +POST /agents/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -19298,6 +19720,36 @@ async def update( POST /agents/{agent_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Agent] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/agents/agents.py) + +POST /agents/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -19797,6 +20249,38 @@ def update( POST /deployments/{deployment_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + status: Union[Literal["active", "paused"], NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + agent_id: Union[str, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Deployment] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/deployments.py) + +POST /deployments/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -20038,6 +20522,38 @@ async def update( POST /deployments/{deployment_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + status: Union[Literal["active", "paused"], NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + agent_id: Union[str, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Deployment] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/deployments.py) + +POST /deployments/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -20502,6 +21018,36 @@ def update( POST /environments/{environment_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Environment] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/environments/environments.py) + +POST /environments/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -20666,6 +21212,36 @@ async def update( POST /environments/{environment_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Environment] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/environments/environments.py) + +POST /environments/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -21131,6 +21707,34 @@ class Files(SyncAPIResource) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/files.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + scope_id: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[FileMetadata] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/files.py) + +POST /files/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -21255,6 +21859,34 @@ class AsyncFiles(AsyncAPIResource) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/files.py) + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + scope_id: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[FileMetadata] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/files.py) + +POST /files/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -21747,6 +22379,36 @@ def update( POST /memory_stores/{memory_store_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[MemoryStore] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/memory_stores/memory_stores.py) + +POST /memory_stores/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -21915,6 +22577,36 @@ async def update( POST /memory_stores/{memory_store_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[MemoryStore] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/memory_stores/memory_stores.py) + +POST /memory_stores/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -22891,6 +23583,45 @@ def update( POST /sessions/{session_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + order: Union[Literal["desc", "asc"], NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + statuses: Union[List[Literal["running", "idle", "rescheduling", + "terminated"]], NotGiven] = NOT_GIVEN, + agent_id: Union[str, NotGiven] = NOT_GIVEN, + agent_version: Union[int, NotGiven] = NOT_GIVEN, + deployment_id: Union[str, NotGiven] = NOT_GIVEN, + memory_store_id: Union[str, NotGiven] = NOT_GIVEN, + created_at_gt: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lt: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Session] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/sessions/sessions.py) + +POST /sessions/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -23122,6 +23853,45 @@ async def update( POST /sessions/{session_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + order: Union[Literal["desc", "asc"], NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + statuses: Union[List[Literal["running", "idle", "rescheduling", + "terminated"]], NotGiven] = NOT_GIVEN, + agent_id: Union[str, NotGiven] = NOT_GIVEN, + agent_version: Union[int, NotGiven] = NOT_GIVEN, + deployment_id: Union[str, NotGiven] = NOT_GIVEN, + memory_store_id: Union[str, NotGiven] = NOT_GIVEN, + created_at_gt: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lt: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Session] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/sessions/sessions.py) + +POST /sessions/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -23568,6 +24338,35 @@ def retrieve( GET /skills/{skill_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + source: Union[Literal["custom", "qoder"], NotGiven] = NOT_GIVEN, + display_title: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Skill] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/skills/skills.py) + +POST /skills/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -23679,6 +24478,35 @@ async def retrieve( GET /skills/{skill_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + source: Union[Literal["custom", "qoder"], NotGiven] = NOT_GIVEN, + display_title: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Skill] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/skills/skills.py) + +POST /skills/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -24395,6 +25223,34 @@ def retrieve( GET /vaults/{vault_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> SyncPage[Vault] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/vaults/vaults.py) + +POST /vaults/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -24524,6 +25380,34 @@ async def retrieve( GET /vaults/{vault_id}. + + +#### search + +```python +def search( + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN +) -> AsyncPaginator[Vault] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/vaults/vaults.py) + +POST /vaults/search. + +Search by metadata and resource filters; automatically enables the Search beta. +Pagination replays next_page in the JSON body, preserving the filters. + #### list @@ -24985,6 +25869,56 @@ class AgentRetrieveParams(TypedDict) #### betas + + +# qca.managed.types.agent\_search\_params + + + +## AgentSearchParams + +```python +class AgentSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/agent_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### name + + + +#### include\_archived + + + +#### created\_at\_gte + + + +#### created\_at\_lte + + + +#### workspace\_id + + + +#### betas + # qca.managed.types.agent\_skill\_union @@ -26947,6 +27881,64 @@ class DeploymentScopedRunRetrieveParams(TypedDict) #### betas + + +# qca.managed.types.deployment\_search\_params + + + +## DeploymentSearchParams + +```python +class DeploymentSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/deployment_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### name + + + +#### status + + + +#### include\_archived + + + +#### agent\_id + + + +#### created\_at\_gte + + + +#### created\_at\_lte + + + +#### workspace\_id + + + +#### betas + # qca.managed.types.deployment\_unpause\_params @@ -27985,6 +28977,56 @@ class EnvironmentRetrieveParams(TypedDict) #### betas + + +# qca.managed.types.environment\_search\_params + + + +## EnvironmentSearchParams + +```python +class EnvironmentSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/environment_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### name + + + +#### include\_archived + + + +#### created\_at\_gte + + + +#### created\_at\_lte + + + +#### workspace\_id + + + +#### betas + # qca.managed.types.environment\_update\_params @@ -28643,6 +29685,48 @@ class FileScope(BaseModel) #### type + + +# qca.managed.types.file\_search\_params + + + +## FileSearchParams + +```python +class FileSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/file_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### name + + + +#### scope\_id + + + +#### workspace\_id + + + +#### betas + # qca.managed.types.file\_upload\_params @@ -29985,6 +31069,56 @@ class MemoryStoreRetrieveParams(TypedDict) #### betas + + +# qca.managed.types.memory\_store\_search\_params + + + +## MemoryStoreSearchParams + +```python +class MemoryStoreSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/memory_store_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### name + + + +#### include\_archived + + + +#### created\_at\_gte + + + +#### created\_at\_lte + + + +#### workspace\_id + + + +#### betas + # qca.managed.types.memory\_store\_update\_params @@ -32719,6 +33853,88 @@ class SessionRetrieveParams(TypedDict) #### betas + + +# qca.managed.types.session\_search\_params + + + +## SessionSearchParams + +```python +class SessionSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/session_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### name + + + +#### order + + + +#### include\_archived + + + +#### statuses + + + +#### agent\_id + + + +#### agent\_version + + + +#### deployment\_id + + + +#### memory\_store\_id + + + +#### created\_at\_gt + + + +#### created\_at\_gte + + + +#### created\_at\_lt + + + +#### created\_at\_lte + + + +#### workspace\_id + + + +#### betas + # qca.managed.types.session\_stats @@ -33745,6 +34961,52 @@ class SkillRetrieveParams(TypedDict) #### betas + + +# qca.managed.types.skill\_search\_params + + + +## SkillSearchParams + +```python +class SkillSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/skill_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### source + + + +#### display\_title + + + +#### name + + + +#### workspace\_id + + + +#### betas + # qca.managed.types.skill\_source @@ -35515,6 +36777,48 @@ class VaultRetrieveParams(TypedDict) #### betas + + +# qca.managed.types.vault\_search\_params + + + +## VaultSearchParams + +```python +class VaultSearchParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/vault_search_params.py) + + + +#### metadata + + + +#### limit + + + +#### page + + + +#### include\_archived + + + +#### name + + + +#### workspace\_id + + + +#### betas + # qca.managed.types.web\_fetch\_tool\_config\_params diff --git a/src/qca/common/_base_client.py b/src/qca/common/_base_client.py index 44eb7a4..78f8fe0 100644 --- a/src/qca/common/_base_client.py +++ b/src/qca/common/_base_client.py @@ -172,6 +172,11 @@ def _headers(self, options: dict[str, Any], token: str | None) -> httpx.Headers: for source in (self.default_headers, options.get("headers", {})): for key, value in source.items(): headers[key] = ",".join(str(v) for v in value) if isinstance(value, (list, tuple)) else str(value) + if required_beta := options.get("required_beta"): + betas = [value.strip() for value in headers.get("x-qoder-beta", "").split(",") if value.strip()] + if required_beta not in betas: + betas.append(required_beta) + headers["x-qoder-beta"] = ",".join(betas) self._validate_headers(headers) return headers @@ -271,10 +276,15 @@ def _parse_api_response( page_cls = AsyncPage if self._is_async else SyncPage page = parse_response(page_cls[cast_to], data, response, strict=self._strict_response_validation) page._style = page_style - page._query = {**self.default_query, **options.get("query", {})} + page_field = "body" if page_style == "BodyPageCursor" else "query" + page._query = ( + dict(options.get("body", {})) + if page_field == "body" + else {**self.default_query, **options.get("query", {})} + ) client = self._copy() if self._raw_response else self page._fetch = lambda query: getattr(client, "request")( - method, path, cast_to=cast_to, options={**options, "query": query}, page_style=page_style + method, path, cast_to=cast_to, options={**options, page_field: query}, page_style=page_style ) return page diff --git a/src/qca/common/pagination.py b/src/qca/common/pagination.py index b3b14e2..85c112e 100644 --- a/src/qca/common/pagination.py +++ b/src/qca/common/pagination.py @@ -16,6 +16,7 @@ class BasePage(BaseModel, Generic[T]): first_id: str | None = None last_id: str | None = None next_page: str | None = None + total: int | None = None _query: dict[str, Any] = PrivateAttr(default_factory=dict) _style: str = PrivateAttr(default="Page") _fetch: Any = PrivateAttr(default=None) @@ -24,7 +25,7 @@ def _next_query(self) -> dict[str, Any] | None: if self.has_more is False: return None query = dict(self._query) - if self._style in ("PageCursor", "BidirectionalPageCursor", "TokenPage"): + if self._style in ("PageCursor", "BodyPageCursor", "BidirectionalPageCursor", "TokenPage"): key = "page_token" if self._style == "TokenPage" else "page" cursor = self.next_page else: @@ -38,7 +39,10 @@ def _next_query(self) -> dict[str, Any] | None: raise RuntimeError("Pagination cursor did not advance") for other in ("after_id", "before_id", "page", "page_token"): if other != key: - query[other] = None + if self._style == "BodyPageCursor": + query.pop(other, None) + else: + query[other] = None query[key] = cursor return query diff --git a/src/qca/forward/resources/environments.py b/src/qca/forward/resources/environments.py index eaab6c7..d9a8e24 100644 --- a/src/qca/forward/resources/environments.py +++ b/src/qca/forward/resources/environments.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime from typing import Any, Dict, Union import httpx @@ -14,6 +15,50 @@ class Environments(SyncAPIResource): + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Environment]: + """POST /environments/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + identity_id is a query selector for PAT callers; omit it with any SAT. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "include_archived": include_archived, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={"identity_id": identity_id}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/environments/search", cast_to=Environment, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -189,6 +234,52 @@ def delete( class AsyncEnvironments(AsyncAPIResource): + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Environment]: + """POST /environments/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + identity_id is a query selector for PAT callers; omit it with any SAT. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "include_archived": include_archived, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={"identity_id": identity_id}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/environments/search", cast_to=Environment, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/forward/resources/files.py b/src/qca/forward/resources/files.py index bc5688f..99c4c9c 100644 --- a/src/qca/forward/resources/files.py +++ b/src/qca/forward/resources/files.py @@ -15,6 +15,46 @@ class Files(SyncAPIResource): + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + scope_id: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[FileMetadata]: + """POST /files/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + identity_id is a query selector for PAT callers; omit it with any SAT. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "scope_id": scope_id, + }, + query={"identity_id": identity_id}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/files/search", cast_to=FileMetadata, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -164,6 +204,48 @@ def download( class AsyncFiles(AsyncAPIResource): + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + scope_id: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[FileMetadata]: + """POST /files/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + identity_id is a query selector for PAT callers; omit it with any SAT. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "scope_id": scope_id, + }, + query={"identity_id": identity_id}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/files/search", cast_to=FileMetadata, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/forward/resources/skills/skills.py b/src/qca/forward/resources/skills/skills.py index e9f2676..1c86c8b 100644 --- a/src/qca/forward/resources/skills/skills.py +++ b/src/qca/forward/resources/skills/skills.py @@ -1,7 +1,7 @@ from __future__ import annotations from functools import cached_property -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Literal, Union import httpx @@ -20,6 +20,46 @@ class Skills(SyncAPIResource): def versions(self) -> Versions: return Versions(self._client) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + source: Union[Literal["custom", "qoder"], NotGiven] = NOT_GIVEN, + display_title: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Skill]: + """POST /skills/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + identity_id is a query selector for PAT callers; omit it with any SAT. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "source": source, + "display_title": display_title, + }, + query={"identity_id": identity_id}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/skills/search", cast_to=Skill, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -200,6 +240,48 @@ class AsyncSkills(AsyncAPIResource): def versions(self) -> AsyncVersions: return AsyncVersions(self._client) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + source: Union[Literal["custom", "qoder"], NotGiven] = NOT_GIVEN, + display_title: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Skill]: + """POST /skills/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + identity_id is a query selector for PAT callers; omit it with any SAT. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "source": source, + "display_title": display_title, + }, + query={"identity_id": identity_id}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/skills/search", cast_to=Skill, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/forward/resources/vaults/vaults.py b/src/qca/forward/resources/vaults/vaults.py index ea4a585..7402c58 100644 --- a/src/qca/forward/resources/vaults/vaults.py +++ b/src/qca/forward/resources/vaults/vaults.py @@ -20,6 +20,46 @@ class Vaults(SyncAPIResource): def credentials(self) -> Credentials: return Credentials(self._client) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Vault]: + """POST /vaults/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + identity_id is a query selector for PAT callers; omit it with any SAT. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "include_archived": include_archived, + "name": name, + }, + query={"identity_id": identity_id}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/vaults/search", cast_to=Vault, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -143,6 +183,48 @@ class AsyncVaults(AsyncAPIResource): def credentials(self) -> AsyncCredentials: return AsyncCredentials(self._client) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Vault]: + """POST /vaults/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + identity_id is a query selector for PAT callers; omit it with any SAT. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "include_archived": include_archived, + "name": name, + }, + query={"identity_id": identity_id}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/vaults/search", cast_to=Vault, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/forward/types/__init__.py b/src/qca/forward/types/__init__.py index d4ef21e..4903c39 100644 --- a/src/qca/forward/types/__init__.py +++ b/src/qca/forward/types/__init__.py @@ -78,11 +78,13 @@ from .environment_config_packages import EnvironmentConfigPackages as EnvironmentConfigPackages from .environment_create_params import EnvironmentCreateParams as EnvironmentCreateParams from .environment_list_params import EnvironmentListParams as EnvironmentListParams +from .environment_search_params import EnvironmentSearchParams as EnvironmentSearchParams from .environment_update_params import EnvironmentUpdateParams as EnvironmentUpdateParams from .environment_variable_override import EnvironmentVariableOverride as EnvironmentVariableOverride from .environment_variable_override_param import EnvironmentVariableOverrideParam as EnvironmentVariableOverrideParam from .file_list_params import FileListParams as FileListParams from .file_metadata import FileMetadata as FileMetadata +from .file_search_params import FileSearchParams as FileSearchParams from .file_upload_params import FileUploadParams as FileUploadParams from .git_hub_repository import GitHubRepository as GitHubRepository from .git_hub_repository_param import GitHubRepositoryParam as GitHubRepositoryParam @@ -228,6 +230,7 @@ from .skill_override import SkillOverride as SkillOverride from .skill_override_param import SkillOverrideParam as SkillOverrideParam from .skill_retrieve_params import SkillRetrieveParams as SkillRetrieveParams +from .skill_search_params import SkillSearchParams as SkillSearchParams from .skill_update_params import SkillUpdateParams as SkillUpdateParams from .skill_version import SkillVersion as SkillVersion from .skill_version_create_params import SkillVersionCreateParams as SkillVersionCreateParams @@ -261,6 +264,7 @@ from .vault_credential_update_params import VaultCredentialUpdateAuth as VaultCredentialUpdateAuth from .vault_credential_update_params import VaultCredentialUpdateParams as VaultCredentialUpdateParams from .vault_list_params import VaultListParams as VaultListParams +from .vault_search_params import VaultSearchParams as VaultSearchParams from .webhook_endpoint import WebhookEndpoint as WebhookEndpoint from .webhook_endpoint import WebhookEndpointCreateResponse as WebhookEndpointCreateResponse from .webhook_endpoint import WebhookEndpointTestResponse as WebhookEndpointTestResponse diff --git a/src/qca/forward/types/environment_search_params.py b/src/qca/forward/types/environment_search_params.py new file mode 100644 index 0000000..25cdb0d --- /dev/null +++ b/src/qca/forward/types/environment_search_params.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Dict, Union + +from typing_extensions import TypedDict + +__all__ = ["EnvironmentSearchParams"] + + +class EnvironmentSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + name: str + include_archived: bool + created_at_gte: Union[datetime, str] + created_at_lte: Union[datetime, str] + identity_id: str diff --git a/src/qca/forward/types/file_search_params.py b/src/qca/forward/types/file_search_params.py new file mode 100644 index 0000000..4fb989d --- /dev/null +++ b/src/qca/forward/types/file_search_params.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Dict + +from typing_extensions import TypedDict + +__all__ = ["FileSearchParams"] + + +class FileSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + name: str + scope_id: str + identity_id: str diff --git a/src/qca/forward/types/skill_search_params.py b/src/qca/forward/types/skill_search_params.py new file mode 100644 index 0000000..b8a6a75 --- /dev/null +++ b/src/qca/forward/types/skill_search_params.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Dict, Literal + +from typing_extensions import TypedDict + +__all__ = ["SkillSearchParams"] + + +class SkillSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + source: Literal["custom", "qoder"] + display_title: str + identity_id: str diff --git a/src/qca/forward/types/vault_search_params.py b/src/qca/forward/types/vault_search_params.py new file mode 100644 index 0000000..7c9f3a6 --- /dev/null +++ b/src/qca/forward/types/vault_search_params.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Dict + +from typing_extensions import TypedDict + +__all__ = ["VaultSearchParams"] + + +class VaultSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + include_archived: bool + name: str + identity_id: str diff --git a/src/qca/managed/resources/agents/agents.py b/src/qca/managed/resources/agents/agents.py index 81df6d2..7b5453c 100644 --- a/src/qca/managed/resources/agents/agents.py +++ b/src/qca/managed/resources/agents/agents.py @@ -145,6 +145,50 @@ def update( ) return self._client.request("POST", _path, cast_to=Agent, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Agent]: + """POST /agents/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "include_archived": include_archived, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/agents/search", cast_to=Agent, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -325,6 +369,52 @@ async def update( ) return await self._client.request("POST", _path, cast_to=Agent, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Agent]: + """POST /agents/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "include_archived": include_archived, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/agents/search", cast_to=Agent, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/managed/resources/deployments.py b/src/qca/managed/resources/deployments.py index ffc2a90..29a7b0d 100644 --- a/src/qca/managed/resources/deployments.py +++ b/src/qca/managed/resources/deployments.py @@ -173,6 +173,54 @@ def update( ) return self._client.request("POST", _path, cast_to=Deployment, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + status: Union[Literal["active", "paused"], NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + agent_id: Union[str, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Deployment]: + """POST /deployments/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "status": status, + "include_archived": include_archived, + "agent_id": agent_id, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/deployments/search", cast_to=Deployment, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -458,6 +506,56 @@ async def update( ) return await self._client.request("POST", _path, cast_to=Deployment, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + status: Union[Literal["active", "paused"], NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + agent_id: Union[str, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Deployment]: + """POST /deployments/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "status": status, + "include_archived": include_archived, + "agent_id": agent_id, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/deployments/search", cast_to=Deployment, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/managed/resources/environments/environments.py b/src/qca/managed/resources/environments/environments.py index 3a37e4f..65934a0 100644 --- a/src/qca/managed/resources/environments/environments.py +++ b/src/qca/managed/resources/environments/environments.py @@ -105,6 +105,50 @@ def update( ) return self._client.request("POST", _path, cast_to=Environment, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Environment]: + """POST /environments/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "include_archived": include_archived, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/environments/search", cast_to=Environment, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -274,6 +318,52 @@ async def update( ) return await self._client.request("POST", _path, cast_to=Environment, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Environment]: + """POST /environments/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "include_archived": include_archived, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/environments/search", cast_to=Environment, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/managed/resources/files.py b/src/qca/managed/resources/files.py index 21f92ff..99114be 100644 --- a/src/qca/managed/resources/files.py +++ b/src/qca/managed/resources/files.py @@ -16,6 +16,46 @@ class Files(SyncAPIResource): + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + scope_id: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[FileMetadata]: + """POST /files/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "scope_id": scope_id, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/files/search", cast_to=FileMetadata, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -155,6 +195,48 @@ def upload( class AsyncFiles(AsyncAPIResource): + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + scope_id: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[FileMetadata]: + """POST /files/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "scope_id": scope_id, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/files/search", cast_to=FileMetadata, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/managed/resources/memory_stores/memory_stores.py b/src/qca/managed/resources/memory_stores/memory_stores.py index 5a82b88..4b89394 100644 --- a/src/qca/managed/resources/memory_stores/memory_stores.py +++ b/src/qca/managed/resources/memory_stores/memory_stores.py @@ -104,6 +104,50 @@ def update( ) return self._client.request("POST", _path, cast_to=MemoryStore, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[MemoryStore]: + """POST /memory_stores/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "include_archived": include_archived, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/memory_stores/search", cast_to=MemoryStore, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -275,6 +319,52 @@ async def update( ) return await self._client.request("POST", _path, cast_to=MemoryStore, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[MemoryStore]: + """POST /memory_stores/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "include_archived": include_archived, + "created_at[gte]": created_at_gte, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/memory_stores/search", cast_to=MemoryStore, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/managed/resources/sessions/sessions.py b/src/qca/managed/resources/sessions/sessions.py index 361710b..a1b41f9 100644 --- a/src/qca/managed/resources/sessions/sessions.py +++ b/src/qca/managed/resources/sessions/sessions.py @@ -186,6 +186,66 @@ def update( ) return self._client.request("POST", _path, cast_to=Session, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + order: Union[Literal["desc", "asc"], NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + statuses: Union[List[Literal["running", "idle", "rescheduling", "terminated"]], NotGiven] = NOT_GIVEN, + agent_id: Union[str, NotGiven] = NOT_GIVEN, + agent_version: Union[int, NotGiven] = NOT_GIVEN, + deployment_id: Union[str, NotGiven] = NOT_GIVEN, + memory_store_id: Union[str, NotGiven] = NOT_GIVEN, + created_at_gt: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lt: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Session]: + """POST /sessions/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "order": order, + "include_archived": include_archived, + "statuses": statuses, + "agent_id": agent_id, + "agent_version": agent_version, + "deployment_id": deployment_id, + "memory_store_id": memory_store_id, + "created_at[gt]": created_at_gt, + "created_at[gte]": created_at_gte, + "created_at[lt]": created_at_lt, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/sessions/search", cast_to=Session, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -443,6 +503,68 @@ async def update( ) return await self._client.request("POST", _path, cast_to=Session, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + order: Union[Literal["desc", "asc"], NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + statuses: Union[List[Literal["running", "idle", "rescheduling", "terminated"]], NotGiven] = NOT_GIVEN, + agent_id: Union[str, NotGiven] = NOT_GIVEN, + agent_version: Union[int, NotGiven] = NOT_GIVEN, + deployment_id: Union[str, NotGiven] = NOT_GIVEN, + memory_store_id: Union[str, NotGiven] = NOT_GIVEN, + created_at_gt: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_gte: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lt: Union[datetime, str, NotGiven] = NOT_GIVEN, + created_at_lte: Union[datetime, str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Session]: + """POST /sessions/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "name": name, + "order": order, + "include_archived": include_archived, + "statuses": statuses, + "agent_id": agent_id, + "agent_version": agent_version, + "deployment_id": deployment_id, + "memory_store_id": memory_store_id, + "created_at[gt]": created_at_gt, + "created_at[gte]": created_at_gte, + "created_at[lt]": created_at_lt, + "created_at[lte]": created_at_lte, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/sessions/search", cast_to=Session, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/managed/resources/skills/skills.py b/src/qca/managed/resources/skills/skills.py index 7388aaf..b75fafa 100644 --- a/src/qca/managed/resources/skills/skills.py +++ b/src/qca/managed/resources/skills/skills.py @@ -1,7 +1,7 @@ from __future__ import annotations from functools import cached_property -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Literal, Union import httpx @@ -71,6 +71,48 @@ def retrieve( ) return self._client.request("GET", _path, cast_to=Skill, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + source: Union[Literal["custom", "qoder"], NotGiven] = NOT_GIVEN, + display_title: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Skill]: + """POST /skills/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "source": source, + "display_title": display_title, + "name": name, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/skills/search", cast_to=Skill, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -189,6 +231,50 @@ async def retrieve( ) return await self._client.request("GET", _path, cast_to=Skill, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + source: Union[Literal["custom", "qoder"], NotGiven] = NOT_GIVEN, + display_title: Union[str, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Skill]: + """POST /skills/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "source": source, + "display_title": display_title, + "name": name, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/skills/search", cast_to=Skill, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/managed/resources/vaults/vaults.py b/src/qca/managed/resources/vaults/vaults.py index 330a8c4..b7b2f67 100644 --- a/src/qca/managed/resources/vaults/vaults.py +++ b/src/qca/managed/resources/vaults/vaults.py @@ -70,6 +70,46 @@ def retrieve( ) return self._client.request("GET", _path, cast_to=Vault, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncPage[Vault]: + """POST /vaults/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "include_archived": include_archived, + "name": name, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return self._client.request( + "POST", "/vaults/search", cast_to=Vault, options=options, page_style="BodyPageCursor" + ) + def list( self, *, @@ -209,6 +249,48 @@ async def retrieve( ) return await self._client.request("GET", _path, cast_to=Vault, options=options) + def search( + self, + *, + metadata: Union[Dict[str, str], NotGiven] = NOT_GIVEN, + limit: Union[int, NotGiven] = NOT_GIVEN, + page: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, NotGiven] = NOT_GIVEN, + name: Union[str, NotGiven] = NOT_GIVEN, + workspace_id: Union[str, NotGiven] = NOT_GIVEN, + betas: Union[List[str], NotGiven] = NOT_GIVEN, + extra_headers: Dict[str, str] | None = None, + extra_query: Dict[str, Any] | None = None, + extra_body: Dict[str, Any] | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Vault]: + """POST /vaults/search. + + Search by metadata and resource filters; automatically enables the Search beta. + Pagination replays next_page in the JSON body, preserving the filters. + """ + options = make_request_options( + body={ + "metadata": metadata, + "limit": limit, + "page": page, + "include_archived": include_archived, + "name": name, + }, + query={}, + headers={"qoder-workspace-id": workspace_id, "x-qoder-beta": betas}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + options["required_beta"] = "search-2026-08-31" + return AsyncPaginator( + lambda: self._client.request( + "POST", "/vaults/search", cast_to=Vault, options=options, page_style="BodyPageCursor" + ) + ) + def list( self, *, diff --git a/src/qca/managed/types/__init__.py b/src/qca/managed/types/__init__.py index c56323b..5c51f5d 100644 --- a/src/qca/managed/types/__init__.py +++ b/src/qca/managed/types/__init__.py @@ -17,6 +17,7 @@ from .agent_params import AgentParams as AgentParams from .agent_reference import AgentReference as AgentReference from .agent_retrieve_params import AgentRetrieveParams as AgentRetrieveParams +from .agent_search_params import AgentSearchParams as AgentSearchParams from .agent_skill_union import AgentSkillUnion as AgentSkillUnion from .agent_thread_message_received_event_content_union import ( AgentThreadMessageReceivedEventContentUnion as AgentThreadMessageReceivedEventContentUnion, @@ -94,6 +95,7 @@ from .deployment_scoped_run_retrieve_params import ( DeploymentScopedRunRetrieveParams as DeploymentScopedRunRetrieveParams, ) +from .deployment_search_params import DeploymentSearchParams as DeploymentSearchParams from .deployment_unpause_params import DeploymentUnpauseParams as DeploymentUnpauseParams from .deployment_update_params import DeploymentUpdateParams as DeploymentUpdateParams from .deployment_user_define_outcome_event_rubric_union import ( @@ -135,6 +137,7 @@ from .environment_delete_response import EnvironmentDeleteResponse as EnvironmentDeleteResponse from .environment_list_params import EnvironmentListParams as EnvironmentListParams from .environment_retrieve_params import EnvironmentRetrieveParams as EnvironmentRetrieveParams +from .environment_search_params import EnvironmentSearchParams as EnvironmentSearchParams from .environment_update_params import EnvironmentUpdateParams as EnvironmentUpdateParams from .environment_variable_auth_response_networking_union import ( EnvironmentVariableAuthResponseNetworkingUnion as EnvironmentVariableAuthResponseNetworkingUnion, @@ -160,6 +163,7 @@ from .file_resource_params import FileResourceParams as FileResourceParams from .file_rubric_params import FileRubricParams as FileRubricParams from .file_scope import FileScope as FileScope +from .file_search_params import FileSearchParams as FileSearchParams from .file_upload_params import FileUploadParams as FileUploadParams from .git_hub_repository_resource_checkout_union import ( GitHubRepositoryResourceCheckoutUnion as GitHubRepositoryResourceCheckoutUnion, @@ -217,6 +221,7 @@ ) from .memory_store_resource_param import MemoryStoreResourceParam as MemoryStoreResourceParam from .memory_store_retrieve_params import MemoryStoreRetrieveParams as MemoryStoreRetrieveParams +from .memory_store_search_params import MemoryStoreSearchParams as MemoryStoreSearchParams from .memory_store_update_params import MemoryStoreUpdateParams as MemoryStoreUpdateParams from .memory_version import MemoryVersion as MemoryVersion from .model_capabilities import ModelCapabilities as ModelCapabilities @@ -303,6 +308,7 @@ SessionResourceUpdateResponseUnion as SessionResourceUpdateResponseUnion, ) from .session_retrieve_params import SessionRetrieveParams as SessionRetrieveParams +from .session_search_params import SessionSearchParams as SessionSearchParams from .session_stats import SessionStats as SessionStats from .session_stream_event import SessionStreamEvent as SessionStreamEvent from .session_stream_event_stop_reason import SessionStreamEventStopReason as SessionStreamEventStopReason @@ -332,6 +338,7 @@ from .skill_delete_params import SkillDeleteParams as SkillDeleteParams from .skill_list_params import SkillListParams as SkillListParams from .skill_retrieve_params import SkillRetrieveParams as SkillRetrieveParams +from .skill_search_params import SkillSearchParams as SkillSearchParams from .skill_source import SkillSource as SkillSource from .skill_version import SkillVersion as SkillVersion from .skill_version_create_params import SkillVersionCreateParams as SkillVersionCreateParams @@ -407,6 +414,7 @@ from .vault_delete_params import VaultDeleteParams as VaultDeleteParams from .vault_list_params import VaultListParams as VaultListParams from .vault_retrieve_params import VaultRetrieveParams as VaultRetrieveParams +from .vault_search_params import VaultSearchParams as VaultSearchParams from .web_fetch_tool_config_params import WebFetchToolConfigParams as WebFetchToolConfigParams from .web_search_tool_config_params import WebSearchToolConfigParams as WebSearchToolConfigParams from .write_tool_config_params import WriteToolConfigParams as WriteToolConfigParams diff --git a/src/qca/managed/types/agent_search_params.py b/src/qca/managed/types/agent_search_params.py new file mode 100644 index 0000000..ce08352 --- /dev/null +++ b/src/qca/managed/types/agent_search_params.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Dict, List, Union + +from typing_extensions import TypedDict + +__all__ = ["AgentSearchParams"] + + +class AgentSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + name: str + include_archived: bool + created_at_gte: Union[datetime, str] + created_at_lte: Union[datetime, str] + workspace_id: str + betas: List[str] diff --git a/src/qca/managed/types/deployment_search_params.py b/src/qca/managed/types/deployment_search_params.py new file mode 100644 index 0000000..e704e81 --- /dev/null +++ b/src/qca/managed/types/deployment_search_params.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Dict, List, Literal, Union + +from typing_extensions import TypedDict + +__all__ = ["DeploymentSearchParams"] + + +class DeploymentSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + name: str + status: Literal["active", "paused"] + include_archived: bool + agent_id: str + created_at_gte: Union[datetime, str] + created_at_lte: Union[datetime, str] + workspace_id: str + betas: List[str] diff --git a/src/qca/managed/types/environment_search_params.py b/src/qca/managed/types/environment_search_params.py new file mode 100644 index 0000000..9f06cc3 --- /dev/null +++ b/src/qca/managed/types/environment_search_params.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Dict, List, Union + +from typing_extensions import TypedDict + +__all__ = ["EnvironmentSearchParams"] + + +class EnvironmentSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + name: str + include_archived: bool + created_at_gte: Union[datetime, str] + created_at_lte: Union[datetime, str] + workspace_id: str + betas: List[str] diff --git a/src/qca/managed/types/file_search_params.py b/src/qca/managed/types/file_search_params.py new file mode 100644 index 0000000..e3b015b --- /dev/null +++ b/src/qca/managed/types/file_search_params.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from typing import Dict, List + +from typing_extensions import TypedDict + +__all__ = ["FileSearchParams"] + + +class FileSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + name: str + scope_id: str + workspace_id: str + betas: List[str] diff --git a/src/qca/managed/types/memory_store_search_params.py b/src/qca/managed/types/memory_store_search_params.py new file mode 100644 index 0000000..2e5f2bc --- /dev/null +++ b/src/qca/managed/types/memory_store_search_params.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Dict, List, Union + +from typing_extensions import TypedDict + +__all__ = ["MemoryStoreSearchParams"] + + +class MemoryStoreSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + name: str + include_archived: bool + created_at_gte: Union[datetime, str] + created_at_lte: Union[datetime, str] + workspace_id: str + betas: List[str] diff --git a/src/qca/managed/types/session_search_params.py b/src/qca/managed/types/session_search_params.py new file mode 100644 index 0000000..34c7b4b --- /dev/null +++ b/src/qca/managed/types/session_search_params.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from datetime import datetime +from typing import Dict, List, Literal, Union + +from typing_extensions import TypedDict + +__all__ = ["SessionSearchParams"] + + +class SessionSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + name: str + order: Literal["desc", "asc"] + include_archived: bool + statuses: List[Literal["running", "idle", "rescheduling", "terminated"]] + agent_id: str + agent_version: int + deployment_id: str + memory_store_id: str + created_at_gt: Union[datetime, str] + created_at_gte: Union[datetime, str] + created_at_lt: Union[datetime, str] + created_at_lte: Union[datetime, str] + workspace_id: str + betas: List[str] diff --git a/src/qca/managed/types/skill_search_params.py b/src/qca/managed/types/skill_search_params.py new file mode 100644 index 0000000..31584e4 --- /dev/null +++ b/src/qca/managed/types/skill_search_params.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from typing import Dict, List, Literal + +from typing_extensions import TypedDict + +__all__ = ["SkillSearchParams"] + + +class SkillSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + source: Literal["custom", "qoder"] + display_title: str + name: str + workspace_id: str + betas: List[str] diff --git a/src/qca/managed/types/vault_search_params.py b/src/qca/managed/types/vault_search_params.py new file mode 100644 index 0000000..491d96d --- /dev/null +++ b/src/qca/managed/types/vault_search_params.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from typing import Dict, List + +from typing_extensions import TypedDict + +__all__ = ["VaultSearchParams"] + + +class VaultSearchParams(TypedDict, total=False): + metadata: Dict[str, str] + limit: int + page: str + include_archived: bool + name: str + workspace_id: str + betas: List[str] diff --git a/tests/fixtures/api-contracts.json b/tests/fixtures/api-contracts.json index 5e75a8e..a7f8e65 100644 --- a/tests/fixtures/api-contracts.json +++ b/tests/fixtures/api-contracts.json @@ -2132,6 +2132,90 @@ "path": "/decisions", "required": [], "kind": "page" + }, + { + "id": "forward.environments.search", + "method": "POST", + "path": "/environments/search", + "required": [], + "kind": "page" + }, + { + "id": "forward.skills.search", + "method": "POST", + "path": "/skills/search", + "required": [], + "kind": "page" + }, + { + "id": "forward.vaults.search", + "method": "POST", + "path": "/vaults/search", + "required": [], + "kind": "page" + }, + { + "id": "forward.files.search", + "method": "POST", + "path": "/files/search", + "required": [], + "kind": "page" + }, + { + "id": "managed.agents.search", + "method": "POST", + "path": "/agents/search", + "required": [], + "kind": "page" + }, + { + "id": "managed.sessions.search", + "method": "POST", + "path": "/sessions/search", + "required": [], + "kind": "page" + }, + { + "id": "managed.deployments.search", + "method": "POST", + "path": "/deployments/search", + "required": [], + "kind": "page" + }, + { + "id": "managed.environments.search", + "method": "POST", + "path": "/environments/search", + "required": [], + "kind": "page" + }, + { + "id": "managed.skills.search", + "method": "POST", + "path": "/skills/search", + "required": [], + "kind": "page" + }, + { + "id": "managed.vaults.search", + "method": "POST", + "path": "/vaults/search", + "required": [], + "kind": "page" + }, + { + "id": "managed.files.search", + "method": "POST", + "path": "/files/search", + "required": [], + "kind": "page" + }, + { + "id": "managed.memory_stores.search", + "method": "POST", + "path": "/memory_stores/search", + "required": [], + "kind": "page" } ], "extensions": [ diff --git a/tests/fixtures/search-contracts.json b/tests/fixtures/search-contracts.json new file mode 100644 index 0000000..a5ce0ab --- /dev/null +++ b/tests/fixtures/search-contracts.json @@ -0,0 +1,623 @@ +{ + "checked_at": "2026-10-27", + "beta": "search-2026-08-31", + "cases": [ + { + "mode": "forward", + "resource": "environments", + "entry": "environments", + "service": "EnvironmentService", + "source": "https://docs.qoder.com/cloud-agents/api/forward/environments/search", + "path": "/environments/search", + "body": { + "metadata": { + "team": "core" + }, + "name": "dev", + "include_archived": false, + "created_at[gte]": "2026-01-01T00:00:00Z", + "created_at[lte]": "2026-10-27T00:00:00Z", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "env_xxx", + "type": "environment", + "name": "dev-env", + "description": "Development environment", + "config": { + "type": "cloud" + }, + "metadata": { + "source": "console" + }, + "created_at": "2026-07-23T10:00:00Z", + "updated_at": "2026-07-23T10:00:00Z", + "identity_id": null + } + ], + "first_id": "env_xxx", + "last_id": "env_xxx", + "has_more": false, + "next_page": null + } + }, + { + "mode": "forward", + "resource": "skills", + "entry": "skills", + "service": "SkillService", + "source": "https://docs.qoder.com/cloud-agents/api/forward/skills/search", + "path": "/skills/search", + "body": { + "metadata": { + "team": "core" + }, + "source": "custom", + "display_title": "Search helper", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "skill_xxx", + "type": "skill", + "display_title": "customer-reply", + "description": "Customer reply skill", + "source": "custom", + "latest_version": "1759178010641129", + "metadata": { + "source": "console" + }, + "created_at": "2026-07-23T10:00:00Z", + "updated_at": "2026-07-23T10:00:00Z", + "identity_id": null, + "icon_url": null, + "binding_info": { + "agent_template_count": 0 + } + } + ], + "first_id": "skill_xxx", + "last_id": "skill_xxx", + "has_more": false, + "next_page": null + } + }, + { + "mode": "forward", + "resource": "vaults", + "entry": "vaults", + "service": "VaultService", + "source": "https://docs.qoder.com/cloud-agents/api/forward/vaults/search", + "path": "/vaults/search", + "body": { + "metadata": { + "team": "core" + }, + "name": "prod", + "include_archived": false, + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "vault_xxx", + "type": "vault", + "display_name": "mcp-vault", + "metadata": { + "source": "console" + }, + "created_at": "2026-07-23T10:00:00Z", + "updated_at": "2026-07-23T10:00:00Z", + "identity_id": null + } + ], + "first_id": "vault_xxx", + "last_id": "vault_xxx", + "has_more": false, + "next_page": null + } + }, + { + "mode": "forward", + "resource": "files", + "entry": "files", + "service": "FileService", + "source": "https://docs.qoder.com/cloud-agents/api/forward/files/search", + "path": "/files/search", + "body": { + "metadata": { + "project": "demo" + }, + "name": "report", + "scope_id": "sess_one", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "file_xxx", + "type": "file", + "filename": "report.pdf", + "size_bytes": 1024, + "mime_type": "application/pdf", + "created_at": "2026-07-23T10:00:00Z", + "updated_at": "2026-07-23T10:00:00Z", + "downloadable": true, + "scope": null, + "metadata": { + "source": "console" + }, + "identity_id": null + } + ], + "first_id": "file_xxx", + "last_id": "file_xxx", + "has_more": false, + "next_page": null + } + }, + { + "mode": "managed", + "resource": "agents", + "entry": "agents", + "service": "AgentService", + "source": "https://docs.qoder.com/cloud-agents/api/agents/search", + "path": "/agents/search", + "body": { + "metadata": { + "team": "core", + "region": "中文" + }, + "name": "agent", + "include_archived": false, + "limit": 1, + "page": "start +/=?", + "created_at[gte]": "2026-01-01T00:00:00Z", + "created_at[lte]": "2026-10-27T00:00:00Z" + }, + "response": { + "data": [ + { + "type": "agent", + "id": "agent_019eXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "name": "doc-test-agent", + "description": "", + "model": "ultimate", + "system": "You are a documentation testing assistant.", + "tools": [], + "mcp_servers": [], + "skills": [], + "metadata": {}, + "multiagent": null, + "version": 1, + "archived_at": null, + "created_at": "2026-05-18T15:26:39.61669Z", + "updated_at": "2026-05-18T15:26:39.61669Z" + }, + { + "type": "agent", + "id": "agent_019eYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "name": "test-agent-e2e", + "description": "Agent used for end-to-end testing", + "model": "ultimate", + "system": "You are a testing assistant.", + "tools": [], + "mcp_servers": [], + "skills": [], + "metadata": {}, + "multiagent": null, + "version": 2, + "archived_at": null, + "created_at": "2026-05-18T03:04:33.952256Z", + "updated_at": "2026-05-18T03:05:28.023697Z" + } + ], + "first_id": "agent_019eXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "last_id": "agent_019eYYYYYYYYYYYYYYYYYYYYYYYYYYYY", + "has_more": false, + "next_page": null, + "total": null + } + }, + { + "mode": "managed", + "resource": "sessions", + "entry": "sessions", + "service": "SessionService", + "source": "https://docs.qoder.com/cloud-agents/api/sessions/search", + "path": "/sessions/search", + "body": { + "metadata": { + "ticket": "A-123" + }, + "name": "session", + "order": "asc", + "include_archived": false, + "statuses": [ + "idle", + "running" + ], + "agent_id": "agent_one", + "agent_version": 0, + "deployment_id": "deploy_one", + "memory_store_id": "mem_one", + "created_at[gt]": "2026-01-01T00:00:00Z", + "created_at[gte]": "2026-01-02T00:00:00Z", + "created_at[lt]": "2026-10-27T00:00:00Z", + "created_at[lte]": "2026-10-26T00:00:00Z", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "sess_019e3bb1e8c171fd9abbb1477ffb84cc", + "type": "session", + "agent": { + "id": "agent_019e390add9f7bac9b6cc806db46fcbd", + "type": "agent", + "version": 2, + "name": "doc-verification-agent", + "description": "", + "model": { + "id": "ultimate", + "effective_context_window": 200000 + }, + "system": "You are an expert software engineer.", + "tools": [ + { + "enabled_tools": [ + "Bash", + "Read", + "Write", + "Edit", + "Glob", + "Grep" + ], + "type": "agent_toolset_20260401" + } + ], + "skills": [], + "mcp_servers": [] + }, + "environment_id": "env_019e2590d33f711fabf42f2857cecd8a", + "title": "API-doc-verification-session", + "status": "idle", + "metadata": {}, + "resources": [], + "vault_ids": [], + "deployment_id": null, + "outcome_evaluations": [], + "stats": { + "active_seconds": 0, + "duration_seconds": 0 + }, + "environment_variables": {}, + "archived_at": null, + "created_at": "2026-05-18T15:26:15.747298Z", + "updated_at": "2026-05-18T15:26:15.747298Z", + "usage": { + "total_credits": 1.25 + } + } + ], + "first_id": "sess_019e3bb1e8c171fd9abbb1477ffb84cc", + "has_more": false, + "last_id": "sess_019e3bb1e8c171fd9abbb1477ffb84cc", + "next_page": null, + "total": null + } + }, + { + "mode": "managed", + "resource": "deployments", + "entry": "deployments", + "service": "DeploymentService", + "source": "https://docs.qoder.com/cloud-agents/api/deployments/search", + "path": "/deployments/search", + "body": { + "metadata": { + "team": "core" + }, + "name": "nightly", + "status": "paused", + "agent_id": "agent_one", + "include_archived": false, + "created_at[gte]": "2026-01-01T00:00:00Z", + "created_at[lte]": "2026-10-27T00:00:00Z", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "agent": { + "id": "agent_019eb4d4a06d747c865d5800b9c57ae2", + "type": "agent", + "version": 1 + }, + "archived_at": null, + "created_at": "2026-06-14T08:53:32Z", + "description": "Deployment created for API documentation verification", + "environment_id": "env_019e64e01a137caf953ac2ac7b42ec5c", + "environment_variables": "", + "id": "dep_019ec556114c78f8b60ee34fcb98bf59", + "initial_events": [ + { + "content": [ + { + "type": "text", + "text": "Generate today's status report" + } + ], + "type": "user.message" + } + ], + "metadata": {}, + "name": "api-doc-verification-deployment", + "paused_reason": null, + "resources": [], + "schedule": { + "expression": "0 9 * * *", + "timezone": "Asia/Shanghai", + "type": "cron", + "upcoming_runs_at": [ + "2026-06-15T01:00:00Z", + "2026-06-16T01:00:00Z", + "2026-06-17T01:00:00Z", + "2026-06-18T01:00:00Z", + "2026-06-19T01:00:00Z" + ] + }, + "status": "active", + "type": "deployment", + "updated_at": "2026-06-14T08:53:32Z", + "vault_ids": [] + } + ], + "first_id": "dep_019ec556114c78f8b60ee34fcb98bf59", + "has_more": false, + "last_id": "dep_019ec51604b87aaea12568c9ab7b7025", + "next_page": null, + "total": null + } + }, + { + "mode": "managed", + "resource": "environments", + "entry": "environments", + "service": "EnvironmentService", + "source": "https://docs.qoder.com/cloud-agents/api/environments/search", + "path": "/environments/search", + "body": { + "metadata": { + "team": "core" + }, + "name": "dev", + "include_archived": false, + "created_at[gte]": "2026-01-01T00:00:00Z", + "created_at[lte]": "2026-10-27T00:00:00Z", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "env_019e3bb39b6774d8878cd0b9d237574b", + "type": "environment", + "name": "doc-test-env", + "description": "", + "config": { + "type": "cloud", + "packages": { + "type": "packages", + "apt": [ + "curl" + ], + "cargo": [], + "gem": [], + "go": [], + "npm": [], + "pip": [] + } + }, + "metadata": {}, + "archived_at": null, + "created_at": "2026-05-18T15:28:07.017808Z", + "updated_at": "2026-05-18T15:28:07.017808Z" + }, + { + "id": "env_019e2590d33f711fabf42f2857cecd8a", + "type": "environment", + "name": "default", + "description": "", + "config": { + "type": "cloud", + "packages": { + "type": "packages", + "apt": [], + "cargo": [], + "gem": [], + "go": [], + "npm": [], + "pip": [] + } + }, + "metadata": {}, + "archived_at": null, + "created_at": "2026-05-14T08:18:28.800813Z", + "updated_at": "2026-05-14T08:18:28.800813Z" + } + ], + "first_id": "env_019e3bb39b6774d8878cd0b9d237574b", + "has_more": false, + "last_id": "env_019e2590d33f711fabf42f2857cecd8a", + "next_page": null, + "total": null + } + }, + { + "mode": "managed", + "resource": "skills", + "entry": "skills", + "service": "SkillService", + "source": "https://docs.qoder.com/cloud-agents/api/skills/search", + "path": "/skills/search", + "body": { + "metadata": { + "team": "core" + }, + "source": "custom", + "display_title": "Search helper", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "skill_019e3bba474b73cfaf19eae9b5f5e66d", + "type": "skill", + "display_title": "test-skill-api-doc", + "source": "custom", + "latest_version": "1759178010641129", + "metadata": { + "team": "docs" + }, + "created_at": "2026-05-18T15:35:24.248164Z", + "updated_at": "2026-05-18T15:35:24.248164Z" + } + ], + "next_page": null, + "first_id": "skill_019e3bba474b73cfaf19eae9b5f5e66d", + "last_id": "skill_019e3bba474b73cfaf19eae9b5f5e66d", + "has_more": false, + "total": null + } + }, + { + "mode": "managed", + "resource": "vaults", + "entry": "vaults", + "service": "VaultService", + "source": "https://docs.qoder.com/cloud-agents/api/vaults/search", + "path": "/vaults/search", + "body": { + "metadata": { + "team": "core" + }, + "name": "prod", + "include_archived": false, + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "vault_019e3bb940277f0db05ab74291acf6ef", + "type": "vault", + "display_name": "my-mcp-vault", + "metadata": {}, + "archived_at": null, + "created_at": "2026-05-18T15:34:16.874877Z", + "updated_at": "2026-05-18T15:34:16.874877Z" + } + ], + "next_page": null, + "first_id": "vault_019e3bb940277f0db05ab74291acf6ef", + "last_id": "vault_019e3bb940277f0db05ab74291acf6ef", + "has_more": false, + "total": null + } + }, + { + "mode": "managed", + "resource": "files", + "entry": "files", + "service": "FileService", + "source": "https://docs.qoder.com/cloud-agents/api/files/search", + "path": "/files/search", + "body": { + "metadata": { + "project": "demo" + }, + "name": "report", + "scope_id": "sess_one", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "file_019e3bb8e6c47d189212a79642136696", + "type": "file", + "filename": "report.txt", + "mime_type": "text/plain", + "size_bytes": 110, + "downloadable": true, + "scope": { + "id": "sess_019e3bb8e6c47d18", + "type": "session" + }, + "metadata": { + "project": "demo" + }, + "created_at": "2026-05-18T15:33:53Z" + } + ], + "next_page": null, + "first_id": "file_019e3bb8e6c47d189212a79642136696", + "has_more": false, + "last_id": "file_019e3bb8e6c47d189212a79642136696", + "total": null + } + }, + { + "mode": "managed", + "resource": "memory_stores", + "entry": "memoryStores", + "service": "MemoryStoreService", + "source": "https://docs.qoder.com/cloud-agents/api/memory-stores/search", + "path": "/memory_stores/search", + "body": { + "metadata": { + "team": "core" + }, + "name": "memory", + "include_archived": false, + "created_at[gte]": "2026-01-01T00:00:00Z", + "created_at[lte]": "2026-10-27T00:00:00Z", + "limit": 1, + "page": "start +/=?" + }, + "response": { + "data": [ + { + "id": "memstore_019e3bb8d50674d9b082b73709c29c87", + "created_at": "2026-05-18T15:33:49.449Z", + "name": "doc-test-store", + "type": "memory_store", + "updated_at": "2026-05-18T15:33:49.449Z", + "archived_at": null, + "description": "Test store", + "metadata": {}, + "status": "active", + "entry_count": 0, + "total_size": 0 + } + ], + "has_more": false, + "first_id": "memstore_019e3bb8d50674d9b082b73709c29c87", + "last_id": "memstore_019e3bb8d50674d9b082b73709c29c87", + "next_page": null, + "total": null + } + } + ] +} diff --git a/tests/integration/test_search.py b/tests/integration/test_search.py new file mode 100644 index 0000000..df4a468 --- /dev/null +++ b/tests/integration/test_search.py @@ -0,0 +1,37 @@ +"""Read-only Search checks; no resource creation or model execution.""" + +import json +from pathlib import Path +from uuid import uuid4 + +import pytest + +from qca import APIError +from tests.support.harness import safe_error + +CASES = json.loads((Path(__file__).parents[1] / "fixtures/search-contracts.json").read_text())["cases"] +pytestmark = pytest.mark.integration + + +@pytest.mark.parametrize( + "strict_live_client,resource", + [(case["mode"], case["resource"]) for case in CASES], + indirect=["strict_live_client"], +) +def test_search_readonly(strict_live_client, resource, record_property): + try: + api = getattr(strict_live_client, resource) + raw = api.with_raw_response.search(limit=1) + page = raw.parse() + record_property("request_id", raw.headers.get("x-request-id", "")) + record_property("first_page_items", len(page.data)) + assert len(page.data) <= 1 and isinstance(page.has_more, bool) + assert all(item.id for item in page.data) + if page.has_next_page(): + next_page = page.get_next_page() + record_property("second_page_items", len(next_page.data)) + assert len(next_page.data) <= 1 and all(item.id for item in next_page.data) + empty = api.search(limit=1, metadata={"sdk_search_probe": uuid4().hex}) + assert empty.data == [] and not empty.has_next_page() + except APIError as error: + pytest.fail(safe_error(error), pytrace=False) diff --git a/tests/test_api_expansion.py b/tests/test_api_expansion.py index 827d737..1f5f5fb 100644 --- a/tests/test_api_expansion.py +++ b/tests/test_api_expansion.py @@ -257,7 +257,6 @@ def handle(req): ) with pytest.raises(TypeError): client.usage.list_identities(start_time=123, end_time=456) - assert not hasattr(client.vaults, "search") finally: await result(client.close()) await result(http.aclose() if async_ else http.close()) diff --git a/tests/test_resource_surface.py b/tests/test_resource_surface.py index 92d246e..414310c 100644 --- a/tests/test_resource_surface.py +++ b/tests/test_resource_surface.py @@ -39,7 +39,7 @@ def test_contract_fixture_has_pinned_provenance(): "commit": "bc5bc655d70b67fcd88623320ac01896a7360d8a", "captured_at": "2026-09-22", } - assert len(CONTRACT_FIXTURE["contracts"]) == 232 + assert len(CONTRACT_FIXTURE["contracts"]) == 244 @pytest.mark.parametrize("endpoint", ENDPOINTS, ids=endpoint_id) diff --git a/tests/test_search.py b/tests/test_search.py new file mode 100644 index 0000000..03c8ca0 --- /dev/null +++ b/tests/test_search.py @@ -0,0 +1,179 @@ +"""Search wire contracts from the official API pages, captured 2026-10-27.""" + +import copy +import importlib +import inspect +import json +from datetime import datetime, timezone +from pathlib import Path + +import httpx +import pytest + +from qca import AsyncForward, AsyncManaged, Forward, Managed +from qca.common import APIStatusError + +FIXTURE = json.loads((Path(__file__).parent / "fixtures/search-contracts.json").read_text()) +CASES = FIXTURE["cases"] +BETA = FIXTURE["beta"] + + +async def resolve(value): + return await value if inspect.isawaitable(value) else value + + +def client_for(case, async_, handler, **options): + cls = { + ("forward", False): Forward, + ("forward", True): AsyncForward, + ("managed", False): Managed, + ("managed", True): AsyncManaged, + }[case["mode"], async_] + http = httpx.AsyncClient if async_ else httpx.Client + return cls( + pat="test", + base_url="https://search.test/", + http_client=http(transport=httpx.MockTransport(handler)), + max_retries=0, + **options, + ) + + +@pytest.mark.parametrize("async_", [False, True]) +@pytest.mark.parametrize("case", CASES, ids=lambda c: c["mode"] + "." + c["resource"]) +async def test_search_filters_body_pagination_and_empty_intermediate_page(case, async_): + expected = copy.deepcopy(case["body"]) + before = copy.deepcopy(expected) + calls = [] + + def handler(request): + body = json.loads(request.content) + calls.append(body) + assert request.method == "POST" and request.url.path == case["path"] + assert dict(request.url.params) == ( + {"identity_id": "idn +/", "trace": "keep"} if case["mode"] == "forward" else {"trace": "keep"} + ) + assert set(request.headers["x-qoder-beta"].split(",")) == {"extra-beta", BETA} + assert request.headers["x-search-test"] == "kept" + want = {**expected, "page": [expected["page"], "opaque +/=? one", "opaque +/=? two"][len(calls) - 1]} + assert body == want + response = copy.deepcopy(case["response"]) + if len(calls) == 2: + response["data"] = [] + response.update(has_more=len(calls) < 3, next_page=["opaque +/=? one", "opaque +/=? two", None][len(calls) - 1]) + return httpx.Response(200, json=response) + + client = client_for(case, async_, handler, default_headers={"x-qoder-beta": "client-beta"}) + try: + params = {k.replace("[", "_").replace("]", ""): v for k, v in expected.items()} + if case["mode"] == "forward": + params["identity_id"] = "idn +/" + params.update( + extra_headers={"X-Qoder-Beta": "extra-beta", "x-search-test": "kept"}, extra_query={"trace": "keep"} + ) + result = getattr(client, case["resource"]).search(**params) + items = [item async for item in result] if async_ else list(result) + assert len(items) == 2 * len(case["response"]["data"]) + assert len(calls) == 3 and expected == before + finally: + await resolve(client.close()) + + +@pytest.mark.parametrize("async_", [False, True]) +@pytest.mark.parametrize("case", CASES, ids=lambda c: c["mode"] + "." + c["resource"]) +async def test_search_no_filters_is_empty_object_and_exported_params(case, async_): + def handler(request): + assert request.content == b"{}" + assert not request.url.query + assert request.headers["x-qoder-beta"] == "client-beta," + BETA + return httpx.Response(200, json={"data": [], "has_more": False, "next_page": None, "total": None}) + + client = client_for(case, async_, handler, default_headers={"X-Qoder-Beta": "client-beta"}) + try: + page = await resolve(getattr(client, case["resource"]).search()) + assert not page.has_next_page() and page.total is None + types = importlib.import_module("qca." + case["mode"] + ".types") + assert getattr(types, case["service"].removesuffix("Service") + "SearchParams") + finally: + await resolve(client.close()) + + +@pytest.mark.parametrize("async_", [False, True]) +@pytest.mark.parametrize("status", [400, 403, 429, 503]) +async def test_search_errors_keep_request_id(async_, status): + def handler(request): + return httpx.Response( + status, + headers={"x-request-id": "req_search"}, + json={"error": {"type": "invalid_request_error", "message": "search failed"}}, + ) + + client = client_for(CASES[0], async_, handler) + try: + with pytest.raises(APIStatusError) as error: + await resolve(client.environments.search()) + assert error.value.status_code == status and error.value.request_id == "req_search" + finally: + await resolve(client.close()) + + +@pytest.mark.parametrize("async_", [False, True]) +async def test_search_cursor_does_not_advance(async_): + calls = [] + + def handler(request): + calls.append(request) + return httpx.Response(200, json={"data": [], "has_more": True, "next_page": "same"}) + + client = client_for(CASES[0], async_, handler) + try: + page = await resolve(client.environments.search(page="same")) + with pytest.raises(RuntimeError, match="did not advance"): + await resolve(page.get_next_page()) + assert len(calls) == 1 + finally: + await resolve(client.close()) + + +async def test_search_datetime_and_raw_response(): + def handler(request): + assert json.loads(request.content) == {"created_at[gte]": "2026-01-01T00:00:00+00:00"} + return httpx.Response(200, headers={"x-request-id": "req_search"}, json={"data": [], "has_more": False}) + + client = client_for(CASES[0], False, handler) + try: + raw = client.environments.with_raw_response.search(created_at_gte=datetime(2026, 1, 1, tzinfo=timezone.utc)) + assert raw.headers["x-request-id"] == "req_search" + assert raw.parse().data == [] + finally: + client.close() + + +@pytest.mark.parametrize("async_", [False, True]) +async def test_search_retry_and_body_override_keep_filters(async_): + calls = [] + + def handler(request): + body = json.loads(request.content) + calls.append(body) + assert body == {"metadata": {"team": "core"}, "page": "start" if len(calls) <= 2 else "next"} + assert request.headers["x-qoder-beta"] == BETA + if len(calls) == 1: + return httpx.Response(429, headers={"retry-after-ms": "0"}, json={"error": {"message": "retry"}}) + return httpx.Response( + 200, json={"data": [], "has_more": len(calls) == 2, "next_page": "next" if len(calls) == 2 else None} + ) + + cls = AsyncForward if async_ else Forward + http_cls = httpx.AsyncClient if async_ else httpx.Client + client = cls(pat="test", http_client=http_cls(transport=httpx.MockTransport(handler)), max_retries=1) + try: + page = await resolve( + client.files.search( + metadata={"team": "core"}, extra_body={"page": "start"}, extra_headers={"x-qoder-beta": BETA} + ) + ) + next_page = await resolve(page.get_next_page()) + assert not next_page.has_next_page() and len(calls) == 3 + finally: + await resolve(client.close())