Path: blob/main/src/vs/platform/agentHost/test/node/protocol/clientTools.integrationTest.ts
13405 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45/**6* Integration tests for client-provided tool handling through the protocol layer.7*8* These tests verify that:9* - tool_start with toolClientId emits only a toolCallStart (no auto-ready)10* - tool_ready without confirmationTitle transitions to Running (auto-confirmed)11* - tool_ready with confirmationTitle transitions to PendingConfirmation12* - toolCallComplete dispatched by the client flows through to the agent13*14* Run with:15* ./scripts/test-integration.sh --run src/vs/platform/agentHost/test/node/protocol/clientTools.integrationTest.ts16*/1718import assert from 'assert';19import { ToolResultContentType } from '../../../common/state/sessionState.js';20import {21createAndSubscribeSession,22dispatchTurnStarted,23getActionEnvelope,24IServerHandle,25isActionNotification,26startServer,27TestProtocolClient,28} from './testHelpers.js';2930suite('Protocol WebSocket — Client Tools', function () {3132let server: IServerHandle;33let client: TestProtocolClient;3435suiteSetup(async function () {36this.timeout(15_000);37server = await startServer();38});3940suiteTeardown(function () {41server.process.kill();42});4344setup(async function () {45this.timeout(10_000);46client = new TestProtocolClient(server.port);47await client.connect();48});4950teardown(function () {51client.close();52});5354// ---- Client tool: tool_start with toolClientId --------------------------5556test('client tool_start emits toolCallStart then toolCallReady (auto-confirmed)', async function () {57this.timeout(10_000);5859const sessionUri = await createAndSubscribeSession(client, 'test-client-tool');60dispatchTurnStarted(client, sessionUri, 'turn-ct', 'client-tool', 1);6162// Wait for toolCallStart63const [toolStartNotif, toolReadyNotif] = await Promise.all([64client.waitForNotification(n => isActionNotification(n, 'session/toolCallStart')),65client.waitForNotification(n => isActionNotification(n, 'session/toolCallReady')),66]);67const toolStartAction = getActionEnvelope(toolStartNotif).action as {68toolCallId: string;69toolClientId?: string;70};71assert.strictEqual(toolStartAction.toolCallId, 'tc-client-1');72assert.strictEqual(toolStartAction.toolClientId, 'test-client-tool');7374const toolReadyAction = getActionEnvelope(toolReadyNotif).action as {75toolCallId: string;76confirmed?: string;77};78assert.strictEqual(toolReadyAction.toolCallId, 'tc-client-1');79assert.strictEqual(toolReadyAction.confirmed, 'not-needed');8081// Complete the client tool call82client.notify('dispatchAction', {83clientSeq: 2,84action: {85type: 'session/toolCallComplete',86session: sessionUri,87turnId: 'turn-ct',88toolCallId: 'tc-client-1',89result: {90success: true,91pastTenseMessage: 'Ran tests',92content: [{ type: ToolResultContentType.Text, text: 'all passed' }],93},94},95});9697// Wait for turn completion98await client.waitForNotification(99n => isActionNotification(n, 'session/turnComplete'),100);101});102103// ---- Client tool with permission request --------------------------------104105test('client tool with permission fires toolCallReady with confirmationTitle', async function () {106this.timeout(10_000);107108const sessionUri = await createAndSubscribeSession(client, 'test-client-perm');109dispatchTurnStarted(client, sessionUri, 'turn-cp', 'client-tool-with-permission', 1);110111// Wait for toolCallStart (should have toolClientId)112const toolStartNotif = await client.waitForNotification(113n => isActionNotification(n, 'session/toolCallStart'),114);115const toolStartAction = getActionEnvelope(toolStartNotif).action as {116toolCallId: string;117toolClientId?: string;118};119assert.strictEqual(toolStartAction.toolCallId, 'tc-client-perm-1');120assert.strictEqual(toolStartAction.toolClientId, 'test-client-tool');121122// Wait for toolCallReady with confirmationTitle (permission flow)123const toolReadyNotif = await client.waitForNotification(124n => isActionNotification(n, 'session/toolCallReady'),125);126const toolReadyAction = getActionEnvelope(toolReadyNotif).action as {127toolCallId: string;128confirmationTitle?: string;129confirmed?: string;130};131assert.strictEqual(toolReadyAction.toolCallId, 'tc-client-perm-1');132assert.strictEqual(toolReadyAction.confirmationTitle, 'Allow Run Tests?');133// Permission flow should NOT have auto-confirmed134assert.strictEqual(toolReadyAction.confirmed, undefined);135136// Approve the permission137client.notify('dispatchAction', {138clientSeq: 2,139action: {140type: 'session/toolCallConfirmed',141session: sessionUri,142turnId: 'turn-cp',143toolCallId: 'tc-client-perm-1',144approved: true,145},146});147148// Wait for turn completion149await client.waitForNotification(150n => isActionNotification(n, 'session/turnComplete'),151);152});153154// ---- tool_ready auto-confirm (non-permission client tools) ---------------155156test('tool_ready without confirmationTitle auto-confirms with NotNeeded', async function () {157this.timeout(10_000);158159const sessionUri = await createAndSubscribeSession(client, 'test-ready-auto');160dispatchTurnStarted(client, sessionUri, 'turn-ra', 'client-tool', 1);161162// Wait for toolCallStart163await client.waitForNotification(164n => isActionNotification(n, 'session/toolCallStart'),165);166167// Dispatch a synthetic tool_ready without confirmationTitle via168// completing the tool — the server-side reducer will process the169// tool_ready that was generated by the event mapper.170client.notify('dispatchAction', {171clientSeq: 2,172action: {173type: 'session/toolCallComplete',174session: sessionUri,175turnId: 'turn-ra',176toolCallId: 'tc-client-1',177result: {178success: true,179pastTenseMessage: 'Done',180content: [{ type: ToolResultContentType.Text, text: 'ok' }],181},182},183});184185await client.waitForNotification(186n => isActionNotification(n, 'session/turnComplete'),187);188});189});190191192