Metadata-Version: 2.4
Name: omniscient-sdk
Version: 0.2.0
Summary: Official Python SDK for the Omniscient platform.
Project-URL: Homepage, https://moxoff.com
Author: Moxoff
License: Proprietary
Keywords: chat,connector,mcp,ocr,omniscient,sdk
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: httpx<1.0,>=0.27
Description-Content-Type: text/markdown

# omniscient-sdk

Official Python SDK for the [Omniscient](https://moxoff.com) platform.

The SDK lets external services talk to an Omniscient backend over HTTP. A
single class — `Omniscient` — wraps every endpoint the backend publishes
under `/sdk/*`:

| Capability          | Token permission | What it does                                   |
| ------------------- | ---------------- | ---------------------------------------------- |
| External connectors | `connector`      | Register a connector, run sync cycles, push files, query checksums. |
| MCP attachments     | `mcp`            | Upload/download files referenced from chat.    |
| OCR                 | `ocr`            | Extract Markdown/JSON/PDF from a document.      |
| Chat                | `chat`           | OpenAI-style chat completions *(not implemented yet)*. |

A token's permissions decide which methods are callable; the backend rejects
calls made with a token that lacks the required permission. The chat methods
raise `NotImplementedError` until the backend implementation lands.

## Installation

```bash
pip install omniscient-sdk
```

Imports use the top-level `omniscient` package:

```python
from omniscient import Omniscient
```

## Authentication

All calls authenticate with an API token sent through the `X-Api-Key` HTTP
header. Tokens are created from the Omniscient admin panel and carry one or
more permissions (`connector`, `mcp`, `ocr`, `chat`).

The client reads two environment variables by default (or accepts them as
constructor arguments):

| Variable               | Purpose                             |
| ---------------------- | ----------------------------------- |
| `OMNISCIENT_API_URL`   | Base URL of the Omniscient backend. |
| `OMNISCIENT_API_TOKEN` | API token (`omni_...`).             |

```python
from omniscient import Omniscient

# Reads OMNISCIENT_API_URL / OMNISCIENT_API_TOKEN from the environment.
with Omniscient() as client:
    print(client.connector_id)        # connector bound to this token, if any
    print(client.last_sync_started_at)  # checkpoint of the last sync run

# …or pass them explicitly:
client = Omniscient(api_url="https://omniscient.example.com", api_token="omni_…")
```

When the token is bound to a connector, the client resolves the connector ID
on construction. Pass `resolve_connector=False` to skip that lookup when you
only use the MCP or OCR features.

## External connectors

A connector pushes documents from an external source into Omniscient. The
`run_sync` helper drives a full cycle — optional auto-register → notify start
→ your sync function → notify end (with stale-item cleanup):

```python
import os
from pathlib import Path

from omniscient import Omniscient


def sync(client: Omniscient) -> list[str]:
    source_dir = Path(os.environ["OMNISCIENT_SOURCE_DIR"])
    existing = set(client.get_existing_checksums())
    active: list[str] = []

    for path in sorted(source_dir.rglob("*")):
        if not path.is_file():
            continue
        checksum = Omniscient.compute_checksum(path)
        active.append(checksum)
        if checksum not in existing:
            client.push_file(path, source_id=str(path.relative_to(source_dir)))

    # Returning the active checksums lets the backend delete stale items.
    return active


if __name__ == "__main__":
    with Omniscient() as client:
        client.run_sync(sync, name="local-files", description="Local files")
```

`item_exists(source_id=...)` / `item_exists(checksum=...)` query the backend
for a single item without fetching the whole checksum list. See
[`examples/external_connector/`](examples/external_connector/) for a runnable
connector and Dockerfile.

## MCP attachments

Upload a file produced by an MCP tool so it can be downloaded from the chat
UI, and fetch it back by id:

```python
from omniscient import Omniscient

with Omniscient(resolve_connector=False) as client:
    info = client.mcp_upload_attachment("./report.pdf", display_name="Q4 report")
    client.mcp_download_attachment(info["attachmentId"], "./downloaded.pdf")
```

## OCR

Run OCR on a single document. By default the structured result is returned as
a dict; pass `output_format` together with `dest` to download the rendered
file instead:

```python
from omniscient import Omniscient

with Omniscient(resolve_connector=False) as client:
    result = client.ocr_extract("./document.pdf", mode="STRUCTURED")
    print(result["markdown"])

    # Render and download a file:
    client.ocr_extract(
        "./document.pdf", output_format="MARKDOWN", dest="./document.md"
    )
```

`mode` accepts `"PLAIN"`, `"STRUCTURED"` (default) or `"VLM"`;
`output_format` accepts `"MARKDOWN"`, `"JSON"` or `"PDF"`. Result keys are
snake_case (`markdown`, `regions`, `page_count`, …).

## Chat *(not implemented)*

```python
from omniscient import Omniscient

with Omniscient(resolve_connector=False) as client:
    # Raises NotImplementedError today.
    client.chat_completions({"messages": [{"role": "user", "content": "Hi"}]})
```

## Development

The project uses [uv](https://docs.astral.sh/uv/) and
[ruff](https://docs.astral.sh/ruff/) (pinned in `pyproject.toml`).

```bash
uv sync
uv run ruff check src tests
uv run ty check
uv run pytest
```

## Versioning & release

The package version lives in `[project].version` of `pyproject.toml` (a single
source of truth; `omniscient.__version__` is read from the installed package
metadata). Releases are driven by CI:

- pushes to **`develop`** publish a dev build to TestPyPI (the version is
  suffixed with `.devN` so each build is unique);
- pushes to **`main`** publish the exact `pyproject.toml` version to PyPI as
  `omniscient-sdk`.

Bump `version` in `pyproject.toml` before promoting a release to `main`.
