From 222e362fc2b390ae8259de8d9507b8c239fb59e4 Mon Sep 17 00:00:00 2001 From: christine betts Date: Tue, 15 Jul 2025 22:20:00 +0000 Subject: [PATCH] [ide-mode] Thread active file through to system prompt (#4264) --- packages/core/src/core/client.test.ts | 6 ++--- packages/core/src/core/client.ts | 6 ++--- packages/core/src/core/prompts.test.ts | 35 ++++++++++++++++++-------- packages/core/src/core/prompts.ts | 27 ++++++++++++++++++-- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index 03793bda..1f9d4e31 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -346,7 +346,7 @@ describe('Gemini Client (client.ts)', () => { model: 'test-model', config: { abortSignal, - systemInstruction: getCoreSystemPrompt(''), + systemInstruction: getCoreSystemPrompt(client['config'], ''), temperature: 0.5, topP: 1, }, @@ -374,7 +374,7 @@ describe('Gemini Client (client.ts)', () => { model: 'test-model', // Should use current model from config config: { abortSignal, - systemInstruction: getCoreSystemPrompt(''), + systemInstruction: getCoreSystemPrompt(client['config'], ''), temperature: 0, topP: 1, responseSchema: schema, @@ -409,7 +409,7 @@ describe('Gemini Client (client.ts)', () => { model: customModel, config: { abortSignal, - systemInstruction: getCoreSystemPrompt(''), + systemInstruction: getCoreSystemPrompt(client['config'], ''), temperature: 0.9, topP: 1, // from default topK: 20, diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 80979812..1163a2f0 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -238,7 +238,7 @@ export class GeminiClient { ]; try { const userMemory = this.config.getUserMemory(); - const systemInstruction = getCoreSystemPrompt(userMemory); + const systemInstruction = getCoreSystemPrompt(this.config, userMemory); const generateContentConfigWithThinking = isThinkingSupported( this.config.getModel(), ) @@ -354,7 +354,7 @@ export class GeminiClient { model || this.config.getModel() || DEFAULT_GEMINI_FLASH_MODEL; try { const userMemory = this.config.getUserMemory(); - const systemInstruction = getCoreSystemPrompt(userMemory); + const systemInstruction = getCoreSystemPrompt(this.config, userMemory); const requestConfig = { abortSignal, ...this.generateContentConfig, @@ -447,7 +447,7 @@ export class GeminiClient { try { const userMemory = this.config.getUserMemory(); - const systemInstruction = getCoreSystemPrompt(userMemory); + const systemInstruction = getCoreSystemPrompt(this.config, userMemory); const requestConfig = { abortSignal, diff --git a/packages/core/src/core/prompts.test.ts b/packages/core/src/core/prompts.test.ts index bb7b0b52..6a7f0f8a 100644 --- a/packages/core/src/core/prompts.test.ts +++ b/packages/core/src/core/prompts.test.ts @@ -4,9 +4,10 @@ * 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 { isGitRepository } from '../utils/gitUtils.js'; +import { Config } from '../config/config.js'; // Mock tool names if they are dynamically generated or complex vi.mock('../tools/ls', () => ({ LSTool: { Name: 'list_directory' } })); @@ -26,11 +27,25 @@ vi.mock('../tools/write-file', () => ({ vi.mock('../utils/gitUtils', () => ({ isGitRepository: vi.fn(), })); +vi.mock('../config/config.js'); 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', () => { vi.stubEnv('SANDBOX', undefined); - const prompt = getCoreSystemPrompt(); + const prompt = getCoreSystemPrompt(mockConfig); 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).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', () => { vi.stubEnv('SANDBOX', undefined); - const prompt = getCoreSystemPrompt(''); + const prompt = getCoreSystemPrompt(mockConfig, ''); expect(prompt).not.toContain('---\n\n'); expect(prompt).toContain('You are an interactive CLI agent'); expect(prompt).toMatchSnapshot(); @@ -46,7 +61,7 @@ describe('Core System Prompt (prompts.ts)', () => { it('should return the base prompt when userMemory is whitespace only', () => { vi.stubEnv('SANDBOX', undefined); - const prompt = getCoreSystemPrompt(' \n \t '); + const prompt = getCoreSystemPrompt(mockConfig, ' \n \t '); expect(prompt).not.toContain('---\n\n'); expect(prompt).toContain('You are an interactive CLI agent'); expect(prompt).toMatchSnapshot(); @@ -56,7 +71,7 @@ describe('Core System Prompt (prompts.ts)', () => { vi.stubEnv('SANDBOX', undefined); const memory = 'This is custom user memory.\nBe extra polite.'; const expectedSuffix = `\n\n---\n\n${memory}`; - const prompt = getCoreSystemPrompt(memory); + const prompt = getCoreSystemPrompt(mockConfig, memory); expect(prompt.endsWith(expectedSuffix)).toBe(true); 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', () => { vi.stubEnv('SANDBOX', 'true'); // Generic sandbox value - const prompt = getCoreSystemPrompt(); + const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).toContain('# Sandbox'); expect(prompt).not.toContain('# MacOS Seatbelt'); 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"', () => { vi.stubEnv('SANDBOX', 'sandbox-exec'); - const prompt = getCoreSystemPrompt(); + const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).toContain('# MacOS Seatbelt'); expect(prompt).not.toContain('# 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', () => { vi.stubEnv('SANDBOX', undefined); // Ensure it's not set - const prompt = getCoreSystemPrompt(); + const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).toContain('# Outside of Sandbox'); expect(prompt).not.toContain('# Sandbox'); 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', () => { vi.stubEnv('SANDBOX', undefined); vi.mocked(isGitRepository).mockReturnValue(true); - const prompt = getCoreSystemPrompt(); + const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).toContain('# Git Repository'); 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', () => { vi.stubEnv('SANDBOX', undefined); vi.mocked(isGitRepository).mockReturnValue(false); - const prompt = getCoreSystemPrompt(); + const prompt = getCoreSystemPrompt(mockConfig); expect(prompt).not.toContain('# Git Repository'); expect(prompt).toMatchSnapshot(); }); diff --git a/packages/core/src/core/prompts.ts b/packages/core/src/core/prompts.ts index 3b23f735..4a0c7c6c 100644 --- a/packages/core/src/core/prompts.ts +++ b/packages/core/src/core/prompts.ts @@ -17,8 +17,13 @@ import { WriteFileTool } from '../tools/write-file.js'; import process from 'node:process'; import { isGitRepository } from '../utils/gitUtils.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 // default path is .gemini/system.md but can be modified via custom path in GEMINI_SYSTEM_MD let systemMdEnabled = false; @@ -153,7 +158,25 @@ ${(function () { } 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) user: 1 + 2