@athenaintel/react 0.39.0 → 0.40.1

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 (126) hide show
  1. package/README.md +39 -3
  2. package/dist/agent-runtime/src/errors.cjs +8 -0
  3. package/dist/agent-runtime/src/errors.cjs.map +1 -1
  4. package/dist/agent-runtime/src/errors.js +8 -0
  5. package/dist/agent-runtime/src/errors.js.map +1 -1
  6. package/dist/agent-runtime/src/messages.cjs +76 -16
  7. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  8. package/dist/agent-runtime/src/messages.js +76 -16
  9. package/dist/agent-runtime/src/messages.js.map +1 -1
  10. package/dist/agent-runtime/src/tool-metadata.cjs +15 -0
  11. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  12. package/dist/agent-runtime/src/tool-metadata.js +15 -0
  13. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  14. package/dist/assets/use-asset-content.cjs +127 -0
  15. package/dist/assets/use-asset-content.cjs.map +1 -0
  16. package/dist/assets/use-asset-content.d.ts +60 -0
  17. package/dist/assets/use-asset-content.js +127 -0
  18. package/dist/assets/use-asset-content.js.map +1 -0
  19. package/dist/chat/AthenaChat.cjs +24 -10
  20. package/dist/chat/AthenaChat.cjs.map +1 -1
  21. package/dist/chat/AthenaChat.js +25 -11
  22. package/dist/chat/AthenaChat.js.map +1 -1
  23. package/dist/chat/run-activity.cjs +29 -10
  24. package/dist/chat/run-activity.cjs.map +1 -1
  25. package/dist/chat/run-activity.d.ts +30 -4
  26. package/dist/chat/run-activity.js +29 -10
  27. package/dist/chat/run-activity.js.map +1 -1
  28. package/dist/chat/super-grouping/CollapsibleGroup.cjs +16 -8
  29. package/dist/chat/super-grouping/CollapsibleGroup.cjs.map +1 -1
  30. package/dist/chat/super-grouping/CollapsibleGroup.d.ts +26 -1
  31. package/dist/chat/super-grouping/CollapsibleGroup.js +16 -8
  32. package/dist/chat/super-grouping/CollapsibleGroup.js.map +1 -1
  33. package/dist/chat/super-grouping/GroupedMessageParts.cjs +59 -0
  34. package/dist/chat/super-grouping/GroupedMessageParts.cjs.map +1 -0
  35. package/dist/chat/super-grouping/GroupedMessageParts.d.ts +25 -0
  36. package/dist/chat/super-grouping/GroupedMessageParts.js +59 -0
  37. package/dist/chat/super-grouping/GroupedMessageParts.js.map +1 -0
  38. package/dist/chat/super-grouping/SuperGroupingCard.cjs +9 -5
  39. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  40. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +2 -1
  41. package/dist/chat/super-grouping/SuperGroupingCard.js +13 -9
  42. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  43. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +10 -6
  44. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  45. package/dist/chat/super-grouping/SuperGroupingTranscript.js +14 -10
  46. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  47. package/dist/chat/super-grouping/grouped-part-keys.cjs +104 -0
  48. package/dist/chat/super-grouping/grouped-part-keys.cjs.map +1 -0
  49. package/dist/chat/super-grouping/grouped-part-keys.d.ts +37 -0
  50. package/dist/chat/super-grouping/grouped-part-keys.js +104 -0
  51. package/dist/chat/super-grouping/grouped-part-keys.js.map +1 -0
  52. package/dist/chat/super-grouping/index.cjs +2 -0
  53. package/dist/chat/super-grouping/index.cjs.map +1 -1
  54. package/dist/chat/super-grouping/index.d.ts +3 -1
  55. package/dist/chat/super-grouping/index.js +2 -0
  56. package/dist/chat/super-grouping/index.js.map +1 -1
  57. package/dist/chat-ui/composer/tiptap-composer.cjs +25 -19
  58. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  59. package/dist/chat-ui/composer/tiptap-composer.d.ts +5 -0
  60. package/dist/chat-ui/composer/tiptap-composer.js +26 -20
  61. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  62. package/dist/composer/clipboard.cjs +16 -0
  63. package/dist/composer/clipboard.cjs.map +1 -1
  64. package/dist/composer/clipboard.d.ts +9 -0
  65. package/dist/composer/clipboard.js +17 -1
  66. package/dist/composer/clipboard.js.map +1 -1
  67. package/dist/diagnostics/errors.cjs +6 -0
  68. package/dist/diagnostics/errors.cjs.map +1 -1
  69. package/dist/diagnostics/errors.d.ts +6 -0
  70. package/dist/diagnostics/errors.js +6 -0
  71. package/dist/diagnostics/errors.js.map +1 -1
  72. package/dist/index.cjs +23 -0
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.ts +8 -1
  75. package/dist/index.js +23 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/lib/use-document-active.cjs +40 -0
  78. package/dist/lib/use-document-active.cjs.map +1 -0
  79. package/dist/lib/use-document-active.d.ts +9 -0
  80. package/dist/lib/use-document-active.js +40 -0
  81. package/dist/lib/use-document-active.js.map +1 -0
  82. package/dist/react/package.json.cjs +1 -1
  83. package/dist/react/package.json.js +1 -1
  84. package/dist/runtime/statewire-lifecycle.cjs +8 -0
  85. package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
  86. package/dist/runtime/statewire-lifecycle.js +9 -1
  87. package/dist/runtime/statewire-lifecycle.js.map +1 -1
  88. package/dist/styles.css +1 -1
  89. package/dist/threads/Inbox.cjs +6 -2
  90. package/dist/threads/Inbox.cjs.map +1 -1
  91. package/dist/threads/Inbox.js +6 -2
  92. package/dist/threads/Inbox.js.map +1 -1
  93. package/dist/threads/mark-viewed-thread-read.cjs +97 -0
  94. package/dist/threads/mark-viewed-thread-read.cjs.map +1 -0
  95. package/dist/threads/mark-viewed-thread-read.d.ts +7 -0
  96. package/dist/threads/mark-viewed-thread-read.js +97 -0
  97. package/dist/threads/mark-viewed-thread-read.js.map +1 -0
  98. package/dist/tools/index.cjs +7 -0
  99. package/dist/tools/index.cjs.map +1 -1
  100. package/dist/tools/index.d.ts +2 -0
  101. package/dist/tools/index.js +7 -0
  102. package/dist/tools/index.js.map +1 -1
  103. package/dist/tools/inline-tools.cjs +10 -0
  104. package/dist/tools/inline-tools.cjs.map +1 -0
  105. package/dist/tools/inline-tools.d.ts +2 -0
  106. package/dist/tools/inline-tools.js +10 -0
  107. package/dist/tools/inline-tools.js.map +1 -0
  108. package/dist/tools/test-recording-report.cjs +175 -0
  109. package/dist/tools/test-recording-report.cjs.map +1 -0
  110. package/dist/tools/test-recording-report.d.ts +82 -0
  111. package/dist/tools/test-recording-report.js +175 -0
  112. package/dist/tools/test-recording-report.js.map +1 -0
  113. package/dist/tools/test-recording-tool-uis.cjs +498 -0
  114. package/dist/tools/test-recording-tool-uis.cjs.map +1 -0
  115. package/dist/tools/test-recording-tool-uis.d.ts +32 -0
  116. package/dist/tools/test-recording-tool-uis.js +498 -0
  117. package/dist/tools/test-recording-tool-uis.js.map +1 -0
  118. package/dist/tools/tool-summary.cjs +49 -1
  119. package/dist/tools/tool-summary.cjs.map +1 -1
  120. package/dist/tools/tool-summary.js +49 -1
  121. package/dist/tools/tool-summary.js.map +1 -1
  122. package/dist/tools/tool-uis.cjs +6 -1
  123. package/dist/tools/tool-uis.cjs.map +1 -1
  124. package/dist/tools/tool-uis.js +6 -1
  125. package/dist/tools/tool-uis.js.map +1 -1
  126. package/package.json +1 -1
@@ -13,11 +13,32 @@ export declare function runActivityKey(parts: ReadonlyArray<unknown>): string |
13
13
  /**
14
14
  * The present-tense verb for what the run is doing right now — the registry's
15
15
  * activity label for a tool call still awaiting its result ("Searching the
16
- * web") — or `null` when the tail is not an unsettled tool call.
16
+ * web") — or `null` when the tail is not a tool call in flight.
17
17
  */
18
18
  export declare function runActivityVerb(parts: ReadonlyArray<unknown>): string | null;
19
- /** Seconds without any streamed change before the row flips to a stall tone. */
19
+ /**
20
+ * Whether the run is waiting on a tool that is still running: the tail part
21
+ * is a tool call with no result yet, or only the streaming placeholder a
22
+ * long tool (Python execution, a task subagent) publishes while it works. A
23
+ * command, a build or a test suite streams nothing for minutes while it
24
+ * works, so a quiet stream then is work, not a stall.
25
+ */
26
+ export declare function isToolCallInFlight(parts: ReadonlyArray<unknown>): boolean;
27
+ /**
28
+ * Seconds without any streamed change before the row flips to a stall tone —
29
+ * while the run waits on the model, never while a tool call is in flight
30
+ * ({@link isRunActivityStalled}).
31
+ */
20
32
  export declare const RUN_ACTIVITY_STALL_SECONDS = 30;
33
+ /**
34
+ * Whether the run looks stalled: nothing has streamed for
35
+ * {@link RUN_ACTIVITY_STALL_SECONDS} while it waits on the model. Never while a
36
+ * tool call is in flight: the call's own row says it is running.
37
+ */
38
+ export declare function isRunActivityStalled({ seconds, toolCallInFlight, }: {
39
+ seconds: number;
40
+ toolCallInFlight: boolean;
41
+ }): boolean;
21
42
  /** Seconds before the row starts showing its elapsed counter. */
22
43
  export declare const RUN_ACTIVITY_COUNTER_SECONDS = 3;
23
44
  /** The default verb when nothing more specific is known. */
@@ -30,6 +51,11 @@ export declare const RUN_ACTIVITY_DEFAULT_VERB = "Thinking";
30
51
  export declare function runElapsedLabel(seconds: number): string | null;
31
52
  /**
32
53
  * The status line for `seconds` since the stream last moved: the verb with an
33
- * ellipsis, the elapsed counter once it is due, and the stall tone.
54
+ * ellipsis and the elapsed counter once it is due (`runElapsedLabel`), or the
55
+ * stall wording when the run looks `stalled` ({@link isRunActivityStalled}).
34
56
  */
35
- export declare function runActivityLabel(seconds: number, verb?: string): string;
57
+ export declare function runActivityLabel({ seconds, verb, stalled, }: {
58
+ seconds: number;
59
+ verb?: string;
60
+ stalled: boolean;
61
+ }): string;
@@ -1,3 +1,4 @@
1
+ import { isStreamingToolPlaceholder } from "./super-grouping/activity-timeline.js";
1
2
  import { getToolDisplayMeta } from "../agent-runtime/src/tool-metadata.js";
2
3
  function runActivityKey(parts) {
3
4
  const tail = tailPart(parts);
@@ -5,22 +6,38 @@ function runActivityKey(parts) {
5
6
  const type = typeof tail["type"] === "string" ? tail["type"] : "unknown";
6
7
  if (type === "text" || type === "reasoning") return null;
7
8
  const argsText = typeof tail["argsText"] === "string" ? tail["argsText"].length : 0;
8
- const settled = tail["result"] === void 0 ? 0 : 1;
9
+ const settled = hasNoResultYet(tail) ? 0 : 1;
9
10
  return `${parts.length}:${type}:${argsText}:${settled}`;
10
11
  }
11
12
  function runActivityVerb(parts) {
12
13
  const tail = tailPart(parts);
13
- if (!tail || tail["type"] !== "tool-call" || tail["result"] !== void 0) return null;
14
+ if (!tail || !isTailToolCallRunning(tail)) return null;
14
15
  const toolName = tail["toolName"];
15
16
  if (typeof toolName !== "string" || toolName.length === 0) return null;
16
17
  return getToolDisplayMeta(toolName).activeLabel;
17
18
  }
19
+ function isToolCallInFlight(parts) {
20
+ const tail = tailPart(parts);
21
+ return tail !== null && isTailToolCallRunning(tail);
22
+ }
23
+ function isTailToolCallRunning(tail) {
24
+ return tail["type"] === "tool-call" && hasNoResultYet(tail);
25
+ }
26
+ function hasNoResultYet(tail) {
27
+ return tail["result"] === void 0 || isStreamingToolPlaceholder({ artifact: tail["artifact"] });
28
+ }
18
29
  function tailPart(parts) {
19
30
  const last = parts[parts.length - 1];
20
31
  if (last === void 0 || last === null || typeof last !== "object") return null;
21
32
  return last;
22
33
  }
23
34
  const RUN_ACTIVITY_STALL_SECONDS = 30;
35
+ function isRunActivityStalled({
36
+ seconds,
37
+ toolCallInFlight
38
+ }) {
39
+ return !toolCallInFlight && seconds >= RUN_ACTIVITY_STALL_SECONDS;
40
+ }
24
41
  const RUN_ACTIVITY_COUNTER_SECONDS = 3;
25
42
  const RUN_ACTIVITY_DEFAULT_VERB = "Thinking";
26
43
  function runElapsedLabel(seconds) {
@@ -30,19 +47,21 @@ function runElapsedLabel(seconds) {
30
47
  const remainder = seconds % 60;
31
48
  return `${minutes}m ${String(remainder).padStart(2, "0")}s`;
32
49
  }
33
- function runActivityLabel(seconds, verb = RUN_ACTIVITY_DEFAULT_VERB) {
34
- if (seconds >= RUN_ACTIVITY_STALL_SECONDS) {
35
- return `Still working — nothing new for ${seconds}s`;
36
- }
37
- if (seconds >= RUN_ACTIVITY_COUNTER_SECONDS) {
38
- return `${verb}… · ${seconds}s`;
39
- }
40
- return `${verb}…`;
50
+ function runActivityLabel({
51
+ seconds,
52
+ verb = RUN_ACTIVITY_DEFAULT_VERB,
53
+ stalled
54
+ }) {
55
+ if (stalled) return `Still working — nothing new for ${seconds}s`;
56
+ const elapsed = runElapsedLabel(seconds);
57
+ return elapsed === null ? `${verb}…` : `${verb}… · ${elapsed}`;
41
58
  }
42
59
  export {
43
60
  RUN_ACTIVITY_COUNTER_SECONDS,
44
61
  RUN_ACTIVITY_DEFAULT_VERB,
45
62
  RUN_ACTIVITY_STALL_SECONDS,
63
+ isRunActivityStalled,
64
+ isToolCallInFlight,
46
65
  runActivityKey,
47
66
  runActivityLabel,
48
67
  runActivityVerb,
@@ -1 +1 @@
1
- {"version":3,"file":"run-activity.js","sources":["../../src/chat/run-activity.ts"],"sourcesContent":["import { getToolDisplayMeta } from '@athenaintel/agent-runtime';\n\n/**\n * Run-liveness key for a streaming assistant message.\n *\n * A tool card's spinner says \"this call is running\"; nothing says the RUN is\n * still alive once parts exist — a stalled stream and a busy one look\n * identical. The activity key summarizes the message tail: it is `null` when\n * no liveness row should show (no parts yet, or a text/reasoning tail that\n * already animates on its own), and otherwise changes whenever anything\n * streams into the tail part — so an elapsed timer keyed on it measures\n * \"time since the stream last moved\", not just \"time since the call began\".\n */\nexport function runActivityKey(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail) return null;\n const type = typeof tail['type'] === 'string' ? (tail['type'] as string) : 'unknown';\n if (type === 'text' || type === 'reasoning') return null;\n const argsText = typeof tail['argsText'] === 'string' ? (tail['argsText'] as string).length : 0;\n const settled = tail['result'] === undefined ? 0 : 1;\n return `${parts.length}:${type}:${argsText}:${settled}`;\n}\n\n/**\n * The present-tense verb for what the run is doing right now — the registry's\n * activity label for a tool call still awaiting its result (\"Searching the\n * web\") — or `null` when the tail is not an unsettled tool call.\n */\nexport function runActivityVerb(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail || tail['type'] !== 'tool-call' || tail['result'] !== undefined) return null;\n const toolName = tail['toolName'];\n if (typeof toolName !== 'string' || toolName.length === 0) return null;\n return getToolDisplayMeta(toolName).activeLabel;\n}\n\nfunction tailPart(parts: ReadonlyArray<unknown>): Record<string, unknown> | null {\n const last = parts[parts.length - 1];\n if (last === undefined || last === null || typeof last !== 'object') return null;\n return last as Record<string, unknown>;\n}\n\n/** Seconds without any streamed change before the row flips to a stall tone. */\nexport const RUN_ACTIVITY_STALL_SECONDS = 30;\n\n/** Seconds before the row starts showing its elapsed counter. */\nexport const RUN_ACTIVITY_COUNTER_SECONDS = 3;\n\n/** The default verb when nothing more specific is known. */\nexport const RUN_ACTIVITY_DEFAULT_VERB = 'Thinking';\n\n/**\n * The counter alone for a card whose header already names the verb: `null`\n * until it is due, then \"11s\" / \"1m 05s\". The stall tone is the caller's\n * (a color and an announcement), not more words on the header row.\n */\nexport function runElapsedLabel(seconds: number): string | null {\n if (seconds < RUN_ACTIVITY_COUNTER_SECONDS) return null;\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainder = seconds % 60;\n return `${minutes}m ${String(remainder).padStart(2, '0')}s`;\n}\n\n/**\n * The status line for `seconds` since the stream last moved: the verb with an\n * ellipsis, the elapsed counter once it is due, and the stall tone.\n */\nexport function runActivityLabel(seconds: number, verb: string = RUN_ACTIVITY_DEFAULT_VERB): string {\n if (seconds >= RUN_ACTIVITY_STALL_SECONDS) {\n return `Still working — nothing new for ${seconds}s`;\n }\n if (seconds >= RUN_ACTIVITY_COUNTER_SECONDS) {\n return `${verb}… · ${seconds}s`;\n }\n return `${verb}…`;\n}\n"],"names":[],"mappings":";AAaO,SAAS,eAAe,OAA8C;AAC3E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,OAAO,KAAK,MAAM,MAAM,WAAY,KAAK,MAAM,IAAe;AAC3E,MAAI,SAAS,UAAU,SAAS,YAAa,QAAO;AACpD,QAAM,WAAW,OAAO,KAAK,UAAU,MAAM,WAAY,KAAK,UAAU,EAAa,SAAS;AAC9F,QAAM,UAAU,KAAK,QAAQ,MAAM,SAAY,IAAI;AACnD,SAAO,GAAG,MAAM,MAAM,IAAI,IAAI,IAAI,QAAQ,IAAI,OAAO;AACvD;AAOO,SAAS,gBAAgB,OAA8C;AAC5E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,QAAQ,KAAK,MAAM,MAAM,eAAe,KAAK,QAAQ,MAAM,OAAW,QAAO;AAClF,QAAM,WAAW,KAAK,UAAU;AAChC,MAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG,QAAO;AAClE,SAAO,mBAAmB,QAAQ,EAAE;AACtC;AAEA,SAAS,SAAS,OAA+D;AAC/E,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,SAAS,UAAa,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC5E,SAAO;AACT;AAGO,MAAM,6BAA6B;AAGnC,MAAM,+BAA+B;AAGrC,MAAM,4BAA4B;AAOlC,SAAS,gBAAgB,SAAgC;AAC9D,MAAI,UAAU,6BAA8B,QAAO;AACnD,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,YAAY,UAAU;AAC5B,SAAO,GAAG,OAAO,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AAC1D;AAMO,SAAS,iBAAiB,SAAiB,OAAe,2BAAmC;AAClG,MAAI,WAAW,4BAA4B;AACzC,WAAO,mCAAmC,OAAO;AAAA,EACnD;AACA,MAAI,WAAW,8BAA8B;AAC3C,WAAO,GAAG,IAAI,OAAO,OAAO;AAAA,EAC9B;AACA,SAAO,GAAG,IAAI;AAChB;"}
1
+ {"version":3,"file":"run-activity.js","sources":["../../src/chat/run-activity.ts"],"sourcesContent":["import { getToolDisplayMeta } from '@athenaintel/agent-runtime';\nimport { isStreamingToolPlaceholder } from './super-grouping/activity-timeline';\n\n/**\n * Run-liveness key for a streaming assistant message.\n *\n * A tool card's spinner says \"this call is running\"; nothing says the RUN is\n * still alive once parts exist — a stalled stream and a busy one look\n * identical. The activity key summarizes the message tail: it is `null` when\n * no liveness row should show (no parts yet, or a text/reasoning tail that\n * already animates on its own), and otherwise changes whenever anything\n * streams into the tail part — so an elapsed timer keyed on it measures\n * \"time since the stream last moved\", not just \"time since the call began\".\n */\nexport function runActivityKey(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail) return null;\n const type = typeof tail['type'] === 'string' ? (tail['type'] as string) : 'unknown';\n if (type === 'text' || type === 'reasoning') return null;\n const argsText = typeof tail['argsText'] === 'string' ? (tail['argsText'] as string).length : 0;\n // A streaming placeholder is not a result: the key moves when the real one replaces it.\n const settled = hasNoResultYet(tail) ? 0 : 1;\n return `${parts.length}:${type}:${argsText}:${settled}`;\n}\n\n/**\n * The present-tense verb for what the run is doing right now — the registry's\n * activity label for a tool call still awaiting its result (\"Searching the\n * web\") — or `null` when the tail is not a tool call in flight.\n */\nexport function runActivityVerb(parts: ReadonlyArray<unknown>): string | null {\n const tail = tailPart(parts);\n if (!tail || !isTailToolCallRunning(tail)) return null;\n const toolName = tail['toolName'];\n if (typeof toolName !== 'string' || toolName.length === 0) return null;\n return getToolDisplayMeta(toolName).activeLabel;\n}\n\n/**\n * Whether the run is waiting on a tool that is still running: the tail part\n * is a tool call with no result yet, or only the streaming placeholder a\n * long tool (Python execution, a task subagent) publishes while it works. A\n * command, a build or a test suite streams nothing for minutes while it\n * works, so a quiet stream then is work, not a stall.\n */\nexport function isToolCallInFlight(parts: ReadonlyArray<unknown>): boolean {\n const tail = tailPart(parts);\n return tail !== null && isTailToolCallRunning(tail);\n}\n\nfunction isTailToolCallRunning(tail: Record<string, unknown>): boolean {\n return tail['type'] === 'tool-call' && hasNoResultYet(tail);\n}\n\n/** No result yet: none at all, or only the streaming placeholder (`isStreamingToolPlaceholder`). */\nfunction hasNoResultYet(tail: Record<string, unknown>): boolean {\n return tail['result'] === undefined || isStreamingToolPlaceholder({ artifact: tail['artifact'] });\n}\n\nfunction tailPart(parts: ReadonlyArray<unknown>): Record<string, unknown> | null {\n const last = parts[parts.length - 1];\n if (last === undefined || last === null || typeof last !== 'object') return null;\n return last as Record<string, unknown>;\n}\n\n/**\n * Seconds without any streamed change before the row flips to a stall tone —\n * while the run waits on the model, never while a tool call is in flight\n * ({@link isRunActivityStalled}).\n */\nexport const RUN_ACTIVITY_STALL_SECONDS = 30;\n\n/**\n * Whether the run looks stalled: nothing has streamed for\n * {@link RUN_ACTIVITY_STALL_SECONDS} while it waits on the model. Never while a\n * tool call is in flight: the call's own row says it is running.\n */\nexport function isRunActivityStalled({\n seconds,\n toolCallInFlight,\n}: {\n seconds: number;\n toolCallInFlight: boolean;\n}): boolean {\n return !toolCallInFlight && seconds >= RUN_ACTIVITY_STALL_SECONDS;\n}\n\n/** Seconds before the row starts showing its elapsed counter. */\nexport const RUN_ACTIVITY_COUNTER_SECONDS = 3;\n\n/** The default verb when nothing more specific is known. */\nexport const RUN_ACTIVITY_DEFAULT_VERB = 'Thinking';\n\n/**\n * The counter alone for a card whose header already names the verb: `null`\n * until it is due, then \"11s\" / \"1m 05s\". The stall tone is the caller's\n * (a color and an announcement), not more words on the header row.\n */\nexport function runElapsedLabel(seconds: number): string | null {\n if (seconds < RUN_ACTIVITY_COUNTER_SECONDS) return null;\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainder = seconds % 60;\n return `${minutes}m ${String(remainder).padStart(2, '0')}s`;\n}\n\n/**\n * The status line for `seconds` since the stream last moved: the verb with an\n * ellipsis and the elapsed counter once it is due (`runElapsedLabel`), or the\n * stall wording when the run looks `stalled` ({@link isRunActivityStalled}).\n */\nexport function runActivityLabel({\n seconds,\n verb = RUN_ACTIVITY_DEFAULT_VERB,\n stalled,\n}: {\n seconds: number;\n verb?: string;\n stalled: boolean;\n}): string {\n if (stalled) return `Still working — nothing new for ${seconds}s`;\n const elapsed = runElapsedLabel(seconds);\n return elapsed === null ? `${verb}…` : `${verb}… · ${elapsed}`;\n}\n"],"names":[],"mappings":";;AAcO,SAAS,eAAe,OAA8C;AAC3E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,OAAO,OAAO,KAAK,MAAM,MAAM,WAAY,KAAK,MAAM,IAAe;AAC3E,MAAI,SAAS,UAAU,SAAS,YAAa,QAAO;AACpD,QAAM,WAAW,OAAO,KAAK,UAAU,MAAM,WAAY,KAAK,UAAU,EAAa,SAAS;AAE9F,QAAM,UAAU,eAAe,IAAI,IAAI,IAAI;AAC3C,SAAO,GAAG,MAAM,MAAM,IAAI,IAAI,IAAI,QAAQ,IAAI,OAAO;AACvD;AAOO,SAAS,gBAAgB,OAA8C;AAC5E,QAAM,OAAO,SAAS,KAAK;AAC3B,MAAI,CAAC,QAAQ,CAAC,sBAAsB,IAAI,EAAG,QAAO;AAClD,QAAM,WAAW,KAAK,UAAU;AAChC,MAAI,OAAO,aAAa,YAAY,SAAS,WAAW,EAAG,QAAO;AAClE,SAAO,mBAAmB,QAAQ,EAAE;AACtC;AASO,SAAS,mBAAmB,OAAwC;AACzE,QAAM,OAAO,SAAS,KAAK;AAC3B,SAAO,SAAS,QAAQ,sBAAsB,IAAI;AACpD;AAEA,SAAS,sBAAsB,MAAwC;AACrE,SAAO,KAAK,MAAM,MAAM,eAAe,eAAe,IAAI;AAC5D;AAGA,SAAS,eAAe,MAAwC;AAC9D,SAAO,KAAK,QAAQ,MAAM,UAAa,2BAA2B,EAAE,UAAU,KAAK,UAAU,GAAG;AAClG;AAEA,SAAS,SAAS,OAA+D;AAC/E,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,MAAI,SAAS,UAAa,SAAS,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC5E,SAAO;AACT;AAOO,MAAM,6BAA6B;AAOnC,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AACF,GAGY;AACV,SAAO,CAAC,oBAAoB,WAAW;AACzC;AAGO,MAAM,+BAA+B;AAGrC,MAAM,4BAA4B;AAOlC,SAAS,gBAAgB,SAAgC;AAC9D,MAAI,UAAU,6BAA8B,QAAO;AACnD,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,YAAY,UAAU;AAC5B,SAAO,GAAG,OAAO,KAAK,OAAO,SAAS,EAAE,SAAS,GAAG,GAAG,CAAC;AAC1D;AAOO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAIW;AACT,MAAI,QAAS,QAAO,mCAAmC,OAAO;AAC9D,QAAM,UAAU,gBAAgB,OAAO;AACvC,SAAO,YAAY,OAAO,GAAG,IAAI,MAAM,GAAG,IAAI,OAAO,OAAO;AAC9D;"}
@@ -28,6 +28,10 @@ const DEFAULT_TOOLKIT_THEME = { color: "#919EAB" };
28
28
  const DEFAULT_GROUPING = groupingFunction.createToolBasedGroupingFunction(toolkitMap.defaultGetToolGroupKey);
29
29
  const SuperGroupingContext = react.createContext(null);
30
30
  const SuperGroupingProvider = SuperGroupingContext.Provider;
31
+ const GroupToolUIsMountedContext = react.createContext(true);
32
+ function GroupToolUI({ children }) {
33
+ return react.useContext(GroupToolUIsMountedContext) ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children }) : null;
34
+ }
31
35
  const CollapsibleGroup = ({
32
36
  groupKey,
33
37
  indices,
@@ -35,21 +39,21 @@ const CollapsibleGroup = ({
35
39
  themes,
36
40
  forceOpen = false,
37
41
  compactWhileRunning = false,
42
+ mountHidden = false,
38
43
  headerActions,
39
44
  groupingFunction: groupingFunction2,
40
45
  className
41
46
  }) => {
42
- var _a;
43
47
  const panelId = react.useId();
44
48
  const isMessageRunning = react$1.useAuiState((s) => {
45
- var _a2;
46
- return ((_a2 = s.message.status) == null ? void 0 : _a2.type) === "running";
49
+ var _a;
50
+ return ((_a = s.message.status) == null ? void 0 : _a.type) === "running";
47
51
  });
48
52
  const messageContent = react$1.useAuiState(
49
53
  (s) => s.message.role === "assistant" ? s.message.content : void 0
50
54
  );
51
- const contextGrouping = (_a = react.useContext(SuperGroupingContext)) == null ? void 0 : _a.groupingFunction;
52
- const grouping = groupingFunction2 ?? contextGrouping ?? DEFAULT_GROUPING;
55
+ const superGrouping = react.useContext(SuperGroupingContext);
56
+ const grouping = groupingFunction2 ?? (superGrouping == null ? void 0 : superGrouping.groupingFunction) ?? DEFAULT_GROUPING;
53
57
  const lastIndex = indices.at(-1);
54
58
  const isLastGroup = react.useMemo(() => {
55
59
  if (!messageContent) return false;
@@ -59,8 +63,8 @@ const CollapsibleGroup = ({
59
63
  const toolCallCount = react.useMemo(
60
64
  () => messageContent ? indices.reduce(
61
65
  (count, index) => {
62
- var _a2;
63
- return ((_a2 = messageContent[index]) == null ? void 0 : _a2.type) === "tool-call" ? count + 1 : count;
66
+ var _a;
67
+ return ((_a = messageContent[index]) == null ? void 0 : _a.type) === "tool-call" ? count + 1 : count;
64
68
  },
65
69
  0
66
70
  ) : 0,
@@ -73,6 +77,9 @@ const CollapsibleGroup = ({
73
77
  const [userOpened, setOpen] = react.useState();
74
78
  const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;
75
79
  const open = userOpened ?? (autoOpen || forceOpen);
80
+ const mountToolUIs = open || isMessageRunning || mountHidden || (superGrouping == null ? void 0 : superGrouping.mountHidden) === true;
81
+ const [toolUIsMounted, setToolUIsMounted] = react.useState(mountToolUIs);
82
+ if (mountToolUIs && !toolUIsMounted) setToolUIsMounted(true);
76
83
  if (!groupKey || indices.length <= 1) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
77
84
  const toggle = () => setOpen(!open);
78
85
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -144,7 +151,7 @@ const CollapsibleGroup = ({
144
151
  open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
145
152
  ),
146
153
  style: { borderColor: `${theme.color}33` },
147
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-2 px-5 py-4", children }) })
154
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(GroupToolUIsMountedContext.Provider, { value: toolUIsMounted, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-2 px-5 py-4", children }) }) })
148
155
  }
149
156
  )
150
157
  ]
@@ -153,6 +160,7 @@ const CollapsibleGroup = ({
153
160
  };
154
161
  exports.CollapsibleGroup = CollapsibleGroup;
155
162
  exports.DEFAULT_TOOLKIT_THEME = DEFAULT_TOOLKIT_THEME;
163
+ exports.GroupToolUI = GroupToolUI;
156
164
  exports.SuperGroupingProvider = SuperGroupingProvider;
157
165
  exports.TOOLKIT_THEMES = TOOLKIT_THEMES;
158
166
  //# sourceMappingURL=CollapsibleGroup.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollapsibleGroup.cjs","sources":["../../../src/chat/super-grouping/CollapsibleGroup.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useId, useMemo, useState } from 'react';\nimport { cn } from '../../lib/utils';\nimport { createToolBasedGroupingFunction, type GroupingFunction } from './grouping-function';\nimport { defaultGetToolGroupKey } from './toolkit-map';\n\n/**\n * Per-toolkit theme: the brand color that tints the group header, and an\n * optional icon URL a host serves (Olympus ships toolkit SVGs; the SDK does\n * not bundle images, so it tints only).\n */\nexport type ToolkitTheme = {\n /** Hex brand color for this toolkit. */\n color: string;\n /** Absolute or root-relative URL of the toolkit icon, when the host has one. */\n icon?: string;\n};\n\n/** Brand colors keyed by toolkit display name — the group keys the grouping function emits. */\nexport const TOOLKIT_THEMES: Record<string, ToolkitTheme> = {\n Web: { color: '#7BCCFA' },\n Document: { color: '#4586F9' },\n 'Document (Word)': { color: '#4586F9' },\n Notebook: { color: '#FFAB00' },\n Python: { color: '#00A76F' },\n Spreadsheet: { color: '#00A76F' },\n Email: { color: '#B584FF' },\n AOP: { color: '#336DFF' },\n App: { color: '#7336F5' },\n Presentation: { color: '#F56B36' },\n 'User Interface': { color: '#7336F5' },\n Canvas: { color: '#002542' },\n Computer: { color: '#3E0042' },\n Database: { color: '#7336F5' },\n 'Chrome Browser': { color: '#4285F4' },\n};\n\nexport const DEFAULT_TOOLKIT_THEME: ToolkitTheme = { color: '#919EAB' };\n\nconst DEFAULT_GROUPING = createToolBasedGroupingFunction(defaultGetToolGroupKey);\n\n/**\n * The grouping in force for the message being rendered. `SuperGroupingCard`\n * provides it so each `CollapsibleGroup` can tell whether it is the last\n * grouped block (the one that opens by default while the run is live) without\n * being handed the function as a prop through `Unstable_PartsGrouped`.\n */\nconst SuperGroupingContext = createContext<{ groupingFunction: GroupingFunction } | null>(null);\n\nexport const SuperGroupingProvider = SuperGroupingContext.Provider;\n\nexport interface CollapsibleGroupProps {\n /** Group key from the grouping function (toolkit display name); `undefined` for ungrouped parts. */\n groupKey: string | undefined;\n /** Indices of the parts belonging to this group. */\n indices: number[];\n children?: ReactNode;\n /** Host toolkit themes (colors, icons), merged over the built-in colors. */\n themes?: Record<string, ToolkitTheme>;\n /**\n * Keep the group open regardless of the run state — a host rule such as\n * \"an unsubmitted email draft stays visible\". The user's own toggle still\n * wins once used.\n */\n forceOpen?: boolean;\n /**\n * Do not open the live group by default (screenshot-heavy browser-control\n * runs hold hundreds of actions); the user can still expand it.\n */\n compactWhileRunning?: boolean;\n /** Extra header controls, rendered as siblings of the toggle (never nested buttons). */\n headerActions?: ReactNode;\n /** The grouping used to find the last grouped block; defaults to the card's, then the SDK's. */\n groupingFunction?: GroupingFunction;\n className?: string;\n}\n\n/**\n * Per-toolkit collapsible block inside the grouped details panel — \"Using\n * Spreadsheet Toolkit · 4 tool calls\" — used by the SDK, Olympus, and the\n * Chrome extension. The last grouped block opens by default while the message\n * is running and settles closed; the user's toggle is remembered for the\n * mounted block.\n *\n * Selector discipline: one raw `s.message.content` read; every per-group\n * derivation (grouping, counts) runs in `useMemo` off it, so the grouping\n * pass happens once per content change instead of once per selector pass.\n */\nexport const CollapsibleGroup = ({\n groupKey,\n indices,\n children,\n themes,\n forceOpen = false,\n compactWhileRunning = false,\n headerActions,\n groupingFunction,\n className,\n}: CollapsibleGroupProps) => {\n const panelId = useId();\n const isMessageRunning = useAuiState((s) => s.message.status?.type === 'running');\n const messageContent = useAuiState((s) =>\n s.message.role === 'assistant' ? s.message.content : undefined,\n );\n const contextGrouping = useContext(SuperGroupingContext)?.groupingFunction;\n const grouping = groupingFunction ?? contextGrouping ?? DEFAULT_GROUPING;\n\n const lastIndex = indices.at(-1);\n const isLastGroup = useMemo(() => {\n if (!messageContent) return false;\n const lastGroup = grouping(messageContent)\n .filter((group) => group.indices.length > 1)\n .at(-1);\n return lastGroup?.indices.at(-1) === lastIndex;\n }, [messageContent, grouping, lastIndex]);\n\n const toolCallCount = useMemo(\n () =>\n messageContent\n ? indices.reduce(\n (count, index) => (messageContent[index]?.type === 'tool-call' ? count + 1 : count),\n 0,\n )\n : 0,\n [messageContent, indices],\n );\n\n const theme = useMemo<ToolkitTheme>(\n () => (groupKey ? { ...TOOLKIT_THEMES, ...themes }[groupKey] ?? DEFAULT_TOOLKIT_THEME : DEFAULT_TOOLKIT_THEME),\n [groupKey, themes],\n );\n\n const [userOpened, setOpen] = useState<boolean>();\n const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;\n const open = userOpened ?? (autoOpen || forceOpen);\n\n if (!groupKey || indices.length <= 1) return <>{children}</>;\n\n const toggle = () => setOpen(!open);\n\n return (\n <div\n data-slot=\"collapsible-group\"\n data-group-key={groupKey}\n className={cn('my-4 rounded-2xl border shadow-sm', className)}\n style={{\n borderColor: `${theme.color}33`,\n background: `linear-gradient(to right, ${theme.color}0d, transparent, ${theme.color}0d)`,\n }}\n >\n <div className=\"flex w-full items-center rounded-2xl\">\n <button\n type=\"button\"\n onClick={toggle}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex min-w-0 flex-1 items-center justify-between gap-3 rounded-2xl px-5 py-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n <span className=\"flex min-w-0 items-center gap-3 text-foreground/90\">\n {theme.icon ? (\n <img src={theme.icon} alt=\"\" aria-hidden=\"true\" className=\"size-5 shrink-0\" />\n ) : (\n <span\n className=\"inline-block size-2 shrink-0 rounded-full\"\n style={{ backgroundColor: theme.color }}\n aria-hidden=\"true\"\n />\n )}\n <span className=\"truncate text-sm font-semibold\">\n {isMessageRunning ? 'Using ' : 'Used '}\n {groupKey} Toolkit\n </span>\n </span>\n <span className=\"shrink-0 text-xs font-medium text-muted-foreground\">\n {toolCallCount} tool {toolCallCount === 1 ? 'call' : 'calls'}\n </span>\n </button>\n <div className=\"flex shrink-0 items-center gap-1 pr-3 pl-1\">\n {headerActions}\n <button\n type=\"button\"\n onClick={toggle}\n aria-label={open ? 'Collapse toolkit group' : 'Expand toolkit group'}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n {open ? <ChevronUp size={16} /> : <ChevronDown size={16} />}\n </button>\n </div>\n </div>\n\n <div\n id={panelId}\n className={cn(\n 'grid overflow-hidden border-t transition-[grid-template-rows] duration-200 ease-in-out motion-reduce:transition-none',\n open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',\n )}\n style={{ borderColor: `${theme.color}33` }}\n >\n <div className=\"overflow-hidden\">\n <div className=\"flex flex-col gap-2 px-5 py-4\">{children}</div>\n </div>\n </div>\n </div>\n );\n};\n"],"names":["createToolBasedGroupingFunction","defaultGetToolGroupKey","createContext","groupingFunction","useId","useAuiState","_a","useContext","useMemo","useState","jsxs","cn","jsx","ChevronUp","ChevronDown"],"mappings":"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"}
1
+ {"version":3,"file":"CollapsibleGroup.cjs","sources":["../../../src/chat/super-grouping/CollapsibleGroup.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useId, useMemo, useState } from 'react';\nimport { cn } from '../../lib/utils';\nimport { createToolBasedGroupingFunction, type GroupingFunction } from './grouping-function';\nimport { defaultGetToolGroupKey } from './toolkit-map';\n\n/**\n * Per-toolkit theme: the brand color that tints the group header, and an\n * optional icon URL a host serves (Olympus ships toolkit SVGs; the SDK does\n * not bundle images, so it tints only).\n */\nexport type ToolkitTheme = {\n /** Hex brand color for this toolkit. */\n color: string;\n /** Absolute or root-relative URL of the toolkit icon, when the host has one. */\n icon?: string;\n};\n\n/** Brand colors keyed by toolkit display name — the group keys the grouping function emits. */\nexport const TOOLKIT_THEMES: Record<string, ToolkitTheme> = {\n Web: { color: '#7BCCFA' },\n Document: { color: '#4586F9' },\n 'Document (Word)': { color: '#4586F9' },\n Notebook: { color: '#FFAB00' },\n Python: { color: '#00A76F' },\n Spreadsheet: { color: '#00A76F' },\n Email: { color: '#B584FF' },\n AOP: { color: '#336DFF' },\n App: { color: '#7336F5' },\n Presentation: { color: '#F56B36' },\n 'User Interface': { color: '#7336F5' },\n Canvas: { color: '#002542' },\n Computer: { color: '#3E0042' },\n Database: { color: '#7336F5' },\n 'Chrome Browser': { color: '#4285F4' },\n};\n\nexport const DEFAULT_TOOLKIT_THEME: ToolkitTheme = { color: '#919EAB' };\n\nconst DEFAULT_GROUPING = createToolBasedGroupingFunction(defaultGetToolGroupKey);\n\n/**\n * The grouping in force for the message being rendered. `SuperGroupingCard`\n * provides it so each `CollapsibleGroup` can tell whether it is the last\n * grouped block (the one that opens by default while the run is live) without\n * being handed the function as a prop through `GroupedMessageParts`, and\n * whether the card keeps its tool UIs mounted while hidden (`mountHidden`:\n * its turn is active, or a deep link targets it).\n */\nconst SuperGroupingContext = createContext<{\n groupingFunction: GroupingFunction;\n mountHidden?: boolean;\n} | null>(null);\n\nexport const SuperGroupingProvider = SuperGroupingContext.Provider;\n\n/** Whether the group a tool UI renders in has mounted its tool UIs; outside a group, always. */\nconst GroupToolUIsMountedContext = createContext(true);\n\n/**\n * A tool call's UI inside a `CollapsibleGroup`: rendered once the group first\n * needs its tool UIs (see `CollapsibleGroup`). `GroupedMessageParts` renders\n * every tool call through it.\n */\nexport function GroupToolUI({ children }: { children: ReactNode }) {\n return useContext(GroupToolUIsMountedContext) ? <>{children}</> : null;\n}\n\nexport interface CollapsibleGroupProps {\n /** Group key from the grouping function (toolkit display name); `undefined` for ungrouped parts. */\n groupKey: string | undefined;\n /** Indices of the parts belonging to this group. */\n indices: number[];\n children?: ReactNode;\n /** Host toolkit themes (colors, icons), merged over the built-in colors. */\n themes?: Record<string, ToolkitTheme>;\n /**\n * Keep the group open regardless of the run state — a host rule such as\n * \"an unsubmitted email draft stays visible\". The user's own toggle still\n * wins once used.\n */\n forceOpen?: boolean;\n /**\n * Do not open the live group by default (screenshot-heavy browser-control\n * runs hold hundreds of actions); the user can still expand it.\n */\n compactWhileRunning?: boolean;\n /**\n * Mount the tool UIs while collapsed (hidden), so a host that needs to find\n * one — a deep link into one of its tool calls — can. The card a group\n * renders in asks the same while its turn is active or its `mountDetails` is\n * set.\n */\n mountHidden?: boolean;\n /** Extra header controls, rendered as siblings of the toggle (never nested buttons). */\n headerActions?: ReactNode;\n /** The grouping used to find the last grouped block; defaults to the card's, then the SDK's. */\n groupingFunction?: GroupingFunction;\n className?: string;\n}\n\n/**\n * Per-toolkit collapsible block inside the grouped details panel — \"Using\n * Spreadsheet Toolkit · 4 tool calls\" — used by the SDK, Olympus, and the\n * Chrome extension. The last grouped block opens by default while the message\n * is running and settles closed; the user's toggle is remembered for the\n * mounted block.\n *\n * The group's tool UIs (the tool calls `GroupedMessageParts` renders through\n * `GroupToolUI`) mount the first time they are needed — the group opens, its\n * turn is running (live tool UIs run their effects), or a host asks\n * (`mountHidden`) — and stay mounted after. A finished turn's collapsed groups\n * mount none of their tool UIs, which a long run holds hundreds of. The rest\n * of the body renders from the start: the agent's text between the calls is\n * part of the message that selections, in-page find and links into the\n * message count.\n *\n * Selector discipline: one raw `s.message.content` read; every per-group\n * derivation (grouping, counts) runs in `useMemo` off it, so the grouping\n * pass happens once per content change instead of once per selector pass.\n */\nexport const CollapsibleGroup = ({\n groupKey,\n indices,\n children,\n themes,\n forceOpen = false,\n compactWhileRunning = false,\n mountHidden = false,\n headerActions,\n groupingFunction,\n className,\n}: CollapsibleGroupProps) => {\n const panelId = useId();\n const isMessageRunning = useAuiState((s) => s.message.status?.type === 'running');\n const messageContent = useAuiState((s) =>\n s.message.role === 'assistant' ? s.message.content : undefined,\n );\n const superGrouping = useContext(SuperGroupingContext);\n const grouping = groupingFunction ?? superGrouping?.groupingFunction ?? DEFAULT_GROUPING;\n\n const lastIndex = indices.at(-1);\n const isLastGroup = useMemo(() => {\n if (!messageContent) return false;\n const lastGroup = grouping(messageContent)\n .filter((group) => group.indices.length > 1)\n .at(-1);\n return lastGroup?.indices.at(-1) === lastIndex;\n }, [messageContent, grouping, lastIndex]);\n\n const toolCallCount = useMemo(\n () =>\n messageContent\n ? indices.reduce(\n (count, index) => (messageContent[index]?.type === 'tool-call' ? count + 1 : count),\n 0,\n )\n : 0,\n [messageContent, indices],\n );\n\n const theme = useMemo<ToolkitTheme>(\n () => (groupKey ? { ...TOOLKIT_THEMES, ...themes }[groupKey] ?? DEFAULT_TOOLKIT_THEME : DEFAULT_TOOLKIT_THEME),\n [groupKey, themes],\n );\n\n const [userOpened, setOpen] = useState<boolean>();\n const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;\n const open = userOpened ?? (autoOpen || forceOpen);\n\n const mountToolUIs =\n open || isMessageRunning || mountHidden || superGrouping?.mountHidden === true;\n const [toolUIsMounted, setToolUIsMounted] = useState(mountToolUIs);\n // Adjusting state during render is React's sanctioned derived-state pattern.\n if (mountToolUIs && !toolUIsMounted) setToolUIsMounted(true);\n\n if (!groupKey || indices.length <= 1) return <>{children}</>;\n\n const toggle = () => setOpen(!open);\n\n return (\n <div\n data-slot=\"collapsible-group\"\n data-group-key={groupKey}\n className={cn('my-4 rounded-2xl border shadow-sm', className)}\n style={{\n borderColor: `${theme.color}33`,\n background: `linear-gradient(to right, ${theme.color}0d, transparent, ${theme.color}0d)`,\n }}\n >\n <div className=\"flex w-full items-center rounded-2xl\">\n <button\n type=\"button\"\n onClick={toggle}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex min-w-0 flex-1 items-center justify-between gap-3 rounded-2xl px-5 py-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n <span className=\"flex min-w-0 items-center gap-3 text-foreground/90\">\n {theme.icon ? (\n <img src={theme.icon} alt=\"\" aria-hidden=\"true\" className=\"size-5 shrink-0\" />\n ) : (\n <span\n className=\"inline-block size-2 shrink-0 rounded-full\"\n style={{ backgroundColor: theme.color }}\n aria-hidden=\"true\"\n />\n )}\n <span className=\"truncate text-sm font-semibold\">\n {isMessageRunning ? 'Using ' : 'Used '}\n {groupKey} Toolkit\n </span>\n </span>\n <span className=\"shrink-0 text-xs font-medium text-muted-foreground\">\n {toolCallCount} tool {toolCallCount === 1 ? 'call' : 'calls'}\n </span>\n </button>\n <div className=\"flex shrink-0 items-center gap-1 pr-3 pl-1\">\n {headerActions}\n <button\n type=\"button\"\n onClick={toggle}\n aria-label={open ? 'Collapse toolkit group' : 'Expand toolkit group'}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n {open ? <ChevronUp size={16} /> : <ChevronDown size={16} />}\n </button>\n </div>\n </div>\n\n <div\n id={panelId}\n className={cn(\n 'grid overflow-hidden border-t transition-[grid-template-rows] duration-200 ease-in-out motion-reduce:transition-none',\n open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',\n )}\n style={{ borderColor: `${theme.color}33` }}\n >\n <div className=\"overflow-hidden\">\n <GroupToolUIsMountedContext.Provider value={toolUIsMounted}>\n <div className=\"flex flex-col gap-2 px-5 py-4\">{children}</div>\n </GroupToolUIsMountedContext.Provider>\n </div>\n </div>\n </div>\n );\n};\n"],"names":["createToolBasedGroupingFunction","defaultGetToolGroupKey","createContext","useContext","jsx","Fragment","groupingFunction","useId","useAuiState","useMemo","useState","jsxs","cn","ChevronUp","ChevronDown"],"mappings":"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"}
@@ -16,7 +16,16 @@ export declare const TOOLKIT_THEMES: Record<string, ToolkitTheme>;
16
16
  export declare const DEFAULT_TOOLKIT_THEME: ToolkitTheme;
17
17
  export declare const SuperGroupingProvider: import("react").Provider<{
18
18
  groupingFunction: GroupingFunction;
19
+ mountHidden?: boolean;
19
20
  } | null>;
21
+ /**
22
+ * A tool call's UI inside a `CollapsibleGroup`: rendered once the group first
23
+ * needs its tool UIs (see `CollapsibleGroup`). `GroupedMessageParts` renders
24
+ * every tool call through it.
25
+ */
26
+ export declare function GroupToolUI({ children }: {
27
+ children: ReactNode;
28
+ }): import("react").JSX.Element | null;
20
29
  export interface CollapsibleGroupProps {
21
30
  /** Group key from the grouping function (toolkit display name); `undefined` for ungrouped parts. */
22
31
  groupKey: string | undefined;
@@ -36,6 +45,13 @@ export interface CollapsibleGroupProps {
36
45
  * runs hold hundreds of actions); the user can still expand it.
37
46
  */
38
47
  compactWhileRunning?: boolean;
48
+ /**
49
+ * Mount the tool UIs while collapsed (hidden), so a host that needs to find
50
+ * one — a deep link into one of its tool calls — can. The card a group
51
+ * renders in asks the same while its turn is active or its `mountDetails` is
52
+ * set.
53
+ */
54
+ mountHidden?: boolean;
39
55
  /** Extra header controls, rendered as siblings of the toggle (never nested buttons). */
40
56
  headerActions?: ReactNode;
41
57
  /** The grouping used to find the last grouped block; defaults to the card's, then the SDK's. */
@@ -49,8 +65,17 @@ export interface CollapsibleGroupProps {
49
65
  * is running and settles closed; the user's toggle is remembered for the
50
66
  * mounted block.
51
67
  *
68
+ * The group's tool UIs (the tool calls `GroupedMessageParts` renders through
69
+ * `GroupToolUI`) mount the first time they are needed — the group opens, its
70
+ * turn is running (live tool UIs run their effects), or a host asks
71
+ * (`mountHidden`) — and stay mounted after. A finished turn's collapsed groups
72
+ * mount none of their tool UIs, which a long run holds hundreds of. The rest
73
+ * of the body renders from the start: the agent's text between the calls is
74
+ * part of the message that selections, in-page find and links into the
75
+ * message count.
76
+ *
52
77
  * Selector discipline: one raw `s.message.content` read; every per-group
53
78
  * derivation (grouping, counts) runs in `useMemo` off it, so the grouping
54
79
  * pass happens once per content change instead of once per selector pass.
55
80
  */
56
- export declare const CollapsibleGroup: ({ groupKey, indices, children, themes, forceOpen, compactWhileRunning, headerActions, groupingFunction, className, }: CollapsibleGroupProps) => import("react").JSX.Element;
81
+ export declare const CollapsibleGroup: ({ groupKey, indices, children, themes, forceOpen, compactWhileRunning, mountHidden, headerActions, groupingFunction, className, }: CollapsibleGroupProps) => import("react").JSX.Element;
@@ -26,6 +26,10 @@ const DEFAULT_TOOLKIT_THEME = { color: "#919EAB" };
26
26
  const DEFAULT_GROUPING = createToolBasedGroupingFunction(defaultGetToolGroupKey);
27
27
  const SuperGroupingContext = createContext(null);
28
28
  const SuperGroupingProvider = SuperGroupingContext.Provider;
29
+ const GroupToolUIsMountedContext = createContext(true);
30
+ function GroupToolUI({ children }) {
31
+ return useContext(GroupToolUIsMountedContext) ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
32
+ }
29
33
  const CollapsibleGroup = ({
30
34
  groupKey,
31
35
  indices,
@@ -33,21 +37,21 @@ const CollapsibleGroup = ({
33
37
  themes,
34
38
  forceOpen = false,
35
39
  compactWhileRunning = false,
40
+ mountHidden = false,
36
41
  headerActions,
37
42
  groupingFunction,
38
43
  className
39
44
  }) => {
40
- var _a;
41
45
  const panelId = useId();
42
46
  const isMessageRunning = useAuiState((s) => {
43
- var _a2;
44
- return ((_a2 = s.message.status) == null ? void 0 : _a2.type) === "running";
47
+ var _a;
48
+ return ((_a = s.message.status) == null ? void 0 : _a.type) === "running";
45
49
  });
46
50
  const messageContent = useAuiState(
47
51
  (s) => s.message.role === "assistant" ? s.message.content : void 0
48
52
  );
49
- const contextGrouping = (_a = useContext(SuperGroupingContext)) == null ? void 0 : _a.groupingFunction;
50
- const grouping = groupingFunction ?? contextGrouping ?? DEFAULT_GROUPING;
53
+ const superGrouping = useContext(SuperGroupingContext);
54
+ const grouping = groupingFunction ?? (superGrouping == null ? void 0 : superGrouping.groupingFunction) ?? DEFAULT_GROUPING;
51
55
  const lastIndex = indices.at(-1);
52
56
  const isLastGroup = useMemo(() => {
53
57
  if (!messageContent) return false;
@@ -57,8 +61,8 @@ const CollapsibleGroup = ({
57
61
  const toolCallCount = useMemo(
58
62
  () => messageContent ? indices.reduce(
59
63
  (count, index) => {
60
- var _a2;
61
- return ((_a2 = messageContent[index]) == null ? void 0 : _a2.type) === "tool-call" ? count + 1 : count;
64
+ var _a;
65
+ return ((_a = messageContent[index]) == null ? void 0 : _a.type) === "tool-call" ? count + 1 : count;
62
66
  },
63
67
  0
64
68
  ) : 0,
@@ -71,6 +75,9 @@ const CollapsibleGroup = ({
71
75
  const [userOpened, setOpen] = useState();
72
76
  const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;
73
77
  const open = userOpened ?? (autoOpen || forceOpen);
78
+ const mountToolUIs = open || isMessageRunning || mountHidden || (superGrouping == null ? void 0 : superGrouping.mountHidden) === true;
79
+ const [toolUIsMounted, setToolUIsMounted] = useState(mountToolUIs);
80
+ if (mountToolUIs && !toolUIsMounted) setToolUIsMounted(true);
74
81
  if (!groupKey || indices.length <= 1) return /* @__PURE__ */ jsx(Fragment, { children });
75
82
  const toggle = () => setOpen(!open);
76
83
  return /* @__PURE__ */ jsxs(
@@ -142,7 +149,7 @@ const CollapsibleGroup = ({
142
149
  open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
143
150
  ),
144
151
  style: { borderColor: `${theme.color}33` },
145
- children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 px-5 py-4", children }) })
152
+ children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx(GroupToolUIsMountedContext.Provider, { value: toolUIsMounted, children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 px-5 py-4", children }) }) })
146
153
  }
147
154
  )
148
155
  ]
@@ -152,6 +159,7 @@ const CollapsibleGroup = ({
152
159
  export {
153
160
  CollapsibleGroup,
154
161
  DEFAULT_TOOLKIT_THEME,
162
+ GroupToolUI,
155
163
  SuperGroupingProvider,
156
164
  TOOLKIT_THEMES
157
165
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CollapsibleGroup.js","sources":["../../../src/chat/super-grouping/CollapsibleGroup.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useId, useMemo, useState } from 'react';\nimport { cn } from '../../lib/utils';\nimport { createToolBasedGroupingFunction, type GroupingFunction } from './grouping-function';\nimport { defaultGetToolGroupKey } from './toolkit-map';\n\n/**\n * Per-toolkit theme: the brand color that tints the group header, and an\n * optional icon URL a host serves (Olympus ships toolkit SVGs; the SDK does\n * not bundle images, so it tints only).\n */\nexport type ToolkitTheme = {\n /** Hex brand color for this toolkit. */\n color: string;\n /** Absolute or root-relative URL of the toolkit icon, when the host has one. */\n icon?: string;\n};\n\n/** Brand colors keyed by toolkit display name — the group keys the grouping function emits. */\nexport const TOOLKIT_THEMES: Record<string, ToolkitTheme> = {\n Web: { color: '#7BCCFA' },\n Document: { color: '#4586F9' },\n 'Document (Word)': { color: '#4586F9' },\n Notebook: { color: '#FFAB00' },\n Python: { color: '#00A76F' },\n Spreadsheet: { color: '#00A76F' },\n Email: { color: '#B584FF' },\n AOP: { color: '#336DFF' },\n App: { color: '#7336F5' },\n Presentation: { color: '#F56B36' },\n 'User Interface': { color: '#7336F5' },\n Canvas: { color: '#002542' },\n Computer: { color: '#3E0042' },\n Database: { color: '#7336F5' },\n 'Chrome Browser': { color: '#4285F4' },\n};\n\nexport const DEFAULT_TOOLKIT_THEME: ToolkitTheme = { color: '#919EAB' };\n\nconst DEFAULT_GROUPING = createToolBasedGroupingFunction(defaultGetToolGroupKey);\n\n/**\n * The grouping in force for the message being rendered. `SuperGroupingCard`\n * provides it so each `CollapsibleGroup` can tell whether it is the last\n * grouped block (the one that opens by default while the run is live) without\n * being handed the function as a prop through `Unstable_PartsGrouped`.\n */\nconst SuperGroupingContext = createContext<{ groupingFunction: GroupingFunction } | null>(null);\n\nexport const SuperGroupingProvider = SuperGroupingContext.Provider;\n\nexport interface CollapsibleGroupProps {\n /** Group key from the grouping function (toolkit display name); `undefined` for ungrouped parts. */\n groupKey: string | undefined;\n /** Indices of the parts belonging to this group. */\n indices: number[];\n children?: ReactNode;\n /** Host toolkit themes (colors, icons), merged over the built-in colors. */\n themes?: Record<string, ToolkitTheme>;\n /**\n * Keep the group open regardless of the run state — a host rule such as\n * \"an unsubmitted email draft stays visible\". The user's own toggle still\n * wins once used.\n */\n forceOpen?: boolean;\n /**\n * Do not open the live group by default (screenshot-heavy browser-control\n * runs hold hundreds of actions); the user can still expand it.\n */\n compactWhileRunning?: boolean;\n /** Extra header controls, rendered as siblings of the toggle (never nested buttons). */\n headerActions?: ReactNode;\n /** The grouping used to find the last grouped block; defaults to the card's, then the SDK's. */\n groupingFunction?: GroupingFunction;\n className?: string;\n}\n\n/**\n * Per-toolkit collapsible block inside the grouped details panel — \"Using\n * Spreadsheet Toolkit · 4 tool calls\" — used by the SDK, Olympus, and the\n * Chrome extension. The last grouped block opens by default while the message\n * is running and settles closed; the user's toggle is remembered for the\n * mounted block.\n *\n * Selector discipline: one raw `s.message.content` read; every per-group\n * derivation (grouping, counts) runs in `useMemo` off it, so the grouping\n * pass happens once per content change instead of once per selector pass.\n */\nexport const CollapsibleGroup = ({\n groupKey,\n indices,\n children,\n themes,\n forceOpen = false,\n compactWhileRunning = false,\n headerActions,\n groupingFunction,\n className,\n}: CollapsibleGroupProps) => {\n const panelId = useId();\n const isMessageRunning = useAuiState((s) => s.message.status?.type === 'running');\n const messageContent = useAuiState((s) =>\n s.message.role === 'assistant' ? s.message.content : undefined,\n );\n const contextGrouping = useContext(SuperGroupingContext)?.groupingFunction;\n const grouping = groupingFunction ?? contextGrouping ?? DEFAULT_GROUPING;\n\n const lastIndex = indices.at(-1);\n const isLastGroup = useMemo(() => {\n if (!messageContent) return false;\n const lastGroup = grouping(messageContent)\n .filter((group) => group.indices.length > 1)\n .at(-1);\n return lastGroup?.indices.at(-1) === lastIndex;\n }, [messageContent, grouping, lastIndex]);\n\n const toolCallCount = useMemo(\n () =>\n messageContent\n ? indices.reduce(\n (count, index) => (messageContent[index]?.type === 'tool-call' ? count + 1 : count),\n 0,\n )\n : 0,\n [messageContent, indices],\n );\n\n const theme = useMemo<ToolkitTheme>(\n () => (groupKey ? { ...TOOLKIT_THEMES, ...themes }[groupKey] ?? DEFAULT_TOOLKIT_THEME : DEFAULT_TOOLKIT_THEME),\n [groupKey, themes],\n );\n\n const [userOpened, setOpen] = useState<boolean>();\n const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;\n const open = userOpened ?? (autoOpen || forceOpen);\n\n if (!groupKey || indices.length <= 1) return <>{children}</>;\n\n const toggle = () => setOpen(!open);\n\n return (\n <div\n data-slot=\"collapsible-group\"\n data-group-key={groupKey}\n className={cn('my-4 rounded-2xl border shadow-sm', className)}\n style={{\n borderColor: `${theme.color}33`,\n background: `linear-gradient(to right, ${theme.color}0d, transparent, ${theme.color}0d)`,\n }}\n >\n <div className=\"flex w-full items-center rounded-2xl\">\n <button\n type=\"button\"\n onClick={toggle}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex min-w-0 flex-1 items-center justify-between gap-3 rounded-2xl px-5 py-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n <span className=\"flex min-w-0 items-center gap-3 text-foreground/90\">\n {theme.icon ? (\n <img src={theme.icon} alt=\"\" aria-hidden=\"true\" className=\"size-5 shrink-0\" />\n ) : (\n <span\n className=\"inline-block size-2 shrink-0 rounded-full\"\n style={{ backgroundColor: theme.color }}\n aria-hidden=\"true\"\n />\n )}\n <span className=\"truncate text-sm font-semibold\">\n {isMessageRunning ? 'Using ' : 'Used '}\n {groupKey} Toolkit\n </span>\n </span>\n <span className=\"shrink-0 text-xs font-medium text-muted-foreground\">\n {toolCallCount} tool {toolCallCount === 1 ? 'call' : 'calls'}\n </span>\n </button>\n <div className=\"flex shrink-0 items-center gap-1 pr-3 pl-1\">\n {headerActions}\n <button\n type=\"button\"\n onClick={toggle}\n aria-label={open ? 'Collapse toolkit group' : 'Expand toolkit group'}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n {open ? <ChevronUp size={16} /> : <ChevronDown size={16} />}\n </button>\n </div>\n </div>\n\n <div\n id={panelId}\n className={cn(\n 'grid overflow-hidden border-t transition-[grid-template-rows] duration-200 ease-in-out motion-reduce:transition-none',\n open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',\n )}\n style={{ borderColor: `${theme.color}33` }}\n >\n <div className=\"overflow-hidden\">\n <div className=\"flex flex-col gap-2 px-5 py-4\">{children}</div>\n </div>\n </div>\n </div>\n );\n};\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"CollapsibleGroup.js","sources":["../../../src/chat/super-grouping/CollapsibleGroup.tsx"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport { ChevronDown, ChevronUp } from 'lucide-react';\nimport type { ReactNode } from 'react';\nimport { createContext, useContext, useId, useMemo, useState } from 'react';\nimport { cn } from '../../lib/utils';\nimport { createToolBasedGroupingFunction, type GroupingFunction } from './grouping-function';\nimport { defaultGetToolGroupKey } from './toolkit-map';\n\n/**\n * Per-toolkit theme: the brand color that tints the group header, and an\n * optional icon URL a host serves (Olympus ships toolkit SVGs; the SDK does\n * not bundle images, so it tints only).\n */\nexport type ToolkitTheme = {\n /** Hex brand color for this toolkit. */\n color: string;\n /** Absolute or root-relative URL of the toolkit icon, when the host has one. */\n icon?: string;\n};\n\n/** Brand colors keyed by toolkit display name — the group keys the grouping function emits. */\nexport const TOOLKIT_THEMES: Record<string, ToolkitTheme> = {\n Web: { color: '#7BCCFA' },\n Document: { color: '#4586F9' },\n 'Document (Word)': { color: '#4586F9' },\n Notebook: { color: '#FFAB00' },\n Python: { color: '#00A76F' },\n Spreadsheet: { color: '#00A76F' },\n Email: { color: '#B584FF' },\n AOP: { color: '#336DFF' },\n App: { color: '#7336F5' },\n Presentation: { color: '#F56B36' },\n 'User Interface': { color: '#7336F5' },\n Canvas: { color: '#002542' },\n Computer: { color: '#3E0042' },\n Database: { color: '#7336F5' },\n 'Chrome Browser': { color: '#4285F4' },\n};\n\nexport const DEFAULT_TOOLKIT_THEME: ToolkitTheme = { color: '#919EAB' };\n\nconst DEFAULT_GROUPING = createToolBasedGroupingFunction(defaultGetToolGroupKey);\n\n/**\n * The grouping in force for the message being rendered. `SuperGroupingCard`\n * provides it so each `CollapsibleGroup` can tell whether it is the last\n * grouped block (the one that opens by default while the run is live) without\n * being handed the function as a prop through `GroupedMessageParts`, and\n * whether the card keeps its tool UIs mounted while hidden (`mountHidden`:\n * its turn is active, or a deep link targets it).\n */\nconst SuperGroupingContext = createContext<{\n groupingFunction: GroupingFunction;\n mountHidden?: boolean;\n} | null>(null);\n\nexport const SuperGroupingProvider = SuperGroupingContext.Provider;\n\n/** Whether the group a tool UI renders in has mounted its tool UIs; outside a group, always. */\nconst GroupToolUIsMountedContext = createContext(true);\n\n/**\n * A tool call's UI inside a `CollapsibleGroup`: rendered once the group first\n * needs its tool UIs (see `CollapsibleGroup`). `GroupedMessageParts` renders\n * every tool call through it.\n */\nexport function GroupToolUI({ children }: { children: ReactNode }) {\n return useContext(GroupToolUIsMountedContext) ? <>{children}</> : null;\n}\n\nexport interface CollapsibleGroupProps {\n /** Group key from the grouping function (toolkit display name); `undefined` for ungrouped parts. */\n groupKey: string | undefined;\n /** Indices of the parts belonging to this group. */\n indices: number[];\n children?: ReactNode;\n /** Host toolkit themes (colors, icons), merged over the built-in colors. */\n themes?: Record<string, ToolkitTheme>;\n /**\n * Keep the group open regardless of the run state — a host rule such as\n * \"an unsubmitted email draft stays visible\". The user's own toggle still\n * wins once used.\n */\n forceOpen?: boolean;\n /**\n * Do not open the live group by default (screenshot-heavy browser-control\n * runs hold hundreds of actions); the user can still expand it.\n */\n compactWhileRunning?: boolean;\n /**\n * Mount the tool UIs while collapsed (hidden), so a host that needs to find\n * one — a deep link into one of its tool calls — can. The card a group\n * renders in asks the same while its turn is active or its `mountDetails` is\n * set.\n */\n mountHidden?: boolean;\n /** Extra header controls, rendered as siblings of the toggle (never nested buttons). */\n headerActions?: ReactNode;\n /** The grouping used to find the last grouped block; defaults to the card's, then the SDK's. */\n groupingFunction?: GroupingFunction;\n className?: string;\n}\n\n/**\n * Per-toolkit collapsible block inside the grouped details panel — \"Using\n * Spreadsheet Toolkit · 4 tool calls\" — used by the SDK, Olympus, and the\n * Chrome extension. The last grouped block opens by default while the message\n * is running and settles closed; the user's toggle is remembered for the\n * mounted block.\n *\n * The group's tool UIs (the tool calls `GroupedMessageParts` renders through\n * `GroupToolUI`) mount the first time they are needed — the group opens, its\n * turn is running (live tool UIs run their effects), or a host asks\n * (`mountHidden`) — and stay mounted after. A finished turn's collapsed groups\n * mount none of their tool UIs, which a long run holds hundreds of. The rest\n * of the body renders from the start: the agent's text between the calls is\n * part of the message that selections, in-page find and links into the\n * message count.\n *\n * Selector discipline: one raw `s.message.content` read; every per-group\n * derivation (grouping, counts) runs in `useMemo` off it, so the grouping\n * pass happens once per content change instead of once per selector pass.\n */\nexport const CollapsibleGroup = ({\n groupKey,\n indices,\n children,\n themes,\n forceOpen = false,\n compactWhileRunning = false,\n mountHidden = false,\n headerActions,\n groupingFunction,\n className,\n}: CollapsibleGroupProps) => {\n const panelId = useId();\n const isMessageRunning = useAuiState((s) => s.message.status?.type === 'running');\n const messageContent = useAuiState((s) =>\n s.message.role === 'assistant' ? s.message.content : undefined,\n );\n const superGrouping = useContext(SuperGroupingContext);\n const grouping = groupingFunction ?? superGrouping?.groupingFunction ?? DEFAULT_GROUPING;\n\n const lastIndex = indices.at(-1);\n const isLastGroup = useMemo(() => {\n if (!messageContent) return false;\n const lastGroup = grouping(messageContent)\n .filter((group) => group.indices.length > 1)\n .at(-1);\n return lastGroup?.indices.at(-1) === lastIndex;\n }, [messageContent, grouping, lastIndex]);\n\n const toolCallCount = useMemo(\n () =>\n messageContent\n ? indices.reduce(\n (count, index) => (messageContent[index]?.type === 'tool-call' ? count + 1 : count),\n 0,\n )\n : 0,\n [messageContent, indices],\n );\n\n const theme = useMemo<ToolkitTheme>(\n () => (groupKey ? { ...TOOLKIT_THEMES, ...themes }[groupKey] ?? DEFAULT_TOOLKIT_THEME : DEFAULT_TOOLKIT_THEME),\n [groupKey, themes],\n );\n\n const [userOpened, setOpen] = useState<boolean>();\n const autoOpen = isLastGroup && isMessageRunning && !compactWhileRunning;\n const open = userOpened ?? (autoOpen || forceOpen);\n\n const mountToolUIs =\n open || isMessageRunning || mountHidden || superGrouping?.mountHidden === true;\n const [toolUIsMounted, setToolUIsMounted] = useState(mountToolUIs);\n // Adjusting state during render is React's sanctioned derived-state pattern.\n if (mountToolUIs && !toolUIsMounted) setToolUIsMounted(true);\n\n if (!groupKey || indices.length <= 1) return <>{children}</>;\n\n const toggle = () => setOpen(!open);\n\n return (\n <div\n data-slot=\"collapsible-group\"\n data-group-key={groupKey}\n className={cn('my-4 rounded-2xl border shadow-sm', className)}\n style={{\n borderColor: `${theme.color}33`,\n background: `linear-gradient(to right, ${theme.color}0d, transparent, ${theme.color}0d)`,\n }}\n >\n <div className=\"flex w-full items-center rounded-2xl\">\n <button\n type=\"button\"\n onClick={toggle}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex min-w-0 flex-1 items-center justify-between gap-3 rounded-2xl px-5 py-3 text-left transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n <span className=\"flex min-w-0 items-center gap-3 text-foreground/90\">\n {theme.icon ? (\n <img src={theme.icon} alt=\"\" aria-hidden=\"true\" className=\"size-5 shrink-0\" />\n ) : (\n <span\n className=\"inline-block size-2 shrink-0 rounded-full\"\n style={{ backgroundColor: theme.color }}\n aria-hidden=\"true\"\n />\n )}\n <span className=\"truncate text-sm font-semibold\">\n {isMessageRunning ? 'Using ' : 'Used '}\n {groupKey} Toolkit\n </span>\n </span>\n <span className=\"shrink-0 text-xs font-medium text-muted-foreground\">\n {toolCallCount} tool {toolCallCount === 1 ? 'call' : 'calls'}\n </span>\n </button>\n <div className=\"flex shrink-0 items-center gap-1 pr-3 pl-1\">\n {headerActions}\n <button\n type=\"button\"\n onClick={toggle}\n aria-label={open ? 'Collapse toolkit group' : 'Expand toolkit group'}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:bg-black/5 dark:hover:bg-white/5\"\n >\n {open ? <ChevronUp size={16} /> : <ChevronDown size={16} />}\n </button>\n </div>\n </div>\n\n <div\n id={panelId}\n className={cn(\n 'grid overflow-hidden border-t transition-[grid-template-rows] duration-200 ease-in-out motion-reduce:transition-none',\n open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',\n )}\n style={{ borderColor: `${theme.color}33` }}\n >\n <div className=\"overflow-hidden\">\n <GroupToolUIsMountedContext.Provider value={toolUIsMounted}>\n <div className=\"flex flex-col gap-2 px-5 py-4\">{children}</div>\n </GroupToolUIsMountedContext.Provider>\n </div>\n </div>\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const react = require("@assistant-ui/react");
5
+ const react$1 = require("react");
6
+ const CollapsibleGroup = require("./CollapsibleGroup.cjs");
7
+ const groupedPartKeys = require("./grouped-part-keys.cjs");
8
+ const NO_GROUPS = [];
9
+ const DefaultText = () => /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { whiteSpace: "pre-line" }, children: [
10
+ /* @__PURE__ */ jsxRuntime.jsx(react.MessagePartPrimitive.Text, {}),
11
+ /* @__PURE__ */ jsxRuntime.jsx(react.MessagePartPrimitive.InProgress, { children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: "revert" }, children: " ●" }) })
12
+ ] });
13
+ const DefaultImage = () => /* @__PURE__ */ jsxRuntime.jsx(react.MessagePartPrimitive.Image, {});
14
+ const PassThroughGroup = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
15
+ function useGroupedPartKeys({
16
+ parts,
17
+ groups
18
+ }) {
19
+ const [keys, setKeys] = react$1.useState(null);
20
+ if (keys !== null && keys.parts === parts && keys.groups === groups) return keys;
21
+ const next = groupedPartKeys.carryGroupedPartKeys({ previous: keys, parts, groups });
22
+ setKeys(next);
23
+ return next;
24
+ }
25
+ const GroupedMessageParts = ({ groupingFunction, components }) => {
26
+ const parts = react.useAuiState((s) => s.message.parts);
27
+ const groups = react$1.useMemo(
28
+ () => parts.length === 0 ? NO_GROUPS : groupingFunction(parts),
29
+ [parts, groupingFunction]
30
+ );
31
+ const keys = useGroupedPartKeys({ parts, groups });
32
+ const elements = react$1.useMemo(() => {
33
+ const leafComponents = {
34
+ ...components,
35
+ Text: (components == null ? void 0 : components.Text) ?? DefaultText,
36
+ Image: (components == null ? void 0 : components.Image) ?? DefaultImage
37
+ };
38
+ const Group = (components == null ? void 0 : components.Group) ?? PassThroughGroup;
39
+ return groups.map((group, groupIndex) => /* @__PURE__ */ jsxRuntime.jsx(Group, { groupKey: group.groupKey, indices: group.indices, children: group.indices.map(
40
+ (partIndex) => {
41
+ var _a;
42
+ return ((_a = parts[partIndex]) == null ? void 0 : _a.type) === "tool-call" ? /* @__PURE__ */ jsxRuntime.jsx(CollapsibleGroup.GroupToolUI, { children: /* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.PartByIndex, { index: partIndex, components: leafComponents }) }, keys.partKeys[partIndex]) : /* @__PURE__ */ jsxRuntime.jsx(
43
+ react.MessagePrimitive.PartByIndex,
44
+ {
45
+ index: partIndex,
46
+ components: leafComponents
47
+ },
48
+ keys.partKeys[partIndex]
49
+ );
50
+ }
51
+ ) }, keys.groupKeys[groupIndex]));
52
+ }, [components, groups, keys, parts]);
53
+ if (parts.length === 0) {
54
+ return /* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.Unstable_PartsGrouped, { groupingFunction, components });
55
+ }
56
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: elements });
57
+ };
58
+ exports.GroupedMessageParts = GroupedMessageParts;
59
+ //# sourceMappingURL=GroupedMessageParts.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupedMessageParts.cjs","sources":["../../../src/chat/super-grouping/GroupedMessageParts.tsx"],"sourcesContent":["import {\n type ImageMessagePartComponent,\n MessagePartPrimitive,\n MessagePrimitive,\n type TextMessagePartComponent,\n useAuiState,\n} from '@assistant-ui/react';\nimport { type ComponentProps, type FC, type ReactNode, useMemo, useState } from 'react';\nimport { GroupToolUI } from './CollapsibleGroup';\nimport { carryGroupedPartKeys, type GroupedPartKeys, type KeyablePart } from './grouped-part-keys';\nimport type { GroupingFunction, MessagePartGroup } from './grouping-function';\n\nexport interface GroupedMessagePartsProps {\n /** Groups the message's parts; each group renders through `components.Group`. */\n groupingFunction: GroupingFunction;\n /** The part and group renderers, as `MessagePrimitive.Unstable_PartsGrouped` takes them. */\n components: ComponentProps<typeof MessagePrimitive.Unstable_PartsGrouped>['components'];\n}\n\nconst NO_GROUPS: readonly MessagePartGroup[] = [];\n\n/** assistant-ui's grouped-parts defaults for the leaves a host does not render. */\nconst DefaultText: TextMessagePartComponent = () => (\n <p style={{ whiteSpace: 'pre-line' }}>\n <MessagePartPrimitive.Text />\n <MessagePartPrimitive.InProgress>\n <span style={{ fontFamily: 'revert' }}>{' ●'}</span>\n </MessagePartPrimitive.InProgress>\n </p>\n);\nconst DefaultImage: ImageMessagePartComponent = () => <MessagePartPrimitive.Image />;\nconst PassThroughGroup = ({ children }: { children?: ReactNode }) => <>{children}</>;\n\n/**\n * The keys of the last render, carried into this one (see\n * {@link carryGroupedPartKeys}). Kept as state, React's way of storing\n * information from previous renders: a render whose parts or groups changed\n * stores the new keys, and React renders again with them before committing.\n */\nfunction useGroupedPartKeys({\n parts,\n groups,\n}: {\n parts: readonly KeyablePart[];\n groups: readonly MessagePartGroup[];\n}): GroupedPartKeys {\n const [keys, setKeys] = useState<GroupedPartKeys | null>(null);\n if (keys !== null && keys.parts === parts && keys.groups === groups) return keys;\n const next = carryGroupedPartKeys({ previous: keys, parts, groups });\n setKeys(next);\n return next;\n}\n\n/**\n * A message's parts grouped by `groupingFunction`, each group through\n * `components.Group` — `MessagePrimitive.Unstable_PartsGrouped` with two\n * differences:\n *\n * - What is mounted stays mounted when the message grows at its head (a page\n * of older steps folding into the first loaded turn) as well as at its tail:\n * groups and parts keep their React keys across renders (see\n * {@link carryGroupedPartKeys}), where assistant-ui keys a group by its\n * first part and a text part by its index.\n * - A tool call's UI renders through {@link GroupToolUI}, so a collapsed\n * `CollapsibleGroup` of a finished turn mounts it only when first needed.\n *\n * An empty message renders as `Unstable_PartsGrouped` renders it.\n */\nexport const GroupedMessageParts: FC<GroupedMessagePartsProps> = ({ groupingFunction, components }) => {\n const parts = useAuiState((s) => s.message.parts);\n const groups = useMemo(\n () => (parts.length === 0 ? NO_GROUPS : groupingFunction(parts)),\n [parts, groupingFunction],\n );\n const keys = useGroupedPartKeys({ parts, groups });\n\n const elements = useMemo(() => {\n const leafComponents = {\n ...components,\n Text: components?.Text ?? DefaultText,\n Image: components?.Image ?? DefaultImage,\n };\n const Group = components?.Group ?? PassThroughGroup;\n return groups.map((group, groupIndex) => (\n <Group key={keys.groupKeys[groupIndex]} groupKey={group.groupKey} indices={group.indices}>\n {group.indices.map((partIndex) =>\n parts[partIndex]?.type === 'tool-call' ? (\n <GroupToolUI key={keys.partKeys[partIndex]}>\n <MessagePrimitive.PartByIndex index={partIndex} components={leafComponents} />\n </GroupToolUI>\n ) : (\n <MessagePrimitive.PartByIndex\n key={keys.partKeys[partIndex]}\n index={partIndex}\n components={leafComponents}\n />\n ),\n )}\n </Group>\n ));\n }, [components, groups, keys, parts]);\n\n if (parts.length === 0) {\n return <MessagePrimitive.Unstable_PartsGrouped groupingFunction={groupingFunction} components={components} />;\n }\n return <>{elements}</>;\n};\n"],"names":["jsxs","jsx","MessagePartPrimitive","useState","carryGroupedPartKeys","useAuiState","useMemo","GroupToolUI","MessagePrimitive"],"mappings":";;;;;;;AAmBA,MAAM,YAAyC,CAAA;AAG/C,MAAM,cAAwC,MAC5CA,2BAAAA,KAAC,KAAA,EAAE,OAAO,EAAE,YAAY,cACtB,UAAA;AAAA,EAAAC,+BAACC,MAAAA,qBAAqB,MAArB,EAA0B;AAAA,EAC3BD,2BAAAA,IAACC,MAAAA,qBAAqB,YAArB,EACC,UAAAD,2BAAAA,IAAC,QAAA,EAAK,OAAO,EAAE,YAAY,SAAA,GAAa,UAAA,KAAA,CAAK,EAAA,CAC/C;AAAA,GACF;AAEF,MAAM,eAA0C,MAAMA,2BAAAA,IAACC,2BAAqB,OAArB,CAAA,CAA2B;AAClF,MAAM,mBAAmB,CAAC,EAAE,SAAA,4DAA4C,UAAS;AAQjF,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AACF,GAGoB;AAClB,QAAM,CAAC,MAAM,OAAO,IAAIC,QAAAA,SAAiC,IAAI;AAC7D,MAAI,SAAS,QAAQ,KAAK,UAAU,SAAS,KAAK,WAAW,OAAQ,QAAO;AAC5E,QAAM,OAAOC,gBAAAA,qBAAqB,EAAE,UAAU,MAAM,OAAO,QAAQ;AACnE,UAAQ,IAAI;AACZ,SAAO;AACT;AAiBO,MAAM,sBAAoD,CAAC,EAAE,kBAAkB,iBAAiB;AACrG,QAAM,QAAQC,MAAAA,YAAY,CAAC,MAAM,EAAE,QAAQ,KAAK;AAChD,QAAM,SAASC,QAAAA;AAAAA,IACb,MAAO,MAAM,WAAW,IAAI,YAAY,iBAAiB,KAAK;AAAA,IAC9D,CAAC,OAAO,gBAAgB;AAAA,EAAA;AAE1B,QAAM,OAAO,mBAAmB,EAAE,OAAO,QAAQ;AAEjD,QAAM,WAAWA,QAAAA,QAAQ,MAAM;AAC7B,UAAM,iBAAiB;AAAA,MACrB,GAAG;AAAA,MACH,OAAM,yCAAY,SAAQ;AAAA,MAC1B,QAAO,yCAAY,UAAS;AAAA,IAAA;AAE9B,UAAM,SAAQ,yCAAY,UAAS;AACnC,WAAO,OAAO,IAAI,CAAC,OAAO,eACxBL,2BAAAA,IAAC,OAAA,EAAuC,UAAU,MAAM,UAAU,SAAS,MAAM,SAC9E,gBAAM,QAAQ;AAAA,MAAI,CAAC;;AAClB,4BAAM,SAAS,MAAf,mBAAkB,UAAS,cACzBA,2BAAAA,IAACM,iBAAAA,aAAA,EACC,UAAAN,+BAACO,MAAAA,iBAAiB,aAAjB,EAA6B,OAAO,WAAW,YAAY,eAAA,CAAgB,KAD5D,KAAK,SAAS,SAAS,CAEzC,IAEAP,2BAAAA;AAAAA,UAACO,MAAAA,iBAAiB;AAAA,UAAjB;AAAA,YAEC,OAAO;AAAA,YACP,YAAY;AAAA,UAAA;AAAA,UAFP,KAAK,SAAS,SAAS;AAAA,QAAA;AAAA;AAAA,IAG9B,EAEJ,GAbU,KAAK,UAAU,UAAU,CAcrC,CACD;AAAA,EACH,GAAG,CAAC,YAAY,QAAQ,MAAM,KAAK,CAAC;AAEpC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAOP,2BAAAA,IAACO,MAAAA,iBAAiB,uBAAjB,EAAuC,kBAAoC,WAAA,CAAwB;AAAA,EAC7G;AACA,+DAAU,UAAA,SAAA,CAAS;AACrB;;"}
@@ -0,0 +1,25 @@
1
+ import { MessagePrimitive } from '@assistant-ui/react';
2
+ import { type ComponentProps, type FC } from 'react';
3
+ import type { GroupingFunction } from './grouping-function';
4
+ export interface GroupedMessagePartsProps {
5
+ /** Groups the message's parts; each group renders through `components.Group`. */
6
+ groupingFunction: GroupingFunction;
7
+ /** The part and group renderers, as `MessagePrimitive.Unstable_PartsGrouped` takes them. */
8
+ components: ComponentProps<typeof MessagePrimitive.Unstable_PartsGrouped>['components'];
9
+ }
10
+ /**
11
+ * A message's parts grouped by `groupingFunction`, each group through
12
+ * `components.Group` — `MessagePrimitive.Unstable_PartsGrouped` with two
13
+ * differences:
14
+ *
15
+ * - What is mounted stays mounted when the message grows at its head (a page
16
+ * of older steps folding into the first loaded turn) as well as at its tail:
17
+ * groups and parts keep their React keys across renders (see
18
+ * {@link carryGroupedPartKeys}), where assistant-ui keys a group by its
19
+ * first part and a text part by its index.
20
+ * - A tool call's UI renders through {@link GroupToolUI}, so a collapsed
21
+ * `CollapsibleGroup` of a finished turn mounts it only when first needed.
22
+ *
23
+ * An empty message renders as `Unstable_PartsGrouped` renders it.
24
+ */
25
+ export declare const GroupedMessageParts: FC<GroupedMessagePartsProps>;