Document processing cartridges: PDF, text, and the standard cap patterns they use.
Content cartridge architecture
Content cartridges are simpler than model cartridges:
- No peer calls; all processing is self-contained.
- No model loading or keepalive; operations complete quickly.
- Shorter handler implementations, typically under 100 lines each.
- Use standard cap URN helpers from
capdag::standard::caps.
The handler pattern is: receive input → process → emit output. No three-phase architecture, no FFI thread management.
Source: pdfcartridge, txtcartridge.
Standard caps
graph LR
PDF["media:document;ext=pdf"] -->|generate_thumbnail| IMG["media:ext=png;image"]
PDF -->|extract_metadata| REC["media:record"]
PDF -->|extract_outline| JSON["media:json"]
PDF -->|disbind| LIST["media:document;ext=pdf;list"]
TXT["media:enc=utf-8"] -->|generate_thumbnail| IMG2["media:ext=png;image"]
TXT -->|extract_metadata| REC2["media:record"]
MD["media:enc=utf-8;ext=md"] -->|extract_outline| JSON2["media:fmt=json;record"]
capdag::standard::caps defines cap URN helpers for common document operations:
| Function | Operation | Description |
|---|---|---|
generate_thumbnail_urn(media) |
generate;target=thumbnail |
Render a visual preview of the input. |
extract_metadata_urn(media) |
extract;target=metadata |
Extract structured metadata (title, author, page count, dates). |
extract_outline_urn(media) |
extract;target=outline |
Extract document outline or table of contents. |
disbind_urn(media) |
op=disbind |
Extract individual pages or sections as separate items. |
Each function takes a media URN (e.g., "media:document;ext=pdf") and returns the cap URN string for that operation on that media type. The cap URN includes the appropriate in= and out= specs.
Source: capdag-rs/src/standard/caps.rs.
pdfcartridge
Provides four caps for PDF documents:
generate_thumbnail
Renders PDF pages as PNG thumbnails. Arguments:
media:document;ext=pdf(required, stdin): The PDF document.media:width;enc=utf-8;numeric(optional): Output width in pixels.media:height;enc=utf-8;numeric(optional): Output height in pixels.media:index-range;enc=utf-8(optional): Which pages to render (e.g., “0-2” for first three pages).
extract_metadata
Extracts PDF metadata as JSON. Returns a record with title, author, subject, creator, creation date, modification date, page count, and other document properties.
Arguments: media:document;ext=pdf (required, stdin).
extract_outline
Extracts the PDF outline (bookmarks/table of contents) as a hierarchical JSON structure. Arguments:
media:document;ext=pdf(required, stdin).media:max-depth;enc=utf-8;numeric(optional): Maximum depth of outline traversal.media:include-order-indexes;enc=utf-8(optional): Whether to include ordering indexes.
disbind
Extracts individual PDF pages as separate items. Uses emit_list_item() to stream pages one at a time:
output.start(true, input_meta.clone())?;
for (i, page_bytes) in pages.iter().enumerate() {
output.progress(i as f32 / pages.len() as f32, &format!("Page {}", i + 1));
output.emit_list_item(&ciborium::Value::Bytes(page_bytes.to_vec()), None).await?;
}
Source: pdfcartridge/src/main.rs.
txtcartridge
Provides thumbnail, metadata, and outline extraction for text-based formats: plain text (.txt), reStructuredText (.rst), log files (.log), and Markdown (.md).
The same handler implementations are registered for all four media types. Outline extraction is available only for structured text formats (Markdown, reStructuredText) where headings define the document structure.
Multi-type handler pattern
A single handler serves multiple media types by trying each known URN:
fn require_text_content(streams: &[(String, Vec<u8>)]) -> Result<&[u8], StreamError> {
for urn in &[MEDIA_TXT, MEDIA_RST, MEDIA_LOG, MEDIA_MD] {
if let Some(bytes) = find_stream(streams, urn) {
return Ok(bytes);
}
}
Err(StreamError::Protocol("No recognized text stream found".to_string()))
}
Registration loops over all media types:
for media in &[MEDIA_TXT, MEDIA_RST, MEDIA_LOG, MEDIA_MD] {
runtime.register_op(&generate_thumbnail_urn(media), || Box::new(ThumbnailOp));
runtime.register_op(&extract_metadata_urn(media), || Box::new(MetadataOp));
}
// Outline only for structured formats
for media in &[MEDIA_MD, MEDIA_RST] {
runtime.register_op(&extract_outline_urn(media), || Box::new(OutlineOp));
}
Cap URN dispatch (see Dispatch) ensures that the incoming request matches exactly one of the registered media types. The handler’s fallback loop over URNs finds the matching stream.
Source: txtcartridge/src/main.rs.
Building a content cartridge
To add a new content cartridge for a hypothetical media type (e.g., EPUB):
-
Create the crate: Set up a Rust binary crate with
#[tokio::main]and dependencies oncapdagandops. - Define cap URNs: Use standard helpers or build custom ones:
let thumb_cap = make_cap(generate_thumbnail_urn("media:document;ext=epub")); let meta_cap = make_cap(extract_metadata_urn("media:document;ext=epub")); - Build the manifest: Include
identity_cap()and all your caps:let manifest = CapManifest::new( "epubcartridge".to_string(), "1.0.0".to_string(), "EPUB document processing".to_string(), vec![identity_cap(), thumb_cap, meta_cap], ); -
Implement handlers: Each handler implements
Op<()>. See Handler patterns for the standard patterns. - Register and run:
let mut runtime = CartridgeRuntime::with_manifest(manifest); runtime.register_op(&generate_thumbnail_urn("media:document;ext=epub"), || Box::new(ThumbnailOp)); runtime.register_op(&extract_metadata_urn("media:document;ext=epub"), || Box::new(MetadataOp)); runtime.run().await?;
Use pdfcartridge as a reference implementation; it demonstrates all four standard operations with clean handler code.