Skip to content

Commit

Permalink
refactor(agent): deprecate tabby/agent/* methods and intro `tabby/c…
Browse files Browse the repository at this point in the history
…onfig/*` and `tabby/stastus/*`. (#2895)
  • Loading branch information
icycodes authored Aug 16, 2024
1 parent 62bf4de commit c3bc3d1
Show file tree
Hide file tree
Showing 8 changed files with 631 additions and 96 deletions.
14 changes: 13 additions & 1 deletion clients/tabby-agent/src/TabbyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export class TabbyAgent extends EventEmitter implements Agent {
return abortSignalFromAnyOf([AbortSignal.timeout(timeout), options?.signal]);
}

private async healthCheck(options?: { signal?: AbortSignal; method?: "GET" | "POST" }): Promise<void> {
public async healthCheck(options?: { signal?: AbortSignal; method?: "GET" | "POST" }): Promise<void> {
const requestId = uuid();
const requestPath = "/v1/health";
const requestDescription = `${options?.method || "GET"} ${this.config.server.endpoint + requestPath}`;
Expand All @@ -258,12 +258,14 @@ export class TabbyAgent extends EventEmitter implements Agent {
throw new Error("http client not initialized");
}
this.logger.debug(`Health check request: ${requestDescription}. [${requestId}]`);
this.emit("connectingStateUpdated", true);
let response;
if (options?.method === "POST") {
response = await this.api.POST(requestPath, requestOptions);
} else {
response = await this.api.GET(requestPath, requestOptions);
}
this.emit("connectingStateUpdated", false);
this.logger.debug(`Health check response status: ${response.response.status}. [${requestId}]`);
if (response.error || !response.response.ok) {
throw new HttpError(response.response);
Expand All @@ -285,6 +287,7 @@ export class TabbyAgent extends EventEmitter implements Agent {
}
this.changeStatus("ready");
} catch (error) {
this.emit("connectingStateUpdated", false);
this.serverHealthState = undefined;
if (error instanceof HttpError && error.status == 405 && options?.method !== "POST") {
return await this.healthCheck({ method: "POST" });
Expand Down Expand Up @@ -600,6 +603,7 @@ export class TabbyAgent extends EventEmitter implements Agent {
solution = new CompletionSolution(context);
// Fetch the completion
this.logger.info(`Fetching completion...`);
this.emit("fetchingStateUpdated", true);
try {
const response = await this.fetchCompletion(
context.language,
Expand All @@ -616,13 +620,15 @@ export class TabbyAgent extends EventEmitter implements Agent {
solution = undefined;
}
}
this.emit("fetchingStateUpdated", false);
} else {
// No cached solution, or cached solution is not completed
// TriggerKind is Manual
// We need to fetch the more choices

solution = cachedSolution?.withContext(context) ?? new CompletionSolution(context);
this.logger.info(`Fetching more completions...`);
this.emit("fetchingStateUpdated", true);

try {
let tries = 0;
Expand Down Expand Up @@ -652,6 +658,7 @@ export class TabbyAgent extends EventEmitter implements Agent {
solution = undefined;
}
}
this.emit("fetchingStateUpdated", false);
}
// Postprocess solution
if (solution) {
Expand Down Expand Up @@ -1032,4 +1039,9 @@ export class TabbyAgent extends EventEmitter implements Agent {
: "";
return `${envInfo} ${tabby} ${ide} ${tabbyPlugin}`.trim();
}

// FIXME(@icycodes): extract data store
public getDataStore(): DataStore | undefined {
return this.dataStore;
}
}
2 changes: 2 additions & 0 deletions clients/tabby-agent/src/dataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import deepEqual from "deep-equal";
import chokidar from "chokidar";
import { isBrowser } from "./env";
import type { PartialAgentConfig } from "./AgentConfig";
import type { StatusIssuesName } from "./lsp/protocol";

export type StoredData = {
anonymousId: string;
auth: { [endpoint: string]: { jwt: string } };
serverConfig: { [endpoint: string]: PartialAgentConfig };
statusIgnoredIssues: StatusIssuesName[];
};

export interface DataStore {
Expand Down
21 changes: 15 additions & 6 deletions clients/tabby-agent/src/lsp/CommandProvider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Connection, ExecuteCommandParams } from "vscode-languageserver";
import { ServerCapabilities, ChatEditResolveParams } from "./protocol";
import {
ServerCapabilities,
StatusShowHelpMessageRequest,
ChatEditResolveRequest,
ChatEditResolveParams,
} from "./protocol";
import { ChatEditProvider } from "./ChatEditProvider";
import { StatusProvider } from "./StatusProvider";

export class CommandProvider {
constructor(
private readonly connection: Connection,
private readonly chatEditProvider: ChatEditProvider,
private readonly statusProvider: StatusProvider,
) {
this.connection.onExecuteCommand(async (params) => {
return this.executeCommand(params);
Expand All @@ -14,15 +21,17 @@ export class CommandProvider {

fillServerCapabilities(capabilities: ServerCapabilities): void {
capabilities.executeCommandProvider = {
commands: ["tabby/chat/edit/resolve"],
commands: [StatusShowHelpMessageRequest.method, ChatEditResolveRequest.method],
};
}

async executeCommand(params: ExecuteCommandParams): Promise<void> {
if (params.command === "tabby/chat/edit/resolve") {
const resolveParams = params.arguments?.[0] as ChatEditResolveParams;
if (resolveParams) {
await this.chatEditProvider.resolveEdit(resolveParams);
if (params.command === StatusShowHelpMessageRequest.method) {
await this.statusProvider.showStatusHelpMessage(this.connection);
} else if (params.command === ChatEditResolveRequest.method) {
const commandParams = params.arguments?.[0] as ChatEditResolveParams;
if (commandParams) {
await this.chatEditProvider.resolveEdit(commandParams);
}
}
}
Expand Down
38 changes: 38 additions & 0 deletions clients/tabby-agent/src/lsp/ConfigProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { EventEmitter } from "events";
import { Connection } from "vscode-languageserver";
import { ClientCapabilities, ServerCapabilities, Config, ConfigRequest, ConfigDidChangeNotification } from "./protocol";
import type { Feature } from "./feature";
import { TabbyAgent } from "../TabbyAgent";

export class ConfigProvider extends EventEmitter implements Feature {
constructor(private readonly agent: TabbyAgent) {
super();
this.agent.on("configUpdated", async () => {
this.update();
});
}

private async update() {
const status = await this.getConfig();
this.emit("updated", status);
}

setup(connection: Connection, clientCapabilities: ClientCapabilities): ServerCapabilities {
connection.onRequest(ConfigRequest.type, async () => {
return this.getConfig();
});
if (clientCapabilities.tabby?.configDidChangeListener) {
this.on("updated", (config: Config) => {
connection.sendNotification(ConfigDidChangeNotification.type, config);
});
}
return {};
}

async getConfig(): Promise<Config> {
const agentConfig = this.agent.getConfig();
return {
server: agentConfig.server,
};
}
}
101 changes: 16 additions & 85 deletions clients/tabby-agent/src/lsp/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ import { TextDocument } from "vscode-languageserver-textdocument";
import deepEqual from "deep-equal";
import { deepmerge } from "deepmerge-ts";
import type {
AgentIssue,
ConfigUpdatedEvent,
StatusChangedEvent,
IssuesUpdatedEvent,
Expand All @@ -96,6 +95,8 @@ import { RecentlyChangedCodeSearch } from "../codeSearch/RecentlyChangedCodeSear
import { isPositionInRange, intersectionRange } from "../utils/range";
import { extractNonReservedWordList } from "../utils/string";
import { splitLines, isBlank } from "../utils";
import { ConfigProvider } from "./ConfigProvider";
import { StatusProvider } from "./StatusProvider";
import { ChatEditProvider } from "./ChatEditProvider";
import { CodeLensProvider } from "./CodeLensProvider";
import { CommandProvider } from "./CommandProvider";
Expand All @@ -109,7 +110,12 @@ export class Server {

private readonly documents = new TextDocuments(TextDocument);
private readonly notebooks = new NotebookDocuments(this.documents);

private recentlyChangedCodeSearch: RecentlyChangedCodeSearch | undefined = undefined;

private config: ConfigProvider;
private status: StatusProvider;

private chatEditProvider: ChatEditProvider;
private codeLensProvider: CodeLensProvider | undefined = undefined;
private commandProvider: CommandProvider;
Expand Down Expand Up @@ -169,8 +175,12 @@ export class Server {
this.connection.onNotification(TelemetryEventNotification.type, async (param) => {
return this.event(param);
});

this.config = new ConfigProvider(this.agent);
this.status = new StatusProvider(this.agent);

// Command
this.commandProvider = new CommandProvider(this.connection, this.chatEditProvider);
this.commandProvider = new CommandProvider(this.connection, this.chatEditProvider, this.status);
}

listen() {
Expand Down Expand Up @@ -227,6 +237,9 @@ export class Server {
}
this.commandProvider.fillServerCapabilities(serverCapabilities);

this.config.setup(this.connection, clientCapabilities);
this.status.setup(this.connection, clientCapabilities);

await this.agent.initialize({
config: this.createInitConfig(clientProvidedConfig),
clientProperties: this.createInitClientProperties(clientInfo, clientProvidedConfig),
Expand Down Expand Up @@ -415,7 +428,7 @@ export class Server {
}
return {
name: detail.name,
helpMessage: this.buildHelpMessage(detail, params.helpMessageFormat),
helpMessage: this.status.buildHelpMessage(detail, params.helpMessageFormat),
};
}

Expand Down Expand Up @@ -1062,86 +1075,4 @@ export class Server {
}),
};
}

private buildHelpMessage(issueDetail: AgentIssue, format?: "plaintext" | "markdown" | "html"): string | undefined {
const outputFormat = format ?? "plaintext";

// "connectionFailed"
if (issueDetail.name == "connectionFailed") {
if (outputFormat == "html") {
return issueDetail.message?.replace(/\n/g, "<br/>");
} else {
return issueDetail.message;
}
}

// "slowCompletionResponseTime" or "highCompletionTimeoutRate"
let statsMessage = "";
if (issueDetail.name == "slowCompletionResponseTime") {
const stats = issueDetail.completionResponseStats;
if (stats && stats["responses"] && stats["averageResponseTime"]) {
statsMessage = `The average response time of recent ${stats["responses"]} completion requests is ${Number(
stats["averageResponseTime"],
).toFixed(0)}ms.<br/><br/>`;
}
}

if (issueDetail.name == "highCompletionTimeoutRate") {
const stats = issueDetail.completionResponseStats;
if (stats && stats["total"] && stats["timeouts"]) {
statsMessage = `${stats["timeouts"]} of ${stats["total"]} completion requests timed out.<br/><br/>`;
}
}

let helpMessageForRunningLargeModelOnCPU = "";
const serverHealthState = this.agent.getServerHealthState();
if (serverHealthState?.device === "cpu" && serverHealthState?.model?.match(/[0-9.]+B$/)) {
helpMessageForRunningLargeModelOnCPU +=
`Your Tabby server is running model <i>${serverHealthState?.model}</i> on CPU. ` +
"This model may be performing poorly due to its large parameter size, please consider trying smaller models or switch to GPU. " +
"You can find a list of recommend models in the <a href='https://tabby.tabbyml.com/'>online documentation</a>.<br/>";
}
let commonHelpMessage = "";
if (helpMessageForRunningLargeModelOnCPU.length == 0) {
commonHelpMessage += `<li>The running model <i>${
serverHealthState?.model ?? ""
}</i> may be performing poorly due to its large parameter size. `;
commonHelpMessage +=
"Please consider trying smaller models. You can find a list of recommend models in the <a href='https://tabby.tabbyml.com/'>online documentation</a>.</li>";
}
const host = new URL(this.serverInfo?.config.endpoint ?? "http://localhost:8080").host;
if (!(host.startsWith("localhost") || host.startsWith("127.0.0.1") || host.startsWith("0.0.0.0"))) {
commonHelpMessage += "<li>A poor network connection. Please check your network and proxy settings.</li>";
commonHelpMessage += "<li>Server overload. Please contact your Tabby server administrator for assistance.</li>";
}
let helpMessage = "";
if (helpMessageForRunningLargeModelOnCPU.length > 0) {
helpMessage += helpMessageForRunningLargeModelOnCPU + "<br/>";
if (commonHelpMessage.length > 0) {
helpMessage += "Other possible causes of this issue: <br/><ul>" + commonHelpMessage + "</ul>";
}
} else {
// commonHelpMessage should not be empty here
helpMessage += "Possible causes of this issue: <br/><ul>" + commonHelpMessage + "</ul>";
}

if (outputFormat == "html") {
return statsMessage + helpMessage;
}
if (outputFormat == "markdown") {
return (statsMessage + helpMessage)
.replace(/<br\/>/g, " \n")
.replace(/<i>(.*?)<\/i>/g, "*$1*")
.replace(/<a\s+(?:[^>]*?\s+)?href=["']([^"']+)["'][^>]*>([^<]+)<\/a>/g, "[$2]($1)")
.replace(/<ul[^>]*>(.*?)<\/ul>/g, "$1")
.replace(/<li[^>]*>(.*?)<\/li>/g, "- $1 \n");
} else {
return (statsMessage + helpMessage)
.replace(/<br\/>/g, " \n")
.replace(/<i>(.*?)<\/i>/g, "$1")
.replace(/<a[^>]*>(.*?)<\/a>/g, "$1")
.replace(/<ul[^>]*>(.*?)<\/ul>/g, "$1")
.replace(/<li[^>]*>(.*?)<\/li>/g, "- $1 \n");
}
}
}
Loading

0 comments on commit c3bc3d1

Please sign in to comment.