xnes
Next Edit Suggestions for VS Code, powered by any OpenAI-compatible
/chat/completions endpoint.
This extension uses the proposed inlineCompletionsAdditions API. Marketplace
may reject proposed-API extensions; Open VSX and supported VS Code hosts can
still use the VSIX.
Requires VS Code 1.128 or newer. The full feature set is validated on VS Code
1.143.1; Cursor 3.25.6 (VS Code 1.128.0) can run the basic inline-edit path
with reduced lifecycle features.
Features
- Cursor-local next edit suggestions rendered as VS Code inline edits or ghost text.
- Streaming
<EDIT> / <INSERT> / <NO_CHANGE> protocol with truncation protection.
- Response hardening: preserves blank lines, strips echoed prompt markers, aligns
whole-file replies to the edit window, and does not delete trailing lines the
model omitted.
- Continuous-tab cache: after accepting one edit, subsequent edits are served
from the same model response without another request, even after small
unrelated edits in between.
- File-switch trigger: switching files shortly after accepting a suggestion asks
for a new one; ordinary file switches do not.
- Adaptive end-of-line debounce for slower typing (
xnes.debounce.adaptive).
- Partial accept continues the cached sequence for inserts and replacements when
the resulting document state matches; ambiguous states are discarded.
XNES: 试运行 runs the production pipeline and logs the result without showing
a suggestion or modifying the document.
- Experimental
searchReplace response format (xnes.model.responseFormat): the
SEARCH fragment must occur once in the edit window, and truncated streams are
discarded.
- Experimental
codeBlock response format: a fenced window that reuses the
streaming diff and truncation protection.
- Experimental next-cursor jump (
xnes.experimental.nextCursorJump): after
accepting an edit, move the cursor to the next edit predicted in the same
response. Off by default.
- Experimental speculative prefetch (
xnes.experimental.prefetchAfterAccept):
after accepting an edit, warm the cache for the next position in the
background when no follow-up edit is cached. Off by default and consumes
extra API quota.
- Experimental cross-file prefetch (
xnes.experimental.crossFilePrefetch):
after accepting an edit, pre-warm a suggestion for the most recently edited
other open file (up to crossFilePrefetchMax). When you switch there, the
cached suggestion is labeled with its origin through the proposed
displayLocation API; a transient status-bar hint links to the target file.
Off by default.
- Per-endpoint API keys in SecretStorage; endpoints and model names in globalState.
- Parameter fallbacks are persisted per endpoint, and diagnostics reports p50/p90
latency from a rolling sample buffer.
- Webview settings panel with model discovery, a production-pipeline speed test,
and advanced response-format / recent-file / adaptive-debounce / next-cursor /
prefetch / cross-file controls. The panel separates editing an endpoint from
making it active, keeps drafts and caret position across updates, shows inline
validation, and groups advanced options into collapsible sections.
- Status bar shows the active model plus session shown/accepted counts. Clicking
it opens a menu for settings, model selection, per-workspace / global enable
toggles, logs and diagnostics; a gear item opens settings directly.
- Speed test covers five cases and reports separate cold/warm medians.
- Privacy filters: workspace trust,
.env/keys/lockfiles, size limits, binary files.
- Recent-file context from other edited files, bounded by token and file caps.
- Proxy support through VS Code
http.proxy / http.proxyStrictSSL and
http.noProxy, falling back to HTTP(S)_PROXY / NO_PROXY.
- Request cancellation, deduplication, minimum interval and per-minute rate limits.
Install
Store installs (VS Code Marketplace, Open VSX, Cursor) work out of the box:
settings, model selection, diagnostics, dry run and inline suggestions are
registered. Because a store install is not listed in the host's
product.json#extensionEnabledApiProposals, the host silently drops the
extension's enabledApiProposals and the proposed inline-completion lifecycle
is limited (no continuous-tab acceptance, cross-file labels or next-cursor
jump, and classic ghost text cannot rewrite text before the cursor).
Run XNES: 启用完整内联建议(写入 argv.json) from the command palette to
grant the proposal; the command patches argv.json and asks for a full restart.
The same can be done manually:
- VS Code:
~/.vscode/argv.json (Insiders: ~/.vscode-insiders/argv.json)
- Cursor:
~/.cursor/argv.json
{
"enable-proposed-api": ["ali50m.xnes"]
}
Then restart the editor and disable Copilot NES if both extensions are installed.
Install from a VSIX (local)
code --install-extension xnes-1.0.2.vsix
Cursor 3.25.6
Cursor 3.25.6 embeds VS Code 1.128.0. ~/.cursor/argv.json (on Windows:
C:\Users\<user>\.cursor\argv.json) grants the proposal as shown above. Then
restart Cursor and disable Cursor's own Tab/NES if both are active.
On 1.128, basic inline edits work, but the host does not provide
handleEndOfLifetime, handleListEndOfLifetime, displayLocation or
jumpToPosition. Continuous-tab acceptance, cross-file labels, next-cursor jump
and accepted-count metrics are therefore degraded; the status bar may show
accepted counts as 0. VS Code 1.143+ keeps the full behavior.
Commands
| Command |
Description |
XNES: 打开设置 |
Endpoint, key, model, speed test |
XNES: 选择模型 |
Model QuickPick / open settings |
XNES: 立即请求建议 |
Explicit trigger |
XNES: 试运行(不显示建议) |
Run the pipeline and log the result without changing the editor |
XNES: 诊断 |
Proposed API, Copilot conflicts, proxy, metrics |
XNES: 在此工作区启用/禁用 |
Per-workspace kill switch |
XNES: 全局启用/禁用 |
Global xnes.enabled switch |
XNES: 显示日志 |
Output channel |
XNES: 启用完整内联建议(写入 argv.json) |
Grant the proposed API without editing argv.json by hand |
Development
npm install
npm run check # tsc --noEmit
npm test # vitest unit tests
npm run build # dist/extension.js + dist/webview
npm run test:integration # extension development host, proposed API enabled
npm run test:integration:store # same suite without --enable-proposed-api
npm run package
Integration tests download VS Code 1.143.1 into .vscode-test/ and run against a
local mock OpenAI server. test:integration:store reproduces a store install by
withholding --enable-proposed-api, which makes the host strip
package.json#enabledApiProposals exactly like the Marketplace and Open VSX do.
Keep both modes green: activation and every command must work in store mode.
Publishing
.github/workflows/publish.yml publishes the same VSIX to the VS Code
Marketplace and Open VSX. It runs manually or on a v* tag, and expects the
VSCE_PAT and OVSX_TOKEN repository secrets. See docs/publishing.md.
Offline evaluation
Use any OpenAI-compatible endpoint. The API key can come from
XNES_API_KEY instead of the command line so it does not end up in shell
history:
$env:XNES_BASE_URL = "https://api.example.com/v1"
$env:XNES_MODEL = "my-model"
$env:XNES_API_KEY = "sk-..."
node test/eval/run.mjs
Or keep the values in a local, gitignored file so a new shell or the agent can
read them without exporting anything (see test/eval/env.example):
# test/eval/.env.local
XNES_BASE_URL=https://api.example.com/v1
XNES_MODEL=my-model
XNES_API_KEY=sk-...
The runner reads test/eval/.env.local by default; use --env-file <path> for
another location. Command-line flags win over process environment, which wins
over the file.
Or pass everything explicitly:
node test/eval/run.mjs --base-url https://api.example.com/v1 --api-key $env:KEY --model my-model --format unifiedXml
--format also accepts searchReplace and codeBlock. Single-file cases live
in test/eval/cases/; results are written to test/eval/.results/latest.json.
Cross-file cases live in test/eval/cross-file-cases/. They seed history with
a source-file edit, run the normal pipeline on a target file, and compare the
result with a hand-written expected target:
node test/eval/run.mjs --cross-file --runs 3
--runs repeats each case to estimate a stable hit rate; each run is a real
model request. Results are written to
test/eval/.results/latest-cross-file.json. Add --cross-file-only to skip the
single-file suite while tuning cross-file behavior. Use --case <name> to run
one case.
Useful tuning flags (both modes):
node test/eval/run.mjs --cross-file-only --runs 5 --recent-files 5 --recent-tokens 2000 --temperature 0 --max-output-tokens 512
The runner reports strict exact and semantic matches. semantic ignores
blank-line and trailing-whitespace differences, which distinguishes formatting
drift from wrong code. latest.json and latest-cross-file.json also include
the raw response and edit details for each attempt.
--recent-files / --recent-tokens change how much other-file context the
prompt carries; for cross-file cases these are the main knobs.
--temperature defaults to 0 for reproducible evaluation.
--max-output-tokens defaults to 512.
Measured baseline on 2026-10-25 with JIUTIAN-75B-32K, unifiedXml,
temperature 0:
| Suite |
Result |
| Single-file |
format 100%, exact 100% (12/12), semantic 100%, no-change 100%, destructive 0 |
Cross-file (--runs 5) |
exact 100% (15/15), semantic 100%, format 100%, destructive 0 |
Single-file initially missed multi-line-py: the model returned only part of
the edit window. The user postscript now requires the complete window,
including blank and unchanged lines, and two consecutive full runs matched
12/12 with no destructive edits. Cross-file initially failed on blank-line
formatting and missing import updates; preserving blank lines, allowing
import-only propagation, and giving the cross-file prompt a larger window
moved it to 100%.
For a quick mock smoke test, point --base-url at any local SSE server that
returns <NO_CHANGE> or a valid <EDIT>; the runner does not require a real
provider.
The runner entry is test/eval/runner.ts; CLI/env parsing, fixtures, scoring,
engine setup, and result writing live in separate modules, and the single-file
and cross-file suites are under test/eval/suites/.
Live multi-file smoke test
With a real endpoint in test/eval/.env.local, run the extension inside a real
VS Code test host:
npm run test:live
This opens copies of the repo's rename-export-ts cross-file fixture, renames
the source export, runs the real cross-file prefetch, and verifies that the
target suggestion is served from cache with the Cross-file next edit
displayLocation label. It does not modify tracked files.
A rename on arbitrary src/ files is also possible, but the current model
returned no suggestion for the applyEdits and CrossFileStore.remember
experiments; use the manual checklist for those larger, intent-driven changes.
Architecture
See dev.md (Chinese) for the full contract. The short version:
provider.ts (VS Code adapter)
→ requestCoordinator (policy, debounce, dedupe, cancel, rate limit, cache)
→ nextEditEngine (prompt → SSE → parser → streaming diff → filters)
→ vendor/vs (ported edit algebra for safe offset mapping)
Core modules under src/core/ never import vscode.
Privacy
The prompt contains only the cursor-local current-file excerpt, recent edit
diffs, the surrounding code window with a relative path, and short excerpts
around recent edits in up to five other files (set
xnes.prompt.recentFilesMax to 0 to disable the last part). Only relative
paths are sent; excluded files never enter history or the prompt. Endpoints,
model names and keys cannot be changed from settings.json or a workspace's
.vscode/settings.json; they are only written by the settings webview. Only the
user/machine http.proxy value is honored; workspace proxy overrides are
ignored. Prompt logging is off by default (xnes.log.includePrompt).
Speculative prefetch is off by default; when enabled it sends the same bounded
prompt after you accept a suggestion, without showing anything. Cross-file
prefetch is also off by default and only targets files that already passed the
history/privacy filters.