@molecule/api-ai-tools 1.0.9 → 1.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/README.md +241 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/input-normalizer.d.ts +92 -0
- package/dist/input-normalizer.d.ts.map +1 -0
- package/dist/input-normalizer.js +191 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +69 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +450 -88
- package/dist/utilities.d.ts +79 -0
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +130 -2
- package/package.json +1 -1
package/dist/utilities.d.ts
CHANGED
|
@@ -64,6 +64,31 @@ export declare function redactSecretsInCode(s: string): string;
|
|
|
64
64
|
* @returns `true` when the file is an env file.
|
|
65
65
|
*/
|
|
66
66
|
export declare function isEnvFilePath(path: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* The time budget a COMMAND declares for itself — a `timeout <seconds>` prefix
|
|
69
|
+
* anywhere in it, or the tool call's own `timeout` (ms) parameter, whichever is
|
|
70
|
+
* larger. Zero when it declares none.
|
|
71
|
+
*
|
|
72
|
+
* @param command - The shell command.
|
|
73
|
+
* @param timeoutParam - The tool call's `timeout` input, in milliseconds.
|
|
74
|
+
* @returns The declared budget in seconds, or 0.
|
|
75
|
+
*/
|
|
76
|
+
export declare function declaredBudgetSeconds(command: string, timeoutParam: unknown): number;
|
|
77
|
+
/**
|
|
78
|
+
* Whether a command's output reaches the tool only when the whole pipeline
|
|
79
|
+
* ends — a pipe into `tail`, `grep`, `sort`, `wc` and friends, which
|
|
80
|
+
* block-buffer when stdout is not a terminal.
|
|
81
|
+
*
|
|
82
|
+
* It matters only in combination with an overrun: a command stopped at the
|
|
83
|
+
* tool's ceiling normally hands back everything it printed, but behind one of
|
|
84
|
+
* these it printed nothing, so the whole budget is spent for no information.
|
|
85
|
+
* Measured across six real agent runs: ten such commands, every one of them
|
|
86
|
+
* piped, 49 minutes returning a few hundred bytes each.
|
|
87
|
+
*
|
|
88
|
+
* @param command - The shell command.
|
|
89
|
+
* @returns True when a kill would return no useful output.
|
|
90
|
+
*/
|
|
91
|
+
export declare function outputIsWithheldUntilExit(command: string): boolean;
|
|
67
92
|
/**
|
|
68
93
|
* Check if a command is blocked for security reasons. Returns error message or null if allowed.
|
|
69
94
|
*
|
|
@@ -127,6 +152,60 @@ export declare const MAX_READ_SIZE: number;
|
|
|
127
152
|
export declare const MAX_WRITE_SIZE: number;
|
|
128
153
|
/** Max command output size (100KB per stream). */
|
|
129
154
|
export declare const MAX_OUTPUT_SIZE: number;
|
|
155
|
+
/**
|
|
156
|
+
* Most lines of surrounding context `search_files` will return per match.
|
|
157
|
+
* Bounded so a broad pattern with a generous context cannot pull a whole tree
|
|
158
|
+
* into one reply.
|
|
159
|
+
*/
|
|
160
|
+
export declare const MAX_SEARCH_CONTEXT_LINES = 40;
|
|
161
|
+
/**
|
|
162
|
+
* Return a line window of `content`, or null when no window was asked for.
|
|
163
|
+
*
|
|
164
|
+
* `read_file` returned whole files only, so an executor that wanted part of one
|
|
165
|
+
* shelled out: across six real agent runs, 260 of 377 shell-inspection commands
|
|
166
|
+
* (69%) were `head`/`tail`/`sed -n` windowing a file the tool could have
|
|
167
|
+
* windowed itself. `offset` is 1-based, matching the line numbers every other
|
|
168
|
+
* tool reports and what `grep -n` prints.
|
|
169
|
+
*
|
|
170
|
+
* @param content - The file's full text.
|
|
171
|
+
* @param window - The requested `offset` (1-based) and `limit` (line count).
|
|
172
|
+
* @returns The window plus its position, or null when neither was given.
|
|
173
|
+
*/
|
|
174
|
+
export declare function sliceLines(content: string, window?: {
|
|
175
|
+
offset?: unknown;
|
|
176
|
+
limit?: unknown;
|
|
177
|
+
}): {
|
|
178
|
+
text: string;
|
|
179
|
+
offset: number;
|
|
180
|
+
lines: number;
|
|
181
|
+
totalLines: number;
|
|
182
|
+
truncated: boolean;
|
|
183
|
+
} | null;
|
|
184
|
+
/** Max search results. */
|
|
185
|
+
/**
|
|
186
|
+
* Most files one batched `read_file` call returns. The cap exists so a survey
|
|
187
|
+
* of a large tree cannot swallow the turn's context in one result; the tool
|
|
188
|
+
* reports where it stopped so the model continues from there.
|
|
189
|
+
*/
|
|
190
|
+
export declare const MAX_BATCH_READ_FILES = 25;
|
|
191
|
+
/**
|
|
192
|
+
* Total content bytes one batched `read_file` call returns, across all files.
|
|
193
|
+
* The per-file {@link MAX_READ_SIZE} still applies to each one.
|
|
194
|
+
*/
|
|
195
|
+
export declare const MAX_BATCH_READ_BYTES: number;
|
|
196
|
+
/**
|
|
197
|
+
* The most one read_file call returns for ONE file when no window was asked
|
|
198
|
+
* for, in chars. Larger files come back as their first window with a note
|
|
199
|
+
* saying how to read the rest.
|
|
200
|
+
*
|
|
201
|
+
* MAX_READ_SIZE (5 MB) is a bound on what the tool will open, not on what it
|
|
202
|
+
* should hand a model: a single batched read that returned a 1.2 MB README
|
|
203
|
+
* put the next three provider calls at 366k tokens (~330k of them fresh)
|
|
204
|
+
* against a 120k-token context cap (X0 run x5, call 26). ~80 KB is ~20–25k
|
|
205
|
+
* tokens — a fifth of that cap, and more than any one file read usually
|
|
206
|
+
* needs.
|
|
207
|
+
*/
|
|
208
|
+
export declare const MAX_READ_RETURN_CHARS: number;
|
|
130
209
|
/** Max search results. */
|
|
131
210
|
export declare const MAX_SEARCH_RESULTS = 50;
|
|
132
211
|
/** Max find results. */
|
package/dist/utilities.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,KAAG,MAEE,CAAA;AAyFhD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGnD;AA2BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkBlE;AAID;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAQrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|
package/dist/utilities.js
CHANGED
|
@@ -153,6 +153,42 @@ export function isEnvFilePath(path) {
|
|
|
153
153
|
const base = path.split('/').pop() ?? '';
|
|
154
154
|
return base === '.env' || base.startsWith('.env.') || base.endsWith('.env');
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* The time budget a COMMAND declares for itself — a `timeout <seconds>` prefix
|
|
158
|
+
* anywhere in it, or the tool call's own `timeout` (ms) parameter, whichever is
|
|
159
|
+
* larger. Zero when it declares none.
|
|
160
|
+
*
|
|
161
|
+
* @param command - The shell command.
|
|
162
|
+
* @param timeoutParam - The tool call's `timeout` input, in milliseconds.
|
|
163
|
+
* @returns The declared budget in seconds, or 0.
|
|
164
|
+
*/
|
|
165
|
+
export function declaredBudgetSeconds(command, timeoutParam) {
|
|
166
|
+
let declared = 0;
|
|
167
|
+
for (const m of command.matchAll(/(?:^|[;&|]\s*|\s)timeout\s+(?:-[a-zA-Z-]+\s+)*(\d+)(?![\d.])/g)) {
|
|
168
|
+
declared = Math.max(declared, Number(m[1]));
|
|
169
|
+
}
|
|
170
|
+
const asMs = Number(timeoutParam);
|
|
171
|
+
if (Number.isFinite(asMs) && asMs > 0)
|
|
172
|
+
declared = Math.max(declared, Math.round(asMs / 1000));
|
|
173
|
+
return declared;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Whether a command's output reaches the tool only when the whole pipeline
|
|
177
|
+
* ends — a pipe into `tail`, `grep`, `sort`, `wc` and friends, which
|
|
178
|
+
* block-buffer when stdout is not a terminal.
|
|
179
|
+
*
|
|
180
|
+
* It matters only in combination with an overrun: a command stopped at the
|
|
181
|
+
* tool's ceiling normally hands back everything it printed, but behind one of
|
|
182
|
+
* these it printed nothing, so the whole budget is spent for no information.
|
|
183
|
+
* Measured across six real agent runs: ten such commands, every one of them
|
|
184
|
+
* piped, 49 minutes returning a few hundred bytes each.
|
|
185
|
+
*
|
|
186
|
+
* @param command - The shell command.
|
|
187
|
+
* @returns True when a kill would return no useful output.
|
|
188
|
+
*/
|
|
189
|
+
export function outputIsWithheldUntilExit(command) {
|
|
190
|
+
return /\|\s*(?:tail|head|grep|egrep|fgrep|sort|uniq|wc|jq)\b/.test(command);
|
|
191
|
+
}
|
|
156
192
|
// ── Command blocking ──────────────────────────────────────────────────────────
|
|
157
193
|
/**
|
|
158
194
|
* Commands that dump environment variables — blocked to prevent secret leakage.
|
|
@@ -236,7 +272,13 @@ export function resolvePath(path, projectRoot) {
|
|
|
236
272
|
* @returns `true` when the pattern contains only allowed characters.
|
|
237
273
|
*/
|
|
238
274
|
export function isValidGlob(pattern) {
|
|
239
|
-
|
|
275
|
+
// `@` is in the set because every package in this ecosystem is scoped:
|
|
276
|
+
// measured on a live build, the executor tried
|
|
277
|
+
// `node_modules/@molecule/app-ui/*` twice and was refused both times, so in
|
|
278
|
+
// a molecule project it could not glob its own dependencies. The character
|
|
279
|
+
// is inert here — patterns are shell-quoted before they reach find/grep, and
|
|
280
|
+
// `@` has no meaning to either.
|
|
281
|
+
return /^[A-Za-z0-9@*?._/()[\]-]+$/.test(pattern);
|
|
240
282
|
}
|
|
241
283
|
/**
|
|
242
284
|
* Validate a file tool's `path` argument is a non-empty string. A weak model
|
|
@@ -276,6 +318,80 @@ export const MAX_READ_SIZE = 5 * 1024 * 1024;
|
|
|
276
318
|
export const MAX_WRITE_SIZE = 10 * 1024 * 1024;
|
|
277
319
|
/** Max command output size (100KB per stream). */
|
|
278
320
|
export const MAX_OUTPUT_SIZE = 100 * 1024;
|
|
321
|
+
/**
|
|
322
|
+
* Most lines of surrounding context `search_files` will return per match.
|
|
323
|
+
* Bounded so a broad pattern with a generous context cannot pull a whole tree
|
|
324
|
+
* into one reply.
|
|
325
|
+
*/
|
|
326
|
+
export const MAX_SEARCH_CONTEXT_LINES = 40;
|
|
327
|
+
/**
|
|
328
|
+
* Return a line window of `content`, or null when no window was asked for.
|
|
329
|
+
*
|
|
330
|
+
* `read_file` returned whole files only, so an executor that wanted part of one
|
|
331
|
+
* shelled out: across six real agent runs, 260 of 377 shell-inspection commands
|
|
332
|
+
* (69%) were `head`/`tail`/`sed -n` windowing a file the tool could have
|
|
333
|
+
* windowed itself. `offset` is 1-based, matching the line numbers every other
|
|
334
|
+
* tool reports and what `grep -n` prints.
|
|
335
|
+
*
|
|
336
|
+
* @param content - The file's full text.
|
|
337
|
+
* @param window - The requested `offset` (1-based) and `limit` (line count).
|
|
338
|
+
* @returns The window plus its position, or null when neither was given.
|
|
339
|
+
*/
|
|
340
|
+
export function sliceLines(content, window) {
|
|
341
|
+
const rawOffset = Number(window?.offset);
|
|
342
|
+
const rawLimit = Number(window?.limit);
|
|
343
|
+
const hasOffset = Number.isFinite(rawOffset) && rawOffset !== 0;
|
|
344
|
+
const hasLimit = Number.isFinite(rawLimit) && rawLimit > 0;
|
|
345
|
+
if (!hasOffset && !hasLimit)
|
|
346
|
+
return null;
|
|
347
|
+
const all = content.split('\n');
|
|
348
|
+
const totalLines = all.length;
|
|
349
|
+
// A NEGATIVE offset counts from the end, the `tail -N` convention. Without it
|
|
350
|
+
// "show me the last 30 lines" has no expression here, and the executor either
|
|
351
|
+
// reads the whole file or goes back to the shell — measured: asked for a
|
|
352
|
+
// file's tail with these tools available, it read the entire file 3 times out
|
|
353
|
+
// of 3 until this existed.
|
|
354
|
+
const fromEnd = hasOffset && rawOffset < 0;
|
|
355
|
+
const offset = fromEnd
|
|
356
|
+
? Math.max(1, totalLines + Math.trunc(rawOffset) + 1)
|
|
357
|
+
: hasOffset
|
|
358
|
+
? Math.min(Math.trunc(rawOffset), totalLines)
|
|
359
|
+
: 1;
|
|
360
|
+
const limit = hasLimit ? Math.trunc(rawLimit) : totalLines - offset + 1;
|
|
361
|
+
const picked = all.slice(offset - 1, offset - 1 + limit);
|
|
362
|
+
return {
|
|
363
|
+
text: picked.join('\n'),
|
|
364
|
+
offset,
|
|
365
|
+
lines: picked.length,
|
|
366
|
+
totalLines,
|
|
367
|
+
truncated: offset > 1 || offset - 1 + picked.length < totalLines,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/** Max search results. */
|
|
371
|
+
/**
|
|
372
|
+
* Most files one batched `read_file` call returns. The cap exists so a survey
|
|
373
|
+
* of a large tree cannot swallow the turn's context in one result; the tool
|
|
374
|
+
* reports where it stopped so the model continues from there.
|
|
375
|
+
*/
|
|
376
|
+
export const MAX_BATCH_READ_FILES = 25;
|
|
377
|
+
/**
|
|
378
|
+
* Total content bytes one batched `read_file` call returns, across all files.
|
|
379
|
+
* The per-file {@link MAX_READ_SIZE} still applies to each one.
|
|
380
|
+
*/
|
|
381
|
+
export const MAX_BATCH_READ_BYTES = 200 * 1024;
|
|
382
|
+
/**
|
|
383
|
+
* The most one read_file call returns for ONE file when no window was asked
|
|
384
|
+
* for, in chars. Larger files come back as their first window with a note
|
|
385
|
+
* saying how to read the rest.
|
|
386
|
+
*
|
|
387
|
+
* MAX_READ_SIZE (5 MB) is a bound on what the tool will open, not on what it
|
|
388
|
+
* should hand a model: a single batched read that returned a 1.2 MB README
|
|
389
|
+
* put the next three provider calls at 366k tokens (~330k of them fresh)
|
|
390
|
+
* against a 120k-token context cap (X0 run x5, call 26). ~80 KB is ~20–25k
|
|
391
|
+
* tokens — a fifth of that cap, and more than any one file read usually
|
|
392
|
+
* needs.
|
|
393
|
+
*/
|
|
394
|
+
export const MAX_READ_RETURN_CHARS = 80 * 1024;
|
|
279
395
|
/** Max search results. */
|
|
280
396
|
export const MAX_SEARCH_RESULTS = 50;
|
|
281
397
|
/** Max find results. */
|
|
@@ -367,7 +483,19 @@ export function truncateMiddle(s, maxLength) {
|
|
|
367
483
|
* @returns The new content if a unique fuzzy run matched, else null.
|
|
368
484
|
*/
|
|
369
485
|
export function whitespaceTolerantReplace(content, oldString, newString) {
|
|
370
|
-
|
|
486
|
+
// Per-line normalization: runs of whitespace become one space, and spaces
|
|
487
|
+
// next to punctuation are dropped entirely. The second rule is for the edit
|
|
488
|
+
// a weak executor makes from memory with one space missing —
|
|
489
|
+
// `readdirSync,readFileSync` against a file that reads
|
|
490
|
+
// `readdirSync, readFileSync` (X0 run x6: 15 of 48 edits failed, most of
|
|
491
|
+
// them this shape, after the file's exact text had left the context). A
|
|
492
|
+
// missing space is zero whitespace, which the runs rule cannot see. This
|
|
493
|
+
// only decides WHERE the block is; the replacement text is applied
|
|
494
|
+
// verbatim, and uniqueness is still required.
|
|
495
|
+
const norm = (s) => s
|
|
496
|
+
.replace(/\s*([,;:{}()[\]=<>.])\s*/g, '$1')
|
|
497
|
+
.replace(/\s+/g, ' ')
|
|
498
|
+
.trim();
|
|
371
499
|
const fileLines = content.split('\n');
|
|
372
500
|
const normOld = oldString.split('\n').map(norm);
|
|
373
501
|
// Refuse a degenerate all-blank search block (would match any blank run).
|