@dudousxd/nestjs-agent-react 0.5.0 → 0.5.2

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.
Files changed (2) hide show
  1. package/README.md +119 -0
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -37,6 +37,125 @@ function Chat() {
37
37
  }
38
38
  ```
39
39
 
40
+ ## Bring your own UI
41
+
42
+ The package is **headless by design**. `useAgentChat` (state/streaming/persistence) and `AgentClient`
43
+ (the REST calls) own all the logic; `MessageList`/`ChatInput` — and the `markdown` subpath — are an
44
+ OPTIONAL, styling-agnostic reference implementation on top. A host with its own design system renders
45
+ straight off the hook's return value and never imports `MessageList` at all.
46
+
47
+ ### `useAgentChat` on its own
48
+
49
+ ```tsx
50
+ import { useAgentChat } from '@dudousxd/nestjs-agent-react';
51
+ import { isTextUIPart, isToolUIPart } from 'ai';
52
+
53
+ function CustomChat({ threadId }: { threadId?: string }) {
54
+ const chat = useAgentChat({
55
+ baseUrl: '/agent',
56
+ // Omit the key entirely when absent — `UseAgentChatOptions` is built with
57
+ // `exactOptionalPropertyTypes`, so an explicit `threadId: undefined` doesn't type-check.
58
+ ...(threadId !== undefined ? { threadId } : {}),
59
+ agent: 'support',
60
+ // Reattach to a turn still streaming when the page loaded — survives a refresh.
61
+ resume: true,
62
+ getHeaders: () => ({ 'x-actor-id': currentUser.id }),
63
+ onThreadCreated: (newThreadId) => router.replace(`/chat/${newThreadId}`),
64
+ // Fires once per run when the SERVER is done writing (title + terminal state persisted) —
65
+ // the right signal to refetch a thread list/sidebar; `onFinish` only means "a turn rendered".
66
+ onRunSettled: ({ status }) => {
67
+ if (status === 'completed') refetchThreadList();
68
+ },
69
+ });
70
+
71
+ return (
72
+ <div className="my-chat-shell">
73
+ {chat.messages.map((message) => (
74
+ <div
75
+ key={message.id}
76
+ className={message.role === 'user' ? 'my-bubble-user' : 'my-bubble-assistant'}
77
+ >
78
+ {message.parts.map((part, i) => {
79
+ if (isTextUIPart(part)) return <p key={i}>{part.text}</p>;
80
+ if (isToolUIPart(part)) return <MyToolCard key={i} part={part} />;
81
+ return null;
82
+ })}
83
+ </div>
84
+ ))}
85
+ <MyComposer
86
+ disabled={chat.status === 'streaming'}
87
+ onSubmit={(text) => chat.sendMessage({ text })}
88
+ />
89
+ </div>
90
+ );
91
+ }
92
+ ```
93
+
94
+ `chat` is the AI SDK v7 `useChat` return value (`messages`, `status`, `sendMessage`, `stop`, …) spread
95
+ together with the extras: `runId`/`activeRunId`, `client` (the raw `AgentClient`), thread list/CRUD
96
+ (`threads`, `loadThreads`, `loadThread`, `deleteThread`, `forkThread`, `renameThread`, `promoteThread`,
97
+ `truncateFromMessage`), `quota`/`loadQuota`, `cancel`, HITL `approve`/`reject`, and `regenerate`.
98
+ `MyToolCard`'s `part` prop above types as the exported `AnyToolUIPart` (`ToolUIPart | DynamicToolUIPart`
99
+ — `MessageItem` uses the same union for its own `renderToolPart` callback).
100
+
101
+ ### The transport, standalone
102
+
103
+ `AgentChatTransport` is a plain AI SDK v7 `ChatTransport` — wire it straight into `useChat` for the SSE
104
+ plumbing alone, with none of `useAgentChat`'s thread/quota/approval state:
105
+
106
+ ```ts
107
+ import { useChat } from '@ai-sdk/react';
108
+ import { AgentChatTransport } from '@dudousxd/nestjs-agent-react';
109
+
110
+ const transport = new AgentChatTransport({
111
+ baseUrl: '/agent',
112
+ getHeaders: () => ({ 'x-actor-id': currentUser.id }),
113
+ onMeta: ({ runId, threadId }) => console.log('turn started', runId, threadId),
114
+ });
115
+ const chat = useChat({ transport });
116
+ ```
117
+
118
+ ### Loading persisted history
119
+
120
+ A reloaded thread's `StoredMessage[]` (from `AgentClient.getThread`) needs converting to `UIMessage[]`
121
+ before it can seed `useChat`'s `initialMessages`:
122
+
123
+ ```ts
124
+ import { storedThreadToUiMessages } from '@dudousxd/nestjs-agent-react';
125
+
126
+ const detail = await chat.client.getThread(threadId);
127
+ const initialMessages = storedThreadToUiMessages(detail.messages);
128
+ // Feed into useAgentChat({ threadId, initialMessages, ... }) on the mount that owns this thread —
129
+ // `initialMessages` is only read once, on mount.
130
+ ```
131
+
132
+ `storedThreadToUiMessages` merges the store's one-row-per-model-iteration turns (a turn with tool
133
+ calls persists as "thinking…" + tool calls, then a separate final-answer row) into ONE `UIMessage` per
134
+ conversational turn, matching how the live stream renders — and stamps `metadata.usage` on any turn it
135
+ merged. For a single already-atomic row, `storedMessageToUiMessage` maps it 1:1 with no merging.
136
+
137
+ ### Attachments and the raw client
138
+
139
+ `uploadAttachment` and the rest of `AgentClient` work outside `useAgentChat` too — e.g. from a
140
+ file-picker or a standalone approvals-inbox screen:
141
+
142
+ ```ts
143
+ const attachment = await chat.client.uploadAttachment(file); // → MessageAttachment
144
+ await chat.sendMessage({ text: 'what is in this?' }, { body: { attachments: [attachment] } });
145
+
146
+ // Human-in-the-loop, callable from any component — not just the chat screen that raised it:
147
+ await chat.approve({ toolCallId });
148
+ await chat.reject({ toolCallId, reason: 'not now' });
149
+ ```
150
+
151
+ ### What a flip-style host owns
152
+
153
+ Everything visual: bubble layout and markdown rendering, the tool-card UI (branch on
154
+ `isToolUIPart`/`isDynamicToolUIPart`), composer design, and theming/design tokens — including how
155
+ "pending approval" reads in your product. The package never renders a pixel unless you opt into
156
+ `MessageList`/`ChatInput`; what it owns is the protocol: SSE parsing, resume/reconnect, thread CRUD,
157
+ quota, and HITL routing.
158
+
40
159
  ### Optional markdown subpath
41
160
 
42
161
  ```tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dudousxd/nestjs-agent-react",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/DavideCarvalho/nestjs-agent.git",
@@ -44,7 +44,7 @@
44
44
  "dist"
45
45
  ],
46
46
  "dependencies": {
47
- "@dudousxd/nestjs-agent-core": "0.7.0"
47
+ "@dudousxd/nestjs-agent-core": "0.9.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@ai-sdk/react": "^4.0.0",