Skip to content

Support top-level model in managed settings while retaining permissions.model - #330574

Draft
joshspicer wants to merge 1 commit into
mainfrom
agents/fix-vscode-issue-330364
Draft

Support top-level model in managed settings while retaining permissions.model#330574
joshspicer wants to merge 1 commit into
mainfrom
agents/fix-vscode-issue-330364

Conversation

@joshspicer

Copy link
Copy Markdown
Member

Fixes #330364

Summary

VS Code drove the default chat model (the ChatDefaultModel policy → chat.defaultModel setting) only from the legacy nested permissions.model managed-settings key. This PR also consumes the new top-level model key while retaining permissions.model for deployments authored against the original schema. When both are present, top-level model wins. The behavior is consistent across all three delivery channels: server API, file on disk, and native MDM.

Why the change lands where it does

  • Server (adaptManagedSettings) and file (FileManagedSettingsService) both funnel through the single normalizer normalizeManagedSettings, which flattens nested objects to dot-paths. { model } is a scalar leaf, so it flattens to the bag key model for free — no structured-settings row needed.
  • Native MDM only watches keys that a policy declares in managedSettings, and projectManagedSettings drops any bag key that isn't declared. So the top-level model key is now declared on the ChatDefaultModel policy — that is what makes native MDM deliver it and projection keep it on every channel.
  • Cross-key precedence (top-level vs. legacy) is resolved in the policy's value() callback managedModelValue(), since the two are distinct bag keys. The channel-precedence logic in pickManagedSettings only merges the same key across channels, so it can't express "top-level supersedes legacy" — that lives in the callback.

Changes

  • copilotManagedSettings.ts — add COPILOT_TOP_LEVEL_MODEL_KEY = 'model'; update managedModelValue() to resolve the top-level key first and fall back to permissions.model (extracting a small normalizeModelValue trim helper; blank/whitespace treated as unset). Preserves the memoized single-reference contract.
  • chat.shared.contribution.ts — declare both keys in the ChatDefaultModel policy's managedSettings so native MDM watches model and projection keeps it.
  • managedSettings.ts (server response type) — type model at the top level and under permissions on IManagedSettingsResponse (documentation/typing; runtime already accepted them via the forward-compatible index signature).
  • Docsgithub-managed-settings.md: add the top-level model rows to the schema + constants tables and a note explaining the key-level precedence.
  • Tests — precedence + normalization coverage in copilotManagedSettings.test.ts and fileManagedSettingsService.test.ts.

Behavior details

  • Precedence is key-level: a non-empty top-level model wins even when permissions.model was supplied by a higher-precedence channel (e.g. native MDM). This matches the issue ("if both are present, top-level model should win").
  • A blank/whitespace-only top-level model is treated as unset and falls back to the legacy key (mirroring the existing blank→unset rule for a cleared field).
  • No policy-data re-export needed: managedSettings mappings aren't part of the exported PolicyDto (policyData.jsonc is unchanged).

Validation

  • Type-checked and executed the changed model-resolution + scalar-flatten logic against the issue's scenarios (top-level wins, legacy fallback, trim, blank fallthrough, both-blank → undefined; server/file normalization keeps both keys distinct) under a strict tsc harness — all pass.
  • Confirmed build/lib/policies/policyData.jsonc is unchanged.

Managed settings reference: https://docs.github.com/en/enterprise-cloud@latest/copilot/reference/enterprise-administrators/enterprise-managed-settings

…ns.model

VS Code drove the default chat model (ChatDefaultModel policy) only from the
legacy nested `permissions.model` managed-settings key. Also consume the new
top-level `model` key, retaining `permissions.model` for original-schema
deployments; when both are present the top-level `model` wins.

Because the server and file channels funnel through `normalizeManagedSettings`
(which flattens scalar leaves) and native MDM watches only declared keys, the
fix is: declare `model` on the policy so it is watched + projected on every
channel, and resolve the top-level-over-legacy precedence in the policy value
callback (`managedModelValue`). Precedence is key-level, so a non-empty
top-level `model` wins even over a `permissions.model` from a higher-precedence
channel; a blank top-level value falls back to the legacy key.

Fixes #330364

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 13, 2026 00:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for top-level managed model settings while preserving legacy permissions.model.

Changes:

  • Adds normalized top-level model precedence and policy wiring.
  • Extends server response typing and documentation.
  • Adds normalization and precedence tests.
Show a summary per file
File Description
src/vs/workbench/services/accounts/browser/managedSettings.ts Types both model locations.
src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts Declares both managed keys.
src/vs/platform/policy/common/copilotManagedSettings.ts Implements model precedence and normalization.
src/vs/platform/policy/test/common/copilotManagedSettings.test.ts Tests resolution behavior.
src/vs/platform/policy/test/common/fileManagedSettingsService.test.ts Tests flattened model keys.
.github/skills/policy-and-managed-settings/github-managed-settings.md Documents schema and precedence.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (2)

src/vs/platform/policy/common/copilotManagedSettings.ts:178

  • This JSDoc exceeds the repository's 1–2 sentence hard limit and enumerates implementation branches and architecture already expressed by the code and tests. Condense it to the callback contract.
 * `value` callback for the default-chat-model managed setting. Resolves the top-level
 * {@link COPILOT_TOP_LEVEL_MODEL_KEY} first and falls back to the legacy nested
 * {@link COPILOT_MODEL_KEY} — so a deployment on either schema shape works, and when both are
 * present the top-level value wins. Like {@link managedSettingValue} it locks the setting to the
 * managed value and otherwise falls through to the user's own value, but it additionally trims each

src/vs/platform/policy/test/common/fileManagedSettingsService.test.ts:148

  • This inline comment only restates the test name and assertions and spans multiple lines, contrary to the repository's one-line hard limit for inline method comments. Remove it.
		// A payload authored against both schema shapes flattens to two distinct bag keys; the
		// policy value callback resolves the top-level one.
  • Files reviewed: 6/6 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment on lines +192 to +193
const topLevel = normalizeModelValue(policyData.managedSettings?.[COPILOT_TOP_LEVEL_MODEL_KEY]);
return topLevel ?? normalizeModelValue(policyData.managedSettings?.[COPILOT_MODEL_KEY]);
Comment on lines +79 to +81
* `permissions.model` in the normalized bag. Retained for deployments authored against the original
* schema; new deployments use the top-level {@link COPILOT_TOP_LEVEL_MODEL_KEY}, which wins when
* both are present. See {@link managedModelValue} for the precedence.
Comment on lines +133 to +135
// The current managed-settings schema carries `model` at the top level; as a scalar leaf it
// flattens to the bag key `model`, which the ChatDefaultModel policy value callback reads
// with precedence over the legacy nested key.
Comment on lines +85 to +86
| `model` | string (`auto`, a model family name, or a full model id) | top-level wins over the legacy nested `permissions.model` |
| `permissions.model` | string (legacy location for `model`) | superseded by top-level `model` when both are present; retained for original-schema deployments |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support top-level model in managed settings while retaining permissions.model

2 participants