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.
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/dist/agent.d.ts +110 -0
- package/dist/agent.js +897 -0
- package/dist/background/index.d.ts +3 -0
- package/dist/background/index.js +9 -0
- package/dist/background/manager.d.ts +32 -0
- package/dist/background/manager.js +108 -0
- package/dist/background/tools.d.ts +5 -0
- package/dist/background/tools.js +98 -0
- package/dist/background/worker.d.ts +19 -0
- package/dist/background/worker.js +30 -0
- package/dist/commands/builtins.d.ts +2 -0
- package/dist/commands/builtins.js +306 -0
- package/dist/commands/index.d.ts +21 -0
- package/dist/commands/index.js +56 -0
- package/dist/commands/types.d.ts +110 -0
- package/dist/commands/types.js +5 -0
- package/dist/compact.d.ts +22 -0
- package/dist/compact.js +67 -0
- package/dist/fs/dexie.d.ts +57 -0
- package/dist/fs/dexie.js +243 -0
- package/dist/fs/index.d.ts +4 -0
- package/dist/fs/index.js +13 -0
- package/dist/fs/linuxTree.d.ts +11 -0
- package/dist/fs/linuxTree.js +43 -0
- package/dist/fs/opfs.d.ts +23 -0
- package/dist/fs/opfs.js +112 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +29 -0
- package/dist/llm/anthropic.d.ts +24 -0
- package/dist/llm/anthropic.js +280 -0
- package/dist/llm/index.d.ts +3 -0
- package/dist/llm/index.js +3 -0
- package/dist/llm/inlineTools.d.ts +11 -0
- package/dist/llm/inlineTools.js +72 -0
- package/dist/llm/openai.d.ts +29 -0
- package/dist/llm/openai.js +224 -0
- package/dist/llm/responses.d.ts +18 -0
- package/dist/llm/responses.js +256 -0
- package/dist/mcp/client.d.ts +20 -0
- package/dist/mcp/client.js +156 -0
- package/dist/mcp/index.d.ts +24 -0
- package/dist/mcp/index.js +157 -0
- package/dist/mcp/proxy.d.ts +3 -0
- package/dist/mcp/proxy.js +25 -0
- package/dist/mcp/sdkServer.d.ts +21 -0
- package/dist/mcp/sdkServer.js +28 -0
- package/dist/mcp/types.d.ts +92 -0
- package/dist/mcp/types.js +5 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.js +5 -0
- package/dist/memory/render.d.ts +7 -0
- package/dist/memory/render.js +46 -0
- package/dist/memory/store.d.ts +20 -0
- package/dist/memory/store.js +79 -0
- package/dist/memory/tools.d.ts +5 -0
- package/dist/memory/tools.js +95 -0
- package/dist/memory/types.d.ts +15 -0
- package/dist/memory/types.js +4 -0
- package/dist/permissions/dangerous.d.ts +4 -0
- package/dist/permissions/dangerous.js +24 -0
- package/dist/permissions/gate.d.ts +21 -0
- package/dist/permissions/gate.js +66 -0
- package/dist/permissions/index.d.ts +5 -0
- package/dist/permissions/index.js +6 -0
- package/dist/permissions/match.d.ts +19 -0
- package/dist/permissions/match.js +104 -0
- package/dist/permissions/planMode.d.ts +3 -0
- package/dist/permissions/planMode.js +33 -0
- package/dist/permissions/types.d.ts +19 -0
- package/dist/permissions/types.js +2 -0
- package/dist/persist.d.ts +15 -0
- package/dist/persist.js +58 -0
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.js +34 -0
- package/dist/query.d.ts +105 -0
- package/dist/query.js +115 -0
- package/dist/queue.d.ts +23 -0
- package/dist/queue.js +43 -0
- package/dist/sandbox/cloudflare.d.ts +48 -0
- package/dist/sandbox/cloudflare.js +124 -0
- package/dist/sandbox/daytona.d.ts +48 -0
- package/dist/sandbox/daytona.js +79 -0
- package/dist/sandbox/e2b.d.ts +54 -0
- package/dist/sandbox/e2b.js +87 -0
- package/dist/sandbox/index.d.ts +8 -0
- package/dist/sandbox/index.js +19 -0
- package/dist/sandbox/local.d.ts +51 -0
- package/dist/sandbox/local.js +155 -0
- package/dist/sandbox/types.d.ts +18 -0
- package/dist/sandbox/types.js +27 -0
- package/dist/sandbox/util.d.ts +15 -0
- package/dist/sandbox/util.js +100 -0
- package/dist/sandbox/vercel.d.ts +48 -0
- package/dist/sandbox/vercel.js +130 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +6 -0
- package/dist/session/store.d.ts +28 -0
- package/dist/session/store.js +122 -0
- package/dist/session/types.d.ts +22 -0
- package/dist/session/types.js +2 -0
- package/dist/settings/index.d.ts +3 -0
- package/dist/settings/index.js +3 -0
- package/dist/settings/load.d.ts +20 -0
- package/dist/settings/load.js +36 -0
- package/dist/settings/merge.d.ts +13 -0
- package/dist/settings/merge.js +65 -0
- package/dist/settings/types.d.ts +17 -0
- package/dist/settings/types.js +3 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.js +5 -0
- package/dist/skills/load.d.ts +23 -0
- package/dist/skills/load.js +54 -0
- package/dist/skills/parse.d.ts +7 -0
- package/dist/skills/parse.js +40 -0
- package/dist/skills/tool.d.ts +2 -0
- package/dist/skills/tool.js +39 -0
- package/dist/skills/types.d.ts +10 -0
- package/dist/skills/types.js +4 -0
- package/dist/team/dispatch.d.ts +2 -0
- package/dist/team/dispatch.js +41 -0
- package/dist/team/index.d.ts +9 -0
- package/dist/team/index.js +11 -0
- package/dist/team/mailbox.d.ts +24 -0
- package/dist/team/mailbox.js +33 -0
- package/dist/team/prompt.d.ts +1 -0
- package/dist/team/prompt.js +12 -0
- package/dist/team/runner.d.ts +20 -0
- package/dist/team/runner.js +45 -0
- package/dist/team/taskBoard.d.ts +41 -0
- package/dist/team/taskBoard.js +73 -0
- package/dist/team/tools.d.ts +7 -0
- package/dist/team/tools.js +190 -0
- package/dist/tools/bash.d.ts +2 -0
- package/dist/tools/bash.js +45 -0
- package/dist/tools/config.d.ts +2 -0
- package/dist/tools/config.js +44 -0
- package/dist/tools/define.d.ts +18 -0
- package/dist/tools/define.js +21 -0
- package/dist/tools/delete_file.d.ts +2 -0
- package/dist/tools/delete_file.js +33 -0
- package/dist/tools/edit_file.d.ts +2 -0
- package/dist/tools/edit_file.js +93 -0
- package/dist/tools/fileTypes.d.ts +32 -0
- package/dist/tools/fileTypes.js +166 -0
- package/dist/tools/glob.d.ts +2 -0
- package/dist/tools/glob.js +53 -0
- package/dist/tools/grep.d.ts +2 -0
- package/dist/tools/grep.js +110 -0
- package/dist/tools/imageProcessor.d.ts +15 -0
- package/dist/tools/imageProcessor.js +83 -0
- package/dist/tools/index.d.ts +28 -0
- package/dist/tools/index.js +45 -0
- package/dist/tools/list_files.d.ts +2 -0
- package/dist/tools/list_files.js +42 -0
- package/dist/tools/multi_edit.d.ts +2 -0
- package/dist/tools/multi_edit.js +112 -0
- package/dist/tools/notebook_edit.d.ts +2 -0
- package/dist/tools/notebook_edit.js +118 -0
- package/dist/tools/plan_mode.d.ts +4 -0
- package/dist/tools/plan_mode.js +44 -0
- package/dist/tools/read_file.d.ts +2 -0
- package/dist/tools/read_file.js +193 -0
- package/dist/tools/task.d.ts +2 -0
- package/dist/tools/task.js +77 -0
- package/dist/tools/todo_write.d.ts +2 -0
- package/dist/tools/todo_write.js +104 -0
- package/dist/tools/tool_search.d.ts +2 -0
- package/dist/tools/tool_search.js +49 -0
- package/dist/tools/types.d.ts +82 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/walk.d.ts +29 -0
- package/dist/tools/walk.js +82 -0
- package/dist/tools/web_fetch.d.ts +2 -0
- package/dist/tools/web_fetch.js +76 -0
- package/dist/tools/web_search.d.ts +22 -0
- package/dist/tools/web_search.js +195 -0
- package/dist/tools/write_file.d.ts +2 -0
- package/dist/tools/write_file.js +39 -0
- package/dist/types/index.d.ts +363 -0
- package/dist/types/index.js +9 -0
- package/dist/util/ids.d.ts +3 -0
- package/dist/util/ids.js +22 -0
- package/dist/util/paths.d.ts +16 -0
- package/dist/util/paths.js +72 -0
- package/dist/util/pricing.d.ts +15 -0
- package/dist/util/pricing.js +81 -0
- package/dist/workspace/index.d.ts +2 -0
- package/dist/workspace/index.js +2 -0
- package/dist/workspace/memory.d.ts +28 -0
- package/dist/workspace/memory.js +97 -0
- package/dist/workspace/webcontainer.d.ts +65 -0
- package/dist/workspace/webcontainer.js +156 -0
- package/package.json +78 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// File-type detection and byte helpers for the read_file subsystem.
|
|
2
|
+
// Browser-safe: no node 'fs'/'buffer'/'path' imports — pure string/byte ops.
|
|
3
|
+
/**
|
|
4
|
+
* Binary file extensions to skip for text-based reads. Ported from Claude
|
|
5
|
+
* Code's constants/files.ts. `.pdf` and image types are included here, but
|
|
6
|
+
* read_file dispatches those to dedicated handlers before the binary check.
|
|
7
|
+
*/
|
|
8
|
+
export const BINARY_EXTENSIONS = new Set([
|
|
9
|
+
// Images
|
|
10
|
+
'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.webp', '.tiff', '.tif',
|
|
11
|
+
// Videos
|
|
12
|
+
'.mp4', '.mov', '.avi', '.mkv', '.webm', '.wmv', '.flv', '.m4v', '.mpeg', '.mpg',
|
|
13
|
+
// Audio
|
|
14
|
+
'.mp3', '.wav', '.ogg', '.flac', '.aac', '.m4a', '.wma', '.aiff', '.opus',
|
|
15
|
+
// Archives
|
|
16
|
+
'.zip', '.tar', '.gz', '.bz2', '.7z', '.rar', '.xz', '.z', '.tgz', '.iso',
|
|
17
|
+
// Executables / binaries
|
|
18
|
+
'.exe', '.dll', '.so', '.dylib', '.bin', '.o', '.a', '.obj', '.lib', '.app',
|
|
19
|
+
'.msi', '.deb', '.rpm',
|
|
20
|
+
// Documents
|
|
21
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.odt', '.ods', '.odp',
|
|
22
|
+
// Fonts
|
|
23
|
+
'.ttf', '.otf', '.woff', '.woff2', '.eot',
|
|
24
|
+
// Bytecode / VM artifacts
|
|
25
|
+
'.pyc', '.pyo', '.class', '.jar', '.war', '.ear', '.node', '.wasm', '.rlib',
|
|
26
|
+
// Databases
|
|
27
|
+
'.sqlite', '.sqlite3', '.db', '.mdb', '.idx',
|
|
28
|
+
// Design / 3D
|
|
29
|
+
'.psd', '.ai', '.eps', '.sketch', '.fig', '.xd', '.blend', '.3ds', '.max',
|
|
30
|
+
// Flash
|
|
31
|
+
'.swf', '.fla',
|
|
32
|
+
// Lock / profiling data
|
|
33
|
+
'.lockb', '.dat', '.data',
|
|
34
|
+
]);
|
|
35
|
+
export const IMAGE_EXTENSIONS = new Set([
|
|
36
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp',
|
|
37
|
+
]);
|
|
38
|
+
export const NOTEBOOK_EXTENSION = '.ipynb';
|
|
39
|
+
export const PDF_EXTENSION = '.pdf';
|
|
40
|
+
/** Default read caps (mirrors Claude Code: 256 KB / 25k tokens / 20 PDF pages). */
|
|
41
|
+
export const DEFAULT_FILE_READ_LIMITS = {
|
|
42
|
+
maxSizeBytes: 256 * 1024,
|
|
43
|
+
maxTokens: 25_000,
|
|
44
|
+
maxImageBytes: Math.round(3.75 * 1024 * 1024),
|
|
45
|
+
maxPdfPages: 20,
|
|
46
|
+
};
|
|
47
|
+
/** Lowercased extension including the leading dot, or '' if none. */
|
|
48
|
+
export function extOf(path) {
|
|
49
|
+
const slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));
|
|
50
|
+
const base = slash === -1 ? path : path.slice(slash + 1);
|
|
51
|
+
const dot = base.lastIndexOf('.');
|
|
52
|
+
if (dot <= 0)
|
|
53
|
+
return ''; // no dot, or dotfile like ".bashrc"
|
|
54
|
+
return base.slice(dot).toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
export function hasBinaryExtension(path) {
|
|
57
|
+
return BINARY_EXTENSIONS.has(extOf(path));
|
|
58
|
+
}
|
|
59
|
+
export function isImageExtension(path) {
|
|
60
|
+
return IMAGE_EXTENSIONS.has(extOf(path));
|
|
61
|
+
}
|
|
62
|
+
export function isPdfExtension(path) {
|
|
63
|
+
return extOf(path) === PDF_EXTENSION;
|
|
64
|
+
}
|
|
65
|
+
export function isNotebookExtension(path) {
|
|
66
|
+
return extOf(path) === NOTEBOOK_EXTENSION;
|
|
67
|
+
}
|
|
68
|
+
/** Map an image extension to its MIME type. */
|
|
69
|
+
export function imageMediaType(ext) {
|
|
70
|
+
switch (ext) {
|
|
71
|
+
case '.png':
|
|
72
|
+
return 'image/png';
|
|
73
|
+
case '.jpg':
|
|
74
|
+
case '.jpeg':
|
|
75
|
+
return 'image/jpeg';
|
|
76
|
+
case '.gif':
|
|
77
|
+
return 'image/gif';
|
|
78
|
+
case '.webp':
|
|
79
|
+
return 'image/webp';
|
|
80
|
+
case '.bmp':
|
|
81
|
+
return 'image/bmp';
|
|
82
|
+
default:
|
|
83
|
+
return 'application/octet-stream';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Detect image MIME type from magic bytes, or null if unrecognized. */
|
|
87
|
+
export function detectImageFormatFromBytes(bytes) {
|
|
88
|
+
if (bytes.length < 4)
|
|
89
|
+
return null;
|
|
90
|
+
// PNG: 89 50 4E 47
|
|
91
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
|
|
92
|
+
return 'image/png';
|
|
93
|
+
}
|
|
94
|
+
// JPEG: FF D8 FF
|
|
95
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
96
|
+
return 'image/jpeg';
|
|
97
|
+
}
|
|
98
|
+
// GIF: "GIF8"
|
|
99
|
+
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38) {
|
|
100
|
+
return 'image/gif';
|
|
101
|
+
}
|
|
102
|
+
// BMP: "BM"
|
|
103
|
+
if (bytes[0] === 0x42 && bytes[1] === 0x4d) {
|
|
104
|
+
return 'image/bmp';
|
|
105
|
+
}
|
|
106
|
+
// WEBP: "RIFF"...."WEBP"
|
|
107
|
+
if (bytes.length >= 12 &&
|
|
108
|
+
bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&
|
|
109
|
+
bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50) {
|
|
110
|
+
return 'image/webp';
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/** Heuristic: a NUL byte in the first 8 KB means the file is binary. */
|
|
115
|
+
export function looksBinary(bytes) {
|
|
116
|
+
const n = Math.min(bytes.length, 8192);
|
|
117
|
+
for (let i = 0; i < n; i++) {
|
|
118
|
+
if (bytes[i] === 0)
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
/** Rough token estimate (~4 chars/token). */
|
|
124
|
+
export function roughTokenCount(text) {
|
|
125
|
+
return Math.ceil(text.length / 4);
|
|
126
|
+
}
|
|
127
|
+
const hasBtoa = typeof btoa === 'function';
|
|
128
|
+
const hasAtob = typeof atob === 'function';
|
|
129
|
+
/** Base64-encode bytes. Works in browser (btoa) and Node (Buffer). */
|
|
130
|
+
export function bytesToBase64(bytes) {
|
|
131
|
+
if (hasBtoa) {
|
|
132
|
+
let binary = '';
|
|
133
|
+
const chunk = 0x8000; // 32 KB chunks to avoid call-stack overflow
|
|
134
|
+
for (let i = 0; i < bytes.length; i += chunk) {
|
|
135
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
|
|
136
|
+
}
|
|
137
|
+
return btoa(binary);
|
|
138
|
+
}
|
|
139
|
+
// Node fallback
|
|
140
|
+
const B = globalThis.Buffer;
|
|
141
|
+
if (B)
|
|
142
|
+
return B.from(bytes).toString('base64');
|
|
143
|
+
throw new Error('No base64 encoder available in this environment');
|
|
144
|
+
}
|
|
145
|
+
/** Base64-decode to bytes. Works in browser (atob) and Node (Buffer). */
|
|
146
|
+
export function base64ToBytes(b64) {
|
|
147
|
+
if (hasAtob) {
|
|
148
|
+
const binary = atob(b64);
|
|
149
|
+
const out = new Uint8Array(binary.length);
|
|
150
|
+
for (let i = 0; i < binary.length; i++)
|
|
151
|
+
out[i] = binary.charCodeAt(i);
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
const B = globalThis.Buffer;
|
|
155
|
+
if (B)
|
|
156
|
+
return new Uint8Array(B.from(b64, 'base64'));
|
|
157
|
+
throw new Error('No base64 decoder available in this environment');
|
|
158
|
+
}
|
|
159
|
+
/** Human-readable byte size. */
|
|
160
|
+
export function formatBytes(n) {
|
|
161
|
+
if (n < 1024)
|
|
162
|
+
return `${n} B`;
|
|
163
|
+
if (n < 1024 * 1024)
|
|
164
|
+
return `${(n / 1024).toFixed(1)} KB`;
|
|
165
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
166
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { globToRegExp, walk } from './walk.js';
|
|
2
|
+
const MAX_RESULTS = 100;
|
|
3
|
+
const DESCRIPTION = `Finds files matching a glob pattern by walking the workspace tree.
|
|
4
|
+
|
|
5
|
+
Supports \`**\` (any depth), \`*\` (within a path segment), and \`?\` (single char).
|
|
6
|
+
Example patterns: \`**/*.ts\`, \`src/**/*.test.js\`, \`*.json\`.
|
|
7
|
+
Returns matching paths relative to the search root (node_modules/.git/dist are ignored).`;
|
|
8
|
+
export const glob = {
|
|
9
|
+
def: {
|
|
10
|
+
type: 'function',
|
|
11
|
+
function: {
|
|
12
|
+
name: 'glob',
|
|
13
|
+
description: DESCRIPTION,
|
|
14
|
+
parameters: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
pattern: { type: 'string', description: 'Glob pattern to match.' },
|
|
18
|
+
path: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Directory to search from (default ".").',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
required: ['pattern'],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
async run(input, ctx) {
|
|
28
|
+
const pattern = String(input.pattern ?? '');
|
|
29
|
+
if (!pattern)
|
|
30
|
+
return { content: 'Error: `pattern` is required.', isError: true };
|
|
31
|
+
const root = input.path ? String(input.path) : '.';
|
|
32
|
+
const re = globToRegExp(pattern);
|
|
33
|
+
const matches = [];
|
|
34
|
+
let truncated = false;
|
|
35
|
+
for await (const entry of walk(ctx.fs, root, { signal: ctx.signal })) {
|
|
36
|
+
if (entry.isDir)
|
|
37
|
+
continue;
|
|
38
|
+
if (re.test(entry.path)) {
|
|
39
|
+
matches.push(entry.path);
|
|
40
|
+
if (matches.length >= MAX_RESULTS) {
|
|
41
|
+
truncated = true;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (matches.length === 0) {
|
|
47
|
+
return { content: `No files match ${pattern} under ${root}.` };
|
|
48
|
+
}
|
|
49
|
+
matches.sort();
|
|
50
|
+
const note = truncated ? `\n\n[truncated to first ${MAX_RESULTS} matches]` : '';
|
|
51
|
+
return { content: matches.join('\n') + note };
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { globToRegExp, joinPath, walk } from './walk.js';
|
|
2
|
+
const MAX_MATCHES = 200;
|
|
3
|
+
const DESCRIPTION = `Searches file contents for a regular expression by walking the workspace tree.
|
|
4
|
+
|
|
5
|
+
- \`pattern\` is a JavaScript regular expression.
|
|
6
|
+
- \`glob\` optionally restricts which files are searched (e.g. \`**/*.ts\`).
|
|
7
|
+
- \`output_mode\`: "files_with_matches" (default) lists matching paths, "content" shows \`path:line:text\`, "count" shows per-file match counts.
|
|
8
|
+
Binary-looking files and node_modules/.git/dist are skipped.`;
|
|
9
|
+
/**
|
|
10
|
+
* Heuristic: treat content containing a NUL byte (char code 0) as binary.
|
|
11
|
+
* Only the first chunk is scanned to keep this cheap on large files.
|
|
12
|
+
*/
|
|
13
|
+
function looksBinary(text) {
|
|
14
|
+
const limit = Math.min(text.length, 8000);
|
|
15
|
+
for (let i = 0; i < limit; i++) {
|
|
16
|
+
if (text.charCodeAt(i) === 0)
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
export const grep = {
|
|
22
|
+
def: {
|
|
23
|
+
type: 'function',
|
|
24
|
+
function: {
|
|
25
|
+
name: 'grep',
|
|
26
|
+
description: DESCRIPTION,
|
|
27
|
+
parameters: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
pattern: { type: 'string', description: 'Regular expression to search for.' },
|
|
31
|
+
path: { type: 'string', description: 'Directory to search from (default ".").' },
|
|
32
|
+
glob: { type: 'string', description: 'Glob filter for filenames.' },
|
|
33
|
+
case_insensitive: { type: 'boolean', description: 'Case-insensitive match.' },
|
|
34
|
+
output_mode: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
enum: ['content', 'files_with_matches', 'count'],
|
|
37
|
+
description: 'Output format (default "files_with_matches").',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ['pattern'],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
async run(input, ctx) {
|
|
45
|
+
const pattern = String(input.pattern ?? '');
|
|
46
|
+
if (!pattern)
|
|
47
|
+
return { content: 'Error: `pattern` is required.', isError: true };
|
|
48
|
+
const root = input.path ? String(input.path) : '.';
|
|
49
|
+
const mode = input.output_mode === 'content' || input.output_mode === 'count'
|
|
50
|
+
? input.output_mode
|
|
51
|
+
: 'files_with_matches';
|
|
52
|
+
let re;
|
|
53
|
+
try {
|
|
54
|
+
re = new RegExp(pattern, input.case_insensitive ? 'i' : '');
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return { content: `Error: invalid regex: ${err.message}`, isError: true };
|
|
58
|
+
}
|
|
59
|
+
const globFilter = input.glob ? globToRegExp(String(input.glob)) : null;
|
|
60
|
+
const contentLines = [];
|
|
61
|
+
const fileMatches = [];
|
|
62
|
+
const counts = [];
|
|
63
|
+
let total = 0;
|
|
64
|
+
let truncated = false;
|
|
65
|
+
for await (const entry of walk(ctx.fs, root, { signal: ctx.signal })) {
|
|
66
|
+
if (entry.isDir)
|
|
67
|
+
continue;
|
|
68
|
+
if (globFilter && !globFilter.test(entry.path))
|
|
69
|
+
continue;
|
|
70
|
+
const full = root === '.' ? entry.path : joinPath(root, entry.path);
|
|
71
|
+
const text = await ctx.fs.readFile(full);
|
|
72
|
+
if (text === null || looksBinary(text))
|
|
73
|
+
continue;
|
|
74
|
+
const lines = text.split('\n');
|
|
75
|
+
let fileCount = 0;
|
|
76
|
+
for (let i = 0; i < lines.length; i++) {
|
|
77
|
+
if (re.test(lines[i])) {
|
|
78
|
+
fileCount++;
|
|
79
|
+
total++;
|
|
80
|
+
if (mode === 'content' && contentLines.length < MAX_MATCHES) {
|
|
81
|
+
contentLines.push(`${full}:${i + 1}:${lines[i]}`);
|
|
82
|
+
}
|
|
83
|
+
if (total >= MAX_MATCHES) {
|
|
84
|
+
truncated = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (fileCount > 0) {
|
|
90
|
+
fileMatches.push(full);
|
|
91
|
+
counts.push({ path: full, count: fileCount });
|
|
92
|
+
}
|
|
93
|
+
if (truncated)
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
if (total === 0) {
|
|
97
|
+
return { content: `No matches for /${pattern}/ under ${root}.` };
|
|
98
|
+
}
|
|
99
|
+
const note = truncated ? `\n\n[truncated at ${MAX_MATCHES} matches]` : '';
|
|
100
|
+
if (mode === 'content') {
|
|
101
|
+
return { content: contentLines.join('\n') + note };
|
|
102
|
+
}
|
|
103
|
+
if (mode === 'count') {
|
|
104
|
+
const body = counts.map((c) => `${c.path}:${c.count}`).join('\n');
|
|
105
|
+
return { content: body + note };
|
|
106
|
+
}
|
|
107
|
+
fileMatches.sort();
|
|
108
|
+
return { content: fileMatches.join('\n') + note };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ProcessedImage {
|
|
2
|
+
/** Base64 payload (no `data:` prefix). */
|
|
3
|
+
data: string;
|
|
4
|
+
media_type: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
/** Set when the image could not be downsampled (e.g. no canvas) but is oversized. */
|
|
8
|
+
note?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Decode, optionally downscale (fit within MAX_DIMENSION, no enlargement) and
|
|
12
|
+
* re-encode an image so the result stays under `maxBytes`. Falls back to the
|
|
13
|
+
* original bytes when DOM imaging APIs are unavailable.
|
|
14
|
+
*/
|
|
15
|
+
export declare function processImage(bytes: Uint8Array, mediaType: string, maxBytes: number): Promise<ProcessedImage>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Browser-first image downsampling for read_file. Uses createImageBitmap +
|
|
2
|
+
// OffscreenCanvas when available (browser / web worker); degrades gracefully in
|
|
3
|
+
// Node-without-DOM by passing the image through unmodified. Never throws.
|
|
4
|
+
import { bytesToBase64 } from './fileTypes.js';
|
|
5
|
+
const MAX_DIMENSION = 2000;
|
|
6
|
+
function canResize() {
|
|
7
|
+
return (typeof createImageBitmap === 'function' &&
|
|
8
|
+
typeof globalThis.OffscreenCanvas === 'function');
|
|
9
|
+
}
|
|
10
|
+
async function blobToBytes(blob) {
|
|
11
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Decode, optionally downscale (fit within MAX_DIMENSION, no enlargement) and
|
|
15
|
+
* re-encode an image so the result stays under `maxBytes`. Falls back to the
|
|
16
|
+
* original bytes when DOM imaging APIs are unavailable.
|
|
17
|
+
*/
|
|
18
|
+
export async function processImage(bytes, mediaType, maxBytes) {
|
|
19
|
+
if (!canResize()) {
|
|
20
|
+
if (bytes.length <= maxBytes) {
|
|
21
|
+
return { data: bytesToBase64(bytes), media_type: mediaType };
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
data: bytesToBase64(bytes),
|
|
25
|
+
media_type: mediaType,
|
|
26
|
+
note: `Image is ${(bytes.length / (1024 * 1024)).toFixed(1)} MB and could not be resized in this runtime (no canvas). It may exceed model limits.`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const OffscreenCanvasCtor = globalThis.OffscreenCanvas;
|
|
31
|
+
const srcBlob = new Blob([bytes], { type: mediaType });
|
|
32
|
+
const bitmap = await createImageBitmap(srcBlob);
|
|
33
|
+
const { width: ow, height: oh } = bitmap;
|
|
34
|
+
const needsResize = bytes.length > maxBytes || ow > MAX_DIMENSION || oh > MAX_DIMENSION;
|
|
35
|
+
if (!needsResize) {
|
|
36
|
+
bitmap.close?.();
|
|
37
|
+
return { data: bytesToBase64(bytes), media_type: mediaType, width: ow, height: oh };
|
|
38
|
+
}
|
|
39
|
+
// Keep alpha for PNG; otherwise prefer JPEG for better compression.
|
|
40
|
+
const keepPng = mediaType === 'image/png';
|
|
41
|
+
const outType = keepPng ? 'image/png' : 'image/jpeg';
|
|
42
|
+
let scale = Math.min(1, MAX_DIMENSION / Math.max(ow, oh));
|
|
43
|
+
let quality = 0.8;
|
|
44
|
+
let best = null;
|
|
45
|
+
for (let attempt = 0; attempt < 6; attempt++) {
|
|
46
|
+
const w = Math.max(1, Math.round(ow * scale));
|
|
47
|
+
const h = Math.max(1, Math.round(oh * scale));
|
|
48
|
+
const canvas = new OffscreenCanvasCtor(w, h);
|
|
49
|
+
const cctx = canvas.getContext('2d');
|
|
50
|
+
if (!cctx)
|
|
51
|
+
break;
|
|
52
|
+
cctx.drawImage(bitmap, 0, 0, w, h);
|
|
53
|
+
const outBlob = await canvas.convertToBlob(outType === 'image/jpeg' ? { type: outType, quality } : { type: outType });
|
|
54
|
+
const outBytes = await blobToBytes(outBlob);
|
|
55
|
+
best = { data: outBytes, w, h };
|
|
56
|
+
if (outBytes.length <= maxBytes)
|
|
57
|
+
break;
|
|
58
|
+
// Shrink further: drop quality first (jpeg), then scale.
|
|
59
|
+
if (outType === 'image/jpeg' && quality > 0.4)
|
|
60
|
+
quality -= 0.15;
|
|
61
|
+
else
|
|
62
|
+
scale *= 0.75;
|
|
63
|
+
}
|
|
64
|
+
bitmap.close?.();
|
|
65
|
+
if (best) {
|
|
66
|
+
return {
|
|
67
|
+
data: bytesToBase64(best.data),
|
|
68
|
+
media_type: outType,
|
|
69
|
+
width: best.w,
|
|
70
|
+
height: best.h,
|
|
71
|
+
note: best.data.length > maxBytes ? 'Image still exceeds the size cap after downsampling.' : undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// fall through to pass-through below
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
data: bytesToBase64(bytes),
|
|
80
|
+
media_type: mediaType,
|
|
81
|
+
note: bytes.length > maxBytes ? 'Image could not be resized; passed through at original size.' : undefined,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ToolDef } from '../types/index.js';
|
|
2
|
+
import { bash } from './bash.js';
|
|
3
|
+
import { deleteFile } from './delete_file.js';
|
|
4
|
+
import { editFile } from './edit_file.js';
|
|
5
|
+
import { glob } from './glob.js';
|
|
6
|
+
import { grep } from './grep.js';
|
|
7
|
+
import { listFiles } from './list_files.js';
|
|
8
|
+
import { multiEdit } from './multi_edit.js';
|
|
9
|
+
import { notebookEdit } from './notebook_edit.js';
|
|
10
|
+
import { readFile } from './read_file.js';
|
|
11
|
+
import { todoWrite } from './todo_write.js';
|
|
12
|
+
import type { Tool } from './types.js';
|
|
13
|
+
import { webFetch } from './web_fetch.js';
|
|
14
|
+
import { webSearch } from './web_search.js';
|
|
15
|
+
import { toolSearch } from './tool_search.js';
|
|
16
|
+
import { config } from './config.js';
|
|
17
|
+
import { writeFile } from './write_file.js';
|
|
18
|
+
export type { Tool, ToolContext, ToolResult, FileReadLimits } from './types.js';
|
|
19
|
+
export { bash, readFile, writeFile, editFile, deleteFile, listFiles, glob, grep };
|
|
20
|
+
export { multiEdit, notebookEdit, todoWrite, webFetch, webSearch, toolSearch, config };
|
|
21
|
+
export { walk, globToRegExp, joinPath, DEFAULT_IGNORE } from './walk.js';
|
|
22
|
+
export { defineTool, type DefineToolSpec } from './define.js';
|
|
23
|
+
/** Every built-in Claude Code tool, ready to pass to `query()`. */
|
|
24
|
+
export declare const ALL_CLAUDE_CODE_TOOLS: Tool[];
|
|
25
|
+
/** Extract the OpenAI-shape definitions to send to the LLM. */
|
|
26
|
+
export declare function toolDefs(tools: Tool[]): ToolDef[];
|
|
27
|
+
/** Build a name→tool lookup for dispatching tool calls. */
|
|
28
|
+
export declare function toolByName(tools: Tool[]): Map<string, Tool>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { bash } from './bash.js';
|
|
2
|
+
import { deleteFile } from './delete_file.js';
|
|
3
|
+
import { editFile } from './edit_file.js';
|
|
4
|
+
import { glob } from './glob.js';
|
|
5
|
+
import { grep } from './grep.js';
|
|
6
|
+
import { listFiles } from './list_files.js';
|
|
7
|
+
import { multiEdit } from './multi_edit.js';
|
|
8
|
+
import { notebookEdit } from './notebook_edit.js';
|
|
9
|
+
import { readFile } from './read_file.js';
|
|
10
|
+
import { todoWrite } from './todo_write.js';
|
|
11
|
+
import { webFetch } from './web_fetch.js';
|
|
12
|
+
import { webSearch } from './web_search.js';
|
|
13
|
+
import { toolSearch } from './tool_search.js';
|
|
14
|
+
import { config } from './config.js';
|
|
15
|
+
import { writeFile } from './write_file.js';
|
|
16
|
+
export { bash, readFile, writeFile, editFile, deleteFile, listFiles, glob, grep };
|
|
17
|
+
export { multiEdit, notebookEdit, todoWrite, webFetch, webSearch, toolSearch, config };
|
|
18
|
+
export { walk, globToRegExp, joinPath, DEFAULT_IGNORE } from './walk.js';
|
|
19
|
+
export { defineTool } from './define.js';
|
|
20
|
+
/** Every built-in Claude Code tool, ready to pass to `query()`. */
|
|
21
|
+
export const ALL_CLAUDE_CODE_TOOLS = [
|
|
22
|
+
bash,
|
|
23
|
+
readFile,
|
|
24
|
+
writeFile,
|
|
25
|
+
editFile,
|
|
26
|
+
multiEdit,
|
|
27
|
+
deleteFile,
|
|
28
|
+
listFiles,
|
|
29
|
+
glob,
|
|
30
|
+
grep,
|
|
31
|
+
notebookEdit,
|
|
32
|
+
todoWrite,
|
|
33
|
+
webFetch,
|
|
34
|
+
webSearch,
|
|
35
|
+
toolSearch,
|
|
36
|
+
config,
|
|
37
|
+
];
|
|
38
|
+
/** Extract the OpenAI-shape definitions to send to the LLM. */
|
|
39
|
+
export function toolDefs(tools) {
|
|
40
|
+
return tools.map((t) => t.def);
|
|
41
|
+
}
|
|
42
|
+
/** Build a name→tool lookup for dispatching tool calls. */
|
|
43
|
+
export function toolByName(tools) {
|
|
44
|
+
return new Map(tools.map((t) => [t.def.function.name, t]));
|
|
45
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const DESCRIPTION = `Lists the entries of a directory in the workspace.
|
|
2
|
+
|
|
3
|
+
Returns a sorted listing with directories suffixed by \`/\`. Defaults to the workspace root.`;
|
|
4
|
+
export const listFiles = {
|
|
5
|
+
def: {
|
|
6
|
+
type: 'function',
|
|
7
|
+
function: {
|
|
8
|
+
name: 'list_files',
|
|
9
|
+
description: DESCRIPTION,
|
|
10
|
+
parameters: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
path: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Directory to list (default ".").',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: [],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
async run(input, ctx) {
|
|
23
|
+
const path = input.path ? String(input.path) : '.';
|
|
24
|
+
const entries = await ctx.fs.readdir(path);
|
|
25
|
+
if (!entries) {
|
|
26
|
+
return { content: `Error: directory not found: ${path}`, isError: true };
|
|
27
|
+
}
|
|
28
|
+
if (entries.length === 0) {
|
|
29
|
+
return { content: `(empty directory: ${path})` };
|
|
30
|
+
}
|
|
31
|
+
// Directories first, then files; each group alphabetized.
|
|
32
|
+
const sorted = [...entries].sort((a, b) => {
|
|
33
|
+
if (a.isDir !== b.isDir)
|
|
34
|
+
return a.isDir ? -1 : 1;
|
|
35
|
+
return a.name.localeCompare(b.name);
|
|
36
|
+
});
|
|
37
|
+
const listing = sorted
|
|
38
|
+
.map((e) => (e.isDir ? `${e.name}/` : e.name))
|
|
39
|
+
.join('\n');
|
|
40
|
+
return { content: listing };
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const DESCRIPTION = `Applies multiple exact string replacements to a single file in one atomic operation.
|
|
2
|
+
|
|
3
|
+
- You must read the file with read_file at least once before editing it; otherwise this tool errors.
|
|
4
|
+
- Provide an ordered list of edits. Each edit is applied in sequence to the result of the previous one.
|
|
5
|
+
- Each \`old_string\` must match the current file contents exactly (including whitespace) and must be unique unless that edit sets \`replace_all\`.
|
|
6
|
+
- Edits are all-or-nothing: if any edit fails (not found, not unique, or identical strings), NO changes are written and the failing edit index is reported.
|
|
7
|
+
- Prefer this over multiple edit_file calls when making several changes to the same file.`;
|
|
8
|
+
/** Count non-overlapping occurrences of needle in haystack. */
|
|
9
|
+
function countOccurrences(haystack, needle) {
|
|
10
|
+
if (needle === '')
|
|
11
|
+
return 0;
|
|
12
|
+
let count = 0;
|
|
13
|
+
let idx = haystack.indexOf(needle);
|
|
14
|
+
while (idx !== -1) {
|
|
15
|
+
count++;
|
|
16
|
+
idx = haystack.indexOf(needle, idx + needle.length);
|
|
17
|
+
}
|
|
18
|
+
return count;
|
|
19
|
+
}
|
|
20
|
+
export const multiEdit = {
|
|
21
|
+
def: {
|
|
22
|
+
type: 'function',
|
|
23
|
+
function: {
|
|
24
|
+
name: 'multi_edit',
|
|
25
|
+
description: DESCRIPTION,
|
|
26
|
+
parameters: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
path: { type: 'string', description: 'Path to the file to edit.' },
|
|
30
|
+
edits: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
description: 'Ordered list of edits to apply sequentially and atomically.',
|
|
33
|
+
items: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
old_string: { type: 'string', description: 'Exact text to find.' },
|
|
37
|
+
new_string: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Replacement text (must differ from old_string).',
|
|
40
|
+
},
|
|
41
|
+
replace_all: {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
description: 'Replace every occurrence instead of requiring uniqueness.',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
required: ['old_string', 'new_string'],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
required: ['path', 'edits'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
async run(input, ctx) {
|
|
55
|
+
const path = String(input.path ?? '');
|
|
56
|
+
if (!path)
|
|
57
|
+
return { content: 'Error: `path` is required.', isError: true };
|
|
58
|
+
if (!Array.isArray(input.edits) || input.edits.length === 0) {
|
|
59
|
+
return { content: 'Error: `edits` must be a non-empty array.', isError: true };
|
|
60
|
+
}
|
|
61
|
+
if (!ctx.readFiles.has(path)) {
|
|
62
|
+
return {
|
|
63
|
+
content: `Error: you must read ${path} with read_file before editing it.`,
|
|
64
|
+
isError: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const edits = input.edits;
|
|
68
|
+
const original = await ctx.fs.readFile(path);
|
|
69
|
+
if (original === null) {
|
|
70
|
+
return { content: `Error: file not found: ${path}`, isError: true };
|
|
71
|
+
}
|
|
72
|
+
let text = original;
|
|
73
|
+
for (let i = 0; i < edits.length; i++) {
|
|
74
|
+
const e = edits[i];
|
|
75
|
+
const oldString = typeof e?.old_string === 'string' ? e.old_string : '';
|
|
76
|
+
const newString = typeof e?.new_string === 'string' ? e.new_string : '';
|
|
77
|
+
const replaceAll = e?.replace_all === true;
|
|
78
|
+
if (oldString === newString) {
|
|
79
|
+
return {
|
|
80
|
+
content: `Error: edit #${i + 1}: \`old_string\` and \`new_string\` are identical.`,
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const occurrences = countOccurrences(text, oldString);
|
|
85
|
+
if (occurrences === 0) {
|
|
86
|
+
return {
|
|
87
|
+
content: `Error: edit #${i + 1}: \`old_string\` not found${i > 0 ? ' (after applying earlier edits)' : ''}.`,
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (!replaceAll && occurrences > 1) {
|
|
92
|
+
return {
|
|
93
|
+
content: `Error: edit #${i + 1}: \`old_string\` is not unique (found ${occurrences} occurrences). Provide more context or set replace_all: true.`,
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
text = replaceAll
|
|
98
|
+
? text.split(oldString).join(newString)
|
|
99
|
+
: text.replace(oldString, newString);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
await ctx.fs.writeFile(path, text);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
return {
|
|
106
|
+
content: `Error writing ${path}: ${err.message}`,
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return { content: `Applied ${edits.length} edit${edits.length === 1 ? '' : 's'} to ${path}.` };
|
|
111
|
+
},
|
|
112
|
+
};
|