@better-fs-tools/pi 0.2.0 → 0.3.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 +172 -47
- package/dist/agent-core.d.ts +141 -0
- package/dist/agent-core.js +121 -0
- package/dist/bash-tool.d.ts +16 -24
- package/dist/bash-tool.js +23 -58
- package/dist/details.d.ts +7 -1
- package/dist/details.js +18 -0
- package/dist/extension.d.ts +12 -0
- package/dist/extension.js +10 -8
- package/dist/fs-tools.d.ts +70 -36
- package/dist/fs-tools.js +33 -88
- package/dist/index.d.ts +9 -4
- package/dist/index.js +2 -0
- package/dist/locks.d.ts +16 -0
- package/dist/locks.js +68 -0
- package/dist/mutation-tools.d.ts +29 -42
- package/dist/mutation-tools.js +42 -93
- package/dist/node.d.ts +23 -0
- package/dist/node.js +76 -0
- package/dist/parts.js +11 -6
- package/dist/prompt.d.ts +30 -0
- package/dist/prompt.js +35 -0
- package/dist/renderers.d.ts +27 -0
- package/dist/renderers.js +72 -0
- package/dist/results.d.ts +13 -0
- package/dist/results.js +18 -0
- package/dist/tool.d.ts +17 -43
- package/dist/tool.js +26 -75
- package/package.json +25 -8
- package/dist/roots.d.ts +0 -20
- package/dist/roots.js +0 -58
package/dist/bash-tool.d.ts
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { TSchema } from "typebox";
|
|
3
3
|
import type { JsonObject } from "@better-fs-tools/read";
|
|
4
|
-
import type {
|
|
5
|
-
import type { BashSignature } from "@better-fs-tools/shell/signature";
|
|
4
|
+
import type { BashToolDefinition, BashToolDefinitionOptions } from "@better-fs-tools/shell/signature";
|
|
6
5
|
import type { PiContentPart } from "./parts.ts";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* Default Pi's own bash input, { command, timeout } with the timeout in
|
|
15
|
-
* seconds, so Pi's bash renderer and prompt fit. Its description names the
|
|
16
|
-
* configured timeouts, output limits, and runner id.
|
|
17
|
-
*/
|
|
18
|
-
readonly signature?: BashSignature;
|
|
19
|
-
readonly promptSnippet?: string;
|
|
20
|
-
readonly promptGuidelines?: readonly string[];
|
|
21
|
-
}
|
|
6
|
+
import type { PiPromptOptions } from "./prompt.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Options for createPiBashTool: the options of bashToolDefinition(), with the
|
|
9
|
+
* Pi extension context as host, plus the Pi prompt options. `runner` and `env`
|
|
10
|
+
* are required: piCwdCommandRunner() from `./node` runs commands in ctx.cwd.
|
|
11
|
+
*/
|
|
12
|
+
export type PiBashToolOptions = BashToolDefinitionOptions<ExtensionContext> & PiPromptOptions;
|
|
22
13
|
/** Pi's BashToolDetails shape, so Pi's bash renderer can show the saved output. */
|
|
23
14
|
export interface PiBashDetails {
|
|
24
15
|
fullOutputPath?: string;
|
|
@@ -26,8 +17,10 @@ export interface PiBashDetails {
|
|
|
26
17
|
export interface PiBashToolResult {
|
|
27
18
|
content: PiContentPart[];
|
|
28
19
|
details: PiBashDetails | undefined;
|
|
20
|
+
/** true for a nonzero exit, a timeout, and an error. */
|
|
21
|
+
isError: boolean;
|
|
29
22
|
}
|
|
30
|
-
/** Assignable to ToolDefinition<TSchema, PiBashDetails | undefined> from Pi
|
|
23
|
+
/** Assignable to ToolDefinition<TSchema, PiBashDetails | undefined> from Pi 1.x. */
|
|
31
24
|
export interface PiBashTool {
|
|
32
25
|
readonly name: string;
|
|
33
26
|
readonly label: string;
|
|
@@ -39,10 +32,9 @@ export interface PiBashTool {
|
|
|
39
32
|
execute(toolCallId: string, input: JsonObject, signal: AbortSignal | undefined, onUpdate: unknown, ctx: ExtensionContext): Promise<PiBashToolResult>;
|
|
40
33
|
}
|
|
41
34
|
/**
|
|
42
|
-
* Creates a Pi bash tool
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* extension does not register this tool, so Pi's own bash stays in place
|
|
46
|
-
* unless you register it.
|
|
35
|
+
* Creates a Pi bash tool with the options and defaults of createBashTool().
|
|
36
|
+
* The Pi extension does not register this tool, so Pi's own bash stays in
|
|
37
|
+
* place unless you register it.
|
|
47
38
|
*/
|
|
48
|
-
export declare function createPiBashTool(options
|
|
39
|
+
export declare function createPiBashTool(options: PiBashToolOptions): PiBashTool;
|
|
40
|
+
export declare function adaptPiBashTool(definition: BashToolDefinition<ExtensionContext>, prompt: PiPromptOptions): PiBashTool;
|
package/dist/bash-tool.js
CHANGED
|
@@ -1,76 +1,41 @@
|
|
|
1
|
-
import { isAbsolute } from "node:path";
|
|
2
1
|
import { Type } from "typebox";
|
|
3
|
-
import {
|
|
4
|
-
import { createBashTool, shellEnv } from "@better-fs-tools/shell";
|
|
5
|
-
import { bashSignatureMessages, defaultBashSignature } from "@better-fs-tools/shell/signature";
|
|
2
|
+
import { bashToolDefinition } from "@better-fs-tools/shell/signature";
|
|
6
3
|
import { toPiPart } from "./parts.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const MAX_CACHED_RUNNERS = 8;
|
|
4
|
+
import { checkPiOptions, piPrompt, splitPrompt } from "./prompt.js";
|
|
5
|
+
import { piBashArgs, setRenderArgs } from "./renderers.js";
|
|
10
6
|
/**
|
|
11
|
-
* Creates a Pi bash tool
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* extension does not register this tool, so Pi's own bash stays in place
|
|
15
|
-
* unless you register it.
|
|
7
|
+
* Creates a Pi bash tool with the options and defaults of createBashTool().
|
|
8
|
+
* The Pi extension does not register this tool, so Pi's own bash stays in
|
|
9
|
+
* place unless you register it.
|
|
16
10
|
*/
|
|
17
|
-
export function createPiBashTool(options
|
|
18
|
-
checkPiOptions(options, "bash");
|
|
19
|
-
const {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
runner,
|
|
31
|
-
cwd: (call) => call.host.cwd,
|
|
32
|
-
env: deps.env ?? shellEnv(() => process.env),
|
|
33
|
-
messages: { ...bashSignatureMessages(signature), ...deps.messages },
|
|
34
|
-
});
|
|
35
|
-
return Object.freeze({
|
|
36
|
-
name: signature.name,
|
|
37
|
-
label: signature.name,
|
|
38
|
-
description: signature.description,
|
|
39
|
-
promptSnippet,
|
|
40
|
-
promptGuidelines: [...promptGuidelines],
|
|
41
|
-
parameters: Type.Unsafe(signature.schema),
|
|
11
|
+
export function createPiBashTool(options) {
|
|
12
|
+
checkPiOptions(options, "Pi bash tool");
|
|
13
|
+
const { prompt, rest } = splitPrompt(options);
|
|
14
|
+
return adaptPiBashTool(bashToolDefinition(rest), prompt);
|
|
15
|
+
}
|
|
16
|
+
export function adaptPiBashTool(definition, prompt) {
|
|
17
|
+
const { signature } = definition;
|
|
18
|
+
const tool = Object.freeze({
|
|
19
|
+
name: definition.name,
|
|
20
|
+
label: definition.name,
|
|
21
|
+
description: definition.description,
|
|
22
|
+
...piPrompt(prompt, definition.description),
|
|
23
|
+
parameters: Type.Unsafe(definition.inputSchema),
|
|
42
24
|
async execute(toolCallId, input, signal, _onUpdate, ctx) {
|
|
43
|
-
checkPiContext(ctx, "bash");
|
|
44
|
-
if (!isAbsolute(ctx.cwd))
|
|
45
|
-
throw new TypeError("Pi bash execution requires an absolute ctx.cwd");
|
|
46
25
|
const call = {
|
|
47
26
|
...(signal === undefined ? {} : { signal }),
|
|
48
27
|
callId: toolCallId,
|
|
49
28
|
host: ctx,
|
|
50
29
|
};
|
|
51
|
-
const result = await
|
|
30
|
+
const result = await definition.execute(input, call);
|
|
52
31
|
const spill = result.output?.spill ?? null;
|
|
53
32
|
return {
|
|
54
33
|
content: result.content.map((part) => toPiPart(part, "bash")),
|
|
55
34
|
details: spill === null ? undefined : { fullOutputPath: spill },
|
|
35
|
+
isError: result.status !== "ok",
|
|
56
36
|
};
|
|
57
37
|
},
|
|
58
38
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const cache = new Map();
|
|
62
|
-
return (call) => {
|
|
63
|
-
const cwd = call.host.cwd;
|
|
64
|
-
const cached = cache.get(cwd);
|
|
65
|
-
if (cached !== undefined) {
|
|
66
|
-
cache.delete(cwd);
|
|
67
|
-
cache.set(cwd, cached);
|
|
68
|
-
return cached;
|
|
69
|
-
}
|
|
70
|
-
const made = nodeCommandRunner({ cwd });
|
|
71
|
-
cache.set(cwd, made);
|
|
72
|
-
if (cache.size > MAX_CACHED_RUNNERS)
|
|
73
|
-
cache.delete(cache.keys().next().value);
|
|
74
|
-
return made;
|
|
75
|
-
};
|
|
39
|
+
setRenderArgs(tool, (args) => signature.toInput(args), piBashArgs);
|
|
40
|
+
return tool;
|
|
76
41
|
}
|
package/dist/details.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ReadResult } from "@better-fs-tools/read";
|
|
1
|
+
import type { ReadFormatContext, ReadFormatter, ReadResult } from "@better-fs-tools/read";
|
|
2
2
|
/** Pi's published TruncationResult. Fields and meanings are Pi's. */
|
|
3
3
|
export interface PiTruncationResult {
|
|
4
4
|
content: string;
|
|
@@ -24,3 +24,9 @@ export interface PiReadDetails {
|
|
|
24
24
|
* total, because an approximate count is worse than none.
|
|
25
25
|
*/
|
|
26
26
|
export declare function toPiReadDetails(result: ReadResult, view: string | null, maxViewBytes: number): PiReadDetails;
|
|
27
|
+
/**
|
|
28
|
+
* The formatted text of an ok read in "view" mode, for toPiReadDetails. null
|
|
29
|
+
* for another status, a formatter that returns parts, or a formatter that
|
|
30
|
+
* throws.
|
|
31
|
+
*/
|
|
32
|
+
export declare function viewOf<THost>(formatter: ReadFormatter<THost>, result: ReadResult, ctx: ReadFormatContext<THost>): string | null;
|
package/dist/details.js
CHANGED
|
@@ -28,3 +28,21 @@ export function toPiReadDetails(result, view, maxViewBytes) {
|
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The formatted text of an ok read in "view" mode, for toPiReadDetails. null
|
|
33
|
+
* for another status, a formatter that returns parts, or a formatter that
|
|
34
|
+
* throws.
|
|
35
|
+
*/
|
|
36
|
+
export function viewOf(formatter, result, ctx) {
|
|
37
|
+
if (result.status !== "ok")
|
|
38
|
+
return null;
|
|
39
|
+
const { content: _content, ...outcome } = result;
|
|
40
|
+
try {
|
|
41
|
+
const view = formatter.format(outcome, ctx);
|
|
42
|
+
return typeof view === "string" ? view : null;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// The core already fell back and reported the error in "model" mode.
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/extension.d.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi package extension entry. Installing the package into a Pi project
|
|
3
|
+
* registers read, edit, and write, which replace Pi's own tools, and nothing
|
|
4
|
+
* else. It uses the recommended Pi configuration: a filesystem rooted at each
|
|
5
|
+
* call's ctx.cwd, Pi's own per-file queue as the locks, so Pi's edit and
|
|
6
|
+
* write wait for these tools, and nodeDigest(), which is faster on Node. Every
|
|
7
|
+
* other option has the shared default: no read store, and read returns images.
|
|
8
|
+
* A renderer resolver lets Pi's built-in read, edit, and write renderers draw
|
|
9
|
+
* them. Nothing is written outside the process: no settings file, no session file.
|
|
10
|
+
*/
|
|
11
|
+
import type { ToolRendererResolver } from "@earendil-works/pi-coding-agent";
|
|
1
12
|
import type { PiMutationTool } from "./mutation-tools.ts";
|
|
2
13
|
import type { PiReadTool } from "./tool.ts";
|
|
3
14
|
/** The part of Pi's ExtensionAPI this extension uses. Pi's full API satisfies it. */
|
|
4
15
|
export interface PiExtensionApi {
|
|
5
16
|
registerTool(tool: PiReadTool): void;
|
|
6
17
|
registerTool(tool: PiMutationTool): void;
|
|
18
|
+
registerToolRenderer(resolver: ToolRendererResolver): void;
|
|
7
19
|
}
|
|
8
20
|
export default function fsToolsExtension(pi: PiExtensionApi): void;
|
package/dist/extension.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
* Pi package extension entry. Installing the package into a Pi project
|
|
3
|
-
* registers read, edit, and write, as Pi's own tools are, and nothing else.
|
|
4
|
-
* There is no read store, so edit and write do not check for a read first.
|
|
5
|
-
* read returns images, as Pi's own read does.
|
|
6
|
-
* Nothing is written outside the process: no settings file, no session file.
|
|
7
|
-
*/
|
|
1
|
+
import { nodeDigest } from "@better-fs-tools/node";
|
|
8
2
|
import { createPiFsTools } from "./fs-tools.js";
|
|
3
|
+
import { piMutationQueueLocks } from "./locks.js";
|
|
4
|
+
import { piCwdFileSystem } from "./node.js";
|
|
5
|
+
import { piToolRenderers } from "./renderers.js";
|
|
9
6
|
export default function fsToolsExtension(pi) {
|
|
10
|
-
const tools = createPiFsTools(
|
|
7
|
+
const tools = createPiFsTools({
|
|
8
|
+
fs: piCwdFileSystem(),
|
|
9
|
+
locks: piMutationQueueLocks(),
|
|
10
|
+
digest: nodeDigest(),
|
|
11
|
+
});
|
|
11
12
|
pi.registerTool(tools.read);
|
|
12
13
|
pi.registerTool(tools.edit);
|
|
13
14
|
pi.registerTool(tools.write);
|
|
15
|
+
pi.registerToolRenderer(piToolRenderers([tools.read, tools.edit, tools.write]));
|
|
14
16
|
}
|
package/dist/fs-tools.d.ts
CHANGED
|
@@ -1,24 +1,48 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { Clock, Digest,
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
6
|
-
import type {
|
|
7
|
-
type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
readonly
|
|
2
|
+
import type { Clock, Digest, ToolCallContext } from "@better-fs-tools/read";
|
|
3
|
+
import type { ReadSignature } from "@better-fs-tools/read/signature";
|
|
4
|
+
import type { BashSignature } from "@better-fs-tools/shell/signature";
|
|
5
|
+
import type { FsToolsApplyPatchOptions, FsToolsBashOptions, FsToolsEditOptions, FsToolsOptions, FsToolsReadOptions, FsToolsState, FsToolsWriteOptions, InvalidateOutcome, LockManager } from "@better-fs-tools/write";
|
|
6
|
+
import type { EditSignature, PatchSignature, WriteSignature } from "@better-fs-tools/write/signature";
|
|
7
|
+
import type { PiBashTool } from "./bash-tool.ts";
|
|
8
|
+
import type { PiMutationTool } from "./mutation-tools.ts";
|
|
9
|
+
import type { PiPromptOptions } from "./prompt.ts";
|
|
10
|
+
import type { PiReadTool } from "./tool.ts";
|
|
11
|
+
type Host = ExtensionContext;
|
|
12
|
+
/** Read tool options for createPiFsTools. Shared keys are set once at the top level. */
|
|
13
|
+
export type PiFsToolsReadOptions = FsToolsReadOptions<Host> & PiPromptOptions & {
|
|
14
|
+
/** Default defaultReadSignature(). */
|
|
15
|
+
readonly signature?: ReadSignature;
|
|
16
|
+
};
|
|
17
|
+
export type PiFsToolsEditOptions = FsToolsEditOptions<Host> & PiPromptOptions & {
|
|
18
|
+
/** Default defaultEditSignature() for `matchers`. */
|
|
19
|
+
readonly signature?: EditSignature;
|
|
20
|
+
};
|
|
21
|
+
export type PiFsToolsWriteOptions = FsToolsWriteOptions<Host> & PiPromptOptions & {
|
|
22
|
+
/** Default defaultWriteSignature(). */
|
|
23
|
+
readonly signature?: WriteSignature;
|
|
24
|
+
};
|
|
25
|
+
export type PiFsToolsApplyPatchOptions = FsToolsApplyPatchOptions<Host> & PiPromptOptions & {
|
|
26
|
+
/** Default defaultPatchSignature(). */
|
|
27
|
+
readonly signature?: PatchSignature;
|
|
28
|
+
};
|
|
29
|
+
/** Bash tool options for createPiFsTools. `runner` and `env` are required. */
|
|
30
|
+
export type PiFsToolsBashOptions = FsToolsBashOptions<Host> & PiPromptOptions & {
|
|
31
|
+
/** Default defaultBashSignature() for the runner's id and the limits. */
|
|
32
|
+
readonly signature?: BashSignature;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The options of createFsTools(), with the Pi extension context as host, and
|
|
36
|
+
* a `signature` and the Pi prompt options in each tool's options.
|
|
37
|
+
*/
|
|
38
|
+
export interface PiFsToolsOptions extends Omit<FsToolsOptions<Host>, "read" | "edit" | "write" | "applyPatch" | "bash"> {
|
|
39
|
+
readonly read?: PiFsToolsReadOptions;
|
|
40
|
+
readonly edit?: PiFsToolsEditOptions;
|
|
41
|
+
readonly write?: PiFsToolsWriteOptions;
|
|
20
42
|
/** Off by default. `true` or an options object adds an apply_patch tool. */
|
|
21
|
-
readonly applyPatch?: boolean |
|
|
43
|
+
readonly applyPatch?: boolean | PiFsToolsApplyPatchOptions;
|
|
44
|
+
/** Off by default. Give a runner and an env to add a bash tool. */
|
|
45
|
+
readonly bash?: false | PiFsToolsBashOptions;
|
|
22
46
|
}
|
|
23
47
|
export interface PiFsTools {
|
|
24
48
|
readonly read: PiReadTool;
|
|
@@ -26,34 +50,44 @@ export interface PiFsTools {
|
|
|
26
50
|
readonly write: PiMutationTool;
|
|
27
51
|
/** null unless options.applyPatch is true or an object. */
|
|
28
52
|
readonly applyPatch: PiMutationTool | null;
|
|
29
|
-
|
|
30
|
-
|
|
53
|
+
/** null unless options.bash is set. */
|
|
54
|
+
readonly bash: PiBashTool | null;
|
|
55
|
+
readonly state: FsToolsState<Host>;
|
|
31
56
|
readonly digest: Digest;
|
|
32
57
|
readonly locks: LockManager;
|
|
33
58
|
readonly clock: Clock;
|
|
34
59
|
/**
|
|
35
|
-
* Forgets the read record for a path
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* throws. With state null it reports recorded: false.
|
|
60
|
+
* Forgets the read record for a path, so the next edit needs a read. Pass the
|
|
61
|
+
* call when fs or state is a factory; in a bash afterRun hook it is ctx.call.
|
|
62
|
+
* Never throws.
|
|
39
63
|
*/
|
|
40
|
-
invalidate(path: string, call?: ToolCallContext<
|
|
64
|
+
invalidate(path: string, call?: ToolCallContext<Host>): Promise<InvalidateOutcome>;
|
|
41
65
|
}
|
|
42
66
|
/** The createPiFsTools result when `applyPatch` is true or an object: it is never null. */
|
|
43
67
|
export interface PiFsToolsWithApplyPatch extends PiFsTools {
|
|
44
68
|
readonly applyPatch: PiMutationTool;
|
|
45
69
|
}
|
|
70
|
+
/** The createPiFsTools result when `bash` is set: it is never null. */
|
|
71
|
+
export interface PiFsToolsWithBash extends PiFsTools {
|
|
72
|
+
readonly bash: PiBashTool;
|
|
73
|
+
}
|
|
46
74
|
/**
|
|
47
|
-
* Creates Pi read, edit, and write tools
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* createPiBashTool() and call invalidate from its afterRun hook.
|
|
75
|
+
* Creates Pi read, edit, and write tools over one filesystem, sharing one
|
|
76
|
+
* digest, lock manager, and clock, and the read state when `state` is given.
|
|
77
|
+
* With `applyPatch`, also apply_patch. With `bash`, also bash, with the same
|
|
78
|
+
* digest and clock. Every option and default is the one of createFsTools().
|
|
79
|
+
* Throws TypeError where createFsTools() does, with createPiFsTools in the
|
|
80
|
+
* message.
|
|
54
81
|
*/
|
|
55
|
-
export declare function createPiFsTools(options:
|
|
56
|
-
readonly applyPatch: true |
|
|
82
|
+
export declare function createPiFsTools(options: PiFsToolsOptions & {
|
|
83
|
+
readonly applyPatch: true | PiFsToolsApplyPatchOptions;
|
|
84
|
+
readonly bash: PiFsToolsBashOptions;
|
|
85
|
+
}): PiFsToolsWithApplyPatch & PiFsToolsWithBash;
|
|
86
|
+
export declare function createPiFsTools(options: PiFsToolsOptions & {
|
|
87
|
+
readonly bash: PiFsToolsBashOptions;
|
|
88
|
+
}): PiFsToolsWithBash;
|
|
89
|
+
export declare function createPiFsTools(options: PiFsToolsOptions & {
|
|
90
|
+
readonly applyPatch: true | PiFsToolsApplyPatchOptions;
|
|
57
91
|
}): PiFsToolsWithApplyPatch;
|
|
58
|
-
export declare function createPiFsTools(options
|
|
92
|
+
export declare function createPiFsTools(options: PiFsToolsOptions): PiFsTools;
|
|
59
93
|
export {};
|
package/dist/fs-tools.js
CHANGED
|
@@ -1,93 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { adaptPiMutationTool
|
|
4
|
-
import { checkPiOptions,
|
|
5
|
-
import { adaptPiReadTool
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (!KNOWN_OPTIONS.has(key))
|
|
22
|
-
throw new TypeError(`Unknown createPiFsTools option: ${key}`);
|
|
23
|
-
}
|
|
24
|
-
const state = options.state;
|
|
25
|
-
if (state !== undefined && state !== null && !isReadStateStore(state)) {
|
|
26
|
-
throw new TypeError(`createPiFsTools state must be a read state store or null: a bundle takes one store, not a per-call factory`);
|
|
27
|
-
}
|
|
28
|
-
const given = {
|
|
29
|
-
read: options.read ?? {},
|
|
30
|
-
edit: options.edit ?? {},
|
|
31
|
-
write: options.write ?? {},
|
|
32
|
-
applyPatch: options.applyPatch === true
|
|
33
|
-
? {}
|
|
34
|
-
: options.applyPatch === false || options.applyPatch === undefined
|
|
35
|
-
? null
|
|
36
|
-
: options.applyPatch,
|
|
37
|
-
};
|
|
38
|
-
for (const [key, value] of Object.entries(given)) {
|
|
39
|
-
if (value === null)
|
|
40
|
-
continue;
|
|
41
|
-
checkPiOptions(value, key);
|
|
42
|
-
for (const shared of BUNDLE_SHARED_KEYS) {
|
|
43
|
-
if (Object.hasOwn(value, shared)) {
|
|
44
|
-
throw new TypeError(`createPiFsTools ${key} options cannot set ${shared}: set it once at the top level`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const { denyRoots, symlinks, hardLinks, newFileMode, newDirectoryMode } = options;
|
|
49
|
-
const fs = piFileSystems({ denyRoots, symlinks, hardLinks, newFileMode, newDirectoryMode });
|
|
50
|
-
const { signature: _readSignature, promptSnippet: _readSnippet, promptGuidelines: _readLines, ...read } = given.read;
|
|
51
|
-
const { signature: _editSignature, promptSnippet: _editSnippet, promptGuidelines: _editLines, ...edit } = given.edit;
|
|
52
|
-
const { signature: _writeSignature, promptSnippet: _writeSnippet, promptGuidelines: _writeLines, ...write } = given.write;
|
|
53
|
-
const parts = {
|
|
54
|
-
read: piReadParts(given.read),
|
|
55
|
-
edit: piEditParts(given.edit),
|
|
56
|
-
write: piWriteParts(given.write),
|
|
57
|
-
};
|
|
58
|
-
let patch = null;
|
|
59
|
-
if (given.applyPatch !== null) {
|
|
60
|
-
const { signature: _patchSignature, promptSnippet: _patchSnippet, promptGuidelines: _patchLines, ...deps } = given.applyPatch;
|
|
61
|
-
patch = { deps, parts: piApplyPatchParts(given.applyPatch) };
|
|
62
|
-
}
|
|
63
|
-
const core = createFsTools({
|
|
64
|
-
fs,
|
|
65
|
-
digest: options.digest ?? nodeDigest(),
|
|
66
|
-
...(options.state === undefined ? {} : { state: options.state }),
|
|
67
|
-
...(options.locks === undefined ? {} : { locks: options.locks }),
|
|
68
|
-
...(options.clock === undefined ? {} : { clock: options.clock }),
|
|
69
|
-
read: { ...read, ...piReadDeps(parts.read) },
|
|
70
|
-
edit: { ...edit, messages: parts.edit.messages },
|
|
71
|
-
write: { ...write, messages: parts.write.messages },
|
|
72
|
-
...(patch === null ? {} : { applyPatch: { ...patch.deps, messages: patch.parts.messages } }),
|
|
73
|
-
});
|
|
1
|
+
import { fsToolDefinitions } from "@better-fs-tools/write/signature";
|
|
2
|
+
import { adaptPiBashTool } from "./bash-tool.js";
|
|
3
|
+
import { adaptPiMutationTool } from "./mutation-tools.js";
|
|
4
|
+
import { checkPiOptions, splitPrompt } from "./prompt.js";
|
|
5
|
+
import { adaptPiReadTool } from "./tool.js";
|
|
6
|
+
export function createPiFsTools(options) {
|
|
7
|
+
checkPiOptions(options, "createPiFsTools");
|
|
8
|
+
const read = splitPrompt(options.read ?? {});
|
|
9
|
+
const edit = splitPrompt(options.edit ?? {});
|
|
10
|
+
const write = splitPrompt(options.write ?? {});
|
|
11
|
+
const applyPatch = isObject(options.applyPatch) ? splitPrompt(options.applyPatch) : null;
|
|
12
|
+
const bash = isObject(options.bash) ? splitPrompt(options.bash) : null;
|
|
13
|
+
const defs = fsToolDefinitions({
|
|
14
|
+
...options,
|
|
15
|
+
...(options.read === undefined ? {} : { read: read.rest }),
|
|
16
|
+
...(options.edit === undefined ? {} : { edit: edit.rest }),
|
|
17
|
+
...(options.write === undefined ? {} : { write: write.rest }),
|
|
18
|
+
...(applyPatch === null ? {} : { applyPatch: applyPatch.rest }),
|
|
19
|
+
...(bash === null ? {} : { bash: bash.rest }),
|
|
20
|
+
}, "createPiFsTools");
|
|
74
21
|
return Object.freeze({
|
|
75
|
-
read: adaptPiReadTool(
|
|
76
|
-
edit: adaptPiMutationTool(
|
|
77
|
-
write: adaptPiMutationTool(
|
|
78
|
-
applyPatch:
|
|
22
|
+
read: adaptPiReadTool(defs.read, read.prompt),
|
|
23
|
+
edit: adaptPiMutationTool("edit", defs.edit, edit.prompt),
|
|
24
|
+
write: adaptPiMutationTool("write", defs.write, write.prompt),
|
|
25
|
+
applyPatch: defs.applyPatch === null
|
|
79
26
|
? null
|
|
80
|
-
: adaptPiMutationTool(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
27
|
+
: adaptPiMutationTool("apply_patch", defs.applyPatch, applyPatch?.prompt ?? {}),
|
|
28
|
+
bash: defs.bash === null ? null : adaptPiBashTool(defs.bash, bash?.prompt ?? {}),
|
|
29
|
+
state: defs.state,
|
|
30
|
+
digest: defs.digest,
|
|
31
|
+
locks: defs.locks,
|
|
32
|
+
clock: defs.clock,
|
|
33
|
+
invalidate: defs.invalidate,
|
|
86
34
|
});
|
|
87
35
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
return false;
|
|
91
|
-
const store = value;
|
|
92
|
-
return ["get", "put", "delete"].every((key) => typeof store[key] === "function");
|
|
36
|
+
function isObject(value) {
|
|
37
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
93
38
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
export { createPiBashTool } from "./bash-tool.ts";
|
|
2
|
-
export type {
|
|
2
|
+
export type { PiBashDetails, PiBashTool, PiBashToolOptions, PiBashToolResult, } from "./bash-tool.ts";
|
|
3
3
|
export { toPiReadDetails } from "./details.ts";
|
|
4
4
|
export type { PiReadDetails, PiTruncationResult } from "./details.ts";
|
|
5
5
|
export { createPiFsTools } from "./fs-tools.ts";
|
|
6
|
-
export type {
|
|
6
|
+
export type { PiFsTools, PiFsToolsApplyPatchOptions, PiFsToolsBashOptions, PiFsToolsEditOptions, PiFsToolsOptions, PiFsToolsReadOptions, PiFsToolsWithApplyPatch, PiFsToolsWithBash, PiFsToolsWriteOptions, } from "./fs-tools.ts";
|
|
7
|
+
export { piMutationQueueLocks } from "./locks.ts";
|
|
8
|
+
export type { PiMutationQueueLocksOptions } from "./locks.ts";
|
|
7
9
|
export { toPiMutationDetails } from "./mutation-details.ts";
|
|
8
10
|
export type { PiMutationDetails } from "./mutation-details.ts";
|
|
9
11
|
export { createPiApplyPatchTool, createPiEditTool, createPiWriteTool } from "./mutation-tools.ts";
|
|
10
|
-
export type {
|
|
12
|
+
export type { PiApplyPatchToolOptions, PiEditToolOptions, PiMutationTool, PiMutationToolResult, PiWriteToolOptions, } from "./mutation-tools.ts";
|
|
13
|
+
export type { PiPromptOptions } from "./prompt.ts";
|
|
14
|
+
export { piToolRenderers } from "./renderers.ts";
|
|
15
|
+
export type { PiToolAnnotations } from "./results.ts";
|
|
11
16
|
export { createPiReadTool } from "./tool.ts";
|
|
12
|
-
export type {
|
|
17
|
+
export type { PiContentPart, PiReadTool, PiReadToolOptions, PiReadToolResult } from "./tool.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { createPiBashTool } from "./bash-tool.js";
|
|
2
2
|
export { toPiReadDetails } from "./details.js";
|
|
3
3
|
export { createPiFsTools } from "./fs-tools.js";
|
|
4
|
+
export { piMutationQueueLocks } from "./locks.js";
|
|
4
5
|
export { toPiMutationDetails } from "./mutation-details.js";
|
|
5
6
|
export { createPiApplyPatchTool, createPiEditTool, createPiWriteTool } from "./mutation-tools.js";
|
|
7
|
+
export { piToolRenderers } from "./renderers.js";
|
|
6
8
|
export { createPiReadTool } from "./tool.js";
|
package/dist/locks.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LockManager } from "@better-fs-tools/write";
|
|
2
|
+
export interface PiMutationQueueLocksOptions {
|
|
3
|
+
/** How long acquire() waits for every key. Default 30 000. */
|
|
4
|
+
readonly timeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A lock manager on Pi's withFileMutationQueue(), the per-file queue of Pi's
|
|
8
|
+
* own edit and write. A Better FS Tools edit and a Pi tool that change the same
|
|
9
|
+
* file then wait for each other. The keys are the resolved paths that the
|
|
10
|
+
* write tools pass, which are the real paths that Pi's queue uses.
|
|
11
|
+
*
|
|
12
|
+
* A caller takes the keys one after another in sorted order, inside one queue
|
|
13
|
+
* slot each. On a timeout or an abort the outcome comes at once, and the slots
|
|
14
|
+
* that the caller got later are given back as soon as they arrive.
|
|
15
|
+
*/
|
|
16
|
+
export declare function piMutationQueueLocks(options?: PiMutationQueueLocksOptions): LockManager;
|
package/dist/locks.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
3
|
+
/**
|
|
4
|
+
* A lock manager on Pi's withFileMutationQueue(), the per-file queue of Pi's
|
|
5
|
+
* own edit and write. A Better FS Tools edit and a Pi tool that change the same
|
|
6
|
+
* file then wait for each other. The keys are the resolved paths that the
|
|
7
|
+
* write tools pass, which are the real paths that Pi's queue uses.
|
|
8
|
+
*
|
|
9
|
+
* A caller takes the keys one after another in sorted order, inside one queue
|
|
10
|
+
* slot each. On a timeout or an abort the outcome comes at once, and the slots
|
|
11
|
+
* that the caller got later are given back as soon as they arrive.
|
|
12
|
+
*/
|
|
13
|
+
export function piMutationQueueLocks(options = {}) {
|
|
14
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
15
|
+
throw new TypeError("piMutationQueueLocks options must be an object");
|
|
16
|
+
}
|
|
17
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
18
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
|
|
19
|
+
throw new TypeError("timeoutMs must be a positive safe integer");
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
id: "pi-file-mutation-queue",
|
|
23
|
+
acquire: (keys, { signal } = {}) => acquire(keys, signal, timeoutMs),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function acquire(keys, signal, timeoutMs) {
|
|
27
|
+
if (signal?.aborted)
|
|
28
|
+
return Promise.resolve({ ok: false, reason: "aborted" });
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
let settled = false;
|
|
31
|
+
const settle = (finish) => {
|
|
32
|
+
if (settled)
|
|
33
|
+
return false;
|
|
34
|
+
settled = true;
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
signal?.removeEventListener("abort", onAbort);
|
|
37
|
+
finish();
|
|
38
|
+
return true;
|
|
39
|
+
};
|
|
40
|
+
const timer = setTimeout(() => settle(() => resolve({ ok: false, reason: "timeout" })), timeoutMs);
|
|
41
|
+
const onAbort = () => settle(() => resolve({ ok: false, reason: "aborted" }));
|
|
42
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
43
|
+
// Each key's queue slot stays taken until the slot callback returns. The
|
|
44
|
+
// innermost callback holds every slot until release() resolves `held`.
|
|
45
|
+
let release = () => { };
|
|
46
|
+
const held = new Promise((done) => {
|
|
47
|
+
release = done;
|
|
48
|
+
});
|
|
49
|
+
const hold = (index) => {
|
|
50
|
+
if (index < keys.length) {
|
|
51
|
+
return withFileMutationQueue(keys[index], () => hold(index + 1));
|
|
52
|
+
}
|
|
53
|
+
let released = false;
|
|
54
|
+
const granted = settle(() => resolve({
|
|
55
|
+
ok: true,
|
|
56
|
+
release() {
|
|
57
|
+
if (released)
|
|
58
|
+
return;
|
|
59
|
+
released = true;
|
|
60
|
+
release();
|
|
61
|
+
},
|
|
62
|
+
}));
|
|
63
|
+
// After a timeout or an abort, give the slots back at once.
|
|
64
|
+
return granted ? held : Promise.resolve();
|
|
65
|
+
};
|
|
66
|
+
hold(0).catch((error) => settle(() => reject(error)));
|
|
67
|
+
});
|
|
68
|
+
}
|