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.
Files changed (56) hide show
  1. package/.agents/skills/devtools-source-maps/SKILL.md +124 -0
  2. package/SECURITY.md +1 -1
  3. package/docs/README.md +1 -0
  4. package/docs/using_source_maps.md +159 -0
  5. package/front_end/core/host/AidaClientTypes.ts +2 -0
  6. package/front_end/core/host/UserMetrics.ts +2 -1
  7. package/front_end/core/root/Runtime.ts +10 -0
  8. package/front_end/core/sdk/DebuggerModel.ts +7 -9
  9. package/front_end/core/sdk/NetworkRequest.ts +0 -24
  10. package/front_end/generated/InspectorBackendCommands.ts +1 -1
  11. package/front_end/generated/SupportedCSSProperties.js +279 -0
  12. package/front_end/generated/protocol.ts +0 -1
  13. package/front_end/models/ai_assistance/AiAgent2.ts +43 -11
  14. package/front_end/models/ai_assistance/AiConversation.ts +4 -2
  15. package/front_end/models/ai_assistance/AiOrigins.ts +63 -2
  16. package/front_end/models/ai_assistance/README.md +15 -4
  17. package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -2
  18. package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +2 -2
  19. package/front_end/models/ai_assistance/agents/FileAgent.ts +9 -42
  20. package/front_end/models/ai_assistance/agents/NetworkAgent.snapshot.txt +2 -2
  21. package/front_end/models/ai_assistance/agents/NetworkAgent.ts +9 -133
  22. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +10 -2
  23. package/front_end/models/ai_assistance/agents/README.md +7 -0
  24. package/front_end/models/ai_assistance/agents/StorageAgent.ts +45 -0
  25. package/front_end/models/ai_assistance/ai_assistance.ts +8 -0
  26. package/front_end/models/ai_assistance/contexts/FileContext.ts +45 -0
  27. package/front_end/models/ai_assistance/contexts/RequestContext.snapshot.txt +48 -0
  28. package/front_end/models/ai_assistance/contexts/RequestContext.ts +116 -0
  29. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +2 -1
  30. package/front_end/models/ai_assistance/skills/Skill.ts +1 -1
  31. package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
  32. package/front_end/models/ai_assistance/skills/network.md +16 -0
  33. package/front_end/models/ai_assistance/skills/styling.md +1 -1
  34. package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +118 -0
  35. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +124 -0
  36. package/front_end/models/ai_assistance/tools/README.md +1 -1
  37. package/front_end/models/ai_assistance/tools/Tool.ts +2 -0
  38. package/front_end/models/ai_assistance/tools/ToolRegistry.ts +4 -0
  39. package/front_end/models/emulation/EmulatedDevices.ts +430 -12
  40. package/front_end/models/trace/handlers/NetworkRequestsHandler.ts +15 -11
  41. package/front_end/models/web_mcp/WebMCPModel.ts +8 -48
  42. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +14 -13
  43. package/front_end/panels/ai_assistance/components/ChatInput.ts +4 -4
  44. package/front_end/panels/application/ApplicationPanelSidebar.ts +25 -0
  45. package/front_end/panels/application/WebMCPTreeElement.ts +8 -0
  46. package/front_end/panels/application/WebMCPView.ts +40 -70
  47. package/front_end/panels/application/components/AdsView.ts +31 -28
  48. package/front_end/panels/application/components/adsView.css +6 -0
  49. package/front_end/panels/common/ExtensionServer.ts +5 -0
  50. package/front_end/panels/profiler/IsolateSelector.ts +4 -2
  51. package/front_end/panels/profiler/ProfileLauncherView.ts +194 -126
  52. package/front_end/panels/profiler/ProfilesPanel.ts +1 -3
  53. package/front_end/third_party/chromium/README.chromium +1 -1
  54. package/front_end/ui/legacy/components/utils/Linkifier.ts +10 -0
  55. package/front_end/ui/visual_logging/KnownContextValues.ts +9 -0
  56. 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 = 'You are a unified AI assistant in Chrome DevTools. You can learn skills to help the user.';
34
- readonly clientFeature = Host.AidaClient.ClientFeature.CHROME_STYLING_AGENT; // Placeholder
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: `Load skills to help with the task. Available skills: ${skillsList}.`,
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
- if (this.#skillsInjected) {
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
- this.#skillsInjected = true;
110
- const skillsManifest =
111
- Object.entries(this.getSkills()).map(([name, skill]) => `- ${name}: ${skill.description}`).join('\n');
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 it.
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, FileContext} from './agents/FileAgent.js';
29
- import {NetworkAgent, RequestContext} from './agents/NetworkAgent.js';
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
- return origin === 'null' || origin === 'data:' || origin.startsWith('about') || origin.startsWith('detached');
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 and trace identifiers.
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
- When defining a context, they must extend the `ConversationContext` interface, which defines a few methods which must be implemented, including:
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 important as for security concerns if the user swaps to a new context with a different origin, a new conversation must be started to avoid any concerns of sharing data across origins.
17
- - `getTitle()` which are used to represent the context in the UI.
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
- When the user begins a conversation with the AI, we want to include information based on the active context to send as part of the prompt. This is done in the agent implementation by overriding the `enhanceQuery()` method, which takes the active context as the second argument. This method can be used to enrich the query with contextual information.
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: createContextDetailsForFileAgent(selectedFile),
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 fileEnchantmentQuery = selectedFile ?
138
- `# Selected file\n${new FileFormatter(selectedFile.getItem()).formatFile()}\n\n# User request\n\n` :
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 \nNetwork request status: pending\n"
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 \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"
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 * as NetworkTimeCalculator from '../../network_time_calculator/network_time_calculator.js';
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: await createContextDetailsForNetworkAgent(selectedNetworkRequest),
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 networkEnchantmentQuery = selectedNetworkRequest ?
200
- `# Selected network request \n${
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.