@dudousxd/nestjs-agent-react 0.20.0 → 0.21.0
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 +60 -37
- package/dist/{client-0Wd-WPsu.d.cts → client-ePlgRwP-.d.cts} +35 -18
- package/dist/{client-0Wd-WPsu.d.ts → client-ePlgRwP-.d.ts} +35 -18
- package/dist/{model-gHqjNjLJ.d.cts → generative-ui-9AJNVKBZ.d.cts} +165 -1
- package/dist/{model-gHqjNjLJ.d.ts → generative-ui-9AJNVKBZ.d.ts} +165 -1
- package/dist/genui-json-render.d.cts +1 -2
- package/dist/genui-json-render.d.ts +1 -2
- package/dist/genui.d.cts +2 -3
- package/dist/genui.d.ts +2 -3
- package/dist/index.cjs +918 -358
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -60
- package/dist/index.d.ts +91 -60
- package/dist/index.js +878 -320
- package/dist/index.js.map +1 -1
- package/dist/media.cjs +13 -16
- package/dist/media.cjs.map +1 -1
- package/dist/media.d.cts +6 -23
- package/dist/media.d.ts +6 -23
- package/dist/media.js +12 -14
- package/dist/media.js.map +1 -1
- package/package.json +1 -1
- package/dist/generative-ui-BhXFni8j.d.ts +0 -167
- package/dist/generative-ui-C53b3FoA.d.cts +0 -167
package/README.md
CHANGED
|
@@ -19,10 +19,7 @@ pnpm add @dudousxd/nestjs-agent-react @ai-sdk/react ai react
|
|
|
19
19
|
import { useAgentChat, MessageList, ChatInput } from '@dudousxd/nestjs-agent-react';
|
|
20
20
|
|
|
21
21
|
function Chat() {
|
|
22
|
-
const chat = useAgentChat(
|
|
23
|
-
baseUrl: '/agent',
|
|
24
|
-
getHeaders: () => ({ 'x-actor-id': me.id, 'x-actor-role': me.roles.join(',') }),
|
|
25
|
-
});
|
|
22
|
+
const chat = useAgentChat(); // same-origin, `/agent` — no provider needed
|
|
26
23
|
return (
|
|
27
24
|
<>
|
|
28
25
|
<MessageList
|
|
@@ -37,6 +34,31 @@ function Chat() {
|
|
|
37
34
|
}
|
|
38
35
|
```
|
|
39
36
|
|
|
37
|
+
### Configure the connection once: `<AgentProvider>`
|
|
38
|
+
|
|
39
|
+
Every hook (`useAgentChat`, `useThreads`, `useModels`, `useAgents`, `useQuota`, `useToolCatalog`,
|
|
40
|
+
`useMessageFeedback`, `useAttachments`) talks to the enclosing provider's backend unless handed a
|
|
41
|
+
`backend` of its own. Without a provider they share one same-origin client on `/agent`.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { AgentProvider } from '@dudousxd/nestjs-agent-react';
|
|
45
|
+
import { mediaAttachments } from '@dudousxd/nestjs-agent-react/media';
|
|
46
|
+
|
|
47
|
+
<AgentProvider
|
|
48
|
+
baseUrl="https://api.example.com" // origin only; default '' (same origin)
|
|
49
|
+
path="api/agent" // AgentModule's `path` + global prefix; default 'agent'
|
|
50
|
+
credentials="include"
|
|
51
|
+
getHeaders={() => ({ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') })}
|
|
52
|
+
attachments={{ upload: mediaAttachments() }} // optional
|
|
53
|
+
genui={{ registry, catalog }} // optional — same props as <GenuiProvider>
|
|
54
|
+
>
|
|
55
|
+
<App />
|
|
56
|
+
</AgentProvider>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`<AgentProvider backend={myBackend}>` takes any `AgentBackend` instead of the connection props.
|
|
60
|
+
`useAgentBackend()` returns the backend in scope, for your own calls.
|
|
61
|
+
|
|
40
62
|
## Bring your own UI
|
|
41
63
|
|
|
42
64
|
The package is **headless by design**, in four layers, and only the top one renders anything:
|
|
@@ -58,7 +80,7 @@ tool grouping, the edit machine, or the copy flash, and it never imports `Messag
|
|
|
58
80
|
import { useAgentChat, useChatTranscript } from '@dudousxd/nestjs-agent-react';
|
|
59
81
|
|
|
60
82
|
function Chat() {
|
|
61
|
-
const chat = useAgentChat(
|
|
83
|
+
const chat = useAgentChat();
|
|
62
84
|
const transcript = useChatTranscript({
|
|
63
85
|
messages: chat.messages,
|
|
64
86
|
status: chat.status,
|
|
@@ -134,8 +156,8 @@ catalog to the transcript and every tool call carries a `description`, and every
|
|
|
134
156
|
`activity` grouping:
|
|
135
157
|
|
|
136
158
|
```tsx
|
|
137
|
-
const chat = useAgentChat(
|
|
138
|
-
const { catalog } = useToolCatalog({
|
|
159
|
+
const chat = useAgentChat();
|
|
160
|
+
const { catalog } = useToolCatalog(); // the provider's backend; `{ backend, agent }` to override
|
|
139
161
|
const transcript = useChatTranscript({ messages: chat.messages, status: chat.status, toolCatalog: catalog });
|
|
140
162
|
|
|
141
163
|
// in a `tools` block:
|
|
@@ -330,11 +352,11 @@ the model, so a `/` menu and the agent's own reach cannot drift apart:
|
|
|
330
352
|
```tsx
|
|
331
353
|
import { createSkillsSource, useAgentChat } from '@dudousxd/nestjs-agent-react';
|
|
332
354
|
|
|
333
|
-
const chat = useAgentChat({
|
|
355
|
+
const chat = useAgentChat({ onThreadCreated: setThreadId });
|
|
334
356
|
// Identity-stable so the list is read once per thread, not once per keystroke.
|
|
335
357
|
const sources = useMemo(
|
|
336
|
-
() => [createSkillsSource({
|
|
337
|
-
[chat.
|
|
358
|
+
() => [createSkillsSource({ backend: chat.backend, getThreadId: () => threadId })],
|
|
359
|
+
[chat.backend],
|
|
338
360
|
);
|
|
339
361
|
```
|
|
340
362
|
|
|
@@ -420,14 +442,12 @@ import { isTextUIPart, isToolUIPart } from 'ai';
|
|
|
420
442
|
|
|
421
443
|
function CustomChat({ threadId }: { threadId?: string }) {
|
|
422
444
|
const chat = useAgentChat({
|
|
423
|
-
baseUrl: '/agent',
|
|
424
445
|
// Omit the key entirely when absent — `UseAgentChatOptions` is built with
|
|
425
446
|
// `exactOptionalPropertyTypes`, so an explicit `threadId: undefined` doesn't type-check.
|
|
426
447
|
...(threadId !== undefined ? { threadId } : {}),
|
|
427
448
|
agent: 'support',
|
|
428
449
|
// Reattach to a turn still streaming when the page loaded — survives a refresh.
|
|
429
450
|
resume: true,
|
|
430
|
-
getHeaders: () => ({ 'x-actor-id': currentUser.id }),
|
|
431
451
|
onThreadCreated: (newThreadId) => router.replace(`/chat/${newThreadId}`),
|
|
432
452
|
// Fires once per run when the SERVER is done writing (title + terminal state persisted) —
|
|
433
453
|
// the right signal to refetch a thread list/sidebar; `onFinish` only means "a turn rendered".
|
|
@@ -517,7 +537,8 @@ const backend: AgentBackend = {
|
|
|
517
537
|
setMessageFeedback: (id, input) => api.messages.feedback(id, input),
|
|
518
538
|
};
|
|
519
539
|
|
|
520
|
-
|
|
540
|
+
<AgentProvider backend={backend}>…</AgentProvider>; // every hook below uses it
|
|
541
|
+
const chat = useAgentChat({ backend }); // or per hook — chat.backend === backend, typed as yours
|
|
521
542
|
```
|
|
522
543
|
|
|
523
544
|
Calling a hook method whose optional backend member is missing throws
|
|
@@ -528,15 +549,17 @@ attachments?, pageContext?, regenerate? }`; the SSE it returns and the REST shap
|
|
|
528
549
|
**Cookie session + CSRF with the default client.** `credentials` and `getHeaders` are all it takes —
|
|
529
550
|
`getHeaders` runs per request, so a rotated token is picked up:
|
|
530
551
|
|
|
531
|
-
```
|
|
552
|
+
```tsx
|
|
532
553
|
const readCookie = (name: string) =>
|
|
533
554
|
decodeURIComponent(document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`))?.[1] ?? '');
|
|
534
555
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
credentials
|
|
538
|
-
getHeaders
|
|
539
|
-
|
|
556
|
+
<AgentProvider
|
|
557
|
+
path="api/agent"
|
|
558
|
+
credentials="include" // 'same-origin' (the fetch default) is enough when the API is same-origin
|
|
559
|
+
getHeaders={() => ({ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') })}
|
|
560
|
+
>
|
|
561
|
+
<App />
|
|
562
|
+
</AgentProvider>;
|
|
540
563
|
```
|
|
541
564
|
|
|
542
565
|
### Reconnecting a dropped stream
|
|
@@ -554,8 +577,8 @@ the last failed attempt the turn ends with an error.
|
|
|
554
577
|
```tsx
|
|
555
578
|
import { useMessageFeedback, useThreads } from '@dudousxd/nestjs-agent-react';
|
|
556
579
|
|
|
557
|
-
const { threads, isLoading, rename, remove, refresh } = useThreads(
|
|
558
|
-
const feedback = useMessageFeedback({
|
|
580
|
+
const { threads, isLoading, rename, remove, refresh } = useThreads();
|
|
581
|
+
const feedback = useMessageFeedback({ threadId: chat.getThreadId });
|
|
559
582
|
|
|
560
583
|
<button aria-pressed={feedback.feedbackOf(message)?.value === 'up'}
|
|
561
584
|
onClick={() => feedback.toggle(message, 'up')}>Helpful</button>
|
|
@@ -573,9 +596,9 @@ its run persisted (the live message carries `metadata.runId`).
|
|
|
573
596
|
import { useAgents, useModels } from '@dudousxd/nestjs-agent-react';
|
|
574
597
|
|
|
575
598
|
const [model, setModel] = useState<string | undefined>();
|
|
576
|
-
const chat = useAgentChat({
|
|
577
|
-
const { providers, find, defaultModel } = useModels({
|
|
578
|
-
const { agents } = useAgents(
|
|
599
|
+
const chat = useAgentChat({ model }); // sent as the body's `model` on every turn
|
|
600
|
+
const { providers, find, defaultModel } = useModels({ agent: 'support' });
|
|
601
|
+
const { agents } = useAgents();
|
|
579
602
|
|
|
580
603
|
<select value={model ?? defaultModel ?? ''} onChange={(e) => setModel(e.target.value)}>
|
|
581
604
|
{providers.map((p) => (
|
|
@@ -601,8 +624,8 @@ model its catalog does not offer as available.
|
|
|
601
624
|
```tsx
|
|
602
625
|
import { QuotaBlockedError, useQuota } from '@dudousxd/nestjs-agent-react';
|
|
603
626
|
|
|
604
|
-
const quota = useQuota(
|
|
605
|
-
const chat = useAgentChat({
|
|
627
|
+
const quota = useQuota(); // GET <base>/quota
|
|
628
|
+
const chat = useAgentChat({ blocked: quota.blocked });
|
|
606
629
|
|
|
607
630
|
<meter value={quota.month?.usedUsd} max={quota.month?.limitUsd} />
|
|
608
631
|
{quota.blocked ? <p>{quota.blocked.reason}</p> : null}
|
|
@@ -623,7 +646,7 @@ import { useChat } from '@ai-sdk/react';
|
|
|
623
646
|
import { AgentChatTransport } from '@dudousxd/nestjs-agent-react';
|
|
624
647
|
|
|
625
648
|
const transport = new AgentChatTransport({
|
|
626
|
-
|
|
649
|
+
path: 'agent', // the default
|
|
627
650
|
getHeaders: () => ({ 'x-actor-id': currentUser.id }),
|
|
628
651
|
onMeta: ({ runId, threadId }) => console.log('turn started', runId, threadId),
|
|
629
652
|
});
|
|
@@ -653,7 +676,7 @@ before it can seed `useChat`'s `initialMessages`:
|
|
|
653
676
|
```ts
|
|
654
677
|
import { storedThreadToUiMessages } from '@dudousxd/nestjs-agent-react';
|
|
655
678
|
|
|
656
|
-
const detail = await chat.
|
|
679
|
+
const detail = await chat.backend.getThread(threadId);
|
|
657
680
|
const initialMessages = storedThreadToUiMessages(detail.messages);
|
|
658
681
|
// Feed into useAgentChat({ threadId, initialMessages, ... }) on the mount that owns this thread —
|
|
659
682
|
// `initialMessages` is only read once, on mount.
|
|
@@ -676,7 +699,7 @@ paste.
|
|
|
676
699
|
import { messageFiles, useAttachments } from '@dudousxd/nestjs-agent-react';
|
|
677
700
|
|
|
678
701
|
const files = useAttachments({
|
|
679
|
-
|
|
702
|
+
// uploads through the provider's backend; or `upload: (file, { signal, onProgress }) => myUpload(file)`
|
|
680
703
|
accept: 'image/*,.pdf',
|
|
681
704
|
maxBytes: 20 * 1024 * 1024,
|
|
682
705
|
maxFiles: 5,
|
|
@@ -713,25 +736,25 @@ flight. `messageFiles(message)` reads the files back off any message — live or
|
|
|
713
736
|
|
|
714
737
|
With `AgentMediaAttachmentsModule` on the server (`@dudousxd/nestjs-agent/media`), one option turns
|
|
715
738
|
it on — uploads go in chunks through nestjs-media's tus endpoint, with progress, abort (`remove`)
|
|
716
|
-
and retry, on the
|
|
739
|
+
and retry, on the client's own connection (origin, path, headers, credentials):
|
|
717
740
|
|
|
718
741
|
```tsx
|
|
719
742
|
import { mediaAttachments } from '@dudousxd/nestjs-agent-react/media';
|
|
720
743
|
|
|
721
|
-
|
|
722
|
-
const files = useAttachments(
|
|
744
|
+
<AgentProvider attachments={{ upload: mediaAttachments() }}>…</AgentProvider>;
|
|
745
|
+
const files = useAttachments(); // uploads resumably
|
|
723
746
|
```
|
|
724
747
|
|
|
725
748
|
Headless; `@dudousxd/nestjs-media-client` is an optional peer only this subpath uses. Extending it:
|
|
726
749
|
|
|
727
|
-
- `mediaAttachments({
|
|
728
|
-
- `new AgentClient({ …connection, attachments: mediaAttachments() })` — your own client
|
|
729
|
-
- `
|
|
730
|
-
|
|
750
|
+
- `mediaAttachments({ chunkSize, retries })` — tuning (the path comes from the client's `path`).
|
|
751
|
+
- `new AgentClient({ …connection, attachments: { upload: mediaAttachments() } })` — your own client.
|
|
752
|
+
- `createMediaUpload(connection)` — a bare `upload` for `useAttachments({ upload })`, or the
|
|
753
|
+
`uploadAttachment` member of your own `AgentBackend`.
|
|
731
754
|
- Refusals throw `MediaUploadError` with the HTTP `status` (`413`, `415`, …); an aborted or failed
|
|
732
755
|
upload is discarded on the server.
|
|
733
756
|
|
|
734
|
-
Your own storage instead:
|
|
757
|
+
Your own storage instead: `<AgentProvider attachments={{ upload: (file, { signal, onProgress }, connection) => … }}>`
|
|
735
758
|
(an `AttachmentUploadStrategy`), or `useAttachments({ upload })`, resolving to a
|
|
736
759
|
`{ mediaId, url, contentType, name }` your server's `AGENT_ATTACHMENT_STAGING` recognises.
|
|
737
760
|
|
|
@@ -54,15 +54,17 @@ interface UploadAttachmentOptions {
|
|
|
54
54
|
}
|
|
55
55
|
/** How {@link AgentClient} reaches the server — handed to an {@link AttachmentUploadStrategy}. */
|
|
56
56
|
interface AgentConnection {
|
|
57
|
-
/**
|
|
57
|
+
/** The server's origin, trailing slash removed (`''` for same-origin). */
|
|
58
58
|
baseUrl: string;
|
|
59
|
+
/** The agent's route prefix, normalized to a leading slash (`'/agent'`), or `''`. */
|
|
60
|
+
path: string;
|
|
59
61
|
/** The client's static + per-request headers, resolved now (auth, CSRF). */
|
|
60
62
|
headers: () => Promise<Record<string, string>>;
|
|
61
63
|
credentials?: RequestCredentials;
|
|
62
64
|
fetch: typeof fetch;
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
|
-
* Replaces {@link AgentClient}'s own `uploadAttachment` (`POST <
|
|
67
|
+
* Replaces {@link AgentClient}'s own `uploadAttachment` (`POST <path>/attachments`) — e.g.
|
|
66
68
|
* `mediaAttachments()` from `@dudousxd/nestjs-agent-react/media`, or your own storage. Gets the
|
|
67
69
|
* client's connection so it needs no configuration of its own. Resolve with the attachment your
|
|
68
70
|
* server's `AGENT_ATTACHMENT_STAGING` recognises by `mediaId`.
|
|
@@ -73,7 +75,8 @@ type AttachmentUploadStrategy = (file: File, options: UploadAttachmentOptions, c
|
|
|
73
75
|
* hooks) and whatever serves the agent. {@link AgentClient} is the default implementation, over the
|
|
74
76
|
* library's own REST routes with `fetch`. An app with its own client — a generated one, a different
|
|
75
77
|
* auth scheme (cookie session + CSRF header), a backend that is not this library at all but speaks
|
|
76
|
-
* docs/stream-protocol.md — implements this instead and passes it as
|
|
78
|
+
* docs/stream-protocol.md — implements this instead and passes it as `<AgentProvider backend>` (or
|
|
79
|
+
* per hook, `useAgentChat({ backend })`).
|
|
77
80
|
*
|
|
78
81
|
* The streaming, thread and cancel members are required: without them there is no chat. The rest
|
|
79
82
|
* are optional; a hook that needs one the backend does not have throws
|
|
@@ -158,8 +161,16 @@ interface OkResult {
|
|
|
158
161
|
ok: boolean;
|
|
159
162
|
}
|
|
160
163
|
interface AgentClientOptions {
|
|
161
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* The server's origin, e.g. `https://api.example.com`. Defaults to `''` (same origin). The
|
|
166
|
+
* agent's route prefix is {@link AgentClientOptions.path}, not part of this.
|
|
167
|
+
*/
|
|
162
168
|
baseUrl?: string;
|
|
169
|
+
/**
|
|
170
|
+
* The agent's route prefix — `AgentModule`'s `path`, with any global prefix in front
|
|
171
|
+
* (`'api/agent'`). Leading/trailing slashes are optional. Defaults to `'agent'`.
|
|
172
|
+
*/
|
|
173
|
+
path?: string;
|
|
163
174
|
/** Static headers merged into every request. */
|
|
164
175
|
headers?: Record<string, string>;
|
|
165
176
|
/**
|
|
@@ -175,12 +186,15 @@ interface AgentClientOptions {
|
|
|
175
186
|
credentials?: RequestCredentials;
|
|
176
187
|
/** Injectable for tests / non-browser runtimes. */
|
|
177
188
|
fetch?: typeof fetch;
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
189
|
+
/** Attachment uploads. */
|
|
190
|
+
attachments?: {
|
|
191
|
+
/**
|
|
192
|
+
* How `uploadAttachment` uploads. Omitted → `POST <path>/attachments` (multipart). Pass
|
|
193
|
+
* `mediaAttachments()` from `@dudousxd/nestjs-agent-react/media` for resumable uploads through
|
|
194
|
+
* nestjs-media, or your own {@link AttachmentUploadStrategy}.
|
|
195
|
+
*/
|
|
196
|
+
upload?: AttachmentUploadStrategy;
|
|
197
|
+
};
|
|
184
198
|
}
|
|
185
199
|
/**
|
|
186
200
|
* Framework-agnostic REST client for the nestjs-agent endpoints — the default {@link AgentBackend}.
|
|
@@ -189,10 +203,10 @@ interface AgentClientOptions {
|
|
|
189
203
|
declare class AgentClient implements AgentBackend {
|
|
190
204
|
private readonly options;
|
|
191
205
|
constructor(options?: AgentClientOptions);
|
|
192
|
-
/** `POST
|
|
206
|
+
/** `POST <path>/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
|
|
193
207
|
openChatStream(request: ChatStreamRequest): Promise<ChatStreamResponse>;
|
|
194
208
|
/**
|
|
195
|
-
* `GET
|
|
209
|
+
* `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
|
|
196
210
|
* streaming under that id (404).
|
|
197
211
|
*/
|
|
198
212
|
resumeChatStream(request: ResumeStreamRequest): Promise<ChatStreamResponse | null>;
|
|
@@ -221,22 +235,22 @@ declare class AgentClient implements AgentBackend {
|
|
|
221
235
|
deleteThread(id: string): Promise<void>;
|
|
222
236
|
forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary>;
|
|
223
237
|
renameThread(id: string, title: string): Promise<OkResult>;
|
|
224
|
-
/** General `PATCH
|
|
238
|
+
/** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */
|
|
225
239
|
updateThread(id: string, patch: ThreadPatch): Promise<OkResult>;
|
|
226
240
|
/**
|
|
227
241
|
* Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
|
|
228
|
-
* mirrors the backend's `POST
|
|
242
|
+
* mirrors the backend's `POST <path>/attachments`. The returned {@link MessageAttachment} is
|
|
229
243
|
* what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
|
|
230
244
|
*/
|
|
231
245
|
uploadAttachment(file: File, options?: UploadAttachmentOptions): Promise<MessageAttachment>;
|
|
232
246
|
private uploadWithProgress;
|
|
233
247
|
promoteThread(id: string): Promise<OkResult>;
|
|
234
248
|
truncateFromMessage(threadId: string, messageId: string): Promise<OkResult>;
|
|
235
|
-
/** `GET
|
|
249
|
+
/** `GET <path>/models?agent=` — the models this caller may pick, grouped by provider. */
|
|
236
250
|
listModels(agent?: string): Promise<ModelCatalogView>;
|
|
237
|
-
/** `GET
|
|
251
|
+
/** `GET <path>/agents` — the registered agents, the default one flagged. */
|
|
238
252
|
listAgents(): Promise<AgentCatalogEntry[]>;
|
|
239
|
-
/** `GET
|
|
253
|
+
/** `GET <path>/quota` — the caller's budget windows and the one blocking sends, if any. */
|
|
240
254
|
getQuota(): Promise<QuotaReport>;
|
|
241
255
|
getQuotaToday(): Promise<QuotaToday>;
|
|
242
256
|
cancelStream(runId: string): Promise<CancelResult>;
|
|
@@ -276,10 +290,13 @@ declare class AgentClient implements AgentBackend {
|
|
|
276
290
|
/** This client's connection, for an {@link AttachmentUploadStrategy}. */
|
|
277
291
|
private connection;
|
|
278
292
|
private baseUrl;
|
|
293
|
+
private agentPath;
|
|
294
|
+
/** Origin + agent path: what every route hangs off. */
|
|
295
|
+
private root;
|
|
279
296
|
private resolveHeaders;
|
|
280
297
|
private credentials;
|
|
281
298
|
private request;
|
|
282
299
|
private handleResponse;
|
|
283
300
|
}
|
|
284
301
|
|
|
285
|
-
export { type AgentBackend as A, type CancelResult as C, type MessageFeedbackInput as M, type QuotaToday as Q, type ResumeStreamRequest as R, type ThreadPatch as T, type UploadAttachmentOptions as U,
|
|
302
|
+
export { type AgentBackend as A, type CancelResult as C, type MessageFeedbackInput as M, type QuotaToday as Q, type ResumeStreamRequest as R, type ThreadPatch as T, type UploadAttachmentOptions as U, type AttachmentUploadStrategy as a, AgentBackendUnsupportedError as b, AgentClient as c, type AgentClientOptions as d, type AgentConnection as e, AgentHttpError as f, type ChatStreamRequest as g, type ChatStreamResponse as h, requireBackendMethod as r };
|
|
@@ -54,15 +54,17 @@ interface UploadAttachmentOptions {
|
|
|
54
54
|
}
|
|
55
55
|
/** How {@link AgentClient} reaches the server — handed to an {@link AttachmentUploadStrategy}. */
|
|
56
56
|
interface AgentConnection {
|
|
57
|
-
/**
|
|
57
|
+
/** The server's origin, trailing slash removed (`''` for same-origin). */
|
|
58
58
|
baseUrl: string;
|
|
59
|
+
/** The agent's route prefix, normalized to a leading slash (`'/agent'`), or `''`. */
|
|
60
|
+
path: string;
|
|
59
61
|
/** The client's static + per-request headers, resolved now (auth, CSRF). */
|
|
60
62
|
headers: () => Promise<Record<string, string>>;
|
|
61
63
|
credentials?: RequestCredentials;
|
|
62
64
|
fetch: typeof fetch;
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
|
-
* Replaces {@link AgentClient}'s own `uploadAttachment` (`POST <
|
|
67
|
+
* Replaces {@link AgentClient}'s own `uploadAttachment` (`POST <path>/attachments`) — e.g.
|
|
66
68
|
* `mediaAttachments()` from `@dudousxd/nestjs-agent-react/media`, or your own storage. Gets the
|
|
67
69
|
* client's connection so it needs no configuration of its own. Resolve with the attachment your
|
|
68
70
|
* server's `AGENT_ATTACHMENT_STAGING` recognises by `mediaId`.
|
|
@@ -73,7 +75,8 @@ type AttachmentUploadStrategy = (file: File, options: UploadAttachmentOptions, c
|
|
|
73
75
|
* hooks) and whatever serves the agent. {@link AgentClient} is the default implementation, over the
|
|
74
76
|
* library's own REST routes with `fetch`. An app with its own client — a generated one, a different
|
|
75
77
|
* auth scheme (cookie session + CSRF header), a backend that is not this library at all but speaks
|
|
76
|
-
* docs/stream-protocol.md — implements this instead and passes it as
|
|
78
|
+
* docs/stream-protocol.md — implements this instead and passes it as `<AgentProvider backend>` (or
|
|
79
|
+
* per hook, `useAgentChat({ backend })`).
|
|
77
80
|
*
|
|
78
81
|
* The streaming, thread and cancel members are required: without them there is no chat. The rest
|
|
79
82
|
* are optional; a hook that needs one the backend does not have throws
|
|
@@ -158,8 +161,16 @@ interface OkResult {
|
|
|
158
161
|
ok: boolean;
|
|
159
162
|
}
|
|
160
163
|
interface AgentClientOptions {
|
|
161
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* The server's origin, e.g. `https://api.example.com`. Defaults to `''` (same origin). The
|
|
166
|
+
* agent's route prefix is {@link AgentClientOptions.path}, not part of this.
|
|
167
|
+
*/
|
|
162
168
|
baseUrl?: string;
|
|
169
|
+
/**
|
|
170
|
+
* The agent's route prefix — `AgentModule`'s `path`, with any global prefix in front
|
|
171
|
+
* (`'api/agent'`). Leading/trailing slashes are optional. Defaults to `'agent'`.
|
|
172
|
+
*/
|
|
173
|
+
path?: string;
|
|
163
174
|
/** Static headers merged into every request. */
|
|
164
175
|
headers?: Record<string, string>;
|
|
165
176
|
/**
|
|
@@ -175,12 +186,15 @@ interface AgentClientOptions {
|
|
|
175
186
|
credentials?: RequestCredentials;
|
|
176
187
|
/** Injectable for tests / non-browser runtimes. */
|
|
177
188
|
fetch?: typeof fetch;
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
189
|
+
/** Attachment uploads. */
|
|
190
|
+
attachments?: {
|
|
191
|
+
/**
|
|
192
|
+
* How `uploadAttachment` uploads. Omitted → `POST <path>/attachments` (multipart). Pass
|
|
193
|
+
* `mediaAttachments()` from `@dudousxd/nestjs-agent-react/media` for resumable uploads through
|
|
194
|
+
* nestjs-media, or your own {@link AttachmentUploadStrategy}.
|
|
195
|
+
*/
|
|
196
|
+
upload?: AttachmentUploadStrategy;
|
|
197
|
+
};
|
|
184
198
|
}
|
|
185
199
|
/**
|
|
186
200
|
* Framework-agnostic REST client for the nestjs-agent endpoints — the default {@link AgentBackend}.
|
|
@@ -189,10 +203,10 @@ interface AgentClientOptions {
|
|
|
189
203
|
declare class AgentClient implements AgentBackend {
|
|
190
204
|
private readonly options;
|
|
191
205
|
constructor(options?: AgentClientOptions);
|
|
192
|
-
/** `POST
|
|
206
|
+
/** `POST <path>/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
|
|
193
207
|
openChatStream(request: ChatStreamRequest): Promise<ChatStreamResponse>;
|
|
194
208
|
/**
|
|
195
|
-
* `GET
|
|
209
|
+
* `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
|
|
196
210
|
* streaming under that id (404).
|
|
197
211
|
*/
|
|
198
212
|
resumeChatStream(request: ResumeStreamRequest): Promise<ChatStreamResponse | null>;
|
|
@@ -221,22 +235,22 @@ declare class AgentClient implements AgentBackend {
|
|
|
221
235
|
deleteThread(id: string): Promise<void>;
|
|
222
236
|
forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary>;
|
|
223
237
|
renameThread(id: string, title: string): Promise<OkResult>;
|
|
224
|
-
/** General `PATCH
|
|
238
|
+
/** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */
|
|
225
239
|
updateThread(id: string, patch: ThreadPatch): Promise<OkResult>;
|
|
226
240
|
/**
|
|
227
241
|
* Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
|
|
228
|
-
* mirrors the backend's `POST
|
|
242
|
+
* mirrors the backend's `POST <path>/attachments`. The returned {@link MessageAttachment} is
|
|
229
243
|
* what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
|
|
230
244
|
*/
|
|
231
245
|
uploadAttachment(file: File, options?: UploadAttachmentOptions): Promise<MessageAttachment>;
|
|
232
246
|
private uploadWithProgress;
|
|
233
247
|
promoteThread(id: string): Promise<OkResult>;
|
|
234
248
|
truncateFromMessage(threadId: string, messageId: string): Promise<OkResult>;
|
|
235
|
-
/** `GET
|
|
249
|
+
/** `GET <path>/models?agent=` — the models this caller may pick, grouped by provider. */
|
|
236
250
|
listModels(agent?: string): Promise<ModelCatalogView>;
|
|
237
|
-
/** `GET
|
|
251
|
+
/** `GET <path>/agents` — the registered agents, the default one flagged. */
|
|
238
252
|
listAgents(): Promise<AgentCatalogEntry[]>;
|
|
239
|
-
/** `GET
|
|
253
|
+
/** `GET <path>/quota` — the caller's budget windows and the one blocking sends, if any. */
|
|
240
254
|
getQuota(): Promise<QuotaReport>;
|
|
241
255
|
getQuotaToday(): Promise<QuotaToday>;
|
|
242
256
|
cancelStream(runId: string): Promise<CancelResult>;
|
|
@@ -276,10 +290,13 @@ declare class AgentClient implements AgentBackend {
|
|
|
276
290
|
/** This client's connection, for an {@link AttachmentUploadStrategy}. */
|
|
277
291
|
private connection;
|
|
278
292
|
private baseUrl;
|
|
293
|
+
private agentPath;
|
|
294
|
+
/** Origin + agent path: what every route hangs off. */
|
|
295
|
+
private root;
|
|
279
296
|
private resolveHeaders;
|
|
280
297
|
private credentials;
|
|
281
298
|
private request;
|
|
282
299
|
private handleResponse;
|
|
283
300
|
}
|
|
284
301
|
|
|
285
|
-
export { type AgentBackend as A, type CancelResult as C, type MessageFeedbackInput as M, type QuotaToday as Q, type ResumeStreamRequest as R, type ThreadPatch as T, type UploadAttachmentOptions as U,
|
|
302
|
+
export { type AgentBackend as A, type CancelResult as C, type MessageFeedbackInput as M, type QuotaToday as Q, type ResumeStreamRequest as R, type ThreadPatch as T, type UploadAttachmentOptions as U, type AttachmentUploadStrategy as a, AgentBackendUnsupportedError as b, AgentClient as c, type AgentClientOptions as d, type AgentConnection as e, AgentHttpError as f, type ChatStreamRequest as g, type ChatStreamResponse as h, requireBackendMethod as r };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
1
3
|
import { ElicitationQuestion, ElicitationInputType, ToolPresentation, ToolCatalogEntry, ToolResultField, ToolResultView, ToolPresentationTone, ElicitationInput } from '@dudousxd/nestjs-agent-core';
|
|
2
4
|
import { ToolUIPart, DynamicToolUIPart, UIMessage } from 'ai';
|
|
3
5
|
|
|
@@ -558,4 +560,166 @@ declare function describeTimestamp(createdAt: string | null | undefined): Timest
|
|
|
558
560
|
*/
|
|
559
561
|
declare function formatRelativeTime(date: Date): string;
|
|
560
562
|
|
|
561
|
-
|
|
563
|
+
/** The `ui` frame component a composed tree is pushed under (`GENUI_TREE_COMPONENT` of `@dudousxd/nestjs-agent-core/genui`). */
|
|
564
|
+
declare const GENUI_TREE_COMPONENT = "genui:tree";
|
|
565
|
+
/** One pushed component, normalized from whatever carried it (a transcript block, a `data-ui` part, a stored entry). */
|
|
566
|
+
interface GenerativeUIItem {
|
|
567
|
+
id: string;
|
|
568
|
+
component: string;
|
|
569
|
+
props: Record<string, unknown>;
|
|
570
|
+
version: number | null;
|
|
571
|
+
toolCallId: string | null;
|
|
572
|
+
}
|
|
573
|
+
/** A node of a composed tree (`genui:tree` frames): `{ type, props, children? }`. */
|
|
574
|
+
interface GenerativeUIElement {
|
|
575
|
+
type: string;
|
|
576
|
+
props: Record<string, unknown>;
|
|
577
|
+
children?: GenerativeUIElement[];
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* An app's renderer for one component: it receives the component's props spread, plus `children`
|
|
581
|
+
* when it is a layout node in a tree. Any React component — the library never styles anything.
|
|
582
|
+
*/
|
|
583
|
+
type GenuiRenderer<P = any> = ComponentType<P & {
|
|
584
|
+
children?: ReactNode;
|
|
585
|
+
}>;
|
|
586
|
+
/** Component name → the app's renderer. */
|
|
587
|
+
type GenuiRegistry = Record<string, GenuiRenderer>;
|
|
588
|
+
/**
|
|
589
|
+
* Resolve a component the registry does not have — typically a tenant's own component, fetched for
|
|
590
|
+
* the exact `version` a message was rendered with. Return `null`/`undefined` for "no such
|
|
591
|
+
* component". May be async; results are cached per resolver, name and version.
|
|
592
|
+
*/
|
|
593
|
+
type ResolveComponent = (name: string, version: number | null) => GenuiRenderer | null | undefined | Promise<GenuiRenderer | null | undefined>;
|
|
594
|
+
interface GenuiIssueLike {
|
|
595
|
+
path: (string | number)[];
|
|
596
|
+
message: string;
|
|
597
|
+
}
|
|
598
|
+
type ValidationLike = {
|
|
599
|
+
ok: true;
|
|
600
|
+
value: Record<string, unknown>;
|
|
601
|
+
} | {
|
|
602
|
+
ok: false;
|
|
603
|
+
issues: GenuiIssueLike[];
|
|
604
|
+
};
|
|
605
|
+
/**
|
|
606
|
+
* What the renderer needs from a catalog to validate props before drawing them. A `Catalog` from
|
|
607
|
+
* `@dudousxd/nestjs-agent-core/genui` satisfies it; declared structurally so any catalog-shaped
|
|
608
|
+
* object does too.
|
|
609
|
+
*/
|
|
610
|
+
interface GenuiCatalogLike {
|
|
611
|
+
has(name: string): boolean;
|
|
612
|
+
validate(name: string, props: unknown): Promise<ValidationLike>;
|
|
613
|
+
validateSync?(name: string, props: unknown): ValidationLike | undefined;
|
|
614
|
+
}
|
|
615
|
+
interface GenerativeUIOptions {
|
|
616
|
+
registry: GenuiRegistry;
|
|
617
|
+
/** Validate props against it before rendering. Components the catalog does not know render unvalidated. */
|
|
618
|
+
catalog?: GenuiCatalogLike;
|
|
619
|
+
resolveComponent?: ResolveComponent;
|
|
620
|
+
/**
|
|
621
|
+
* Draws a composed tree frame (`genui:tree`) whole — e.g. through json-render (see the
|
|
622
|
+
* `/genui/json-render` subpath's `GenuiProvider`). Omitted → trees render node by node through
|
|
623
|
+
* `registry`.
|
|
624
|
+
*/
|
|
625
|
+
treeRenderer?: GenuiRenderer<{
|
|
626
|
+
root?: GenerativeUIElement;
|
|
627
|
+
}>;
|
|
628
|
+
}
|
|
629
|
+
/** Why an item did not render. */
|
|
630
|
+
type GenerativeUIProblem = {
|
|
631
|
+
reason: 'unknown';
|
|
632
|
+
item: GenerativeUIItem;
|
|
633
|
+
} | {
|
|
634
|
+
reason: 'invalid';
|
|
635
|
+
item: GenerativeUIItem;
|
|
636
|
+
issues: GenuiIssueLike[];
|
|
637
|
+
} | {
|
|
638
|
+
reason: 'error';
|
|
639
|
+
item: GenerativeUIItem;
|
|
640
|
+
error: unknown;
|
|
641
|
+
};
|
|
642
|
+
type GenerativeUIState = {
|
|
643
|
+
status: 'ready';
|
|
644
|
+
item: GenerativeUIItem;
|
|
645
|
+
Component: GenuiRenderer;
|
|
646
|
+
props: Record<string, unknown>;
|
|
647
|
+
} | {
|
|
648
|
+
status: 'loading';
|
|
649
|
+
item: GenerativeUIItem;
|
|
650
|
+
} | ({
|
|
651
|
+
status: 'problem';
|
|
652
|
+
} & GenerativeUIProblem);
|
|
653
|
+
|
|
654
|
+
/** What to draw instead of a component that did not render. Default: nothing. */
|
|
655
|
+
type GenerativeUIFallback = ReactNode | ((problem: GenerativeUIProblem) => ReactNode);
|
|
656
|
+
/**
|
|
657
|
+
* Renders a `genui:tree` frame's `{ root }` node by node through the same registry, catalog and
|
|
658
|
+
* resolver as top-level components. Used automatically for tree frames unless a `treeRenderer`
|
|
659
|
+
* (e.g. the json-render one) or a registry entry for `genui:tree` takes over.
|
|
660
|
+
*/
|
|
661
|
+
declare function GenuiTree({ root }: {
|
|
662
|
+
root?: GenerativeUIElement;
|
|
663
|
+
}): React.JSX.Element | null;
|
|
664
|
+
interface GenerativeUIScopeProps extends GenerativeUIOptions {
|
|
665
|
+
fallback?: GenerativeUIFallback;
|
|
666
|
+
loading?: ReactNode;
|
|
667
|
+
onError?: (error: unknown, item: GenerativeUIItem) => void;
|
|
668
|
+
children?: ReactNode;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* The registry, catalog and fallbacks tree nodes render with. `<GenerativeUI>` provides it; wrap
|
|
672
|
+
* your own chrome in it when you draw `useGenerativeUI`'s `Component` yourself and it may be a tree.
|
|
673
|
+
*/
|
|
674
|
+
declare function GenerativeUIScope({ registry, catalog, resolveComponent, fallback, loading, onError, children, }: GenerativeUIScopeProps): React.JSX.Element;
|
|
675
|
+
/** What `<GenuiProvider>` hands every `<GenerativeUI>` / `useGenerativeUI` below it. */
|
|
676
|
+
interface GenuiProviderValue extends Partial<GenerativeUIOptions> {
|
|
677
|
+
fallback?: GenerativeUIFallback;
|
|
678
|
+
loading?: ReactNode;
|
|
679
|
+
onError?: (error: unknown, item: GenerativeUIItem) => void;
|
|
680
|
+
}
|
|
681
|
+
/** The enclosing `<GenuiProvider>`'s settings, or `null` outside one. */
|
|
682
|
+
declare function useGenuiProvider(): GenuiProviderValue | null;
|
|
683
|
+
interface GenuiProviderProps extends GenuiProviderValue {
|
|
684
|
+
children?: ReactNode;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Set generative UI up once, at the app root: the registry of your renderers, the catalog to
|
|
688
|
+
* validate against, a resolver for components the registry lacks, and what to draw when one does
|
|
689
|
+
* not render. Every `<GenerativeUI>` below reads it (its own props still win), and `MessageItem` /
|
|
690
|
+
* `MessageList` draw pushed components with it — no `renderUi` per message.
|
|
691
|
+
*
|
|
692
|
+
* ```tsx
|
|
693
|
+
* <GenuiProvider registry={registry} catalog={catalog} fallback={({ item }) => <Unknown name={item.component} />}>
|
|
694
|
+
* <App />
|
|
695
|
+
* </GenuiProvider>
|
|
696
|
+
* ```
|
|
697
|
+
*/
|
|
698
|
+
declare function GenuiProvider({ registry, catalog, resolveComponent, treeRenderer, fallback, loading, onError, children, }: GenuiProviderProps): React.JSX.Element;
|
|
699
|
+
/**
|
|
700
|
+
* The headless half of {@link GenerativeUI}: what to draw for one pushed component, or `null` when
|
|
701
|
+
* `part` is not one. Options default to the enclosing `<GenuiProvider>`'s.
|
|
702
|
+
*/
|
|
703
|
+
declare function useGenerativeUI(part: unknown, options?: Partial<GenerativeUIOptions>): GenerativeUIState | null;
|
|
704
|
+
interface GenerativeUIProps extends Partial<GenerativeUIOptions> {
|
|
705
|
+
/** A transcript `ui` block, a `data-ui` message part, or a stored `{ id, component, props, version? }`. */
|
|
706
|
+
part: TranscriptUiBlock | GenerativeUIItem | unknown;
|
|
707
|
+
/** Drawn for an unknown component, invalid props, or a renderer that threw. Default: the provider's, else nothing. */
|
|
708
|
+
fallback?: GenerativeUIFallback;
|
|
709
|
+
/** Drawn while a resolver or an async validation is pending. Default: the provider's, else nothing. */
|
|
710
|
+
loading?: ReactNode;
|
|
711
|
+
/** A renderer threw (the item shows `fallback`). */
|
|
712
|
+
onError?: (error: unknown, item: GenerativeUIItem) => void;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Draws one server-pushed component with the app's own renderer. Headless: it adds no element and
|
|
716
|
+
* no style of its own — only what the registry's component renders (and whatever `fallback` /
|
|
717
|
+
* `loading` you pass). Everything but `part` defaults to the enclosing `<GenuiProvider>`.
|
|
718
|
+
*
|
|
719
|
+
* ```tsx
|
|
720
|
+
* <GenerativeUI part={block} />
|
|
721
|
+
* ```
|
|
722
|
+
*/
|
|
723
|
+
declare function GenerativeUI({ part, registry, catalog, resolveComponent, treeRenderer, fallback: ownFallback, loading: ownLoading, onError: ownOnError, }: GenerativeUIProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
|
|
724
|
+
|
|
725
|
+
export { type TranscriptQuestionOption as $, type ApproveOptions as A, type BuildBlocksOptions as B, type ChatStatus as C, type CoercibleQuestion as D, type DescribeToolCallOptions as E, type ElicitationBlockOptions as F, type GenerativeUIItem as G, type GroupToolActivityOptions as H, type RawAnswer as I, type ResolvedReading as J, type ResolvedResultView as K, type RetrievedPassage as L, type MessageUsageInfo as M, type ToolActivityGroup as N, type ToolCallDescription as O, type ToolCallState as P, type ToolCallStatus as Q, type ResolveComponent as R, type SettleAction as S, type TranscriptUiBlock as T, type UsageSummary as U, type TranscriptApproval as V, type TranscriptApprovalStatus as W, type TranscriptElicitationBlock as X, type TranscriptElicitationOutcome as Y, type TranscriptFilesBlock as Z, type TranscriptQuestion as _, GENUI_TREE_COMPONENT as a, type TranscriptReasoningBlock as a0, type TranscriptSettleState as a1, type TranscriptSource as a2, type TranscriptSourcesBlock as a3, type TranscriptTextBlock as a4, type TranscriptToolBlock as a5, type TranscriptToolCall as a6, buildTranscriptBlocks as a7, coerceAnswer as a8, correctedCallIds as a9, describeTimestamp as aa, describeToolCall as ab, describeUsage as ac, extractMessageText as ad, fillTemplate as ae, formatRelativeTime as af, groupToolActivity as ag, inferResultView as ah, isActionCall as ai, phraseFor as aj, readPath as ak, resolveResultView as al, toolCallState as am, toolCatalogFrom as an, GenerativeUI as b, type GenerativeUIElement as c, type GenerativeUIFallback as d, type GenerativeUIOptions as e, type GenerativeUIProblem as f, type GenerativeUIProps as g, GenerativeUIScope as h, type GenerativeUIScopeProps as i, type GenerativeUIState as j, type GenuiCatalogLike as k, type GenuiIssueLike as l, GenuiProvider as m, type GenuiProviderProps as n, type GenuiProviderValue as o, type GenuiRegistry as p, type GenuiRenderer as q, GenuiTree as r, useGenuiProvider as s, type TranscriptBlock as t, useGenerativeUI as u, type TimestampInfo as v, type ToolCatalog as w, type AnyToolUIPart as x, type TranscriptFile as y, type ApprovalBlockOptions as z };
|