chrome-devtools-frontend
Version:
Chrome DevTools UI
204 lines (180 loc) • 6.76 kB
text/typescript
// Copyright 2025 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 Common from '../../core/common/common.js';
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 {debugLog} from './debug.js';
export const enum DisabledReason {
GEO_RESTRICTED = 'geo-restricted',
POLICY_RESTRICTED = 'policy-restricted',
WRONG_LOCALE = 'wrong-locale',
NOT_SUPPORTED = 'not-supported',
}
function isLocaleRestricted(): boolean {
try {
const devtoolsLocale = i18n.DevToolsLocale.DevToolsLocale.instance();
return !devtoolsLocale.locale.startsWith('en-');
} catch {
return false;
}
}
function isGeoRestricted(config?: Root.Runtime.HostConfig): boolean {
return config?.aidaAvailability?.blockedByGeo === true;
}
function isPolicyRestricted(config?: Root.Runtime.HostConfig): boolean {
return config?.aidaAvailability?.blockedByEnterprisePolicy === true;
}
function isConsoleInsightsFeatureEnabled(config?: Root.Runtime.HostConfig): boolean {
return config?.aidaAvailability?.enabled !== false && config?.devToolsConsoleInsights?.enabled === true;
}
export const consoleInsightsEnabledSettingDescriptor:
Common.Settings.ConditionalSettingDescriptor<boolean, DisabledReason[]> = {
name: 'console-insights-enabled',
type: Common.Settings.SettingType.BOOLEAN,
defaultValue: false,
isAvailable: (config?: Root.Runtime.HostConfig): Common.Settings.SettingAvailabilityStatus<DisabledReason[]> => {
if (!isConsoleInsightsFeatureEnabled(config)) {
return {
status: Common.Settings.SettingAvailability.UNAVAILABLE,
reason: [DisabledReason.NOT_SUPPORTED],
};
}
const reasons: DisabledReason[] = [];
if (isGeoRestricted(config)) {
reasons.push(DisabledReason.GEO_RESTRICTED);
}
if (isPolicyRestricted(config)) {
reasons.push(DisabledReason.POLICY_RESTRICTED);
}
if (isLocaleRestricted()) {
reasons.push(DisabledReason.WRONG_LOCALE);
}
if (reasons.length > 0) {
return {
status: Common.Settings.SettingAvailability.DISABLED,
reason: reasons,
};
}
return {
status: Common.Settings.SettingAvailability.AVAILABLE,
};
},
};
export function isGeminiBranding(): boolean {
return !!Root.Runtime.hostConfig.devToolsGeminiRebranding?.enabled;
}
/**
* Preconditions determined entirely on the DevTools frontend side (e.g. Incognito
* mode or age restrictions) that prevent AI assistance features from running.
* These are evaluated independently of AIDA service-level availability.
*/
export const enum FrontendAccessPrecondition {
IS_OFF_THE_RECORD = 'is-off-the-record',
AGE_RESTRICTED = 'age-restricted',
}
/**
* The unified set of preconditions that can disable AI assistance.
* This is a union of low-level AIDA service availability preconditions
* and DevTools frontend-specific preconditions.
*/
export type AccessPrecondition =
Exclude<Host.AidaClient.AidaAccessPreconditions, Host.AidaClient.AidaAccessPreconditions.AVAILABLE>|
FrontendAccessPrecondition;
/**
* Returns the list of active preconditions currently preventing AI assistance from being enabled.
* Checks local frontend constraints (e.g. incognito, age check) and combines them with the
* provided AIDA service availability status.
*/
export function getDisabledReasons(aidaAvailability: Host.AidaClient.AidaAccessPreconditions): AccessPrecondition[] {
const reasons: AccessPrecondition[] = [];
if (Root.Runtime.hostConfig.isOffTheRecord) {
reasons.push(FrontendAccessPrecondition.IS_OFF_THE_RECORD);
}
if (aidaAvailability !== Host.AidaClient.AidaAccessPreconditions.AVAILABLE) {
reasons.push(aidaAvailability);
}
// No age check if there is no logged in user. Age check would always fail in that case.
if ((aidaAvailability === Host.AidaClient.AidaAccessPreconditions.AVAILABLE ||
aidaAvailability === Host.AidaClient.AidaAccessPreconditions.NO_INTERNET) &&
Root.Runtime.hostConfig?.aidaAvailability?.blockedByAge === true) {
reasons.push(FrontendAccessPrecondition.AGE_RESTRICTED);
}
return reasons;
}
export function getIconName(): string {
return isGeminiBranding() ? 'spark' : 'smart-assistant';
}
export function isSameOrigin(url1: Platform.DevToolsPath.UrlString, url2: Platform.DevToolsPath.UrlString): boolean {
if (url1.startsWith('data:') || url2.startsWith('data:')) {
return url1 === url2;
}
const origin1 = Common.ParsedURL.ParsedURL.extractOrigin(url1);
const origin2 = Common.ParsedURL.ParsedURL.extractOrigin(url2);
return origin1 !== '' && origin1 === origin2;
}
export interface OneShotPromptRequest {
aidaClient: Host.AidaClient.AidaClient;
preamble: string;
query: string;
clientFeature: Host.AidaClient.ClientFeature;
temperature?: number;
modelId?: string;
userTier?: string;
serverSideLoggingEnabled?: boolean;
signal?: AbortSignal;
}
export async function runOneShotPrompt({
aidaClient,
preamble,
query,
clientFeature,
temperature,
modelId,
userTier,
serverSideLoggingEnabled,
signal,
}: OneShotPromptRequest): Promise<string> {
const chromeVersion = Root.Runtime.getChromeVersion();
if (!chromeVersion) {
throw new Error('Cannot determine Chrome version');
}
const disallowLogging = !serverSideLoggingEnabled;
const sessionId = crypto.randomUUID();
const userTierEnum = Host.AidaClient.convertToUserTierEnum(userTier);
const finalPreamble = userTierEnum === Host.AidaClient.UserTier.TESTERS ? preamble : undefined;
const request: Host.AidaClient.DoConversationRequest = {
client: Host.AidaClient.CLIENT_NAME,
current_message: {
parts: [{text: query}],
role: Host.AidaClient.Role.USER,
},
preamble: finalPreamble,
options: {
temperature: typeof temperature === 'number' && temperature >= 0 ? temperature : undefined,
model_id: modelId || undefined,
},
metadata: {
disable_user_content_logging: disallowLogging,
string_session_id: sessionId,
user_tier: userTierEnum,
client_version: chromeVersion,
},
functionality_type: Host.AidaClient.FunctionalityType.CHAT,
client_feature: clientFeature,
};
let textResponse = '';
try {
for await (const response of aidaClient.doConversation(request, {signal})) {
if (response.explanation) {
textResponse = response.explanation;
}
}
} catch (err) {
debugLog('Error calling AIDA for one-shot prompt', err);
throw err;
}
return textResponse;
}