@@ -21,6 +21,7 @@ import {
2121 docsRequestChatResult ,
2222 schemaRequestChatResult ,
2323 createCancelledRequestChatResult ,
24+ codeBlockIdentifier ,
2425} from './constants' ;
2526import { SchemaFormatter } from './schema' ;
2627import { getSimplifiedSampleDocuments } from './sampleDocuments' ;
@@ -38,7 +39,8 @@ import {
3839} from '../telemetry/telemetryService' ;
3940import { DocsChatbotAIService } from './docsChatbotAIService' ;
4041import type TelemetryService from '../telemetry/telemetryService' ;
41- import { IntentPrompt , type PromptIntent } from './prompts/intent' ;
42+ import { processStreamWithIdentifiers } from './streamParsing' ;
43+ import type { PromptIntent } from './prompts/intent' ;
4244
4345const log = createLogger ( 'participant' ) ;
4446
@@ -59,16 +61,6 @@ export type ParticipantCommand = '/query' | '/schema' | '/docs';
5961
6062const MAX_MARKDOWN_LIST_LENGTH = 10 ;
6163
62- export function getRunnableContentFromString ( text : string ) : string {
63- const matchedJSresponseContent = text . match ( / ` ` ` j a v a s c r i p t ( ( .| \n ) * ) ` ` ` / ) ;
64-
65- const code =
66- matchedJSresponseContent && matchedJSresponseContent . length > 1
67- ? matchedJSresponseContent [ 1 ]
68- : '' ;
69- return code . trim ( ) ;
70- }
71-
7264export default class ParticipantController {
7365 _participant ?: vscode . ChatParticipant ;
7466 _connectionController : ConnectionController ;
@@ -171,48 +163,113 @@ export default class ParticipantController {
171163 } ) ;
172164 }
173165
174- async getChatResponseContent ( {
166+ async _getChatResponse ( {
175167 messages,
176168 token,
177169 } : {
178170 messages : vscode . LanguageModelChatMessage [ ] ;
179171 token : vscode . CancellationToken ;
180- } ) : Promise < string > {
172+ } ) : Promise < vscode . LanguageModelChatResponse > {
181173 const model = await getCopilotModel ( ) ;
182- let responseContent = '' ;
183- if ( model ) {
184- const chatResponse = await model . sendRequest ( messages , { } , token ) ;
185- for await ( const fragment of chatResponse . text ) {
186- responseContent += fragment ;
187- }
174+
175+ if ( ! model ) {
176+ throw new Error ( 'Copilot model not found' ) ;
188177 }
189178
190- return responseContent ;
179+ return await model . sendRequest ( messages , { } , token ) ;
191180 }
192181
193- _streamRunnableContentActions ( {
194- responseContent ,
182+ async streamChatResponse ( {
183+ messages ,
195184 stream,
185+ token,
196186 } : {
197- responseContent : string ;
187+ messages : vscode . LanguageModelChatMessage [ ] ;
188+ stream : vscode . ChatResponseStream ;
189+ token : vscode . CancellationToken ;
190+ } ) : Promise < void > {
191+ const chatResponse = await this . _getChatResponse ( {
192+ messages,
193+ token,
194+ } ) ;
195+ for await ( const fragment of chatResponse . text ) {
196+ stream . markdown ( fragment ) ;
197+ }
198+ }
199+
200+ _streamCodeBlockActions ( {
201+ runnableContent,
202+ stream,
203+ } : {
204+ runnableContent : string ;
198205 stream : vscode . ChatResponseStream ;
199206 } ) : void {
200- const runnableContent = getRunnableContentFromString ( responseContent ) ;
201- if ( runnableContent ) {
202- const commandArgs : RunParticipantQueryCommandArgs = {
203- runnableContent,
204- } ;
205- stream . button ( {
206- command : EXTENSION_COMMANDS . RUN_PARTICIPANT_QUERY ,
207- title : vscode . l10n . t ( '▶️ Run' ) ,
208- arguments : [ commandArgs ] ,
209- } ) ;
210- stream . button ( {
211- command : EXTENSION_COMMANDS . OPEN_PARTICIPANT_QUERY_IN_PLAYGROUND ,
212- title : vscode . l10n . t ( 'Open in playground' ) ,
213- arguments : [ commandArgs ] ,
214- } ) ;
207+ runnableContent = runnableContent . trim ( ) ;
208+
209+ if ( ! runnableContent ) {
210+ return ;
215211 }
212+
213+ const commandArgs : RunParticipantQueryCommandArgs = {
214+ runnableContent,
215+ } ;
216+ stream . button ( {
217+ command : EXTENSION_COMMANDS . RUN_PARTICIPANT_QUERY ,
218+ title : vscode . l10n . t ( '▶️ Run' ) ,
219+ arguments : [ commandArgs ] ,
220+ } ) ;
221+ stream . button ( {
222+ command : EXTENSION_COMMANDS . OPEN_PARTICIPANT_QUERY_IN_PLAYGROUND ,
223+ title : vscode . l10n . t ( 'Open in playground' ) ,
224+ arguments : [ commandArgs ] ,
225+ } ) ;
226+ }
227+
228+ async streamChatResponseContentWithCodeActions ( {
229+ messages,
230+ stream,
231+ token,
232+ } : {
233+ messages : vscode . LanguageModelChatMessage [ ] ;
234+ stream : vscode . ChatResponseStream ;
235+ token : vscode . CancellationToken ;
236+ } ) : Promise < void > {
237+ const chatResponse = await this . _getChatResponse ( {
238+ messages,
239+ token,
240+ } ) ;
241+
242+ await processStreamWithIdentifiers ( {
243+ processStreamFragment : ( fragment : string ) => {
244+ stream . markdown ( fragment ) ;
245+ } ,
246+ onStreamIdentifier : ( content : string ) => {
247+ this . _streamCodeBlockActions ( { runnableContent : content , stream } ) ;
248+ } ,
249+ inputIterable : chatResponse . text ,
250+ identifier : codeBlockIdentifier ,
251+ } ) ;
252+ }
253+
254+ // This will stream all of the response content and create a string from it.
255+ // It should only be used when the entire response is needed at one time.
256+ async getChatResponseContent ( {
257+ messages,
258+ token,
259+ } : {
260+ messages : vscode . LanguageModelChatMessage [ ] ;
261+ token : vscode . CancellationToken ;
262+ } ) : Promise < string > {
263+ let responseContent = '' ;
264+ const chatResponse = await this . _getChatResponse ( {
265+ messages,
266+ token,
267+ } ) ;
268+ for await ( const fragment of chatResponse . text ) {
269+ responseContent += fragment ;
270+ }
271+
272+ return responseContent ;
216273 }
217274
218275 async _handleRoutedGenericRequest (
@@ -227,14 +284,9 @@ export default class ParticipantController {
227284 connectionNames : this . _getConnectionNames ( ) ,
228285 } ) ;
229286
230- const responseContent = await this . getChatResponseContent ( {
287+ await this . streamChatResponseContentWithCodeActions ( {
231288 messages,
232289 token,
233- } ) ;
234- stream . markdown ( responseContent ) ;
235-
236- this . _streamRunnableContentActions ( {
237- responseContent,
238290 stream,
239291 } ) ;
240292
@@ -293,7 +345,7 @@ export default class ParticipantController {
293345 token,
294346 } ) ;
295347
296- return IntentPrompt . getIntentFromModelResponse ( responseContent ) ;
348+ return Prompts . intent . getIntentFromModelResponse ( responseContent ) ;
297349 }
298350
299351 async handleGenericRequest (
@@ -1001,11 +1053,11 @@ export default class ParticipantController {
10011053 connectionNames : this . _getConnectionNames ( ) ,
10021054 ...( sampleDocuments ? { sampleDocuments } : { } ) ,
10031055 } ) ;
1004- const responseContent = await this . getChatResponseContent ( {
1056+ await this . streamChatResponse ( {
10051057 messages,
1058+ stream,
10061059 token,
10071060 } ) ;
1008- stream . markdown ( responseContent ) ;
10091061
10101062 stream . button ( {
10111063 command : EXTENSION_COMMANDS . PARTICIPANT_OPEN_RAW_SCHEMA_OUTPUT ,
@@ -1104,16 +1156,11 @@ export default class ParticipantController {
11041156 connectionNames : this . _getConnectionNames ( ) ,
11051157 ...( sampleDocuments ? { sampleDocuments } : { } ) ,
11061158 } ) ;
1107- const responseContent = await this . getChatResponseContent ( {
1108- messages,
1109- token,
1110- } ) ;
1111-
1112- stream . markdown ( responseContent ) ;
11131159
1114- this . _streamRunnableContentActions ( {
1115- responseContent ,
1160+ await this . streamChatResponseContentWithCodeActions ( {
1161+ messages ,
11161162 stream,
1163+ token,
11171164 } ) ;
11181165
11191166 return queryRequestChatResult ( context . history ) ;
@@ -1181,32 +1228,41 @@ export default class ParticipantController {
11811228 vscode . ChatResponseStream ,
11821229 vscode . CancellationToken
11831230 ]
1184- ) : Promise < {
1185- responseContent : string ;
1186- responseReferences ?: Reference [ ] ;
1187- } > {
1188- const [ request , context , , token ] = args ;
1231+ ) : Promise < void > {
1232+ const [ request , context , stream , token ] = args ;
11891233 const messages = await Prompts . generic . buildMessages ( {
11901234 request,
11911235 context,
11921236 connectionNames : this . _getConnectionNames ( ) ,
11931237 } ) ;
11941238
1195- const responseContent = await this . getChatResponseContent ( {
1239+ await this . streamChatResponseContentWithCodeActions ( {
11961240 messages,
1241+ stream,
11971242 token,
11981243 } ) ;
1199- const responseReferences = [
1200- {
1244+
1245+ this . _streamResponseReference ( {
1246+ reference : {
12011247 url : MONGODB_DOCS_LINK ,
12021248 title : 'View MongoDB documentation' ,
12031249 } ,
1204- ] ;
1250+ stream,
1251+ } ) ;
1252+ }
12051253
1206- return {
1207- responseContent,
1208- responseReferences,
1209- } ;
1254+ _streamResponseReference ( {
1255+ reference,
1256+ stream,
1257+ } : {
1258+ reference : Reference ;
1259+ stream : vscode . ChatResponseStream ;
1260+ } ) : void {
1261+ const link = new vscode . MarkdownString (
1262+ `- [${ reference . title } ](${ reference . url } )\n`
1263+ ) ;
1264+ link . supportHtml = true ;
1265+ stream . markdown ( link ) ;
12101266 }
12111267
12121268 async handleDocsRequest (
@@ -1235,6 +1291,19 @@ export default class ParticipantController {
12351291 token,
12361292 stream,
12371293 } ) ;
1294+
1295+ if ( docsResult . responseReferences ) {
1296+ for ( const reference of docsResult . responseReferences ) {
1297+ this . _streamResponseReference ( {
1298+ reference,
1299+ stream,
1300+ } ) ;
1301+ }
1302+ }
1303+
1304+ if ( docsResult . responseContent ) {
1305+ stream . markdown ( docsResult . responseContent ) ;
1306+ }
12381307 } catch ( error ) {
12391308 // If the docs chatbot API is not available, fall back to Copilot’s LLM and include
12401309 // the MongoDB documentation link for users to go to our documentation site directly.
@@ -1255,25 +1324,7 @@ export default class ParticipantController {
12551324 }
12561325 ) ;
12571326
1258- docsResult = await this . _handleDocsRequestWithCopilot ( ...args ) ;
1259- }
1260-
1261- if ( docsResult . responseContent ) {
1262- stream . markdown ( docsResult . responseContent ) ;
1263- this . _streamRunnableContentActions ( {
1264- responseContent : docsResult . responseContent ,
1265- stream,
1266- } ) ;
1267- }
1268-
1269- if ( docsResult . responseReferences ) {
1270- for ( const ref of docsResult . responseReferences ) {
1271- const link = new vscode . MarkdownString (
1272- `- [${ ref . title } ](${ ref . url } )\n`
1273- ) ;
1274- link . supportHtml = true ;
1275- stream . markdown ( link ) ;
1276- }
1327+ await this . _handleDocsRequestWithCopilot ( ...args ) ;
12771328 }
12781329
12791330 return docsRequestChatResult ( {
0 commit comments