diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/.env.example b/apps/agentstack-sdk-ts/examples/chat-ui/.env.example
new file mode 100644
index 000000000..55b1a7aed
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/.env.example
@@ -0,0 +1,4 @@
+VITE_AGENTSTACK_BASE_URL="http://localhost:8333"
+
+VITE_AGENTSTACK_PROVIDER_ID="983ab5fd-d7c9-0be1-77d9-442a68c5949e"
+
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/.gitignore b/apps/agentstack-sdk-ts/examples/chat-ui/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/eslint.config.js b/apps/agentstack-sdk-ts/examples/chat-ui/eslint.config.js
new file mode 100644
index 000000000..325178462
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/eslint.config.js
@@ -0,0 +1,10 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import baseConfig from '@i-am-bee/lint-config/eslint';
+import { defineConfig } from 'eslint/config';
+import reactHooks from 'eslint-plugin-react-hooks';
+
+export default defineConfig([...baseConfig, reactHooks.configs.flat['recommended-latest']]);
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/index.html b/apps/agentstack-sdk-ts/examples/chat-ui/index.html
new file mode 100644
index 000000000..01fc80e3e
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+ Agent Stack Chat Example
+
+
+
+
+
+
+
+
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/package.json b/apps/agentstack-sdk-ts/examples/chat-ui/package.json
new file mode 100644
index 000000000..20b388b0a
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@i-am-bee/chat-ui-example",
+ "author": "IBM Corp.",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@a2a-js/sdk": "^0.3.10",
+ "agentstack-sdk": "^0.6.1",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4"
+ },
+ "devDependencies": {
+ "@i-am-bee/lint-config": "workspace:*",
+ "@types/node": "^22.19.10",
+ "@types/react": "^19.2.13",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.4",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "eslint": "^9.39.2",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "prettier": "^3.8.1",
+ "stylelint": "^16.26.1",
+ "typescript": "5.9.3",
+ "vite": "^7.3.1"
+ },
+ "packageManager": "pnpm@9.9.0"
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/prettier.config.js b/apps/agentstack-sdk-ts/examples/chat-ui/prettier.config.js
new file mode 100644
index 000000000..d6a490d19
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/prettier.config.js
@@ -0,0 +1,6 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export { default } from '@i-am-bee/lint-config/prettier';
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/App.tsx b/apps/agentstack-sdk-ts/examples/chat-ui/src/App.tsx
new file mode 100644
index 000000000..ea6c00d5f
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/App.tsx
@@ -0,0 +1,117 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { type SubmitEvent, useState } from 'react';
+
+import { useAgent } from './client';
+import { BASE_URL, PROVIDER_ID } from './constants';
+import type { ChatMessage } from './types';
+import { createMessage } from './utils';
+
+export function App() {
+ const [messages, setMessages] = useState([]);
+ const [input, setInput] = useState('');
+ const [isSending, setIsSending] = useState(false);
+
+ const { session, isInitializing, error, sendMessage } = useAgent();
+
+ const isError = Boolean(error);
+ const isSubmitDisabled = isInitializing || isSending || isError || !input.trim();
+
+ const handleSubmit = async (event: SubmitEvent) => {
+ event.preventDefault();
+
+ if (isSubmitDisabled) {
+ return;
+ }
+
+ const text = input.trim();
+
+ setMessages((prevMessages) => [...prevMessages, createMessage({ role: 'user', text })]);
+ setInput('');
+ setIsSending(true);
+
+ try {
+ const response = await sendMessage({ text });
+
+ setMessages((prevMessages) => [
+ ...prevMessages,
+ createMessage({ role: 'agent', text: response.text || 'No response from agent.' }),
+ ]);
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Failed to send message';
+
+ setMessages((prevMessages) => [...prevMessages, createMessage({ role: 'agent', text: `Error: ${message}` })]);
+ } finally {
+ setIsSending(false);
+ }
+ };
+
+ return (
+
+ Agent Stack Chat Example
+
+
+
+ Base URL: {BASE_URL}
+
+
+
+ Provider ID: {PROVIDER_ID}
+
+
+
+ Context ID: {session?.contextId}
+
+
+
+
+
+
+
+ {messages.length > 0 && (
+
+ {messages.map((message) => (
+
+
+
+ {message.text}
+
+ ))}
+
+ )}
+
+
+
+ Status:{' '}
+ {isError ? 'Error' : isInitializing ? 'Connecting to agent…' : isSending ? 'Agent is thinking…' : 'Ready'}
+
+
+ {isError && (
+
+ Error: {error}
+
+ )}
+
+
+
+
+ );
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/api.ts b/apps/agentstack-sdk-ts/examples/chat-ui/src/api.ts
new file mode 100644
index 000000000..876783e8c
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/api.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { buildApiClient, unwrapResult } from 'agentstack-sdk';
+
+import { BASE_URL, PROVIDER_ID } from './constants';
+
+export const api = buildApiClient({ baseUrl: BASE_URL });
+
+export async function createContext() {
+ const result = await api.createContext({ provider_id: PROVIDER_ID });
+
+ return unwrapResult(result);
+}
+
+export async function createContextToken(contextId: string) {
+ const result = await api.createContextToken({
+ context_id: contextId,
+ grant_global_permissions: {
+ a2a_proxy: [PROVIDER_ID],
+ llm: ['*'],
+ },
+ grant_context_permissions: {
+ context_data: ['*'],
+ },
+ });
+
+ return unwrapResult(result);
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/client.ts b/apps/agentstack-sdk-ts/examples/chat-ui/src/client.ts
new file mode 100644
index 000000000..21a404c97
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/client.ts
@@ -0,0 +1,142 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {
+ ClientFactory,
+ ClientFactoryOptions,
+ DefaultAgentCardResolver,
+ JsonRpcTransportFactory,
+} from '@a2a-js/sdk/client';
+import { type ContextToken, createAuthenticatedFetch, getAgentCardPath } from 'agentstack-sdk';
+import { useEffect, useState } from 'react';
+
+import { createContext, createContextToken } from './api';
+import { BASE_URL, PROVIDER_ID } from './constants';
+import type { Session } from './types';
+import { extractTextFromMessage, resolveAgentMetadata } from './utils';
+
+async function ensureSession() {
+ if (!BASE_URL || !PROVIDER_ID) {
+ throw new Error(`Missing required environment variables.`);
+ }
+
+ const context = await createContext();
+ const contextToken = await createContextToken(context.id);
+ const client = await createClient(contextToken);
+ const metadata = await resolveAgentMetadata({ client, contextToken });
+
+ return {
+ client,
+ contextId: context.id,
+ metadata,
+ };
+}
+
+async function createClient(contextToken: ContextToken) {
+ const fetchImpl = createAuthenticatedFetch(contextToken.token);
+
+ const factory = new ClientFactory(
+ ClientFactoryOptions.createFrom(ClientFactoryOptions.default, {
+ transports: [new JsonRpcTransportFactory({ fetchImpl })],
+ cardResolver: new DefaultAgentCardResolver({ fetchImpl }),
+ }),
+ );
+
+ const agentCardPath = getAgentCardPath(PROVIDER_ID);
+ const client = await factory.createFromUrl(BASE_URL, agentCardPath);
+
+ return client;
+}
+
+export function useAgent() {
+ const [session, setSession] = useState();
+ const [isInitializing, setIsInitializing] = useState(false);
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ if (session) {
+ return;
+ }
+
+ let cancelled = false;
+
+ (async () => {
+ try {
+ setIsInitializing(true);
+
+ const session = await ensureSession();
+
+ if (cancelled) {
+ return;
+ }
+
+ setSession(session);
+ } catch (error) {
+ if (cancelled) {
+ return;
+ }
+
+ const message = error instanceof Error ? error.message : 'Failed to connect to agent.';
+
+ setError(message);
+ } finally {
+ if (!cancelled) {
+ setIsInitializing(false);
+ }
+ }
+ })();
+
+ return () => {
+ cancelled = true;
+ };
+ }, [session]);
+
+ const sendMessage = async ({ text }: { text: string }) => {
+ if (!session) {
+ throw new Error('Agent is not ready yet.');
+ }
+
+ const { client, contextId, metadata } = session;
+
+ const runStream = async () => {
+ const stream = client.sendMessageStream({
+ message: {
+ kind: 'message',
+ role: 'user',
+ messageId: crypto.randomUUID(),
+ contextId,
+ parts: [{ kind: 'text', text }],
+ metadata,
+ },
+ });
+
+ let agentText = '';
+
+ for await (const event of stream) {
+ if (event.kind === 'status-update' || event.kind === 'message') {
+ const message = event.kind === 'message' ? event : event.status.message;
+ const text = extractTextFromMessage(message);
+
+ if (text) {
+ agentText += text;
+ }
+ }
+ }
+
+ return {
+ text: agentText,
+ };
+ };
+
+ return await runStream();
+ };
+
+ return {
+ session,
+ isInitializing,
+ error,
+ sendMessage,
+ };
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/constants.ts b/apps/agentstack-sdk-ts/examples/chat-ui/src/constants.ts
new file mode 100644
index 000000000..f0774ce33
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/constants.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export const BASE_URL = import.meta.env.VITE_AGENTSTACK_BASE_URL;
+
+export const PROVIDER_ID = import.meta.env.VITE_AGENTSTACK_PROVIDER_ID;
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/main.tsx b/apps/agentstack-sdk-ts/examples/chat-ui/src/main.tsx
new file mode 100644
index 000000000..43d4ce48e
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/main.tsx
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import './styles.css';
+
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+
+import { App } from './App';
+
+const rootElement = document.getElementById('root')!;
+
+createRoot(rootElement).render(
+
+
+ ,
+);
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/styles.css b/apps/agentstack-sdk-ts/examples/chat-ui/src/styles.css
new file mode 100644
index 000000000..955f394b9
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/styles.css
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+:root {
+ font-family: sans-serif;
+ color: #161616;
+ background: #f3f4f6;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+}
+
+.app {
+ max-inline-size: 48rem;
+ margin: 0 auto;
+ padding: 1rem;
+ display: grid;
+ gap: 1rem;
+}
+
+.heading {
+ margin: 0;
+ font-size: 1.5rem;
+}
+
+.meta {
+ font-size: 0.875rem;
+ display: grid;
+ gap: 0.5rem;
+}
+
+.meta p {
+ margin: 0;
+}
+
+.error {
+ color: #da1e28;
+}
+
+.controls {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.button {
+ padding: 0.5rem 0.75rem;
+ background: #161616;
+ color: #ffffff;
+ border: 1px solid #161616;
+ border-radius: 0.5rem;
+ font-size: 1rem;
+ line-height: 1.375;
+ cursor: pointer;
+}
+
+.button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.messages {
+ display: grid;
+ gap: 0.5rem;
+}
+
+.message {
+ border-radius: 0.5rem;
+ padding: 0.5rem;
+ border: 1px solid;
+}
+
+.message.user {
+ border-color: #78a9ff;
+ background-color: #edf5ff;
+}
+
+.message.agent {
+ border-color: #42be65;
+ background-color: #defbe6;
+}
+
+.message header {
+ margin-block-end: 0.5rem;
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ color: #8d8d8d;
+}
+
+.message p {
+ margin: 0;
+ white-space: pre-wrap;
+}
+
+.form {
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 0.5rem;
+}
+
+.input {
+ inline-size: 100%;
+ padding: 0.5rem;
+ border: 1px solid #d1d1d1;
+ border-radius: 0.5rem;
+ font-size: 1rem;
+ line-height: 1.375;
+}
\ No newline at end of file
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/types.ts b/apps/agentstack-sdk-ts/examples/chat-ui/src/types.ts
new file mode 100644
index 000000000..0f70c6817
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/types.ts
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type { Client } from '@a2a-js/sdk/client';
+
+export interface Session {
+ client: Client;
+ contextId: string;
+ metadata: Record;
+}
+
+export interface ChatMessage {
+ id: string;
+ role: 'user' | 'agent';
+ text: string;
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/utils.ts b/apps/agentstack-sdk-ts/examples/chat-ui/src/utils.ts
new file mode 100644
index 000000000..bb2efe9fb
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/utils.ts
@@ -0,0 +1,36 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type { Client } from '@a2a-js/sdk/client';
+import { buildLLMExtensionFulfillmentResolver, type ContextToken, handleAgentCard, type Message } from 'agentstack-sdk';
+
+import { api } from './api';
+import type { ChatMessage } from './types';
+
+export function createMessage({ role, text }: Pick): ChatMessage {
+ return {
+ id: crypto.randomUUID(),
+ role,
+ text,
+ };
+}
+
+export async function resolveAgentMetadata({ client, contextToken }: { client: Client; contextToken: ContextToken }) {
+ const agentCard = await client.getAgentCard();
+ const { resolveMetadata } = handleAgentCard(agentCard);
+ const llmResolver = buildLLMExtensionFulfillmentResolver(api, contextToken);
+ const agentMetadata = await resolveMetadata({ llm: llmResolver });
+
+ return agentMetadata;
+}
+
+export function extractTextFromMessage(message: Message | undefined) {
+ const text = message?.parts
+ .filter((part) => part.kind === 'text')
+ .map((part) => part.text)
+ .join('\n');
+
+ return text;
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/src/vite-env.d.ts b/apps/agentstack-sdk-ts/examples/chat-ui/src/vite-env.d.ts
new file mode 100644
index 000000000..77e8b8bda
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/src/vite-env.d.ts
@@ -0,0 +1,13 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+interface ImportMetaEnv {
+ readonly VITE_AGENTSTACK_BASE_URL: string;
+ readonly VITE_AGENTSTACK_PROVIDER_ID: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/stylelint.config.js b/apps/agentstack-sdk-ts/examples/chat-ui/stylelint.config.js
new file mode 100644
index 000000000..137bc437d
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/stylelint.config.js
@@ -0,0 +1,11 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/** @type {import('stylelint').Config} */
+const config = {
+ extends: ['@i-am-bee/lint-config/stylelint'],
+};
+
+export default config;
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.app.json b/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.app.json
new file mode 100644
index 000000000..a9b5a59ca
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.app.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.json b/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.json
new file mode 100644
index 000000000..1ffef600d
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.node.json b/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.node.json
new file mode 100644
index 000000000..8a67f62f4
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/tsconfig.node.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "types": ["node"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/apps/agentstack-sdk-ts/examples/chat-ui/vite.config.ts b/apps/agentstack-sdk-ts/examples/chat-ui/vite.config.ts
new file mode 100644
index 000000000..b7fa1f18a
--- /dev/null
+++ b/apps/agentstack-sdk-ts/examples/chat-ui/vite.config.ts
@@ -0,0 +1,17 @@
+/**
+ * Copyright 2025 © BeeAI a Series of LF Projects, LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ plugins: [
+ react({
+ babel: {
+ plugins: ['babel-plugin-react-compiler'],
+ },
+ }),
+ ],
+});
diff --git a/docs/development/custom-ui/architecture-guide.mdx b/docs/development/custom-ui/architecture-guide.mdx
new file mode 100644
index 000000000..ef75b2479
--- /dev/null
+++ b/docs/development/custom-ui/architecture-guide.mdx
@@ -0,0 +1,224 @@
+---
+title: Custom UI Architecture Guide
+description: Practical approach to building a custom Agent Stack UI using the chat-ui reference example
+---
+
+If you are building your own user interface, start with the reference implementation:
+
+- [chat-ui example source](https://github.com/i-am-bee/agentstack/tree/main/apps/agentstack-sdk-ts/examples/chat-ui)
+- Local path in this monorepo: `apps/agentstack-sdk-ts/examples/chat-ui`
+
+This page explains the approach used in that example and how to extend it into a full custom UI.
+
+The example is intentionally minimal: it runs against one provider/agent at a time, with `VITE_AGENTSTACK_PROVIDER_ID` set manually in `.env`.
+This keeps the core SDK flow easy to follow before you add provider discovery, routing, and richer UI state.
+
+It uses React, TypeScript, and Vite for a clean demonstration and fast local iteration.
+The architecture itself is not tied to this stack. You can apply the same API, context token, and A2A streaming flow in other frontend frameworks.
+
+This example assumes the following runtime setup:
+
+- Your Agent Stack server is running and reachable.
+- The server has at least one provider/agent available.
+- `VITE_AGENTSTACK_PROVIDER_ID` matches an existing provider in that server.
+- When running from the Vite dev server (`http://localhost:5173`), CORS on the Agent Stack server allows that origin.
+
+If your server cannot allow the frontend origin directly, you can route requests through a frontend proxy, but proxy configuration is intentionally out of scope for this basic example.
+
+If you still need the SDK setup basics, start with **[Getting Started](./getting-started)** and then return here.
+
+## What a custom UI needs
+
+Most implementations follow the same core flow:
+
+1. Create a platform API client
+2. Create a context and context token
+3. Create an authenticated A2A client
+4. Resolve agent card demands into metadata
+5. Send messages and stream task events
+6. Add UI flows for forms, approvals, OAuth, errors, and other extension-driven interactions
+
+## 1. Configure environment and target provider
+
+The example keeps runtime configuration in environment variables and injects the target provider ID directly.
+
+`.env` values:
+
+```bash
+VITE_AGENTSTACK_BASE_URL="http://localhost:8333"
+VITE_AGENTSTACK_PROVIDER_ID="your-provider-id"
+```
+
+- `VITE_AGENTSTACK_BASE_URL`: URL of your actual Agent Stack server instance
+- `VITE_AGENTSTACK_PROVIDER_ID`: provider/agent ID the example will call
+
+In the example code, these values are read as:
+
+```typescript
+const BASE_URL = import.meta.env.VITE_AGENTSTACK_BASE_URL;
+const PROVIDER_ID = import.meta.env.VITE_AGENTSTACK_PROVIDER_ID;
+```
+
+## 2. Create a context and context token
+
+Before sending messages, create a conversation context and a token scoped for agent access:
+
+```typescript
+import { buildApiClient, unwrapResult } from "agentstack-sdk";
+
+const api = buildApiClient({ baseUrl: BASE_URL });
+
+const context = unwrapResult(await api.createContext({ provider_id: PROVIDER_ID }));
+
+const contextToken = unwrapResult(
+ await api.createContextToken({
+ context_id: context.id,
+ grant_global_permissions: {
+ a2a_proxy: [PROVIDER_ID],
+ llm: ["*"],
+ },
+ grant_context_permissions: {
+ context_data: ["*"],
+ },
+ }),
+);
+```
+
+If your Agent Stack server requires user authentication, initialize `buildApiClient` with an authenticated fetch (for example, `createAuthenticatedFetch(accessToken)`), as shown in **[Getting Started](./getting-started)**.
+
+Keep permissions minimal for your use case. See **[Permissions and Tokens](./permissions-and-tokens)** for scope details.
+
+## 3. Create an authenticated A2A client
+
+Use the context token with `createAuthenticatedFetch`, then pass it to both the transport and card resolver:
+
+```typescript
+import {
+ ClientFactory,
+ ClientFactoryOptions,
+ DefaultAgentCardResolver,
+ JsonRpcTransportFactory,
+} from "@a2a-js/sdk/client";
+import { createAuthenticatedFetch, getAgentCardPath } from "agentstack-sdk";
+
+const fetchImpl = createAuthenticatedFetch(contextToken.token);
+
+const factory = new ClientFactory(
+ ClientFactoryOptions.createFrom(ClientFactoryOptions.default, {
+ transports: [new JsonRpcTransportFactory({ fetchImpl })],
+ cardResolver: new DefaultAgentCardResolver({ fetchImpl }),
+ }),
+);
+
+const agentCardPath = getAgentCardPath(PROVIDER_ID);
+const client = await factory.createFromUrl(BASE_URL, agentCardPath);
+```
+
+## 4. Resolve agent requirements once per session
+
+Read the agent card and resolve demand fulfillments before the first message:
+
+```typescript
+import { buildLLMExtensionFulfillmentResolver, handleAgentCard } from "agentstack-sdk";
+
+const agentCard = await client.getAgentCard();
+const { resolveMetadata } = handleAgentCard(agentCard);
+
+const llmResolver = buildLLMExtensionFulfillmentResolver(api, contextToken);
+const metadata = await resolveMetadata({ llm: llmResolver });
+```
+
+This keeps extension fulfillment logic centralized and reusable. See **[Agent Requirements](./agent-requirements)**.
+
+## 5. Send messages and process stream events
+
+The example sends a user message and reads streamed output from both `status-update` and `message` events:
+
+```typescript
+const stream = client.sendMessageStream({
+ message: {
+ kind: "message",
+ role: "user",
+ messageId: crypto.randomUUID(),
+ contextId,
+ parts: [{ kind: "text", text }],
+ metadata,
+ },
+});
+
+let agentText = "";
+
+for await (const event of stream) {
+ if (event.kind === "status-update" || event.kind === "message") {
+ const message = event.kind === "message" ? event : event.status.message;
+ const text = extractTextFromMessage(message);
+
+ if (text) {
+ agentText += text;
+ }
+ }
+}
+```
+
+This aggregation is intentionally minimal and text-only for readability.
+
+For full handling of `task`, `artifact-update`, cancellation, and failure states, use the patterns in **[A2A Client Integration](./a2a-client)** and **[Error Handling](./error-handling)**.
+
+## 6. Extend the basic chat loop for production
+
+The example intentionally keeps UI logic minimal. Production apps usually add:
+
+- `handleTaskStatusUpdate` to drive form, approval, OAuth, and secret prompts
+- `resolveUserMetadata` to submit structured user responses
+- Citation and trajectory rendering from message metadata
+- Artifact rendering for files and non-text outputs
+- Retry and cancellation controls for long-running tasks
+
+Related guides:
+
+- **[User Messages](./user-messages)**
+- **[Agent Responses](./agent-responses)**
+- **[Agent Requirements](./agent-requirements)**
+
+## Implementation checklist
+
+1. Configure `VITE_AGENTSTACK_BASE_URL` and `VITE_AGENTSTACK_PROVIDER_ID`
+2. Create `context` and `contextToken`
+3. Build authenticated A2A client
+4. Resolve agent card demands to metadata
+5. Send message stream and render updates
+6. Handle structured UI interactions and errors
+
+## Run the reference example
+
+```bash
+cd apps/agentstack-sdk-ts/examples/chat-ui
+cp .env.example .env
+pnpm install
+pnpm dev
+```
+
+Update `.env` with a valid `VITE_AGENTSTACK_BASE_URL` and `VITE_AGENTSTACK_PROVIDER_ID` before starting.
+
+## Troubleshooting
+
+- **`Missing required environment variables.` on startup**
+ `VITE_AGENTSTACK_BASE_URL` or `VITE_AGENTSTACK_PROVIDER_ID` is missing. Check your `.env` file and restart `pnpm dev`.
+
+- **Network errors when creating context/token**
+ `VITE_AGENTSTACK_BASE_URL` is wrong, unreachable, or points to a different environment. Verify the server URL and that the Agent Stack API is running.
+
+- **CORS errors in the browser console**
+ The Agent Stack server must allow the frontend origin (for Vite dev, `http://localhost:5173`). Update server CORS settings or use a proxy.
+
+- **401/403 responses from platform API endpoints**
+ Your server likely requires user auth. Use `buildApiClient` with authenticated fetch (for example, `createAuthenticatedFetch(accessToken)`), as shown in **[Getting Started](./getting-started)**.
+
+- **Context token created, but agent run fails with permission-related errors**
+ The token grants may be too narrow for the provider/agent. Recheck `grant_global_permissions` and `grant_context_permissions` in Step 2.
+
+- **Provider not found / invalid provider ID errors**
+ `VITE_AGENTSTACK_PROVIDER_ID` must match an existing provider on the target server. Confirm the ID in your Agent Stack instance.
+
+- **UI shows little or no useful output even though requests succeed**
+ This example intentionally aggregates only text parts. Agents that return files, data parts, citations, forms, or artifacts need additional rendering logic (see **[Agent Responses](./agent-responses)** and **[A2A Client Integration](./a2a-client)**).
diff --git a/docs/development/custom-ui/getting-started.mdx b/docs/development/custom-ui/getting-started.mdx
index f0eb8aa19..86242b300 100644
--- a/docs/development/custom-ui/getting-started.mdx
+++ b/docs/development/custom-ui/getting-started.mdx
@@ -178,6 +178,7 @@ These exports are useful when building strongly typed UI layers or validating in
## Next Steps
+- **[Custom UI Architecture Guide](./architecture-guide)** for a practical implementation approach using the `chat-ui` reference example
- **[A2A Client Integration](./a2a-client)** for the full streaming, UI events, and response flow
- **[Agent Requirements](./agent-requirements)** for service and UI extension handling
- **[Agent Responses](./agent-responses)** for rendering message parts and citations
diff --git a/docs/docs.json b/docs/docs.json
index ebe79a12d..ec71654df 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -70,6 +70,7 @@
"group": "Custom UI Integration",
"pages": [
"stable/custom-ui/getting-started",
+ "stable/custom-ui/architecture-guide",
"stable/custom-ui/a2a-client",
"stable/custom-ui/agent-requirements",
"stable/custom-ui/platform-api-client",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a68d04a82..57b85d0a5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -112,6 +112,58 @@ importers:
specifier: ^3.1.3
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.2)(jiti@1.21.7)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
+ apps/agentstack-sdk-ts/examples/chat-ui:
+ dependencies:
+ '@a2a-js/sdk':
+ specifier: ^0.3.10
+ version: 0.3.10(@bufbuild/protobuf@2.11.0)(express@4.21.2)
+ agentstack-sdk:
+ specifier: ^0.6.1
+ version: 0.6.1(@bufbuild/protobuf@2.11.0)(express@4.21.2)
+ react:
+ specifier: ^19.2.4
+ version: 19.2.4
+ react-dom:
+ specifier: ^19.2.4
+ version: 19.2.4(react@19.2.4)
+ devDependencies:
+ '@i-am-bee/lint-config':
+ specifier: workspace:*
+ version: link:../../../lint-config
+ '@types/node':
+ specifier: ^22.19.10
+ version: 22.19.10
+ '@types/react':
+ specifier: ^19.2.13
+ version: 19.2.13
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.13)
+ '@vitejs/plugin-react':
+ specifier: ^5.1.4
+ version: 5.1.4(vite@7.3.1(@types/node@22.19.10)(jiti@1.21.7)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
+ babel-plugin-react-compiler:
+ specifier: ^1.0.0
+ version: 1.0.0
+ eslint:
+ specifier: ^9.39.2
+ version: 9.39.2(jiti@1.21.7)
+ eslint-plugin-react-hooks:
+ specifier: ^7.0.1
+ version: 7.0.1(eslint@9.39.2(jiti@1.21.7))
+ prettier:
+ specifier: ^3.8.1
+ version: 3.8.1
+ stylelint:
+ specifier: ^16.26.1
+ version: 16.26.1(typescript@5.9.3)
+ typescript:
+ specifier: 5.9.3
+ version: 5.9.3
+ vite:
+ specifier: ^7.3.1
+ version: 7.3.1(@types/node@22.19.10)(jiti@1.21.7)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
+
apps/agentstack-ui:
dependencies:
'@a2a-js/sdk':
@@ -119,7 +171,7 @@ importers:
version: 0.3.10(@bufbuild/protobuf@2.11.0)(express@4.21.2)
'@bprogress/next':
specifier: ^3.2.12
- version: 3.2.12(next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ version: 3.2.12(next@15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@carbon/icons-react':
specifier: 'catalog:'
version: 11.74.0(react@19.2.4)
@@ -194,10 +246,10 @@ importers:
version: 11.12.2
next:
specifier: 'catalog:'
- version: 15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
+ version: 15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
next-auth:
specifier: 5.0.0-beta.30
- version: 5.0.0-beta.30(next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4)
+ version: 5.0.0-beta.30(next@15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4)
openid-client:
specifier: ^6.8.2
version: 6.8.2
@@ -369,7 +421,7 @@ importers:
version: 2.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
next:
specifier: 'catalog:'
- version: 15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
+ version: 15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
react:
specifier: 'catalog:'
version: 19.2.4
@@ -3467,6 +3519,9 @@ packages:
'@rolldown/pluginutils@1.0.0-beta.27':
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+ '@rolldown/pluginutils@1.0.0-rc.3':
+ resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==}
+
'@rollup/pluginutils@5.3.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
@@ -4343,6 +4398,12 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+ '@vitejs/plugin-react@5.1.4':
+ resolution: {integrity: sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
'@vitest/expect@3.2.4':
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
@@ -4411,6 +4472,9 @@ packages:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
+ agentstack-sdk@0.6.1:
+ resolution: {integrity: sha512-f+TawO+0vZuuLSyhvhl5O3uSPOmGQ8oB+2EGRCcU9fvmiXAkKjr11TV7ouR7SXDvEsnSItZPeL4ETKaDm9IsAQ==}
+
aggregate-error@4.0.1:
resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==}
engines: {node: '>=12'}
@@ -4639,6 +4703,9 @@ packages:
peerDependencies:
'@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
+ babel-plugin-react-compiler@1.0.0:
+ resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==}
+
bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
@@ -5691,6 +5758,12 @@ packages:
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+ eslint-plugin-react-hooks@7.0.1:
+ resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
eslint-plugin-react@7.37.5:
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
engines: {node: '>=4'}
@@ -6240,6 +6313,12 @@ packages:
hastscript@9.0.1:
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
+ hermes-estree@0.25.1:
+ resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+ hermes-parser@0.25.1:
+ resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
hex-rgb@5.0.0:
resolution: {integrity: sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w==}
engines: {node: '>=12'}
@@ -7813,6 +7892,10 @@ packages:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
+ react-refresh@0.18.0:
+ resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
+ engines: {node: '>=0.10.0'}
+
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
@@ -9372,6 +9455,12 @@ packages:
peerDependencies:
zod: ^3.20.0
+ zod-validation-error@4.0.2:
+ resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+
zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
@@ -10207,11 +10296,11 @@ snapshots:
'@bprogress/core@1.3.4': {}
- '@bprogress/next@3.2.12(next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ '@bprogress/next@3.2.12(next@15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@bprogress/core': 1.3.4
'@bprogress/react': 1.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
- next: 15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
+ next: 15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
@@ -13048,6 +13137,8 @@ snapshots:
'@rolldown/pluginutils@1.0.0-beta.27': {}
+ '@rolldown/pluginutils@1.0.0-rc.3': {}
+
'@rollup/pluginutils@5.3.0(rollup@4.57.1)':
dependencies:
'@types/estree': 1.0.8
@@ -13587,7 +13678,7 @@ snapshots:
'@types/body-parser@1.19.6':
dependencies:
'@types/connect': 3.4.38
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
'@types/chai@5.2.3':
dependencies:
@@ -13596,13 +13687,13 @@ snapshots:
'@types/connect@3.4.38':
dependencies:
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
'@types/cookie@0.4.1': {}
'@types/cors@2.8.19':
dependencies:
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
'@types/d3-array@3.2.2': {}
@@ -13731,7 +13822,7 @@ snapshots:
'@types/es-aggregate-error@1.0.6':
dependencies:
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
'@types/estree-jsx@1.0.5':
dependencies:
@@ -13741,7 +13832,7 @@ snapshots:
'@types/express-serve-static-core@4.19.8':
dependencies:
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
'@types/qs': 6.14.0
'@types/range-parser': 1.2.7
'@types/send': 1.2.1
@@ -13835,16 +13926,16 @@ snapshots:
'@types/send@0.17.6':
dependencies:
'@types/mime': 1.3.5
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
'@types/send@1.2.1':
dependencies:
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
'@types/serve-static@1.15.10':
dependencies:
'@types/http-errors': 2.0.5
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
'@types/send': 0.17.6
'@types/trusted-types@2.0.7':
@@ -13860,7 +13951,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
optional: true
'@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)':
@@ -14034,6 +14125,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@22.19.10)(jiti@1.21.7)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
+ '@rolldown/pluginutils': 1.0.0-rc.3
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.18.0
+ vite: 7.3.1(@types/node@22.19.10)(jiti@1.21.7)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
+ transitivePeerDependencies:
+ - supports-color
+
'@vitest/expect@3.2.4':
dependencies:
'@types/chai': 5.2.3
@@ -14107,6 +14210,15 @@ snapshots:
agent-base@7.1.4: {}
+ agentstack-sdk@0.6.1(@bufbuild/protobuf@2.11.0)(express@4.21.2):
+ dependencies:
+ '@a2a-js/sdk': 0.3.10(@bufbuild/protobuf@2.11.0)(express@4.21.2)
+ zod: 4.3.6
+ transitivePeerDependencies:
+ - '@bufbuild/protobuf'
+ - '@grpc/grpc-js'
+ - express
+
aggregate-error@4.0.1:
dependencies:
clean-stack: 4.2.0
@@ -14352,6 +14464,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ babel-plugin-react-compiler@1.0.0:
+ dependencies:
+ '@babel/types': 7.29.0
+
bail@2.0.2: {}
balanced-match@1.0.2: {}
@@ -15232,7 +15348,7 @@ snapshots:
dependencies:
'@types/cookie': 0.4.1
'@types/cors': 2.8.19
- '@types/node': 25.2.2
+ '@types/node': 22.19.10
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.4.2
@@ -15612,6 +15728,17 @@ snapshots:
dependencies:
eslint: 9.39.2(jiti@1.21.7)
+ eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@1.21.7)):
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/parser': 7.29.0
+ eslint: 9.39.2(jiti@1.21.7)
+ hermes-parser: 0.25.1
+ zod: 4.3.6
+ zod-validation-error: 4.0.2(zod@4.3.6)
+ transitivePeerDependencies:
+ - supports-color
+
eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@1.21.7)):
dependencies:
array-includes: 3.1.9
@@ -16443,6 +16570,12 @@ snapshots:
property-information: 7.1.0
space-separated-tokens: 2.0.2
+ hermes-estree@0.25.1: {}
+
+ hermes-parser@0.25.1:
+ dependencies:
+ hermes-estree: 0.25.1
+
hex-rgb@5.0.0: {}
highlight.js@10.7.3: {}
@@ -17679,10 +17812,10 @@ snapshots:
netmask@2.0.2: {}
- next-auth@5.0.0-beta.30(next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4):
+ next-auth@5.0.0-beta.30(next@15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3))(react@19.2.4):
dependencies:
'@auth/core': 0.41.0
- next: 15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
+ next: 15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3)
react: 19.2.4
next-mdx-remote-client@1.1.4(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.3))(react@19.2.3)(unified@11.0.5):
@@ -17701,7 +17834,7 @@ snapshots:
- supports-color
- unified
- next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3):
+ next@15.5.12(@babel/core@7.29.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.97.3):
dependencies:
'@next/env': 15.5.12
'@swc/helpers': 0.5.15
@@ -17719,6 +17852,7 @@ snapshots:
'@next/swc-linux-x64-musl': 15.5.12
'@next/swc-win32-arm64-msvc': 15.5.12
'@next/swc-win32-x64-msvc': 15.5.12
+ babel-plugin-react-compiler: 1.0.0
sass: 1.97.3
sharp: 0.34.5
transitivePeerDependencies:
@@ -18361,6 +18495,8 @@ snapshots:
react-refresh@0.17.0: {}
+ react-refresh@0.18.0: {}
+
react-remove-scroll-bar@2.3.8(@types/react@19.2.13)(react@19.2.3):
dependencies:
react: 19.2.3
@@ -20155,6 +20291,24 @@ snapshots:
sass-embedded: 1.97.3
terser: 5.46.0
+ vite@7.3.1(@types/node@22.19.10)(jiti@1.21.7)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
+ dependencies:
+ esbuild: 0.27.2
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.57.1
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 22.19.10
+ fsevents: 2.3.3
+ jiti: 1.21.7
+ sass: 1.97.3
+ sass-embedded: 1.97.3
+ terser: 5.46.0
+ tsx: 4.21.0
+ yaml: 2.8.2
+
vite@7.3.1(@types/node@25.2.2)(jiti@1.21.7)(sass-embedded@1.97.3)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
esbuild: 0.27.2
@@ -20424,6 +20578,10 @@ snapshots:
dependencies:
zod: 3.24.0
+ zod-validation-error@4.0.2(zod@4.3.6):
+ dependencies:
+ zod: 4.3.6
+
zod@3.21.4: {}
zod@3.23.8: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index a68c17199..df9b7316f 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -3,6 +3,7 @@ packages:
- apps/beeai-web
- apps/lint-config
- apps/agentstack-sdk-ts
+ - apps/agentstack-sdk-ts/examples/chat-ui
- apps/keycloak-theme
- docs