@@ -10,6 +10,7 @@ import { NotebookDocumentSnapshot } from '../../../platform/editing/common/noteb
1010import { TextDocumentSnapshot } from '../../../platform/editing/common/textDocumentSnapshot' ;
1111import { modelShouldUseReplaceStringHealing } from '../../../platform/endpoint/common/chatModelCapabilities' ;
1212import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider' ;
13+ import { isScenarioAutomation } from '../../../platform/env/common/envService' ;
1314import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService' ;
1415import { ILanguageDiagnosticsService } from '../../../platform/languages/common/languageDiagnosticsService' ;
1516import { ILogService } from '../../../platform/log/common/logService' ;
@@ -28,7 +29,7 @@ import { extUriBiasedIgnorePathCase } from '../../../util/vs/base/common/resourc
2829import { isDefined } from '../../../util/vs/base/common/types' ;
2930import { URI } from '../../../util/vs/base/common/uri' ;
3031import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation' ;
31- import { ChatRequestEditorData , ChatResponseTextEditPart , EndOfLine , ExtendedLanguageModelToolResult , Position as ExtPosition , LanguageModelPromptTsxPart , LanguageModelToolResult , TextEdit } from '../../../vscodeTypes' ;
32+ import { ChatRequestEditorData , ChatResponseTextEditPart , EndOfLine , ExtendedLanguageModelToolResult , Position as ExtPosition , LanguageModelPromptTsxPart , LanguageModelToolResult , TextEdit , WorkspaceEdit } from '../../../vscodeTypes' ;
3233import { IBuildPromptContext } from '../../prompt/common/intents' ;
3334import { renderPromptElementJSON } from '../../prompts/node/base/promptRenderer' ;
3435import { CellOrNotebookEdit , processFullRewriteNotebookEdits } from '../../prompts/node/codeMapper/codeMapper' ;
@@ -173,7 +174,7 @@ export abstract class AbstractReplaceStringTool<T extends { explanation: string
173174 }
174175
175176 // Validate parameters
176- if ( ! input . filePath || input . oldString === undefined || input . newString === undefined || ! this . _promptContext ) {
177+ if ( ! input . filePath || input . oldString === undefined || input . newString === undefined || ( ! this . _promptContext && ! isScenarioAutomation ) ) {
177178 this . sendReplaceTelemetry ( 'invalidStrings' , options , input , undefined , undefined , undefined ) ;
178179 throw new Error ( 'Invalid input' ) ;
179180 }
@@ -207,12 +208,12 @@ export abstract class AbstractReplaceStringTool<T extends { explanation: string
207208 const model = await this . modelForTelemetry ( options ) ;
208209 const telemetryOptions : NotebookEditGenerationTelemtryOptions = {
209210 model,
210- requestId : this . _promptContext . requestId ,
211+ requestId : this . _promptContext ? .requestId ,
211212 source : NotebookEditGenrationSource . stringReplace ,
212213 } ;
213214
214215 notebookEdits = await Iterable . asyncToArray ( processFullRewriteNotebookEdits ( document . document , updatedFile , this . alternativeNotebookEditGenerator , telemetryOptions , token ) ) ;
215- sendEditNotebookTelemetry ( this . telemetryService , this . endpointProvider , 'stringReplace' , document . uri , this . _promptContext . requestId , model || 'unknown' ) ;
216+ sendEditNotebookTelemetry ( this . telemetryService , this . endpointProvider , 'stringReplace' , document . uri , this . _promptContext ? .requestId , model || 'unknown' ) ;
216217 updated = NotebookDocumentSnapshot . fromNewText ( updatedFile , document ) ;
217218 } else {
218219 updated = TextDocumentSnapshot . fromNewText ( updatedFile , document ) ;
@@ -255,12 +256,67 @@ export abstract class AbstractReplaceStringTool<T extends { explanation: string
255256 }
256257
257258 protected async applyAllEdits ( options : vscode . LanguageModelToolInvocationOptions < T > , edits : IPrepareEdit [ ] , token : vscode . CancellationToken ) {
258- if ( ! this . _promptContext ?. stream ) {
259+ const hasStream = ! ! this . _promptContext ?. stream ;
260+ if ( ! hasStream && ! isScenarioAutomation ) {
259261 throw new Error ( 'no prompt context found' ) ;
260262 }
261263
262264 logEditToolResult ( this . logService , options . chatRequestId , ...edits . map ( e => ( { input : e . input , success : e . generatedEdit . success , healed : e . healed } ) ) ) ;
263265
266+ // Scenario automation / headless mode: apply edits directly without streaming
267+ if ( ! hasStream ) {
268+ const fileResults : IEditedFile [ ] = [ ] ;
269+ const workspaceEdit = new WorkspaceEdit ( ) ;
270+
271+ for ( const { document, uri, generatedEdit, healed } of edits ) {
272+ const isNotebook = this . notebookService . hasSupportedNotebooks ( uri ) ;
273+ const existingDiagnostics = document ? this . languageDiagnosticsService . getDiagnostics ( document . uri ) : [ ] ;
274+
275+ if ( ! generatedEdit . success ) {
276+ fileResults . push ( { operation : ActionType . UPDATE , uri, isNotebook, existingDiagnostics, error : generatedEdit . errorMessage } ) ;
277+ continue ;
278+ }
279+
280+ if ( generatedEdit . textEdits ) {
281+ for ( const edit of generatedEdit . textEdits ) {
282+ workspaceEdit . replace ( uri , edit . range , edit . newText ) ;
283+ }
284+ }
285+
286+ fileResults . push ( {
287+ operation : ActionType . UPDATE ,
288+ uri,
289+ isNotebook,
290+ existingDiagnostics,
291+ healed : healed ? JSON . stringify ( { oldString : healed . oldString , newString : healed . newString } , null , 2 ) : undefined
292+ } ) ;
293+ }
294+
295+ await this . workspaceService . applyEdit ( workspaceEdit ) ;
296+
297+ const result = new ExtendedLanguageModelToolResult ( [
298+ new LanguageModelPromptTsxPart (
299+ await renderPromptElementJSON (
300+ this . instantiationService ,
301+ EditFileResult ,
302+ { files : fileResults , diagnosticsTimeout : 2000 , toolName : this . toolName ( ) , requestId : options . chatRequestId , model : options . model } ,
303+ options . tokenizationOptions ?? {
304+ tokenBudget : 5000 ,
305+ countTokens : ( t ) => Promise . resolve ( t . length * 3 / 4 )
306+ } ,
307+ token ,
308+ ) ,
309+ )
310+ ] ) ;
311+ result . hasError = fileResults . some ( f => f . error ) ;
312+ return result ;
313+ }
314+
315+ // Stream path: _promptContext is guaranteed to exist since hasStream is true
316+ if ( ! this . _promptContext ?. stream ) {
317+ throw new Error ( 'no prompt context found' ) ;
318+ }
319+
264320 const fileResults : IEditedFile [ ] = [ ] ;
265321 const existingDiagnosticMap = new ResourceMap < vscode . Diagnostic [ ] > ( ) ;
266322
0 commit comments