Skip to content

Commit 9c3a2f7

Browse files
committed
Copilot remote agent tests
1 parent 3872678 commit 9c3a2f7

4 files changed

Lines changed: 1070 additions & 1 deletion

File tree

src/lm/tools/copilotRemoteAgentTool.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ export class CopilotRemoteAgentTool implements vscode.LanguageModelTool<CopilotR
127127
};
128128
const buffer: Buffer = Buffer.from(JSON.stringify(preferredRendering));
129129
const data: Uint8Array = Uint8Array.from(buffer);
130-
lmResult.push(new vscode.LanguageModelDataPart2(data, 'application/pull-request+json', [vscode.LanguageModelPartAudience.User]));
130+
131+
// API might not be available for tests, this guarantees we are still able to test
132+
const userAudience = (vscode as any).LanguageModelPartAudience?.User ?? 1;
133+
lmResult.push(new vscode.LanguageModelDataPart2(data, 'application/pull-request+json', [userAudience]));
131134
}
132135

133136
return new vscode.LanguageModelToolResult2(lmResult);
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
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+
import { default as assert } from 'assert';
7+
import {
8+
parseSessionLogs,
9+
parseToolCallDetails,
10+
parseDiff,
11+
toFileLabel,
12+
SessionResponseLogChunk
13+
} from '../../../common/sessionParsing';
14+
15+
describe('sessionParsing', function () {
16+
describe('parseSessionLogs()', function () {
17+
it('should parse valid session logs', function () {
18+
const rawText = `data: {"choices":[{"finish_reason":"tool_calls","delta":{"content":"","role":"assistant","tool_calls":[{"function":{"arguments":"{\\"command\\": \\"view\\", \\"path\\": \\"/home/runner/work/repo/repo/src/file.ts\\"}","name":"str_replace_editor"},"id":"call_123","type":"function","index":0}]}}],"created":1640995200,"id":"chatcmpl-123","usage":{"completion_tokens":10,"prompt_tokens":50,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":60},"model":"gpt-4","object":"chat.completion.chunk"}`;
19+
20+
const result = parseSessionLogs(rawText);
21+
22+
assert.strictEqual(result.length, 1);
23+
assert.strictEqual(result[0].choices.length, 1);
24+
assert.strictEqual(result[0].choices[0].finish_reason, 'tool_calls');
25+
assert.strictEqual(result[0].choices[0].delta.role, 'assistant');
26+
assert.strictEqual(result[0].choices[0].delta.tool_calls?.length, 1);
27+
assert.strictEqual(result[0].choices[0].delta.tool_calls?.[0].function.name, 'str_replace_editor');
28+
});
29+
30+
it('should handle malformed JSON gracefully', function () {
31+
const rawText = `data: {"invalid": "json"
32+
data: {"choices":[{"finish_reason":"stop","delta":{"content":"Hello","role":"assistant"}}],"created":1640995200,"id":"chatcmpl-123","usage":{"completion_tokens":1,"prompt_tokens":10,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":11},"model":"gpt-4","object":"chat.completion.chunk"}`;
33+
34+
assert.throws(() => {
35+
parseSessionLogs(rawText);
36+
});
37+
});
38+
39+
it('should parse tool calls correctly', function () {
40+
const rawText = `data: {"choices":[{"finish_reason":"tool_calls","delta":{"content":"","role":"assistant","tool_calls":[{"function":{"arguments":"{\\"command\\": \\"bash\\", \\"args\\": \\"ls -la\\"}","name":"bash"},"id":"call_456","type":"function","index":0}]}}],"created":1640995200,"id":"chatcmpl-456","usage":{"completion_tokens":5,"prompt_tokens":20,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":25},"model":"gpt-4","object":"chat.completion.chunk"}`;
41+
42+
const result = parseSessionLogs(rawText);
43+
44+
assert.strictEqual(result.length, 1);
45+
assert.strictEqual(result[0].choices[0].delta.tool_calls?.[0].function.name, 'bash');
46+
});
47+
48+
it('should filter out non-data lines', function () {
49+
const rawText = `some random line
50+
data: {"choices":[{"finish_reason":"stop","delta":{"content":"Hello","role":"assistant"}}],"created":1640995200,"id":"chatcmpl-123","usage":{"completion_tokens":1,"prompt_tokens":10,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":11},"model":"gpt-4","object":"chat.completion.chunk"}
51+
another non-data line`;
52+
53+
const result = parseSessionLogs(rawText);
54+
55+
assert.strictEqual(result.length, 1);
56+
});
57+
});
58+
59+
describe('parseToolCallDetails()', function () {
60+
it('should parse str_replace_editor tool calls with view command', function () {
61+
const toolCall = {
62+
function: {
63+
name: 'str_replace_editor',
64+
arguments: '{"command": "view", "path": "/home/runner/work/repo/repo/src/example.ts"}'
65+
},
66+
id: 'call_123',
67+
type: 'function',
68+
index: 0
69+
};
70+
71+
const result = parseToolCallDetails(toolCall, '');
72+
73+
assert.strictEqual(result.toolName, 'Read');
74+
assert.strictEqual(result.invocationMessage, 'Read src/example.ts');
75+
assert.strictEqual(result.pastTenseMessage, 'Read src/example.ts');
76+
if (result.toolSpecificData && 'command' in result.toolSpecificData) {
77+
assert.strictEqual(result.toolSpecificData.command, 'view');
78+
}
79+
});
80+
81+
it('should parse str_replace_editor tool calls with edit command', function () {
82+
const toolCall = {
83+
function: {
84+
name: 'str_replace_editor',
85+
arguments: '{"command": "str_replace", "path": "/home/runner/work/repo/repo/src/example.ts"}'
86+
},
87+
id: 'call_123',
88+
type: 'function',
89+
index: 0
90+
};
91+
92+
const result = parseToolCallDetails(toolCall, '');
93+
94+
assert.strictEqual(result.toolName, 'Edit');
95+
assert.strictEqual(result.invocationMessage, 'Edit [](src/example.ts)');
96+
assert.strictEqual(result.pastTenseMessage, 'Edit [](src/example.ts)');
97+
if (result.toolSpecificData && 'command' in result.toolSpecificData) {
98+
assert.strictEqual(result.toolSpecificData.command, 'str_replace');
99+
}
100+
});
101+
102+
it('should parse bash tool calls', function () {
103+
const toolCall = {
104+
function: {
105+
name: 'bash',
106+
arguments: '{"command": "npm test"}'
107+
},
108+
id: 'call_456',
109+
type: 'function',
110+
index: 0
111+
};
112+
113+
const result = parseToolCallDetails(toolCall, 'Test output here');
114+
115+
assert.strictEqual(result.toolName, 'Run Bash command');
116+
assert.strictEqual(result.invocationMessage, '$ npm test\nTest output here');
117+
if (result.toolSpecificData && 'language' in result.toolSpecificData) {
118+
assert.strictEqual(result.toolSpecificData.language, 'bash');
119+
assert.strictEqual(result.toolSpecificData.commandLine.original, 'npm test');
120+
}
121+
});
122+
123+
it('should parse think tool calls', function () {
124+
const toolCall = {
125+
function: {
126+
name: 'think',
127+
arguments: '{}'
128+
},
129+
id: 'call_789',
130+
type: 'function',
131+
index: 0
132+
};
133+
134+
const result = parseToolCallDetails(toolCall, 'I need to analyze this code');
135+
136+
assert.strictEqual(result.toolName, 'Thought');
137+
assert.strictEqual(result.invocationMessage, 'I need to analyze this code');
138+
});
139+
140+
it('should parse report_progress tool calls', function () {
141+
const toolCall = {
142+
function: {
143+
name: 'report_progress',
144+
arguments: '{"prDescription": "Updated the test files", "commitMessage": "feat: add new tests"}'
145+
},
146+
id: 'call_101',
147+
type: 'function',
148+
index: 0
149+
};
150+
151+
const result = parseToolCallDetails(toolCall, '');
152+
153+
assert.strictEqual(result.toolName, 'Progress Update');
154+
assert.strictEqual(result.invocationMessage, 'Updated the test files');
155+
assert.strictEqual(result.originMessage, 'Commit: feat: add new tests');
156+
});
157+
158+
it('should handle unknown tool types', function () {
159+
const toolCall = {
160+
function: {
161+
name: 'unknown_tool',
162+
arguments: '{"param": "value"}'
163+
},
164+
id: 'call_999',
165+
type: 'function',
166+
index: 0
167+
};
168+
169+
const result = parseToolCallDetails(toolCall, 'some content');
170+
171+
assert.strictEqual(result.toolName, 'unknown_tool');
172+
assert.strictEqual(result.invocationMessage, 'some content');
173+
});
174+
175+
it('should handle malformed tool arguments', function () {
176+
const toolCall = {
177+
function: {
178+
name: 'str_replace_editor',
179+
arguments: '{"invalid": json}'
180+
},
181+
id: 'call_error',
182+
type: 'function',
183+
index: 0
184+
};
185+
186+
const result = parseToolCallDetails(toolCall, '');
187+
188+
// Should fall back gracefully with empty args - goes to the 'else' branch which returns 'Edit'
189+
assert.strictEqual(result.toolName, 'Edit');
190+
});
191+
192+
it('should handle repository root paths correctly', function () {
193+
const toolCall = {
194+
function: {
195+
name: 'str_replace_editor',
196+
arguments: '{"command": "view", "path": "/home/runner/work/repo/repo/"}'
197+
},
198+
id: 'call_root',
199+
type: 'function',
200+
index: 0
201+
};
202+
203+
const result = parseToolCallDetails(toolCall, '');
204+
205+
assert.strictEqual(result.toolName, 'Read repository');
206+
assert.strictEqual(result.invocationMessage, 'Read repository');
207+
});
208+
});
209+
210+
describe('parseDiff()', function () {
211+
it('should parse diff content correctly', function () {
212+
const diffContent = `diff --git a/src/file.ts b/src/file.ts
213+
index 1234567..abcdefg 100644
214+
--- a/src/file.ts
215+
+++ b/src/file.ts
216+
@@ -1,4 +1,4 @@
217+
export function hello() {
218+
- console.log('hello');
219+
+ console.log('hello world');
220+
}`;
221+
222+
const result = parseDiff(diffContent);
223+
224+
assert(result);
225+
assert.strictEqual(result.fileA, '/src/file.ts');
226+
assert.strictEqual(result.fileB, '/src/file.ts');
227+
assert(result.content.includes("export function hello()"));
228+
assert(result.content.includes("console.log('hello world')"));
229+
});
230+
231+
it('should extract file paths from diff headers', function () {
232+
const diffContent = `diff --git a/package.json b/package.json
233+
index 1111111..2222222 100644
234+
--- a/package.json
235+
+++ b/package.json
236+
@@ -1,5 +1,5 @@
237+
{
238+
"name": "test"
239+
}`;
240+
241+
const result = parseDiff(diffContent);
242+
243+
assert(result);
244+
assert.strictEqual(result.fileA, '/package.json');
245+
assert.strictEqual(result.fileB, '/package.json');
246+
});
247+
248+
it('should handle malformed diffs', function () {
249+
const diffContent = `not a diff at all`;
250+
251+
const result = parseDiff(diffContent);
252+
253+
assert.strictEqual(result, undefined);
254+
});
255+
256+
it('should handle diffs without @@ lines', function () {
257+
const diffContent = `diff --git a/file.txt b/file.txt
258+
index 1234567..abcdefg 100644
259+
--- a/file.txt
260+
+++ b/file.txt`;
261+
262+
const result = parseDiff(diffContent);
263+
264+
assert.strictEqual(result, undefined);
265+
});
266+
});
267+
268+
describe('toFileLabel()', function () {
269+
it('should convert absolute paths to relative labels', function () {
270+
const path = '/home/runner/work/repo/repo/src/components/Button.tsx';
271+
272+
const result = toFileLabel(path);
273+
274+
assert.strictEqual(result, 'src/components/Button.tsx');
275+
});
276+
277+
it('should handle various path formats', function () {
278+
assert.strictEqual(toFileLabel('/home/runner/work/repo/repo/package.json'), 'package.json');
279+
assert.strictEqual(toFileLabel('/home/runner/work/repo/repo/src/index.ts'), 'src/index.ts');
280+
assert.strictEqual(toFileLabel('/home/runner/work/repo/repo/docs/README.md'), 'docs/README.md');
281+
});
282+
283+
it('should handle edge cases', function () {
284+
assert.strictEqual(toFileLabel('/home/runner/work/repo/repo/'), '');
285+
assert.strictEqual(toFileLabel('/'), '');
286+
assert.strictEqual(toFileLabel(''), '');
287+
});
288+
289+
it('should handle shorter paths', function () {
290+
const shortPath = '/home/runner/work/repo';
291+
292+
const result = toFileLabel(shortPath);
293+
294+
// Should return empty string when path is too short
295+
assert.strictEqual(result, '');
296+
});
297+
});
298+
});

0 commit comments

Comments
 (0)