[ide-mode] Thread active file through to system prompt (#4264)
This commit is contained in:
parent
b61016f2a5
commit
222e362fc2
|
@ -346,7 +346,7 @@ describe('Gemini Client (client.ts)', () => {
|
||||||
model: 'test-model',
|
model: 'test-model',
|
||||||
config: {
|
config: {
|
||||||
abortSignal,
|
abortSignal,
|
||||||
systemInstruction: getCoreSystemPrompt(''),
|
systemInstruction: getCoreSystemPrompt(client['config'], ''),
|
||||||
temperature: 0.5,
|
temperature: 0.5,
|
||||||
topP: 1,
|
topP: 1,
|
||||||
},
|
},
|
||||||
|
@ -374,7 +374,7 @@ describe('Gemini Client (client.ts)', () => {
|
||||||
model: 'test-model', // Should use current model from config
|
model: 'test-model', // Should use current model from config
|
||||||
config: {
|
config: {
|
||||||
abortSignal,
|
abortSignal,
|
||||||
systemInstruction: getCoreSystemPrompt(''),
|
systemInstruction: getCoreSystemPrompt(client['config'], ''),
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
topP: 1,
|
topP: 1,
|
||||||
responseSchema: schema,
|
responseSchema: schema,
|
||||||
|
@ -409,7 +409,7 @@ describe('Gemini Client (client.ts)', () => {
|
||||||
model: customModel,
|
model: customModel,
|
||||||
config: {
|
config: {
|
||||||
abortSignal,
|
abortSignal,
|
||||||
systemInstruction: getCoreSystemPrompt(''),
|
systemInstruction: getCoreSystemPrompt(client['config'], ''),
|
||||||
temperature: 0.9,
|
temperature: 0.9,
|
||||||
topP: 1, // from default
|
topP: 1, // from default
|
||||||
topK: 20,
|
topK: 20,
|
||||||
|
|
|
@ -238,7 +238,7 @@ export class GeminiClient {
|
||||||
];
|
];
|
||||||
try {
|
try {
|
||||||
const userMemory = this.config.getUserMemory();
|
const userMemory = this.config.getUserMemory();
|
||||||
const systemInstruction = getCoreSystemPrompt(userMemory);
|
const systemInstruction = getCoreSystemPrompt(this.config, userMemory);
|
||||||
const generateContentConfigWithThinking = isThinkingSupported(
|
const generateContentConfigWithThinking = isThinkingSupported(
|
||||||
this.config.getModel(),
|
this.config.getModel(),
|
||||||
)
|
)
|
||||||
|
@ -354,7 +354,7 @@ export class GeminiClient {
|
||||||
model || this.config.getModel() || DEFAULT_GEMINI_FLASH_MODEL;
|
model || this.config.getModel() || DEFAULT_GEMINI_FLASH_MODEL;
|
||||||
try {
|
try {
|
||||||
const userMemory = this.config.getUserMemory();
|
const userMemory = this.config.getUserMemory();
|
||||||
const systemInstruction = getCoreSystemPrompt(userMemory);
|
const systemInstruction = getCoreSystemPrompt(this.config, userMemory);
|
||||||
const requestConfig = {
|
const requestConfig = {
|
||||||
abortSignal,
|
abortSignal,
|
||||||
...this.generateContentConfig,
|
...this.generateContentConfig,
|
||||||
|
@ -447,7 +447,7 @@ export class GeminiClient {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userMemory = this.config.getUserMemory();
|
const userMemory = this.config.getUserMemory();
|
||||||
const systemInstruction = getCoreSystemPrompt(userMemory);
|
const systemInstruction = getCoreSystemPrompt(this.config, userMemory);
|
||||||
|
|
||||||
const requestConfig = {
|
const requestConfig = {
|
||||||
abortSignal,
|
abortSignal,
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { getCoreSystemPrompt } from './prompts.js';
|
import { getCoreSystemPrompt } from './prompts.js';
|
||||||
import { isGitRepository } from '../utils/gitUtils.js';
|
import { isGitRepository } from '../utils/gitUtils.js';
|
||||||
|
import { Config } from '../config/config.js';
|
||||||
|
|
||||||
// Mock tool names if they are dynamically generated or complex
|
// Mock tool names if they are dynamically generated or complex
|
||||||
vi.mock('../tools/ls', () => ({ LSTool: { Name: 'list_directory' } }));
|
vi.mock('../tools/ls', () => ({ LSTool: { Name: 'list_directory' } }));
|
||||||
|
@ -26,11 +27,25 @@ vi.mock('../tools/write-file', () => ({
|
||||||
vi.mock('../utils/gitUtils', () => ({
|
vi.mock('../utils/gitUtils', () => ({
|
||||||
isGitRepository: vi.fn(),
|
isGitRepository: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
vi.mock('../config/config.js');
|
||||||
|
|
||||||
describe('Core System Prompt (prompts.ts)', () => {
|
describe('Core System Prompt (prompts.ts)', () => {
|
||||||
|
let mockConfig: Config;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const MockedConfig = vi.mocked(Config, true);
|
||||||
|
MockedConfig.mockImplementation(() => {
|
||||||
|
const mock = {
|
||||||
|
getIdeMode: vi.fn().mockReturnValue(false),
|
||||||
|
};
|
||||||
|
return mock as unknown as Config;
|
||||||
|
});
|
||||||
|
mockConfig = new Config({} as never);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return the base prompt when no userMemory is provided', () => {
|
it('should return the base prompt when no userMemory is provided', () => {
|
||||||
vi.stubEnv('SANDBOX', undefined);
|
vi.stubEnv('SANDBOX', undefined);
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt(mockConfig);
|
||||||
expect(prompt).not.toContain('---\n\n'); // Separator should not be present
|
expect(prompt).not.toContain('---\n\n'); // Separator should not be present
|
||||||
expect(prompt).toContain('You are an interactive CLI agent'); // Check for core content
|
expect(prompt).toContain('You are an interactive CLI agent'); // Check for core content
|
||||||
expect(prompt).toMatchSnapshot(); // Use snapshot for base prompt structure
|
expect(prompt).toMatchSnapshot(); // Use snapshot for base prompt structure
|
||||||
|
@ -38,7 +53,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
|
|
||||||
it('should return the base prompt when userMemory is empty string', () => {
|
it('should return the base prompt when userMemory is empty string', () => {
|
||||||
vi.stubEnv('SANDBOX', undefined);
|
vi.stubEnv('SANDBOX', undefined);
|
||||||
const prompt = getCoreSystemPrompt('');
|
const prompt = getCoreSystemPrompt(mockConfig, '');
|
||||||
expect(prompt).not.toContain('---\n\n');
|
expect(prompt).not.toContain('---\n\n');
|
||||||
expect(prompt).toContain('You are an interactive CLI agent');
|
expect(prompt).toContain('You are an interactive CLI agent');
|
||||||
expect(prompt).toMatchSnapshot();
|
expect(prompt).toMatchSnapshot();
|
||||||
|
@ -46,7 +61,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
|
|
||||||
it('should return the base prompt when userMemory is whitespace only', () => {
|
it('should return the base prompt when userMemory is whitespace only', () => {
|
||||||
vi.stubEnv('SANDBOX', undefined);
|
vi.stubEnv('SANDBOX', undefined);
|
||||||
const prompt = getCoreSystemPrompt(' \n \t ');
|
const prompt = getCoreSystemPrompt(mockConfig, ' \n \t ');
|
||||||
expect(prompt).not.toContain('---\n\n');
|
expect(prompt).not.toContain('---\n\n');
|
||||||
expect(prompt).toContain('You are an interactive CLI agent');
|
expect(prompt).toContain('You are an interactive CLI agent');
|
||||||
expect(prompt).toMatchSnapshot();
|
expect(prompt).toMatchSnapshot();
|
||||||
|
@ -56,7 +71,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
vi.stubEnv('SANDBOX', undefined);
|
vi.stubEnv('SANDBOX', undefined);
|
||||||
const memory = 'This is custom user memory.\nBe extra polite.';
|
const memory = 'This is custom user memory.\nBe extra polite.';
|
||||||
const expectedSuffix = `\n\n---\n\n${memory}`;
|
const expectedSuffix = `\n\n---\n\n${memory}`;
|
||||||
const prompt = getCoreSystemPrompt(memory);
|
const prompt = getCoreSystemPrompt(mockConfig, memory);
|
||||||
|
|
||||||
expect(prompt.endsWith(expectedSuffix)).toBe(true);
|
expect(prompt.endsWith(expectedSuffix)).toBe(true);
|
||||||
expect(prompt).toContain('You are an interactive CLI agent'); // Ensure base prompt follows
|
expect(prompt).toContain('You are an interactive CLI agent'); // Ensure base prompt follows
|
||||||
|
@ -65,7 +80,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
|
|
||||||
it('should include sandbox-specific instructions when SANDBOX env var is set', () => {
|
it('should include sandbox-specific instructions when SANDBOX env var is set', () => {
|
||||||
vi.stubEnv('SANDBOX', 'true'); // Generic sandbox value
|
vi.stubEnv('SANDBOX', 'true'); // Generic sandbox value
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt(mockConfig);
|
||||||
expect(prompt).toContain('# Sandbox');
|
expect(prompt).toContain('# Sandbox');
|
||||||
expect(prompt).not.toContain('# MacOS Seatbelt');
|
expect(prompt).not.toContain('# MacOS Seatbelt');
|
||||||
expect(prompt).not.toContain('# Outside of Sandbox');
|
expect(prompt).not.toContain('# Outside of Sandbox');
|
||||||
|
@ -74,7 +89,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
|
|
||||||
it('should include seatbelt-specific instructions when SANDBOX env var is "sandbox-exec"', () => {
|
it('should include seatbelt-specific instructions when SANDBOX env var is "sandbox-exec"', () => {
|
||||||
vi.stubEnv('SANDBOX', 'sandbox-exec');
|
vi.stubEnv('SANDBOX', 'sandbox-exec');
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt(mockConfig);
|
||||||
expect(prompt).toContain('# MacOS Seatbelt');
|
expect(prompt).toContain('# MacOS Seatbelt');
|
||||||
expect(prompt).not.toContain('# Sandbox');
|
expect(prompt).not.toContain('# Sandbox');
|
||||||
expect(prompt).not.toContain('# Outside of Sandbox');
|
expect(prompt).not.toContain('# Outside of Sandbox');
|
||||||
|
@ -83,7 +98,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
|
|
||||||
it('should include non-sandbox instructions when SANDBOX env var is not set', () => {
|
it('should include non-sandbox instructions when SANDBOX env var is not set', () => {
|
||||||
vi.stubEnv('SANDBOX', undefined); // Ensure it's not set
|
vi.stubEnv('SANDBOX', undefined); // Ensure it's not set
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt(mockConfig);
|
||||||
expect(prompt).toContain('# Outside of Sandbox');
|
expect(prompt).toContain('# Outside of Sandbox');
|
||||||
expect(prompt).not.toContain('# Sandbox');
|
expect(prompt).not.toContain('# Sandbox');
|
||||||
expect(prompt).not.toContain('# MacOS Seatbelt');
|
expect(prompt).not.toContain('# MacOS Seatbelt');
|
||||||
|
@ -93,7 +108,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
it('should include git instructions when in a git repo', () => {
|
it('should include git instructions when in a git repo', () => {
|
||||||
vi.stubEnv('SANDBOX', undefined);
|
vi.stubEnv('SANDBOX', undefined);
|
||||||
vi.mocked(isGitRepository).mockReturnValue(true);
|
vi.mocked(isGitRepository).mockReturnValue(true);
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt(mockConfig);
|
||||||
expect(prompt).toContain('# Git Repository');
|
expect(prompt).toContain('# Git Repository');
|
||||||
expect(prompt).toMatchSnapshot();
|
expect(prompt).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -101,7 +116,7 @@ describe('Core System Prompt (prompts.ts)', () => {
|
||||||
it('should not include git instructions when not in a git repo', () => {
|
it('should not include git instructions when not in a git repo', () => {
|
||||||
vi.stubEnv('SANDBOX', undefined);
|
vi.stubEnv('SANDBOX', undefined);
|
||||||
vi.mocked(isGitRepository).mockReturnValue(false);
|
vi.mocked(isGitRepository).mockReturnValue(false);
|
||||||
const prompt = getCoreSystemPrompt();
|
const prompt = getCoreSystemPrompt(mockConfig);
|
||||||
expect(prompt).not.toContain('# Git Repository');
|
expect(prompt).not.toContain('# Git Repository');
|
||||||
expect(prompt).toMatchSnapshot();
|
expect(prompt).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,8 +17,13 @@ import { WriteFileTool } from '../tools/write-file.js';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { isGitRepository } from '../utils/gitUtils.js';
|
import { isGitRepository } from '../utils/gitUtils.js';
|
||||||
import { MemoryTool, GEMINI_CONFIG_DIR } from '../tools/memoryTool.js';
|
import { MemoryTool, GEMINI_CONFIG_DIR } from '../tools/memoryTool.js';
|
||||||
|
import { Config } from '../config/config.js';
|
||||||
|
import { ideContext } from '../services/ideContext.js';
|
||||||
|
|
||||||
export function getCoreSystemPrompt(userMemory?: string): string {
|
export function getCoreSystemPrompt(
|
||||||
|
config: Config,
|
||||||
|
userMemory?: string,
|
||||||
|
): string {
|
||||||
// if GEMINI_SYSTEM_MD is set (and not 0|false), override system prompt from file
|
// if GEMINI_SYSTEM_MD is set (and not 0|false), override system prompt from file
|
||||||
// default path is .gemini/system.md but can be modified via custom path in GEMINI_SYSTEM_MD
|
// default path is .gemini/system.md but can be modified via custom path in GEMINI_SYSTEM_MD
|
||||||
let systemMdEnabled = false;
|
let systemMdEnabled = false;
|
||||||
|
@ -153,7 +158,25 @@ ${(function () {
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
})()}
|
})()}
|
||||||
|
${(function () {
|
||||||
|
if (config.getIdeMode()) {
|
||||||
|
const activeFile = ideContext.getActiveFileContext();
|
||||||
|
if (activeFile?.filePath) {
|
||||||
|
let prompt = `
|
||||||
|
# IDE Mode
|
||||||
|
You are running in IDE mode. The user has the following file open:
|
||||||
|
- Path: ${activeFile.filePath}`;
|
||||||
|
if (activeFile.cursor) {
|
||||||
|
prompt += `
|
||||||
|
- Cursor Position: Line ${activeFile.cursor.line}, Character ${activeFile.cursor.character}`;
|
||||||
|
}
|
||||||
|
prompt += `
|
||||||
|
Focus on providing contextually relevant assistance for this file.`;
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
})()}
|
||||||
# Examples (Illustrating Tone and Workflow)
|
# Examples (Illustrating Tone and Workflow)
|
||||||
<example>
|
<example>
|
||||||
user: 1 + 2
|
user: 1 + 2
|
||||||
|
|
Loading…
Reference in New Issue