@compilr-dev/agents 0.0.1
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/README.md +1277 -0
- package/dist/agent.d.ts +1272 -0
- package/dist/agent.js +1912 -0
- package/dist/anchors/builtin.d.ts +24 -0
- package/dist/anchors/builtin.js +61 -0
- package/dist/anchors/index.d.ts +6 -0
- package/dist/anchors/index.js +5 -0
- package/dist/anchors/manager.d.ts +115 -0
- package/dist/anchors/manager.js +412 -0
- package/dist/anchors/types.d.ts +168 -0
- package/dist/anchors/types.js +10 -0
- package/dist/context/index.d.ts +12 -0
- package/dist/context/index.js +10 -0
- package/dist/context/manager.d.ts +224 -0
- package/dist/context/manager.js +770 -0
- package/dist/context/types.d.ts +377 -0
- package/dist/context/types.js +7 -0
- package/dist/costs/index.d.ts +8 -0
- package/dist/costs/index.js +7 -0
- package/dist/costs/tracker.d.ts +121 -0
- package/dist/costs/tracker.js +295 -0
- package/dist/costs/types.d.ts +157 -0
- package/dist/costs/types.js +8 -0
- package/dist/errors.d.ts +178 -0
- package/dist/errors.js +249 -0
- package/dist/guardrails/builtin.d.ts +27 -0
- package/dist/guardrails/builtin.js +223 -0
- package/dist/guardrails/index.d.ts +6 -0
- package/dist/guardrails/index.js +5 -0
- package/dist/guardrails/manager.d.ts +117 -0
- package/dist/guardrails/manager.js +288 -0
- package/dist/guardrails/types.d.ts +159 -0
- package/dist/guardrails/types.js +7 -0
- package/dist/hooks/index.d.ts +31 -0
- package/dist/hooks/index.js +29 -0
- package/dist/hooks/manager.d.ts +147 -0
- package/dist/hooks/manager.js +600 -0
- package/dist/hooks/types.d.ts +368 -0
- package/dist/hooks/types.js +12 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +73 -0
- package/dist/mcp/client.d.ts +93 -0
- package/dist/mcp/client.js +287 -0
- package/dist/mcp/errors.d.ts +60 -0
- package/dist/mcp/errors.js +78 -0
- package/dist/mcp/index.d.ts +43 -0
- package/dist/mcp/index.js +45 -0
- package/dist/mcp/manager.d.ts +120 -0
- package/dist/mcp/manager.js +276 -0
- package/dist/mcp/tools.d.ts +54 -0
- package/dist/mcp/tools.js +99 -0
- package/dist/mcp/types.d.ts +150 -0
- package/dist/mcp/types.js +40 -0
- package/dist/memory/index.d.ts +8 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/loader.d.ts +114 -0
- package/dist/memory/loader.js +463 -0
- package/dist/memory/types.d.ts +182 -0
- package/dist/memory/types.js +8 -0
- package/dist/messages/index.d.ts +82 -0
- package/dist/messages/index.js +155 -0
- package/dist/permissions/index.d.ts +5 -0
- package/dist/permissions/index.js +4 -0
- package/dist/permissions/manager.d.ts +125 -0
- package/dist/permissions/manager.js +379 -0
- package/dist/permissions/types.d.ts +162 -0
- package/dist/permissions/types.js +7 -0
- package/dist/providers/claude.d.ts +90 -0
- package/dist/providers/claude.js +348 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.js +11 -0
- package/dist/providers/mock.d.ts +133 -0
- package/dist/providers/mock.js +204 -0
- package/dist/providers/types.d.ts +168 -0
- package/dist/providers/types.js +4 -0
- package/dist/rate-limit/index.d.ts +45 -0
- package/dist/rate-limit/index.js +47 -0
- package/dist/rate-limit/limiter.d.ts +104 -0
- package/dist/rate-limit/limiter.js +326 -0
- package/dist/rate-limit/provider-wrapper.d.ts +112 -0
- package/dist/rate-limit/provider-wrapper.js +201 -0
- package/dist/rate-limit/retry.d.ts +108 -0
- package/dist/rate-limit/retry.js +287 -0
- package/dist/rate-limit/types.d.ts +181 -0
- package/dist/rate-limit/types.js +22 -0
- package/dist/rehearsal/file-analyzer.d.ts +22 -0
- package/dist/rehearsal/file-analyzer.js +351 -0
- package/dist/rehearsal/git-analyzer.d.ts +22 -0
- package/dist/rehearsal/git-analyzer.js +472 -0
- package/dist/rehearsal/index.d.ts +35 -0
- package/dist/rehearsal/index.js +36 -0
- package/dist/rehearsal/manager.d.ts +100 -0
- package/dist/rehearsal/manager.js +290 -0
- package/dist/rehearsal/types.d.ts +235 -0
- package/dist/rehearsal/types.js +8 -0
- package/dist/skills/index.d.ts +160 -0
- package/dist/skills/index.js +282 -0
- package/dist/state/agent-state.d.ts +41 -0
- package/dist/state/agent-state.js +88 -0
- package/dist/state/checkpointer.d.ts +110 -0
- package/dist/state/checkpointer.js +362 -0
- package/dist/state/errors.d.ts +66 -0
- package/dist/state/errors.js +88 -0
- package/dist/state/index.d.ts +35 -0
- package/dist/state/index.js +37 -0
- package/dist/state/serializer.d.ts +55 -0
- package/dist/state/serializer.js +172 -0
- package/dist/state/types.d.ts +312 -0
- package/dist/state/types.js +14 -0
- package/dist/tools/builtin/bash-output.d.ts +61 -0
- package/dist/tools/builtin/bash-output.js +90 -0
- package/dist/tools/builtin/bash.d.ts +150 -0
- package/dist/tools/builtin/bash.js +354 -0
- package/dist/tools/builtin/edit.d.ts +50 -0
- package/dist/tools/builtin/edit.js +215 -0
- package/dist/tools/builtin/glob.d.ts +62 -0
- package/dist/tools/builtin/glob.js +244 -0
- package/dist/tools/builtin/grep.d.ts +74 -0
- package/dist/tools/builtin/grep.js +363 -0
- package/dist/tools/builtin/index.d.ts +44 -0
- package/dist/tools/builtin/index.js +69 -0
- package/dist/tools/builtin/kill-shell.d.ts +44 -0
- package/dist/tools/builtin/kill-shell.js +80 -0
- package/dist/tools/builtin/read-file.d.ts +57 -0
- package/dist/tools/builtin/read-file.js +184 -0
- package/dist/tools/builtin/shell-manager.d.ts +176 -0
- package/dist/tools/builtin/shell-manager.js +337 -0
- package/dist/tools/builtin/task.d.ts +202 -0
- package/dist/tools/builtin/task.js +350 -0
- package/dist/tools/builtin/todo.d.ts +207 -0
- package/dist/tools/builtin/todo.js +453 -0
- package/dist/tools/builtin/utils.d.ts +27 -0
- package/dist/tools/builtin/utils.js +70 -0
- package/dist/tools/builtin/web-fetch.d.ts +96 -0
- package/dist/tools/builtin/web-fetch.js +290 -0
- package/dist/tools/builtin/write-file.d.ts +54 -0
- package/dist/tools/builtin/write-file.js +147 -0
- package/dist/tools/define.d.ts +60 -0
- package/dist/tools/define.js +65 -0
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.js +37 -0
- package/dist/tools/registry.d.ts +79 -0
- package/dist/tools/registry.js +151 -0
- package/dist/tools/types.d.ts +59 -0
- package/dist/tools/types.js +4 -0
- package/dist/tracing/hooks.d.ts +58 -0
- package/dist/tracing/hooks.js +377 -0
- package/dist/tracing/index.d.ts +51 -0
- package/dist/tracing/index.js +55 -0
- package/dist/tracing/logging.d.ts +78 -0
- package/dist/tracing/logging.js +310 -0
- package/dist/tracing/manager.d.ts +160 -0
- package/dist/tracing/manager.js +468 -0
- package/dist/tracing/otel.d.ts +102 -0
- package/dist/tracing/otel.js +246 -0
- package/dist/tracing/types.d.ts +346 -0
- package/dist/tracing/types.js +38 -0
- package/dist/utils/index.d.ts +23 -0
- package/dist/utils/index.js +44 -0
- package/package.json +79 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for builtin tools
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Type guard for Node.js errors with code property
|
|
6
|
+
*/
|
|
7
|
+
export function isNodeError(error) {
|
|
8
|
+
return error instanceof Error && 'code' in error;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get the file extension from a path, including the leading dot.
|
|
12
|
+
* Returns empty string if no extension found.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* getExtension('/path/to/file.ts') // returns '.ts'
|
|
16
|
+
* getExtension('/path/to/file') // returns ''
|
|
17
|
+
* getExtension('/path/to/.gitignore') // returns '' (dotfile, not extension)
|
|
18
|
+
*/
|
|
19
|
+
export function getExtension(filePath) {
|
|
20
|
+
const fileName = filePath.split('/').pop() ?? '';
|
|
21
|
+
const lastDot = fileName.lastIndexOf('.');
|
|
22
|
+
// No dot, or dot is at start (dotfile like .gitignore)
|
|
23
|
+
if (lastDot <= 0) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
return fileName.substring(lastDot).toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Check if a file extension is in the allowed list.
|
|
30
|
+
* Handles both formats: ['.ts', '.js'] or ['ts', 'js']
|
|
31
|
+
*/
|
|
32
|
+
export function isExtensionAllowed(filePath, allowedExtensions) {
|
|
33
|
+
const ext = getExtension(filePath);
|
|
34
|
+
// No extension - not allowed if there are restrictions
|
|
35
|
+
if (!ext) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
// Check both with and without the dot for flexibility
|
|
39
|
+
const extWithoutDot = ext.substring(1);
|
|
40
|
+
return allowedExtensions.some((allowed) => allowed === ext || allowed === extWithoutDot);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Write a file atomically by writing to a temp file first, then renaming.
|
|
44
|
+
* This prevents file corruption if the write is interrupted.
|
|
45
|
+
*/
|
|
46
|
+
export async function atomicWriteFile(filePath, content, encoding = 'utf-8') {
|
|
47
|
+
const fs = await import('node:fs/promises');
|
|
48
|
+
const path = await import('node:path');
|
|
49
|
+
const crypto = await import('node:crypto');
|
|
50
|
+
// Create temp file in same directory (ensures same filesystem for atomic rename)
|
|
51
|
+
const dir = path.dirname(filePath);
|
|
52
|
+
const tempName = `.${path.basename(filePath)}.${crypto.randomBytes(6).toString('hex')}.tmp`;
|
|
53
|
+
const tempPath = dir === '.' ? tempName : `${dir}/${tempName}`;
|
|
54
|
+
try {
|
|
55
|
+
// Write to temp file
|
|
56
|
+
await fs.writeFile(tempPath, content, { encoding });
|
|
57
|
+
// Atomic rename
|
|
58
|
+
await fs.rename(tempPath, filePath);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// Clean up temp file on failure
|
|
62
|
+
try {
|
|
63
|
+
await fs.unlink(tempPath);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Ignore cleanup errors
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebFetch Tool - Fetch and process web content
|
|
3
|
+
*
|
|
4
|
+
* Fetches content from a URL and optionally processes it with an LLM.
|
|
5
|
+
*/
|
|
6
|
+
import type { Tool } from '../types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Input parameters for webFetch tool
|
|
9
|
+
*/
|
|
10
|
+
export interface WebFetchInput {
|
|
11
|
+
/**
|
|
12
|
+
* The URL to fetch content from
|
|
13
|
+
*/
|
|
14
|
+
url: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional prompt to process the content with
|
|
17
|
+
* If provided, the content will be processed and summarized
|
|
18
|
+
*/
|
|
19
|
+
prompt?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Request timeout in milliseconds (default: 30000)
|
|
22
|
+
*/
|
|
23
|
+
timeout?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum content size to return in bytes (default: 100KB)
|
|
26
|
+
* Larger content will be truncated
|
|
27
|
+
*/
|
|
28
|
+
maxSize?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Result of webFetch tool
|
|
32
|
+
*/
|
|
33
|
+
export interface WebFetchResult {
|
|
34
|
+
/**
|
|
35
|
+
* The fetched content (or processed result if prompt provided)
|
|
36
|
+
*/
|
|
37
|
+
content: string;
|
|
38
|
+
/**
|
|
39
|
+
* The URL that was fetched (may differ from input if redirected)
|
|
40
|
+
*/
|
|
41
|
+
url: string;
|
|
42
|
+
/**
|
|
43
|
+
* HTTP status code
|
|
44
|
+
*/
|
|
45
|
+
status: number;
|
|
46
|
+
/**
|
|
47
|
+
* Content type from response headers
|
|
48
|
+
*/
|
|
49
|
+
contentType: string;
|
|
50
|
+
/**
|
|
51
|
+
* Whether the content was truncated
|
|
52
|
+
*/
|
|
53
|
+
truncated: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Original content size in bytes
|
|
56
|
+
*/
|
|
57
|
+
originalSize: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* WebFetch tool definition
|
|
61
|
+
*/
|
|
62
|
+
export declare const webFetchTool: Tool<WebFetchInput>;
|
|
63
|
+
/**
|
|
64
|
+
* Options for creating a webFetch tool
|
|
65
|
+
*/
|
|
66
|
+
export interface WebFetchOptions {
|
|
67
|
+
/**
|
|
68
|
+
* Default timeout for requests (default: 30000ms)
|
|
69
|
+
*/
|
|
70
|
+
defaultTimeout?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Default max content size (default: 100KB)
|
|
73
|
+
*/
|
|
74
|
+
defaultMaxSize?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Blocked domains (requests to these domains will fail)
|
|
77
|
+
*/
|
|
78
|
+
blockedDomains?: string[];
|
|
79
|
+
/**
|
|
80
|
+
* Allowed domains (if set, only these domains are allowed)
|
|
81
|
+
*/
|
|
82
|
+
allowedDomains?: string[];
|
|
83
|
+
/**
|
|
84
|
+
* Custom content processor function
|
|
85
|
+
* Can be used to integrate LLM processing
|
|
86
|
+
*/
|
|
87
|
+
contentProcessor?: (content: string, prompt?: string) => Promise<string>;
|
|
88
|
+
/**
|
|
89
|
+
* Custom headers to include in all requests
|
|
90
|
+
*/
|
|
91
|
+
customHeaders?: Record<string, string>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Factory function to create a webFetch tool with custom options
|
|
95
|
+
*/
|
|
96
|
+
export declare function createWebFetchTool(options?: WebFetchOptions): Tool<WebFetchInput>;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebFetch Tool - Fetch and process web content
|
|
3
|
+
*
|
|
4
|
+
* Fetches content from a URL and optionally processes it with an LLM.
|
|
5
|
+
*/
|
|
6
|
+
import { defineTool, createSuccessResult, createErrorResult } from '../define.js';
|
|
7
|
+
/**
|
|
8
|
+
* Default timeout (30 seconds)
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_TIMEOUT = 30000;
|
|
11
|
+
/**
|
|
12
|
+
* Default max content size (100KB)
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_MAX_SIZE = 100 * 1024;
|
|
15
|
+
/**
|
|
16
|
+
* WebFetch tool definition
|
|
17
|
+
*/
|
|
18
|
+
export const webFetchTool = defineTool({
|
|
19
|
+
name: 'web_fetch',
|
|
20
|
+
description: 'Fetch content from a URL. Returns the page content as text. ' +
|
|
21
|
+
'Use for retrieving documentation, API responses, or web pages. ' +
|
|
22
|
+
'HTTP URLs are automatically upgraded to HTTPS.',
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
url: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'The URL to fetch content from',
|
|
29
|
+
},
|
|
30
|
+
prompt: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'Optional prompt to describe what information to extract from the page. ' +
|
|
33
|
+
'If not provided, returns the raw content.',
|
|
34
|
+
},
|
|
35
|
+
timeout: {
|
|
36
|
+
type: 'number',
|
|
37
|
+
description: 'Request timeout in milliseconds (default: 30000)',
|
|
38
|
+
},
|
|
39
|
+
maxSize: {
|
|
40
|
+
type: 'number',
|
|
41
|
+
description: 'Maximum content size to return in bytes (default: 102400)',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ['url'],
|
|
45
|
+
},
|
|
46
|
+
execute: async (input) => {
|
|
47
|
+
return executeWebFetch(input);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
/**
|
|
51
|
+
* Execute the webFetch tool
|
|
52
|
+
*/
|
|
53
|
+
async function executeWebFetch(input, options) {
|
|
54
|
+
const { url: inputUrl, timeout = DEFAULT_TIMEOUT, maxSize = DEFAULT_MAX_SIZE } = input;
|
|
55
|
+
// Validate and normalize URL
|
|
56
|
+
let url;
|
|
57
|
+
try {
|
|
58
|
+
url = new URL(inputUrl);
|
|
59
|
+
// Upgrade HTTP to HTTPS
|
|
60
|
+
if (url.protocol === 'http:') {
|
|
61
|
+
url.protocol = 'https:';
|
|
62
|
+
}
|
|
63
|
+
// Only allow http(s) protocols
|
|
64
|
+
if (url.protocol !== 'https:') {
|
|
65
|
+
return createErrorResult(`Unsupported protocol: ${url.protocol}. Only HTTP(S) is supported.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return createErrorResult(`Invalid URL: ${inputUrl}`);
|
|
70
|
+
}
|
|
71
|
+
// Build fetch options
|
|
72
|
+
const fetchOptions = {
|
|
73
|
+
method: 'GET',
|
|
74
|
+
headers: {
|
|
75
|
+
'User-Agent': 'Mozilla/5.0 (compatible; AgentBot/1.0)',
|
|
76
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.7',
|
|
77
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
|
78
|
+
},
|
|
79
|
+
redirect: 'follow',
|
|
80
|
+
signal: AbortSignal.timeout(timeout),
|
|
81
|
+
};
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetch(url.toString(), fetchOptions);
|
|
84
|
+
// Get content type
|
|
85
|
+
const contentType = response.headers.get('content-type') ?? 'text/plain';
|
|
86
|
+
// Check for redirect to different host
|
|
87
|
+
const finalUrl = response.url;
|
|
88
|
+
const finalUrlObj = new URL(finalUrl);
|
|
89
|
+
if (finalUrlObj.host !== url.host) {
|
|
90
|
+
return createSuccessResult({
|
|
91
|
+
content: `Redirected to different host. New URL: ${finalUrl}`,
|
|
92
|
+
url: finalUrl,
|
|
93
|
+
status: response.status,
|
|
94
|
+
contentType,
|
|
95
|
+
truncated: false,
|
|
96
|
+
originalSize: 0,
|
|
97
|
+
redirected: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Read response body
|
|
101
|
+
let content = await response.text();
|
|
102
|
+
const originalSize = content.length;
|
|
103
|
+
let truncated = false;
|
|
104
|
+
// Truncate if too large
|
|
105
|
+
if (content.length > maxSize) {
|
|
106
|
+
content = content.slice(0, maxSize) + '\n\n... [Content truncated]';
|
|
107
|
+
truncated = true;
|
|
108
|
+
}
|
|
109
|
+
// Convert HTML to plain text (basic conversion)
|
|
110
|
+
if (contentType.includes('text/html')) {
|
|
111
|
+
content = htmlToText(content);
|
|
112
|
+
}
|
|
113
|
+
// Apply custom processor if provided
|
|
114
|
+
if (options?.contentProcessor) {
|
|
115
|
+
content = await options.contentProcessor(content, input.prompt);
|
|
116
|
+
}
|
|
117
|
+
return createSuccessResult({
|
|
118
|
+
content,
|
|
119
|
+
url: finalUrl,
|
|
120
|
+
status: response.status,
|
|
121
|
+
contentType,
|
|
122
|
+
truncated,
|
|
123
|
+
originalSize,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error instanceof Error) {
|
|
128
|
+
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
|
129
|
+
return createErrorResult(`Request timed out after ${String(timeout)}ms`);
|
|
130
|
+
}
|
|
131
|
+
return createErrorResult(`Fetch failed: ${error.message}`);
|
|
132
|
+
}
|
|
133
|
+
return createErrorResult(`Fetch failed: ${String(error)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Basic HTML to text conversion
|
|
138
|
+
* Strips tags and normalizes whitespace
|
|
139
|
+
*/
|
|
140
|
+
function htmlToText(html) {
|
|
141
|
+
// Remove script and style elements
|
|
142
|
+
let text = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
|
143
|
+
text = text.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');
|
|
144
|
+
// Replace common block elements with newlines
|
|
145
|
+
text = text.replace(/<\/?(p|div|br|h[1-6]|li|tr|td|th|article|section|header|footer)[^>]*>/gi, '\n');
|
|
146
|
+
// Replace list items with bullets
|
|
147
|
+
text = text.replace(/<li[^>]*>/gi, '\n• ');
|
|
148
|
+
// Remove remaining tags
|
|
149
|
+
text = text.replace(/<[^>]+>/g, '');
|
|
150
|
+
// Decode common HTML entities
|
|
151
|
+
text = text.replace(/ /g, ' ');
|
|
152
|
+
text = text.replace(/&/g, '&');
|
|
153
|
+
text = text.replace(/</g, '<');
|
|
154
|
+
text = text.replace(/>/g, '>');
|
|
155
|
+
text = text.replace(/"/g, '"');
|
|
156
|
+
text = text.replace(/'/g, "'");
|
|
157
|
+
text = text.replace(/'/g, "'");
|
|
158
|
+
// Normalize whitespace
|
|
159
|
+
text = text.replace(/[ \t]+/g, ' ');
|
|
160
|
+
text = text.replace(/\n\s*\n/g, '\n\n');
|
|
161
|
+
text = text.trim();
|
|
162
|
+
return text;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Factory function to create a webFetch tool with custom options
|
|
166
|
+
*/
|
|
167
|
+
export function createWebFetchTool(options) {
|
|
168
|
+
const { defaultTimeout = DEFAULT_TIMEOUT, defaultMaxSize = DEFAULT_MAX_SIZE, blockedDomains = [], allowedDomains, customHeaders = {}, } = options ?? {};
|
|
169
|
+
return defineTool({
|
|
170
|
+
name: 'web_fetch',
|
|
171
|
+
description: 'Fetch content from a URL. Returns the page content as text. ' +
|
|
172
|
+
'Use for retrieving documentation, API responses, or web pages. ' +
|
|
173
|
+
'HTTP URLs are automatically upgraded to HTTPS.',
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
url: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'The URL to fetch content from',
|
|
180
|
+
},
|
|
181
|
+
prompt: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
description: 'Optional prompt to describe what information to extract from the page. ' +
|
|
184
|
+
'If not provided, returns the raw content.',
|
|
185
|
+
},
|
|
186
|
+
timeout: {
|
|
187
|
+
type: 'number',
|
|
188
|
+
description: `Request timeout in milliseconds (default: ${String(defaultTimeout)})`,
|
|
189
|
+
},
|
|
190
|
+
maxSize: {
|
|
191
|
+
type: 'number',
|
|
192
|
+
description: `Maximum content size to return in bytes (default: ${String(defaultMaxSize)})`,
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
required: ['url'],
|
|
196
|
+
},
|
|
197
|
+
execute: async (input) => {
|
|
198
|
+
const timeout = input.timeout ?? defaultTimeout;
|
|
199
|
+
const maxSize = input.maxSize ?? defaultMaxSize;
|
|
200
|
+
// Validate URL
|
|
201
|
+
let url;
|
|
202
|
+
try {
|
|
203
|
+
url = new URL(input.url);
|
|
204
|
+
// Upgrade HTTP to HTTPS
|
|
205
|
+
if (url.protocol === 'http:') {
|
|
206
|
+
url.protocol = 'https:';
|
|
207
|
+
}
|
|
208
|
+
// Only allow http(s) protocols
|
|
209
|
+
if (url.protocol !== 'https:') {
|
|
210
|
+
return createErrorResult(`Unsupported protocol: ${url.protocol}. Only HTTP(S) is supported.`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return createErrorResult(`Invalid URL: ${input.url}`);
|
|
215
|
+
}
|
|
216
|
+
// Check blocked domains
|
|
217
|
+
if (blockedDomains.some((d) => url.hostname.endsWith(d))) {
|
|
218
|
+
return createErrorResult(`Domain '${url.hostname}' is blocked.`);
|
|
219
|
+
}
|
|
220
|
+
// Check allowed domains
|
|
221
|
+
if (allowedDomains && !allowedDomains.some((d) => url.hostname.endsWith(d))) {
|
|
222
|
+
return createErrorResult(`Domain '${url.hostname}' is not in allowed list.`);
|
|
223
|
+
}
|
|
224
|
+
// Build fetch options
|
|
225
|
+
const fetchOptions = {
|
|
226
|
+
method: 'GET',
|
|
227
|
+
headers: {
|
|
228
|
+
'User-Agent': 'Mozilla/5.0 (compatible; AgentBot/1.0)',
|
|
229
|
+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.7',
|
|
230
|
+
'Accept-Language': 'en-US,en;q=0.5',
|
|
231
|
+
...customHeaders,
|
|
232
|
+
},
|
|
233
|
+
redirect: 'follow',
|
|
234
|
+
signal: AbortSignal.timeout(timeout),
|
|
235
|
+
};
|
|
236
|
+
try {
|
|
237
|
+
const response = await fetch(url.toString(), fetchOptions);
|
|
238
|
+
const contentType = response.headers.get('content-type') ?? 'text/plain';
|
|
239
|
+
// Check for redirect to different host
|
|
240
|
+
const finalUrl = response.url;
|
|
241
|
+
const finalUrlObj = new URL(finalUrl);
|
|
242
|
+
if (finalUrlObj.host !== url.host) {
|
|
243
|
+
return createSuccessResult({
|
|
244
|
+
content: `Redirected to different host. New URL: ${finalUrl}`,
|
|
245
|
+
url: finalUrl,
|
|
246
|
+
status: response.status,
|
|
247
|
+
contentType,
|
|
248
|
+
truncated: false,
|
|
249
|
+
originalSize: 0,
|
|
250
|
+
redirected: true,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
// Read response body
|
|
254
|
+
let content = await response.text();
|
|
255
|
+
const originalSize = content.length;
|
|
256
|
+
let truncated = false;
|
|
257
|
+
// Truncate if too large
|
|
258
|
+
if (content.length > maxSize) {
|
|
259
|
+
content = content.slice(0, maxSize) + '\n\n... [Content truncated]';
|
|
260
|
+
truncated = true;
|
|
261
|
+
}
|
|
262
|
+
// Convert HTML to plain text
|
|
263
|
+
if (contentType.includes('text/html')) {
|
|
264
|
+
content = htmlToText(content);
|
|
265
|
+
}
|
|
266
|
+
// Apply custom processor if provided
|
|
267
|
+
if (options?.contentProcessor) {
|
|
268
|
+
content = await options.contentProcessor(content, input.prompt);
|
|
269
|
+
}
|
|
270
|
+
return createSuccessResult({
|
|
271
|
+
content,
|
|
272
|
+
url: finalUrl,
|
|
273
|
+
status: response.status,
|
|
274
|
+
contentType,
|
|
275
|
+
truncated,
|
|
276
|
+
originalSize,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
if (error instanceof Error) {
|
|
281
|
+
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
|
282
|
+
return createErrorResult(`Request timed out after ${String(timeout)}ms`);
|
|
283
|
+
}
|
|
284
|
+
return createErrorResult(`Fetch failed: ${error.message}`);
|
|
285
|
+
}
|
|
286
|
+
return createErrorResult(`Fetch failed: ${String(error)}`);
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write File Tool - Write or create files on the filesystem
|
|
3
|
+
*/
|
|
4
|
+
import type { Tool } from '../types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Input parameters for write_file tool
|
|
7
|
+
*/
|
|
8
|
+
export interface WriteFileInput {
|
|
9
|
+
/**
|
|
10
|
+
* Path to the file to write
|
|
11
|
+
*/
|
|
12
|
+
path: string;
|
|
13
|
+
/**
|
|
14
|
+
* Content to write to the file
|
|
15
|
+
*/
|
|
16
|
+
content: string;
|
|
17
|
+
/**
|
|
18
|
+
* Encoding to use (default: utf-8)
|
|
19
|
+
*/
|
|
20
|
+
encoding?: BufferEncoding;
|
|
21
|
+
/**
|
|
22
|
+
* Create parent directories if they don't exist (default: true)
|
|
23
|
+
*/
|
|
24
|
+
createDirs?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Append to file instead of overwriting (default: false)
|
|
27
|
+
*/
|
|
28
|
+
append?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Write file tool definition
|
|
32
|
+
*/
|
|
33
|
+
export declare const writeFileTool: Tool<WriteFileInput>;
|
|
34
|
+
/**
|
|
35
|
+
* Factory function to create a write_file tool with custom options
|
|
36
|
+
*/
|
|
37
|
+
export declare function createWriteFileTool(options?: {
|
|
38
|
+
/**
|
|
39
|
+
* Base directory to resolve relative paths against
|
|
40
|
+
*/
|
|
41
|
+
baseDir?: string;
|
|
42
|
+
/**
|
|
43
|
+
* List of allowed file extensions (e.g., ['.ts', '.js', '.json'])
|
|
44
|
+
*/
|
|
45
|
+
allowedExtensions?: string[];
|
|
46
|
+
/**
|
|
47
|
+
* Maximum content size in bytes (default: 10MB)
|
|
48
|
+
*/
|
|
49
|
+
maxContentSize?: number;
|
|
50
|
+
/**
|
|
51
|
+
* Directories that cannot be written to
|
|
52
|
+
*/
|
|
53
|
+
blockedPaths?: string[];
|
|
54
|
+
}): Tool<WriteFileInput>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write File Tool - Write or create files on the filesystem
|
|
3
|
+
*/
|
|
4
|
+
import { writeFile as fsWriteFile, mkdir } from 'node:fs/promises';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { defineTool, createSuccessResult, createErrorResult } from '../define.js';
|
|
7
|
+
import { isNodeError, isExtensionAllowed } from './utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Write file tool definition
|
|
10
|
+
*/
|
|
11
|
+
export const writeFileTool = defineTool({
|
|
12
|
+
name: 'write_file',
|
|
13
|
+
description: 'Write content to a file. Creates the file if it does not exist. ' +
|
|
14
|
+
'Use append: true to add to existing content instead of overwriting.',
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
path: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Absolute or relative path to the file',
|
|
21
|
+
},
|
|
22
|
+
content: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Content to write to the file',
|
|
25
|
+
},
|
|
26
|
+
encoding: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'File encoding (default: utf-8)',
|
|
29
|
+
},
|
|
30
|
+
createDirs: {
|
|
31
|
+
type: 'boolean',
|
|
32
|
+
description: 'Create parent directories if needed (default: true)',
|
|
33
|
+
},
|
|
34
|
+
append: {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
description: 'Append to file instead of overwriting (default: false)',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: ['path', 'content'],
|
|
40
|
+
},
|
|
41
|
+
execute: executeWriteFile,
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Execute the write_file tool
|
|
45
|
+
*/
|
|
46
|
+
async function executeWriteFile(input) {
|
|
47
|
+
try {
|
|
48
|
+
const { path, content, encoding = 'utf-8', createDirs = true, append = false } = input;
|
|
49
|
+
// Create parent directories if needed
|
|
50
|
+
if (createDirs) {
|
|
51
|
+
const dir = dirname(path);
|
|
52
|
+
if (dir && dir !== '.') {
|
|
53
|
+
await mkdir(dir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Write the file
|
|
57
|
+
const flag = append ? 'a' : 'w';
|
|
58
|
+
await fsWriteFile(path, content, { encoding, flag });
|
|
59
|
+
return createSuccessResult({
|
|
60
|
+
path,
|
|
61
|
+
bytesWritten: Buffer.byteLength(content, encoding),
|
|
62
|
+
mode: append ? 'appended' : 'written',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (isNodeError(error)) {
|
|
67
|
+
switch (error.code) {
|
|
68
|
+
case 'EACCES':
|
|
69
|
+
return createErrorResult(`Permission denied: ${input.path}`);
|
|
70
|
+
case 'ENOENT':
|
|
71
|
+
return createErrorResult(`Directory does not exist: ${dirname(input.path)}`);
|
|
72
|
+
case 'ENOSPC':
|
|
73
|
+
return createErrorResult('No space left on device');
|
|
74
|
+
case 'EROFS':
|
|
75
|
+
return createErrorResult('Read-only file system');
|
|
76
|
+
default:
|
|
77
|
+
return createErrorResult(`Failed to write file: ${error.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Factory function to create a write_file tool with custom options
|
|
85
|
+
*/
|
|
86
|
+
export function createWriteFileTool(options) {
|
|
87
|
+
return defineTool({
|
|
88
|
+
name: 'write_file',
|
|
89
|
+
description: 'Write content to a file. Creates the file if it does not exist. ' +
|
|
90
|
+
'Use append: true to add to existing content instead of overwriting.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: 'object',
|
|
93
|
+
properties: {
|
|
94
|
+
path: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
description: 'Absolute or relative path to the file',
|
|
97
|
+
},
|
|
98
|
+
content: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: 'Content to write to the file',
|
|
101
|
+
},
|
|
102
|
+
encoding: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'File encoding (default: utf-8)',
|
|
105
|
+
},
|
|
106
|
+
createDirs: {
|
|
107
|
+
type: 'boolean',
|
|
108
|
+
description: 'Create parent directories if needed (default: true)',
|
|
109
|
+
},
|
|
110
|
+
append: {
|
|
111
|
+
type: 'boolean',
|
|
112
|
+
description: 'Append to file instead of overwriting (default: false)',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
required: ['path', 'content'],
|
|
116
|
+
},
|
|
117
|
+
execute: async (input) => {
|
|
118
|
+
const { baseDir, allowedExtensions, maxContentSize = 10 * 1024 * 1024, blockedPaths = [], } = options ?? {};
|
|
119
|
+
let filePath = input.path;
|
|
120
|
+
// Resolve relative paths and normalize to prevent path traversal
|
|
121
|
+
if (baseDir && !filePath.startsWith('/')) {
|
|
122
|
+
filePath = resolve(baseDir, filePath);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
filePath = resolve(filePath);
|
|
126
|
+
}
|
|
127
|
+
// Check blocked paths (compare resolved paths to prevent traversal bypass)
|
|
128
|
+
for (const blocked of blockedPaths) {
|
|
129
|
+
const resolvedBlocked = resolve(blocked);
|
|
130
|
+
if (filePath.startsWith(resolvedBlocked) || filePath === resolvedBlocked) {
|
|
131
|
+
return createErrorResult(`Cannot write to blocked path: ${blocked}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Check extension if restricted
|
|
135
|
+
if (allowedExtensions && allowedExtensions.length > 0) {
|
|
136
|
+
if (!isExtensionAllowed(filePath, allowedExtensions)) {
|
|
137
|
+
return createErrorResult(`File extension not allowed. Allowed: ${allowedExtensions.join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Check content size
|
|
141
|
+
if (Buffer.byteLength(input.content, input.encoding ?? 'utf-8') > maxContentSize) {
|
|
142
|
+
return createErrorResult(`Content too large (max: ${String(maxContentSize)} bytes)`);
|
|
143
|
+
}
|
|
144
|
+
return executeWriteFile({ ...input, path: filePath });
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|