Skip to content

Commit b449c1a

Browse files
osortegaalexr00
andauthored
Copilot remote agent tests (#7528)
* Copilot remote agent tests * Add reference types (#7530) * Moving diffs to fixture --------- Co-authored-by: Alex Ross <38270282+alexr00@users.noreply.github.com>
1 parent 46bbef3 commit b449c1a

6 files changed

Lines changed: 1076 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.LanguageModelPartAudience.User;
133+
lmResult.push(new vscode.LanguageModelDataPart2(data, 'application/pull-request+json', [userAudience]));
131134
}
132135

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

0 commit comments

Comments
 (0)