豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit e47cf44

Browse files
Samiya CaurDevtools-frontend LUCI CQ
authored andcommitted
Add code for creating request and calling generateCode
Also adds new host configuration for AI code completion and UMA metrics for impression and acceptance Bug: 430199612 Change-Id: I33c55140e45ba1fa3c05e8ce1e71a27dfa913eec Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7106218 Commit-Queue: Ergün Erdoğmuş <ergunsh@chromium.org> Reviewed-by: Ergün Erdoğmuş <ergunsh@chromium.org> Auto-Submit: Samiya Caur <samiyac@chromium.org> Commit-Queue: Samiya Caur <samiyac@chromium.org>
1 parent d31e1d7 commit e47cf44

File tree

9 files changed

+327
-1
lines changed

9 files changed

+327
-1
lines changed

front_end/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ group("unittests") {
128128
"entrypoints/wasmparser_worker:unittests",
129129
"models/ai_assistance:unittests",
130130
"models/ai_code_completion:unittests",
131+
"models/ai_code_generation:unittests",
131132
"models/autofill_manager:unittests",
132133
"models/badges:unittests",
133134
"models/bindings:unittests",

front_end/core/host/AidaClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ export interface AidaRegisterClientEvent {
342342
disable_user_content_logging: boolean;
343343
do_conversation_client_event?: DoConversationClientEvent;
344344
complete_code_client_event?: {user_acceptance: UserAcceptance}|{user_impression: UserImpression};
345+
generate_code_client_event?: {user_acceptance: UserAcceptance}|{user_impression: UserImpression};
345346
}
346347
/* eslint-enable @typescript-eslint/naming-convention */
347348

front_end/core/host/UserMetrics.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,9 @@ export enum Action {
527527
InsightTeaserGenerationCompleted = 192,
528528
InsightTeaserGenerationAborted = 193,
529529
InsightTeaserGenerationErrored = 194,
530-
MAX_VALUE = 195,
530+
AiCodeGenerationSuggestionDisplayed = 195,
531+
AiCodeGenerationSuggestionAccepted = 196,
532+
MAX_VALUE = 197,
531533
/* eslint-enable @typescript-eslint/naming-convention */
532534
}
533535

front_end/core/root/Runtime.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,13 @@ export interface HostConfigAiCodeCompletion {
422422
userTier: string;
423423
}
424424

425+
export interface HostConfigAiCodeGeneration {
426+
modelId: string;
427+
temperature: number;
428+
enabled: boolean;
429+
userTier: string;
430+
}
431+
425432
export interface HostConfigDeepLinksViaExtensibilityApi {
426433
enabled: boolean;
427434
}
@@ -556,6 +563,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
556563
devToolsAiAssistanceFileAgent: HostConfigAiAssistanceFileAgent,
557564
devToolsAiAssistancePerformanceAgent: HostConfigAiAssistancePerformanceAgent,
558565
devToolsAiCodeCompletion: HostConfigAiCodeCompletion,
566+
devToolsAiCodeGeneration: HostConfigAiCodeGeneration,
559567
devToolsVeLogging: HostConfigVeLogging,
560568
devToolsWellKnown: HostConfigWellKnown,
561569
devToolsPrivacyUI: HostConfigPrivacyUI,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2025 The Chromium Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as Host from '../../core/host/host.js';
6+
import {
7+
describeWithEnvironment,
8+
updateHostConfig,
9+
} from '../../testing/EnvironmentHelpers.js';
10+
11+
import * as AiCodeGeneration from './ai_code_generation.js';
12+
13+
describeWithEnvironment('AiCodeGeneration', () => {
14+
beforeEach(() => {
15+
updateHostConfig({
16+
devToolsAiCodeGeneration: {
17+
enabled: true,
18+
temperature: 0.5,
19+
modelId: 'test-model',
20+
userTier: 'BETA',
21+
},
22+
});
23+
});
24+
25+
it('builds a request and calls the AIDA client', async () => {
26+
const mockAidaClient = sinon.createStubInstance(Host.AidaClient.AidaClient, {
27+
generateCode: Promise.resolve(null),
28+
});
29+
const aiCodeGeneration = new AiCodeGeneration.AiCodeGeneration.AiCodeGeneration(
30+
{aidaClient: mockAidaClient},
31+
);
32+
33+
await aiCodeGeneration.generateCode('prompt', 'preamble');
34+
35+
sinon.assert.calledOnce(mockAidaClient.generateCode);
36+
const request = mockAidaClient.generateCode.firstCall.args[0];
37+
assert.strictEqual(request.client, 'CHROME_DEVTOOLS');
38+
assert.strictEqual(request.preamble, 'preamble');
39+
assert.deepEqual(request.current_message, {
40+
parts: [{
41+
text: 'prompt',
42+
}],
43+
role: Host.AidaClient.Role.USER,
44+
});
45+
assert.strictEqual(request.use_case, Host.AidaClient.UseCase.CODE_GENERATION);
46+
assert.deepEqual(request.options, {
47+
temperature: 0.5,
48+
model_id: 'test-model',
49+
inference_language: Host.AidaClient.AidaInferenceLanguage.JAVASCRIPT,
50+
});
51+
assert.isTrue(request.metadata.disable_user_content_logging);
52+
assert.strictEqual(request.metadata.user_tier, Host.AidaClient.UserTier.BETA);
53+
});
54+
55+
it('returns the response from the AIDA client', async () => {
56+
const mockAidaClient = sinon.createStubInstance(Host.AidaClient.AidaClient, {
57+
generateCode: Promise.resolve({
58+
samples: [{
59+
generationString: 'suggestion',
60+
sampleId: 1,
61+
score: 1,
62+
}],
63+
metadata: {rpcGlobalId: 1},
64+
}),
65+
});
66+
const aiCodeGeneration = new AiCodeGeneration.AiCodeGeneration.AiCodeGeneration(
67+
{aidaClient: mockAidaClient},
68+
);
69+
70+
const response = await aiCodeGeneration.generateCode('prompt', 'preamble');
71+
72+
assert.deepEqual(response, {
73+
samples: [{
74+
generationString: 'suggestion',
75+
sampleId: 1,
76+
score: 1,
77+
}],
78+
metadata: {rpcGlobalId: 1},
79+
});
80+
});
81+
});
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright 2025 The Chromium Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import * as Host from '../../core/host/host.js';
6+
import * as Root from '../../core/root/root.js';
7+
8+
import {debugLog} from './debug.js';
9+
10+
export const basePreamble =
11+
`You are a highly skilled senior software engineer with deep expertise across multiple web technologies and programming languages, including JavaScript, TypeScript, HTML, and CSS.
12+
Your role is to act as an expert pair programmer within the Chrome DevTools environment.
13+
14+
**Core Directives (Adhere to these strictly):**
15+
16+
1. **Language and Quality:**
17+
* Generate code that is modern, efficient, and idiomatic for the inferred language (e.g., modern JavaScript/ES6+, semantic HTML5, efficient CSS).
18+
* Where appropriate, include basic error handling (e.g., for API calls).
19+
`;
20+
21+
export const additionalContextForConsole = `
22+
You are operating within the execution environment of the Chrome DevTools Console.
23+
The console has direct access to the inspected page's \`window\` and \`document\`.
24+
25+
* **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.
26+
`;
27+
28+
interface Options {
29+
aidaClient: Host.AidaClient.AidaClient;
30+
serverSideLoggingEnabled?: boolean;
31+
confirmSideEffectForTest?: typeof Promise.withResolvers;
32+
}
33+
34+
interface RequestOptions {
35+
temperature?: number;
36+
modelId?: string;
37+
}
38+
39+
/**
40+
* The AiCodeGeneration class is responsible for fetching generated code suggestions
41+
* from the AIDA backend.
42+
*/
43+
export class AiCodeGeneration {
44+
readonly #sessionId: string = crypto.randomUUID();
45+
readonly #aidaClient: Host.AidaClient.AidaClient;
46+
readonly #serverSideLoggingEnabled: boolean;
47+
48+
constructor(opts: Options) {
49+
this.#aidaClient = opts.aidaClient;
50+
this.#serverSideLoggingEnabled = opts.serverSideLoggingEnabled ?? false;
51+
}
52+
53+
#buildRequest(
54+
prompt: string,
55+
preamble: string,
56+
inferenceLanguage: Host.AidaClient.AidaInferenceLanguage = Host.AidaClient.AidaInferenceLanguage.JAVASCRIPT,
57+
): Host.AidaClient.GenerateCodeRequest {
58+
const userTier = Host.AidaClient.convertToUserTierEnum(this.#userTier);
59+
function validTemperature(temperature: number|undefined): number|undefined {
60+
return typeof temperature === 'number' && temperature >= 0 ? temperature : undefined;
61+
}
62+
return {
63+
client: Host.AidaClient.CLIENT_NAME,
64+
preamble,
65+
current_message: {
66+
parts: [{
67+
text: prompt,
68+
}],
69+
role: Host.AidaClient.Role.USER,
70+
},
71+
use_case: Host.AidaClient.UseCase.CODE_GENERATION,
72+
options: {
73+
inference_language: inferenceLanguage,
74+
temperature: validTemperature(this.#options.temperature),
75+
model_id: this.#options.modelId || undefined,
76+
},
77+
metadata: {
78+
disable_user_content_logging: !(this.#serverSideLoggingEnabled ?? false),
79+
string_session_id: this.#sessionId,
80+
user_tier: userTier,
81+
client_version: Root.Runtime.getChromeVersion(),
82+
},
83+
};
84+
}
85+
86+
get #userTier(): string|undefined {
87+
return Root.Runtime.hostConfig.devToolsAiCodeGeneration?.userTier;
88+
}
89+
90+
get #options(): RequestOptions {
91+
const temperature = Root.Runtime.hostConfig.devToolsAiCodeGeneration?.temperature;
92+
const modelId = Root.Runtime.hostConfig.devToolsAiCodeGeneration?.modelId;
93+
94+
return {
95+
temperature,
96+
modelId,
97+
};
98+
}
99+
100+
registerUserImpression(rpcGlobalId: Host.AidaClient.RpcGlobalId, latency: number, sampleId?: number): void {
101+
const seconds = Math.floor(latency / 1_000);
102+
const remainingMs = latency % 1_000;
103+
const nanos = Math.floor(remainingMs * 1_000_000);
104+
105+
void this.#aidaClient.registerClientEvent({
106+
corresponding_aida_rpc_global_id: rpcGlobalId,
107+
disable_user_content_logging: true,
108+
generate_code_client_event: {
109+
user_impression: {
110+
sample: {
111+
sample_id: sampleId,
112+
},
113+
latency: {
114+
duration: {
115+
seconds,
116+
nanos,
117+
},
118+
}
119+
},
120+
},
121+
});
122+
debugLog('Registered user impression with latency {seconds:', seconds, ', nanos:', nanos, '}');
123+
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeGenerationSuggestionDisplayed);
124+
}
125+
126+
registerUserAcceptance(rpcGlobalId: Host.AidaClient.RpcGlobalId, sampleId?: number): void {
127+
void this.#aidaClient.registerClientEvent({
128+
corresponding_aida_rpc_global_id: rpcGlobalId,
129+
disable_user_content_logging: true,
130+
generate_code_client_event: {
131+
user_acceptance: {
132+
sample: {
133+
sample_id: sampleId,
134+
}
135+
},
136+
},
137+
});
138+
debugLog('Registered user acceptance');
139+
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiCodeGenerationSuggestionAccepted);
140+
}
141+
142+
async generateCode(prompt: string, preamble: string, inferenceLanguage?: Host.AidaClient.AidaInferenceLanguage):
143+
Promise<Host.AidaClient.GenerateCodeResponse|null> {
144+
const request = this.#buildRequest(prompt, preamble, inferenceLanguage);
145+
const response = await this.#aidaClient.generateCode(request);
146+
147+
debugLog({request, response});
148+
149+
return response;
150+
}
151+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2025 The Chromium Authors
2+
# Use of this source code is governed by a BSD-style license that can be
3+
# found in the LICENSE file.
4+
5+
import("../../../scripts/build/ninja/devtools_entrypoint.gni")
6+
import("../../../scripts/build/ninja/devtools_module.gni")
7+
import("../../../scripts/build/typescript/typescript.gni")
8+
import("../visibility.gni")
9+
10+
devtools_module("ai_code_generation") {
11+
sources = [
12+
"AiCodeGeneration.ts",
13+
"debug.ts",
14+
]
15+
16+
deps = [
17+
"../../core/host:bundle",
18+
"../../core/root:bundle",
19+
]
20+
}
21+
22+
devtools_entrypoint("bundle") {
23+
entrypoint = "ai_code_generation.ts"
24+
25+
deps = [ ":ai_code_generation" ]
26+
27+
visibility = [
28+
":*",
29+
"../../panels/console/*",
30+
"../../panels/sources/*",
31+
"../../ui/components/text_editor/*",
32+
]
33+
34+
visibility += devtools_models_visibility
35+
}
36+
37+
ts_library("unittests") {
38+
testonly = true
39+
40+
sources = [ "AiCodeGeneration.test.ts" ]
41+
42+
deps = [
43+
":bundle",
44+
"../../testing",
45+
]
46+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright 2025 The Chromium Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
export * from './debug.js';
6+
export * as AiCodeGeneration from './AiCodeGeneration.js';
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2025 The Chromium Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
/**
6+
* @file Local debugging utilities.
7+
*/
8+
9+
export function isDebugMode(): boolean {
10+
return Boolean(localStorage.getItem('debugAiCodeGenerationEnabled'));
11+
}
12+
13+
export function debugLog(...log: unknown[]): void {
14+
if (!isDebugMode()) {
15+
return;
16+
}
17+
18+
// eslint-disable-next-line no-console
19+
console.log(...log);
20+
}
21+
22+
function setDebugAiCodeGenerationEnabled(enabled: boolean): void {
23+
if (enabled) {
24+
localStorage.setItem('debugAiCodeGenerationEnabled', 'true');
25+
} else {
26+
localStorage.removeItem('debugAiCodeGenerationEnabled');
27+
}
28+
}
29+
// @ts-expect-error
30+
globalThis.setDebugAiCodeGenerationEnabled = setDebugAiCodeGenerationEnabled;

0 commit comments

Comments
 (0)