Docs

React UI

Request shape

Understand the default React wire shape, createRequest, metadata, and stream formats.

@anvia/react-ui renders a controller from @anvia/react. The controller decides what gets sent to your route.

The examples use a separate API app at http://localhost:8787, so endpoints are full URLs. Replace that origin with your deployed API origin in production.

By default, both useChat(...) and useCompletion(...) send:

type UIStreamRequest = {
  messages: Message[];
  stream: true;
  metadata?: JsonValue;
};

The messages field is already converted from browser-side UIMessage[] into core Anvia Message[].

The shared wire shape does not mean the UI primitives are interchangeable. useChat(...) maintains a transcript and works with ChatProvider, Thread, Composer, Message, and HumanInput. useCompletion(...) maintains prompt/output state and works with CompletionProvider and Completion.*.

What createRequest receives

Use createRequest when the server needs request-level options such as model, reasoning effort, provider, temperature, tenant, or feature flags.

The example below uses useChat(...); the same createRequest pattern applies to useCompletion(...) when a completion route needs request-level options.

import type { Message } from "@anvia/core/completion";
import { useChat } from "@anvia/react";

type ChatRequest = {
  messages: Message[];
  stream: true;
  metadata: {
    model: string;
    reasoningEffort: "low" | "medium" | "high";
  };
};

const chat = useChat<ChatRequest>({
  endpoint: "http://localhost:8787/api/chat",
  createRequest: ({ coreMessages }) => ({
    messages: coreMessages,
    stream: true,
    metadata: {
      model,
      reasoningEffort,
    },
  }),
});

createRequest receives three message views:

Field Shape Use
coreMessages Anvia core Message[] Send to ordinary server routes.
messages UIMessage[] Backward-compatible UI-shaped message state.
uiMessages UIMessage[] Same as messages; use this name when code needs clarity.

Prefer coreMessages for server requests unless your endpoint explicitly expects UI messages.

Custom request body

If your server already has a product-specific contract, type that request and build it in createRequest.

type SupportChatRequest = {
  threadId: string;
  messages: Message[];
  options: {
    model: string;
    temperature: number;
  };
};

const chat = useChat<SupportChatRequest>({
  endpoint: "http://localhost:8787/api/support/chat",
  createRequest: ({ coreMessages }) => ({
    threadId,
    messages: coreMessages,
    options: {
      model,
      temperature,
    },
  }),
});

Then the server should validate that product shape before running the model:

export async function POST(request: Request) {
  const body = await parseSupportChatRequest(request);
  const agent = createSupportAgent({
    model: requireAllowedModel(body.options.model),
  });

  return createEventStream(agent.prompt(body.messages).stream());
}

Metadata vs message metadata

Use request metadata for options that affect the whole request:

  • selected model
  • tenant or workspace id
  • reasoning effort
  • feature flags
  • trace labels

Use per-message metadata when the value belongs to one submitted message:

await chat.sendMessage({
  text: input,
  metadata: {
    source: "composer",
    selectedModel: model,
  },
});

Formats

The default transport reads JSONL:

const chat = useChat({ endpoint: "http://localhost:8787/api/chat" });

Configure both sides for SSE when needed:

const chat = useChat({
  endpoint: "http://localhost:8787/api/chat",
  format: "sse",
});
return createEventStream(agent.prompt(body.messages).stream(), {
  format: "sse",
});

Do not let the route return JSONL while the client expects SSE, or the stream parser will fail.