@dudousxd/nestjs-agent-react 0.3.3 → 0.4.1

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/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as ai from 'ai';
2
2
  import { ChatTransport, UIMessage, UIMessageChunk, ToolUIPart, DynamicToolUIPart } from 'ai';
3
3
  import * as _dudousxd_nestjs_agent_core from '@dudousxd/nestjs-agent-core';
4
- import { ThreadSummary, ThreadDetail, QuotaView } from '@dudousxd/nestjs-agent-core';
4
+ import { ThreadSummary, ThreadDetail, MessageAttachment, QuotaView, StoredMessage } from '@dudousxd/nestjs-agent-core';
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
6
  import React from 'react';
7
7
 
@@ -112,6 +112,15 @@ interface CancelResult {
112
112
  interface OkResult {
113
113
  ok: boolean;
114
114
  }
115
+ /**
116
+ * Partial update accepted by `PATCH /agent/threads/:threadId`. `defaultAgent: null` clears a
117
+ * previously-set default back to the module's own default; omitting it leaves the thread's
118
+ * current default untouched.
119
+ */
120
+ interface ThreadPatch {
121
+ title?: string;
122
+ defaultAgent?: string | null;
123
+ }
115
124
  interface AgentClientOptions {
116
125
  /** Origin + base path, e.g. `https://api.example.com`. Defaults to `''`. */
117
126
  baseUrl?: string;
@@ -136,6 +145,14 @@ declare class AgentClient {
136
145
  deleteThread(id: string): Promise<void>;
137
146
  forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary>;
138
147
  renameThread(id: string, title: string): Promise<OkResult>;
148
+ /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
149
+ updateThread(id: string, patch: ThreadPatch): Promise<OkResult>;
150
+ /**
151
+ * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
152
+ * mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
153
+ * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
154
+ */
155
+ uploadAttachment(file: File): Promise<MessageAttachment>;
139
156
  promoteThread(id: string): Promise<OkResult>;
140
157
  truncateFromMessage(threadId: string, messageId: string): Promise<OkResult>;
141
158
  getQuotaToday(): Promise<QuotaToday>;
@@ -148,8 +165,31 @@ declare class AgentClient {
148
165
  reason?: string;
149
166
  }): Promise<void>;
150
167
  private request;
168
+ private handleResponse;
151
169
  }
152
170
 
171
+ /**
172
+ * Convert a persisted `StoredMessage` (the agent-lib's on-disk shape) into an AI SDK v7
173
+ * `UIMessage` so a loaded thread can seed `useChat`'s `initialMessages` / `messages`.
174
+ *
175
+ * The lib stores a message as flat `content` text plus `toolCalls`/`toolResults` arrays; the SDK
176
+ * renders from `parts`. This maps:
177
+ * - `content` → a single `text` part (skipped when empty).
178
+ * - each `attachment` → a `file` part (image/PDF) so the user bubble re-renders its
179
+ * thumbnails on a reloaded thread.
180
+ * - each `toolCall` → a `tool-<name>` part, pairing its `toolResult` (matched by id):
181
+ * - a result found → `output-available` state, carrying `output`.
182
+ * - no result found → `input-available` state (the call never finished, e.g. the run was
183
+ * interrupted) — `output` is omitted, never a fabricated value.
184
+ * When the store reports the tool's kind (`'read' | 'action'`), it rides along as
185
+ * `toolMetadata.toolKind` so a UI can render an approval affordance for `action` tools
186
+ * without hardcoding tool-name sets, matching how live-streamed tool parts carry it.
187
+ *
188
+ * Live streaming still arrives as fully-typed SDK tool parts (see `AgentChatTransport`); this
189
+ * converter only feeds replayed history, so the tool cards render the same either way.
190
+ */
191
+ declare function storedMessageToUiMessage(message: StoredMessage): UIMessage;
192
+
153
193
  interface UseAgentChatOptions {
154
194
  /** Origin + base path, e.g. `https://api.example.com`. Defaults to `''`. */
155
195
  baseUrl?: string;
@@ -173,8 +213,19 @@ interface UseAgentChatOptions {
173
213
  * Run id of a buffered stream to reconnect to on mount — wire this to the thread's
174
214
  * `activeStreamId`. Its presence both gates the SDK's `resume` and names the run, so a normal
175
215
  * mount (no id) never fires a doomed resume GET. Omitted → no resume.
216
+ *
217
+ * For the common case (no id in hand yet), prefer {@link UseAgentChatOptions.resume} — it does
218
+ * this fetch-and-wire for you.
176
219
  */
177
220
  resumeRunId?: string;
221
+ /**
222
+ * When `true` and the hook (re)mounts with a `threadId`, the hook fetches the thread and, if its
223
+ * `activeRunId` is non-null (a turn was still streaming when the page loaded/reloaded), attaches
224
+ * to that run's `GET /agent/chat/:runId/stream` automatically — the in-flight turn streams into
225
+ * this session's message state exactly as if the send had happened here. A no-op when `threadId`
226
+ * is omitted (nothing to resume) or the fetched thread has no active run. Default `false`.
227
+ */
228
+ resume?: boolean;
178
229
  /** Read at every send to capture a page snapshot for the page-assistant. */
179
230
  getPageContext?: () => Record<string, unknown> | null;
180
231
  /** Fired after each streamed turn finishes (e.g. to refetch the sidebar). */
@@ -201,6 +252,8 @@ interface AddToolResultArgs {
201
252
  declare function useAgentChat(options: UseAgentChatOptions): {
202
253
  addToolResult: ({ tool, toolCallId, output }: AddToolResultArgs) => void | PromiseLike<void>;
203
254
  runId: string | undefined;
255
+ /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
256
+ activeRunId: string | null;
204
257
  client: AgentClient;
205
258
  threads: ThreadSummary[];
206
259
  loadThreads: () => Promise<ThreadSummary[]>;
@@ -229,7 +282,7 @@ declare function useAgentChat(options: UseAgentChatOptions): {
229
282
  status: ai.ChatStatus;
230
283
  sendMessage: (message?: (Omit<UIMessage<unknown, ai.UIDataTypes, ai.UITools>, "id" | "role"> & {
231
284
  id?: string;
232
- role?: "system" | "user" | "assistant";
285
+ role?: "user" | "assistant" | "system";
233
286
  } & {
234
287
  text?: never;
235
288
  files?: never;
@@ -437,4 +490,4 @@ interface SpeechRecognitionHook {
437
490
  */
438
491
  declare function useSpeechRecognition({ lang, onFinalTranscript, onInterimTranscript, }: UseSpeechRecognitionOptions): SpeechRecognitionHook;
439
492
 
440
- export { AgentChatTransport, type AgentChatTransportOptions, AgentClient, type AgentClientOptions, AgentHttpError, type AgentStreamMeta, type AnyToolUIPart, type CancelResult, ChatInput, type ChatInputProps, type ChatStatus, DEFAULT_SPEECH_LANG, MessageItem, type MessageItemClassNames, type MessageItemProps, MessageList, type MessageListClassNames, type MessageListProps, type MessageUsageInfo, type QuotaToday, type RenderTextFn, type RenderToolGroupFn, type RenderToolPartFn, SPEECH_LANGUAGES, type SpeechLanguage, type SpeechRecognitionHook, type UseAgentChatOptions, type UseSpeechRecognitionOptions, formatRelativeTime, loadStoredSpeechLang, persistSpeechLang, useAgentChat, useSpeechRecognition };
493
+ export { AgentChatTransport, type AgentChatTransportOptions, AgentClient, type AgentClientOptions, AgentHttpError, type AgentStreamMeta, type AnyToolUIPart, type CancelResult, ChatInput, type ChatInputProps, type ChatStatus, DEFAULT_SPEECH_LANG, MessageItem, type MessageItemClassNames, type MessageItemProps, MessageList, type MessageListClassNames, type MessageListProps, type MessageUsageInfo, type QuotaToday, type RenderTextFn, type RenderToolGroupFn, type RenderToolPartFn, SPEECH_LANGUAGES, type SpeechLanguage, type SpeechRecognitionHook, type ThreadPatch, type UseAgentChatOptions, type UseSpeechRecognitionOptions, formatRelativeTime, loadStoredSpeechLang, persistSpeechLang, storedMessageToUiMessage, useAgentChat, useSpeechRecognition };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as ai from 'ai';
2
2
  import { ChatTransport, UIMessage, UIMessageChunk, ToolUIPart, DynamicToolUIPart } from 'ai';
3
3
  import * as _dudousxd_nestjs_agent_core from '@dudousxd/nestjs-agent-core';
4
- import { ThreadSummary, ThreadDetail, QuotaView } from '@dudousxd/nestjs-agent-core';
4
+ import { ThreadSummary, ThreadDetail, MessageAttachment, QuotaView, StoredMessage } from '@dudousxd/nestjs-agent-core';
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
6
6
  import React from 'react';
7
7
 
@@ -112,6 +112,15 @@ interface CancelResult {
112
112
  interface OkResult {
113
113
  ok: boolean;
114
114
  }
115
+ /**
116
+ * Partial update accepted by `PATCH /agent/threads/:threadId`. `defaultAgent: null` clears a
117
+ * previously-set default back to the module's own default; omitting it leaves the thread's
118
+ * current default untouched.
119
+ */
120
+ interface ThreadPatch {
121
+ title?: string;
122
+ defaultAgent?: string | null;
123
+ }
115
124
  interface AgentClientOptions {
116
125
  /** Origin + base path, e.g. `https://api.example.com`. Defaults to `''`. */
117
126
  baseUrl?: string;
@@ -136,6 +145,14 @@ declare class AgentClient {
136
145
  deleteThread(id: string): Promise<void>;
137
146
  forkFromMessage(threadId: string, messageId: string): Promise<ThreadSummary>;
138
147
  renameThread(id: string, title: string): Promise<OkResult>;
148
+ /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
149
+ updateThread(id: string, patch: ThreadPatch): Promise<OkResult>;
150
+ /**
151
+ * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
152
+ * mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
153
+ * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
154
+ */
155
+ uploadAttachment(file: File): Promise<MessageAttachment>;
139
156
  promoteThread(id: string): Promise<OkResult>;
140
157
  truncateFromMessage(threadId: string, messageId: string): Promise<OkResult>;
141
158
  getQuotaToday(): Promise<QuotaToday>;
@@ -148,8 +165,31 @@ declare class AgentClient {
148
165
  reason?: string;
149
166
  }): Promise<void>;
150
167
  private request;
168
+ private handleResponse;
151
169
  }
152
170
 
171
+ /**
172
+ * Convert a persisted `StoredMessage` (the agent-lib's on-disk shape) into an AI SDK v7
173
+ * `UIMessage` so a loaded thread can seed `useChat`'s `initialMessages` / `messages`.
174
+ *
175
+ * The lib stores a message as flat `content` text plus `toolCalls`/`toolResults` arrays; the SDK
176
+ * renders from `parts`. This maps:
177
+ * - `content` → a single `text` part (skipped when empty).
178
+ * - each `attachment` → a `file` part (image/PDF) so the user bubble re-renders its
179
+ * thumbnails on a reloaded thread.
180
+ * - each `toolCall` → a `tool-<name>` part, pairing its `toolResult` (matched by id):
181
+ * - a result found → `output-available` state, carrying `output`.
182
+ * - no result found → `input-available` state (the call never finished, e.g. the run was
183
+ * interrupted) — `output` is omitted, never a fabricated value.
184
+ * When the store reports the tool's kind (`'read' | 'action'`), it rides along as
185
+ * `toolMetadata.toolKind` so a UI can render an approval affordance for `action` tools
186
+ * without hardcoding tool-name sets, matching how live-streamed tool parts carry it.
187
+ *
188
+ * Live streaming still arrives as fully-typed SDK tool parts (see `AgentChatTransport`); this
189
+ * converter only feeds replayed history, so the tool cards render the same either way.
190
+ */
191
+ declare function storedMessageToUiMessage(message: StoredMessage): UIMessage;
192
+
153
193
  interface UseAgentChatOptions {
154
194
  /** Origin + base path, e.g. `https://api.example.com`. Defaults to `''`. */
155
195
  baseUrl?: string;
@@ -173,8 +213,19 @@ interface UseAgentChatOptions {
173
213
  * Run id of a buffered stream to reconnect to on mount — wire this to the thread's
174
214
  * `activeStreamId`. Its presence both gates the SDK's `resume` and names the run, so a normal
175
215
  * mount (no id) never fires a doomed resume GET. Omitted → no resume.
216
+ *
217
+ * For the common case (no id in hand yet), prefer {@link UseAgentChatOptions.resume} — it does
218
+ * this fetch-and-wire for you.
176
219
  */
177
220
  resumeRunId?: string;
221
+ /**
222
+ * When `true` and the hook (re)mounts with a `threadId`, the hook fetches the thread and, if its
223
+ * `activeRunId` is non-null (a turn was still streaming when the page loaded/reloaded), attaches
224
+ * to that run's `GET /agent/chat/:runId/stream` automatically — the in-flight turn streams into
225
+ * this session's message state exactly as if the send had happened here. A no-op when `threadId`
226
+ * is omitted (nothing to resume) or the fetched thread has no active run. Default `false`.
227
+ */
228
+ resume?: boolean;
178
229
  /** Read at every send to capture a page snapshot for the page-assistant. */
179
230
  getPageContext?: () => Record<string, unknown> | null;
180
231
  /** Fired after each streamed turn finishes (e.g. to refetch the sidebar). */
@@ -201,6 +252,8 @@ interface AddToolResultArgs {
201
252
  declare function useAgentChat(options: UseAgentChatOptions): {
202
253
  addToolResult: ({ tool, toolCallId, output }: AddToolResultArgs) => void | PromiseLike<void>;
203
254
  runId: string | undefined;
255
+ /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
256
+ activeRunId: string | null;
204
257
  client: AgentClient;
205
258
  threads: ThreadSummary[];
206
259
  loadThreads: () => Promise<ThreadSummary[]>;
@@ -229,7 +282,7 @@ declare function useAgentChat(options: UseAgentChatOptions): {
229
282
  status: ai.ChatStatus;
230
283
  sendMessage: (message?: (Omit<UIMessage<unknown, ai.UIDataTypes, ai.UITools>, "id" | "role"> & {
231
284
  id?: string;
232
- role?: "system" | "user" | "assistant";
285
+ role?: "user" | "assistant" | "system";
233
286
  } & {
234
287
  text?: never;
235
288
  files?: never;
@@ -437,4 +490,4 @@ interface SpeechRecognitionHook {
437
490
  */
438
491
  declare function useSpeechRecognition({ lang, onFinalTranscript, onInterimTranscript, }: UseSpeechRecognitionOptions): SpeechRecognitionHook;
439
492
 
440
- export { AgentChatTransport, type AgentChatTransportOptions, AgentClient, type AgentClientOptions, AgentHttpError, type AgentStreamMeta, type AnyToolUIPart, type CancelResult, ChatInput, type ChatInputProps, type ChatStatus, DEFAULT_SPEECH_LANG, MessageItem, type MessageItemClassNames, type MessageItemProps, MessageList, type MessageListClassNames, type MessageListProps, type MessageUsageInfo, type QuotaToday, type RenderTextFn, type RenderToolGroupFn, type RenderToolPartFn, SPEECH_LANGUAGES, type SpeechLanguage, type SpeechRecognitionHook, type UseAgentChatOptions, type UseSpeechRecognitionOptions, formatRelativeTime, loadStoredSpeechLang, persistSpeechLang, useAgentChat, useSpeechRecognition };
493
+ export { AgentChatTransport, type AgentChatTransportOptions, AgentClient, type AgentClientOptions, AgentHttpError, type AgentStreamMeta, type AnyToolUIPart, type CancelResult, ChatInput, type ChatInputProps, type ChatStatus, DEFAULT_SPEECH_LANG, MessageItem, type MessageItemClassNames, type MessageItemProps, MessageList, type MessageListClassNames, type MessageListProps, type MessageUsageInfo, type QuotaToday, type RenderTextFn, type RenderToolGroupFn, type RenderToolPartFn, SPEECH_LANGUAGES, type SpeechLanguage, type SpeechRecognitionHook, type ThreadPatch, type UseAgentChatOptions, type UseSpeechRecognitionOptions, formatRelativeTime, loadStoredSpeechLang, persistSpeechLang, storedMessageToUiMessage, useAgentChat, useSpeechRecognition };
package/dist/index.js CHANGED
@@ -177,6 +177,14 @@ var AgentChatTransport = class {
177
177
  break;
178
178
  case "step-finish":
179
179
  closeStep();
180
+ if (event.costUsd !== void 0) {
181
+ controller.enqueue({
182
+ type: "message-metadata",
183
+ messageMetadata: {
184
+ costUsd: event.costUsd
185
+ }
186
+ });
187
+ }
180
188
  break;
181
189
  case "text":
182
190
  ensureStep();
@@ -213,7 +221,15 @@ var AgentChatTransport = class {
213
221
  controller.enqueue({
214
222
  type: "tool-input-start",
215
223
  toolCallId: event.id,
216
- toolName: event.name
224
+ toolName: event.name,
225
+ // `toolKind` is absent on older backends — omitted (not `undefined`-valued) so
226
+ // `toolMetadata` itself is only present when there's something to say, letting a
227
+ // UI gate approval affordances on `kind === 'action'` without hardcoding tool names.
228
+ ...event.toolKind !== void 0 ? {
229
+ toolMetadata: {
230
+ toolKind: event.toolKind
231
+ }
232
+ } : {}
217
233
  });
218
234
  break;
219
235
  case "tool-input-delta":
@@ -230,7 +246,12 @@ var AgentChatTransport = class {
230
246
  type: "tool-input-available",
231
247
  toolCallId: event.id,
232
248
  toolName: event.name,
233
- input: event.input
249
+ input: event.input,
250
+ ...event.toolKind !== void 0 ? {
251
+ toolMetadata: {
252
+ toolKind: event.toolKind
253
+ }
254
+ } : {}
234
255
  });
235
256
  break;
236
257
  case "tool-output":
@@ -412,10 +433,39 @@ var AgentClient = class {
412
433
  return this.request("POST", `/agent/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
413
434
  }
414
435
  renameThread(id, title) {
415
- return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, {
436
+ return this.updateThread(id, {
416
437
  title
417
438
  });
418
439
  }
440
+ /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
441
+ updateThread(id, patch) {
442
+ return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, patch);
443
+ }
444
+ /**
445
+ * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
446
+ * mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
447
+ * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
448
+ */
449
+ async uploadAttachment(file) {
450
+ const fetchImpl = this.options.fetch ?? globalThis.fetch;
451
+ const baseUrl = (this.options.baseUrl ?? "").replace(/\/$/, "");
452
+ const dynamic = await this.options.getHeaders?.() ?? {};
453
+ const formData = new FormData();
454
+ formData.append("file", file);
455
+ const response = await fetchImpl(`${baseUrl}/agent/attachments`, {
456
+ method: "POST",
457
+ headers: {
458
+ accept: "application/json",
459
+ ...this.options.headers,
460
+ ...dynamic
461
+ },
462
+ body: formData,
463
+ ...this.options.credentials !== void 0 ? {
464
+ credentials: this.options.credentials
465
+ } : {}
466
+ });
467
+ return this.handleResponse(response, "POST", "/agent/attachments");
468
+ }
419
469
  promoteThread(id) {
420
470
  return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
421
471
  }
@@ -455,6 +505,9 @@ var AgentClient = class {
455
505
  credentials: this.options.credentials
456
506
  } : {}
457
507
  });
508
+ return this.handleResponse(response, method, path);
509
+ }
510
+ async handleResponse(response, method, path) {
458
511
  if (!response.ok) {
459
512
  throw new AgentHttpError(response.status, method, path, response.statusText);
460
513
  }
@@ -465,15 +518,64 @@ var AgentClient = class {
465
518
  }
466
519
  };
467
520
 
521
+ // src/stored-message-to-ui-message.ts
522
+ function storedMessageToUiMessage(message) {
523
+ const parts = [];
524
+ if (message.content) {
525
+ parts.push({
526
+ type: "text",
527
+ text: message.content
528
+ });
529
+ }
530
+ for (const attachment of message.attachments ?? []) {
531
+ parts.push({
532
+ type: "file",
533
+ mediaType: attachment.contentType,
534
+ filename: attachment.name,
535
+ url: attachment.url
536
+ });
537
+ }
538
+ for (const call of message.toolCalls ?? []) {
539
+ const result = message.toolResults?.find((candidate) => candidate.id === call.id);
540
+ const toolKind = call.kind;
541
+ parts.push({
542
+ type: `tool-${call.name}`,
543
+ toolCallId: call.id,
544
+ ...toolKind !== void 0 ? {
545
+ toolMetadata: {
546
+ toolKind
547
+ }
548
+ } : {},
549
+ ...result !== void 0 ? {
550
+ state: "output-available",
551
+ input: call.input,
552
+ output: result.output
553
+ } : {
554
+ state: "input-available",
555
+ input: call.input
556
+ }
557
+ });
558
+ }
559
+ return {
560
+ id: message.id,
561
+ role: message.role,
562
+ parts
563
+ };
564
+ }
565
+ __name(storedMessageToUiMessage, "storedMessageToUiMessage");
566
+
468
567
  // src/use-agent-chat.ts
469
568
  import { useChat } from "@ai-sdk/react";
470
- import { useCallback, useMemo, useRef, useState } from "react";
569
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
471
570
  function useAgentChat(options) {
472
571
  const latest = useRef(options);
473
572
  latest.current = options;
474
573
  const [runId, setRunId] = useState(options.resumeRunId);
475
574
  const runIdRef = useRef(runId);
476
575
  runIdRef.current = runId;
576
+ const autoResumeRunIdRef = useRef(void 0);
577
+ const [autoResume, setAutoResume] = useState(false);
578
+ const [activeRunId, setActiveRunId] = useState(null);
477
579
  const regenerateNext = useRef(false);
478
580
  const createdThreadId = useRef(void 0);
479
581
  const client = useMemo(() => {
@@ -491,6 +593,26 @@ function useAgentChat(options) {
491
593
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
492
594
  });
493
595
  }, []);
596
+ useEffect(() => {
597
+ if (!options.resume || options.threadId === void 0) return;
598
+ let cancelled = false;
599
+ const threadId = options.threadId;
600
+ void client.getThread(threadId).then((thread) => {
601
+ if (cancelled) return;
602
+ setActiveRunId(thread.activeRunId ?? null);
603
+ if (thread.activeRunId != null) {
604
+ autoResumeRunIdRef.current = thread.activeRunId;
605
+ setAutoResume(true);
606
+ }
607
+ });
608
+ return () => {
609
+ cancelled = true;
610
+ };
611
+ }, [
612
+ client,
613
+ options.resume,
614
+ options.threadId
615
+ ]);
494
616
  const transport = useMemo(() => {
495
617
  function onMeta(meta) {
496
618
  setRunId(meta.runId);
@@ -532,13 +654,13 @@ function useAgentChat(options) {
532
654
  } : {}
533
655
  };
534
656
  }, "getBody"),
535
- getResumeRunId: /* @__PURE__ */ __name(() => latest.current.resumeRunId, "getResumeRunId"),
657
+ getResumeRunId: /* @__PURE__ */ __name(() => latest.current.resumeRunId ?? autoResumeRunIdRef.current, "getResumeRunId"),
536
658
  onMeta
537
659
  });
538
660
  }, []);
539
661
  const chat = useChat({
540
662
  transport,
541
- resume: options.resumeRunId !== void 0,
663
+ resume: options.resumeRunId !== void 0 || autoResume,
542
664
  ...options.threadId !== void 0 ? {
543
665
  id: options.threadId
544
666
  } : {},
@@ -605,10 +727,10 @@ function useAgentChat(options) {
605
727
  ]);
606
728
  const cancel = useCallback(async () => {
607
729
  chatRef.current.stop();
608
- const activeRunId = runIdRef.current;
609
- if (activeRunId) {
730
+ const activeRunId2 = runIdRef.current;
731
+ if (activeRunId2) {
610
732
  try {
611
- await client.cancelStream(activeRunId);
733
+ await client.cancelStream(activeRunId2);
612
734
  } catch {
613
735
  }
614
736
  }
@@ -640,6 +762,8 @@ function useAgentChat(options) {
640
762
  ...chat,
641
763
  addToolResult,
642
764
  runId,
765
+ /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
766
+ activeRunId,
643
767
  client,
644
768
  threads,
645
769
  loadThreads,
@@ -668,10 +792,10 @@ async function mergeHeaders(options) {
668
792
  __name(mergeHeaders, "mergeHeaders");
669
793
 
670
794
  // src/components/chat-input.tsx
671
- import React, { useEffect as useEffect2, useState as useState3 } from "react";
795
+ import React, { useEffect as useEffect3, useState as useState3 } from "react";
672
796
 
673
797
  // src/components/use-speech-recognition.ts
674
- import { useCallback as useCallback2, useEffect, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
798
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
675
799
  function getRecognitionCtor() {
676
800
  if (typeof window === "undefined") return null;
677
801
  const scope = window;
@@ -687,7 +811,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
687
811
  const interimRef = useRef2(onInterimTranscript);
688
812
  finalRef.current = onFinalTranscript;
689
813
  interimRef.current = onInterimTranscript;
690
- useEffect(() => {
814
+ useEffect2(() => {
691
815
  if (!ctor) return;
692
816
  const recognition = new ctor();
693
817
  recognition.lang = lang;
@@ -767,7 +891,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
767
891
  }, "onFinalTranscript"),
768
892
  onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
769
893
  });
770
- useEffect2(() => {
894
+ useEffect3(() => {
771
895
  if (!speech.isListening) setInterim("");
772
896
  }, [
773
897
  speech.isListening
@@ -826,7 +950,7 @@ __name(ChatInput, "ChatInput");
826
950
 
827
951
  // src/components/message-item.tsx
828
952
  import { isTextUIPart, isToolUIPart } from "ai";
829
- import React2, { useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
953
+ import React2, { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
830
954
  function MessageItem({ message, renderToolPart, renderToolGroup, renderText, classNames, isStreaming = false, editable, onEditSubmit, regeneratable, onRegenerate, onFork, usage, createdAt }) {
831
955
  const isUser = message.role === "user";
832
956
  const parts = message.parts ?? [];
@@ -930,7 +1054,7 @@ function EditableUserBubble({ message, onSubmit, renderText, classNames, actions
930
1054
  const [isEditing, setIsEditing] = useState4(false);
931
1055
  const [draft, setDraft] = useState4(initialText);
932
1056
  const textareaRef = useRef3(null);
933
- useEffect3(() => {
1057
+ useEffect4(() => {
934
1058
  if (isEditing && textareaRef.current) {
935
1059
  const el = textareaRef.current;
936
1060
  el.focus();
@@ -1248,6 +1372,7 @@ export {
1248
1372
  formatRelativeTime,
1249
1373
  loadStoredSpeechLang,
1250
1374
  persistSpeechLang,
1375
+ storedMessageToUiMessage,
1251
1376
  useAgentChat,
1252
1377
  useSpeechRecognition
1253
1378
  };