@@ -109,12 +109,14 @@ base mixin DartToolingDaemonSupport
109109 final resource = Resource (
110110 uri: '$runtimeErrorsScheme ://${debugSession .id }' ,
111111 name: debugSession.name,
112+ description:
113+ 'Recent runtime errors seen for debug session '
114+ '"${debugSession .name }".' ,
112115 );
113116 addResource (resource, (request) async {
114- final errors = errorService.errors;
115117 return ReadResourceResult (
116118 contents: [
117- for (var error in errors)
119+ for (var error in errorService.errorLog. errors)
118120 TextResourceContents (uri: resource.uri, text: error),
119121 ],
120122 );
@@ -294,7 +296,7 @@ base mixin DartToolingDaemonSupport
294296 (await _AppErrorsListener .forVmService (
295297 vmService,
296298 this ,
297- )).errors .clear ();
299+ )).errorLog .clear ();
298300 }
299301
300302 final vm = await vmService.getVM ();
@@ -372,9 +374,9 @@ base mixin DartToolingDaemonSupport
372374 vmService,
373375 this ,
374376 );
375- final errors = errorService.errors ;
377+ final errorLog = errorService.errorLog ;
376378
377- if (errors.isEmpty) {
379+ if (errorLog. errors.isEmpty) {
378380 return CallToolResult (
379381 content: [TextContent (text: 'No runtime errors found.' )],
380382 );
@@ -383,14 +385,14 @@ base mixin DartToolingDaemonSupport
383385 content: [
384386 TextContent (
385387 text:
386- 'Found ${errors .length } '
387- 'error${errors .length == 1 ? '' : 's' }:\n ' ,
388+ 'Found ${errorLog . errors .length } '
389+ 'error${errorLog . errors .length == 1 ? '' : 's' }:\n ' ,
388390 ),
389- ...errors. map ((e) => TextContent (text: e.toString () )),
391+ for ( final e in errorLog.errors) TextContent (text: e.toString ()),
390392 ],
391393 );
392394 if (request.arguments? ['clearRuntimeErrors' ] == true ) {
393- errorService.errors .clear ();
395+ errorService.errorLog .clear ();
394396 }
395397 return result;
396398 } catch (e) {
@@ -614,9 +616,9 @@ base mixin DartToolingDaemonSupport
614616 static final getRuntimeErrorsTool = Tool (
615617 name: 'get_runtime_errors' ,
616618 description:
617- 'Retrieves the list of runtime errors that have occurred in the active '
618- 'Dart or Flutter application. Requires "${connectTool .name }" to be '
619- 'successfully called first.' ,
619+ 'Retrieves the most recent runtime errors that have occurred in the '
620+ 'active Dart or Flutter application. Requires "${connectTool .name }" to '
621+ 'be successfully called first.' ,
620622 annotations: ToolAnnotations (
621623 title: 'Get runtime errors' ,
622624 readOnlyHint: true ,
@@ -767,7 +769,7 @@ base mixin DartToolingDaemonSupport
767769/// Listens on a VM service for errors.
768770class _AppErrorsListener {
769771 /// All the errors recorded so far (may be cleared explicitly).
770- final List < String > errors ;
772+ final ErrorLog errorLog ;
771773
772774 /// A broadcast stream of all errors that come in after you start listening.
773775 Stream <String > get errorsStream => _errorsController.stream;
@@ -785,7 +787,7 @@ class _AppErrorsListener {
785787 final VmService _vmService;
786788
787789 _AppErrorsListener ._(
788- this .errors ,
790+ this .errorLog ,
789791 this ._errorsController,
790792 this ._extensionEventsListener,
791793 this ._stderrEventsListener,
@@ -809,8 +811,8 @@ class _AppErrorsListener {
809811 // list but also expose it to clients so they can know when new errors
810812 // are added.
811813 final errorsController = StreamController <String >.broadcast ();
812- final errors = < String > [] ;
813- errorsController.stream.listen (errors .add);
814+ final errorLog = ErrorLog () ;
815+ errorsController.stream.listen (errorLog .add);
814816 // We need to listen to streams with history so that we can get errors
815817 // that occurred before this tool call.
816818 // TODO(https://github.com/dart-lang/ai/issues/57): this can result in
@@ -849,7 +851,7 @@ class _AppErrorsListener {
849851 logger.log (LoggingLevel .error, 'Error subscribing to app errors: $e ' );
850852 }
851853 return _AppErrorsListener ._(
852- errors ,
854+ errorLog ,
853855 errorsController,
854856 extensionEvents,
855857 stderrEvents,
@@ -859,7 +861,7 @@ class _AppErrorsListener {
859861 }
860862
861863 Future <void > shutdown () async {
862- errors .clear ();
864+ errorLog .clear ();
863865 await _errorsController.close ();
864866 await _extensionEventsListener.cancel ();
865867 await _stderrEventsListener.cancel ();
@@ -958,3 +960,52 @@ extension type DebugSession.fromJson(Map<String, Object?> _value)
958960 if (vmServiceUri != null ) 'vmServiceUri' : vmServiceUri,
959961 });
960962}
963+
964+ /// Manages a log of errors with a maximum size in terms of total characters.
965+ @visibleForTesting
966+ class ErrorLog {
967+ Iterable <String > get errors => _errors;
968+ final List <String > _errors = [];
969+ int _characters = 0 ;
970+
971+ /// The number of characters used by all errors in the log.
972+ @visibleForTesting
973+ int get characters => _characters;
974+
975+ final int _maxSize;
976+
977+ ErrorLog ({
978+ // One token is ~4 characters. Allow up to 5k tokens by default, so 20k
979+ // characters.
980+ int maxSize = 20000 ,
981+ }) : _maxSize = maxSize;
982+
983+ /// Adds a new [error] to the log.
984+ void add (String error) {
985+ if (error.length > _maxSize) {
986+ // If we get a single error over the max size, just trim it and clear
987+ // all other errors.
988+ final trimmed = error.substring (0 , _maxSize);
989+ _errors.clear ();
990+ _characters = trimmed.length;
991+ _errors.add (trimmed);
992+ } else {
993+ // Otherwise, we append the error and then remove as many errors from the
994+ // front as we need to in order to get under the max size.
995+ _characters += error.length;
996+ _errors.add (error);
997+ var removeCount = 0 ;
998+ while (_characters > _maxSize) {
999+ _characters -= _errors[removeCount].length;
1000+ removeCount++ ;
1001+ }
1002+ _errors.removeRange (0 , removeCount);
1003+ }
1004+ }
1005+
1006+ /// Clears all errors.
1007+ void clear () {
1008+ _characters = 0 ;
1009+ _errors.clear ();
1010+ }
1011+ }
0 commit comments