From b38d16b544ab2ccfbf6d4aceb8b19213eb77d17c Mon Sep 17 00:00:00 2001 From: moonyue-w <300878504+moonyue-w@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:43:51 +0800 Subject: [PATCH 1/2] feat(api): align SDK with current official contracts --- CHANGELOG.md | 21 + README.md | 64 + docs/api/reference.md | 1778 +++++++++++++++-- src/qca/forward/_client.py | 9 + src/qca/forward/resources/__init__.py | 2 + src/qca/forward/resources/channel_pairings.py | 177 +- src/qca/forward/resources/dreams.py | 259 +++ .../resources/identities/identities.py | 36 +- .../resources/identities/memory_stores.py | 32 +- src/qca/forward/resources/schedule_runs.py | 6 +- src/qca/forward/resources/schedules.py | 9 + src/qca/forward/types/__init__.py | 26 + .../types/channel_pairing_list_params.py | 15 + .../types/channel_pairing_update_params.py | 8 + src/qca/forward/types/dream.py | 92 + src/qca/forward/types/identity.py | 1 + src/qca/forward/types/identity_config_spec.py | 7 +- .../types/identity_config_spec_param.py | 27 +- .../forward/types/identity_create_params.py | 1 + .../identity_memory_store_list_params.py | 8 + .../identity_memory_store_list_response.py | 2 + .../identity_memory_store_mount_params.py | 3 + .../forward/types/identity_update_params.py | 1 + .../types/managed_capability_override.py | 11 + src/qca/forward/types/multiagent_config.py | 4 +- .../forward/types/multiagent_config_param.py | 4 +- src/qca/forward/types/multiagent_entry.py | 1 + .../forward/types/multiagent_entry_param.py | 1 + src/qca/forward/types/outcome_rubric.py | 31 + src/qca/forward/types/schedule.py | 2 + .../forward/types/schedule_create_params.py | 3 + .../forward/types/schedule_file_resource.py | 17 + src/qca/forward/types/schedule_run.py | 4 +- .../types/schedule_run_retrieve_params.py | 1 + .../forward/types/schedule_update_params.py | 3 + src/qca/forward/types/session_event.py | 4 +- src/qca/forward/types/session_event_param.py | 3 +- src/qca/managed/_client.py | 9 + src/qca/managed/resources/__init__.py | 2 + src/qca/managed/resources/dreams.py | 4 +- .../resources/memory_stores/memory_stores.py | 8 + src/qca/managed/resources/oauth.py | 76 + src/qca/managed/types/__init__.py | 10 + src/qca/managed/types/bucket_storage.py | 58 + src/qca/managed/types/cloud_config_params.py | 2 + src/qca/managed/types/dream_create_params.py | 2 +- src/qca/managed/types/dream_model_config.py | 5 +- .../managed/types/dream_model_config_param.py | 5 +- .../managed/types/environment_config_union.py | 2 + .../managed/types/memory_store_list_params.py | 2 + src/qca/managed/types/model_config.py | 15 +- src/qca/managed/types/model_config_params.py | 35 +- src/qca/managed/types/oauth_start_params.py | 9 + src/qca/managed/types/oauth_start_response.py | 7 + tests/fixtures/api-contracts.json | 87 +- .../fixtures/official-contract-responses.json | 56 + tests/test_official_contract_alignment.py | 284 +++ tests/test_resource_surface.py | 2 +- tests/test_response_alignment.py | 9 +- 59 files changed, 3073 insertions(+), 289 deletions(-) create mode 100644 src/qca/forward/resources/dreams.py create mode 100644 src/qca/forward/types/channel_pairing_list_params.py create mode 100644 src/qca/forward/types/channel_pairing_update_params.py create mode 100644 src/qca/forward/types/dream.py create mode 100644 src/qca/forward/types/identity_memory_store_list_params.py create mode 100644 src/qca/forward/types/managed_capability_override.py create mode 100644 src/qca/forward/types/outcome_rubric.py create mode 100644 src/qca/forward/types/schedule_file_resource.py create mode 100644 src/qca/managed/resources/oauth.py create mode 100644 src/qca/managed/types/bucket_storage.py create mode 100644 src/qca/managed/types/oauth_start_params.py create mode 100644 src/qca/managed/types/oauth_start_response.py create mode 100644 tests/fixtures/official-contract-responses.json create mode 100644 tests/test_official_contract_alignment.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e09a3e0..097cd0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ existing `0.1.0` release; earlier development prereleases are not listed. ## [Unreleased] +### Added + +- Add Forward Dreams (create, list, retrieve, cancel, archive), channel pairing list/retrieve/update, and Managed OAuth start with typed requests and responses. +- Add Identity automatic memory-store control, mount access and cursor pagination, sparse managed-capability overrides, and full tools-array replacement. +- Support string Template references and Advisor model entries in Forward Multiagent rosters, Schedule file resources, and optional Run input snapshots. +- Add typed Managed OSS bucket storage with separate write credentials/read views, and Memory Store ID cursor parameters. + +### Fixed + +- Align Forward Outcome rubric objects and Managed Agent/Dream model configuration with the official API. Dream model defaults to `auto` when omitted; object configuration supports `effort` and `context_window`. + +### Breaking changes + +- Forward event `rubric` is a text/content or file/file_id object instead of a string. +- Managed Agent `effort` is a level string instead of an object; Agent and Dream model configuration no longer declares the unsupported `speed` field. +- Identity mount lists now return standard pages (and an `AsyncPaginator` for the async client). Existing awaited calls still yield a page; use normal or async iteration to traverse all pages. + +### Migration + +- Send `rubric={"type": "text", "content": "..."}` and `model={"id": "ultimate", "effort": "high"}`; remove Managed `speed` arguments. See the README's “Official contract alignment” section. + ## [0.12.0] ### Added diff --git a/README.md b/README.md index 0b33304..b88e92d 100644 --- a/README.md +++ b/README.md @@ -868,3 +868,67 @@ 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`. + +## Official contract alignment + +The next release aligns these public types with the official API documentation +retrieved on 2026-10-27. It introduces source changes relative to 0.12.0: +Outcome `rubric` is an object; Managed Agent `effort` is a string; Managed Agent +and Dream model objects do not accept `speed`. Managed Dream `model` is optional +and defaults to `auto`. Discover model IDs and supported efforts/context windows +with List models. These Managed model changes do not remove Forward model features. + +```python +from qca import Forward, Managed + +client = Forward(pat="...") +rubric = {"type": "text", "content": "report exists"} +model = {"id": "ultimate", "effort": "high"} +dream = client.dreams.create(inputs=[{ + "type": "identity_template", "identity_id": "idn_...", "template_id": "tmpl_...", +}]) # model defaults to auto + +for mount in client.identities.memory_stores.list( + "tmpl_...", identity_id="idn_...", limit=20, +): + print(mount.memory_store_id) +``` + +The async mount list returns an `AsyncPaginator`: use `async for` directly, or +`await` it to obtain the first `AsyncPage`. Sync lists return `SyncPage`. +Identity `tools=[]` replaces all tools with no tools; `tools=None` clears that +override. Pass `managed_tool_config={"schedule": {"enabled": False}, "drive": None}` +to disable Schedule tools and restore inherited Drive settings. Omitted values +remain unchanged. Multiagent `agents` accepts Template ID strings and +`{"type": "advisor", "model": "ultimate"}` objects. + +New Forward Dream calls require a PAT or an admin SAT; an Identity SAT is not +supported. Inputs contain exactly one primary Memory Store or Identity/Template +source, optionally plus Session sources. Pairing `update` changes an active +binding; supply at least one nonempty Identity or Template ID. Use pairing +`create` to complete a pending pairing. Pending records preserve empty +`identity_id`, `template_id`, and `paired_at` strings. + +Identity `auto_memory_store=false` disables automatic default-store creation. +Independent mounts now accept `access="read_write"`; omit access for a new +read-only mount, or to preserve an existing mount's permission. Schedule +`resources` accepts file entries and `[]` clears them; Run retrieval supports +`include_initial_events=true` to return the saved input snapshot. + +Managed cloud Environment `config.storage.buckets` supports up to eight OSS +mounts. When the resulting configuration has buckets, explicitly include beta +`oss-bucket-2026-09-23` (`Betas` in Go, `betas` in Python/TS). Send credentials +only when creating or replacing them; omit `credential` on an existing named +bucket to keep it. Responses expose a read view without role ARN or AccessKey +secret. `storage: {buckets: []}` clears mounts. + +Managed `OAuth.Start` (Go) / `oauth.start` (Python/TS) returns `authorization_url`, +`state`, and `callback_origin`; the user completes authorization in a browser. +Client secrets go in the POST body, never in a URL. This method does not open a +browser or complete consent automatically. + +See the official [Outcome contract](https://docs.qoder.com/cloud-agents/api/forward/sessions/send-events), +[Identity overrides](https://docs.qoder.com/cloud-agents/api/forward/identities/upsert-config), +[Dream inputs](https://docs.qoder.com/cloud-agents/api/forward/dreams/create), +[OSS storage](https://docs.qoder.com/cloud-agents/api/environments/storage-buckets), +and [OAuth flow](https://docs.qoder.com/cloud-agents/api/vaults/start-oauth). diff --git a/docs/api/reference.md b/docs/api/reference.md index de22e6a..b1b5a81 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -2070,6 +2070,17 @@ class Forward(SyncAPIClient) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/_client.py) + + +#### dreams + +```python +@cached_property +def dreams() -> Dreams +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/_client.py) + #### decisions @@ -2300,6 +2311,17 @@ class AsyncForward(AsyncAPIClient) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/_client.py) + + +#### dreams + +```python +@cached_property +def dreams() -> AsyncDreams +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/_client.py) + #### decisions @@ -2903,6 +2925,73 @@ class ChannelPairings(SyncAPIResource) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/channel_pairings.py) + + +#### retrieve + +```python +def retrieve( + pairing_id: str, + *, + 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 +) -> ChannelPairing +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/channel_pairings.py) + +GET /channel_pairings/{pairing_id}. + + + +#### update + +```python +def update( + pairing_id: str, + *, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + template_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 +) -> ChannelPairing +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/channel_pairings.py) + +POST /channel_pairings/{pairing_id}. Provide at least one field; omitted fields retain their value. + + + +#### list + +```python +def list( + *, + channel_id: Union[str, NotGiven] = NOT_GIVEN, + target_external_id: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + template_id: Union[str, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_id: Union[str, NotGiven] = NOT_GIVEN, + status: Union[Literal["pairing", "active"], NotGiven] = NOT_GIVEN, + target_type: Union[Literal["user", "group"], NotGiven] = NOT_GIVEN, + limit: Union[int, 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[ChannelPairing] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/channel_pairings.py) + +GET /channel_pairings. + #### create @@ -2957,6 +3046,73 @@ class AsyncChannelPairings(AsyncAPIResource) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/channel_pairings.py) + + +#### retrieve + +```python +async def retrieve( + pairing_id: str, + *, + 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 +) -> ChannelPairing +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/channel_pairings.py) + +GET /channel_pairings/{pairing_id}. + + + +#### update + +```python +async def update( + pairing_id: str, + *, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + template_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 +) -> ChannelPairing +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/channel_pairings.py) + +POST /channel_pairings/{pairing_id}. Provide at least one field; omitted fields retain their value. + + + +#### list + +```python +def list( + *, + channel_id: Union[str, NotGiven] = NOT_GIVEN, + target_external_id: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + template_id: Union[str, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_id: Union[str, NotGiven] = NOT_GIVEN, + status: Union[Literal["pairing", "active"], NotGiven] = NOT_GIVEN, + target_type: Union[Literal["user", "group"], NotGiven] = NOT_GIVEN, + limit: Union[int, 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[ChannelPairing] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/channel_pairings.py) + +GET /channel_pairings. + #### create @@ -3559,133 +3715,353 @@ List in server order, optionally filtered by an accessible Session. Limit is 1-100 (default 20). after_id and before_id are mutually exclusive. Identity scope comes from authentication, not a query parameter. - + -# qca.forward.resources.drives +# qca.forward.resources.dreams - + -## Drives +## Dreams ```python -class Drives(SyncAPIResource) +class Dreams(SyncAPIResource) ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) -Persistent Identity files (Beta). Identity SATs also require identity_id. +Forward memory consolidation; requires PAT or an admin SAT. - + -#### list\_entries +#### create ```python -def list_entries( - *, - identity_id: str, - path: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - page_token: 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 -) -> DriveListEntriesResponse +def create( + *, + inputs: List[DreamInputParam], + model: Union[str, DreamModelConfigParam, None, NotGiven] = NOT_GIVEN, + instructions: 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) -> Dream ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) -GET /drives/entries. Returns one page in service order; reuse next_page_token in the same scope/path. +POST /dreams. - + -#### create\_upload\_url +#### list ```python -def create_upload_url( +def list( *, - identity_id: str, - path: str, - content_type: str | NotGiven = NOT_GIVEN, - idempotency_key: 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, + limit: Union[int, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_id: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, 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 -) -> DriveUploadURL +) -> SyncPage[Dream] ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) -POST /drives/upload-url. Use only the returned headers when uploading; do not send API credentials to storage. +GET /dreams. - + -#### create\_download\_url +#### retrieve ```python -def create_download_url( - *, - identity_id: str, - path: str, - idempotency_key: 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 -) -> DriveDownloadURL +def retrieve( + dream_id: str, + *, + 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) -> Dream ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) -POST /drives/download-url. Use a new Idempotency-Key or omit it to refresh expiry; replay does not renew it. +GET /dreams/{dream_id}. - + -#### delete\_entry +#### cancel ```python -def delete_entry( - *, - identity_id: str, - path: str, - recursive: bool | 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 -) -> DriveDeleteEntryResponse +def cancel( + dream_id: str, + *, + 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) -> Dream ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) -DELETE /drives/entries. Nonempty directories require recursive=True. Partial failures retain error.deleted_count. +POST /dreams/{dream_id}/cancel. - + -#### clear +#### archive ```python -def clear( - *, - identity_id: str, - 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 -) -> DriveClearResponse +def archive( + dream_id: str, + *, + 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) -> Dream ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) -DELETE /drives. Deletes all files in the selected Identity Drive. +POST /dreams/{dream_id}/archive. - + -## AsyncDrives +## AsyncDreams ```python -class AsyncDrives(AsyncAPIResource) +class AsyncDreams(AsyncAPIResource) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) + +Forward memory consolidation; requires PAT or an admin SAT. + + + +#### create + +```python +async def create( + *, + inputs: List[DreamInputParam], + model: Union[str, DreamModelConfigParam, None, NotGiven] = NOT_GIVEN, + instructions: 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) -> Dream +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) + +POST /dreams. + + + +#### list + +```python +def list( + *, + limit: Union[int, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_id: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, 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[Dream] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) + +GET /dreams. + + + +#### retrieve + +```python +async def retrieve( + dream_id: str, + *, + 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) -> Dream +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) + +GET /dreams/{dream_id}. + + + +#### cancel + +```python +async def cancel( + dream_id: str, + *, + 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) -> Dream +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) + +POST /dreams/{dream_id}/cancel. + + + +#### archive + +```python +async def archive( + dream_id: str, + *, + 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) -> Dream +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/dreams.py) + +POST /dreams/{dream_id}/archive. + + + +# qca.forward.resources.drives + + + +## Drives + +```python +class Drives(SyncAPIResource) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) + +Persistent Identity files (Beta). Identity SATs also require identity_id. + + + +#### list\_entries + +```python +def list_entries( + *, + identity_id: str, + path: str | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + page_token: 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 +) -> DriveListEntriesResponse +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) + +GET /drives/entries. Returns one page in service order; reuse next_page_token in the same scope/path. + + + +#### create\_upload\_url + +```python +def create_upload_url( + *, + identity_id: str, + path: str, + content_type: str | NotGiven = NOT_GIVEN, + idempotency_key: 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 +) -> DriveUploadURL +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) + +POST /drives/upload-url. Use only the returned headers when uploading; do not send API credentials to storage. + + + +#### create\_download\_url + +```python +def create_download_url( + *, + identity_id: str, + path: str, + idempotency_key: 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 +) -> DriveDownloadURL +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) + +POST /drives/download-url. Use a new Idempotency-Key or omit it to refresh expiry; replay does not renew it. + + + +#### delete\_entry + +```python +def delete_entry( + *, + identity_id: str, + path: str, + recursive: bool | 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 +) -> DriveDeleteEntryResponse +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) + +DELETE /drives/entries. Nonempty directories require recursive=True. Partial failures retain error.deleted_count. + + + +#### clear + +```python +def clear( + *, + identity_id: str, + 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 +) -> DriveClearResponse +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) + +DELETE /drives. Deletes all files in the selected Identity Drive. + + + +## AsyncDrives + +```python +class AsyncDrives(AsyncAPIResource) ``` [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/drives.py) @@ -4765,6 +5141,7 @@ def create( enabled: Union[bool, None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + auto_memory_store: Union[bool, None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -4844,6 +5221,7 @@ def update( enabled: Union[bool, None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + auto_memory_store: Union[bool, None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -5031,6 +5409,7 @@ async def create( enabled: Union[bool, None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + auto_memory_store: Union[bool, None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -5110,6 +5489,7 @@ async def update( enabled: Union[bool, None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + auto_memory_store: Union[bool, None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -5240,11 +5620,14 @@ def list( template_id: str, *, identity_id: str, + limit: Union[int, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_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 -) -> IdentityMemoryStoreListResponse +) -> SyncPage[MemoryStoreMount] ``` [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/identities/memory_stores.py) @@ -5261,6 +5644,7 @@ def mount( *, identity_id: str, memory_store_id: str, + access: Union[Literal["read_only", "read_write"], NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -5308,15 +5692,18 @@ class AsyncMemoryStores(AsyncAPIResource) #### list ```python -async def list( +def list( template_id: str, *, identity_id: str, + limit: Union[int, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_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 -) -> IdentityMemoryStoreListResponse +) -> AsyncPaginator[MemoryStoreMount] ``` [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/identities/memory_stores.py) @@ -5333,6 +5720,7 @@ async def mount( *, identity_id: str, memory_store_id: str, + access: Union[Literal["read_only", "read_write"], NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -7044,6 +7432,7 @@ def retrieve( run_id: str, *, identity_id: Union[str, None, NotGiven] = NOT_GIVEN, + include_initial_events: Union[bool, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -7102,6 +7491,7 @@ async def retrieve( run_id: str, *, identity_id: Union[str, None, NotGiven] = NOT_GIVEN, + include_initial_events: Union[bool, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -7172,6 +7562,8 @@ def create( sinks: Union[List[Dict[str, Any]], None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + resources: Union[List[ScheduleFileResourceParam], None, + NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -7241,6 +7633,8 @@ def update( sinks: Union[List[Dict[str, Any]], None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + resources: Union[List[ScheduleFileResourceParam], None, + NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -7387,6 +7781,8 @@ async def create( sinks: Union[List[Dict[str, Any]], None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + resources: Union[List[ScheduleFileResourceParam], None, + NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -7456,6 +7852,8 @@ async def update( sinks: Union[List[Dict[str, Any]], None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + resources: Union[List[ScheduleFileResourceParam], None, + NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -11383,57 +11781,131 @@ Supply exactly one of code or channel_pairing_id. #### idempotency\_key - + -# qca.forward.types.channel\_qr\_session +# qca.forward.types.channel\_pairing\_list\_params - + -## ChannelQRSession +## ChannelPairingListParams ```python -class ChannelQRSession(BaseModel) +class ChannelPairingListParams(TypedDict) ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/channel_qr_session.py) - - - -#### session\_key +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/channel_pairing_list_params.py) - + #### channel\_id - - -#### channel\_type - - + #### status - + -#### qr\_code\_content +#### target\_type - + -#### qr\_code\_image\_base64 +#### target\_external\_id - + -#### expires\_at +#### identity\_id - + -#### err\_code +#### template\_id - + -#### err\_msg +#### limit - + + +#### after\_id + + + +#### before\_id + + + +# qca.forward.types.channel\_pairing\_update\_params + + + +## ChannelPairingUpdateParams + +```python +class ChannelPairingUpdateParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/channel_pairing_update_params.py) + +Provide at least one nonempty field; omitted fields retain their value. + + + +#### identity\_id + + + +#### template\_id + + + +# qca.forward.types.channel\_qr\_session + + + +## ChannelQRSession + +```python +class ChannelQRSession(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/channel_qr_session.py) + + + +#### session\_key + + + +#### channel\_id + + + +#### channel\_type + + + +#### status + + + +#### qr\_code\_content + + + +#### qr\_code\_image\_base64 + + + +#### expires\_at + + + +#### err\_code + + + +#### err\_msg + + # qca.forward.types.channel\_qr\_session\_create\_params @@ -11926,139 +12398,455 @@ class DeletedChannelPairing(BaseModel) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_channel_pairing.py) - + + +#### id + + + +#### deleted + + + +# qca.forward.types.deleted\_identity + + + +## DeletedIdentity + +```python +class DeletedIdentity(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_identity.py) + + + +#### id + + + +#### deleted + + + +# qca.forward.types.deleted\_memory + + + +## DeletedMemory + +```python +class DeletedMemory(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_memory.py) + + + +#### id + + + +#### type + + + +#### deleted + + + +# qca.forward.types.deleted\_memory\_store + + + +## DeletedMemoryStore + +```python +class DeletedMemoryStore(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_memory_store.py) + + + +#### id + + + +#### type + + + +#### deleted + + + +# qca.forward.types.deleted\_memory\_store\_mount + + + +## DeletedMemoryStoreMount + +```python +class DeletedMemoryStoreMount(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_memory_store_mount.py) + + + +#### id + + + +#### type + + + +#### deleted + + + +# qca.forward.types.deleted\_skill\_version + + + +## DeletedSkillVersion + +```python +class DeletedSkillVersion(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_skill_version.py) + + + +#### id + + + +#### type + + + +#### deleted + + + +# qca.forward.types.dream + + + +## DreamMemoryStoreInputParam + +```python +class DreamMemoryStoreInputParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) + + + +#### type + + + +#### memory\_store\_id + + + +## DreamIdentityTemplateInputParam + +```python +class DreamIdentityTemplateInputParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) + + + +#### type + + + +#### identity\_id + + + +#### template\_id + + + +## DreamSessionsInputParam + +```python +class DreamSessionsInputParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) + + + +#### type + + + +#### session\_ids + + + +#### DreamInputParam + + + +## DreamModelConfigParam + +```python +class DreamModelConfigParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) + + + +#### id + + + +#### effort + + + +#### context\_window + + + +## DreamCreateParams + +```python +class DreamCreateParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) + + + +#### inputs + + + +#### model + + + +#### instructions + + + +## DreamListParams + +```python +class DreamListParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) + + + +#### limit + + + +#### after\_id + + + +#### before\_id + + + +#### include\_archived + + + +## DreamInput + +```python +class DreamInput(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) + + + +#### type + + + +#### memory\_store\_id + + + +#### identity\_id + + + +#### template\_id + + + +#### session\_ids + + + +## DreamModelConfig + +```python +class DreamModelConfig(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) + + #### id - + -#### deleted +#### effort - + -# qca.forward.types.deleted\_identity +#### context\_window - + -## DeletedIdentity +## DreamOutput ```python -class DeletedIdentity(BaseModel) +class DreamOutput(BaseModel) ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_identity.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) - + -#### id +#### type - + -#### deleted +#### memory\_store\_id - + -# qca.forward.types.deleted\_memory +#### files\_touched - + -## DeletedMemory +## DreamUsage ```python -class DeletedMemory(BaseModel) +class DreamUsage(BaseModel) ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_memory.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) - + -#### id +#### input\_tokens - + -#### type +#### output\_tokens - + -#### deleted +#### cache\_creation\_input\_tokens - + -# qca.forward.types.deleted\_memory\_store +#### cache\_read\_input\_tokens - + -## DeletedMemoryStore +## DreamError ```python -class DeletedMemoryStore(BaseModel) +class DreamError(BaseModel) ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_memory_store.py) - - - -#### id +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) - + #### type - + -#### deleted +#### code - + -# qca.forward.types.deleted\_memory\_store\_mount +#### message - + -## DeletedMemoryStoreMount +## Dream ```python -class DeletedMemoryStoreMount(BaseModel) +class Dream(BaseModel) ``` -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_memory_store_mount.py) +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/dream.py) - + #### id - + #### type - + -#### deleted +#### status - + -# qca.forward.types.deleted\_skill\_version +#### inputs - + -## DeletedSkillVersion +#### outputs -```python -class DeletedSkillVersion(BaseModel) -``` + -[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/deleted_skill_version.py) +#### model - + -#### id +#### instructions - + -#### type +#### usage - + -#### deleted +#### error + + + +#### created\_at + + + +#### ended\_at + + + +#### archived\_at @@ -13110,6 +13898,10 @@ class Identity(BaseModel) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/identity.py) + + +#### auto\_memory\_store + #### id @@ -13334,6 +14126,10 @@ class IdentityConfigSpec(BaseModel) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/identity_config_spec.py) + + +#### managed\_tool\_config + #### system @@ -13392,6 +14188,10 @@ class IdentityConfigSpecParam(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/identity_config_spec_param.py) + + +#### managed\_tool\_config + #### system @@ -13480,6 +14280,10 @@ class IdentityCreateParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/identity_create_params.py) + + +#### auto\_memory\_store + #### external\_id @@ -13560,6 +14364,36 @@ class IdentityListTemplatesResponse(BaseModel) #### data + + +# qca.forward.types.identity\_memory\_store\_list\_params + + + +## IdentityMemoryStoreListParams + +```python +class IdentityMemoryStoreListParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/identity_memory_store_list_params.py) + + + +#### identity\_id + + + +#### limit + + + +#### after\_id + + + +#### before\_id + # qca.forward.types.identity\_memory\_store\_list\_response @@ -13574,6 +14408,14 @@ class IdentityMemoryStoreListResponse(BaseModel) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/identity_memory_store_list_response.py) + + +#### first\_id + + + +#### last\_id + #### data @@ -13596,6 +14438,10 @@ class IdentityMemoryStoreMountParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/identity_memory_store_mount_params.py) + + +#### access + #### memory\_store\_id @@ -13674,6 +14520,10 @@ class IdentityUpdateParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/identity_update_params.py) + + +#### auto\_memory\_store + #### external\_id @@ -13762,6 +14612,38 @@ class ImageSourceParam(TypedDict) #### file\_id + + +# qca.forward.types.managed\_capability\_override + + + +## ManagedCapabilityOverrideParam + +```python +class ManagedCapabilityOverrideParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/managed_capability_override.py) + + + +#### enabled + + + +## ManagedCapabilityOverride + +```python +class ManagedCapabilityOverride(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/managed_capability_override.py) + + + +#### enabled + # qca.forward.types.managed\_tool\_config @@ -14572,6 +15454,10 @@ class MultiagentEntry(BaseModel) #### type + + +#### model + #### template\_id @@ -14598,6 +15484,10 @@ class MultiagentEntryParam(TypedDict) #### type + + +#### model + #### template\_id @@ -14606,6 +15496,90 @@ class MultiagentEntryParam(TypedDict) #### name + + +# qca.forward.types.outcome\_rubric + + + +## TextOutcomeRubricParam + +```python +class TextOutcomeRubricParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/outcome_rubric.py) + + + +#### type + + + +#### content + + + +## FileOutcomeRubricParam + +```python +class FileOutcomeRubricParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/outcome_rubric.py) + + + +#### type + + + +#### file\_id + + + +#### OutcomeRubricParam + + + +## TextOutcomeRubric + +```python +class TextOutcomeRubric(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/outcome_rubric.py) + + + +#### type + + + +#### content + + + +## FileOutcomeRubric + +```python +class FileOutcomeRubric(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/outcome_rubric.py) + + + +#### type + + + +#### file\_id + + + +#### OutcomeRubric + # qca.forward.types.permission\_policy @@ -15364,6 +16338,10 @@ class Schedule(BaseModel) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/schedule.py) + + +#### resources + #### source @@ -15512,6 +16490,10 @@ class ScheduleCreateParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/schedule_create_params.py) + + +#### resources + #### identity\_id @@ -15586,6 +16568,54 @@ class ScheduleExecution(BaseModel) #### timeout\_ms + + +# qca.forward.types.schedule\_file\_resource + + + +## ScheduleFileResourceParam + +```python +class ScheduleFileResourceParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/schedule_file_resource.py) + + + +#### type + + + +#### file\_id + + + +#### mount\_path + + + +## ScheduleFileResource + +```python +class ScheduleFileResource(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/schedule_file_resource.py) + + + +#### type + + + +#### file\_id + + + +#### mount\_path + # qca.forward.types.schedule\_initial\_events\_item @@ -15708,6 +16738,10 @@ class ScheduleRun(BaseModel) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/schedule_run.py) + + +#### initial\_events + #### source @@ -15882,6 +16916,10 @@ class ScheduleRunRetrieveParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/schedule_run_retrieve_params.py) + + +#### include\_initial\_events + #### identity\_id @@ -16030,6 +17068,10 @@ class ScheduleUpdateParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/schedule_update_params.py) + + +#### resources + #### name @@ -19171,6 +20213,17 @@ class Managed(SyncAPIClient) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/_client.py) + + +#### oauth + +```python +@cached_property +def oauth() -> OAuth +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/_client.py) + #### agents @@ -19302,6 +20355,17 @@ class AsyncManaged(AsyncAPIClient) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/_client.py) + + +#### oauth + +```python +@cached_property +def oauth() -> AsyncOAuth +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/_client.py) + #### agents @@ -20690,7 +21754,7 @@ def create( *, inputs: List[Union[DreamMemoryStoreInputParam, DreamSessionsInputParam]], - model: Union[str, DreamModelConfigParam], + model: Union[str, DreamModelConfigParam, None, NotGiven] = NOT_GIVEN, instructions: Union[str, None, NotGiven] = NOT_GIVEN, workspace_id: Union[str, None, NotGiven] = NOT_GIVEN, output_behavior: Union[Union[OutputBehaviorCreateNewParam, @@ -20813,7 +21877,7 @@ async def create( *, inputs: List[Union[DreamMemoryStoreInputParam, DreamSessionsInputParam]], - model: Union[str, DreamModelConfigParam], + model: Union[str, DreamModelConfigParam, None, NotGiven] = NOT_GIVEN, instructions: Union[str, None, NotGiven] = NOT_GIVEN, workspace_id: Union[str, None, NotGiven] = NOT_GIVEN, output_behavior: Union[Union[OutputBehaviorCreateNewParam, @@ -22424,6 +23488,8 @@ def list( page: Union[str, None, NotGiven] = NOT_GIVEN, workspace_id: Union[str, None, NotGiven] = NOT_GIVEN, betas: Union[List[str], None, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_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, @@ -22622,6 +23688,8 @@ def list( page: Union[str, None, NotGiven] = NOT_GIVEN, workspace_id: Union[str, None, NotGiven] = NOT_GIVEN, betas: Union[List[str], None, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_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, @@ -22923,6 +23991,76 @@ def list( GET /models. + + +# qca.managed.resources.oauth + + + +## OAuth + +```python +class OAuth(SyncAPIResource) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/oauth.py) + + + +#### start + +```python +def start( + *, + vault_id: str, + mcp_server_url: str, + client_id: Union[str, NotGiven] = NOT_GIVEN, + client_secret: Union[str, NotGiven] = NOT_GIVEN, + workspace_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 +) -> OAuthStartResponse +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/oauth.py) + +POST /oauth/start. Returns a browser authorization URL; this call does not complete authorization. + + + +## AsyncOAuth + +```python +class AsyncOAuth(AsyncAPIResource) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/oauth.py) + + + +#### start + +```python +async def start( + *, + vault_id: str, + mcp_server_url: str, + client_id: Union[str, NotGiven] = NOT_GIVEN, + client_secret: Union[str, NotGiven] = NOT_GIVEN, + workspace_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 +) -> OAuthStartResponse +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/oauth.py) + +POST /oauth/start. Returns a browser authorization URL; this call does not complete authorization. + # qca.managed.resources.sessions.events @@ -26611,6 +27749,186 @@ class BranchCheckoutParam(TypedDict) #### type + + +# qca.managed.types.bucket\_storage + + + +## OSSRAMRoleCredentialParam + +```python +class OSSRAMRoleCredentialParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/bucket_storage.py) + + + +#### type + + + +#### role\_arn + + + +## OSSAccessKeyCredentialParam + +```python +class OSSAccessKeyCredentialParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/bucket_storage.py) + + + +#### type + + + +#### access\_key\_id + + + +#### access\_key\_secret + + + +#### OSSBucketCredentialParam + + + +## OSSBucketParam + +```python +class OSSBucketParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/bucket_storage.py) + + + +#### name + + + +#### provider + + + +#### region + + + +#### bucket + + + +#### prefix + + + +#### access + + + +#### accelerator\_endpoint + + + +#### credential + + + +## BucketStorageParam + +```python +class BucketStorageParam(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/bucket_storage.py) + + + +#### buckets + + + +## OSSBucketCredential + +```python +class OSSBucketCredential(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/bucket_storage.py) + +Read view; the API never returns role ARN or AccessKey secret. + + + +#### type + + + +#### access\_key\_id + + + +## OSSBucket + +```python +class OSSBucket(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/bucket_storage.py) + + + +#### name + + + +#### provider + + + +#### region + + + +#### bucket + + + +#### prefix + + + +#### access + + + +#### accelerator\_endpoint + + + +#### credential + + + +## BucketStorage + +```python +class BucketStorage(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/bucket_storage.py) + + + +#### buckets + # qca.managed.types.budget\_limit @@ -26739,6 +28057,10 @@ class CloudConfigParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/cloud_config_params.py) + + +#### storage + #### setup\_script @@ -28437,9 +29759,13 @@ class DreamModelConfig(BaseModel) #### id - + -#### speed +#### effort + + + +#### context\_window @@ -28459,9 +29785,13 @@ class DreamModelConfigParam(TypedDict) #### id - + -#### speed +#### effort + + + +#### context\_window @@ -28811,6 +30141,10 @@ class EnvironmentConfigUnion(BaseModel) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/environment_config_union.py) + + +#### storage + #### setup\_script @@ -30697,6 +32031,14 @@ class MemoryStoreListParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/memory_store_list_params.py) + + +#### before\_id + + + +#### after\_id + #### name @@ -31283,9 +32625,9 @@ class ModelConfig(BaseModel) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/model_config.py) - + -#### context\_window +#### inference\_geo @@ -31295,13 +32637,13 @@ class ModelConfig(BaseModel) #### effort - + -#### inference\_geo +#### context\_window - + -#### speed +#### effective\_context\_window @@ -31335,25 +32677,21 @@ class ModelConfigParams(TypedDict) [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/model_config_params.py) - + -#### context\_window +#### inference\_geo #### id - - -#### inference\_geo - #### effort - + -#### speed +#### context\_window @@ -31603,6 +32941,66 @@ class MultiagentSelfParams(TypedDict) #### type + + +# qca.managed.types.oauth\_start\_params + + + +## OAuthStartParams + +```python +class OAuthStartParams(TypedDict) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/oauth_start_params.py) + + + +#### vault\_id + + + +#### mcp\_server\_url + + + +#### client\_id + + + +#### client\_secret + + + +#### workspace\_id + + + +# qca.managed.types.oauth\_start\_response + + + +## OAuthStartResponse + +```python +class OAuthStartResponse(BaseModel) +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/types/oauth_start_response.py) + + + +#### authorization\_url + + + +#### state + + + +#### callback\_origin + # qca.managed.types.outcome\_evaluation\_resource diff --git a/src/qca/forward/_client.py b/src/qca/forward/_client.py index 32654e7..380484d 100644 --- a/src/qca/forward/_client.py +++ b/src/qca/forward/_client.py @@ -8,6 +8,7 @@ from .resources.channel_pairings import AsyncChannelPairings, ChannelPairings from .resources.channels.channels import AsyncChannels, Channels from .resources.decisions import AsyncDecisions, Decisions +from .resources.dreams import AsyncDreams, Dreams from .resources.drives import AsyncDrives, Drives from .resources.environments import AsyncEnvironments, Environments from .resources.files import AsyncFiles, Files @@ -27,6 +28,10 @@ class Forward(SyncAPIClient): + @cached_property + def dreams(self) -> Dreams: + return Dreams(self) + @cached_property def decisions(self) -> Decisions: return Decisions(self) @@ -112,6 +117,10 @@ def models(self) -> Models: class AsyncForward(AsyncAPIClient): + @cached_property + def dreams(self) -> AsyncDreams: + return AsyncDreams(self) + @cached_property def decisions(self) -> AsyncDecisions: return AsyncDecisions(self) diff --git a/src/qca/forward/resources/__init__.py b/src/qca/forward/resources/__init__.py index c32de00..6dd7385 100644 --- a/src/qca/forward/resources/__init__.py +++ b/src/qca/forward/resources/__init__.py @@ -2,6 +2,8 @@ from .channel_pairings import ChannelPairings as ChannelPairings from .decisions import AsyncDecisions as AsyncDecisions from .decisions import Decisions as Decisions +from .dreams import AsyncDreams as AsyncDreams +from .dreams import Dreams as Dreams from .drives import AsyncDrives as AsyncDrives from .drives import Drives as Drives from .environments import AsyncEnvironments as AsyncEnvironments diff --git a/src/qca/forward/resources/channel_pairings.py b/src/qca/forward/resources/channel_pairings.py index 1bd6d45..52fb705 100644 --- a/src/qca/forward/resources/channel_pairings.py +++ b/src/qca/forward/resources/channel_pairings.py @@ -1,12 +1,13 @@ from __future__ import annotations -from typing import Any, Dict, Union +from typing import Any, Dict, Literal, Union import httpx from qca.common._resource import AsyncAPIResource, SyncAPIResource from qca.common._types import NOT_GIVEN, NotGiven from qca.common._utils import make_request_options, path_template +from qca.common.pagination import AsyncPaginator, SyncPage from qca.forward.types.channel_pairing import ChannelPairing from qca.forward.types.deleted_channel_pairing import DeletedChannelPairing @@ -14,6 +15,92 @@ class ChannelPairings(SyncAPIResource): + def retrieve( + self, + pairing_id: str, + *, + 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, + ) -> ChannelPairing: + """GET /channel_pairings/{pairing_id}.""" + _path = path_template("/channel_pairings/{pairing_id}", pairing_id=pairing_id) + options = make_request_options( + body={}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return self._client.request("GET", _path, cast_to=ChannelPairing, options=options) + + def update( + self, + pairing_id: str, + *, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + template_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, + ) -> ChannelPairing: + """POST /channel_pairings/{pairing_id}. Provide at least one field; omitted fields retain their value.""" + _path = path_template("/channel_pairings/{pairing_id}", pairing_id=pairing_id) + options = make_request_options( + body={"identity_id": identity_id, "template_id": template_id}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return self._client.request("POST", _path, cast_to=ChannelPairing, options=options) + + def list( + self, + *, + channel_id: Union[str, NotGiven] = NOT_GIVEN, + target_external_id: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + template_id: Union[str, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_id: Union[str, NotGiven] = NOT_GIVEN, + status: Union[Literal["pairing", "active"], NotGiven] = NOT_GIVEN, + target_type: Union[Literal["user", "group"], NotGiven] = NOT_GIVEN, + limit: Union[int, 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[ChannelPairing]: + """GET /channel_pairings.""" + _path = path_template("/channel_pairings") + options = make_request_options( + body={}, + query={ + "channel_id": channel_id, + "target_external_id": target_external_id, + "identity_id": identity_id, + "template_id": template_id, + "after_id": after_id, + "before_id": before_id, + "status": status, + "target_type": target_type, + "limit": limit, + }, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return self._client.request("GET", _path, cast_to=ChannelPairing, options=options, page_style="Page") + def create( self, *, @@ -72,6 +159,94 @@ def delete( class AsyncChannelPairings(AsyncAPIResource): + async def retrieve( + self, + pairing_id: str, + *, + 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, + ) -> ChannelPairing: + """GET /channel_pairings/{pairing_id}.""" + _path = path_template("/channel_pairings/{pairing_id}", pairing_id=pairing_id) + options = make_request_options( + body={}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return await self._client.request("GET", _path, cast_to=ChannelPairing, options=options) + + async def update( + self, + pairing_id: str, + *, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + template_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, + ) -> ChannelPairing: + """POST /channel_pairings/{pairing_id}. Provide at least one field; omitted fields retain their value.""" + _path = path_template("/channel_pairings/{pairing_id}", pairing_id=pairing_id) + options = make_request_options( + body={"identity_id": identity_id, "template_id": template_id}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return await self._client.request("POST", _path, cast_to=ChannelPairing, options=options) + + def list( + self, + *, + channel_id: Union[str, NotGiven] = NOT_GIVEN, + target_external_id: Union[str, NotGiven] = NOT_GIVEN, + identity_id: Union[str, NotGiven] = NOT_GIVEN, + template_id: Union[str, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_id: Union[str, NotGiven] = NOT_GIVEN, + status: Union[Literal["pairing", "active"], NotGiven] = NOT_GIVEN, + target_type: Union[Literal["user", "group"], NotGiven] = NOT_GIVEN, + limit: Union[int, 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[ChannelPairing]: + """GET /channel_pairings.""" + _path = path_template("/channel_pairings") + options = make_request_options( + body={}, + query={ + "channel_id": channel_id, + "target_external_id": target_external_id, + "identity_id": identity_id, + "template_id": template_id, + "after_id": after_id, + "before_id": before_id, + "status": status, + "target_type": target_type, + "limit": limit, + }, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return AsyncPaginator( + lambda: self._client.request("GET", _path, cast_to=ChannelPairing, options=options, page_style="Page") + ) + async def create( self, *, diff --git a/src/qca/forward/resources/dreams.py b/src/qca/forward/resources/dreams.py new file mode 100644 index 0000000..e889162 --- /dev/null +++ b/src/qca/forward/resources/dreams.py @@ -0,0 +1,259 @@ +from __future__ import annotations + +from typing import Any, Dict, List, Union + +import httpx + +from qca.common._resource import AsyncAPIResource, SyncAPIResource +from qca.common._types import NOT_GIVEN, NotGiven +from qca.common._utils import make_request_options, path_template +from qca.common.pagination import AsyncPaginator, SyncPage +from qca.forward.types.dream import Dream, DreamInputParam, DreamModelConfigParam + + +class Dreams(SyncAPIResource): + """Forward memory consolidation; requires PAT or an admin SAT.""" + + def create( + self, + *, + inputs: List[DreamInputParam], + model: Union[str, DreamModelConfigParam, None, NotGiven] = NOT_GIVEN, + instructions: 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, + ) -> Dream: + """POST /dreams.""" + _path = path_template("/dreams") + options = make_request_options( + body={"inputs": inputs, "model": model, "instructions": instructions}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return self._client.request("POST", _path, cast_to=Dream, options=options) + + def list( + self, + *, + limit: Union[int, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_id: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, 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[Dream]: + """GET /dreams.""" + _path = path_template("/dreams") + options = make_request_options( + body={}, + query={"limit": limit, "after_id": after_id, "before_id": before_id, "include_archived": include_archived}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return self._client.request("GET", _path, cast_to=Dream, options=options, page_style="Page") + + def retrieve( + self, + dream_id: str, + *, + 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, + ) -> Dream: + """GET /dreams/{dream_id}.""" + _path = path_template("/dreams/{dream_id}", dream_id=dream_id) + options = make_request_options( + body={}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return self._client.request("GET", _path, cast_to=Dream, options=options) + + def cancel( + self, + dream_id: str, + *, + 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, + ) -> Dream: + """POST /dreams/{dream_id}/cancel.""" + _path = path_template("/dreams/{dream_id}/cancel", dream_id=dream_id) + options = make_request_options( + body={}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not options["body"]: + options["body"] = None + return self._client.request("POST", _path, cast_to=Dream, options=options) + + def archive( + self, + dream_id: str, + *, + 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, + ) -> Dream: + """POST /dreams/{dream_id}/archive.""" + _path = path_template("/dreams/{dream_id}/archive", dream_id=dream_id) + options = make_request_options( + body={}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not options["body"]: + options["body"] = None + return self._client.request("POST", _path, cast_to=Dream, options=options) + + +class AsyncDreams(AsyncAPIResource): + """Forward memory consolidation; requires PAT or an admin SAT.""" + + async def create( + self, + *, + inputs: List[DreamInputParam], + model: Union[str, DreamModelConfigParam, None, NotGiven] = NOT_GIVEN, + instructions: 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, + ) -> Dream: + """POST /dreams.""" + _path = path_template("/dreams") + options = make_request_options( + body={"inputs": inputs, "model": model, "instructions": instructions}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return await self._client.request("POST", _path, cast_to=Dream, options=options) + + def list( + self, + *, + limit: Union[int, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_id: Union[str, NotGiven] = NOT_GIVEN, + include_archived: Union[bool, 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[Dream]: + """GET /dreams.""" + _path = path_template("/dreams") + options = make_request_options( + body={}, + query={"limit": limit, "after_id": after_id, "before_id": before_id, "include_archived": include_archived}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return AsyncPaginator( + lambda: self._client.request("GET", _path, cast_to=Dream, options=options, page_style="Page") + ) + + async def retrieve( + self, + dream_id: str, + *, + 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, + ) -> Dream: + """GET /dreams/{dream_id}.""" + _path = path_template("/dreams/{dream_id}", dream_id=dream_id) + options = make_request_options( + body={}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return await self._client.request("GET", _path, cast_to=Dream, options=options) + + async def cancel( + self, + dream_id: str, + *, + 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, + ) -> Dream: + """POST /dreams/{dream_id}/cancel.""" + _path = path_template("/dreams/{dream_id}/cancel", dream_id=dream_id) + options = make_request_options( + body={}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not options["body"]: + options["body"] = None + return await self._client.request("POST", _path, cast_to=Dream, options=options) + + async def archive( + self, + dream_id: str, + *, + 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, + ) -> Dream: + """POST /dreams/{dream_id}/archive.""" + _path = path_template("/dreams/{dream_id}/archive", dream_id=dream_id) + options = make_request_options( + body={}, + query={}, + headers={}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not options["body"]: + options["body"] = None + return await self._client.request("POST", _path, cast_to=Dream, options=options) diff --git a/src/qca/forward/resources/identities/identities.py b/src/qca/forward/resources/identities/identities.py index ead096f..5ad2233 100644 --- a/src/qca/forward/resources/identities/identities.py +++ b/src/qca/forward/resources/identities/identities.py @@ -78,6 +78,7 @@ def create( enabled: Union[bool, None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + auto_memory_store: Union[bool, None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -86,7 +87,13 @@ def create( """POST /identities.""" _path = path_template("/identities") options = make_request_options( - body={"external_id": external_id, "name": name, "enabled": enabled, "metadata": metadata}, + body={ + "auto_memory_store": auto_memory_store, + "external_id": external_id, + "name": name, + "enabled": enabled, + "metadata": metadata, + }, query={}, headers={"Idempotency-Key": idempotency_key}, extra_headers=extra_headers, @@ -169,6 +176,7 @@ def update( enabled: Union[bool, None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + auto_memory_store: Union[bool, None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -177,7 +185,13 @@ def update( """POST /identities/{identity_id}.""" _path = path_template("/identities/{identity_id}", identity_id=identity_id) options = make_request_options( - body={"external_id": external_id, "name": name, "enabled": enabled, "metadata": metadata}, + body={ + "auto_memory_store": auto_memory_store, + "external_id": external_id, + "name": name, + "enabled": enabled, + "metadata": metadata, + }, query={}, headers={"Idempotency-Key": idempotency_key}, extra_headers=extra_headers, @@ -358,6 +372,7 @@ async def create( enabled: Union[bool, None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + auto_memory_store: Union[bool, None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -366,7 +381,13 @@ async def create( """POST /identities.""" _path = path_template("/identities") options = make_request_options( - body={"external_id": external_id, "name": name, "enabled": enabled, "metadata": metadata}, + body={ + "auto_memory_store": auto_memory_store, + "external_id": external_id, + "name": name, + "enabled": enabled, + "metadata": metadata, + }, query={}, headers={"Idempotency-Key": idempotency_key}, extra_headers=extra_headers, @@ -449,6 +470,7 @@ async def update( enabled: Union[bool, None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + auto_memory_store: Union[bool, None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -457,7 +479,13 @@ async def update( """POST /identities/{identity_id}.""" _path = path_template("/identities/{identity_id}", identity_id=identity_id) options = make_request_options( - body={"external_id": external_id, "name": name, "enabled": enabled, "metadata": metadata}, + body={ + "auto_memory_store": auto_memory_store, + "external_id": external_id, + "name": name, + "enabled": enabled, + "metadata": metadata, + }, query={}, headers={"Idempotency-Key": idempotency_key}, extra_headers=extra_headers, diff --git a/src/qca/forward/resources/identities/memory_stores.py b/src/qca/forward/resources/identities/memory_stores.py index 23a1868..c8e834f 100644 --- a/src/qca/forward/resources/identities/memory_stores.py +++ b/src/qca/forward/resources/identities/memory_stores.py @@ -1,14 +1,14 @@ from __future__ import annotations -from typing import Any, Dict +from typing import Any, Dict, Literal, Union import httpx from qca.common._resource import AsyncAPIResource, SyncAPIResource from qca.common._types import NOT_GIVEN, NotGiven from qca.common._utils import make_request_options, path_template +from qca.common.pagination import AsyncPaginator, SyncPage from qca.forward.types.deleted_memory_store_mount import DeletedMemoryStoreMount -from qca.forward.types.identity_memory_store_list_response import IdentityMemoryStoreListResponse from qca.forward.types.memory_store_mount import MemoryStoreMount __all__ = ["MemoryStores", "AsyncMemoryStores"] @@ -20,11 +20,14 @@ def list( template_id: str, *, identity_id: str, + limit: Union[int, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_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, - ) -> IdentityMemoryStoreListResponse: + ) -> SyncPage[MemoryStoreMount]: """GET /identities/{identity_id}/templates/{template_id}/memory_stores.""" _path = path_template( "/identities/{identity_id}/templates/{template_id}/memory_stores", @@ -33,14 +36,14 @@ def list( ) options = make_request_options( body={}, - query={}, + query={"limit": limit, "after_id": after_id, "before_id": before_id}, headers={}, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, ) - return self._client.request("GET", _path, cast_to=IdentityMemoryStoreListResponse, options=options) + return self._client.request("GET", _path, cast_to=MemoryStoreMount, options=options, page_style="Page") def mount( self, @@ -48,6 +51,7 @@ def mount( *, identity_id: str, memory_store_id: str, + access: Union[Literal["read_only", "read_write"], NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -60,7 +64,7 @@ def mount( identity_id=identity_id, ) options = make_request_options( - body={"memory_store_id": memory_store_id}, + body={"memory_store_id": memory_store_id, "access": access}, query={}, headers={}, extra_headers=extra_headers, @@ -101,16 +105,19 @@ def detach( class AsyncMemoryStores(AsyncAPIResource): - async def list( + def list( self, template_id: str, *, identity_id: str, + limit: Union[int, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_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, - ) -> IdentityMemoryStoreListResponse: + ) -> AsyncPaginator[MemoryStoreMount]: """GET /identities/{identity_id}/templates/{template_id}/memory_stores.""" _path = path_template( "/identities/{identity_id}/templates/{template_id}/memory_stores", @@ -119,14 +126,16 @@ async def list( ) options = make_request_options( body={}, - query={}, + query={"limit": limit, "after_id": after_id, "before_id": before_id}, headers={}, extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, ) - return await self._client.request("GET", _path, cast_to=IdentityMemoryStoreListResponse, options=options) + return AsyncPaginator( + lambda: self._client.request("GET", _path, cast_to=MemoryStoreMount, options=options, page_style="Page") + ) async def mount( self, @@ -134,6 +143,7 @@ async def mount( *, identity_id: str, memory_store_id: str, + access: Union[Literal["read_only", "read_write"], NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -146,7 +156,7 @@ async def mount( identity_id=identity_id, ) options = make_request_options( - body={"memory_store_id": memory_store_id}, + body={"memory_store_id": memory_store_id, "access": access}, query={}, headers={}, extra_headers=extra_headers, diff --git a/src/qca/forward/resources/schedule_runs.py b/src/qca/forward/resources/schedule_runs.py index c3cc1df..a4b85ab 100644 --- a/src/qca/forward/resources/schedule_runs.py +++ b/src/qca/forward/resources/schedule_runs.py @@ -61,6 +61,7 @@ def retrieve( run_id: str, *, identity_id: Union[str, None, NotGiven] = NOT_GIVEN, + include_initial_events: Union[bool, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -70,7 +71,7 @@ def retrieve( _path = path_template("/schedule_runs/{run_id}", run_id=run_id) options = make_request_options( body={}, - query={"identity_id": identity_id}, + query={"include_initial_events": include_initial_events, "identity_id": identity_id}, headers={}, extra_headers=extra_headers, extra_query=extra_query, @@ -130,6 +131,7 @@ async def retrieve( run_id: str, *, identity_id: Union[str, None, NotGiven] = NOT_GIVEN, + include_initial_events: Union[bool, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -139,7 +141,7 @@ async def retrieve( _path = path_template("/schedule_runs/{run_id}", run_id=run_id) options = make_request_options( body={}, - query={"identity_id": identity_id}, + query={"include_initial_events": include_initial_events, "identity_id": identity_id}, headers={}, extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/qca/forward/resources/schedules.py b/src/qca/forward/resources/schedules.py index 9d92088..e846674 100644 --- a/src/qca/forward/resources/schedules.py +++ b/src/qca/forward/resources/schedules.py @@ -10,6 +10,7 @@ from qca.common.pagination import AsyncPaginator, SyncPage from qca.forward.types.schedule import Schedule from qca.forward.types.schedule_archive_many_response import ScheduleArchiveManyResponse +from qca.forward.types.schedule_file_resource import ScheduleFileResourceParam from qca.forward.types.schedule_run import ScheduleRun __all__ = ["Schedules", "AsyncSchedules"] @@ -70,6 +71,7 @@ def create( sinks: Union[List[Dict[str, Any]], None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + resources: Union[List[ScheduleFileResourceParam], None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -79,6 +81,7 @@ def create( _path = path_template("/schedules") options = make_request_options( body={ + "resources": resources, "identity_id": identity_id, "template_id": template_id, "name": name, @@ -158,6 +161,7 @@ def update( sinks: Union[List[Dict[str, Any]], None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + resources: Union[List[ScheduleFileResourceParam], None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -167,6 +171,7 @@ def update( _path = path_template("/schedules/{schedule_id}", schedule_id=schedule_id) options = make_request_options( body={ + "resources": resources, "name": name, "description": description, "template_id": template_id, @@ -336,6 +341,7 @@ async def create( sinks: Union[List[Dict[str, Any]], None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + resources: Union[List[ScheduleFileResourceParam], None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -345,6 +351,7 @@ async def create( _path = path_template("/schedules") options = make_request_options( body={ + "resources": resources, "identity_id": identity_id, "template_id": template_id, "name": name, @@ -424,6 +431,7 @@ async def update( sinks: Union[List[Dict[str, Any]], None, NotGiven] = NOT_GIVEN, metadata: Union[Dict[str, Any], None, NotGiven] = NOT_GIVEN, idempotency_key: Union[str, None, NotGiven] = NOT_GIVEN, + resources: Union[List[ScheduleFileResourceParam], None, NotGiven] = NOT_GIVEN, extra_headers: Dict[str, str] | None = None, extra_query: Dict[str, Any] | None = None, extra_body: Dict[str, Any] | None = None, @@ -433,6 +441,7 @@ async def update( _path = path_template("/schedules/{schedule_id}", schedule_id=schedule_id) options = make_request_options( body={ + "resources": resources, "name": name, "description": description, "template_id": template_id, diff --git a/src/qca/forward/types/__init__.py b/src/qca/forward/types/__init__.py index 4903c39..67e82bb 100644 --- a/src/qca/forward/types/__init__.py +++ b/src/qca/forward/types/__init__.py @@ -26,6 +26,8 @@ from .channel_list_params import ChannelListParams as ChannelListParams from .channel_pairing import ChannelPairing as ChannelPairing from .channel_pairing_create_params import ChannelPairingCreateParams as ChannelPairingCreateParams +from .channel_pairing_list_params import ChannelPairingListParams as ChannelPairingListParams +from .channel_pairing_update_params import ChannelPairingUpdateParams as ChannelPairingUpdateParams from .channel_qr_session import ChannelQRSession as ChannelQRSession from .channel_qr_session_create_params import ChannelQRSessionCreateParams as ChannelQRSessionCreateParams from .channel_update_params import ChannelUpdateParams as ChannelUpdateParams @@ -51,6 +53,19 @@ from .deleted_memory_store import DeletedMemoryStore as DeletedMemoryStore from .deleted_memory_store_mount import DeletedMemoryStoreMount as DeletedMemoryStoreMount from .deleted_skill_version import DeletedSkillVersion as DeletedSkillVersion +from .dream import Dream as Dream +from .dream import DreamCreateParams as DreamCreateParams +from .dream import DreamError as DreamError +from .dream import DreamIdentityTemplateInputParam as DreamIdentityTemplateInputParam +from .dream import DreamInput as DreamInput +from .dream import DreamInputParam as DreamInputParam +from .dream import DreamListParams as DreamListParams +from .dream import DreamMemoryStoreInputParam as DreamMemoryStoreInputParam +from .dream import DreamModelConfig as DreamModelConfig +from .dream import DreamModelConfigParam as DreamModelConfigParam +from .dream import DreamOutput as DreamOutput +from .dream import DreamSessionsInputParam as DreamSessionsInputParam +from .dream import DreamUsage as DreamUsage from .drive import DriveClearParams as DriveClearParams from .drive import DriveClearResponse as DriveClearResponse from .drive import DriveCreateDownloadURLParams as DriveCreateDownloadURLParams @@ -100,6 +115,7 @@ from .identity_create_params import IdentityCreateParams as IdentityCreateParams from .identity_list_params import IdentityListParams as IdentityListParams from .identity_list_templates_response import IdentityListTemplatesResponse as IdentityListTemplatesResponse +from .identity_memory_store_list_params import IdentityMemoryStoreListParams as IdentityMemoryStoreListParams from .identity_memory_store_list_response import IdentityMemoryStoreListResponse as IdentityMemoryStoreListResponse from .identity_memory_store_mount_params import IdentityMemoryStoreMountParams as IdentityMemoryStoreMountParams from .identity_stats import IdentityStats as IdentityStats @@ -107,6 +123,8 @@ from .identity_update_params import IdentityUpdateParams as IdentityUpdateParams from .identity_usage import IdentityUsage as IdentityUsage from .image_source_param import ImageSourceParam as ImageSourceParam +from .managed_capability_override import ManagedCapabilityOverride as ManagedCapabilityOverride +from .managed_capability_override import ManagedCapabilityOverrideParam as ManagedCapabilityOverrideParam from .managed_tool_config import ManagedToolConfig as ManagedToolConfig from .managed_tool_config_param import ManagedToolConfigParam as ManagedToolConfigParam from .mcp_server import MCPServer as MCPServer @@ -135,6 +153,12 @@ from .multiagent_config_param import MultiagentConfigParam as MultiagentConfigParam from .multiagent_entry import MultiagentEntry as MultiagentEntry from .multiagent_entry_param import MultiagentEntryParam as MultiagentEntryParam +from .outcome_rubric import FileOutcomeRubric as FileOutcomeRubric +from .outcome_rubric import FileOutcomeRubricParam as FileOutcomeRubricParam +from .outcome_rubric import OutcomeRubric as OutcomeRubric +from .outcome_rubric import OutcomeRubricParam as OutcomeRubricParam +from .outcome_rubric import TextOutcomeRubric as TextOutcomeRubric +from .outcome_rubric import TextOutcomeRubricParam as TextOutcomeRubricParam from .permission_policy import PermissionPolicy as PermissionPolicy from .permission_policy_param import PermissionPolicyParam as PermissionPolicyParam from .realtime import AsyncRealtimeHistoryPage as AsyncRealtimeHistoryPage @@ -172,6 +196,8 @@ from .schedule_archive_params import ScheduleArchiveParams as ScheduleArchiveParams from .schedule_create_params import ScheduleCreateParams as ScheduleCreateParams from .schedule_execution import ScheduleExecution as ScheduleExecution +from .schedule_file_resource import ScheduleFileResource as ScheduleFileResource +from .schedule_file_resource import ScheduleFileResourceParam as ScheduleFileResourceParam from .schedule_initial_events_item import ScheduleInitialEventsItem as ScheduleInitialEventsItem from .schedule_list_params import ScheduleListParams as ScheduleListParams from .schedule_pause_params import SchedulePauseParams as SchedulePauseParams diff --git a/src/qca/forward/types/channel_pairing_list_params.py b/src/qca/forward/types/channel_pairing_list_params.py new file mode 100644 index 0000000..566bbf6 --- /dev/null +++ b/src/qca/forward/types/channel_pairing_list_params.py @@ -0,0 +1,15 @@ +from typing import Literal + +from typing_extensions import TypedDict + + +class ChannelPairingListParams(TypedDict, total=False): + channel_id: str + status: Literal["pairing", "active"] + target_type: Literal["user", "group"] + target_external_id: str + identity_id: str + template_id: str + limit: int + after_id: str + before_id: str diff --git a/src/qca/forward/types/channel_pairing_update_params.py b/src/qca/forward/types/channel_pairing_update_params.py new file mode 100644 index 0000000..c2e2d56 --- /dev/null +++ b/src/qca/forward/types/channel_pairing_update_params.py @@ -0,0 +1,8 @@ +from typing_extensions import TypedDict + + +class ChannelPairingUpdateParams(TypedDict, total=False): + """Provide at least one nonempty field; omitted fields retain their value.""" + + identity_id: str + template_id: str diff --git a/src/qca/forward/types/dream.py b/src/qca/forward/types/dream.py new file mode 100644 index 0000000..b0a6716 --- /dev/null +++ b/src/qca/forward/types/dream.py @@ -0,0 +1,92 @@ +from datetime import datetime +from typing import List, Literal, Optional, Union + +from typing_extensions import Required, TypedDict + +from qca.common._models import BaseModel + + +class DreamMemoryStoreInputParam(TypedDict): + type: Literal["memory_store"] + memory_store_id: str + + +class DreamIdentityTemplateInputParam(TypedDict): + type: Literal["identity_template"] + identity_id: str + template_id: str + + +class DreamSessionsInputParam(TypedDict): + type: Literal["sessions"] + session_ids: List[str] + + +DreamInputParam = Union[DreamMemoryStoreInputParam, DreamIdentityTemplateInputParam, DreamSessionsInputParam] + + +class DreamModelConfigParam(TypedDict, total=False): + id: Required[str] + effort: Literal["none", "low", "medium", "high", "xhigh", "max"] + context_window: int + + +class DreamCreateParams(TypedDict, total=False): + inputs: Required[List[DreamInputParam]] + model: Union[str, DreamModelConfigParam, None] + instructions: str + + +class DreamListParams(TypedDict, total=False): + limit: int + after_id: str + before_id: str + include_archived: bool + + +class DreamInput(BaseModel): + type: str + memory_store_id: Optional[str] = None + identity_id: Optional[str] = None + template_id: Optional[str] = None + session_ids: Optional[List[str]] = None + + +class DreamModelConfig(BaseModel): + id: str + effort: Optional[str] = None + context_window: Optional[int] = None + + +class DreamOutput(BaseModel): + type: str + memory_store_id: str + files_touched: Optional[List[str]] = None + + +class DreamUsage(BaseModel): + input_tokens: int + output_tokens: int + cache_creation_input_tokens: int + cache_read_input_tokens: int + + +class DreamError(BaseModel): + type: str + code: Optional[str] = None + message: str + + +class Dream(BaseModel): + id: str + type: Literal["dream"] + status: str + inputs: List[DreamInput] + outputs: List[DreamOutput] + model: DreamModelConfig + instructions: str + usage: DreamUsage + error: Optional[DreamError] = None + created_at: datetime + ended_at: Optional[datetime] = None + archived_at: Optional[datetime] = None diff --git a/src/qca/forward/types/identity.py b/src/qca/forward/types/identity.py index f787d71..6d440e5 100644 --- a/src/qca/forward/types/identity.py +++ b/src/qca/forward/types/identity.py @@ -9,6 +9,7 @@ class Identity(BaseModel): + auto_memory_store: Optional[bool] = None id: Optional[str] = None external_id: Optional[str] = None name: Optional[str] = None diff --git a/src/qca/forward/types/identity_config_spec.py b/src/qca/forward/types/identity_config_spec.py index 675634e..4b1a639 100644 --- a/src/qca/forward/types/identity_config_spec.py +++ b/src/qca/forward/types/identity_config_spec.py @@ -1,26 +1,29 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from qca.common._models import BaseModel if TYPE_CHECKING: from .environment_variable_override import EnvironmentVariableOverride from .git_hub_repository import GitHubRepository + from .managed_capability_override import ManagedCapabilityOverride from .mcp_server_override import MCPServerOverride from .model_config import ModelConfig from .resource_binding import ResourceBinding from .skill_override import SkillOverride from .system_override import SystemOverride + from .tool import Tool from .tool_override import ToolOverride __all__ = ["IdentityConfigSpec"] class IdentityConfigSpec(BaseModel): + managed_tool_config: Optional[Dict[str, Optional[ManagedCapabilityOverride]]] = None system: Optional[SystemOverride] = None model: Optional[Union[str, ModelConfig]] = None - tools: Optional[Dict[str, ToolOverride]] = None + tools: Optional[Union[Dict[str, Optional[ToolOverride]], List[Tool]]] = None mcp_servers: Optional[Dict[str, MCPServerOverride]] = None skills: Optional[Dict[str, SkillOverride]] = None toolsets: Optional[Dict[str, Any]] = None diff --git a/src/qca/forward/types/identity_config_spec_param.py b/src/qca/forward/types/identity_config_spec_param.py index 54c2758..feb4583 100644 --- a/src/qca/forward/types/identity_config_spec_param.py +++ b/src/qca/forward/types/identity_config_spec_param.py @@ -1,31 +1,34 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union from typing_extensions import TypedDict if TYPE_CHECKING: from .environment_variable_override_param import EnvironmentVariableOverrideParam from .git_hub_repository_param import GitHubRepositoryParam + from .managed_capability_override import ManagedCapabilityOverrideParam from .mcp_server_override_param import MCPServerOverrideParam from .model_config_param import ModelConfigParam from .resource_binding_param import ResourceBindingParam from .skill_override_param import SkillOverrideParam from .system_override_param import SystemOverrideParam from .tool_override_param import ToolOverrideParam + from .tool_param import ToolParam __all__ = ["IdentityConfigSpecParam"] class IdentityConfigSpecParam(TypedDict, total=False): - system: SystemOverrideParam - model: Union[str, ModelConfigParam] - tools: Dict[str, ToolOverrideParam] - mcp_servers: Dict[str, MCPServerOverrideParam] - skills: Dict[str, SkillOverrideParam] - toolsets: Dict[str, Any] - agent_metadata: Dict[str, Any] - vaults: Dict[str, ResourceBindingParam] - files: Dict[str, ResourceBindingParam] - github_repositories: Dict[str, GitHubRepositoryParam] - environment_variables: Dict[str, EnvironmentVariableOverrideParam] + managed_tool_config: Optional[Dict[str, Optional[ManagedCapabilityOverrideParam]]] + system: Optional[SystemOverrideParam] + model: Optional[Union[str, ModelConfigParam]] + tools: Optional[Union[Dict[str, Optional[ToolOverrideParam]], List[ToolParam]]] + mcp_servers: Optional[Dict[str, MCPServerOverrideParam]] + skills: Optional[Dict[str, SkillOverrideParam]] + toolsets: Optional[Dict[str, Any]] + agent_metadata: Optional[Dict[str, Any]] + vaults: Optional[Dict[str, ResourceBindingParam]] + files: Optional[Dict[str, ResourceBindingParam]] + github_repositories: Optional[Dict[str, GitHubRepositoryParam]] + environment_variables: Optional[Dict[str, EnvironmentVariableOverrideParam]] diff --git a/src/qca/forward/types/identity_create_params.py b/src/qca/forward/types/identity_create_params.py index 3df82d4..4eac90a 100644 --- a/src/qca/forward/types/identity_create_params.py +++ b/src/qca/forward/types/identity_create_params.py @@ -8,6 +8,7 @@ class IdentityCreateParams(TypedDict, total=False): + auto_memory_store: Optional[bool] external_id: Required[str] name: Optional[str] enabled: Optional[bool] diff --git a/src/qca/forward/types/identity_memory_store_list_params.py b/src/qca/forward/types/identity_memory_store_list_params.py new file mode 100644 index 0000000..23d0a62 --- /dev/null +++ b/src/qca/forward/types/identity_memory_store_list_params.py @@ -0,0 +1,8 @@ +from typing_extensions import Required, TypedDict + + +class IdentityMemoryStoreListParams(TypedDict, total=False): + identity_id: Required[str] + limit: int + after_id: str + before_id: str diff --git a/src/qca/forward/types/identity_memory_store_list_response.py b/src/qca/forward/types/identity_memory_store_list_response.py index 0bb606f..be6dc5e 100644 --- a/src/qca/forward/types/identity_memory_store_list_response.py +++ b/src/qca/forward/types/identity_memory_store_list_response.py @@ -11,5 +11,7 @@ class IdentityMemoryStoreListResponse(BaseModel): + first_id: Optional[str] = None + last_id: Optional[str] = None data: Optional[List[MemoryStoreMount]] = None has_more: Optional[bool] = None diff --git a/src/qca/forward/types/identity_memory_store_mount_params.py b/src/qca/forward/types/identity_memory_store_mount_params.py index f049b44..0e51cc8 100644 --- a/src/qca/forward/types/identity_memory_store_mount_params.py +++ b/src/qca/forward/types/identity_memory_store_mount_params.py @@ -1,9 +1,12 @@ from __future__ import annotations +from typing import Literal + from typing_extensions import Required, TypedDict __all__ = ["IdentityMemoryStoreMountParams"] class IdentityMemoryStoreMountParams(TypedDict, total=False): + access: Literal["read_only", "read_write"] memory_store_id: Required[str] diff --git a/src/qca/forward/types/identity_update_params.py b/src/qca/forward/types/identity_update_params.py index d0468e0..a57f585 100644 --- a/src/qca/forward/types/identity_update_params.py +++ b/src/qca/forward/types/identity_update_params.py @@ -8,6 +8,7 @@ class IdentityUpdateParams(TypedDict, total=False): + auto_memory_store: Optional[bool] external_id: Optional[str] name: Optional[str] enabled: Optional[bool] diff --git a/src/qca/forward/types/managed_capability_override.py b/src/qca/forward/types/managed_capability_override.py new file mode 100644 index 0000000..4c48804 --- /dev/null +++ b/src/qca/forward/types/managed_capability_override.py @@ -0,0 +1,11 @@ +from typing_extensions import TypedDict + +from qca.common._models import BaseModel + + +class ManagedCapabilityOverrideParam(TypedDict): + enabled: bool + + +class ManagedCapabilityOverride(BaseModel): + enabled: bool diff --git a/src/qca/forward/types/multiagent_config.py b/src/qca/forward/types/multiagent_config.py index 24bdb70..da27cb4 100644 --- a/src/qca/forward/types/multiagent_config.py +++ b/src/qca/forward/types/multiagent_config.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, List, Optional, Union from qca.common._models import BaseModel @@ -12,4 +12,4 @@ class MultiagentConfig(BaseModel): type: Optional[str] = None - agents: Optional[List[MultiagentEntry]] = None + agents: Optional[List[Union[str, MultiagentEntry]]] = None diff --git a/src/qca/forward/types/multiagent_config_param.py b/src/qca/forward/types/multiagent_config_param.py index 2183ee3..733409b 100644 --- a/src/qca/forward/types/multiagent_config_param.py +++ b/src/qca/forward/types/multiagent_config_param.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Union from typing_extensions import Required, TypedDict @@ -12,4 +12,4 @@ class MultiagentConfigParam(TypedDict, total=False): type: Required[str] - agents: Required[List[MultiagentEntryParam]] + agents: Required[List[Union[str, MultiagentEntryParam]]] diff --git a/src/qca/forward/types/multiagent_entry.py b/src/qca/forward/types/multiagent_entry.py index 741e101..920f69a 100644 --- a/src/qca/forward/types/multiagent_entry.py +++ b/src/qca/forward/types/multiagent_entry.py @@ -9,5 +9,6 @@ class MultiagentEntry(BaseModel): type: Optional[str] = None + model: Optional[str] = None template_id: Optional[str] = None name: Optional[str] = None diff --git a/src/qca/forward/types/multiagent_entry_param.py b/src/qca/forward/types/multiagent_entry_param.py index e9a6bae..6fedf64 100644 --- a/src/qca/forward/types/multiagent_entry_param.py +++ b/src/qca/forward/types/multiagent_entry_param.py @@ -9,5 +9,6 @@ class MultiagentEntryParam(TypedDict, total=False): type: Required[str] + model: str template_id: Optional[str] name: Optional[str] diff --git a/src/qca/forward/types/outcome_rubric.py b/src/qca/forward/types/outcome_rubric.py new file mode 100644 index 0000000..c71d476 --- /dev/null +++ b/src/qca/forward/types/outcome_rubric.py @@ -0,0 +1,31 @@ +from typing import Literal, Union + +from typing_extensions import TypedDict + +from qca.common._models import BaseModel + + +class TextOutcomeRubricParam(TypedDict): + type: Literal["text"] + content: str + + +class FileOutcomeRubricParam(TypedDict): + type: Literal["file"] + file_id: str + + +OutcomeRubricParam = Union[TextOutcomeRubricParam, FileOutcomeRubricParam] + + +class TextOutcomeRubric(BaseModel): + type: Literal["text"] + content: str + + +class FileOutcomeRubric(BaseModel): + type: Literal["file"] + file_id: str + + +OutcomeRubric = Union[TextOutcomeRubric, FileOutcomeRubric] diff --git a/src/qca/forward/types/schedule.py b/src/qca/forward/types/schedule.py index 655c850..9dc7e2b 100644 --- a/src/qca/forward/types/schedule.py +++ b/src/qca/forward/types/schedule.py @@ -7,6 +7,7 @@ if TYPE_CHECKING: from .schedule_execution import ScheduleExecution + from .schedule_file_resource import ScheduleFileResource from .schedule_initial_events_item import ScheduleInitialEventsItem from .schedule_paused_reason import SchedulePausedReason from .schedule_sinks_item import ScheduleSinksItem @@ -16,6 +17,7 @@ class Schedule(BaseModel): + resources: Optional[List[ScheduleFileResource]] = None source: Optional[str] = None source_session_id: Optional[str] = None initial_events_version: Optional[int] = None diff --git a/src/qca/forward/types/schedule_create_params.py b/src/qca/forward/types/schedule_create_params.py index 36d0cc9..c96e331 100644 --- a/src/qca/forward/types/schedule_create_params.py +++ b/src/qca/forward/types/schedule_create_params.py @@ -4,10 +4,13 @@ from typing_extensions import Required, TypedDict +from .schedule_file_resource import ScheduleFileResourceParam + __all__ = ["ScheduleCreateParams"] class ScheduleCreateParams(TypedDict, total=False): + resources: Optional[List[ScheduleFileResourceParam]] identity_id: Required[str] template_id: Required[str] name: Required[str] diff --git a/src/qca/forward/types/schedule_file_resource.py b/src/qca/forward/types/schedule_file_resource.py new file mode 100644 index 0000000..b16782f --- /dev/null +++ b/src/qca/forward/types/schedule_file_resource.py @@ -0,0 +1,17 @@ +from typing import Literal, Optional + +from typing_extensions import Required, TypedDict + +from qca.common._models import BaseModel + + +class ScheduleFileResourceParam(TypedDict, total=False): + type: Required[Literal["file"]] + file_id: Required[str] + mount_path: str + + +class ScheduleFileResource(BaseModel): + type: Literal["file"] + file_id: str + mount_path: Optional[str] = None diff --git a/src/qca/forward/types/schedule_run.py b/src/qca/forward/types/schedule_run.py index 98b30e1..1c203c7 100644 --- a/src/qca/forward/types/schedule_run.py +++ b/src/qca/forward/types/schedule_run.py @@ -1,17 +1,19 @@ from __future__ import annotations from datetime import datetime -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional from qca.common._models import BaseModel if TYPE_CHECKING: from .schedule_run_trigger_context import ScheduleRunTriggerContext + from .session_event import SessionEvent __all__ = ["ScheduleRun"] class ScheduleRun(BaseModel): + initial_events: Optional[List[SessionEvent]] = None source: Optional[str] = None source_session_id: Optional[str] = None initial_events_version: Optional[int] = None diff --git a/src/qca/forward/types/schedule_run_retrieve_params.py b/src/qca/forward/types/schedule_run_retrieve_params.py index fedb695..84dff72 100644 --- a/src/qca/forward/types/schedule_run_retrieve_params.py +++ b/src/qca/forward/types/schedule_run_retrieve_params.py @@ -8,4 +8,5 @@ class ScheduleRunRetrieveParams(TypedDict, total=False): + include_initial_events: Optional[bool] identity_id: Optional[str] diff --git a/src/qca/forward/types/schedule_update_params.py b/src/qca/forward/types/schedule_update_params.py index e44a9ed..40e9ccb 100644 --- a/src/qca/forward/types/schedule_update_params.py +++ b/src/qca/forward/types/schedule_update_params.py @@ -4,10 +4,13 @@ from typing_extensions import TypedDict +from .schedule_file_resource import ScheduleFileResourceParam + __all__ = ["ScheduleUpdateParams"] class ScheduleUpdateParams(TypedDict, total=False): + resources: Optional[List[ScheduleFileResourceParam]] name: Optional[str] description: Optional[str] template_id: Optional[str] diff --git a/src/qca/forward/types/session_event.py b/src/qca/forward/types/session_event.py index 98083a6..512d8cb 100644 --- a/src/qca/forward/types/session_event.py +++ b/src/qca/forward/types/session_event.py @@ -5,6 +5,8 @@ from qca.common._models import BaseModel +from .outcome_rubric import OutcomeRubric + __all__ = ["SessionEvent"] @@ -27,7 +29,7 @@ class SessionEvent(BaseModel): result: Optional[str] = None deny_message: Optional[str] = None description: Optional[str] = None - rubric: Optional[str] = None + rubric: Optional[OutcomeRubric] = None outcome_id: Optional[str] = None max_iterations: Optional[int] = None message_id: Optional[str] = None diff --git a/src/qca/forward/types/session_event_param.py b/src/qca/forward/types/session_event_param.py index adf2caa..dc3919c 100644 --- a/src/qca/forward/types/session_event_param.py +++ b/src/qca/forward/types/session_event_param.py @@ -6,6 +6,7 @@ if TYPE_CHECKING: from .content_block_param import ContentBlockParam + from .outcome_rubric import OutcomeRubricParam __all__ = ["SessionEventParam"] @@ -19,6 +20,6 @@ class SessionEventParam(TypedDict, total=False): deny_message: Optional[str] is_error: Optional[bool] description: Optional[str] - rubric: Optional[str] + rubric: Optional[OutcomeRubricParam] outcome_id: Optional[str] max_iterations: Optional[int] diff --git a/src/qca/managed/_client.py b/src/qca/managed/_client.py index e0aa16c..154e981 100644 --- a/src/qca/managed/_client.py +++ b/src/qca/managed/_client.py @@ -12,12 +12,17 @@ from .resources.files import AsyncFiles, Files from .resources.memory_stores.memory_stores import AsyncMemoryStores, MemoryStores from .resources.models import AsyncModels, Models +from .resources.oauth import AsyncOAuth, OAuth from .resources.sessions.sessions import AsyncSessions, Sessions from .resources.skills.skills import AsyncSkills, Skills from .resources.vaults.vaults import AsyncVaults, Vaults class Managed(SyncAPIClient): + @cached_property + def oauth(self) -> OAuth: + return OAuth(self) + _default_base_url = "https://api.qoder.com/api/v1/cloud/" _base_url_env = "QODER_MANAGED_BASE_URL" @@ -67,6 +72,10 @@ def models(self) -> Models: class AsyncManaged(AsyncAPIClient): + @cached_property + def oauth(self) -> AsyncOAuth: + return AsyncOAuth(self) + _default_base_url = "https://api.qoder.com/api/v1/cloud/" _base_url_env = "QODER_MANAGED_BASE_URL" diff --git a/src/qca/managed/resources/__init__.py b/src/qca/managed/resources/__init__.py index f507ca6..5afa6ce 100644 --- a/src/qca/managed/resources/__init__.py +++ b/src/qca/managed/resources/__init__.py @@ -10,3 +10,5 @@ from .files import Files as Files from .models import AsyncModels as AsyncModels from .models import Models as Models +from .oauth import AsyncOAuth as AsyncOAuth +from .oauth import OAuth as OAuth diff --git a/src/qca/managed/resources/dreams.py b/src/qca/managed/resources/dreams.py index 650fe68..745e9d0 100644 --- a/src/qca/managed/resources/dreams.py +++ b/src/qca/managed/resources/dreams.py @@ -24,7 +24,7 @@ def create( self, *, inputs: List[Union[DreamMemoryStoreInputParam, DreamSessionsInputParam]], - model: Union[str, DreamModelConfigParam], + model: Union[str, DreamModelConfigParam, None, NotGiven] = NOT_GIVEN, instructions: Union[str, None, NotGiven] = NOT_GIVEN, workspace_id: Union[str, None, NotGiven] = NOT_GIVEN, output_behavior: Union[ @@ -165,7 +165,7 @@ async def create( self, *, inputs: List[Union[DreamMemoryStoreInputParam, DreamSessionsInputParam]], - model: Union[str, DreamModelConfigParam], + model: Union[str, DreamModelConfigParam, None, NotGiven] = NOT_GIVEN, instructions: Union[str, None, NotGiven] = NOT_GIVEN, workspace_id: Union[str, None, NotGiven] = NOT_GIVEN, output_behavior: Union[ diff --git a/src/qca/managed/resources/memory_stores/memory_stores.py b/src/qca/managed/resources/memory_stores/memory_stores.py index 4b89394..d6a63a3 100644 --- a/src/qca/managed/resources/memory_stores/memory_stores.py +++ b/src/qca/managed/resources/memory_stores/memory_stores.py @@ -159,6 +159,8 @@ def list( page: Union[str, None, NotGiven] = NOT_GIVEN, workspace_id: Union[str, None, NotGiven] = NOT_GIVEN, betas: Union[List[str], None, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_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, @@ -169,6 +171,8 @@ def list( options = make_request_options( body={}, query={ + "after_id": after_id, + "before_id": before_id, "name": name, "created_at[gte]": created_at_gte, "created_at[lte]": created_at_lte, @@ -376,6 +380,8 @@ def list( page: Union[str, None, NotGiven] = NOT_GIVEN, workspace_id: Union[str, None, NotGiven] = NOT_GIVEN, betas: Union[List[str], None, NotGiven] = NOT_GIVEN, + after_id: Union[str, NotGiven] = NOT_GIVEN, + before_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, @@ -386,6 +392,8 @@ def list( options = make_request_options( body={}, query={ + "after_id": after_id, + "before_id": before_id, "name": name, "created_at[gte]": created_at_gte, "created_at[lte]": created_at_lte, diff --git a/src/qca/managed/resources/oauth.py b/src/qca/managed/resources/oauth.py new file mode 100644 index 0000000..0c70404 --- /dev/null +++ b/src/qca/managed/resources/oauth.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from typing import Any, Dict, Union + +import httpx + +from qca.common._resource import AsyncAPIResource, SyncAPIResource +from qca.common._types import NOT_GIVEN, NotGiven +from qca.common._utils import make_request_options, path_template +from qca.managed.types.oauth_start_response import OAuthStartResponse + + +class OAuth(SyncAPIResource): + def start( + self, + *, + vault_id: str, + mcp_server_url: str, + client_id: Union[str, NotGiven] = NOT_GIVEN, + client_secret: Union[str, NotGiven] = NOT_GIVEN, + workspace_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, + ) -> OAuthStartResponse: + """POST /oauth/start. Returns a browser authorization URL; this call does not complete authorization.""" + _path = path_template("/oauth/start") + options = make_request_options( + body={ + "vault_id": vault_id, + "mcp_server_url": mcp_server_url, + "client_id": client_id, + "client_secret": client_secret, + }, + query={}, + headers={"qoder-workspace-id": workspace_id}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return self._client.request("POST", _path, cast_to=OAuthStartResponse, options=options) + + +class AsyncOAuth(AsyncAPIResource): + async def start( + self, + *, + vault_id: str, + mcp_server_url: str, + client_id: Union[str, NotGiven] = NOT_GIVEN, + client_secret: Union[str, NotGiven] = NOT_GIVEN, + workspace_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, + ) -> OAuthStartResponse: + """POST /oauth/start. Returns a browser authorization URL; this call does not complete authorization.""" + _path = path_template("/oauth/start") + options = make_request_options( + body={ + "vault_id": vault_id, + "mcp_server_url": mcp_server_url, + "client_id": client_id, + "client_secret": client_secret, + }, + query={}, + headers={"qoder-workspace-id": workspace_id}, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + return await self._client.request("POST", _path, cast_to=OAuthStartResponse, options=options) diff --git a/src/qca/managed/types/__init__.py b/src/qca/managed/types/__init__.py index 5c51f5d..f937356 100644 --- a/src/qca/managed/types/__init__.py +++ b/src/qca/managed/types/__init__.py @@ -52,6 +52,14 @@ from .base64_image_source_param import Base64ImageSourceParam as Base64ImageSourceParam from .bash_tool_config_params import BashToolConfigParams as BashToolConfigParams from .branch_checkout_param import BranchCheckoutParam as BranchCheckoutParam +from .bucket_storage import BucketStorage as BucketStorage +from .bucket_storage import BucketStorageParam as BucketStorageParam +from .bucket_storage import OSSAccessKeyCredentialParam as OSSAccessKeyCredentialParam +from .bucket_storage import OSSBucket as OSSBucket +from .bucket_storage import OSSBucketCredential as OSSBucketCredential +from .bucket_storage import OSSBucketCredentialParam as OSSBucketCredentialParam +from .bucket_storage import OSSBucketParam as OSSBucketParam +from .bucket_storage import OSSRAMRoleCredentialParam as OSSRAMRoleCredentialParam from .budget_limit import BudgetLimit as BudgetLimit from .budget_limit_param import BudgetLimitParam as BudgetLimitParam from .cache_creation_usage import CacheCreationUsage as CacheCreationUsage @@ -236,6 +244,8 @@ from .multiagent_agent_union import MultiagentAgentUnion as MultiagentAgentUnion from .multiagent_params import MultiagentParams as MultiagentParams from .multiagent_self_params import MultiagentSelfParams as MultiagentSelfParams +from .oauth_start_params import OAuthStartParams as OAuthStartParams +from .oauth_start_response import OAuthStartResponse as OAuthStartResponse from .outcome_evaluation_resource import OutcomeEvaluationResource as OutcomeEvaluationResource from .output_behavior_create_new_param import OutputBehaviorCreateNewParam as OutputBehaviorCreateNewParam from .output_behavior_union import OutputBehaviorUnion as OutputBehaviorUnion diff --git a/src/qca/managed/types/bucket_storage.py b/src/qca/managed/types/bucket_storage.py new file mode 100644 index 0000000..c423f75 --- /dev/null +++ b/src/qca/managed/types/bucket_storage.py @@ -0,0 +1,58 @@ +from typing import List, Literal, Optional, Union + +from typing_extensions import Required, TypedDict + +from qca.common._models import BaseModel + + +class OSSRAMRoleCredentialParam(TypedDict, total=False): + type: Required[Literal["ram_role"]] + # Required for a new bucket; omit to retain a stored credential on update. + role_arn: str + + +class OSSAccessKeyCredentialParam(TypedDict, total=False): + type: Required[Literal["access_key"]] + access_key_id: Required[str] + # Required for a new key; omitted when echoing a masked credential on update. + access_key_secret: str + + +OSSBucketCredentialParam = Union[OSSRAMRoleCredentialParam, OSSAccessKeyCredentialParam] + + +class OSSBucketParam(TypedDict, total=False): + name: Required[str] + provider: Required[Literal["aliyun-oss"]] + region: Required[str] + bucket: Required[str] + prefix: str + access: Literal["read_only", "read_write"] + accelerator_endpoint: str + credential: OSSBucketCredentialParam + + +class BucketStorageParam(TypedDict): + buckets: List[OSSBucketParam] + + +class OSSBucketCredential(BaseModel): + """Read view; the API never returns role ARN or AccessKey secret.""" + + type: Literal["ram_role", "access_key"] + access_key_id: Optional[str] = None + + +class OSSBucket(BaseModel): + name: str + provider: Literal["aliyun-oss"] + region: str + bucket: str + prefix: Optional[str] = None + access: Optional[Literal["read_only", "read_write"]] = None + accelerator_endpoint: Optional[str] = None + credential: Optional[OSSBucketCredential] = None + + +class BucketStorage(BaseModel): + buckets: List[OSSBucket] diff --git a/src/qca/managed/types/cloud_config_params.py b/src/qca/managed/types/cloud_config_params.py index 9dba39f..4cac283 100644 --- a/src/qca/managed/types/cloud_config_params.py +++ b/src/qca/managed/types/cloud_config_params.py @@ -5,6 +5,7 @@ from typing_extensions import TypedDict if TYPE_CHECKING: + from .bucket_storage import BucketStorageParam from .limited_network_params import LimitedNetworkParams from .packages_params import PackagesParams from .unrestricted_network_param import UnrestrictedNetworkParam @@ -13,6 +14,7 @@ class CloudConfigParams(TypedDict, total=False): + storage: BucketStorageParam setup_script: Optional[str] networking: Union[UnrestrictedNetworkParam, LimitedNetworkParams] packages: PackagesParams diff --git a/src/qca/managed/types/dream_create_params.py b/src/qca/managed/types/dream_create_params.py index 329298d..7cc94c4 100644 --- a/src/qca/managed/types/dream_create_params.py +++ b/src/qca/managed/types/dream_create_params.py @@ -16,7 +16,7 @@ class DreamCreateParams(TypedDict, total=False): inputs: Required[List[Union[DreamMemoryStoreInputParam, DreamSessionsInputParam]]] - model: Required[Union[str, DreamModelConfigParam]] + model: Optional[Union[str, DreamModelConfigParam]] instructions: Optional[str] workspace_id: Optional[str] output_behavior: Union[OutputBehaviorCreateNewParam, OutputBehaviorUpdateExistingParam] diff --git a/src/qca/managed/types/dream_model_config.py b/src/qca/managed/types/dream_model_config.py index 241d60e..f8313f2 100644 --- a/src/qca/managed/types/dream_model_config.py +++ b/src/qca/managed/types/dream_model_config.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Optional from qca.common._models import BaseModel @@ -9,4 +7,5 @@ class DreamModelConfig(BaseModel): id: Optional[str] = None - speed: Optional[str] = None + effort: Optional[str] = None + context_window: Optional[int] = None diff --git a/src/qca/managed/types/dream_model_config_param.py b/src/qca/managed/types/dream_model_config_param.py index 90b6a31..46bb18f 100644 --- a/src/qca/managed/types/dream_model_config_param.py +++ b/src/qca/managed/types/dream_model_config_param.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from typing import Literal from typing_extensions import Required, TypedDict @@ -9,4 +7,5 @@ class DreamModelConfigParam(TypedDict, total=False): id: Required[str] - speed: Literal["standard", "fast"] + effort: Literal["none", "low", "medium", "high", "xhigh", "max"] + context_window: int diff --git a/src/qca/managed/types/environment_config_union.py b/src/qca/managed/types/environment_config_union.py index 0b02efb..0df728f 100644 --- a/src/qca/managed/types/environment_config_union.py +++ b/src/qca/managed/types/environment_config_union.py @@ -5,6 +5,7 @@ from qca.common._models import BaseModel if TYPE_CHECKING: + from .bucket_storage import BucketStorage from .cloud_config_networking_union import CloudConfigNetworkingUnion from .packages import Packages @@ -12,6 +13,7 @@ class EnvironmentConfigUnion(BaseModel): + storage: Optional[BucketStorage] = None setup_script: Optional[str] = None networking: Optional[CloudConfigNetworkingUnion] = None packages: Optional[Packages] = None diff --git a/src/qca/managed/types/memory_store_list_params.py b/src/qca/managed/types/memory_store_list_params.py index be296e1..fedca2a 100644 --- a/src/qca/managed/types/memory_store_list_params.py +++ b/src/qca/managed/types/memory_store_list_params.py @@ -9,6 +9,8 @@ class MemoryStoreListParams(TypedDict, total=False): + before_id: Optional[str] + after_id: Optional[str] name: Optional[str] created_at_gte: Optional[datetime] created_at_lte: Optional[datetime] diff --git a/src/qca/managed/types/model_config.py b/src/qca/managed/types/model_config.py index 4409db7..c6ba47a 100644 --- a/src/qca/managed/types/model_config.py +++ b/src/qca/managed/types/model_config.py @@ -1,18 +1,13 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Optional, Union +from typing import Optional from qca.common._models import BaseModel -if TYPE_CHECKING: - from .model_config_effort_union import ModelConfigEffortUnion - __all__ = ["ModelConfig"] class ModelConfig(BaseModel): - context_window: Optional[int] = None - id: Optional[str] = None - effort: Optional[Union[str, ModelConfigEffortUnion]] = None inference_geo: Optional[str] = None - speed: Optional[str] = None + id: Optional[str] = None + effort: Optional[str] = None + context_window: Optional[int] = None + effective_context_window: Optional[int] = None diff --git a/src/qca/managed/types/model_config_params.py b/src/qca/managed/types/model_config_params.py index 610cb20..915df62 100644 --- a/src/qca/managed/types/model_config_params.py +++ b/src/qca/managed/types/model_config_params.py @@ -1,39 +1,12 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Literal, Optional, Union +from typing import Literal, Optional from typing_extensions import Required, TypedDict -if TYPE_CHECKING: - from .effort_high_param import EffortHighParam - from .effort_low_param import EffortLowParam - from .effort_max_param import EffortMaxParam - from .effort_medium_param import EffortMediumParam - from .effort_xhigh_param import EffortXhighParam - __all__ = ["ModelConfigParams"] class ModelConfigParams(TypedDict, total=False): - context_window: Optional[int] - id: Required[ - Literal[ - "claude-fable-5-1", - "claude-sonnet-5", - "claude-fable-5", - "claude-opus-5", - "claude-opus-4-8", - "claude-opus-4-7", - "claude-opus-4-6", - "claude-sonnet-4-6", - "claude-haiku-4-5", - "claude-haiku-4-5-20251001", - "claude-opus-4-5", - "claude-opus-4-5-20251101", - "claude-sonnet-4-5", - "claude-sonnet-4-5-20250929", - ] - ] inference_geo: Optional[str] - effort: Union[str, EffortLowParam, EffortMediumParam, EffortHighParam, EffortXhighParam, EffortMaxParam] - speed: Literal["standard", "fast"] + id: Required[str] + effort: Literal["none", "low", "medium", "high", "xhigh", "max"] + context_window: int diff --git a/src/qca/managed/types/oauth_start_params.py b/src/qca/managed/types/oauth_start_params.py new file mode 100644 index 0000000..6bd121e --- /dev/null +++ b/src/qca/managed/types/oauth_start_params.py @@ -0,0 +1,9 @@ +from typing_extensions import Required, TypedDict + + +class OAuthStartParams(TypedDict, total=False): + vault_id: Required[str] + mcp_server_url: Required[str] + client_id: str + client_secret: str + workspace_id: str diff --git a/src/qca/managed/types/oauth_start_response.py b/src/qca/managed/types/oauth_start_response.py new file mode 100644 index 0000000..9f5415c --- /dev/null +++ b/src/qca/managed/types/oauth_start_response.py @@ -0,0 +1,7 @@ +from qca.common._models import BaseModel + + +class OAuthStartResponse(BaseModel): + authorization_url: str + state: str + callback_origin: str diff --git a/tests/fixtures/api-contracts.json b/tests/fixtures/api-contracts.json index a7f8e65..8f29167 100644 --- a/tests/fixtures/api-contracts.json +++ b/tests/fixtures/api-contracts.json @@ -370,7 +370,7 @@ "identity_id", "template_id" ], - "kind": "model" + "kind": "page" }, { "id": "forward.identities.memory_stores.mount", @@ -1170,8 +1170,7 @@ "method": "POST", "path": "/dreams", "required": [ - "inputs", - "model" + "inputs" ], "kind": "model" }, @@ -2216,6 +2215,84 @@ "path": "/memory_stores/search", "required": [], "kind": "page" + }, + { + "id": "forward.dreams.create", + "method": "POST", + "path": "/dreams", + "required": [ + "inputs" + ], + "kind": "model" + }, + { + "id": "forward.dreams.list", + "method": "GET", + "path": "/dreams", + "required": [], + "kind": "page" + }, + { + "id": "forward.dreams.retrieve", + "method": "GET", + "path": "/dreams/{dream_id}", + "required": [ + "dream_id" + ], + "kind": "model" + }, + { + "id": "forward.dreams.cancel", + "method": "POST", + "path": "/dreams/{dream_id}/cancel", + "required": [ + "dream_id" + ], + "kind": "model" + }, + { + "id": "forward.dreams.archive", + "method": "POST", + "path": "/dreams/{dream_id}/archive", + "required": [ + "dream_id" + ], + "kind": "model" + }, + { + "id": "forward.channel_pairings.list", + "method": "GET", + "path": "/channel_pairings", + "required": [], + "kind": "page" + }, + { + "id": "forward.channel_pairings.retrieve", + "method": "GET", + "path": "/channel_pairings/{pairing_id}", + "required": [ + "pairing_id" + ], + "kind": "model" + }, + { + "id": "forward.channel_pairings.update", + "method": "POST", + "path": "/channel_pairings/{pairing_id}", + "required": [ + "pairing_id" + ], + "kind": "model" + }, + { + "id": "managed.oauth.start", + "method": "POST", + "path": "/oauth/start", + "required": [ + "mcp_server_url", + "vault_id" + ], + "kind": "model" } ], "extensions": [ @@ -2230,6 +2307,10 @@ { "source": "cloud-agents-forward-service@8b9474c6: internal/router/router.go, internal/handler/decision/handler.go, internal/model/dto/decision.go, internal/service/decision/{validation,service,context}.go", "operations": 3 + }, + { + "source": "https://docs.qoder.com/cloud-agents/api/forward/dreams/create; https://docs.qoder.com/cloud-agents/api/forward/channels/list-channel-pairings; https://docs.qoder.com/cloud-agents/api/vaults/start-oauth (2026-10-27)", + "operations": 9 } ] } diff --git a/tests/fixtures/official-contract-responses.json b/tests/fixtures/official-contract-responses.json new file mode 100644 index 0000000..40fa371 --- /dev/null +++ b/tests/fixtures/official-contract-responses.json @@ -0,0 +1,56 @@ +{ + "source": "https://docs.qoder.com/cloud-agents/api/forward/dreams/create; https://docs.qoder.com/cloud-agents/api/forward/channels/list-channel-pairings; https://docs.qoder.com/cloud-agents/api/vaults/start-oauth (2026-10-27)", + "dream": { + "id": "drm_contract", + "type": "dream", + "status": "completed", + "inputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract" + } + ], + "outputs": [ + { + "type": "memory_store", + "memory_store_id": "mem_contract", + "files_touched": [ + "MEMORY.md" + ] + } + ], + "model": { + "id": "auto", + "effort": "none", + "context_window": 400000 + }, + "instructions": "", + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "cache_creation_input_tokens": 0, + "cache_read_input_tokens": 0 + }, + "error": null, + "created_at": "2026-10-27T00:00:00Z", + "ended_at": "2026-10-27T00:01:00Z", + "archived_at": null + }, + "pairing": { + "id": "cp_contract", + "channel_id": "ch_contract", + "identity_id": "idn_contract", + "template_id": "tmpl_contract", + "scope_type": "direct", + "scope_external_id": "user_contract", + "scope_display_name": "Contract user", + "status": "active", + "paired_at": "2026-10-27T00:00:00Z", + "created_at": "2026-10-27T00:00:00Z" + }, + "oauth": { + "authorization_url": "https://mcp.example.com/authorize?state=opaque", + "state": "opaque", + "callback_origin": "https://console.example.com" + } +} diff --git a/tests/test_official_contract_alignment.py b/tests/test_official_contract_alignment.py new file mode 100644 index 0000000..424b164 --- /dev/null +++ b/tests/test_official_contract_alignment.py @@ -0,0 +1,284 @@ +"""Official docs.qoder.com model/Outcome contracts retrieved 2026-10-27.""" + +import inspect +import json + +import httpx +import pytest + +from qca import AsyncForward, AsyncManaged, Forward, Managed + + +async def resolve(value): + return await value if inspect.isawaitable(value) else value + + +@pytest.mark.parametrize("async_", [False, True]) +@pytest.mark.parametrize( + "model", ["omitted", None, "", "auto", {"id": "auto", "effort": "none", "context_window": 400000}] +) +async def test_dream_model_default_and_tuning(async_, model): + body = {"inputs": [{"type": "memory_store", "memory_store_id": "memstore_test"}]} + if model != "omitted": + body["model"] = model + + def handle(req): + assert req.url.path.endswith("/dreams") + assert json.loads(req.content) == body + return httpx.Response( + 200, json={"id": "dream_test", "model": model if isinstance(model, dict) else {"id": "auto"}} + ) + + http = (httpx.AsyncClient if async_ else httpx.Client)(transport=httpx.MockTransport(handle)) + client = (AsyncManaged if async_ else Managed)(pat="test", http_client=http, _strict_response_validation=True) + try: + dream = await resolve(client.dreams.create(**body)) + if isinstance(model, dict): + assert dream.model.effort == "none" + assert dream.model.context_window == 400000 + else: + assert dream.model.id == "auto" + finally: + await resolve(client.close()) + + +@pytest.mark.parametrize("async_", [False, True]) +@pytest.mark.parametrize( + "rubric", [{"type": "text", "content": "report exists"}, {"type": "file", "file_id": "file_test"}] +) +async def test_forward_outcome_rubric_roundtrip(async_, rubric): + event = {"id": "evt_test", "type": "user.define_outcome", "rubric": rubric} + + def handle(req): + if req.method == "POST": + assert json.loads(req.content)["events"][0]["rubric"] == rubric + return httpx.Response(200, json={"data": [event], "has_more": False}) + + http = (httpx.AsyncClient if async_ else httpx.Client)(transport=httpx.MockTransport(handle)) + client = (AsyncForward if async_ else Forward)(pat="test", http_client=http, _strict_response_validation=True) + try: + await resolve( + client.sessions.events.send( + "sess_test", events=[{"type": "user.define_outcome", "description": "write report", "rubric": rubric}] + ) + ) + page = await resolve(client.sessions.events.list("sess_test")) + assert page.data[0].rubric.to_dict() == rubric + finally: + await resolve(client.close()) + + +@pytest.fixture +async def mock_sdk(): + clients = [] + + def create(mode, async_, handler, strict=True): + cls = { + ("forward", False): Forward, + ("forward", True): AsyncForward, + ("managed", False): Managed, + ("managed", True): AsyncManaged, + }[mode, async_] + http = (httpx.AsyncClient if async_ else httpx.Client)(transport=httpx.MockTransport(handler)) + client = cls(pat="test", http_client=http, _strict_response_validation=strict, max_retries=0) + clients.append(client) + return client + + yield create + for client in clients: + await resolve(client.close()) + + +@pytest.mark.parametrize("async_", [False, True]) +async def test_identity_config_and_schedule_fields(mock_sdk, async_): + responses = { + "/identities/idn": {"id": "idn", "auto_memory_store": False}, + "/identities/idn/templates/tmpl/config": {}, + "/identities/idn/templates/tmpl/memory_stores": {"memory_store_id": "mem", "access": "read_write"}, + "/schedules/sched": {"resources": [{"type": "file", "file_id": "file_test", "mount_path": "/data/test.txt"}]}, + "/schedule_runs/run": { + "initial_events": [{"type": "user.message", "content": [{"type": "text", "text": "input"}]}] + }, + } + expected = [] + + def handle(req): + path, body = expected.pop(0) + assert req.url.path.endswith(path) + if body is None: + assert req.url.params["include_initial_events"] == "true" + assert not req.content + else: + assert json.loads(req.content) == body + return httpx.Response(200, json=responses[path]) + + client = mock_sdk("forward", async_, handle) + expected.append(("/identities/idn", {"auto_memory_store": False})) + identity = await resolve(client.identities.update("idn", auto_memory_store=False)) + assert identity.auto_memory_store is False + for spec in [ + {"tools": [], "managed_tool_config": {"schedule": {"enabled": False}, "drive": None}}, + {"tools": {"Bash": {"enabled": False}}}, + {"tools": None, "managed_tool_config": None, "system": None, "model": None}, + ]: + expected.append(("/identities/idn/templates/tmpl/config", {"identity_config": spec})) + responses["/identities/idn/templates/tmpl/config"] = {"identity_config": spec} + result = await resolve(client.identities.configs.upsert("tmpl", identity_id="idn", identity_config=spec)) + assert result.identity_config.to_dict() == spec + expected.append( + ("/identities/idn/templates/tmpl/memory_stores", {"memory_store_id": "mem", "access": "read_write"}) + ) + mount = await resolve( + client.identities.memory_stores.mount("tmpl", identity_id="idn", memory_store_id="mem", access="read_write") + ) + assert mount.access == "read_write" + resources = responses["/schedules/sched"]["resources"] + expected.append(("/schedules/sched", {"resources": resources})) + schedule = await resolve(client.schedules.update("sched", resources=resources)) + assert schedule.resources[0].file_id == "file_test" + expected.append(("/schedule_runs/run", None)) + run = await resolve(client.schedule_runs.retrieve("run", include_initial_events=True)) + assert run.initial_events[0].type == "user.message" + assert not expected + + +@pytest.mark.parametrize("async_", [False, True]) +@pytest.mark.parametrize("direction", ["after_id", "before_id"]) +async def test_mount_pagination_retains_scope(mock_sdk, async_, direction): + calls = [] + + def handle(req): + assert req.url.path.endswith("/identities/idn/templates/tmpl/memory_stores") + assert req.url.params["limit"] == "1" + cursor = "start" if not calls else "mem_one" + assert req.url.params[direction] == cursor + assert ("before_id" if direction == "after_id" else "after_id") not in req.url.params + calls.append(req) + id_ = "mem_one" if len(calls) == 1 else "mem_two" + return httpx.Response( + 200, + json={ + "data": [{"memory_store_id": id_, "access": "read_write"}], + "first_id": id_, + "last_id": id_, + "has_more": len(calls) == 1, + }, + ) + + client = mock_sdk("forward", async_, handle) + page = client.identities.memory_stores.list("tmpl", identity_id="idn", limit=1, **{direction: "start"}) + ids = [item.memory_store_id async for item in page] if async_ else [item.memory_store_id for item in page] + assert ids == ["mem_one", "mem_two"] + assert len(calls) == 2 + + +@pytest.mark.parametrize("async_", [False, True]) +async def test_forward_dream_and_pairing_lifecycle(mock_sdk, async_): + from pathlib import Path + + fixtures = json.loads((Path(__file__).parent / "fixtures/official-contract-responses.json").read_text()) + expected = [] + + def handle(req): + method, path, body, result = expected.pop(0) + assert req.method == method and req.url.path.endswith(path) + if body is None: + assert not req.content + else: + assert json.loads(req.content) == body + return httpx.Response(200, json=result) + + client = mock_sdk("forward", async_, handle) + dream = fixtures["dream"] + inputs = [ + {"type": "identity_template", "identity_id": "idn", "template_id": "tmpl"}, + {"type": "sessions", "session_ids": ["sess"]}, + ] + for model in ["omitted", None, "", "auto", {"id": "auto", "effort": "none", "context_window": 400000}]: + body = {"inputs": inputs} + if model != "omitted": + body["model"] = model + expected.append(("POST", "/dreams", body, dream)) + result = await resolve(client.dreams.create(**body)) + assert result.model.effort == "none" and result.outputs[0].files_touched == ["MEMORY.md"] + assert result.usage.input_tokens == 5 and result.error is None + for method in ["retrieve", "cancel", "archive"]: + expected.append( + ( + "GET" if method == "retrieve" else "POST", + "/dreams/drm" + ("" if method == "retrieve" else "/" + method), + None, + dream, + ) + ) + assert (await resolve(getattr(client.dreams, method)("drm"))).id == "drm_contract" + pending = {**fixtures["pairing"], "status": "pairing", "identity_id": "", "template_id": "", "paired_at": ""} + expected.append(("GET", "/channel_pairings/pair", None, pending)) + result = await resolve(client.channel_pairings.retrieve("pair")) + assert result.paired_at == "" and result.identity_id == "" and result.status == "pairing" + expected.append(("POST", "/channel_pairings/pair", {"template_id": "tmpl_new"}, fixtures["pairing"])) + await resolve(client.channel_pairings.update("pair", template_id="tmpl_new")) + assert not expected + + +@pytest.mark.parametrize("async_", [False, True]) +async def test_oauth_and_managed_memory_cursor(mock_sdk, async_): + from pathlib import Path + + fixture = json.loads((Path(__file__).parent / "fixtures/official-contract-responses.json").read_text())["oauth"] + body = { + "vault_id": "vault", + "mcp_server_url": "https://mcp.example.com", + "client_id": "client", + "client_secret": "test-secret", + } + calls = [] + + def handle(req): + calls.append(req) + if req.url.path.endswith("/oauth/start"): + assert req.method == "POST" and json.loads(req.content) == body + assert not req.url.query and req.headers["qoder-workspace-id"] == "workspace" + return httpx.Response(200, json=fixture) + assert req.url.path.endswith("/memory_stores") + assert req.url.params["before_id"] == "mem_before" + assert req.url.params["include_archived"] == "false" and not req.content + return httpx.Response(200, json={"data": [], "has_more": False}) + + client = mock_sdk("managed", async_, handle) + result = await resolve(client.oauth.start(**body, workspace_id="workspace")) + assert result.to_dict() == fixture + await resolve(client.memory_stores.list(before_id="mem_before", include_archived=False)) + assert len(calls) == 2 + + +@pytest.mark.parametrize("async_", [False, True]) +async def test_oss_storage_write_read_and_clear(mock_sdk, async_): + bucket = { + "name": "assets", + "provider": "aliyun-oss", + "region": "cn-hangzhou", + "bucket": "example", + "access": "read_only", + "credential": {"type": "access_key", "access_key_id": "test-ak", "access_key_secret": "test-secret"}, + } + read_bucket = {**bucket, "credential": {"type": "access_key", "access_key_id": "test****"}} + expected = [] + + def handle(req): + body = expected.pop(0) + assert req.method == "POST" and req.url.path.endswith("/environments/env") + assert json.loads(req.content) == body + assert req.headers.get("x-qoder-beta") == "oss-bucket-2026-09-23" + assert not req.url.query + return httpx.Response(200, json={"config": {"type": "cloud", "storage": {"buckets": [read_bucket]}}}) + + client = mock_sdk("managed", async_, handle) + for buckets in [[bucket], [{k: v for k, v in read_bucket.items() if k != "credential"}], []]: + config = {"type": "cloud", "storage": {"buckets": buckets}} + expected.append({"config": config}) + env = await resolve(client.environments.update("env", config=config, betas=["oss-bucket-2026-09-23"])) + credential = env.config.storage.buckets[0].credential + assert credential.to_dict() == {"type": "access_key", "access_key_id": "test****"} + assert not hasattr(credential, "access_key_secret") + assert not expected diff --git a/tests/test_resource_surface.py b/tests/test_resource_surface.py index 414310c..7f6e98b 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"]) == 244 + assert len(CONTRACT_FIXTURE["contracts"]) == 253 @pytest.mark.parametrize("endpoint", ENDPOINTS, ids=endpoint_id) diff --git a/tests/test_response_alignment.py b/tests/test_response_alignment.py index 618d3d6..e0332e7 100644 --- a/tests/test_response_alignment.py +++ b/tests/test_response_alignment.py @@ -120,7 +120,7 @@ async def read_models(client, view): @pytest.mark.parametrize("client_type", [Managed, AsyncManaged]) @pytest.mark.parametrize("resource", ["agents", "sessions"]) -@pytest.mark.parametrize("effort", ["low", {"type": "high"}, None]) +@pytest.mark.parametrize("effort", ["low", "high", "none", None]) async def test_managed_model_effort_response_shapes(client_type, resource, effort): model = {"id": "qfmodel", "effort": effort, "context_window": 200000, "effective_context_window": 200000} item = ( @@ -140,16 +140,13 @@ async def test_managed_model_effort_response_shapes(client_type, resource, effor assert agent.model.id == "qfmodel" assert agent.model.context_window == 200000 assert agent.model.effective_context_window == 200000 - if isinstance(effort, dict): - assert agent.model.effort.type == effort["type"] - else: - assert agent.model.effort == effort + assert agent.model.effort == effort finally: await resolve(client.close()) @pytest.mark.parametrize("client_type", [Managed, AsyncManaged]) -@pytest.mark.parametrize("effort", [123, ["low"]]) +@pytest.mark.parametrize("effort", [123, ["low"], {"type": "high"}]) async def test_managed_model_effort_still_rejects_invalid_response_shapes(client_type, effort): payload = {"data": [{"id": "sess-1", "agent": {"model": {"id": "qfmodel", "effort": effort}}}]} http_type = httpx.AsyncClient if client_type is AsyncManaged else httpx.Client From 029582be8f416ac2269059018f4cc47d03f6ff91 Mon Sep 17 00:00:00 2001 From: moonyue-w <300878504+moonyue-w@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:02:24 +0800 Subject: [PATCH 2/2] feat(sessions): identify SDK-created sessions without overwriting metadata --- CHANGELOG.md | 3 + README.md | 26 +++++ docs/api/reference.md | 22 +++- src/qca/common/_session_metadata.py | 25 ++++ .../forward/resources/sessions/sessions.py | 7 +- .../managed/resources/sessions/sessions.py | 7 +- tests/test_session_metadata.py | 109 ++++++++++++++++++ 7 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 src/qca/common/_session_metadata.py create mode 100644 tests/test_session_metadata.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 097cd0a..d0820ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ existing `0.1.0` release; earlier development prereleases are not listed. ### Added +- Automatically mark direct Forward and Managed Session creation with `__qca_sdk_created`, `__qca_sdk_language`, and the actual `__qca_sdk_version`, preserving caller metadata. - Add Forward Dreams (create, list, retrieve, cancel, archive), channel pairing list/retrieve/update, and Managed OAuth start with typed requests and responses. - Add Identity automatic memory-store control, mount access and cursor pagination, sparse managed-capability overrides, and full tools-array replacement. - Support string Template references and Advisor model entries in Forward Multiagent rosters, Schedule file resources, and optional Run input snapshots. @@ -18,12 +19,14 @@ existing `0.1.0` release; earlier development prereleases are not listed. ### Breaking changes +- Session creation rejects conflicting values for the three reserved SDK metadata keys before sending; matching string values remain accepted. Missing or null metadata now contains the markers, which count toward API metadata limits. - Forward event `rubric` is a text/content or file/file_id object instead of a string. - Managed Agent `effort` is a level string instead of an object; Agent and Dream model configuration no longer declares the unsupported `speed` field. - Identity mount lists now return standard pages (and an `AsyncPaginator` for the async client). Existing awaited calls still yield a page; use normal or async iteration to traverse all pages. ### Migration +- Rename business fields that collide with the three `__qca_sdk_` keys and leave space for automatic markers. See “SDK-created Session metadata” in the README. - Send `rubric={"type": "text", "content": "..."}` and `model={"id": "ultimate", "effort": "high"}`; remove Managed `speed` arguments. See the README's “Official contract alignment” section. ## [0.12.0] diff --git a/README.md b/README.md index b88e92d..17be1d9 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,32 @@ Under the hood these run `pydoc-markdown` via `uv` on a pinned Python 3.12: uv run --python 3.12 --extra dev --locked pydoc-markdown pydoc-markdown.yml ``` +## SDK-created Session metadata + +Direct Forward and Managed Session creation automatically adds these string-valued +metadata fields (Python includes both sync and async clients): + +| Field | Value | +| --- | --- | +| `__qca_sdk_created` | `"true"` | +| `__qca_sdk_language` | `"python"` | +| `__qca_sdk_version` | The installed SDK's version | + +Reserve the `__qca_sdk_` prefix for SDK provenance. Business metadata is preserved, +and the caller's metadata object is not mutated. Omitted or null metadata becomes +an object containing the markers. Passing an existing marker with the exact same +string value is accepted; a different value or type (including null) fails locally +before any HTTP request. This also applies after `extra_body` overrides are merged. + +Session updates, reads and event operations do not inject or repair these fields. +Sessions created indirectly through Schedules, Batches or Realtime are outside this +behavior. These markers describe the creating SDK; they are client-supplied metadata, +not an authentication or authorization mechanism. + +Migration: rename any business fields that already use these three keys before +upgrading. Automatic markers count toward API metadata limits; the SDK never drops +business fields to make room. No additional parameters or opt-in are required. + ## Usage ```python diff --git a/docs/api/reference.md b/docs/api/reference.md index b1b5a81..4bd5870 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -1315,6 +1315,20 @@ async def __aexit__(*_: object) -> None [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/common/_resumable_streaming.py) + + +# qca.common.\_session\_metadata + + + +#### with\_session\_metadata + +```python +def with_session_metadata(body: Mapping[str, Any]) -> dict[str, Any] +``` + +[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/common/_session_metadata.py) + # qca.common.\_streaming @@ -8454,7 +8468,7 @@ def create( [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/sessions/sessions.py) -POST /sessions. +POST /sessions. Adds __qca_sdk_ metadata; conflicting reserved values raise ValueError. @@ -8642,7 +8656,7 @@ async def create( [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/resources/sessions/sessions.py) -POST /sessions. +POST /sessions. Adds __qca_sdk_ metadata; conflicting reserved values raise ValueError. @@ -24670,7 +24684,7 @@ def create( [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/sessions/sessions.py) -POST /sessions. +POST /sessions. Adds __qca_sdk_ metadata; conflicting reserved values raise ValueError. @@ -24940,7 +24954,7 @@ async def create( [[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/managed/resources/sessions/sessions.py) -POST /sessions. +POST /sessions. Adds __qca_sdk_ metadata; conflicting reserved values raise ValueError. diff --git a/src/qca/common/_session_metadata.py b/src/qca/common/_session_metadata.py new file mode 100644 index 0000000..e7ce01f --- /dev/null +++ b/src/qca/common/_session_metadata.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import Any, Mapping + +from qca._version import __version__ + +from ._types import NotGiven + + +def with_session_metadata(body: Mapping[str, Any]) -> dict[str, Any]: + metadata = body.get("metadata") + if metadata is None or isinstance(metadata, NotGiven): + metadata = {} + if not isinstance(metadata, Mapping): + raise ValueError("session creation metadata must be a JSON object or null") + merged = {key: value for key, value in metadata.items() if not isinstance(value, NotGiven)} + for key, expected in { + "__qca_sdk_created": "true", + "__qca_sdk_language": "python", + "__qca_sdk_version": __version__, + }.items(): + if key in merged and (not isinstance(merged[key], str) or merged[key] != expected): + raise ValueError(f"conflicting SDK metadata field: {key}") + merged[key] = expected + return {**body, "metadata": merged} diff --git a/src/qca/forward/resources/sessions/sessions.py b/src/qca/forward/resources/sessions/sessions.py index becaf8e..0014e46 100644 --- a/src/qca/forward/resources/sessions/sessions.py +++ b/src/qca/forward/resources/sessions/sessions.py @@ -7,6 +7,7 @@ import httpx from qca.common._resource import AsyncAPIResource, SyncAPIResource +from qca.common._session_metadata import with_session_metadata from qca.common._types import NOT_GIVEN, NotGiven from qca.common._utils import make_request_options, path_template from qca.common.pagination import AsyncPaginator, SyncPage @@ -105,7 +106,7 @@ def create( extra_body: Dict[str, Any] | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Session: - """POST /sessions.""" + """POST /sessions. Adds __qca_sdk_ metadata; conflicting reserved values raise ValueError.""" _path = path_template("/sessions") options = make_request_options( body={ @@ -123,6 +124,7 @@ def create( extra_body=extra_body, timeout=timeout, ) + options["body"] = with_session_metadata(options["body"]) return self._client.request("POST", _path, cast_to=Session, options=options) def retrieve( @@ -306,7 +308,7 @@ async def create( extra_body: Dict[str, Any] | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Session: - """POST /sessions.""" + """POST /sessions. Adds __qca_sdk_ metadata; conflicting reserved values raise ValueError.""" _path = path_template("/sessions") options = make_request_options( body={ @@ -324,6 +326,7 @@ async def create( extra_body=extra_body, timeout=timeout, ) + options["body"] = with_session_metadata(options["body"]) return await self._client.request("POST", _path, cast_to=Session, options=options) async def retrieve( diff --git a/src/qca/managed/resources/sessions/sessions.py b/src/qca/managed/resources/sessions/sessions.py index a1b41f9..f1afdae 100644 --- a/src/qca/managed/resources/sessions/sessions.py +++ b/src/qca/managed/resources/sessions/sessions.py @@ -7,6 +7,7 @@ import httpx from qca.common._resource import AsyncAPIResource, SyncAPIResource +from qca.common._session_metadata import with_session_metadata from qca.common._types import NOT_GIVEN, NotGiven from qca.common._utils import make_request_options, path_template from qca.common.pagination import AsyncPaginator, SyncPage @@ -102,7 +103,7 @@ def create( extra_body: Dict[str, Any] | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Session: - """POST /sessions.""" + """POST /sessions. Adds __qca_sdk_ metadata; conflicting reserved values raise ValueError.""" _path = path_template("/sessions") options = make_request_options( body={ @@ -123,6 +124,7 @@ def create( extra_body=extra_body, timeout=timeout, ) + options["body"] = with_session_metadata(options["body"]) return self._client.request("POST", _path, cast_to=Session, options=options) def retrieve( @@ -419,7 +421,7 @@ async def create( extra_body: Dict[str, Any] | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Session: - """POST /sessions.""" + """POST /sessions. Adds __qca_sdk_ metadata; conflicting reserved values raise ValueError.""" _path = path_template("/sessions") options = make_request_options( body={ @@ -440,6 +442,7 @@ async def create( extra_body=extra_body, timeout=timeout, ) + options["body"] = with_session_metadata(options["body"]) return await self._client.request("POST", _path, cast_to=Session, options=options) async def retrieve( diff --git a/tests/test_session_metadata.py b/tests/test_session_metadata.py new file mode 100644 index 0000000..6fc98f1 --- /dev/null +++ b/tests/test_session_metadata.py @@ -0,0 +1,109 @@ +import inspect +import json +from copy import deepcopy + +import httpx +import pytest + +from qca import AsyncForward, AsyncManaged, Forward, Managed +from qca._version import __version__ +from qca.common._types import NOT_GIVEN + +STAMP = { + "__qca_sdk_created": "true", + "__qca_sdk_language": "python", + "__qca_sdk_version": __version__, +} + + +async def resolve(value): + return await value if inspect.isawaitable(value) else value + + +@pytest.fixture(params=[("forward", False), ("forward", True), ("managed", False), ("managed", True)]) +async def session_client(request): + mode, async_ = request.param + recorded = [] + + def handle(req): + recorded.append(req) + return httpx.Response(200, json={"id": "sess_test"}) + + cls = { + (False, "forward"): Forward, + (True, "forward"): AsyncForward, + (False, "managed"): Managed, + (True, "managed"): AsyncManaged, + }[async_, mode] + http = (httpx.AsyncClient if async_ else httpx.Client)(transport=httpx.MockTransport(handle)) + client = cls(pat="test", http_client=http, max_retries=0) + params = ( + {"identity_id": "idn", "template_id": "tmpl"} + if mode == "forward" + else {"agent": "agent", "environment_id": "env"} + ) + yield client, params, recorded, mode + await resolve(client.close()) + + +@pytest.mark.parametrize("metadata", [NOT_GIVEN, None, {}, {"ticket": "A-123"}, STAMP]) +async def test_create_stamps_metadata_without_mutating_input(session_client, metadata): + client, params, recorded, _ = session_client + original = deepcopy(metadata) if isinstance(metadata, dict) else metadata + for _ in range(2): + await resolve(client.sessions.create(**params, metadata=metadata)) + expected = {**(metadata if isinstance(metadata, dict) else {}), **STAMP} + for req in recorded: + assert req.method == "POST" and req.url.path.endswith("/sessions") + assert json.loads(req.content) == {**params, "metadata": expected} + assert req.headers["x-qoder-package-version"] == expected["__qca_sdk_version"] + assert metadata == original + + +@pytest.mark.parametrize("key", STAMP) +@pytest.mark.parametrize("value", ["user-value", None, True, 12]) +@pytest.mark.parametrize("extra", [False, True]) +async def test_conflicts_fail_before_transport(session_client, key, value, extra): + client, params, recorded, _ = session_client + metadata = {"ticket": "A-123", key: value} + kwargs = {"extra_body": {"metadata": metadata}} if extra else {"metadata": metadata} + with pytest.raises(ValueError, match=f"conflicting SDK metadata field: {key}") as exc: + await resolve(client.sessions.create(**params, **kwargs)) + assert "user-value" not in str(exc.value) + assert metadata == {"ticket": "A-123", key: value} + assert not recorded + + +async def test_extra_body_and_headers_keep_real_sdk_version(session_client): + client, params, recorded, mode = session_client + metadata = {"ticket": "extra", "__qca_sdk_future": "preserved"} + if mode == "forward": + metadata["nested"] = {"items": [1, False, None]} + original = deepcopy(metadata) + await resolve( + client.sessions.create( + **params, + metadata={"replaced": "typed"}, + extra_body={"metadata": metadata}, + extra_headers={"X-Qoder-Package-Version": "custom-header"}, + ) + ) + assert json.loads(recorded[0].content) == {**params, "metadata": {**original, **STAMP}} + assert metadata == original + + +@pytest.mark.parametrize("metadata", [[], "invalid", 1, False]) +async def test_invalid_metadata_fails_before_transport(session_client, metadata): + client, params, recorded, _ = session_client + with pytest.raises(ValueError, match="metadata must be a JSON object or null"): + await resolve(client.sessions.create(**params, extra_body={"metadata": metadata})) + assert not recorded + + +async def test_update_and_retrieve_do_not_stamp_metadata(session_client): + client, _, recorded, _ = session_client + metadata = {"ticket": "A-123", "__qca_sdk_language": "historic"} + await resolve(client.sessions.update("sess_test", metadata=metadata)) + await resolve(client.sessions.retrieve("sess_test")) + assert json.loads(recorded[0].content) == {"metadata": metadata} + assert not recorded[1].content