diff --git a/CHANGELOG.md b/CHANGELOG.md
index e09a3e0..d0820ba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,30 @@ existing `0.1.0` release; earlier development prereleases are not listed.
## [Unreleased]
+### 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.
+- 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
+
+- 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]
### Added
diff --git a/README.md b/README.md
index 0b33304..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
@@ -868,3 +894,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..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
@@ -2070,6 +2084,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 +2325,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 +2939,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 +3060,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,121 +3729,341 @@ 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/dreams.py)
+
+POST /dreams/{dream_id}/archive.
+
+
+
+## AsyncDreams
+
+```python
+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)
@@ -4765,6 +5155,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 +5235,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 +5423,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 +5503,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 +5634,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 +5658,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 +5706,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 +5734,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 +7446,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 +7505,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 +7576,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 +7647,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 +7795,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 +7866,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,
@@ -8056,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.
@@ -8244,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.
@@ -11383,57 +11795,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)
+[[view_source]](https://github.com/QoderAI/qoder-cloud-agents-sdk-python/blob/main/src/qca/forward/types/channel_pairing_list_params.py)
-
+
-#### session\_key
+#### channel\_id
-
+
-#### channel\_id
+#### status
-
+
-#### channel\_type
+#### target\_type
-
+
-#### status
+#### target\_external\_id
-
+
-#### qr\_code\_content
+#### identity\_id
-
+
-#### qr\_code\_image\_base64
+#### template\_id
-
+
-#### expires\_at
+#### limit
-
+
-#### err\_code
+#### after\_id
-
+
-#### err\_msg
+#### 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 +12412,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 +13912,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 +14140,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 +14202,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 +14294,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 +14378,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 +14422,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 +14452,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 +14534,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 +14626,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 +15468,10 @@ class MultiagentEntry(BaseModel)
#### type
+
+
+#### model
+
#### template\_id
@@ -14598,6 +15498,10 @@ class MultiagentEntryParam(TypedDict)
#### type
+
+
+#### model
+
#### template\_id
@@ -14606,6 +15510,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 +16352,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 +16504,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 +16582,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 +16752,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 +16930,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 +17082,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 +20227,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 +20369,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 +21768,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 +21891,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 +23502,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 +23702,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 +24005,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
@@ -23532,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.
@@ -23802,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.
@@ -26611,6 +27763,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 +28071,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 +29773,13 @@ class DreamModelConfig(BaseModel)
#### id
-
+
-#### speed
+#### effort
+
+
+
+#### context\_window
@@ -28459,9 +29799,13 @@ class DreamModelConfigParam(TypedDict)
#### id
-
+
-#### speed
+#### effort
+
+
+
+#### context\_window
@@ -28811,6 +30155,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 +32045,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 +32639,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 +32651,13 @@ class ModelConfig(BaseModel)
#### effort
-
+
-#### inference\_geo
+#### context\_window
-
+
-#### speed
+#### effective\_context\_window
@@ -31335,25 +32691,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 +32955,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/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/_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/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/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/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/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
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