Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/intents/test/node/toolCallingLoopUsage.spec.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
import { Raw } from '@vscode/prompt-tsx';
7
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
8
import type { ChatRequest, LanguageModelChat, LanguageModelToolInformation } from 'vscode';
9
import { ChatFetchResponseType, ChatResponse } from '../../../../platform/chat/common/commonTypes';
10
import { toTextPart } from '../../../../platform/chat/common/globalStringUtils';
11
import { ITestingServicesAccessor } from '../../../../platform/test/node/services';
12
import { ChatResponseStreamImpl } from '../../../../util/common/chatResponseStreamImpl';
13
import { CancellationTokenSource } from '../../../../util/vs/base/common/cancellation';
14
import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';
15
import { generateUuid } from '../../../../util/vs/base/common/uuid';
16
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
17
import { Conversation, Turn } from '../../../prompt/common/conversation';
18
import { IBuildPromptContext } from '../../../prompt/common/intents';
19
import { IBuildPromptResult, nullRenderPromptResult } from '../../../prompt/node/intents';
20
import { createExtensionUnitTestingServices } from '../../../test/node/services';
21
import { IToolCallingLoopOptions, ToolCallingLoop } from '../../node/toolCallingLoop';
22
23
class UsageCapturingStream extends ChatResponseStreamImpl {
24
public readonly usages: Array<{ promptTokens: number; completionTokens: number }>;
25
26
constructor() {
27
const usages: Array<{ promptTokens: number; completionTokens: number }> = [];
28
super(
29
() => { },
30
() => { },
31
undefined,
32
undefined,
33
undefined,
34
() => Promise.resolve(undefined),
35
(usage) => {
36
usages.push({
37
promptTokens: usage.promptTokens,
38
completionTokens: usage.completionTokens
39
});
40
}
41
);
42
this.usages = usages;
43
}
44
}
45
46
class UsageTestToolCallingLoop extends ToolCallingLoop<IToolCallingLoopOptions> {
47
protected override async buildPrompt(_buildPromptContext: IBuildPromptContext): Promise<IBuildPromptResult> {
48
return {
49
...nullRenderPromptResult(),
50
messages: [{ role: Raw.ChatRole.User, content: [toTextPart('hello world')] }],
51
};
52
}
53
54
protected override async getAvailableTools(): Promise<LanguageModelToolInformation[]> {
55
return [];
56
}
57
58
protected override async fetch(): Promise<ChatResponse> {
59
return {
60
type: ChatFetchResponseType.Success,
61
value: 'test-response',
62
requestId: 'request-id',
63
serverRequestId: undefined,
64
usage: {
65
prompt_tokens: 100,
66
completion_tokens: 20,
67
total_tokens: 120
68
},
69
resolvedModel: 'gpt-4.1'
70
};
71
}
72
}
73
74
const chatPanelLocation: ChatRequest['location'] = 1;
75
76
function createMockChatRequest(overrides: Partial<ChatRequest> = {}): ChatRequest {
77
return {
78
prompt: 'test prompt',
79
command: undefined,
80
references: [],
81
location: chatPanelLocation,
82
location2: undefined,
83
attempt: 0,
84
enableCommandDetection: false,
85
isParticipantDetected: false,
86
toolReferences: [],
87
toolInvocationToken: {} as ChatRequest['toolInvocationToken'],
88
model: { family: 'test' } as LanguageModelChat,
89
tools: new Map(),
90
id: generateUuid(),
91
sessionId: generateUuid(),
92
sessionResource: {} as ChatRequest['sessionResource'],
93
hasHooksEnabled: false,
94
...overrides,
95
} satisfies ChatRequest;
96
}
97
98
function createConversation(prompt: string): Conversation {
99
return new Conversation(generateUuid(), [
100
new Turn(generateUuid(), { type: 'user', message: prompt })
101
]);
102
}
103
104
describe('ToolCallingLoop usage reporting', () => {
105
let disposables: DisposableStore;
106
let accessor: ITestingServicesAccessor;
107
let instantiationService: IInstantiationService;
108
let tokenSource: CancellationTokenSource;
109
110
beforeEach(() => {
111
disposables = new DisposableStore();
112
const serviceCollection = disposables.add(createExtensionUnitTestingServices());
113
accessor = serviceCollection.createTestingAccessor();
114
instantiationService = accessor.get(IInstantiationService);
115
tokenSource = new CancellationTokenSource();
116
disposables.add(tokenSource);
117
});
118
119
afterEach(() => {
120
accessor.dispose();
121
disposables.dispose();
122
});
123
124
it('reports usage for regular requests', async () => {
125
const request = createMockChatRequest();
126
const loop = instantiationService.createInstance(
127
UsageTestToolCallingLoop,
128
{
129
conversation: createConversation(request.prompt),
130
toolCallLimit: 1,
131
request,
132
}
133
);
134
disposables.add(loop);
135
const stream = new UsageCapturingStream();
136
137
await loop.runOne(stream, 0, tokenSource.token);
138
139
expect(stream.usages).toEqual([{ promptTokens: 100, completionTokens: 20 }]);
140
});
141
142
it('does not report usage for subagent requests', async () => {
143
const request = createMockChatRequest({
144
subAgentInvocationId: 'subagent-usage-test',
145
subAgentName: 'search'
146
});
147
const loop = instantiationService.createInstance(
148
UsageTestToolCallingLoop,
149
{
150
conversation: createConversation(request.prompt),
151
toolCallLimit: 1,
152
request,
153
}
154
);
155
disposables.add(loop);
156
const stream = new UsageCapturingStream();
157
158
await loop.runOne(stream, 0, tokenSource.token);
159
160
expect(stream.usages).toHaveLength(0);
161
});
162
});
163
164