import { isBrowser } from "./env"; export type AgentConfig = { server: { endpoint: string; token: string; requestHeaders: Record; requestTimeout: number; }; completion: { prompt: { experimentalStripAutoClosingCharacters: boolean; maxPrefixLines: number; maxSuffixLines: number; }; debounce: { mode: "adaptive" | "fixed"; interval: number; }; timeout: { auto: number; manually: number; }; }; postprocess: { limitScopeByIndentation: { // When completion is continuing the current line, limit the scope to: // false(default): the line scope, meaning use the next indent level as the limit. // true: the block scope, meaning use the current indent level as the limit. experimentalKeepBlockScopeWhenCompletingLine: boolean; }; }; logs: { level: "debug" | "error" | "silent"; }; anonymousUsageTracking: { disable: boolean; }; }; type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial[] : T[P] extends object | undefined ? RecursivePartial : T[P]; }; export type PartialAgentConfig = RecursivePartial; export const defaultAgentConfig: AgentConfig = { server: { endpoint: "http://localhost:8080", token: "", requestHeaders: {}, requestTimeout: 30000, // 30s }, completion: { prompt: { experimentalStripAutoClosingCharacters: false, maxPrefixLines: 20, maxSuffixLines: 20, }, debounce: { mode: "adaptive", interval: 250, // ms }, // Deprecated: There is a timeout of 3s on the server side since v0.3.0. timeout: { auto: 4000, // 4s manually: 4000, // 4s }, }, postprocess: { limitScopeByIndentation: { experimentalKeepBlockScopeWhenCompletingLine: false, }, }, logs: { level: "silent", }, anonymousUsageTracking: { disable: false, }, }; const configTomlTemplate = `## Tabby agent configuration file ## Online documentation: https://tabby.tabbyml.com/docs/extensions/configuration ## You can uncomment and edit the values below to change the default settings. ## Configurations in this file have lower priority than the IDE settings. ## Server ## You can set the server endpoint here and an optional authentication token if required. # [server] # endpoint = "http://localhost:8080" # http or https URL # token = "your-token-here" # if token is set, request header Authorization = "Bearer $token" will be added automatically ## You can add custom request headers. # [server.requestHeaders] # Header1 = "Value1" # list your custom headers here # Header2 = "Value2" # values can be strings, numbers or booleans ## Logs ## You can set the log level here. The log file is located at ~/.tabby-client/agent/logs/. # [logs] # level = "silent" # "silent" or "error" or "debug" ## Anonymous usage tracking ## Tabby collects anonymous usage data and sends it to the Tabby team to help improve our products. ## Your code, generated completions, or any sensitive information is never tracked or sent. ## For more details on data collection, see https://tabby.tabbyml.com/docs/extensions/configuration#usage-collection ## Your contribution is greatly appreciated. However, if you prefer not to participate, you can disable anonymous usage tracking here. # [anonymousUsageTracking] # disable = false # set to true to disable `; export const userAgentConfig = isBrowser ? null : (() => { const EventEmitter = require("events"); const fs = require("fs-extra"); const toml = require("toml"); const chokidar = require("chokidar"); const deepEqual = require("deep-equal"); class ConfigFile extends EventEmitter { filepath: string; data: PartialAgentConfig = {}; watcher: ReturnType | null = null; logger = require("./logger").rootLogger.child({ component: "ConfigFile" }); constructor(filepath: string) { super(); this.filepath = filepath; } get config(): PartialAgentConfig { return this.data; } async load() { try { const fileContent = await fs.readFile(this.filepath, "utf8"); const data = toml.parse(fileContent); // If the config file contains no value, overwrite it with the new template. if (Object.keys(data).length === 0 && fileContent.trim() !== configTomlTemplate.trim()) { await this.createTemplate(); return; } this.data = data; } catch (error) { if (error.code === "ENOENT") { await this.createTemplate(); } else { this.logger.error({ error }, "Failed to load config file"); } } } async createTemplate() { try { await fs.outputFile(this.filepath, configTomlTemplate); } catch (error) { this.logger.error({ error }, "Failed to create config template file"); } } watch() { this.watcher = chokidar.watch(this.filepath, { interval: 1000, }); const onChanged = async () => { const oldData = this.data; await this.load(); if (!deepEqual(oldData, this.data)) { super.emit("updated", this.data); } }; this.watcher.on("add", onChanged); this.watcher.on("change", onChanged); } } const configFile = require("path").join(require("os").homedir(), ".tabby-client", "agent", "config.toml"); return new ConfigFile(configFile); })();