本地代理 · 开源 · 零侵入 · v0.3.1

让 ZCode 不再被 WAF 误拦

一个本地反向代理,在请求到达第三方 API 供应商之前,自动改写 ZCode 注入的 git 上下文等触发内容审查的 prompt 片段。改写规则完全可配置,SSE 流式透传,支持后台守护运行,自带可视化管理台。v0.3 起还可修复腾讯「思考块碎裂」问题。

$ npx @honlnk/zcode-prompt-sanitizer
快速开始 了解问题 查看源码

问题

接入腾讯 API 的两个已知问题

ZCode 直连腾讯 Copilot / WorkBuddy 时,除了众所周知的「敏感内容」拦截,还有一个隐蔽的思考展示问题。两者都与你的输入无关,也都不需要改 ZCode 代码即可修复。

问题一:WAF 误拦截 —— 发任何消息都报「敏感内容」

当你在 ZCode 中使用第三方 API 供应商(如腾讯 Copilot / WorkBuddy)时,在 git 项目里发任何消息都会被拦截,返回「检测到敏感内容」。这不是你的内容有问题——是 WAF 的误报。

ZCode 在 git 项目中自动注入 system prompt:
│ "Main branch (you will usually use this for PRs): master"
│
ZCode ──► 第三方 WAF ──✕── 模型
│      "you will usually use this for PRs" 被误判为 prompt 注入
│      请求被直接拦截,模型根本没处理(tokens = 0)
根因:ZCode 每次请求前在内存里把 git 上下文拼进 system prompt。腾讯 WAF 对 "you will usually use this for PRs" 做了模式匹配,误判为越狱语句。跨 6 个 git 项目、2 个不同模型全部复现。

排查路径

问题二:思考块碎裂 —— 一次回答刷出几十个「思考 · 持续了几秒」

使用腾讯的思考模型(如混元系)时,明明是一段连贯的思考,界面上却碎成几十个独立的「思考」小块,每个只显示持续几秒,刷屏且无法连贯阅读。

腾讯 SSE:每一个 chunk 的 delta 里都塞着 "tool_calls": [] 和 "finish_reason": ""
│
ZCode 内置的旧版 AI SDK:if (delta.tool_calls != null) → 空数组也成立 → 关闭当前思考块
│
下一个 chunk 的 reasoning_content 到来 → 又新开一个思考块
│
结果:1 段思考 = N 个碎片(实测一次回答产生 33 个 token 级碎片)
根因:腾讯在每个 SSE chunk 里带上空的 tool_calls: [] 和 finish_reason: ""。ZCode 捆绑的旧版 Vercel AI SDK 用 != null 判断字段是否存在——空数组和空字符串都"非 null",于是每个 chunk 都触发一次「思考结束」。新版 AI SDK 已修复此问题,但 ZCode 内置的仍是旧逻辑。

方案

本地代理 · 自动改写 · 流式透传

zcode-prompt-sanitizer 运行在 ZCode 和供应商之间,拦截请求、改写触发词、转发到真实上游——模型仍然能看到分支名,WAF 不再触发。

ZCode ──► [sanitizer :18790] ──► 供应商 (copilot.tencent.com)
                       │
                       ├─ 解析 JSON 请求体
                       ├─ 对 system 消息做精确字符串替换
                       ├─ 重建请求体并转发
                       └─ SSE 响应逐 chunk 透传,不缓冲
                          └─ 可选:剥除空 tool_calls / 空 finish_reason(修复思考块碎裂)

核心特性

🛡️

精确改写

纯字符串匹配(非正则),只改 system 角色内容,不误改用户消息。可配置 scopes 扩展到其他角色。

⚡

SSE 流式透传

响应用 pipe 逐 chunk 流式转发,不缓冲完整响应。可选开启响应规整,顺带修复思考块碎裂。

🎛️

可视化管理台

自带 Dashboard 网页:实时匹配统计、在线增删改规则、响应修复开关,改动即时生效并持久化。

⚙️

规则可配置

YAML 配置文件定义匹配/替换规则,不硬编码。内置默认规则覆盖已知触发词。

🐳

双部署方式

npm 全局安装或 Docker 容器部署,配置统一存放在 ~/.zcode-prompt-sanitizer/,支持 --daemon 后台常驻。

🔒

只绑本地

默认绑定 127.0.0.1,纯本地工具不暴露到网络。管理台不做鉴权,不过度设计。

可选修复:SSE 响应规整(Response Fixes)

针对上面的「问题二」,代理可以在转发 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。所有匹配项都会被替换,规则按顺序执行可链式生效。

内置默认规则

触发 Main branch (you will usually use this for PRs): → 安全 Default git branch:
触发 main branch (you will usually use this for PRs): → 安全 Default git branch:
模型仍能看到分支名:改写后 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。无需克隆仓库,开箱即用。

1. 安装并启动代理

# 推荐:后台守护模式,启动后终端立即可用
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

2. 创建配置文件

# ~/.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

3. 修改 ZCode 的 provider 地址

在 ZCode 设置里,把供应商的 Base URL 从真实地址改成代理地址:

改之前:https://copilot.tencent.com/v2
改之后:http://127.0.0.1:18790/v2
API Key 不变。
完成!现在 ZCode 的请求会先经过代理改写触发词,再转发到腾讯。在 http://127.0.0.1:18790/__zps__ 可以打开管理台查看实时匹配统计、切换响应修复开关。

后台常驻(daemon 模式)

日常使用推荐守护模式:打印启动 banner 后终端立即可用,关掉终端代理仍在运行。

zps --daemon   # 后台启动
zps --stop     # 优雅停止(通过 pidfile 发送 SIGTERM)

状态文件存放在 ~/.zcode-prompt-sanitizer/:zps.pid(进程号)、zps.log(运行日志,超过 5MB 自动轮转为 zps.log.1,只保留两代,不会无限占磁盘)。

CLI 参数

参数环境变量默认值说明
--config <path>ZPS_CONFIG—配置文件路径
--port <n>ZPS_PORT18790监听端口
--host <addr>ZPS_HOST127.0.0.1绑定地址
-d, --daemonZPS_DAEMON=1关后台守护模式运行
--stop——停止后台守护进程
--verboseZPS_VERBOSE=1关打印每个请求的日志
--no-dashboardZPS_DASHBOARD=0开关闭管理台
-v, --version——打印版本号
-h, --help——显示帮助

常见问题

ZCode 接入腾讯 API · 常见问题

关于「系统检测到您当前输入的信息存在敏感内容」的 WAF 误拦截、以及思考块碎裂的高频疑问。

为什么 ZCode 连接 WorkBuddy 后提示「系统检测到您当前输入的信息存在敏感内容」?

这是腾讯 WAF 的误报,不是你输入的内容有问题。根因是 ZCode 在 git 项目里发消息时,会自动把 git 上下文拼进 system prompt,其中 "Main branch (you will usually use this for PRs)" 这句被 WAF 模式匹配成 prompt 注入 / 越狱语句,整个请求在网关层就被拦下,模型根本没处理(被拦请求的 tokens = 0)。

提示「敏感内容」是我自己输入的问题吗?

不是。即使用户只输入「你好」,ZCode 实际发出的 prompt 也有 8000+ 字符(大部分是自动注入的 git 上下文)。拦截发生在注入的上下文上,与你输入的内容无关——所以在 git 项目里发任何消息都会被拦截。

zcode-prompt-sanitizer 会影响模型理解吗?

不会。它把触发 WAF 的句子(如 Main branch (you will usually use this for PRs):)改写成中性表述(Default git branch:)。分支名仍然保留,模型照样能看到,只是去掉了触发 WAF 的句式,功能不受影响。

ZCode 用腾讯模型时,思考过程碎成几十个「思考 · 持续了几秒」小块,怎么修复?

这不是模型的问题,也不是你的设置问题。腾讯的 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 自动轮转,只保留两代。

只支持腾讯 Copilot / WorkBuddy 吗?

不。它是通用代理,规则完全可配置。内置默认规则覆盖已知的腾讯 WAF 触发词;你可以自定义规则处理任意供应商的内容审查拦截。只要供应商兼容 OpenAI Chat Completions 接口就能用。

需要改 ZCode 的代码吗?会被检测到吗?

不需要改任何代码。只需把 ZCode 里供应商的 Base URL 从真实地址改成本地代理地址(http://127.0.0.1:18790),API Key 不变。代理只做字符串改写和转发,SSE 流式透传,对客户端完全透明。

是免费的吗?开源吗?

完全免费、开源(MIT 协议)。本地运行,请求不经过任何第三方服务器,配置和密钥都留在你自己的机器上。