@maheidem/pi-auto-image-attach 0.1.0 → 0.1.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/index.ts +47 -22
- package/package.json +5 -3
package/index.ts
CHANGED
|
@@ -29,27 +29,31 @@ const IMAGE_EXTENSIONS = new Set([
|
|
|
29
29
|
".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp",
|
|
30
30
|
]);
|
|
31
31
|
|
|
32
|
+
/** Images above this size are skipped (protects context/cost). */
|
|
33
|
+
const MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
34
|
+
|
|
32
35
|
function isImagePath(filePath: string): boolean {
|
|
33
36
|
return IMAGE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
async function detectImageMimeType(filePath: string): Promise<string | null> {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
const fd = await fs.promises.open(filePath, "r");
|
|
41
|
+
const buffer = Buffer.alloc(4100);
|
|
42
|
+
const { bytesRead } = await fd.read(buffer, 0, 4100, 0);
|
|
43
|
+
await fd.close();
|
|
44
|
+
const s = buffer.subarray(0, bytesRead);
|
|
45
|
+
if (s[0] === 0x89 && s[1] === 0x50 && s[2] === 0x4e && s[3] === 0x47 &&
|
|
46
|
+
s[4] === 0x0d && s[5] === 0x0a && s[6] === 0x1a && s[7] === 0x0a) return "image/png";
|
|
47
|
+
if (s[0] === 0xff && s[1] === 0xd8 && s[2] === 0xff) return "image/jpeg";
|
|
48
|
+
if (s[0] === 0x47 && s[1] === 0x49 && s[2] === 0x46 && s[3] === 0x38) return "image/gif";
|
|
49
|
+
if (s[0] === 0x52 && s[1] === 0x49 && s[2] === 0x46 && s[3] === 0x46 &&
|
|
50
|
+
s[8] === 0x57 && s[9] === 0x45 && s[10] === 0x42 && s[11] === 0x50) return "image/webp";
|
|
51
|
+
if (s[0] === 0x42 && s[1] === 0x4d) return "image/bmp";
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function skip(filePath: string, reason: string): void {
|
|
56
|
+
console.error(`[auto-image-attach] skipped ${filePath}: ${reason}`);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
interface ImagePathEntry {
|
|
@@ -69,10 +73,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
69
73
|
const extRegex = /\.(png|jpe?g|webp|gif|bmp)($|["'\s,;:])/gi;
|
|
70
74
|
let m;
|
|
71
75
|
while ((m = extRegex.exec(text)) !== null) {
|
|
72
|
-
|
|
76
|
+
// m[0] may include a trailing delimiter (space/quote/punct); exclude it from the path
|
|
77
|
+
const end = m.index + m[0].length - (m[2] ? m[2].length : 0);
|
|
73
78
|
// Scan back to unescaped space or beginning
|
|
74
79
|
let start = end - 1;
|
|
75
80
|
while (start > 0 && !(text[start] === " " && text[start - 1] !== "\\")) start--;
|
|
81
|
+
if (text[start] === " " && text[start - 1] !== "\\") start++; // drop the delimiter space
|
|
76
82
|
const raw = text.substring(start, end);
|
|
77
83
|
const clean = raw.replace(/\\/g, "");
|
|
78
84
|
if ((clean.startsWith("/") || clean.startsWith("./") || clean.startsWith("../"))
|
|
@@ -85,20 +91,39 @@ export default function (pi: ExtensionAPI) {
|
|
|
85
91
|
|
|
86
92
|
// Read and encode images
|
|
87
93
|
const images: ImageContent[] = [];
|
|
88
|
-
|
|
94
|
+
const attached: string[] = []; // rawPaths of successfully attached entries
|
|
95
|
+
for (const { rawPath, cleanPath } of entries) {
|
|
89
96
|
try {
|
|
97
|
+
let size = 0;
|
|
98
|
+
try {
|
|
99
|
+
size = fs.statSync(cleanPath).size;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
skip(cleanPath, `stat failed (${(err as Error).message})`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (size > MAX_IMAGE_BYTES) {
|
|
105
|
+
// Oversize: do not attach; leave the path in the text (user may need it)
|
|
106
|
+
console.error(`[auto-image-attach] skipped ${cleanPath}: ${size} exceeds 10MB cap`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
90
109
|
const mimeType = await detectImageMimeType(cleanPath);
|
|
91
|
-
if (!mimeType)
|
|
110
|
+
if (!mimeType) {
|
|
111
|
+
skip(cleanPath, "unrecognized image format");
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
92
114
|
const data = fs.readFileSync(cleanPath).toString("base64");
|
|
93
115
|
images.push({ type: "image", data, mimeType });
|
|
94
|
-
|
|
116
|
+
attached.push(rawPath);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
skip(cleanPath, `read failed (${(err as Error).message})`);
|
|
119
|
+
}
|
|
95
120
|
}
|
|
96
121
|
|
|
97
122
|
if (images.length === 0) return { action: "continue" };
|
|
98
123
|
|
|
99
|
-
// Remove image paths from text
|
|
124
|
+
// Remove attached image paths from text (skipped entries keep their path)
|
|
100
125
|
let cleaned = text;
|
|
101
|
-
for (const
|
|
126
|
+
for (const rawPath of attached) {
|
|
102
127
|
const escaped = rawPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
103
128
|
cleaned = cleaned.replace(new RegExp(escaped, "g"), "");
|
|
104
129
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maheidem/pi-auto-image-attach",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pi extension: auto-attaches pasted/dragged image file paths as ImageContent blocks so the model actually sees the image.",
|
|
6
6
|
"keywords": [
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"LICENSE"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"typecheck": "tsc -p tsconfig.json"
|
|
17
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
18
|
+
"test": "node --test *.test.ts",
|
|
19
|
+
"prepack": "npm run typecheck && npm test"
|
|
18
20
|
},
|
|
19
21
|
"peerDependencies": {
|
|
20
22
|
"@earendil-works/pi-coding-agent": ">=0.84.0"
|
|
@@ -43,4 +45,4 @@
|
|
|
43
45
|
"bugs": {
|
|
44
46
|
"url": "https://github.com/Maheidem/pi-coder-management/issues"
|
|
45
47
|
}
|
|
46
|
-
}
|
|
48
|
+
}
|