chrome-devtools-frontend 1.0.1516909 → 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.
- package/config/owner/COMMON_OWNERS +2 -2
- package/docs/checklist/README.md +2 -2
- package/docs/checklist/javascript.md +1 -1
- package/docs/contributing/README.md +1 -1
- package/docs/contributing/settings-experiments-features.md +9 -8
- package/docs/cookbook/devtools_on_devtools.md +2 -2
- package/docs/cookbook/localization.md +10 -10
- package/docs/devtools-protocol.md +9 -8
- package/docs/ecosystem/automatic_workspace_folders.md +3 -3
- package/docs/get_the_code.md +0 -2
- package/docs/styleguide/ux/components.md +166 -85
- package/docs/styleguide/ux/numbers.md +3 -4
- package/eslint.config.mjs +1 -0
- package/front_end/core/common/README.md +13 -12
- package/front_end/core/host/GdpClient.ts +16 -1
- package/front_end/core/host/UserMetrics.ts +4 -2
- package/front_end/core/root/Runtime.ts +13 -0
- package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
- package/front_end/core/sdk/EnhancedTracesParser.ts +5 -5
- package/front_end/core/sdk/RehydratingConnection.snapshot.txt +211 -0
- package/front_end/core/sdk/TargetManager.ts +4 -0
- package/front_end/entrypoints/main/MainImpl.ts +6 -3
- package/front_end/generated/InspectorBackendCommands.js +10 -7
- package/front_end/generated/SupportedCSSProperties.js +40 -11
- package/front_end/generated/protocol-mapping.d.ts +16 -1
- package/front_end/generated/protocol-proxy-api.d.ts +13 -1
- package/front_end/generated/protocol.ts +95 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +57 -10
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +119 -51
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +0 -31
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +19 -315
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +224 -50
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +310 -11
- package/front_end/models/ai_assistance/performance/AIContext.ts +15 -2
- package/front_end/models/ai_code_completion/AiCodeCompletion.ts +22 -11
- package/front_end/models/badges/AiExplorerBadge.ts +19 -3
- package/front_end/models/badges/Badge.ts +10 -3
- package/front_end/models/badges/CodeWhispererBadge.ts +3 -4
- package/front_end/models/badges/DOMDetectiveBadge.ts +1 -0
- package/front_end/models/badges/SpeedsterBadge.ts +1 -0
- package/front_end/models/badges/StarterBadge.ts +3 -2
- package/front_end/models/badges/UserBadges.ts +21 -3
- package/front_end/models/badges/badges.ts +1 -0
- package/front_end/models/javascript_metadata/NativeFunctions.js +2 -2
- package/front_end/models/trace/EventsSerializer.ts +4 -3
- package/front_end/models/trace/README.md +28 -1
- package/front_end/models/trace/handlers/UserInteractionsHandler.ts +101 -73
- package/front_end/models/trace/handlers/UserTimingsHandler.ts +1 -1
- package/front_end/models/trace/helpers/Timing.ts +1 -1
- package/front_end/models/trace/helpers/Trace.ts +99 -43
- package/front_end/models/trace/types/TraceEvents.ts +9 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +113 -191
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +9 -9
- package/front_end/panels/accessibility/AccessibilitySubPane.ts +6 -4
- package/front_end/panels/accessibility/accessibilityProperties.css +2 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +20 -3
- package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +44 -0
- package/front_end/panels/application/components/BounceTrackingMitigationsView.ts +2 -2
- package/front_end/panels/common/AiCodeCompletionDisclaimer.ts +32 -9
- package/front_end/panels/common/AiCodeCompletionSummaryToolbar.ts +7 -1
- package/front_end/panels/common/BadgeNotification.ts +21 -5
- package/front_end/panels/common/GdpSignUpDialog.ts +20 -12
- package/front_end/panels/console/ConsolePrompt.ts +1 -1
- package/front_end/panels/console/ConsoleView.ts +6 -2
- package/front_end/panels/css_overview/CSSOverviewCompletedView.ts +5 -5
- package/front_end/panels/elements/ElementsPanel.ts +4 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +18 -0
- package/front_end/panels/elements/ElementsTreeOutline.ts +13 -0
- package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
- package/front_end/panels/media/TickingFlameChart.ts +1 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
- package/front_end/panels/recorder/components/RecordingView.ts +2 -2
- package/front_end/panels/search/SearchResultsPane.ts +167 -152
- package/front_end/panels/search/SearchView.ts +36 -26
- package/front_end/panels/search/searchResultsPane.css +9 -0
- package/front_end/panels/security/CookieControlsView.ts +2 -1
- package/front_end/panels/settings/AISettingsTab.ts +6 -3
- package/front_end/panels/settings/components/SyncSection.ts +39 -17
- package/front_end/panels/settings/emulation/components/UserAgentClientHintsForm.ts +1 -1
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +9 -1
- package/front_end/panels/sources/SourcesPanel.ts +4 -1
- package/front_end/panels/sources/sourcesView.css +6 -1
- package/front_end/panels/timeline/AppenderUtils.ts +2 -2
- package/front_end/panels/timeline/ExtensionTrackAppender.ts +13 -4
- package/front_end/panels/timeline/GPUTrackAppender.ts +2 -1
- package/front_end/panels/timeline/InteractionsTrackAppender.ts +5 -1
- package/front_end/panels/timeline/LayoutShiftsTrackAppender.ts +2 -1
- package/front_end/panels/timeline/ThreadAppender.ts +12 -3
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +9 -4
- package/front_end/panels/timeline/TimelinePanel.ts +3 -2
- package/front_end/panels/timeline/TimelineUIUtils.ts +5 -4
- package/front_end/panels/timeline/TimingsTrackAppender.ts +6 -1
- package/front_end/panels/timeline/components/CPUThrottlingSelector.ts +95 -82
- package/front_end/panels/timeline/components/LayoutShiftDetails.ts +1 -1
- package/front_end/panels/timeline/components/LiveMetricsView.ts +2 -2
- package/front_end/panels/timeline/components/NetworkRequestDetails.ts +1 -1
- package/front_end/panels/timeline/components/RelatedInsightChips.ts +1 -1
- package/front_end/panels/timeline/components/SidebarSingleInsightSet.ts +1 -1
- package/front_end/panels/timeline/components/cpuThrottlingSelector.css +17 -15
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +3 -0
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/chunk/codemirror.js.map +1 -1
- package/front_end/third_party/codemirror.next/codemirror.next.d.ts +6 -9
- package/front_end/third_party/codemirror.next/package.json +2 -1
- package/front_end/third_party/diff/README.chromium +1 -0
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Realm.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/node/ChromeLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js +16 -25
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Function.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +19 -28
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js +1 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/node/ChromeLauncher.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js +16 -25
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/Function.js.map +1 -1
- package/front_end/third_party/puppeteer/package/package.json +10 -3
- package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/node/ChromeLauncher.ts +1 -0
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/Function.ts +22 -30
- package/front_end/ui/components/dialogs/Dialog.ts +1 -1
- package/front_end/ui/components/markdown_view/MarkdownImage.ts +4 -5
- package/front_end/ui/components/switch/SwitchImpl.ts +12 -1
- package/front_end/ui/components/text_editor/config.ts +22 -9
- package/front_end/ui/components/tooltips/Tooltip.ts +70 -31
- package/front_end/ui/legacy/README.md +33 -24
- package/front_end/ui/legacy/SearchableView.ts +19 -26
- package/front_end/ui/legacy/TextPrompt.ts +166 -1
- package/front_end/ui/legacy/Treeoutline.ts +19 -3
- package/front_end/ui/legacy/UIUtils.ts +15 -2
- package/front_end/ui/legacy/XElement.ts +0 -43
- package/front_end/ui/legacy/components/perf_ui/FlameChart.ts +20 -4
- package/front_end/ui/legacy/components/source_frame/XMLView.ts +12 -11
- package/front_end/ui/lit/i18n-template.ts +5 -2
- package/front_end/ui/visual_logging/KnownContextValues.ts +23 -6
- package/front_end/ui/visual_logging/README.md +43 -27
- package/package.json +1 -1
@@ -1082,8 +1082,10 @@ export namespace Audits {
|
|
1082
1082
|
WriteErrorInsufficientResources = 'WriteErrorInsufficientResources',
|
1083
1083
|
WriteErrorInvalidMatchField = 'WriteErrorInvalidMatchField',
|
1084
1084
|
WriteErrorInvalidStructuredHeader = 'WriteErrorInvalidStructuredHeader',
|
1085
|
+
WriteErrorInvalidTTLField = 'WriteErrorInvalidTTLField',
|
1085
1086
|
WriteErrorNavigationRequest = 'WriteErrorNavigationRequest',
|
1086
1087
|
WriteErrorNoMatchField = 'WriteErrorNoMatchField',
|
1088
|
+
WriteErrorNonIntegerTTLField = 'WriteErrorNonIntegerTTLField',
|
1087
1089
|
WriteErrorNonListMatchDestField = 'WriteErrorNonListMatchDestField',
|
1088
1090
|
WriteErrorNonSecureContext = 'WriteErrorNonSecureContext',
|
1089
1091
|
WriteErrorNonStringIdField = 'WriteErrorNonStringIdField',
|
@@ -9962,6 +9964,10 @@ export namespace Network {
|
|
9962
9964
|
* request corresponding to the main frame.
|
9963
9965
|
*/
|
9964
9966
|
isSameSite?: boolean;
|
9967
|
+
/**
|
9968
|
+
* True when the resource request is ad-related.
|
9969
|
+
*/
|
9970
|
+
isAdRelated?: boolean;
|
9965
9971
|
}
|
9966
9972
|
|
9967
9973
|
/**
|
@@ -10999,6 +11005,43 @@ export namespace Network {
|
|
10999
11005
|
Zstd = 'zstd',
|
11000
11006
|
}
|
11001
11007
|
|
11008
|
+
export interface NetworkConditions {
|
11009
|
+
/**
|
11010
|
+
* Only matching requests will be affected by these conditions. Patterns use the URLPattern constructor string
|
11011
|
+
* syntax (https://urlpattern.spec.whatwg.org/). If the pattern is empty, all requests are matched (including p2p
|
11012
|
+
* connections).
|
11013
|
+
*/
|
11014
|
+
urlPattern: string;
|
11015
|
+
/**
|
11016
|
+
* Minimum latency from request sent to response headers received (ms).
|
11017
|
+
*/
|
11018
|
+
latency: number;
|
11019
|
+
/**
|
11020
|
+
* Maximal aggregated download throughput (bytes/sec). -1 disables download throttling.
|
11021
|
+
*/
|
11022
|
+
downloadThroughput: number;
|
11023
|
+
/**
|
11024
|
+
* Maximal aggregated upload throughput (bytes/sec). -1 disables upload throttling.
|
11025
|
+
*/
|
11026
|
+
uploadThroughput: number;
|
11027
|
+
/**
|
11028
|
+
* Connection type if known.
|
11029
|
+
*/
|
11030
|
+
connectionType?: ConnectionType;
|
11031
|
+
/**
|
11032
|
+
* WebRTC packet loss (percent, 0-100). 0 disables packet loss emulation, 100 drops all the packets.
|
11033
|
+
*/
|
11034
|
+
packetLoss?: number;
|
11035
|
+
/**
|
11036
|
+
* WebRTC packet queue length (packet). 0 removes any queue length limitations.
|
11037
|
+
*/
|
11038
|
+
packetQueueLength?: integer;
|
11039
|
+
/**
|
11040
|
+
* WebRTC packetReordering feature.
|
11041
|
+
*/
|
11042
|
+
packetReordering?: boolean;
|
11043
|
+
}
|
11044
|
+
|
11002
11045
|
export const enum DirectSocketDnsQueryType {
|
11003
11046
|
Ipv4 = 'ipv4',
|
11004
11047
|
Ipv6 = 'ipv6',
|
@@ -11364,6 +11407,50 @@ export namespace Network {
|
|
11364
11407
|
packetReordering?: boolean;
|
11365
11408
|
}
|
11366
11409
|
|
11410
|
+
export interface EmulateNetworkConditionsByRuleRequest {
|
11411
|
+
/**
|
11412
|
+
* True to emulate internet disconnection.
|
11413
|
+
*/
|
11414
|
+
offline: boolean;
|
11415
|
+
/**
|
11416
|
+
* Configure conditions for matching requests. If multiple entries match a request, the first entry wins. Global
|
11417
|
+
* conditions can be configured by leaving the urlPattern for the conditions empty. These global conditions are
|
11418
|
+
* also applied for throttling of p2p connections.
|
11419
|
+
*/
|
11420
|
+
matchedNetworkConditions: NetworkConditions[];
|
11421
|
+
}
|
11422
|
+
|
11423
|
+
export interface EmulateNetworkConditionsByRuleResponse extends ProtocolResponseWithError {
|
11424
|
+
/**
|
11425
|
+
* An id for each entry in matchedNetworkConditions. The id will be included in the requestWillBeSentExtraInfo for
|
11426
|
+
* requests affected by a rule.
|
11427
|
+
*/
|
11428
|
+
ruleIds: string[];
|
11429
|
+
}
|
11430
|
+
|
11431
|
+
export interface OverrideNetworkStateRequest {
|
11432
|
+
/**
|
11433
|
+
* True to emulate internet disconnection.
|
11434
|
+
*/
|
11435
|
+
offline: boolean;
|
11436
|
+
/**
|
11437
|
+
* Minimum latency from request sent to response headers received (ms).
|
11438
|
+
*/
|
11439
|
+
latency: number;
|
11440
|
+
/**
|
11441
|
+
* Maximal aggregated download throughput (bytes/sec). -1 disables download throttling.
|
11442
|
+
*/
|
11443
|
+
downloadThroughput: number;
|
11444
|
+
/**
|
11445
|
+
* Maximal aggregated upload throughput (bytes/sec). -1 disables upload throttling.
|
11446
|
+
*/
|
11447
|
+
uploadThroughput: number;
|
11448
|
+
/**
|
11449
|
+
* Connection type if known.
|
11450
|
+
*/
|
11451
|
+
connectionType?: ConnectionType;
|
11452
|
+
}
|
11453
|
+
|
11367
11454
|
export interface EnableRequest {
|
11368
11455
|
/**
|
11369
11456
|
* Buffer size in bytes to use when preserving network payloads (XHRs, etc).
|
@@ -12358,6 +12445,11 @@ export namespace Network {
|
|
12358
12445
|
* Whether the site has partitioned cookies stored in a partition different than the current one.
|
12359
12446
|
*/
|
12360
12447
|
siteHasCookieInOtherPartition?: boolean;
|
12448
|
+
/**
|
12449
|
+
* The network conditions id if this request was affected by network conditions configured via
|
12450
|
+
* emulateNetworkConditionsByRule.
|
12451
|
+
*/
|
12452
|
+
appliedNetworkConditionsId?: string;
|
12361
12453
|
}
|
12362
12454
|
|
12363
12455
|
/**
|
@@ -20896,6 +20988,7 @@ export namespace Runtime {
|
|
20896
20988
|
Dataview = 'dataview',
|
20897
20989
|
Webassemblymemory = 'webassemblymemory',
|
20898
20990
|
Wasmvalue = 'wasmvalue',
|
20991
|
+
Trustedtype = 'trustedtype',
|
20899
20992
|
}
|
20900
20993
|
|
20901
20994
|
/**
|
@@ -20989,6 +21082,7 @@ export namespace Runtime {
|
|
20989
21082
|
Dataview = 'dataview',
|
20990
21083
|
Webassemblymemory = 'webassemblymemory',
|
20991
21084
|
Wasmvalue = 'wasmvalue',
|
21085
|
+
Trustedtype = 'trustedtype',
|
20992
21086
|
}
|
20993
21087
|
|
20994
21088
|
/**
|
@@ -21053,6 +21147,7 @@ export namespace Runtime {
|
|
21053
21147
|
Dataview = 'dataview',
|
21054
21148
|
Webassemblymemory = 'webassemblymemory',
|
21055
21149
|
Wasmvalue = 'wasmvalue',
|
21150
|
+
Trustedtype = 'trustedtype',
|
21056
21151
|
}
|
21057
21152
|
|
21058
21153
|
export interface PropertyPreview {
|
@@ -133,13 +133,7 @@ export interface ParsedAnswer {
|
|
133
133
|
suggestions?: [string, ...string[]];
|
134
134
|
}
|
135
135
|
|
136
|
-
export
|
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.
|
410
|
-
* by StylingAgent.
|
457
|
+
* though/action/title/answer/suggestions component.
|
411
458
|
*/
|
412
459
|
parseTextResponse(response: string): ParsedResponse {
|
413
|
-
return
|
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';
|
@@ -15,7 +13,6 @@ import * as Trace from '../../trace/trace.js';
|
|
15
13
|
import {ConversationType} from '../AiHistoryStorage.js';
|
16
14
|
import {
|
17
15
|
PerformanceInsightFormatter,
|
18
|
-
TraceEventFormatter,
|
19
16
|
} from '../data_formatters/PerformanceInsightFormatter.js';
|
20
17
|
import {PerformanceTraceFormatter} from '../data_formatters/PerformanceTraceFormatter.js';
|
21
18
|
import {debugLog} from '../debug.js';
|
@@ -62,7 +59,7 @@ const lockedString = i18n.i18n.lockedString;
|
|
62
59
|
*
|
63
60
|
* Check token length in https://aistudio.google.com/
|
64
61
|
*/
|
65
|
-
const
|
62
|
+
const preamble = `You are an assistant, expert in web performance and highly skilled with Chrome DevTools.
|
66
63
|
|
67
64
|
Your primary goal is to provide actionable advice to web developers about their web page by using the Chrome Performance Panel and analyzing a trace. You may need to diagnose problems yourself, or you may be given direction for what to focus on by the user.
|
68
65
|
|
@@ -129,6 +126,16 @@ Adhere to the following critical requirements:
|
|
129
126
|
- Do not provide answers on non-web-development topics, such as legal, financial, medical, or personal advice.
|
130
127
|
`;
|
131
128
|
|
129
|
+
const extraPreambleWhenNotExternal = `Additional notes:
|
130
|
+
|
131
|
+
When referring to a trace event that has a corresponding \`eventKey\`, annotate your output using markdown link syntax. For example:
|
132
|
+
- When referring to an event that is a long task: [Long task](#r-123)
|
133
|
+
- When referring to a URL for which you know the eventKey of: [https://www.example.com](#s-1827)
|
134
|
+
- Never show the eventKey (like "eventKey: s-1852"); instead, use a markdown link as described above.
|
135
|
+
|
136
|
+
When asking the user to make a choice between multiple options, output a list of choices at the end of your text response. The format is \`SUGGESTIONS: ["suggestion1", "suggestion2", "suggestion3"]\`. This MUST start on a newline, and be a single line.
|
137
|
+
`;
|
138
|
+
|
132
139
|
const callFrameDataFormatDescription = `Each call frame is presented in the following format:
|
133
140
|
|
134
141
|
'id;name;duration;selfTime;urlIndex;childRange;[S]'
|
@@ -159,8 +166,8 @@ enum ScorePriority {
|
|
159
166
|
}
|
160
167
|
|
161
168
|
export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
|
162
|
-
static
|
163
|
-
return new PerformanceTraceContext(AgentFocus.
|
169
|
+
static fromParsedTrace(parsedTrace: Trace.TraceModel.ParsedTrace): PerformanceTraceContext {
|
170
|
+
return new PerformanceTraceContext(AgentFocus.fromParsedTrace(parsedTrace));
|
164
171
|
}
|
165
172
|
|
166
173
|
static fromInsight(parsedTrace: Trace.TraceModel.ParsedTrace, insight: Trace.Insights.Types.InsightModel):
|
@@ -224,7 +231,7 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
|
|
224
231
|
}
|
225
232
|
|
226
233
|
if (data.insight) {
|
227
|
-
return new PerformanceInsightFormatter(
|
234
|
+
return new PerformanceInsightFormatter(this.#focus, data.insight).getSuggestions();
|
228
235
|
}
|
229
236
|
|
230
237
|
const suggestions: ConversationSuggestions =
|
@@ -247,6 +254,15 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
|
|
247
254
|
if (cls && ModelHandlers.LayoutShifts.scoreClassificationForLayoutShift(cls.value) !== GOOD) {
|
248
255
|
suggestions.push({title: 'How can I improve CLS?', jslogContext: 'performance-default'});
|
249
256
|
}
|
257
|
+
|
258
|
+
// Add up to 3 suggestions from the top failing insights.
|
259
|
+
const top3FailingInsightSuggestions =
|
260
|
+
Object.values(data.insightSet.model)
|
261
|
+
.filter(model => model.state !== 'pass')
|
262
|
+
.map(model => new PerformanceInsightFormatter(this.#focus, model).getSuggestions().at(-1))
|
263
|
+
.filter(suggestion => !!suggestion)
|
264
|
+
.slice(0, 3);
|
265
|
+
suggestions.push(...top3FailingInsightSuggestions);
|
250
266
|
}
|
251
267
|
|
252
268
|
return suggestions;
|
@@ -263,7 +279,6 @@ const MAX_FUNCTION_RESULT_BYTE_LENGTH = 16384 * 4;
|
|
263
279
|
export class PerformanceAgent extends AiAgent<AgentFocus> {
|
264
280
|
#formatter: PerformanceTraceFormatter|null = null;
|
265
281
|
#lastInsightForEnhancedQuery: Trace.Insights.Types.InsightModel|undefined;
|
266
|
-
#eventsSerializer = new Trace.EventsSerializer.EventsSerializer();
|
267
282
|
#hasShownAnalyzeTraceContext = false;
|
268
283
|
|
269
284
|
/**
|
@@ -279,8 +294,12 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
279
294
|
*/
|
280
295
|
#functionCallCacheForFocus = new Map<AgentFocus, Record<string, Host.AidaClient.RequestFact>>();
|
281
296
|
|
297
|
+
#notExternalExtraPreambleFact: Host.AidaClient.RequestFact = {
|
298
|
+
text: extraPreambleWhenNotExternal,
|
299
|
+
metadata: {source: 'devtools', score: ScorePriority.CRITICAL}
|
300
|
+
};
|
282
301
|
#networkDataDescriptionFact: Host.AidaClient.RequestFact = {
|
283
|
-
text:
|
302
|
+
text: PerformanceTraceFormatter.networkDataFormatDescription,
|
284
303
|
metadata: {source: 'devtools', score: ScorePriority.CRITICAL}
|
285
304
|
};
|
286
305
|
#callFrameDataDescriptionFact: Host.AidaClient.RequestFact = {
|
@@ -290,7 +309,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
290
309
|
#traceFacts: Host.AidaClient.RequestFact[] = [];
|
291
310
|
|
292
311
|
get preamble(): string {
|
293
|
-
return
|
312
|
+
return preamble;
|
294
313
|
}
|
295
314
|
|
296
315
|
get clientFeature(): Host.AidaClient.ClientFeature {
|
@@ -313,23 +332,6 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
313
332
|
return ConversationType.PERFORMANCE;
|
314
333
|
}
|
315
334
|
|
316
|
-
#lookupEvent(key: Trace.Types.File.SerializableKey): Trace.Types.Events.Event|null {
|
317
|
-
const parsedTrace = this.context?.getItem().data.parsedTrace;
|
318
|
-
if (!parsedTrace) {
|
319
|
-
return null;
|
320
|
-
}
|
321
|
-
|
322
|
-
try {
|
323
|
-
return this.#eventsSerializer.eventForKey(key, parsedTrace);
|
324
|
-
} catch (err) {
|
325
|
-
if (err.toString().includes('Unknown trace event')) {
|
326
|
-
return null;
|
327
|
-
}
|
328
|
-
|
329
|
-
throw err;
|
330
|
-
}
|
331
|
-
}
|
332
|
-
|
333
335
|
async *
|
334
336
|
handleContextDetails(context: ConversationContext<AgentFocus>|null): AsyncGenerator<ContextResponse, void, void> {
|
335
337
|
if (!context) {
|
@@ -360,21 +362,81 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
360
362
|
return response.length > MAX_FUNCTION_RESULT_BYTE_LENGTH;
|
361
363
|
}
|
362
364
|
|
363
|
-
|
365
|
+
/**
|
366
|
+
* Sometimes the model will output URLs as plaintext; or a markdown link
|
367
|
+
* where the link is the actual URL. This function transforms such output
|
368
|
+
* to an eventKey link.
|
369
|
+
*
|
370
|
+
* A simple way to see when this gets utilized is:
|
371
|
+
* 1. go to paulirish.com, record a trace
|
372
|
+
* 2. say "What performance issues exist with my page?"
|
373
|
+
* 3. then say "images"
|
374
|
+
*
|
375
|
+
* TODO(cjamcl): reduce the reliance on this by making sure all URL references
|
376
|
+
* (such as the insight formatters) add the "eventKey" as a suffix, just like all
|
377
|
+
* other events.
|
378
|
+
*/
|
379
|
+
#parseForKnownUrls(response: string): string {
|
380
|
+
const focus = this.context?.getItem();
|
381
|
+
if (!focus) {
|
382
|
+
return response;
|
383
|
+
}
|
384
|
+
|
385
|
+
// Regex with two main parts, separated by | (OR):
|
386
|
+
// 1. (\[(.*?)\]\((.*?)\)): Captures a full markdown link.
|
387
|
+
// - Group 1: The whole link, e.g., "[text](url)"
|
388
|
+
// - Group 2: The link text, e.g., "text"
|
389
|
+
// - Group 3: The link destination, e.g., "url"
|
390
|
+
// 2. (https?:\/\/[^\s<>()]+): Captures a standalone URL.
|
391
|
+
// - Group 4: The standalone URL, e.g., "https://google.com"
|
392
|
+
const urlRegex = /(\[(.*?)\]\((.*?)\))|(https?:\/\/[^\s<>()]+)/g;
|
393
|
+
|
394
|
+
return response.replace(urlRegex, (match, markdownLink, linkText, linkDest, standaloneUrlText) => {
|
395
|
+
if (markdownLink) {
|
396
|
+
if (linkDest.startsWith('#')) {
|
397
|
+
return match;
|
398
|
+
}
|
399
|
+
}
|
400
|
+
|
401
|
+
const urlText = linkDest ?? standaloneUrlText;
|
402
|
+
if (!urlText) {
|
403
|
+
return match;
|
404
|
+
}
|
405
|
+
|
406
|
+
const request =
|
407
|
+
focus.data.parsedTrace.data.NetworkRequests.byTime.find(request => request.args.data.url === urlText);
|
408
|
+
if (!request) {
|
409
|
+
return match;
|
410
|
+
}
|
411
|
+
|
412
|
+
const eventKey = focus.eventsSerializer.keyForEvent(request);
|
413
|
+
if (!eventKey) {
|
414
|
+
return match;
|
415
|
+
}
|
416
|
+
|
417
|
+
return `[${urlText}](#${eventKey})`;
|
418
|
+
});
|
419
|
+
}
|
420
|
+
|
421
|
+
#parseMarkdown(response: string): string {
|
364
422
|
/**
|
365
423
|
* Sometimes the LLM responds with code chunks that wrap a text based markdown response.
|
366
424
|
* If this happens, we want to remove those before continuing.
|
367
425
|
* See b/405054694 for more details.
|
368
426
|
*/
|
369
|
-
const trimmed = response.trim();
|
370
427
|
const FIVE_BACKTICKS = '`````';
|
371
|
-
if (
|
372
|
-
|
373
|
-
// newlines that are at the very start or end.
|
374
|
-
const stripped = trimmed.slice(FIVE_BACKTICKS.length, -FIVE_BACKTICKS.length);
|
375
|
-
return super.parseTextResponse(stripped);
|
428
|
+
if (response.startsWith(FIVE_BACKTICKS) && response.endsWith(FIVE_BACKTICKS)) {
|
429
|
+
return response.slice(FIVE_BACKTICKS.length, -FIVE_BACKTICKS.length);
|
376
430
|
}
|
377
|
-
|
431
|
+
|
432
|
+
return response;
|
433
|
+
}
|
434
|
+
|
435
|
+
override parseTextResponse(response: string): ParsedResponse {
|
436
|
+
const parsedResponse = super.parseTextResponse(response);
|
437
|
+
parsedResponse.answer = this.#parseForKnownUrls(parsedResponse.answer);
|
438
|
+
parsedResponse.answer = this.#parseMarkdown(parsedResponse.answer);
|
439
|
+
return parsedResponse;
|
378
440
|
}
|
379
441
|
|
380
442
|
override async enhanceQuery(query: string, context: PerformanceTraceContext|null): Promise<string> {
|
@@ -426,15 +488,15 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
426
488
|
}
|
427
489
|
|
428
490
|
override async * run(initialQuery: string, options: {
|
429
|
-
selected:
|
491
|
+
selected: PerformanceTraceContext|null,
|
430
492
|
signal?: AbortSignal,
|
431
493
|
}): AsyncGenerator<ResponseData, void, void> {
|
432
494
|
const focus = options.selected?.getItem();
|
433
495
|
|
434
496
|
// Clear any previous facts in case the user changed the active context.
|
435
497
|
this.clearFacts();
|
436
|
-
if (focus) {
|
437
|
-
this.#addFacts(
|
498
|
+
if (options.selected && focus) {
|
499
|
+
this.#addFacts(options.selected);
|
438
500
|
}
|
439
501
|
|
440
502
|
return yield* super.run(initialQuery, options);
|
@@ -518,12 +580,18 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
518
580
|
});
|
519
581
|
}
|
520
582
|
|
521
|
-
#addFacts(
|
583
|
+
#addFacts(context: PerformanceTraceContext): void {
|
584
|
+
const focus = context.getItem();
|
585
|
+
|
586
|
+
if (!context.external) {
|
587
|
+
this.addFact(this.#notExternalExtraPreambleFact);
|
588
|
+
}
|
589
|
+
|
522
590
|
this.addFact(this.#callFrameDataDescriptionFact);
|
523
591
|
this.addFact(this.#networkDataDescriptionFact);
|
524
592
|
|
525
593
|
if (!this.#traceFacts.length) {
|
526
|
-
this.#formatter = new PerformanceTraceFormatter(focus
|
594
|
+
this.#formatter = new PerformanceTraceFormatter(focus);
|
527
595
|
this.#createFactForTraceSummary();
|
528
596
|
this.#createFactForCriticalRequests();
|
529
597
|
this.#createFactForMainThreadBottomUpSummary();
|
@@ -585,7 +653,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
585
653
|
return {error: 'No insight available'};
|
586
654
|
}
|
587
655
|
|
588
|
-
const details = new PerformanceInsightFormatter(
|
656
|
+
const details = new PerformanceInsightFormatter(focus, insight).formatInsight();
|
589
657
|
|
590
658
|
const key = `getInsightDetails('${params.insightName}')`;
|
591
659
|
this.#cacheFunctionResult(focus, key, details);
|
@@ -613,7 +681,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
613
681
|
},
|
614
682
|
handler: async params => {
|
615
683
|
debugLog('Function call: getEventByKey', params);
|
616
|
-
const event =
|
684
|
+
const event = focus.lookupEvent(params.eventKey as Trace.Types.File.SerializableKey);
|
617
685
|
if (!event) {
|
618
686
|
return {error: 'Invalid eventKey'};
|
619
687
|
}
|
@@ -772,7 +840,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
772
840
|
},
|
773
841
|
},
|
774
842
|
displayInfoFromArgs: args => {
|
775
|
-
return {title: lockedString('Looking at call tree…'), action: `getDetailedCallTree(${args.eventKey})`};
|
843
|
+
return {title: lockedString('Looking at call tree…'), action: `getDetailedCallTree('${args.eventKey}')`};
|
776
844
|
},
|
777
845
|
handler: async args => {
|
778
846
|
debugLog('Function call: getDetailedCallTree');
|
@@ -781,7 +849,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
781
849
|
throw new Error('missing formatter');
|
782
850
|
}
|
783
851
|
|
784
|
-
const event =
|
852
|
+
const event = focus.lookupEvent(args.eventKey as Trace.Types.File.SerializableKey);
|
785
853
|
if (!event) {
|
786
854
|
return {error: 'Invalid eventKey'};
|
787
855
|
}
|
@@ -816,7 +884,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
816
884
|
},
|
817
885
|
},
|
818
886
|
displayInfoFromArgs: args => {
|
819
|
-
return {title: lockedString('Looking at resource content…'), action: `getResourceContent(${args.url})`};
|
887
|
+
return {title: lockedString('Looking at resource content…'), action: `getResourceContent('${args.url}')`};
|
820
888
|
},
|
821
889
|
handler: async args => {
|
822
890
|
debugLog('Function call: getResourceContent');
|
@@ -829,14 +897,14 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
829
897
|
}
|
830
898
|
}
|
831
899
|
|
832
|
-
const content = resource.
|
833
|
-
if (
|
834
|
-
return {error:
|
900
|
+
const content = await resource.requestContentData();
|
901
|
+
if ('error' in content) {
|
902
|
+
return {error: `Could not get resource content: ${content.error}`};
|
835
903
|
}
|
836
904
|
|
837
905
|
const key = `getResourceContent(${args.url})`;
|
838
|
-
this.#cacheFunctionResult(focus, key, content);
|
839
|
-
return {result: {content}};
|
906
|
+
this.#cacheFunctionResult(focus, key, content.text);
|
907
|
+
return {result: {content: content.text}};
|
840
908
|
},
|
841
909
|
|
842
910
|
});
|
@@ -863,7 +931,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
863
931
|
},
|
864
932
|
handler: async params => {
|
865
933
|
debugLog('Function call: selectEventByKey', params);
|
866
|
-
const event =
|
934
|
+
const event = focus.lookupEvent(params.eventKey as Trace.Types.File.SerializableKey);
|
867
935
|
if (!event) {
|
868
936
|
return {error: 'Invalid eventKey'};
|
869
937
|
}
|
@@ -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
|
|