diff --git a/CHANGELOG.md b/CHANGELOG.md index 22bcdfb..2316571 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## v2.7.5 (2026-03-19) + +### 🏗️ 常量集中管理 + +- **新增 `constants.ts`**:将 `REFUSAL_PATTERNS`(50+ 条拒绝检测规则)、`IDENTITY_PROBE_PATTERNS`、`TOOL_CAPABILITY_PATTERNS`、`CLAUDE_IDENTITY_RESPONSE`、`CLAUDE_TOOLS_RESPONSE` 及自定义拒绝规则逻辑从 `handler.ts` 提取到独立文件 +- **提升可维护性**:贡献者修改内置规则时只需查看 `constants.ts`,无需翻阅 2000 行的 handler 业务逻辑 +- **`isRefusal()` 函数统一导出**:内置规则 + 自定义规则合并检测,所有调用点自动生效 + +### 🔧 自定义拒绝检测规则 + +- **`config.yaml` 新增 `refusal_patterns` 字段**:用户可添加自定义正则匹配规则,追加到内置列表之后(不替换),匹配到即触发重试逻辑 +- **无效正则容错**:无效的正则表达式自动退化为字面量匹配,不会导致服务报错 +- **缓存编译**:自定义规则只在配置变更时重新编译 RegExp,运行时零开销 +- **热重载支持**:修改后下一次请求即生效 + +### 🔀 响应内容清洗开关 + +- **`config.yaml` 新增 `sanitize_response` 字段**:控制 `sanitizeResponse()` 函数(将 Cursor 身份引用替换为 Claude),**默认关闭** +- **环境变量支持**:`SANITIZE_RESPONSE=true` 可覆盖配置文件 +- **零开销设计**:关闭时函数入口直接返回原文本,无正则计算 +- **热重载支持**:修改配置后立即生效 + +--- + ## v2.7.4 (2026-03-18) ### 🛡️ 截断安全 — 防止损坏的工具调用 diff --git a/README.md b/README.md index d5fbb69..70afe13 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Cursor2API v2.7.4 +# Cursor2API v2.7.5 将 Cursor 文档页免费 AI 对话接口代理转换为 **Anthropic Messages API** 和 **OpenAI Chat Completions API**,支持 **Claude Code** 和 **Cursor IDE** 使用。 -> ⚠️ **版本说明**:当前 v2.7.4 截断安全(防止损坏工具调用)、默认禁用代理续写(让客户端原生续写)、日志查看器提示词对比视图。 +> ⚠️ **版本说明**:当前 v2.7.5 新增常量集中管理、自定义拒绝规则、响应清洗开关,代码可维护性大幅提升。 ## 原理 @@ -36,8 +36,8 @@ - **工具参数自动修复** - 字段名映射 (`file_path` → `path`)、智能引号替换、模糊匹配修复 - **多模态视觉降级处理** - 内置纯本地 CPU OCR 图片文字提取(零配置免 Key),或支持外接第三方免费视觉大模型 API 解释图片 - **全工具支持** - 无工具白名单限制,支持所有 MCP 工具和自定义扩展 -- **多层拒绝拦截** - 50+ 正则模式匹配拒绝文本(中英文),自动重试 + 认知重构绕过 -- **三层身份保护** - 身份探针拦截 + 拒绝重试 + 响应清洗,确保输出永远呈现 Claude 身份 +- **多层拒绝拦截** - 50+ 正则模式匹配拒绝文本(中英文),自动重试 + 认知重构绕过,支持自定义规则 +- **三层身份保护** - 身份探针拦截 + 拒绝重试 + 响应清洗(可配置开关),确保输出永远呈现 Claude 身份 - **截断无缝续写** - Proxy 底层自动拼接被截断的工具响应(最多 6 次),含智能去重 - **渐进式历史压缩** - 智能识别消息类型,工具调用摘要化、工具结果头尾保留,不破坏 JSON 结构 - **🆕 可配置压缩系统** - 支持开关 + 3档级别(轻度/中等/激进)+ 自定义参数,环境变量可覆盖 @@ -81,6 +81,8 @@ cp config.yaml.example config.yaml | `logging.dir` | 日志存储目录 | `./logs` | | `logging.max_days` | 日志保留天数 | `7` | | `max_auto_continue` | 截断自动续写次数 (`0`=禁用,交由客户端续写) | `0` | +| `sanitize_response` | 响应内容清洗开关(替换 Cursor 身份引用为 Claude) | `false` | +| `refusal_patterns` | 自定义拒绝检测规则列表(追加到内置规则) | 不配置 | > 💡 详细配置说明请参见 `config.yaml.example` 中的注释。 @@ -150,6 +152,7 @@ cursor2api/ │ ├── index.ts # 入口 + Express 服务 + 路由 + API 鉴权中间件 │ ├── config.ts # 配置管理(含 auth_tokens / vision.proxy) │ ├── types.ts # 类型定义(含 thinking / authTokens) +│ ├── constants.ts # 全局常量(拒绝模式、身份探针、回复模板) │ ├── cursor-client.ts # Cursor API 客户端 + Chrome TLS 指纹 │ ├── converter.ts # 协议转换 + 提示词注入 + 上下文清洗 + 动态预算 │ ├── handler.ts # Anthropic API 处理器 + 身份保护 + 拒绝拦截 + Thinking @@ -243,6 +246,7 @@ AI 按此格式输出 → 我们解析并转换为标准的 Anthropic `tool_use` | `LOG_FILE_ENABLED` | 日志文件持久化 (`true`/`false`) | | `LOG_DIR` | 日志文件目录 | | `MAX_AUTO_CONTINUE` | 截断自动续写次数 (`0`=禁用) | +| `SANITIZE_RESPONSE` | 响应内容清洗开关 (`true`/`false`,默认 `false`) | ## 免责声明 / Disclaimer diff --git a/config.yaml.example b/config.yaml.example index 70bb6ef..6f587d6 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -106,6 +106,26 @@ tools: # exclude: # - "some_mcp_tool" +# ==================== 响应内容清洗(可选,默认关闭) ==================== +# 开启后,会将响应中 Cursor 相关的身份引用替换为 Claude +# 例如 "I am Cursor's support assistant" → "I am Claude, an AI assistant by Anthropic" +# 同时清洗工具可用性声明、提示注入指控等敏感内容 +# 💡 如果你不需要伪装身份,建议保持关闭以获得最佳性能 +# 💡 开启后,所有响应都会经过正则替换处理,有轻微性能开销 +# sanitize_response: true + +# ==================== 自定义拒绝检测规则(可选) ==================== +# 追加到内置拒绝检测列表之后(不替换内置规则),匹配到则触发重试逻辑 +# 每条规则作为正则表达式解析(不区分大小写),无效正则会自动退化为字面量匹配 +# 💡 适用场景:特定语言的拒绝措辞、项目特有的拒绝响应、新的 Cursor 拒绝模式 +# 支持热重载:修改后下一次请求即生效 +# refusal_patterns: +# - "我无法协助" +# - "this violates our" +# - "I must decline" +# - "无法为您提供" +# - "this request is outside" + # 浏览器指纹配置 fingerprint: user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36" diff --git a/docker-compose.yml b/docker-compose.yml index 0aeec92..b6d1da8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,3 +50,10 @@ services: # ── Vision 图片处理 ── # 默认使用本地 OCR(零配置),如需外部 Vision API 请在 config.yaml 中修改 vision.mode 为 'api' # 并配置 vision.base_url / vision.api_key / vision.model + + # ── 响应内容清洗 ── + # 开启后会将响应中 Cursor 身份引用替换为 Claude(默认关闭) + # - SANITIZE_RESPONSE=true + + # ── 自定义拒绝检测规则 ── + # 仅支持 config.yaml 配置(无环境变量覆盖),详见 config.yaml.example 中的 refusal_patterns 节 diff --git a/package.json b/package.json index 0eed37b..37a72bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cursor2api", - "version": "2.7.4", + "version": "2.7.5", "description": "Proxy Cursor docs AI to Anthropic Messages API for Claude Code", "type": "module", "scripts": { diff --git a/src/config.ts b/src/config.ts index 2adeca5..2797ee5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -91,6 +91,14 @@ function parseYamlConfig(defaults: AppConfig): { config: AppConfig; raw: Record< exclude: Array.isArray(t.exclude) ? t.exclude.map(String) : undefined, }; } + // ★ 响应内容清洗开关(默认关闭) + if (yaml.sanitize_response !== undefined) { + result.sanitizeEnabled = yaml.sanitize_response === true; + } + // ★ 自定义拒绝检测规则 + if (Array.isArray(yaml.refusal_patterns)) { + result.refusalPatterns = yaml.refusal_patterns.map(String).filter(Boolean); + } } catch (e) { console.warn('[Config] 读取 config.yaml 失败:', e); } @@ -136,6 +144,10 @@ function applyEnvOverrides(cfg: AppConfig): void { if (!cfg.logging) cfg.logging = { file_enabled: false, dir: './logs', max_days: 7 }; cfg.logging.dir = process.env.LOG_DIR; } + // 响应内容清洗环境变量覆盖 + if (process.env.SANITIZE_RESPONSE !== undefined) { + cfg.sanitizeEnabled = process.env.SANITIZE_RESPONSE === 'true' || process.env.SANITIZE_RESPONSE === '1'; + } // 从 base64 FP 环境变量解析指纹 if (process.env.FP) { @@ -158,6 +170,7 @@ function defaultConfig(): AppConfig { cursorModel: 'anthropic/claude-sonnet-4.6', maxAutoContinue: 0, maxHistoryMessages: -1, + sanitizeEnabled: false, // 默认关闭响应内容清洗 fingerprint: { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36', }, @@ -197,6 +210,12 @@ function detectChanges(oldCfg: AppConfig, newCfg: AppConfig): string[] { // tools if (JSON.stringify(oldCfg.tools) !== JSON.stringify(newCfg.tools)) changes.push('tools: (changed)'); + // refusalPatterns + // sanitize_response + if (oldCfg.sanitizeEnabled !== newCfg.sanitizeEnabled) changes.push(`sanitize_response: ${oldCfg.sanitizeEnabled} → ${newCfg.sanitizeEnabled}`); + + if (JSON.stringify(oldCfg.refusalPatterns) !== JSON.stringify(newCfg.refusalPatterns)) changes.push(`refusal_patterns: ${oldCfg.refusalPatterns?.length || 0} → ${newCfg.refusalPatterns?.length || 0} rule(s)`); + // fingerprint if (oldCfg.fingerprint.userAgent !== newCfg.fingerprint.userAgent) changes.push('fingerprint: (changed)'); diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..9273942 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,231 @@ +/** + * constants.ts - 全局常量定义 + * + * 集中管理拒绝检测规则、身份探针模式、固定回复模板等常量。 + * 方便查阅和修改内置规则,无需翻阅 handler.ts 的业务逻辑。 + */ + +import { getConfig } from './config.js'; + +// ==================== 拒绝模式识别 ==================== +// 模型返回以下任意模式匹配的内容时,判定为拒绝响应并触发重试 +// 如需添加新规则,请追加到对应分类末尾,或通过 config.yaml 的 refusal_patterns 配置 + +export const REFUSAL_PATTERNS: RegExp[] = [ + // ── English: 身份拒绝 ── + /Cursor(?:'s)?\s+support\s+assistant/i, + /support\s+assistant\s+for\s+Cursor/i, + /I[''']\s*m\s+sorry/i, + /I\s+am\s+sorry/i, + /not\s+able\s+to\s+fulfill/i, + /cannot\s+perform/i, + /I\s+can\s+only\s+answer/i, + /I\s+only\s+answer/i, + /cannot\s+write\s+files/i, + /pricing[, \s]*or\s*troubleshooting/i, + /I\s+cannot\s+help\s+with/i, + /I'm\s+a\s+coding\s+assistant/i, + /not\s+able\s+to\s+search/i, + /not\s+in\s+my\s+core/i, + /outside\s+my\s+capabilities/i, + /I\s+cannot\s+search/i, + /focused\s+on\s+software\s+development/i, + /not\s+able\s+to\s+help\s+with\s+(?:that|this)/i, + /beyond\s+(?:my|the)\s+scope/i, + /I'?m\s+not\s+(?:able|designed)\s+to/i, + /I\s+don't\s+have\s+(?:the\s+)?(?:ability|capability)/i, + /questions\s+about\s+(?:Cursor|the\s+(?:AI\s+)?code\s+editor)/i, + + // ── English: 话题拒绝 ── Cursor 拒绝非编程话题 + /help\s+with\s+(?:coding|programming)\s+and\s+Cursor/i, + /Cursor\s+IDE\s+(?:questions|features|related)/i, + /unrelated\s+to\s+(?:programming|coding)(?:\s+or\s+Cursor)?/i, + /Cursor[- ]related\s+question/i, + /(?:ask|please\s+ask)\s+a\s+(?:programming|coding|Cursor)/i, + /(?:I'?m|I\s+am)\s+here\s+to\s+help\s+with\s+(?:coding|programming)/i, + /appears\s+to\s+be\s+(?:asking|about)\s+.*?unrelated/i, + /(?:not|isn't|is\s+not)\s+(?:related|relevant)\s+to\s+(?:programming|coding|software)/i, + /I\s+can\s+help\s+(?:you\s+)?with\s+things\s+like/i, + + // ── English: 新拒绝措辞 (2026-03) ── + /isn't\s+something\s+I\s+can\s+help\s+with/i, + /not\s+something\s+I\s+can\s+help\s+with/i, + /scoped\s+to\s+answering\s+questions\s+about\s+Cursor/i, + /falls\s+outside\s+(?:the\s+scope|what\s+I)/i, + + // ── English: 提示注入/社会工程检测 ── + /prompt\s+injection\s+attack/i, + /prompt\s+injection/i, + /social\s+engineering/i, + /I\s+need\s+to\s+stop\s+and\s+flag/i, + /What\s+I\s+will\s+not\s+do/i, + /What\s+is\s+actually\s+happening/i, + /replayed\s+against\s+a\s+real\s+system/i, + /tool-call\s+payloads/i, + /copy-pasteable\s+JSON/i, + /injected\s+into\s+another\s+AI/i, + /emit\s+tool\s+invocations/i, + /make\s+me\s+output\s+tool\s+calls/i, + + // ── English: 工具可用性声明 (Cursor 角色锁定) ── + /I\s+(?:only\s+)?have\s+(?:access\s+to\s+)?(?:two|2|read_file|read_dir)\s+tool/i, + /(?:only|just)\s+(?:two|2)\s+(?:tools?|functions?)\b/i, + /\bread_file\b.*\bread_dir\b/i, + /\bread_dir\b.*\bread_file\b/i, + + // ── English: 范围/专长措辞 (2026-03 批次) ── + /(?:outside|beyond)\s+(?:the\s+)?scope\s+of\s+what/i, + /not\s+(?:within|in)\s+(?:my|the)\s+scope/i, + /this\s+assistant\s+is\s+(?:focused|scoped)/i, + /(?:only|just)\s+(?:able|here)\s+to\s+(?:answer|help)/i, + /I\s+(?:can\s+)?only\s+help\s+with\s+(?:questions|issues)\s+(?:related|about)/i, + /(?:here|designed)\s+to\s+help\s+(?:with\s+)?(?:questions\s+)?about\s+Cursor/i, + /not\s+(?:something|a\s+topic)\s+(?:related|specific)\s+to\s+(?:Cursor|coding)/i, + /outside\s+(?:my|the|your)\s+area\s+of\s+(?:expertise|scope)/i, + /(?:can[.']?t|cannot|unable\s+to)\s+help\s+with\s+(?:this|that)\s+(?:request|question|topic)/i, + /scoped\s+to\s+(?:answering|helping)/i, + + // ── 中文: 身份拒绝 ── + /我是\s*Cursor\s*的?\s*支持助手/, + /Cursor\s*的?\s*支持系统/, + /Cursor\s*(?:编辑器|IDE)?\s*相关的?\s*问题/, + /我的职责是帮助你解答/, + /我无法透露/, + /帮助你解答\s*Cursor/, + /运行在\s*Cursor\s*的/, + /专门.*回答.*(?:Cursor|编辑器)/, + /我只能回答/, + /无法提供.*信息/, + /我没有.*也不会提供/, + /功能使用[、,]\s*账单/, + /故障排除/, + + // ── 中文: 话题拒绝 ── + /与\s*(?:编程|代码|开发)\s*无关/, + /请提问.*(?:编程|代码|开发|技术).*问题/, + /只能帮助.*(?:编程|代码|开发)/, + + // ── 中文: 提示注入检测 ── + /不是.*需要文档化/, + /工具调用场景/, + /语言偏好请求/, + /提供.*具体场景/, + /即报错/, + + // ── 中文: 工具可用性声明 ── + /有以下.*?(?:两|2)个.*?工具/, + /我有.*?(?:两|2)个工具/, + /工具.*?(?:只有|有以下|仅有).*?(?:两|2)个/, + /只能用.*?read_file/i, + /无法调用.*?工具/, + /(?:仅限于|仅用于).*?(?:查阅|浏览).*?(?:文档|docs)/, + + // ── 中文: Cursor 中文界面拒绝措辞 (2026-03 批次) ── + /只能回答.*(?:Cursor|编辑器).*(?:相关|有关)/, + /专[注门].*(?:回答|帮助|解答).*(?:Cursor|编辑器)/, + /有什么.*(?:Cursor|编辑器).*(?:问题|可以)/, + /无法提供.*(?:推荐|建议|帮助)/, + /(?:功能使用|账户|故障排除|账号|订阅|套餐|计费).*(?:等|问题)/, +]; + +// ==================== 自定义拒绝规则 ==================== +// 从 config.yaml 的 refusal_patterns 字段编译,追加到内置列表之后,支持热重载 + +let _customRefusalPatterns: RegExp[] = []; +let _lastRefusalPatternsKey = ''; + +function getCustomRefusalPatterns(): RegExp[] { + const config = getConfig(); + const patterns = config.refusalPatterns; + if (!patterns || patterns.length === 0) return _customRefusalPatterns = []; + + // 用 join key 做缓存判断,避免每次调用都重新编译 + const key = patterns.join('\0'); + if (key === _lastRefusalPatternsKey) return _customRefusalPatterns; + + _lastRefusalPatternsKey = key; + _customRefusalPatterns = []; + for (const p of patterns) { + try { + _customRefusalPatterns.push(new RegExp(p, 'i')); + } catch { + // 无效正则 → 退化为字面量匹配 + _customRefusalPatterns.push(new RegExp(p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')); + console.warn(`[Config] refusal_patterns: "${p}" 不是有效正则,已转换为字面量匹配`); + } + } + console.log(`[Config] 加载了 ${_customRefusalPatterns.length} 条自定义拒绝规则`); + return _customRefusalPatterns; +} + +/** + * 检查文本是否匹配拒绝模式(内置 + 自定义规则) + */ +export function isRefusal(text: string): boolean { + if (REFUSAL_PATTERNS.some(p => p.test(text))) return true; + const custom = getCustomRefusalPatterns(); + return custom.length > 0 && custom.some(p => p.test(text)); +} + +// ==================== 身份探针检测 ==================== +// 用户消息匹配以下模式时判定为身份探针,直接返回 mock 回复 + +export const IDENTITY_PROBE_PATTERNS: RegExp[] = [ + // 精确短句 + /^\s*(who are you\??|你是谁[呀啊吗]?\??|what is your name\??|你叫什么\??|你叫什么名字\??|what are you\??|你是什么\??|Introduce yourself\??|自我介绍一下\??|hi\??|hello\??|hey\??|你好\??|在吗\??|哈喽\??)\s*$/i, + // 问模型/身份类 + /(?:什么|哪个|啥)\s*模型/, + /(?:真实|底层|实际|真正).{0,10}(?:模型|身份|名字)/, + /模型\s*(?:id|名|名称|名字|是什么)/i, + /(?:what|which)\s+model/i, + /(?:real|actual|true|underlying)\s+(?:model|identity|name)/i, + /your\s+(?:model|identity|real\s+name)/i, + // 问平台/运行环境类 + /运行在\s*(?:哪|那|什么)/, + /(?:哪个|什么)\s*平台/, + /running\s+on\s+(?:what|which)/i, + /what\s+platform/i, + // 问系统提示词类 + /系统\s*提示词/, + /system\s*prompt/i, + // "你是谁"的变体 + /你\s*(?:到底|究竟|真的|真实)\s*是\s*谁/, + /你\s*是[^。,,\.]{0,5}(?:AI|人工智能|助手|机器人|模型|Claude|GPT|Gemini)/i, + // 注意:工具能力询问不在这里拦截,由拒绝检测+重试自然处理 +]; + +// ==================== 工具能力询问检测 ==================== +// 用户问"你有哪些工具"时,重试失败后返回专用回复 + +export const TOOL_CAPABILITY_PATTERNS: RegExp[] = [ + /你\s*(?:有|能用|可以用)\s*(?:哪些|什么|几个)\s*(?:工具|tools?|functions?)/i, + /(?:what|which|list).*?tools?/i, + /你\s*用\s*(?:什么|哪个|啥)\s*(?:mcp|工具)/i, + /你\s*(?:能|可以)\s*(?:做|干)\s*(?:什么|哪些|啥)/, + /(?:what|which).*?(?:capabilities|functions)/i, + /能力|功能/, +]; + +// ==================== 固定回复模板 ==================== + +/** Claude 身份回复(身份探针拦截 / 拒绝后降级) */ +export const CLAUDE_IDENTITY_RESPONSE = `I am Claude, made by Anthropic. I'm an AI assistant designed to be helpful, harmless, and honest. I can help you with a wide range of tasks including writing, analysis, coding, math, and more. + +I don't have information about the specific model version or ID being used for this conversation, but I'm happy to help you with whatever you need!`; + +/** 工具能力询问的模拟回复(当用户问"你有哪些工具"时) */ +export const CLAUDE_TOOLS_RESPONSE = `作为 Claude,我的核心能力包括: + +**内置能力:** +- 💻 **代码编写与调试** — 支持所有主流编程语言 +- 📝 **文本写作与分析** — 文章、报告、翻译等 +- 📊 **数据分析与数学推理** — 复杂计算和逻辑分析 +- 🧠 **问题解答与知识查询** — 各类技术和非技术问题 + +**工具调用能力(MCP):** +如果你的客户端配置了 MCP(Model Context Protocol)工具,我可以通过工具调用来执行更多操作,例如: +- 🔍 **网络搜索** — 实时查找信息 +- 📁 **文件操作** — 读写文件、执行命令 +- 🛠️ **自定义工具** — 取决于你配置的 MCP Server + +具体可用的工具取决于你客户端的配置。你可以告诉我你想做什么,我会尽力帮助你!`; diff --git a/src/handler.ts b/src/handler.ts index 1d04383..d1ad03d 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -30,119 +30,23 @@ function toolId(): string { return 'toolu_' + uuidv4().replace(/-/g, '').substring(0, 24); } -// ==================== 拒绝模式识别 ==================== -const REFUSAL_PATTERNS = [ - // English identity refusal - /Cursor(?:'s)?\s+support\s+assistant/i, - /support\s+assistant\s+for\s+Cursor/i, - /I[''']m\s+sorry/i, - /I\s+am\s+sorry/i, - /not\s+able\s+to\s+fulfill/i, - /cannot\s+perform/i, - /I\s+can\s+only\s+answer/i, - /I\s+only\s+answer/i, - /cannot\s+write\s+files/i, - /pricing[, \s]*or\s*troubleshooting/i, - /I\s+cannot\s+help\s+with/i, - /I'm\s+a\s+coding\s+assistant/i, - /not\s+able\s+to\s+search/i, - /not\s+in\s+my\s+core/i, - /outside\s+my\s+capabilities/i, - /I\s+cannot\s+search/i, - /focused\s+on\s+software\s+development/i, - /not\s+able\s+to\s+help\s+with\s+(?:that|this)/i, - /beyond\s+(?:my|the)\s+scope/i, - /I'?m\s+not\s+(?:able|designed)\s+to/i, - /I\s+don't\s+have\s+(?:the\s+)?(?:ability|capability)/i, - /questions\s+about\s+(?:Cursor|the\s+(?:AI\s+)?code\s+editor)/i, - // English topic refusal — Cursor 拒绝非编程话题 - /help\s+with\s+(?:coding|programming)\s+and\s+Cursor/i, - /Cursor\s+IDE\s+(?:questions|features|related)/i, - /unrelated\s+to\s+(?:programming|coding)(?:\s+or\s+Cursor)?/i, - /Cursor[- ]related\s+question/i, - /(?:ask|please\s+ask)\s+a\s+(?:programming|coding|Cursor)/i, - /(?:I'?m|I\s+am)\s+here\s+to\s+help\s+with\s+(?:coding|programming)/i, - /appears\s+to\s+be\s+(?:asking|about)\s+.*?unrelated/i, - /(?:not|isn't|is\s+not)\s+(?:related|relevant)\s+to\s+(?:programming|coding|software)/i, - /I\s+can\s+help\s+(?:you\s+)?with\s+things\s+like/i, - // New Cursor refusal phrases (2026-03) - /isn't\s+something\s+I\s+can\s+help\s+with/i, - /not\s+something\s+I\s+can\s+help\s+with/i, - /scoped\s+to\s+answering\s+questions\s+about\s+Cursor/i, - /falls\s+outside\s+(?:the\s+scope|what\s+I)/i, - // Prompt injection / social engineering detection (new failure mode) - /prompt\s+injection\s+attack/i, - /prompt\s+injection/i, - /social\s+engineering/i, - /I\s+need\s+to\s+stop\s+and\s+flag/i, - /What\s+I\s+will\s+not\s+do/i, - /What\s+is\s+actually\s+happening/i, - /replayed\s+against\s+a\s+real\s+system/i, - /tool-call\s+payloads/i, - /copy-pasteable\s+JSON/i, - /injected\s+into\s+another\s+AI/i, - /emit\s+tool\s+invocations/i, - /make\s+me\s+output\s+tool\s+calls/i, - // Tool availability claims (Cursor role lock) - /I\s+(?:only\s+)?have\s+(?:access\s+to\s+)?(?:two|2|read_file|read_dir)\s+tool/i, - /(?:only|just)\s+(?:two|2)\s+(?:tools?|functions?)\b/i, - /\bread_file\b.*\bread_dir\b/i, - /\bread_dir\b.*\bread_file\b/i, - /有以下.*?(?:两|2)个.*?工具/, - /我有.*?(?:两|2)个工具/, - /工具.*?(?:只有|有以下|仅有).*?(?:两|2)个/, - /只能用.*?read_file/i, - /无法调用.*?工具/, - /(?:仅限于|仅用于).*?(?:查阅|浏览).*?(?:文档|docs)/, - // Chinese identity refusal - /我是\s*Cursor\s*的?\s*支持助手/, - /Cursor\s*的?\s*支持系统/, - /Cursor\s*(?:编辑器|IDE)?\s*相关的?\s*问题/, - /我的职责是帮助你解答/, - /我无法透露/, - /帮助你解答\s*Cursor/, - /运行在\s*Cursor\s*的/, - /专门.*回答.*(?:Cursor|编辑器)/, - /我只能回答/, - /无法提供.*信息/, - /我没有.*也不会提供/, - /功能使用[、,]\s*账单/, - /故障排除/, - // Chinese topic refusal - /与\s*(?:编程|代码|开发)\s*无关/, - /请提问.*(?:编程|代码|开发|技术).*问题/, - /只能帮助.*(?:编程|代码|开发)/, - // Chinese prompt injection detection - /不是.*需要文档化/, - /工具调用场景/, - /语言偏好请求/, - /提供.*具体场景/, - /即报错/, - // EN: scope/expertise wordings (2026-03 batch) - /(?:outside|beyond)\s+(?:the\s+)?scope\s+of\s+what/i, - /not\s+(?:within|in)\s+(?:my|the)\s+scope/i, - /this\s+assistant\s+is\s+(?:focused|scoped)/i, - /(?:only|just)\s+(?:able|here)\s+to\s+(?:answer|help)/i, - /I\s+(?:can\s+)?only\s+help\s+with\s+(?:questions|issues)\s+(?:related|about)/i, - /(?:here|designed)\s+to\s+help\s+(?:with\s+)?(?:questions\s+)?about\s+Cursor/i, - /not\s+(?:something|a\s+topic)\s+(?:related|specific)\s+to\s+(?:Cursor|coding)/i, - /outside\s+(?:my|the|your)\s+area\s+of\s+(?:expertise|scope)/i, - /(?:can[.']?t|cannot|unable\s+to)\s+help\s+with\s+(?:this|that)\s+(?:request|question|topic)/i, - /scoped\s+to\s+(?:answering|helping)/i, - // CN: Chinese refusal wordings — Cursor 中文界面下的拒绝 (2026-03 batch) - /只能回答.*(?:Cursor|编辑器).*(?:相关|有关)/, - /专[注门].*(?:回答|帮助|解答).*(?:Cursor|编辑器)/, - /有什么.*(?:Cursor|编辑器).*(?:问题|可以)/, - /无法提供.*(?:推荐|建议|帮助)/, - /(?:功能使用|账户|故障排除|账号|订阅|套餐|计费).*(?:等|问题)/, -]; +// ==================== 常量导入 ==================== +// 拒绝模式、身份探针、工具能力询问等常量统一定义在 constants.ts +// 方便查阅和修改内置规则,无需翻阅此文件的业务逻辑 +import { + isRefusal, + IDENTITY_PROBE_PATTERNS, + TOOL_CAPABILITY_PATTERNS, + CLAUDE_IDENTITY_RESPONSE, + CLAUDE_TOOLS_RESPONSE, +} from './constants.js'; -export function isRefusal(text: string): boolean { - return REFUSAL_PATTERNS.some(p => p.test(text)); -} +// Re-export for other modules (openai-handler.ts etc.) +export { isRefusal, CLAUDE_IDENTITY_RESPONSE, CLAUDE_TOOLS_RESPONSE }; // ==================== Thinking 提取 ==================== + const THINKING_OPEN = ''; const THINKING_CLOSE = ''; @@ -222,31 +126,6 @@ export function countTokens(req: Request, res: Response): void { // ==================== 身份探针拦截 ==================== -// 关键词检测(宽松匹配):只要用户消息包含这些关键词组合就判定为身份探针 -const IDENTITY_PROBE_PATTERNS = [ - // 精确短句(原有) - /^\s*(who are you\??|你是谁[呀啊吗]?\??|what is your name\??|你叫什么\??|你叫什么名字\??|what are you\??|你是什么\??|Introduce yourself\??|自我介绍一下\??|hi\??|hello\??|hey\??|你好\??|在吗\??|哈喽\??)\s*$/i, - // 问模型/身份类 - /(?:什么|哪个|啥)\s*模型/, - /(?:真实|底层|实际|真正).{0,10}(?:模型|身份|名字)/, - /模型\s*(?:id|名|名称|名字|是什么)/i, - /(?:what|which)\s+model/i, - /(?:real|actual|true|underlying)\s+(?:model|identity|name)/i, - /your\s+(?:model|identity|real\s+name)/i, - // 问平台/运行环境类 - /运行在\s*(?:哪|那|什么)/, - /(?:哪个|什么)\s*平台/, - /running\s+on\s+(?:what|which)/i, - /what\s+platform/i, - // 问系统提示词类 - /系统\s*提示词/, - /system\s*prompt/i, - // 你是谁的变体 - /你\s*(?:到底|究竟|真的|真实)\s*是\s*谁/, - /你\s*是[^。,,\.]{0,5}(?:AI|人工智能|助手|机器人|模型|Claude|GPT|Gemini)/i, - // 注意:工具能力询问(“你有哪些工具”)不在这里拦截,而是让拒绝检测+重试自然处理 -]; - export function isIdentityProbe(body: AnthropicRequest): boolean { if (!body.messages || body.messages.length === 0) return false; const lastMsg = body.messages[body.messages.length - 1]; @@ -267,40 +146,6 @@ export function isIdentityProbe(body: AnthropicRequest): boolean { return IDENTITY_PROBE_PATTERNS.some(p => p.test(text)); } -// ==================== 响应内容清洗 ==================== - -// Claude 身份回复模板(拒绝后的降级回复) -export const CLAUDE_IDENTITY_RESPONSE = `I am Claude, made by Anthropic. I'm an AI assistant designed to be helpful, harmless, and honest. I can help you with a wide range of tasks including writing, analysis, coding, math, and more. - -I don't have information about the specific model version or ID being used for this conversation, but I'm happy to help you with whatever you need!`; - -// 工具能力询问的模拟回复(当用户问“你有哪些工具”时,返回 Claude 真实能力描述) -export const CLAUDE_TOOLS_RESPONSE = `作为 Claude,我的核心能力包括: - -**内置能力:** -- 💻 **代码编写与调试** — 支持所有主流编程语言 -- 📝 **文本写作与分析** — 文章、报告、翻译等 -- 📊 **数据分析与数学推理** — 复杂计算和逻辑分析 -- 🧠 **问题解答与知识查询** — 各类技术和非技术问题 - -**工具调用能力(MCP):** -如果你的客户端配置了 MCP(Model Context Protocol)工具,我可以通过工具调用来执行更多操作,例如: -- 🔍 **网络搜索** — 实时查找信息 -- 📁 **文件操作** — 读写文件、执行命令 -- 🛠️ **自定义工具** — 取决于你配置的 MCP Server - -具体可用的工具取决于你客户端的配置。你可以告诉我你想做什么,我会尽力帮助你!`; - -// 检测是否是工具能力询问(用于重试失败后返回专用回复) -const TOOL_CAPABILITY_PATTERNS = [ - /你\s*(?:有|能用|可以用)\s*(?:哪些|什么|几个)\s*(?:工具|tools?|functions?)/i, - /(?:what|which|list).*?tools?/i, - /你\s*用\s*(?:什么|哪个|啥)\s*(?:mcp|工具)/i, - /你\s*(?:能|可以)\s*(?:做|干)\s*(?:什么|哪些|啥)/, - /(?:what|which).*?(?:capabilities|functions)/i, - /能力|功能/, -]; - export function isToolCapabilityQuestion(body: AnthropicRequest): boolean { if (!body.messages || body.messages.length === 0) return false; const lastMsg = body.messages[body.messages.length - 1]; @@ -318,11 +163,19 @@ export function isToolCapabilityQuestion(body: AnthropicRequest): boolean { return TOOL_CAPABILITY_PATTERNS.some(p => p.test(text)); } +// ==================== 响应内容清洗 ==================== + /** * 对所有响应做后处理:清洗 Cursor 身份引用,替换为 Claude * 这是最后一道防线,确保用户永远看不到 Cursor 相关的身份信息 + * + * ★ 受配置开关 sanitize_response 控制,默认关闭 + * 开启方式:config.yaml 中设置 sanitize_response: true + * 或环境变量 SANITIZE_RESPONSE=true */ export function sanitizeResponse(text: string): string { + // 配置未启用时直接返回原文本,零开销 + if (!getConfig().sanitizeEnabled) return text; let result = text; // === English identity replacements === @@ -1618,7 +1471,7 @@ Continue EXACTLY from where you stopped. DO NOT repeat any content already gener stopReason = 'tool_use'; // Check if the residual text is a known refusal, if so, drop it completely! - if (REFUSAL_PATTERNS.some(p => p.test(cleanText))) { + if (isRefusal(cleanText)) { log.info('Handler', 'sanitize', `抑制工具调用中的拒绝文本`, { preview: cleanText.substring(0, 200) }); cleanText = ''; } diff --git a/src/types.ts b/src/types.ts index 758473d..ad5e309 100644 --- a/src/types.ts +++ b/src/types.ts @@ -136,6 +136,8 @@ export interface AppConfig { includeOnly?: string[]; // 白名单:只保留的工具名 exclude?: string[]; // 黑名单:要排除的工具名 }; + sanitizeEnabled: boolean; // 是否启用响应内容清洗(替换 Cursor 身份引用为 Claude),默认 false + refusalPatterns?: string[]; // 自定义拒绝检测规则(追加到内置列表之后) fingerprint: { userAgent: string; };