@agentscope-ai/agentscope 0.0.2

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 (136) hide show
  1. package/dist/agent/index.d.mts +234 -0
  2. package/dist/agent/index.d.ts +234 -0
  3. package/dist/agent/index.js +1412 -0
  4. package/dist/agent/index.js.map +1 -0
  5. package/dist/agent/index.mjs +1375 -0
  6. package/dist/agent/index.mjs.map +1 -0
  7. package/dist/base-BOx3UzOl.d.mts +41 -0
  8. package/dist/base-BoIps2RL.d.ts +41 -0
  9. package/dist/base-C7jwyH4Z.d.mts +52 -0
  10. package/dist/base-Cwi4bjze.d.ts +127 -0
  11. package/dist/base-DYlBMCy_.d.mts +127 -0
  12. package/dist/base-NX-knWOv.d.ts +52 -0
  13. package/dist/block-VsnHrllL.d.mts +48 -0
  14. package/dist/block-VsnHrllL.d.ts +48 -0
  15. package/dist/event/index.d.mts +181 -0
  16. package/dist/event/index.d.ts +181 -0
  17. package/dist/event/index.js +58 -0
  18. package/dist/event/index.js.map +1 -0
  19. package/dist/event/index.mjs +33 -0
  20. package/dist/event/index.mjs.map +1 -0
  21. package/dist/formatter/index.d.mts +187 -0
  22. package/dist/formatter/index.d.ts +187 -0
  23. package/dist/formatter/index.js +647 -0
  24. package/dist/formatter/index.js.map +1 -0
  25. package/dist/formatter/index.mjs +616 -0
  26. package/dist/formatter/index.mjs.map +1 -0
  27. package/dist/index-BTJDlKvQ.d.mts +195 -0
  28. package/dist/index-BcatlwXQ.d.ts +195 -0
  29. package/dist/index-CAxQAkiP.d.mts +21 -0
  30. package/dist/index-CAxQAkiP.d.ts +21 -0
  31. package/dist/mcp/index.d.mts +9 -0
  32. package/dist/mcp/index.d.ts +9 -0
  33. package/dist/mcp/index.js +432 -0
  34. package/dist/mcp/index.js.map +1 -0
  35. package/dist/mcp/index.mjs +408 -0
  36. package/dist/mcp/index.mjs.map +1 -0
  37. package/dist/message/index.d.mts +10 -0
  38. package/dist/message/index.d.ts +10 -0
  39. package/dist/message/index.js +67 -0
  40. package/dist/message/index.js.map +1 -0
  41. package/dist/message/index.mjs +37 -0
  42. package/dist/message/index.mjs.map +1 -0
  43. package/dist/message-CkN21KaY.d.mts +99 -0
  44. package/dist/message-CzLeTlua.d.ts +99 -0
  45. package/dist/model/index.d.mts +377 -0
  46. package/dist/model/index.d.ts +377 -0
  47. package/dist/model/index.js +1880 -0
  48. package/dist/model/index.js.map +1 -0
  49. package/dist/model/index.mjs +1849 -0
  50. package/dist/model/index.mjs.map +1 -0
  51. package/dist/storage/index.d.mts +68 -0
  52. package/dist/storage/index.d.ts +68 -0
  53. package/dist/storage/index.js +250 -0
  54. package/dist/storage/index.js.map +1 -0
  55. package/dist/storage/index.mjs +212 -0
  56. package/dist/storage/index.mjs.map +1 -0
  57. package/dist/tool/index.d.mts +311 -0
  58. package/dist/tool/index.d.ts +311 -0
  59. package/dist/tool/index.js +1494 -0
  60. package/dist/tool/index.js.map +1 -0
  61. package/dist/tool/index.mjs +1447 -0
  62. package/dist/tool/index.mjs.map +1 -0
  63. package/dist/toolkit-CEpulFi0.d.ts +99 -0
  64. package/dist/toolkit-CGEZSZPa.d.mts +99 -0
  65. package/jest.config.js +11 -0
  66. package/package.json +92 -0
  67. package/src/_utils/common.ts +104 -0
  68. package/src/_utils/index.ts +1 -0
  69. package/src/agent/agent-base.ts +0 -0
  70. package/src/agent/agent.test.ts +1028 -0
  71. package/src/agent/agent.ts +1032 -0
  72. package/src/agent/index.ts +2 -0
  73. package/src/agent/interfaces.ts +23 -0
  74. package/src/agent/test-compression.ts +72 -0
  75. package/src/event/index.ts +250 -0
  76. package/src/formatter/base.ts +133 -0
  77. package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
  78. package/src/formatter/dashscope-chat-formatter.ts +163 -0
  79. package/src/formatter/deepseek-chat-formatter.ts +130 -0
  80. package/src/formatter/index.ts +5 -0
  81. package/src/formatter/ollama-chat-formatter.ts +67 -0
  82. package/src/formatter/openai-chat-formatter.test.ts +263 -0
  83. package/src/formatter/openai-chat-formatter.ts +301 -0
  84. package/src/formatter/openai.md +767 -0
  85. package/src/mcp/base.ts +114 -0
  86. package/src/mcp/http.test.ts +303 -0
  87. package/src/mcp/http.ts +224 -0
  88. package/src/mcp/index.ts +2 -0
  89. package/src/mcp/stdio.test.ts +91 -0
  90. package/src/mcp/stdio.ts +119 -0
  91. package/src/message/block.ts +60 -0
  92. package/src/message/enums.ts +4 -0
  93. package/src/message/index.ts +12 -0
  94. package/src/message/message.test.ts +80 -0
  95. package/src/message/message.ts +131 -0
  96. package/src/model/base.ts +226 -0
  97. package/src/model/dashscope-model.test.ts +335 -0
  98. package/src/model/dashscope-model.ts +441 -0
  99. package/src/model/deepseek-model.test.ts +279 -0
  100. package/src/model/deepseek-model.ts +401 -0
  101. package/src/model/index.ts +7 -0
  102. package/src/model/ollama-model.test.ts +307 -0
  103. package/src/model/ollama-model.ts +356 -0
  104. package/src/model/openai-model.ts +327 -0
  105. package/src/model/response.ts +22 -0
  106. package/src/model/usage.ts +12 -0
  107. package/src/storage/base.ts +52 -0
  108. package/src/storage/file-system.test.ts +587 -0
  109. package/src/storage/file-system.ts +269 -0
  110. package/src/storage/index.ts +2 -0
  111. package/src/tool/base.ts +23 -0
  112. package/src/tool/bash.test.ts +174 -0
  113. package/src/tool/bash.ts +152 -0
  114. package/src/tool/edit.test.ts +83 -0
  115. package/src/tool/edit.ts +95 -0
  116. package/src/tool/glob.test.ts +63 -0
  117. package/src/tool/glob.ts +166 -0
  118. package/src/tool/grep.test.ts +74 -0
  119. package/src/tool/grep.ts +256 -0
  120. package/src/tool/index.ts +10 -0
  121. package/src/tool/read.test.ts +77 -0
  122. package/src/tool/read.ts +117 -0
  123. package/src/tool/response.ts +82 -0
  124. package/src/tool/task.test.ts +299 -0
  125. package/src/tool/task.ts +399 -0
  126. package/src/tool/toolkit.test.ts +636 -0
  127. package/src/tool/toolkit.ts +601 -0
  128. package/src/tool/write.test.ts +52 -0
  129. package/src/tool/write.ts +57 -0
  130. package/src/type/index.ts +52 -0
  131. package/tsconfig.build.json +4 -0
  132. package/tsconfig.cjs.json +11 -0
  133. package/tsconfig.esm.json +10 -0
  134. package/tsconfig.json +14 -0
  135. package/tsup.config.ts +20 -0
  136. package/typedoc.json +52 -0
@@ -0,0 +1,256 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ import { z } from 'zod';
5
+
6
+ type OutputMode = 'content' | 'files_with_matches' | 'count';
7
+
8
+ const TYPE_EXTENSIONS: Record<string, string[]> = {
9
+ js: ['.js', '.mjs', '.cjs'],
10
+ ts: ['.ts', '.mts', '.cts'],
11
+ tsx: ['.tsx'],
12
+ jsx: ['.jsx'],
13
+ py: ['.py'],
14
+ rust: ['.rs'],
15
+ go: ['.go'],
16
+ java: ['.java'],
17
+ cpp: ['.cpp', '.cc', '.cxx', '.h', '.hpp'],
18
+ c: ['.c', '.h'],
19
+ css: ['.css'],
20
+ html: ['.html', '.htm'],
21
+ json: ['.json'],
22
+ md: ['.md', '.markdown'],
23
+ yaml: ['.yaml', '.yml'],
24
+ toml: ['.toml'],
25
+ sh: ['.sh', '.bash'],
26
+ };
27
+
28
+ /**
29
+ * Tool for searching file contents using regular expressions.
30
+ * Supports multiple output modes, file type filtering, and multiline matching.
31
+ *
32
+ * @returns A Tool object for performing regex searches across files, with a call method that executes the search and returns results based on the specified output mode.
33
+ */
34
+ export function Grep() {
35
+ /**
36
+ * Collects all files under a base directory, optionally filtered by glob or type.
37
+ * @param baseDir - The base directory to search from.
38
+ * @param glob - Optional glob pattern to filter files by name.
39
+ * @param type - Optional file type key to filter by extension.
40
+ * @returns An array of matching file paths.
41
+ */
42
+ const collectFiles = (baseDir: string, glob?: string, type?: string): string[] => {
43
+ const results: string[] = [];
44
+ const extensions = type ? TYPE_EXTENSIONS[type] : undefined;
45
+
46
+ const walk = (dir: string): void => {
47
+ let entries: fs.Dirent[];
48
+ try {
49
+ entries = fs.readdirSync(dir, { withFileTypes: true });
50
+ } catch {
51
+ return;
52
+ }
53
+
54
+ for (const entry of entries) {
55
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
56
+
57
+ const fullPath = path.join(dir, entry.name);
58
+ if (entry.isDirectory()) {
59
+ walk(fullPath);
60
+ } else if (entry.isFile()) {
61
+ if (extensions) {
62
+ const ext = path.extname(entry.name);
63
+ if (extensions.includes(ext)) results.push(fullPath);
64
+ } else if (glob) {
65
+ if (matchGlob(glob, entry.name)) results.push(fullPath);
66
+ } else {
67
+ results.push(fullPath);
68
+ }
69
+ }
70
+ }
71
+ };
72
+
73
+ if (fs.existsSync(baseDir) && fs.statSync(baseDir).isFile()) {
74
+ results.push(baseDir);
75
+ } else {
76
+ walk(baseDir);
77
+ }
78
+
79
+ return results;
80
+ };
81
+
82
+ /**
83
+ * Tests whether a filename matches a glob pattern.
84
+ * @param pattern - The glob pattern to match against.
85
+ * @param filename - The filename to test.
86
+ * @returns True if the filename matches the pattern, false otherwise.
87
+ */
88
+ const matchGlob = (pattern: string, filename: string): boolean => {
89
+ const braceMatch = pattern.match(/\*\.\\{(.+)\\}/);
90
+ if (braceMatch) {
91
+ const exts = braceMatch[1].split(',').map(e => `.${e.trim()}`);
92
+ return exts.includes(path.extname(filename));
93
+ }
94
+ const escaped = pattern
95
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
96
+ .replace(/\*/g, '.*')
97
+ .replace(/\?/g, '.');
98
+ return new RegExp(`^${escaped}$`).test(filename);
99
+ };
100
+
101
+ return {
102
+ name: 'Grep',
103
+ description: `A powerful search tool built on regular expressions.
104
+
105
+ Usage:
106
+ - ALWAYS use Grep for search tasks. NEVER invoke \`grep\` or \`rg\` as a Bash command. The Grep tool has been optimized for correct permissions and access.
107
+ - Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
108
+ - Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
109
+ - Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
110
+ - Use Task tool for open-ended searches requiring multiple rounds
111
+ - Pattern syntax: Uses ripgrep-style regex - literal braces need escaping (use \`interface\\{\\}\` to find \`interface{}\` in Go code)
112
+ - Multiline matching: By default patterns match within single lines only. For cross-line patterns, use \`multiline: true\``,
113
+ inputSchema: z.object({
114
+ pattern: z
115
+ .string()
116
+ .describe('The regular expression pattern to search for in file contents'),
117
+ path: z
118
+ .string()
119
+ .optional()
120
+ .describe('File or directory to search in. Defaults to current working directory.'),
121
+ glob: z
122
+ .string()
123
+ .optional()
124
+ .describe('Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}")'),
125
+ type: z
126
+ .string()
127
+ .optional()
128
+ .describe(
129
+ 'File type to search (e.g., "js", "ts", "py"). More efficient than glob for standard file types.'
130
+ ),
131
+ output_mode: z
132
+ .enum(['content', 'files_with_matches', 'count'])
133
+ .optional()
134
+ .describe(
135
+ 'Output mode: "content" | "files_with_matches" | "count". Defaults to "files_with_matches".'
136
+ ),
137
+ multiline: z
138
+ .boolean()
139
+ .optional()
140
+ .describe(
141
+ 'Enable multiline mode where . matches newlines and patterns can span lines. Default: false.'
142
+ ),
143
+ case_insensitive: z
144
+ .boolean()
145
+ .optional()
146
+ .describe('Case insensitive search. Default: false.'),
147
+ context: z
148
+ .number()
149
+ .int()
150
+ .optional()
151
+ .describe(
152
+ 'Number of lines to show before and after each match. Requires output_mode: "content".'
153
+ ),
154
+ head_limit: z
155
+ .number()
156
+ .int()
157
+ .optional()
158
+ .describe('Limit output to first N lines/entries.'),
159
+ }),
160
+ requireUserConfirm: true,
161
+
162
+ /**
163
+ * Searches files for a regex pattern and returns results in the specified output mode.
164
+ *
165
+ * @param root0 - The parameters object
166
+ * @param root0.pattern - The regular expression pattern to search for
167
+ * @param root0.path - File or directory to search in; defaults to cwd
168
+ * @param root0.glob - Glob pattern to filter which files are searched
169
+ * @param root0.type - File type shorthand (e.g. "ts", "py") to filter files
170
+ * @param root0.output_mode - How to format results: "content", "files_with_matches", or "count"
171
+ * @param root0.multiline - Whether the pattern can span multiple lines
172
+ * @param root0.case_insensitive - Whether the search is case-insensitive
173
+ * @param root0.context - Number of surrounding lines to include with each match
174
+ * @param root0.head_limit - Maximum number of result entries to return
175
+ * @returns A newline-separated string of results, or a no-matches message
176
+ */
177
+ call({
178
+ pattern,
179
+ path: searchPath,
180
+ glob,
181
+ type,
182
+ output_mode = 'files_with_matches',
183
+ multiline = false,
184
+ case_insensitive = false,
185
+ context,
186
+ head_limit,
187
+ }: {
188
+ pattern: string;
189
+ path?: string;
190
+ glob?: string;
191
+ type?: string;
192
+ output_mode?: OutputMode;
193
+ multiline?: boolean;
194
+ case_insensitive?: boolean;
195
+ context?: number;
196
+ head_limit?: number;
197
+ }): string {
198
+ const baseDir = searchPath ? searchPath : process.cwd();
199
+
200
+ let flags = multiline ? 'gms' : 'gm';
201
+ if (case_insensitive) flags += 'i';
202
+
203
+ const regex = new RegExp(pattern, flags);
204
+ const files = collectFiles(baseDir, glob, type);
205
+
206
+ if (files.length === 0) {
207
+ return 'No files found to search.';
208
+ }
209
+
210
+ const results: string[] = [];
211
+
212
+ for (const file of files) {
213
+ try {
214
+ const content = fs.readFileSync(file, 'utf-8');
215
+
216
+ if (output_mode === 'files_with_matches') {
217
+ if (regex.test(content)) {
218
+ results.push(file);
219
+ }
220
+ regex.lastIndex = 0;
221
+ } else if (output_mode === 'count') {
222
+ const matches = content.match(regex);
223
+ if (matches) {
224
+ results.push(`${file}: ${matches.length}`);
225
+ }
226
+ regex.lastIndex = 0;
227
+ } else if (output_mode === 'content') {
228
+ const lines = content.split('\n');
229
+ for (let i = 0; i < lines.length; i++) {
230
+ const lineRegex = new RegExp(pattern, case_insensitive ? 'i' : '');
231
+ if (lineRegex.test(lines[i])) {
232
+ const start = context !== undefined ? Math.max(0, i - context) : i;
233
+ const end =
234
+ context !== undefined
235
+ ? Math.min(lines.length - 1, i + context)
236
+ : i;
237
+ for (let j = start; j <= end; j++) {
238
+ results.push(`${file}:${j + 1}:${lines[j]}`);
239
+ }
240
+ }
241
+ }
242
+ }
243
+ } catch {
244
+ // skip unreadable files
245
+ }
246
+ }
247
+
248
+ if (results.length === 0) {
249
+ return `No matches found for pattern: ${pattern}`;
250
+ }
251
+
252
+ const output = head_limit !== undefined ? results.slice(0, head_limit) : results;
253
+ return output.join('\n');
254
+ },
255
+ };
256
+ }
@@ -0,0 +1,10 @@
1
+ export { ToolResponse } from './response';
2
+ export { Tool } from './base';
3
+ export { Toolkit } from './toolkit';
4
+ export { Bash } from './bash';
5
+ export { Read } from './read';
6
+ export { Write } from './write';
7
+ export { Edit } from './edit';
8
+ export { Glob } from './glob';
9
+ export { Grep } from './grep';
10
+ export { TaskCreate, TaskUpdate, TaskGet, TaskList } from './task';
@@ -0,0 +1,77 @@
1
+ import * as fs from 'fs';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+
5
+ import { Tool } from './base';
6
+ import { Read } from './read';
7
+ import { ToolResponse } from './response';
8
+
9
+ describe('Read', () => {
10
+ let tmpDir: string;
11
+ let read: Tool;
12
+
13
+ beforeEach(() => {
14
+ read = Read();
15
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'read-test-'));
16
+ });
17
+
18
+ afterEach(() => {
19
+ fs.rmSync(tmpDir, { recursive: true, force: true });
20
+ });
21
+
22
+ const getTextFromResponse = (response: ToolResponse): string => {
23
+ const textBlock = response.content.find(block => block.type === 'text');
24
+ return textBlock && 'text' in textBlock ? textBlock.text : '';
25
+ };
26
+
27
+ it('reads a file with line numbers', () => {
28
+ const filePath = path.join(tmpDir, 'test.txt');
29
+ fs.writeFileSync(filePath, 'line1\nline2\nline3');
30
+ const response = read.call!({ file_path: filePath }) as ToolResponse;
31
+ const result = getTextFromResponse(response);
32
+ expect(result).toContain('1\tline1');
33
+ expect(result).toContain('2\tline2');
34
+ expect(result).toContain('3\tline3');
35
+ });
36
+
37
+ it('respects offset and limit', () => {
38
+ const filePath = path.join(tmpDir, 'test.txt');
39
+ fs.writeFileSync(filePath, 'a\nb\nc\nd\ne');
40
+ const response = read.call!({ file_path: filePath, offset: 2, limit: 2 }) as ToolResponse;
41
+ const result = getTextFromResponse(response);
42
+ expect(result).toContain('2\tb');
43
+ expect(result).toContain('3\tc');
44
+ expect(result).not.toContain('1\ta');
45
+ expect(result).not.toContain('4\td');
46
+ });
47
+
48
+ it('throws on relative path', () => {
49
+ expect(() => read.call!({ file_path: 'relative.txt' })).toThrow('absolute path');
50
+ });
51
+
52
+ it('throws on non-existent file', () => {
53
+ expect(() => read.call!({ file_path: path.join(tmpDir, 'nope.txt') })).toThrow(
54
+ 'File not found'
55
+ );
56
+ });
57
+
58
+ it('throws on directory', () => {
59
+ expect(() => read.call!({ file_path: tmpDir })).toThrow('directory');
60
+ });
61
+
62
+ it('returns warning for empty file', () => {
63
+ const filePath = path.join(tmpDir, 'empty.txt');
64
+ fs.writeFileSync(filePath, '');
65
+ const response = read.call!({ file_path: filePath }) as ToolResponse;
66
+ const result = getTextFromResponse(response);
67
+ expect(result).toBe('');
68
+ });
69
+
70
+ it('truncates lines longer than 2000 characters', () => {
71
+ const filePath = path.join(tmpDir, 'long.txt');
72
+ fs.writeFileSync(filePath, 'x'.repeat(2100));
73
+ const response = read.call!({ file_path: filePath }) as ToolResponse;
74
+ const result = getTextFromResponse(response);
75
+ expect(result).toContain('[truncated]');
76
+ });
77
+ });
@@ -0,0 +1,117 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ import { z } from 'zod';
5
+
6
+ import { createToolResponse, ToolResponse } from './response';
7
+
8
+ /**
9
+ * Tool for reading files from the local filesystem.
10
+ * Returns file contents with line numbers in cat -n format.
11
+ *
12
+ * @returns A Tool object for reading files, with a call method that performs the read operation and returns the formatted contents or a warning if the file is empty.
13
+ */
14
+ export function Read() {
15
+ return {
16
+ name: 'Read',
17
+ description: `Reads a file from the local filesystem. You can access any file directly by using this tool.
18
+ Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
19
+
20
+ Usage:
21
+ - The file_path parameter must be an absolute path, not a relative path
22
+ - By default, it reads up to 2000 lines starting from the beginning of the file
23
+ - You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
24
+ - Any lines longer than 2000 characters will be truncated
25
+ - Results are returned using cat -n format, with line numbers starting at 1
26
+ - This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
27
+ - You can call multiple tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
28
+ - If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.`,
29
+ inputSchema: z.object({
30
+ file_path: z.string().describe('The absolute path to the file to read'),
31
+ offset: z
32
+ .number()
33
+ .int()
34
+ .positive()
35
+ .optional()
36
+ .describe(
37
+ 'The line number to start reading from. Only provide if the file is too large to read at once'
38
+ ),
39
+ limit: z
40
+ .number()
41
+ .int()
42
+ .positive()
43
+ .optional()
44
+ .describe(
45
+ 'The number of lines to read. Only provide if the file is too large to read at once'
46
+ ),
47
+ }),
48
+ requireUserConfirm: true,
49
+
50
+ /**
51
+ * Reads a file and returns its contents with line numbers.
52
+ *
53
+ * @param root0 - The parameters object
54
+ * @param root0.file_path - Absolute path to the file to read
55
+ * @param root0.offset - Line number to start reading from (1-based)
56
+ * @param root0.limit - Maximum number of lines to read (capped at 2000)
57
+ * @returns The file contents formatted with line numbers, or a warning if the file is empty
58
+ * @throws If the path is not absolute, the file does not exist, or the path is a directory
59
+ */
60
+ call({
61
+ file_path,
62
+ offset,
63
+ limit,
64
+ }: {
65
+ file_path: string;
66
+ offset?: number;
67
+ limit?: number;
68
+ }): ToolResponse {
69
+ if (!path.isAbsolute(file_path)) {
70
+ throw new Error(`file_path must be an absolute path, got: ${file_path}`);
71
+ }
72
+
73
+ if (!fs.existsSync(file_path)) {
74
+ throw new Error(`File not found: ${file_path}`);
75
+ }
76
+
77
+ const stat = fs.statSync(file_path);
78
+ if (stat.isDirectory()) {
79
+ throw new Error(
80
+ `${file_path} is a directory, not a file. Use Bash with ls to read directories.`
81
+ );
82
+ }
83
+
84
+ const rawContent = fs.readFileSync(file_path, 'utf-8');
85
+
86
+ if (rawContent.length === 0) {
87
+ return createToolResponse({
88
+ content: [{ id: crypto.randomUUID(), type: 'text', text: rawContent }],
89
+ state: 'success',
90
+ });
91
+ }
92
+
93
+ const allLines = rawContent.split('\n');
94
+ const startLine = offset !== undefined ? offset - 1 : 0;
95
+ const maxLines = 2000;
96
+ const effectiveLimit = limit !== undefined ? Math.min(limit, maxLines) : maxLines;
97
+ const selectedLines = allLines.slice(startLine, startLine + effectiveLimit);
98
+
99
+ const maxLineLength = 2000;
100
+ const formatted = selectedLines
101
+ .map((line, i) => {
102
+ const lineNum = startLine + i + 1;
103
+ const truncated =
104
+ line.length > maxLineLength
105
+ ? line.substring(0, maxLineLength) + '[truncated]'
106
+ : line;
107
+ return `${String(lineNum).padStart(6)}\t${truncated}`;
108
+ })
109
+ .join('\n');
110
+
111
+ return createToolResponse({
112
+ content: [{ id: crypto.randomUUID(), type: 'text', text: formatted }],
113
+ state: 'success',
114
+ });
115
+ },
116
+ };
117
+ }
@@ -0,0 +1,82 @@
1
+ import { DataBlock, TextBlock } from '../message';
2
+ import { JSONSerializableObject } from '../type';
3
+
4
+ /**
5
+ * The tool response structure.
6
+ */
7
+ export interface ToolResponse {
8
+ content: Array<TextBlock | DataBlock>;
9
+ id: string;
10
+ createdAt: string;
11
+ metadata: Record<string, JSONSerializableObject>;
12
+ state: 'success' | 'error' | 'interrupted' | 'running';
13
+ isLast: boolean;
14
+ isInterrupted: boolean;
15
+ }
16
+
17
+ /**
18
+ * Create a tool response object with the given parameters.
19
+ *
20
+ * @param root0
21
+ * @param root0.content
22
+ * @param root0.state
23
+ * @param root0.id
24
+ * @param root0.createdAt
25
+ * @param root0.metadata
26
+ * @param root0.stream
27
+ * @param root0.isLast
28
+ * @param root0.isInterrupted
29
+ * @returns A ToolResponse object
30
+ */
31
+ export function createToolResponse({
32
+ content,
33
+ state,
34
+ id = crypto.randomUUID(),
35
+ createdAt = new Date().toISOString(),
36
+ metadata = {},
37
+ stream = false,
38
+ isLast = true,
39
+ isInterrupted = false,
40
+ }: {
41
+ content: Array<TextBlock | DataBlock>;
42
+ state: 'success' | 'error' | 'interrupted' | 'running';
43
+ id?: string;
44
+ createdAt?: string;
45
+ metadata?: Record<string, JSONSerializableObject>;
46
+ stream?: boolean;
47
+ isLast?: boolean;
48
+ isInterrupted?: boolean;
49
+ }) {
50
+ return {
51
+ content,
52
+ id,
53
+ createdAt,
54
+ metadata,
55
+ state,
56
+ stream,
57
+ isLast,
58
+ isInterrupted,
59
+ } as ToolResponse;
60
+ }
61
+
62
+ /**
63
+ * If the given object conforms to the ToolResponse structure.
64
+ *
65
+ * @param obj
66
+ * @returns True if the object is a ToolResponse, false otherwise
67
+ */
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ export function isToolResponse(obj: any): boolean {
70
+ return (
71
+ obj &&
72
+ typeof obj === 'object' &&
73
+ typeof obj.id === 'string' &&
74
+ typeof obj.createdAt === 'string' &&
75
+ Array.isArray(obj.content) &&
76
+ typeof obj.metadata === 'object' &&
77
+ typeof obj.stream === 'boolean' &&
78
+ typeof obj.isLast === 'boolean' &&
79
+ typeof obj.isInterrupted === 'boolean' &&
80
+ ['success', 'error', 'interrupted', 'running'].includes(obj.state)
81
+ );
82
+ }