diff --git a/auto-setup.ps1 b/auto-setup.ps1 index adebbfc..14415d8 100644 --- a/auto-setup.ps1 +++ b/auto-setup.ps1 @@ -16,7 +16,7 @@ param( $ErrorActionPreference = "Stop" # ─── 版本号 (每次更新递增, build.ps1 自动读取) ────── -$BWVersion = "1.9.0" +$BWVersion = "2.0.0" # ─── B4: 单实例保护 (防止双击两次导致竞态) ───────── $mutexCreated = $false @@ -1081,8 +1081,10 @@ if (Test-Path $templateFile) { $content = Get-Content $templateFile -Raw $content = $content -replace '\{\{CLAUDE_ROOT\}\}', $claudeRoot - $content = $content -replace '\{\{HOME\}\}', ($homeDir -replace '\\', '\\') - $content = $content -replace '\{\{PWSH_PATH\}\}', ($pwshJsonPath -replace '\\', '\\') + # HOME 必须用正斜杠: C:\Users\x 中 \U 是非法 JSON 转义序列 + $homeForward = $homeDir.Replace('\', '/') + $content = $content -replace '\{\{HOME\}\}', $homeForward + $content = $content -replace '\{\{PWSH_PATH\}\}', $pwshJsonPath Set-Content $settingsFile -Value $content -Encoding UTF8 Log-OK "settings.json 已渲染 (ROOT=$claudeRoot, SHELL=$pwshJsonPath)" @@ -1093,9 +1095,9 @@ if (Test-Path $templateFile) { if (Test-Path $localTpl) { $lc = Get-Content $localTpl -Raw $lc = $lc -replace '\{\{CLAUDE_ROOT\}\}', $claudeRoot - $lc = $lc -replace '\{\{HOME\}\}', ($homeDir -replace '\\', '\\') + $lc = $lc -replace '\{\{HOME\}\}', $homeForward $lc = $lc -replace '\{\{USERNAME\}\}', $env:USERNAME - $lc = $lc -replace '\{\{PWSH_PATH\}\}', ($pwshJsonPath -replace '\\', '\\') + $lc = $lc -replace '\{\{PWSH_PATH\}\}', $pwshJsonPath Set-Content $localSet -Value $lc -Encoding UTF8 Log-OK "settings.local.json 已渲染" } diff --git a/diagnose-tooluse.js b/diagnose-tooluse.js new file mode 100644 index 0000000..ceadca4 --- /dev/null +++ b/diagnose-tooluse.js @@ -0,0 +1,179 @@ +#!/usr/bin/env node +/** + * Bookworm tool_use 诊断脚本 + * 测试中转站是否正确透传 Anthropic tool_use 协议 + * 用法: node diagnose-tooluse.js + */ +const https = require('https'); +const http = require('http'); + +const API_KEY = process.env.ANTHROPIC_API_KEY; +const BASE_URL = process.env.ANTHROPIC_BASE_URL || 'https://api.anthropic.com'; + +if (!API_KEY) { + console.error('[FAIL] ANTHROPIC_API_KEY 未设置'); + process.exit(1); +} + +console.log(`[INFO] 中转站: ${BASE_URL}`); +console.log(`[INFO] API Key: ${API_KEY.substring(0, 8)}...`); +console.log(''); + +// 测试 1: 非流式 tool_use +async function testToolUse() { + const url = new URL('/v1/messages', BASE_URL); + const isHttps = url.protocol === 'https:'; + const lib = isHttps ? https : http; + + const body = JSON.stringify({ + model: 'claude-sonnet-4-20250514', + max_tokens: 200, + tools: [{ + name: 'get_disk_info', + description: 'Get disk space information', + input_schema: { + type: 'object', + properties: { drive: { type: 'string', description: 'Drive letter' } }, + required: ['drive'] + } + }], + tool_choice: { type: 'tool', name: 'get_disk_info' }, + messages: [{ role: 'user', content: 'Check disk C:' }] + }); + + return new Promise((resolve, reject) => { + const req = lib.request(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': API_KEY, + 'anthropic-version': '2023-06-01' + } + }, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { + const json = JSON.parse(data); + resolve({ status: res.statusCode, body: json }); + } catch (e) { + resolve({ status: res.statusCode, raw: data.substring(0, 500) }); + } + }); + }); + req.on('error', reject); + req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')); }); + req.write(body); + req.end(); + }); +} + +// 测试 2: 流式 tool_use +async function testStreamToolUse() { + const url = new URL('/v1/messages', BASE_URL); + const isHttps = url.protocol === 'https:'; + const lib = isHttps ? https : http; + + const body = JSON.stringify({ + model: 'claude-sonnet-4-20250514', + max_tokens: 200, + stream: true, + tools: [{ + name: 'get_disk_info', + description: 'Get disk space information', + input_schema: { + type: 'object', + properties: { drive: { type: 'string', description: 'Drive letter' } }, + required: ['drive'] + } + }], + tool_choice: { type: 'tool', name: 'get_disk_info' }, + messages: [{ role: 'user', content: 'Check disk C:' }] + }); + + return new Promise((resolve, reject) => { + const req = lib.request(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': API_KEY, + 'anthropic-version': '2023-06-01' + } + }, (res) => { + let data = ''; + let hasToolUseStart = false; + let hasInputJsonDelta = false; + + res.on('data', chunk => { + const text = chunk.toString(); + data += text; + if (text.includes('"type":"tool_use"') || text.includes('"type": "tool_use"')) hasToolUseStart = true; + if (text.includes('input_json_delta')) hasInputJsonDelta = true; + }); + res.on('end', () => { + resolve({ + status: res.statusCode, + hasToolUseStart, + hasInputJsonDelta, + sample: data.substring(0, 800) + }); + }); + }); + req.on('error', reject); + req.setTimeout(30000, () => { req.destroy(); reject(new Error('timeout')); }); + req.write(body); + req.end(); + }); +} + +(async () => { + // 测试 1: 非流式 + console.log('=== 测试 1: 非流式 tool_use ==='); + try { + const r = await testToolUse(); + if (r.status !== 200) { + console.log(`[FAIL] HTTP ${r.status}`); + console.log(JSON.stringify(r.body || r.raw, null, 2).substring(0, 300)); + } else { + const content = r.body?.content || []; + const toolUseBlock = content.find(b => b.type === 'tool_use'); + if (toolUseBlock) { + console.log(`[PASS] 收到 tool_use block: name=${toolUseBlock.name}, id=${toolUseBlock.id}`); + console.log(` input: ${JSON.stringify(toolUseBlock.input)}`); + } else { + console.log('[FAIL] 未收到 tool_use block!'); + console.log(' content types:', content.map(b => b.type).join(', ') || '(empty)'); + console.log(' stop_reason:', r.body?.stop_reason); + if (content[0]?.text) console.log(' text:', content[0].text.substring(0, 200)); + } + } + } catch (e) { + console.log(`[FAIL] 请求失败: ${e.message}`); + } + + console.log(''); + + // 测试 2: 流式 + console.log('=== 测试 2: 流式 tool_use (Claude Code 实际使用模式) ==='); + try { + const r = await testStreamToolUse(); + if (r.status !== 200) { + console.log(`[FAIL] HTTP ${r.status}`); + console.log(r.sample?.substring(0, 300)); + } else { + console.log(` tool_use block in stream: ${r.hasToolUseStart ? '[PASS]' : '[FAIL]'}`); + console.log(` input_json_delta events: ${r.hasInputJsonDelta ? '[PASS]' : '[FAIL]'}`); + if (!r.hasToolUseStart) { + console.log(' stream sample:', r.sample?.substring(0, 400)); + } + } + } catch (e) { + console.log(`[FAIL] 请求失败: ${e.message}`); + } + + console.log(''); + console.log('=== 诊断结论 ==='); + console.log('如果两项测试均 PASS: 中转站 tool_use 正常'); + console.log('如果非流式 PASS 流式 FAIL: 中转站流式 SSE 处理有 bug, 联系管理员升级 NewAPI'); + console.log('如果均 FAIL: 中转站不支持 Anthropic tool_use, 需开启原生 Anthropic 透传模式'); +})();