> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deep.space/llms.txt
> Use this file to discover all available pages before exploring further.

# AI reference

> Provider routing, context compaction, chat-history wrappers, and built-in tools.

```ts theme={null}
import {
  // Provider
  createDeepSpaceAI,
  // Compaction
  prepareMessagesWithCompaction, truncateOldToolResults, applySlidingWindow,
  capToolResultSize, totalChars,
  turnsToCoreMessages, buildUiParts, unwrapToolOutput,
  makeDefaultSummarizer,
  DEFAULT_CONTEXT_CONFIG,
  // Chat history (DO tools API wrappers)
  getChat, createChat, updateChat, deleteChatCascade,
  loadMessages, appendMessage,
  // Schemas
  AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA,
  // Built-in tools
  BUILT_IN_TOOLS,
} from 'deepspace/worker'

import type {
  DeepSpaceAIEnv, DeepSpaceAIOptions, DeepSpaceModelFactory,
  ChatContextConfig, ChatTurn, Summarizer,
  ChatRow, ChatMessageRow,
  ToolSchema,
} from 'deepspace/worker'
```

## `createDeepSpaceAI(env, provider, options?)`

Returns a Vercel AI SDK v5 model factory routed through the DeepSpace API worker.

```ts theme={null}
function createDeepSpaceAI(
  env: DeepSpaceAIEnv,
  provider: 'anthropic' | 'openai' | 'cerebras',
  options?: { authToken?: string },
): DeepSpaceModelFactory

type DeepSpaceModelFactory = (modelId: string) => LanguageModel
```

| Option                | Effect                                         |
| --------------------- | ---------------------------------------------- |
| `authToken` (passed)  | Caller pays - JWT subject is billed            |
| `authToken` (omitted) | Owner pays - falls back to `env.APP_OWNER_JWT` |

Use the returned factory with `streamText` / `generateText` from the `ai` package:

```ts theme={null}
import { streamText } from 'ai'
const ai = createDeepSpaceAI(env, 'anthropic', { authToken })
const result = await streamText({
  model: ai('claude-sonnet-4-6'),
  messages,
  tools,
})
```

## Context compaction

### `prepareMessagesWithCompaction(messages, config, options)`

Pre-stream pipeline that keeps the conversation under the context budget.

```ts theme={null}
function prepareMessagesWithCompaction(
  messages: ChatTurn[],
  config: ChatContextConfig,
  options: {
    summarizer: Summarizer
    cachedSummary?: { text: string; throughId: string }
  },
): Promise<{
  messages: ChatTurn[]
  newSummary?: { text: string; throughId: string }
}>
```

`cachedSummary` is the previous turn's summary (if any), anchored to a known message id. When the helper produces a fresh summary, it returns `newSummary` for persistence - store it on the chat row so the next turn can pass it back as `cachedSummary`.

Order of operations:

1. `truncateOldToolResults` - replace old tool-result payloads with a small marker.
2. Apply `cachedSummary` if its `throughId` is found in the history.
3. Summarize the older half if still over budget; return as `newSummary`.
4. Fall back to `applySlidingWindow` on summarizer error or missing message ids.

### `truncateOldToolResults(messages, keepRecent)`

Replaces old tool-result payloads with markers; preserves errors (`success: false`) and the `keepRecent` most recent assistant turns intact.

### `applySlidingWindow(messages, charCap, minKept)`

Drops oldest messages until under `charCap`, never below `minKept`. System messages are pinned.

### `capToolResultSize(result, byteCap)`

Caps individual tool-result payloads with a structured "result too large; narrow your query" error. Preserves a 2KB preview.

### `totalChars(messages)`

Sum of `content` + `JSON.stringify(parts)` lengths.

### `DEFAULT_CONTEXT_CONFIG`

```ts theme={null}
const DEFAULT_CONTEXT_CONFIG: ChatContextConfig = {
  contextBudget: 240_000,       // chars ≈ 60–80K tokens
  toolResultCap: 30_000,        // bytes per tool result
  keepRecentToolResults: 5,
  minKept: 10,                  // sliding-window floor
}
```

Sized for 200K+ context models (Claude Sonnet/Opus, GPT-4.1). Lower for shorter-context models.

## Format conversions

```ts theme={null}
function turnsToCoreMessages(turns: ChatTurn[]): ModelMessage[]
function buildUiParts(responseMessages: ModelMessage[]): unknown[]
function unwrapToolOutput(output: unknown): unknown
```

`turnsToCoreMessages` converts persisted UI-shape `ChatTurn` rows into Vercel AI SDK v5 `ModelMessage`s, splitting assistant rows at each tool-call boundary so Anthropic's `tool_use → tool_result` pairing is preserved.

`buildUiParts` is the inverse - converts `onFinish` response messages into the flat UI-shape `parts` array we persist on `ai-messages` rows.

`unwrapToolOutput` unwraps v5's tagged `output` (`{ type: 'json' | 'text' | ..., value }`) into the flat shape we persist.

## Summarizers

### `makeDefaultSummarizer(env, options?)`

Returns a Claude Haiku 4.5 summarizer.

```ts theme={null}
function makeDefaultSummarizer(
  env: DeepSpaceAIEnv,
  options?: { authToken?: string },
): Summarizer
```

Omit `authToken` to bill the owner (compaction as infrastructure cost). Pass the caller's JWT to bill the user (compaction as part of chat cost).

The default summary anchors on the last real message ID in the older half (skipping prior-summary system rows so re-summarization doesn't loop) - preserve that anchoring if you replace it.

### `Summarizer` type

```ts theme={null}
type Summarizer = (messages: ChatTurn[]) => Promise<string>
```

Roll your own implementation if you want a different model or strategy.

## Chat history helpers (DO tools API wrappers)

These read and write the `ai-chats` and `ai-messages` collections with `X-App-Action: 'true'` (bypassing user RBAC). **The worker is the trust boundary** - callers MUST verify chat ownership before invoking write helpers.

```ts theme={null}
function getChat(
  stub: DurableObjectStub,
  chatId: string,
  userId: string,
): Promise<ChatRow | null>

function createChat(
  stub: DurableObjectStub,
  userId: string,
  opts?: { title?: string; model?: string },
): Promise<ChatRow>

function updateChat(
  stub: DurableObjectStub,
  chatId: string,
  userId: string,
  patch: Partial<Pick<ChatRow, 'title' | 'model' | 'compactedSummary' | 'compactedThroughId'>>,
): Promise<void>

function deleteChatCascade(
  stub: DurableObjectStub,
  chatId: string,
  userId: string,
): Promise<void>

function loadMessages(
  stub: DurableObjectStub,
  chatId: string,
  userId: string,
): Promise<ChatMessageRow[]>

function appendMessage(
  stub: DurableObjectStub,
  msg: {
    id: string
    chatId: string
    userId: string
    role: 'user' | 'assistant' | 'system'
    content: string
    parts?: unknown[]
  },
): Promise<void>
```

`appendMessage` takes an `id` field that becomes the new row's `recordId` on the underlying tools API.

| Type             | Shape                                                                                                   |
| ---------------- | ------------------------------------------------------------------------------------------------------- |
| `ChatRow`        | `{ recordId, id, userId, title, model?, compactedSummary?, compactedThroughId?, createdAt, updatedAt }` |
| `ChatMessageRow` | `{ recordId, id, chatId, userId, role, content, parts?, createdAt }`                                    |

<Note>
  Both row shapes expose `recordId` as the canonical identifier and keep `id` as a deprecated alias for backward compatibility. Read `recordId` in new code.
</Note>

## Built-in tools

```ts theme={null}
const BUILT_IN_TOOLS: ToolSchema[]

interface ToolSchema {
  name: string
  description: string
  params: Record<string, {
    type: 'string' | 'number' | 'boolean' | 'object' | 'array'
    description: string
    required?: boolean
    default?: unknown
  }>
}
```

`BUILT_IN_TOOLS` is an **array** of tool schemas, not a record keyed by name. Each entry declares its parameters as a flat `{ type, description, required?, default? }` map - this is an MCP-like description used by the worker's tools API and by app authors who want to surface SDK tools to an LLM.

The catalog (records, schemas, users, storage, backup, Yjs):

| Tool                                            | Purpose                                           |
| ----------------------------------------------- | ------------------------------------------------- |
| `records.query`                                 | Filter and list records                           |
| `records.get`                                   | Fetch one record                                  |
| `records.create`                                | Create a record                                   |
| `records.update`                                | Patch a record                                    |
| `records.delete`                                | Delete a record                                   |
| `schema.list`                                   | Enumerate collection names                        |
| `schema.describe`                               | Describe one collection's columns and permissions |
| `user.current`                                  | Look up the caller's user record                  |
| `user.list`                                     | List all users in the room                        |
| `storage.list` / `read` / `write` / `delete`    | Key-value storage                                 |
| `backup.create` / `list` / `restore` / `delete` | Yjs doc backups                                   |
| `yjs.list` / `getText` / `setText`              | Collaborative doc text access                     |

See `src/ai/tools.ts` in the scaffold for `buildSystemPrompt(appName, schemas)` and `buildReadOnlyTools(executor)` - both are app-local references you can edit to customize the assistant's tool surface and system prompt.

## See also

* [AI chat guide](/guides/ai-chat) - end-to-end usage
* [Server actions reference](/sdk-reference/worker/server-actions) - `tools.integration` for non-streamed calls
