@hef2024/llmasaservice-ui 0.24.2 → 0.24.4
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/index.css +256 -62
- package/dist/index.d.mts +158 -115
- package/dist/index.d.ts +158 -115
- package/dist/index.js +3099 -568
- package/dist/index.mjs +3099 -568
- package/index.ts +6 -1
- package/package.json +1 -1
- package/src/AIAgentPanel.tsx +647 -207
- package/src/AIChatPanel.css +286 -37
- package/src/AIChatPanel.tsx +2871 -358
- package/src/AgentPanel.tsx +4 -0
- package/src/ChatPanel.tsx +254 -104
- package/src/hooks/useAgentRegistry.ts +2 -1
- package/src/mcpAuth.ts +36 -0
- package/src/toolArgsParser.ts +346 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
const TOOL_ARGS_CANDIDATE_PARSE_DEPTH = 3;
|
|
2
|
+
|
|
3
|
+
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const stripMarkdownCodeFence = (input: string): string => {
|
|
8
|
+
const trimmed = input.trim();
|
|
9
|
+
if (!trimmed.startsWith('```')) return trimmed;
|
|
10
|
+
|
|
11
|
+
const withoutStart = trimmed.replace(/^```[a-zA-Z0-9_-]*\s*/u, '');
|
|
12
|
+
return withoutStart.replace(/```$/u, '').trim();
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const stripOuterQuotes = (input: string): string => {
|
|
16
|
+
const trimmed = input.trim();
|
|
17
|
+
if (trimmed.length < 2) return trimmed;
|
|
18
|
+
|
|
19
|
+
const first = trimmed[0];
|
|
20
|
+
const last = trimmed[trimmed.length - 1];
|
|
21
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
22
|
+
return trimmed.slice(1, -1).trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return trimmed;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const normalizeSmartQuotes = (input: string): string => {
|
|
29
|
+
return input
|
|
30
|
+
.replace(/[“”]/gu, '"')
|
|
31
|
+
.replace(/[‘’]/gu, "'");
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const convertSingleQuotedStrings = (input: string): string => {
|
|
35
|
+
let output = '';
|
|
36
|
+
let inSingle = false;
|
|
37
|
+
let inDouble = false;
|
|
38
|
+
let escaped = false;
|
|
39
|
+
|
|
40
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
41
|
+
const char = input[index];
|
|
42
|
+
|
|
43
|
+
if (inSingle) {
|
|
44
|
+
if (escaped) {
|
|
45
|
+
output += char === '"' ? '\\"' : char;
|
|
46
|
+
escaped = false;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (char === '\\') {
|
|
51
|
+
output += '\\';
|
|
52
|
+
escaped = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (char === "'") {
|
|
57
|
+
output += '"';
|
|
58
|
+
inSingle = false;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
output += char === '"' ? '\\"' : char;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (inDouble) {
|
|
67
|
+
output += char;
|
|
68
|
+
|
|
69
|
+
if (escaped) {
|
|
70
|
+
escaped = false;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (char === '\\') {
|
|
75
|
+
escaped = true;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (char === '"') {
|
|
80
|
+
inDouble = false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (char === "'") {
|
|
87
|
+
output += '"';
|
|
88
|
+
inSingle = true;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (char === '"') {
|
|
93
|
+
output += '"';
|
|
94
|
+
inDouble = true;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
output += char;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return output;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const quoteBareObjectKeys = (input: string): string => {
|
|
105
|
+
return input.replace(
|
|
106
|
+
/([{,]\s*)([A-Za-z_$][A-Za-z0-9_$-]*)(\s*:)/gu,
|
|
107
|
+
'$1"$2"$3',
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const stripTrailingCommas = (input: string): string => {
|
|
112
|
+
return input.replace(/,\s*([}\]])/gu, '$1');
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const normalizeEscapesOutsideStrings = (input: string): string => {
|
|
116
|
+
let output = '';
|
|
117
|
+
let inDouble = false;
|
|
118
|
+
let inSingle = false;
|
|
119
|
+
let escaped = false;
|
|
120
|
+
|
|
121
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
122
|
+
const char = input[index];
|
|
123
|
+
const next = index + 1 < input.length ? input[index + 1] : '';
|
|
124
|
+
|
|
125
|
+
if (inDouble || inSingle) {
|
|
126
|
+
output += char;
|
|
127
|
+
|
|
128
|
+
if (escaped) {
|
|
129
|
+
escaped = false;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (char === '\\') {
|
|
134
|
+
escaped = true;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (inDouble && char === '"') {
|
|
139
|
+
inDouble = false;
|
|
140
|
+
} else if (inSingle && char === "'") {
|
|
141
|
+
inSingle = false;
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (char === '"') {
|
|
147
|
+
inDouble = true;
|
|
148
|
+
output += char;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (char === "'") {
|
|
153
|
+
inSingle = true;
|
|
154
|
+
output += char;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (char === '\\') {
|
|
159
|
+
if (next === 'n') {
|
|
160
|
+
output += '\n';
|
|
161
|
+
index += 1;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (next === 'r') {
|
|
165
|
+
output += '\r';
|
|
166
|
+
index += 1;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (next === 't') {
|
|
170
|
+
output += '\t';
|
|
171
|
+
index += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (next === '"') {
|
|
175
|
+
output += '"';
|
|
176
|
+
index += 1;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (next === "'") {
|
|
180
|
+
output += "'";
|
|
181
|
+
index += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (next === '\\') {
|
|
185
|
+
output += '\\';
|
|
186
|
+
index += 1;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
output += char;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return output;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const normalizeJsLikeJson = (input: string): string => {
|
|
198
|
+
const normalizedQuotes = normalizeSmartQuotes(input);
|
|
199
|
+
const normalizedSingleQuotes = convertSingleQuotedStrings(normalizedQuotes);
|
|
200
|
+
const withQuotedKeys = quoteBareObjectKeys(normalizedSingleQuotes);
|
|
201
|
+
return stripTrailingCommas(withQuotedKeys);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const extractFirstJsonObject = (input: string): string | null => {
|
|
205
|
+
const start = input.indexOf('{');
|
|
206
|
+
if (start === -1) return null;
|
|
207
|
+
|
|
208
|
+
let depth = 0;
|
|
209
|
+
let inString = false;
|
|
210
|
+
let quoteChar = '';
|
|
211
|
+
let escaped = false;
|
|
212
|
+
|
|
213
|
+
for (let index = start; index < input.length; index += 1) {
|
|
214
|
+
const char = input[index];
|
|
215
|
+
|
|
216
|
+
if (inString) {
|
|
217
|
+
if (escaped) {
|
|
218
|
+
escaped = false;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (char === '\\') {
|
|
223
|
+
escaped = true;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (char === quoteChar) {
|
|
228
|
+
inString = false;
|
|
229
|
+
quoteChar = '';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (char === '"' || char === "'") {
|
|
236
|
+
inString = true;
|
|
237
|
+
quoteChar = char;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (char === '{') {
|
|
242
|
+
depth += 1;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (char === '}') {
|
|
247
|
+
depth -= 1;
|
|
248
|
+
if (depth === 0) {
|
|
249
|
+
return input.slice(start, index + 1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return null;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const parseMaybeNestedJson = (input: string): unknown => {
|
|
258
|
+
let current: unknown = input;
|
|
259
|
+
|
|
260
|
+
for (let depth = 0; depth < TOOL_ARGS_CANDIDATE_PARSE_DEPTH; depth += 1) {
|
|
261
|
+
if (typeof current !== 'string') {
|
|
262
|
+
return current;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const trimmed = current.trim();
|
|
266
|
+
if (!trimmed) {
|
|
267
|
+
return {};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
current = JSON.parse(trimmed);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return current;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const addCandidate = (candidates: Set<string>, value: string | null | undefined) => {
|
|
277
|
+
if (typeof value !== 'string') return;
|
|
278
|
+
const trimmed = value.trim();
|
|
279
|
+
if (!trimmed) return;
|
|
280
|
+
candidates.add(trimmed);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const buildProgressiveQuoteUnescapes = (input: string, rounds = 4): string[] => {
|
|
284
|
+
const values: string[] = [];
|
|
285
|
+
let current = input;
|
|
286
|
+
|
|
287
|
+
for (let round = 0; round < rounds; round += 1) {
|
|
288
|
+
const next = current.replace(/\\"/gu, '"');
|
|
289
|
+
if (next === current) {
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
values.push(next);
|
|
293
|
+
current = next;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return values;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export const parseToolArguments = (rawArgs: unknown): Record<string, unknown> | null => {
|
|
300
|
+
if (isPlainObject(rawArgs)) {
|
|
301
|
+
return rawArgs;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (typeof rawArgs !== 'string') {
|
|
305
|
+
return {};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const base = rawArgs.trim();
|
|
309
|
+
if (!base) {
|
|
310
|
+
return {};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const codeFenceStripped = stripMarkdownCodeFence(base);
|
|
314
|
+
const extracted = extractFirstJsonObject(codeFenceStripped);
|
|
315
|
+
|
|
316
|
+
const candidates = new Set<string>();
|
|
317
|
+
addCandidate(candidates, base);
|
|
318
|
+
addCandidate(candidates, codeFenceStripped);
|
|
319
|
+
addCandidate(candidates, stripOuterQuotes(codeFenceStripped));
|
|
320
|
+
buildProgressiveQuoteUnescapes(base).forEach((candidate) => addCandidate(candidates, candidate));
|
|
321
|
+
buildProgressiveQuoteUnescapes(codeFenceStripped).forEach((candidate) =>
|
|
322
|
+
addCandidate(candidates, candidate),
|
|
323
|
+
);
|
|
324
|
+
addCandidate(candidates, extracted);
|
|
325
|
+
buildProgressiveQuoteUnescapes(extracted || '').forEach((candidate) =>
|
|
326
|
+
addCandidate(candidates, candidate),
|
|
327
|
+
);
|
|
328
|
+
addCandidate(candidates, normalizeJsLikeJson(codeFenceStripped));
|
|
329
|
+
addCandidate(candidates, extracted ? normalizeJsLikeJson(extracted) : null);
|
|
330
|
+
Array.from(candidates).forEach((candidate) => {
|
|
331
|
+
addCandidate(candidates, normalizeEscapesOutsideStrings(candidate));
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
for (const candidate of candidates) {
|
|
335
|
+
try {
|
|
336
|
+
const parsed = parseMaybeNestedJson(candidate);
|
|
337
|
+
if (isPlainObject(parsed)) {
|
|
338
|
+
return parsed;
|
|
339
|
+
}
|
|
340
|
+
} catch {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return null;
|
|
346
|
+
};
|