chrome-devtools-frontend 1.0.1642899 → 1.0.1643099

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 (93) hide show
  1. package/front_end/core/protocol_client/InspectorBackend.ts +4 -0
  2. package/front_end/entrypoints/heap_snapshot_worker/HeapSnapshot.ts +4 -5
  3. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  4. package/front_end/generated/protocol.ts +7 -0
  5. package/front_end/models/ai_assistance/AiAgent2.ts +23 -5
  6. package/front_end/models/ai_assistance/AiConversation.ts +15 -12
  7. package/front_end/models/ai_assistance/AiUtils.ts +71 -0
  8. package/front_end/models/ai_assistance/ChangeManager.ts +2 -5
  9. package/front_end/models/ai_assistance/{agents/ConversationSummaryAgent.ts → ConversationSummary.ts} +29 -66
  10. package/front_end/models/ai_assistance/ExtensionScope.ts +1 -4
  11. package/front_end/models/ai_assistance/{agents/PerformanceAnnotationsAgent.ts → PerformanceAnnotations.ts} +47 -89
  12. package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +31 -21
  13. package/front_end/models/ai_assistance/agents/AiAgent.ts +21 -6
  14. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +11 -0
  15. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +53 -3
  16. package/front_end/models/ai_assistance/agents/ExecuteJavascript.ts +2 -0
  17. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +72 -79
  18. package/front_end/models/ai_assistance/agents/StorageAgent.ts +47 -38
  19. package/front_end/models/ai_assistance/agents/StylingAgent.ts +22 -21
  20. package/front_end/models/ai_assistance/ai_assistance.ts +6 -4
  21. package/front_end/models/ai_assistance/skills/styling.md +12 -4
  22. package/front_end/models/ai_assistance/tools/ExecuteJavaScript.ts +140 -0
  23. package/front_end/models/ai_assistance/tools/GetStyles.ts +6 -2
  24. package/front_end/models/ai_assistance/tools/Tool.ts +10 -1
  25. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +2 -0
  26. package/front_end/models/heap_snapshot/HeapSnapshotProxy.ts +5 -7
  27. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +6 -7
  28. package/front_end/panels/ai_assistance/components/ChatMessage.ts +96 -4
  29. package/front_end/panels/ai_assistance/components/chatMessage.css +6 -0
  30. package/front_end/panels/application/components/AdsView.ts +219 -0
  31. package/front_end/panels/application/components/adsView.css +54 -0
  32. package/front_end/panels/application/components/components.ts +2 -0
  33. package/front_end/panels/console/SymbolizedErrorWidget.ts +73 -22
  34. package/front_end/panels/network/NetworkLogView.ts +5 -1
  35. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +5 -4
  36. package/front_end/third_party/chromium/README.chromium +1 -1
  37. package/front_end/third_party/lighthouse/README.chromium +2 -2
  38. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +1607 -5733
  39. package/front_end/third_party/lighthouse/locales/ar-XB.json +290 -65
  40. package/front_end/third_party/lighthouse/locales/ar.json +290 -65
  41. package/front_end/third_party/lighthouse/locales/bg.json +290 -65
  42. package/front_end/third_party/lighthouse/locales/ca.json +295 -70
  43. package/front_end/third_party/lighthouse/locales/cs.json +290 -65
  44. package/front_end/third_party/lighthouse/locales/da.json +294 -69
  45. package/front_end/third_party/lighthouse/locales/de.json +295 -70
  46. package/front_end/third_party/lighthouse/locales/el.json +290 -65
  47. package/front_end/third_party/lighthouse/locales/en-GB.json +290 -65
  48. package/front_end/third_party/lighthouse/locales/en-US.json +79 -67
  49. package/front_end/third_party/lighthouse/locales/en-XA.json +253 -64
  50. package/front_end/third_party/lighthouse/locales/en-XL.json +79 -67
  51. package/front_end/third_party/lighthouse/locales/es-419.json +290 -65
  52. package/front_end/third_party/lighthouse/locales/es.json +298 -73
  53. package/front_end/third_party/lighthouse/locales/fi.json +290 -65
  54. package/front_end/third_party/lighthouse/locales/fil.json +290 -65
  55. package/front_end/third_party/lighthouse/locales/fr.json +294 -69
  56. package/front_end/third_party/lighthouse/locales/he.json +293 -68
  57. package/front_end/third_party/lighthouse/locales/hi.json +291 -66
  58. package/front_end/third_party/lighthouse/locales/hr.json +290 -65
  59. package/front_end/third_party/lighthouse/locales/hu.json +290 -65
  60. package/front_end/third_party/lighthouse/locales/id.json +290 -65
  61. package/front_end/third_party/lighthouse/locales/it.json +294 -69
  62. package/front_end/third_party/lighthouse/locales/ja.json +290 -65
  63. package/front_end/third_party/lighthouse/locales/ko.json +290 -65
  64. package/front_end/third_party/lighthouse/locales/lt.json +290 -65
  65. package/front_end/third_party/lighthouse/locales/lv.json +290 -65
  66. package/front_end/third_party/lighthouse/locales/nl.json +290 -65
  67. package/front_end/third_party/lighthouse/locales/no.json +290 -65
  68. package/front_end/third_party/lighthouse/locales/pl.json +290 -65
  69. package/front_end/third_party/lighthouse/locales/pt-PT.json +291 -66
  70. package/front_end/third_party/lighthouse/locales/pt.json +290 -65
  71. package/front_end/third_party/lighthouse/locales/ro.json +290 -65
  72. package/front_end/third_party/lighthouse/locales/ru.json +301 -76
  73. package/front_end/third_party/lighthouse/locales/sk.json +291 -66
  74. package/front_end/third_party/lighthouse/locales/sl.json +290 -65
  75. package/front_end/third_party/lighthouse/locales/sr-Latn.json +290 -65
  76. package/front_end/third_party/lighthouse/locales/sr.json +290 -65
  77. package/front_end/third_party/lighthouse/locales/sv.json +297 -72
  78. package/front_end/third_party/lighthouse/locales/ta.json +291 -66
  79. package/front_end/third_party/lighthouse/locales/te.json +293 -68
  80. package/front_end/third_party/lighthouse/locales/th.json +291 -66
  81. package/front_end/third_party/lighthouse/locales/tr.json +290 -65
  82. package/front_end/third_party/lighthouse/locales/uk.json +290 -65
  83. package/front_end/third_party/lighthouse/locales/vi.json +291 -66
  84. package/front_end/third_party/lighthouse/locales/zh-HK.json +292 -67
  85. package/front_end/third_party/lighthouse/locales/zh-TW.json +291 -66
  86. package/front_end/third_party/lighthouse/locales/zh.json +291 -66
  87. package/front_end/third_party/lighthouse/report/bundle.d.ts +6 -6
  88. package/front_end/third_party/lighthouse/report/bundle.js +4 -7
  89. package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +2 -2
  90. package/front_end/ui/legacy/Widget.ts +32 -8
  91. package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
  92. package/mcp/mcp.ts +1 -0
  93. package/package.json +1 -1
@@ -339,6 +339,10 @@ export class TargetBase {
339
339
  return this.getAgent('Autofill');
340
340
  }
341
341
 
342
+ adsAgent(): ProtocolProxyApi.AdsApi {
343
+ return this.getAgent('Ads');
344
+ }
345
+
342
346
  browserAgent(): ProtocolProxyApi.BrowserApi {
343
347
  return this.getAgent('Browser');
344
348
  }
@@ -876,7 +876,7 @@ export abstract class HeapSnapshot {
876
876
  readonly #progress: HeapSnapshotProgress;
877
877
  readonly #noDistance = -5;
878
878
  rootNodeIndexInternal = 0;
879
- #snapshotDiffs: Record<string, Record<string, HeapSnapshotModel.HeapSnapshotModel.Diff>> = {};
879
+ #snapshotDiffs: Record<number, Record<string, HeapSnapshotModel.HeapSnapshotModel.Diff>> = {};
880
880
  #aggregatesForDiff?: {
881
881
  interfaceDefinitions: string,
882
882
  aggregates: Record<string, HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff>,
@@ -2688,9 +2688,8 @@ export abstract class HeapSnapshot {
2688
2688
  throw new Error('Not implemented');
2689
2689
  }
2690
2690
 
2691
- calculateSnapshotDiff(
2692
- baseSnapshotId: string,
2693
- baseSnapshotAggregates: Record<string, HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff>):
2691
+ calculateSnapshotDiff(baseSnapshotId: number,
2692
+ baseSnapshotAggregates: Record<string, HeapSnapshotModel.HeapSnapshotModel.AggregateForDiff>):
2694
2693
  Record<string, HeapSnapshotModel.HeapSnapshotModel.Diff> {
2695
2694
  let snapshotDiff: Record<string, HeapSnapshotModel.HeapSnapshotModel.Diff> = this.#snapshotDiffs[baseSnapshotId];
2696
2695
  if (snapshotDiff) {
@@ -2976,7 +2975,7 @@ export abstract class HeapSnapshot {
2976
2975
  return {paths, limitsReached};
2977
2976
  }
2978
2977
 
2979
- createAddedNodesProvider(baseSnapshotId: string, classKey: string): HeapSnapshotNodesProvider {
2978
+ createAddedNodesProvider(baseSnapshotId: number, classKey: string): HeapSnapshotNodesProvider {
2980
2979
  const snapshotDiff = this.#snapshotDiffs[baseSnapshotId];
2981
2980
  const diffForClass = snapshotDiff[classKey];
2982
2981
  return new HeapSnapshotNodesProvider(this, diffForClass.addedIndexes);
@@ -435,7 +435,7 @@ inspectorBackend.registerCommand("DOM.getFrameOwner", [{"name": "frameId", "type
435
435
  inspectorBackend.registerCommand("DOM.getContainerForNode", [{"name": "nodeId", "type": "number", "optional": false, "description": "", "typeRef": "DOM.NodeId"}, {"name": "containerName", "type": "string", "optional": true, "description": "", "typeRef": null}, {"name": "physicalAxes", "type": "string", "optional": true, "description": "", "typeRef": "DOM.PhysicalAxes"}, {"name": "logicalAxes", "type": "string", "optional": true, "description": "", "typeRef": "DOM.LogicalAxes"}, {"name": "queriesScrollState", "type": "boolean", "optional": true, "description": "", "typeRef": null}, {"name": "queriesAnchored", "type": "boolean", "optional": true, "description": "", "typeRef": null}], ["nodeId"], "Returns the query container of the given node based on container query conditions: containerName, physical and logical axes, and whether it queries scroll-state or anchored elements. If no axes are provided and queriesScrollState is false, the style container is returned, which is the direct parent or the closest element with a matching container-name.");
436
436
  inspectorBackend.registerCommand("DOM.getQueryingDescendantsForContainer", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the container node to find querying descendants from.", "typeRef": "DOM.NodeId"}], ["nodeIds"], "Returns the descendants of a container query container that have container queries against this container.");
437
437
  inspectorBackend.registerCommand("DOM.getAnchorElement", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the positioned element from which to find the anchor.", "typeRef": "DOM.NodeId"}, {"name": "anchorSpecifier", "type": "string", "optional": true, "description": "An optional anchor specifier, as defined in https://www.w3.org/TR/css-anchor-position-1/#anchor-specifier. If not provided, it will return the implicit anchor element for the given positioned element.", "typeRef": null}], ["nodeId"], "Returns the target anchor element of the given anchor query according to https://www.w3.org/TR/css-anchor-position-1/#target.");
438
- inspectorBackend.registerCommand("DOM.forceShowPopover", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the popover HTMLElement", "typeRef": "DOM.NodeId"}, {"name": "enable", "type": "boolean", "optional": false, "description": "If true, opens the popover and keeps it open. If false, closes the popover if it was previously force-opened.", "typeRef": null}], ["nodeIds"], "When enabling, this API force-opens the popover identified by nodeId and keeps it open until disabled.");
438
+ inspectorBackend.registerCommand("DOM.forceShowPopover", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the popover HTMLElement", "typeRef": "DOM.NodeId"}, {"name": "enable", "type": "boolean", "optional": false, "description": "If true, opens the popover and keeps it open. If false, closes the popover if it was previously force-opened.", "typeRef": null}, {"name": "invokerNodeId", "type": "number", "optional": true, "description": "Optional ID of the element invoking this popover, used to establish the implicit anchor. If not provided, it will fall back to the first invoker in the document, preferring elements with a popovertarget attribute over those with a commandfor attribute. Note that if there are multiple invokers, this is just an estimate.", "typeRef": "DOM.BackendNodeId"}], ["nodeIds"], "When enabling, this API force-opens the popover identified by nodeId and keeps it open until disabled.");
439
439
  inspectorBackend.registerType("DOM.BackendNode", [{"name": "nodeType", "type": "number", "optional": false, "description": "`Node`'s nodeType.", "typeRef": null}, {"name": "nodeName", "type": "string", "optional": false, "description": "`Node`'s nodeName.", "typeRef": null}, {"name": "backendNodeId", "type": "number", "optional": false, "description": "", "typeRef": "DOM.BackendNodeId"}]);
440
440
  inspectorBackend.registerType("DOM.Node", [{"name": "nodeId", "type": "number", "optional": false, "description": "Node identifier that is passed into the rest of the DOM messages as the `nodeId`. Backend will only push node with given `id` once. It is aware of all requested nodes and will only fire DOM events for nodes known to the client.", "typeRef": "DOM.NodeId"}, {"name": "parentId", "type": "number", "optional": true, "description": "The id of the parent node if any.", "typeRef": "DOM.NodeId"}, {"name": "backendNodeId", "type": "number", "optional": false, "description": "The BackendNodeId for this node.", "typeRef": "DOM.BackendNodeId"}, {"name": "nodeType", "type": "number", "optional": false, "description": "`Node`'s nodeType.", "typeRef": null}, {"name": "nodeName", "type": "string", "optional": false, "description": "`Node`'s nodeName.", "typeRef": null}, {"name": "localName", "type": "string", "optional": false, "description": "`Node`'s localName.", "typeRef": null}, {"name": "nodeValue", "type": "string", "optional": false, "description": "`Node`'s nodeValue.", "typeRef": null}, {"name": "childNodeCount", "type": "number", "optional": true, "description": "Child count for `Container` nodes.", "typeRef": null}, {"name": "children", "type": "array", "optional": true, "description": "Child nodes of this node when requested with children.", "typeRef": "DOM.Node"}, {"name": "attributes", "type": "array", "optional": true, "description": "Attributes of the `Element` node in the form of flat array `[name1, value1, name2, value2]`.", "typeRef": "string"}, {"name": "documentURL", "type": "string", "optional": true, "description": "Document URL that `Document` or `FrameOwner` node points to.", "typeRef": null}, {"name": "baseURL", "type": "string", "optional": true, "description": "Base URL that `Document` or `FrameOwner` node uses for URL completion.", "typeRef": null}, {"name": "publicId", "type": "string", "optional": true, "description": "`DocumentType`'s publicId.", "typeRef": null}, {"name": "systemId", "type": "string", "optional": true, "description": "`DocumentType`'s systemId.", "typeRef": null}, {"name": "internalSubset", "type": "string", "optional": true, "description": "`DocumentType`'s internalSubset.", "typeRef": null}, {"name": "xmlVersion", "type": "string", "optional": true, "description": "`Document`'s XML version in case of XML documents.", "typeRef": null}, {"name": "name", "type": "string", "optional": true, "description": "`Attr`'s name.", "typeRef": null}, {"name": "value", "type": "string", "optional": true, "description": "`Attr`'s value.", "typeRef": null}, {"name": "pseudoType", "type": "string", "optional": true, "description": "Pseudo element type for this node.", "typeRef": "DOM.PseudoType"}, {"name": "pseudoIdentifier", "type": "string", "optional": true, "description": "Pseudo element identifier for this node. Only present if there is a valid pseudoType.", "typeRef": null}, {"name": "shadowRootType", "type": "string", "optional": true, "description": "Shadow root type.", "typeRef": "DOM.ShadowRootType"}, {"name": "frameId", "type": "string", "optional": true, "description": "Frame ID for frame owner elements.", "typeRef": "Page.FrameId"}, {"name": "contentDocument", "type": "object", "optional": true, "description": "Content document for frame owner elements.", "typeRef": "DOM.Node"}, {"name": "shadowRoots", "type": "array", "optional": true, "description": "Shadow root list for given element host.", "typeRef": "DOM.Node"}, {"name": "templateContent", "type": "object", "optional": true, "description": "Content document fragment for template elements.", "typeRef": "DOM.Node"}, {"name": "pseudoElements", "type": "array", "optional": true, "description": "Pseudo elements associated with this node.", "typeRef": "DOM.Node"}, {"name": "importedDocument", "type": "object", "optional": true, "description": "Deprecated, as the HTML Imports API has been removed (crbug.com/937746). This property used to return the imported document for the HTMLImport links. The property is always undefined now.", "typeRef": "DOM.Node"}, {"name": "distributedNodes", "type": "array", "optional": true, "description": "Distributed nodes for given insertion point.", "typeRef": "DOM.BackendNode"}, {"name": "isSVG", "type": "boolean", "optional": true, "description": "Whether the node is SVG.", "typeRef": null}, {"name": "compatibilityMode", "type": "string", "optional": true, "description": "", "typeRef": "DOM.CompatibilityMode"}, {"name": "assignedSlot", "type": "object", "optional": true, "description": "", "typeRef": "DOM.BackendNode"}, {"name": "isScrollable", "type": "boolean", "optional": true, "description": "", "typeRef": null}, {"name": "affectedByStartingStyles", "type": "boolean", "optional": true, "description": "", "typeRef": null}, {"name": "adoptedStyleSheets", "type": "array", "optional": true, "description": "", "typeRef": "DOM.StyleSheetId"}, {"name": "adProvenance", "type": "object", "optional": true, "description": "", "typeRef": "Network.AdProvenance"}]);
441
441
  inspectorBackend.registerType("DOM.DetachedElementInfo", [{"name": "treeNode", "type": "object", "optional": false, "description": "", "typeRef": "DOM.Node"}, {"name": "retainedNodeIds", "type": "array", "optional": false, "description": "", "typeRef": "DOM.NodeId"}]);
@@ -5849,6 +5849,13 @@ export namespace DOM {
5849
5849
  * popover if it was previously force-opened.
5850
5850
  */
5851
5851
  enable: boolean;
5852
+ /**
5853
+ * Optional ID of the element invoking this popover, used to establish the implicit anchor.
5854
+ * If not provided, it will fall back to the first invoker in the document, preferring
5855
+ * elements with a popovertarget attribute over those with a commandfor attribute. Note that
5856
+ * if there are multiple invokers, this is just an estimate.
5857
+ */
5858
+ invokerNodeId?: BackendNodeId;
5852
5859
  }
5853
5860
 
5854
5861
  export interface ForceShowPopoverResponse extends ProtocolResponseWithError {
@@ -5,7 +5,6 @@
5
5
  import * as Host from '../../core/host/host.js';
6
6
 
7
7
  import {
8
- type AgentOptions,
9
8
  AiAgent,
10
9
  type ContextResponse,
11
10
  type ConversationContext,
@@ -13,7 +12,11 @@ import {
13
12
  type RequestOptions,
14
13
  ResponseType
15
14
  } from './agents/AiAgent.js';
15
+ import {type ExecuteJsAgentOptions, executeJsCode} from './agents/ExecuteJavascript.js';
16
+ import {ChangeManager} from './ChangeManager.js';
17
+ import {DOMNodeContext} from './contexts/DOMNodeContext.js';
16
18
  import {debugLog} from './debug.js';
19
+ import {ExtensionScope} from './ExtensionScope.js';
17
20
  import type {Skill, SkillName} from './skills/Skill.js';
18
21
  import {SKILLS} from './skills/SkillRegistry.js';
19
22
  import type {Tool} from './tools/Tool.js';
@@ -30,6 +33,8 @@ export class AiAgent2 extends AiAgent<unknown> {
30
33
  readonly userTier = 'TESTERS';
31
34
 
32
35
  #skillsInjected = false;
36
+ #changes = new ChangeManager();
37
+ #execJs: typeof executeJsCode;
33
38
 
34
39
  get options(): RequestOptions {
35
40
  return {};
@@ -38,8 +43,9 @@ export class AiAgent2 extends AiAgent<unknown> {
38
43
  readonly #activeSkills = new Set<SkillName>();
39
44
  readonly #declaredTools = new Set<string>();
40
45
 
41
- constructor(opts: AgentOptions) {
46
+ constructor(opts: ExecuteJsAgentOptions) {
42
47
  super(opts);
48
+ this.#execJs = opts.execJs ?? executeJsCode;
43
49
  this.#declaredTools.add('learnSkills');
44
50
  const skillsList = Object.keys(SKILLS).join(', ');
45
51
  this.declareFunction<{skills: SkillName[]}>('learnSkills', {
@@ -154,6 +160,11 @@ User query: ${enhancedQuery}`;
154
160
  return response.trim();
155
161
  }
156
162
 
163
+ #createExtensionScope(changes: ChangeManager): {install(): Promise<void>, uninstall(): Promise<void>} {
164
+ const selectedNode = this.context && this.context instanceof DOMNodeContext ? this.context.getItem() : null;
165
+ return new ExtensionScope(changes, this.sessionId, selectedNode);
166
+ }
167
+
157
168
  /**
158
169
  * Declares a tool to be available to the agent model, verifying first that
159
170
  * it hasn't already been declared to prevent duplicate declaration errors.
@@ -168,9 +179,16 @@ User query: ${enhancedQuery}`;
168
179
  description: tool.description,
169
180
  parameters: tool.parameters,
170
181
  displayInfoFromArgs: tool.displayInfoFromArgs,
171
- handler: args => tool.handler(args, {
172
- conversationContext: this.context ?? null,
173
- }),
182
+ handler: (args, options) => tool.handler(
183
+ args,
184
+ {
185
+ conversationContext: this.context ?? null,
186
+ changeManager: this.#changes,
187
+ createExtensionScope: this.#createExtensionScope.bind(this),
188
+ execJs: this.#execJs,
189
+ },
190
+ options,
191
+ ),
174
192
  });
175
193
  }
176
194
 
@@ -328,24 +328,27 @@ export class AiConversation {
328
328
  };
329
329
  }
330
330
 
331
+ #filterHistoryForNewAgent(): Host.AidaClient.Content[] {
332
+ return this.#agent?.history
333
+ .map(content => {
334
+ return {
335
+ ...content,
336
+ parts: content.parts.filter(part => !('functionCall' in part) && !('functionResponse' in part)),
337
+ };
338
+ })
339
+ .filter(content => content.parts.length > 0) ??
340
+ [];
341
+ }
342
+
331
343
  #updateAgent(type: ConversationType): void {
332
344
  if (this.#type === type) {
333
345
  return;
334
346
  }
335
347
 
336
- this.#type = type;
348
+ const isTransitioningFromStorage = this.#type === ConversationType.STORAGE && type !== ConversationType.STORAGE;
349
+ const history = isTransitioningFromStorage ? [] : this.#filterHistoryForNewAgent();
337
350
 
338
- // We need to filter out the function calls
339
- // as the LLM tries to call the existing ones.
340
- const history =
341
- this.#agent?.history
342
- .map(content => {
343
- return {
344
- ...content,
345
- parts: content.parts.filter(part => !('functionCall' in part) && !('functionResponse' in part)),
346
- };
347
- })
348
- .filter(content => content.parts.length > 0);
351
+ this.#type = type;
349
352
 
350
353
  const options = {
351
354
  aidaClient: this.#aidaClient,
@@ -8,6 +8,8 @@ import * as i18n from '../../core/i18n/i18n.js';
8
8
  import type * as Platform from '../../core/platform/platform.js';
9
9
  import * as Root from '../../core/root/root.js';
10
10
 
11
+ import {debugLog} from './debug.js';
12
+
11
13
  const UIStrings = {
12
14
  /**
13
15
  * @description Message shown to the user if the age check is not successful.
@@ -71,3 +73,72 @@ export function isSameOrigin(url1: Platform.DevToolsPath.UrlString, url2: Platfo
71
73
  const origin2 = Common.ParsedURL.ParsedURL.extractOrigin(url2);
72
74
  return origin1 !== '' && origin1 === origin2;
73
75
  }
76
+
77
+ export interface OneShotPromptRequest {
78
+ aidaClient: Host.AidaClient.AidaClient;
79
+ preamble: string;
80
+ query: string;
81
+ clientFeature: Host.AidaClient.ClientFeature;
82
+ temperature?: number;
83
+ modelId?: string;
84
+ userTier?: string;
85
+ serverSideLoggingEnabled?: boolean;
86
+ signal?: AbortSignal;
87
+ }
88
+
89
+ export async function runOneShotPrompt({
90
+ aidaClient,
91
+ preamble,
92
+ query,
93
+ clientFeature,
94
+ temperature,
95
+ modelId,
96
+ userTier,
97
+ serverSideLoggingEnabled,
98
+ signal,
99
+ }: OneShotPromptRequest): Promise<string> {
100
+ const chromeVersion = Root.Runtime.getChromeVersion();
101
+ if (!chromeVersion) {
102
+ throw new Error('Cannot determine Chrome version');
103
+ }
104
+ const disallowLogging = !serverSideLoggingEnabled;
105
+ const sessionId = crypto.randomUUID();
106
+
107
+ const userTierEnum = Host.AidaClient.convertToUserTierEnum(userTier);
108
+ const finalPreamble = userTierEnum === Host.AidaClient.UserTier.TESTERS ? preamble : undefined;
109
+
110
+ const request: Host.AidaClient.DoConversationRequest = {
111
+ client: Host.AidaClient.CLIENT_NAME,
112
+ current_message: {
113
+ parts: [{text: query}],
114
+ role: Host.AidaClient.Role.USER,
115
+ },
116
+ preamble: finalPreamble,
117
+ options: {
118
+ temperature: typeof temperature === 'number' && temperature >= 0 ? temperature : undefined,
119
+ model_id: modelId || undefined,
120
+ },
121
+ metadata: {
122
+ disable_user_content_logging: disallowLogging,
123
+ string_session_id: sessionId,
124
+ user_tier: userTierEnum,
125
+ client_version: chromeVersion,
126
+ },
127
+ functionality_type: Host.AidaClient.FunctionalityType.CHAT,
128
+ client_feature: clientFeature,
129
+ };
130
+
131
+ let textResponse = '';
132
+ try {
133
+ for await (const response of aidaClient.doConversation(request, {signal})) {
134
+ if (response.explanation) {
135
+ textResponse = response.explanation;
136
+ }
137
+ }
138
+ } catch (err) {
139
+ debugLog('Error calling AIDA for one-shot prompt', err);
140
+ throw err;
141
+ }
142
+
143
+ return textResponse;
144
+ }
@@ -9,8 +9,6 @@ import type * as Protocol from '../../generated/protocol.js';
9
9
 
10
10
  export interface Change {
11
11
  groupId: string;
12
- // Optional turn ID to group changes from the same turn.
13
- turnId?: number;
14
12
  // Optional about where in the source the selector was defined.
15
13
  sourceLocation?: string;
16
14
  // Selector used by the page or a simple selector as the fallback.
@@ -104,7 +102,6 @@ export class ChangeManager {
104
102
  // it currently causes crashes in the Styles tab when duplicate selectors exist (crbug.com/393515428).
105
103
  // This workaround avoids that crash.
106
104
  existingChange.groupId = change.groupId;
107
- existingChange.turnId = change.turnId;
108
105
  } else {
109
106
  changes.push({
110
107
  ...change,
@@ -126,11 +123,11 @@ export class ChangeManager {
126
123
  .join('\n\n');
127
124
  }
128
125
 
129
- getChangedNodesForGroupId(groupId: string, turnId?: number): Protocol.DOM.BackendNodeId[] {
126
+ getChangedNodesForGroupId(groupId: string): Protocol.DOM.BackendNodeId[] {
130
127
  const nodes = new Set<Protocol.DOM.BackendNodeId>();
131
128
  for (const changes of this.#stylesheetChanges.values()) {
132
129
  for (const change of changes) {
133
- if (change.groupId === groupId && change.backendNodeId && (turnId === undefined || change.turnId === turnId)) {
130
+ if (change.groupId === groupId && change.backendNodeId) {
134
131
  nodes.add(change.backendNodeId);
135
132
  }
136
133
  }
@@ -2,10 +2,10 @@
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 * as Host from '../../../core/host/host.js';
6
- import * as Root from '../../../core/root/root.js';
5
+ import * as Host from '../../core/host/host.js';
6
+ import * as Root from '../../core/root/root.js';
7
7
 
8
- import {AiAgent, type ContextResponse, ConversationContext, type RequestOptions, ResponseType} from './AiAgent.js';
8
+ import {runOneShotPrompt} from './AiUtils.js';
9
9
 
10
10
  const preamble = `### Role
11
11
  You are a Conversation Summarizer. Your task is to take a transcript of a conversation between a user and a DevTools AI agent and produce a succinct, actionable Markdown summary. This summary will be used to help apply fixes in an IDE, so it must capture all relevant technical details, findings, and proposed code changes without any conversational fluff.
@@ -98,90 +98,53 @@ color: red;
98
98
  - Professional, objective, and dense.
99
99
  - Past tense for actions; Present tense for technical facts.`;
100
100
 
101
- export class ConversationSummaryContext extends ConversationContext<string> {
102
- #conversation: string;
103
- constructor(conversation: string) {
104
- super();
105
- this.#conversation = conversation;
106
- }
107
-
108
- override getURL(): string {
109
- return 'devtools://ai-assistance';
110
- }
111
-
112
- getItem(): string {
113
- return this.#conversation;
114
- }
115
-
116
- override getTitle(): string {
117
- return 'Conversation';
118
- }
101
+ export interface ConversationSummaryOptions {
102
+ aidaClient: Host.AidaClient.AidaClient;
103
+ serverSideLoggingEnabled?: boolean;
119
104
  }
120
105
 
121
106
  /**
122
- * An agent that takes a full conversation between a user and an agent in markdown
107
+ * A class that takes a full conversation between a user and an agent in markdown
123
108
  * format and produces a succinct summary of the conversation.
124
109
  *
125
110
  * This summary is designed to be read by a local agent in the user's IDE and it
126
111
  * will be used to help apply fixes to the user's local codebase based on the
127
112
  * debugging information the devtools agent found.
128
- *
129
- * This agent is not intended to be used directly by users in the AI Assistance
130
- * panel when chatting with DevTools AI.
131
113
  */
132
- export class ConversationSummaryAgent extends AiAgent<string> {
133
- override preamble = preamble;
114
+ export class ConversationSummary {
115
+ readonly #aidaClient: Host.AidaClient.AidaClient;
116
+ readonly #serverSideLoggingEnabled: boolean;
134
117
 
135
- get clientFeature(): Host.AidaClient.ClientFeature {
136
- return Host.AidaClient.ClientFeature.CHROME_CONVERSATION_SUMMARY_AGENT;
118
+ constructor(options: ConversationSummaryOptions) {
119
+ this.#aidaClient = options.aidaClient;
120
+ this.#serverSideLoggingEnabled = options.serverSideLoggingEnabled ?? false;
137
121
  }
138
122
 
139
- get userTier(): string|undefined {
140
- // TODO(b/491772868): tidy up userTier & feature flags in the backend.
141
- return Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
142
- }
123
+ async summarizeConversation(conversation: string): Promise<string> {
124
+ const enhancedQuery = `Summarize the following conversation:\n\n${conversation}`;
143
125
 
144
- get options(): RequestOptions {
145
126
  // TODO(b/491772868): tidy up userTier & feature flags in the backend.
146
127
  const temperature = Root.Runtime.hostConfig.devToolsFreestyler?.temperature;
147
128
  const modelId = Root.Runtime.hostConfig.devToolsFreestyler?.modelId;
129
+ const userTier = Root.Runtime.hostConfig.devToolsFreestyler?.userTier;
148
130
 
149
- return {
131
+ const resultText = await runOneShotPrompt({
132
+ aidaClient: this.#aidaClient,
133
+ preamble,
134
+ query: enhancedQuery,
135
+ clientFeature: Host.AidaClient.ClientFeature.CHROME_CONVERSATION_SUMMARY_AGENT,
150
136
  temperature,
151
137
  modelId,
152
- };
153
- }
138
+ userTier,
139
+ serverSideLoggingEnabled: this.#serverSideLoggingEnabled,
140
+ });
154
141
 
155
- async * handleContextDetails(context: ConversationContext<string>|null): AsyncGenerator<ContextResponse, void, void> {
156
- if (!context) {
157
- return;
142
+ if (!resultText) {
143
+ throw new Error('Failed to summarize conversation');
158
144
  }
159
145
 
160
- yield {
161
- type: ResponseType.CONTEXT,
162
- details: [
163
- {
164
- title: 'Conversation transcript',
165
- text: context.getItem(),
166
- },
167
- ],
168
- };
169
- }
170
-
171
- override async enhanceQuery(query: string, context: ConversationContext<string>|null): Promise<string> {
172
- const conversation = context ? context.getItem() : query;
173
- return `Summarize the following conversation:\n\n${conversation}`;
174
- }
175
-
176
- async summarizeConversation(conversation: string): Promise<string> {
177
- const context = new ConversationSummaryContext(conversation);
178
- const response = await Array.fromAsync(this.run('', {selected: context}));
179
- const lastResponse = response.at(-1);
180
- if (lastResponse && lastResponse.type === ResponseType.ANSWER && lastResponse.complete === true) {
181
- const disclaimer =
182
- '*Note: The code fixes and findings above were identified on a live page in DevTools. When applying them to your codebase, please adapt them to your project\'s specific technical stack (e.g., Tailwind CSS classes, CSS modules, framework components) rather than applying them as literal CSS overrides.*';
183
- return `${lastResponse.text.trim()}\n\n${disclaimer}`;
184
- }
185
- throw new Error('Failed to summarize conversation');
146
+ const disclaimer =
147
+ '*Note: The code fixes and findings above were identified on a live page in DevTools. When applying them to your codebase, please adapt them to your project\'s specific technical stack (e.g., Tailwind CSS classes, CSS modules, framework components) rather than applying them as literal CSS overrides.*';
148
+ return `${resultText.trim()}\n\n${disclaimer}`;
186
149
  }
187
150
  }
@@ -34,7 +34,6 @@ export class ExtensionScope {
34
34
  }) => Promise<void>> = [];
35
35
  #changeManager: ChangeManager;
36
36
  #agentId: string;
37
- #turnId?: number;
38
37
  /** Don't use directly use the getter */
39
38
  #frameId?: Protocol.Page.FrameId|null;
40
39
  /** Don't use directly use the getter */
@@ -42,12 +41,11 @@ export class ExtensionScope {
42
41
 
43
42
  readonly #bindingMutex = new Common.Mutex.Mutex();
44
43
 
45
- constructor(changes: ChangeManager, agentId: string, selectedNode: SDK.DOMModel.DOMNode|null, turnId?: number) {
44
+ constructor(changes: ChangeManager, agentId: string, selectedNode: SDK.DOMModel.DOMNode|null) {
46
45
  this.#changeManager = changes;
47
46
  const frameId = selectedNode?.frameId();
48
47
  const target = selectedNode?.domModel().target();
49
48
  this.#agentId = agentId;
50
- this.#turnId = turnId;
51
49
  this.#target = target;
52
50
  this.#frameId = frameId;
53
51
  }
@@ -357,7 +355,6 @@ export class ExtensionScope {
357
355
  const sanitizedStyles = await this.sanitizedStyleChanges(context.selector, arg.styles);
358
356
  const styleChanges = await this.#changeManager.addChange(cssModel, this.frameId, {
359
357
  groupId: this.#agentId,
360
- turnId: this.#turnId,
361
358
  sourceLocation: context.sourceLocation,
362
359
  selector: context.selector,
363
360
  simpleSelector: context.simpleSelector,
@@ -2,21 +2,12 @@
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 * as Host from '../../../core/host/host.js';
6
- import * as Root from '../../../core/root/root.js';
7
- import type {AICallTree} from '../performance/AICallTree.js';
8
- import type {AgentFocus} from '../performance/AIContext.js';
9
-
10
- import {AiAgent, type ContextResponse, type ConversationContext, type RequestOptions, ResponseType} from './AiAgent.js';
11
- import {PerformanceTraceContext} from './PerformanceAgent.js';
12
-
13
- /**
14
- * Preamble clocks in at ~970 tokens.
15
- * The prose is around 4.5 chars per token.
16
- * The data can be as bad as 1.8 chars per token
17
- *
18
- * Check token length in https://aistudio.google.com/
19
- */
5
+ import * as Host from '../../core/host/host.js';
6
+ import * as Root from '../../core/root/root.js';
7
+
8
+ import {runOneShotPrompt} from './AiUtils.js';
9
+ import type {AICallTree} from './performance/AICallTree.js';
10
+
20
11
  const callTreePreamble = `You are an expert performance analyst embedded within Chrome DevTools.
21
12
  You meticulously examine web application behavior captured by the Chrome DevTools Performance Panel and Chrome tracing.
22
13
  You will receive a structured text representation of a call tree, derived from a user-selected call frame within a performance trace's flame chart.
@@ -79,80 +70,6 @@ The 'calculatePosition' function, taking 80ms, is a potential bottleneck.
79
70
  Consider optimizing the position calculation logic or reducing the frequency of calls to improve animation performance.
80
71
  `;
81
72
 
82
- export class PerformanceAnnotationsAgent extends AiAgent<AgentFocus> {
83
- override preamble = callTreePreamble;
84
-
85
- get clientFeature(): Host.AidaClient.ClientFeature {
86
- return Host.AidaClient.ClientFeature.CHROME_PERFORMANCE_ANNOTATIONS_AGENT;
87
- }
88
-
89
- get userTier(): string|undefined {
90
- return Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.userTier;
91
- }
92
-
93
- get options(): RequestOptions {
94
- const temperature = Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.temperature;
95
- const modelId = Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.modelId;
96
-
97
- return {
98
- temperature,
99
- modelId,
100
- };
101
- }
102
-
103
- async *
104
- handleContextDetails(context: ConversationContext<AgentFocus>|null): AsyncGenerator<ContextResponse, void, void> {
105
- if (!context) {
106
- return;
107
- }
108
-
109
- const focus = context.getItem();
110
- if (!focus.callTree) {
111
- throw new Error('unexpected context');
112
- }
113
-
114
- const callTree = focus.callTree;
115
-
116
- yield {
117
- type: ResponseType.CONTEXT,
118
- details: [
119
- {
120
- title: 'Selected call tree',
121
- text: callTree.serialize(),
122
- },
123
- ],
124
- };
125
- }
126
-
127
- override async enhanceQuery(query: string, context: ConversationContext<AgentFocus>|null): Promise<string> {
128
- if (!context) {
129
- return query;
130
- }
131
-
132
- const focus = context.getItem();
133
- if (!focus.callTree) {
134
- throw new Error('unexpected context');
135
- }
136
-
137
- const callTree = focus.callTree;
138
- const contextString = callTree.serialize();
139
- return `${contextString}\n\n# User request\n\n${query}`;
140
- }
141
-
142
- /**
143
- * Used in the Performance panel to automatically generate a label for a selected entry.
144
- */
145
- async generateAIEntryLabel(callTree: AICallTree): Promise<string> {
146
- const context = PerformanceTraceContext.fromCallTree(callTree);
147
- const response = await Array.fromAsync(this.run(AI_LABEL_GENERATION_PROMPT, {selected: context}));
148
- const lastResponse = response.at(-1);
149
- if (lastResponse && lastResponse.type === ResponseType.ANSWER && lastResponse.complete === true) {
150
- return lastResponse.text.trim();
151
- }
152
- throw new Error('Failed to generate AI entry label');
153
- }
154
- }
155
-
156
73
  const AI_LABEL_GENERATION_PROMPT = `## Instruction:
157
74
  Generate a concise label (max 60 chars, single line) describing the *user-visible effect* of the selected call tree's activity, based solely on the provided call tree data.
158
75
 
@@ -168,3 +85,44 @@ Generate a concise label (max 60 chars, single line) describing the *user-visibl
168
85
  - Only include third-party script names if their identification is highly confident.
169
86
  - Very important: Only output the 60 character label text, your response will be used in full to show to the user as an annotation in the timeline.
170
87
  `;
88
+
89
+ export interface PerformanceAnnotationsOptions {
90
+ aidaClient: Host.AidaClient.AidaClient;
91
+ serverSideLoggingEnabled?: boolean;
92
+ }
93
+
94
+ export class PerformanceAnnotations {
95
+ readonly #aidaClient: Host.AidaClient.AidaClient;
96
+ readonly #serverSideLoggingEnabled: boolean;
97
+
98
+ constructor(options: PerformanceAnnotationsOptions) {
99
+ this.#aidaClient = options.aidaClient;
100
+ this.#serverSideLoggingEnabled = options.serverSideLoggingEnabled ?? false;
101
+ }
102
+
103
+ async generateAIEntryLabel(callTree: AICallTree): Promise<string> {
104
+ const contextString = callTree.serialize();
105
+ const query = `${contextString}\n\n# User request\n\n${AI_LABEL_GENERATION_PROMPT}`;
106
+
107
+ const temperature = Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.temperature;
108
+ const modelId = Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.modelId;
109
+ const userTier = Root.Runtime.hostConfig.devToolsAiAssistancePerformanceAgent?.userTier;
110
+
111
+ const resultText = await runOneShotPrompt({
112
+ aidaClient: this.#aidaClient,
113
+ preamble: callTreePreamble,
114
+ query,
115
+ clientFeature: Host.AidaClient.ClientFeature.CHROME_PERFORMANCE_ANNOTATIONS_AGENT,
116
+ temperature,
117
+ modelId,
118
+ userTier,
119
+ serverSideLoggingEnabled: this.#serverSideLoggingEnabled,
120
+ });
121
+
122
+ if (!resultText) {
123
+ throw new Error('Failed to generate AI entry label');
124
+ }
125
+
126
+ return resultText.trim();
127
+ }
128
+ }