Skip to content

Commit 34786eb

Browse files
authored
Merge pull request microsoft#257839 from microsoft/tyriar/257822
Count user input events and when SIGINT is used
2 parents adad3bb + 09b93c2 commit 34786eb

File tree

1 file changed

+54
-15
lines changed

1 file changed

+54
-15
lines changed

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalTool.ts

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@ export interface IRunInTerminalInputParams {
111111
isBackground: boolean;
112112
}
113113

114+
/**
115+
* A set of characters to ignore when reporting telemetry
116+
*/
117+
const telemetryIgnoredSequences = [
118+
'\x1b[I', // Focus in
119+
'\x1b[O', // Focus out
120+
];
121+
114122
export class RunInTerminalTool extends Disposable implements IToolImpl {
115123

116124
protected readonly _commandLineAutoApprover: CommandLineAutoApprover;
@@ -271,6 +279,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
271279
const termId = generateUuid();
272280

273281
if (args.isBackground) {
282+
const store = new DisposableStore();
274283
let outputAndIdle: { terminalExecutionIdleBeforeTimeout: boolean; output: string; pollDurationMs?: number; modelOutputEvalResponse?: string } | undefined = undefined;
275284

276285
this._logService.debug(`RunInTerminalTool: Creating background terminal with ID=${termId}`);
@@ -285,12 +294,23 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
285294
this._terminalService.setActiveInstance(toolTerminal.instance);
286295
const timingConnectMs = Date.now() - timingStart;
287296

297+
const xterm = await toolTerminal.instance.xtermReadyPromise;
298+
if (!xterm) {
299+
throw new Error('Instance was disposed before xterm.js was ready');
300+
}
301+
302+
let inputUserChars = 0;
303+
let inputUserSigint = false;
304+
store.add(xterm.raw.onData(data => {
305+
if (!telemetryIgnoredSequences.includes(data)) {
306+
inputUserChars += data.length;
307+
}
308+
inputUserSigint ||= data === '\x03';
309+
}));
310+
288311
try {
289312
this._logService.debug(`RunInTerminalTool: Starting background execution \`${command}\``);
290-
const xterm = await toolTerminal.instance.xtermReadyPromise;
291-
if (!xterm) {
292-
throw new Error('Instance was disposed before xterm.js was ready');
293-
}
313+
294314
const execution = new BackgroundTerminalExecution(toolTerminal.instance, xterm, command);
295315
RunInTerminalTool._backgroundExecutions.set(termId, execution);
296316
outputAndIdle = await pollForOutputAndIdle(execution, false, token, this._languageModelsService);
@@ -331,12 +351,12 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
331351
}
332352
throw e;
333353
} finally {
354+
store.dispose();
334355
this._logService.debug(`RunInTerminalTool: Finished polling \`${outputAndIdle?.output.length}\` lines of output in \`${outputAndIdle?.pollDurationMs}\``);
335356
const timingExecuteMs = Date.now() - timingStart;
336357
this._sendTelemetry(toolTerminal.instance, {
337358
didUserEditCommand,
338359
didToolEditCommand,
339-
didAcceptUserInput: false,
340360
shellIntegrationQuality: toolTerminal.shellIntegrationQuality,
341361
isBackground: true,
342362
error,
@@ -347,9 +367,12 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
347367
terminalExecutionIdleBeforeTimeout: outputAndIdle?.terminalExecutionIdleBeforeTimeout,
348368
outputLineCount: outputAndIdle?.output ? count(outputAndIdle.output, '\n') : 0,
349369
pollDurationMs: outputAndIdle?.pollDurationMs,
370+
inputUserChars,
371+
inputUserSigint,
350372
});
351373
}
352374
} else {
375+
const store = new DisposableStore();
353376
let toolTerminal: IToolTerminal | undefined = this._sessionTerminalAssociations.get(chatSessionId);
354377
const isNewSession = !toolTerminal;
355378
if (toolTerminal) {
@@ -369,14 +392,22 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
369392

370393
const timingConnectMs = Date.now() - timingStart;
371394

372-
const store = new DisposableStore();
373-
let didAcceptUserInput = false;
374395
const xterm = await toolTerminal.instance.xtermReadyPromise;
375-
if (xterm) {
376-
store.add(xterm.raw.onData(() => didAcceptUserInput = true));
396+
if (!xterm) {
397+
throw new Error('Instance was disposed before xterm.js was ready');
377398
}
378399

400+
let inputUserChars = 0;
401+
let inputUserSigint = false;
402+
store.add(xterm.raw.onData(data => {
403+
if (!telemetryIgnoredSequences.includes(data)) {
404+
inputUserChars += data.length;
405+
}
406+
inputUserSigint ||= data === '\x03';
407+
}));
408+
379409
let terminalResult = '';
410+
380411
let outputLineCount = -1;
381412
let exitCode: number | undefined;
382413
try {
@@ -423,7 +454,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
423454
this._sendTelemetry(toolTerminal.instance, {
424455
didUserEditCommand,
425456
didToolEditCommand,
426-
didAcceptUserInput,
427457
isBackground: false,
428458
shellIntegrationQuality: toolTerminal.shellIntegrationQuality,
429459
error,
@@ -432,6 +462,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
432462
exitCode,
433463
timingExecuteMs,
434464
timingConnectMs,
465+
inputUserChars,
466+
inputUserSigint,
435467
});
436468
}
437469

@@ -592,7 +624,6 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
592624
private _sendTelemetry(instance: ITerminalInstance, state: {
593625
didUserEditCommand: boolean;
594626
didToolEditCommand: boolean;
595-
didAcceptUserInput: boolean;
596627
error: string | undefined;
597628
isBackground: boolean;
598629
isNewSession: boolean;
@@ -603,6 +634,8 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
603634
terminalExecutionIdleBeforeTimeout?: boolean;
604635
timingExecuteMs: number;
605636
exitCode: number | undefined;
637+
inputUserChars: number;
638+
inputUserSigint: boolean;
606639
}) {
607640
type TelemetryEvent = {
608641
terminalSessionId: string;
@@ -611,14 +644,16 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
611644
strategy: 0 | 1 | 2;
612645
userEditedCommand: 0 | 1;
613646
toolEditedCommand: 0 | 1;
614-
acceptedUserInput: 0 | 1;
615647
isBackground: 0 | 1;
616648
isNewSession: 0 | 1;
617649
outputLineCount: number;
618650
nonZeroExitCode: -1 | 0 | 1;
619651
timingConnectMs: number;
620652
pollDurationMs: number;
621653
terminalExecutionIdleBeforeTimeout: boolean;
654+
655+
inputUserChars: number;
656+
inputUserSigint: boolean;
622657
};
623658
type TelemetryClassification = {
624659
owner: 'tyriar';
@@ -630,29 +665,33 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
630665
strategy: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'What strategy was used to execute the command (0=none, 1=basic, 2=rich)' };
631666
userEditedCommand: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the user edited the command' };
632667
toolEditedCommand: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the tool edited the command' };
633-
acceptedUserInput: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the terminal accepted user input during the execution' };
634668
isBackground: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the command is a background command' };
635669
isNewSession: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether this was the first execution for the terminal session' };
636670
outputLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How many lines of output were produced, this is -1 when isBackground is true or if there\'s an error' };
637671
nonZeroExitCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the command exited with a non-zero code (-1=error/unknown, 0=zero exit code, 1=non-zero)' };
638672
timingConnectMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How long the terminal took to start up and connect to' };
639673
pollDurationMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How long the tool polled for output, this is undefined when isBackground is true or if there\'s an error' };
640674
terminalExecutionIdleBeforeTimeout: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Indicates whether a terminal became idle before the run-in-terminal tool timed out or was cancelled by the user. This occurs when no data events are received twice consecutively and the model determines, based on terminal output, that the command has completed.' };
675+
676+
inputUserChars: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of characters the user input manually, a single key stroke could map to several characters. Focus in/out sequences are not counted as part of this' };
677+
inputUserSigint: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the user input the SIGINT signal' };
641678
};
642679
this._telemetryService.publicLog2<TelemetryEvent, TelemetryClassification>('toolUse.runInTerminal', {
643680
terminalSessionId: instance.sessionId,
644681
result: state.error ?? 'success',
645682
strategy: state.shellIntegrationQuality === ShellIntegrationQuality.Rich ? 2 : state.shellIntegrationQuality === ShellIntegrationQuality.Basic ? 1 : 0,
646683
userEditedCommand: state.didUserEditCommand ? 1 : 0,
647684
toolEditedCommand: state.didToolEditCommand ? 1 : 0,
648-
acceptedUserInput: state.didAcceptUserInput ? 1 : 0,
649685
isBackground: state.isBackground ? 1 : 0,
650686
isNewSession: state.isNewSession ? 1 : 0,
651687
outputLineCount: state.outputLineCount,
652688
nonZeroExitCode: state.exitCode === undefined ? -1 : state.exitCode === 0 ? 0 : 1,
653689
timingConnectMs: state.timingConnectMs,
654690
pollDurationMs: state.pollDurationMs ?? 0,
655-
terminalExecutionIdleBeforeTimeout: state.terminalExecutionIdleBeforeTimeout ?? false
691+
terminalExecutionIdleBeforeTimeout: state.terminalExecutionIdleBeforeTimeout ?? false,
692+
693+
inputUserChars: state.inputUserChars,
694+
inputUserSigint: state.inputUserSigint,
656695
});
657696
}
658697
}

0 commit comments

Comments
 (0)