@hsafa/ui-sdk 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -2
- package/dist/index.cjs +23 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +572 -20
- package/dist/index.d.ts +572 -20
- package/dist/index.js +23 -23
- package/dist/index.js.map +1 -1
- package/docs/CUSTOM_UI_EXAMPLES.md +309 -0
- package/docs/DYNAMIC_PAGE_SCHEMAS.md +261 -0
- package/docs/HEADLESS_QUICK_REFERENCE.md +426 -0
- package/docs/HEADLESS_USAGE.md +682 -0
- package/docs/MIGRATION_TO_HEADLESS.md +408 -0
- package/docs/README.md +43 -71
- package/docs/handbook/00-Overview.md +69 -0
- package/docs/handbook/01-Quickstart.md +133 -0
- package/docs/handbook/02-Architecture.md +75 -0
- package/docs/handbook/03-Components-and-Hooks.md +81 -0
- package/docs/handbook/04-Streaming-and-Transport.md +73 -0
- package/docs/handbook/05-Tools-and-UI.md +73 -0
- package/docs/handbook/06-Storage-and-History.md +63 -0
- package/docs/handbook/07-Dynamic-Pages.md +49 -0
- package/docs/handbook/08-Server-Integration.md +84 -0
- package/docs/handbook/09-Agent-Studio-Client.md +40 -0
- package/docs/handbook/10-Examples-and-Recipes.md +154 -0
- package/docs/handbook/11-API-Reference-Map.md +48 -0
- package/docs/handbook/README.md +24 -0
- package/examples/custom-tools-example.tsx +401 -0
- package/examples/custom-ui-customizations-example.tsx +543 -0
- package/examples/dynamic-page-example.tsx +380 -0
- package/examples/headless-chat-example.tsx +537 -0
- package/examples/minimal-headless-example.tsx +142 -0
- package/package.json +3 -2
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 00 — Overview
|
|
2
|
+
|
|
3
|
+
This overview orients you across the SDK codebase, the Agent Studio client, and the server agent runtime.
|
|
4
|
+
|
|
5
|
+
- SDK package root: `sdk/`
|
|
6
|
+
- Public entry: `sdk/src/index.ts`
|
|
7
|
+
- Provider: `sdk/src/providers/HsafaProvider.tsx`
|
|
8
|
+
- Primary UI: `sdk/src/components/HsafaChat.tsx`
|
|
9
|
+
- Headless hooks: `sdk/src/hooks/*`
|
|
10
|
+
- Dynamic pages: `sdk/src/components/dynamic-page/`
|
|
11
|
+
- Built-in tools/UI for the agent: `sdk/src/components/hsafa-chat/utils/`
|
|
12
|
+
- Client Agent Studio: `client/src/features/agent/`
|
|
13
|
+
- Server agent runtime: `server/src/modules/agents/`
|
|
14
|
+
|
|
15
|
+
## Repository map (selected)
|
|
16
|
+
|
|
17
|
+
- `sdk/`
|
|
18
|
+
- `src/index.ts` — re-exports components, hooks, providers, dynamic page utilities, types.
|
|
19
|
+
- `src/providers/HsafaProvider.tsx` — context with config, streaming/open state, dynamic page type registration.
|
|
20
|
+
- `src/components/HsafaChat.tsx` — production chat component built on Vercel AI SDK v5. Integrates tools, UI injection, persistence, and dynamic page tools.
|
|
21
|
+
- `src/components/hsafa-chat/` — child UI parts (header, input, history, message list, editor, error boundary) and streaming helpers.
|
|
22
|
+
- `src/components/hsafa-chat/utils/`
|
|
23
|
+
- `transport.ts` — `createHsafaTransport(baseUrl, agentId, chatId)` using `DefaultChatTransport`.
|
|
24
|
+
- `builtInTools.ts` — `getDomComponents`, `controlCursor`, `fillActiveInput`, `requestInput`.
|
|
25
|
+
- `renderUserForm.ts` — runtime-rendered inline forms for `requestInput` tool.
|
|
26
|
+
- `src/components/web-controler/` — DOM discovery and cursor control utilities for built-in tools.
|
|
27
|
+
- `src/hooks/` — `useHsafaAgent`, `useChatStorage`, `useFileUploadHook`, `useMessageEditor`, `useAutoScroll`.
|
|
28
|
+
- `src/components/dynamic-page/` — dynamic page runtime, operations, storage, and tools factory.
|
|
29
|
+
- `src/types/` — shared public types (`chat.ts`, `messages.ts`).
|
|
30
|
+
- `docs/` — Advanced guide and generated API (`docs/api/`).
|
|
31
|
+
|
|
32
|
+
- `client/src/features/agent/` — Agent Studio (builder):
|
|
33
|
+
- `AgentPage.tsx`, `components/AgentFlow.tsx`, `hooks/useAgentFlow.ts`, `components/nodes/*`.
|
|
34
|
+
- Designs agents as flow graphs (nodes/edges); persists in database via GraphQL/backend.
|
|
35
|
+
|
|
36
|
+
- `server/src/modules/agents/`
|
|
37
|
+
- `run-agent-handler.ts` — HTTP handler for `POST /api/run/:id` streaming via Vercel AI SDK v5 (`streamText` + `toUIMessageStream`).
|
|
38
|
+
- `run-core/*` — model providers, MCP, tools setup, prompts, utilities.
|
|
39
|
+
- `agent-service.ts` — CRUD for agents with Prisma.
|
|
40
|
+
|
|
41
|
+
## High-level data flow
|
|
42
|
+
|
|
43
|
+
```mermaid
|
|
44
|
+
flowchart LR
|
|
45
|
+
UI[HsafaChat/useHsafaAgent] -- UIMessage[] --> /api/run/:agentId
|
|
46
|
+
Server -- stream NDJSON --> UI
|
|
47
|
+
subgraph Client
|
|
48
|
+
UI --> Tools[built-in + custom tools]
|
|
49
|
+
Tools --> UI
|
|
50
|
+
UI --> Storage[useChatStorage]
|
|
51
|
+
end
|
|
52
|
+
subgraph Server
|
|
53
|
+
Handler[run-agent-handler.ts]\nconvertToModelMessages
|
|
54
|
+
Handler --> streamText --> UIStream
|
|
55
|
+
UIStream --> toUIMessageStream --> NDJSON
|
|
56
|
+
ToolsSetup[run-core/tools.ts]\nMCP
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Public exports (from `src/index.ts`)
|
|
61
|
+
|
|
62
|
+
- Components: `HsafaChat`, `ContentContainer`, `Button`, `FloatingChatButton`
|
|
63
|
+
- Provider: `HsafaProvider`, `useHsafa`
|
|
64
|
+
- Hooks: `useHsafaAgent`, `useChatStorage`, `useFileUploadHook`, `useAutoScroll`, `useMessageEditor`
|
|
65
|
+
- Dynamic pages: `DynamicPage`, `useDynamicPage`, `createDynamicPageTools`, storage helpers, types
|
|
66
|
+
- Web controller tools: `getDomComponents`, `guideCursor`, `controlCursor`, `FillActiveInput`, `CursorController`
|
|
67
|
+
- Types: selected chat types
|
|
68
|
+
|
|
69
|
+
See also `sdk/docs/README.md` and `sdk/docs/api/`.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# 01 — Quickstart
|
|
2
|
+
|
|
3
|
+
This quickstart shows how to install the SDK, render the chat UI, and optionally build a custom headless UI.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @hsafa/ui-sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @hsafa/ui-sdk
|
|
11
|
+
# or
|
|
12
|
+
npm i @hsafa/ui-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Peer deps: React 18+, React DOM 18+.
|
|
16
|
+
|
|
17
|
+
## Minimal UI setup
|
|
18
|
+
|
|
19
|
+
Wrap your app with the provider and render `HsafaChat`.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { HsafaProvider, HsafaChat } from '@hsafa/ui-sdk';
|
|
23
|
+
|
|
24
|
+
export default function App() {
|
|
25
|
+
return (
|
|
26
|
+
<HsafaProvider baseUrl={process.env.NEXT_PUBLIC_API_BASE || ''}>
|
|
27
|
+
<HsafaChat agentId="my-agent" theme="dark" />
|
|
28
|
+
</HsafaProvider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `baseUrl` points to your backend. The chat posts to `POST {baseUrl}/api/run/:agentId`.
|
|
34
|
+
- File uploads post to `POST {baseUrl}/api/uploads`.
|
|
35
|
+
|
|
36
|
+
## Customizing the chat
|
|
37
|
+
|
|
38
|
+
`HsafaChat` supports theming and UI options (see `sdk/src/types/chat.ts`).
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<HsafaChat
|
|
42
|
+
agentId="my-agent"
|
|
43
|
+
theme="light"
|
|
44
|
+
primaryColor="#3b82f6"
|
|
45
|
+
backgroundColor="#ffffff"
|
|
46
|
+
borderColor="#e5e7eb"
|
|
47
|
+
textColor="#111827"
|
|
48
|
+
accentColor="#F9FAFB"
|
|
49
|
+
/>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Adding custom UI components and tools
|
|
53
|
+
|
|
54
|
+
- UI components: pass via `HsafaUI` prop or via the headless hook (see below). The agent can render them by name via the `ui` tool.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
function ProductCard({ name, price }: { name: string; price: number }) {
|
|
58
|
+
return <div>{name}: ${price}</div>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
<HsafaChat agentId="my-agent" HsafaUI={{ ProductCard }} />
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- Frontend tools: pass as `HsafaTools` to `HsafaChat`. Each tool is a function or `{ tool, executeEachToken }`.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
const tools = {
|
|
68
|
+
add: async ({ a, b }: any) => ({ sum: a + b })
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
<HsafaChat agentId="my-agent" HsafaTools={tools} />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Headless: build your own chat UI
|
|
75
|
+
|
|
76
|
+
Use `useHsafaAgent` for full control over rendering.
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { useHsafaAgent } from '@hsafa/ui-sdk';
|
|
80
|
+
|
|
81
|
+
export function MyChat() {
|
|
82
|
+
const agent = useHsafaAgent({
|
|
83
|
+
agentId: 'my-agent',
|
|
84
|
+
baseUrl: '',
|
|
85
|
+
tools: {
|
|
86
|
+
add: async ({ a, b }) => ({ sum: a + b }),
|
|
87
|
+
},
|
|
88
|
+
uiComponents: {
|
|
89
|
+
ProductCard: ({ name, price }: any) => <div>{name}: ${price}</div>,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div>
|
|
95
|
+
{agent.messages.map((m) => (
|
|
96
|
+
<pre key={m.id}>{JSON.stringify(m, null, 2)}</pre>
|
|
97
|
+
))}
|
|
98
|
+
<input value={agent.input} onChange={(e) => agent.setInput(e.target.value)} />
|
|
99
|
+
<button onClick={() => agent.sendMessage()}>Send</button>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Persist chat history
|
|
106
|
+
|
|
107
|
+
Use `useChatStorage` to manage chat history, switching, and metadata. `HsafaChat` uses the same storage utility under the hood.
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { useChatStorage } from '@hsafa/ui-sdk';
|
|
111
|
+
|
|
112
|
+
function History({ agentId, chatId, messages, setMessages }: any) {
|
|
113
|
+
const storage = useChatStorage({ agentId, chatId, messages, isLoading: false });
|
|
114
|
+
return (
|
|
115
|
+
<div>
|
|
116
|
+
{storage.chatList.map((m) => (
|
|
117
|
+
<button key={m.id} onClick={() => storage.switchToChat(m.id, setMessages)}>
|
|
118
|
+
{m.title}
|
|
119
|
+
</button>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Backend endpoint
|
|
127
|
+
|
|
128
|
+
Implement `POST /api/run/:agentId` using the Vercel AI SDK v5. Reference: `server/src/modules/agents/run-agent-handler.ts`.
|
|
129
|
+
|
|
130
|
+
- Input: `{ messages: UIMessage[], ... }` (the SDK automatically adds `chatId` via `createHsafaTransport`).
|
|
131
|
+
- Output: NDJSON stream produced via `createUIMessageStreamResponse`.
|
|
132
|
+
|
|
133
|
+
If you already use the provided server code, the SDK will work out of the box.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# 02 — Architecture
|
|
2
|
+
|
|
3
|
+
This section explains how the SDK layers fit together across UI, hooks, storage, transport, and the server runtime.
|
|
4
|
+
|
|
5
|
+
## Layers
|
|
6
|
+
|
|
7
|
+
- **Provider**: `sdk/src/providers/HsafaProvider.tsx`
|
|
8
|
+
- Holds SDK config (`baseUrl`, `dir`, `theme`).
|
|
9
|
+
- Tracks per-chat streaming/open state and dynamic page types.
|
|
10
|
+
- **Chat UI**: `sdk/src/components/HsafaChat.tsx`
|
|
11
|
+
- Vercel AI SDK v5 `useChat` based.
|
|
12
|
+
- Integrates tools (`HsafaTools`) and renderable UI components (`HsafaUI`), plus inline form rendering for `requestInput`.
|
|
13
|
+
- Persists chat history via `createChatStorage()` and `useChatStorage()`.
|
|
14
|
+
- **Headless Agent**: `sdk/src/hooks/useHsafaAgent.ts`
|
|
15
|
+
- Same semantics as `HsafaChat` without UI.
|
|
16
|
+
- Exposes `messages`, `sendMessage()`, `stop()`, `chatId`, `setChatId()`, tool/UI plumbing, and form handling.
|
|
17
|
+
- **Storage**: `sdk/src/hooks/useChatStorage.ts`, `sdk/src/utils/chat-storage.ts`
|
|
18
|
+
- LocalStorage-based persistence per `agentId`.
|
|
19
|
+
- **Transport**: `sdk/src/components/hsafa-chat/utils/transport.ts`
|
|
20
|
+
- `createHsafaTransport(baseUrl, agentId, chatId)` wraps `DefaultChatTransport` and injects `chatId`.
|
|
21
|
+
- **Tools & UI**: `sdk/src/components/hsafa-chat/utils/`
|
|
22
|
+
- Built-in browser tools (cursor control, DOM discovery, fill input) and inline form renderer for `requestInput`.
|
|
23
|
+
- **Dynamic Pages**: `sdk/src/components/dynamic-page/*`
|
|
24
|
+
- Runtime for rendering complex, tool-driven UI pages.
|
|
25
|
+
|
|
26
|
+
## Data contracts
|
|
27
|
+
|
|
28
|
+
- **Client → Server**: UI messages via Vercel AI SDK v5 `useChat()`.
|
|
29
|
+
- The SDK posts to `POST {baseUrl}/api/run/:agentId` with body `{ messages: UIMessage[], chatId, ... }`.
|
|
30
|
+
- **Server → Client**: NDJSON stream created by `createUIMessageStreamResponse()`.
|
|
31
|
+
- Contains deltas for text, tool calls/results, reasoning, and final response items.
|
|
32
|
+
|
|
33
|
+
## Message flow
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
sequenceDiagram
|
|
37
|
+
participant UI as HsafaChat/useHsafaAgent
|
|
38
|
+
participant S as Server run-agent-handler
|
|
39
|
+
UI->>S: POST /api/run/:agentId (UIMessage[])
|
|
40
|
+
S-->>UI: NDJSON stream (toUIMessageStream)
|
|
41
|
+
UI->>UI: render deltas (text, tool-calls, ui, reasoning)
|
|
42
|
+
UI->>UI: addToolResult() after tools/inline UI
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Tool execution
|
|
46
|
+
|
|
47
|
+
- **Frontend tools**: provided as functions in `HsafaTools` (or `tools` for headless). Executed inside `onToolCall` when the tool name matches.
|
|
48
|
+
- **UI tools**: the agent can request `ui` or a registered component name. Rendering is deferred; result posted with `addToolResult()` on success/error.
|
|
49
|
+
- **Request input**: special tool that renders a form inside a message using `renderUserForm.ts` and waits for user.
|
|
50
|
+
|
|
51
|
+
## Persistence model
|
|
52
|
+
|
|
53
|
+
Storage keys (from `createChatStorage()`):
|
|
54
|
+
- Index: `hsafaChat_${agentId}.chats`
|
|
55
|
+
- Chat data: `hsafaChat_${agentId}.chat.${chatId}`
|
|
56
|
+
- Current chat id: `hsafaChat_${agentId}.currentChatId`
|
|
57
|
+
- Show chat: `hsafaChat_${agentId}.showChat`
|
|
58
|
+
|
|
59
|
+
## Dynamic Page integration
|
|
60
|
+
|
|
61
|
+
- Register dynamic page types via `HsafaProvider.registerDynamicPageTypes()`.
|
|
62
|
+
- Use `useDynamicPage(chatId, types)` and `createDynamicPageTools(getOperations)` to expose operations to the agent.
|
|
63
|
+
|
|
64
|
+
## Server architecture (overview)
|
|
65
|
+
|
|
66
|
+
- `server/src/modules/agents/run-agent-handler.ts`:
|
|
67
|
+
- Loads agent config (nodes/edges) from Prisma.
|
|
68
|
+
- Builds model/tool stack (including MCP) via `run-core/*`.
|
|
69
|
+
- Converts `UIMessage[]` to model messages (`convertToModelMessages`).
|
|
70
|
+
- Streams via `streamText(...).toUIMessageStream()` and returns `createUIMessageStreamResponse()`.
|
|
71
|
+
- `run-core/` includes:
|
|
72
|
+
- `models.ts` (provider factories),
|
|
73
|
+
- `mcp.ts` (MCP runtime),
|
|
74
|
+
- `tools/` (HTTP, web_control, etc.),
|
|
75
|
+
- `prompts.ts` (system prompt construction).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# 03 — Components & Hooks
|
|
2
|
+
|
|
3
|
+
This is a practical catalogue of public components, hooks, and types, with examples and source references.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- **`HsafaChat`** (`sdk/src/components/HsafaChat.tsx`)
|
|
8
|
+
- Full-featured chat UI built on Vercel AI SDK v5.
|
|
9
|
+
- Props (selected — see `sdk/src/types/chat.ts` for all):
|
|
10
|
+
- `agentId: string` (required)
|
|
11
|
+
- `theme?: 'dark' | 'light'`
|
|
12
|
+
- Colors: `primaryColor`, `primaryColorDark`, `primaryColorLight`, `backgroundColor`, `borderColor`, `textColor`, `accentColor`
|
|
13
|
+
- Layout: `width`, `height`, `defaultOpen`, `floatingButtonPosition`
|
|
14
|
+
- Behavior/UI: `componentAboveInput`, `editProcessContent`
|
|
15
|
+
- Extensions: `HsafaTools?: Record<string, HsafaTool>`, `HsafaUI?: Record<string, React.ComponentType<any>>`, `dynamicPageTypes?: any[]`
|
|
16
|
+
- Example:
|
|
17
|
+
```tsx
|
|
18
|
+
<HsafaChat
|
|
19
|
+
agentId="my-agent"
|
|
20
|
+
theme="dark"
|
|
21
|
+
HsafaTools={{ add: async ({ a, b }) => ({ sum: a + b }) }}
|
|
22
|
+
HsafaUI={{ ProductCard: (p: any) => <div>{p.name}</div> }}
|
|
23
|
+
/>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- **`ContentContainer`** (`sdk/src/components/ContentContainer.tsx`)
|
|
27
|
+
- Generic content wrapper used across docs/examples.
|
|
28
|
+
|
|
29
|
+
- **`FloatingChatButton`** (`sdk/src/components/FloatingChatButton.tsx`)
|
|
30
|
+
- Floating FAB to reopen a minimized chat.
|
|
31
|
+
|
|
32
|
+
## Provider
|
|
33
|
+
|
|
34
|
+
- **`HsafaProvider`** (`sdk/src/providers/HsafaProvider.tsx`)
|
|
35
|
+
- Props: `baseUrl?: string`, `dir?: 'ltr' | 'rtl'`, `theme?: 'dark' | 'light'`.
|
|
36
|
+
- Context via `useHsafa()`:
|
|
37
|
+
- Streaming/open state per chat: `setStreamingState(chatId, isStreaming)`, `setChatOpenState(chatId, isOpen)`.
|
|
38
|
+
- `currentChatId`, dynamic page type registration.
|
|
39
|
+
- Extensions are passed directly to `HsafaChat` (`HsafaTools`, `HsafaUI`) or to `useHsafaAgent` (`tools`, `uiComponents`).
|
|
40
|
+
|
|
41
|
+
## Hooks
|
|
42
|
+
|
|
43
|
+
- **`useHsafaAgent(config)`** (`sdk/src/hooks/useHsafaAgent.ts`)
|
|
44
|
+
- Headless agent hook used by `HsafaChat`.
|
|
45
|
+
- Input (selected): `{ agentId, baseUrl?, tools: HsafaTools?, uiComponents: HsafaUI?, dynamicPageTypes?, onFinish?, onError?, initialMessages?, onMessagesChange? }`
|
|
46
|
+
- Output (selected):
|
|
47
|
+
- State: `input`, `messages`, `isLoading`, `status`, `error`
|
|
48
|
+
- Actions: `setInput`, `sendMessage({ text?, files? })`, `stop()`, `newChat()`, `setMessages(messages)`
|
|
49
|
+
- Advanced: `chatApi`, `chatId`, `setChatId(id)`, `tools`, `uiComponents`, `dynamicPage`
|
|
50
|
+
- Inline forms/UI: `formHostRef`, `formStateRef`, `cleanupForms()`, `onUISuccess()`, `onUIError()`
|
|
51
|
+
- Example:
|
|
52
|
+
```tsx
|
|
53
|
+
const agent = useHsafaAgent({ agentId: 'my-agent', baseUrl: '' });
|
|
54
|
+
agent.setInput('Hello');
|
|
55
|
+
await agent.sendMessage();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- **`useChatStorage(config)`** (`sdk/src/hooks/useChatStorage.ts`)
|
|
59
|
+
- Input: `{ agentId, chatId, messages, isLoading?, autoSave?, autoRestore? }`
|
|
60
|
+
- Output: `{ chatList, currentChatMeta, refreshChatList, loadChat, saveCurrentChat, deleteChat, switchToChat, createNewChat, searchChats, storage }`
|
|
61
|
+
- `storage` is the raw util from `sdk/src/utils/chat-storage.ts` with keys prefixed by `hsafaChat_${agentId}`.
|
|
62
|
+
|
|
63
|
+
- **`useFileUploadHook(baseUrl?)`** (`sdk/src/hooks/useFileUploadHook.ts`)
|
|
64
|
+
- Output: `{ attachments, uploading, fileInputRef, formatBytes, handleRemoveAttachment, handleFileSelection, buildUserContent, clearAttachments, setAttachments }`
|
|
65
|
+
- Posts to `{baseUrl}/api/uploads`.
|
|
66
|
+
|
|
67
|
+
- **`useMessageEditor(config)`** (`sdk/src/hooks/useMessageEditor.ts`)
|
|
68
|
+
- Utilities for message editing+regeneration from a point in history.
|
|
69
|
+
- Output: `{ editingMessageId, editingText, setEditingText, editAttachments, setEditAttachments, editUploading, startEdit, cancelEdit, saveEdit, isEditing, addEditAttachments, removeEditAttachment }`
|
|
70
|
+
|
|
71
|
+
- **`useAutoScroll<T>()`** (`sdk/src/hooks/useAutoScroll.ts`)
|
|
72
|
+
- Auto-scroll container to bottom while streaming.
|
|
73
|
+
|
|
74
|
+
## Types
|
|
75
|
+
|
|
76
|
+
- From `sdk/src/types/chat.ts` (selected):
|
|
77
|
+
- `Attachment`, `UserContentPart`, `AssistantContentPart`, `ChatMessage`
|
|
78
|
+
- `HsafaTool`
|
|
79
|
+
- `HsafaChatProps`, `EditProcessContent`
|
|
80
|
+
|
|
81
|
+
Refer to `sdk/src/index.ts` for the full export surface.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 04 — Streaming & Transport
|
|
2
|
+
|
|
3
|
+
The SDK uses Vercel AI SDK v5 for robust, incremental streaming of assistant responses, tool calls, and UI instructions.
|
|
4
|
+
|
|
5
|
+
## Transport
|
|
6
|
+
|
|
7
|
+
- File: `sdk/src/components/hsafa-chat/utils/transport.ts`
|
|
8
|
+
- Function: `createHsafaTransport(baseUrl, agentId, chatId)`
|
|
9
|
+
- Wraps `DefaultChatTransport` to point to `POST {baseUrl}/api/run/:agentId`.
|
|
10
|
+
- Injects `chatId` into the request body so the server can correlate sessions.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { DefaultChatTransport } from 'ai';
|
|
14
|
+
|
|
15
|
+
export function createHsafaTransport(baseUrl: string, agentId: string, chatId: string) {
|
|
16
|
+
return new DefaultChatTransport({
|
|
17
|
+
api: `${baseUrl}/api/run/${agentId}`,
|
|
18
|
+
fetch: async (input, init) => {
|
|
19
|
+
const body = init?.body ? JSON.parse(init.body as string) : {};
|
|
20
|
+
const enhancedBody = { ...body, chatId };
|
|
21
|
+
return fetch(input, { ...init, body: JSON.stringify(enhancedBody) });
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Client streaming: useChat (v5)
|
|
28
|
+
|
|
29
|
+
Both `HsafaChat` and `useHsafaAgent` wire `useChat({ transport, sendAutomaticallyWhen, onToolCall, onFinish, onError })`.
|
|
30
|
+
|
|
31
|
+
- `sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls` — automatically resubmits after tool results are added.
|
|
32
|
+
- `onToolCall` — resolves frontend tools and UI rendering.
|
|
33
|
+
- `addToolResult({ tool, toolCallId, output | state: 'output-error', errorText })` — posts results/errors back to the assistant message.
|
|
34
|
+
|
|
35
|
+
### UI tool calls
|
|
36
|
+
|
|
37
|
+
- If the tool name is `'ui'` or matches a registered UI component, rendering is deferred to the message renderer (`MessageList/AssistantMassage`).
|
|
38
|
+
- Success/error is acknowledged via `onUISuccess(toolCallId, toolName)` / `onUIError(toolCallId, toolName, error)` which then call `addToolResult`.
|
|
39
|
+
|
|
40
|
+
### Request input (inline forms)
|
|
41
|
+
|
|
42
|
+
- Special tool `'requestInput'` renders a form inside the message using `renderUserForm.ts`.
|
|
43
|
+
- The form posts a tool result when submitted or skipped.
|
|
44
|
+
|
|
45
|
+
## Server streaming
|
|
46
|
+
|
|
47
|
+
- File: `server/src/modules/agents/run-agent-handler.ts`
|
|
48
|
+
- Core pipeline:
|
|
49
|
+
1) Parse `UIMessage[]` from request body (AI SDK v5 format) and convert to model messages via `convertToModelMessages()`.
|
|
50
|
+
2) Build model/tools (including MCP) using `run-core/*`.
|
|
51
|
+
3) Call `streamText({ model, tools, messages, ... })`.
|
|
52
|
+
4) Convert to a UI message stream: `const uiStream = agentResponse.toUIMessageStream({ originalMessages: uiMessages })`.
|
|
53
|
+
5) Send with `createUIMessageStreamResponse({ stream, originalMessages })`.
|
|
54
|
+
|
|
55
|
+
### Event types
|
|
56
|
+
|
|
57
|
+
Common NDJSON events (consumed by the AI SDK v5 UI layer):
|
|
58
|
+
|
|
59
|
+
- Text: `text-delta`, `text-end`
|
|
60
|
+
- Reasoning: `reasoning-start`, `reasoning-delta`, `reasoning-end`
|
|
61
|
+
- Tools: `tool-call`, `tool-result`, `tool-error`
|
|
62
|
+
- Final: `final` (with `value.items`)
|
|
63
|
+
- Error: `error`
|
|
64
|
+
|
|
65
|
+
The client’s `useChat` integrates these automatically into assistant messages.
|
|
66
|
+
|
|
67
|
+
## File uploads
|
|
68
|
+
|
|
69
|
+
- Hook: `useFileUploadHook(baseUrl?)`
|
|
70
|
+
- Endpoint: `POST {baseUrl}/api/uploads`
|
|
71
|
+
- Response JSON should include: `{ id, name, url, mimeType, size }`.
|
|
72
|
+
|
|
73
|
+
Uploaded files are attached to the next user message as AI SDK v5 file parts (the SDK converts `url` to proper `data` where needed).
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 05 — Tools & UI
|
|
2
|
+
|
|
3
|
+
This section documents built-in tools/UI and how to add your own front-end tools and renderable UI components.
|
|
4
|
+
|
|
5
|
+
## Built-in tools (frontend)
|
|
6
|
+
|
|
7
|
+
File: `sdk/src/components/hsafa-chat/utils/builtInTools.ts`
|
|
8
|
+
|
|
9
|
+
- **`getDomComponents`**
|
|
10
|
+
- Discovers DOM components (optionally including hidden) for web control scenarios.
|
|
11
|
+
- Signature: `{ includeHidden?: boolean, selector?: string }` → DOM metadata
|
|
12
|
+
- **`controlCursor`**
|
|
13
|
+
- Guides a visual cursor to move/click/drag on elements for demos or automation.
|
|
14
|
+
- Signature: `{ target: string | { x,y }, action?: 'move'|'click'|'drag', anchor?, durationMs?, dragTo? }`
|
|
15
|
+
- **`fillActiveInput`**
|
|
16
|
+
- Fills the currently focused input.
|
|
17
|
+
- Signature: `{ value: string }`
|
|
18
|
+
- **`requestInput`**
|
|
19
|
+
- Special tool to request form input from the user inside a message. The form is rendered inline and its submission posts a tool result.
|
|
20
|
+
|
|
21
|
+
All built-ins are available automatically via `useHsafaAgent`/`HsafaChat` under their tool names.
|
|
22
|
+
|
|
23
|
+
### Progressive tool execution
|
|
24
|
+
|
|
25
|
+
If a tool config has `{ executeEachToken: true }`, it will be executed on streaming deltas via `useStreamingToolInput.ts` when the input changes.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// Example from dynamic page tools
|
|
29
|
+
editObject: {
|
|
30
|
+
executeEachToken: true,
|
|
31
|
+
tool: async (input) => { /* ... */ }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Built-in UI components
|
|
36
|
+
|
|
37
|
+
File: `sdk/src/components/hsafa-chat/utils/builtInUI.tsx`
|
|
38
|
+
|
|
39
|
+
- **`plainText`**: renders markdown text with Mermaid support using `MarkdownRendererWithMermaid`.
|
|
40
|
+
- Always available (no need to register in `HsafaUI`).
|
|
41
|
+
|
|
42
|
+
## Rendering custom UI from agent
|
|
43
|
+
|
|
44
|
+
Expose UI to the agent by passing `HsafaUI` to `HsafaChat` (or `uiComponents` to `useHsafaAgent`). The agent can then emit a UI tool call to render the component by name.
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<HsafaChat agentId="my-agent" HsafaUI={{ ProductCard }} />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The agent can request a UI component by returning a tool call named `'ui'` or the exact component name. Rendering is deferred and when complete, `addToolResult` is emitted with `{ rendered: true }`.
|
|
51
|
+
|
|
52
|
+
## Inline forms: `requestInput`
|
|
53
|
+
|
|
54
|
+
File: `sdk/src/components/hsafa-chat/utils/renderUserForm.ts`
|
|
55
|
+
|
|
56
|
+
- Renders a form host inside the assistant message for a specific `toolCallId`.
|
|
57
|
+
- Persists form state across rerenders (submitted/skipped/values).
|
|
58
|
+
- Posts results via `addToolResult({ tool: 'requestInput', toolCallId, output })`.
|
|
59
|
+
|
|
60
|
+
## Adding your own tools
|
|
61
|
+
|
|
62
|
+
Pass a map of tools to the chat component or the headless hook.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
const tools = {
|
|
66
|
+
add: async ({ a, b }: any) => ({ sum: a + b }),
|
|
67
|
+
myTool: { executeEachToken: false, tool: async (input: any) => ({ ok: true }) },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
<HsafaChat agentId="my-agent" HsafaTools={tools} />
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Each tool receives `input` from the agent. Return values are serialized and sent back via `addToolResult`.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# 06 — Storage & History
|
|
2
|
+
|
|
3
|
+
Local persistence is handled via `useChatStorage` and the lower-level `createChatStorage` utility.
|
|
4
|
+
|
|
5
|
+
## Storage utility
|
|
6
|
+
|
|
7
|
+
File: `sdk/src/utils/chat-storage.ts`
|
|
8
|
+
|
|
9
|
+
- Prefix: `hsafaChat_${agentId}`
|
|
10
|
+
- Keys:
|
|
11
|
+
- Index: `hsafaChat_${agentId}.chats` — array of `{ id, title, createdAt, updatedAt }`
|
|
12
|
+
- Chat data: `hsafaChat_${agentId}.chat.${chatId}` — `{ id, messages, agentId? }`
|
|
13
|
+
- Current chat id: `hsafaChat_${agentId}.currentChatId`
|
|
14
|
+
- Show chat flag: `hsafaChat_${agentId}.showChat`
|
|
15
|
+
- API:
|
|
16
|
+
- `loadChatsIndex()`, `saveChatsIndex(list)`
|
|
17
|
+
- `loadChat(id)`, `saveChat(data)`
|
|
18
|
+
- `upsertChatMeta(meta)`
|
|
19
|
+
- `deleteChatMeta(id)`, `deleteChatData(id)`, `deleteChat(id)`
|
|
20
|
+
- `loadShowChatPreference(default)`, `saveShowChatPreference(value)`
|
|
21
|
+
- `loadCurrentChatId()`, `saveCurrentChatId(id)`, `removeCurrentChatId()`
|
|
22
|
+
|
|
23
|
+
## Hook: useChatStorage
|
|
24
|
+
|
|
25
|
+
File: `sdk/src/hooks/useChatStorage.ts`
|
|
26
|
+
|
|
27
|
+
Input:
|
|
28
|
+
```ts
|
|
29
|
+
useChatStorage({ agentId, chatId, messages, isLoading?, autoSave = true, autoRestore = true })
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Output (selected):
|
|
33
|
+
- `chatList: ChatMetadata[]`
|
|
34
|
+
- `currentChatMeta: ChatMetadata | null`
|
|
35
|
+
- `refreshChatList()`
|
|
36
|
+
- `loadChat(chatId)`
|
|
37
|
+
- `saveCurrentChat()`
|
|
38
|
+
- `deleteChat(chatId)`
|
|
39
|
+
- `switchToChat(chatId, setMessages)` — loads and applies stored messages
|
|
40
|
+
- `createNewChat(onNewChat)` — not a saver; executes `onNewChat()` which should set a new `chatId`
|
|
41
|
+
- `searchChats(query)`
|
|
42
|
+
- `storage` — underlying `createChatStorage(agentId)` instance
|
|
43
|
+
|
|
44
|
+
### Auto-save behavior
|
|
45
|
+
|
|
46
|
+
- On first user message, inserts a chat meta with a title derived from the first user text part.
|
|
47
|
+
- On subsequent message updates (and when `!isLoading`), saves chat data and updates `updatedAt`.
|
|
48
|
+
- After a streaming session finishes, saves one more time to ensure the final assistant message is captured.
|
|
49
|
+
|
|
50
|
+
### Using with `HsafaChat` or headless
|
|
51
|
+
|
|
52
|
+
- `HsafaChat` uses the same storage semantics, saving UI preferences (`.showChat`) as well.
|
|
53
|
+
- In headless UIs, couple `useChatStorage` with `useHsafaAgent` by wiring `chatId`, `messages`, and `setMessages`.
|
|
54
|
+
|
|
55
|
+
### Example: chat switching
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
const storage = useChatStorage({ agentId, chatId, messages, isLoading });
|
|
59
|
+
|
|
60
|
+
function openChat(id: string) {
|
|
61
|
+
storage.switchToChat(id, agent.setMessages);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# 07 — Dynamic Pages
|
|
2
|
+
|
|
3
|
+
Dynamic Pages let the agent construct and manipulate a UI page (grid of objects/components) by calling dedicated tools. This is useful for dashboards, inspectors, and interactive builders.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- Runtime: `sdk/src/components/dynamic-page/DynamicPage.tsx`
|
|
8
|
+
- Operations: `sdk/src/components/dynamic-page/operations.ts`
|
|
9
|
+
- Tools factory: `sdk/src/components/dynamic-page/tools.ts`
|
|
10
|
+
- Storage: `sdk/src/components/dynamic-page/storage.ts`
|
|
11
|
+
- Types: `sdk/src/components/dynamic-page/types.ts`
|
|
12
|
+
- Hook: `sdk/src/components/dynamic-page/useDynamicPage.ts`
|
|
13
|
+
|
|
14
|
+
## Enabling in HsafaChat
|
|
15
|
+
|
|
16
|
+
1) Declare supported dynamic page types in your app and pass as `dynamicPageTypes` prop (or via `useHsafaAgent`).
|
|
17
|
+
2) `HsafaChat` registers types with `HsafaProvider` and creates dynamic page tools using `createDynamicPageTools(getOperations)`.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<HsafaChat
|
|
21
|
+
agentId="my-agent"
|
|
22
|
+
dynamicPageTypes={[ /* type configs */ ]}
|
|
23
|
+
/>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
From `createDynamicPageTools(getOperations)`:
|
|
29
|
+
|
|
30
|
+
- `setGrid(input)` / `readGrid()` / `validateGrid()`
|
|
31
|
+
- `readAvailableTypes()`
|
|
32
|
+
- `setObject(input)` (streaming friendly)
|
|
33
|
+
- `editObject(input)` (streaming friendly; merges options for safe defaults)
|
|
34
|
+
- `deleteObject(input)`
|
|
35
|
+
- `readAllObjects()` / `readObject(input)` / `readAtPointer(input)`
|
|
36
|
+
- `renameObject(input)` / `moveObject(input)`
|
|
37
|
+
- `readComponentErrors(input)` / `clearComponentErrors()`
|
|
38
|
+
|
|
39
|
+
All functions call into `DynamicPageOperations` returned by `useDynamicPage().getOperations`.
|
|
40
|
+
|
|
41
|
+
## Storage helpers
|
|
42
|
+
|
|
43
|
+
- `saveDynamicPageState(id, state)` / `loadDynamicPageState(id)` / `deleteDynamicPageState(id)` / `dynamicPageExists(id)`
|
|
44
|
+
|
|
45
|
+
## Recommended usage pattern
|
|
46
|
+
|
|
47
|
+
- Enable dynamic pages only for specific chats where needed.
|
|
48
|
+
- Let the agent progressively build the page while streaming tool inputs.
|
|
49
|
+
- Use UI components to visualize objects; integrate with your own component library.
|