blob: 632117d938c07fbf9dc7afdcef9051f9bc5bb242 [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 {
describeWithEnvironment,
updateHostConfig,
} from '../../testing/EnvironmentHelpers.js';
import * as TextEditor from '../../ui/components/text_editor/text_editor.js';
import * as AiCodeCompletion from './ai_code_completion.js';
const DEFAULT_CURSOR_POSITION = 0;
function createCallbacks(editor: TextEditor.TextEditor.TextEditor): AiCodeCompletion.AiCodeCompletion.Callbacks {
return {
getSelectionHead: () => editor.editor.state.selection.main.head,
getCompletionHint: () => editor.editor.plugin(TextEditor.Config.showCompletionHint)?.currentHint,
setAiAutoCompletion: (args: {
text: string,
from: number,
startTime: number,
onImpression: (rpcGlobalId: Host.AidaClient.RpcGlobalId, latency: number, sampleId?: number) => void,
clearCachedRequest: () => void,
rpcGlobalId?: Host.AidaClient.RpcGlobalId,
sampleId?: number,
}|null) => editor.dispatch({effects: TextEditor.Config.setAiAutoCompleteSuggestion.of(args)}),
};
}
describeWithEnvironment('AiCodeCompletion', () => {
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
clock = sinon.useFakeTimers();
updateHostConfig({
devToolsAiCodeCompletion: {
enabled: true,
temperature: 0.5,
modelId: 'test-model',
userTier: 'BETA',
},
});
});
afterEach(() => {
clock.restore();
});
it('builds a request and calls the AIDA client on text changed', async () => {
const completeCodeResponse = {
generatedSamples: [{
generationString: 'suggestion',
sampleId: 1,
score: 1,
attributionMetadata: {
attributionAction: Host.AidaClient.RecitationAction.CITE,
citations: [{uri: 'https://example.com'}],
},
}],
metadata: {rpcGlobalId: 1},
};
const mockAidaClient = sinon.createStubInstance(Host.AidaClient.AidaClient, {
completeCode: Promise.resolve(completeCodeResponse),
});
const aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
{aidaClient: mockAidaClient},
AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE,
createCallbacks(sinon.createStubInstance(TextEditor.TextEditor.TextEditor)),
['\n'],
);
const actualResponse = await aiCodeCompletion.completeCode('prefix', 'suffix', 6);
sinon.assert.calledOnce(mockAidaClient.completeCode);
const request = mockAidaClient.completeCode.firstCall.args[0];
assert.strictEqual(request.client, 'CHROME_DEVTOOLS');
assert.strictEqual(request.prefix, '\nprefix');
assert.strictEqual(request.suffix, 'suffix');
assert.deepEqual(request.options, {
temperature: 0.5,
model_id: 'test-model',
inference_language: Host.AidaClient.AidaInferenceLanguage.JAVASCRIPT,
stop_sequences: ['\n'],
});
assert.isFalse(actualResponse.fromCache);
assert.deepEqual(actualResponse.response, completeCodeResponse);
});
it('caches suggestions from AIDA', async () => {
const mockAidaClient = sinon.createStubInstance(Host.AidaClient.AidaClient, {
completeCode: Promise.resolve({
generatedSamples: [{
generationString: 'suggestion',
sampleId: 1,
score: 1,
}],
metadata: {
rpcGlobalId: 1,
},
}),
});
const aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
{aidaClient: mockAidaClient},
AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE,
createCallbacks(sinon.createStubInstance(TextEditor.TextEditor.TextEditor)),
);
await aiCodeCompletion.completeCode('prefix', 'suffix', DEFAULT_CURSOR_POSITION);
await aiCodeCompletion.completeCode('prefix', 'suffix', DEFAULT_CURSOR_POSITION);
sinon.assert.calledOnce(mockAidaClient.completeCode);
});
it('does not use cache for different requests', async () => {
const mockAidaClient = sinon.createStubInstance(Host.AidaClient.AidaClient, {
completeCode: Promise.resolve({
generatedSamples: [{
generationString: 'suggestion',
sampleId: 1,
score: 1,
}],
metadata: {},
}),
});
const aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
{aidaClient: mockAidaClient},
AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE,
createCallbacks(sinon.createStubInstance(TextEditor.TextEditor.TextEditor)),
);
await aiCodeCompletion.completeCode('prefix', 'suffix', DEFAULT_CURSOR_POSITION);
await aiCodeCompletion.completeCode('prefix re', 'suffix', DEFAULT_CURSOR_POSITION);
sinon.assert.calledTwice(mockAidaClient.completeCode);
});
it('does not use cache for different suffix', async () => {
const mockAidaClient = sinon.createStubInstance(Host.AidaClient.AidaClient, {
completeCode: Promise.resolve({
generatedSamples: [{
generationString: 'suggestion',
sampleId: 1,
score: 1,
}],
metadata: {},
}),
});
const aiCodeCompletion = new AiCodeCompletion.AiCodeCompletion.AiCodeCompletion(
{aidaClient: mockAidaClient},
AiCodeCompletion.AiCodeCompletion.ContextFlavor.CONSOLE,
createCallbacks(sinon.createStubInstance(TextEditor.TextEditor.TextEditor)),
);
await aiCodeCompletion.completeCode('prefix', 'suffix', DEFAULT_CURSOR_POSITION);
await aiCodeCompletion.completeCode('prefix', 'suffixes', DEFAULT_CURSOR_POSITION);
sinon.assert.calledTwice(mockAidaClient.completeCode);
});
});