Skip to content

Commit

Permalink
VS Code: Release v1.40.2 (#6064)
Browse files Browse the repository at this point in the history
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

<!-- Required. See
https://docs-legacy.sourcegraph.com/dev/background-information/testing_principles.
-->

## Changelog

<!-- OPTIONAL; info at
https://www.notion.so/sourcegraph/Writing-a-changelog-entry-dd997f411d524caabf0d8d38a24a878c
-->

Co-authored-by: Dominic Cooney <[email protected]>
Co-authored-by: Piotr Kukielka <[email protected]>
  • Loading branch information
3 people authored Nov 4, 2024
1 parent 3e31877 commit 70db14a
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 85 deletions.
40 changes: 33 additions & 7 deletions agent/src/AgentWorkspaceConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ describe('AgentWorkspaceConfiguration', () => {
"d1.d2": {
"v": 1
}
}
},
"dotted.property.name": 42
}
}
`
Expand All @@ -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')
Expand Down Expand Up @@ -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', () => {
Expand Down
159 changes: 81 additions & 78 deletions agent/src/AgentWorkspaceConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 70db14a

Please sign in to comment.