1. 这个插件到底解决什么问题写代码的人都有一个共同的“懒癌”时刻功能改完了测试也过了打开终端准备提交结果对着git commit -m 的引号发呆。憋了半天写出一句fix bug过两周回头看提交记录自己都不知道当时修的是哪个 bug。团队协作的时候更尴尬代码评审的人看到一堆update、修改、提交这样的信息根本没法从提交历史里快速定位变更意图。VSCode Commit AI这个项目要干的事情非常直接把“写提交信息”这件事交给 AI。你在 VSCode 的源代码管理面板里点一下按钮插件自动读取当前暂存区的 diff调用大模型生成一条符合 Conventional Commits 规范的提交信息你确认或者微调之后直接提交。整个链路不需要离开编辑器不需要复制 diff 到网页端也不需要记那些feat(scope):、fix(scope):的格式规则。这个插件适合几类人一是日常用 Git 但懒得写规范提交信息的独立开发者二是团队里推行了提交规范、但成员总是记不住格式的前端或后端工程师三是想学习 VSCode 插件开发、想了解怎么把 OpenAI API 集成进编辑器扩展的技术爱好者。哪怕你之前没写过 VSIX 插件只要会基本的 TypeScript 和 npm 操作跟着思路走也能理解整个实现脉络。我先把结论放在前面这个项目的技术栈并不复杂核心难点不在“调用 OpenAI”而在怎么拿到精准的 diff、怎么设计 prompt 让输出稳定、怎么处理 API Key 的安全存储以及怎么打包成可以分发的.vsix离线包。下面我按实际开发顺序把每个环节拆开讲。2. 整体架构设计与技术选型思路2.1 为什么选 VSCode 扩展而不是独立 CLI 工具很多人第一反应是写个命令行工具commit-ai一敲就生成提交信息。但这个方案有个致命问题拿不到编辑器上下文。CLI 工具要读取 diff得自己调git diff --cached然后解析输出。而 VSCode 扩展可以直接通过内置的 Git 扩展 API 拿到结构化的变更信息包括哪些文件被修改、每个文件的具体变更行甚至能拿到用户当前在源代码管理面板里选中的文件范围。另一个考虑是交互体验。提交信息生成之后用户大概率需要看一眼、改一改。如果做成 CLI你得把结果打印到终端用户再手动复制粘贴到git commit -m里多了一步操作。而 VSCode 扩展可以直接把生成的信息填进源代码管理面板的输入框用户按CtrlEnter就提交了整个流程是闭环的。从分发角度看.vsix离线包的分发成本极低。你把文件发给同事对方在 VSCode 里点“从 VSIX 安装”选一下文件就装好了。不需要配置 npm 源不需要处理 Node 版本兼容对非技术岗位的协作者特别友好。2.2 核心模块划分整个插件我把它拆成四个模块每个模块职责单一方便单独调试模块职责关键依赖Git 交互层获取暂存区 diff、执行提交VSCode Git Extension APIAI 调用层组装 prompt、请求 OpenAI、解析响应openai npm 包或原生 fetch配置管理层读写 API Key、模型选择、语言偏好VSCode Configuration API SecretStorageUI 交互层注册命令、状态栏按钮、输入框回填VSCode Commands API这样拆的好处是AI 调用层可以独立测试。你可以写个脚本喂一段固定的 diff 进去看模型输出是否符合预期不用每次都启动整个扩展宿主。2.3 关于模型选择的取舍热词里出现了openai、openai的api key获取方法、openai注册教程说明大家默认会用 OpenAI 的接口。但实际开发中我建议把模型调用抽象成可配置的原因有两个一是不同模型的输出风格差异很大gpt-4o-mini便宜但有时候会漏掉 scopegpt-4o稳定但成本高二是团队里可能有人用其他兼容 OpenAI 协议的服务抽象一层之后切换成本很低。具体做法是在配置里暴露commitAi.model和commitAi.baseUrl两个选项默认值分别设为gpt-4o-mini和官方地址。用户想换模型或者换服务商改配置就行不用改代码。注意API Key 绝对不能明文存在settings.json里。VSCode 提供了SecretStorageAPI底层走的是系统钥匙串Windows 上是 Credential ManagermacOS 上是 Keychain。这个细节后面会详细讲。3. 开发环境搭建与项目初始化3.1 前置工具安装先把基础环境准备好。你需要 Node.js 18 以上版本npm 随 Node 一起装好。Git 是必须的因为插件本身要调用 Git 命令而且开发过程中你也要用 Git 管理代码。VSCode 建议用最新稳定版老版本可能缺少某些 Extension API。验证环境是否就绪依次执行node -v npm -v git --version code --version四个命令都能正常输出版本号说明基础环境没问题。如果code命令不识别在 VSCode 里按CtrlShiftP输入Shell Command: Install code command in PATH执行一下就好。3.2 用官方脚手架生成项目骨架VSCode 官方提供了yo code生成器比手动建目录结构省事得多。全局安装npm install -g yo generator-code然后在你存放项目的目录下执行yo code交互式问答里这样选类型选New Extension (TypeScript)名称填commit-ai标识符自动生成直接回车描述写Generate commit messages with AI初始化 Git 仓库选Yes包管理器选npm生成完之后目录结构大致是这样commit-ai/ ├── .vscode/ │ ├── launch.json │ └── tasks.json ├── src/ │ └── extension.ts ├── package.json ├── tsconfig.json └── .vscodeignorelaunch.json里已经配好了扩展宿主调试配置按F5就能启动一个加载了当前插件的 VSCode 窗口改代码之后在新窗口里CtrlR重载即可生效。3.3 安装核心依赖进入项目目录装两个关键包npm install openai npm install --save-dev types/vscodeopenai是官方 SDK封装了鉴权、重试、流式响应等逻辑比手写 fetch 省心。types/vscode提供 Extension API 的类型定义写代码时有补全和类型检查。这里有个坑要提前说openai包的版本更新很快v4 和 v3 的 API 写法完全不同。如果你在网上搜到的示例代码用的是new OpenAIApi(configuration)这种写法那是 v3 的老语法装最新版会报错。本文基于 v4 的写法核心是new OpenAI({ apiKey })然后client.chat.completions.create(...)。3.4 package.json 里的关键声明package.json是 VSCode 插件的“身份证”几个字段必须配对{ name: commit-ai, displayName: Commit AI, description: Generate commit messages with AI, version: 0.0.1, engines: { vscode: ^1.85.0 }, activationEvents: [], main: ./out/extension.js, contributes: { commands: [ { command: commitAi.generate, title: Commit AI: Generate Message } ], configuration: { title: Commit AI, properties: { commitAi.model: { type: string, default: gpt-4o-mini, description: Model used for generating commit messages }, commitAi.language: { type: string, default: zh-CN, enum: [zh-CN, en-US], description: Language of generated commit messages } } } } }activationEvents留空数组是 VSCode 1.74 之后的新规范命令注册会自动触发激活不用再写onCommand:commitAi.generate。engines.vscode填你实际开发用的版本填太高会导致老版本 VSCode 用户装不上。4. 核心功能实现从 diff 到提交信息4.1 获取暂存区 diff 的正确姿势这是整个插件最容易被做错的地方。很多人直接child_process.exec(git diff --cached)能跑通但有几个问题一是拿不到 VSCode Git 扩展已经缓存的状态二是如果用户的工作区有多个 Git 仓库你不知道该用哪个三是输出格式受用户 Git 配置影响可能带颜色转义字符。正确做法是通过 VSCode 内置的 Git 扩展 APIimport * as vscode from vscode; async function getStagedDiff(): Promisestring | undefined { const gitExtension vscode.extensions.getExtension(vscode.git); if (!gitExtension) { vscode.window.showErrorMessage(Git extension not found); return undefined; } const git gitExtension.exports.getAPI(1); const repo git.repositories[0]; if (!repo) { vscode.window.showErrorMessage(No Git repository found); return undefined; } const diff await repo.diff(true); // true 表示只取暂存区 return diff; }repo.diff(true)返回的是纯文本 diff已经去掉了颜色转义格式干净。git.repositories[0]取第一个仓库如果你的项目是 monorepo 有多个子仓库需要根据当前活动文件判断用哪个这里先按单仓库处理。拿到 diff 之后要做一次长度检查。有些提交涉及几千行变更全量塞给模型会超 token 限制而且费用也高。我的做法是设一个阈值比如 8000 字符超过就截断并在 prompt 里说明“diff 已截断请基于可见部分生成概括性信息”。4.2 Prompt 设计让输出稳定可解析Prompt 写得好不好直接决定生成质量。我试过很多版本最后稳定下来的结构是这样的function buildPrompt(diff: string, language: string): string { const langInstruction language zh-CN ? 请用简体中文输出提交信息。 : Output the commit message in English.; return 你是一个 Git 提交信息生成助手。根据下面的 diff 生成一条提交信息。 规则 1. 遵循 Conventional Commits 规范格式为 type(scope): subject 2. type 只能是 feat、fix、docs、style、refactor、test、chore 之一 3. scope 用英文小写表示影响的模块没有明确模块时省略 4. subject 不超过 50 个字符不以句号结尾 5. 只输出提交信息本身不要任何解释、不要 markdown 代码块 6. ${langInstruction} diff ${diff}; }几个关键点解释一下。第一明确列出 type 的枚举值模型就不会自创update、improve这种不在规范里的类型。第二强调“只输出提交信息本身”否则模型很可能给你来一段“根据 diff 分析建议的提交信息是...”你还得写正则去提取。第三语言指令放在规则里而不是单独一句模型遵循度更高。实测下来gpt-4o-mini在这个 prompt 下的输出合格率大概在 85% 左右偶尔会漏 scope 或者 subject 超长。gpt-4o能到 95% 以上。如果对成本不敏感直接用gpt-4o省心。4.3 调用 OpenAI 接口v4 SDK 的调用写法import OpenAI from openai; async function generateCommitMessage( diff: string, apiKey: string, model: string, language: string ): Promisestring { const client new OpenAI({ apiKey }); const response await client.chat.completions.create({ model, messages: [ { role: user, content: buildPrompt(diff, language) } ], temperature: 0.3, max_tokens: 100 }); const content response.choices[0]?.message?.content?.trim(); if (!content) { throw new Error(Empty response from model); } return content; }temperature设 0.3 是权衡的结果。设 0 输出最稳定但有时候过于死板设 0.7 会开始“发挥创意”写出奇怪的 scope。0.3 在稳定性和灵活性之间比较平衡。max_tokens设 100 足够了一条提交信息撑死几十个 token设太大反而浪费。错误处理要覆盖几种情况API Key 无效401、余额不足429、网络超时。每种情况给用户不同的提示不要统一报“请求失败”。4.4 把结果回填到源代码管理输入框生成完之后最自然的交互是把信息填进 VSCode 源代码管理面板的输入框。这需要拿到 Git 扩展的仓库对象设置inputBox.valueasync function fillCommitMessage(message: string) { const gitExtension vscode.extensions.getExtension(vscode.git); const git gitExtension?.exports.getAPI(1); const repo git?.repositories[0]; if (repo) { repo.inputBox.value message; } }设置完之后用户直接按CtrlEnter就提交了。如果用户想改光标已经在输入框里直接编辑就行。这个体验比弹个模态框让用户复制粘贴好太多。4.5 注册命令与状态栏入口在activate函数里注册命令export function activate(context: vscode.ExtensionContext) { const disposable vscode.commands.registerCommand( commitAi.generate, async () { // 完整流程取 diff - 取 key - 调模型 - 回填 } ); context.subscriptions.push(disposable); }再在状态栏加个按钮方便一键触发const statusBar vscode.window.createStatusBarItem( vscode.StatusBarAlignment.Left, 100 ); statusBar.text $(sparkle) Commit AI; statusBar.command commitAi.generate; statusBar.tooltip Generate commit message with AI; statusBar.show(); context.subscriptions.push(statusBar);$(sparkle)是 VSCode 内置的图标语法会渲染成一个小星星。状态栏按钮的好处是不用记快捷键鼠标点一下就行。5. API Key 安全存储与配置管理5.1 为什么不能用 settings.json 存 Keysettings.json是明文文件而且很多人会把它同步到云端或者提交到 dotfiles 仓库。API Key 写在那里等于公开。VSCode 从 1.53 版本开始提供了SecretStorageAPI底层调用操作系统的凭据管理服务加密存储其他扩展读不到。用法很简单// 存 await context.secrets.store(commitAi.apiKey, apiKey); // 取 const apiKey await context.secrets.get(commitAi.apiKey); // 删 await context.secrets.delete(commitAi.apiKey);第一次使用时检测不到 Key就弹输入框让用户填async function ensureApiKey(context: vscode.ExtensionContext): Promisestring | undefined { let apiKey await context.secrets.get(commitAi.apiKey); if (!apiKey) { apiKey await vscode.window.showInputBox({ prompt: Enter your OpenAI API Key, password: true, ignoreFocusOut: true }); if (apiKey) { await context.secrets.store(commitAi.apiKey, apiKey); } } return apiKey; }password: true让输入内容显示为圆点ignoreFocusOut: true防止用户点到别处时输入框自动关闭导致输入丢失。5.2 配置项的读取与默认值处理模型和语言从配置里读const config vscode.workspace.getConfiguration(commitAi); const model config.getstring(model, gpt-4o-mini); const language config.getstring(language, zh-CN);第二个参数是默认值即使用户没配也能拿到合理值。如果想让配置修改后立即生效可以监听onDidChangeConfiguration事件不过对于这个插件来说每次生成时重新读取配置就够了没必要监听。5.3 提供一个清除 Key 的命令用户可能想换 Key或者怀疑 Key 泄露了想删掉。加一个命令vscode.commands.registerCommand(commitAi.clearApiKey, async () { await context.secrets.delete(commitAi.apiKey); vscode.window.showInformationMessage(API Key cleared); });在package.json的contributes.commands里也声明一下这样用户在命令面板里能搜到。6. 打包分发与离线安装6.1 用 vsce 打包成 .vsixvsce是 VSCode 官方的打包工具npm install -g vscode/vsce打包之前先确认package.json里有这几个字段name、displayName、description、version、publisher、engines.vscode。publisher如果没有可以随便填一个本地安装不校验。然后执行vsce package生成的.vsix文件就在项目根目录。如果报错说缺少 README 或 LICENSE建两个空文件就行。如果报错说repository字段格式不对把package.json里的repository字段删掉或者填一个合法的 URL。6.2 离线安装的两种方式方式一在 VSCode 里按CtrlShiftP输入Extensions: Install from VSIX选择文件。方式二命令行安装code --install-extension commit-ai-0.0.1.vsix装完之后重启 VSCode状态栏应该能看到Commit AI按钮。如果没看到检查一下activationEvents和main字段是否配对。6.3 .vscodeignore 的配置打包时默认会把src/、node_modules/里的开发依赖都打进去导致.vsix文件很大。在.vscodeignore里排除掉不需要的文件.vscode/** src/** node_modules/** tsconfig.json **/*.map .gitignore注意node_modules不能全排除运行时依赖比如openai包需要打进去。更精细的做法是用esbuild或webpack把代码和依赖打包成单个 JS 文件这样.vsix能压到几百 KB。不过对于内部使用的插件文件大一点无所谓先跑通再说。7. 常见问题与排查技巧7.1 问题速查表现象可能原因排查方法命令面板搜不到 Commit AIpackage.json的contributes.commands没配对检查 command id 是否和registerCommand一致提示 No Git repository found工作区没打开 Git 仓库或 Git 扩展未激活确认文件夹里有.git目录生成的信息是英文配置里language没设成zh-CN检查 settings.json401 错误API Key 无效或过期用commitAi.clearApiKey清除后重新输入429 错误余额不足或请求频率超限去 OpenAI 后台检查用量生成的 scope 是中文模型没遵循 prompt 规则换gpt-4o或加强 prompt 约束打包报错 missing publisherpackage.json缺publisher字段随便填一个字符串7.2 几个踩过的坑坑一diff 太大导致超时。有一次提交涉及自动生成的 lock 文件diff 有十几万行请求发出去直接超时。后来加了过滤逻辑package-lock.json、yarn.lock、pnpm-lock.yaml这类文件直接从 diff 里剔除因为它们对理解变更意图没有帮助。坑二模型输出带 markdown 代码块。早期 prompt 没强调“不要 markdown”模型经常输出text feat(auth): add login回填到输入框里带着三个反引号还得手动删。后来在 prompt 里明确禁止并且在代码里加了一层清洗 typescript function cleanMessage(raw: string): string { return raw .replace(/^[a-z]*\n?/i, ) .replace(/\n?$/i, ) .trim(); }坑三SecretStorage 在开发模式下不持久。用F5启动的扩展宿主每次重启都是一个全新的环境之前存的 Key 会丢。这是正常的因为开发宿主用的是独立的存储空间。正式安装的插件不会有这个问题。坑四Windows 上路径分隔符问题。如果你在代码里拼路径用了/在 Windows 上可能出问题。统一用path.join()处理。7.3 提升生成质量的小技巧如果发现生成的提交信息不够准确可以试试这几个调整。第一在 prompt 里加一句“优先关注函数名、变量名和注释的变化它们最能反映变更意图”。第二把 diff 里的上下文行数从默认的 3 行调到 5 行给模型更多信息。第三如果项目有特定的提交规范比如必须带 Jira 单号在 prompt 里加一条规则说明格式。还有一个进阶玩法把最近 20 条提交记录也塞进 prompt让模型学习项目的提交风格。这个做法对保持团队提交历史的一致性很有效代价是 prompt 变长、费用增加。8. 后续可以扩展的方向这个插件的基础版本跑通之后有几个方向值得继续做。一是支持多仓库根据当前活动文件自动判断用哪个仓库的 diff。二是加一个“重新生成”按钮用户对第一次结果不满意时不用重新走一遍流程。三是支持自定义 prompt 模板让团队可以把自己的规范写进配置里。四是接入流式响应生成过程中实时显示文字减少等待焦虑。我个人在实际使用中体会最深的一点是AI 生成的提交信息最好还是让用户过一眼再提交。完全自动化的git commit有风险万一模型理解错了变更意图提交历史就被污染了。把生成结果填进输入框、让用户确认这个“人在回路”的设计是最稳妥的。