Skip to content

Commit a54d428

Browse files
OrKoNDevtools-frontend LUCI CQ
authored andcommitted
[AI Assistance] Extract data formatters from agents
Includes the network and file agents for now. Bug: none Change-Id: Id6211a64199daede16b821a4693b3ed474b71249 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6243235 Reviewed-by: Nikolay Vitkov <[email protected]> Commit-Queue: Alex Rudenko <[email protected]>
1 parent c2671e2 commit a54d428

File tree

13 files changed

+672
-569
lines changed

13 files changed

+672
-569
lines changed

config/gni/devtools_grd_files.gni

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,8 @@ grd_files_debug_sources = [
11931193
"front_end/panels/ai_assistance/components/UserActionRow.js",
11941194
"front_end/panels/ai_assistance/components/chatView.css.js",
11951195
"front_end/panels/ai_assistance/components/userActionRow.css.js",
1196+
"front_end/panels/ai_assistance/data_formatters/FileFormatter.js",
1197+
"front_end/panels/ai_assistance/data_formatters/NetworkRequestFormatter.js",
11961198
"front_end/panels/animation/AnimationGroupPreviewUI.js",
11971199
"front_end/panels/animation/AnimationScreenshotPopover.js",
11981200
"front_end/panels/animation/AnimationTimeline.js",

front_end/panels/ai_assistance/BUILD.gn

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ devtools_module("ai_assistance") {
3131
"agents/StylingAgent.ts",
3232
"components/ChatView.ts",
3333
"components/UserActionRow.ts",
34+
"data_formatters/FileFormatter.ts",
35+
"data_formatters/NetworkRequestFormatter.ts",
3436
]
3537

3638
deps = [
@@ -97,6 +99,8 @@ ts_library("unittests") {
9799
"agents/StylingAgent.test.ts",
98100
"components/ChatView.test.ts",
99101
"components/UserActionRow.test.ts",
102+
"data_formatters/FileFormatter.test.ts",
103+
"data_formatters/NetworkRequestFormatter.test.ts",
100104
]
101105

102106
deps = [

front_end/panels/ai_assistance/agents/FileAgent.test.ts

Lines changed: 2 additions & 168 deletions
Large diffs are not rendered by default.

front_end/panels/ai_assistance/agents/FileAgent.ts

Lines changed: 5 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import * as Common from '../../../core/common/common.js';
66
import * as Host from '../../../core/host/host.js';
77
import * as i18n from '../../../core/i18n/i18n.js';
8-
import * as Bindings from '../../../models/bindings/bindings.js';
98
import type * as Workspace from '../../../models/workspace/workspace.js';
109
import * as PanelUtils from '../../utils/utils.js';
10+
import {FileFormatter} from '../data_formatters/FileFormatter.js';
1111

1212
import {
1313
AgentType,
@@ -18,7 +18,6 @@ import {
1818
type RequestOptions,
1919
ResponseType,
2020
} from './AiAgent.js';
21-
import {formatRequestInitiatorChain} from './NetworkAgent.js';
2221

2322
const preamble =
2423
`You are a highly skilled software engineer with expertise in various programming languages and frameworks.
@@ -71,8 +70,6 @@ const UIStringsNotTranslate = {
7170

7271
const lockedString = i18n.i18n.lockedString;
7372

74-
const MAX_FILE_SIZE = 10000;
75-
7673
export class FileContext extends ConversationContext<Workspace.UISourceCode.UISourceCode> {
7774
#file: Workspace.UISourceCode.UISourceCode;
7875

@@ -141,8 +138,9 @@ export class FileAgent extends AiAgent<Workspace.UISourceCode.UISourceCode> {
141138

142139
override async enhanceQuery(
143140
query: string, selectedFile: ConversationContext<Workspace.UISourceCode.UISourceCode>|null): Promise<string> {
144-
const fileEnchantmentQuery =
145-
selectedFile ? `# Selected file\n${formatFile(selectedFile.getItem())}\n\n# User request\n\n` : '';
141+
const fileEnchantmentQuery = selectedFile ?
142+
`# Selected file\n${new FileFormatter(selectedFile.getItem()).formatFile()}\n\n# User request\n\n` :
143+
'';
146144
return `${fileEnchantmentQuery}${query}`;
147145
}
148146
}
@@ -152,69 +150,7 @@ function createContextDetailsForFileAgent(selectedFile: ConversationContext<Work
152150
return [
153151
{
154152
title: 'Selected file',
155-
text: formatFile(selectedFile.getItem()),
153+
text: new FileFormatter(selectedFile.getItem()).formatFile(),
156154
},
157155
];
158156
}
159-
160-
export function formatFile(selectedFile: Workspace.UISourceCode.UISourceCode): string {
161-
const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
162-
const sourceMapDetails = formatSourceMapDetails(selectedFile, debuggerWorkspaceBinding);
163-
const lines = [
164-
`File name: ${selectedFile.displayName()}`,
165-
`URL: ${selectedFile.url()}`,
166-
sourceMapDetails,
167-
];
168-
const resource = Bindings.ResourceUtils.resourceForURL(selectedFile.url());
169-
if (resource?.request) {
170-
lines.push(`Request initiator chain:
171-
${formatRequestInitiatorChain(resource?.request)}`);
172-
}
173-
lines.push(`File content:
174-
${formatFileContent(selectedFile)}`);
175-
return lines.filter(line => line.trim() !== '').join('\n');
176-
}
177-
178-
function formatFileContent(selectedFile: Workspace.UISourceCode.UISourceCode): string {
179-
const contentData = selectedFile.workingCopyContentData();
180-
const content = contentData.isTextContent ? contentData.text : '<binary data>';
181-
const truncated = content.length > MAX_FILE_SIZE ? content.slice(0, MAX_FILE_SIZE) + '...' : content;
182-
return `\`\`\`
183-
${truncated}
184-
\`\`\``;
185-
}
186-
187-
export function formatSourceMapDetails(
188-
selectedFile: Workspace.UISourceCode.UISourceCode,
189-
debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding): string {
190-
const mappedFileUrls = [];
191-
const sourceMapUrls = [];
192-
if (selectedFile.contentType().isFromSourceMap()) {
193-
for (const script of debuggerWorkspaceBinding.scriptsForUISourceCode(selectedFile)) {
194-
const uiSourceCode = debuggerWorkspaceBinding.uiSourceCodeForScript(script);
195-
if (uiSourceCode) {
196-
mappedFileUrls.push(uiSourceCode.url());
197-
if (script.sourceMapURL !== undefined) {
198-
sourceMapUrls.push(script.sourceMapURL);
199-
}
200-
}
201-
}
202-
for (const originURL of Bindings.SASSSourceMapping.SASSSourceMapping.uiSourceOrigin(selectedFile)) {
203-
mappedFileUrls.push(originURL);
204-
}
205-
} else if (selectedFile.contentType().isScript()) {
206-
for (const script of debuggerWorkspaceBinding.scriptsForUISourceCode(selectedFile)) {
207-
if (script.sourceMapURL !== undefined && script.sourceMapURL !== '') {
208-
sourceMapUrls.push(script.sourceMapURL);
209-
}
210-
}
211-
}
212-
if (sourceMapUrls.length === 0) {
213-
return '';
214-
}
215-
let sourceMapDetails = 'Source map: ' + sourceMapUrls;
216-
if (mappedFileUrls.length > 0) {
217-
sourceMapDetails += '\nSource mapped from: ' + mappedFileUrls;
218-
}
219-
return sourceMapDetails;
220-
}

front_end/panels/ai_assistance/agents/NetworkAgent.test.ts

Lines changed: 2 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ import {createNetworkPanelForMockConnection} from '../../../testing/NetworkHelpe
1616
import * as RenderCoordinator from '../../../ui/components/render_coordinator/render_coordinator.js';
1717
import type * as Network from '../../network/network.js';
1818
import {
19-
allowHeader,
20-
formatHeaders,
21-
formatInitiatorUrl,
2219
NetworkAgent,
2320
RequestContext,
2421
ResponseType,
@@ -166,11 +163,11 @@ describeWithMockConnection('NetworkAgent', () => {
166163
details: [
167164
{
168165
title: 'Request',
169-
text: 'Request URL: https://www.example.com\n\nRequest Headers\ncontent-type: bar1',
166+
text: 'Request URL: https://www.example.com\n\nRequest headers:\ncontent-type: bar1',
170167
},
171168
{
172169
title: 'Response',
173-
text: 'Response Status: 200 \n\nResponse Headers\ncontent-type: bar2\nx-forwarded-for: bar3',
170+
text: 'Response Status: 200 \n\nResponse headers:\ncontent-type: bar2\nx-forwarded-for: bar3',
174171
},
175172
{
176173
title: 'Timing',
@@ -236,76 +233,4 @@ test`,
236233
]);
237234
});
238235
});
239-
240-
describe('allowHeader', () => {
241-
it('allows a header from the list', () => {
242-
assert.isTrue(allowHeader({name: 'content-type', value: 'foo'}));
243-
});
244-
245-
it('disallows headers not on the list', () => {
246-
assert.isFalse(allowHeader({name: 'cookie', value: 'foo'}));
247-
assert.isFalse(allowHeader({name: 'set-cookie', value: 'foo'}));
248-
assert.isFalse(allowHeader({name: 'authorization', value: 'foo'}));
249-
});
250-
});
251-
252-
describe('formatInitiatorUrl', () => {
253-
const tests = [
254-
{
255-
allowedResource: 'https://example.test',
256-
targetResource: 'https://example.test',
257-
shouldBeRedacted: false,
258-
},
259-
{
260-
allowedResource: 'https://example.test',
261-
targetResource: 'https://another-example.test',
262-
shouldBeRedacted: true,
263-
},
264-
{
265-
allowedResource: 'file://test',
266-
targetResource: 'https://another-example.test',
267-
shouldBeRedacted: true,
268-
},
269-
{
270-
allowedResource: 'https://another-example.test',
271-
targetResource: 'file://test',
272-
shouldBeRedacted: true,
273-
},
274-
{
275-
allowedResource: 'https://test.example.test',
276-
targetResource: 'https://example.test',
277-
shouldBeRedacted: true,
278-
},
279-
{
280-
allowedResource: 'https://test.example.test:9900',
281-
targetResource: 'https://test.example.test:9901',
282-
shouldBeRedacted: true,
283-
},
284-
];
285-
286-
for (const t of tests) {
287-
it(`${t.targetResource} test when allowed resource is ${t.allowedResource}`, () => {
288-
const formatted = formatInitiatorUrl(new URL(t.targetResource).origin, new URL(t.allowedResource).origin);
289-
if (t.shouldBeRedacted) {
290-
assert.strictEqual(
291-
formatted, '<redacted cross-origin initiator URL>', `${JSON.stringify(t)} was not redacted`);
292-
} else {
293-
assert.strictEqual(formatted, t.targetResource, `${JSON.stringify(t)} was redacted`);
294-
}
295-
});
296-
}
297-
});
298-
299-
describe('formatHeaders', () => {
300-
it('does not redact a header from the list', () => {
301-
assert.strictEqual(formatHeaders('test:', [{name: 'content-type', value: 'foo'}]), 'test:\ncontent-type: foo');
302-
});
303-
304-
it('disallows headers not on the list', () => {
305-
assert.strictEqual(formatHeaders('test:', [{name: 'cookie', value: 'foo'}]), 'test:\ncookie: <redacted>');
306-
assert.strictEqual(formatHeaders('test:', [{name: 'set-cookie', value: 'foo'}]), 'test:\nset-cookie: <redacted>');
307-
assert.strictEqual(
308-
formatHeaders('test:', [{name: 'authorization', value: 'foo'}]), 'test:\nauthorization: <redacted>');
309-
});
310-
});
311236
});

0 commit comments

Comments
 (0)