chrome-devtools-frontend
Version:
Chrome DevTools UI
128 lines (110 loc) • 4.25 kB
text/typescript
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Host from '../../../core/host/host.js';
import * as i18n from '../../../core/i18n/i18n.js';
import type * as Platform from '../../../core/platform/platform.js';
import * as Root from '../../../core/root/root.js';
import * as SDK from '../../../core/sdk/sdk.js';
import * as TextUtils from '../../../core/text_utils/text_utils.js';
import {canResourceContentsBeReadForTrace} from '../AiOrigins.js';
import {PerformanceTraceContext} from '../contexts/PerformanceTraceContext.js';
import {
type BaseToolCapability,
type DataHandlerResult,
type DataTool,
type TargetCapability,
type ToolArgs,
ToolName,
} from './Tool.js';
const UIStringsNotTranslate = {
lookingAtResourceContent: 'Looking at resource content',
} as const;
const lockedString = i18n.i18n.lockedString;
export interface GetResourceContentArgs extends ToolArgs {
url: string;
}
export class GetResourceContentTool implements
DataTool<GetResourceContentArgs, {content: string}, BaseToolCapability&TargetCapability> {
readonly name = ToolName.GET_RESOURCE_CONTENT;
readonly description =
'Returns the content of the resource with the given url. Only use this for text resource types.';
readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetResourceContentArgs> = {
type: Host.AidaClient.ParametersTypes.OBJECT,
description: 'Arguments for looking up resource content.',
nullable: false,
properties: {
url: {
type: Host.AidaClient.ParametersTypes.STRING,
description: 'The url for the resource.',
nullable: false,
},
},
required: ['url'],
};
displayInfoFromArgs(params: GetResourceContentArgs): {
title: string,
action: string,
} {
return {
title: lockedString(UIStringsNotTranslate.lookingAtResourceContent),
action: `getResourceContent('${params.url}')`,
};
}
async handler(
params: GetResourceContentArgs,
capabilities: BaseToolCapability&TargetCapability,
): Promise<DataHandlerResult<{content: string}>> {
const conversationContext = capabilities.conversationContext;
if (!conversationContext || !(conversationContext instanceof PerformanceTraceContext)) {
return {error: 'Performance trace context is not available.'};
}
if (conversationContext.getOrigin().startsWith('imported-trace://')) {
return {error: 'Cannot use this tool on an imported file.'};
}
const allowedOrigin = conversationContext.getOrigin();
if (!canResourceContentsBeReadForTrace(params.url, allowedOrigin)) {
return {error: 'Resource not found'};
}
const focus = conversationContext.getItem();
const {parsedTrace} = focus;
let content: string;
const url = params.url as Platform.DevToolsPath.UrlString;
const script = parsedTrace.data.Scripts?.scripts.find(script => script.url === params.url);
if (script?.content !== undefined) {
content = script.content;
} else {
const target = capabilities.getTarget();
const isTraceApp = Root.Runtime.Runtime.isTraceApp();
if (target || isTraceApp) {
const targetManager = target?.targetManager() ??
// eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
SDK.TargetManager.TargetManager.instance();
const resource = SDK.ResourceTreeModel.ResourceTreeModel.resourceForURL(targetManager, url);
if (!resource) {
return {error: 'Resource not found'};
}
const data = await resource.requestContentData();
if (TextUtils.ContentData.ContentData.isError(data)) {
return {error: `Could not get resource content: ${data.error}`};
}
if (!data.isTextContent) {
return {error: 'Cannot retrieve content for non-text resource'};
}
content = data.text;
} else {
return {error: 'Resource not found'};
}
}
return {
result: {content},
widgets: [{
name: 'SOURCE_CODE',
data: {
url,
code: content,
},
}],
};
}
}