Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/networking/common/openai.ts
13401 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 { OpenAI, OutputMode, Raw, toMode } from '@vscode/prompt-tsx';
7
import { ChatCompletionContentPartImage } from '@vscode/prompt-tsx/dist/base/output/openaiTypes';
8
import { ChatCompletionContentPartKind } from '@vscode/prompt-tsx/dist/base/output/rawTypes';
9
import { rawPartAsThinkingData } from '../../endpoint/common/thinkingDataContainer';
10
import { TelemetryData } from '../../telemetry/common/telemetryData';
11
import { ThinkingData, ThinkingDataInMessage } from '../../thinking/common/thinking';
12
import { ICopilotReference, RequestId } from './fetch';
13
14
/**
15
* How the logprobs field looks in the OpenAI API chunks.
16
*/
17
export interface APILogprobs {
18
text_offset: number[];
19
token_logprobs: number[];
20
top_logprobs?: { [key: string]: number }[];
21
tokens: string[];
22
}
23
24
/**
25
* Usage statistics for the completion request.
26
*/
27
export interface APIUsage {
28
/**
29
* Number of tokens in the prompt.
30
*/
31
prompt_tokens: number;
32
/**
33
* Number of tokens in the generated completion.
34
*/
35
completion_tokens: number;
36
/**
37
* Total number of tokens used in the request (prompt + completion).
38
*/
39
total_tokens: number;
40
/**
41
* Breakdown of tokens used in the prompt.
42
*/
43
prompt_tokens_details?: {
44
cached_tokens: number;
45
cache_creation_input_tokens?: number;
46
};
47
/**
48
* Breakdown of tokens used in a completion.
49
*
50
* @remark it's an optional field because Copilot Proxy returns this information but not CAPI as of 18 Jun 2025
51
*/
52
completion_tokens_details?: {
53
/**
54
* Tokens generated by the model for reasoning.
55
*/
56
reasoning_tokens: number;
57
/**
58
* When using Predicted Outputs, the number of tokens in the prediction that appeared in the completion.
59
*/
60
accepted_prediction_tokens: number;
61
/**
62
* When using Predicted Outputs, the number of tokens in the prediction that did not appear in the completion.
63
* However, like reasoning tokens, these tokens are still counted in the total completion tokens for purposes of billing,
64
* output, and context window limits.
65
*/
66
rejected_prediction_tokens: number;
67
};
68
}
69
70
export function isApiUsage(obj: unknown): obj is APIUsage {
71
return typeof (obj as APIUsage).prompt_tokens === 'number' &&
72
typeof (obj as APIUsage).completion_tokens === 'number' &&
73
typeof (obj as APIUsage).total_tokens === 'number';
74
}
75
76
77
export interface APIJsonData {
78
text: string;
79
/* Joining this together produces `text`, due to the way the proxy works. */
80
tokens: readonly string[];
81
/* These are only generated in certain situations. */
82
logprobs?: APILogprobs;
83
}
84
85
export interface APIErrorResponse {
86
code: number;
87
message: string;
88
metadata?: Record<string, any>;
89
}
90
91
export const openAIContextManagementCompactionType = 'compaction';
92
93
export const modelsWithoutResponsesContextManagement = new Set(['gpt-5', 'gpt-5.1', 'gpt-5.2']);
94
95
96
97
export interface OpenAIContextManagement {
98
type: typeof openAIContextManagementCompactionType;
99
compact_threshold: number;
100
}
101
102
103
export interface OpenAIContextManagementResponse {
104
encrypted_content: string;
105
type: typeof openAIContextManagementCompactionType;
106
id: string;
107
}
108
109
110
export enum ChatRole {
111
System = 'system',
112
User = 'user',
113
Assistant = 'assistant',
114
Function = 'function',
115
Tool = 'tool'
116
}
117
118
119
export type CAPIChatMessage = OpenAI.ChatMessage & {
120
/**
121
* CAPI references used in this message.
122
*/
123
copilot_references?: ICopilotReference[];
124
/**
125
* CAPI confirmations used in this message.
126
*/
127
copilot_confirmations?: { state: string; confirmation: any }[];
128
129
copilot_cache_control?: {
130
'type': 'ephemeral';
131
};
132
} & ThinkingDataInMessage;
133
134
export function getCAPITextPart(content: string | OpenAI.ChatCompletionContentPart[] | OpenAI.ChatCompletionContentPart): string {
135
if (Array.isArray(content)) {
136
return content.map((part) => getCAPITextPart(part)).join('');
137
} else if (typeof content === 'string') {
138
return content;
139
} else if (typeof content === 'object' && 'text' in content) {
140
return content.text;
141
} else {
142
return '';
143
}
144
}
145
146
export type RawMessageConversionCallback = (message: CAPIChatMessage, thinkingData?: ThinkingData) => void;
147
/**
148
* Converts a raw TSX chat message to CAPI's format.
149
*
150
* **Extra:** the raw message can have `copilot_references` and
151
* `copilot_confirmations` properties, which are copied to the CAPI message.
152
*/
153
export function rawMessageToCAPI(message: Raw.ChatMessage, callback?: RawMessageConversionCallback): CAPIChatMessage;
154
export function rawMessageToCAPI(message: Raw.ChatMessage[], callback?: RawMessageConversionCallback): CAPIChatMessage[];
155
export function rawMessageToCAPI(message: Raw.ChatMessage[] | Raw.ChatMessage, callback?: RawMessageConversionCallback): CAPIChatMessage | CAPIChatMessage[] {
156
if (Array.isArray(message)) {
157
return message.map(m => rawMessageToCAPI(m, callback));
158
}
159
160
const out: CAPIChatMessage = toMode(OutputMode.OpenAI, message);
161
if ('copilot_references' in message) {
162
out.copilot_references = (message as any).copilot_references;
163
}
164
if ('copilot_confirmations' in message) {
165
out.copilot_confirmations = (message as any).copilot_confirmations;
166
}
167
if (typeof out.content === 'string') {
168
out.content = out.content.trimEnd();
169
} else {
170
for (let i = 0; i < out.content.length; i++) {
171
const part = out.content[i];
172
if (part.type === 'text') {
173
part.text = part.text.trimEnd();
174
} else if (part.type === 'image_url' && Array.isArray(message.content) && i < message.content.length) {
175
const rawPart = message.content[i] as Raw.ChatCompletionContentPart;
176
if (rawPart?.type === Raw.ChatCompletionContentPartKind.Image && rawPart.imageUrl?.mediaType) {
177
// CAPI expects `media_type` instead of `mediaType`. This is only used for CAPI and not OpenAI.
178
const { mediaType, ...rawImageUrl } = rawPart.imageUrl;
179
(part.image_url as ChatCompletionContentPartImage.ImageURL & { media_type: string }) = {
180
...rawImageUrl,
181
media_type: mediaType
182
};
183
}
184
}
185
}
186
}
187
188
if (message.content.find(part => part.type === ChatCompletionContentPartKind.CacheBreakpoint)) {
189
out.copilot_cache_control = { type: 'ephemeral' };
190
}
191
192
for (const content of message.content) {
193
if (content.type === Raw.ChatCompletionContentPartKind.Opaque) {
194
const data = rawPartAsThinkingData(content);
195
if (callback && data) {
196
callback(out, data);
197
}
198
}
199
}
200
201
return out;
202
}
203
204
export enum FinishedCompletionReason {
205
/**
206
* Reason generated by the server. See https://platform.openai.com/docs/guides/gpt/chat-completions-api
207
*/
208
Stop = 'stop',
209
/**
210
* Reason generated by the server. See https://platform.openai.com/docs/guides/gpt/chat-completions-api
211
*/
212
Length = 'length',
213
/**
214
* Reason generated by the server. See https://platform.openai.com/docs/guides/gpt/chat-completions-api
215
*/
216
FunctionCall = 'function_call',
217
/**
218
* Reason generated by the server. See https://platform.openai.com/docs/guides/gpt/chat-completions-api
219
*/
220
ToolCalls = 'tool_calls',
221
/**
222
* Reason generated by the server. See https://platform.openai.com/docs/guides/gpt/chat-completions-api
223
*/
224
ContentFilter = 'content_filter',
225
/**
226
* Reason generated by the server (CAPI). Happens when the stream cannot be completed and the server must terminate the response.
227
*/
228
ServerError = 'error',
229
/**
230
* Reason generated by the client when the finish callback asked for processing to stop.
231
*/
232
ClientTrimmed = 'client-trimmed',
233
/**
234
* Reason generated by the client when we never received a finish_reason for this particular completion (indicates a server-side bug)
235
*/
236
ClientIterationDone = 'Iteration Done',
237
/**
238
* Reason generated by the client when we never received a finish_reason for this particular completion (indicates a server-side bug)
239
*/
240
ClientDone = 'DONE',
241
}
242
243
export interface IToolCall {
244
index: number;
245
id?: string;
246
function?: { name: string; arguments: string };
247
}
248
249
/**
250
* Contains the possible reasons a response can be filtered
251
*/
252
export enum FilterReason {
253
/**
254
* Content deemed to be hateful
255
*/
256
Hate = 'hate',
257
/**
258
* Content deemed to cause self harm
259
*/
260
SelfHarm = 'self_harm',
261
/**
262
* Content deemed to be sexual in nature
263
*/
264
Sexual = 'sexual',
265
/**
266
* Content deemed to be violent in nature
267
*/
268
Violence = 'violence',
269
/**
270
* Content contains copyrighted material
271
*/
272
Copyright = 'snippy',
273
/**
274
* The prompt was filtered, the reason was not provided
275
*/
276
Prompt = 'prompt'
277
}
278
279
export interface ChatCompletion {
280
message: Raw.ChatMessage;
281
choiceIndex: number;
282
requestId: RequestId;
283
tokens: readonly string[];
284
usage: APIUsage | undefined;
285
model: string;
286
blockFinished: boolean; // Whether the block completion was determined to be finished
287
finishReason: FinishedCompletionReason;
288
filterReason?: FilterReason; // optional filter reason if the completion was filtered
289
telemetryData: TelemetryData; // optional telemetry data providing background
290
error?: APIErrorResponse; // optional, error was encountered during the response
291
}
292
293
export interface ChoiceLogProbs {
294
content: ChoiceLogProbsContent[];
295
}
296
297
export interface TokenLogProb {
298
bytes: number[];
299
token: string;
300
logprob: number;
301
}
302
303
export interface ChoiceLogProbsContent extends TokenLogProb {
304
top_logprobs: TokenLogProb[];
305
}
306
307