chrome-devtools-frontend 1.0.1518653 → 1.0.1519267

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/config/owner/COMMON_OWNERS +2 -2
  2. package/eslint.config.mjs +1 -0
  3. package/front_end/core/sdk/EnhancedTracesParser.ts +5 -5
  4. package/front_end/core/sdk/RehydratingConnection.snapshot.txt +211 -0
  5. package/front_end/core/sdk/TargetManager.ts +4 -0
  6. package/front_end/generated/SupportedCSSProperties.js +19 -4
  7. package/front_end/models/ai_assistance/agents/AiAgent.ts +57 -10
  8. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +4 -53
  9. package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -31
  10. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +6 -0
  11. package/front_end/models/ai_assistance/performance/AIContext.ts +1 -1
  12. package/front_end/models/ai_code_completion/AiCodeCompletion.ts +5 -0
  13. package/front_end/models/badges/AiExplorerBadge.ts +19 -3
  14. package/front_end/models/badges/Badge.ts +2 -0
  15. package/front_end/models/badges/CodeWhispererBadge.ts +1 -0
  16. package/front_end/models/badges/DOMDetectiveBadge.ts +1 -0
  17. package/front_end/models/badges/SpeedsterBadge.ts +1 -0
  18. package/front_end/models/badges/StarterBadge.ts +1 -0
  19. package/front_end/models/badges/badges.ts +1 -0
  20. package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
  21. package/front_end/models/trace/EventsSerializer.ts +4 -3
  22. package/front_end/models/trace/handlers/UserInteractionsHandler.ts +101 -73
  23. package/front_end/models/trace/helpers/Timing.ts +1 -1
  24. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +4 -1
  25. package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +16 -14
  26. package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +2 -2
  27. package/front_end/panels/common/BadgeNotification.ts +3 -3
  28. package/front_end/panels/common/GdpSignUpDialog.ts +3 -4
  29. package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
  30. package/front_end/panels/recorder/components/RecordingView.ts +2 -2
  31. package/front_end/panels/search/SearchResultsPane.ts +157 -138
  32. package/front_end/panels/search/SearchView.ts +12 -9
  33. package/front_end/panels/search/searchResultsPane.css +9 -0
  34. package/front_end/panels/security/CookieControlsView.ts +2 -1
  35. package/front_end/panels/settings/AISettingsTab.ts +6 -3
  36. package/front_end/panels/settings/components/SyncSection.ts +23 -9
  37. package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +1 -1
  38. package/front_end/panels/sources/AiCodeCompletionPlugin.ts +3 -0
  39. package/front_end/panels/sources/SourcesPanel.ts +1 -1
  40. package/front_end/panels/sources/sourcesView.css +6 -1
  41. package/front_end/panels/timeline/components/LayoutShiftDetails.ts +1 -1
  42. package/front_end/panels/timeline/components/NetworkRequestDetails.ts +1 -1
  43. package/front_end/panels/timeline/components/RelatedInsightChips.ts +1 -1
  44. package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
  45. package/front_end/third_party/chromium/README.chromium +1 -1
  46. package/front_end/third_party/puppeteer/README.chromium +2 -2
  47. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
  48. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
  49. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
  50. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
  51. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
  52. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
  53. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  54. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
  55. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
  56. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
  57. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
  58. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
  59. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.d.ts.map +1 -1
  60. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +16 -25
  61. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
  62. package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
  63. package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +19 -28
  64. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
  65. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
  66. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
  67. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
  68. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
  69. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
  70. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
  71. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
  72. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
  73. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
  74. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.d.ts.map +1 -1
  75. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +16 -25
  76. package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
  77. package/front_end/third_party/puppeteer/package/package.json +10 -3
  78. package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
  79. package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
  80. package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
  81. package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
  82. package/front_end/third_party/puppeteer/package/src/util/Function.ts +22 -30
  83. package/front_end/ui/components/dialogs/Dialog.ts +1 -1
  84. package/front_end/ui/components/markdown_view/MarkdownImage.ts +4 -5
  85. package/front_end/ui/components/switch/SwitchImpl.ts +12 -1
  86. package/front_end/ui/components/text_editor/config.ts +16 -2
  87. package/front_end/ui/legacy/Treeoutline.ts +3 -1
  88. package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -11
  89. package/front_end/ui/lit/i18n-template.ts +5 -2
  90. package/front_end/ui/visual_logging/KnownContextValues.ts +9 -5
  91. package/package.json +1 -1
@@ -16,15 +16,15 @@ kprokopenko@chromium.org
16
16
  leese@chromium.org
17
17
  mathias@chromium.org
18
18
  nancyly@chromium.org
19
- nechaev@chromium.org
20
19
  nharshunova@chromium.org
20
+ nroscino@chromium.org
21
21
  nvitkov@chromium.org
22
22
  paulirish@chromium.org
23
23
  petermueller@chromium.org
24
24
  pfaffe@chromium.org
25
25
  piotrpaulski@chromium.org
26
26
  sadym@chromium.org
27
- samiyac@chromium.org
27
+ samiyac@google.com
28
28
  szuend@chromium.org
29
29
  victorporof@chromium.org
30
30
  wolfi@chromium.org
package/eslint.config.mjs CHANGED
@@ -720,6 +720,7 @@ export default defineConfig([
720
720
  'rulesdir/no-assert-deep-strict-equal': 'error',
721
721
  'rulesdir/no-assert-equal': 'error',
722
722
  'rulesdir/no-assert-equal-boolean-null-undefined': 'error',
723
+ 'rulesdir/no-capture-screenshot': 'error',
723
724
  'rulesdir/no-imperative-dom-api': 'off',
724
725
  'rulesdir/no-lit-render-outside-of-view': 'off',
725
726
  'rulesdir/prefer-assert-instance-of': 'error',
@@ -459,11 +459,11 @@ export class EnhancedTracesParser {
459
459
  for (const orphanScript of orphanScripts) {
460
460
  const orphanScriptExecutionContextIsolateId =
461
461
  this.getExecutionContextIsolateId(orphanScript.isolate, orphanScript.executionContextId);
462
- if (orphanScriptExecutionContextIsolateId in executionContextIsolateToTarget) {
463
- const frameId = executionContextIsolateToTarget.get(orphanScriptExecutionContextIsolateId);
464
- if (frameId) {
465
- targetToScripts.get(frameId)?.push(orphanScript);
466
- }
462
+ const frameId = executionContextIsolateToTarget.get(orphanScriptExecutionContextIsolateId);
463
+
464
+ if (frameId) {
465
+ // Found a link via execution context, use it.
466
+ targetToScripts.get(frameId)?.push(orphanScript);
467
467
  } else if (orphanScript.pid) {
468
468
  const target = targets.find(target => target.pid === orphanScript.pid);
469
469
  if (target) {
@@ -992,6 +992,217 @@ Content:
992
992
  "sessionId": 1
993
993
  }
994
994
 
995
+ /* RehydratingConnection says: */
996
+ {
997
+ "method": "Debugger.scriptParsed",
998
+ "params": {
999
+ "scriptId": "6",
1000
+ "isolate": "7348673817420155000",
1001
+ "buildId": "",
1002
+ "executionContextId": 1,
1003
+ "startLine": 0,
1004
+ "startColumn": 0,
1005
+ "endLine": 0,
1006
+ "endColumn": 0,
1007
+ "hash": "",
1008
+ "isModule": false,
1009
+ "url": "",
1010
+ "hasSourceURL": false,
1011
+ "sourceURL": "",
1012
+ "pid": 97964,
1013
+ "sourceText": "(async function(){ a…",
1014
+ "length": 32
1015
+ },
1016
+ "sessionId": 1
1017
+ }
1018
+
1019
+ /* fakeDevToolsFrontend says: */
1020
+ {
1021
+ "id": 21,
1022
+ "sessionId": 1,
1023
+ "method": "Debugger.getScriptSource",
1024
+ "params": {
1025
+ "scriptId": "6"
1026
+ }
1027
+ }
1028
+
1029
+ /* RehydratingConnection says: */
1030
+ {
1031
+ "id": 21,
1032
+ "result": {
1033
+ "scriptSource": "(async function(){ a…"
1034
+ },
1035
+ "sessionId": 1
1036
+ }
1037
+
1038
+ /* RehydratingConnection says: */
1039
+ {
1040
+ "method": "Debugger.scriptParsed",
1041
+ "params": {
1042
+ "scriptId": "1",
1043
+ "isolate": "7348673817420155000",
1044
+ "buildId": "",
1045
+ "executionContextId": 1,
1046
+ "startLine": 0,
1047
+ "startColumn": 0,
1048
+ "endLine": 0,
1049
+ "endColumn": 0,
1050
+ "hash": "",
1051
+ "isModule": false,
1052
+ "url": "",
1053
+ "hasSourceURL": false,
1054
+ "sourceURL": "",
1055
+ "pid": 97964
1056
+ },
1057
+ "sessionId": 1
1058
+ }
1059
+
1060
+ /* fakeDevToolsFrontend says: */
1061
+ {
1062
+ "id": 22,
1063
+ "sessionId": 1,
1064
+ "method": "Debugger.getScriptSource",
1065
+ "params": {
1066
+ "scriptId": "1"
1067
+ }
1068
+ }
1069
+
1070
+ /* RehydratingConnection says: */
1071
+ {
1072
+ "id": 22,
1073
+ "result": {
1074
+ "scriptSource": "No source text avail…"
1075
+ },
1076
+ "sessionId": 1
1077
+ }
1078
+
1079
+ /* RehydratingConnection says: */
1080
+ {
1081
+ "method": "Debugger.scriptParsed",
1082
+ "params": {
1083
+ "scriptId": "2",
1084
+ "isolate": "7348673817420155000",
1085
+ "buildId": "",
1086
+ "executionContextId": 1,
1087
+ "startLine": 0,
1088
+ "startColumn": 0,
1089
+ "endLine": 0,
1090
+ "endColumn": 0,
1091
+ "hash": "",
1092
+ "isModule": false,
1093
+ "url": "",
1094
+ "hasSourceURL": false,
1095
+ "sourceURL": "",
1096
+ "pid": 97964,
1097
+ "sourceText": "() {}…",
1098
+ "length": 5
1099
+ },
1100
+ "sessionId": 1
1101
+ }
1102
+
1103
+ /* fakeDevToolsFrontend says: */
1104
+ {
1105
+ "id": 23,
1106
+ "sessionId": 1,
1107
+ "method": "Debugger.getScriptSource",
1108
+ "params": {
1109
+ "scriptId": "2"
1110
+ }
1111
+ }
1112
+
1113
+ /* RehydratingConnection says: */
1114
+ {
1115
+ "id": 23,
1116
+ "result": {
1117
+ "scriptSource": "() {}…"
1118
+ },
1119
+ "sessionId": 1
1120
+ }
1121
+
1122
+ /* RehydratingConnection says: */
1123
+ {
1124
+ "method": "Debugger.scriptParsed",
1125
+ "params": {
1126
+ "scriptId": "3",
1127
+ "isolate": "7348673817420155000",
1128
+ "buildId": "",
1129
+ "executionContextId": 1,
1130
+ "startLine": 0,
1131
+ "startColumn": 0,
1132
+ "endLine": 0,
1133
+ "endColumn": 0,
1134
+ "hash": "",
1135
+ "isModule": false,
1136
+ "url": "extensions::SafeBuiltins",
1137
+ "hasSourceURL": false,
1138
+ "sourceURL": "",
1139
+ "pid": 97964
1140
+ },
1141
+ "sessionId": 1
1142
+ }
1143
+
1144
+ /* fakeDevToolsFrontend says: */
1145
+ {
1146
+ "id": 24,
1147
+ "sessionId": 1,
1148
+ "method": "Debugger.getScriptSource",
1149
+ "params": {
1150
+ "scriptId": "3"
1151
+ }
1152
+ }
1153
+
1154
+ /* RehydratingConnection says: */
1155
+ {
1156
+ "id": 24,
1157
+ "result": {
1158
+ "scriptSource": "No source text avail…"
1159
+ },
1160
+ "sessionId": 1
1161
+ }
1162
+
1163
+ /* RehydratingConnection says: */
1164
+ {
1165
+ "method": "Debugger.scriptParsed",
1166
+ "params": {
1167
+ "scriptId": "4",
1168
+ "isolate": "7348673817420155000",
1169
+ "buildId": "",
1170
+ "executionContextId": 1,
1171
+ "startLine": 0,
1172
+ "startColumn": 0,
1173
+ "endLine": 0,
1174
+ "endColumn": 0,
1175
+ "hash": "",
1176
+ "isModule": false,
1177
+ "url": "v8/LoadTimes",
1178
+ "hasSourceURL": false,
1179
+ "sourceURL": "",
1180
+ "pid": 97964,
1181
+ "sourceText": "var chrome;if (!chro…",
1182
+ "length": 198
1183
+ },
1184
+ "sessionId": 1
1185
+ }
1186
+
1187
+ /* fakeDevToolsFrontend says: */
1188
+ {
1189
+ "id": 25,
1190
+ "sessionId": 1,
1191
+ "method": "Debugger.getScriptSource",
1192
+ "params": {
1193
+ "scriptId": "4"
1194
+ }
1195
+ }
1196
+
1197
+ /* RehydratingConnection says: */
1198
+ {
1199
+ "id": 25,
1200
+ "result": {
1201
+ "scriptSource": "var chrome;if (!chro…"
1202
+ },
1203
+ "sessionId": 1
1204
+ }
1205
+
995
1206
  /* RehydratingConnection says: */
996
1207
  {
997
1208
  "id": 2,
@@ -318,6 +318,10 @@ export class TargetManager extends Common.ObjectWrapper.ObjectWrapper<EventTypes
318
318
  * (eg., tab URL of `devtools://devtools/bundled/devtools_app.html` uses a MainConnection but has no CDP server behind it).
319
319
  */
320
320
  hasFakeConnection(): boolean {
321
+ // Rehydrated DevTools always has a fake connection, so we shortcut and avoid the race.
322
+ if (Root.Runtime.getPathName().includes('rehydrated_devtools_app')) {
323
+ return true;
324
+ }
321
325
  // There _may_ be a race condition hiding here on the router/connection creation.
322
326
  // So we play it safe and consider "no connection yet" as "not fake".
323
327
  const connection = this.primaryPageTarget()?.router()?.connection();
@@ -641,6 +641,7 @@ export const generatedProperties = [
641
641
  "row-rule-style",
642
642
  "row-rule-width",
643
643
  "ruby-align",
644
+ "ruby-overhang",
644
645
  "ruby-position",
645
646
  "rx",
646
647
  "ry",
@@ -738,7 +739,7 @@ export const generatedProperties = [
738
739
  "timeline-trigger-name",
739
740
  "timeline-trigger-range-end",
740
741
  "timeline-trigger-range-start",
741
- "timeline-trigger-timeline",
742
+ "timeline-trigger-source",
742
743
  "top",
743
744
  "touch-action",
744
745
  "transform",
@@ -3732,6 +3733,14 @@ export const generatedProperties = [
3732
3733
  ],
3733
3734
  "name": "ruby-align"
3734
3735
  },
3736
+ {
3737
+ "inherited": true,
3738
+ "keywords": [
3739
+ "auto",
3740
+ "none"
3741
+ ],
3742
+ "name": "ruby-overhang"
3743
+ },
3735
3744
  {
3736
3745
  "inherited": true,
3737
3746
  "keywords": [
@@ -4422,7 +4431,7 @@ export const generatedProperties = [
4422
4431
  {
4423
4432
  "longhands": [
4424
4433
  "timeline-trigger-name",
4425
- "timeline-trigger-timeline",
4434
+ "timeline-trigger-source",
4426
4435
  "timeline-trigger-behavior",
4427
4436
  "timeline-trigger-range-start",
4428
4437
  "timeline-trigger-range-end",
@@ -4460,7 +4469,7 @@ export const generatedProperties = [
4460
4469
  "none",
4461
4470
  "auto"
4462
4471
  ],
4463
- "name": "timeline-trigger-timeline"
4472
+ "name": "timeline-trigger-source"
4464
4473
  },
4465
4474
  {
4466
4475
  "keywords": [
@@ -6482,6 +6491,12 @@ export const generatedPropertyValues = {
6482
6491
  "space-between"
6483
6492
  ]
6484
6493
  },
6494
+ "ruby-overhang": {
6495
+ "values": [
6496
+ "auto",
6497
+ "none"
6498
+ ]
6499
+ },
6485
6500
  "ruby-position": {
6486
6501
  "values": [
6487
6502
  "over",
@@ -6839,7 +6854,7 @@ export const generatedPropertyValues = {
6839
6854
  "state"
6840
6855
  ]
6841
6856
  },
6842
- "timeline-trigger-timeline": {
6857
+ "timeline-trigger-source": {
6843
6858
  "values": [
6844
6859
  "none",
6845
6860
  "auto"
@@ -133,13 +133,7 @@ export interface ParsedAnswer {
133
133
  suggestions?: [string, ...string[]];
134
134
  }
135
135
 
136
- export interface ParsedStep {
137
- thought?: string;
138
- title?: string;
139
- action?: string;
140
- }
141
-
142
- export type ParsedResponse = ParsedAnswer|ParsedStep;
136
+ export type ParsedResponse = ParsedAnswer;
143
137
 
144
138
  export const MAX_STEPS = 10;
145
139
 
@@ -404,13 +398,66 @@ export abstract class AiAgent<T> {
404
398
  return this.#origin;
405
399
  }
406
400
 
401
+ /**
402
+ * The AI has instructions to emit structured suggestions in their response. This
403
+ * function parses for that.
404
+ *
405
+ * Note: currently only StylingAgent and PerformanceAgent utilize this, but
406
+ * eventually all agents should support this.
407
+ */
408
+ parseTextResponseForSuggestions(text: string): ParsedResponse {
409
+ if (!text) {
410
+ return {answer: ''};
411
+ }
412
+
413
+ const lines = text.split('\n');
414
+ const answerLines: string[] = [];
415
+ let suggestions: [string, ...string[]]|undefined;
416
+
417
+ for (const line of lines) {
418
+ const trimmed = line.trim();
419
+ if (trimmed.startsWith('SUGGESTIONS:')) {
420
+ try {
421
+ // TODO: Do basic validation this is an array with strings
422
+ suggestions = JSON.parse(trimmed.substring('SUGGESTIONS:'.length).trim());
423
+ } catch {
424
+ }
425
+ } else {
426
+ answerLines.push(line);
427
+ }
428
+ }
429
+
430
+ // Sometimes the model fails to put the SUGGESTIONS text on its own line. Handle
431
+ // the case where the suggestions are part of the last line of the answer.
432
+ if (!suggestions && answerLines.at(-1)?.includes('SUGGESTIONS:')) {
433
+ const [answer, suggestionsText] = answerLines[answerLines.length - 1].split('SUGGESTIONS:', 2);
434
+ try {
435
+ // TODO: Do basic validation this is an array with strings
436
+ suggestions = JSON.parse(suggestionsText.trim().substring('SUGGESTIONS:'.length).trim());
437
+ } catch {
438
+ }
439
+ answerLines[answerLines.length - 1] = answer;
440
+ }
441
+
442
+ const response: ParsedResponse = {
443
+ // If we could not parse the parts, consider the response to be an
444
+ // answer.
445
+ answer: answerLines.join('\n'),
446
+ };
447
+
448
+ if (suggestions) {
449
+ response.suggestions = suggestions;
450
+ }
451
+
452
+ return response;
453
+ }
454
+
407
455
  /**
408
456
  * Parses a streaming text response into a
409
- * though/action/title/answer/suggestions component. This is only used
410
- * by StylingAgent.
457
+ * though/action/title/answer/suggestions component.
411
458
  */
412
459
  parseTextResponse(response: string): ParsedResponse {
413
- return {answer: response};
460
+ return this.parseTextResponseForSuggestions(response.trim());
414
461
  }
415
462
 
416
463
  /**
@@ -2,8 +2,6 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
- import '../../../ui/components/icon_button/icon_button.js';
6
-
7
5
  import * as Common from '../../../core/common/common.js';
8
6
  import * as Host from '../../../core/host/host.js';
9
7
  import * as i18n from '../../../core/i18n/i18n.js';
@@ -420,53 +418,6 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
420
418
  });
421
419
  }
422
420
 
423
- #parseSuggestions(text: string): ParsedResponse {
424
- if (!text) {
425
- return {answer: ''};
426
- }
427
-
428
- const lines = text.split('\n');
429
- const answerLines: string[] = [];
430
- let suggestions: [string, ...string[]]|undefined;
431
-
432
- for (const line of lines) {
433
- const trimmed = line.trim();
434
- if (trimmed.startsWith('SUGGESTIONS:')) {
435
- try {
436
- // TODO: Do basic validation this is an array with strings
437
- suggestions = JSON.parse(trimmed.substring('SUGGESTIONS:'.length).trim());
438
- } catch {
439
- }
440
- } else {
441
- answerLines.push(line);
442
- }
443
- }
444
-
445
- // Sometimes the model fails to put the SUGGESTIONS text on its own line. Handle
446
- // the case where the suggestions are part of the last line of the answer.
447
- if (!suggestions && answerLines.at(-1)?.includes('SUGGESTIONS:')) {
448
- const [answer, suggestionsText] = answerLines[answerLines.length - 1].split('SUGGESTIONS:', 2);
449
- try {
450
- // TODO: Do basic validation this is an array with strings
451
- suggestions = JSON.parse(suggestionsText.trim().substring('SUGGESTIONS:'.length).trim());
452
- } catch {
453
- }
454
- answerLines[answerLines.length - 1] = answer;
455
- }
456
-
457
- const response: ParsedResponse = {
458
- // If we could not parse the parts, consider the response to be an
459
- // answer.
460
- answer: answerLines.join('\n'),
461
- };
462
-
463
- if (suggestions) {
464
- response.suggestions = suggestions;
465
- }
466
-
467
- return response;
468
- }
469
-
470
421
  #parseMarkdown(response: string): string {
471
422
  /**
472
423
  * Sometimes the LLM responds with code chunks that wrap a text based markdown response.
@@ -482,10 +433,10 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
482
433
  }
483
434
 
484
435
  override parseTextResponse(response: string): ParsedResponse {
485
- response = response.trim();
486
- response = this.#parseForKnownUrls(response);
487
- response = this.#parseMarkdown(response);
488
- return this.#parseSuggestions(response);
436
+ const parsedResponse = super.parseTextResponse(response);
437
+ parsedResponse.answer = this.#parseForKnownUrls(parsedResponse.answer);
438
+ parsedResponse.answer = this.#parseMarkdown(parsedResponse.answer);
439
+ return parsedResponse;
489
440
  }
490
441
 
491
442
  override async enhanceQuery(query: string, context: PerformanceTraceContext|null): Promise<string> {
@@ -21,7 +21,6 @@ import {
21
21
  type ConversationSuggestions,
22
22
  type FunctionCallHandlerResult,
23
23
  MultimodalInputType,
24
- type ParsedResponse,
25
24
  type RequestOptions,
26
25
  ResponseType,
27
26
  } from './AiAgent.js';
@@ -265,36 +264,6 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
265
264
  override preambleFeatures(): string[] {
266
265
  return ['function_calling'];
267
266
  }
268
- override parseTextResponse(text: string): ParsedResponse {
269
- // We're returning an empty answer to denote the erroneous case.
270
- if (!text.trim()) {
271
- return {answer: ''};
272
- }
273
-
274
- const lines = text.split('\n');
275
- const answerLines: string[] = [];
276
- let suggestions: [string, ...string[]]|undefined;
277
-
278
- for (const line of lines) {
279
- const trimmed = line.trim();
280
- if (trimmed.startsWith('SUGGESTIONS:')) {
281
- try {
282
- // TODO: Do basic validation this is an array with strings
283
- suggestions = JSON.parse(trimmed.substring('SUGGESTIONS:'.length).trim());
284
- } catch {
285
- }
286
- } else {
287
- answerLines.push(line);
288
- }
289
- }
290
-
291
- return {
292
- // If we could not parse the parts, consider the response to be an
293
- // answer.
294
- answer: answerLines.join('\n'),
295
- suggestions,
296
- };
297
- }
298
267
 
299
268
  #execJs: typeof executeJsCode;
300
269
 
@@ -100,6 +100,12 @@ export class PerformanceInsightFormatter extends PerformanceTraceFormatter {
100
100
  }
101
101
 
102
102
  insightIsSupported(): boolean {
103
+ // Although our types don't show it, Insights can end up as Errors if there
104
+ // is an issue in the processing stage. In this case we should gracefully
105
+ // ignore this error.
106
+ if (this.#insight instanceof Error) {
107
+ return false;
108
+ }
103
109
  return this.#description().length > 0;
104
110
  }
105
111
 
@@ -95,7 +95,7 @@ export class AgentFocus {
95
95
  try {
96
96
  return this.eventsSerializer.eventForKey(key, this.#data.parsedTrace);
97
97
  } catch (err) {
98
- if (err.toString().includes('Unknown trace event')) {
98
+ if (err.toString().includes('Unknown trace event') || err.toString().includes('Unknown profile call')) {
99
99
  return null;
100
100
  }
101
101
 
@@ -332,6 +332,7 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
332
332
  sampleId,
333
333
  startTime,
334
334
  onImpression: this.#registerUserImpression.bind(this),
335
+ clearCachedRequest: this.#clearCachedRequest.bind(this),
335
336
  })
336
337
  });
337
338
 
@@ -414,6 +415,10 @@ export class AiCodeCompletion extends Common.ObjectWrapper.ObjectWrapper<EventTy
414
415
  this.#aidaRequestCache = {request, response};
415
416
  }
416
417
 
418
+ #clearCachedRequest(): void {
419
+ this.#aidaRequestCache = undefined;
420
+ }
421
+
417
422
  #registerUserImpression(rpcGlobalId: Host.AidaClient.RpcGlobalId, sampleId: number, latency: number): void {
418
423
  const seconds = Math.floor(latency / 1_000);
419
424
  const remainingMs = latency % 1_000;
@@ -2,20 +2,36 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
- import {Badge, type BadgeAction} from './Badge.js';
5
+ import * as Common from '../../core/common/common.js';
6
+
7
+ import {Badge, BadgeAction} from './Badge.js';
6
8
 
7
9
  const AI_EXPLORER_BADGE_URI = new URL('../../Images/ai-explorer-badge.svg', import.meta.url).toString();
10
+ const AI_CONVERSATION_COUNT_SETTING_NAME = 'gdp.ai-conversation-count';
11
+ const AI_CONVERSATION_COUNT_LIMIT = 5;
12
+
8
13
  export class AiExplorerBadge extends Badge {
9
14
  override readonly name =
10
15
  'profiles/me/awards/developers.google.com%2Fprofile%2Fbadges%2Factivity%2Fchrome-devtools%2Fai-explorer';
11
16
  override readonly title = 'AI Explorer';
17
+ override readonly jslogContext = 'ai-explorer';
12
18
  override readonly imageUri = AI_EXPLORER_BADGE_URI;
19
+ #aiConversationCountSetting: Common.Settings.Setting<number> = Common.Settings.Settings.instance().createSetting(
20
+ AI_CONVERSATION_COUNT_SETTING_NAME, 0, Common.Settings.SettingStorageType.SYNCED);
13
21
 
14
22
  override readonly interestedActions = [
15
- // TODO(ergunsh): Instrument related actions.
23
+ BadgeAction.STARTED_AI_CONVERSATION,
16
24
  ] as const;
17
25
 
18
26
  handleAction(_action: BadgeAction): void {
19
- this.trigger();
27
+ const currentCount = this.#aiConversationCountSetting.get();
28
+ if (currentCount >= AI_CONVERSATION_COUNT_LIMIT) {
29
+ return;
30
+ }
31
+
32
+ this.#aiConversationCountSetting.set(currentCount + 1);
33
+ if (this.#aiConversationCountSetting.get() === AI_CONVERSATION_COUNT_LIMIT) {
34
+ this.trigger();
35
+ }
20
36
  }
21
37
  }
@@ -10,6 +10,7 @@ export enum BadgeAction {
10
10
  CSS_RULE_MODIFIED = 'css-rule-modified',
11
11
  DOM_ELEMENT_OR_ATTRIBUTE_EDITED = 'dom-element-or-attribute-edited',
12
12
  MODERN_DOM_BADGE_CLICKED = 'modern-dom-badge-clicked',
13
+ STARTED_AI_CONVERSATION = 'started-ai-conversation',
13
14
  // TODO(ergunsh): Instrument performance insight clicks.
14
15
  PERFORMANCE_INSIGHT_CLICKED = 'performance-insight-clicked',
15
16
  DEBUGGER_PAUSED = 'debugger-paused'
@@ -36,6 +37,7 @@ export abstract class Badge {
36
37
  abstract readonly title: string;
37
38
  abstract readonly imageUri: string;
38
39
  abstract readonly interestedActions: readonly BadgeAction[];
40
+ abstract readonly jslogContext: string;
39
41
  readonly isStarterBadge: boolean = false;
40
42
 
41
43
  constructor(context: BadgeContext) {
@@ -9,6 +9,7 @@ export class CodeWhispererBadge extends Badge {
9
9
  override readonly name =
10
10
  'profiles/me/awards/developers.google.com%2Fprofile%2Fbadges%2Factivity%2Fchrome-devtools%2Fcode-whisperer';
11
11
  override readonly title = 'Code Whisperer';
12
+ override readonly jslogContext = 'code-whisperer';
12
13
  override readonly imageUri = CODE_WHISPERER_BADGE_IMAGE_URI;
13
14
 
14
15
  override readonly interestedActions = [BadgeAction.DEBUGGER_PAUSED] as const;
@@ -9,6 +9,7 @@ export class DOMDetectiveBadge extends Badge {
9
9
  override readonly name =
10
10
  'profiles/me/awards/developers.google.com%2Fprofile%2Fbadges%2Factivity%2Fchrome-devtools%2Fdom-detective';
11
11
  override readonly title = 'DOM Detective';
12
+ override readonly jslogContext = 'dom-detective';
12
13
  override readonly imageUri = DOM_DETECTIVE_BADGE_IMAGE_URI;
13
14
 
14
15
  override readonly interestedActions = [
@@ -9,6 +9,7 @@ export class SpeedsterBadge extends Badge {
9
9
  override readonly name =
10
10
  'profiles/me/awards/developers.google.com%2Fprofile%2Fbadges%2Factivity%2Fchrome-devtools%2Fspeedster';
11
11
  override readonly title = 'Speedster';
12
+ override readonly jslogContext = 'speedster';
12
13
  override readonly interestedActions = [
13
14
  BadgeAction.PERFORMANCE_INSIGHT_CLICKED,
14
15
  ] as const;