@better-fs-tools/pi 0.1.0 → 0.2.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @better-fs-tools/pi
2
2
 
3
- Better FS Tools for the [Pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent). It replaces Pi's built-in `read`, `edit`, and `write` tools, and adds `apply_patch`. Every call is confined to the working directory of that call, and `edit` and `write` need a read first.
3
+ Better FS Tools for the [Pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent). It replaces Pi's built-in `read`, `edit`, and `write` tools, and can add `apply_patch`. Every call is confined to the working directory of that call. A read store, so that `edit` and `write` need a read first, is opt-in.
4
4
 
5
5
  ## Install
6
6
 
@@ -10,7 +10,7 @@ npm install @better-fs-tools/pi @better-fs-tools/read @better-fs-tools/write @ea
10
10
 
11
11
  `@earendil-works/pi-coding-agent` (`^0.84.2`) and `typebox` (`^1.3.16`) are required peers. The package needs Node 24 or later.
12
12
 
13
- The package has a `pi.extensions` entry. When Pi loads the package, the entry registers `read`, `edit`, `write`, and `apply_patch` from `createPiFsTools()`. The four tools share one in-memory read store, so `edit` and `write` need a read first. Writes stay inside `ctx.cwd`. The entry writes no settings or session file.
13
+ The package has a `pi.extensions` entry. When Pi loads the package, the entry registers `read`, `edit`, and `write` from `createPiFsTools()`, as Pi's own tools are. `read` returns images, as Pi's own `read` does. There is no read store, so `edit` and `write` do not check for a read first. Writes stay inside `ctx.cwd`. The entry writes no settings or session file.
14
14
 
15
15
  ## Example
16
16
 
@@ -19,13 +19,15 @@ To choose your own options, register the tools from your own extension:
19
19
  ```ts
20
20
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
21
21
  import { createPiFsTools } from "@better-fs-tools/pi";
22
- import { denyPaths, unicodeRepair } from "@better-fs-tools/read";
22
+ import { denyPaths, memoryStore, unicodeRepair } from "@better-fs-tools/read";
23
23
  import { hashlineFormat } from "@better-fs-tools/read/formats";
24
24
  import { protectPaths } from "@better-fs-tools/write";
25
25
 
26
26
  export default function fsToolsExtension(pi: ExtensionAPI): void {
27
- // Four tools with one read store, so edit and write need a read first.
27
+ // Four tools with one read store, so edit and write need a read first. The
28
+ // store and apply_patch are opt-in.
28
29
  const tools = createPiFsTools({
30
+ state: memoryStore(),
29
31
  read: {
30
32
  resolve: unicodeRepair({ note: false }),
31
33
  authorize: denyPaths(["**/.env", "**/.env.*"]),
@@ -69,14 +71,15 @@ export default function readExtension(pi: ExtensionAPI): void {
69
71
  - `fs`, `cwd`, and `allowedRoots` throw `TypeError`. Each call reads through `nodeFileSystem` with Pi's `ctx.cwd` as the only allowed root. When the working directory changes between calls, the root changes too. The tool keeps the filesystems for the last 8 working directories.
70
72
  - Pi's `ExtensionContext` is the host type. Host functions get it as `ctx.call.host`, for example `ctx.call.host.ui.confirm(...)` in `askUser`, or `ctx.call.host.sessionManager.getSessionId()` in a `state` factory.
71
73
  - The default signature is `defaultReadSignature({ name: "read" })`. Its schema has integer `offset` and `limit`, so a model that sends `2.5` gets Pi's validation error. `digest` defaults to `nodeDigest()`.
74
+ - `converters` defaults to `[imageConverter()]`, as Pi's own `read` returns images. This holds for `createPiReadTool()`, `createPiFsTools()`, and the extension entry. A list you pass replaces the default, so `converters: []` turns images off, and a list without `imageConverter()` refuses images. In `createPiFsTools()` the option goes under `read`, for example `read: { converters: [] }`.
72
75
  - Image parts become Pi `image` parts. Other media becomes a text part that says what was left out.
73
76
  - `details.truncation` follows Pi's own read tool for line and byte stops. Its `content` comes from the formatter in `"view"` mode. For other results, or a formatter that returns parts, `details` is `{}`. `toPiReadDetails()` builds the details for your own tool.
74
77
 
75
78
  ## What the write tools do
76
79
 
77
- - `createPiFsTools(options?)` wraps `createFsTools()` from `@better-fs-tools/write`. It builds `read`, `edit`, `write`, and `apply_patch` with one `memoryStore({ clock })`, one `nodeDigest()`, one lock manager, one clock, and one cache of filesystems over `ctx.cwd`. `state: null` turns read-before-write off. `digest` (default `nodeDigest()`), `locks`, `clock`, and the root options (`denyRoots`, `symlinks`, `hardLinks`, `newFileMode`, `newDirectoryMode`) are set once for all four. Each tool's other options go under `read`, `edit`, `write`, and `applyPatch`. The result also has `state`, `digest`, `locks`, `clock`, and `invalidate(path, call?)`, which returns an `InvalidateOutcome`. Pass the call, so the path resolves under its `ctx.cwd`: in a bash `afterRun` hook it is `ctx.call`. Without it the outcome is `{ ok: false }` with reason `unsupported`. An unknown top-level key, or `state`, `digest`, `locks`, `clock`, or a root option inside a tool's options, throws `TypeError`.
78
- - `createPiEditTool()`, `createPiWriteTool()`, and `createPiApplyPatchTool()` build one tool each. They default to `state: null`, so every update carries a `read-before-write-off` note. Pass the same `state` as your read tool, or use `createPiFsTools()`.
79
- - `fs`, `cwd`, and `allowedRoots` throw `TypeError`, as for the read tool. Writes go through `nodeFileSystem` with `ctx.cwd` as the only allowed root, and `hardLinks: "refuse"` by default. `newFileMode` and `newDirectoryMode` set the modes of new files and directories exactly. Without them, a new file is `0o666` and a new directory `0o777`, less the process umask.
80
+ - `createPiFsTools(options?)` wraps `createFsTools()` from `@better-fs-tools/write`. It builds `read`, `edit`, and `write` with one `nodeDigest()`, one lock manager, one clock, and one cache of filesystems over `ctx.cwd`. `applyPatch: true`, or an options object, adds `apply_patch`. `state` defaults to `null`, so read-before-write is off; `state: memoryStore()` turns it on. `digest` (default `nodeDigest()`), `locks`, `clock`, and the root options (`denyRoots`, `symlinks`, `hardLinks`, `newFileMode`, `newDirectoryMode`) are set once for all the tools. Each tool's other options go under `read`, `edit`, `write`, and `applyPatch`. The result also has `applyPatch` (`null` when off), `state`, `digest`, `locks`, `clock`, and `invalidate(path, call?)`, which returns an `InvalidateOutcome`. Pass the call, so the path resolves under its `ctx.cwd`: in a bash `afterRun` hook it is `ctx.call`. Without it the outcome is `{ ok: false }` with reason `unsupported`. An unknown top-level key, or `state`, `digest`, `locks`, `clock`, or a root option inside a tool's options, throws `TypeError`.
81
+ - `createPiEditTool()`, `createPiWriteTool()`, and `createPiApplyPatchTool()` build one tool each. They default to `state: null`, so read-before-write is off. Pass the same `state` as your read tool, or use `createPiFsTools()`.
82
+ - `fs`, `cwd`, and `allowedRoots` throw `TypeError`, as for the read tool. Writes go through `nodeFileSystem` with `ctx.cwd` as the only allowed root, and `hardLinks: "in-place"` by default: a file with more than one hard link is written through the link, not atomically. `hardLinks: "refuse"` fails that write with `DENIED`. `newFileMode` and `newDirectoryMode` set the modes of new files and directories exactly. Without them, a new file is `0o666` and a new directory `0o777`, less the process umask.
80
83
  - The default signatures follow Pi's own tools: `edit` takes `path` and `edits: [{ oldText, newText }]` (`multiEditSignature({ name: "edit" })`), and `write` takes `path` and `content`. Their prompt snippets and guidelines are Pi's own, so the system prompt stays the same. `apply_patch` uses `freeformPatchSignature()`: models with grammar-tool support get the Codex patch grammar, and other models get the JSON schema.
81
84
  - `edit` and `apply_patch` return `details` in the shape of Pi's `EditToolDetails` (`diff`, `patch`, `firstChangedLine`), so Pi's edit renderer draws the diff. `toPiMutationDetails()` builds them for your own tool. `write` returns no details, as Pi's own `write` does.
82
85
  - A tool error becomes Pi content text. The tool does not throw for a tool error.
@@ -105,11 +108,13 @@ With the file tools, a bash `afterRun` hook can make the next edit of a file nee
105
108
  ```ts
106
109
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
107
110
  import { createPiBashTool, createPiFsTools } from "@better-fs-tools/pi";
111
+ import { memoryStore } from "@better-fs-tools/read";
108
112
 
109
- // The four file tools and Pi-shaped bash. After each command, the next edit of
110
- // package.json needs a read, since the command may have changed it.
113
+ // The file tools with a read store, and Pi-shaped bash. After each command,
114
+ // the next edit of package.json needs a read, since the command may have
115
+ // changed it.
111
116
  export default function fsAndBashExtension(pi: ExtensionAPI): void {
112
- const tools = createPiFsTools();
117
+ const tools = createPiFsTools({ state: memoryStore() });
113
118
  const bash = createPiBashTool({
114
119
  afterRun: [
115
120
  {
@@ -125,7 +130,6 @@ export default function fsAndBashExtension(pi: ExtensionAPI): void {
125
130
  pi.registerTool(tools.read);
126
131
  pi.registerTool(tools.edit);
127
132
  pi.registerTool(tools.write);
128
- pi.registerTool(tools.applyPatch);
129
133
  pi.registerTool(bash);
130
134
  }
131
135
  ```
package/dist/extension.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Pi package extension entry. Installing the package into a Pi project
3
- * registers read, edit, write, and apply_patch, and nothing else. The four
4
- * tools share one in-memory read store, so edit and write need a read first.
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.
5
6
  * Nothing is written outside the process: no settings file, no session file.
6
7
  */
7
8
  import { createPiFsTools } from "./fs-tools.js";
@@ -10,5 +11,4 @@ export default function fsToolsExtension(pi) {
10
11
  pi.registerTool(tools.read);
11
12
  pi.registerTool(tools.edit);
12
13
  pi.registerTool(tools.write);
13
- pi.registerTool(tools.applyPatch);
14
14
  }
@@ -6,7 +6,7 @@ import type { PiRootOptions } from "./roots.ts";
6
6
  import type { CreatePiReadToolOptions, PiReadTool } from "./tool.ts";
7
7
  type BundleSharedKey = FsToolsSharedKey | keyof PiRootOptions;
8
8
  export interface CreatePiFsToolsOptions extends PiRootOptions {
9
- /** Default a memory store on the bundle clock. null turns read-before-write off. */
9
+ /** Default null: no read-before-write. A store, for example memoryStore(), turns it on. */
10
10
  readonly state?: ReadStateStore | null;
11
11
  /** Default nodeDigest(). */
12
12
  readonly digest?: Digest;
@@ -17,13 +17,15 @@ export interface CreatePiFsToolsOptions extends PiRootOptions {
17
17
  readonly read?: Omit<CreatePiReadToolOptions, BundleSharedKey>;
18
18
  readonly edit?: Omit<CreatePiEditToolOptions, BundleSharedKey>;
19
19
  readonly write?: Omit<CreatePiWriteToolOptions, BundleSharedKey>;
20
- readonly applyPatch?: Omit<CreatePiApplyPatchToolOptions, BundleSharedKey>;
20
+ /** Off by default. `true` or an options object adds an apply_patch tool. */
21
+ readonly applyPatch?: boolean | Omit<CreatePiApplyPatchToolOptions, BundleSharedKey>;
21
22
  }
22
23
  export interface PiFsTools {
23
24
  readonly read: PiReadTool;
24
25
  readonly edit: PiMutationTool;
25
26
  readonly write: PiMutationTool;
26
- readonly applyPatch: PiMutationTool;
27
+ /** null unless options.applyPatch is true or an object. */
28
+ readonly applyPatch: PiMutationTool | null;
27
29
  readonly state: ReadStateStore | null;
28
30
  /** options.digest, or nodeDigest(). */
29
31
  readonly digest: Digest;
@@ -37,13 +39,21 @@ export interface PiFsTools {
37
39
  */
38
40
  invalidate(path: string, call?: ToolCallContext<ExtensionContext>): Promise<InvalidateOutcome>;
39
41
  }
42
+ /** The createPiFsTools result when `applyPatch` is true or an object: it is never null. */
43
+ export interface PiFsToolsWithApplyPatch extends PiFsTools {
44
+ readonly applyPatch: PiMutationTool;
45
+ }
40
46
  /**
41
- * Creates Pi read, edit, write, and apply_patch tools rooted at each call's
42
- * ctx.cwd. They share one read state store, digest, lock manager, and clock.
47
+ * Creates Pi read, edit, and write tools rooted at each call's ctx.cwd, and
48
+ * an apply_patch tool when `applyPatch` is true or an object. They share one
49
+ * digest, lock manager, and clock, and the read store when `state` is given.
43
50
  * Throws TypeError on fs, cwd, or allowedRoots anywhere in the options, on an
44
51
  * unknown key, and on a shared option (state, digest, locks, clock, or a root
45
52
  * option) inside one tool's options. There is no bash: register
46
53
  * createPiBashTool() and call invalidate from its afterRun hook.
47
54
  */
55
+ export declare function createPiFsTools(options: CreatePiFsToolsOptions & {
56
+ readonly applyPatch: true | Omit<CreatePiApplyPatchToolOptions, BundleSharedKey>;
57
+ }): PiFsToolsWithApplyPatch;
48
58
  export declare function createPiFsTools(options?: CreatePiFsToolsOptions): PiFsTools;
49
59
  export {};
package/dist/fs-tools.js CHANGED
@@ -15,14 +15,6 @@ const KNOWN_OPTIONS = new Set([
15
15
  "applyPatch",
16
16
  ]);
17
17
  const BUNDLE_SHARED_KEYS = ["state", "digest", "locks", "clock", ...PI_ROOT_KEYS];
18
- /**
19
- * Creates Pi read, edit, write, and apply_patch tools rooted at each call's
20
- * ctx.cwd. They share one read state store, digest, lock manager, and clock.
21
- * Throws TypeError on fs, cwd, or allowedRoots anywhere in the options, on an
22
- * unknown key, and on a shared option (state, digest, locks, clock, or a root
23
- * option) inside one tool's options. There is no bash: register
24
- * createPiBashTool() and call invalidate from its afterRun hook.
25
- */
26
18
  export function createPiFsTools(options = {}) {
27
19
  checkPiOptions(options, "fs");
28
20
  for (const key of Object.keys(options)) {
@@ -37,9 +29,15 @@ export function createPiFsTools(options = {}) {
37
29
  read: options.read ?? {},
38
30
  edit: options.edit ?? {},
39
31
  write: options.write ?? {},
40
- applyPatch: options.applyPatch ?? {},
32
+ applyPatch: options.applyPatch === true
33
+ ? {}
34
+ : options.applyPatch === false || options.applyPatch === undefined
35
+ ? null
36
+ : options.applyPatch,
41
37
  };
42
38
  for (const [key, value] of Object.entries(given)) {
39
+ if (value === null)
40
+ continue;
43
41
  checkPiOptions(value, key);
44
42
  for (const shared of BUNDLE_SHARED_KEYS) {
45
43
  if (Object.hasOwn(value, shared)) {
@@ -52,13 +50,16 @@ export function createPiFsTools(options = {}) {
52
50
  const { signature: _readSignature, promptSnippet: _readSnippet, promptGuidelines: _readLines, ...read } = given.read;
53
51
  const { signature: _editSignature, promptSnippet: _editSnippet, promptGuidelines: _editLines, ...edit } = given.edit;
54
52
  const { signature: _writeSignature, promptSnippet: _writeSnippet, promptGuidelines: _writeLines, ...write } = given.write;
55
- const { signature: _patchSignature, promptSnippet: _patchSnippet, promptGuidelines: _patchLines, ...applyPatch } = given.applyPatch;
56
53
  const parts = {
57
54
  read: piReadParts(given.read),
58
55
  edit: piEditParts(given.edit),
59
56
  write: piWriteParts(given.write),
60
- applyPatch: piApplyPatchParts(given.applyPatch),
61
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
+ }
62
63
  const core = createFsTools({
63
64
  fs,
64
65
  digest: options.digest ?? nodeDigest(),
@@ -68,13 +69,15 @@ export function createPiFsTools(options = {}) {
68
69
  read: { ...read, ...piReadDeps(parts.read) },
69
70
  edit: { ...edit, messages: parts.edit.messages },
70
71
  write: { ...write, messages: parts.write.messages },
71
- applyPatch: { ...applyPatch, messages: parts.applyPatch.messages },
72
+ ...(patch === null ? {} : { applyPatch: { ...patch.deps, messages: patch.parts.messages } }),
72
73
  });
73
74
  return Object.freeze({
74
75
  read: adaptPiReadTool(parts.read, core.read, core.digest),
75
76
  edit: adaptPiMutationTool(parts.edit, core.edit),
76
77
  write: adaptPiMutationTool(parts.write, core.write),
77
- applyPatch: adaptPiMutationTool(parts.applyPatch, core.applyPatch),
78
+ applyPatch: patch === null || core.applyPatch === null
79
+ ? null
80
+ : adaptPiMutationTool(patch.parts, core.applyPatch),
78
81
  state: core.state,
79
82
  digest: core.digest,
80
83
  locks: core.locks,
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { CreatePiBashToolOptions, PiBashDetails, PiBashTool, PiBashToolResu
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 { CreatePiFsToolsOptions, PiFsTools } from "./fs-tools.ts";
6
+ export type { CreatePiFsToolsOptions, PiFsTools, PiFsToolsWithApplyPatch } from "./fs-tools.ts";
7
7
  export { toPiMutationDetails } from "./mutation-details.ts";
8
8
  export type { PiMutationDetails } from "./mutation-details.ts";
9
9
  export { createPiApplyPatchTool, createPiEditTool, createPiWriteTool } from "./mutation-tools.ts";
@@ -61,7 +61,7 @@ export declare function piApplyPatchParts(options: PartsInput<PatchSignature>):
61
61
  /**
62
62
  * Creates a Pi edit tool rooted at each call's ctx.cwd. Throws TypeError when
63
63
  * options set fs, cwd, or allowedRoots. Read-before-write is off unless you
64
- * pass a `state`; use createPiFsTools() for a store shared with read.
64
+ * pass a `state`; use createPiFsTools({ state }) for a store shared with read.
65
65
  */
66
66
  export declare function createPiEditTool(options?: CreatePiEditToolOptions): PiMutationTool;
67
67
  /** Creates a Pi write tool, as createPiEditTool. `details` is always undefined, as for Pi's own write. */
@@ -43,7 +43,7 @@ function parts(tool, signature, options, snippet, guidelines) {
43
43
  /**
44
44
  * Creates a Pi edit tool rooted at each call's ctx.cwd. Throws TypeError when
45
45
  * options set fs, cwd, or allowedRoots. Read-before-write is off unless you
46
- * pass a `state`; use createPiFsTools() for a store shared with read.
46
+ * pass a `state`; use createPiFsTools({ state }) for a store shared with read.
47
47
  */
48
48
  export function createPiEditTool(options = {}) {
49
49
  checkPiOptions(options, "edit");
package/dist/roots.d.ts CHANGED
@@ -6,7 +6,7 @@ export interface PiRootOptions {
6
6
  /** Added to /dev, /proc, /sys. */
7
7
  readonly denyRoots?: readonly string[];
8
8
  readonly symlinks?: NodeFileSystemOptions["symlinks"];
9
- /** How to replace a file with more than one hard link. Default "refuse". */
9
+ /** How to replace a file with more than one hard link. Default "in-place". "refuse" fails with DENIED. */
10
10
  readonly hardLinks?: "refuse" | "in-place";
11
11
  /** Mode of a new file, exactly. Default 0o666 less the process umask. */
12
12
  readonly newFileMode?: number;
package/dist/tool.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import type { TSchema } from "typebox";
3
3
  import type { NodeFileSystemOptions } from "@better-fs-tools/node";
4
- import type { Digest, JsonObject, ReadFormatter, ReadLimits, ReadMessageCatalog, ReadTool, ReadToolDeps, StateNeedsDigestOrDefault } from "@better-fs-tools/read";
4
+ import type { Converter, Digest, JsonObject, ReadFormatter, ReadLimits, ReadMessageCatalog, ReadTool, ReadToolDeps, StateNeedsDigestOrDefault } from "@better-fs-tools/read";
5
5
  import type { ReadSignature } from "@better-fs-tools/read/signature";
6
6
  import type { PiReadDetails } from "./details.ts";
7
7
  import type { PiContentPart } from "./parts.ts";
@@ -17,7 +17,8 @@ export interface PiReadOptions {
17
17
  }
18
18
  /**
19
19
  * Options for createPiReadTool. digest defaults to nodeDigest(); a state needs
20
- * a digest that is not null.
20
+ * a digest that is not null. converters defaults to [imageConverter()], as
21
+ * Pi's own read shows images. A list you pass replaces it: [] turns images off.
21
22
  */
22
23
  export type CreatePiReadToolOptions = Omit<ReadToolDeps<ExtensionContext>, "fs"> & StateNeedsDigestOrDefault & PiReadOptions & {
23
24
  /** Added to /dev, /proc, /sys. */
@@ -42,7 +43,8 @@ export interface PiReadTool {
42
43
  /**
43
44
  * Creates a Pi read tool rooted at each call's ctx.cwd. Throws TypeError when
44
45
  * options set fs, cwd, or allowedRoots. It keeps no read state by default and
45
- * writes nothing outside the process.
46
+ * writes nothing outside the process. An image becomes a Pi image part unless
47
+ * options.converters leaves imageConverter() out.
46
48
  */
47
49
  export declare function createPiReadTool(options?: CreatePiReadToolOptions): PiReadTool;
48
50
  export interface PiReadParts {
@@ -52,11 +54,13 @@ export interface PiReadParts {
52
54
  readonly limits: Readonly<ReadLimits>;
53
55
  readonly formatter: ReadFormatter<ExtensionContext>;
54
56
  readonly messages: Partial<ReadMessageCatalog>;
57
+ readonly converters: readonly Converter<ExtensionContext>[];
55
58
  }
56
- export declare function piReadParts(options: PiReadOptions & Pick<Partial<ReadToolDeps<ExtensionContext>>, "limits" | "messages" | "formatter">): PiReadParts;
59
+ export declare function piReadParts(options: PiReadOptions & Pick<Partial<ReadToolDeps<ExtensionContext>>, "limits" | "messages" | "formatter" | "converters">): PiReadParts;
57
60
  export declare function piReadDeps(parts: PiReadParts): {
58
61
  limits: Readonly<ReadLimits>;
59
62
  formatter: ReadFormatter<ExtensionContext>;
60
63
  messages: Partial<ReadMessageCatalog>;
64
+ converters: readonly Converter<ExtensionContext>[];
61
65
  };
62
66
  export declare function adaptPiReadTool(parts: PiReadParts, read: ReadTool<ExtensionContext>, digest: Digest | null): PiReadTool;
package/dist/tool.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Type } from "typebox";
2
2
  import { nodeDigest } from "@better-fs-tools/node";
3
- import { createReadTool, lineNumberFormatter, resolveReadLimits } from "@better-fs-tools/read";
3
+ import { createReadTool, imageConverter, lineNumberFormatter, resolveReadLimits, } from "@better-fs-tools/read";
4
4
  import { defaultReadSignature, readSignatureMessages } from "@better-fs-tools/read/signature";
5
5
  import { toPiReadDetails } from "./details.js";
6
6
  import { toPiPart } from "./parts.js";
@@ -12,7 +12,8 @@ const PI_BUILTIN_READ_GUIDELINES = [
12
12
  /**
13
13
  * Creates a Pi read tool rooted at each call's ctx.cwd. Throws TypeError when
14
14
  * options set fs, cwd, or allowedRoots. It keeps no read state by default and
15
- * writes nothing outside the process.
15
+ * writes nothing outside the process. An image becomes a Pi image part unless
16
+ * options.converters leaves imageConverter() out.
16
17
  */
17
18
  export function createPiReadTool(options = {}) {
18
19
  checkPiOptions(options, "read");
@@ -37,10 +38,17 @@ export function piReadParts(options) {
37
38
  limits: resolveReadLimits(options.limits),
38
39
  formatter: options.formatter ?? lineNumberFormatter(),
39
40
  messages: { ...readSignatureMessages(signature), ...options.messages },
41
+ // Pi's own read returns images, so the Pi host turns them on.
42
+ converters: options.converters ?? [imageConverter()],
40
43
  };
41
44
  }
42
45
  export function piReadDeps(parts) {
43
- return { limits: parts.limits, formatter: parts.formatter, messages: parts.messages };
46
+ return {
47
+ limits: parts.limits,
48
+ formatter: parts.formatter,
49
+ messages: parts.messages,
50
+ converters: parts.converters,
51
+ };
44
52
  }
45
53
  export function adaptPiReadTool(parts, read, digest) {
46
54
  const { signature, promptSnippet, promptGuidelines, limits, formatter } = parts;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-fs-tools/pi",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The Better FS Tools read, edit, write, apply_patch, and bash tools and extension for the Pi coding agent.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,13 +42,13 @@
42
42
  }
43
43
  },
44
44
  "dependencies": {
45
- "@better-fs-tools/node": "^0.1.0",
46
- "@better-fs-tools/read": "^0.1.0",
47
- "@better-fs-tools/shell": "^0.1.0",
48
- "@better-fs-tools/write": "^0.1.0"
45
+ "@better-fs-tools/node": "^0.2.0",
46
+ "@better-fs-tools/read": "^0.2.0",
47
+ "@better-fs-tools/shell": "^0.2.0",
48
+ "@better-fs-tools/write": "^0.2.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@better-fs-tools/fs": "^0.1.0",
51
+ "@better-fs-tools/fs": "^0.2.0",
52
52
  "@earendil-works/pi-coding-agent": "^0.84.2",
53
53
  "typebox": "^1.3.16"
54
54
  },