anyclaude-sdk 0.1.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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/dist/agent.d.ts +110 -0
  4. package/dist/agent.js +897 -0
  5. package/dist/background/index.d.ts +3 -0
  6. package/dist/background/index.js +9 -0
  7. package/dist/background/manager.d.ts +32 -0
  8. package/dist/background/manager.js +108 -0
  9. package/dist/background/tools.d.ts +5 -0
  10. package/dist/background/tools.js +98 -0
  11. package/dist/background/worker.d.ts +19 -0
  12. package/dist/background/worker.js +30 -0
  13. package/dist/commands/builtins.d.ts +2 -0
  14. package/dist/commands/builtins.js +306 -0
  15. package/dist/commands/index.d.ts +21 -0
  16. package/dist/commands/index.js +56 -0
  17. package/dist/commands/types.d.ts +110 -0
  18. package/dist/commands/types.js +5 -0
  19. package/dist/compact.d.ts +22 -0
  20. package/dist/compact.js +67 -0
  21. package/dist/fs/dexie.d.ts +57 -0
  22. package/dist/fs/dexie.js +243 -0
  23. package/dist/fs/index.d.ts +4 -0
  24. package/dist/fs/index.js +13 -0
  25. package/dist/fs/linuxTree.d.ts +11 -0
  26. package/dist/fs/linuxTree.js +43 -0
  27. package/dist/fs/opfs.d.ts +23 -0
  28. package/dist/fs/opfs.js +112 -0
  29. package/dist/index.d.ts +26 -0
  30. package/dist/index.js +29 -0
  31. package/dist/llm/anthropic.d.ts +24 -0
  32. package/dist/llm/anthropic.js +280 -0
  33. package/dist/llm/index.d.ts +3 -0
  34. package/dist/llm/index.js +3 -0
  35. package/dist/llm/inlineTools.d.ts +11 -0
  36. package/dist/llm/inlineTools.js +72 -0
  37. package/dist/llm/openai.d.ts +29 -0
  38. package/dist/llm/openai.js +224 -0
  39. package/dist/llm/responses.d.ts +18 -0
  40. package/dist/llm/responses.js +256 -0
  41. package/dist/mcp/client.d.ts +20 -0
  42. package/dist/mcp/client.js +156 -0
  43. package/dist/mcp/index.d.ts +24 -0
  44. package/dist/mcp/index.js +157 -0
  45. package/dist/mcp/proxy.d.ts +3 -0
  46. package/dist/mcp/proxy.js +25 -0
  47. package/dist/mcp/sdkServer.d.ts +21 -0
  48. package/dist/mcp/sdkServer.js +28 -0
  49. package/dist/mcp/types.d.ts +92 -0
  50. package/dist/mcp/types.js +5 -0
  51. package/dist/memory/index.d.ts +4 -0
  52. package/dist/memory/index.js +5 -0
  53. package/dist/memory/render.d.ts +7 -0
  54. package/dist/memory/render.js +46 -0
  55. package/dist/memory/store.d.ts +20 -0
  56. package/dist/memory/store.js +79 -0
  57. package/dist/memory/tools.d.ts +5 -0
  58. package/dist/memory/tools.js +95 -0
  59. package/dist/memory/types.d.ts +15 -0
  60. package/dist/memory/types.js +4 -0
  61. package/dist/permissions/dangerous.d.ts +4 -0
  62. package/dist/permissions/dangerous.js +24 -0
  63. package/dist/permissions/gate.d.ts +21 -0
  64. package/dist/permissions/gate.js +66 -0
  65. package/dist/permissions/index.d.ts +5 -0
  66. package/dist/permissions/index.js +6 -0
  67. package/dist/permissions/match.d.ts +19 -0
  68. package/dist/permissions/match.js +104 -0
  69. package/dist/permissions/planMode.d.ts +3 -0
  70. package/dist/permissions/planMode.js +33 -0
  71. package/dist/permissions/types.d.ts +19 -0
  72. package/dist/permissions/types.js +2 -0
  73. package/dist/persist.d.ts +15 -0
  74. package/dist/persist.js +58 -0
  75. package/dist/prompt.d.ts +6 -0
  76. package/dist/prompt.js +34 -0
  77. package/dist/query.d.ts +105 -0
  78. package/dist/query.js +115 -0
  79. package/dist/queue.d.ts +23 -0
  80. package/dist/queue.js +43 -0
  81. package/dist/sandbox/cloudflare.d.ts +48 -0
  82. package/dist/sandbox/cloudflare.js +124 -0
  83. package/dist/sandbox/daytona.d.ts +48 -0
  84. package/dist/sandbox/daytona.js +79 -0
  85. package/dist/sandbox/e2b.d.ts +54 -0
  86. package/dist/sandbox/e2b.js +87 -0
  87. package/dist/sandbox/index.d.ts +8 -0
  88. package/dist/sandbox/index.js +19 -0
  89. package/dist/sandbox/local.d.ts +51 -0
  90. package/dist/sandbox/local.js +155 -0
  91. package/dist/sandbox/types.d.ts +18 -0
  92. package/dist/sandbox/types.js +27 -0
  93. package/dist/sandbox/util.d.ts +15 -0
  94. package/dist/sandbox/util.js +100 -0
  95. package/dist/sandbox/vercel.d.ts +48 -0
  96. package/dist/sandbox/vercel.js +130 -0
  97. package/dist/session/index.d.ts +2 -0
  98. package/dist/session/index.js +6 -0
  99. package/dist/session/store.d.ts +28 -0
  100. package/dist/session/store.js +122 -0
  101. package/dist/session/types.d.ts +22 -0
  102. package/dist/session/types.js +2 -0
  103. package/dist/settings/index.d.ts +3 -0
  104. package/dist/settings/index.js +3 -0
  105. package/dist/settings/load.d.ts +20 -0
  106. package/dist/settings/load.js +36 -0
  107. package/dist/settings/merge.d.ts +13 -0
  108. package/dist/settings/merge.js +65 -0
  109. package/dist/settings/types.d.ts +17 -0
  110. package/dist/settings/types.js +3 -0
  111. package/dist/skills/index.d.ts +4 -0
  112. package/dist/skills/index.js +5 -0
  113. package/dist/skills/load.d.ts +23 -0
  114. package/dist/skills/load.js +54 -0
  115. package/dist/skills/parse.d.ts +7 -0
  116. package/dist/skills/parse.js +40 -0
  117. package/dist/skills/tool.d.ts +2 -0
  118. package/dist/skills/tool.js +39 -0
  119. package/dist/skills/types.d.ts +10 -0
  120. package/dist/skills/types.js +4 -0
  121. package/dist/team/dispatch.d.ts +2 -0
  122. package/dist/team/dispatch.js +41 -0
  123. package/dist/team/index.d.ts +9 -0
  124. package/dist/team/index.js +11 -0
  125. package/dist/team/mailbox.d.ts +24 -0
  126. package/dist/team/mailbox.js +33 -0
  127. package/dist/team/prompt.d.ts +1 -0
  128. package/dist/team/prompt.js +12 -0
  129. package/dist/team/runner.d.ts +20 -0
  130. package/dist/team/runner.js +45 -0
  131. package/dist/team/taskBoard.d.ts +41 -0
  132. package/dist/team/taskBoard.js +73 -0
  133. package/dist/team/tools.d.ts +7 -0
  134. package/dist/team/tools.js +190 -0
  135. package/dist/tools/bash.d.ts +2 -0
  136. package/dist/tools/bash.js +45 -0
  137. package/dist/tools/config.d.ts +2 -0
  138. package/dist/tools/config.js +44 -0
  139. package/dist/tools/define.d.ts +18 -0
  140. package/dist/tools/define.js +21 -0
  141. package/dist/tools/delete_file.d.ts +2 -0
  142. package/dist/tools/delete_file.js +33 -0
  143. package/dist/tools/edit_file.d.ts +2 -0
  144. package/dist/tools/edit_file.js +93 -0
  145. package/dist/tools/fileTypes.d.ts +32 -0
  146. package/dist/tools/fileTypes.js +166 -0
  147. package/dist/tools/glob.d.ts +2 -0
  148. package/dist/tools/glob.js +53 -0
  149. package/dist/tools/grep.d.ts +2 -0
  150. package/dist/tools/grep.js +110 -0
  151. package/dist/tools/imageProcessor.d.ts +15 -0
  152. package/dist/tools/imageProcessor.js +83 -0
  153. package/dist/tools/index.d.ts +28 -0
  154. package/dist/tools/index.js +45 -0
  155. package/dist/tools/list_files.d.ts +2 -0
  156. package/dist/tools/list_files.js +42 -0
  157. package/dist/tools/multi_edit.d.ts +2 -0
  158. package/dist/tools/multi_edit.js +112 -0
  159. package/dist/tools/notebook_edit.d.ts +2 -0
  160. package/dist/tools/notebook_edit.js +118 -0
  161. package/dist/tools/plan_mode.d.ts +4 -0
  162. package/dist/tools/plan_mode.js +44 -0
  163. package/dist/tools/read_file.d.ts +2 -0
  164. package/dist/tools/read_file.js +193 -0
  165. package/dist/tools/task.d.ts +2 -0
  166. package/dist/tools/task.js +77 -0
  167. package/dist/tools/todo_write.d.ts +2 -0
  168. package/dist/tools/todo_write.js +104 -0
  169. package/dist/tools/tool_search.d.ts +2 -0
  170. package/dist/tools/tool_search.js +49 -0
  171. package/dist/tools/types.d.ts +82 -0
  172. package/dist/tools/types.js +1 -0
  173. package/dist/tools/walk.d.ts +29 -0
  174. package/dist/tools/walk.js +82 -0
  175. package/dist/tools/web_fetch.d.ts +2 -0
  176. package/dist/tools/web_fetch.js +76 -0
  177. package/dist/tools/web_search.d.ts +22 -0
  178. package/dist/tools/web_search.js +195 -0
  179. package/dist/tools/write_file.d.ts +2 -0
  180. package/dist/tools/write_file.js +39 -0
  181. package/dist/types/index.d.ts +363 -0
  182. package/dist/types/index.js +9 -0
  183. package/dist/util/ids.d.ts +3 -0
  184. package/dist/util/ids.js +22 -0
  185. package/dist/util/paths.d.ts +16 -0
  186. package/dist/util/paths.js +72 -0
  187. package/dist/util/pricing.d.ts +15 -0
  188. package/dist/util/pricing.js +81 -0
  189. package/dist/workspace/index.d.ts +2 -0
  190. package/dist/workspace/index.js +2 -0
  191. package/dist/workspace/memory.d.ts +28 -0
  192. package/dist/workspace/memory.js +97 -0
  193. package/dist/workspace/webcontainer.d.ts +65 -0
  194. package/dist/workspace/webcontainer.js +156 -0
  195. package/package.json +78 -0
@@ -0,0 +1,2 @@
1
+ import type { Tool } from './types.js';
2
+ export declare const notebookEdit: Tool;
@@ -0,0 +1,118 @@
1
+ const DESCRIPTION = `Edits a cell in a Jupyter notebook (.ipynb file).
2
+
3
+ - \`cell_id\` is the 0-indexed cell position.
4
+ - edit_mode=replace (default): completely replaces the source of the cell at cell_id.
5
+ - edit_mode=insert: inserts a new cell at index cell_id (use cell_type to choose code/markdown).
6
+ - edit_mode=delete: removes the cell at cell_id.
7
+ - New code cells are created with empty outputs and a null execution_count.`;
8
+ /** Store source as an array of newline-terminated lines (the canonical .ipynb form). */
9
+ function toSourceLines(text) {
10
+ const parts = text.split('\n');
11
+ return parts.map((line, i) => (i < parts.length - 1 ? line + '\n' : line));
12
+ }
13
+ export const notebookEdit = {
14
+ def: {
15
+ type: 'function',
16
+ function: {
17
+ name: 'notebook_edit',
18
+ description: DESCRIPTION,
19
+ parameters: {
20
+ type: 'object',
21
+ properties: {
22
+ path: { type: 'string', description: 'Path to the .ipynb notebook.' },
23
+ cell_id: {
24
+ type: ['number', 'string'],
25
+ description: '0-indexed cell position to edit/insert/delete.',
26
+ },
27
+ new_source: {
28
+ type: 'string',
29
+ description: 'New cell source (ignored for delete).',
30
+ },
31
+ cell_type: {
32
+ type: 'string',
33
+ enum: ['code', 'markdown'],
34
+ description: 'Cell type for replace/insert. Defaults to code.',
35
+ },
36
+ edit_mode: {
37
+ type: 'string',
38
+ enum: ['replace', 'insert', 'delete'],
39
+ description: 'Operation to perform. Defaults to replace.',
40
+ },
41
+ },
42
+ required: ['path', 'new_source'],
43
+ },
44
+ },
45
+ },
46
+ async run(input, ctx) {
47
+ const path = String(input.path ?? '');
48
+ if (!path)
49
+ return { content: 'Error: `path` is required.', isError: true };
50
+ const editMode = input.edit_mode ?? 'replace';
51
+ if (!['replace', 'insert', 'delete'].includes(editMode)) {
52
+ return { content: `Error: invalid edit_mode "${editMode}".`, isError: true };
53
+ }
54
+ const raw = await ctx.fs.readFile(path);
55
+ if (raw === null)
56
+ return { content: `Error: file not found: ${path}`, isError: true };
57
+ let nb;
58
+ try {
59
+ nb = JSON.parse(raw);
60
+ }
61
+ catch (err) {
62
+ return { content: `Error: ${path} is not valid JSON: ${err.message}`, isError: true };
63
+ }
64
+ if (!nb || !Array.isArray(nb.cells)) {
65
+ return { content: `Error: ${path} is not a valid notebook (missing cells array).`, isError: true };
66
+ }
67
+ const index = Number(input.cell_id ?? 0);
68
+ if (!Number.isInteger(index) || index < 0) {
69
+ return { content: `Error: cell_id must be a non-negative integer.`, isError: true };
70
+ }
71
+ const newSource = typeof input.new_source === 'string' ? input.new_source : '';
72
+ const cellType = input.cell_type ?? 'code';
73
+ if (editMode === 'delete') {
74
+ if (index >= nb.cells.length) {
75
+ return { content: `Error: cell index ${index} out of range (notebook has ${nb.cells.length} cells).`, isError: true };
76
+ }
77
+ nb.cells.splice(index, 1);
78
+ }
79
+ else if (editMode === 'insert') {
80
+ if (index > nb.cells.length) {
81
+ return { content: `Error: insert index ${index} out of range (notebook has ${nb.cells.length} cells).`, isError: true };
82
+ }
83
+ const cell = {
84
+ cell_type: cellType,
85
+ metadata: {},
86
+ source: toSourceLines(newSource),
87
+ };
88
+ if (cellType === 'code') {
89
+ cell.outputs = [];
90
+ cell.execution_count = null;
91
+ }
92
+ nb.cells.splice(index, 0, cell);
93
+ }
94
+ else {
95
+ // replace
96
+ if (index >= nb.cells.length) {
97
+ return { content: `Error: cell index ${index} out of range (notebook has ${nb.cells.length} cells).`, isError: true };
98
+ }
99
+ const cell = nb.cells[index];
100
+ cell.source = toSourceLines(newSource);
101
+ if (input.cell_type) {
102
+ cell.cell_type = cellType;
103
+ if (cellType === 'code' && cell.outputs === undefined) {
104
+ cell.outputs = [];
105
+ cell.execution_count = null;
106
+ }
107
+ }
108
+ }
109
+ try {
110
+ await ctx.fs.writeFile(path, JSON.stringify(nb, null, 2));
111
+ }
112
+ catch (err) {
113
+ return { content: `Error writing ${path}: ${err.message}`, isError: true };
114
+ }
115
+ const verb = editMode === 'delete' ? 'Deleted' : editMode === 'insert' ? 'Inserted' : 'Replaced';
116
+ return { content: `${verb} cell ${index} in ${path}. Notebook now has ${nb.cells.length} cells.` };
117
+ },
118
+ };
@@ -0,0 +1,4 @@
1
+ import type { Tool } from './types.js';
2
+ export declare const enterPlanMode: Tool;
3
+ export declare const exitPlanMode: Tool;
4
+ export declare const PLAN_MODE_TOOLS: Tool[];
@@ -0,0 +1,44 @@
1
+ const ENTER_DESC = `Enter plan mode: research and design WITHOUT making changes.
2
+
3
+ While in plan mode, mutating tools (write_file, edit_file, bash that changes state, etc.) are blocked. Use read-only tools to investigate, then present a clear plan and call exit_plan_mode to proceed.`;
4
+ const EXIT_DESC = `Exit plan mode and present your plan.
5
+
6
+ Pass the finalized \`plan\` (markdown). After this, mutating tools are allowed again so you can execute.`;
7
+ export const enterPlanMode = {
8
+ def: {
9
+ type: 'function',
10
+ function: {
11
+ name: 'enter_plan_mode',
12
+ description: ENTER_DESC,
13
+ parameters: { type: 'object', properties: {}, required: [] },
14
+ },
15
+ },
16
+ async run(_input, ctx) {
17
+ if (!ctx.planMode)
18
+ return { content: 'Plan mode is not available in this session.', isError: true };
19
+ ctx.planMode.active = true;
20
+ return { content: 'Entered plan mode. Mutating tools are disabled; investigate with read-only tools, then call exit_plan_mode with your plan.' };
21
+ },
22
+ };
23
+ export const exitPlanMode = {
24
+ def: {
25
+ type: 'function',
26
+ function: {
27
+ name: 'exit_plan_mode',
28
+ description: EXIT_DESC,
29
+ parameters: {
30
+ type: 'object',
31
+ properties: { plan: { type: 'string', description: 'The finalized plan (markdown).' } },
32
+ required: [],
33
+ },
34
+ },
35
+ },
36
+ async run(input, ctx) {
37
+ if (!ctx.planMode)
38
+ return { content: 'Plan mode is not available in this session.', isError: true };
39
+ ctx.planMode.active = false;
40
+ const plan = String(input.plan ?? '').trim();
41
+ return { content: `Exited plan mode. Mutating tools are enabled.${plan ? '\n\nPlan:\n' + plan : ''}` };
42
+ },
43
+ };
44
+ export const PLAN_MODE_TOOLS = [enterPlanMode, exitPlanMode];
@@ -0,0 +1,2 @@
1
+ import type { Tool } from './types.js';
2
+ export declare const readFile: Tool;
@@ -0,0 +1,193 @@
1
+ import { DEFAULT_FILE_READ_LIMITS, bytesToBase64, detectImageFormatFromBytes, extOf, formatBytes, hasBinaryExtension, imageMediaType, isImageExtension, isNotebookExtension, isPdfExtension, looksBinary, roughTokenCount, } from './fileTypes.js';
2
+ import { processImage } from './imageProcessor.js';
3
+ const DESCRIPTION = `Reads a file from the workspace filesystem.
4
+
5
+ - \`path\` may be absolute or relative to the workspace root.
6
+ - Text files are returned with line numbers (cat -n style). Use \`offset\`/\`limit\` (1-based lines) to read a slice of large files.
7
+ - Image files (png/jpg/jpeg/gif/webp/bmp) are returned as a viewable image block, downsampled if large.
8
+ - PDF files are returned as a document block for the model to read directly.
9
+ - Jupyter notebooks (.ipynb) are rendered as cells with their outputs.
10
+ - Reads are capped by size and token count; very large text files must be read with offset/limit or searched with grep.`;
11
+ const PARAMS = {
12
+ type: 'object',
13
+ properties: {
14
+ path: { type: 'string', description: 'Path to the file to read.' },
15
+ offset: { type: 'number', description: 'Line number to start reading from (1-based, text files).' },
16
+ limit: { type: 'number', description: 'Maximum number of lines to read (text files).' },
17
+ pages: { type: 'string', description: 'PDF page range hint, e.g. "1-5,8" (informational only).' },
18
+ },
19
+ required: ['path'],
20
+ };
21
+ function limitsOf(ctx) {
22
+ return { ...DEFAULT_FILE_READ_LIMITS, ...(ctx.limits ?? {}) };
23
+ }
24
+ function baseName(path) {
25
+ const slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
26
+ return slash === -1 ? path : path.slice(slash + 1);
27
+ }
28
+ const NOT_FOUND = (path) => `Error: file not found: ${path}. Check the path is correct and relative to the workspace root, or use list_files/glob to locate it.`;
29
+ const CELL_OUTPUT_CAP = 10_000;
30
+ function joinSource(src) {
31
+ if (!src)
32
+ return '';
33
+ return Array.isArray(src) ? src.join('') : src;
34
+ }
35
+ function truncate(text, cap) {
36
+ return text.length > cap ? text.slice(0, cap) + `\n… [output truncated, ${text.length - cap} more chars]` : text;
37
+ }
38
+ function renderOutput(out) {
39
+ switch (out.output_type) {
40
+ case 'stream':
41
+ return truncate(joinSource(out.text), CELL_OUTPUT_CAP);
42
+ case 'execute_result':
43
+ case 'display_data': {
44
+ const text = joinSource(out.data?.['text/plain']);
45
+ const hasImg = typeof out.data?.['image/png'] === 'string' || typeof out.data?.['image/jpeg'] === 'string';
46
+ const parts = [text ? truncate(text, CELL_OUTPUT_CAP) : '', hasImg ? '[image output]' : ''];
47
+ return parts.filter(Boolean).join('\n');
48
+ }
49
+ case 'error':
50
+ return truncate(`${out.ename ?? 'Error'}: ${out.evalue ?? ''}\n${(out.traceback ?? []).join('\n')}`, CELL_OUTPUT_CAP);
51
+ default:
52
+ return '';
53
+ }
54
+ }
55
+ function renderNotebook(text) {
56
+ let nb;
57
+ try {
58
+ nb = JSON.parse(text);
59
+ }
60
+ catch {
61
+ return 'Error: notebook is not valid JSON.';
62
+ }
63
+ const cells = nb.cells ?? [];
64
+ const out = [];
65
+ cells.forEach((cell, i) => {
66
+ const kind = cell.cell_type ?? 'unknown';
67
+ out.push(`#%% [cell ${i + 1}] (${kind})`);
68
+ const source = joinSource(cell.source);
69
+ if (source)
70
+ out.push(source);
71
+ if (kind === 'code' && cell.outputs?.length) {
72
+ const rendered = cell.outputs.map(renderOutput).filter(Boolean).join('\n');
73
+ if (rendered)
74
+ out.push('# --- output ---\n' + rendered);
75
+ }
76
+ out.push('');
77
+ });
78
+ return out.join('\n').trimEnd() || '(empty notebook)';
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // Tool
82
+ // ---------------------------------------------------------------------------
83
+ export const readFile = {
84
+ def: { type: 'function', function: { name: 'read_file', description: DESCRIPTION, parameters: PARAMS } },
85
+ // read_file is already self-bounded by size/token caps; never spill its
86
+ // output to a file (that would be circular — the model reads files with this).
87
+ maxResultChars: Infinity,
88
+ async run(input, ctx) {
89
+ const path = String(input.path ?? '');
90
+ if (!path)
91
+ return { content: 'Error: `path` is required.', isError: true };
92
+ const limits = limitsOf(ctx);
93
+ const ext = extOf(path);
94
+ const name = baseName(path);
95
+ // ---- Image ----
96
+ if (isImageExtension(path)) {
97
+ const bytes = await ctx.fs.readBinary(path);
98
+ if (!bytes)
99
+ return { content: NOT_FOUND(path), isError: true };
100
+ ctx.readFiles.add(path);
101
+ const media = detectImageFormatFromBytes(bytes) ?? imageMediaType(ext);
102
+ const img = await processImage(bytes, media, limits.maxImageBytes);
103
+ const dims = img.width && img.height ? `${img.width}x${img.height}, ` : '';
104
+ const meta = `Image: ${name} (${dims}${formatBytes(bytes.length)}${img.media_type !== media ? `, re-encoded as ${img.media_type}` : ''}).${img.note ? ' ' + img.note : ''}`;
105
+ const blocks = [
106
+ { type: 'image', source: { type: 'base64', media_type: img.media_type, data: img.data } },
107
+ { type: 'text', text: meta },
108
+ ];
109
+ return { content: blocks };
110
+ }
111
+ // ---- PDF ----
112
+ if (isPdfExtension(path)) {
113
+ const bytes = await ctx.fs.readBinary(path);
114
+ if (!bytes)
115
+ return { content: NOT_FOUND(path), isError: true };
116
+ ctx.readFiles.add(path);
117
+ const large = bytes.length > 3 * 1024 * 1024;
118
+ const pagesNote = typeof input.pages === 'string' && input.pages.trim()
119
+ ? ` Requested pages "${input.pages}" — page-range extraction is not performed client-side; the full document is provided.`
120
+ : '';
121
+ const doc = {
122
+ type: 'document',
123
+ source: { type: 'base64', media_type: 'application/pdf', data: bytesToBase64(bytes) },
124
+ title: name,
125
+ };
126
+ const note = {
127
+ type: 'text',
128
+ text: `PDF: ${name} (${formatBytes(bytes.length)}).${large ? ' Large file.' : ''}${pagesNote} The full document is attached for the model to read.`,
129
+ };
130
+ return { content: [doc, note] };
131
+ }
132
+ // ---- Notebook ----
133
+ if (isNotebookExtension(path)) {
134
+ const text = await ctx.fs.readFile(path);
135
+ if (text === null)
136
+ return { content: NOT_FOUND(path), isError: true };
137
+ ctx.readFiles.add(path);
138
+ return { content: renderNotebook(text) };
139
+ }
140
+ // ---- Binary by extension ----
141
+ if (hasBinaryExtension(path)) {
142
+ return {
143
+ content: `Error: cannot read binary file ${name} (${ext || 'unknown type'}) as text. Use the bash tool or a dedicated tool if you need its contents.`,
144
+ isError: true,
145
+ };
146
+ }
147
+ // ---- Text (with binary sniff) ----
148
+ const bytes = await ctx.fs.readBinary(path);
149
+ if (bytes === null)
150
+ return { content: NOT_FOUND(path), isError: true };
151
+ if (looksBinary(bytes)) {
152
+ return {
153
+ content: `Error: ${name} appears to be a binary file (contains NUL bytes) and cannot be read as text. Use the bash tool if needed.`,
154
+ isError: true,
155
+ };
156
+ }
157
+ if (bytes.length > limits.maxSizeBytes) {
158
+ return {
159
+ content: `Error: file ${name} is ${formatBytes(bytes.length)}, exceeding the ${formatBytes(limits.maxSizeBytes)} read limit. Read a slice with offset/limit, or search with grep instead of reading the whole file.`,
160
+ isError: true,
161
+ };
162
+ }
163
+ const text = await ctx.fs.readFile(path);
164
+ if (text === null)
165
+ return { content: NOT_FOUND(path), isError: true };
166
+ ctx.readFiles.add(path);
167
+ if (text === '')
168
+ return { content: '(file is empty)' };
169
+ const lines = text.split('\n');
170
+ const offset = typeof input.offset === 'number' && input.offset > 0 ? Math.floor(input.offset) : 1;
171
+ const limit = typeof input.limit === 'number' && input.limit > 0 ? Math.floor(input.limit) : lines.length;
172
+ const start = offset - 1;
173
+ const slice = lines.slice(start, start + limit);
174
+ // cat -n style: right-aligned line number, tab, content.
175
+ const lastNum = start + slice.length;
176
+ const width = String(lastNum).length;
177
+ const numbered = slice
178
+ .map((line, i) => `${String(start + i + 1).padStart(width, ' ')}\t${line}`)
179
+ .join('\n');
180
+ const tokens = roughTokenCount(numbered);
181
+ if (tokens > limits.maxTokens) {
182
+ return {
183
+ content: `Error: the requested content is ~${tokens} tokens, exceeding the ${limits.maxTokens}-token cap. Narrow the read with offset/limit, or search with grep.`,
184
+ isError: true,
185
+ };
186
+ }
187
+ const shownEnd = start + slice.length;
188
+ const note = shownEnd < lines.length || start > 0
189
+ ? `\n\n[showing lines ${offset}-${shownEnd} of ${lines.length}]`
190
+ : '';
191
+ return { content: numbered + note };
192
+ },
193
+ };
@@ -0,0 +1,2 @@
1
+ import type { Tool } from './types.js';
2
+ export declare const task: Tool;
@@ -0,0 +1,77 @@
1
+ const DESCRIPTION = `Launch a sub-agent to handle a complex, multi-step task autonomously.
2
+
3
+ - Provide a short \`description\` (3-5 words) and a detailed \`prompt\` describing exactly what the sub-agent should do.
4
+ - Optionally set \`subagent_type\` to select a configured agent; otherwise a general-purpose agent is used.
5
+ - The sub-agent runs its own tool loop to completion and returns only its final result — its intermediate steps do not enter this conversation.
6
+ - Use this to parallelize or isolate self-contained work (research, broad searches, multi-file changes). For a single quick action, just call the relevant tool directly.`;
7
+ export const task = {
8
+ def: {
9
+ type: 'function',
10
+ function: {
11
+ name: 'task',
12
+ description: DESCRIPTION,
13
+ parameters: {
14
+ type: 'object',
15
+ properties: {
16
+ description: {
17
+ type: 'string',
18
+ description: 'A short (3-5 word) description of the task.',
19
+ },
20
+ prompt: {
21
+ type: 'string',
22
+ description: 'The detailed task for the sub-agent to perform.',
23
+ },
24
+ subagent_type: {
25
+ type: 'string',
26
+ description: 'Optional name of a configured agent type to use.',
27
+ },
28
+ run_in_background: {
29
+ type: 'boolean',
30
+ description: 'Run the sub-agent in the background and return immediately with a task id. Poll it with task_output/task_list.',
31
+ },
32
+ },
33
+ required: ['description', 'prompt'],
34
+ },
35
+ },
36
+ },
37
+ async run(input, ctx) {
38
+ if (!ctx.runSubagent) {
39
+ return {
40
+ content: 'Sub-agents are not enabled for this session.',
41
+ isError: true,
42
+ };
43
+ }
44
+ const prompt = String(input.prompt ?? '').trim();
45
+ if (!prompt)
46
+ return { content: 'Error: `prompt` is required.', isError: true };
47
+ const description = String(input.description ?? '').trim();
48
+ const agentType = input.subagent_type ? String(input.subagent_type) : undefined;
49
+ // Background mode: detach via the BackgroundTaskManager and return a handle.
50
+ if (input.run_in_background) {
51
+ if (!ctx.background) {
52
+ return {
53
+ content: 'Background tasks are not enabled for this session.',
54
+ isError: true,
55
+ };
56
+ }
57
+ const id = ctx.background.start(description || 'sub-agent', async (signal, append) => {
58
+ const r = await ctx.runSubagent({
59
+ description,
60
+ prompt,
61
+ agentType,
62
+ signal, // so task_stop actually aborts the sub-agent
63
+ onProgress: (t) => append(t.endsWith('\n') ? t : t + '\n'), // stream progress to task_output
64
+ });
65
+ return r.text;
66
+ });
67
+ return {
68
+ content: `Started background task ${id} (${description || 'sub-agent'}). Use task_output with task_id "${id}" to read its results, or task_list to see status.`,
69
+ };
70
+ }
71
+ const result = await ctx.runSubagent({ description, prompt, agentType });
72
+ return {
73
+ content: result.text || '(sub-agent produced no output)',
74
+ isError: result.isError,
75
+ };
76
+ },
77
+ };
@@ -0,0 +1,2 @@
1
+ import type { Tool } from './types.js';
2
+ export declare const todoWrite: Tool;
@@ -0,0 +1,104 @@
1
+ const DESCRIPTION = `Create and manage a structured task list for the current session.
2
+
3
+ ## When to use
4
+ - Complex multi-step tasks (3+ distinct steps).
5
+ - Non-trivial tasks that benefit from planning.
6
+ - When the user provides multiple tasks, or explicitly asks for a todo list.
7
+
8
+ ## When NOT to use
9
+ - A single, straightforward, or trivial task.
10
+ - Purely conversational/informational requests.
11
+
12
+ ## Rules
13
+ - Mark a task in_progress BEFORE starting it; keep only ONE task in_progress at a time.
14
+ - Mark a task completed IMMEDIATELY after finishing it — don't batch completions.
15
+ - Each todo has \`content\` (imperative, e.g. "Add tests") and optionally \`activeForm\` (present continuous, e.g. "Adding tests").
16
+ - Calling this tool replaces the entire todo list with the provided array.`;
17
+ // Fallback store used when the agent loop doesn't supply ctx.store (e.g. tools
18
+ // used standalone). Keeps the tool functional in isolation.
19
+ const fallbackStore = { todos: [] };
20
+ const GLYPH = {
21
+ pending: '☐',
22
+ in_progress: '◐',
23
+ completed: '☒',
24
+ };
25
+ function render(todos) {
26
+ if (!todos.length)
27
+ return 'Todo list cleared (no items).';
28
+ return todos
29
+ .map((t) => `${GLYPH[t.status] ?? '☐'} ${t.content}`)
30
+ .join('\n');
31
+ }
32
+ export const todoWrite = {
33
+ def: {
34
+ type: 'function',
35
+ function: {
36
+ name: 'todo_write',
37
+ description: DESCRIPTION,
38
+ parameters: {
39
+ type: 'object',
40
+ properties: {
41
+ todos: {
42
+ type: 'array',
43
+ description: 'The complete, updated todo list (replaces any existing list).',
44
+ items: {
45
+ type: 'object',
46
+ properties: {
47
+ content: { type: 'string', description: 'Imperative task description.' },
48
+ status: {
49
+ type: 'string',
50
+ enum: ['pending', 'in_progress', 'completed'],
51
+ description: 'Current status of the task.',
52
+ },
53
+ activeForm: {
54
+ type: 'string',
55
+ description: 'Present-continuous form shown while in progress.',
56
+ },
57
+ },
58
+ required: ['content', 'status'],
59
+ },
60
+ },
61
+ },
62
+ required: ['todos'],
63
+ },
64
+ },
65
+ },
66
+ async run(input, ctx) {
67
+ if (!Array.isArray(input.todos)) {
68
+ return { content: 'Error: `todos` must be an array.', isError: true };
69
+ }
70
+ const todos = [];
71
+ for (const raw of input.todos) {
72
+ const t = raw;
73
+ const content = typeof t?.content === 'string' ? t.content : '';
74
+ const status = t?.status;
75
+ if (!content)
76
+ return { content: 'Error: every todo needs a non-empty `content`.', isError: true };
77
+ if (status !== 'pending' && status !== 'in_progress' && status !== 'completed') {
78
+ return { content: `Error: invalid status "${String(status)}" for "${content}".`, isError: true };
79
+ }
80
+ todos.push({
81
+ content,
82
+ status,
83
+ ...(typeof t.activeForm === 'string' ? { activeForm: t.activeForm } : {}),
84
+ });
85
+ }
86
+ const inProgress = todos.filter((t) => t.status === 'in_progress').length;
87
+ if (inProgress > 1) {
88
+ return {
89
+ content: `Error: only one task may be in_progress at a time (found ${inProgress}).`,
90
+ isError: true,
91
+ };
92
+ }
93
+ const target = ctx.store ?? fallbackStore;
94
+ target.todos = todos;
95
+ const counts = {
96
+ pending: todos.filter((t) => t.status === 'pending').length,
97
+ in_progress: inProgress,
98
+ completed: todos.filter((t) => t.status === 'completed').length,
99
+ };
100
+ return {
101
+ content: `${render(todos)}\n\n(${counts.completed} done, ${counts.in_progress} in progress, ${counts.pending} pending)`,
102
+ };
103
+ },
104
+ };
@@ -0,0 +1,2 @@
1
+ import type { Tool } from './types.js';
2
+ export declare const toolSearch: Tool;
@@ -0,0 +1,49 @@
1
+ const DESCRIPTION = `Searches the available tools by keyword and returns the best matches (name + description).
2
+
3
+ Use this to discover which tool fits a task when many tools are loaded (e.g. lots of MCP tools). Returns ranked matches; then call the chosen tool directly.`;
4
+ export const toolSearch = {
5
+ def: {
6
+ type: 'function',
7
+ function: {
8
+ name: 'tool_search',
9
+ description: DESCRIPTION,
10
+ parameters: {
11
+ type: 'object',
12
+ properties: {
13
+ query: { type: 'string', description: 'Keywords describing what you want to do.' },
14
+ limit: { type: 'number', description: 'Max results (default 8).' },
15
+ },
16
+ required: ['query'],
17
+ },
18
+ },
19
+ },
20
+ async run(input, ctx) {
21
+ const index = ctx.toolIndex ?? [];
22
+ if (!index.length)
23
+ return { content: 'No tool index available.', isError: true };
24
+ const q = String(input.query ?? '').toLowerCase().trim();
25
+ const terms = q.split(/\s+/).filter(Boolean);
26
+ const limit = Math.min(Math.max(Number(input.limit) || 8, 1), 25);
27
+ const scored = index
28
+ .map((t) => {
29
+ const hay = (t.name + ' ' + t.description).toLowerCase();
30
+ let score = 0;
31
+ for (const term of terms) {
32
+ if (t.name.toLowerCase().includes(term))
33
+ score += 3;
34
+ else if (hay.includes(term))
35
+ score += 1;
36
+ }
37
+ return { t, score };
38
+ })
39
+ .filter((x) => x.score > 0)
40
+ .sort((a, b) => b.score - a.score)
41
+ .slice(0, limit);
42
+ if (!scored.length)
43
+ return { content: `No tools matched "${q}".` };
44
+ return {
45
+ content: `Matching tools for "${q}":\n` +
46
+ scored.map(({ t }) => ` ${t.name} — ${t.description.split('\n')[0]}`).join('\n'),
47
+ };
48
+ },
49
+ };