pi-read-map 1.2.3 → 1.2.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.2.5] - 2026-02-15
6
+
7
+ ### Fixed
8
+
9
+ - `sendMessage` calls in `tool_result` handler now use `deliverAs: "followUp"` instead of the default `"steer"` mode. The default `"steer"` mode interrupts streaming and skips remaining parallel tools — when multiple reads ran concurrently and one triggered a file-map or directory-listing message, the remaining tool calls were skipped, leaving `tool_use` blocks without matching `tool_result` blocks. This caused a 400 error from the Claude API on the next request.
10
+
11
+ ## [1.2.4] - 2026-02-15
12
+
13
+ ### Fixed
14
+
15
+ - Binary/image files (`.jpg`, `.png`, `.gif`, `.webp`, etc.) no longer enter the map generation pipeline — they are delegated directly to the built-in read tool. Previously, a >50 KB image would pass the size threshold, run `wc -l` on binary data, and trigger ctags/grep fallback. When multiple image reads ran in parallel, the resulting `sendMessage` calls broke the Claude API's `tool_use`/`tool_result` pairing requirement.
16
+ - Fallback mapper now returns `null` when grep finds zero symbols instead of returning an empty `FileMap`. This prevents unnecessary `sendMessage` calls for unmappable files.
17
+
5
18
  ## [1.2.2] - 2026-02-14
6
19
 
7
20
  ### Changed
package/README.md CHANGED
@@ -157,9 +157,10 @@ tests/
157
157
 
158
158
  The extension intercepts `read` calls and decides:
159
159
 
160
- 1. **Small files** (≤2,000 lines, ≤50 KB): Delegate to built-in read tool
161
- 2. **Targeted reads** (offset or limit provided): Delegate to built-in read tool
162
- 3. **Large files:**
160
+ 1. **Binary files** (images, audio, video, archives, etc.): Delegate to built-in read tool
161
+ 2. **Small files** (≤2,000 lines, ≤50 KB): Delegate to built-in read tool
162
+ 3. **Targeted reads** (offset or limit provided): Delegate to built-in read tool
163
+ 4. **Large files:**
163
164
  - Call built-in read for the first chunk
164
165
  - Detect language from file extension
165
166
  - Dispatch to a mapper (language-specific → ctags → grep fallback)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-read-map",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Pi extension that adds structural file maps for large files",
5
5
  "type": "module",
6
6
  "pi": {
package/src/index.ts CHANGED
@@ -10,7 +10,7 @@ import { Text } from "@mariozechner/pi-tui";
10
10
  import { Type } from "@sinclair/typebox";
11
11
  import { exec } from "node:child_process";
12
12
  import { stat } from "node:fs/promises";
13
- import { basename, resolve } from "node:path";
13
+ import { basename, extname, resolve } from "node:path";
14
14
  import { promisify } from "node:util";
15
15
 
16
16
  import type { FileMapMessageDetails } from "./types.js";
@@ -22,6 +22,60 @@ export type { FileMapMessageDetails } from "./types.js";
22
22
 
23
23
  const execAsync = promisify(exec);
24
24
 
25
+ /**
26
+ * File extensions that are binary/image files and should be
27
+ * delegated directly to the built-in read tool without map generation.
28
+ */
29
+ const BINARY_EXTENSIONS = new Set([
30
+ ".jpg",
31
+ ".jpeg",
32
+ ".png",
33
+ ".gif",
34
+ ".webp",
35
+ ".bmp",
36
+ ".ico",
37
+ ".tiff",
38
+ ".tif",
39
+ ".svg",
40
+ ".avif",
41
+ ".heic",
42
+ ".heif",
43
+ // Audio/video
44
+ ".mp3",
45
+ ".mp4",
46
+ ".wav",
47
+ ".avi",
48
+ ".mov",
49
+ ".mkv",
50
+ ".flac",
51
+ ".ogg",
52
+ ".webm",
53
+ // Archives
54
+ ".zip",
55
+ ".tar",
56
+ ".gz",
57
+ ".bz2",
58
+ ".xz",
59
+ ".7z",
60
+ ".rar",
61
+ // Binary data
62
+ ".bin",
63
+ ".exe",
64
+ ".dll",
65
+ ".so",
66
+ ".dylib",
67
+ ".o",
68
+ ".a",
69
+ ".wasm",
70
+ ".pdf",
71
+ ".doc",
72
+ ".docx",
73
+ ".xls",
74
+ ".xlsx",
75
+ ".ppt",
76
+ ".pptx",
77
+ ]);
78
+
25
79
  // In-memory cache for maps
26
80
  const mapCache = new Map<string, { mtime: number; map: string }>();
27
81
 
@@ -82,11 +136,14 @@ export default function piReadMapExtension(pi: ExtensionAPI): void {
82
136
  // Send pending directory listing after read-on-directory error
83
137
  const pendingLs = pendingDirectoryLs.get(event.toolCallId);
84
138
  if (pendingLs) {
85
- pi.sendMessage({
86
- customType: "directory-listing",
87
- content: `${pendingLs.path} is a directory. Here is ls:\n${pendingLs.listing}`,
88
- display: true,
89
- });
139
+ pi.sendMessage(
140
+ {
141
+ customType: "directory-listing",
142
+ content: `${pendingLs.path} is a directory. Here is ls:\n${pendingLs.listing}`,
143
+ display: true,
144
+ },
145
+ { deliverAs: "followUp" }
146
+ );
90
147
  pendingDirectoryLs.delete(event.toolCallId);
91
148
  }
92
149
 
@@ -96,12 +153,15 @@ export default function piReadMapExtension(pi: ExtensionAPI): void {
96
153
  }
97
154
 
98
155
  // Send the map as a custom message
99
- pi.sendMessage({
100
- customType: "file-map",
101
- content: pending.map,
102
- display: true,
103
- details: pending.details,
104
- });
156
+ pi.sendMessage(
157
+ {
158
+ customType: "file-map",
159
+ content: pending.map,
160
+ display: true,
161
+ details: pending.details,
162
+ },
163
+ { deliverAs: "followUp" }
164
+ );
105
165
 
106
166
  // Clean up
107
167
  pendingMaps.delete(event.toolCallId);
@@ -257,6 +317,11 @@ export default function piReadMapExtension(pi: ExtensionAPI): void {
257
317
  // Resolve path
258
318
  const absPath = resolve(cwd, inputPath.replace(/^@/, ""));
259
319
 
320
+ // Skip binary/image files — delegate directly without map generation
321
+ if (BINARY_EXTENSIONS.has(extname(absPath).toLowerCase())) {
322
+ return builtInRead.execute(toolCallId, params, signal, onUpdate);
323
+ }
324
+
260
325
  // Check file size and line count
261
326
  let stats;
262
327
  try {
@@ -141,6 +141,11 @@ export async function fallbackMapper(
141
141
  };
142
142
  });
143
143
 
144
+ // No symbols found — nothing useful to map
145
+ if (symbols.length === 0) {
146
+ return null;
147
+ }
148
+
144
149
  // Get language info
145
150
  const langInfo = detectLanguage(filePath);
146
151