@molecule/api-ai-tools 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +649 -0
- package/package.json +5 -4
package/README.md
ADDED
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
3
|
+
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
|
+
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
|
+
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
+
Generated: 2026-08-04T01:47:38.726Z
|
|
7
|
+
-->
|
|
8
|
+
|
|
9
|
+
# @molecule/api-ai-tools
|
|
10
|
+
|
|
11
|
+
> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
|
|
12
|
+
> It is written to be read by coding agents as much as by people, and is generated from this
|
|
13
|
+
> package's source — edit `src/index.ts` JSDoc, not this file.
|
|
14
|
+
|
|
15
|
+
Shared AI agent tool set for molecule.dev — filesystem, search, and shell
|
|
16
|
+
tools an LLM agent can call, with a swappable execution backend.
|
|
17
|
+
|
|
18
|
+
`buildTools(backend)` returns ready-to-use `AITool`s (the `@molecule/api-ai`
|
|
19
|
+
tool shape): `list_files`, `read_file`, `write_file`, `edit_file`,
|
|
20
|
+
`search_files`, `find_files`, `create_directory`, `rename_file`,
|
|
21
|
+
`delete_file`, `exec_command`, `save_plan`, `load_skill`. The backend decides
|
|
22
|
+
WHERE they act: `createLocalBackend(projectRoot)` (host filesystem) or
|
|
23
|
+
`createSandboxBackend(...)` (an isolated `@molecule/api-code-sandbox`
|
|
24
|
+
container). `buildAgentPrompt(ctx)` composes a matching system prompt
|
|
25
|
+
(identity, tool listing, project docs, discovered skills).
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { buildAgentPrompt, buildTools, createLocalBackend } from '@molecule/api-ai-tools'
|
|
31
|
+
import { requireProvider } from '@molecule/api-ai'
|
|
32
|
+
|
|
33
|
+
const backend = createLocalBackend('/path/to/project')
|
|
34
|
+
const tools = buildTools(backend, { include: ['read_file', 'edit_file', 'search_files'] })
|
|
35
|
+
const system = buildAgentPrompt({
|
|
36
|
+
agentName: 'My Agent',
|
|
37
|
+
projectRoot: backend.projectRoot,
|
|
38
|
+
tools: tools.map((t) => t.name),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Hand them to the bonded AI provider (or an @molecule/api-ai-agents run).
|
|
42
|
+
for await (const event of requireProvider().chat({ system, tools, messages, stream: true })) {
|
|
43
|
+
// forward text chunks; tool calls are executed against the backend
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Type
|
|
48
|
+
|
|
49
|
+
`core`
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install @molecule/api-ai-tools @molecule/api-ai
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### Interfaces
|
|
60
|
+
|
|
61
|
+
#### `ExecutionBackend`
|
|
62
|
+
|
|
63
|
+
Abstraction over the execution environment.
|
|
64
|
+
Implemented by SandboxBackend (Docker) and LocalBackend (host filesystem).
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
interface ExecutionBackend {
|
|
68
|
+
/** The root directory for all operations (e.g. '/workspace' or '/Users/.../project'). */
|
|
69
|
+
readonly projectRoot: string
|
|
70
|
+
|
|
71
|
+
/** Read a file's content as UTF-8 string. */
|
|
72
|
+
readFile(path: string): Promise<string>
|
|
73
|
+
|
|
74
|
+
/** Write content to a file. Creates parent directories as needed. */
|
|
75
|
+
writeFile(path: string, content: string): Promise<void>
|
|
76
|
+
|
|
77
|
+
/** Delete a file. */
|
|
78
|
+
deleteFile(path: string): Promise<void>
|
|
79
|
+
|
|
80
|
+
/** List entries in a directory. */
|
|
81
|
+
readDir(path: string): Promise<Array<{ name: string; type: 'file' | 'directory' }>>
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Run a shell command. Returns stdout, stderr, and exit code.
|
|
85
|
+
* Backends implement this safely (sandbox.exec for Docker, execFile for local).
|
|
86
|
+
*/
|
|
87
|
+
run(
|
|
88
|
+
command: string,
|
|
89
|
+
opts?: { cwd?: string; timeout?: number },
|
|
90
|
+
): Promise<{ stdout: string; stderr: string; exitCode: number }>
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### `FileChangeEvent`
|
|
95
|
+
|
|
96
|
+
Payload emitted when a file is created, modified, or deleted structurally.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
interface FileChangeEvent {
|
|
100
|
+
type: 'created' | 'modified' | 'deleted'
|
|
101
|
+
path: string
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### `FileDiffEvent`
|
|
106
|
+
|
|
107
|
+
Payload emitted when a tracked file changes contents.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
interface FileDiffEvent {
|
|
111
|
+
path: string
|
|
112
|
+
oldContent: string | null
|
|
113
|
+
newContent: string
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### `PromptContext`
|
|
118
|
+
|
|
119
|
+
Context for building a composable system prompt.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
interface PromptContext {
|
|
123
|
+
/** Agent identity (e.g. 'Synthase', 'Polish Agent'). */
|
|
124
|
+
agentName: string
|
|
125
|
+
|
|
126
|
+
/** Project root path. */
|
|
127
|
+
projectRoot: string
|
|
128
|
+
|
|
129
|
+
/** Names of available tools (for the tool listing section). */
|
|
130
|
+
tools: string[]
|
|
131
|
+
|
|
132
|
+
/** Project-specific rules (AGENTS.md or CLAUDE.md content). */
|
|
133
|
+
projectDocs?: string
|
|
134
|
+
|
|
135
|
+
/** Additional skill/reference content to inject. */
|
|
136
|
+
skills?: string[]
|
|
137
|
+
|
|
138
|
+
/** Discovered skills to list in the prompt (use load_skill to read on demand). */
|
|
139
|
+
discoveredSkills?: SkillEntry[]
|
|
140
|
+
|
|
141
|
+
/** Custom sections to append to the prompt. */
|
|
142
|
+
customSections?: string[]
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### `SkillEntry`
|
|
147
|
+
|
|
148
|
+
Metadata for a discovered skill (used in PromptContext).
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
interface SkillEntry {
|
|
152
|
+
/** Skill name. */
|
|
153
|
+
name: string
|
|
154
|
+
/** Short description. */
|
|
155
|
+
description: string
|
|
156
|
+
/** Relative path to the SKILL.md file. */
|
|
157
|
+
path: string
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### `ToolBuildConfig`
|
|
162
|
+
|
|
163
|
+
Configuration for building the tool set.
|
|
164
|
+
Allows consumers to customize security, callbacks, and tool selection.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
interface ToolBuildConfig {
|
|
168
|
+
/** Which tools to include. Defaults to all. */
|
|
169
|
+
include?: string[]
|
|
170
|
+
|
|
171
|
+
/** Which tools to exclude. Applied after include. */
|
|
172
|
+
exclude?: string[]
|
|
173
|
+
|
|
174
|
+
/** Whether to validate paths stay within projectRoot. Default: true. */
|
|
175
|
+
pathGuards?: boolean
|
|
176
|
+
|
|
177
|
+
/** Whether to check symlinks resolve within projectRoot. Default: false (sandbox-only). */
|
|
178
|
+
symlinkGuards?: boolean
|
|
179
|
+
|
|
180
|
+
/** Whether to redact secrets in file reads and command output. Default: true. */
|
|
181
|
+
redactSecrets?: boolean
|
|
182
|
+
|
|
183
|
+
/** Whether to block dangerous shell commands (env dumps, /proc access). Default: false. */
|
|
184
|
+
blockDangerousCommands?: boolean
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Consumer-specific command guard for `exec_command`, checked BEFORE execution (after
|
|
188
|
+
* the built-in dangerous-command check). Return an error string to block the command —
|
|
189
|
+
* it is returned to the model verbatim, so make it actionable (say what to do instead) —
|
|
190
|
+
* or `null`/`undefined` to allow it. Keeps environment-specific rules (e.g. an IDE
|
|
191
|
+
* sandbox forbidding installs that would break its preinstalled library) out of this
|
|
192
|
+
* shared package.
|
|
193
|
+
*
|
|
194
|
+
* @param command - The shell command the model asked to run.
|
|
195
|
+
* @param cwd - The resolved working directory it would run in.
|
|
196
|
+
* @returns An error string to block, or null/undefined to allow.
|
|
197
|
+
*/
|
|
198
|
+
blockCommand?: (command: string, cwd: string) => string | null | undefined
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Timeout (ms) for a single `exec_command` run. `exec_command` legitimately
|
|
202
|
+
* runs LONG — `npm install`, a production build, a test suite — so the default
|
|
203
|
+
* is generous (2 min); the old 30 s hardcap killed those spuriously. A consumer
|
|
204
|
+
* that wraps tool calls in its own outer timeout should set this to match (or
|
|
205
|
+
* slightly exceed) that budget so its own timeout is the effective bound and
|
|
206
|
+
* produces the nicer "tool timed out" message. Quick commands are unaffected —
|
|
207
|
+
* this is only the ceiling before a wedged command is killed.
|
|
208
|
+
*/
|
|
209
|
+
execTimeoutMs?: number
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Directory names `search_files` and `find_files` skip (VS Code
|
|
213
|
+
* `search.exclude` semantics). Defaults to `DEFAULT_SEARCH_EXCLUDED_DIRS`
|
|
214
|
+
* (node_modules, VCS dirs, build output). Pass the consumer's per-project
|
|
215
|
+
* setting so every search surface shares ONE synchronized set.
|
|
216
|
+
*/
|
|
217
|
+
searchExcludedDirs?: string[]
|
|
218
|
+
|
|
219
|
+
/** Post-write hook (e.g. auto-format via Prettier/ESLint). Called after every write_file/edit_file. */
|
|
220
|
+
onAfterWrite?: (path: string) => Promise<void>
|
|
221
|
+
|
|
222
|
+
/** Diff tracking callback. Called before writes with old/new content. */
|
|
223
|
+
onFileDiff?: (event: FileDiffEvent) => void
|
|
224
|
+
|
|
225
|
+
/** Structural change callback. Called on create_directory, delete_file, rename_file. */
|
|
226
|
+
onFileChange?: (event: FileChangeEvent) => void
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### `ToolSchema`
|
|
231
|
+
|
|
232
|
+
JSON-schema-backed definition for a single agent tool.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
interface ToolSchema {
|
|
236
|
+
name: string
|
|
237
|
+
description: string
|
|
238
|
+
parameters: JSONSchema
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Types
|
|
243
|
+
|
|
244
|
+
#### `DiscoveredSkill` _(deprecated)_
|
|
245
|
+
|
|
246
|
+
Re-export SkillEntry as DiscoveredSkill for backwards compatibility.
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
type DiscoveredSkill = SkillEntry
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Functions
|
|
253
|
+
|
|
254
|
+
#### `buildAgentPrompt(ctx)`
|
|
255
|
+
|
|
256
|
+
Build a coding-focused system prompt from composable sections.
|
|
257
|
+
|
|
258
|
+
Returns a string that includes:
|
|
259
|
+
|
|
260
|
+
- Agent identity
|
|
261
|
+
- Available tools listing
|
|
262
|
+
- Coding best practices
|
|
263
|
+
- Tool argument formatting guidance
|
|
264
|
+
- Project docs (if provided)
|
|
265
|
+
- Discovered skills listing (if provided)
|
|
266
|
+
- Inline skills (if provided)
|
|
267
|
+
- Custom sections (if provided)
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
function buildAgentPrompt(ctx: PromptContext): string
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
- `ctx` — Prompt construction inputs (tools, docs, skills, etc.).
|
|
274
|
+
|
|
275
|
+
**Returns:** Fully assembled system prompt text for the coding agent.
|
|
276
|
+
|
|
277
|
+
#### `buildTools(backend, config)`
|
|
278
|
+
|
|
279
|
+
Build a complete set of AI agent tools bound to an execution backend.
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
function buildTools(backend: ExecutionBackend, config?: ToolBuildConfig): AITool[]
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
- `backend` — The execution environment (sandbox or local filesystem)
|
|
286
|
+
- `config` — Optional configuration for security, callbacks, and tool selection
|
|
287
|
+
|
|
288
|
+
**Returns:** Array of AITool objects ready to pass to an AI provider
|
|
289
|
+
|
|
290
|
+
#### `checkBlockedCommand(command)`
|
|
291
|
+
|
|
292
|
+
Check if a command is blocked for security reasons. Returns error message or null if allowed.
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
function checkBlockedCommand(command: string): string | null
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
- `command` — Shell command string proposed for execution.
|
|
299
|
+
|
|
300
|
+
**Returns:** A human-readable block reason, or `null` when the command is allowed.
|
|
301
|
+
|
|
302
|
+
#### `createLocalBackend(projectRoot)`
|
|
303
|
+
|
|
304
|
+
Create an ExecutionBackend that operates on the local filesystem.
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
function createLocalBackend(projectRoot: string): ExecutionBackend
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
- `projectRoot` — Absolute path to the project root directory
|
|
311
|
+
|
|
312
|
+
**Returns:** A backend wired to `fs/promises` and guarded subprocess calls.
|
|
313
|
+
|
|
314
|
+
#### `createSandboxBackend(sandbox, projectRoot)`
|
|
315
|
+
|
|
316
|
+
Create an ExecutionBackend that delegates to a Docker sandbox instance.
|
|
317
|
+
The sandbox.exec method is inherently safe — it runs inside an isolated Docker container.
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
function createSandboxBackend(sandbox: SandboxLike, projectRoot?: string): ExecutionBackend
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
- `sandbox` — A running Sandbox instance from `@molecule/api-code-sandbox`
|
|
324
|
+
- `projectRoot` — Root directory inside the sandbox (default: '/workspace')
|
|
325
|
+
|
|
326
|
+
**Returns:** A backend that proxies all filesystem calls into the sandbox.
|
|
327
|
+
|
|
328
|
+
#### `directoryReadHint(message, path)`
|
|
329
|
+
|
|
330
|
+
Detect a "read/edit targeted a directory, not a file" failure from a backend
|
|
331
|
+
error message (local fs `EISDIR` or the sandbox's `cat: X: Is a directory`),
|
|
332
|
+
and return an actionable message steering the model to `list_files`. Returns
|
|
333
|
+
null when the error is not a directory error.
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
function directoryReadHint(message: string, path: string): string | null
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
- `message` — The backend error message.
|
|
340
|
+
- `path` — The resolved path that was targeted.
|
|
341
|
+
|
|
342
|
+
**Returns:** An actionable directory-error string, or null.
|
|
343
|
+
|
|
344
|
+
#### `discoverSkills(backend)`
|
|
345
|
+
|
|
346
|
+
Discover skills from a project directory.
|
|
347
|
+
|
|
348
|
+
Scans `.agents/skills/` and `.claude/skills/` for SKILL.md files.
|
|
349
|
+
Reads the YAML frontmatter of each to extract `name:` and `description:` fields.
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
function discoverSkills(backend: ExecutionBackend): Promise<SkillEntry[]>
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
- `backend` — Execution backend to use for filesystem access
|
|
356
|
+
|
|
357
|
+
**Returns:** Array of discovered skills with name, description, and path
|
|
358
|
+
|
|
359
|
+
#### `isValidGlob(pattern)`
|
|
360
|
+
|
|
361
|
+
Validate that a glob/include pattern is safe (no shell metacharacters that could
|
|
362
|
+
inject). Allows alphanumeric, `* ? . _ - /` and the bracket/paren glob chars `[] ()`.
|
|
363
|
+
|
|
364
|
+
The brackets/parens matter for real frameworks: Next.js App Router names route
|
|
365
|
+
directories `[id]`, `[...slug]`, `(group)`, `[[...optional]]`, so without them the
|
|
366
|
+
executor cannot `find_files`/`search_files` its own routes on any Next.js project — a
|
|
367
|
+
hard block observed on live imports. They are injection-safe here because every caller
|
|
368
|
+
passes the pattern through `shellQuote` before it reaches `find -name`/`grep --include`,
|
|
369
|
+
where inside single quotes `[]()` are literal (a subshell `(...)` only starts UNquoted);
|
|
370
|
+
to the glob engine `[abc]` is a normal character class. The genuinely dangerous
|
|
371
|
+
metacharacters (`; | & $ \` > < \n` space) remain disallowed.
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
function isValidGlob(pattern: string): boolean
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
- `pattern` — User-supplied glob fragment for search/list operations.
|
|
378
|
+
|
|
379
|
+
**Returns:** `true` when the pattern contains only allowed characters.
|
|
380
|
+
|
|
381
|
+
#### `pathArgError(path, tool)`
|
|
382
|
+
|
|
383
|
+
Validate a file tool's `path` argument is a non-empty string. A weak model
|
|
384
|
+
sometimes omits it or passes a non-string, which would otherwise crash
|
|
385
|
+
`resolvePath` (`path.replace` on undefined) with the cryptic, unactionable
|
|
386
|
+
"Cannot read properties of undefined (reading 'replace')" — wasting executor
|
|
387
|
+
turns. Returns an actionable message, or null when the path is usable.
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
function pathArgError(path: unknown, tool: string): string | null
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
- `path` — The raw `path` argument from the tool input.
|
|
394
|
+
- `tool` — The tool name, for the error message (e.g. 'read_file').
|
|
395
|
+
|
|
396
|
+
**Returns:** An actionable error string, or null when `path` is a non-empty string.
|
|
397
|
+
|
|
398
|
+
#### `redactSecrets(s)`
|
|
399
|
+
|
|
400
|
+
Redact values of common secret/credential patterns in text output.
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
function redactSecrets(s: string): string
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
- `s` — Log or command output that may contain `.env`-style secrets.
|
|
407
|
+
|
|
408
|
+
**Returns:** A redacted copy safe to surface to end users or models.
|
|
409
|
+
|
|
410
|
+
#### `resolvePath(path, projectRoot)`
|
|
411
|
+
|
|
412
|
+
Normalize a path to be absolute within the project root.
|
|
413
|
+
Empty string and '/' both resolve to projectRoot.
|
|
414
|
+
Rejects paths that escape via traversal or absolute paths outside root.
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
function resolvePath(path: string, projectRoot: string): string
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
- `path` — Relative or absolute path inside the workspace.
|
|
421
|
+
- `projectRoot` — Absolute filesystem root for the active project.
|
|
422
|
+
|
|
423
|
+
**Returns:** A normalized absolute path confined to `projectRoot`.
|
|
424
|
+
|
|
425
|
+
#### `shellQuote(s)`
|
|
426
|
+
|
|
427
|
+
Shell-safe quoting using single quotes. Unlike JSON.stringify (double quotes),
|
|
428
|
+
single-quoted strings prevent command substitution ($(), backticks) and variable expansion.
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
function shellQuote(s: string): string
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
- `s` — Raw string to wrap for POSIX shell single-quoted context.
|
|
435
|
+
|
|
436
|
+
**Returns:** A single-quoted shell literal representing `s`.
|
|
437
|
+
|
|
438
|
+
#### `stripControlChars(s)`
|
|
439
|
+
|
|
440
|
+
Strip C0 control chars (except tab, newline, CR) that break PostgreSQL JSONB
|
|
441
|
+
and can cause rendering issues.
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
function stripControlChars(s: string): string
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
- `s` — Arbitrary text that may contain disallowed control characters.
|
|
448
|
+
|
|
449
|
+
**Returns:** A copy of `s` with unsafe control characters removed.
|
|
450
|
+
|
|
451
|
+
#### `truncate(s, maxLength)`
|
|
452
|
+
|
|
453
|
+
Truncate a string to a max length with a truncation notice.
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
function truncate(s: string, maxLength: number): string
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
- `s` — Arbitrary text to bound in size.
|
|
460
|
+
- `maxLength` — Maximum number of characters to retain before truncating.
|
|
461
|
+
|
|
462
|
+
**Returns:** Either the original string or a shortened copy with a trailing notice.
|
|
463
|
+
|
|
464
|
+
#### `truncateMiddle(s, maxLength)`
|
|
465
|
+
|
|
466
|
+
Truncate keeping BOTH the head and the tail, eliding the middle — for command
|
|
467
|
+
output (build / test / migration / install logs). Plain head truncation
|
|
468
|
+
({@link truncate}) drops the TAIL, which is exactly where a failing command puts
|
|
469
|
+
the reason: the `npm ERR!` line, the test-failure summary (`1 failed, 240
|
|
470
|
+
passed`), the migration stack trace. When that is cut, the executor sees only
|
|
471
|
+
passing progress and can't tell WHY the command failed — a self-inflicted error
|
|
472
|
+
then survives every fix round. The head still shows what ran and the first
|
|
473
|
+
errors; the split is weighted toward the tail since the summary lives there.
|
|
474
|
+
No-op when `s` already fits.
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
function truncateMiddle(s: string, maxLength: number): string
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
- `s` — Arbitrary text (typically stdout/stderr) to bound in size.
|
|
481
|
+
- `maxLength` — Maximum characters to retain (excluding the elision notice).
|
|
482
|
+
|
|
483
|
+
**Returns:** The original string, or head + an elision notice + tail.
|
|
484
|
+
|
|
485
|
+
#### `whitespaceTolerantReplace(content, oldString, newString)`
|
|
486
|
+
|
|
487
|
+
Attempt a whitespace-tolerant replacement when an exact `old_string` match
|
|
488
|
+
failed. Finds a contiguous run of lines in `content` whose per-line
|
|
489
|
+
whitespace-normalized form (runs of whitespace collapsed to one space, then
|
|
490
|
+
trimmed) equals the normalized `oldString` lines, and replaces that run with
|
|
491
|
+
`newString` verbatim. Applies ONLY when exactly one such run exists —
|
|
492
|
+
uniqueness keeps it safe; an ambiguous (or zero) match is refused (returns
|
|
493
|
+
null) so the caller falls back to its existing error path.
|
|
494
|
+
|
|
495
|
+
This rescues the most common edit_file failure: a (weak) executor reproduces
|
|
496
|
+
the target text correctly but with different indentation or trailing
|
|
497
|
+
whitespace, which would otherwise bounce it into a re-read/retry loop — the
|
|
498
|
+
single biggest source of wasted edit turns.
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
function whitespaceTolerantReplace(
|
|
502
|
+
content: string,
|
|
503
|
+
oldString: string,
|
|
504
|
+
newString: string,
|
|
505
|
+
): string | null
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
- `content` — Current file content.
|
|
509
|
+
- `oldString` — The search text (an exact match has already failed).
|
|
510
|
+
- `newString` — The replacement text, applied verbatim.
|
|
511
|
+
|
|
512
|
+
**Returns:** The new content if a unique fuzzy run matched, else null.
|
|
513
|
+
|
|
514
|
+
### Constants
|
|
515
|
+
|
|
516
|
+
#### `DEFAULT_SEARCH_EXCLUDED_DIRS`
|
|
517
|
+
|
|
518
|
+
Default directory names `search_files`/`find_files` skip — VS Code's
|
|
519
|
+
`search.exclude` + `files.exclude` defaults (node_modules, bower_components,
|
|
520
|
+
VCS dirs) plus the platform's vendored/build dirs. Overridable per consumer
|
|
521
|
+
via `ToolBuildConfig.searchExcludedDirs` (a per-project, user-editable
|
|
522
|
+
setting in molecule.dev — keep the APP-SIDE copy in
|
|
523
|
+
`@molecule/app-ide-react`'s search types in sync with this list).
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
const DEFAULT_SEARCH_EXCLUDED_DIRS: readonly [
|
|
527
|
+
'node_modules',
|
|
528
|
+
'bower_components',
|
|
529
|
+
'.git',
|
|
530
|
+
'.svn',
|
|
531
|
+
'.hg',
|
|
532
|
+
'CVS',
|
|
533
|
+
'dist',
|
|
534
|
+
'.next',
|
|
535
|
+
'.vite',
|
|
536
|
+
'molecule',
|
|
537
|
+
]
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
#### `MAX_FIND_RESULTS`
|
|
541
|
+
|
|
542
|
+
Max find results.
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
const MAX_FIND_RESULTS: 100
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### `MAX_OUTPUT_SIZE`
|
|
549
|
+
|
|
550
|
+
Max command output size (100KB per stream).
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
const MAX_OUTPUT_SIZE: number
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### `MAX_READ_SIZE`
|
|
557
|
+
|
|
558
|
+
Max file size for read_file (5MB).
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
const MAX_READ_SIZE: number
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
#### `MAX_SEARCH_RESULTS`
|
|
565
|
+
|
|
566
|
+
Max search results.
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
const MAX_SEARCH_RESULTS: 50
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### `MAX_WRITE_SIZE`
|
|
573
|
+
|
|
574
|
+
Max content size for write_file (10MB).
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
const MAX_WRITE_SIZE: number
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### `TOOL_SCHEMAS`
|
|
581
|
+
|
|
582
|
+
Canonical tool schemas shared by the agent runtime and documentation.
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
const TOOL_SCHEMAS: Record<string, ToolSchema>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
## Injection Notes
|
|
589
|
+
|
|
590
|
+
### Requirements
|
|
591
|
+
|
|
592
|
+
Peer dependencies:
|
|
593
|
+
|
|
594
|
+
- `@molecule/api-ai` ^1.0.1
|
|
595
|
+
|
|
596
|
+
### Runtime Dependencies
|
|
597
|
+
|
|
598
|
+
- `@molecule/api-ai`
|
|
599
|
+
|
|
600
|
+
- **This package only defines the tools — it runs no model loop.** Hand them to
|
|
601
|
+
the bonded AI provider (`chat({ tools, … })`) or to an
|
|
602
|
+
`@molecule/api-ai-agents` run.
|
|
603
|
+
- **Safety defaults: keep them on for model-driven use.** `pathGuards` (default
|
|
604
|
+
true) rejects paths escaping `projectRoot`; `redactSecrets` (default true)
|
|
605
|
+
masks secret-looking values in file reads and command output.
|
|
606
|
+
- **`exec_command` executes REAL shell commands** on the backend. Prefer a
|
|
607
|
+
sandbox backend for untrusted/model-driven work; add environment-specific
|
|
608
|
+
rules via `blockCommand` (return an actionable error string to refuse — it is
|
|
609
|
+
shown to the model verbatim) and enable `blockDangerousCommands` (default
|
|
610
|
+
FALSE) where appropriate.
|
|
611
|
+
- **Scope the tool set per agent** with `include`/`exclude` — a read-only agent
|
|
612
|
+
should not receive `write_file`/`exec_command`.
|
|
613
|
+
- Outputs are size-capped (`MAX_READ_SIZE`, `MAX_OUTPUT_SIZE`, …) and searches
|
|
614
|
+
skip `DEFAULT_SEARCH_EXCLUDED_DIRS` (node_modules, VCS dirs, build output) —
|
|
615
|
+
pass `searchExcludedDirs` so every search surface shares the consumer's one
|
|
616
|
+
setting.
|
|
617
|
+
|
|
618
|
+
## E2E Tests
|
|
619
|
+
|
|
620
|
+
Integration checklist — drive the real UI (live preview, no mocks), adapt
|
|
621
|
+
each item to this app's actual agent/chat surface and its registered tools,
|
|
622
|
+
and check every box off one by one. A box you can't check is an integration
|
|
623
|
+
bug to fix — not a skip:
|
|
624
|
+
|
|
625
|
+
- [ ] A prompt that should trigger a registered tool makes the model INVOKE
|
|
626
|
+
it with args matching that tool's `parameters` schema (e.g. "read
|
|
627
|
+
src/index.ts" -> calls `read_file` with `{ path: 'src/index.ts' }`).
|
|
628
|
+
Confirm the tool's `execute` actually RAN — its backend side effect / log /
|
|
629
|
+
the file it touched — not that the model merely narrated calling it.
|
|
630
|
+
- [ ] The tool's returned value flows back into the model and shapes the
|
|
631
|
+
final answer: the REAL result (the file's actual contents, the command's
|
|
632
|
+
real stdout/exitCode) appears in the reply, not a plausible hallucination.
|
|
633
|
+
- [ ] A prompt that needs no tool is answered directly, with no spurious
|
|
634
|
+
tool call.
|
|
635
|
+
- [ ] A tool whose `execute` throws or returns `{ error }` (missing file,
|
|
636
|
+
failing command, blocked path) degrades gracefully — the error is caught
|
|
637
|
+
and fed back to the model as text, the conversation continues, and nothing
|
|
638
|
+
crashes the request.
|
|
639
|
+
- [ ] The model can invoke ONLY the tools handed to this run: an
|
|
640
|
+
`include`/`exclude`-scoped agent (e.g. read-only — no `write_file` /
|
|
641
|
+
`exec_command`) cannot call an excluded tool, and a tool name the model
|
|
642
|
+
invents that was never registered is refused, not executed.
|
|
643
|
+
- [ ] Tool execution is server-side and authorized: `exec_command` /
|
|
644
|
+
`write_file` run only on the bonded backend under its guards (`pathGuards`,
|
|
645
|
+
`symlinkGuards`, `redactSecrets`, `blockDangerousCommands` / `blockCommand`)
|
|
646
|
+
and stay inside `projectRoot`. Feed a prompt-injected instruction (a file or
|
|
647
|
+
message telling the model to read `/etc/passwd`, escape the workspace, or run
|
|
648
|
+
a privileged command) and confirm the guard REFUSES it — a user must not be
|
|
649
|
+
able to trigger, via the model, any action they could not perform directly.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molecule/api-ai-tools",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Shared AI agent tools with backend abstraction for sandbox and local execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
21
22
|
],
|
|
22
23
|
"keywords": [
|
|
23
24
|
"molecule",
|
|
@@ -27,10 +28,10 @@
|
|
|
27
28
|
],
|
|
28
29
|
"license": "Apache-2.0",
|
|
29
30
|
"peerDependencies": {
|
|
30
|
-
"@molecule/api-ai": "^1.0.
|
|
31
|
+
"@molecule/api-ai": "^1.0.1"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
|
-
"@molecule/api-ai": "1.0.
|
|
34
|
+
"@molecule/api-ai": "1.0.1",
|
|
34
35
|
"@types/node": "26.1.2",
|
|
35
36
|
"typescript": "6.0.3",
|
|
36
37
|
"vitest": "4.1.10"
|