Skip to content

Commit

Permalink
feat(tabby-agent): adding providing best fit range for smart apply fu…
Browse files Browse the repository at this point in the history
…nction

add provideSmartApplyLineRange functionality to TabbyAgent and ChatEditProvider

a
  • Loading branch information
Sma1lboy committed Sep 11, 2024
1 parent 4b848cc commit 6241339
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 3 deletions.
8 changes: 7 additions & 1 deletion clients/tabby-agent/src/AgentConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import generateCommitMessagePrompt from "./prompts/generate-commit-message.md";
import generateDocsPrompt from "./prompts/generate-docs.md";
import editCommandReplacePrompt from "./prompts/edit-command-replace.md";
import editCommandInsertPrompt from "./prompts/edit-command-insert.md";

import provideSmartApplyLineRangePrompt from "./prompts/provide-smart-apply-line-range.md";
export type AgentConfig = {
server: {
endpoint: string;
Expand Down Expand Up @@ -98,6 +98,9 @@ export type AgentConfig = {
promptTemplate: string;
responseMatcher: string;
};
provideSmartApplyLineRange: {
promptTemplate: string;
};
};
logs: {
// Controls the level of the logger written to the `~/.tabby-client/agent/logs/`
Expand Down Expand Up @@ -206,6 +209,9 @@ export const defaultAgentConfig: AgentConfig = {
responseMatcher:
/(?<=(["'`]+)?\s*)(feat|fix|docs|refactor|style|test|build|ci|chore)(\(\S+\))?:.+(?=\s*\1)/gis.toString(),
},
provideSmartApplyLineRange: {
promptTemplate: provideSmartApplyLineRangePrompt,
},
},
logs: {
level: "silent",
Expand Down
67 changes: 66 additions & 1 deletion clients/tabby-agent/src/TabbyAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import { AnonymousUsageLogger } from "./AnonymousUsageLogger";
import { loadTlsCaCerts } from "./loadCaCerts";
import { createProxyForUrl, ProxyConfig } from "./http/proxy";
import { name as agentName, version as agentVersion } from "../package.json";

export class TabbyAgent extends EventEmitter implements Agent {
private readonly logger = getLogger("TabbyAgent");
private anonymousUsageLogger = new AnonymousUsageLogger();
Expand Down Expand Up @@ -1024,6 +1023,72 @@ export class TabbyAgent extends EventEmitter implements Agent {
}
}

public async provideSmartApplyLineRange(document: string, applyCode: string): Promise<Readable | null> {
if (this.status === "notInitialized") {
throw new Error("Agent is not initialized");
}

const promptTemplate = this.config.chat.provideSmartApplyLineRange.promptTemplate;

// request chat api
const requestId = uuid();
try {
if (!this.api) {
throw new Error("http client not initialized");
}
const requestPath = "/v1/chat/completions";
const messages: { role: "user"; content: string }[] = [
{
role: "user",
content: promptTemplate.replace(/{{document}}|{{applyCode}}/g, (pattern: string) => {
switch (pattern) {
case "{{document}}":
return document;
case "{{applyCode}}":
return applyCode;
default:
return "";
}
}),
},
];
const requestOptions = {
body: {
messages,
model: "",
stream: true,
},
parseAs: "stream" as ParseAs,
};
const requestDescription = `POST ${this.config.server.endpoint + requestPath}`;
this.logger.debug(`Chat request: ${requestDescription}. [${requestId}]`);
this.logger.trace(`Chat request body: [${requestId}]`, requestOptions.body);
const response = await this.api.POST(requestPath, requestOptions);
this.logger.debug(`Chat response status: ${response.response.status}. [${requestId}]`);
if (response.error || !response.response.ok) {
throw new HttpError(response.response);
}
if (!response.response.body) {
return null;
}
const readableStream = readChatStream(response.response.body);
return readableStream;
} catch (error) {
if (error instanceof HttpError && error.status == 404) {
return await this.provideSmartApplyLineRange(document, applyCode);
}
if (isCanceledError(error)) {
this.logger.debug(`Chat request canceled. [${requestId}]`);
} else if (isUnauthorizedError(error)) {
this.logger.debug(`Chat request failed due to unauthorized. [${requestId}]`);
} else {
this.logger.error(`Chat request failed. [${requestId}]`, error);
}
this.healthCheck();
return null;
}
}

private clientInfoToString(session: Record<string, any> | undefined): string {
let envInfo = `Node.js/${process.version}`;

Expand Down
64 changes: 63 additions & 1 deletion clients/tabby-agent/src/lsp/ChatEditProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
ChatEditMutexError,
ApplyWorkspaceEditRequest,
ApplyWorkspaceEditParams,
ChatLineRangeSmartApplyRequest,
ChatLineRangeSmartApplyParams,
ChatLineRangeSmartApplyResult,
} from "./protocol";
import { TextDocuments } from "./TextDocuments";
import { TextDocument } from "vscode-languageserver-textdocument";
Expand All @@ -23,7 +26,6 @@ import * as Diff from "diff";
import { TabbyAgent } from "../TabbyAgent";
import { isEmptyRange } from "../utils/range";
import { isBlank } from "../utils";

export type Edit = {
id: ChatEditToken;
location: Location;
Expand Down Expand Up @@ -54,6 +56,10 @@ export class ChatEditProvider {
this.connection.onRequest(ChatEditResolveRequest.type, async (params) => {
return this.resolveEdit(params);
});

this.connection.onRequest(ChatLineRangeSmartApplyRequest.type, async (params) => {
return this.provideSmartApplyLineRange(params);
});
}

isCurrentEdit(id: ChatEditToken): boolean {
Expand Down Expand Up @@ -428,6 +434,62 @@ export class ChatEditProvider {
}
}

async provideSmartApplyLineRange(
params: ChatLineRangeSmartApplyParams,
): Promise<ChatLineRangeSmartApplyResult | undefined> {
const document = this.documents.get(params.uri);
if (!document) {
return undefined;
}
if (!this.agent.getServerHealthState()?.chat_model) {
throw {
name: "ChatFeatureNotAvailableError",
message: "Chat feature not available",
} as ChatFeatureNotAvailableError;
}

//TODO(Sma1lboy): maybe visible range with huge offset, don't do whole file
const documentText = document
.getText()
.split("\n")
.map((line, idx) => `${idx + 1} | ${line}`)
.join("\n");

if (this.mutexAbortController) {
throw {
name: "ChatEditMutexError",
message: "Another edit is already in progress",
} as ChatEditMutexError;
}
this.mutexAbortController = new AbortController();

const stream = await this.agent.provideSmartApplyLineRange(documentText, params.applyCode);
if (!stream) {
return undefined;
}
let response = "";
for await (const delta of stream) {
response += delta;
}

const regex = /<GENERATEDCODE>(.*?)<\/GENERATEDCODE>/s;
const match = response.match(regex);

if (match && match[1]) {
response = match[1].trim();
}

const range = response.split("-");

if (range.length !== 2) {
return undefined;
}
const startLine = parseInt(range[0] ? range[0] : "0");
const endLine = parseInt(range[1] ? range[1] : "0");
this.mutexAbortController = null;
return { start: startLine, end: endLine };
}

// header line
// <<<<<<< Editing by Tabby <.#=+->
// markers:
Expand Down
31 changes: 31 additions & 0 deletions clients/tabby-agent/src/lsp/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,37 @@ export type ChatEditResolveCommand = LspCommand & {
arguments: [ChatEditResolveParams];
};

/**
* [Tabby] Provide Best fit line range for smart apply request(↩️)
*
* This method is sent from the client to server to smart apply from chat panel.
* - method: `tabby/chat/smartApply`
* - params: {@link ChatLineRangeSmartApplyParams}
* - result: {@link ChatLineRangeSmartApplyResult} | null
*/
export namespace ChatLineRangeSmartApplyRequest {
export const method = "tabby/chat/smartApply";
export const messageDirection = MessageDirection.clientToServer;
export const type = new ProtocolRequestType<
ChatLineRangeSmartApplyParams,
ChatLineRangeSmartApplyResult | null,
void,
ChatFeatureNotAvailableError,
void
>(method);
}

export type ChatLineRangeSmartApplyParams = {
//the uri of the document
uri: string;
applyCode: string;
};

export type ChatLineRangeSmartApplyResult = {
start: number;
end: number;
};

/**
* [Tabby] GenerateCommitMessage Request(↩️)
*
Expand Down
28 changes: 28 additions & 0 deletions clients/tabby-agent/src/prompts/provide-smart-apply-line-range.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
You are an AI assistant specialized in determining the most appropriate location to insert new code into an existing file. Your task is to analyze the given file content and the code to be inserted, then provide only the line range where the new code should be inserted.

The file content is provided line by line, with each line in the format:
line number | code

The new code to be inserted is provided in <APPLYCODE></APPLYCODE> XML tags.

Your task:

1. Analyze the existing code structure and the new code to be inserted.
2. Determine the most appropriate location for insertion.
3. Provide ONLY the line range for insertion.

You must reply with ONLY the suggested insertion range in the format startLine-endLine, enclosed in <GENERATEDCODE></GENERATEDCODE> XML tags.

Do not include any explanation, existing code, or the code to be inserted in your response.

File content:
<DOCUMENT>
{{document}}
</DOCUMENT>

Code to be inserted:
<APPLYCODE>
{{applyCode}}
</APPLYCODE>

Provide only the appropriate insertion range.

0 comments on commit 6241339

Please sign in to comment.