diff --git a/docs/docs.json b/docs/docs.json
index 08e484bbe..177b39c7c 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -101,8 +101,20 @@
{
"group": "Server Customisations",
"pages": [
+ "ui-sdk/setup-custom-servers/overview",
+ "ui-sdk/setup-custom-servers/connect-with-trueforge",
"ui-sdk/setup-custom-servers/connect-with-truefoundry",
- "ui-sdk/setup-custom-servers/custom-server",
+ {
+ "group": "Bring your own server",
+ "pages": [
+ "ui-sdk/setup-custom-servers/custom-server",
+ "ui-sdk/setup-custom-servers/core-concepts",
+ "ui-sdk/setup-custom-servers/chat",
+ "ui-sdk/setup-custom-servers/catalog",
+ "ui-sdk/setup-custom-servers/agents",
+ "ui-sdk/setup-custom-servers/putting-it-together"
+ ]
+ },
"ui-sdk/setup-custom-servers/server-contract"
]
},
diff --git a/docs/ui-sdk/concepts/architecture.mdx b/docs/ui-sdk/concepts/architecture.mdx
index d78b785de..66f61fef3 100644
--- a/docs/ui-sdk/concepts/architecture.mdx
+++ b/docs/ui-sdk/concepts/architecture.mdx
@@ -25,7 +25,7 @@ Two kinds of component are exported, and the distinction determines which one yo
**Atoms** are presentational. `AssistantMessageBubble`, `ComposerShell`, and `ToolCallCard` take everything they render as props and hold no runtime state. You rarely render an atom directly — instead you replace one, and the container that renders it picks up your version.
-That replacement is what `overrides` does: name a component, supply your own, and it is used everywhere that component appears. See [Slot overrides](/ui-sdk/guides/slot-overrides).
+That replacement is what `overrides` does: name a component, supply your own, and it is used everywhere that component appears. See [Slot overrides](/ui-sdk/setup-custom-ui/slot-overrides).
## Provider stack
@@ -53,4 +53,4 @@ Order matters: `SlotsProvider` must be outside `TrueFoundryChatProvider`.
These class names and attributes are stable and safe to target from your own CSS: `.aui-root`, `.aui-theme-root`, `.aui-markdown`, `.aui-syntax-highlighter`, `.aui-openui`, `.aui-monaco`, plus `data-slot` attributes on individual components. Most slot values are prefixed `aui_`, but not all — `avatar`, `tool-call-card`, and `tool-group-card` are among the unprefixed ones, so inspect the element rather than assuming the prefix.
-For token-level changes, prefer [theming](/ui-sdk/guides/theming) over CSS overrides.
\ No newline at end of file
+For token-level changes, prefer [theming](/ui-sdk/setup-custom-ui/custom-theme) over CSS overrides.
\ No newline at end of file
diff --git a/docs/ui-sdk/get-started/quickstart.mdx b/docs/ui-sdk/get-started/quickstart.mdx
index 0dde55e85..508542e51 100644
--- a/docs/ui-sdk/get-started/quickstart.mdx
+++ b/docs/ui-sdk/get-started/quickstart.mdx
@@ -46,6 +46,8 @@ export default function App() {
That is enough to get a full agent chat with streaming, history, and tool calls.
+For TrueFoundry, a custom `AgentUIServer`, auth, or catalog overrides, see [Server Customisations](/ui-sdk/setup-custom-servers/overview).
+

diff --git a/docs/ui-sdk/guides/troubleshooting.mdx b/docs/ui-sdk/guides/troubleshooting.mdx
index f64b853cd..f4f01e33e 100644
--- a/docs/ui-sdk/guides/troubleshooting.mdx
+++ b/docs/ui-sdk/guides/troubleshooting.mdx
@@ -79,7 +79,7 @@ description: "Common failure modes and their causes."
- A `screenType=mcp-auth` query parameter is present on the URL. `TrueforgeUI` renders the OAuth completion screen exclusively in that case. Keep the callback on its own route; see [MCP OAuth](/ui-sdk/guides/mcp-oauth).
+ A `screenType=mcp-auth` query parameter is present on the URL. `TrueforgeUI` renders the OAuth completion screen exclusively in that case. Keep the callback on its own route; see [MCP OAuth](/ui-sdk/setup-custom-ui/mcp-oauth).
@@ -87,7 +87,7 @@ description: "Common failure modes and their causes."
- A few names are published as types only, so importing them as values fails. `Button` and `IconButton` are restyled through [theme tokens](/ui-sdk/guides/theming) rather than replaced. `AskUserPrompt`, `McpAuthPrompt`, and `ReasoningCard` are override-only — supply your own component typed with the published props.
+ A few names are published as types only, so importing them as values fails. `Button` and `IconButton` are restyled through [theme tokens](/ui-sdk/setup-custom-ui/custom-theme) rather than replaced. `AskUserPrompt`, `McpAuthPrompt`, and `ReasoningCard` are override-only — supply your own component typed with the published props.
diff --git a/docs/ui-sdk/reference/atoms.mdx b/docs/ui-sdk/reference/atoms.mdx
index b3ebc1f14..d81ffb94c 100644
--- a/docs/ui-sdk/reference/atoms.mdx
+++ b/docs/ui-sdk/reference/atoms.mdx
@@ -5,7 +5,7 @@ description: "Presentational components and their prop types."
Atoms are pure presentational components. Most publish a matching `*Props` type, and most are
overridable slots — but neither is universal, so check
-[Slot overrides](/ui-sdk/guides/slot-overrides) for which names `overrides` accepts, and rely on your
+[Slot overrides](/ui-sdk/setup-custom-ui/slot-overrides) for which names `overrides` accepts, and rely on your
editor for which props types exist.
## Messages and content
@@ -79,7 +79,7 @@ type ToolCallCardProps = {
`icon` is an icon **name** looked up in the icon registry, not a rendered node — it defaults
to `"mcp-server"`. Passing an element renders nothing useful. To supply your own artwork, map
- the name through `theme.icons`; see [Theming](/ui-sdk/guides/theming).
+ the name through `theme.icons`; see [Theming](/ui-sdk/setup-custom-ui/custom-theme).
Also `ToolCallContentBlock`, `ToolApprovalBar`, `ToolGroupCard`, `SubAgentCard`,
@@ -115,5 +115,5 @@ theme and settings buttons.
`ButtonProps`, `ButtonVariant`, `ButtonSize`, and `IconButtonProps` are available as types
for typing your own components. The buttons themselves are styled through semantic tokens
- rather than replaced — see [Theming](/ui-sdk/guides/theming).
+ rather than replaced — see [Theming](/ui-sdk/setup-custom-ui/custom-theme).
diff --git a/docs/ui-sdk/reference/catalog.mdx b/docs/ui-sdk/reference/catalog.mdx
index f522e138a..7e2689a36 100644
--- a/docs/ui-sdk/reference/catalog.mdx
+++ b/docs/ui-sdk/reference/catalog.mdx
@@ -3,7 +3,7 @@ title: "Settings catalog"
description: "The optional catalog port behind the model, connector, skill, and sandbox settings."
---
-`catalog` is the optional fourth part of an [`AgentUIServer`](/ui-sdk/concepts/server-contract). Attach
+`catalog` is the optional fourth part of an [`AgentUIServer`](/ui-sdk/setup-custom-servers/server-contract). Attach
it and the settings UI appears; omit it and those surfaces stay hidden.
```ts
@@ -234,7 +234,7 @@ must render either way.
## Widening
Every catalog interface is generic over its row and request types, following the same pattern as
-[the chat and builder ports](/ui-sdk/concepts/server-contract#widening-the-types):
+[the chat and builder ports](/ui-sdk/setup-custom-servers/server-contract#widening-the-types):
```ts
interface MyModelEntry extends ModelEntry {
diff --git a/docs/ui-sdk/reference/events.mdx b/docs/ui-sdk/reference/events.mdx
index 113ae791a..d494c9bf7 100644
--- a/docs/ui-sdk/reference/events.mdx
+++ b/docs/ui-sdk/reference/events.mdx
@@ -23,7 +23,7 @@ interface TurnStreamData {
`sequenceNumber` must increase monotonically within a turn. It is what
-[`subscribeToTurn`](/ui-sdk/concepts/server-contract#agentchatserver) uses to resume — a reconnecting
+[`subscribeToTurn`](/ui-sdk/setup-custom-servers/server-contract#agentchatserver) uses to resume — a reconnecting
client passes the last number it saw as `afterSequenceNumber`, and you replay only what follows.
```ts
@@ -222,7 +222,7 @@ interface McpAuthRequiredEvent {
}
```
-`authUrl` is the URL the popup opens. See [MCP OAuth](/ui-sdk/guides/mcp-oauth).
+`authUrl` is the URL the popup opens. See [MCP OAuth](/ui-sdk/setup-custom-ui/mcp-oauth).
These events also appear in `requiredActions` on a `done` turn state. That is how a turn that
@@ -231,7 +231,7 @@ interface McpAuthRequiredEvent {
The user's reply comes back through `createTurn` as a `user.tool_approval` or
-`user.tool_response` [input item](/ui-sdk/concepts/server-contract#turn-input), carrying the same
+`user.tool_response` [input item](/ui-sdk/setup-custom-servers/server-contract#turn-input), carrying the same
`toolCallId`.
## Sub-agents
@@ -277,7 +277,7 @@ interface SandboxCreatedEvent {
```
`sandboxId` is what you pass to
-[`downloadSandboxFile`](/ui-sdk/concepts/server-contract#agentchatserver) to retrieve artifacts the
+[`downloadSandboxFile`](/ui-sdk/setup-custom-servers/server-contract#agentchatserver) to retrieve artifacts the
agent produced.
`mcp.initialize` (`{ type, id, createdAt, threadId }`, plus any additional fields you attach)
diff --git a/docs/ui-sdk/reference/hooks.mdx b/docs/ui-sdk/reference/hooks.mdx
index 774aba9e1..9e8aff3bd 100644
--- a/docs/ui-sdk/reference/hooks.mdx
+++ b/docs/ui-sdk/reference/hooks.mdx
@@ -24,7 +24,7 @@ const capabilities = useServerCapabilities();
Returns the resolved value from your server's `getCapabilities()`, or `null` while it is still
loading or if the call failed. Use it to gate your own chrome the same way the SDK gates its
sandbox, skill, and settings affordances. See
-[Server contract](/ui-sdk/concepts/server-contract#capabilities).
+[Server contract](/ui-sdk/setup-custom-servers/server-contract#capabilities).
## MCP auth
@@ -34,7 +34,7 @@ const auth = useMCPAuth({ callbackPath: "/mcp-callback" });
`MCP_AUTH_POPUP_CHANNEL` is `"truefoundry-mcp-auth-popup"` — the `BroadcastChannel` name used
to signal completion back to the opener. Types: `McpAuthCallback`, `McpAuthPopupMessage`,
-`UseMCPAuthOptions`. See [MCP OAuth](/ui-sdk/guides/mcp-oauth).
+`UseMCPAuthOptions`. See [MCP OAuth](/ui-sdk/setup-custom-ui/mcp-oauth).
## assistant-ui state
diff --git a/docs/ui-sdk/reference/server.mdx b/docs/ui-sdk/reference/server.mdx
index 9abfde574..2985aabf6 100644
--- a/docs/ui-sdk/reference/server.mdx
+++ b/docs/ui-sdk/reference/server.mdx
@@ -83,5 +83,5 @@ to reference:
-Full method signatures are on [Server contract](/ui-sdk/concepts/server-contract); a worked
-implementation is on [Bring your own server](/ui-sdk/guides/custom-server).
+Full method signatures are on [Server contract](/ui-sdk/setup-custom-servers/server-contract); a worked
+implementation is on [Bring your own server](/ui-sdk/setup-custom-servers/custom-server).
diff --git a/docs/ui-sdk/reference/theme.mdx b/docs/ui-sdk/reference/theme.mdx
index fb55845fd..ac8361a97 100644
--- a/docs/ui-sdk/reference/theme.mdx
+++ b/docs/ui-sdk/reference/theme.mdx
@@ -47,7 +47,7 @@ type SlotOverrides = Partial;
`AtomSlots` is derived from the SDK's default slot table, so it enumerates exactly the
overridable slots — your editor's autocomplete on `overrides` is authoritative. See
-[Slot overrides](/ui-sdk/guides/slot-overrides).
+[Slot overrides](/ui-sdk/setup-custom-ui/slot-overrides).
Most token names map to a CSS variable of the same name; the exceptions are listed in
-[Theming](/ui-sdk/guides/theming).
+[Theming](/ui-sdk/setup-custom-ui/custom-theme).
diff --git a/docs/ui-sdk/reference/trueforge-ui.mdx b/docs/ui-sdk/reference/trueforge-ui.mdx
index 8c2f4dc9b..e81c84cf2 100644
--- a/docs/ui-sdk/reference/trueforge-ui.mdx
+++ b/docs/ui-sdk/reference/trueforge-ui.mdx
@@ -192,13 +192,13 @@ import { TrueforgeUI } from "@truefoundry/trueforge-ui";
- See [Theming](/ui-sdk/guides/theming).
+ See [Theming](/ui-sdk/setup-custom-ui/custom-theme).
Replace any built-in component. `Partial`, derived from the SDK's default slot
table — so editor autocomplete lists the overridable names. See
- [Slot overrides](/ui-sdk/guides/slot-overrides).
+ [Slot overrides](/ui-sdk/setup-custom-ui/slot-overrides).
@@ -228,7 +228,7 @@ Persisted sessions remain available through the thread list.
If `window.location.search` contains `screenType=mcp-auth`, the component renders only the
- OAuth completion screen and no layout. See [MCP OAuth](/ui-sdk/guides/mcp-oauth).
+ OAuth completion screen and no layout. See [MCP OAuth](/ui-sdk/setup-custom-ui/mcp-oauth).
## Example
diff --git a/docs/ui-sdk/setup-custom-servers/agents.mdx b/docs/ui-sdk/setup-custom-servers/agents.mdx
new file mode 100644
index 000000000..f061c5053
--- /dev/null
+++ b/docs/ui-sdk/setup-custom-servers/agents.mdx
@@ -0,0 +1,81 @@
+---
+title: "Agents"
+description: "Implement AgentBuilderServer — capabilities, pickers, and the agent library."
+---
+
+`AgentBuilderServer` powers the agent library and the draft composer. It is required on every `AgentUIServer`, even when you only expose a fixed agent — return honest empty lists and disable capabilities you do not support.
+
+Full signatures are on [Server contract](/ui-sdk/setup-custom-servers/server-contract#agentbuilderserver).
+
+## Capabilities
+
+`getCapabilities` tells the UI which optional features your backend supports, so it can hide what you do not offer rather than surfacing controls that fail.
+
+```ts
+async getCapabilities() {
+ return {
+ data: {
+ sandbox: { enabled: false },
+ skill: { enabled: true },
+ // settings: { enabled: false }, // hides Settings even when catalog is attached
+ },
+ };
+}
+```
+
+It is fetched once when the server resolves. `skill.reason` is shown when skills are disabled. Read the resolved value in your own components with [`useServerCapabilities()`](/ui-sdk/reference/hooks).
+
+## Pickers
+
+These methods feed the composer selectors. They are **not** the same as [catalog](/ui-sdk/setup-custom-servers/catalog) CRUD — catalog manages Settings; pickers list what an agent can attach right now.
+
+| Method | Returns |
+| --- | --- |
+| `getModels` | `ModelSelection[]` — `id`, `name`, `provider`, `properties` |
+| `getSkills` | `AgentSkill[]` — `id`, `name`, optional `description` |
+| `getMcp` | `ConnectorState[]` — `id`, `name`, optional auth flags |
+
+Return a non-empty `properties.reasoningEfforts` on a `ModelSelection` and the UI shows a reasoning-effort picker beside the model selector.
+
+## Agent library
+
+| Method | Role |
+| --- | --- |
+| `searchAgents` | Search / list library entries (`query?`, `limit?`, `offset?`) |
+| `saveAgent` | Create or update (`intent: "create" \| "update"`) |
+| `deleteAgent?` | Optional — omit to hide delete |
+
+```ts
+async searchAgents({ query, limit, offset } = {}) {
+ return [
+ {
+ agentId: "support-agent",
+ name: "Support agent",
+ // agentSpec: { ... } // supply to enable Edit; omit for try-only
+ },
+ ];
+}
+
+async saveAgent({ agentName, agentSpec, intent, sessionId }) {
+ // persist; return { agentId?, sessionUpdatedAt? }
+ return { agentId: agentName };
+}
+```
+
+On `AgentLibraryEntry`, `agentId` is the stable identifier used for selection, history filtering, and session binding; omit it and the row falls back to `name`.
+
+## Chat with a library agent
+
+How the shell exposes library vs composer is controlled by `agentConfig` on `TrueforgeUI` — see [Agent modes](/ui-sdk/guides/agent-modes). Creating a session with `agentName` (or binding a mutable `agentSpec`) is what ties chat to a library entry; the builder methods above only populate the pickers and save flow.
+
+## Next
+
+
+
+ Compose chat, builder, and catalog.
+
+
+
+ `AgentSpec`, save request, and picker row types.
+
+
diff --git a/docs/ui-sdk/setup-custom-servers/catalog.mdx b/docs/ui-sdk/setup-custom-servers/catalog.mdx
new file mode 100644
index 000000000..556c9d55c
--- /dev/null
+++ b/docs/ui-sdk/setup-custom-servers/catalog.mdx
@@ -0,0 +1,85 @@
+---
+title: "Catalog"
+description: "Attach a CatalogServer so Settings can manage models, connectors, skills, and sandboxes."
+---
+
+`catalog` is the optional fourth part of an [`AgentUIServer`](/ui-sdk/setup-custom-servers/server-contract). Attach it and the settings UI appears; omit it and those surfaces stay hidden.
+
+```ts
+interface CatalogServer {
+ modelCatalog: ModelCatalogServer;
+ connectorCatalog: ConnectorCatalogServer;
+ skillCatalog?: SkillCatalogServer;
+ sandboxCatalog?: SandboxCatalogServer;
+}
+```
+
+Models and connectors are required; skills and sandboxes are optional — omit either when your product has no such settings page.
+
+Two conventions recur across all four. Every catalog exposes a **discovery** list of things the user could add (`get*Catalog`) alongside a list of what they have already configured (`list*`). And public read shapes never carry secrets — `apiKey` is accepted on writes and never returned.
+
+Full method and DTO shapes are on [Settings catalog](/ui-sdk/reference/catalog).
+
+| Sub-port | Settings surface |
+| --- | --- |
+| `modelCatalog` | Model providers and models |
+| `connectorCatalog` | MCP connectors (including auth / disconnect) |
+| `skillCatalog?` | Skills registry |
+| `sandboxCatalog?` | Sandbox providers |
+
+`getCapabilities().data.settings.enabled` can hide the settings control even when a `catalog` is attached — see [Capabilities](/ui-sdk/setup-custom-servers/server-contract#capabilities).
+
+## Attach catalog to your server
+
+```ts
+import type { AgentUIServer, CatalogServer } from "@truefoundry/trueforge-ui";
+
+const catalog: CatalogServer = {
+ modelCatalog: {
+ getModelProviderCatalog: async () => [],
+ listModelProviders: async () => [],
+ createModelProvider: async (req) => {
+ /* persist and return public shape */
+ throw new Error("unimplemented");
+ },
+ updateModelProvider: async (req) => {
+ throw new Error("unimplemented");
+ },
+ },
+ connectorCatalog: {
+ getConnectorCatalog: async () => [],
+ listConnectors: async () => [],
+ createConnector: async (req) => {
+ throw new Error("unimplemented");
+ },
+ updateConnector: async (req) => {
+ throw new Error("unimplemented");
+ },
+ authenticateConnector: async (req) => {
+ throw new Error("unimplemented");
+ },
+ disconnectConnector: async (req) => {
+ throw new Error("unimplemented");
+ },
+ },
+};
+
+const server: AgentUIServer = {
+ ...chatAndBuilder,
+ catalog,
+};
+```
+
+The stubs above are enough to show the Settings shell; fill in persistence as you add each surface. On `type: "trueforge"`, the same `catalog` field overrides the adapter defaults — see [Connect with TrueForge](/ui-sdk/setup-custom-servers/connect-with-trueforge#custom-catalog).
+
+## Next
+
+
+
+ Builder pickers are separate from catalog CRUD.
+
+
+
+ Full `CatalogServer` reference.
+
+
diff --git a/docs/ui-sdk/setup-custom-servers/chat.mdx b/docs/ui-sdk/setup-custom-servers/chat.mdx
new file mode 100644
index 000000000..04096b9a9
--- /dev/null
+++ b/docs/ui-sdk/setup-custom-servers/chat.mdx
@@ -0,0 +1,94 @@
+---
+title: "Chat"
+description: "Implement AgentChatServer — sessions, turns, history, cancel, and resume."
+---
+
+`AgentChatServer` is the runtime the chat UI calls. All operations are keyed by a flat `sessionId`. Method signatures live on [Server contract](/ui-sdk/setup-custom-servers/server-contract#agentchatserver); this page is the cookbook.
+
+## Sessions
+
+| Method | Role |
+| --- | --- |
+| `createSession` | Open a conversation (`agentName?`, `agentSpec?`, `title?`) |
+| `listSessions` | Paginated session list for the thread sidebar |
+| `getSession` | Load one session by id |
+| `updateSession` | Update title or mutable agent spec |
+| `deleteSession?` | Optional — omit to hide delete in the session list |
+
+```ts
+async createSession({ agentName, agentSpec, title } = {}) {
+ return {
+ id: crypto.randomUUID(),
+ agentName: agentName ?? null,
+ agentSpec,
+ title: title ?? null,
+ isMutable: agentSpec != null,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ };
+}
+```
+
+List methods return `{ data, nextPageToken? }` — see [Pagination](/ui-sdk/setup-custom-servers/custom-server#pagination) on the landing page.
+
+## Turns
+
+| Method | Role |
+| --- | --- |
+| `createTurn` | Start a turn and stream `{ sequenceNumber, event }` envelopes |
+| `listTurns` | Paginated turns in a session |
+| `getTurn` | Load one turn (state, input, timestamps) |
+| `cancelSession` | Cancel the in-flight turn for a session |
+
+`createTurn` accepts `input?: TurnInputItem[]` — a plain chat message, a tool approval, or a tool response. Branch on `type` as described in [Turn input](/ui-sdk/setup-custom-servers/server-contract#turn-input).
+
+The minimum stream is covered on [Bring your own server](/ui-sdk/setup-custom-servers/custom-server#streaming). For tool calls, approvals, threads, and sandboxes, implement the shapes in [Streaming events](/ui-sdk/reference/events).
+
+```ts
+async *createTurn({ sessionId, input, abortSignal }) {
+ let seq = 0;
+ for await (const event of myBackendStream({ sessionId, input, abortSignal })) {
+ yield { sequenceNumber: seq++, event };
+ }
+}
+```
+
+## Resume a live turn
+
+`subscribeToTurn` is optional. When present, a reconnecting client passes the last `sequenceNumber` it saw as `afterSequenceNumber`, and you replay only what follows — not the whole turn.
+
+```ts
+async *subscribeToTurn({ sessionId, turnId, afterSequenceNumber, abortSignal }) {
+ let seq = (afterSequenceNumber ?? -1) + 1;
+ for await (const event of myResumeStream({ sessionId, turnId, afterSequenceNumber, abortSignal })) {
+ yield { sequenceNumber: seq++, event };
+ }
+}
+```
+
+Pass the same `abortSignal` you use on `createTurn` to tear the subscription down.
+
+## History
+
+| Method | Role |
+| --- | --- |
+| `listEvents` | Hydrate session history (`pageToken?`, `lastTurnId?`, `limit?`) |
+| `listTurnEvents?` | Optional — events for a single in-flight turn when your backend can serve them per turn |
+
+`listEvents` returns session-level wrappers that identify the turn; see [Streaming events](/ui-sdk/reference/events#the-unions) for how history differs from the live stream (no deltas — the assembled `model.message` is stored instead).
+
+## Sandbox downloads
+
+`downloadSandboxFile` is optional. Return a `Blob` when agents produce artifacts users should download. It receives both `turnId` and `sandboxId` because backends address sandboxes differently — see the note on [Server contract](/ui-sdk/setup-custom-servers/server-contract#agentchatserver).
+
+## Next
+
+
+
+ Attach settings catalogs.
+
+
+
+ Builder pickers and the agent library.
+
+
diff --git a/docs/ui-sdk/setup-custom-servers/connect-with-trueforge.mdx b/docs/ui-sdk/setup-custom-servers/connect-with-trueforge.mdx
new file mode 100644
index 000000000..85cec7df3
--- /dev/null
+++ b/docs/ui-sdk/setup-custom-servers/connect-with-trueforge.mdx
@@ -0,0 +1,96 @@
+---
+title: "Connect with TrueForge"
+description: "Point TrueforgeUI at a TrueForge server"
+---
+
+Point `TrueforgeUI` at a TrueForge server. The SDK builds the HTTP client, and composes chat + builder + default settings catalogs into an `AgentUIServer`.
+
+
+## Render against TrueForge
+
+Omit `baseUrl` to talk same-origin (or through a reverse proxy). Pass an absolute URL when the UI and harness are on different origins.
+
+```tsx App.tsx
+import { TrueforgeUI } from "@truefoundry/trueforge-ui";
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+```
+
+Because this config is resolved asynchronously, the SDK shows a brief loading indicator before the chat appears.
+
+
+## Auth
+
+How the UI authenticates to TrueForge is separate from whether the **server** itself requires login.
+
+| Approach | When to use |
+| --- | --- |
+| Omit `token` and `fetch` | Same-origin or a proxy that already attaches credentials |
+| `token` | Bearer auth for embeds and remote APIs |
+| Custom `fetch` | Cookie sessions, OIDC-aware hosts, or other request interceptors |
+
+**Bearer token:**
+
+```tsx
+
+```
+
+**Cookie / same-origin hosts** — omit `token` and inject `fetch` (for example an auth-aware wrapper that follows OIDC cookies):
+
+```tsx
+
+```
+
+To leave login off on the TrueForge server, or to turn on OIDC for a shared deployment, see [Setup Login](/authentication/overview). That page covers server-side configuration; this page only covers how the UI SDK sends credentials.
+
+## Custom catalog
+
+Pass `catalog` to override the adapter's default settings catalogs. Without it, those Settings surfaces use the built-in catalogs from the TrueForge adapter (when the server exposes them). Supplying your own `CatalogServer` replaces that default.
+
+```tsx
+import type { CatalogServer } from "@truefoundry/trueforge-ui";
+
+const catalog: CatalogServer = {
+ modelCatalog: {
+ /* ... */
+ },
+ connectorCatalog: {
+ /* ... */
+ },
+ // skillCatalog and sandboxCatalog are optional
+};
+
+
+```
+
+The full port — model providers, connectors, skills, and sandboxes — is on [Settings catalog](/ui-sdk/reference/catalog). For a hand-written server that attaches catalog the same way, see [Catalog](/ui-sdk/setup-custom-servers/catalog) under Bring your own server.
diff --git a/docs/ui-sdk/setup-custom-servers/connect-with-truefoundry.mdx b/docs/ui-sdk/setup-custom-servers/connect-with-truefoundry.mdx
index 6b6bf5721..76d2456f2 100644
--- a/docs/ui-sdk/setup-custom-servers/connect-with-truefoundry.mdx
+++ b/docs/ui-sdk/setup-custom-servers/connect-with-truefoundry.mdx
@@ -1,8 +1,11 @@
---
title: "Connect with TrueFoundry"
+description: "Point TrueforgeUI at TrueFoundry Control Plane + Gateway, or compose hosted chat with your own builder."
---
-The zero-config path. The SDK builds the agent UI server from your API key and control plane URL; the gateway URL is optional and resolved from the control plane when omitted.
+The zero-config path for TrueFoundry. For TrueForge or a hand-written `AgentUIServer`, see [Overview](/ui-sdk/setup-custom-servers/overview).
+
+The SDK builds the agent UI server from your API key and control plane URL; the gateway URL is optional and resolved from the control plane when omitted.
```jsx App.tsx
import { TrueforgeUI } from "@truefoundry/trueforge-ui";
@@ -16,7 +19,6 @@ export default function App() {
apiKey: {{API_KEY}},
controlPlaneURL: {{CONTROL_PLANE_URL}},
}}
- layout="sidebar"
/>
);
@@ -25,76 +27,10 @@ export default function App() {
Add `gatewayPlaneURL` alongside `controlPlaneURL` to target a specific gateway.
-Because this config is resolved asynchronously, the SDK shows a brief loading indicator before the chat appears. The other two paths mount immediately.
-
-## Build the server yourself
-
-If you need to hold the resolved `AgentUIServer` — to wrap it, log through it, or pass it around before mounting — build it with `createTrueFoundryAgentUIServer`. It returns a ready server (gateway chat plus the Control Plane builder lists) that you hand to `server` directly, so there is no async resolution and no loading indicator on mount.
-
-```tsx App.tsx
-import { useEffect, useState } from "react";
-import { TrueforgeUI } from "@truefoundry/trueforge-ui";
-import {
- createTrueFoundryAgentUIServer,
- type TrueFoundryAgentUIServer,
-} from "@truefoundry/assistant-ui-runtime/plugins/truefoundry-agent-server-adapter";
-
-export default function App() {
- const [server, setServer] = useState(null);
-
- useEffect(() => {
- createTrueFoundryAgentUIServer({
- apiKey: import.meta.env.VITE_TFY_API_KEY,
- cpURL: import.meta.env.VITE_TFY_CONTROL_PLANE_URL,
- }).then(setServer);
- }, []);
-
- if (server === null) {
- return null;
- }
-
- return (
-
-
-
- );
-}
-```
-
-`gatewayURL` is optional — when omitted it is resolved from `cpURL`.
-
-## TrueFoundry chat, your own builder
+{/* TODO: Add links for connecting to TrueFoundry and creating an account. */}
-To keep TrueFoundry's hosted chat streaming but serve the builder surfaces — models, skills, connectors, and the agent library — from your own backend, compose the gateway chat port with your callbacks through `createTrueFoundryServer`. The stubs below return empty lists, which is enough to mount; fill them in as you add agent management.
-
-```jsx App.tsx
-import { TrueforgeUI, createTrueFoundryServer } from "@truefoundry/trueforge-ui";
-import { createTrueFoundryChatServer } from "@truefoundry/assistant-ui-runtime/plugins/truefoundry-agent-server-adapter";
-
-const chatServer = createTrueFoundryChatServer({
- apiKey: import.meta.env.VITE_TFY_API_KEY,
- baseUrl: import.meta.env.VITE_TFY_GATEWAY_URL,
-});
+Because this config is resolved asynchronously, the SDK shows a brief loading indicator before the chat appears. The other two paths mount immediately.
-const server = createTrueFoundryServer({
- chatServer,
- getCapabilities: async () => ({
- data: { sandbox: { enabled: false }, skill: { enabled: false } },
- }),
- getModels: async () => [],
- getSkills: async () => [],
- getMcp: async () => [],
- searchAgents: async () => [],
- saveAgent: async () => ({}),
-});
-export default function App() {
- return (
-
-
-
- );
-}
-```
To own chat streaming as well — every call, including the `createTurn` event stream, served by your backend — implement the full interface instead: [Bring your own server](/ui-sdk/setup-custom-servers/custom-server).
\ No newline at end of file
diff --git a/docs/ui-sdk/setup-custom-servers/core-concepts.mdx b/docs/ui-sdk/setup-custom-servers/core-concepts.mdx
new file mode 100644
index 000000000..bb443e566
--- /dev/null
+++ b/docs/ui-sdk/setup-custom-servers/core-concepts.mdx
@@ -0,0 +1,82 @@
+---
+title: "Core concepts"
+description: "Sessions, turns, and the event families a custom AgentUIServer must speak."
+---
+
+The UI only speaks [`AgentUIServer`](/ui-sdk/setup-custom-servers/server-contract). Your backend can be TrueForge HTTP, a BFF, or something else — as long as it implements the port and emits event shapes the UI folds into a transcript.
+
+Interaction follows **Agent → Session → Turn → Event → Delta**. The product [SDK Overview](/api/overview) walks that hierarchy with a running example; this page maps each layer to the methods `TrueforgeUI` calls.
+
+| Layer | What it is | UI methods |
+| --- | --- | --- |
+| **Agent** | A saved definition (model, tools, config) — not a running process | Builder: `searchAgents`, `saveAgent`; sessions may carry `agentName` / `agentSpec` |
+| **Session** | One conversation / issue | `createSession`, `listSessions`, `getSession`, `updateSession` |
+| **Turn** | One request boundary (message, approval, or tool response) | `createTurn`, `listTurns`, `getTurn`, `cancelSession` |
+| **Event** | What the stream (and history) record | Yielded from `createTurn` / `subscribeToTurn`; hydrated via `listEvents` |
+| **Delta** | Incremental chunks that merge into a parent event by `id` | e.g. `model.message.delta` → `model.message` |
+
+Full signatures are on [Server contract](/ui-sdk/setup-custom-servers/server-contract). The stream protocol is on [Streaming events](/ui-sdk/reference/events).
+
+## Sessions
+
+A session is one conversation context. Persist its `id` so the user can return later. Only one turn runs inside a session at a time.
+
+`Session` requires `id`, `isMutable`, `createdAt`, and `updatedAt` (ISO strings). Set `isMutable: true` while the agent spec may still be edited — that is what permits `updateSession`.
+
+## Turns
+
+A turn is one back-and-forth boundary. Each user message, approval, or tool response is a new `createTurn` call. Turns chain via `previousTurnId` (often `"auto"` on TrueForge); the agent sees earlier turns without you resending history.
+
+Input arrives as [`TurnInputItem`](/ui-sdk/setup-custom-servers/server-contract#turn-input): `user.message`, `user.tool_approval`, or `user.tool_response`. Branch on `type`.
+
+A turn ends in `done`, `cancelled`, or `error`. A `done` turn may still carry `requiredActions` — that means it is waiting on the user, not finished outright.
+
+## Events
+
+`createTurn` yields `{ sequenceNumber, event }` envelopes. `sequenceNumber` must increase monotonically within the turn so [`subscribeToTurn`](/ui-sdk/setup-custom-servers/server-contract#agentchatserver) can resume from `afterSequenceNumber`.
+
+### Lifecycle
+
+Every turn is bracketed by `turn.created` and exactly one `turn.done`. A stream that ends without `turn.done` leaves the UI showing the turn as still running.
+
+### Content
+
+| Event | Role |
+| --- | --- |
+| `model.message` | A complete assistant message |
+| `model.message.delta` | Incremental text — the _increment_, not the running total; reuse one `id` across deltas for the same message |
+
+### Pausing for the user
+
+| Event | User reply on the next `createTurn` |
+| --- | --- |
+| `tool.approval_required` | `user.tool_approval` |
+| `tool.response_required` | `user.tool_response` (ask-user questions) |
+| `mcp.auth_required` | Complete OAuth, then start the next turn — see [MCP OAuth](/ui-sdk/setup-custom-ui/mcp-oauth) |
+
+These pause events also appear in `requiredActions` on a `done` turn state.
+
+### MCP, sandbox, and sub-agents
+
+| Event | Role |
+| --- | --- |
+| `mcp.initialize` | MCP servers connected for the thread |
+| `tool.response` | Result of a tool call |
+| `sandbox.created` | Sandbox provisioned — use `sandboxId` with optional `downloadSandboxFile` |
+| `thread.created` / `thread.done` | Sub-agent threads; the UI groups events by `threadId` |
+
+### Generative UI
+
+Generative UI is enabled on the agent via `config` (for example `generativeUi.enabled` on [`AgentSpec`](/ui-sdk/setup-custom-servers/server-contract#agentspec)) and rendered from message content. For how TrueForge authors it, see [Generative UI](/create-agent/generative-ui) — do not reinvent a parallel protocol in the UI SDK.
+
+## Next
+
+
+
+ Implement session and turn operations.
+
+
+
+ Full event shapes and unions.
+
+
diff --git a/docs/ui-sdk/setup-custom-servers/custom-server.mdx b/docs/ui-sdk/setup-custom-servers/custom-server.mdx
index 7c68afb20..9ce3368a5 100644
--- a/docs/ui-sdk/setup-custom-servers/custom-server.mdx
+++ b/docs/ui-sdk/setup-custom-servers/custom-server.mdx
@@ -3,14 +3,10 @@ title: "Bring your own server"
description: "Implement AgentUIServer and pass the object straight to the component."
---
-Use this when your backend owns the whole contract — sessions, chat streaming, and the agent builder. You implement `AgentUIServer` and pass the object straight to the component, with no `type` wrapper: every call the UI makes, including the `createTurn` event stream, hits your endpoints. Nothing here depends on TrueFoundry.
+Use this when your backend owns the whole contract — sessions, chat streaming, and the agent builder. You implement `AgentUIServer` and pass the object straight to the component, every call the UI makes, including the `createTurn` event stream, hits your endpoints.
Because `server` accepts an `AgentUIServer` directly, you can front any backend — including your own proxy that keeps API keys server-side. The object is used immediately, so there is no loading indicator on mount.
-
- To keep TrueFoundry's hosted chat and supply only your own builder callbacks, use the composition helpers on [Connect with TrueFoundry](/ui-sdk/setup-custom-servers/connect-with-truefoundry#truefoundry-chat-your-own-builder) instead. This page assumes your backend serves everything.
-
-
## Minimal implementation
The shape below is the smallest thing that mounts. Optional methods are omitted; required ones must exist even if they only throw in paths your UI never reaches.
@@ -92,7 +88,7 @@ export const server: AgentUIServer = {
Declaring `sandbox` and `skill` as disabled is the honest starting point — the UI then hides those affordances instead of offering controls your backend cannot serve. Flip them on as you implement each.
```tsx title="App.tsx"
-
+
```
## Streaming
@@ -141,7 +137,7 @@ async *createTurn({ sessionId, input }) {
Two things are easy to get wrong. Deltas carry the _increment_ rather than the accumulated text, and every delta for one message shares a single `id`. And a stream that ends without a `turn.done` leaves the UI showing the turn as still running.
-[Streaming events](/ui-sdk/reference/events) documents the full protocol — tool calls, approval prompts, sub-agent threads, and sandbox artifacts.
+[Streaming events](/ui-sdk/reference/events) documents the full protocol — tool calls, approval prompts, sub-agent threads, and sandbox artifacts. For how sessions, turns, and events fit together, see [Core concepts](/ui-sdk/setup-custom-servers/core-concepts).
## Pagination
@@ -179,33 +175,42 @@ Spreading `nextPageToken` in only when present keeps the result valid under `exa
- Attach a [`CatalogServer`](/ui-sdk/reference/catalog) to enable the settings UI — models, connectors, skills, and sandboxes. Without it those surfaces do not render.
+ Attach a [`CatalogServer`](/ui-sdk/reference/catalog) to enable the settings UI — models, connectors, skills, and sandboxes. Without it those surfaces do not render. See [Catalog](/ui-sdk/setup-custom-servers/catalog).
-## Widening the types
+## Attachments
-The interfaces are generic so you can carry backend-specific fields through the UI without casting. `AgentUIServer` itself is a plain alias, so compose the widened chat and builder ports directly:
+Attachment handling defaults to `trueFoundryAttachmentAdapter`. Override it through the `adapters` prop on `TrueforgeUI` if your backend stores uploads differently.
-```ts
-interface MyModel extends ModelSelection {
- modelId: string;
-}
+```tsx
+
+```
-type MySkillMount = { fqn: string; preload: boolean };
-type MySpec = AgentSpec;
+## What's next
-const server: AgentChatServer & AgentBuilderServer = {
- /* ... */
-};
-```
+
+
+ Sessions, turns, and the event families the UI understands.
+
-See [Widening the types](/ui-sdk/setup-custom-servers/server-contract#widening-the-types) for the full set of parameters.
+
+ Session and turn operations on `AgentChatServer`.
+
-## Attachments
+
+ Attach settings catalogs for models, connectors, skills, and sandboxes.
+
-Attachment handling defaults to `trueFoundryAttachmentAdapter`. Override it through the `adapters` prop on `TrueforgeUI` if your backend stores uploads differently.
+
+ Builder pickers, capabilities, and the agent library.
+
-```tsx
-
-```
\ No newline at end of file
+
+ Compose chat, builder, and catalog into one server.
+
+
+
+ Full method signatures and type widening.
+
+
diff --git a/docs/ui-sdk/setup-custom-servers/overview.mdx b/docs/ui-sdk/setup-custom-servers/overview.mdx
new file mode 100644
index 000000000..ae9764e18
--- /dev/null
+++ b/docs/ui-sdk/setup-custom-servers/overview.mdx
@@ -0,0 +1,90 @@
+---
+title: "Overview"
+description: "Choose how TrueforgeUI talks to a backend: TrueForge, TrueFoundry, or your own AgentUIServer."
+---
+
+The `server` prop accepts a built-in config object or an object implementing `AgentUIServer`. TrueForge is the default path for a self-hosted harness; TrueFoundry wires Control Plane + Gateway; a hand-written `AgentUIServer` is used when your backend owns the whole contract.
+
+```ts
+type TrueforgeServerConfig =
+ | {
+ type: "trueforge";
+ baseUrl?: string;
+ token?: string;
+ fetch?: typeof fetch;
+ catalog?: CatalogServer;
+ }
+ | {
+ type: "truefoundry";
+ apiKey: string;
+ controlPlaneURL: string;
+ gatewayPlaneURL?: string;
+ catalog?: CatalogServer;
+ }
+ | AgentUIServer;
+```
+
+There is no `type: "custom"` — pass the `AgentUIServer` object directly.
+
+
+ Built-in configs (`trueforge` / `truefoundry`) resolve asynchronously, so the SDK renders a brief loading indicator first. An `AgentUIServer` object is used immediately and mounts without that delay.
+
+
+## TrueForge (default)
+
+Point `TrueforgeUI` at a TrueForge server. Omit `baseUrl` to talk same-origin; pass `token` or a custom `fetch` when the host owns auth.
+
+```tsx
+
+```
+
+See [Connect with TrueForge](/ui-sdk/setup-custom-servers/connect-with-trueforge).
+
+## TrueFoundry
+
+The SDK builds the agent UI server from your API key and control plane URL.
+
+```tsx
+
+```
+
+See [Connect with TrueFoundry](/ui-sdk/setup-custom-servers/connect-with-truefoundry).
+
+## Bring your own server
+
+Implement `AgentUIServer` and pass the object straight to the component — every call the UI makes, including the `createTurn` event stream, hits your endpoints.
+
+```tsx
+
+```
+
+Start with [Bring your own server](/ui-sdk/setup-custom-servers/custom-server). The typed contract lives on [Server contract](/ui-sdk/setup-custom-servers/server-contract).
+
+
+
+ Default harness path — auth and optional catalog overrides.
+
+
+
+ Control Plane + Gateway, or chat from TrueFoundry with your own builder.
+
+
+
+ Implement `AgentUIServer` and pass it straight to `TrueforgeUI`.
+
+
+
+ Chat, builder, and catalog method signatures.
+
+
diff --git a/docs/ui-sdk/setup-custom-servers/putting-it-together.mdx b/docs/ui-sdk/setup-custom-servers/putting-it-together.mdx
new file mode 100644
index 000000000..4ddfb69ff
--- /dev/null
+++ b/docs/ui-sdk/setup-custom-servers/putting-it-together.mdx
@@ -0,0 +1,89 @@
+---
+title: "Putting it together"
+description: "Compose chat, builder, and catalog into a complete AgentUIServer."
+---
+
+An `AgentUIServer` is chat and builder, plus an optional settings catalog:
+
+```ts
+type AgentUIServer = AgentChatServer & AgentBuilderServer & { catalog?: CatalogServer };
+```
+
+Build it in layers. Each step mounts; add the next when your backend can serve it.
+
+## 1. Chat-only stub
+
+Implement the [minimal chat + builder surface](/ui-sdk/setup-custom-servers/custom-server#minimal-implementation) so `TrueforgeUI` mounts. Disable sandbox and skills in `getCapabilities` until those paths exist. Empty `searchAgents` / `getSkills` / `getMcp` is fine for a first chat.
+
+## 2. Add builder callbacks
+
+Fill `getModels`, `getSkills`, `getMcp`, `searchAgents`, and `saveAgent` so the composer and library work. See [Agents](/ui-sdk/setup-custom-servers/agents).
+
+## 3. Attach catalog
+
+Add a [`CatalogServer`](/ui-sdk/setup-custom-servers/catalog) when Settings should manage providers and connectors:
+
+```ts
+const server: AgentUIServer = {
+ ...chatServerMethods,
+ ...builderMethods,
+ catalog,
+};
+```
+
+```tsx
+
+```
+
+## End-to-end sketch
+
+```ts
+import type {
+ AgentBuilderServer,
+ AgentChatServer,
+ AgentUIServer,
+ CatalogServer,
+} from "@truefoundry/trueforge-ui";
+
+const chat: AgentChatServer = {
+ /* createSession, listSessions, getSession, updateSession,
+ createTurn, cancelSession, listTurns, getTurn, listEvents */
+};
+
+const builder: AgentBuilderServer = {
+ /* getCapabilities, getModels, getSkills, getMcp, searchAgents, saveAgent */
+};
+
+const catalog: CatalogServer = {
+ modelCatalog: {
+ /* ... */
+ },
+ connectorCatalog: {
+ /* ... */
+ },
+};
+
+export const server: AgentUIServer = {
+ ...chat,
+ ...builder,
+ catalog,
+};
+```
+
+Widen types when your backend returns richer rows — see [Widening the types](/ui-sdk/setup-custom-servers/server-contract#widening-the-types).
+
+
+ To keep TrueFoundry's hosted chat streaming but serve builder surfaces from your own backend, compose with `createTrueFoundryServer` instead of hand-writing chat — [TrueFoundry chat, your own builder](/ui-sdk/setup-custom-servers/connect-with-truefoundry#truefoundry-chat-your-own-builder).
+
+
+## Next
+
+
+
+ Every method signature.
+
+
+
+ TrueForge, TrueFoundry, or bring your own.
+
+
diff --git a/docs/ui-sdk/setup-custom-servers/server-contract.mdx b/docs/ui-sdk/setup-custom-servers/server-contract.mdx
index 95700c0b4..910cdabf7 100644
--- a/docs/ui-sdk/setup-custom-servers/server-contract.mdx
+++ b/docs/ui-sdk/setup-custom-servers/server-contract.mdx
@@ -315,15 +315,23 @@ const server: AgentChatServer & AgentBuilderServer = {
};
```
-The UI keeps reading only the fields it knows about; your own components — supplied through [slot overrides](/ui-sdk/guides/slot-overrides) — can read the rest.
+The UI keeps reading only the fields it knows about; your own components — supplied through [slot overrides](/ui-sdk/setup-custom-ui/slot-overrides) — can read the rest.
## Next
-
+
+ TrueForge, TrueFoundry, or bring your own.
+
+
+
A minimal `AgentUIServer` you can copy.
+
+ Compose chat, builder, and catalog.
+
+
The event protocol `createTurn` yields.
diff --git a/docs/ui-sdk/setup-custom-ui/slot-overrides.mdx b/docs/ui-sdk/setup-custom-ui/slot-overrides.mdx
index bc15fc320..144e52225 100644
--- a/docs/ui-sdk/setup-custom-ui/slot-overrides.mdx
+++ b/docs/ui-sdk/setup-custom-ui/slot-overrides.mdx
@@ -91,7 +91,7 @@ honor the props you receive — several are rendered nodes (`actionBar`, `error`
`Button` and `IconButton` publish their prop types for typing your own components. The
buttons themselves are restyled through semantic tokens and CSS rather than replaced — see
- [Theming](/ui-sdk/guides/theming).
+ [Theming](/ui-sdk/setup-custom-ui/custom-theme).
## Discovering slot names