Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/agentHost/test/node/protocol/clientTools.integrationTest.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
/**
7
* Integration tests for client-provided tool handling through the protocol layer.
8
*
9
* These tests verify that:
10
* - tool_start with toolClientId emits only a toolCallStart (no auto-ready)
11
* - tool_ready without confirmationTitle transitions to Running (auto-confirmed)
12
* - tool_ready with confirmationTitle transitions to PendingConfirmation
13
* - toolCallComplete dispatched by the client flows through to the agent
14
*
15
* Run with:
16
* ./scripts/test-integration.sh --run src/vs/platform/agentHost/test/node/protocol/clientTools.integrationTest.ts
17
*/
18
19
import assert from 'assert';
20
import { ToolResultContentType } from '../../../common/state/sessionState.js';
21
import {
22
createAndSubscribeSession,
23
dispatchTurnStarted,
24
getActionEnvelope,
25
IServerHandle,
26
isActionNotification,
27
startServer,
28
TestProtocolClient,
29
} from './testHelpers.js';
30
31
suite('Protocol WebSocket — Client Tools', function () {
32
33
let server: IServerHandle;
34
let client: TestProtocolClient;
35
36
suiteSetup(async function () {
37
this.timeout(15_000);
38
server = await startServer();
39
});
40
41
suiteTeardown(function () {
42
server.process.kill();
43
});
44
45
setup(async function () {
46
this.timeout(10_000);
47
client = new TestProtocolClient(server.port);
48
await client.connect();
49
});
50
51
teardown(function () {
52
client.close();
53
});
54
55
// ---- Client tool: tool_start with toolClientId --------------------------
56
57
test('client tool_start emits toolCallStart then toolCallReady (auto-confirmed)', async function () {
58
this.timeout(10_000);
59
60
const sessionUri = await createAndSubscribeSession(client, 'test-client-tool');
61
dispatchTurnStarted(client, sessionUri, 'turn-ct', 'client-tool', 1);
62
63
// Wait for toolCallStart
64
const [toolStartNotif, toolReadyNotif] = await Promise.all([
65
client.waitForNotification(n => isActionNotification(n, 'session/toolCallStart')),
66
client.waitForNotification(n => isActionNotification(n, 'session/toolCallReady')),
67
]);
68
const toolStartAction = getActionEnvelope(toolStartNotif).action as {
69
toolCallId: string;
70
toolClientId?: string;
71
};
72
assert.strictEqual(toolStartAction.toolCallId, 'tc-client-1');
73
assert.strictEqual(toolStartAction.toolClientId, 'test-client-tool');
74
75
const toolReadyAction = getActionEnvelope(toolReadyNotif).action as {
76
toolCallId: string;
77
confirmed?: string;
78
};
79
assert.strictEqual(toolReadyAction.toolCallId, 'tc-client-1');
80
assert.strictEqual(toolReadyAction.confirmed, 'not-needed');
81
82
// Complete the client tool call
83
client.notify('dispatchAction', {
84
clientSeq: 2,
85
action: {
86
type: 'session/toolCallComplete',
87
session: sessionUri,
88
turnId: 'turn-ct',
89
toolCallId: 'tc-client-1',
90
result: {
91
success: true,
92
pastTenseMessage: 'Ran tests',
93
content: [{ type: ToolResultContentType.Text, text: 'all passed' }],
94
},
95
},
96
});
97
98
// Wait for turn completion
99
await client.waitForNotification(
100
n => isActionNotification(n, 'session/turnComplete'),
101
);
102
});
103
104
// ---- Client tool with permission request --------------------------------
105
106
test('client tool with permission fires toolCallReady with confirmationTitle', async function () {
107
this.timeout(10_000);
108
109
const sessionUri = await createAndSubscribeSession(client, 'test-client-perm');
110
dispatchTurnStarted(client, sessionUri, 'turn-cp', 'client-tool-with-permission', 1);
111
112
// Wait for toolCallStart (should have toolClientId)
113
const toolStartNotif = await client.waitForNotification(
114
n => isActionNotification(n, 'session/toolCallStart'),
115
);
116
const toolStartAction = getActionEnvelope(toolStartNotif).action as {
117
toolCallId: string;
118
toolClientId?: string;
119
};
120
assert.strictEqual(toolStartAction.toolCallId, 'tc-client-perm-1');
121
assert.strictEqual(toolStartAction.toolClientId, 'test-client-tool');
122
123
// Wait for toolCallReady with confirmationTitle (permission flow)
124
const toolReadyNotif = await client.waitForNotification(
125
n => isActionNotification(n, 'session/toolCallReady'),
126
);
127
const toolReadyAction = getActionEnvelope(toolReadyNotif).action as {
128
toolCallId: string;
129
confirmationTitle?: string;
130
confirmed?: string;
131
};
132
assert.strictEqual(toolReadyAction.toolCallId, 'tc-client-perm-1');
133
assert.strictEqual(toolReadyAction.confirmationTitle, 'Allow Run Tests?');
134
// Permission flow should NOT have auto-confirmed
135
assert.strictEqual(toolReadyAction.confirmed, undefined);
136
137
// Approve the permission
138
client.notify('dispatchAction', {
139
clientSeq: 2,
140
action: {
141
type: 'session/toolCallConfirmed',
142
session: sessionUri,
143
turnId: 'turn-cp',
144
toolCallId: 'tc-client-perm-1',
145
approved: true,
146
},
147
});
148
149
// Wait for turn completion
150
await client.waitForNotification(
151
n => isActionNotification(n, 'session/turnComplete'),
152
);
153
});
154
155
// ---- tool_ready auto-confirm (non-permission client tools) ---------------
156
157
test('tool_ready without confirmationTitle auto-confirms with NotNeeded', async function () {
158
this.timeout(10_000);
159
160
const sessionUri = await createAndSubscribeSession(client, 'test-ready-auto');
161
dispatchTurnStarted(client, sessionUri, 'turn-ra', 'client-tool', 1);
162
163
// Wait for toolCallStart
164
await client.waitForNotification(
165
n => isActionNotification(n, 'session/toolCallStart'),
166
);
167
168
// Dispatch a synthetic tool_ready without confirmationTitle via
169
// completing the tool — the server-side reducer will process the
170
// tool_ready that was generated by the event mapper.
171
client.notify('dispatchAction', {
172
clientSeq: 2,
173
action: {
174
type: 'session/toolCallComplete',
175
session: sessionUri,
176
turnId: 'turn-ra',
177
toolCallId: 'tc-client-1',
178
result: {
179
success: true,
180
pastTenseMessage: 'Done',
181
content: [{ type: ToolResultContentType.Text, text: 'ok' }],
182
},
183
},
184
});
185
186
await client.waitForNotification(
187
n => isActionNotification(n, 'session/turnComplete'),
188
);
189
});
190
});
191
192