tabstack
v0.1.1Browsing infrastructure for AI agents on the iii bus — tabstack::* functions turn any URL into clean markdown or structured JSON, and run cited web research through the hosted Tabstack API.
Install
iii trigger compose::add worker=tabstack@0.1.1Run in your project with the iii engine running. Exact versions are immutable and digest-pinned.
Full markdown
/workers/tabstack.md?version=0.1.1. Paste it into an LLM prompt or pipe it through curl from a worker.Install
iii trigger compose::add worker=tabstack@0.1.1Dependencies
Readme
Tabstack
Tabstack reads the sources, checks what is missing, reconciles conflicting information, and returns a cited answer in one call. The same API also returns clean text, matching JSON, or a completed public web task.
Backed by the hosted Tabstack API, built by Mozilla. You bring a Tabstack API key; usage is billed to your own account. Full endpoint semantics live in the Tabstack API reference.
Install
iii trigger compose::add worker=tabstackiii trigger compose::add resolves the worker and its dependencies, writes exact
declarations to worker-compose.yaml, and reconciles the Compose project.
Configuration
The worker needs a Tabstack API key. Sign up free — the trial needs no card.
It registers a schema and seed in the configuration worker on boot, so the key
can be set from the Console or with configuration::set:
tabstack:
api_key: your-api-key # required; no default
base_url: https://api.tabstack.ai/v1 # override only for testingIf no key is stored, the worker falls back to the TABSTACK_API_KEY environment
variable. With neither set, every call fails with tabstack_not_configured.
Note the configuration entry is namespaced: in the default namespace the id is
default-tabstack, not tabstack.
Quickstart
Read a page as markdown:
iii trigger tabstack::extract-markdown url=https://example.com{
"ok": true,
"url": "https://example.com",
"content": "---\ntitle: Example Domain\n---\n\nThis domain is for use in documentation examples..."
}Pull structured data out of a page by describing the shape you want:
iii trigger tabstack::extract-json url=https://news.ycombinator.com \
json_schema='{"type":"object","properties":{"stories":{"type":"array","items":{"type":"object","properties":{"rank":{"type":"integer"},"title":{"type":"string"},"points":{"type":"integer"}}},"maxItems":5}}}'{
"ok": true,
"data": {
"stories": [
{ "rank": 1, "title": "Web Search API", "points": 318 },
{ "rank": 2, "title": "The future of independence is interdependence", "points": 41 }
]
}
}From another worker:
import { registerWorker } from 'iii-sdk';
const iii = registerWorker('ws://localhost:49134', { workerName: 'my-worker' });
const result = await iii.trigger({
function_id: 'tabstack::extract-markdown',
payload: { url: 'https://example.com' },
timeoutMs: 60_000,
});Raise timeoutMs. iii's default is 30 seconds. extract-markdown and
extract-json usually return well inside that, but effort: "max" renders the
page in a full browser and is documented at up to 60 seconds. A caller on the
default timeout will give up before the work finishes.
The functions
| Function | Takes | Returns |
|---|---|---|
tabstack::extract-markdown |
url |
the page as markdown in content |
tabstack::extract-json |
url, json_schema |
matching object in data |
tabstack::generate-json |
url, json_schema, instructions |
matching object in data |
tabstack::research |
query |
a cited markdown answer in report |
See the Tabstack API reference for more details about each function.
Researching rather than reading
research is the one function that does not take a url. Give it a question and
it searches, reads and synthesises an answer with inline citations:
iii trigger tabstack::research query='What is the iii.dev worker registry?' timeout_ms=180000{
"ok": true,
"report": "The iii.dev worker registry is a central online repository located at `workers.iii.dev` ... [1][2]",
"metadata": {
"citedPages": [{ "id": "5YI5Rc", "title": "iii worker - iii.dev", "url": "https://workers.iii.dev/" }],
"mode": "fast",
"totalPagesAnalyzed": 3
}
}Choosing between extract-json and generate-json
extract-json takes a schema alone and is the cheaper of the two. Reach for it
whenever the schema says enough about what you want.
generate-json additionally takes natural-language instructions, for when the
schema alone cannot express how values should be derived — ranking, classifying,
or comparing across a whole page. "Pick the dominant theme across these stories"
needs an instruction; a bare schema cannot say it.
Reading the result
Every function returns an envelope. On success, ok is true and the result
sits alongside it. When Tabstack returns an error, ok is false and the call
still succeeds at the iii level:
{ "ok": false, "error": "tabstack_invalid_request", "message": "invalid URL" }error |
Meaning |
|---|---|
tabstack_auth_failed |
key missing, wrong or revoked |
tabstack_out_of_credits |
account balance exhausted |
tabstack_rate_limited |
too many requests; back off and retry |
tabstack_not_found |
endpoint not found |
tabstack_invalid_request |
bad URL, malformed schema, or similar |
tabstack_upstream_error |
Tabstack-side failure; safe to retry |
Failures are returned rather than thrown so callers can branch on error. The
Node SDK collapses every thrown handler error to invocation_failed, which would
make these indistinguishable.
Two cases are thrown, because no useful result exists: tabstack_not_configured
when no API key is available, and tabstack_unreachable / tabstack_timeout when
the request never reached Tabstack at all.
These codes map onto the HTTP statuses in the Tabstack API reference, which is the authority on what each endpoint can return.