@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.
Files changed (160) hide show
  1. package/README.md +1277 -0
  2. package/dist/agent.d.ts +1272 -0
  3. package/dist/agent.js +1912 -0
  4. package/dist/anchors/builtin.d.ts +24 -0
  5. package/dist/anchors/builtin.js +61 -0
  6. package/dist/anchors/index.d.ts +6 -0
  7. package/dist/anchors/index.js +5 -0
  8. package/dist/anchors/manager.d.ts +115 -0
  9. package/dist/anchors/manager.js +412 -0
  10. package/dist/anchors/types.d.ts +168 -0
  11. package/dist/anchors/types.js +10 -0
  12. package/dist/context/index.d.ts +12 -0
  13. package/dist/context/index.js +10 -0
  14. package/dist/context/manager.d.ts +224 -0
  15. package/dist/context/manager.js +770 -0
  16. package/dist/context/types.d.ts +377 -0
  17. package/dist/context/types.js +7 -0
  18. package/dist/costs/index.d.ts +8 -0
  19. package/dist/costs/index.js +7 -0
  20. package/dist/costs/tracker.d.ts +121 -0
  21. package/dist/costs/tracker.js +295 -0
  22. package/dist/costs/types.d.ts +157 -0
  23. package/dist/costs/types.js +8 -0
  24. package/dist/errors.d.ts +178 -0
  25. package/dist/errors.js +249 -0
  26. package/dist/guardrails/builtin.d.ts +27 -0
  27. package/dist/guardrails/builtin.js +223 -0
  28. package/dist/guardrails/index.d.ts +6 -0
  29. package/dist/guardrails/index.js +5 -0
  30. package/dist/guardrails/manager.d.ts +117 -0
  31. package/dist/guardrails/manager.js +288 -0
  32. package/dist/guardrails/types.d.ts +159 -0
  33. package/dist/guardrails/types.js +7 -0
  34. package/dist/hooks/index.d.ts +31 -0
  35. package/dist/hooks/index.js +29 -0
  36. package/dist/hooks/manager.d.ts +147 -0
  37. package/dist/hooks/manager.js +600 -0
  38. package/dist/hooks/types.d.ts +368 -0
  39. package/dist/hooks/types.js +12 -0
  40. package/dist/index.d.ts +45 -0
  41. package/dist/index.js +73 -0
  42. package/dist/mcp/client.d.ts +93 -0
  43. package/dist/mcp/client.js +287 -0
  44. package/dist/mcp/errors.d.ts +60 -0
  45. package/dist/mcp/errors.js +78 -0
  46. package/dist/mcp/index.d.ts +43 -0
  47. package/dist/mcp/index.js +45 -0
  48. package/dist/mcp/manager.d.ts +120 -0
  49. package/dist/mcp/manager.js +276 -0
  50. package/dist/mcp/tools.d.ts +54 -0
  51. package/dist/mcp/tools.js +99 -0
  52. package/dist/mcp/types.d.ts +150 -0
  53. package/dist/mcp/types.js +40 -0
  54. package/dist/memory/index.d.ts +8 -0
  55. package/dist/memory/index.js +7 -0
  56. package/dist/memory/loader.d.ts +114 -0
  57. package/dist/memory/loader.js +463 -0
  58. package/dist/memory/types.d.ts +182 -0
  59. package/dist/memory/types.js +8 -0
  60. package/dist/messages/index.d.ts +82 -0
  61. package/dist/messages/index.js +155 -0
  62. package/dist/permissions/index.d.ts +5 -0
  63. package/dist/permissions/index.js +4 -0
  64. package/dist/permissions/manager.d.ts +125 -0
  65. package/dist/permissions/manager.js +379 -0
  66. package/dist/permissions/types.d.ts +162 -0
  67. package/dist/permissions/types.js +7 -0
  68. package/dist/providers/claude.d.ts +90 -0
  69. package/dist/providers/claude.js +348 -0
  70. package/dist/providers/index.d.ts +8 -0
  71. package/dist/providers/index.js +11 -0
  72. package/dist/providers/mock.d.ts +133 -0
  73. package/dist/providers/mock.js +204 -0
  74. package/dist/providers/types.d.ts +168 -0
  75. package/dist/providers/types.js +4 -0
  76. package/dist/rate-limit/index.d.ts +45 -0
  77. package/dist/rate-limit/index.js +47 -0
  78. package/dist/rate-limit/limiter.d.ts +104 -0
  79. package/dist/rate-limit/limiter.js +326 -0
  80. package/dist/rate-limit/provider-wrapper.d.ts +112 -0
  81. package/dist/rate-limit/provider-wrapper.js +201 -0
  82. package/dist/rate-limit/retry.d.ts +108 -0
  83. package/dist/rate-limit/retry.js +287 -0
  84. package/dist/rate-limit/types.d.ts +181 -0
  85. package/dist/rate-limit/types.js +22 -0
  86. package/dist/rehearsal/file-analyzer.d.ts +22 -0
  87. package/dist/rehearsal/file-analyzer.js +351 -0
  88. package/dist/rehearsal/git-analyzer.d.ts +22 -0
  89. package/dist/rehearsal/git-analyzer.js +472 -0
  90. package/dist/rehearsal/index.d.ts +35 -0
  91. package/dist/rehearsal/index.js +36 -0
  92. package/dist/rehearsal/manager.d.ts +100 -0
  93. package/dist/rehearsal/manager.js +290 -0
  94. package/dist/rehearsal/types.d.ts +235 -0
  95. package/dist/rehearsal/types.js +8 -0
  96. package/dist/skills/index.d.ts +160 -0
  97. package/dist/skills/index.js +282 -0
  98. package/dist/state/agent-state.d.ts +41 -0
  99. package/dist/state/agent-state.js +88 -0
  100. package/dist/state/checkpointer.d.ts +110 -0
  101. package/dist/state/checkpointer.js +362 -0
  102. package/dist/state/errors.d.ts +66 -0
  103. package/dist/state/errors.js +88 -0
  104. package/dist/state/index.d.ts +35 -0
  105. package/dist/state/index.js +37 -0
  106. package/dist/state/serializer.d.ts +55 -0
  107. package/dist/state/serializer.js +172 -0
  108. package/dist/state/types.d.ts +312 -0
  109. package/dist/state/types.js +14 -0
  110. package/dist/tools/builtin/bash-output.d.ts +61 -0
  111. package/dist/tools/builtin/bash-output.js +90 -0
  112. package/dist/tools/builtin/bash.d.ts +150 -0
  113. package/dist/tools/builtin/bash.js +354 -0
  114. package/dist/tools/builtin/edit.d.ts +50 -0
  115. package/dist/tools/builtin/edit.js +215 -0
  116. package/dist/tools/builtin/glob.d.ts +62 -0
  117. package/dist/tools/builtin/glob.js +244 -0
  118. package/dist/tools/builtin/grep.d.ts +74 -0
  119. package/dist/tools/builtin/grep.js +363 -0
  120. package/dist/tools/builtin/index.d.ts +44 -0
  121. package/dist/tools/builtin/index.js +69 -0
  122. package/dist/tools/builtin/kill-shell.d.ts +44 -0
  123. package/dist/tools/builtin/kill-shell.js +80 -0
  124. package/dist/tools/builtin/read-file.d.ts +57 -0
  125. package/dist/tools/builtin/read-file.js +184 -0
  126. package/dist/tools/builtin/shell-manager.d.ts +176 -0
  127. package/dist/tools/builtin/shell-manager.js +337 -0
  128. package/dist/tools/builtin/task.d.ts +202 -0
  129. package/dist/tools/builtin/task.js +350 -0
  130. package/dist/tools/builtin/todo.d.ts +207 -0
  131. package/dist/tools/builtin/todo.js +453 -0
  132. package/dist/tools/builtin/utils.d.ts +27 -0
  133. package/dist/tools/builtin/utils.js +70 -0
  134. package/dist/tools/builtin/web-fetch.d.ts +96 -0
  135. package/dist/tools/builtin/web-fetch.js +290 -0
  136. package/dist/tools/builtin/write-file.d.ts +54 -0
  137. package/dist/tools/builtin/write-file.js +147 -0
  138. package/dist/tools/define.d.ts +60 -0
  139. package/dist/tools/define.js +65 -0
  140. package/dist/tools/index.d.ts +10 -0
  141. package/dist/tools/index.js +37 -0
  142. package/dist/tools/registry.d.ts +79 -0
  143. package/dist/tools/registry.js +151 -0
  144. package/dist/tools/types.d.ts +59 -0
  145. package/dist/tools/types.js +4 -0
  146. package/dist/tracing/hooks.d.ts +58 -0
  147. package/dist/tracing/hooks.js +377 -0
  148. package/dist/tracing/index.d.ts +51 -0
  149. package/dist/tracing/index.js +55 -0
  150. package/dist/tracing/logging.d.ts +78 -0
  151. package/dist/tracing/logging.js +310 -0
  152. package/dist/tracing/manager.d.ts +160 -0
  153. package/dist/tracing/manager.js +468 -0
  154. package/dist/tracing/otel.d.ts +102 -0
  155. package/dist/tracing/otel.js +246 -0
  156. package/dist/tracing/types.d.ts +346 -0
  157. package/dist/tracing/types.js +38 -0
  158. package/dist/utils/index.d.ts +23 -0
  159. package/dist/utils/index.js +44 -0
  160. 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(/&nbsp;/g, ' ');
152
+ text = text.replace(/&amp;/g, '&');
153
+ text = text.replace(/&lt;/g, '<');
154
+ text = text.replace(/&gt;/g, '>');
155
+ text = text.replace(/&quot;/g, '"');
156
+ text = text.replace(/&#39;/g, "'");
157
+ text = text.replace(/&apos;/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
+ }