Skip to content

Commit

Permalink
feat(agent&vscode): add filepath & git_url in completion request. (Ta…
Browse files Browse the repository at this point in the history
…bbyML#1682)

* feat(agent): add fileInfo in completion request.

* fix(agent): updates based on the completion request api changes.

* fix(vscode): fix field when getting `completion.prompt.filepath` config.

* fix: Remove filepath experimentalEnabled config. Add types for git api.
  • Loading branch information
icycodes authored Mar 25, 2024
1 parent efcdfc6 commit 0b9e0d2
Show file tree
Hide file tree
Showing 11 changed files with 531 additions and 15 deletions.
2 changes: 1 addition & 1 deletion clients/example-vscode-lsp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"devDependencies": {
"@types/vscode": "^1.82.0",
"esbuild-plugin-copy": "^2.1.1",
"tabby-agent": "1.4.2"
"tabby-agent": "1.5.0-dev"
},
"dependencies": {
"vscode-languageclient": "^9.0.1"
Expand Down
2 changes: 1 addition & 1 deletion clients/intellij/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"devDependencies": {
"cpy-cli": "^4.2.0",
"rimraf": "^5.0.1",
"tabby-agent": "1.4.2"
"tabby-agent": "1.5.0-dev"
}
}
12 changes: 11 additions & 1 deletion clients/tabby-agent/openapi/tabby.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"title": "Tabby Server",
"description": "\n[![tabby stars](https://img.shields.io/github/stars/TabbyML/tabby)](https://github.com/TabbyML/tabby)\n[![Join Slack](https://shields.io/badge/Join-Tabby%20Slack-red?logo=slack)](https://links.tabbyml.com/join-slack)\n\nInstall following IDE / Editor extensions to get started with [Tabby](https://github.com/TabbyML/tabby).\n* [VSCode Extension](https://github.com/TabbyML/tabby/tree/main/clients/vscode) – Install from the [marketplace](https://marketplace.visualstudio.com/items?itemName=TabbyML.vscode-tabby), or [open-vsx.org](https://open-vsx.org/extension/TabbyML/vscode-tabby)\n* [VIM Extension](https://github.com/TabbyML/tabby/tree/main/clients/vim)\n* [IntelliJ Platform Plugin](https://github.com/TabbyML/tabby/tree/main/clients/intellij) – Install from the [marketplace](https://plugins.jetbrains.com/plugin/22379-tabby)\n",
"license": { "name": "Apache 2.0", "url": "https://github.com/TabbyML/tabby/blob/main/LICENSE" },
"version": "0.9.0-dev"
"version": "0.10.0-dev.0"
},
"servers": [{ "url": "/", "description": "Server" }],
"paths": {
Expand Down Expand Up @@ -307,6 +307,16 @@
"description": "Content that appears after the cursor in the editor window.",
"nullable": true
},
"filepath": {
"type": "string",
"description": "The relative path of the file that is being edited.\nWhen git_url is set, this is the path of the file in the git repository.\nWhen git_url is empty, this is the path of the file in the workspace.",
"nullable": true
},
"git_url": {
"type": "string",
"description": "The remote URL of the current git repository.\nLeave this empty if the file is not in a git repository,\nor the git repository does not have a remote URL.",
"nullable": true
},
"clipboard": {
"type": "string",
"description": "Clipboard content when requesting code completion.",
Expand Down
2 changes: 1 addition & 1 deletion clients/tabby-agent/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tabby-agent",
"version": "1.4.2",
"version": "1.5.0-dev",
"description": "Generic client agent for Tabby AI coding assistant IDE extensions.",
"homepage": "https://tabby.tabbyml.com/",
"repository": "https://github.com/TabbyML/tabby",
Expand Down
20 changes: 20 additions & 0 deletions clients/tabby-agent/src/CompletionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ export type CompletionRequest = {
indentation?: string;
clipboard?: string;
manually?: boolean;
workspace?: string;
git?: {
root: string;
remotes: {
name: string;
url: string;
}[];
};
};

export type CompletionResponseChoice = {
Expand Down Expand Up @@ -47,6 +55,15 @@ export class CompletionContext {

clipboard: string;

workspace?: string;
git?: {
root: string;
remotes: {
name: string;
url: string;
}[];
};

// "default": the cursor is at the end of the line
// "fill-in-line": the cursor is not at the end of the line, except auto closed characters
// In this case, we assume the completion should be a single line, so multiple lines completion will be dropped.
Expand All @@ -69,6 +86,9 @@ export class CompletionContext {

this.clipboard = request.clipboard?.trim() ?? "";

this.workspace = request.workspace;
this.git = request.git;

const lineEnd = isAtLineEndExcludingAutoClosedChar(this.suffixLines[0] ?? "");
this.mode = lineEnd ? "default" : "fill-in-line";
this.hash = hashObject({
Expand Down
48 changes: 40 additions & 8 deletions clients/tabby-agent/src/TabbyAgent.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { EventEmitter } from "events";
import path from "path";
import { v4 as uuid } from "uuid";
import deepEqual from "deep-equal";
import { deepmerge } from "deepmerge-ts";
import { getProperty, setProperty, deleteProperty } from "dot-prop";
import createClient from "openapi-fetch";
import type { ParseAs } from "openapi-fetch";
import type { paths as TabbyApi } from "./types/tabbyApi";
import type { paths as TabbyApi, components as TabbyApiComponents } from "./types/tabbyApi";
import type {
Agent,
AgentStatus,
Expand Down Expand Up @@ -321,7 +322,7 @@ export class TabbyAgent extends EventEmitter implements Agent {
}
}

private createSegments(context: CompletionContext): { prefix: string; suffix: string; clipboard?: string } {
private async createSegments(context: CompletionContext): Promise<TabbyApiComponents["schemas"]["Segments"] | null> {
// max lines in prefix and suffix configurable
const maxPrefixLines = this.config.completion.prompt.maxPrefixLines;
const maxSuffixLines = this.config.completion.prompt.maxSuffixLines;
Expand All @@ -333,13 +334,45 @@ export class TabbyAgent extends EventEmitter implements Agent {
} else {
suffix = suffixLines.slice(0, maxSuffixLines).join("");
}
if (isBlank(prefix)) {
return null;
}

// filepath
let filepathInfo: { filepath: string; git_url?: string } | undefined = undefined;
const { filepath, workspace, git } = context;
if (git && git.remotes.length > 0) {
// find remote url: origin > upstream > first
const remote =
git.remotes.find((remote) => remote.name === "origin") ||
git.remotes.find((remote) => remote.name === "upstream") ||
git.remotes[0];
if (remote) {
filepathInfo = {
filepath: path.relative(git.root, filepath),
git_url: remote.url,
};
}
}
if (!filepathInfo && workspace) {
// if filepathInfo is not set by git context, use path relative to workspace
filepathInfo = {
filepath: path.relative(workspace, filepath),
};
}

// clipboard
let clipboard = undefined;
const clipboardConfig = this.config.completion.prompt.clipboard;
if (context.clipboard.length >= clipboardConfig.minChars && context.clipboard.length <= clipboardConfig.maxChars) {
clipboard = context.clipboard;
}
return { prefix, suffix, clipboard };
return {
prefix,
suffix,
...filepathInfo,
clipboard,
};
}

public async initialize(options: AgentInitOptions): Promise<boolean> {
Expand Down Expand Up @@ -547,11 +580,10 @@ export class TabbyAgent extends EventEmitter implements Agent {
} else {
// Cache miss
stats.cacheHit = false;
const segments = this.createSegments(context);
if (isBlank(segments.prefix)) {
// Empty prompt
stats = undefined; // no need to record stats for empty prompt
this.logger.debug("Segment prefix is blank, returning empty completion response");
const segments = await this.createSegments(context);
if (!segments) {
stats = undefined; // no need to record stats when no segments
this.logger.debug("Can not build segments, returning empty completion response");
completionResponse = {
id: "agent-" + uuid(),
choices: [],
Expand Down
12 changes: 12 additions & 0 deletions clients/tabby-agent/src/types/tabbyApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ export interface components {
prefix: string;
/** @description Content that appears after the cursor in the editor window. */
suffix?: string | null;
/**
* @description The relative path of the file that is being edited.
* When git_url is set, this is the path of the file in the git repository.
* When git_url is empty, this is the path of the file in the workspace.
*/
filepath?: string | null;
/**
* @description The remote URL of the current git repository.
* Leave this empty if the file is not in a git repository,
* or the git repository does not have a remote URL.
*/
git_url?: string | null;
/** @description Clipboard content when requesting code completion. */
clipboard?: string | null;
};
Expand Down
2 changes: 1 addition & 1 deletion clients/vim/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"devDependencies": {
"cpy-cli": "^4.2.0",
"rimraf": "^5.0.1",
"tabby-agent": "1.4.2"
"tabby-agent": "1.5.0-dev"
}
}
4 changes: 2 additions & 2 deletions clients/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"repository": "https://github.com/TabbyML/tabby",
"bugs": "https://github.com/TabbyML/tabby/issues",
"license": "Apache-2.0",
"version": "1.4.0",
"version": "1.5.0-dev",
"keywords": [
"ai",
"autocomplete",
Expand Down Expand Up @@ -237,6 +237,6 @@
},
"dependencies": {
"@xstate/fsm": "^2.0.1",
"tabby-agent": "1.4.2"
"tabby-agent": "1.5.0-dev"
}
}
29 changes: 29 additions & 0 deletions clients/vscode/src/TabbyCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import {
TextDocument,
NotebookDocument,
NotebookRange,
Uri,
extensions,
window,
workspace,
} from "vscode";
import { EventEmitter } from "events";
import { CompletionRequest, CompletionResponse, LogEventRequest } from "tabby-agent";
import { API as GitAPI } from "./types/git";
import { logger } from "./logger";
import { agent } from "./agent";

Expand All @@ -29,6 +32,7 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
private onGoingRequestAbortController: AbortController | null = null;
private loading: boolean = false;
private displayedCompletion: DisplayedCompletion | null = null;
private gitApi: GitAPI | null = null;

public constructor() {
super();
Expand All @@ -38,6 +42,11 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
this.updateConfiguration();
}
});

const gitExt = extensions.getExtension("vscode.git");
if (gitExt && gitExt.isActive) {
this.gitApi = gitExt.exports.getAPI(1); // version: 1
}
}

public getTriggerMode(): "automatic" | "manual" | "disabled" {
Expand Down Expand Up @@ -96,6 +105,8 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
position: additionalContext.prefix.length + document.offsetAt(position),
indentation: this.getEditorIndentation(),
manually: context.triggerKind === InlineCompletionTriggerKind.Invoke,
workspace: workspace.getWorkspaceFolder(document.uri)?.uri.fsPath,
git: this.getGitContext(document.uri),
};

const abortController = new AbortController();
Expand Down Expand Up @@ -282,4 +293,22 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
})
.join("\n\n");
}

private getGitContext(uri: Uri): CompletionRequest["git"] | undefined {
const repo = this.gitApi?.getRepository(uri);
if (!repo) {
return undefined;
}
return {
root: repo.rootUri.fsPath,
remotes: repo.state.remotes
.map((remote) => ({
name: remote.name,
url: remote.fetchUrl ?? remote.pushUrl ?? "",
}))
.filter((remote) => {
return remote.url.length > 0;
}),
};
}
}
Loading

0 comments on commit 0b9e0d2

Please sign in to comment.