@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.
- package/agent-native.eject.json +9 -0
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +10 -4
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/context.d.ts +13 -1
- package/dist/app/agentkit/react/context.d.ts.map +1 -1
- package/dist/app/agentkit/react/context.js +3 -1
- package/dist/app/agentkit/react/context.js.map +1 -1
- package/dist/app/agentkit/react/index.d.ts +1 -0
- package/dist/app/agentkit/react/index.d.ts.map +1 -1
- package/dist/app/agentkit/react/index.js +1 -0
- package/dist/app/agentkit/react/index.js.map +1 -1
- package/dist/app/agentkit/react/message-content-order.d.ts +21 -0
- package/dist/app/agentkit/react/message-content-order.d.ts.map +1 -0
- package/dist/app/agentkit/react/message-content-order.js +173 -0
- package/dist/app/agentkit/react/message-content-order.js.map +1 -0
- package/dist/app/agentkit/react/root.d.ts +3 -1
- package/dist/app/agentkit/react/root.d.ts.map +1 -1
- package/dist/app/agentkit/react/root.js +2 -2
- package/dist/app/agentkit/react/root.js.map +1 -1
- package/dist/app/agentkit/react/styles.css +2 -2
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +7 -2
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/AgentPanel.d.ts +14 -2
- package/dist/app/chat/AgentPanel.d.ts.map +1 -1
- package/dist/app/chat/AgentPanel.js +22 -5
- package/dist/app/chat/AgentPanel.js.map +1 -1
- package/dist/app/chat/chat/surface-types.d.ts +8 -0
- package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
- package/dist/app/chat/chat/surface-types.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartRenderer.js +127 -30
- package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -2
- package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartWidget.js +14 -9
- package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataInsightsWidget.js +1 -1
- package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataTableWidget.d.ts +3 -2
- package/dist/app/chat/chat/widgets/DataTableWidget.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataTableWidget.js +10 -66
- package/dist/app/chat/chat/widgets/DataTableWidget.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +11 -0
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -0
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +131 -0
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -0
- package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
- package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
- package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/app/chat/mcp-apps/McpAppRenderer.js +6 -5
- package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts +2 -0
- package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts.map +1 -0
- package/dist/app/chat/mcp-apps/mcp-app-theme.js +11 -0
- package/dist/app/chat/mcp-apps/mcp-app-theme.js.map +1 -0
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +8 -0
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +8 -0
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +8 -0
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +8 -0
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +8 -0
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +8 -0
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +8 -0
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +8 -0
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +8 -0
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +8 -0
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +8 -0
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +8 -0
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/providers/AppProviders.d.ts +2 -1
- package/dist/app/providers/AppProviders.d.ts.map +1 -1
- package/dist/app/providers/AppProviders.js +4 -3
- package/dist/app/providers/AppProviders.js.map +1 -1
- package/dist/app/providers/ThemeFamilySync.d.ts +2 -0
- package/dist/app/providers/ThemeFamilySync.d.ts.map +1 -0
- package/dist/app/providers/ThemeFamilySync.js +6 -0
- package/dist/app/providers/ThemeFamilySync.js.map +1 -0
- package/dist/app/settings/account/PreferencesSettings.d.ts.map +1 -1
- package/dist/app/settings/account/PreferencesSettings.js +2 -1
- package/dist/app/settings/account/PreferencesSettings.js.map +1 -1
- package/dist/app/settings/account/ThemeFamilySettings.d.ts +2 -0
- package/dist/app/settings/account/ThemeFamilySettings.d.ts.map +1 -0
- package/dist/app/settings/account/ThemeFamilySettings.js +31 -0
- package/dist/app/settings/account/ThemeFamilySettings.js.map +1 -0
- package/dist/app/settings/account/search-entries.d.ts.map +1 -1
- package/dist/app/settings/account/search-entries.js +6 -0
- package/dist/app/settings/account/search-entries.js.map +1 -1
- package/dist/app/shared/DefaultSpinner.d.ts.map +1 -1
- package/dist/app/shared/DefaultSpinner.js +4 -5
- package/dist/app/shared/DefaultSpinner.js.map +1 -1
- package/dist/app/shared/ErrorBoundary.d.ts +1 -0
- package/dist/app/shared/ErrorBoundary.d.ts.map +1 -1
- package/dist/app/shared/ErrorBoundary.js +27 -12
- package/dist/app/shared/ErrorBoundary.js.map +1 -1
- package/dist/app/shared/PoweredByBadge.d.ts.map +1 -1
- package/dist/app/shared/PoweredByBadge.js +8 -49
- package/dist/app/shared/PoweredByBadge.js.map +1 -1
- package/dist/app/styles/agent-conversation.css +0 -23
- package/dist/app/styles/agent-native.css +1 -1
- package/dist/app/styles/theme-families.css +66 -0
- package/dist/composer/ComposerContextBanner.d.ts +22 -0
- package/dist/composer/ComposerContextBanner.d.ts.map +1 -0
- package/dist/composer/ComposerContextBanner.js +9 -0
- package/dist/composer/ComposerContextBanner.js.map +1 -0
- package/dist/composer/ComposerModePicker.d.ts +26 -0
- package/dist/composer/ComposerModePicker.d.ts.map +1 -0
- package/dist/composer/ComposerModePicker.js +14 -0
- package/dist/composer/ComposerModePicker.js.map +1 -0
- package/dist/composer/index.d.ts +2 -0
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +2 -0
- package/dist/composer/index.js.map +1 -1
- package/dist/styles.css +1 -0
- package/package.json +3 -3
- package/src/app/agentkit/react/components.spec.tsx +12 -0
- package/src/app/agentkit/react/components.tsx +41 -22
- package/src/app/agentkit/react/context.tsx +17 -0
- package/src/app/agentkit/react/index.ts +1 -0
- package/src/app/agentkit/react/lifecycle.spec.tsx +116 -1
- package/src/app/agentkit/react/message-content-order.spec.ts +190 -0
- package/src/app/agentkit/react/message-content-order.ts +246 -0
- package/src/app/agentkit/react/root.tsx +4 -0
- package/src/app/agentkit/react/styles.css +2 -2
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +19 -0
- package/src/app/chat/AgentKitAssistantChat.tsx +10 -1
- package/src/app/chat/AgentPanel.header.spec.ts +72 -14
- package/src/app/chat/AgentPanel.tsx +97 -53
- package/src/app/chat/chat/surface-types.ts +8 -0
- package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +217 -0
- package/src/app/chat/chat/widgets/DataChartRenderer.tsx +306 -68
- package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +329 -0
- package/src/app/chat/chat/widgets/DataChartWidget.ssr.spec.tsx +25 -0
- package/src/app/chat/chat/widgets/DataChartWidget.tsx +48 -33
- package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +2 -2
- package/src/app/chat/chat/widgets/DataTableWidget.spec.tsx +37 -3
- package/src/app/chat/chat/widgets/DataTableWidget.tsx +22 -86
- package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +301 -0
- package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -5
- package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -5
- package/src/app/chat/mcp-apps/mcp-app-theme.spec.ts +30 -0
- package/src/app/chat/mcp-apps/mcp-app-theme.ts +11 -0
- package/src/app/i18n/catalogs/ar-SA.ts +9 -0
- package/src/app/i18n/catalogs/de-DE.ts +9 -0
- package/src/app/i18n/catalogs/en-US.ts +9 -0
- package/src/app/i18n/catalogs/es-ES.ts +10 -0
- package/src/app/i18n/catalogs/fr-FR.ts +10 -0
- package/src/app/i18n/catalogs/hi-IN.ts +9 -0
- package/src/app/i18n/catalogs/ja-JP.ts +9 -0
- package/src/app/i18n/catalogs/ko-KR.ts +9 -0
- package/src/app/i18n/catalogs/pt-BR.ts +9 -0
- package/src/app/i18n/catalogs/zh-CN.ts +8 -0
- package/src/app/i18n/catalogs/zh-TW.ts +9 -0
- package/src/app/providers/AppProviders.spec.tsx +71 -0
- package/src/app/providers/AppProviders.tsx +10 -0
- package/src/app/providers/ThemeFamilySync.tsx +6 -0
- package/src/app/settings/account/PreferencesSettings.spec.tsx +13 -2
- package/src/app/settings/account/PreferencesSettings.tsx +8 -0
- package/src/app/settings/account/ThemeFamilySettings.tsx +61 -0
- package/src/app/settings/account/search-entries.ts +6 -0
- package/src/app/shared/DefaultSpinner.spec.tsx +14 -0
- package/src/app/shared/DefaultSpinner.tsx +4 -5
- package/src/app/shared/ErrorBoundary.spec.tsx +45 -2
- package/src/app/shared/ErrorBoundary.tsx +37 -12
- package/src/app/shared/PoweredByBadge.spec.tsx +16 -0
- package/src/app/shared/PoweredByBadge.tsx +8 -50
- package/src/app/styles/agent-conversation.css +0 -23
- package/src/app/styles/agent-native.css +1 -1
- package/src/app/styles/agent-native.spec.ts +18 -0
- package/src/app/styles/theme-families.css +66 -0
- package/src/app/styles/theme-families.spec.ts +29 -0
- package/src/composer/ComposerContextBanner.spec.tsx +74 -0
- package/src/composer/ComposerContextBanner.tsx +72 -0
- package/src/composer/ComposerModePicker.spec.tsx +101 -0
- package/src/composer/ComposerModePicker.tsx +98 -0
- package/src/composer/index.ts +9 -0
- 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:
|
|
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:
|
|
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={
|
|
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("
|
|
354
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
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", () => {
|