# 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](https://tabstack.ai) 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](https://docs.tabstack.ai/api).

## Install

```bash
iii trigger compose::add worker=tabstack
```

`iii 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](https://console.tabstack.ai/signup?source=iii) — 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`:

```yaml
tabstack:
  api_key: your-api-key                 # required; no default
  base_url: https://api.tabstack.ai/v1  # override only for testing
```

If 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:

```bash
iii trigger tabstack::extract-markdown url=https://example.com
```

```json
{
  "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:

```bash
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}}}'
```

```json
{
  "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:

```ts
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](https://docs.tabstack.ai/api) 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:

```bash
iii trigger tabstack::research query='What is the iii.dev worker registry?' timeout_ms=180000
```

```json
{
  "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:

```json
{ "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](https://docs.tabstack.ai/api), which is the authority on
what each endpoint can return.
