AI SDKs

TanStack AI

Adding Spectron memory to a TanStack AI application.

@tanstack/ai is a type-safe, provider-agnostic AI SDK for streaming chat, tool calling, and agents. Spectron adds long-term memory to a chat() call: recall relevant context before a generation, then store the exchange afterwards. There is no dedicated adapter. The JavaScript SDK (@surrealdb/spectron) runs in any server handler that calls chat().

Note

This is an integration guide. There is no first-party TanStack package; the code below wires the Spectron SDK into a @tanstack/ai handler, and you can adapt it to your server shape.

npm install @surrealdb/spectron @tanstack/ai @tanstack/ai-openai

Spectron holds an API key, so construct the client only on the server, never in a component or loader that ships to the browser.

Recall context, prepend it to the system prompt, generate, then store the turn:

// src/routes/api/chat.ts
import { Spectron } from "@surrealdb/spectron";
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";

const spectron = new Spectron({
    endpoint: process.env.SPECTRON_ENDPOINT!,
    context: "acme-prod",
    apiKey: process.env.SPECTRON_API_KEY!,
});

export async function POST(request: Request) {
    const { userId, message } = await request.json();
    const scope = [`org/acme/user/${userId}`];

    // 1. Recall relevant memory as a ready-to-inject context block.
    const memory = await spectron.context(message, { scope, k: 8 });

    // 2. Generate with the context block in the system prompt.
    const stream = chat({
        adapter: openaiText("gpt-4o"),
        messages: [
            { role: "system", content: `You are a helpful assistant.\n\n## Memory\n${memory}` },
            { role: "user", content: message },
        ],
    });

    // 3. Store the exchange so it is available next time.
    //    (streamToText collects the reply; stream once to the client instead
    //    if you prefer, and store from a tee.)
    return toServerSentEventsResponse(stream);
}

To store the turn, collect the reply with streamToText before returning it:

import { chat, streamToText } from "@tanstack/ai";

const text = await streamToText(chat({
    adapter: openaiText("gpt-4o"),
    messages: [
        { role: "system", content: `You are a helpful assistant.\n\n## Memory\n${memory}` },
        { role: "user", content: message },
    ],
}));

await spectron.rememberMany(
    [
        { role: "user", content: message },
        { role: "assistant", content: text },
    ],
    { scope },
);

To let the model decide when to reach for memory, expose recall as a @tanstack/ai tool. Build it with toolDefinition(...).server(...) and pass it to chat:

import { chat, toolDefinition } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
import { z } from "zod";

const recall = toolDefinition({
    name: "recall",
    description: "Search long-term memory for context relevant to a query.",
    inputSchema: z.object({ query: z.string() }),
    outputSchema: z.object({ context: z.string() }),
}).server(async ({ query }) => {
    const context = await spectron.context(query, { scope, k: 8 });
    return { context };
});

const stream = chat({
    adapter: openaiText("gpt-4o"),
    messages: [{ role: "user", content: message }],
    tools: [recall],
});

The agent loop invokes the tool automatically when the model asks for it.

Pass a scope on every call to isolate memory. A scope is a slash path or an array of paths, for example ["org/acme/user/alice"] for one user, or a session-specific path such as ["org/acme/session/abc123"]. Register paths with spectron scopes create before first use.

Was this page helpful?