@agent-native/toolkit 0.213.1 → 0.214.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/agent-native.eject.json +9 -0
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +10 -4
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/agentkit/react/context.d.ts +13 -1
  6. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/context.js +3 -1
  8. package/dist/app/agentkit/react/context.js.map +1 -1
  9. package/dist/app/agentkit/react/index.d.ts +1 -0
  10. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  11. package/dist/app/agentkit/react/index.js +1 -0
  12. package/dist/app/agentkit/react/index.js.map +1 -1
  13. package/dist/app/agentkit/react/message-content-order.d.ts +21 -0
  14. package/dist/app/agentkit/react/message-content-order.d.ts.map +1 -0
  15. package/dist/app/agentkit/react/message-content-order.js +173 -0
  16. package/dist/app/agentkit/react/message-content-order.js.map +1 -0
  17. package/dist/app/agentkit/react/root.d.ts +3 -1
  18. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  19. package/dist/app/agentkit/react/root.js +2 -2
  20. package/dist/app/agentkit/react/root.js.map +1 -1
  21. package/dist/app/agentkit/react/styles.css +2 -2
  22. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  23. package/dist/app/chat/AgentKitAssistantChat.js +7 -2
  24. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  25. package/dist/app/chat/AgentPanel.d.ts +14 -2
  26. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  27. package/dist/app/chat/AgentPanel.js +22 -5
  28. package/dist/app/chat/AgentPanel.js.map +1 -1
  29. package/dist/app/chat/chat/surface-types.d.ts +8 -0
  30. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  31. package/dist/app/chat/chat/surface-types.js.map +1 -1
  32. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  33. package/dist/app/chat/chat/widgets/DataChartRenderer.js +127 -30
  34. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  35. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -2
  36. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  37. package/dist/app/chat/chat/widgets/DataChartWidget.js +14 -9
  38. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  39. package/dist/app/chat/chat/widgets/DataInsightsWidget.js +1 -1
  40. package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
  41. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts +3 -2
  42. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts.map +1 -1
  43. package/dist/app/chat/chat/widgets/DataTableWidget.js +10 -66
  44. package/dist/app/chat/chat/widgets/DataTableWidget.js.map +1 -1
  45. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +11 -0
  46. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -0
  47. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +131 -0
  48. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -0
  49. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
  50. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
  51. package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  52. package/dist/app/chat/mcp-apps/McpAppRenderer.js +6 -5
  53. package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
  54. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts +2 -0
  55. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts.map +1 -0
  56. package/dist/app/chat/mcp-apps/mcp-app-theme.js +11 -0
  57. package/dist/app/chat/mcp-apps/mcp-app-theme.js.map +1 -0
  58. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  59. package/dist/app/i18n/catalogs/ar-SA.js +8 -0
  60. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  61. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  62. package/dist/app/i18n/catalogs/de-DE.js +8 -0
  63. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  64. package/dist/app/i18n/catalogs/en-US.d.ts +8 -0
  65. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  66. package/dist/app/i18n/catalogs/en-US.js +8 -0
  67. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  68. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  69. package/dist/app/i18n/catalogs/es-ES.js +8 -0
  70. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  71. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  72. package/dist/app/i18n/catalogs/fr-FR.js +8 -0
  73. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  74. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  75. package/dist/app/i18n/catalogs/hi-IN.js +8 -0
  76. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  77. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  78. package/dist/app/i18n/catalogs/ja-JP.js +8 -0
  79. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  80. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  81. package/dist/app/i18n/catalogs/ko-KR.js +8 -0
  82. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  83. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  84. package/dist/app/i18n/catalogs/pt-BR.js +8 -0
  85. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  86. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  87. package/dist/app/i18n/catalogs/zh-CN.js +8 -0
  88. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  89. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  90. package/dist/app/i18n/catalogs/zh-TW.js +8 -0
  91. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  92. package/dist/app/providers/AppProviders.d.ts +2 -1
  93. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  94. package/dist/app/providers/AppProviders.js +4 -3
  95. package/dist/app/providers/AppProviders.js.map +1 -1
  96. package/dist/app/providers/ThemeFamilySync.d.ts +2 -0
  97. package/dist/app/providers/ThemeFamilySync.d.ts.map +1 -0
  98. package/dist/app/providers/ThemeFamilySync.js +6 -0
  99. package/dist/app/providers/ThemeFamilySync.js.map +1 -0
  100. package/dist/app/settings/account/PreferencesSettings.d.ts.map +1 -1
  101. package/dist/app/settings/account/PreferencesSettings.js +2 -1
  102. package/dist/app/settings/account/PreferencesSettings.js.map +1 -1
  103. package/dist/app/settings/account/ThemeFamilySettings.d.ts +2 -0
  104. package/dist/app/settings/account/ThemeFamilySettings.d.ts.map +1 -0
  105. package/dist/app/settings/account/ThemeFamilySettings.js +31 -0
  106. package/dist/app/settings/account/ThemeFamilySettings.js.map +1 -0
  107. package/dist/app/settings/account/search-entries.d.ts.map +1 -1
  108. package/dist/app/settings/account/search-entries.js +6 -0
  109. package/dist/app/settings/account/search-entries.js.map +1 -1
  110. package/dist/app/shared/DefaultSpinner.d.ts.map +1 -1
  111. package/dist/app/shared/DefaultSpinner.js +4 -5
  112. package/dist/app/shared/DefaultSpinner.js.map +1 -1
  113. package/dist/app/shared/ErrorBoundary.d.ts +1 -0
  114. package/dist/app/shared/ErrorBoundary.d.ts.map +1 -1
  115. package/dist/app/shared/ErrorBoundary.js +27 -12
  116. package/dist/app/shared/ErrorBoundary.js.map +1 -1
  117. package/dist/app/shared/PoweredByBadge.d.ts.map +1 -1
  118. package/dist/app/shared/PoweredByBadge.js +8 -49
  119. package/dist/app/shared/PoweredByBadge.js.map +1 -1
  120. package/dist/app/styles/agent-conversation.css +0 -23
  121. package/dist/app/styles/agent-native.css +1 -1
  122. package/dist/app/styles/theme-families.css +66 -0
  123. package/dist/composer/ComposerContextBanner.d.ts +22 -0
  124. package/dist/composer/ComposerContextBanner.d.ts.map +1 -0
  125. package/dist/composer/ComposerContextBanner.js +9 -0
  126. package/dist/composer/ComposerContextBanner.js.map +1 -0
  127. package/dist/composer/ComposerModePicker.d.ts +26 -0
  128. package/dist/composer/ComposerModePicker.d.ts.map +1 -0
  129. package/dist/composer/ComposerModePicker.js +14 -0
  130. package/dist/composer/ComposerModePicker.js.map +1 -0
  131. package/dist/composer/index.d.ts +2 -0
  132. package/dist/composer/index.d.ts.map +1 -1
  133. package/dist/composer/index.js +2 -0
  134. package/dist/composer/index.js.map +1 -1
  135. package/dist/styles.css +1 -0
  136. package/package.json +3 -3
  137. package/src/app/agentkit/react/components.spec.tsx +12 -0
  138. package/src/app/agentkit/react/components.tsx +41 -22
  139. package/src/app/agentkit/react/context.tsx +17 -0
  140. package/src/app/agentkit/react/index.ts +1 -0
  141. package/src/app/agentkit/react/lifecycle.spec.tsx +116 -1
  142. package/src/app/agentkit/react/message-content-order.spec.ts +190 -0
  143. package/src/app/agentkit/react/message-content-order.ts +246 -0
  144. package/src/app/agentkit/react/root.tsx +4 -0
  145. package/src/app/agentkit/react/styles.css +2 -2
  146. package/src/app/chat/AgentKitAssistantChat.spec.tsx +19 -0
  147. package/src/app/chat/AgentKitAssistantChat.tsx +10 -1
  148. package/src/app/chat/AgentPanel.header.spec.ts +72 -14
  149. package/src/app/chat/AgentPanel.tsx +97 -53
  150. package/src/app/chat/chat/surface-types.ts +8 -0
  151. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +217 -0
  152. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +306 -68
  153. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +329 -0
  154. package/src/app/chat/chat/widgets/DataChartWidget.ssr.spec.tsx +25 -0
  155. package/src/app/chat/chat/widgets/DataChartWidget.tsx +48 -33
  156. package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +2 -2
  157. package/src/app/chat/chat/widgets/DataTableWidget.spec.tsx +37 -3
  158. package/src/app/chat/chat/widgets/DataTableWidget.tsx +22 -86
  159. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +301 -0
  160. package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -5
  161. package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -5
  162. package/src/app/chat/mcp-apps/mcp-app-theme.spec.ts +30 -0
  163. package/src/app/chat/mcp-apps/mcp-app-theme.ts +11 -0
  164. package/src/app/i18n/catalogs/ar-SA.ts +9 -0
  165. package/src/app/i18n/catalogs/de-DE.ts +9 -0
  166. package/src/app/i18n/catalogs/en-US.ts +9 -0
  167. package/src/app/i18n/catalogs/es-ES.ts +10 -0
  168. package/src/app/i18n/catalogs/fr-FR.ts +10 -0
  169. package/src/app/i18n/catalogs/hi-IN.ts +9 -0
  170. package/src/app/i18n/catalogs/ja-JP.ts +9 -0
  171. package/src/app/i18n/catalogs/ko-KR.ts +9 -0
  172. package/src/app/i18n/catalogs/pt-BR.ts +9 -0
  173. package/src/app/i18n/catalogs/zh-CN.ts +8 -0
  174. package/src/app/i18n/catalogs/zh-TW.ts +9 -0
  175. package/src/app/providers/AppProviders.spec.tsx +71 -0
  176. package/src/app/providers/AppProviders.tsx +10 -0
  177. package/src/app/providers/ThemeFamilySync.tsx +6 -0
  178. package/src/app/settings/account/PreferencesSettings.spec.tsx +13 -2
  179. package/src/app/settings/account/PreferencesSettings.tsx +8 -0
  180. package/src/app/settings/account/ThemeFamilySettings.tsx +61 -0
  181. package/src/app/settings/account/search-entries.ts +6 -0
  182. package/src/app/shared/DefaultSpinner.spec.tsx +14 -0
  183. package/src/app/shared/DefaultSpinner.tsx +4 -5
  184. package/src/app/shared/ErrorBoundary.spec.tsx +45 -2
  185. package/src/app/shared/ErrorBoundary.tsx +37 -12
  186. package/src/app/shared/PoweredByBadge.spec.tsx +16 -0
  187. package/src/app/shared/PoweredByBadge.tsx +8 -50
  188. package/src/app/styles/agent-conversation.css +0 -23
  189. package/src/app/styles/agent-native.css +1 -1
  190. package/src/app/styles/agent-native.spec.ts +18 -0
  191. package/src/app/styles/theme-families.css +66 -0
  192. package/src/app/styles/theme-families.spec.ts +29 -0
  193. package/src/composer/ComposerContextBanner.spec.tsx +74 -0
  194. package/src/composer/ComposerContextBanner.tsx +72 -0
  195. package/src/composer/ComposerModePicker.spec.tsx +101 -0
  196. package/src/composer/ComposerModePicker.tsx +98 -0
  197. package/src/composer/index.ts +9 -0
  198. package/src/styles.css +1 -0
@@ -0,0 +1,190 @@
1
+ import type {
2
+ AgentEvent,
3
+ AgentMessage,
4
+ AgentWidget,
5
+ } from "@agent-native/agentkit/protocol";
6
+ import { describe, expect, it } from "vitest";
7
+
8
+ import {
9
+ orderMessageContent,
10
+ type AgentMessageContentOrderResolver,
11
+ } from "./message-content-order.js";
12
+
13
+ const widget: AgentWidget = {
14
+ id: "chart-1",
15
+ kind: "core.data-chart",
16
+ data: { toolCallId: "tool-1" },
17
+ };
18
+
19
+ function event(
20
+ sequence: number,
21
+ payload: Omit<
22
+ AgentEvent,
23
+ "id" | "threadId" | "runId" | "sequence" | "occurredAt"
24
+ >,
25
+ ): AgentEvent {
26
+ return {
27
+ id: `event-${sequence}`,
28
+ threadId: "thread-1",
29
+ runId: "run-1",
30
+ sequence,
31
+ occurredAt: `2026-10-21T00:00:0${sequence}.000Z`,
32
+ ...payload,
33
+ } as AgentEvent;
34
+ }
35
+
36
+ describe("orderMessageContent", () => {
37
+ it("keeps an attached chart between the streamed introduction and conclusion", () => {
38
+ const message: AgentMessage = {
39
+ id: "assistant-1",
40
+ role: "assistant",
41
+ status: "complete",
42
+ parts: [
43
+ {
44
+ type: "text",
45
+ text: "Intro before chart. Conclusion after chart.",
46
+ format: "markdown",
47
+ },
48
+ ],
49
+ };
50
+ const events: AgentEvent[] = [
51
+ event(1, {
52
+ type: "message.created",
53
+ message: { ...message, status: "streaming", parts: [] },
54
+ }),
55
+ event(2, {
56
+ type: "message.delta",
57
+ messageId: message.id,
58
+ text: "Intro before chart. ",
59
+ format: "markdown",
60
+ }),
61
+ event(3, { type: "widget.created", messageId: message.id, widget }),
62
+ event(4, {
63
+ type: "message.delta",
64
+ messageId: message.id,
65
+ text: "Conclusion after chart.",
66
+ format: "markdown",
67
+ }),
68
+ event(5, { type: "message.completed", message }),
69
+ ];
70
+
71
+ const result = orderMessageContent(
72
+ message,
73
+ message.parts,
74
+ [widget],
75
+ events,
76
+ );
77
+
78
+ expect(result.map((item) => item.type)).toEqual(["part", "widget", "part"]);
79
+ expect(result[0]).toMatchObject({
80
+ type: "part",
81
+ part: { text: "Intro before chart. " },
82
+ });
83
+ expect(result[1]).toMatchObject({ type: "widget", widget });
84
+ expect(result[2]).toMatchObject({
85
+ type: "part",
86
+ part: { text: "Conclusion after chart." },
87
+ });
88
+ });
89
+
90
+ it("keeps legacy append order when streamed text cannot be matched exactly", () => {
91
+ const message: AgentMessage = {
92
+ id: "assistant-2",
93
+ role: "assistant",
94
+ parts: [{ type: "text", text: "Final answer" }],
95
+ };
96
+
97
+ const result = orderMessageContent(message, message.parts, [widget], []);
98
+
99
+ expect(result.map((item) => item.type)).toEqual(["part", "widget"]);
100
+ });
101
+
102
+ it("lets a host split text around a widget when stream order is unavailable", () => {
103
+ const message: AgentMessage = {
104
+ id: "assistant-3",
105
+ role: "assistant",
106
+ parts: [{ type: "text", text: "Intro. Summary." }],
107
+ };
108
+ const textPart = message.parts[0];
109
+ if (textPart?.type !== "text") throw new Error("Expected text part");
110
+
111
+ const result = orderMessageContent(
112
+ message,
113
+ message.parts,
114
+ [widget],
115
+ [],
116
+ ({ parts, widgets }) => {
117
+ const part = parts[0];
118
+ const chart = widgets[0];
119
+ if (part?.type !== "text" || !chart) return [];
120
+ return [
121
+ {
122
+ key: "intro",
123
+ type: "part",
124
+ part: { ...part, text: "Intro. " },
125
+ },
126
+ { key: chart.id, type: "widget", widget: chart },
127
+ {
128
+ key: "summary",
129
+ type: "part",
130
+ part: { ...part, text: "Summary." },
131
+ },
132
+ ];
133
+ },
134
+ );
135
+
136
+ expect(result.map((item) => item.type)).toEqual(["part", "widget", "part"]);
137
+ expect(result[0]).toMatchObject({ part: { text: "Intro. " } });
138
+ expect(result[1]).toMatchObject({ type: "widget", widget });
139
+ expect(result[2]).toMatchObject({ part: { text: "Summary." } });
140
+ expect(textPart.text).toBe("Intro. Summary.");
141
+ });
142
+
143
+ it("falls back when the host ordering result drops all visible content", () => {
144
+ const message: AgentMessage = {
145
+ id: "assistant-4",
146
+ role: "assistant",
147
+ parts: [{ type: "text", text: "A saved answer" }],
148
+ };
149
+
150
+ const result = orderMessageContent(
151
+ message,
152
+ message.parts,
153
+ [widget],
154
+ [],
155
+ () => [],
156
+ );
157
+
158
+ expect(result).toEqual([
159
+ {
160
+ key: "assistant-4-0",
161
+ type: "part",
162
+ part: message.parts[0],
163
+ },
164
+ { key: widget.id, type: "widget", widget },
165
+ ]);
166
+ });
167
+
168
+ it("falls back when the host ordering result loses text or a widget", () => {
169
+ const message: AgentMessage = {
170
+ id: "assistant-5",
171
+ role: "assistant",
172
+ parts: [{ type: "text", text: "Visible answer" }],
173
+ };
174
+ const incompleteOrders: AgentMessageContentOrderResolver[] = [
175
+ () => [{ key: widget.id, type: "widget" as const, widget }],
176
+ ({ defaultOrder }) => defaultOrder.slice(0, 1),
177
+ ];
178
+
179
+ for (const resolveOrder of incompleteOrders) {
180
+ const result = orderMessageContent(
181
+ message,
182
+ message.parts,
183
+ [widget],
184
+ [],
185
+ resolveOrder,
186
+ );
187
+ expect(result.map((item) => item.type)).toEqual(["part", "widget"]);
188
+ }
189
+ });
190
+ });
@@ -0,0 +1,246 @@
1
+ import type {
2
+ AgentEvent,
3
+ AgentMessage,
4
+ AgentMessagePart,
5
+ AgentWidget,
6
+ } from "@agent-native/agentkit/protocol";
7
+
8
+ export type AgentMessageContentItem =
9
+ | { key: string; type: "part"; part: AgentMessagePart }
10
+ | { key: string; type: "widget"; widget: AgentWidget };
11
+
12
+ export interface AgentMessageContentOrderInput {
13
+ message: AgentMessage;
14
+ parts: AgentMessagePart[];
15
+ widgets: AgentWidget[];
16
+ events: AgentEvent[];
17
+ defaultOrder: AgentMessageContentItem[];
18
+ }
19
+
20
+ export type AgentMessageContentOrderResolver = (
21
+ input: AgentMessageContentOrderInput,
22
+ ) => AgentMessageContentItem[];
23
+
24
+ type TextContentOrderItem = Extract<
25
+ AgentMessageContentItem,
26
+ { type: "part" }
27
+ > & {
28
+ part: Extract<AgentMessagePart, { type: "text" }>;
29
+ };
30
+
31
+ function isTextContentOrderItem(
32
+ item: AgentMessageContentItem | null | undefined,
33
+ ): item is TextContentOrderItem {
34
+ return (
35
+ item?.type === "part" &&
36
+ typeof item.key === "string" &&
37
+ item.part?.type === "text" &&
38
+ typeof item.part.text === "string"
39
+ );
40
+ }
41
+
42
+ function resolvedItemsPreserveContent(
43
+ defaultOrder: AgentMessageContentItem[],
44
+ resolvedOrder: AgentMessageContentItem[],
45
+ ): AgentMessageContentItem[] | undefined {
46
+ if (!Array.isArray(resolvedOrder)) return undefined;
47
+
48
+ const defaultText = defaultOrder
49
+ .filter(isTextContentOrderItem)
50
+ .map((item) => item.part.text)
51
+ .join("");
52
+ const resolvedText = resolvedOrder
53
+ .filter(isTextContentOrderItem)
54
+ .map((item) => item.part.text)
55
+ .join("");
56
+ if (defaultText !== resolvedText) return undefined;
57
+
58
+ const expectedItems = new Map<string, AgentMessageContentItem[]>();
59
+ for (const item of defaultOrder) {
60
+ if (item.type === "part" && item.part.type === "text") continue;
61
+ const identity = `${item.type}:${item.key}`;
62
+ const matchingItems = expectedItems.get(identity) ?? [];
63
+ matchingItems.push(item);
64
+ expectedItems.set(identity, matchingItems);
65
+ }
66
+
67
+ const preservedItems: AgentMessageContentItem[] = [];
68
+ for (const item of resolvedOrder) {
69
+ if (!item || typeof item !== "object") return undefined;
70
+ if (item.type === "part" && item.part?.type === "text") {
71
+ if (typeof item.key !== "string" || typeof item.part.text !== "string") {
72
+ return undefined;
73
+ }
74
+ preservedItems.push(item);
75
+ continue;
76
+ }
77
+
78
+ if (
79
+ (item.type !== "part" && item.type !== "widget") ||
80
+ typeof item.key !== "string"
81
+ ) {
82
+ return undefined;
83
+ }
84
+
85
+ const identity = `${item.type}:${item.key}`;
86
+ const matchingItems = expectedItems.get(identity);
87
+ const original = matchingItems?.shift();
88
+ if (!original) return undefined;
89
+ preservedItems.push(original);
90
+ }
91
+
92
+ if (
93
+ [...expectedItems.values()].some((matchingItems) => matchingItems.length)
94
+ ) {
95
+ return undefined;
96
+ }
97
+ return preservedItems;
98
+ }
99
+
100
+ /** Uses the stream timeline only when it reconstructs the saved text exactly. */
101
+ export function orderMessageContent(
102
+ message: AgentMessage,
103
+ visibleParts: AgentMessagePart[],
104
+ attachedWidgets: AgentWidget[],
105
+ events: AgentEvent[],
106
+ resolveOrder?: AgentMessageContentOrderResolver,
107
+ ): AgentMessageContentItem[] {
108
+ const fallback = [
109
+ ...visibleParts.map((part, index) => ({
110
+ key: `${message.id}-${index}`,
111
+ type: "part" as const,
112
+ part,
113
+ })),
114
+ ...attachedWidgets.map((widget) => ({
115
+ key: widget.id,
116
+ type: "widget" as const,
117
+ widget,
118
+ })),
119
+ ];
120
+ const resolve = (defaultOrder: AgentMessageContentItem[]) => {
121
+ const resolvedOrder = resolveOrder?.({
122
+ message,
123
+ parts: visibleParts,
124
+ widgets: attachedWidgets,
125
+ events,
126
+ defaultOrder,
127
+ });
128
+ if (resolvedOrder == null) return defaultOrder;
129
+ return (
130
+ resolvedItemsPreserveContent(defaultOrder, resolvedOrder) ?? defaultOrder
131
+ );
132
+ };
133
+
134
+ if (
135
+ attachedWidgets.length === 0 ||
136
+ message.parts.length !== 1 ||
137
+ message.parts[0]?.type !== "text" ||
138
+ visibleParts.length !== 1 ||
139
+ visibleParts[0]?.type !== "text" ||
140
+ visibleParts[0].text !== message.parts[0].text
141
+ ) {
142
+ return resolve(fallback);
143
+ }
144
+
145
+ const widgetIds = new Set(attachedWidgets.map((widget) => widget.id));
146
+ const widgetOffsets = new Map<string, number>();
147
+ const widgetEventOrder = new Map<string, number>();
148
+ let reconstructedText = "";
149
+ let textOffset = 0;
150
+
151
+ for (const [eventIndex, event] of events.entries()) {
152
+ if (event.type === "message.created" && event.message.id === message.id) {
153
+ const createdParts = event.message.parts;
154
+ if (
155
+ createdParts.length > 1 ||
156
+ createdParts.some((part) => part.type !== "text")
157
+ ) {
158
+ return resolve(fallback);
159
+ }
160
+ reconstructedText =
161
+ createdParts[0]?.type === "text" ? createdParts[0].text : "";
162
+ textOffset = reconstructedText.length;
163
+ continue;
164
+ }
165
+
166
+ if (event.type === "message.delta" && event.messageId === message.id) {
167
+ reconstructedText += event.text;
168
+ textOffset += event.text.length;
169
+ continue;
170
+ }
171
+
172
+ if (
173
+ (event.type === "widget.created" || event.type === "widget.updated") &&
174
+ event.messageId === message.id &&
175
+ widgetIds.has(event.widget.id) &&
176
+ !widgetOffsets.has(event.widget.id)
177
+ ) {
178
+ widgetOffsets.set(event.widget.id, textOffset);
179
+ widgetEventOrder.set(event.widget.id, eventIndex);
180
+ }
181
+ }
182
+
183
+ const fullText = message.parts[0].text;
184
+ if (
185
+ reconstructedText !== fullText ||
186
+ widgetOffsets.size !== attachedWidgets.length ||
187
+ [...widgetOffsets.values()].some((offset) => {
188
+ if (offset === 0 || offset === fullText.length) return false;
189
+ const preceding = fullText.charCodeAt(offset - 1);
190
+ const following = fullText.charCodeAt(offset);
191
+ return (
192
+ preceding >= 0xd800 &&
193
+ preceding <= 0xdbff &&
194
+ following >= 0xdc00 &&
195
+ following <= 0xdfff
196
+ );
197
+ })
198
+ ) {
199
+ return resolve(fallback);
200
+ }
201
+
202
+ const widgetsAtOffset = new Map<number, AgentWidget[]>();
203
+ for (const widget of attachedWidgets) {
204
+ const offset = widgetOffsets.get(widget.id)!;
205
+ const widgets = widgetsAtOffset.get(offset) ?? [];
206
+ widgets.push(widget);
207
+ widgetsAtOffset.set(offset, widgets);
208
+ }
209
+ for (const widgets of widgetsAtOffset.values()) {
210
+ widgets.sort(
211
+ (first, second) =>
212
+ widgetEventOrder.get(first.id)! - widgetEventOrder.get(second.id)!,
213
+ );
214
+ }
215
+
216
+ const insertions = [...widgetsAtOffset.entries()].sort(
217
+ ([first], [second]) => first - second,
218
+ );
219
+ const part = visibleParts[0];
220
+ if (part.type !== "text") return resolve(fallback);
221
+
222
+ const ordered: AgentMessageContentItem[] = [];
223
+ let cursor = 0;
224
+ for (const [offset, widgets] of insertions) {
225
+ if (offset > cursor) {
226
+ ordered.push({
227
+ key: `${message.id}-text-${cursor}`,
228
+ type: "part",
229
+ part: { ...part, text: part.text.slice(cursor, offset) },
230
+ });
231
+ }
232
+ for (const widget of widgets) {
233
+ ordered.push({ key: widget.id, type: "widget", widget });
234
+ }
235
+ cursor = offset;
236
+ }
237
+ if (cursor < part.text.length) {
238
+ ordered.push({
239
+ key: `${message.id}-text-${cursor}`,
240
+ type: "part",
241
+ part: { ...part, text: part.text.slice(cursor) },
242
+ });
243
+ }
244
+
245
+ return resolve(ordered);
246
+ }
@@ -30,6 +30,7 @@ import {
30
30
  type AgentKitRunUsageLoader,
31
31
  type AgentKitSlots,
32
32
  } from "./context.js";
33
+ import type { AgentMessageContentOrderResolver } from "./message-content-order.js";
33
34
 
34
35
  const mountedManagedClients = new WeakMap<AgentKitClient, number>();
35
36
 
@@ -84,6 +85,7 @@ export interface AgentKitRootBaseProps {
84
85
  request: AgentConnectionRequest,
85
86
  ) => Promise<AgentConnectionResponse>;
86
87
  onRenderError?: (failure: AgentKitRenderFailure) => void;
88
+ resolveMessageContentOrder?: AgentMessageContentOrderResolver;
87
89
  onClientEffect?: (effect: {
88
90
  type: "client.effect" | "client.deeplink";
89
91
  name: string;
@@ -113,6 +115,7 @@ export function AgentKitRoot({
113
115
  loadRunUsage,
114
116
  onConnectionRequest,
115
117
  onRenderError,
118
+ resolveMessageContentOrder,
116
119
  onClientEffect,
117
120
  load = "auto",
118
121
  onLoadError,
@@ -290,6 +293,7 @@ export function AgentKitRoot({
290
293
  loadRunUsage={loadRunUsage}
291
294
  onConnectionRequest={onConnectionRequest}
292
295
  onRenderError={onRenderError}
296
+ resolveMessageContentOrder={resolveMessageContentOrder}
293
297
  onClientEffect={onClientEffect}
294
298
  >
295
299
  {children}
@@ -1216,7 +1216,7 @@ body:has(> [data-agent-native-composer-popover="true"]) > .fixed.inset-0 {
1216
1216
  inline-size: fit-content;
1217
1217
  max-inline-size: 100%;
1218
1218
  align-items: center;
1219
- gap: 0.375rem;
1219
+ gap: 1rem;
1220
1220
  }
1221
1221
 
1222
1222
  .agentkit-activities {
@@ -1418,7 +1418,7 @@ body:has(> [data-agent-native-composer-popover="true"]) > .fixed.inset-0 {
1418
1418
  }
1419
1419
 
1420
1420
  .agentkit-activities-summary > .agentkit-summary-chevron {
1421
- opacity: 0;
1421
+ opacity: 1;
1422
1422
  }
1423
1423
 
1424
1424
  .agentkit-activities[data-expandable="true"]
@@ -3348,6 +3348,25 @@ describe("AgentKitAssistantChat host behavior", () => {
3348
3348
  expect(container.querySelector(".agentkit-home-intro")).toBeNull();
3349
3349
  });
3350
3350
 
3351
+ it("renders the shared context banner slot alongside the existing composer slot", async () => {
3352
+ await mount(
3353
+ baseProps({
3354
+ composerSlot: <div data-testid="legacy-composer-slot">Notice</div>,
3355
+ composerContextBannerSlot: (
3356
+ <div data-testid="composer-context-banner-slot">Add context</div>
3357
+ ),
3358
+ }),
3359
+ );
3360
+
3361
+ const composer = container.querySelector(".agentkit-host-composer")!;
3362
+ expect(
3363
+ composer.querySelector('[data-testid="legacy-composer-slot"]'),
3364
+ ).not.toBeNull();
3365
+ expect(
3366
+ composer.querySelector('[data-testid="composer-context-banner-slot"]'),
3367
+ ).not.toBeNull();
3368
+ });
3369
+
3351
3370
  it("puts the beginning revert before messages inside the scrollable transcript", async () => {
3352
3371
  chatMocks.thread.messages = [
3353
3372
  { id: "first-message", role: "user", parts: [] },
@@ -2093,9 +2093,17 @@ export const AgentKitAssistantChat = forwardRef<
2093
2093
  threadId={threadId}
2094
2094
  load={agentKitLoad}
2095
2095
  onLoadError={onThreadRestoreLoadError}
2096
- slots={agentKitSlots}
2096
+ slots={
2097
+ props.workedForSummarySlot
2098
+ ? {
2099
+ ...agentKitSlots,
2100
+ workedForSummary: props.workedForSummarySlot,
2101
+ }
2102
+ : agentKitSlots
2103
+ }
2097
2104
  registry={agentKitRegistry}
2098
2105
  labels={labels}
2106
+ resolveMessageContentOrder={props.resolveMessageContentOrder}
2099
2107
  branchNavigation={props.branchNavigation}
2100
2108
  loadRunUsage={loadRunUsage}
2101
2109
  buildFeedbackReport={formatFeedbackReport}
@@ -5979,6 +5987,7 @@ function AgentKitComposerSurface({
5979
5987
  })}
5980
5988
  />
5981
5989
  {props.composerSlot}
5990
+ {props.composerContextBannerSlot}
5982
5991
  {showHomeIntro ? (
5983
5992
  <div className="agentkit-home-intro">{props.homeIntroSlot}</div>
5984
5993
  ) : null}
@@ -31,6 +31,8 @@ import {
31
31
  shouldHandleAgentSidebarToggle,
32
32
  shouldHandleAgentPanelChatShortcut,
33
33
  shouldShowAgentPanelFullViewAction,
34
+ resolveAgentPanelPageHeaderActions,
35
+ resolveAgentPanelPageToolbarSlot,
34
36
  shouldShowAgentPanelPageHeader,
35
37
  shouldShowAgentPanelPageNewChatButton,
36
38
  shouldShowAgentPanelChatTabBar,
@@ -350,22 +352,60 @@ describe("AgentPanel header tab visibility", () => {
350
352
  expect(shouldDefaultAgentChatSurfacePageHeader(undefined)).toBe(false);
351
353
  });
352
354
 
353
- it("exposes page header composition without moving it into app chrome", () => {
354
- const source = readSource("src/app/chat/AgentPanel.tsx", "utf8");
355
+ it("keeps share and host toolbar actions when the title menu is hidden", async () => {
356
+ expect(
357
+ resolveAgentPanelPageHeaderActions({
358
+ showPageTitleMenu: false,
359
+ canShareActiveTab: true,
360
+ }),
361
+ ).toEqual({ showPageTitleMenu: false, showShareAction: true });
355
362
 
356
- expect(source).toContain('data-agent-page-chat-header=""');
357
- expect(source).toContain("pageHeaderLeadingSlot");
358
- expect(source).toContain("pageToolbarSlot");
359
- expect(source).toContain("activeTab?.label");
360
- expect(source).toContain('data-agent-page-title-menu=""');
361
- expect(source).toContain("<IconShare3 size={15}");
362
- expect(source.indexOf("<IconShare3 size={15}")).toBeLessThan(
363
- source.indexOf(
364
- "{pageToolbarSlot}",
365
- source.indexOf("triggerContent={<IconShare3"),
366
- ),
363
+ const container = document.createElement("div");
364
+ document.body.append(container);
365
+ const root = createRoot(container);
366
+ const toggleHistory = vi.fn();
367
+ const clearActiveTab = vi.fn();
368
+ const toolbar = resolveAgentPanelPageToolbarSlot(
369
+ (actions) =>
370
+ React.createElement(
371
+ "div",
372
+ null,
373
+ React.createElement(
374
+ "button",
375
+ { onClick: actions.toggleHistory },
376
+ actions.showHistory ? "Hide chats" : "All chats",
377
+ ),
378
+ actions.activeTabMessageCount > 0
379
+ ? React.createElement(
380
+ "button",
381
+ { onClick: actions.clearActiveTab },
382
+ "Clear chat",
383
+ )
384
+ : null,
385
+ ),
386
+ {
387
+ activeTabMessageCount: 2,
388
+ clearActiveTab,
389
+ showHistory: false,
390
+ toggleHistory,
391
+ },
367
392
  );
368
- expect(source).toContain("border-b border-border/70");
393
+
394
+ try {
395
+ await act(async () => root.render(toolbar));
396
+ const buttons = container.querySelectorAll("button");
397
+ expect([...buttons].map((button) => button.textContent)).toEqual([
398
+ "All chats",
399
+ "Clear chat",
400
+ ]);
401
+ await act(async () => buttons[0]?.click());
402
+ await act(async () => buttons[1]?.click());
403
+ expect(toggleHistory).toHaveBeenCalledOnce();
404
+ expect(clearActiveTab).toHaveBeenCalledOnce();
405
+ } finally {
406
+ await act(async () => root.unmount());
407
+ container.remove();
408
+ }
369
409
  });
370
410
 
371
411
  it("normalizes legacy and unknown modes back to chat", () => {
@@ -1193,6 +1233,24 @@ describe("AgentChatSurface chrome defaults", () => {
1193
1233
  expect(surface.props.showHeader).toBe(true);
1194
1234
  expect(surface.props.showTabBar).toBe(true);
1195
1235
  });
1236
+
1237
+ it("allows a page host to hide only the title menu", () => {
1238
+ const surface = AgentChatSurface({
1239
+ mode: "page",
1240
+ showPageTitleMenu: false,
1241
+ });
1242
+ const panel = surface.props.children[1];
1243
+ const headerActions = resolveAgentPanelPageHeaderActions({
1244
+ showPageTitleMenu: false,
1245
+ canShareActiveTab: true,
1246
+ });
1247
+
1248
+ expect(panel.props.showPageTitleMenu).toBe(false);
1249
+ expect(headerActions).toEqual({
1250
+ showPageTitleMenu: false,
1251
+ showShareAction: true,
1252
+ });
1253
+ });
1196
1254
  });
1197
1255
 
1198
1256
  describe("AgentPanel stale lazy chunk recovery", () => {