@assistant-ui/react-devtools 1.2.16 → 1.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shell/DevToolsPanel.js +128 -143
- package/dist/shell/DevToolsPanel.js.map +1 -1
- package/dist/shell/tabs/ThreadTab.js +214 -235
- package/dist/shell/tabs/ThreadTab.js.map +1 -1
- package/dist/utils/serialization.d.ts.map +1 -1
- package/dist/utils/serialization.js +59 -33
- package/dist/utils/serialization.js.map +1 -1
- package/dist/utils/toolNormalization.d.ts.map +1 -1
- package/dist/utils/toolNormalization.js +49 -17
- package/dist/utils/toolNormalization.js.map +1 -1
- package/dist/utils/unserializable.d.ts +6 -0
- package/dist/utils/unserializable.d.ts.map +1 -0
- package/dist/utils/unserializable.js +13 -0
- package/dist/utils/unserializable.js.map +1 -0
- package/dist/views/context/contextNodes.d.ts.map +1 -1
- package/dist/views/context/contextNodes.js +18 -5
- package/dist/views/context/contextNodes.js.map +1 -1
- package/package.json +8 -8
- package/src/data/projectApi.test.ts +39 -0
- package/src/shell/DevToolsPanel.tsx +7 -5
- package/src/shell/tabs/ThreadTab.tsx +10 -6
- package/src/utils/serialization.test.ts +106 -0
- package/src/utils/serialization.ts +79 -45
- package/src/utils/toolNormalization.test.ts +66 -0
- package/src/utils/toolNormalization.ts +64 -30
- package/src/utils/unserializable.ts +9 -0
- package/src/views/context/contextNodes.test.ts +21 -0
- package/src/views/context/contextNodes.ts +13 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UNSERIALIZABLE, readProperty } from "./unserializable.js";
|
|
1
2
|
import { normalizeToolList } from "./toolNormalization.js";
|
|
2
3
|
//#region src/utils/serialization.ts
|
|
3
4
|
const sanitizeForMessage = (value, seen = /* @__PURE__ */ new WeakSet()) => {
|
|
@@ -5,36 +6,59 @@ const sanitizeForMessage = (value, seen = /* @__PURE__ */ new WeakSet()) => {
|
|
|
5
6
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
6
7
|
if (typeof value === "bigint" || typeof value === "symbol") return String(value);
|
|
7
8
|
if (typeof value === "function") return "[Function]";
|
|
8
|
-
if (value instanceof Date) return Number.isNaN(value.getTime()) ? String(value) : value.toISOString();
|
|
9
|
-
if (value instanceof Map) {
|
|
10
|
-
if (seen.has(value)) return "[Circular]";
|
|
11
|
-
seen.add(value);
|
|
12
|
-
const result = {};
|
|
13
|
-
for (const [key, entry] of value.entries()) result[String(key)] = sanitizeForMessage(entry, seen);
|
|
14
|
-
seen.delete(value);
|
|
15
|
-
return result;
|
|
16
|
-
}
|
|
17
|
-
if (value instanceof Set) {
|
|
18
|
-
if (seen.has(value)) return "[Circular]";
|
|
19
|
-
seen.add(value);
|
|
20
|
-
const result = Array.from(value).map((entry) => sanitizeForMessage(entry, seen));
|
|
21
|
-
seen.delete(value);
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
if (Array.isArray(value)) {
|
|
25
|
-
if (seen.has(value)) return "[Circular]";
|
|
26
|
-
seen.add(value);
|
|
27
|
-
const result = value.map((entry) => sanitizeForMessage(entry, seen)).filter((item) => item !== void 0);
|
|
28
|
-
seen.delete(value);
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
9
|
if (typeof value === "object") {
|
|
32
10
|
if (seen.has(value)) return "[Circular]";
|
|
33
11
|
seen.add(value);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
12
|
+
try {
|
|
13
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? String(value) : value.toISOString();
|
|
14
|
+
if (value instanceof Map) {
|
|
15
|
+
const result = {};
|
|
16
|
+
const nextSuffixByKey = /* @__PURE__ */ new Map();
|
|
17
|
+
for (const [key, entry] of value.entries()) {
|
|
18
|
+
let serializedKey;
|
|
19
|
+
try {
|
|
20
|
+
serializedKey = String(key);
|
|
21
|
+
} catch {
|
|
22
|
+
serializedKey = UNSERIALIZABLE;
|
|
23
|
+
}
|
|
24
|
+
if (Object.hasOwn(result, serializedKey)) {
|
|
25
|
+
const baseKey = serializedKey;
|
|
26
|
+
let suffix = nextSuffixByKey.get(baseKey) ?? 2;
|
|
27
|
+
do {
|
|
28
|
+
serializedKey = `${baseKey} (${suffix})`;
|
|
29
|
+
suffix += 1;
|
|
30
|
+
} while (Object.hasOwn(result, serializedKey));
|
|
31
|
+
nextSuffixByKey.set(baseKey, suffix);
|
|
32
|
+
} else nextSuffixByKey.set(serializedKey, 2);
|
|
33
|
+
result[serializedKey] = sanitizeForMessage(entry, seen);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
if (value instanceof Set) return Array.from(value).map((entry) => sanitizeForMessage(entry, seen));
|
|
38
|
+
if (Array.isArray(value)) {
|
|
39
|
+
const result = [];
|
|
40
|
+
const length = value.length;
|
|
41
|
+
for (let index = 0; index < length; index++) try {
|
|
42
|
+
if (!(index in value)) continue;
|
|
43
|
+
const item = sanitizeForMessage(value[index], seen);
|
|
44
|
+
if (item !== void 0) result.push(item);
|
|
45
|
+
} catch {
|
|
46
|
+
result.push(UNSERIALIZABLE);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
const result = {};
|
|
51
|
+
for (const key of Object.keys(value)) try {
|
|
52
|
+
result[key] = sanitizeForMessage(value[key], seen);
|
|
53
|
+
} catch {
|
|
54
|
+
result[key] = UNSERIALIZABLE;
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
} catch {
|
|
58
|
+
return UNSERIALIZABLE;
|
|
59
|
+
} finally {
|
|
60
|
+
seen.delete(value);
|
|
61
|
+
}
|
|
38
62
|
}
|
|
39
63
|
return value;
|
|
40
64
|
};
|
|
@@ -91,9 +115,9 @@ const serializeModelContext = (context) => {
|
|
|
91
115
|
if (!context || typeof context !== "object") return;
|
|
92
116
|
const modelContext = context;
|
|
93
117
|
const result = {};
|
|
94
|
-
const systemValue = modelContext
|
|
118
|
+
const systemValue = readProperty(modelContext, "system");
|
|
95
119
|
if (typeof systemValue === "string" && systemValue.length > 0) result.system = systemValue;
|
|
96
|
-
const tools = normalizeToolList(modelContext
|
|
120
|
+
const tools = normalizeToolList(readProperty(modelContext, "tools"));
|
|
97
121
|
if (tools.length > 0) result.tools = tools.map((tool) => {
|
|
98
122
|
return {
|
|
99
123
|
...tool,
|
|
@@ -104,12 +128,14 @@ const serializeModelContext = (context) => {
|
|
|
104
128
|
...tool.backendDefault !== void 0 ? { backendDefault: sanitizeForMessage(tool.backendDefault) } : {}
|
|
105
129
|
};
|
|
106
130
|
});
|
|
107
|
-
|
|
108
|
-
|
|
131
|
+
const callSettingsValue = readProperty(modelContext, "callSettings");
|
|
132
|
+
if (callSettingsValue !== void 0) {
|
|
133
|
+
const callSettings = sanitizeAndRedact(callSettingsValue);
|
|
109
134
|
if (callSettings && typeof callSettings === "object" && !Array.isArray(callSettings)) result.callSettings = callSettings;
|
|
110
135
|
}
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
const configValue = readProperty(modelContext, "config");
|
|
137
|
+
if (configValue !== void 0) {
|
|
138
|
+
const config = sanitizeAndRedact(configValue);
|
|
113
139
|
if (config && typeof config === "object" && !Array.isArray(config)) result.config = config;
|
|
114
140
|
}
|
|
115
141
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialization.js","names":["ModelContext","SerializedModelContext","normalizeToolList","NormalizedTool","sanitizeForMessage","value","seen","WeakSet","undefined","String","Date","Number","isNaN","getTime","toISOString","Map","has","add","result","Record","key","entry","entries","delete","Set","Array","from","map","isArray","filter","item","Object","REDACTED","SENSITIVE_KEYS","normalizeKey","toLowerCase","replace","MASK_ALL_KEYS","redactSensitive","maskAll","normalized","sanitizeAndRedact","serializeModelContext","context","modelContext","systemValue","system","length","tools","tool","parameters","providerOptions","providerArgs","server","backendDefault","callSettings","config","keys"],"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 === \"bigint\" || typeof value === \"symbol\") {\n return String(value);\n }\n if (typeof value === \"function\") {\n return \"[Function]\";\n }\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? String(value) : value.toISOString();\n }\n if (value instanceof Map) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n const result: Record<string, unknown> = {};\n for (const [key, entry] of value.entries()) {\n result[String(key)] = sanitizeForMessage(entry, seen);\n }\n seen.delete(value);\n return result;\n }\n if (value instanceof Set) {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n const result = Array.from(value).map((entry) =>\n sanitizeForMessage(entry, seen),\n );\n seen.delete(value);\n return result;\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 const result = value\n .map((entry) => sanitizeForMessage(entry, seen))\n .filter((item) => item !== undefined);\n seen.delete(value as unknown as object);\n return result;\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 seen.delete(value as object);\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":"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"}
|
|
1
|
+
{"version":3,"file":"serialization.js","names":["ModelContext","SerializedModelContext","normalizeToolList","NormalizedTool","readProperty","UNSERIALIZABLE","sanitizeForMessage","value","seen","WeakSet","undefined","String","has","add","Date","Number","isNaN","getTime","toISOString","Map","result","Record","nextSuffixByKey","key","entry","entries","serializedKey","Object","hasOwn","baseKey","suffix","get","set","Set","Array","from","map","isArray","length","index","item","push","keys","delete","REDACTED","SENSITIVE_KEYS","normalizeKey","toLowerCase","replace","MASK_ALL_KEYS","redactSensitive","maskAll","normalized","sanitizeAndRedact","serializeModelContext","context","modelContext","systemValue","system","tools","tool","parameters","providerOptions","providerArgs","server","backendDefault","callSettingsValue","callSettings","configValue","config"],"sources":["../../src/utils/serialization.ts"],"sourcesContent":["import type { ModelContext } from \"@assistant-ui/react\";\nimport type { SerializedModelContext } from \"../types\";\nimport { normalizeToolList, type NormalizedTool } from \"./toolNormalization\";\nimport { readProperty, UNSERIALIZABLE } from \"./unserializable\";\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 === \"bigint\" || typeof value === \"symbol\") {\n return String(value);\n }\n if (typeof value === \"function\") {\n return \"[Function]\";\n }\n if (typeof value === \"object\") {\n if (seen.has(value)) return \"[Circular]\";\n seen.add(value);\n try {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime())\n ? String(value)\n : value.toISOString();\n }\n if (value instanceof Map) {\n const result: Record<string, unknown> = {};\n const nextSuffixByKey = new Map<string, number>();\n for (const [key, entry] of value.entries()) {\n let serializedKey: string;\n try {\n serializedKey = String(key);\n } catch {\n serializedKey = UNSERIALIZABLE;\n }\n\n if (Object.hasOwn(result, serializedKey)) {\n const baseKey = serializedKey;\n let suffix = nextSuffixByKey.get(baseKey) ?? 2;\n do {\n serializedKey = `${baseKey} (${suffix})`;\n suffix += 1;\n } while (Object.hasOwn(result, serializedKey));\n nextSuffixByKey.set(baseKey, suffix);\n } else {\n nextSuffixByKey.set(serializedKey, 2);\n }\n\n result[serializedKey] = sanitizeForMessage(entry, seen);\n }\n return result;\n }\n if (value instanceof Set) {\n return Array.from(value).map((entry) =>\n sanitizeForMessage(entry, seen),\n );\n }\n if (Array.isArray(value)) {\n const result: unknown[] = [];\n const length = value.length;\n for (let index = 0; index < length; index++) {\n try {\n if (!(index in value)) continue;\n const item = sanitizeForMessage(value[index], seen);\n if (item !== undefined) result.push(item);\n } catch {\n result.push(UNSERIALIZABLE);\n }\n }\n return result;\n }\n\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n try {\n result[key] = sanitizeForMessage(\n (value as Record<string, unknown>)[key],\n seen,\n );\n } catch {\n result[key] = UNSERIALIZABLE;\n }\n }\n return result;\n } catch {\n return UNSERIALIZABLE;\n } finally {\n seen.delete(value);\n }\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 = readProperty(modelContext, \"system\");\n if (typeof systemValue === \"string\" && systemValue.length > 0) {\n result.system = systemValue;\n }\n\n const tools = normalizeToolList(readProperty(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 const callSettingsValue = readProperty(modelContext, \"callSettings\");\n if (callSettingsValue !== undefined) {\n const callSettings = sanitizeAndRedact(callSettingsValue);\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 const configValue = readProperty(modelContext, \"config\");\n if (configValue !== undefined) {\n const config = sanitizeAndRedact(configValue);\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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolNormalization.d.ts","names":[],"sources":["../../src/utils/toolNormalization.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"toolNormalization.d.ts","names":[],"sources":["../../src/utils/toolNormalization.ts"],"mappings":";KAGY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;cAqFW,oBAAqB,mBAAiB"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { UNSERIALIZABLE, readProperty } from "./unserializable.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
//#region src/utils/toolNormalization.ts
|
|
3
4
|
const isRecord = (value) => value !== null && typeof value === "object";
|
|
4
5
|
const toJsonSchema = (value) => {
|
|
5
|
-
|
|
6
|
-
return z.toJSONSchema(value);
|
|
6
|
+
try {
|
|
7
|
+
if (value instanceof z.ZodType) return z.toJSONSchema(value);
|
|
7
8
|
} catch {
|
|
8
9
|
return value;
|
|
9
10
|
}
|
|
@@ -11,32 +12,63 @@ const toJsonSchema = (value) => {
|
|
|
11
12
|
};
|
|
12
13
|
const mapToNormalizedTool = (name, raw) => {
|
|
13
14
|
const tool = { name };
|
|
14
|
-
|
|
15
|
-
if (typeof
|
|
16
|
-
|
|
17
|
-
if (typeof
|
|
18
|
-
|
|
19
|
-
if (typeof
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
15
|
+
const type = readProperty(raw, "type");
|
|
16
|
+
if (typeof type === "string") tool.type = type;
|
|
17
|
+
const description = readProperty(raw, "description");
|
|
18
|
+
if (typeof description === "string") tool.description = description;
|
|
19
|
+
const disabled = readProperty(raw, "disabled");
|
|
20
|
+
if (typeof disabled === "boolean") tool.disabled = disabled;
|
|
21
|
+
const display = readProperty(raw, "display");
|
|
22
|
+
if (typeof display === "string") tool.display = display;
|
|
23
|
+
const providerId = readProperty(raw, "providerId");
|
|
24
|
+
if (typeof providerId === "string") tool.providerId = providerId;
|
|
25
|
+
const supportsDeferredResults = readProperty(raw, "supportsDeferredResults");
|
|
26
|
+
if (typeof supportsDeferredResults === "boolean") tool.supportsDeferredResults = supportsDeferredResults;
|
|
27
|
+
const backendDefault = readProperty(raw, "unstable_backendDefault");
|
|
28
|
+
if (backendDefault !== void 0) tool.backendDefault = backendDefault;
|
|
29
|
+
const providerOptions = readProperty(raw, "providerOptions");
|
|
30
|
+
if (providerOptions !== void 0) tool.providerOptions = providerOptions;
|
|
31
|
+
const providerArgs = readProperty(raw, "args");
|
|
32
|
+
if (providerArgs !== void 0) tool.providerArgs = providerArgs;
|
|
33
|
+
const server = readProperty(raw, "server");
|
|
34
|
+
if (server !== void 0) tool.server = server;
|
|
35
|
+
try {
|
|
36
|
+
if (Object.hasOwn(raw, "parameters")) tool.parameters = toJsonSchema(readProperty(raw, "parameters"));
|
|
37
|
+
} catch {
|
|
38
|
+
tool.parameters = UNSERIALIZABLE;
|
|
39
|
+
}
|
|
25
40
|
return tool;
|
|
26
41
|
};
|
|
27
42
|
const normalizeToolList = (value) => {
|
|
43
|
+
if (value === "[Unserializable]") return [{ name: UNSERIALIZABLE }];
|
|
28
44
|
if (!value || typeof value !== "object") return [];
|
|
29
45
|
if (Array.isArray(value)) {
|
|
30
46
|
const tools = [];
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
const length = readProperty(value, "length");
|
|
48
|
+
if (typeof length !== "number") return [{ name: UNSERIALIZABLE }];
|
|
49
|
+
for (let index = 0; index < length; index++) {
|
|
50
|
+
const entry = readProperty(value, index);
|
|
51
|
+
if (entry === "[Unserializable]") {
|
|
52
|
+
tools.push({ name: UNSERIALIZABLE });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!isRecord(entry)) continue;
|
|
56
|
+
const name = readProperty(entry, "name");
|
|
57
|
+
if (typeof name !== "string") continue;
|
|
58
|
+
tools.push(mapToNormalizedTool(name, entry));
|
|
34
59
|
}
|
|
35
60
|
return tools;
|
|
36
61
|
}
|
|
37
62
|
if (isRecord(value)) {
|
|
38
63
|
const tools = [];
|
|
39
|
-
|
|
64
|
+
let names;
|
|
65
|
+
try {
|
|
66
|
+
names = Object.keys(value);
|
|
67
|
+
} catch {
|
|
68
|
+
return [{ name: UNSERIALIZABLE }];
|
|
69
|
+
}
|
|
70
|
+
for (const name of names) {
|
|
71
|
+
const entry = readProperty(value, name);
|
|
40
72
|
if (!isRecord(entry)) {
|
|
41
73
|
tools.push({ name });
|
|
42
74
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolNormalization.js","names":["z","NormalizedTool","name","type","description","disabled","display","providerId","supportsDeferredResults","backendDefault","providerOptions","providerArgs","server","parameters","isRecord","value","Record","toJsonSchema","ZodType","toJSONSchema","mapToNormalizedTool","raw","tool","
|
|
1
|
+
{"version":3,"file":"toolNormalization.js","names":["z","readProperty","UNSERIALIZABLE","NormalizedTool","name","type","description","disabled","display","providerId","supportsDeferredResults","backendDefault","providerOptions","providerArgs","server","parameters","isRecord","value","Record","toJsonSchema","ZodType","toJSONSchema","mapToNormalizedTool","raw","tool","undefined","Object","hasOwn","normalizeToolList","Array","isArray","tools","length","index","entry","push","names","keys"],"sources":["../../src/utils/toolNormalization.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { readProperty, UNSERIALIZABLE } from \"./unserializable\";\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 try {\n if (value instanceof z.ZodType) {\n return z.toJSONSchema(value);\n }\n } catch {\n return value;\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 const type = readProperty(raw, \"type\");\n if (typeof type === \"string\") {\n tool.type = type;\n }\n\n const description = readProperty(raw, \"description\");\n if (typeof description === \"string\") {\n tool.description = description;\n }\n\n const disabled = readProperty(raw, \"disabled\");\n if (typeof disabled === \"boolean\") {\n tool.disabled = disabled;\n }\n\n const display = readProperty(raw, \"display\");\n if (typeof display === \"string\") {\n tool.display = display;\n }\n\n const providerId = readProperty(raw, \"providerId\");\n if (typeof providerId === \"string\") {\n tool.providerId = providerId;\n }\n\n const supportsDeferredResults = readProperty(raw, \"supportsDeferredResults\");\n if (typeof supportsDeferredResults === \"boolean\") {\n tool.supportsDeferredResults = supportsDeferredResults;\n }\n\n const backendDefault = readProperty(raw, \"unstable_backendDefault\");\n if (backendDefault !== undefined) {\n tool.backendDefault = backendDefault;\n }\n\n const providerOptions = readProperty(raw, \"providerOptions\");\n if (providerOptions !== undefined) {\n tool.providerOptions = providerOptions;\n }\n\n const providerArgs = readProperty(raw, \"args\");\n if (providerArgs !== undefined) {\n tool.providerArgs = providerArgs;\n }\n\n const server = readProperty(raw, \"server\");\n if (server !== undefined) {\n tool.server = server;\n }\n\n try {\n if (Object.hasOwn(raw, \"parameters\")) {\n tool.parameters = toJsonSchema(readProperty(raw, \"parameters\"));\n }\n } catch {\n tool.parameters = UNSERIALIZABLE;\n }\n\n return tool;\n};\n\nexport const normalizeToolList = (value: unknown): NormalizedTool[] => {\n if (value === UNSERIALIZABLE) {\n return [{ name: UNSERIALIZABLE }];\n }\n if (!value || typeof value !== \"object\") {\n return [];\n }\n\n if (Array.isArray(value)) {\n const tools: NormalizedTool[] = [];\n const length = readProperty(value, \"length\");\n if (typeof length !== \"number\") return [{ name: UNSERIALIZABLE }];\n\n for (let index = 0; index < length; index++) {\n const entry = readProperty(value, index);\n if (entry === UNSERIALIZABLE) {\n tools.push({ name: UNSERIALIZABLE });\n continue;\n }\n if (!isRecord(entry)) continue;\n const name = readProperty(entry, \"name\");\n if (typeof name !== \"string\") continue;\n tools.push(mapToNormalizedTool(name, entry));\n }\n\n return tools;\n }\n\n if (isRecord(value)) {\n const tools: NormalizedTool[] = [];\n let names: string[];\n try {\n names = Object.keys(value);\n } catch {\n return [{ name: UNSERIALIZABLE }];\n }\n\n for (const name of names) {\n const entry = readProperty(value, name);\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":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region src/utils/unserializable.d.ts
|
|
2
|
+
declare const UNSERIALIZABLE = "[Unserializable]";
|
|
3
|
+
declare const readProperty: (value: object, key: PropertyKey) => unknown;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { UNSERIALIZABLE, readProperty };
|
|
6
|
+
//# sourceMappingURL=unserializable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unserializable.d.ts","names":[],"sources":["../../src/utils/unserializable.ts"],"mappings":";cAAa;cAEA,eAAgB,eAAe,KAAK"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/utils/unserializable.ts
|
|
2
|
+
const UNSERIALIZABLE = "[Unserializable]";
|
|
3
|
+
const readProperty = (value, key) => {
|
|
4
|
+
try {
|
|
5
|
+
return Reflect.get(value, key);
|
|
6
|
+
} catch {
|
|
7
|
+
return UNSERIALIZABLE;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
export { UNSERIALIZABLE, readProperty };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=unserializable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unserializable.js","names":["UNSERIALIZABLE","readProperty","value","key","PropertyKey","Reflect","get"],"sources":["../../src/utils/unserializable.ts"],"sourcesContent":["export const UNSERIALIZABLE = \"[Unserializable]\";\n\nexport const readProperty = (value: object, key: PropertyKey): unknown => {\n try {\n return Reflect.get(value, key);\n } catch {\n return UNSERIALIZABLE;\n }\n};\n"],"mappings":";AAAA,MAAaA,iBAAiB;AAE9B,MAAaC,gBAAgBC,OAAeC,QAA8B;CACxE,IAAI;EACF,OAAOE,QAAQC,IAAIJ,OAAOC,GAAG;CAC/B,QAAQ;EACN,OAAOH;CACT;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextNodes.d.ts","names":[],"sources":["../../../src/views/context/contextNodes.ts"],"mappings":";;;;KAKY;EACN;EAAkB;EAAgB;;EAClC;EAA0B;EAAc,MAAM;;EAC9C;EAAwB;;EACxB;EAAkB;;EAClB;EAAe;;EACf;EAAmB;;KAEb,kBAAkB,SAAS;cAK1B,kBAAmB,MAAM,YAAU;
|
|
1
|
+
{"version":3,"file":"contextNodes.d.ts","names":[],"sources":["../../../src/views/context/contextNodes.ts"],"mappings":";;;;KAKY;EACN;EAAkB;EAAgB;;EAClC;EAA0B;EAAc,MAAM;;EAC9C;EAAwB;;EACxB;EAAkB;;EAClB;EAAe;;EACf;EAAmB;;KAEb,kBAAkB,SAAS;cAK1B,kBAAmB,MAAM,YAAU;cAwDnC,cAAe"}
|
|
@@ -9,11 +9,24 @@ const buildContextNav = (data) => {
|
|
|
9
9
|
kind: "system",
|
|
10
10
|
preview: model.system
|
|
11
11
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
tool
|
|
16
|
-
|
|
12
|
+
const nextToolOccurrence = /* @__PURE__ */ new Map();
|
|
13
|
+
const toolNodeIds = /* @__PURE__ */ new Set();
|
|
14
|
+
for (const tool of model?.tools ?? []) {
|
|
15
|
+
const baseId = `ctx:tool:${tool.name}`;
|
|
16
|
+
let occurrence = nextToolOccurrence.get(tool.name) ?? 1;
|
|
17
|
+
let id = occurrence === 1 ? baseId : `${baseId}:${occurrence}`;
|
|
18
|
+
while (toolNodeIds.has(id)) {
|
|
19
|
+
occurrence += 1;
|
|
20
|
+
id = `${baseId}:${occurrence}`;
|
|
21
|
+
}
|
|
22
|
+
nextToolOccurrence.set(tool.name, occurrence + 1);
|
|
23
|
+
toolNodeIds.add(id);
|
|
24
|
+
modelNodes.push({
|
|
25
|
+
id,
|
|
26
|
+
kind: "tool",
|
|
27
|
+
tool
|
|
28
|
+
});
|
|
29
|
+
}
|
|
17
30
|
if (hasKeys(model?.callSettings)) modelNodes.push({
|
|
18
31
|
id: "ctx:callSettings",
|
|
19
32
|
kind: "callSettings"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextNodes.js","names":["ApiInfo","NormalizedTool","isRecord","NavGroup","ContextNode","id","kind","preview","tool","ContextNavGroup","hasKeys","value","Record","Boolean","Object","keys","length","buildContextNav","data","model","modelContext","modelNodes","system","push","tools","name","callSettings","config","runtimeNodes","state","mcp","undefined","label","nodes","toolUiCount"],"sources":["../../../src/views/context/contextNodes.ts"],"sourcesContent":["import type { ApiInfo } from \"../../data/types\";\nimport type { NormalizedTool } from \"../../utils/toolNormalization\";\nimport { isRecord } from \"../../utils/common\";\nimport type { NavGroup } from \"../nav\";\n\nexport type ContextNode =\n | { id: \"ctx:system\"; kind: \"system\"; preview: string }\n | { id: `ctx:tool:${string}`; kind: \"tool\"; tool: NormalizedTool }\n | { id: \"ctx:callSettings\"; kind: \"callSettings\" }\n | { id: \"ctx:config\"; kind: \"config\" }\n | { id: \"ctx:mcp\"; kind: \"mcp\" }\n | { id: \"ctx:toolUIs\"; kind: \"toolUIs\" };\n\nexport type ContextNavGroup = NavGroup<ContextNode>;\n\nconst hasKeys = (value: Record<string, unknown> | undefined) =>\n Boolean(value && Object.keys(value).length > 0);\n\nexport const buildContextNav = (data: ApiInfo): ContextNavGroup[] => {\n const model = data.modelContext;\n const modelNodes: ContextNode[] = [];\n\n if (model?.system) {\n modelNodes.push({\n id: \"ctx:system\",\n kind: \"system\",\n preview: model.system,\n });\n }\n\n for (const tool of model?.tools ?? []) {\n
|
|
1
|
+
{"version":3,"file":"contextNodes.js","names":["ApiInfo","NormalizedTool","isRecord","NavGroup","ContextNode","id","kind","preview","tool","ContextNavGroup","hasKeys","value","Record","Boolean","Object","keys","length","buildContextNav","data","model","modelContext","modelNodes","system","push","nextToolOccurrence","Map","toolNodeIds","Set","tools","baseId","name","const","occurrence","get","has","set","add","callSettings","config","runtimeNodes","state","mcp","undefined","label","nodes","toolUiCount"],"sources":["../../../src/views/context/contextNodes.ts"],"sourcesContent":["import type { ApiInfo } from \"../../data/types\";\nimport type { NormalizedTool } from \"../../utils/toolNormalization\";\nimport { isRecord } from \"../../utils/common\";\nimport type { NavGroup } from \"../nav\";\n\nexport type ContextNode =\n | { id: \"ctx:system\"; kind: \"system\"; preview: string }\n | { id: `ctx:tool:${string}`; kind: \"tool\"; tool: NormalizedTool }\n | { id: \"ctx:callSettings\"; kind: \"callSettings\" }\n | { id: \"ctx:config\"; kind: \"config\" }\n | { id: \"ctx:mcp\"; kind: \"mcp\" }\n | { id: \"ctx:toolUIs\"; kind: \"toolUIs\" };\n\nexport type ContextNavGroup = NavGroup<ContextNode>;\n\nconst hasKeys = (value: Record<string, unknown> | undefined) =>\n Boolean(value && Object.keys(value).length > 0);\n\nexport const buildContextNav = (data: ApiInfo): ContextNavGroup[] => {\n const model = data.modelContext;\n const modelNodes: ContextNode[] = [];\n\n if (model?.system) {\n modelNodes.push({\n id: \"ctx:system\",\n kind: \"system\",\n preview: model.system,\n });\n }\n\n const nextToolOccurrence = new Map<string, number>();\n const toolNodeIds = new Set<string>();\n for (const tool of model?.tools ?? []) {\n const baseId = `ctx:tool:${tool.name}` as const;\n let occurrence = nextToolOccurrence.get(tool.name) ?? 1;\n let id: `ctx:tool:${string}` =\n occurrence === 1 ? baseId : `${baseId}:${occurrence}`;\n while (toolNodeIds.has(id)) {\n occurrence += 1;\n id = `${baseId}:${occurrence}`;\n }\n nextToolOccurrence.set(tool.name, occurrence + 1);\n toolNodeIds.add(id);\n modelNodes.push({\n id,\n kind: \"tool\",\n tool,\n });\n }\n\n if (hasKeys(model?.callSettings)) {\n modelNodes.push({ id: \"ctx:callSettings\", kind: \"callSettings\" });\n }\n\n if (hasKeys(model?.config)) {\n modelNodes.push({ id: \"ctx:config\", kind: \"config\" });\n }\n\n const runtimeNodes: ContextNode[] = [];\n\n if (data.state.mcp !== undefined) {\n runtimeNodes.push({ id: \"ctx:mcp\", kind: \"mcp\" });\n }\n\n if (data.state.tools !== undefined) {\n runtimeNodes.push({ id: \"ctx:toolUIs\", kind: \"toolUIs\" });\n }\n\n return [\n ...(modelNodes.length ? [{ label: \"Model\", nodes: modelNodes }] : []),\n ...(runtimeNodes.length ? [{ label: \"Runtime\", nodes: runtimeNodes }] : []),\n ];\n};\n\nexport const toolUiCount = (value: unknown) => {\n if (!isRecord(value)) return 0;\n return Object.keys(value).length;\n};\n"],"mappings":";;AAeA,MAAMU,WAAWC,UACfE,QAAQF,SAASG,OAAOC,KAAKJ,KAAK,CAAC,CAACK,SAAS,CAAC;AAEhD,MAAaC,mBAAmBC,SAAqC;CACnE,MAAMC,QAAQD,KAAKE;CACnB,MAAMC,aAA4B,CAAA;CAElC,IAAIF,OAAOG,QACTD,WAAWE,KAAK;EACdlB,IAAI;EACJC,MAAM;EACNC,SAASY,MAAMG;CACjB,CAAC;CAGH,MAAME,qCAAqB,IAAIC,IAAoB;CACnD,MAAMC,8BAAc,IAAIC,IAAY;CACpC,KAAK,MAAMnB,QAAQW,OAAOS,SAAS,CAAA,GAAI;EACrC,MAAMC,SAAS,YAAYrB,KAAKsB;EAChC,IAAIE,aAAaR,mBAAmBS,IAAIzB,KAAKsB,IAAI,KAAK;EACtD,IAAIzB,KACF2B,eAAe,IAAIH,SAAS,GAAGA,OAAM,GAAIG;EAC3C,OAAON,YAAYQ,IAAI7B,EAAE,GAAG;GAC1B2B,cAAc;GACd3B,KAAK,GAAGwB,OAAM,GAAIG;EACpB;EACAR,mBAAmBW,IAAI3B,KAAKsB,MAAME,aAAa,CAAC;EAChDN,YAAYU,IAAI/B,EAAE;EAClBgB,WAAWE,KAAK;GACdlB;GACAC,MAAM;GACNE;EACF,CAAC;CACH;CAEA,IAAIE,QAAQS,OAAOkB,YAAY,GAC7BhB,WAAWE,KAAK;EAAElB,IAAI;EAAoBC,MAAM;CAAe,CAAC;CAGlE,IAAII,QAAQS,OAAOmB,MAAM,GACvBjB,WAAWE,KAAK;EAAElB,IAAI;EAAcC,MAAM;CAAS,CAAC;CAGtD,MAAMiC,eAA8B,CAAA;CAEpC,IAAIrB,KAAKsB,MAAMC,QAAQC,KAAAA,GACrBH,aAAahB,KAAK;EAAElB,IAAI;EAAWC,MAAM;CAAM,CAAC;CAGlD,IAAIY,KAAKsB,MAAMZ,UAAUc,KAAAA,GACvBH,aAAahB,KAAK;EAAElB,IAAI;EAAeC,MAAM;CAAU,CAAC;CAG1D,OAAO,CACL,GAAIe,WAAWL,SAAS,CAAC;EAAE2B,OAAO;EAASC,OAAOvB;CAAW,CAAC,IAAI,CAAA,GAClE,GAAIkB,aAAavB,SAAS,CAAC;EAAE2B,OAAO;EAAWC,OAAOL;CAAa,CAAC,IAAI,CAAA,CAAG;AAE/E;AAEA,MAAaM,eAAelC,UAAmB;CAC7C,IAAI,CAACT,SAASS,KAAK,GAAG,OAAO;CAC7B,OAAOG,OAAOC,KAAKJ,KAAK,CAAC,CAACK;AAC5B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assistant-ui/react-devtools",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.18",
|
|
4
4
|
"description": "React development tools for assistant-ui components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"assistant-ui",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"clsx": "^2.1.1",
|
|
31
|
-
"zod": "^4.4
|
|
31
|
+
"zod": "^4.5.4"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
34
|
"@assistant-ui/react": "^0.15.0",
|
|
35
|
-
"@assistant-ui/tap": "^0.9.
|
|
35
|
+
"@assistant-ui/tap": "^0.9.17",
|
|
36
36
|
"@types/react": "*",
|
|
37
37
|
"@types/react-dom": "*",
|
|
38
38
|
"react": "^18 || ^19",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@assistant-ui/react": "0.15.
|
|
51
|
-
"@assistant-ui/tap": "0.9.
|
|
52
|
-
"@assistant-ui/x-buildutils": "0.0.
|
|
50
|
+
"@assistant-ui/react": "0.15.19",
|
|
51
|
+
"@assistant-ui/tap": "0.9.17",
|
|
52
|
+
"@assistant-ui/x-buildutils": "0.0.26",
|
|
53
53
|
"@tailwindcss/cli": "^4.3.3",
|
|
54
|
-
"@types/node": "^26.
|
|
54
|
+
"@types/node": "^26.4.1",
|
|
55
55
|
"@types/react": "^19.2.18",
|
|
56
|
-
"@types/react-dom": "^19.2.
|
|
56
|
+
"@types/react-dom": "^19.2.7",
|
|
57
57
|
"react": "^19.2.8",
|
|
58
58
|
"react-dom": "^19.2.8",
|
|
59
59
|
"tailwindcss": "^4.3.3",
|
|
@@ -77,6 +77,45 @@ describe("projectApi", () => {
|
|
|
77
77
|
expect(result.modelContext).toEqual({ system: "be nice" });
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
+
it("keeps readable model-context fields when others are unreadable", () => {
|
|
81
|
+
const tools = new Proxy(
|
|
82
|
+
{},
|
|
83
|
+
{
|
|
84
|
+
ownKeys: () => {
|
|
85
|
+
throw new Error("tools unavailable");
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
const modelContext = {
|
|
90
|
+
tools,
|
|
91
|
+
config: { model: "test-model" },
|
|
92
|
+
};
|
|
93
|
+
Object.defineProperty(modelContext, "system", {
|
|
94
|
+
get: () => {
|
|
95
|
+
throw new Error("system unavailable");
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
const thread = scope(
|
|
99
|
+
"root",
|
|
100
|
+
{},
|
|
101
|
+
{
|
|
102
|
+
getState: () => ({ messages: [], isRunning: false }),
|
|
103
|
+
getModelContext: () => modelContext,
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const projected = projectApi(1, {
|
|
108
|
+
api: { thread },
|
|
109
|
+
logs: [],
|
|
110
|
+
} as unknown as Parameters<typeof projectApi>[1]);
|
|
111
|
+
|
|
112
|
+
expect(projected.modelContext).toEqual({
|
|
113
|
+
system: "[Unserializable]",
|
|
114
|
+
tools: [{ name: "[Unserializable]" }],
|
|
115
|
+
config: { model: "test-model" },
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
80
119
|
it("keeps event-log timestamps as Date instances", () => {
|
|
81
120
|
expect(result.logs[0]?.time).toBeInstanceOf(Date);
|
|
82
121
|
expect(result.logs[0]?.event).toBe("thread.run-start");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useCallback,
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
4
|
import clsx from "clsx";
|
|
5
5
|
import { useDevToolsClient } from "../data/useDevToolsClient";
|
|
6
6
|
import { inProcessClient } from "../data/createInProcessClient";
|
|
@@ -40,10 +40,12 @@ export const DevToolsPanel = ({
|
|
|
40
40
|
resolvedClient,
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
const resolvedApiId =
|
|
44
|
+
selectedApiId !== null && apiIds.includes(selectedApiId)
|
|
45
|
+
? selectedApiId
|
|
46
|
+
: (apiIds[0] ?? null);
|
|
47
|
+
|
|
48
|
+
if (resolvedApiId !== selectedApiId) setSelectedApiId(resolvedApiId);
|
|
47
49
|
|
|
48
50
|
const allPlugins = useMemo(
|
|
49
51
|
() =>
|
|
@@ -79,12 +79,16 @@ export const ThreadTab = ({
|
|
|
79
79
|
() => threadList?.mainThreadId ?? threadList?.threadIds[0] ?? "",
|
|
80
80
|
);
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
const resolvedThreadId =
|
|
83
|
+
hasConversationList && threadList
|
|
84
|
+
? [...threadList.threadIds, ...threadList.archivedThreadIds].includes(
|
|
85
|
+
activeThreadId,
|
|
86
|
+
)
|
|
87
|
+
? activeThreadId
|
|
88
|
+
: (threadList.mainThreadId ?? threadList.threadIds[0] ?? "")
|
|
89
|
+
: activeThreadId;
|
|
90
|
+
|
|
91
|
+
if (resolvedThreadId !== activeThreadId) setActiveThreadId(resolvedThreadId);
|
|
88
92
|
|
|
89
93
|
const prevThreadIdRef = useRef(activeThreadId);
|
|
90
94
|
useEffect(() => {
|