一个本地反向代理,在请求到达第三方 API 供应商之前,自动改写 ZCode 注入的 git 上下文等触发内容审查的 prompt 片段。改写规则完全可配置,SSE 流式透传,支持后台守护运行,自带可视化管理台。v0.3 起还可修复腾讯「思考块碎裂」问题。
npx @honlnk/zcode-prompt-sanitizer
问题
ZCode 直连腾讯 Copilot / WorkBuddy 时,除了众所周知的「敏感内容」拦截,还有一个隐蔽的思考展示问题。两者都与你的输入无关,也都不需要改 ZCode 代码即可修复。
当你在 ZCode 中使用第三方 API 供应商(如腾讯 Copilot / WorkBuddy)时,在 git 项目里发任何消息都会被拦截,返回「检测到敏感内容」。这不是你的内容有问题——是 WAF 的误报。
"you will usually use this for PRs" 做了模式匹配,误判为越狱语句。跨 6 个 git 项目、2 个不同模型全部复现。
part 表 → 被拦请求 tokens = 0,说明网关层直接拒答~/.zcode/cli/rollout/*.jsonl → 用户只输入"你好",实际发出的 prompt 有 8000+ 字符MAIN_BRANCH_LABEL 常量使用腾讯的思考模型(如混元系)时,明明是一段连贯的思考,界面上却碎成几十个独立的「思考」小块,每个只显示持续几秒,刷屏且无法连贯阅读。
tool_calls: [] 和 finish_reason: ""。ZCode 捆绑的旧版 Vercel AI SDK 用 != null 判断字段是否存在——空数组和空字符串都"非 null",于是每个 chunk 都触发一次「思考结束」。新版 AI SDK 已修复此问题,但 ZCode 内置的仍是旧逻辑。
方案
zcode-prompt-sanitizer 运行在 ZCode 和供应商之间,拦截请求、改写触发词、转发到真实上游——模型仍然能看到分支名,WAF 不再触发。
纯字符串匹配(非正则),只改 system 角色内容,不误改用户消息。可配置 scopes 扩展到其他角色。
响应用 pipe 逐 chunk 流式转发,不缓冲完整响应。可选开启响应规整,顺带修复思考块碎裂。
自带 Dashboard 网页:实时匹配统计、在线增删改规则、响应修复开关,改动即时生效并持久化。
YAML 配置文件定义匹配/替换规则,不硬编码。内置默认规则覆盖已知触发词。
npm 全局安装或 Docker 容器部署,配置统一存放在 ~/.zcode-prompt-sanitizer/,支持 --daemon 后台常驻。
默认绑定 127.0.0.1,纯本地工具不暴露到网络。管理台不做鉴权,不过度设计。
针对上面的「问题二」,代理可以在转发 SSE 响应时剥除腾讯注入的空字段(tool_calls: []、finish_reason: ""、空 content 等),让 ZCode 内置的旧版 SDK 不再误判「思考结束」。只删除空字段,其余内容字节级原样透传。默认关闭,按需开启:
# ~/.zcode-prompt-sanitizer/config.yaml
responseFixes:
stripEmptyDeltaFields: true # 修复腾讯思考块碎裂,默认 false
也可以打开管理台(http://127.0.0.1:18790/__zps__),在 Response Fixes 区域一键开关——即时生效,并自动写回配置文件持久化。
规则
规则是纯字符串匹配——在 system 消息内容中搜索 match 子串,替换为 replacement。所有匹配项都会被替换,规则按顺序执行可链式生效。
Default git branch: master 保留了分支信息,只是去掉了触发 WAF 的句式。功能不受影响。
# ~/.zcode-prompt-sanitizer/config.yaml
rules:
- id: redact-internal-token
enabled: true
scopes: [system, user]
match: "INTERNAL-TOKEN-"
replacement: "" # 空字符串 = 删除匹配项
- id: custom-waf-trigger
enabled: true
scopes: [system]
match: "某些触发WAF的句子"
replacement: "安全的表述"
| 字段 | 必填 | 说明 |
|---|---|---|
id | 是 | 唯一标识,用于统计/日志 |
match | 是 | 要匹配的字面量子串(非正则) |
replacement | 否 | 替换文本,空字符串表示删除 |
scopes | 否 | 作用角色,默认 ["system"] |
enabled | 否 | 是否启用,默认 true |
使用
需要 Node.js ≥ 18。无需克隆仓库,开箱即用。
# 推荐:后台守护模式,启动后终端立即可用
npx @honlnk/zcode-prompt-sanitizer@latest --daemon
# 停止后台代理
npx @honlnk/zcode-prompt-sanitizer@latest --stop
# 或全局安装后使用(zps / zps --daemon / zps --stop)
npm install -g @honlnk/zcode-prompt-sanitizer
zps --daemon
# docker compose
docker compose up -d
# 或纯 docker
docker run -d --name zps \
-p 18790:18790 \
-v "$PWD/config.yaml:/data/config.yaml" \
honlnk/zcode-prompt-sanitizer
# ~/.zcode-prompt-sanitizer/config.yaml
port: 18790
host: 127.0.0.1
upstreams:
# 把请求转发到腾讯 Copilot(唯一的 upstream 会自动路由,无需额外 header)
workbuddy:
target: https://copilot.tencent.com/v2
changeHost: true
# 可选:修复腾讯「思考块碎裂」(默认关,也可在管理台一键开关)
responseFixes:
stripEmptyDeltaFields: true
在 ZCode 设置里,把供应商的 Base URL 从真实地址改成代理地址:
https://copilot.tencent.com/v2http://127.0.0.1:18790/v2http://127.0.0.1:18790/__zps__ 可以打开管理台查看实时匹配统计、切换响应修复开关。
日常使用推荐守护模式:打印启动 banner 后终端立即可用,关掉终端代理仍在运行。
zps --daemon # 后台启动
zps --stop # 优雅停止(通过 pidfile 发送 SIGTERM)
状态文件存放在 ~/.zcode-prompt-sanitizer/:zps.pid(进程号)、zps.log(运行日志,超过 5MB 自动轮转为 zps.log.1,只保留两代,不会无限占磁盘)。
| 参数 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
--config <path> | ZPS_CONFIG | — | 配置文件路径 |
--port <n> | ZPS_PORT | 18790 | 监听端口 |
--host <addr> | ZPS_HOST | 127.0.0.1 | 绑定地址 |
-d, --daemon | ZPS_DAEMON=1 | 关 | 后台守护模式运行 |
--stop | — | — | 停止后台守护进程 |
--verbose | ZPS_VERBOSE=1 | 关 | 打印每个请求的日志 |
--no-dashboard | ZPS_DASHBOARD=0 | 开 | 关闭管理台 |
-v, --version | — | — | 打印版本号 |
-h, --help | — | — | 显示帮助 |
常见问题
关于「系统检测到您当前输入的信息存在敏感内容」的 WAF 误拦截、以及思考块碎裂的高频疑问。
这是腾讯 WAF 的误报,不是你输入的内容有问题。根因是 ZCode 在 git 项目里发消息时,会自动把 git 上下文拼进 system prompt,其中 "Main branch (you will usually use this for PRs)" 这句被 WAF 模式匹配成 prompt 注入 / 越狱语句,整个请求在网关层就被拦下,模型根本没处理(被拦请求的 tokens = 0)。
不是。即使用户只输入「你好」,ZCode 实际发出的 prompt 也有 8000+ 字符(大部分是自动注入的 git 上下文)。拦截发生在注入的上下文上,与你输入的内容无关——所以在 git 项目里发任何消息都会被拦截。
不会。它把触发 WAF 的句子(如 Main branch (you will usually use this for PRs):)改写成中性表述(Default git branch:)。分支名仍然保留,模型照样能看到,只是去掉了触发 WAF 的句式,功能不受影响。
这不是模型的问题,也不是你的设置问题。腾讯的 SSE 响应在每个 chunk 里都携带空的 tool_calls: [] 和 finish_reason: "";ZCode 内置的旧版 AI SDK 用 != null 判断字段是否存在,空数组和空字符串都"非 null",于是每个 chunk 都触发一次「思考结束」,思考被切成一个个 token 大小的碎片。开启本工具的响应修复开关即可:管理台 Response Fixes 一键打开,或在配置里设 responseFixes.stripEmptyDeltaFields: true,代理转发时剥除这些空字段,思考恢复为完整的一块。
用守护模式启动:zps --daemon(或 npx @honlnk/zcode-prompt-sanitizer@latest --daemon),打印启动信息后终端立即可用,关闭终端代理也继续运行;zps --stop 优雅停止。日志写入 ~/.zcode-prompt-sanitizer/zps.log,超过 5MB 自动轮转,只保留两代。
不。它是通用代理,规则完全可配置。内置默认规则覆盖已知的腾讯 WAF 触发词;你可以自定义规则处理任意供应商的内容审查拦截。只要供应商兼容 OpenAI Chat Completions 接口就能用。
不需要改任何代码。只需把 ZCode 里供应商的 Base URL 从真实地址改成本地代理地址(http://127.0.0.1:18790),API Key 不变。代理只做字符串改写和转发,SSE 流式透传,对客户端完全透明。
完全免费、开源(MIT 协议)。本地运行,请求不经过任何第三方服务器,配置和密钥都留在你自己的机器上。