@dudousxd/nestjs-agent-react 0.13.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.js CHANGED
@@ -1,6 +1,246 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
+ // src/client.ts
5
+ var AgentHttpError = class extends Error {
6
+ static {
7
+ __name(this, "AgentHttpError");
8
+ }
9
+ status;
10
+ method;
11
+ path;
12
+ constructor(status, method, path, statusText) {
13
+ super(`Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
14
+ this.name = "AgentHttpError";
15
+ }
16
+ };
17
+ var HEADER_RUN_ID = "x-agent-run-id";
18
+ var HEADER_THREAD_ID = "x-agent-thread-id";
19
+ var AgentClient = class {
20
+ static {
21
+ __name(this, "AgentClient");
22
+ }
23
+ options;
24
+ constructor(options = {}) {
25
+ this.options = options;
26
+ }
27
+ /** `POST /agent/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
28
+ async openChatStream(request) {
29
+ const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat`, {
30
+ method: "POST",
31
+ headers: {
32
+ "content-type": "application/json",
33
+ accept: "text/event-stream",
34
+ ...await this.resolveHeaders(),
35
+ ...request.headers
36
+ },
37
+ body: JSON.stringify(request.body),
38
+ ...this.credentials(),
39
+ ...request.signal !== void 0 ? {
40
+ signal: request.signal
41
+ } : {}
42
+ });
43
+ if (!response.ok || !response.body) {
44
+ throw new AgentHttpError(response.status, "POST", "/agent/chat", response.statusText);
45
+ }
46
+ return streamResponse(response);
47
+ }
48
+ /**
49
+ * `GET /agent/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
50
+ * streaming under that id (404).
51
+ */
52
+ async resumeChatStream(request) {
53
+ const path = `/agent/chat/${encodeURIComponent(request.runId)}/stream`;
54
+ const query = request.after !== void 0 && request.after > 0 ? `?after=${request.after}` : "";
55
+ const response = await this.fetchImpl()(`${this.baseUrl()}${path}${query}`, {
56
+ method: "GET",
57
+ headers: {
58
+ accept: "text/event-stream",
59
+ ...await this.resolveHeaders(),
60
+ ...request.headers
61
+ },
62
+ ...this.credentials(),
63
+ ...request.signal !== void 0 ? {
64
+ signal: request.signal
65
+ } : {}
66
+ });
67
+ if (response.status === 404) {
68
+ return null;
69
+ }
70
+ if (!response.ok || !response.body) {
71
+ throw new AgentHttpError(response.status, "GET", path, response.statusText);
72
+ }
73
+ return streamResponse(response);
74
+ }
75
+ /**
76
+ * Rate a message (`'up'`/`'down'`, optional comment) or clear its rating (`value: null`). Answers
77
+ * the stored rating.
78
+ */
79
+ setMessageFeedback(messageId, input) {
80
+ return this.request("POST", `/agent/messages/${encodeURIComponent(messageId)}/feedback`, input);
81
+ }
82
+ listThreads() {
83
+ return this.request("GET", "/agent/threads");
84
+ }
85
+ /**
86
+ * The skills this caller can invoke right now, scope-resolved — the same list, built by the same
87
+ * call, that the model is offered, so what a user can type after a `/` and what the agent can
88
+ * reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to
89
+ * one conversation; omitted, the server reads it as a brand-new thread.
90
+ */
91
+ listSkills(threadId) {
92
+ const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
93
+ return this.request("GET", `/agent/skills${query}`);
94
+ }
95
+ /**
96
+ * The tools this caller can reach through `agent` (the default agent when omitted), each with the
97
+ * server-declared `presentation` a chat narrates it by — the same list the model is offered.
98
+ * Prefer {@link useToolCatalog}, which fetches it once and shares it.
99
+ */
100
+ listTools(agent) {
101
+ const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
102
+ return this.request("GET", `/agent/tools${query}`);
103
+ }
104
+ getThread(id) {
105
+ return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
106
+ }
107
+ deleteThread(id) {
108
+ return this.request("DELETE", `/agent/threads/${encodeURIComponent(id)}`);
109
+ }
110
+ forkFromMessage(threadId, messageId) {
111
+ return this.request("POST", `/agent/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
112
+ }
113
+ renameThread(id, title) {
114
+ return this.updateThread(id, {
115
+ title
116
+ });
117
+ }
118
+ /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
119
+ updateThread(id, patch) {
120
+ return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, patch);
121
+ }
122
+ /**
123
+ * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
124
+ * mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
125
+ * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
126
+ */
127
+ async uploadAttachment(file, options = {}) {
128
+ const formData = new FormData();
129
+ formData.append("file", file);
130
+ const response = await this.fetchImpl()(`${this.baseUrl()}/agent/attachments`, {
131
+ method: "POST",
132
+ headers: {
133
+ accept: "application/json",
134
+ ...await this.resolveHeaders()
135
+ },
136
+ body: formData,
137
+ ...this.credentials(),
138
+ ...options.signal !== void 0 ? {
139
+ signal: options.signal
140
+ } : {}
141
+ });
142
+ return this.handleResponse(response, "POST", "/agent/attachments");
143
+ }
144
+ promoteThread(id) {
145
+ return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
146
+ }
147
+ truncateFromMessage(threadId, messageId) {
148
+ return this.request("DELETE", `/agent/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`);
149
+ }
150
+ getQuotaToday() {
151
+ return this.request("GET", "/agent/quota/today");
152
+ }
153
+ cancelStream(runId) {
154
+ return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
155
+ }
156
+ /**
157
+ * `remember` approves later calls of the same tool in the same thread; `via` names the surface
158
+ * the decision came through (the server records `'web'` when omitted).
159
+ */
160
+ approveToolCall(input) {
161
+ return this.request("POST", "/agent/tool-call/approve", input);
162
+ }
163
+ rejectToolCall(input) {
164
+ return this.request("POST", "/agent/tool-call/reject", input);
165
+ }
166
+ /**
167
+ * Settle a parked question set. `answers` is questionId → chosen option values; a question left
168
+ * out takes the pre-picked default the request carried, resolved server-side against the request
169
+ * the run already holds. Omit the whole object and the user has confirmed every pre-picked
170
+ * answer — which is the point of the surface, so it is a valid submission rather than a blank.
171
+ */
172
+ answerToolCall(input) {
173
+ return this.request("POST", "/agent/tool-call/answer", input);
174
+ }
175
+ /**
176
+ * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
177
+ * values a confirmation would, and persists differently on purpose — only one of them is
178
+ * evidence the user chose them.
179
+ */
180
+ skipToolCall(input) {
181
+ return this.request("POST", "/agent/tool-call/skip", input);
182
+ }
183
+ fetchImpl() {
184
+ return this.options.fetch ?? globalThis.fetch;
185
+ }
186
+ baseUrl() {
187
+ return (this.options.baseUrl ?? "").replace(/\/$/, "");
188
+ }
189
+ async resolveHeaders() {
190
+ const dynamic = await this.options.getHeaders?.() ?? {};
191
+ return {
192
+ ...this.options.headers,
193
+ ...dynamic
194
+ };
195
+ }
196
+ credentials() {
197
+ return this.options.credentials !== void 0 ? {
198
+ credentials: this.options.credentials
199
+ } : {};
200
+ }
201
+ async request(method, path, body) {
202
+ const response = await this.fetchImpl()(`${this.baseUrl()}${path}`, {
203
+ method,
204
+ headers: {
205
+ accept: "application/json",
206
+ ...body !== void 0 ? {
207
+ "content-type": "application/json"
208
+ } : {},
209
+ ...await this.resolveHeaders()
210
+ },
211
+ ...body !== void 0 ? {
212
+ body: JSON.stringify(body)
213
+ } : {},
214
+ ...this.credentials()
215
+ });
216
+ return this.handleResponse(response, method, path);
217
+ }
218
+ async handleResponse(response, method, path) {
219
+ if (!response.ok) {
220
+ throw new AgentHttpError(response.status, method, path, response.statusText);
221
+ }
222
+ if (response.status === 204) return void 0;
223
+ const text = await response.text();
224
+ if (!text) return void 0;
225
+ return JSON.parse(text);
226
+ }
227
+ };
228
+ function streamResponse(response) {
229
+ const body = response.body;
230
+ const runId = response.headers?.get(HEADER_RUN_ID) ?? void 0;
231
+ const threadId = response.headers?.get(HEADER_THREAD_ID) ?? void 0;
232
+ return {
233
+ body,
234
+ ...runId ? {
235
+ runId
236
+ } : {},
237
+ ...threadId ? {
238
+ threadId
239
+ } : {}
240
+ };
241
+ }
242
+ __name(streamResponse, "streamResponse");
243
+
4
244
  // src/reasoning/timing.ts
5
245
  import { useEffect, useRef, useState } from "react";
6
246
  var REASONING_METADATA_KEY = "agent";
@@ -61,8 +301,11 @@ function formatElapsed(ms) {
61
301
  __name(formatElapsed, "formatElapsed");
62
302
 
63
303
  // src/agent-chat-transport.ts
64
- var HEADER_RUN_ID = "x-agent-run-id";
65
- var HEADER_THREAD_ID = "x-agent-thread-id";
304
+ var DEFAULT_RECONNECT = {
305
+ maxAttempts: 6,
306
+ baseDelayMs: 500,
307
+ maxDelayMs: 8e3
308
+ };
66
309
  var ASK_TOOL_NAME = "ask";
67
310
  var AgentChatTransport = class {
68
311
  static {
@@ -79,8 +322,26 @@ var AgentChatTransport = class {
79
322
  * several entries under each id — which React renders as duplicate keys.
80
323
  */
81
324
  attemptLive = false;
325
+ backend;
82
326
  constructor(options = {}) {
83
327
  this.options = options;
328
+ this.backend = options.backend ?? new AgentClient({
329
+ ...options.baseUrl !== void 0 ? {
330
+ baseUrl: options.baseUrl
331
+ } : {},
332
+ ...options.headers !== void 0 ? {
333
+ headers: options.headers
334
+ } : {},
335
+ ...options.getHeaders !== void 0 ? {
336
+ getHeaders: options.getHeaders
337
+ } : {},
338
+ ...options.credentials !== void 0 ? {
339
+ credentials: options.credentials
340
+ } : {},
341
+ ...options.fetch !== void 0 ? {
342
+ fetch: options.fetch
343
+ } : {}
344
+ });
84
345
  }
85
346
  /** Run id of the most recent stream — HITL approve/reject target this. */
86
347
  get runId() {
@@ -113,26 +374,25 @@ var AgentChatTransport = class {
113
374
  ...options.body ?? {},
114
375
  message
115
376
  };
116
- const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat`, {
117
- method: "POST",
118
- headers: {
119
- "content-type": "application/json",
120
- accept: "text/event-stream",
121
- ...await this.resolveHeaders(options.headers)
122
- },
123
- body: JSON.stringify(body),
124
- ...this.options.credentials !== void 0 ? {
125
- credentials: this.options.credentials
377
+ const headers = headerRecord(options.headers);
378
+ const response = await this.backend.openChatStream({
379
+ body,
380
+ ...headers !== void 0 ? {
381
+ headers
382
+ } : {},
383
+ ...options.abortSignal ? {
384
+ signal: options.abortSignal
385
+ } : {}
386
+ });
387
+ this.captureHeaderMeta(response);
388
+ return this.toChunkStream(response, {
389
+ ...headers !== void 0 ? {
390
+ headers
126
391
  } : {},
127
392
  ...options.abortSignal ? {
128
393
  signal: options.abortSignal
129
394
  } : {}
130
395
  });
131
- if (!response.ok || !response.body) {
132
- throw new Error(`Agent chat request failed: ${response.status} ${response.statusText}`);
133
- }
134
- this.captureHeaderMeta(response.headers);
135
- return this.toChunkStream(response.body);
136
396
  } catch (error) {
137
397
  this.attemptLive = false;
138
398
  throw error;
@@ -145,51 +405,41 @@ var AgentChatTransport = class {
145
405
  if (runId === void 0) return null;
146
406
  this.attemptLive = true;
147
407
  try {
148
- const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat/${encodeURIComponent(runId)}/stream`, {
149
- method: "GET",
150
- headers: {
151
- accept: "text/event-stream",
152
- ...await this.resolveHeaders(options.headers)
153
- },
154
- ...this.options.credentials !== void 0 ? {
155
- credentials: this.options.credentials
408
+ const headers = headerRecord(options.headers);
409
+ const response = await this.backend.resumeChatStream({
410
+ runId,
411
+ ...headers !== void 0 ? {
412
+ headers
156
413
  } : {}
157
414
  });
158
- if (response.status === 404) {
415
+ if (response === null) {
159
416
  this.attemptLive = false;
160
417
  return null;
161
418
  }
162
- if (!response.ok || !response.body) {
163
- throw new Error(`Agent stream reconnect failed: ${response.status} ${response.statusText}`);
164
- }
165
- this.captureHeaderMeta(response.headers);
166
- return this.toChunkStream(response.body);
419
+ this.captureHeaderMeta(response);
420
+ return this.toChunkStream(response, {
421
+ runId,
422
+ ...headers !== void 0 ? {
423
+ headers
424
+ } : {}
425
+ });
167
426
  } catch (error) {
168
427
  this.attemptLive = false;
169
428
  throw error;
170
429
  }
171
430
  }
172
- fetchImpl() {
173
- return this.options.fetch ?? globalThis.fetch;
174
- }
175
- baseUrl() {
176
- return (this.options.baseUrl ?? "").replace(/\/$/, "");
431
+ captureHeaderMeta(response) {
432
+ if (response.runId) this.currentRunId = response.runId;
433
+ if (response.threadId) this.currentThreadId = response.threadId;
177
434
  }
178
- async resolveHeaders(perRequest) {
179
- const dynamic = await this.options.getHeaders?.() ?? {};
180
- const extra = perRequest instanceof Headers ? Object.fromEntries(perRequest.entries()) : perRequest ?? {};
435
+ reconnectPolicy() {
436
+ const configured = this.options.reconnect;
437
+ if (configured === false) return null;
181
438
  return {
182
- ...this.options.headers,
183
- ...dynamic,
184
- ...extra
439
+ ...DEFAULT_RECONNECT,
440
+ ...configured
185
441
  };
186
442
  }
187
- captureHeaderMeta(headers) {
188
- const runId = headers.get(HEADER_RUN_ID);
189
- const threadId = headers.get(HEADER_THREAD_ID);
190
- if (runId) this.currentRunId = runId;
191
- if (threadId) this.currentThreadId = threadId;
192
- }
193
443
  recordMeta(meta) {
194
444
  this.currentRunId = meta.runId;
195
445
  this.currentThreadId = meta.threadId;
@@ -208,10 +458,66 @@ var AgentChatTransport = class {
208
458
  * call plus its tool execution; text and reasoning open lazily and close at the step boundary, so
209
459
  * tool-call cards (input streaming → output) render live between the prose.
210
460
  */
211
- toChunkStream(source) {
212
- const reader = source.getReader();
213
- const decoder = new TextDecoder();
461
+ toChunkStream(source, context) {
462
+ let reader = source.body.getReader();
463
+ let decoder = new TextDecoder();
214
464
  let buffer = "";
465
+ let streamRunId = source.runId ?? context.runId;
466
+ let lastSeq;
467
+ let cancelled = false;
468
+ const backend = this.backend;
469
+ const policy = this.reconnectPolicy();
470
+ const notify = /* @__PURE__ */ __name((state) => this.options.onConnectionChange?.(state), "notify");
471
+ const aborted = /* @__PURE__ */ __name(() => cancelled || context.signal?.aborted === true, "aborted");
472
+ const canResume = /* @__PURE__ */ __name(() => policy !== null && lastSeq !== void 0 && streamRunId !== void 0 && !aborted(), "canResume");
473
+ const resume = /* @__PURE__ */ __name(async () => {
474
+ const runId = streamRunId;
475
+ const after = lastSeq ?? 0;
476
+ const { maxAttempts, baseDelayMs, maxDelayMs } = policy;
477
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
478
+ notify({
479
+ status: "reconnecting",
480
+ runId,
481
+ attempt,
482
+ after
483
+ });
484
+ await delay(Math.min(maxDelayMs, baseDelayMs * 2 ** (attempt - 1)), context.signal);
485
+ if (aborted()) return "aborted";
486
+ try {
487
+ const next = await backend.resumeChatStream({
488
+ runId,
489
+ after,
490
+ ...context.headers !== void 0 ? {
491
+ headers: context.headers
492
+ } : {},
493
+ ...context.signal !== void 0 ? {
494
+ signal: context.signal
495
+ } : {}
496
+ });
497
+ if (next === null) {
498
+ notify({
499
+ status: "gone",
500
+ runId
501
+ });
502
+ return "gone";
503
+ }
504
+ reader = next.body.getReader();
505
+ decoder = new TextDecoder();
506
+ buffer = "";
507
+ notify({
508
+ status: "live"
509
+ });
510
+ return "resumed";
511
+ } catch {
512
+ if (aborted()) return "aborted";
513
+ }
514
+ }
515
+ notify({
516
+ status: "failed",
517
+ runId
518
+ });
519
+ return "failed";
520
+ }, "resume");
215
521
  let started = false;
216
522
  let stepOpen = false;
217
523
  let stepIndex = 0;
@@ -222,7 +528,10 @@ var AgentChatTransport = class {
222
528
  let reasoningSeq = 0;
223
529
  const announced = /* @__PURE__ */ new Set();
224
530
  const toolMetadata = /* @__PURE__ */ new Map();
225
- const record = /* @__PURE__ */ __name((meta) => this.recordMeta(meta), "record");
531
+ const record = /* @__PURE__ */ __name((meta) => {
532
+ streamRunId = meta.runId;
533
+ this.recordMeta(meta);
534
+ }, "record");
226
535
  const endAttempt = /* @__PURE__ */ __name(() => {
227
536
  this.attemptLive = false;
228
537
  }, "endAttempt");
@@ -231,7 +540,12 @@ var AgentChatTransport = class {
231
540
  function ensureStarted() {
232
541
  if (started) return;
233
542
  started = true;
234
- controller.enqueue({
543
+ controller.enqueue(streamRunId !== void 0 ? {
544
+ type: "start",
545
+ messageMetadata: {
546
+ runId: streamRunId
547
+ }
548
+ } : {
235
549
  type: "start"
236
550
  });
237
551
  }
@@ -487,33 +801,67 @@ var AgentChatTransport = class {
487
801
  endAttempt();
488
802
  }
489
803
  __name(finish, "finish");
804
+ function fail(errorText, start = true) {
805
+ if (start) ensureStarted();
806
+ controller.enqueue({
807
+ type: "error",
808
+ errorText
809
+ });
810
+ controller.close();
811
+ endAttempt();
812
+ }
813
+ __name(fail, "fail");
814
+ async function recover(error) {
815
+ if (!canResume()) {
816
+ if (error === void 0) {
817
+ finish();
818
+ } else {
819
+ fail(error instanceof Error ? error.message : "Agent stream error", false);
820
+ }
821
+ return false;
822
+ }
823
+ const outcome = await resume();
824
+ if (outcome === "resumed") return true;
825
+ if (outcome === "failed") {
826
+ fail("Lost the connection to the agent stream");
827
+ } else {
828
+ finish();
829
+ }
830
+ return false;
831
+ }
832
+ __name(recover, "recover");
490
833
  try {
491
834
  while (true) {
492
- const { done, value } = await reader.read();
493
- if (done) {
494
- finish();
835
+ let result;
836
+ try {
837
+ result = await reader.read();
838
+ } catch (error) {
839
+ if (await recover(error)) continue;
840
+ return;
841
+ }
842
+ if (result.done) {
843
+ if (await recover(void 0)) continue;
495
844
  return;
496
845
  }
497
- buffer += decoder.decode(value, {
846
+ buffer += decoder.decode(result.value, {
498
847
  stream: true
499
848
  });
500
849
  let separator = buffer.indexOf("\n\n");
501
850
  while (separator !== -1) {
502
851
  const rawEvent = buffer.slice(0, separator);
503
852
  buffer = buffer.slice(separator + 2);
853
+ separator = buffer.indexOf("\n\n");
504
854
  const frame = parseSseFrame(rawEvent);
855
+ if (frame.id !== void 0) {
856
+ if (lastSeq !== void 0 && frame.id <= lastSeq) continue;
857
+ lastSeq = frame.id;
858
+ }
505
859
  if (frame.event === "done") {
506
860
  finish();
507
861
  return;
508
862
  }
509
863
  if (frame.event === "error") {
510
- ensureStarted();
511
- controller.enqueue({
512
- type: "error",
513
- errorText: parseErrorText(frame.data)
514
- });
515
- controller.close();
516
- endAttempt();
864
+ fail(parseErrorText(frame.data));
517
865
  return;
518
866
  }
519
867
  if (frame.event === "meta") {
@@ -523,19 +871,14 @@ var AgentChatTransport = class {
523
871
  const event = parseEvent(frame.data);
524
872
  if (event) emit(event);
525
873
  }
526
- separator = buffer.indexOf("\n\n");
527
874
  }
528
875
  }
529
876
  } catch (error) {
530
- controller.enqueue({
531
- type: "error",
532
- errorText: error instanceof Error ? error.message : "Agent stream error"
533
- });
534
- controller.close();
535
- endAttempt();
877
+ fail(error instanceof Error ? error.message : "Agent stream error", false);
536
878
  }
537
879
  },
538
880
  cancel() {
881
+ cancelled = true;
539
882
  void reader.cancel();
540
883
  endAttempt();
541
884
  }
@@ -544,17 +887,22 @@ var AgentChatTransport = class {
544
887
  };
545
888
  function parseSseFrame(raw) {
546
889
  let event;
890
+ let id;
547
891
  const dataLines = [];
548
892
  for (const line of raw.split("\n")) {
549
893
  if (line.startsWith("event:")) {
550
894
  event = line.slice("event:".length).trim();
895
+ } else if (line.startsWith("id:")) {
896
+ const parsed = Number(line.slice("id:".length).trim());
897
+ if (Number.isSafeInteger(parsed) && parsed > 0) id = parsed;
551
898
  } else if (line.startsWith("data:")) {
552
899
  dataLines.push(line.slice("data:".length).trimStart());
553
900
  }
554
901
  }
555
902
  return {
556
903
  event,
557
- data: dataLines.length > 0 ? dataLines.join("\n") : void 0
904
+ data: dataLines.length > 0 ? dataLines.join("\n") : void 0,
905
+ id
558
906
  };
559
907
  }
560
908
  __name(parseSseFrame, "parseSseFrame");
@@ -584,26 +932,293 @@ function parseErrorText(data) {
584
932
  }
585
933
  return "Agent run failed";
586
934
  }
587
- __name(parseErrorText, "parseErrorText");
588
- function parseEvent(data) {
589
- try {
590
- const parsed = JSON.parse(data);
591
- if (parsed !== null && typeof parsed === "object" && "kind" in parsed) {
592
- return parsed;
593
- }
594
- } catch {
935
+ __name(parseErrorText, "parseErrorText");
936
+ function parseEvent(data) {
937
+ try {
938
+ const parsed = JSON.parse(data);
939
+ if (parsed !== null && typeof parsed === "object" && "kind" in parsed) {
940
+ return parsed;
941
+ }
942
+ } catch {
943
+ }
944
+ return null;
945
+ }
946
+ __name(parseEvent, "parseEvent");
947
+ function extractText(message) {
948
+ let text = "";
949
+ for (const part of message.parts ?? []) {
950
+ if (part.type === "text") text += part.text;
951
+ }
952
+ return text;
953
+ }
954
+ __name(extractText, "extractText");
955
+ function headerRecord(headers) {
956
+ if (headers === void 0) return void 0;
957
+ return headers instanceof Headers ? Object.fromEntries(headers.entries()) : headers;
958
+ }
959
+ __name(headerRecord, "headerRecord");
960
+ function delay(ms, signal) {
961
+ if (ms <= 0 || signal?.aborted) return Promise.resolve();
962
+ return new Promise((resolve) => {
963
+ const timer = setTimeout(done, ms);
964
+ function done() {
965
+ clearTimeout(timer);
966
+ signal?.removeEventListener("abort", done);
967
+ resolve();
968
+ }
969
+ __name(done, "done");
970
+ signal?.addEventListener("abort", done, {
971
+ once: true
972
+ });
973
+ });
974
+ }
975
+ __name(delay, "delay");
976
+
977
+ // src/backend.ts
978
+ var AgentBackendUnsupportedError = class extends Error {
979
+ static {
980
+ __name(this, "AgentBackendUnsupportedError");
981
+ }
982
+ method;
983
+ constructor(method) {
984
+ super(`The agent backend does not implement ${method}()`), this.method = method;
985
+ this.name = "AgentBackendUnsupportedError";
986
+ }
987
+ };
988
+ function requireBackendMethod(backend, name) {
989
+ const method = backend[name];
990
+ if (typeof method !== "function") {
991
+ throw new AgentBackendUnsupportedError(name);
992
+ }
993
+ return method.bind(backend);
994
+ }
995
+ __name(requireBackendMethod, "requireBackendMethod");
996
+
997
+ // src/feedback/use-message-feedback.ts
998
+ import { useCallback, useRef as useRef2, useState as useState2 } from "react";
999
+ function useMessageFeedback(options) {
1000
+ const latest = useRef2(options);
1001
+ latest.current = options;
1002
+ const [local, setLocal] = useState2(/* @__PURE__ */ new Map());
1003
+ const [pending, setPending] = useState2(/* @__PURE__ */ new Set());
1004
+ const [error, setError] = useState2(null);
1005
+ const localRef = useRef2(local);
1006
+ localRef.current = local;
1007
+ const resolved = useRef2(/* @__PURE__ */ new Map());
1008
+ const feedbackOf = useCallback((message) => {
1009
+ const mine = local.get(message.id);
1010
+ if (mine !== void 0) return mine.feedback;
1011
+ return metadataOf(message).feedback ?? null;
1012
+ }, [
1013
+ local
1014
+ ]);
1015
+ const isPending = useCallback((message) => pending.has(message.id), [
1016
+ pending
1017
+ ]);
1018
+ const storedIdFor = useCallback(async (message) => {
1019
+ const runId = metadataOf(message).runId;
1020
+ if (runId === void 0) return message.id;
1021
+ const known = resolved.current.get(message.id);
1022
+ if (known !== void 0) return known;
1023
+ const { backend, threadId } = latest.current;
1024
+ const thread = typeof threadId === "function" ? threadId() : threadId;
1025
+ if (thread === void 0) {
1026
+ throw new Error("useMessageFeedback: a streamed message needs `threadId` to be rated");
1027
+ }
1028
+ const detail = await backend.getThread(thread);
1029
+ const row = [
1030
+ ...detail.messages
1031
+ ].reverse().find((candidate) => candidate.role === "assistant" && candidate.runId === runId);
1032
+ if (row === void 0) {
1033
+ throw new Error(`useMessageFeedback: run ${runId} has no persisted answer yet`);
1034
+ }
1035
+ resolved.current.set(message.id, row.id);
1036
+ return row.id;
1037
+ }, []);
1038
+ const rate = useCallback(async (message, value, comment) => {
1039
+ const setFeedback = requireBackendMethod(latest.current.backend, "setMessageFeedback");
1040
+ const had = localRef.current.get(message.id);
1041
+ const optimistic = value === null ? null : {
1042
+ value,
1043
+ ...comment !== void 0 && comment.trim().length > 0 ? {
1044
+ comment: comment.trim()
1045
+ } : {},
1046
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1047
+ };
1048
+ setLocal((current) => new Map(current).set(message.id, {
1049
+ feedback: optimistic
1050
+ }));
1051
+ setPending((current) => new Set(current).add(message.id));
1052
+ try {
1053
+ const storedId = await storedIdFor(message);
1054
+ const { feedback } = await setFeedback(storedId, {
1055
+ value,
1056
+ ...comment !== void 0 ? {
1057
+ comment
1058
+ } : {}
1059
+ });
1060
+ setLocal((current) => new Map(current).set(message.id, {
1061
+ feedback
1062
+ }));
1063
+ setError(null);
1064
+ } catch (caught) {
1065
+ setLocal((current) => {
1066
+ const next = new Map(current);
1067
+ if (had === void 0) next.delete(message.id);
1068
+ else next.set(message.id, had);
1069
+ return next;
1070
+ });
1071
+ const failure = caught instanceof Error ? caught : new Error(String(caught));
1072
+ setError(failure);
1073
+ throw failure;
1074
+ } finally {
1075
+ setPending((current) => {
1076
+ const next = new Set(current);
1077
+ next.delete(message.id);
1078
+ return next;
1079
+ });
1080
+ }
1081
+ }, [
1082
+ storedIdFor
1083
+ ]);
1084
+ const toggle = useCallback((message, value) => {
1085
+ const mine = localRef.current.get(message.id);
1086
+ const current = mine !== void 0 ? mine.feedback : metadataOf(message).feedback ?? null;
1087
+ return rate(message, current?.value === value ? null : value);
1088
+ }, [
1089
+ rate
1090
+ ]);
1091
+ return {
1092
+ feedbackOf,
1093
+ isPending,
1094
+ rate,
1095
+ toggle,
1096
+ error
1097
+ };
1098
+ }
1099
+ __name(useMessageFeedback, "useMessageFeedback");
1100
+ function metadataOf(message) {
1101
+ const metadata = message.metadata;
1102
+ return metadata !== null && typeof metadata === "object" ? metadata : {};
1103
+ }
1104
+ __name(metadataOf, "metadataOf");
1105
+
1106
+ // src/threads/threads-events.ts
1107
+ var listeners = /* @__PURE__ */ new WeakMap();
1108
+ function notifyThreads(backend, event) {
1109
+ for (const listener of listeners.get(backend) ?? []) {
1110
+ listener(event);
595
1111
  }
596
- return null;
597
1112
  }
598
- __name(parseEvent, "parseEvent");
599
- function extractText(message) {
600
- let text = "";
601
- for (const part of message.parts ?? []) {
602
- if (part.type === "text") text += part.text;
603
- }
604
- return text;
1113
+ __name(notifyThreads, "notifyThreads");
1114
+ function onThreadsEvent(backend, listener) {
1115
+ let set = listeners.get(backend);
1116
+ if (set === void 0) {
1117
+ set = /* @__PURE__ */ new Set();
1118
+ listeners.set(backend, set);
1119
+ }
1120
+ set.add(listener);
1121
+ return () => {
1122
+ set.delete(listener);
1123
+ };
605
1124
  }
606
- __name(extractText, "extractText");
1125
+ __name(onThreadsEvent, "onThreadsEvent");
1126
+
1127
+ // src/threads/use-threads.ts
1128
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
1129
+ function useThreads(options) {
1130
+ const { backend, enabled = true } = options;
1131
+ const [threads, setThreads] = useState3([]);
1132
+ const [isLoading, setIsLoading] = useState3(enabled);
1133
+ const [error, setError] = useState3(null);
1134
+ const threadsRef = useRef3(threads);
1135
+ threadsRef.current = threads;
1136
+ const generation = useRef3(0);
1137
+ const refresh = useCallback2(async () => {
1138
+ const mine = ++generation.current;
1139
+ setIsLoading(true);
1140
+ try {
1141
+ const list = await backend.listThreads();
1142
+ if (mine !== generation.current) return;
1143
+ setThreads(list);
1144
+ setError(null);
1145
+ } catch (caught) {
1146
+ if (mine !== generation.current) return;
1147
+ setError(caught instanceof Error ? caught : new Error(String(caught)));
1148
+ } finally {
1149
+ if (mine === generation.current) setIsLoading(false);
1150
+ }
1151
+ }, [
1152
+ backend
1153
+ ]);
1154
+ useEffect2(() => {
1155
+ if (!enabled) return;
1156
+ void refresh();
1157
+ return onThreadsEvent(backend, (event) => {
1158
+ if (event.type === "title") {
1159
+ setThreads((current) => current.map((thread) => thread.id === event.threadId ? {
1160
+ ...thread,
1161
+ title: event.title
1162
+ } : thread));
1163
+ } else if (event.type === "removed") {
1164
+ setThreads((current) => current.filter((thread) => thread.id !== event.threadId));
1165
+ } else {
1166
+ void refresh();
1167
+ }
1168
+ });
1169
+ }, [
1170
+ backend,
1171
+ enabled,
1172
+ refresh
1173
+ ]);
1174
+ const rename = useCallback2(async (id, title) => {
1175
+ const before = threadsRef.current;
1176
+ setThreads((current) => current.map((thread) => thread.id === id ? {
1177
+ ...thread,
1178
+ title
1179
+ } : thread));
1180
+ try {
1181
+ await backend.updateThread(id, {
1182
+ title
1183
+ });
1184
+ notifyThreads(backend, {
1185
+ type: "title",
1186
+ threadId: id,
1187
+ title
1188
+ });
1189
+ } catch (caught) {
1190
+ setThreads(before);
1191
+ throw caught;
1192
+ }
1193
+ }, [
1194
+ backend
1195
+ ]);
1196
+ const remove = useCallback2(async (id) => {
1197
+ const before = threadsRef.current;
1198
+ setThreads((current) => current.filter((thread) => thread.id !== id));
1199
+ try {
1200
+ await backend.deleteThread(id);
1201
+ notifyThreads(backend, {
1202
+ type: "removed",
1203
+ threadId: id
1204
+ });
1205
+ } catch (caught) {
1206
+ setThreads(before);
1207
+ throw caught;
1208
+ }
1209
+ }, [
1210
+ backend
1211
+ ]);
1212
+ return {
1213
+ threads,
1214
+ isLoading,
1215
+ error,
1216
+ refresh,
1217
+ rename,
1218
+ remove
1219
+ };
1220
+ }
1221
+ __name(useThreads, "useThreads");
607
1222
 
608
1223
  // src/background-runs.ts
609
1224
  function asReceipt(output) {
@@ -652,170 +1267,11 @@ function backgroundRunsFromThread(messages) {
652
1267
  }
653
1268
  __name(backgroundRunsFromThread, "backgroundRunsFromThread");
654
1269
 
655
- // src/client.ts
656
- var AgentHttpError = class extends Error {
657
- static {
658
- __name(this, "AgentHttpError");
659
- }
660
- status;
661
- method;
662
- path;
663
- constructor(status, method, path, statusText) {
664
- super(`Agent request failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
665
- this.name = "AgentHttpError";
666
- }
667
- };
668
- var AgentClient = class {
669
- static {
670
- __name(this, "AgentClient");
671
- }
672
- options;
673
- constructor(options = {}) {
674
- this.options = options;
675
- }
676
- listThreads() {
677
- return this.request("GET", "/agent/threads");
678
- }
679
- /**
680
- * The skills this caller can invoke right now, scope-resolved — the same list, built by the same
681
- * call, that the model is offered, so what a user can type after a `/` and what the agent can
682
- * reach cannot drift apart. `threadId` reaches the host's own resolver, which may scope a skill to
683
- * one conversation; omitted, the server reads it as a brand-new thread.
684
- */
685
- listSkills(threadId) {
686
- const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
687
- return this.request("GET", `/agent/skills${query}`);
688
- }
689
- /**
690
- * The tools this caller can reach through `agent` (the default agent when omitted), each with the
691
- * server-declared `presentation` a chat narrates it by — the same list the model is offered.
692
- * Prefer {@link useToolCatalog}, which fetches it once and shares it.
693
- */
694
- listTools(agent) {
695
- const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
696
- return this.request("GET", `/agent/tools${query}`);
697
- }
698
- getThread(id) {
699
- return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
700
- }
701
- deleteThread(id) {
702
- return this.request("DELETE", `/agent/threads/${encodeURIComponent(id)}`);
703
- }
704
- forkFromMessage(threadId, messageId) {
705
- return this.request("POST", `/agent/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
706
- }
707
- renameThread(id, title) {
708
- return this.updateThread(id, {
709
- title
710
- });
711
- }
712
- /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
713
- updateThread(id, patch) {
714
- return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, patch);
715
- }
716
- /**
717
- * Uploads a file (image/PDF) for a vision-capable model turn. Multipart, field name `file` —
718
- * mirrors the backend's `POST /agent/attachments`. The returned {@link MessageAttachment} is
719
- * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
720
- */
721
- async uploadAttachment(file) {
722
- const fetchImpl = this.options.fetch ?? globalThis.fetch;
723
- const baseUrl = (this.options.baseUrl ?? "").replace(/\/$/, "");
724
- const dynamic = await this.options.getHeaders?.() ?? {};
725
- const formData = new FormData();
726
- formData.append("file", file);
727
- const response = await fetchImpl(`${baseUrl}/agent/attachments`, {
728
- method: "POST",
729
- headers: {
730
- accept: "application/json",
731
- ...this.options.headers,
732
- ...dynamic
733
- },
734
- body: formData,
735
- ...this.options.credentials !== void 0 ? {
736
- credentials: this.options.credentials
737
- } : {}
738
- });
739
- return this.handleResponse(response, "POST", "/agent/attachments");
740
- }
741
- promoteThread(id) {
742
- return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
743
- }
744
- truncateFromMessage(threadId, messageId) {
745
- return this.request("DELETE", `/agent/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`);
746
- }
747
- getQuotaToday() {
748
- return this.request("GET", "/agent/quota/today");
749
- }
750
- cancelStream(runId) {
751
- return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
752
- }
753
- /**
754
- * `remember` approves later calls of the same tool in the same thread; `via` names the surface
755
- * the decision came through (the server records `'web'` when omitted).
756
- */
757
- approveToolCall(input) {
758
- return this.request("POST", "/agent/tool-call/approve", input);
759
- }
760
- rejectToolCall(input) {
761
- return this.request("POST", "/agent/tool-call/reject", input);
762
- }
763
- /**
764
- * Settle a parked question set. `answers` is questionId → chosen option values; a question left
765
- * out takes the pre-picked default the request carried, resolved server-side against the request
766
- * the run already holds. Omit the whole object and the user has confirmed every pre-picked
767
- * answer — which is the point of the surface, so it is a valid submission rather than a blank.
768
- */
769
- answerToolCall(input) {
770
- return this.request("POST", "/agent/tool-call/answer", input);
771
- }
772
- /**
773
- * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
774
- * values a confirmation would, and persists differently on purpose — only one of them is
775
- * evidence the user chose them.
776
- */
777
- skipToolCall(input) {
778
- return this.request("POST", "/agent/tool-call/skip", input);
779
- }
780
- async request(method, path, body) {
781
- const fetchImpl = this.options.fetch ?? globalThis.fetch;
782
- const baseUrl = (this.options.baseUrl ?? "").replace(/\/$/, "");
783
- const dynamic = await this.options.getHeaders?.() ?? {};
784
- const response = await fetchImpl(`${baseUrl}${path}`, {
785
- method,
786
- headers: {
787
- accept: "application/json",
788
- ...body !== void 0 ? {
789
- "content-type": "application/json"
790
- } : {},
791
- ...this.options.headers,
792
- ...dynamic
793
- },
794
- ...body !== void 0 ? {
795
- body: JSON.stringify(body)
796
- } : {},
797
- ...this.options.credentials !== void 0 ? {
798
- credentials: this.options.credentials
799
- } : {}
800
- });
801
- return this.handleResponse(response, method, path);
802
- }
803
- async handleResponse(response, method, path) {
804
- if (!response.ok) {
805
- throw new AgentHttpError(response.status, method, path, response.statusText);
806
- }
807
- if (response.status === 204) return void 0;
808
- const text = await response.text();
809
- if (!text) return void 0;
810
- return JSON.parse(text);
811
- }
812
- };
813
-
814
1270
  // src/components/chat-input.tsx
815
- import React2, { useEffect as useEffect3, useState as useState3 } from "react";
1271
+ import React2, { useEffect as useEffect4, useState as useState5 } from "react";
816
1272
 
817
1273
  // src/components/use-speech-recognition.ts
818
- import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
1274
+ import { useCallback as useCallback3, useEffect as useEffect3, useMemo, useRef as useRef4, useState as useState4 } from "react";
819
1275
  function getRecognitionCtor() {
820
1276
  if (typeof window === "undefined") return null;
821
1277
  const scope = window;
@@ -823,15 +1279,15 @@ function getRecognitionCtor() {
823
1279
  }
824
1280
  __name(getRecognitionCtor, "getRecognitionCtor");
825
1281
  function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
826
- const recognitionRef = useRef2(null);
827
- const [isListening, setIsListening] = useState2(false);
1282
+ const recognitionRef = useRef4(null);
1283
+ const [isListening, setIsListening] = useState4(false);
828
1284
  const ctor = useMemo(() => getRecognitionCtor(), []);
829
1285
  const isSupported = ctor !== null;
830
- const finalRef = useRef2(onFinalTranscript);
831
- const interimRef = useRef2(onInterimTranscript);
1286
+ const finalRef = useRef4(onFinalTranscript);
1287
+ const interimRef = useRef4(onInterimTranscript);
832
1288
  finalRef.current = onFinalTranscript;
833
1289
  interimRef.current = onInterimTranscript;
834
- useEffect2(() => {
1290
+ useEffect3(() => {
835
1291
  if (!ctor) return;
836
1292
  const recognition = new ctor();
837
1293
  recognition.lang = lang;
@@ -870,7 +1326,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
870
1326
  ctor,
871
1327
  lang
872
1328
  ]);
873
- const start = useCallback(() => {
1329
+ const start = useCallback3(() => {
874
1330
  const recognition = recognitionRef.current;
875
1331
  if (!recognition) return;
876
1332
  try {
@@ -880,7 +1336,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
880
1336
  console.warn("SpeechRecognition start failed", error);
881
1337
  }
882
1338
  }, []);
883
- const stop = useCallback(() => {
1339
+ const stop = useCallback3(() => {
884
1340
  const recognition = recognitionRef.current;
885
1341
  if (!recognition) return;
886
1342
  try {
@@ -899,8 +1355,8 @@ __name(useSpeechRecognition, "useSpeechRecognition");
899
1355
 
900
1356
  // src/components/chat-input.tsx
901
1357
  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 }) {
902
- const [value, setValue] = useState3("");
903
- const [interim, setInterim] = useState3("");
1358
+ const [value, setValue] = useState5("");
1359
+ const [interim, setInterim] = useState5("");
904
1360
  const speech = useSpeechRecognition({
905
1361
  lang: speechLang,
906
1362
  onFinalTranscript: /* @__PURE__ */ __name((text) => {
@@ -911,7 +1367,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
911
1367
  }, "onFinalTranscript"),
912
1368
  onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
913
1369
  });
914
- useEffect3(() => {
1370
+ useEffect4(() => {
915
1371
  if (!speech.isListening) setInterim("");
916
1372
  }, [
917
1373
  speech.isListening
@@ -978,7 +1434,7 @@ __name(ChatInput, "ChatInput");
978
1434
  import React3 from "react";
979
1435
 
980
1436
  // src/transcript/use-chat-transcript.ts
981
- import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
1437
+ import { useCallback as useCallback5, useEffect as useEffect6, useRef as useRef6, useState as useState7 } from "react";
982
1438
 
983
1439
  // src/transcript/model.ts
984
1440
  import { readElicitationInput, validateElicitationAnswer as validateElicitationAnswer2 } from "@dudousxd/nestjs-agent-core";
@@ -1955,11 +2411,11 @@ function formatCostUsd(cost) {
1955
2411
  __name(formatCostUsd, "formatCostUsd");
1956
2412
 
1957
2413
  // src/transcript/use-stick-to-bottom.ts
1958
- import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
2414
+ import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5, useState as useState6 } from "react";
1959
2415
  function useStickToBottom({ contentKey, threshold = 32 }) {
1960
- const containerRef = useRef3(null);
1961
- const [isAtBottom, setIsAtBottom] = useState4(true);
1962
- const measure = useCallback2(() => {
2416
+ const containerRef = useRef5(null);
2417
+ const [isAtBottom, setIsAtBottom] = useState6(true);
2418
+ const measure = useCallback4(() => {
1963
2419
  const element = containerRef.current;
1964
2420
  if (!element) {
1965
2421
  return;
@@ -1969,7 +2425,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1969
2425
  }, [
1970
2426
  threshold
1971
2427
  ]);
1972
- const scrollToBottom = useCallback2(() => {
2428
+ const scrollToBottom = useCallback4(() => {
1973
2429
  const element = containerRef.current;
1974
2430
  if (!element) {
1975
2431
  return;
@@ -1977,7 +2433,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1977
2433
  element.scrollTop = element.scrollHeight;
1978
2434
  setIsAtBottom(true);
1979
2435
  }, []);
1980
- const setContainer = useCallback2((element) => {
2436
+ const setContainer = useCallback4((element) => {
1981
2437
  containerRef.current = element;
1982
2438
  if (element) {
1983
2439
  measure();
@@ -1985,7 +2441,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1985
2441
  }, [
1986
2442
  measure
1987
2443
  ]);
1988
- useEffect4(() => {
2444
+ useEffect5(() => {
1989
2445
  if (isAtBottom) {
1990
2446
  scrollToBottom();
1991
2447
  }
@@ -1994,7 +2450,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
1994
2450
  isAtBottom,
1995
2451
  scrollToBottom
1996
2452
  ]);
1997
- const getContainerProps = useCallback2(() => ({
2453
+ const getContainerProps = useCallback4(() => ({
1998
2454
  ref: setContainer,
1999
2455
  onScroll: measure
2000
2456
  }), [
@@ -2016,14 +2472,14 @@ var DEFAULT_LOAD_EARLIER_STEP = 50;
2016
2472
  var DEFAULT_COPY_RESET_MS = 1500;
2017
2473
  function useChatTranscript(options) {
2018
2474
  const { messages, status, initialVisibleCount = DEFAULT_VISIBLE_COUNT, loadEarlierStep = DEFAULT_LOAD_EARLIER_STEP, followUps, stickToBottomThreshold } = options;
2019
- const latest = useRef4(options);
2475
+ const latest = useRef6(options);
2020
2476
  latest.current = options;
2021
- const [visibleCount, setVisibleCount] = useState5(initialVisibleCount);
2022
- const [stopRequested, setStopRequested] = useState5(false);
2023
- const isStreaming = status === "streaming";
2477
+ const [visibleCount, setVisibleCount] = useState7(initialVisibleCount);
2478
+ const [stopRequested, setStopRequested] = useState7(false);
2479
+ const isStreaming = status === "streaming" || status === "reconnecting";
2024
2480
  const isBusy = status === "submitted" || isStreaming;
2025
2481
  const isEmpty = messages.length === 0;
2026
- useEffect5(() => {
2482
+ useEffect6(() => {
2027
2483
  if (!isBusy) {
2028
2484
  setStopRequested(false);
2029
2485
  }
@@ -2040,16 +2496,16 @@ function useChatTranscript(options) {
2040
2496
  streamingMessageId,
2041
2497
  options
2042
2498
  });
2043
- const stop = useCallback3(() => {
2499
+ const stop = useCallback5(() => {
2044
2500
  const current = latest.current;
2045
- if (current.status !== "submitted" && current.status !== "streaming") {
2501
+ if (current.status !== "submitted" && current.status !== "streaming" && current.status !== "reconnecting") {
2046
2502
  return;
2047
2503
  }
2048
2504
  setStopRequested(true);
2049
2505
  void current.onStop?.();
2050
2506
  }, []);
2051
- const loadEarlier = useCallback3(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
2052
- const showAll = useCallback3(() => setVisibleCount(latest.current.messages.length), []);
2507
+ const loadEarlier = useCallback5(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
2508
+ const showAll = useCallback5(() => setVisibleCount(latest.current.messages.length), []);
2053
2509
  const scroll = useStickToBottom({
2054
2510
  contentKey: contentSignature(messages),
2055
2511
  ...stickToBottomThreshold !== void 0 ? {
@@ -2085,7 +2541,7 @@ function useChatTranscript(options) {
2085
2541
  __name(useChatTranscript, "useChatTranscript");
2086
2542
  function useTranscriptItem(options) {
2087
2543
  const { message, isStreaming = false } = options;
2088
- const messagesRef = useRef4([
2544
+ const messagesRef = useRef6([
2089
2545
  message
2090
2546
  ]);
2091
2547
  if (messagesRef.current[0] !== message) {
@@ -2103,13 +2559,13 @@ function useTranscriptItem(options) {
2103
2559
  }
2104
2560
  __name(useTranscriptItem, "useTranscriptItem");
2105
2561
  function useTranscriptItems({ messages, visibleStart, streamingMessageId, options }) {
2106
- const [drafts, setDrafts] = useState5(() => /* @__PURE__ */ new Map());
2107
- const [copied, setCopied] = useState5(() => /* @__PURE__ */ new Set());
2108
- const [openReasoning, setOpenReasoning] = useState5(() => /* @__PURE__ */ new Map());
2109
- const [picks, setPicks] = useState5(() => /* @__PURE__ */ new Map());
2110
- const [settling, setSettling] = useState5(() => /* @__PURE__ */ new Map());
2111
- const [settleErrors, setSettleErrors] = useState5(() => /* @__PURE__ */ new Map());
2112
- const latest = useRef4({
2562
+ const [drafts, setDrafts] = useState7(() => /* @__PURE__ */ new Map());
2563
+ const [copied, setCopied] = useState7(() => /* @__PURE__ */ new Set());
2564
+ const [openReasoning, setOpenReasoning] = useState7(() => /* @__PURE__ */ new Map());
2565
+ const [picks, setPicks] = useState7(() => /* @__PURE__ */ new Map());
2566
+ const [settling, setSettling] = useState7(() => /* @__PURE__ */ new Map());
2567
+ const [settleErrors, setSettleErrors] = useState7(() => /* @__PURE__ */ new Map());
2568
+ const latest = useRef6({
2113
2569
  options,
2114
2570
  drafts,
2115
2571
  picks,
@@ -2121,23 +2577,23 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2121
2577
  picks,
2122
2578
  messages
2123
2579
  };
2124
- const copyTimers = useRef4(/* @__PURE__ */ new Map());
2125
- useEffect5(() => () => {
2580
+ const copyTimers = useRef6(/* @__PURE__ */ new Map());
2581
+ useEffect6(() => () => {
2126
2582
  for (const timer of copyTimers.current.values()) {
2127
2583
  clearTimeout(timer);
2128
2584
  }
2129
2585
  copyTimers.current.clear();
2130
2586
  }, []);
2131
- const callbackCache = useRef4(/* @__PURE__ */ new Map());
2132
- const reasoningToggleCache = useRef4(/* @__PURE__ */ new Map());
2133
- const toggleReasoning = useCallback3((key, open) => {
2587
+ const callbackCache = useRef6(/* @__PURE__ */ new Map());
2588
+ const reasoningToggleCache = useRef6(/* @__PURE__ */ new Map());
2589
+ const toggleReasoning = useCallback5((key, open) => {
2134
2590
  setOpenReasoning((current) => {
2135
2591
  const next = new Map(current);
2136
2592
  next.set(key, open ?? !(current.get(key) ?? false));
2137
2593
  return next;
2138
2594
  });
2139
2595
  }, []);
2140
- const stableToggle = useCallback3((key) => {
2596
+ const stableToggle = useCallback5((key) => {
2141
2597
  let cached = reasoningToggleCache.current.get(key);
2142
2598
  if (!cached) {
2143
2599
  cached = /* @__PURE__ */ __name((open) => toggleReasoning(key, open), "cached");
@@ -2147,10 +2603,10 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2147
2603
  }, [
2148
2604
  toggleReasoning
2149
2605
  ]);
2150
- const pick = useCallback3((toolCallId, questionId, values) => {
2606
+ const pick = useCallback5((toolCallId, questionId, values) => {
2151
2607
  setPicks((current) => new Map(current).set(pickKey(toolCallId, questionId), values));
2152
2608
  }, []);
2153
- const settle = useCallback3((toolCallId, action, send) => {
2609
+ const settle = useCallback5((toolCallId, action, send) => {
2154
2610
  setSettleErrors((current) => {
2155
2611
  if (!current.has(toolCallId)) {
2156
2612
  return current;
@@ -2174,7 +2630,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2174
2630
  failed(error);
2175
2631
  }
2176
2632
  }, []);
2177
- const answer = useCallback3((toolCallId) => {
2633
+ const answer = useCallback5((toolCallId) => {
2178
2634
  const current = latest.current;
2179
2635
  const onAnswer = current.options.onAnswer;
2180
2636
  if (!onAnswer) {
@@ -2184,7 +2640,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2184
2640
  }, [
2185
2641
  settle
2186
2642
  ]);
2187
- const skip = useCallback3((toolCallId) => {
2643
+ const skip = useCallback5((toolCallId) => {
2188
2644
  const onSkip = latest.current.options.onSkip;
2189
2645
  if (onSkip) {
2190
2646
  settle(toolCallId, "skip", () => onSkip(toolCallId));
@@ -2192,7 +2648,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2192
2648
  }, [
2193
2649
  settle
2194
2650
  ]);
2195
- const approve = useCallback3((toolCallId, options2) => {
2651
+ const approve = useCallback5((toolCallId, options2) => {
2196
2652
  const onApprove = latest.current.options.onApprove;
2197
2653
  if (onApprove) {
2198
2654
  settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
@@ -2200,7 +2656,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2200
2656
  }, [
2201
2657
  settle
2202
2658
  ]);
2203
- const reject = useCallback3((toolCallId) => {
2659
+ const reject = useCallback5((toolCallId) => {
2204
2660
  const onReject = latest.current.options.onReject;
2205
2661
  if (onReject) {
2206
2662
  settle(toolCallId, "reject", () => onReject(toolCallId));
@@ -2208,7 +2664,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2208
2664
  }, [
2209
2665
  settle
2210
2666
  ]);
2211
- const callbacksFor = useCallback3((id) => {
2667
+ const callbacksFor = useCallback5((id) => {
2212
2668
  const cached = callbackCache.current.get(id);
2213
2669
  if (cached) {
2214
2670
  return cached;
@@ -2831,7 +3287,11 @@ function createSkillsSource(options) {
2831
3287
  if (cached) {
2832
3288
  return cached;
2833
3289
  }
2834
- const pending = options.client.listSkills(threadId).then((entries) => entries.map(toItem)).catch((error) => {
3290
+ const listSkills = options.client.listSkills;
3291
+ if (listSkills === void 0) {
3292
+ return Promise.reject(new AgentBackendUnsupportedError("listSkills"));
3293
+ }
3294
+ const pending = listSkills.call(options.client, threadId).then((entries) => entries.map(toItem)).catch((error) => {
2835
3295
  byThread.delete(key);
2836
3296
  throw error;
2837
3297
  });
@@ -2861,18 +3321,18 @@ function toItem(entry) {
2861
3321
  __name(toItem, "toItem");
2862
3322
 
2863
3323
  // src/composer/use-composer-autocomplete.ts
2864
- import { useCallback as useCallback4, useEffect as useEffect6, useId, useLayoutEffect, useMemo as useMemo2, useRef as useRef5, useState as useState6 } from "react";
3324
+ import { useCallback as useCallback6, useEffect as useEffect7, useId, useLayoutEffect, useMemo as useMemo2, useRef as useRef7, useState as useState8 } from "react";
2865
3325
  function useComposerAutocomplete(options) {
2866
3326
  const { value, sources, acceptOnTab = true } = options;
2867
- const latest = useRef5(options);
3327
+ const latest = useRef7(options);
2868
3328
  latest.current = options;
2869
- const inputRef = useRef5(null);
2870
- const [caret, setCaret] = useState6(value.length);
2871
- const [items, setItems] = useState6([]);
2872
- const [isLoading, setLoading] = useState6(false);
2873
- const [error, setError] = useState6(null);
2874
- const [activeIndex, setActiveIndex] = useState6(0);
2875
- const [dismissedIndex, setDismissedIndex] = useState6(null);
3329
+ const inputRef = useRef7(null);
3330
+ const [caret, setCaret] = useState8(value.length);
3331
+ const [items, setItems] = useState8([]);
3332
+ const [isLoading, setLoading] = useState8(false);
3333
+ const [error, setError] = useState8(null);
3334
+ const [activeIndex, setActiveIndex] = useState8(0);
3335
+ const [dismissedIndex, setDismissedIndex] = useState8(null);
2876
3336
  const safeCaret = Math.min(caret, value.length);
2877
3337
  const match = useMemo2(() => findActiveTrigger(value, safeCaret, sources), [
2878
3338
  value,
@@ -2880,16 +3340,16 @@ function useComposerAutocomplete(options) {
2880
3340
  sources
2881
3341
  ]);
2882
3342
  const isActive = match !== null && dismissedIndex !== match.index;
2883
- const matchRef = useRef5(match);
3343
+ const matchRef = useRef7(match);
2884
3344
  matchRef.current = match;
2885
- const caretRef = useRef5(safeCaret);
3345
+ const caretRef = useRef7(safeCaret);
2886
3346
  caretRef.current = safeCaret;
2887
- const itemsRef = useRef5(items);
3347
+ const itemsRef = useRef7(items);
2888
3348
  itemsRef.current = items;
2889
- const activeIndexRef = useRef5(activeIndex);
3349
+ const activeIndexRef = useRef7(activeIndex);
2890
3350
  activeIndexRef.current = activeIndex;
2891
3351
  const requestKey = isActive && match ? `${match.source.id}\0${match.query}` : null;
2892
- useEffect6(() => {
3352
+ useEffect7(() => {
2893
3353
  const current = matchRef.current;
2894
3354
  if (requestKey === null || current === null) {
2895
3355
  setItems([]);
@@ -2939,23 +3399,23 @@ function useComposerAutocomplete(options) {
2939
3399
  const activeItem = isOpen ? items[activeIndex] ?? null : null;
2940
3400
  const reactId = useId();
2941
3401
  const listboxId = options.id ?? `agent-autocomplete-${reactId}`;
2942
- const optionId = useCallback4((index) => `${listboxId}-option-${index}`, [
3402
+ const optionId = useCallback6((index) => `${listboxId}-option-${index}`, [
2943
3403
  listboxId
2944
3404
  ]);
2945
- const highlight = useCallback4((index) => {
3405
+ const highlight = useCallback6((index) => {
2946
3406
  const total = itemsRef.current.length;
2947
3407
  if (total === 0) return;
2948
3408
  setActiveIndex((index % total + total) % total);
2949
3409
  }, []);
2950
- const moveHighlight = useCallback4((delta) => highlight(activeIndexRef.current + delta), [
3410
+ const moveHighlight = useCallback6((delta) => highlight(activeIndexRef.current + delta), [
2951
3411
  highlight
2952
3412
  ]);
2953
- const dismiss = useCallback4(() => {
3413
+ const dismiss = useCallback6(() => {
2954
3414
  const current = matchRef.current;
2955
3415
  if (current) setDismissedIndex(current.index);
2956
3416
  }, []);
2957
- const pendingCaret = useRef5(null);
2958
- const accept = useCallback4((item) => {
3417
+ const pendingCaret = useRef7(null);
3418
+ const accept = useCallback6((item) => {
2959
3419
  const current = matchRef.current;
2960
3420
  if (!current) return;
2961
3421
  const chosen = item ?? itemsRef.current[activeIndexRef.current];
@@ -2972,10 +3432,10 @@ function useComposerAutocomplete(options) {
2972
3432
  pendingCaret.current = null;
2973
3433
  inputRef.current?.setSelectionRange(position, position);
2974
3434
  });
2975
- const readCaret = useCallback4((element) => {
3435
+ const readCaret = useCallback6((element) => {
2976
3436
  setCaret(element.selectionStart ?? element.value.length);
2977
3437
  }, []);
2978
- const onKeyDown = useCallback4((event) => {
3438
+ const onKeyDown = useCallback6((event) => {
2979
3439
  if (!isOpen) return;
2980
3440
  const hasItems = itemsRef.current.length > 0;
2981
3441
  switch (event.key) {
@@ -3013,23 +3473,23 @@ function useComposerAutocomplete(options) {
3013
3473
  dismiss,
3014
3474
  moveHighlight
3015
3475
  ]);
3016
- const onChange = useCallback4((event) => {
3476
+ const onChange = useCallback6((event) => {
3017
3477
  readCaret(event.target);
3018
3478
  const next = findActiveTrigger(event.target.value, event.target.selectionStart ?? event.target.value.length, latest.current.sources);
3019
3479
  setDismissedIndex((current) => current !== null && next?.index === current ? current : null);
3020
3480
  }, [
3021
3481
  readCaret
3022
3482
  ]);
3023
- const onSelect = useCallback4((event) => readCaret(event.currentTarget), [
3483
+ const onSelect = useCallback6((event) => readCaret(event.currentTarget), [
3024
3484
  readCaret
3025
3485
  ]);
3026
- const onClick = useCallback4((event) => readCaret(event.currentTarget), [
3486
+ const onClick = useCallback6((event) => readCaret(event.currentTarget), [
3027
3487
  readCaret
3028
3488
  ]);
3029
- const setInput = useCallback4((element) => {
3489
+ const setInput = useCallback6((element) => {
3030
3490
  inputRef.current = element;
3031
3491
  }, []);
3032
- const getInputProps = useCallback4(() => ({
3492
+ const getInputProps = useCallback6(() => ({
3033
3493
  ref: setInput,
3034
3494
  onChange,
3035
3495
  onKeyDown,
@@ -3057,7 +3517,7 @@ function useComposerAutocomplete(options) {
3057
3517
  activeIndex,
3058
3518
  optionId
3059
3519
  ]);
3060
- const getListboxProps = useCallback4(() => ({
3520
+ const getListboxProps = useCallback6(() => ({
3061
3521
  id: listboxId,
3062
3522
  role: "listbox",
3063
3523
  "aria-label": match?.source.label ?? match?.source.id ?? "Suggestions"
@@ -3065,7 +3525,7 @@ function useComposerAutocomplete(options) {
3065
3525
  listboxId,
3066
3526
  match
3067
3527
  ]);
3068
- const getOptionProps = useCallback4((index) => ({
3528
+ const getOptionProps = useCallback6((index) => ({
3069
3529
  id: optionId(index),
3070
3530
  role: "option",
3071
3531
  "aria-selected": index === activeIndex
@@ -3099,7 +3559,7 @@ function isPromise(value) {
3099
3559
  __name(isPromise, "isPromise");
3100
3560
 
3101
3561
  // src/presentation/use-tool-catalog.ts
3102
- import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo3, useState as useState7 } from "react";
3562
+ import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo3, useState as useState9 } from "react";
3103
3563
  var shared = /* @__PURE__ */ new WeakMap();
3104
3564
  function load(client, agent) {
3105
3565
  let byAgent = shared.get(client);
@@ -3110,6 +3570,9 @@ function load(client, agent) {
3110
3570
  const key = agent ?? "";
3111
3571
  const cached = byAgent.get(key);
3112
3572
  if (cached !== void 0) return cached;
3573
+ if (client.listTools === void 0) {
3574
+ return Promise.reject(new AgentBackendUnsupportedError("listTools"));
3575
+ }
3113
3576
  const request = client.listTools(agent).catch((error) => {
3114
3577
  byAgent.delete(key);
3115
3578
  throw error;
@@ -3120,11 +3583,11 @@ function load(client, agent) {
3120
3583
  __name(load, "load");
3121
3584
  function useToolCatalog(options) {
3122
3585
  const { client, agent, enabled = true } = options;
3123
- const [entries, setEntries] = useState7([]);
3124
- const [isLoading, setLoading] = useState7(enabled);
3125
- const [error, setError] = useState7(null);
3126
- const [generation, setGeneration] = useState7(0);
3127
- useEffect7(() => {
3586
+ const [entries, setEntries] = useState9([]);
3587
+ const [isLoading, setLoading] = useState9(enabled);
3588
+ const [error, setError] = useState9(null);
3589
+ const [generation, setGeneration] = useState9(0);
3590
+ useEffect8(() => {
3128
3591
  if (!enabled) {
3129
3592
  setLoading(false);
3130
3593
  return;
@@ -3150,7 +3613,7 @@ function useToolCatalog(options) {
3150
3613
  enabled,
3151
3614
  generation
3152
3615
  ]);
3153
- const refresh = useCallback5(() => {
3616
+ const refresh = useCallback7(() => {
3154
3617
  shared.get(client)?.delete(agent ?? "");
3155
3618
  setGeneration((current) => current + 1);
3156
3619
  }, [
@@ -3171,7 +3634,7 @@ function useToolCatalog(options) {
3171
3634
  __name(useToolCatalog, "useToolCatalog");
3172
3635
 
3173
3636
  // src/approvals/countdown.ts
3174
- import { useEffect as useEffect8, useRef as useRef6, useState as useState8 } from "react";
3637
+ import { useEffect as useEffect9, useRef as useRef8, useState as useState10 } from "react";
3175
3638
  function approvalCountdown(expiresAt, now = Date.now()) {
3176
3639
  const deadline = expiresAt == null ? Number.NaN : Date.parse(expiresAt);
3177
3640
  if (!Number.isFinite(deadline)) {
@@ -3189,10 +3652,10 @@ function approvalCountdown(expiresAt, now = Date.now()) {
3189
3652
  __name(approvalCountdown, "approvalCountdown");
3190
3653
  function useApprovalCountdown(expiresAt, options = {}) {
3191
3654
  const { intervalMs = 1e3 } = options;
3192
- const nowRef = useRef6(options.now ?? Date.now);
3655
+ const nowRef = useRef8(options.now ?? Date.now);
3193
3656
  nowRef.current = options.now ?? Date.now;
3194
- const [state, setState] = useState8(() => approvalCountdown(expiresAt, nowRef.current()));
3195
- useEffect8(() => {
3657
+ const [state, setState] = useState10(() => approvalCountdown(expiresAt, nowRef.current()));
3658
+ useEffect9(() => {
3196
3659
  const read = /* @__PURE__ */ __name(() => approvalCountdown(expiresAt, nowRef.current()), "read");
3197
3660
  const first = read();
3198
3661
  setState(first);
@@ -3358,7 +3821,12 @@ function storedMessageToUiMessage(message) {
3358
3821
  return {
3359
3822
  id: message.id,
3360
3823
  role: message.role,
3361
- parts
3824
+ parts,
3825
+ ...message.feedback !== void 0 ? {
3826
+ metadata: {
3827
+ feedback: message.feedback
3828
+ }
3829
+ } : {}
3362
3830
  };
3363
3831
  }
3364
3832
  __name(storedMessageToUiMessage, "storedMessageToUiMessage");
@@ -3394,14 +3862,21 @@ function mergeTurn(rows) {
3394
3862
  }
3395
3863
  const parts = rows.flatMap((row) => storedMessageToUiMessage(row).parts);
3396
3864
  const usage = sumUsage(rows);
3865
+ const feedback = lastRow.feedback;
3866
+ const metadata = {
3867
+ ...usage !== void 0 ? {
3868
+ usage
3869
+ } : {},
3870
+ ...feedback !== void 0 ? {
3871
+ feedback
3872
+ } : {}
3873
+ };
3397
3874
  return {
3398
3875
  id: lastRow.id,
3399
3876
  role: lastRow.role,
3400
3877
  parts,
3401
- ...usage !== void 0 ? {
3402
- metadata: {
3403
- usage
3404
- }
3878
+ ...Object.keys(metadata).length > 0 ? {
3879
+ metadata
3405
3880
  } : {}
3406
3881
  };
3407
3882
  }
@@ -3422,20 +3897,21 @@ __name(sumUsage, "sumUsage");
3422
3897
 
3423
3898
  // src/use-agent-chat.ts
3424
3899
  import { useChat } from "@ai-sdk/react";
3425
- import { useCallback as useCallback6, useEffect as useEffect9, useMemo as useMemo4, useRef as useRef7, useState as useState9 } from "react";
3900
+ import { useCallback as useCallback8, useEffect as useEffect10, useMemo as useMemo4, useRef as useRef9, useState as useState11 } from "react";
3426
3901
  function useAgentChat(options) {
3427
- const latest = useRef7(options);
3902
+ const latest = useRef9(options);
3428
3903
  latest.current = options;
3429
- const [runId, setRunId] = useState9(options.resumeRunId);
3430
- const runIdRef = useRef7(runId);
3904
+ const [runId, setRunId] = useState11(options.resumeRunId);
3905
+ const runIdRef = useRef9(runId);
3431
3906
  runIdRef.current = runId;
3432
- const settlingRunIdRef = useRef7(void 0);
3433
- const autoResumeRunIdRef = useRef7(void 0);
3434
- const [autoResume, setAutoResume] = useState9(false);
3435
- const [activeRunId, setActiveRunId] = useState9(null);
3436
- const regenerateNext = useRef7(false);
3437
- const createdThreadId = useRef7(void 0);
3907
+ const settlingRunIdRef = useRef9(void 0);
3908
+ const autoResumeRunIdRef = useRef9(void 0);
3909
+ const [autoResume, setAutoResume] = useState11(false);
3910
+ const [activeRunId, setActiveRunId] = useState11(null);
3911
+ const regenerateNext = useRef9(false);
3912
+ const createdThreadId = useRef9(void 0);
3438
3913
  const client = useMemo4(() => {
3914
+ if (options.backend) return options.backend;
3439
3915
  if (options.client) return options.client;
3440
3916
  return new AgentClient({
3441
3917
  ...options.baseUrl !== void 0 ? {
@@ -3450,7 +3926,11 @@ function useAgentChat(options) {
3450
3926
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
3451
3927
  });
3452
3928
  }, []);
3453
- useEffect9(() => {
3929
+ const [connection, setConnection] = useState11({
3930
+ status: "live"
3931
+ });
3932
+ const resyncAfterSettle = useRef9(false);
3933
+ useEffect10(() => {
3454
3934
  if (!options.resume || options.threadId === void 0) return;
3455
3935
  let cancelled = false;
3456
3936
  const threadId = options.threadId;
@@ -3477,23 +3957,26 @@ function useAgentChat(options) {
3477
3957
  if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
3478
3958
  createdThreadId.current = meta.threadId;
3479
3959
  latest.current.onThreadCreated?.(meta.threadId);
3960
+ notifyThreads(client, {
3961
+ type: "changed"
3962
+ });
3480
3963
  }
3481
3964
  }
3482
3965
  __name(onMeta, "onMeta");
3483
3966
  return new AgentChatTransport({
3484
- ...options.baseUrl !== void 0 ? {
3485
- baseUrl: options.baseUrl
3486
- } : {},
3487
- ...options.credentials !== void 0 ? {
3488
- credentials: options.credentials
3489
- } : {},
3490
- ...options.fetch !== void 0 ? {
3491
- fetch: options.fetch
3967
+ backend: client,
3968
+ ...options.reconnect !== void 0 ? {
3969
+ reconnect: options.reconnect
3492
3970
  } : {},
3971
+ onConnectionChange: /* @__PURE__ */ __name((state) => {
3972
+ if (state.status === "gone") resyncAfterSettle.current = true;
3973
+ setConnection(state.status === "gone" ? {
3974
+ status: "live"
3975
+ } : state);
3976
+ }, "onConnectionChange"),
3493
3977
  ...options.agent !== void 0 ? {
3494
3978
  agent: options.agent
3495
3979
  } : {},
3496
- getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders"),
3497
3980
  getBody: /* @__PURE__ */ __name(() => {
3498
3981
  const current = latest.current;
3499
3982
  const pageContext = current.getPageContext?.() ?? null;
@@ -3519,7 +4002,7 @@ function useAgentChat(options) {
3519
4002
  }, "onAttemptStart")
3520
4003
  });
3521
4004
  }, []);
3522
- const backgroundRef = useRef7(() => Promise.resolve());
4005
+ const backgroundRef = useRef9(() => Promise.resolve());
3523
4006
  const chat = useChat({
3524
4007
  transport,
3525
4008
  resume: options.resumeRunId !== void 0 || autoResume,
@@ -3533,7 +4016,15 @@ function useAgentChat(options) {
3533
4016
  latest.current.onData?.(part);
3534
4017
  if (part.type === "data-title") {
3535
4018
  const title = part.data?.title;
3536
- if (typeof title === "string") latest.current.onTitle?.(title);
4019
+ if (typeof title === "string") {
4020
+ latest.current.onTitle?.(title);
4021
+ const threadId = latest.current.threadId ?? createdThreadId.current;
4022
+ if (threadId !== void 0) notifyThreads(client, {
4023
+ type: "title",
4024
+ threadId,
4025
+ title
4026
+ });
4027
+ }
3537
4028
  }
3538
4029
  }, "onData"),
3539
4030
  onFinish: /* @__PURE__ */ __name(({ isError }) => {
@@ -3547,16 +4038,36 @@ function useAgentChat(options) {
3547
4038
  runId: settledRunId,
3548
4039
  status: isError ? "failed" : "completed"
3549
4040
  });
4041
+ notifyThreads(client, {
4042
+ type: "changed"
4043
+ });
4044
+ }
4045
+ setConnection({
4046
+ status: "live"
4047
+ });
4048
+ if (resyncAfterSettle.current) {
4049
+ resyncAfterSettle.current = false;
4050
+ void resyncFromThread();
3550
4051
  }
3551
4052
  }, "onFinish")
3552
4053
  });
3553
- const chatRef = useRef7(chat);
4054
+ const chatRef = useRef9(chat);
3554
4055
  chatRef.current = chat;
3555
- const turnInFlight = useRef7(false);
3556
- const isTurnInFlight = useCallback6(() => turnInFlight.current || transport.isAttemptLive, [
4056
+ async function resyncFromThread() {
4057
+ const threadId = latest.current.threadId ?? createdThreadId.current;
4058
+ if (threadId === void 0) return;
4059
+ try {
4060
+ const thread = await client.getThread(threadId);
4061
+ chatRef.current.setMessages(storedThreadToUiMessages(thread.messages));
4062
+ } catch {
4063
+ }
4064
+ }
4065
+ __name(resyncFromThread, "resyncFromThread");
4066
+ const turnInFlight = useRef9(false);
4067
+ const isTurnInFlight = useCallback8(() => turnInFlight.current || transport.isAttemptLive, [
3557
4068
  transport
3558
4069
  ]);
3559
- const sendMessage = useCallback6(async (...args) => {
4070
+ const sendMessage = useCallback8(async (...args) => {
3560
4071
  if (isTurnInFlight()) return;
3561
4072
  turnInFlight.current = true;
3562
4073
  try {
@@ -3567,15 +4078,15 @@ function useAgentChat(options) {
3567
4078
  }, [
3568
4079
  isTurnInFlight
3569
4080
  ]);
3570
- const addToolResult = useCallback6(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
4081
+ const addToolResult = useCallback8(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
3571
4082
  tool,
3572
4083
  toolCallId,
3573
4084
  output
3574
4085
  }), []);
3575
- const [threads, setThreads] = useState9([]);
3576
- const [quota, setQuota] = useState9(null);
3577
- const [backgroundRuns, setBackgroundRuns] = useState9([]);
3578
- const refreshBackground = useCallback6(async () => {
4086
+ const [threads, setThreads] = useState11([]);
4087
+ const [quota, setQuota] = useState11(null);
4088
+ const [backgroundRuns, setBackgroundRuns] = useState11([]);
4089
+ const refreshBackground = useCallback8(async () => {
3579
4090
  const threadId = latest.current.threadId ?? createdThreadId.current;
3580
4091
  if (threadId === void 0) {
3581
4092
  return;
@@ -3600,7 +4111,7 @@ function useAgentChat(options) {
3600
4111
  ]);
3601
4112
  backgroundRef.current = refreshBackground;
3602
4113
  const isWorking = backgroundRuns.some((run) => run.status === "running");
3603
- useEffect9(() => {
4114
+ useEffect10(() => {
3604
4115
  if (options.background !== true || options.threadId === void 0) {
3605
4116
  return;
3606
4117
  }
@@ -3610,7 +4121,7 @@ function useAgentChat(options) {
3610
4121
  options.threadId,
3611
4122
  refreshBackground
3612
4123
  ]);
3613
- useEffect9(() => {
4124
+ useEffect10(() => {
3614
4125
  const every = latest.current.backgroundPollMs ?? 5e3;
3615
4126
  if (latest.current.background !== true || !isWorking || every <= 0) {
3616
4127
  return;
@@ -3621,27 +4132,29 @@ function useAgentChat(options) {
3621
4132
  isWorking,
3622
4133
  refreshBackground
3623
4134
  ]);
3624
- const loadThreads = useCallback6(async () => {
4135
+ const loadThreads = useCallback8(async () => {
3625
4136
  const list = await client.listThreads();
3626
4137
  setThreads(list);
3627
4138
  return list;
3628
4139
  }, [
3629
4140
  client
3630
4141
  ]);
3631
- const loadThread = useCallback6((id) => client.getThread(id), [
4142
+ const loadThread = useCallback8((id) => client.getThread(id), [
3632
4143
  client
3633
4144
  ]);
3634
- const deleteThread = useCallback6(async (id) => {
4145
+ const deleteThread = useCallback8(async (id) => {
3635
4146
  await client.deleteThread(id);
3636
4147
  setThreads((current) => current.filter((thread) => thread.id !== id));
3637
4148
  }, [
3638
4149
  client
3639
4150
  ]);
3640
- const forkThread = useCallback6((threadId, messageId) => client.forkFromMessage(threadId, messageId), [
4151
+ const forkThread = useCallback8((threadId, messageId) => requireBackendMethod(client, "forkFromMessage")(threadId, messageId), [
3641
4152
  client
3642
4153
  ]);
3643
- const renameThread = useCallback6(async (id, title) => {
3644
- await client.renameThread(id, title);
4154
+ const renameThread = useCallback8(async (id, title) => {
4155
+ await client.updateThread(id, {
4156
+ title
4157
+ });
3645
4158
  setThreads((current) => current.map((thread) => thread.id === id ? {
3646
4159
  ...thread,
3647
4160
  title
@@ -3649,24 +4162,24 @@ function useAgentChat(options) {
3649
4162
  }, [
3650
4163
  client
3651
4164
  ]);
3652
- const promoteThread = useCallback6(async (id) => {
3653
- await client.promoteThread(id);
4165
+ const promoteThread = useCallback8(async (id) => {
4166
+ await requireBackendMethod(client, "promoteThread")(id);
3654
4167
  await loadThreads();
3655
4168
  }, [
3656
4169
  client,
3657
4170
  loadThreads
3658
4171
  ]);
3659
- const truncateFromMessage = useCallback6((threadId, messageId) => client.truncateFromMessage(threadId, messageId).then(() => void 0), [
4172
+ const truncateFromMessage = useCallback8((threadId, messageId) => requireBackendMethod(client, "truncateFromMessage")(threadId, messageId).then(() => void 0), [
3660
4173
  client
3661
4174
  ]);
3662
- const loadQuota = useCallback6(async () => {
3663
- const today = await client.getQuotaToday();
4175
+ const loadQuota = useCallback8(async () => {
4176
+ const today = await requireBackendMethod(client, "getQuotaToday")();
3664
4177
  setQuota(today);
3665
4178
  return today;
3666
4179
  }, [
3667
4180
  client
3668
4181
  ]);
3669
- const cancel = useCallback6(async () => {
4182
+ const cancel = useCallback8(async () => {
3670
4183
  chatRef.current.stop();
3671
4184
  const activeRunId2 = runIdRef.current;
3672
4185
  if (activeRunId2) {
@@ -3678,8 +4191,8 @@ function useAgentChat(options) {
3678
4191
  }, [
3679
4192
  client
3680
4193
  ]);
3681
- const approve = useCallback6(async ({ toolCallId, remember, via }) => {
3682
- await client.approveToolCall({
4194
+ const approve = useCallback8(async ({ toolCallId, remember, via }) => {
4195
+ await requireBackendMethod(client, "approveToolCall")({
3683
4196
  toolCallId,
3684
4197
  ...remember === true ? {
3685
4198
  remember: true
@@ -3691,8 +4204,8 @@ function useAgentChat(options) {
3691
4204
  }, [
3692
4205
  client
3693
4206
  ]);
3694
- const reject = useCallback6(async ({ toolCallId, reason, via }) => {
3695
- await client.rejectToolCall({
4207
+ const reject = useCallback8(async ({ toolCallId, reason, via }) => {
4208
+ await requireBackendMethod(client, "rejectToolCall")({
3696
4209
  toolCallId,
3697
4210
  ...reason !== void 0 ? {
3698
4211
  reason
@@ -3704,8 +4217,8 @@ function useAgentChat(options) {
3704
4217
  }, [
3705
4218
  client
3706
4219
  ]);
3707
- const answer = useCallback6(async ({ toolCallId, answers }) => {
3708
- await client.answerToolCall({
4220
+ const answer = useCallback8(async ({ toolCallId, answers }) => {
4221
+ await requireBackendMethod(client, "answerToolCall")({
3709
4222
  toolCallId,
3710
4223
  ...answers !== void 0 ? {
3711
4224
  answers
@@ -3714,14 +4227,14 @@ function useAgentChat(options) {
3714
4227
  }, [
3715
4228
  client
3716
4229
  ]);
3717
- const skip = useCallback6(async ({ toolCallId }) => {
3718
- await client.skipToolCall({
4230
+ const skip = useCallback8(async ({ toolCallId }) => {
4231
+ await requireBackendMethod(client, "skipToolCall")({
3719
4232
  toolCallId
3720
4233
  });
3721
4234
  }, [
3722
4235
  client
3723
4236
  ]);
3724
- const regenerate = useCallback6(() => {
4237
+ const regenerate = useCallback8(() => {
3725
4238
  if (isTurnInFlight()) return;
3726
4239
  regenerateNext.current = true;
3727
4240
  turnInFlight.current = true;
@@ -3736,8 +4249,13 @@ function useAgentChat(options) {
3736
4249
  isWorking,
3737
4250
  refresh: refreshBackground
3738
4251
  };
4252
+ const getThreadId = useCallback8(() => latest.current.threadId ?? createdThreadId.current, []);
4253
+ const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
3739
4254
  return {
3740
4255
  ...chat,
4256
+ status,
4257
+ /** Where the live stream stands — see {@link StreamConnectionState}. */
4258
+ connection,
3741
4259
  sendMessage,
3742
4260
  addToolResult,
3743
4261
  /** Sub-agents this conversation started and did not wait for. */
@@ -3745,6 +4263,14 @@ function useAgentChat(options) {
3745
4263
  runId,
3746
4264
  /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
3747
4265
  activeRunId,
4266
+ /**
4267
+ * The thread this chat is on right now: the `threadId` option, else the one the backend created
4268
+ * on the first send. A getter, because the created id is learned mid-stream.
4269
+ */
4270
+ getThreadId,
4271
+ /** The backend this chat talks to — pass it to `useThreads`, `useMessageFeedback`, … */
4272
+ backend: client,
4273
+ /** Same object as {@link backend}; kept for callers that read it under its old name. */
3748
4274
  client,
3749
4275
  threads,
3750
4276
  loadThreads,
@@ -3774,6 +4300,7 @@ async function mergeHeaders(options) {
3774
4300
  }
3775
4301
  __name(mergeHeaders, "mergeHeaders");
3776
4302
  export {
4303
+ AgentBackendUnsupportedError,
3777
4304
  AgentChatTransport,
3778
4305
  AgentClient,
3779
4306
  AgentHttpError,
@@ -3803,10 +4330,13 @@ export {
3803
4330
  inferResultView,
3804
4331
  isActionCall,
3805
4332
  loadStoredSpeechLang,
4333
+ notifyThreads,
4334
+ onThreadsEvent,
3806
4335
  persistSpeechLang,
3807
4336
  phraseFor,
3808
4337
  readPath,
3809
4338
  readReasoningMs,
4339
+ requireBackendMethod,
3810
4340
  resolveResultView,
3811
4341
  storedMessageToUiMessage,
3812
4342
  storedThreadToUiMessages,
@@ -3817,8 +4347,10 @@ export {
3817
4347
  useChatTranscript,
3818
4348
  useComposerAutocomplete,
3819
4349
  useElapsed,
4350
+ useMessageFeedback,
3820
4351
  useSpeechRecognition,
3821
4352
  useStickToBottom,
4353
+ useThreads,
3822
4354
  useToolCatalog,
3823
4355
  useTranscriptItem,
3824
4356
  validateElicitationAnswer as validateAnswer,