@dudousxd/nestjs-agent-react 0.7.5 → 0.8.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
@@ -140,7 +140,8 @@ var AgentChatTransport = class {
140
140
  * Parse the backend's SSE byte stream and re-emit it as a valid v7 UI-message chunk stream.
141
141
  * Recognized frames:
142
142
  * - `event: meta` `data: {"runId","threadId"}` → records identity
143
- * - `data: <AgentStreamEvent JSON>` → mapped to UI chunks (text / reasoning / tool)
143
+ * - `data: <AgentStreamEvent JSON>` → mapped to UI chunks (text / reasoning / tool /
144
+ * `data-*`; see {@link AgentChatTransport} docs)
144
145
  * - `event: done` `data: {}` → terminates
145
146
  * - `event: error` `data: {code,message}` → error chunk
146
147
  *
@@ -157,7 +158,10 @@ var AgentChatTransport = class {
157
158
  let stepIndex = 0;
158
159
  let textId = null;
159
160
  let reasoningId = null;
161
+ let textSeq = 0;
162
+ let reasoningSeq = 0;
160
163
  const announced = /* @__PURE__ */ new Set();
164
+ const toolMetadata = /* @__PURE__ */ new Map();
161
165
  const record = /* @__PURE__ */ __name((meta) => this.recordMeta(meta), "record");
162
166
  const endAttempt = /* @__PURE__ */ __name(() => {
163
167
  this.attemptLive = false;
@@ -179,6 +183,8 @@ var AgentChatTransport = class {
179
183
  stepIndex += 1;
180
184
  textId = null;
181
185
  reasoningId = null;
186
+ textSeq = 0;
187
+ reasoningSeq = 0;
182
188
  controller.enqueue({
183
189
  type: "start-step"
184
190
  });
@@ -188,8 +194,7 @@ var AgentChatTransport = class {
188
194
  if (!stepOpen) openStep();
189
195
  }
190
196
  __name(ensureStep, "ensureStep");
191
- function closeStep() {
192
- if (!stepOpen) return;
197
+ function closeRuns() {
193
198
  if (textId !== null) {
194
199
  controller.enqueue({
195
200
  type: "text-end",
@@ -204,12 +209,43 @@ var AgentChatTransport = class {
204
209
  });
205
210
  reasoningId = null;
206
211
  }
212
+ }
213
+ __name(closeRuns, "closeRuns");
214
+ function closeStep() {
215
+ if (!stepOpen) return;
216
+ closeRuns();
207
217
  controller.enqueue({
208
218
  type: "finish-step"
209
219
  });
210
220
  stepOpen = false;
211
221
  }
212
222
  __name(closeStep, "closeStep");
223
+ function metadataFor(id, toolKind2, parentId) {
224
+ const merged = {
225
+ ...toolMetadata.get(id)
226
+ };
227
+ if (toolKind2 !== void 0) merged.toolKind = toolKind2;
228
+ if (parentId !== void 0 && merged.parentId === void 0) merged.parentId = parentId;
229
+ if (Object.keys(merged).length === 0) return void 0;
230
+ toolMetadata.set(id, merged);
231
+ return merged;
232
+ }
233
+ __name(metadataFor, "metadataFor");
234
+ function forwardAsData(event, transient) {
235
+ ensureStarted();
236
+ const { kind, ...data } = event;
237
+ controller.enqueue({
238
+ type: `data-${kind}`,
239
+ ...typeof data.id === "string" ? {
240
+ id: data.id
241
+ } : {},
242
+ data,
243
+ ...transient ? {
244
+ transient: true
245
+ } : {}
246
+ });
247
+ }
248
+ __name(forwardAsData, "forwardAsData");
213
249
  function emit(event) {
214
250
  switch (event.kind) {
215
251
  case "step-start":
@@ -229,7 +265,8 @@ var AgentChatTransport = class {
229
265
  case "text":
230
266
  ensureStep();
231
267
  if (textId === null) {
232
- textId = `txt-${stepIndex}`;
268
+ textId = textSeq === 0 ? `txt-${stepIndex}` : `txt-${stepIndex}.${textSeq}`;
269
+ textSeq += 1;
233
270
  controller.enqueue({
234
271
  type: "text-start",
235
272
  id: textId
@@ -244,7 +281,8 @@ var AgentChatTransport = class {
244
281
  case "reasoning":
245
282
  ensureStep();
246
283
  if (reasoningId === null) {
247
- reasoningId = `rsn-${stepIndex}`;
284
+ reasoningId = reasoningSeq === 0 ? `rsn-${stepIndex}` : `rsn-${stepIndex}.${reasoningSeq}`;
285
+ reasoningSeq += 1;
248
286
  controller.enqueue({
249
287
  type: "reasoning-start",
250
288
  id: reasoningId
@@ -256,23 +294,20 @@ var AgentChatTransport = class {
256
294
  delta: event.text
257
295
  });
258
296
  break;
259
- case "tool-input-start":
297
+ case "tool-input-start": {
260
298
  ensureStep();
261
299
  announced.add(event.id);
300
+ const metadata = metadataFor(event.id, event.toolKind, event.parentId);
262
301
  controller.enqueue({
263
302
  type: "tool-input-start",
264
303
  toolCallId: event.id,
265
304
  toolName: event.name,
266
- // `toolKind` is absent on older backends — omitted (not `undefined`-valued) so
267
- // `toolMetadata` itself is only present when there's something to say, letting a
268
- // UI gate approval affordances on `kind === 'action'` without hardcoding tool names.
269
- ...event.toolKind !== void 0 ? {
270
- toolMetadata: {
271
- toolKind: event.toolKind
272
- }
305
+ ...metadata !== void 0 ? {
306
+ toolMetadata: metadata
273
307
  } : {}
274
308
  });
275
309
  break;
310
+ }
276
311
  case "tool-input-delta":
277
312
  ensureStep();
278
313
  controller.enqueue({
@@ -281,21 +316,21 @@ var AgentChatTransport = class {
281
316
  inputTextDelta: event.delta
282
317
  });
283
318
  break;
284
- case "tool-input-available":
319
+ case "tool-input-available": {
285
320
  ensureStep();
286
321
  announced.add(event.id);
322
+ const metadata = metadataFor(event.id, event.toolKind, event.parentId);
287
323
  controller.enqueue({
288
324
  type: "tool-input-available",
289
325
  toolCallId: event.id,
290
326
  toolName: event.name,
291
327
  input: event.input,
292
- ...event.toolKind !== void 0 ? {
293
- toolMetadata: {
294
- toolKind: event.toolKind
295
- }
328
+ ...metadata !== void 0 ? {
329
+ toolMetadata: metadata
296
330
  } : {}
297
331
  });
298
332
  break;
333
+ }
299
334
  case "elicitation":
300
335
  ensureStep();
301
336
  if (!announced.has(event.id)) {
@@ -336,7 +371,39 @@ var AgentChatTransport = class {
336
371
  toolCallId: event.id
337
372
  });
338
373
  break;
374
+ case "approval-requested":
375
+ ensureStep();
376
+ forwardAsData(event, false);
377
+ if (announced.has(event.id)) {
378
+ controller.enqueue({
379
+ type: "tool-approval-request",
380
+ approvalId: event.id,
381
+ toolCallId: event.id
382
+ });
383
+ }
384
+ break;
385
+ case "ui":
386
+ ensureStep();
387
+ closeRuns();
388
+ controller.enqueue({
389
+ type: "data-ui",
390
+ id: event.id,
391
+ data: {
392
+ id: event.id,
393
+ component: event.component,
394
+ props: event.props,
395
+ ...event.version !== void 0 ? {
396
+ version: event.version
397
+ } : {}
398
+ }
399
+ });
400
+ break;
401
+ case "title":
402
+ case "cancelled":
403
+ forwardAsData(event, true);
404
+ break;
339
405
  default:
406
+ forwardAsData(event, false);
340
407
  break;
341
408
  }
342
409
  }
@@ -832,7 +899,7 @@ import React3 from "react";
832
899
  import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
833
900
 
834
901
  // src/transcript/model.ts
835
- import { getToolName, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
902
+ import { getToolName, isDataUIPart, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
836
903
  function buildTranscriptBlocks(message, options) {
837
904
  const blocks = [];
838
905
  let toolBuffer = [];
@@ -844,15 +911,18 @@ function buildTranscriptBlocks(message, options) {
844
911
  let reasoningCounter = 0;
845
912
  let sourcesCounter = 0;
846
913
  let elicitationCounter = 0;
914
+ const approvals = readApprovals(message.parts ?? []);
847
915
  function flushTools() {
848
916
  if (toolBuffer.length === 0) {
849
917
  return;
850
918
  }
919
+ const calls = toolBuffer.map((part) => buildToolCall(part, options.approval, approvals.get(part.toolCallId) ?? null));
851
920
  blocks.push({
852
921
  kind: "tools",
853
922
  key: `${message.id}-tools-${toolCounter++}`,
854
923
  parts: toolBuffer,
855
- calls: toolBuffer.map((part) => buildToolCall(part, options.approval))
924
+ calls,
925
+ roots: nestToolCalls(calls)
856
926
  });
857
927
  toolBuffer = [];
858
928
  }
@@ -915,7 +985,21 @@ function buildTranscriptBlocks(message, options) {
915
985
  fileBuffer.push(part);
916
986
  continue;
917
987
  }
988
+ if (part.type === APPROVAL_PART) {
989
+ continue;
990
+ }
918
991
  flushAll();
992
+ if (isDataUIPart(part)) {
993
+ const ui = part.type === UI_PART ? readUiComponent(part) : null;
994
+ if (ui !== null) {
995
+ blocks.push({
996
+ kind: "ui",
997
+ key: `${message.id}-ui-${ui.id}`,
998
+ ...ui
999
+ });
1000
+ }
1001
+ continue;
1002
+ }
919
1003
  if (isTextUIPart(part)) {
920
1004
  blocks.push({
921
1005
  kind: "text",
@@ -999,7 +1083,42 @@ function isAwaitingApproval(part) {
999
1083
  return toolKind(part) === "action" && readElicitationRequest(part) === null;
1000
1084
  }
1001
1085
  __name(isAwaitingApproval, "isAwaitingApproval");
1002
- function buildToolCall(part, options) {
1086
+ function toolParentId(part) {
1087
+ const metadata = part.toolMetadata;
1088
+ return isRecord(metadata) && typeof metadata.parentId === "string" ? metadata.parentId : null;
1089
+ }
1090
+ __name(toolParentId, "toolParentId");
1091
+ function nestToolCalls(calls) {
1092
+ const byId = new Map(calls.map((call) => [
1093
+ call.toolCallId,
1094
+ call
1095
+ ]));
1096
+ const roots = [];
1097
+ for (const call of calls) {
1098
+ const parent = call.parentId !== null ? byId.get(call.parentId) : void 0;
1099
+ if (parent === void 0 || isAncestor(call, parent, byId)) {
1100
+ roots.push(call);
1101
+ continue;
1102
+ }
1103
+ parent.children.push(call);
1104
+ }
1105
+ return roots;
1106
+ }
1107
+ __name(nestToolCalls, "nestToolCalls");
1108
+ function isAncestor(call, candidate, byId) {
1109
+ const seen = /* @__PURE__ */ new Set();
1110
+ let current = candidate;
1111
+ while (current !== void 0 && !seen.has(current.toolCallId)) {
1112
+ if (current.toolCallId === call.toolCallId) {
1113
+ return true;
1114
+ }
1115
+ seen.add(current.toolCallId);
1116
+ current = current.parentId !== null ? byId.get(current.parentId) : void 0;
1117
+ }
1118
+ return false;
1119
+ }
1120
+ __name(isAncestor, "isAncestor");
1121
+ function buildToolCall(part, options, approval) {
1003
1122
  const toolCallId = part.toolCallId;
1004
1123
  const awaiting = isAwaitingApproval(part);
1005
1124
  const sending = awaiting ? options?.submitting(toolCallId) ?? null : null;
@@ -1007,6 +1126,10 @@ function buildToolCall(part, options) {
1007
1126
  part,
1008
1127
  toolCallId,
1009
1128
  name: getToolName(part),
1129
+ toolKind: toolKind(part) ?? null,
1130
+ parentId: toolParentId(part),
1131
+ children: [],
1132
+ approval,
1010
1133
  isAwaitingApproval: awaiting,
1011
1134
  approve: {
1012
1135
  available: awaiting && options?.canApprove === true,
@@ -1137,6 +1260,44 @@ function buildElicitationBlock(key, part, request, options) {
1137
1260
  };
1138
1261
  }
1139
1262
  __name(buildElicitationBlock, "buildElicitationBlock");
1263
+ var UI_PART = "data-ui";
1264
+ var APPROVAL_PART = "data-approval-requested";
1265
+ function readApprovals(parts) {
1266
+ const out = /* @__PURE__ */ new Map();
1267
+ for (const part of parts) {
1268
+ if (part.type !== APPROVAL_PART) {
1269
+ continue;
1270
+ }
1271
+ const data = part.data;
1272
+ if (!isRecord(data) || typeof data.id !== "string" || typeof data.approver !== "string") {
1273
+ continue;
1274
+ }
1275
+ out.set(data.id, {
1276
+ approver: data.approver,
1277
+ expiresAt: typeof data.expiresAt === "string" ? data.expiresAt : null,
1278
+ reason: typeof data.reason === "string" ? data.reason : null
1279
+ });
1280
+ }
1281
+ return out;
1282
+ }
1283
+ __name(readApprovals, "readApprovals");
1284
+ function readUiComponent(part) {
1285
+ const data = part.data;
1286
+ if (!isRecord(data) || typeof data.component !== "string") {
1287
+ return null;
1288
+ }
1289
+ const id = typeof data.id === "string" ? data.id : part.id;
1290
+ if (id === void 0) {
1291
+ return null;
1292
+ }
1293
+ return {
1294
+ id,
1295
+ component: data.component,
1296
+ props: isRecord(data.props) ? data.props : {},
1297
+ version: typeof data.version === "number" ? data.version : null
1298
+ };
1299
+ }
1300
+ __name(readUiComponent, "readUiComponent");
1140
1301
  function isRecord(value) {
1141
1302
  return typeof value === "object" && value !== null;
1142
1303
  }
@@ -1830,6 +1991,14 @@ function renderBlocks(item, slots) {
1830
1991
  }
1831
1992
  if (block.kind === "tools") {
1832
1993
  nodes.push(...renderToolBlock(item, block, slots));
1994
+ continue;
1995
+ }
1996
+ if (block.kind === "ui" && slots.renderUi) {
1997
+ nodes.push(/* @__PURE__ */ React3.createElement("div", {
1998
+ key: block.key,
1999
+ "data-slot": "ui",
2000
+ "data-component": block.component
2001
+ }, slots.renderUi(block)));
1833
2002
  }
1834
2003
  }
1835
2004
  return nodes;
@@ -1937,7 +2106,7 @@ function joinClasses(...entries) {
1937
2106
  __name(joinClasses, "joinClasses");
1938
2107
 
1939
2108
  // src/components/message-list.tsx
1940
- function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, reasoningLabel, getMeta, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
2109
+ function MessageList({ messages, status, renderToolPart, renderToolGroup, renderText, renderReasoning, renderFiles, renderUi, reasoningLabel, getMeta, onFork, editable, onEditSubmit, regeneratable, onRegenerate, getUsage, getCreatedAt, emptyState, typingIndicator, followUps, onFollowUpClick, error, onRetry, classNames }) {
1941
2110
  const transcript = useChatTranscript({
1942
2111
  messages,
1943
2112
  status,
@@ -1987,6 +2156,9 @@ function MessageList({ messages, status, renderToolPart, renderToolGroup, render
1987
2156
  ...renderFiles ? {
1988
2157
  renderFiles
1989
2158
  } : {},
2159
+ ...renderUi ? {
2160
+ renderUi
2161
+ } : {},
1990
2162
  ...reasoningLabel !== void 0 ? {
1991
2163
  reasoningLabel
1992
2164
  } : {},
@@ -2641,6 +2813,13 @@ function useAgentChat(options) {
2641
2813
  ...options.initialMessages !== void 0 ? {
2642
2814
  messages: options.initialMessages
2643
2815
  } : {},
2816
+ onData: /* @__PURE__ */ __name((part) => {
2817
+ latest.current.onData?.(part);
2818
+ if (part.type === "data-title") {
2819
+ const title = part.data?.title;
2820
+ if (typeof title === "string") latest.current.onTitle?.(title);
2821
+ }
2822
+ }, "onData"),
2644
2823
  onFinish: /* @__PURE__ */ __name(({ isError }) => {
2645
2824
  latest.current.onFinish?.();
2646
2825
  if (latest.current.background === true) {