@dudousxd/nestjs-agent-react 0.20.0 → 0.22.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 +110 -85
- package/dist/{client-0Wd-WPsu.d.cts → client-CYlhK7XD.d.cts} +36 -24
- package/dist/{client-0Wd-WPsu.d.ts → client-CYlhK7XD.d.ts} +36 -24
- 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 +1304 -491
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +259 -167
- package/dist/index.d.ts +259 -167
- package/dist/index.js +1264 -453
- 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,24 +19,58 @@ 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
|
-
<MessageList
|
|
29
|
-
messages={chat.messages}
|
|
30
|
-
status={chat.status}
|
|
31
|
-
regeneratable
|
|
32
|
-
onRegenerate={() => chat.regenerate()}
|
|
33
|
-
/>
|
|
25
|
+
<MessageList messages={chat.messages} status={chat.status} />
|
|
34
26
|
<ChatInput onSubmit={(text) => chat.sendMessage({ text })} />
|
|
35
27
|
</>
|
|
36
28
|
);
|
|
37
29
|
}
|
|
38
30
|
```
|
|
39
31
|
|
|
32
|
+
`useAgentChat` does the wiring itself: give it a `threadId` and it loads that thread's history and
|
|
33
|
+
re-attaches to a turn still streaming on it (`history: false` / `resume: false` opt out); it gates
|
|
34
|
+
sends on the reported quota; approvals and question sets in the transcript are actionable with no
|
|
35
|
+
handlers passed. On top of the AI SDK chat it returns:
|
|
36
|
+
|
|
37
|
+
| On `chat` | What it is |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `transcript` | `useChatTranscript` already bound to the chat — approve/reject/answer/skip, stop, fork, regenerate, the tool catalog, and timestamps/usage read from message metadata. Override any of it with `useAgentChat({ transcript: { … } })`. |
|
|
40
|
+
| `composer` | `{ text, setText, files, canSend, blockedBy, submit() }` — `files` is `useAttachments` on the chat's backend; `submit()` sends the draft with the ready files attached and clears both; `blockedBy` is `'empty' \| 'busy' \| 'uploading' \| 'quota'`. |
|
|
41
|
+
| `models` | `{ list, providers, selected, select(id), pinToThread(id) }` — loaded the first time `list` is read. |
|
|
42
|
+
| `quota` / `blocked` | `useQuota`'s state, and the window blocking sends (the `blocked` option overrides it). |
|
|
43
|
+
| `approve` / `reject` / `answer` / `skip` | `({ toolCallId, … })` — the same object shape the transcript's handlers take. |
|
|
44
|
+
| `fork` / `truncateFrom` / `promote` | `({ messageId, threadId? })` / `({ threadId? })`, defaulting to this chat's thread. |
|
|
45
|
+
| `cancel`, `regenerate`, `getThreadId`, `backend`, `runId`, `activeRunId`, `isLoadingHistory`, `connection`, `background` | — |
|
|
46
|
+
|
|
47
|
+
Threads (list, rename, delete) are `useThreads()`; the granular hooks stay the escape hatch.
|
|
48
|
+
|
|
49
|
+
### Configure the connection once: `<AgentProvider>`
|
|
50
|
+
|
|
51
|
+
Every hook (`useAgentChat`, `useThreads`, `useModels`, `useAgents`, `useQuota`, `useToolCatalog`,
|
|
52
|
+
`useMessageFeedback`, `useAttachments`) talks to the enclosing provider's backend unless handed a
|
|
53
|
+
`backend` of its own. Without a provider they share one same-origin client on `/agent`.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { AgentProvider } from '@dudousxd/nestjs-agent-react';
|
|
57
|
+
import { mediaAttachments } from '@dudousxd/nestjs-agent-react/media';
|
|
58
|
+
|
|
59
|
+
<AgentProvider
|
|
60
|
+
baseUrl="https://api.example.com" // origin only; default '' (same origin)
|
|
61
|
+
path="api/agent" // AgentModule's `path` + global prefix; default 'agent'
|
|
62
|
+
credentials="include"
|
|
63
|
+
getHeaders={() => ({ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') })}
|
|
64
|
+
attachments={{ upload: mediaAttachments() }} // optional
|
|
65
|
+
genui={{ registry, catalog }} // optional — same props as <GenuiProvider>
|
|
66
|
+
>
|
|
67
|
+
<App />
|
|
68
|
+
</AgentProvider>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`<AgentProvider backend={myBackend}>` takes any `AgentBackend` instead of the connection props.
|
|
72
|
+
`useAgentBackend()` returns the backend in scope, for your own calls.
|
|
73
|
+
|
|
40
74
|
## Bring your own UI
|
|
41
75
|
|
|
42
76
|
The package is **headless by design**, in four layers, and only the top one renders anything:
|
|
@@ -58,15 +92,15 @@ tool grouping, the edit machine, or the copy flash, and it never imports `Messag
|
|
|
58
92
|
import { useAgentChat, useChatTranscript } from '@dudousxd/nestjs-agent-react';
|
|
59
93
|
|
|
60
94
|
function Chat() {
|
|
61
|
-
const chat = useAgentChat(
|
|
95
|
+
const chat = useAgentChat();
|
|
96
|
+
// `chat.transcript` is this, pre-wired; `useChatTranscript` directly for any AI SDK chat.
|
|
62
97
|
const transcript = useChatTranscript({
|
|
63
98
|
messages: chat.messages,
|
|
64
99
|
status: chat.status,
|
|
65
100
|
editable: true,
|
|
66
|
-
onEditSubmit: (
|
|
67
|
-
onFork: (
|
|
101
|
+
onEditSubmit: ({ text }) => chat.sendMessage({ text }),
|
|
102
|
+
onFork: ({ messageId }) => chat.fork({ messageId }),
|
|
68
103
|
onStop: () => chat.cancel(),
|
|
69
|
-
getUsage: (message) => readUsage(message),
|
|
70
104
|
});
|
|
71
105
|
|
|
72
106
|
return (
|
|
@@ -134,8 +168,8 @@ catalog to the transcript and every tool call carries a `description`, and every
|
|
|
134
168
|
`activity` grouping:
|
|
135
169
|
|
|
136
170
|
```tsx
|
|
137
|
-
const chat = useAgentChat(
|
|
138
|
-
const { catalog } = useToolCatalog({
|
|
171
|
+
const chat = useAgentChat();
|
|
172
|
+
const { catalog } = useToolCatalog(); // the provider's backend; `{ backend, agent }` to override
|
|
139
173
|
const transcript = useChatTranscript({ messages: chat.messages, status: chat.status, toolCatalog: catalog });
|
|
140
174
|
|
|
141
175
|
// in a `tools` block:
|
|
@@ -330,11 +364,11 @@ the model, so a `/` menu and the agent's own reach cannot drift apart:
|
|
|
330
364
|
```tsx
|
|
331
365
|
import { createSkillsSource, useAgentChat } from '@dudousxd/nestjs-agent-react';
|
|
332
366
|
|
|
333
|
-
const chat = useAgentChat({
|
|
367
|
+
const chat = useAgentChat({ onThreadCreated: setThreadId });
|
|
334
368
|
// Identity-stable so the list is read once per thread, not once per keystroke.
|
|
335
369
|
const sources = useMemo(
|
|
336
|
-
() => [createSkillsSource({
|
|
337
|
-
[chat.
|
|
370
|
+
() => [createSkillsSource({ backend: chat.backend, getThreadId: () => threadId })],
|
|
371
|
+
[chat.backend],
|
|
338
372
|
);
|
|
339
373
|
```
|
|
340
374
|
|
|
@@ -420,14 +454,11 @@ import { isTextUIPart, isToolUIPart } from 'ai';
|
|
|
420
454
|
|
|
421
455
|
function CustomChat({ threadId }: { threadId?: string }) {
|
|
422
456
|
const chat = useAgentChat({
|
|
423
|
-
baseUrl: '/agent',
|
|
424
457
|
// Omit the key entirely when absent — `UseAgentChatOptions` is built with
|
|
425
458
|
// `exactOptionalPropertyTypes`, so an explicit `threadId: undefined` doesn't type-check.
|
|
426
459
|
...(threadId !== undefined ? { threadId } : {}),
|
|
427
460
|
agent: 'support',
|
|
428
|
-
//
|
|
429
|
-
resume: true,
|
|
430
|
-
getHeaders: () => ({ 'x-actor-id': currentUser.id }),
|
|
461
|
+
// A `threadId` loads its history and re-attaches to a turn still streaming — by default.
|
|
431
462
|
onThreadCreated: (newThreadId) => router.replace(`/chat/${newThreadId}`),
|
|
432
463
|
// Fires once per run when the SERVER is done writing (title + terminal state persisted) —
|
|
433
464
|
// the right signal to refetch a thread list/sidebar; `onFinish` only means "a turn rendered".
|
|
@@ -472,10 +503,7 @@ disables itself while busy never noticed; a double-submitted one, and StrictMode
|
|
|
472
503
|
effect, did.
|
|
473
504
|
|
|
474
505
|
`chat` is the AI SDK v7 `useChat` return value (`messages`, `status`, `sendMessage`, `stop`, …) spread
|
|
475
|
-
together with the extras
|
|
476
|
-
under its old name `client`), thread list/CRUD
|
|
477
|
-
(`threads`, `loadThreads`, `loadThread`, `deleteThread`, `forkThread`, `renameThread`, `promoteThread`,
|
|
478
|
-
`truncateFromMessage`), `quota`/`loadQuota`, `cancel`, HITL `approve`/`reject` and `answer`/`skip`, and `regenerate`.
|
|
506
|
+
together with the extras in the table above.
|
|
479
507
|
`MyToolCard`'s `part` prop above types as the exported `AnyToolUIPart` (`ToolUIPart | DynamicToolUIPart`
|
|
480
508
|
— `MessageItem` uses the same union for its own `renderToolPart` callback).
|
|
481
509
|
|
|
@@ -517,7 +545,8 @@ const backend: AgentBackend = {
|
|
|
517
545
|
setMessageFeedback: (id, input) => api.messages.feedback(id, input),
|
|
518
546
|
};
|
|
519
547
|
|
|
520
|
-
|
|
548
|
+
<AgentProvider backend={backend}>…</AgentProvider>; // every hook below uses it
|
|
549
|
+
const chat = useAgentChat({ backend }); // or per hook — chat.backend === backend, typed as yours
|
|
521
550
|
```
|
|
522
551
|
|
|
523
552
|
Calling a hook method whose optional backend member is missing throws
|
|
@@ -528,15 +557,17 @@ attachments?, pageContext?, regenerate? }`; the SSE it returns and the REST shap
|
|
|
528
557
|
**Cookie session + CSRF with the default client.** `credentials` and `getHeaders` are all it takes —
|
|
529
558
|
`getHeaders` runs per request, so a rotated token is picked up:
|
|
530
559
|
|
|
531
|
-
```
|
|
560
|
+
```tsx
|
|
532
561
|
const readCookie = (name: string) =>
|
|
533
562
|
decodeURIComponent(document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`))?.[1] ?? '');
|
|
534
563
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
credentials
|
|
538
|
-
getHeaders
|
|
539
|
-
|
|
564
|
+
<AgentProvider
|
|
565
|
+
path="api/agent"
|
|
566
|
+
credentials="include" // 'same-origin' (the fetch default) is enough when the API is same-origin
|
|
567
|
+
getHeaders={() => ({ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') })}
|
|
568
|
+
>
|
|
569
|
+
<App />
|
|
570
|
+
</AgentProvider>;
|
|
540
571
|
```
|
|
541
572
|
|
|
542
573
|
### Reconnecting a dropped stream
|
|
@@ -554,8 +585,8 @@ the last failed attempt the turn ends with an error.
|
|
|
554
585
|
```tsx
|
|
555
586
|
import { useMessageFeedback, useThreads } from '@dudousxd/nestjs-agent-react';
|
|
556
587
|
|
|
557
|
-
const { threads, isLoading, rename, remove, refresh } = useThreads(
|
|
558
|
-
const feedback = useMessageFeedback({
|
|
588
|
+
const { threads, isLoading, rename, remove, refresh } = useThreads();
|
|
589
|
+
const feedback = useMessageFeedback({ threadId: chat.getThreadId });
|
|
559
590
|
|
|
560
591
|
<button aria-pressed={feedback.feedbackOf(message)?.value === 'up'}
|
|
561
592
|
onClick={() => feedback.toggle(message, 'up')}>Helpful</button>
|
|
@@ -570,14 +601,10 @@ its run persisted (the live message carries `metadata.runId`).
|
|
|
570
601
|
### Picking a model and an agent
|
|
571
602
|
|
|
572
603
|
```tsx
|
|
573
|
-
|
|
604
|
+
const chat = useAgentChat();
|
|
605
|
+
const { providers, selected, select } = chat.models; // loaded on first read
|
|
574
606
|
|
|
575
|
-
|
|
576
|
-
const chat = useAgentChat({ backend, model }); // sent as the body's `model` on every turn
|
|
577
|
-
const { providers, find, defaultModel } = useModels({ backend: chat.backend, agent: 'support' });
|
|
578
|
-
const { agents } = useAgents({ backend: chat.backend });
|
|
579
|
-
|
|
580
|
-
<select value={model ?? defaultModel ?? ''} onChange={(e) => setModel(e.target.value)}>
|
|
607
|
+
<select value={selected ?? ''} onChange={(e) => select(e.target.value)}>
|
|
581
608
|
{providers.map((p) => (
|
|
582
609
|
<optgroup key={p.id} label={p.label}>
|
|
583
610
|
{p.models.map((m) => (
|
|
@@ -590,10 +617,14 @@ const { agents } = useAgents({ backend: chat.backend });
|
|
|
590
617
|
</select>
|
|
591
618
|
```
|
|
592
619
|
|
|
593
|
-
`
|
|
594
|
-
availability; `
|
|
595
|
-
|
|
596
|
-
|
|
620
|
+
`chat.models` reads `GET <base>/models?agent=` (models grouped by provider, with badges and
|
|
621
|
+
availability; `list` is the same flattened) the first time `list`/`providers` is read. `select(id)`
|
|
622
|
+
runs the following turns on it (sent as the body's `model`); `selected` is the pick, else the
|
|
623
|
+
thread's pin, else the server default. `pinToThread(id)` pins it on the thread (`null` unpins) so
|
|
624
|
+
it survives reloads — on a chat with no thread yet, the pin lands when the first send creates one.
|
|
625
|
+
`useAgentChat({ model })` controls the model yourself; a single send can override it with
|
|
626
|
+
`sendMessage(msg, { body: { model } })`. `useModels()` / `useAgents()` are the standalone hooks
|
|
627
|
+
(an agent picker: `useAgents().agents`, sent as `useAgentChat({ agent })`). The server refuses a
|
|
597
628
|
model its catalog does not offer as available.
|
|
598
629
|
|
|
599
630
|
### Quota
|
|
@@ -601,17 +632,19 @@ model its catalog does not offer as available.
|
|
|
601
632
|
```tsx
|
|
602
633
|
import { QuotaBlockedError, useQuota } from '@dudousxd/nestjs-agent-react';
|
|
603
634
|
|
|
604
|
-
const
|
|
605
|
-
const
|
|
635
|
+
const chat = useAgentChat(); // reads GET <base>/quota and gates sends on it
|
|
636
|
+
const { quota } = chat;
|
|
606
637
|
|
|
607
638
|
<meter value={quota.month?.usedUsd} max={quota.month?.limitUsd} />
|
|
608
|
-
{
|
|
639
|
+
{chat.blocked ? <p>{chat.blocked.reason}</p> : null}
|
|
609
640
|
```
|
|
610
641
|
|
|
611
|
-
`useQuota` returns every window (`day`, `month`, …) with its
|
|
612
|
-
one is exhausted; it re-reads after every run a chat on the
|
|
613
|
-
|
|
614
|
-
instead of starting a turn the server
|
|
642
|
+
`chat.quota` (and the standalone `useQuota()`) returns every window (`day`, `month`, …) with its
|
|
643
|
+
usage and ceilings, and `blocked` when one is exhausted; it re-reads after every run a chat on the
|
|
644
|
+
same backend settles. While a window is exhausted, `sendMessage`/`regenerate` reject with
|
|
645
|
+
`QuotaBlockedError` (and `chat.composer.blockedBy` is `'quota'`) instead of starting a turn the server
|
|
646
|
+
would refuse with `429`. `useAgentChat({ blocked })` overrides the gate (`null` never blocks);
|
|
647
|
+
`quota: false` skips the request.
|
|
615
648
|
|
|
616
649
|
### The transport, standalone
|
|
617
650
|
|
|
@@ -623,7 +656,7 @@ import { useChat } from '@ai-sdk/react';
|
|
|
623
656
|
import { AgentChatTransport } from '@dudousxd/nestjs-agent-react';
|
|
624
657
|
|
|
625
658
|
const transport = new AgentChatTransport({
|
|
626
|
-
|
|
659
|
+
path: 'agent', // the default
|
|
627
660
|
getHeaders: () => ({ 'x-actor-id': currentUser.id }),
|
|
628
661
|
onMeta: ({ runId, threadId }) => console.log('turn started', runId, threadId),
|
|
629
662
|
});
|
|
@@ -647,16 +680,15 @@ contract — for a backend that serves these routes without this library's loop
|
|
|
647
680
|
|
|
648
681
|
### Loading persisted history
|
|
649
682
|
|
|
650
|
-
|
|
651
|
-
|
|
683
|
+
`useAgentChat({ threadId })` loads it for you. To seed a chat with history you already have (SSR,
|
|
684
|
+
a cache), convert the thread's `StoredMessage[]` and pass it as `initialMessages` — the hook then
|
|
685
|
+
skips its own read:
|
|
652
686
|
|
|
653
687
|
```ts
|
|
654
688
|
import { storedThreadToUiMessages } from '@dudousxd/nestjs-agent-react';
|
|
655
689
|
|
|
656
|
-
const detail = await chat.client.getThread(threadId);
|
|
657
690
|
const initialMessages = storedThreadToUiMessages(detail.messages);
|
|
658
|
-
|
|
659
|
-
// `initialMessages` is only read once, on mount.
|
|
691
|
+
useAgentChat({ threadId, initialMessages });
|
|
660
692
|
```
|
|
661
693
|
|
|
662
694
|
`storedThreadToUiMessages` merges the store's one-row-per-model-iteration turns (a turn with tool
|
|
@@ -668,19 +700,17 @@ persisted pushed components as `data-ui` parts, so a reloaded thread shows what
|
|
|
668
700
|
|
|
669
701
|
### Attachments
|
|
670
702
|
|
|
671
|
-
`useAttachments` is the composer's file tray without the
|
|
672
|
-
with progress and cancel, retry, image previews, and the
|
|
673
|
-
paste.
|
|
703
|
+
`chat.composer.files` (or `useAttachments()` on its own) is the composer's file tray without the
|
|
704
|
+
tray: validation, one upload per file with progress and cancel, retry, image previews, and the
|
|
705
|
+
handlers for a file input, a drop zone and paste. `chat.composer.submit()` sends the ready files
|
|
706
|
+
with the draft and clears them.
|
|
674
707
|
|
|
675
708
|
```tsx
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
backend: chat.backend, // or `upload: (file, { signal, onProgress }) => myUpload(file)`
|
|
680
|
-
accept: 'image/*,.pdf',
|
|
681
|
-
maxBytes: 20 * 1024 * 1024,
|
|
682
|
-
maxFiles: 5,
|
|
709
|
+
const chat = useAgentChat({
|
|
710
|
+
// optional — or `upload: (file, { signal, onProgress }) => myUpload(file)`
|
|
711
|
+
composer: { accept: 'image/*,.pdf', maxBytes: 20 * 1024 * 1024, maxFiles: 5 },
|
|
683
712
|
});
|
|
713
|
+
const { files } = chat.composer;
|
|
684
714
|
|
|
685
715
|
<div {...files.dropZoneProps} data-dragging={files.isDragging}>
|
|
686
716
|
<textarea onPaste={files.onPaste} />
|
|
@@ -695,13 +725,8 @@ const files = useAttachments({
|
|
|
695
725
|
))}
|
|
696
726
|
</div>
|
|
697
727
|
|
|
698
|
-
<
|
|
699
|
-
|
|
700
|
-
onClick={async () => {
|
|
701
|
-
await chat.sendMessage({ text }, { body: { attachments: files.refs } });
|
|
702
|
-
files.clear();
|
|
703
|
-
}}
|
|
704
|
-
/>
|
|
728
|
+
<textarea value={chat.composer.text} onChange={(e) => chat.composer.setText(e.target.value)} />
|
|
729
|
+
<button disabled={!chat.composer.canSend} onClick={() => chat.composer.submit()} />
|
|
705
730
|
```
|
|
706
731
|
|
|
707
732
|
An item is `uploading`, `ready`, `error` (retry with `files.retry(id)`) or `rejected` (failed
|
|
@@ -713,25 +738,25 @@ flight. `messageFiles(message)` reads the files back off any message — live or
|
|
|
713
738
|
|
|
714
739
|
With `AgentMediaAttachmentsModule` on the server (`@dudousxd/nestjs-agent/media`), one option turns
|
|
715
740
|
it on — uploads go in chunks through nestjs-media's tus endpoint, with progress, abort (`remove`)
|
|
716
|
-
and retry, on the
|
|
741
|
+
and retry, on the client's own connection (origin, path, headers, credentials):
|
|
717
742
|
|
|
718
743
|
```tsx
|
|
719
744
|
import { mediaAttachments } from '@dudousxd/nestjs-agent-react/media';
|
|
720
745
|
|
|
721
|
-
|
|
722
|
-
const files = useAttachments(
|
|
746
|
+
<AgentProvider attachments={{ upload: mediaAttachments() }}>…</AgentProvider>;
|
|
747
|
+
const files = useAttachments(); // uploads resumably
|
|
723
748
|
```
|
|
724
749
|
|
|
725
750
|
Headless; `@dudousxd/nestjs-media-client` is an optional peer only this subpath uses. Extending it:
|
|
726
751
|
|
|
727
|
-
- `mediaAttachments({
|
|
728
|
-
- `new AgentClient({ …connection, attachments: mediaAttachments() })` — your own client
|
|
729
|
-
- `
|
|
730
|
-
|
|
752
|
+
- `mediaAttachments({ chunkSize, retries })` — tuning (the path comes from the client's `path`).
|
|
753
|
+
- `new AgentClient({ …connection, attachments: { upload: mediaAttachments() } })` — your own client.
|
|
754
|
+
- `createMediaUpload(connection)` — a bare `upload` for `useAttachments({ upload })`, or the
|
|
755
|
+
`uploadAttachment` member of your own `AgentBackend`.
|
|
731
756
|
- Refusals throw `MediaUploadError` with the HTTP `status` (`413`, `415`, …); an aborted or failed
|
|
732
757
|
upload is discarded on the server.
|
|
733
758
|
|
|
734
|
-
Your own storage instead:
|
|
759
|
+
Your own storage instead: `<AgentProvider attachments={{ upload: (file, { signal, onProgress }, connection) => … }}>`
|
|
735
760
|
(an `AttachmentUploadStrategy`), or `useAttachments({ upload })`, resolving to a
|
|
736
761
|
`{ mediaId, url, contentType, name }` your server's `AGENT_ATTACHMENT_STAGING` recognises.
|
|
737
762
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ThreadSummary, ThreadDetail, MessageAttachment, ToolCatalogEntry, SkillCatalogEntry,
|
|
1
|
+
import { ThreadSummary, ThreadDetail, MessageAttachment, ToolCatalogEntry, SkillCatalogEntry, QuotaReport, ModelCatalogView, AgentCatalogEntry, MessageFeedbackValue, MessageFeedback } from '@dudousxd/nestjs-agent-core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Partial update accepted by `PATCH <base>/threads/:threadId`. `defaultAgent: null` clears a
|
|
@@ -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
|
|
@@ -114,7 +117,6 @@ interface AgentBackend {
|
|
|
114
117
|
uploadAttachment?(file: File, options?: UploadAttachmentOptions): Promise<MessageAttachment>;
|
|
115
118
|
listTools?(agent?: string): Promise<ToolCatalogEntry[]>;
|
|
116
119
|
listSkills?(threadId?: string): Promise<SkillCatalogEntry[]>;
|
|
117
|
-
getQuotaToday?(): Promise<QuotaView>;
|
|
118
120
|
/** `GET <base>/quota` — every budget window, and which one blocks sends, if any. */
|
|
119
121
|
getQuota?(): Promise<QuotaReport>;
|
|
120
122
|
/** `GET <base>/models?agent=` — what a model picker offers. */
|
|
@@ -149,8 +151,6 @@ declare class AgentHttpError extends Error {
|
|
|
149
151
|
readonly path: string;
|
|
150
152
|
constructor(status: number, method: string, path: string, statusText: string);
|
|
151
153
|
}
|
|
152
|
-
/** The quota-today read-model: usage, the configured limit (null → unlimited), and USD spend. */
|
|
153
|
-
type QuotaToday = QuotaView;
|
|
154
154
|
interface CancelResult {
|
|
155
155
|
aborted: boolean;
|
|
156
156
|
}
|
|
@@ -158,8 +158,16 @@ interface OkResult {
|
|
|
158
158
|
ok: boolean;
|
|
159
159
|
}
|
|
160
160
|
interface AgentClientOptions {
|
|
161
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* The server's origin, e.g. `https://api.example.com`. Defaults to `''` (same origin). The
|
|
163
|
+
* agent's route prefix is {@link AgentClientOptions.path}, not part of this.
|
|
164
|
+
*/
|
|
162
165
|
baseUrl?: string;
|
|
166
|
+
/**
|
|
167
|
+
* The agent's route prefix — `AgentModule`'s `path`, with any global prefix in front
|
|
168
|
+
* (`'api/agent'`). Leading/trailing slashes are optional. Defaults to `'agent'`.
|
|
169
|
+
*/
|
|
170
|
+
path?: string;
|
|
163
171
|
/** Static headers merged into every request. */
|
|
164
172
|
headers?: Record<string, string>;
|
|
165
173
|
/**
|
|
@@ -175,12 +183,15 @@ interface AgentClientOptions {
|
|
|
175
183
|
credentials?: RequestCredentials;
|
|
176
184
|
/** Injectable for tests / non-browser runtimes. */
|
|
177
185
|
fetch?: typeof fetch;
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
/** Attachment uploads. */
|
|
187
|
+
attachments?: {
|
|
188
|
+
/**
|
|
189
|
+
* How `uploadAttachment` uploads. Omitted → `POST <path>/attachments` (multipart). Pass
|
|
190
|
+
* `mediaAttachments()` from `@dudousxd/nestjs-agent-react/media` for resumable uploads through
|
|
191
|
+
* nestjs-media, or your own {@link AttachmentUploadStrategy}.
|
|
192
|
+
*/
|
|
193
|
+
upload?: AttachmentUploadStrategy;
|
|
194
|
+
};
|
|
184
195
|
}
|
|
185
196
|
/**
|
|
186
197
|
* Framework-agnostic REST client for the nestjs-agent endpoints — the default {@link AgentBackend}.
|
|
@@ -189,10 +200,10 @@ interface AgentClientOptions {
|
|
|
189
200
|
declare class AgentClient implements AgentBackend {
|
|
190
201
|
private readonly options;
|
|
191
202
|
constructor(options?: AgentClientOptions);
|
|
192
|
-
/** `POST
|
|
203
|
+
/** `POST <path>/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
|
|
193
204
|
openChatStream(request: ChatStreamRequest): Promise<ChatStreamResponse>;
|
|
194
205
|
/**
|
|
195
|
-
* `GET
|
|
206
|
+
* `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
|
|
196
207
|
* streaming under that id (404).
|
|
197
208
|
*/
|
|
198
209
|
resumeChatStream(request: ResumeStreamRequest): Promise<ChatStreamResponse | null>;
|
|
@@ -220,25 +231,23 @@ declare class AgentClient implements AgentBackend {
|
|
|
220
231
|
getThread(id: string): Promise<ThreadDetail>;
|
|
221
232
|
deleteThread(id: string): Promise<void>;
|
|
222
233
|
forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary>;
|
|
223
|
-
|
|
224
|
-
/** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
|
|
234
|
+
/** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */
|
|
225
235
|
updateThread(id: string, patch: ThreadPatch): Promise<OkResult>;
|
|
226
236
|
/**
|
|
227
237
|
* Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
|
|
228
|
-
* mirrors the backend's `POST
|
|
238
|
+
* mirrors the backend's `POST <path>/attachments`. The returned {@link MessageAttachment} is
|
|
229
239
|
* what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
|
|
230
240
|
*/
|
|
231
241
|
uploadAttachment(file: File, options?: UploadAttachmentOptions): Promise<MessageAttachment>;
|
|
232
242
|
private uploadWithProgress;
|
|
233
243
|
promoteThread(id: string): Promise<OkResult>;
|
|
234
244
|
truncateFromMessage(threadId: string, messageId: string): Promise<OkResult>;
|
|
235
|
-
/** `GET
|
|
245
|
+
/** `GET <path>/models?agent=` — the models this caller may pick, grouped by provider. */
|
|
236
246
|
listModels(agent?: string): Promise<ModelCatalogView>;
|
|
237
|
-
/** `GET
|
|
247
|
+
/** `GET <path>/agents` — the registered agents, the default one flagged. */
|
|
238
248
|
listAgents(): Promise<AgentCatalogEntry[]>;
|
|
239
|
-
/** `GET
|
|
249
|
+
/** `GET <path>/quota` — the caller's budget windows and the one blocking sends, if any. */
|
|
240
250
|
getQuota(): Promise<QuotaReport>;
|
|
241
|
-
getQuotaToday(): Promise<QuotaToday>;
|
|
242
251
|
cancelStream(runId: string): Promise<CancelResult>;
|
|
243
252
|
/**
|
|
244
253
|
* `remember` approves later calls of the same tool in the same thread; `via` names the surface
|
|
@@ -276,10 +285,13 @@ declare class AgentClient implements AgentBackend {
|
|
|
276
285
|
/** This client's connection, for an {@link AttachmentUploadStrategy}. */
|
|
277
286
|
private connection;
|
|
278
287
|
private baseUrl;
|
|
288
|
+
private agentPath;
|
|
289
|
+
/** Origin + agent path: what every route hangs off. */
|
|
290
|
+
private root;
|
|
279
291
|
private resolveHeaders;
|
|
280
292
|
private credentials;
|
|
281
293
|
private request;
|
|
282
294
|
private handleResponse;
|
|
283
295
|
}
|
|
284
296
|
|
|
285
|
-
export { type AgentBackend as A, type CancelResult as C, type MessageFeedbackInput as M, type
|
|
297
|
+
export { type AgentBackend as A, type CancelResult as C, type MessageFeedbackInput as M, 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 };
|