@dedenlabs/claude-code-router-cli 2.0.3 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +753 -678
- package/dist/cli.js +14 -3
- package/dist/index.html +464 -0
- package/examples/README-External-Rules.md +32 -2
- package/examples/README-/350/260/203/350/257/225/346/227/245/345/277/227/350/204/232/346/234/254.md +386 -0
- package/examples/config-with-debug-logger.json +111 -0
- package/examples/external-rules/debug-logger.js +254 -0
- package/examples/external-rules-example.js +6 -6
- package/examples/image/README/1765879508612.png +0 -0
- package/examples/image/README-/350/260/203/350/257/225/346/227/245/345/277/227/350/204/232/346/234/254/1765945871911.png +0 -0
- package/package.json +76 -76
- package/examples/external-rules/user-preference.js +0 -70
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 调试日志外部路由脚本
|
|
3
|
+
*
|
|
4
|
+
* 专门用于打印模型请求数据的外部路由函数
|
|
5
|
+
* 优先级最高,但不拦截任何请求(始终返回false)
|
|
6
|
+
*
|
|
7
|
+
* 特性:
|
|
8
|
+
* - 变量式日志收集:每种日志类型单独存储
|
|
9
|
+
* - 选择性输出:用户可指定要输出的日志类型
|
|
10
|
+
* - 文件存储:增量写入到 ~/.claude-code-router/logs/
|
|
11
|
+
* - 时间戳:每个日志条目带时间标记
|
|
12
|
+
* - 折叠友好:支持编辑器折叠功能
|
|
13
|
+
*
|
|
14
|
+
* 使用方式:
|
|
15
|
+
* 1. 在配置中添加此外部规则
|
|
16
|
+
* 2. 设置最高优先级(999)
|
|
17
|
+
* 3. 该函数会收集所有请求数据但不拦截
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const util = require('util');
|
|
23
|
+
const os = require('node:os');
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 打印详细的模型请求数据
|
|
29
|
+
* @param {RouteContext} context - 路由上下文,包含完整的请求信息
|
|
30
|
+
* @returns {boolean} - 始终返回false,不拦截路由
|
|
31
|
+
*/
|
|
32
|
+
function printModelRequestData(context) {
|
|
33
|
+
// ========== 收集日志 ==========
|
|
34
|
+
// 修改下面的数组来选择要输出的日志类型
|
|
35
|
+
// 可选值: 'basic', 'headers', 'messages', 'system', 'tools', 'body', 'usage', 'event'
|
|
36
|
+
const outputTypes = [
|
|
37
|
+
'body',
|
|
38
|
+
// 'basic',
|
|
39
|
+
// 'headers',
|
|
40
|
+
// 'messages',
|
|
41
|
+
// 'system',
|
|
42
|
+
// 'tools',
|
|
43
|
+
// 'usage',
|
|
44
|
+
// 'event'
|
|
45
|
+
];
|
|
46
|
+
// 是否写入到日志文件 `~/.claude-code-router/logs/*.log`
|
|
47
|
+
const logToFile = true;
|
|
48
|
+
// 是否输出到控制台
|
|
49
|
+
const logToConsole = false;
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
// ========== 直接收集日志 ==========
|
|
53
|
+
// 保留变量名,但将逻辑直接写在这里,方便修改和检查
|
|
54
|
+
|
|
55
|
+
// 基本信息
|
|
56
|
+
let basicInfo = '';
|
|
57
|
+
basicInfo += '📊 【基本信息】\n';
|
|
58
|
+
basicInfo += ` Token 数量: ${context.tokenCount}\n`;
|
|
59
|
+
basicInfo += ` 会话ID: ${context.sessionId || 'N/A'}\n`;
|
|
60
|
+
|
|
61
|
+
// 请求头信息
|
|
62
|
+
let headersMsg = '';
|
|
63
|
+
if (context.req?.headers) {
|
|
64
|
+
headersMsg += '📋 【请求头】\n';
|
|
65
|
+
const headers = context.req.headers;
|
|
66
|
+
Object.keys(headers).forEach(key => {
|
|
67
|
+
headersMsg += ` ${key}: ${headers[key]}\n`;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 消息内容
|
|
72
|
+
let messages = '';
|
|
73
|
+
if (context.messages && context.messages.length > 0) {
|
|
74
|
+
messages += `💬 【消息内容】(共 ${context.messages.length} 条)\n`;
|
|
75
|
+
|
|
76
|
+
context.messages.forEach((message, index) => {
|
|
77
|
+
messages += `\n [消息 ${index + 1}]\n`;
|
|
78
|
+
messages += ` Role: ${message.role}\n`;
|
|
79
|
+
|
|
80
|
+
// 处理内容
|
|
81
|
+
if (message.content) {
|
|
82
|
+
if (typeof message.content === 'string') {
|
|
83
|
+
const preview = message.content.substring(0, 200);
|
|
84
|
+
messages += ` Content: ${preview}${message.content.length > 200 ? '...' : ''}\n`;
|
|
85
|
+
} else if (Array.isArray(message.content)) {
|
|
86
|
+
messages += ` Content (多部分):\n`;
|
|
87
|
+
message.content.forEach((part, partIndex) => {
|
|
88
|
+
messages += ` [${partIndex}] Type: ${part.type}\n`;
|
|
89
|
+
if (part.type === 'text') {
|
|
90
|
+
const preview = part.text?.substring(0, 100) || '';
|
|
91
|
+
messages += ` Text: ${preview}${part.text?.length > 100 ? '...' : ''}\n`;
|
|
92
|
+
} else if (part.type === 'image_url') {
|
|
93
|
+
messages += ` Image URL: ${part.image_url?.url || 'N/A'}\n`;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 处理工具调用
|
|
100
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
101
|
+
messages += ` Tool Calls (${message.tool_calls.length} 个):\n`;
|
|
102
|
+
message.tool_calls.forEach((toolCall, toolIndex) => {
|
|
103
|
+
messages += ` [${toolIndex}] ${toolCall.name}(${toolCall.id})\n`;
|
|
104
|
+
messages += ` Args: ${JSON.stringify(toolCall.arguments, null, 2)}\n`;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 系统消息
|
|
111
|
+
let systemMessages = '';
|
|
112
|
+
if (context.system && context.system.length > 0) {
|
|
113
|
+
systemMessages += `⚙️ 【系统消息】(共 ${context.system.length} 条)\n`;
|
|
114
|
+
|
|
115
|
+
context.system.forEach((sysMsg, index) => {
|
|
116
|
+
systemMessages += `\n [系统消息 ${index + 1}]\n`;
|
|
117
|
+
if (sysMsg.content) {
|
|
118
|
+
const preview = sysMsg.content.substring(0, 300);
|
|
119
|
+
systemMessages += ` Content: ${preview}${sysMsg.content.length > 300 ? '...' : ''}\n`;
|
|
120
|
+
}
|
|
121
|
+
if (sysMsg.type) {
|
|
122
|
+
systemMessages += ` Type: ${sysMsg.type}\n`;
|
|
123
|
+
}
|
|
124
|
+
if (sysMsg.name) {
|
|
125
|
+
systemMessages += ` Name: ${sysMsg.name}\n`;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 可用工具
|
|
131
|
+
let tools = '';
|
|
132
|
+
if (context.tools && context.tools.length > 0) {
|
|
133
|
+
tools += `🔧 【可用工具】(共 ${context.tools.length} 个)\n`;
|
|
134
|
+
|
|
135
|
+
context.tools.forEach((tool, index) => {
|
|
136
|
+
tools += `\n [${index + 1}] ${tool.name}\n`;
|
|
137
|
+
tools += ` Description: ${tool.description || 'N/A'}\n`;
|
|
138
|
+
if (tool.input_schema) {
|
|
139
|
+
tools += ` Input Schema: ${JSON.stringify(tool.input_schema, null, 2)}\n`;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 请求体原始数据
|
|
145
|
+
let requestBody = '';
|
|
146
|
+
if (context.req) {
|
|
147
|
+
requestBody += '📦 【请求体原始数据】\n';
|
|
148
|
+
|
|
149
|
+
// 安全地打印请求信息,避免循环引用和修改原始数据
|
|
150
|
+
const safeReq = {
|
|
151
|
+
method: context.req.method,
|
|
152
|
+
url: context.req.url,
|
|
153
|
+
headers: context.req.headers,
|
|
154
|
+
body: context.req.body,
|
|
155
|
+
httpVersion: context.req.httpVersion,
|
|
156
|
+
socket: context.req.socket ? '[Socket Object]' : undefined
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// 创建 body 的深拷贝,避免修改原始数据
|
|
160
|
+
if (safeReq.body) {
|
|
161
|
+
safeReq.body = { ...safeReq.body };
|
|
162
|
+
// 清理可能过大的字段(仅在副本上操作)
|
|
163
|
+
if (safeReq.body.messages) {
|
|
164
|
+
safeReq.body.messages = `[包含 ${safeReq.body.messages.length} 条消息的数组]`;
|
|
165
|
+
}
|
|
166
|
+
if (safeReq.body.system) {
|
|
167
|
+
safeReq.body.system = `[包含 ${safeReq.body.system.length} 条系统消息的数组]`;
|
|
168
|
+
}
|
|
169
|
+
if (safeReq.body.tools) {
|
|
170
|
+
safeReq.body.tools = `[包含 ${safeReq.body.tools.length} 个工具定义的数组]`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
requestBody += util.inspect(safeReq, { depth: null, colors: false, breakLength: Infinity }) + '\n';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 使用统计
|
|
178
|
+
let usageStats = '';
|
|
179
|
+
if (context.lastUsage) {
|
|
180
|
+
usageStats += '📈 【使用统计】\n';
|
|
181
|
+
usageStats += ` 输入 tokens: ${context.lastUsage.input_tokens || 0}\n`;
|
|
182
|
+
usageStats += ` 输出 tokens: ${context.lastUsage.output_tokens || 0}\n`;
|
|
183
|
+
usageStats += ` 总 tokens: ${context.lastUsage.total_tokens || 0}\n`;
|
|
184
|
+
if (context.lastUsage.cost) {
|
|
185
|
+
usageStats += ` 估算成本: $${context.lastUsage.cost}\n`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 事件信息
|
|
190
|
+
let eventInfo = '';
|
|
191
|
+
if (context.event) {
|
|
192
|
+
eventInfo += '🎯 【事件信息】\n';
|
|
193
|
+
eventInfo += util.inspect(context.event, { depth: 2, colors: false }) + '\n';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ========== 生成完整日志 ==========
|
|
197
|
+
let fullLog = '\n========================================\n';
|
|
198
|
+
fullLog += '🔍 【调试日志】模型请求数据\n';
|
|
199
|
+
fullLog += '========================================\n\n';
|
|
200
|
+
|
|
201
|
+
// 根据选择输出日志
|
|
202
|
+
if (outputTypes.includes('basic')) fullLog += basicInfo + '\n';
|
|
203
|
+
if (outputTypes.includes('headers') && headersMsg) fullLog += headersMsg + '\n';
|
|
204
|
+
if (outputTypes.includes('messages') && messages) fullLog += messages + '\n';
|
|
205
|
+
if (outputTypes.includes('system') && systemMessages) fullLog += systemMessages + '\n';
|
|
206
|
+
if (outputTypes.includes('tools') && tools) fullLog += tools + '\n';
|
|
207
|
+
if (outputTypes.includes('body') && requestBody) fullLog += requestBody + '\n';
|
|
208
|
+
if (outputTypes.includes('usage') && usageStats) fullLog += usageStats + '\n';
|
|
209
|
+
if (outputTypes.includes('event') && eventInfo) fullLog += eventInfo + '\n';
|
|
210
|
+
|
|
211
|
+
fullLog += '========================================\n';
|
|
212
|
+
fullLog += '✅ 【调试日志】打印完成 - 路由继续执行\n';
|
|
213
|
+
fullLog += '========================================\n\n';
|
|
214
|
+
|
|
215
|
+
// ========== 输出方式 ==========
|
|
216
|
+
// 1. 输出到控制台
|
|
217
|
+
if (logToConsole) console.log(fullLog);
|
|
218
|
+
|
|
219
|
+
// 2. 写入到日志文件(增量追加)
|
|
220
|
+
if (logToFile) writeLogToFile(fullLog);
|
|
221
|
+
|
|
222
|
+
// 始终返回 false,不拦截任何路由
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 日志文件路径
|
|
228
|
+
*/
|
|
229
|
+
const getLogFilePath = () => {
|
|
230
|
+
const logDir = path.join(os.homedir(), '.claude-code-router', 'logs');
|
|
231
|
+
if (!fs.existsSync(logDir)) {
|
|
232
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
233
|
+
}
|
|
234
|
+
const timestamp = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
235
|
+
return path.join(logDir, `debug-logger-${timestamp}.log`);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* 写入日志到文件(增量追加)
|
|
240
|
+
* @param {string} content - 日志内容
|
|
241
|
+
*/
|
|
242
|
+
function writeLogToFile(content) {
|
|
243
|
+
try {
|
|
244
|
+
const logFile = getLogFilePath();
|
|
245
|
+
const timestamp = new Date().toISOString();
|
|
246
|
+
const logEntry = `[${timestamp}] ${content}\n`;
|
|
247
|
+
fs.appendFileSync(logFile, logEntry, 'utf8');
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('❌ 写入日志文件失败:', error.message);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 导出函数
|
|
254
|
+
module.exports = { printModelRequestData };
|
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
* 演示如何使用 externalFunction 条件类型来加载外部 JS 规则
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
// 示例1
|
|
8
|
-
const
|
|
9
|
-
name: '
|
|
7
|
+
// 示例1:只打印模型请求的数据不进行匹配
|
|
8
|
+
const printModelRequestDataRule = {
|
|
9
|
+
name: 'printModelRequestData',
|
|
10
10
|
priority: 100,
|
|
11
11
|
condition: {
|
|
12
12
|
type: 'externalFunction',
|
|
13
13
|
externalFunction: {
|
|
14
|
-
path: './external-rules/
|
|
14
|
+
path: './external-rules/debug-logger.js',
|
|
15
15
|
functionName: 'checkUserPreference'
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
18
|
action: {
|
|
19
|
-
route: '
|
|
19
|
+
route: ''
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
|
|
@@ -54,7 +54,7 @@ const complexRoutingRule = {
|
|
|
54
54
|
|
|
55
55
|
// 将这些规则添加到配置中
|
|
56
56
|
export const externalRules = [
|
|
57
|
-
|
|
57
|
+
printModelRequestDataRule,
|
|
58
58
|
timeBasedRule,
|
|
59
59
|
complexRoutingRule
|
|
60
60
|
];
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@dedenlabs/claude-code-router-cli",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "基于@musistudio/claude-code-router的增强版CLI路由工具 - 支持统一路由引擎、外部规则加载、智能日志系统、自动配置迁移和GLM思考模式",
|
|
5
|
-
"bin": {
|
|
6
|
-
"ccr": "dist/cli.js"
|
|
7
|
-
},
|
|
8
|
-
"scripts": {
|
|
9
|
-
"build": "node scripts/build.js",
|
|
10
|
-
"release": "npm run build && npm publish",
|
|
11
|
-
"prepublishOnly": "node scripts/prepublish-check.js",
|
|
12
|
-
"test": "vitest run",
|
|
13
|
-
"test:watch": "vitest",
|
|
14
|
-
"test:coverage": "vitest run --coverage",
|
|
15
|
-
"test:ui": "vitest --ui",
|
|
16
|
-
"test:integration": "vitest run --config ./vitest.config.ts tests/integration",
|
|
17
|
-
"test:unit": "vitest run --config ./vitest.config.ts tests/unit",
|
|
18
|
-
"test:ui:unit": "vitest run --config ./vitest.config.ts tests/ui"
|
|
19
|
-
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"claude",
|
|
22
|
-
"code",
|
|
23
|
-
"router",
|
|
24
|
-
"llm",
|
|
25
|
-
"anthropic",
|
|
26
|
-
"cli",
|
|
27
|
-
"unified",
|
|
28
|
-
"external-rules"
|
|
29
|
-
],
|
|
30
|
-
"author": "dedenlabs <deden.labs@gmail.com> (Original by musistudio)",
|
|
31
|
-
"license": "MIT",
|
|
32
|
-
"repository": {
|
|
33
|
-
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/dedenlabs/claude-code-router-cli.git"
|
|
35
|
-
},
|
|
36
|
-
"bugs": {
|
|
37
|
-
"url": "https://github.com/dedenlabs/claude-code-router-cli/issues"
|
|
38
|
-
},
|
|
39
|
-
"homepage": "https://github.com/dedenlabs/claude-code-router-cli#readme",
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"@fastify/static": "^8.2.0",
|
|
42
|
-
"@inquirer/prompts": "^5.0.0",
|
|
43
|
-
"@musistudio/llms": "^1.0.48",
|
|
44
|
-
"dotenv": "^16.4.7",
|
|
45
|
-
"find-process": "^2.0.0",
|
|
46
|
-
"json5": "^2.2.3",
|
|
47
|
-
"lru-cache": "^11.2.2",
|
|
48
|
-
"minimist": "^1.2.8",
|
|
49
|
-
"rotating-file-stream": "^3.2.7",
|
|
50
|
-
"shell-quote": "^1.8.3",
|
|
51
|
-
"tiktoken": "^1.0.21",
|
|
52
|
-
"uuid": "^11.1.0"
|
|
53
|
-
},
|
|
54
|
-
"devDependencies": {
|
|
55
|
-
"@testing-library/react": "^14.3.1",
|
|
56
|
-
"@types/node": "^24.0.15",
|
|
57
|
-
"@vitest/coverage-v8": "^2.1.9",
|
|
58
|
-
"@vitest/ui": "^2.1.9",
|
|
59
|
-
"esbuild": "^0.25.1",
|
|
60
|
-
"fastify": "^5.4.0",
|
|
61
|
-
"jsdom": "^25.0.1",
|
|
62
|
-
"shx": "^0.4.0",
|
|
63
|
-
"typescript": "^5.8.2",
|
|
64
|
-
"vitest": "^2.1.9"
|
|
65
|
-
},
|
|
66
|
-
"files": [
|
|
67
|
-
"dist/",
|
|
68
|
-
"examples/",
|
|
69
|
-
"LICENSE",
|
|
70
|
-
"README.md",
|
|
71
|
-
"tsconfig.json"
|
|
72
|
-
],
|
|
73
|
-
"publishConfig": {
|
|
74
|
-
"access": "public"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@dedenlabs/claude-code-router-cli",
|
|
3
|
+
"version": "2.0.4",
|
|
4
|
+
"description": "基于@musistudio/claude-code-router的增强版CLI路由工具 - 支持统一路由引擎、外部规则加载、智能日志系统、自动配置迁移和GLM思考模式",
|
|
5
|
+
"bin": {
|
|
6
|
+
"ccr": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "node scripts/build.js",
|
|
10
|
+
"release": "npm run build && npm publish",
|
|
11
|
+
"prepublishOnly": "node scripts/prepublish-check.js",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"test:coverage": "vitest run --coverage",
|
|
15
|
+
"test:ui": "vitest --ui",
|
|
16
|
+
"test:integration": "vitest run --config ./vitest.config.ts tests/integration",
|
|
17
|
+
"test:unit": "vitest run --config ./vitest.config.ts tests/unit",
|
|
18
|
+
"test:ui:unit": "vitest run --config ./vitest.config.ts tests/ui"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"claude",
|
|
22
|
+
"code",
|
|
23
|
+
"router",
|
|
24
|
+
"llm",
|
|
25
|
+
"anthropic",
|
|
26
|
+
"cli",
|
|
27
|
+
"unified",
|
|
28
|
+
"external-rules"
|
|
29
|
+
],
|
|
30
|
+
"author": "dedenlabs <deden.labs@gmail.com> (Original by musistudio)",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/dedenlabs/claude-code-router-cli.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/dedenlabs/claude-code-router-cli/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/dedenlabs/claude-code-router-cli#readme",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@fastify/static": "^8.2.0",
|
|
42
|
+
"@inquirer/prompts": "^5.0.0",
|
|
43
|
+
"@musistudio/llms": "^1.0.48",
|
|
44
|
+
"dotenv": "^16.4.7",
|
|
45
|
+
"find-process": "^2.0.0",
|
|
46
|
+
"json5": "^2.2.3",
|
|
47
|
+
"lru-cache": "^11.2.2",
|
|
48
|
+
"minimist": "^1.2.8",
|
|
49
|
+
"rotating-file-stream": "^3.2.7",
|
|
50
|
+
"shell-quote": "^1.8.3",
|
|
51
|
+
"tiktoken": "^1.0.21",
|
|
52
|
+
"uuid": "^11.1.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@testing-library/react": "^14.3.1",
|
|
56
|
+
"@types/node": "^24.0.15",
|
|
57
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
58
|
+
"@vitest/ui": "^2.1.9",
|
|
59
|
+
"esbuild": "^0.25.1",
|
|
60
|
+
"fastify": "^5.4.0",
|
|
61
|
+
"jsdom": "^25.0.1",
|
|
62
|
+
"shx": "^0.4.0",
|
|
63
|
+
"typescript": "^5.8.2",
|
|
64
|
+
"vitest": "^2.1.9"
|
|
65
|
+
},
|
|
66
|
+
"files": [
|
|
67
|
+
"dist/",
|
|
68
|
+
"examples/",
|
|
69
|
+
"LICENSE",
|
|
70
|
+
"README.md",
|
|
71
|
+
"tsconfig.json"
|
|
72
|
+
],
|
|
73
|
+
"publishConfig": {
|
|
74
|
+
"access": "public"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 用户偏好路由规则
|
|
3
|
-
* 根据用户的系统消息中保存的偏好设置进行路由
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// 模拟用户偏好数据库
|
|
7
|
-
const userPreferences = {
|
|
8
|
-
'user@example.com': {
|
|
9
|
-
preferredModel: 'gpt-4',
|
|
10
|
-
provider: 'openai'
|
|
11
|
-
},
|
|
12
|
-
'admin@example.com': {
|
|
13
|
-
preferredModel: 'claude-3-opus',
|
|
14
|
-
provider: 'anthropic'
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 检查用户偏好
|
|
20
|
-
* @param {RouteContext} context - 路由上下文
|
|
21
|
-
* @returns {boolean} - 是否匹配用户偏好
|
|
22
|
-
*/
|
|
23
|
-
function checkUserPreference(context) {
|
|
24
|
-
// 从系统消息或会话信息中提取用户标识
|
|
25
|
-
const userEmail = extractUserEmail(context);
|
|
26
|
-
|
|
27
|
-
if (!userEmail) {
|
|
28
|
-
console.log(`无法识别用户邮箱,使用默认路由`);
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const preference = userPreferences[userEmail];
|
|
33
|
-
if (!preference) {
|
|
34
|
-
console.log(`用户 ${userEmail} 没有设置偏好,使用默认路由`);
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 记录匹配结果
|
|
39
|
-
console.log(`用户偏好匹配: ${userEmail} -> ${preference.provider}/${preference.preferredModel}`);
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 从上下文中提取用户邮箱
|
|
45
|
-
*/
|
|
46
|
-
function extractUserEmail(context) {
|
|
47
|
-
// 尝试从多个可能的来源获取用户邮箱
|
|
48
|
-
if (context.req?.headers?.['x-user-email']) {
|
|
49
|
-
return context.req.headers['x-user-email'];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (context.sessionId && context.sessionId.includes('@')) {
|
|
53
|
-
return context.sessionId;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 可以从系统消息中解析
|
|
57
|
-
if (context.system && context.system[0]?.text) {
|
|
58
|
-
const emailMatch = context.system[0].text.match(/user:\s*([^\s]+)/);
|
|
59
|
-
if (emailMatch) {
|
|
60
|
-
return emailMatch[1];
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// 导出函数
|
|
68
|
-
module.exports = {
|
|
69
|
-
checkUserPreference
|
|
70
|
-
};
|