@dudousxd/nestjs-agent-react 0.14.0 → 0.15.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 CHANGED
@@ -468,12 +468,101 @@ disables itself while busy never noticed; a double-submitted one, and StrictMode
468
468
  effect, did.
469
469
 
470
470
  `chat` is the AI SDK v7 `useChat` return value (`messages`, `status`, `sendMessage`, `stop`, …) spread
471
- together with the extras: `runId`/`activeRunId`, `client` (the raw `AgentClient`), thread list/CRUD
471
+ together with the extras: `runId`/`activeRunId`, `connection`, `getThreadId`, `backend` (the `AgentBackend`; also
472
+ under its old name `client`), thread list/CRUD
472
473
  (`threads`, `loadThreads`, `loadThread`, `deleteThread`, `forkThread`, `renameThread`, `promoteThread`,
473
474
  `truncateFromMessage`), `quota`/`loadQuota`, `cancel`, HITL `approve`/`reject` and `answer`/`skip`, and `regenerate`.
474
475
  `MyToolCard`'s `part` prop above types as the exported `AnyToolUIPart` (`ToolUIPart | DynamicToolUIPart`
475
476
  — `MessageItem` uses the same union for its own `renderToolPart` callback).
476
477
 
478
+ ### Your own backend (`AgentBackend`)
479
+
480
+ Everything the hooks ask of a server goes through one interface, `AgentBackend`: start a turn
481
+ (`openChatStream`), attach to a streaming run (`resumeChatStream`), cancel, list/get/update/delete
482
+ threads — required — plus optional fork/promote/truncate, approve/reject/answer/skip, upload, tools,
483
+ skills, quota and message feedback. `AgentClient` (fetch over this library's routes) is the default.
484
+ An app with its own client, auth scheme or server implements the interface and passes it in:
485
+
486
+ ```ts
487
+ import type { AgentBackend } from '@dudousxd/nestjs-agent-react';
488
+
489
+ const backend: AgentBackend = {
490
+ async openChatStream({ body, headers, signal }) {
491
+ const res = await fetch('/api/chat', {
492
+ method: 'POST',
493
+ credentials: 'include',
494
+ headers: { 'content-type': 'application/json', ...csrfHeader(), ...headers },
495
+ body: JSON.stringify(body),
496
+ signal,
497
+ });
498
+ if (!res.ok || !res.body) throw new Error(`chat failed: ${res.status}`);
499
+ return { body: res.body }; // SSE bytes in docs/stream-protocol.md framing
500
+ },
501
+ async resumeChatStream({ runId, after, signal }) {
502
+ const res = await fetch(`/api/chat/${runId}/stream${after ? `?after=${after}` : ''}`, {
503
+ credentials: 'include',
504
+ signal,
505
+ });
506
+ return res.status === 404 ? null : { body: res.body! };
507
+ },
508
+ cancelStream: (runId) => api.chat.cancel(runId),
509
+ listThreads: () => api.threads.list(),
510
+ getThread: (id) => api.threads.get(id),
511
+ updateThread: (id, patch) => api.threads.update(id, patch),
512
+ deleteThread: (id) => api.threads.remove(id),
513
+ setMessageFeedback: (id, input) => api.messages.feedback(id, input),
514
+ };
515
+
516
+ const chat = useAgentChat({ backend }); // chat.backend === backend, typed as yours
517
+ ```
518
+
519
+ Calling a hook method whose optional backend member is missing throws
520
+ `AgentBackendUnsupportedError`. The body a backend receives is `{ message, threadId?, agent?,
521
+ attachments?, pageContext?, regenerate? }`; the SSE it returns and the REST shapes are in
522
+ [docs/stream-protocol.md](../../docs/stream-protocol.md).
523
+
524
+ **Cookie session + CSRF with the default client.** `credentials` and `getHeaders` are all it takes —
525
+ `getHeaders` runs per request, so a rotated token is picked up:
526
+
527
+ ```ts
528
+ const readCookie = (name: string) =>
529
+ decodeURIComponent(document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`))?.[1] ?? '');
530
+
531
+ useAgentChat({
532
+ baseUrl: '/api',
533
+ credentials: 'include', // 'same-origin' (the fetch default) is enough when the API is same-origin
534
+ getHeaders: () => ({ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') }),
535
+ });
536
+ ```
537
+
538
+ ### Reconnecting a dropped stream
539
+
540
+ A server that numbers its frames (SSE `id:`, as this library's does) can be resumed: when the
541
+ connection drops mid-run, the transport re-attaches with `GET <base>/chat/:runId/stream?after=<last
542
+ id>`, backing off 0.5s, 1s, 2s… (`reconnect: { maxAttempts, baseDelayMs, maxDelayMs }`, or `false`).
543
+ Meanwhile `chat.status` is `'reconnecting'` (a busy status to `useChatTranscript`) and
544
+ `chat.connection` says which attempt it is on. If the run ended while the client was away (the
545
+ resume answers 404), the hook reloads the thread so the full answer replaces the partial one. After
546
+ the last failed attempt the turn ends with an error.
547
+
548
+ ### Thread list and message feedback
549
+
550
+ ```tsx
551
+ import { useMessageFeedback, useThreads } from '@dudousxd/nestjs-agent-react';
552
+
553
+ const { threads, isLoading, rename, remove, refresh } = useThreads({ backend: chat.backend });
554
+ const feedback = useMessageFeedback({ backend: chat.backend, threadId: chat.getThreadId });
555
+
556
+ <button aria-pressed={feedback.feedbackOf(message)?.value === 'up'}
557
+ onClick={() => feedback.toggle(message, 'up')}>Helpful</button>
558
+ ```
559
+
560
+ `useThreads` refreshes itself when a chat on the same backend creates a thread or settles a run,
561
+ and patches a streamed title in place; `rename`/`remove` are optimistic and roll back on failure.
562
+ `useMessageFeedback` reads a replayed message's rating from `message.metadata.feedback`, rates
563
+ through `POST <base>/messages/:id/feedback`, and maps a message streamed in this session to the row
564
+ its run persisted (the live message carries `metadata.runId`).
565
+
477
566
  ### The transport, standalone
478
567
 
479
568
  `AgentChatTransport` is a plain AI SDK v7 `ChatTransport` — wire it straight into `useChat` for the SSE
package/dist/genui.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { T as TranscriptUiBlock } from './model-CMNhyau6.cjs';
3
+ import { T as TranscriptUiBlock } from './model-gHqjNjLJ.cjs';
4
4
  import { G as GenerativeUIOptions, a as GenerativeUIItem, b as GenerativeUIProblem, c as GenerativeUIElement, d as GenerativeUIState } from './types-DFD-soWU.cjs';
5
5
  export { e as GENUI_TREE_COMPONENT, f as GenuiCatalogLike, g as GenuiIssueLike, h as GenuiRegistry, i as GenuiRenderer, R as ResolveComponent } from './types-DFD-soWU.cjs';
6
6
  import '@dudousxd/nestjs-agent-core';
package/dist/genui.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { T as TranscriptUiBlock } from './model-CMNhyau6.js';
3
+ import { T as TranscriptUiBlock } from './model-gHqjNjLJ.js';
4
4
  import { G as GenerativeUIOptions, a as GenerativeUIItem, b as GenerativeUIProblem, c as GenerativeUIElement, d as GenerativeUIState } from './types-DFD-soWU.js';
5
5
  export { e as GENUI_TREE_COMPONENT, f as GenuiCatalogLike, g as GenuiIssueLike, h as GenuiRegistry, i as GenuiRenderer, R as ResolveComponent } from './types-DFD-soWU.js';
6
6
  import '@dudousxd/nestjs-agent-core';