@dahawa/hawa-cli-analysis 1.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.
@@ -0,0 +1,305 @@
1
+ // server.js
2
+ import Fastify from "fastify";
3
+ import {mergeAnthropic} from '../api-anthropic.js';
4
+ import LoggerManage from "../logger-manager.js";
5
+ import { join } from "path";
6
+ import { readFileSync } from "fs";
7
+ import anthropicTransformer from "../anthropic-transformer.js"
8
+ import {parseOpenAIChatCompletion} from "../api-openai.js";
9
+ import portManager from '../port-manager.js';
10
+
11
+ let logger = LoggerManage.getLogger("claudecode");
12
+ const BASE_URL = process.env.BASE_URL;// || "https://api.anthropic.com";
13
+ logger.system.debug("-------------Clogger Start--------------------------");
14
+
15
+ // 配置文件相关功能
16
+ let toolsConfig = {
17
+ blacklist: [],
18
+ descriptions: {}
19
+ };
20
+
21
+ function loadToolsConfig() {
22
+ try {
23
+ const configPath = join(process.cwd(), '.tools.json');
24
+ const configContent = readFileSync(configPath, 'utf8');
25
+ const config = JSON.parse(configContent);
26
+
27
+ if (config.tools) {
28
+ toolsConfig.blacklist = config.tools.blacklist || [];
29
+ toolsConfig.descriptions = config.tools.descriptions || {};
30
+ logger.system.debug(`成功加载配置文件,黑名单: ${toolsConfig.blacklist.length} 个工具,描述重写: ${Object.keys(toolsConfig.descriptions).length} 个工具`);
31
+ }
32
+ } catch (error) {
33
+ if (error.code === 'ENOENT') {
34
+ logger.system.debug('未找到 .tools.json 配置文件,使用默认配置');
35
+ } else {
36
+ logger.system.error(`加载配置文件失败: ${error.message}`);
37
+ }
38
+ }
39
+ }
40
+
41
+ // 在启动时加载配置
42
+ loadToolsConfig();
43
+
44
+ function deepClone(obj) {
45
+ const result = JSON.parse(JSON.stringify(obj));
46
+ return result;
47
+ }
48
+
49
+ // 处理请求body中的tools,应用黑名单过滤和描述改写
50
+ function processRequestTools(body) {
51
+ if (!body || !body.tools || !Array.isArray(body.tools)) {
52
+ return body;
53
+ }
54
+
55
+ const originalCount = body.tools.length;
56
+
57
+ // 过滤黑名单中的工具
58
+ body.tools = body.tools.filter(tool => {
59
+ const toolName = tool.name || tool.function?.name;
60
+ if (!toolName) return true;
61
+
62
+ const isBlacklisted = toolsConfig.blacklist.includes(toolName);
63
+ if (isBlacklisted) {
64
+ logger.system.debug(`过滤黑名单工具: ${toolName}`);
65
+ }
66
+ return !isBlacklisted;
67
+ });
68
+
69
+ // 改写工具描述
70
+ body.tools = body.tools.map(tool => {
71
+ const toolName = tool.name || tool.function?.name;
72
+ if (toolName && toolsConfig.descriptions[toolName]) {
73
+ logger.system.debug(`改写工具描述: ${toolName} -> ${toolsConfig.descriptions[toolName]}`);
74
+
75
+ // 根据工具结构更新描述
76
+ if (tool.description !== undefined) {
77
+ tool.description = toolsConfig.descriptions[toolName];
78
+ } else if (tool.function && tool.function.description !== undefined) {
79
+ tool.function.description = toolsConfig.descriptions[toolName];
80
+ }
81
+ }
82
+ return tool;
83
+ });
84
+
85
+ const filteredCount = body.tools.length;
86
+ if (originalCount !== filteredCount) {
87
+ logger.system.debug(`工具过滤完成: ${originalCount} -> ${filteredCount}`);
88
+ }
89
+
90
+ return body;
91
+ }
92
+ function formateLine(str){
93
+ let r = str.replace(/\\n/g, '\n');
94
+ return r;
95
+ }
96
+ function toSimpleLog(fullLog){
97
+ //删除 tool 列表
98
+ let slog = deepClone(fullLog);
99
+ let result = {
100
+ request:slog.request.body.messages,
101
+ response:slog.response.body.content
102
+ };
103
+ return result;
104
+ }
105
+
106
+ function logAPI(fullLog){
107
+ //console.log('Writing to log files...');
108
+ try {
109
+ logger.full.debug(fullLog);
110
+ logger.simple.debug(toSimpleLog(fullLog));
111
+
112
+ // 立即同步到文件
113
+ if (logger.full.flush) {
114
+ logger.full.flush();
115
+ }
116
+ if (logger.simple.flush) {
117
+ logger.simple.flush();
118
+ }
119
+
120
+ // console.log('Log files written successfully');
121
+ } catch (error) {
122
+ console.error('Error writing to log files:', error);
123
+ }
124
+ }
125
+
126
+ function headersToObject(headers) {
127
+ const obj = {};
128
+ try {
129
+ for (const [k, v] of headers.entries()) obj[k] = v;
130
+ } catch {}
131
+ return obj;
132
+ }
133
+
134
+ const fastify = Fastify(
135
+ {
136
+ requestTimeout: 0, // never time out request (or set e.g. 10 * 60 * 1000)
137
+ connectionTimeout: 0, // no connection timeout
138
+ keepAliveTimeout: 120000, // 120s
139
+ }
140
+ );
141
+
142
+ // 注册一个 POST 接口
143
+ fastify.all("/*", async (request, reply) => {
144
+ //console.log("处理请求:", request.url);
145
+ return await handel(request, reply, request.url);
146
+ });
147
+ function joinUrl(base, ...paths) {
148
+ return [base.replace(/\/+$/, ''), ...paths.map(p => p.replace(/^\/+/, ''))]
149
+ .join('/');
150
+ }
151
+
152
+ async function handel(request, reply, endpoint){
153
+ const url = joinUrl(BASE_URL , endpoint);
154
+ const endpoints = [
155
+ '/v1/messages',
156
+ '/anthropic/v1/messages'
157
+ ];
158
+
159
+ let urlPath = (new URL(url)).pathname;
160
+
161
+ if(!(endpoints.some(t => urlPath.includes(t) && (request.method == "POST" || request.method == "post" )))){
162
+ logger.system.debug("不是模型请求直接返回" +request.method +":" + url +" -> " + urlPath);
163
+ // 将Fastify请求转换为标准的fetch请求
164
+ const fetchOptions = {
165
+ method: request.method,
166
+ headers: headersToObject(request.headers),
167
+ };
168
+
169
+ // 如果有请求体,添加到fetch选项中
170
+ if (request.body) {
171
+ fetchOptions.body = typeof request.body === 'string' ? request.body : JSON.stringify(request.body);
172
+ // 确保Content-Type头存在
173
+ if (!fetchOptions.headers['content-type'] && !fetchOptions.headers['Content-Type']) {
174
+ fetchOptions.headers['content-type'] = 'application/json';
175
+ }
176
+ }
177
+
178
+ return fetch(url, fetchOptions);
179
+ }
180
+
181
+ //console.log("请求地址: " + url);
182
+ //转换前的请求
183
+ let initBody = request.body;
184
+ //请求的 JSON
185
+ let requestBody = await anthropicTransformer.transformRequestOut(initBody);
186
+ //转换后的请求
187
+ let openaiRequestBodyString = JSON.stringify(requestBody);
188
+
189
+ //console.log(JSON.parse(openaiRequestBody));
190
+
191
+ //打印请求信息 init.body
192
+ const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
193
+ method: "POST",
194
+ headers: {
195
+ "Content-Type": "application/json",
196
+ Authorization: `Bearer ${process.env.ANTHROPIC_AUTH_TOKEN}`
197
+ },
198
+ body: openaiRequestBodyString,
199
+ });
200
+
201
+ let responseToClient = response.clone();
202
+
203
+ // 判断OpenRouter响应是否为异常
204
+ if (!response.ok) {
205
+ // 读取OpenRouter错误响应
206
+ const openrouterErrorText = await response.text();
207
+ logger.system.error(`OpenRouter API error response: ${response.status} ${response.statusText}`, {
208
+ url: url,
209
+ status: response.status,
210
+ errorResponse: openrouterErrorText
211
+ });
212
+
213
+ // 将OpenRouter错误响应转换为Claude Code错误响应格式
214
+ let claudeErrorResponse;
215
+ try {
216
+ const openrouterError = JSON.parse(openrouterErrorText);
217
+ claudeErrorResponse = {
218
+ type: "error",
219
+ error: {
220
+ type: "api_error",
221
+ message: openrouterError.error?.message || `OpenRouter API error: ${response.statusText}`,
222
+ code: `OPENROUTER_${response.status}_ERROR`
223
+ }
224
+ };
225
+ } catch (parseError) {
226
+ // 如果无法解析OpenRouter的错误JSON,使用通用错误格式
227
+ claudeErrorResponse = {
228
+ type: "error",
229
+ error: {
230
+ type: "api_error",
231
+ message: `OpenRouter API error: ${response.statusText}`,
232
+ code: `OPENROUTER_${response.status}_ERROR`
233
+ }
234
+ };
235
+ }
236
+
237
+ // 返回转换后的错误响应
238
+ return new Response(JSON.stringify(claudeErrorResponse), {
239
+ status: response.status,
240
+ statusText: response.statusText,
241
+ headers: {
242
+ "Content-Type": "application/json"
243
+ }
244
+ });
245
+ }
246
+
247
+ //完整的请求日志,保护请求和响应
248
+ let fullLog = {request:{
249
+ url:url,
250
+ method: init.method,
251
+ headers: headersToObject(init.headers),
252
+ body: initBody
253
+ },response:{
254
+ status: response.status,
255
+ statusText: response.statusText,
256
+ headers: headersToObject(response.headers)
257
+ },openai:{
258
+ request: {
259
+ body: requestBody
260
+ },
261
+ response: {}
262
+ }};
263
+
264
+ let res = await anthropicTransformer.transformResponseIn(responseToClient);
265
+ let toClientRes = await res.clone();
266
+
267
+ (async () => {
268
+
269
+ fullLog.openai.response.body = await parseOpenAIChatCompletion(await response.text());
270
+ fullLog.response.body = mergeAnthropic(await res.text());
271
+
272
+ //其他类型是错误的
273
+ logAPI(fullLog);
274
+
275
+ })().catch(err => console.error('日志解析错误:', err));
276
+
277
+ return toClientRes;
278
+
279
+ }
280
+
281
+ // 启动服务
282
+ const startServer = async () => {
283
+ try {
284
+ // 从环境变量获取端口,如果没有则动态分配
285
+ let port = process.env.PROXY_PORT ? parseInt(process.env.PROXY_PORT) : null;
286
+
287
+ if (!port) {
288
+ port = await portManager.getAvailablePort();
289
+ if (!port) {
290
+ logger.system.error('无法获取可用端口');
291
+ process.exit(1);
292
+ }
293
+ }
294
+
295
+ await fastify.listen({ port: port, host: "0.0.0.0" });
296
+ logger.system.debug(`✅ Server started on port ${port}`);
297
+
298
+ // 输出端口信息到标准输出,供父进程读取
299
+ console.log(`PROXY_PORT:${port}`);
300
+ } catch (err) {
301
+ fastify.log.error(err);
302
+ process.exit(1);
303
+ }
304
+ };
305
+ startServer();
@@ -0,0 +1,341 @@
1
+ // server.js
2
+ import Fastify from "fastify";
3
+ import {mergeAnthropic} from '../api-anthropic.js';
4
+ import LoggerManage from "../logger-manager.js";
5
+ import { join } from "path";
6
+ import { readFileSync } from "fs";
7
+ import portManager from '../port-manager.js';
8
+
9
+ let logger = LoggerManage.getLogger("claudecode");
10
+ const BASE_URL = process.env.BASE_URL;// || "https://api.anthropic.com";
11
+ logger.system.debug("-------------Clogger Start--------------------------");
12
+
13
+ // 配置文件相关功能
14
+ let toolsConfig = {
15
+ blacklist: [],
16
+ descriptions: {}
17
+ };
18
+
19
+ function loadToolsConfig() {
20
+ try {
21
+ const configPath = join(process.cwd(), '.tools.json');
22
+ const configContent = readFileSync(configPath, 'utf8');
23
+ const config = JSON.parse(configContent);
24
+
25
+ if (config.tools) {
26
+ toolsConfig.blacklist = config.tools.blacklist || [];
27
+ toolsConfig.descriptions = config.tools.descriptions || {};
28
+ logger.system.debug(`成功加载配置文件,黑名单: ${toolsConfig.blacklist.length} 个工具,描述重写: ${Object.keys(toolsConfig.descriptions).length} 个工具`);
29
+ }
30
+ } catch (error) {
31
+ if (error.code === 'ENOENT') {
32
+ logger.system.debug('未找到 .tools.json 配置文件,使用默认配置');
33
+ } else {
34
+ logger.system.error(`加载配置文件失败: ${error.message}`);
35
+ }
36
+ }
37
+ }
38
+
39
+ // 在启动时加载配置
40
+ loadToolsConfig();
41
+
42
+ function deepClone(obj) {
43
+ const result = JSON.parse(JSON.stringify(obj));
44
+ return result;
45
+ }
46
+
47
+ // 处理请求body中的tools,应用黑名单过滤和描述改写
48
+ function processRequestTools(body) {
49
+ if (!body || !body.tools || !Array.isArray(body.tools)) {
50
+ return body;
51
+ }
52
+
53
+ const originalCount = body.tools.length;
54
+
55
+ // 过滤黑名单中的工具
56
+ body.tools = body.tools.filter(tool => {
57
+ const toolName = tool.name || tool.function?.name;
58
+ if (!toolName) return true;
59
+
60
+ const isBlacklisted = toolsConfig.blacklist.includes(toolName);
61
+ if (isBlacklisted) {
62
+ logger.system.debug(`过滤黑名单工具: ${toolName}`);
63
+ }
64
+ return !isBlacklisted;
65
+ });
66
+
67
+ // 改写工具描述
68
+ body.tools = body.tools.map(tool => {
69
+ const toolName = tool.name || tool.function?.name;
70
+ if (toolName && toolsConfig.descriptions[toolName]) {
71
+ logger.system.debug(`改写工具描述: ${toolName} -> ${toolsConfig.descriptions[toolName]}`);
72
+
73
+ // 根据工具结构更新描述
74
+ if (tool.description !== undefined) {
75
+ tool.description = toolsConfig.descriptions[toolName];
76
+ } else if (tool.function && tool.function.description !== undefined) {
77
+ tool.function.description = toolsConfig.descriptions[toolName];
78
+ }
79
+ }
80
+ return tool;
81
+ });
82
+
83
+ const filteredCount = body.tools.length;
84
+ if (originalCount !== filteredCount) {
85
+ logger.system.debug(`工具过滤完成: ${originalCount} -> ${filteredCount}`);
86
+ }
87
+
88
+ return body;
89
+ }
90
+ function formateLine(str){
91
+ let r = str.replace(/\\n/g, '\n');
92
+ return r;
93
+ }
94
+ function toSimpleLog(fullLog){
95
+ //删除 tool 列表
96
+ let slog = deepClone(fullLog);
97
+ let result = {
98
+ request:slog.request.body.messages,
99
+ response:slog.response.body.content
100
+ };
101
+ return result;
102
+ }
103
+
104
+ function logAPI(fullLog){
105
+ //console.log('Writing to log files...');
106
+ try {
107
+ logger.full.debug(fullLog);
108
+ logger.simple.debug(toSimpleLog(fullLog));
109
+
110
+ // 立即同步到文件
111
+ if (logger.full.flush) {
112
+ logger.full.flush();
113
+ }
114
+ if (logger.simple.flush) {
115
+ logger.simple.flush();
116
+ }
117
+
118
+ // console.log('Log files written successfully');
119
+ } catch (error) {
120
+ console.error('Error writing to log files:', error);
121
+ }
122
+ }
123
+
124
+ function headersToObject(headers) {
125
+ const obj = {};
126
+ try {
127
+ for (const [k, v] of headers.entries()) obj[k] = v;
128
+ } catch {}
129
+ return obj;
130
+ }
131
+
132
+ const fastify = Fastify(
133
+ {
134
+ requestTimeout: 0, // never time out request (or set e.g. 10 * 60 * 1000)
135
+ connectionTimeout: 0, // no connection timeout
136
+ keepAliveTimeout: 120000, // 120s
137
+ }
138
+ );
139
+
140
+ // 注册一个 POST 接口
141
+ fastify.all("/*", async (request, reply) => {
142
+ //console.log("处理请求:", request.url);
143
+ return await handel(request, reply, request.url);
144
+ });
145
+ function joinUrl(base, ...paths) {
146
+ return [base.replace(/\/+$/, ''), ...paths.map(p => p.replace(/^\/+/, ''))]
147
+ .join('/');
148
+ }
149
+
150
+ async function handel(request, reply, endpoint){
151
+ const url = joinUrl(BASE_URL , endpoint);
152
+ const endpoints = [
153
+ '/v1/messages',
154
+ '/anthropic/v1/messages'
155
+ ];
156
+
157
+ let urlPath = (new URL(url)).pathname;
158
+
159
+ if(!(endpoints.some(t => urlPath.includes(t) && (request.method == "POST" || request.method == "post" )))){
160
+ logger.system.debug("不是模型请求直接返回" +request.method +":" + url +" -> " + urlPath);
161
+ // 将Fastify请求转换为标准的fetch请求
162
+ const fetchOptions = {
163
+ method: request.method,
164
+ headers: headersToObject(request.headers),
165
+ };
166
+
167
+ // 如果有请求体,添加到fetch选项中
168
+ if (request.body) {
169
+ fetchOptions.body = typeof request.body === 'string' ? request.body : JSON.stringify(request.body);
170
+ // 确保Content-Type头存在
171
+ if (!fetchOptions.headers['content-type'] && !fetchOptions.headers['Content-Type']) {
172
+ fetchOptions.headers['content-type'] = 'application/json';
173
+ }
174
+ }
175
+
176
+ return fetch(url, fetchOptions);
177
+ }
178
+
179
+ //打印请求信息 request.body
180
+ let processedBody = JSON.stringify(request.body);
181
+
182
+ logger.system.debug('请求 body' + processedBody);
183
+
184
+ // 如果是模型请求,处理请求body中的tools
185
+ if (request.body) {
186
+ try {
187
+ const processedBodyObj = processRequestTools(request.body);
188
+ processedBody = JSON.stringify(processedBodyObj);
189
+ //后面再改
190
+ if (processedBody !== request.body) {
191
+ logger.system.debug('请求body中的tools已被处理');
192
+ }
193
+ } catch (error) {
194
+ logger.system.error(`处理请求body失败: ${error.message}`);
195
+ // 如果处理失败,使用原始body
196
+ processedBody = request.body;
197
+ }
198
+ }
199
+ //如果对 tools 修改了这里的长度肯定要变化的
200
+ let requestHeaders = {...request.headers}
201
+ delete requestHeaders["content-length"]; //可能还有大小写问题
202
+ //console.log(requestHeaders);
203
+
204
+ let response;
205
+ try{
206
+ response = await fetch(url, {
207
+ method: request.method,
208
+ headers: requestHeaders,
209
+ body: processedBody,
210
+ });
211
+
212
+ }catch(error){
213
+ logger.system.error(`处理请求失败: ${error.message}`);
214
+ }
215
+
216
+ // 检查响应状态,处理错误情况
217
+ if (!response.ok) {
218
+ // 读取原始错误响应
219
+ const errorText = await response.text();
220
+ logger.system.error(`API error response: ${response.status} ${response.statusText}`, {
221
+ url: url,
222
+ status: response.status,
223
+ errorResponse: errorText
224
+ });
225
+
226
+ // 尝试解析错误响应并转换为通用错误格式
227
+ let errorResponse;
228
+ try {
229
+ const originalError = JSON.parse(errorText);
230
+ // 构建通用错误响应格式
231
+ errorResponse = {
232
+ type: "error",
233
+ error: {
234
+ type: "api_error",
235
+ message: originalError.error?.message || originalError.message || `API error: ${response.statusText}`,
236
+ code: originalError.error?.code || originalError.code || `API_${response.status}_ERROR`
237
+ }
238
+ };
239
+
240
+ // 如果有详细错误信息,保留原始结构
241
+ if (originalError.error?.details) {
242
+ errorResponse.error.details = originalError.error.details;
243
+ }
244
+
245
+ } catch (parseError) {
246
+ // 如果无法解析错误JSON,使用通用错误格式
247
+ logger.system.error(`Failed to parse error response: ${parseError.message}`);
248
+ errorResponse = {
249
+ type: "error",
250
+ error: {
251
+ type: "api_error",
252
+ message: `API error: ${response.statusText}`,
253
+ code: `API_${response.status}_ERROR`
254
+ }
255
+ };
256
+ }
257
+
258
+ // 返回标准化的错误响应
259
+ return new Response(JSON.stringify(errorResponse), {
260
+ status: response.status,
261
+ statusText: response.statusText,
262
+ headers: {
263
+ "Content-Type": "application/json"
264
+ }
265
+ });
266
+ }
267
+
268
+ //response = proxyResponse(response);
269
+ let responseToClient = response.clone()
270
+ // stream 不能通过 content type 判断,
271
+ let isStream = true;
272
+ if(Object.hasOwn(request.body, "stream") && !request.body.stream){
273
+ isStream = false;
274
+ logger.full.debug("模型不是流请求");
275
+ }
276
+
277
+
278
+ //完整的请求日志,保护请求和响应
279
+ let fullLog = {request:{
280
+ url:url,
281
+ method: request.method,
282
+ headers: headersToObject(request.headers),
283
+ body: JSON.parse(processedBody)
284
+ },response:{
285
+ status: response.status,
286
+ statusText: response.statusText,
287
+ headers: headersToObject(response.headers)
288
+ }};
289
+
290
+ try{
291
+ //日志解析要异步执行保证效率
292
+ (async ()=>{
293
+ if(isStream){
294
+ let alllog = await response.text();
295
+ //logger.full.debug("alllog "+alllog)
296
+ fullLog.response.body = mergeAnthropic(alllog);
297
+ }else{
298
+ fullLog.response.body = await response.json();
299
+ }
300
+
301
+ logAPI(fullLog);
302
+
303
+ })().catch(err => logger.system.error('日志解析错误:' + "\nStack trace: " + err.stack));
304
+
305
+
306
+
307
+ return new Response(responseToClient.body, {
308
+ status: response.status,
309
+ statusText: response.statusText,
310
+ headers: response.headers
311
+ });
312
+ }catch(e){
313
+ logger.system.error(e);
314
+ }
315
+ }
316
+
317
+ // 启动服务
318
+ const startServer = async () => {
319
+ try {
320
+ // 从环境变量获取端口,如果没有则动态分配
321
+ let port = process.env.PROXY_PORT ? parseInt(process.env.PROXY_PORT) : null;
322
+
323
+ if (!port) {
324
+ port = await portManager.getAvailablePort();
325
+ if (!port) {
326
+ logger.system.error('无法获取可用端口');
327
+ process.exit(1);
328
+ }
329
+ }
330
+
331
+ await fastify.listen({ port: port, host: "0.0.0.0" });
332
+ logger.system.debug(`✅ Server started on port ${port}`);
333
+
334
+ // 输出端口信息到标准输出,供父进程读取
335
+ console.log(`PROXY_PORT:${port}`);
336
+ } catch (err) {
337
+ fastify.log.error(err);
338
+ process.exit(1);
339
+ }
340
+ };
341
+ startServer();