Agent: Add completion cache. (#158)

add-prefix-suffix
Zhiming Ma 2023-05-29 10:09:44 +08:00 committed by GitHub
parent 48796ecd77
commit a9d74f7a35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 5398 additions and 322 deletions

File diff suppressed because one or more lines are too long

View File

@ -130,6 +130,7 @@ declare class TabbyAgent extends EventEmitter implements Agent {
private serverUrl;
private status;
private api;
private completionCache;
constructor();
private changeStatus;
private ping;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -61,10 +61,10 @@ var import_axios2 = __toESM(require("axios"));
var import_events = require("events");
var import_assert = require("assert");
// src/utils.ts
function sleep(milliseconds) {
return new Promise((r) => setTimeout(r, milliseconds));
}
// src/CompletionCache.ts
var import_lru_cache = require("lru-cache");
var import_object_hash = __toESM(require("object-hash"));
var import_object_sizeof = __toESM(require("object-sizeof"));
// src/generated/core/BaseHttpRequest.ts
var BaseHttpRequest = class {
@ -495,6 +495,134 @@ var EventType = /* @__PURE__ */ ((EventType2) => {
return EventType2;
})(EventType || {});
// src/utils.ts
function sleep(milliseconds) {
return new Promise((r) => setTimeout(r, milliseconds));
}
function splitLines(input) {
return input.match(/.*(?:$|\r?\n)/g).filter(Boolean);
}
function splitWords(input) {
return input.match(/\w+|\W+/g).filter(Boolean);
}
function cancelable(promise, cancel) {
return new CancelablePromise((resolve2, reject, onCancel) => {
promise.then((resp) => {
resolve2(resp);
}).catch((err) => {
reject(err);
});
onCancel(() => {
cancel();
});
});
}
// src/CompletionCache.ts
var CompletionCache = class {
constructor() {
this.options = {
maxSize: 1 * 1024 * 1024,
// 1MB
partiallyAcceptedCacheGeneration: {
enabled: true,
perCharacter: {
lines: 1,
words: 10,
max: 30
},
perWord: {
lines: 1,
max: 20
},
perLine: {
max: 3
}
}
};
this.cache = new import_lru_cache.LRUCache({
maxSize: this.options.maxSize,
sizeCalculation: import_object_sizeof.default
});
}
has(key) {
return this.cache.has(this.hash(key));
}
set(key, value) {
for (const entry of this.createCacheEntries(key, value)) {
this.cache.set(this.hash(entry.key), entry.value);
}
}
get(key) {
return this.cache.get(this.hash(key));
}
hash(key) {
return (0, import_object_hash.default)(key);
}
createCacheEntries(key, value) {
const list = [{ key, value }];
if (this.options.partiallyAcceptedCacheGeneration.enabled) {
const entries = value.choices.map((choice) => {
return this.calculatePartiallyAcceptedPositions(choice.text).map((position) => {
return {
prefix: choice.text.slice(0, position),
suffix: choice.text.slice(position),
choiceIndex: choice.index
};
});
}).flat().reduce((grouped, entry) => {
grouped[entry.prefix] = grouped[entry.prefix] || [];
grouped[entry.prefix].push({ suffix: entry.suffix, choiceIndex: entry.choiceIndex });
return grouped;
}, {});
for (const prefix in entries) {
const cacheKey = { ...key, prompt: key.prompt + prefix };
const cacheValue = {
...value,
choices: entries[prefix].map((choice) => {
return {
index: choice.choiceIndex,
text: choice.suffix
};
})
};
list.push({
key: cacheKey,
value: cacheValue
});
}
}
return list;
}
calculatePartiallyAcceptedPositions(completion) {
const positions = [];
const option = this.options.partiallyAcceptedCacheGeneration;
const lines = splitLines(completion);
let index = 0;
let offset = 0;
while (index < lines.length - 1 && index < option.perLine.max) {
offset += lines[index].length;
positions.push(offset);
index++;
}
const words = lines.slice(0, option.perWord.lines).map(splitWords).flat();
index = 0;
offset = 0;
while (index < words.length && index < option.perWord.max) {
offset += words[index].length;
positions.push(offset);
index++;
}
const characters = lines.slice(0, option.perCharacter.lines).map(splitWords).flat().slice(0, option.perCharacter.words).join("");
offset = 1;
while (offset < characters.length && offset < option.perCharacter.max) {
positions.push(offset);
offset++;
}
return positions.filter((v, i, arr) => arr.indexOf(v) === i).sort((a, b) => a - b);
}
};
// src/TabbyAgent.ts
var TabbyAgent = class extends import_events.EventEmitter {
constructor() {
@ -503,6 +631,7 @@ var TabbyAgent = class extends import_events.EventEmitter {
this.status = "connecting";
this.ping();
this.api = new TabbyApi({ BASE: this.serverUrl });
this.completionCache = new CompletionCache();
}
changeStatus(status) {
if (this.status != status) {
@ -529,22 +658,18 @@ var TabbyAgent = class extends import_events.EventEmitter {
}
}
wrapApiPromise(promise) {
return new CancelablePromise((resolve2, reject, onCancel) => {
promise.then((resp) => {
return cancelable(
promise.then((resolved) => {
this.changeStatus("ready");
resolve2(resp);
}).catch((err) => {
reject(err);
return resolved;
}).catch((err) => {
this.changeStatus("disconnected");
reject(err);
}).catch((err) => {
reject(err);
});
onCancel(() => {
throw err;
}),
() => {
promise.cancel();
});
});
}
);
}
setServerUrl(serverUrl) {
this.serverUrl = serverUrl.replace(/\/$/, "");
@ -559,12 +684,24 @@ var TabbyAgent = class extends import_events.EventEmitter {
return this.status;
}
getCompletions(request2) {
const promise = this.api.default.completionsV1CompletionsPost(request2);
return this.wrapApiPromise(promise);
if (this.completionCache.has(request2)) {
return new CancelablePromise((resolve2) => {
resolve2(this.completionCache.get(request2));
});
}
const promise = this.wrapApiPromise(this.api.default.completionsV1CompletionsPost(request2));
return cancelable(
promise.then((response) => {
this.completionCache.set(request2, response);
return response;
}),
() => {
promise.cancel();
}
);
}
postEvent(request2) {
const promise = this.api.default.eventsV1EventsPost(request2);
return this.wrapApiPromise(promise);
return this.wrapApiPromise(this.api.default.eventsV1EventsPost(request2));
}
};

File diff suppressed because one or more lines are too long

View File

@ -28,6 +28,9 @@
},
"dependencies": {
"axios": "^1.4.0",
"form-data": "^4.0.0"
"form-data": "^4.0.0",
"lru-cache": "^9.1.1",
"object-hash": "^3.0.0",
"object-sizeof": "^2.6.1"
}
}

View File

@ -0,0 +1,136 @@
import { LRUCache } from "lru-cache";
import hashObject from "object-hash";
import sizeOfObject from "object-sizeof";
import { CompletionRequest, CompletionResponse } from "./generated";
import { splitLines, splitWords } from "./utils";
type CompletionCacheKey = CompletionRequest;
type CompletionCacheValue = CompletionResponse;
export class CompletionCache {
private cache: LRUCache<string, CompletionCacheValue>;
private options = {
maxSize: 1 * 1024 * 1024, // 1MB
partiallyAcceptedCacheGeneration: {
enabled: true,
perCharacter: {
lines: 1,
words: 10,
max: 30,
},
perWord: {
lines: 1,
max: 20,
},
perLine: {
max: 3,
},
},
};
constructor() {
this.cache = new LRUCache<string, CompletionCacheValue>({
maxSize: this.options.maxSize,
sizeCalculation: sizeOfObject,
});
}
has(key: CompletionCacheKey): boolean {
return this.cache.has(this.hash(key));
}
set(key: CompletionCacheKey, value: CompletionCacheValue): void {
for (const entry of this.createCacheEntries(key, value)) {
this.cache.set(this.hash(entry.key), entry.value);
}
}
get(key: CompletionCacheKey): CompletionCacheValue | undefined {
return this.cache.get(this.hash(key));
}
private hash(key: CompletionCacheKey): string {
return hashObject(key);
}
private createCacheEntries(
key: CompletionCacheKey,
value: CompletionCacheValue
): { key: CompletionCacheKey; value: CompletionCacheValue }[] {
const list = [{ key, value }];
if (this.options.partiallyAcceptedCacheGeneration.enabled) {
const entries = value.choices
.map((choice) => {
return this.calculatePartiallyAcceptedPositions(choice.text).map((position) => {
return {
prefix: choice.text.slice(0, position),
suffix: choice.text.slice(position),
choiceIndex: choice.index,
};
});
})
.flat()
.reduce((grouped: { [key: string]: { suffix: string; choiceIndex: number }[] }, entry) => {
grouped[entry.prefix] = grouped[entry.prefix] || [];
grouped[entry.prefix].push({ suffix: entry.suffix, choiceIndex: entry.choiceIndex });
return grouped;
}, {});
for (const prefix in entries) {
const cacheKey = { ...key, prompt: key.prompt + prefix };
const cacheValue = {
...value,
choices: entries[prefix].map((choice) => {
return {
index: choice.choiceIndex,
text: choice.suffix,
};
}),
};
list.push({
key: cacheKey,
value: cacheValue,
});
}
}
return list;
}
private calculatePartiallyAcceptedPositions(completion: string): number[] {
const positions: number[] = [];
const option = this.options.partiallyAcceptedCacheGeneration;
const lines = splitLines(completion);
let index = 0;
let offset = 0;
// `index < lines.length - 1` to exclude the last line
while (index < lines.length - 1 && index < option.perLine.max) {
offset += lines[index].length;
positions.push(offset);
index++;
}
const words = lines.slice(0, option.perWord.lines).map(splitWords).flat();
index = 0;
offset = 0;
while (index < words.length && index < option.perWord.max) {
offset += words[index].length;
positions.push(offset);
index++;
}
const characters = lines
.slice(0, option.perCharacter.lines)
.map(splitWords)
.flat()
.slice(0, option.perCharacter.words)
.join("");
offset = 1;
while (offset < characters.length && offset < option.perCharacter.max) {
positions.push(offset);
offset++;
}
// distinct and sort ascending
return positions.filter((v, i, arr) => arr.indexOf(v) === i).sort((a, b) => a - b);
}
}

View File

@ -1,12 +1,12 @@
import axios from "axios";
import { EventEmitter } from "events";
import { strict as assert } from "assert";
import { sleep } from "./utils";
import { CompletionCache } from "./CompletionCache";
import { sleep, cancelable } from "./utils";
import { Agent, AgentEvent } from "./types";
import {
TabbyApi,
CancelablePromise,
CancelError,
ApiError,
CompletionRequest,
CompletionResponse,
@ -18,11 +18,13 @@ export class TabbyAgent extends EventEmitter implements Agent {
private serverUrl: string = "http://127.0.0.1:5000";
private status: "connecting" | "ready" | "disconnected" = "connecting";
private api: TabbyApi;
private completionCache: CompletionCache;
constructor() {
super();
this.ping();
this.api = new TabbyApi({ BASE: this.serverUrl });
this.completionCache = new CompletionCache();
}
private changeStatus(status: "connecting" | "ready" | "disconnected") {
@ -52,26 +54,20 @@ export class TabbyAgent extends EventEmitter implements Agent {
}
private wrapApiPromise<T>(promise: CancelablePromise<T>): CancelablePromise<T> {
return new CancelablePromise((resolve, reject, onCancel) => {
return cancelable(
promise
.then((resp: T) => {
.then((resolved: T) => {
this.changeStatus("ready");
resolve(resp);
})
.catch((err: CancelError) => {
reject(err);
return resolved;
})
.catch((err: ApiError) => {
this.changeStatus("disconnected");
reject(err);
})
.catch((err: Error) => {
reject(err);
});
onCancel(() => {
throw err;
}),
() => {
promise.cancel();
});
});
}
);
}
public setServerUrl(serverUrl: string): string {
@ -90,12 +86,24 @@ export class TabbyAgent extends EventEmitter implements Agent {
}
public getCompletions(request: CompletionRequest): CancelablePromise<CompletionResponse> {
const promise = this.api.default.completionsV1CompletionsPost(request);
return this.wrapApiPromise(promise);
if (this.completionCache.has(request)) {
return new CancelablePromise((resolve) => {
resolve(this.completionCache.get(request));
});
}
const promise = this.wrapApiPromise(this.api.default.completionsV1CompletionsPost(request));
return cancelable(
promise.then((response: CompletionResponse) => {
this.completionCache.set(request, response);
return response;
}),
() => {
promise.cancel();
}
);
}
public postEvent(request: ChoiceEvent | CompletionEvent): CancelablePromise<any> {
const promise = this.api.default.eventsV1EventsPost(request);
return this.wrapApiPromise(promise);
return this.wrapApiPromise(this.api.default.eventsV1EventsPost(request));
}
}

View File

@ -5,3 +5,23 @@ export function sleep(milliseconds: number) {
export function splitLines(input: string) {
return input.match(/.*(?:$|\r?\n)/g).filter(Boolean) // Split lines and keep newline character
}
export function splitWords(input: string) {
return input.match(/\w+|\W+/g).filter(Boolean); // Split consecutive words and non-words
}
import { CancelablePromise } from "./generated";
export function cancelable<T>(promise: Promise<T>, cancel: () => void): CancelablePromise<T> {
return new CancelablePromise((resolve, reject, onCancel) => {
promise
.then((resp: T) => {
resolve(resp);
})
.catch((err: Error) => {
reject(err);
});
onCancel(() => {
cancel();
});
});
}

View File

@ -1,7 +1,8 @@
import { defineConfig } from "tsup";
import { polyfillNode } from "esbuild-plugin-polyfill-node";
import { dependencies } from "./package.json";
export default [
export default async () => [
defineConfig({
name: "lib-node",
entry: ["src/index.ts"],
@ -32,6 +33,7 @@ export default [
name: "cli",
entry: ["src/cli.ts"],
platform: "node",
noExternal: Object.keys(dependencies),
minify: true,
clean: true,
}),

View File

@ -281,6 +281,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
@ -308,6 +313,14 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
bundle-require@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-4.0.1.tgz#2cc1ad76428043d15e0e7f30990ee3d5404aa2e3"
@ -608,6 +621,11 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
@ -827,6 +845,18 @@ object-assign@^4.0.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
object-hash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
object-sizeof@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/object-sizeof/-/object-sizeof-2.6.1.tgz#1e2b6a01d182c268dbb07ee3403f539de45f63d3"
integrity sha512-a7VJ1Zx7ZuHceKwjgfsSqzV/X0PVGvpZz7ho3Dn4Cs0LLcR5e5WuV+gsbizmplD8s0nAXMJmckKB2rkSiPm/Gg==
dependencies:
buffer "^6.0.3"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"

File diff suppressed because one or more lines are too long

View File

@ -137,6 +137,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
@ -151,6 +156,14 @@ braces@^3.0.2:
dependencies:
fill-range "^7.0.1"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
camelcase-keys@^7.0.0:
version "7.0.2"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252"
@ -434,6 +447,11 @@ hosted-git-info@^4.0.1:
dependencies:
lru-cache "^6.0.0"
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
@ -642,6 +660,18 @@ normalize-package-data@^3.0.2:
semver "^7.3.4"
validate-npm-package-license "^3.0.1"
object-hash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
object-sizeof@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/object-sizeof/-/object-sizeof-2.6.1.tgz#1e2b6a01d182c268dbb07ee3403f539de45f63d3"
integrity sha512-a7VJ1Zx7ZuHceKwjgfsSqzV/X0PVGvpZz7ho3Dn4Cs0LLcR5e5WuV+gsbizmplD8s0nAXMJmckKB2rkSiPm/Gg==
dependencies:
buffer "^6.0.3"
p-event@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5"
@ -876,9 +906,9 @@ string-width@^5.0.1, string-width@^5.1.2:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
dependencies:
ansi-regex "^6.0.1"
@ -901,6 +931,9 @@ supports-color@^5.3.0:
dependencies:
axios "^1.4.0"
form-data "^4.0.0"
lru-cache "^9.1.1"
object-hash "^3.0.0"
object-sizeof "^2.6.1"
to-regex-range@^5.0.1:
version "5.0.1"

View File

@ -103,7 +103,6 @@
},
"dependencies": {
"@sapphire/duration": "^1.1.0",
"linked-list-typescript": "^1.0.15",
"tabby-agent": "file:../tabby-agent"
}
}

View File

@ -1,73 +0,0 @@
import { LinkedList } from "linked-list-typescript";
import { CompletionResponse, Choice } from "tabby-agent";
type Range = {
start: number;
end: number;
};
export type CompletionCacheEntry = {
documentId: any;
promptRange: Range;
prompt: string;
completion: CompletionResponse;
};
export class CompletionCache {
public static capacity = 10;
private cache = new LinkedList<CompletionCacheEntry>();
constructor() {}
private refresh(entry: CompletionCacheEntry) {
this.cache.remove(entry);
this.cache.prepend(entry);
}
public add(entry: CompletionCacheEntry) {
this.cache.prepend(entry);
while (this.cache.length > CompletionCache.capacity) {
this.cache.removeTail();
}
}
public findCompatible(documentId: any, text: string, cursor: number): CompletionResponse | null {
let hit: { entry: CompletionCacheEntry; compatibleChoices: Choice[] } | null = null;
for (const entry of this.cache) {
if (entry.documentId !== documentId) {
continue;
}
// Check if text in prompt range has not changed
if (text.slice(entry.promptRange.start, entry.promptRange.end) !== entry.prompt) {
continue;
}
// Filter choices that start with inputted text after prompt
const compatibleChoices = entry.completion.choices
.filter((choice) => choice.text.startsWith(text.slice(entry.promptRange.end, cursor)))
.map((choice) => {
return {
index: choice.index,
text: choice.text.substring(cursor - entry.promptRange.end),
};
});
if (compatibleChoices.length > 0) {
hit = {
entry,
compatibleChoices,
};
break;
}
}
if (hit) {
this.refresh(hit.entry);
return {
id: hit.entry.completion.id,
created: hit.entry.completion.created,
choices: hit.compatibleChoices,
};
}
return null;
}
}

View File

@ -12,7 +12,6 @@ import {
} from "vscode";
import { CompletionResponse, EventType, ChoiceEvent, CancelablePromise } from "tabby-agent";
import { Agent } from "./Agent";
import { CompletionCache } from "./CompletionCache";
import { sleep } from "./utils";
export class TabbyCompletionProvider implements InlineCompletionItemProvider {
@ -21,7 +20,6 @@ export class TabbyCompletionProvider implements InlineCompletionItemProvider {
private pendingCompletion: CancelablePromise<CompletionResponse> | null = null;
private agent = Agent.getInstance();
private completionCache = new CompletionCache();
// User Settings
private enabled: boolean = true;
private suggestionDelay: number = 150;
@ -61,13 +59,6 @@ export class TabbyCompletionProvider implements InlineCompletionItemProvider {
const replaceRange = this.calculateReplaceRange(document, position);
const compatibleCache = this.completionCache.findCompatible(document.uri, document.getText(), document.offsetAt(position));
if (compatibleCache) {
const completions = this.toInlineCompletions(compatibleCache, replaceRange);
console.debug("Use cached completions: ", compatibleCache);
return Promise.resolve(completions);
}
console.debug(
"Requesting: ",
{
@ -91,14 +82,6 @@ export class TabbyCompletionProvider implements InlineCompletionItemProvider {
});
this.pendingCompletion = null;
if (completion) {
this.completionCache.add({
documentId: document.uri,
promptRange: { start: document.offsetAt(promptRange.start), end: document.offsetAt(promptRange.end) },
prompt,
completion,
});
}
const completions = this.toInlineCompletions(completion, replaceRange);
console.debug("Result completions: ", completions);
return Promise.resolve(completions);

View File

@ -578,7 +578,7 @@ balanced-match@^1.0.0:
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
binary-extensions@^2.0.0:
@ -655,6 +655,14 @@ buffer@^5.5.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@ -1489,9 +1497,9 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
ieee754@^1.1.13:
ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0:
@ -1755,11 +1763,6 @@ lie@~3.3.0:
dependencies:
immediate "~3.0.5"
linked-list-typescript@^1.0.15:
version "1.0.15"
resolved "https://registry.yarnpkg.com/linked-list-typescript/-/linked-list-typescript-1.0.15.tgz#faeed93cf9203f102e2158c29edcddda320abe82"
integrity sha512-RIyUu9lnJIyIaMe63O7/aFv/T2v3KsMFuXMBbUQCHX+cgtGro86ETDj5ed0a8gQL2+DFjzYYsgVG4I36/cUwgw==
linkify-it@^3.0.1:
version "3.0.3"
resolved "https://registry.npmmirror.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
@ -1806,6 +1809,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
lru-cache@^9.1.1:
version "9.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.1.1.tgz#c58a93de58630b688de39ad04ef02ef26f1902f1"
integrity sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==
markdown-it@^12.3.2:
version "12.3.2"
resolved "https://registry.npmmirror.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90"
@ -1989,6 +1997,11 @@ nth-check@^2.0.1:
dependencies:
boolbase "^1.0.0"
object-hash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
object-inspect@^1.9.0:
version "1.12.3"
resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
@ -2007,6 +2020,13 @@ object-keys@^1.1.1:
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object-sizeof@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/object-sizeof/-/object-sizeof-2.6.1.tgz#1e2b6a01d182c268dbb07ee3403f539de45f63d3"
integrity sha512-a7VJ1Zx7ZuHceKwjgfsSqzV/X0PVGvpZz7ho3Dn4Cs0LLcR5e5WuV+gsbizmplD8s0nAXMJmckKB2rkSiPm/Gg==
dependencies:
buffer "^6.0.3"
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@ -2510,6 +2530,9 @@ supports-preserve-symlinks-flag@^1.0.0:
dependencies:
axios "^1.4.0"
form-data "^4.0.0"
lru-cache "^9.1.1"
object-hash "^3.0.0"
object-sizeof "^2.6.1"
tapable@^2.1.1, tapable@^2.2.0:
version "2.2.1"