tabby/clients/tabby-agent/src/AgentConfig.ts

189 lines
5.7 KiB
TypeScript
Raw Normal View History

import { isBrowser } from "./env";
export type AgentConfig = {
server: {
endpoint: string;
token: string;
requestHeaders: Record<string, string | number | boolean | null | undefined>;
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<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? RecursivePartial<U>[]
: T[P] extends object | undefined
? RecursivePartial<T[P]>
: T[P];
};
export type PartialAgentConfig = RecursivePartial<AgentConfig>;
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<typeof chokidar.watch> | 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);
})();