@dudousxd/nestjs-agent-react 0.17.0 → 0.19.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
@@ -125,6 +125,9 @@ var AgentClient = class {
125
125
  * what a caller then rides on `sendMessage({ text }, { body: { attachments: [...] } })`.
126
126
  */
127
127
  async uploadAttachment(file, options = {}) {
128
+ if (options.onProgress !== void 0 && this.options.fetch === void 0 && typeof XMLHttpRequest !== "undefined") {
129
+ return this.uploadWithProgress(file, options);
130
+ }
128
131
  const formData = new FormData();
129
132
  formData.append("file", file);
130
133
  const response = await this.fetchImpl()(`${this.baseUrl()}/agent/attachments`, {
@@ -139,7 +142,47 @@ var AgentClient = class {
139
142
  signal: options.signal
140
143
  } : {}
141
144
  });
142
- return this.handleResponse(response, "POST", "/agent/attachments");
145
+ const attachment = await this.handleResponse(response, "POST", "/agent/attachments");
146
+ options.onProgress?.(1);
147
+ return attachment;
148
+ }
149
+ async uploadWithProgress(file, { signal, onProgress }) {
150
+ const headers = {
151
+ accept: "application/json",
152
+ ...await this.resolveHeaders()
153
+ };
154
+ const url = `${this.baseUrl()}/agent/attachments`;
155
+ return new Promise((resolve, reject) => {
156
+ const xhr = new XMLHttpRequest();
157
+ xhr.open("POST", url);
158
+ for (const [name, value] of Object.entries(headers)) xhr.setRequestHeader(name, value);
159
+ xhr.withCredentials = this.options.credentials === "include";
160
+ xhr.upload.onprogress = (event) => {
161
+ if (event.lengthComputable && event.total > 0) onProgress?.(event.loaded / event.total);
162
+ };
163
+ xhr.onload = () => {
164
+ if (xhr.status < 200 || xhr.status >= 300) {
165
+ reject(new AgentHttpError(xhr.status, "POST", "/agent/attachments", xhr.statusText));
166
+ return;
167
+ }
168
+ onProgress?.(1);
169
+ resolve(JSON.parse(xhr.responseText));
170
+ };
171
+ xhr.onerror = () => reject(new TypeError("Network error while uploading the attachment"));
172
+ xhr.onabort = () => reject(new DOMException("The upload was aborted", "AbortError"));
173
+ if (signal !== void 0) {
174
+ if (signal.aborted) {
175
+ reject(new DOMException("The upload was aborted", "AbortError"));
176
+ return;
177
+ }
178
+ signal.addEventListener("abort", () => xhr.abort(), {
179
+ once: true
180
+ });
181
+ }
182
+ const formData = new FormData();
183
+ formData.append("file", file);
184
+ xhr.send(formData);
185
+ });
143
186
  }
144
187
  promoteThread(id) {
145
188
  return this.request("POST", `/agent/threads/${encodeURIComponent(id)}/promote`);
@@ -987,6 +1030,68 @@ function delay(ms, signal) {
987
1030
  }
988
1031
  __name(delay, "delay");
989
1032
 
1033
+ // src/attachments/files.ts
1034
+ function fileKind(mediaType) {
1035
+ const type = mediaType.toLowerCase();
1036
+ if (type.startsWith("image/")) return "image";
1037
+ if (type === "application/pdf") return "pdf";
1038
+ if (type.startsWith("text/") || type === "application/json") return "text";
1039
+ if (type.startsWith("audio/")) return "audio";
1040
+ if (type.startsWith("video/")) return "video";
1041
+ return "other";
1042
+ }
1043
+ __name(fileKind, "fileKind");
1044
+ function messageFiles(message) {
1045
+ const files = [];
1046
+ for (const part of message.parts) {
1047
+ if (part.type !== "file") continue;
1048
+ const file = part;
1049
+ const filename = file.filename ?? null;
1050
+ const dot = filename?.lastIndexOf(".") ?? -1;
1051
+ const mediaId = file.providerMetadata?.agent?.mediaId;
1052
+ files.push({
1053
+ url: file.url,
1054
+ mediaType: file.mediaType,
1055
+ filename,
1056
+ kind: fileKind(file.mediaType),
1057
+ extension: filename !== null && dot > 0 ? filename.slice(dot + 1).toLowerCase() : null,
1058
+ mediaId: typeof mediaId === "string" ? mediaId : null
1059
+ });
1060
+ }
1061
+ return files;
1062
+ }
1063
+ __name(messageFiles, "messageFiles");
1064
+ function acceptsFile(accept, file) {
1065
+ const rules = (typeof accept === "string" ? accept.split(",") : accept ?? []).map((rule) => rule.trim().toLowerCase()).filter((rule) => rule.length > 0);
1066
+ if (rules.length === 0) return true;
1067
+ const type = file.type.toLowerCase();
1068
+ const name = file.name.toLowerCase();
1069
+ return rules.some((rule) => {
1070
+ if (rule.startsWith(".")) return name.endsWith(rule);
1071
+ if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1));
1072
+ return type === rule;
1073
+ });
1074
+ }
1075
+ __name(acceptsFile, "acceptsFile");
1076
+ function filesFromClipboard(data) {
1077
+ if (!data) return [];
1078
+ const files = [];
1079
+ for (const item of Array.from(data.items ?? [])) {
1080
+ if (item.kind !== "file") continue;
1081
+ const file = item.getAsFile();
1082
+ if (file !== null) files.push(file);
1083
+ }
1084
+ return files.length > 0 ? files : Array.from(data.files ?? []);
1085
+ }
1086
+ __name(filesFromClipboard, "filesFromClipboard");
1087
+ function dragHasFiles(data) {
1088
+ return Array.from(data?.types ?? []).includes("Files");
1089
+ }
1090
+ __name(dragHasFiles, "dragHasFiles");
1091
+
1092
+ // src/attachments/use-attachments.ts
1093
+ import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
1094
+
990
1095
  // src/backend.ts
991
1096
  var AgentBackendUnsupportedError = class extends Error {
992
1097
  static {
@@ -1007,19 +1112,239 @@ function requireBackendMethod(backend, name) {
1007
1112
  }
1008
1113
  __name(requireBackendMethod, "requireBackendMethod");
1009
1114
 
1115
+ // src/attachments/use-attachments.ts
1116
+ var DEFAULT_REJECTION = {
1117
+ type: /* @__PURE__ */ __name((file) => `${file.name} is not a supported file type`, "type"),
1118
+ size: /* @__PURE__ */ __name((file) => `${file.name} is too large`, "size"),
1119
+ count: /* @__PURE__ */ __name(() => "Too many files", "count")
1120
+ };
1121
+ var sequence = 0;
1122
+ function useAttachments(options) {
1123
+ const latest = useRef2(options);
1124
+ latest.current = options;
1125
+ const [items, setItems] = useState2([]);
1126
+ const itemsRef = useRef2(items);
1127
+ itemsRef.current = items;
1128
+ const controllers = useRef2(/* @__PURE__ */ new Map());
1129
+ const [dragDepth, setDragDepth] = useState2(0);
1130
+ const patch = useCallback((id, change) => {
1131
+ setItems((current) => current.map((item) => item.id === id ? {
1132
+ ...item,
1133
+ ...change
1134
+ } : item));
1135
+ }, []);
1136
+ const start = useCallback((item) => {
1137
+ const { upload, backend } = latest.current;
1138
+ const run = upload ?? (backend?.uploadAttachment !== void 0 ? backend.uploadAttachment.bind(backend) : void 0);
1139
+ if (run === void 0) {
1140
+ patch(item.id, {
1141
+ status: "error",
1142
+ error: new AgentBackendUnsupportedError("uploadAttachment").message
1143
+ });
1144
+ return;
1145
+ }
1146
+ const controller = new AbortController();
1147
+ controllers.current.set(item.id, controller);
1148
+ run(item.file, {
1149
+ signal: controller.signal,
1150
+ onProgress: /* @__PURE__ */ __name((fraction) => patch(item.id, {
1151
+ progress: Math.max(0, Math.min(1, fraction))
1152
+ }), "onProgress")
1153
+ }).then((attachment) => {
1154
+ if (controller.signal.aborted) return;
1155
+ controllers.current.delete(item.id);
1156
+ patch(item.id, {
1157
+ status: "ready",
1158
+ progress: 1,
1159
+ attachment
1160
+ });
1161
+ }, (error) => {
1162
+ if (controller.signal.aborted) return;
1163
+ controllers.current.delete(item.id);
1164
+ patch(item.id, {
1165
+ status: "error",
1166
+ error: error instanceof Error ? error.message : String(error)
1167
+ });
1168
+ });
1169
+ }, [
1170
+ patch
1171
+ ]);
1172
+ const add = useCallback((files) => {
1173
+ if (files == null) return;
1174
+ const { accept: accept2, maxBytes, maxFiles, describeRejection } = latest.current;
1175
+ const describe = /* @__PURE__ */ __name((reason, file) => describeRejection?.(reason, file) ?? DEFAULT_REJECTION[reason](file), "describe");
1176
+ let room = maxFiles === void 0 ? Number.POSITIVE_INFINITY : maxFiles - itemsRef.current.filter((item) => item.status !== "rejected").length;
1177
+ const staged = [];
1178
+ for (const file of Array.from(files)) {
1179
+ sequence += 1;
1180
+ const base = {
1181
+ id: `att-${sequence}`,
1182
+ file,
1183
+ name: file.name,
1184
+ size: file.size,
1185
+ type: file.type,
1186
+ progress: 0
1187
+ };
1188
+ const reason = !acceptsFile(accept2, file) ? "type" : maxBytes !== void 0 && file.size > maxBytes ? "size" : room <= 0 ? "count" : void 0;
1189
+ if (reason !== void 0) {
1190
+ staged.push({
1191
+ ...base,
1192
+ status: "rejected",
1193
+ error: describe(reason, file)
1194
+ });
1195
+ continue;
1196
+ }
1197
+ room -= 1;
1198
+ const previewUrl = file.type.startsWith("image/") && typeof URL.createObjectURL === "function" ? URL.createObjectURL(file) : void 0;
1199
+ staged.push({
1200
+ ...base,
1201
+ status: "uploading",
1202
+ ...previewUrl ? {
1203
+ previewUrl
1204
+ } : {}
1205
+ });
1206
+ }
1207
+ if (staged.length === 0) return;
1208
+ setItems((current) => [
1209
+ ...current,
1210
+ ...staged
1211
+ ]);
1212
+ for (const item of staged) {
1213
+ if (item.status === "uploading") start(item);
1214
+ }
1215
+ }, [
1216
+ start
1217
+ ]);
1218
+ const release = useCallback((item) => {
1219
+ if (item === void 0) return;
1220
+ controllers.current.get(item.id)?.abort();
1221
+ controllers.current.delete(item.id);
1222
+ if (item.previewUrl !== void 0 && typeof URL.revokeObjectURL === "function") {
1223
+ URL.revokeObjectURL(item.previewUrl);
1224
+ }
1225
+ }, []);
1226
+ const remove = useCallback((id) => {
1227
+ release(itemsRef.current.find((item) => item.id === id));
1228
+ setItems((current) => current.filter((item) => item.id !== id));
1229
+ }, [
1230
+ release
1231
+ ]);
1232
+ const clear = useCallback(() => {
1233
+ for (const item of itemsRef.current) release(item);
1234
+ setItems([]);
1235
+ }, [
1236
+ release
1237
+ ]);
1238
+ const retry = useCallback((id) => {
1239
+ const item = itemsRef.current.find((candidate) => candidate.id === id);
1240
+ if (item === void 0 || item.status !== "error") return;
1241
+ const { error: _previous, ...rest } = item;
1242
+ const again = {
1243
+ ...rest,
1244
+ status: "uploading",
1245
+ progress: 0
1246
+ };
1247
+ setItems((current) => current.map((candidate) => candidate.id === id ? again : candidate));
1248
+ start(again);
1249
+ }, [
1250
+ start
1251
+ ]);
1252
+ useEffect2(() => () => {
1253
+ for (const item of itemsRef.current) release(item);
1254
+ }, [
1255
+ release
1256
+ ]);
1257
+ const onPaste = useCallback((event) => {
1258
+ const files = filesFromClipboard(event.clipboardData);
1259
+ if (files.length === 0) return;
1260
+ event.preventDefault();
1261
+ add(files);
1262
+ }, [
1263
+ add
1264
+ ]);
1265
+ const dropZoneProps = useMemo(() => ({
1266
+ onDragEnter: /* @__PURE__ */ __name((event) => {
1267
+ if (!dragHasFiles(event.dataTransfer)) return;
1268
+ event.preventDefault();
1269
+ setDragDepth((depth) => depth + 1);
1270
+ }, "onDragEnter"),
1271
+ onDragOver: /* @__PURE__ */ __name((event) => {
1272
+ if (dragHasFiles(event.dataTransfer)) event.preventDefault();
1273
+ }, "onDragOver"),
1274
+ onDragLeave: /* @__PURE__ */ __name((event) => {
1275
+ if (!dragHasFiles(event.dataTransfer)) return;
1276
+ setDragDepth((depth) => Math.max(0, depth - 1));
1277
+ }, "onDragLeave"),
1278
+ onDrop: /* @__PURE__ */ __name((event) => {
1279
+ if (!dragHasFiles(event.dataTransfer)) return;
1280
+ event.preventDefault();
1281
+ setDragDepth(0);
1282
+ add(event.dataTransfer?.files);
1283
+ }, "onDrop")
1284
+ }), [
1285
+ add
1286
+ ]);
1287
+ const accept = options.accept;
1288
+ const inputProps = useMemo(() => ({
1289
+ type: "file",
1290
+ accept: typeof accept === "string" ? accept : accept?.join(","),
1291
+ multiple: options.maxFiles === void 0 || options.maxFiles > 1,
1292
+ onChange: /* @__PURE__ */ __name((event) => {
1293
+ const input = event.currentTarget;
1294
+ add(input.files);
1295
+ input.value = "";
1296
+ }, "onChange")
1297
+ }), [
1298
+ accept,
1299
+ options.maxFiles,
1300
+ add
1301
+ ]);
1302
+ return useMemo(() => {
1303
+ const ready = items.filter((item) => item.status === "ready" && item.attachment !== void 0);
1304
+ const attachments = ready.map((item) => item.attachment);
1305
+ return {
1306
+ items,
1307
+ add,
1308
+ remove,
1309
+ retry,
1310
+ clear,
1311
+ isUploading: items.some((item) => item.status === "uploading"),
1312
+ attachments,
1313
+ refs: attachments.map((attachment) => ({
1314
+ mediaId: attachment.mediaId
1315
+ })),
1316
+ inputProps,
1317
+ dropZoneProps,
1318
+ isDragging: dragDepth > 0,
1319
+ onPaste
1320
+ };
1321
+ }, [
1322
+ items,
1323
+ add,
1324
+ remove,
1325
+ retry,
1326
+ clear,
1327
+ inputProps,
1328
+ dropZoneProps,
1329
+ dragDepth,
1330
+ onPaste
1331
+ ]);
1332
+ }
1333
+ __name(useAttachments, "useAttachments");
1334
+
1010
1335
  // src/catalog/use-agents.ts
1011
- import { useMemo } from "react";
1336
+ import { useMemo as useMemo2 } from "react";
1012
1337
 
1013
1338
  // src/catalog/use-resource.ts
1014
- import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
1339
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
1015
1340
  function useResource(load2, key, enabled) {
1016
- const [data, setData] = useState2(void 0);
1017
- const [isLoading, setIsLoading] = useState2(enabled);
1018
- const [error, setError] = useState2(null);
1019
- const loadRef = useRef2(load2);
1341
+ const [data, setData] = useState3(void 0);
1342
+ const [isLoading, setIsLoading] = useState3(enabled);
1343
+ const [error, setError] = useState3(null);
1344
+ const loadRef = useRef3(load2);
1020
1345
  loadRef.current = load2;
1021
- const generation = useRef2(0);
1022
- const refresh = useCallback(async () => {
1346
+ const generation = useRef3(0);
1347
+ const refresh = useCallback2(async () => {
1023
1348
  const mine = ++generation.current;
1024
1349
  setIsLoading(true);
1025
1350
  try {
@@ -1034,7 +1359,7 @@ function useResource(load2, key, enabled) {
1034
1359
  if (mine === generation.current) setIsLoading(false);
1035
1360
  }
1036
1361
  }, []);
1037
- useEffect2(() => {
1362
+ useEffect3(() => {
1038
1363
  if (!enabled) return;
1039
1364
  void refresh();
1040
1365
  }, [
@@ -1055,7 +1380,7 @@ __name(useResource, "useResource");
1055
1380
  function useAgents(options) {
1056
1381
  const { backend, enabled = true } = options;
1057
1382
  const { data, isLoading, error, refresh } = useResource(() => requireBackendMethod(backend, "listAgents")(), "", enabled);
1058
- return useMemo(() => {
1383
+ return useMemo2(() => {
1059
1384
  const agents = data ?? [];
1060
1385
  return {
1061
1386
  agents,
@@ -1074,11 +1399,11 @@ function useAgents(options) {
1074
1399
  __name(useAgents, "useAgents");
1075
1400
 
1076
1401
  // src/catalog/use-models.ts
1077
- import { useMemo as useMemo2 } from "react";
1402
+ import { useMemo as useMemo3 } from "react";
1078
1403
  function useModels(options) {
1079
1404
  const { backend, agent, enabled = true } = options;
1080
1405
  const { data, isLoading, error, refresh } = useResource(() => requireBackendMethod(backend, "listModels")(agent), `${agent ?? ""}`, enabled);
1081
- return useMemo2(() => {
1406
+ return useMemo3(() => {
1082
1407
  const providers = data?.providers ?? [];
1083
1408
  const models = providers.flatMap((provider) => provider.models.map((model) => ({
1084
1409
  ...model,
@@ -1108,7 +1433,7 @@ function useModels(options) {
1108
1433
  __name(useModels, "useModels");
1109
1434
 
1110
1435
  // src/quota/use-quota.ts
1111
- import { useEffect as useEffect3, useMemo as useMemo3 } from "react";
1436
+ import { useEffect as useEffect4, useMemo as useMemo4 } from "react";
1112
1437
 
1113
1438
  // src/threads/threads-events.ts
1114
1439
  var listeners = /* @__PURE__ */ new WeakMap();
@@ -1135,7 +1460,7 @@ __name(onThreadsEvent, "onThreadsEvent");
1135
1460
  function useQuota(options) {
1136
1461
  const { backend, enabled = true, pollMs } = options;
1137
1462
  const { data, isLoading, error, refresh } = useResource(() => requireBackendMethod(backend, "getQuota")(), "", enabled);
1138
- useEffect3(() => {
1463
+ useEffect4(() => {
1139
1464
  if (!enabled) return;
1140
1465
  return onThreadsEvent(backend, (event) => {
1141
1466
  if (event.type === "changed") void refresh();
@@ -1145,7 +1470,7 @@ function useQuota(options) {
1145
1470
  enabled,
1146
1471
  refresh
1147
1472
  ]);
1148
- useEffect3(() => {
1473
+ useEffect4(() => {
1149
1474
  if (!enabled || pollMs === void 0 || pollMs <= 0) return;
1150
1475
  const timer = setInterval(() => void refresh(), pollMs);
1151
1476
  return () => clearInterval(timer);
@@ -1154,7 +1479,7 @@ function useQuota(options) {
1154
1479
  pollMs,
1155
1480
  refresh
1156
1481
  ]);
1157
- return useMemo3(() => {
1482
+ return useMemo4(() => {
1158
1483
  const windows = data?.windows ?? [];
1159
1484
  return {
1160
1485
  report: data,
@@ -1176,27 +1501,27 @@ function useQuota(options) {
1176
1501
  __name(useQuota, "useQuota");
1177
1502
 
1178
1503
  // src/feedback/use-message-feedback.ts
1179
- import { useCallback as useCallback2, useRef as useRef3, useState as useState3 } from "react";
1504
+ import { useCallback as useCallback3, useRef as useRef4, useState as useState4 } from "react";
1180
1505
  function useMessageFeedback(options) {
1181
- const latest = useRef3(options);
1506
+ const latest = useRef4(options);
1182
1507
  latest.current = options;
1183
- const [local, setLocal] = useState3(/* @__PURE__ */ new Map());
1184
- const [pending, setPending] = useState3(/* @__PURE__ */ new Set());
1185
- const [error, setError] = useState3(null);
1186
- const localRef = useRef3(local);
1508
+ const [local, setLocal] = useState4(/* @__PURE__ */ new Map());
1509
+ const [pending, setPending] = useState4(/* @__PURE__ */ new Set());
1510
+ const [error, setError] = useState4(null);
1511
+ const localRef = useRef4(local);
1187
1512
  localRef.current = local;
1188
- const resolved = useRef3(/* @__PURE__ */ new Map());
1189
- const feedbackOf = useCallback2((message) => {
1513
+ const resolved = useRef4(/* @__PURE__ */ new Map());
1514
+ const feedbackOf = useCallback3((message) => {
1190
1515
  const mine = local.get(message.id);
1191
1516
  if (mine !== void 0) return mine.feedback;
1192
1517
  return metadataOf(message).feedback ?? null;
1193
1518
  }, [
1194
1519
  local
1195
1520
  ]);
1196
- const isPending = useCallback2((message) => pending.has(message.id), [
1521
+ const isPending = useCallback3((message) => pending.has(message.id), [
1197
1522
  pending
1198
1523
  ]);
1199
- const storedIdFor = useCallback2(async (message) => {
1524
+ const storedIdFor = useCallback3(async (message) => {
1200
1525
  const runId = metadataOf(message).runId;
1201
1526
  if (runId === void 0) return message.id;
1202
1527
  const known = resolved.current.get(message.id);
@@ -1216,7 +1541,7 @@ function useMessageFeedback(options) {
1216
1541
  resolved.current.set(message.id, row.id);
1217
1542
  return row.id;
1218
1543
  }, []);
1219
- const rate = useCallback2(async (message, value, comment) => {
1544
+ const rate = useCallback3(async (message, value, comment) => {
1220
1545
  const setFeedback = requireBackendMethod(latest.current.backend, "setMessageFeedback");
1221
1546
  const had = localRef.current.get(message.id);
1222
1547
  const optimistic = value === null ? null : {
@@ -1262,7 +1587,7 @@ function useMessageFeedback(options) {
1262
1587
  }, [
1263
1588
  storedIdFor
1264
1589
  ]);
1265
- const toggle = useCallback2((message, value) => {
1590
+ const toggle = useCallback3((message, value) => {
1266
1591
  const mine = localRef.current.get(message.id);
1267
1592
  const current = mine !== void 0 ? mine.feedback : metadataOf(message).feedback ?? null;
1268
1593
  return rate(message, current?.value === value ? null : value);
@@ -1285,16 +1610,16 @@ function metadataOf(message) {
1285
1610
  __name(metadataOf, "metadataOf");
1286
1611
 
1287
1612
  // src/threads/use-threads.ts
1288
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
1613
+ import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5, useState as useState5 } from "react";
1289
1614
  function useThreads(options) {
1290
1615
  const { backend, enabled = true } = options;
1291
- const [threads, setThreads] = useState4([]);
1292
- const [isLoading, setIsLoading] = useState4(enabled);
1293
- const [error, setError] = useState4(null);
1294
- const threadsRef = useRef4(threads);
1616
+ const [threads, setThreads] = useState5([]);
1617
+ const [isLoading, setIsLoading] = useState5(enabled);
1618
+ const [error, setError] = useState5(null);
1619
+ const threadsRef = useRef5(threads);
1295
1620
  threadsRef.current = threads;
1296
- const generation = useRef4(0);
1297
- const refresh = useCallback3(async () => {
1621
+ const generation = useRef5(0);
1622
+ const refresh = useCallback4(async () => {
1298
1623
  const mine = ++generation.current;
1299
1624
  setIsLoading(true);
1300
1625
  try {
@@ -1311,7 +1636,7 @@ function useThreads(options) {
1311
1636
  }, [
1312
1637
  backend
1313
1638
  ]);
1314
- useEffect4(() => {
1639
+ useEffect5(() => {
1315
1640
  if (!enabled) return;
1316
1641
  void refresh();
1317
1642
  return onThreadsEvent(backend, (event) => {
@@ -1331,7 +1656,7 @@ function useThreads(options) {
1331
1656
  enabled,
1332
1657
  refresh
1333
1658
  ]);
1334
- const rename = useCallback3(async (id, title) => {
1659
+ const rename = useCallback4(async (id, title) => {
1335
1660
  const before = threadsRef.current;
1336
1661
  setThreads((current) => current.map((thread) => thread.id === id ? {
1337
1662
  ...thread,
@@ -1353,7 +1678,7 @@ function useThreads(options) {
1353
1678
  }, [
1354
1679
  backend
1355
1680
  ]);
1356
- const remove = useCallback3(async (id) => {
1681
+ const remove = useCallback4(async (id) => {
1357
1682
  const before = threadsRef.current;
1358
1683
  setThreads((current) => current.filter((thread) => thread.id !== id));
1359
1684
  try {
@@ -1427,11 +1752,29 @@ function backgroundRunsFromThread(messages) {
1427
1752
  }
1428
1753
  __name(backgroundRunsFromThread, "backgroundRunsFromThread");
1429
1754
 
1755
+ // src/components/ambient-ui.ts
1756
+ import { createContext, useContext } from "react";
1757
+ function sharedContext(key) {
1758
+ const symbol = Symbol.for(key);
1759
+ const holder = globalThis;
1760
+ const existing = holder[symbol];
1761
+ if (existing !== void 0) return existing;
1762
+ const created = createContext(null);
1763
+ holder[symbol] = created;
1764
+ return created;
1765
+ }
1766
+ __name(sharedContext, "sharedContext");
1767
+ var AmbientRenderUiContext = sharedContext("@dudousxd/nestjs-agent-react:ambient-render-ui");
1768
+ function useAmbientRenderUi() {
1769
+ return useContext(AmbientRenderUiContext);
1770
+ }
1771
+ __name(useAmbientRenderUi, "useAmbientRenderUi");
1772
+
1430
1773
  // src/components/chat-input.tsx
1431
- import React2, { useEffect as useEffect6, useState as useState6 } from "react";
1774
+ import React2, { useEffect as useEffect7, useState as useState7 } from "react";
1432
1775
 
1433
1776
  // src/components/use-speech-recognition.ts
1434
- import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo4, useRef as useRef5, useState as useState5 } from "react";
1777
+ import { useCallback as useCallback5, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef6, useState as useState6 } from "react";
1435
1778
  function getRecognitionCtor() {
1436
1779
  if (typeof window === "undefined") return null;
1437
1780
  const scope = window;
@@ -1439,15 +1782,15 @@ function getRecognitionCtor() {
1439
1782
  }
1440
1783
  __name(getRecognitionCtor, "getRecognitionCtor");
1441
1784
  function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTranscript }) {
1442
- const recognitionRef = useRef5(null);
1443
- const [isListening, setIsListening] = useState5(false);
1444
- const ctor = useMemo4(() => getRecognitionCtor(), []);
1785
+ const recognitionRef = useRef6(null);
1786
+ const [isListening, setIsListening] = useState6(false);
1787
+ const ctor = useMemo5(() => getRecognitionCtor(), []);
1445
1788
  const isSupported = ctor !== null;
1446
- const finalRef = useRef5(onFinalTranscript);
1447
- const interimRef = useRef5(onInterimTranscript);
1789
+ const finalRef = useRef6(onFinalTranscript);
1790
+ const interimRef = useRef6(onInterimTranscript);
1448
1791
  finalRef.current = onFinalTranscript;
1449
1792
  interimRef.current = onInterimTranscript;
1450
- useEffect5(() => {
1793
+ useEffect6(() => {
1451
1794
  if (!ctor) return;
1452
1795
  const recognition = new ctor();
1453
1796
  recognition.lang = lang;
@@ -1486,7 +1829,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
1486
1829
  ctor,
1487
1830
  lang
1488
1831
  ]);
1489
- const start = useCallback4(() => {
1832
+ const start = useCallback5(() => {
1490
1833
  const recognition = recognitionRef.current;
1491
1834
  if (!recognition) return;
1492
1835
  try {
@@ -1496,7 +1839,7 @@ function useSpeechRecognition({ lang = "en-US", onFinalTranscript, onInterimTran
1496
1839
  console.warn("SpeechRecognition start failed", error);
1497
1840
  }
1498
1841
  }, []);
1499
- const stop = useCallback4(() => {
1842
+ const stop = useCallback5(() => {
1500
1843
  const recognition = recognitionRef.current;
1501
1844
  if (!recognition) return;
1502
1845
  try {
@@ -1515,8 +1858,8 @@ __name(useSpeechRecognition, "useSpeechRecognition");
1515
1858
 
1516
1859
  // src/components/chat-input.tsx
1517
1860
  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 }) {
1518
- const [value, setValue] = useState6("");
1519
- const [interim, setInterim] = useState6("");
1861
+ const [value, setValue] = useState7("");
1862
+ const [interim, setInterim] = useState7("");
1520
1863
  const speech = useSpeechRecognition({
1521
1864
  lang: speechLang,
1522
1865
  onFinalTranscript: /* @__PURE__ */ __name((text) => {
@@ -1527,7 +1870,7 @@ function ChatInput({ onSubmit, disabled, placeholder, rows = 2, enableSpeech = t
1527
1870
  }, "onFinalTranscript"),
1528
1871
  onInterimTranscript: /* @__PURE__ */ __name((text) => setInterim(text), "onInterimTranscript")
1529
1872
  });
1530
- useEffect6(() => {
1873
+ useEffect7(() => {
1531
1874
  if (!speech.isListening) setInterim("");
1532
1875
  }, [
1533
1876
  speech.isListening
@@ -1594,7 +1937,7 @@ __name(ChatInput, "ChatInput");
1594
1937
  import React3 from "react";
1595
1938
 
1596
1939
  // src/transcript/use-chat-transcript.ts
1597
- import { useCallback as useCallback6, useEffect as useEffect8, useRef as useRef7, useState as useState8 } from "react";
1940
+ import { useCallback as useCallback7, useEffect as useEffect9, useRef as useRef8, useState as useState9 } from "react";
1598
1941
 
1599
1942
  // src/transcript/model.ts
1600
1943
  import { readElicitationInput, validateElicitationAnswer as validateElicitationAnswer2 } from "@dudousxd/nestjs-agent-core";
@@ -2571,11 +2914,11 @@ function formatCostUsd(cost) {
2571
2914
  __name(formatCostUsd, "formatCostUsd");
2572
2915
 
2573
2916
  // src/transcript/use-stick-to-bottom.ts
2574
- import { useCallback as useCallback5, useEffect as useEffect7, useRef as useRef6, useState as useState7 } from "react";
2917
+ import { useCallback as useCallback6, useEffect as useEffect8, useRef as useRef7, useState as useState8 } from "react";
2575
2918
  function useStickToBottom({ contentKey, threshold = 32 }) {
2576
- const containerRef = useRef6(null);
2577
- const [isAtBottom, setIsAtBottom] = useState7(true);
2578
- const measure = useCallback5(() => {
2919
+ const containerRef = useRef7(null);
2920
+ const [isAtBottom, setIsAtBottom] = useState8(true);
2921
+ const measure = useCallback6(() => {
2579
2922
  const element = containerRef.current;
2580
2923
  if (!element) {
2581
2924
  return;
@@ -2585,7 +2928,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2585
2928
  }, [
2586
2929
  threshold
2587
2930
  ]);
2588
- const scrollToBottom = useCallback5(() => {
2931
+ const scrollToBottom = useCallback6(() => {
2589
2932
  const element = containerRef.current;
2590
2933
  if (!element) {
2591
2934
  return;
@@ -2593,7 +2936,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2593
2936
  element.scrollTop = element.scrollHeight;
2594
2937
  setIsAtBottom(true);
2595
2938
  }, []);
2596
- const setContainer = useCallback5((element) => {
2939
+ const setContainer = useCallback6((element) => {
2597
2940
  containerRef.current = element;
2598
2941
  if (element) {
2599
2942
  measure();
@@ -2601,7 +2944,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2601
2944
  }, [
2602
2945
  measure
2603
2946
  ]);
2604
- useEffect7(() => {
2947
+ useEffect8(() => {
2605
2948
  if (isAtBottom) {
2606
2949
  scrollToBottom();
2607
2950
  }
@@ -2610,7 +2953,7 @@ function useStickToBottom({ contentKey, threshold = 32 }) {
2610
2953
  isAtBottom,
2611
2954
  scrollToBottom
2612
2955
  ]);
2613
- const getContainerProps = useCallback5(() => ({
2956
+ const getContainerProps = useCallback6(() => ({
2614
2957
  ref: setContainer,
2615
2958
  onScroll: measure
2616
2959
  }), [
@@ -2632,14 +2975,14 @@ var DEFAULT_LOAD_EARLIER_STEP = 50;
2632
2975
  var DEFAULT_COPY_RESET_MS = 1500;
2633
2976
  function useChatTranscript(options) {
2634
2977
  const { messages, status, initialVisibleCount = DEFAULT_VISIBLE_COUNT, loadEarlierStep = DEFAULT_LOAD_EARLIER_STEP, followUps, stickToBottomThreshold } = options;
2635
- const latest = useRef7(options);
2978
+ const latest = useRef8(options);
2636
2979
  latest.current = options;
2637
- const [visibleCount, setVisibleCount] = useState8(initialVisibleCount);
2638
- const [stopRequested, setStopRequested] = useState8(false);
2980
+ const [visibleCount, setVisibleCount] = useState9(initialVisibleCount);
2981
+ const [stopRequested, setStopRequested] = useState9(false);
2639
2982
  const isStreaming = status === "streaming" || status === "reconnecting";
2640
2983
  const isBusy = status === "submitted" || isStreaming;
2641
2984
  const isEmpty = messages.length === 0;
2642
- useEffect8(() => {
2985
+ useEffect9(() => {
2643
2986
  if (!isBusy) {
2644
2987
  setStopRequested(false);
2645
2988
  }
@@ -2656,7 +2999,7 @@ function useChatTranscript(options) {
2656
2999
  streamingMessageId,
2657
3000
  options
2658
3001
  });
2659
- const stop = useCallback6(() => {
3002
+ const stop = useCallback7(() => {
2660
3003
  const current = latest.current;
2661
3004
  if (current.status !== "submitted" && current.status !== "streaming" && current.status !== "reconnecting") {
2662
3005
  return;
@@ -2664,8 +3007,8 @@ function useChatTranscript(options) {
2664
3007
  setStopRequested(true);
2665
3008
  void current.onStop?.();
2666
3009
  }, []);
2667
- const loadEarlier = useCallback6(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
2668
- const showAll = useCallback6(() => setVisibleCount(latest.current.messages.length), []);
3010
+ const loadEarlier = useCallback7(() => setVisibleCount((count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)), []);
3011
+ const showAll = useCallback7(() => setVisibleCount(latest.current.messages.length), []);
2669
3012
  const scroll = useStickToBottom({
2670
3013
  contentKey: contentSignature(messages),
2671
3014
  ...stickToBottomThreshold !== void 0 ? {
@@ -2701,7 +3044,7 @@ function useChatTranscript(options) {
2701
3044
  __name(useChatTranscript, "useChatTranscript");
2702
3045
  function useTranscriptItem(options) {
2703
3046
  const { message, isStreaming = false } = options;
2704
- const messagesRef = useRef7([
3047
+ const messagesRef = useRef8([
2705
3048
  message
2706
3049
  ]);
2707
3050
  if (messagesRef.current[0] !== message) {
@@ -2719,13 +3062,13 @@ function useTranscriptItem(options) {
2719
3062
  }
2720
3063
  __name(useTranscriptItem, "useTranscriptItem");
2721
3064
  function useTranscriptItems({ messages, visibleStart, streamingMessageId, options }) {
2722
- const [drafts, setDrafts] = useState8(() => /* @__PURE__ */ new Map());
2723
- const [copied, setCopied] = useState8(() => /* @__PURE__ */ new Set());
2724
- const [openReasoning, setOpenReasoning] = useState8(() => /* @__PURE__ */ new Map());
2725
- const [picks, setPicks] = useState8(() => /* @__PURE__ */ new Map());
2726
- const [settling, setSettling] = useState8(() => /* @__PURE__ */ new Map());
2727
- const [settleErrors, setSettleErrors] = useState8(() => /* @__PURE__ */ new Map());
2728
- const latest = useRef7({
3065
+ const [drafts, setDrafts] = useState9(() => /* @__PURE__ */ new Map());
3066
+ const [copied, setCopied] = useState9(() => /* @__PURE__ */ new Set());
3067
+ const [openReasoning, setOpenReasoning] = useState9(() => /* @__PURE__ */ new Map());
3068
+ const [picks, setPicks] = useState9(() => /* @__PURE__ */ new Map());
3069
+ const [settling, setSettling] = useState9(() => /* @__PURE__ */ new Map());
3070
+ const [settleErrors, setSettleErrors] = useState9(() => /* @__PURE__ */ new Map());
3071
+ const latest = useRef8({
2729
3072
  options,
2730
3073
  drafts,
2731
3074
  picks,
@@ -2737,23 +3080,23 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2737
3080
  picks,
2738
3081
  messages
2739
3082
  };
2740
- const copyTimers = useRef7(/* @__PURE__ */ new Map());
2741
- useEffect8(() => () => {
3083
+ const copyTimers = useRef8(/* @__PURE__ */ new Map());
3084
+ useEffect9(() => () => {
2742
3085
  for (const timer of copyTimers.current.values()) {
2743
3086
  clearTimeout(timer);
2744
3087
  }
2745
3088
  copyTimers.current.clear();
2746
3089
  }, []);
2747
- const callbackCache = useRef7(/* @__PURE__ */ new Map());
2748
- const reasoningToggleCache = useRef7(/* @__PURE__ */ new Map());
2749
- const toggleReasoning = useCallback6((key, open) => {
3090
+ const callbackCache = useRef8(/* @__PURE__ */ new Map());
3091
+ const reasoningToggleCache = useRef8(/* @__PURE__ */ new Map());
3092
+ const toggleReasoning = useCallback7((key, open) => {
2750
3093
  setOpenReasoning((current) => {
2751
3094
  const next = new Map(current);
2752
3095
  next.set(key, open ?? !(current.get(key) ?? false));
2753
3096
  return next;
2754
3097
  });
2755
3098
  }, []);
2756
- const stableToggle = useCallback6((key) => {
3099
+ const stableToggle = useCallback7((key) => {
2757
3100
  let cached = reasoningToggleCache.current.get(key);
2758
3101
  if (!cached) {
2759
3102
  cached = /* @__PURE__ */ __name((open) => toggleReasoning(key, open), "cached");
@@ -2763,10 +3106,10 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2763
3106
  }, [
2764
3107
  toggleReasoning
2765
3108
  ]);
2766
- const pick = useCallback6((toolCallId, questionId, values) => {
3109
+ const pick = useCallback7((toolCallId, questionId, values) => {
2767
3110
  setPicks((current) => new Map(current).set(pickKey(toolCallId, questionId), values));
2768
3111
  }, []);
2769
- const settle = useCallback6((toolCallId, action, send) => {
3112
+ const settle = useCallback7((toolCallId, action, send) => {
2770
3113
  setSettleErrors((current) => {
2771
3114
  if (!current.has(toolCallId)) {
2772
3115
  return current;
@@ -2790,7 +3133,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2790
3133
  failed(error);
2791
3134
  }
2792
3135
  }, []);
2793
- const answer = useCallback6((toolCallId) => {
3136
+ const answer = useCallback7((toolCallId) => {
2794
3137
  const current = latest.current;
2795
3138
  const onAnswer = current.options.onAnswer;
2796
3139
  if (!onAnswer) {
@@ -2800,7 +3143,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2800
3143
  }, [
2801
3144
  settle
2802
3145
  ]);
2803
- const skip = useCallback6((toolCallId) => {
3146
+ const skip = useCallback7((toolCallId) => {
2804
3147
  const onSkip = latest.current.options.onSkip;
2805
3148
  if (onSkip) {
2806
3149
  settle(toolCallId, "skip", () => onSkip(toolCallId));
@@ -2808,7 +3151,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2808
3151
  }, [
2809
3152
  settle
2810
3153
  ]);
2811
- const approve = useCallback6((toolCallId, options2) => {
3154
+ const approve = useCallback7((toolCallId, options2) => {
2812
3155
  const onApprove = latest.current.options.onApprove;
2813
3156
  if (onApprove) {
2814
3157
  settle(toolCallId, "approve", () => options2 === void 0 ? onApprove(toolCallId) : onApprove(toolCallId, options2));
@@ -2816,7 +3159,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2816
3159
  }, [
2817
3160
  settle
2818
3161
  ]);
2819
- const reject = useCallback6((toolCallId) => {
3162
+ const reject = useCallback7((toolCallId) => {
2820
3163
  const onReject = latest.current.options.onReject;
2821
3164
  if (onReject) {
2822
3165
  settle(toolCallId, "reject", () => onReject(toolCallId));
@@ -2824,7 +3167,7 @@ function useTranscriptItems({ messages, visibleStart, streamingMessageId, option
2824
3167
  }, [
2825
3168
  settle
2826
3169
  ]);
2827
- const callbacksFor = useCallback6((id) => {
3170
+ const callbacksFor = useCallback7((id) => {
2828
3171
  const cached = callbackCache.current.get(id);
2829
3172
  if (cached) {
2830
3173
  return cached;
@@ -3072,7 +3415,12 @@ function MessageItem({ message, isStreaming = false, editable, onEditSubmit, reg
3072
3415
  });
3073
3416
  }
3074
3417
  __name(MessageItem, "MessageItem");
3075
- function MessageItemView({ item, ...slots }) {
3418
+ function MessageItemView({ item, ...given }) {
3419
+ const ambientRenderUi = useAmbientRenderUi();
3420
+ const slots = given.renderUi === void 0 && ambientRenderUi !== null ? {
3421
+ ...given,
3422
+ renderUi: ambientRenderUi
3423
+ } : given;
3076
3424
  const { classNames } = slots;
3077
3425
  const actionRow = /* @__PURE__ */ React3.createElement("div", {
3078
3426
  className: classNames?.actions,
@@ -3481,35 +3829,35 @@ function toItem(entry) {
3481
3829
  __name(toItem, "toItem");
3482
3830
 
3483
3831
  // src/composer/use-composer-autocomplete.ts
3484
- import { useCallback as useCallback7, useEffect as useEffect9, useId, useLayoutEffect, useMemo as useMemo5, useRef as useRef8, useState as useState9 } from "react";
3832
+ import { useCallback as useCallback8, useEffect as useEffect10, useId, useLayoutEffect, useMemo as useMemo6, useRef as useRef9, useState as useState10 } from "react";
3485
3833
  function useComposerAutocomplete(options) {
3486
3834
  const { value, sources, acceptOnTab = true } = options;
3487
- const latest = useRef8(options);
3835
+ const latest = useRef9(options);
3488
3836
  latest.current = options;
3489
- const inputRef = useRef8(null);
3490
- const [caret, setCaret] = useState9(value.length);
3491
- const [items, setItems] = useState9([]);
3492
- const [isLoading, setLoading] = useState9(false);
3493
- const [error, setError] = useState9(null);
3494
- const [activeIndex, setActiveIndex] = useState9(0);
3495
- const [dismissedIndex, setDismissedIndex] = useState9(null);
3837
+ const inputRef = useRef9(null);
3838
+ const [caret, setCaret] = useState10(value.length);
3839
+ const [items, setItems] = useState10([]);
3840
+ const [isLoading, setLoading] = useState10(false);
3841
+ const [error, setError] = useState10(null);
3842
+ const [activeIndex, setActiveIndex] = useState10(0);
3843
+ const [dismissedIndex, setDismissedIndex] = useState10(null);
3496
3844
  const safeCaret = Math.min(caret, value.length);
3497
- const match = useMemo5(() => findActiveTrigger(value, safeCaret, sources), [
3845
+ const match = useMemo6(() => findActiveTrigger(value, safeCaret, sources), [
3498
3846
  value,
3499
3847
  safeCaret,
3500
3848
  sources
3501
3849
  ]);
3502
3850
  const isActive = match !== null && dismissedIndex !== match.index;
3503
- const matchRef = useRef8(match);
3851
+ const matchRef = useRef9(match);
3504
3852
  matchRef.current = match;
3505
- const caretRef = useRef8(safeCaret);
3853
+ const caretRef = useRef9(safeCaret);
3506
3854
  caretRef.current = safeCaret;
3507
- const itemsRef = useRef8(items);
3855
+ const itemsRef = useRef9(items);
3508
3856
  itemsRef.current = items;
3509
- const activeIndexRef = useRef8(activeIndex);
3857
+ const activeIndexRef = useRef9(activeIndex);
3510
3858
  activeIndexRef.current = activeIndex;
3511
3859
  const requestKey = isActive && match ? `${match.source.id}\0${match.query}` : null;
3512
- useEffect9(() => {
3860
+ useEffect10(() => {
3513
3861
  const current = matchRef.current;
3514
3862
  if (requestKey === null || current === null) {
3515
3863
  setItems([]);
@@ -3559,23 +3907,23 @@ function useComposerAutocomplete(options) {
3559
3907
  const activeItem = isOpen ? items[activeIndex] ?? null : null;
3560
3908
  const reactId = useId();
3561
3909
  const listboxId = options.id ?? `agent-autocomplete-${reactId}`;
3562
- const optionId = useCallback7((index) => `${listboxId}-option-${index}`, [
3910
+ const optionId = useCallback8((index) => `${listboxId}-option-${index}`, [
3563
3911
  listboxId
3564
3912
  ]);
3565
- const highlight = useCallback7((index) => {
3913
+ const highlight = useCallback8((index) => {
3566
3914
  const total = itemsRef.current.length;
3567
3915
  if (total === 0) return;
3568
3916
  setActiveIndex((index % total + total) % total);
3569
3917
  }, []);
3570
- const moveHighlight = useCallback7((delta) => highlight(activeIndexRef.current + delta), [
3918
+ const moveHighlight = useCallback8((delta) => highlight(activeIndexRef.current + delta), [
3571
3919
  highlight
3572
3920
  ]);
3573
- const dismiss = useCallback7(() => {
3921
+ const dismiss = useCallback8(() => {
3574
3922
  const current = matchRef.current;
3575
3923
  if (current) setDismissedIndex(current.index);
3576
3924
  }, []);
3577
- const pendingCaret = useRef8(null);
3578
- const accept = useCallback7((item) => {
3925
+ const pendingCaret = useRef9(null);
3926
+ const accept = useCallback8((item) => {
3579
3927
  const current = matchRef.current;
3580
3928
  if (!current) return;
3581
3929
  const chosen = item ?? itemsRef.current[activeIndexRef.current];
@@ -3592,10 +3940,10 @@ function useComposerAutocomplete(options) {
3592
3940
  pendingCaret.current = null;
3593
3941
  inputRef.current?.setSelectionRange(position, position);
3594
3942
  });
3595
- const readCaret = useCallback7((element) => {
3943
+ const readCaret = useCallback8((element) => {
3596
3944
  setCaret(element.selectionStart ?? element.value.length);
3597
3945
  }, []);
3598
- const onKeyDown = useCallback7((event) => {
3946
+ const onKeyDown = useCallback8((event) => {
3599
3947
  if (!isOpen) return;
3600
3948
  const hasItems = itemsRef.current.length > 0;
3601
3949
  switch (event.key) {
@@ -3633,23 +3981,23 @@ function useComposerAutocomplete(options) {
3633
3981
  dismiss,
3634
3982
  moveHighlight
3635
3983
  ]);
3636
- const onChange = useCallback7((event) => {
3984
+ const onChange = useCallback8((event) => {
3637
3985
  readCaret(event.target);
3638
3986
  const next = findActiveTrigger(event.target.value, event.target.selectionStart ?? event.target.value.length, latest.current.sources);
3639
3987
  setDismissedIndex((current) => current !== null && next?.index === current ? current : null);
3640
3988
  }, [
3641
3989
  readCaret
3642
3990
  ]);
3643
- const onSelect = useCallback7((event) => readCaret(event.currentTarget), [
3991
+ const onSelect = useCallback8((event) => readCaret(event.currentTarget), [
3644
3992
  readCaret
3645
3993
  ]);
3646
- const onClick = useCallback7((event) => readCaret(event.currentTarget), [
3994
+ const onClick = useCallback8((event) => readCaret(event.currentTarget), [
3647
3995
  readCaret
3648
3996
  ]);
3649
- const setInput = useCallback7((element) => {
3997
+ const setInput = useCallback8((element) => {
3650
3998
  inputRef.current = element;
3651
3999
  }, []);
3652
- const getInputProps = useCallback7(() => ({
4000
+ const getInputProps = useCallback8(() => ({
3653
4001
  ref: setInput,
3654
4002
  onChange,
3655
4003
  onKeyDown,
@@ -3677,7 +4025,7 @@ function useComposerAutocomplete(options) {
3677
4025
  activeIndex,
3678
4026
  optionId
3679
4027
  ]);
3680
- const getListboxProps = useCallback7(() => ({
4028
+ const getListboxProps = useCallback8(() => ({
3681
4029
  id: listboxId,
3682
4030
  role: "listbox",
3683
4031
  "aria-label": match?.source.label ?? match?.source.id ?? "Suggestions"
@@ -3685,7 +4033,7 @@ function useComposerAutocomplete(options) {
3685
4033
  listboxId,
3686
4034
  match
3687
4035
  ]);
3688
- const getOptionProps = useCallback7((index) => ({
4036
+ const getOptionProps = useCallback8((index) => ({
3689
4037
  id: optionId(index),
3690
4038
  role: "option",
3691
4039
  "aria-selected": index === activeIndex
@@ -3719,7 +4067,7 @@ function isPromise(value) {
3719
4067
  __name(isPromise, "isPromise");
3720
4068
 
3721
4069
  // src/presentation/use-tool-catalog.ts
3722
- import { useCallback as useCallback8, useEffect as useEffect10, useMemo as useMemo6, useState as useState10 } from "react";
4070
+ import { useCallback as useCallback9, useEffect as useEffect11, useMemo as useMemo7, useState as useState11 } from "react";
3723
4071
  var shared = /* @__PURE__ */ new WeakMap();
3724
4072
  function load(client, agent) {
3725
4073
  let byAgent = shared.get(client);
@@ -3743,11 +4091,11 @@ function load(client, agent) {
3743
4091
  __name(load, "load");
3744
4092
  function useToolCatalog(options) {
3745
4093
  const { client, agent, enabled = true } = options;
3746
- const [entries, setEntries] = useState10([]);
3747
- const [isLoading, setLoading] = useState10(enabled);
3748
- const [error, setError] = useState10(null);
3749
- const [generation, setGeneration] = useState10(0);
3750
- useEffect10(() => {
4094
+ const [entries, setEntries] = useState11([]);
4095
+ const [isLoading, setLoading] = useState11(enabled);
4096
+ const [error, setError] = useState11(null);
4097
+ const [generation, setGeneration] = useState11(0);
4098
+ useEffect11(() => {
3751
4099
  if (!enabled) {
3752
4100
  setLoading(false);
3753
4101
  return;
@@ -3773,14 +4121,14 @@ function useToolCatalog(options) {
3773
4121
  enabled,
3774
4122
  generation
3775
4123
  ]);
3776
- const refresh = useCallback8(() => {
4124
+ const refresh = useCallback9(() => {
3777
4125
  shared.get(client)?.delete(agent ?? "");
3778
4126
  setGeneration((current) => current + 1);
3779
4127
  }, [
3780
4128
  client,
3781
4129
  agent
3782
4130
  ]);
3783
- const catalog = useMemo6(() => toolCatalogFrom(entries), [
4131
+ const catalog = useMemo7(() => toolCatalogFrom(entries), [
3784
4132
  entries
3785
4133
  ]);
3786
4134
  return {
@@ -3794,7 +4142,7 @@ function useToolCatalog(options) {
3794
4142
  __name(useToolCatalog, "useToolCatalog");
3795
4143
 
3796
4144
  // src/approvals/countdown.ts
3797
- import { useEffect as useEffect11, useRef as useRef9, useState as useState11 } from "react";
4145
+ import { useEffect as useEffect12, useRef as useRef10, useState as useState12 } from "react";
3798
4146
  function approvalCountdown(expiresAt, now = Date.now()) {
3799
4147
  const deadline = expiresAt == null ? Number.NaN : Date.parse(expiresAt);
3800
4148
  if (!Number.isFinite(deadline)) {
@@ -3812,10 +4160,10 @@ function approvalCountdown(expiresAt, now = Date.now()) {
3812
4160
  __name(approvalCountdown, "approvalCountdown");
3813
4161
  function useApprovalCountdown(expiresAt, options = {}) {
3814
4162
  const { intervalMs = 1e3 } = options;
3815
- const nowRef = useRef9(options.now ?? Date.now);
4163
+ const nowRef = useRef10(options.now ?? Date.now);
3816
4164
  nowRef.current = options.now ?? Date.now;
3817
- const [state, setState] = useState11(() => approvalCountdown(expiresAt, nowRef.current()));
3818
- useEffect11(() => {
4165
+ const [state, setState] = useState12(() => approvalCountdown(expiresAt, nowRef.current()));
4166
+ useEffect12(() => {
3819
4167
  const read = /* @__PURE__ */ __name(() => approvalCountdown(expiresAt, nowRef.current()), "read");
3820
4168
  const first = read();
3821
4169
  setState(first);
@@ -3878,7 +4226,13 @@ function storedMessageToUiMessage(message) {
3878
4226
  type: "file",
3879
4227
  mediaType: attachment.contentType,
3880
4228
  filename: attachment.name,
3881
- url: attachment.url
4229
+ url: attachment.url,
4230
+ // The stored id rides along, so a replayed file can be referenced again (`messageFiles`).
4231
+ providerMetadata: {
4232
+ agent: {
4233
+ mediaId: attachment.mediaId
4234
+ }
4235
+ }
3882
4236
  });
3883
4237
  }
3884
4238
  const toolCallIds = new Set((message.toolCalls ?? []).map((call) => call.id));
@@ -4057,20 +4411,20 @@ __name(sumUsage, "sumUsage");
4057
4411
 
4058
4412
  // src/use-agent-chat.ts
4059
4413
  import { useChat } from "@ai-sdk/react";
4060
- import { useCallback as useCallback9, useEffect as useEffect12, useMemo as useMemo7, useRef as useRef10, useState as useState12 } from "react";
4414
+ import { useCallback as useCallback10, useEffect as useEffect13, useMemo as useMemo8, useRef as useRef11, useState as useState13 } from "react";
4061
4415
  function useAgentChat(options) {
4062
- const latest = useRef10(options);
4416
+ const latest = useRef11(options);
4063
4417
  latest.current = options;
4064
- const [runId, setRunId] = useState12(options.resumeRunId);
4065
- const runIdRef = useRef10(runId);
4418
+ const [runId, setRunId] = useState13(options.resumeRunId);
4419
+ const runIdRef = useRef11(runId);
4066
4420
  runIdRef.current = runId;
4067
- const settlingRunIdRef = useRef10(void 0);
4068
- const autoResumeRunIdRef = useRef10(void 0);
4069
- const [autoResume, setAutoResume] = useState12(false);
4070
- const [activeRunId, setActiveRunId] = useState12(null);
4071
- const regenerateNext = useRef10(false);
4072
- const createdThreadId = useRef10(void 0);
4073
- const client = useMemo7(() => {
4421
+ const settlingRunIdRef = useRef11(void 0);
4422
+ const autoResumeRunIdRef = useRef11(void 0);
4423
+ const [autoResume, setAutoResume] = useState13(false);
4424
+ const [activeRunId, setActiveRunId] = useState13(null);
4425
+ const regenerateNext = useRef11(false);
4426
+ const createdThreadId = useRef11(void 0);
4427
+ const client = useMemo8(() => {
4074
4428
  if (options.backend) return options.backend;
4075
4429
  if (options.client) return options.client;
4076
4430
  return new AgentClient({
@@ -4086,11 +4440,11 @@ function useAgentChat(options) {
4086
4440
  getHeaders: /* @__PURE__ */ __name(async () => mergeHeaders(latest.current), "getHeaders")
4087
4441
  });
4088
4442
  }, []);
4089
- const [connection, setConnection] = useState12({
4443
+ const [connection, setConnection] = useState13({
4090
4444
  status: "live"
4091
4445
  });
4092
- const resyncAfterSettle = useRef10(false);
4093
- useEffect12(() => {
4446
+ const resyncAfterSettle = useRef11(false);
4447
+ useEffect13(() => {
4094
4448
  if (!options.resume || options.threadId === void 0) return;
4095
4449
  let cancelled = false;
4096
4450
  const threadId = options.threadId;
@@ -4110,7 +4464,7 @@ function useAgentChat(options) {
4110
4464
  options.resume,
4111
4465
  options.threadId
4112
4466
  ]);
4113
- const transport = useMemo7(() => {
4467
+ const transport = useMemo8(() => {
4114
4468
  function onMeta(meta) {
4115
4469
  settlingRunIdRef.current = meta.runId;
4116
4470
  setRunId(meta.runId);
@@ -4165,7 +4519,7 @@ function useAgentChat(options) {
4165
4519
  }, "onAttemptStart")
4166
4520
  });
4167
4521
  }, []);
4168
- const backgroundRef = useRef10(() => Promise.resolve());
4522
+ const backgroundRef = useRef11(() => Promise.resolve());
4169
4523
  const chat = useChat({
4170
4524
  transport,
4171
4525
  resume: options.resumeRunId !== void 0 || autoResume,
@@ -4214,7 +4568,7 @@ function useAgentChat(options) {
4214
4568
  }
4215
4569
  }, "onFinish")
4216
4570
  });
4217
- const chatRef = useRef10(chat);
4571
+ const chatRef = useRef11(chat);
4218
4572
  chatRef.current = chat;
4219
4573
  async function resyncFromThread() {
4220
4574
  const threadId = latest.current.threadId ?? createdThreadId.current;
@@ -4226,11 +4580,11 @@ function useAgentChat(options) {
4226
4580
  }
4227
4581
  }
4228
4582
  __name(resyncFromThread, "resyncFromThread");
4229
- const turnInFlight = useRef10(false);
4230
- const isTurnInFlight = useCallback9(() => turnInFlight.current || transport.isAttemptLive, [
4583
+ const turnInFlight = useRef11(false);
4584
+ const isTurnInFlight = useCallback10(() => turnInFlight.current || transport.isAttemptLive, [
4231
4585
  transport
4232
4586
  ]);
4233
- const sendMessage = useCallback9(async (...args) => {
4587
+ const sendMessage = useCallback10(async (...args) => {
4234
4588
  const blocked = latest.current.blocked;
4235
4589
  if (blocked != null) throw new QuotaBlockedError(blocked);
4236
4590
  if (isTurnInFlight()) return;
@@ -4243,15 +4597,15 @@ function useAgentChat(options) {
4243
4597
  }, [
4244
4598
  isTurnInFlight
4245
4599
  ]);
4246
- const addToolResult = useCallback9(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
4600
+ const addToolResult = useCallback10(({ tool, toolCallId, output }) => chatRef.current.addToolResult({
4247
4601
  tool,
4248
4602
  toolCallId,
4249
4603
  output
4250
4604
  }), []);
4251
- const [threads, setThreads] = useState12([]);
4252
- const [quota, setQuota] = useState12(null);
4253
- const [backgroundRuns, setBackgroundRuns] = useState12([]);
4254
- const refreshBackground = useCallback9(async () => {
4605
+ const [threads, setThreads] = useState13([]);
4606
+ const [quota, setQuota] = useState13(null);
4607
+ const [backgroundRuns, setBackgroundRuns] = useState13([]);
4608
+ const refreshBackground = useCallback10(async () => {
4255
4609
  const threadId = latest.current.threadId ?? createdThreadId.current;
4256
4610
  if (threadId === void 0) {
4257
4611
  return;
@@ -4276,7 +4630,7 @@ function useAgentChat(options) {
4276
4630
  ]);
4277
4631
  backgroundRef.current = refreshBackground;
4278
4632
  const isWorking = backgroundRuns.some((run) => run.status === "running");
4279
- useEffect12(() => {
4633
+ useEffect13(() => {
4280
4634
  if (options.background !== true || options.threadId === void 0) {
4281
4635
  return;
4282
4636
  }
@@ -4286,7 +4640,7 @@ function useAgentChat(options) {
4286
4640
  options.threadId,
4287
4641
  refreshBackground
4288
4642
  ]);
4289
- useEffect12(() => {
4643
+ useEffect13(() => {
4290
4644
  const every = latest.current.backgroundPollMs ?? 5e3;
4291
4645
  if (latest.current.background !== true || !isWorking || every <= 0) {
4292
4646
  return;
@@ -4297,26 +4651,26 @@ function useAgentChat(options) {
4297
4651
  isWorking,
4298
4652
  refreshBackground
4299
4653
  ]);
4300
- const loadThreads = useCallback9(async () => {
4654
+ const loadThreads = useCallback10(async () => {
4301
4655
  const list = await client.listThreads();
4302
4656
  setThreads(list);
4303
4657
  return list;
4304
4658
  }, [
4305
4659
  client
4306
4660
  ]);
4307
- const loadThread = useCallback9((id) => client.getThread(id), [
4661
+ const loadThread = useCallback10((id) => client.getThread(id), [
4308
4662
  client
4309
4663
  ]);
4310
- const deleteThread = useCallback9(async (id) => {
4664
+ const deleteThread = useCallback10(async (id) => {
4311
4665
  await client.deleteThread(id);
4312
4666
  setThreads((current) => current.filter((thread) => thread.id !== id));
4313
4667
  }, [
4314
4668
  client
4315
4669
  ]);
4316
- const forkThread = useCallback9((threadId, messageId) => requireBackendMethod(client, "forkFromMessage")(threadId, messageId), [
4670
+ const forkThread = useCallback10((threadId, messageId) => requireBackendMethod(client, "forkFromMessage")(threadId, messageId), [
4317
4671
  client
4318
4672
  ]);
4319
- const renameThread = useCallback9(async (id, title) => {
4673
+ const renameThread = useCallback10(async (id, title) => {
4320
4674
  await client.updateThread(id, {
4321
4675
  title
4322
4676
  });
@@ -4327,24 +4681,24 @@ function useAgentChat(options) {
4327
4681
  }, [
4328
4682
  client
4329
4683
  ]);
4330
- const promoteThread = useCallback9(async (id) => {
4684
+ const promoteThread = useCallback10(async (id) => {
4331
4685
  await requireBackendMethod(client, "promoteThread")(id);
4332
4686
  await loadThreads();
4333
4687
  }, [
4334
4688
  client,
4335
4689
  loadThreads
4336
4690
  ]);
4337
- const truncateFromMessage = useCallback9((threadId, messageId) => requireBackendMethod(client, "truncateFromMessage")(threadId, messageId).then(() => void 0), [
4691
+ const truncateFromMessage = useCallback10((threadId, messageId) => requireBackendMethod(client, "truncateFromMessage")(threadId, messageId).then(() => void 0), [
4338
4692
  client
4339
4693
  ]);
4340
- const loadQuota = useCallback9(async () => {
4694
+ const loadQuota = useCallback10(async () => {
4341
4695
  const today = await requireBackendMethod(client, "getQuotaToday")();
4342
4696
  setQuota(today);
4343
4697
  return today;
4344
4698
  }, [
4345
4699
  client
4346
4700
  ]);
4347
- const cancel = useCallback9(async () => {
4701
+ const cancel = useCallback10(async () => {
4348
4702
  chatRef.current.stop();
4349
4703
  const activeRunId2 = runIdRef.current;
4350
4704
  if (activeRunId2) {
@@ -4356,7 +4710,7 @@ function useAgentChat(options) {
4356
4710
  }, [
4357
4711
  client
4358
4712
  ]);
4359
- const approve = useCallback9(async ({ toolCallId, remember, via }) => {
4713
+ const approve = useCallback10(async ({ toolCallId, remember, via }) => {
4360
4714
  await requireBackendMethod(client, "approveToolCall")({
4361
4715
  toolCallId,
4362
4716
  ...remember === true ? {
@@ -4369,7 +4723,7 @@ function useAgentChat(options) {
4369
4723
  }, [
4370
4724
  client
4371
4725
  ]);
4372
- const reject = useCallback9(async ({ toolCallId, reason, via }) => {
4726
+ const reject = useCallback10(async ({ toolCallId, reason, via }) => {
4373
4727
  await requireBackendMethod(client, "rejectToolCall")({
4374
4728
  toolCallId,
4375
4729
  ...reason !== void 0 ? {
@@ -4382,7 +4736,7 @@ function useAgentChat(options) {
4382
4736
  }, [
4383
4737
  client
4384
4738
  ]);
4385
- const answer = useCallback9(async ({ toolCallId, answers }) => {
4739
+ const answer = useCallback10(async ({ toolCallId, answers }) => {
4386
4740
  await requireBackendMethod(client, "answerToolCall")({
4387
4741
  toolCallId,
4388
4742
  ...answers !== void 0 ? {
@@ -4392,14 +4746,14 @@ function useAgentChat(options) {
4392
4746
  }, [
4393
4747
  client
4394
4748
  ]);
4395
- const skip = useCallback9(async ({ toolCallId }) => {
4749
+ const skip = useCallback10(async ({ toolCallId }) => {
4396
4750
  await requireBackendMethod(client, "skipToolCall")({
4397
4751
  toolCallId
4398
4752
  });
4399
4753
  }, [
4400
4754
  client
4401
4755
  ]);
4402
- const regenerate = useCallback9(() => {
4756
+ const regenerate = useCallback10(() => {
4403
4757
  const blocked = latest.current.blocked;
4404
4758
  if (blocked != null) throw new QuotaBlockedError(blocked);
4405
4759
  if (isTurnInFlight()) return;
@@ -4416,7 +4770,7 @@ function useAgentChat(options) {
4416
4770
  isWorking,
4417
4771
  refresh: refreshBackground
4418
4772
  };
4419
- const setThreadModel = useCallback9(async (model) => {
4773
+ const setThreadModel = useCallback10(async (model) => {
4420
4774
  const threadId = latest.current.threadId ?? createdThreadId.current;
4421
4775
  if (threadId === void 0) {
4422
4776
  throw new Error("setThreadModel: this chat has no thread yet");
@@ -4430,7 +4784,7 @@ function useAgentChat(options) {
4430
4784
  }, [
4431
4785
  client
4432
4786
  ]);
4433
- const getThreadId = useCallback9(() => latest.current.threadId ?? createdThreadId.current, []);
4787
+ const getThreadId = useCallback10(() => latest.current.threadId ?? createdThreadId.current, []);
4434
4788
  const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
4435
4789
  return {
4436
4790
  ...chat,
@@ -4496,6 +4850,7 @@ export {
4496
4850
  AgentChatTransport,
4497
4851
  AgentClient,
4498
4852
  AgentHttpError,
4853
+ AmbientRenderUiContext,
4499
4854
  ChatInput,
4500
4855
  DEFAULT_SPEECH_LANG,
4501
4856
  MessageItem,
@@ -4503,6 +4858,7 @@ export {
4503
4858
  MessageList,
4504
4859
  QuotaBlockedError,
4505
4860
  SPEECH_LANGUAGES,
4861
+ acceptsFile,
4506
4862
  applyCompletion,
4507
4863
  approvalCountdown,
4508
4864
  backgroundRunsFromThread,
@@ -4513,7 +4869,10 @@ export {
4513
4869
  describeTimestamp,
4514
4870
  describeToolCall,
4515
4871
  describeUsage,
4872
+ dragHasFiles,
4516
4873
  extractMessageText,
4874
+ fileKind,
4875
+ filesFromClipboard,
4517
4876
  fillTemplate,
4518
4877
  filterAutocompleteItems,
4519
4878
  findActiveTrigger,
@@ -4523,6 +4882,7 @@ export {
4523
4882
  inferResultView,
4524
4883
  isActionCall,
4525
4884
  loadStoredSpeechLang,
4885
+ messageFiles,
4526
4886
  notifyThreads,
4527
4887
  onThreadsEvent,
4528
4888
  persistSpeechLang,
@@ -4537,7 +4897,9 @@ export {
4537
4897
  toolCatalogFrom,
4538
4898
  useAgentChat,
4539
4899
  useAgents,
4900
+ useAmbientRenderUi,
4540
4901
  useApprovalCountdown,
4902
+ useAttachments,
4541
4903
  useChatTranscript,
4542
4904
  useComposerAutocomplete,
4543
4905
  useElapsed,