feat(core): Fully migrate packages/core to AgentLoopContext. (#22115)

This commit is contained in:
joshualitt
2026-03-12 18:56:31 -07:00
committed by GitHub
parent 1d2585dba6
commit de656f01d7
53 changed files with 522 additions and 292 deletions
@@ -36,12 +36,15 @@ describe('ConsecaSafetyChecker', () => {
checker = ConsecaSafetyChecker.getInstance();
mockConfig = {
get config() {
return this;
},
enableConseca: true,
getToolRegistry: vi.fn().mockReturnValue({
getFunctionDeclarations: vi.fn().mockReturnValue([]),
}),
} as unknown as Config;
checker.setConfig(mockConfig);
checker.setContext(mockConfig);
vi.clearAllMocks();
// Default mock implementations
@@ -72,9 +75,12 @@ describe('ConsecaSafetyChecker', () => {
it('should return ALLOW if enableConseca is false', async () => {
const disabledConfig = {
get config() {
return this;
},
enableConseca: false,
} as unknown as Config;
checker.setConfig(disabledConfig);
checker.setContext(disabledConfig);
const input: SafetyCheckInput = {
protocolVersion: '1.0.0',
+10 -9
View File
@@ -23,12 +23,13 @@ import type { Config } from '../../config/config.js';
import { generatePolicy } from './policy-generator.js';
import { enforcePolicy } from './policy-enforcer.js';
import type { SecurityPolicy } from './types.js';
import type { AgentLoopContext } from '../../config/agent-loop-context.js';
export class ConsecaSafetyChecker implements InProcessChecker {
private static instance: ConsecaSafetyChecker | undefined;
private currentPolicy: SecurityPolicy | null = null;
private activeUserPrompt: string | null = null;
private config: Config | null = null;
private context: AgentLoopContext | null = null;
/**
* Private constructor to enforce singleton pattern.
@@ -50,8 +51,8 @@ export class ConsecaSafetyChecker implements InProcessChecker {
ConsecaSafetyChecker.instance = undefined;
}
setConfig(config: Config): void {
this.config = config;
setContext(context: AgentLoopContext): void {
this.context = context;
}
async check(input: SafetyCheckInput): Promise<SafetyCheckResult> {
@@ -59,7 +60,7 @@ export class ConsecaSafetyChecker implements InProcessChecker {
`[Conseca] check called. History is: ${JSON.stringify(input.context.history)}`,
);
if (!this.config) {
if (!this.context) {
debugLogger.debug('[Conseca] check failed: Config not initialized');
return {
decision: SafetyCheckDecision.ALLOW,
@@ -67,7 +68,7 @@ export class ConsecaSafetyChecker implements InProcessChecker {
};
}
if (!this.config.enableConseca) {
if (!this.context.config.enableConseca) {
debugLogger.debug('[Conseca] check skipped: Conseca is not enabled.');
return {
decision: SafetyCheckDecision.ALLOW,
@@ -78,14 +79,14 @@ export class ConsecaSafetyChecker implements InProcessChecker {
const userPrompt = this.extractUserPrompt(input);
let trustedContent = '';
const toolRegistry = this.config.getToolRegistry();
const toolRegistry = this.context.toolRegistry;
if (toolRegistry) {
const tools = toolRegistry.getFunctionDeclarations();
trustedContent = JSON.stringify(tools, null, 2);
}
if (userPrompt) {
await this.getPolicy(userPrompt, trustedContent, this.config);
await this.getPolicy(userPrompt, trustedContent, this.context.config);
} else {
debugLogger.debug(
`[Conseca] Skipping policy generation because userPrompt is null`,
@@ -104,12 +105,12 @@ export class ConsecaSafetyChecker implements InProcessChecker {
result = await enforcePolicy(
this.currentPolicy,
input.toolCall,
this.config,
this.context.config,
);
}
logConsecaVerdict(
this.config,
this.context.config,
new ConsecaVerdictEvent(
userPrompt || '',
JSON.stringify(this.currentPolicy || {}),
@@ -8,6 +8,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ContextBuilder } from './context-builder.js';
import type { Config } from '../config/config.js';
import type { Content, FunctionCall } from '@google/genai';
import type { GeminiClient } from '../core/client.js';
describe('ContextBuilder', () => {
let contextBuilder: ContextBuilder;
@@ -20,15 +21,20 @@ describe('ContextBuilder', () => {
vi.spyOn(process, 'cwd').mockReturnValue(mockCwd);
mockHistory = [];
const mockGeminiClient = {
getHistory: vi.fn().mockImplementation(() => mockHistory),
};
mockConfig = {
get config() {
return this as unknown as Config;
},
geminiClient: mockGeminiClient as unknown as GeminiClient,
getWorkspaceContext: vi.fn().mockReturnValue({
getDirectories: vi.fn().mockReturnValue(mockWorkspaces),
}),
getQuestion: vi.fn().mockReturnValue('mock question'),
getGeminiClient: vi.fn().mockReturnValue({
getHistory: vi.fn().mockImplementation(() => mockHistory),
}),
};
getGeminiClient: vi.fn().mockReturnValue(mockGeminiClient),
} as Partial<Config>;
contextBuilder = new ContextBuilder(mockConfig as unknown as Config);
});
+5 -5
View File
@@ -5,21 +5,21 @@
*/
import type { SafetyCheckInput, ConversationTurn } from './protocol.js';
import type { Config } from '../config/config.js';
import { debugLogger } from '../utils/debugLogger.js';
import type { Content, FunctionCall } from '@google/genai';
import type { AgentLoopContext } from '../config/agent-loop-context.js';
/**
* Builds context objects for safety checkers, ensuring sensitive data is filtered.
*/
export class ContextBuilder {
constructor(private readonly config: Config) {}
constructor(private readonly context: AgentLoopContext) {}
/**
* Builds the full context object with all available data.
*/
buildFullContext(): SafetyCheckInput['context'] {
const clientHistory = this.config.getGeminiClient()?.getHistory() || [];
const clientHistory = this.context.geminiClient?.getHistory() || [];
const history = this.convertHistoryToTurns(clientHistory);
debugLogger.debug(
@@ -29,7 +29,7 @@ export class ContextBuilder {
// ContextBuilder's responsibility is to provide the *current* context.
// If the conversation hasn't started (history is empty), we check if there's a pending question.
// However, if the history is NOT empty, we trust it reflects the true state.
const currentQuestion = this.config.getQuestion();
const currentQuestion = this.context.config.getQuestion();
if (currentQuestion && history.length === 0) {
history.push({
user: {
@@ -43,7 +43,7 @@ export class ContextBuilder {
environment: {
cwd: process.cwd(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
workspaces: this.config
workspaces: this.context.config
.getWorkspaceContext()
.getDirectories() as string[],
},