Repository navigation
Conversation
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…loopback proxy
`programs.vscode` makes the daemon write VS Code's `chatLanguageModels.json`
(Copilot Chat's built-in Custom Endpoint provider) so the model picker offers
the configured models through the proxy's `/vscode-copilot` route with client
id `vscode-copilot`, replacing the hand-written file from the lab. Own models
only (`copilotChat: ownModels`, an enum so GitHub's models can follow); user
mode only, the file lives in the VS Code user profile directory (per-OS root
shared with MCP discovery; local override `daemon.vscode.config`, an error in
system mode); a system daemon rejects the program before any provider writes.
Config: `models` is a map keyed by the model id VS Code sends; each entry is
typed (`name` defaulting to "<id> (agentdesktop)", `toolCalling` default true,
`vision` default false, `maxInputTokens`/`maxOutputTokens`, other keys passed
through as given). Validated at parse time: reserved keys `id`, `url`,
`requestHeaders` and `apiKey` (all case-insensitive, also for the Copilot CLI
program); empty ids; at least one model with a gateway, matched on the
`copilotChat` value so a new source must decide its own rule; sandbox with the
program. `validate_one_shot` refuses the program (the listener cannot run in
one-shot mode). `VsCodeConfig::model_documents` is config-only; the proxy url
and the pairing header are added in the agent crate (`vscode/reconcile.rs`),
which is the only place that knows the proxy.
Written content: one vendor entry `{name: agentdesktop, vendor: customendpoint,
apiKey: unused, apiType: chat-completions, models: [...]}` with each model's
`url` on `http://<listen>/vscode-copilot/v1/chat/completions` and the pairing
in `requestHeaders` (VS Code keeps `apiKey` in the OS keyring and does not send
it reliably; the placeholder satisfies the schema and no secret is written).
The file's top level is an array, so json_merge learned a root keyed array
(`KeyedArray { field: "" }`): a file must have the managed document's shape
at the root on merge and the sidecar's shape on removal (else conflict),
keyed replacement and removal work on the root, an empty root array is
deleted only if the daemon created the file; an empty or whitespace-only file
is filled and, emptied by hand, releases the sidecar (and is deleted if the
daemon created it), while a file with comments or trailing commas is a
conflict rather than rewritten without them (documented, with the effect of a
conflict on the device). Object-root callers (Claude Code, Claude Desktop)
see two changes, both tested with default options: an empty settings file is
filled instead of being a conflict, and a settings file emptied by hand
releases the sidecar (and is deleted if the daemon created it) instead of
being a conflict on removal. A file whose root shape changed since the merge
is a conflict on removal (tested). The vendor entry is owned
by name once written (hand edits replaced, dropped models disappear since the
entry is replaced wholesale, removed by name on removal); other vendor entries
are the user's. Before the first write, a user entry named `agentdesktop`
without a pairing header on any model is a conflict with the reason in the
plan report. Written 0600, a created directory 0700, reports redacted, and
without a sidecar the entry carrying this daemon's own pairing value is
stripped on removal with the file's mode kept.
Removal: program absent, `useLlmGateway: false`, no `llmGateway`, or no proxy
(a warning points at llmProxy.error in daemon-info).
Discovery is unchanged; `daemon.vscode.config` overrides the path (an error
in system mode), the default derives from the daemon's resolved home
directory. Frontend: `AgentKind` vscode, "VS Code (Copilot
Chat)" in the configurable programs, `vscode-copilot` in the allowedClientIds
snippet, sandbox-unsupported. README: supported-tools row and a "Managed
program: VS Code Copilot Chat on own models" section (file per OS, vendor
entry, `apiKey` placeholder and pairing header, ownership and conflict rules,
no dry run, `--once` refused, upgrade note, restart VS Code once,
`chat.defaultModel` by policy, plain JSON only, sidecar contents, profiles
and XDG_CONFIG_HOME not considered); provider matrix updated; schema
regenerated.
Tests. From the test author's baseline commit a690317, unchanged apart from
one comment line (listed in the ledger): 20 in provider/vscode, 18 of which
run on any one OS (fixture equals the lab file, fresh file 0600 + sidecar, user
vendor entry survives re-apply and re-pairing, hand edit replaced, dropped
model disappears, removal cases incl. mode kept and sidecar-less strip,
conflicts, system-mode rejection through Provider::plan, per-OS path, config
validation), 5 in json_merge (root array merge/removal, object-root callers
unchanged, non-array conflict), the reconciler directory-mode test and two
daemon tests (`--once` refusal, system-mode `daemon.vscode.config` error).
Added with the implementation: json_merge tests for empty files (filled;
emptied by hand releases the sidecar and, if created by the daemon, the
file), comments (conflict) and a root shape change on removal (conflict); a
provider/vscode test for reserved and secret keys in any case; and a Docker
integration test in the existing VS Code image (user-mode daemon as
`tester`, file written with the daemon's pairing, a curl with the file's url
and header plus its own Authorization and x-api-key arriving at the stub
gateway without them, removal keeps the user's entry at the mode the user
set).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Owner
Author
|
Superseded: landed upstream in agentdesktop-dev#135 (VS Code custom models). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on this fork so the diff is this PR only (base:
copilot/pr2-copilot-cli). Fifth change in the series; James gets one integration PR fromcopilot/integration(the tip of the stack) when the series is ready.What it does.
programs.vscodemakes the daemon write VS Code'schatLanguageModels.json(Copilot Chat's built-in Custom Endpoint provider) so the model picker offers the configured models through the loopback proxy's/vscode-copilotroute with client IDvscode-copilot; this replaces the hand-written file from the lab. Own models only (copilotChat: ownModels, an enum so GitHub's models can follow in 3b); user mode only, the file lives in the VS Code user profile directory (per-OS root shared with MCP discovery, local overridedaemon.vscode.config).modelsis a map keyed by the model ID VS Code sends; typed entries (name,toolCalling,vision,maxInputTokens,maxOutputTokens, other keys passed through as given). Validated at parse time: reservedid/url/requestHeaders(case-insensitive, also for the Copilot CLI program),apiKey, at least one model with a gateway, sandbox with the program.--oncerefuses the program.agentdesktop(customendpoint,chat-completions) with each model'surlon the proxy and the pairing inrequestHeaders(apiKey: unusedsatisfies VS Code's schema; the proxy adds the credential). Written 0600, a created directory 0700, reports redacted. The proxy URL and header are added in the agent crate;VsCodeConfig::model_documentsstays config-only.json_mergelearned a root keyed array (KeyedArray { field: "" }); the root must match the managed document's shape on merge and the sidecar's on removal; an empty file is filled, a file with comments or trailing commas is a documented conflict (plain JSON is rewritten; the plan's JSONC decision). Theagentdesktopentry is owned by name once written (replaced as a whole, dropped models disappear, removed by name); other vendor entries are the user's. Before the first write a user entry with that name and no pairing header is a conflict; without a sidecar the entry carrying this daemon's own pairing is stripped on removal, mode kept.vscode-copilotinallowedClientIds, ownership rules, restart VS Code after an apply that changes the file, plain-JSON note, sidecar note, profiles andXDG_CONFIG_HOMEnot considered,chat.defaultModelby policy), supported-tools row, provider matrix, schema.Process. Frozen spec (
specs/PR-3a.md), tests first by a separate agent in their own baseline commit (a690317), test guard (no baseline test changed), gate (182 agent + 18 core tests, clippy, schema, frontend build + lint), three review lenses on two models, delta review, two independent lab runs. Round 1 findings folded in: proxy details had leaked into the core crate; stale "not wired" text shipped into the schema; an empty file was a conflict; removal accepted a shape change silently;apiKeypass-through; the Docker test's mode check could not fail.Rounds 2 and 3 (delta reviews on Opus): the Docker test asserted a status the stub gateway never returns, replaced by an assertion on the recorded upstream request with the client's own Authorization and x-api-key sent and stripped; a daemon-created file emptied by hand is deleted on removal; a root shape change since the merge is a conflict on removal (tested); reserved and secret keys rejected in any case (tested); commit-message corrections. Head 00b3e40 differs from the lab-run 6a32533 by those changes, all unit-tested.
Lab (Linux VM, user mode, VS Code 1.139.0, controller-pushed config): on f5d69e4 all automated items passed (re-owning the existing hand-written file, fresh write with the expected shape, user entries kept, hand edit replaced, re-pairing, removal with mode kept, conflict on a foreign
agentdesktopentry, controller rejects a reserved key,--onceand system-mode refusals, curl through the proxy answered 200 withllm.client="vscode-copilot"); on 6a32533 the re-check passed (empty file filled, comment conflict, mode 640 kept,apiKeyandRequestHeadersrejected by the controller, system-mode message, regression curl 200). V9, the VS Code picker and a chat turn through the managed file, was driven with Playwright over CDP against the real VS Code 1.139 in the VM: picker showsgpt-4.1-mini (agentdesktop), the answer is PONG in 6 s, gateway logllm.client="vscode-copilot"with the enrolled device and user; VS Code did not rewrite the file.Not covered by a test: CI's Docker suite runs the integration test (user-mode daemon as
testerin the VS Code image); it does not finish on my machine.🤖 Generated with Claude Code