Problem: Adding AI to Your Development Workflow
You want to integrate AI into VS Code but existing extensions don't fit your workflow. Building a custom extension seems complex, but with modern tools and AI APIs, you can ship one in under an hour.
You'll learn:
- Set up a VS Code extension from scratch
- Integrate Claude API for code analysis
- Handle user input and editor interactions
- Package and publish your extension
Time: 45 min | Level: Intermediate
Why Build Your Own Extension
Off-the-shelf AI extensions are generic. A custom extension lets you:
Benefits:
- Tailor prompts to your coding style
- Add team-specific refactoring rules
- Control costs by caching responses
- Integrate with your existing tools
Common use cases:
- Code review automation
- Custom snippet generation
- Documentation writers
- Test case generators
Prerequisites
# Check versions
node --version # Need 18.x or higher
npm --version # Need 9.x or higher
You'll need:
- VS Code 1.85+ installed
- Node.js 18+ and npm
- Anthropic API key (get at console.anthropic.com)
- 45 minutes of focused time
Solution
Step 1: Scaffold the Extension
# Install Yeoman and VS Code generator
npm install -g yo generator-code
# Generate extension
yo code
# Select these options:
# ? What type: New Extension (TypeScript)
# ? Name: ai-refactor-helper
# ? Identifier: ai-refactor-helper
# ? Description: AI-powered Code Refactoring
# ? Initialize git: Yes
# ? Package manager: npm
cd ai-refactor-helper
Expected: Creates project with package.json, src/extension.ts, and config files.
If it fails:
- "yo: command not found": Run
npm install -g yo generator-codeagain - EACCES error: Use
sudo npm install -gon macOS/Linux
Step 2: Add Claude API Integration
# Install Anthropic SDK
npm install @anthropic-ai/sdk
npm install --save-dev @types/node
Update src/extension.ts:
import * as vscode from 'vscode';
import Anthropic from '@anthropic-ai/sdk';
let anthropic: Anthropic;
export function activate(context: vscode.ExtensionContext) {
// Initialize Anthropic client
const apiKey = vscode.workspace.getConfiguration('aiRefactor').get<string>('apiKey');
if (!apiKey) {
vscode.window.showErrorMessage('Set your Anthropic API key in settings');
return;
}
anthropic = new Anthropic({ apiKey });
// Register refactor command
let disposable = vscode.commands.registerCommand(
'ai-refactor-helper.refactorCode',
async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('No active editor');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
if (!selectedText) {
vscode.window.showWarningMessage('Select code to refactor');
return;
}
await refactorWithAI(editor, selection, selectedText);
}
);
context.subscriptions.push(disposable);
}
async function refactorWithAI(
editor: vscode.TextEditor,
selection: vscode.Selection,
code: string
) {
// Show progress
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'Refactoring with AI...',
cancellable: false,
},
async () => {
try {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 2000,
messages: [
{
role: 'user',
content: `Refactor this code for better readability and performance. Return ONLY the refactored code, no explanations:
\`\`\`
${code}
\`\`\``,
},
],
});
// Extract refactored code
const refactoredCode = message.content[0].type === 'text'
? message.content[0].text.replace(/```[\w]*\n?/g, '').trim()
: '';
// Replace selection
await editor.edit((editBuilder) => {
editBuilder.replace(selection, refactoredCode);
});
vscode.window.showInformationMessage('Code refactored successfully!');
} catch (error) {
vscode.window.showErrorMessage(`Refactor failed: ${error}`);
}
}
);
}
export function deactivate() {}
Why this works:
withProgressshows user feedback during API calls- Prompt asks for code only (no markdown formatting to strip)
editor.editproperly handles undo/redo- Error handling prevents crashes on API failures
Step 3: Configure Extension Settings
Update package.json to add API key configuration:
{
"name": "ai-refactor-helper",
"displayName": "AI Refactor Helper",
"description": "AI-powered Code Refactoring using Claude",
"version": "0.0.1",
"engines": {
"vscode": "^1.85.0"
},
"categories": ["Other"],
"activationEvents": [],
"main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "ai-refactor-helper.refactorCode",
"title": "Refactor Code with AI"
}
],
"configuration": {
"title": "AI Refactor Helper",
"properties": {
"aiRefactor.apiKey": {
"type": "string",
"default": "",
"description": "Your Anthropic API key (get from console.anthropic.com)",
"markdownDescription": "Your Anthropic API key. Get one at [console.anthropic.com](https://console.anthropic.com)"
}
}
},
"keybindings": [
{
"command": "ai-refactor-helper.refactorCode",
"key": "ctrl+shift+r",
"mac": "cmd+shift+r",
"when": "editorHasSelection"
}
]
},
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"lint": "eslint src --ext ts"
},
"devDependencies": {
"@types/node": "^18.x",
"@types/vscode": "^1.85.0",
"@typescript-eslint/eslint-plugin": "^6.x",
"@typescript-eslint/parser": "^6.x",
"eslint": "^8.x",
"typescript": "^5.3.0"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.30.0"
}
}
Key additions:
configurationsection lets users set API key in VS Code settingskeybindingsadds Ctrl+Shift+R (Cmd+Shift+R on Mac) shortcutwhenclause ensures command only works when text is selected
Step 4: Test Your Extension
# Compile TypeScript
npm run compile
# Press F5 in VS Code to launch Extension Development Host
# Or run:
code --extensionDevelopmentPath=$(pwd)
In the Extension Development Host window:
- Open VS Code settings (Ctrl+,)
- Search for "AI Refactor"
- Paste your Anthropic API key
- Open any code file
- Select some code
- Press Ctrl+Shift+R or run "Refactor Code with AI" from Command Palette (Ctrl+Shift+P)
Expected: Selected code gets refactored and replaced in the editor.
If it fails:
- "API key not set": Check Settings > Extensions > AI Refactor Helper
- "No response": Check API key is valid at console.anthropic.com
- Compile errors: Run
npm installagain to ensure dependencies installed
Step 5: Add Error Handling and Validation
Enhance src/extension.ts with better error handling:
// Add at the top
interface RefactorConfig {
apiKey: string;
maxTokens: number;
temperature: number;
}
function getConfig(): RefactorConfig | null {
const config = vscode.workspace.getConfiguration('aiRefactor');
const apiKey = config.get<string>('apiKey', '');
if (!apiKey || apiKey.length < 10) {
vscode.window.showErrorMessage(
'Invalid API key. Set it in Settings > AI Refactor Helper',
'Open Settings'
).then(selection => {
if (selection === 'Open Settings') {
vscode.commands.executeCommand('workbench.action.openSettings', 'aiRefactor.apiKey');
}
});
return null;
}
return {
apiKey,
maxTokens: config.get<number>('maxTokens', 2000),
temperature: config.get<number>('temperature', 0.3),
};
}
// Update activate function
export function activate(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand(
'ai-refactor-helper.refactorCode',
async () => {
const config = getConfig();
if (!config) return;
// Initialize client per-request (handles key changes)
anthropic = new Anthropic({ apiKey: config.apiKey });
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('No active editor');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
// Validate selection
if (!selectedText || selectedText.length < 10) {
vscode.window.showWarningMessage('Select at least 10 characters of code');
return;
}
if (selectedText.length > 4000) {
vscode.window.showWarningMessage('Selection too large (max 4000 chars). Select less code.');
return;
}
await refactorWithAI(editor, selection, selectedText, config);
}
);
context.subscriptions.push(disposable);
}
Why these checks matter:
- API key validation prevents wasted API calls
- Character limits control costs
- "Open Settings" button improves UX
- Per-request client init allows key changes without restart
Step 6: Package the Extension
# Install packaging tool
npm install -g @vscode/vsce
# Package extension
vsce package
# Creates: ai-refactor-helper-0.0.1.vsix
Expected: Creates a .vsix file you can install locally or publish.
To install locally:
# In VS Code
code --install-extension ai-refactor-helper-0.0.1.vsix
If it fails:
- "Missing publisher": Add
"publisher": "yourname"to package.json - "Missing LICENSE": Create LICENSE.txt or add
"license": "MIT"to package.json - "Missing README": Add basic README.md explaining the extension
Step 7: Publish to VS Code Marketplace (Optional)
# Create publisher account at marketplace.visualstudio.com
# Get Personal Access Token from Azure DevOps
# Login
vsce login yourpublisher
# Publish
vsce publish
Before publishing:
- Add icon.png (128x128) to project root
- Write comprehensive README.md
- Add screenshots showing the extension in action
- Test on Windows, Mac, and Linux
- Set up GitHub repository for issues
Publishing requirements:
- Valid publisher account
- Azure DevOps Personal Access Token
- Icon and README
- Unique extension name
Verification
Test the complete workflow:
- Install your extension
- Set API key in settings
- Open a TypeScript/JavaScript file
- Select a function
- Press Ctrl+Shift+R
- Verify code is refactored
You should see:
- Progress notification appears
- Selected code is replaced
- Success message shows
- Undo (Ctrl+Z) restores original code
Advanced: Add Multiple Refactor Options
Make the extension more useful with multiple AI commands:
// Add to package.json commands array
{
"command": "ai-refactor-helper.explainCode",
"title": "Explain Selected Code"
},
{
"command": "ai-refactor-helper.addTests",
"title": "Generate Tests for Code"
}
// Add to extension.ts
context.subscriptions.push(
vscode.commands.registerCommand('ai-refactor-helper.explainCode', async () => {
const { editor, selectedText } = getEditorAndSelection();
if (!editor || !selectedText) return;
const explanation = await callClaude(`Explain this code concisely:\n\n${selectedText}`);
// Show in sidebar
const panel = vscode.window.createWebviewPanel(
'codeExplanation',
'Code Explanation',
vscode.ViewColumn.Beside,
{}
);
panel.webview.html = `<html><body><pre>${explanation}</pre></body></html>`;
})
);
context.subscriptions.push(
vscode.commands.registerCommand('ai-refactor-helper.addTests', async () => {
const { editor, selectedText } = getEditorAndSelection();
if (!editor || !selectedText) return;
const tests = await callClaude(
`Generate Jest unit tests for this code:\n\n${selectedText}`
);
// Create new file with tests
const doc = await vscode.workspace.openTextDocument({
content: tests,
language: 'typescript',
});
await vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside);
})
);
What You Learned
- VS Code extensions use standard TypeScript and Node.js
- Claude API integration takes ~20 lines of code
- User settings are defined in package.json
contributes.configuration - Progress notifications improve perceived performance
- Proper error handling prevents user frustration
Limitations:
- Claude API has rate limits (check console.anthropic.com)
- Large code selections consume more tokens
- Network latency affects responsiveness
When NOT to use this approach:
- Real-time suggestions (use Language Server Protocol instead)
- Offline functionality needed (bundle a local model)
- Sub-second response required (cache or preprocess)
Cost Considerations
Typical usage:
- Refactoring 100 lines: ~500 tokens input, ~150 tokens output
- Cost per request: ~$0.001 (Claude Sonnet)
- 1000 refactors/month: ~$1.00
Optimization tips:
- Cache common refactoring patterns
- Use prompt caching for system prompts
- Add token limits in settings
- Batch similar requests
Troubleshooting
Extension Not Loading
# Check logs
code --log-level trace
# Look for activation errors in Output > Extension Host
Common causes:
- Syntax error in extension.ts
- Missing dependency in package.json
- Invalid activation event
API Calls Failing
// Add detailed logging
try {
const message = await anthropic.messages.create({...});
console.log('API Response:', message);
} catch (error) {
console.error('API Error:', error);
if (error instanceof Anthropic.APIError) {
vscode.window.showErrorMessage(`API Error: ${error.message}`);
}
}
Check:
- API key is valid and has credits
- Network connection works
- Rate limits not exceeded
Command Not Appearing
Verify package.json:
- Command ID matches code
- Title is user-friendly
- No typos in command name
Force reload:
Ctrl+Shift+P > Developer: Reload Window
Production Checklist
Before publishing:
- Test on Windows, Mac, Linux
- Add comprehensive README with screenshots
- Include LICENSE file
- Set up GitHub repository
- Add issue templates
- Write CHANGELOG.md
- Add CI/CD for automated testing
- Include icon.png (128x128)
- Set semantic version (e.g., 1.0.0)
- Test with large codebases
- Verify all error messages are helpful
- Add telemetry (optional, with opt-out)
- Document configuration options
- Create demo video
Resources
Official Documentation:
Example Extensions:
- GitHub Copilot - AI pair programming
- REST Client - API testing
- GitLens - Git integration
Tools:
- @vscode/test-electron - Testing framework
- yo code - Extension generator
- ovsx - Open VSX publishing
Tested on VS Code 1.86.0, Node.js 20.11.0, @anthropic-ai/sdk 0.30.0, macOS Sonoma & Windows 11