Skip to content

Commit 2dca14c

Browse files
Fix zod array parsing issue
1 parent a70a5dc commit 2dca14c

2 files changed

Lines changed: 80 additions & 2 deletions

File tree

packages/server/api/src/app/ai/mcp/llm-query-router.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,15 @@ function findFirstKeyInObject(
244244
* Attempts to repair a malformed JSON string by extracting the expected schema fields.
245245
* Returns null if the input cannot be parsed or repaired, so then ai sdk will throw an error.
246246
*/
247-
const repairText = (text: string): string | null => {
247+
export const repairText = (text: string): string | null => {
248248
try {
249249
const parsedText = JSON.parse(text);
250250

251+
const rawToolNames = findFirstKeyInObject(parsedText, 'tool_names');
252+
const toolNames = normalizeToolNames(rawToolNames);
253+
251254
return JSON.stringify({
252-
tool_names: findFirstKeyInObject(parsedText, 'tool_names') || [],
255+
tool_names: toolNames,
253256
query_classification:
254257
findFirstKeyInObject(parsedText, 'query_classification') || [],
255258
reasoning: findFirstKeyInObject(parsedText, 'reasoning') || '',
@@ -260,3 +263,18 @@ const repairText = (text: string): string | null => {
260263
return null;
261264
}
262265
};
266+
267+
export const normalizeToolNames = (value: unknown): string[] => {
268+
if (Array.isArray(value)) {
269+
return value;
270+
}
271+
272+
if (typeof value === 'string') {
273+
return value
274+
.split(',')
275+
.map((name) => name.trim())
276+
.filter((name) => name.length > 0);
277+
}
278+
279+
return [];
280+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
normalizeToolNames,
3+
repairText,
4+
} from '../../../src/app/ai/mcp/llm-query-router';
5+
6+
describe('normalizeToolNames', () => {
7+
it('should return array as-is when input is already an array', () => {
8+
expect(normalizeToolNames(['Tool1', 'Tool2'])).toEqual(['Tool1', 'Tool2']);
9+
});
10+
11+
it('should split comma-separated string into array and trim whitespace', () => {
12+
expect(normalizeToolNames('Tool1, Tool2, Tool3')).toEqual([
13+
'Tool1',
14+
'Tool2',
15+
'Tool3',
16+
]);
17+
});
18+
19+
it('should return empty array for invalid input', () => {
20+
expect(normalizeToolNames(null)).toEqual([]);
21+
expect(normalizeToolNames(undefined)).toEqual([]);
22+
});
23+
});
24+
25+
describe('repairText', () => {
26+
it('should handle tool_names as comma-separated string (bug fix)', () => {
27+
const input = JSON.stringify({
28+
tool_names: 'Get_Run_Details, Get_Latest_Flow_Version_By_Id',
29+
query_classification: ['openops', 'run_investigation'],
30+
reasoning: 'Some reasoning',
31+
user_facing_reasoning: 'User facing reasoning',
32+
});
33+
34+
const result = repairText(input);
35+
const parsed = JSON.parse(result || '{}');
36+
37+
expect(parsed.tool_names).toEqual([
38+
'Get_Run_Details',
39+
'Get_Latest_Flow_Version_By_Id',
40+
]);
41+
});
42+
43+
it('should handle tool_names as an array (correct format)', () => {
44+
const input = JSON.stringify({
45+
tool_names: ['Tool1', 'Tool2'],
46+
query_classification: ['general'],
47+
reasoning: 'Some reasoning',
48+
user_facing_reasoning: 'User facing reasoning',
49+
});
50+
51+
const result = repairText(input);
52+
const parsed = JSON.parse(result || '{}');
53+
54+
expect(parsed.tool_names).toEqual(['Tool1', 'Tool2']);
55+
});
56+
57+
it('should return null for invalid JSON', () => {
58+
expect(repairText('not valid json')).toBeNull();
59+
});
60+
});

0 commit comments

Comments
 (0)