Files
gemini-cli/packages/core/src/code_assist/server.ts

209 lines
5.8 KiB
TypeScript
Raw Normal View History

2025-06-10 16:00:13 -07:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2025-06-30 08:47:01 -07:00
import { OAuth2Client } from 'google-auth-library';
2025-06-10 16:00:13 -07:00
import {
CodeAssistGlobalUserSettingResponse,
2025-06-10 16:00:13 -07:00
LoadCodeAssistRequest,
LoadCodeAssistResponse,
2025-06-10 16:00:13 -07:00
LongrunningOperationResponse,
OnboardUserRequest,
SetCodeAssistGlobalUserSettingRequest,
2025-06-10 16:00:13 -07:00
} from './types.js';
import {
CountTokensParameters,
CountTokensResponse,
EmbedContentParameters,
EmbedContentResponse,
GenerateContentParameters,
GenerateContentResponse,
2025-06-10 16:00:13 -07:00
} from '@google/genai';
import * as readline from 'readline';
import { ContentGenerator } from '../core/contentGenerator.js';
2025-06-12 18:00:17 -07:00
import {
CaCountTokenResponse,
2025-06-18 10:29:42 -07:00
CaGenerateContentResponse,
fromCountTokenResponse,
2025-06-18 10:29:42 -07:00
fromGenerateContentResponse,
toCountTokenRequest,
toGenerateContentRequest,
2025-06-12 18:00:17 -07:00
} from './converter.js';
import { PassThrough } from 'node:stream';
2025-06-10 16:00:13 -07:00
/** HTTP options to be used in each of the requests. */
export interface HttpOptions {
/** Additional HTTP headers to be sent with the request. */
headers?: Record<string, string>;
}
2025-06-10 16:00:13 -07:00
// TODO: Use production endpoint once it supports our methods.
export const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com';
2025-06-12 18:00:17 -07:00
export const CODE_ASSIST_API_VERSION = 'v1internal';
2025-06-10 16:00:13 -07:00
2025-06-12 18:00:17 -07:00
export class CodeAssistServer implements ContentGenerator {
2025-06-10 16:00:13 -07:00
constructor(
2025-06-30 08:47:01 -07:00
readonly client: OAuth2Client,
2025-06-10 16:00:13 -07:00
readonly projectId?: string,
readonly httpOptions: HttpOptions = {},
readonly sessionId?: string,
2025-06-10 16:00:13 -07:00
) {}
async generateContentStream(
req: GenerateContentParameters,
): Promise<AsyncGenerator<GenerateContentResponse>> {
const resps = await this.requestStreamingPost<CaGenerateContentResponse>(
2025-06-10 16:00:13 -07:00
'streamGenerateContent',
toGenerateContentRequest(req, this.projectId, this.sessionId),
req.config?.abortSignal,
2025-06-10 16:00:13 -07:00
);
return (async function* (): AsyncGenerator<GenerateContentResponse> {
for await (const resp of resps) {
2025-06-18 10:29:42 -07:00
yield fromGenerateContentResponse(resp);
}
})();
2025-06-10 16:00:13 -07:00
}
async generateContent(
req: GenerateContentParameters,
): Promise<GenerateContentResponse> {
const resp = await this.requestPost<CaGenerateContentResponse>(
2025-06-10 16:00:13 -07:00
'generateContent',
toGenerateContentRequest(req, this.projectId, this.sessionId),
req.config?.abortSignal,
2025-06-10 16:00:13 -07:00
);
2025-06-18 10:29:42 -07:00
return fromGenerateContentResponse(resp);
2025-06-10 16:00:13 -07:00
}
async onboardUser(
req: OnboardUserRequest,
): Promise<LongrunningOperationResponse> {
return await this.requestPost<LongrunningOperationResponse>(
2025-06-10 16:00:13 -07:00
'onboardUser',
req,
);
}
async loadCodeAssist(
req: LoadCodeAssistRequest,
): Promise<LoadCodeAssistResponse> {
return await this.requestPost<LoadCodeAssistResponse>(
2025-06-10 16:00:13 -07:00
'loadCodeAssist',
req,
);
}
async getCodeAssistGlobalUserSetting(): Promise<CodeAssistGlobalUserSettingResponse> {
return await this.requestGet<CodeAssistGlobalUserSettingResponse>(
'getCodeAssistGlobalUserSetting',
);
}
async setCodeAssistGlobalUserSetting(
req: SetCodeAssistGlobalUserSettingRequest,
): Promise<CodeAssistGlobalUserSettingResponse> {
return await this.requestPost<CodeAssistGlobalUserSettingResponse>(
'setCodeAssistGlobalUserSetting',
req,
);
}
2025-06-18 10:29:42 -07:00
async countTokens(req: CountTokensParameters): Promise<CountTokensResponse> {
const resp = await this.requestPost<CaCountTokenResponse>(
2025-06-18 10:29:42 -07:00
'countTokens',
toCountTokenRequest(req),
);
return fromCountTokenResponse(resp);
2025-06-10 16:00:13 -07:00
}
async embedContent(
_req: EmbedContentParameters,
): Promise<EmbedContentResponse> {
throw Error();
}
async requestPost<T>(
method: string,
req: object,
signal?: AbortSignal,
): Promise<T> {
2025-06-30 08:47:01 -07:00
const res = await this.client.request({
url: this.getMethodUrl(method),
2025-06-10 16:00:13 -07:00
method: 'POST',
headers: {
'Content-Type': 'application/json',
...this.httpOptions.headers,
2025-06-10 16:00:13 -07:00
},
responseType: 'json',
body: JSON.stringify(req),
signal,
2025-06-10 16:00:13 -07:00
});
return res.data as T;
}
async requestGet<T>(method: string, signal?: AbortSignal): Promise<T> {
2025-06-30 08:47:01 -07:00
const res = await this.client.request({
url: this.getMethodUrl(method),
method: 'GET',
headers: {
'Content-Type': 'application/json',
...this.httpOptions.headers,
},
responseType: 'json',
signal,
});
return res.data as T;
}
async requestStreamingPost<T>(
2025-06-10 16:00:13 -07:00
method: string,
req: object,
signal?: AbortSignal,
2025-06-10 16:00:13 -07:00
): Promise<AsyncGenerator<T>> {
2025-06-30 08:47:01 -07:00
const res = await this.client.request({
url: this.getMethodUrl(method),
2025-06-10 16:00:13 -07:00
method: 'POST',
params: {
alt: 'sse',
},
headers: {
'Content-Type': 'application/json',
...this.httpOptions.headers,
},
2025-06-10 16:00:13 -07:00
responseType: 'stream',
body: JSON.stringify(req),
signal,
2025-06-10 16:00:13 -07:00
});
return (async function* (): AsyncGenerator<T> {
const rl = readline.createInterface({
input: res.data as PassThrough,
2025-06-10 16:00:13 -07:00
crlfDelay: Infinity, // Recognizes '\r\n' and '\n' as line breaks
});
let bufferedLines: string[] = [];
for await (const line of rl) {
// blank lines are used to separate JSON objects in the stream
if (line === '') {
if (bufferedLines.length === 0) {
continue; // no data to yield
}
yield JSON.parse(bufferedLines.join('\n')) as T;
bufferedLines = []; // Reset the buffer after yielding
} else if (line.startsWith('data: ')) {
bufferedLines.push(line.slice(6).trim());
} else {
throw new Error(`Unexpected line format in response: ${line}`);
}
}
})();
}
getMethodUrl(method: string): string {
const endpoint = process.env.CODE_ASSIST_ENDPOINT ?? CODE_ASSIST_ENDPOINT;
return `${endpoint}/${CODE_ASSIST_API_VERSION}:${method}`;
}
2025-06-10 16:00:13 -07:00
}