Files
vscode/scripts/mock-policy-server/endpoints.ts
T
8fb912f511 Mock policy server: upstream passthrough, per-endpoint mocking, request log (#330711)
* Mock policy server: upstream passthrough, per-endpoint mocking, request log

The mock policy server only worked via product.overrides.json, which limits it
to Code OSS running from sources, requires a reload after every change, and
cannot exercise a stable/Insiders build or the CLI. Support a system HTTP proxy
as a second wiring path, keeping product.overrides.json as the default.

- Proxy anything not explicitly mocked to the real API (--upstream, default
  https://api.github.com) and stream it back, so a blanket proxy rule is safe:
  only endpoints deliberately switched on are faked. Rewrites Host, strips
  hop-by-hop headers and accept-encoding, forwards Authorization untouched, and
  reports upstream failures as 502.
- Add a per-endpoint mock/passthrough toggle. Only managed settings is mocked by
  default; applying a preset implicitly switches mocking on.
- Add managed-settings disk cache clearing. A cache entry under an hour old
  makes the runtime skip the network entirely, so an override is never even
  requested. Paths verified against managed_settings_cache.rs and
  path_helpers.rs, including the COPILOT_CACHE_HOME override.
- Add a rolling request log (GET/DELETE /api/log) surfaced in the GUI, so it is
  obvious whether the client actually reached the server.
- Add realistic managed-settings presets, each validated against the schema, and
  branch-point presets for the other endpoints.
- Only warn about unknown schema keys on 2xx, and re-validate on status change:
  a 404/466/500 body is an error payload, not a policy document.
- Route GUI assets from an explicit allowlist instead of probing public/ for
  anything that looks like a file, which would otherwise shadow proxied paths.

UX:
- Make save semantics consistent: everything auto-saves, with a pill showing
  whether the editor matches what is being served.
- Surface mocked vs proxied via tab dots, a checkbox, and reactive help text.
- Add a light palette; the dark-only one declared color-scheme: light dark, so
  UA form controls rendered light on a dark page.
- Make the schema disclosure a real button with aria-expanded, add focus-visible
  styles, and expose tab state to screen readers.
- Build the validation table from DOM nodes rather than innerHTML.
- Surface save and wire failures instead of failing silently, and fall back to
  the shared endpoint definitions when the control API is unreachable.
- Answer the GUI's own favicon request so it stops appearing in the log as a
  proxied 404.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Polish mock policy server workflows

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Route runtime policy diagnostics through proxy

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Minimize runtime proxy integration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Move runtime proxy fix to separate PR

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address mock policy server review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-13 23:29:50 +00:00

222 lines
7.0 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Shared definition of the Copilot "policy" endpoints that
* `DefaultAccountService` (src/vs/workbench/services/accounts/browser/defaultAccount.ts)
* calls, together with sample response bodies. This is the single source of
* truth shared by the Node server (route table + default state) and the browser
* GUI (endpoint tabs + preset dropdown), exported UMD-style so it loads in both
* environments without a build step.
*
* For the default (github.com) provider these URLs are read verbatim from
* `product.json` -> `defaultChatAgent.<productKey>`, so pointing all of them at
* a local server via `product.overrides.json` lets a dev exercise the whole
* policy pipeline offline. The same paths are also served under a system proxy
* rule, which is how a stable/Insiders build or the CLI reaches this server.
*
* Endpoints not marked `mockedByDefault` start in passthrough: the server
* forwards them to the real API so a blanket proxy rule stays safe.
*
* NOTE: The server uses `module.stripTypeScriptTypes()` to serve this file to
* the browser as plain JavaScript — no build step is needed.
*/
export interface EndpointPreset {
id: string;
label: string;
description: string;
status?: number;
body: unknown;
}
export interface EndpointDef {
/** Stable id used by the API + GUI. */
id: string;
/** Human label for the GUI tab. */
label: string;
/** URL path the server serves / Code OSS calls. */
path: string;
/** Key under product.json `defaultChatAgent`. */
productKey: string;
/** One-line summary for the GUI. */
description: string;
/**
* Whether this endpoint is mocked when the server starts. Everything else
* is proxied to the real API, so a blanket proxy rule stays safe: only the
* endpoints you deliberately turn on get faked.
*/
mockedByDefault?: boolean;
/** Validate 2xx bodies against the managed-settings JSON schema. */
schema?: boolean;
/** First preset is used as the default body. */
presets: EndpointPreset[];
}
/* eslint-disable-next-line no-var -- UMD global for browser <script> context */
declare var MOCK_POLICY_ENDPOINTS: EndpointDef[];
(function (root: Record<string, unknown> | undefined, factory: () => EndpointDef[]) {
if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else if (root) {
root.MOCK_POLICY_ENDPOINTS = factory();
}
})(typeof self !== 'undefined' ? self as unknown as Record<string, unknown> : undefined, function (): EndpointDef[] {
const endpoints: EndpointDef[] = [
{
id: 'managedSettings',
label: 'Managed Settings',
path: '/copilot_internal/managed_settings',
productKey: 'managedSettingsUrl',
description: 'Enterprise copilot settings from .github/copilot/settings.json. An empty object means no policy file is present.',
mockedByDefault: true,
schema: true,
presets: [
{
id: 'empty',
label: 'Empty (no policy file)',
description: 'An empty object is a successful "no enterprise policy file present" response.',
status: 200,
body: {}
},
{
id: 'disable-bypass-permissions',
label: 'Disable bypass permissions',
description: 'Disables bypass permissions mode.',
status: 200,
body: {
permissions: {
disableBypassPermissionsMode: 'disable'
}
}
},
{
id: 'model-auto',
label: 'Model: auto',
description: 'Sets the managed model to auto.',
status: 200,
body: {
model: 'auto'
}
},
{
id: 'extra-known-marketplaces',
label: 'Extra known marketplaces',
description: 'Adds marketplaces with managed auto-update settings.',
status: 200,
body: {
extraKnownMarketplaces: {
'vscode-team-kit': {
source: {
source: 'github',
repo: 'microsoft/vscode-team-kit'
},
autoUpdate: true
},
'awesome-copilot': {
source: {
source: 'github',
repo: 'github/awesome-copilot',
ref: 'marketplace'
},
autoUpdate: false
}
}
}
},
{
id: 'customization-lockdown',
label: 'Customization lockdown',
description: 'Allows only managed plugins, MCP servers, and hooks, and forces a remote settings refresh.',
status: 200,
body: {
strictPluginOnlyCustomization: true,
allowManagedMcpServersOnly: true,
allowManagedHooksOnly: true,
forceRemoteSettingsRefresh: true
}
},
{
id: 'not-configured',
label: 'Not configured (404)',
description: 'No server-managed policy is configured.',
status: 404,
body: {}
},
{
id: 'update-required',
label: 'Client update required (466)',
description: 'Rejects the client because it cannot enforce the effective managed settings.',
status: 466,
body: {
error_code: 'client_update_required',
client_id: 'vscode',
client_version: '1.132.0',
minimum_client_version: '1.133.0'
}
}
]
},
{
id: 'entitlements',
label: 'Entitlements',
path: '/copilot_internal/user',
productKey: 'entitlementUrl',
description: 'Gates the entire flow — token and managed settings are only fetched when chat_enabled is true.',
presets: [
{
id: 'enterprise-enabled',
label: 'Enterprise, chat enabled',
description: 'Chat enabled with cloud session storage; the common dev case.',
body: {
access_type_sku: 'copilot_enterprise_seat',
chat_enabled: true,
assigned_date: '2024-01-01T00:00:00Z',
can_signup_for_limited: false,
copilot_plan: 'enterprise',
organization_login_list: ['contoso'],
analytics_tracking_id: 'dev-analytics-id',
cloud_session_storage_enabled: true
}
}
]
},
{
id: 'token',
label: 'Token',
path: '/copilot_internal/v2/token',
productKey: 'tokenEntitlementUrl',
description: 'Token string carries policy flags as key=value pairs separated by semicolons. Flags: agent_mode, editor_preview_features, mcp, sn, fcv1.',
presets: [
{
id: 'all-enabled',
label: 'All features enabled',
description: 'agent_mode=1, editor_preview_features=1, mcp=1.',
body: { token: 'agent_mode=1;editor_preview_features=1;mcp=1;sn=dev;fcv1=dev:devsignature' }
}
]
},
{
id: 'mcpRegistry',
label: 'MCP Registry',
path: '/copilot/mcp_registry',
productKey: 'mcpRegistryDataUrl',
description: 'Only fetched when the token has mcp=1. Returns the enterprise MCP registry URL and access level.',
presets: [
{
id: 'registry-only',
label: 'Registry only',
description: 'Restrict MCP servers to the enterprise registry.',
body: { mcp_registries: [{ url: 'https://mcp.contoso.example/registry', registry_access: 'registry_only' }] }
}
]
}
];
return endpoints;
});