@dudousxd/nestjs-agent-react 0.20.0 → 0.22.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
@@ -14,6 +14,11 @@ var AgentHttpError = class extends Error {
14
14
  this.name = "AgentHttpError";
15
15
  }
16
16
  };
17
+ function normalizeAgentPath(path) {
18
+ const trimmed = (path ?? "agent").replace(/^\/+|\/+$/g, "");
19
+ return trimmed === "" ? "" : `/${trimmed}`;
20
+ }
21
+ __name(normalizeAgentPath, "normalizeAgentPath");
17
22
  var HEADER_RUN_ID = "x-agent-run-id";
18
23
  var HEADER_THREAD_ID = "x-agent-thread-id";
19
24
  var AgentClient = class {
@@ -24,9 +29,9 @@ var AgentClient = class {
24
29
  constructor(options = {}) {
25
30
  this.options = options;
26
31
  }
27
- /** `POST /agent/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
32
+ /** `POST <path>/chat` → the turn's SSE stream. Throws {@link AgentHttpError} on a non-2xx. */
28
33
  async openChatStream(request) {
29
- const response = await this.fetchImpl()(`${this.baseUrl()}/agent/chat`, {
34
+ const response = await this.fetchImpl()(`${this.root()}/chat`, {
30
35
  method: "POST",
31
36
  headers: {
32
37
  "content-type": "application/json",
@@ -41,18 +46,18 @@ var AgentClient = class {
41
46
  } : {}
42
47
  });
43
48
  if (!response.ok || !response.body) {
44
- throw new AgentHttpError(response.status, "POST", "/agent/chat", response.statusText);
49
+ throw new AgentHttpError(response.status, "POST", `${this.agentPath()}/chat`, response.statusText);
45
50
  }
46
51
  return streamResponse(response);
47
52
  }
48
53
  /**
49
- * `GET /agent/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
54
+ * `GET <path>/chat/:runId/stream[?after=<seq>]` → the run's SSE stream, or `null` when nothing is
50
55
  * streaming under that id (404).
51
56
  */
52
57
  async resumeChatStream(request) {
53
- const path = `/agent/chat/${encodeURIComponent(request.runId)}/stream`;
58
+ const path = `/chat/${encodeURIComponent(request.runId)}/stream`;
54
59
  const query = request.after !== void 0 && request.after > 0 ? `?after=${request.after}` : "";
55
- const response = await this.fetchImpl()(`${this.baseUrl()}${path}${query}`, {
60
+ const response = await this.fetchImpl()(`${this.root()}${path}${query}`, {
56
61
  method: "GET",
57
62
  headers: {
58
63
  accept: "text/event-stream",
@@ -68,7 +73,7 @@ var AgentClient = class {
68
73
  return null;
69
74
  }
70
75
  if (!response.ok || !response.body) {
71
- throw new AgentHttpError(response.status, "GET", path, response.statusText);
76
+ throw new AgentHttpError(response.status, "GET", `${this.agentPath()}${path}`, response.statusText);
72
77
  }
73
78
  return streamResponse(response);
74
79
  }
@@ -77,10 +82,10 @@ var AgentClient = class {
77
82
  * the stored rating.
78
83
  */
79
84
  setMessageFeedback(messageId, input) {
80
- return this.request("POST", `/agent/messages/${encodeURIComponent(messageId)}/feedback`, input);
85
+ return this.request("POST", `/messages/${encodeURIComponent(messageId)}/feedback`, input);
81
86
  }
82
87
  listThreads() {
83
- return this.request("GET", "/agent/threads");
88
+ return this.request("GET", "/threads");
84
89
  }
85
90
  /**
86
91
  * The skills this caller can invoke right now, scope-resolved — the same list, built by the same
@@ -90,7 +95,7 @@ var AgentClient = class {
90
95
  */
91
96
  listSkills(threadId) {
92
97
  const query = threadId === void 0 ? "" : `?threadId=${encodeURIComponent(threadId)}`;
93
- return this.request("GET", `/agent/skills${query}`);
98
+ return this.request("GET", `/skills${query}`);
94
99
  }
95
100
  /**
96
101
  * The tools this caller can reach through `agent` (the default agent when omitted), each with the
@@ -99,41 +104,37 @@ var AgentClient = class {
99
104
  */
100
105
  listTools(agent) {
101
106
  const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
102
- return this.request("GET", `/agent/tools${query}`);
107
+ return this.request("GET", `/tools${query}`);
103
108
  }
104
109
  getThread(id) {
105
- return this.request("GET", `/agent/threads/${encodeURIComponent(id)}`);
110
+ return this.request("GET", `/threads/${encodeURIComponent(id)}`);
106
111
  }
107
112
  deleteThread(id) {
108
- return this.request("DELETE", `/agent/threads/${encodeURIComponent(id)}`);
113
+ return this.request("DELETE", `/threads/${encodeURIComponent(id)}`);
109
114
  }
110
115
  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
- });
116
+ return this.request("POST", `/threads/${encodeURIComponent(threadId)}/fork-from/${encodeURIComponent(messageId)}`);
117
117
  }
118
- /** General `PATCH /agent/threads/:threadId` — title and/or the thread's pinned default agent. */
118
+ /** General `PATCH <path>/threads/:threadId` — title and/or the thread's pinned default agent. */
119
119
  updateThread(id, patch) {
120
- return this.request("PATCH", `/agent/threads/${encodeURIComponent(id)}`, patch);
120
+ return this.request("PATCH", `/threads/${encodeURIComponent(id)}`, patch);
121
121
  }
122
122
  /**
123
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
124
+ * mirrors the backend's `POST <path>/attachments`. The returned {@link MessageAttachment} is
125
125
  * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
126
126
  */
127
127
  async uploadAttachment(file, options = {}) {
128
- if (this.options.attachments !== void 0) {
129
- return this.options.attachments(file, options, this.connection());
128
+ const upload = this.options.attachments?.upload;
129
+ if (upload !== void 0) {
130
+ return upload(file, options, this.connection());
130
131
  }
131
132
  if (options.onProgress !== void 0 && this.options.fetch === void 0 && typeof XMLHttpRequest !== "undefined") {
132
133
  return this.uploadWithProgress(file, options);
133
134
  }
134
135
  const formData = new FormData();
135
136
  formData.append("file", file);
136
- const response = await this.fetchImpl()(`${this.baseUrl()}/agent/attachments`, {
137
+ const response = await this.fetchImpl()(`${this.root()}/attachments`, {
137
138
  method: "POST",
138
139
  headers: {
139
140
  accept: "application/json",
@@ -145,7 +146,7 @@ var AgentClient = class {
145
146
  signal: options.signal
146
147
  } : {}
147
148
  });
148
- const attachment = await this.handleResponse(response, "POST", "/agent/attachments");
149
+ const attachment = await this.handleResponse(response, "POST", `${this.agentPath()}/attachments`);
149
150
  options.onProgress?.(1);
150
151
  return attachment;
151
152
  }
@@ -154,7 +155,7 @@ var AgentClient = class {
154
155
  accept: "application/json",
155
156
  ...await this.resolveHeaders()
156
157
  };
157
- const url = `${this.baseUrl()}/agent/attachments`;
158
+ const url = `${this.root()}/attachments`;
158
159
  return new Promise((resolve, reject) => {
159
160
  const xhr = new XMLHttpRequest();
160
161
  xhr.open("POST", url);
@@ -165,7 +166,7 @@ var AgentClient = class {
165
166
  };
166
167
  xhr.onload = () => {
167
168
  if (xhr.status < 200 || xhr.status >= 300) {
168
- reject(new AgentHttpError(xhr.status, "POST", "/agent/attachments", xhr.statusText));
169
+ reject(new AgentHttpError(xhr.status, "POST", `${this.agentPath()}/attachments`, xhr.statusText));
169
170
  return;
170
171
  }
171
172
  onProgress?.(1);
@@ -188,39 +189,36 @@ var AgentClient = class {
188
189
  });
189
190
  }
190
191
  promoteThread(id) {
191
- return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
192
+ return this.request("POST", `/threads/${encodeURIComponent(id)}/promote`);
192
193
  }
193
194
  truncateFromMessage(threadId, messageId) {
194
- return this.request("DELETE", `/agent/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`);
195
+ return this.request("DELETE", `/threads/${encodeURIComponent(threadId)}/from/${encodeURIComponent(messageId)}`);
195
196
  }
196
- /** `GET /agent/models?agent=` — the models this caller may pick, grouped by provider. */
197
+ /** `GET <path>/models?agent=` — the models this caller may pick, grouped by provider. */
197
198
  listModels(agent) {
198
199
  const query = agent === void 0 ? "" : `?agent=${encodeURIComponent(agent)}`;
199
- return this.request("GET", `/agent/models${query}`);
200
+ return this.request("GET", `/models${query}`);
200
201
  }
201
- /** `GET /agent/agents` — the registered agents, the default one flagged. */
202
+ /** `GET <path>/agents` — the registered agents, the default one flagged. */
202
203
  listAgents() {
203
- return this.request("GET", "/agent/agents");
204
+ return this.request("GET", "/agents");
204
205
  }
205
- /** `GET /agent/quota` — the caller's budget windows and the one blocking sends, if any. */
206
+ /** `GET <path>/quota` — the caller's budget windows and the one blocking sends, if any. */
206
207
  getQuota() {
207
- return this.request("GET", "/agent/quota");
208
- }
209
- getQuotaToday() {
210
- return this.request("GET", "/agent/quota/today");
208
+ return this.request("GET", "/quota");
211
209
  }
212
210
  cancelStream(runId) {
213
- return this.request("POST", `/agent/chat/${encodeURIComponent(runId)}/cancel`);
211
+ return this.request("POST", `/chat/${encodeURIComponent(runId)}/cancel`);
214
212
  }
215
213
  /**
216
214
  * `remember` approves later calls of the same tool in the same thread; `via` names the surface
217
215
  * the decision came through (the server records `'web'` when omitted).
218
216
  */
219
217
  approveToolCall(input) {
220
- return this.request("POST", "/agent/tool-call/approve", input);
218
+ return this.request("POST", "/tool-call/approve", input);
221
219
  }
222
220
  rejectToolCall(input) {
223
- return this.request("POST", "/agent/tool-call/reject", input);
221
+ return this.request("POST", "/tool-call/reject", input);
224
222
  }
225
223
  /**
226
224
  * Settle a parked question set. `answers` is questionId → chosen option values; a question left
@@ -229,7 +227,7 @@ var AgentClient = class {
229
227
  * answer — which is the point of the surface, so it is a valid submission rather than a blank.
230
228
  */
231
229
  answerToolCall(input) {
232
- return this.request("POST", "/agent/tool-call/answer", input);
230
+ return this.request("POST", "/tool-call/answer", input);
233
231
  }
234
232
  /**
235
233
  * Decline to answer and let the agent proceed on its own pre-picked values. Lands on the same
@@ -237,7 +235,7 @@ var AgentClient = class {
237
235
  * evidence the user chose them.
238
236
  */
239
237
  skipToolCall(input) {
240
- return this.request("POST", "/agent/tool-call/skip", input);
238
+ return this.request("POST", "/tool-call/skip", input);
241
239
  }
242
240
  fetchImpl() {
243
241
  return this.options.fetch ?? globalThis.fetch;
@@ -246,13 +244,21 @@ var AgentClient = class {
246
244
  connection() {
247
245
  return {
248
246
  baseUrl: this.baseUrl(),
247
+ path: this.agentPath(),
249
248
  headers: /* @__PURE__ */ __name(() => this.resolveHeaders(), "headers"),
250
249
  fetch: this.fetchImpl(),
251
250
  ...this.credentials()
252
251
  };
253
252
  }
254
253
  baseUrl() {
255
- return (this.options.baseUrl ?? "").replace(/\/$/, "");
254
+ return (this.options.baseUrl ?? "").replace(/\/+$/, "");
255
+ }
256
+ agentPath() {
257
+ return normalizeAgentPath(this.options.path);
258
+ }
259
+ /** Origin + agent path: what every route hangs off. */
260
+ root() {
261
+ return `${this.baseUrl()}${this.agentPath()}`;
256
262
  }
257
263
  async resolveHeaders() {
258
264
  const dynamic = await this.options.getHeaders?.() ?? {};
@@ -266,7 +272,8 @@ var AgentClient = class {
266
272
  credentials: this.options.credentials
267
273
  } : {};
268
274
  }
269
- async request(method, path, body) {
275
+ async request(method, route, body) {
276
+ const path = `${this.agentPath()}${route}`;
270
277
  const response = await this.fetchImpl()(`${this.baseUrl()}${path}`, {
271
278
  method,
272
279
  headers: {
@@ -397,6 +404,9 @@ var AgentChatTransport = class {
397
404
  ...options.baseUrl !== void 0 ? {
398
405
  baseUrl: options.baseUrl
399
406
  } : {},
407
+ ...options.path !== void 0 ? {
408
+ path: options.path
409
+ } : {},
400
410
  ...options.headers !== void 0 ? {
401
411
  headers: options.headers
402
412
  } : {},
@@ -608,13 +618,14 @@ var AgentChatTransport = class {
608
618
  function ensureStarted() {
609
619
  if (started) return;
610
620
  started = true;
611
- controller.enqueue(streamRunId !== void 0 ? {
621
+ controller.enqueue({
612
622
  type: "start",
613
623
  messageMetadata: {
614
- runId: streamRunId
624
+ ...streamRunId !== void 0 ? {
625
+ runId: streamRunId
626
+ } : {},
627
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
615
628
  }
616
- } : {
617
- type: "start"
618
629
  });
619
630
  }
620
631
  __name(ensureStarted, "ensureStarted");
@@ -1102,7 +1113,7 @@ function dragHasFiles(data) {
1102
1113
  __name(dragHasFiles, "dragHasFiles");
1103
1114
 
1104
1115
  // src/attachments/use-attachments.ts
1105
- import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
1116
+ import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
1106
1117
 
1107
1118
  // src/backend.ts
1108
1119
  var AgentBackendUnsupportedError = class extends Error {
@@ -1124,6 +1135,570 @@ function requireBackendMethod(backend, name) {
1124
1135
  }
1125
1136
  __name(requireBackendMethod, "requireBackendMethod");
1126
1137
 
1138
+ // src/provider.tsx
1139
+ import { useContext as useContext3, useMemo as useMemo3, useRef as useRef2 } from "react";
1140
+
1141
+ // src/components/ambient-ui.ts
1142
+ import { createContext, useContext } from "react";
1143
+ function sharedContext(key) {
1144
+ const symbol = Symbol.for(key);
1145
+ const holder = globalThis;
1146
+ const existing = holder[symbol];
1147
+ if (existing !== void 0) return existing;
1148
+ const created = createContext(null);
1149
+ holder[symbol] = created;
1150
+ return created;
1151
+ }
1152
+ __name(sharedContext, "sharedContext");
1153
+ var AmbientRenderUiContext = sharedContext("@dudousxd/nestjs-agent-react:ambient-render-ui");
1154
+ function useAmbientRenderUi() {
1155
+ return useContext(AmbientRenderUiContext);
1156
+ }
1157
+ __name(useAmbientRenderUi, "useAmbientRenderUi");
1158
+
1159
+ // src/genui/generative-ui.tsx
1160
+ import { Component, createContext as createContext2, useCallback, useContext as useContext2, useMemo as useMemo2 } from "react";
1161
+
1162
+ // src/genui/use-generative-ui.ts
1163
+ import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
1164
+
1165
+ // src/genui/types.ts
1166
+ var GENUI_TREE_COMPONENT = "genui:tree";
1167
+
1168
+ // src/genui/use-generative-ui.ts
1169
+ function isRecord(value) {
1170
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1171
+ }
1172
+ __name(isRecord, "isRecord");
1173
+ function toGenerativeUIItem(part) {
1174
+ if (!isRecord(part)) return null;
1175
+ const source = part.type === "data-ui" && isRecord(part.data) ? part.data : part;
1176
+ if (typeof source.component !== "string") return null;
1177
+ const id = typeof source.id === "string" ? source.id : typeof part.id === "string" ? part.id : null;
1178
+ if (id === null) return null;
1179
+ return {
1180
+ id,
1181
+ component: source.component,
1182
+ props: isRecord(source.props) ? source.props : {},
1183
+ version: typeof source.version === "number" ? source.version : null,
1184
+ toolCallId: typeof source.toolCallId === "string" ? source.toolCallId : null
1185
+ };
1186
+ }
1187
+ __name(toGenerativeUIItem, "toGenerativeUIItem");
1188
+ var resolverCaches = /* @__PURE__ */ new WeakMap();
1189
+ function resolveCached(resolve, name, version) {
1190
+ let cache = resolverCaches.get(resolve);
1191
+ if (cache === void 0) {
1192
+ cache = /* @__PURE__ */ new Map();
1193
+ resolverCaches.set(resolve, cache);
1194
+ }
1195
+ const key = `${name}@${version ?? ""}`;
1196
+ const hit = cache.get(key);
1197
+ if (hit !== void 0) return hit;
1198
+ let entry;
1199
+ try {
1200
+ const result = resolve(name, version);
1201
+ if (result instanceof Promise) {
1202
+ entry = {
1203
+ resolution: {
1204
+ status: "loading"
1205
+ },
1206
+ settled: null
1207
+ };
1208
+ entry.settled = result.then((Component2) => {
1209
+ entry.resolution = Component2 ? {
1210
+ status: "ready",
1211
+ Component: Component2
1212
+ } : {
1213
+ status: "unknown"
1214
+ };
1215
+ }, (error) => {
1216
+ entry.resolution = {
1217
+ status: "error",
1218
+ error
1219
+ };
1220
+ });
1221
+ } else {
1222
+ entry = {
1223
+ resolution: result ? {
1224
+ status: "ready",
1225
+ Component: result
1226
+ } : {
1227
+ status: "unknown"
1228
+ },
1229
+ settled: null
1230
+ };
1231
+ }
1232
+ } catch (error) {
1233
+ entry = {
1234
+ resolution: {
1235
+ status: "error",
1236
+ error
1237
+ },
1238
+ settled: null
1239
+ };
1240
+ }
1241
+ cache.set(key, entry);
1242
+ return entry;
1243
+ }
1244
+ __name(resolveCached, "resolveCached");
1245
+ function useResolvedComponent(name, version, registry, resolve, fallbackTree) {
1246
+ const [, setTick] = useState2(0);
1247
+ const own = registry[name];
1248
+ const entry = own !== void 0 || resolve === void 0 ? null : resolveCached(resolve, name, version);
1249
+ const pending = entry?.resolution.status === "loading" ? entry.settled : null;
1250
+ useEffect2(() => {
1251
+ if (pending === null) return;
1252
+ let live = true;
1253
+ void pending.then(() => {
1254
+ if (live) setTick((tick) => tick + 1);
1255
+ });
1256
+ return () => {
1257
+ live = false;
1258
+ };
1259
+ }, [
1260
+ pending
1261
+ ]);
1262
+ if (own !== void 0) return {
1263
+ status: "ready",
1264
+ Component: own
1265
+ };
1266
+ if (entry !== null && entry.resolution.status !== "unknown") return entry.resolution;
1267
+ if (name === GENUI_TREE_COMPONENT && fallbackTree !== void 0) {
1268
+ return {
1269
+ status: "ready",
1270
+ Component: fallbackTree
1271
+ };
1272
+ }
1273
+ return {
1274
+ status: "unknown"
1275
+ };
1276
+ }
1277
+ __name(useResolvedComponent, "useResolvedComponent");
1278
+ function useValidatedProps(catalog, name, props) {
1279
+ const checked = catalog?.has(name) === true;
1280
+ const immediate = useMemo(() => {
1281
+ if (!checked || catalog === void 0) return {
1282
+ status: "ok",
1283
+ props
1284
+ };
1285
+ const result = catalog.validateSync?.(name, props);
1286
+ if (result === void 0) return null;
1287
+ return result.ok ? {
1288
+ status: "ok",
1289
+ props: result.value
1290
+ } : {
1291
+ status: "invalid",
1292
+ issues: result.issues
1293
+ };
1294
+ }, [
1295
+ checked,
1296
+ catalog,
1297
+ name,
1298
+ props
1299
+ ]);
1300
+ const [late, setLate] = useState2(null);
1301
+ useEffect2(() => {
1302
+ if (immediate !== null || catalog === void 0) return;
1303
+ let live = true;
1304
+ catalog.validate(name, props).then((result) => {
1305
+ if (!live) return;
1306
+ setLate({
1307
+ props,
1308
+ result: result.ok ? {
1309
+ status: "ok",
1310
+ props: result.value
1311
+ } : {
1312
+ status: "invalid",
1313
+ issues: result.issues
1314
+ }
1315
+ });
1316
+ }, (error) => {
1317
+ if (!live) return;
1318
+ setLate({
1319
+ props,
1320
+ result: {
1321
+ status: "invalid",
1322
+ issues: [
1323
+ {
1324
+ path: [],
1325
+ message: error instanceof Error ? error.message : String(error)
1326
+ }
1327
+ ]
1328
+ }
1329
+ });
1330
+ });
1331
+ return () => {
1332
+ live = false;
1333
+ };
1334
+ }, [
1335
+ immediate,
1336
+ catalog,
1337
+ name,
1338
+ props
1339
+ ]);
1340
+ if (immediate !== null) return immediate;
1341
+ return late !== null && late.props === props ? late.result : {
1342
+ status: "pending"
1343
+ };
1344
+ }
1345
+ __name(useValidatedProps, "useValidatedProps");
1346
+ function useGenerativeUIState(part, options, fallbackTree) {
1347
+ const item = useMemo(() => toGenerativeUIItem(part), [
1348
+ part
1349
+ ]);
1350
+ const name = item?.component ?? "";
1351
+ const resolution = useResolvedComponent(name, item?.version ?? null, options.registry, item === null ? void 0 : options.resolveComponent, fallbackTree);
1352
+ const empty = useMemo(() => ({}), []);
1353
+ const validation = useValidatedProps(item === null || name === GENUI_TREE_COMPONENT ? void 0 : options.catalog, name, item?.props ?? empty);
1354
+ if (item === null) return null;
1355
+ if (resolution.status === "loading") return {
1356
+ status: "loading",
1357
+ item
1358
+ };
1359
+ if (resolution.status === "unknown") return {
1360
+ status: "problem",
1361
+ reason: "unknown",
1362
+ item
1363
+ };
1364
+ if (resolution.status === "error") {
1365
+ return {
1366
+ status: "problem",
1367
+ reason: "error",
1368
+ item,
1369
+ error: resolution.error
1370
+ };
1371
+ }
1372
+ if (validation.status === "pending") return {
1373
+ status: "loading",
1374
+ item
1375
+ };
1376
+ if (validation.status === "invalid") {
1377
+ return {
1378
+ status: "problem",
1379
+ reason: "invalid",
1380
+ item,
1381
+ issues: validation.issues
1382
+ };
1383
+ }
1384
+ return {
1385
+ status: "ready",
1386
+ item,
1387
+ Component: resolution.Component,
1388
+ props: validation.props
1389
+ };
1390
+ }
1391
+ __name(useGenerativeUIState, "useGenerativeUIState");
1392
+
1393
+ // src/genui/generative-ui.tsx
1394
+ var TreeScopeContext = /* @__PURE__ */ createContext2(null);
1395
+ function renderFallback(fallback2, problem) {
1396
+ if (typeof fallback2 === "function") return fallback2(problem);
1397
+ return fallback2 ?? null;
1398
+ }
1399
+ __name(renderFallback, "renderFallback");
1400
+ var ItemBoundary = class ItemBoundary2 extends Component {
1401
+ static {
1402
+ __name(this, "ItemBoundary");
1403
+ }
1404
+ state = {
1405
+ error: void 0,
1406
+ failed: false
1407
+ };
1408
+ static getDerivedStateFromError(error) {
1409
+ return {
1410
+ error,
1411
+ failed: true
1412
+ };
1413
+ }
1414
+ componentDidCatch(error, _info) {
1415
+ this.props.onError?.(error, this.props.item);
1416
+ }
1417
+ componentDidUpdate(previous) {
1418
+ if (this.state.failed && previous.item !== this.props.item) {
1419
+ this.setState({
1420
+ error: void 0,
1421
+ failed: false
1422
+ });
1423
+ }
1424
+ }
1425
+ render() {
1426
+ if (this.state.failed) {
1427
+ return renderFallback(this.props.fallback, {
1428
+ reason: "error",
1429
+ item: this.props.item,
1430
+ error: this.state.error
1431
+ });
1432
+ }
1433
+ return this.props.children;
1434
+ }
1435
+ };
1436
+ function nodeItem(node, id) {
1437
+ return {
1438
+ id,
1439
+ component: node.type,
1440
+ props: node.props ?? {},
1441
+ version: null,
1442
+ toolCallId: null
1443
+ };
1444
+ }
1445
+ __name(nodeItem, "nodeItem");
1446
+ function TreeNode({ node, id }) {
1447
+ const scope = useContext2(TreeScopeContext);
1448
+ const item = useMemo2(() => nodeItem(node, id), [
1449
+ node,
1450
+ id
1451
+ ]);
1452
+ const resolution = useResolvedComponent(node.type, null, scope?.options.registry ?? {}, scope?.options.resolveComponent);
1453
+ const validation = useValidatedProps(scope?.options.catalog, node.type, item.props);
1454
+ if (scope === null) return null;
1455
+ if (resolution.status === "loading" || validation.status === "pending") return scope.loading;
1456
+ if (resolution.status === "unknown") {
1457
+ return renderFallback(scope.fallback, {
1458
+ reason: "unknown",
1459
+ item
1460
+ });
1461
+ }
1462
+ if (resolution.status === "error") {
1463
+ return renderFallback(scope.fallback, {
1464
+ reason: "error",
1465
+ item,
1466
+ error: resolution.error
1467
+ });
1468
+ }
1469
+ if (validation.status === "invalid") {
1470
+ return renderFallback(scope.fallback, {
1471
+ reason: "invalid",
1472
+ item,
1473
+ issues: validation.issues
1474
+ });
1475
+ }
1476
+ const Renderer = resolution.Component;
1477
+ const children = Array.isArray(node.children) ? node.children : [];
1478
+ return /* @__PURE__ */ React.createElement(Renderer, validation.props, children.length > 0 ? children.map((child, index) => {
1479
+ const childId = `${id}.${index}`;
1480
+ return /* @__PURE__ */ React.createElement(ItemBoundary, {
1481
+ key: childId,
1482
+ item: nodeItem(child, childId),
1483
+ fallback: scope.fallback,
1484
+ onError: scope.onError
1485
+ }, /* @__PURE__ */ React.createElement(TreeNode, {
1486
+ node: child,
1487
+ id: childId
1488
+ }));
1489
+ }) : void 0);
1490
+ }
1491
+ __name(TreeNode, "TreeNode");
1492
+ function GenuiTree({ root }) {
1493
+ if (root === void 0 || root === null || typeof root.type !== "string") return null;
1494
+ return /* @__PURE__ */ React.createElement(TreeNode, {
1495
+ node: root,
1496
+ id: "root"
1497
+ });
1498
+ }
1499
+ __name(GenuiTree, "GenuiTree");
1500
+ function GenerativeUIScope({ registry, catalog, resolveComponent, fallback: fallback2, loading = null, onError, children }) {
1501
+ const scope = useMemo2(() => ({
1502
+ options: {
1503
+ registry,
1504
+ ...catalog !== void 0 ? {
1505
+ catalog
1506
+ } : {},
1507
+ ...resolveComponent !== void 0 ? {
1508
+ resolveComponent
1509
+ } : {}
1510
+ },
1511
+ fallback: fallback2,
1512
+ loading,
1513
+ onError
1514
+ }), [
1515
+ registry,
1516
+ catalog,
1517
+ resolveComponent,
1518
+ fallback2,
1519
+ loading,
1520
+ onError
1521
+ ]);
1522
+ return /* @__PURE__ */ React.createElement(TreeScopeContext.Provider, {
1523
+ value: scope
1524
+ }, children);
1525
+ }
1526
+ __name(GenerativeUIScope, "GenerativeUIScope");
1527
+ var GenuiContext = sharedContext("@dudousxd/nestjs-agent-react:genui-provider");
1528
+ function GenuiProvider({ registry, catalog, resolveComponent, treeRenderer, fallback: fallback2, loading, onError, children }) {
1529
+ const value = useMemo2(() => ({
1530
+ ...registry !== void 0 ? {
1531
+ registry
1532
+ } : {},
1533
+ ...catalog !== void 0 ? {
1534
+ catalog
1535
+ } : {},
1536
+ ...resolveComponent !== void 0 ? {
1537
+ resolveComponent
1538
+ } : {},
1539
+ ...treeRenderer !== void 0 ? {
1540
+ treeRenderer
1541
+ } : {},
1542
+ ...fallback2 !== void 0 ? {
1543
+ fallback: fallback2
1544
+ } : {},
1545
+ ...loading !== void 0 ? {
1546
+ loading
1547
+ } : {},
1548
+ ...onError !== void 0 ? {
1549
+ onError
1550
+ } : {}
1551
+ }), [
1552
+ registry,
1553
+ catalog,
1554
+ resolveComponent,
1555
+ treeRenderer,
1556
+ fallback2,
1557
+ loading,
1558
+ onError
1559
+ ]);
1560
+ const renderUi = useCallback((block) => /* @__PURE__ */ React.createElement(GenerativeUI, {
1561
+ part: block
1562
+ }), []);
1563
+ return /* @__PURE__ */ React.createElement(GenuiContext.Provider, {
1564
+ value
1565
+ }, /* @__PURE__ */ React.createElement(AmbientRenderUiContext.Provider, {
1566
+ value: renderUi
1567
+ }, children));
1568
+ }
1569
+ __name(GenuiProvider, "GenuiProvider");
1570
+ var NO_REGISTRY = {};
1571
+ function useMergedOptions(own) {
1572
+ const provided = useContext2(GenuiContext);
1573
+ const registry = own.registry ?? provided?.registry ?? NO_REGISTRY;
1574
+ const catalog = own.catalog ?? provided?.catalog;
1575
+ const resolveComponent = own.resolveComponent ?? provided?.resolveComponent;
1576
+ const treeRenderer = own.treeRenderer ?? provided?.treeRenderer;
1577
+ return useMemo2(() => ({
1578
+ registry,
1579
+ ...catalog !== void 0 ? {
1580
+ catalog
1581
+ } : {},
1582
+ ...resolveComponent !== void 0 ? {
1583
+ resolveComponent
1584
+ } : {},
1585
+ ...treeRenderer !== void 0 ? {
1586
+ treeRenderer
1587
+ } : {}
1588
+ }), [
1589
+ registry,
1590
+ catalog,
1591
+ resolveComponent,
1592
+ treeRenderer
1593
+ ]);
1594
+ }
1595
+ __name(useMergedOptions, "useMergedOptions");
1596
+ function GenerativeUI({ part, registry, catalog, resolveComponent, treeRenderer, fallback: ownFallback, loading: ownLoading, onError: ownOnError }) {
1597
+ const provided = useContext2(GenuiContext);
1598
+ const fallback2 = ownFallback ?? provided?.fallback;
1599
+ const loading = ownLoading ?? provided?.loading ?? null;
1600
+ const onError = ownOnError ?? provided?.onError;
1601
+ const options = useMergedOptions({
1602
+ ...registry !== void 0 ? {
1603
+ registry
1604
+ } : {},
1605
+ ...catalog !== void 0 ? {
1606
+ catalog
1607
+ } : {},
1608
+ ...resolveComponent !== void 0 ? {
1609
+ resolveComponent
1610
+ } : {},
1611
+ ...treeRenderer !== void 0 ? {
1612
+ treeRenderer
1613
+ } : {}
1614
+ });
1615
+ const state = useGenerativeUIState(part, options, options.treeRenderer ?? GenuiTree);
1616
+ if (state === null) return null;
1617
+ if (state.status === "loading") return loading;
1618
+ if (state.status === "problem") {
1619
+ const { status: _status, ...problem } = state;
1620
+ return renderFallback(fallback2, problem);
1621
+ }
1622
+ const Renderer = state.Component;
1623
+ return /* @__PURE__ */ React.createElement(GenerativeUIScope, {
1624
+ ...options,
1625
+ fallback: fallback2,
1626
+ loading,
1627
+ ...onError !== void 0 ? {
1628
+ onError
1629
+ } : {}
1630
+ }, /* @__PURE__ */ React.createElement(ItemBoundary, {
1631
+ item: state.item,
1632
+ fallback: fallback2,
1633
+ onError
1634
+ }, /* @__PURE__ */ React.createElement(Renderer, state.props)));
1635
+ }
1636
+ __name(GenerativeUI, "GenerativeUI");
1637
+
1638
+ // src/provider.tsx
1639
+ var AgentBackendContext = sharedContext("@dudousxd/nestjs-agent-react:agent-backend");
1640
+ function AgentProvider({ backend, baseUrl, path, headers, getHeaders, credentials, fetch, attachments, genui, children }) {
1641
+ const latestHeaders = useRef2({
1642
+ headers,
1643
+ getHeaders
1644
+ });
1645
+ latestHeaders.current = {
1646
+ headers,
1647
+ getHeaders
1648
+ };
1649
+ const upload = attachments?.upload;
1650
+ const value = useMemo3(() => backend ?? new AgentClient({
1651
+ ...baseUrl !== void 0 ? {
1652
+ baseUrl
1653
+ } : {},
1654
+ ...path !== void 0 ? {
1655
+ path
1656
+ } : {},
1657
+ ...credentials !== void 0 ? {
1658
+ credentials
1659
+ } : {},
1660
+ ...fetch !== void 0 ? {
1661
+ fetch
1662
+ } : {},
1663
+ ...upload !== void 0 ? {
1664
+ attachments: {
1665
+ upload
1666
+ }
1667
+ } : {},
1668
+ getHeaders: /* @__PURE__ */ __name(async () => {
1669
+ const current = latestHeaders.current;
1670
+ const dynamic = await current.getHeaders?.() ?? {};
1671
+ return {
1672
+ ...current.headers,
1673
+ ...dynamic
1674
+ };
1675
+ }, "getHeaders")
1676
+ }), [
1677
+ backend,
1678
+ baseUrl,
1679
+ path,
1680
+ credentials,
1681
+ fetch,
1682
+ upload
1683
+ ]);
1684
+ const content = genui !== void 0 ? /* @__PURE__ */ React.createElement(GenuiProvider, genui, children) : children;
1685
+ return /* @__PURE__ */ React.createElement(AgentBackendContext.Provider, {
1686
+ value
1687
+ }, content);
1688
+ }
1689
+ __name(AgentProvider, "AgentProvider");
1690
+ var fallback;
1691
+ function defaultBackend() {
1692
+ fallback ??= new AgentClient();
1693
+ return fallback;
1694
+ }
1695
+ __name(defaultBackend, "defaultBackend");
1696
+ function useAgentBackend(own) {
1697
+ const provided = useContext3(AgentBackendContext);
1698
+ return own ?? provided ?? defaultBackend();
1699
+ }
1700
+ __name(useAgentBackend, "useAgentBackend");
1701
+
1127
1702
  // src/attachments/use-attachments.ts
1128
1703
  var DEFAULT_REJECTION = {
1129
1704
  type: /* @__PURE__ */ __name((file) => `${file.name} is not a supported file type`, "type"),
@@ -1131,23 +1706,30 @@ var DEFAULT_REJECTION = {
1131
1706
  count: /* @__PURE__ */ __name(() => "Too many files", "count")
1132
1707
  };
1133
1708
  var sequence = 0;
1134
- function useAttachments(options) {
1135
- const latest = useRef2(options);
1136
- latest.current = options;
1137
- const [items, setItems] = useState2([]);
1138
- const itemsRef = useRef2(items);
1709
+ function useAttachments(options = {}) {
1710
+ const backend = useAgentBackend(options.backend);
1711
+ const latest = useRef3({
1712
+ ...options,
1713
+ backend
1714
+ });
1715
+ latest.current = {
1716
+ ...options,
1717
+ backend
1718
+ };
1719
+ const [items, setItems] = useState3([]);
1720
+ const itemsRef = useRef3(items);
1139
1721
  itemsRef.current = items;
1140
- const controllers = useRef2(/* @__PURE__ */ new Map());
1141
- const [dragDepth, setDragDepth] = useState2(0);
1142
- const patch = useCallback((id, change) => {
1722
+ const controllers = useRef3(/* @__PURE__ */ new Map());
1723
+ const [dragDepth, setDragDepth] = useState3(0);
1724
+ const patch = useCallback2((id, change) => {
1143
1725
  setItems((current) => current.map((item) => item.id === id ? {
1144
1726
  ...item,
1145
1727
  ...change
1146
1728
  } : item));
1147
1729
  }, []);
1148
- const start = useCallback((item) => {
1149
- const { upload, backend } = latest.current;
1150
- const run = upload ?? (backend?.uploadAttachment !== void 0 ? backend.uploadAttachment.bind(backend) : void 0);
1730
+ const start = useCallback2((item) => {
1731
+ const { upload, backend: backend2 } = latest.current;
1732
+ const run = upload ?? (backend2.uploadAttachment !== void 0 ? backend2.uploadAttachment.bind(backend2) : void 0);
1151
1733
  if (run === void 0) {
1152
1734
  patch(item.id, {
1153
1735
  status: "error",
@@ -1181,7 +1763,7 @@ function useAttachments(options) {
1181
1763
  }, [
1182
1764
  patch
1183
1765
  ]);
1184
- const add = useCallback((files) => {
1766
+ const add = useCallback2((files) => {
1185
1767
  if (files == null) return;
1186
1768
  const { accept: accept2, maxBytes, maxFiles, describeRejection } = latest.current;
1187
1769
  const describe = /* @__PURE__ */ __name((reason, file) => describeRejection?.(reason, file) ?? DEFAULT_REJECTION[reason](file), "describe");
@@ -1227,7 +1809,7 @@ function useAttachments(options) {
1227
1809
  }, [
1228
1810
  start
1229
1811
  ]);
1230
- const release = useCallback((item) => {
1812
+ const release = useCallback2((item) => {
1231
1813
  if (item === void 0) return;
1232
1814
  controllers.current.get(item.id)?.abort();
1233
1815
  controllers.current.delete(item.id);
@@ -1235,19 +1817,19 @@ function useAttachments(options) {
1235
1817
  URL.revokeObjectURL(item.previewUrl);
1236
1818
  }
1237
1819
  }, []);
1238
- const remove = useCallback((id) => {
1820
+ const remove = useCallback2((id) => {
1239
1821
  release(itemsRef.current.find((item) => item.id === id));
1240
1822
  setItems((current) => current.filter((item) => item.id !== id));
1241
1823
  }, [
1242
1824
  release
1243
1825
  ]);
1244
- const clear = useCallback(() => {
1826
+ const clear = useCallback2(() => {
1245
1827
  for (const item of itemsRef.current) release(item);
1246
1828
  setItems([]);
1247
1829
  }, [
1248
1830
  release
1249
1831
  ]);
1250
- const retry = useCallback((id) => {
1832
+ const retry = useCallback2((id) => {
1251
1833
  const item = itemsRef.current.find((candidate) => candidate.id === id);
1252
1834
  if (item === void 0 || item.status !== "error") return;
1253
1835
  const { error: _previous, ...rest } = item;
@@ -1261,12 +1843,12 @@ function useAttachments(options) {
1261
1843
  }, [
1262
1844
  start
1263
1845
  ]);
1264
- useEffect2(() => () => {
1846
+ useEffect3(() => () => {
1265
1847
  for (const item of itemsRef.current) release(item);
1266
1848
  }, [
1267
1849
  release
1268
1850
  ]);
1269
- const onPaste = useCallback((event) => {
1851
+ const onPaste = useCallback2((event) => {
1270
1852
  const files = filesFromClipboard(event.clipboardData);
1271
1853
  if (files.length === 0) return;
1272
1854
  event.preventDefault();
@@ -1274,7 +1856,7 @@ function useAttachments(options) {
1274
1856
  }, [
1275
1857
  add
1276
1858
  ]);
1277
- const dropZoneProps = useMemo(() => ({
1859
+ const dropZoneProps = useMemo4(() => ({
1278
1860
  onDragEnter: /* @__PURE__ */ __name((event) => {
1279
1861
  if (!dragHasFiles(event.dataTransfer)) return;
1280
1862
  event.preventDefault();
@@ -1297,7 +1879,7 @@ function useAttachments(options) {
1297
1879
  add
1298
1880
  ]);
1299
1881
  const accept = options.accept;
1300
- const inputProps = useMemo(() => ({
1882
+ const inputProps = useMemo4(() => ({
1301
1883
  type: "file",
1302
1884
  accept: typeof accept === "string" ? accept : accept?.join(","),
1303
1885
  multiple: options.maxFiles === void 0 || options.maxFiles > 1,
@@ -1311,7 +1893,7 @@ function useAttachments(options) {
1311
1893
  options.maxFiles,
1312
1894
  add
1313
1895
  ]);
1314
- return useMemo(() => {
1896
+ return useMemo4(() => {
1315
1897
  const ready = items.filter((item) => item.status === "ready" && item.attachment !== void 0);
1316
1898
  const attachments = ready.map((item) => item.attachment);
1317
1899
  return {
@@ -1345,18 +1927,18 @@ function useAttachments(options) {
1345
1927
  __name(useAttachments, "useAttachments");
1346
1928
 
1347
1929
  // src/catalog/use-agents.ts
1348
- import { useMemo as useMemo2 } from "react";
1930
+ import { useMemo as useMemo5 } from "react";
1349
1931
 
1350
1932
  // src/catalog/use-resource.ts
1351
- import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1933
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
1352
1934
  function useResource(load2, key, enabled) {
1353
- const [data, setData] = useState3(void 0);
1354
- const [isLoading, setIsLoading] = useState3(enabled);
1355
- const [error, setError] = useState3(null);
1356
- const loadRef = useRef3(load2);
1935
+ const [data, setData] = useState4(void 0);
1936
+ const [isLoading, setIsLoading] = useState4(enabled);
1937
+ const [error, setError] = useState4(null);
1938
+ const loadRef = useRef4(load2);
1357
1939
  loadRef.current = load2;
1358
- const generation = useRef3(0);
1359
- const refresh = useCallback2(async () => {
1940
+ const generation = useRef4(0);
1941
+ const refresh = useCallback3(async () => {
1360
1942
  const mine = ++generation.current;
1361
1943
  setIsLoading(true);
1362
1944
  try {
@@ -1371,7 +1953,7 @@ function useResource(load2, key, enabled) {
1371
1953
  if (mine === generation.current) setIsLoading(false);
1372
1954
  }
1373
1955
  }, []);
1374
- useEffect3(() => {
1956
+ useEffect4(() => {
1375
1957
  if (!enabled) return;
1376
1958
  void refresh();
1377
1959
  }, [
@@ -1389,10 +1971,11 @@ function useResource(load2, key, enabled) {
1389
1971
  __name(useResource, "useResource");
1390
1972
 
1391
1973
  // src/catalog/use-agents.ts
1392
- function useAgents(options) {
1393
- const { backend, enabled = true } = options;
1974
+ function useAgents(options = {}) {
1975
+ const { enabled = true } = options;
1976
+ const backend = useAgentBackend(options.backend);
1394
1977
  const { data, isLoading, error, refresh } = useResource(() => requireBackendMethod(backend, "listAgents")(), "", enabled);
1395
- return useMemo2(() => {
1978
+ return useMemo5(() => {
1396
1979
  const agents = data ?? [];
1397
1980
  return {
1398
1981
  agents,
@@ -1411,11 +1994,12 @@ function useAgents(options) {
1411
1994
  __name(useAgents, "useAgents");
1412
1995
 
1413
1996
  // src/catalog/use-models.ts
1414
- import { useMemo as useMemo3 } from "react";
1415
- function useModels(options) {
1416
- const { backend, agent, enabled = true } = options;
1997
+ import { useMemo as useMemo6 } from "react";
1998
+ function useModels(options = {}) {
1999
+ const { agent, enabled = true } = options;
2000
+ const backend = useAgentBackend(options.backend);
1417
2001
  const { data, isLoading, error, refresh } = useResource(() => requireBackendMethod(backend, "listModels")(agent), `${agent ?? ""}`, enabled);
1418
- return useMemo3(() => {
2002
+ return useMemo6(() => {
1419
2003
  const providers = data?.providers ?? [];
1420
2004
  const models = providers.flatMap((provider) => provider.models.map((model) => ({
1421
2005
  ...model,
@@ -1445,7 +2029,7 @@ function useModels(options) {
1445
2029
  __name(useModels, "useModels");
1446
2030
 
1447
2031
  // src/quota/use-quota.ts
1448
- import { useEffect as useEffect4, useMemo as useMemo4 } from "react";
2032
+ import { useEffect as useEffect5, useMemo as useMemo7 } from "react";
1449
2033
 
1450
2034
  // src/threads/threads-events.ts
1451
2035
  var listeners = /* @__PURE__ */ new WeakMap();
@@ -1469,10 +2053,11 @@ function onThreadsEvent(backend, listener) {
1469
2053
  __name(onThreadsEvent, "onThreadsEvent");
1470
2054
 
1471
2055
  // src/quota/use-quota.ts
1472
- function useQuota(options) {
1473
- const { backend, enabled = true, pollMs } = options;
2056
+ function useQuota(options = {}) {
2057
+ const { enabled = true, pollMs } = options;
2058
+ const backend = useAgentBackend(options.backend);
1474
2059
  const { data, isLoading, error, refresh } = useResource(() => requireBackendMethod(backend, "getQuota")(), "", enabled);
1475
- useEffect4(() => {
2060
+ useEffect5(() => {
1476
2061
  if (!enabled) return;
1477
2062
  return onThreadsEvent(backend, (event) => {
1478
2063
  if (event.type === "changed") void refresh();
@@ -1482,7 +2067,7 @@ function useQuota(options) {
1482
2067
  enabled,
1483
2068
  refresh
1484
2069
  ]);
1485
- useEffect4(() => {
2070
+ useEffect5(() => {
1486
2071
  if (!enabled || pollMs === void 0 || pollMs <= 0) return;
1487
2072
  const timer = setInterval(() => void refresh(), pollMs);
1488
2073
  return () => clearInterval(timer);
@@ -1491,7 +2076,7 @@ function useQuota(options) {
1491
2076
  pollMs,
1492
2077
  refresh
1493
2078
  ]);
1494
- return useMemo4(() => {
2079
+ return useMemo7(() => {
1495
2080
  const windows = data?.windows ?? [];
1496
2081
  return {
1497
2082
  report: data,
@@ -1513,37 +2098,44 @@ function useQuota(options) {
1513
2098
  __name(useQuota, "useQuota");
1514
2099
 
1515
2100
  // src/feedback/use-message-feedback.ts
1516
- import { useCallback as useCallback3, useRef as useRef4, useState as useState4 } from "react";
1517
- function useMessageFeedback(options) {
1518
- const latest = useRef4(options);
1519
- latest.current = options;
1520
- const [local, setLocal] = useState4(/* @__PURE__ */ new Map());
1521
- const [pending, setPending] = useState4(/* @__PURE__ */ new Set());
1522
- const [error, setError] = useState4(null);
1523
- const localRef = useRef4(local);
2101
+ import { useCallback as useCallback4, useRef as useRef5, useState as useState5 } from "react";
2102
+ function useMessageFeedback(options = {}) {
2103
+ const backend = useAgentBackend(options.backend);
2104
+ const latest = useRef5({
2105
+ ...options,
2106
+ backend
2107
+ });
2108
+ latest.current = {
2109
+ ...options,
2110
+ backend
2111
+ };
2112
+ const [local, setLocal] = useState5(/* @__PURE__ */ new Map());
2113
+ const [pending, setPending] = useState5(/* @__PURE__ */ new Set());
2114
+ const [error, setError] = useState5(null);
2115
+ const localRef = useRef5(local);
1524
2116
  localRef.current = local;
1525
- const resolved = useRef4(/* @__PURE__ */ new Map());
1526
- const feedbackOf = useCallback3((message) => {
2117
+ const resolved = useRef5(/* @__PURE__ */ new Map());
2118
+ const feedbackOf = useCallback4((message) => {
1527
2119
  const mine = local.get(message.id);
1528
2120
  if (mine !== void 0) return mine.feedback;
1529
2121
  return metadataOf(message).feedback ?? null;
1530
2122
  }, [
1531
2123
  local
1532
2124
  ]);
1533
- const isPending = useCallback3((message) => pending.has(message.id), [
2125
+ const isPending = useCallback4((message) => pending.has(message.id), [
1534
2126
  pending
1535
2127
  ]);
1536
- const storedIdFor = useCallback3(async (message) => {
2128
+ const storedIdFor = useCallback4(async (message) => {
1537
2129
  const runId = metadataOf(message).runId;
1538
2130
  if (runId === void 0) return message.id;
1539
2131
  const known = resolved.current.get(message.id);
1540
2132
  if (known !== void 0) return known;
1541
- const { backend, threadId } = latest.current;
2133
+ const { backend: backend2, threadId } = latest.current;
1542
2134
  const thread = typeof threadId === "function" ? threadId() : threadId;
1543
2135
  if (thread === void 0) {
1544
2136
  throw new Error("useMessageFeedback: a streamed message needs `threadId` to be rated");
1545
2137
  }
1546
- const detail = await backend.getThread(thread);
2138
+ const detail = await backend2.getThread(thread);
1547
2139
  const row = [
1548
2140
  ...detail.messages
1549
2141
  ].reverse().find((candidate) => candidate.role === "assistant" && candidate.runId === runId);
@@ -1553,7 +2145,7 @@ function useMessageFeedback(options) {
1553
2145
  resolved.current.set(message.id, row.id);
1554
2146
  return row.id;
1555
2147
  }, []);
1556
- const rate = useCallback3(async (message, value, comment) => {
2148
+ const rate = useCallback4(async (message, value, comment) => {
1557
2149
  const setFeedback = requireBackendMethod(latest.current.backend, "setMessageFeedback");
1558
2150
  const had = localRef.current.get(message.id);
1559
2151
  const optimistic = value === null ? null : {
@@ -1599,7 +2191,7 @@ function useMessageFeedback(options) {
1599
2191
  }, [
1600
2192
  storedIdFor
1601
2193
  ]);
1602
- const toggle = useCallback3((message, value) => {
2194
+ const toggle = useCallback4((message, value) => {
1603
2195
  const mine = localRef.current.get(message.id);
1604
2196
  const current = mine !== void 0 ? mine.feedback : metadataOf(message).feedback ?? null;
1605
2197
  return rate(message, current?.value === value ? null : value);
@@ -1622,16 +2214,17 @@ function metadataOf(message) {
1622
2214
  __name(metadataOf, "metadataOf");
1623
2215
 
1624
2216
  // src/threads/use-threads.ts
1625
- import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1626
- function useThreads(options) {
1627
- const { backend, enabled = true } = options;
1628
- const [threads, setThreads] = useState5([]);
1629
- const [isLoading, setIsLoading] = useState5(enabled);
1630
- const [error, setError] = useState5(null);
1631
- const threadsRef = useRef5(threads);
2217
+ import { useCallback as useCallback5, useEffect as useEffect6, useRef as useRef6, useState as useState6 } from "react";
2218
+ function useThreads(options = {}) {
2219
+ const { enabled = true } = options;
2220
+ const backend = useAgentBackend(options.backend);
2221
+ const [threads, setThreads] = useState6([]);
2222
+ const [isLoading, setIsLoading] = useState6(enabled);
2223
+ const [error, setError] = useState6(null);
2224
+ const threadsRef = useRef6(threads);
1632
2225
  threadsRef.current = threads;
1633
- const generation = useRef5(0);
1634
- const refresh = useCallback4(async () => {
2226
+ const generation = useRef6(0);
2227
+ const refresh = useCallback5(async () => {
1635
2228
  const mine = ++generation.current;
1636
2229
  setIsLoading(true);
1637
2230
  try {
@@ -1648,7 +2241,7 @@ function useThreads(options) {
1648
2241
  }, [
1649
2242
  backend
1650
2243
  ]);
1651
- useEffect5(() => {
2244
+ useEffect6(() => {
1652
2245
  if (!enabled) return;
1653
2246
  void refresh();
1654
2247
  return onThreadsEvent(backend, (event) => {
@@ -1668,7 +2261,7 @@ function useThreads(options) {
1668
2261
  enabled,
1669
2262
  refresh
1670
2263
  ]);
1671
- const rename = useCallback4(async (id, title) => {
2264
+ const rename = useCallback5(async (id, title) => {
1672
2265
  const before = threadsRef.current;
1673
2266
  setThreads((current) => current.map((thread) => thread.id === id ? {
1674
2267
  ...thread,
@@ -1690,7 +2283,7 @@ function useThreads(options) {
1690
2283
  }, [
1691
2284
  backend
1692
2285
  ]);
1693
- const remove = useCallback4(async (id) => {
2286
+ const remove = useCallback5(async (id) => {
1694
2287
  const before = threadsRef.current;
1695
2288
  setThreads((current) => current.filter((thread) => thread.id !== id));
1696
2289
  try {
@@ -1764,29 +2357,11 @@ function backgroundRunsFromThread(messages) {
1764
2357
  }
1765
2358
  __name(backgroundRunsFromThread, "backgroundRunsFromThread");
1766
2359
 
1767
- // src/components/ambient-ui.ts
1768
- import { createContext, useContext } from "react";
1769
- function sharedContext(key) {
1770
- const symbol = Symbol.for(key);
1771
- const holder = globalThis;
1772
- const existing = holder[symbol];
1773
- if (existing !== void 0) return existing;
1774
- const created = createContext(null);
1775
- holder[symbol] = created;
1776
- return created;
1777
- }
1778
- __name(sharedContext, "sharedContext");
1779
- var AmbientRenderUiContext = sharedContext("@dudousxd/nestjs-agent-react:ambient-render-ui");
1780
- function useAmbientRenderUi() {
1781
- return useContext(AmbientRenderUiContext);
1782
- }
1783
- __name(useAmbientRenderUi, "useAmbientRenderUi");
1784
-
1785
2360
  // src/components/chat-input.tsx
1786
- import React2, { useEffect as useEffect7, useState as useState7 } from "react";
2361
+ import React2, { useEffect as useEffect8, useState as useState8 } from "react";
1787
2362
 
1788
2363
  // src/components/use-speech-recognition.ts
1789
- import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef6, useState as useState6 } from "react";
2364
+ import { useCallback as useCallback6, useEffect as useEffect7, useMemo as useMemo8, useRef as useRef7, useState as useState7 } from "react";
1790
2365
  function getRecognitionCtor() {
1791
2366
  if (typeof window === "undefined") return null;
1792
2367
  const scope = window;
@@ -1794,15 +2369,15 @@ function getRecognitionCtor() {
1794
2369
  }
1795
2370
  __name(getRecognitionCtor, "getRecognitionCtor");
1796
2371
  function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
1797
- const recognitionRef = useRef6(null);
1798
- const [isListening, setIsListening] = useState6(false);
1799
- const ctor = useMemo5(() => getRecognitionCtor(), []);
2372
+ const recognitionRef = useRef7(null);
2373
+ const [isListening, setIsListening] = useState7(false);
2374
+ const ctor = useMemo8(() => getRecognitionCtor(), []);
1800
2375
  const isSupported = ctor !== null;
1801
- const finalRef = useRef6(onFinalTranscript);
1802
- const interimRef = useRef6(onInterimTranscript);
2376
+ const finalRef = useRef7(onFinalTranscript);
2377
+ const interimRef = useRef7(onInterimTranscript);
1803
2378
  finalRef.current = onFinalTranscript;
1804
2379
  interimRef.current = onInterimTranscript;
1805
- useEffect6(() => {
2380
+ useEffect7(() => {
1806
2381
  if (!ctor) return;
1807
2382
  const recognition = new ctor();
1808
2383
  recognition.lang = lang;
@@ -1841,7 +2416,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
1841
2416
  ctor,
1842
2417
  lang
1843
2418
  ]);
1844
- const start = useCallback5(() => {
2419
+ const start = useCallback6(() => {
1845
2420
  const recognition = recognitionRef.current;
1846
2421
  if (!recognition) return;
1847
2422
  try {
@@ -1851,7 +2426,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
1851
2426
  console.warn("SpeechRecognition start failed", error);
1852
2427
  }
1853
2428
  }, []);
1854
- const stop = useCallback5(() => {
2429
+ const stop = useCallback6(() => {
1855
2430
  const recognition = recognitionRef.current;
1856
2431
  if (!recognition) return;
1857
2432
  try {
@@ -1870,8 +2445,8 @@ __name(useSpeechRecognition, "useSpeechRecognition");
1870
2445
 
1871
2446
  // src/components/chat-input.tsx
1872
2447
  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 }) {
1873
- const [value, setValue] = useState7("");
1874
- const [interim, setInterim] = useState7("");
2448
+ const [value, setValue] = useState8("");
2449
+ const [interim, setInterim] = useState8("");
1875
2450
  const speech = useSpeechRecognition({
1876
2451
  lang: speechLang,
1877
2452
  onFinalTranscript: /* @__PURE__ */ __name((text) => {
@@ -1882,7 +2457,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
1882
2457
  }, "onFinalTranscript"),
1883
2458
  onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
1884
2459
  });
1885
- useEffect7(() => {
2460
+ useEffect8(() => {
1886
2461
  if (!speech.isListening) setInterim("");
1887
2462
  }, [
1888
2463
  speech.isListening
@@ -1949,7 +2524,7 @@ __name(ChatInput, "ChatInput");
1949
2524
  import React3 from "react";
1950
2525
 
1951
2526
  // src/transcript/use-chat-transcript.ts
1952
- import { useCallback as useCallback7, useEffect as useEffect9, useRef as useRef8, useState as useState9 } from "react";
2527
+ import { useCallback as useCallback8, useEffect as useEffect10, useRef as useRef9, useState as useState10 } from "react";
1953
2528
 
1954
2529
  // src/transcript/model.ts
1955
2530
  import { readElicitationInput, validateElicitationAnswer as validateElicitationAnswer2 } from "@dudousxd/nestjs-agent-core";
@@ -2046,21 +2621,21 @@ function fillTemplate(template, context) {
2046
2621
  }).replace(/\s{2,}/g, " ").trim();
2047
2622
  }
2048
2623
  __name(fillTemplate, "fillTemplate");
2049
- function phraseFor(presentation, input, isSettled2, fallback = {
2624
+ function phraseFor(presentation, input, isSettled2, fallback2 = {
2050
2625
  running: "Working",
2051
2626
  done: "Done"
2052
2627
  }) {
2053
- if (presentation === void 0) return isSettled2 ? fallback.done : fallback.running;
2628
+ if (presentation === void 0) return isSettled2 ? fallback2.done : fallback2.running;
2054
2629
  const filled = fillTemplate(isSettled2 ? presentation.done : presentation.running, input);
2055
2630
  return filled === "" ? presentation.label : filled;
2056
2631
  }
2057
2632
  __name(phraseFor, "phraseFor");
2058
2633
 
2059
2634
  // src/presentation/result-view.ts
2060
- function isRecord(value) {
2635
+ function isRecord2(value) {
2061
2636
  return typeof value === "object" && value !== null && !Array.isArray(value);
2062
2637
  }
2063
- __name(isRecord, "isRecord");
2638
+ __name(isRecord2, "isRecord");
2064
2639
  function toText(value) {
2065
2640
  if (value === null || value === void 0) return "\u2014";
2066
2641
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
@@ -2071,7 +2646,7 @@ function toText(value) {
2071
2646
  }
2072
2647
  __name(toText, "toText");
2073
2648
  function inferResultView(output) {
2074
- if (!isRecord(output)) return void 0;
2649
+ if (!isRecord2(output)) return void 0;
2075
2650
  for (const [key, value] of Object.entries(output)) {
2076
2651
  if (!Array.isArray(value) || value.length === 0) continue;
2077
2652
  if (value.every((entry) => typeof entry === "string")) {
@@ -2080,12 +2655,12 @@ function inferResultView(output) {
2080
2655
  lines: key
2081
2656
  };
2082
2657
  }
2083
- if (value.every(isRecord)) {
2658
+ if (value.every(isRecord2)) {
2084
2659
  const first = value[0];
2085
2660
  return {
2086
2661
  kind: "table",
2087
2662
  rows: key,
2088
- columns: Object.keys(first).filter((column) => !isRecord(first[column])).map((column) => ({
2663
+ columns: Object.keys(first).filter((column) => !isRecord2(first[column])).map((column) => ({
2089
2664
  path: column,
2090
2665
  label: column
2091
2666
  }))
@@ -2461,7 +3036,7 @@ function isSettled(state) {
2461
3036
  __name(isSettled, "isSettled");
2462
3037
  function toolKind(part) {
2463
3038
  const metadata = part.toolMetadata;
2464
- return isRecord2(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
3039
+ return isRecord3(metadata) && typeof metadata.toolKind === "string" ? metadata.toolKind : void 0;
2465
3040
  }
2466
3041
  __name(toolKind, "toolKind");
2467
3042
  function isAwaitingApproval(part) {
@@ -2476,7 +3051,7 @@ function isAwaitingApproval(part) {
2476
3051
  __name(isAwaitingApproval, "isAwaitingApproval");
2477
3052
  function toolParentId(part) {
2478
3053
  const metadata = part.toolMetadata;
2479
- return isRecord2(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
3054
+ return isRecord3(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
2480
3055
  }
2481
3056
  __name(toolParentId, "toolParentId");
2482
3057
  function nestToolCalls(calls) {
@@ -2543,12 +3118,12 @@ function buildToolCall(part, options, approval, catalog) {
2543
3118
  __name(buildToolCall, "buildToolCall");
2544
3119
  function readElicitationRequest(part) {
2545
3120
  const input = part.input;
2546
- if (!isRecord2(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
3121
+ if (!isRecord3(input) || !Array.isArray(input.questions) || input.questions.length === 0) {
2547
3122
  return null;
2548
3123
  }
2549
3124
  const questions = [];
2550
3125
  for (const candidate of input.questions) {
2551
- if (!isRecord2(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string") {
3126
+ if (!isRecord3(candidate) || typeof candidate.id !== "string" || typeof candidate.prompt !== "string") {
2552
3127
  return null;
2553
3128
  }
2554
3129
  const input2 = readElicitationInput(candidate.input) ?? null;
@@ -2558,7 +3133,7 @@ function readElicitationRequest(part) {
2558
3133
  }
2559
3134
  const options = [];
2560
3135
  for (const option of rawOptions) {
2561
- if (!isRecord2(option) || typeof option.value !== "string" || typeof option.label !== "string") {
3136
+ if (!isRecord3(option) || typeof option.value !== "string" || typeof option.label !== "string") {
2562
3137
  return null;
2563
3138
  }
2564
3139
  options.push({
@@ -2586,7 +3161,7 @@ function readElicitationRequest(part) {
2586
3161
  __name(readElicitationRequest, "readElicitationRequest");
2587
3162
  function readElicitationOutcome(part) {
2588
3163
  const output = part.output;
2589
- if (!isRecord2(output) || !isRecord2(output.answers) || typeof output.skipped !== "boolean") {
3164
+ if (!isRecord3(output) || !isRecord3(output.answers) || typeof output.skipped !== "boolean") {
2590
3165
  return null;
2591
3166
  }
2592
3167
  const answers = {};
@@ -2723,7 +3298,7 @@ function readApprovals(parts) {
2723
3298
  continue;
2724
3299
  }
2725
3300
  const data = part.data;
2726
- if (!isRecord2(data) || typeof data.id !== "string") {
3301
+ if (!isRecord3(data) || typeof data.id !== "string") {
2727
3302
  continue;
2728
3303
  }
2729
3304
  const approval = entry(data.id);
@@ -2779,7 +3354,7 @@ function statusFromPart(call) {
2779
3354
  __name(statusFromPart, "statusFromPart");
2780
3355
  function readUiComponent(part) {
2781
3356
  const data = part.data;
2782
- if (!isRecord2(data) || typeof data.component !== "string") {
3357
+ if (!isRecord3(data) || typeof data.component !== "string") {
2783
3358
  return null;
2784
3359
  }
2785
3360
  const id = typeof data.id === "string" ? data.id : part.id;
@@ -2789,19 +3364,19 @@ function readUiComponent(part) {
2789
3364
  return {
2790
3365
  id,
2791
3366
  component: data.component,
2792
- props: isRecord2(data.props) ? data.props : {},
3367
+ props: isRecord3(data.props) ? data.props : {},
2793
3368
  version: typeof data.version === "number" ? data.version : null,
2794
3369
  toolCallId: typeof data.toolCallId === "string" ? data.toolCallId : null
2795
3370
  };
2796
3371
  }
2797
3372
  __name(readUiComponent, "readUiComponent");
2798
- function isRecord2(value) {
3373
+ function isRecord3(value) {
2799
3374
  return typeof value === "object" && value !== null;
2800
3375
  }
2801
- __name(isRecord2, "isRecord");
3376
+ __name(isRecord3, "isRecord");
2802
3377
  function readQuery(part) {
2803
3378
  const input = part.input;
2804
- if (!isRecord2(input) || typeof input.query !== "string") {
3379
+ if (!isRecord3(input) || typeof input.query !== "string") {
2805
3380
  return null;
2806
3381
  }
2807
3382
  return input.query;
@@ -2809,12 +3384,12 @@ function readQuery(part) {
2809
3384
  __name(readQuery, "readQuery");
2810
3385
  function readPassages(part) {
2811
3386
  const output = part.output;
2812
- if (!isRecord2(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
3387
+ if (!isRecord3(output) || !Array.isArray(output.passages) || output.passages.length === 0) {
2813
3388
  return null;
2814
3389
  }
2815
3390
  const passages = [];
2816
3391
  for (const candidate of output.passages) {
2817
- if (!isRecord2(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
3392
+ if (!isRecord3(candidate) || typeof candidate.id !== "string" || typeof candidate.text !== "string") {
2818
3393
  return null;
2819
3394
  }
2820
3395
  passages.push({
@@ -2824,7 +3399,7 @@ function readPassages(part) {
2824
3399
  ...typeof candidate.source === "string" ? {
2825
3400
  source: candidate.source
2826
3401
  } : {},
2827
- ...isRecord2(candidate.metadata) ? {
3402
+ ...isRecord3(candidate.metadata) ? {
2828
3403
  metadata: candidate.metadata
2829
3404
  } : {}
2830
3405
  });
@@ -2926,11 +3501,11 @@ function formatCostUsd(cost) {
2926
3501
  __name(formatCostUsd, "formatCostUsd");
2927
3502
 
2928
3503
  // src/transcript/use-stick-to-bottom.ts
2929
- import { useCallback as useCallback6, useEffect as useEffect8, useRef as useRef7, useState as useState8 } from "react";
3504
+ import { useCallback as useCallback7, useEffect as useEffect9, useRef as useRef8, useState as useState9 } from "react";
2930
3505
  function useStickToBottom({ contentKey, threshold = 32 }) {
2931
- const containerRef = useRef7(null);
2932
- const [isAtBottom, setIsAtBottom] = useState8(true);
2933
- const measure = useCallback6(() => {
3506
+ const containerRef = useRef8(null);
3507
+ const [isAtBottom, setIsAtBottom] = useState9(true);
3508
+ const measure = useCallback7(() => {
2934
3509
  const element = containerRef.current;
2935
3510
  if (!element) {
2936
3511
  return;
@@ -2940,7 +3515,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2940
3515
  }, [
2941
3516
  threshold
2942
3517
  ]);
2943
- const scrollToBottom = useCallback6(() => {
3518
+ const scrollToBottom = useCallback7(() => {
2944
3519
  const element = containerRef.current;
2945
3520
  if (!element) {
2946
3521
  return;
@@ -2948,7 +3523,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2948
3523
  element.scrollTop = element.scrollHeight;
2949
3524
  setIsAtBottom(true);
2950
3525
  }, []);
2951
- const setContainer = useCallback6((element) => {
3526
+ const setContainer = useCallback7((element) => {
2952
3527
  containerRef.current = element;
2953
3528
  if (element) {
2954
3529
  measure();
@@ -2956,7 +3531,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2956
3531
  }, [
2957
3532
  measure
2958
3533
  ]);
2959
- useEffect8(() => {
3534
+ useEffect9(() => {
2960
3535
  if (isAtBottom) {
2961
3536
  scrollToBottom();
2962
3537
  }
@@ -2965,7 +3540,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2965
3540
  isAtBottom,
2966
3541
  scrollToBottom
2967
3542
  ]);
2968
- const getContainerProps = useCallback6(() => ({
3543
+ const getContainerProps = useCallback7(() => ({
2969
3544
  ref: setContainer,
2970
3545
  onScroll: measure
2971
3546
  }), [
@@ -2987,14 +3562,14 @@ var DEFAULT_LOAD_EARLIER_STEP = 50;
2987
3562
  var DEFAULT_COPY_RESET_MS = 1500;
2988
3563
  function useChatTranscript(options) {
2989
3564
  const { messages, status, initialVisibleCount = DEFAULT_VISIBLE_COUNT, loadEarlierStep = DEFAULT_LOAD_EARLIER_STEP, followUps, stickToBottomThreshold } = options;
2990
- const latest = useRef8(options);
3565
+ const latest = useRef9(options);
2991
3566
  latest.current = options;
2992
- const [visibleCount, setVisibleCount] = useState9(initialVisibleCount);
2993
- const [stopRequested, setStopRequested] = useState9(false);
3567
+ const [visibleCount, setVisibleCount] = useState10(initialVisibleCount);
3568
+ const [stopRequested, setStopRequested] = useState10(false);
2994
3569
  const isStreaming = status === "streaming" || status === "reconnecting";
2995
3570
  const isBusy = status === "submitted" || isStreaming;
2996
3571
  const isEmpty = messages.length === 0;
2997
- useEffect9(() => {
3572
+ useEffect10(() => {
2998
3573
  if (!isBusy) {
2999
3574
  setStopRequested(false);
3000
3575
  }
@@ -3011,7 +3586,7 @@ function useChatTranscript(options) {
3011
3586
  streamingMessageId,
3012
3587
  options
3013
3588
  });
3014
- const stop = useCallback7(() => {
3589
+ const stop = useCallback8(() => {
3015
3590
  const current = latest.current;
3016
3591
  if (current.status !== "submitted" && current.status !== "streaming" && current.status !== "reconnecting") {
3017
3592
  return;
@@ -3019,8 +3594,8 @@ function useChatTranscript(options) {
3019
3594
  setStopRequested(true);
3020
3595
  void current.onStop?.();
3021
3596
  }, []);
3022
- const loadEarlier = useCallback7(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
3023
- const showAll = useCallback7(() => setVisibleCount(latest.current.messages.length), []);
3597
+ const loadEarlier = useCallback8(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
3598
+ const showAll = useCallback8(() => setVisibleCount(latest.current.messages.length), []);
3024
3599
  const scroll = useStickToBottom({
3025
3600
  contentKey: contentSignature(messages),
3026
3601
  ...stickToBottomThreshold !== void 0 ? {
@@ -3056,7 +3631,7 @@ function useChatTranscript(options) {
3056
3631
  __name(useChatTranscript, "useChatTranscript");
3057
3632
  function useTranscriptItem(options) {
3058
3633
  const { message, isStreaming = false } = options;
3059
- const messagesRef = useRef8([
3634
+ const messagesRef = useRef9([
3060
3635
  message
3061
3636
  ]);
3062
3637
  if (messagesRef.current[0] !== message) {
@@ -3074,41 +3649,45 @@ function useTranscriptItem(options) {
3074
3649
  }
3075
3650
  __name(useTranscriptItem, "useTranscriptItem");
3076
3651
  function useTranscriptItems({ messages, visibleStart, streamingMessageId, options }) {
3077
- const [drafts, setDrafts] = useState9(() => /* @__PURE__ */ new Map());
3078
- const [copied, setCopied] = useState9(() => /* @__PURE__ */ new Set());
3079
- const [openReasoning, setOpenReasoning] = useState9(() => /* @__PURE__ */ new Map());
3080
- const [picks, setPicks] = useState9(() => /* @__PURE__ */ new Map());
3081
- const [settling, setSettling] = useState9(() => /* @__PURE__ */ new Map());
3082
- const [settleErrors, setSettleErrors] = useState9(() => /* @__PURE__ */ new Map());
3083
- const latest = useRef8({
3652
+ const [drafts, setDrafts] = useState10(() => /* @__PURE__ */ new Map());
3653
+ const [copied, setCopied] = useState10(() => /* @__PURE__ */ new Set());
3654
+ const [openReasoning, setOpenReasoning] = useState10(() => /* @__PURE__ */ new Map());
3655
+ const [picks, setPicks] = useState10(() => /* @__PURE__ */ new Map());
3656
+ const [settling, setSettling] = useState10(() => /* @__PURE__ */ new Map());
3657
+ const [settleErrors, setSettleErrors] = useState10(() => /* @__PURE__ */ new Map());
3658
+ const backend = useAgentBackend();
3659
+ const handlers = settleHandlers(options, backend);
3660
+ const latest = useRef9({
3084
3661
  options,
3662
+ handlers,
3085
3663
  drafts,
3086
3664
  picks,
3087
3665
  messages
3088
3666
  });
3089
3667
  latest.current = {
3090
3668
  options,
3669
+ handlers,
3091
3670
  drafts,
3092
3671
  picks,
3093
3672
  messages
3094
3673
  };
3095
- const copyTimers = useRef8(/* @__PURE__ */ new Map());
3096
- useEffect9(() => () => {
3674
+ const copyTimers = useRef9(/* @__PURE__ */ new Map());
3675
+ useEffect10(() => () => {
3097
3676
  for (const timer of copyTimers.current.values()) {
3098
3677
  clearTimeout(timer);
3099
3678
  }
3100
3679
  copyTimers.current.clear();
3101
3680
  }, []);
3102
- const callbackCache = useRef8(/* @__PURE__ */ new Map());
3103
- const reasoningToggleCache = useRef8(/* @__PURE__ */ new Map());
3104
- const toggleReasoning = useCallback7((key, open) => {
3681
+ const callbackCache = useRef9(/* @__PURE__ */ new Map());
3682
+ const reasoningToggleCache = useRef9(/* @__PURE__ */ new Map());
3683
+ const toggleReasoning = useCallback8((key, open) => {
3105
3684
  setOpenReasoning((current) => {
3106
3685
  const next = new Map(current);
3107
3686
  next.set(key, open ?? !(current.get(key) ?? false));
3108
3687
  return next;
3109
3688
  });
3110
3689
  }, []);
3111
- const stableToggle = useCallback7((key) => {
3690
+ const stableToggle = useCallback8((key) => {
3112
3691
  let cached = reasoningToggleCache.current.get(key);
3113
3692
  if (!cached) {
3114
3693
  cached = /* @__PURE__ */ __name((open) => toggleReasoning(key, open), "cached");
@@ -3118,10 +3697,10 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3118
3697
  }, [
3119
3698
  toggleReasoning
3120
3699
  ]);
3121
- const pick = useCallback7((toolCallId, questionId, values) => {
3700
+ const pick = useCallback8((toolCallId, questionId, values) => {
3122
3701
  setPicks((current) => new Map(current).set(pickKey(toolCallId, questionId), values));
3123
3702
  }, []);
3124
- const settle = useCallback7((toolCallId, action, send) => {
3703
+ const settle = useCallback8((toolCallId, action, send) => {
3125
3704
  setSettleErrors((current) => {
3126
3705
  if (!current.has(toolCallId)) {
3127
3706
  return current;
@@ -3145,41 +3724,53 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3145
3724
  failed(error);
3146
3725
  }
3147
3726
  }, []);
3148
- const answer = useCallback7((toolCallId) => {
3727
+ const answer = useCallback8((toolCallId) => {
3149
3728
  const current = latest.current;
3150
- const onAnswer = current.options.onAnswer;
3729
+ const onAnswer = current.handlers.onAnswer;
3151
3730
  if (!onAnswer) {
3152
3731
  return;
3153
3732
  }
3154
- settle(toolCallId, "answer", () => onAnswer(toolCallId, answersFor(current.picks, toolCallId)));
3733
+ settle(toolCallId, "answer", () => onAnswer({
3734
+ toolCallId,
3735
+ answers: answersFor(current.picks, toolCallId)
3736
+ }));
3155
3737
  }, [
3156
3738
  settle
3157
3739
  ]);
3158
- const skip = useCallback7((toolCallId) => {
3159
- const onSkip = latest.current.options.onSkip;
3740
+ const skip = useCallback8((toolCallId) => {
3741
+ const onSkip = latest.current.handlers.onSkip;
3160
3742
  if (onSkip) {
3161
- settle(toolCallId, "skip", () => onSkip(toolCallId));
3743
+ settle(toolCallId, "skip", () => onSkip({
3744
+ toolCallId
3745
+ }));
3162
3746
  }
3163
3747
  }, [
3164
3748
  settle
3165
3749
  ]);
3166
- const approve = useCallback7((toolCallId, options2) => {
3167
- const onApprove = latest.current.options.onApprove;
3750
+ const approve = useCallback8((toolCallId, options2) => {
3751
+ const onApprove = latest.current.handlers.onApprove;
3168
3752
  if (onApprove) {
3169
- settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
3753
+ settle(toolCallId, "approve", () => onApprove({
3754
+ toolCallId,
3755
+ ...options2?.remember === true ? {
3756
+ remember: true
3757
+ } : {}
3758
+ }));
3170
3759
  }
3171
3760
  }, [
3172
3761
  settle
3173
3762
  ]);
3174
- const reject = useCallback7((toolCallId) => {
3175
- const onReject = latest.current.options.onReject;
3763
+ const reject = useCallback8((toolCallId) => {
3764
+ const onReject = latest.current.handlers.onReject;
3176
3765
  if (onReject) {
3177
- settle(toolCallId, "reject", () => onReject(toolCallId));
3766
+ settle(toolCallId, "reject", () => onReject({
3767
+ toolCallId
3768
+ }));
3178
3769
  }
3179
3770
  }, [
3180
3771
  settle
3181
3772
  ]);
3182
- const callbacksFor = useCallback7((id) => {
3773
+ const callbacksFor = useCallback8((id) => {
3183
3774
  const cached = callbackCache.current.get(id);
3184
3775
  if (cached) {
3185
3776
  return cached;
@@ -3229,13 +3820,20 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3229
3820
  return;
3230
3821
  }
3231
3822
  created.cancelEdit();
3232
- void latest.current.options.onEditSubmit?.(id, next);
3823
+ void latest.current.options.onEditSubmit?.({
3824
+ messageId: id,
3825
+ text: next
3826
+ });
3233
3827
  }, "save"),
3234
3828
  fork: /* @__PURE__ */ __name(() => {
3235
- void latest.current.options.onFork?.(id);
3829
+ void latest.current.options.onFork?.({
3830
+ messageId: id
3831
+ });
3236
3832
  }, "fork"),
3237
3833
  regenerate: /* @__PURE__ */ __name(() => {
3238
- void latest.current.options.onRegenerate?.(id);
3834
+ void latest.current.options.onRegenerate?.({
3835
+ messageId: id
3836
+ });
3239
3837
  }, "regenerate"),
3240
3838
  textareaRef: /* @__PURE__ */ __name((element) => {
3241
3839
  if (!element) {
@@ -3272,7 +3870,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3272
3870
  const draft = drafts.get(message.id);
3273
3871
  const isEditing = draft !== void 0;
3274
3872
  const text = extractMessageText(message.parts);
3275
- const usage = options.getUsage?.(message) ?? null;
3873
+ const usage = (options.getUsage ?? usageFromMetadata)(message);
3276
3874
  const isUser = message.role === "user";
3277
3875
  const isAssistant = message.role === "assistant";
3278
3876
  const isLastAssistant = isAssistant && message.id === lastAssistantId;
@@ -3294,22 +3892,22 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3294
3892
  ...options.toolCatalog !== void 0 ? {
3295
3893
  toolCatalog: options.toolCatalog
3296
3894
  } : {},
3297
- ...options.onAnswer !== void 0 ? {
3895
+ ...handlers.onAnswer !== void 0 ? {
3298
3896
  elicitation: {
3299
3897
  picked: /* @__PURE__ */ __name((toolCallId, questionId) => picks.get(pickKey(toolCallId, questionId)), "picked"),
3300
3898
  pick,
3301
3899
  canAnswer: true,
3302
- canSkip: options.onSkip !== void 0,
3900
+ canSkip: handlers.onSkip !== void 0,
3303
3901
  answer,
3304
3902
  skip,
3305
3903
  submitting: /* @__PURE__ */ __name((toolCallId) => settling.get(toolCallId) ?? null, "submitting"),
3306
3904
  errorOf: /* @__PURE__ */ __name((toolCallId) => settleErrors.get(toolCallId) ?? null, "errorOf")
3307
3905
  }
3308
3906
  } : {},
3309
- ...options.onApprove !== void 0 || options.onReject !== void 0 ? {
3907
+ ...handlers.onApprove !== void 0 || handlers.onReject !== void 0 ? {
3310
3908
  approval: {
3311
- canApprove: options.onApprove !== void 0,
3312
- canReject: options.onReject !== void 0,
3909
+ canApprove: handlers.onApprove !== void 0,
3910
+ canReject: handlers.onReject !== void 0,
3313
3911
  approve,
3314
3912
  reject,
3315
3913
  submitting: /* @__PURE__ */ __name((toolCallId) => settling.get(toolCallId) ?? null, "submitting"),
@@ -3319,7 +3917,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3319
3917
  }),
3320
3918
  text,
3321
3919
  usage: usage ? describeUsage(usage) : null,
3322
- timestamp: describeTimestamp(options.getCreatedAt?.(message)),
3920
+ timestamp: describeTimestamp((options.getCreatedAt ?? createdAtFromMetadata)(message)),
3323
3921
  copy: {
3324
3922
  available: text.length > 0,
3325
3923
  copied: copied.has(message.id),
@@ -3354,6 +3952,43 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
3354
3952
  return items;
3355
3953
  }
3356
3954
  __name(useTranscriptItems, "useTranscriptItems");
3955
+ function settleHandlers(options, backend) {
3956
+ const pick = /* @__PURE__ */ __name((own, fallback2) => own === null ? void 0 : own ?? fallback2, "pick");
3957
+ return {
3958
+ onAnswer: pick(options.onAnswer, backend.answerToolCall ? async (input) => {
3959
+ await backend.answerToolCall?.(input);
3960
+ } : void 0),
3961
+ onSkip: pick(options.onSkip, backend.skipToolCall ? async (input) => {
3962
+ await backend.skipToolCall?.(input);
3963
+ } : void 0),
3964
+ onApprove: pick(options.onApprove, backend.approveToolCall ? async (input) => {
3965
+ await backend.approveToolCall?.(input);
3966
+ } : void 0),
3967
+ onReject: pick(options.onReject, backend.rejectToolCall ? async (input) => {
3968
+ await backend.rejectToolCall?.(input);
3969
+ } : void 0)
3970
+ };
3971
+ }
3972
+ __name(settleHandlers, "settleHandlers");
3973
+ function metadataOf2(message) {
3974
+ const metadata = message.metadata;
3975
+ return metadata !== null && typeof metadata === "object" ? metadata : {};
3976
+ }
3977
+ __name(metadataOf2, "metadataOf");
3978
+ function usageFromMetadata(message) {
3979
+ const usage = metadataOf2(message).usage;
3980
+ if (usage === void 0) return null;
3981
+ return {
3982
+ inputTokens: usage.inputTokens,
3983
+ outputTokens: usage.outputTokens,
3984
+ costUsd: usage.costUsd ?? null
3985
+ };
3986
+ }
3987
+ __name(usageFromMetadata, "usageFromMetadata");
3988
+ function createdAtFromMetadata(message) {
3989
+ return metadataOf2(message).createdAt ?? null;
3990
+ }
3991
+ __name(createdAtFromMetadata, "createdAtFromMetadata");
3357
3992
  function pickKey(toolCallId, questionId) {
3358
3993
  return `${toolCallId}|${questionId}`;
3359
3994
  }
@@ -3403,7 +4038,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
3403
4038
  editable
3404
4039
  } : {},
3405
4040
  ...onEditSubmit ? {
3406
- onEditSubmit: /* @__PURE__ */ __name((_id, next) => onEditSubmit(next), "onEditSubmit")
4041
+ onEditSubmit
3407
4042
  } : {},
3408
4043
  ...onFork ? {
3409
4044
  onFork
@@ -3412,7 +4047,7 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
3412
4047
  regeneratable
3413
4048
  } : {},
3414
4049
  ...onRegenerate ? {
3415
- onRegenerate: /* @__PURE__ */ __name(() => onRegenerate(), "onRegenerate")
4050
+ onRegenerate
3416
4051
  } : {},
3417
4052
  ...usage != null ? {
3418
4053
  getUsage: /* @__PURE__ */ __name(() => usage, "getUsage")
@@ -3807,11 +4442,11 @@ function createSkillsSource(options) {
3807
4442
  if (cached) {
3808
4443
  return cached;
3809
4444
  }
3810
- const listSkills = options.client.listSkills;
4445
+ const listSkills = options.backend.listSkills;
3811
4446
  if (listSkills === void 0) {
3812
4447
  return Promise.reject(new AgentBackendUnsupportedError("listSkills"));
3813
4448
  }
3814
- const pending = listSkills.call(options.client, threadId).then((entries) => entries.map(toItem)).catch((error) => {
4449
+ const pending = listSkills.call(options.backend, threadId).then((entries) => entries.map(toItem)).catch((error) => {
3815
4450
  byThread.delete(key);
3816
4451
  throw error;
3817
4452
  });
@@ -3841,35 +4476,35 @@ function toItem(entry) {
3841
4476
  __name(toItem, "toItem");
3842
4477
 
3843
4478
  // src/composer/use-composer-autocomplete.ts
3844
- import { useCallback as useCallback8, useEffect as useEffect10, useId, useLayoutEffect, useMemo as useMemo6, useRef as useRef9, useState as useState10 } from "react";
4479
+ import { useCallback as useCallback9, useEffect as useEffect11, useId, useLayoutEffect, useMemo as useMemo9, useRef as useRef10, useState as useState11 } from "react";
3845
4480
  function useComposerAutocomplete(options) {
3846
4481
  const { value, sources, acceptOnTab = true } = options;
3847
- const latest = useRef9(options);
4482
+ const latest = useRef10(options);
3848
4483
  latest.current = options;
3849
- const inputRef = useRef9(null);
3850
- const [caret, setCaret] = useState10(value.length);
3851
- const [items, setItems] = useState10([]);
3852
- const [isLoading, setLoading] = useState10(false);
3853
- const [error, setError] = useState10(null);
3854
- const [activeIndex, setActiveIndex] = useState10(0);
3855
- const [dismissedIndex, setDismissedIndex] = useState10(null);
4484
+ const inputRef = useRef10(null);
4485
+ const [caret, setCaret] = useState11(value.length);
4486
+ const [items, setItems] = useState11([]);
4487
+ const [isLoading, setLoading] = useState11(false);
4488
+ const [error, setError] = useState11(null);
4489
+ const [activeIndex, setActiveIndex] = useState11(0);
4490
+ const [dismissedIndex, setDismissedIndex] = useState11(null);
3856
4491
  const safeCaret = Math.min(caret, value.length);
3857
- const match = useMemo6(() => findActiveTrigger(value, safeCaret, sources), [
4492
+ const match = useMemo9(() => findActiveTrigger(value, safeCaret, sources), [
3858
4493
  value,
3859
4494
  safeCaret,
3860
4495
  sources
3861
4496
  ]);
3862
4497
  const isActive = match !== null && dismissedIndex !== match.index;
3863
- const matchRef = useRef9(match);
4498
+ const matchRef = useRef10(match);
3864
4499
  matchRef.current = match;
3865
- const caretRef = useRef9(safeCaret);
4500
+ const caretRef = useRef10(safeCaret);
3866
4501
  caretRef.current = safeCaret;
3867
- const itemsRef = useRef9(items);
4502
+ const itemsRef = useRef10(items);
3868
4503
  itemsRef.current = items;
3869
- const activeIndexRef = useRef9(activeIndex);
4504
+ const activeIndexRef = useRef10(activeIndex);
3870
4505
  activeIndexRef.current = activeIndex;
3871
4506
  const requestKey = isActive && match ? `${match.source.id}\0${match.query}` : null;
3872
- useEffect10(() => {
4507
+ useEffect11(() => {
3873
4508
  const current = matchRef.current;
3874
4509
  if (requestKey === null || current === null) {
3875
4510
  setItems([]);
@@ -3919,23 +4554,23 @@ function useComposerAutocomplete(options) {
3919
4554
  const activeItem = isOpen ? items[activeIndex] ?? null : null;
3920
4555
  const reactId = useId();
3921
4556
  const listboxId = options.id ?? `agent-autocomplete-${reactId}`;
3922
- const optionId = useCallback8((index) => `${listboxId}-option-${index}`, [
4557
+ const optionId = useCallback9((index) => `${listboxId}-option-${index}`, [
3923
4558
  listboxId
3924
4559
  ]);
3925
- const highlight = useCallback8((index) => {
4560
+ const highlight = useCallback9((index) => {
3926
4561
  const total = itemsRef.current.length;
3927
4562
  if (total === 0) return;
3928
4563
  setActiveIndex((index % total + total) % total);
3929
4564
  }, []);
3930
- const moveHighlight = useCallback8((delta) => highlight(activeIndexRef.current + delta), [
4565
+ const moveHighlight = useCallback9((delta) => highlight(activeIndexRef.current + delta), [
3931
4566
  highlight
3932
4567
  ]);
3933
- const dismiss = useCallback8(() => {
4568
+ const dismiss = useCallback9(() => {
3934
4569
  const current = matchRef.current;
3935
4570
  if (current) setDismissedIndex(current.index);
3936
4571
  }, []);
3937
- const pendingCaret = useRef9(null);
3938
- const accept = useCallback8((item) => {
4572
+ const pendingCaret = useRef10(null);
4573
+ const accept = useCallback9((item) => {
3939
4574
  const current = matchRef.current;
3940
4575
  if (!current) return;
3941
4576
  const chosen = item ?? itemsRef.current[activeIndexRef.current];
@@ -3952,10 +4587,10 @@ function useComposerAutocomplete(options) {
3952
4587
  pendingCaret.current = null;
3953
4588
  inputRef.current?.setSelectionRange(position, position);
3954
4589
  });
3955
- const readCaret = useCallback8((element) => {
4590
+ const readCaret = useCallback9((element) => {
3956
4591
  setCaret(element.selectionStart ?? element.value.length);
3957
4592
  }, []);
3958
- const onKeyDown = useCallback8((event) => {
4593
+ const onKeyDown = useCallback9((event) => {
3959
4594
  if (!isOpen) return;
3960
4595
  const hasItems = itemsRef.current.length > 0;
3961
4596
  switch (event.key) {
@@ -3993,23 +4628,23 @@ function useComposerAutocomplete(options) {
3993
4628
  dismiss,
3994
4629
  moveHighlight
3995
4630
  ]);
3996
- const onChange = useCallback8((event) => {
4631
+ const onChange = useCallback9((event) => {
3997
4632
  readCaret(event.target);
3998
4633
  const next = findActiveTrigger(event.target.value, event.target.selectionStart ?? event.target.value.length, latest.current.sources);
3999
4634
  setDismissedIndex((current) => current !== null && next?.index === current ? current : null);
4000
4635
  }, [
4001
4636
  readCaret
4002
4637
  ]);
4003
- const onSelect = useCallback8((event) => readCaret(event.currentTarget), [
4638
+ const onSelect = useCallback9((event) => readCaret(event.currentTarget), [
4004
4639
  readCaret
4005
4640
  ]);
4006
- const onClick = useCallback8((event) => readCaret(event.currentTarget), [
4641
+ const onClick = useCallback9((event) => readCaret(event.currentTarget), [
4007
4642
  readCaret
4008
4643
  ]);
4009
- const setInput = useCallback8((element) => {
4644
+ const setInput = useCallback9((element) => {
4010
4645
  inputRef.current = element;
4011
4646
  }, []);
4012
- const getInputProps = useCallback8(() => ({
4647
+ const getInputProps = useCallback9(() => ({
4013
4648
  ref: setInput,
4014
4649
  onChange,
4015
4650
  onKeyDown,
@@ -4037,7 +4672,7 @@ function useComposerAutocomplete(options) {
4037
4672
  activeIndex,
4038
4673
  optionId
4039
4674
  ]);
4040
- const getListboxProps = useCallback8(() => ({
4675
+ const getListboxProps = useCallback9(() => ({
4041
4676
  id: listboxId,
4042
4677
  role: "listbox",
4043
4678
  "aria-label": match?.source.label ?? match?.source.id ?? "Suggestions"
@@ -4045,7 +4680,7 @@ function useComposerAutocomplete(options) {
4045
4680
  listboxId,
4046
4681
  match
4047
4682
  ]);
4048
- const getOptionProps = useCallback8((index) => ({
4683
+ const getOptionProps = useCallback9((index) => ({
4049
4684
  id: optionId(index),
4050
4685
  role: "option",
4051
4686
  "aria-selected": index === activeIndex
@@ -4079,7 +4714,7 @@ function isPromise(value) {
4079
4714
  __name(isPromise, "isPromise");
4080
4715
 
4081
4716
  // src/presentation/use-tool-catalog.ts
4082
- import { useCallback as useCallback9, useEffect as useEffect11, useMemo as useMemo7, useState as useState11 } from "react";
4717
+ import { useCallback as useCallback10, useEffect as useEffect12, useMemo as useMemo10, useState as useState12 } from "react";
4083
4718
  var shared = /* @__PURE__ */ new WeakMap();
4084
4719
  function load(client, agent) {
4085
4720
  let byAgent = shared.get(client);
@@ -4101,13 +4736,14 @@ function load(client, agent) {
4101
4736
  return request;
4102
4737
  }
4103
4738
  __name(load, "load");
4104
- function useToolCatalog(options) {
4105
- const { client, agent, enabled = true } = options;
4106
- const [entries, setEntries] = useState11([]);
4107
- const [isLoading, setLoading] = useState11(enabled);
4108
- const [error, setError] = useState11(null);
4109
- const [generation, setGeneration] = useState11(0);
4110
- useEffect11(() => {
4739
+ function useToolCatalog(options = {}) {
4740
+ const { agent, enabled = true } = options;
4741
+ const client = useAgentBackend(options.backend);
4742
+ const [entries, setEntries] = useState12([]);
4743
+ const [isLoading, setLoading] = useState12(enabled);
4744
+ const [error, setError] = useState12(null);
4745
+ const [generation, setGeneration] = useState12(0);
4746
+ useEffect12(() => {
4111
4747
  if (!enabled) {
4112
4748
  setLoading(false);
4113
4749
  return;
@@ -4116,7 +4752,7 @@ function useToolCatalog(options) {
4116
4752
  setLoading(true);
4117
4753
  load(client, agent).then((loaded) => {
4118
4754
  if (cancelled) return;
4119
- setEntries(loaded);
4755
+ setEntries(Array.isArray(loaded) ? loaded : []);
4120
4756
  setError(null);
4121
4757
  setLoading(false);
4122
4758
  }, (failure) => {
@@ -4133,14 +4769,14 @@ function useToolCatalog(options) {
4133
4769
  enabled,
4134
4770
  generation
4135
4771
  ]);
4136
- const refresh = useCallback9(() => {
4772
+ const refresh = useCallback10(() => {
4137
4773
  shared.get(client)?.delete(agent ?? "");
4138
4774
  setGeneration((current) => current + 1);
4139
4775
  }, [
4140
4776
  client,
4141
4777
  agent
4142
4778
  ]);
4143
- const catalog = useMemo7(() => toolCatalogFrom(entries), [
4779
+ const catalog = useMemo10(() => toolCatalogFrom(entries), [
4144
4780
  entries
4145
4781
  ]);
4146
4782
  return {
@@ -4154,7 +4790,7 @@ function useToolCatalog(options) {
4154
4790
  __name(useToolCatalog, "useToolCatalog");
4155
4791
 
4156
4792
  // src/approvals/countdown.ts
4157
- import { useEffect as useEffect12, useRef as useRef10, useState as useState12 } from "react";
4793
+ import { useEffect as useEffect13, useRef as useRef11, useState as useState13 } from "react";
4158
4794
  function approvalCountdown(expiresAt, now = Date.now()) {
4159
4795
  const deadline = expiresAt == null ? Number.NaN : Date.parse(expiresAt);
4160
4796
  if (!Number.isFinite(deadline)) {
@@ -4172,10 +4808,10 @@ function approvalCountdown(expiresAt, now = Date.now()) {
4172
4808
  __name(approvalCountdown, "approvalCountdown");
4173
4809
  function useApprovalCountdown(expiresAt, options = {}) {
4174
4810
  const { intervalMs = 1e3 } = options;
4175
- const nowRef = useRef10(options.now ?? Date.now);
4811
+ const nowRef = useRef11(options.now ?? Date.now);
4176
4812
  nowRef.current = options.now ?? Date.now;
4177
- const [state, setState] = useState12(() => approvalCountdown(expiresAt, nowRef.current()));
4178
- useEffect12(() => {
4813
+ const [state, setState] = useState13(() => approvalCountdown(expiresAt, nowRef.current()));
4814
+ useEffect13(() => {
4179
4815
  const read = /* @__PURE__ */ __name(() => approvalCountdown(expiresAt, nowRef.current()), "read");
4180
4816
  const first = read();
4181
4817
  setState(first);
@@ -4344,14 +4980,27 @@ function storedMessageToUiMessage(message) {
4344
4980
  });
4345
4981
  parts.push(...pushedByCall.get(call.id) ?? []);
4346
4982
  }
4983
+ const metadata = {
4984
+ ...message.feedback !== void 0 ? {
4985
+ feedback: message.feedback
4986
+ } : {},
4987
+ ...message.createdAt ? {
4988
+ createdAt: message.createdAt
4989
+ } : {},
4990
+ ...message.usage !== void 0 ? {
4991
+ usage: {
4992
+ inputTokens: message.usage.inputTokens,
4993
+ outputTokens: message.usage.outputTokens,
4994
+ costUsd: message.usage.costUsd ?? null
4995
+ }
4996
+ } : {}
4997
+ };
4347
4998
  return {
4348
4999
  id: message.id,
4349
5000
  role: message.role,
4350
5001
  parts,
4351
- ...message.feedback !== void 0 ? {
4352
- metadata: {
4353
- feedback: message.feedback
4354
- }
5002
+ ...Object.keys(metadata).length > 0 ? {
5003
+ metadata
4355
5004
  } : {}
4356
5005
  };
4357
5006
  }
@@ -4389,12 +5038,16 @@ function mergeTurn(rows) {
4389
5038
  const parts = rows.flatMap((row) => storedMessageToUiMessage(row).parts);
4390
5039
  const usage = sumUsage(rows);
4391
5040
  const feedback = lastRow.feedback;
5041
+ const createdAt = rows[0]?.createdAt;
4392
5042
  const metadata = {
4393
5043
  ...usage !== void 0 ? {
4394
5044
  usage
4395
5045
  } : {},
4396
5046
  ...feedback !== void 0 ? {
4397
5047
  feedback
5048
+ } : {},
5049
+ ...createdAt ? {
5050
+ createdAt
4398
5051
  } : {}
4399
5052
  };
4400
5053
  return {
@@ -4423,68 +5076,62 @@ __name(sumUsage, "sumUsage");
4423
5076
 
4424
5077
  // src/use-agent-chat.ts
4425
5078
  import { useChat } from "@ai-sdk/react";
4426
- import { useCallback as useCallback10, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef11, useState as useState13 } from "react";
4427
- function useAgentChat(options) {
4428
- const latest = useRef11(options);
5079
+ import { useCallback as useCallback11, useEffect as useEffect14, useMemo as useMemo11, useRef as useRef12, useState as useState14 } from "react";
5080
+ var localChatSequence = 0;
5081
+ function localChatId() {
5082
+ localChatSequence += 1;
5083
+ return `local-chat-${localChatSequence}`;
5084
+ }
5085
+ __name(localChatId, "localChatId");
5086
+ function useAgentChat(options = {}) {
5087
+ const latest = useRef12(options);
4429
5088
  latest.current = options;
4430
- const [runId, setRunId] = useState13(options.resumeRunId);
4431
- const runIdRef = useRef11(runId);
5089
+ const resolved = useAgentBackend(options.backend);
5090
+ const client = useRef12(resolved).current;
5091
+ const createdThreadId = useRef12(void 0);
5092
+ const chatIdRef = useRef12(options.threadId ?? localChatId());
5093
+ const boundThreadRef = useRef12(options.threadId);
5094
+ if (options.threadId !== boundThreadRef.current) {
5095
+ boundThreadRef.current = options.threadId;
5096
+ const adopting = options.threadId !== void 0 && options.threadId === createdThreadId.current;
5097
+ if (!adopting) {
5098
+ chatIdRef.current = options.threadId ?? localChatId();
5099
+ createdThreadId.current = void 0;
5100
+ }
5101
+ }
5102
+ const chatId = chatIdRef.current;
5103
+ const [runId, setRunId] = useState14(options.resumeRunId);
5104
+ const runIdRef = useRef12(runId);
4432
5105
  runIdRef.current = runId;
4433
- const settlingRunIdRef = useRef11(void 0);
4434
- const autoResumeRunIdRef = useRef11(void 0);
4435
- const [autoResume, setAutoResume] = useState13(false);
4436
- const [activeRunId, setActiveRunId] = useState13(null);
4437
- const regenerateNext = useRef11(false);
4438
- const createdThreadId = useRef11(void 0);
4439
- const client = useMemo8(() => {
4440
- if (options.backend) return options.backend;
4441
- if (options.client) return options.client;
4442
- return new AgentClient({
4443
- ...options.baseUrl !== void 0 ? {
4444
- baseUrl: options.baseUrl
4445
- } : {},
4446
- ...options.credentials !== void 0 ? {
4447
- credentials: options.credentials
4448
- } : {},
4449
- ...options.fetch !== void 0 ? {
4450
- fetch: options.fetch
4451
- } : {},
4452
- ...options.attachments !== void 0 ? {
4453
- attachments: options.attachments
4454
- } : {},
4455
- getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
4456
- });
4457
- }, []);
4458
- const [connection, setConnection] = useState13({
5106
+ const settlingRunIdRef = useRef12(void 0);
5107
+ const autoResumeRunIdRef = useRef12(void 0);
5108
+ const [activeRunId, setActiveRunId] = useState14(null);
5109
+ const [isLoadingHistory, setIsLoadingHistory] = useState14(false);
5110
+ const [historyError, setHistoryError] = useState14(null);
5111
+ const [threadModel, setThreadModel] = useState14(null);
5112
+ const [pickedModel, setPickedModel] = useState14(void 0);
5113
+ const pickedModelRef = useRef12(pickedModel);
5114
+ pickedModelRef.current = pickedModel;
5115
+ const pendingPin = useRef12(void 0);
5116
+ const regenerateNext = useRef12(false);
5117
+ const currentThreadId = useCallback11(() => latest.current.threadId ?? createdThreadId.current, []);
5118
+ const [connection, setConnection] = useState14({
4459
5119
  status: "live"
4460
5120
  });
4461
- const resyncAfterSettle = useRef11(false);
4462
- useEffect13(() => {
4463
- if (!options.resume || options.threadId === void 0) return;
4464
- let cancelled = false;
4465
- const threadId = options.threadId;
4466
- void client.getThread(threadId).then((thread) => {
4467
- if (cancelled) return;
4468
- setActiveRunId(thread.activeRunId ?? null);
4469
- if (thread.activeRunId != null) {
4470
- autoResumeRunIdRef.current = thread.activeRunId;
4471
- setAutoResume(true);
4472
- }
4473
- });
4474
- return () => {
4475
- cancelled = true;
4476
- };
4477
- }, [
4478
- client,
4479
- options.resume,
4480
- options.threadId
4481
- ]);
4482
- const transport = useMemo8(() => {
5121
+ const resyncAfterSettle = useRef12(false);
5122
+ const transport = useMemo11(() => {
4483
5123
  function onMeta(meta) {
4484
5124
  settlingRunIdRef.current = meta.runId;
4485
5125
  setRunId(meta.runId);
4486
5126
  if (latest.current.threadId === void 0 && meta.threadId && createdThreadId.current !== meta.threadId) {
4487
5127
  createdThreadId.current = meta.threadId;
5128
+ const pin = pendingPin.current;
5129
+ if (pin !== void 0) {
5130
+ pendingPin.current = void 0;
5131
+ void client.updateThread(meta.threadId, {
5132
+ model: pin.model
5133
+ }).catch(() => void 0);
5134
+ }
4488
5135
  latest.current.onThreadCreated?.(meta.threadId);
4489
5136
  notifyThreads(client, {
4490
5137
  type: "changed"
@@ -4511,13 +5158,14 @@ function useAgentChat(options) {
4511
5158
  const pageContext = current.getPageContext?.() ?? null;
4512
5159
  const regenerate2 = regenerateNext.current;
4513
5160
  regenerateNext.current = false;
4514
- const threadId = current.threadId ?? createdThreadId.current;
5161
+ const threadId = currentThreadId();
5162
+ const model = current.model ?? pickedModelRef.current;
4515
5163
  return {
4516
5164
  ...threadId !== void 0 ? {
4517
5165
  threadId
4518
5166
  } : {},
4519
- ...current.model !== void 0 ? {
4520
- model: current.model
5167
+ ...model !== void 0 ? {
5168
+ model
4521
5169
  } : {},
4522
5170
  ...pageContext ? {
4523
5171
  pageContext
@@ -4534,13 +5182,11 @@ function useAgentChat(options) {
4534
5182
  }, "onAttemptStart")
4535
5183
  });
4536
5184
  }, []);
4537
- const backgroundRef = useRef11(() => Promise.resolve());
5185
+ const backgroundRef = useRef12(() => Promise.resolve());
4538
5186
  const chat = useChat({
4539
5187
  transport,
4540
- resume: options.resumeRunId !== void 0 || autoResume,
4541
- ...options.threadId !== void 0 ? {
4542
- id: options.threadId
4543
- } : {},
5188
+ id: chatId,
5189
+ resume: options.resumeRunId !== void 0,
4544
5190
  ...options.initialMessages !== void 0 ? {
4545
5191
  messages: options.initialMessages
4546
5192
  } : {},
@@ -4550,7 +5196,7 @@ function useAgentChat(options) {
4550
5196
  const title = part.data?.title;
4551
5197
  if (typeof title === "string") {
4552
5198
  latest.current.onTitle?.(title);
4553
- const threadId = latest.current.threadId ?? createdThreadId.current;
5199
+ const threadId = currentThreadId();
4554
5200
  if (threadId !== void 0) notifyThreads(client, {
4555
5201
  type: "title",
4556
5202
  threadId,
@@ -4583,10 +5229,10 @@ function useAgentChat(options) {
4583
5229
  }
4584
5230
  }, "onFinish")
4585
5231
  });
4586
- const chatRef = useRef11(chat);
5232
+ const chatRef = useRef12(chat);
4587
5233
  chatRef.current = chat;
4588
5234
  async function resyncFromThread() {
4589
- const threadId = latest.current.threadId ?? createdThreadId.current;
5235
+ const threadId = currentThreadId();
4590
5236
  if (threadId === void 0) return;
4591
5237
  try {
4592
5238
  const thread = await client.getThread(threadId);
@@ -4595,13 +5241,75 @@ function useAgentChat(options) {
4595
5241
  }
4596
5242
  }
4597
5243
  __name(resyncFromThread, "resyncFromThread");
4598
- const turnInFlight = useRef11(false);
4599
- const isTurnInFlight = useCallback10(() => turnInFlight.current || transport.isAttemptLive, [
5244
+ const firstChatId = useRef12(chatId);
5245
+ useEffect14(() => {
5246
+ if (chatId === firstChatId.current) return;
5247
+ firstChatId.current = chatId;
5248
+ setRunId(void 0);
5249
+ setActiveRunId(null);
5250
+ setThreadModel(null);
5251
+ setBackgroundRuns([]);
5252
+ setHistoryError(null);
5253
+ autoResumeRunIdRef.current = void 0;
5254
+ pendingPin.current = void 0;
5255
+ }, [
5256
+ chatId
5257
+ ]);
5258
+ useEffect14(() => {
5259
+ const threadId = options.threadId;
5260
+ if (threadId === void 0) return;
5261
+ if (threadId === createdThreadId.current) return;
5262
+ const current = latest.current;
5263
+ const wantHistory = current.history !== false && current.initialMessages === void 0;
5264
+ const wantResume = current.resume !== false && current.resumeRunId === void 0;
5265
+ if (!wantHistory && !wantResume) return;
5266
+ let cancelled = false;
5267
+ if (wantHistory) setIsLoadingHistory(true);
5268
+ client.getThread(threadId).then((thread) => {
5269
+ if (cancelled || thread == null) return;
5270
+ const active = thread.activeRunId ?? null;
5271
+ const stored = Array.isArray(thread.messages) ? thread.messages : [];
5272
+ setActiveRunId(active);
5273
+ setThreadModel(thread.model ?? null);
5274
+ setHistoryError(null);
5275
+ const resuming = wantResume && active !== null;
5276
+ if (wantHistory && chatRef.current.messages.length === 0) {
5277
+ const rows = resuming ? stored.filter((message) => !(message.role === "assistant" && message.runId === active)) : stored;
5278
+ chatRef.current.setMessages(storedThreadToUiMessages(rows));
5279
+ }
5280
+ if (resuming) {
5281
+ autoResumeRunIdRef.current = active;
5282
+ void chatRef.current.resumeStream();
5283
+ }
5284
+ }, (error) => {
5285
+ if (cancelled) return;
5286
+ setHistoryError(error instanceof Error ? error : new Error(String(error)));
5287
+ }).finally(() => {
5288
+ if (!cancelled) setIsLoadingHistory(false);
5289
+ });
5290
+ return () => {
5291
+ cancelled = true;
5292
+ setIsLoadingHistory(false);
5293
+ };
5294
+ }, [
5295
+ client,
5296
+ chatId,
5297
+ options.threadId
5298
+ ]);
5299
+ const turnInFlight = useRef12(false);
5300
+ const isTurnInFlight = useCallback11(() => turnInFlight.current || transport.isAttemptLive, [
4600
5301
  transport
4601
5302
  ]);
4602
- const sendMessage = useCallback10(async (...args) => {
4603
- const blocked = latest.current.blocked;
4604
- if (blocked != null) throw new QuotaBlockedError(blocked);
5303
+ const quota = useQuota({
5304
+ backend: client,
5305
+ enabled: options.quota !== false && typeof client.getQuota === "function"
5306
+ });
5307
+ const blocked = options.blocked !== void 0 ? options.blocked : quota.blocked;
5308
+ const blockedRef = useRef12(blocked);
5309
+ blockedRef.current = blocked;
5310
+ const sendMessage = useCallback11(async (...args) => {
5311
+ const block = blockedRef.current;
5312
+ if (block != null) throw new QuotaBlockedError(block);
4605
5313
  if (isTurnInFlight()) return;
4606
5314
  turnInFlight.current = true;
4607
5315
  try {
@@ -4612,16 +5320,14 @@ function useAgentChat(options) {
4612
5320
  }, [
4613
5321
  isTurnInFlight
4614
5322
  ]);
4615
- const addToolResult = useCallback10(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
5323
+ const addToolResult = useCallback11(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
4616
5324
  tool,
4617
5325
  toolCallId,
4618
5326
  output
4619
5327
  }), []);
4620
- const [threads, setThreads] = useState13([]);
4621
- const [quota, setQuota] = useState13(null);
4622
- const [backgroundRuns, setBackgroundRuns] = useState13([]);
4623
- const refreshBackground = useCallback10(async () => {
4624
- const threadId = latest.current.threadId ?? createdThreadId.current;
5328
+ const [backgroundRuns, setBackgroundRuns] = useState14([]);
5329
+ const refreshBackground = useCallback11(async () => {
5330
+ const threadId = currentThreadId();
4625
5331
  if (threadId === void 0) {
4626
5332
  return;
4627
5333
  }
@@ -4641,11 +5347,12 @@ function useAgentChat(options) {
4641
5347
  ];
4642
5348
  });
4643
5349
  }, [
4644
- client
5350
+ client,
5351
+ currentThreadId
4645
5352
  ]);
4646
5353
  backgroundRef.current = refreshBackground;
4647
5354
  const isWorking = backgroundRuns.some((run) => run.status === "running");
4648
- useEffect13(() => {
5355
+ useEffect14(() => {
4649
5356
  if (options.background !== true || options.threadId === void 0) {
4650
5357
  return;
4651
5358
  }
@@ -4653,9 +5360,10 @@ function useAgentChat(options) {
4653
5360
  }, [
4654
5361
  options.background,
4655
5362
  options.threadId,
5363
+ chatId,
4656
5364
  refreshBackground
4657
5365
  ]);
4658
- useEffect13(() => {
5366
+ useEffect14(() => {
4659
5367
  const every = latest.current.backgroundPollMs ?? 5e3;
4660
5368
  if (latest.current.background !== true || !isWorking || every <= 0) {
4661
5369
  return;
@@ -4666,66 +5374,19 @@ function useAgentChat(options) {
4666
5374
  isWorking,
4667
5375
  refreshBackground
4668
5376
  ]);
4669
- const loadThreads = useCallback10(async () => {
4670
- const list = await client.listThreads();
4671
- setThreads(list);
4672
- return list;
4673
- }, [
4674
- client
4675
- ]);
4676
- const loadThread = useCallback10((id) => client.getThread(id), [
4677
- client
4678
- ]);
4679
- const deleteThread = useCallback10(async (id) => {
4680
- await client.deleteThread(id);
4681
- setThreads((current) => current.filter((thread) => thread.id !== id));
4682
- }, [
4683
- client
4684
- ]);
4685
- const forkThread = useCallback10((threadId, messageId) => requireBackendMethod(client, "forkFromMessage")(threadId, messageId), [
4686
- client
4687
- ]);
4688
- const renameThread = useCallback10(async (id, title) => {
4689
- await client.updateThread(id, {
4690
- title
4691
- });
4692
- setThreads((current) => current.map((thread) => thread.id === id ? {
4693
- ...thread,
4694
- title
4695
- } : thread));
4696
- }, [
4697
- client
4698
- ]);
4699
- const promoteThread = useCallback10(async (id) => {
4700
- await requireBackendMethod(client, "promoteThread")(id);
4701
- await loadThreads();
4702
- }, [
4703
- client,
4704
- loadThreads
4705
- ]);
4706
- const truncateFromMessage = useCallback10((threadId, messageId) => requireBackendMethod(client, "truncateFromMessage")(threadId, messageId).then(() => void 0), [
4707
- client
4708
- ]);
4709
- const loadQuota = useCallback10(async () => {
4710
- const today = await requireBackendMethod(client, "getQuotaToday")();
4711
- setQuota(today);
4712
- return today;
4713
- }, [
4714
- client
4715
- ]);
4716
- const cancel = useCallback10(async () => {
5377
+ const cancel = useCallback11(async () => {
4717
5378
  chatRef.current.stop();
4718
- const activeRunId2 = runIdRef.current;
4719
- if (activeRunId2) {
5379
+ const active = runIdRef.current;
5380
+ if (active) {
4720
5381
  try {
4721
- await client.cancelStream(activeRunId2);
5382
+ await client.cancelStream(active);
4722
5383
  } catch {
4723
5384
  }
4724
5385
  }
4725
5386
  }, [
4726
5387
  client
4727
5388
  ]);
4728
- const approve = useCallback10(async ({ toolCallId, remember, via }) => {
5389
+ const approve = useCallback11(async ({ toolCallId, remember, via }) => {
4729
5390
  await requireBackendMethod(client, "approveToolCall")({
4730
5391
  toolCallId,
4731
5392
  ...remember === true ? {
@@ -4738,7 +5399,7 @@ function useAgentChat(options) {
4738
5399
  }, [
4739
5400
  client
4740
5401
  ]);
4741
- const reject = useCallback10(async ({ toolCallId, reason, via }) => {
5402
+ const reject = useCallback11(async ({ toolCallId, reason, via }) => {
4742
5403
  await requireBackendMethod(client, "rejectToolCall")({
4743
5404
  toolCallId,
4744
5405
  ...reason !== void 0 ? {
@@ -4751,7 +5412,7 @@ function useAgentChat(options) {
4751
5412
  }, [
4752
5413
  client
4753
5414
  ]);
4754
- const answer = useCallback10(async ({ toolCallId, answers }) => {
5415
+ const answer = useCallback11(async ({ toolCallId, answers }) => {
4755
5416
  await requireBackendMethod(client, "answerToolCall")({
4756
5417
  toolCallId,
4757
5418
  ...answers !== void 0 ? {
@@ -4761,16 +5422,16 @@ function useAgentChat(options) {
4761
5422
  }, [
4762
5423
  client
4763
5424
  ]);
4764
- const skip = useCallback10(async ({ toolCallId }) => {
5425
+ const skip = useCallback11(async ({ toolCallId }) => {
4765
5426
  await requireBackendMethod(client, "skipToolCall")({
4766
5427
  toolCallId
4767
5428
  });
4768
5429
  }, [
4769
5430
  client
4770
5431
  ]);
4771
- const regenerate = useCallback10(() => {
4772
- const blocked = latest.current.blocked;
4773
- if (blocked != null) throw new QuotaBlockedError(blocked);
5432
+ const regenerate = useCallback11((_input) => {
5433
+ const block = blockedRef.current;
5434
+ if (block != null) throw new QuotaBlockedError(block);
4774
5435
  if (isTurnInFlight()) return;
4775
5436
  regenerateNext.current = true;
4776
5437
  turnInFlight.current = true;
@@ -4780,15 +5441,78 @@ function useAgentChat(options) {
4780
5441
  }, [
4781
5442
  isTurnInFlight
4782
5443
  ]);
4783
- const background = {
4784
- runs: backgroundRuns,
4785
- isWorking,
4786
- refresh: refreshBackground
4787
- };
4788
- const setThreadModel = useCallback10(async (model) => {
4789
- const threadId = latest.current.threadId ?? createdThreadId.current;
5444
+ const storedIdOf = useCallback11(async (messageId, threadId) => {
5445
+ const message = chatRef.current.messages.find((candidate) => candidate.id === messageId);
5446
+ const runOf = message?.metadata?.runId;
5447
+ if (runOf === void 0) return messageId;
5448
+ const thread = await client.getThread(threadId);
5449
+ const row = [
5450
+ ...thread.messages
5451
+ ].reverse().find((candidate) => candidate.role === "assistant" && candidate.runId === runOf);
5452
+ return row?.id ?? messageId;
5453
+ }, [
5454
+ client
5455
+ ]);
5456
+ const fork = useCallback11(async ({ messageId, threadId }) => {
5457
+ const source = threadId ?? currentThreadId();
5458
+ if (source === void 0) throw new Error("fork: this chat has no thread yet");
5459
+ const stored = await storedIdOf(messageId, source);
5460
+ const forked = await requireBackendMethod(client, "forkFromMessage")(source, stored);
5461
+ notifyThreads(client, {
5462
+ type: "changed"
5463
+ });
5464
+ latest.current.onThreadCreated?.(forked.id);
5465
+ return forked;
5466
+ }, [
5467
+ client,
5468
+ currentThreadId,
5469
+ storedIdOf
5470
+ ]);
5471
+ const truncateFrom = useCallback11(async ({ messageId, threadId }) => {
5472
+ const target = threadId ?? currentThreadId();
5473
+ if (target === void 0) throw new Error("truncateFrom: this chat has no thread yet");
5474
+ const stored = await storedIdOf(messageId, target);
5475
+ await requireBackendMethod(client, "truncateFromMessage")(target, stored);
5476
+ }, [
5477
+ client,
5478
+ currentThreadId,
5479
+ storedIdOf
5480
+ ]);
5481
+ const promote = useCallback11(async ({ threadId } = {}) => {
5482
+ const target = threadId ?? currentThreadId();
5483
+ if (target === void 0) throw new Error("promote: this chat has no thread yet");
5484
+ await requireBackendMethod(client, "promoteThread")(target);
5485
+ notifyThreads(client, {
5486
+ type: "changed"
5487
+ });
5488
+ }, [
5489
+ client,
5490
+ currentThreadId
5491
+ ]);
5492
+ const [modelsWanted, setModelsWanted] = useState14(false);
5493
+ const modelsWantedRef = useRef12(modelsWanted);
5494
+ modelsWantedRef.current = modelsWanted;
5495
+ const catalog = useModels({
5496
+ backend: client,
5497
+ ...options.agent !== void 0 ? {
5498
+ agent: options.agent
5499
+ } : {},
5500
+ enabled: modelsWanted && typeof client.listModels === "function"
5501
+ });
5502
+ const wantModels = useCallback11(() => {
5503
+ if (modelsWantedRef.current) return;
5504
+ modelsWantedRef.current = true;
5505
+ queueMicrotask(() => setModelsWanted(true));
5506
+ }, []);
5507
+ const selectModel = useCallback11((id) => setPickedModel(id), []);
5508
+ const pinToThread = useCallback11(async (model) => {
5509
+ const threadId = currentThreadId();
5510
+ setThreadModel(model);
4790
5511
  if (threadId === void 0) {
4791
- throw new Error("setThreadModel: this chat has no thread yet");
5512
+ pendingPin.current = {
5513
+ model
5514
+ };
5515
+ return;
4792
5516
  }
4793
5517
  await client.updateThread(threadId, {
4794
5518
  model
@@ -4797,10 +5521,99 @@ function useAgentChat(options) {
4797
5521
  type: "changed"
4798
5522
  });
4799
5523
  }, [
4800
- client
5524
+ client,
5525
+ currentThreadId
4801
5526
  ]);
4802
- const getThreadId = useCallback10(() => latest.current.threadId ?? createdThreadId.current, []);
5527
+ const models = {
5528
+ get list() {
5529
+ wantModels();
5530
+ return catalog.models;
5531
+ },
5532
+ get providers() {
5533
+ wantModels();
5534
+ return catalog.providers;
5535
+ },
5536
+ selected: options.model ?? pickedModel ?? threadModel ?? catalog.defaultModel ?? null,
5537
+ select: selectModel,
5538
+ pinToThread,
5539
+ isLoading: modelsWanted && catalog.isLoading,
5540
+ error: catalog.error
5541
+ };
4803
5542
  const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
5543
+ const isBusy = status === "submitted" || status === "streaming" || status === "reconnecting";
5544
+ const files = useAttachments({
5545
+ ...options.composer,
5546
+ backend: client
5547
+ });
5548
+ const [text, setText] = useState14("");
5549
+ const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy ? "busy" : text.trim().length === 0 && files.attachments.length === 0 ? "empty" : null;
5550
+ const composerRef = useRef12({
5551
+ text,
5552
+ files,
5553
+ blockedBy
5554
+ });
5555
+ composerRef.current = {
5556
+ text,
5557
+ files,
5558
+ blockedBy
5559
+ };
5560
+ const submit = useCallback11(async () => {
5561
+ const current = composerRef.current;
5562
+ if (current.blockedBy !== null) return;
5563
+ const refs = current.files.refs;
5564
+ const draft = current.text.trim();
5565
+ setText("");
5566
+ current.files.clear();
5567
+ await sendMessage({
5568
+ text: draft
5569
+ }, refs.length > 0 ? {
5570
+ body: {
5571
+ attachments: refs
5572
+ }
5573
+ } : void 0);
5574
+ }, [
5575
+ sendMessage
5576
+ ]);
5577
+ const composer = {
5578
+ text,
5579
+ setText,
5580
+ files,
5581
+ canSend: blockedBy === null,
5582
+ blockedBy,
5583
+ submit
5584
+ };
5585
+ const ownCatalog = options.transcript?.toolCatalog;
5586
+ const tools = useToolCatalog({
5587
+ backend: client,
5588
+ ...options.agent !== void 0 ? {
5589
+ agent: options.agent
5590
+ } : {},
5591
+ enabled: ownCatalog === void 0 && typeof client.listTools === "function"
5592
+ });
5593
+ const canFork = typeof client.forkFromMessage === "function";
5594
+ const transcript = useChatTranscript({
5595
+ messages: chat.messages,
5596
+ status,
5597
+ onStop: cancel,
5598
+ onApprove: approve,
5599
+ onReject: reject,
5600
+ onAnswer: answer,
5601
+ onSkip: skip,
5602
+ regeneratable: true,
5603
+ onRegenerate: regenerate,
5604
+ ...canFork ? {
5605
+ onFork: /* @__PURE__ */ __name((input) => fork(input).then(() => void 0), "onFork")
5606
+ } : {},
5607
+ ...ownCatalog === void 0 ? {
5608
+ toolCatalog: tools.catalog
5609
+ } : {},
5610
+ ...options.transcript
5611
+ });
5612
+ const background = {
5613
+ runs: backgroundRuns,
5614
+ isWorking,
5615
+ refresh: refreshBackground
5616
+ };
4804
5617
  return {
4805
5618
  ...chat,
4806
5619
  status,
@@ -4811,34 +5624,38 @@ function useAgentChat(options) {
4811
5624
  /** Sub-agents this conversation started and did not wait for. */
4812
5625
  background,
4813
5626
  runId,
4814
- /** The `resume`-fetched thread's active run id, or `null` once resolved with none. */
5627
+ /** The loaded thread's running turn, or `null` once read with none. */
4815
5628
  activeRunId,
5629
+ /** The thread's history is being read. */
5630
+ isLoadingHistory,
5631
+ /** Reading the thread's history failed. */
5632
+ historyError,
4816
5633
  /**
4817
5634
  * The thread this chat is on right now: the `threadId` option, else the one the backend created
4818
5635
  * on the first send. A getter, because the created id is learned mid-stream.
4819
5636
  */
4820
- getThreadId,
4821
- setThreadModel,
4822
- /** The backend this chat talks to — pass it to `useThreads`, `useMessageFeedback`, … */
5637
+ getThreadId: currentThreadId,
5638
+ /** The backend this chat talks to — the same one every other hook under the provider uses. */
4823
5639
  backend: client,
4824
- /** Same object as {@link backend}; kept for callers that read it under its old name. */
4825
- client,
4826
- threads,
4827
- loadThreads,
4828
- loadThread,
4829
- deleteThread,
4830
- forkThread,
4831
- renameThread,
4832
- promoteThread,
4833
- truncateFromMessage,
5640
+ /** The transcript model, with every action already bound to this chat. */
5641
+ transcript,
5642
+ /** A headless composer: draft, files, send gate, `submit()`. */
5643
+ composer,
5644
+ /** The model picker: `list`, `selected`, `select(id)`, `pinToThread(id)`. */
5645
+ models,
5646
+ /** The caller's budget (`GET <base>/quota`); `quota.blocked` gates sends. */
4834
5647
  quota,
4835
- loadQuota,
5648
+ /** The window blocking sends right now (the `blocked` option, else the quota report's). */
5649
+ blocked,
4836
5650
  cancel,
4837
5651
  approve,
4838
5652
  reject,
4839
5653
  answer,
4840
5654
  skip,
4841
- regenerate
5655
+ regenerate,
5656
+ fork,
5657
+ truncateFrom,
5658
+ promote
4842
5659
  };
4843
5660
  }
4844
5661
  __name(useAgentChat, "useAgentChat");
@@ -4852,19 +5669,12 @@ var QuotaBlockedError = class extends Error {
4852
5669
  this.name = "QuotaBlockedError";
4853
5670
  }
4854
5671
  };
4855
- async function mergeHeaders(options) {
4856
- const dynamic = await options.getHeaders?.() ?? {};
4857
- return {
4858
- ...options.headers,
4859
- ...dynamic
4860
- };
4861
- }
4862
- __name(mergeHeaders, "mergeHeaders");
4863
5672
  export {
4864
5673
  AgentBackendUnsupportedError,
4865
5674
  AgentChatTransport,
4866
5675
  AgentClient,
4867
5676
  AgentHttpError,
5677
+ AgentProvider,
4868
5678
  AmbientRenderUiContext,
4869
5679
  ChatInput,
4870
5680
  DEFAULT_SPEECH_LANG,
@@ -4910,6 +5720,7 @@ export {
4910
5720
  storedThreadToUiMessages,
4911
5721
  toolCallState,
4912
5722
  toolCatalogFrom,
5723
+ useAgentBackend,
4913
5724
  useAgentChat,
4914
5725
  useAgents,
4915
5726
  useAmbientRenderUi,