@assistant-ui/react-devtools 1.2.1 → 1.2.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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { FrameHost } from "./FrameHost.js";
3
3
  import { DEFAULT_FRAME_URL } from "./constants.js";
4
- import { useCallback, useEffect, useMemo, useRef } from "react";
4
+ import { useCallback, useEffect, useMemo, useRef } from "@assistant-ui/tap/react-shim";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  //#region src/DevToolsFrame.tsx
7
7
  const DevToolsFrame = ({ frameUrl = DEFAULT_FRAME_URL, className, style, title = "assistant-ui DevTools" }) => {
@@ -18,6 +18,7 @@ interface HostToFrameMessage {
18
18
  state: any;
19
19
  events: any[];
20
20
  modelContext?: any;
21
+ scopes?: any;
21
22
  }>;
22
23
  };
23
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DevToolsHost.d.ts","names":[],"sources":["../src/DevToolsHost.ts"],"mappings":";UAMU,kBAAA;EACR,IAAA;EACA,IAAA;IACE,OAAA;IACA,IAAA;IACA,KAAA;EAAA;AAAA;AAAA,UAIM,kBAAA;EACR,IAAA;EACA,IAAA;IACE,OAAA,GAAU,KAAA;MAAQ,KAAA;IAAA;IAClB,IAAA,GAAO,KAAK;MACV,KAAA;MACA,KAAA;MACA,MAAA;MACA,YAAA;IAAA;EAAA;AAAA;AAAA,cAKO,YAAA;EAAA,QACH,YAAA;EAAA,QAOA,WAAA;EAAA,QACA,aAAA;cAEI,aAAA,GAAgB,OAAA,EAAS,kBAAA;EAKrC,gBAAA,CAAiB,OAAA,EAAS,kBAAkB;EAAA,QAcpC,kBAAA;EAAA,QAiBA,mBAAA;EAAA,QAMA,UAAA;EA+DR,OAAA,CAAA;AAAA"}
1
+ {"version":3,"file":"DevToolsHost.d.ts","names":[],"sources":["../src/DevToolsHost.ts"],"mappings":";UAOU,kBAAA;EACR,IAAA;EACA,IAAA;IACE,OAAA;IACA,IAAA;IACA,KAAA;EAAA;AAAA;AAAA,UAIM,kBAAA;EACR,IAAA;EACA,IAAA;IACE,OAAA,GAAU,KAAA;MAAQ,KAAA;IAAA;IAClB,IAAA,GAAO,KAAK;MACV,KAAA;MACA,KAAA;MACA,MAAA;MACA,YAAA;MACA,MAAA;IAAA;EAAA;AAAA;AAAA,cAKO,YAAA;EAAA,QACH,YAAA;EAAA,QAOA,WAAA;EAAA,QACA,aAAA;cAEI,aAAA,GAAgB,OAAA,EAAS,kBAAA;EAKrC,gBAAA,CAAiB,OAAA,EAAS,kBAAkB;EAAA,QAcpC,kBAAA;EAAA,QAiBA,mBAAA;EAAA,QAMA,UAAA;EAyFR,OAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { sanitizeForMessage, serializeModelContext } from "./utils/serialization.js";
1
+ import { sanitizeAndRedact, sanitizeForMessage, serializeModelContext } from "./utils/serialization.js";
2
2
  import { DevToolsHooks } from "@assistant-ui/react";
3
3
  //#region src/DevToolsHost.ts
4
4
  var DevToolsHost = class {
@@ -52,12 +52,21 @@ var DevToolsHost = class {
52
52
  const apiEntry = allApis.get(apiId);
53
53
  if (apiEntry) {
54
54
  const state = {};
55
+ const scopes = [];
55
56
  if (apiEntry.api) {
56
57
  for (const [name, scope] of Object.entries(apiEntry.api)) if (typeof scope === "function" && "source" in scope) {
57
- if (scope.source === "root") {
58
+ let methods = [];
59
+ try {
58
60
  const scopeValue = scope();
59
- state[name] = scopeValue?.getState?.() ?? scopeValue;
60
- }
61
+ if (scopeValue && typeof scopeValue === "object") methods = Object.keys(scopeValue).filter((key) => typeof scopeValue[key] === "function");
62
+ if (scope.source === "root") state[name] = scopeValue?.getState?.() ?? scopeValue;
63
+ } catch {}
64
+ scopes.push({
65
+ name,
66
+ source: scope.source,
67
+ query: scope.query,
68
+ methods
69
+ });
61
70
  }
62
71
  }
63
72
  const modelContext = serializeModelContext(apiEntry.api?.thread?.().getModelContext());
@@ -65,7 +74,8 @@ var DevToolsHost = class {
65
74
  apiId,
66
75
  state: sanitizeForMessage(state),
67
76
  events: sanitizeForMessage(apiEntry.logs),
68
- modelContext
77
+ modelContext,
78
+ scopes: sanitizeAndRedact(scopes)
69
79
  });
70
80
  }
71
81
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DevToolsHost.js","names":[],"sources":["../src/DevToolsHost.ts"],"sourcesContent":["import { DevToolsHooks } from \"@assistant-ui/react\";\nimport {\n sanitizeForMessage,\n serializeModelContext,\n} from \"./utils/serialization\";\n\ninterface FrameToHostMessage {\n type: \"subscription\" | \"clearEvents\";\n data: {\n apiList?: boolean;\n apis?: number[];\n apiId?: number;\n };\n}\n\ninterface HostToFrameMessage {\n type: \"update\";\n data: {\n apiList?: Array<{ apiId: number }>;\n apis?: Array<{\n apiId: number;\n state: any;\n events: any[];\n modelContext?: any;\n }>;\n };\n}\n\nexport class DevToolsHost {\n private subscription: {\n apiList: boolean;\n apis: Set<number>;\n } = {\n apiList: false,\n apis: new Set(),\n };\n private unsubscribe?: () => void;\n private onSendMessage: (message: HostToFrameMessage) => void;\n\n constructor(onSendMessage: (message: HostToFrameMessage) => void) {\n this.onSendMessage = onSendMessage;\n this.subscribeToDevTools();\n }\n\n onReceiveMessage(message: FrameToHostMessage) {\n switch (message.type) {\n case \"subscription\":\n this.handleSubscription(message.data);\n break;\n case \"clearEvents\":\n if (typeof message.data.apiId === \"number\") {\n DevToolsHooks.clearEventLogs(message.data.apiId);\n // The subscription will automatically trigger an update\n }\n break;\n }\n }\n\n private handleSubscription(data: FrameToHostMessage[\"data\"]) {\n const prevApiList = this.subscription.apiList;\n const prevApis = new Set(this.subscription.apis);\n\n this.subscription.apiList = data.apiList || false;\n this.subscription.apis = new Set(data.apis);\n\n // Only send update if subscription actually changed\n const apisChanged =\n prevApis.size !== this.subscription.apis.size ||\n [...this.subscription.apis].some((id) => !prevApis.has(id));\n\n if (prevApiList !== this.subscription.apiList || apisChanged) {\n this.sendUpdate();\n }\n }\n\n private subscribeToDevTools() {\n this.unsubscribe = DevToolsHooks.subscribe(() => {\n this.sendUpdate();\n });\n }\n\n private sendUpdate() {\n const update: HostToFrameMessage = {\n type: \"update\",\n data: {},\n };\n\n const allApis = DevToolsHooks.getApis();\n for (const subscriptionApiId of this.subscription.apis) {\n if (!allApis.has(subscriptionApiId)) {\n this.subscription.apis.delete(subscriptionApiId);\n }\n }\n\n if (this.subscription.apiList) {\n update.data.apiList = [...allApis.keys()].map((apiId) => ({ apiId }));\n\n if (this.subscription.apis.size === 0 && allApis.size > 0) {\n this.subscription.apis = new Set([allApis.keys().next().value!]);\n }\n }\n\n if (this.subscription.apis.size > 0) {\n update.data.apis = [];\n\n for (const apiId of this.subscription.apis) {\n const apiEntry = allApis.get(apiId);\n if (apiEntry) {\n // Collect state from api scopes (only root source)\n const state: Record<string, unknown> = {};\n if (apiEntry.api) {\n for (const [name, scope] of Object.entries(apiEntry.api)) {\n if (typeof scope === \"function\" && \"source\" in scope) {\n // Only forward scopes with source === \"root\"\n if (scope.source === \"root\") {\n const scopeValue = scope();\n state[name] = scopeValue?.getState?.() ?? scopeValue;\n }\n }\n }\n }\n\n // Extract model context from thread runtime\n const modelContext = serializeModelContext(\n apiEntry.api?.thread?.().getModelContext(),\n );\n\n update.data.apis.push({\n apiId,\n state: sanitizeForMessage(state),\n events: sanitizeForMessage(apiEntry.logs) as unknown[],\n modelContext: modelContext,\n });\n }\n }\n }\n\n if (Object.keys(update.data).length === 0) {\n return;\n }\n\n this.onSendMessage(update);\n }\n\n destroy() {\n if (this.unsubscribe) {\n this.unsubscribe();\n }\n }\n}\n"],"mappings":";;;AA4BA,IAAa,eAAb,MAA0B;CACxB,eAGI;EACF,SAAS;EACT,sBAAM,IAAI,IAAI;CAChB;CACA;CACA;CAEA,YAAY,eAAsD;EAChE,KAAK,gBAAgB;EACrB,KAAK,oBAAoB;CAC3B;CAEA,iBAAiB,SAA6B;EAC5C,QAAQ,QAAQ,MAAhB;GACE,KAAK;IACH,KAAK,mBAAmB,QAAQ,IAAI;IACpC;GACF,KAAK;IACH,IAAI,OAAO,QAAQ,KAAK,UAAU,UAChC,cAAc,eAAe,QAAQ,KAAK,KAAK;IAGjD;EACJ;CACF;CAEA,mBAA2B,MAAkC;EAC3D,MAAM,cAAc,KAAK,aAAa;EACtC,MAAM,WAAW,IAAI,IAAI,KAAK,aAAa,IAAI;EAE/C,KAAK,aAAa,UAAU,KAAK,WAAW;EAC5C,KAAK,aAAa,OAAO,IAAI,IAAI,KAAK,IAAI;EAG1C,MAAM,cACJ,SAAS,SAAS,KAAK,aAAa,KAAK,QACzC,CAAC,GAAG,KAAK,aAAa,IAAI,EAAE,MAAM,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;EAE5D,IAAI,gBAAgB,KAAK,aAAa,WAAW,aAC/C,KAAK,WAAW;CAEpB;CAEA,sBAA8B;EAC5B,KAAK,cAAc,cAAc,gBAAgB;GAC/C,KAAK,WAAW;EAClB,CAAC;CACH;CAEA,aAAqB;EACnB,MAAM,SAA6B;GACjC,MAAM;GACN,MAAM,CAAC;EACT;EAEA,MAAM,UAAU,cAAc,QAAQ;EACtC,KAAK,MAAM,qBAAqB,KAAK,aAAa,MAChD,IAAI,CAAC,QAAQ,IAAI,iBAAiB,GAChC,KAAK,aAAa,KAAK,OAAO,iBAAiB;EAInD,IAAI,KAAK,aAAa,SAAS;GAC7B,OAAO,KAAK,UAAU,CAAC,GAAG,QAAQ,KAAK,CAAC,EAAE,KAAK,WAAW,EAAE,MAAM,EAAE;GAEpE,IAAI,KAAK,aAAa,KAAK,SAAS,KAAK,QAAQ,OAAO,GACtD,KAAK,aAAa,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,EAAE,KAAK,EAAE,KAAM,CAAC;EAEnE;EAEA,IAAI,KAAK,aAAa,KAAK,OAAO,GAAG;GACnC,OAAO,KAAK,OAAO,CAAC;GAEpB,KAAK,MAAM,SAAS,KAAK,aAAa,MAAM;IAC1C,MAAM,WAAW,QAAQ,IAAI,KAAK;IAClC,IAAI,UAAU;KAEZ,MAAM,QAAiC,CAAC;KACxC,IAAI,SAAS;WACN,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,SAAS,GAAG,GACrD,IAAI,OAAO,UAAU,cAAc,YAAY;WAEzC,MAAM,WAAW,QAAQ;QAC3B,MAAM,aAAa,MAAM;QACzB,MAAM,QAAQ,YAAY,WAAW,KAAK;OAC5C;;;KAMN,MAAM,eAAe,sBACnB,SAAS,KAAK,SAAS,EAAE,gBAAgB,CAC3C;KAEA,OAAO,KAAK,KAAK,KAAK;MACpB;MACA,OAAO,mBAAmB,KAAK;MAC/B,QAAQ,mBAAmB,SAAS,IAAI;MAC1B;KAChB,CAAC;IACH;GACF;EACF;EAEA,IAAI,OAAO,KAAK,OAAO,IAAI,EAAE,WAAW,GACtC;EAGF,KAAK,cAAc,MAAM;CAC3B;CAEA,UAAU;EACR,IAAI,KAAK,aACP,KAAK,YAAY;CAErB;AACF"}
1
+ {"version":3,"file":"DevToolsHost.js","names":[],"sources":["../src/DevToolsHost.ts"],"sourcesContent":["import { DevToolsHooks } from \"@assistant-ui/react\";\nimport {\n sanitizeAndRedact,\n sanitizeForMessage,\n serializeModelContext,\n} from \"./utils/serialization\";\n\ninterface FrameToHostMessage {\n type: \"subscription\" | \"clearEvents\";\n data: {\n apiList?: boolean;\n apis?: number[];\n apiId?: number;\n };\n}\n\ninterface HostToFrameMessage {\n type: \"update\";\n data: {\n apiList?: Array<{ apiId: number }>;\n apis?: Array<{\n apiId: number;\n state: any;\n events: any[];\n modelContext?: any;\n scopes?: any;\n }>;\n };\n}\n\nexport class DevToolsHost {\n private subscription: {\n apiList: boolean;\n apis: Set<number>;\n } = {\n apiList: false,\n apis: new Set(),\n };\n private unsubscribe?: () => void;\n private onSendMessage: (message: HostToFrameMessage) => void;\n\n constructor(onSendMessage: (message: HostToFrameMessage) => void) {\n this.onSendMessage = onSendMessage;\n this.subscribeToDevTools();\n }\n\n onReceiveMessage(message: FrameToHostMessage) {\n switch (message.type) {\n case \"subscription\":\n this.handleSubscription(message.data);\n break;\n case \"clearEvents\":\n if (typeof message.data.apiId === \"number\") {\n DevToolsHooks.clearEventLogs(message.data.apiId);\n // The subscription will automatically trigger an update\n }\n break;\n }\n }\n\n private handleSubscription(data: FrameToHostMessage[\"data\"]) {\n const prevApiList = this.subscription.apiList;\n const prevApis = new Set(this.subscription.apis);\n\n this.subscription.apiList = data.apiList || false;\n this.subscription.apis = new Set(data.apis);\n\n // Only send update if subscription actually changed\n const apisChanged =\n prevApis.size !== this.subscription.apis.size ||\n [...this.subscription.apis].some((id) => !prevApis.has(id));\n\n if (prevApiList !== this.subscription.apiList || apisChanged) {\n this.sendUpdate();\n }\n }\n\n private subscribeToDevTools() {\n this.unsubscribe = DevToolsHooks.subscribe(() => {\n this.sendUpdate();\n });\n }\n\n private sendUpdate() {\n const update: HostToFrameMessage = {\n type: \"update\",\n data: {},\n };\n\n const allApis = DevToolsHooks.getApis();\n for (const subscriptionApiId of this.subscription.apis) {\n if (!allApis.has(subscriptionApiId)) {\n this.subscription.apis.delete(subscriptionApiId);\n }\n }\n\n if (this.subscription.apiList) {\n update.data.apiList = [...allApis.keys()].map((apiId) => ({ apiId }));\n\n if (this.subscription.apis.size === 0 && allApis.size > 0) {\n this.subscription.apis = new Set([allApis.keys().next().value!]);\n }\n }\n\n if (this.subscription.apis.size > 0) {\n update.data.apis = [];\n\n for (const apiId of this.subscription.apis) {\n const apiEntry = allApis.get(apiId);\n if (apiEntry) {\n // Collect root-scope state plus the identity and method catalog of\n // every accessor (root and derived) for the scope graph.\n const state: Record<string, unknown> = {};\n const scopes: Array<{\n name: string;\n source: string | null;\n query: Record<string, unknown> | null;\n methods: string[];\n }> = [];\n if (apiEntry.api) {\n for (const [name, scope] of Object.entries(apiEntry.api)) {\n if (typeof scope === \"function\" && \"source\" in scope) {\n let methods: string[] = [];\n try {\n const scopeValue = scope();\n if (scopeValue && typeof scopeValue === \"object\") {\n methods = Object.keys(scopeValue).filter(\n (key) =>\n typeof (scopeValue as Record<string, unknown>)[key] ===\n \"function\",\n );\n }\n if (scope.source === \"root\") {\n state[name] = scopeValue?.getState?.() ?? scopeValue;\n }\n } catch {\n // scope() may throw (derived scopes before the client is\n // mounted, or a broken root scope implementation).\n }\n scopes.push({\n name,\n source: scope.source,\n query: scope.query,\n methods,\n });\n }\n }\n }\n\n // Extract model context from thread runtime\n const modelContext = serializeModelContext(\n apiEntry.api?.thread?.().getModelContext(),\n );\n\n update.data.apis.push({\n apiId,\n state: sanitizeForMessage(state),\n events: sanitizeForMessage(apiEntry.logs) as unknown[],\n modelContext: modelContext,\n scopes: sanitizeAndRedact(scopes),\n });\n }\n }\n }\n\n if (Object.keys(update.data).length === 0) {\n return;\n }\n\n this.onSendMessage(update);\n }\n\n destroy() {\n if (this.unsubscribe) {\n this.unsubscribe();\n }\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  //#region src/DevToolsModal.d.ts
2
- declare const DevToolsModal: () => import("react/jsx-runtime").JSX.Element | null;
2
+ declare const DevToolsModal: () => import("react").JSX.Element | null;
3
3
  //#endregion
4
4
  export { DevToolsModal };
5
5
  //# sourceMappingURL=DevToolsModal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DevToolsModal.d.ts","names":[],"sources":["../src/DevToolsModal.tsx"],"mappings":";cAmLa,aAAA,oCAAa,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"DevToolsModal.d.ts","names":[],"sources":["../src/DevToolsModal.tsx"],"mappings":";cAiLa,aAAA,wBAAa,GAAA,CAAA,OAAA"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { DevToolsFrame } from "./DevToolsFrame.js";
3
3
  import { ANIMATION_STYLES, getStyles } from "./styles/DevToolsModal.styles.js";
4
- import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
4
+ import { useEffect, useMemo, useState, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region src/DevToolsModal.tsx
7
7
  const isDarkMode = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"DevToolsModal.js","names":[],"sources":["../src/DevToolsModal.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState, useSyncExternalStore } from \"react\";\nimport { DevToolsFrame } from \"./DevToolsFrame\";\nimport { getStyles, ANIMATION_STYLES } from \"./styles/DevToolsModal.styles\";\n\nconst isDarkMode = (): boolean => {\n if (typeof document === \"undefined\") return false;\n return (\n document.documentElement.classList.contains(\"dark\") ||\n document.body.classList.contains(\"dark\")\n );\n};\n\nconst subscribeToThemeChanges = (callback: () => void) => {\n if (typeof MutationObserver === \"undefined\") {\n return () => {};\n }\n\n const observer = new MutationObserver(callback);\n\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n });\n\n if (document.body !== document.documentElement) {\n observer.observe(document.body, {\n attributes: true,\n attributeFilter: [\"class\"],\n });\n }\n\n return () => observer.disconnect();\n};\n\nconst DevToolsModalImpl = () => {\n const [isOpen, setIsOpen] = useState(false);\n const [buttonHover, setButtonHover] = useState(false);\n const [closeHover, setCloseHover] = useState(false);\n\n const darkMode = useSyncExternalStore(\n subscribeToThemeChanges,\n isDarkMode,\n () => false, // Server-side always returns false\n );\n\n const styles = useMemo(() => getStyles(darkMode), [darkMode]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const styleId = \"devtools-modal-animations\";\n if (!document.getElementById(styleId)) {\n const style = document.createElement(\"style\");\n style.id = styleId;\n style.textContent = ANIMATION_STYLES;\n document.head.appendChild(style);\n }\n\n return () => {\n const style = document.getElementById(styleId);\n if (style && !document.querySelector(\"[data-devtools-modal]\")) {\n style.remove();\n }\n };\n }, []);\n\n useEffect(() => {\n if (!isOpen) return;\n\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n setIsOpen(false);\n }\n };\n\n document.addEventListener(\"keydown\", handleEscape);\n return () => document.removeEventListener(\"keydown\", handleEscape);\n }, [isOpen]);\n\n return (\n <>\n <div style={styles.floatingContainer}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(true)}\n onMouseEnter={() => setButtonHover(true)}\n onMouseLeave={() => setButtonHover(false)}\n style={{\n ...styles.floatingButton,\n ...(buttonHover ? styles.floatingButtonHover : {}),\n }}\n aria-label=\"Open assistant-ui DevTools\"\n title=\"Open assistant-ui DevTools\"\n >\n <svg\n aria-hidden=\"true\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n style={{ width: \"20px\", height: \"20px\" }}\n >\n <path\n d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.8\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </button>\n </div>\n\n {isOpen && (\n <>\n {/* biome-ignore lint/a11y/useKeyWithClickEvents: backdrop click is a convenience; keyboard users use the close button */}\n {/* biome-ignore lint/a11y/noStaticElementInteractions: backdrop overlay */}\n <div style={styles.backdrop} onClick={() => setIsOpen(false)} />\n\n <div style={styles.modal} data-devtools-modal>\n <button\n type=\"button\"\n onClick={() => setIsOpen(false)}\n onMouseEnter={() => setCloseHover(true)}\n onMouseLeave={() => setCloseHover(false)}\n style={{\n ...styles.dismissButton,\n ...(closeHover ? styles.dismissButtonHover : {}),\n }}\n aria-label=\"Close DevTools\"\n >\n <svg\n aria-hidden=\"true\"\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M6 6L18 18\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n\n <div style={styles.modalContent}>\n <DevToolsFrame\n style={{\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n borderRadius: \"12px\",\n backgroundColor: \"transparent\",\n }}\n />\n </div>\n </div>\n </>\n )}\n </>\n );\n};\n\n// Export a component that only renders in development\nexport const DevToolsModal = () => {\n // Check if we're in production - most bundlers will replace process.env.NODE_ENV\n // This allows the entire component to be eliminated via dead code elimination\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"production\"\n ) {\n return null;\n }\n\n return <DevToolsModalImpl />;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"DevToolsModal.js","names":[],"sources":["../src/DevToolsModal.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useState, useSyncExternalStore } from \"react\";\nimport { DevToolsFrame } from \"./DevToolsFrame\";\nimport { getStyles, ANIMATION_STYLES } from \"./styles/DevToolsModal.styles\";\n\nconst isDarkMode = (): boolean => {\n if (typeof document === \"undefined\") return false;\n return (\n document.documentElement.classList.contains(\"dark\") ||\n document.body.classList.contains(\"dark\")\n );\n};\n\nconst subscribeToThemeChanges = (callback: () => void) => {\n if (typeof MutationObserver === \"undefined\") {\n return () => {};\n }\n\n const observer = new MutationObserver(callback);\n\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n });\n\n if (document.body !== document.documentElement) {\n observer.observe(document.body, {\n attributes: true,\n attributeFilter: [\"class\"],\n });\n }\n\n return () => observer.disconnect();\n};\n\nconst DevToolsModalImpl = () => {\n const [isOpen, setIsOpen] = useState(false);\n const [buttonHover, setButtonHover] = useState(false);\n const [closeHover, setCloseHover] = useState(false);\n\n const darkMode = useSyncExternalStore(\n subscribeToThemeChanges,\n isDarkMode,\n () => false, // Server-side always returns false\n );\n\n const styles = useMemo(() => getStyles(darkMode), [darkMode]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const styleId = \"devtools-modal-animations\";\n if (!document.getElementById(styleId)) {\n const style = document.createElement(\"style\");\n style.id = styleId;\n style.textContent = ANIMATION_STYLES;\n document.head.appendChild(style);\n }\n\n return () => {\n const style = document.getElementById(styleId);\n if (style && !document.querySelector(\"[data-devtools-modal]\")) {\n style.remove();\n }\n };\n }, []);\n\n useEffect(() => {\n if (!isOpen) return;\n\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n setIsOpen(false);\n }\n };\n\n document.addEventListener(\"keydown\", handleEscape);\n return () => document.removeEventListener(\"keydown\", handleEscape);\n }, [isOpen]);\n\n return (\n <>\n <div style={styles.floatingContainer}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(true)}\n onMouseEnter={() => setButtonHover(true)}\n onMouseLeave={() => setButtonHover(false)}\n style={{\n ...styles.floatingButton,\n ...(buttonHover ? styles.floatingButtonHover : {}),\n }}\n aria-label=\"Open assistant-ui DevTools\"\n title=\"Open assistant-ui DevTools\"\n >\n <svg\n aria-hidden=\"true\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n style={{ width: \"20px\", height: \"20px\" }}\n >\n <path\n d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"\n stroke=\"currentColor\"\n strokeWidth=\"1.8\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n fill=\"none\"\n />\n </svg>\n </button>\n </div>\n\n {isOpen && (\n <>\n <div style={styles.backdrop} onClick={() => setIsOpen(false)} />\n\n <div style={styles.modal} data-devtools-modal>\n <button\n type=\"button\"\n onClick={() => setIsOpen(false)}\n onMouseEnter={() => setCloseHover(true)}\n onMouseLeave={() => setCloseHover(false)}\n style={{\n ...styles.dismissButton,\n ...(closeHover ? styles.dismissButtonHover : {}),\n }}\n aria-label=\"Close DevTools\"\n >\n <svg\n aria-hidden=\"true\"\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M18 6L6 18\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n <path\n d=\"M6 6L18 18\"\n stroke=\"currentColor\"\n strokeWidth=\"1.6\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n\n <div style={styles.modalContent}>\n <DevToolsFrame\n style={{\n width: \"100%\",\n height: \"100%\",\n border: \"none\",\n borderRadius: \"12px\",\n backgroundColor: \"transparent\",\n }}\n />\n </div>\n </div>\n </>\n )}\n </>\n );\n};\n\n// Export a component that only renders in development\nexport const DevToolsModal = () => {\n // Check if we're in production - most bundlers will replace process.env.NODE_ENV\n // This allows the entire component to be eliminated via dead code elimination\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV === \"production\"\n ) {\n return null;\n }\n\n return <DevToolsModalImpl />;\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExtensionHost.d.ts","names":[],"sources":["../src/ExtensionHost.ts"],"mappings":";cAEa,aAAA;EAAA,QACH,YAAA;EAAA,QACA,eAAA;;EAgDR,OAAA,CAAA;AAAA"}
1
+ {"version":3,"file":"ExtensionHost.d.ts","names":[],"sources":["../src/ExtensionHost.ts"],"mappings":";cAEa,aAAA;EAAA,QACH,YAAA;EAAA,QACA,eAAA;;EAgDR,OAAA;AAAA"}
@@ -3,7 +3,8 @@ interface ApiData {
3
3
  apiId: number;
4
4
  state: any;
5
5
  events: any[];
6
- context?: any;
6
+ modelContext?: any;
7
+ scopes?: any;
7
8
  }
8
9
  type UpdateListener = (data: {
9
10
  apiList?: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"FrameClient.d.ts","names":[],"sources":["../src/FrameClient.ts"],"mappings":";UAAU,OAAA;EACR,KAAA;EACA,KAAA;EACA,MAAA;EACA,OAAA;AAAA;AAAA,KAeG,cAAA,IAAkB,IAAA;EACrB,OAAA,GAAU,KAAA;IAAQ,KAAA;EAAA;EAClB,IAAA,GAAO,OAAO;AAAA;AAAA,cAGH,WAAA;EAAA,QACH,SAAA;EAAA,QACA,mBAAA;EAAA,QACA,UAAA;;UASA,oBAAA;EAeR,eAAA,CAAgB,QAAA;EAOhB,SAAA,CAAU,QAAA,EAAU,cAAA;EAapB,eAAA,CAAgB,OAAA;IAAW,OAAA;IAAmB,IAAA;EAAA;EAU9C,WAAA,CAAY,KAAA;EAAA,QAUJ,eAAA;EAKR,aAAA,CAAA;cApEY,KAAA;MAAQ,KAAA;IAAA;WACX,OAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"FrameClient.d.ts","names":[],"sources":["../src/FrameClient.ts"],"mappings":";UAAU,OAAA;EACR,KAAA;EACA,KAAA;EACA,MAAA;EACA,YAAA;EACA,MAAA;AAAA;AAAA,KAeG,cAAA,IAAkB,IAAA;EACrB,OAAA,GAAU,KAAA;IAAQ,KAAA;EAAA;EAClB,IAAA,GAAO,OAAO;AAAA;AAAA,cAGH,WAAA;EAAA,QACH,SAAA;EAAA,QACA,mBAAA;EAAA,QACA,UAAA;;UASA,oBAAA;EAcR,eAAA,CAAgB,QAAA;EAOhB,SAAA,CAAU,QAAA,EAAU,cAAA;EAapB,eAAA,CAAgB,OAAA;IAAW,OAAA;IAAmB,IAAA;EAAA;EAU9C,WAAA,CAAY,KAAA;EAAA,QAUJ,eAAA;EAIR,aAAA;cAlEY,KAAA;MAAQ,KAAA;IAAA;WACX,OAAA;EAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"FrameClient.js","names":[],"sources":["../src/FrameClient.ts"],"sourcesContent":["interface ApiData {\n apiId: number;\n state: any;\n events: any[];\n context?: any;\n}\n\ninterface UpdateMessage {\n type: \"update\";\n data: {\n apiList?: Array<{ apiId: number }>;\n apis?: ApiData[];\n };\n}\n\ninterface HostConnectedMessage {\n type: \"host-connected\";\n}\n\ntype UpdateListener = (data: {\n apiList?: Array<{ apiId: number }>;\n apis?: ApiData[];\n}) => void;\n\nexport class FrameClient {\n private listeners = new Set<UpdateListener>();\n private connectionListeners = new Set<() => void>();\n private lastUpdate: {\n apiList?: Array<{ apiId: number }>;\n apis?: ApiData[];\n } = {};\n\n constructor() {\n this.setupMessageListener();\n }\n\n private setupMessageListener() {\n window.addEventListener(\"message\", (event) => {\n const message = event.data as UpdateMessage | HostConnectedMessage;\n\n if (message.type === \"update\") {\n this.lastUpdate = message.data;\n this.notifyListeners(message.data);\n } else if (message.type === \"host-connected\") {\n // Host has reconnected (page refresh), notify listeners to re-subscribe\n // biome-ignore lint/suspicious/useIterableCallbackReturn: forEach callback intentionally has no return\n this.connectionListeners.forEach((listener) => listener());\n }\n });\n }\n\n onHostConnected(listener: () => void): () => void {\n this.connectionListeners.add(listener);\n return () => {\n this.connectionListeners.delete(listener);\n };\n }\n\n subscribe(listener: UpdateListener): () => void {\n this.listeners.add(listener);\n\n // Send the last update to the new listener\n if (this.lastUpdate.apiList || this.lastUpdate.apis) {\n listener(this.lastUpdate);\n }\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n setSubscription(options: { apiList?: boolean; apis?: number[] }) {\n window.parent.postMessage(\n {\n type: \"subscription\",\n data: options,\n },\n \"*\",\n );\n }\n\n clearEvents(apiId: number) {\n window.parent.postMessage(\n {\n type: \"clearEvents\",\n data: { apiId },\n },\n \"*\",\n );\n }\n\n private notifyListeners(data: UpdateMessage[\"data\"]) {\n // biome-ignore lint/suspicious/useIterableCallbackReturn: forEach callback intentionally has no return\n this.listeners.forEach((listener) => listener(data));\n }\n\n getLastUpdate() {\n return this.lastUpdate;\n }\n}\n"],"mappings":";AAwBA,IAAa,cAAb,MAAyB;CACvB,4BAAoB,IAAI,IAAoB;CAC5C,sCAA8B,IAAI,IAAgB;CAClD,aAGI,CAAC;CAEL,cAAc;EACZ,KAAK,qBAAqB;CAC5B;CAEA,uBAA+B;EAC7B,OAAO,iBAAiB,YAAY,UAAU;GAC5C,MAAM,UAAU,MAAM;GAEtB,IAAI,QAAQ,SAAS,UAAU;IAC7B,KAAK,aAAa,QAAQ;IAC1B,KAAK,gBAAgB,QAAQ,IAAI;GACnC,OAAO,IAAI,QAAQ,SAAS,kBAG1B,KAAK,oBAAoB,SAAS,aAAa,SAAS,CAAC;EAE7D,CAAC;CACH;CAEA,gBAAgB,UAAkC;EAChD,KAAK,oBAAoB,IAAI,QAAQ;EACrC,aAAa;GACX,KAAK,oBAAoB,OAAO,QAAQ;EAC1C;CACF;CAEA,UAAU,UAAsC;EAC9C,KAAK,UAAU,IAAI,QAAQ;EAG3B,IAAI,KAAK,WAAW,WAAW,KAAK,WAAW,MAC7C,SAAS,KAAK,UAAU;EAG1B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;CAEA,gBAAgB,SAAiD;EAC/D,OAAO,OAAO,YACZ;GACE,MAAM;GACN,MAAM;EACR,GACA,GACF;CACF;CAEA,YAAY,OAAe;EACzB,OAAO,OAAO,YACZ;GACE,MAAM;GACN,MAAM,EAAE,MAAM;EAChB,GACA,GACF;CACF;CAEA,gBAAwB,MAA6B;EAEnD,KAAK,UAAU,SAAS,aAAa,SAAS,IAAI,CAAC;CACrD;CAEA,gBAAgB;EACd,OAAO,KAAK;CACd;AACF"}
1
+ {"version":3,"file":"FrameClient.js","names":[],"sources":["../src/FrameClient.ts"],"sourcesContent":["interface ApiData {\n apiId: number;\n state: any;\n events: any[];\n modelContext?: any;\n scopes?: any;\n}\n\ninterface UpdateMessage {\n type: \"update\";\n data: {\n apiList?: Array<{ apiId: number }>;\n apis?: ApiData[];\n };\n}\n\ninterface HostConnectedMessage {\n type: \"host-connected\";\n}\n\ntype UpdateListener = (data: {\n apiList?: Array<{ apiId: number }>;\n apis?: ApiData[];\n}) => void;\n\nexport class FrameClient {\n private listeners = new Set<UpdateListener>();\n private connectionListeners = new Set<() => void>();\n private lastUpdate: {\n apiList?: Array<{ apiId: number }>;\n apis?: ApiData[];\n } = {};\n\n constructor() {\n this.setupMessageListener();\n }\n\n private setupMessageListener() {\n window.addEventListener(\"message\", (event) => {\n const message = event.data as UpdateMessage | HostConnectedMessage;\n\n if (message.type === \"update\") {\n this.lastUpdate = message.data;\n this.notifyListeners(message.data);\n } else if (message.type === \"host-connected\") {\n // Host has reconnected (page refresh), notify listeners to re-subscribe\n this.connectionListeners.forEach((listener) => listener());\n }\n });\n }\n\n onHostConnected(listener: () => void): () => void {\n this.connectionListeners.add(listener);\n return () => {\n this.connectionListeners.delete(listener);\n };\n }\n\n subscribe(listener: UpdateListener): () => void {\n this.listeners.add(listener);\n\n // Send the last update to the new listener\n if (this.lastUpdate.apiList || this.lastUpdate.apis) {\n listener(this.lastUpdate);\n }\n\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n setSubscription(options: { apiList?: boolean; apis?: number[] }) {\n window.parent.postMessage(\n {\n type: \"subscription\",\n data: options,\n },\n \"*\",\n );\n }\n\n clearEvents(apiId: number) {\n window.parent.postMessage(\n {\n type: \"clearEvents\",\n data: { apiId },\n },\n \"*\",\n );\n }\n\n private notifyListeners(data: UpdateMessage[\"data\"]) {\n this.listeners.forEach((listener) => listener(data));\n }\n\n getLastUpdate() {\n return this.lastUpdate;\n }\n}\n"],"mappings":";AAyBA,IAAa,cAAb,MAAyB;CACvB,4BAAoB,IAAI,IAAoB;CAC5C,sCAA8B,IAAI,IAAgB;CAClD,aAGI,CAAC;CAEL,cAAc;EACZ,KAAK,qBAAqB;CAC5B;CAEA,uBAA+B;EAC7B,OAAO,iBAAiB,YAAY,UAAU;GAC5C,MAAM,UAAU,MAAM;GAEtB,IAAI,QAAQ,SAAS,UAAU;IAC7B,KAAK,aAAa,QAAQ;IAC1B,KAAK,gBAAgB,QAAQ,IAAI;GACnC,OAAO,IAAI,QAAQ,SAAS,kBAE1B,KAAK,oBAAoB,SAAS,aAAa,SAAS,CAAC;EAE7D,CAAC;CACH;CAEA,gBAAgB,UAAkC;EAChD,KAAK,oBAAoB,IAAI,QAAQ;EACrC,aAAa;GACX,KAAK,oBAAoB,OAAO,QAAQ;EAC1C;CACF;CAEA,UAAU,UAAsC;EAC9C,KAAK,UAAU,IAAI,QAAQ;EAG3B,IAAI,KAAK,WAAW,WAAW,KAAK,WAAW,MAC7C,SAAS,KAAK,UAAU;EAG1B,aAAa;GACX,KAAK,UAAU,OAAO,QAAQ;EAChC;CACF;CAEA,gBAAgB,SAAiD;EAC/D,OAAO,OAAO,YACZ;GACE,MAAM;GACN,MAAM;EACR,GACA,GACF;CACF;CAEA,YAAY,OAAe;EACzB,OAAO,OAAO,YACZ;GACE,MAAM;GACN,MAAM,EAAE,MAAM;EAChB,GACA,GACF;CACF;CAEA,gBAAwB,MAA6B;EACnD,KAAK,UAAU,SAAS,aAAa,SAAS,IAAI,CAAC;CACrD;CAEA,gBAAgB;EACd,OAAO,KAAK;CACd;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"FrameHost.d.ts","names":[],"sources":["../src/FrameHost.ts"],"mappings":";cAEa,SAAA;EAAA,QACH,KAAA;EAAA,QACA,YAAA;EAAA,QACA,eAAA;cAEI,KAAA,EAAO,iBAAiB;EAmBpC,OAAA,CAAA;AAAA"}
1
+ {"version":3,"file":"FrameHost.d.ts","names":[],"sources":["../src/FrameHost.ts"],"mappings":";cAEa,SAAA;EAAA,QACH,KAAA;EAAA,QACA,YAAA;EAAA,QACA,eAAA;cAEI,KAAA,EAAO,iBAAiB;EAmBpC,OAAA;AAAA"}
@@ -3,7 +3,17 @@ import { ModelContext } from "@assistant-ui/react";
3
3
 
4
4
  //#region src/utils/serialization.d.ts
5
5
  declare const sanitizeForMessage: (value: unknown, seen?: WeakSet<object>) => unknown;
6
+ declare const REDACTED = "[redacted]";
7
+ /**
8
+ * Mask values whose key names a known credential, and mask every value inside
9
+ * an `env`/`headers` subtree wholesale. Operates on already sanitized plain
10
+ * data (primitives, arrays, plain objects). Applied only to config-bearing
11
+ * subtrees, never to a tool's parameters schema, so a schema property literally
12
+ * named `token` is not corrupted.
13
+ */
14
+ declare const redactSensitive: (value: unknown, maskAll?: boolean) => unknown;
15
+ declare const sanitizeAndRedact: (value: unknown) => unknown;
6
16
  declare const serializeModelContext: (context: ModelContext | undefined) => SerializedModelContext | undefined;
7
17
  //#endregion
8
- export { sanitizeForMessage, serializeModelContext };
18
+ export { REDACTED, redactSensitive, sanitizeAndRedact, sanitizeForMessage, serializeModelContext };
9
19
  //# sourceMappingURL=serialization.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serialization.d.ts","names":[],"sources":["../../src/utils/serialization.ts"],"mappings":";;;;cAIa,kBAAA,GACX,KAAA,WACA,IAAA,GAAA,OAAA;AAAA,cAgDW,qBAAA,GACX,OAAA,EAAS,YAAA,iBACR,sBAAsB"}
1
+ {"version":3,"file":"serialization.d.ts","names":[],"sources":["../../src/utils/serialization.ts"],"mappings":";;;;cAIa,kBAAA,GACX,KAAA,WACA,IAAA,GAAA,OAAA;AAAA,cAgDW,QAAA;AAlDb;;;;;;;AAAA,cA0Fa,eAAA,GAAmB,KAAA,WAAgB,OAAe;AAAA,cAoBlD,iBAAA,GAAqB,KAAc;AAAA,cAGnC,qBAAA,GACX,OAAA,EAAS,YAAA,iBACR,sBAAsB"}
@@ -25,6 +25,55 @@ const sanitizeForMessage = (value, seen = /* @__PURE__ */ new WeakSet()) => {
25
25
  }
26
26
  return value;
27
27
  };
28
+ const REDACTED = "[redacted]";
29
+ const SENSITIVE_KEYS = new Set([
30
+ "apikey",
31
+ "xapikey",
32
+ "accesskey",
33
+ "authorization",
34
+ "password",
35
+ "passwd",
36
+ "secret",
37
+ "clientsecret",
38
+ "token",
39
+ "accesstoken",
40
+ "refreshtoken",
41
+ "cookie",
42
+ "setcookie",
43
+ "credential",
44
+ "credentials",
45
+ "privatekey",
46
+ "bearer",
47
+ "sessionid"
48
+ ]);
49
+ const normalizeKey = (key) => key.toLowerCase().replace(/[-_]/g, "");
50
+ /**
51
+ * Subtrees that are credential maps with arbitrary, user-defined key names
52
+ * (MCP stdio `env`, HTTP `headers`). Per-key name matching cannot catch
53
+ * `OPENAI_API_KEY` or a custom auth header, so every leaf inside one of these
54
+ * is masked wholesale.
55
+ */
56
+ const MASK_ALL_KEYS = new Set(["env", "headers"]);
57
+ /**
58
+ * Mask values whose key names a known credential, and mask every value inside
59
+ * an `env`/`headers` subtree wholesale. Operates on already sanitized plain
60
+ * data (primitives, arrays, plain objects). Applied only to config-bearing
61
+ * subtrees, never to a tool's parameters schema, so a schema property literally
62
+ * named `token` is not corrupted.
63
+ */
64
+ const redactSensitive = (value, maskAll = false) => {
65
+ if (Array.isArray(value)) return value.map((entry) => redactSensitive(entry, maskAll));
66
+ if (value && typeof value === "object") {
67
+ const result = {};
68
+ for (const [key, entry] of Object.entries(value)) {
69
+ const normalized = normalizeKey(key);
70
+ result[key] = maskAll || SENSITIVE_KEYS.has(normalized) ? REDACTED : redactSensitive(entry, MASK_ALL_KEYS.has(normalized));
71
+ }
72
+ return result;
73
+ }
74
+ return maskAll ? REDACTED : value;
75
+ };
76
+ const sanitizeAndRedact = (value) => redactSensitive(sanitizeForMessage(value));
28
77
  const serializeModelContext = (context) => {
29
78
  if (!context || typeof context !== "object") return;
30
79
  const modelContext = context;
@@ -32,21 +81,27 @@ const serializeModelContext = (context) => {
32
81
  const systemValue = modelContext.system;
33
82
  if (typeof systemValue === "string" && systemValue.length > 0) result.system = systemValue;
34
83
  const tools = normalizeToolList(modelContext.tools);
35
- if (tools.length > 0) result.tools = tools.map((tool) => ({
36
- ...tool,
37
- parameters: sanitizeForMessage(tool.parameters)
38
- }));
84
+ if (tools.length > 0) result.tools = tools.map((tool) => {
85
+ return {
86
+ ...tool,
87
+ parameters: sanitizeForMessage(tool.parameters),
88
+ ...tool.providerOptions !== void 0 ? { providerOptions: sanitizeAndRedact(tool.providerOptions) } : {},
89
+ ...tool.providerArgs !== void 0 ? { providerArgs: sanitizeAndRedact(tool.providerArgs) } : {},
90
+ ...tool.server !== void 0 ? { server: sanitizeAndRedact(tool.server) } : {},
91
+ ...tool.backendDefault !== void 0 ? { backendDefault: sanitizeForMessage(tool.backendDefault) } : {}
92
+ };
93
+ });
39
94
  if (modelContext.callSettings !== void 0) {
40
- const callSettings = sanitizeForMessage(modelContext.callSettings);
95
+ const callSettings = sanitizeAndRedact(modelContext.callSettings);
41
96
  if (callSettings && typeof callSettings === "object" && !Array.isArray(callSettings)) result.callSettings = callSettings;
42
97
  }
43
98
  if (modelContext.config !== void 0) {
44
- const config = sanitizeForMessage(modelContext.config);
99
+ const config = sanitizeAndRedact(modelContext.config);
45
100
  if (config && typeof config === "object" && !Array.isArray(config)) result.config = config;
46
101
  }
47
102
  return Object.keys(result).length > 0 ? result : void 0;
48
103
  };
49
104
  //#endregion
50
- export { sanitizeForMessage, serializeModelContext };
105
+ export { REDACTED, redactSensitive, sanitizeAndRedact, sanitizeForMessage, serializeModelContext };
51
106
 
52
107
  //# sourceMappingURL=serialization.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"serialization.js","names":[],"sources":["../../src/utils/serialization.ts"],"sourcesContent":["import type { ModelContext } from \"@assistant-ui/react\";\nimport type { SerializedModelContext } from \"../types\";\nimport { normalizeToolList } from \"./toolNormalization\";\n\nexport const sanitizeForMessage = (\n value: unknown,\n seen = new WeakSet<object>(),\n): unknown => {\n // Early return for primitives\n if (value === null || value === undefined) return value;\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n if (typeof value === \"function\") {\n return \"[Function]\";\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (value instanceof Map) {\n const result: Record<string, unknown> = {};\n for (const [key, entry] of value.entries()) {\n result[String(key)] = sanitizeForMessage(entry, seen);\n }\n return result;\n }\n if (value instanceof Set) {\n return Array.from(value).map((entry) => sanitizeForMessage(entry, seen));\n }\n if (Array.isArray(value)) {\n if (seen.has(value as unknown as object)) return \"[Circular]\";\n seen.add(value as unknown as object);\n return value\n .map((entry) => sanitizeForMessage(entry, seen))\n .filter((item) => item !== undefined);\n }\n if (typeof value === \"object\") {\n if (seen.has(value as object)) return \"[Circular]\";\n seen.add(value as object);\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(\n value as Record<string, unknown>,\n )) {\n result[key] = sanitizeForMessage(entry, seen);\n }\n return result;\n }\n return value;\n};\n\nexport const serializeModelContext = (\n context: ModelContext | undefined,\n): SerializedModelContext | undefined => {\n if (!context || typeof context !== \"object\") {\n return undefined;\n }\n\n const modelContext = context as Record<string, unknown>;\n const result: SerializedModelContext = {};\n\n const systemValue = modelContext.system;\n if (typeof systemValue === \"string\" && systemValue.length > 0) {\n result.system = systemValue;\n }\n\n const tools = normalizeToolList(modelContext.tools);\n if (tools.length > 0) {\n result.tools = tools.map((tool) => ({\n ...tool,\n parameters: sanitizeForMessage(tool.parameters),\n }));\n }\n\n if (modelContext.callSettings !== undefined) {\n const callSettings = sanitizeForMessage(modelContext.callSettings);\n if (\n callSettings &&\n typeof callSettings === \"object\" &&\n !Array.isArray(callSettings)\n ) {\n result.callSettings = callSettings as Record<string, unknown>;\n }\n }\n\n if (modelContext.config !== undefined) {\n const config = sanitizeForMessage(modelContext.config);\n if (config && typeof config === \"object\" && !Array.isArray(config)) {\n result.config = config as Record<string, unknown>;\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n};\n"],"mappings":";;AAIA,MAAa,sBACX,OACA,uBAAO,IAAI,QAAgB,MACf;CAEZ,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WAEjB,OAAO;CAET,IAAI,OAAO,UAAU,YACnB,OAAO;CAET,IAAI,iBAAiB,MACnB,OAAO,MAAM,YAAY;CAE3B,IAAI,iBAAiB,KAAK;EACxB,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,MAAM,QAAQ,GACvC,OAAO,OAAO,GAAG,KAAK,mBAAmB,OAAO,IAAI;EAEtD,OAAO;CACT;CACA,IAAI,iBAAiB,KACnB,OAAO,MAAM,KAAK,KAAK,EAAE,KAAK,UAAU,mBAAmB,OAAO,IAAI,CAAC;CAEzE,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,IAAI,KAAK,IAAI,KAA0B,GAAG,OAAO;EACjD,KAAK,IAAI,KAA0B;EACnC,OAAO,MACJ,KAAK,UAAU,mBAAmB,OAAO,IAAI,CAAC,EAC9C,QAAQ,SAAS,SAAS,KAAA,CAAS;CACxC;CACA,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,KAAK,IAAI,KAAe,GAAG,OAAO;EACtC,KAAK,IAAI,KAAe;EACxB,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAChC,KACF,GACE,OAAO,OAAO,mBAAmB,OAAO,IAAI;EAE9C,OAAO;CACT;CACA,OAAO;AACT;AAEA,MAAa,yBACX,YACuC;CACvC,IAAI,CAAC,WAAW,OAAO,YAAY,UACjC;CAGF,MAAM,eAAe;CACrB,MAAM,SAAiC,CAAC;CAExC,MAAM,cAAc,aAAa;CACjC,IAAI,OAAO,gBAAgB,YAAY,YAAY,SAAS,GAC1D,OAAO,SAAS;CAGlB,MAAM,QAAQ,kBAAkB,aAAa,KAAK;CAClD,IAAI,MAAM,SAAS,GACjB,OAAO,QAAQ,MAAM,KAAK,UAAU;EAClC,GAAG;EACH,YAAY,mBAAmB,KAAK,UAAU;CAChD,EAAE;CAGJ,IAAI,aAAa,iBAAiB,KAAA,GAAW;EAC3C,MAAM,eAAe,mBAAmB,aAAa,YAAY;EACjE,IACE,gBACA,OAAO,iBAAiB,YACxB,CAAC,MAAM,QAAQ,YAAY,GAE3B,OAAO,eAAe;CAE1B;CAEA,IAAI,aAAa,WAAW,KAAA,GAAW;EACrC,MAAM,SAAS,mBAAmB,aAAa,MAAM;EACrD,IAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC/D,OAAO,SAAS;CAEpB;CAEA,OAAO,OAAO,KAAK,MAAM,EAAE,SAAS,IAAI,SAAS,KAAA;AACnD"}
1
+ {"version":3,"file":"serialization.js","names":[],"sources":["../../src/utils/serialization.ts"],"sourcesContent":["import type { ModelContext } from \"@assistant-ui/react\";\nimport type { SerializedModelContext } from \"../types\";\nimport { normalizeToolList, type NormalizedTool } from \"./toolNormalization\";\n\nexport const sanitizeForMessage = (\n value: unknown,\n seen = new WeakSet<object>(),\n): unknown => {\n // Early return for primitives\n if (value === null || value === undefined) return value;\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n if (typeof value === \"function\") {\n return \"[Function]\";\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (value instanceof Map) {\n const result: Record<string, unknown> = {};\n for (const [key, entry] of value.entries()) {\n result[String(key)] = sanitizeForMessage(entry, seen);\n }\n return result;\n }\n if (value instanceof Set) {\n return Array.from(value).map((entry) => sanitizeForMessage(entry, seen));\n }\n if (Array.isArray(value)) {\n if (seen.has(value as unknown as object)) return \"[Circular]\";\n seen.add(value as unknown as object);\n return value\n .map((entry) => sanitizeForMessage(entry, seen))\n .filter((item) => item !== undefined);\n }\n if (typeof value === \"object\") {\n if (seen.has(value as object)) return \"[Circular]\";\n seen.add(value as object);\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(\n value as Record<string, unknown>,\n )) {\n result[key] = sanitizeForMessage(entry, seen);\n }\n return result;\n }\n return value;\n};\n\nexport const REDACTED = \"[redacted]\";\n\nconst SENSITIVE_KEYS = new Set([\n \"apikey\",\n \"xapikey\",\n \"accesskey\",\n \"authorization\",\n \"password\",\n \"passwd\",\n \"secret\",\n \"clientsecret\",\n \"token\",\n \"accesstoken\",\n \"refreshtoken\",\n \"cookie\",\n \"setcookie\",\n \"credential\",\n \"credentials\",\n \"privatekey\",\n \"bearer\",\n \"sessionid\",\n]);\n\nconst normalizeKey = (key: string) => key.toLowerCase().replace(/[-_]/g, \"\");\n\n/**\n * Subtrees that are credential maps with arbitrary, user-defined key names\n * (MCP stdio `env`, HTTP `headers`). Per-key name matching cannot catch\n * `OPENAI_API_KEY` or a custom auth header, so every leaf inside one of these\n * is masked wholesale.\n */\nconst MASK_ALL_KEYS = new Set([\"env\", \"headers\"]);\n\n/**\n * Mask values whose key names a known credential, and mask every value inside\n * an `env`/`headers` subtree wholesale. Operates on already sanitized plain\n * data (primitives, arrays, plain objects). Applied only to config-bearing\n * subtrees, never to a tool's parameters schema, so a schema property literally\n * named `token` is not corrupted.\n */\nexport const redactSensitive = (value: unknown, maskAll = false): unknown => {\n if (Array.isArray(value)) {\n return value.map((entry) => redactSensitive(entry, maskAll));\n }\n if (value && typeof value === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(\n value as Record<string, unknown>,\n )) {\n const normalized = normalizeKey(key);\n result[key] =\n maskAll || SENSITIVE_KEYS.has(normalized)\n ? REDACTED\n : redactSensitive(entry, MASK_ALL_KEYS.has(normalized));\n }\n return result;\n }\n return maskAll ? REDACTED : value;\n};\n\nexport const sanitizeAndRedact = (value: unknown): unknown =>\n redactSensitive(sanitizeForMessage(value));\n\nexport const serializeModelContext = (\n context: ModelContext | undefined,\n): SerializedModelContext | undefined => {\n if (!context || typeof context !== \"object\") {\n return undefined;\n }\n\n const modelContext = context as Record<string, unknown>;\n const result: SerializedModelContext = {};\n\n const systemValue = modelContext.system;\n if (typeof systemValue === \"string\" && systemValue.length > 0) {\n result.system = systemValue;\n }\n\n const tools = normalizeToolList(modelContext.tools);\n if (tools.length > 0) {\n result.tools = tools.map((tool): NormalizedTool => {\n return {\n ...tool,\n parameters: sanitizeForMessage(tool.parameters),\n ...(tool.providerOptions !== undefined\n ? { providerOptions: sanitizeAndRedact(tool.providerOptions) }\n : {}),\n ...(tool.providerArgs !== undefined\n ? { providerArgs: sanitizeAndRedact(tool.providerArgs) }\n : {}),\n ...(tool.server !== undefined\n ? { server: sanitizeAndRedact(tool.server) }\n : {}),\n ...(tool.backendDefault !== undefined\n ? { backendDefault: sanitizeForMessage(tool.backendDefault) }\n : {}),\n };\n });\n }\n\n if (modelContext.callSettings !== undefined) {\n const callSettings = sanitizeAndRedact(modelContext.callSettings);\n if (\n callSettings &&\n typeof callSettings === \"object\" &&\n !Array.isArray(callSettings)\n ) {\n result.callSettings = callSettings as Record<string, unknown>;\n }\n }\n\n if (modelContext.config !== undefined) {\n const config = sanitizeAndRedact(modelContext.config);\n if (config && typeof config === \"object\" && !Array.isArray(config)) {\n result.config = config as Record<string, unknown>;\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n};\n"],"mappings":";;AAIA,MAAa,sBACX,OACA,uBAAO,IAAI,QAAgB,MACf;CAEZ,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,WAEjB,OAAO;CAET,IAAI,OAAO,UAAU,YACnB,OAAO;CAET,IAAI,iBAAiB,MACnB,OAAO,MAAM,YAAY;CAE3B,IAAI,iBAAiB,KAAK;EACxB,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,MAAM,QAAQ,GACvC,OAAO,OAAO,GAAG,KAAK,mBAAmB,OAAO,IAAI;EAEtD,OAAO;CACT;CACA,IAAI,iBAAiB,KACnB,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,KAAK,UAAU,mBAAmB,OAAO,IAAI,CAAC;CAEzE,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,IAAI,KAAK,IAAI,KAA0B,GAAG,OAAO;EACjD,KAAK,IAAI,KAA0B;EACnC,OAAO,MACJ,KAAK,UAAU,mBAAmB,OAAO,IAAI,CAAC,CAAC,CAC/C,QAAQ,SAAS,SAAS,KAAA,CAAS;CACxC;CACA,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,KAAK,IAAI,KAAe,GAAG,OAAO;EACtC,KAAK,IAAI,KAAe;EACxB,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAChC,KACF,GACE,OAAO,OAAO,mBAAmB,OAAO,IAAI;EAE9C,OAAO;CACT;CACA,OAAO;AACT;AAEA,MAAa,WAAW;AAExB,MAAM,iBAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,gBAAgB,QAAgB,IAAI,YAAY,CAAC,CAAC,QAAQ,SAAS,EAAE;;;;;;;AAQ3E,MAAM,gBAAgB,IAAI,IAAI,CAAC,OAAO,SAAS,CAAC;;;;;;;;AAShD,MAAa,mBAAmB,OAAgB,UAAU,UAAmB;CAC3E,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,UAAU,gBAAgB,OAAO,OAAO,CAAC;CAE7D,IAAI,SAAS,OAAO,UAAU,UAAU;EACtC,MAAM,SAAkC,CAAC;EACzC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAChC,KACF,GAAG;GACD,MAAM,aAAa,aAAa,GAAG;GACnC,OAAO,OACL,WAAW,eAAe,IAAI,UAAU,IACpC,WACA,gBAAgB,OAAO,cAAc,IAAI,UAAU,CAAC;EAC5D;EACA,OAAO;CACT;CACA,OAAO,UAAU,WAAW;AAC9B;AAEA,MAAa,qBAAqB,UAChC,gBAAgB,mBAAmB,KAAK,CAAC;AAE3C,MAAa,yBACX,YACuC;CACvC,IAAI,CAAC,WAAW,OAAO,YAAY,UACjC;CAGF,MAAM,eAAe;CACrB,MAAM,SAAiC,CAAC;CAExC,MAAM,cAAc,aAAa;CACjC,IAAI,OAAO,gBAAgB,YAAY,YAAY,SAAS,GAC1D,OAAO,SAAS;CAGlB,MAAM,QAAQ,kBAAkB,aAAa,KAAK;CAClD,IAAI,MAAM,SAAS,GACjB,OAAO,QAAQ,MAAM,KAAK,SAAyB;EACjD,OAAO;GACL,GAAG;GACH,YAAY,mBAAmB,KAAK,UAAU;GAC9C,GAAI,KAAK,oBAAoB,KAAA,IACzB,EAAE,iBAAiB,kBAAkB,KAAK,eAAe,EAAE,IAC3D,CAAC;GACL,GAAI,KAAK,iBAAiB,KAAA,IACtB,EAAE,cAAc,kBAAkB,KAAK,YAAY,EAAE,IACrD,CAAC;GACL,GAAI,KAAK,WAAW,KAAA,IAChB,EAAE,QAAQ,kBAAkB,KAAK,MAAM,EAAE,IACzC,CAAC;GACL,GAAI,KAAK,mBAAmB,KAAA,IACxB,EAAE,gBAAgB,mBAAmB,KAAK,cAAc,EAAE,IAC1D,CAAC;EACP;CACF,CAAC;CAGH,IAAI,aAAa,iBAAiB,KAAA,GAAW;EAC3C,MAAM,eAAe,kBAAkB,aAAa,YAAY;EAChE,IACE,gBACA,OAAO,iBAAiB,YACxB,CAAC,MAAM,QAAQ,YAAY,GAE3B,OAAO,eAAe;CAE1B;CAEA,IAAI,aAAa,WAAW,KAAA,GAAW;EACrC,MAAM,SAAS,kBAAkB,aAAa,MAAM;EACpD,IAAI,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,GAC/D,OAAO,SAAS;CAEpB;CAEA,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,SAAS,KAAA;AACnD"}
@@ -4,6 +4,13 @@ type NormalizedTool = {
4
4
  type?: string;
5
5
  description?: string;
6
6
  disabled?: boolean;
7
+ display?: string;
8
+ providerId?: string;
9
+ supportsDeferredResults?: boolean;
10
+ backendDefault?: unknown;
11
+ providerOptions?: unknown;
12
+ providerArgs?: unknown;
13
+ server?: unknown;
7
14
  parameters?: unknown;
8
15
  };
9
16
  declare const normalizeToolList: (value: unknown) => NormalizedTool[];
@@ -1 +1 @@
1
- {"version":3,"file":"toolNormalization.d.ts","names":[],"sources":["../../src/utils/toolNormalization.ts"],"mappings":";KAEY,cAAA;EACV,IAAA;EACA,IAAA;EACA,WAAA;EACA,QAAA;EACA,UAAA;AAAA;AAAA,cA2CW,iBAAA,GAAqB,KAAA,cAAiB,cAAc"}
1
+ {"version":3,"file":"toolNormalization.d.ts","names":[],"sources":["../../src/utils/toolNormalization.ts"],"mappings":";KAEY,cAAA;EACV,IAAA;EACA,IAAA;EACA,WAAA;EACA,QAAA;EACA,OAAA;EACA,UAAA;EACA,uBAAA;EACA,cAAA;EACA,eAAA;EACA,YAAA;EACA,MAAA;EACA,UAAA;AAAA;AAAA,cAuEW,iBAAA,GAAqB,KAAA,cAAiB,cAAc"}
@@ -14,6 +14,13 @@ const mapToNormalizedTool = (name, raw) => {
14
14
  if (typeof raw.type === "string") tool.type = raw.type;
15
15
  if (typeof raw.description === "string") tool.description = raw.description;
16
16
  if (typeof raw.disabled === "boolean") tool.disabled = raw.disabled;
17
+ if (typeof raw.display === "string") tool.display = raw.display;
18
+ if (typeof raw.providerId === "string") tool.providerId = raw.providerId;
19
+ if (typeof raw.supportsDeferredResults === "boolean") tool.supportsDeferredResults = raw.supportsDeferredResults;
20
+ if (raw.unstable_backendDefault !== void 0) tool.backendDefault = raw.unstable_backendDefault;
21
+ if (raw.providerOptions !== void 0) tool.providerOptions = raw.providerOptions;
22
+ if (raw.args !== void 0) tool.providerArgs = raw.args;
23
+ if (raw.server !== void 0) tool.server = raw.server;
17
24
  if (Object.hasOwn(raw, "parameters")) tool.parameters = toJsonSchema(raw.parameters);
18
25
  return tool;
19
26
  };
@@ -1 +1 @@
1
- {"version":3,"file":"toolNormalization.js","names":[],"sources":["../../src/utils/toolNormalization.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport type NormalizedTool = {\n name: string;\n type?: string;\n description?: string;\n disabled?: boolean;\n parameters?: unknown;\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\";\n\nconst toJsonSchema = (value: unknown): unknown => {\n if (value instanceof z.ZodType) {\n try {\n return z.toJSONSchema(value);\n } catch {\n return value;\n }\n }\n\n return value;\n};\n\nconst mapToNormalizedTool = (\n name: string,\n raw: Record<string, unknown>,\n): NormalizedTool => {\n const tool: NormalizedTool = { name };\n\n if (typeof raw.type === \"string\") {\n tool.type = raw.type as string;\n }\n\n if (typeof raw.description === \"string\") {\n tool.description = raw.description as string;\n }\n\n if (typeof raw.disabled === \"boolean\") {\n tool.disabled = raw.disabled as boolean;\n }\n\n if (Object.hasOwn(raw, \"parameters\")) {\n tool.parameters = toJsonSchema(raw.parameters);\n }\n\n return tool;\n};\n\nexport const normalizeToolList = (value: unknown): NormalizedTool[] => {\n if (!value || typeof value !== \"object\") {\n return [];\n }\n\n if (Array.isArray(value)) {\n const tools: NormalizedTool[] = [];\n\n for (const entry of value) {\n if (!isRecord(entry) || typeof entry.name !== \"string\") continue;\n tools.push(mapToNormalizedTool(entry.name as string, entry));\n }\n\n return tools;\n }\n\n if (isRecord(value)) {\n const tools: NormalizedTool[] = [];\n\n for (const [name, entry] of Object.entries(value)) {\n if (!isRecord(entry)) {\n tools.push({ name });\n continue;\n }\n\n tools.push(mapToNormalizedTool(name, entry));\n }\n\n return tools;\n }\n\n return [];\n};\n"],"mappings":";;AAUA,MAAM,YAAY,UAChB,UAAU,QAAQ,OAAO,UAAU;AAErC,MAAM,gBAAgB,UAA4B;CAChD,IAAI,iBAAiB,EAAE,SACrB,IAAI;EACF,OAAO,EAAE,aAAa,KAAK;CAC7B,QAAQ;EACN,OAAO;CACT;CAGF,OAAO;AACT;AAEA,MAAM,uBACJ,MACA,QACmB;CACnB,MAAM,OAAuB,EAAE,KAAK;CAEpC,IAAI,OAAO,IAAI,SAAS,UACtB,KAAK,OAAO,IAAI;CAGlB,IAAI,OAAO,IAAI,gBAAgB,UAC7B,KAAK,cAAc,IAAI;CAGzB,IAAI,OAAO,IAAI,aAAa,WAC1B,KAAK,WAAW,IAAI;CAGtB,IAAI,OAAO,OAAO,KAAK,YAAY,GACjC,KAAK,aAAa,aAAa,IAAI,UAAU;CAG/C,OAAO;AACT;AAEA,MAAa,qBAAqB,UAAqC;CACrE,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO,CAAC;CAGV,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,MAAM,QAA0B,CAAC;EAEjC,KAAK,MAAM,SAAS,OAAO;GACzB,IAAI,CAAC,SAAS,KAAK,KAAK,OAAO,MAAM,SAAS,UAAU;GACxD,MAAM,KAAK,oBAAoB,MAAM,MAAgB,KAAK,CAAC;EAC7D;EAEA,OAAO;CACT;CAEA,IAAI,SAAS,KAAK,GAAG;EACnB,MAAM,QAA0B,CAAC;EAEjC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAAG;GACjD,IAAI,CAAC,SAAS,KAAK,GAAG;IACpB,MAAM,KAAK,EAAE,KAAK,CAAC;IACnB;GACF;GAEA,MAAM,KAAK,oBAAoB,MAAM,KAAK,CAAC;EAC7C;EAEA,OAAO;CACT;CAEA,OAAO,CAAC;AACV"}
1
+ {"version":3,"file":"toolNormalization.js","names":[],"sources":["../../src/utils/toolNormalization.ts"],"sourcesContent":["import { z } from \"zod\";\n\nexport type NormalizedTool = {\n name: string;\n type?: string;\n description?: string;\n disabled?: boolean;\n display?: string;\n providerId?: string;\n supportsDeferredResults?: boolean;\n backendDefault?: unknown;\n providerOptions?: unknown;\n providerArgs?: unknown;\n server?: unknown;\n parameters?: unknown;\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\";\n\nconst toJsonSchema = (value: unknown): unknown => {\n if (value instanceof z.ZodType) {\n try {\n return z.toJSONSchema(value);\n } catch {\n return value;\n }\n }\n\n return value;\n};\n\nconst mapToNormalizedTool = (\n name: string,\n raw: Record<string, unknown>,\n): NormalizedTool => {\n const tool: NormalizedTool = { name };\n\n if (typeof raw.type === \"string\") {\n tool.type = raw.type as string;\n }\n\n if (typeof raw.description === \"string\") {\n tool.description = raw.description as string;\n }\n\n if (typeof raw.disabled === \"boolean\") {\n tool.disabled = raw.disabled as boolean;\n }\n\n if (typeof raw.display === \"string\") {\n tool.display = raw.display as string;\n }\n\n if (typeof raw.providerId === \"string\") {\n tool.providerId = raw.providerId as string;\n }\n\n if (typeof raw.supportsDeferredResults === \"boolean\") {\n tool.supportsDeferredResults = raw.supportsDeferredResults as boolean;\n }\n\n if (raw.unstable_backendDefault !== undefined) {\n tool.backendDefault = raw.unstable_backendDefault;\n }\n\n if (raw.providerOptions !== undefined) {\n tool.providerOptions = raw.providerOptions;\n }\n\n if (raw.args !== undefined) {\n tool.providerArgs = raw.args;\n }\n\n if (raw.server !== undefined) {\n tool.server = raw.server;\n }\n\n if (Object.hasOwn(raw, \"parameters\")) {\n tool.parameters = toJsonSchema(raw.parameters);\n }\n\n return tool;\n};\n\nexport const normalizeToolList = (value: unknown): NormalizedTool[] => {\n if (!value || typeof value !== \"object\") {\n return [];\n }\n\n if (Array.isArray(value)) {\n const tools: NormalizedTool[] = [];\n\n for (const entry of value) {\n if (!isRecord(entry) || typeof entry.name !== \"string\") continue;\n tools.push(mapToNormalizedTool(entry.name as string, entry));\n }\n\n return tools;\n }\n\n if (isRecord(value)) {\n const tools: NormalizedTool[] = [];\n\n for (const [name, entry] of Object.entries(value)) {\n if (!isRecord(entry)) {\n tools.push({ name });\n continue;\n }\n\n tools.push(mapToNormalizedTool(name, entry));\n }\n\n return tools;\n }\n\n return [];\n};\n"],"mappings":";;AAiBA,MAAM,YAAY,UAChB,UAAU,QAAQ,OAAO,UAAU;AAErC,MAAM,gBAAgB,UAA4B;CAChD,IAAI,iBAAiB,EAAE,SACrB,IAAI;EACF,OAAO,EAAE,aAAa,KAAK;CAC7B,QAAQ;EACN,OAAO;CACT;CAGF,OAAO;AACT;AAEA,MAAM,uBACJ,MACA,QACmB;CACnB,MAAM,OAAuB,EAAE,KAAK;CAEpC,IAAI,OAAO,IAAI,SAAS,UACtB,KAAK,OAAO,IAAI;CAGlB,IAAI,OAAO,IAAI,gBAAgB,UAC7B,KAAK,cAAc,IAAI;CAGzB,IAAI,OAAO,IAAI,aAAa,WAC1B,KAAK,WAAW,IAAI;CAGtB,IAAI,OAAO,IAAI,YAAY,UACzB,KAAK,UAAU,IAAI;CAGrB,IAAI,OAAO,IAAI,eAAe,UAC5B,KAAK,aAAa,IAAI;CAGxB,IAAI,OAAO,IAAI,4BAA4B,WACzC,KAAK,0BAA0B,IAAI;CAGrC,IAAI,IAAI,4BAA4B,KAAA,GAClC,KAAK,iBAAiB,IAAI;CAG5B,IAAI,IAAI,oBAAoB,KAAA,GAC1B,KAAK,kBAAkB,IAAI;CAG7B,IAAI,IAAI,SAAS,KAAA,GACf,KAAK,eAAe,IAAI;CAG1B,IAAI,IAAI,WAAW,KAAA,GACjB,KAAK,SAAS,IAAI;CAGpB,IAAI,OAAO,OAAO,KAAK,YAAY,GACjC,KAAK,aAAa,aAAa,IAAI,UAAU;CAG/C,OAAO;AACT;AAEA,MAAa,qBAAqB,UAAqC;CACrE,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO,CAAC;CAGV,IAAI,MAAM,QAAQ,KAAK,GAAG;EACxB,MAAM,QAA0B,CAAC;EAEjC,KAAK,MAAM,SAAS,OAAO;GACzB,IAAI,CAAC,SAAS,KAAK,KAAK,OAAO,MAAM,SAAS,UAAU;GACxD,MAAM,KAAK,oBAAoB,MAAM,MAAgB,KAAK,CAAC;EAC7D;EAEA,OAAO;CACT;CAEA,IAAI,SAAS,KAAK,GAAG;EACnB,MAAM,QAA0B,CAAC;EAEjC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAAG;GACjD,IAAI,CAAC,SAAS,KAAK,GAAG;IACpB,MAAM,KAAK,EAAE,KAAK,CAAC;IACnB;GACF;GAEA,MAAM,KAAK,oBAAoB,MAAM,KAAK,CAAC;EAC7C;EAEA,OAAO;CACT;CAEA,OAAO,CAAC;AACV"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react-devtools",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "React development tools for assistant-ui components",
5
5
  "keywords": [
6
6
  "assistant-ui",
@@ -30,8 +30,8 @@
30
30
  "zod": "^4.4.3"
31
31
  },
32
32
  "peerDependencies": {
33
- "@assistant-ui/react": "^0.14.8",
34
- "@assistant-ui/tap": "^0.5.12",
33
+ "@assistant-ui/react": "^0.14.14",
34
+ "@assistant-ui/tap": "^0.6.0",
35
35
  "@types/react": "*",
36
36
  "@types/react-dom": "*",
37
37
  "react": "^18 || ^19",
@@ -46,14 +46,15 @@
46
46
  }
47
47
  },
48
48
  "devDependencies": {
49
- "@types/node": "^25.9.1",
50
- "@types/react": "^19.2.15",
49
+ "@types/node": "^25.9.2",
50
+ "@types/react": "^19.2.17",
51
51
  "@types/react-dom": "^19.2.3",
52
- "react": "^19.2.6",
53
- "react-dom": "^19.2.6",
54
- "@assistant-ui/react": "0.14.8",
55
- "@assistant-ui/tap": "0.5.12",
56
- "@assistant-ui/x-buildutils": "0.0.9"
52
+ "react": "^19.2.7",
53
+ "react-dom": "^19.2.7",
54
+ "vitest": "^4.1.8",
55
+ "@assistant-ui/react": "0.14.15",
56
+ "@assistant-ui/tap": "0.6.0",
57
+ "@assistant-ui/x-buildutils": "0.0.12"
57
58
  },
58
59
  "publishConfig": {
59
60
  "access": "public",
@@ -69,6 +70,7 @@
69
70
  "url": "https://github.com/assistant-ui/assistant-ui/issues"
70
71
  },
71
72
  "scripts": {
72
- "build": "aui-build"
73
+ "build": "aui-build",
74
+ "test": "vitest run"
73
75
  }
74
76
  }
@@ -1,5 +1,6 @@
1
1
  import { DevToolsHooks } from "@assistant-ui/react";
2
2
  import {
3
+ sanitizeAndRedact,
3
4
  sanitizeForMessage,
4
5
  serializeModelContext,
5
6
  } from "./utils/serialization";
@@ -22,6 +23,7 @@ interface HostToFrameMessage {
22
23
  state: any;
23
24
  events: any[];
24
25
  modelContext?: any;
26
+ scopes?: any;
25
27
  }>;
26
28
  };
27
29
  }
@@ -106,16 +108,41 @@ export class DevToolsHost {
106
108
  for (const apiId of this.subscription.apis) {
107
109
  const apiEntry = allApis.get(apiId);
108
110
  if (apiEntry) {
109
- // Collect state from api scopes (only root source)
111
+ // Collect root-scope state plus the identity and method catalog of
112
+ // every accessor (root and derived) for the scope graph.
110
113
  const state: Record<string, unknown> = {};
114
+ const scopes: Array<{
115
+ name: string;
116
+ source: string | null;
117
+ query: Record<string, unknown> | null;
118
+ methods: string[];
119
+ }> = [];
111
120
  if (apiEntry.api) {
112
121
  for (const [name, scope] of Object.entries(apiEntry.api)) {
113
122
  if (typeof scope === "function" && "source" in scope) {
114
- // Only forward scopes with source === "root"
115
- if (scope.source === "root") {
123
+ let methods: string[] = [];
124
+ try {
116
125
  const scopeValue = scope();
117
- state[name] = scopeValue?.getState?.() ?? scopeValue;
126
+ if (scopeValue && typeof scopeValue === "object") {
127
+ methods = Object.keys(scopeValue).filter(
128
+ (key) =>
129
+ typeof (scopeValue as Record<string, unknown>)[key] ===
130
+ "function",
131
+ );
132
+ }
133
+ if (scope.source === "root") {
134
+ state[name] = scopeValue?.getState?.() ?? scopeValue;
135
+ }
136
+ } catch {
137
+ // scope() may throw (derived scopes before the client is
138
+ // mounted, or a broken root scope implementation).
118
139
  }
140
+ scopes.push({
141
+ name,
142
+ source: scope.source,
143
+ query: scope.query,
144
+ methods,
145
+ });
119
146
  }
120
147
  }
121
148
  }
@@ -130,6 +157,7 @@ export class DevToolsHost {
130
157
  state: sanitizeForMessage(state),
131
158
  events: sanitizeForMessage(apiEntry.logs) as unknown[],
132
159
  modelContext: modelContext,
160
+ scopes: sanitizeAndRedact(scopes),
133
161
  });
134
162
  }
135
163
  }
@@ -117,8 +117,6 @@ const DevToolsModalImpl = () => {
117
117
 
118
118
  {isOpen && (
119
119
  <>
120
- {/* biome-ignore lint/a11y/useKeyWithClickEvents: backdrop click is a convenience; keyboard users use the close button */}
121
- {/* biome-ignore lint/a11y/noStaticElementInteractions: backdrop overlay */}
122
120
  <div style={styles.backdrop} onClick={() => setIsOpen(false)} />
123
121
 
124
122
  <div style={styles.modal} data-devtools-modal>
@@ -2,7 +2,8 @@ interface ApiData {
2
2
  apiId: number;
3
3
  state: any;
4
4
  events: any[];
5
- context?: any;
5
+ modelContext?: any;
6
+ scopes?: any;
6
7
  }
7
8
 
8
9
  interface UpdateMessage {
@@ -43,7 +44,6 @@ export class FrameClient {
43
44
  this.notifyListeners(message.data);
44
45
  } else if (message.type === "host-connected") {
45
46
  // Host has reconnected (page refresh), notify listeners to re-subscribe
46
- // biome-ignore lint/suspicious/useIterableCallbackReturn: forEach callback intentionally has no return
47
47
  this.connectionListeners.forEach((listener) => listener());
48
48
  }
49
49
  });
@@ -90,7 +90,6 @@ export class FrameClient {
90
90
  }
91
91
 
92
92
  private notifyListeners(data: UpdateMessage["data"]) {
93
- // biome-ignore lint/suspicious/useIterableCallbackReturn: forEach callback intentionally has no return
94
93
  this.listeners.forEach((listener) => listener(data));
95
94
  }
96
95
 
@@ -0,0 +1,128 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ REDACTED,
4
+ redactSensitive,
5
+ serializeModelContext,
6
+ } from "./serialization";
7
+
8
+ describe("redactSensitive", () => {
9
+ it("masks credential-named keys but leaves lookalikes alone", () => {
10
+ const out = redactSensitive({
11
+ apiKey: "sk-123",
12
+ authorization: "Bearer abc",
13
+ maxTokens: 100,
14
+ tokenCount: 42,
15
+ nested: { client_secret: "shh", model: "gpt" },
16
+ }) as Record<string, unknown>;
17
+
18
+ expect(out.apiKey).toBe(REDACTED);
19
+ expect(out.authorization).toBe(REDACTED);
20
+ expect(out.maxTokens).toBe(100);
21
+ expect(out.tokenCount).toBe(42);
22
+ expect(out.nested).toEqual({ client_secret: REDACTED, model: "gpt" });
23
+ });
24
+
25
+ it("recurses through arrays", () => {
26
+ const out = redactSensitive([{ token: "a" }, { ok: 1 }]) as unknown[];
27
+ expect(out).toEqual([{ token: REDACTED }, { ok: 1 }]);
28
+ });
29
+
30
+ it("masks every value inside env/headers, including arbitrary names", () => {
31
+ const out = redactSensitive({
32
+ env: { OPENAI_API_KEY: "sk-1", GITHUB_TOKEN: "ghp", PATH: "/bin" },
33
+ headers: { "X-Custom-Auth": "abc", "Content-Type": "application/json" },
34
+ url: "https://mcp.example.com",
35
+ }) as Record<string, Record<string, unknown>>;
36
+
37
+ expect(out.env).toEqual({
38
+ OPENAI_API_KEY: REDACTED,
39
+ GITHUB_TOKEN: REDACTED,
40
+ PATH: REDACTED,
41
+ });
42
+ expect(out.headers).toEqual({
43
+ "X-Custom-Auth": REDACTED,
44
+ "Content-Type": REDACTED,
45
+ });
46
+ expect(out.url).toBe("https://mcp.example.com");
47
+ });
48
+ });
49
+
50
+ describe("serializeModelContext", () => {
51
+ it("redacts secrets in config and call settings, keeps the rest", () => {
52
+ const result = serializeModelContext({
53
+ config: { apiKey: "sk-123", baseUrl: "https://api.example.com" },
54
+ callSettings: {
55
+ maxTokens: 256,
56
+ headers: { Authorization: "Bearer xyz" },
57
+ },
58
+ } as never);
59
+
60
+ expect(result?.config).toEqual({
61
+ apiKey: REDACTED,
62
+ baseUrl: "https://api.example.com",
63
+ });
64
+ expect(result?.callSettings).toEqual({
65
+ maxTokens: 256,
66
+ headers: { Authorization: REDACTED },
67
+ });
68
+ });
69
+
70
+ it("redacts tool providerOptions/server but never the parameters schema", () => {
71
+ const result = serializeModelContext({
72
+ tools: {
73
+ search: {
74
+ type: "frontend",
75
+ parameters: {
76
+ type: "object",
77
+ properties: { token: { type: "string" } },
78
+ },
79
+ providerOptions: { openai: { apiKey: "sk-xyz" } },
80
+ },
81
+ mcp_tool: {
82
+ type: "mcp",
83
+ server: { type: "http", url: "https://x", headers: { token: "t" } },
84
+ },
85
+ },
86
+ } as never);
87
+
88
+ const search = result?.tools?.find((t) => t.name === "search");
89
+ const mcpTool = result?.tools?.find((t) => t.name === "mcp_tool");
90
+
91
+ expect(search?.providerOptions).toEqual({ openai: { apiKey: REDACTED } });
92
+ expect(search?.parameters).toEqual({
93
+ type: "object",
94
+ properties: { token: { type: "string" } },
95
+ });
96
+ expect(mcpTool?.server).toEqual({
97
+ type: "http",
98
+ url: "https://x",
99
+ headers: { token: REDACTED },
100
+ });
101
+ });
102
+
103
+ it("masks arbitrary env names on an MCP stdio server tool", () => {
104
+ const result = serializeModelContext({
105
+ tools: {
106
+ gh: {
107
+ type: "mcp",
108
+ server: {
109
+ type: "stdio",
110
+ command: "mcp-github",
111
+ env: { GITHUB_TOKEN: "ghp_secret", OPENAI_API_KEY: "sk" },
112
+ },
113
+ },
114
+ },
115
+ } as never);
116
+
117
+ const gh = result?.tools?.find((t) => t.name === "gh");
118
+ expect(gh?.server).toEqual({
119
+ type: "stdio",
120
+ command: "mcp-github",
121
+ env: { GITHUB_TOKEN: REDACTED, OPENAI_API_KEY: REDACTED },
122
+ });
123
+ });
124
+
125
+ it("returns undefined when there is no context", () => {
126
+ expect(serializeModelContext(undefined)).toBeUndefined();
127
+ });
128
+ });
@@ -1,6 +1,6 @@
1
1
  import type { ModelContext } from "@assistant-ui/react";
2
2
  import type { SerializedModelContext } from "../types";
3
- import { normalizeToolList } from "./toolNormalization";
3
+ import { normalizeToolList, type NormalizedTool } from "./toolNormalization";
4
4
 
5
5
  export const sanitizeForMessage = (
6
6
  value: unknown,
@@ -52,6 +52,69 @@ export const sanitizeForMessage = (
52
52
  return value;
53
53
  };
54
54
 
55
+ export const REDACTED = "[redacted]";
56
+
57
+ const SENSITIVE_KEYS = new Set([
58
+ "apikey",
59
+ "xapikey",
60
+ "accesskey",
61
+ "authorization",
62
+ "password",
63
+ "passwd",
64
+ "secret",
65
+ "clientsecret",
66
+ "token",
67
+ "accesstoken",
68
+ "refreshtoken",
69
+ "cookie",
70
+ "setcookie",
71
+ "credential",
72
+ "credentials",
73
+ "privatekey",
74
+ "bearer",
75
+ "sessionid",
76
+ ]);
77
+
78
+ const normalizeKey = (key: string) => key.toLowerCase().replace(/[-_]/g, "");
79
+
80
+ /**
81
+ * Subtrees that are credential maps with arbitrary, user-defined key names
82
+ * (MCP stdio `env`, HTTP `headers`). Per-key name matching cannot catch
83
+ * `OPENAI_API_KEY` or a custom auth header, so every leaf inside one of these
84
+ * is masked wholesale.
85
+ */
86
+ const MASK_ALL_KEYS = new Set(["env", "headers"]);
87
+
88
+ /**
89
+ * Mask values whose key names a known credential, and mask every value inside
90
+ * an `env`/`headers` subtree wholesale. Operates on already sanitized plain
91
+ * data (primitives, arrays, plain objects). Applied only to config-bearing
92
+ * subtrees, never to a tool's parameters schema, so a schema property literally
93
+ * named `token` is not corrupted.
94
+ */
95
+ export const redactSensitive = (value: unknown, maskAll = false): unknown => {
96
+ if (Array.isArray(value)) {
97
+ return value.map((entry) => redactSensitive(entry, maskAll));
98
+ }
99
+ if (value && typeof value === "object") {
100
+ const result: Record<string, unknown> = {};
101
+ for (const [key, entry] of Object.entries(
102
+ value as Record<string, unknown>,
103
+ )) {
104
+ const normalized = normalizeKey(key);
105
+ result[key] =
106
+ maskAll || SENSITIVE_KEYS.has(normalized)
107
+ ? REDACTED
108
+ : redactSensitive(entry, MASK_ALL_KEYS.has(normalized));
109
+ }
110
+ return result;
111
+ }
112
+ return maskAll ? REDACTED : value;
113
+ };
114
+
115
+ export const sanitizeAndRedact = (value: unknown): unknown =>
116
+ redactSensitive(sanitizeForMessage(value));
117
+
55
118
  export const serializeModelContext = (
56
119
  context: ModelContext | undefined,
57
120
  ): SerializedModelContext | undefined => {
@@ -69,14 +132,28 @@ export const serializeModelContext = (
69
132
 
70
133
  const tools = normalizeToolList(modelContext.tools);
71
134
  if (tools.length > 0) {
72
- result.tools = tools.map((tool) => ({
73
- ...tool,
74
- parameters: sanitizeForMessage(tool.parameters),
75
- }));
135
+ result.tools = tools.map((tool): NormalizedTool => {
136
+ return {
137
+ ...tool,
138
+ parameters: sanitizeForMessage(tool.parameters),
139
+ ...(tool.providerOptions !== undefined
140
+ ? { providerOptions: sanitizeAndRedact(tool.providerOptions) }
141
+ : {}),
142
+ ...(tool.providerArgs !== undefined
143
+ ? { providerArgs: sanitizeAndRedact(tool.providerArgs) }
144
+ : {}),
145
+ ...(tool.server !== undefined
146
+ ? { server: sanitizeAndRedact(tool.server) }
147
+ : {}),
148
+ ...(tool.backendDefault !== undefined
149
+ ? { backendDefault: sanitizeForMessage(tool.backendDefault) }
150
+ : {}),
151
+ };
152
+ });
76
153
  }
77
154
 
78
155
  if (modelContext.callSettings !== undefined) {
79
- const callSettings = sanitizeForMessage(modelContext.callSettings);
156
+ const callSettings = sanitizeAndRedact(modelContext.callSettings);
80
157
  if (
81
158
  callSettings &&
82
159
  typeof callSettings === "object" &&
@@ -87,7 +164,7 @@ export const serializeModelContext = (
87
164
  }
88
165
 
89
166
  if (modelContext.config !== undefined) {
90
- const config = sanitizeForMessage(modelContext.config);
167
+ const config = sanitizeAndRedact(modelContext.config);
91
168
  if (config && typeof config === "object" && !Array.isArray(config)) {
92
169
  result.config = config as Record<string, unknown>;
93
170
  }
@@ -0,0 +1,71 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { normalizeToolList } from "./toolNormalization";
3
+
4
+ describe("normalizeToolList", () => {
5
+ it("retains the full metadata of a provider tool", () => {
6
+ const [tool] = normalizeToolList({
7
+ web_search: {
8
+ type: "provider",
9
+ providerId: "openai.web_search_preview",
10
+ args: { searchContextSize: "high" },
11
+ supportsDeferredResults: true,
12
+ disabled: false,
13
+ },
14
+ });
15
+
16
+ expect(tool).toMatchObject({
17
+ name: "web_search",
18
+ type: "provider",
19
+ providerId: "openai.web_search_preview",
20
+ providerArgs: { searchContextSize: "high" },
21
+ supportsDeferredResults: true,
22
+ disabled: false,
23
+ });
24
+ });
25
+
26
+ it("retains the MCP server config and display mode", () => {
27
+ const [tool] = normalizeToolList({
28
+ list_repos: {
29
+ type: "mcp",
30
+ display: "standalone",
31
+ server: { type: "http", url: "https://mcp.example.com" },
32
+ },
33
+ });
34
+
35
+ expect(tool?.type).toBe("mcp");
36
+ expect(tool?.display).toBe("standalone");
37
+ expect(tool?.server).toEqual({
38
+ type: "http",
39
+ url: "https://mcp.example.com",
40
+ });
41
+ });
42
+
43
+ it("retains providerOptions and backend defaults", () => {
44
+ const [tool] = normalizeToolList({
45
+ submit: {
46
+ type: "frontend",
47
+ parameters: { type: "object" },
48
+ providerOptions: { openai: { strict: true } },
49
+ unstable_backendDefault: { parameters: true },
50
+ },
51
+ });
52
+
53
+ expect(tool?.providerOptions).toEqual({ openai: { strict: true } });
54
+ expect(tool?.backendDefault).toEqual({ parameters: true });
55
+ expect(tool?.parameters).toEqual({ type: "object" });
56
+ });
57
+
58
+ it("handles the array form", () => {
59
+ const tools = normalizeToolList([
60
+ { name: "a", type: "frontend" },
61
+ { name: "b", type: "backend", disabled: true },
62
+ ]);
63
+ expect(tools.map((t) => t.name)).toEqual(["a", "b"]);
64
+ expect(tools[1]?.disabled).toBe(true);
65
+ });
66
+
67
+ it("returns an empty list for non-objects", () => {
68
+ expect(normalizeToolList(undefined)).toEqual([]);
69
+ expect(normalizeToolList(null)).toEqual([]);
70
+ });
71
+ });
@@ -5,6 +5,13 @@ export type NormalizedTool = {
5
5
  type?: string;
6
6
  description?: string;
7
7
  disabled?: boolean;
8
+ display?: string;
9
+ providerId?: string;
10
+ supportsDeferredResults?: boolean;
11
+ backendDefault?: unknown;
12
+ providerOptions?: unknown;
13
+ providerArgs?: unknown;
14
+ server?: unknown;
8
15
  parameters?: unknown;
9
16
  };
10
17
 
@@ -41,6 +48,34 @@ const mapToNormalizedTool = (
41
48
  tool.disabled = raw.disabled as boolean;
42
49
  }
43
50
 
51
+ if (typeof raw.display === "string") {
52
+ tool.display = raw.display as string;
53
+ }
54
+
55
+ if (typeof raw.providerId === "string") {
56
+ tool.providerId = raw.providerId as string;
57
+ }
58
+
59
+ if (typeof raw.supportsDeferredResults === "boolean") {
60
+ tool.supportsDeferredResults = raw.supportsDeferredResults as boolean;
61
+ }
62
+
63
+ if (raw.unstable_backendDefault !== undefined) {
64
+ tool.backendDefault = raw.unstable_backendDefault;
65
+ }
66
+
67
+ if (raw.providerOptions !== undefined) {
68
+ tool.providerOptions = raw.providerOptions;
69
+ }
70
+
71
+ if (raw.args !== undefined) {
72
+ tool.providerArgs = raw.args;
73
+ }
74
+
75
+ if (raw.server !== undefined) {
76
+ tool.server = raw.server;
77
+ }
78
+
44
79
  if (Object.hasOwn(raw, "parameters")) {
45
80
  tool.parameters = toJsonSchema(raw.parameters);
46
81
  }