var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; // src/index.ts var src_exports = {}; __export(src_exports, { CancelablePromise: () => CancelablePromise, TabbyAgent: () => TabbyAgent, agentEventNames: () => agentEventNames }); module.exports = __toCommonJS(src_exports); // src/TabbyAgent.ts var import_events2 = require("events"); var import_uuid2 = require("uuid"); var import_deep_equal = __toESM(require("deep-equal")); var import_deepmerge = __toESM(require("deepmerge")); // src/generated/core/BaseHttpRequest.ts var BaseHttpRequest = class { constructor(config) { this.config = config; } }; // src/generated/core/request.ts var import_axios = __toESM(require("axios")); var import_form_data = __toESM(require("form-data")); // src/generated/core/ApiError.ts var ApiError = class extends Error { constructor(request2, response, message) { super(message); this.name = "ApiError"; this.url = response.url; this.status = response.status; this.statusText = response.statusText; this.body = response.body; this.request = request2; } }; // src/generated/core/CancelablePromise.ts var CancelError = class extends Error { constructor(message) { super(message); this.name = "CancelError"; } get isCancelled() { return true; } }; var _isResolved, _isRejected, _isCancelled, _cancelHandlers, _promise, _resolve, _reject; var CancelablePromise = class { constructor(executor) { __privateAdd(this, _isResolved, void 0); __privateAdd(this, _isRejected, void 0); __privateAdd(this, _isCancelled, void 0); __privateAdd(this, _cancelHandlers, void 0); __privateAdd(this, _promise, void 0); __privateAdd(this, _resolve, void 0); __privateAdd(this, _reject, void 0); __privateSet(this, _isResolved, false); __privateSet(this, _isRejected, false); __privateSet(this, _isCancelled, false); __privateSet(this, _cancelHandlers, []); __privateSet(this, _promise, new Promise((resolve2, reject) => { __privateSet(this, _resolve, resolve2); __privateSet(this, _reject, reject); const onResolve = (value) => { var _a; if (__privateGet(this, _isResolved) || __privateGet(this, _isRejected) || __privateGet(this, _isCancelled)) { return; } __privateSet(this, _isResolved, true); (_a = __privateGet(this, _resolve)) == null ? void 0 : _a.call(this, value); }; const onReject = (reason) => { var _a; if (__privateGet(this, _isResolved) || __privateGet(this, _isRejected) || __privateGet(this, _isCancelled)) { return; } __privateSet(this, _isRejected, true); (_a = __privateGet(this, _reject)) == null ? void 0 : _a.call(this, reason); }; const onCancel = (cancelHandler) => { if (__privateGet(this, _isResolved) || __privateGet(this, _isRejected) || __privateGet(this, _isCancelled)) { return; } __privateGet(this, _cancelHandlers).push(cancelHandler); }; Object.defineProperty(onCancel, "isResolved", { get: () => __privateGet(this, _isResolved) }); Object.defineProperty(onCancel, "isRejected", { get: () => __privateGet(this, _isRejected) }); Object.defineProperty(onCancel, "isCancelled", { get: () => __privateGet(this, _isCancelled) }); return executor(onResolve, onReject, onCancel); })); } get [Symbol.toStringTag]() { return "Cancellable Promise"; } then(onFulfilled, onRejected) { return __privateGet(this, _promise).then(onFulfilled, onRejected); } catch(onRejected) { return __privateGet(this, _promise).catch(onRejected); } finally(onFinally) { return __privateGet(this, _promise).finally(onFinally); } cancel() { var _a; if (__privateGet(this, _isResolved) || __privateGet(this, _isRejected) || __privateGet(this, _isCancelled)) { return; } __privateSet(this, _isCancelled, true); if (__privateGet(this, _cancelHandlers).length) { try { for (const cancelHandler of __privateGet(this, _cancelHandlers)) { cancelHandler(); } } catch (error) { console.warn("Cancellation threw an error", error); return; } } __privateGet(this, _cancelHandlers).length = 0; (_a = __privateGet(this, _reject)) == null ? void 0 : _a.call(this, new CancelError("Request aborted")); } get isCancelled() { return __privateGet(this, _isCancelled); } }; _isResolved = new WeakMap(); _isRejected = new WeakMap(); _isCancelled = new WeakMap(); _cancelHandlers = new WeakMap(); _promise = new WeakMap(); _resolve = new WeakMap(); _reject = new WeakMap(); // src/generated/core/request.ts var isDefined = (value) => { return value !== void 0 && value !== null; }; var isString = (value) => { return typeof value === "string"; }; var isStringWithValue = (value) => { return isString(value) && value !== ""; }; var isBlob = (value) => { return typeof value === "object" && typeof value.type === "string" && typeof value.stream === "function" && typeof value.arrayBuffer === "function" && typeof value.constructor === "function" && typeof value.constructor.name === "string" && /^(Blob|File)$/.test(value.constructor.name) && /^(Blob|File)$/.test(value[Symbol.toStringTag]); }; var isFormData = (value) => { return value instanceof import_form_data.default; }; var isSuccess = (status) => { return status >= 200 && status < 300; }; var base64 = (str) => { try { return btoa(str); } catch (err) { return Buffer.from(str).toString("base64"); } }; var getQueryString = (params) => { const qs = []; const append = (key, value) => { qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); }; const process2 = (key, value) => { if (isDefined(value)) { if (Array.isArray(value)) { value.forEach((v) => { process2(key, v); }); } else if (typeof value === "object") { Object.entries(value).forEach(([k, v]) => { process2(`${key}[${k}]`, v); }); } else { append(key, value); } } }; Object.entries(params).forEach(([key, value]) => { process2(key, value); }); if (qs.length > 0) { return `?${qs.join("&")}`; } return ""; }; var getUrl = (config, options) => { const encoder = config.ENCODE_PATH || encodeURI; const path = options.url.replace("{api-version}", config.VERSION).replace(/{(.*?)}/g, (substring, group) => { if (options.path?.hasOwnProperty(group)) { return encoder(String(options.path[group])); } return substring; }); const url = `${config.BASE}${path}`; if (options.query) { return `${url}${getQueryString(options.query)}`; } return url; }; var getFormData = (options) => { if (options.formData) { const formData = new import_form_data.default(); const process2 = (key, value) => { if (isString(value) || isBlob(value)) { formData.append(key, value); } else { formData.append(key, JSON.stringify(value)); } }; Object.entries(options.formData).filter(([_, value]) => isDefined(value)).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach((v) => process2(key, v)); } else { process2(key, value); } }); return formData; } return void 0; }; var resolve = async (options, resolver) => { if (typeof resolver === "function") { return resolver(options); } return resolver; }; var getHeaders = async (config, options, formData) => { const token = await resolve(options, config.TOKEN); const username = await resolve(options, config.USERNAME); const password = await resolve(options, config.PASSWORD); const additionalHeaders = await resolve(options, config.HEADERS); const formHeaders = typeof formData?.getHeaders === "function" && formData?.getHeaders() || {}; const headers = Object.entries({ Accept: "application/json", ...additionalHeaders, ...options.headers, ...formHeaders }).filter(([_, value]) => isDefined(value)).reduce((headers2, [key, value]) => ({ ...headers2, [key]: String(value) }), {}); if (isStringWithValue(token)) { headers["Authorization"] = `Bearer ${token}`; } if (isStringWithValue(username) && isStringWithValue(password)) { const credentials = base64(`${username}:${password}`); headers["Authorization"] = `Basic ${credentials}`; } if (options.body) { if (options.mediaType) { headers["Content-Type"] = options.mediaType; } else if (isBlob(options.body)) { headers["Content-Type"] = options.body.type || "application/octet-stream"; } else if (isString(options.body)) { headers["Content-Type"] = "text/plain"; } else if (!isFormData(options.body)) { headers["Content-Type"] = "application/json"; } } return headers; }; var getRequestBody = (options) => { if (options.body) { return options.body; } return void 0; }; var sendRequest = async (config, options, url, body, formData, headers, onCancel) => { const source = import_axios.default.CancelToken.source(); const requestConfig = { url, headers, data: body ?? formData, method: options.method, withCredentials: config.WITH_CREDENTIALS, cancelToken: source.token }; onCancel(() => source.cancel("The user aborted a request.")); try { return await import_axios.default.request(requestConfig); } catch (error) { const axiosError = error; if (axiosError.response) { return axiosError.response; } throw error; } }; var getResponseHeader = (response, responseHeader) => { if (responseHeader) { const content = response.headers[responseHeader]; if (isString(content)) { return content; } } return void 0; }; var getResponseBody = (response) => { if (response.status !== 204) { return response.data; } return void 0; }; var catchErrorCodes = (options, result) => { const errors = { 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Not Found", 500: "Internal Server Error", 502: "Bad Gateway", 503: "Service Unavailable", ...options.errors }; const error = errors[result.status]; if (error) { throw new ApiError(options, result, error); } if (!result.ok) { throw new ApiError(options, result, "Generic Error"); } }; var request = (config, options) => { return new CancelablePromise(async (resolve2, reject, onCancel) => { try { const url = getUrl(config, options); const formData = getFormData(options); const body = getRequestBody(options); const headers = await getHeaders(config, options, formData); if (!onCancel.isCancelled) { const response = await sendRequest(config, options, url, body, formData, headers, onCancel); const responseBody = getResponseBody(response); const responseHeader = getResponseHeader(response, options.responseHeader); const result = { url, ok: isSuccess(response.status), status: response.status, statusText: response.statusText, body: responseHeader ?? responseBody }; catchErrorCodes(options, result); resolve2(result.body); } } catch (error) { reject(error); } }); }; // src/generated/core/AxiosHttpRequest.ts var AxiosHttpRequest = class extends BaseHttpRequest { constructor(config) { super(config); } /** * Request method * @param options The request options from the service * @returns CancelablePromise * @throws ApiError */ request(options) { return request(this.config, options); } }; // src/generated/services/V1Service.ts var V1Service = class { constructor(httpRequest) { this.httpRequest = httpRequest; } /** * @param requestBody * @returns CompletionResponse Success * @throws ApiError */ completion(requestBody) { return this.httpRequest.request({ method: "POST", url: "/v1/completions", body: requestBody, mediaType: "application/json", errors: { 400: `Bad Request` } }); } /** * @param requestBody * @returns any Success * @throws ApiError */ event(requestBody) { return this.httpRequest.request({ method: "POST", url: "/v1/events", body: requestBody, mediaType: "application/json", errors: { 400: `Bad Request` } }); } /** * @returns HealthState Success * @throws ApiError */ health() { return this.httpRequest.request({ method: "POST", url: "/v1/health" }); } }; // src/generated/TabbyApi.ts var TabbyApi = class { constructor(config, HttpRequest = AxiosHttpRequest) { this.request = new HttpRequest({ BASE: config?.BASE ?? "https://playground.app.tabbyml.com/tabby", VERSION: config?.VERSION ?? "0.1.0", WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false, CREDENTIALS: config?.CREDENTIALS ?? "include", TOKEN: config?.TOKEN, USERNAME: config?.USERNAME, PASSWORD: config?.PASSWORD, HEADERS: config?.HEADERS, ENCODE_PATH: config?.ENCODE_PATH }); this.v1 = new V1Service(this.request); } }; // src/utils.ts function splitLines(input) { return input.match(/.*(?:$|\r?\n)/g).filter(Boolean); } function splitWords(input) { return input.match(/\w+|\W+/g).filter(Boolean); } function isBlank(input) { return input.trim().length === 0; } function cancelable(promise, cancel) { return new CancelablePromise((resolve2, reject, onCancel) => { promise.then((resp) => { resolve2(resp); }).catch((err) => { reject(err); }); onCancel(() => { cancel(); }); }); } // src/Auth.ts var import_events = require("events"); var import_jwt_decode = __toESM(require("jwt-decode")); // src/cloud/services/ApiService.ts var ApiService = class { constructor(httpRequest) { this.httpRequest = httpRequest; } /** * @returns DeviceTokenResponse Success * @throws ApiError */ deviceToken(body) { return this.httpRequest.request({ method: "POST", url: "/device-token", body }); } /** * @param code * @returns DeviceTokenAcceptResponse Success * @throws ApiError */ deviceTokenAccept(query) { return this.httpRequest.request({ method: "POST", url: "/device-token/accept", query }); } /** * @param token * @returns DeviceTokenRefreshResponse Success * @throws ApiError */ deviceTokenRefresh(token) { return this.httpRequest.request({ method: "POST", url: "/device-token/refresh", headers: { Authorization: `Bearer ${token}` } }); } /** * @param body object for anonymous usage tracking */ usage(body) { return this.httpRequest.request({ method: "POST", url: "/usage", body }); } }; // src/cloud/CloudApi.ts var CloudApi = class { constructor(config, HttpRequest = AxiosHttpRequest) { this.request = new HttpRequest({ BASE: config?.BASE ?? "https://app.tabbyml.com/api", VERSION: config?.VERSION ?? "0.0.0", WITH_CREDENTIALS: config?.WITH_CREDENTIALS ?? false, CREDENTIALS: config?.CREDENTIALS ?? "include", TOKEN: config?.TOKEN, USERNAME: config?.USERNAME, PASSWORD: config?.PASSWORD, HEADERS: config?.HEADERS, ENCODE_PATH: config?.ENCODE_PATH }); this.api = new ApiService(this.request); } }; // src/env.ts var isBrowser = false; // src/dataStore.ts var dataStore = isBrowser ? null : (() => { const dataFile = require("path").join(require("os").homedir(), ".tabby", "agent", "data.json"); const fs = require("fs-extra"); return { data: {}, load: async function() { this.data = await fs.readJson(dataFile, { throws: false }) || {}; }, save: async function() { await fs.outputJson(dataFile, this.data); } }; })(); // src/logger.ts var import_pino = __toESM(require("pino")); var stream = isBrowser ? null : ( /** * Default rotating file locate at `~/.tabby/agent-logs/`. */ require("rotating-file-stream").createStream("tabby-agent.log", { path: require("path").join(require("os").homedir(), ".tabby", "agent-logs"), size: "10M", interval: "1d" }) ); var rootLogger = !!stream ? (0, import_pino.default)(stream) : (0, import_pino.default)(); var allLoggers = [rootLogger]; rootLogger.onChild = (child) => { allLoggers.push(child); }; // src/Auth.ts var _Auth = class extends import_events.EventEmitter { constructor(options) { super(); this.logger = rootLogger.child({ component: "Auth" }); this.dataStore = null; this.pollingTokenTimer = null; this.stopPollingTokenTimer = null; this.refreshTokenTimer = null; this.authApi = null; this.jwt = null; this.endpoint = options.endpoint; this.dataStore = options.dataStore || dataStore; this.authApi = new CloudApi(); } static async create(options) { const auth = new _Auth(options); await auth.load(); return auth; } get token() { return this.jwt?.token; } get user() { return this.jwt?.payload.email; } async load() { if (!this.dataStore) return; try { await this.dataStore.load(); const storedJwt = this.dataStore.data["auth"]?.[this.endpoint]?.jwt; if (typeof storedJwt === "string" && this.jwt?.token !== storedJwt) { this.logger.debug({ storedJwt }, "Load jwt from data store."); const jwt = { token: storedJwt, payload: (0, import_jwt_decode.default)(storedJwt) }; if (jwt.payload.exp * 1e3 - Date.now() < _Auth.tokenStrategy.refresh.beforeExpire) { this.jwt = await this.refreshToken(jwt, _Auth.tokenStrategy.refresh.whenLoaded); await this.save(); } else { this.jwt = jwt; } this.scheduleRefreshToken(); } } catch (error) { this.logger.debug({ error }, "Error when loading auth"); } } async save() { if (!this.dataStore) return; try { if (this.jwt) { if (this.dataStore.data["auth"]?.[this.endpoint]?.jwt === this.jwt.token) return; this.dataStore.data["auth"] = { ...this.dataStore.data["auth"], [this.endpoint]: { jwt: this.jwt.token } }; } else { if (typeof this.dataStore.data["auth"]?.[this.endpoint] === "undefined") return; delete this.dataStore.data["auth"][this.endpoint]; } await this.dataStore.save(); this.logger.debug("Save changes to data store."); } catch (error) { this.logger.error({ error }, "Error when saving auth"); } } async reset() { if (this.jwt) { this.jwt = null; await this.save(); } if (this.refreshTokenTimer) { clearTimeout(this.refreshTokenTimer); this.refreshTokenTimer = null; } if (this.pollingTokenTimer) { clearInterval(this.pollingTokenTimer); this.pollingTokenTimer = null; } if (this.stopPollingTokenTimer) { clearTimeout(this.stopPollingTokenTimer); this.stopPollingTokenTimer = null; } } async requestToken() { try { await this.reset(); this.logger.debug("Start to request device token"); const deviceToken = await this.authApi.api.deviceToken({ auth_url: this.endpoint }); this.logger.debug({ deviceToken }, "Request device token response"); const authUrl = new URL(_Auth.authPageUrl); authUrl.searchParams.append("code", deviceToken.data.code); this.schedulePollingToken(deviceToken.data.code); return authUrl.toString(); } catch (error) { this.logger.error({ error }, "Error when requesting token"); throw error; } } async refreshToken(jwt, options = { maxTry: 1, retryDelay: 1e3 }, retry = 0) { try { this.logger.debug({ retry }, "Start to refresh token"); const refreshedJwt = await this.authApi.api.deviceTokenRefresh(jwt.token); this.logger.debug({ refreshedJwt }, "Refresh token response"); return { token: refreshedJwt.data.jwt, payload: (0, import_jwt_decode.default)(refreshedJwt.data.jwt) }; } catch (error) { if (error instanceof ApiError && [400, 401, 403, 405].indexOf(error.status) !== -1) { this.logger.debug({ error }, "Error when refreshing jwt"); } else { this.logger.error({ error }, "Unknown error when refreshing jwt"); if (retry < options.maxTry) { this.logger.debug(`Retry refreshing jwt after ${options.retryDelay}ms`); await new Promise((resolve2) => setTimeout(resolve2, options.retryDelay)); return this.refreshToken(jwt, options, retry + 1); } } throw { ...error, retry }; } } async schedulePollingToken(code) { this.pollingTokenTimer = setInterval(async () => { try { const response = await this.authApi.api.deviceTokenAccept({ code }); this.logger.debug({ response }, "Poll jwt response"); this.jwt = { token: response.data.jwt, payload: (0, import_jwt_decode.default)(response.data.jwt) }; await this.save(); this.scheduleRefreshToken(); super.emit("updated", this.jwt); clearInterval(this.pollingTokenTimer); this.pollingTokenTimer = null; } catch (error) { if (error instanceof ApiError && [400, 401, 403, 405].indexOf(error.status) !== -1) { this.logger.debug({ error }, "Expected error when polling jwt"); } else { this.logger.error({ error }, "Error when polling jwt"); } } }, _Auth.tokenStrategy.polling.interval); this.stopPollingTokenTimer = setTimeout(() => { if (this.pollingTokenTimer) { clearInterval(this.pollingTokenTimer); this.pollingTokenTimer = null; } }, _Auth.tokenStrategy.polling.timeout); } scheduleRefreshToken() { if (this.refreshTokenTimer) { clearTimeout(this.refreshTokenTimer); this.refreshTokenTimer = null; } if (!this.jwt) { return null; } this.refreshTokenTimer = setInterval(async () => { if (this.jwt.payload.exp * 1e3 - Date.now() < _Auth.tokenStrategy.refresh.beforeExpire) { try { this.jwt = await this.refreshToken(this.jwt, _Auth.tokenStrategy.refresh.whenScheduled); await this.save(); this.scheduleRefreshToken(); super.emit("updated", this.jwt); } catch (error) { this.logger.error({ error }, "Error when refreshing jwt"); } } else { this.logger.debug("Check token, still valid"); } }, _Auth.tokenStrategy.refresh.interval); } }; var Auth = _Auth; Auth.authPageUrl = "https://app.tabbyml.com/account/device-token"; Auth.tokenStrategy = { polling: { // polling token after auth url generated interval: 5e3, // polling token every 5 seconds timeout: 5 * 60 * 1e3 // stop polling after trying for 5 min }, refresh: { // check token every 15 min, refresh token if it expires in 30 min interval: 15 * 60 * 1e3, beforeExpire: 30 * 60 * 1e3, whenLoaded: { // after token loaded from data store, refresh token if it is about to expire or has expired maxTry: 5, // keep loading time not too long retryDelay: 1e3 // retry after 1 seconds }, whenScheduled: { // if running until token is about to expire, refresh token as scheduled maxTry: 60, retryDelay: 30 * 1e3 // retry after 30 seconds } } }; // src/AgentConfig.ts var defaultAgentConfig = { server: { endpoint: "http://localhost:8080" }, logs: { level: "silent" }, anonymousUsageTracking: { disable: false } }; // 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")); var CompletionCache = class { constructor() { this.logger = rootLogger.child({ component: "CompletionCache" }); 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.logger.debug({ entry }, "Setting cache entry"); this.cache.set(this.hash(entry.key), entry.value); } this.logger.debug({ size: this.cache.calculatedSize }, "Cache size"); } 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, text: key.text.slice(0, key.position) + prefix + key.text.slice(key.position), position: key.position + prefix.length }; 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/postprocess/filter.ts var logger = rootLogger.child({ component: "Postprocess" }); var applyFilter = (filter) => { return async (response) => { response.choices = (await Promise.all( response.choices.map(async (choice) => { choice.text = await filter(choice.text); return choice; }) )).filter(Boolean); return response; }; }; // src/postprocess/limitScopeByIndentation.ts function calcIndentLevel(line) { return line.match(/^[ \t]*/)?.[0]?.length || 0; } function isIndentBlockClosingAllowed(currentIndentLevel, suffixLines) { let index = 1; while (index < suffixLines.length && isBlank(suffixLines[index])) { index++; } if (index >= suffixLines.length) { return true; } else { const indentLevel = calcIndentLevel(suffixLines[index]); return indentLevel < currentIndentLevel; } } function isOpeningIndentBlock(lines, index) { if (index >= lines.length - 1) { return false; } return calcIndentLevel(lines[index]) < calcIndentLevel(lines[index + 1]); } var limitScopeByIndentation = (context) => { return (input) => { const prefix = context.text.slice(0, context.position); const suffix = context.text.slice(context.position); const prefixLines = splitLines(prefix); const suffixLines = splitLines(suffix); const inputLines = splitLines(input); const currentIndentLevel = calcIndentLevel(prefixLines[prefixLines.length - 1]); let index; for (index = 1; index < inputLines.length; index++) { if (isBlank(inputLines[index])) { continue; } const indentLevel = calcIndentLevel(inputLines[index]); if (indentLevel < currentIndentLevel) { if (isIndentBlockClosingAllowed(currentIndentLevel, suffixLines) && !isOpeningIndentBlock(inputLines, index)) { index++; } break; } } if (index < inputLines.length) { logger.debug({ input, prefix, suffix, scopeEndAt: index }, "Remove content out of scope"); return inputLines.slice(0, index).join("").trimEnd(); } return input; }; }; // src/postprocess/removeOverlapping.ts var removeOverlapping = (context) => { return (input) => { const suffix = context.text.slice(context.position); for (let index = Math.max(0, input.length - suffix.length); index < input.length; index++) { if (input.slice(index) === suffix.slice(0, input.length - index)) { logger.debug({ input, suffix, overlappedAt: index }, "Remove overlapped content"); return input.slice(0, index); } } return input; }; }; // src/postprocess/dropBlank.ts var dropBlank = () => { return (input) => { return isBlank(input) ? null : input; }; }; // src/postprocess/index.ts async function postprocess(request2, response) { return new Promise((resolve2) => resolve2(response)).then(applyFilter(limitScopeByIndentation(request2))).then(applyFilter(removeOverlapping(request2))).then(applyFilter(dropBlank())); } // package.json var name = "tabby-agent"; var version = "0.0.1"; // src/AnonymousUsageLogger.ts var import_uuid = require("uuid"); var AnonymousUsageLogger = class { constructor() { this.anonymousUsageTrackingApi = new CloudApi(); this.logger = rootLogger.child({ component: "AnonymousUsage" }); this.systemData = { agent: `${name}, ${version}`, browser: isBrowser ? navigator?.userAgent || "browser" : void 0, node: isBrowser ? void 0 : `${process.version} ${process.platform} ${require("os").arch()} ${require("os").release()}` }; this.dataStore = null; } static async create(options) { const logger2 = new AnonymousUsageLogger(); logger2.dataStore = options.dataStore || dataStore; await logger2.checkAnonymousId(); return logger2; } async checkAnonymousId() { if (this.dataStore) { try { await this.dataStore.load(); } catch (error) { this.logger.debug({ error }, "Error when loading anonymousId"); } if (typeof this.dataStore.data["anonymousId"] === "string") { this.anonymousId = this.dataStore.data["anonymousId"]; } else { this.anonymousId = (0, import_uuid.v4)(); this.dataStore.data["anonymousId"] = this.anonymousId; try { await this.dataStore.save(); } catch (error) { this.logger.debug({ error }, "Error when saving anonymousId"); } } } else { this.anonymousId = (0, import_uuid.v4)(); } } async event(event, data) { if (this.disabled) { return; } await this.anonymousUsageTrackingApi.api.usage({ distinctId: this.anonymousId, event, properties: { ...this.systemData, ...data } }).catch((error) => { this.logger.error({ error }, "Error when sending anonymous usage data"); }); } }; // src/TabbyAgent.ts var _TabbyAgent = class extends import_events2.EventEmitter { constructor() { super(); this.logger = rootLogger.child({ component: "TabbyAgent" }); this.config = defaultAgentConfig; this.status = "notInitialized"; this.dataStore = null; this.completionCache = new CompletionCache(); // 30s this.tryingConnectTimer = null; this.tryingConnectTimer = setInterval(async () => { if (this.status === "disconnected") { this.logger.debug("Trying to connect..."); await this.healthCheck(); } }, _TabbyAgent.tryConnectInterval); } static async create(options) { const agent = new _TabbyAgent(); agent.dataStore = options?.dataStore; agent.anonymousUsageLogger = await AnonymousUsageLogger.create({ dataStore: options?.dataStore }); return agent; } async applyConfig() { allLoggers.forEach((logger2) => logger2.level = this.config.logs.level); this.anonymousUsageLogger.disabled = this.config.anonymousUsageTracking.disable; if (this.config.server.endpoint !== this.auth?.endpoint) { this.auth = await Auth.create({ endpoint: this.config.server.endpoint, dataStore: this.dataStore }); this.auth.on("updated", this.setupApi.bind(this)); } await this.setupApi(); } async setupApi() { this.api = new TabbyApi({ BASE: this.config.server.endpoint.replace(/\/+$/, ""), // remove trailing slash TOKEN: this.auth?.token }); await this.healthCheck(); } changeStatus(status) { if (this.status != status) { this.status = status; const event = { event: "statusChanged", status }; this.logger.debug({ event }, "Status changed"); super.emit("statusChanged", event); } } callApi(api, request2) { this.logger.debug({ api: api.name, request: request2 }, "API request"); const promise = api.call(this.api.v1, request2); return cancelable( promise.then((response) => { this.logger.debug({ api: api.name, response }, "API response"); this.changeStatus("ready"); return response; }).catch((error) => { if (!!error.isCancelled) { this.logger.debug({ api: api.name, error }, "API request canceled"); } else if (error.name === "ApiError" && [401, 403, 405].indexOf(error.status) !== -1) { this.logger.debug({ api: api.name, error }, "API unauthorized"); this.changeStatus("unauthorized"); } else if (error.name === "ApiError") { this.logger.error({ api: api.name, error }, "API error"); this.changeStatus("disconnected"); } else { this.logger.error({ api: api.name, error }, "API request failed with unknown error"); this.changeStatus("disconnected"); } throw error; }), () => { promise.cancel(); } ); } healthCheck() { return this.callApi(this.api.v1.health, {}).catch(() => { }); } createSegments(request2) { const maxPrefixLines = request2.maxPrefixLines; const maxSuffixLines = request2.maxSuffixLines; const prefix = request2.text.slice(0, request2.position); const prefixLines = splitLines(prefix); const suffix = request2.text.slice(request2.position); const suffixLines = splitLines(suffix); return { prefix: prefixLines.slice(Math.max(prefixLines.length - maxPrefixLines, 0)).join(""), suffix: suffixLines.slice(0, maxSuffixLines).join("") }; } async initialize(options) { if (options.client) { allLoggers.forEach((logger2) => logger2.setBindings?.({ client: options.client })); } if (options.config) { this.config = (0, import_deepmerge.default)(this.config, options.config); } await this.applyConfig(); await this.anonymousUsageLogger.event("AgentInitialized", { client: options.client }); this.logger.debug({ options }, "Initialized"); return this.status !== "notInitialized"; } async updateConfig(config) { const mergedConfig = (0, import_deepmerge.default)(this.config, config); if (!(0, import_deep_equal.default)(this.config, mergedConfig)) { this.config = mergedConfig; await this.applyConfig(); const event = { event: "configUpdated", config: this.config }; this.logger.debug({ event }, "Config updated"); super.emit("configUpdated", event); } return true; } getConfig() { return this.config; } getStatus() { return this.status; } startAuth() { if (this.status === "notInitialized") { throw new Error("Agent is not initialized"); } return cancelable( this.healthCheck().then(() => { if (this.status === "unauthorized") { return this.auth.requestToken(); } return null; }), () => { if (this.status === "unauthorized") { this.auth.reset(); } } ); } getCompletions(request2) { if (this.status === "notInitialized") { throw new Error("Agent is not initialized"); } if (this.completionCache.has(request2)) { this.logger.debug({ request: request2 }, "Completion cache hit"); return new CancelablePromise((resolve2) => { resolve2(this.completionCache.get(request2)); }); } const segments = this.createSegments(request2); if (isBlank(segments.prefix)) { this.logger.debug("Segment prefix is blank, returning empty completion response"); return new CancelablePromise((resolve2) => { resolve2({ id: "agent-" + (0, import_uuid2.v4)(), choices: [] }); }); } const promise = this.callApi(this.api.v1.completion, { language: request2.language, segments, user: this.auth?.user }); return cancelable( promise.then((response) => { this.completionCache.set(request2, response); return response; }).then((response) => { return postprocess(request2, response); }), () => { promise.cancel(); } ); } postEvent(request2) { if (this.status === "notInitialized") { throw new Error("Agent is not initialized"); } return this.callApi(this.api.v1.event, request2); } }; var TabbyAgent = _TabbyAgent; TabbyAgent.tryConnectInterval = 1e3 * 30; // src/Agent.ts var agentEventNames = ["statusChanged", "configUpdated"]; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { CancelablePromise, TabbyAgent, agentEventNames }); //# sourceMappingURL=index.js.map