pi-paster 0.1.5 → 0.2.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/README.md +10 -6
- package/dist/index.d.mts +75 -3
- package/dist/index.mjs +546 -48
- package/package.json +5 -2
- package/src/clipboard.ts +73 -3
- package/src/config.ts +14 -0
- package/src/editor.ts +107 -19
- package/src/image-utils.ts +200 -11
- package/src/index.ts +27 -10
- package/src/optimize-image.ts +209 -0
- package/src/preview.ts +54 -5
- package/src/terminal-input.ts +2 -6
- package/src/types.ts +19 -1
package/dist/index.mjs
CHANGED
|
@@ -3,22 +3,233 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { existsSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import { isAbsolute, join, resolve } from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { platform } from "node:process";
|
|
7
|
+
import { Box, Container, Image, Spacer, Text, getCellDimensions, getImageDimensions, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
7
8
|
import { CustomEditor } from "@earendil-works/pi-coding-agent";
|
|
8
9
|
//#region src/types.ts
|
|
9
10
|
const EXTENSION_NAME = "paster";
|
|
10
|
-
const MAX_IMAGE_BYTES =
|
|
11
|
+
const MAX_IMAGE_BYTES = 64 * 1024 * 1024;
|
|
12
|
+
const ANTHROPIC_MAX_DIMENSION = 8e3;
|
|
13
|
+
const ANTHROPIC_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/optimize-image.ts
|
|
16
|
+
let _sharp;
|
|
17
|
+
async function getSharp() {
|
|
18
|
+
if (_sharp !== void 0) return _sharp;
|
|
19
|
+
try {
|
|
20
|
+
const mod = await import("sharp");
|
|
21
|
+
const fn = typeof mod === "function" ? mod : mod?.default;
|
|
22
|
+
_sharp = typeof fn === "function" ? fn : null;
|
|
23
|
+
} catch {
|
|
24
|
+
_sharp = null;
|
|
25
|
+
}
|
|
26
|
+
return _sharp;
|
|
27
|
+
}
|
|
28
|
+
const SHRINK_LADDER = [
|
|
29
|
+
6e3,
|
|
30
|
+
4e3,
|
|
31
|
+
3e3,
|
|
32
|
+
2e3
|
|
33
|
+
];
|
|
34
|
+
const JPEG_QUALITY_LADDER = [
|
|
35
|
+
95,
|
|
36
|
+
90,
|
|
37
|
+
85,
|
|
38
|
+
80,
|
|
39
|
+
70,
|
|
40
|
+
60
|
|
41
|
+
];
|
|
42
|
+
async function optimizeImageBytes(input, mime) {
|
|
43
|
+
const originalBytes = input.length;
|
|
44
|
+
const noop = () => ({
|
|
45
|
+
data: input.toString("base64"),
|
|
46
|
+
mimeType: mime,
|
|
47
|
+
originalBytes,
|
|
48
|
+
finalBytes: originalBytes,
|
|
49
|
+
actions: [],
|
|
50
|
+
changed: false
|
|
51
|
+
});
|
|
52
|
+
if (originalBytes <= 5242880) {
|
|
53
|
+
if (originalBytes <= 256 * 1024) return noop();
|
|
54
|
+
}
|
|
55
|
+
const sharp = await getSharp();
|
|
56
|
+
if (!sharp) return noop();
|
|
57
|
+
const meta = await sharp(input).metadata();
|
|
58
|
+
const origW = meta.width ?? 0;
|
|
59
|
+
const origH = meta.height ?? 0;
|
|
60
|
+
if (!origW || !origH) return noop();
|
|
61
|
+
const actions = [];
|
|
62
|
+
const originalDim = {
|
|
63
|
+
width: origW,
|
|
64
|
+
height: origH
|
|
65
|
+
};
|
|
66
|
+
let workBuf = input;
|
|
67
|
+
let workW = origW;
|
|
68
|
+
let workH = origH;
|
|
69
|
+
if (workW > 8e3 || workH > 8e3) {
|
|
70
|
+
workBuf = await sharp(workBuf).resize({
|
|
71
|
+
width: workW >= workH ? ANTHROPIC_MAX_DIMENSION : void 0,
|
|
72
|
+
height: workH > workW ? ANTHROPIC_MAX_DIMENSION : void 0,
|
|
73
|
+
fit: "inside",
|
|
74
|
+
withoutEnlargement: true
|
|
75
|
+
}).toBuffer();
|
|
76
|
+
const m = await sharp(workBuf).metadata();
|
|
77
|
+
workW = m.width ?? workW;
|
|
78
|
+
workH = m.height ?? workH;
|
|
79
|
+
actions.push(`resize to ${workW}x${workH} (8000px cap)`);
|
|
80
|
+
}
|
|
81
|
+
if (workBuf.length <= 5242880 && actions.length === 0) return noop();
|
|
82
|
+
if (workBuf.length <= 5242880) return {
|
|
83
|
+
data: workBuf.toString("base64"),
|
|
84
|
+
mimeType: mime,
|
|
85
|
+
originalBytes,
|
|
86
|
+
finalBytes: workBuf.length,
|
|
87
|
+
originalDim,
|
|
88
|
+
finalDim: {
|
|
89
|
+
width: workW,
|
|
90
|
+
height: workH
|
|
91
|
+
},
|
|
92
|
+
actions,
|
|
93
|
+
changed: true
|
|
94
|
+
};
|
|
95
|
+
let outMime = "image/jpeg";
|
|
96
|
+
let attempt = workBuf;
|
|
97
|
+
for (const q of JPEG_QUALITY_LADDER) {
|
|
98
|
+
attempt = await sharp(workBuf).jpeg({
|
|
99
|
+
quality: q,
|
|
100
|
+
mozjpeg: true
|
|
101
|
+
}).toBuffer();
|
|
102
|
+
if (attempt.length <= 5242880) {
|
|
103
|
+
actions.push(`jpeg q=${q} → ${formatBytes(attempt.length)}`);
|
|
104
|
+
return {
|
|
105
|
+
data: attempt.toString("base64"),
|
|
106
|
+
mimeType: outMime,
|
|
107
|
+
originalBytes,
|
|
108
|
+
finalBytes: attempt.length,
|
|
109
|
+
originalDim,
|
|
110
|
+
finalDim: {
|
|
111
|
+
width: workW,
|
|
112
|
+
height: workH
|
|
113
|
+
},
|
|
114
|
+
actions,
|
|
115
|
+
changed: true
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
actions.push(`jpeg q=60 still ${formatBytes(attempt.length)} — shrinking`);
|
|
120
|
+
for (const longEdge of SHRINK_LADDER) {
|
|
121
|
+
if (Math.max(workW, workH) <= longEdge) continue;
|
|
122
|
+
const resized = await sharp(workBuf).resize({
|
|
123
|
+
width: workW >= workH ? longEdge : void 0,
|
|
124
|
+
height: workH > workW ? longEdge : void 0,
|
|
125
|
+
fit: "inside",
|
|
126
|
+
withoutEnlargement: true
|
|
127
|
+
}).toBuffer();
|
|
128
|
+
const m = await sharp(resized).metadata();
|
|
129
|
+
const newW = m.width ?? workW;
|
|
130
|
+
const newH = m.height ?? workH;
|
|
131
|
+
for (const q of JPEG_QUALITY_LADDER) {
|
|
132
|
+
attempt = await sharp(resized).jpeg({
|
|
133
|
+
quality: q,
|
|
134
|
+
mozjpeg: true
|
|
135
|
+
}).toBuffer();
|
|
136
|
+
if (attempt.length <= 5242880) {
|
|
137
|
+
actions.push(`resize ${newW}x${newH} + jpeg q=${q} → ${formatBytes(attempt.length)}`);
|
|
138
|
+
return {
|
|
139
|
+
data: attempt.toString("base64"),
|
|
140
|
+
mimeType: outMime,
|
|
141
|
+
originalBytes,
|
|
142
|
+
finalBytes: attempt.length,
|
|
143
|
+
originalDim,
|
|
144
|
+
finalDim: {
|
|
145
|
+
width: newW,
|
|
146
|
+
height: newH
|
|
147
|
+
},
|
|
148
|
+
actions,
|
|
149
|
+
changed: true
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
workBuf = resized;
|
|
154
|
+
workW = newW;
|
|
155
|
+
workH = newH;
|
|
156
|
+
}
|
|
157
|
+
actions.push(`final ${formatBytes(attempt.length)} — over limit`);
|
|
158
|
+
return {
|
|
159
|
+
data: attempt.toString("base64"),
|
|
160
|
+
mimeType: outMime,
|
|
161
|
+
originalBytes,
|
|
162
|
+
finalBytes: attempt.length,
|
|
163
|
+
originalDim,
|
|
164
|
+
finalDim: {
|
|
165
|
+
width: workW,
|
|
166
|
+
height: workH
|
|
167
|
+
},
|
|
168
|
+
actions,
|
|
169
|
+
changed: true
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function formatBytes(bytes) {
|
|
173
|
+
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(2)}MB`;
|
|
174
|
+
if (bytes >= 1024) return `${(bytes / 1024).toFixed(0)}KB`;
|
|
175
|
+
return `${bytes}B`;
|
|
176
|
+
}
|
|
11
177
|
//#endregion
|
|
12
178
|
//#region src/image-utils.ts
|
|
179
|
+
const MAX_BARE_PATH_EXTENSIONS = 8;
|
|
13
180
|
function detectImageMimeType(bytes) {
|
|
14
181
|
if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) return "image/png";
|
|
15
182
|
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
16
183
|
if (bytes.length >= 6 && bytes[0] === 71 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 56 && (bytes[4] === 55 || bytes[4] === 57) && bytes[5] === 97) return "image/gif";
|
|
17
184
|
if (bytes.length >= 12 && bytes[0] === 82 && bytes[1] === 73 && bytes[2] === 70 && bytes[3] === 70 && bytes[8] === 87 && bytes[9] === 69 && bytes[10] === 66 && bytes[11] === 80) return "image/webp";
|
|
18
185
|
}
|
|
186
|
+
const WINDOWS_DRIVE_PATH = /^([a-zA-Z]):[\\/](.*)$/;
|
|
187
|
+
function isWindowsDrivePath(value) {
|
|
188
|
+
return WINDOWS_DRIVE_PATH.test(value);
|
|
189
|
+
}
|
|
190
|
+
function isWindowsUncPath(value) {
|
|
191
|
+
return value.startsWith("\\\\") && value.length > 2;
|
|
192
|
+
}
|
|
193
|
+
function isWindowsLikePath(value) {
|
|
194
|
+
return isWindowsDrivePath(value) || isWindowsUncPath(value);
|
|
195
|
+
}
|
|
196
|
+
let cachedIsWsl;
|
|
197
|
+
function isWsl() {
|
|
198
|
+
if (cachedIsWsl !== void 0) return cachedIsWsl;
|
|
199
|
+
if (platform !== "linux") {
|
|
200
|
+
cachedIsWsl = false;
|
|
201
|
+
return cachedIsWsl;
|
|
202
|
+
}
|
|
203
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) {
|
|
204
|
+
cachedIsWsl = true;
|
|
205
|
+
return cachedIsWsl;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const release = readFileSync("/proc/version", "utf8");
|
|
209
|
+
cachedIsWsl = /microsoft|wsl/i.test(release);
|
|
210
|
+
} catch {
|
|
211
|
+
cachedIsWsl = false;
|
|
212
|
+
}
|
|
213
|
+
return cachedIsWsl;
|
|
214
|
+
}
|
|
215
|
+
function windowsToWslPath(windowsPath) {
|
|
216
|
+
const driveMatch = WINDOWS_DRIVE_PATH.exec(windowsPath);
|
|
217
|
+
if (driveMatch) {
|
|
218
|
+
const drive = driveMatch[1].toLowerCase();
|
|
219
|
+
const rest = driveMatch[2].replace(/\\/g, "/");
|
|
220
|
+
return rest.length > 0 ? `/mnt/${drive}/${rest}` : `/mnt/${drive}`;
|
|
221
|
+
}
|
|
222
|
+
if (isWindowsUncPath(windowsPath)) return windowsPath.replace(/\\/g, "/");
|
|
223
|
+
return windowsPath;
|
|
224
|
+
}
|
|
19
225
|
function resolveImagePath(input, cwd) {
|
|
20
226
|
if (input === "~") return homedir();
|
|
21
227
|
if (input.startsWith("~/")) return resolve(homedir(), input.slice(2));
|
|
228
|
+
if (isWindowsLikePath(input)) {
|
|
229
|
+
if (platform === "win32") return input;
|
|
230
|
+
if (isWsl()) return windowsToWslPath(input);
|
|
231
|
+
return input;
|
|
232
|
+
}
|
|
22
233
|
if (isAbsolute(input)) return input;
|
|
23
234
|
return resolve(cwd, input);
|
|
24
235
|
}
|
|
@@ -32,7 +243,12 @@ function shellUnescape(input) {
|
|
|
32
243
|
return result;
|
|
33
244
|
}
|
|
34
245
|
function isPathLike(value) {
|
|
35
|
-
return value.startsWith("/") || value.startsWith("~/") || value === "~" || value.startsWith("./") || value.startsWith("../");
|
|
246
|
+
return value.startsWith("/") || value.startsWith("~/") || value === "~" || value.startsWith("./") || value.startsWith("../") || isWindowsLikePath(value);
|
|
247
|
+
}
|
|
248
|
+
function startsWithWindowsPath(text, index) {
|
|
249
|
+
if (index + 2 < text.length && /[a-zA-Z]/.test(text[index]) && text[index + 1] === ":" && (text[index + 2] === "\\" || text[index + 2] === "/")) return true;
|
|
250
|
+
if (index + 1 < text.length && text[index] === "\\" && text[index + 1] === "\\") return true;
|
|
251
|
+
return false;
|
|
36
252
|
}
|
|
37
253
|
function tokenizePathLikeText(text) {
|
|
38
254
|
const tokens = [];
|
|
@@ -47,11 +263,12 @@ function tokenizePathLikeText(text) {
|
|
|
47
263
|
if (char === "'" || char === "\"") {
|
|
48
264
|
const quote = char;
|
|
49
265
|
index++;
|
|
266
|
+
const windowsMode = startsWithWindowsPath(text, index);
|
|
50
267
|
let value = "";
|
|
51
268
|
let closed = false;
|
|
52
269
|
while (index < text.length) {
|
|
53
270
|
const current = text[index];
|
|
54
|
-
if (current === "\\" && quote === "\"" && index + 1 < text.length) {
|
|
271
|
+
if (!windowsMode && current === "\\" && quote === "\"" && index + 1 < text.length) {
|
|
55
272
|
value += text[index + 1];
|
|
56
273
|
index += 2;
|
|
57
274
|
continue;
|
|
@@ -68,15 +285,17 @@ function tokenizePathLikeText(text) {
|
|
|
68
285
|
raw: text.slice(start, index),
|
|
69
286
|
value,
|
|
70
287
|
start,
|
|
71
|
-
end: index
|
|
288
|
+
end: index,
|
|
289
|
+
bare: false
|
|
72
290
|
});
|
|
73
291
|
continue;
|
|
74
292
|
}
|
|
293
|
+
const windowsMode = startsWithWindowsPath(text, index);
|
|
75
294
|
let rawValue = "";
|
|
76
295
|
while (index < text.length) {
|
|
77
296
|
const current = text[index];
|
|
78
297
|
if (/\s/.test(current)) break;
|
|
79
|
-
if (current === "\\" && index + 1 < text.length) {
|
|
298
|
+
if (!windowsMode && current === "\\" && index + 1 < text.length) {
|
|
80
299
|
rawValue += current + text[index + 1];
|
|
81
300
|
index += 2;
|
|
82
301
|
continue;
|
|
@@ -84,16 +303,59 @@ function tokenizePathLikeText(text) {
|
|
|
84
303
|
rawValue += current;
|
|
85
304
|
index++;
|
|
86
305
|
}
|
|
87
|
-
const value = shellUnescape(rawValue);
|
|
306
|
+
const value = windowsMode ? rawValue : shellUnescape(rawValue);
|
|
88
307
|
if (isPathLike(value)) tokens.push({
|
|
89
308
|
raw: rawValue,
|
|
90
309
|
value,
|
|
91
310
|
start,
|
|
92
|
-
end: index
|
|
311
|
+
end: index,
|
|
312
|
+
bare: true
|
|
93
313
|
});
|
|
94
314
|
}
|
|
95
315
|
return tokens;
|
|
96
316
|
}
|
|
317
|
+
function tryExtendBareToken(text, token, attempt) {
|
|
318
|
+
let value = token.value;
|
|
319
|
+
let end = token.end;
|
|
320
|
+
let lastResult = attempt(value);
|
|
321
|
+
if (lastResult.ok || lastResult.reason === "too-large" || !token.bare) return {
|
|
322
|
+
value,
|
|
323
|
+
end,
|
|
324
|
+
result: lastResult
|
|
325
|
+
};
|
|
326
|
+
let scan = end;
|
|
327
|
+
for (let i = 0; i < MAX_BARE_PATH_EXTENSIONS; i++) {
|
|
328
|
+
let wsEnd = scan;
|
|
329
|
+
while (wsEnd < text.length) {
|
|
330
|
+
const ch = text[wsEnd];
|
|
331
|
+
if (ch === "\n" || ch === "\r") break;
|
|
332
|
+
if (!/\s/.test(ch)) break;
|
|
333
|
+
wsEnd++;
|
|
334
|
+
}
|
|
335
|
+
if (wsEnd === scan) break;
|
|
336
|
+
let wordEnd = wsEnd;
|
|
337
|
+
while (wordEnd < text.length && !/\s/.test(text[wordEnd])) wordEnd++;
|
|
338
|
+
if (wordEnd === wsEnd) break;
|
|
339
|
+
const nextWord = shellUnescape(text.slice(wsEnd, wordEnd));
|
|
340
|
+
if (isPathLike(nextWord)) break;
|
|
341
|
+
const extendedValue = value + text.slice(scan, wsEnd) + nextWord;
|
|
342
|
+
const candidate = attempt(extendedValue);
|
|
343
|
+
scan = wordEnd;
|
|
344
|
+
if (candidate.ok || candidate.reason === "too-large") return {
|
|
345
|
+
value: extendedValue,
|
|
346
|
+
end: wordEnd,
|
|
347
|
+
result: candidate
|
|
348
|
+
};
|
|
349
|
+
value = extendedValue;
|
|
350
|
+
end = wordEnd;
|
|
351
|
+
lastResult = candidate;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
value,
|
|
355
|
+
end,
|
|
356
|
+
result: lastResult
|
|
357
|
+
};
|
|
358
|
+
}
|
|
97
359
|
function dimensionsForImage(data, mimeType) {
|
|
98
360
|
return getImageDimensions(data, mimeType) ?? void 0;
|
|
99
361
|
}
|
|
@@ -154,15 +416,16 @@ function replaceImagePathsInText(text, options) {
|
|
|
154
416
|
const accepted = [];
|
|
155
417
|
const loadImage = options.loadImage ?? loadImageFromPath;
|
|
156
418
|
for (const token of tokens) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
419
|
+
if (token.start < cursor) continue;
|
|
420
|
+
const extended = tryExtendBareToken(text, token, (path) => loadImage(path, options.cwd));
|
|
421
|
+
if (!extended.result.ok) {
|
|
422
|
+
if (extended.result.reason === "too-large") options.onReject?.(extended.result);
|
|
160
423
|
continue;
|
|
161
424
|
}
|
|
162
|
-
const attachment = options.store.add(result.image);
|
|
425
|
+
const attachment = options.store.add(extended.result.image);
|
|
163
426
|
accepted.push(attachment);
|
|
164
427
|
output += text.slice(cursor, token.start) + attachment.placeholder;
|
|
165
|
-
cursor =
|
|
428
|
+
cursor = extended.end;
|
|
166
429
|
replaced++;
|
|
167
430
|
}
|
|
168
431
|
if (replaced === 0) return {
|
|
@@ -184,14 +447,145 @@ function imagesForText(store, text, existing = []) {
|
|
|
184
447
|
data: attachment.data
|
|
185
448
|
}))];
|
|
186
449
|
}
|
|
450
|
+
function appendImagePathContext(text, attachments) {
|
|
451
|
+
if (attachments.length === 0) return text;
|
|
452
|
+
return `${text}\n\nAttached image paths:\n${attachments.map((attachment) => `- ${attachment.placeholder}: ${attachment.originalPath}`).join("\n")}`;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Async variant of imagesForText that runs each attachment through the
|
|
456
|
+
* Anthropic-aware image optimizer (resize to 8000px cap, JPEG ladder to stay
|
|
457
|
+
* under the 5 MB / 32 MB request caps). Optimization is cached on the
|
|
458
|
+
* attachment so the cost is paid once per image, not per submit.
|
|
459
|
+
*
|
|
460
|
+
* Used by paster's `input` handler; safe to await on the hot path because
|
|
461
|
+
* sharp is only invoked when the image is actually over the limits.
|
|
462
|
+
*/
|
|
463
|
+
async function imagesForTextOptimized(store, text, existing = []) {
|
|
464
|
+
const attachments = store.matchingPlaceholders(text);
|
|
465
|
+
const optimized = [];
|
|
466
|
+
for (const attachment of attachments) {
|
|
467
|
+
if (!attachment.optimized) try {
|
|
468
|
+
const result = await optimizeImageBytes(Buffer.from(attachment.data, "base64"), attachment.mimeType);
|
|
469
|
+
if (result.changed) {
|
|
470
|
+
attachment.data = result.data;
|
|
471
|
+
attachment.mimeType = result.mimeType;
|
|
472
|
+
if (result.finalDim) attachment.dimensions = {
|
|
473
|
+
widthPx: result.finalDim.width,
|
|
474
|
+
heightPx: result.finalDim.height
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
attachment.optimized = true;
|
|
478
|
+
attachment.originalBytes = result.originalBytes;
|
|
479
|
+
attachment.finalBytes = result.finalBytes;
|
|
480
|
+
attachment.optimizeActions = result.actions;
|
|
481
|
+
} catch {
|
|
482
|
+
attachment.optimized = true;
|
|
483
|
+
}
|
|
484
|
+
optimized.push({
|
|
485
|
+
type: "image",
|
|
486
|
+
mimeType: attachment.mimeType,
|
|
487
|
+
data: attachment.data
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return [...existing, ...optimized];
|
|
491
|
+
}
|
|
492
|
+
function describeReject(result, notify) {
|
|
493
|
+
if (!notify) return;
|
|
494
|
+
if (result.reason === "too-large") notify(`paster: image is too large and was not attached: ${result.path}`);
|
|
495
|
+
}
|
|
187
496
|
//#endregion
|
|
188
497
|
//#region src/clipboard.ts
|
|
189
498
|
function readClipboardImage(maxBytes = MAX_IMAGE_BYTES) {
|
|
190
|
-
if (process.platform
|
|
499
|
+
if (process.platform === "darwin") return readMacOSClipboardImage(maxBytes);
|
|
500
|
+
if (process.platform === "win32") return readWindowsClipboardImage(maxBytes);
|
|
501
|
+
if (process.platform === "linux" && isWSL()) return readWindowsClipboardImage(maxBytes);
|
|
502
|
+
return {
|
|
191
503
|
ok: false,
|
|
192
504
|
reason: "unsupported-platform"
|
|
193
505
|
};
|
|
194
|
-
|
|
506
|
+
}
|
|
507
|
+
function isWSL() {
|
|
508
|
+
try {
|
|
509
|
+
return /microsoft|wsl/i.test(readFileSync("/proc/version", "utf8"));
|
|
510
|
+
} catch {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function resolvePowerShell() {
|
|
515
|
+
if (process.platform === "win32") return "powershell.exe";
|
|
516
|
+
for (const c of ["/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe", "/mnt/c/WINDOWS/System32/WindowsPowerShell/v1.0/powershell.exe"]) if (existsSync(c)) return c;
|
|
517
|
+
return "powershell.exe";
|
|
518
|
+
}
|
|
519
|
+
function readWindowsClipboardImage(maxBytes) {
|
|
520
|
+
const exe = resolvePowerShell();
|
|
521
|
+
if (!exe) return {
|
|
522
|
+
ok: false,
|
|
523
|
+
reason: "unsupported-platform"
|
|
524
|
+
};
|
|
525
|
+
const script = [
|
|
526
|
+
"$ErrorActionPreference = 'Stop'",
|
|
527
|
+
"Add-Type -AssemblyName System.Windows.Forms | Out-Null",
|
|
528
|
+
"Add-Type -AssemblyName System.Drawing | Out-Null",
|
|
529
|
+
"$img = [System.Windows.Forms.Clipboard]::GetImage()",
|
|
530
|
+
"if ($img -eq $null) { exit 2 }",
|
|
531
|
+
"$ms = New-Object System.IO.MemoryStream",
|
|
532
|
+
"$img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)",
|
|
533
|
+
"[Console]::Out.Write([Convert]::ToBase64String($ms.ToArray()))"
|
|
534
|
+
].join("; ");
|
|
535
|
+
try {
|
|
536
|
+
const result = spawnSync(exe, [
|
|
537
|
+
"-NoProfile",
|
|
538
|
+
"-NonInteractive",
|
|
539
|
+
"-STA",
|
|
540
|
+
"-Command",
|
|
541
|
+
script
|
|
542
|
+
], {
|
|
543
|
+
timeout: 5e3,
|
|
544
|
+
encoding: "utf8",
|
|
545
|
+
maxBuffer: 64 * 1024 * 1024
|
|
546
|
+
});
|
|
547
|
+
if (result.status === 2) return {
|
|
548
|
+
ok: false,
|
|
549
|
+
reason: "empty"
|
|
550
|
+
};
|
|
551
|
+
if (result.status !== 0) return {
|
|
552
|
+
ok: false,
|
|
553
|
+
reason: "read-error"
|
|
554
|
+
};
|
|
555
|
+
const data = (result.stdout || "").trim();
|
|
556
|
+
if (!data) return {
|
|
557
|
+
ok: false,
|
|
558
|
+
reason: "empty"
|
|
559
|
+
};
|
|
560
|
+
const bytes = Buffer.from(data, "base64");
|
|
561
|
+
if (bytes.length === 0) return {
|
|
562
|
+
ok: false,
|
|
563
|
+
reason: "empty"
|
|
564
|
+
};
|
|
565
|
+
if (bytes.length > maxBytes) return {
|
|
566
|
+
ok: false,
|
|
567
|
+
reason: "too-large"
|
|
568
|
+
};
|
|
569
|
+
const mimeType = detectImageMimeType(bytes);
|
|
570
|
+
if (!mimeType) return {
|
|
571
|
+
ok: false,
|
|
572
|
+
reason: "unsupported"
|
|
573
|
+
};
|
|
574
|
+
return {
|
|
575
|
+
ok: true,
|
|
576
|
+
image: {
|
|
577
|
+
originalPath: "clipboard.png",
|
|
578
|
+
mimeType,
|
|
579
|
+
data,
|
|
580
|
+
dimensions: dimensionsForImage(data, mimeType)
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
} catch {
|
|
584
|
+
return {
|
|
585
|
+
ok: false,
|
|
586
|
+
reason: "read-error"
|
|
587
|
+
};
|
|
588
|
+
}
|
|
195
589
|
}
|
|
196
590
|
function readMacOSClipboardImage(maxBytes) {
|
|
197
591
|
for (const attempt of [{
|
|
@@ -254,23 +648,72 @@ function readMacOSClipboardImage(maxBytes) {
|
|
|
254
648
|
}
|
|
255
649
|
//#endregion
|
|
256
650
|
//#region src/config.ts
|
|
257
|
-
const DEFAULT_PASTER_CONFIG = {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
651
|
+
const DEFAULT_PASTER_CONFIG = {
|
|
652
|
+
submittedPreviewStyle: "raw",
|
|
653
|
+
includeImagePathsInPrompt: true,
|
|
654
|
+
customEditor: {
|
|
655
|
+
enabled: true,
|
|
656
|
+
showImagePreview: true,
|
|
657
|
+
deletePlaceholderAsBlock: true
|
|
658
|
+
}
|
|
659
|
+
};
|
|
262
660
|
function resolvePasterConfig(config = {}) {
|
|
263
|
-
return {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
661
|
+
return {
|
|
662
|
+
submittedPreviewStyle: config.submittedPreviewStyle ?? DEFAULT_PASTER_CONFIG.submittedPreviewStyle,
|
|
663
|
+
includeImagePathsInPrompt: config.includeImagePathsInPrompt ?? DEFAULT_PASTER_CONFIG.includeImagePathsInPrompt,
|
|
664
|
+
customEditor: {
|
|
665
|
+
enabled: config.customEditor?.enabled ?? DEFAULT_PASTER_CONFIG.customEditor.enabled,
|
|
666
|
+
showImagePreview: config.customEditor?.showImagePreview ?? DEFAULT_PASTER_CONFIG.customEditor.showImagePreview,
|
|
667
|
+
deletePlaceholderAsBlock: config.customEditor?.deletePlaceholderAsBlock ?? DEFAULT_PASTER_CONFIG.customEditor.deletePlaceholderAsBlock
|
|
668
|
+
}
|
|
669
|
+
};
|
|
268
670
|
}
|
|
269
671
|
//#endregion
|
|
270
672
|
//#region src/editor.ts
|
|
271
673
|
const PASTE_START = "\x1B[200~";
|
|
272
674
|
const PASTE_END = "\x1B[201~";
|
|
273
675
|
const PLACEHOLDER_REGEX = /\[#image \d+\]/g;
|
|
676
|
+
const PASTE_MARKER_REGEX = /\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]/g;
|
|
677
|
+
const baseSegmenter = new Intl.Segmenter();
|
|
678
|
+
function atomicSpansForText(text, validPasteIds) {
|
|
679
|
+
const spans = [];
|
|
680
|
+
for (const match of text.matchAll(PASTE_MARKER_REGEX)) {
|
|
681
|
+
const id = Number.parseInt(match[1], 10);
|
|
682
|
+
if (!validPasteIds.has(id)) continue;
|
|
683
|
+
spans.push({
|
|
684
|
+
start: match.index,
|
|
685
|
+
end: match.index + match[0].length
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
for (const match of text.matchAll(PLACEHOLDER_REGEX)) {
|
|
689
|
+
const placeholder = match[0];
|
|
690
|
+
spans.push({
|
|
691
|
+
start: match.index,
|
|
692
|
+
end: match.index + placeholder.length
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
return spans.sort((a, b) => a.start - b.start || a.end - b.end);
|
|
696
|
+
}
|
|
697
|
+
function segmentTextWithAtomicImages(text, store, validPasteIds = /* @__PURE__ */ new Set()) {
|
|
698
|
+
const spans = atomicSpansForText(text, validPasteIds);
|
|
699
|
+
if (spans.length === 0) return [...baseSegmenter.segment(text)];
|
|
700
|
+
const result = [];
|
|
701
|
+
let spanIndex = 0;
|
|
702
|
+
for (const segment of baseSegmenter.segment(text)) {
|
|
703
|
+
while (spanIndex < spans.length && spans[spanIndex].end <= segment.index) spanIndex++;
|
|
704
|
+
const span = spans[spanIndex];
|
|
705
|
+
if (span && segment.index >= span.start && segment.index < span.end) {
|
|
706
|
+
if (segment.index === span.start) result.push({
|
|
707
|
+
segment: text.slice(span.start, span.end),
|
|
708
|
+
index: span.start,
|
|
709
|
+
input: text
|
|
710
|
+
});
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
result.push(segment);
|
|
714
|
+
}
|
|
715
|
+
return result;
|
|
716
|
+
}
|
|
274
717
|
function findPlaceholderAtCursor(store, lines, cursor, mode) {
|
|
275
718
|
const line = lines[cursor.line] ?? "";
|
|
276
719
|
for (const match of line.matchAll(PLACEHOLDER_REGEX)) {
|
|
@@ -278,21 +721,24 @@ function findPlaceholderAtCursor(store, lines, cursor, mode) {
|
|
|
278
721
|
const start = match.index;
|
|
279
722
|
const end = start + placeholder.length;
|
|
280
723
|
const attachment = store.get(placeholder);
|
|
281
|
-
if (!attachment) continue;
|
|
724
|
+
if (!attachment && mode !== "hover") continue;
|
|
282
725
|
if (mode === "hover" && cursor.col >= start && cursor.col < end) return {
|
|
283
726
|
attachment,
|
|
727
|
+
placeholder,
|
|
284
728
|
line: cursor.line,
|
|
285
729
|
start,
|
|
286
730
|
end
|
|
287
731
|
};
|
|
288
732
|
if (mode === "backspace" && cursor.col > start && cursor.col <= end) return {
|
|
289
733
|
attachment,
|
|
734
|
+
placeholder,
|
|
290
735
|
line: cursor.line,
|
|
291
736
|
start,
|
|
292
737
|
end
|
|
293
738
|
};
|
|
294
739
|
if (mode === "delete" && cursor.col >= start && cursor.col < end) return {
|
|
295
740
|
attachment,
|
|
741
|
+
placeholder,
|
|
296
742
|
line: cursor.line,
|
|
297
743
|
start,
|
|
298
744
|
end
|
|
@@ -306,6 +752,7 @@ var PasterEditor = class extends CustomEditor {
|
|
|
306
752
|
super(tui, theme, pasterKeybindings);
|
|
307
753
|
this.pasterKeybindings = pasterKeybindings;
|
|
308
754
|
this.pasterOptions = pasterOptions;
|
|
755
|
+
this.installAtomicImageSegmentation();
|
|
309
756
|
this.onPasteImage = () => {
|
|
310
757
|
this.handlePasteClipboardImage();
|
|
311
758
|
};
|
|
@@ -317,6 +764,7 @@ var PasterEditor = class extends CustomEditor {
|
|
|
317
764
|
}
|
|
318
765
|
handleInput(data) {
|
|
319
766
|
if (this.handleBracketedPaste(data)) return;
|
|
767
|
+
if (this.handleAtomicPlaceholderNavigation(data)) return;
|
|
320
768
|
if (this.pasterOptions.deletePlaceholderAsBlock && this.handleAtomicPlaceholderDelete(data)) return;
|
|
321
769
|
super.handleInput(data);
|
|
322
770
|
this.updateCursorPreview();
|
|
@@ -325,6 +773,10 @@ var PasterEditor = class extends CustomEditor {
|
|
|
325
773
|
this.activePreviewPlaceholder = void 0;
|
|
326
774
|
this.pasterOptions.setCursorPreview(void 0);
|
|
327
775
|
}
|
|
776
|
+
installAtomicImageSegmentation() {
|
|
777
|
+
const editor = this;
|
|
778
|
+
editor.segment = (text) => segmentTextWithAtomicImages(text, this.pasterOptions.store, new Set(editor.pastes?.keys() ?? []));
|
|
779
|
+
}
|
|
328
780
|
async handlePasteClipboardImage() {
|
|
329
781
|
const attachment = await this.pasterOptions.pasteClipboardImage?.();
|
|
330
782
|
if (!attachment) return;
|
|
@@ -365,6 +817,20 @@ var PasterEditor = class extends CustomEditor {
|
|
|
365
817
|
this.updateCursorPreview();
|
|
366
818
|
return true;
|
|
367
819
|
}
|
|
820
|
+
handleAtomicPlaceholderNavigation(data) {
|
|
821
|
+
const isLeft = this.pasterKeybindings.matches(data, "tui.editor.cursorLeft");
|
|
822
|
+
const isRight = this.pasterKeybindings.matches(data, "tui.editor.cursorRight");
|
|
823
|
+
if (!isLeft && !isRight) return false;
|
|
824
|
+
const line = this.getLines()[this.getCursor().line] ?? "";
|
|
825
|
+
const cursor = this.getCursor();
|
|
826
|
+
const matches = [...line.matchAll(PLACEHOLDER_REGEX)];
|
|
827
|
+
const target = isRight ? matches.find((match) => cursor.col >= match.index && cursor.col < match.index + match[0].length) : matches.find((match) => cursor.col > match.index && cursor.col <= match.index + match[0].length);
|
|
828
|
+
if (!target) return false;
|
|
829
|
+
this.setCursor(target.index + (isRight ? target[0].length : 0));
|
|
830
|
+
this.updateCursorPreview();
|
|
831
|
+
this.tui.requestRender();
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
368
834
|
handleAtomicPlaceholderDelete(data) {
|
|
369
835
|
const isBackspace = this.pasterKeybindings.matches(data, "tui.editor.deleteCharBackward");
|
|
370
836
|
const isDelete = this.pasterKeybindings.matches(data, "tui.editor.deleteCharForward");
|
|
@@ -376,14 +842,18 @@ var PasterEditor = class extends CustomEditor {
|
|
|
376
842
|
this.updateCursorPreview();
|
|
377
843
|
return true;
|
|
378
844
|
}
|
|
845
|
+
setCursor(col) {
|
|
846
|
+
const editor = this;
|
|
847
|
+
if (editor.setCursorCol) editor.setCursorCol(col);
|
|
848
|
+
else editor.state.cursorCol = col;
|
|
849
|
+
}
|
|
379
850
|
deleteLineRange(lineIndex, start, end) {
|
|
380
851
|
const editor = this;
|
|
381
852
|
editor.pushUndoSnapshot?.();
|
|
382
853
|
const line = editor.state.lines[lineIndex] ?? "";
|
|
383
854
|
editor.state.lines[lineIndex] = line.slice(0, start) + line.slice(end);
|
|
384
855
|
editor.state.cursorLine = lineIndex;
|
|
385
|
-
|
|
386
|
-
else editor.state.cursorCol = start;
|
|
856
|
+
this.setCursor(start);
|
|
387
857
|
editor.lastAction = null;
|
|
388
858
|
editor.historyIndex = -1;
|
|
389
859
|
this.onChange?.(this.getText());
|
|
@@ -393,14 +863,12 @@ var PasterEditor = class extends CustomEditor {
|
|
|
393
863
|
return replaceImagePathsInText(text, {
|
|
394
864
|
cwd: this.pasterOptions.cwd,
|
|
395
865
|
store: this.pasterOptions.store,
|
|
396
|
-
onReject: (result) =>
|
|
397
|
-
if (result.reason === "too-large") this.pasterOptions.notify(`paster: image is over 10 MB and was not attached: ${result.path}`);
|
|
398
|
-
}
|
|
866
|
+
onReject: (result) => describeReject(result, this.pasterOptions.notify)
|
|
399
867
|
});
|
|
400
868
|
}
|
|
401
869
|
updateCursorPreview() {
|
|
402
870
|
const target = findPlaceholderAtCursor(this.pasterOptions.store, this.getLines(), this.getCursor(), "hover");
|
|
403
|
-
const nextPlaceholder = target?.attachment
|
|
871
|
+
const nextPlaceholder = target?.attachment?.placeholder;
|
|
404
872
|
if (nextPlaceholder === this.activePreviewPlaceholder) return;
|
|
405
873
|
this.activePreviewPlaceholder = nextPlaceholder;
|
|
406
874
|
this.pasterOptions.setCursorPreview(target?.attachment);
|
|
@@ -415,26 +883,48 @@ function formatAttachmentLine(attachment, width, style) {
|
|
|
415
883
|
}
|
|
416
884
|
var ImagePreviewMessage = class {
|
|
417
885
|
images;
|
|
418
|
-
constructor(attachments, theme) {
|
|
886
|
+
constructor(attachments, theme, options = {}) {
|
|
419
887
|
this.attachments = attachments;
|
|
420
888
|
this.theme = theme;
|
|
889
|
+
this.options = options;
|
|
421
890
|
this.images = attachments.map((attachment) => new Image(attachment.data, attachment.mimeType, theme, {
|
|
422
891
|
maxWidthCells: 60,
|
|
423
892
|
maxHeightCells: 16,
|
|
424
893
|
filename: attachment.placeholder
|
|
425
894
|
}));
|
|
426
895
|
}
|
|
896
|
+
invalidate() {
|
|
897
|
+
for (const image of this.images) image.invalidate();
|
|
898
|
+
}
|
|
427
899
|
render(width) {
|
|
900
|
+
return this.options.style === "collapsible" ? this.renderCollapsible(width) : this.renderRaw(width);
|
|
901
|
+
}
|
|
902
|
+
renderRaw(width) {
|
|
428
903
|
const lines = [];
|
|
904
|
+
const safeWidth = Math.max(1, width);
|
|
429
905
|
for (let index = 0; index < this.attachments.length; index++) {
|
|
430
906
|
const attachment = this.attachments[index];
|
|
431
|
-
lines.push(formatAttachmentLine(attachment,
|
|
432
|
-
lines.push(...this.images[index].render(
|
|
907
|
+
lines.push(formatAttachmentLine(attachment, safeWidth, this.theme.fallbackColor));
|
|
908
|
+
lines.push(...this.images[index].render(safeWidth));
|
|
433
909
|
}
|
|
434
910
|
return lines;
|
|
435
911
|
}
|
|
436
|
-
|
|
437
|
-
|
|
912
|
+
renderCollapsible(width) {
|
|
913
|
+
const container = new Container();
|
|
914
|
+
container.addChild(new Spacer(1));
|
|
915
|
+
const box = new Box(1, 1, this.theme.background);
|
|
916
|
+
container.addChild(box);
|
|
917
|
+
const title = this.theme.title ?? this.theme.fallbackColor;
|
|
918
|
+
const muted = this.theme.muted ?? this.theme.fallbackColor;
|
|
919
|
+
const summary = this.attachments.length === 1 ? `Attached ${this.attachments[0].placeholder}` : `Attached ${this.attachments.length} images`;
|
|
920
|
+
const suffix = this.options.expanded ? " (ctrl+o to collapse)" : " (ctrl+o to expand)";
|
|
921
|
+
box.addChild(new Text(`${title(summary)}${muted(suffix)}`, 0, 0));
|
|
922
|
+
for (const attachment of this.attachments) box.addChild(new Text(formatAttachmentLine(attachment, width, muted), 0, 0));
|
|
923
|
+
const lines = container.render(width);
|
|
924
|
+
if (!this.options.expanded) return lines;
|
|
925
|
+
const safeWidth = Math.max(1, width);
|
|
926
|
+
for (let index = 0; index < this.attachments.length; index++) lines.push(...this.images[index].render(safeWidth));
|
|
927
|
+
return lines;
|
|
438
928
|
}
|
|
439
929
|
};
|
|
440
930
|
var CursorImagePreviewWidget = class {
|
|
@@ -512,9 +1002,7 @@ function createImagePasteTerminalInputHandler(options) {
|
|
|
512
1002
|
cwd: options.cwd,
|
|
513
1003
|
store: options.store,
|
|
514
1004
|
loadImage: options.loadImage,
|
|
515
|
-
onReject: (result) =>
|
|
516
|
-
if (result.reason === "too-large") options.notify?.(`paster: image is over 10 MB and was not attached: ${result.path}`);
|
|
517
|
-
}
|
|
1005
|
+
onReject: (result) => describeReject(result, options.notify)
|
|
518
1006
|
});
|
|
519
1007
|
return (data) => {
|
|
520
1008
|
let prefix = "";
|
|
@@ -550,11 +1038,19 @@ function paster(pi, config = {}) {
|
|
|
550
1038
|
let pendingPreview = [];
|
|
551
1039
|
let activeEditor;
|
|
552
1040
|
let unsubscribeTerminalInput;
|
|
553
|
-
pi.registerMessageRenderer("paster-preview", (message,
|
|
1041
|
+
pi.registerMessageRenderer("paster-preview", (message, options, theme) => {
|
|
554
1042
|
const placeholders = message.details?.placeholders ?? [];
|
|
555
1043
|
const attachments = store.list().filter((attachment) => placeholders.includes(attachment.placeholder));
|
|
556
1044
|
if (attachments.length === 0) return void 0;
|
|
557
|
-
return new ImagePreviewMessage(attachments, {
|
|
1045
|
+
return new ImagePreviewMessage(attachments, {
|
|
1046
|
+
fallbackColor: (text) => theme.fg("muted", text),
|
|
1047
|
+
background: (text) => theme.bg("toolSuccessBg", text),
|
|
1048
|
+
title: (text) => theme.fg("toolTitle", theme.bold(text)),
|
|
1049
|
+
muted: (text) => theme.fg("muted", text)
|
|
1050
|
+
}, {
|
|
1051
|
+
expanded: options.expanded,
|
|
1052
|
+
style: resolvedConfig.submittedPreviewStyle
|
|
1053
|
+
});
|
|
558
1054
|
});
|
|
559
1055
|
pi.on("session_start", (_event, ctx) => {
|
|
560
1056
|
store.clear();
|
|
@@ -612,24 +1108,26 @@ function paster(pi, config = {}) {
|
|
|
612
1108
|
store.clear();
|
|
613
1109
|
});
|
|
614
1110
|
function previewMessage(attachments) {
|
|
1111
|
+
const placeholders = attachments.map((attachment) => attachment.placeholder);
|
|
615
1112
|
return {
|
|
616
1113
|
customType: "paster-preview",
|
|
617
|
-
content: ""
|
|
1114
|
+
content: `(attachment preview: ${placeholders.join(", ")})`,
|
|
618
1115
|
display: true,
|
|
619
|
-
details: { placeholders
|
|
1116
|
+
details: { placeholders }
|
|
620
1117
|
};
|
|
621
1118
|
}
|
|
622
|
-
pi.on("input", (event, ctx) => {
|
|
1119
|
+
pi.on("input", async (event, ctx) => {
|
|
623
1120
|
if (event.source === "extension") return { action: "continue" };
|
|
624
1121
|
if (ctx.hasUI) activeEditor?.clearCursorPreview();
|
|
625
1122
|
const attachments = store.matchingPlaceholders(event.text);
|
|
626
1123
|
if (attachments.length === 0) return { action: "continue" };
|
|
627
1124
|
if (ctx.isIdle()) pendingPreview = attachments;
|
|
628
1125
|
else pi.sendMessage(previewMessage(attachments), { deliverAs: "followUp" });
|
|
1126
|
+
const images = await imagesForTextOptimized(store, event.text, event.images);
|
|
629
1127
|
return {
|
|
630
1128
|
action: "transform",
|
|
631
|
-
text: event.text,
|
|
632
|
-
images
|
|
1129
|
+
text: resolvedConfig.includeImagePathsInPrompt ? appendImagePathContext(event.text, attachments) : event.text,
|
|
1130
|
+
images
|
|
633
1131
|
};
|
|
634
1132
|
});
|
|
635
1133
|
pi.on("before_agent_start", () => {
|
|
@@ -640,4 +1138,4 @@ function paster(pi, config = {}) {
|
|
|
640
1138
|
});
|
|
641
1139
|
}
|
|
642
1140
|
//#endregion
|
|
643
|
-
export { AttachmentStore, CursorImagePreviewWidget, DEFAULT_PASTER_CONFIG, EXTENSION_NAME, ImagePreviewMessage, MAX_IMAGE_BYTES, PASTE_END, PASTE_START, PasterEditor, createImagePasteTerminalInputHandler, createPaster, paster as default, detectImageMimeType, dimensionsForImage, imagesForText, loadImageFromPath, readClipboardImage, replaceImagePathsInText, resolveImagePath, resolvePasterConfig, shellUnescape, tokenizePathLikeText };
|
|
1141
|
+
export { ANTHROPIC_MAX_DIMENSION, ANTHROPIC_MAX_IMAGE_BYTES, AttachmentStore, CursorImagePreviewWidget, DEFAULT_PASTER_CONFIG, EXTENSION_NAME, ImagePreviewMessage, MAX_IMAGE_BYTES, PASTE_END, PASTE_START, PasterEditor, appendImagePathContext, createImagePasteTerminalInputHandler, createPaster, paster as default, describeReject, detectImageMimeType, dimensionsForImage, imagesForText, imagesForTextOptimized, isWindowsDrivePath, isWindowsLikePath, isWindowsUncPath, isWsl, loadImageFromPath, optimizeImageBytes, readClipboardImage, replaceImagePathsInText, resolveImagePath, resolvePasterConfig, segmentTextWithAtomicImages, shellUnescape, tokenizePathLikeText, windowsToWslPath };
|