3
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
- import { CancellationToken } from 'vs/base/common/cancellation' ;
6
+ import { CancellationToken , CancellationTokenSource } from 'vs/base/common/cancellation' ;
7
7
import { IDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
8
8
import { ILogService } from 'vs/platform/log/common/log' ;
9
9
import { ExtHostChatProviderShape , IMainContext , MainContext , MainThreadChatProviderShape } from 'vs/workbench/api/common/extHost.protocol' ;
@@ -12,12 +12,82 @@ import type * as vscode from 'vscode';
12
12
import { Progress } from 'vs/platform/progress/common/progress' ;
13
13
import { IChatMessage , IChatResponseFragment } from 'vs/workbench/contrib/chat/common/chatProvider' ;
14
14
import { ExtensionIdentifier , ExtensionIdentifierMap } from 'vs/platform/extensions/common/extensions' ;
15
+ import { DeferredAsyncIterableObject } from 'vs/base/common/async' ;
16
+ import { Emitter } from 'vs/base/common/event' ;
15
17
16
18
type ProviderData = {
17
19
readonly extension : ExtensionIdentifier ;
18
20
readonly provider : vscode . ChatResponseProvider ;
19
21
} ;
20
22
23
+ class ChatResponseStream {
24
+
25
+ readonly apiObj : vscode . ChatResponseStream ;
26
+ readonly stream = new DeferredAsyncIterableObject < string > ( ) ;
27
+
28
+ constructor ( option : number , stream ?: DeferredAsyncIterableObject < string > ) {
29
+ this . stream = stream ?? new DeferredAsyncIterableObject < string > ( ) ;
30
+ const that = this ;
31
+ this . apiObj = {
32
+ option : option ,
33
+ response : that . stream . asyncIterable
34
+ } ;
35
+ }
36
+ }
37
+
38
+ class ChatRequest {
39
+
40
+ readonly apiObject : vscode . ChatRequest ;
41
+
42
+ private readonly _onDidStart = new Emitter < vscode . ChatResponseStream > ( ) ;
43
+ private readonly _responseStreams = new Map < number , ChatResponseStream > ( ) ;
44
+ private readonly _defaultStream = new DeferredAsyncIterableObject < string > ( ) ;
45
+ private _isDone : boolean = false ;
46
+
47
+ constructor (
48
+ promise : Promise < any > ,
49
+ cts : CancellationTokenSource
50
+ ) {
51
+ const that = this ;
52
+ this . apiObject = {
53
+ result : promise ,
54
+ response : that . _defaultStream . asyncIterable ,
55
+ onDidStartResponseStream : that . _onDidStart . event ,
56
+ cancel ( ) { cts . cancel ( ) ; } ,
57
+ } ;
58
+
59
+ promise . finally ( ( ) => {
60
+ this . _isDone = true ;
61
+ if ( this . _responseStreams . size > 0 ) {
62
+ for ( const [ , value ] of this . _responseStreams ) {
63
+ value . stream . complete ( ) ;
64
+ }
65
+ } else {
66
+ this . _defaultStream . complete ( ) ;
67
+ }
68
+ } ) ;
69
+ }
70
+
71
+ handleFragment ( fragment : IChatResponseFragment ) : void {
72
+ if ( this . _isDone ) {
73
+ return ;
74
+ }
75
+ let res = this . _responseStreams . get ( fragment . index ) ;
76
+ if ( ! res ) {
77
+ if ( this . _responseStreams . size === 0 ) {
78
+ // the first response claims the default response
79
+ res = new ChatResponseStream ( fragment . index , this . _defaultStream ) ;
80
+ } else {
81
+ res = new ChatResponseStream ( fragment . index ) ;
82
+ }
83
+ this . _responseStreams . set ( fragment . index , res ) ;
84
+ this . _onDidStart . fire ( res . apiObj ) ;
85
+ }
86
+ res . stream . emit ( fragment . part ) ;
87
+ }
88
+
89
+ }
90
+
21
91
export class ExtHostChatProvider implements ExtHostChatProviderShape {
22
92
23
93
private static _idPool = 1 ;
@@ -62,7 +132,7 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape {
62
132
63
133
//#region --- making request
64
134
65
- private readonly _pendingRequest = new Map < number , vscode . Progress < vscode . ChatResponseFragment > > ( ) ;
135
+ private readonly _pendingRequest = new Map < number , { res : ChatRequest } > ( ) ;
66
136
67
137
private readonly _chatAccessAllowList = new ExtensionIdentifierMap < Promise < unknown > > ( ) ;
68
138
@@ -84,24 +154,31 @@ export class ExtHostChatProvider implements ExtHostChatProviderShape {
84
154
get isRevoked ( ) {
85
155
return ! that . _chatAccessAllowList . has ( from ) ;
86
156
} ,
87
- async makeRequest ( messages , options , progress , token ) {
157
+ makeRequest ( messages , options , token ) {
88
158
89
159
if ( ! that . _chatAccessAllowList . has ( from ) ) {
90
160
throw new Error ( 'Access to chat has been revoked' ) ;
91
161
}
92
162
163
+ const cts = new CancellationTokenSource ( token ) ;
93
164
const requestId = ( Math . random ( ) * 1e6 ) | 0 ;
94
- that . _pendingRequest . set ( requestId , progress ) ;
95
- try {
96
- await that . _proxy . $fetchResponse ( from , identifier , requestId , messages . map ( typeConvert . ChatMessage . from ) , options , token ) ;
97
- } finally {
165
+ const requestPromise = that . _proxy . $fetchResponse ( from , identifier , requestId , messages . map ( typeConvert . ChatMessage . from ) , options ?? { } , cts . token ) ;
166
+ const res = new ChatRequest ( requestPromise , cts ) ;
167
+ that . _pendingRequest . set ( requestId , { res } ) ;
168
+
169
+ requestPromise . finally ( ( ) => {
98
170
that . _pendingRequest . delete ( requestId ) ;
99
- }
171
+ } ) ;
172
+
173
+ return res . apiObject ;
100
174
} ,
101
175
} ;
102
176
}
103
177
104
178
async $handleResponseFragment ( requestId : number , chunk : IChatResponseFragment ) : Promise < void > {
105
- this . _pendingRequest . get ( requestId ) ?. report ( chunk ) ;
179
+ const data = this . _pendingRequest . get ( requestId ) ; //.report(chunk);
180
+ if ( data ) {
181
+ data . res . handleFragment ( chunk ) ;
182
+ }
106
183
}
107
184
}
0 commit comments