
1. 从零构建 VS Code AI 编程助手 — 总览篇
难度:⭐⭐ | 核心素材:整个项目架构
这篇文章适合谁?
想了解 AI 编程助手内部原理的开发者 想自己写 VS Code 插件的 TypeScript 开发者 想知道「AI 怎么调用工具」的好奇者
CodeHi 是什么?
CodeHi 是一个 VS Code 侧边栏插件,功能类似 Cursor/Cline——在编辑器里和 AI 对话,AI 能读文件、搜索代码、编辑文件、执行命令。
┌────────────────────────────┐│ VS Code 窗口 ││ ┌──────────┬────────────┐ ││ │ │ 侧边栏 │ ││ │ 编辑器 │ ┌────────┐ │ ││ │ │ │ 对话回答 │ │ ││ │ │ ├────────┤ │ ││ │ │ │ 输入框 │ │ ││ │ │ │ │ │ ││ │ │ ├────────┤ │ ││ │ │ │ 模型▼ │ │ ││ │ │ └────────┘ │ ││ └──────────┴────────────┘ │└────────────────────────────┘技术栈一览
| VS Code 扩展 | extension.tssidebar.ts | |
| LLM 集成 | client.tsstream.ts | |
| 提供者管理 | manager.ts | |
| 工具系统 | tools.tsexecutor.ts, definitions.ts | |
| 提示词构建 | prompt.ts | |
| 上下文管理 | manager.ts | |
| 前端 UI | app.jshandlers.js, popover.js, style.css | |
| Markdown 渲染 | ||
| 持久化 | sidebar.ts |
核心架构图
extension.ts (入口) │ ├── ProviderManager (单例,事件驱动) │ ├── getAllAvailableProviders() │ ├── getEndpointConfig() │ ├── addCustomProvider() │ └── _onDidChange (EventEmitter) │ ├── LLMClient │ ├── chatAgentLoop() ← 核心循环 │ │ ├── requestLLM() → processStream() │ │ └── executeToolCalls() │ └── createClient() → OpenAI 实例 │ ├── ContextManager │ ├── getProjectContext() → 目录树 │ ├── resolveReferences() → @file 解析 │ └── getEditorContext() │ ├── tools/ │ ├── tools.ts → Facade + write 开关 │ ├── executor.ts → 20+ 工具实现 │ └── definitions.ts → ToolDefinition[] │ ├── chat/ │ ├── prompt.ts → buildSystemPrompt() │ └── stream.ts → processStream() │ └── sidebar/ └── sidebar.ts → SidebarView (消息路由+生命周期) │ └── Webview (frontend/) ├── app.js → 主控制器 ├── handlers.js → 消息处理路由表 ├── popover.js → 模型选择 Popover └── style.css → 1809 行纯手写 CSS一条消息的完整旅程
从用户输入到 AI 回答,一条消息经历的全部步骤:
1. 用户输入 "帮我优化 foo.ts" │2. app.js sendMessage() └─→ postMessage({ type: 'userInput', content: '...' }) │3. sidebar.ts onDidReceiveMessage └─→ handleUserInput() │4. contextManager.resolveReferences() └─→ 解析 @引用,注入文件内容 │5. contextManager.getProjectContext() └─→ 首条消息注入项目目录树 │6. buildSystemPrompt() └─→ 动态拼装系统提示词(含写入规则/Provider身份/Skills) │7. trimHistoryForLLM() └─→ 瘦身历史消息 │8. llmClient.chatAgentLoop(messages) └─→ 进入 Agent Loop │ ┌──────────────────────────────┐ │ Agent Loop (最多 200 轮) │ │ │ │ 8a. requestLLM() │ │ └─→ OpenAI SDK 流式请求 │ │ └─→ processStream() │ │ └─→ 逐 chunk 推送 │ │ Webview │ │ │ │ 8b. LLM 返回 tool_calls │ │ └─→ executeToolCalls() │ │ └─→ executeToolCall() │ │ └─→ switch(name) │ │ ├─ read_file │ │ ├─ search_code │ │ ├─ write_file │ │ └─ ... │ │ │ │ 8c. tool 结果追加到 messages │ │ └─→ 回到 8a 继续循环 │ │ │ │ 8d. LLM 返回纯文本 → 退出 │ └──────────────────────────────┘ │9. 保存聊天历史到 globalState │10. postMessage({ type: 'requestEnd', content }) └─→ Webview 显示最终答案关键数字
设计原则
- 最小依赖
:前端零框架(纯 Vanilla JS),后端只用 OpenAI SDK + json5 + marked - 事件驱动
:Provider 变更通过 EventEmitter 广播,UI 自动刷新 - 安全优先
:写入开关 + 危险命令检测 + 文件大小限制 - KV Cache 友好
:历史瘦身保持消息结构不变,最大化缓存命中 - 错误容忍
:工具调用失败不崩溃,作为 tool result 返回给 LLM
下一篇:#2 VS Code 扩展的 Facade 模式实践
夜雨聆风