strapi-plugin-ai-sdk 0.7.0 → 0.7.3

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.
@@ -6,7 +6,7 @@ const reactRouterDom = require("react-router-dom");
6
6
  const designSystem = require("@strapi/design-system");
7
7
  const react = require("react");
8
8
  const styled = require("styled-components");
9
- const index = require("./index-BNk29VRc.js");
9
+ const index = require("./index-Cw2aiQ8K.js");
10
10
  const icons = require("@strapi/icons");
11
11
  const Markdown = require("react-markdown");
12
12
  const remarkGfm = require("remark-gfm");
@@ -101,21 +101,37 @@ function generateId() {
101
101
  return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
102
102
  }
103
103
  function toUIMessages(messages) {
104
- return messages.map((message) => ({
105
- id: message.id,
106
- role: message.role,
107
- parts: [{ type: "text", text: message.content }]
108
- }));
104
+ return messages.map((message) => {
105
+ const parts = [];
106
+ if (message.content) {
107
+ parts.push({ type: "text", text: message.content });
108
+ }
109
+ if (message.toolCalls) {
110
+ for (const tc of message.toolCalls) {
111
+ parts.push({
112
+ type: `tool-${tc.toolName}`,
113
+ toolCallId: tc.toolCallId,
114
+ toolName: tc.toolName,
115
+ state: tc.output !== void 0 ? "output-available" : "input-available",
116
+ input: tc.input,
117
+ ...tc.output !== void 0 ? { output: tc.output } : {}
118
+ });
119
+ }
120
+ }
121
+ return { id: message.id, role: message.role, parts };
122
+ });
109
123
  }
110
- async function fetchChatStream(messages) {
124
+ async function fetchChatStream(messages, enabledToolSources) {
111
125
  const token = getToken();
126
+ const body = { messages: toUIMessages(messages) };
127
+ if (enabledToolSources) body.enabledToolSources = enabledToolSources;
112
128
  const response = await fetch(`${getBackendURL()}/${index.PLUGIN_ID}/chat`, {
113
129
  method: "POST",
114
130
  headers: {
115
131
  "Content-Type": "application/json",
116
132
  ...token ? { Authorization: `Bearer ${token}` } : {}
117
133
  },
118
- body: JSON.stringify({ messages: toUIMessages(messages) })
134
+ body: JSON.stringify(body)
119
135
  });
120
136
  if (!response.ok) throw new Error(`Request failed: ${response.status}`);
121
137
  const reader = response.body?.getReader();
@@ -160,7 +176,7 @@ function useChat(options) {
160
176
  setIsLoading(true);
161
177
  setError(null);
162
178
  try {
163
- const reader = await fetchChatStream([...messages, userMessage]);
179
+ const reader = await fetchChatStream([...messages, userMessage], options?.enabledToolSources);
164
180
  let streamStarted = false;
165
181
  const result = await readSSEStream(reader, {
166
182
  onTextDelta: (content) => {
@@ -392,6 +408,51 @@ function useMemories() {
392
408
  }, []);
393
409
  return { memories, loading, addMemory, editMemory, removeMemory, refresh: load };
394
410
  }
411
+ const STORAGE_KEY = `${index.PLUGIN_ID}:enabledToolSources`;
412
+ function useToolSources() {
413
+ const [sources, setSources] = react.useState([]);
414
+ const [enabledSources, setEnabledSources] = react.useState(() => {
415
+ try {
416
+ const stored = localStorage.getItem(STORAGE_KEY);
417
+ if (stored) return new Set(JSON.parse(stored));
418
+ } catch {
419
+ }
420
+ return /* @__PURE__ */ new Set();
421
+ });
422
+ const [loaded, setLoaded] = react.useState(false);
423
+ react.useEffect(() => {
424
+ const token = getToken();
425
+ fetch(`${getBackendURL()}/${index.PLUGIN_ID}/tool-sources`, {
426
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
427
+ }).then((res) => res.json()).then((json) => {
428
+ const data = json.data ?? [];
429
+ setSources(data);
430
+ setEnabledSources((prev) => {
431
+ if (prev.size === 0) {
432
+ const all = new Set(data.filter((s) => s.id !== "built-in").map((s) => s.id));
433
+ localStorage.setItem(STORAGE_KEY, JSON.stringify([...all]));
434
+ return all;
435
+ }
436
+ return prev;
437
+ });
438
+ setLoaded(true);
439
+ }).catch(() => setLoaded(true));
440
+ }, []);
441
+ const toggleSource = react.useCallback((id) => {
442
+ setEnabledSources((prev) => {
443
+ const next = new Set(prev);
444
+ if (next.has(id)) next.delete(id);
445
+ else next.add(id);
446
+ localStorage.setItem(STORAGE_KEY, JSON.stringify([...next]));
447
+ return next;
448
+ });
449
+ }, []);
450
+ const enabledToolSources = react.useMemo(
451
+ () => loaded ? [...enabledSources] : void 0,
452
+ [enabledSources, loaded]
453
+ );
454
+ return { sources, enabledSources, enabledToolSources, toggleSource, loaded };
455
+ }
395
456
  const SidebarRoot = styled__default.default.div`
396
457
  width: ${({ $open }) => $open ? "260px" : "0px"};
397
458
  min-width: ${({ $open }) => $open ? "260px" : "0px"};
@@ -618,6 +679,142 @@ function MemoryPanel({ memories, open, onDelete }) {
618
679
  ] })
619
680
  ] });
620
681
  }
682
+ const Wrapper = styled__default.default.div`
683
+ position: relative;
684
+ `;
685
+ const IconBtn = styled__default.default.button`
686
+ display: flex;
687
+ align-items: center;
688
+ justify-content: center;
689
+ width: 32px;
690
+ height: 32px;
691
+ border: 1px solid #dcdce4;
692
+ border-radius: 4px;
693
+ background: #ffffff;
694
+ color: #666687;
695
+ cursor: pointer;
696
+ flex-shrink: 0;
697
+
698
+ &:hover {
699
+ background: #f0f0f5;
700
+ color: #4945ff;
701
+ border-color: #4945ff;
702
+ }
703
+
704
+ svg {
705
+ width: 16px;
706
+ height: 16px;
707
+ }
708
+ `;
709
+ const Popover = styled__default.default.div`
710
+ position: absolute;
711
+ top: 40px;
712
+ left: 0;
713
+ z-index: 10;
714
+ width: 240px;
715
+ max-height: 360px;
716
+ overflow-y: auto;
717
+ background: #ffffff;
718
+ border: 1px solid #dcdce4;
719
+ border-radius: 4px;
720
+ box-shadow: 0 2px 12px rgba(33, 33, 52, 0.12);
721
+ padding: 8px 0;
722
+ `;
723
+ const SourceRow = styled__default.default.label`
724
+ display: flex;
725
+ flex-direction: column;
726
+ padding: 6px 12px;
727
+ cursor: pointer;
728
+ font-size: 13px;
729
+ color: #32324d;
730
+
731
+ &:hover {
732
+ background: #f6f6f9;
733
+ }
734
+ `;
735
+ const SourceMain = styled__default.default.div`
736
+ display: flex;
737
+ flex-direction: row;
738
+ align-items: center;
739
+ gap: 8px;
740
+ `;
741
+ const Badge = styled__default.default.div`
742
+ font-size: 11px;
743
+ color: #a5a5ba;
744
+ margin-left: 24px;
745
+ margin-top: 1px;
746
+ `;
747
+ const Toggle = styled__default.default.input`
748
+ accent-color: #4945ff;
749
+ flex-shrink: 0;
750
+ width: 16px;
751
+ height: 16px;
752
+ margin: 0;
753
+ `;
754
+ const Header = styled__default.default.div`
755
+ padding: 6px 12px 4px;
756
+ font-size: 11px;
757
+ font-weight: 600;
758
+ color: #a5a5ba;
759
+ text-transform: uppercase;
760
+ letter-spacing: 0.5px;
761
+ `;
762
+ function ToolSourcePicker({ sources, enabledSources, onToggle }) {
763
+ const [open, setOpen] = react.useState(false);
764
+ const ref = react.useRef(null);
765
+ react.useEffect(() => {
766
+ if (!open) return;
767
+ function handleClick(e) {
768
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
769
+ }
770
+ document.addEventListener("mousedown", handleClick);
771
+ return () => document.removeEventListener("mousedown", handleClick);
772
+ }, [open]);
773
+ const builtIn = sources.find((s) => s.id === "built-in");
774
+ const plugins = sources.filter((s) => s.id !== "built-in");
775
+ if (sources.length === 0) return null;
776
+ return /* @__PURE__ */ jsxRuntime.jsxs(Wrapper, { ref, children: [
777
+ /* @__PURE__ */ jsxRuntime.jsx(IconBtn, { onClick: () => setOpen((p) => !p), "aria-label": "Tool sources", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
778
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9.5 2.5L13 6l-7 7H2.5v-3.5l7-7z" }),
779
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 4l4 4" })
780
+ ] }) }),
781
+ open && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
782
+ builtIn && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
783
+ /* @__PURE__ */ jsxRuntime.jsx(Header, { children: "Built-in" }),
784
+ /* @__PURE__ */ jsxRuntime.jsxs(SourceRow, { children: [
785
+ /* @__PURE__ */ jsxRuntime.jsxs(SourceMain, { children: [
786
+ /* @__PURE__ */ jsxRuntime.jsx(Toggle, { type: "checkbox", checked: true, disabled: true }),
787
+ builtIn.label
788
+ ] }),
789
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { children: [
790
+ builtIn.toolCount,
791
+ " tools"
792
+ ] })
793
+ ] })
794
+ ] }),
795
+ plugins.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
796
+ /* @__PURE__ */ jsxRuntime.jsx(Header, { children: "Plugins" }),
797
+ plugins.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(SourceRow, { children: [
798
+ /* @__PURE__ */ jsxRuntime.jsxs(SourceMain, { children: [
799
+ /* @__PURE__ */ jsxRuntime.jsx(
800
+ Toggle,
801
+ {
802
+ type: "checkbox",
803
+ checked: enabledSources.has(s.id),
804
+ onChange: () => onToggle(s.id)
805
+ }
806
+ ),
807
+ s.label
808
+ ] }),
809
+ /* @__PURE__ */ jsxRuntime.jsxs(Badge, { children: [
810
+ s.toolCount,
811
+ " tools"
812
+ ] })
813
+ ] }, s.id))
814
+ ] })
815
+ ] })
816
+ ] });
817
+ }
621
818
  const SCORE_LABELS = {
622
819
  1: "Negligible",
623
820
  2: "Minor",
@@ -1355,9 +1552,11 @@ function Chat() {
1355
1552
  removeConversation
1356
1553
  } = useConversations();
1357
1554
  const { memories, removeMemory, refresh: refreshMemories } = useMemories();
1555
+ const { sources, enabledSources, enabledToolSources, toggleSource } = useToolSources();
1358
1556
  const { messages, sendMessage, isLoading, error } = useChat({
1359
1557
  initialMessages,
1360
- conversationId: activeId
1558
+ conversationId: activeId,
1559
+ enabledToolSources
1361
1560
  });
1362
1561
  react.useEffect(() => {
1363
1562
  if (prevIsLoadingRef.current && !isLoading && messages.length > 0) {
@@ -1431,6 +1630,14 @@ function Chat() {
1431
1630
  ] })
1432
1631
  }
1433
1632
  ),
1633
+ /* @__PURE__ */ jsxRuntime.jsx(
1634
+ ToolSourcePicker,
1635
+ {
1636
+ sources,
1637
+ enabledSources,
1638
+ onToggle: toggleSource
1639
+ }
1640
+ ),
1434
1641
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: 1 } }),
1435
1642
  /* @__PURE__ */ jsxRuntime.jsx(
1436
1643
  ToggleSidebarBtn,
@@ -1,10 +1,10 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { Layouts, Page } from "@strapi/strapi/admin";
3
3
  import { Link, useNavigate, Routes, Route } from "react-router-dom";
4
4
  import { Box, Typography, TextInput, Button, Main, SearchForm, Searchbar, Table, Thead, Tr, Th, Tbody, Td, Flex, Pagination, Modal, Field, Textarea, SingleSelect, SingleSelectOption } from "@strapi/design-system";
5
- import { useState, useEffect, useCallback, forwardRef, useRef, useMemo } from "react";
5
+ import { useState, useEffect, useCallback, useMemo, useRef, forwardRef } from "react";
6
6
  import styled from "styled-components";
7
- import { P as PLUGIN_ID } from "./index-CFO5UshL.mjs";
7
+ import { P as PLUGIN_ID } from "./index-BMrDQVQl.mjs";
8
8
  import { Plus, Trash, Sparkle, ArrowLeft, Pencil } from "@strapi/icons";
9
9
  import Markdown from "react-markdown";
10
10
  import remarkGfm from "remark-gfm";
@@ -95,21 +95,37 @@ function generateId() {
95
95
  return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
96
96
  }
97
97
  function toUIMessages(messages) {
98
- return messages.map((message) => ({
99
- id: message.id,
100
- role: message.role,
101
- parts: [{ type: "text", text: message.content }]
102
- }));
98
+ return messages.map((message) => {
99
+ const parts = [];
100
+ if (message.content) {
101
+ parts.push({ type: "text", text: message.content });
102
+ }
103
+ if (message.toolCalls) {
104
+ for (const tc of message.toolCalls) {
105
+ parts.push({
106
+ type: `tool-${tc.toolName}`,
107
+ toolCallId: tc.toolCallId,
108
+ toolName: tc.toolName,
109
+ state: tc.output !== void 0 ? "output-available" : "input-available",
110
+ input: tc.input,
111
+ ...tc.output !== void 0 ? { output: tc.output } : {}
112
+ });
113
+ }
114
+ }
115
+ return { id: message.id, role: message.role, parts };
116
+ });
103
117
  }
104
- async function fetchChatStream(messages) {
118
+ async function fetchChatStream(messages, enabledToolSources) {
105
119
  const token = getToken();
120
+ const body = { messages: toUIMessages(messages) };
121
+ if (enabledToolSources) body.enabledToolSources = enabledToolSources;
106
122
  const response = await fetch(`${getBackendURL()}/${PLUGIN_ID}/chat`, {
107
123
  method: "POST",
108
124
  headers: {
109
125
  "Content-Type": "application/json",
110
126
  ...token ? { Authorization: `Bearer ${token}` } : {}
111
127
  },
112
- body: JSON.stringify({ messages: toUIMessages(messages) })
128
+ body: JSON.stringify(body)
113
129
  });
114
130
  if (!response.ok) throw new Error(`Request failed: ${response.status}`);
115
131
  const reader = response.body?.getReader();
@@ -154,7 +170,7 @@ function useChat(options) {
154
170
  setIsLoading(true);
155
171
  setError(null);
156
172
  try {
157
- const reader = await fetchChatStream([...messages, userMessage]);
173
+ const reader = await fetchChatStream([...messages, userMessage], options?.enabledToolSources);
158
174
  let streamStarted = false;
159
175
  const result = await readSSEStream(reader, {
160
176
  onTextDelta: (content) => {
@@ -386,6 +402,51 @@ function useMemories() {
386
402
  }, []);
387
403
  return { memories, loading, addMemory, editMemory, removeMemory, refresh: load };
388
404
  }
405
+ const STORAGE_KEY = `${PLUGIN_ID}:enabledToolSources`;
406
+ function useToolSources() {
407
+ const [sources, setSources] = useState([]);
408
+ const [enabledSources, setEnabledSources] = useState(() => {
409
+ try {
410
+ const stored = localStorage.getItem(STORAGE_KEY);
411
+ if (stored) return new Set(JSON.parse(stored));
412
+ } catch {
413
+ }
414
+ return /* @__PURE__ */ new Set();
415
+ });
416
+ const [loaded, setLoaded] = useState(false);
417
+ useEffect(() => {
418
+ const token = getToken();
419
+ fetch(`${getBackendURL()}/${PLUGIN_ID}/tool-sources`, {
420
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
421
+ }).then((res) => res.json()).then((json) => {
422
+ const data = json.data ?? [];
423
+ setSources(data);
424
+ setEnabledSources((prev) => {
425
+ if (prev.size === 0) {
426
+ const all = new Set(data.filter((s) => s.id !== "built-in").map((s) => s.id));
427
+ localStorage.setItem(STORAGE_KEY, JSON.stringify([...all]));
428
+ return all;
429
+ }
430
+ return prev;
431
+ });
432
+ setLoaded(true);
433
+ }).catch(() => setLoaded(true));
434
+ }, []);
435
+ const toggleSource = useCallback((id) => {
436
+ setEnabledSources((prev) => {
437
+ const next = new Set(prev);
438
+ if (next.has(id)) next.delete(id);
439
+ else next.add(id);
440
+ localStorage.setItem(STORAGE_KEY, JSON.stringify([...next]));
441
+ return next;
442
+ });
443
+ }, []);
444
+ const enabledToolSources = useMemo(
445
+ () => loaded ? [...enabledSources] : void 0,
446
+ [enabledSources, loaded]
447
+ );
448
+ return { sources, enabledSources, enabledToolSources, toggleSource, loaded };
449
+ }
389
450
  const SidebarRoot = styled.div`
390
451
  width: ${({ $open }) => $open ? "260px" : "0px"};
391
452
  min-width: ${({ $open }) => $open ? "260px" : "0px"};
@@ -612,6 +673,142 @@ function MemoryPanel({ memories, open, onDelete }) {
612
673
  ] })
613
674
  ] });
614
675
  }
676
+ const Wrapper = styled.div`
677
+ position: relative;
678
+ `;
679
+ const IconBtn = styled.button`
680
+ display: flex;
681
+ align-items: center;
682
+ justify-content: center;
683
+ width: 32px;
684
+ height: 32px;
685
+ border: 1px solid #dcdce4;
686
+ border-radius: 4px;
687
+ background: #ffffff;
688
+ color: #666687;
689
+ cursor: pointer;
690
+ flex-shrink: 0;
691
+
692
+ &:hover {
693
+ background: #f0f0f5;
694
+ color: #4945ff;
695
+ border-color: #4945ff;
696
+ }
697
+
698
+ svg {
699
+ width: 16px;
700
+ height: 16px;
701
+ }
702
+ `;
703
+ const Popover = styled.div`
704
+ position: absolute;
705
+ top: 40px;
706
+ left: 0;
707
+ z-index: 10;
708
+ width: 240px;
709
+ max-height: 360px;
710
+ overflow-y: auto;
711
+ background: #ffffff;
712
+ border: 1px solid #dcdce4;
713
+ border-radius: 4px;
714
+ box-shadow: 0 2px 12px rgba(33, 33, 52, 0.12);
715
+ padding: 8px 0;
716
+ `;
717
+ const SourceRow = styled.label`
718
+ display: flex;
719
+ flex-direction: column;
720
+ padding: 6px 12px;
721
+ cursor: pointer;
722
+ font-size: 13px;
723
+ color: #32324d;
724
+
725
+ &:hover {
726
+ background: #f6f6f9;
727
+ }
728
+ `;
729
+ const SourceMain = styled.div`
730
+ display: flex;
731
+ flex-direction: row;
732
+ align-items: center;
733
+ gap: 8px;
734
+ `;
735
+ const Badge = styled.div`
736
+ font-size: 11px;
737
+ color: #a5a5ba;
738
+ margin-left: 24px;
739
+ margin-top: 1px;
740
+ `;
741
+ const Toggle = styled.input`
742
+ accent-color: #4945ff;
743
+ flex-shrink: 0;
744
+ width: 16px;
745
+ height: 16px;
746
+ margin: 0;
747
+ `;
748
+ const Header = styled.div`
749
+ padding: 6px 12px 4px;
750
+ font-size: 11px;
751
+ font-weight: 600;
752
+ color: #a5a5ba;
753
+ text-transform: uppercase;
754
+ letter-spacing: 0.5px;
755
+ `;
756
+ function ToolSourcePicker({ sources, enabledSources, onToggle }) {
757
+ const [open, setOpen] = useState(false);
758
+ const ref = useRef(null);
759
+ useEffect(() => {
760
+ if (!open) return;
761
+ function handleClick(e) {
762
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
763
+ }
764
+ document.addEventListener("mousedown", handleClick);
765
+ return () => document.removeEventListener("mousedown", handleClick);
766
+ }, [open]);
767
+ const builtIn = sources.find((s) => s.id === "built-in");
768
+ const plugins = sources.filter((s) => s.id !== "built-in");
769
+ if (sources.length === 0) return null;
770
+ return /* @__PURE__ */ jsxs(Wrapper, { ref, children: [
771
+ /* @__PURE__ */ jsx(IconBtn, { onClick: () => setOpen((p) => !p), "aria-label": "Tool sources", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
772
+ /* @__PURE__ */ jsx("path", { d: "M9.5 2.5L13 6l-7 7H2.5v-3.5l7-7z" }),
773
+ /* @__PURE__ */ jsx("path", { d: "M8 4l4 4" })
774
+ ] }) }),
775
+ open && /* @__PURE__ */ jsxs(Popover, { children: [
776
+ builtIn && /* @__PURE__ */ jsxs(Fragment, { children: [
777
+ /* @__PURE__ */ jsx(Header, { children: "Built-in" }),
778
+ /* @__PURE__ */ jsxs(SourceRow, { children: [
779
+ /* @__PURE__ */ jsxs(SourceMain, { children: [
780
+ /* @__PURE__ */ jsx(Toggle, { type: "checkbox", checked: true, disabled: true }),
781
+ builtIn.label
782
+ ] }),
783
+ /* @__PURE__ */ jsxs(Badge, { children: [
784
+ builtIn.toolCount,
785
+ " tools"
786
+ ] })
787
+ ] })
788
+ ] }),
789
+ plugins.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
790
+ /* @__PURE__ */ jsx(Header, { children: "Plugins" }),
791
+ plugins.map((s) => /* @__PURE__ */ jsxs(SourceRow, { children: [
792
+ /* @__PURE__ */ jsxs(SourceMain, { children: [
793
+ /* @__PURE__ */ jsx(
794
+ Toggle,
795
+ {
796
+ type: "checkbox",
797
+ checked: enabledSources.has(s.id),
798
+ onChange: () => onToggle(s.id)
799
+ }
800
+ ),
801
+ s.label
802
+ ] }),
803
+ /* @__PURE__ */ jsxs(Badge, { children: [
804
+ s.toolCount,
805
+ " tools"
806
+ ] })
807
+ ] }, s.id))
808
+ ] })
809
+ ] })
810
+ ] });
811
+ }
615
812
  const SCORE_LABELS = {
616
813
  1: "Negligible",
617
814
  2: "Minor",
@@ -1349,9 +1546,11 @@ function Chat() {
1349
1546
  removeConversation
1350
1547
  } = useConversations();
1351
1548
  const { memories, removeMemory, refresh: refreshMemories } = useMemories();
1549
+ const { sources, enabledSources, enabledToolSources, toggleSource } = useToolSources();
1352
1550
  const { messages, sendMessage, isLoading, error } = useChat({
1353
1551
  initialMessages,
1354
- conversationId: activeId
1552
+ conversationId: activeId,
1553
+ enabledToolSources
1355
1554
  });
1356
1555
  useEffect(() => {
1357
1556
  if (prevIsLoadingRef.current && !isLoading && messages.length > 0) {
@@ -1425,6 +1624,14 @@ function Chat() {
1425
1624
  ] })
1426
1625
  }
1427
1626
  ),
1627
+ /* @__PURE__ */ jsx(
1628
+ ToolSourcePicker,
1629
+ {
1630
+ sources,
1631
+ enabledSources,
1632
+ onToggle: toggleSource
1633
+ }
1634
+ ),
1428
1635
  /* @__PURE__ */ jsx("div", { style: { flex: 1 } }),
1429
1636
  /* @__PURE__ */ jsx(
1430
1637
  ToggleSidebarBtn,
@@ -36,7 +36,7 @@ const index = {
36
36
  defaultMessage: PLUGIN_ID
37
37
  },
38
38
  Component: async () => {
39
- const { App } = await import("./App-v0CobEGM.mjs");
39
+ const { App } = await import("./App-DCV7o6Hc.mjs");
40
40
  return App;
41
41
  }
42
42
  });
@@ -37,7 +37,7 @@ const index = {
37
37
  defaultMessage: PLUGIN_ID
38
38
  },
39
39
  Component: async () => {
40
- const { App } = await Promise.resolve().then(() => require("./App-BGIUzHMh.js"));
40
+ const { App } = await Promise.resolve().then(() => require("./App-CEEsJsKL.js"));
41
41
  return App;
42
42
  }
43
43
  });
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-BNk29VRc.js");
2
+ const index = require("../_chunks/index-Cw2aiQ8K.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-CFO5UshL.mjs";
1
+ import { i } from "../_chunks/index-BMrDQVQl.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -0,0 +1,8 @@
1
+ import type { ToolSource } from '../hooks/useToolSources';
2
+ interface Props {
3
+ sources: ToolSource[];
4
+ enabledSources: Set<string>;
5
+ onToggle: (id: string) => void;
6
+ }
7
+ export declare function ToolSourcePicker({ sources, enabledSources, onToggle }: Props): import("react/jsx-runtime").JSX.Element | null;
8
+ export {};
@@ -15,6 +15,7 @@ export interface UseChatOptions {
15
15
  initialMessages?: Message[];
16
16
  conversationId?: string | null;
17
17
  onStreamStart?: () => void;
18
+ enabledToolSources?: string[];
18
19
  }
19
20
  export declare function useChat(options?: UseChatOptions): {
20
21
  messages: Message[];
@@ -0,0 +1,13 @@
1
+ export interface ToolSource {
2
+ id: string;
3
+ label: string;
4
+ toolCount: number;
5
+ tools: string[];
6
+ }
7
+ export declare function useToolSources(): {
8
+ sources: ToolSource[];
9
+ enabledSources: Set<string>;
10
+ enabledToolSources: string[] | undefined;
11
+ toggleSource: (id: string) => void;
12
+ loaded: boolean;
13
+ };
@@ -204,6 +204,29 @@ class ToolRegistry {
204
204
  }
205
205
  return result;
206
206
  }
207
+ /** Returns metadata about tool sources grouped by plugin prefix */
208
+ getToolSources() {
209
+ const groups = /* @__PURE__ */ new Map();
210
+ for (const name of this.tools.keys()) {
211
+ const sepIndex = name.indexOf("__");
212
+ if (sepIndex === -1) {
213
+ const list = groups.get("built-in") ?? [];
214
+ list.push(name);
215
+ groups.set("built-in", list);
216
+ } else {
217
+ const prefix = name.substring(0, sepIndex);
218
+ const list = groups.get(prefix) ?? [];
219
+ list.push(name);
220
+ groups.set(prefix, list);
221
+ }
222
+ }
223
+ return Array.from(groups.entries()).map(([id, tools]) => ({
224
+ id,
225
+ label: id === "built-in" ? "Built-in Tools" : id,
226
+ toolCount: tools.length,
227
+ tools
228
+ }));
229
+ }
207
230
  /** Only tools marked safe for unauthenticated public chat */
208
231
  getPublicSafe() {
209
232
  const result = /* @__PURE__ */ new Map();
@@ -1582,7 +1605,7 @@ function validateBody(ctx) {
1582
1605
  return { prompt, system };
1583
1606
  }
1584
1607
  function validateChatBody(ctx) {
1585
- const { messages, system } = ctx.request.body;
1608
+ const { messages, system, enabledToolSources } = ctx.request.body;
1586
1609
  if (!messages || !Array.isArray(messages) || messages.length === 0) {
1587
1610
  ctx.badRequest("messages is required and must be a non-empty array");
1588
1611
  return null;
@@ -1591,7 +1614,11 @@ function validateChatBody(ctx) {
1591
1614
  ctx.badRequest("system must be a string if provided");
1592
1615
  return null;
1593
1616
  }
1594
- return { messages, system };
1617
+ if (enabledToolSources !== void 0 && (!Array.isArray(enabledToolSources) || !enabledToolSources.every((s) => typeof s === "string"))) {
1618
+ ctx.badRequest("enabledToolSources must be an array of strings if provided");
1619
+ return null;
1620
+ }
1621
+ return { messages, system, enabledToolSources };
1595
1622
  }
1596
1623
  function createSSEStream(ctx) {
1597
1624
  ctx.set({
@@ -1658,7 +1685,11 @@ const controller = ({ strapi }) => ({
1658
1685
  const service2 = getService(strapi, ctx);
1659
1686
  if (!service2) return;
1660
1687
  const adminUserId = ctx.state?.user?.id;
1661
- const result = await service2.chat(body.messages, { system: body.system, adminUserId });
1688
+ const result = await service2.chat(body.messages, {
1689
+ system: body.system,
1690
+ adminUserId,
1691
+ enabledToolSources: body.enabledToolSources
1692
+ });
1662
1693
  const response = result.toUIMessageStreamResponse();
1663
1694
  ctx.status = 200;
1664
1695
  ctx.set("Content-Type", "text/event-stream; charset=utf-8");
@@ -1686,6 +1717,15 @@ const controller = ({ strapi }) => ({
1686
1717
  ctx.set("x-vercel-ai-ui-message-stream", "v1");
1687
1718
  ctx.body = node_stream.Readable.fromWeb(response.body);
1688
1719
  },
1720
+ async getToolSources(ctx) {
1721
+ const plugin = strapi.plugin("ai-sdk");
1722
+ const registry = plugin.toolRegistry;
1723
+ if (!registry) {
1724
+ ctx.badRequest("Tool registry not initialized");
1725
+ return;
1726
+ }
1727
+ ctx.body = { data: registry.getToolSources() };
1728
+ },
1689
1729
  async serveWidget(ctx) {
1690
1730
  const pluginRoot = path__namespace.resolve(__dirname, "..", "..");
1691
1731
  const widgetPath = path__namespace.join(pluginRoot, "dist", "widget", "widget.js");
@@ -2456,6 +2496,12 @@ const contentAPIRoutes = {
2456
2496
  const adminAPIRoutes = {
2457
2497
  type: "admin",
2458
2498
  routes: [
2499
+ {
2500
+ method: "GET",
2501
+ path: "/tool-sources",
2502
+ handler: "controller.getToolSources",
2503
+ config: { policies: [] }
2504
+ },
2459
2505
  {
2460
2506
  method: "POST",
2461
2507
  path: "/chat",
@@ -2579,8 +2625,16 @@ function createTools(strapi, context) {
2579
2625
  if (!registry) {
2580
2626
  throw new Error("Tool registry not initialized");
2581
2627
  }
2628
+ const enabledSources = context?.enabledToolSources;
2582
2629
  const tools = {};
2583
2630
  for (const [name, def] of registry.getAll()) {
2631
+ if (enabledSources) {
2632
+ const sepIndex = name.indexOf("__");
2633
+ if (sepIndex !== -1) {
2634
+ const prefix = name.substring(0, sepIndex);
2635
+ if (!enabledSources.includes(prefix)) continue;
2636
+ }
2637
+ }
2584
2638
  tools[name] = ai.tool({
2585
2639
  description: def.description,
2586
2640
  inputSchema: ai.zodSchema(def.schema),
@@ -2704,7 +2758,7 @@ const service = ({ strapi }) => {
2704
2758
  const maxSteps = config2?.maxSteps ?? DEFAULT_MAX_STEPS;
2705
2759
  const trimmedMessages = messages.length > maxMessages ? messages.slice(-maxMessages) : messages;
2706
2760
  const modelMessages = await ai.convertToModelMessages(trimmedMessages);
2707
- const tools = createTools(strapi, { adminUserId: options2?.adminUserId });
2761
+ const tools = createTools(strapi, { adminUserId: options2?.adminUserId, enabledToolSources: options2?.enabledToolSources });
2708
2762
  const toolsDescription = describeTools(tools);
2709
2763
  let system = composeSystemPrompt(config2, toolsDescription, options2?.system);
2710
2764
  if (options2?.adminUserId) {
@@ -184,6 +184,29 @@ class ToolRegistry {
184
184
  }
185
185
  return result;
186
186
  }
187
+ /** Returns metadata about tool sources grouped by plugin prefix */
188
+ getToolSources() {
189
+ const groups = /* @__PURE__ */ new Map();
190
+ for (const name of this.tools.keys()) {
191
+ const sepIndex = name.indexOf("__");
192
+ if (sepIndex === -1) {
193
+ const list = groups.get("built-in") ?? [];
194
+ list.push(name);
195
+ groups.set("built-in", list);
196
+ } else {
197
+ const prefix = name.substring(0, sepIndex);
198
+ const list = groups.get(prefix) ?? [];
199
+ list.push(name);
200
+ groups.set(prefix, list);
201
+ }
202
+ }
203
+ return Array.from(groups.entries()).map(([id, tools]) => ({
204
+ id,
205
+ label: id === "built-in" ? "Built-in Tools" : id,
206
+ toolCount: tools.length,
207
+ tools
208
+ }));
209
+ }
187
210
  /** Only tools marked safe for unauthenticated public chat */
188
211
  getPublicSafe() {
189
212
  const result = /* @__PURE__ */ new Map();
@@ -1562,7 +1585,7 @@ function validateBody(ctx) {
1562
1585
  return { prompt, system };
1563
1586
  }
1564
1587
  function validateChatBody(ctx) {
1565
- const { messages, system } = ctx.request.body;
1588
+ const { messages, system, enabledToolSources } = ctx.request.body;
1566
1589
  if (!messages || !Array.isArray(messages) || messages.length === 0) {
1567
1590
  ctx.badRequest("messages is required and must be a non-empty array");
1568
1591
  return null;
@@ -1571,7 +1594,11 @@ function validateChatBody(ctx) {
1571
1594
  ctx.badRequest("system must be a string if provided");
1572
1595
  return null;
1573
1596
  }
1574
- return { messages, system };
1597
+ if (enabledToolSources !== void 0 && (!Array.isArray(enabledToolSources) || !enabledToolSources.every((s) => typeof s === "string"))) {
1598
+ ctx.badRequest("enabledToolSources must be an array of strings if provided");
1599
+ return null;
1600
+ }
1601
+ return { messages, system, enabledToolSources };
1575
1602
  }
1576
1603
  function createSSEStream(ctx) {
1577
1604
  ctx.set({
@@ -1638,7 +1665,11 @@ const controller = ({ strapi }) => ({
1638
1665
  const service2 = getService(strapi, ctx);
1639
1666
  if (!service2) return;
1640
1667
  const adminUserId = ctx.state?.user?.id;
1641
- const result = await service2.chat(body.messages, { system: body.system, adminUserId });
1668
+ const result = await service2.chat(body.messages, {
1669
+ system: body.system,
1670
+ adminUserId,
1671
+ enabledToolSources: body.enabledToolSources
1672
+ });
1642
1673
  const response = result.toUIMessageStreamResponse();
1643
1674
  ctx.status = 200;
1644
1675
  ctx.set("Content-Type", "text/event-stream; charset=utf-8");
@@ -1666,6 +1697,15 @@ const controller = ({ strapi }) => ({
1666
1697
  ctx.set("x-vercel-ai-ui-message-stream", "v1");
1667
1698
  ctx.body = Readable.fromWeb(response.body);
1668
1699
  },
1700
+ async getToolSources(ctx) {
1701
+ const plugin = strapi.plugin("ai-sdk");
1702
+ const registry = plugin.toolRegistry;
1703
+ if (!registry) {
1704
+ ctx.badRequest("Tool registry not initialized");
1705
+ return;
1706
+ }
1707
+ ctx.body = { data: registry.getToolSources() };
1708
+ },
1669
1709
  async serveWidget(ctx) {
1670
1710
  const pluginRoot = path.resolve(__dirname, "..", "..");
1671
1711
  const widgetPath = path.join(pluginRoot, "dist", "widget", "widget.js");
@@ -2436,6 +2476,12 @@ const contentAPIRoutes = {
2436
2476
  const adminAPIRoutes = {
2437
2477
  type: "admin",
2438
2478
  routes: [
2479
+ {
2480
+ method: "GET",
2481
+ path: "/tool-sources",
2482
+ handler: "controller.getToolSources",
2483
+ config: { policies: [] }
2484
+ },
2439
2485
  {
2440
2486
  method: "POST",
2441
2487
  path: "/chat",
@@ -2559,8 +2605,16 @@ function createTools(strapi, context) {
2559
2605
  if (!registry) {
2560
2606
  throw new Error("Tool registry not initialized");
2561
2607
  }
2608
+ const enabledSources = context?.enabledToolSources;
2562
2609
  const tools = {};
2563
2610
  for (const [name, def] of registry.getAll()) {
2611
+ if (enabledSources) {
2612
+ const sepIndex = name.indexOf("__");
2613
+ if (sepIndex !== -1) {
2614
+ const prefix = name.substring(0, sepIndex);
2615
+ if (!enabledSources.includes(prefix)) continue;
2616
+ }
2617
+ }
2564
2618
  tools[name] = tool({
2565
2619
  description: def.description,
2566
2620
  inputSchema: zodSchema(def.schema),
@@ -2684,7 +2738,7 @@ const service = ({ strapi }) => {
2684
2738
  const maxSteps = config2?.maxSteps ?? DEFAULT_MAX_STEPS;
2685
2739
  const trimmedMessages = messages.length > maxMessages ? messages.slice(-maxMessages) : messages;
2686
2740
  const modelMessages = await convertToModelMessages(trimmedMessages);
2687
- const tools = createTools(strapi, { adminUserId: options2?.adminUserId });
2741
+ const tools = createTools(strapi, { adminUserId: options2?.adminUserId, enabledToolSources: options2?.enabledToolSources });
2688
2742
  const toolsDescription = describeTools(tools);
2689
2743
  let system = composeSystemPrompt(config2, toolsDescription, options2?.system);
2690
2744
  if (options2?.adminUserId) {
@@ -14,6 +14,7 @@ declare const controller: ({ strapi }: {
14
14
  * Public chat endpoint - restricted tools, public memories, no admin auth
15
15
  */
16
16
  publicChat(ctx: Context): Promise<void>;
17
+ getToolSources(ctx: Context): Promise<void>;
17
18
  serveWidget(ctx: Context): Promise<void>;
18
19
  };
19
20
  export default controller;
@@ -7,6 +7,7 @@ declare const _default: {
7
7
  askStream(ctx: import("koa").Context): Promise<void>;
8
8
  chat(ctx: import("koa").Context): Promise<void>;
9
9
  publicChat(ctx: import("koa").Context): Promise<void>;
10
+ getToolSources(ctx: import("koa").Context): Promise<void>;
10
11
  serveWidget(ctx: import("koa").Context): Promise<void>;
11
12
  };
12
13
  mcp: ({ strapi }: {
@@ -45,6 +45,7 @@ declare const _default: {
45
45
  askStream(ctx: import("koa").Context): Promise<void>;
46
46
  chat(ctx: import("koa").Context): Promise<void>;
47
47
  publicChat(ctx: import("koa").Context): Promise<void>;
48
+ getToolSources(ctx: import("koa").Context): Promise<void>;
48
49
  serveWidget(ctx: import("koa").Context): Promise<void>;
49
50
  };
50
51
  mcp: ({ strapi }: {
@@ -118,7 +119,7 @@ declare const _default: {
118
119
  handler: string;
119
120
  config: {
120
121
  policies: any[];
121
- middlewares: string[];
122
+ middlewares?: undefined;
122
123
  };
123
124
  } | {
124
125
  method: string;
@@ -126,7 +127,7 @@ declare const _default: {
126
127
  handler: string;
127
128
  config: {
128
129
  policies: any[];
129
- middlewares?: undefined;
130
+ middlewares: string[];
130
131
  };
131
132
  })[];
132
133
  };
@@ -144,6 +145,7 @@ declare const _default: {
144
145
  chat(messages: import("ai").UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>[], options?: {
145
146
  system?: string;
146
147
  adminUserId?: number;
148
+ enabledToolSources?: string[];
147
149
  }): Promise<import("./lib/ai-provider").StreamTextRawResult>;
148
150
  publicChat(messages: import("ai").UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>[], options?: {
149
151
  system?: string;
@@ -2,6 +2,7 @@ import type { Core } from '@strapi/strapi';
2
2
  import type { z } from 'zod';
3
3
  export interface ToolContext {
4
4
  adminUserId?: number;
5
+ enabledToolSources?: string[];
5
6
  }
6
7
  export interface ToolDefinition {
7
8
  name: string;
@@ -25,6 +26,13 @@ export declare class ToolRegistry {
25
26
  getAll(): Map<string, ToolDefinition>;
26
27
  /** Only tools that should be exposed via MCP (non-internal) */
27
28
  getPublic(): Map<string, ToolDefinition>;
29
+ /** Returns metadata about tool sources grouped by plugin prefix */
30
+ getToolSources(): Array<{
31
+ id: string;
32
+ label: string;
33
+ toolCount: number;
34
+ tools: string[];
35
+ }>;
28
36
  /** Only tools marked safe for unauthenticated public chat */
29
37
  getPublicSafe(): Map<string, ToolDefinition>;
30
38
  }
@@ -20,6 +20,7 @@ export declare function validateBody(ctx: Context): {
20
20
  export declare function validateChatBody(ctx: Context): {
21
21
  messages: UIMessage[];
22
22
  system?: string;
23
+ enabledToolSources?: string[];
23
24
  } | null;
24
25
  /**
25
26
  * Setup SSE stream with proper headers
@@ -6,7 +6,7 @@ declare const _default: {
6
6
  handler: string;
7
7
  config: {
8
8
  policies: any[];
9
- middlewares: string[];
9
+ middlewares?: undefined;
10
10
  };
11
11
  } | {
12
12
  method: string;
@@ -14,7 +14,7 @@ declare const _default: {
14
14
  handler: string;
15
15
  config: {
16
16
  policies: any[];
17
- middlewares?: undefined;
17
+ middlewares: string[];
18
18
  };
19
19
  })[];
20
20
  };
@@ -27,7 +27,7 @@ declare const routes: {
27
27
  handler: string;
28
28
  config: {
29
29
  policies: any[];
30
- middlewares: string[];
30
+ middlewares?: undefined;
31
31
  };
32
32
  } | {
33
33
  method: string;
@@ -35,7 +35,7 @@ declare const routes: {
35
35
  handler: string;
36
36
  config: {
37
37
  policies: any[];
38
- middlewares?: undefined;
38
+ middlewares: string[];
39
39
  };
40
40
  })[];
41
41
  };
@@ -11,6 +11,7 @@ declare const _default: {
11
11
  chat(messages: import("ai").UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>[], options?: {
12
12
  system?: string;
13
13
  adminUserId?: number;
14
+ enabledToolSources?: string[];
14
15
  }): Promise<import("../lib/ai-provider").StreamTextRawResult>;
15
16
  publicChat(messages: import("ai").UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>[], options?: {
16
17
  system?: string;
@@ -17,6 +17,7 @@ declare const service: ({ strapi }: {
17
17
  chat(messages: UIMessage[], options?: {
18
18
  system?: string;
19
19
  adminUserId?: number;
20
+ enabledToolSources?: string[];
20
21
  }): Promise<StreamTextRawResult>;
21
22
  /**
22
23
  * Public chat - restricted tools, public memories, no admin auth
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.7.0",
2
+ "version": "0.7.3",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "strapi-plugin",