chrome-devtools-frontend 1.0.1645245 → 1.0.1646286
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/.agents/skills/devtools-source-maps/SKILL.md +124 -0
- package/docs/README.md +1 -0
- package/docs/using_source_maps.md +159 -0
- package/front_end/core/host/AidaClientTypes.ts +2 -0
- package/front_end/core/host/UserMetrics.ts +2 -1
- package/front_end/core/root/Runtime.ts +10 -0
- package/front_end/core/sdk/DebuggerModel.ts +7 -9
- package/front_end/core/sdk/NetworkRequest.ts +0 -24
- package/front_end/generated/InspectorBackendCommands.ts +2 -2
- package/front_end/generated/SupportedCSSProperties.js +75 -0
- package/front_end/generated/protocol.ts +0 -5
- package/front_end/models/ai_assistance/AiAgent2.ts +29 -2
- package/front_end/models/ai_assistance/AiConversation.ts +4 -2
- package/front_end/models/ai_assistance/AiOrigins.ts +63 -2
- package/front_end/models/ai_assistance/README.md +15 -4
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
- package/front_end/models/ai_assistance/agents/FileAgent.ts +9 -42
- package/front_end/models/ai_assistance/agents/NetworkAgent.snapshot.txt +2 -2
- package/front_end/models/ai_assistance/agents/NetworkAgent.ts +9 -133
- package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +10 -2
- package/front_end/models/ai_assistance/agents/README.md +7 -0
- package/front_end/models/ai_assistance/ai_assistance.ts +4 -0
- package/front_end/models/ai_assistance/contexts/FileContext.ts +45 -0
- package/front_end/models/ai_assistance/contexts/RequestContext.snapshot.txt +48 -0
- package/front_end/models/ai_assistance/contexts/RequestContext.ts +116 -0
- package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +2 -1
- package/front_end/models/ai_assistance/tools/README.md +1 -1
- package/front_end/models/trace/handlers/NetworkRequestsHandler.ts +15 -11
- package/front_end/models/web_mcp/WebMCPModel.ts +8 -48
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +13 -13
- package/front_end/panels/ai_assistance/components/ChatInput.ts +4 -4
- package/front_end/panels/application/ApplicationPanelSidebar.ts +25 -0
- package/front_end/panels/application/WebMCPView.ts +40 -70
- package/front_end/panels/application/components/AdsView.ts +31 -28
- package/front_end/panels/application/components/adsView.css +6 -0
- package/front_end/panels/common/ExtensionServer.ts +5 -0
- package/front_end/panels/profiler/IsolateSelector.ts +4 -2
- package/front_end/panels/profiler/ProfileLauncherView.ts +194 -126
- package/front_end/panels/profiler/ProfilesPanel.ts +1 -3
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
|
@@ -2,21 +2,14 @@
|
|
|
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 Common from '../../../core/common/common.js';
|
|
6
5
|
import * as Host from '../../../core/host/host.js';
|
|
7
|
-
import * as i18n from '../../../core/i18n/i18n.js';
|
|
8
|
-
import type * as Platform from '../../../core/platform/platform.js';
|
|
9
6
|
import * as Root from '../../../core/root/root.js';
|
|
10
7
|
import type * as SDK from '../../../core/sdk/sdk.js';
|
|
11
|
-
import type
|
|
12
|
-
import {extractContextOrigin} from '../AiOrigins.js';
|
|
13
|
-
import {NetworkRequestFormatter} from '../data_formatters/NetworkRequestFormatter.js';
|
|
8
|
+
import type {RequestContext} from '../contexts/RequestContext.js';
|
|
14
9
|
|
|
15
10
|
import {
|
|
16
11
|
AiAgent,
|
|
17
|
-
type ContextDetail,
|
|
18
12
|
type ContextResponse,
|
|
19
|
-
ConversationContext,
|
|
20
13
|
type RequestOptions,
|
|
21
14
|
ResponseType,
|
|
22
15
|
} from './AiAgent.js';
|
|
@@ -73,90 +66,6 @@ This request aims to retrieve a list of products matching the search query "lapt
|
|
|
73
66
|
`;
|
|
74
67
|
/* clang-format on */
|
|
75
68
|
|
|
76
|
-
/*
|
|
77
|
-
* Strings that don't need to be translated at this time.
|
|
78
|
-
*/
|
|
79
|
-
const UIStringsNotTranslate = {
|
|
80
|
-
/**
|
|
81
|
-
* @description Heading text for the block that shows the network request details.
|
|
82
|
-
*/
|
|
83
|
-
request: 'Request',
|
|
84
|
-
/**
|
|
85
|
-
* @description Heading text for the block that shows the network response details.
|
|
86
|
-
*/
|
|
87
|
-
response: 'Response',
|
|
88
|
-
/**
|
|
89
|
-
* @description Prefix text for request URL.
|
|
90
|
-
*/
|
|
91
|
-
requestUrl: 'Request URL',
|
|
92
|
-
/**
|
|
93
|
-
* @description Title text for request timing details.
|
|
94
|
-
*/
|
|
95
|
-
timing: 'Timing',
|
|
96
|
-
/**
|
|
97
|
-
* @description Title text for request initiator chain.
|
|
98
|
-
*/
|
|
99
|
-
requestInitiatorChain: 'Request initiator chain',
|
|
100
|
-
} as const;
|
|
101
|
-
|
|
102
|
-
const lockedString = i18n.i18n.lockedString;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Returns the origin for a network request in the AI context.
|
|
106
|
-
*
|
|
107
|
-
* To prevent cross-origin prompt injection attacks, HAR-imported requests
|
|
108
|
-
* are isolated from live pages. We assign them a virtual origin
|
|
109
|
-
* (`imported-har://${domain}`) so they do not share the origin of live pages
|
|
110
|
-
* (e.g., `https://${domain}`). This forces a conversation reset when transitioning
|
|
111
|
-
* between imported HAR data and live pages.
|
|
112
|
-
*/
|
|
113
|
-
export function getRequestContextOrigin(request: SDK.NetworkRequest.NetworkRequest): string {
|
|
114
|
-
const origin = extractContextOrigin(request.documentURL);
|
|
115
|
-
if (request.isImportedHar()) {
|
|
116
|
-
const parsed = Common.ParsedURL.ParsedURL.fromString(origin as Platform.DevToolsPath.UrlString);
|
|
117
|
-
return `imported-har://${parsed ? parsed.domain() : origin}`;
|
|
118
|
-
}
|
|
119
|
-
return origin;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export class RequestContext extends ConversationContext<SDK.NetworkRequest.NetworkRequest> {
|
|
123
|
-
#request: SDK.NetworkRequest.NetworkRequest;
|
|
124
|
-
#calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator;
|
|
125
|
-
|
|
126
|
-
constructor(
|
|
127
|
-
request: SDK.NetworkRequest.NetworkRequest, calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator) {
|
|
128
|
-
super();
|
|
129
|
-
this.#request = request;
|
|
130
|
-
this.#calculator = calculator;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Note: this is not the literal origin of the network request. This URL
|
|
135
|
-
* is used to determine when we should force the user to start a new AI
|
|
136
|
-
* conversation when the context changes. We allow a single AI conversation to
|
|
137
|
-
* inspect all network requests that were made for that given target URL.
|
|
138
|
-
*/
|
|
139
|
-
override getURL(): string {
|
|
140
|
-
return this.#request.documentURL;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
override getOrigin(): string {
|
|
144
|
-
return getRequestContextOrigin(this.#request);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
override getItem(): SDK.NetworkRequest.NetworkRequest {
|
|
148
|
-
return this.#request;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
get calculator(): NetworkTimeCalculator.NetworkTimeCalculator {
|
|
152
|
-
return this.#calculator;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
override getTitle(): string {
|
|
156
|
-
return this.#request.name();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
69
|
/**
|
|
161
70
|
* One agent instance handles one conversation. Create a new agent
|
|
162
71
|
* instance for a new conversation.
|
|
@@ -183,9 +92,14 @@ export class NetworkAgent extends AiAgent<SDK.NetworkRequest.NetworkRequest> {
|
|
|
183
92
|
return;
|
|
184
93
|
}
|
|
185
94
|
|
|
95
|
+
const details = await selectedNetworkRequest.getUserFacingDetails();
|
|
96
|
+
if (!details) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
186
100
|
yield {
|
|
187
101
|
type: ResponseType.CONTEXT,
|
|
188
|
-
details
|
|
102
|
+
details,
|
|
189
103
|
widgets: [{
|
|
190
104
|
name: 'NETWORK_REQUEST_GENERAL_HEADERS',
|
|
191
105
|
data: {
|
|
@@ -196,46 +110,8 @@ export class NetworkAgent extends AiAgent<SDK.NetworkRequest.NetworkRequest> {
|
|
|
196
110
|
}
|
|
197
111
|
|
|
198
112
|
override async enhanceQuery(query: string, selectedNetworkRequest: RequestContext|null): Promise<string> {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
await (new NetworkRequestFormatter(selectedNetworkRequest.getItem(), selectedNetworkRequest.calculator)
|
|
202
|
-
.formatNetworkRequest())}\n\n# User request\n\n` :
|
|
203
|
-
'';
|
|
113
|
+
const promptDetails = selectedNetworkRequest ? await selectedNetworkRequest.getPromptDetails() : null;
|
|
114
|
+
const networkEnchantmentQuery = promptDetails ? `${promptDetails}\n\n# User request\n\n` : '';
|
|
204
115
|
return `${networkEnchantmentQuery}${query}`;
|
|
205
116
|
}
|
|
206
117
|
}
|
|
207
|
-
|
|
208
|
-
async function createContextDetailsForNetworkAgent(
|
|
209
|
-
selectedNetworkRequest: RequestContext,
|
|
210
|
-
): Promise<[ContextDetail, ...ContextDetail[]]> {
|
|
211
|
-
const request = selectedNetworkRequest.getItem();
|
|
212
|
-
const formatter = new NetworkRequestFormatter(request, selectedNetworkRequest.calculator);
|
|
213
|
-
const requestContextDetail: ContextDetail = {
|
|
214
|
-
title: lockedString(UIStringsNotTranslate.request),
|
|
215
|
-
text: lockedString(UIStringsNotTranslate.requestUrl) + ': ' + request.url() + '\n\n' +
|
|
216
|
-
formatter.formatRequestHeaders(),
|
|
217
|
-
};
|
|
218
|
-
const responseBody = await formatter.formatResponseBody();
|
|
219
|
-
const responseBodyString = responseBody ? `\n\n${responseBody}` : '';
|
|
220
|
-
|
|
221
|
-
const responseContextDetail: ContextDetail = {
|
|
222
|
-
title: lockedString(UIStringsNotTranslate.response),
|
|
223
|
-
text: formatter.formatResponseHeaders() + responseBodyString +
|
|
224
|
-
`\n\n${formatter.formatStatus()}${formatter.formatFailureReasons()}`,
|
|
225
|
-
};
|
|
226
|
-
const timingContextDetail: ContextDetail = {
|
|
227
|
-
title: lockedString(UIStringsNotTranslate.timing),
|
|
228
|
-
text: formatter.formatNetworkRequestTiming(),
|
|
229
|
-
};
|
|
230
|
-
const initiatorChainContextDetail: ContextDetail = {
|
|
231
|
-
title: lockedString(UIStringsNotTranslate.requestInitiatorChain),
|
|
232
|
-
text: formatter.formatRequestInitiatorChain(),
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
return [
|
|
236
|
-
requestContextDetail,
|
|
237
|
-
responseContextDetail,
|
|
238
|
-
timingContextDetail,
|
|
239
|
-
initiatorChainContextDetail,
|
|
240
|
-
];
|
|
241
|
-
}
|
|
@@ -15,7 +15,7 @@ import * as Logs from '../../logs/logs.js';
|
|
|
15
15
|
import * as SourceMapScopes from '../../source_map_scopes/source_map_scopes.js';
|
|
16
16
|
import * as TextUtils from '../../text_utils/text_utils.js';
|
|
17
17
|
import * as Trace from '../../trace/trace.js';
|
|
18
|
-
import {extractContextOrigin} from '../AiOrigins.js';
|
|
18
|
+
import {canResourceContentsBeReadForTrace, extractContextOrigin} from '../AiOrigins.js';
|
|
19
19
|
import {sanitizeHeaders} from '../data_formatters/NetworkRequestFormatter.js';
|
|
20
20
|
import {
|
|
21
21
|
PerformanceInsightFormatter,
|
|
@@ -1472,8 +1472,16 @@ export class PerformanceAgent extends AiAgent<AgentFocus> {
|
|
|
1472
1472
|
},
|
|
1473
1473
|
handler: async args => {
|
|
1474
1474
|
debugLog('Function call: getResourceContent');
|
|
1475
|
+
if (!isFresh) {
|
|
1476
|
+
return {error: 'Cannot use this tool on an imported file.'};
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
const url = args.url;
|
|
1480
|
+
const allowedOrigin = context.getOrigin();
|
|
1481
|
+
if (!canResourceContentsBeReadForTrace(url, allowedOrigin)) {
|
|
1482
|
+
return {error: 'Resource not found'};
|
|
1483
|
+
}
|
|
1475
1484
|
|
|
1476
|
-
const url = args.url as Platform.DevToolsPath.UrlString;
|
|
1477
1485
|
let content: string|undefined;
|
|
1478
1486
|
|
|
1479
1487
|
// First check parsedTrace.data.Scripts.
|
|
@@ -31,6 +31,13 @@ Example from `PerformanceAgent`:
|
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
### Origin Validation (`isOriginAllowed`)
|
|
35
|
+
|
|
36
|
+
Before running an execution loop, the system validates the context's origin:
|
|
37
|
+
* **Opaque Origin Rejection**: Opaque origins are rejected immediately, blocking conversations on untrusted/opaque contexts (like `about:blank`, `data:` URLs, or opaque blob URLs).
|
|
38
|
+
* **Origin Lock Integrity**: If the conversation is already locked to an origin, the new context's origin must be equivalent (as defined by `areOriginsEquivalent()`) to the locked origin.
|
|
39
|
+
* **Safety Termination**: If validation fails, a cross-origin error is yielded and execution stops.
|
|
40
|
+
|
|
34
41
|
## Performance Agent
|
|
35
42
|
|
|
36
43
|
The `PerformanceAgent` analyzes performance traces. This documentation details the specific data provided to the agent and the data it can retrieve via functions.
|
|
@@ -23,6 +23,8 @@ import * as AiUtils from './AiUtils.js';
|
|
|
23
23
|
import * as BuiltInAi from './BuiltInAi.js';
|
|
24
24
|
import * as ChangeManager from './ChangeManager.js';
|
|
25
25
|
import * as DOMNodeContext from './contexts/DOMNodeContext.js';
|
|
26
|
+
import * as FileContext from './contexts/FileContext.js';
|
|
27
|
+
import * as RequestContext from './contexts/RequestContext.js';
|
|
26
28
|
import * as ConversationSummary from './ConversationSummary.js';
|
|
27
29
|
import * as FileFormatter from './data_formatters/FileFormatter.js';
|
|
28
30
|
import * as LighthouseFormatter from './data_formatters/LighthouseFormatter.js';
|
|
@@ -66,6 +68,7 @@ export {
|
|
|
66
68
|
ExecuteJavaScript,
|
|
67
69
|
ExtensionScope,
|
|
68
70
|
FileAgent,
|
|
71
|
+
FileContext,
|
|
69
72
|
FileFormatter,
|
|
70
73
|
GetStyles,
|
|
71
74
|
GreenDevAgent,
|
|
@@ -80,6 +83,7 @@ export {
|
|
|
80
83
|
PerformanceAnnotations,
|
|
81
84
|
PerformanceInsightFormatter,
|
|
82
85
|
PerformanceTraceFormatter,
|
|
86
|
+
RequestContext,
|
|
83
87
|
StorageAgent,
|
|
84
88
|
StorageItem,
|
|
85
89
|
StylingAgent,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import type * as Workspace from '../../workspace/workspace.js';
|
|
6
|
+
import {type ContextDetail, ConversationContext} from '../agents/AiAgent.js';
|
|
7
|
+
import {FileFormatter} from '../data_formatters/FileFormatter.js';
|
|
8
|
+
|
|
9
|
+
export class FileContext extends ConversationContext<Workspace.UISourceCode.UISourceCode> {
|
|
10
|
+
#file: Workspace.UISourceCode.UISourceCode;
|
|
11
|
+
|
|
12
|
+
constructor(file: Workspace.UISourceCode.UISourceCode) {
|
|
13
|
+
super();
|
|
14
|
+
this.#file = file;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override getURL(): string {
|
|
18
|
+
return this.#file.url();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override getItem(): Workspace.UISourceCode.UISourceCode {
|
|
22
|
+
return this.#file;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override getTitle(): string {
|
|
26
|
+
return this.#file.displayName();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override async getPromptDetails(): Promise<string|null> {
|
|
30
|
+
return `# Selected file\n${new FileFormatter(this.#file).formatFile()}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override async getUserFacingDetails(): Promise<[ContextDetail, ...ContextDetail[]]|null> {
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
title: 'Selected file',
|
|
37
|
+
text: new FileFormatter(this.#file).formatFile(),
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override async refresh(): Promise<void> {
|
|
43
|
+
await this.#file.requestContentData();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Title: RequestContext getPromptDetails describes the network request correctly
|
|
2
|
+
Content:
|
|
3
|
+
# Selected network request
|
|
4
|
+
Request: https://www.example.com/api/users
|
|
5
|
+
|
|
6
|
+
Request headers:
|
|
7
|
+
Accept: application/json
|
|
8
|
+
|
|
9
|
+
Response headers:
|
|
10
|
+
Content-Type: application/json
|
|
11
|
+
|
|
12
|
+
Response body:
|
|
13
|
+
{}
|
|
14
|
+
|
|
15
|
+
Response status: 200
|
|
16
|
+
Network request status: pending
|
|
17
|
+
|
|
18
|
+
Request timing:
|
|
19
|
+
Queued at (timestamp): 0 s
|
|
20
|
+
Started at (timestamp): 0 s
|
|
21
|
+
Connection start (stalled) (duration): -
|
|
22
|
+
Duration (duration): -
|
|
23
|
+
|
|
24
|
+
Request initiator chain:
|
|
25
|
+
- URL: https://www.example.com/api/users
|
|
26
|
+
=== end content
|
|
27
|
+
|
|
28
|
+
Title: RequestContext getUserFacingDetails returns details correctly
|
|
29
|
+
Content:
|
|
30
|
+
[
|
|
31
|
+
{
|
|
32
|
+
"title": "Request",
|
|
33
|
+
"text": "Request URL: https://www.example.com/api/users\n\nRequest headers:\nAccept: application/json"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"title": "Response",
|
|
37
|
+
"text": "Response headers:\nContent-Type: application/json\n\nResponse body:\n{}\n\nResponse status: 200\nNetwork request status: pending\n"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"title": "Timing",
|
|
41
|
+
"text": "Queued at (timestamp): 0 s\nStarted at (timestamp): 0 s\nConnection start (stalled) (duration): -\nDuration (duration): -"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"title": "Request initiator chain",
|
|
45
|
+
"text": "- URL: https://www.example.com/api/users"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
=== end content
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
import * as Common from '../../../core/common/common.js';
|
|
6
|
+
import * as i18n from '../../../core/i18n/i18n.js';
|
|
7
|
+
import type * as Platform from '../../../core/platform/platform.js';
|
|
8
|
+
import type * as SDK from '../../../core/sdk/sdk.js';
|
|
9
|
+
import type * as NetworkTimeCalculator from '../../network_time_calculator/network_time_calculator.js';
|
|
10
|
+
import {
|
|
11
|
+
type ContextDetail,
|
|
12
|
+
ConversationContext,
|
|
13
|
+
} from '../agents/AiAgent.js';
|
|
14
|
+
import {extractContextOrigin} from '../AiOrigins.js';
|
|
15
|
+
import {NetworkRequestFormatter} from '../data_formatters/NetworkRequestFormatter.js';
|
|
16
|
+
|
|
17
|
+
const UIStringsNotTranslate = {
|
|
18
|
+
request: 'Request',
|
|
19
|
+
response: 'Response',
|
|
20
|
+
requestUrl: 'Request URL',
|
|
21
|
+
timing: 'Timing',
|
|
22
|
+
requestInitiatorChain: 'Request initiator chain',
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
const lockedString = i18n.i18n.lockedString;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns the origin for a network request in the AI context.
|
|
29
|
+
*
|
|
30
|
+
* To prevent cross-origin prompt injection attacks, HAR-imported requests
|
|
31
|
+
* are isolated from live pages. We assign them a virtual origin
|
|
32
|
+
* (`imported-har://${domain}`) so they do not share the origin of live pages
|
|
33
|
+
* (e.g., `https://${domain}`). This forces a conversation reset when transitioning
|
|
34
|
+
* between imported HAR data and live pages.
|
|
35
|
+
*/
|
|
36
|
+
export function getRequestContextOrigin(request: SDK.NetworkRequest.NetworkRequest): string {
|
|
37
|
+
const origin = extractContextOrigin(request.documentURL);
|
|
38
|
+
if (request.isImportedHar()) {
|
|
39
|
+
const parsed = Common.ParsedURL.ParsedURL.fromString(origin as Platform.DevToolsPath.UrlString);
|
|
40
|
+
return `imported-har://${parsed ? parsed.domain() : origin}`;
|
|
41
|
+
}
|
|
42
|
+
return origin;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class RequestContext extends ConversationContext<SDK.NetworkRequest.NetworkRequest> {
|
|
46
|
+
#request: SDK.NetworkRequest.NetworkRequest;
|
|
47
|
+
#calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator;
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
request: SDK.NetworkRequest.NetworkRequest,
|
|
51
|
+
calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator,
|
|
52
|
+
) {
|
|
53
|
+
super();
|
|
54
|
+
this.#request = request;
|
|
55
|
+
this.#calculator = calculator;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Note: this is not the literal origin of the network request. This URL
|
|
60
|
+
* is used to determine when we should force the user to start a new AI
|
|
61
|
+
* conversation when the context changes. We allow a single AI conversation to
|
|
62
|
+
* inspect all network requests that were made for that given target URL.
|
|
63
|
+
*/
|
|
64
|
+
override getURL(): string {
|
|
65
|
+
return this.#request.documentURL;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override getOrigin(): string {
|
|
69
|
+
return getRequestContextOrigin(this.#request);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
override getItem(): SDK.NetworkRequest.NetworkRequest {
|
|
73
|
+
return this.#request;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override getTitle(): string {
|
|
77
|
+
return this.#request.name();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override async getPromptDetails(): Promise<string|null> {
|
|
81
|
+
const formatter = new NetworkRequestFormatter(this.#request, this.#calculator);
|
|
82
|
+
return `# Selected network request\n${await formatter.formatNetworkRequest()}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override async getUserFacingDetails(): Promise<[ContextDetail, ...ContextDetail[]]|null> {
|
|
86
|
+
const formatter = new NetworkRequestFormatter(this.#request, this.#calculator);
|
|
87
|
+
const requestContextDetail: ContextDetail = {
|
|
88
|
+
title: lockedString(UIStringsNotTranslate.request),
|
|
89
|
+
text: lockedString(UIStringsNotTranslate.requestUrl) + ': ' + this.#request.url() + '\n\n' +
|
|
90
|
+
formatter.formatRequestHeaders(),
|
|
91
|
+
};
|
|
92
|
+
const responseBody = await formatter.formatResponseBody();
|
|
93
|
+
const responseBodyString = responseBody ? `\n\n${responseBody}` : '';
|
|
94
|
+
|
|
95
|
+
const responseContextDetail: ContextDetail = {
|
|
96
|
+
title: lockedString(UIStringsNotTranslate.response),
|
|
97
|
+
text: formatter.formatResponseHeaders() + responseBodyString +
|
|
98
|
+
`\n\n${formatter.formatStatus()}${formatter.formatFailureReasons()}`,
|
|
99
|
+
};
|
|
100
|
+
const timingContextDetail: ContextDetail = {
|
|
101
|
+
title: lockedString(UIStringsNotTranslate.timing),
|
|
102
|
+
text: formatter.formatNetworkRequestTiming(),
|
|
103
|
+
};
|
|
104
|
+
const initiatorChainContextDetail: ContextDetail = {
|
|
105
|
+
title: lockedString(UIStringsNotTranslate.requestInitiatorChain),
|
|
106
|
+
text: formatter.formatRequestInitiatorChain(),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return [
|
|
110
|
+
requestContextDetail,
|
|
111
|
+
responseContextDetail,
|
|
112
|
+
timingContextDetail,
|
|
113
|
+
initiatorChainContextDetail,
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -92,7 +92,8 @@ export class NetworkRequestFormatter {
|
|
|
92
92
|
}): string {
|
|
93
93
|
let responseStatus = '';
|
|
94
94
|
if (status.statusCode) {
|
|
95
|
-
|
|
95
|
+
const statusText = status.statusText ? ` ${status.statusText}` : '';
|
|
96
|
+
responseStatus = `Response status: ${status.statusCode}${statusText}\n`;
|
|
96
97
|
}
|
|
97
98
|
const flags = [];
|
|
98
99
|
flags.push(status.finished ? 'finished' : 'pending');
|
|
@@ -17,7 +17,7 @@ Instead of passing a monolithic "grab-bag" context object to all tool handlers,
|
|
|
17
17
|
- `PageExecutionCapability`: For tools executing JavaScript code on the inspected page.
|
|
18
18
|
- `StyleMutationCapability`: For tools managing and applying style mutations via a `ChangeManager`.
|
|
19
19
|
- `TargetCapability`: For tools requiring access to the page's current SDK `Target`.
|
|
20
|
-
- `OriginLockCapability`: For tools enforcing cross-origin security via origin locks.
|
|
20
|
+
- `OriginLockCapability`: For tools enforcing cross-origin security via origin locks. Tools that fetch resources or state from the inspected page (such as styling or source code) must use this capability to verify that the target resource matches the conversation's established origin. This prevents the LLM from executing tools against out-of-origin elements or documents.
|
|
21
21
|
|
|
22
22
|
### Unified Context
|
|
23
23
|
|
|
@@ -213,6 +213,14 @@ export function handleEvent(event: Types.Events.Event): void {
|
|
|
213
213
|
|
|
214
214
|
export async function finalize(): Promise<void> {
|
|
215
215
|
const {rendererProcessesByFrame} = metaHandlerData();
|
|
216
|
+
|
|
217
|
+
const allowedProtocols = [
|
|
218
|
+
'blob:',
|
|
219
|
+
'file:',
|
|
220
|
+
'filesystem:',
|
|
221
|
+
'http:',
|
|
222
|
+
'https:',
|
|
223
|
+
];
|
|
216
224
|
for (const [requestId, request] of requestMap.entries()) {
|
|
217
225
|
// If we have an incomplete set of events here, we choose to drop the network
|
|
218
226
|
// request rather than attempt to synthesize the missing data.
|
|
@@ -248,7 +256,7 @@ export async function finalize(): Promise<void> {
|
|
|
248
256
|
}
|
|
249
257
|
|
|
250
258
|
redirects.push({
|
|
251
|
-
url: sendRequest.args.data.url,
|
|
259
|
+
url: allowedProtocols.some(p => sendRequest.args.data.url.startsWith(p)) ? sendRequest.args.data.url : '',
|
|
252
260
|
priority: sendRequest.args.data.priority,
|
|
253
261
|
requestMethod: sendRequest.args.data.requestMethod,
|
|
254
262
|
ts,
|
|
@@ -360,15 +368,7 @@ export async function finalize(): Promise<void> {
|
|
|
360
368
|
}
|
|
361
369
|
}
|
|
362
370
|
|
|
363
|
-
|
|
364
|
-
const allowedProtocols = [
|
|
365
|
-
'blob:',
|
|
366
|
-
'file:',
|
|
367
|
-
'filesystem:',
|
|
368
|
-
'http:',
|
|
369
|
-
'https:',
|
|
370
|
-
];
|
|
371
|
-
if (!allowedProtocols.some(p => firstSendRequest.args.data.url.startsWith(p))) {
|
|
371
|
+
if (!allowedProtocols.some(p => finalSendRequest.args.data.url.startsWith(p))) {
|
|
372
372
|
continue;
|
|
373
373
|
}
|
|
374
374
|
|
|
@@ -572,7 +572,11 @@ export async function finalize(): Promise<void> {
|
|
|
572
572
|
responseHeaders: request.receiveResponse?.args.data.headers ?? null,
|
|
573
573
|
fetchPriorityHint: finalSendRequest.args.data.fetchPriorityHint ?? 'auto',
|
|
574
574
|
initiator: finalSendRequest.args.data.initiator,
|
|
575
|
-
stackTrace: finalSendRequest.args.data.stackTrace
|
|
575
|
+
stackTrace: finalSendRequest.args.data.stackTrace?.map(
|
|
576
|
+
frame => ({
|
|
577
|
+
...frame,
|
|
578
|
+
url: allowedProtocols.some(p => frame.url.startsWith(p)) ? frame.url : '',
|
|
579
|
+
})),
|
|
576
580
|
timing,
|
|
577
581
|
lrServerResponseTime,
|
|
578
582
|
url,
|
|
@@ -9,7 +9,7 @@ import * as SDK from '../../core/sdk/sdk.js';
|
|
|
9
9
|
import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
|
|
10
10
|
import type * as Protocol from '../../generated/protocol.js';
|
|
11
11
|
import * as Bindings from '../bindings/bindings.js';
|
|
12
|
-
import * as StackTrace from '../stack_trace/stack_trace.js';
|
|
12
|
+
import type * as StackTrace from '../stack_trace/stack_trace.js';
|
|
13
13
|
|
|
14
14
|
export const enum Events {
|
|
15
15
|
TOOLS_ADDED = 'ToolsAdded',
|
|
@@ -18,20 +18,13 @@ export const enum Events {
|
|
|
18
18
|
TOOL_RESPONDED = 'ToolResponded',
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export interface ExceptionDetails {
|
|
22
|
-
readonly error: SDK.RemoteObject.RemoteObject;
|
|
23
|
-
readonly description: string;
|
|
24
|
-
readonly frames: StackTrace.ErrorStackParser.ParsedErrorFrame[];
|
|
25
|
-
readonly cause?: ExceptionDetails;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
21
|
export class Result {
|
|
29
22
|
readonly status: Protocol.WebMCP.InvocationStatus;
|
|
30
23
|
readonly output?: unknown;
|
|
31
24
|
readonly errorText?: string;
|
|
32
25
|
// TODO(crbug.com/494516094) Clean this up if the target disappears?
|
|
33
26
|
readonly #exception?: SDK.RemoteObject.RemoteObject;
|
|
34
|
-
#
|
|
27
|
+
#symbolizedError?: Promise<Bindings.SymbolizedError.SymbolizedError|null>;
|
|
35
28
|
|
|
36
29
|
constructor(
|
|
37
30
|
status: Protocol.WebMCP.InvocationStatus, output: unknown|undefined, errorText: string|undefined,
|
|
@@ -42,46 +35,13 @@ export class Result {
|
|
|
42
35
|
this.output = output;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
get
|
|
46
|
-
if (!this.#
|
|
47
|
-
this.#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async #resolveExceptionDetails(errorObj: SDK.RemoteObject.RemoteObject|undefined):
|
|
53
|
-
Promise<ExceptionDetails|undefined> {
|
|
54
|
-
if (!errorObj) {
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
const error = SDK.RemoteObject.RemoteError.objectAsError(errorObj);
|
|
58
|
-
const [details, cause] = await Promise.all([error.exceptionDetails(), error.cause()]);
|
|
59
|
-
const description = error.errorStack;
|
|
60
|
-
|
|
61
|
-
const frames =
|
|
62
|
-
StackTrace.ErrorStackParser.parseSourcePositionsFromErrorStack(errorObj.runtimeModel(), error.errorStack) || [];
|
|
63
|
-
if (details?.stackTrace) {
|
|
64
|
-
StackTrace.ErrorStackParser.augmentErrorStackWithScriptIds(frames, details.stackTrace);
|
|
38
|
+
get symbolizedError(): Promise<Bindings.SymbolizedError.SymbolizedError|null>|undefined {
|
|
39
|
+
if (!this.#symbolizedError) {
|
|
40
|
+
this.#symbolizedError = this.#exception ?
|
|
41
|
+
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().createSymbolizedError(this.#exception) :
|
|
42
|
+
Promise.resolve(null);
|
|
65
43
|
}
|
|
66
|
-
|
|
67
|
-
if (cause?.subtype === 'error') {
|
|
68
|
-
return {error: errorObj, description, frames, cause: await this.#resolveExceptionDetails(cause)};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (cause?.type === 'string') {
|
|
72
|
-
return {
|
|
73
|
-
error: errorObj,
|
|
74
|
-
description,
|
|
75
|
-
frames,
|
|
76
|
-
cause: {
|
|
77
|
-
error: cause,
|
|
78
|
-
description: cause.value as string,
|
|
79
|
-
frames: [],
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return {error: errorObj, description, frames};
|
|
44
|
+
return this.#symbolizedError;
|
|
85
45
|
}
|
|
86
46
|
}
|
|
87
47
|
|