feat(core): Land ContextCompressionService (#24483)

This commit is contained in:
joshualitt
2026-04-02 09:22:04 -07:00
committed by GitHub
parent 826bc3359a
commit aee2dd8577
32 changed files with 1160 additions and 229 deletions
+17 -15
View File
@@ -7,21 +7,23 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { discoverJitContext, appendJitContext } from './jit-context.js';
import type { Config } from '../config/config.js';
import type { ContextManager } from '../context/contextManager.js';
import type { MemoryContextManager } from '../context/memoryContextManager.js';
describe('jit-context', () => {
describe('discoverJitContext', () => {
let mockConfig: Config;
let mockContextManager: ContextManager;
let mockMemoryContextManager: MemoryContextManager;
beforeEach(() => {
mockContextManager = {
mockMemoryContextManager = {
discoverContext: vi.fn().mockResolvedValue(''),
} as unknown as ContextManager;
} as unknown as MemoryContextManager;
mockConfig = {
isJitContextEnabled: vi.fn().mockReturnValue(false),
getContextManager: vi.fn().mockReturnValue(mockContextManager),
getMemoryContextManager: vi
.fn()
.mockReturnValue(mockMemoryContextManager),
getWorkspaceContext: vi.fn().mockReturnValue({
getDirectories: vi.fn().mockReturnValue(['/app']),
}),
@@ -34,27 +36,27 @@ describe('jit-context', () => {
const result = await discoverJitContext(mockConfig, '/app/src/file.ts');
expect(result).toBe('');
expect(mockContextManager.discoverContext).not.toHaveBeenCalled();
expect(mockMemoryContextManager.discoverContext).not.toHaveBeenCalled();
});
it('should return empty string when contextManager is undefined', async () => {
it('should return empty string when memoryContextManager is undefined', async () => {
vi.mocked(mockConfig.isJitContextEnabled).mockReturnValue(true);
vi.mocked(mockConfig.getContextManager).mockReturnValue(undefined);
vi.mocked(mockConfig.getMemoryContextManager).mockReturnValue(undefined);
const result = await discoverJitContext(mockConfig, '/app/src/file.ts');
expect(result).toBe('');
});
it('should call contextManager.discoverContext with correct args when JIT is enabled', async () => {
it('should call memoryContextManager.discoverContext with correct args when JIT is enabled', async () => {
vi.mocked(mockConfig.isJitContextEnabled).mockReturnValue(true);
vi.mocked(mockContextManager.discoverContext).mockResolvedValue(
vi.mocked(mockMemoryContextManager.discoverContext).mockResolvedValue(
'Subdirectory context content',
);
const result = await discoverJitContext(mockConfig, '/app/src/file.ts');
expect(mockContextManager.discoverContext).toHaveBeenCalledWith(
expect(mockMemoryContextManager.discoverContext).toHaveBeenCalledWith(
'/app/src/file.ts',
['/app'],
);
@@ -66,11 +68,11 @@ describe('jit-context', () => {
vi.mocked(mockConfig.getWorkspaceContext).mockReturnValue({
getDirectories: vi.fn().mockReturnValue(['/app', '/lib']),
} as unknown as ReturnType<Config['getWorkspaceContext']>);
vi.mocked(mockContextManager.discoverContext).mockResolvedValue('');
vi.mocked(mockMemoryContextManager.discoverContext).mockResolvedValue('');
await discoverJitContext(mockConfig, '/app/src/file.ts');
expect(mockContextManager.discoverContext).toHaveBeenCalledWith(
expect(mockMemoryContextManager.discoverContext).toHaveBeenCalledWith(
'/app/src/file.ts',
['/app', '/lib'],
);
@@ -78,7 +80,7 @@ describe('jit-context', () => {
it('should return empty string when no new context is found', async () => {
vi.mocked(mockConfig.isJitContextEnabled).mockReturnValue(true);
vi.mocked(mockContextManager.discoverContext).mockResolvedValue('');
vi.mocked(mockMemoryContextManager.discoverContext).mockResolvedValue('');
const result = await discoverJitContext(mockConfig, '/app/src/file.ts');
@@ -87,7 +89,7 @@ describe('jit-context', () => {
it('should return empty string when discoverContext throws', async () => {
vi.mocked(mockConfig.isJitContextEnabled).mockReturnValue(true);
vi.mocked(mockContextManager.discoverContext).mockRejectedValue(
vi.mocked(mockMemoryContextManager.discoverContext).mockRejectedValue(
new Error('Permission denied'),
);
+6 -3
View File
@@ -25,15 +25,18 @@ export async function discoverJitContext(
return '';
}
const contextManager = config.getContextManager();
if (!contextManager) {
const memoryContextManager = config.getMemoryContextManager();
if (!memoryContextManager) {
return '';
}
const trustedRoots = [...config.getWorkspaceContext().getDirectories()];
try {
return await contextManager.discoverContext(accessedPath, trustedRoots);
return await memoryContextManager.discoverContext(
accessedPath,
trustedRoots,
);
} catch {
// JIT context is supplementary — never fail the tool's primary operation.
return '';
+5 -5
View File
@@ -293,7 +293,7 @@ describe('WebFetchTool', () => {
})),
},
isInteractive: () => false,
isAutoDistillationEnabled: vi.fn().mockReturnValue(false),
isContextManagementEnabled: vi.fn().mockReturnValue(false),
} as unknown as Config;
});
@@ -1120,8 +1120,8 @@ describe('WebFetchTool', () => {
expect(result.error?.type).toBe(ToolErrorType.WEB_FETCH_PROCESSING_ERROR);
});
it('should bypass truncation if isAutoDistillationEnabled is true', async () => {
vi.spyOn(mockConfig, 'isAutoDistillationEnabled').mockReturnValue(true);
it('should bypass truncation if isContextManagementEnabled is true', async () => {
vi.spyOn(mockConfig, 'isContextManagementEnabled').mockReturnValue(true);
const largeContent = 'a'.repeat(300000); // Larger than MAX_CONTENT_LENGTH (250000)
mockFetch('https://example.com/large-text', {
status: 200,
@@ -1136,8 +1136,8 @@ describe('WebFetchTool', () => {
expect((result.llmContent as string).length).toBe(300000); // No truncation
});
it('should truncate if isAutoDistillationEnabled is false', async () => {
vi.spyOn(mockConfig, 'isAutoDistillationEnabled').mockReturnValue(false);
it('should truncate if isContextManagementEnabled is false', async () => {
vi.spyOn(mockConfig, 'isContextManagementEnabled').mockReturnValue(false);
const largeContent = 'a'.repeat(300000); // Larger than MAX_CONTENT_LENGTH (250000)
mockFetch('https://example.com/large-text2', {
status: 200,
+6 -6
View File
@@ -338,7 +338,7 @@ class WebFetchToolInvocation extends BaseToolInvocation<
textContent = rawContent;
}
if (!this.context.config.isAutoDistillationEnabled()) {
if (!this.context.config.isContextManagementEnabled()) {
return truncateString(
textContent,
MAX_CONTENT_LENGTH,
@@ -413,7 +413,7 @@ class WebFetchToolInvocation extends BaseToolInvocation<
}
const finalContentsByUrl = new Map<string, string>();
if (this.context.config.isAutoDistillationEnabled()) {
if (this.context.config.isContextManagementEnabled()) {
successes.forEach((success) =>
finalContentsByUrl.set(success.url, success.content),
);
@@ -659,7 +659,7 @@ ${aggregatedContent}
if (status >= 400) {
let rawResponseText = bodyBuffer.toString('utf8');
if (!this.context.config.isAutoDistillationEnabled()) {
if (!this.context.config.isContextManagementEnabled()) {
rawResponseText = truncateString(
rawResponseText,
10000,
@@ -689,7 +689,7 @@ Response: ${rawResponseText}`;
lowContentType.includes('application/json')
) {
let text = bodyBuffer.toString('utf8');
if (!this.context.config.isAutoDistillationEnabled()) {
if (!this.context.config.isContextManagementEnabled()) {
text = truncateString(text, MAX_CONTENT_LENGTH, TRUNCATION_WARNING);
}
return {
@@ -706,7 +706,7 @@ Response: ${rawResponseText}`;
{ selector: 'a', options: { ignoreHref: false, baseUrl: url } },
],
});
if (!this.context.config.isAutoDistillationEnabled()) {
if (!this.context.config.isContextManagementEnabled()) {
textContent = truncateString(
textContent,
MAX_CONTENT_LENGTH,
@@ -738,7 +738,7 @@ Response: ${rawResponseText}`;
// Fallback for unknown types - try as text
let text = bodyBuffer.toString('utf8');
if (!this.context.config.isAutoDistillationEnabled()) {
if (!this.context.config.isContextManagementEnabled()) {
text = truncateString(text, MAX_CONTENT_LENGTH, TRUNCATION_WARNING);
}
return {