@dudousxd/nestjs-agent-react 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,10 +61,13 @@ __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,
@@ -74,8 +78,10 @@ __export(src_exports, {
74
78
  useChatTranscript: () => useChatTranscript,
75
79
  useComposerAutocomplete: () => useComposerAutocomplete,
76
80
  useElapsed: () => useElapsed,
81
+ useMessageFeedback: () => useMessageFeedback,
77
82
  useSpeechRecognition: () => useSpeechRecognition,
78
83
  useStickToBottom: () => useStickToBottom,
84
+ useThreads: () => useThreads,
79
85
  useToolCatalog: () => useToolCatalog,
80
86
  useTranscriptItem: () => useTranscriptItem,
81
87
  validateAnswer: () => import_nestjs_agent_core.validateElicitationAnswer,
@@ -83,6 +89,246 @@ __export(src_exports, {
83
89
  });
84
90
  module.exports = __toCommonJS(src_exports);
85
91
 
92
+ // src/client.ts
93
+ var AgentHttpError = class extends Error {
94
+ static {
95
+ __name(this, "AgentHttpError");
96
+ }
97
+ status;
98
+ method;
99
+ path;
100
+ constructor(status, method, path, statusText) {
101
+ super(`Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
102
+ this.name = "AgentHttpError";
103
+ }
104
+ };
105
+ var HEADER_RUN_ID = "x-agent-run-id";
106
+ var HEADER_THREAD_ID = "x-agent-thread-id";
107
+ var AgentClient = class {
108
+ static {
109
+ __name(this, "AgentClient");
110
+ }
111
+ options;
112
+ constructor(options = {}) {
113
+ this.options = options;
114
+ }
115
+ /** `POST /agent/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
116
+ async openChatStream(request) {
117
+ const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat`, {
118
+ method: "POST",
119
+ headers: {
120
+ "content-type": "application/json",
121
+ accept: "text/event-stream",
122
+ ...await this.resolveHeaders(),
123
+ ...request.headers
124
+ },
125
+ body: JSON.stringify(request.body),
126
+ ...this.credentials(),
127
+ ...request.signal !== void 0 ? {
128
+ signal: request.signal
129
+ } : {}
130
+ });
131
+ if (!response.ok || !response.body) {
132
+ throw new AgentHttpError(response.status, "POST", "/agent/chat", response.statusText);
133
+ }
134
+ return streamResponse(response);
135
+ }
136
+ /**
137
+ * `GET /agent/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
138
+ * streaming under that id (404).
139
+ */
140
+ async resumeChatStream(request) {
141
+ const path = `/agent/chat/${encodeURIComponent(request.runId)}/stream`;
142
+ const query = request.after !== void 0 && request.after > 0 ? `?after=${request.after}` : "";
143
+ const response = await this.fetchImpl()(`${this.baseUrl()}${path}${query}`, {
144
+ method: "GET",
145
+ headers: {
146
+ accept: "text/event-stream",
147
+ ...await this.resolveHeaders(),
148
+ ...request.headers
149
+ },
150
+ ...this.credentials(),
151
+ ...request.signal !== void 0 ? {
152
+ signal: request.signal
153
+ } : {}
154
+ });
155
+ if (response.status === 404) {
156
+ return null;
157
+ }
158
+ if (!response.ok || !response.body) {
159
+ throw new AgentHttpError(response.status, "GET", path, response.statusText);
160
+ }
161
+ return streamResponse(response);
162
+ }
163
+ /**
164
+ * Rate a message (`'up'`/`'down'`, optional comment) or clear its rating (`value: null`). Answers
165
+ * the stored rating.
166
+ */
167
+ setMessageFeedback(messageId, input) {
168
+ return this.request("POST", `/agent/messages/${encodeURIComponent(messageId)}/feedback`, input);
169
+ }
170
+ listThreads() {
171
+ return this.request("GET", "/agent/threads");
172
+ }
173
+ /**
174
+ * The skills this caller can invoke right now, scope-resolved — the same list, built by the same
175
+ * call, that the model is offered, so what a user can type after a `/` and what the agent can
176
+ * reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to
177
+ * one conversation; omitted, the server reads it as a brand-new thread.
178
+ */
179
+ listSkills(threadId) {
180
+ const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
181
+ return this.request("GET", `/agent/skills${query}`);
182
+ }
183
+ /**
184
+ * The tools this caller can reach through `agent` (the default agent when omitted), each with the
185
+ * server-declared `presentation` a chat narrates it by — the same list the model is offered.
186
+ * Prefer {@link useToolCatalog}, which fetches it once and shares it.
187
+ */
188
+ listTools(agent) {
189
+ const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
190
+ return this.request("GET", `/agent/tools${query}`);
191
+ }
192
+ getThread(id) {
193
+ return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
194
+ }
195
+ deleteThread(id) {
196
+ return this.request("DELETE", `/agent/threads/${encodeURIComponent(id)}`);
197
+ }
198
+ forkFromMessage(threadId, messageId) {
199
+ return this.request("POST", `/agent/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
200
+ }
201
+ renameThread(id, title) {
202
+ return this.updateThread(id, {
203
+ title
204
+ });
205
+ }
206
+ /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
207
+ updateThread(id, patch) {
208
+ return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, patch);
209
+ }
210
+ /**
211
+ * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
212
+ * mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
213
+ * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
214
+ */
215
+ async uploadAttachment(file, options = {}) {
216
+ const formData = new FormData();
217
+ formData.append("file", file);
218
+ const response = await this.fetchImpl()(`${this.baseUrl()}/agent/attachments`, {
219
+ method: "POST",
220
+ headers: {
221
+ accept: "application/json",
222
+ ...await this.resolveHeaders()
223
+ },
224
+ body: formData,
225
+ ...this.credentials(),
226
+ ...options.signal !== void 0 ? {
227
+ signal: options.signal
228
+ } : {}
229
+ });
230
+ return this.handleResponse(response, "POST", "/agent/attachments");
231
+ }
232
+ promoteThread(id) {
233
+ return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
234
+ }
235
+ truncateFromMessage(threadId, messageId) {
236
+ return this.request("DELETE", `/agent/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`);
237
+ }
238
+ getQuotaToday() {
239
+ return this.request("GET", "/agent/quota/today");
240
+ }
241
+ cancelStream(runId) {
242
+ return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
243
+ }
244
+ /**
245
+ * `remember` approves later calls of the same tool in the same thread; `via` names the surface
246
+ * the decision came through (the server records `'web'` when omitted).
247
+ */
248
+ approveToolCall(input) {
249
+ return this.request("POST", "/agent/tool-call/approve", input);
250
+ }
251
+ rejectToolCall(input) {
252
+ return this.request("POST", "/agent/tool-call/reject", input);
253
+ }
254
+ /**
255
+ * Settle a parked question set. `answers` is questionId → chosen option values; a question left
256
+ * out takes the pre-picked default the request carried, resolved server-side against the request
257
+ * the run already holds. Omit the whole object and the user has confirmed every pre-picked
258
+ * answer — which is the point of the surface, so it is a valid submission rather than a blank.
259
+ */
260
+ answerToolCall(input) {
261
+ return this.request("POST", "/agent/tool-call/answer", input);
262
+ }
263
+ /**
264
+ * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
265
+ * values a confirmation would, and persists differently on purpose — only one of them is
266
+ * evidence the user chose them.
267
+ */
268
+ skipToolCall(input) {
269
+ return this.request("POST", "/agent/tool-call/skip", input);
270
+ }
271
+ fetchImpl() {
272
+ return this.options.fetch ?? globalThis.fetch;
273
+ }
274
+ baseUrl() {
275
+ return (this.options.baseUrl ?? "").replace(/\/$/, "");
276
+ }
277
+ async resolveHeaders() {
278
+ const dynamic = await this.options.getHeaders?.() ?? {};
279
+ return {
280
+ ...this.options.headers,
281
+ ...dynamic
282
+ };
283
+ }
284
+ credentials() {
285
+ return this.options.credentials !== void 0 ? {
286
+ credentials: this.options.credentials
287
+ } : {};
288
+ }
289
+ async request(method, path, body) {
290
+ const response = await this.fetchImpl()(`${this.baseUrl()}${path}`, {
291
+ method,
292
+ headers: {
293
+ accept: "application/json",
294
+ ...body !== void 0 ? {
295
+ "content-type": "application/json"
296
+ } : {},
297
+ ...await this.resolveHeaders()
298
+ },
299
+ ...body !== void 0 ? {
300
+ body: JSON.stringify(body)
301
+ } : {},
302
+ ...this.credentials()
303
+ });
304
+ return this.handleResponse(response, method, path);
305
+ }
306
+ async handleResponse(response, method, path) {
307
+ if (!response.ok) {
308
+ throw new AgentHttpError(response.status, method, path, response.statusText);
309
+ }
310
+ if (response.status === 204) return void 0;
311
+ const text = await response.text();
312
+ if (!text) return void 0;
313
+ return JSON.parse(text);
314
+ }
315
+ };
316
+ function streamResponse(response) {
317
+ const body = response.body;
318
+ const runId = response.headers?.get(HEADER_RUN_ID) ?? void 0;
319
+ const threadId = response.headers?.get(HEADER_THREAD_ID) ?? void 0;
320
+ return {
321
+ body,
322
+ ...runId ? {
323
+ runId
324
+ } : {},
325
+ ...threadId ? {
326
+ threadId
327
+ } : {}
328
+ };
329
+ }
330
+ __name(streamResponse, "streamResponse");
331
+
86
332
  // src/reasoning/timing.ts
87
333
  var import_react = require("react");
88
334
  var REASONING_METADATA_KEY = "agent";
@@ -143,8 +389,11 @@ function formatElapsed(ms) {
143
389
  __name(formatElapsed, "formatElapsed");
144
390
 
145
391
  // src/agent-chat-transport.ts
146
- var HEADER_RUN_ID = "x-agent-run-id";
147
- var HEADER_THREAD_ID = "x-agent-thread-id";
392
+ var DEFAULT_RECONNECT = {
393
+ maxAttempts: 6,
394
+ baseDelayMs: 500,
395
+ maxDelayMs: 8e3
396
+ };
148
397
  var ASK_TOOL_NAME = "ask";
149
398
  var AgentChatTransport = class {
150
399
  static {
@@ -161,8 +410,26 @@ var AgentChatTransport = class {
161
410
  * several entries under each id — which React renders as duplicate keys.
162
411
  */
163
412
  attemptLive = false;
413
+ backend;
164
414
  constructor(options = {}) {
165
415
  this.options = options;
416
+ this.backend = options.backend ?? new AgentClient({
417
+ ...options.baseUrl !== void 0 ? {
418
+ baseUrl: options.baseUrl
419
+ } : {},
420
+ ...options.headers !== void 0 ? {
421
+ headers: options.headers
422
+ } : {},
423
+ ...options.getHeaders !== void 0 ? {
424
+ getHeaders: options.getHeaders
425
+ } : {},
426
+ ...options.credentials !== void 0 ? {
427
+ credentials: options.credentials
428
+ } : {},
429
+ ...options.fetch !== void 0 ? {
430
+ fetch: options.fetch
431
+ } : {}
432
+ });
166
433
  }
167
434
  /** Run id of the most recent stream — HITL approve/reject target this. */
168
435
  get runId() {
@@ -195,26 +462,25 @@ var AgentChatTransport = class {
195
462
  ...options.body ?? {},
196
463
  message
197
464
  };
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
465
+ const headers = headerRecord(options.headers);
466
+ const response = await this.backend.openChatStream({
467
+ body,
468
+ ...headers !== void 0 ? {
469
+ headers
470
+ } : {},
471
+ ...options.abortSignal ? {
472
+ signal: options.abortSignal
473
+ } : {}
474
+ });
475
+ this.captureHeaderMeta(response);
476
+ return this.toChunkStream(response, {
477
+ ...headers !== void 0 ? {
478
+ headers
208
479
  } : {},
209
480
  ...options.abortSignal ? {
210
481
  signal: options.abortSignal
211
482
  } : {}
212
483
  });
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
484
  } catch (error) {
219
485
  this.attemptLive = false;
220
486
  throw error;
@@ -227,51 +493,41 @@ var AgentChatTransport = class {
227
493
  if (runId === void 0) return null;
228
494
  this.attemptLive = true;
229
495
  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
496
+ const headers = headerRecord(options.headers);
497
+ const response = await this.backend.resumeChatStream({
498
+ runId,
499
+ ...headers !== void 0 ? {
500
+ headers
238
501
  } : {}
239
502
  });
240
- if (response.status === 404) {
503
+ if (response === null) {
241
504
  this.attemptLive = false;
242
505
  return null;
243
506
  }
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);
507
+ this.captureHeaderMeta(response);
508
+ return this.toChunkStream(response, {
509
+ runId,
510
+ ...headers !== void 0 ? {
511
+ headers
512
+ } : {}
513
+ });
249
514
  } catch (error) {
250
515
  this.attemptLive = false;
251
516
  throw error;
252
517
  }
253
518
  }
254
- fetchImpl() {
255
- return this.options.fetch ?? globalThis.fetch;
519
+ captureHeaderMeta(response) {
520
+ if (response.runId) this.currentRunId = response.runId;
521
+ if (response.threadId) this.currentThreadId = response.threadId;
256
522
  }
257
- baseUrl() {
258
- return (this.options.baseUrl ?? "").replace(/\/$/, "");
259
- }
260
- async resolveHeaders(perRequest) {
261
- const dynamic = await this.options.getHeaders?.() ?? {};
262
- const extra = perRequest instanceof Headers ? Object.fromEntries(perRequest.entries()) : perRequest ?? {};
523
+ reconnectPolicy() {
524
+ const configured = this.options.reconnect;
525
+ if (configured === false) return null;
263
526
  return {
264
- ...this.options.headers,
265
- ...dynamic,
266
- ...extra
527
+ ...DEFAULT_RECONNECT,
528
+ ...configured
267
529
  };
268
530
  }
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
531
  recordMeta(meta) {
276
532
  this.currentRunId = meta.runId;
277
533
  this.currentThreadId = meta.threadId;
@@ -290,10 +546,66 @@ var AgentChatTransport = class {
290
546
  * call plus its tool execution; text and reasoning open lazily and close at the step boundary, so
291
547
  * tool-call cards (input streaming → output) render live between the prose.
292
548
  */
293
- toChunkStream(source) {
294
- const reader = source.getReader();
295
- const decoder = new TextDecoder();
549
+ toChunkStream(source, context) {
550
+ let reader = source.body.getReader();
551
+ let decoder = new TextDecoder();
296
552
  let buffer = "";
553
+ let streamRunId = source.runId ?? context.runId;
554
+ let lastSeq;
555
+ let cancelled = false;
556
+ const backend = this.backend;
557
+ const policy = this.reconnectPolicy();
558
+ const notify = /* @__PURE__ */ __name((state) => this.options.onConnectionChange?.(state), "notify");
559
+ const aborted = /* @__PURE__ */ __name(() => cancelled || context.signal?.aborted === true, "aborted");
560
+ const canResume = /* @__PURE__ */ __name(() => policy !== null && lastSeq !== void 0 && streamRunId !== void 0 && !aborted(), "canResume");
561
+ const resume = /* @__PURE__ */ __name(async () => {
562
+ const runId = streamRunId;
563
+ const after = lastSeq ?? 0;
564
+ const { maxAttempts, baseDelayMs, maxDelayMs } = policy;
565
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
566
+ notify({
567
+ status: "reconnecting",
568
+ runId,
569
+ attempt,
570
+ after
571
+ });
572
+ await delay(Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1)), context.signal);
573
+ if (aborted()) return "aborted";
574
+ try {
575
+ const next = await backend.resumeChatStream({
576
+ runId,
577
+ after,
578
+ ...context.headers !== void 0 ? {
579
+ headers: context.headers
580
+ } : {},
581
+ ...context.signal !== void 0 ? {
582
+ signal: context.signal
583
+ } : {}
584
+ });
585
+ if (next === null) {
586
+ notify({
587
+ status: "gone",
588
+ runId
589
+ });
590
+ return "gone";
591
+ }
592
+ reader = next.body.getReader();
593
+ decoder = new TextDecoder();
594
+ buffer = "";
595
+ notify({
596
+ status: "live"
597
+ });
598
+ return "resumed";
599
+ } catch {
600
+ if (aborted()) return "aborted";
601
+ }
602
+ }
603
+ notify({
604
+ status: "failed",
605
+ runId
606
+ });
607
+ return "failed";
608
+ }, "resume");
297
609
  let started = false;
298
610
  let stepOpen = false;
299
611
  let stepIndex = 0;
@@ -304,7 +616,10 @@ var AgentChatTransport = class {
304
616
  let reasoningSeq = 0;
305
617
  const announced = /* @__PURE__ */ new Set();
306
618
  const toolMetadata = /* @__PURE__ */ new Map();
307
- const record = /* @__PURE__ */ __name((meta) => this.recordMeta(meta), "record");
619
+ const record = /* @__PURE__ */ __name((meta) => {
620
+ streamRunId = meta.runId;
621
+ this.recordMeta(meta);
622
+ }, "record");
308
623
  const endAttempt = /* @__PURE__ */ __name(() => {
309
624
  this.attemptLive = false;
310
625
  }, "endAttempt");
@@ -313,7 +628,12 @@ var AgentChatTransport = class {
313
628
  function ensureStarted() {
314
629
  if (started) return;
315
630
  started = true;
316
- controller.enqueue({
631
+ controller.enqueue(streamRunId !== void 0 ? {
632
+ type: "start",
633
+ messageMetadata: {
634
+ runId: streamRunId
635
+ }
636
+ } : {
317
637
  type: "start"
318
638
  });
319
639
  }
@@ -569,33 +889,67 @@ var AgentChatTransport = class {
569
889
  endAttempt();
570
890
  }
571
891
  __name(finish, "finish");
892
+ function fail(errorText, start = true) {
893
+ if (start) ensureStarted();
894
+ controller.enqueue({
895
+ type: "error",
896
+ errorText
897
+ });
898
+ controller.close();
899
+ endAttempt();
900
+ }
901
+ __name(fail, "fail");
902
+ async function recover(error) {
903
+ if (!canResume()) {
904
+ if (error === void 0) {
905
+ finish();
906
+ } else {
907
+ fail(error instanceof Error ? error.message : "Agent stream error", false);
908
+ }
909
+ return false;
910
+ }
911
+ const outcome = await resume();
912
+ if (outcome === "resumed") return true;
913
+ if (outcome === "failed") {
914
+ fail("Lost the connection to the agent stream");
915
+ } else {
916
+ finish();
917
+ }
918
+ return false;
919
+ }
920
+ __name(recover, "recover");
572
921
  try {
573
922
  while (true) {
574
- const { done, value } = await reader.read();
575
- if (done) {
576
- finish();
923
+ let result;
924
+ try {
925
+ result = await reader.read();
926
+ } catch (error) {
927
+ if (await recover(error)) continue;
928
+ return;
929
+ }
930
+ if (result.done) {
931
+ if (await recover(void 0)) continue;
577
932
  return;
578
933
  }
579
- buffer += decoder.decode(value, {
934
+ buffer += decoder.decode(result.value, {
580
935
  stream: true
581
936
  });
582
937
  let separator = buffer.indexOf("\n\n");
583
938
  while (separator !== -1) {
584
939
  const rawEvent = buffer.slice(0, separator);
585
940
  buffer = buffer.slice(separator + 2);
941
+ separator = buffer.indexOf("\n\n");
586
942
  const frame = parseSseFrame(rawEvent);
943
+ if (frame.id !== void 0) {
944
+ if (lastSeq !== void 0 && frame.id <= lastSeq) continue;
945
+ lastSeq = frame.id;
946
+ }
587
947
  if (frame.event === "done") {
588
948
  finish();
589
949
  return;
590
950
  }
591
951
  if (frame.event === "error") {
592
- ensureStarted();
593
- controller.enqueue({
594
- type: "error",
595
- errorText: parseErrorText(frame.data)
596
- });
597
- controller.close();
598
- endAttempt();
952
+ fail(parseErrorText(frame.data));
599
953
  return;
600
954
  }
601
955
  if (frame.event === "meta") {
@@ -605,19 +959,14 @@ var AgentChatTransport = class {
605
959
  const event = parseEvent(frame.data);
606
960
  if (event) emit(event);
607
961
  }
608
- separator = buffer.indexOf("\n\n");
609
962
  }
610
963
  }
611
964
  } catch (error) {
612
- controller.enqueue({
613
- type: "error",
614
- errorText: error instanceof Error ? error.message : "Agent stream error"
615
- });
616
- controller.close();
617
- endAttempt();
965
+ fail(error instanceof Error ? error.message : "Agent stream error", false);
618
966
  }
619
967
  },
620
968
  cancel() {
969
+ cancelled = true;
621
970
  void reader.cancel();
622
971
  endAttempt();
623
972
  }
@@ -626,17 +975,22 @@ var AgentChatTransport = class {
626
975
  };
627
976
  function parseSseFrame(raw) {
628
977
  let event;
978
+ let id;
629
979
  const dataLines = [];
630
980
  for (const line of raw.split("\n")) {
631
981
  if (line.startsWith("event:")) {
632
982
  event = line.slice("event:".length).trim();
983
+ } else if (line.startsWith("id:")) {
984
+ const parsed = Number(line.slice("id:".length).trim());
985
+ if (Number.isSafeInteger(parsed) && parsed > 0) id = parsed;
633
986
  } else if (line.startsWith("data:")) {
634
987
  dataLines.push(line.slice("data:".length).trimStart());
635
988
  }
636
989
  }
637
990
  return {
638
991
  event,
639
- data: dataLines.length > 0 ? dataLines.join("\n") : void 0
992
+ data: dataLines.length > 0 ? dataLines.join("\n") : void 0,
993
+ id
640
994
  };
641
995
  }
642
996
  __name(parseSseFrame, "parseSseFrame");
@@ -677,15 +1031,282 @@ function parseEvent(data) {
677
1031
  }
678
1032
  return null;
679
1033
  }
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;
1034
+ __name(parseEvent, "parseEvent");
1035
+ function extractText(message) {
1036
+ let text = "";
1037
+ for (const part of message.parts ?? []) {
1038
+ if (part.type === "text") text += part.text;
1039
+ }
1040
+ return text;
1041
+ }
1042
+ __name(extractText, "extractText");
1043
+ function headerRecord(headers) {
1044
+ if (headers === void 0) return void 0;
1045
+ return headers instanceof Headers ? Object.fromEntries(headers.entries()) : headers;
1046
+ }
1047
+ __name(headerRecord, "headerRecord");
1048
+ function delay(ms, signal) {
1049
+ if (ms <= 0 || signal?.aborted) return Promise.resolve();
1050
+ return new Promise((resolve) => {
1051
+ const timer = setTimeout(done, ms);
1052
+ function done() {
1053
+ clearTimeout(timer);
1054
+ signal?.removeEventListener("abort", done);
1055
+ resolve();
1056
+ }
1057
+ __name(done, "done");
1058
+ signal?.addEventListener("abort", done, {
1059
+ once: true
1060
+ });
1061
+ });
1062
+ }
1063
+ __name(delay, "delay");
1064
+
1065
+ // src/backend.ts
1066
+ var AgentBackendUnsupportedError = class extends Error {
1067
+ static {
1068
+ __name(this, "AgentBackendUnsupportedError");
1069
+ }
1070
+ method;
1071
+ constructor(method) {
1072
+ super(`The agent backend does not implement ${method}()`), this.method = method;
1073
+ this.name = "AgentBackendUnsupportedError";
1074
+ }
1075
+ };
1076
+ function requireBackendMethod(backend, name) {
1077
+ const method = backend[name];
1078
+ if (typeof method !== "function") {
1079
+ throw new AgentBackendUnsupportedError(name);
1080
+ }
1081
+ return method.bind(backend);
1082
+ }
1083
+ __name(requireBackendMethod, "requireBackendMethod");
1084
+
1085
+ // src/feedback/use-message-feedback.ts
1086
+ var import_react2 = require("react");
1087
+ function useMessageFeedback(options) {
1088
+ const latest = (0, import_react2.useRef)(options);
1089
+ latest.current = options;
1090
+ const [local, setLocal] = (0, import_react2.useState)(/* @__PURE__ */ new Map());
1091
+ const [pending, setPending] = (0, import_react2.useState)(/* @__PURE__ */ new Set());
1092
+ const [error, setError] = (0, import_react2.useState)(null);
1093
+ const localRef = (0, import_react2.useRef)(local);
1094
+ localRef.current = local;
1095
+ const resolved = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
1096
+ const feedbackOf = (0, import_react2.useCallback)((message) => {
1097
+ const mine = local.get(message.id);
1098
+ if (mine !== void 0) return mine.feedback;
1099
+ return metadataOf(message).feedback ?? null;
1100
+ }, [
1101
+ local
1102
+ ]);
1103
+ const isPending = (0, import_react2.useCallback)((message) => pending.has(message.id), [
1104
+ pending
1105
+ ]);
1106
+ const storedIdFor = (0, import_react2.useCallback)(async (message) => {
1107
+ const runId = metadataOf(message).runId;
1108
+ if (runId === void 0) return message.id;
1109
+ const known = resolved.current.get(message.id);
1110
+ if (known !== void 0) return known;
1111
+ const { backend, threadId } = latest.current;
1112
+ const thread = typeof threadId === "function" ? threadId() : threadId;
1113
+ if (thread === void 0) {
1114
+ throw new Error("useMessageFeedback: a streamed message needs `threadId` to be rated");
1115
+ }
1116
+ const detail = await backend.getThread(thread);
1117
+ const row = [
1118
+ ...detail.messages
1119
+ ].reverse().find((candidate) => candidate.role === "assistant" && candidate.runId === runId);
1120
+ if (row === void 0) {
1121
+ throw new Error(`useMessageFeedback: run ${runId} has no persisted answer yet`);
1122
+ }
1123
+ resolved.current.set(message.id, row.id);
1124
+ return row.id;
1125
+ }, []);
1126
+ const rate = (0, import_react2.useCallback)(async (message, value, comment) => {
1127
+ const setFeedback = requireBackendMethod(latest.current.backend, "setMessageFeedback");
1128
+ const had = localRef.current.get(message.id);
1129
+ const optimistic = value === null ? null : {
1130
+ value,
1131
+ ...comment !== void 0 && comment.trim().length > 0 ? {
1132
+ comment: comment.trim()
1133
+ } : {},
1134
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1135
+ };
1136
+ setLocal((current) => new Map(current).set(message.id, {
1137
+ feedback: optimistic
1138
+ }));
1139
+ setPending((current) => new Set(current).add(message.id));
1140
+ try {
1141
+ const storedId = await storedIdFor(message);
1142
+ const { feedback } = await setFeedback(storedId, {
1143
+ value,
1144
+ ...comment !== void 0 ? {
1145
+ comment
1146
+ } : {}
1147
+ });
1148
+ setLocal((current) => new Map(current).set(message.id, {
1149
+ feedback
1150
+ }));
1151
+ setError(null);
1152
+ } catch (caught) {
1153
+ setLocal((current) => {
1154
+ const next = new Map(current);
1155
+ if (had === void 0) next.delete(message.id);
1156
+ else next.set(message.id, had);
1157
+ return next;
1158
+ });
1159
+ const failure = caught instanceof Error ? caught : new Error(String(caught));
1160
+ setError(failure);
1161
+ throw failure;
1162
+ } finally {
1163
+ setPending((current) => {
1164
+ const next = new Set(current);
1165
+ next.delete(message.id);
1166
+ return next;
1167
+ });
1168
+ }
1169
+ }, [
1170
+ storedIdFor
1171
+ ]);
1172
+ const toggle = (0, import_react2.useCallback)((message, value) => {
1173
+ const mine = localRef.current.get(message.id);
1174
+ const current = mine !== void 0 ? mine.feedback : metadataOf(message).feedback ?? null;
1175
+ return rate(message, current?.value === value ? null : value);
1176
+ }, [
1177
+ rate
1178
+ ]);
1179
+ return {
1180
+ feedbackOf,
1181
+ isPending,
1182
+ rate,
1183
+ toggle,
1184
+ error
1185
+ };
1186
+ }
1187
+ __name(useMessageFeedback, "useMessageFeedback");
1188
+ function metadataOf(message) {
1189
+ const metadata = message.metadata;
1190
+ return metadata !== null && typeof metadata === "object" ? metadata : {};
1191
+ }
1192
+ __name(metadataOf, "metadataOf");
1193
+
1194
+ // src/threads/threads-events.ts
1195
+ var listeners = /* @__PURE__ */ new WeakMap();
1196
+ function notifyThreads(backend, event) {
1197
+ for (const listener of listeners.get(backend) ?? []) {
1198
+ listener(event);
685
1199
  }
686
- return text;
687
1200
  }
688
- __name(extractText, "extractText");
1201
+ __name(notifyThreads, "notifyThreads");
1202
+ function onThreadsEvent(backend, listener) {
1203
+ let set = listeners.get(backend);
1204
+ if (set === void 0) {
1205
+ set = /* @__PURE__ */ new Set();
1206
+ listeners.set(backend, set);
1207
+ }
1208
+ set.add(listener);
1209
+ return () => {
1210
+ set.delete(listener);
1211
+ };
1212
+ }
1213
+ __name(onThreadsEvent, "onThreadsEvent");
1214
+
1215
+ // src/threads/use-threads.ts
1216
+ var import_react3 = require("react");
1217
+ function useThreads(options) {
1218
+ const { backend, enabled = true } = options;
1219
+ const [threads, setThreads] = (0, import_react3.useState)([]);
1220
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(enabled);
1221
+ const [error, setError] = (0, import_react3.useState)(null);
1222
+ const threadsRef = (0, import_react3.useRef)(threads);
1223
+ threadsRef.current = threads;
1224
+ const generation = (0, import_react3.useRef)(0);
1225
+ const refresh = (0, import_react3.useCallback)(async () => {
1226
+ const mine = ++generation.current;
1227
+ setIsLoading(true);
1228
+ try {
1229
+ const list = await backend.listThreads();
1230
+ if (mine !== generation.current) return;
1231
+ setThreads(list);
1232
+ setError(null);
1233
+ } catch (caught) {
1234
+ if (mine !== generation.current) return;
1235
+ setError(caught instanceof Error ? caught : new Error(String(caught)));
1236
+ } finally {
1237
+ if (mine === generation.current) setIsLoading(false);
1238
+ }
1239
+ }, [
1240
+ backend
1241
+ ]);
1242
+ (0, import_react3.useEffect)(() => {
1243
+ if (!enabled) return;
1244
+ void refresh();
1245
+ return onThreadsEvent(backend, (event) => {
1246
+ if (event.type === "title") {
1247
+ setThreads((current) => current.map((thread) => thread.id === event.threadId ? {
1248
+ ...thread,
1249
+ title: event.title
1250
+ } : thread));
1251
+ } else if (event.type === "removed") {
1252
+ setThreads((current) => current.filter((thread) => thread.id !== event.threadId));
1253
+ } else {
1254
+ void refresh();
1255
+ }
1256
+ });
1257
+ }, [
1258
+ backend,
1259
+ enabled,
1260
+ refresh
1261
+ ]);
1262
+ const rename = (0, import_react3.useCallback)(async (id, title) => {
1263
+ const before = threadsRef.current;
1264
+ setThreads((current) => current.map((thread) => thread.id === id ? {
1265
+ ...thread,
1266
+ title
1267
+ } : thread));
1268
+ try {
1269
+ await backend.updateThread(id, {
1270
+ title
1271
+ });
1272
+ notifyThreads(backend, {
1273
+ type: "title",
1274
+ threadId: id,
1275
+ title
1276
+ });
1277
+ } catch (caught) {
1278
+ setThreads(before);
1279
+ throw caught;
1280
+ }
1281
+ }, [
1282
+ backend
1283
+ ]);
1284
+ const remove = (0, import_react3.useCallback)(async (id) => {
1285
+ const before = threadsRef.current;
1286
+ setThreads((current) => current.filter((thread) => thread.id !== id));
1287
+ try {
1288
+ await backend.deleteThread(id);
1289
+ notifyThreads(backend, {
1290
+ type: "removed",
1291
+ threadId: id
1292
+ });
1293
+ } catch (caught) {
1294
+ setThreads(before);
1295
+ throw caught;
1296
+ }
1297
+ }, [
1298
+ backend
1299
+ ]);
1300
+ return {
1301
+ threads,
1302
+ isLoading,
1303
+ error,
1304
+ refresh,
1305
+ rename,
1306
+ remove
1307
+ };
1308
+ }
1309
+ __name(useThreads, "useThreads");
689
1310
 
690
1311
  // src/background-runs.ts
691
1312
  function asReceipt(output) {
@@ -734,170 +1355,11 @@ function backgroundRunsFromThread(messages) {
734
1355
  }
735
1356
  __name(backgroundRunsFromThread, "backgroundRunsFromThread");
736
1357
 
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
1358
  // src/components/chat-input.tsx
897
- var import_react3 = __toESM(require("react"), 1);
1359
+ var import_react5 = __toESM(require("react"), 1);
898
1360
 
899
1361
  // src/components/use-speech-recognition.ts
900
- var import_react2 = require("react");
1362
+ var import_react4 = require("react");
901
1363
  function getRecognitionCtor() {
902
1364
  if (typeof window === "undefined") return null;
903
1365
  const scope = window;
@@ -905,15 +1367,15 @@ function getRecognitionCtor() {
905
1367
  }
906
1368
  __name(getRecognitionCtor, "getRecognitionCtor");
907
1369
  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(), []);
1370
+ const recognitionRef = (0, import_react4.useRef)(null);
1371
+ const [isListening, setIsListening] = (0, import_react4.useState)(false);
1372
+ const ctor = (0, import_react4.useMemo)(() => getRecognitionCtor(), []);
911
1373
  const isSupported = ctor !== null;
912
- const finalRef = (0, import_react2.useRef)(onFinalTranscript);
913
- const interimRef = (0, import_react2.useRef)(onInterimTranscript);
1374
+ const finalRef = (0, import_react4.useRef)(onFinalTranscript);
1375
+ const interimRef = (0, import_react4.useRef)(onInterimTranscript);
914
1376
  finalRef.current = onFinalTranscript;
915
1377
  interimRef.current = onInterimTranscript;
916
- (0, import_react2.useEffect)(() => {
1378
+ (0, import_react4.useEffect)(() => {
917
1379
  if (!ctor) return;
918
1380
  const recognition = new ctor();
919
1381
  recognition.lang = lang;
@@ -952,7 +1414,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
952
1414
  ctor,
953
1415
  lang
954
1416
  ]);
955
- const start = (0, import_react2.useCallback)(() => {
1417
+ const start = (0, import_react4.useCallback)(() => {
956
1418
  const recognition = recognitionRef.current;
957
1419
  if (!recognition) return;
958
1420
  try {
@@ -962,7 +1424,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
962
1424
  console.warn("SpeechRecognition start failed", error);
963
1425
  }
964
1426
  }, []);
965
- const stop = (0, import_react2.useCallback)(() => {
1427
+ const stop = (0, import_react4.useCallback)(() => {
966
1428
  const recognition = recognitionRef.current;
967
1429
  if (!recognition) return;
968
1430
  try {
@@ -981,8 +1443,8 @@ __name(useSpeechRecognition, "useSpeechRecognition");
981
1443
 
982
1444
  // src/components/chat-input.tsx
983
1445
  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)("");
1446
+ const [value, setValue] = (0, import_react5.useState)("");
1447
+ const [interim, setInterim] = (0, import_react5.useState)("");
986
1448
  const speech = useSpeechRecognition({
987
1449
  lang: speechLang,
988
1450
  onFinalTranscript: /* @__PURE__ */ __name((text) => {
@@ -993,7 +1455,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
993
1455
  }, "onFinalTranscript"),
994
1456
  onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
995
1457
  });
996
- (0, import_react3.useEffect)(() => {
1458
+ (0, import_react5.useEffect)(() => {
997
1459
  if (!speech.isListening) setInterim("");
998
1460
  }, [
999
1461
  speech.isListening
@@ -1021,9 +1483,9 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
1021
1483
  const displayValue = speech.isListening && interim ? `${value}${value && !value.endsWith(" ") ? " " : ""}${interim}` : value;
1022
1484
  const showMic = enableSpeech && speech.isSupported;
1023
1485
  const showStop = isStreaming && onStop !== void 0;
1024
- return /* @__PURE__ */ import_react3.default.createElement("div", {
1486
+ return /* @__PURE__ */ import_react5.default.createElement("div", {
1025
1487
  className
1026
- }, /* @__PURE__ */ import_react3.default.createElement("textarea", {
1488
+ }, /* @__PURE__ */ import_react5.default.createElement("textarea", {
1027
1489
  className: textareaClassName,
1028
1490
  rows,
1029
1491
  value: displayValue,
@@ -1034,17 +1496,17 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
1034
1496
  }, "onChange"),
1035
1497
  onKeyDown: handleKeyDown,
1036
1498
  placeholder: placeholder ?? "Ask something (Enter to send, Shift+Enter for newline)"
1037
- }), /* @__PURE__ */ import_react3.default.createElement("button", {
1499
+ }), /* @__PURE__ */ import_react5.default.createElement("button", {
1038
1500
  type: "button",
1039
1501
  className: sendButtonClassName,
1040
1502
  onClick: submit,
1041
1503
  disabled: disabled || !value.trim()
1042
- }, sendLabel), showStop ? /* @__PURE__ */ import_react3.default.createElement("button", {
1504
+ }, sendLabel), showStop ? /* @__PURE__ */ import_react5.default.createElement("button", {
1043
1505
  type: "button",
1044
1506
  className: stopButtonClassName,
1045
1507
  onClick: /* @__PURE__ */ __name(() => void onStop(), "onClick"),
1046
1508
  "aria-label": "Stop generating"
1047
- }, stopLabel) : null, showMic ? /* @__PURE__ */ import_react3.default.createElement("button", {
1509
+ }, stopLabel) : null, showMic ? /* @__PURE__ */ import_react5.default.createElement("button", {
1048
1510
  type: "button",
1049
1511
  className: micButtonClassName,
1050
1512
  onClick: toggleMic,
@@ -1057,10 +1519,10 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
1057
1519
  __name(ChatInput, "ChatInput");
1058
1520
 
1059
1521
  // src/components/message-item.tsx
1060
- var import_react6 = __toESM(require("react"), 1);
1522
+ var import_react8 = __toESM(require("react"), 1);
1061
1523
 
1062
1524
  // src/transcript/use-chat-transcript.ts
1063
- var import_react5 = require("react");
1525
+ var import_react7 = require("react");
1064
1526
 
1065
1527
  // src/transcript/model.ts
1066
1528
  var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
@@ -2037,11 +2499,11 @@ function formatCostUsd(cost) {
2037
2499
  __name(formatCostUsd, "formatCostUsd");
2038
2500
 
2039
2501
  // src/transcript/use-stick-to-bottom.ts
2040
- var import_react4 = require("react");
2502
+ var import_react6 = require("react");
2041
2503
  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)(() => {
2504
+ const containerRef = (0, import_react6.useRef)(null);
2505
+ const [isAtBottom, setIsAtBottom] = (0, import_react6.useState)(true);
2506
+ const measure = (0, import_react6.useCallback)(() => {
2045
2507
  const element = containerRef.current;
2046
2508
  if (!element) {
2047
2509
  return;
@@ -2051,7 +2513,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2051
2513
  }, [
2052
2514
  threshold
2053
2515
  ]);
2054
- const scrollToBottom = (0, import_react4.useCallback)(() => {
2516
+ const scrollToBottom = (0, import_react6.useCallback)(() => {
2055
2517
  const element = containerRef.current;
2056
2518
  if (!element) {
2057
2519
  return;
@@ -2059,7 +2521,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2059
2521
  element.scrollTop = element.scrollHeight;
2060
2522
  setIsAtBottom(true);
2061
2523
  }, []);
2062
- const setContainer = (0, import_react4.useCallback)((element) => {
2524
+ const setContainer = (0, import_react6.useCallback)((element) => {
2063
2525
  containerRef.current = element;
2064
2526
  if (element) {
2065
2527
  measure();
@@ -2067,7 +2529,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2067
2529
  }, [
2068
2530
  measure
2069
2531
  ]);
2070
- (0, import_react4.useEffect)(() => {
2532
+ (0, import_react6.useEffect)(() => {
2071
2533
  if (isAtBottom) {
2072
2534
  scrollToBottom();
2073
2535
  }
@@ -2076,7 +2538,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2076
2538
  isAtBottom,
2077
2539
  scrollToBottom
2078
2540
  ]);
2079
- const getContainerProps = (0, import_react4.useCallback)(() => ({
2541
+ const getContainerProps = (0, import_react6.useCallback)(() => ({
2080
2542
  ref: setContainer,
2081
2543
  onScroll: measure
2082
2544
  }), [
@@ -2098,14 +2560,14 @@ var DEFAULT_LOAD_EARLIER_STEP = 50;
2098
2560
  var DEFAULT_COPY_RESET_MS = 1500;
2099
2561
  function useChatTranscript(options) {
2100
2562
  const { messages, status, initialVisibleCount = DEFAULT_VISIBLE_COUNT, loadEarlierStep = DEFAULT_LOAD_EARLIER_STEP, followUps, stickToBottomThreshold } = options;
2101
- const latest = (0, import_react5.useRef)(options);
2563
+ const latest = (0, import_react7.useRef)(options);
2102
2564
  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";
2565
+ const [visibleCount, setVisibleCount] = (0, import_react7.useState)(initialVisibleCount);
2566
+ const [stopRequested, setStopRequested] = (0, import_react7.useState)(false);
2567
+ const isStreaming = status === "streaming" || status === "reconnecting";
2106
2568
  const isBusy = status === "submitted" || isStreaming;
2107
2569
  const isEmpty = messages.length === 0;
2108
- (0, import_react5.useEffect)(() => {
2570
+ (0, import_react7.useEffect)(() => {
2109
2571
  if (!isBusy) {
2110
2572
  setStopRequested(false);
2111
2573
  }
@@ -2122,16 +2584,16 @@ function useChatTranscript(options) {
2122
2584
  streamingMessageId,
2123
2585
  options
2124
2586
  });
2125
- const stop = (0, import_react5.useCallback)(() => {
2587
+ const stop = (0, import_react7.useCallback)(() => {
2126
2588
  const current = latest.current;
2127
- if (current.status !== "submitted" && current.status !== "streaming") {
2589
+ if (current.status !== "submitted" && current.status !== "streaming" && current.status !== "reconnecting") {
2128
2590
  return;
2129
2591
  }
2130
2592
  setStopRequested(true);
2131
2593
  void current.onStop?.();
2132
2594
  }, []);
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), []);
2595
+ const loadEarlier = (0, import_react7.useCallback)(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
2596
+ const showAll = (0, import_react7.useCallback)(() => setVisibleCount(latest.current.messages.length), []);
2135
2597
  const scroll = useStickToBottom({
2136
2598
  contentKey: contentSignature(messages),
2137
2599
  ...stickToBottomThreshold !== void 0 ? {
@@ -2167,7 +2629,7 @@ function useChatTranscript(options) {
2167
2629
  __name(useChatTranscript, "useChatTranscript");
2168
2630
  function useTranscriptItem(options) {
2169
2631
  const { message, isStreaming = false } = options;
2170
- const messagesRef = (0, import_react5.useRef)([
2632
+ const messagesRef = (0, import_react7.useRef)([
2171
2633
  message
2172
2634
  ]);
2173
2635
  if (messagesRef.current[0] !== message) {
@@ -2185,13 +2647,13 @@ function useTranscriptItem(options) {
2185
2647
  }
2186
2648
  __name(useTranscriptItem, "useTranscriptItem");
2187
2649
  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)({
2650
+ const [drafts, setDrafts] = (0, import_react7.useState)(() => /* @__PURE__ */ new Map());
2651
+ const [copied, setCopied] = (0, import_react7.useState)(() => /* @__PURE__ */ new Set());
2652
+ const [openReasoning, setOpenReasoning] = (0, import_react7.useState)(() => /* @__PURE__ */ new Map());
2653
+ const [picks, setPicks] = (0, import_react7.useState)(() => /* @__PURE__ */ new Map());
2654
+ const [settling, setSettling] = (0, import_react7.useState)(() => /* @__PURE__ */ new Map());
2655
+ const [settleErrors, setSettleErrors] = (0, import_react7.useState)(() => /* @__PURE__ */ new Map());
2656
+ const latest = (0, import_react7.useRef)({
2195
2657
  options,
2196
2658
  drafts,
2197
2659
  picks,
@@ -2203,23 +2665,23 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2203
2665
  picks,
2204
2666
  messages
2205
2667
  };
2206
- const copyTimers = (0, import_react5.useRef)(/* @__PURE__ */ new Map());
2207
- (0, import_react5.useEffect)(() => () => {
2668
+ const copyTimers = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
2669
+ (0, import_react7.useEffect)(() => () => {
2208
2670
  for (const timer of copyTimers.current.values()) {
2209
2671
  clearTimeout(timer);
2210
2672
  }
2211
2673
  copyTimers.current.clear();
2212
2674
  }, []);
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) => {
2675
+ const callbackCache = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
2676
+ const reasoningToggleCache = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
2677
+ const toggleReasoning = (0, import_react7.useCallback)((key, open) => {
2216
2678
  setOpenReasoning((current) => {
2217
2679
  const next = new Map(current);
2218
2680
  next.set(key, open ?? !(current.get(key) ?? false));
2219
2681
  return next;
2220
2682
  });
2221
2683
  }, []);
2222
- const stableToggle = (0, import_react5.useCallback)((key) => {
2684
+ const stableToggle = (0, import_react7.useCallback)((key) => {
2223
2685
  let cached = reasoningToggleCache.current.get(key);
2224
2686
  if (!cached) {
2225
2687
  cached = /* @__PURE__ */ __name((open) => toggleReasoning(key, open), "cached");
@@ -2229,10 +2691,10 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2229
2691
  }, [
2230
2692
  toggleReasoning
2231
2693
  ]);
2232
- const pick = (0, import_react5.useCallback)((toolCallId, questionId, values) => {
2694
+ const pick = (0, import_react7.useCallback)((toolCallId, questionId, values) => {
2233
2695
  setPicks((current) => new Map(current).set(pickKey(toolCallId, questionId), values));
2234
2696
  }, []);
2235
- const settle = (0, import_react5.useCallback)((toolCallId, action, send) => {
2697
+ const settle = (0, import_react7.useCallback)((toolCallId, action, send) => {
2236
2698
  setSettleErrors((current) => {
2237
2699
  if (!current.has(toolCallId)) {
2238
2700
  return current;
@@ -2256,7 +2718,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2256
2718
  failed(error);
2257
2719
  }
2258
2720
  }, []);
2259
- const answer = (0, import_react5.useCallback)((toolCallId) => {
2721
+ const answer = (0, import_react7.useCallback)((toolCallId) => {
2260
2722
  const current = latest.current;
2261
2723
  const onAnswer = current.options.onAnswer;
2262
2724
  if (!onAnswer) {
@@ -2266,7 +2728,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2266
2728
  }, [
2267
2729
  settle
2268
2730
  ]);
2269
- const skip = (0, import_react5.useCallback)((toolCallId) => {
2731
+ const skip = (0, import_react7.useCallback)((toolCallId) => {
2270
2732
  const onSkip = latest.current.options.onSkip;
2271
2733
  if (onSkip) {
2272
2734
  settle(toolCallId, "skip", () => onSkip(toolCallId));
@@ -2274,7 +2736,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2274
2736
  }, [
2275
2737
  settle
2276
2738
  ]);
2277
- const approve = (0, import_react5.useCallback)((toolCallId, options2) => {
2739
+ const approve = (0, import_react7.useCallback)((toolCallId, options2) => {
2278
2740
  const onApprove = latest.current.options.onApprove;
2279
2741
  if (onApprove) {
2280
2742
  settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
@@ -2282,7 +2744,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2282
2744
  }, [
2283
2745
  settle
2284
2746
  ]);
2285
- const reject = (0, import_react5.useCallback)((toolCallId) => {
2747
+ const reject = (0, import_react7.useCallback)((toolCallId) => {
2286
2748
  const onReject = latest.current.options.onReject;
2287
2749
  if (onReject) {
2288
2750
  settle(toolCallId, "reject", () => onReject(toolCallId));
@@ -2290,7 +2752,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2290
2752
  }, [
2291
2753
  settle
2292
2754
  ]);
2293
- const callbacksFor = (0, import_react5.useCallback)((id) => {
2755
+ const callbacksFor = (0, import_react7.useCallback)((id) => {
2294
2756
  const cached = callbackCache.current.get(id);
2295
2757
  if (cached) {
2296
2758
  return cached;
@@ -2532,7 +2994,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
2532
2994
  getCreatedAt: /* @__PURE__ */ __name(() => createdAt, "getCreatedAt")
2533
2995
  } : {}
2534
2996
  });
2535
- return /* @__PURE__ */ import_react6.default.createElement(MessageItemView, {
2997
+ return /* @__PURE__ */ import_react8.default.createElement(MessageItemView, {
2536
2998
  item,
2537
2999
  ...slots
2538
3000
  });
@@ -2540,51 +3002,51 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
2540
3002
  __name(MessageItem, "MessageItem");
2541
3003
  function MessageItemView({ item, ...slots }) {
2542
3004
  const { classNames } = slots;
2543
- const actionRow = /* @__PURE__ */ import_react6.default.createElement("div", {
3005
+ const actionRow = /* @__PURE__ */ import_react8.default.createElement("div", {
2544
3006
  className: classNames?.actions,
2545
3007
  "data-actions-for": item.role
2546
- }, item.copy.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
3008
+ }, item.copy.available ? /* @__PURE__ */ import_react8.default.createElement("button", {
2547
3009
  type: "button",
2548
3010
  onClick: item.copy.copy,
2549
3011
  className: classNames?.actionButton,
2550
3012
  title: item.copy.copied ? "Copied!" : "Copy message text"
2551
- }, item.copy.copied ? "Copied" : "Copy") : null, item.fork.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
3013
+ }, item.copy.copied ? "Copied" : "Copy") : null, item.fork.available ? /* @__PURE__ */ import_react8.default.createElement("button", {
2552
3014
  type: "button",
2553
3015
  onClick: item.fork.run,
2554
3016
  className: classNames?.actionButton,
2555
3017
  title: "Fork thread from this message"
2556
- }, "Fork") : null, item.regenerate.available ? /* @__PURE__ */ import_react6.default.createElement("button", {
3018
+ }, "Fork") : null, item.regenerate.available ? /* @__PURE__ */ import_react8.default.createElement("button", {
2557
3019
  type: "button",
2558
3020
  onClick: item.regenerate.run,
2559
3021
  className: classNames?.actionButton,
2560
3022
  title: "Regenerate response"
2561
- }, "Regenerate") : null, slots.meta, !item.isUser && item.usage ? /* @__PURE__ */ import_react6.default.createElement("span", {
3023
+ }, "Regenerate") : null, slots.meta, !item.isUser && item.usage ? /* @__PURE__ */ import_react8.default.createElement("span", {
2562
3024
  className: classNames?.usage,
2563
3025
  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", {
3026
+ }, item.usage.costLabel, " \xB7 ", item.usage.tokensLabel) : null, item.timestamp ? /* @__PURE__ */ import_react8.default.createElement("span", {
2565
3027
  className: classNames?.timestamp,
2566
3028
  title: item.timestamp.absolute
2567
3029
  }, item.timestamp.relative) : null);
2568
3030
  if (item.edit.available) {
2569
- return /* @__PURE__ */ import_react6.default.createElement(EditableUserBubble, {
3031
+ return /* @__PURE__ */ import_react8.default.createElement(EditableUserBubble, {
2570
3032
  item,
2571
3033
  slots,
2572
3034
  actions: actionRow
2573
3035
  });
2574
3036
  }
2575
3037
  const className = joinClasses(classNames?.root, classNames?.byRole?.[item.role]);
2576
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3038
+ return /* @__PURE__ */ import_react8.default.createElement("div", {
2577
3039
  className,
2578
3040
  "data-role": item.role,
2579
3041
  "data-streaming": item.isStreaming ? "true" : void 0
2580
- }, /* @__PURE__ */ import_react6.default.createElement("div", null, renderBlocks(item, slots)), actionRow);
3042
+ }, /* @__PURE__ */ import_react8.default.createElement("div", null, renderBlocks(item, slots)), actionRow);
2581
3043
  }
2582
3044
  __name(MessageItemView, "MessageItemView");
2583
3045
  function renderBlocks(item, slots) {
2584
3046
  const nodes = [];
2585
3047
  for (const block of item.blocks) {
2586
3048
  if (block.kind === "text") {
2587
- nodes.push(/* @__PURE__ */ import_react6.default.createElement("div", {
3049
+ nodes.push(/* @__PURE__ */ import_react8.default.createElement("div", {
2588
3050
  key: block.key,
2589
3051
  className: slots.classNames?.text
2590
3052
  }, slots.renderText ? slots.renderText(block.text, {
@@ -2605,7 +3067,7 @@ function renderBlocks(item, slots) {
2605
3067
  continue;
2606
3068
  }
2607
3069
  if (block.kind === "ui" && slots.renderUi) {
2608
- nodes.push(/* @__PURE__ */ import_react6.default.createElement("div", {
3070
+ nodes.push(/* @__PURE__ */ import_react8.default.createElement("div", {
2609
3071
  key: block.key,
2610
3072
  "data-slot": "ui",
2611
3073
  "data-component": block.component
@@ -2616,18 +3078,18 @@ function renderBlocks(item, slots) {
2616
3078
  }
2617
3079
  __name(renderBlocks, "renderBlocks");
2618
3080
  function renderFilesBlock(block, slots) {
2619
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3081
+ return /* @__PURE__ */ import_react8.default.createElement("div", {
2620
3082
  key: block.key,
2621
3083
  className: slots.classNames?.files,
2622
3084
  "data-files": "true"
2623
- }, slots.renderFiles ? slots.renderFiles(block.files) : block.files.map((file) => /* @__PURE__ */ import_react6.default.createElement("a", {
3085
+ }, slots.renderFiles ? slots.renderFiles(block.files) : block.files.map((file) => /* @__PURE__ */ import_react8.default.createElement("a", {
2624
3086
  key: file.url,
2625
3087
  href: file.url,
2626
3088
  target: "_blank",
2627
3089
  rel: "noreferrer",
2628
3090
  className: slots.classNames?.file,
2629
3091
  title: file.filename ?? file.mediaType
2630
- }, file.isImage ? /* @__PURE__ */ import_react6.default.createElement("img", {
3092
+ }, file.isImage ? /* @__PURE__ */ import_react8.default.createElement("img", {
2631
3093
  src: file.url,
2632
3094
  alt: file.filename ?? "image",
2633
3095
  className: slots.classNames?.fileImage
@@ -2635,17 +3097,17 @@ function renderFilesBlock(block, slots) {
2635
3097
  }
2636
3098
  __name(renderFilesBlock, "renderFilesBlock");
2637
3099
  function renderReasoningBlock(block, slots) {
2638
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3100
+ return /* @__PURE__ */ import_react8.default.createElement("div", {
2639
3101
  key: block.key,
2640
3102
  className: slots.classNames?.reasoning,
2641
3103
  "data-reasoning": "true",
2642
3104
  "data-streaming": block.isStreaming ? "true" : void 0
2643
- }, /* @__PURE__ */ import_react6.default.createElement("button", {
3105
+ }, /* @__PURE__ */ import_react8.default.createElement("button", {
2644
3106
  type: "button",
2645
3107
  className: slots.classNames?.reasoningToggle,
2646
3108
  onClick: /* @__PURE__ */ __name(() => block.toggle(), "onClick"),
2647
3109
  "aria-expanded": block.isOpen
2648
- }, slots.reasoningLabel ?? "Reasoning"), block.isOpen ? /* @__PURE__ */ import_react6.default.createElement("div", {
3110
+ }, slots.reasoningLabel ?? "Reasoning"), block.isOpen ? /* @__PURE__ */ import_react8.default.createElement("div", {
2649
3111
  className: slots.classNames?.reasoningText
2650
3112
  }, slots.renderReasoning ? slots.renderReasoning(block.text, {
2651
3113
  isStreaming: block.isStreaming,
@@ -2656,7 +3118,7 @@ __name(renderReasoningBlock, "renderReasoningBlock");
2656
3118
  function renderToolBlock(item, block, slots) {
2657
3119
  if (slots.renderToolGroup) {
2658
3120
  return [
2659
- /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, {
3121
+ /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, {
2660
3122
  key: block.key
2661
3123
  }, slots.renderToolGroup(block.parts, block.key))
2662
3124
  ];
@@ -2667,7 +3129,7 @@ function renderToolBlock(item, block, slots) {
2667
3129
  const renderToolPart = slots.renderToolPart;
2668
3130
  return block.parts.map((part) => {
2669
3131
  const partKey = `${item.id}-${part.toolCallId}`;
2670
- return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, {
3132
+ return /* @__PURE__ */ import_react8.default.createElement(import_react8.default.Fragment, {
2671
3133
  key: partKey
2672
3134
  }, renderToolPart(part, partKey));
2673
3135
  });
@@ -2677,14 +3139,14 @@ function EditableUserBubble({ item, slots, actions }) {
2677
3139
  const { classNames } = slots;
2678
3140
  const { edit } = item;
2679
3141
  if (!edit.isEditing) {
2680
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3142
+ return /* @__PURE__ */ import_react8.default.createElement("div", {
2681
3143
  className: joinClasses(classNames?.root, classNames?.byRole?.user),
2682
3144
  "data-role": "user"
2683
- }, /* @__PURE__ */ import_react6.default.createElement("div", {
3145
+ }, /* @__PURE__ */ import_react8.default.createElement("div", {
2684
3146
  className: classNames?.text
2685
3147
  }, slots.renderText ? slots.renderText(item.text, {
2686
3148
  isStreaming: false
2687
- }) : item.text), /* @__PURE__ */ import_react6.default.createElement("button", {
3149
+ }) : item.text), /* @__PURE__ */ import_react8.default.createElement("button", {
2688
3150
  type: "button",
2689
3151
  onClick: edit.start,
2690
3152
  className: classNames?.actionButton,
@@ -2692,18 +3154,18 @@ function EditableUserBubble({ item, slots, actions }) {
2692
3154
  }, "Edit"), actions);
2693
3155
  }
2694
3156
  const textareaProps = edit.getTextareaProps();
2695
- return /* @__PURE__ */ import_react6.default.createElement("div", {
3157
+ return /* @__PURE__ */ import_react8.default.createElement("div", {
2696
3158
  "data-editing": "true"
2697
- }, /* @__PURE__ */ import_react6.default.createElement("textarea", {
3159
+ }, /* @__PURE__ */ import_react8.default.createElement("textarea", {
2698
3160
  ...textareaProps,
2699
3161
  rows: Math.min(8, edit.draft.split("\n").length + 1),
2700
3162
  className: classNames?.editTextarea
2701
- }), /* @__PURE__ */ import_react6.default.createElement("div", {
3163
+ }), /* @__PURE__ */ import_react8.default.createElement("div", {
2702
3164
  className: classNames?.editActions
2703
- }, /* @__PURE__ */ import_react6.default.createElement("button", {
3165
+ }, /* @__PURE__ */ import_react8.default.createElement("button", {
2704
3166
  type: "button",
2705
3167
  onClick: edit.cancel
2706
- }, "Cancel"), /* @__PURE__ */ import_react6.default.createElement("button", {
3168
+ }, "Cancel"), /* @__PURE__ */ import_react8.default.createElement("button", {
2707
3169
  type: "button",
2708
3170
  onClick: edit.save,
2709
3171
  disabled: !edit.canSave
@@ -2913,7 +3375,11 @@ function createSkillsSource(options) {
2913
3375
  if (cached) {
2914
3376
  return cached;
2915
3377
  }
2916
- const pending = options.client.listSkills(threadId).then((entries) => entries.map(toItem)).catch((error) => {
3378
+ const listSkills = options.client.listSkills;
3379
+ if (listSkills === void 0) {
3380
+ return Promise.reject(new AgentBackendUnsupportedError("listSkills"));
3381
+ }
3382
+ const pending = listSkills.call(options.client, threadId).then((entries) => entries.map(toItem)).catch((error) => {
2917
3383
  byThread.delete(key);
2918
3384
  throw error;
2919
3385
  });
@@ -2943,35 +3409,35 @@ function toItem(entry) {
2943
3409
  __name(toItem, "toItem");
2944
3410
 
2945
3411
  // src/composer/use-composer-autocomplete.ts
2946
- var import_react7 = require("react");
3412
+ var import_react9 = require("react");
2947
3413
  function useComposerAutocomplete(options) {
2948
3414
  const { value, sources, acceptOnTab = true } = options;
2949
- const latest = (0, import_react7.useRef)(options);
3415
+ const latest = (0, import_react9.useRef)(options);
2950
3416
  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);
3417
+ const inputRef = (0, import_react9.useRef)(null);
3418
+ const [caret, setCaret] = (0, import_react9.useState)(value.length);
3419
+ const [items, setItems] = (0, import_react9.useState)([]);
3420
+ const [isLoading, setLoading] = (0, import_react9.useState)(false);
3421
+ const [error, setError] = (0, import_react9.useState)(null);
3422
+ const [activeIndex, setActiveIndex] = (0, import_react9.useState)(0);
3423
+ const [dismissedIndex, setDismissedIndex] = (0, import_react9.useState)(null);
2958
3424
  const safeCaret = Math.min(caret, value.length);
2959
- const match = (0, import_react7.useMemo)(() => findActiveTrigger(value, safeCaret, sources), [
3425
+ const match = (0, import_react9.useMemo)(() => findActiveTrigger(value, safeCaret, sources), [
2960
3426
  value,
2961
3427
  safeCaret,
2962
3428
  sources
2963
3429
  ]);
2964
3430
  const isActive = match !== null && dismissedIndex !== match.index;
2965
- const matchRef = (0, import_react7.useRef)(match);
3431
+ const matchRef = (0, import_react9.useRef)(match);
2966
3432
  matchRef.current = match;
2967
- const caretRef = (0, import_react7.useRef)(safeCaret);
3433
+ const caretRef = (0, import_react9.useRef)(safeCaret);
2968
3434
  caretRef.current = safeCaret;
2969
- const itemsRef = (0, import_react7.useRef)(items);
3435
+ const itemsRef = (0, import_react9.useRef)(items);
2970
3436
  itemsRef.current = items;
2971
- const activeIndexRef = (0, import_react7.useRef)(activeIndex);
3437
+ const activeIndexRef = (0, import_react9.useRef)(activeIndex);
2972
3438
  activeIndexRef.current = activeIndex;
2973
3439
  const requestKey = isActive && match ? `${match.source.id}\0${match.query}` : null;
2974
- (0, import_react7.useEffect)(() => {
3440
+ (0, import_react9.useEffect)(() => {
2975
3441
  const current = matchRef.current;
2976
3442
  if (requestKey === null || current === null) {
2977
3443
  setItems([]);
@@ -3019,25 +3485,25 @@ function useComposerAutocomplete(options) {
3019
3485
  ]);
3020
3486
  const isOpen = isActive;
3021
3487
  const activeItem = isOpen ? items[activeIndex] ?? null : null;
3022
- const reactId = (0, import_react7.useId)();
3488
+ const reactId = (0, import_react9.useId)();
3023
3489
  const listboxId = options.id ?? `agent-autocomplete-${reactId}`;
3024
- const optionId = (0, import_react7.useCallback)((index) => `${listboxId}-option-${index}`, [
3490
+ const optionId = (0, import_react9.useCallback)((index) => `${listboxId}-option-${index}`, [
3025
3491
  listboxId
3026
3492
  ]);
3027
- const highlight = (0, import_react7.useCallback)((index) => {
3493
+ const highlight = (0, import_react9.useCallback)((index) => {
3028
3494
  const total = itemsRef.current.length;
3029
3495
  if (total === 0) return;
3030
3496
  setActiveIndex((index % total + total) % total);
3031
3497
  }, []);
3032
- const moveHighlight = (0, import_react7.useCallback)((delta) => highlight(activeIndexRef.current + delta), [
3498
+ const moveHighlight = (0, import_react9.useCallback)((delta) => highlight(activeIndexRef.current + delta), [
3033
3499
  highlight
3034
3500
  ]);
3035
- const dismiss = (0, import_react7.useCallback)(() => {
3501
+ const dismiss = (0, import_react9.useCallback)(() => {
3036
3502
  const current = matchRef.current;
3037
3503
  if (current) setDismissedIndex(current.index);
3038
3504
  }, []);
3039
- const pendingCaret = (0, import_react7.useRef)(null);
3040
- const accept = (0, import_react7.useCallback)((item) => {
3505
+ const pendingCaret = (0, import_react9.useRef)(null);
3506
+ const accept = (0, import_react9.useCallback)((item) => {
3041
3507
  const current = matchRef.current;
3042
3508
  if (!current) return;
3043
3509
  const chosen = item ?? itemsRef.current[activeIndexRef.current];
@@ -3048,16 +3514,16 @@ function useComposerAutocomplete(options) {
3048
3514
  setDismissedIndex(null);
3049
3515
  pendingCaret.current = edit.caret;
3050
3516
  }, []);
3051
- (0, import_react7.useLayoutEffect)(() => {
3517
+ (0, import_react9.useLayoutEffect)(() => {
3052
3518
  const position = pendingCaret.current;
3053
3519
  if (position === null) return;
3054
3520
  pendingCaret.current = null;
3055
3521
  inputRef.current?.setSelectionRange(position, position);
3056
3522
  });
3057
- const readCaret = (0, import_react7.useCallback)((element) => {
3523
+ const readCaret = (0, import_react9.useCallback)((element) => {
3058
3524
  setCaret(element.selectionStart ?? element.value.length);
3059
3525
  }, []);
3060
- const onKeyDown = (0, import_react7.useCallback)((event) => {
3526
+ const onKeyDown = (0, import_react9.useCallback)((event) => {
3061
3527
  if (!isOpen) return;
3062
3528
  const hasItems = itemsRef.current.length > 0;
3063
3529
  switch (event.key) {
@@ -3095,23 +3561,23 @@ function useComposerAutocomplete(options) {
3095
3561
  dismiss,
3096
3562
  moveHighlight
3097
3563
  ]);
3098
- const onChange = (0, import_react7.useCallback)((event) => {
3564
+ const onChange = (0, import_react9.useCallback)((event) => {
3099
3565
  readCaret(event.target);
3100
3566
  const next = findActiveTrigger(event.target.value, event.target.selectionStart ?? event.target.value.length, latest.current.sources);
3101
3567
  setDismissedIndex((current) => current !== null && next?.index === current ? current : null);
3102
3568
  }, [
3103
3569
  readCaret
3104
3570
  ]);
3105
- const onSelect = (0, import_react7.useCallback)((event) => readCaret(event.currentTarget), [
3571
+ const onSelect = (0, import_react9.useCallback)((event) => readCaret(event.currentTarget), [
3106
3572
  readCaret
3107
3573
  ]);
3108
- const onClick = (0, import_react7.useCallback)((event) => readCaret(event.currentTarget), [
3574
+ const onClick = (0, import_react9.useCallback)((event) => readCaret(event.currentTarget), [
3109
3575
  readCaret
3110
3576
  ]);
3111
- const setInput = (0, import_react7.useCallback)((element) => {
3577
+ const setInput = (0, import_react9.useCallback)((element) => {
3112
3578
  inputRef.current = element;
3113
3579
  }, []);
3114
- const getInputProps = (0, import_react7.useCallback)(() => ({
3580
+ const getInputProps = (0, import_react9.useCallback)(() => ({
3115
3581
  ref: setInput,
3116
3582
  onChange,
3117
3583
  onKeyDown,
@@ -3139,7 +3605,7 @@ function useComposerAutocomplete(options) {
3139
3605
  activeIndex,
3140
3606
  optionId
3141
3607
  ]);
3142
- const getListboxProps = (0, import_react7.useCallback)(() => ({
3608
+ const getListboxProps = (0, import_react9.useCallback)(() => ({
3143
3609
  id: listboxId,
3144
3610
  role: "listbox",
3145
3611
  "aria-label": match?.source.label ?? match?.source.id ?? "Suggestions"
@@ -3147,7 +3613,7 @@ function useComposerAutocomplete(options) {
3147
3613
  listboxId,
3148
3614
  match
3149
3615
  ]);
3150
- const getOptionProps = (0, import_react7.useCallback)((index) => ({
3616
+ const getOptionProps = (0, import_react9.useCallback)((index) => ({
3151
3617
  id: optionId(index),
3152
3618
  role: "option",
3153
3619
  "aria-selected": index === activeIndex
@@ -3181,7 +3647,7 @@ function isPromise(value) {
3181
3647
  __name(isPromise, "isPromise");
3182
3648
 
3183
3649
  // src/presentation/use-tool-catalog.ts
3184
- var import_react8 = require("react");
3650
+ var import_react10 = require("react");
3185
3651
  var shared = /* @__PURE__ */ new WeakMap();
3186
3652
  function load(client, agent) {
3187
3653
  let byAgent = shared.get(client);
@@ -3192,6 +3658,9 @@ function load(client, agent) {
3192
3658
  const key = agent ?? "";
3193
3659
  const cached = byAgent.get(key);
3194
3660
  if (cached !== void 0) return cached;
3661
+ if (client.listTools === void 0) {
3662
+ return Promise.reject(new AgentBackendUnsupportedError("listTools"));
3663
+ }
3195
3664
  const request = client.listTools(agent).catch((error) => {
3196
3665
  byAgent.delete(key);
3197
3666
  throw error;
@@ -3202,11 +3671,11 @@ function load(client, agent) {
3202
3671
  __name(load, "load");
3203
3672
  function useToolCatalog(options) {
3204
3673
  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)(() => {
3674
+ const [entries, setEntries] = (0, import_react10.useState)([]);
3675
+ const [isLoading, setLoading] = (0, import_react10.useState)(enabled);
3676
+ const [error, setError] = (0, import_react10.useState)(null);
3677
+ const [generation, setGeneration] = (0, import_react10.useState)(0);
3678
+ (0, import_react10.useEffect)(() => {
3210
3679
  if (!enabled) {
3211
3680
  setLoading(false);
3212
3681
  return;
@@ -3232,14 +3701,14 @@ function useToolCatalog(options) {
3232
3701
  enabled,
3233
3702
  generation
3234
3703
  ]);
3235
- const refresh = (0, import_react8.useCallback)(() => {
3704
+ const refresh = (0, import_react10.useCallback)(() => {
3236
3705
  shared.get(client)?.delete(agent ?? "");
3237
3706
  setGeneration((current) => current + 1);
3238
3707
  }, [
3239
3708
  client,
3240
3709
  agent
3241
3710
  ]);
3242
- const catalog = (0, import_react8.useMemo)(() => toolCatalogFrom(entries), [
3711
+ const catalog = (0, import_react10.useMemo)(() => toolCatalogFrom(entries), [
3243
3712
  entries
3244
3713
  ]);
3245
3714
  return {
@@ -3253,7 +3722,7 @@ function useToolCatalog(options) {
3253
3722
  __name(useToolCatalog, "useToolCatalog");
3254
3723
 
3255
3724
  // src/approvals/countdown.ts
3256
- var import_react9 = require("react");
3725
+ var import_react11 = require("react");
3257
3726
  function approvalCountdown(expiresAt, now = Date.now()) {
3258
3727
  const deadline = expiresAt == null ? Number.NaN : Date.parse(expiresAt);
3259
3728
  if (!Number.isFinite(deadline)) {
@@ -3271,10 +3740,10 @@ function approvalCountdown(expiresAt, now = Date.now()) {
3271
3740
  __name(approvalCountdown, "approvalCountdown");
3272
3741
  function useApprovalCountdown(expiresAt, options = {}) {
3273
3742
  const { intervalMs = 1e3 } = options;
3274
- const nowRef = (0, import_react9.useRef)(options.now ?? Date.now);
3743
+ const nowRef = (0, import_react11.useRef)(options.now ?? Date.now);
3275
3744
  nowRef.current = options.now ?? Date.now;
3276
- const [state, setState] = (0, import_react9.useState)(() => approvalCountdown(expiresAt, nowRef.current()));
3277
- (0, import_react9.useEffect)(() => {
3745
+ const [state, setState] = (0, import_react11.useState)(() => approvalCountdown(expiresAt, nowRef.current()));
3746
+ (0, import_react11.useEffect)(() => {
3278
3747
  const read = /* @__PURE__ */ __name(() => approvalCountdown(expiresAt, nowRef.current()), "read");
3279
3748
  const first = read();
3280
3749
  setState(first);
@@ -3440,7 +3909,12 @@ function storedMessageToUiMessage(message) {
3440
3909
  return {
3441
3910
  id: message.id,
3442
3911
  role: message.role,
3443
- parts
3912
+ parts,
3913
+ ...message.feedback !== void 0 ? {
3914
+ metadata: {
3915
+ feedback: message.feedback
3916
+ }
3917
+ } : {}
3444
3918
  };
3445
3919
  }
3446
3920
  __name(storedMessageToUiMessage, "storedMessageToUiMessage");
@@ -3476,14 +3950,21 @@ function mergeTurn(rows) {
3476
3950
  }
3477
3951
  const parts = rows.flatMap((row) => storedMessageToUiMessage(row).parts);
3478
3952
  const usage = sumUsage(rows);
3953
+ const feedback = lastRow.feedback;
3954
+ const metadata = {
3955
+ ...usage !== void 0 ? {
3956
+ usage
3957
+ } : {},
3958
+ ...feedback !== void 0 ? {
3959
+ feedback
3960
+ } : {}
3961
+ };
3479
3962
  return {
3480
3963
  id: lastRow.id,
3481
3964
  role: lastRow.role,
3482
3965
  parts,
3483
- ...usage !== void 0 ? {
3484
- metadata: {
3485
- usage
3486
- }
3966
+ ...Object.keys(metadata).length > 0 ? {
3967
+ metadata
3487
3968
  } : {}
3488
3969
  };
3489
3970
  }
@@ -3503,21 +3984,22 @@ function sumUsage(rows) {
3503
3984
  __name(sumUsage, "sumUsage");
3504
3985
 
3505
3986
  // src/use-agent-chat.ts
3506
- var import_react10 = require("@ai-sdk/react");
3507
- var import_react11 = require("react");
3987
+ var import_react12 = require("@ai-sdk/react");
3988
+ var import_react13 = require("react");
3508
3989
  function useAgentChat(options) {
3509
- const latest = (0, import_react11.useRef)(options);
3990
+ const latest = (0, import_react13.useRef)(options);
3510
3991
  latest.current = options;
3511
- const [runId, setRunId] = (0, import_react11.useState)(options.resumeRunId);
3512
- const runIdRef = (0, import_react11.useRef)(runId);
3992
+ const [runId, setRunId] = (0, import_react13.useState)(options.resumeRunId);
3993
+ const runIdRef = (0, import_react13.useRef)(runId);
3513
3994
  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)(() => {
3995
+ const settlingRunIdRef = (0, import_react13.useRef)(void 0);
3996
+ const autoResumeRunIdRef = (0, import_react13.useRef)(void 0);
3997
+ const [autoResume, setAutoResume] = (0, import_react13.useState)(false);
3998
+ const [activeRunId, setActiveRunId] = (0, import_react13.useState)(null);
3999
+ const regenerateNext = (0, import_react13.useRef)(false);
4000
+ const createdThreadId = (0, import_react13.useRef)(void 0);
4001
+ const client = (0, import_react13.useMemo)(() => {
4002
+ if (options.backend) return options.backend;
3521
4003
  if (options.client) return options.client;
3522
4004
  return new AgentClient({
3523
4005
  ...options.baseUrl !== void 0 ? {
@@ -3532,7 +4014,11 @@ function useAgentChat(options) {
3532
4014
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
3533
4015
  });
3534
4016
  }, []);
3535
- (0, import_react11.useEffect)(() => {
4017
+ const [connection, setConnection] = (0, import_react13.useState)({
4018
+ status: "live"
4019
+ });
4020
+ const resyncAfterSettle = (0, import_react13.useRef)(false);
4021
+ (0, import_react13.useEffect)(() => {
3536
4022
  if (!options.resume || options.threadId === void 0) return;
3537
4023
  let cancelled = false;
3538
4024
  const threadId = options.threadId;
@@ -3552,30 +4038,33 @@ function useAgentChat(options) {
3552
4038
  options.resume,
3553
4039
  options.threadId
3554
4040
  ]);
3555
- const transport = (0, import_react11.useMemo)(() => {
4041
+ const transport = (0, import_react13.useMemo)(() => {
3556
4042
  function onMeta(meta) {
3557
4043
  settlingRunIdRef.current = meta.runId;
3558
4044
  setRunId(meta.runId);
3559
4045
  if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
3560
4046
  createdThreadId.current = meta.threadId;
3561
4047
  latest.current.onThreadCreated?.(meta.threadId);
4048
+ notifyThreads(client, {
4049
+ type: "changed"
4050
+ });
3562
4051
  }
3563
4052
  }
3564
4053
  __name(onMeta, "onMeta");
3565
4054
  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
4055
+ backend: client,
4056
+ ...options.reconnect !== void 0 ? {
4057
+ reconnect: options.reconnect
3574
4058
  } : {},
4059
+ onConnectionChange: /* @__PURE__ */ __name((state) => {
4060
+ if (state.status === "gone") resyncAfterSettle.current = true;
4061
+ setConnection(state.status === "gone" ? {
4062
+ status: "live"
4063
+ } : state);
4064
+ }, "onConnectionChange"),
3575
4065
  ...options.agent !== void 0 ? {
3576
4066
  agent: options.agent
3577
4067
  } : {},
3578
- getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders"),
3579
4068
  getBody: /* @__PURE__ */ __name(() => {
3580
4069
  const current = latest.current;
3581
4070
  const pageContext = current.getPageContext?.() ?? null;
@@ -3601,8 +4090,8 @@ function useAgentChat(options) {
3601
4090
  }, "onAttemptStart")
3602
4091
  });
3603
4092
  }, []);
3604
- const backgroundRef = (0, import_react11.useRef)(() => Promise.resolve());
3605
- const chat = (0, import_react10.useChat)({
4093
+ const backgroundRef = (0, import_react13.useRef)(() => Promise.resolve());
4094
+ const chat = (0, import_react12.useChat)({
3606
4095
  transport,
3607
4096
  resume: options.resumeRunId !== void 0 || autoResume,
3608
4097
  ...options.threadId !== void 0 ? {
@@ -3615,7 +4104,15 @@ function useAgentChat(options) {
3615
4104
  latest.current.onData?.(part);
3616
4105
  if (part.type === "data-title") {
3617
4106
  const title = part.data?.title;
3618
- if (typeof title === "string") latest.current.onTitle?.(title);
4107
+ if (typeof title === "string") {
4108
+ latest.current.onTitle?.(title);
4109
+ const threadId = latest.current.threadId ?? createdThreadId.current;
4110
+ if (threadId !== void 0) notifyThreads(client, {
4111
+ type: "title",
4112
+ threadId,
4113
+ title
4114
+ });
4115
+ }
3619
4116
  }
3620
4117
  }, "onData"),
3621
4118
  onFinish: /* @__PURE__ */ __name(({ isError }) => {
@@ -3629,16 +4126,36 @@ function useAgentChat(options) {
3629
4126
  runId: settledRunId,
3630
4127
  status: isError ? "failed" : "completed"
3631
4128
  });
4129
+ notifyThreads(client, {
4130
+ type: "changed"
4131
+ });
4132
+ }
4133
+ setConnection({
4134
+ status: "live"
4135
+ });
4136
+ if (resyncAfterSettle.current) {
4137
+ resyncAfterSettle.current = false;
4138
+ void resyncFromThread();
3632
4139
  }
3633
4140
  }, "onFinish")
3634
4141
  });
3635
- const chatRef = (0, import_react11.useRef)(chat);
4142
+ const chatRef = (0, import_react13.useRef)(chat);
3636
4143
  chatRef.current = chat;
3637
- const turnInFlight = (0, import_react11.useRef)(false);
3638
- const isTurnInFlight = (0, import_react11.useCallback)(() => turnInFlight.current || transport.isAttemptLive, [
4144
+ async function resyncFromThread() {
4145
+ const threadId = latest.current.threadId ?? createdThreadId.current;
4146
+ if (threadId === void 0) return;
4147
+ try {
4148
+ const thread = await client.getThread(threadId);
4149
+ chatRef.current.setMessages(storedThreadToUiMessages(thread.messages));
4150
+ } catch {
4151
+ }
4152
+ }
4153
+ __name(resyncFromThread, "resyncFromThread");
4154
+ const turnInFlight = (0, import_react13.useRef)(false);
4155
+ const isTurnInFlight = (0, import_react13.useCallback)(() => turnInFlight.current || transport.isAttemptLive, [
3639
4156
  transport
3640
4157
  ]);
3641
- const sendMessage = (0, import_react11.useCallback)(async (...args) => {
4158
+ const sendMessage = (0, import_react13.useCallback)(async (...args) => {
3642
4159
  if (isTurnInFlight()) return;
3643
4160
  turnInFlight.current = true;
3644
4161
  try {
@@ -3649,15 +4166,15 @@ function useAgentChat(options) {
3649
4166
  }, [
3650
4167
  isTurnInFlight
3651
4168
  ]);
3652
- const addToolResult = (0, import_react11.useCallback)(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
4169
+ const addToolResult = (0, import_react13.useCallback)(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
3653
4170
  tool,
3654
4171
  toolCallId,
3655
4172
  output
3656
4173
  }), []);
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 () => {
4174
+ const [threads, setThreads] = (0, import_react13.useState)([]);
4175
+ const [quota, setQuota] = (0, import_react13.useState)(null);
4176
+ const [backgroundRuns, setBackgroundRuns] = (0, import_react13.useState)([]);
4177
+ const refreshBackground = (0, import_react13.useCallback)(async () => {
3661
4178
  const threadId = latest.current.threadId ?? createdThreadId.current;
3662
4179
  if (threadId === void 0) {
3663
4180
  return;
@@ -3682,7 +4199,7 @@ function useAgentChat(options) {
3682
4199
  ]);
3683
4200
  backgroundRef.current = refreshBackground;
3684
4201
  const isWorking = backgroundRuns.some((run) => run.status === "running");
3685
- (0, import_react11.useEffect)(() => {
4202
+ (0, import_react13.useEffect)(() => {
3686
4203
  if (options.background !== true || options.threadId === void 0) {
3687
4204
  return;
3688
4205
  }
@@ -3692,7 +4209,7 @@ function useAgentChat(options) {
3692
4209
  options.threadId,
3693
4210
  refreshBackground
3694
4211
  ]);
3695
- (0, import_react11.useEffect)(() => {
4212
+ (0, import_react13.useEffect)(() => {
3696
4213
  const every = latest.current.backgroundPollMs ?? 5e3;
3697
4214
  if (latest.current.background !== true || !isWorking || every <= 0) {
3698
4215
  return;
@@ -3703,27 +4220,29 @@ function useAgentChat(options) {
3703
4220
  isWorking,
3704
4221
  refreshBackground
3705
4222
  ]);
3706
- const loadThreads = (0, import_react11.useCallback)(async () => {
4223
+ const loadThreads = (0, import_react13.useCallback)(async () => {
3707
4224
  const list = await client.listThreads();
3708
4225
  setThreads(list);
3709
4226
  return list;
3710
4227
  }, [
3711
4228
  client
3712
4229
  ]);
3713
- const loadThread = (0, import_react11.useCallback)((id) => client.getThread(id), [
4230
+ const loadThread = (0, import_react13.useCallback)((id) => client.getThread(id), [
3714
4231
  client
3715
4232
  ]);
3716
- const deleteThread = (0, import_react11.useCallback)(async (id) => {
4233
+ const deleteThread = (0, import_react13.useCallback)(async (id) => {
3717
4234
  await client.deleteThread(id);
3718
4235
  setThreads((current) => current.filter((thread) => thread.id !== id));
3719
4236
  }, [
3720
4237
  client
3721
4238
  ]);
3722
- const forkThread = (0, import_react11.useCallback)((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
4239
+ const forkThread = (0, import_react13.useCallback)((threadId, messageId) => requireBackendMethod(client, "forkFromMessage")(threadId, messageId), [
3723
4240
  client
3724
4241
  ]);
3725
- const renameThread = (0, import_react11.useCallback)(async (id, title) => {
3726
- await client.renameThread(id, title);
4242
+ const renameThread = (0, import_react13.useCallback)(async (id, title) => {
4243
+ await client.updateThread(id, {
4244
+ title
4245
+ });
3727
4246
  setThreads((current) => current.map((thread) => thread.id === id ? {
3728
4247
  ...thread,
3729
4248
  title
@@ -3731,24 +4250,24 @@ function useAgentChat(options) {
3731
4250
  }, [
3732
4251
  client
3733
4252
  ]);
3734
- const promoteThread = (0, import_react11.useCallback)(async (id) => {
3735
- await client.promoteThread(id);
4253
+ const promoteThread = (0, import_react13.useCallback)(async (id) => {
4254
+ await requireBackendMethod(client, "promoteThread")(id);
3736
4255
  await loadThreads();
3737
4256
  }, [
3738
4257
  client,
3739
4258
  loadThreads
3740
4259
  ]);
3741
- const truncateFromMessage = (0, import_react11.useCallback)((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
4260
+ const truncateFromMessage = (0, import_react13.useCallback)((threadId, messageId) => requireBackendMethod(client, "truncateFromMessage")(threadId, messageId).then(() => void 0), [
3742
4261
  client
3743
4262
  ]);
3744
- const loadQuota = (0, import_react11.useCallback)(async () => {
3745
- const today = await client.getQuotaToday();
4263
+ const loadQuota = (0, import_react13.useCallback)(async () => {
4264
+ const today = await requireBackendMethod(client, "getQuotaToday")();
3746
4265
  setQuota(today);
3747
4266
  return today;
3748
4267
  }, [
3749
4268
  client
3750
4269
  ]);
3751
- const cancel = (0, import_react11.useCallback)(async () => {
4270
+ const cancel = (0, import_react13.useCallback)(async () => {
3752
4271
  chatRef.current.stop();
3753
4272
  const activeRunId2 = runIdRef.current;
3754
4273
  if (activeRunId2) {
@@ -3760,8 +4279,8 @@ function useAgentChat(options) {
3760
4279
  }, [
3761
4280
  client
3762
4281
  ]);
3763
- const approve = (0, import_react11.useCallback)(async ({ toolCallId, remember, via }) => {
3764
- await client.approveToolCall({
4282
+ const approve = (0, import_react13.useCallback)(async ({ toolCallId, remember, via }) => {
4283
+ await requireBackendMethod(client, "approveToolCall")({
3765
4284
  toolCallId,
3766
4285
  ...remember === true ? {
3767
4286
  remember: true
@@ -3773,8 +4292,8 @@ function useAgentChat(options) {
3773
4292
  }, [
3774
4293
  client
3775
4294
  ]);
3776
- const reject = (0, import_react11.useCallback)(async ({ toolCallId, reason, via }) => {
3777
- await client.rejectToolCall({
4295
+ const reject = (0, import_react13.useCallback)(async ({ toolCallId, reason, via }) => {
4296
+ await requireBackendMethod(client, "rejectToolCall")({
3778
4297
  toolCallId,
3779
4298
  ...reason !== void 0 ? {
3780
4299
  reason
@@ -3786,8 +4305,8 @@ function useAgentChat(options) {
3786
4305
  }, [
3787
4306
  client
3788
4307
  ]);
3789
- const answer = (0, import_react11.useCallback)(async ({ toolCallId, answers }) => {
3790
- await client.answerToolCall({
4308
+ const answer = (0, import_react13.useCallback)(async ({ toolCallId, answers }) => {
4309
+ await requireBackendMethod(client, "answerToolCall")({
3791
4310
  toolCallId,
3792
4311
  ...answers !== void 0 ? {
3793
4312
  answers
@@ -3796,14 +4315,14 @@ function useAgentChat(options) {
3796
4315
  }, [
3797
4316
  client
3798
4317
  ]);
3799
- const skip = (0, import_react11.useCallback)(async ({ toolCallId }) => {
3800
- await client.skipToolCall({
4318
+ const skip = (0, import_react13.useCallback)(async ({ toolCallId }) => {
4319
+ await requireBackendMethod(client, "skipToolCall")({
3801
4320
  toolCallId
3802
4321
  });
3803
4322
  }, [
3804
4323
  client
3805
4324
  ]);
3806
- const regenerate = (0, import_react11.useCallback)(() => {
4325
+ const regenerate = (0, import_react13.useCallback)(() => {
3807
4326
  if (isTurnInFlight()) return;
3808
4327
  regenerateNext.current = true;
3809
4328
  turnInFlight.current = true;
@@ -3818,8 +4337,13 @@ function useAgentChat(options) {
3818
4337
  isWorking,
3819
4338
  refresh: refreshBackground
3820
4339
  };
4340
+ const getThreadId = (0, import_react13.useCallback)(() => latest.current.threadId ?? createdThreadId.current, []);
4341
+ const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
3821
4342
  return {
3822
4343
  ...chat,
4344
+ status,
4345
+ /** Where the live stream stands — see {@link StreamConnectionState}. */
4346
+ connection,
3823
4347
  sendMessage,
3824
4348
  addToolResult,
3825
4349
  /** Sub-agents this conversation started and did not wait for. */
@@ -3827,6 +4351,14 @@ function useAgentChat(options) {
3827
4351
  runId,
3828
4352
  /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
3829
4353
  activeRunId,
4354
+ /**
4355
+ * The thread this chat is on right now: the `threadId` option, else the one the backend created
4356
+ * on the first send. A getter, because the created id is learned mid-stream.
4357
+ */
4358
+ getThreadId,
4359
+ /** The backend this chat talks to — pass it to `useThreads`, `useMessageFeedback`, … */
4360
+ backend: client,
4361
+ /** Same object as {@link backend}; kept for callers that read it under its old name. */
3830
4362
  client,
3831
4363
  threads,
3832
4364
  loadThreads,
@@ -3857,6 +4389,7 @@ async function mergeHeaders(options) {
3857
4389
  __name(mergeHeaders, "mergeHeaders");
3858
4390
  // Annotate the CommonJS export names for ESM import in node:
3859
4391
  0 && (module.exports = {
4392
+ AgentBackendUnsupportedError,
3860
4393
  AgentChatTransport,
3861
4394
  AgentClient,
3862
4395
  AgentHttpError,
@@ -3886,10 +4419,13 @@ __name(mergeHeaders, "mergeHeaders");
3886
4419
  inferResultView,
3887
4420
  isActionCall,
3888
4421
  loadStoredSpeechLang,
4422
+ notifyThreads,
4423
+ onThreadsEvent,
3889
4424
  persistSpeechLang,
3890
4425
  phraseFor,
3891
4426
  readPath,
3892
4427
  readReasoningMs,
4428
+ requireBackendMethod,
3893
4429
  resolveResultView,
3894
4430
  storedMessageToUiMessage,
3895
4431
  storedThreadToUiMessages,
@@ -3900,8 +4436,10 @@ __name(mergeHeaders, "mergeHeaders");
3900
4436
  useChatTranscript,
3901
4437
  useComposerAutocomplete,
3902
4438
  useElapsed,
4439
+ useMessageFeedback,
3903
4440
  useSpeechRecognition,
3904
4441
  useStickToBottom,
4442
+ useThreads,
3905
4443
  useToolCatalog,
3906
4444
  useTranscriptItem,
3907
4445
  validateAnswer,