Agent: Add completion cache. (#158)
parent
48796ecd77
commit
a9d74f7a35
File diff suppressed because one or more lines are too long
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@sapphire/duration": "^1.1.0",
|
||||
"linked-list-typescript": "^1.0.15",
|
||||
"tabby-agent": "file:../tabby-agent"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue