2023-05-24 16:21:38 +00:00
|
|
|
import { EventEmitter } from "events";
|
2023-06-02 03:58:34 +00:00
|
|
|
import { v4 as uuid } from "uuid";
|
2023-06-06 13:29:04 +00:00
|
|
|
import deepEqual from "deep-equal";
|
2023-08-11 11:39:17 +00:00
|
|
|
import { deepmerge } from "deepmerge-ts";
|
|
|
|
|
import { getProperty, setProperty, deleteProperty } from "dot-prop";
|
2023-06-15 15:53:21 +00:00
|
|
|
import { TabbyApi, CancelablePromise } from "./generated";
|
|
|
|
|
import { cancelable, splitLines, isBlank } from "./utils";
|
|
|
|
|
import {
|
|
|
|
|
Agent,
|
|
|
|
|
AgentStatus,
|
2023-08-17 14:28:41 +00:00
|
|
|
AgentIssue,
|
2023-06-15 15:53:21 +00:00
|
|
|
AgentEvent,
|
|
|
|
|
AgentInitOptions,
|
2023-08-17 14:28:41 +00:00
|
|
|
ServerHealthState,
|
2023-06-15 15:53:21 +00:00
|
|
|
CompletionRequest,
|
|
|
|
|
CompletionResponse,
|
|
|
|
|
LogEventRequest,
|
|
|
|
|
} from "./Agent";
|
|
|
|
|
import { Auth } from "./Auth";
|
2023-08-11 11:39:17 +00:00
|
|
|
import { AgentConfig, PartialAgentConfig, defaultAgentConfig, userAgentConfig } from "./AgentConfig";
|
2023-06-06 13:29:04 +00:00
|
|
|
import { CompletionCache } from "./CompletionCache";
|
2023-09-01 05:30:53 +00:00
|
|
|
import { CompletionDebounce } from "./CompletionDebounce";
|
2023-06-15 15:53:21 +00:00
|
|
|
import { DataStore } from "./dataStore";
|
2023-07-13 08:31:20 +00:00
|
|
|
import { postprocess, preCacheProcess } from "./postprocess";
|
2023-06-06 14:25:31 +00:00
|
|
|
import { rootLogger, allLoggers } from "./logger";
|
2023-06-16 08:58:50 +00:00
|
|
|
import { AnonymousUsageLogger } from "./AnonymousUsageLogger";
|
2023-08-17 14:28:41 +00:00
|
|
|
import { ResponseStats, completionResponseTimeStatsStrategy } from "./ResponseStats";
|
2023-05-24 01:50:57 +00:00
|
|
|
|
2023-06-15 15:53:21 +00:00
|
|
|
/**
|
|
|
|
|
* Different from AgentInitOptions or AgentConfig, this may contain non-serializable objects,
|
|
|
|
|
* so it is not suitable for cli, but only used when imported as module by other js project.
|
|
|
|
|
*/
|
|
|
|
|
export type TabbyAgentOptions = {
|
2023-08-11 11:39:17 +00:00
|
|
|
dataStore?: DataStore;
|
2023-06-15 15:53:21 +00:00
|
|
|
};
|
|
|
|
|
|
2023-05-24 01:50:57 +00:00
|
|
|
export class TabbyAgent extends EventEmitter implements Agent {
|
2023-06-06 14:25:31 +00:00
|
|
|
private readonly logger = rootLogger.child({ component: "TabbyAgent" });
|
2023-06-16 08:58:50 +00:00
|
|
|
private anonymousUsageLogger: AnonymousUsageLogger;
|
2023-06-06 13:29:04 +00:00
|
|
|
private config: AgentConfig = defaultAgentConfig;
|
2023-09-10 02:25:23 +00:00
|
|
|
private userConfig: PartialAgentConfig = {}; // config from `~/.tabby-client/agent/config.toml`
|
2023-08-11 11:39:17 +00:00
|
|
|
private clientConfig: PartialAgentConfig = {}; // config from `initialize` and `updateConfig` method
|
2023-06-15 15:53:21 +00:00
|
|
|
private status: AgentStatus = "notInitialized";
|
2023-08-17 14:28:41 +00:00
|
|
|
private issues: AgentIssue["name"][] = [];
|
|
|
|
|
private serverHealthState: ServerHealthState | null = null;
|
2023-05-24 01:50:57 +00:00
|
|
|
private api: TabbyApi;
|
2023-06-15 15:53:21 +00:00
|
|
|
private auth: Auth;
|
|
|
|
|
private dataStore: DataStore | null = null;
|
2023-06-06 13:29:04 +00:00
|
|
|
private completionCache: CompletionCache = new CompletionCache();
|
2023-09-01 05:30:53 +00:00
|
|
|
private CompletionDebounce: CompletionDebounce = new CompletionDebounce();
|
2023-06-15 15:53:21 +00:00
|
|
|
static readonly tryConnectInterval = 1000 * 30; // 30s
|
|
|
|
|
private tryingConnectTimer: ReturnType<typeof setInterval> | null = null;
|
2023-08-17 14:28:41 +00:00
|
|
|
private completionResponseStats: ResponseStats = new ResponseStats(completionResponseTimeStatsStrategy);
|
2023-05-24 01:50:57 +00:00
|
|
|
|
2023-06-15 15:53:21 +00:00
|
|
|
private constructor() {
|
2023-05-24 01:50:57 +00:00
|
|
|
super();
|
2023-06-15 15:53:21 +00:00
|
|
|
|
|
|
|
|
this.tryingConnectTimer = setInterval(async () => {
|
|
|
|
|
if (this.status === "disconnected") {
|
|
|
|
|
this.logger.debug("Trying to connect...");
|
|
|
|
|
await this.healthCheck();
|
|
|
|
|
}
|
|
|
|
|
}, TabbyAgent.tryConnectInterval);
|
2023-08-17 14:28:41 +00:00
|
|
|
|
|
|
|
|
this.completionResponseStats.on("healthy", () => {
|
|
|
|
|
this.popIssue("slowCompletionResponseTime");
|
|
|
|
|
this.popIssue("highCompletionTimeoutRate");
|
|
|
|
|
});
|
|
|
|
|
this.completionResponseStats.on("highTimeoutRate", () => {
|
|
|
|
|
if (this.status === "ready" || this.status === "issuesExist") {
|
|
|
|
|
this.popIssue("slowCompletionResponseTime");
|
|
|
|
|
this.pushIssue("highCompletionTimeoutRate");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.completionResponseStats.on("slowResponseTime", () => {
|
|
|
|
|
if (this.status === "ready" || this.status === "issuesExist") {
|
|
|
|
|
this.popIssue("highCompletionTimeoutRate");
|
|
|
|
|
this.pushIssue("slowCompletionResponseTime");
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-06-06 13:29:04 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 11:39:17 +00:00
|
|
|
static async create(options?: TabbyAgentOptions): Promise<TabbyAgent> {
|
2023-06-15 15:53:21 +00:00
|
|
|
const agent = new TabbyAgent();
|
|
|
|
|
agent.dataStore = options?.dataStore;
|
2023-06-16 08:58:50 +00:00
|
|
|
agent.anonymousUsageLogger = await AnonymousUsageLogger.create({ dataStore: options?.dataStore });
|
2023-06-15 15:53:21 +00:00
|
|
|
return agent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async applyConfig() {
|
2023-08-11 11:39:17 +00:00
|
|
|
this.config = deepmerge(defaultAgentConfig, this.userConfig, this.clientConfig);
|
2023-06-06 14:25:31 +00:00
|
|
|
allLoggers.forEach((logger) => (logger.level = this.config.logs.level));
|
2023-06-16 08:58:50 +00:00
|
|
|
this.anonymousUsageLogger.disabled = this.config.anonymousUsageTracking.disable;
|
2023-08-11 11:39:17 +00:00
|
|
|
if (this.config.server.requestHeaders["Authorization"] === undefined) {
|
|
|
|
|
if (this.config.server.endpoint !== this.auth?.endpoint) {
|
|
|
|
|
this.auth = await Auth.create({ endpoint: this.config.server.endpoint, dataStore: this.dataStore });
|
|
|
|
|
this.auth.on("updated", this.setupApi.bind(this));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.auth = null;
|
2023-06-15 15:53:21 +00:00
|
|
|
}
|
2023-06-24 13:33:33 +00:00
|
|
|
await this.setupApi();
|
2023-05-24 01:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2023-06-24 13:33:33 +00:00
|
|
|
private async setupApi() {
|
|
|
|
|
this.api = new TabbyApi({
|
|
|
|
|
BASE: this.config.server.endpoint.replace(/\/+$/, ""), // remove trailing slash
|
|
|
|
|
TOKEN: this.auth?.token,
|
2023-08-11 11:39:17 +00:00
|
|
|
HEADERS: this.config.server.requestHeaders,
|
2023-06-24 13:33:33 +00:00
|
|
|
});
|
2023-06-15 15:53:21 +00:00
|
|
|
await this.healthCheck();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private changeStatus(status: AgentStatus) {
|
2023-05-24 01:50:57 +00:00
|
|
|
if (this.status != status) {
|
|
|
|
|
this.status = status;
|
|
|
|
|
const event: AgentEvent = { event: "statusChanged", status };
|
2023-06-06 14:25:31 +00:00
|
|
|
this.logger.debug({ event }, "Status changed");
|
2023-05-24 16:21:38 +00:00
|
|
|
super.emit("statusChanged", event);
|
2023-08-11 11:39:17 +00:00
|
|
|
if (this.status === "unauthorized") {
|
|
|
|
|
this.emitAuthRequired();
|
|
|
|
|
}
|
2023-05-24 01:50:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-17 14:28:41 +00:00
|
|
|
private issueWithDetails(issue: AgentIssue["name"]): AgentIssue {
|
|
|
|
|
switch (issue) {
|
|
|
|
|
case "highCompletionTimeoutRate":
|
|
|
|
|
return {
|
|
|
|
|
name: "highCompletionTimeoutRate",
|
|
|
|
|
completionResponseStats: this.completionResponseStats.stats(),
|
|
|
|
|
};
|
|
|
|
|
case "slowCompletionResponseTime":
|
|
|
|
|
return {
|
|
|
|
|
name: "slowCompletionResponseTime",
|
|
|
|
|
completionResponseStats: this.completionResponseStats.stats(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private pushIssue(issue: AgentIssue["name"]) {
|
|
|
|
|
if (this.issues.indexOf(issue) === -1) {
|
|
|
|
|
this.issues.push(issue);
|
|
|
|
|
this.changeStatus("issuesExist");
|
|
|
|
|
const event: AgentEvent = { event: "newIssue", issue: this.issueWithDetails(issue) };
|
|
|
|
|
this.logger.debug({ event }, "New issue");
|
|
|
|
|
super.emit("newIssue", event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private popIssue(issue: AgentIssue["name"]) {
|
|
|
|
|
this.issues = this.issues.filter((i) => i !== issue);
|
|
|
|
|
if (this.issues.length === 0 && this.status === "issuesExist") {
|
|
|
|
|
this.changeStatus("ready");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-11 11:39:17 +00:00
|
|
|
private emitAuthRequired() {
|
|
|
|
|
const event: AgentEvent = { event: "authRequired", server: this.config.server };
|
|
|
|
|
super.emit("authRequired", event);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 14:25:31 +00:00
|
|
|
private callApi<Request, Response>(
|
|
|
|
|
api: (request: Request) => CancelablePromise<Response>,
|
2023-07-13 08:31:20 +00:00
|
|
|
request: Request,
|
2023-08-17 14:28:41 +00:00
|
|
|
options: { timeout?: number } = { timeout: this.config.server.requestTimeout },
|
2023-06-06 14:25:31 +00:00
|
|
|
): CancelablePromise<Response> {
|
2023-08-17 14:28:41 +00:00
|
|
|
return new CancelablePromise((resolve, reject, onCancel) => {
|
|
|
|
|
const requestId = uuid();
|
|
|
|
|
this.logger.debug({ requestId, api: api.name, request }, "API request");
|
|
|
|
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
|
let timeoutCancelled = false;
|
|
|
|
|
const apiRequest = api.call(this.api.v1, request);
|
|
|
|
|
const requestStartedAt = performance.now();
|
|
|
|
|
apiRequest
|
2023-06-06 14:25:31 +00:00
|
|
|
.then((response: Response) => {
|
2023-08-17 14:28:41 +00:00
|
|
|
this.logger.debug({ requestId, api: api.name, response }, "API response");
|
|
|
|
|
if (this.status !== "issuesExist") {
|
|
|
|
|
this.changeStatus("ready");
|
|
|
|
|
}
|
|
|
|
|
if (api.name === "completion") {
|
|
|
|
|
this.completionResponseStats.push({
|
|
|
|
|
name: api.name,
|
|
|
|
|
status: 200,
|
|
|
|
|
responseTime: performance.now() - requestStartedAt,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (timeout) {
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
}
|
|
|
|
|
resolve(response);
|
2023-05-24 01:50:57 +00:00
|
|
|
})
|
2023-06-15 15:53:21 +00:00
|
|
|
.catch((error) => {
|
2023-08-17 14:28:41 +00:00
|
|
|
if (
|
|
|
|
|
(!!error.isCancelled && timeoutCancelled) ||
|
|
|
|
|
(!error.isCancelled && error.code === "ECONNABORTED") ||
|
|
|
|
|
(error.name === "ApiError" && [408, 499].indexOf(error.status) !== -1)
|
|
|
|
|
) {
|
|
|
|
|
error.isTimeoutError = true;
|
|
|
|
|
this.logger.debug({ requestId, api: api.name, error }, "API request timeout");
|
|
|
|
|
} else if (!!error.isCancelled) {
|
|
|
|
|
this.logger.debug({ requestId, api: api.name, error }, "API request cancelled");
|
2023-08-11 11:39:17 +00:00
|
|
|
} else if (
|
|
|
|
|
error.name === "ApiError" &&
|
|
|
|
|
[401, 403, 405].indexOf(error.status) !== -1 &&
|
|
|
|
|
new URL(this.config.server.endpoint).hostname.endsWith("app.tabbyml.com") &&
|
|
|
|
|
this.config.server.requestHeaders["Authorization"] === undefined
|
|
|
|
|
) {
|
2023-08-17 14:28:41 +00:00
|
|
|
this.logger.debug({ requestId, api: api.name, error }, "API unauthorized");
|
2023-06-15 15:53:21 +00:00
|
|
|
this.changeStatus("unauthorized");
|
|
|
|
|
} else if (error.name === "ApiError") {
|
2023-08-17 14:28:41 +00:00
|
|
|
this.logger.error({ requestId, api: api.name, error }, "API error");
|
2023-06-15 15:53:21 +00:00
|
|
|
this.changeStatus("disconnected");
|
|
|
|
|
} else {
|
2023-08-17 14:28:41 +00:00
|
|
|
this.logger.error({ requestId, api: api.name, error }, "API request failed with unknown error");
|
2023-06-15 15:53:21 +00:00
|
|
|
this.changeStatus("disconnected");
|
|
|
|
|
}
|
2023-08-17 14:28:41 +00:00
|
|
|
// don't record cancelled request in stats
|
|
|
|
|
if (api.name === "completion" && (error.isTimeoutError || !error.isCancelled)) {
|
|
|
|
|
this.completionResponseStats.push({
|
|
|
|
|
name: api.name,
|
|
|
|
|
status: error.status,
|
|
|
|
|
responseTime: performance.now() - requestStartedAt,
|
|
|
|
|
error,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (timeout) {
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
}
|
|
|
|
|
reject(error);
|
|
|
|
|
});
|
|
|
|
|
// It seems that openapi-typescript-codegen does not provide timeout options passing to axios,
|
|
|
|
|
// Just use setTimeout to cancel the request manually.
|
|
|
|
|
if (options.timeout && options.timeout > 0) {
|
|
|
|
|
timeout = setTimeout(
|
|
|
|
|
() => {
|
|
|
|
|
this.logger.debug({ api: api.name, timeout: options.timeout }, "Cancel API request due to timeout");
|
|
|
|
|
timeoutCancelled = true;
|
|
|
|
|
apiRequest.cancel();
|
|
|
|
|
},
|
|
|
|
|
Math.min(options.timeout, 0x7fffffff),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
onCancel(() => {
|
|
|
|
|
if (timeout) {
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
}
|
|
|
|
|
apiRequest.cancel();
|
|
|
|
|
});
|
|
|
|
|
});
|
2023-05-24 01:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2023-06-24 13:33:33 +00:00
|
|
|
private healthCheck(): Promise<any> {
|
2023-08-17 14:28:41 +00:00
|
|
|
return this.callApi(this.api.v1.health, {})
|
|
|
|
|
.then((healthState) => {
|
|
|
|
|
this.serverHealthState = healthState;
|
2023-09-05 07:06:35 +00:00
|
|
|
if (this.status === "ready") {
|
|
|
|
|
this.anonymousUsageLogger.uniqueEvent("AgentConnected", healthState);
|
|
|
|
|
}
|
2023-08-17 14:28:41 +00:00
|
|
|
})
|
|
|
|
|
.catch(() => {});
|
2023-06-15 15:53:21 +00:00
|
|
|
}
|
|
|
|
|
|
2023-06-07 16:11:31 +00:00
|
|
|
private createSegments(request: CompletionRequest): { prefix: string; suffix: string } {
|
2023-06-22 03:01:57 +00:00
|
|
|
// max lines in prefix and suffix configurable
|
2023-09-01 05:30:53 +00:00
|
|
|
const maxPrefixLines = this.config.completion.prompt.maxPrefixLines;
|
|
|
|
|
const maxSuffixLines = this.config.completion.prompt.maxSuffixLines;
|
2023-06-02 03:58:34 +00:00
|
|
|
const prefix = request.text.slice(0, request.position);
|
2023-06-07 16:11:31 +00:00
|
|
|
const prefixLines = splitLines(prefix);
|
|
|
|
|
const suffix = request.text.slice(request.position);
|
|
|
|
|
const suffixLines = splitLines(suffix);
|
|
|
|
|
return {
|
2023-06-22 03:01:57 +00:00
|
|
|
prefix: prefixLines.slice(Math.max(prefixLines.length - maxPrefixLines, 0)).join(""),
|
|
|
|
|
suffix: suffixLines.slice(0, maxSuffixLines).join(""),
|
2023-06-07 16:11:31 +00:00
|
|
|
};
|
2023-06-02 03:58:34 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 11:39:17 +00:00
|
|
|
public async initialize(options: AgentInitOptions): Promise<boolean> {
|
2023-06-15 15:53:21 +00:00
|
|
|
if (options.client) {
|
2023-06-06 14:25:31 +00:00
|
|
|
// Client info is only used in logging for now
|
|
|
|
|
// `pino.Logger.setBindings` is not present in the browser
|
2023-06-24 13:33:33 +00:00
|
|
|
allLoggers.forEach((logger) => logger.setBindings?.({ client: options.client }));
|
2023-08-10 09:14:45 +00:00
|
|
|
this.anonymousUsageLogger.addProperties({ client: options.client });
|
2023-06-24 13:33:33 +00:00
|
|
|
}
|
2023-07-03 03:19:09 +00:00
|
|
|
if (userAgentConfig) {
|
|
|
|
|
await userAgentConfig.load();
|
|
|
|
|
this.userConfig = userAgentConfig.config;
|
|
|
|
|
userAgentConfig.on("updated", async (config) => {
|
|
|
|
|
this.userConfig = config;
|
|
|
|
|
await this.applyConfig();
|
|
|
|
|
});
|
|
|
|
|
userAgentConfig.watch();
|
|
|
|
|
}
|
2023-06-24 13:33:33 +00:00
|
|
|
if (options.config) {
|
2023-08-11 11:39:17 +00:00
|
|
|
this.clientConfig = options.config;
|
2023-06-06 14:25:31 +00:00
|
|
|
}
|
2023-06-24 13:33:33 +00:00
|
|
|
await this.applyConfig();
|
2023-08-10 09:14:45 +00:00
|
|
|
await this.anonymousUsageLogger.uniqueEvent("AgentInitialized");
|
2023-06-15 15:53:21 +00:00
|
|
|
this.logger.debug({ options }, "Initialized");
|
|
|
|
|
return this.status !== "notInitialized";
|
2023-06-06 13:29:04 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 11:39:17 +00:00
|
|
|
public async updateConfig(key: string, value: any): Promise<boolean> {
|
|
|
|
|
const current = getProperty(this.clientConfig, key);
|
|
|
|
|
if (!deepEqual(current, value)) {
|
|
|
|
|
if (value === undefined) {
|
|
|
|
|
deleteProperty(this.clientConfig, key);
|
|
|
|
|
} else {
|
|
|
|
|
setProperty(this.clientConfig, key, value);
|
|
|
|
|
}
|
|
|
|
|
const prevStatus = this.status;
|
2023-06-15 15:53:21 +00:00
|
|
|
await this.applyConfig();
|
2023-08-17 14:28:41 +00:00
|
|
|
// If server config changed, clear server health state
|
|
|
|
|
if (key.startsWith("server")) {
|
|
|
|
|
this.serverHealthState = null;
|
|
|
|
|
}
|
2023-08-11 11:39:17 +00:00
|
|
|
// If status unchanged, `authRequired` will not be emitted when `applyConfig`,
|
|
|
|
|
// so we need to emit it manually.
|
|
|
|
|
if (key.startsWith("server") && prevStatus === "unauthorized" && this.status === "unauthorized") {
|
|
|
|
|
this.emitAuthRequired();
|
|
|
|
|
}
|
2023-06-06 13:29:04 +00:00
|
|
|
const event: AgentEvent = { event: "configUpdated", config: this.config };
|
2023-06-06 14:25:31 +00:00
|
|
|
this.logger.debug({ event }, "Config updated");
|
2023-06-06 13:29:04 +00:00
|
|
|
super.emit("configUpdated", event);
|
|
|
|
|
}
|
2023-06-23 06:43:55 +00:00
|
|
|
return true;
|
2023-05-24 01:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-11 11:39:17 +00:00
|
|
|
public async clearConfig(key: string): Promise<boolean> {
|
|
|
|
|
return await this.updateConfig(key, undefined);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 13:29:04 +00:00
|
|
|
public getConfig(): AgentConfig {
|
|
|
|
|
return this.config;
|
2023-05-24 01:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2023-06-15 15:53:21 +00:00
|
|
|
public getStatus(): AgentStatus {
|
2023-05-24 16:21:38 +00:00
|
|
|
return this.status;
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-17 14:28:41 +00:00
|
|
|
public getIssues(): AgentIssue[] {
|
|
|
|
|
return this.issues.map((issue) => this.issueWithDetails(issue));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public getServerHealthState(): ServerHealthState | null {
|
|
|
|
|
return this.serverHealthState;
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-24 21:43:13 +00:00
|
|
|
public requestAuthUrl(): CancelablePromise<{ authUrl: string; code: string } | null> {
|
|
|
|
|
if (this.status === "notInitialized") {
|
|
|
|
|
return cancelable(Promise.reject("Agent is not initialized"), () => {});
|
|
|
|
|
}
|
|
|
|
|
return new CancelablePromise(async (resolve, reject, onCancel) => {
|
|
|
|
|
let request: CancelablePromise<{ authUrl: string; code: string }>;
|
|
|
|
|
onCancel(() => {
|
|
|
|
|
request?.cancel();
|
|
|
|
|
});
|
|
|
|
|
await this.healthCheck();
|
|
|
|
|
if (onCancel.isCancelled) return;
|
|
|
|
|
if (this.status === "unauthorized") {
|
|
|
|
|
request = this.auth.requestAuthUrl();
|
|
|
|
|
resolve(request);
|
|
|
|
|
} else {
|
|
|
|
|
}
|
|
|
|
|
resolve(null);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public waitForAuthToken(code: string): CancelablePromise<any> {
|
2023-06-23 06:43:55 +00:00
|
|
|
if (this.status === "notInitialized") {
|
2023-06-24 21:43:13 +00:00
|
|
|
return cancelable(Promise.reject("Agent is not initialized"), () => {});
|
2023-06-23 06:43:55 +00:00
|
|
|
}
|
2023-06-24 21:43:13 +00:00
|
|
|
const polling = this.auth.pollingToken(code);
|
2023-06-15 15:53:21 +00:00
|
|
|
return cancelable(
|
2023-06-24 21:43:13 +00:00
|
|
|
polling.then(() => {
|
|
|
|
|
return this.setupApi();
|
2023-06-15 15:53:21 +00:00
|
|
|
}),
|
|
|
|
|
() => {
|
2023-06-24 21:43:13 +00:00
|
|
|
polling.cancel();
|
2023-07-13 08:31:20 +00:00
|
|
|
},
|
2023-06-15 15:53:21 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 05:30:53 +00:00
|
|
|
public provideCompletions(request: CompletionRequest): CancelablePromise<CompletionResponse> {
|
2023-06-15 15:53:21 +00:00
|
|
|
if (this.status === "notInitialized") {
|
2023-06-24 21:43:13 +00:00
|
|
|
return cancelable(Promise.reject("Agent is not initialized"), () => {});
|
2023-06-15 15:53:21 +00:00
|
|
|
}
|
2023-07-13 08:31:20 +00:00
|
|
|
const cancelableList: CancelablePromise<any>[] = [];
|
2023-05-29 02:09:44 +00:00
|
|
|
return cancelable(
|
2023-07-13 08:31:20 +00:00
|
|
|
Promise.resolve(null)
|
|
|
|
|
// From cache
|
2023-09-01 05:30:53 +00:00
|
|
|
.then(async (response: CompletionResponse | null) => {
|
2023-07-13 08:31:20 +00:00
|
|
|
if (response) return response;
|
|
|
|
|
if (this.completionCache.has(request)) {
|
|
|
|
|
this.logger.debug({ request }, "Completion cache hit");
|
2023-09-01 05:30:53 +00:00
|
|
|
const debounce = this.CompletionDebounce.debounce(request, this.config.completion.debounce, 0);
|
|
|
|
|
cancelableList.push(debounce);
|
|
|
|
|
await debounce;
|
2023-07-13 08:31:20 +00:00
|
|
|
return this.completionCache.get(request);
|
|
|
|
|
}
|
2023-09-01 05:30:53 +00:00
|
|
|
return null;
|
2023-06-22 06:22:35 +00:00
|
|
|
})
|
2023-07-13 08:31:20 +00:00
|
|
|
// From api
|
2023-09-01 05:30:53 +00:00
|
|
|
.then(async (response: CompletionResponse | null) => {
|
2023-07-13 08:31:20 +00:00
|
|
|
if (response) return response;
|
|
|
|
|
const segments = this.createSegments(request);
|
|
|
|
|
if (isBlank(segments.prefix)) {
|
|
|
|
|
this.logger.debug("Segment prefix is blank, returning empty completion response");
|
|
|
|
|
return {
|
|
|
|
|
id: "agent-" + uuid(),
|
|
|
|
|
choices: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
2023-09-01 05:30:53 +00:00
|
|
|
const debounce = this.CompletionDebounce.debounce(
|
|
|
|
|
request,
|
|
|
|
|
this.config.completion.debounce,
|
|
|
|
|
this.completionResponseStats.stats()["averageResponseTime"],
|
|
|
|
|
);
|
|
|
|
|
cancelableList.push(debounce);
|
|
|
|
|
await debounce;
|
2023-08-17 14:28:41 +00:00
|
|
|
const apiRequest = this.callApi(
|
|
|
|
|
this.api.v1.completion,
|
|
|
|
|
{
|
|
|
|
|
language: request.language,
|
|
|
|
|
segments,
|
|
|
|
|
user: this.auth?.user,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
timeout: request.manually ? this.config.completion.timeout.manually : this.config.completion.timeout.auto,
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-07-13 08:31:20 +00:00
|
|
|
cancelableList.push(apiRequest);
|
2023-09-01 05:30:53 +00:00
|
|
|
let res = await apiRequest;
|
|
|
|
|
res = await preCacheProcess(request, res);
|
|
|
|
|
this.completionCache.set(request, res);
|
|
|
|
|
return res;
|
2023-07-13 08:31:20 +00:00
|
|
|
})
|
|
|
|
|
// Postprocess
|
2023-09-01 05:30:53 +00:00
|
|
|
.then(async (response: CompletionResponse | null) => {
|
2023-06-22 06:22:35 +00:00
|
|
|
return postprocess(request, response);
|
2023-06-08 17:19:10 +00:00
|
|
|
}),
|
2023-05-29 02:09:44 +00:00
|
|
|
() => {
|
2023-07-13 08:31:20 +00:00
|
|
|
cancelableList.forEach((cancelable) => cancelable.cancel());
|
|
|
|
|
},
|
2023-05-29 02:09:44 +00:00
|
|
|
);
|
2023-05-24 01:50:57 +00:00
|
|
|
}
|
|
|
|
|
|
2023-06-07 16:11:31 +00:00
|
|
|
public postEvent(request: LogEventRequest): CancelablePromise<boolean> {
|
2023-06-15 15:53:21 +00:00
|
|
|
if (this.status === "notInitialized") {
|
2023-06-24 21:43:13 +00:00
|
|
|
return cancelable(Promise.reject("Agent is not initialized"), () => {});
|
2023-06-15 15:53:21 +00:00
|
|
|
}
|
2023-06-07 16:11:31 +00:00
|
|
|
return this.callApi(this.api.v1.event, request);
|
2023-05-24 01:50:57 +00:00
|
|
|
}
|
|
|
|
|
}
|