document
v0.1.10Convert Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV and PDF documents to markdown on this machine, detect the format from the bytes, pull out the images embedded in them, and transcribe a scan by rendering its pages and reading them with a vision model.
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64
exact versions are immutable; binary and bundle artifacts are digest-pinned.
full markdown
/workers/document.md?version=0.1.10. paste it into an llm prompt or pipe it through curl from a worker.install
dependencies
readme
document
Read office documents on the machine, with no conversion service and no API key. This worker takes a Word, PowerPoint, Excel, OpenDocument, RTF, EPUB or CSV file and returns markdown that keeps its headings, lists, tables and notes, in single-digit milliseconds for a typical document. It identifies a file from its bytes rather than trusting its name, so a mislabelled attachment still converts. And it hands back the images markdown cannot carry, which is what a deck of diagrams actually holds. Nothing is uploaded, and a long document is capped rather than dumped, so a report does not swallow the context an agent needed for the answer.
Install
iii trigger compose::add worker=documentReading a scanned document also needs something to turn its pages into pixels and something to read them, neither of which ships here:
iii trigger compose::add worker=browserWith browser installed and a vision model configured through
llm-router, document::ocr transcribes scans. Every other
function works without both.
Quickstart
use iii_sdk::{register_worker, InitOptions};
use iii_sdk::protocol::TriggerRequest;
use serde_json::json;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let iii = register_worker("ws://localhost:49134", InitOptions::default());
let markdown = iii.trigger(TriggerRequest {
function_id: "document::to-markdown".into(),
payload: json!({ "path": "/tmp/quarterly.docx" }),
action: None,
timeout_ms: Some(60_000),
}).await?;
// { "format": "docx", "family": "prose", "detected_from": "content",
// "body": { "text": "# Quarterly Notes\n…", "chars": 5693,
// "total_chars": 5693, "truncated": false },
// "asset_count": 0, "elapsed_ms": 4, … }
println!("{markdown:#?}");
Ok(())
}A document with no path goes in as bytes_base64 instead — the shape a composer
attachment takes. Add file_name with it: a CSV carries no signature of its
own, and without a name it cannot be recognised.
Formats
| Format | Extensions |
|---|---|
| Word | .doc, .docx, .docm |
| PowerPoint | .ppt, .pps, .pot, .pptx, .pptm, .ppsx, .ppsm |
| Excel | .xls, .xlsx, .xlsm, .xlsb |
| OpenDocument | .odt, .ods, .odp |
| Rich Text | .rtf |
| EPUB | .epub |
| CSV | .csv |
.pdf (text-based; see below) |
Container variants collapse onto one name: .docm is docx, .xlsb is
excel. A caller matches on the format, never on the extension it happened to
send.
PDFs
A text-based PDF converts here, which makes this worker a complete answer for a
mixed pile of attachments on its own. When the pdf worker is
installed it is the better route for them: it classifies scanned versus
text-based and names the individual pages that need OCR, where this worker can
only convert or fail.
Detect before you convert
document::detect reads the signature in the first bytes of a file and answers
in microseconds. It exists for the case where something arrives and nobody knows
what it is.
{
"format": "pptx",
"family": "presentation",
"detected_from": "content",
"convertible": true,
"has_assets": true,
"size_bytes": 184320,
"source": "roadmap.pptx",
"elapsed_ms": 0
}detected_from is the field worth reading. content means the bytes named the
format, which is the strong answer. extension means they did not, and only the
file name suggested it — expected for a CSV, and a reason for suspicion on
anything else. A format of null is an answer too: this is not a document
this worker reads, not a document that is broken.
The images markdown drops
Markdown renders an embedded image as its alt text. For prose that is right. For a deck built out of diagrams it throws away the content and leaves a page of titles, which reads as a document that had little to say.
document::to-markdown reports asset_count so that case is visible, and
document::extract-assets returns the bytes:
{
"format": "pptx",
"assets": [
{
"index": 0,
"media_type": "image/png",
"origin_part": "ppt/media/image1.png",
"size_bytes": 48211,
"bytes_base64": "iVBORw0KGgo…"
}
],
"total_count": 1,
"truncated": false
}Three ceilings apply, and all of them report what they dropped rather than
trimming silently. max_assets bounds how many come back. max_asset_bytes
bounds one payload, and max_assets_total_bytes bounds the response as a whole,
because two dozen assets each just under the per-asset limit still add up to a
quarter of a gigabyte once base64 inflates them. An asset left out either way is
still listed with its type and size, with omitted saying which ceiling it hit
(too_large or budget_spent), so a caller can ask for it on its own.
include_bytes: false inventories a document without moving anything.
Reading a scan
A scanned page holds no text to extract: the characters exist only in the
pixels. document::ocr renders those pages and reads them with a vision model.
{
"via": "pdf-render",
"body": { "text": "INVOICE 4471\nDue 30 June…", "chars": 812, "truncated": false },
"pages": [{ "page": 1, "text": "INVOICE 4471…", "chars": 812, "cached": false }],
"pages_transcribed": 1,
"pages_cached": 0,
"model": "claude-haiku-4-5"
}Three inputs, one answer. An image goes straight to the model. A PDF is
rendered a page at a time by the browser worker, which is the
only thing that turns a page into pixels. An office document whose text came
back empty has its embedded images pulled out and read the same way.
Both of those dependencies are soft. Neither is declared in
worker-compose.yaml, every other function works without them, and a call that
needs one it cannot reach says which to install. Someone who installed this
worker to read a .docx never pays for Chromium.
This is the one function here that costs money, so nothing runs it implicitly.
pdf::classify reports which pages are scans, and passing that list is the
difference between transcribing one page of a report and all four hundred:
{ "path": "/tmp/report.pdf", "pages": [1], "model": "claude-haiku-4-5" }The model is checked for vision support before anything is rendered, because a model that cannot see fails on the first page after the render has been paid for.
Page transcriptions cache in the state worker, keyed by the rendered PIXELS
and the model that read them. Keying on the image rather than the source
document is what makes the cache self-correcting: a page that rendered badly
hashes differently once the render is fixed, so bad entries fall out instead of
being served forever. A hit still re-renders — that is a second of local
Chromium — and skips the model call, which is the part that costs money. The
images themselves are never stored, and exist only in flight between the browser
and the model.
For scale: one rendered page of a text PDF measured about 1,400 input tokens on
claude-haiku-4-5, or roughly $0.0016 a page.
Rendering a PDF needs the file on disk (path, not bytes_base64) and the
browser worker allowed to open it: its Behavior settings carry an allowed
URL schemes list that ships as http, https, and a local PDF needs file
added. It hot-applies on save. That list is deliberately narrow — the browser
does not check a path against the session's filesystem scope the way this
worker does, so widening it widens what any caller can read.
Response caps
Every text-bearing response is capped and says so. truncated: true with a
total_chars far above chars means you are holding a fragment. max_chars: 0
takes the whole document, and belongs in a pipeline moving a document to
storage rather than in a call whose result lands in a conversation.
Configuration
Configuration lives in the configuration worker under the id document and
every field hot-reloads. Nothing here needs a restart.
max_input_bytes: 67108864 # largest document accepted, before parsing
max_chars: 40000 # default cap on returned markdown
preview_chars: 600 # leading characters shown alongside a capped body
max_assets: 24 # assets returned in one response
max_asset_bytes: 8388608 # largest single asset returned with its bytes
max_assets_total_bytes: 33554432 # total asset payload one response may carry
ocr_model: # vision model document::ocr reads with; unset = every call chooses
max_ocr_pages: 20 # pages one document::ocr call transcribes
ocr_timeout_ms: 120000 # budget for one render or one model read
ocr_render_settle_ms: 2000 # let a rendered page paint before capturing it
ocr_cache: true # cache page transcriptions in the state workerA per-call max_assets narrows this ceiling and cannot raise it: the limit
bounds one response, and a caller asking for a thousand images is the case it
exists for.
Defaults live in src/config.rs.
Called on demand
This worker registers no harness hook and injects nothing into any prompt. A
conversation that never touches a document never pays for it, and there is no
per-turn cost to having it installed. An agent finds it the ordinary way,
through the function registry and skills/SKILL.md.
What this worker does not do
It does not run OCR by itself. document::ocr renders and asks a model, which
means a scan costs money per page and needs a vision model configured. Nothing
transcribes implicitly.
It does not write documents. Conversion is one way, into markdown.
It cannot open an encrypted document. There is no password parameter, because there is nothing behind it that could decrypt one.
api reference (json)
{
"functions": [
{
"description": "Identify a document's file format from its bytes. Reports the family it belongs to and whether this worker can convert it, falling back to the file name for CSV, which carries no signature. Microseconds, and no conversion.",
"metadata": {},
"name": "document::detect",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"FsScope": {
"description": "The filesystem jail a call runs under; `path` is checked against it.",
"properties": {
"grants": {
"default": [],
"description": "Additional directories or files explicitly granted to this session.",
"items": {
"type": "string"
},
"type": "array"
},
"root": {
"description": "The session's working directory.",
"type": "string"
}
},
"required": [
"root"
],
"type": "object"
}
},
"description": "Where the document comes from. Exactly one of `path` and `bytes_base64` must be set.",
"properties": {
"bytes_base64": {
"default": null,
"description": "Base64-encoded document bytes, for a document with no path — an attachment held in memory. Mutually exclusive with `path`.",
"type": [
"string",
"null"
]
},
"file_name": {
"default": null,
"description": "Original file name for `bytes_base64`, used only to recognise a format the content cannot name (`.csv`). Ignored when `path` is set.",
"type": [
"string",
"null"
]
},
"fs_scope": {
"anyOf": [
{
"$ref": "#/definitions/FsScope"
},
{
"type": "null"
}
],
"description": "Filesystem jail for this call. Stamped by the harness on an agent's call; absent on an operator or console call."
},
"path": {
"default": null,
"description": "Filesystem path to the document. Mutually exclusive with `bytes_base64`.",
"type": [
"string",
"null"
]
}
},
"title": "Request",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"DetectedFrom": {
"description": "How the format was arrived at, weakest claim last.",
"oneOf": [
{
"description": "The caller named it, and the bytes were not consulted.",
"enum": [
"requested"
],
"type": "string"
},
{
"description": "The signature the format's specification designates (PDF header, RTF open group, OLE stream names, ZIP package mimetype).",
"enum": [
"content"
],
"type": "string"
},
{
"description": "The file extension only. CSV carries no signature, so this is the only way it is ever recognised; for any other format it means the content did not match anything known.",
"enum": [
"extension"
],
"type": "string"
}
]
},
"Family": {
"description": "What the document is, rather than which program wrote it.\n\nA caller routing a mixed bag of attachments cares that a file is a spreadsheet, not that it is `.ods` rather than `.xlsx`.",
"oneOf": [
{
"description": "Prose: Word, OpenDocument Text, RTF.",
"enum": [
"prose"
],
"type": "string"
},
{
"description": "Rows and columns: Excel, OpenDocument Spreadsheet, CSV.",
"enum": [
"spreadsheet"
],
"type": "string"
},
{
"description": "Slides: PowerPoint, OpenDocument Presentation.",
"enum": [
"presentation"
],
"type": "string"
},
{
"description": "A book: EPUB.",
"enum": [
"book"
],
"type": "string"
},
{
"description": "PDF, which is its own family because it is the one format with a dedicated worker and a page-level OCR decision.",
"enum": [
"pdf"
],
"type": "string"
}
]
},
"Format": {
"description": "A format this worker converts. Names are independent of the file extension that named them (`.docm` is `docx`, `.xlsb` is `excel`).",
"oneOf": [
{
"description": "Binary Word 97-2003 (`.doc`).",
"enum": [
"doc"
],
"type": "string"
},
{
"description": "WordprocessingML (`.docx`, `.docm`).",
"enum": [
"docx"
],
"type": "string"
},
{
"description": "OpenDocument Text (`.odt`).",
"enum": [
"odt"
],
"type": "string"
},
{
"description": "Rich Text Format (`.rtf`).",
"enum": [
"rtf"
],
"type": "string"
},
{
"description": "Binary PowerPoint 97-2003 (`.ppt`, `.pps`, `.pot`).",
"enum": [
"ppt"
],
"type": "string"
},
{
"description": "PresentationML (`.pptx`, `.pptm`, `.ppsx`, `.ppsm`).",
"enum": [
"pptx"
],
"type": "string"
},
{
"description": "OpenDocument Presentation (`.odp`).",
"enum": [
"odp"
],
"type": "string"
},
{
"description": "Excel workbooks in every container (`.xlsx`, `.xlsm`, `.xlsb`, `.xls`).",
"enum": [
"excel"
],
"type": "string"
},
{
"description": "OpenDocument Spreadsheet (`.ods`).",
"enum": [
"ods"
],
"type": "string"
},
{
"description": "Delimiter-separated text (`.csv`).",
"enum": [
"csv"
],
"type": "string"
},
{
"description": "EPUB 2 and 3 (`.epub`).",
"enum": [
"epub"
],
"type": "string"
},
{
"description": "Portable Document Format (`.pdf`).",
"enum": [
"pdf"
],
"type": "string"
}
]
}
},
"properties": {
"convertible": {
"description": "`true` when `document::to-markdown` can convert this file.",
"type": "boolean"
},
"detected_from": {
"anyOf": [
{
"$ref": "#/definitions/DetectedFrom"
},
{
"type": "null"
}
],
"description": "How the format was arrived at. `extension` is the weaker claim: the content matched nothing known, and only the file name suggested this."
},
"elapsed_ms": {
"description": "Wall-clock time for the detection.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"family": {
"anyOf": [
{
"$ref": "#/definitions/Family"
},
{
"type": "null"
}
],
"description": "What the document is: prose, a spreadsheet, a presentation, a book, a PDF. Absent when the format is unknown."
},
"format": {
"anyOf": [
{
"$ref": "#/definitions/Format"
},
{
"type": "null"
}
],
"description": "The format, or `null` when nothing recognised it. A null means the file is not one of the formats this worker reads — an image, an archive, a plain text file — not that it is broken."
},
"has_assets": {
"description": "`true` when the format can carry embedded assets for `document::extract-assets` to pull out. False for a PDF, which converts straight to markdown without a document model, and for a CSV, which is rows of text with nowhere to put a picture. A caller routing on this should not spend a call to be told a spreadsheet has no images.",
"type": "boolean"
},
"size_bytes": {
"description": "Size of the document in bytes.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"source": {
"description": "Source label: the file name, or `<inline>` for an in-memory document that arrived without one.",
"type": "string"
}
},
"required": [
"convertible",
"elapsed_ms",
"has_assets",
"size_bytes",
"source"
],
"title": "Response",
"type": "object"
}
},
{
"description": "Extract the images and embedded objects from a document as base64, for a deck or report whose content is pictures rather than text. Capped per response and per asset; anything left out is listed with its type and size. Not available for PDFs — use pdf::extract-regions.",
"metadata": {},
"name": "document::extract-assets",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Format": {
"description": "A format this worker converts. Names are independent of the file extension that named them (`.docm` is `docx`, `.xlsb` is `excel`).",
"oneOf": [
{
"description": "Binary Word 97-2003 (`.doc`).",
"enum": [
"doc"
],
"type": "string"
},
{
"description": "WordprocessingML (`.docx`, `.docm`).",
"enum": [
"docx"
],
"type": "string"
},
{
"description": "OpenDocument Text (`.odt`).",
"enum": [
"odt"
],
"type": "string"
},
{
"description": "Rich Text Format (`.rtf`).",
"enum": [
"rtf"
],
"type": "string"
},
{
"description": "Binary PowerPoint 97-2003 (`.ppt`, `.pps`, `.pot`).",
"enum": [
"ppt"
],
"type": "string"
},
{
"description": "PresentationML (`.pptx`, `.pptm`, `.ppsx`, `.ppsm`).",
"enum": [
"pptx"
],
"type": "string"
},
{
"description": "OpenDocument Presentation (`.odp`).",
"enum": [
"odp"
],
"type": "string"
},
{
"description": "Excel workbooks in every container (`.xlsx`, `.xlsm`, `.xlsb`, `.xls`).",
"enum": [
"excel"
],
"type": "string"
},
{
"description": "OpenDocument Spreadsheet (`.ods`).",
"enum": [
"ods"
],
"type": "string"
},
{
"description": "Delimiter-separated text (`.csv`).",
"enum": [
"csv"
],
"type": "string"
},
{
"description": "EPUB 2 and 3 (`.epub`).",
"enum": [
"epub"
],
"type": "string"
},
{
"description": "Portable Document Format (`.pdf`).",
"enum": [
"pdf"
],
"type": "string"
}
]
},
"FsScope": {
"description": "The filesystem jail a call runs under; `path` is checked against it.",
"properties": {
"grants": {
"default": [],
"description": "Additional directories or files explicitly granted to this session.",
"items": {
"type": "string"
},
"type": "array"
},
"root": {
"description": "The session's working directory.",
"type": "string"
}
},
"required": [
"root"
],
"type": "object"
}
},
"description": "Where the document comes from. Exactly one of `path` and `bytes_base64` must be set.",
"properties": {
"bytes_base64": {
"default": null,
"description": "Base64-encoded document bytes, for a document with no path — an attachment held in memory. Mutually exclusive with `path`.",
"type": [
"string",
"null"
]
},
"file_name": {
"default": null,
"description": "Original file name for `bytes_base64`, used only to recognise a format the content cannot name (`.csv`). Ignored when `path` is set.",
"type": [
"string",
"null"
]
},
"format": {
"anyOf": [
{
"$ref": "#/definitions/Format"
},
{
"type": "null"
}
],
"default": null,
"description": "Force a format instead of detecting one."
},
"fs_scope": {
"anyOf": [
{
"$ref": "#/definitions/FsScope"
},
{
"type": "null"
}
],
"description": "Filesystem jail for this call. Stamped by the harness on an agent's call; absent on an operator or console call."
},
"include_bytes": {
"default": true,
"description": "Include the base64 payload. Set `false` to inventory a document — what it holds and how big — without moving the bytes.",
"type": "boolean"
},
"max_assets": {
"default": null,
"description": "Assets to return in this response. Narrows the configured ceiling; it cannot raise it.",
"format": "uint",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"media_type_prefix": {
"default": null,
"description": "Return only assets whose media type starts with this, e.g. `image/`. Omit for every asset.",
"type": [
"string",
"null"
]
},
"path": {
"default": null,
"description": "Filesystem path to the document. Mutually exclusive with `bytes_base64`.",
"type": [
"string",
"null"
]
}
},
"title": "Request",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Asset": {
"description": "One embedded asset. `bytes_base64` is absent when the caller asked for an inventory, or when this asset is over the per-asset ceiling — `omitted` says which.",
"properties": {
"bytes_base64": {
"description": "The payload, base64-encoded.",
"type": [
"string",
"null"
]
},
"index": {
"description": "Position in the document's asset list, stable for a given document.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"media_type": {
"description": "MIME type, e.g. `image/png`.",
"type": "string"
},
"omitted": {
"description": "Why the payload is absent, when it is: `not_requested`, `too_large` (this asset alone is over the per-asset ceiling), or `budget_spent` (the response's total byte budget went on earlier assets — ask for this one on its own).",
"type": [
"string",
"null"
]
},
"origin_part": {
"description": "The package part or stream it came from, for provenance.",
"type": "string"
},
"size_bytes": {
"description": "Size of the payload in bytes, whether or not the payload is included.",
"format": "uint64",
"minimum": 0,
"type": "integer"
}
},
"required": [
"index",
"media_type",
"origin_part",
"size_bytes"
],
"type": "object"
},
"Format": {
"description": "A format this worker converts. Names are independent of the file extension that named them (`.docm` is `docx`, `.xlsb` is `excel`).",
"oneOf": [
{
"description": "Binary Word 97-2003 (`.doc`).",
"enum": [
"doc"
],
"type": "string"
},
{
"description": "WordprocessingML (`.docx`, `.docm`).",
"enum": [
"docx"
],
"type": "string"
},
{
"description": "OpenDocument Text (`.odt`).",
"enum": [
"odt"
],
"type": "string"
},
{
"description": "Rich Text Format (`.rtf`).",
"enum": [
"rtf"
],
"type": "string"
},
{
"description": "Binary PowerPoint 97-2003 (`.ppt`, `.pps`, `.pot`).",
"enum": [
"ppt"
],
"type": "string"
},
{
"description": "PresentationML (`.pptx`, `.pptm`, `.ppsx`, `.ppsm`).",
"enum": [
"pptx"
],
"type": "string"
},
{
"description": "OpenDocument Presentation (`.odp`).",
"enum": [
"odp"
],
"type": "string"
},
{
"description": "Excel workbooks in every container (`.xlsx`, `.xlsm`, `.xlsb`, `.xls`).",
"enum": [
"excel"
],
"type": "string"
},
{
"description": "OpenDocument Spreadsheet (`.ods`).",
"enum": [
"ods"
],
"type": "string"
},
{
"description": "Delimiter-separated text (`.csv`).",
"enum": [
"csv"
],
"type": "string"
},
{
"description": "EPUB 2 and 3 (`.epub`).",
"enum": [
"epub"
],
"type": "string"
},
{
"description": "Portable Document Format (`.pdf`).",
"enum": [
"pdf"
],
"type": "string"
}
]
}
},
"properties": {
"assets": {
"description": "The assets, in document order, up to the effective ceiling.",
"items": {
"$ref": "#/definitions/Asset"
},
"type": "array"
},
"elapsed_ms": {
"description": "Wall-clock time for the extraction.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"format": {
"allOf": [
{
"$ref": "#/definitions/Format"
}
],
"description": "The format that was parsed."
},
"source": {
"description": "Source label: the file name, or `<inline>` for an in-memory document.",
"type": "string"
},
"total_count": {
"description": "Assets the document holds after `media_type_prefix` is applied. Larger than `assets.len()` when the ceiling cut the response short.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"truncated": {
"description": "`true` when the ceiling cut the response short.",
"type": "boolean"
}
},
"required": [
"assets",
"elapsed_ms",
"format",
"source",
"total_count",
"truncated"
],
"title": "Response",
"type": "object"
}
},
{
"description": "Transcribe a scanned document with no readable text: a scanned PDF, a photographed page, a picture-only deck. Renders the pages and reads them with a vision model. Costs money per page: pass `pages` (pdf::classify) to narrow it.",
"metadata": {},
"name": "document::ocr",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"FsScope": {
"description": "The filesystem jail a call runs under; `path` is checked against it.",
"properties": {
"grants": {
"default": [],
"description": "Additional directories or files explicitly granted to this session.",
"items": {
"type": "string"
},
"type": "array"
},
"root": {
"description": "The session's working directory.",
"type": "string"
}
},
"required": [
"root"
],
"type": "object"
}
},
"description": "Where the document comes from. Exactly one of `path` and `bytes_base64` must be set.",
"properties": {
"bytes_base64": {
"default": null,
"description": "Base64-encoded document bytes, for a document with no path — an attachment held in memory. Mutually exclusive with `path`.",
"type": [
"string",
"null"
]
},
"file_name": {
"default": null,
"description": "Original file name for `bytes_base64`, used only to recognise a format the content cannot name (`.csv`). Ignored when `path` is set.",
"type": [
"string",
"null"
]
},
"fs_scope": {
"anyOf": [
{
"$ref": "#/definitions/FsScope"
},
{
"type": "null"
}
],
"description": "Filesystem jail for this call. Stamped by the harness on an agent's call; absent on an operator or console call."
},
"max_chars": {
"default": null,
"description": "Characters to return before truncating. Omit for the configured default; `0` returns everything transcribed.",
"format": "uint",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"model": {
"default": null,
"description": "Vision model to read with. Omit for the configured default. The model is checked for vision support before anything is rendered.",
"type": [
"string",
"null"
]
},
"pages": {
"default": null,
"description": "1-indexed PDF pages to transcribe; omit for every page up to the configured ceiling. Pass the scan pages `pdf::classify` reports.",
"items": {
"format": "uint32",
"minimum": 0,
"type": "integer"
},
"type": [
"array",
"null"
]
},
"path": {
"default": null,
"description": "Filesystem path to the document. Mutually exclusive with `bytes_base64`.",
"type": [
"string",
"null"
]
}
},
"title": "Request",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Body": {
"description": "A body that may have been shortened to fit one response, and the numbers a caller needs to decide what to do about it.\n\nThe cap is what keeps a long document from flooding a model's context. A caller that genuinely wants the whole thing asks for `max_chars: 0`, which is the shape a worker-to-worker pipeline uses to move a document without it passing through anyone's context. Same shape the pdf worker returns, so a caller handling both reads one field set.",
"properties": {
"chars": {
"description": "Characters returned in `text`.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"preview": {
"description": "Leading characters of the content. Present only when the body was truncated, so a caller can see the shape of what it did not get without re-reading the start of `text`.",
"type": [
"string",
"null"
]
},
"text": {
"description": "The markdown, shortened to the effective character cap.",
"type": "string"
},
"total_chars": {
"description": "Characters the document actually holds. Equal to `chars` when nothing was dropped.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"truncated": {
"description": "`true` when `text` stops short of the document. Ask again with `max_chars: 0` to take everything.",
"type": "boolean"
}
},
"required": [
"chars",
"text",
"total_chars",
"truncated"
],
"type": "object"
},
"PageText": {
"description": "What one page turned into.",
"properties": {
"cached": {
"description": "`true` when this page came from the cache rather than the model.",
"type": "boolean"
},
"chars": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"page": {
"description": "1-indexed page number, or the asset's index for an office document.",
"format": "uint32",
"minimum": 0,
"type": "integer"
},
"text": {
"description": "The transcription. Empty when the page held no legible text.",
"type": "string"
}
},
"required": [
"cached",
"chars",
"page",
"text"
],
"type": "object"
}
},
"properties": {
"body": {
"allOf": [
{
"$ref": "#/definitions/Body"
}
],
"description": "The joined transcription, capped per `max_chars`."
},
"elapsed_ms": {
"description": "Wall-clock time, rendering included.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"model": {
"description": "The model that read them.",
"type": "string"
},
"pages": {
"description": "Per-page transcriptions, in order.",
"items": {
"$ref": "#/definitions/PageText"
},
"type": "array"
},
"pages_cached": {
"description": "Pages served from the cache, costing nothing.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"pages_transcribed": {
"description": "Pages actually read by the model this call. Excludes cache hits, so this is what was paid for.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"source": {
"description": "Source label: the file name, or `<inline>` for an in-memory document.",
"type": "string"
},
"via": {
"description": "How the pixels were obtained: `image`, `pdf-render` or `document-assets`.",
"type": "string"
}
},
"required": [
"body",
"elapsed_ms",
"model",
"pages",
"pages_cached",
"pages_transcribed",
"source",
"via"
],
"title": "Response",
"type": "object"
}
},
{
"description": "Internal: hot-reload the document worker from the authoritative configuration when it changes, swapping the per-call snapshot.",
"metadata": {
"internal": true
},
"name": "document::on-config-change",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Payload of the internal config-change handler. The handler re-fetches the authoritative value, so this carries only the advisory id; a struct rather than a `Value` keeps the request schema concrete.",
"properties": {
"id": {
"default": null,
"description": "Configuration id that changed (advisory; the handler re-fetches).",
"type": [
"string",
"null"
]
}
},
"title": "OnConfigChangeEvent",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Ack returned by the internal config-change handler.",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
],
"title": "OnConfigChangeResponse",
"type": "object"
}
},
{
"description": "Convert a Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV or PDF document to markdown, preserving headings, lists, links and tables. Responses are capped; pass max_chars 0 for the whole document. For a PDF run pdf::classify first to find pages needing OCR.",
"metadata": {},
"name": "document::to-markdown",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Format": {
"description": "A format this worker converts. Names are independent of the file extension that named them (`.docm` is `docx`, `.xlsb` is `excel`).",
"oneOf": [
{
"description": "Binary Word 97-2003 (`.doc`).",
"enum": [
"doc"
],
"type": "string"
},
{
"description": "WordprocessingML (`.docx`, `.docm`).",
"enum": [
"docx"
],
"type": "string"
},
{
"description": "OpenDocument Text (`.odt`).",
"enum": [
"odt"
],
"type": "string"
},
{
"description": "Rich Text Format (`.rtf`).",
"enum": [
"rtf"
],
"type": "string"
},
{
"description": "Binary PowerPoint 97-2003 (`.ppt`, `.pps`, `.pot`).",
"enum": [
"ppt"
],
"type": "string"
},
{
"description": "PresentationML (`.pptx`, `.pptm`, `.ppsx`, `.ppsm`).",
"enum": [
"pptx"
],
"type": "string"
},
{
"description": "OpenDocument Presentation (`.odp`).",
"enum": [
"odp"
],
"type": "string"
},
{
"description": "Excel workbooks in every container (`.xlsx`, `.xlsm`, `.xlsb`, `.xls`).",
"enum": [
"excel"
],
"type": "string"
},
{
"description": "OpenDocument Spreadsheet (`.ods`).",
"enum": [
"ods"
],
"type": "string"
},
{
"description": "Delimiter-separated text (`.csv`).",
"enum": [
"csv"
],
"type": "string"
},
{
"description": "EPUB 2 and 3 (`.epub`).",
"enum": [
"epub"
],
"type": "string"
},
{
"description": "Portable Document Format (`.pdf`).",
"enum": [
"pdf"
],
"type": "string"
}
]
},
"FsScope": {
"description": "The filesystem jail a call runs under; `path` is checked against it.",
"properties": {
"grants": {
"default": [],
"description": "Additional directories or files explicitly granted to this session.",
"items": {
"type": "string"
},
"type": "array"
},
"root": {
"description": "The session's working directory.",
"type": "string"
}
},
"required": [
"root"
],
"type": "object"
}
},
"description": "Where the document comes from. Exactly one of `path` and `bytes_base64` must be set.",
"properties": {
"bytes_base64": {
"default": null,
"description": "Base64-encoded document bytes, for a document with no path — an attachment held in memory. Mutually exclusive with `path`.",
"type": [
"string",
"null"
]
},
"file_name": {
"default": null,
"description": "Original file name for `bytes_base64`, used only to recognise a format the content cannot name (`.csv`). Ignored when `path` is set.",
"type": [
"string",
"null"
]
},
"format": {
"anyOf": [
{
"$ref": "#/definitions/Format"
},
{
"type": "null"
}
],
"default": null,
"description": "Force a format instead of detecting one. Only needed when the content carries no signature and the file name is absent or wrong."
},
"fs_scope": {
"anyOf": [
{
"$ref": "#/definitions/FsScope"
},
{
"type": "null"
}
],
"description": "Filesystem jail for this call. Stamped by the harness on an agent's call; absent on an operator or console call."
},
"max_chars": {
"default": null,
"description": "Characters to return before truncating. Omit for the configured default; `0` returns the whole document.",
"format": "uint",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"path": {
"default": null,
"description": "Filesystem path to the document. Mutually exclusive with `bytes_base64`.",
"type": [
"string",
"null"
]
}
},
"title": "Request",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Body": {
"description": "A body that may have been shortened to fit one response, and the numbers a caller needs to decide what to do about it.\n\nThe cap is what keeps a long document from flooding a model's context. A caller that genuinely wants the whole thing asks for `max_chars: 0`, which is the shape a worker-to-worker pipeline uses to move a document without it passing through anyone's context. Same shape the pdf worker returns, so a caller handling both reads one field set.",
"properties": {
"chars": {
"description": "Characters returned in `text`.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"preview": {
"description": "Leading characters of the content. Present only when the body was truncated, so a caller can see the shape of what it did not get without re-reading the start of `text`.",
"type": [
"string",
"null"
]
},
"text": {
"description": "The markdown, shortened to the effective character cap.",
"type": "string"
},
"total_chars": {
"description": "Characters the document actually holds. Equal to `chars` when nothing was dropped.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"truncated": {
"description": "`true` when `text` stops short of the document. Ask again with `max_chars: 0` to take everything.",
"type": "boolean"
}
},
"required": [
"chars",
"text",
"total_chars",
"truncated"
],
"type": "object"
},
"DetectedFrom": {
"description": "How the format was arrived at, weakest claim last.",
"oneOf": [
{
"description": "The caller named it, and the bytes were not consulted.",
"enum": [
"requested"
],
"type": "string"
},
{
"description": "The signature the format's specification designates (PDF header, RTF open group, OLE stream names, ZIP package mimetype).",
"enum": [
"content"
],
"type": "string"
},
{
"description": "The file extension only. CSV carries no signature, so this is the only way it is ever recognised; for any other format it means the content did not match anything known.",
"enum": [
"extension"
],
"type": "string"
}
]
},
"Family": {
"description": "What the document is, rather than which program wrote it.\n\nA caller routing a mixed bag of attachments cares that a file is a spreadsheet, not that it is `.ods` rather than `.xlsx`.",
"oneOf": [
{
"description": "Prose: Word, OpenDocument Text, RTF.",
"enum": [
"prose"
],
"type": "string"
},
{
"description": "Rows and columns: Excel, OpenDocument Spreadsheet, CSV.",
"enum": [
"spreadsheet"
],
"type": "string"
},
{
"description": "Slides: PowerPoint, OpenDocument Presentation.",
"enum": [
"presentation"
],
"type": "string"
},
{
"description": "A book: EPUB.",
"enum": [
"book"
],
"type": "string"
},
{
"description": "PDF, which is its own family because it is the one format with a dedicated worker and a page-level OCR decision.",
"enum": [
"pdf"
],
"type": "string"
}
]
},
"Format": {
"description": "A format this worker converts. Names are independent of the file extension that named them (`.docm` is `docx`, `.xlsb` is `excel`).",
"oneOf": [
{
"description": "Binary Word 97-2003 (`.doc`).",
"enum": [
"doc"
],
"type": "string"
},
{
"description": "WordprocessingML (`.docx`, `.docm`).",
"enum": [
"docx"
],
"type": "string"
},
{
"description": "OpenDocument Text (`.odt`).",
"enum": [
"odt"
],
"type": "string"
},
{
"description": "Rich Text Format (`.rtf`).",
"enum": [
"rtf"
],
"type": "string"
},
{
"description": "Binary PowerPoint 97-2003 (`.ppt`, `.pps`, `.pot`).",
"enum": [
"ppt"
],
"type": "string"
},
{
"description": "PresentationML (`.pptx`, `.pptm`, `.ppsx`, `.ppsm`).",
"enum": [
"pptx"
],
"type": "string"
},
{
"description": "OpenDocument Presentation (`.odp`).",
"enum": [
"odp"
],
"type": "string"
},
{
"description": "Excel workbooks in every container (`.xlsx`, `.xlsm`, `.xlsb`, `.xls`).",
"enum": [
"excel"
],
"type": "string"
},
{
"description": "OpenDocument Spreadsheet (`.ods`).",
"enum": [
"ods"
],
"type": "string"
},
{
"description": "Delimiter-separated text (`.csv`).",
"enum": [
"csv"
],
"type": "string"
},
{
"description": "EPUB 2 and 3 (`.epub`).",
"enum": [
"epub"
],
"type": "string"
},
{
"description": "Portable Document Format (`.pdf`).",
"enum": [
"pdf"
],
"type": "string"
}
]
}
},
"properties": {
"asset_count": {
"description": "Embedded images and objects the document carries. Their bytes are not here — call `document::extract-assets` for those — but the count says whether a deck's content is pictures rather than text, which markdown alone would not reveal.",
"format": "uint",
"minimum": 0,
"type": "integer"
},
"body": {
"allOf": [
{
"$ref": "#/definitions/Body"
}
],
"description": "The markdown, capped per `max_chars`."
},
"detected_from": {
"allOf": [
{
"$ref": "#/definitions/DetectedFrom"
}
],
"description": "How the format was arrived at."
},
"elapsed_ms": {
"description": "Wall-clock time for the conversion.",
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"family": {
"allOf": [
{
"$ref": "#/definitions/Family"
}
],
"description": "What the document is: prose, a spreadsheet, a presentation, a book, a PDF."
},
"format": {
"allOf": [
{
"$ref": "#/definitions/Format"
}
],
"description": "The format that was converted."
},
"source": {
"description": "Source label: the file name, or `<inline>` for an in-memory document.",
"type": "string"
}
},
"required": [
"asset_count",
"body",
"detected_from",
"elapsed_ms",
"family",
"format",
"source"
],
"title": "Response",
"type": "object"
}
}
],
"triggers": []
}