chrome-devtools-frontend 1.0.1645245 → 1.0.1646714
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/SECURITY.md +1 -1
- 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 +1 -1
- package/front_end/generated/SupportedCSSProperties.js +279 -0
- package/front_end/generated/protocol.ts +0 -1
- package/front_end/models/ai_assistance/AiAgent2.ts +43 -11
- 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/AiAgent.ts +2 -2
- 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/agents/StorageAgent.ts +45 -0
- package/front_end/models/ai_assistance/ai_assistance.ts +8 -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/skills/Skill.ts +1 -1
- package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
- package/front_end/models/ai_assistance/skills/network.md +16 -0
- package/front_end/models/ai_assistance/skills/styling.md +1 -1
- package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +118 -0
- package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +124 -0
- package/front_end/models/ai_assistance/tools/README.md +1 -1
- package/front_end/models/ai_assistance/tools/Tool.ts +2 -0
- package/front_end/models/ai_assistance/tools/ToolRegistry.ts +4 -0
- package/front_end/models/emulation/EmulatedDevices.ts +430 -12
- 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 +14 -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/WebMCPTreeElement.ts +8 -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/legacy/components/utils/Linkifier.ts +10 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +9 -0
- package/package.json +1 -1
|
@@ -26,15 +26,42 @@ import {ToolRegistry} from './tools/ToolRegistry.js';
|
|
|
26
26
|
|
|
27
27
|
const SKILL_DISPLAY_NAMES: Record<SkillName, string> = {
|
|
28
28
|
styling: 'CSS and styling',
|
|
29
|
+
network: 'Network requests',
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
const preamble = `You are the most advanced unified AI assistant integrated into Chrome DevTools.
|
|
33
|
+
Your role is to help web developers debug, analyze, and optimize web applications by learning specialized skills and utilizing tools.
|
|
34
|
+
|
|
35
|
+
# Style Guidelines
|
|
36
|
+
* **Precision and Brevity**: Use the precision of Strunk & White, the brevity of Hemingway, and the simple clarity of Vonnegut. Keep answers short, direct, and avoid repeated information or filler.
|
|
37
|
+
* **Tone**: Technical, precise, educational, and supportive.
|
|
38
|
+
* **No Self-Reference**: Do not mention that you are an AI, or refer to yourself in the third person. Simulate a senior web development expert.
|
|
39
|
+
* **No Internal Details**: Do not mention internal implementation details like the names of functions or tools you called (e.g., do not say "I called getStyles").
|
|
40
|
+
|
|
41
|
+
# Workflow
|
|
42
|
+
1. **Analyze**: Understand the user's intent, the context provided, and what they are trying to achieve.
|
|
43
|
+
2. **Investigate**: Proactively use your learned skills and tools to gather live data. Do not make assumptions or guess without sufficient evidence.
|
|
44
|
+
3. **Analyze**: Explore multiple potential explanations and solutions. Distinguish between the primary root cause and contributing factors.
|
|
45
|
+
4. **Respond**: Provide a structured, clear, and actionable response.
|
|
46
|
+
|
|
47
|
+
# Response Structure
|
|
48
|
+
If the user asks a question that requires an investigation or debugging, use this structure:
|
|
49
|
+
* **Root Cause(s)**: Point out the root cause(s) of the problem.
|
|
50
|
+
- Example: "**Root Cause**: [reason]" or "**Root Causes**:" followed by a bulleted list.
|
|
51
|
+
* **Suggestion(s)**: List actionable solution suggestion(s) in order of impact.
|
|
52
|
+
- Example: "**Suggestion**: [Suggestion]" or "**Suggestions**:" followed by a bulleted list.
|
|
53
|
+
|
|
54
|
+
# Constraints
|
|
55
|
+
* **CRITICAL**: You are a web development assistant. NEVER provide answers to questions of unrelated topics (such as legal advice, financial advice, personal opinions, medical advice, religion, race, politics, sexuality, gender, or any other non-web-development topics). If asked about these, respond with: "Sorry, I can't answer that. I'm best at questions about web development and debugging."
|
|
56
|
+
* **CRITICAL**: Do not write full Python programs or other scripts to interact with the environment. Only invoke the allowed tools.
|
|
57
|
+
* **CRITICAL**: Do not expose raw, internal system identifiers (such as database IDs, internal node paths, or event keys) directly to the user. Use descriptive names instead.`;
|
|
58
|
+
|
|
31
59
|
export class AiAgent2 extends AiAgent<unknown> {
|
|
32
60
|
// TODO: The static preamble is a placeholder and will eventually live server-side.
|
|
33
|
-
readonly preamble =
|
|
34
|
-
readonly clientFeature = Host.AidaClient.ClientFeature.
|
|
61
|
+
readonly preamble = preamble;
|
|
62
|
+
readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_DEVTOOLS_V2_AGENT;
|
|
35
63
|
readonly userTier = 'TESTERS';
|
|
36
64
|
|
|
37
|
-
#skillsInjected = false;
|
|
38
65
|
#changes = new ChangeManager();
|
|
39
66
|
#execJs: typeof executeJsCode;
|
|
40
67
|
readonly #allowedOrigin?: () => AllowedOriginResult;
|
|
@@ -51,9 +78,12 @@ export class AiAgent2 extends AiAgent<unknown> {
|
|
|
51
78
|
this.#execJs = opts.execJs ?? executeJsCode;
|
|
52
79
|
this.#allowedOrigin = opts.allowedOrigin;
|
|
53
80
|
this.#declaredTools.add('learnSkills');
|
|
54
|
-
const skillsList = Object.keys(SKILLS).join(', ');
|
|
55
81
|
this.declareFunction<{skills: SkillName[]}>('learnSkills', {
|
|
56
|
-
description:
|
|
82
|
+
description: () => {
|
|
83
|
+
const unloadedSkills = Object.keys(SKILLS).filter(name => !this.#activeSkills.has(name as SkillName));
|
|
84
|
+
return `Loads the specified skills to gain access to their specialized tools. Call this if the user's query relates to an available skill that is not yet loaded. Available skills: ${
|
|
85
|
+
unloadedSkills.join(', ')}.`;
|
|
86
|
+
},
|
|
57
87
|
parameters: {
|
|
58
88
|
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
59
89
|
description: 'Parameters for learning skills',
|
|
@@ -103,16 +133,18 @@ QUERY: ${query}`;
|
|
|
103
133
|
}
|
|
104
134
|
}
|
|
105
135
|
|
|
106
|
-
|
|
136
|
+
const unloadedSkills =
|
|
137
|
+
Object.entries(this.getSkills()).filter(([name]) => !this.#activeSkills.has(name as SkillName));
|
|
138
|
+
if (unloadedSkills.length === 0) {
|
|
107
139
|
return enhancedQuery;
|
|
108
140
|
}
|
|
109
|
-
|
|
110
|
-
const skillsManifest =
|
|
111
|
-
|
|
112
|
-
return `Available skills:
|
|
141
|
+
|
|
142
|
+
const skillsManifest = unloadedSkills.map(([name, skill]) => `- ${name}: ${skill.description}`).join('\n');
|
|
143
|
+
return `Available skills that are not yet loaded:
|
|
113
144
|
${skillsManifest}
|
|
114
145
|
|
|
115
|
-
You must call \`learnSkills\` to load a skill before you can use
|
|
146
|
+
You must call \`learnSkills\` to load a skill before you can use its tools.
|
|
147
|
+
If the user's request requires a skill that is not currently loaded, you MUST call \`learnSkills\` to load that skill first, instead of attempting to solve the query using tools from other skills.
|
|
116
148
|
|
|
117
149
|
User query: ${enhancedQuery}`;
|
|
118
150
|
}
|
|
@@ -25,8 +25,8 @@ import {
|
|
|
25
25
|
type UserQuery
|
|
26
26
|
} from './agents/AiAgent.js';
|
|
27
27
|
import {ContextSelectionAgent} from './agents/ContextSelectionAgent.js';
|
|
28
|
-
import {FileAgent
|
|
29
|
-
import {NetworkAgent
|
|
28
|
+
import {FileAgent} from './agents/FileAgent.js';
|
|
29
|
+
import {NetworkAgent} from './agents/NetworkAgent.js';
|
|
30
30
|
import {PerformanceAgent, PerformanceTraceContext} from './agents/PerformanceAgent.js';
|
|
31
31
|
import {StorageAgent, StorageContext} from './agents/StorageAgent.js';
|
|
32
32
|
import {StylingAgent} from './agents/StylingAgent.js';
|
|
@@ -34,6 +34,8 @@ import {AiAgent2} from './AiAgent2.js';
|
|
|
34
34
|
import {AiHistoryStorage, ConversationType, type SerializedConversation} from './AiHistoryStorage.js';
|
|
35
35
|
import type {ChangeManager} from './ChangeManager.js';
|
|
36
36
|
import {DOMNodeContext} from './contexts/DOMNodeContext.js';
|
|
37
|
+
import {FileContext} from './contexts/FileContext.js';
|
|
38
|
+
import {RequestContext} from './contexts/RequestContext.js';
|
|
37
39
|
|
|
38
40
|
export const NOT_FOUND_IMAGE_DATA = '';
|
|
39
41
|
export const CONTEXT_TITLE = 'Analyzing data';
|
|
@@ -9,6 +9,8 @@ import type * as Platform from '../../core/platform/platform.js';
|
|
|
9
9
|
* Returns true if the origin is considered opaque and should be blocked from
|
|
10
10
|
* AI assistance to prevent potential data leakage.
|
|
11
11
|
*
|
|
12
|
+
* @param origin The origin string to check.
|
|
13
|
+
* @returns True if the origin is opaque and blocked.
|
|
12
14
|
* @see https://crbug.com/513732588
|
|
13
15
|
*/
|
|
14
16
|
export function isOpaqueOrigin(origin: string): boolean {
|
|
@@ -16,13 +18,22 @@ export function isOpaqueOrigin(origin: string): boolean {
|
|
|
16
18
|
* Origins starting with 'about' (like about:blank or about:srcdoc) are
|
|
17
19
|
* considered opaque. 'about://' is the sentinel used by DevTools
|
|
18
20
|
* ParsedURL.securityOrigin() for these.
|
|
21
|
+
*
|
|
22
|
+
* We also treat empty origins, 'undefined', and 'null' as opaque to prevent
|
|
23
|
+
* bypasses when URL parsing fails or variables are uninitialized.
|
|
19
24
|
*/
|
|
20
|
-
|
|
25
|
+
const lower = origin.toLowerCase();
|
|
26
|
+
return lower === '' || lower === 'null' || lower === 'data:' || lower.startsWith('about') ||
|
|
27
|
+
lower.startsWith('detached') || lower.startsWith('undefined');
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
/**
|
|
24
31
|
* Extracts the origin from a context URL or identifier.
|
|
25
|
-
* Handles special cases like "detached" nodes
|
|
32
|
+
* Handles special cases like "detached" nodes, trace identifiers,
|
|
33
|
+
* opaque blob URLs, and isolates local file paths.
|
|
34
|
+
*
|
|
35
|
+
* @param contextURL The context URL or trace/node identifier.
|
|
36
|
+
* @returns The extracted origin string.
|
|
26
37
|
*/
|
|
27
38
|
export function extractContextOrigin(contextURL: string): string {
|
|
28
39
|
if (isOpaqueOrigin(contextURL)) {
|
|
@@ -31,6 +42,28 @@ export function extractContextOrigin(contextURL: string): string {
|
|
|
31
42
|
if (contextURL.startsWith('trace-')) {
|
|
32
43
|
return contextURL;
|
|
33
44
|
}
|
|
45
|
+
// If a blob URL has an opaque inner origin (e.g. blob:null/uuid, blob:about:blank),
|
|
46
|
+
// it won't contain "://". We classify these as opaque ('null') to prevent cross-origin leaks.
|
|
47
|
+
if (/^blob:/i.test(contextURL)) {
|
|
48
|
+
const innerURL = contextURL.substring(5);
|
|
49
|
+
if (!innerURL.includes('://')) {
|
|
50
|
+
return 'null';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Local files collapse to a generic "file://" origin by default. We isolate them
|
|
54
|
+
// by appending the normalized path, ensuring distinct files are treated as different origins.
|
|
55
|
+
if (/^file:\/\//i.test(contextURL)) {
|
|
56
|
+
const parsed = Common.ParsedURL.ParsedURL.fromString(contextURL as Platform.DevToolsPath.UrlString);
|
|
57
|
+
if (parsed) {
|
|
58
|
+
// Include host and port (authority) to preserve server names in Windows UNC paths
|
|
59
|
+
// (e.g. file://server/path) so different network shares are isolated.
|
|
60
|
+
const authority = parsed.host + (parsed.port ? ':' + parsed.port : '');
|
|
61
|
+
return 'file://' + authority + parsed.path;
|
|
62
|
+
}
|
|
63
|
+
// Fallback to 'null' (opaque) if parsing fails. This prevents malformed file URLs
|
|
64
|
+
// from bypassing isolation by collapsing to the same 'file://' string.
|
|
65
|
+
return 'null';
|
|
66
|
+
}
|
|
34
67
|
return Common.ParsedURL.ParsedURL.extractOrigin(contextURL as Platform.DevToolsPath.UrlString);
|
|
35
68
|
}
|
|
36
69
|
|
|
@@ -44,3 +77,31 @@ export function areOriginsEquivalent(origin1: string, origin2: string): boolean
|
|
|
44
77
|
}
|
|
45
78
|
return origin1 === origin2;
|
|
46
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Validates if the source code contents of a file (identified by targetURL) can be retrieved
|
|
83
|
+
* and shared with the AI, based on the origin of the active trace context.
|
|
84
|
+
*
|
|
85
|
+
* This function handles the following branches:
|
|
86
|
+
* 1. **Opaque origins**: If either the trace origin or target file origin is opaque
|
|
87
|
+
* (e.g., data URLs, about:blank, sandboxed frames), access is blocked.
|
|
88
|
+
* 2. **Fresh Recordings**: For live page recordings, a strict same-origin comparison
|
|
89
|
+
* (scheme, host, and port matching) is enforced.
|
|
90
|
+
*
|
|
91
|
+
* @param targetURL The URL of the file to be read.
|
|
92
|
+
* @param traceOrigin The allowed origin of the trace context.
|
|
93
|
+
* @returns true if reading the file is permitted; false otherwise.
|
|
94
|
+
*/
|
|
95
|
+
export function canResourceContentsBeReadForTrace(targetURL: string, traceOrigin: string): boolean {
|
|
96
|
+
// We explicitly block all file:// URLs. While we want to allow users to debug
|
|
97
|
+
// traces on local sites, allowing file:// access poses security risks (e.g.,
|
|
98
|
+
// reading local files like /etc/passwd via prompt injection) because file://
|
|
99
|
+
// origins are not sufficiently isolated from each other in DevTools' origin model.
|
|
100
|
+
if (traceOrigin.startsWith('file://') || targetURL.startsWith('file://')) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const targetOrigin = extractContextOrigin(targetURL);
|
|
105
|
+
|
|
106
|
+
return areOriginsEquivalent(targetOrigin, traceOrigin);
|
|
107
|
+
}
|
|
@@ -11,12 +11,14 @@ Each agent has a _context_ defined, which represents the selected data that form
|
|
|
11
11
|
- The `PerformanceAgent` has an individual performance trace and a specific focus (an insight, or a call tree) as its context.
|
|
12
12
|
- The `StylingAgent` has a DOM Node as its context.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Contexts are defined as subclasses extending the `ConversationContext` abstract class, which defines the following key methods:
|
|
15
15
|
|
|
16
|
-
- `getOrigin()`: the origin of the data. This is
|
|
17
|
-
- `getTitle()
|
|
16
|
+
- `getOrigin()`: the origin of the data. This is critical for security: if the user switches to a new context with a different origin, the panel forces a new conversation to avoid cross-origin data exposure.
|
|
17
|
+
- `getTitle()`: returns the user-facing title of the context.
|
|
18
|
+
- `getPromptDetails()`: returns a Markdown formatted description of the context item to be directly included in the LLM prompt.
|
|
19
|
+
- `getUserFacingDetails()`: returns structured details (such as request/response headers, timings, etc.) displayed to the user in the UI under the "Analyzing data" accordion.
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
Encapsulating prompt formatting and UI generation within the context classes simplifies agent implementations and promotes reusability. Contexts are located in the `contexts/` directory.
|
|
20
22
|
|
|
21
23
|
### Formatters
|
|
22
24
|
|
|
@@ -102,3 +104,12 @@ To aid debugging, you can enable the AI Assistance logging. This setting is stor
|
|
|
102
104
|
4. In the console, run `setDebugAiAssistanceEnabled(true)`.
|
|
103
105
|
|
|
104
106
|
Now, when interacting with the AI and sending requests, you will see output logged to the console. You can use the `debugLog` helper to add your own logging as you are building out your feature.
|
|
107
|
+
|
|
108
|
+
## Security & Origin Isolation
|
|
109
|
+
|
|
110
|
+
To prevent prompt injection attacks and cross-origin data leaks, the AI Assistance panel enforces strict origin-lock boundaries:
|
|
111
|
+
|
|
112
|
+
* **Origin Locking**: Once a conversation session begins, it locks to the origin of the initial data context (e.g. `https://google.com` or a specific file path).
|
|
113
|
+
* **Opaque Origins**: Any origin classified as opaque (e.g. `data:`, `about:`, `detached` nodes, unparsed `undefined://` or empty origins) is blocked from starting AI assistance (`isOpaqueOrigin()`).
|
|
114
|
+
* **File Isolation**: Local files (`file://`) do not share a single wildcard origin. Instead, the path is appended (e.g. `file:///path/to/file.js`) to treat each local file as its own unique origin. Swapping local files in the same conversation is blocked.
|
|
115
|
+
* **Equivalence**: `areOriginsEquivalent()` ensures opaque origins are never equivalent to anything (including themselves), forcing a conversation reset on transitions.
|
|
@@ -410,7 +410,7 @@ export interface FunctionDeclaration<Args extends Record<string, unknown>, Retur
|
|
|
410
410
|
* Description of function, this is send to the LLM
|
|
411
411
|
* to explain what will the function do.
|
|
412
412
|
*/
|
|
413
|
-
description: string;
|
|
413
|
+
description: string|(() => string);
|
|
414
414
|
/**
|
|
415
415
|
* JSON schema like representation of the parameters
|
|
416
416
|
* the function needs to be called with.
|
|
@@ -582,7 +582,7 @@ export abstract class AiAgent<T> {
|
|
|
582
582
|
for (const [name, definition] of this.#functionDeclarations.entries()) {
|
|
583
583
|
declarations.push({
|
|
584
584
|
name,
|
|
585
|
-
description: definition.description,
|
|
585
|
+
description: typeof definition.description === 'function' ? definition.description() : definition.description,
|
|
586
586
|
parameters: definition.parameters,
|
|
587
587
|
});
|
|
588
588
|
}
|
|
@@ -15,6 +15,8 @@ import type * as Trace from '../../trace/trace.js';
|
|
|
15
15
|
import * as Workspace from '../../workspace/workspace.js';
|
|
16
16
|
import {isOpaqueOrigin} from '../AiOrigins.js';
|
|
17
17
|
import {DOMNodeContext} from '../contexts/DOMNodeContext.js';
|
|
18
|
+
import {FileContext} from '../contexts/FileContext.js';
|
|
19
|
+
import {getRequestContextOrigin, RequestContext} from '../contexts/RequestContext.js';
|
|
18
20
|
import {debugLog} from '../debug.js';
|
|
19
21
|
import {StorageItem} from '../StorageItem.js';
|
|
20
22
|
|
|
@@ -26,8 +28,6 @@ import {
|
|
|
26
28
|
type ContextResponse,
|
|
27
29
|
type RequestOptions,
|
|
28
30
|
} from './AiAgent.js';
|
|
29
|
-
import {FileContext} from './FileAgent.js';
|
|
30
|
-
import {getRequestContextOrigin, RequestContext} from './NetworkAgent.js';
|
|
31
31
|
import {PerformanceTraceContext} from './PerformanceAgent.js';
|
|
32
32
|
import {StorageContext} from './StorageAgent.js';
|
|
33
33
|
|
|
@@ -5,13 +5,11 @@
|
|
|
5
5
|
import * as Host from '../../../core/host/host.js';
|
|
6
6
|
import * as Root from '../../../core/root/root.js';
|
|
7
7
|
import type * as Workspace from '../../workspace/workspace.js';
|
|
8
|
-
import {FileFormatter} from '../data_formatters/FileFormatter.js';
|
|
9
8
|
|
|
10
9
|
import {
|
|
11
10
|
AiAgent,
|
|
12
|
-
type ContextDetail,
|
|
13
11
|
type ContextResponse,
|
|
14
|
-
ConversationContext,
|
|
12
|
+
type ConversationContext,
|
|
15
13
|
type RequestOptions,
|
|
16
14
|
ResponseType,
|
|
17
15
|
} from './AiAgent.js';
|
|
@@ -75,31 +73,6 @@ External Resources:
|
|
|
75
73
|
MDN Web Docs: JavaScript Functions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
|
|
76
74
|
`;
|
|
77
75
|
|
|
78
|
-
export class FileContext extends ConversationContext<Workspace.UISourceCode.UISourceCode> {
|
|
79
|
-
#file: Workspace.UISourceCode.UISourceCode;
|
|
80
|
-
|
|
81
|
-
constructor(file: Workspace.UISourceCode.UISourceCode) {
|
|
82
|
-
super();
|
|
83
|
-
this.#file = file;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
override getURL(): string {
|
|
87
|
-
return this.#file.url();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
override getItem(): Workspace.UISourceCode.UISourceCode {
|
|
91
|
-
return this.#file;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
override getTitle(): string {
|
|
95
|
-
return this.#file.displayName();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
override async refresh(): Promise<void> {
|
|
99
|
-
await this.#file.requestContentData();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
76
|
/**
|
|
104
77
|
* One agent instance handles one conversation. Create a new agent
|
|
105
78
|
* instance for a new conversation.
|
|
@@ -126,27 +99,21 @@ export class FileAgent extends AiAgent<Workspace.UISourceCode.UISourceCode> {
|
|
|
126
99
|
return;
|
|
127
100
|
}
|
|
128
101
|
|
|
102
|
+
const details = await selectedFile.getUserFacingDetails();
|
|
103
|
+
if (!details) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
129
107
|
yield {
|
|
130
108
|
type: ResponseType.CONTEXT,
|
|
131
|
-
details
|
|
109
|
+
details,
|
|
132
110
|
};
|
|
133
111
|
}
|
|
134
112
|
|
|
135
113
|
override async enhanceQuery(
|
|
136
114
|
query: string, selectedFile: ConversationContext<Workspace.UISourceCode.UISourceCode>|null): Promise<string> {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
'';
|
|
115
|
+
const promptDetails = selectedFile ? await selectedFile.getPromptDetails() : null;
|
|
116
|
+
const fileEnchantmentQuery = promptDetails ? `${promptDetails}\n\n# User request\n\n` : '';
|
|
140
117
|
return `${fileEnchantmentQuery}${query}`;
|
|
141
118
|
}
|
|
142
119
|
}
|
|
143
|
-
|
|
144
|
-
function createContextDetailsForFileAgent(selectedFile: ConversationContext<Workspace.UISourceCode.UISourceCode>):
|
|
145
|
-
[ContextDetail, ...ContextDetail[]] {
|
|
146
|
-
return [
|
|
147
|
-
{
|
|
148
|
-
title: 'Selected file',
|
|
149
|
-
text: new FileFormatter(selectedFile.getItem()).formatFile(),
|
|
150
|
-
},
|
|
151
|
-
];
|
|
152
|
-
}
|
|
@@ -10,7 +10,7 @@ Content:
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"title": "Response",
|
|
13
|
-
"text": "Response headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200
|
|
13
|
+
"text": "Response headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status: pending\n"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"title": "Timing",
|
|
@@ -59,7 +59,7 @@ Content:
|
|
|
59
59
|
{
|
|
60
60
|
"parts": [
|
|
61
61
|
{
|
|
62
|
-
"text": "# Selected network request
|
|
62
|
+
"text": "# Selected network request\nRequest: https://www.example.com\n\nRequest headers:\ncontent-type: bar1\n\nResponse headers:\ncontent-type: bar2\nx-forwarded-for: bar3\n\nResponse body:\n{\"request\":\"body\"}\n\nResponse status: 200\nNetwork request status: pending\n\nRequest timing:\nQueued at (timestamp): 0 s\nStarted at (timestamp): 501 s\nQueueing (duration): 501 s\nConnection start (stalled) (duration): 800 ms\nRequest sent (duration): 100 ms\nDuration (duration): 501 s\n\nRequest initiator chain:\n- URL: <redacted cross-origin initiator URL>\n\t- URL: https://www.example.com\n\t\t- URL: https://www.example.com/1\n\t\t- URL: https://www.example.com/2\n\n# User request\n\ntest"
|
|
63
63
|
}
|
|
64
64
|
],
|
|
65
65
|
"role": 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.
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
AiAgent,
|
|
15
15
|
type ContextResponse,
|
|
16
16
|
ConversationContext,
|
|
17
|
+
type ConversationSuggestions,
|
|
17
18
|
type RequestOptions,
|
|
18
19
|
ResponseType,
|
|
19
20
|
} from './AiAgent.js';
|
|
@@ -97,6 +98,50 @@ export class StorageContext extends ConversationContext<StorageItem> {
|
|
|
97
98
|
}
|
|
98
99
|
return `Storage: ${this.getOrigin()}`;
|
|
99
100
|
}
|
|
101
|
+
|
|
102
|
+
override async getSuggestions(): Promise<ConversationSuggestions|undefined> {
|
|
103
|
+
if (this.#item instanceof CookieItem) {
|
|
104
|
+
if (this.#item.name) {
|
|
105
|
+
return [
|
|
106
|
+
{
|
|
107
|
+
title: 'Why is this cookie set?',
|
|
108
|
+
jslogContext: 'storage-cookie',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
title: 'Explain the value of this cookie',
|
|
112
|
+
jslogContext: 'storage-cookie',
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
title: 'Explain the cookies set by this page',
|
|
119
|
+
jslogContext: 'storage-cookie',
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
if (this.#item instanceof DOMStorageItem) {
|
|
124
|
+
if (this.#item.key) {
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
title: 'What is the purpose of this storage entry?',
|
|
128
|
+
jslogContext: 'storage-domstorage',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
title: 'Explain the value of this storage entry',
|
|
132
|
+
jslogContext: 'storage-domstorage',
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
return [
|
|
137
|
+
{
|
|
138
|
+
title: 'Explain these storage items',
|
|
139
|
+
jslogContext: 'storage-domstorage',
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
100
145
|
}
|
|
101
146
|
|
|
102
147
|
// Maximum character length of values allowed.
|