tabby/clients/vscode/src/TabbyClient.ts

122 lines
2.9 KiB
TypeScript

import { workspace } from "vscode";
import axios from "axios";
import { sleep } from "./utils";
import { EventEmitter } from "node:events";
import { strict as assert } from "node:assert";
const logAxios = false;
if (logAxios) {
axios.interceptors.request.use((request) => {
console.debug("Starting Request: ", request);
return request;
});
axios.interceptors.response.use((response) => {
console.debug("Response: ", response);
return response;
});
}
export interface TabbyCompletion {
id?: string;
created?: number;
choices?: Array<{
index: number;
text: string;
}>;
}
export enum EventType {
InlineCompletionDisplayed = "view",
InlineCompletionAccepted = "select",
}
export interface Event {
type: EventType;
id?: string;
index?: number;
}
export class TabbyClient extends EventEmitter {
private static instance: TabbyClient;
static getInstance(): TabbyClient {
if (!TabbyClient.instance) {
TabbyClient.instance = new TabbyClient();
}
return TabbyClient.instance;
}
private tabbyServerUrl: string = "";
status: "connecting" | "ready" | "disconnected" = "connecting";
constructor() {
super();
this.updateConfiguration();
workspace.onDidChangeConfiguration((event) => {
if (event.affectsConfiguration("tabby")) {
this.updateConfiguration();
}
});
}
private updateConfiguration() {
const configuration = workspace.getConfiguration("tabby");
this.tabbyServerUrl = configuration.get("serverUrl", "http://127.0.0.1:5000");
this.ping();
}
private changeStatus(status: "connecting" | "ready" | "disconnected") {
if (this.status != status) {
this.status = status;
this.emit("statusChanged", status);
}
}
private async ping(tries: number = 0) {
try {
const response = await axios.get(`${this.tabbyServerUrl}/`);
assert(response.status == 200);
this.changeStatus("ready");
} catch (e) {
if (tries > 5) {
this.changeStatus("disconnected");
return;
}
this.changeStatus("connecting");
const pingRetryDelay = 1000;
await sleep(pingRetryDelay);
this.ping(tries + 1);
}
}
public async getCompletion(prompt: string): Promise<TabbyCompletion | null> {
if (this.status == "disconnected") {
this.ping();
}
try {
const response = await axios.post<TabbyCompletion>(`${this.tabbyServerUrl}/v1/completions`, {
prompt,
});
assert(response.status == 200);
return response.data;
} catch (e) {
this.ping();
return null;
}
}
public async postEvent(event: Event) {
if (this.status == "disconnected") {
this.ping();
}
try {
const response = await axios.post(
`${this.tabbyServerUrl}/v1/completions/${event.id}/choices/${event.index}/${event.type}`
);
assert(response.status == 200);
} catch (e) {
this.ping();
}
}
}