# LiteFold Python SDK

Python client for the LiteFold Rosalind API. Designed for programmatic access by OpenClaw agents running in parallel (10–100+).

## Install

```bash
cd LiteFoldPythonSDK
pip install -e .
```

Dependencies: `httpx`, `click`.

## Quick Start

```python
from litefold import Client

client = Client(api_key="lf_...", base_url="https://agentsapi.litefold.ai")
```

## Chat Lifecycle

### Create and send (fire-and-forget)

```python
client.rosalind.create_chat(
    project="Default", chat_name="run-1",
    description="KRAS study", device="cpu",
)

client.rosalind.send_message(
    project="Default", chat_name="run-1",
    message="Describe KRAS G12D in cancer.",
    mode="pro",       # "max" | "pro" | "lite"
    depth="balanced",  # "balanced" | "ultra"
)
```

`send_message` returns immediately — it does **not** block on the agent stream.

### Check status

```python
status = client.rosalind.chat_status(project="Default", chat_name="run-1")
status.is_running  # True while agent is working
status.status      # "running" | "completed" | "error" | "cancelled" | "unknown"
```

### Wait until done

```python
import time

while True:
    status = client.rosalind.chat_status(project="Default", chat_name="run-1")
    if not status.is_running:
        break
    time.sleep(5)
```

## Reading Results

### List chats

```python
chats = client.rosalind.list_chats(project="Default", limit=5)
for c in chats:
    print(c.chat_name, c.total_cost_usd, c.device)
```

### Blocks

Chat history is stored as an ordered list of **blocks**. Each block is one of:

| `content_type` | What it is | Key fields |
|---|---|---|
| `text` | Assistant response text | `.text`, `.assistant_message` |
| `thinking` | Model thinking | `.text` |
| `tool_use` | Tool invocation | `.tool_name`, `.tool_input` |
| `mcp_result` | Tool output | `.tool_name`, `.tool_result`, `.is_output_panel` |

```python
total = client.rosalind.list_blocks(project="Default", chat_name="run-1")

blocks = client.rosalind.get_blocks(
    project="Default", chat_name="run-1",
    start=0, end=20,  # slice — omit for all blocks
)

for b in blocks:
    if b.assistant_message:
        print(b.assistant_message)
    elif b.content_type == "mcp_result":
        print(f"[{b.tool_name}] {b.tool_result}")
```

### Bash outputs

```python
bash = client.rosalind.get_bash_outputs(
    project="Default", chat_name="run-1",
    start=0, end=50,
)
for b in bash:
    print(f"$ {b.command}")
    print(f"  exit={b.exit_code}  output={b.output[:200]}")
```

### Workspace files

```python
ws = client.rosalind.workspace(project="Default", chat_name="run-1")

ws.list_folders()                          # ["scripts", "documents", "figures", ...]
ws.list_files(folder="scripts")            # [WorkspaceFile, ...]
ws.get_file_content("scripts/main.py")     # FileContent with .content
```

## Files

### Upload

```python
# Upload from disk (single file or list)
result = client.files.upload("Default", "data/protein.pdb")
result = client.files.upload("Default", ["data/protein.pdb", "data/ligand.sdf"], folder="structure")

# Upload raw bytes
result = client.files.upload_bytes("Default", "notes.txt", b"Hello world")

print(result.completed, result.failed)  # 2, 0
for r in result.results:
    print(r.filename, r.status, r.format_type)
```

### List

```python
files = client.files.list("Default", folder="upload")
all_files = client.files.list_all("Default")
structures = client.files.list_by_format("Default", format_type="structure")

for f in files:
    print(f.file_name, f.folder, f.format_type)
```

### Download

```python
# Get text content
content = client.files.get_content("Default", "result.csv")

# Download as bytes
raw = client.files.download("Default", "protein.pdb")

# Download straight to disk
client.files.download_to("Default", "protein.pdb", "./output/")
```

### Metadata

```python
info = client.files.get_metadata("Default", "protein.pdb")
print(info.format_type, info.created_at, info.metadata)

client.files.update_metadata("Default", "protein.pdb", metadata={"notes": "cleaned"})
```

## Projects

```python
client.projects.create(name="MyProject", description="...")
client.projects.list()       # ProjectList
client.projects.get("MyProject")
```

## CLI

The SDK ships with a `litefold` CLI so you can do everything from your terminal without writing a script.

```bash
export LITEFOLD_API_KEY=lf_...
```

All commands accept `--json` for machine-readable output. Use `--help` on any command for full options.

### Projects

```bash
litefold projects list
litefold projects get MyProject
litefold projects create MyProject --description "KRAS study"
```

### Files

```bash
# Upload
litefold files upload data/protein.pdb --project Default
litefold files upload data/protein.pdb data/ligand.sdf --project Default --folder structure

# List
litefold files list --project Default
litefold files list-all --project Default
litefold files list-by-format --project Default --format pdb

# Read / Download
litefold files get-content --project Default --file-name result.csv
litefold files download --project Default --file-name protein.pdb --dest ./output/

# Metadata
litefold files get-metadata --project Default --file-name protein.pdb
litefold files update-metadata --project Default --file-name protein.pdb --metadata '{"notes": "cleaned"}'
```

### Rosalind (Agent Chats)

```bash
# Chat lifecycle
litefold rosalind create-chat --project Default --chat-name run-1 --description "KRAS study"
litefold rosalind send-message --project Default --chat-name run-1 --message "Describe KRAS G12D in cancer."
litefold rosalind chat-status --project Default --chat-name run-1
litefold rosalind cancel-chat --project Default --chat-name run-1

# List / read
litefold rosalind list-chats --project Default --limit 5
litefold rosalind list-blocks --project Default --chat-name run-1
litefold rosalind get-blocks --project Default --chat-name run-1 --start 0 --end 20
litefold rosalind get-bash-outputs --project Default --chat-name run-1
```

### Workspace

```bash
litefold rosalind workspace list-folders --project Default --chat-name run-1
litefold rosalind workspace list-files --project Default --chat-name run-1 --folder scripts
litefold rosalind workspace get-file-content --project Default --chat-name run-1 --file-path scripts/main.py
```

### JSON Output

Append `--json` to any command for machine-readable output:

```bash
litefold --json projects list
litefold --json rosalind get-blocks -p Default -n run-1
```

### Short Flags

| Flag | Meaning |
|---|---|
| `-p` | `--project` |
| `-n` | `--chat-name` |
| `-m` | `--message` |
| `-f` | `--folder` |
| `-d` | `--description` |
| `-o` | `--dest` |
| `-l` | `--limit` |

## Multi-Agent Usage

The SDK is designed for 10–100+ agents running concurrently:

- **One client per process** — the connection pool (200 sockets) handles all threads.
- **One client per agent** — also fine; each gets its own pool.
- **Fire-and-forget** — `send_message` returns instantly, agents check `chat_status` at their own pace.
- **Retry built-in** — transient errors (429, 5xx, connection resets) are retried automatically with exponential backoff.

```python
# Tune for your workload
client = Client(
    api_key="lf_...",
    base_url="https://agentsapi.litefold.ai",
    timeout=60.0,
    max_retries=3,
    retry_backoff=1.0,
    pool_max_connections=200,
    pool_max_keepalive=40,
)
```

## Error Handling

```python
from litefold import (
    LiteFoldError,          # base
    LiteFoldAPIError,       # HTTP error (has .status_code, .message)
    LiteFoldAuthError,      # 401/403
    LiteFoldNotFoundError,  # 404
    LiteFoldRateLimitError, # 429
    LiteFoldTimeoutError,   # timeout
)

try:
    client.rosalind.send_message(...)
except LiteFoldRateLimitError:
    print("Out of credits")
except LiteFoldAuthError:
    print("Bad API key")
except LiteFoldAPIError as e:
    print(f"HTTP {e.status_code}: {e.message}")
```

## All Models

| Model | Fields |
|---|---|
| `ChatInfo` | `chat_name`, `description`, `created_at`, `last_activity_at`, `total_input_tokens`, `total_output_tokens`, `total_cost_usd`, `device` |
| `ChatStatus` | `chat_name`, `is_running`, `task_id`, `status` |
| `Block` | `index`, `content_type`, `role`, `text`, `tool_name`, `tool_input`, `tool_result`, `where_to_show`, `.assistant_message`, `.is_output_panel`, `.is_bash` |
| `BashOutput` | `index`, `command`, `output`, `exit_code` |
| `UploadResponse` | `success`, `message`, `project`, `folder`, `total_files`, `completed`, `failed`, `results` |
| `UploadResult` | `filename`, `status`, `storage_key`, `format_type`, `error` |
| `FileInfo` | `file_name`, `folder`, `storage_key`, `format_type`, `parent_file_name`, `created_at`, `metadata`, `alias` |
| `WorkspaceFile` | `name`, `location`, `folder`, `size` |
| `FileContent` | `location`, `content`, `size` |
| `Project` | `job_name`, `description`, `created_at`, `jobs` |
| `ProjectList` | `total`, `projects` |
