# Server Functions

MIRARI's business logic lives in **TanStack server functions** — typed RPCs that the client calls via `useServerFn`. They run inside the same server as SSR, so there's no separate API service to deploy.

## The protected-fn pattern

```ts
// src/lib/chat.functions.ts
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { requireSupabaseAuth } from "@/integrations/supabase/auth-middleware";

export const chatCompletion = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator((data) =>
    z.object({
      connectionId: z.string().uuid(),
      conversationId: z.string().uuid(),
      apiKey: z.string().min(1),
      userMessage: z.string().min(1).max(8000),
    }).parse(data),
  )
  .handler(async ({ data, context }) => {
    const { supabase, userId } = context;

    // 1. Load connection (RLS-scoped to this user)
    const { data: conn } = await supabase
      .from("agent_connections")
      .select("base_url, model")
      .eq("id", data.connectionId)
      .single();
    if (!conn) throw new Error("Connection not found");

    // 2. Load recent messages
    const { data: history } = await supabase
      .from("messages")
      .select("role, content")
      .eq("conversation_id", data.conversationId)
      .order("created_at")
      .limit(40);

    // 3. Inject strong memory
    const { data: atlas } = await supabase
      .from("memory_nodes")
      .select("label, category")
      .gte("strength", 0.75)
      .limit(40);

    const messages = [
      { role: "system", content: buildSystem(atlas ?? []) },
      ...(history ?? []),
      { role: "user", content: data.userMessage },
    ];

    // 4. Call the Hermes Agent
    const res = await fetch(`${conn.base_url}/chat/completions`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${data.apiKey}`,
      },
      body: JSON.stringify({ model: conn.model, messages, temperature: 0.7 }),
    });
    const json = await res.json();
    const reply = json.choices?.[0]?.message?.content ?? "";

    // 5. Persist both turns
    await supabase.from("messages").insert([
      { user_id: userId, conversation_id: data.conversationId,
        role: "user", content: data.userMessage },
      { user_id: userId, conversation_id: data.conversationId,
        role: "assistant", content: reply },
    ]);

    return { reply };
  });
```

## Calling from the client

```tsx
import { useServerFn } from "@tanstack/react-start";
import { chatCompletion } from "@/lib/chat.functions";
import { getKey } from "@/lib/key-vault";

function Composer({ connectionId, conversationId }: Props) {
  const chat = useServerFn(chatCompletion);
  const [input, setInput] = useState("");

  async function send() {
    const { reply } = await chat({
      data: {
        connectionId,
        conversationId,
        apiKey: getKey(connectionId)!,   // read from local vault
        userMessage: input,
      },
    });
    // ...append to UI
  }
}
```

## The full catalog

| Function         | File                        | Purpose                                                  |
| ---------------- | --------------------------- | -------------------------------------------------------- |
| `chatCompletion` | `src/lib/chat.functions.ts` | Send a turn to a bound Hermes Agent and persist the pair |
| `testConnection` | `src/lib/chat.functions.ts` | One-token ping; updates `status`                         |
| `analyzeMirror`  | `src/lib/ai.functions.ts`   | Run Mirror Mode on logs, return structured report        |
| `suggestSkill`   | `src/lib/ai.functions.ts`   | Propose a new skill from context                         |
| `walletAuth*`    | `src/lib/wallet-auth.*`     | Optional wallet-based sign-in                            |

Every one of them is middleware-protected with `requireSupabaseAuth`, and every one validates its input with Zod.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://entermirari.gitbook.io/entermirari-docs/architecture/server-functions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
