@gram-ai/elements 1.34.0 → 1.35.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 (233) hide show
  1. package/dist/compat-shims-CO9JXXV4.cjs.map +1 -1
  2. package/dist/compat-shims-DxtUrORi.js.map +1 -1
  3. package/dist/compat-shims.d.ts +9 -8
  4. package/dist/components/Chat/index.d.ts +2 -1
  5. package/dist/components/ChatHistory.d.ts +1 -1
  6. package/dist/components/FrontendTools/index.d.ts +1 -1
  7. package/dist/components/Replay.d.ts +1 -1
  8. package/dist/components/Replay.stories.d.ts +2 -2
  9. package/dist/components/ShadowRoot.d.ts +1 -1
  10. package/dist/components/ShareButton/index.d.ts +1 -1
  11. package/dist/components/ui/avatar.d.ts +3 -3
  12. package/dist/components/ui/button.d.ts +2 -2
  13. package/dist/components/ui/buttonVariants.d.ts +2 -2
  14. package/dist/components/ui/calendar.d.ts +2 -1
  15. package/dist/components/ui/collapsible.d.ts +3 -3
  16. package/dist/components/ui/dialog.d.ts +10 -10
  17. package/dist/components/ui/popover.d.ts +4 -4
  18. package/dist/components/ui/skeleton.d.ts +1 -1
  19. package/dist/components/ui/time-range-picker.d.ts +2 -1
  20. package/dist/components/ui/tool-ui.d.ts +7 -7
  21. package/dist/components/ui/tooltip.d.ts +4 -4
  22. package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
  23. package/dist/contexts/ElementsProvider.d.ts +1 -1
  24. package/dist/contexts/ToolApprovalContext.d.ts +2 -2
  25. package/dist/contexts/ToolExecutionContext.d.ts +1 -1
  26. package/dist/contexts/portal-container.d.ts +1 -1
  27. package/dist/elements.cjs +1 -1
  28. package/dist/elements.css +1 -1
  29. package/dist/elements.js +2 -2
  30. package/dist/hooks/useDensity.d.ts +1 -1
  31. package/dist/hooks/useElements.d.ts +2 -1
  32. package/dist/hooks/useGramThreadListAdapter.d.ts +13 -0
  33. package/dist/hooks/useRadius.d.ts +1 -1
  34. package/dist/hooks/useThemeProps.d.ts +1 -1
  35. package/dist/hooks/useToolApproval.d.ts +2 -1
  36. package/dist/{index-BFU6NvbL.js → index-BhIowiZF.js} +9408 -9204
  37. package/dist/index-BhIowiZF.js.map +1 -0
  38. package/dist/{index-C08dvTEo.cjs → index-D0jIGQr7.cjs} +3 -3
  39. package/dist/index-D0jIGQr7.cjs.map +1 -0
  40. package/dist/{index-B5lZrrO2.js → index-Dz13dSDa.js} +57 -15
  41. package/dist/index-Dz13dSDa.js.map +1 -0
  42. package/dist/index-PXd3rs95.cjs +194 -0
  43. package/dist/index-PXd3rs95.cjs.map +1 -0
  44. package/dist/lib/errorTracking.d.ts +1 -1
  45. package/dist/lib/tools.d.ts +11 -10
  46. package/dist/plugins/generative-ui/catalog.d.ts +3 -3
  47. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
  48. package/dist/plugins/generative-ui/ui/accordion.d.ts +4 -4
  49. package/dist/plugins/generative-ui/ui/action-button.d.ts +1 -1
  50. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +2 -1
  51. package/dist/plugins/generative-ui/ui/alert.d.ts +3 -3
  52. package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +2 -1
  53. package/dist/plugins/generative-ui/ui/avatar.d.ts +6 -6
  54. package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
  55. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -1
  56. package/dist/plugins/generative-ui/ui/button.d.ts +3 -3
  57. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +1 -1
  58. package/dist/plugins/generative-ui/ui/card.d.ts +7 -7
  59. package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +2 -1
  60. package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
  61. package/dist/plugins/generative-ui/ui/data-table.d.ts +1 -1
  62. package/dist/plugins/generative-ui/ui/dialog.d.ts +10 -10
  63. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +15 -15
  64. package/dist/plugins/generative-ui/ui/grid.d.ts +1 -1
  65. package/dist/plugins/generative-ui/ui/index.d.ts +57 -40
  66. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +2 -1
  67. package/dist/plugins/generative-ui/ui/input.d.ts +1 -1
  68. package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
  69. package/dist/plugins/generative-ui/ui/list.d.ts +2 -1
  70. package/dist/plugins/generative-ui/ui/metric.d.ts +1 -1
  71. package/dist/plugins/generative-ui/ui/pagination.d.ts +7 -7
  72. package/dist/plugins/generative-ui/ui/popover.d.ts +7 -7
  73. package/dist/plugins/generative-ui/ui/progress.d.ts +1 -1
  74. package/dist/plugins/generative-ui/ui/radio-group.d.ts +2 -2
  75. package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +2 -1
  76. package/dist/plugins/generative-ui/ui/select.d.ts +10 -10
  77. package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
  78. package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +2 -1
  79. package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
  80. package/dist/plugins/generative-ui/ui/stack.d.ts +1 -1
  81. package/dist/plugins/generative-ui/ui/switch.d.ts +1 -1
  82. package/dist/plugins/generative-ui/ui/table.d.ts +8 -8
  83. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +2 -2
  84. package/dist/plugins/generative-ui/ui/tabs.d.ts +4 -4
  85. package/dist/plugins/generative-ui/ui/text.d.ts +1 -1
  86. package/dist/plugins/generative-ui/ui/textarea.d.ts +1 -1
  87. package/dist/plugins/generative-ui/ui/tooltip.d.ts +4 -4
  88. package/dist/plugins.cjs +1 -1
  89. package/dist/plugins.js +1 -1
  90. package/dist/{profiler-KLSTpp6I.js → profiler-CtGKTWWP.js} +2 -2
  91. package/dist/{profiler-KLSTpp6I.js.map → profiler-CtGKTWWP.js.map} +1 -1
  92. package/dist/{profiler-BRnyr1GA.cjs → profiler-l7_HjTyw.cjs} +2 -2
  93. package/dist/{profiler-BRnyr1GA.cjs.map → profiler-l7_HjTyw.cjs.map} +1 -1
  94. package/dist/react-shim.cjs.map +1 -1
  95. package/dist/react-shim.d.ts +1 -1
  96. package/dist/react-shim.js +1 -4
  97. package/dist/react-shim.js.map +1 -1
  98. package/dist/server/bun.cjs.map +1 -1
  99. package/dist/server/bun.js.map +1 -1
  100. package/dist/server/express.cjs.map +1 -1
  101. package/dist/server/express.js.map +1 -1
  102. package/dist/server/fastify.cjs.map +1 -1
  103. package/dist/server/fastify.js.map +1 -1
  104. package/dist/server/hono.cjs.map +1 -1
  105. package/dist/server/hono.js.map +1 -1
  106. package/dist/server/nextjs.cjs.map +1 -1
  107. package/dist/server/nextjs.js.map +1 -1
  108. package/dist/server/tanstack-start.cjs.map +1 -1
  109. package/dist/server/tanstack-start.js.map +1 -1
  110. package/dist/{startRecording-CKx-YWbq.cjs → startRecording-DEw2Aeq4.cjs} +2 -2
  111. package/dist/{startRecording-CKx-YWbq.cjs.map → startRecording-DEw2Aeq4.cjs.map} +1 -1
  112. package/dist/{startRecording-BfxB1xxR.js → startRecording-iYEL0-vr.js} +2 -2
  113. package/dist/{startRecording-BfxB1xxR.js.map → startRecording-iYEL0-vr.js.map} +1 -1
  114. package/dist/types/index.d.ts +29 -3
  115. package/package.json +7 -10
  116. package/src/compat-shims.ts +16 -2
  117. package/src/components/Chat/index.tsx +4 -1
  118. package/src/components/Chat/stories/FrontendTools.stories.tsx +1 -1
  119. package/src/components/Chat/stories/ToolApproval.stories.tsx +2 -2
  120. package/src/components/Chat/stories/Tools.stories.tsx +13 -5
  121. package/src/components/ChatHistory.tsx +3 -1
  122. package/src/components/FrontendTools/index.tsx +1 -1
  123. package/src/components/MessageContent.tsx +1 -0
  124. package/src/components/Replay.stories.tsx +2 -3
  125. package/src/components/Replay.tsx +17 -10
  126. package/src/components/ShadowRoot.tsx +2 -2
  127. package/src/components/ShareButton/index.tsx +4 -2
  128. package/src/components/assistant-ui/assistant-modal.tsx +5 -3
  129. package/src/components/assistant-ui/attachment.tsx +1 -1
  130. package/src/components/assistant-ui/error-boundary.tsx +1 -1
  131. package/src/components/assistant-ui/markdown-text.tsx +1 -1
  132. package/src/components/assistant-ui/thread.tsx +249 -11
  133. package/src/components/assistant-ui/tool-mention-autocomplete.tsx +1 -1
  134. package/src/components/ui/avatar.tsx +3 -3
  135. package/src/components/ui/calendar.tsx +1 -1
  136. package/src/components/ui/collapsible.tsx +7 -3
  137. package/src/components/ui/dialog.tsx +18 -10
  138. package/src/components/ui/generative-ui.tsx +9 -4
  139. package/src/components/ui/popover.tsx +4 -4
  140. package/src/components/ui/skeleton.tsx +4 -1
  141. package/src/components/ui/time-range-picker.stories.tsx +164 -154
  142. package/src/components/ui/time-range-picker.tsx +11 -5
  143. package/src/components/ui/tool-ui.tsx +18 -9
  144. package/src/components/ui/tooltip.tsx +4 -4
  145. package/src/contexts/ChatIdContext.tsx +1 -1
  146. package/src/contexts/ConnectionStatusContext.tsx +6 -5
  147. package/src/contexts/ElementsProvider.tsx +64 -41
  148. package/src/contexts/ReplayContext.ts +1 -1
  149. package/src/contexts/ToolApprovalContext.tsx +5 -1
  150. package/src/contexts/ToolExecutionContext.tsx +1 -1
  151. package/src/contexts/portal-container.tsx +1 -1
  152. package/src/hooks/useAuth.ts +2 -1
  153. package/src/hooks/useDensity.ts +1 -1
  154. package/src/hooks/useElements.ts +2 -1
  155. package/src/hooks/useFollowOnSuggestions.ts +3 -6
  156. package/src/hooks/useGramThreadListAdapter.tsx +50 -3
  157. package/src/hooks/useMCPTools.ts +2 -2
  158. package/src/hooks/useModel.ts +1 -3
  159. package/src/hooks/usePluginComponents.ts +3 -1
  160. package/src/hooks/useRadius.ts +1 -1
  161. package/src/hooks/useSession.ts +3 -1
  162. package/src/hooks/useThemeProps.ts +5 -5
  163. package/src/hooks/useToolApproval.ts +2 -1
  164. package/src/lib/cassette.ts +20 -8
  165. package/src/lib/errorTracking.ts +1 -4
  166. package/src/lib/messageConverter.test.ts +11 -13
  167. package/src/lib/messageConverter.ts +13 -4
  168. package/src/lib/token.ts +2 -5
  169. package/src/lib/tool-mentions.ts +5 -2
  170. package/src/lib/tools.byte-cap.test.ts +1 -1
  171. package/src/lib/tools.test.ts +1 -1
  172. package/src/lib/tools.ts +15 -5
  173. package/src/lib/utils.ts +2 -2
  174. package/src/lib.d.ts +8 -1
  175. package/src/plugins/chart/chart.test.ts +3 -4
  176. package/src/plugins/chart/component.tsx +7 -6
  177. package/src/plugins/chart/ui/area-chart.tsx +1 -1
  178. package/src/plugins/chart/ui/line-chart.tsx +1 -1
  179. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +2 -2
  180. package/src/plugins/generative-ui/ui/accordion.tsx +4 -4
  181. package/src/plugins/generative-ui/ui/action-button.tsx +4 -2
  182. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +1 -1
  183. package/src/plugins/generative-ui/ui/alert.tsx +7 -3
  184. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +5 -1
  185. package/src/plugins/generative-ui/ui/avatar.tsx +12 -6
  186. package/src/plugins/generative-ui/ui/badge.tsx +1 -1
  187. package/src/plugins/generative-ui/ui/button-wrapper.tsx +1 -1
  188. package/src/plugins/generative-ui/ui/button.tsx +1 -1
  189. package/src/plugins/generative-ui/ui/card-wrapper.tsx +1 -1
  190. package/src/plugins/generative-ui/ui/card.tsx +28 -7
  191. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +1 -1
  192. package/src/plugins/generative-ui/ui/checkbox.tsx +1 -1
  193. package/src/plugins/generative-ui/ui/data-table.tsx +1 -1
  194. package/src/plugins/generative-ui/ui/dialog.tsx +15 -10
  195. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +33 -15
  196. package/src/plugins/generative-ui/ui/grid.tsx +1 -1
  197. package/src/plugins/generative-ui/ui/index.ts +154 -40
  198. package/src/plugins/generative-ui/ui/input-wrapper.tsx +1 -1
  199. package/src/plugins/generative-ui/ui/input.tsx +5 -1
  200. package/src/plugins/generative-ui/ui/label.tsx +1 -1
  201. package/src/plugins/generative-ui/ui/list.tsx +5 -1
  202. package/src/plugins/generative-ui/ui/metric.tsx +2 -1
  203. package/src/plugins/generative-ui/ui/pagination.tsx +12 -7
  204. package/src/plugins/generative-ui/ui/popover.tsx +13 -7
  205. package/src/plugins/generative-ui/ui/progress.tsx +1 -1
  206. package/src/plugins/generative-ui/ui/radio-group.tsx +2 -2
  207. package/src/plugins/generative-ui/ui/select-wrapper.tsx +1 -1
  208. package/src/plugins/generative-ui/ui/select.tsx +14 -10
  209. package/src/plugins/generative-ui/ui/separator.tsx +1 -1
  210. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +1 -1
  211. package/src/plugins/generative-ui/ui/skeleton.tsx +4 -1
  212. package/src/plugins/generative-ui/ui/stack.tsx +1 -1
  213. package/src/plugins/generative-ui/ui/switch.tsx +1 -1
  214. package/src/plugins/generative-ui/ui/table.tsx +29 -8
  215. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +5 -2
  216. package/src/plugins/generative-ui/ui/tabs.tsx +4 -4
  217. package/src/plugins/generative-ui/ui/text.tsx +1 -1
  218. package/src/plugins/generative-ui/ui/textarea.tsx +4 -1
  219. package/src/plugins/generative-ui/ui/tooltip.tsx +4 -4
  220. package/src/react-shim.ts +9 -4
  221. package/src/server/bun.ts +1 -1
  222. package/src/server/express.ts +1 -1
  223. package/src/server/fastify.ts +1 -1
  224. package/src/server/hono.ts +1 -1
  225. package/src/server/nextjs.ts +1 -1
  226. package/src/server/tanstack-start.ts +1 -1
  227. package/src/storybook.d.ts +5 -0
  228. package/src/types/index.ts +39 -3
  229. package/dist/index-B5lZrrO2.js.map +0 -1
  230. package/dist/index-BFU6NvbL.js.map +0 -1
  231. package/dist/index-C08dvTEo.cjs.map +0 -1
  232. package/dist/index-DzZ1-jQY.cjs +0 -194
  233. package/dist/index-DzZ1-jQY.cjs.map +0 -1
@@ -34,7 +34,7 @@ type RadiusSize = "sm" | "md" | "lg" | "xl" | "full";
34
34
  * Hook to get radius classes based on theme config
35
35
  * Use: const r = useRadius(); then r('lg') returns the appropriate rounded class
36
36
  */
37
- export const useRadius = () => {
37
+ export const useRadius = (): ((size: RadiusSize) => string) => {
38
38
  const { config } = useElements();
39
39
  const radius = config.theme?.radius ?? "soft";
40
40
 
@@ -1,7 +1,9 @@
1
1
  import { GetSessionFn } from "@/types";
2
2
  import { useQuery, useQueryClient } from "@tanstack/react-query";
3
3
 
4
- export function getChatSessionQueryKey(projectSlug: string) {
4
+ export function getChatSessionQueryKey(
5
+ projectSlug: string,
6
+ ): readonly ["chatSession", string] {
5
7
  return ["chatSession", projectSlug] as const;
6
8
  }
7
9
 
@@ -4,13 +4,13 @@ import { useElements } from "./useElements";
4
4
  /**
5
5
  * Hook to get theme-related props including dark mode class
6
6
  */
7
- export const useThemeProps = () => {
7
+ export const useThemeProps = (): {
8
+ readonly className: string | undefined;
9
+ } => {
8
10
  const { config } = useElements();
9
- const theme = config.theme ?? {};
11
+ const colorScheme = config.theme?.colorScheme ?? "light";
10
12
 
11
13
  return useMemo(() => {
12
- const { colorScheme = "light" } = theme;
13
-
14
14
  const isDark =
15
15
  colorScheme === "dark" ||
16
16
  (colorScheme === "system" &&
@@ -20,5 +20,5 @@ export const useThemeProps = () => {
20
20
  return {
21
21
  className: isDark ? "dark" : undefined,
22
22
  } as const;
23
- }, [theme]);
23
+ }, [colorScheme]);
24
24
  };
@@ -1,11 +1,12 @@
1
1
  import { useContext } from "react";
2
2
  import { ToolApprovalContext } from "@/contexts/contexts";
3
+ import type { ToolApprovalContextType } from "@/contexts/ToolApprovalContext";
3
4
 
4
5
  /**
5
6
  * Hook to access the tool approval context for managing human-in-the-loop
6
7
  * tool execution approval.
7
8
  */
8
- export const useToolApproval = () => {
9
+ export const useToolApproval = (): ToolApprovalContextType => {
9
10
  const context = useContext(ToolApprovalContext);
10
11
  if (!context) {
11
12
  throw new Error(
@@ -10,7 +10,12 @@
10
10
  * 3. Pass it to `<Replay cassette={...}>` to play it back
11
11
  */
12
12
 
13
- import { createUIMessageStream, type ChatTransport, type UIMessage } from "ai";
13
+ import {
14
+ createUIMessageStream,
15
+ type ChatTransport,
16
+ type UIMessage,
17
+ type UIMessageStreamWriter,
18
+ } from "ai";
14
19
  import type { ThreadMessage } from "@assistant-ui/react";
15
20
  import { sleep } from "@/lib/utils";
16
21
 
@@ -95,6 +100,12 @@ export function recordCassette(messages: readonly ThreadMessage[]): Cassette {
95
100
  });
96
101
  break;
97
102
  // Skip image, file, audio, source, data parts for now
103
+ case "image":
104
+ case "file":
105
+ case "audio":
106
+ case "source":
107
+ case "data":
108
+ break;
98
109
  }
99
110
  }
100
111
 
@@ -135,7 +146,7 @@ export function createReplayTransport(
135
146
  // Advance cursor past it (it should be pointing at a user message).
136
147
  if (
137
148
  cursor < cassette.messages.length &&
138
- cassette.messages[cursor].role === "user"
149
+ cassette.messages[cursor]?.role === "user"
139
150
  ) {
140
151
  cursor++;
141
152
  }
@@ -145,9 +156,10 @@ export function createReplayTransport(
145
156
  const assistantMessages: CassetteMessage[] = [];
146
157
  while (
147
158
  cursor < cassette.messages.length &&
148
- cassette.messages[cursor].role === "assistant"
159
+ cassette.messages[cursor]?.role === "assistant"
149
160
  ) {
150
- assistantMessages.push(cassette.messages[cursor]);
161
+ const m = cassette.messages[cursor];
162
+ if (m) assistantMessages.push(m);
151
163
  cursor++;
152
164
  }
153
165
 
@@ -186,10 +198,10 @@ export function createReplayTransport(
186
198
  // Stream writing helpers
187
199
  // ---------------------------------------------------------------------------
188
200
 
189
- interface StreamWriter {
190
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
- write(part: any): void;
192
- }
201
+ // Match the `write` shape of `UIMessageStreamWriter<UIMessage>` so callers can
202
+ // pass the writer they receive from `createUIMessageStream` directly. We only
203
+ // rely on `write` here, so this stays narrow on purpose.
204
+ type StreamWriter = Pick<UIMessageStreamWriter<UIMessage>, "write">;
193
205
 
194
206
  async function writeReplayPart(
195
207
  writer: StreamWriter,
@@ -75,10 +75,7 @@ export function initErrorTracking(config: ErrorTrackingConfig = {}): void {
75
75
  * Track an error to Datadog RUM.
76
76
  * Includes context about where the error originated.
77
77
  */
78
- export function trackError(
79
- error: Error | unknown,
80
- context: ErrorContext,
81
- ): void {
78
+ export function trackError(error: unknown, context: ErrorContext): void {
82
79
  if (!enabled || !initialized) {
83
80
  return;
84
81
  }
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import { describe, expect, it } from "vitest";
3
2
  import {
4
3
  convertGramMessagesToExported,
@@ -6,6 +5,7 @@ import {
6
5
  convertGramMessagePartsToUIMessageParts,
7
6
  type GramChatMessage,
8
7
  } from "./messageConverter";
8
+ import type { ToolCallMessagePart } from "@assistant-ui/react";
9
9
 
10
10
  /**
11
11
  * Helper to create a minimal GramChatMessage for testing.
@@ -41,10 +41,7 @@ describe("convertGramMessagePartsToUIMessageParts", () => {
41
41
  tool_calls: makeToolCallsJSON([{ id: "tc_1", name: "search_deals" }]),
42
42
  } as Partial<GramChatMessage> & { role: string });
43
43
 
44
- const parts = convertGramMessagePartsToUIMessageParts(
45
- msg as any,
46
- new Map(),
47
- );
44
+ const parts = convertGramMessagePartsToUIMessageParts(msg, new Map());
48
45
 
49
46
  const toolParts = parts.filter((p) => p.type === "dynamic-tool");
50
47
  expect(toolParts).toHaveLength(1);
@@ -66,11 +63,7 @@ describe("convertGramMessagePartsToUIMessageParts", () => {
66
63
  ]),
67
64
  } as Partial<GramChatMessage> & { role: string });
68
65
 
69
- const parts = convertGramMessagePartsToUIMessageParts(
70
- msg as any,
71
- new Map(),
72
- seen,
73
- );
66
+ const parts = convertGramMessagePartsToUIMessageParts(msg, new Map(), seen);
74
67
 
75
68
  const toolParts = parts.filter((p) => p.type === "dynamic-tool");
76
69
  expect(toolParts).toHaveLength(1);
@@ -230,10 +223,15 @@ describe("convertGramMessagesToExported - string content with tool calls", () =>
230
223
 
231
224
  const result = convertGramMessagesToExported(messages);
232
225
  const assistantEntry = result.messages.find(
233
- (m) => m.message.role === "assistant",
226
+ (m): m is typeof m & { message: { role: "assistant" } } =>
227
+ m.message.role === "assistant",
234
228
  )!;
235
- const toolCallParts = (assistantEntry.message as any).content.filter(
236
- (p: any) => p.type === "tool-call",
229
+ const assistantMessage = assistantEntry.message;
230
+ if (assistantMessage.role !== "assistant") {
231
+ throw new Error("expected assistant message");
232
+ }
233
+ const toolCallParts = assistantMessage.content.filter(
234
+ (p): p is ToolCallMessagePart => p.type === "tool-call",
237
235
  );
238
236
  expect(toolCallParts).toHaveLength(1);
239
237
  expect(toolCallParts[0]).toMatchObject({
@@ -1,5 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
-
3
1
  /**
4
2
  * Message format converter for Gram API <-> assistant-ui.
5
3
  *
@@ -195,7 +193,18 @@ function buildAssistantContentParts(
195
193
  });
196
194
  }
197
195
 
198
- let toolCalls = tryParseJSON(msg.tool_calls || "[]");
196
+ // Accept both the OpenAI/OpenRouter shape (`{ id, function: { name, arguments } }`)
197
+ // and the assistant-ui shape (`{ toolCallId, toolName, args }`). Tool calls
198
+ // arrive as JSON the server stored opaquely, so we model the union here.
199
+ type WireToolCall = {
200
+ id?: string;
201
+ toolCallId?: string;
202
+ function?: { name?: string; arguments?: string | Record<string, unknown> };
203
+ toolName?: string;
204
+ args?: string | Record<string, unknown>;
205
+ };
206
+
207
+ let toolCalls = tryParseJSON<WireToolCall[]>(msg.tool_calls || "[]");
199
208
  if (!Array.isArray(toolCalls)) {
200
209
  console.warn("Invalid tool_calls format, expected an array.");
201
210
  toolCalls = [];
@@ -564,7 +573,7 @@ function mediaTypeFromURL(url: string): string {
564
573
  return match?.[1] || unspecified;
565
574
  }
566
575
 
567
- function tryParseJSON<T = any>(str: string): T | null {
576
+ function tryParseJSON<T = unknown>(str: string): T | null {
568
577
  try {
569
578
  return JSON.parse(str) as T;
570
579
  } catch {
package/src/lib/token.ts CHANGED
@@ -9,7 +9,7 @@ export function getTokenExpiry(token: string): number | null {
9
9
  if (parts.length !== 3) return null;
10
10
 
11
11
  // base64url → base64 → decode
12
- let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
12
+ let payload = parts[1]!.replace(/-/g, "+").replace(/_/g, "/");
13
13
  while (payload.length % 4) payload += "=";
14
14
 
15
15
  const json = atob(payload);
@@ -29,10 +29,7 @@ export function getTokenExpiry(token: string): number | null {
29
29
  * of expiry. Fails open (returns false) for non-JWT tokens or tokens
30
30
  * without an `exp` claim so they pass through unchanged.
31
31
  */
32
- export function isTokenExpired(
33
- token: string,
34
- bufferMs: number = 30_000,
35
- ): boolean {
32
+ export function isTokenExpired(token: string, bufferMs = 30_000): boolean {
36
33
  const exp = getTokenExpiry(token);
37
34
  if (exp === null) return false; // fail-open for non-JWT tokens
38
35
  return Date.now() >= exp * 1000 - bufferMs;
@@ -24,7 +24,10 @@ export function toolSetToMentionableTools(
24
24
  name,
25
25
  description:
26
26
  typeof tool === "object" && tool !== null && "description" in tool
27
- ? String((tool as { description?: unknown }).description ?? "")
27
+ ? (() => {
28
+ const desc = (tool as { description?: unknown }).description;
29
+ return typeof desc === "string" ? desc : "";
30
+ })()
28
31
  : undefined,
29
32
  }));
30
33
  }
@@ -38,7 +41,7 @@ export function parseMentionedTools(text: string, tools: ToolRecord): string[] {
38
41
 
39
42
  MENTION_PATTERN.lastIndex = 0;
40
43
  while ((match = MENTION_PATTERN.exec(text)) !== null) {
41
- mentions.push(match[1].toLowerCase());
44
+ mentions.push(match[1]!.toLowerCase());
42
45
  }
43
46
 
44
47
  const matchedToolIds = toolNames.filter((name) =>
@@ -114,7 +114,7 @@ describe("wrapToolsWithByteCap", () => {
114
114
  t: { description: "", inputSchema: { type: "object" }, execute } as never,
115
115
  };
116
116
  const wrapped = wrapToolsWithByteCap(tools, 256);
117
- const wrappedExecute = wrapped.t.execute!;
117
+ const wrappedExecute = wrapped.t!.execute!;
118
118
  const out = (await wrappedExecute({}, { toolCallId: "id" } as never)) as {
119
119
  content: Array<{ text: string }>;
120
120
  };
@@ -252,7 +252,7 @@ describe("frontend tool Skip flow (sendAutomaticallyWhen fix)", () => {
252
252
  0,
253
253
  );
254
254
  const toolResult = (
255
- next?.content as Array<{ type: string; output?: { type?: string } }>
255
+ next!.content as Array<{ type: string; output?: { type?: string } }>
256
256
  )[0];
257
257
  expect(toolResult?.type).toBe("tool-result");
258
258
  });
package/src/lib/tools.ts CHANGED
@@ -11,7 +11,9 @@ import z from "zod";
11
11
  /**
12
12
  * Converts from assistant-ui tool format to the AI SDK tool shape
13
13
  */
14
- export const toAISDKTools = (tools: Record<string, Tool>) => {
14
+ export const toAISDKTools = (
15
+ tools: Record<string, Tool>,
16
+ ): Record<string, { description?: string; parameters: JSONSchema7 }> => {
15
17
  return Object.fromEntries(
16
18
  Object.entries(tools).map(([name, tool]) => [
17
19
  name,
@@ -28,7 +30,9 @@ export const toAISDKTools = (tools: Record<string, Tool>) => {
28
30
  /**
29
31
  * Returns only frontend tools that are enabled
30
32
  */
31
- export const getEnabledTools = (tools: Record<string, Tool>) => {
33
+ export const getEnabledTools = (
34
+ tools: Record<string, Tool>,
35
+ ): Record<string, Tool> => {
32
36
  return Object.fromEntries(
33
37
  Object.entries(tools).filter(
34
38
  ([, tool]) => !tool.disabled && tool.type !== "backend",
@@ -38,10 +42,16 @@ export const getEnabledTools = (tools: Record<string, Tool>) => {
38
42
 
39
43
  /**
40
44
  * A frontend tool is a tool that is defined by the user and can be used in the chat.
45
+ *
46
+ * Shape mirrors assistant-ui's `AssistantTool`: an `FC` (rendered with no props
47
+ * at runtime to register the tool) plus an `unstable_tool` describing the tool
48
+ * itself. Keeping the FC unparameterised here matches the SDK and allows tools
49
+ * with different `TArgs`/`TResult` to coexist in a `Record<string, FrontendTool<...>>`.
41
50
  */
42
- export type FrontendTool<TArgs extends Record<string, unknown>, TResult> = FC<
43
- AssistantToolProps<TArgs, TResult>
44
- > & {
51
+ export type FrontendTool<
52
+ TArgs extends Record<string, unknown>,
53
+ TResult,
54
+ > = FC & {
45
55
  unstable_tool: AssistantToolProps<TArgs, TResult>;
46
56
  };
47
57
 
package/src/lib/utils.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { clsx, type ClassValue } from "clsx";
2
2
  import { twMerge } from "tailwind-merge";
3
3
 
4
- export function cn(...inputs: ClassValue[]) {
4
+ export function cn(...inputs: ClassValue[]): string {
5
5
  return twMerge(clsx(inputs));
6
6
  }
7
7
 
8
8
  export function assertNever(value: unknown): never {
9
- throw new Error(`Unexpected value: ${value}`);
9
+ throw new Error(`Unexpected value: ${String(value)}`);
10
10
  }
11
11
 
12
12
  export function assert(condition: unknown, message: string): asserts condition {
package/src/lib.d.ts CHANGED
@@ -1 +1,8 @@
1
- type FIXME<T, S extends string> = (T & S) | T;
1
+ // Escape hatch type flags any-cast call sites with a required reason message
2
+ // so they're greppable and reviewable.
3
+ //
4
+ // Usage:
5
+ // const value = apiCall() as FIXME<"Need to fix upstream API types">;
6
+
7
+ // oxlint-disable-next-line typescript/no-explicit-any
8
+ type FIXME<M extends string> = any;
@@ -1,10 +1,10 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { parse, View, Warn } from "vega";
2
+ import { parse, View, Warn, type Spec } from "vega";
3
3
  import { expressionInterpreter } from "vega-interpreter";
4
4
 
5
5
  describe("ChartRenderer CSP compliance", () => {
6
6
  it("renders a chart using vega-interpreter without eval", async () => {
7
- const spec = {
7
+ const spec: Spec = {
8
8
  $schema: "https://vega.github.io/schema/vega/v5.json",
9
9
  width: 400,
10
10
  height: 200,
@@ -46,8 +46,7 @@ describe("ChartRenderer CSP compliance", () => {
46
46
  ],
47
47
  };
48
48
 
49
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
- const runtime = parse(spec as any, undefined, { ast: true });
49
+ const runtime = parse(spec, undefined, { ast: true });
51
50
 
52
51
  // This is the key - using expr: vegaInterpreter means no eval() is called
53
52
  const view = new View(runtime, {
@@ -5,7 +5,7 @@ import { cn } from "@/lib/utils";
5
5
  import { isJsonRenderTree, type JsonRenderNode } from "@/lib/generative-ui";
6
6
  import { SyntaxHighlighterProps } from "@assistant-ui/react-markdown";
7
7
  import { AlertCircleIcon } from "lucide-react";
8
- import { FC, useMemo } from "react";
8
+ import { ElementType, FC, useMemo } from "react";
9
9
  import { MacOSWindowFrame } from "../components/MacOSWindowFrame";
10
10
  import { PluginLoadingState } from "../components/PluginLoadingState";
11
11
 
@@ -27,15 +27,16 @@ const loadingMessages = [
27
27
  "Processing data...",
28
28
  ];
29
29
 
30
- function getRandomLoadingMessage() {
31
- return loadingMessages[Math.floor(Math.random() * loadingMessages.length)];
30
+ function getRandomLoadingMessage(): string {
31
+ return loadingMessages[Math.floor(Math.random() * loadingMessages.length)]!;
32
32
  }
33
33
 
34
34
  /**
35
- * Chart components registry
35
+ * Chart components registry. Each entry accepts the chart-specific prop shape
36
+ * declared in `./ui`, but the registry erases those generics via `ElementType`
37
+ * so heterogeneous components can coexist under one key-indexed map.
36
38
  */
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- const chartComponents: Record<string, FC<any>> = {
39
+ const chartComponents: Record<string, ElementType> = {
39
40
  BarChart,
40
41
  LineChart,
41
42
  AreaChart,
@@ -72,7 +72,7 @@ export const AreaChart: FC<AreaChartProps> = ({
72
72
  const seriesKeys = useMemo(() => {
73
73
  if (series && series.length > 0) return series;
74
74
  if (data.length === 0) return [];
75
- const keys = Object.keys(data[0]).filter((k) => k !== "label");
75
+ const keys = Object.keys(data[0]!).filter((k) => k !== "label");
76
76
  return keys;
77
77
  }, [data, series]);
78
78
 
@@ -74,7 +74,7 @@ export const LineChart: FC<LineChartProps> = ({
74
74
  const seriesKeys = useMemo(() => {
75
75
  if (series && series.length > 0) return series;
76
76
  if (data.length === 0) return [];
77
- const keys = Object.keys(data[0]).filter((k) => k !== "label");
77
+ const keys = Object.keys(data[0]!).filter((k) => k !== "label");
78
78
  return keys;
79
79
  }, [data, series]);
80
80
 
@@ -19,7 +19,7 @@ export interface AccordionWrapperProps {
19
19
  export function AccordionWrapper({
20
20
  type = "single",
21
21
  children,
22
- }: AccordionWrapperProps) {
22
+ }: AccordionWrapperProps): React.JSX.Element {
23
23
  // Type assertion needed because Radix types are complex
24
24
  const AccordionRoot = AccordionPrimitive as React.FC<{
25
25
  type: "single" | "multiple";
@@ -47,7 +47,7 @@ export function AccordionItemWrapper({
47
47
  value,
48
48
  title,
49
49
  children,
50
- }: AccordionItemWrapperProps) {
50
+ }: AccordionItemWrapperProps): React.JSX.Element {
51
51
  return (
52
52
  <AccordionItemPrimitive value={value}>
53
53
  <AccordionTrigger>{title}</AccordionTrigger>
@@ -8,14 +8,14 @@ import { cn } from "@/lib/utils";
8
8
 
9
9
  function Accordion({
10
10
  ...props
11
- }: React.ComponentProps<typeof AccordionPrimitive.Root>) {
11
+ }: React.ComponentProps<typeof AccordionPrimitive.Root>): React.JSX.Element {
12
12
  return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
13
13
  }
14
14
 
15
15
  function AccordionItem({
16
16
  className,
17
17
  ...props
18
- }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
18
+ }: React.ComponentProps<typeof AccordionPrimitive.Item>): React.JSX.Element {
19
19
  return (
20
20
  <AccordionPrimitive.Item
21
21
  data-slot="accordion-item"
@@ -29,7 +29,7 @@ function AccordionTrigger({
29
29
  className,
30
30
  children,
31
31
  ...props
32
- }: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
32
+ }: React.ComponentProps<typeof AccordionPrimitive.Trigger>): React.JSX.Element {
33
33
  return (
34
34
  <AccordionPrimitive.Header className="flex">
35
35
  <AccordionPrimitive.Trigger
@@ -51,7 +51,7 @@ function AccordionContent({
51
51
  className,
52
52
  children,
53
53
  ...props
54
- }: React.ComponentProps<typeof AccordionPrimitive.Content>) {
54
+ }: React.ComponentProps<typeof AccordionPrimitive.Content>): React.JSX.Element {
55
55
  return (
56
56
  <AccordionPrimitive.Content
57
57
  data-slot="accordion-content"
@@ -25,7 +25,7 @@ export function ActionButton({
25
25
  className,
26
26
  disabled,
27
27
  ...props
28
- }: ActionButtonProps) {
28
+ }: ActionButtonProps): React.JSX.Element {
29
29
  const { executeTool, isToolAvailable } = useToolExecution();
30
30
  const [isLoading, setIsLoading] = React.useState(false);
31
31
 
@@ -56,7 +56,9 @@ export function ActionButton({
56
56
  variant={error ? "destructive" : variant}
57
57
  size={size}
58
58
  className={cn(className)}
59
- onClick={handleClick}
59
+ onClick={() => {
60
+ void handleClick();
61
+ }}
60
62
  disabled={disabled || isLoading || !toolAvailable}
61
63
  {...props}
62
64
  >
@@ -16,7 +16,7 @@ export function AlertWrapper({
16
16
  title,
17
17
  description,
18
18
  variant = "default",
19
- }: AlertWrapperProps) {
19
+ }: AlertWrapperProps): React.JSX.Element {
20
20
  return (
21
21
  <Alert variant={variant}>
22
22
  <AlertTitle>{title}</AlertTitle>
@@ -23,7 +23,8 @@ function Alert({
23
23
  className,
24
24
  variant,
25
25
  ...props
26
- }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
26
+ }: React.ComponentProps<"div"> &
27
+ VariantProps<typeof alertVariants>): React.JSX.Element {
27
28
  return (
28
29
  <div
29
30
  data-slot="alert"
@@ -34,7 +35,10 @@ function Alert({
34
35
  );
35
36
  }
36
37
 
37
- function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
38
+ function AlertTitle({
39
+ className,
40
+ ...props
41
+ }: React.ComponentProps<"div">): React.JSX.Element {
38
42
  return (
39
43
  <div
40
44
  data-slot="alert-title"
@@ -50,7 +54,7 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
50
54
  function AlertDescription({
51
55
  className,
52
56
  ...props
53
- }: React.ComponentProps<"div">) {
57
+ }: React.ComponentProps<"div">): React.JSX.Element {
54
58
  return (
55
59
  <div
56
60
  data-slot="alert-description"
@@ -12,7 +12,11 @@ export interface AvatarWrapperProps {
12
12
  /**
13
13
  * Avatar wrapper that takes src, alt, and fallback as props.
14
14
  */
15
- export function AvatarWrapper({ src, alt, fallback }: AvatarWrapperProps) {
15
+ export function AvatarWrapper({
16
+ src,
17
+ alt,
18
+ fallback,
19
+ }: AvatarWrapperProps): React.JSX.Element {
16
20
  return (
17
21
  <Avatar>
18
22
  {src && <AvatarImage src={src} alt={alt} />}
@@ -11,7 +11,7 @@ function Avatar({
11
11
  ...props
12
12
  }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
13
13
  size?: "default" | "sm" | "lg";
14
- }) {
14
+ }): React.JSX.Element {
15
15
  return (
16
16
  <AvatarPrimitive.Root
17
17
  data-slot="avatar"
@@ -28,7 +28,7 @@ function Avatar({
28
28
  function AvatarImage({
29
29
  className,
30
30
  ...props
31
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
31
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>): React.JSX.Element {
32
32
  return (
33
33
  <AvatarPrimitive.Image
34
34
  data-slot="avatar-image"
@@ -41,7 +41,7 @@ function AvatarImage({
41
41
  function AvatarFallback({
42
42
  className,
43
43
  ...props
44
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
44
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>): React.JSX.Element {
45
45
  return (
46
46
  <AvatarPrimitive.Fallback
47
47
  data-slot="avatar-fallback"
@@ -54,7 +54,10 @@ function AvatarFallback({
54
54
  );
55
55
  }
56
56
 
57
- function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
57
+ function AvatarBadge({
58
+ className,
59
+ ...props
60
+ }: React.ComponentProps<"span">): React.JSX.Element {
58
61
  return (
59
62
  <span
60
63
  data-slot="avatar-badge"
@@ -70,7 +73,10 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
70
73
  );
71
74
  }
72
75
 
73
- function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
76
+ function AvatarGroup({
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"div">): React.JSX.Element {
74
80
  return (
75
81
  <div
76
82
  data-slot="avatar-group"
@@ -86,7 +92,7 @@ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
86
92
  function AvatarGroupCount({
87
93
  className,
88
94
  ...props
89
- }: React.ComponentProps<"div">) {
95
+ }: React.ComponentProps<"div">): React.JSX.Element {
90
96
  return (
91
97
  <div
92
98
  data-slot="avatar-group-count"
@@ -46,7 +46,7 @@ function Badge({
46
46
  content,
47
47
  children,
48
48
  ...props
49
- }: BadgeProps) {
49
+ }: BadgeProps): React.JSX.Element {
50
50
  const Comp = asChild ? Slot.Root : "span";
51
51
 
52
52
  return (
@@ -23,7 +23,7 @@ export function ButtonWrapper({
23
23
  variant = "default",
24
24
  size = "default",
25
25
  disabled = false,
26
- }: ButtonWrapperProps) {
26
+ }: ButtonWrapperProps): React.JSX.Element {
27
27
  return (
28
28
  <Button variant={variant} size={size} disabled={disabled}>
29
29
  {label}
@@ -47,7 +47,7 @@ function Button({
47
47
  }: React.ComponentProps<"button"> &
48
48
  VariantProps<typeof buttonVariants> & {
49
49
  asChild?: boolean;
50
- }) {
50
+ }): React.JSX.Element {
51
51
  const Comp = asChild ? Slot.Root : "button";
52
52
 
53
53
  return (