mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-29 13:03:42 +01:00
251 lines
9.4 KiB
TypeScript
251 lines
9.4 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import assert from 'assert';
|
|
import { URI } from '../../../../base/common/uri.js';
|
|
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
|
|
import type {
|
|
IAgentDeltaEvent,
|
|
IAgentErrorEvent,
|
|
IAgentIdleEvent,
|
|
IAgentMessageEvent,
|
|
IAgentReasoningEvent,
|
|
IAgentTitleChangedEvent,
|
|
IAgentToolCompleteEvent,
|
|
IAgentToolStartEvent,
|
|
IAgentUsageEvent,
|
|
} from '../../common/agentService.js';
|
|
import type {
|
|
IDeltaAction,
|
|
IReasoningAction,
|
|
IResponsePartAction,
|
|
ISessionAction,
|
|
ISessionErrorAction,
|
|
ITitleChangedAction,
|
|
IToolCallCompleteAction,
|
|
IToolCallReadyAction,
|
|
IToolCallStartAction,
|
|
ITurnCompleteAction,
|
|
IUsageAction,
|
|
} from '../../common/state/sessionActions.js';
|
|
import { ToolResultContentType, type IMarkdownResponsePart, type IReasoningResponsePart } from '../../common/state/sessionState.js';
|
|
import { AgentEventMapper } from '../../node/agentEventMapper.js';
|
|
|
|
/** Helper: flatten the result of mapProgressEventToActions into an array. */
|
|
function mapToArray(result: ISessionAction | ISessionAction[] | undefined): ISessionAction[] {
|
|
if (!result) {
|
|
return [];
|
|
}
|
|
return Array.isArray(result) ? result : [result];
|
|
}
|
|
|
|
suite('AgentEventMapper', () => {
|
|
|
|
const session = URI.from({ scheme: 'copilot', path: '/test-session' });
|
|
const turnId = 'turn-1';
|
|
let mapper: AgentEventMapper;
|
|
|
|
setup(() => {
|
|
mapper = new AgentEventMapper();
|
|
});
|
|
|
|
ensureNoDisposablesAreLeakedInTestSuite();
|
|
|
|
test('first delta event creates a responsePart with content', () => {
|
|
const event: IAgentDeltaEvent = {
|
|
session,
|
|
type: 'delta',
|
|
messageId: 'msg-1',
|
|
content: 'hello world',
|
|
};
|
|
|
|
const actions = mapToArray(mapper.mapProgressEventToActions(event, session.toString(), turnId));
|
|
assert.strictEqual(actions.length, 1);
|
|
assert.strictEqual(actions[0].type, 'session/responsePart');
|
|
const part = (actions[0] as IResponsePartAction).part;
|
|
assert.strictEqual(part.kind, 'markdown');
|
|
assert.strictEqual(part.content, 'hello world');
|
|
assert.ok(part.id);
|
|
});
|
|
|
|
test('subsequent delta event maps to session/delta action', () => {
|
|
const first: IAgentDeltaEvent = { session, type: 'delta', messageId: 'msg-1', content: 'hello ' };
|
|
const second: IAgentDeltaEvent = { session, type: 'delta', messageId: 'msg-1', content: 'world' };
|
|
|
|
const firstActions = mapToArray(mapper.mapProgressEventToActions(first, session.toString(), turnId));
|
|
const partId = ((firstActions[0] as IResponsePartAction).part as IMarkdownResponsePart).id;
|
|
|
|
const secondActions = mapToArray(mapper.mapProgressEventToActions(second, session.toString(), turnId));
|
|
assert.strictEqual(secondActions.length, 1);
|
|
const delta = secondActions[0] as IDeltaAction;
|
|
assert.strictEqual(delta.type, 'session/delta');
|
|
assert.strictEqual(delta.content, 'world');
|
|
assert.strictEqual(delta.partId, partId);
|
|
});
|
|
|
|
test('tool_start event maps to toolCallStart + toolCallReady actions', () => {
|
|
const event: IAgentToolStartEvent = {
|
|
session,
|
|
type: 'tool_start',
|
|
toolCallId: 'tc-1',
|
|
toolName: 'readFile',
|
|
displayName: 'Read File',
|
|
invocationMessage: 'Reading file...',
|
|
toolInput: '/src/foo.ts',
|
|
toolKind: 'terminal',
|
|
language: 'shellscript',
|
|
};
|
|
|
|
const actions = mapToArray(mapper.mapProgressEventToActions(event, session.toString(), turnId));
|
|
assert.strictEqual(actions.length, 2);
|
|
|
|
const startAction = actions[0] as IToolCallStartAction;
|
|
assert.strictEqual(startAction.type, 'session/toolCallStart');
|
|
assert.strictEqual(startAction.toolCallId, 'tc-1');
|
|
assert.strictEqual(startAction.toolName, 'readFile');
|
|
assert.strictEqual(startAction.displayName, 'Read File');
|
|
assert.strictEqual(startAction._meta?.toolKind, 'terminal');
|
|
assert.strictEqual(startAction._meta?.language, 'shellscript');
|
|
|
|
const readyAction = actions[1] as IToolCallReadyAction;
|
|
assert.strictEqual(readyAction.type, 'session/toolCallReady');
|
|
assert.strictEqual(readyAction.toolCallId, 'tc-1');
|
|
assert.strictEqual(readyAction.invocationMessage, 'Reading file...');
|
|
assert.strictEqual(readyAction.toolInput, '/src/foo.ts');
|
|
assert.strictEqual(readyAction.confirmed, 'not-needed');
|
|
});
|
|
|
|
test('tool_complete event maps to session/toolCallComplete action', () => {
|
|
const event: IAgentToolCompleteEvent = {
|
|
session,
|
|
type: 'tool_complete',
|
|
toolCallId: 'tc-1',
|
|
result: {
|
|
success: true,
|
|
pastTenseMessage: 'Read file successfully',
|
|
content: [{ type: ToolResultContentType.Text, text: 'file contents here' }],
|
|
},
|
|
};
|
|
|
|
const actions = mapToArray(mapper.mapProgressEventToActions(event, session.toString(), turnId));
|
|
assert.strictEqual(actions.length, 1);
|
|
const complete = actions[0] as IToolCallCompleteAction;
|
|
assert.strictEqual(complete.type, 'session/toolCallComplete');
|
|
assert.strictEqual(complete.toolCallId, 'tc-1');
|
|
assert.strictEqual(complete.result.success, true);
|
|
assert.strictEqual(complete.result.pastTenseMessage, 'Read file successfully');
|
|
assert.deepStrictEqual(complete.result.content, [{ type: 'text', text: 'file contents here' }]);
|
|
});
|
|
|
|
test('idle event maps to session/turnComplete action', () => {
|
|
const event: IAgentIdleEvent = {
|
|
session,
|
|
type: 'idle',
|
|
};
|
|
|
|
const actions = mapToArray(mapper.mapProgressEventToActions(event, session.toString(), turnId));
|
|
assert.strictEqual(actions.length, 1);
|
|
const turnComplete = actions[0] as ITurnCompleteAction;
|
|
assert.strictEqual(turnComplete.type, 'session/turnComplete');
|
|
assert.strictEqual(turnComplete.session.toString(), session.toString());
|
|
assert.strictEqual(turnComplete.turnId, turnId);
|
|
});
|
|
|
|
test('error event maps to session/error action', () => {
|
|
const event: IAgentErrorEvent = {
|
|
session,
|
|
type: 'error',
|
|
errorType: 'runtime',
|
|
message: 'Something went wrong',
|
|
stack: 'Error: Something went wrong\n at foo.ts:1',
|
|
};
|
|
|
|
const actions = mapToArray(mapper.mapProgressEventToActions(event, session.toString(), turnId));
|
|
assert.strictEqual(actions.length, 1);
|
|
const errorAction = actions[0] as ISessionErrorAction;
|
|
assert.strictEqual(errorAction.type, 'session/error');
|
|
assert.strictEqual(errorAction.error.errorType, 'runtime');
|
|
assert.strictEqual(errorAction.error.message, 'Something went wrong');
|
|
assert.strictEqual(errorAction.error.stack, 'Error: Something went wrong\n at foo.ts:1');
|
|
});
|
|
|
|
test('usage event maps to session/usage action', () => {
|
|
const event: IAgentUsageEvent = {
|
|
session,
|
|
type: 'usage',
|
|
inputTokens: 100,
|
|
outputTokens: 50,
|
|
model: 'gpt-4',
|
|
cacheReadTokens: 25,
|
|
};
|
|
|
|
const actions = mapToArray(mapper.mapProgressEventToActions(event, session.toString(), turnId));
|
|
assert.strictEqual(actions.length, 1);
|
|
const usageAction = actions[0] as IUsageAction;
|
|
assert.strictEqual(usageAction.type, 'session/usage');
|
|
assert.strictEqual(usageAction.usage.inputTokens, 100);
|
|
assert.strictEqual(usageAction.usage.outputTokens, 50);
|
|
assert.strictEqual(usageAction.usage.model, 'gpt-4');
|
|
assert.strictEqual(usageAction.usage.cacheReadTokens, 25);
|
|
});
|
|
|
|
test('title_changed event maps to session/titleChanged action', () => {
|
|
const event: IAgentTitleChangedEvent = {
|
|
session,
|
|
type: 'title_changed',
|
|
title: 'New Title',
|
|
};
|
|
|
|
const actions = mapToArray(mapper.mapProgressEventToActions(event, session.toString(), turnId));
|
|
assert.strictEqual(actions.length, 1);
|
|
assert.strictEqual(actions[0].type, 'session/titleChanged');
|
|
assert.strictEqual((actions[0] as ITitleChangedAction).title, 'New Title');
|
|
});
|
|
|
|
test('first reasoning event creates a responsePart with content', () => {
|
|
const event: IAgentReasoningEvent = {
|
|
session,
|
|
type: 'reasoning',
|
|
content: 'Let me think about this...',
|
|
};
|
|
|
|
const actions = mapToArray(mapper.mapProgressEventToActions(event, session.toString(), turnId));
|
|
assert.strictEqual(actions.length, 1);
|
|
assert.strictEqual(actions[0].type, 'session/responsePart');
|
|
const part = (actions[0] as IResponsePartAction).part;
|
|
assert.strictEqual(part.kind, 'reasoning');
|
|
assert.strictEqual(part.content, 'Let me think about this...');
|
|
assert.ok(part.id);
|
|
});
|
|
|
|
test('subsequent reasoning event maps to session/reasoning action', () => {
|
|
const first: IAgentReasoningEvent = { session, type: 'reasoning', content: 'Let me think...' };
|
|
const second: IAgentReasoningEvent = { session, type: 'reasoning', content: ' more thoughts' };
|
|
|
|
const firstActions = mapToArray(mapper.mapProgressEventToActions(first, session.toString(), turnId));
|
|
const partId = ((firstActions[0] as IResponsePartAction).part as IReasoningResponsePart).id;
|
|
|
|
const secondActions = mapToArray(mapper.mapProgressEventToActions(second, session.toString(), turnId));
|
|
assert.strictEqual(secondActions.length, 1);
|
|
const reasoning = secondActions[0] as IReasoningAction;
|
|
assert.strictEqual(reasoning.type, 'session/reasoning');
|
|
assert.strictEqual(reasoning.content, ' more thoughts');
|
|
assert.strictEqual(reasoning.partId, partId);
|
|
});
|
|
|
|
test('message event returns undefined', () => {
|
|
const event: IAgentMessageEvent = {
|
|
session,
|
|
type: 'message',
|
|
role: 'assistant',
|
|
messageId: 'msg-1',
|
|
content: 'Some full message',
|
|
};
|
|
|
|
const result = mapper.mapProgressEventToActions(event, session.toString(), turnId);
|
|
assert.strictEqual(result, undefined);
|
|
});
|
|
});
|