@gram-ai/elements 1.27.4 → 1.27.5

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 (277) hide show
  1. package/README.md +72 -60
  2. package/README.typedoc.md +6 -6
  3. package/bin/cli.js +74 -74
  4. package/dist/compat-shims-CO9JXXV4.cjs.map +1 -1
  5. package/dist/{compat-shims-BPJ7Q68c.js → compat-shims-DxtUrORi.js} +4 -2
  6. package/dist/compat-shims-DxtUrORi.js.map +1 -0
  7. package/dist/components/ShareButton/index.d.ts +2 -2
  8. package/dist/components/assistant-ui/message-feedback.d.ts +1 -1
  9. package/dist/components/assistant-ui/tooltip-icon-button.d.ts +2 -2
  10. package/dist/components/ui/avatar.d.ts +2 -2
  11. package/dist/components/ui/button.d.ts +1 -1
  12. package/dist/components/ui/calendar.d.ts +1 -1
  13. package/dist/components/ui/collapsible.d.ts +1 -1
  14. package/dist/components/ui/dialog.d.ts +4 -4
  15. package/dist/components/ui/popover.d.ts +2 -2
  16. package/dist/components/ui/skeleton.d.ts +1 -1
  17. package/dist/components/ui/time-range-picker.d.ts +1 -1
  18. package/dist/components/ui/tool-ui.d.ts +7 -7
  19. package/dist/components/ui/tooltip.d.ts +2 -2
  20. package/dist/contexts/ConnectionStatusContext.d.ts +1 -1
  21. package/dist/elements.cjs +1 -1
  22. package/dist/elements.js +2 -2
  23. package/dist/hooks/useDensity.d.ts +73 -73
  24. package/dist/hooks/useMCPTools.d.ts +1 -1
  25. package/dist/hooks/useRadius.d.ts +1 -1
  26. package/dist/{index-BpJstUh1.cjs → index-C4bFBGfl.cjs} +4 -4
  27. package/dist/{index-BpJstUh1.cjs.map → index-C4bFBGfl.cjs.map} +1 -1
  28. package/dist/{index-CUitXazZ.js → index-D93pV0_o.js} +55 -55
  29. package/dist/{index-CUitXazZ.js.map → index-D93pV0_o.js.map} +1 -1
  30. package/dist/{index-D0bAYNQy.js → index-DuCQRbcQ.js} +279 -265
  31. package/dist/index-DuCQRbcQ.js.map +1 -0
  32. package/dist/{index-KSX4Qjip.cjs → index-y_PNN5vK.cjs} +10 -10
  33. package/dist/index-y_PNN5vK.cjs.map +1 -0
  34. package/dist/lib/cassette.d.ts +4 -4
  35. package/dist/lib/errorTracking.d.ts +1 -1
  36. package/dist/lib/messageConverter.d.ts +1 -1
  37. package/dist/lib/models.d.ts +1 -1
  38. package/dist/plugins/chart/ui/bar-chart.d.ts +1 -1
  39. package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +2 -2
  40. package/dist/plugins/generative-ui/ui/accordion.d.ts +1 -1
  41. package/dist/plugins/generative-ui/ui/action-button.d.ts +2 -2
  42. package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +1 -1
  43. package/dist/plugins/generative-ui/ui/alert.d.ts +4 -4
  44. package/dist/plugins/generative-ui/ui/avatar.d.ts +5 -5
  45. package/dist/plugins/generative-ui/ui/badge.d.ts +2 -2
  46. package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +2 -2
  47. package/dist/plugins/generative-ui/ui/button.d.ts +2 -2
  48. package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +2 -2
  49. package/dist/plugins/generative-ui/ui/card.d.ts +8 -8
  50. package/dist/plugins/generative-ui/ui/checkbox.d.ts +1 -1
  51. package/dist/plugins/generative-ui/ui/data-table.d.ts +2 -2
  52. package/dist/plugins/generative-ui/ui/dialog.d.ts +3 -3
  53. package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +3 -3
  54. package/dist/plugins/generative-ui/ui/grid.d.ts +3 -3
  55. package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +1 -1
  56. package/dist/plugins/generative-ui/ui/input.d.ts +2 -2
  57. package/dist/plugins/generative-ui/ui/label.d.ts +1 -1
  58. package/dist/plugins/generative-ui/ui/metric.d.ts +3 -3
  59. package/dist/plugins/generative-ui/ui/pagination.d.ts +6 -6
  60. package/dist/plugins/generative-ui/ui/popover.d.ts +4 -4
  61. package/dist/plugins/generative-ui/ui/progress.d.ts +2 -2
  62. package/dist/plugins/generative-ui/ui/radio-group.d.ts +1 -1
  63. package/dist/plugins/generative-ui/ui/select.d.ts +2 -2
  64. package/dist/plugins/generative-ui/ui/separator.d.ts +1 -1
  65. package/dist/plugins/generative-ui/ui/skeleton.d.ts +1 -1
  66. package/dist/plugins/generative-ui/ui/stack.d.ts +6 -6
  67. package/dist/plugins/generative-ui/ui/switch.d.ts +2 -2
  68. package/dist/plugins/generative-ui/ui/table.d.ts +9 -9
  69. package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +1 -1
  70. package/dist/plugins/generative-ui/ui/tabs.d.ts +1 -1
  71. package/dist/plugins/generative-ui/ui/text.d.ts +3 -3
  72. package/dist/plugins/generative-ui/ui/textarea.d.ts +2 -2
  73. package/dist/plugins/generative-ui/ui/tooltip.d.ts +1 -1
  74. package/dist/plugins.cjs +1 -1
  75. package/dist/plugins.js +1 -1
  76. package/dist/{profiler-BFkhZRxj.js → profiler-FpBY9eRv.js} +2 -2
  77. package/dist/{profiler-BFkhZRxj.js.map → profiler-FpBY9eRv.js.map} +1 -1
  78. package/dist/{profiler-CyzxBxVz.cjs → profiler-_mthyjvo.cjs} +2 -2
  79. package/dist/{profiler-CyzxBxVz.cjs.map → profiler-_mthyjvo.cjs.map} +1 -1
  80. package/dist/react-shim.js +1 -1
  81. package/dist/server/express.cjs.map +1 -1
  82. package/dist/server/express.js.map +1 -1
  83. package/dist/{startRecording-Dq92sEHf.cjs → startRecording-NJcpiHw-.cjs} +2 -2
  84. package/dist/{startRecording-Dq92sEHf.cjs.map → startRecording-NJcpiHw-.cjs.map} +1 -1
  85. package/dist/{startRecording-C-PPAs_Z.js → startRecording-r5MXQ2Dm.js} +2 -2
  86. package/dist/{startRecording-C-PPAs_Z.js.map → startRecording-r5MXQ2Dm.js.map} +1 -1
  87. package/dist/types/index.d.ts +2 -2
  88. package/package.json +1 -5
  89. package/src/compat-plugin.ts +14 -14
  90. package/src/compat-shims.ts +33 -31
  91. package/src/compat.test.ts +48 -48
  92. package/src/compat.ts +6 -6
  93. package/src/components/Chat/index.tsx +17 -17
  94. package/src/components/Chat/stories/Charts.stories.tsx +98 -98
  95. package/src/components/Chat/stories/Composer.stories.tsx +15 -15
  96. package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +44 -44
  97. package/src/components/Chat/stories/CustomComponents.stories.tsx +17 -17
  98. package/src/components/Chat/stories/Density.stories.tsx +20 -20
  99. package/src/components/Chat/stories/ErrorBoundary.stories.tsx +47 -47
  100. package/src/components/Chat/stories/FrontendTools.stories.tsx +39 -39
  101. package/src/components/Chat/stories/GenerativeUI.stories.tsx +48 -48
  102. package/src/components/Chat/stories/MessageFeedback.stories.tsx +52 -52
  103. package/src/components/Chat/stories/Modal.stories.tsx +28 -28
  104. package/src/components/Chat/stories/Model.stories.tsx +11 -11
  105. package/src/components/Chat/stories/Radius.stories.tsx +20 -20
  106. package/src/components/Chat/stories/Sidecar.stories.tsx +13 -13
  107. package/src/components/Chat/stories/StyleIsolation.stories.tsx +11 -11
  108. package/src/components/Chat/stories/Theme.stories.tsx +25 -25
  109. package/src/components/Chat/stories/Thread.stories.tsx +25 -25
  110. package/src/components/Chat/stories/ToolApproval.stories.tsx +55 -55
  111. package/src/components/Chat/stories/ToolMentions.stories.tsx +17 -17
  112. package/src/components/Chat/stories/Tools.stories.tsx +88 -88
  113. package/src/components/Chat/stories/Variants.stories.tsx +32 -32
  114. package/src/components/Chat/stories/Welcome.stories.tsx +14 -14
  115. package/src/components/ChatHistory.tsx +7 -7
  116. package/src/components/FrontendTools/index.tsx +5 -5
  117. package/src/components/Replay.stories.tsx +157 -157
  118. package/src/components/Replay.tsx +76 -73
  119. package/src/components/ShadowRoot.tsx +40 -40
  120. package/src/components/ShareButton/index.tsx +32 -32
  121. package/src/components/assistant-ui/assistant-modal.tsx +92 -87
  122. package/src/components/assistant-ui/assistant-sidecar.tsx +35 -35
  123. package/src/components/assistant-ui/attachment.tsx +80 -80
  124. package/src/components/assistant-ui/connection-status-indicator.tsx +33 -33
  125. package/src/components/assistant-ui/error-boundary.tsx +34 -34
  126. package/src/components/assistant-ui/follow-on-suggestions.tsx +26 -26
  127. package/src/components/assistant-ui/markdown-text.tsx +69 -69
  128. package/src/components/assistant-ui/mentioned-tools-badges.tsx +38 -38
  129. package/src/components/assistant-ui/message-feedback.tsx +57 -50
  130. package/src/components/assistant-ui/reasoning.tsx +83 -83
  131. package/src/components/assistant-ui/thread-list.tsx +45 -45
  132. package/src/components/assistant-ui/thread.tsx +278 -278
  133. package/src/components/assistant-ui/tool-fallback.tsx +37 -37
  134. package/src/components/assistant-ui/tool-group.tsx +26 -26
  135. package/src/components/assistant-ui/tool-mention-autocomplete.tsx +122 -122
  136. package/src/components/assistant-ui/tooltip-icon-button.tsx +18 -18
  137. package/src/components/ui/avatar.tsx +12 -12
  138. package/src/components/ui/button.tsx +12 -12
  139. package/src/components/ui/buttonVariants.ts +17 -17
  140. package/src/components/ui/calendar.tsx +106 -106
  141. package/src/components/ui/charts.stories.tsx +56 -56
  142. package/src/components/ui/collapsible.tsx +5 -5
  143. package/src/components/ui/dialog.tsx +30 -30
  144. package/src/components/ui/generative-ui.stories.tsx +200 -200
  145. package/src/components/ui/generative-ui.tsx +26 -26
  146. package/src/components/ui/popover.tsx +14 -14
  147. package/src/components/ui/skeleton.tsx +5 -5
  148. package/src/components/ui/time-range-picker.stories.tsx +80 -80
  149. package/src/components/ui/time-range-picker.tsx +245 -244
  150. package/src/components/ui/tool-ui.stories.tsx +37 -37
  151. package/src/components/ui/tool-ui.tsx +221 -215
  152. package/src/components/ui/tooltip.tsx +15 -15
  153. package/src/constants/tailwind.ts +1 -1
  154. package/src/contexts/ChatIdContext.tsx +7 -7
  155. package/src/contexts/ConnectionStatusContext.tsx +64 -64
  156. package/src/contexts/ElementsProvider.tsx +214 -213
  157. package/src/contexts/ReplayContext.ts +3 -3
  158. package/src/contexts/ToolApprovalContext.tsx +54 -54
  159. package/src/contexts/ToolExecutionContext.tsx +34 -34
  160. package/src/contexts/contexts.ts +7 -7
  161. package/src/contexts/portal-container-context.ts +2 -2
  162. package/src/contexts/portal-container.tsx +7 -7
  163. package/src/embedded.ts +1 -1
  164. package/src/global.css +25 -25
  165. package/src/hooks/useAuth.ts +72 -72
  166. package/src/hooks/useDensity.ts +79 -79
  167. package/src/hooks/useElements.ts +6 -6
  168. package/src/hooks/useExpanded.ts +12 -12
  169. package/src/hooks/useFollowOnSuggestions.ts +83 -83
  170. package/src/hooks/useGramThreadListAdapter.tsx +99 -99
  171. package/src/hooks/useMCPTools.ts +47 -47
  172. package/src/hooks/useModel.ts +14 -14
  173. package/src/hooks/usePluginComponents.ts +11 -11
  174. package/src/hooks/usePortalContainer.ts +5 -5
  175. package/src/hooks/useRadius.ts +23 -23
  176. package/src/hooks/useRecordCassette.ts +34 -34
  177. package/src/hooks/useSession.ts +11 -11
  178. package/src/hooks/useThemeProps.ts +13 -13
  179. package/src/hooks/useThreadId.ts +4 -4
  180. package/src/hooks/useToolApproval.ts +7 -7
  181. package/src/hooks/useToolMentions.ts +40 -40
  182. package/src/index.ts +26 -26
  183. package/src/lib/api.test.ts +61 -61
  184. package/src/lib/api.ts +4 -3
  185. package/src/lib/auth.ts +13 -13
  186. package/src/lib/cassette.ts +84 -84
  187. package/src/lib/easing.ts +1 -1
  188. package/src/lib/errorTracking.config.ts +5 -5
  189. package/src/lib/errorTracking.ts +29 -29
  190. package/src/lib/generative-ui.ts +7 -7
  191. package/src/lib/humanize.ts +3 -3
  192. package/src/lib/messageConverter.test.ts +130 -127
  193. package/src/lib/messageConverter.ts +196 -196
  194. package/src/lib/models.ts +21 -20
  195. package/src/lib/token.test.ts +56 -56
  196. package/src/lib/token.ts +14 -14
  197. package/src/lib/tool-mentions.ts +45 -45
  198. package/src/lib/tools.ts +66 -62
  199. package/src/lib/utils.ts +5 -5
  200. package/src/lib.d.ts +1 -1
  201. package/src/plugins/README.md +5 -5
  202. package/src/plugins/chart/catalog.ts +18 -18
  203. package/src/plugins/chart/chart.test.ts +31 -31
  204. package/src/plugins/chart/component.tsx +34 -34
  205. package/src/plugins/chart/index.ts +4 -4
  206. package/src/plugins/chart/ui/area-chart.tsx +42 -42
  207. package/src/plugins/chart/ui/bar-chart.tsx +46 -46
  208. package/src/plugins/chart/ui/donut-chart.tsx +48 -48
  209. package/src/plugins/chart/ui/index.ts +7 -7
  210. package/src/plugins/chart/ui/line-chart.tsx +43 -43
  211. package/src/plugins/chart/ui/pie-chart.tsx +44 -44
  212. package/src/plugins/chart/ui/radar-chart.tsx +33 -33
  213. package/src/plugins/chart/ui/scatter-chart.tsx +43 -43
  214. package/src/plugins/components/MacOSWindowFrame.tsx +15 -15
  215. package/src/plugins/components/PluginLoadingState.tsx +10 -10
  216. package/src/plugins/components/index.ts +1 -1
  217. package/src/plugins/generative-ui/catalog.ts +54 -54
  218. package/src/plugins/generative-ui/component.tsx +85 -85
  219. package/src/plugins/generative-ui/index.ts +4 -4
  220. package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +16 -16
  221. package/src/plugins/generative-ui/ui/accordion.tsx +16 -16
  222. package/src/plugins/generative-ui/ui/action-button.tsx +28 -28
  223. package/src/plugins/generative-ui/ui/alert-wrapper.tsx +8 -8
  224. package/src/plugins/generative-ui/ui/alert.tsx +20 -20
  225. package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +7 -7
  226. package/src/plugins/generative-ui/ui/avatar.tsx +30 -30
  227. package/src/plugins/generative-ui/ui/badge.tsx +22 -22
  228. package/src/plugins/generative-ui/ui/button-wrapper.tsx +12 -12
  229. package/src/plugins/generative-ui/ui/button.tsx +28 -28
  230. package/src/plugins/generative-ui/ui/card-wrapper.tsx +8 -8
  231. package/src/plugins/generative-ui/ui/card.tsx +27 -27
  232. package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +9 -9
  233. package/src/plugins/generative-ui/ui/checkbox.tsx +9 -9
  234. package/src/plugins/generative-ui/ui/data-table.tsx +8 -8
  235. package/src/plugins/generative-ui/ui/dialog.tsx +31 -31
  236. package/src/plugins/generative-ui/ui/dropdown-menu.tsx +44 -44
  237. package/src/plugins/generative-ui/ui/grid.tsx +12 -12
  238. package/src/plugins/generative-ui/ui/index.ts +40 -40
  239. package/src/plugins/generative-ui/ui/input-wrapper.tsx +11 -11
  240. package/src/plugins/generative-ui/ui/input.tsx +9 -9
  241. package/src/plugins/generative-ui/ui/label.tsx +8 -8
  242. package/src/plugins/generative-ui/ui/list.tsx +11 -11
  243. package/src/plugins/generative-ui/ui/metric.tsx +23 -23
  244. package/src/plugins/generative-ui/ui/pagination.tsx +28 -28
  245. package/src/plugins/generative-ui/ui/popover.tsx +21 -21
  246. package/src/plugins/generative-ui/ui/progress.tsx +13 -13
  247. package/src/plugins/generative-ui/ui/radio-group.tsx +12 -12
  248. package/src/plugins/generative-ui/ui/select-wrapper.tsx +7 -7
  249. package/src/plugins/generative-ui/ui/select.tsx +37 -37
  250. package/src/plugins/generative-ui/ui/separator.tsx +9 -9
  251. package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +10 -10
  252. package/src/plugins/generative-ui/ui/skeleton.tsx +5 -5
  253. package/src/plugins/generative-ui/ui/stack.tsx +28 -28
  254. package/src/plugins/generative-ui/ui/switch.tsx +11 -11
  255. package/src/plugins/generative-ui/ui/table.tsx +32 -32
  256. package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +11 -11
  257. package/src/plugins/generative-ui/ui/tabs.tsx +26 -26
  258. package/src/plugins/generative-ui/ui/text.tsx +12 -12
  259. package/src/plugins/generative-ui/ui/textarea.tsx +7 -7
  260. package/src/plugins/generative-ui/ui/tooltip.tsx +12 -12
  261. package/src/plugins/index.ts +7 -7
  262. package/src/react-shim.ts +6 -6
  263. package/src/server/bun.ts +12 -12
  264. package/src/server/core.ts +25 -25
  265. package/src/server/express.ts +17 -15
  266. package/src/server/fastify.ts +14 -14
  267. package/src/server/hono.ts +9 -9
  268. package/src/server/nextjs.ts +12 -12
  269. package/src/server/tanstack-start.ts +12 -12
  270. package/src/server.ts +27 -27
  271. package/src/storybook.d.ts +4 -4
  272. package/src/types/index.ts +122 -122
  273. package/src/types/plugins.ts +7 -7
  274. package/src/vite-env.d.ts +12 -12
  275. package/dist/compat-shims-BPJ7Q68c.js.map +0 -1
  276. package/dist/index-D0bAYNQy.js.map +0 -1
  277. package/dist/index-KSX4Qjip.cjs.map +0 -1
package/src/lib/models.ts CHANGED
@@ -1,23 +1,24 @@
1
1
  // List of openrouter models available to the user
2
2
  // This list should be updated to match the model whitelist on the backend side.
3
3
  export const MODELS = [
4
- 'anthropic/claude-opus-4.6',
5
- 'anthropic/claude-sonnet-4.5',
6
- 'anthropic/claude-haiku-4.5',
7
- 'anthropic/claude-sonnet-4',
8
- 'anthropic/claude-opus-4.5',
9
- 'openai/gpt-4o',
10
- 'openai/gpt-4o-mini',
11
- 'openai/gpt-5.1-codex',
12
- 'openai/gpt-5',
13
- 'openai/gpt-5.1',
14
- 'openai/gpt-4.1',
15
- 'anthropic/claude-3.7-sonnet',
16
- 'anthropic/claude-opus-4',
17
- 'google/gemini-2.5-pro-preview',
18
- 'google/gemini-3-pro-preview',
19
- 'moonshotai/kimi-k2',
20
- 'mistralai/mistral-medium-3',
21
- 'mistralai/mistral-medium-3.1',
22
- 'mistralai/codestral-2501',
23
- ] as const
4
+ "anthropic/claude-opus-4.6",
5
+ "anthropic/claude-sonnet-4.5",
6
+ "anthropic/claude-haiku-4.5",
7
+ "anthropic/claude-sonnet-4",
8
+ "anthropic/claude-opus-4.5",
9
+ "openai/gpt-5.4",
10
+ "openai/gpt-4o",
11
+ "openai/gpt-4o-mini",
12
+ "openai/gpt-5.1-codex",
13
+ "openai/gpt-5",
14
+ "openai/gpt-5.1",
15
+ "openai/gpt-4.1",
16
+ "anthropic/claude-3.7-sonnet",
17
+ "anthropic/claude-opus-4",
18
+ "google/gemini-2.5-pro-preview",
19
+ "google/gemini-3-pro-preview",
20
+ "moonshotai/kimi-k2",
21
+ "mistralai/mistral-medium-3",
22
+ "mistralai/mistral-medium-3.1",
23
+ "mistralai/codestral-2501",
24
+ ] as const;
@@ -1,79 +1,79 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { getTokenExpiry, isTokenExpired } from './token'
1
+ import { describe, expect, it } from "vitest";
2
+ import { getTokenExpiry, isTokenExpired } from "./token";
3
3
 
4
4
  /** Helper: build a JWT with a given payload (no signature verification needed). */
5
5
  function makeJwt(payload: Record<string, unknown>): string {
6
- const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
7
- const body = btoa(JSON.stringify(payload))
8
- return `${header}.${body}.fake-signature`
6
+ const header = btoa(JSON.stringify({ alg: "HS256", typ: "JWT" }));
7
+ const body = btoa(JSON.stringify(payload));
8
+ return `${header}.${body}.fake-signature`;
9
9
  }
10
10
 
11
- describe('getTokenExpiry', () => {
12
- it('returns exp for a valid JWT', () => {
13
- const exp = 1700000000
14
- expect(getTokenExpiry(makeJwt({ exp }))).toBe(exp)
15
- })
11
+ describe("getTokenExpiry", () => {
12
+ it("returns exp for a valid JWT", () => {
13
+ const exp = 1700000000;
14
+ expect(getTokenExpiry(makeJwt({ exp }))).toBe(exp);
15
+ });
16
16
 
17
- it('returns null when exp is missing', () => {
18
- expect(getTokenExpiry(makeJwt({ sub: 'user' }))).toBeNull()
19
- })
17
+ it("returns null when exp is missing", () => {
18
+ expect(getTokenExpiry(makeJwt({ sub: "user" }))).toBeNull();
19
+ });
20
20
 
21
- it('returns null for a non-JWT string', () => {
22
- expect(getTokenExpiry('not-a-jwt')).toBeNull()
23
- })
21
+ it("returns null for a non-JWT string", () => {
22
+ expect(getTokenExpiry("not-a-jwt")).toBeNull();
23
+ });
24
24
 
25
- it('returns null for an empty string', () => {
26
- expect(getTokenExpiry('')).toBeNull()
27
- })
25
+ it("returns null for an empty string", () => {
26
+ expect(getTokenExpiry("")).toBeNull();
27
+ });
28
28
 
29
- it('returns null when payload is not valid JSON', () => {
29
+ it("returns null when payload is not valid JSON", () => {
30
30
  // Two dots but the middle segment is not valid base64 JSON
31
- expect(getTokenExpiry('a.!!!.b')).toBeNull()
32
- })
31
+ expect(getTokenExpiry("a.!!!.b")).toBeNull();
32
+ });
33
33
 
34
- it('handles base64url characters (- and _)', () => {
34
+ it("handles base64url characters (- and _)", () => {
35
35
  // Manually craft a payload with base64url-specific chars
36
- const payload = { exp: 1700000000 }
37
- const json = JSON.stringify(payload)
36
+ const payload = { exp: 1700000000 };
37
+ const json = JSON.stringify(payload);
38
38
  const b64 = btoa(json)
39
- .replace(/\+/g, '-')
40
- .replace(/\//g, '_')
41
- .replace(/=+$/, '')
42
- const token = `header.${b64}.sig`
43
- expect(getTokenExpiry(token)).toBe(1700000000)
44
- })
45
- })
39
+ .replace(/\+/g, "-")
40
+ .replace(/\//g, "_")
41
+ .replace(/=+$/, "");
42
+ const token = `header.${b64}.sig`;
43
+ expect(getTokenExpiry(token)).toBe(1700000000);
44
+ });
45
+ });
46
46
 
47
- describe('isTokenExpired', () => {
48
- it('returns true for an expired token', () => {
47
+ describe("isTokenExpired", () => {
48
+ it("returns true for an expired token", () => {
49
49
  // exp = 1 second ago
50
- const exp = Math.floor(Date.now() / 1000) - 1
51
- expect(isTokenExpired(makeJwt({ exp }))).toBe(true)
52
- })
50
+ const exp = Math.floor(Date.now() / 1000) - 1;
51
+ expect(isTokenExpired(makeJwt({ exp }))).toBe(true);
52
+ });
53
53
 
54
- it('returns true when token is within the buffer window', () => {
54
+ it("returns true when token is within the buffer window", () => {
55
55
  // exp = 20 seconds from now (within 30s default buffer)
56
- const exp = Math.floor(Date.now() / 1000) + 20
57
- expect(isTokenExpired(makeJwt({ exp }))).toBe(true)
58
- })
56
+ const exp = Math.floor(Date.now() / 1000) + 20;
57
+ expect(isTokenExpired(makeJwt({ exp }))).toBe(true);
58
+ });
59
59
 
60
- it('returns false when token is well outside the buffer', () => {
60
+ it("returns false when token is well outside the buffer", () => {
61
61
  // exp = 5 minutes from now
62
- const exp = Math.floor(Date.now() / 1000) + 300
63
- expect(isTokenExpired(makeJwt({ exp }))).toBe(false)
64
- })
62
+ const exp = Math.floor(Date.now() / 1000) + 300;
63
+ expect(isTokenExpired(makeJwt({ exp }))).toBe(false);
64
+ });
65
65
 
66
- it('respects a custom buffer', () => {
66
+ it("respects a custom buffer", () => {
67
67
  // exp = 20 seconds from now, buffer = 10s → should NOT be expired
68
- const exp = Math.floor(Date.now() / 1000) + 20
69
- expect(isTokenExpired(makeJwt({ exp }), 10_000)).toBe(false)
70
- })
68
+ const exp = Math.floor(Date.now() / 1000) + 20;
69
+ expect(isTokenExpired(makeJwt({ exp }), 10_000)).toBe(false);
70
+ });
71
71
 
72
- it('returns false (fail-open) for a non-JWT string', () => {
73
- expect(isTokenExpired('opaque-session-token')).toBe(false)
74
- })
72
+ it("returns false (fail-open) for a non-JWT string", () => {
73
+ expect(isTokenExpired("opaque-session-token")).toBe(false);
74
+ });
75
75
 
76
- it('returns false (fail-open) for an empty string', () => {
77
- expect(isTokenExpired('')).toBe(false)
78
- })
79
- })
76
+ it("returns false (fail-open) for an empty string", () => {
77
+ expect(isTokenExpired("")).toBe(false);
78
+ });
79
+ });
package/src/lib/token.ts CHANGED
@@ -5,22 +5,22 @@
5
5
  */
6
6
  export function getTokenExpiry(token: string): number | null {
7
7
  try {
8
- const parts = token.split('.')
9
- if (parts.length !== 3) return null
8
+ const parts = token.split(".");
9
+ if (parts.length !== 3) return null;
10
10
 
11
11
  // base64url → base64 → decode
12
- let payload = parts[1].replace(/-/g, '+').replace(/_/g, '/')
13
- while (payload.length % 4) payload += '='
12
+ let payload = parts[1].replace(/-/g, "+").replace(/_/g, "/");
13
+ while (payload.length % 4) payload += "=";
14
14
 
15
- const json = atob(payload)
16
- const parsed = JSON.parse(json)
15
+ const json = atob(payload);
16
+ const parsed = JSON.parse(json);
17
17
 
18
- if (typeof parsed.exp === 'number') {
19
- return parsed.exp
18
+ if (typeof parsed.exp === "number") {
19
+ return parsed.exp;
20
20
  }
21
- return null
21
+ return null;
22
22
  } catch {
23
- return null
23
+ return null;
24
24
  }
25
25
  }
26
26
 
@@ -31,9 +31,9 @@ export function getTokenExpiry(token: string): number | null {
31
31
  */
32
32
  export function isTokenExpired(
33
33
  token: string,
34
- bufferMs: number = 30_000
34
+ bufferMs: number = 30_000,
35
35
  ): boolean {
36
- const exp = getTokenExpiry(token)
37
- if (exp === null) return false // fail-open for non-JWT tokens
38
- return Date.now() >= exp * 1000 - bufferMs
36
+ const exp = getTokenExpiry(token);
37
+ if (exp === null) return false; // fail-open for non-JWT tokens
38
+ return Date.now() >= exp * 1000 - bufferMs;
39
39
  }
@@ -1,106 +1,106 @@
1
- export type ToolRecord = Record<string, unknown> | undefined
1
+ export type ToolRecord = Record<string, unknown> | undefined;
2
2
 
3
3
  export interface MentionableTool {
4
- id: string
5
- name: string
6
- description?: string
4
+ id: string;
5
+ name: string;
6
+ description?: string;
7
7
  }
8
8
 
9
9
  export interface MentionContext {
10
- isInMention: boolean
11
- query: string
12
- atPosition: number
10
+ isInMention: boolean;
11
+ query: string;
12
+ atPosition: number;
13
13
  }
14
14
 
15
- const MENTION_PATTERN = /@(\w+)/g
15
+ const MENTION_PATTERN = /@(\w+)/g;
16
16
 
17
17
  export function toolSetToMentionableTools(
18
- tools: ToolRecord
18
+ tools: ToolRecord,
19
19
  ): MentionableTool[] {
20
- if (!tools) return []
20
+ if (!tools) return [];
21
21
 
22
22
  return Object.entries(tools).map(([name, tool]) => ({
23
23
  id: name,
24
24
  name,
25
25
  description:
26
- typeof tool === 'object' && tool !== null && 'description' in tool
27
- ? String((tool as { description?: unknown }).description ?? '')
26
+ typeof tool === "object" && tool !== null && "description" in tool
27
+ ? String((tool as { description?: unknown }).description ?? "")
28
28
  : undefined,
29
- }))
29
+ }));
30
30
  }
31
31
 
32
32
  export function parseMentionedTools(text: string, tools: ToolRecord): string[] {
33
- if (!tools || !text) return []
33
+ if (!tools || !text) return [];
34
34
 
35
- const toolNames = Object.keys(tools)
36
- const mentions: string[] = []
37
- let match: RegExpExecArray | null
35
+ const toolNames = Object.keys(tools);
36
+ const mentions: string[] = [];
37
+ let match: RegExpExecArray | null;
38
38
 
39
- MENTION_PATTERN.lastIndex = 0
39
+ MENTION_PATTERN.lastIndex = 0;
40
40
  while ((match = MENTION_PATTERN.exec(text)) !== null) {
41
- mentions.push(match[1].toLowerCase())
41
+ mentions.push(match[1].toLowerCase());
42
42
  }
43
43
 
44
44
  const matchedToolIds = toolNames.filter((name) =>
45
- mentions.includes(name.toLowerCase())
46
- )
45
+ mentions.includes(name.toLowerCase()),
46
+ );
47
47
 
48
- return [...new Set(matchedToolIds)]
48
+ return [...new Set(matchedToolIds)];
49
49
  }
50
50
 
51
51
  export function detectMentionContext(
52
52
  text: string,
53
- cursorPosition: number
53
+ cursorPosition: number,
54
54
  ): MentionContext {
55
- const textBeforeCursor = text.slice(0, cursorPosition)
56
- const lastAtSymbol = textBeforeCursor.lastIndexOf('@')
55
+ const textBeforeCursor = text.slice(0, cursorPosition);
56
+ const lastAtSymbol = textBeforeCursor.lastIndexOf("@");
57
57
 
58
58
  if (lastAtSymbol === -1) {
59
- return { isInMention: false, query: '', atPosition: -1 }
59
+ return { isInMention: false, query: "", atPosition: -1 };
60
60
  }
61
61
 
62
- const textAfterAt = textBeforeCursor.slice(lastAtSymbol + 1)
62
+ const textAfterAt = textBeforeCursor.slice(lastAtSymbol + 1);
63
63
 
64
- if (textAfterAt.includes(' ') || textAfterAt.includes('\n')) {
65
- return { isInMention: false, query: '', atPosition: -1 }
64
+ if (textAfterAt.includes(" ") || textAfterAt.includes("\n")) {
65
+ return { isInMention: false, query: "", atPosition: -1 };
66
66
  }
67
67
 
68
68
  return {
69
69
  isInMention: true,
70
70
  query: textAfterAt.toLowerCase(),
71
71
  atPosition: lastAtSymbol,
72
- }
72
+ };
73
73
  }
74
74
 
75
75
  export function filterToolsByQuery(
76
76
  tools: MentionableTool[],
77
- query: string
77
+ query: string,
78
78
  ): MentionableTool[] {
79
- if (!query) return tools
79
+ if (!query) return tools;
80
80
 
81
- const queryLower = query.toLowerCase()
81
+ const queryLower = query.toLowerCase();
82
82
 
83
83
  return tools.filter((tool) => {
84
- const nameMatch = tool.name.toLowerCase().includes(queryLower)
85
- const descMatch = tool.description?.toLowerCase().includes(queryLower)
86
- return nameMatch || descMatch
87
- })
84
+ const nameMatch = tool.name.toLowerCase().includes(queryLower);
85
+ const descMatch = tool.description?.toLowerCase().includes(queryLower);
86
+ return nameMatch || descMatch;
87
+ });
88
88
  }
89
89
 
90
90
  export function insertToolMention(
91
91
  text: string,
92
92
  toolName: string,
93
93
  atPosition: number,
94
- cursorPosition: number
94
+ cursorPosition: number,
95
95
  ): { text: string; cursorPosition: number } {
96
- const beforeMention = text.slice(0, atPosition)
97
- const afterCursor = text.slice(cursorPosition)
98
- const newText = `${beforeMention}@${toolName} ${afterCursor}`
99
- const newCursorPosition = atPosition + toolName.length + 2
100
- return { text: newText, cursorPosition: newCursorPosition }
96
+ const beforeMention = text.slice(0, atPosition);
97
+ const afterCursor = text.slice(cursorPosition);
98
+ const newText = `${beforeMention}@${toolName} ${afterCursor}`;
99
+ const newCursorPosition = atPosition + toolName.length + 2;
100
+ return { text: newText, cursorPosition: newCursorPosition };
101
101
  }
102
102
 
103
103
  export function removeToolMention(text: string, toolName: string): string {
104
- const pattern = new RegExp(`@${toolName}\\s?`, 'gi')
105
- return text.replace(pattern, '')
104
+ const pattern = new RegExp(`@${toolName}\\s?`, "gi");
105
+ return text.replace(pattern, "");
106
106
  }
package/src/lib/tools.ts CHANGED
@@ -1,12 +1,12 @@
1
- import type { ToolsFilter } from '@/types'
1
+ import type { ToolsFilter } from "@/types";
2
2
  import {
3
3
  AssistantToolProps,
4
4
  Tool,
5
5
  makeAssistantTool,
6
- } from '@assistant-ui/react'
7
- import { JSONSchema7, ToolSet, type ToolCallOptions } from 'ai'
8
- import { FC } from 'react'
9
- import z from 'zod'
6
+ } from "@assistant-ui/react";
7
+ import { JSONSchema7, ToolSet, type ToolCallOptions } from "ai";
8
+ import { FC } from "react";
9
+ import z from "zod";
10
10
 
11
11
  /**
12
12
  * Converts from assistant-ui tool format to the AI SDK tool shape
@@ -21,9 +21,9 @@ export const toAISDKTools = (tools: Record<string, Tool>) => {
21
21
  ? z.toJSONSchema(tool.parameters)
22
22
  : tool.parameters) as JSONSchema7,
23
23
  },
24
- ])
25
- )
26
- }
24
+ ]),
25
+ );
26
+ };
27
27
 
28
28
  /**
29
29
  * Returns only frontend tools that are enabled
@@ -31,10 +31,10 @@ export const toAISDKTools = (tools: Record<string, Tool>) => {
31
31
  export const getEnabledTools = (tools: Record<string, Tool>) => {
32
32
  return Object.fromEntries(
33
33
  Object.entries(tools).filter(
34
- ([, tool]) => !tool.disabled && tool.type !== 'backend'
35
- )
36
- )
37
- }
34
+ ([, tool]) => !tool.disabled && tool.type !== "backend",
35
+ ),
36
+ );
37
+ };
38
38
 
39
39
  /**
40
40
  * A frontend tool is a tool that is defined by the user and can be used in the chat.
@@ -42,37 +42,37 @@ export const getEnabledTools = (tools: Record<string, Tool>) => {
42
42
  export type FrontendTool<TArgs extends Record<string, unknown>, TResult> = FC<
43
43
  AssistantToolProps<TArgs, TResult>
44
44
  > & {
45
- unstable_tool: AssistantToolProps<TArgs, TResult>
46
- }
45
+ unstable_tool: AssistantToolProps<TArgs, TResult>;
46
+ };
47
47
 
48
48
  /**
49
49
  * Module-level approval config that gets set by ElementsProvider at runtime.
50
50
  * This allows defineFrontendTool to check approval status during execute.
51
51
  */
52
52
  let approvalConfig: {
53
- helpers: ApprovalHelpers
54
- requiresApproval: (toolName: string) => boolean
55
- } | null = null
53
+ helpers: ApprovalHelpers;
54
+ requiresApproval: (toolName: string) => boolean;
55
+ } | null = null;
56
56
 
57
57
  /**
58
58
  * Sets the approval configuration. Called by ElementsProvider.
59
59
  */
60
60
  export function setFrontendToolApprovalConfig(
61
61
  helpers: ApprovalHelpers,
62
- toolsRequiringApproval: ToolsFilter
62
+ toolsRequiringApproval: ToolsFilter,
63
63
  ): void {
64
- const requiresApproval = createRequiresApprovalFn(toolsRequiringApproval)
64
+ const requiresApproval = createRequiresApprovalFn(toolsRequiringApproval);
65
65
  approvalConfig = {
66
66
  helpers,
67
67
  requiresApproval,
68
- }
68
+ };
69
69
  }
70
70
 
71
71
  /**
72
72
  * Clears the approval configuration. Called when ElementsProvider unmounts.
73
73
  */
74
74
  export function clearFrontendToolApprovalConfig(): void {
75
- approvalConfig = null
75
+ approvalConfig = null;
76
76
  }
77
77
 
78
78
  /**
@@ -80,18 +80,18 @@ export function clearFrontendToolApprovalConfig(): void {
80
80
  * Handles both array and function-based configurations.
81
81
  */
82
82
  function createRequiresApprovalFn(
83
- toolsRequiringApproval: ToolsFilter | undefined
83
+ toolsRequiringApproval: ToolsFilter | undefined,
84
84
  ): (toolName: string) => boolean {
85
85
  if (!toolsRequiringApproval) {
86
- return () => false
86
+ return () => false;
87
87
  }
88
88
 
89
- if (typeof toolsRequiringApproval === 'function') {
90
- return (toolName: string) => toolsRequiringApproval({ toolName })
89
+ if (typeof toolsRequiringApproval === "function") {
90
+ return (toolName: string) => toolsRequiringApproval({ toolName });
91
91
  }
92
92
 
93
- const approvalSet = new Set(toolsRequiringApproval)
94
- return (toolName: string) => approvalSet.has(toolName)
93
+ const approvalSet = new Set(toolsRequiringApproval);
94
+ return (toolName: string) => approvalSet.has(toolName);
95
95
  }
96
96
 
97
97
  /**
@@ -102,42 +102,46 @@ export const defineFrontendTool = <
102
102
  TResult,
103
103
  >(
104
104
  tool: Tool,
105
- name: string
105
+ name: string,
106
106
  ): FrontendTool<TArgs, TResult> => {
107
107
  type ToolExecutionContext = Parameters<
108
- NonNullable<Tool<Record<string, unknown>, void>['execute']>
109
- >[1]
108
+ NonNullable<Tool<Record<string, unknown>, void>["execute"]>
109
+ >[1];
110
110
  return makeAssistantTool({
111
111
  ...tool,
112
112
  execute: async (args: TArgs, context: ToolExecutionContext) => {
113
113
  // Check if this tool requires approval at runtime
114
114
  if (approvalConfig?.requiresApproval(name)) {
115
- const { helpers } = approvalConfig
116
- const toolCallId = context.toolCallId ?? ''
115
+ const { helpers } = approvalConfig;
116
+ const toolCallId = context.toolCallId ?? "";
117
117
 
118
118
  // Check if already approved (user chose "Approve always" previously)
119
119
  if (!helpers.isToolApproved(name)) {
120
- const approved = await helpers.requestApproval(name, toolCallId, args)
120
+ const approved = await helpers.requestApproval(
121
+ name,
122
+ toolCallId,
123
+ args,
124
+ );
121
125
 
122
126
  if (!approved) {
123
127
  return {
124
128
  content: [
125
129
  {
126
- type: 'text',
130
+ type: "text",
127
131
  text: `Tool "${name}" execution was denied by the user. Please acknowledge this and continue without using this tool's result.`,
128
132
  },
129
133
  ],
130
134
  isError: true,
131
- } as TResult
135
+ } as TResult;
132
136
  }
133
137
  }
134
138
  }
135
139
 
136
- return tool.execute?.(args, context)
140
+ return tool.execute?.(args, context);
137
141
  },
138
142
  toolName: name,
139
- } as AssistantToolProps<TArgs, TResult>)
140
- }
143
+ } as AssistantToolProps<TArgs, TResult>);
144
+ };
141
145
 
142
146
  /**
143
147
  * Helpers for requesting and tracking tool approval state.
@@ -146,10 +150,10 @@ export interface ApprovalHelpers {
146
150
  requestApproval: (
147
151
  toolName: string,
148
152
  toolCallId: string,
149
- args: unknown
150
- ) => Promise<boolean>
151
- isToolApproved: (toolName: string) => boolean
152
- whitelistTool: (toolName: string) => void
153
+ args: unknown,
154
+ ) => Promise<boolean>;
155
+ isToolApproved: (toolName: string) => boolean;
156
+ whitelistTool: (toolName: string) => void;
153
157
  }
154
158
 
155
159
  /**
@@ -158,10 +162,10 @@ export interface ApprovalHelpers {
158
162
  export function wrapToolsWithApproval(
159
163
  tools: ToolSet,
160
164
  toolsRequiringApproval: ToolsFilter | undefined,
161
- approvalHelpers: ApprovalHelpers
165
+ approvalHelpers: ApprovalHelpers,
162
166
  ): ToolSet {
163
167
  if (!toolsRequiringApproval) {
164
- return tools
168
+ return tools;
165
169
  }
166
170
 
167
171
  // Handle empty array case
@@ -169,20 +173,20 @@ export function wrapToolsWithApproval(
169
173
  Array.isArray(toolsRequiringApproval) &&
170
174
  toolsRequiringApproval.length === 0
171
175
  ) {
172
- return tools
176
+ return tools;
173
177
  }
174
178
 
175
- const requiresApproval = createRequiresApprovalFn(toolsRequiringApproval)
179
+ const requiresApproval = createRequiresApprovalFn(toolsRequiringApproval);
176
180
 
177
181
  return Object.fromEntries(
178
182
  Object.entries(tools).map(([name, tool]) => {
179
183
  if (!requiresApproval(name)) {
180
- return [name, tool]
184
+ return [name, tool];
181
185
  }
182
186
 
183
- const originalExecute = tool.execute
187
+ const originalExecute = tool.execute;
184
188
  if (!originalExecute) {
185
- return [name, tool]
189
+ return [name, tool];
186
190
  }
187
191
 
188
192
  return [
@@ -192,36 +196,36 @@ export function wrapToolsWithApproval(
192
196
  execute: async (args: unknown, options?: ToolCallOptions) => {
193
197
  const opts = (options ?? {}) as Parameters<
194
198
  typeof originalExecute
195
- >[1]
199
+ >[1];
196
200
  // Extract toolCallId from options
197
201
  const toolCallId =
198
- (opts as { toolCallId?: string }).toolCallId ?? ''
202
+ (opts as { toolCallId?: string }).toolCallId ?? "";
199
203
 
200
204
  // Check if already approved (user chose "Approve always" previously)
201
205
  if (approvalHelpers.isToolApproved(name)) {
202
206
  return originalExecute(
203
207
  args,
204
- opts as Parameters<typeof originalExecute>[1]
205
- )
208
+ opts as Parameters<typeof originalExecute>[1],
209
+ );
206
210
  }
207
211
 
208
212
  // Request approval using the actual toolCallId from the stream
209
213
  const approved = await approvalHelpers.requestApproval(
210
214
  name,
211
215
  toolCallId,
212
- args
213
- )
216
+ args,
217
+ );
214
218
 
215
219
  if (!approved) {
216
220
  return {
217
221
  content: [
218
222
  {
219
- type: 'text',
223
+ type: "text",
220
224
  text: `Tool "${name}" execution was denied by the user. Please acknowledge this and continue without using this tool's result.`,
221
225
  },
222
226
  ],
223
227
  isError: true,
224
- }
228
+ };
225
229
  }
226
230
 
227
231
  // Note: Tool is marked as approved via the UI when user clicks "Approve always"
@@ -229,11 +233,11 @@ export function wrapToolsWithApproval(
229
233
 
230
234
  return originalExecute(
231
235
  args,
232
- opts as Parameters<typeof originalExecute>[1]
233
- )
236
+ opts as Parameters<typeof originalExecute>[1],
237
+ );
234
238
  },
235
239
  },
236
- ]
237
- })
238
- ) as ToolSet
240
+ ];
241
+ }),
242
+ ) as ToolSet;
239
243
  }