@dudousxd/nestjs-agent-react 0.19.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 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({ baseUrl: '/agent' });
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({ baseUrl: '/agent' });
138
- const { catalog } = useToolCatalog({ client: chat.client });
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({ baseUrl: '/agent', onThreadCreated: setThreadId });
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({ client: chat.client, getThreadId: () => threadId })],
337
- [chat.client],
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
- const chat = useAgentChat({ backend }); // chat.backend === backend, typed as yours
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
- ```ts
552
+ ```tsx
532
553
  const readCookie = (name: string) =>
533
554
  decodeURIComponent(document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`))?.[1] ?? '');
534
555
 
535
- useAgentChat({
536
- baseUrl: '/api',
537
- credentials: 'include', // 'same-origin' (the fetch default) is enough when the API is same-origin
538
- getHeaders: () => ({ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') }),
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({ backend: chat.backend });
558
- const feedback = useMessageFeedback({ backend: chat.backend, threadId: chat.getThreadId });
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({ 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 });
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({ backend }); // GET <base>/quota
605
- const chat = useAgentChat({ backend, blocked: quota.blocked });
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
- baseUrl: '/agent',
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.client.getThread(threadId);
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
- backend: chat.backend, // or `upload: (file, { signal, onProgress }) => myUpload(file)`
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,
@@ -709,6 +732,32 @@ An item is `uploading`, `ready`, `error` (retry with `files.retry(id)`) or `reje
709
732
  flight. `messageFiles(message)` reads the files back off any message — live or replayed — with a
710
733
  `kind` (`image`, `pdf`, `text`, …), the extension and, for replayed ones, the stored `mediaId`.
711
734
 
735
+ #### Resumable uploads on nestjs-media (`@dudousxd/nestjs-agent-react/media`)
736
+
737
+ With `AgentMediaAttachmentsModule` on the server (`@dudousxd/nestjs-agent/media`), one option turns
738
+ it on — uploads go in chunks through nestjs-media's tus endpoint, with progress, abort (`remove`)
739
+ and retry, on the client's own connection (origin, path, headers, credentials):
740
+
741
+ ```tsx
742
+ import { mediaAttachments } from '@dudousxd/nestjs-agent-react/media';
743
+
744
+ <AgentProvider attachments={{ upload: mediaAttachments() }}>…</AgentProvider>;
745
+ const files = useAttachments(); // uploads resumably
746
+ ```
747
+
748
+ Headless; `@dudousxd/nestjs-media-client` is an optional peer only this subpath uses. Extending it:
749
+
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`.
754
+ - Refusals throw `MediaUploadError` with the HTTP `status` (`413`, `415`, …); an aborted or failed
755
+ upload is discarded on the server.
756
+
757
+ Your own storage instead: `<AgentProvider attachments={{ upload: (file, { signal, onProgress }, connection) => … }}>`
758
+ (an `AttachmentUploadStrategy`), or `useAttachments({ upload })`, resolving to a
759
+ `{ mediaId, url, contentType, name }` your server's `AGENT_ATTACHMENT_STAGING` recognises.
760
+
712
761
  The rest of `AgentClient` works outside `useAgentChat` too — e.g. a standalone approvals inbox:
713
762
 
714
763
  ```ts
@@ -0,0 +1,302 @@
1
+ import { ThreadSummary, ThreadDetail, MessageAttachment, ToolCatalogEntry, SkillCatalogEntry, QuotaView, QuotaReport, ModelCatalogView, AgentCatalogEntry, MessageFeedbackValue, MessageFeedback } from '@dudousxd/nestjs-agent-core';
2
+
3
+ /**
4
+ * Partial update accepted by `PATCH <base>/threads/:threadId`. `defaultAgent: null` clears a
5
+ * previously-set default back to the module's own default; omitting it leaves the thread's
6
+ * current default untouched.
7
+ */
8
+ interface ThreadPatch {
9
+ title?: string;
10
+ defaultAgent?: string | null;
11
+ /** Pin a catalog model on the thread; `null` unpins it. */
12
+ model?: string | null;
13
+ }
14
+ /** Starting a turn: the body `POST <base>/chat` takes (see docs/stream-protocol.md). */
15
+ interface ChatStreamRequest {
16
+ /** `{ message, threadId?, agent?, model?, attachments?, pageContext?, regenerate?, … }`. */
17
+ body: Record<string, unknown>;
18
+ /** Per-request headers the AI SDK was handed for this send. */
19
+ headers?: Record<string, string>;
20
+ /** Aborted when the user stops the turn. */
21
+ signal?: AbortSignal;
22
+ }
23
+ /** Attaching to a run that is already streaming: `GET <base>/chat/:runId/stream?after=<seq>`. */
24
+ interface ResumeStreamRequest {
25
+ runId: string;
26
+ /**
27
+ * The sequence number (SSE `id:`) of the last frame the client already has. Omitted → replay
28
+ * from the first frame. A backend that cannot skip may ignore it: the transport also drops frames
29
+ * at or below it.
30
+ */
31
+ after?: number;
32
+ headers?: Record<string, string>;
33
+ signal?: AbortSignal;
34
+ }
35
+ /**
36
+ * An open chat stream: the raw `text/event-stream` bytes in the stream-protocol framing, plus the
37
+ * identity a backend may learn before the first frame (the `X-Agent-Run-Id` / `X-Agent-Thread-Id`
38
+ * headers). The body's own `event: meta` frame supplies them otherwise.
39
+ */
40
+ interface ChatStreamResponse {
41
+ body: ReadableStream<Uint8Array>;
42
+ runId?: string;
43
+ threadId?: string;
44
+ }
45
+ /** What `POST <base>/messages/:id/feedback` takes. `value: null` clears the rating. */
46
+ interface MessageFeedbackInput {
47
+ value: MessageFeedbackValue | null;
48
+ comment?: string;
49
+ }
50
+ interface UploadAttachmentOptions {
51
+ signal?: AbortSignal;
52
+ /** Called with 0..1 as the upload progresses, when the backend can observe it. */
53
+ onProgress?: (fraction: number) => void;
54
+ }
55
+ /** How {@link AgentClient} reaches the server — handed to an {@link AttachmentUploadStrategy}. */
56
+ interface AgentConnection {
57
+ /** The server's origin, trailing slash removed (`''` for same-origin). */
58
+ baseUrl: string;
59
+ /** The agent's route prefix, normalized to a leading slash (`'/agent'`), or `''`. */
60
+ path: string;
61
+ /** The client's static + per-request headers, resolved now (auth, CSRF). */
62
+ headers: () => Promise<Record<string, string>>;
63
+ credentials?: RequestCredentials;
64
+ fetch: typeof fetch;
65
+ }
66
+ /**
67
+ * Replaces {@link AgentClient}'s own `uploadAttachment` (`POST <path>/attachments`) — e.g.
68
+ * `mediaAttachments()` from `@dudousxd/nestjs-agent-react/media`, or your own storage. Gets the
69
+ * client's connection so it needs no configuration of its own. Resolve with the attachment your
70
+ * server's `AGENT_ATTACHMENT_STAGING` recognises by `mediaId`.
71
+ */
72
+ type AttachmentUploadStrategy = (file: File, options: UploadAttachmentOptions, connection: AgentConnection) => Promise<MessageAttachment>;
73
+ /**
74
+ * Everything the React layer asks of a server — the seam between `useAgentChat` (and the other
75
+ * hooks) and whatever serves the agent. {@link AgentClient} is the default implementation, over the
76
+ * library's own REST routes with `fetch`. An app with its own client — a generated one, a different
77
+ * auth scheme (cookie session + CSRF header), a backend that is not this library at all but speaks
78
+ * docs/stream-protocol.md — implements this instead and passes it as `<AgentProvider backend>` (or
79
+ * per hook, `useAgentChat({ backend })`).
80
+ *
81
+ * The streaming, thread and cancel members are required: without them there is no chat. The rest
82
+ * are optional; a hook that needs one the backend does not have throws
83
+ * {@link AgentBackendUnsupportedError} when it is called, so a backend only implements what its
84
+ * server supports.
85
+ */
86
+ interface AgentBackend {
87
+ /** Start a turn and return its SSE stream. Throw on a non-2xx answer. */
88
+ openChatStream(request: ChatStreamRequest): Promise<ChatStreamResponse>;
89
+ /** Attach to a streaming run. Resolve `null` when nothing is streaming under that id (HTTP 404). */
90
+ resumeChatStream(request: ResumeStreamRequest): Promise<ChatStreamResponse | null>;
91
+ /** Hard-stop a run server-side. */
92
+ cancelStream(runId: string): Promise<unknown>;
93
+ listThreads(): Promise<ThreadSummary[]>;
94
+ getThread(id: string): Promise<ThreadDetail>;
95
+ updateThread(id: string, patch: ThreadPatch): Promise<unknown>;
96
+ deleteThread(id: string): Promise<unknown>;
97
+ forkFromMessage?(threadId: string, messageId: string): Promise<ThreadSummary>;
98
+ promoteThread?(id: string): Promise<unknown>;
99
+ truncateFromMessage?(threadId: string, messageId: string): Promise<unknown>;
100
+ approveToolCall?(input: {
101
+ toolCallId: string;
102
+ remember?: boolean;
103
+ via?: string;
104
+ }): Promise<unknown>;
105
+ rejectToolCall?(input: {
106
+ toolCallId: string;
107
+ reason?: string;
108
+ via?: string;
109
+ }): Promise<unknown>;
110
+ answerToolCall?(input: {
111
+ toolCallId: string;
112
+ answers?: Record<string, string[]>;
113
+ }): Promise<unknown>;
114
+ skipToolCall?(input: {
115
+ toolCallId: string;
116
+ }): Promise<unknown>;
117
+ uploadAttachment?(file: File, options?: UploadAttachmentOptions): Promise<MessageAttachment>;
118
+ listTools?(agent?: string): Promise<ToolCatalogEntry[]>;
119
+ listSkills?(threadId?: string): Promise<SkillCatalogEntry[]>;
120
+ getQuotaToday?(): Promise<QuotaView>;
121
+ /** `GET <base>/quota` — every budget window, and which one blocks sends, if any. */
122
+ getQuota?(): Promise<QuotaReport>;
123
+ /** `GET <base>/models?agent=` — what a model picker offers. */
124
+ listModels?(agent?: string): Promise<ModelCatalogView>;
125
+ /** `GET <base>/agents` — what an agent picker offers. */
126
+ listAgents?(): Promise<AgentCatalogEntry[]>;
127
+ setMessageFeedback?(messageId: string, input: MessageFeedbackInput): Promise<{
128
+ feedback: MessageFeedback | null;
129
+ }>;
130
+ }
131
+ /** An optional {@link AgentBackend} member the bound backend does not implement was called. */
132
+ declare class AgentBackendUnsupportedError extends Error {
133
+ readonly method: string;
134
+ constructor(method: string);
135
+ }
136
+ type OptionalMethod = {
137
+ [K in keyof AgentBackend]-?: undefined extends AgentBackend[K] ? K : never;
138
+ }[keyof AgentBackend];
139
+ /**
140
+ * The optional backend method `name`, bound — or a throw naming it. For hook code that has to call
141
+ * something a backend may not offer.
142
+ */
143
+ declare function requireBackendMethod<K extends OptionalMethod>(backend: AgentBackend, name: K): NonNullable<AgentBackend[K]>;
144
+
145
+ /**
146
+ * Thrown by {@link AgentClient} on a non-2xx response. Carries the HTTP `status` so callers can
147
+ * branch (e.g. 403 → "not your thread", 429 → quota) instead of string-matching a generic Error.
148
+ */
149
+ declare class AgentHttpError extends Error {
150
+ readonly status: number;
151
+ readonly method: string;
152
+ readonly path: string;
153
+ constructor(status: number, method: string, path: string, statusText: string);
154
+ }
155
+ /** The quota-today read-model: usage, the configured limit (null → unlimited), and USD spend. */
156
+ type QuotaToday = QuotaView;
157
+ interface CancelResult {
158
+ aborted: boolean;
159
+ }
160
+ interface OkResult {
161
+ ok: boolean;
162
+ }
163
+ interface AgentClientOptions {
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
+ */
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;
174
+ /** Static headers merged into every request. */
175
+ headers?: Record<string, string>;
176
+ /**
177
+ * Resolved per request — for short-lived bearer tokens, or a CSRF header read from a cookie
178
+ * (`{ 'X-XSRF-TOKEN': readCookie('XSRF-TOKEN') }`), which has to be read at request time because
179
+ * the server may rotate it.
180
+ */
181
+ getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
182
+ /**
183
+ * Forwarded to fetch so cookie auth works. Same-origin requests send cookies by default; set
184
+ * `'include'` when the API lives on another origin (and have it answer with credentialed CORS).
185
+ */
186
+ credentials?: RequestCredentials;
187
+ /** Injectable for tests / non-browser runtimes. */
188
+ fetch?: typeof fetch;
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
+ };
198
+ }
199
+ /**
200
+ * Framework-agnostic REST client for the nestjs-agent endpoints — the default {@link AgentBackend}.
201
+ * Used by `useAgentChat`, but standalone-usable (vanilla fetch, no React).
202
+ */
203
+ declare class AgentClient implements AgentBackend {
204
+ private readonly options;
205
+ constructor(options?: AgentClientOptions);
206
+ /** `POST <path>/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
207
+ openChatStream(request: ChatStreamRequest): Promise<ChatStreamResponse>;
208
+ /**
209
+ * `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
210
+ * streaming under that id (404).
211
+ */
212
+ resumeChatStream(request: ResumeStreamRequest): Promise<ChatStreamResponse | null>;
213
+ /**
214
+ * Rate a message (`'up'`/`'down'`, optional comment) or clear its rating (`value: null`). Answers
215
+ * the stored rating.
216
+ */
217
+ setMessageFeedback(messageId: string, input: MessageFeedbackInput): Promise<{
218
+ feedback: MessageFeedback | null;
219
+ }>;
220
+ listThreads(): Promise<ThreadSummary[]>;
221
+ /**
222
+ * The skills this caller can invoke right now, scope-resolved — the same list, built by the same
223
+ * call, that the model is offered, so what a user can type after a `/` and what the agent can
224
+ * reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to
225
+ * one conversation; omitted, the server reads it as a brand-new thread.
226
+ */
227
+ listSkills(threadId?: string): Promise<SkillCatalogEntry[]>;
228
+ /**
229
+ * The tools this caller can reach through `agent` (the default agent when omitted), each with the
230
+ * server-declared `presentation` a chat narrates it by — the same list the model is offered.
231
+ * Prefer {@link useToolCatalog}, which fetches it once and shares it.
232
+ */
233
+ listTools(agent?: string): Promise<ToolCatalogEntry[]>;
234
+ getThread(id: string): Promise<ThreadDetail>;
235
+ deleteThread(id: string): Promise<void>;
236
+ forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary>;
237
+ renameThread(id: string, title: string): Promise<OkResult>;
238
+ /** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */
239
+ updateThread(id: string, patch: ThreadPatch): Promise<OkResult>;
240
+ /**
241
+ * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
242
+ * mirrors the backend's `POST <path>/attachments`. The returned {@link MessageAttachment} is
243
+ * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
244
+ */
245
+ uploadAttachment(file: File, options?: UploadAttachmentOptions): Promise<MessageAttachment>;
246
+ private uploadWithProgress;
247
+ promoteThread(id: string): Promise<OkResult>;
248
+ truncateFromMessage(threadId: string, messageId: string): Promise<OkResult>;
249
+ /** `GET <path>/models?agent=` — the models this caller may pick, grouped by provider. */
250
+ listModels(agent?: string): Promise<ModelCatalogView>;
251
+ /** `GET <path>/agents` — the registered agents, the default one flagged. */
252
+ listAgents(): Promise<AgentCatalogEntry[]>;
253
+ /** `GET <path>/quota` — the caller's budget windows and the one blocking sends, if any. */
254
+ getQuota(): Promise<QuotaReport>;
255
+ getQuotaToday(): Promise<QuotaToday>;
256
+ cancelStream(runId: string): Promise<CancelResult>;
257
+ /**
258
+ * `remember` approves later calls of the same tool in the same thread; `via` names the surface
259
+ * the decision came through (the server records `'web'` when omitted).
260
+ */
261
+ approveToolCall(input: {
262
+ toolCallId: string;
263
+ remember?: boolean;
264
+ via?: string;
265
+ }): Promise<void>;
266
+ rejectToolCall(input: {
267
+ toolCallId: string;
268
+ reason?: string;
269
+ via?: string;
270
+ }): Promise<void>;
271
+ /**
272
+ * Settle a parked question set. `answers` is questionId → chosen option values; a question left
273
+ * out takes the pre-picked default the request carried, resolved server-side against the request
274
+ * the run already holds. Omit the whole object and the user has confirmed every pre-picked
275
+ * answer — which is the point of the surface, so it is a valid submission rather than a blank.
276
+ */
277
+ answerToolCall(input: {
278
+ toolCallId: string;
279
+ answers?: Record<string, string[]>;
280
+ }): Promise<void>;
281
+ /**
282
+ * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
283
+ * values a confirmation would, and persists differently on purpose — only one of them is
284
+ * evidence the user chose them.
285
+ */
286
+ skipToolCall(input: {
287
+ toolCallId: string;
288
+ }): Promise<void>;
289
+ private fetchImpl;
290
+ /** This client's connection, for an {@link AttachmentUploadStrategy}. */
291
+ private connection;
292
+ private baseUrl;
293
+ private agentPath;
294
+ /** Origin + agent path: what every route hangs off. */
295
+ private root;
296
+ private resolveHeaders;
297
+ private credentials;
298
+ private request;
299
+ private handleResponse;
300
+ }
301
+
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 };