agenthooksprotocol 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agenthooksprotocol-0.1.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.2.0}/PKG-INFO +141 -10
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/README.md +140 -9
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/pyproject.toml +1 -1
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/__init__.py +6 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_content.py +215 -86
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_grants.py +60 -23
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_hooks.py +177 -14
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/__init__.py +33474 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/capability.py +12 -120
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/content.py +64 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/effect.py +5 -9
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/event.py +52 -30
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/registration.py +7 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/subscription.py +0 -10
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_models/tool.py +0 -32
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/_models/transport.py +24 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/ahp-codegen.lock.json +14 -6
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/attachment.py +136 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/auth.py +6 -3
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/capability.py +1 -39
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/content.py +23 -1
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/effect.py +9 -19
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/event.py +27 -15
- agenthooksprotocol-0.2.0/src/agenthooksprotocol/generated.py +8872 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/interop.py +7 -11
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle.py +2 -11
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle_server.py +21 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/schemas.json +171 -123
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/attachments.py +12 -5
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/state.py +3 -3
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/subscription.py +0 -5
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/tool.py +0 -16
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transport.py +7 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/http.py +10 -5
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0/src/agenthooksprotocol.egg-info}/PKG-INFO +141 -10
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/SOURCES.txt +6 -1
- agenthooksprotocol-0.2.0/tests/test_attachments.py +731 -0
- agenthooksprotocol-0.2.0/tests/test_content_lifetime.py +315 -0
- agenthooksprotocol-0.2.0/tests/test_content_receipts.py +155 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_integration_edges.py +1 -1
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_interop.py +15 -3
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_lifecycle.py +2 -2
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_lifecycle_auth.py +68 -1
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_owned_content.py +8 -5
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_public_content.py +26 -3
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_public_server.py +1 -1
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_schema_corrections.py +2 -2
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_sdk_unification.py +7 -5
- agenthooksprotocol-0.2.0/tests/test_structural_facade.py +151 -0
- agenthooksprotocol-0.1.0/src/agenthooksprotocol/_models/__init__.py +0 -18414
- agenthooksprotocol-0.1.0/src/agenthooksprotocol/_models/content.py +0 -58
- agenthooksprotocol-0.1.0/src/agenthooksprotocol/_models/registration.py +0 -20
- agenthooksprotocol-0.1.0/src/agenthooksprotocol/_models/transport.py +0 -8
- agenthooksprotocol-0.1.0/src/agenthooksprotocol/generated.py +0 -11595
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/LICENSE +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/setup.cfg +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_boundaries.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/_diagnostics.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/candidate.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/catalogue.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/codec.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/compaction.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/diagnostics.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/elicitation.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/integrations/__init__.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/integrations/pydantic.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lifecycle_client.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/lineage.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/observation_chain.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/path.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/permission.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/py.typed +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/registration.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/runtime.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/__init__.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/_framing.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/_json.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/asgi.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/hooks.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/server/stdio.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/__init__.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol/transports/stdio.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/dependency_links.txt +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/requires.txt +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/src/agenthooksprotocol.egg-info/top_level.txt +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_catalogue.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_codec.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_compaction.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_compaction_wire.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_content.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_fixture_public.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_integration.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_observation.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_observation_chain_transport.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_owned_content_hooks.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_package.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_public_examples.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_public_hooks.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_registration_auth.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_safety.py +0 -0
- {agenthooksprotocol-0.1.0 → agenthooksprotocol-0.2.0}/tests/test_session_manifest.py +0 -0
{agenthooksprotocol-0.1.0/src/agenthooksprotocol.egg-info → agenthooksprotocol-0.2.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agenthooksprotocol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Official non-normative Python SDK for the Agent Hooks Protocol
|
|
5
5
|
Author: Agent Hooks Protocol maintainers
|
|
6
6
|
License:
|
|
@@ -228,10 +228,10 @@ A typed, asynchronous SDK for the [Agent Hooks Protocol](https://github.com/agen
|
|
|
228
228
|
|
|
229
229
|
## Install
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
Install the published `agenthooksprotocol` package from [PyPI](https://pypi.org/project/agenthooksprotocol/):
|
|
232
232
|
|
|
233
233
|
```sh
|
|
234
|
-
python -m pip install 'agenthooksprotocol[http]
|
|
234
|
+
python -m pip install 'agenthooksprotocol[http]'
|
|
235
235
|
```
|
|
236
236
|
|
|
237
237
|
HTTPX is an opt-in dependency (`[http]`). Pydantic is an independent opt-in integration (`[pydantic]`); neither a web framework nor Pydantic is needed for the core SDK or stdio.
|
|
@@ -260,7 +260,7 @@ async def review(existing_arguments):
|
|
|
260
260
|
return result # Host validation and authorization still precede execution.
|
|
261
261
|
```
|
|
262
262
|
|
|
263
|
-
Generated `Input` objects are distinct from canonical wire events. They flatten only protocol-owned wrappers, never application arguments, and `to_wire()` performs the shared mapping. `path` and `origin` remain separate required facts. Optional host IDs, timestamps, parent IDs, and other facts are retained; `event_id=` overrides an input ID. The SDK supplies type/source and the configured session manifest. Low-level `dispatch`
|
|
263
|
+
Generated `Input` objects are distinct from canonical wire events. They flatten only protocol-owned wrappers, never application arguments, and `to_wire()` performs the shared mapping. `path` and `origin` remain separate required facts. Optional host IDs, timestamps, parent IDs, and other facts are retained; `event_id=` overrides an input ID. The SDK supplies type/source and the configured session manifest. Low-level `dispatch` accepts canonical dictionaries.
|
|
264
264
|
|
|
265
265
|
Capability builders are immutable: reusing a base declaration does not widen it. `intercept()` deliberately advertises **both intercept and observe**; `observe()` is observation-only. `modify_input(replace=True)` supplies both the effect grant and operation block. Empty operations and observation effect grants are rejected; boundary compatibility is validated before delivery. Form and URL elicitation grants are explicit (`elicitation_form()` / `elicitation_url()`). Raw manifests retain their exact modes; omission never implies permission. Per-call `capabilities=` only narrows configured grants.
|
|
266
266
|
|
|
@@ -269,7 +269,7 @@ Runnable, statically checked consumer programs live in [`examples/`](examples):
|
|
|
269
269
|
- `typed_tool.py`: JSON registration, generated tool input, typed grants, initial state, real in-process ASGI handler, borrowed HTTP client, and explicit application decoding.
|
|
270
270
|
- `common_cases.py`: rewrite/allow, deny, invalid application shape, host policy, occurrence narrowing, native initial state, and fail-open/fail-closed reports. The host operation is an in-memory execution recorder, not a shell.
|
|
271
271
|
- `stdio_tool.py` / `stdio_backend.py`: a real owned backend process using the same public Handler, denial, and child cleanup.
|
|
272
|
-
- `upload.py`: streamed binary upload, metadata no-read behavior, independent upload credentials, verified
|
|
272
|
+
- `upload.py`: streamed binary upload, metadata no-read behavior, independent upload credentials, verified upload receipt, and caller-owned immutable storage.
|
|
273
273
|
|
|
274
274
|
- `lifecycle.py`: caller-owned task groups, operation-owned observations, interrupted requests, idempotent close, and proof that an owned subprocess was reaped.
|
|
275
275
|
- `http_auth.py`: real loopback HTTP, canonical backend callbacks, registration `tokenEnv` resolution, and separate content-upload authorization. Its standard-library HTTP routing is example host code, not a production server.
|
|
@@ -313,7 +313,7 @@ The default `auth.EnvironmentAuthProvider` resolves bearer `tokenEnv` or optiona
|
|
|
313
313
|
|
|
314
314
|
## Static host manifest
|
|
315
315
|
|
|
316
|
-
Configure `Hooks(config, *, source, manifest=full_manifest, transport=None, auth_provider=None)` with a complete canonical static manifest, **or** use
|
|
316
|
+
Configure `Hooks(config, *, source, manifest=full_manifest, transport=None, auth_provider=None)` with a complete canonical static manifest, **or** use `capabilities=event_declarations`. These options are mutually exclusive. The full manifest supplies the same event/mode/effect authority used for admission; it is not backend discovery. `hooks.manifest` returns a detached snapshot.
|
|
317
317
|
|
|
318
318
|
The event-map shorthand advertises only explicitly declared events and modes. Its derived manifest records a gap for every omitted canonical event and for host-level facilities that the map cannot establish (transports, authentication, tool paths, content categories, limits, and managed policy). Empty lists do not mean universal support. Use a full manifest to declare those facilities accurately; explicit full manifests are preserved rather than augmented with assumed support.
|
|
319
319
|
|
|
@@ -329,7 +329,7 @@ The attachment receiver verifies byte length and digest through EOF before commi
|
|
|
329
329
|
|
|
330
330
|
## Body-bound boundaries
|
|
331
331
|
|
|
332
|
-
Use `ContentContext` for MCP elicitation and compaction bodies. Its asynchronous `resolve(reference)` and `upload(bytes)` functions are trusted storage/transport adapters, not application-schema callbacks. `upload` must return a verified
|
|
332
|
+
Use `ContentContext` for MCP elicitation and compaction bodies. Its asynchronous `resolve(reference)` and `upload(bytes)` functions are trusted storage/transport adapters, not application-schema callbacks. `upload` must return a verified `ContentUploadReceipt` (`ref`, `size`, `sha256`) after storage commit. The SDK checks receipt length and digest against the actual uploaded bytes. `resolve` receives only `{ "ref": ... }` and must resolve immutable bytes within its authorized storage scope; event metadata is not an integrity authority. The SDK detects changes to previously resolved bytes.
|
|
333
333
|
|
|
334
334
|
Bindings are explicit paths within the canonical event:
|
|
335
335
|
|
|
@@ -346,11 +346,11 @@ Pass `content=context` to the named boundary. An elicitation result needs the or
|
|
|
346
346
|
|
|
347
347
|
`OwnedContentSource(stream, max_bytes=..., timeout=...)` wraps an async native `read(size)`/`receive(size)` stream with `aclose()`. Construction does not read. Size/hash expectations are optional and verified, not trusted. Generated inputs expose named `bind_<slot>_source(source)` methods; repeated content slots additionally require `index=`. For example, `input.bind_items_source(source, index=0)` binds an existing metadata item without putting the stream into wire JSON.
|
|
348
348
|
|
|
349
|
-
Pass the bound input to a named hook method with `uploads={backend_id: authorized_upload_callback}`. Each callback receives immutable bytes and returns a receiver-allocated canonical
|
|
349
|
+
Pass the bound input to a named hook method with `uploads={backend_id: authorized_upload_callback}`. Each callback receives immutable bytes and returns a receiver-allocated canonical upload receipt; it can use `auth.AuthenticatedHTTPTransport.upload(..., authentication=upload_binding)`. Metadata, omit, and unmatched receivers consume no bytes and require no uploader. Selected body delivery snapshots once within limits, hashes actual raw bytes, uploads independently for each authorized destination, and verifies receipts before event delivery. Missing receiver authorization fails before reading. Use `ContentSources` to supply explicit source bindings, or `ContentContext` to resolve canonical references.
|
|
350
350
|
|
|
351
|
-
Ownership transfers to the source wrapper, then to the hook operation when passed. Streams close on success, failure, cancellation, and unused selection; snapshots release when the call finishes. Sources are single-operation values, not concurrently reusable. Call `aclose()` or use an async context manager for a source that never reaches a hook call. Cleanup is shielded and bounded separately from the operation budget.
|
|
351
|
+
Ownership transfers to the source wrapper, then to the hook operation when passed. Streams close on success, failure, cancellation, and unused selection; snapshots release when the call finishes. Closed wrappers also release their reader, so retaining a source or result does not retain the reader’s input buffer. Sources are single-operation values, not concurrently reusable. Call `aclose()` or use an async context manager for a source that never reaches a hook call. Cleanup is shielded and bounded separately from the operation budget.
|
|
352
352
|
|
|
353
|
-
For custom transport integrations, `begin(canonical_request)` returns a `PendingInvocation` with separate acquisition, cancellation, and acceptance. `exchange(canonical_request)` uses the same settlement engine. Content-aware pending invocations use `await accept_content()`; this keeps upload completion and cancellation inside the publication boundary. The ordinary named-method path does this automatically.
|
|
353
|
+
For custom transport integrations, `begin(canonical_request)` returns a `PendingInvocation` with separate acquisition, cancellation, and acceptance. `exchange(canonical_request)` uses the same settlement engine. Content-aware pending invocations use `await accept_content()`; this keeps upload completion and cancellation inside the publication boundary. The ordinary named-method path does this automatically. Pending invocations retain selected bytes only until acceptance, cancellation, acquisition/finalization failure, or harness close. Failed finalization may be retried and resolves fresh bytes. Harness tracking is weak: dropping a pending invocation does not keep its prepared payload alive. Terminal handles also detach their resolver/uploader context, so retained handles do not retain the caller’s backing store. Returned references continue to resolve in caller-owned storage; cleanup never clears that persistent store.
|
|
354
354
|
|
|
355
355
|
## Generated models and provenance
|
|
356
356
|
|
|
@@ -368,3 +368,134 @@ uv run python -m build
|
|
|
368
368
|
```
|
|
369
369
|
|
|
370
370
|
Integration tests also use a matching sibling `../agent-hooks-protocol` checkout for shared fixtures and public synthetic certificates. The installed runtime uses its bundled schemas and does not need that checkout. No SDK API executes host tools on your behalf.
|
|
371
|
+
|
|
372
|
+
### Typed composed payloads
|
|
373
|
+
|
|
374
|
+
Generated facade constructors retain nested model types through schema
|
|
375
|
+
compositions. MCP connection `gaps` parameters accept lists of typed gap models,
|
|
376
|
+
and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
|
|
377
|
+
attributes. `ModelVisibleItem` has composed content constructors with typed roles.
|
|
378
|
+
|
|
379
|
+
Facade objects are mappings with read-only attributes.
|
|
380
|
+
Optional attributes return `None` when absent; mapping membership still records
|
|
381
|
+
presence. Attributes conflicting with mapping methods use a trailing underscore
|
|
382
|
+
(such as `items_`), so `dict.items()` keeps working. Wire parsers return lossless mappings, not hydrated facade instances.
|
|
383
|
+
|
|
384
|
+
Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
|
|
385
|
+
check location/evidence alternatives and other structural schema constraints. Application
|
|
386
|
+
JSON, native payloads, and extension values remain intentionally dynamic.
|
|
387
|
+
|
|
388
|
+
Upload receipts and event references are distinct. To attach a successful upload,
|
|
389
|
+
use `content.reference(receipt)` (or `{"ref": receipt["ref"]}`). Never attach the
|
|
390
|
+
receipt itself: body-selected content carries only the ref, with no outer `size`
|
|
391
|
+
or `sha256`. Metadata-only content and body gaps may still disclose size/digest.
|
|
392
|
+
|
|
393
|
+
### Structural model decoding and effect-family support
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
from agenthooksprotocol import ContentReference, capability, effect
|
|
397
|
+
|
|
398
|
+
caps = capability.Capabilities.from_dict({"effects": ["deny", "vendor.custom"]})
|
|
399
|
+
assert caps.supports(capability.EffectName.DENY)
|
|
400
|
+
assert caps.supports("vendor.custom")
|
|
401
|
+
assert effect.EffectName is capability.EffectName
|
|
402
|
+
reference = ContentReference.from_dict({"ref": "opaque", "vendor": {"version": 2}})
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
`supports(effect)` is available on generic and incoming capability models. It
|
|
406
|
+
checks the advertised `effects` list only. Nested grants do not imply family
|
|
407
|
+
support. The result is **not authorization**: it does not check target, operation,
|
|
408
|
+
mode, contextual restrictions, or permission. `EffectName` contains schema-derived
|
|
409
|
+
known identifiers; custom family identifiers remain ordinary strings.
|
|
410
|
+
|
|
411
|
+
Wire-model keyword constructors and `Model.from_dict(mapping)` validate with
|
|
412
|
+
the same structural descriptor engine as generated `parse_*` functions. The
|
|
413
|
+
original composition is retained, including required members, explicit nulls,
|
|
414
|
+
literals, closed enums, forbidden combinations, and union ambiguity. This is the
|
|
415
|
+
SDK's structural contract, not complete contextual protocol validation.
|
|
416
|
+
`from_dict` does not supply omitted wire literals or defaults. It validates once
|
|
417
|
+
and privately hydrates nested typed models without recursively calling public
|
|
418
|
+
constructors. For example, a decoded HTTP connection supports
|
|
419
|
+
`connection.gaps[0].reason`, just like a constructed connection. Union selection
|
|
420
|
+
reuses memoized descriptor checks from that decode; subtrees are not repeatedly
|
|
421
|
+
validated during hydration. Extension/application values remain ordinary JSON
|
|
422
|
+
values. Descriptors and hydration metadata are cached.
|
|
423
|
+
|
|
424
|
+
Invalid wire models raise `ValueError`; validation failures carry `result`,
|
|
425
|
+
`diagnostics`, and `raw` attributes. Use `parse_*` when you need the full result on
|
|
426
|
+
both success and failure, including unknown-variant warnings and lossless raw
|
|
427
|
+
values. Open extension values remain intact. Finite `Decimal` values are retained;
|
|
428
|
+
non-finite numbers and non-JSON values are rejected. Parsing raw JSON through
|
|
429
|
+
`parse_*` retains decimal precision; decoding it first with standard-library
|
|
430
|
+
`json.loads` cannot recover precision already lost to a float.
|
|
431
|
+
|
|
432
|
+
Provide valid enum/literal values and all required wire members to `from_dict`. A null candidate, a candidate with
|
|
433
|
+
`{"value": null}`, and a candidate with `{"value": 0}` remain distinct; omitting a
|
|
434
|
+
required candidate is an error. Generated input projections and capability grant
|
|
435
|
+
builders are construction helpers, not wire decode entrypoints; their final wire
|
|
436
|
+
objects are validated at the dispatch/parse boundary. Direct dictionary
|
|
437
|
+
mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
|
|
438
|
+
are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
|
|
439
|
+
|
|
440
|
+
Wire constructors also privately hydrate nested mappings: for example,
|
|
441
|
+
`capability.Capabilities(effects=["modify"], modify={}).modify.input` returns
|
|
442
|
+
`None`, not an attribute error. Already typed child models retain their identity;
|
|
443
|
+
plain nested mappings become typed models after the constructor's single cached
|
|
444
|
+
validation pass. Family queries include all generated per-occurrence capability
|
|
445
|
+
models, such as `capability.ToolBeforeCapabilities`.
|
|
446
|
+
|
|
447
|
+
Constructor aliases cannot silently overwrite wire keys. Supplying both
|
|
448
|
+
`tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
|
|
449
|
+
optional/defaulted fields, a lone wire spelling (for example `addressForm=` or
|
|
450
|
+
`protocolVersion=`) is used and validated rather than replaced by a default.
|
|
451
|
+
Required constructor parameters use their documented Python spelling;
|
|
452
|
+
use `from_dict` to decode complete wire-keyed objects. In `from_dict`, distinct
|
|
453
|
+
keys such as `toolName` and `tool_name` remain distinct wire/extension keys.
|
|
454
|
+
|
|
455
|
+
### Owned attachments
|
|
456
|
+
|
|
457
|
+
The owned attachment APIs in this section are not included in the published 0.1.1 package.
|
|
458
|
+
|
|
459
|
+
Use `Attachment.from_bytes(data)` for immutable Python `bytes`, or
|
|
460
|
+
`Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Bind it
|
|
461
|
+
with the typed input's `bind_*_source` method on an item without a body reference.
|
|
462
|
+
Content IDs, media types, and other metadata belong on the content item. See the
|
|
463
|
+
runnable [file attachment example](examples/file_attachment.py).
|
|
464
|
+
|
|
465
|
+
Dispatch transfers ownership once. Metadata-only and unmatched deliveries do not
|
|
466
|
+
invoke the loader. Selected body deliveries share one immutable snapshot, with
|
|
467
|
+
receiver-specific references. Successful results own the bytes **or still-unread
|
|
468
|
+
loader**, independent of `Hooks` shutdown. Use `async with result:` or
|
|
469
|
+
`await result.aclose()` and read with
|
|
470
|
+
`await result.attachments.read("context.compact.before.items[0]")`. Cleanup runs
|
|
471
|
+
for unopened sources too. Do not reuse an attachment across invocations; create a
|
|
472
|
+
new attachment (which can share the same immutable `bytes`) instead.
|
|
473
|
+
|
|
474
|
+
The default accepted size is 4 MiB and lazy loading timeout is 30 seconds;
|
|
475
|
+
constructors accept `max_bytes` and lazy construction accepts `timeout`. Loaders
|
|
476
|
+
must bound their own allocations. `Attachment(async_stream, ...)` also supports
|
|
477
|
+
the bounded `read(size)`/`receive(max_bytes)` source contract. Stream cleanup
|
|
478
|
+
is shielded and bounded to one second. Cancellation and failure never retry a
|
|
479
|
+
loader. Python mutable buffers are rejected, and reads
|
|
480
|
+
return immutable bytes.
|
|
481
|
+
|
|
482
|
+
Remote body delivery requires an authorized receiver uploader through
|
|
483
|
+
`uploads={backend_id: async_upload}`. The uploader supplies a reference that the
|
|
484
|
+
receiver can read.
|
|
485
|
+
Metadata-only delivery and result reads require no uploader.
|
|
486
|
+
|
|
487
|
+
Attachments own their immutable bytes directly. A lazy loader's `bytes` object
|
|
488
|
+
becomes the attachment snapshot; upload and result reads borrow that same object.
|
|
489
|
+
Receiver applications manage upload storage.
|
|
490
|
+
|
|
491
|
+
Compaction text and elicitation JSON effects read selected attachment
|
|
492
|
+
owners. An accepted replacement creates a new effective attachment; subsequent
|
|
493
|
+
deliveries and the result use that owner. Original attachment size bounds also
|
|
494
|
+
apply to replacements. Generated candidates are available through
|
|
495
|
+
`await result.attachments.read("candidate")`. Binary/file editing is not supported.
|
|
496
|
+
Elicitation results require their original request correlation context;
|
|
497
|
+
`ContentContext.attachments` can provide the original request owner without a
|
|
498
|
+
resolver. A resolver is an optional
|
|
499
|
+
adapter for externally supplied references.
|
|
500
|
+
|
|
501
|
+
Results are invocation-local, not session archives.
|
|
@@ -4,10 +4,10 @@ A typed, asynchronous SDK for the [Agent Hooks Protocol](https://github.com/agen
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Install the published `agenthooksprotocol` package from [PyPI](https://pypi.org/project/agenthooksprotocol/):
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
|
-
python -m pip install 'agenthooksprotocol[http]
|
|
10
|
+
python -m pip install 'agenthooksprotocol[http]'
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
HTTPX is an opt-in dependency (`[http]`). Pydantic is an independent opt-in integration (`[pydantic]`); neither a web framework nor Pydantic is needed for the core SDK or stdio.
|
|
@@ -36,7 +36,7 @@ async def review(existing_arguments):
|
|
|
36
36
|
return result # Host validation and authorization still precede execution.
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Generated `Input` objects are distinct from canonical wire events. They flatten only protocol-owned wrappers, never application arguments, and `to_wire()` performs the shared mapping. `path` and `origin` remain separate required facts. Optional host IDs, timestamps, parent IDs, and other facts are retained; `event_id=` overrides an input ID. The SDK supplies type/source and the configured session manifest. Low-level `dispatch`
|
|
39
|
+
Generated `Input` objects are distinct from canonical wire events. They flatten only protocol-owned wrappers, never application arguments, and `to_wire()` performs the shared mapping. `path` and `origin` remain separate required facts. Optional host IDs, timestamps, parent IDs, and other facts are retained; `event_id=` overrides an input ID. The SDK supplies type/source and the configured session manifest. Low-level `dispatch` accepts canonical dictionaries.
|
|
40
40
|
|
|
41
41
|
Capability builders are immutable: reusing a base declaration does not widen it. `intercept()` deliberately advertises **both intercept and observe**; `observe()` is observation-only. `modify_input(replace=True)` supplies both the effect grant and operation block. Empty operations and observation effect grants are rejected; boundary compatibility is validated before delivery. Form and URL elicitation grants are explicit (`elicitation_form()` / `elicitation_url()`). Raw manifests retain their exact modes; omission never implies permission. Per-call `capabilities=` only narrows configured grants.
|
|
42
42
|
|
|
@@ -45,7 +45,7 @@ Runnable, statically checked consumer programs live in [`examples/`](examples):
|
|
|
45
45
|
- `typed_tool.py`: JSON registration, generated tool input, typed grants, initial state, real in-process ASGI handler, borrowed HTTP client, and explicit application decoding.
|
|
46
46
|
- `common_cases.py`: rewrite/allow, deny, invalid application shape, host policy, occurrence narrowing, native initial state, and fail-open/fail-closed reports. The host operation is an in-memory execution recorder, not a shell.
|
|
47
47
|
- `stdio_tool.py` / `stdio_backend.py`: a real owned backend process using the same public Handler, denial, and child cleanup.
|
|
48
|
-
- `upload.py`: streamed binary upload, metadata no-read behavior, independent upload credentials, verified
|
|
48
|
+
- `upload.py`: streamed binary upload, metadata no-read behavior, independent upload credentials, verified upload receipt, and caller-owned immutable storage.
|
|
49
49
|
|
|
50
50
|
- `lifecycle.py`: caller-owned task groups, operation-owned observations, interrupted requests, idempotent close, and proof that an owned subprocess was reaped.
|
|
51
51
|
- `http_auth.py`: real loopback HTTP, canonical backend callbacks, registration `tokenEnv` resolution, and separate content-upload authorization. Its standard-library HTTP routing is example host code, not a production server.
|
|
@@ -89,7 +89,7 @@ The default `auth.EnvironmentAuthProvider` resolves bearer `tokenEnv` or optiona
|
|
|
89
89
|
|
|
90
90
|
## Static host manifest
|
|
91
91
|
|
|
92
|
-
Configure `Hooks(config, *, source, manifest=full_manifest, transport=None, auth_provider=None)` with a complete canonical static manifest, **or** use
|
|
92
|
+
Configure `Hooks(config, *, source, manifest=full_manifest, transport=None, auth_provider=None)` with a complete canonical static manifest, **or** use `capabilities=event_declarations`. These options are mutually exclusive. The full manifest supplies the same event/mode/effect authority used for admission; it is not backend discovery. `hooks.manifest` returns a detached snapshot.
|
|
93
93
|
|
|
94
94
|
The event-map shorthand advertises only explicitly declared events and modes. Its derived manifest records a gap for every omitted canonical event and for host-level facilities that the map cannot establish (transports, authentication, tool paths, content categories, limits, and managed policy). Empty lists do not mean universal support. Use a full manifest to declare those facilities accurately; explicit full manifests are preserved rather than augmented with assumed support.
|
|
95
95
|
|
|
@@ -105,7 +105,7 @@ The attachment receiver verifies byte length and digest through EOF before commi
|
|
|
105
105
|
|
|
106
106
|
## Body-bound boundaries
|
|
107
107
|
|
|
108
|
-
Use `ContentContext` for MCP elicitation and compaction bodies. Its asynchronous `resolve(reference)` and `upload(bytes)` functions are trusted storage/transport adapters, not application-schema callbacks. `upload` must return a verified
|
|
108
|
+
Use `ContentContext` for MCP elicitation and compaction bodies. Its asynchronous `resolve(reference)` and `upload(bytes)` functions are trusted storage/transport adapters, not application-schema callbacks. `upload` must return a verified `ContentUploadReceipt` (`ref`, `size`, `sha256`) after storage commit. The SDK checks receipt length and digest against the actual uploaded bytes. `resolve` receives only `{ "ref": ... }` and must resolve immutable bytes within its authorized storage scope; event metadata is not an integrity authority. The SDK detects changes to previously resolved bytes.
|
|
109
109
|
|
|
110
110
|
Bindings are explicit paths within the canonical event:
|
|
111
111
|
|
|
@@ -122,11 +122,11 @@ Pass `content=context` to the named boundary. An elicitation result needs the or
|
|
|
122
122
|
|
|
123
123
|
`OwnedContentSource(stream, max_bytes=..., timeout=...)` wraps an async native `read(size)`/`receive(size)` stream with `aclose()`. Construction does not read. Size/hash expectations are optional and verified, not trusted. Generated inputs expose named `bind_<slot>_source(source)` methods; repeated content slots additionally require `index=`. For example, `input.bind_items_source(source, index=0)` binds an existing metadata item without putting the stream into wire JSON.
|
|
124
124
|
|
|
125
|
-
Pass the bound input to a named hook method with `uploads={backend_id: authorized_upload_callback}`. Each callback receives immutable bytes and returns a receiver-allocated canonical
|
|
125
|
+
Pass the bound input to a named hook method with `uploads={backend_id: authorized_upload_callback}`. Each callback receives immutable bytes and returns a receiver-allocated canonical upload receipt; it can use `auth.AuthenticatedHTTPTransport.upload(..., authentication=upload_binding)`. Metadata, omit, and unmatched receivers consume no bytes and require no uploader. Selected body delivery snapshots once within limits, hashes actual raw bytes, uploads independently for each authorized destination, and verifies receipts before event delivery. Missing receiver authorization fails before reading. Use `ContentSources` to supply explicit source bindings, or `ContentContext` to resolve canonical references.
|
|
126
126
|
|
|
127
|
-
Ownership transfers to the source wrapper, then to the hook operation when passed. Streams close on success, failure, cancellation, and unused selection; snapshots release when the call finishes. Sources are single-operation values, not concurrently reusable. Call `aclose()` or use an async context manager for a source that never reaches a hook call. Cleanup is shielded and bounded separately from the operation budget.
|
|
127
|
+
Ownership transfers to the source wrapper, then to the hook operation when passed. Streams close on success, failure, cancellation, and unused selection; snapshots release when the call finishes. Closed wrappers also release their reader, so retaining a source or result does not retain the reader’s input buffer. Sources are single-operation values, not concurrently reusable. Call `aclose()` or use an async context manager for a source that never reaches a hook call. Cleanup is shielded and bounded separately from the operation budget.
|
|
128
128
|
|
|
129
|
-
For custom transport integrations, `begin(canonical_request)` returns a `PendingInvocation` with separate acquisition, cancellation, and acceptance. `exchange(canonical_request)` uses the same settlement engine. Content-aware pending invocations use `await accept_content()`; this keeps upload completion and cancellation inside the publication boundary. The ordinary named-method path does this automatically.
|
|
129
|
+
For custom transport integrations, `begin(canonical_request)` returns a `PendingInvocation` with separate acquisition, cancellation, and acceptance. `exchange(canonical_request)` uses the same settlement engine. Content-aware pending invocations use `await accept_content()`; this keeps upload completion and cancellation inside the publication boundary. The ordinary named-method path does this automatically. Pending invocations retain selected bytes only until acceptance, cancellation, acquisition/finalization failure, or harness close. Failed finalization may be retried and resolves fresh bytes. Harness tracking is weak: dropping a pending invocation does not keep its prepared payload alive. Terminal handles also detach their resolver/uploader context, so retained handles do not retain the caller’s backing store. Returned references continue to resolve in caller-owned storage; cleanup never clears that persistent store.
|
|
130
130
|
|
|
131
131
|
## Generated models and provenance
|
|
132
132
|
|
|
@@ -144,3 +144,134 @@ uv run python -m build
|
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
Integration tests also use a matching sibling `../agent-hooks-protocol` checkout for shared fixtures and public synthetic certificates. The installed runtime uses its bundled schemas and does not need that checkout. No SDK API executes host tools on your behalf.
|
|
147
|
+
|
|
148
|
+
### Typed composed payloads
|
|
149
|
+
|
|
150
|
+
Generated facade constructors retain nested model types through schema
|
|
151
|
+
compositions. MCP connection `gaps` parameters accept lists of typed gap models,
|
|
152
|
+
and HTTP, SSE, stdio, and custom connection objects expose typed location and gap
|
|
153
|
+
attributes. `ModelVisibleItem` has composed content constructors with typed roles.
|
|
154
|
+
|
|
155
|
+
Facade objects are mappings with read-only attributes.
|
|
156
|
+
Optional attributes return `None` when absent; mapping membership still records
|
|
157
|
+
presence. Attributes conflicting with mapping methods use a trailing underscore
|
|
158
|
+
(such as `items_`), so `dict.items()` keeps working. Wire parsers return lossless mappings, not hydrated facade instances.
|
|
159
|
+
|
|
160
|
+
Use generated nested models for typed constructor arguments. Wire constructors, dictionary decoding, and runtime parsing
|
|
161
|
+
check location/evidence alternatives and other structural schema constraints. Application
|
|
162
|
+
JSON, native payloads, and extension values remain intentionally dynamic.
|
|
163
|
+
|
|
164
|
+
Upload receipts and event references are distinct. To attach a successful upload,
|
|
165
|
+
use `content.reference(receipt)` (or `{"ref": receipt["ref"]}`). Never attach the
|
|
166
|
+
receipt itself: body-selected content carries only the ref, with no outer `size`
|
|
167
|
+
or `sha256`. Metadata-only content and body gaps may still disclose size/digest.
|
|
168
|
+
|
|
169
|
+
### Structural model decoding and effect-family support
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from agenthooksprotocol import ContentReference, capability, effect
|
|
173
|
+
|
|
174
|
+
caps = capability.Capabilities.from_dict({"effects": ["deny", "vendor.custom"]})
|
|
175
|
+
assert caps.supports(capability.EffectName.DENY)
|
|
176
|
+
assert caps.supports("vendor.custom")
|
|
177
|
+
assert effect.EffectName is capability.EffectName
|
|
178
|
+
reference = ContentReference.from_dict({"ref": "opaque", "vendor": {"version": 2}})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`supports(effect)` is available on generic and incoming capability models. It
|
|
182
|
+
checks the advertised `effects` list only. Nested grants do not imply family
|
|
183
|
+
support. The result is **not authorization**: it does not check target, operation,
|
|
184
|
+
mode, contextual restrictions, or permission. `EffectName` contains schema-derived
|
|
185
|
+
known identifiers; custom family identifiers remain ordinary strings.
|
|
186
|
+
|
|
187
|
+
Wire-model keyword constructors and `Model.from_dict(mapping)` validate with
|
|
188
|
+
the same structural descriptor engine as generated `parse_*` functions. The
|
|
189
|
+
original composition is retained, including required members, explicit nulls,
|
|
190
|
+
literals, closed enums, forbidden combinations, and union ambiguity. This is the
|
|
191
|
+
SDK's structural contract, not complete contextual protocol validation.
|
|
192
|
+
`from_dict` does not supply omitted wire literals or defaults. It validates once
|
|
193
|
+
and privately hydrates nested typed models without recursively calling public
|
|
194
|
+
constructors. For example, a decoded HTTP connection supports
|
|
195
|
+
`connection.gaps[0].reason`, just like a constructed connection. Union selection
|
|
196
|
+
reuses memoized descriptor checks from that decode; subtrees are not repeatedly
|
|
197
|
+
validated during hydration. Extension/application values remain ordinary JSON
|
|
198
|
+
values. Descriptors and hydration metadata are cached.
|
|
199
|
+
|
|
200
|
+
Invalid wire models raise `ValueError`; validation failures carry `result`,
|
|
201
|
+
`diagnostics`, and `raw` attributes. Use `parse_*` when you need the full result on
|
|
202
|
+
both success and failure, including unknown-variant warnings and lossless raw
|
|
203
|
+
values. Open extension values remain intact. Finite `Decimal` values are retained;
|
|
204
|
+
non-finite numbers and non-JSON values are rejected. Parsing raw JSON through
|
|
205
|
+
`parse_*` retains decimal precision; decoding it first with standard-library
|
|
206
|
+
`json.loads` cannot recover precision already lost to a float.
|
|
207
|
+
|
|
208
|
+
Provide valid enum/literal values and all required wire members to `from_dict`. A null candidate, a candidate with
|
|
209
|
+
`{"value": null}`, and a candidate with `{"value": 0}` remain distinct; omitting a
|
|
210
|
+
required candidate is an error. Generated input projections and capability grant
|
|
211
|
+
builders are construction helpers, not wire decode entrypoints; their final wire
|
|
212
|
+
objects are validated at the dispatch/parse boundary. Direct dictionary
|
|
213
|
+
mutation, standard-library `json.loads`, TypedDict annotations, and type assertions
|
|
214
|
+
are not SDK validation entrypoints. Re-parse after manually mutating a mapping.
|
|
215
|
+
|
|
216
|
+
Wire constructors also privately hydrate nested mappings: for example,
|
|
217
|
+
`capability.Capabilities(effects=["modify"], modify={}).modify.input` returns
|
|
218
|
+
`None`, not an attribute error. Already typed child models retain their identity;
|
|
219
|
+
plain nested mappings become typed models after the constructor's single cached
|
|
220
|
+
validation pass. Family queries include all generated per-occurrence capability
|
|
221
|
+
models, such as `capability.ToolBeforeCapabilities`.
|
|
222
|
+
|
|
223
|
+
Constructor aliases cannot silently overwrite wire keys. Supplying both
|
|
224
|
+
`tool_name=` and `toolName=` raises `TypeError`, even if the values agree. For
|
|
225
|
+
optional/defaulted fields, a lone wire spelling (for example `addressForm=` or
|
|
226
|
+
`protocolVersion=`) is used and validated rather than replaced by a default.
|
|
227
|
+
Required constructor parameters use their documented Python spelling;
|
|
228
|
+
use `from_dict` to decode complete wire-keyed objects. In `from_dict`, distinct
|
|
229
|
+
keys such as `toolName` and `tool_name` remain distinct wire/extension keys.
|
|
230
|
+
|
|
231
|
+
### Owned attachments
|
|
232
|
+
|
|
233
|
+
The owned attachment APIs in this section are not included in the published 0.1.1 package.
|
|
234
|
+
|
|
235
|
+
Use `Attachment.from_bytes(data)` for immutable Python `bytes`, or
|
|
236
|
+
`Attachment.lazy(async_loader, aclose=async_cleanup)` to defer reading. Bind it
|
|
237
|
+
with the typed input's `bind_*_source` method on an item without a body reference.
|
|
238
|
+
Content IDs, media types, and other metadata belong on the content item. See the
|
|
239
|
+
runnable [file attachment example](examples/file_attachment.py).
|
|
240
|
+
|
|
241
|
+
Dispatch transfers ownership once. Metadata-only and unmatched deliveries do not
|
|
242
|
+
invoke the loader. Selected body deliveries share one immutable snapshot, with
|
|
243
|
+
receiver-specific references. Successful results own the bytes **or still-unread
|
|
244
|
+
loader**, independent of `Hooks` shutdown. Use `async with result:` or
|
|
245
|
+
`await result.aclose()` and read with
|
|
246
|
+
`await result.attachments.read("context.compact.before.items[0]")`. Cleanup runs
|
|
247
|
+
for unopened sources too. Do not reuse an attachment across invocations; create a
|
|
248
|
+
new attachment (which can share the same immutable `bytes`) instead.
|
|
249
|
+
|
|
250
|
+
The default accepted size is 4 MiB and lazy loading timeout is 30 seconds;
|
|
251
|
+
constructors accept `max_bytes` and lazy construction accepts `timeout`. Loaders
|
|
252
|
+
must bound their own allocations. `Attachment(async_stream, ...)` also supports
|
|
253
|
+
the bounded `read(size)`/`receive(max_bytes)` source contract. Stream cleanup
|
|
254
|
+
is shielded and bounded to one second. Cancellation and failure never retry a
|
|
255
|
+
loader. Python mutable buffers are rejected, and reads
|
|
256
|
+
return immutable bytes.
|
|
257
|
+
|
|
258
|
+
Remote body delivery requires an authorized receiver uploader through
|
|
259
|
+
`uploads={backend_id: async_upload}`. The uploader supplies a reference that the
|
|
260
|
+
receiver can read.
|
|
261
|
+
Metadata-only delivery and result reads require no uploader.
|
|
262
|
+
|
|
263
|
+
Attachments own their immutable bytes directly. A lazy loader's `bytes` object
|
|
264
|
+
becomes the attachment snapshot; upload and result reads borrow that same object.
|
|
265
|
+
Receiver applications manage upload storage.
|
|
266
|
+
|
|
267
|
+
Compaction text and elicitation JSON effects read selected attachment
|
|
268
|
+
owners. An accepted replacement creates a new effective attachment; subsequent
|
|
269
|
+
deliveries and the result use that owner. Original attachment size bounds also
|
|
270
|
+
apply to replacements. Generated candidates are available through
|
|
271
|
+
`await result.attachments.read("candidate")`. Binary/file editing is not supported.
|
|
272
|
+
Elicitation results require their original request correlation context;
|
|
273
|
+
`ContentContext.attachments` can provide the original request owner without a
|
|
274
|
+
resolver. A resolver is an optional
|
|
275
|
+
adapter for externally supplied references.
|
|
276
|
+
|
|
277
|
+
Results are invocation-local, not session archives.
|
|
@@ -15,6 +15,10 @@ from . import (
|
|
|
15
15
|
tool,
|
|
16
16
|
transport,
|
|
17
17
|
)
|
|
18
|
+
from .attachment import (
|
|
19
|
+
Attachment as Attachment,
|
|
20
|
+
AttachmentContents as AttachmentContents,
|
|
21
|
+
)
|
|
18
22
|
from ._content import ContentContext as ContentContext
|
|
19
23
|
from ._models import * # noqa: F403
|
|
20
24
|
from ._models import __all__ as _model_names
|
|
@@ -36,6 +40,8 @@ from .runtime import (
|
|
|
36
40
|
)
|
|
37
41
|
|
|
38
42
|
__all__ = list(_model_names) + [
|
|
43
|
+
"Attachment",
|
|
44
|
+
"AttachmentContents",
|
|
39
45
|
"ContentContext",
|
|
40
46
|
"OwnedContentSource",
|
|
41
47
|
"ContentSources",
|