VS Code Extension Development
Overview
Build VS Code extensions that integrate natively into the editor's UI — activity bar, chat sidebar, and tool infrastructure. Covers the full lifecycle: project setup, tool registration, chat integration, build pipeline, and packaging.
Critical Pitfalls
1. ChatParticipant ≠ Chat Provider (COMMON MISTAKE)
vscode.chat.createChatParticipant registers an @mention participant in the built-in chat. vscode.lm.registerLanguageModelChatProvider + languageModelChatProviders registers a MODEL in the chat model picker. Neither adds a "+ dropdown" entry — see the ground-truth note below.
Correct approach (all three, for full integration):
ChatParticipant→@Hermesmentions in any chat sessionLanguageModelChatProvider→ Hermes in the model pickerviewsContainers.activitybar→ your own sidebar chat (the surface real third-party AI extensions use)
GROUND TRUTH (verified VS Code 1.131, July 2026): the Chat "+" dropdown ("New Chat / New Codex Agent / New Copilot CLI Session") is populated ONLY by VS Code's built-in agent-host harnesses (Agent Host Protocol). Third-party extensions do not appear there — not even Claude Code (inspected anthropic.claude-code-2.1.220/package.json: zero chatParticipants, zero languageModelChatProviders — only viewsContainers/commands). A "missing + dropdown entry" is a non-bug; do not chase it. The agent-host protocol (chat.agentHost.enabled) is the only opt-in path, and it's experimental.
2. activitybar vs panel in viewsContainers
viewsContainers.panel puts your webview in the BOTTOM panel area. viewsContainers.activitybar puts an ICON in the LEFT sidebar (like Explorer, Search, Git). For Claude-like integration, use activitybar.
"viewsContainers": {
"activitybar": [{
"id": "hermes-chat",
"title": "Hermes Agent",
"icon": "./assets/icon.svg"
}]
}
3. LanguageModelChatProvider API location
registerLanguageModelChatProvider is in vscode.lm, NOT vscode.chat. And it may not be in @types/vscode for older type versions — use (vscode.lm as any).registerLanguageModelChatProvider(...) with try/catch for forward compatibility.
4. MCP tools/list requires initialization first
The MCP protocol requires initialize before tools/list. Without it, tools returns empty array. Always send initialize first in tests.
5. Extension activation on reload
After code --install-extension, VS Code doesn't always reload. Use code -r -g D:/path/file:1 to force a window reload that triggers the new extension host.
6. onStartupFinished is essential
Without onStartupFinished in activationEvents, the MCP server won't auto-start. Early builds failed because the stale .vsix didn't have this event.
Build Pipeline
# 1. Type-check (zero errors required)
npx tsc --noEmit --pretty
# 2. Bundle with esbuild
node esbuild.js
# 3. Package .vsix
rm -f *.vsix && npx vsce package
# 4. Kill old MCP server
PID=$(netstat -ano | grep 19999 | grep LISTENING | awk '{print $5}')
[ -n "$PID" ] && taskkill /F /PID $PID
# 5. Install and reload
code --install-extension hermes-agent-vscode-1.0.0.vsix --force
code -r -g README.md:1
Tool Call Loop Pattern
The LLM outputs tool calls in ```tool {"name":"...","arguments":{...}} ``` blocks. Parse, execute, feed results back, repeat.
// Extract tool calls from LLM text
const calls = [];
const pattern = /```tool\n?([\s\S]*?)```/g;
let match;
while ((match = pattern.exec(text)) !== null) {
const parsed = JSON.parse(match[1].trim());
if (parsed?.name) calls.push({ name: parsed.name, arguments: parsed.arguments || {} });
}
// Remove tool blocks from final text
const clean = text.replace(/```tool\n?[\s\S]*?```/g, '').trim();
Max 8-10 iterations. Each tool call result goes back as a user message with [Tool Result: name] wrapper.
Native Chat Integration Checklist
-
package.jsonhaslanguageModelChatProviderscontribution with vendor name -
package.jsonhasviewsContainers.activitybarfor sidebar icon -
package.jsonhasactivationEvents: ["onStartupFinished"] - Extension registers
ChatParticipantviavscode.chat.createChatParticipant - Extension registers
LanguageModelChatProviderviavscode.lm.registerLanguageModelChatProvider - Extension registers
WebviewViewProviderfor the activity bar panel - SVG icon is 24x24, stroke-based, matches VS Code's icon style
-
.vscodeignoredoes NOT excludeassets/directory - System prompt includes tool descriptions in the LLM-compatible format
package.json Contribution Structure
{
"contributes": {
"languageModelChatProviders": [{
"vendor": "hermes",
"icon": "./assets/hermes-icon.svg",
"name": "Hermes Agent",
"description": "..."
}],
"viewsContainers": {
"activitybar": [{
"id": "hermes-chat",
"title": "Hermes Agent",
"icon": "./assets/hermes-icon.svg"
}]
},
"views": {
"hermes-chat": [{
"type": "webview",
"id": "hermes.chatPanel",
"name": "Chat"
}]
},
"commands": [...],
"configuration": { "properties": { ... } }
}
}
When to Use
- Building any VS Code extension with chat UI
- Integrating AI agents into VS Code's native chat
- Setting up MCP servers as VS Code extensions
- Creating activity bar panels with webview UI
Support Files
references/native-chat-integration.md— deeper chat integration detailreferences/shared-db-bridge.md— safe pattern for a second process reading/writing another app's live SQLite DB (node:sqlite, read-only reads, source-tagged writes, live-DB verification recipe). Use when syncing an extension with an app-owned state DB (e.g. Hermes Desktopstate.db).references/skin-theme-sync.md— mirror an app's skin/theme engine onto webview CSS custom properties (built-in presets + user YAML overrides, light/dark polarity from VS Code's active theme, live reload via config watcher + fs.watch, lazyrequire('vscode')accessor so the module stays unit-testable in pure Node).references/eslint9-flat-config.md— hardeningnpm run lintfor a TS extension: ESLint 9 + typescript-eslint 8 flat config, rule tuning rationale (require-await off for tool-registry contract, no-misused-promises checksVoidReturn:false for event-emitter code, unnecessary-condition/optional-chain off for defensive config paths), and the dead-code cleanup workflow (89→0 problems, zero behavior change).references/mcp-reliability-self-healing.md— making an MCP-over-HTTP server hosted by a VS Code extension self-healing: EADDRINUSE port-fallback retry (error listener + recursivelisten), health-check watchdog with auto-restart after N consecutive failures, and usingcontext.secrets(SecretStorage) instead of settings.json for API keys.