diff --git a/clients/tabby-agent/src/AgentConfig.ts b/clients/tabby-agent/src/AgentConfig.ts index 8ae757db7e64..8138fac00ed3 100644 --- a/clients/tabby-agent/src/AgentConfig.ts +++ b/clients/tabby-agent/src/AgentConfig.ts @@ -12,6 +12,7 @@ export type AgentConfig = { requestTimeout: number; }; proxy: { + authorization: string; url: string; }; completion: { @@ -128,6 +129,7 @@ export const defaultAgentConfig: AgentConfig = { requestTimeout: 2 * 60 * 1000, // 2 minutes }, proxy: { + authorization: "", url: "", }, completion: { diff --git a/clients/tabby-agent/src/lsp/Server.ts b/clients/tabby-agent/src/lsp/Server.ts index 6b221640efce..c7ff40c49855 100644 --- a/clients/tabby-agent/src/lsp/Server.ts +++ b/clients/tabby-agent/src/lsp/Server.ts @@ -344,54 +344,93 @@ export class Server { } } + private shouldConfigurationUpdate( + clientProvidedConfig: ClientProvidedConfig, + key1: keyof ClientProvidedConfig, + key2?: keyof ClientProvidedConfig, + ): boolean { + const prop = clientProvidedConfig?.[key1]; + const prevProp = this.clientProvidedConfig?.[key1]; + return key2 + ? prop?.[key2 as keyof typeof prop] !== prevProp?.[key2 as keyof typeof prevProp] + : clientProvidedConfig?.[key1] !== this.clientProvidedConfig?.[key1]; + } + + private checkClientProvidedConfig( + clientProvidedConfig: ClientProvidedConfig, + key: keyof ClientProvidedConfig, + x: string, + ): boolean { + const prop = clientProvidedConfig?.[key]; + return prop?.[x as keyof typeof prop] !== undefined && !isBlank(prop[x as keyof typeof prop]); + } + private async updateConfiguration(params: DidChangeConfigurationParams) { const clientProvidedConfig: ClientProvidedConfig | null = params.settings; - if ( - clientProvidedConfig?.server?.endpoint !== undefined && - clientProvidedConfig.server.endpoint !== this.clientProvidedConfig?.server?.endpoint - ) { - if (clientProvidedConfig.server.endpoint.trim().length > 0) { - this.agent.updateConfig("server.endpoint", clientProvidedConfig.server.endpoint); - } else { - this.agent.clearConfig("server.endpoint"); - } - } - if ( - clientProvidedConfig?.server?.token !== undefined && - clientProvidedConfig.server.token !== this.clientProvidedConfig?.server?.token - ) { - if (clientProvidedConfig.server.token.trim().length > 0) { - this.agent.updateConfig("server.token", clientProvidedConfig.server.token); - } else { - this.agent.clearConfig("server.token"); - } - } - if ( - clientProvidedConfig?.anonymousUsageTracking?.disable !== undefined && - clientProvidedConfig.anonymousUsageTracking.disable !== this.clientProvidedConfig?.anonymousUsageTracking?.disable - ) { - if (clientProvidedConfig.anonymousUsageTracking.disable) { - this.agent.updateConfig("anonymousUsageTracking.disable", true); - } else { - this.agent.clearConfig("anonymousUsageTracking.disable"); + + if (!clientProvidedConfig) return; + + const fieldsToCheck = [ + { + key: "server.endpoint", + validation: (key: keyof ClientProvidedConfig, x: string) => + this.checkClientProvidedConfig(clientProvidedConfig, key, x), + }, + { + key: "server.token", + validation: (key: keyof ClientProvidedConfig, x: string) => + this.checkClientProvidedConfig(clientProvidedConfig, key, x), + }, + { + key: "anonymousUsageTracking.disable", + validation: () => clientProvidedConfig?.anonymousUsageTracking?.disable, + }, + { + key: "proxy.url", + validation: (key: keyof ClientProvidedConfig, x: string) => + this.checkClientProvidedConfig(clientProvidedConfig, key, x), + }, + { + key: "proxy.authorization", + validation: (key: keyof ClientProvidedConfig, x: string) => + this.checkClientProvidedConfig(clientProvidedConfig, key, x), + }, + ]; + + for (const { key, validation } of fieldsToCheck) { + const [first, second] = key.split("."); + if ( + this.shouldConfigurationUpdate( + clientProvidedConfig, + first as keyof ClientProvidedConfig, + second as keyof ClientProvidedConfig, + ) + ) { + if (validation(first as keyof ClientProvidedConfig, second!)) { + const firstProp = clientProvidedConfig[first as keyof typeof clientProvidedConfig]; + this.agent.updateConfig(`${first}.${second}`, firstProp?.[second as keyof typeof firstProp]); + } else { + this.agent.clearConfig(`${first}.${second}`); + } } } + const clientType = this.getClientType(this.clientInfo); if ( - clientProvidedConfig?.inlineCompletion?.triggerMode !== undefined && - clientProvidedConfig.inlineCompletion.triggerMode !== this.clientProvidedConfig?.inlineCompletion?.triggerMode + this.shouldConfigurationUpdate( + clientProvidedConfig, + "inlineCompletion", + "triggerMode" as keyof ClientProvidedConfig["inlineCompletion"], + ) ) { this.agent.updateClientProperties( "user", `${clientType}.triggerMode`, - clientProvidedConfig.inlineCompletion?.triggerMode, + clientProvidedConfig!.inlineCompletion?.triggerMode, ); } - if ( - clientProvidedConfig?.keybindings !== undefined && - clientProvidedConfig.keybindings !== this.clientProvidedConfig?.keybindings - ) { - this.agent.updateClientProperties("user", `${clientType}.keybindings`, clientProvidedConfig.keybindings); + if (this.shouldConfigurationUpdate(clientProvidedConfig, "keybindings")) { + this.agent.updateClientProperties("user", `${clientType}.keybindings`, clientProvidedConfig!.keybindings); } this.clientProvidedConfig = clientProvidedConfig; } @@ -535,25 +574,29 @@ export class Server { private createInitConfig(clientProvidedConfig: ClientProvidedConfig | undefined): PartialAgentConfig { const config: PartialAgentConfig = {}; - if (clientProvidedConfig?.server?.endpoint && clientProvidedConfig.server.endpoint.trim().length > 0) { - config.server = { - endpoint: clientProvidedConfig.server.endpoint, - }; + + if (!clientProvidedConfig) { + return config; } - if (clientProvidedConfig?.server?.token && clientProvidedConfig.server.token.trim().length > 0) { - if (config.server) { - config.server.token = clientProvidedConfig.server.token; - } else { - config.server = { - token: clientProvidedConfig.server.token, - }; + + const fieldsToCheck = [ + { name: "server.endpoint", validation: (x?: string) => x && x.trim().length > 0 }, + { name: "server.token", validation: (x?: string) => x && x.trim().length > 0 }, + { name: "anonymousUsageTracking.disable", validation: (x?: boolean) => x !== undefined }, + { name: "proxy.url", validation: (x?: unknown) => x !== undefined }, + { name: "proxy.authorization", validation: (x?: unknown) => x !== undefined }, + ]; + + for (const { name, validation } of fieldsToCheck) { + const [key1, key2] = name.split("."); + const prop = clientProvidedConfig[key1 as keyof ClientProvidedConfig]; + + if (prop && validation(prop[key2 as keyof typeof prop])) { + const configuration = config[key1 as keyof PartialAgentConfig] || {}; + configuration[key2 as keyof typeof prop] = prop[key2 as keyof typeof prop]; + config[key1 as keyof PartialAgentConfig] = configuration; } } - if (clientProvidedConfig?.anonymousUsageTracking?.disable !== undefined) { - config.anonymousUsageTracking = { - disable: clientProvidedConfig.anonymousUsageTracking.disable, - }; - } return config; } diff --git a/clients/tabby-agent/src/lsp/protocol.ts b/clients/tabby-agent/src/lsp/protocol.ts index f04b7751b12a..8e4ce2b8b4a3 100644 --- a/clients/tabby-agent/src/lsp/protocol.ts +++ b/clients/tabby-agent/src/lsp/protocol.ts @@ -200,6 +200,10 @@ export namespace ConfigurationRequest { * [Tabby] Defines the config supported to be changed on the client side (IDE). */ export type ClientProvidedConfig = { + proxy?: { + authorization: string; + url: string; + }; /** * Specifies the endpoint and token for connecting to the Tabby server. */ diff --git a/clients/vscode/src/Config.ts b/clients/vscode/src/Config.ts index 1461bf51743e..181a6311cc6c 100644 --- a/clients/vscode/src/Config.ts +++ b/clients/vscode/src/Config.ts @@ -18,7 +18,12 @@ export class Config extends EventEmitter { super(); context.subscriptions.push( workspace.onDidChangeConfiguration(async (event) => { - if (event.affectsConfiguration("tabby")) { + if ( + event.affectsConfiguration("tabby") || + event.affectsConfiguration("http.proxy") || + event.affectsConfiguration("https.proxy") || + event.affectsConfiguration("http.proxyAuthorization") + ) { this.emit("updated"); } }), @@ -145,8 +150,47 @@ export class Config extends EventEmitter { this.serverEndpoint = config.endpoint; } + get httpConfig() { + return workspace.getConfiguration("http"); + } + + get authorization() { + return this.httpConfig.get("authorization", ""); + } + + set authorization(value: string) { + if (value !== this.authorization) { + this.httpConfig.update("authorization", value); + } + } + + get url() { + const https = workspace.getConfiguration("https"); + const httpsProxy = https.get("proxy", ""); + const httpProxy = this.httpConfig.get("proxy", ""); + + return httpsProxy || httpProxy; + } + + set url(value: string) { + if (value !== this.url) { + const isHTTPS = value.includes("https"); + if (isHTTPS) { + workspace.getConfiguration("https").update("proxy", value); + } else { + this.httpConfig.update("proxy", value); + } + } + } + buildClientProvidedConfig(): ClientProvidedConfig { return { + // Note: current we only support http.proxy | http.authorization + // More properties we will land later. + proxy: { + url: this.url, + authorization: this.authorization, + }, server: { endpoint: this.serverEndpoint, token: this.serverToken,