chrome-devtools-frontend 1.0.1515988 → 1.0.1518653
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/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/front_end/core/common/README.md +13 -12
- package/front_end/core/host/GdpClient.ts +16 -1
- package/front_end/core/host/UserMetrics.ts +8 -2
- package/front_end/core/root/Runtime.ts +13 -0
- package/front_end/core/sdk/CSSMatchedStyles.ts +5 -1
- package/front_end/entrypoints/main/MainImpl.ts +6 -3
- package/front_end/generated/InspectorBackendCommands.js +10 -7
- package/front_end/generated/SupportedCSSProperties.js +21 -7
- 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/PerformanceAgent.ts +170 -54
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +14 -181
- package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +13 -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 +41 -19
- package/front_end/models/badges/Badge.ts +8 -3
- package/front_end/models/badges/CodeWhispererBadge.ts +2 -4
- package/front_end/models/badges/StarterBadge.ts +2 -2
- package/front_end/models/badges/UserBadges.ts +59 -6
- package/front_end/models/formatter/FormatterWorkerPool.ts +3 -3
- package/front_end/models/javascript_metadata/NativeFunctions.js +1 -1
- package/front_end/models/trace/README.md +28 -1
- package/front_end/models/trace/handlers/UserTimingsHandler.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 +16 -2
- package/front_end/panels/ai_assistance/components/ChatView.ts +9 -10
- package/front_end/panels/ai_assistance/components/PerformanceAgentMarkdownRenderer.ts +42 -0
- 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 +67 -15
- package/front_end/panels/common/GdpSignUpDialog.ts +18 -9
- package/front_end/panels/console/ConsolePrompt.ts +1 -1
- package/front_end/panels/console/ConsoleView.ts +6 -2
- package/front_end/panels/elements/ComputedStyleWidget.ts +1 -2
- 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/LayoutPane.ts +1 -1
- package/front_end/panels/elements/StylePropertyTreeElement.ts +21 -6
- package/front_end/panels/media/TickingFlameChart.ts +1 -1
- package/front_end/panels/network/NetworkLogView.ts +5 -1
- package/front_end/panels/profiler/HeapSnapshotView.ts +34 -19
- package/front_end/panels/search/SearchResultsPane.ts +126 -145
- package/front_end/panels/search/SearchView.ts +43 -59
- package/front_end/panels/settings/components/SyncSection.ts +16 -8
- package/front_end/panels/sources/AiCodeCompletionPlugin.ts +6 -1
- package/front_end/panels/sources/OutlineQuickOpen.ts +3 -1
- package/front_end/panels/sources/SourcesPanel.ts +3 -0
- 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 +18 -12
- 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/LiveMetricsView.ts +2 -2
- 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/cdp/Accessibility.js +0 -20
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Accessibility.js.map +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/revisions.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +1 -1
- 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/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +2 -23
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js +0 -20
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Accessibility.js.map +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/revisions.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/package.json +1 -1
- package/front_end/third_party/puppeteer/package/src/cdp/Accessibility.ts +1 -21
- package/front_end/third_party/puppeteer/package/src/generated/version.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/revisions.ts +1 -1
- package/front_end/ui/components/text_editor/config.ts +36 -8
- package/front_end/ui/components/tooltips/Tooltip.ts +71 -34
- 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 +16 -2
- 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/visual_logging/KnownContextValues.ts +24 -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 {
|
@@ -15,7 +15,6 @@ import * as Trace from '../../trace/trace.js';
|
|
15
15
|
import {ConversationType} from '../AiHistoryStorage.js';
|
16
16
|
import {
|
17
17
|
PerformanceInsightFormatter,
|
18
|
-
TraceEventFormatter,
|
19
18
|
} from '../data_formatters/PerformanceInsightFormatter.js';
|
20
19
|
import {PerformanceTraceFormatter} from '../data_formatters/PerformanceTraceFormatter.js';
|
21
20
|
import {debugLog} from '../debug.js';
|
@@ -62,7 +61,7 @@ const lockedString = i18n.i18n.lockedString;
|
|
62
61
|
*
|
63
62
|
* Check token length in https://aistudio.google.com/
|
64
63
|
*/
|
65
|
-
const
|
64
|
+
const preamble = `You are an assistant, expert in web performance and highly skilled with Chrome DevTools.
|
66
65
|
|
67
66
|
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
67
|
|
@@ -129,6 +128,16 @@ Adhere to the following critical requirements:
|
|
129
128
|
- Do not provide answers on non-web-development topics, such as legal, financial, medical, or personal advice.
|
130
129
|
`;
|
131
130
|
|
131
|
+
const extraPreambleWhenNotExternal = `Additional notes:
|
132
|
+
|
133
|
+
When referring to a trace event that has a corresponding \`eventKey\`, annotate your output using markdown link syntax. For example:
|
134
|
+
- When referring to an event that is a long task: [Long task](#r-123)
|
135
|
+
- When referring to a URL for which you know the eventKey of: [https://www.example.com](#s-1827)
|
136
|
+
- Never show the eventKey (like "eventKey: s-1852"); instead, use a markdown link as described above.
|
137
|
+
|
138
|
+
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.
|
139
|
+
`;
|
140
|
+
|
132
141
|
const callFrameDataFormatDescription = `Each call frame is presented in the following format:
|
133
142
|
|
134
143
|
'id;name;duration;selfTime;urlIndex;childRange;[S]'
|
@@ -159,8 +168,8 @@ enum ScorePriority {
|
|
159
168
|
}
|
160
169
|
|
161
170
|
export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
|
162
|
-
static
|
163
|
-
return new PerformanceTraceContext(AgentFocus.
|
171
|
+
static fromParsedTrace(parsedTrace: Trace.TraceModel.ParsedTrace): PerformanceTraceContext {
|
172
|
+
return new PerformanceTraceContext(AgentFocus.fromParsedTrace(parsedTrace));
|
164
173
|
}
|
165
174
|
|
166
175
|
static fromInsight(parsedTrace: Trace.TraceModel.ParsedTrace, insight: Trace.Insights.Types.InsightModel):
|
@@ -224,7 +233,7 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
|
|
224
233
|
}
|
225
234
|
|
226
235
|
if (data.insight) {
|
227
|
-
return new PerformanceInsightFormatter(
|
236
|
+
return new PerformanceInsightFormatter(this.#focus, data.insight).getSuggestions();
|
228
237
|
}
|
229
238
|
|
230
239
|
const suggestions: ConversationSuggestions =
|
@@ -247,6 +256,15 @@ export class PerformanceTraceContext extends ConversationContext<AgentFocus> {
|
|
247
256
|
if (cls && ModelHandlers.LayoutShifts.scoreClassificationForLayoutShift(cls.value) !== GOOD) {
|
248
257
|
suggestions.push({title: 'How can I improve CLS?', jslogContext: 'performance-default'});
|
249
258
|
}
|
259
|
+
|
260
|
+
// Add up to 3 suggestions from the top failing insights.
|
261
|
+
const top3FailingInsightSuggestions =
|
262
|
+
Object.values(data.insightSet.model)
|
263
|
+
.filter(model => model.state !== 'pass')
|
264
|
+
.map(model => new PerformanceInsightFormatter(this.#focus, model).getSuggestions().at(-1))
|
265
|
+
.filter(suggestion => !!suggestion)
|
266
|
+
.slice(0, 3);
|
267
|
+
suggestions.push(...top3FailingInsightSuggestions);
|
250
268
|
}
|
251
269
|
|
252
270
|
return suggestions;
|
@@ -263,8 +281,7 @@ const MAX_FUNCTION_RESULT_BYTE_LENGTH = 16384 * 4;
|
|
263
281
|
export class PerformanceAgent extends AiAgent<AgentFocus> {
|
264
282
|
#formatter: PerformanceTraceFormatter|null = null;
|
265
283
|
#lastInsightForEnhancedQuery: Trace.Insights.Types.InsightModel|undefined;
|
266
|
-
#
|
267
|
-
#lastFocusHandledForContextDetails: AgentFocus|null = null;
|
284
|
+
#hasShownAnalyzeTraceContext = false;
|
268
285
|
|
269
286
|
/**
|
270
287
|
* Cache of all function calls made by the agent. This allows us to include (as a
|
@@ -279,8 +296,12 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
279
296
|
*/
|
280
297
|
#functionCallCacheForFocus = new Map<AgentFocus, Record<string, Host.AidaClient.RequestFact>>();
|
281
298
|
|
299
|
+
#notExternalExtraPreambleFact: Host.AidaClient.RequestFact = {
|
300
|
+
text: extraPreambleWhenNotExternal,
|
301
|
+
metadata: {source: 'devtools', score: ScorePriority.CRITICAL}
|
302
|
+
};
|
282
303
|
#networkDataDescriptionFact: Host.AidaClient.RequestFact = {
|
283
|
-
text:
|
304
|
+
text: PerformanceTraceFormatter.networkDataFormatDescription,
|
284
305
|
metadata: {source: 'devtools', score: ScorePriority.CRITICAL}
|
285
306
|
};
|
286
307
|
#callFrameDataDescriptionFact: Host.AidaClient.RequestFact = {
|
@@ -290,7 +311,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
290
311
|
#traceFacts: Host.AidaClient.RequestFact[] = [];
|
291
312
|
|
292
313
|
get preamble(): string {
|
293
|
-
return
|
314
|
+
return preamble;
|
294
315
|
}
|
295
316
|
|
296
317
|
get clientFeature(): Host.AidaClient.ClientFeature {
|
@@ -313,36 +334,16 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
313
334
|
return ConversationType.PERFORMANCE;
|
314
335
|
}
|
315
336
|
|
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
337
|
async *
|
334
338
|
handleContextDetails(context: ConversationContext<AgentFocus>|null): AsyncGenerator<ContextResponse, void, void> {
|
335
339
|
if (!context) {
|
336
340
|
return;
|
337
341
|
}
|
338
342
|
|
339
|
-
|
340
|
-
if (this.#lastFocusHandledForContextDetails === focus) {
|
343
|
+
if (this.#hasShownAnalyzeTraceContext) {
|
341
344
|
return;
|
342
345
|
}
|
343
346
|
|
344
|
-
this.#lastFocusHandledForContextDetails = focus;
|
345
|
-
|
346
347
|
yield {
|
347
348
|
type: ResponseType.CONTEXT,
|
348
349
|
title: lockedString(UIStringsNotTranslated.analyzingTrace),
|
@@ -353,6 +354,8 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
353
354
|
},
|
354
355
|
],
|
355
356
|
};
|
357
|
+
|
358
|
+
this.#hasShownAnalyzeTraceContext = true;
|
356
359
|
}
|
357
360
|
|
358
361
|
#callTreeContextSet = new WeakSet();
|
@@ -361,21 +364,128 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
361
364
|
return response.length > MAX_FUNCTION_RESULT_BYTE_LENGTH;
|
362
365
|
}
|
363
366
|
|
364
|
-
|
367
|
+
/**
|
368
|
+
* Sometimes the model will output URLs as plaintext; or a markdown link
|
369
|
+
* where the link is the actual URL. This function transforms such output
|
370
|
+
* to an eventKey link.
|
371
|
+
*
|
372
|
+
* A simple way to see when this gets utilized is:
|
373
|
+
* 1. go to paulirish.com, record a trace
|
374
|
+
* 2. say "What performance issues exist with my page?"
|
375
|
+
* 3. then say "images"
|
376
|
+
*
|
377
|
+
* TODO(cjamcl): reduce the reliance on this by making sure all URL references
|
378
|
+
* (such as the insight formatters) add the "eventKey" as a suffix, just like all
|
379
|
+
* other events.
|
380
|
+
*/
|
381
|
+
#parseForKnownUrls(response: string): string {
|
382
|
+
const focus = this.context?.getItem();
|
383
|
+
if (!focus) {
|
384
|
+
return response;
|
385
|
+
}
|
386
|
+
|
387
|
+
// Regex with two main parts, separated by | (OR):
|
388
|
+
// 1. (\[(.*?)\]\((.*?)\)): Captures a full markdown link.
|
389
|
+
// - Group 1: The whole link, e.g., "[text](url)"
|
390
|
+
// - Group 2: The link text, e.g., "text"
|
391
|
+
// - Group 3: The link destination, e.g., "url"
|
392
|
+
// 2. (https?:\/\/[^\s<>()]+): Captures a standalone URL.
|
393
|
+
// - Group 4: The standalone URL, e.g., "https://google.com"
|
394
|
+
const urlRegex = /(\[(.*?)\]\((.*?)\))|(https?:\/\/[^\s<>()]+)/g;
|
395
|
+
|
396
|
+
return response.replace(urlRegex, (match, markdownLink, linkText, linkDest, standaloneUrlText) => {
|
397
|
+
if (markdownLink) {
|
398
|
+
if (linkDest.startsWith('#')) {
|
399
|
+
return match;
|
400
|
+
}
|
401
|
+
}
|
402
|
+
|
403
|
+
const urlText = linkDest ?? standaloneUrlText;
|
404
|
+
if (!urlText) {
|
405
|
+
return match;
|
406
|
+
}
|
407
|
+
|
408
|
+
const request =
|
409
|
+
focus.data.parsedTrace.data.NetworkRequests.byTime.find(request => request.args.data.url === urlText);
|
410
|
+
if (!request) {
|
411
|
+
return match;
|
412
|
+
}
|
413
|
+
|
414
|
+
const eventKey = focus.eventsSerializer.keyForEvent(request);
|
415
|
+
if (!eventKey) {
|
416
|
+
return match;
|
417
|
+
}
|
418
|
+
|
419
|
+
return `[${urlText}](#${eventKey})`;
|
420
|
+
});
|
421
|
+
}
|
422
|
+
|
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
|
+
#parseMarkdown(response: string): string {
|
365
471
|
/**
|
366
472
|
* Sometimes the LLM responds with code chunks that wrap a text based markdown response.
|
367
473
|
* If this happens, we want to remove those before continuing.
|
368
474
|
* See b/405054694 for more details.
|
369
475
|
*/
|
370
|
-
const trimmed = response.trim();
|
371
476
|
const FIVE_BACKTICKS = '`````';
|
372
|
-
if (
|
373
|
-
|
374
|
-
// newlines that are at the very start or end.
|
375
|
-
const stripped = trimmed.slice(FIVE_BACKTICKS.length, -FIVE_BACKTICKS.length);
|
376
|
-
return super.parseTextResponse(stripped);
|
477
|
+
if (response.startsWith(FIVE_BACKTICKS) && response.endsWith(FIVE_BACKTICKS)) {
|
478
|
+
return response.slice(FIVE_BACKTICKS.length, -FIVE_BACKTICKS.length);
|
377
479
|
}
|
378
|
-
|
480
|
+
|
481
|
+
return response;
|
482
|
+
}
|
483
|
+
|
484
|
+
override parseTextResponse(response: string): ParsedResponse {
|
485
|
+
response = response.trim();
|
486
|
+
response = this.#parseForKnownUrls(response);
|
487
|
+
response = this.#parseMarkdown(response);
|
488
|
+
return this.#parseSuggestions(response);
|
379
489
|
}
|
380
490
|
|
381
491
|
override async enhanceQuery(query: string, context: PerformanceTraceContext|null): Promise<string> {
|
@@ -427,15 +537,15 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
427
537
|
}
|
428
538
|
|
429
539
|
override async * run(initialQuery: string, options: {
|
430
|
-
selected:
|
540
|
+
selected: PerformanceTraceContext|null,
|
431
541
|
signal?: AbortSignal,
|
432
542
|
}): AsyncGenerator<ResponseData, void, void> {
|
433
543
|
const focus = options.selected?.getItem();
|
434
544
|
|
435
545
|
// Clear any previous facts in case the user changed the active context.
|
436
546
|
this.clearFacts();
|
437
|
-
if (focus) {
|
438
|
-
this.#addFacts(
|
547
|
+
if (options.selected && focus) {
|
548
|
+
this.#addFacts(options.selected);
|
439
549
|
}
|
440
550
|
|
441
551
|
return yield* super.run(initialQuery, options);
|
@@ -519,12 +629,18 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
519
629
|
});
|
520
630
|
}
|
521
631
|
|
522
|
-
#addFacts(
|
632
|
+
#addFacts(context: PerformanceTraceContext): void {
|
633
|
+
const focus = context.getItem();
|
634
|
+
|
635
|
+
if (!context.external) {
|
636
|
+
this.addFact(this.#notExternalExtraPreambleFact);
|
637
|
+
}
|
638
|
+
|
523
639
|
this.addFact(this.#callFrameDataDescriptionFact);
|
524
640
|
this.addFact(this.#networkDataDescriptionFact);
|
525
641
|
|
526
642
|
if (!this.#traceFacts.length) {
|
527
|
-
this.#formatter = new PerformanceTraceFormatter(focus
|
643
|
+
this.#formatter = new PerformanceTraceFormatter(focus);
|
528
644
|
this.#createFactForTraceSummary();
|
529
645
|
this.#createFactForCriticalRequests();
|
530
646
|
this.#createFactForMainThreadBottomUpSummary();
|
@@ -586,7 +702,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
586
702
|
return {error: 'No insight available'};
|
587
703
|
}
|
588
704
|
|
589
|
-
const details = new PerformanceInsightFormatter(
|
705
|
+
const details = new PerformanceInsightFormatter(focus, insight).formatInsight();
|
590
706
|
|
591
707
|
const key = `getInsightDetails('${params.insightName}')`;
|
592
708
|
this.#cacheFunctionResult(focus, key, details);
|
@@ -614,7 +730,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
614
730
|
},
|
615
731
|
handler: async params => {
|
616
732
|
debugLog('Function call: getEventByKey', params);
|
617
|
-
const event =
|
733
|
+
const event = focus.lookupEvent(params.eventKey as Trace.Types.File.SerializableKey);
|
618
734
|
if (!event) {
|
619
735
|
return {error: 'Invalid eventKey'};
|
620
736
|
}
|
@@ -773,7 +889,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
773
889
|
},
|
774
890
|
},
|
775
891
|
displayInfoFromArgs: args => {
|
776
|
-
return {title: lockedString('Looking at call tree…'), action: `getDetailedCallTree(${args.eventKey})`};
|
892
|
+
return {title: lockedString('Looking at call tree…'), action: `getDetailedCallTree('${args.eventKey}')`};
|
777
893
|
},
|
778
894
|
handler: async args => {
|
779
895
|
debugLog('Function call: getDetailedCallTree');
|
@@ -782,7 +898,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
782
898
|
throw new Error('missing formatter');
|
783
899
|
}
|
784
900
|
|
785
|
-
const event =
|
901
|
+
const event = focus.lookupEvent(args.eventKey as Trace.Types.File.SerializableKey);
|
786
902
|
if (!event) {
|
787
903
|
return {error: 'Invalid eventKey'};
|
788
904
|
}
|
@@ -817,7 +933,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
817
933
|
},
|
818
934
|
},
|
819
935
|
displayInfoFromArgs: args => {
|
820
|
-
return {title: lockedString('Looking at resource content…'), action: `getResourceContent(${args.url})`};
|
936
|
+
return {title: lockedString('Looking at resource content…'), action: `getResourceContent('${args.url}')`};
|
821
937
|
},
|
822
938
|
handler: async args => {
|
823
939
|
debugLog('Function call: getResourceContent');
|
@@ -830,14 +946,14 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
830
946
|
}
|
831
947
|
}
|
832
948
|
|
833
|
-
const content = resource.
|
834
|
-
if (
|
835
|
-
return {error:
|
949
|
+
const content = await resource.requestContentData();
|
950
|
+
if ('error' in content) {
|
951
|
+
return {error: `Could not get resource content: ${content.error}`};
|
836
952
|
}
|
837
953
|
|
838
954
|
const key = `getResourceContent(${args.url})`;
|
839
|
-
this.#cacheFunctionResult(focus, key, content);
|
840
|
-
return {result: {content}};
|
955
|
+
this.#cacheFunctionResult(focus, key, content.text);
|
956
|
+
return {result: {content: content.text}};
|
841
957
|
},
|
842
958
|
|
843
959
|
});
|
@@ -864,7 +980,7 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
864
980
|
},
|
865
981
|
handler: async params => {
|
866
982
|
debugLog('Function call: selectEventByKey', params);
|
867
|
-
const event =
|
983
|
+
const event = focus.lookupEvent(params.eventKey as Trace.Types.File.SerializableKey);
|
868
984
|
if (!event) {
|
869
985
|
return {error: 'Invalid eventKey'};
|
870
986
|
}
|