@assistant-ui/react-ag-ui 0.0.59 → 0.0.61

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.
Files changed (45) hide show
  1. package/dist/agUiExtras.d.ts +2 -5
  2. package/dist/agUiExtras.d.ts.map +1 -1
  3. package/dist/hooks.d.ts +9 -12
  4. package/dist/hooks.d.ts.map +1 -1
  5. package/dist/index.d.ts +7 -5
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/runtime/AgUiThreadRuntimeCore.d.ts +153 -146
  8. package/dist/runtime/AgUiThreadRuntimeCore.d.ts.map +1 -1
  9. package/dist/runtime/AgUiThreadRuntimeCore.js +146 -26
  10. package/dist/runtime/AgUiThreadRuntimeCore.js.map +1 -1
  11. package/dist/runtime/adapter/conversions.d.ts +54 -56
  12. package/dist/runtime/adapter/conversions.d.ts.map +1 -1
  13. package/dist/runtime/adapter/conversions.js +164 -23
  14. package/dist/runtime/adapter/conversions.js.map +1 -1
  15. package/dist/runtime/adapter/run-aggregator.d.ts +98 -98
  16. package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
  17. package/dist/runtime/adapter/run-aggregator.js +35 -2
  18. package/dist/runtime/adapter/run-aggregator.js.map +1 -1
  19. package/dist/runtime/adapter/subscriber.d.ts +109 -111
  20. package/dist/runtime/adapter/subscriber.d.ts.map +1 -1
  21. package/dist/runtime/adapter/tool-approval.d.ts +7 -9
  22. package/dist/runtime/adapter/tool-approval.d.ts.map +1 -1
  23. package/dist/runtime/event-parser.d.ts +5 -8
  24. package/dist/runtime/event-parser.d.ts.map +1 -1
  25. package/dist/runtime/interrupt-internals.d.ts +4 -7
  26. package/dist/runtime/interrupt-internals.d.ts.map +1 -1
  27. package/dist/runtime/logger.d.ts +4 -7
  28. package/dist/runtime/logger.d.ts.map +1 -1
  29. package/dist/runtime/mcp-tool-result.d.ts +7 -10
  30. package/dist/runtime/mcp-tool-result.d.ts.map +1 -1
  31. package/dist/runtime/types.d.ts +248 -250
  32. package/dist/runtime/types.d.ts.map +1 -1
  33. package/dist/useAgUiRuntime.d.ts +15 -18
  34. package/dist/useAgUiRuntime.d.ts.map +1 -1
  35. package/dist/useAgUiRuntime.js +9 -2
  36. package/dist/useAgUiRuntime.js.map +1 -1
  37. package/package.json +12 -11
  38. package/src/runtime/AgUiThreadRuntimeCore.ts +246 -30
  39. package/src/runtime/adapter/conversions.content.test.ts +56 -0
  40. package/src/runtime/adapter/conversions.ts +278 -36
  41. package/src/runtime/adapter/run-aggregator.ts +53 -2
  42. package/src/useAgUiRuntime.history.test.tsx +93 -2
  43. package/src/useAgUiRuntime.thread-switch.test.tsx +84 -4
  44. package/src/useAgUiRuntime.ts +12 -3
  45. package/src/useAgUiRuntime.voice.test.tsx +402 -0
@@ -1,69 +1,67 @@
1
- import { ThreadMessageLike } from "@assistant-ui/core";
2
- import { InputContent, RunAgentParameters } from "@ag-ui/client";
3
- import { Tool } from "assistant-stream";
4
- //#region src/runtime/adapter/conversions.d.ts
1
+ import type { InputContent, RunAgentParameters } from "@ag-ui/client";
2
+ import type { ThreadMessageLike as CoreThreadMessageLike } from "@assistant-ui/core";
3
+ import { type Tool } from "assistant-stream";
4
+ export type { InputContent };
5
5
  type AttachmentLike = {
6
- name?: string | undefined;
7
- contentType?: string | undefined;
8
- content?: readonly unknown[] | undefined;
6
+ name?: string | undefined;
7
+ contentType?: string | undefined;
8
+ content?: readonly unknown[] | undefined;
9
9
  };
10
- type ThreadMessageLike$1 = {
11
- id?: string;
12
- role: string;
13
- content: unknown;
14
- metadata?: unknown;
15
- name?: string;
16
- toolCallId?: string;
17
- error?: string;
18
- attachments?: readonly AttachmentLike[];
10
+ type ThreadMessageLike = {
11
+ id?: string | undefined;
12
+ role: string;
13
+ content: unknown;
14
+ metadata?: unknown;
15
+ name?: string | undefined;
16
+ toolCallId?: string | undefined;
17
+ error?: string | undefined;
18
+ attachments?: readonly AttachmentLike[] | undefined;
19
19
  };
20
20
  type AgUiToolCall = {
21
- id: string;
22
- type: "function";
23
- function: {
24
- name: string;
25
- arguments: string;
26
- };
21
+ id: string;
22
+ type: "function";
23
+ function: {
24
+ name: string;
25
+ arguments: string;
26
+ };
27
27
  };
28
- type AgUiMessage = {
29
- id: string;
30
- role: "user";
31
- content: string | InputContent[];
32
- name?: string;
28
+ export type AgUiMessage = {
29
+ id: string;
30
+ role: "user";
31
+ content: string | InputContent[];
32
+ name?: string;
33
33
  } | {
34
- id: string;
35
- role: "assistant";
36
- content: string;
37
- name?: string;
38
- toolCalls?: AgUiToolCall[];
34
+ id: string;
35
+ role: "assistant";
36
+ content: string;
37
+ name?: string;
38
+ toolCalls?: AgUiToolCall[];
39
39
  } | {
40
- id: string;
41
- role: "system" | "developer";
42
- content: string;
43
- name?: string;
40
+ id: string;
41
+ role: "system" | "developer";
42
+ content: string;
43
+ name?: string;
44
44
  } | {
45
- id: string;
46
- role: "reasoning";
47
- content: string;
48
- encryptedValue?: string;
45
+ id: string;
46
+ role: "reasoning";
47
+ content: string;
48
+ encryptedValue?: string;
49
49
  } | {
50
- id: string;
51
- role: "tool";
52
- content: string;
53
- toolCallId: string;
54
- error?: string;
50
+ id: string;
51
+ role: "tool";
52
+ content: string;
53
+ toolCallId: string;
54
+ error?: string;
55
55
  };
56
- type FromAgUiMessagesOptions = {
57
- /**
58
- * Whether to convert `reasoning` messages into visible reasoning parts.
59
- * Matches the `showThinking` option of `useAgUiRuntime`. Defaults to `true`.
60
- */
61
- showThinking?: boolean;
56
+ export type FromAgUiMessagesOptions = {
57
+ /**
58
+ * Whether to convert `reasoning` messages into visible reasoning parts.
59
+ * Matches the `showThinking` option of `useAgUiRuntime`. Defaults to `true`.
60
+ */
61
+ showThinking?: boolean;
62
62
  };
63
- declare function fromAgUiMessages(messages: readonly unknown[], options?: FromAgUiMessagesOptions): ThreadMessageLike[];
64
- declare function toAgUiMessages(messages: readonly ThreadMessageLike$1[]): AgUiMessage[];
63
+ export declare function fromAgUiMessages(messages: readonly unknown[], options?: FromAgUiMessagesOptions): CoreThreadMessageLike[];
64
+ export declare function toAgUiMessages(messages: readonly ThreadMessageLike[]): AgUiMessage[];
65
65
  type AgUiTool = NonNullable<RunAgentParameters["tools"]>[number];
66
- declare function toAgUiTools(tools: Record<string, Tool> | undefined): AgUiTool[];
67
- //#endregion
68
- export { AgUiMessage, FromAgUiMessagesOptions, type InputContent, fromAgUiMessages, toAgUiMessages, toAgUiTools };
66
+ export declare function toAgUiTools(tools: Record<string, Tool> | undefined): AgUiTool[];
69
67
  //# sourceMappingURL=conversions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"conversions.d.ts","names":[],"sources":["../../../src/runtime/adapter/conversions.ts"],"mappings":";;;;KAuCK;EACH;EACA;EACA;;KAGG;EACH;EACA;EACA;EACA;EACA;EACA;EACA;EACA,uBAAuB;;KAKpB;EACH;EACA;EACA;IAAY;IAAc;;;KAGhB;EAEN;EACA;EACA,kBAAkB;EAClB;;EAGA;EACA;EACA;EACA;EACA,YAAY;;EAGZ;EACA;EACA;EACA;;EAGA;EACA;EACA;EACA;;EAGA;EACA;EACA;EACA;EACA;;KAgoBM;;;;;EAKV;;iBAGc,iBACd,8BACA,UAAU,0BACT;iBAuaa,eACd,mBAAmB,wBAClB;KAsFE,WAAW,YAAY;iBAEZ,YACd,OAAO,eAAe,oBACrB"}
1
+ {"version":3,"file":"conversions.d.ts","sourceRoot":"","sources":["../../../src/runtime/adapter/conversions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEtE,OAAO,KAAK,EACV,iBAAiB,IAAI,qBAAqB,EAM3C,MAAM,oBAAoB,CAAC;AAM5B,OAAO,EAAE,KAAK,IAAI,EAAqB,MAAM,kBAAkB,CAAC;AAuBhE,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B,KAAK,cAAc,GAAG;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,CAAC;CAC1C,CAAC;AAEF,KAAK,iBAAiB,GAAG;IACvB,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,cAAc,EAAE,GAAG,SAAS,CAAC;CACrD,CAAC;AAIF,KAAK,YAAY,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,WAAW,GACnB;IACE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GACD;IACE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;CAC5B,GACD;IACE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GACD;IACE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GACD;IACE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA8vBN,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,OAAO,CAAC,EAAE,uBAAuB,GAChC,qBAAqB,EAAE,CAsXzB;AAiKD,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,iBAAiB,EAAE,GACrC,WAAW,EAAE,CAoFf;AAED,KAAK,QAAQ,GAAG,WAAW,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,SAAS,GACtC,QAAQ,EAAE,CASZ"}
@@ -4,7 +4,7 @@ import { projectAgUiToolApprovals } from "./tool-approval.js";
4
4
  import { AG_UI_METADATA_NAMESPACE } from "./run-aggregator.js";
5
5
  import { generateId } from "@assistant-ui/core";
6
6
  import { getAutoStatus, parseDataUrl, resolveFilePartSource } from "@assistant-ui/core/internal";
7
- import { applyA2uiOperations, convertSurfaceToUISpec } from "@assistant-ui/react-generative-ui/a2ui";
7
+ import { applyA2uiOperations, convertSurfaceToUISpec, surfaceToOperations } from "@assistant-ui/react-generative-ui/a2ui";
8
8
  import { toToolsJSONSchema } from "assistant-stream";
9
9
  //#region src/runtime/adapter/conversions.ts
10
10
  const isObject = (value) => typeof value === "object" && value !== null;
@@ -79,7 +79,7 @@ function buildInputSource(value, declaredMimeType, sourceType) {
79
79
  const source = resolveFilePartSource({
80
80
  data: value,
81
81
  mimeType: declaredMimeType ?? "application/octet-stream",
82
- sourceType
82
+ sourceType: /^data:/i.test(value) && !parseDataUrl(value) ? "url" : sourceType
83
83
  });
84
84
  if (source.kind === "url") return declaredMimeType !== void 0 ? {
85
85
  type: "url",
@@ -424,7 +424,8 @@ function attachA2uiSurfaces(message, state) {
424
424
  toolName: "present",
425
425
  args: spec,
426
426
  argsText: JSON.stringify(spec),
427
- result: {}
427
+ result: {},
428
+ artifact: { a2ui: surfaceToOperations(surface) }
428
429
  });
429
430
  }
430
431
  const preserved = (Array.isArray(message.content) ? message.content : []).filter((part) => !(isObject(part) && part.type === "tool-call" && typeof part.toolCallId === "string" && part.toolCallId.startsWith("a2ui:")));
@@ -433,17 +434,100 @@ function attachA2uiSurfaces(message, state) {
433
434
  content: [...preserved, ...a2uiParts]
434
435
  };
435
436
  }
437
+ function isSyntheticToolCallContainer(message) {
438
+ if (message.role !== "assistant" || !Array.isArray(message.content)) return false;
439
+ let carriesOwnId = false;
440
+ for (const part of message.content) {
441
+ if (!isObject(part)) continue;
442
+ if (part.type === "text") return false;
443
+ if (part.type === "tool-call" && getString(part, "toolCallId") === message.id) carriesOwnId = true;
444
+ }
445
+ return carriesOwnId;
446
+ }
447
+ function carriesPart(message, type) {
448
+ return Array.isArray(message.content) && message.content.some((part) => isObject(part) && part.type === type);
449
+ }
450
+ function carriesText(message) {
451
+ return carriesPart(message, "text");
452
+ }
453
+ function opensTurn(message) {
454
+ return message.role === "assistant" && (carriesText(message) || carriesPart(message, "tool-call"));
455
+ }
456
+ function answersToolCallContainer(message) {
457
+ if (message.role !== "assistant" || !Array.isArray(message.content)) return false;
458
+ if (message.content.length === 0) return false;
459
+ return !message.content.some((part) => isObject(part) && part.type === "tool-call");
460
+ }
461
+ function readCustomMetadata(metadata) {
462
+ if (!isObject(metadata) || !isObject(metadata.custom)) return {};
463
+ return metadata.custom;
464
+ }
465
+ function readNamespacedMetadata(metadata) {
466
+ const namespaced = readCustomMetadata(metadata)[AG_UI_METADATA_NAMESPACE];
467
+ return isObject(namespaced) ? namespaced : {};
468
+ }
469
+ function foldTurnRecords(previous, message) {
470
+ const interrupts = [...readPersistedInterrupts(previous.metadata) ?? [], ...readPersistedInterrupts(message.metadata) ?? []];
471
+ const trailsCalls = carriesPart(previous, "tool-call");
472
+ const opaqueReasoning = [...readOpaqueReasoning(previous.metadata), ...readOpaqueReasoning(message.metadata).map((entry) => trailsCalls ? {
473
+ ...entry,
474
+ after: true
475
+ } : entry)];
476
+ const namespaced = {
477
+ ...readNamespacedMetadata(previous.metadata),
478
+ ...readNamespacedMetadata(message.metadata),
479
+ ...interrupts.length > 0 ? { interrupts } : {},
480
+ ...opaqueReasoning.length > 0 ? { opaqueReasoning } : {}
481
+ };
482
+ const custom = {
483
+ ...readCustomMetadata(previous.metadata),
484
+ ...readCustomMetadata(message.metadata),
485
+ ...Object.keys(namespaced).length > 0 ? { [AG_UI_METADATA_NAMESPACE]: namespaced } : {}
486
+ };
487
+ const metadata = {
488
+ ...isObject(previous.metadata) ? previous.metadata : {},
489
+ ...isObject(message.metadata) ? message.metadata : {},
490
+ ...Object.keys(custom).length > 0 ? { custom } : {}
491
+ };
492
+ return {
493
+ ...previous,
494
+ ...message,
495
+ id: carriesText(message) ? message.id : previous.id,
496
+ content: [...Array.isArray(previous.content) ? previous.content : [], ...Array.isArray(message.content) ? message.content : []],
497
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
498
+ };
499
+ }
436
500
  function fromAgUiMessages(messages, options) {
437
501
  const showThinking = options?.showThinking ?? true;
438
502
  const converted = [];
439
503
  const a2uiBuckets = /* @__PURE__ */ new Map();
440
504
  const a2uiBucketOwnerIndices = /* @__PURE__ */ new Map();
505
+ const activityParts = /* @__PURE__ */ new Map();
441
506
  const opaqueReasoning = [];
507
+ const pendingReasoning = [];
508
+ const flushPendingReasoning = () => {
509
+ for (const { id, part } of pendingReasoning) converted.push({
510
+ id: id ?? generateId(),
511
+ role: "assistant",
512
+ content: [part]
513
+ });
514
+ pendingReasoning.length = 0;
515
+ };
516
+ const withPendingReasoning = (message) => {
517
+ if (pendingReasoning.length === 0) return message;
518
+ const parts = pendingReasoning.map(({ part }) => part);
519
+ pendingReasoning.length = 0;
520
+ return {
521
+ ...message,
522
+ content: [...parts, ...Array.isArray(message.content) ? message.content : []]
523
+ };
524
+ };
442
525
  for (const rawMessage of messages) {
443
526
  if (!isObject(rawMessage)) continue;
444
527
  const role = getString(rawMessage, "role");
445
528
  if (!role) continue;
446
529
  if (role === "tool") {
530
+ flushPendingReasoning();
447
531
  const toolCallId = getToolCallId(rawMessage) ?? `tool-${generateId()}`;
448
532
  const toolMessageId = getString(rawMessage, "id");
449
533
  const modelContent = extractText(rawMessage.content);
@@ -503,9 +587,9 @@ function fromAgUiMessages(messages, options) {
503
587
  continue;
504
588
  }
505
589
  if (role === "activity") {
506
- if (getString(rawMessage, "activityType") !== "a2ui-surface") continue;
507
- const operations = (isObject(rawMessage.content) ? rawMessage.content : null)?.["a2ui_operations"];
508
- if (!Array.isArray(operations)) continue;
590
+ const activityType = getString(rawMessage, "activityType");
591
+ if (activityType === void 0 || activityType === "mcp-apps") continue;
592
+ flushPendingReasoning();
509
593
  let ownerIndex = -1;
510
594
  for (let i = converted.length - 1; i >= 0; i--) {
511
595
  const candidate = converted[i];
@@ -516,6 +600,42 @@ function fromAgUiMessages(messages, options) {
516
600
  }
517
601
  if (ownerIndex === -1) continue;
518
602
  const owner = converted[ownerIndex];
603
+ if (activityType !== "a2ui-surface") {
604
+ const bucketKey = getString(rawMessage, "id") ?? `agui-activity:${activityType}`;
605
+ const part = {
606
+ type: "data",
607
+ name: `agui-activity/${activityType}`,
608
+ data: rawMessage.content
609
+ };
610
+ const existing = activityParts.get(bucketKey);
611
+ const existingOwner = existing && converted[existing.ownerIndex];
612
+ const existingIndex = existingOwner && Array.isArray(existingOwner.content) ? existingOwner.content.indexOf(existing.part) : -1;
613
+ if (existing && existingOwner && existingIndex !== -1) {
614
+ const content = [...existingOwner.content];
615
+ content[existingIndex] = part;
616
+ converted[existing.ownerIndex] = {
617
+ ...existingOwner,
618
+ content
619
+ };
620
+ activityParts.set(bucketKey, {
621
+ ownerIndex: existing.ownerIndex,
622
+ part
623
+ });
624
+ continue;
625
+ }
626
+ const content = Array.isArray(owner.content) ? owner.content : [];
627
+ activityParts.set(bucketKey, {
628
+ ownerIndex,
629
+ part
630
+ });
631
+ converted[ownerIndex] = {
632
+ ...owner,
633
+ content: [...content, part]
634
+ };
635
+ continue;
636
+ }
637
+ const operations = (isObject(rawMessage.content) ? rawMessage.content : null)?.["a2ui_operations"];
638
+ if (!Array.isArray(operations)) continue;
519
639
  const bucketKey = getString(rawMessage, "id") ?? "a2ui:anonymous";
520
640
  const { state } = applyA2uiOperations(/* @__PURE__ */ new Map(), operations);
521
641
  a2uiBuckets.delete(bucketKey);
@@ -531,7 +651,7 @@ function fromAgUiMessages(messages, options) {
531
651
  continue;
532
652
  }
533
653
  if (role === "assistant") {
534
- converted.push(toAssistantSnapshotMessage(rawMessage));
654
+ converted.push(withPendingReasoning(toAssistantSnapshotMessage(rawMessage)));
535
655
  continue;
536
656
  }
537
657
  if (role === "reasoning") {
@@ -539,11 +659,14 @@ function fromAgUiMessages(messages, options) {
539
659
  const encryptedValue = getString(rawMessage, "encryptedValue");
540
660
  if (text.trim().length === 0) {
541
661
  const opaqueId = getString(rawMessage, "id");
542
- if (opaqueId?.trim() && encryptedValue?.trim()) opaqueReasoning.push({
543
- id: opaqueId,
544
- encryptedValue,
545
- anchor: converted.length
546
- });
662
+ if (opaqueId?.trim() && encryptedValue?.trim()) {
663
+ flushPendingReasoning();
664
+ opaqueReasoning.push({
665
+ id: opaqueId,
666
+ encryptedValue,
667
+ anchor: converted.length
668
+ });
669
+ }
547
670
  continue;
548
671
  }
549
672
  if (!showThinking) {
@@ -555,19 +678,28 @@ function fromAgUiMessages(messages, options) {
555
678
  });
556
679
  continue;
557
680
  }
558
- converted.push({
559
- id: getString(rawMessage, "id") ?? generateId(),
560
- role: "assistant",
561
- content: [{
681
+ const rawReasoningId = getString(rawMessage, "id");
682
+ const reasoningId = rawReasoningId?.trim() ? rawReasoningId : void 0;
683
+ const meta = {
684
+ ...reasoningId !== void 0 ? { reasoningId } : {},
685
+ ...encryptedValue !== void 0 ? { encryptedValue } : {}
686
+ };
687
+ pendingReasoning.push({
688
+ id: reasoningId,
689
+ part: {
562
690
  type: "reasoning",
563
691
  text,
564
- ...encryptedValue !== void 0 ? { providerMetadata: { [AG_UI_METADATA_NAMESPACE]: { encryptedValue } } } : {}
565
- }]
692
+ ...Object.keys(meta).length > 0 ? { providerMetadata: { [AG_UI_METADATA_NAMESPACE]: meta } } : {}
693
+ }
566
694
  });
567
695
  continue;
568
696
  }
569
- if (role === "user" || role === "system" || role === "developer") converted.push(toUserOrSystemSnapshotMessage(role, rawMessage));
697
+ if (role === "user" || role === "system" || role === "developer") {
698
+ flushPendingReasoning();
699
+ converted.push(toUserOrSystemSnapshotMessage(role, rawMessage));
700
+ }
570
701
  }
702
+ flushPendingReasoning();
571
703
  for (const { anchor, ...entry } of opaqueReasoning) {
572
704
  const trailing = anchor >= converted.length;
573
705
  const index = trailing ? converted.length - 1 : anchor;
@@ -578,17 +710,26 @@ function fromAgUiMessages(messages, options) {
578
710
  after: true
579
711
  }] : [entry]]);
580
712
  }
581
- for (let i = 0; i < converted.length; i++) {
582
- const message = converted[i];
713
+ const folded = [];
714
+ for (const message of converted) {
715
+ const previous = folded[folded.length - 1];
716
+ if (previous !== void 0 && (isSyntheticToolCallContainer(message) ? opensTurn(previous) : isSyntheticToolCallContainer(previous) && answersToolCallContainer(message))) {
717
+ folded[folded.length - 1] = foldTurnRecords(previous, message);
718
+ continue;
719
+ }
720
+ folded.push(message);
721
+ }
722
+ for (let i = 0; i < folded.length; i++) {
723
+ const message = folded[i];
583
724
  if (message.role !== "assistant") continue;
584
725
  const hasInterrupt = readPersistedInterrupts(message.metadata) !== void 0;
585
726
  const hasPendingToolCall = Array.isArray(message.content) && message.content.some((part) => isObject(part) && part.type === "tool-call" && part.result === void 0);
586
- if (hasInterrupt || hasPendingToolCall) converted[i] = {
727
+ if (hasInterrupt || hasPendingToolCall) folded[i] = {
587
728
  ...message,
588
729
  status: getAutoStatus(false, false, hasInterrupt, hasPendingToolCall, void 0)
589
730
  };
590
731
  }
591
- return converted;
732
+ return folded;
592
733
  }
593
734
  function convertAssistantMessage(message, converted) {
594
735
  const content = extractText(message.content);