@lincoln504/pi-research 0.0.0
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/LICENSE +21 -0
- package/README.md +131 -0
- package/config/searxng/default-settings.yml +189 -0
- package/config/searxng/limiter.toml +34 -0
- package/config/tooling/eslint.config.js +73 -0
- package/config/tooling/vitest.config.integration.ts +17 -0
- package/config/tooling/vitest.config.ts +40 -0
- package/config/tooling/vitest.config.unit.ts +12 -0
- package/docs/ARCHITECTURE.md +109 -0
- package/docs/assets/README-banner.jpg +0 -0
- package/package.json +92 -0
- package/scripts/setup.js +174 -0
- package/src/config.ts +206 -0
- package/src/constants.ts +148 -0
- package/src/healthcheck/index.ts +309 -0
- package/src/index.ts +71 -0
- package/src/infrastructure/searxng-lifecycle.ts +577 -0
- package/src/infrastructure/searxng-manager.ts +1173 -0
- package/src/infrastructure/state-manager.ts +918 -0
- package/src/logger.ts +246 -0
- package/src/orchestration/deep-research-orchestrator.ts +848 -0
- package/src/orchestration/deep-research-reducer.ts +129 -0
- package/src/orchestration/deep-research-types.ts +54 -0
- package/src/orchestration/id-utils.ts +63 -0
- package/src/orchestration/researcher.ts +104 -0
- package/src/orchestration/session-context.ts +43 -0
- package/src/prompts/research-tool-usage.md +45 -0
- package/src/prompts/researcher.md +166 -0
- package/src/prompts/system-coordinator.md +24 -0
- package/src/prompts/system-lead-evaluator.md +225 -0
- package/src/security/cisa-kev.ts +242 -0
- package/src/security/github-advisories.ts +607 -0
- package/src/security/index.ts +406 -0
- package/src/security/nvd.ts +532 -0
- package/src/security/osv.ts +487 -0
- package/src/security/types.ts +138 -0
- package/src/stackexchange/cache.ts +111 -0
- package/src/stackexchange/index.ts +352 -0
- package/src/stackexchange/output/compact.ts +64 -0
- package/src/stackexchange/output/index.ts +7 -0
- package/src/stackexchange/output/json.ts +21 -0
- package/src/stackexchange/output/table.ts +123 -0
- package/src/stackexchange/queries.ts +168 -0
- package/src/stackexchange/rest-client.ts +127 -0
- package/src/stackexchange/types.ts +129 -0
- package/src/tool.ts +430 -0
- package/src/tools/grep.ts +335 -0
- package/src/tools/index.ts +77 -0
- package/src/tools/scrape.ts +451 -0
- package/src/tools/search.ts +188 -0
- package/src/tools/security.ts +292 -0
- package/src/tools/stackexchange.ts +101 -0
- package/src/tui/research-panel.ts +576 -0
- package/src/tui/searxng-status.ts +80 -0
- package/src/types/extension-context.ts +60 -0
- package/src/types/llm.ts +111 -0
- package/src/utils/inject-date.ts +29 -0
- package/src/utils/input-validation.ts +103 -0
- package/src/utils/json-utils.ts +262 -0
- package/src/utils/make-resource-loader.ts +43 -0
- package/src/utils/research-error.ts +205 -0
- package/src/utils/research-export.ts +199 -0
- package/src/utils/searxng-config.ts +94 -0
- package/src/utils/session-state.ts +295 -0
- package/src/utils/shared-links.ts +123 -0
- package/src/utils/shutdown-manager.ts +54 -0
- package/src/utils/text-utils.ts +83 -0
- package/src/utils/tool-usage-tracker.ts +135 -0
- package/src/web-research/dependencies.ts +20 -0
- package/src/web-research/retry-utils.ts +232 -0
- package/src/web-research/scrapers.ts +935 -0
- package/src/web-research/search.ts +235 -0
- package/src/web-research/types.ts +47 -0
- package/src/web-research/utils.ts +158 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rg_grep Tool (Ripgrep)
|
|
3
|
+
*
|
|
4
|
+
* Standalone grep tool using ripgrep (rg) or fallback to grep.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import * as nodePath from 'node:path';
|
|
9
|
+
import type { ToolDefinition, AgentToolResult, ExtensionContext } from '@mariozechner/pi-coding-agent';
|
|
10
|
+
import { Type } from '@sinclair/typebox';
|
|
11
|
+
import type { ToolUsageTracker } from '../utils/tool-usage-tracker.ts';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_MAX_BYTES = 100 * 1024; // 100KB
|
|
14
|
+
const DEFAULT_MAX_LINES = 200;
|
|
15
|
+
const STREAM_MAX_BYTES = 10 * 1024 * 1024; // 10MB max in stream (prevent RangeError)
|
|
16
|
+
|
|
17
|
+
interface RgGrepParams {
|
|
18
|
+
pattern: string;
|
|
19
|
+
path?: string;
|
|
20
|
+
flags?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function truncateHead(content: string, maxBytes: number, maxLines: number): string {
|
|
24
|
+
const lines = content.split('\n');
|
|
25
|
+
|
|
26
|
+
if (lines.length <= maxLines && Buffer.byteLength(content, 'utf-8') <= maxBytes) {
|
|
27
|
+
return content;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const truncatedLines = lines.slice(0, maxLines);
|
|
31
|
+
let result = truncatedLines.join('\n');
|
|
32
|
+
|
|
33
|
+
if (Buffer.byteLength(result, 'utf-8') > maxBytes) {
|
|
34
|
+
let byteCount = 0;
|
|
35
|
+
const charArray: string[] = [];
|
|
36
|
+
|
|
37
|
+
for (const char of result) {
|
|
38
|
+
byteCount += Buffer.byteLength(char, 'utf-8');
|
|
39
|
+
if (byteCount > maxBytes) break;
|
|
40
|
+
charArray.push(char);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
result = charArray.join('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function execCommand(
|
|
50
|
+
command: string,
|
|
51
|
+
args: string[],
|
|
52
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number | null; wasTruncated: boolean }> {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const child = spawn(command, args);
|
|
55
|
+
let stdout = '';
|
|
56
|
+
let stderr = '';
|
|
57
|
+
let wasTruncated = false;
|
|
58
|
+
|
|
59
|
+
child.stdout?.on('data', (data) => {
|
|
60
|
+
// Check if adding this chunk would exceed stream limit
|
|
61
|
+
const chunk = data.toString();
|
|
62
|
+
if (stdout.length + chunk.length > STREAM_MAX_BYTES) {
|
|
63
|
+
// Stop collecting data to prevent RangeError
|
|
64
|
+
wasTruncated = true;
|
|
65
|
+
child.kill();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
stdout += chunk;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
child.stderr?.on('data', (data) => {
|
|
72
|
+
stderr += data.toString();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
child.on('close', (code) => {
|
|
76
|
+
resolve({ stdout, stderr, exitCode: code, wasTruncated });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
child.on('error', (error) => {
|
|
80
|
+
reject(error);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function resolveWorkspacePath(input: string): string {
|
|
86
|
+
const root = process.cwd();
|
|
87
|
+
const resolved = nodePath.resolve(root, input);
|
|
88
|
+
if (resolved !== root && !resolved.startsWith(root + nodePath.sep)) {
|
|
89
|
+
throw new Error(`Path outside workspace: ${input}`);
|
|
90
|
+
}
|
|
91
|
+
return resolved;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sanitizeInput(input: string): string[] {
|
|
95
|
+
// Simple sanitization: split on whitespace, preserve quoted strings
|
|
96
|
+
const result: string[] = [];
|
|
97
|
+
let current = '';
|
|
98
|
+
let inQuotes = false;
|
|
99
|
+
|
|
100
|
+
for (const char of input) {
|
|
101
|
+
if (char === '"' || char === "'") {
|
|
102
|
+
inQuotes = !inQuotes;
|
|
103
|
+
} else if (char === ' ' && !inQuotes) {
|
|
104
|
+
if (current) {
|
|
105
|
+
result.push(current);
|
|
106
|
+
current = '';
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
current += char;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (current) {
|
|
114
|
+
result.push(current);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeSearchOutput(output: string): string {
|
|
121
|
+
return process.platform === 'win32' ? output.replace(/\\/g, '/') : output;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createGrepTool(options: {
|
|
125
|
+
tracker: ToolUsageTracker;
|
|
126
|
+
}): ToolDefinition {
|
|
127
|
+
return {
|
|
128
|
+
name: 'grep',
|
|
129
|
+
label: 'Code Search',
|
|
130
|
+
description: 'Search codebase using ripgrep (rg) or grep fallback. Fast recursive text search.',
|
|
131
|
+
promptSnippet: 'Search codebase using ripgrep/grep',
|
|
132
|
+
promptGuidelines: [
|
|
133
|
+
'Use grep for fast recursive text search in codebases.',
|
|
134
|
+
'Pattern supports regex. Path and flags are optional.',
|
|
135
|
+
'Falls back to grep if rg is not available.',
|
|
136
|
+
'CRITICAL: You are allowed a maximum of 4 gathering calls total across ALL tools. Use them for breadth.',
|
|
137
|
+
],
|
|
138
|
+
parameters: Type.Object({
|
|
139
|
+
pattern: Type.String({ description: 'Search pattern (regex supported)' }),
|
|
140
|
+
path: Type.Optional(Type.String({ description: 'Directory to search (default: current directory)' })),
|
|
141
|
+
flags: Type.Optional(Type.String({ description: 'Additional flags (e.g., "-i" for case-insensitive)' })),
|
|
142
|
+
}),
|
|
143
|
+
async execute(
|
|
144
|
+
_toolCallId: string,
|
|
145
|
+
params: unknown,
|
|
146
|
+
_signal: unknown,
|
|
147
|
+
_onUpdate: unknown,
|
|
148
|
+
_ctx: ExtensionContext,
|
|
149
|
+
): Promise<AgentToolResult<unknown>> {
|
|
150
|
+
// Record call in tracker - returns false if limit reached
|
|
151
|
+
const allowed = options.tracker.recordCall('grep');
|
|
152
|
+
if (!allowed) {
|
|
153
|
+
// THROW to prevent researcher from calling again
|
|
154
|
+
throw new Error(options.tracker.getLimitMessage('grep'));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const record = params as RgGrepParams;
|
|
158
|
+
const { pattern } = record;
|
|
159
|
+
const path = resolveWorkspacePath(record.path ?? '.');
|
|
160
|
+
const flags = record.flags ?? '';
|
|
161
|
+
|
|
162
|
+
if (!pattern) {
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: 'text', text: 'Error: pattern is required' }],
|
|
165
|
+
details: {},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Sanitize inputs to prevent shell injection
|
|
170
|
+
const flagParts = flags ? sanitizeInput(flags) : [];
|
|
171
|
+
|
|
172
|
+
// Try rg first
|
|
173
|
+
try {
|
|
174
|
+
const rgArgs = ['--no-heading', '-n', ...flagParts, pattern, path];
|
|
175
|
+
const { stdout, stderr, exitCode, wasTruncated } = await execCommand('rg', rgArgs);
|
|
176
|
+
|
|
177
|
+
const truncated = truncateHead(normalizeSearchOutput(stdout), DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES);
|
|
178
|
+
|
|
179
|
+
let markdown = '# Search Results (rg)\n\n';
|
|
180
|
+
markdown += `**Pattern:** \`${pattern}\`\n`;
|
|
181
|
+
markdown += `**Path:** \`${path}\`\n`;
|
|
182
|
+
if (flags) markdown += `**Flags:** \`${flags}\`\n`;
|
|
183
|
+
markdown += `**Exit Code:** ${exitCode}\n\n`;
|
|
184
|
+
|
|
185
|
+
if (wasTruncated) {
|
|
186
|
+
markdown += '⚠️ **Output was truncated to prevent memory overflow (>10MB)**\n\n';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (truncated) {
|
|
190
|
+
markdown += '```\n' + truncated + '\n```';
|
|
191
|
+
} else {
|
|
192
|
+
markdown += 'No matches found.';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (stderr) {
|
|
196
|
+
markdown += '\n\n**Stderr:**\n```\n' + stderr + '\n```';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: 'text', text: markdown }],
|
|
201
|
+
details: {
|
|
202
|
+
command: 'rg',
|
|
203
|
+
args: rgArgs,
|
|
204
|
+
exitCode,
|
|
205
|
+
wasTruncated,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
} catch (rgError) {
|
|
209
|
+
// rg not found, try grep
|
|
210
|
+
try {
|
|
211
|
+
const grepArgs = ['-rn', ...flagParts, pattern, path];
|
|
212
|
+
const { stdout, stderr, exitCode, wasTruncated } = await execCommand('grep', grepArgs);
|
|
213
|
+
|
|
214
|
+
const truncated = truncateHead(normalizeSearchOutput(stdout), DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES);
|
|
215
|
+
|
|
216
|
+
let markdown = '# Search Results (grep)\n\n';
|
|
217
|
+
markdown += `**Pattern:** \`${pattern}\`\n`;
|
|
218
|
+
markdown += `**Path:** \`${path}\`\n`;
|
|
219
|
+
if (flags) markdown += `**Flags:** \`${flags}\`\n`;
|
|
220
|
+
markdown += `**Exit Code:** ${exitCode}\n\n`;
|
|
221
|
+
markdown += '*Note: Using grep fallback (rg not available)*\n\n';
|
|
222
|
+
|
|
223
|
+
if (wasTruncated) {
|
|
224
|
+
markdown += '⚠️ **Output was truncated to prevent memory overflow (>10MB)**\n\n';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (truncated) {
|
|
228
|
+
markdown += '```\n' + truncated + '\n```';
|
|
229
|
+
} else {
|
|
230
|
+
markdown += 'No matches found.';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (stderr) {
|
|
234
|
+
markdown += '\n\n**Stderr:**\n```\n' + stderr + '\n```';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
content: [{ type: 'text', text: markdown }],
|
|
239
|
+
details: {
|
|
240
|
+
command: 'grep',
|
|
241
|
+
args: grepArgs,
|
|
242
|
+
exitCode,
|
|
243
|
+
wasTruncated,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
} catch (grepError) {
|
|
247
|
+
return {
|
|
248
|
+
content: [
|
|
249
|
+
{
|
|
250
|
+
type: 'text',
|
|
251
|
+
text: `Error: Neither rg nor grep is available.\n\nrg error: ${rgError}\ngrep error: ${grepError}`,
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
details: {},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Standalone function to execute rg/grep search
|
|
264
|
+
*/
|
|
265
|
+
export async function grep(pattern: string, path: string = '.', flags: string = ''): Promise<string> {
|
|
266
|
+
if (!pattern) {
|
|
267
|
+
throw new Error('Pattern is required');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const resolvedPath = resolveWorkspacePath(path);
|
|
271
|
+
const flagParts = flags ? sanitizeInput(flags) : [];
|
|
272
|
+
|
|
273
|
+
// Try rg first
|
|
274
|
+
try {
|
|
275
|
+
const rgArgs = ['--no-heading', '-n', ...flagParts, pattern, resolvedPath];
|
|
276
|
+
const { stdout, stderr, exitCode, wasTruncated } = await execCommand('rg', rgArgs);
|
|
277
|
+
|
|
278
|
+
let markdown = '# Search Results (rg)\n\n';
|
|
279
|
+
markdown += `**Pattern:** \`${pattern}\`\n`;
|
|
280
|
+
markdown += `**Path:** \`${resolvedPath}\`\n`;
|
|
281
|
+
if (flags) markdown += `**Flags:** \`${flags}\`\n`;
|
|
282
|
+
markdown += `**Exit Code:** ${exitCode}\n\n`;
|
|
283
|
+
|
|
284
|
+
const truncated = truncateHead(normalizeSearchOutput(stdout), DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES);
|
|
285
|
+
|
|
286
|
+
if (wasTruncated) {
|
|
287
|
+
markdown += '⚠️ **Output was truncated to prevent memory overflow (>10MB)**\n\n';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (truncated) {
|
|
291
|
+
markdown += '```\n' + truncated + '\n```';
|
|
292
|
+
} else {
|
|
293
|
+
markdown += 'No matches found.';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (stderr) {
|
|
297
|
+
markdown += '\n\n**Stderr:**\n```\n' + stderr + '\n```';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return markdown;
|
|
301
|
+
} catch (rgError) {
|
|
302
|
+
// rg not found, try grep
|
|
303
|
+
try {
|
|
304
|
+
const grepArgs = ['-rn', ...flagParts, pattern, resolvedPath];
|
|
305
|
+
const { stdout, stderr, exitCode, wasTruncated } = await execCommand('grep', grepArgs);
|
|
306
|
+
|
|
307
|
+
let markdown = '# Search Results (grep)\n\n';
|
|
308
|
+
markdown += `**Pattern:** \`${pattern}\`\n`;
|
|
309
|
+
markdown += `**Path:** \`${resolvedPath}\`\n`;
|
|
310
|
+
if (flags) markdown += `**Flags:** \`${flags}\`\n`;
|
|
311
|
+
markdown += `**Exit Code:** ${exitCode}\n\n`;
|
|
312
|
+
markdown += '*Note: Using grep fallback (rg not available)*\n\n';
|
|
313
|
+
|
|
314
|
+
const truncated = truncateHead(normalizeSearchOutput(stdout), DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES);
|
|
315
|
+
|
|
316
|
+
if (wasTruncated) {
|
|
317
|
+
markdown += '⚠️ **Output was truncated to prevent memory overflow (>10MB)**\n\n';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (truncated) {
|
|
321
|
+
markdown += '```\n' + truncated + '\n```';
|
|
322
|
+
} else {
|
|
323
|
+
markdown += 'No matches found.';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (stderr) {
|
|
327
|
+
markdown += '\n\n**Stderr:**\n```\n' + stderr + '\n```';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return markdown;
|
|
331
|
+
} catch (grepError) {
|
|
332
|
+
return `Error: Neither rg nor grep is available.\n\nrg error: ${rgError}\ngrep error: ${grepError}`;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates all research tools with properly configured SearXNG URL.
|
|
5
|
+
* These tools are used by both direct tool calls and researcher agents.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ToolDefinition, ExtensionContext } from '@mariozechner/pi-coding-agent';
|
|
9
|
+
import type { ToolUsageTracker } from '../utils/tool-usage-tracker.ts';
|
|
10
|
+
import type { SystemResearchState } from '../orchestration/deep-research-types.ts';
|
|
11
|
+
import { createSearchTool } from './search.ts';
|
|
12
|
+
import { createScrapeTool } from './scrape.ts';
|
|
13
|
+
import { createSecuritySearchTool } from './security.ts';
|
|
14
|
+
import { createStackexchangeTool } from './stackexchange.ts';
|
|
15
|
+
import { createGrepTool } from './grep.ts';
|
|
16
|
+
|
|
17
|
+
interface CreateToolsOptions {
|
|
18
|
+
searxngUrl: string;
|
|
19
|
+
ctx: ExtensionContext;
|
|
20
|
+
tracker: ToolUsageTracker;
|
|
21
|
+
getGlobalState?: () => SystemResearchState;
|
|
22
|
+
updateGlobalLinks?: (links: string[]) => void;
|
|
23
|
+
/** Returns tokens consumed by this researcher session so far (for context-aware scrape gating). */
|
|
24
|
+
getTokensUsed?: () => number;
|
|
25
|
+
/** Model context window size in tokens; defaults to DEFAULT_MODEL_CONTEXT_WINDOW. */
|
|
26
|
+
contextWindowSize?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create all research tools
|
|
31
|
+
*/
|
|
32
|
+
export function createResearchTools(options: CreateToolsOptions): ToolDefinition[] {
|
|
33
|
+
// Create a fresh fallback state per tool instance to avoid state leakage between research runs
|
|
34
|
+
// This is only used when getGlobalState is not provided (e.g., quick mode with single researcher)
|
|
35
|
+
const createFallbackState = (): SystemResearchState => ({
|
|
36
|
+
version: 1,
|
|
37
|
+
rootQuery: '',
|
|
38
|
+
complexity: 1,
|
|
39
|
+
currentRound: 1,
|
|
40
|
+
status: 'researching',
|
|
41
|
+
lastUpdated: Date.now(),
|
|
42
|
+
initialAgenda: [],
|
|
43
|
+
allScrapedLinks: [],
|
|
44
|
+
aspects: {},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const fallbackState = createFallbackState();
|
|
48
|
+
|
|
49
|
+
const resolvedOptions = {
|
|
50
|
+
...options,
|
|
51
|
+
getGlobalState: options.getGlobalState ?? (() => fallbackState),
|
|
52
|
+
updateGlobalLinks: options.updateGlobalLinks ?? ((links: string[]) => {
|
|
53
|
+
fallbackState.allScrapedLinks = [...new Set([...fallbackState.allScrapedLinks, ...links])];
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return [
|
|
58
|
+
createSearchTool(resolvedOptions),
|
|
59
|
+
createScrapeTool({
|
|
60
|
+
...resolvedOptions,
|
|
61
|
+
getTokensUsed: options.getTokensUsed,
|
|
62
|
+
contextWindowSize: options.contextWindowSize,
|
|
63
|
+
}),
|
|
64
|
+
createSecuritySearchTool(resolvedOptions),
|
|
65
|
+
createStackexchangeTool(resolvedOptions),
|
|
66
|
+
createGrepTool(resolvedOptions),
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create individual tool exports for direct registration
|
|
72
|
+
*/
|
|
73
|
+
export { createSearchTool } from './search.ts';
|
|
74
|
+
export { createScrapeTool } from './scrape.ts';
|
|
75
|
+
export { createSecuritySearchTool } from './security.ts';
|
|
76
|
+
export { createStackexchangeTool } from './stackexchange.ts';
|
|
77
|
+
export { createGrepTool } from './grep.ts';
|