Metadata-Version: 2.4
Name: tex-sdk
Version: 1.2.1
Summary: Tex Python SDK — SuperMemory-compatible memory API
Author: Tex
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24.0
Provides-Extra: http2
Requires-Dist: h2>=4.1.0; extra == "http2"
Provides-Extra: test
Requires-Dist: respx>=0.20.0; extra == "test"
Requires-Dist: pytest>=7.0; extra == "test"

# Tex Python SDK

SuperMemory-compatible memory SDK for Python.

## Installation

```bash
pip install tex-sdk
```

Optional HTTP/2 support:

```bash
pip install "tex-sdk[http2]"
```

Requires Python 3.9+.

## Quick Start

### API key

```python
from tex import Tex

client = Tex(api_key="sk-...", base_url="http://localhost:8000")

client.add("The quick brown fox jumps over the lazy dog.")
results = client.search.documents("fox")
```

### Environment variables

Set `TEX_API_KEY` and `TEX_BASE_URL`, then:

```python
from tex import Tex

client = Tex()
```

### Per-user clients (production)

To act as one end user of your app, pass that user's id alongside your API key.
The SDK makes a single `POST /auth/token-exchange` with
`{api_key, user_id, session_id}` and receives a JWT minted for that user:

```python
import os
from tex import Tex

# base_url falls back to TEX_BASE_URL, api_key to TEX_API_KEY
alice = Tex(
    api_key=os.environ["TEX_API_KEY"],
    base_url=os.environ["TEX_BASE_URL"],
    org_id="org1",          # optional; if set, must match the key's org
    user_id="alice",
    session_id="chat-42",   # optional
)
alice.recall(q="what did I order last week?", session_id="chat-42")  # every call is scoped to alice
```

- The API key must carry an impersonation-capable scope, normally
  `impersonate_user` (also accepted: `tenant:write:any_user`, `agent`,
  `service`, `system`, `*`). Without one the backend refuses the `user_id`
  and the SDK raises `PermissionDeniedError` saying so.
- The minted token carries an `act` claim naming the key: it is locked to
  that user and cannot impersonate anyone else. The SDK also checks the
  returned token is really for the requested user.
- On expiry the SDK uses the refresh token, and otherwise re-exchanges for the
  same `user_id`.
- The per-user token is sent on **every** call, including the
  Supermemory-compatible ones (`memories.add`, `documents.add`,
  `search.memories`, `profile`, ...), so they also act as that user with that
  user's team scopes. `container_tag` on such a client must be omitted or
  equal the user id; naming another user is refused with 403
  `container_tag_not_permitted`. (Requires a backend that accepts per-user
  tokens on `/v3` and `/v4`.)
- Omit `user_id` to act as the key's own identity; compat calls then send the
  raw API key and `container_tag` selects the end user, Supermemory-style.

### Org + user login (development servers only)

```python
from tex import Tex

client = Tex(base_url="http://localhost:8000", org_id="org1", user_id="user1")
```

With no API key the SDK calls `POST /auth/login`, which exists only when the
backend runs with `DEBUG=true`. Production returns 403 and the SDK raises
`PermissionDeniedError` pointing you to the per-user API key pattern above.

## Documents

Add, retrieve, update, delete, list, and batch-manage documents.

```python
# Top-level shortcut
resp = client.add("Document content", container_tag="notes")

# Equivalent resource call
resp = client.documents.add("Document content", container_tag="notes")

# Get by ID
doc = client.documents.get("doc_abc123")

# Update
client.documents.update("doc_abc123", content="Updated content")

# Delete
client.documents.delete("doc_abc123")

# List with filters
docs = client.documents.list(container_tags=["notes"], limit=20)

# Batch add
client.documents.batch_add(
    ["First document", "Second document"],
    container_tag="batch-import",
)

# Bulk delete
client.documents.delete_bulk(container_tags=["old-tag"])
```

## Search

### Document search

```python
results = client.search.documents("quarterly revenue", limit=10)

# Alias
results = client.search.execute("quarterly revenue", limit=10)
```

### Memory search

```python
results = client.search.memories("user preferences", container_tag="user1")
```

## Memories

```python
# Add memories
client.memories.add(
    [{"content": "User prefers dark mode"}],
    container_tag="prefs",
)

# Forget a memory
client.memories.forget("prefs", id="mem_abc123")

# Update a memory
client.memories.update_memory("prefs", "User prefers light mode", id="mem_abc123")
```

## Profile

```python
profile = client.profile("user-container-tag")
print(profile.profile)
```

## Tex-Unique Features

### NLQ (Natural Language Query)

```python
result = client.nlq.execute("What meetings did I have last week?")
print(result.text)
print(result.evidence)
```

### Episodes

```python
# Store a conversation
client.episodes.store(
    [
        {"role": "user", "content": "Book a table for two"},
        {"role": "assistant", "content": "Which restaurant?"},
    ],
    episode_id="ep_001",
)

# List episodes
episodes = client.episodes.list(limit=10)
```

### Preferences

```python
client.preferences.store([
    {"key": "drink", "value": "espresso", "confidence": 0.9},
])
```

### DB

```python
result = client.db.query("MATCH (n) RETURN n LIMIT $limit", params={"limit": 5})
schema = client.db.schema()
```

### Auth

```python
info = client.auth.whoami()
```

### Jobs

```python
status = client.jobs.get("job_abc123")
```

## Error Handling

The SDK provides a detailed error hierarchy:

```
TexError
  APIError
    APIConnectionError
      APITimeoutError
    APIStatusError
      BadRequestError          (400)
      AuthenticationError      (401)
      PermissionDeniedError    (403)
      NotFoundError            (404)
      ConflictError            (409)
      UnprocessableEntityError (422)
      RateLimitError           (429)
      InternalServerError      (500+)
    APIResponseValidationError
```

```python
from tex import Tex, NotFoundError, AuthenticationError, APIStatusError

client = Tex(api_key="sk-...", base_url="http://localhost:8000")

try:
    doc = client.documents.get("nonexistent")
except NotFoundError as e:
    print(f"Not found: {e}")
except AuthenticationError as e:
    print(f"Auth failed: {e}")
except APIStatusError as e:
    print(f"HTTP {e.status_code}: {e.message}")
    print(f"Request ID: {e.request_id}")
```

## Active memory inactivity window

Conversation turns stored with `conversations.remember` land in short-term *active memory* first. An
active-memory episode (one per `session_id`) is closed and consolidated into long-term memory once it has been
idle for its **inactivity window** — 30 minutes by default. Tune it to the use case: a support chat can close
after a few minutes, a long-running agent task may need hours.

```python
# Per conversation (the episode keeps this window for later writes)
client.conversations.remember(turns, session_id="ticket-981", inactivity_timeout_seconds=2 * 60 * 60)

# Client-wide default for every write that does not pass its own
client = Tex(api_key="sk-...", active_inactivity_timeout_seconds=10 * 60)
```

- Resolution: per-call value → client default → org default (set by an org admin with `PATCH /me/settings`
  `{"active_memory_inactivity_seconds": 3600}`) → 30 minutes.
- Bounds: 300–86400 seconds (5 minutes to 24 hours). Invalid values raise `InvalidInactivityTimeoutError`
  before any request.
- `memories.add` and `memories.update_memory` accept the same `inactivity_timeout_seconds`.

## Configuration

Use `TexConfig` and `TexEndpoints` for advanced configuration:

```python
from tex import Tex, TexConfig, TexEndpoints

config = TexConfig(
    base_url="https://api.example.com",
    api_key="sk-...",
    timeout_s=30.0,
    max_retries=3,
    http2=True,
    active_inactivity_timeout_seconds=3600,  # optional; see "Active memory inactivity window"
    endpoints=TexEndpoints(
        db_query="/custom/db/query",
        db_schema="/custom/db/schema",
    ),
)

client = Tex(config=config)
```

Default timeout is 60 seconds. Default max retries is 2.

## Migration from SuperMemory

| SuperMemory | Tex SDK |
|---|---|
| `client.add(content)` | `client.add(content)` or `client.documents.add(content)` |
| `client.documents.get(id)` | `client.documents.get(id)` |
| `client.documents.update(id)` | `client.documents.update(id)` |
| `client.documents.delete(id)` | `client.documents.delete(id)` |
| `client.documents.list()` | `client.documents.list()` |
| `client.documents.batch_add(docs)` | `client.documents.batch_add(docs)` |
| `client.search.execute(q)` | `client.search.execute(q)` or `client.search.documents(q)` |
| `client.search.memories(q)` | `client.search.memories(q)` |
| `client.memories.add(memories)` | `client.memories.add(memories)` |
| `client.memories.forget(tag)` | `client.memories.forget(tag)` |
| `client.profile(tag)` | `client.profile(tag)` |

Tex extends SuperMemory with additional resources: `nlq`, `episodes`, `preferences`, `db`, `auth`, and `jobs`.

## HTTP/2

HTTP/2 is enabled by default. If the `h2` package is not installed, the SDK falls back to HTTP/1.1 automatically. To install with HTTP/2 support:

```bash
pip install "tex-sdk[http2]"
```

To explicitly disable HTTP/2:

```python
client = Tex(api_key="sk-...", base_url="http://localhost:8000", http2=False)
```
