aicodeswitch 1.6.2 → 1.7.0
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/CLAUDE.md +1 -2
- package/README.md +4 -0
- package/dist/server/proxy-server.js +76 -135
- package/dist/server/transformers/claude-openai.js +46 -1
- package/dist/server/transformers/streaming.js +65 -410
- package/dist/ui/assets/{index-BwrBKpaK.js → index-DGODOljs.js} +50 -39
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/server/transformers/openai-responses.js +0 -392
package/CLAUDE.md
CHANGED
|
@@ -92,14 +92,13 @@ aicos version # Show current version information
|
|
|
92
92
|
#### 2. Proxy Server - `server/proxy-server.ts`
|
|
93
93
|
- **Route Matching**: Finds active route based on target type (claude-code/codex)
|
|
94
94
|
- **Rule Matching**: Determines content type from request (image-understanding/thinking/long-context/background/default)
|
|
95
|
-
- **Request Transformation**: Converts between different API formats (Claude ↔ OpenAI
|
|
95
|
+
- **Request Transformation**: Converts between different API formats (Claude ↔ OpenAI Chat)
|
|
96
96
|
- **Streaming**: Handles SSE (Server-Sent Events) streaming responses with real-time transformation
|
|
97
97
|
- **Logging**: Tracks requests, responses, and errors
|
|
98
98
|
|
|
99
99
|
#### 3. Transformers - `server/transformers/`
|
|
100
100
|
- **streaming.ts**: SSE parsing/serialization and event transformation
|
|
101
101
|
- **claude-openai.ts**: Claude ↔ OpenAI Chat format conversion
|
|
102
|
-
- **openai-responses.ts**: OpenAI Responses format conversion
|
|
103
102
|
- **chunk-collector.ts**: Collects streaming chunks for logging
|
|
104
103
|
|
|
105
104
|
#### 4. Database - `server/database.ts`
|
package/README.md
CHANGED
|
@@ -199,6 +199,10 @@ PORT=4567
|
|
|
199
199
|
* [indb](https://github.com/tangshuang/indb): 网页端轻量kv数据库操作库
|
|
200
200
|
* [Formast](https://github.com/tangshuang/formast): 复杂业务场景下的企业级JSON驱动表单框架
|
|
201
201
|
|
|
202
|
+
## 关联资源
|
|
203
|
+
|
|
204
|
+
* [Claude Code 深度教程](https://claudecode.tangshuang.net): 100%免费的Claude Code入门到精通教程
|
|
205
|
+
|
|
202
206
|
## 支持我
|
|
203
207
|
|
|
204
208
|

|
|
@@ -18,7 +18,6 @@ const stream_1 = require("stream");
|
|
|
18
18
|
const streaming_1 = require("./transformers/streaming");
|
|
19
19
|
const chunk_collector_1 = require("./transformers/chunk-collector");
|
|
20
20
|
const claude_openai_1 = require("./transformers/claude-openai");
|
|
21
|
-
const openai_responses_1 = require("./transformers/openai-responses");
|
|
22
21
|
const SUPPORTED_TARGETS = ['claude-code', 'codex'];
|
|
23
22
|
class ProxyServer {
|
|
24
23
|
constructor(dbManager, app) {
|
|
@@ -34,6 +33,8 @@ class ProxyServer {
|
|
|
34
33
|
writable: true,
|
|
35
34
|
value: void 0
|
|
36
35
|
});
|
|
36
|
+
// 以下字段用于缓存备份(将来可能用于性能优化)
|
|
37
|
+
// 实际使用时,所有配置都从数据库实时读取
|
|
37
38
|
Object.defineProperty(this, "routes", {
|
|
38
39
|
enumerable: true,
|
|
39
40
|
configurable: true,
|
|
@@ -161,7 +162,7 @@ class ProxyServer {
|
|
|
161
162
|
if (!rule) {
|
|
162
163
|
return res.status(404).json({ error: 'No matching rule found' });
|
|
163
164
|
}
|
|
164
|
-
const service = this.
|
|
165
|
+
const service = this.getServiceById(rule.targetServiceId);
|
|
165
166
|
if (!service) {
|
|
166
167
|
return res.status(500).json({ error: 'Target service not configured' });
|
|
167
168
|
}
|
|
@@ -176,7 +177,7 @@ class ProxyServer {
|
|
|
176
177
|
// 尝试每个规则,直到成功或全部失败
|
|
177
178
|
let lastError = null;
|
|
178
179
|
for (const rule of allRules) {
|
|
179
|
-
const service = this.
|
|
180
|
+
const service = this.getServiceById(rule.targetServiceId);
|
|
180
181
|
if (!service)
|
|
181
182
|
continue;
|
|
182
183
|
// 检查黑名单
|
|
@@ -283,7 +284,7 @@ class ProxyServer {
|
|
|
283
284
|
if (!rule) {
|
|
284
285
|
return res.status(404).json({ error: 'No matching rule found' });
|
|
285
286
|
}
|
|
286
|
-
const service = this.
|
|
287
|
+
const service = this.getServiceById(rule.targetServiceId);
|
|
287
288
|
if (!service) {
|
|
288
289
|
return res.status(500).json({ error: 'Target service not configured' });
|
|
289
290
|
}
|
|
@@ -298,7 +299,7 @@ class ProxyServer {
|
|
|
298
299
|
// 尝试每个规则,直到成功或全部失败
|
|
299
300
|
let lastError = null;
|
|
300
301
|
for (const rule of allRules) {
|
|
301
|
-
const service = this.
|
|
302
|
+
const service = this.getServiceById(rule.targetServiceId);
|
|
302
303
|
if (!service)
|
|
303
304
|
continue;
|
|
304
305
|
// 检查黑名单
|
|
@@ -381,18 +382,45 @@ class ProxyServer {
|
|
|
381
382
|
}
|
|
382
383
|
});
|
|
383
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* 从数据库实时获取所有活跃路由
|
|
387
|
+
* @returns 活跃路由列表
|
|
388
|
+
*/
|
|
389
|
+
getActiveRoutes() {
|
|
390
|
+
return this.dbManager.getRoutes().filter(route => route.isActive);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* 从数据库实时获取指定路由的规则
|
|
394
|
+
* @param routeId 路由ID
|
|
395
|
+
* @returns 规则列表(按 sortOrder 降序排序)
|
|
396
|
+
*/
|
|
397
|
+
getRulesByRouteId(routeId) {
|
|
398
|
+
const routeRules = this.dbManager.getRules(routeId);
|
|
399
|
+
return routeRules.sort((a, b) => (b.sortOrder || 0) - (a.sortOrder || 0));
|
|
400
|
+
}
|
|
384
401
|
findMatchingRoute(_req) {
|
|
385
402
|
// Find active route based on targetType - for now, return the first active route
|
|
386
403
|
// This can be extended later based on specific routing logic
|
|
387
|
-
|
|
404
|
+
const activeRoutes = this.getActiveRoutes();
|
|
405
|
+
return activeRoutes.find(route => route.isActive);
|
|
388
406
|
}
|
|
389
407
|
findRouteByTargetType(targetType) {
|
|
390
|
-
|
|
408
|
+
const activeRoutes = this.getActiveRoutes();
|
|
409
|
+
return activeRoutes.find(route => route.targetType === targetType && route.isActive);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* 从数据库实时获取服务配置
|
|
413
|
+
* @param serviceId 服务ID
|
|
414
|
+
* @returns 服务配置,如果不存在则返回 undefined
|
|
415
|
+
*/
|
|
416
|
+
getServiceById(serviceId) {
|
|
417
|
+
const allServices = this.dbManager.getAPIServices();
|
|
418
|
+
return allServices.find(service => service.id === serviceId);
|
|
391
419
|
}
|
|
392
420
|
findMatchingRule(routeId, req) {
|
|
393
421
|
return __awaiter(this, void 0, void 0, function* () {
|
|
394
|
-
const rules = this.
|
|
395
|
-
if (!rules)
|
|
422
|
+
const rules = this.getRulesByRouteId(routeId);
|
|
423
|
+
if (!rules || rules.length === 0)
|
|
396
424
|
return undefined;
|
|
397
425
|
const body = req.body;
|
|
398
426
|
const requestModel = body === null || body === void 0 ? void 0 : body.model;
|
|
@@ -432,8 +460,8 @@ class ProxyServer {
|
|
|
432
460
|
});
|
|
433
461
|
}
|
|
434
462
|
getAllMatchingRules(routeId, req) {
|
|
435
|
-
const rules = this.
|
|
436
|
-
if (!rules)
|
|
463
|
+
const rules = this.getRulesByRouteId(routeId);
|
|
464
|
+
if (!rules || rules.length === 0)
|
|
437
465
|
return [];
|
|
438
466
|
const body = req.body;
|
|
439
467
|
const requestModel = body === null || body === void 0 ? void 0 : body.model;
|
|
@@ -693,13 +721,10 @@ class ProxyServer {
|
|
|
693
721
|
return length;
|
|
694
722
|
}
|
|
695
723
|
isClaudeSource(sourceType) {
|
|
696
|
-
return sourceType === 'claude-chat'
|
|
724
|
+
return sourceType === 'claude-chat';
|
|
697
725
|
}
|
|
698
726
|
isOpenAIChatSource(sourceType) {
|
|
699
|
-
return sourceType === 'openai-chat' || sourceType === '
|
|
700
|
-
}
|
|
701
|
-
isOpenAIResponsesSource(sourceType) {
|
|
702
|
-
return sourceType === 'openai-responses';
|
|
727
|
+
return sourceType === 'openai-chat' || sourceType === 'deepseek-chat';
|
|
703
728
|
}
|
|
704
729
|
applyModelOverride(body, rule) {
|
|
705
730
|
// 如果 targetModel 为空或不存在,保留原始 model(透传)
|
|
@@ -803,9 +828,6 @@ class ProxyServer {
|
|
|
803
828
|
extractTokenUsage(usage) {
|
|
804
829
|
if (!usage)
|
|
805
830
|
return undefined;
|
|
806
|
-
if (typeof usage.input_tokens === 'number' && typeof usage.output_tokens === 'number' && usage.prompt_tokens === undefined) {
|
|
807
|
-
return (0, openai_responses_1.extractTokenUsageFromOpenAIResponsesUsage)(usage);
|
|
808
|
-
}
|
|
809
831
|
if (typeof usage.prompt_tokens === 'number' || typeof usage.completion_tokens === 'number') {
|
|
810
832
|
return (0, claude_openai_1.extractTokenUsageFromOpenAIUsage)(usage);
|
|
811
833
|
}
|
|
@@ -833,25 +855,17 @@ class ProxyServer {
|
|
|
833
855
|
// Claude → OpenAI Chat: /v1/messages → /v1/chat/completions
|
|
834
856
|
return originalPath.replace(/\/v1\/messages\b/, '/v1/chat/completions');
|
|
835
857
|
}
|
|
836
|
-
else if (this.isOpenAIResponsesSource(targetSourceType)) {
|
|
837
|
-
// Claude → OpenAI Responses: /v1/messages → /v1/responses/completions
|
|
838
|
-
return originalPath.replace(/\/v1\/messages\b/, '/v1/responses/completions');
|
|
839
|
-
}
|
|
840
858
|
}
|
|
841
859
|
// Codex 发起的请求
|
|
842
860
|
if (sourceTool === 'codex') {
|
|
843
|
-
// Codex 默认使用 OpenAI
|
|
844
|
-
if (this.
|
|
845
|
-
// OpenAI
|
|
861
|
+
// Codex 默认使用 OpenAI Chat API 格式
|
|
862
|
+
if (this.isOpenAIChatSource(targetSourceType)) {
|
|
863
|
+
// OpenAI Chat → OpenAI Chat: 直接透传路径
|
|
846
864
|
return originalPath;
|
|
847
865
|
}
|
|
848
|
-
else if (this.isOpenAIChatSource(targetSourceType)) {
|
|
849
|
-
// OpenAI Responses → OpenAI Chat: /v1/responses/completions → /v1/chat/completions
|
|
850
|
-
return originalPath.replace(/\/v1\/responses\/completions\b/, '/v1/chat/completions');
|
|
851
|
-
}
|
|
852
866
|
else if (this.isClaudeSource(targetSourceType)) {
|
|
853
|
-
// OpenAI
|
|
854
|
-
return originalPath.replace(/\/v1\/
|
|
867
|
+
// OpenAI Chat → Claude: /v1/chat/completions → /v1/messages
|
|
868
|
+
return originalPath.replace(/\/v1\/chat\/completions\b/, '/v1/messages');
|
|
855
869
|
}
|
|
856
870
|
}
|
|
857
871
|
// 默认:直接返回原始路径
|
|
@@ -915,9 +929,6 @@ class ProxyServer {
|
|
|
915
929
|
else if (this.isOpenAIChatSource(sourceType)) {
|
|
916
930
|
requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
|
|
917
931
|
}
|
|
918
|
-
else if (this.isOpenAIResponsesSource(sourceType)) {
|
|
919
|
-
requestBody = (0, openai_responses_1.transformClaudeRequestToOpenAIResponses)(requestBody, rule.targetModel);
|
|
920
|
-
}
|
|
921
932
|
else {
|
|
922
933
|
res.status(400).json({ error: 'Unsupported source type for Claude Code.' });
|
|
923
934
|
yield finalizeLog(400, 'Unsupported source type for Claude Code');
|
|
@@ -925,17 +936,14 @@ class ProxyServer {
|
|
|
925
936
|
}
|
|
926
937
|
}
|
|
927
938
|
else if (targetType === 'codex') {
|
|
928
|
-
if (this.
|
|
939
|
+
if (this.isOpenAIChatSource(sourceType)) {
|
|
929
940
|
requestBody = this.applyModelOverride(requestBody, rule);
|
|
930
941
|
}
|
|
931
|
-
else if (this.isOpenAIChatSource(sourceType)) {
|
|
932
|
-
requestBody = (0, openai_responses_1.transformOpenAIResponsesRequestToOpenAIChat)(requestBody, rule.targetModel);
|
|
933
|
-
}
|
|
934
942
|
else if (this.isClaudeSource(sourceType)) {
|
|
935
|
-
requestBody = (0,
|
|
943
|
+
requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
|
|
936
944
|
}
|
|
937
945
|
else {
|
|
938
|
-
res.status(400).json({ error: '
|
|
946
|
+
res.status(400).json({ error: 'Unsupported source type for Codex.' });
|
|
939
947
|
yield finalizeLog(400, 'Unsupported source type for Codex');
|
|
940
948
|
return;
|
|
941
949
|
}
|
|
@@ -955,7 +963,7 @@ class ProxyServer {
|
|
|
955
963
|
method: req.method,
|
|
956
964
|
url: `${service.apiUrl}${mappedPath}`,
|
|
957
965
|
headers: this.buildUpstreamHeaders(req, service, sourceType, streamRequested),
|
|
958
|
-
timeout: service.timeout ||
|
|
966
|
+
timeout: service.timeout || 3000000, // 默认300秒
|
|
959
967
|
validateStatus: () => true,
|
|
960
968
|
responseType: streamRequested ? 'stream' : 'json',
|
|
961
969
|
};
|
|
@@ -1009,50 +1017,19 @@ class ProxyServer {
|
|
|
1009
1017
|
});
|
|
1010
1018
|
return;
|
|
1011
1019
|
}
|
|
1012
|
-
if (targetType === 'claude-code' && this.isOpenAIResponsesSource(sourceType)) {
|
|
1013
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
1014
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
1015
|
-
res.setHeader('Connection', 'keep-alive');
|
|
1016
|
-
const parser = new streaming_1.SSEParserTransform();
|
|
1017
|
-
const eventCollector = new chunk_collector_1.SSEEventCollectorTransform();
|
|
1018
|
-
const converter = new streaming_1.OpenAIResponsesToClaudeEventTransform({ model: requestBody === null || requestBody === void 0 ? void 0 : requestBody.model });
|
|
1019
|
-
const serializer = new streaming_1.SSESerializerTransform();
|
|
1020
|
-
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
1021
|
-
res.on('finish', () => {
|
|
1022
|
-
const usage = converter.getUsage();
|
|
1023
|
-
if (usage) {
|
|
1024
|
-
usageForLog = (0, claude_openai_1.extractTokenUsageFromClaudeUsage)(usage);
|
|
1025
|
-
}
|
|
1026
|
-
else {
|
|
1027
|
-
// 尝试从event collector中提取usage
|
|
1028
|
-
const extractedUsage = eventCollector.extractUsage();
|
|
1029
|
-
if (extractedUsage) {
|
|
1030
|
-
usageForLog = this.extractTokenUsage(extractedUsage);
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
streamChunksForLog = eventCollector.getChunks();
|
|
1034
|
-
void finalizeLog(res.statusCode);
|
|
1035
|
-
});
|
|
1036
|
-
(0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, res, (error) => {
|
|
1037
|
-
if (error) {
|
|
1038
|
-
void finalizeLog(500, error.message);
|
|
1039
|
-
}
|
|
1040
|
-
});
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
1020
|
if (targetType === 'codex' && this.isClaudeSource(sourceType)) {
|
|
1044
1021
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
1045
1022
|
res.setHeader('Cache-Control', 'no-cache');
|
|
1046
1023
|
res.setHeader('Connection', 'keep-alive');
|
|
1047
1024
|
const parser = new streaming_1.SSEParserTransform();
|
|
1048
1025
|
const eventCollector = new chunk_collector_1.SSEEventCollectorTransform();
|
|
1049
|
-
const converter = new streaming_1.
|
|
1026
|
+
const converter = new streaming_1.ClaudeToOpenAIChatEventTransform({ model: requestBody === null || requestBody === void 0 ? void 0 : requestBody.model });
|
|
1050
1027
|
const serializer = new streaming_1.SSESerializerTransform();
|
|
1051
1028
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
1052
1029
|
res.on('finish', () => {
|
|
1053
1030
|
const usage = converter.getUsage();
|
|
1054
1031
|
if (usage) {
|
|
1055
|
-
usageForLog = (0, claude_openai_1.
|
|
1032
|
+
usageForLog = (0, claude_openai_1.extractTokenUsageFromOpenAIUsage)(usage);
|
|
1056
1033
|
}
|
|
1057
1034
|
else {
|
|
1058
1035
|
// 尝试从event collector中提取usage
|
|
@@ -1071,38 +1048,6 @@ class ProxyServer {
|
|
|
1071
1048
|
});
|
|
1072
1049
|
return;
|
|
1073
1050
|
}
|
|
1074
|
-
if (targetType === 'codex' && this.isOpenAIChatSource(sourceType)) {
|
|
1075
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
1076
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
1077
|
-
res.setHeader('Connection', 'keep-alive');
|
|
1078
|
-
const parser = new streaming_1.SSEParserTransform();
|
|
1079
|
-
const eventCollector = new chunk_collector_1.SSEEventCollectorTransform();
|
|
1080
|
-
const toClaude = new streaming_1.OpenAIToClaudeEventTransform({ model: requestBody === null || requestBody === void 0 ? void 0 : requestBody.model });
|
|
1081
|
-
const toResponses = new streaming_1.ClaudeToOpenAIResponsesEventTransform({ model: requestBody === null || requestBody === void 0 ? void 0 : requestBody.model });
|
|
1082
|
-
const serializer = new streaming_1.SSESerializerTransform();
|
|
1083
|
-
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
1084
|
-
res.on('finish', () => {
|
|
1085
|
-
const usage = toResponses.getUsage();
|
|
1086
|
-
if (usage) {
|
|
1087
|
-
usageForLog = (0, claude_openai_1.extractTokenUsageFromClaudeUsage)(usage);
|
|
1088
|
-
}
|
|
1089
|
-
else {
|
|
1090
|
-
// 尝试从event collector中提取usage
|
|
1091
|
-
const extractedUsage = eventCollector.extractUsage();
|
|
1092
|
-
if (extractedUsage) {
|
|
1093
|
-
usageForLog = this.extractTokenUsage(extractedUsage);
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
streamChunksForLog = eventCollector.getChunks();
|
|
1097
|
-
void finalizeLog(res.statusCode);
|
|
1098
|
-
});
|
|
1099
|
-
(0, stream_1.pipeline)(response.data, parser, eventCollector, toClaude, toResponses, serializer, res, (error) => {
|
|
1100
|
-
if (error) {
|
|
1101
|
-
void finalizeLog(500, error.message);
|
|
1102
|
-
}
|
|
1103
|
-
});
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
1051
|
// 默认stream处理(无转换)
|
|
1107
1052
|
const eventCollector = new chunk_collector_1.SSEEventCollectorTransform();
|
|
1108
1053
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
@@ -1151,25 +1096,12 @@ class ProxyServer {
|
|
|
1151
1096
|
responseBodyForLog = JSON.stringify(converted);
|
|
1152
1097
|
res.status(response.status).json(converted);
|
|
1153
1098
|
}
|
|
1154
|
-
else if (targetType === 'claude-code' && this.isOpenAIResponsesSource(sourceType)) {
|
|
1155
|
-
const converted = (0, openai_responses_1.transformOpenAIResponsesToClaude)(responseData);
|
|
1156
|
-
usageForLog = (0, openai_responses_1.extractTokenUsageFromOpenAIResponsesUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usage);
|
|
1157
|
-
responseBodyForLog = JSON.stringify(converted);
|
|
1158
|
-
res.status(response.status).json(converted);
|
|
1159
|
-
}
|
|
1160
1099
|
else if (targetType === 'codex' && this.isClaudeSource(sourceType)) {
|
|
1161
|
-
const converted = (0,
|
|
1100
|
+
const converted = (0, claude_openai_1.transformClaudeResponseToOpenAIChat)(responseData);
|
|
1162
1101
|
usageForLog = (0, claude_openai_1.extractTokenUsageFromClaudeUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usage);
|
|
1163
1102
|
responseBodyForLog = JSON.stringify(converted);
|
|
1164
1103
|
res.status(response.status).json(converted);
|
|
1165
1104
|
}
|
|
1166
|
-
else if (targetType === 'codex' && this.isOpenAIChatSource(sourceType)) {
|
|
1167
|
-
const claudeResponse = (0, claude_openai_1.transformOpenAIChatResponseToClaude)(responseData);
|
|
1168
|
-
const converted = (0, openai_responses_1.transformClaudeResponseToOpenAIResponses)(claudeResponse);
|
|
1169
|
-
usageForLog = (0, claude_openai_1.extractTokenUsageFromOpenAIUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usage);
|
|
1170
|
-
responseBodyForLog = JSON.stringify(converted);
|
|
1171
|
-
res.status(response.status).json(converted);
|
|
1172
|
-
}
|
|
1173
1105
|
else {
|
|
1174
1106
|
usageForLog = this.extractTokenUsage(responseData === null || responseData === void 0 ? void 0 : responseData.usage);
|
|
1175
1107
|
// 记录原始响应体
|
|
@@ -1211,21 +1143,30 @@ class ProxyServer {
|
|
|
1211
1143
|
}
|
|
1212
1144
|
reloadRoutes() {
|
|
1213
1145
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
const sortedRules = [...routeRules].sort((a, b) => (b.sortOrder || 0) - (a.sortOrder || 0));
|
|
1220
|
-
this.rules.set(route.id, sortedRules);
|
|
1221
|
-
}
|
|
1222
|
-
// Load all services
|
|
1146
|
+
// 注意:所有配置(路由、规则、服务)现在都在每次请求时实时从数据库读取
|
|
1147
|
+
// 这个方法主要用于初始化和日志记录
|
|
1148
|
+
// 修改数据库后无需调用此方法,配置会自动生效
|
|
1149
|
+
const allRoutes = this.dbManager.getRoutes();
|
|
1150
|
+
const activeRoutes = allRoutes.filter((g) => g.isActive);
|
|
1223
1151
|
const allServices = this.dbManager.getAPIServices();
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1152
|
+
// 保留缓存以备将来可能的性能优化需求
|
|
1153
|
+
this.routes = activeRoutes;
|
|
1154
|
+
if (this.rules) {
|
|
1155
|
+
this.rules.clear();
|
|
1156
|
+
for (const route of activeRoutes) {
|
|
1157
|
+
const routeRules = this.dbManager.getRules(route.id);
|
|
1158
|
+
const sortedRules = [...routeRules].sort((a, b) => (b.sortOrder || 0) - (a.sortOrder || 0));
|
|
1159
|
+
this.rules.set(route.id, sortedRules);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (this.services) {
|
|
1163
|
+
const services = this.services;
|
|
1164
|
+
services.clear();
|
|
1165
|
+
allServices.forEach((service) => {
|
|
1166
|
+
services.set(service.id, service);
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
console.log(`Initialized with ${activeRoutes.length} active routes and ${allServices.length} services (all config read from database in real-time)`);
|
|
1229
1170
|
});
|
|
1230
1171
|
}
|
|
1231
1172
|
updateConfig(config) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractTokenUsageFromClaudeUsage = exports.extractTokenUsageFromOpenAIUsage = exports.transformOpenAIChatResponseToClaude = exports.transformClaudeRequestToOpenAIChat = exports.mapStopReason = exports.convertOpenAIUsageToClaude = void 0;
|
|
3
|
+
exports.extractTokenUsageFromClaudeUsage = exports.extractTokenUsageFromOpenAIUsage = exports.transformClaudeResponseToOpenAIChat = exports.transformOpenAIChatResponseToClaude = exports.transformClaudeRequestToOpenAIChat = exports.mapStopReason = exports.convertOpenAIUsageToClaude = void 0;
|
|
4
4
|
const toTextContent = (content) => {
|
|
5
5
|
if (typeof content === 'string')
|
|
6
6
|
return content;
|
|
@@ -203,6 +203,51 @@ const transformOpenAIChatResponseToClaude = (body) => {
|
|
|
203
203
|
};
|
|
204
204
|
};
|
|
205
205
|
exports.transformOpenAIChatResponseToClaude = transformOpenAIChatResponseToClaude;
|
|
206
|
+
const transformClaudeResponseToOpenAIChat = (body) => {
|
|
207
|
+
const content = (body === null || body === void 0 ? void 0 : body.content) || [];
|
|
208
|
+
let textContent = '';
|
|
209
|
+
const toolCalls = [];
|
|
210
|
+
for (const block of content) {
|
|
211
|
+
if ((block === null || block === void 0 ? void 0 : block.type) === 'text') {
|
|
212
|
+
textContent += block.text || '';
|
|
213
|
+
}
|
|
214
|
+
else if ((block === null || block === void 0 ? void 0 : block.type) === 'tool_use') {
|
|
215
|
+
toolCalls.push({
|
|
216
|
+
id: block.id,
|
|
217
|
+
type: 'function',
|
|
218
|
+
function: {
|
|
219
|
+
name: block.name || 'tool',
|
|
220
|
+
arguments: typeof block.input === 'string' ? block.input : JSON.stringify(block.input || {}),
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const message = {
|
|
226
|
+
role: 'assistant',
|
|
227
|
+
content: textContent,
|
|
228
|
+
};
|
|
229
|
+
if (toolCalls.length > 0) {
|
|
230
|
+
message.tool_calls = toolCalls;
|
|
231
|
+
}
|
|
232
|
+
const usage = (body === null || body === void 0 ? void 0 : body.usage) ? {
|
|
233
|
+
prompt_tokens: body.usage.input_tokens || 0,
|
|
234
|
+
completion_tokens: body.usage.output_tokens || 0,
|
|
235
|
+
total_tokens: (body.usage.input_tokens || 0) + (body.usage.output_tokens || 0),
|
|
236
|
+
} : undefined;
|
|
237
|
+
return {
|
|
238
|
+
id: body === null || body === void 0 ? void 0 : body.id,
|
|
239
|
+
object: 'chat.completion',
|
|
240
|
+
created: Math.floor(Date.now() / 1000),
|
|
241
|
+
model: body === null || body === void 0 ? void 0 : body.model,
|
|
242
|
+
choices: [{
|
|
243
|
+
index: 0,
|
|
244
|
+
message,
|
|
245
|
+
finish_reason: (0, exports.mapStopReason)(body === null || body === void 0 ? void 0 : body.stop_reason),
|
|
246
|
+
}],
|
|
247
|
+
usage,
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
exports.transformClaudeResponseToOpenAIChat = transformClaudeResponseToOpenAIChat;
|
|
206
251
|
const extractTokenUsageFromOpenAIUsage = (usage) => {
|
|
207
252
|
if (!usage)
|
|
208
253
|
return undefined;
|