]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/blob
69c95210f32cac2a8385eb14a00914470a3a90a5
[pkg/ggml/sources/llama.cpp] /
1 // Meta parser for `grep_search` tool calls. Reads the path/pattern
2 // triplet from args (strict parsing - we wait for the args to
3 // complete) and the matches from the result blob. The result parser
4 // keeps the original "scan result as raw text on JSON.parse failure"
5 // fallback so MCP servers that return unparseable output still get
6 // surfaced.
7
8 import { parseToolArgs } from './_shared';
9 import { BuiltInTool } from '$lib/enums';
10 import { type AgenticSection, splitSearchSummaryList } from '$lib/utils';
11
12 export type GrepSearchMatch = {
13 file: string;
14 line?: number;
15 content: string;
16 };
17
18 export type GrepSearchMeta = {
19 path: string;
20 pattern: string;
21 include: string;
22 exclude?: string;
23 showLineNumbers: boolean;
24 matches: GrepSearchMatch[];
25 totalMatches?: number;
26 errorMessage?: string;
27 };
28
29 export function parseGrepSearchMeta(section: AgenticSection): GrepSearchMeta | null {
30 const args = parseToolArgs(BuiltInTool.GREP_SEARCH, section);
31
32 if (!args) return null;
33
34 const path = typeof args.path === 'string' ? args.path : '';
35 const pattern = typeof args.pattern === 'string' ? args.pattern : '';
36
37 if (!path || !pattern) return null;
38
39 const include = typeof args.include === 'string' && args.include ? args.include : '**';
40 const exclude = typeof args.exclude === 'string' && args.exclude ? args.exclude : undefined;
41 const showLineNumbers = args.return_line_numbers === true;
42
43 let matches: GrepSearchMatch[] = [];
44 let totalMatches: number | undefined;
45 let errorMessage: string | undefined;
46
47 const toolResultString = section.toolResult;
48
49 if (toolResultString) {
50 try {
51 const parsed: unknown = JSON.parse(toolResultString);
52
53 if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
54 const obj = parsed as Record<string, unknown>;
55
56 if (typeof obj.error === 'string') {
57 errorMessage = obj.error;
58 } else if (typeof obj.plain_text_response === 'string') {
59 const split = splitSearchSummaryList(obj.plain_text_response, (total) => {
60 totalMatches = total;
61 });
62
63 matches = split.lines.map((line) => parseGrepLine(line, showLineNumbers));
64 }
65 }
66 } catch {
67 // Result wasn't JSON: keep behaviour for MCP servers that
68 // emit raw text and treat each line as a `<file>:<content>`
69 // (or `<file>:<line>:<content>`) match.
70 const split = splitSearchSummaryList(toolResultString, (total) => {
71 totalMatches = total;
72 });
73
74 matches = split.lines.map((line) => parseGrepLine(line, showLineNumbers));
75 }
76 }
77
78 return {
79 errorMessage,
80 exclude,
81 include,
82 matches,
83 path,
84 pattern,
85 showLineNumbers,
86 totalMatches
87 };
88 }
89
90 function parseGrepLine(line: string, showLineNumbers: boolean): GrepSearchMatch {
91 // Server output:
92 // <file>:<content> when return_line_numbers=false
93 // <file>:<lineno>:<content> when return_line_numbers=true
94 const firstColon = line.indexOf(':');
95
96 if (firstColon === -1) {
97 return { content: '', file: line };
98 }
99
100 const file = line.slice(0, firstColon);
101 const tail = line.slice(firstColon + 1);
102
103 if (!showLineNumbers) {
104 return { content: tail, file };
105 }
106
107 const secondColon = tail.indexOf(':');
108
109 if (secondColon === -1) {
110 return { content: tail, file };
111 }
112
113 const lineNum = parseInt(tail.slice(0, secondColon), 10);
114
115 return {
116 content: tail.slice(secondColon + 1),
117 file,
118 line: Number.isFinite(lineNum) ? lineNum : undefined
119 };
120 }