Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/chat/common/chatMLFetcher.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 type { CancellationToken } from 'vscode';
7
import { createServiceIdentifier } from '../../../util/common/services';
8
import { AsyncIterableObject, AsyncIterableSource } from '../../../util/vs/base/common/async';
9
import { Event } from '../../../util/vs/base/common/event';
10
import { FinishedCallback, IResponseDelta, OptionalChatRequestParams } from '../../networking/common/fetch';
11
import { IChatEndpoint, IMakeChatRequestOptions } from '../../networking/common/networking';
12
import { ChatResponse, ChatResponses } from './commonTypes';
13
14
export interface Source {
15
readonly extensionId?: string;
16
}
17
18
export interface IResponsePart {
19
readonly delta: IResponseDelta;
20
}
21
22
export interface IFetchMLOptions extends IMakeChatRequestOptions {
23
endpoint: IChatEndpoint;
24
requestOptions: OptionalChatRequestParams;
25
}
26
27
28
export const IChatMLFetcher = createServiceIdentifier<IChatMLFetcher>('IChatMLFetcher');
29
30
export interface IChatMLFetcher {
31
32
readonly _serviceBrand: undefined;
33
34
readonly onDidMakeChatMLRequest: Event<{ readonly model: string; readonly source?: Source; readonly tokenCount?: number }>;
35
36
fetchOne(options: IFetchMLOptions, token: CancellationToken): Promise<ChatResponse>;
37
38
/**
39
* Note: the returned array of strings may be less than `n` (e.g., in case there were errors during streaming)
40
*/
41
fetchMany(options: IFetchMLOptions, token: CancellationToken): Promise<ChatResponses>;
42
}
43
44
interface IResponsePartWithText extends IResponsePart {
45
readonly text: string;
46
}
47
48
export class FetchStreamSource {
49
50
private _stream = new AsyncIterableSource<IResponsePart>();
51
private _paused?: (IResponsePartWithText | undefined)[];
52
53
// This means that we will only show one instance of each annotation type, but the IDs are not correct and there is no other way
54
private _seenAnnotationTypes = new Set<string>();
55
56
public get stream(): AsyncIterableObject<IResponsePart> {
57
return this._stream.asyncIterable;
58
}
59
60
constructor() { }
61
62
pause() {
63
this._paused ??= [];
64
}
65
66
unpause() {
67
const toEmit = this._paused;
68
if (!toEmit) {
69
return;
70
}
71
72
this._paused = undefined;
73
for (const part of toEmit) {
74
if (part) {
75
this.update(part.text, part.delta);
76
} else {
77
this.resolve();
78
}
79
}
80
}
81
82
update(text: string, delta: IResponseDelta): void {
83
if (this._paused) {
84
this._paused.push({ text, delta });
85
return;
86
}
87
88
if (delta.codeVulnAnnotations) {
89
// We can only display vulnerabilities inside codeblocks, and it's ok to discard annotations that fell outside of them
90
const numTripleBackticks = text.match(/(^|\n)```/g)?.length ?? 0;
91
const insideCodeblock = numTripleBackticks % 2 === 1;
92
if (!insideCodeblock || text.match(/(^|\n)```\w*\s*$/)) { // Not inside a codeblock, or right on the start triple-backtick of a codeblock
93
delta.codeVulnAnnotations = undefined;
94
}
95
}
96
97
if (delta.codeVulnAnnotations) {
98
delta.codeVulnAnnotations = delta.codeVulnAnnotations.filter(annotation => !this._seenAnnotationTypes.has(annotation.details.type));
99
delta.codeVulnAnnotations.forEach(annotation => this._seenAnnotationTypes.add(annotation.details.type));
100
}
101
this._stream.emitOne({ delta });
102
}
103
104
resolve(): void {
105
if (this._paused) {
106
this._paused.push(undefined);
107
return;
108
}
109
110
this._stream.resolve();
111
}
112
113
reject(error: Error): void {
114
this._paused = undefined;
115
this._stream.reject(error);
116
}
117
}
118
119
export class FetchStreamRecorder {
120
public readonly callback: FinishedCallback;
121
public readonly deltas: IResponseDelta[] = [];
122
123
// TTFTe
124
private _firstTokenEmittedTime: number | undefined;
125
public get firstTokenEmittedTime(): number | undefined {
126
return this._firstTokenEmittedTime;
127
}
128
129
constructor(
130
callback: FinishedCallback | undefined
131
) {
132
this.callback = async (text: string, index: number, delta: IResponseDelta): Promise<number | undefined> => {
133
if (this._firstTokenEmittedTime === undefined && (delta.text || delta.beginToolCalls || (typeof delta.thinking?.text === 'string' && delta.thinking?.text || delta.thinking?.text?.length) || delta.copilotToolCalls || delta.copilotToolCallStreamUpdates)) {
134
this._firstTokenEmittedTime = Date.now();
135
}
136
137
const result = callback ? await callback(text, index, delta) : undefined;
138
this.deltas.push(delta);
139
return result;
140
};
141
}
142
}
143
144