@dudousxd/nestjs-agent-react 0.32.0 → 0.33.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
@@ -9,9 +9,9 @@ function readErrorAnswer(text) {
9
9
  }
10
10
  if (body === null || typeof body !== "object")
11
11
  return { body, message: void 0, code: void 0 };
12
- const record = body;
13
- const message = typeof record.message === "string" && record.message !== "" ? record.message : Array.isArray(record.message) && record.message.length > 0 && record.message.every((each) => typeof each === "string") ? record.message.join("; ") : void 0;
14
- const code = typeof record.code === "string" && record.code !== "" ? record.code : void 0;
12
+ const record2 = body;
13
+ const message = typeof record2.message === "string" && record2.message !== "" ? record2.message : Array.isArray(record2.message) && record2.message.length > 0 && record2.message.every((each) => typeof each === "string") ? record2.message.join("; ") : void 0;
14
+ const code = typeof record2.code === "string" && record2.code !== "" ? record2.code : void 0;
15
15
  return { body, message, code };
16
16
  }
17
17
  async function readErrorResponse(response) {
@@ -88,6 +88,12 @@ var AgentClient = class {
88
88
  if (response.status === 202) {
89
89
  return { body: emptyStream(), queued: await response.json() };
90
90
  }
91
+ if (response.ok && response.headers.get("content-type")?.includes("application/json")) {
92
+ const decision = await response.json();
93
+ if (decision.proposalDecision !== void 0 && typeof decision.threadId === "string")
94
+ return { body: emptyStream(), threadId: decision.threadId, proposalDecision: decision };
95
+ throw new Error("Unexpected JSON chat response");
96
+ }
91
97
  if (!response.ok || !response.body) {
92
98
  throw await this.failure(response, "POST", `${this.agentPath()}/chat`);
93
99
  }
@@ -320,6 +326,54 @@ var AgentClient = class {
320
326
  * `remember` approves later calls of the same tool in the same thread; `via` names the surface
321
327
  * the decision came through (the server records `'web'` when omitted).
322
328
  */
329
+ async listActionProposals({ threadId }) {
330
+ const proposals = /* @__PURE__ */ new Map();
331
+ let after;
332
+ for (; ; ) {
333
+ const route = `/threads/${encodeURIComponent(threadId)}/action-proposals${after === void 0 ? "" : `?after=${encodeURIComponent(JSON.stringify(after))}`}`;
334
+ const response = await this.requestResponse("GET", route);
335
+ const page = await this.handleResponse(
336
+ response,
337
+ "GET",
338
+ `${this.agentPath()}${route}`
339
+ );
340
+ if (!Array.isArray(page)) throw new TypeError("Invalid action proposal page");
341
+ for (const proposal of page) proposals.set(proposal.id, proposal);
342
+ const header = response.headers.get("X-Action-Proposals-Next");
343
+ if (header === null) return [...proposals.values()];
344
+ let next;
345
+ try {
346
+ next = JSON.parse(decodeURIComponent(header));
347
+ } catch {
348
+ throw new TypeError("Invalid action proposal cursor");
349
+ }
350
+ if (typeof next !== "object" || next === null || !("createdAt" in next) || typeof next.createdAt !== "number" || !Number.isSafeInteger(next.createdAt) || !("id" in next) || typeof next.id !== "string" || next.id.length === 0 || next.id.length > 255 || after !== void 0 && (next.createdAt < after.createdAt || next.createdAt === after.createdAt && next.id <= after.id))
351
+ throw new TypeError("Invalid or non-advancing action proposal cursor");
352
+ after = { createdAt: next.createdAt, id: next.id };
353
+ }
354
+ }
355
+ approveActionProposal({
356
+ threadId,
357
+ proposalId,
358
+ remember
359
+ }) {
360
+ return this.request(
361
+ "POST",
362
+ `/threads/${encodeURIComponent(threadId)}/action-proposals/${encodeURIComponent(proposalId)}/approve`,
363
+ remember === void 0 ? {} : { remember }
364
+ );
365
+ }
366
+ rejectActionProposal({
367
+ threadId,
368
+ proposalId,
369
+ reason
370
+ }) {
371
+ return this.request(
372
+ "POST",
373
+ `/threads/${encodeURIComponent(threadId)}/action-proposals/${encodeURIComponent(proposalId)}/reject`,
374
+ reason === void 0 ? {} : { reason }
375
+ );
376
+ }
323
377
  approveToolCall(input) {
324
378
  return this.request("POST", "/tool-call/approve", input);
325
379
  }
@@ -376,6 +430,10 @@ var AgentClient = class {
376
430
  return this.options.credentials !== void 0 ? { credentials: this.options.credentials } : {};
377
431
  }
378
432
  async request(method, route, body) {
433
+ const response = await this.requestResponse(method, route, body);
434
+ return this.handleResponse(response, method, `${this.agentPath()}${route}`);
435
+ }
436
+ async requestResponse(method, route, body) {
379
437
  const path = `${this.agentPath()}${route}`;
380
438
  const response = await this.fetchImpl()(`${this.baseUrl()}${path}`, {
381
439
  method,
@@ -387,7 +445,7 @@ var AgentClient = class {
387
445
  ...body !== void 0 ? { body: JSON.stringify(body) } : {},
388
446
  ...this.credentials()
389
447
  });
390
- return this.handleResponse(response, method, path);
448
+ return response;
391
449
  }
392
450
  /** The error for a refused `response`, already reported to `onHttpError`. */
393
451
  async failure(response, method, path) {
@@ -543,6 +601,10 @@ var AgentChatTransport = class {
543
601
  return queuedChunkStream(response.queued);
544
602
  }
545
603
  this.captureHeaderMeta(response);
604
+ if (response.proposalDecision !== void 0) {
605
+ this.attemptLive = false;
606
+ return proposalDecisionChunkStream(response.proposalDecision);
607
+ }
546
608
  return this.toChunkStream(response, {
547
609
  ...headers !== void 0 ? { headers } : {},
548
610
  ...options.abortSignal ? { signal: options.abortSignal } : {}
@@ -669,7 +731,7 @@ var AgentChatTransport = class {
669
731
  let reasoningSeq = 0;
670
732
  const announced = /* @__PURE__ */ new Set();
671
733
  const toolMetadata = /* @__PURE__ */ new Map();
672
- const record = (meta) => {
734
+ const record2 = (meta) => {
673
735
  streamRunId = meta.runId;
674
736
  this.recordMeta(meta);
675
737
  };
@@ -957,7 +1019,7 @@ var AgentChatTransport = class {
957
1019
  }
958
1020
  if (frame.event === "meta") {
959
1021
  const meta = parseMeta(frame.data);
960
- if (meta) record(meta);
1022
+ if (meta) record2(meta);
961
1023
  } else if (frame.data) {
962
1024
  const event = parseEvent(frame.data);
963
1025
  if (event) emit(event);
@@ -1025,10 +1087,10 @@ function parseErrorFrame(data) {
1025
1087
  try {
1026
1088
  const parsed = JSON.parse(data);
1027
1089
  if (parsed !== null && typeof parsed === "object") {
1028
- const record = parsed;
1090
+ const record2 = parsed;
1029
1091
  return {
1030
- code: typeof record.code === "string" && record.code !== "" ? record.code : void 0,
1031
- message: typeof record.message === "string" && record.message !== "" ? record.message : fallback2.message
1092
+ code: typeof record2.code === "string" && record2.code !== "" ? record2.code : void 0,
1093
+ message: typeof record2.message === "string" && record2.message !== "" ? record2.message : fallback2.message
1032
1094
  };
1033
1095
  }
1034
1096
  } catch {
@@ -1068,6 +1130,21 @@ function delay(ms, signal) {
1068
1130
  signal?.addEventListener("abort", done, { once: true });
1069
1131
  });
1070
1132
  }
1133
+ function proposalDecisionChunkStream(decision) {
1134
+ return new ReadableStream({
1135
+ start(controller) {
1136
+ controller.enqueue({ type: "start", messageId: `decision:${crypto.randomUUID()}` });
1137
+ controller.enqueue({ type: "data-proposal-decision", data: decision, transient: true });
1138
+ if (decision.text !== void 0 && decision.text.length > 0) {
1139
+ controller.enqueue({ type: "text-start", id: "decision" });
1140
+ controller.enqueue({ type: "text-delta", id: "decision", delta: decision.text });
1141
+ controller.enqueue({ type: "text-end", id: "decision" });
1142
+ }
1143
+ controller.enqueue({ type: "finish", finishReason: "stop" });
1144
+ controller.close();
1145
+ }
1146
+ });
1147
+ }
1071
1148
 
1072
1149
  // src/attachments/files.ts
1073
1150
  function fileKind(mediaType) {
@@ -1206,6 +1283,10 @@ function toGenerativeUIItem(part) {
1206
1283
  return {
1207
1284
  id,
1208
1285
  component: source.component,
1286
+ ...isRecord(source.componentVersions) && Object.values(source.componentVersions).every(
1287
+ (version) => typeof version === "number" && Number.isSafeInteger(version) && version > 0
1288
+ ) ? { componentVersions: { ...source.componentVersions } } : {},
1289
+ ...typeof source.fallbackText === "string" ? { fallbackText: source.fallbackText } : {},
1209
1290
  props: isRecord(source.props) ? source.props : {},
1210
1291
  version: typeof source.version === "number" ? source.version : null,
1211
1292
  toolCallId: typeof source.toolCallId === "string" ? source.toolCallId : null
@@ -1311,20 +1392,29 @@ function useValidatedProps(catalog, name, props) {
1311
1392
  function useGenerativeUIState(part, options, fallbackTree) {
1312
1393
  const item = useMemo(() => toGenerativeUIItem(part), [part]);
1313
1394
  const name = item?.component ?? "";
1395
+ const definition = options.catalog?.get?.(name);
1396
+ const incompatible = item?.fallbackText !== void 0 && item.version !== null && definition !== void 0 && item.version !== (definition.version ?? 1);
1314
1397
  const resolution = useResolvedComponent(
1315
1398
  name,
1316
1399
  item?.version ?? null,
1317
- options.registry,
1400
+ incompatible ? {} : options.registry,
1318
1401
  item === null ? void 0 : options.resolveComponent,
1319
1402
  fallbackTree
1320
1403
  );
1321
1404
  const empty = useMemo(() => ({}), []);
1322
1405
  const validation = useValidatedProps(
1323
- item === null || name === GENUI_TREE_COMPONENT ? void 0 : options.catalog,
1406
+ item === null || name === GENUI_TREE_COMPONENT || incompatible ? void 0 : options.catalog,
1324
1407
  name,
1325
1408
  item?.props ?? empty
1326
1409
  );
1327
1410
  if (item === null) return null;
1411
+ if (name === GENUI_TREE_COMPONENT && item.fallbackText !== void 0 && (!isRecord(item.props.root) || typeof item.props.root.type !== "string"))
1412
+ return {
1413
+ status: "problem",
1414
+ reason: "invalid",
1415
+ item,
1416
+ issues: [{ path: ["root"], message: "Invalid UI tree root" }]
1417
+ };
1328
1418
  if (resolution.status === "loading") return { status: "loading", item };
1329
1419
  if (resolution.status === "unknown") return { status: "problem", reason: "unknown", item };
1330
1420
  if (resolution.status === "error") {
@@ -1342,7 +1432,8 @@ import { jsx } from "react/jsx-runtime";
1342
1432
  var TreeScopeContext = createContext2(null);
1343
1433
  function renderFallback(fallback2, problem) {
1344
1434
  if (typeof fallback2 === "function") return fallback2(problem);
1345
- return fallback2 ?? null;
1435
+ if (fallback2 !== void 0) return fallback2;
1436
+ return problem.item.fallbackText !== void 0 ? /* @__PURE__ */ jsx("span", { style: { whiteSpace: "pre-wrap" }, children: problem.item.fallbackText }) : null;
1346
1437
  }
1347
1438
  var ItemBoundary = class extends Component {
1348
1439
  state = { error: void 0, failed: false };
@@ -1374,28 +1465,42 @@ function nodeItem(node, id) {
1374
1465
  function TreeNode({ node, id }) {
1375
1466
  const scope = useContext2(TreeScopeContext);
1376
1467
  const item = useMemo2(() => nodeItem(node, id), [node, id]);
1468
+ const storedVersion = scope?.componentVersions?.[node.type];
1469
+ const definition = scope?.options.catalog?.get?.(node.type);
1470
+ const incompatible = storedVersion !== void 0 && definition !== void 0 && storedVersion !== (definition.version ?? 1);
1377
1471
  const resolution = useResolvedComponent(
1378
1472
  node.type,
1379
- null,
1380
- scope?.options.registry ?? {},
1473
+ storedVersion ?? null,
1474
+ incompatible ? {} : scope?.options.registry ?? {},
1381
1475
  scope?.options.resolveComponent
1382
1476
  );
1383
- const validation = useValidatedProps(scope?.options.catalog, node.type, item.props);
1477
+ const validation = useValidatedProps(
1478
+ incompatible ? void 0 : scope?.options.catalog,
1479
+ node.type,
1480
+ item.props
1481
+ );
1384
1482
  if (scope === null) return null;
1385
1483
  if (resolution.status === "loading" || validation.status === "pending") return scope.loading;
1386
1484
  if (resolution.status === "unknown") {
1485
+ if (scope.fallback === void 0 && scope.fallbackText !== void 0)
1486
+ throw new Error("Unknown tree component");
1387
1487
  return renderFallback(scope.fallback, { reason: "unknown", item });
1388
1488
  }
1389
1489
  if (resolution.status === "error") {
1490
+ if (scope.fallback === void 0 && scope.fallbackText !== void 0) throw resolution.error;
1390
1491
  return renderFallback(scope.fallback, { reason: "error", item, error: resolution.error });
1391
1492
  }
1392
1493
  if (validation.status === "invalid") {
1494
+ if (scope.fallback === void 0 && scope.fallbackText !== void 0)
1495
+ throw new Error("Invalid tree component");
1393
1496
  return renderFallback(scope.fallback, { reason: "invalid", item, issues: validation.issues });
1394
1497
  }
1395
1498
  const Renderer = resolution.Component;
1396
1499
  const children = Array.isArray(node.children) ? node.children : [];
1397
1500
  return /* @__PURE__ */ jsx(Renderer, { ...validation.props, children: children.length > 0 ? children.map((child, index) => {
1398
1501
  const childId = `${id}.${index}`;
1502
+ if (scope.fallback === void 0 && scope.fallbackText !== void 0)
1503
+ return /* @__PURE__ */ jsx(TreeNode, { node: child, id: childId }, childId);
1399
1504
  return /* @__PURE__ */ jsx(
1400
1505
  ItemBoundary,
1401
1506
  {
@@ -1417,6 +1522,8 @@ function GenerativeUIScope({
1417
1522
  catalog,
1418
1523
  resolveComponent,
1419
1524
  fallback: fallback2,
1525
+ fallbackText,
1526
+ componentVersions,
1420
1527
  loading = null,
1421
1528
  onError,
1422
1529
  children
@@ -1429,10 +1536,21 @@ function GenerativeUIScope({
1429
1536
  ...resolveComponent !== void 0 ? { resolveComponent } : {}
1430
1537
  },
1431
1538
  fallback: fallback2,
1539
+ ...componentVersions !== void 0 ? { componentVersions } : {},
1540
+ ...fallbackText !== void 0 ? { fallbackText } : {},
1432
1541
  loading,
1433
1542
  onError
1434
1543
  }),
1435
- [registry, catalog, resolveComponent, fallback2, loading, onError]
1544
+ [
1545
+ registry,
1546
+ catalog,
1547
+ resolveComponent,
1548
+ fallback2,
1549
+ fallbackText,
1550
+ componentVersions,
1551
+ loading,
1552
+ onError
1553
+ ]
1436
1554
  );
1437
1555
  return /* @__PURE__ */ jsx(TreeScopeContext.Provider, { value: scope, children });
1438
1556
  }
@@ -1492,7 +1610,7 @@ function GenerativeUI({
1492
1610
  onError: ownOnError
1493
1611
  }) {
1494
1612
  const provided = useContext2(GenuiContext);
1495
- const fallback2 = ownFallback ?? provided?.fallback;
1613
+ const fallback2 = ownFallback !== void 0 ? ownFallback : provided?.fallback;
1496
1614
  const loading = ownLoading ?? provided?.loading ?? null;
1497
1615
  const onError = ownOnError ?? provided?.onError;
1498
1616
  const options = useMergedOptions({
@@ -1517,6 +1635,8 @@ function GenerativeUI({
1517
1635
  GenerativeUIScope,
1518
1636
  {
1519
1637
  ...options,
1638
+ ...state.item.componentVersions !== void 0 ? { componentVersions: state.item.componentVersions } : {},
1639
+ ...state.item.fallbackText !== void 0 ? { fallbackText: state.item.fallbackText } : {},
1520
1640
  fallback: fallback2,
1521
1641
  loading,
1522
1642
  ...onError !== void 0 ? { onError } : {},
@@ -1532,6 +1652,7 @@ var AgentBackendContext = sharedContext(
1532
1652
  );
1533
1653
  function AgentProvider({
1534
1654
  backend,
1655
+ uiCapabilities,
1535
1656
  baseUrl,
1536
1657
  path,
1537
1658
  headers,
@@ -1563,7 +1684,7 @@ function AgentProvider({
1563
1684
  [backend, baseUrl, path, credentials, fetch, upload]
1564
1685
  );
1565
1686
  const content = genui !== void 0 ? /* @__PURE__ */ jsx2(GenuiProvider, { ...genui, children }) : children;
1566
- return /* @__PURE__ */ jsx2(AgentBackendContext.Provider, { value, children: content });
1687
+ return /* @__PURE__ */ jsx2(AgentUiCapabilitiesContext.Provider, { value: uiCapabilities ?? null, children: /* @__PURE__ */ jsx2(AgentBackendContext.Provider, { value, children: content }) });
1567
1688
  }
1568
1689
  var fallback;
1569
1690
  function defaultBackend() {
@@ -1574,6 +1695,12 @@ function useAgentBackend(own) {
1574
1695
  const provided = useContext3(AgentBackendContext);
1575
1696
  return own ?? provided ?? defaultBackend();
1576
1697
  }
1698
+ var AgentUiCapabilitiesContext = sharedContext(
1699
+ "@dudousxd/nestjs-agent-react:ui-capabilities"
1700
+ );
1701
+ function useAgentUiCapabilities() {
1702
+ return useContext3(AgentUiCapabilitiesContext) ?? void 0;
1703
+ }
1577
1704
 
1578
1705
  // src/config/use-agent-config.ts
1579
1706
  var shared = /* @__PURE__ */ new WeakMap();
@@ -1839,7 +1966,7 @@ async function defaultRefusal(response) {
1839
1966
  }
1840
1967
  function defaultForwardedProps(body) {
1841
1968
  const out = {};
1842
- for (const key of ["pageContext", "agent", "model"]) {
1969
+ for (const key of ["pageContext", "agent", "model", "persona", "uiCapabilities"]) {
1843
1970
  if (body[key] !== void 0 && body[key] !== null) out[key] = body[key];
1844
1971
  }
1845
1972
  return Object.keys(out).length > 0 ? out : void 0;
@@ -2888,6 +3015,15 @@ function isActionCall(part) {
2888
3015
  }
2889
3016
  function toolCallState(part) {
2890
3017
  const output = "output" in part ? part.output : void 0;
3018
+ if (output !== null && typeof output === "object" && "proposalId" in output && "executed" in output && output.executed === false)
3019
+ return {
3020
+ status: "awaiting-approval",
3021
+ isSettled: false,
3022
+ isFailed: false,
3023
+ isDenied: false,
3024
+ output,
3025
+ error: null
3026
+ };
2891
3027
  const errorText = "errorText" in part ? part.errorText : void 0;
2892
3028
  const outputError = output !== null && typeof output === "object" && "error" in output ? String(output.error) : void 0;
2893
3029
  const isDenied = part.state === "output-denied";
@@ -2932,7 +3068,11 @@ function describeToolCall(part, catalog, options = {}) {
2932
3068
  icon: presentation?.icon ?? null,
2933
3069
  tone: presentation?.tone ?? "neutral",
2934
3070
  detail: presentation?.detail ?? null,
2935
- confirm: confirm === void 0 ? null : {
3071
+ confirm: options.confirmation !== void 0 ? {
3072
+ title: options.confirmation.title,
3073
+ verb: options.confirmation.verb,
3074
+ detail: options.confirmation.detail ?? null
3075
+ } : confirm === void 0 ? null : {
2936
3076
  title: fillTemplate(confirm.title, input),
2937
3077
  verb: confirm.verb,
2938
3078
  detail: confirm.detail !== void 0 ? fillTemplate(confirm.detail, input) : null
@@ -2946,6 +3086,9 @@ function describeToolCall(part, catalog, options = {}) {
2946
3086
  }
2947
3087
  var STATUS_WEIGHT = {
2948
3088
  running: 4,
3089
+ queued: 4,
3090
+ executing: 4,
3091
+ succeeded: 0,
2949
3092
  "awaiting-approval": 3,
2950
3093
  failed: 2,
2951
3094
  denied: 1,
@@ -2965,7 +3108,7 @@ function groupToolActivity(calls, options = {}) {
2965
3108
  if (corrected.has(call.toolCallId)) continue;
2966
3109
  const presentation = options.catalog?.[call.name];
2967
3110
  const key = options.keyOf?.(call, presentation) ?? presentation?.label ?? call.name;
2968
- const status = toolCallState(call.part).status;
3111
+ const status = call.description.status;
2969
3112
  const phrase = phraseFor(
2970
3113
  presentation,
2971
3114
  call.part.input,
@@ -3207,7 +3350,7 @@ function isAncestor(call, candidate, byId) {
3207
3350
  }
3208
3351
  function buildToolCall(part, options, approval, catalog) {
3209
3352
  const toolCallId = part.toolCallId;
3210
- const awaiting = isAwaitingApproval(part);
3353
+ const awaiting = approval?.target?.kind === "proposal" ? approval.status === "pending" : isAwaitingApproval(part);
3211
3354
  const sending = awaiting ? options?.submitting(toolCallId) ?? null : null;
3212
3355
  return {
3213
3356
  part,
@@ -3217,19 +3360,30 @@ function buildToolCall(part, options, approval, catalog) {
3217
3360
  parentId: toolParentId(part),
3218
3361
  children: [],
3219
3362
  approval,
3220
- description: describeToolCall(part, catalog),
3363
+ description: proposalDescription(
3364
+ describeToolCall(
3365
+ part,
3366
+ catalog,
3367
+ approval?.confirmation === void 0 ? {} : { confirmation: approval.confirmation }
3368
+ ),
3369
+ approval
3370
+ ),
3221
3371
  isAwaitingApproval: awaiting,
3222
3372
  approve: {
3223
3373
  available: awaiting && options?.canApprove === true,
3224
3374
  isSubmitting: sending === "approve",
3225
3375
  // Read by field, not passed through: `onClick={call.approve.run}` hands this a click event,
3226
3376
  // and a host's handler must not receive that as its options.
3227
- run: (approveOptions) => approveOptions?.remember === true ? options?.approve(toolCallId, { remember: true }) : options?.approve(toolCallId)
3377
+ run: (approveOptions) => approval?.target === void 0 ? approveOptions?.remember === true ? options?.approve(toolCallId, { remember: true }) : options?.approve(toolCallId) : options?.approve(
3378
+ toolCallId,
3379
+ approveOptions?.remember === true ? { remember: true } : void 0,
3380
+ approval.target
3381
+ )
3228
3382
  },
3229
3383
  reject: {
3230
3384
  available: awaiting && options?.canReject === true,
3231
3385
  isSubmitting: sending === "reject",
3232
- run: () => options?.reject(toolCallId)
3386
+ run: () => approval?.target === void 0 ? options?.reject(toolCallId) : options?.reject(toolCallId, approval.target)
3233
3387
  },
3234
3388
  error: options?.errorOf(toolCallId) ?? null,
3235
3389
  errorCode: options?.errorCodeOf?.(toolCallId) ?? null
@@ -3379,7 +3533,7 @@ function buildElicitationBlock(key, part, request, options) {
3379
3533
  var UI_PART = "data-ui";
3380
3534
  var APPROVAL_PART = "data-approval-requested";
3381
3535
  var SETTLED_PART = "data-approval-settled";
3382
- var SETTLED_STATUSES = ["approved", "rejected", "expired"];
3536
+ var SETTLED_STATUSES = ["approved", "rejected", "expired", "superseded"];
3383
3537
  function readApprovals(parts) {
3384
3538
  const out = /* @__PURE__ */ new Map();
3385
3539
  const entry = (id) => {
@@ -3400,7 +3554,7 @@ function readApprovals(parts) {
3400
3554
  return found;
3401
3555
  };
3402
3556
  for (const part of parts) {
3403
- if (part.type !== APPROVAL_PART && part.type !== SETTLED_PART) {
3557
+ if (part.type !== APPROVAL_PART && part.type !== SETTLED_PART && part.type !== "data-action-proposal") {
3404
3558
  continue;
3405
3559
  }
3406
3560
  const data = part.data;
@@ -3408,12 +3562,35 @@ function readApprovals(parts) {
3408
3562
  continue;
3409
3563
  }
3410
3564
  const approval = entry(data.id);
3411
- if (part.type === APPROVAL_PART) {
3565
+ if (part.type === APPROVAL_PART || part.type === "data-action-proposal") {
3566
+ if (isRecord4(data.target) && data.target.kind === "proposal" && typeof data.target.proposalId === "string") {
3567
+ approval.target = {
3568
+ kind: "proposal",
3569
+ proposalId: data.target.proposalId,
3570
+ ...typeof data.target.threadId === "string" ? { threadId: data.target.threadId } : {}
3571
+ };
3572
+ }
3573
+ if (part.type === "data-action-proposal") {
3574
+ if (typeof data.status === "string" && ["pending", "approved", "rejected", "expired", "superseded"].includes(data.status))
3575
+ approval.status = data.status === "pending" ? null : data.status;
3576
+ if (data.executionStatus === null || data.executionStatus === "queued" || data.executionStatus === "executing" || data.executionStatus === "succeeded" || data.executionStatus === "failed")
3577
+ approval.executionStatus = data.executionStatus;
3578
+ approval.remember = data.remember === true;
3579
+ approval.decidedBy = typeof data.decidedBy === "string" ? data.decidedBy : null;
3580
+ approval.decidedVia = typeof data.decidedVia === "string" ? data.decidedVia : null;
3581
+ }
3412
3582
  if (typeof data.approver !== "string") {
3413
3583
  continue;
3414
3584
  }
3415
3585
  approval.approver = data.approver;
3416
3586
  approval.expiresAt = typeof data.expiresAt === "string" ? data.expiresAt : null;
3587
+ if (isRecord4(data.confirmation) && typeof data.confirmation.title === "string" && typeof data.confirmation.verb === "string" && (data.confirmation.detail === void 0 || typeof data.confirmation.detail === "string")) {
3588
+ approval.confirmation = {
3589
+ title: data.confirmation.title,
3590
+ verb: data.confirmation.verb,
3591
+ ...typeof data.confirmation.detail === "string" ? { detail: data.confirmation.detail } : {}
3592
+ };
3593
+ }
3417
3594
  approval.reason = typeof data.reason === "string" ? data.reason : null;
3418
3595
  continue;
3419
3596
  }
@@ -3437,9 +3614,12 @@ function approvalOf(parts, call) {
3437
3614
  }
3438
3615
  return {
3439
3616
  approver: parts.approver,
3617
+ ...parts.target === void 0 ? {} : { target: parts.target },
3618
+ ...parts.executionStatus === void 0 ? {} : { executionStatus: parts.executionStatus },
3619
+ ...parts.confirmation !== void 0 ? { confirmation: parts.confirmation } : {},
3440
3620
  expiresAt: parts.expiresAt,
3441
3621
  reason: parts.reason,
3442
- status: parts.status ?? statusFromPart(call),
3622
+ status: parts.status ?? (parts.target?.kind === "proposal" ? "pending" : statusFromPart(call)),
3443
3623
  remember: parts.remember,
3444
3624
  decidedBy: parts.decidedBy,
3445
3625
  decidedVia: parts.decidedVia,
@@ -3469,6 +3649,10 @@ function readUiComponent(part) {
3469
3649
  component: data.component,
3470
3650
  props: isRecord4(data.props) ? data.props : {},
3471
3651
  version: typeof data.version === "number" ? data.version : null,
3652
+ ...isRecord4(data.componentVersions) && Object.values(data.componentVersions).every(
3653
+ (version) => typeof version === "number" && Number.isSafeInteger(version) && version > 0
3654
+ ) ? { componentVersions: { ...data.componentVersions } } : {},
3655
+ ...typeof data.fallbackText === "string" ? { fallbackText: data.fallbackText } : {},
3472
3656
  toolCallId: typeof data.toolCallId === "string" ? data.toolCallId : null
3473
3657
  };
3474
3658
  }
@@ -3579,6 +3763,12 @@ function formatCostUsd(cost) {
3579
3763
  }
3580
3764
  return `$${cost.toFixed(2)}`;
3581
3765
  }
3766
+ function proposalDescription(description, approval) {
3767
+ if (approval?.target?.kind !== "proposal") return description;
3768
+ const status = approval.status === "pending" ? "awaiting-approval" : approval.status === "approved" ? approval.executionStatus ?? "queued" : "denied";
3769
+ const phrase = status === "awaiting-approval" ? "Awaiting approval" : status === "queued" ? "Queued" : status === "executing" ? "Executing" : status === "succeeded" ? "Succeeded" : status === "failed" ? "Failed" : approval.status === "expired" ? "Expired" : approval.status === "superseded" ? "Replaced" : "Rejected";
3770
+ return { ...description, status, phrase, result: null };
3771
+ }
3582
3772
 
3583
3773
  // src/transcript/use-stick-to-bottom.ts
3584
3774
  import { useCallback as useCallback8, useEffect as useEffect10, useRef as useRef8, useState as useState10 } from "react";
@@ -3871,23 +4061,31 @@ function useTranscriptItems({
3871
4061
  [settle]
3872
4062
  );
3873
4063
  const approve = useCallback9(
3874
- (toolCallId, options2) => {
4064
+ (toolCallId, options2, target) => {
3875
4065
  const onApprove = latest.current.handlers.onApprove;
3876
4066
  if (onApprove) {
3877
4067
  settle(
3878
4068
  toolCallId,
3879
4069
  "approve",
3880
- () => onApprove({ toolCallId, ...options2?.remember === true ? { remember: true } : {} })
4070
+ () => onApprove({
4071
+ toolCallId,
4072
+ ...target === void 0 ? {} : { target },
4073
+ ...options2?.remember === true ? { remember: true } : {}
4074
+ })
3881
4075
  );
3882
4076
  }
3883
4077
  },
3884
4078
  [settle]
3885
4079
  );
3886
4080
  const reject = useCallback9(
3887
- (toolCallId) => {
4081
+ (toolCallId, target) => {
3888
4082
  const onReject = latest.current.handlers.onReject;
3889
4083
  if (onReject) {
3890
- settle(toolCallId, "reject", () => onReject({ toolCallId }));
4084
+ settle(
4085
+ toolCallId,
4086
+ "reject",
4087
+ () => onReject({ toolCallId, ...target === void 0 ? {} : { target } })
4088
+ );
3891
4089
  }
3892
4090
  },
3893
4091
  [settle]
@@ -4085,14 +4283,36 @@ function settleHandlers(options, backend) {
4085
4283
  ),
4086
4284
  onApprove: pick(
4087
4285
  options.onApprove,
4088
- backend.approveToolCall ? async (input) => {
4089
- await backend.approveToolCall?.(input);
4286
+ backend.approveToolCall || backend.approveActionProposal ? async (input) => {
4287
+ if (input.target?.kind === "proposal") {
4288
+ if (input.target.threadId === void 0)
4289
+ throw new Error("A proposal decision requires its thread scope");
4290
+ await requireBackendMethod(
4291
+ backend,
4292
+ "approveActionProposal"
4293
+ )({
4294
+ threadId: input.target.threadId,
4295
+ proposalId: input.target.proposalId,
4296
+ ...input.remember === void 0 ? {} : { remember: input.remember }
4297
+ });
4298
+ } else await requireBackendMethod(backend, "approveToolCall")(input);
4090
4299
  } : void 0
4091
4300
  ),
4092
4301
  onReject: pick(
4093
4302
  options.onReject,
4094
- backend.rejectToolCall ? async (input) => {
4095
- await backend.rejectToolCall?.(input);
4303
+ backend.rejectToolCall || backend.rejectActionProposal ? async (input) => {
4304
+ if (input.target?.kind === "proposal") {
4305
+ if (input.target.threadId === void 0)
4306
+ throw new Error("A proposal decision requires its thread scope");
4307
+ await requireBackendMethod(
4308
+ backend,
4309
+ "rejectActionProposal"
4310
+ )({
4311
+ threadId: input.target.threadId,
4312
+ proposalId: input.target.proposalId,
4313
+ ...input.reason === void 0 ? {} : { reason: input.reason }
4314
+ });
4315
+ } else await requireBackendMethod(backend, "rejectToolCall")(input);
4096
4316
  } : void 0
4097
4317
  )
4098
4318
  };
@@ -4997,6 +5217,8 @@ function storedMessageToUiMessage(message) {
4997
5217
  id: component.id,
4998
5218
  component: component.component,
4999
5219
  props: component.props,
5220
+ ...component.componentVersions !== void 0 ? { componentVersions: component.componentVersions } : {},
5221
+ ...component.fallbackText !== void 0 ? { fallbackText: component.fallbackText } : {},
5000
5222
  ...component.version !== void 0 ? { version: component.version } : {},
5001
5223
  ...component.toolCallId !== void 0 ? { toolCallId: component.toolCallId } : {}
5002
5224
  }
@@ -5016,6 +5238,8 @@ function storedMessageToUiMessage(message) {
5016
5238
  data: {
5017
5239
  id: approval.toolCallId,
5018
5240
  approver: approval.approver,
5241
+ ...approval.target !== void 0 ? { target: approval.target } : approval.proposalId !== void 0 ? { target: { kind: "proposal", proposalId: approval.proposalId } } : {},
5242
+ ...approval.confirmation !== void 0 ? { confirmation: approval.confirmation } : {},
5019
5243
  ...approval.expiresAt !== void 0 ? { expiresAt: approval.expiresAt } : {}
5020
5244
  }
5021
5245
  });
@@ -5072,6 +5296,7 @@ function storedMessageToUiMessage(message) {
5072
5296
  const metadata = {
5073
5297
  // The host's own facts first: the library's keys win on a clash.
5074
5298
  ...message.metadata,
5299
+ ...message.actionProposalOutcome === void 0 ? {} : { actionProposalOutcomeId: message.actionProposalOutcome.id },
5075
5300
  ...message.feedback !== void 0 ? { feedback: message.feedback } : {},
5076
5301
  ...message.createdAt ? { createdAt: message.createdAt } : {},
5077
5302
  ...message.agentName !== void 0 ? { agentName: message.agentName } : {},
@@ -5107,6 +5332,8 @@ function storedThreadToUiMessages(messages) {
5107
5332
  return turns.map(mergeTurn);
5108
5333
  }
5109
5334
  function sameRun(previous, next) {
5335
+ if (previous.actionProposalOutcome !== void 0 || next.actionProposalOutcome !== void 0)
5336
+ return false;
5110
5337
  return previous.runId === void 0 || next.runId === void 0 || previous.runId === next.runId;
5111
5338
  }
5112
5339
  function mergeTurn(rows) {
@@ -5154,7 +5381,162 @@ function sumUsage(rows) {
5154
5381
 
5155
5382
  // src/use-agent-chat.ts
5156
5383
  import { useChat } from "@ai-sdk/react";
5157
- import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useMemo11, useRef as useRef12, useState as useState15 } from "react";
5384
+ import { useCallback as useCallback13, useEffect as useEffect16, useMemo as useMemo11, useRef as useRef13, useState as useState16 } from "react";
5385
+
5386
+ // src/approvals/use-action-proposals.ts
5387
+ import { useCallback as useCallback12, useEffect as useEffect15, useRef as useRef12, useState as useState15 } from "react";
5388
+
5389
+ // src/approvals/proposals.ts
5390
+ function proposalNeedsPolling(proposal) {
5391
+ return proposal.decision === "pending" || proposal.execution?.status === "queued" || proposal.execution?.status === "executing" || proposal.outcomeDelivery?.status === "pending";
5392
+ }
5393
+ function reconcileProposalMessages(current, proposals, facts) {
5394
+ const messages = current.map((message) => {
5395
+ const relevant = proposals.filter(
5396
+ (proposal) => message.parts.some((part) => {
5397
+ if ("data" in part && record(part.data) && record(part.data.target) && part.data.target.kind === "proposal")
5398
+ return part.data.target.proposalId === proposal.id;
5399
+ if ("toolCallId" in part && part.toolCallId === proposal.originToolCallId) {
5400
+ if ("output" in part && record(part.output) && typeof part.output.proposalId === "string")
5401
+ return part.output.proposalId === proposal.id;
5402
+ return record(message.metadata) && message.metadata.runId === proposal.originRunId;
5403
+ }
5404
+ return false;
5405
+ })
5406
+ );
5407
+ if (relevant.length === 0) return message;
5408
+ const ids = new Set(relevant.map((proposal) => proposal.originToolCallId));
5409
+ const parts = message.parts.filter(
5410
+ (part) => !(part.type === "data-action-proposal" && "id" in part && typeof part.id === "string" && ids.has(part.id))
5411
+ );
5412
+ for (const proposal of relevant)
5413
+ parts.push({
5414
+ type: "data-action-proposal",
5415
+ id: proposal.originToolCallId,
5416
+ data: {
5417
+ id: proposal.originToolCallId,
5418
+ target: { kind: "proposal", proposalId: proposal.id, threadId: proposal.threadId },
5419
+ approver: proposal.approver,
5420
+ confirmation: proposal.confirmation,
5421
+ expiresAt: proposal.expiresAt === null ? null : Math.abs(proposal.expiresAt) <= 864e13 ? new Date(proposal.expiresAt).toISOString() : null,
5422
+ status: proposal.decision,
5423
+ executionStatus: proposal.execution?.status ?? null,
5424
+ remember: proposal.decisionAudit?.remember === true,
5425
+ decidedBy: proposal.decisionAudit?.actorRef ?? null,
5426
+ decidedVia: proposal.decisionAudit?.via ?? null,
5427
+ reason: proposal.decisionAudit?.reason ?? null
5428
+ }
5429
+ });
5430
+ return { ...message, parts };
5431
+ });
5432
+ const knownMessages = new Set(messages.map((message) => message.id));
5433
+ const knownOutcomes = new Set(
5434
+ messages.map((message) => outcomeId(message.metadata)).filter(Boolean)
5435
+ );
5436
+ for (const fact of facts) {
5437
+ const id = fact.actionProposalOutcome?.id;
5438
+ if (id === void 0 || knownMessages.has(fact.id) || knownOutcomes.has(id)) continue;
5439
+ const message = storedMessageToUiMessage(fact);
5440
+ messages.push({
5441
+ ...message,
5442
+ metadata: {
5443
+ ...record(message.metadata) ? message.metadata : {},
5444
+ actionProposalOutcomeId: id
5445
+ }
5446
+ });
5447
+ knownMessages.add(fact.id);
5448
+ knownOutcomes.add(id);
5449
+ }
5450
+ return messages;
5451
+ }
5452
+ function record(value) {
5453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5454
+ }
5455
+ function outcomeId(value) {
5456
+ return record(value) && typeof value.actionProposalOutcomeId === "string" ? value.actionProposalOutcomeId : void 0;
5457
+ }
5458
+
5459
+ // src/approvals/use-action-proposals.ts
5460
+ function useActionProposals(backend, threadId, setMessages, refreshKey, pollMs = 1e3) {
5461
+ const [items, setItems] = useState15([]);
5462
+ const [error, setError] = useState15(null);
5463
+ const latest = useRef12({ threadId, setMessages });
5464
+ latest.current = { threadId, setMessages };
5465
+ const mounted = useRef12(true);
5466
+ const requests = useRef12(/* @__PURE__ */ new Map());
5467
+ useEffect15(() => {
5468
+ mounted.current = true;
5469
+ return () => {
5470
+ mounted.current = false;
5471
+ };
5472
+ }, []);
5473
+ const refresh = useCallback12(async () => {
5474
+ if (threadId === void 0 || backend.listActionProposals === void 0) return;
5475
+ const pending = requests.current.get(threadId);
5476
+ if (pending) return pending;
5477
+ const request = (async () => {
5478
+ try {
5479
+ const proposals = await backend.listActionProposals?.({ threadId });
5480
+ if (!mounted.current || latest.current.threadId !== threadId || !Array.isArray(proposals))
5481
+ return;
5482
+ const admitted = proposals.some(
5483
+ (proposal) => proposal.outcomeDelivery?.status === "admitted"
5484
+ );
5485
+ const facts = admitted ? (await backend.getThread(threadId)).messages.filter(
5486
+ (message) => message.actionProposalOutcome !== void 0
5487
+ ) : [];
5488
+ if (!mounted.current || latest.current.threadId !== threadId) return;
5489
+ setItems(proposals);
5490
+ setError(null);
5491
+ latest.current.setMessages(
5492
+ (current) => reconcileProposalMessages(current, proposals, facts)
5493
+ );
5494
+ } catch (cause) {
5495
+ if (mounted.current && latest.current.threadId === threadId)
5496
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
5497
+ }
5498
+ })();
5499
+ requests.current.set(threadId, request);
5500
+ try {
5501
+ await request;
5502
+ } finally {
5503
+ requests.current.delete(threadId);
5504
+ }
5505
+ }, [backend, threadId]);
5506
+ useEffect15(() => {
5507
+ setItems([]);
5508
+ setError(null);
5509
+ }, [threadId]);
5510
+ useEffect15(() => {
5511
+ void refresh();
5512
+ }, [refresh, refreshKey]);
5513
+ useEffect15(() => {
5514
+ if (!items.some(proposalNeedsPolling) || pollMs <= 0) return;
5515
+ const timer = setInterval(() => {
5516
+ void refresh();
5517
+ }, pollMs);
5518
+ return () => clearInterval(timer);
5519
+ }, [items, pollMs, refresh]);
5520
+ useEffect15(() => {
5521
+ const refetch = () => {
5522
+ void refresh();
5523
+ };
5524
+ const visible = () => {
5525
+ if (document.visibilityState === "visible") refetch();
5526
+ };
5527
+ window.addEventListener("focus", refetch);
5528
+ window.addEventListener("online", refetch);
5529
+ document.addEventListener("visibilitychange", visible);
5530
+ return () => {
5531
+ window.removeEventListener("focus", refetch);
5532
+ window.removeEventListener("online", refetch);
5533
+ document.removeEventListener("visibilitychange", visible);
5534
+ };
5535
+ }, [refresh]);
5536
+ return { items, error, refresh };
5537
+ }
5538
+
5539
+ // src/use-agent-chat.ts
5158
5540
  var EMPTY_QUEUE = { items: [], paused: null };
5159
5541
  var localQueueSequence = 0;
5160
5542
  function queuedUserMessage(turn) {
@@ -5209,13 +5591,16 @@ function localChatId() {
5209
5591
  return `local-chat-${localChatSequence}`;
5210
5592
  }
5211
5593
  function useAgentChat(options = {}) {
5212
- const latest = useRef12(options);
5594
+ const providerCapabilities = useAgentUiCapabilities();
5595
+ const capabilitiesRef = useRef13(providerCapabilities);
5596
+ capabilitiesRef.current = providerCapabilities;
5597
+ const latest = useRef13(options);
5213
5598
  latest.current = options;
5214
5599
  const resolved = useAgentBackend(options.backend);
5215
- const client = useRef12(resolved).current;
5216
- const createdThreadId = useRef12(void 0);
5217
- const chatIdRef = useRef12(options.threadId ?? localChatId());
5218
- const boundThreadRef = useRef12(options.threadId);
5600
+ const client = useRef13(resolved).current;
5601
+ const createdThreadId = useRef13(void 0);
5602
+ const chatIdRef = useRef13(options.threadId ?? localChatId());
5603
+ const boundThreadRef = useRef13(options.threadId);
5219
5604
  if (options.threadId !== boundThreadRef.current) {
5220
5605
  boundThreadRef.current = options.threadId;
5221
5606
  const adopting = options.threadId !== void 0 && options.threadId === createdThreadId.current;
@@ -5225,39 +5610,41 @@ function useAgentChat(options = {}) {
5225
5610
  }
5226
5611
  }
5227
5612
  const chatId = chatIdRef.current;
5228
- const [runId, setRunId] = useState15(options.resumeRunId);
5229
- const runIdRef = useRef12(runId);
5613
+ const [runId, setRunId] = useState16(options.resumeRunId);
5614
+ const runIdRef = useRef13(runId);
5230
5615
  runIdRef.current = runId;
5231
- const settlingRunIdRef = useRef12(void 0);
5232
- const autoResumeRunIdRef = useRef12(void 0);
5233
- const [activeRunId, setActiveRunId] = useState15(null);
5234
- const [historyFor, setHistoryFor] = useState15(void 0);
5235
- const [historyError, setHistoryError] = useState15(null);
5236
- const [runError, setRunError] = useState15(null);
5237
- const [threadModel, setThreadModel] = useState15(null);
5238
- const [threadPersona, setThreadPersona] = useState15(null);
5239
- const [pickedModel, setPickedModel] = useState15(void 0);
5240
- const pickedModelRef = useRef12(pickedModel);
5616
+ const settlingRunIdRef = useRef13(void 0);
5617
+ const autoResumeRunIdRef = useRef13(void 0);
5618
+ const [activeRunId, setActiveRunId] = useState16(null);
5619
+ const [historyFor, setHistoryFor] = useState16(void 0);
5620
+ const [historyError, setHistoryError] = useState16(null);
5621
+ const [runError, setRunError] = useState16(null);
5622
+ const [threadModel, setThreadModel] = useState16(null);
5623
+ const [threadPersona, setThreadPersona] = useState16(null);
5624
+ const [pickedModel, setPickedModel] = useState16(void 0);
5625
+ const pickedModelRef = useRef13(pickedModel);
5241
5626
  pickedModelRef.current = pickedModel;
5242
- const lockedRef = useRef12(false);
5243
- const pendingPin = useRef12(void 0);
5244
- const regenerateNext = useRef12(false);
5245
- const currentThreadId = useCallback12(
5627
+ const lockedRef = useRef13(false);
5628
+ const pendingPin = useRef13(void 0);
5629
+ const regenerateNext = useRef13(false);
5630
+ const currentThreadId = useCallback13(
5246
5631
  () => latest.current.threadId ?? createdThreadId.current,
5247
5632
  []
5248
5633
  );
5249
- const [queueState, setQueueState] = useState15(EMPTY_QUEUE);
5250
- const queueStateRef = useRef12(queueState);
5251
- const applyQueue = useCallback12((next) => {
5634
+ const [queueState, setQueueState] = useState16(EMPTY_QUEUE);
5635
+ const queueStateRef = useRef13(queueState);
5636
+ const applyQueue = useCallback13((next) => {
5252
5637
  queueStateRef.current = next;
5253
5638
  setQueueState(next);
5254
5639
  }, []);
5255
- const [pendingSends, setPendingSends] = useState15([]);
5256
- const [queueError, setQueueError] = useState15(null);
5257
- const handoff = useRef12(void 0);
5258
- const queuedSend = useRef12(void 0);
5259
- const threadWaiters = useRef12([]);
5260
- const turnBody = useCallback12(() => {
5640
+ const [pendingSends, setPendingSends] = useState16([]);
5641
+ const [queueError, setQueueError] = useState16(null);
5642
+ const handoff = useRef13(void 0);
5643
+ const queuedSend = useRef13(void 0);
5644
+ const proposalRefreshRef = useRef13(async () => {
5645
+ });
5646
+ const threadWaiters = useRef13([]);
5647
+ const turnBody = useCallback13(() => {
5261
5648
  const current = latest.current;
5262
5649
  if (current.persona !== void 0) setThreadPersona(current.persona);
5263
5650
  const pageContext = current.getPageContext?.() ?? null;
@@ -5270,11 +5657,12 @@ function useAgentChat(options = {}) {
5270
5657
  ...current.persona !== void 0 ? { persona: current.persona } : {},
5271
5658
  ...threadId !== void 0 ? { threadId } : {},
5272
5659
  ...model !== void 0 ? { model } : {},
5273
- ...pageContext ? { pageContext } : {}
5660
+ ...pageContext ? { pageContext } : {},
5661
+ ...(current.uiCapabilities ?? capabilitiesRef.current) === void 0 ? {} : { uiCapabilities: current.uiCapabilities ?? capabilitiesRef.current }
5274
5662
  };
5275
5663
  }, [currentThreadId]);
5276
- const [connection, setConnection] = useState15({ status: "live" });
5277
- const resyncAfterSettle = useRef12(false);
5664
+ const [connection, setConnection] = useState16({ status: "live" });
5665
+ const resyncAfterSettle = useRef13(false);
5278
5666
  const transport = useMemo11(() => {
5279
5667
  function onMeta(meta) {
5280
5668
  settlingRunIdRef.current = meta.runId;
@@ -5327,7 +5715,7 @@ function useAgentChat(options = {}) {
5327
5715
  }
5328
5716
  });
5329
5717
  }, []);
5330
- const backgroundRef = useRef12(() => Promise.resolve());
5718
+ const backgroundRef = useRef13(() => Promise.resolve());
5331
5719
  const chat = useChat({
5332
5720
  transport,
5333
5721
  id: chatId,
@@ -5335,6 +5723,7 @@ function useAgentChat(options = {}) {
5335
5723
  ...options.initialMessages !== void 0 ? { messages: options.initialMessages } : {},
5336
5724
  onData: (part) => {
5337
5725
  latest.current.onData?.(part);
5726
+ if (part.type === "data-proposal-decision") void proposalRefreshRef.current();
5338
5727
  if (part.type === "data-queue") {
5339
5728
  onQueueData(part.data);
5340
5729
  }
@@ -5381,7 +5770,7 @@ function useAgentChat(options = {}) {
5381
5770
  }
5382
5771
  }
5383
5772
  });
5384
- const chatRef = useRef12(chat);
5773
+ const chatRef = useRef13(chat);
5385
5774
  chatRef.current = chat;
5386
5775
  function onQueueData(data) {
5387
5776
  const frame = data ?? {};
@@ -5429,8 +5818,8 @@ function useAgentChat(options = {}) {
5429
5818
  } catch {
5430
5819
  }
5431
5820
  }
5432
- const firstChatId = useRef12(chatId);
5433
- useEffect15(() => {
5821
+ const firstChatId = useRef13(chatId);
5822
+ useEffect16(() => {
5434
5823
  if (chatId === firstChatId.current) return;
5435
5824
  firstChatId.current = chatId;
5436
5825
  setRunId(void 0);
@@ -5449,7 +5838,7 @@ function useAgentChat(options = {}) {
5449
5838
  handoff.current = void 0;
5450
5839
  queuedSend.current = void 0;
5451
5840
  }, [chatId]);
5452
- useEffect15(() => {
5841
+ useEffect16(() => {
5453
5842
  const threadId = options.threadId;
5454
5843
  if (threadId === void 0) return;
5455
5844
  if (threadId === createdThreadId.current) return;
@@ -5496,8 +5885,8 @@ function useAgentChat(options = {}) {
5496
5885
  };
5497
5886
  }, [client, chatId, options.threadId]);
5498
5887
  const isLoadingHistory = options.threadId !== void 0 && options.threadId !== createdThreadId.current && options.history !== false && options.initialMessages === void 0 && historyFor !== options.threadId;
5499
- const turnInFlight = useRef12(false);
5500
- const isTurnInFlight = useCallback12(
5888
+ const turnInFlight = useRef13(false);
5889
+ const isTurnInFlight = useCallback13(
5501
5890
  () => turnInFlight.current || transport.isAttemptLive,
5502
5891
  [transport]
5503
5892
  );
@@ -5506,20 +5895,20 @@ function useAgentChat(options = {}) {
5506
5895
  enabled: options.quota !== false && typeof client.getQuota === "function"
5507
5896
  });
5508
5897
  const blocked = options.blocked !== void 0 ? options.blocked : quota.blocked;
5509
- const blockedRef = useRef12(blocked);
5898
+ const blockedRef = useRef13(blocked);
5510
5899
  blockedRef.current = blocked;
5511
5900
  const whileRunning = typeof client.enqueueMessage === "function" ? options.whileRunning ?? "queue" : "block";
5512
- const whileRunningRef = useRef12(whileRunning);
5901
+ const whileRunningRef = useRef13(whileRunning);
5513
5902
  whileRunningRef.current = whileRunning;
5514
- const canQueueRef = useRef12(false);
5903
+ const canQueueRef = useRef13(false);
5515
5904
  canQueueRef.current = typeof client.enqueueMessage === "function";
5516
- const threadForQueue = useCallback12(async () => {
5905
+ const threadForQueue = useCallback13(async () => {
5517
5906
  const known = currentThreadId();
5518
5907
  if (known !== void 0) return known;
5519
5908
  if (!isTurnInFlight()) throw new Error("queue: this chat has no thread yet");
5520
5909
  return new Promise((resolve) => threadWaiters.current.push(resolve));
5521
5910
  }, [currentThreadId, isTurnInFlight]);
5522
- const queueCall = useCallback12(async (run) => {
5911
+ const queueCall = useCallback13(async (run) => {
5523
5912
  try {
5524
5913
  const result = await run();
5525
5914
  setQueueError(null);
@@ -5529,7 +5918,7 @@ function useAgentChat(options = {}) {
5529
5918
  throw error;
5530
5919
  }
5531
5920
  }, []);
5532
- const enqueue = useCallback12(
5921
+ const enqueue = useCallback13(
5533
5922
  async (text2, {
5534
5923
  attachments = [],
5535
5924
  mode = "queue"
@@ -5577,7 +5966,7 @@ function useAgentChat(options = {}) {
5577
5966
  },
5578
5967
  [client, queueCall, threadForQueue, turnBody, applyQueue, isTurnInFlight]
5579
5968
  );
5580
- const sendMessage = useCallback12(
5969
+ const sendMessage = useCallback13(
5581
5970
  async (message, options2) => {
5582
5971
  const block = blockedRef.current;
5583
5972
  if (block != null) throw new QuotaBlockedError(block);
@@ -5601,7 +5990,7 @@ function useAgentChat(options = {}) {
5601
5990
  },
5602
5991
  [isTurnInFlight, enqueue]
5603
5992
  );
5604
- const removeQueued = useCallback12(
5993
+ const removeQueued = useCallback13(
5605
5994
  async (id) => {
5606
5995
  const previous = queueStateRef.current;
5607
5996
  applyQueue({ ...previous, items: previous.items.filter((item) => item.id !== id) });
@@ -5614,7 +6003,7 @@ function useAgentChat(options = {}) {
5614
6003
  },
5615
6004
  [client, queueCall, applyQueue]
5616
6005
  );
5617
- const interruptQueued = useCallback12(
6006
+ const interruptQueued = useCallback13(
5618
6007
  async (id) => {
5619
6008
  const previous = queueStateRef.current;
5620
6009
  const moving = previous.items.find((item) => item.id === id);
@@ -5649,7 +6038,7 @@ function useAgentChat(options = {}) {
5649
6038
  },
5650
6039
  [client, queueCall, applyQueue, isTurnInFlight]
5651
6040
  );
5652
- const editQueued = useCallback12(
6041
+ const editQueued = useCallback13(
5653
6042
  async (id, text2) => {
5654
6043
  applyQueue(
5655
6044
  await queueCall(
@@ -5659,7 +6048,7 @@ function useAgentChat(options = {}) {
5659
6048
  },
5660
6049
  [client, queueCall, applyQueue]
5661
6050
  );
5662
- const moveQueued = useCallback12(
6051
+ const moveQueued = useCallback13(
5663
6052
  async (id, index) => {
5664
6053
  const previous = queueStateRef.current;
5665
6054
  const moving = previous.items.find((item) => item.id === id);
@@ -5681,12 +6070,12 @@ function useAgentChat(options = {}) {
5681
6070
  },
5682
6071
  [client, queueCall, applyQueue]
5683
6072
  );
5684
- const clearQueue = useCallback12(async () => {
6073
+ const clearQueue = useCallback13(async () => {
5685
6074
  const threadId = currentThreadId();
5686
6075
  if (threadId === void 0) return;
5687
6076
  applyQueue(await queueCall(() => requireBackendMethod(client, "clearQueue")(threadId)));
5688
6077
  }, [client, queueCall, currentThreadId, applyQueue]);
5689
- const resumeQueue = useCallback12(async () => {
6078
+ const resumeQueue = useCallback13(async () => {
5690
6079
  const threadId = currentThreadId();
5691
6080
  if (threadId === void 0) return;
5692
6081
  const before = queueStateRef.current;
@@ -5704,7 +6093,7 @@ function useAgentChat(options = {}) {
5704
6093
  else attachTo(turn);
5705
6094
  }
5706
6095
  }, [client, queueCall, currentThreadId, applyQueue, isTurnInFlight]);
5707
- const resumeQueueRef = useRef12(resumeQueue);
6096
+ const resumeQueueRef = useRef13(resumeQueue);
5708
6097
  resumeQueueRef.current = resumeQueue;
5709
6098
  const queuedItems = [
5710
6099
  ...pendingSends.filter((item) => item.interrupt),
@@ -5734,7 +6123,7 @@ function useAgentChat(options = {}) {
5734
6123
  resume: resumeQueue,
5735
6124
  error: queueError
5736
6125
  };
5737
- const addToolResult = useCallback12(
6126
+ const addToolResult = useCallback13(
5738
6127
  ({ tool, toolCallId, output }) => chatRef.current.addToolResult({
5739
6128
  tool,
5740
6129
  toolCallId,
@@ -5742,8 +6131,8 @@ function useAgentChat(options = {}) {
5742
6131
  }),
5743
6132
  []
5744
6133
  );
5745
- const [backgroundRuns, setBackgroundRuns] = useState15([]);
5746
- const refreshBackground = useCallback12(async () => {
6134
+ const [backgroundRuns, setBackgroundRuns] = useState16([]);
6135
+ const refreshBackground = useCallback13(async () => {
5747
6136
  const threadId = currentThreadId();
5748
6137
  if (threadId === void 0) {
5749
6138
  return;
@@ -5763,13 +6152,13 @@ function useAgentChat(options = {}) {
5763
6152
  }, [client, currentThreadId]);
5764
6153
  backgroundRef.current = refreshBackground;
5765
6154
  const isWorking = backgroundRuns.some((run) => run.status === "running");
5766
- useEffect15(() => {
6155
+ useEffect16(() => {
5767
6156
  if (options.background !== true || options.threadId === void 0) {
5768
6157
  return;
5769
6158
  }
5770
6159
  void refreshBackground();
5771
6160
  }, [options.background, options.threadId, chatId, refreshBackground]);
5772
- useEffect15(() => {
6161
+ useEffect16(() => {
5773
6162
  const every = latest.current.backgroundPollMs ?? 5e3;
5774
6163
  if (latest.current.background !== true || !isWorking || every <= 0) {
5775
6164
  return;
@@ -5777,7 +6166,15 @@ function useAgentChat(options = {}) {
5777
6166
  const timer = setInterval(() => void refreshBackground(), every);
5778
6167
  return () => clearInterval(timer);
5779
6168
  }, [isWorking, refreshBackground]);
5780
- const cancel = useCallback12(async () => {
6169
+ const proposals = useActionProposals(
6170
+ client,
6171
+ options.proposals === false || options.proposals !== true && options.history === false && options.resume === false ? void 0 : currentThreadId(),
6172
+ (update) => chatRef.current.setMessages(update),
6173
+ `${chat.status}:${connection.status}`,
6174
+ options.proposalPollMs
6175
+ );
6176
+ proposalRefreshRef.current = proposals.refresh;
6177
+ const cancel = useCallback13(async () => {
5781
6178
  chatRef.current.stop();
5782
6179
  const active = runIdRef.current;
5783
6180
  if (active) {
@@ -5794,8 +6191,23 @@ function useAgentChat(options = {}) {
5794
6191
  }
5795
6192
  }
5796
6193
  }, [client, currentThreadId, applyQueue]);
5797
- const approve = useCallback12(
5798
- async ({ toolCallId, remember, via }) => {
6194
+ const approve = useCallback13(
6195
+ async ({ toolCallId, target, remember, via }) => {
6196
+ if (target?.kind === "proposal") {
6197
+ const threadId = target.threadId ?? currentThreadId();
6198
+ if (threadId === void 0)
6199
+ throw new Error("A proposal decision requires its thread scope");
6200
+ await requireBackendMethod(
6201
+ client,
6202
+ "approveActionProposal"
6203
+ )({
6204
+ threadId,
6205
+ proposalId: target.proposalId,
6206
+ ...remember === true ? { remember: true } : {}
6207
+ });
6208
+ await proposals.refresh();
6209
+ return;
6210
+ }
5799
6211
  await requireBackendMethod(
5800
6212
  client,
5801
6213
  "approveToolCall"
@@ -5805,10 +6217,25 @@ function useAgentChat(options = {}) {
5805
6217
  ...via !== void 0 ? { via } : {}
5806
6218
  });
5807
6219
  },
5808
- [client]
6220
+ [client, currentThreadId, proposals.refresh]
5809
6221
  );
5810
- const reject = useCallback12(
5811
- async ({ toolCallId, reason, via }) => {
6222
+ const reject = useCallback13(
6223
+ async ({ toolCallId, target, reason, via }) => {
6224
+ if (target?.kind === "proposal") {
6225
+ const threadId = target.threadId ?? currentThreadId();
6226
+ if (threadId === void 0)
6227
+ throw new Error("A proposal decision requires its thread scope");
6228
+ await requireBackendMethod(
6229
+ client,
6230
+ "rejectActionProposal"
6231
+ )({
6232
+ threadId,
6233
+ proposalId: target.proposalId,
6234
+ ...reason === void 0 ? {} : { reason }
6235
+ });
6236
+ await proposals.refresh();
6237
+ return;
6238
+ }
5812
6239
  await requireBackendMethod(
5813
6240
  client,
5814
6241
  "rejectToolCall"
@@ -5818,9 +6245,9 @@ function useAgentChat(options = {}) {
5818
6245
  ...via !== void 0 ? { via } : {}
5819
6246
  });
5820
6247
  },
5821
- [client]
6248
+ [client, currentThreadId, proposals.refresh]
5822
6249
  );
5823
- const answer = useCallback12(
6250
+ const answer = useCallback13(
5824
6251
  async ({ toolCallId, answers, via }) => {
5825
6252
  await requireBackendMethod(
5826
6253
  client,
@@ -5833,7 +6260,7 @@ function useAgentChat(options = {}) {
5833
6260
  },
5834
6261
  [client]
5835
6262
  );
5836
- const skip = useCallback12(
6263
+ const skip = useCallback13(
5837
6264
  async ({ toolCallId, via }) => {
5838
6265
  await requireBackendMethod(
5839
6266
  client,
@@ -5842,7 +6269,7 @@ function useAgentChat(options = {}) {
5842
6269
  },
5843
6270
  [client]
5844
6271
  );
5845
- const regenerate = useCallback12(
6272
+ const regenerate = useCallback13(
5846
6273
  (_input) => {
5847
6274
  const block = blockedRef.current;
5848
6275
  if (block != null) throw new QuotaBlockedError(block);
@@ -5855,7 +6282,7 @@ function useAgentChat(options = {}) {
5855
6282
  },
5856
6283
  [isTurnInFlight]
5857
6284
  );
5858
- const storedIdOf = useCallback12(
6285
+ const storedIdOf = useCallback13(
5859
6286
  async (messageId, threadId) => {
5860
6287
  const message = chatRef.current.messages.find((candidate) => candidate.id === messageId);
5861
6288
  const runOf = message?.metadata?.runId;
@@ -5866,7 +6293,7 @@ function useAgentChat(options = {}) {
5866
6293
  },
5867
6294
  [client]
5868
6295
  );
5869
- const fork = useCallback12(
6296
+ const fork = useCallback13(
5870
6297
  async ({
5871
6298
  messageId,
5872
6299
  threadId
@@ -5881,7 +6308,7 @@ function useAgentChat(options = {}) {
5881
6308
  },
5882
6309
  [client, currentThreadId, storedIdOf]
5883
6310
  );
5884
- const truncateFrom = useCallback12(
6311
+ const truncateFrom = useCallback13(
5885
6312
  async ({ messageId, threadId }) => {
5886
6313
  const target = threadId ?? currentThreadId();
5887
6314
  if (target === void 0) throw new Error("truncateFrom: this chat has no thread yet");
@@ -5890,7 +6317,7 @@ function useAgentChat(options = {}) {
5890
6317
  },
5891
6318
  [client, currentThreadId, storedIdOf]
5892
6319
  );
5893
- const promote = useCallback12(
6320
+ const promote = useCallback13(
5894
6321
  async ({ threadId } = {}) => {
5895
6322
  const target = threadId ?? currentThreadId();
5896
6323
  if (target === void 0) throw new Error("promote: this chat has no thread yet");
@@ -5899,24 +6326,24 @@ function useAgentChat(options = {}) {
5899
6326
  },
5900
6327
  [client, currentThreadId]
5901
6328
  );
5902
- const [modelsWanted, setModelsWanted] = useState15(false);
5903
- const modelsWantedRef = useRef12(modelsWanted);
6329
+ const [modelsWanted, setModelsWanted] = useState16(false);
6330
+ const modelsWantedRef = useRef13(modelsWanted);
5904
6331
  modelsWantedRef.current = modelsWanted;
5905
6332
  const catalog = useModels({
5906
6333
  backend: client,
5907
6334
  ...options.agent !== void 0 ? { agent: options.agent } : {},
5908
6335
  enabled: modelsWanted && typeof client.listModels === "function"
5909
6336
  });
5910
- const wantModels = useCallback12(() => {
6337
+ const wantModels = useCallback13(() => {
5911
6338
  if (modelsWantedRef.current) return;
5912
6339
  modelsWantedRef.current = true;
5913
6340
  queueMicrotask(() => setModelsWanted(true));
5914
6341
  }, []);
5915
6342
  lockedRef.current = catalog.locked !== null;
5916
- const selectModel = useCallback12((id) => {
6343
+ const selectModel = useCallback13((id) => {
5917
6344
  if (!lockedRef.current) setPickedModel(id);
5918
6345
  }, []);
5919
- const pinToThread = useCallback12(
6346
+ const pinToThread = useCallback13(
5920
6347
  async (model) => {
5921
6348
  const threadId = currentThreadId();
5922
6349
  setThreadModel(model);
@@ -5956,14 +6383,14 @@ function useAgentChat(options = {}) {
5956
6383
  };
5957
6384
  const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
5958
6385
  const isBusy = status === "submitted" || status === "streaming" || status === "reconnecting";
5959
- const isBusyRef = useRef12(isBusy);
6386
+ const isBusyRef = useRef13(isBusy);
5960
6387
  isBusyRef.current = isBusy;
5961
6388
  const files = useAttachments({ ...options.composer, backend: client });
5962
- const [text, setText] = useState15("");
6389
+ const [text, setText] = useState16("");
5963
6390
  const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy && whileRunning === "block" ? "busy" : text.trim().length === 0 && files.refs.length === 0 ? "empty" : null;
5964
- const composerRef = useRef12({ text, files, blockedBy });
6391
+ const composerRef = useRef13({ text, files, blockedBy });
5965
6392
  composerRef.current = { text, files, blockedBy };
5966
- const submit = useCallback12(
6393
+ const submit = useCallback13(
5967
6394
  async (options2) => {
5968
6395
  const current = composerRef.current;
5969
6396
  const asked = canQueueRef.current ? options2?.mode : void 0;
@@ -6045,6 +6472,7 @@ function useAgentChat(options = {}) {
6045
6472
  addToolResult,
6046
6473
  /** Sub-agents this conversation started and did not wait for. */
6047
6474
  background,
6475
+ proposals,
6048
6476
  runId,
6049
6477
  /** The loaded thread's running turn, or `null` once read with none. */
6050
6478
  activeRunId,
@@ -6168,8 +6596,10 @@ export {
6168
6596
  onThreadsEvent,
6169
6597
  persistSpeechLang,
6170
6598
  phraseFor,
6599
+ proposalNeedsPolling,
6171
6600
  readPath,
6172
6601
  readReasoningMs,
6602
+ reconcileProposalMessages,
6173
6603
  reframeAgUiStream,
6174
6604
  requireBackendMethod,
6175
6605
  resolveResultView,
@@ -6177,6 +6607,7 @@ export {
6177
6607
  storedThreadToUiMessages,
6178
6608
  toolCallState,
6179
6609
  toolCatalogFrom,
6610
+ useActionProposals,
6180
6611
  useAgentBackend,
6181
6612
  useAgentChat,
6182
6613
  useAgentConfig,