@dudousxd/nestjs-agent-react 0.14.0 → 0.16.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/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var src_exports = {};
33
33
  __export(src_exports, {
34
+ AgentBackendUnsupportedError: () => AgentBackendUnsupportedError,
34
35
  AgentChatTransport: () => AgentChatTransport,
35
36
  AgentClient: () => AgentClient,
36
37
  AgentHttpError: () => AgentHttpError,
@@ -60,22 +61,29 @@ __export(src_exports, {
60
61
  inferResultView: () => inferResultView,
61
62
  isActionCall: () => isActionCall,
62
63
  loadStoredSpeechLang: () => loadStoredSpeechLang,
64
+ notifyThreads: () => notifyThreads,
65
+ onThreadsEvent: () => onThreadsEvent,
63
66
  persistSpeechLang: () => persistSpeechLang,
64
67
  phraseFor: () => phraseFor,
65
68
  readPath: () => readPath,
66
69
  readReasoningMs: () => readReasoningMs,
70
+ requireBackendMethod: () => requireBackendMethod,
67
71
  resolveResultView: () => resolveResultView,
68
72
  storedMessageToUiMessage: () => storedMessageToUiMessage,
69
73
  storedThreadToUiMessages: () => storedThreadToUiMessages,
70
74
  toolCallState: () => toolCallState,
71
75
  toolCatalogFrom: () => toolCatalogFrom,
72
76
  useAgentChat: () => useAgentChat,
77
+ useAgents: () => useAgents,
73
78
  useApprovalCountdown: () => useApprovalCountdown,
74
79
  useChatTranscript: () => useChatTranscript,
75
80
  useComposerAutocomplete: () => useComposerAutocomplete,
76
81
  useElapsed: () => useElapsed,
82
+ useMessageFeedback: () => useMessageFeedback,
83
+ useModels: () => useModels,
77
84
  useSpeechRecognition: () => useSpeechRecognition,
78
85
  useStickToBottom: () => useStickToBottom,
86
+ useThreads: () => useThreads,
79
87
  useToolCatalog: () => useToolCatalog,
80
88
  useTranscriptItem: () => useTranscriptItem,
81
89
  validateAnswer: () => import_nestjs_agent_core.validateElicitationAnswer,
@@ -83,6 +91,255 @@ __export(src_exports, {
83
91
  });
84
92
  module.exports = __toCommonJS(src_exports);
85
93
 
94
+ // src/client.ts
95
+ var AgentHttpError = class extends Error {
96
+ static {
97
+ __name(this, "AgentHttpError");
98
+ }
99
+ status;
100
+ method;
101
+ path;
102
+ constructor(status, method, path, statusText) {
103
+ super(`Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
104
+ this.name = "AgentHttpError";
105
+ }
106
+ };
107
+ var HEADER_RUN_ID = "x-agent-run-id";
108
+ var HEADER_THREAD_ID = "x-agent-thread-id";
109
+ var AgentClient = class {
110
+ static {
111
+ __name(this, "AgentClient");
112
+ }
113
+ options;
114
+ constructor(options = {}) {
115
+ this.options = options;
116
+ }
117
+ /** `POST /agent/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
118
+ async openChatStream(request) {
119
+ const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat`, {
120
+ method: "POST",
121
+ headers: {
122
+ "content-type": "application/json",
123
+ accept: "text/event-stream",
124
+ ...await this.resolveHeaders(),
125
+ ...request.headers
126
+ },
127
+ body: JSON.stringify(request.body),
128
+ ...this.credentials(),
129
+ ...request.signal !== void 0 ? {
130
+ signal: request.signal
131
+ } : {}
132
+ });
133
+ if (!response.ok || !response.body) {
134
+ throw new AgentHttpError(response.status, "POST", "/agent/chat", response.statusText);
135
+ }
136
+ return streamResponse(response);
137
+ }
138
+ /**
139
+ * `GET /agent/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
140
+ * streaming under that id (404).
141
+ */
142
+ async resumeChatStream(request) {
143
+ const path = `/agent/chat/${encodeURIComponent(request.runId)}/stream`;
144
+ const query = request.after !== void 0 && request.after > 0 ? `?after=${request.after}` : "";
145
+ const response = await this.fetchImpl()(`${this.baseUrl()}${path}${query}`, {
146
+ method: "GET",
147
+ headers: {
148
+ accept: "text/event-stream",
149
+ ...await this.resolveHeaders(),
150
+ ...request.headers
151
+ },
152
+ ...this.credentials(),
153
+ ...request.signal !== void 0 ? {
154
+ signal: request.signal
155
+ } : {}
156
+ });
157
+ if (response.status === 404) {
158
+ return null;
159
+ }
160
+ if (!response.ok || !response.body) {
161
+ throw new AgentHttpError(response.status, "GET", path, response.statusText);
162
+ }
163
+ return streamResponse(response);
164
+ }
165
+ /**
166
+ * Rate a message (`'up'`/`'down'`, optional comment) or clear its rating (`value: null`). Answers
167
+ * the stored rating.
168
+ */
169
+ setMessageFeedback(messageId, input) {
170
+ return this.request("POST", `/agent/messages/${encodeURIComponent(messageId)}/feedback`, input);
171
+ }
172
+ listThreads() {
173
+ return this.request("GET", "/agent/threads");
174
+ }
175
+ /**
176
+ * The skills this caller can invoke right now, scope-resolved — the same list, built by the same
177
+ * call, that the model is offered, so what a user can type after a `/` and what the agent can
178
+ * reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to
179
+ * one conversation; omitted, the server reads it as a brand-new thread.
180
+ */
181
+ listSkills(threadId) {
182
+ const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
183
+ return this.request("GET", `/agent/skills${query}`);
184
+ }
185
+ /**
186
+ * The tools this caller can reach through `agent` (the default agent when omitted), each with the
187
+ * server-declared `presentation` a chat narrates it by — the same list the model is offered.
188
+ * Prefer {@link useToolCatalog}, which fetches it once and shares it.
189
+ */
190
+ listTools(agent) {
191
+ const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
192
+ return this.request("GET", `/agent/tools${query}`);
193
+ }
194
+ getThread(id) {
195
+ return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
196
+ }
197
+ deleteThread(id) {
198
+ return this.request("DELETE", `/agent/threads/${encodeURIComponent(id)}`);
199
+ }
200
+ forkFromMessage(threadId, messageId) {
201
+ return this.request("POST", `/agent/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
202
+ }
203
+ renameThread(id, title) {
204
+ return this.updateThread(id, {
205
+ title
206
+ });
207
+ }
208
+ /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
209
+ updateThread(id, patch) {
210
+ return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, patch);
211
+ }
212
+ /**
213
+ * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
214
+ * mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
215
+ * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
216
+ */
217
+ async uploadAttachment(file, options = {}) {
218
+ const formData = new FormData();
219
+ formData.append("file", file);
220
+ const response = await this.fetchImpl()(`${this.baseUrl()}/agent/attachments`, {
221
+ method: "POST",
222
+ headers: {
223
+ accept: "application/json",
224
+ ...await this.resolveHeaders()
225
+ },
226
+ body: formData,
227
+ ...this.credentials(),
228
+ ...options.signal !== void 0 ? {
229
+ signal: options.signal
230
+ } : {}
231
+ });
232
+ return this.handleResponse(response, "POST", "/agent/attachments");
233
+ }
234
+ promoteThread(id) {
235
+ return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
236
+ }
237
+ truncateFromMessage(threadId, messageId) {
238
+ return this.request("DELETE", `/agent/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`);
239
+ }
240
+ /** `GET /agent/models?agent=` — the models this caller may pick, grouped by provider. */
241
+ listModels(agent) {
242
+ const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
243
+ return this.request("GET", `/agent/models${query}`);
244
+ }
245
+ /** `GET /agent/agents` — the registered agents, the default one flagged. */
246
+ listAgents() {
247
+ return this.request("GET", "/agent/agents");
248
+ }
249
+ getQuotaToday() {
250
+ return this.request("GET", "/agent/quota/today");
251
+ }
252
+ cancelStream(runId) {
253
+ return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
254
+ }
255
+ /**
256
+ * `remember` approves later calls of the same tool in the same thread; `via` names the surface
257
+ * the decision came through (the server records `'web'` when omitted).
258
+ */
259
+ approveToolCall(input) {
260
+ return this.request("POST", "/agent/tool-call/approve", input);
261
+ }
262
+ rejectToolCall(input) {
263
+ return this.request("POST", "/agent/tool-call/reject", input);
264
+ }
265
+ /**
266
+ * Settle a parked question set. `answers` is questionId → chosen option values; a question left
267
+ * out takes the pre-picked default the request carried, resolved server-side against the request
268
+ * the run already holds. Omit the whole object and the user has confirmed every pre-picked
269
+ * answer — which is the point of the surface, so it is a valid submission rather than a blank.
270
+ */
271
+ answerToolCall(input) {
272
+ return this.request("POST", "/agent/tool-call/answer", input);
273
+ }
274
+ /**
275
+ * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
276
+ * values a confirmation would, and persists differently on purpose — only one of them is
277
+ * evidence the user chose them.
278
+ */
279
+ skipToolCall(input) {
280
+ return this.request("POST", "/agent/tool-call/skip", input);
281
+ }
282
+ fetchImpl() {
283
+ return this.options.fetch ?? globalThis.fetch;
284
+ }
285
+ baseUrl() {
286
+ return (this.options.baseUrl ?? "").replace(/\/$/, "");
287
+ }
288
+ async resolveHeaders() {
289
+ const dynamic = await this.options.getHeaders?.() ?? {};
290
+ return {
291
+ ...this.options.headers,
292
+ ...dynamic
293
+ };
294
+ }
295
+ credentials() {
296
+ return this.options.credentials !== void 0 ? {
297
+ credentials: this.options.credentials
298
+ } : {};
299
+ }
300
+ async request(method, path, body) {
301
+ const response = await this.fetchImpl()(`${this.baseUrl()}${path}`, {
302
+ method,
303
+ headers: {
304
+ accept: "application/json",
305
+ ...body !== void 0 ? {
306
+ "content-type": "application/json"
307
+ } : {},
308
+ ...await this.resolveHeaders()
309
+ },
310
+ ...body !== void 0 ? {
311
+ body: JSON.stringify(body)
312
+ } : {},
313
+ ...this.credentials()
314
+ });
315
+ return this.handleResponse(response, method, path);
316
+ }
317
+ async handleResponse(response, method, path) {
318
+ if (!response.ok) {
319
+ throw new AgentHttpError(response.status, method, path, response.statusText);
320
+ }
321
+ if (response.status === 204) return void 0;
322
+ const text = await response.text();
323
+ if (!text) return void 0;
324
+ return JSON.parse(text);
325
+ }
326
+ };
327
+ function streamResponse(response) {
328
+ const body = response.body;
329
+ const runId = response.headers?.get(HEADER_RUN_ID) ?? void 0;
330
+ const threadId = response.headers?.get(HEADER_THREAD_ID) ?? void 0;
331
+ return {
332
+ body,
333
+ ...runId ? {
334
+ runId
335
+ } : {},
336
+ ...threadId ? {
337
+ threadId
338
+ } : {}
339
+ };
340
+ }
341
+ __name(streamResponse, "streamResponse");
342
+
86
343
  // src/reasoning/timing.ts
87
344
  var import_react = require("react");
88
345
  var REASONING_METADATA_KEY = "agent";
@@ -143,8 +400,11 @@ function formatElapsed(ms) {
143
400
  __name(formatElapsed, "formatElapsed");
144
401
 
145
402
  // src/agent-chat-transport.ts
146
- var HEADER_RUN_ID = "x-agent-run-id";
147
- var HEADER_THREAD_ID = "x-agent-thread-id";
403
+ var DEFAULT_RECONNECT = {
404
+ maxAttempts: 6,
405
+ baseDelayMs: 500,
406
+ maxDelayMs: 8e3
407
+ };
148
408
  var ASK_TOOL_NAME = "ask";
149
409
  var AgentChatTransport = class {
150
410
  static {
@@ -161,8 +421,26 @@ var AgentChatTransport = class {
161
421
  * several entries under each id — which React renders as duplicate keys.
162
422
  */
163
423
  attemptLive = false;
424
+ backend;
164
425
  constructor(options = {}) {
165
426
  this.options = options;
427
+ this.backend = options.backend ?? new AgentClient({
428
+ ...options.baseUrl !== void 0 ? {
429
+ baseUrl: options.baseUrl
430
+ } : {},
431
+ ...options.headers !== void 0 ? {
432
+ headers: options.headers
433
+ } : {},
434
+ ...options.getHeaders !== void 0 ? {
435
+ getHeaders: options.getHeaders
436
+ } : {},
437
+ ...options.credentials !== void 0 ? {
438
+ credentials: options.credentials
439
+ } : {},
440
+ ...options.fetch !== void 0 ? {
441
+ fetch: options.fetch
442
+ } : {}
443
+ });
166
444
  }
167
445
  /** Run id of the most recent stream — HITL approve/reject target this. */
168
446
  get runId() {
@@ -195,26 +473,25 @@ var AgentChatTransport = class {
195
473
  ...options.body ?? {},
196
474
  message
197
475
  };
198
- const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat`, {
199
- method: "POST",
200
- headers: {
201
- "content-type": "application/json",
202
- accept: "text/event-stream",
203
- ...await this.resolveHeaders(options.headers)
204
- },
205
- body: JSON.stringify(body),
206
- ...this.options.credentials !== void 0 ? {
207
- credentials: this.options.credentials
476
+ const headers = headerRecord(options.headers);
477
+ const response = await this.backend.openChatStream({
478
+ body,
479
+ ...headers !== void 0 ? {
480
+ headers
481
+ } : {},
482
+ ...options.abortSignal ? {
483
+ signal: options.abortSignal
484
+ } : {}
485
+ });
486
+ this.captureHeaderMeta(response);
487
+ return this.toChunkStream(response, {
488
+ ...headers !== void 0 ? {
489
+ headers
208
490
  } : {},
209
491
  ...options.abortSignal ? {
210
492
  signal: options.abortSignal
211
493
  } : {}
212
494
  });
213
- if (!response.ok || !response.body) {
214
- throw new Error(`Agent chat request failed: ${response.status} ${response.statusText}`);
215
- }
216
- this.captureHeaderMeta(response.headers);
217
- return this.toChunkStream(response.body);
218
495
  } catch (error) {
219
496
  this.attemptLive = false;
220
497
  throw error;
@@ -227,51 +504,41 @@ var AgentChatTransport = class {
227
504
  if (runId === void 0) return null;
228
505
  this.attemptLive = true;
229
506
  try {
230
- const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat/${encodeURIComponent(runId)}/stream`, {
231
- method: "GET",
232
- headers: {
233
- accept: "text/event-stream",
234
- ...await this.resolveHeaders(options.headers)
235
- },
236
- ...this.options.credentials !== void 0 ? {
237
- credentials: this.options.credentials
507
+ const headers = headerRecord(options.headers);
508
+ const response = await this.backend.resumeChatStream({
509
+ runId,
510
+ ...headers !== void 0 ? {
511
+ headers
238
512
  } : {}
239
513
  });
240
- if (response.status === 404) {
514
+ if (response === null) {
241
515
  this.attemptLive = false;
242
516
  return null;
243
517
  }
244
- if (!response.ok || !response.body) {
245
- throw new Error(`Agent stream reconnect failed: ${response.status} ${response.statusText}`);
246
- }
247
- this.captureHeaderMeta(response.headers);
248
- return this.toChunkStream(response.body);
518
+ this.captureHeaderMeta(response);
519
+ return this.toChunkStream(response, {
520
+ runId,
521
+ ...headers !== void 0 ? {
522
+ headers
523
+ } : {}
524
+ });
249
525
  } catch (error) {
250
526
  this.attemptLive = false;
251
527
  throw error;
252
528
  }
253
529
  }
254
- fetchImpl() {
255
- return this.options.fetch ?? globalThis.fetch;
256
- }
257
- baseUrl() {
258
- return (this.options.baseUrl ?? "").replace(/\/$/, "");
530
+ captureHeaderMeta(response) {
531
+ if (response.runId) this.currentRunId = response.runId;
532
+ if (response.threadId) this.currentThreadId = response.threadId;
259
533
  }
260
- async resolveHeaders(perRequest) {
261
- const dynamic = await this.options.getHeaders?.() ?? {};
262
- const extra = perRequest instanceof Headers ? Object.fromEntries(perRequest.entries()) : perRequest ?? {};
534
+ reconnectPolicy() {
535
+ const configured = this.options.reconnect;
536
+ if (configured === false) return null;
263
537
  return {
264
- ...this.options.headers,
265
- ...dynamic,
266
- ...extra
538
+ ...DEFAULT_RECONNECT,
539
+ ...configured
267
540
  };
268
541
  }
269
- captureHeaderMeta(headers) {
270
- const runId = headers.get(HEADER_RUN_ID);
271
- const threadId = headers.get(HEADER_THREAD_ID);
272
- if (runId) this.currentRunId = runId;
273
- if (threadId) this.currentThreadId = threadId;
274
- }
275
542
  recordMeta(meta) {
276
543
  this.currentRunId = meta.runId;
277
544
  this.currentThreadId = meta.threadId;
@@ -290,10 +557,66 @@ var AgentChatTransport = class {
290
557
  * call plus its tool execution; text and reasoning open lazily and close at the step boundary, so
291
558
  * tool-call cards (input streaming → output) render live between the prose.
292
559
  */
293
- toChunkStream(source) {
294
- const reader = source.getReader();
295
- const decoder = new TextDecoder();
560
+ toChunkStream(source, context) {
561
+ let reader = source.body.getReader();
562
+ let decoder = new TextDecoder();
296
563
  let buffer = "";
564
+ let streamRunId = source.runId ?? context.runId;
565
+ let lastSeq;
566
+ let cancelled = false;
567
+ const backend = this.backend;
568
+ const policy = this.reconnectPolicy();
569
+ const notify = /* @__PURE__ */ __name((state) => this.options.onConnectionChange?.(state), "notify");
570
+ const aborted = /* @__PURE__ */ __name(() => cancelled || context.signal?.aborted === true, "aborted");
571
+ const canResume = /* @__PURE__ */ __name(() => policy !== null && lastSeq !== void 0 && streamRunId !== void 0 && !aborted(), "canResume");
572
+ const resume = /* @__PURE__ */ __name(async () => {
573
+ const runId = streamRunId;
574
+ const after = lastSeq ?? 0;
575
+ const { maxAttempts, baseDelayMs, maxDelayMs } = policy;
576
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
577
+ notify({
578
+ status: "reconnecting",
579
+ runId,
580
+ attempt,
581
+ after
582
+ });
583
+ await delay(Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1)), context.signal);
584
+ if (aborted()) return "aborted";
585
+ try {
586
+ const next = await backend.resumeChatStream({
587
+ runId,
588
+ after,
589
+ ...context.headers !== void 0 ? {
590
+ headers: context.headers
591
+ } : {},
592
+ ...context.signal !== void 0 ? {
593
+ signal: context.signal
594
+ } : {}
595
+ });
596
+ if (next === null) {
597
+ notify({
598
+ status: "gone",
599
+ runId
600
+ });
601
+ return "gone";
602
+ }
603
+ reader = next.body.getReader();
604
+ decoder = new TextDecoder();
605
+ buffer = "";
606
+ notify({
607
+ status: "live"
608
+ });
609
+ return "resumed";
610
+ } catch {
611
+ if (aborted()) return "aborted";
612
+ }
613
+ }
614
+ notify({
615
+ status: "failed",
616
+ runId
617
+ });
618
+ return "failed";
619
+ }, "resume");
297
620
  let started = false;
298
621
  let stepOpen = false;
299
622
  let stepIndex = 0;
@@ -304,7 +627,10 @@ var AgentChatTransport = class {
304
627
  let reasoningSeq = 0;
305
628
  const announced = /* @__PURE__ */ new Set();
306
629
  const toolMetadata = /* @__PURE__ */ new Map();
307
- const record = /* @__PURE__ */ __name((meta) => this.recordMeta(meta), "record");
630
+ const record = /* @__PURE__ */ __name((meta) => {
631
+ streamRunId = meta.runId;
632
+ this.recordMeta(meta);
633
+ }, "record");
308
634
  const endAttempt = /* @__PURE__ */ __name(() => {
309
635
  this.attemptLive = false;
310
636
  }, "endAttempt");
@@ -313,7 +639,12 @@ var AgentChatTransport = class {
313
639
  function ensureStarted() {
314
640
  if (started) return;
315
641
  started = true;
316
- controller.enqueue({
642
+ controller.enqueue(streamRunId !== void 0 ? {
643
+ type: "start",
644
+ messageMetadata: {
645
+ runId: streamRunId
646
+ }
647
+ } : {
317
648
  type: "start"
318
649
  });
319
650
  }
@@ -569,33 +900,67 @@ var AgentChatTransport = class {
569
900
  endAttempt();
570
901
  }
571
902
  __name(finish, "finish");
903
+ function fail(errorText, start = true) {
904
+ if (start) ensureStarted();
905
+ controller.enqueue({
906
+ type: "error",
907
+ errorText
908
+ });
909
+ controller.close();
910
+ endAttempt();
911
+ }
912
+ __name(fail, "fail");
913
+ async function recover(error) {
914
+ if (!canResume()) {
915
+ if (error === void 0) {
916
+ finish();
917
+ } else {
918
+ fail(error instanceof Error ? error.message : "Agent stream error", false);
919
+ }
920
+ return false;
921
+ }
922
+ const outcome = await resume();
923
+ if (outcome === "resumed") return true;
924
+ if (outcome === "failed") {
925
+ fail("Lost the connection to the agent stream");
926
+ } else {
927
+ finish();
928
+ }
929
+ return false;
930
+ }
931
+ __name(recover, "recover");
572
932
  try {
573
933
  while (true) {
574
- const { done, value } = await reader.read();
575
- if (done) {
576
- finish();
934
+ let result;
935
+ try {
936
+ result = await reader.read();
937
+ } catch (error) {
938
+ if (await recover(error)) continue;
939
+ return;
940
+ }
941
+ if (result.done) {
942
+ if (await recover(void 0)) continue;
577
943
  return;
578
944
  }
579
- buffer += decoder.decode(value, {
945
+ buffer += decoder.decode(result.value, {
580
946
  stream: true
581
947
  });
582
948
  let separator = buffer.indexOf("\n\n");
583
949
  while (separator !== -1) {
584
950
  const rawEvent = buffer.slice(0, separator);
585
951
  buffer = buffer.slice(separator + 2);
952
+ separator = buffer.indexOf("\n\n");
586
953
  const frame = parseSseFrame(rawEvent);
954
+ if (frame.id !== void 0) {
955
+ if (lastSeq !== void 0 && frame.id <= lastSeq) continue;
956
+ lastSeq = frame.id;
957
+ }
587
958
  if (frame.event === "done") {
588
959
  finish();
589
960
  return;
590
961
  }
591
962
  if (frame.event === "error") {
592
- ensureStarted();
593
- controller.enqueue({
594
- type: "error",
595
- errorText: parseErrorText(frame.data)
596
- });
597
- controller.close();
598
- endAttempt();
963
+ fail(parseErrorText(frame.data));
599
964
  return;
600
965
  }
601
966
  if (frame.event === "meta") {
@@ -605,19 +970,14 @@ var AgentChatTransport = class {
605
970
  const event = parseEvent(frame.data);
606
971
  if (event) emit(event);
607
972
  }
608
- separator = buffer.indexOf("\n\n");
609
973
  }
610
974
  }
611
975
  } catch (error) {
612
- controller.enqueue({
613
- type: "error",
614
- errorText: error instanceof Error ? error.message : "Agent stream error"
615
- });
616
- controller.close();
617
- endAttempt();
976
+ fail(error instanceof Error ? error.message : "Agent stream error", false);
618
977
  }
619
978
  },
620
979
  cancel() {
980
+ cancelled = true;
621
981
  void reader.cancel();
622
982
  endAttempt();
623
983
  }
@@ -626,17 +986,22 @@ var AgentChatTransport = class {
626
986
  };
627
987
  function parseSseFrame(raw) {
628
988
  let event;
989
+ let id;
629
990
  const dataLines = [];
630
991
  for (const line of raw.split("\n")) {
631
992
  if (line.startsWith("event:")) {
632
993
  event = line.slice("event:".length).trim();
994
+ } else if (line.startsWith("id:")) {
995
+ const parsed = Number(line.slice("id:".length).trim());
996
+ if (Number.isSafeInteger(parsed) && parsed > 0) id = parsed;
633
997
  } else if (line.startsWith("data:")) {
634
998
  dataLines.push(line.slice("data:".length).trimStart());
635
999
  }
636
1000
  }
637
1001
  return {
638
1002
  event,
639
- data: dataLines.length > 0 ? dataLines.join("\n") : void 0
1003
+ data: dataLines.length > 0 ? dataLines.join("\n") : void 0,
1004
+ id
640
1005
  };
641
1006
  }
642
1007
  __name(parseSseFrame, "parseSseFrame");
@@ -677,15 +1042,382 @@ function parseEvent(data) {
677
1042
  }
678
1043
  return null;
679
1044
  }
680
- __name(parseEvent, "parseEvent");
681
- function extractText(message) {
682
- let text = "";
683
- for (const part of message.parts ?? []) {
684
- if (part.type === "text") text += part.text;
1045
+ __name(parseEvent, "parseEvent");
1046
+ function extractText(message) {
1047
+ let text = "";
1048
+ for (const part of message.parts ?? []) {
1049
+ if (part.type === "text") text += part.text;
1050
+ }
1051
+ return text;
1052
+ }
1053
+ __name(extractText, "extractText");
1054
+ function headerRecord(headers) {
1055
+ if (headers === void 0) return void 0;
1056
+ return headers instanceof Headers ? Object.fromEntries(headers.entries()) : headers;
1057
+ }
1058
+ __name(headerRecord, "headerRecord");
1059
+ function delay(ms, signal) {
1060
+ if (ms <= 0 || signal?.aborted) return Promise.resolve();
1061
+ return new Promise((resolve) => {
1062
+ const timer = setTimeout(done, ms);
1063
+ function done() {
1064
+ clearTimeout(timer);
1065
+ signal?.removeEventListener("abort", done);
1066
+ resolve();
1067
+ }
1068
+ __name(done, "done");
1069
+ signal?.addEventListener("abort", done, {
1070
+ once: true
1071
+ });
1072
+ });
1073
+ }
1074
+ __name(delay, "delay");
1075
+
1076
+ // src/backend.ts
1077
+ var AgentBackendUnsupportedError = class extends Error {
1078
+ static {
1079
+ __name(this, "AgentBackendUnsupportedError");
1080
+ }
1081
+ method;
1082
+ constructor(method) {
1083
+ super(`The agent backend does not implement ${method}()`), this.method = method;
1084
+ this.name = "AgentBackendUnsupportedError";
1085
+ }
1086
+ };
1087
+ function requireBackendMethod(backend, name) {
1088
+ const method = backend[name];
1089
+ if (typeof method !== "function") {
1090
+ throw new AgentBackendUnsupportedError(name);
1091
+ }
1092
+ return method.bind(backend);
1093
+ }
1094
+ __name(requireBackendMethod, "requireBackendMethod");
1095
+
1096
+ // src/catalog/use-agents.ts
1097
+ var import_react3 = require("react");
1098
+
1099
+ // src/catalog/use-resource.ts
1100
+ var import_react2 = require("react");
1101
+ function useResource(load2, key, enabled) {
1102
+ const [data, setData] = (0, import_react2.useState)(void 0);
1103
+ const [isLoading, setIsLoading] = (0, import_react2.useState)(enabled);
1104
+ const [error, setError] = (0, import_react2.useState)(null);
1105
+ const loadRef = (0, import_react2.useRef)(load2);
1106
+ loadRef.current = load2;
1107
+ const generation = (0, import_react2.useRef)(0);
1108
+ const refresh = (0, import_react2.useCallback)(async () => {
1109
+ const mine = ++generation.current;
1110
+ setIsLoading(true);
1111
+ try {
1112
+ const value = await loadRef.current();
1113
+ if (mine !== generation.current) return;
1114
+ setData(value);
1115
+ setError(null);
1116
+ } catch (caught) {
1117
+ if (mine !== generation.current) return;
1118
+ setError(caught instanceof Error ? caught : new Error(String(caught)));
1119
+ } finally {
1120
+ if (mine === generation.current) setIsLoading(false);
1121
+ }
1122
+ }, []);
1123
+ (0, import_react2.useEffect)(() => {
1124
+ if (!enabled) return;
1125
+ void refresh();
1126
+ }, [
1127
+ enabled,
1128
+ key,
1129
+ refresh
1130
+ ]);
1131
+ return {
1132
+ data,
1133
+ isLoading,
1134
+ error,
1135
+ refresh
1136
+ };
1137
+ }
1138
+ __name(useResource, "useResource");
1139
+
1140
+ // src/catalog/use-agents.ts
1141
+ function useAgents(options) {
1142
+ const { backend, enabled = true } = options;
1143
+ const { data, isLoading, error, refresh } = useResource(() => requireBackendMethod(backend, "listAgents")(), "", enabled);
1144
+ return (0, import_react3.useMemo)(() => {
1145
+ const agents = data ?? [];
1146
+ return {
1147
+ agents,
1148
+ defaultAgent: agents.find((agent) => agent.isDefault === true)?.name ?? null,
1149
+ isLoading,
1150
+ error,
1151
+ refresh
1152
+ };
1153
+ }, [
1154
+ data,
1155
+ isLoading,
1156
+ error,
1157
+ refresh
1158
+ ]);
1159
+ }
1160
+ __name(useAgents, "useAgents");
1161
+
1162
+ // src/catalog/use-models.ts
1163
+ var import_react4 = require("react");
1164
+ function useModels(options) {
1165
+ const { backend, agent, enabled = true } = options;
1166
+ const { data, isLoading, error, refresh } = useResource(() => requireBackendMethod(backend, "listModels")(agent), `${agent ?? ""}`, enabled);
1167
+ return (0, import_react4.useMemo)(() => {
1168
+ const providers = data?.providers ?? [];
1169
+ const models = providers.flatMap((provider) => provider.models.map((model) => ({
1170
+ ...model,
1171
+ providerId: provider.id,
1172
+ providerLabel: provider.label
1173
+ })));
1174
+ const byId = new Map(models.map((model) => [
1175
+ model.id,
1176
+ model
1177
+ ]));
1178
+ return {
1179
+ providers,
1180
+ models,
1181
+ defaultModel: data?.default ?? null,
1182
+ find: /* @__PURE__ */ __name((id) => id == null ? void 0 : byId.get(id), "find"),
1183
+ isLoading,
1184
+ error,
1185
+ refresh
1186
+ };
1187
+ }, [
1188
+ data,
1189
+ isLoading,
1190
+ error,
1191
+ refresh
1192
+ ]);
1193
+ }
1194
+ __name(useModels, "useModels");
1195
+
1196
+ // src/feedback/use-message-feedback.ts
1197
+ var import_react5 = require("react");
1198
+ function useMessageFeedback(options) {
1199
+ const latest = (0, import_react5.useRef)(options);
1200
+ latest.current = options;
1201
+ const [local, setLocal] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
1202
+ const [pending, setPending] = (0, import_react5.useState)(/* @__PURE__ */ new Set());
1203
+ const [error, setError] = (0, import_react5.useState)(null);
1204
+ const localRef = (0, import_react5.useRef)(local);
1205
+ localRef.current = local;
1206
+ const resolved = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
1207
+ const feedbackOf = (0, import_react5.useCallback)((message) => {
1208
+ const mine = local.get(message.id);
1209
+ if (mine !== void 0) return mine.feedback;
1210
+ return metadataOf(message).feedback ?? null;
1211
+ }, [
1212
+ local
1213
+ ]);
1214
+ const isPending = (0, import_react5.useCallback)((message) => pending.has(message.id), [
1215
+ pending
1216
+ ]);
1217
+ const storedIdFor = (0, import_react5.useCallback)(async (message) => {
1218
+ const runId = metadataOf(message).runId;
1219
+ if (runId === void 0) return message.id;
1220
+ const known = resolved.current.get(message.id);
1221
+ if (known !== void 0) return known;
1222
+ const { backend, threadId } = latest.current;
1223
+ const thread = typeof threadId === "function" ? threadId() : threadId;
1224
+ if (thread === void 0) {
1225
+ throw new Error("useMessageFeedback: a streamed message needs `threadId` to be rated");
1226
+ }
1227
+ const detail = await backend.getThread(thread);
1228
+ const row = [
1229
+ ...detail.messages
1230
+ ].reverse().find((candidate) => candidate.role === "assistant" && candidate.runId === runId);
1231
+ if (row === void 0) {
1232
+ throw new Error(`useMessageFeedback: run ${runId} has no persisted answer yet`);
1233
+ }
1234
+ resolved.current.set(message.id, row.id);
1235
+ return row.id;
1236
+ }, []);
1237
+ const rate = (0, import_react5.useCallback)(async (message, value, comment) => {
1238
+ const setFeedback = requireBackendMethod(latest.current.backend, "setMessageFeedback");
1239
+ const had = localRef.current.get(message.id);
1240
+ const optimistic = value === null ? null : {
1241
+ value,
1242
+ ...comment !== void 0 && comment.trim().length > 0 ? {
1243
+ comment: comment.trim()
1244
+ } : {},
1245
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1246
+ };
1247
+ setLocal((current) => new Map(current).set(message.id, {
1248
+ feedback: optimistic
1249
+ }));
1250
+ setPending((current) => new Set(current).add(message.id));
1251
+ try {
1252
+ const storedId = await storedIdFor(message);
1253
+ const { feedback } = await setFeedback(storedId, {
1254
+ value,
1255
+ ...comment !== void 0 ? {
1256
+ comment
1257
+ } : {}
1258
+ });
1259
+ setLocal((current) => new Map(current).set(message.id, {
1260
+ feedback
1261
+ }));
1262
+ setError(null);
1263
+ } catch (caught) {
1264
+ setLocal((current) => {
1265
+ const next = new Map(current);
1266
+ if (had === void 0) next.delete(message.id);
1267
+ else next.set(message.id, had);
1268
+ return next;
1269
+ });
1270
+ const failure = caught instanceof Error ? caught : new Error(String(caught));
1271
+ setError(failure);
1272
+ throw failure;
1273
+ } finally {
1274
+ setPending((current) => {
1275
+ const next = new Set(current);
1276
+ next.delete(message.id);
1277
+ return next;
1278
+ });
1279
+ }
1280
+ }, [
1281
+ storedIdFor
1282
+ ]);
1283
+ const toggle = (0, import_react5.useCallback)((message, value) => {
1284
+ const mine = localRef.current.get(message.id);
1285
+ const current = mine !== void 0 ? mine.feedback : metadataOf(message).feedback ?? null;
1286
+ return rate(message, current?.value === value ? null : value);
1287
+ }, [
1288
+ rate
1289
+ ]);
1290
+ return {
1291
+ feedbackOf,
1292
+ isPending,
1293
+ rate,
1294
+ toggle,
1295
+ error
1296
+ };
1297
+ }
1298
+ __name(useMessageFeedback, "useMessageFeedback");
1299
+ function metadataOf(message) {
1300
+ const metadata = message.metadata;
1301
+ return metadata !== null && typeof metadata === "object" ? metadata : {};
1302
+ }
1303
+ __name(metadataOf, "metadataOf");
1304
+
1305
+ // src/threads/threads-events.ts
1306
+ var listeners = /* @__PURE__ */ new WeakMap();
1307
+ function notifyThreads(backend, event) {
1308
+ for (const listener of listeners.get(backend) ?? []) {
1309
+ listener(event);
685
1310
  }
686
- return text;
687
1311
  }
688
- __name(extractText, "extractText");
1312
+ __name(notifyThreads, "notifyThreads");
1313
+ function onThreadsEvent(backend, listener) {
1314
+ let set = listeners.get(backend);
1315
+ if (set === void 0) {
1316
+ set = /* @__PURE__ */ new Set();
1317
+ listeners.set(backend, set);
1318
+ }
1319
+ set.add(listener);
1320
+ return () => {
1321
+ set.delete(listener);
1322
+ };
1323
+ }
1324
+ __name(onThreadsEvent, "onThreadsEvent");
1325
+
1326
+ // src/threads/use-threads.ts
1327
+ var import_react6 = require("react");
1328
+ function useThreads(options) {
1329
+ const { backend, enabled = true } = options;
1330
+ const [threads, setThreads] = (0, import_react6.useState)([]);
1331
+ const [isLoading, setIsLoading] = (0, import_react6.useState)(enabled);
1332
+ const [error, setError] = (0, import_react6.useState)(null);
1333
+ const threadsRef = (0, import_react6.useRef)(threads);
1334
+ threadsRef.current = threads;
1335
+ const generation = (0, import_react6.useRef)(0);
1336
+ const refresh = (0, import_react6.useCallback)(async () => {
1337
+ const mine = ++generation.current;
1338
+ setIsLoading(true);
1339
+ try {
1340
+ const list = await backend.listThreads();
1341
+ if (mine !== generation.current) return;
1342
+ setThreads(list);
1343
+ setError(null);
1344
+ } catch (caught) {
1345
+ if (mine !== generation.current) return;
1346
+ setError(caught instanceof Error ? caught : new Error(String(caught)));
1347
+ } finally {
1348
+ if (mine === generation.current) setIsLoading(false);
1349
+ }
1350
+ }, [
1351
+ backend
1352
+ ]);
1353
+ (0, import_react6.useEffect)(() => {
1354
+ if (!enabled) return;
1355
+ void refresh();
1356
+ return onThreadsEvent(backend, (event) => {
1357
+ if (event.type === "title") {
1358
+ setThreads((current) => current.map((thread) => thread.id === event.threadId ? {
1359
+ ...thread,
1360
+ title: event.title
1361
+ } : thread));
1362
+ } else if (event.type === "removed") {
1363
+ setThreads((current) => current.filter((thread) => thread.id !== event.threadId));
1364
+ } else {
1365
+ void refresh();
1366
+ }
1367
+ });
1368
+ }, [
1369
+ backend,
1370
+ enabled,
1371
+ refresh
1372
+ ]);
1373
+ const rename = (0, import_react6.useCallback)(async (id, title) => {
1374
+ const before = threadsRef.current;
1375
+ setThreads((current) => current.map((thread) => thread.id === id ? {
1376
+ ...thread,
1377
+ title
1378
+ } : thread));
1379
+ try {
1380
+ await backend.updateThread(id, {
1381
+ title
1382
+ });
1383
+ notifyThreads(backend, {
1384
+ type: "title",
1385
+ threadId: id,
1386
+ title
1387
+ });
1388
+ } catch (caught) {
1389
+ setThreads(before);
1390
+ throw caught;
1391
+ }
1392
+ }, [
1393
+ backend
1394
+ ]);
1395
+ const remove = (0, import_react6.useCallback)(async (id) => {
1396
+ const before = threadsRef.current;
1397
+ setThreads((current) => current.filter((thread) => thread.id !== id));
1398
+ try {
1399
+ await backend.deleteThread(id);
1400
+ notifyThreads(backend, {
1401
+ type: "removed",
1402
+ threadId: id
1403
+ });
1404
+ } catch (caught) {
1405
+ setThreads(before);
1406
+ throw caught;
1407
+ }
1408
+ }, [
1409
+ backend
1410
+ ]);
1411
+ return {
1412
+ threads,
1413
+ isLoading,
1414
+ error,
1415
+ refresh,
1416
+ rename,
1417
+ remove
1418
+ };
1419
+ }
1420
+ __name(useThreads, "useThreads");
689
1421
 
690
1422
  // src/background-runs.ts
691
1423
  function asReceipt(output) {
@@ -734,170 +1466,11 @@ function backgroundRunsFromThread(messages) {
734
1466
  }
735
1467
  __name(backgroundRunsFromThread, "backgroundRunsFromThread");
736
1468
 
737
- // src/client.ts
738
- var AgentHttpError = class extends Error {
739
- static {
740
- __name(this, "AgentHttpError");
741
- }
742
- status;
743
- method;
744
- path;
745
- constructor(status, method, path, statusText) {
746
- super(`Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
747
- this.name = "AgentHttpError";
748
- }
749
- };
750
- var AgentClient = class {
751
- static {
752
- __name(this, "AgentClient");
753
- }
754
- options;
755
- constructor(options = {}) {
756
- this.options = options;
757
- }
758
- listThreads() {
759
- return this.request("GET", "/agent/threads");
760
- }
761
- /**
762
- * The skills this caller can invoke right now, scope-resolved — the same list, built by the same
763
- * call, that the model is offered, so what a user can type after a `/` and what the agent can
764
- * reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to
765
- * one conversation; omitted, the server reads it as a brand-new thread.
766
- */
767
- listSkills(threadId) {
768
- const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
769
- return this.request("GET", `/agent/skills${query}`);
770
- }
771
- /**
772
- * The tools this caller can reach through `agent` (the default agent when omitted), each with the
773
- * server-declared `presentation` a chat narrates it by — the same list the model is offered.
774
- * Prefer {@link useToolCatalog}, which fetches it once and shares it.
775
- */
776
- listTools(agent) {
777
- const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
778
- return this.request("GET", `/agent/tools${query}`);
779
- }
780
- getThread(id) {
781
- return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
782
- }
783
- deleteThread(id) {
784
- return this.request("DELETE", `/agent/threads/${encodeURIComponent(id)}`);
785
- }
786
- forkFromMessage(threadId, messageId) {
787
- return this.request("POST", `/agent/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
788
- }
789
- renameThread(id, title) {
790
- return this.updateThread(id, {
791
- title
792
- });
793
- }
794
- /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
795
- updateThread(id, patch) {
796
- return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, patch);
797
- }
798
- /**
799
- * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
800
- * mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
801
- * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
802
- */
803
- async uploadAttachment(file) {
804
- const fetchImpl = this.options.fetch ?? globalThis.fetch;
805
- const baseUrl = (this.options.baseUrl ?? "").replace(/\/$/, "");
806
- const dynamic = await this.options.getHeaders?.() ?? {};
807
- const formData = new FormData();
808
- formData.append("file", file);
809
- const response = await fetchImpl(`${baseUrl}/agent/attachments`, {
810
- method: "POST",
811
- headers: {
812
- accept: "application/json",
813
- ...this.options.headers,
814
- ...dynamic
815
- },
816
- body: formData,
817
- ...this.options.credentials !== void 0 ? {
818
- credentials: this.options.credentials
819
- } : {}
820
- });
821
- return this.handleResponse(response, "POST", "/agent/attachments");
822
- }
823
- promoteThread(id) {
824
- return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
825
- }
826
- truncateFromMessage(threadId, messageId) {
827
- return this.request("DELETE", `/agent/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`);
828
- }
829
- getQuotaToday() {
830
- return this.request("GET", "/agent/quota/today");
831
- }
832
- cancelStream(runId) {
833
- return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
834
- }
835
- /**
836
- * `remember` approves later calls of the same tool in the same thread; `via` names the surface
837
- * the decision came through (the server records `'web'` when omitted).
838
- */
839
- approveToolCall(input) {
840
- return this.request("POST", "/agent/tool-call/approve", input);
841
- }
842
- rejectToolCall(input) {
843
- return this.request("POST", "/agent/tool-call/reject", input);
844
- }
845
- /**
846
- * Settle a parked question set. `answers` is questionId → chosen option values; a question left
847
- * out takes the pre-picked default the request carried, resolved server-side against the request
848
- * the run already holds. Omit the whole object and the user has confirmed every pre-picked
849
- * answer — which is the point of the surface, so it is a valid submission rather than a blank.
850
- */
851
- answerToolCall(input) {
852
- return this.request("POST", "/agent/tool-call/answer", input);
853
- }
854
- /**
855
- * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
856
- * values a confirmation would, and persists differently on purpose — only one of them is
857
- * evidence the user chose them.
858
- */
859
- skipToolCall(input) {
860
- return this.request("POST", "/agent/tool-call/skip", input);
861
- }
862
- async request(method, path, body) {
863
- const fetchImpl = this.options.fetch ?? globalThis.fetch;
864
- const baseUrl = (this.options.baseUrl ?? "").replace(/\/$/, "");
865
- const dynamic = await this.options.getHeaders?.() ?? {};
866
- const response = await fetchImpl(`${baseUrl}${path}`, {
867
- method,
868
- headers: {
869
- accept: "application/json",
870
- ...body !== void 0 ? {
871
- "content-type": "application/json"
872
- } : {},
873
- ...this.options.headers,
874
- ...dynamic
875
- },
876
- ...body !== void 0 ? {
877
- body: JSON.stringify(body)
878
- } : {},
879
- ...this.options.credentials !== void 0 ? {
880
- credentials: this.options.credentials
881
- } : {}
882
- });
883
- return this.handleResponse(response, method, path);
884
- }
885
- async handleResponse(response, method, path) {
886
- if (!response.ok) {
887
- throw new AgentHttpError(response.status, method, path, response.statusText);
888
- }
889
- if (response.status === 204) return void 0;
890
- const text = await response.text();
891
- if (!text) return void 0;
892
- return JSON.parse(text);
893
- }
894
- };
895
-
896
1469
  // src/components/chat-input.tsx
897
- var import_react3 = __toESM(require("react"), 1);
1470
+ var import_react8 = __toESM(require("react"), 1);
898
1471
 
899
1472
  // src/components/use-speech-recognition.ts
900
- var import_react2 = require("react");
1473
+ var import_react7 = require("react");
901
1474
  function getRecognitionCtor() {
902
1475
  if (typeof window === "undefined") return null;
903
1476
  const scope = window;
@@ -905,15 +1478,15 @@ function getRecognitionCtor() {
905
1478
  }
906
1479
  __name(getRecognitionCtor, "getRecognitionCtor");
907
1480
  function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
908
- const recognitionRef = (0, import_react2.useRef)(null);
909
- const [isListening, setIsListening] = (0, import_react2.useState)(false);
910
- const ctor = (0, import_react2.useMemo)(() => getRecognitionCtor(), []);
1481
+ const recognitionRef = (0, import_react7.useRef)(null);
1482
+ const [isListening, setIsListening] = (0, import_react7.useState)(false);
1483
+ const ctor = (0, import_react7.useMemo)(() => getRecognitionCtor(), []);
911
1484
  const isSupported = ctor !== null;
912
- const finalRef = (0, import_react2.useRef)(onFinalTranscript);
913
- const interimRef = (0, import_react2.useRef)(onInterimTranscript);
1485
+ const finalRef = (0, import_react7.useRef)(onFinalTranscript);
1486
+ const interimRef = (0, import_react7.useRef)(onInterimTranscript);
914
1487
  finalRef.current = onFinalTranscript;
915
1488
  interimRef.current = onInterimTranscript;
916
- (0, import_react2.useEffect)(() => {
1489
+ (0, import_react7.useEffect)(() => {
917
1490
  if (!ctor) return;
918
1491
  const recognition = new ctor();
919
1492
  recognition.lang = lang;
@@ -952,7 +1525,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
952
1525
  ctor,
953
1526
  lang
954
1527
  ]);
955
- const start = (0, import_react2.useCallback)(() => {
1528
+ const start = (0, import_react7.useCallback)(() => {
956
1529
  const recognition = recognitionRef.current;
957
1530
  if (!recognition) return;
958
1531
  try {
@@ -962,7 +1535,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
962
1535
  console.warn("SpeechRecognition start failed", error);
963
1536
  }
964
1537
  }, []);
965
- const stop = (0, import_react2.useCallback)(() => {
1538
+ const stop = (0, import_react7.useCallback)(() => {
966
1539
  const recognition = recognitionRef.current;
967
1540
  if (!recognition) return;
968
1541
  try {
@@ -981,8 +1554,8 @@ __name(useSpeechRecognition, "useSpeechRecognition");
981
1554
 
982
1555
  // src/components/chat-input.tsx
983
1556
  function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = true, speechLang = "en-US", sendLabel = "Send", onStop, isStreaming = false, stopLabel = "Stop generating", className, textareaClassName, sendButtonClassName, micButtonClassName, stopButtonClassName, renderMic }) {
984
- const [value, setValue] = (0, import_react3.useState)("");
985
- const [interim, setInterim] = (0, import_react3.useState)("");
1557
+ const [value, setValue] = (0, import_react8.useState)("");
1558
+ const [interim, setInterim] = (0, import_react8.useState)("");
986
1559
  const speech = useSpeechRecognition({
987
1560
  lang: speechLang,
988
1561
  onFinalTranscript: /* @__PURE__ */ __name((text) => {
@@ -993,7 +1566,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
993
1566
  }, "onFinalTranscript"),
994
1567
  onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
995
1568
  });
996
- (0, import_react3.useEffect)(() => {
1569
+ (0, import_react8.useEffect)(() => {
997
1570
  if (!speech.isListening) setInterim("");
998
1571
  }, [
999
1572
  speech.isListening
@@ -1021,9 +1594,9 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
1021
1594
  const displayValue = speech.isListening && interim ? `${value}${value && !value.endsWith(" ") ? " " : ""}${interim}` : value;
1022
1595
  const showMic = enableSpeech && speech.isSupported;
1023
1596
  const showStop = isStreaming && onStop !== void 0;
1024
- return /* @__PURE__ */ import_react3.default.createElement("div", {
1597
+ return /* @__PURE__ */ import_react8.default.createElement("div", {
1025
1598
  className
1026
- }, /* @__PURE__ */ import_react3.default.createElement("textarea", {
1599
+ }, /* @__PURE__ */ import_react8.default.createElement("textarea", {
1027
1600
  className: textareaClassName,
1028
1601
  rows,
1029
1602
  value: displayValue,
@@ -1034,17 +1607,17 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
1034
1607
  }, "onChange"),
1035
1608
  onKeyDown: handleKeyDown,
1036
1609
  placeholder: placeholder ?? "Ask something (Enter to send, Shift+Enter for newline)"
1037
- }), /* @__PURE__ */ import_react3.default.createElement("button", {
1610
+ }), /* @__PURE__ */ import_react8.default.createElement("button", {
1038
1611
  type: "button",
1039
1612
  className: sendButtonClassName,
1040
1613
  onClick: submit,
1041
1614
  disabled: disabled || !value.trim()
1042
- }, sendLabel), showStop ? /* @__PURE__ */ import_react3.default.createElement("button", {
1615
+ }, sendLabel), showStop ? /* @__PURE__ */ import_react8.default.createElement("button", {
1043
1616
  type: "button",
1044
1617
  className: stopButtonClassName,
1045
1618
  onClick: /* @__PURE__ */ __name(() => void onStop(), "onClick"),
1046
1619
  "aria-label": "Stop generating"
1047
- }, stopLabel) : null, showMic ? /* @__PURE__ */ import_react3.default.createElement("button", {
1620
+ }, stopLabel) : null, showMic ? /* @__PURE__ */ import_react8.default.createElement("button", {
1048
1621
  type: "button",
1049
1622
  className: micButtonClassName,
1050
1623
  onClick: toggleMic,
@@ -1057,10 +1630,10 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
1057
1630
  __name(ChatInput, "ChatInput");
1058
1631
 
1059
1632
  // src/components/message-item.tsx
1060
- var import_react6 = __toESM(require("react"), 1);
1633
+ var import_react11 = __toESM(require("react"), 1);
1061
1634
 
1062
1635
  // src/transcript/use-chat-transcript.ts
1063
- var import_react5 = require("react");
1636
+ var import_react10 = require("react");
1064
1637
 
1065
1638
  // src/transcript/model.ts
1066
1639
  var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
@@ -2037,11 +2610,11 @@ function formatCostUsd(cost) {
2037
2610
  __name(formatCostUsd, "formatCostUsd");
2038
2611
 
2039
2612
  // src/transcript/use-stick-to-bottom.ts
2040
- var import_react4 = require("react");
2613
+ var import_react9 = require("react");
2041
2614
  function useStickToBottom({ contentKey, threshold = 32 }) {
2042
- const containerRef = (0, import_react4.useRef)(null);
2043
- const [isAtBottom, setIsAtBottom] = (0, import_react4.useState)(true);
2044
- const measure = (0, import_react4.useCallback)(() => {
2615
+ const containerRef = (0, import_react9.useRef)(null);
2616
+ const [isAtBottom, setIsAtBottom] = (0, import_react9.useState)(true);
2617
+ const measure = (0, import_react9.useCallback)(() => {
2045
2618
  const element = containerRef.current;
2046
2619
  if (!element) {
2047
2620
  return;
@@ -2051,7 +2624,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2051
2624
  }, [
2052
2625
  threshold
2053
2626
  ]);
2054
- const scrollToBottom = (0, import_react4.useCallback)(() => {
2627
+ const scrollToBottom = (0, import_react9.useCallback)(() => {
2055
2628
  const element = containerRef.current;
2056
2629
  if (!element) {
2057
2630
  return;
@@ -2059,7 +2632,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2059
2632
  element.scrollTop = element.scrollHeight;
2060
2633
  setIsAtBottom(true);
2061
2634
  }, []);
2062
- const setContainer = (0, import_react4.useCallback)((element) => {
2635
+ const setContainer = (0, import_react9.useCallback)((element) => {
2063
2636
  containerRef.current = element;
2064
2637
  if (element) {
2065
2638
  measure();
@@ -2067,7 +2640,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2067
2640
  }, [
2068
2641
  measure
2069
2642
  ]);
2070
- (0, import_react4.useEffect)(() => {
2643
+ (0, import_react9.useEffect)(() => {
2071
2644
  if (isAtBottom) {
2072
2645
  scrollToBottom();
2073
2646
  }
@@ -2076,7 +2649,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2076
2649
  isAtBottom,
2077
2650
  scrollToBottom
2078
2651
  ]);
2079
- const getContainerProps = (0, import_react4.useCallback)(() => ({
2652
+ const getContainerProps = (0, import_react9.useCallback)(() => ({
2080
2653
  ref: setContainer,
2081
2654
  onScroll: measure
2082
2655
  }), [
@@ -2098,14 +2671,14 @@ var DEFAULT_LOAD_EARLIER_STEP = 50;
2098
2671
  var DEFAULT_COPY_RESET_MS = 1500;
2099
2672
  function useChatTranscript(options) {
2100
2673
  const { messages, status, initialVisibleCount = DEFAULT_VISIBLE_COUNT, loadEarlierStep = DEFAULT_LOAD_EARLIER_STEP, followUps, stickToBottomThreshold } = options;
2101
- const latest = (0, import_react5.useRef)(options);
2674
+ const latest = (0, import_react10.useRef)(options);
2102
2675
  latest.current = options;
2103
- const [visibleCount, setVisibleCount] = (0, import_react5.useState)(initialVisibleCount);
2104
- const [stopRequested, setStopRequested] = (0, import_react5.useState)(false);
2105
- const isStreaming = status === "streaming";
2676
+ const [visibleCount, setVisibleCount] = (0, import_react10.useState)(initialVisibleCount);
2677
+ const [stopRequested, setStopRequested] = (0, import_react10.useState)(false);
2678
+ const isStreaming = status === "streaming" || status === "reconnecting";
2106
2679
  const isBusy = status === "submitted" || isStreaming;
2107
2680
  const isEmpty = messages.length === 0;
2108
- (0, import_react5.useEffect)(() => {
2681
+ (0, import_react10.useEffect)(() => {
2109
2682
  if (!isBusy) {
2110
2683
  setStopRequested(false);
2111
2684
  }
@@ -2122,16 +2695,16 @@ function useChatTranscript(options) {
2122
2695
  streamingMessageId,
2123
2696
  options
2124
2697
  });
2125
- const stop = (0, import_react5.useCallback)(() => {
2698
+ const stop = (0, import_react10.useCallback)(() => {
2126
2699
  const current = latest.current;
2127
- if (current.status !== "submitted" && current.status !== "streaming") {
2700
+ if (current.status !== "submitted" && current.status !== "streaming" && current.status !== "reconnecting") {
2128
2701
  return;
2129
2702
  }
2130
2703
  setStopRequested(true);
2131
2704
  void current.onStop?.();
2132
2705
  }, []);
2133
- const loadEarlier = (0, import_react5.useCallback)(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
2134
- const showAll = (0, import_react5.useCallback)(() => setVisibleCount(latest.current.messages.length), []);
2706
+ const loadEarlier = (0, import_react10.useCallback)(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
2707
+ const showAll = (0, import_react10.useCallback)(() => setVisibleCount(latest.current.messages.length), []);
2135
2708
  const scroll = useStickToBottom({
2136
2709
  contentKey: contentSignature(messages),
2137
2710
  ...stickToBottomThreshold !== void 0 ? {
@@ -2167,7 +2740,7 @@ function useChatTranscript(options) {
2167
2740
  __name(useChatTranscript, "useChatTranscript");
2168
2741
  function useTranscriptItem(options) {
2169
2742
  const { message, isStreaming = false } = options;
2170
- const messagesRef = (0, import_react5.useRef)([
2743
+ const messagesRef = (0, import_react10.useRef)([
2171
2744
  message
2172
2745
  ]);
2173
2746
  if (messagesRef.current[0] !== message) {
@@ -2185,13 +2758,13 @@ function useTranscriptItem(options) {
2185
2758
  }
2186
2759
  __name(useTranscriptItem, "useTranscriptItem");
2187
2760
  function useTranscriptItems({ messages, visibleStart, streamingMessageId, options }) {
2188
- const [drafts, setDrafts] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2189
- const [copied, setCopied] = (0, import_react5.useState)(() => /* @__PURE__ */ new Set());
2190
- const [openReasoning, setOpenReasoning] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2191
- const [picks, setPicks] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2192
- const [settling, setSettling] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2193
- const [settleErrors, setSettleErrors] = (0, import_react5.useState)(() => /* @__PURE__ */ new Map());
2194
- const latest = (0, import_react5.useRef)({
2761
+ const [drafts, setDrafts] = (0, import_react10.useState)(() => /* @__PURE__ */ new Map());
2762
+ const [copied, setCopied] = (0, import_react10.useState)(() => /* @__PURE__ */ new Set());
2763
+ const [openReasoning, setOpenReasoning] = (0, import_react10.useState)(() => /* @__PURE__ */ new Map());
2764
+ const [picks, setPicks] = (0, import_react10.useState)(() => /* @__PURE__ */ new Map());
2765
+ const [settling, setSettling] = (0, import_react10.useState)(() => /* @__PURE__ */ new Map());
2766
+ const [settleErrors, setSettleErrors] = (0, import_react10.useState)(() => /* @__PURE__ */ new Map());
2767
+ const latest = (0, import_react10.useRef)({
2195
2768
  options,
2196
2769
  drafts,
2197
2770
  picks,
@@ -2203,23 +2776,23 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2203
2776
  picks,
2204
2777
  messages
2205
2778
  };
2206
- const copyTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
2207
- (0, import_react5.useEffect)(() => () => {
2779
+ const copyTimers = (0, import_react10.useRef)(/* @__PURE__ */ new Map());
2780
+ (0, import_react10.useEffect)(() => () => {
2208
2781
  for (const timer of copyTimers.current.values()) {
2209
2782
  clearTimeout(timer);
2210
2783
  }
2211
2784
  copyTimers.current.clear();
2212
2785
  }, []);
2213
- const callbackCache = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
2214
- const reasoningToggleCache = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
2215
- const toggleReasoning = (0, import_react5.useCallback)((key, open) => {
2786
+ const callbackCache = (0, import_react10.useRef)(/* @__PURE__ */ new Map());
2787
+ const reasoningToggleCache = (0, import_react10.useRef)(/* @__PURE__ */ new Map());
2788
+ const toggleReasoning = (0, import_react10.useCallback)((key, open) => {
2216
2789
  setOpenReasoning((current) => {
2217
2790
  const next = new Map(current);
2218
2791
  next.set(key, open ?? !(current.get(key) ?? false));
2219
2792
  return next;
2220
2793
  });
2221
2794
  }, []);
2222
- const stableToggle = (0, import_react5.useCallback)((key) => {
2795
+ const stableToggle = (0, import_react10.useCallback)((key) => {
2223
2796
  let cached = reasoningToggleCache.current.get(key);
2224
2797
  if (!cached) {
2225
2798
  cached = /* @__PURE__ */ __name((open) => toggleReasoning(key, open), "cached");
@@ -2229,10 +2802,10 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2229
2802
  }, [
2230
2803
  toggleReasoning
2231
2804
  ]);
2232
- const pick = (0, import_react5.useCallback)((toolCallId, questionId, values) => {
2805
+ const pick = (0, import_react10.useCallback)((toolCallId, questionId, values) => {
2233
2806
  setPicks((current) => new Map(current).set(pickKey(toolCallId, questionId), values));
2234
2807
  }, []);
2235
- const settle = (0, import_react5.useCallback)((toolCallId, action, send) => {
2808
+ const settle = (0, import_react10.useCallback)((toolCallId, action, send) => {
2236
2809
  setSettleErrors((current) => {
2237
2810
  if (!current.has(toolCallId)) {
2238
2811
  return current;
@@ -2256,7 +2829,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2256
2829
  failed(error);
2257
2830
  }
2258
2831
  }, []);
2259
- const answer = (0, import_react5.useCallback)((toolCallId) => {
2832
+ const answer = (0, import_react10.useCallback)((toolCallId) => {
2260
2833
  const current = latest.current;
2261
2834
  const onAnswer = current.options.onAnswer;
2262
2835
  if (!onAnswer) {
@@ -2266,7 +2839,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2266
2839
  }, [
2267
2840
  settle
2268
2841
  ]);
2269
- const skip = (0, import_react5.useCallback)((toolCallId) => {
2842
+ const skip = (0, import_react10.useCallback)((toolCallId) => {
2270
2843
  const onSkip = latest.current.options.onSkip;
2271
2844
  if (onSkip) {
2272
2845
  settle(toolCallId, "skip", () => onSkip(toolCallId));
@@ -2274,7 +2847,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2274
2847
  }, [
2275
2848
  settle
2276
2849
  ]);
2277
- const approve = (0, import_react5.useCallback)((toolCallId, options2) => {
2850
+ const approve = (0, import_react10.useCallback)((toolCallId, options2) => {
2278
2851
  const onApprove = latest.current.options.onApprove;
2279
2852
  if (onApprove) {
2280
2853
  settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
@@ -2282,7 +2855,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2282
2855
  }, [
2283
2856
  settle
2284
2857
  ]);
2285
- const reject = (0, import_react5.useCallback)((toolCallId) => {
2858
+ const reject = (0, import_react10.useCallback)((toolCallId) => {
2286
2859
  const onReject = latest.current.options.onReject;
2287
2860
  if (onReject) {
2288
2861
  settle(toolCallId, "reject", () => onReject(toolCallId));
@@ -2290,7 +2863,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2290
2863
  }, [
2291
2864
  settle
2292
2865
  ]);
2293
- const callbacksFor = (0, import_react5.useCallback)((id) => {
2866
+ const callbacksFor = (0, import_react10.useCallback)((id) => {
2294
2867
  const cached = callbackCache.current.get(id);
2295
2868
  if (cached) {
2296
2869
  return cached;
@@ -2532,7 +3105,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
2532
3105
  getCreatedAt: /* @__PURE__ */ __name(() => createdAt, "getCreatedAt")
2533
3106
  } : {}
2534
3107
  });
2535
- return /* @__PURE__ */ import_react6.default.createElement(MessageItemView, {
3108
+ return /* @__PURE__ */ import_react11.default.createElement(MessageItemView, {
2536
3109
  item,
2537
3110
  ...slots
2538
3111
  });
@@ -2540,51 +3113,51 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
2540
3113
  __name(MessageItem, "MessageItem");
2541
3114
  function MessageItemView({ item, ...slots }) {
2542
3115
  const { classNames } = slots;
2543
- const actionRow = /* @__PURE__ */ import_react6.default.createElement("div", {
3116
+ const actionRow = /* @__PURE__ */ import_react11.default.createElement("div", {
2544
3117
  className: classNames?.actions,
2545
3118
  "data-actions-for": item.role
2546
- }, item.copy.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
3119
+ }, item.copy.available ? /* @__PURE__ */ import_react11.default.createElement("button", {
2547
3120
  type: "button",
2548
3121
  onClick: item.copy.copy,
2549
3122
  className: classNames?.actionButton,
2550
3123
  title: item.copy.copied ? "Copied!" : "Copy message text"
2551
- }, item.copy.copied ? "Copied" : "Copy") : null, item.fork.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
3124
+ }, item.copy.copied ? "Copied" : "Copy") : null, item.fork.available ? /* @__PURE__ */ import_react11.default.createElement("button", {
2552
3125
  type: "button",
2553
3126
  onClick: item.fork.run,
2554
3127
  className: classNames?.actionButton,
2555
3128
  title: "Fork thread from this message"
2556
- }, "Fork") : null, item.regenerate.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
3129
+ }, "Fork") : null, item.regenerate.available ? /* @__PURE__ */ import_react11.default.createElement("button", {
2557
3130
  type: "button",
2558
3131
  onClick: item.regenerate.run,
2559
3132
  className: classNames?.actionButton,
2560
3133
  title: "Regenerate response"
2561
- }, "Regenerate") : null, slots.meta, !item.isUser && item.usage ? /* @__PURE__ */ import_react6.default.createElement("span", {
3134
+ }, "Regenerate") : null, slots.meta, !item.isUser && item.usage ? /* @__PURE__ */ import_react11.default.createElement("span", {
2562
3135
  className: classNames?.usage,
2563
3136
  title: `Input ${item.usage.inputTokens.toLocaleString()} \xB7 Output ${item.usage.outputTokens.toLocaleString()} tokens`
2564
- }, item.usage.costLabel, " \xB7 ", item.usage.tokensLabel) : null, item.timestamp ? /* @__PURE__ */ import_react6.default.createElement("span", {
3137
+ }, item.usage.costLabel, " \xB7 ", item.usage.tokensLabel) : null, item.timestamp ? /* @__PURE__ */ import_react11.default.createElement("span", {
2565
3138
  className: classNames?.timestamp,
2566
3139
  title: item.timestamp.absolute
2567
3140
  }, item.timestamp.relative) : null);
2568
3141
  if (item.edit.available) {
2569
- return /* @__PURE__ */ import_react6.default.createElement(EditableUserBubble, {
3142
+ return /* @__PURE__ */ import_react11.default.createElement(EditableUserBubble, {
2570
3143
  item,
2571
3144
  slots,
2572
3145
  actions: actionRow
2573
3146
  });
2574
3147
  }
2575
3148
  const className = joinClasses(classNames?.root, classNames?.byRole?.[item.role]);
2576
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3149
+ return /* @__PURE__ */ import_react11.default.createElement("div", {
2577
3150
  className,
2578
3151
  "data-role": item.role,
2579
3152
  "data-streaming": item.isStreaming ? "true" : void 0
2580
- }, /* @__PURE__ */ import_react6.default.createElement("div", null, renderBlocks(item, slots)), actionRow);
3153
+ }, /* @__PURE__ */ import_react11.default.createElement("div", null, renderBlocks(item, slots)), actionRow);
2581
3154
  }
2582
3155
  __name(MessageItemView, "MessageItemView");
2583
3156
  function renderBlocks(item, slots) {
2584
3157
  const nodes = [];
2585
3158
  for (const block of item.blocks) {
2586
3159
  if (block.kind === "text") {
2587
- nodes.push(/* @__PURE__ */ import_react6.default.createElement("div", {
3160
+ nodes.push(/* @__PURE__ */ import_react11.default.createElement("div", {
2588
3161
  key: block.key,
2589
3162
  className: slots.classNames?.text
2590
3163
  }, slots.renderText ? slots.renderText(block.text, {
@@ -2605,7 +3178,7 @@ function renderBlocks(item, slots) {
2605
3178
  continue;
2606
3179
  }
2607
3180
  if (block.kind === "ui" && slots.renderUi) {
2608
- nodes.push(/* @__PURE__ */ import_react6.default.createElement("div", {
3181
+ nodes.push(/* @__PURE__ */ import_react11.default.createElement("div", {
2609
3182
  key: block.key,
2610
3183
  "data-slot": "ui",
2611
3184
  "data-component": block.component
@@ -2616,18 +3189,18 @@ function renderBlocks(item, slots) {
2616
3189
  }
2617
3190
  __name(renderBlocks, "renderBlocks");
2618
3191
  function renderFilesBlock(block, slots) {
2619
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3192
+ return /* @__PURE__ */ import_react11.default.createElement("div", {
2620
3193
  key: block.key,
2621
3194
  className: slots.classNames?.files,
2622
3195
  "data-files": "true"
2623
- }, slots.renderFiles ? slots.renderFiles(block.files) : block.files.map((file) => /* @__PURE__ */ import_react6.default.createElement("a", {
3196
+ }, slots.renderFiles ? slots.renderFiles(block.files) : block.files.map((file) => /* @__PURE__ */ import_react11.default.createElement("a", {
2624
3197
  key: file.url,
2625
3198
  href: file.url,
2626
3199
  target: "_blank",
2627
3200
  rel: "noreferrer",
2628
3201
  className: slots.classNames?.file,
2629
3202
  title: file.filename ?? file.mediaType
2630
- }, file.isImage ? /* @__PURE__ */ import_react6.default.createElement("img", {
3203
+ }, file.isImage ? /* @__PURE__ */ import_react11.default.createElement("img", {
2631
3204
  src: file.url,
2632
3205
  alt: file.filename ?? "image",
2633
3206
  className: slots.classNames?.fileImage
@@ -2635,17 +3208,17 @@ function renderFilesBlock(block, slots) {
2635
3208
  }
2636
3209
  __name(renderFilesBlock, "renderFilesBlock");
2637
3210
  function renderReasoningBlock(block, slots) {
2638
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3211
+ return /* @__PURE__ */ import_react11.default.createElement("div", {
2639
3212
  key: block.key,
2640
3213
  className: slots.classNames?.reasoning,
2641
3214
  "data-reasoning": "true",
2642
3215
  "data-streaming": block.isStreaming ? "true" : void 0
2643
- }, /* @__PURE__ */ import_react6.default.createElement("button", {
3216
+ }, /* @__PURE__ */ import_react11.default.createElement("button", {
2644
3217
  type: "button",
2645
3218
  className: slots.classNames?.reasoningToggle,
2646
3219
  onClick: /* @__PURE__ */ __name(() => block.toggle(), "onClick"),
2647
3220
  "aria-expanded": block.isOpen
2648
- }, slots.reasoningLabel ?? "Reasoning"), block.isOpen ? /* @__PURE__ */ import_react6.default.createElement("div", {
3221
+ }, slots.reasoningLabel ?? "Reasoning"), block.isOpen ? /* @__PURE__ */ import_react11.default.createElement("div", {
2649
3222
  className: slots.classNames?.reasoningText
2650
3223
  }, slots.renderReasoning ? slots.renderReasoning(block.text, {
2651
3224
  isStreaming: block.isStreaming,
@@ -2656,7 +3229,7 @@ __name(renderReasoningBlock, "renderReasoningBlock");
2656
3229
  function renderToolBlock(item, block, slots) {
2657
3230
  if (slots.renderToolGroup) {
2658
3231
  return [
2659
- /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, {
3232
+ /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, {
2660
3233
  key: block.key
2661
3234
  }, slots.renderToolGroup(block.parts, block.key))
2662
3235
  ];
@@ -2667,7 +3240,7 @@ function renderToolBlock(item, block, slots) {
2667
3240
  const renderToolPart = slots.renderToolPart;
2668
3241
  return block.parts.map((part) => {
2669
3242
  const partKey = `${item.id}-${part.toolCallId}`;
2670
- return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, {
3243
+ return /* @__PURE__ */ import_react11.default.createElement(import_react11.default.Fragment, {
2671
3244
  key: partKey
2672
3245
  }, renderToolPart(part, partKey));
2673
3246
  });
@@ -2677,14 +3250,14 @@ function EditableUserBubble({ item, slots, actions }) {
2677
3250
  const { classNames } = slots;
2678
3251
  const { edit } = item;
2679
3252
  if (!edit.isEditing) {
2680
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3253
+ return /* @__PURE__ */ import_react11.default.createElement("div", {
2681
3254
  className: joinClasses(classNames?.root, classNames?.byRole?.user),
2682
3255
  "data-role": "user"
2683
- }, /* @__PURE__ */ import_react6.default.createElement("div", {
3256
+ }, /* @__PURE__ */ import_react11.default.createElement("div", {
2684
3257
  className: classNames?.text
2685
3258
  }, slots.renderText ? slots.renderText(item.text, {
2686
3259
  isStreaming: false
2687
- }) : item.text), /* @__PURE__ */ import_react6.default.createElement("button", {
3260
+ }) : item.text), /* @__PURE__ */ import_react11.default.createElement("button", {
2688
3261
  type: "button",
2689
3262
  onClick: edit.start,
2690
3263
  className: classNames?.actionButton,
@@ -2692,18 +3265,18 @@ function EditableUserBubble({ item, slots, actions }) {
2692
3265
  }, "Edit"), actions);
2693
3266
  }
2694
3267
  const textareaProps = edit.getTextareaProps();
2695
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3268
+ return /* @__PURE__ */ import_react11.default.createElement("div", {
2696
3269
  "data-editing": "true"
2697
- }, /* @__PURE__ */ import_react6.default.createElement("textarea", {
3270
+ }, /* @__PURE__ */ import_react11.default.createElement("textarea", {
2698
3271
  ...textareaProps,
2699
3272
  rows: Math.min(8, edit.draft.split("\n").length + 1),
2700
3273
  className: classNames?.editTextarea
2701
- }), /* @__PURE__ */ import_react6.default.createElement("div", {
3274
+ }), /* @__PURE__ */ import_react11.default.createElement("div", {
2702
3275
  className: classNames?.editActions
2703
- }, /* @__PURE__ */ import_react6.default.createElement("button", {
3276
+ }, /* @__PURE__ */ import_react11.default.createElement("button", {
2704
3277
  type: "button",
2705
3278
  onClick: edit.cancel
2706
- }, "Cancel"), /* @__PURE__ */ import_react6.default.createElement("button", {
3279
+ }, "Cancel"), /* @__PURE__ */ import_react11.default.createElement("button", {
2707
3280
  type: "button",
2708
3281
  onClick: edit.save,
2709
3282
  disabled: !edit.canSave
@@ -2913,7 +3486,11 @@ function createSkillsSource(options) {
2913
3486
  if (cached) {
2914
3487
  return cached;
2915
3488
  }
2916
- const pending = options.client.listSkills(threadId).then((entries) => entries.map(toItem)).catch((error) => {
3489
+ const listSkills = options.client.listSkills;
3490
+ if (listSkills === void 0) {
3491
+ return Promise.reject(new AgentBackendUnsupportedError("listSkills"));
3492
+ }
3493
+ const pending = listSkills.call(options.client, threadId).then((entries) => entries.map(toItem)).catch((error) => {
2917
3494
  byThread.delete(key);
2918
3495
  throw error;
2919
3496
  });
@@ -2943,35 +3520,35 @@ function toItem(entry) {
2943
3520
  __name(toItem, "toItem");
2944
3521
 
2945
3522
  // src/composer/use-composer-autocomplete.ts
2946
- var import_react7 = require("react");
3523
+ var import_react12 = require("react");
2947
3524
  function useComposerAutocomplete(options) {
2948
3525
  const { value, sources, acceptOnTab = true } = options;
2949
- const latest = (0, import_react7.useRef)(options);
3526
+ const latest = (0, import_react12.useRef)(options);
2950
3527
  latest.current = options;
2951
- const inputRef = (0, import_react7.useRef)(null);
2952
- const [caret, setCaret] = (0, import_react7.useState)(value.length);
2953
- const [items, setItems] = (0, import_react7.useState)([]);
2954
- const [isLoading, setLoading] = (0, import_react7.useState)(false);
2955
- const [error, setError] = (0, import_react7.useState)(null);
2956
- const [activeIndex, setActiveIndex] = (0, import_react7.useState)(0);
2957
- const [dismissedIndex, setDismissedIndex] = (0, import_react7.useState)(null);
3528
+ const inputRef = (0, import_react12.useRef)(null);
3529
+ const [caret, setCaret] = (0, import_react12.useState)(value.length);
3530
+ const [items, setItems] = (0, import_react12.useState)([]);
3531
+ const [isLoading, setLoading] = (0, import_react12.useState)(false);
3532
+ const [error, setError] = (0, import_react12.useState)(null);
3533
+ const [activeIndex, setActiveIndex] = (0, import_react12.useState)(0);
3534
+ const [dismissedIndex, setDismissedIndex] = (0, import_react12.useState)(null);
2958
3535
  const safeCaret = Math.min(caret, value.length);
2959
- const match = (0, import_react7.useMemo)(() => findActiveTrigger(value, safeCaret, sources), [
3536
+ const match = (0, import_react12.useMemo)(() => findActiveTrigger(value, safeCaret, sources), [
2960
3537
  value,
2961
3538
  safeCaret,
2962
3539
  sources
2963
3540
  ]);
2964
3541
  const isActive = match !== null && dismissedIndex !== match.index;
2965
- const matchRef = (0, import_react7.useRef)(match);
3542
+ const matchRef = (0, import_react12.useRef)(match);
2966
3543
  matchRef.current = match;
2967
- const caretRef = (0, import_react7.useRef)(safeCaret);
3544
+ const caretRef = (0, import_react12.useRef)(safeCaret);
2968
3545
  caretRef.current = safeCaret;
2969
- const itemsRef = (0, import_react7.useRef)(items);
3546
+ const itemsRef = (0, import_react12.useRef)(items);
2970
3547
  itemsRef.current = items;
2971
- const activeIndexRef = (0, import_react7.useRef)(activeIndex);
3548
+ const activeIndexRef = (0, import_react12.useRef)(activeIndex);
2972
3549
  activeIndexRef.current = activeIndex;
2973
3550
  const requestKey = isActive && match ? `${match.source.id}\0${match.query}` : null;
2974
- (0, import_react7.useEffect)(() => {
3551
+ (0, import_react12.useEffect)(() => {
2975
3552
  const current = matchRef.current;
2976
3553
  if (requestKey === null || current === null) {
2977
3554
  setItems([]);
@@ -3019,25 +3596,25 @@ function useComposerAutocomplete(options) {
3019
3596
  ]);
3020
3597
  const isOpen = isActive;
3021
3598
  const activeItem = isOpen ? items[activeIndex] ?? null : null;
3022
- const reactId = (0, import_react7.useId)();
3599
+ const reactId = (0, import_react12.useId)();
3023
3600
  const listboxId = options.id ?? `agent-autocomplete-${reactId}`;
3024
- const optionId = (0, import_react7.useCallback)((index) => `${listboxId}-option-${index}`, [
3601
+ const optionId = (0, import_react12.useCallback)((index) => `${listboxId}-option-${index}`, [
3025
3602
  listboxId
3026
3603
  ]);
3027
- const highlight = (0, import_react7.useCallback)((index) => {
3604
+ const highlight = (0, import_react12.useCallback)((index) => {
3028
3605
  const total = itemsRef.current.length;
3029
3606
  if (total === 0) return;
3030
3607
  setActiveIndex((index % total + total) % total);
3031
3608
  }, []);
3032
- const moveHighlight = (0, import_react7.useCallback)((delta) => highlight(activeIndexRef.current + delta), [
3609
+ const moveHighlight = (0, import_react12.useCallback)((delta) => highlight(activeIndexRef.current + delta), [
3033
3610
  highlight
3034
3611
  ]);
3035
- const dismiss = (0, import_react7.useCallback)(() => {
3612
+ const dismiss = (0, import_react12.useCallback)(() => {
3036
3613
  const current = matchRef.current;
3037
3614
  if (current) setDismissedIndex(current.index);
3038
3615
  }, []);
3039
- const pendingCaret = (0, import_react7.useRef)(null);
3040
- const accept = (0, import_react7.useCallback)((item) => {
3616
+ const pendingCaret = (0, import_react12.useRef)(null);
3617
+ const accept = (0, import_react12.useCallback)((item) => {
3041
3618
  const current = matchRef.current;
3042
3619
  if (!current) return;
3043
3620
  const chosen = item ?? itemsRef.current[activeIndexRef.current];
@@ -3048,16 +3625,16 @@ function useComposerAutocomplete(options) {
3048
3625
  setDismissedIndex(null);
3049
3626
  pendingCaret.current = edit.caret;
3050
3627
  }, []);
3051
- (0, import_react7.useLayoutEffect)(() => {
3628
+ (0, import_react12.useLayoutEffect)(() => {
3052
3629
  const position = pendingCaret.current;
3053
3630
  if (position === null) return;
3054
3631
  pendingCaret.current = null;
3055
3632
  inputRef.current?.setSelectionRange(position, position);
3056
3633
  });
3057
- const readCaret = (0, import_react7.useCallback)((element) => {
3634
+ const readCaret = (0, import_react12.useCallback)((element) => {
3058
3635
  setCaret(element.selectionStart ?? element.value.length);
3059
3636
  }, []);
3060
- const onKeyDown = (0, import_react7.useCallback)((event) => {
3637
+ const onKeyDown = (0, import_react12.useCallback)((event) => {
3061
3638
  if (!isOpen) return;
3062
3639
  const hasItems = itemsRef.current.length > 0;
3063
3640
  switch (event.key) {
@@ -3095,23 +3672,23 @@ function useComposerAutocomplete(options) {
3095
3672
  dismiss,
3096
3673
  moveHighlight
3097
3674
  ]);
3098
- const onChange = (0, import_react7.useCallback)((event) => {
3675
+ const onChange = (0, import_react12.useCallback)((event) => {
3099
3676
  readCaret(event.target);
3100
3677
  const next = findActiveTrigger(event.target.value, event.target.selectionStart ?? event.target.value.length, latest.current.sources);
3101
3678
  setDismissedIndex((current) => current !== null && next?.index === current ? current : null);
3102
3679
  }, [
3103
3680
  readCaret
3104
3681
  ]);
3105
- const onSelect = (0, import_react7.useCallback)((event) => readCaret(event.currentTarget), [
3682
+ const onSelect = (0, import_react12.useCallback)((event) => readCaret(event.currentTarget), [
3106
3683
  readCaret
3107
3684
  ]);
3108
- const onClick = (0, import_react7.useCallback)((event) => readCaret(event.currentTarget), [
3685
+ const onClick = (0, import_react12.useCallback)((event) => readCaret(event.currentTarget), [
3109
3686
  readCaret
3110
3687
  ]);
3111
- const setInput = (0, import_react7.useCallback)((element) => {
3688
+ const setInput = (0, import_react12.useCallback)((element) => {
3112
3689
  inputRef.current = element;
3113
3690
  }, []);
3114
- const getInputProps = (0, import_react7.useCallback)(() => ({
3691
+ const getInputProps = (0, import_react12.useCallback)(() => ({
3115
3692
  ref: setInput,
3116
3693
  onChange,
3117
3694
  onKeyDown,
@@ -3139,7 +3716,7 @@ function useComposerAutocomplete(options) {
3139
3716
  activeIndex,
3140
3717
  optionId
3141
3718
  ]);
3142
- const getListboxProps = (0, import_react7.useCallback)(() => ({
3719
+ const getListboxProps = (0, import_react12.useCallback)(() => ({
3143
3720
  id: listboxId,
3144
3721
  role: "listbox",
3145
3722
  "aria-label": match?.source.label ?? match?.source.id ?? "Suggestions"
@@ -3147,7 +3724,7 @@ function useComposerAutocomplete(options) {
3147
3724
  listboxId,
3148
3725
  match
3149
3726
  ]);
3150
- const getOptionProps = (0, import_react7.useCallback)((index) => ({
3727
+ const getOptionProps = (0, import_react12.useCallback)((index) => ({
3151
3728
  id: optionId(index),
3152
3729
  role: "option",
3153
3730
  "aria-selected": index === activeIndex
@@ -3181,7 +3758,7 @@ function isPromise(value) {
3181
3758
  __name(isPromise, "isPromise");
3182
3759
 
3183
3760
  // src/presentation/use-tool-catalog.ts
3184
- var import_react8 = require("react");
3761
+ var import_react13 = require("react");
3185
3762
  var shared = /* @__PURE__ */ new WeakMap();
3186
3763
  function load(client, agent) {
3187
3764
  let byAgent = shared.get(client);
@@ -3192,6 +3769,9 @@ function load(client, agent) {
3192
3769
  const key = agent ?? "";
3193
3770
  const cached = byAgent.get(key);
3194
3771
  if (cached !== void 0) return cached;
3772
+ if (client.listTools === void 0) {
3773
+ return Promise.reject(new AgentBackendUnsupportedError("listTools"));
3774
+ }
3195
3775
  const request = client.listTools(agent).catch((error) => {
3196
3776
  byAgent.delete(key);
3197
3777
  throw error;
@@ -3202,11 +3782,11 @@ function load(client, agent) {
3202
3782
  __name(load, "load");
3203
3783
  function useToolCatalog(options) {
3204
3784
  const { client, agent, enabled = true } = options;
3205
- const [entries, setEntries] = (0, import_react8.useState)([]);
3206
- const [isLoading, setLoading] = (0, import_react8.useState)(enabled);
3207
- const [error, setError] = (0, import_react8.useState)(null);
3208
- const [generation, setGeneration] = (0, import_react8.useState)(0);
3209
- (0, import_react8.useEffect)(() => {
3785
+ const [entries, setEntries] = (0, import_react13.useState)([]);
3786
+ const [isLoading, setLoading] = (0, import_react13.useState)(enabled);
3787
+ const [error, setError] = (0, import_react13.useState)(null);
3788
+ const [generation, setGeneration] = (0, import_react13.useState)(0);
3789
+ (0, import_react13.useEffect)(() => {
3210
3790
  if (!enabled) {
3211
3791
  setLoading(false);
3212
3792
  return;
@@ -3232,14 +3812,14 @@ function useToolCatalog(options) {
3232
3812
  enabled,
3233
3813
  generation
3234
3814
  ]);
3235
- const refresh = (0, import_react8.useCallback)(() => {
3815
+ const refresh = (0, import_react13.useCallback)(() => {
3236
3816
  shared.get(client)?.delete(agent ?? "");
3237
3817
  setGeneration((current) => current + 1);
3238
3818
  }, [
3239
3819
  client,
3240
3820
  agent
3241
3821
  ]);
3242
- const catalog = (0, import_react8.useMemo)(() => toolCatalogFrom(entries), [
3822
+ const catalog = (0, import_react13.useMemo)(() => toolCatalogFrom(entries), [
3243
3823
  entries
3244
3824
  ]);
3245
3825
  return {
@@ -3253,7 +3833,7 @@ function useToolCatalog(options) {
3253
3833
  __name(useToolCatalog, "useToolCatalog");
3254
3834
 
3255
3835
  // src/approvals/countdown.ts
3256
- var import_react9 = require("react");
3836
+ var import_react14 = require("react");
3257
3837
  function approvalCountdown(expiresAt, now = Date.now()) {
3258
3838
  const deadline = expiresAt == null ? Number.NaN : Date.parse(expiresAt);
3259
3839
  if (!Number.isFinite(deadline)) {
@@ -3271,10 +3851,10 @@ function approvalCountdown(expiresAt, now = Date.now()) {
3271
3851
  __name(approvalCountdown, "approvalCountdown");
3272
3852
  function useApprovalCountdown(expiresAt, options = {}) {
3273
3853
  const { intervalMs = 1e3 } = options;
3274
- const nowRef = (0, import_react9.useRef)(options.now ?? Date.now);
3854
+ const nowRef = (0, import_react14.useRef)(options.now ?? Date.now);
3275
3855
  nowRef.current = options.now ?? Date.now;
3276
- const [state, setState] = (0, import_react9.useState)(() => approvalCountdown(expiresAt, nowRef.current()));
3277
- (0, import_react9.useEffect)(() => {
3856
+ const [state, setState] = (0, import_react14.useState)(() => approvalCountdown(expiresAt, nowRef.current()));
3857
+ (0, import_react14.useEffect)(() => {
3278
3858
  const read = /* @__PURE__ */ __name(() => approvalCountdown(expiresAt, nowRef.current()), "read");
3279
3859
  const first = read();
3280
3860
  setState(first);
@@ -3440,7 +4020,12 @@ function storedMessageToUiMessage(message) {
3440
4020
  return {
3441
4021
  id: message.id,
3442
4022
  role: message.role,
3443
- parts
4023
+ parts,
4024
+ ...message.feedback !== void 0 ? {
4025
+ metadata: {
4026
+ feedback: message.feedback
4027
+ }
4028
+ } : {}
3444
4029
  };
3445
4030
  }
3446
4031
  __name(storedMessageToUiMessage, "storedMessageToUiMessage");
@@ -3476,14 +4061,21 @@ function mergeTurn(rows) {
3476
4061
  }
3477
4062
  const parts = rows.flatMap((row) => storedMessageToUiMessage(row).parts);
3478
4063
  const usage = sumUsage(rows);
4064
+ const feedback = lastRow.feedback;
4065
+ const metadata = {
4066
+ ...usage !== void 0 ? {
4067
+ usage
4068
+ } : {},
4069
+ ...feedback !== void 0 ? {
4070
+ feedback
4071
+ } : {}
4072
+ };
3479
4073
  return {
3480
4074
  id: lastRow.id,
3481
4075
  role: lastRow.role,
3482
4076
  parts,
3483
- ...usage !== void 0 ? {
3484
- metadata: {
3485
- usage
3486
- }
4077
+ ...Object.keys(metadata).length > 0 ? {
4078
+ metadata
3487
4079
  } : {}
3488
4080
  };
3489
4081
  }
@@ -3503,21 +4095,22 @@ function sumUsage(rows) {
3503
4095
  __name(sumUsage, "sumUsage");
3504
4096
 
3505
4097
  // src/use-agent-chat.ts
3506
- var import_react10 = require("@ai-sdk/react");
3507
- var import_react11 = require("react");
4098
+ var import_react15 = require("@ai-sdk/react");
4099
+ var import_react16 = require("react");
3508
4100
  function useAgentChat(options) {
3509
- const latest = (0, import_react11.useRef)(options);
4101
+ const latest = (0, import_react16.useRef)(options);
3510
4102
  latest.current = options;
3511
- const [runId, setRunId] = (0, import_react11.useState)(options.resumeRunId);
3512
- const runIdRef = (0, import_react11.useRef)(runId);
4103
+ const [runId, setRunId] = (0, import_react16.useState)(options.resumeRunId);
4104
+ const runIdRef = (0, import_react16.useRef)(runId);
3513
4105
  runIdRef.current = runId;
3514
- const settlingRunIdRef = (0, import_react11.useRef)(void 0);
3515
- const autoResumeRunIdRef = (0, import_react11.useRef)(void 0);
3516
- const [autoResume, setAutoResume] = (0, import_react11.useState)(false);
3517
- const [activeRunId, setActiveRunId] = (0, import_react11.useState)(null);
3518
- const regenerateNext = (0, import_react11.useRef)(false);
3519
- const createdThreadId = (0, import_react11.useRef)(void 0);
3520
- const client = (0, import_react11.useMemo)(() => {
4106
+ const settlingRunIdRef = (0, import_react16.useRef)(void 0);
4107
+ const autoResumeRunIdRef = (0, import_react16.useRef)(void 0);
4108
+ const [autoResume, setAutoResume] = (0, import_react16.useState)(false);
4109
+ const [activeRunId, setActiveRunId] = (0, import_react16.useState)(null);
4110
+ const regenerateNext = (0, import_react16.useRef)(false);
4111
+ const createdThreadId = (0, import_react16.useRef)(void 0);
4112
+ const client = (0, import_react16.useMemo)(() => {
4113
+ if (options.backend) return options.backend;
3521
4114
  if (options.client) return options.client;
3522
4115
  return new AgentClient({
3523
4116
  ...options.baseUrl !== void 0 ? {
@@ -3532,7 +4125,11 @@ function useAgentChat(options) {
3532
4125
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
3533
4126
  });
3534
4127
  }, []);
3535
- (0, import_react11.useEffect)(() => {
4128
+ const [connection, setConnection] = (0, import_react16.useState)({
4129
+ status: "live"
4130
+ });
4131
+ const resyncAfterSettle = (0, import_react16.useRef)(false);
4132
+ (0, import_react16.useEffect)(() => {
3536
4133
  if (!options.resume || options.threadId === void 0) return;
3537
4134
  let cancelled = false;
3538
4135
  const threadId = options.threadId;
@@ -3552,30 +4149,33 @@ function useAgentChat(options) {
3552
4149
  options.resume,
3553
4150
  options.threadId
3554
4151
  ]);
3555
- const transport = (0, import_react11.useMemo)(() => {
4152
+ const transport = (0, import_react16.useMemo)(() => {
3556
4153
  function onMeta(meta) {
3557
4154
  settlingRunIdRef.current = meta.runId;
3558
4155
  setRunId(meta.runId);
3559
4156
  if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
3560
4157
  createdThreadId.current = meta.threadId;
3561
4158
  latest.current.onThreadCreated?.(meta.threadId);
4159
+ notifyThreads(client, {
4160
+ type: "changed"
4161
+ });
3562
4162
  }
3563
4163
  }
3564
4164
  __name(onMeta, "onMeta");
3565
4165
  return new AgentChatTransport({
3566
- ...options.baseUrl !== void 0 ? {
3567
- baseUrl: options.baseUrl
3568
- } : {},
3569
- ...options.credentials !== void 0 ? {
3570
- credentials: options.credentials
3571
- } : {},
3572
- ...options.fetch !== void 0 ? {
3573
- fetch: options.fetch
4166
+ backend: client,
4167
+ ...options.reconnect !== void 0 ? {
4168
+ reconnect: options.reconnect
3574
4169
  } : {},
4170
+ onConnectionChange: /* @__PURE__ */ __name((state) => {
4171
+ if (state.status === "gone") resyncAfterSettle.current = true;
4172
+ setConnection(state.status === "gone" ? {
4173
+ status: "live"
4174
+ } : state);
4175
+ }, "onConnectionChange"),
3575
4176
  ...options.agent !== void 0 ? {
3576
4177
  agent: options.agent
3577
4178
  } : {},
3578
- getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders"),
3579
4179
  getBody: /* @__PURE__ */ __name(() => {
3580
4180
  const current = latest.current;
3581
4181
  const pageContext = current.getPageContext?.() ?? null;
@@ -3586,6 +4186,9 @@ function useAgentChat(options) {
3586
4186
  ...threadId !== void 0 ? {
3587
4187
  threadId
3588
4188
  } : {},
4189
+ ...current.model !== void 0 ? {
4190
+ model: current.model
4191
+ } : {},
3589
4192
  ...pageContext ? {
3590
4193
  pageContext
3591
4194
  } : {},
@@ -3601,8 +4204,8 @@ function useAgentChat(options) {
3601
4204
  }, "onAttemptStart")
3602
4205
  });
3603
4206
  }, []);
3604
- const backgroundRef = (0, import_react11.useRef)(() => Promise.resolve());
3605
- const chat = (0, import_react10.useChat)({
4207
+ const backgroundRef = (0, import_react16.useRef)(() => Promise.resolve());
4208
+ const chat = (0, import_react15.useChat)({
3606
4209
  transport,
3607
4210
  resume: options.resumeRunId !== void 0 || autoResume,
3608
4211
  ...options.threadId !== void 0 ? {
@@ -3615,7 +4218,15 @@ function useAgentChat(options) {
3615
4218
  latest.current.onData?.(part);
3616
4219
  if (part.type === "data-title") {
3617
4220
  const title = part.data?.title;
3618
- if (typeof title === "string") latest.current.onTitle?.(title);
4221
+ if (typeof title === "string") {
4222
+ latest.current.onTitle?.(title);
4223
+ const threadId = latest.current.threadId ?? createdThreadId.current;
4224
+ if (threadId !== void 0) notifyThreads(client, {
4225
+ type: "title",
4226
+ threadId,
4227
+ title
4228
+ });
4229
+ }
3619
4230
  }
3620
4231
  }, "onData"),
3621
4232
  onFinish: /* @__PURE__ */ __name(({ isError }) => {
@@ -3629,16 +4240,36 @@ function useAgentChat(options) {
3629
4240
  runId: settledRunId,
3630
4241
  status: isError ? "failed" : "completed"
3631
4242
  });
4243
+ notifyThreads(client, {
4244
+ type: "changed"
4245
+ });
4246
+ }
4247
+ setConnection({
4248
+ status: "live"
4249
+ });
4250
+ if (resyncAfterSettle.current) {
4251
+ resyncAfterSettle.current = false;
4252
+ void resyncFromThread();
3632
4253
  }
3633
4254
  }, "onFinish")
3634
4255
  });
3635
- const chatRef = (0, import_react11.useRef)(chat);
4256
+ const chatRef = (0, import_react16.useRef)(chat);
3636
4257
  chatRef.current = chat;
3637
- const turnInFlight = (0, import_react11.useRef)(false);
3638
- const isTurnInFlight = (0, import_react11.useCallback)(() => turnInFlight.current || transport.isAttemptLive, [
4258
+ async function resyncFromThread() {
4259
+ const threadId = latest.current.threadId ?? createdThreadId.current;
4260
+ if (threadId === void 0) return;
4261
+ try {
4262
+ const thread = await client.getThread(threadId);
4263
+ chatRef.current.setMessages(storedThreadToUiMessages(thread.messages));
4264
+ } catch {
4265
+ }
4266
+ }
4267
+ __name(resyncFromThread, "resyncFromThread");
4268
+ const turnInFlight = (0, import_react16.useRef)(false);
4269
+ const isTurnInFlight = (0, import_react16.useCallback)(() => turnInFlight.current || transport.isAttemptLive, [
3639
4270
  transport
3640
4271
  ]);
3641
- const sendMessage = (0, import_react11.useCallback)(async (...args) => {
4272
+ const sendMessage = (0, import_react16.useCallback)(async (...args) => {
3642
4273
  if (isTurnInFlight()) return;
3643
4274
  turnInFlight.current = true;
3644
4275
  try {
@@ -3649,15 +4280,15 @@ function useAgentChat(options) {
3649
4280
  }, [
3650
4281
  isTurnInFlight
3651
4282
  ]);
3652
- const addToolResult = (0, import_react11.useCallback)(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
4283
+ const addToolResult = (0, import_react16.useCallback)(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
3653
4284
  tool,
3654
4285
  toolCallId,
3655
4286
  output
3656
4287
  }), []);
3657
- const [threads, setThreads] = (0, import_react11.useState)([]);
3658
- const [quota, setQuota] = (0, import_react11.useState)(null);
3659
- const [backgroundRuns, setBackgroundRuns] = (0, import_react11.useState)([]);
3660
- const refreshBackground = (0, import_react11.useCallback)(async () => {
4288
+ const [threads, setThreads] = (0, import_react16.useState)([]);
4289
+ const [quota, setQuota] = (0, import_react16.useState)(null);
4290
+ const [backgroundRuns, setBackgroundRuns] = (0, import_react16.useState)([]);
4291
+ const refreshBackground = (0, import_react16.useCallback)(async () => {
3661
4292
  const threadId = latest.current.threadId ?? createdThreadId.current;
3662
4293
  if (threadId === void 0) {
3663
4294
  return;
@@ -3682,7 +4313,7 @@ function useAgentChat(options) {
3682
4313
  ]);
3683
4314
  backgroundRef.current = refreshBackground;
3684
4315
  const isWorking = backgroundRuns.some((run) => run.status === "running");
3685
- (0, import_react11.useEffect)(() => {
4316
+ (0, import_react16.useEffect)(() => {
3686
4317
  if (options.background !== true || options.threadId === void 0) {
3687
4318
  return;
3688
4319
  }
@@ -3692,7 +4323,7 @@ function useAgentChat(options) {
3692
4323
  options.threadId,
3693
4324
  refreshBackground
3694
4325
  ]);
3695
- (0, import_react11.useEffect)(() => {
4326
+ (0, import_react16.useEffect)(() => {
3696
4327
  const every = latest.current.backgroundPollMs ?? 5e3;
3697
4328
  if (latest.current.background !== true || !isWorking || every <= 0) {
3698
4329
  return;
@@ -3703,27 +4334,29 @@ function useAgentChat(options) {
3703
4334
  isWorking,
3704
4335
  refreshBackground
3705
4336
  ]);
3706
- const loadThreads = (0, import_react11.useCallback)(async () => {
4337
+ const loadThreads = (0, import_react16.useCallback)(async () => {
3707
4338
  const list = await client.listThreads();
3708
4339
  setThreads(list);
3709
4340
  return list;
3710
4341
  }, [
3711
4342
  client
3712
4343
  ]);
3713
- const loadThread = (0, import_react11.useCallback)((id) => client.getThread(id), [
4344
+ const loadThread = (0, import_react16.useCallback)((id) => client.getThread(id), [
3714
4345
  client
3715
4346
  ]);
3716
- const deleteThread = (0, import_react11.useCallback)(async (id) => {
4347
+ const deleteThread = (0, import_react16.useCallback)(async (id) => {
3717
4348
  await client.deleteThread(id);
3718
4349
  setThreads((current) => current.filter((thread) => thread.id !== id));
3719
4350
  }, [
3720
4351
  client
3721
4352
  ]);
3722
- const forkThread = (0, import_react11.useCallback)((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
4353
+ const forkThread = (0, import_react16.useCallback)((threadId, messageId) => requireBackendMethod(client, "forkFromMessage")(threadId, messageId), [
3723
4354
  client
3724
4355
  ]);
3725
- const renameThread = (0, import_react11.useCallback)(async (id, title) => {
3726
- await client.renameThread(id, title);
4356
+ const renameThread = (0, import_react16.useCallback)(async (id, title) => {
4357
+ await client.updateThread(id, {
4358
+ title
4359
+ });
3727
4360
  setThreads((current) => current.map((thread) => thread.id === id ? {
3728
4361
  ...thread,
3729
4362
  title
@@ -3731,24 +4364,24 @@ function useAgentChat(options) {
3731
4364
  }, [
3732
4365
  client
3733
4366
  ]);
3734
- const promoteThread = (0, import_react11.useCallback)(async (id) => {
3735
- await client.promoteThread(id);
4367
+ const promoteThread = (0, import_react16.useCallback)(async (id) => {
4368
+ await requireBackendMethod(client, "promoteThread")(id);
3736
4369
  await loadThreads();
3737
4370
  }, [
3738
4371
  client,
3739
4372
  loadThreads
3740
4373
  ]);
3741
- const truncateFromMessage = (0, import_react11.useCallback)((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
4374
+ const truncateFromMessage = (0, import_react16.useCallback)((threadId, messageId) => requireBackendMethod(client, "truncateFromMessage")(threadId, messageId).then(() => void 0), [
3742
4375
  client
3743
4376
  ]);
3744
- const loadQuota = (0, import_react11.useCallback)(async () => {
3745
- const today = await client.getQuotaToday();
4377
+ const loadQuota = (0, import_react16.useCallback)(async () => {
4378
+ const today = await requireBackendMethod(client, "getQuotaToday")();
3746
4379
  setQuota(today);
3747
4380
  return today;
3748
4381
  }, [
3749
4382
  client
3750
4383
  ]);
3751
- const cancel = (0, import_react11.useCallback)(async () => {
4384
+ const cancel = (0, import_react16.useCallback)(async () => {
3752
4385
  chatRef.current.stop();
3753
4386
  const activeRunId2 = runIdRef.current;
3754
4387
  if (activeRunId2) {
@@ -3760,8 +4393,8 @@ function useAgentChat(options) {
3760
4393
  }, [
3761
4394
  client
3762
4395
  ]);
3763
- const approve = (0, import_react11.useCallback)(async ({ toolCallId, remember, via }) => {
3764
- await client.approveToolCall({
4396
+ const approve = (0, import_react16.useCallback)(async ({ toolCallId, remember, via }) => {
4397
+ await requireBackendMethod(client, "approveToolCall")({
3765
4398
  toolCallId,
3766
4399
  ...remember === true ? {
3767
4400
  remember: true
@@ -3773,8 +4406,8 @@ function useAgentChat(options) {
3773
4406
  }, [
3774
4407
  client
3775
4408
  ]);
3776
- const reject = (0, import_react11.useCallback)(async ({ toolCallId, reason, via }) => {
3777
- await client.rejectToolCall({
4409
+ const reject = (0, import_react16.useCallback)(async ({ toolCallId, reason, via }) => {
4410
+ await requireBackendMethod(client, "rejectToolCall")({
3778
4411
  toolCallId,
3779
4412
  ...reason !== void 0 ? {
3780
4413
  reason
@@ -3786,8 +4419,8 @@ function useAgentChat(options) {
3786
4419
  }, [
3787
4420
  client
3788
4421
  ]);
3789
- const answer = (0, import_react11.useCallback)(async ({ toolCallId, answers }) => {
3790
- await client.answerToolCall({
4422
+ const answer = (0, import_react16.useCallback)(async ({ toolCallId, answers }) => {
4423
+ await requireBackendMethod(client, "answerToolCall")({
3791
4424
  toolCallId,
3792
4425
  ...answers !== void 0 ? {
3793
4426
  answers
@@ -3796,14 +4429,14 @@ function useAgentChat(options) {
3796
4429
  }, [
3797
4430
  client
3798
4431
  ]);
3799
- const skip = (0, import_react11.useCallback)(async ({ toolCallId }) => {
3800
- await client.skipToolCall({
4432
+ const skip = (0, import_react16.useCallback)(async ({ toolCallId }) => {
4433
+ await requireBackendMethod(client, "skipToolCall")({
3801
4434
  toolCallId
3802
4435
  });
3803
4436
  }, [
3804
4437
  client
3805
4438
  ]);
3806
- const regenerate = (0, import_react11.useCallback)(() => {
4439
+ const regenerate = (0, import_react16.useCallback)(() => {
3807
4440
  if (isTurnInFlight()) return;
3808
4441
  regenerateNext.current = true;
3809
4442
  turnInFlight.current = true;
@@ -3818,8 +4451,27 @@ function useAgentChat(options) {
3818
4451
  isWorking,
3819
4452
  refresh: refreshBackground
3820
4453
  };
4454
+ const setThreadModel = (0, import_react16.useCallback)(async (model) => {
4455
+ const threadId = latest.current.threadId ?? createdThreadId.current;
4456
+ if (threadId === void 0) {
4457
+ throw new Error("setThreadModel: this chat has no thread yet");
4458
+ }
4459
+ await client.updateThread(threadId, {
4460
+ model
4461
+ });
4462
+ notifyThreads(client, {
4463
+ type: "changed"
4464
+ });
4465
+ }, [
4466
+ client
4467
+ ]);
4468
+ const getThreadId = (0, import_react16.useCallback)(() => latest.current.threadId ?? createdThreadId.current, []);
4469
+ const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
3821
4470
  return {
3822
4471
  ...chat,
4472
+ status,
4473
+ /** Where the live stream stands — see {@link StreamConnectionState}. */
4474
+ connection,
3823
4475
  sendMessage,
3824
4476
  addToolResult,
3825
4477
  /** Sub-agents this conversation started and did not wait for. */
@@ -3827,6 +4479,15 @@ function useAgentChat(options) {
3827
4479
  runId,
3828
4480
  /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
3829
4481
  activeRunId,
4482
+ /**
4483
+ * The thread this chat is on right now: the `threadId` option, else the one the backend created
4484
+ * on the first send. A getter, because the created id is learned mid-stream.
4485
+ */
4486
+ getThreadId,
4487
+ setThreadModel,
4488
+ /** The backend this chat talks to — pass it to `useThreads`, `useMessageFeedback`, … */
4489
+ backend: client,
4490
+ /** Same object as {@link backend}; kept for callers that read it under its old name. */
3830
4491
  client,
3831
4492
  threads,
3832
4493
  loadThreads,
@@ -3857,6 +4518,7 @@ async function mergeHeaders(options) {
3857
4518
  __name(mergeHeaders, "mergeHeaders");
3858
4519
  // Annotate the CommonJS export names for ESM import in node:
3859
4520
  0 && (module.exports = {
4521
+ AgentBackendUnsupportedError,
3860
4522
  AgentChatTransport,
3861
4523
  AgentClient,
3862
4524
  AgentHttpError,
@@ -3886,22 +4548,29 @@ __name(mergeHeaders, "mergeHeaders");
3886
4548
  inferResultView,
3887
4549
  isActionCall,
3888
4550
  loadStoredSpeechLang,
4551
+ notifyThreads,
4552
+ onThreadsEvent,
3889
4553
  persistSpeechLang,
3890
4554
  phraseFor,
3891
4555
  readPath,
3892
4556
  readReasoningMs,
4557
+ requireBackendMethod,
3893
4558
  resolveResultView,
3894
4559
  storedMessageToUiMessage,
3895
4560
  storedThreadToUiMessages,
3896
4561
  toolCallState,
3897
4562
  toolCatalogFrom,
3898
4563
  useAgentChat,
4564
+ useAgents,
3899
4565
  useApprovalCountdown,
3900
4566
  useChatTranscript,
3901
4567
  useComposerAutocomplete,
3902
4568
  useElapsed,
4569
+ useMessageFeedback,
4570
+ useModels,
3903
4571
  useSpeechRecognition,
3904
4572
  useStickToBottom,
4573
+ useThreads,
3905
4574
  useToolCatalog,
3906
4575
  useTranscriptItem,
3907
4576
  validateAnswer,