wip: next round of implementation, this time tests
This commit is contained in:
152
tests/engine/OpenCodeManager.protocol.test.ts
Normal file
152
tests/engine/OpenCodeManager.protocol.test.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { OpenCodeManager } from '../../src/main/engine/OpenCodeManager';
|
||||
import { getProtocolTelemetryService } from '../../src/main/agentic/observability/protocolTelemetry';
|
||||
|
||||
interface MockConversation {
|
||||
id: string;
|
||||
model?: string;
|
||||
messages: Array<{ role: 'user' | 'assistant' | 'system' | 'tool'; content: string }>;
|
||||
}
|
||||
|
||||
function createChatEngineMock(conversation: MockConversation) {
|
||||
const settings = new Map<string, string>();
|
||||
const addMessage = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
return {
|
||||
getConversation: vi.fn().mockResolvedValue(conversation),
|
||||
addMessage,
|
||||
getDefaultSystemPrompt: vi.fn().mockResolvedValue('system prompt'),
|
||||
getSetting: vi.fn(async (key: string) => settings.get(key) ?? null),
|
||||
setSetting: vi.fn(async (key: string, value: string) => {
|
||||
settings.set(key, value);
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
describe('OpenCodeManager protocol integration', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('uses protocol envelope flow and persists workflow checkpoint for needs_input turns', async () => {
|
||||
const conversation: MockConversation = {
|
||||
id: 'conversation-1',
|
||||
model: 'gpt-5',
|
||||
messages: [
|
||||
{ role: 'system', content: 'system' },
|
||||
{ role: 'user', content: 'previous user message' },
|
||||
],
|
||||
};
|
||||
|
||||
const chatEngineMock = createChatEngineMock(conversation);
|
||||
const manager = new OpenCodeManager(
|
||||
chatEngineMock as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
() => null,
|
||||
);
|
||||
|
||||
manager.setApiKey('test-api-key');
|
||||
|
||||
const providerSpy = vi.spyOn(manager as never, 'sendOpenAIMessage').mockResolvedValue({
|
||||
content: JSON.stringify({
|
||||
protocolVersion: '2.0',
|
||||
assistantText: 'Please provide a date range.',
|
||||
intent: 'ask_input',
|
||||
needsInput: {
|
||||
required: true,
|
||||
fields: [{ key: 'dateRange', label: 'Date range', inputType: 'date' }],
|
||||
},
|
||||
actions: [],
|
||||
confidence: 0.82,
|
||||
traceId: 'trace-needs-input',
|
||||
}),
|
||||
toolCalls: [],
|
||||
});
|
||||
|
||||
const telemetryBefore = getProtocolTelemetryService().getSnapshot();
|
||||
|
||||
const result = await manager.sendMessage('conversation-1', 'Generate report', {
|
||||
metadata: { surface: 'tab' },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.envelope?.protocolVersion).toBe('2.0');
|
||||
expect(result.envelope?.needsInput.required).toBe(true);
|
||||
expect(result.envelope?.needsInput.fields[0]?.key).toBe('dateRange');
|
||||
|
||||
expect(providerSpy).toHaveBeenCalledTimes(1);
|
||||
const providerMessages = providerSpy.mock.calls[0][2] as Array<{ role: string; content?: string }>;
|
||||
const latestUserMessage = providerMessages[providerMessages.length - 1]?.content ?? '';
|
||||
expect(latestUserMessage).toContain('[Protocol request envelope]');
|
||||
expect(latestUserMessage).toContain('"protocolVersion": "2.0"');
|
||||
expect(latestUserMessage).toContain('"surface": "tab"');
|
||||
|
||||
const checkpointKey = 'agui.workflow.conversation-1';
|
||||
expect(chatEngineMock.setSetting).toHaveBeenCalledWith(
|
||||
checkpointKey,
|
||||
expect.any(String),
|
||||
);
|
||||
|
||||
const persistedCheckpoint = await chatEngineMock.getSetting(checkpointKey);
|
||||
const checkpoint = JSON.parse(persistedCheckpoint as string) as { state: string; pendingFields: string[]; lastTraceId: string };
|
||||
expect(checkpoint.state).toBe('awaiting_input');
|
||||
expect(checkpoint.pendingFields).toEqual(['dateRange']);
|
||||
expect(checkpoint.lastTraceId).toBe('trace-needs-input');
|
||||
|
||||
const telemetryAfter = getProtocolTelemetryService().getSnapshot();
|
||||
expect(telemetryAfter.totalTurns).toBe(telemetryBefore.totalTurns + 1);
|
||||
expect(telemetryAfter.validEnvelopeTurns).toBe(telemetryBefore.validEnvelopeTurns + 1);
|
||||
});
|
||||
|
||||
it('blocks unsupported actions and records blocked-action telemetry', async () => {
|
||||
const conversation: MockConversation = {
|
||||
id: 'conversation-2',
|
||||
model: 'gpt-5',
|
||||
messages: [{ role: 'user', content: 'previous user message' }],
|
||||
};
|
||||
|
||||
const chatEngineMock = createChatEngineMock(conversation);
|
||||
const manager = new OpenCodeManager(
|
||||
chatEngineMock as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
() => null,
|
||||
);
|
||||
manager.setApiKey('test-api-key');
|
||||
|
||||
vi.spyOn(manager as never, 'sendOpenAIMessage').mockResolvedValue({
|
||||
content: JSON.stringify({
|
||||
protocolVersion: '2.0',
|
||||
assistantText: 'Try toggling sidebar.',
|
||||
intent: 'propose_action',
|
||||
needsInput: { required: false, fields: [] },
|
||||
actions: [
|
||||
{
|
||||
id: 'a1',
|
||||
action: 'toggleAssistantSidebar',
|
||||
label: 'Toggle assistant sidebar',
|
||||
policy: 'silent',
|
||||
requiresConfirmation: false,
|
||||
},
|
||||
],
|
||||
confidence: 0.74,
|
||||
traceId: 'trace-blocked-action',
|
||||
}),
|
||||
toolCalls: [],
|
||||
});
|
||||
|
||||
const telemetryBefore = getProtocolTelemetryService().getSnapshot();
|
||||
|
||||
const result = await manager.sendMessage('conversation-2', 'Toggle it', {
|
||||
metadata: { surface: 'tab' },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.envelope?.actions).toEqual([]);
|
||||
expect(result.warnings?.some((warning) => warning.includes('Blocked unsupported action: toggleAssistantSidebar'))).toBe(true);
|
||||
|
||||
const telemetryAfter = getProtocolTelemetryService().getSnapshot();
|
||||
expect(telemetryAfter.blockedActionCount).toBe(telemetryBefore.blockedActionCount + 1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user