# ADR-0019 浏览器令牌、CORS 与 SDK 包分发 - 状态:Accepted · 日期:2026-10-23 · 补充 ADR-0015 ## 背景 业务平台(Vue + TS + UnoCSS)需要在浏览器里直接对话:流式事件、审批、上传文件。原设计在前端直接使用 API Key,这样任何能打开平台页面的人都能从开发者工具中拿到 Key 并长期使用。另外,`@agent-server/client` / `chat-ui` 只是仓库内的 TS 源码,平台项目无法直接安装。 ## 决策 - **浏览器令牌**:平台后端用 API Key 调 `POST /v1/browser-tokens` 换取短期令牌 `bt_…`,交给浏览器使用。 - 默认 1 小时,最长 12 小时 - 令牌无状态:HMAC-SHA256 签名,载荷含签发它的 Key id、过期时间和随机数;密钥随机生成,存在数据库 settings 中,服务重启后仍然有效 - 每次使用都检查 Key 仍然启用,停用或删除 Key 即作废它签发的全部令牌 - 浏览器令牌不能再签发令牌;会话、用量都归属于签发它的 Key - 平台后端应先校验自己的登录态,只为已登录用户签发 - **客户端**: - `AgentClient` 新增 `getToken` - `browserTokenProvider` 负责缓存令牌、过期前 60 秒换新、合并并发请求;遇到 401 时刷新令牌并重试一次 - 请求使用 `credentials: 'same-origin'`:后台同源时继续使用 Cookie,跨域时不带 Cookie - **CORS**: - 只对 `/v1` 开放给后台"设置 › 安全 › CORS 来源"中逐个列出的地址(`https://host[:port]`) - 不返回 `Access-Control-Allow-Credentials` - `/admin/v1`、`/mcp` 不开放 - SSE 响应在接管连接后同样带上 CORS 头 - **SDK 分发**:`scripts/build-sdk.mjs` 构建三个包,部署时发布到服务的 `/sdk/`(公开,不含密钥),平台用 URL 安装。 - 三个包:`@agent-server/shared`(类型与常量)、`@agent-server/client`、`@agent-server/chat-ui`(ESM + 类型声明 + 编译好的 `style.css`) - 包之间是对等依赖(peerDependencies),平台同时安装三个包,pnpm 按包名关联,不访问 npm 仓库 - 文件名带版本号,内容不可变(长期缓存);旧版本保留,平台可以锁定版本 - **样式隔离**:chat-ui 使用 UnoCSS `vue-scoped` 模式,生成的原子类写入各组件的 scoped 样式,不与平台自己的 UnoCSS / Tailwind 类冲突。 - `style.css` 只包含 `--app-*` 设计变量,以及作用于 `.agent-chat` 内部的基础样式,不改动宿主页面的 `html` / `body` - 平台可以覆盖 `--app-*` 变量调整配色;`` 可以指定亮/暗 ## 后果 - API Key 只存在于平台后端,泄露面缩小到短期令牌 - 没有后端的纯前端平台需要加一个最小的令牌接口(examples/platform-demo/server.mjs 约 30 行) - 升级 SDK:修改 packages/*/package.json 的版本号后部署,平台改安装 URL 中的版本号