blob: 64233b40f75b4ba6d7ee7be04fcb6575dd5b5d9d [file] [log] [blame]
// 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 Host from '../../core/host/host.js';
import * as Root from '../../core/root/root.js';
import {debugLog} from './debug.js';
export const basePreamble =
`You are a highly skilled senior software engineer with deep expertise across multiple web technologies and programming languages, including JavaScript, TypeScript, HTML, and CSS.
Your role is to act as an expert pair programmer within the Chrome DevTools environment.
**Core Directives (Adhere to these strictly):**
1. **Language and Quality:**
* Generate code that is modern, efficient, and idiomatic for the inferred language (e.g., modern JavaScript/ES6+, semantic HTML5, efficient CSS).
* Where appropriate, include basic error handling (e.g., for API calls).
* Determine the programming language from the user's prompt.
2. **Output Format (Strict):**
* **Return ONLY code blocks.** * Do NOT include any introductory text, explanations, or concluding remarks.
* Do NOT provide step-by-step guides or descriptions of how the code works.
* Inline comments within the code are permitted and encouraged for clarity.
`;
export const additionalContextForConsole = `
You are operating within the execution environment of the Chrome DevTools Console.
The console has direct access to the inspected page's \`window\` and \`document\`.
* **Utilize Console Utilities:** You have access to the Console Utilities API. You **should** use these helper functions and variables when they are the most direct way to accomplish the user's goal.
`;
interface Options {
aidaClient: Host.AidaClient.AidaClient;
serverSideLoggingEnabled?: boolean;
confirmSideEffectForTest?: typeof Promise.withResolvers;
}
interface RequestOptions {
temperature?: number;
modelId?: string;
}
/**
* The AiCodeGeneration class is responsible for fetching generated code suggestions
* from the AIDA backend.
*/
export class AiCodeGeneration {
readonly #sessionId: string = crypto.randomUUID();
readonly #aidaClient: Host.AidaClient.AidaClient;
readonly #serverSideLoggingEnabled: boolean;
constructor(opts: Options) {
this.#aidaClient = opts.aidaClient;
this.#serverSideLoggingEnabled = opts.serverSideLoggingEnabled ?? false;
}
#buildRequest(
prompt: string,
preamble: string,
inferenceLanguage: Host.AidaClient.AidaInferenceLanguage = Host.AidaClient.AidaInferenceLanguage.JAVASCRIPT,
): Host.AidaClient.GenerateCodeRequest {
const userTier = Host.AidaClient.convertToUserTierEnum(this.#userTier);
function validTemperature(temperature: number|undefined): number|undefined {
return typeof temperature === 'number' && temperature >= 0 ? temperature : undefined;
}
// Workaround: Combine preamble and target language into the main prompt to enforce instructions.
// The API and model ignores system-level instructions provided in the preamble field of the request.
prompt = preamble + prompt + '\n**Target Language:** ' + inferenceLanguage;
return {
client: Host.AidaClient.CLIENT_NAME,
preamble,
current_message: {
parts: [{
text: prompt,
}],
role: Host.AidaClient.Role.USER,
},
use_case: Host.AidaClient.UseCase.CODE_GENERATION,
options: {
temperature: validTemperature(this.#options.temperature),
model_id: this.#options.modelId || undefined,
},
metadata: {
disable_user_content_logging: !(this.#serverSideLoggingEnabled ?? false),
string_session_id: this.#sessionId,
user_tier: userTier,
client_version: Root.Runtime.getChromeVersion(),
},
};
}
get #userTier(): string|undefined {
return Root.Runtime.hostConfig.devToolsAiCodeGeneration?.userTier;
}
get #options(): RequestOptions {
const temperature = Root.Runtime.hostConfig.devToolsAiCodeGeneration?.temperature;
const modelId = Root.Runtime.hostConfig.devToolsAiCodeGeneration?.modelId;
return {
temperature,
modelId,
};
}
registerUserImpression(rpcGlobalId: Host.AidaClient.RpcGlobalId, latency: number, sampleId?: number): void {
const seconds = Math.floor(latency / 1_000);
const remainingMs = latency % 1_000;
const nanos = Math.floor(remainingMs * 1_000_000);
void this.#aidaClient.registerClientEvent({
corresponding_aida_rpc_global_id: rpcGlobalId,
disable_user_content_logging: true,
generate_code_client_event: {
user_impression: {
sample: {
sample_id: sampleId,
},
latency: {
duration: {
seconds,
nanos,
},
}
},
},
});
debugLog('Registered user impression with latency {seconds:', seconds, ', nanos:', nanos, '}');
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeGenerationSuggestionDisplayed);
}
registerUserAcceptance(rpcGlobalId: Host.AidaClient.RpcGlobalId, sampleId?: number): void {
void this.#aidaClient.registerClientEvent({
corresponding_aida_rpc_global_id: rpcGlobalId,
disable_user_content_logging: true,
generate_code_client_event: {
user_acceptance: {
sample: {
sample_id: sampleId,
}
},
},
});
debugLog('Registered user acceptance');
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeGenerationSuggestionAccepted);
}
async generateCode(
prompt: string, preamble: string, inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage,
options?: {signal?: AbortSignal}): Promise<Host.AidaClient.GenerateCodeResponse|null> {
const request = this.#buildRequest(prompt, preamble, inferenceLanguage);
const response = await this.#aidaClient.generateCode(request, options);
debugLog({request, response});
return response;
}
static isAiCodeGenerationEnabled(locale: string): boolean {
if (!locale.startsWith('en-')) {
return false;
}
const aidaAvailability = Root.Runtime.hostConfig.aidaAvailability;
if (!aidaAvailability || aidaAvailability.blockedByGeo || aidaAvailability.blockedByAge ||
aidaAvailability.blockedByEnterprisePolicy) {
return false;
}
return Boolean(aidaAvailability.enabled && Root.Runtime.hostConfig.devToolsAiCodeGeneration?.enabled);
}
}