Skip to content

Commit 2ac9984

Browse files
committed
fix: address PR #6150 review feedback
- Consolidate duplicate getMimeType functions into shared utilities - Remove duplicate MediaThumbnails component, enhance Thumbnails to support video - Add JSDoc comments to VideoContentBlock interface - Convert inline styles to Tailwind classes in ChatRow - Add robust error handling for video processing - Create centralized media configuration for accepted file types - Ensure consistent test naming conventions - Fix ESLint warnings
1 parent ec674d2 commit 2ac9984

File tree

12 files changed

+378
-276
lines changed

12 files changed

+378
-276
lines changed

src/api/transform/gemini-format.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import { Anthropic } from "@anthropic-ai/sdk"
22
import { Content, Part } from "@google/genai"
33

4-
// Extended type to support video content blocks that aren't in the standard Anthropic SDK
4+
/**
5+
* Extended content block type to support video content that isn't in the standard Anthropic SDK.
6+
* This interface extends the standard Anthropic content blocks to include video support for Gemini models.
7+
*
8+
* @interface VideoContentBlock
9+
* @property {string} type - Must be "video" to identify this as a video content block
10+
* @property {Object} source - The video source information
11+
* @property {string} source.type - Must be "base64" for base64-encoded video data
12+
* @property {string} source.data - The base64-encoded video data
13+
* @property {string} source.media_type - The MIME type of the video (e.g., "video/mp4", "video/webm")
14+
*/
515
interface VideoContentBlock {
616
type: "video"
717
source: {
@@ -11,6 +21,10 @@ interface VideoContentBlock {
1121
}
1222
}
1323

24+
/**
25+
* Extended content block parameter type that includes both standard Anthropic content blocks
26+
* and our custom video content block for Gemini model support.
27+
*/
1428
type ExtendedContentBlockParam = Anthropic.ContentBlockParam | VideoContentBlock
1529

1630
export function convertAnthropicContentToGemini(content: string | ExtendedContentBlockParam[]): Part[] {
@@ -28,11 +42,39 @@ export function convertAnthropicContentToGemini(content: string | ExtendedConten
2842
}
2943

3044
return { inlineData: { data: block.source.data, mimeType: block.source.media_type } }
31-
case "video":
45+
case "video": {
3246
if (block.source.type !== "base64") {
33-
throw new Error("Unsupported video source type")
47+
throw new Error("Unsupported video source type. Only base64 encoded videos are supported.")
3448
}
49+
50+
// Validate video MIME type
51+
const supportedVideoTypes = ["video/mp4", "video/webm", "video/ogg", "video/quicktime"]
52+
if (!supportedVideoTypes.includes(block.source.media_type)) {
53+
throw new Error(
54+
`Unsupported video format: ${block.source.media_type}. Supported formats: ${supportedVideoTypes.join(", ")}`,
55+
)
56+
}
57+
58+
// Check if video data exists
59+
if (!block.source.data || block.source.data.trim() === "") {
60+
throw new Error("Video data is empty or missing")
61+
}
62+
63+
// Validate base64 format
64+
try {
65+
// Basic validation - check if it's valid base64
66+
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/
67+
if (!base64Regex.test(block.source.data.replace(/\s/g, ""))) {
68+
throw new Error("Invalid base64 format for video data")
69+
}
70+
} catch (e) {
71+
throw new Error(
72+
`Failed to validate video data: ${e instanceof Error ? e.message : "Unknown error"}`,
73+
)
74+
}
75+
3576
return { inlineData: { data: block.source.data, mimeType: block.source.media_type } }
77+
}
3678
case "tool_use":
3779
return {
3880
functionCall: {
Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from "vscode"
22
import fs from "fs/promises"
3-
import * as path from "path"
3+
import { getMimeType } from "../../shared/utils/media"
44

55
export async function selectImages(): Promise<string[]> {
66
const options: vscode.OpenDialogOptions = {
@@ -23,23 +23,11 @@ export async function selectImages(): Promise<string[]> {
2323
const buffer = await fs.readFile(imagePath)
2424
const base64 = buffer.toString("base64")
2525
const mimeType = getMimeType(imagePath)
26+
if (!mimeType) {
27+
throw new Error(`Unsupported file type: ${imagePath}`)
28+
}
2629
const dataUrl = `data:${mimeType};base64,${base64}`
2730
return dataUrl
2831
}),
2932
)
3033
}
31-
32-
function getMimeType(filePath: string): string {
33-
const ext = path.extname(filePath).toLowerCase()
34-
switch (ext) {
35-
case ".png":
36-
return "image/png"
37-
case ".jpeg":
38-
case ".jpg":
39-
return "image/jpeg"
40-
case ".webp":
41-
return "image/webp"
42-
default:
43-
throw new Error(`Unsupported file type: ${ext}`)
44-
}
45-
}

src/shared/utils/media.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as path from "path"
2+
3+
/**
4+
* Get MIME type from either a file path or a data URI
5+
* @param input - Either a file path or a data URI
6+
* @returns The MIME type or null if not found
7+
*/
8+
export function getMimeType(input: string): string | null {
9+
// Check if it's a data URI
10+
if (input.startsWith("data:")) {
11+
const match = input.match(/^data:(.*?);/)
12+
return match ? match[1] : null
13+
}
14+
15+
// Otherwise, treat it as a file path
16+
const ext = path.extname(input).toLowerCase()
17+
switch (ext) {
18+
case ".png":
19+
return "image/png"
20+
case ".jpeg":
21+
case ".jpg":
22+
return "image/jpeg"
23+
case ".webp":
24+
return "image/webp"
25+
case ".mp4":
26+
return "video/mp4"
27+
case ".webm":
28+
return "video/webm"
29+
case ".ogg":
30+
return "video/ogg"
31+
case ".mov":
32+
return "video/quicktime"
33+
default:
34+
return null
35+
}
36+
}
37+
38+
/**
39+
* Check if a MIME type represents a video
40+
* @param mimeType - The MIME type to check
41+
* @returns True if it's a video MIME type
42+
*/
43+
export function isVideoMimeType(mimeType: string | null): boolean {
44+
return mimeType?.startsWith("video/") ?? false
45+
}
46+
47+
/**
48+
* Check if a MIME type represents an image
49+
* @param mimeType - The MIME type to check
50+
* @returns True if it's an image MIME type
51+
*/
52+
export function isImageMimeType(mimeType: string | null): boolean {
53+
return mimeType?.startsWith("image/") ?? false
54+
}

0 commit comments

Comments
 (0)