一个浏览器内的 AI 编程工作台:文件树、Monaco 编辑器、Sandpack 实时预览与真实 coding agent 共享同一个 TanStack Start 应用。Agent 会在请求级隔离工作区中调用工具,最终提交结构化 ChangeSet;只有用户审核并接受后,变更才会原子写入浏览器工作区。
工作台内置 6 种主题,可从右上角主题菜单切换,也可使用 D / N / S / R / W / B 快捷键。
Summer 主题 fork from https://dany.works/
- TanStack Start(SPA/客户端 IDE 路由与同源 Server Routes)
- Hono(
/api/*API、校验、请求 ID、大小限制与错误边界) - Mastra + AI SDK v6(多步 Agent、工具循环与 UI Message Stream)
- AI Elements + shadcn/ui + Tailwind CSS v4
- Zustand + IndexedDB(浏览器工作区与持久化)
- Monaco + Sandpack(编辑、diff 与实时预览)
要求 Node.js >=22.13.0 与 pnpm。
pnpm install
cp .env.example .env.local
pnpm dev打开 http://localhost:3000。至少配置一个 Provider key 后才能发送消息;未配置的 Provider 仍会出现在选择列表中,但保持禁用状态。
部署配置的 Key 只在服务端读取,不使用 VITE_ 前缀,也不会返回给浏览器;页面级 BYOK 例外见下方“一次性演示密钥”。
| Provider | 环境变量 | 默认模型 |
|---|---|---|
| OpenAI | OPENAI_API_KEY |
openai/gpt-5.4 |
| Qwen / DashScope | DASHSCOPE_API_KEY |
alibaba-cn/qwen3-coder-plus |
| DeepSeek | DEEPSEEK_API_KEY |
deepseek/deepseek-chat |
| Anthropic | ANTHROPIC_API_KEY |
anthropic/claude-sonnet-4-6 |
| Google Gemini | GOOGLE_API_KEY、GOOGLE_GENERATIVE_AI_API_KEY 或 GEMINI_API_KEY |
google/gemini-2.5-pro |
| Moonshot / Kimi | MOONSHOT_API_KEY |
moonshotai/kimi-k2.7-code |
| xAI | XAI_API_KEY |
xai/grok-build-0.1 |
| OpenRouter | OPENROUTER_API_KEY |
openrouter/anthropic/claude-sonnet-4.6 |
每个 Provider 只开放服务端目录中审核过、支持工具调用的模型。浏览器只保存 { providerId, modelId };若已保存的选择失效,会自动回退到第一个已配置 Provider。
如需调整每个 Provider 的默认模型,可使用 .env.example 中的 *_DEFAULT_MODEL 非敏感变量;只有内置 allowlist 中的模型 ID 会生效。
Coding Agent 标题栏的设置按钮允许临时填写 DEEPSEEK_API_KEY 和 TAVILY_API_KEY。这是访问者 BYOK 的演示兜底,不会修改 process.env:
- 值只保存在当前 Chat 页面内存中,并随需要它的
POST /api/chat请求发送。 - 不写入 localStorage、sessionStorage、IndexedDB、Cookie、URL、消息历史或项目导出。
- 刷新、关闭、离开页面或卸载 Chat 面板后不会恢复;“Clear page credentials” 会先停止当前 Agent 请求。
- 临时值存在时优先于同名服务端环境变量;没有临时值时,本地
.env.local和托管平台 Secret 的原路径保持不变。 - 服务端
CHAT_ENABLED总开关优先级更高;关闭时页面 BYOK 也不能保存或发起 Chat。 - 保存不会主动请求 DeepSeek/Tavily 验证 Key,只有实际 Chat/Research 调用才可能产生费用。
浏览器 DevTools Network、同源脚本和浏览器扩展仍可能看到页面输入的 Key,JavaScript 字符串也无法保证物理清零。只在可信 HTTPS 演示页面使用低额度、可撤销的专用 Key,用后从 Provider 控制台轮换或吊销。
Agent 可按需调用两个只读研究工具:
web_search:通过 Tavily basic search 查询公开网页,每次最多返回 5 条经过校验的来源。需在服务端配置TAVILY_API_KEY,不要使用VITE_前缀。weather_search:通过 Open-Meteo 地理编码与天气接口查询当前天气及 1–7 日预报,无需 key。
来源会在工具执行完成时立即出现在消息时间线中;回答末尾再按 URL 去重聚合为最多 10 条的 N sources 列表,可 hover、键盘聚焦或点击查看。Agent 只能引用工具实际返回的 URL;搜索结果一律视为不可信参考内容,不会作为指令执行,也不得在查询中发送密钥、完整文件或私有源码。
当前 Open-Meteo 免费接口仅按非商业、本地或受控环境使用,并在每次天气结果中展示归因。商业或公开生产部署前,请改用其 Customer API 或满足业务许可要求的天气服务,并遵守 Open-Meteo 许可条款。
Browser workspace snapshot
│
▼
POST /api/chat → Hono → Mastra coding agent
├── research only → web_search / weather_search
│ │
│ ▼
│ cited answer
│
└── code change → request-scoped RunWorkspace
│
▼
finalize_changes
│
▼
file/hunk review in browser
│ accept │ reject
▼ ▼
Workspace.apply() discard
核心约束:
- Agent 没有 shell、部署、宿主文件系统或任意网络工具;外部访问仅限服务端审核过的 Tavily 与 Open-Meteo 适配器。
- 现有文件必须先读取,才能写入或删除。
- Agent 只修改 shadow workspace,不能直接写浏览器文件。
- ChangeSet 带工作区 revision 与逐文件 hash;任何冲突都会使整次应用失败,不会部分写入。
- 接受后的事务支持一次安全 Undo;工作区若再次变化则拒绝冲突撤销。
发给 Agent 的快照默认最多 250 个文本文件、单文件 256 KiB、合计 2 MiB。以下内容会在浏览器预检时排除并提示:
.env*、私钥、证书、凭据文件与.sshnode_modules、dist、.output等构建目录- 二进制内容和超限文件
服务端会再次验证规范化 POSIX 路径、内容 hash、模型 allowlist 与请求结构。生产公开部署前仍应增加身份认证、用户级限流与配额;当前版本适合可信本地或受控内网环境。
GET /api/health:运行状态GET /api/providers:公开 Provider/模型描述、configured与hostedChat布尔状态,不包含 keyPOST /api/chat:AI SDK v6 UI message stream
pnpm typecheck
pnpm lint
pnpm test
pnpm build
pnpm check测试覆盖 Workspace 原子事务/冲突/Undo、IndexedDB v1→v2 迁移与写入合并、Provider allowlist 与密钥脱敏、RunWorkspace 安全约束、Research Gateway 边界,以及 Hono → Mastra → 工具循环 → ChangeSet 的完整流式集成。
如需对已配置的真实 Provider 做一次付费、显式启用的端到端冒烟测试,先运行应用,再执行:
SMOKE_PROVIDER_ID=openai SMOKE_MODEL_ID=openai/gpt-5.4 pnpm smoke:provider未提供 SMOKE_PROVIDER_ID 时会使用第一个已配置 Provider;脚本不会读取或输出 key。
pnpm build
pnpm start构建通过 Nitro 生成 Node server 到 .output/server/index.mjs,静态资源位于 .output/public。通过 PORT/HOST 设置监听地址;运行时同样要求 Node.js >=22.13.0。
推荐把演示者自己的 Key 配置为托管平台的加密 Secret,而不是在页面中填写:
- 只在确实需要 Chat 的 Production/Preview 环境中添加
DEEPSEEK_API_KEY、可选的TAVILY_API_KEY,并为每个持有 Key 的环境显式配置自己的CHAT_ENABLED=true;不要添加VITE_前缀。 - 确认变量只对服务端运行时可见,并关闭反向代理/APM 的请求正文与敏感 Header 采集。
- 重新部署或重启 Node runtime;环境变量变更不会自动进入已经运行的实例。
- 为演示站点增加登录或访问密码、用户级限流和额度上限;同源校验不能替代身份认证。
部署完成后按以下顺序验证:
curl -fsS https://YOUR_DEMO_HOST/api/health
curl -fsS https://YOUR_DEMO_HOST/api/providers/api/health应返回ok: true。/api/providers中 DeepSeek 应为configured: true、hostedChat.enabled应为true,配置 Tavily 时hostedChat.tavilyConfigured也应为true;响应中不得出现 Key 或 Key 片段。- 打开 Demo credentials 设置,DeepSeek/Tavily 应显示 “Configured by hosted environment”,输入框中不会出现托管 Key。
- 不打开 Demo credentials,选择 DeepSeek 发一条简单请求;成功表示托管平台
DEEPSEEK_API_KEY路径有效。 - 再询问一个需要当前网页信息的问题;消息时间线应出现
web_search和来源列表,表示托管平台TAVILY_API_KEY路径有效。 - 在浏览器 Network 中确认
/api/chat响应包含Cache-Control: no-store,且应用响应和控制台日志没有 Key。平台日志也不应采集请求 body。
CHAT_ENABLED 是服务端总开关,控制托管 Key 和页面一次性 BYOK 的所有新 Chat 请求,并采用显式开启策略:
- 只有显式配置为精确值
true时才启用 Chat。 - 未配置、
false、空值、大小写或空白不匹配以及其他值都会 fail closed:POST /api/chat在读取请求体和调用 DeepSeek/Tavily 前返回503 CHAT_DISABLED。
该变量按 Vercel environment/deployment 生效,不是跨 Production、Preview 和 Custom Environment 的账户级总开关。为了让下面的 Production 切换成为唯一线上入口,最简单的做法是只在 Production 放 Provider Key;如果 Preview/Custom 也持有 Key,必须在各自环境中单独配置并切换 CHAT_ENABLED。
Vercel Dashboard 快速切换:
- 打开 Project → Settings → Environment Variables,搜索
CHAT_ENABLED。 - 编辑 Production 值为
false(关)或true(开);首次使用则新增该变量并只勾选 Production。 - 打开 Deployments,找到当前 Production deployment,选择 Redeploy。
- 等 Production 域名切换到新 deployment 后执行下方验证。
已经安装并登录 Vercel CLI 时,可以在已 vercel link 的项目目录运行:
# 首次添加;按提示输入 true
vercel env add CHAT_ENABLED production
# 后续切换;按提示输入 false 或 true,--yes 只跳过更新确认
vercel env update CHAT_ENABLED production --yes
# 使用 Deployments 页面中的 immutable deployment URL 或 deployment ID
vercel redeploy <production-deployment-id-or-url>环境变量修改不会进入旧 deployment,必须 Redeploy。每次切换后先验证公开状态:
curl -sS https://YOUR_DEMO_HOST/api/providers
# 关闭时应返回 HTTP 503、CHAT_DISABLED 和 Cache-Control: no-store
curl -i -X POST https://YOUR_DEMO_HOST/api/chat \
-H 'content-type: application/json' \
--data '{}'关闭后,hostedChat.enabled 应为 false,页面应提示 “Disabled by deployment”,输入、建议和一次性 Key 保存均不可用;上述 Chat 请求应为 503,Vercel/上游日志不应出现新的模型或 Tavily 调用。开启后,hostedChat.enabled 应为 true,再从 Production 域名发一条低成本受控 Chat,并按前述步骤验证 DeepSeek 与 Tavily。
旧 deployment URL 会保留创建时的旧环境变量,即使 Production 域名已指向新 deployment 也可能继续可访问。紧急停流时不要只依赖此开关:同时启用 Vercel Deployment Protection,必要时立即在 DeepSeek/Tavily 控制台吊销或轮换 Key。
验证一次性 BYOK 流程时,最好使用没有配置 DeepSeek/Tavily Secret 的独立 Preview 部署:
- 用无痕窗口打开页面,确认 DeepSeek 初始不可发送。
- 打开 Coding Agent 设置,填写低额度 DeepSeek/Tavily 测试 Key;DeepSeek 应立即变为可选,不会先产生验证请求。
- 发起 Chat 和一次网页搜索;只有
/api/chat请求体会包含ephemeralCredentials,响应不得包含它们。 - 点击 “Clear page credentials”,确认当前请求停止,后续
/api/chat不再带ephemeralCredentials。 - 再次填写后刷新页面;绿色临时配置标记应消失。如果部署本身没有 DeepSeek Secret,DeepSeek 会恢复禁用;如果平台已配置 Secret,它仍可用,但走的是服务端环境变量路径。
- 演示结束后在 DeepSeek/Tavily 控制台吊销或轮换测试 Key。
src/
├── routes/ # TanStack Start 文档、客户端 IDE 与 Hono catch-all
├── server/
│ ├── agent/ # Mastra Agent、工具与请求级 RunWorkspace
│ └── providers/ # server-only ProviderCatalog
├── modules/
│ ├── agent-chat/ # Provider 选择与 ChangeSet review
│ ├── workspace/ # snapshot / preview / apply / undo 深模块
│ ├── chat/ # AI SDK + AI Elements 聊天界面
│ ├── fs/ # 浏览器虚拟文件系统与 IndexedDB
│ ├── editor/ # Monaco 会话与 diff
│ └── preview/ # Sandpack 与 console bridge
└── components/
├── ai-elements/ # 按需安装的 AI Elements registry 源码
└── ui/ # shadcn/ui 组件
MIT
