Skip to content

feat(agent): manage Copilot CLI and VS Code Copilot Chat - #135

Merged
peterj merged 3 commits into
agentdesktop-dev:mainfrom
LutzLange:upstream/s3-copilot-programs
Oct 7, 2026
Merged

peterj merged 3 commits into
agentdesktop-dev:mainfrom
LutzLange:upstream/s3-copilot-programs

Conversation

@LutzLange

@LutzLange LutzLange commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Part of #86 (series overview: #86 overview). Stacked on #134; review the last three commits (the second fixes a providers.json edge case and makes the README more precise; the third addresses the review below).

Why

The clients still have to be pointed at the proxy by hand, on each machine, with the pairing value.

Change

Two managed programs, user mode only.

  • programs.copilot writes the Copilot CLI's bring-your-own-key provider file (~/.copilot/providers.json). It adds two providers on /copilot-cli and keeps the user's own.
  • programs.vscode, copilotChat: ownModels (default), writes chatLanguageModels.json for Copilot Chat's Custom Endpoint provider on /vscode-copilot.
  • copilotChat: githubModels keeps GitHub's models and the user's Copilot seat:
    • It sets github.copilot.advanced.debug.overrideCapiUrl (the Copilot API base URL) in the user settings.json to /vscode-copilot-capi/<pairing>. The pairing value is in the path, because VS Code cannot add a header.
    • The proxy moves the client's Copilot token to x-llm-token, puts the gateway identity in Authorization, and forwards to llmGateway.proxyUrl.
    • It tunnels the Responses WebSocket (agent mode).
    • It keeps the override out of Settings Sync.
  • settings.json is edited in place (JSONC): only the managed keys change. Removal restores the file byte for byte.
  • File handling. Each managed file is written 0600 with a small sidecar file next to it that records what the daemon added, so removal takes out exactly that. A file the daemon cannot parse is left alone and reported as a conflict; a sidecar it cannot read fails the apply, so the user's earlier value is never dropped.

Risk and compatibility

  • overrideCapiUrl is an undocumented Copilot Chat setting.
  • On GitHub's models, VS Code sends its Copilot token and the pairing value to the loopback port. If the proxy is down, another local user could bind that port, so use this mode on single-user devices.
  • Inline completions still go to GitHub directly.
  • New program keys: upgrade the controller first.
  • macOS and Windows paths are covered by unit tests only.

How to review

  • provider/copilot/reconcile.rs and provider/vscode/{reconcile,settings}.rs hold the three writers.
  • The llm_proxy.rs additions hold the Copilot API route and the tunnel.
  • json_merge.rs adds root-array support.
  • About two-thirds of the added lines are tests and fixtures.

Review fixes

The last commit (6bcbcb6) addresses the Copilot review:

  • An unreadable .settings.json.agentdesktop now fails the apply, as the providers.json and chatLanguageModels.json sidecars already do. Before, it was ignored and the user's own overrideCapiUrl was lost on removal. The error names the path and, for a syntax error, the line and column, never a value.
  • An identical settings sidecar looser than 0600 is written again at 0600.
  • 9 new unit tests; lab-checked on the Linux VM (corrupt sidecar: program failed, both files unchanged, the user's value back after removal).

Tests

CI green, including Docker lifecycle tests for all three writers and the WebSocket tunnel. Lab-tested on a Linux VM with VS Code 1.139, Copilot CLI 1.0.88 and a Copilot Business seat; the steps are in the guide in #138. After the rebase onto the review fixes in #134, the WebSocket tunnel takes its deadline from the shared credential fetch; that change is covered by unit tests only, not re-tested on the live Copilot path. The tunnel closes when that credential expires (4 to 5 minutes with the default gatewayJwt.lifetime), also mid-turn; whether VS Code then reconnects without the user noticing has not been verified.

Follow-ups

Screenshot

Controller configuration view with the two Copilot programs and the pass-through URL.

Controller configuration view with the two Copilot programs and the pass-through URL.

🤖 Generated with Claude Code

@peterj

peterj commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

@copilot resolve the merge conflicts in this pull request

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Permission drift can expose pairing values, and an invalid VS Code sidecar can prevent restoration of user settings.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Adds managed GitHub Copilot CLI and VS Code Copilot Chat routing through the local LLM proxy.

Changes:

  • Adds Copilot/VS Code configuration reconcilers, discovery, schemas, and controller UI.
  • Adds proxy authentication, GitHub OAuth, pass-through, and WebSocket support.
  • Expands lifecycle tests, fixtures, and documentation.
File Description
schema/​daemon-config.md Documents new configuration keys.
schema/​daemon-config.json Adds generated schemas for proxy and Copilot programs.
README.md Documents proxy setup, routes, security, and managed programs.
frontend/​ui/​src/​tools.tsx Adds Copilot CLI display metadata.
frontend/​desktop/​src/​views/​StatusView.stories.tsx Covers bound and failed proxy states.
frontend/​desktop/​src/​types.ts Types proxy status information.
frontend/​desktop/​src/​stories/​fixtures.ts Adds proxy status fixtures.
frontend/​desktop/​src/​components/​DaemonInformation.tsx Displays local proxy status.
frontend/​controller/​src/​views/​ConfigurationView.tsx Adds proxy URL and Copilot program controls.
frontend/​controller/​src/​types.ts Extends controller configuration types.
crates/​core/​src/​model.rs Adds proxy status models.
crates/​core/​src/​config.rs Adds and validates proxy and Copilot configuration.
crates/​agent/​tests/​integration.rs Registers Copilot integration tests.
crates/​agent/​tests/​common/​gateway.rs Captures forwarded LLM tokens.
crates/​agent/​tests/​common/​container.rs Improves container file and process handling.
crates/​agent/​src/​remote.rs Bounds controller credential requests.
crates/​agent/​src/​reconcile/​plan.rs Adds private-directory planning.
crates/​agent/​src/​reconcile/​mod.rs Registers new reconcilers and proxy context.
crates/​agent/​src/​provider/​vscode/​testdata/​Dockerfile Prepares VS Code test runtime state.
crates/​agent/​src/​provider/​vscode/​testdata/​chatLanguageModels.lab.json Adds a managed-model fixture.
crates/​agent/​src/​provider/​vscode/​settings.rs Manages Copilot CAPI settings in JSONC.
crates/​agent/​src/​provider/​vscode/​reconcile.rs Manages custom endpoint models.
crates/​agent/​src/​provider/​vscode/​mod.rs Integrates VS Code reconciliation.
crates/​agent/​src/​provider/​vscode/​integration_tests.rs Tests both VS Code modes and tunneling.
crates/​agent/​src/​provider/​vscode/​discovery.rs Exposes the shared VS Code profile path.
crates/​agent/​src/​provider/​README.md Updates the provider feature matrix.
crates/​agent/​src/​provider/​mod.rs Registers Copilot and proxy context.
crates/​agent/​src/​provider/​json_merge.rs Adds root-array and keyed-array merging.
crates/​agent/​src/​provider/​copilot/​testdata/​providers.lab.json Adds a Copilot provider fixture.
crates/​agent/​src/​provider/​copilot/​testdata/​Dockerfile Adds a Copilot CLI test image.
crates/​agent/​src/​provider/​copilot/​reconcile.rs Reconciles Copilot CLI providers and models.
crates/​agent/​src/​provider/​copilot/​mod.rs Integrates the Copilot provider.
crates/​agent/​src/​provider/​copilot/​integration_tests.rs Tests Copilot lifecycle and proxy routing.
crates/​agent/​src/​provider/​copilot/​discovery.rs Discovers Copilot CLI installations.
crates/​agent/​src/​provider/​claude_code/​mod.rs Updates reconcile test context.
crates/​agent/​src/​oidc.rs Extends authorization pages for GitHub.
crates/​agent/​src/​lib.rs Registers proxy and GitHub OAuth modules.
crates/​agent/​src/​identity.rs Adds device-ID-only loading.
crates/​agent/​src/​github_oauth.rs Implements GitHub device authorization and refresh.
crates/​agent/​src/​github_oauth.html Adds the GitHub authorization page.
crates/​agent/​src/​gateway_oidc.rs Chains GitHub authorization after OIDC.
crates/​agent/​src/​api.rs Exposes shared gateway credential acquisition.
crates/​agent/​Cargo.toml Adds agent proxy and JSONC dependencies.
Cargo.toml Adds workspace networking and JSONC dependencies.
Cargo.lock Locks the new dependencies.

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

Comment on lines 173 to +190
let action = match existing.as_deref() {
Some(existing) if existing == contents => "unchanged",
Some(_) => "update",
None => "create",
};
plan.record_diff(
record(
plan,
display_name,
description,
action,
path,
existing.as_deref(),
Some(&contents),
options.redact_diff,
);

if action != "unchanged" {
plan.write_file(path, &contents, 0o644)?;
plan.write_file(path, &contents, options.mode)?;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in #137: plan_merge_with treats an identical file whose mode is looser than planned as an update and rewrites it at the planned mode; #137 extends that to every program that writes a managed file.

Comment on lines +656 to +661
let state = bytes.as_deref().and_then(|bytes| {
let state = read_state(bytes);
if state.is_none() {
warn!(path = %state_path.display(), "ignoring a VS Code settings sidecar that cannot be parsed");
}
state

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 6bcbcb6: an unreadable .settings.json.agentdesktop now fails the apply, as the providers.json and chatLanguageModels.json sidecars already do, so the user's earlier overrideCapiUrl is never dropped. The error names the path (and the line and column for a syntax error), never a value from the file; the README says to fix or delete it. Test: corrupt_sidecar_does_not_lose_the_users_override.

LutzLange and others added 2 commits October 7, 2026 01:12
Part of agentdesktop-dev#86. Two programs that point Copilot at the local LLM proxy, user
mode only:

- `programs.copilot` writes the Copilot CLI's BYOK provider registry
  (`~/.copilot/providers.json`, or `$COPILOT_HOME` / `COPILOT_PROVIDERS_CONFIG`):
  providers `agentdesktop` (OpenAI-shaped) and `agentdesktop-anthropic` on the
  proxy's `/copilot-cli` route, with the pairing header; the user's own
  providers are kept. Discovery reports the CLI and its version from the npm
  manifest.
- `programs.vscode` with `copilotChat: ownModels` (default) writes
  `chatLanguageModels.json` for Copilot Chat's built-in Custom Endpoint
  provider on `/vscode-copilot`; the user's entries are kept.
- `copilotChat: githubModels` keeps GitHub's own models and the user's
  Copilot seat: the daemon sets `github.copilot.advanced.debug.overrideCapiUrl`
  in the user `settings.json` to the proxy's `/vscode-copilot-capi/<pairing>`
  route (pairing in the path, since VS Code cannot add a header), forwards to
  `llmGateway.proxyUrl` with the client's Copilot token moved aside, tunnels
  the Responses WebSocket for agent mode, forwards `_ping` without a
  credential, and keeps the override out of Settings Sync. `settings.json` is
  edited in place (JSONC): only the managed keys change, comments, key order
  and formatting stay, and removal restores the file byte for byte.

Managed files are written 0600 and carry a sidecar that records what the
daemon added, so removal takes out exactly that. A file the daemon cannot
parse is a conflict and left alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…recision

- Copilot CLI: removing our providers by their pairing header (no sidecar)
  no longer adds "models": null to a providers.json that has no models key.
- README: the CAPI WebSocket tunnel closes after 4 to 5 minutes with the
  controller's default gatewayJwt.lifetime, also mid-turn; VS Code's
  reconnect behaviour is not verified.
- README: the settings.json removal exceptions are listed in full (multi-line
  empty object, comment-only file).
- README: the providers.json sidecar holds the user's own apiKey values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LutzLange
LutzLange force-pushed the upstream/s3-copilot-programs branch from 4318c32 to 588c8a8 Compare October 6, 2026 23:19
…idecar mode repaired

Review on agentdesktop-dev#135.

- `.settings.json.agentdesktop` that is not JSON or not a known settings
  state now fails the apply, as the providers.json and chatLanguageModels.json
  sidecars already do: like a conflict, every managed file on the device
  stays untouched, also while `programs.vscode` is not configured (removal
  reads the sidecar too), and the daemon does not start. Before, the sidecar
  was ignored and rebuilt from the sidecar-less rules, so a user's own
  `overrideCapiUrl` was lost on removal. The error names the sidecar path and,
  for a syntax error, the line and column only, never a value. A sidecar
  without `version` (the earlier whole-document form) is still upgraded; a
  leading byte-order mark is accepted.
- An identical sidecar whose mode is looser than 0600 is written again at
  0600, as settings.json is; settings.json and its sidecar use the same
  check (a 0700 settings.json is now also tightened). The rewrite of the
  sidecar alone is not shown in the plan summary, since settings.json is
  unchanged.
- README: the unreadable-sidecar behaviour and the sidecar's mode.

Behaviour change: the test `an_unreadable_sidecar_warns_and_apply_proceeds` is
replaced. Tests: 9 new, 2 of them unix-only. The file-mode tests in this
module (old and new) assume umask 022.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

3 participants