1. 为什么要在 Trae 里做这个 Chrome 扩展骨架Chrome 扩展开发最劝退的地方不是写 JS而是环境搭建和调试链路太长Manifest V3 把后台脚本换成了 Service Worker生命周期短、日志入口藏得深再加上权限声明写错一个字段插件直接加载失败还不给明确报错。很多人第一次做扩展卡在「加载已解压的扩展程序」这一步就放弃了。这篇要交付的是一个最小可运行骨架用 Trae 作为编码环境从零建一个 Manifest V3 扩展弹窗里放一个按钮点击后通过 TaoToken 的统一 Key 通道发一次真实的模型请求把返回内容显示在弹窗里。整个过程你能拿到三样东西——可复制的manifest.json、popup.htmlpopup.js、background.jsService Worker以及一份settings.json里填 TaoToken Key 的骨架。适合谁写过一点 HTML/JS、想入门 Chrome 扩展但被 MV3 卡住的人已经在用 Trae 写代码、想把 AI 请求能力塞进浏览器插件的人以及手上有多家模型 Key、想统一走一个通道减少配置成本的人。核心检索词就三个Trae、Chrome 扩展插件、Manifest V3。下面所有步骤都能直接跟做不需要额外装构建工具。2. TaoToken 前置统一 Key 与 API 通道准备在写代码之前先把请求通道准备好。TaoToken 在这里扮演的角色是「统一 Key 统一 API 入口」你不需要在插件里分别对接不同厂商的域名和鉴权格式只要拿一个 Key请求发到同一个 API 地址返回结构也是统一的。对插件这种前端环境来说少一套鉴权逻辑就少一堆坑。你需要做两件事第一拿到 API Key。进入控制台创建密钥路径是 console 页面创建后复制那串以sk-开头的字符串先存到本地文本里后面填进settings.json。第二确认 API 基地址。请求统一发到https://taotoken.net/api对话补全的路径是/v1/chat/completions和常见的 OpenAI 兼容格式一致。也就是说你在插件里用fetch发一个标准 POST 请求就行不需要引入任何 SDK。注意Key 只放在本地settings.json或插件的chrome.storage里不要硬编码进popup.js然后提交到公开仓库。插件代码是明文可分发的Key 泄露等于额度被人白用。如果你还没决定用哪个模型可以先去模型对话页面手动试一条请求确认 Key 和通道是通的再回来写插件代码。这一步能帮你排除掉「到底是 Key 错了还是插件写错了」的扯皮。3. 可复制配置manifest.json 与项目结构先在 Trae 里新建一个空目录比如trae-chrome-ext然后按下面的结构放文件。Trae 的好处是你可以直接把这段结构描述丢给它让它生成初始文件但下面这份是我实测能直接加载的版本建议手动核对一遍字段。trae-chrome-ext/ ├── manifest.json ├── background.js ├── popup.html ├── popup.js └── settings.jsonmanifest.json是整个扩展的配置中心MV3 下manifest_version必须是 3后台脚本用service_worker声明。下面这份配置只申请了必要的权限{ manifest_version: 3, name: TaoToken 请求骨架, version: 1.0.0, description: 在弹窗中通过 TaoToken 统一通道发起一次模型请求, permissions: [storage], host_permissions: [https://taotoken.net/*], background: { service_worker: background.js }, action: { default_popup: popup.html, default_title: TaoToken 请求 } }几个字段值得单独说。permissions里只放了storage因为我们要把 Key 存进chrome.storage.localhost_permissions必须包含https://taotoken.net/*否则 Service Worker 里的fetch会被跨域策略拦掉这是新手最容易漏的一步。action.default_popup指向弹窗页面点击工具栏图标就会打开它。settings.json是给本地开发用的 Key 存放骨架内容长这样{ taotoken: { apiBase: https://taotoken.net/api, apiKey: sk-替换成你自己的Key, model: gpt-4o-mini } }提示settings.json不会被扩展自动读取它只是你手动复制 Key 的来源。真正运行时Key 通过弹窗里的输入框写进chrome.storage.local这样换机器不用改代码。4. 编写 background.jsService Worker 里发请求MV3 的 Service Worker 会在空闲时被浏览器挂起所以不要把状态存在全局变量里所有需要持久化的东西都走chrome.storage。请求逻辑放在这里是因为 Service Worker 环境不受页面 CSP 限制跨域请求更稳。// background.js const API_BASE https://taotoken.net/api; chrome.runtime.onMessage.addListener((request, sender, sendResponse) { if (request.action chat) { handleChat(request.prompt) .then((data) sendResponse({ ok: true, data })) .catch((err) sendResponse({ ok: false, error: err.message })); return true; // 异步响应必须返回 true } }); async function handleChat(prompt) { const { taotoken } await chrome.storage.local.get(taotoken); if (!taotoken || !taotoken.apiKey) { throw new Error(未配置 API Key请先在弹窗中保存); } const res await fetch(${API_BASE}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${taotoken.apiKey} }, body: JSON.stringify({ model: taotoken.model || gpt-4o-mini, messages: [{ role: user, content: prompt }] }) }); if (!res.ok) { const text await res.text(); throw new Error(HTTP ${res.status}: ${text.slice(0, 120)}); } const json await res.json(); return json.choices?.[0]?.message?.content ?? 返回结构异常; }这里有个关键点onMessage的监听器里如果要做异步操作必须return true否则消息通道会提前关闭sendResponse发不回去。我踩过的坑就是忘了这行弹窗一直显示「等待中」查了半天才发现是通道关了。5. 编写 popup.html 与 popup.js弹窗交互弹窗负责两件事保存 Key、触发请求并展示结果。HTML 保持极简一个输入框、一个按钮、一块结果区。!DOCTYPE html html head meta charsetutf-8 / style body { width: 320px; padding: 12px; font-family: system-ui, sans-serif; } input, button { width: 100%; box-sizing: border-box; margin-bottom: 8px; padding: 6px; } #result { white-space: pre-wrap; font-size: 13px; color: #333; min-height: 40px; } /style /head body h3TaoToken 请求骨架/h3 input idapiKey typepassword placeholder粘贴 sk- 开头的 Key / button idsave保存 Key/button input idprompt typetext value用一句话介绍 Chrome 扩展 / button idsend发送请求/button div idresult等待操作…/div script srcpopup.js/script /body /htmlpopup.js里做三件事页面加载时回填已保存的 Key、保存 Key 到chrome.storage.local、发消息给 Service Worker 并渲染返回。// popup.js const $ (id) document.getElementById(id); document.addEventListener(DOMContentLoaded, async () { const { taotoken } await chrome.storage.local.get(taotoken); if (taotoken?.apiKey) $(apiKey).value taotoken.apiKey; }); $(save).addEventListener(click, async () { const apiKey $(apiKey).value.trim(); if (!apiKey.startsWith(sk-)) { $(result).textContent Key 格式不对应以 sk- 开头; return; } await chrome.storage.local.set({ taotoken: { apiKey, apiBase: https://taotoken.net/api, model: gpt-4o-mini } }); $(result).textContent Key 已保存到本地存储; }); $(send).addEventListener(click, () { $(result).textContent 请求中…; chrome.runtime.sendMessage({ action: chat, prompt: $(prompt).value }, (resp) { if (!resp) { $(result).textContent 无响应检查 Service Worker 是否报错; return; } $(result).textContent resp.ok ? resp.data : 出错${resp.error}; }); });注意chrome.runtime.sendMessage的回调里要先判断resp是否存在。如果 Service Worker 抛了未捕获异常回调参数会是undefined直接读resp.ok会二次报错把真正的错误盖掉。6. 加载扩展并验证请求返回代码写完进入验证环节。打开 Chrome地址栏输入chrome://extensions/右上角打开「开发者模式」点击「加载已解压的扩展程序」选中trae-chrome-ext目录。加载成功后工具栏会出现插件图标如果图标是灰色的说明manifest.json有字段错误点扩展卡片上的「错误」按钮看详情。点击图标打开弹窗先粘贴 Key 点保存再点发送请求。正常情况下结果区会显示模型返回的一句话。如果一直转圈或显示无响应按下面的顺序排查。第一看 Service Worker 日志。在chrome://extensions/找到你的扩展卡片点击「Service Worker」链接会弹出一个 DevTools 窗口console.log和网络请求都在这里。请求失败时Network 面板能看到具体的状态码和响应体。第二确认host_permissions写的是https://taotoken.net/*少一个斜杠或写成http都会导致请求被拦。改完manifest.json必须在扩展页面点一次「重新加载」否则改动不生效。第三检查 Key 是否真的存进去了。在 Service Worker 的 DevTools 控制台执行chrome.storage.local.get(taotoken, console.log)能看到对象说明存储正常看到undefined说明保存那步没走通。第四如果返回HTTP 401是 Key 无效或没带上Authorization头返回HTTP 404多半是路径拼错确认是/v1/chat/completions而不是/chat/completions。验证通过后这个骨架就可以往上加功能了比如把结果写回当前标签页、加历史记录、换模型参数。请求通道这块不用再动Key 和地址都统一在chrome.storage里。7. 常见报错与排查清单把上面几步里最容易翻车的点集中列一下遇到问题按表查。现象可能原因处理方式扩展加载失败无报错manifest.jsonJSON 语法错误用编辑器格式化检查逗号、引号弹窗打开空白default_popup路径写错确认文件名与配置一致请求被 CORS 拦缺host_permissions补https://taotoken.net/*并重载回调收到 undefinedService Worker 抛异常打开 SW DevTools 看 console401 未授权Key 错误或未保存重新保存并确认sk-前缀改了代码没生效未重新加载扩展扩展页面点「重新加载」注意调试期间建议把 Service Worker 的 DevTools 一直开着MV3 的 SW 会被挂起关掉窗口后日志就没了重新触发要再点一次弹窗。如果你在接入阶段反复卡在鉴权或路径上可以直接对照接入文档逐字段核对想先确认模型通道本身是否可用去模型对话页面发一条消息最快如果打算把这个骨架扩展成长期用的编码或 Agent 工具Coding Plan 那条线更适合持续调用。8. 把骨架变成你自己的工具到这里一个能跑通真实请求的 Chrome 扩展骨架就完成了。它没有花哨功能但把 MV3 最容易出问题的三块——Service Worker 消息通道、跨域权限、Key 存储——都跑通了。接下来你可以在这个基础上做很多事把弹窗换成侧边栏做常驻助手、加content_scripts把选中文本直接发给模型、用chrome.storage.sync让配置跨设备同步。一个实用技巧把model字段做成弹窗里的下拉选项写进chrome.storage这样切换模型不用改代码。另一个是给请求加超时控制fetch本身没有超时用AbortController包一层避免 Service Worker 被挂起时请求悬空。代码能跑通只是起点真正省时间的是把 Key 和通道统一之后你换模型、换参数都不用动请求逻辑。这个骨架的价值就在这。