From 70db14a86870e4df043c34547fc664518cd658bf Mon Sep 17 00:00:00 2001 From: Kalan <51868853+kalanchan@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:22:29 -0800 Subject: [PATCH] VS Code: Release v1.40.2 (#6064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry picking #6041 to pass unit test … (#6041) VSCode's configuration setting parsing will split dotted names where they appear at the top level. Retrieving will walk nested property names concatenated by a dot ...but not dotted property names. See the unit tests for details. ## Test plan ``` pnpm -C agent test ``` --------- ## Test plan ## Changelog Co-authored-by: Dominic Cooney Co-authored-by: Piotr Kukielka --- agent/src/AgentWorkspaceConfiguration.test.ts | 40 ++++- agent/src/AgentWorkspaceConfiguration.ts | 159 +++++++++--------- 2 files changed, 114 insertions(+), 85 deletions(-) diff --git a/agent/src/AgentWorkspaceConfiguration.test.ts b/agent/src/AgentWorkspaceConfiguration.test.ts index 317a7a48301e..845d623800db 100644 --- a/agent/src/AgentWorkspaceConfiguration.test.ts +++ b/agent/src/AgentWorkspaceConfiguration.test.ts @@ -39,7 +39,8 @@ describe('AgentWorkspaceConfiguration', () => { "d1.d2": { "v": 1 } - } + }, + "dotted.property.name": 42 } } ` @@ -65,10 +66,11 @@ describe('AgentWorkspaceConfiguration', () => { describe('get', () => { it('can return sub-configuration object', () => { - expect(config.get('cody.serverEndpoint')).toBe('https://sourcegraph.test') + expect(config.get('cody.serverEndpoint')).toEqual('https://sourcegraph.test') expect(config.get('cody.customHeaders')).toEqual({ 'X-Test': 'test' }) expect(config.get('cody.telemetry.level')).toBe('agent') - expect(config.get('cody.telemetry.clientName')).toBe('test-client') + // clientName undefined because custom JSON specified telemetry with level alone. + expect(config.get('cody.telemetry.clientName')).toBeUndefined() expect(config.get('cody.autocomplete.enabled')).toBe(true) expect(config.get('cody.autocomplete.advanced.provider')).toBe('anthropic') expect(config.get('cody.autocomplete.advanced.model')).toBe('claude-2') @@ -127,16 +129,40 @@ describe('AgentWorkspaceConfiguration', () => { }, serverEndpoint: 'https://sourcegraph.test', telemetry: { - clientName: 'test-client', level: 'agent', }, }) }) it('handles parsing nested keys as objects', () => { - expect(config.get('foo.bar.baz.qux')).toBe(true) - expect(config.get('foo.bar.baz')).toStrictEqual({ d1: { d2: { v: 1 } }, qux: true }) - expect(config.get('foo.bar.baz.d1')).toStrictEqual({ d2: { v: 1 } }) + expect(config.get('foo.bar.baz')).toStrictEqual({ 'd1.d2': { v: 1 } }) + }) + + // This reflects VSCode's behavior around configuration. OpenContext + // providers rely on this behavior, specifically, that property names + // at the top level are split on dots (so 'openctx.provider' is part of + // 'openctx') but within subobjects are not split (so + // 'https://github.com/foo/bar': true is not split into 'https://github' + // and 'com/foo/bar') + it('does not split apart dotted property names in nested objects', () => { + // The shape of the foo object, note the outermost dotted name + // foo.bar can be split into foo and bar. + expect(config.get('foo')).toStrictEqual({ + bar: { + 'baz.qux': true, + baz: { + 'd1.d2': { + v: 1, + }, + }, + 'dotted.property.name': 42, + }, + }) + // 'baz.qux' is an atom different to baz containing qux. It is not + // outermost thus it is not split. + expect(config.get('foo.bar.baz.qux')).toStrictEqual(undefined) + // 'd1.d2' is an atom that should not be split at d1. + expect(config.get('foo.bar.baz.d1')).toBeUndefined() }) it('handles agent capabilities correctly', () => { diff --git a/agent/src/AgentWorkspaceConfiguration.ts b/agent/src/AgentWorkspaceConfiguration.ts index 1d5206e3d53b..778611cb6bb8 100644 --- a/agent/src/AgentWorkspaceConfiguration.ts +++ b/agent/src/AgentWorkspaceConfiguration.ts @@ -13,61 +13,7 @@ export class AgentWorkspaceConfiguration implements vscode.WorkspaceConfiguratio private clientInfo: () => ClientInfo | undefined, private extensionConfig: () => ExtensionConfiguration | undefined, private dictionary: any = {} - ) { - const config = this.extensionConfig() - const capabilities = this.clientInfo()?.capabilities - - this.put('editor.insertSpaces', true) - this.put('cody', { - advanced: { - agent: { - 'capabilities.storage': - capabilities?.globalState === 'server-managed' || - capabilities?.globalState === 'client-managed', - 'extension.version': this.clientInfo()?.version, - ide: { - name: AgentWorkspaceConfiguration.clientNameToIDE(this.clientInfo()?.name ?? ''), - version: this.clientInfo()?.ideVersion, - }, - running: true, - }, - hasNativeWebview: capabilities?.webview === 'native' ?? false, - }, - autocomplete: { - advanced: { - model: config?.autocompleteAdvancedModel ?? null, - provider: config?.autocompleteAdvancedProvider ?? null, - }, - enabled: true, - }, - codebase: config?.codebase, - customHeaders: config?.customHeaders, - 'debug.verbose': config?.verboseDebug ?? false, - 'experimental.tracing': config?.verboseDebug ?? false, - serverEndpoint: config?.serverEndpoint, - // Use the dedicated `telemetry/recordEvent` to send telemetry from - // agent clients. The reason we disable telemetry via config is - // that we don't want to submit vscode-specific events when - // running inside the agent. - telemetry: { - clientName: config?.telemetryClientName, - level: 'agent', - }, - }) - - const fromCustomConfigurationJson = config?.customConfigurationJson - if (fromCustomConfigurationJson) { - const configJson = JSON.parse(fromCustomConfigurationJson) - _.merge(this.dictionary, this.normalize(configJson)) - } - - const customConfiguration = config?.customConfiguration - if (customConfiguration) { - for (const key of Object.keys(customConfiguration)) { - this.put(key, customConfiguration[key]) - } - } - } + ) {} public withPrefix(prefix: string): AgentWorkspaceConfiguration { return new AgentWorkspaceConfiguration( @@ -78,30 +24,8 @@ export class AgentWorkspaceConfiguration implements vscode.WorkspaceConfiguratio ) } - private normalize(cfg: any): any { - if (cfg && typeof cfg === 'object') { - if (Array.isArray(cfg)) { - const normalized = [] - for (const value of Object.values(cfg)) { - normalized.push(this.normalize(value)) - } - return normalized - } - - const normalized = {} - for (const key of Object.keys(cfg)) { - const tmp = {} - _.set(tmp, key, this.normalize(cfg[key])) - _.merge(normalized, tmp) - } - return normalized - } - - return cfg - } - private put(key: string, value: any): void { - _.set(this.dictionary, key, this.normalize(value)) + _.set(this.dictionary, key, value) } private actualSection(section: string): string { @@ -136,10 +60,89 @@ export class AgentWorkspaceConfiguration implements vscode.WorkspaceConfiguratio public get(userSection: string, defaultValue?: unknown): any { const section = this.actualSection(userSection) + + const config = this.extensionConfig() + const capabilities = this.clientInfo()?.capabilities + const baseConfig = { + editor: { + insertSpaces: true, + }, + cody: { + advanced: { + agent: { + capabilities: { + storage: + capabilities?.globalState === 'server-managed' || + capabilities?.globalState === 'client-managed', + }, + extension: { + version: this.clientInfo()?.version, + }, + ide: { + name: AgentWorkspaceConfiguration.clientNameToIDE( + this.clientInfo()?.name ?? '' + ), + version: this.clientInfo()?.ideVersion, + }, + running: true, + }, + hasNativeWebview: capabilities?.webview === 'native', + }, + autocomplete: { + advanced: { + model: config?.autocompleteAdvancedModel ?? null, + provider: config?.autocompleteAdvancedProvider ?? null, + }, + enabled: true, + }, + codebase: config?.codebase, + customHeaders: config?.customHeaders, + debug: { verbose: config?.verboseDebug ?? false }, + experimental: { tracing: config?.verboseDebug ?? false }, + serverEndpoint: config?.serverEndpoint, + // Use the dedicated `telemetry/recordEvent` to send telemetry from + // agent clients. The reason we disable telemetry via config is + // that we don't want to submit vscode-specific events when + // running inside the agent. + telemetry: { + clientName: config?.telemetryClientName, + level: 'agent', + }, + }, + } + + const customConfiguration = config?.customConfiguration + if (customConfiguration) { + for (const [key, value] of Object.entries(customConfiguration)) { + _.set(baseConfig, key, value) + } + } + + const fromCustomConfigurationJson = config?.customConfigurationJson + if (fromCustomConfigurationJson) { + const configJson = JSON.parse(fromCustomConfigurationJson) + for (const [key, value] of Object.entries(configJson)) { + _.set(baseConfig, key, value) + } + } + + const fromBaseConfig = _.get(baseConfig, section) const fromDict = _.get(this.dictionary, section) + if ( + typeof fromBaseConfig === 'object' && + typeof fromDict === 'object' && + !Array.isArray(fromBaseConfig) && + !Array.isArray(fromDict) + ) { + return structuredClone(_.extend(fromBaseConfig, fromDict)) + } if (fromDict !== undefined) { return structuredClone(fromDict) } + if (fromBaseConfig !== undefined) { + return fromBaseConfig + } + return defaultConfigurationValue(section) ?? defaultValue }