(req: AnthropicRequest)
| 208 | * 不覆盖模型身份,而是顺应它在 IDE 内的角色,让它认为自己在执行 IDE 内部的自动化任务 |
| 209 | */ |
| 210 | export async function convertToCursorRequest(req: AnthropicRequest): Promise<CursorChatRequest> { |
| 211 | const config = getConfig(); |
| 212 | |
| 213 | // ★ 图片预处理:在协议转换之前,检测并处理 Anthropic 格式的 ImageBlockParam |
| 214 | await preprocessImages(req.messages); |
| 215 | |
| 216 | // ★ 预估原始上下文大小,驱动动态工具结果预算 |
| 217 | let estimatedContextChars = 0; |
| 218 | if (req.system) { |
| 219 | estimatedContextChars += typeof req.system === 'string' ? req.system.length : JSON.stringify(req.system).length; |
| 220 | } |
| 221 | for (const msg of req.messages ?? []) { |
| 222 | estimatedContextChars += typeof msg.content === 'string' ? msg.content.length : JSON.stringify(msg.content).length; |
| 223 | } |
| 224 | if (req.tools && req.tools.length > 0) { |
| 225 | estimatedContextChars += req.tools.length * 150; // 压缩后每个工具约 150 chars |
| 226 | } |
| 227 | setCurrentContextChars(estimatedContextChars); |
| 228 | |
| 229 | const messages: CursorMessage[] = []; |
| 230 | const hasTools = req.tools && req.tools.length > 0; |
| 231 | |
| 232 | // 提取系统提示词 |
| 233 | let combinedSystem = ''; |
| 234 | if (req.system) { |
| 235 | if (typeof req.system === 'string') combinedSystem = req.system; |
| 236 | else if (Array.isArray(req.system)) { |
| 237 | combinedSystem = req.system.filter(b => b.type === 'text').map(b => b.text).join('\n'); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | // ★ 计费头清除:x-anthropic-billing-header 会被模型判定为恶意伪造并触发注入警告 |
| 242 | if (combinedSystem) { |
| 243 | combinedSystem = combinedSystem.replace(/^x-anthropic-billing-header[^\n]*$/gim, ''); |
| 244 | // ★ Claude Code 身份声明清除:模型看到 "You are Claude Code" 会认为是 prompt injection |
| 245 | combinedSystem = combinedSystem.replace(/^You are Claude Code[^\n]*$/gim, ''); |
| 246 | combinedSystem = combinedSystem.replace(/^You are Claude,\s+Anthropic's[^\n]*$/gim, ''); |
| 247 | combinedSystem = combinedSystem.replace(/\n{3,}/g, '\n\n').trim(); |
| 248 | } |
| 249 | // ★ Thinking 提示注入:根据是否有工具选择不同的注入位置 |
| 250 | // 有工具时:放在工具指令末尾(不会被工具定义覆盖,模型更容易注意) |
| 251 | // 无工具时:放在系统提示词末尾(原有行为,已验证有效) |
| 252 | const thinkingEnabled = req.thinking?.type === 'enabled' || req.thinking?.type === 'adaptive'; |
| 253 | const thinkingHint = '\n\n**IMPORTANT**: Before your response, you MUST first think through the problem step by step inside <thinking>...</thinking> tags. Your thinking process will be extracted and shown separately. After the closing </thinking> tag, provide your actual response or actions.'; |
| 254 | if (thinkingEnabled && !hasTools) { |
| 255 | combinedSystem = (combinedSystem || '') + thinkingHint; |
| 256 | } |
| 257 | |
| 258 | if (hasTools) { |
| 259 | const tools = req.tools!; |
| 260 | const toolChoice = req.tool_choice; |
| 261 | const toolsCfg = config.tools || { schemaMode: 'compact', descriptionMaxLength: 50 }; |
| 262 | const isDisabled = toolsCfg.disabled === true; |
| 263 | const isPassthrough = toolsCfg.passthrough === true; |
| 264 | |
| 265 | if (isDisabled) { |
| 266 | // ★ 禁用模式:完全不注入工具定义和 few-shot 示例 |
| 267 | // 目的:最大化节省上下文空间,让模型凭训练记忆处理工具调用 |
no test coverage detected