Skip to content

Commit cd7faa3

Browse files
committed
consolidate binary and claude image extension lists
1 parent 30e5dfa commit cd7faa3

7 files changed

Lines changed: 181 additions & 46 deletions

File tree

apps/code/src/renderer/features/editor/utils/cloud-prompt.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ContentBlock } from "@agentclientprotocol/sdk";
22
import {
33
CLOUD_PROMPT_PREFIX,
44
getImageMimeType,
5+
isClaudeImageFile,
56
isImageFile,
67
serializeCloudPrompt,
78
} from "@posthog/shared";
@@ -65,8 +66,6 @@ const TEXT_FILENAMES = new Set([
6566
"README",
6667
"README.md",
6768
]);
68-
const CLOUD_IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp"]);
69-
7069
const MAX_EMBEDDED_IMAGE_BYTES = 5 * 1024 * 1024;
7170

7271
function isTextAttachment(filePath: string): boolean {
@@ -75,10 +74,6 @@ function isTextAttachment(filePath: string): boolean {
7574
return TEXT_FILENAMES.has(fileName) || TEXT_EXTENSIONS.has(ext);
7675
}
7776

78-
export function isSupportedCloudImageAttachment(filePath: string): boolean {
79-
return CLOUD_IMAGE_EXTENSIONS.has(getFileExtension(filePath));
80-
}
81-
8277
export function isSupportedCloudTextAttachment(filePath: string): boolean {
8378
return isTextAttachment(filePath);
8479
}
@@ -167,7 +162,7 @@ async function buildAttachmentBlock(filePath: string): Promise<ContentBlock> {
167162
const fileName = getFileName(filePath);
168163
const uri = pathToFileUri(filePath);
169164

170-
if (isSupportedCloudImageAttachment(fileName)) {
165+
if (isClaudeImageFile(fileName)) {
171166
const base64 = await trpcClient.fs.readFileAsBase64.query({ filePath });
172167
if (!base64) {
173168
throw new Error(`Unable to read attached image ${fileName}`);

apps/code/src/renderer/utils/generateTitle.ts

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { fetchAuthState } from "@features/auth/hooks/authQueries";
22
import { xmlToContent } from "@features/message-editor/utils/content";
3+
import { isBinaryFile } from "@posthog/shared";
34
import { trpcClient } from "@renderer/trpc";
45
import { logger } from "@utils/logger";
56

@@ -8,44 +9,6 @@ const log = logger.scope("title-generator");
89
const ATTACHED_FILES_REGEX = /^\[?Attached files:.*]?$/gm;
910
const PASTED_TEXT_SNIPPET_LIMIT = 500;
1011

11-
const BINARY_EXTENSIONS = new Set([
12-
"png",
13-
"jpg",
14-
"jpeg",
15-
"gif",
16-
"webp",
17-
"bmp",
18-
"ico",
19-
"svg",
20-
"mp3",
21-
"mp4",
22-
"wav",
23-
"avi",
24-
"mov",
25-
"mkv",
26-
"pdf",
27-
"zip",
28-
"tar",
29-
"gz",
30-
"rar",
31-
"7z",
32-
"exe",
33-
"dll",
34-
"so",
35-
"dylib",
36-
"wasm",
37-
"ttf",
38-
"otf",
39-
"woff",
40-
"woff2",
41-
"eot",
42-
]);
43-
44-
function getExtension(filePath: string): string {
45-
const dot = filePath.lastIndexOf(".");
46-
return dot >= 0 ? filePath.slice(dot + 1).toLowerCase() : "";
47-
}
48-
4912
function getFileName(filePath: string): string {
5013
const slash = filePath.lastIndexOf("/");
5114
return slash >= 0 ? filePath.slice(slash + 1) : filePath;
@@ -74,7 +37,7 @@ export async function enrichDescriptionWithFileContent(
7437

7538
const parts = await Promise.all(
7639
paths.map(async (filePath) => {
77-
if (BINARY_EXTENSIONS.has(getExtension(filePath))) {
40+
if (isBinaryFile(filePath)) {
7841
return `[Attached: ${getFileName(filePath)}]`;
7942
}
8043
try {

packages/shared/src/binary.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { describe, expect, it } from "vitest";
2+
import { BINARY_EXTENSIONS, isBinaryFile } from "./binary";
3+
4+
describe("isBinaryFile", () => {
5+
it.each([
6+
["foo.png"],
7+
["foo.JPG"],
8+
["path/to/foo.gif"],
9+
["foo.webp"],
10+
["foo.tiff"],
11+
["foo.avif"],
12+
["foo.heic"],
13+
["foo.svg"],
14+
["foo.mp3"],
15+
["foo.mp4"],
16+
["foo.mov"],
17+
["foo.pdf"],
18+
["foo.zip"],
19+
["foo.tar.gz"],
20+
["foo.7z"],
21+
["foo.exe"],
22+
["foo.dll"],
23+
["foo.dylib"],
24+
["foo.wasm"],
25+
["foo.ttf"],
26+
["foo.woff2"],
27+
])("returns true for %s", (filename) => {
28+
expect(isBinaryFile(filename)).toBe(true);
29+
});
30+
31+
it.each([
32+
["foo.txt"],
33+
["foo.md"],
34+
["foo.ts"],
35+
["foo.json"],
36+
["foo"],
37+
[""],
38+
["README"],
39+
])("returns false for %s", (filename) => {
40+
expect(isBinaryFile(filename)).toBe(false);
41+
});
42+
43+
it("includes every canonical image extension", () => {
44+
for (const ext of [
45+
"png",
46+
"jpg",
47+
"jpeg",
48+
"gif",
49+
"webp",
50+
"bmp",
51+
"ico",
52+
"tiff",
53+
"tif",
54+
"svg",
55+
"heic",
56+
"heif",
57+
"avif",
58+
]) {
59+
expect(BINARY_EXTENSIONS.has(ext)).toBe(true);
60+
}
61+
});
62+
});

packages/shared/src/binary.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { IMAGE_MIME_TYPES } from "./image";
2+
3+
export const AUDIO_VIDEO_EXTENSIONS: ReadonlySet<string> = new Set([
4+
"mp3",
5+
"mp4",
6+
"wav",
7+
"avi",
8+
"mov",
9+
"mkv",
10+
"webm",
11+
"mpg",
12+
"mpeg",
13+
"flac",
14+
"ogg",
15+
"m4a",
16+
"aac",
17+
]);
18+
19+
export const ARCHIVE_EXTENSIONS: ReadonlySet<string> = new Set([
20+
"zip",
21+
"tar",
22+
"gz",
23+
"tgz",
24+
"bz2",
25+
"xz",
26+
"rar",
27+
"7z",
28+
]);
29+
30+
export const EXECUTABLE_EXTENSIONS: ReadonlySet<string> = new Set([
31+
"exe",
32+
"dll",
33+
"so",
34+
"dylib",
35+
"wasm",
36+
"bin",
37+
"o",
38+
]);
39+
40+
export const FONT_EXTENSIONS: ReadonlySet<string> = new Set([
41+
"ttf",
42+
"otf",
43+
"woff",
44+
"woff2",
45+
"eot",
46+
]);
47+
48+
export const DOCUMENT_BINARY_EXTENSIONS: ReadonlySet<string> = new Set(["pdf"]);
49+
50+
export const BINARY_EXTENSIONS: ReadonlySet<string> = new Set([
51+
...Object.keys(IMAGE_MIME_TYPES),
52+
...AUDIO_VIDEO_EXTENSIONS,
53+
...ARCHIVE_EXTENSIONS,
54+
...EXECUTABLE_EXTENSIONS,
55+
...FONT_EXTENSIONS,
56+
...DOCUMENT_BINARY_EXTENSIONS,
57+
]);
58+
59+
function extensionOf(filename: string): string {
60+
return filename.split(".").pop()?.toLowerCase() ?? "";
61+
}
62+
63+
export function isBinaryFile(filename: string): boolean {
64+
const ext = extensionOf(filename);
65+
return ext.length > 0 && BINARY_EXTENSIONS.has(ext);
66+
}

packages/shared/src/image.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
buildImageDataUrl,
44
getImageMimeType,
55
isAllowedImageMimeType,
6+
isClaudeImageFile,
67
isGifFile,
78
isImageFile,
89
isRasterImageFile,
@@ -201,6 +202,30 @@ describe("isRasterImageFile", () => {
201202
});
202203
});
203204

205+
describe("isClaudeImageFile", () => {
206+
it.each([["foo.png"], ["foo.JPG"], ["foo.jpeg"], ["foo.gif"], ["foo.webp"]])(
207+
"returns true for Claude-supported %s",
208+
(filename) => {
209+
expect(isClaudeImageFile(filename)).toBe(true);
210+
},
211+
);
212+
213+
it.each([
214+
["foo.bmp"],
215+
["foo.ico"],
216+
["foo.tiff"],
217+
["foo.svg"],
218+
["foo.heic"],
219+
["foo.heif"],
220+
["foo.avif"],
221+
["foo.txt"],
222+
["foo"],
223+
[""],
224+
])("returns false for unsupported %s", (filename) => {
225+
expect(isClaudeImageFile(filename)).toBe(false);
226+
});
227+
});
228+
204229
describe("isGifFile", () => {
205230
it("returns true for .gif", () => {
206231
expect(isGifFile("foo.gif")).toBe(true);

packages/shared/src/image.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ export type ClaudeImageMimeType =
5252
| "image/gif"
5353
| "image/webp";
5454

55+
export const CLAUDE_IMAGE_EXTENSIONS: ReadonlySet<string> = new Set([
56+
"png",
57+
"jpg",
58+
"jpeg",
59+
"gif",
60+
"webp",
61+
]);
62+
5563
const DATA_URL_PATTERN =
5664
/^data:([a-zA-Z]+\/[a-zA-Z0-9.+-]+)(?:;[a-zA-Z0-9-]+=[^;,]+)*;base64,([A-Za-z0-9+/=\s]+)$/;
5765

@@ -77,6 +85,11 @@ export function isRasterImageFile(filename: string): boolean {
7785
return ext.length > 0 && RASTER_IMAGE_EXTENSIONS.has(ext);
7886
}
7987

88+
export function isClaudeImageFile(filename: string): boolean {
89+
const ext = extensionOf(filename);
90+
return ext.length > 0 && CLAUDE_IMAGE_EXTENSIONS.has(ext);
91+
}
92+
8093
export function isGifFile(filename: string): boolean {
8194
return extensionOf(filename) === "gif";
8295
}

packages/shared/src/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
export {
2+
ARCHIVE_EXTENSIONS,
3+
AUDIO_VIDEO_EXTENSIONS,
4+
BINARY_EXTENSIONS,
5+
DOCUMENT_BINARY_EXTENSIONS,
6+
EXECUTABLE_EXTENSIONS,
7+
FONT_EXTENSIONS,
8+
isBinaryFile,
9+
} from "./binary";
110
export {
211
CLOUD_PROMPT_PREFIX,
312
deserializeCloudPrompt,
@@ -7,10 +16,12 @@ export {
716
export {
817
ALLOWED_IMAGE_MIME_TYPES,
918
buildImageDataUrl,
19+
CLAUDE_IMAGE_EXTENSIONS,
1020
type ClaudeImageMimeType,
1121
getImageMimeType,
1222
IMAGE_MIME_TYPES,
1323
isAllowedImageMimeType,
24+
isClaudeImageFile,
1425
isGifFile,
1526
isImageFile,
1627
isRasterImageFile,

0 commit comments

Comments
 (0)