mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-01 07:24:38 -07:00
Merge branch 'main' into mk-packing
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ../../scripts/build_package.js",
|
||||
"clean": "rm -rf dist",
|
||||
"start": "node dist/index.js",
|
||||
"debug": "node --inspect-brk dist/index.js",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
LoadedSettings,
|
||||
loadSettings,
|
||||
SettingScope,
|
||||
USER_SETTINGS_PATH,
|
||||
} from './config/settings.js';
|
||||
import { themeManager } from './ui/themes/theme-manager.js';
|
||||
import { getStartupWarnings } from './utils/startupWarnings.js';
|
||||
@@ -279,7 +280,7 @@ async function validateNonInterActiveAuth(
|
||||
// still expect that exists
|
||||
if (!selectedAuthType && !process.env.GEMINI_API_KEY) {
|
||||
console.error(
|
||||
'Please set an Auth method in your .gemini/settings.json OR specify GEMINI_API_KEY env variable file before running',
|
||||
`Please set an Auth method in your ${USER_SETTINGS_PATH} OR specify GEMINI_API_KEY env variable file before running`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -278,7 +278,10 @@ function visitBoxRow(element: React.ReactNode): Row {
|
||||
// Allow the key prop, which is automatically added by React.
|
||||
maxExpectedProps += 1;
|
||||
}
|
||||
if (boxProps.flexDirection !== 'row') {
|
||||
if (
|
||||
boxProps.flexDirection !== undefined &&
|
||||
boxProps.flexDirection !== 'row'
|
||||
) {
|
||||
debugReportError(
|
||||
'MaxSizedBox children must have flexDirection="row".',
|
||||
element,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "node ../../scripts/build_package.js",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"format": "prettier --write .",
|
||||
"test": "vitest run",
|
||||
|
||||
@@ -14,6 +14,33 @@ import fs from 'fs'; // Actual fs for setup
|
||||
import os from 'os';
|
||||
import { Config } from '../config/config.js';
|
||||
|
||||
vi.mock('mime-types', () => {
|
||||
const lookup = (filename: string) => {
|
||||
if (filename.endsWith('.ts') || filename.endsWith('.js')) {
|
||||
return 'text/plain';
|
||||
}
|
||||
if (filename.endsWith('.png')) {
|
||||
return 'image/png';
|
||||
}
|
||||
if (filename.endsWith('.pdf')) {
|
||||
return 'application/pdf';
|
||||
}
|
||||
if (filename.endsWith('.mp3') || filename.endsWith('.wav')) {
|
||||
return 'audio/mpeg';
|
||||
}
|
||||
if (filename.endsWith('.mp4') || filename.endsWith('.mov')) {
|
||||
return 'video/mp4';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return {
|
||||
default: {
|
||||
lookup,
|
||||
},
|
||||
lookup,
|
||||
};
|
||||
});
|
||||
|
||||
describe('ReadManyFilesTool', () => {
|
||||
let tool: ReadManyFilesTool;
|
||||
let tempRootDir: string;
|
||||
|
||||
@@ -211,6 +211,16 @@ describe('fileUtils', () => {
|
||||
expect(detectFileType('file.pdf')).toBe('pdf');
|
||||
});
|
||||
|
||||
it('should detect audio type by extension', () => {
|
||||
mockMimeLookup.mockReturnValueOnce('audio/mpeg');
|
||||
expect(detectFileType('song.mp3')).toBe('audio');
|
||||
});
|
||||
|
||||
it('should detect video type by extension', () => {
|
||||
mockMimeLookup.mockReturnValueOnce('video/mp4');
|
||||
expect(detectFileType('movie.mp4')).toBe('video');
|
||||
});
|
||||
|
||||
it('should detect known binary extensions as binary (e.g. .zip)', () => {
|
||||
mockMimeLookup.mockReturnValueOnce('application/zip');
|
||||
expect(detectFileType('archive.zip')).toBe('binary');
|
||||
@@ -427,5 +437,23 @@ describe('fileUtils', () => {
|
||||
);
|
||||
expect(result.isTruncated).toBe(true);
|
||||
});
|
||||
|
||||
it('should return an error if the file size exceeds 20MB', async () => {
|
||||
// Create a file just over 20MB
|
||||
const twentyOneMB = 21 * 1024 * 1024;
|
||||
const buffer = Buffer.alloc(twentyOneMB, 0x61); // Fill with 'a'
|
||||
actualNodeFs.writeFileSync(testTextFilePath, buffer);
|
||||
|
||||
const result = await processSingleFileContent(
|
||||
testTextFilePath,
|
||||
tempRootDir,
|
||||
);
|
||||
|
||||
expect(result.error).toContain('File size exceeds the 20MB limit');
|
||||
expect(result.returnDisplay).toContain(
|
||||
'File size exceeds the 20MB limit',
|
||||
);
|
||||
expect(result.llmContent).toContain('File size exceeds the 20MB limit');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -94,19 +94,27 @@ export function isBinaryFile(filePath: string): boolean {
|
||||
/**
|
||||
* Detects the type of file based on extension and content.
|
||||
* @param filePath Path to the file.
|
||||
* @returns 'text', 'image', 'pdf', or 'binary'.
|
||||
* @returns 'text', 'image', 'pdf', 'audio', 'video', or 'binary'.
|
||||
*/
|
||||
export function detectFileType(
|
||||
filePath: string,
|
||||
): 'text' | 'image' | 'pdf' | 'binary' {
|
||||
): 'text' | 'image' | 'pdf' | 'audio' | 'video' | 'binary' {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const lookedUpMimeType = mime.lookup(filePath); // Returns false if not found, or the mime type string
|
||||
|
||||
if (lookedUpMimeType && lookedUpMimeType.startsWith('image/')) {
|
||||
return 'image';
|
||||
}
|
||||
if (lookedUpMimeType && lookedUpMimeType === 'application/pdf') {
|
||||
return 'pdf';
|
||||
if (lookedUpMimeType) {
|
||||
if (lookedUpMimeType.startsWith('image/')) {
|
||||
return 'image';
|
||||
}
|
||||
if (lookedUpMimeType.startsWith('audio/')) {
|
||||
return 'audio';
|
||||
}
|
||||
if (lookedUpMimeType.startsWith('video/')) {
|
||||
return 'video';
|
||||
}
|
||||
if (lookedUpMimeType === 'application/pdf') {
|
||||
return 'pdf';
|
||||
}
|
||||
}
|
||||
|
||||
// Stricter binary check for common non-text extensions before content check
|
||||
@@ -187,7 +195,7 @@ export async function processSingleFileContent(
|
||||
error: `File not found: ${filePath}`,
|
||||
};
|
||||
}
|
||||
const stats = fs.statSync(filePath); // Sync check
|
||||
const stats = await fs.promises.stat(filePath);
|
||||
if (stats.isDirectory()) {
|
||||
return {
|
||||
llmContent: '',
|
||||
@@ -196,6 +204,19 @@ export async function processSingleFileContent(
|
||||
};
|
||||
}
|
||||
|
||||
const fileSizeInBytes = stats.size;
|
||||
// 20MB limit
|
||||
const maxFileSize = 20 * 1024 * 1024;
|
||||
|
||||
if (fileSizeInBytes > maxFileSize) {
|
||||
throw new Error(
|
||||
`File size exceeds the 20MB limit: ${filePath} (${(
|
||||
fileSizeInBytes /
|
||||
(1024 * 1024)
|
||||
).toFixed(2)}MB)`,
|
||||
);
|
||||
}
|
||||
|
||||
const fileType = detectFileType(filePath);
|
||||
const relativePathForDisplay = path
|
||||
.relative(rootDirectory, filePath)
|
||||
@@ -253,7 +274,9 @@ export async function processSingleFileContent(
|
||||
};
|
||||
}
|
||||
case 'image':
|
||||
case 'pdf': {
|
||||
case 'pdf':
|
||||
case 'audio':
|
||||
case 'video': {
|
||||
const contentBuffer = await fs.promises.readFile(filePath);
|
||||
const base64Data = contentBuffer.toString('base64');
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user