aicodeswitch 4.0.0 → 4.0.2
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/dist/server/auth.js +12 -6
- package/dist/server/fs-database.js +240 -6
- package/dist/server/main.js +28 -10
- package/dist/server/proxy-server.js +126 -63
- package/dist/server/rules-status-service.js +20 -137
- package/dist/server/transformers/chunk-collector.js +38 -5
- package/dist/server/transformers/streaming.js +47 -29
- package/dist/server/transformers/transformers.js +20 -18
- package/dist/server/version-check.js +3 -2
- package/dist/ui/assets/index-BRHavnvn.js +514 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-GQBwe1Rm.js +0 -514
|
@@ -2175,7 +2175,7 @@ class ProxyServer {
|
|
|
2175
2175
|
}
|
|
2176
2176
|
}
|
|
2177
2177
|
if (!headers['content-type']) {
|
|
2178
|
-
headers['content-type'] = 'application/json';
|
|
2178
|
+
headers['content-type'] = 'application/json; charset=utf-8';
|
|
2179
2179
|
}
|
|
2180
2180
|
// 添加 content-length(对于有请求体的方法)
|
|
2181
2181
|
if (requestBody && ['POST', 'PUT', 'PATCH'].includes(req.method.toUpperCase())) {
|
|
@@ -2260,11 +2260,14 @@ class ProxyServer {
|
|
|
2260
2260
|
readStreamBody(stream) {
|
|
2261
2261
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2262
2262
|
return new Promise((resolve, reject) => {
|
|
2263
|
-
|
|
2263
|
+
const chunks = [];
|
|
2264
2264
|
stream.on('data', (chunk) => {
|
|
2265
|
-
|
|
2265
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
2266
|
+
});
|
|
2267
|
+
stream.on('end', () => {
|
|
2268
|
+
const fullBuffer = Buffer.concat(chunks);
|
|
2269
|
+
resolve(fullBuffer.toString('utf8'));
|
|
2266
2270
|
});
|
|
2267
|
-
stream.on('end', () => resolve(data));
|
|
2268
2271
|
stream.on('error', reject);
|
|
2269
2272
|
});
|
|
2270
2273
|
});
|
|
@@ -2286,7 +2289,8 @@ class ProxyServer {
|
|
|
2286
2289
|
var _a, _b;
|
|
2287
2290
|
if (type === 'claude-code') {
|
|
2288
2291
|
// Claude Code 使用 metadata.user_id
|
|
2289
|
-
|
|
2292
|
+
const rawUserId = (_b = (_a = request.body) === null || _a === void 0 ? void 0 : _a.metadata) === null || _b === void 0 ? void 0 : _b.user_id;
|
|
2293
|
+
return ProxyServer.extractSessionIdFromUserId(rawUserId);
|
|
2290
2294
|
}
|
|
2291
2295
|
else if (type === 'codex') {
|
|
2292
2296
|
// Codex 使用 headers.session_id
|
|
@@ -2300,6 +2304,25 @@ class ProxyServer {
|
|
|
2300
2304
|
}
|
|
2301
2305
|
return null;
|
|
2302
2306
|
}
|
|
2307
|
+
/**
|
|
2308
|
+
* 从 metadata.user_id 中提取 session ID
|
|
2309
|
+
* 新版本格式: JSON 字符串 {"device_id":"...","account_uuid":"...","session_id":"..."}
|
|
2310
|
+
* 旧版本格式: 纯字符串 session ID
|
|
2311
|
+
*/
|
|
2312
|
+
static extractSessionIdFromUserId(rawUserId) {
|
|
2313
|
+
if (!rawUserId || typeof rawUserId !== 'string')
|
|
2314
|
+
return null;
|
|
2315
|
+
try {
|
|
2316
|
+
const parsed = JSON.parse(rawUserId);
|
|
2317
|
+
if (parsed && typeof parsed === 'object' && parsed.session_id) {
|
|
2318
|
+
return parsed.session_id;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
catch (_a) {
|
|
2322
|
+
// 不是 JSON,按旧版本纯字符串处理
|
|
2323
|
+
}
|
|
2324
|
+
return rawUserId;
|
|
2325
|
+
}
|
|
2303
2326
|
/**
|
|
2304
2327
|
* 提取会话标题(默认方法)
|
|
2305
2328
|
* 对于新会话,尝试从第一条消息的内容中提取标题
|
|
@@ -2307,44 +2330,75 @@ class ProxyServer {
|
|
|
2307
2330
|
* 对于结构化内容(数组),从最后一个元素取值
|
|
2308
2331
|
*/
|
|
2309
2332
|
defaultExtractSessionTitle(request, sessionId) {
|
|
2310
|
-
var _a;
|
|
2333
|
+
var _a, _b;
|
|
2311
2334
|
const existingSession = this.dbManager.getSession(sessionId);
|
|
2312
2335
|
if (existingSession) {
|
|
2313
2336
|
// 已存在的会话,保持原有标题
|
|
2314
2337
|
return existingSession.title;
|
|
2315
2338
|
}
|
|
2316
|
-
//
|
|
2317
|
-
const
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2339
|
+
// 1. Claude Code 格式:从 messages 数组提取
|
|
2340
|
+
const rawText = this.extractTitleFromMessages((_a = request.body) === null || _a === void 0 ? void 0 : _a.messages)
|
|
2341
|
+
|| this.extractTitleFromInput((_b = request.body) === null || _b === void 0 ? void 0 : _b.input)
|
|
2342
|
+
|| null;
|
|
2343
|
+
if (rawText) {
|
|
2344
|
+
return this.formatSessionTitle(rawText);
|
|
2345
|
+
}
|
|
2346
|
+
return undefined;
|
|
2347
|
+
}
|
|
2348
|
+
/**
|
|
2349
|
+
* 从 messages 数组提取标题(Claude Code / OpenAI Chat 格式)
|
|
2350
|
+
*/
|
|
2351
|
+
extractTitleFromMessages(messages) {
|
|
2352
|
+
if (!Array.isArray(messages) || messages.length === 0)
|
|
2353
|
+
return null;
|
|
2354
|
+
const firstUserMessage = messages.find((msg) => msg.role === 'user');
|
|
2355
|
+
if (!firstUserMessage)
|
|
2356
|
+
return null;
|
|
2357
|
+
const content = firstUserMessage.content;
|
|
2358
|
+
if (typeof content === 'string') {
|
|
2359
|
+
return content;
|
|
2360
|
+
}
|
|
2361
|
+
else if (Array.isArray(content) && content.length > 0) {
|
|
2362
|
+
const lastBlock = content[content.length - 1];
|
|
2363
|
+
if ((lastBlock === null || lastBlock === void 0 ? void 0 : lastBlock.type) === 'text' && (lastBlock === null || lastBlock === void 0 ? void 0 : lastBlock.text)) {
|
|
2364
|
+
return lastBlock.text;
|
|
2365
|
+
}
|
|
2366
|
+
const textBlock = content.find((block) => (block === null || block === void 0 ? void 0 : block.type) === 'text');
|
|
2367
|
+
if (textBlock === null || textBlock === void 0 ? void 0 : textBlock.text)
|
|
2368
|
+
return textBlock.text;
|
|
2369
|
+
}
|
|
2370
|
+
return null;
|
|
2371
|
+
}
|
|
2372
|
+
/**
|
|
2373
|
+
* 从 input 数组提取标题(Codex Responses API 格式)
|
|
2374
|
+
* 忽略 developer 消息和系统级内容(AGENTS.md、<tag> 包裹的内容),
|
|
2375
|
+
* 使用最后一条有效的用户输入作为标题
|
|
2376
|
+
*/
|
|
2377
|
+
extractTitleFromInput(input) {
|
|
2378
|
+
if (!Array.isArray(input) || input.length === 0)
|
|
2379
|
+
return null;
|
|
2380
|
+
const userMessages = input.filter((item) => item.type === 'message' && item.role === 'user');
|
|
2381
|
+
for (let i = userMessages.length - 1; i >= 0; i--) {
|
|
2382
|
+
const msg = userMessages[i];
|
|
2383
|
+
const content = msg.content;
|
|
2384
|
+
if (!Array.isArray(content))
|
|
2385
|
+
continue;
|
|
2386
|
+
// 拼接所有 input_text,排除 AGENTS.md 和 <tag> 包裹的内容
|
|
2387
|
+
const textParts = [];
|
|
2388
|
+
for (const block of content) {
|
|
2389
|
+
if (block.type === 'input_text' && typeof block.text === 'string') {
|
|
2390
|
+
const text = block.text.trim();
|
|
2391
|
+
if (text.startsWith('# AGENTS.md') || text.startsWith('<environment_context>') || /^<\w+>/.test(text)) {
|
|
2392
|
+
continue;
|
|
2340
2393
|
}
|
|
2394
|
+
textParts.push(text);
|
|
2341
2395
|
}
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2396
|
+
}
|
|
2397
|
+
if (textParts.length > 0) {
|
|
2398
|
+
return textParts.join(' ');
|
|
2345
2399
|
}
|
|
2346
2400
|
}
|
|
2347
|
-
return
|
|
2401
|
+
return null;
|
|
2348
2402
|
}
|
|
2349
2403
|
/**
|
|
2350
2404
|
* 格式化会话标题
|
|
@@ -2617,7 +2671,7 @@ class ProxyServer {
|
|
|
2617
2671
|
// Claude:responseData 可能是 usage 对象本身,也可能包含 usage 字段
|
|
2618
2672
|
if (this.isClaudeSource(sourceType) || this.isClaudeChatSource(sourceType)) {
|
|
2619
2673
|
// 如果 responseData 直接包含 input_tokens/output_tokens,说明它本身就是 usage 对象
|
|
2620
|
-
if (typeof responseData.input_tokens === 'number' || typeof responseData.output_tokens === 'number') {
|
|
2674
|
+
if (typeof (responseData === null || responseData === void 0 ? void 0 : responseData.input_tokens) === 'number' || typeof (responseData === null || responseData === void 0 ? void 0 : responseData.output_tokens) === 'number') {
|
|
2621
2675
|
return (0, transformers_1.extractTokenUsageFromClaudeUsage)(responseData);
|
|
2622
2676
|
}
|
|
2623
2677
|
// 否则尝试从 usage 字段提取
|
|
@@ -2627,24 +2681,27 @@ class ProxyServer {
|
|
|
2627
2681
|
if (!usage)
|
|
2628
2682
|
return undefined;
|
|
2629
2683
|
// OpenAI 使用 prompt_tokens 和 completion_tokens
|
|
2630
|
-
if (typeof usage.prompt_tokens === 'number' || typeof usage.completion_tokens === 'number') {
|
|
2684
|
+
if (typeof (usage === null || usage === void 0 ? void 0 : usage.prompt_tokens) === 'number' || typeof (usage === null || usage === void 0 ? void 0 : usage.completion_tokens) === 'number') {
|
|
2631
2685
|
return (0, transformers_1.extractTokenUsageFromOpenAIUsage)(usage);
|
|
2632
2686
|
}
|
|
2633
2687
|
// Claude 使用 input_tokens 和 output_tokens
|
|
2634
|
-
if (typeof usage.input_tokens === 'number' || typeof usage.output_tokens === 'number') {
|
|
2688
|
+
if (typeof (usage === null || usage === void 0 ? void 0 : usage.input_tokens) === 'number' || typeof (usage === null || usage === void 0 ? void 0 : usage.output_tokens) === 'number') {
|
|
2635
2689
|
return (0, transformers_1.extractTokenUsageFromClaudeUsage)(usage);
|
|
2636
2690
|
}
|
|
2637
2691
|
return undefined;
|
|
2638
2692
|
}
|
|
2639
2693
|
proxyRequest(req, res, route, rule, service, options) {
|
|
2640
2694
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2641
|
-
var _a, _b, _c, _d, _e, _f;
|
|
2695
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
2642
2696
|
res.locals.skipLog = true;
|
|
2643
2697
|
const startTime = Date.now();
|
|
2644
2698
|
const rawSourceType = service.sourceType || 'openai-chat';
|
|
2645
2699
|
// 标准化 sourceType,将旧类型转换为新类型(向下兼容)
|
|
2646
2700
|
const sourceType = (0, type_migration_1.normalizeSourceType)(rawSourceType);
|
|
2647
2701
|
const targetType = route.targetType;
|
|
2702
|
+
const sessionId = this.defaultExtractSessionId(req, targetType) || '-';
|
|
2703
|
+
const vendor = this.dbManager.getVendorByServiceId(service.id);
|
|
2704
|
+
console.log(`\x1b[32m[Request Start]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, model=${rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model) || '-'}`);
|
|
2648
2705
|
const failoverEnabled = (options === null || options === void 0 ? void 0 : options.failoverEnabled) === true;
|
|
2649
2706
|
const forwardedToServiceName = options === null || options === void 0 ? void 0 : options.forwardedToServiceName;
|
|
2650
2707
|
const useOriginalConfig = (options === null || options === void 0 ? void 0 : options.useOriginalConfig) === true;
|
|
@@ -2783,15 +2840,22 @@ class ProxyServer {
|
|
|
2783
2840
|
var _a, _b;
|
|
2784
2841
|
if (logged)
|
|
2785
2842
|
return;
|
|
2843
|
+
const isError = statusCode >= 400;
|
|
2844
|
+
if (isError) {
|
|
2845
|
+
console.log(`\x1b[31m[Request Error]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, status=${statusCode}, time=${Date.now() - startTime}ms${error ? `, error=${error}` : ''}`);
|
|
2846
|
+
}
|
|
2847
|
+
else {
|
|
2848
|
+
console.log(`\x1b[33m[Request End]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, status=${statusCode}, time=${Date.now() - startTime}ms`);
|
|
2849
|
+
}
|
|
2786
2850
|
// 检查是否启用日志记录(默认启用)
|
|
2787
2851
|
const enableLogging = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging) !== false; // 默认为 true
|
|
2788
2852
|
if (!enableLogging) {
|
|
2789
2853
|
return;
|
|
2790
2854
|
}
|
|
2791
2855
|
logged = true;
|
|
2792
|
-
//
|
|
2856
|
+
// 供应商信息已在函数顶部获取
|
|
2793
2857
|
const vendors = this.dbManager.getVendors();
|
|
2794
|
-
const
|
|
2858
|
+
const vendorForLog = vendors.find(v => v.id === service.vendorId);
|
|
2795
2859
|
// 从请求体中提取模型信息
|
|
2796
2860
|
const requestModel = (_b = req.body) === null || _b === void 0 ? void 0 : _b.model;
|
|
2797
2861
|
const tagsForLog = this.buildRelayTags(relayedForLog, useOriginalConfig);
|
|
@@ -2817,7 +2881,7 @@ class ProxyServer {
|
|
|
2817
2881
|
targetServiceName: service.name,
|
|
2818
2882
|
targetModel: rule.targetModel || requestModel,
|
|
2819
2883
|
vendorId: service.vendorId,
|
|
2820
|
-
vendorName:
|
|
2884
|
+
vendorName: vendorForLog === null || vendorForLog === void 0 ? void 0 : vendorForLog.name,
|
|
2821
2885
|
requestModel,
|
|
2822
2886
|
tags: tagsForLog,
|
|
2823
2887
|
responseHeaders: responseHeadersForLog,
|
|
@@ -2827,8 +2891,7 @@ class ProxyServer {
|
|
|
2827
2891
|
downstreamResponseBody: downstreamResponseBodyForLog !== null && downstreamResponseBodyForLog !== void 0 ? downstreamResponseBodyForLog : responseBodyForLog,
|
|
2828
2892
|
});
|
|
2829
2893
|
// Session 索引逻辑
|
|
2830
|
-
|
|
2831
|
-
if (sessionId) {
|
|
2894
|
+
if (sessionId && sessionId !== '-') {
|
|
2832
2895
|
// 正确计算当前请求的tokens:优先使用totalTokens,否则使用input+output
|
|
2833
2896
|
const totalTokens = (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.totalTokens) ||
|
|
2834
2897
|
(((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.inputTokens) || 0) + ((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.outputTokens) || 0));
|
|
@@ -2841,7 +2904,7 @@ class ProxyServer {
|
|
|
2841
2904
|
firstRequestAt: startTime,
|
|
2842
2905
|
lastRequestAt: Date.now(),
|
|
2843
2906
|
vendorId: service.vendorId,
|
|
2844
|
-
vendorName:
|
|
2907
|
+
vendorName: vendorForLog === null || vendorForLog === void 0 ? void 0 : vendorForLog.name,
|
|
2845
2908
|
serviceId: service.id,
|
|
2846
2909
|
serviceName: service.name,
|
|
2847
2910
|
model: requestModel || rule.targetModel,
|
|
@@ -2859,10 +2922,10 @@ class ProxyServer {
|
|
|
2859
2922
|
((usageForLog.inputTokens || 0) + (usageForLog.outputTokens || 0));
|
|
2860
2923
|
if (totalTokens > 0) {
|
|
2861
2924
|
this.dbManager.incrementRuleTokenUsage(rule.id, totalTokens);
|
|
2862
|
-
//
|
|
2925
|
+
// 获取更新后的规则数据并更新状态
|
|
2863
2926
|
const updatedRule = this.dbManager.getRule(rule.id);
|
|
2864
2927
|
if (updatedRule) {
|
|
2865
|
-
rules_status_service_1.rulesStatusBroadcaster.
|
|
2928
|
+
rules_status_service_1.rulesStatusBroadcaster.updateRuleUsage(rule.id, updatedRule.totalTokensUsed || 0, updatedRule.totalRequestsUsed || 0);
|
|
2866
2929
|
}
|
|
2867
2930
|
}
|
|
2868
2931
|
}
|
|
@@ -2875,10 +2938,10 @@ class ProxyServer {
|
|
|
2875
2938
|
this.dbManager.incrementRuleRequestCount(rule.id, 1);
|
|
2876
2939
|
// 更新频率限制跟踪
|
|
2877
2940
|
this.recordRequest(rule.id);
|
|
2878
|
-
//
|
|
2941
|
+
// 获取更新后的规则数据并更新状态
|
|
2879
2942
|
const updatedRule = this.dbManager.getRule(rule.id);
|
|
2880
2943
|
if (updatedRule) {
|
|
2881
|
-
rules_status_service_1.rulesStatusBroadcaster.
|
|
2944
|
+
rules_status_service_1.rulesStatusBroadcaster.updateRuleUsage(rule.id, updatedRule.totalTokensUsed || 0, updatedRule.totalRequestsUsed || 0);
|
|
2882
2945
|
}
|
|
2883
2946
|
}
|
|
2884
2947
|
// 定期清理过期缓存
|
|
@@ -2974,7 +3037,7 @@ class ProxyServer {
|
|
|
2974
3037
|
totalTokens: inputTokens,
|
|
2975
3038
|
};
|
|
2976
3039
|
responseHeadersForLog = {
|
|
2977
|
-
'content-type': 'application/json',
|
|
3040
|
+
'content-type': 'application/json; charset=utf-8',
|
|
2978
3041
|
};
|
|
2979
3042
|
responseBodyForLog = JSON.stringify(localTokenResponse);
|
|
2980
3043
|
streamChunksForLog = undefined;
|
|
@@ -3003,7 +3066,7 @@ class ProxyServer {
|
|
|
3003
3066
|
try {
|
|
3004
3067
|
// 使用统一的请求转换方法
|
|
3005
3068
|
const transformedRequestBody = this.transformRequestToUpstream(targetType, sourceType, requestBody, rule.targetModel);
|
|
3006
|
-
requestBody = (
|
|
3069
|
+
requestBody = (_b = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : requestBody) !== null && _b !== void 0 ? _b : {};
|
|
3007
3070
|
// 应用 max_output_tokens 限制
|
|
3008
3071
|
requestBody = this.applyMaxOutputTokensLimit(requestBody, service);
|
|
3009
3072
|
if (this.shouldDefaultStreamingForClaudeBridge(req, targetType, sourceType, requestBody)
|
|
@@ -3099,7 +3162,7 @@ class ProxyServer {
|
|
|
3099
3162
|
let errorResponseData = response.data;
|
|
3100
3163
|
if (streamRequested && response.data && typeof response.data.on === 'function') {
|
|
3101
3164
|
const raw = yield this.readStreamBody(response.data);
|
|
3102
|
-
errorResponseData = (
|
|
3165
|
+
errorResponseData = (_c = this.safeJsonParse(raw)) !== null && _c !== void 0 ? _c : raw;
|
|
3103
3166
|
}
|
|
3104
3167
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
3105
3168
|
yield handleUpstreamHttpError(response.status, errorResponseData, responseHeaders, contentType);
|
|
@@ -3110,7 +3173,7 @@ class ProxyServer {
|
|
|
3110
3173
|
// 统一走默认流式链路(transformSSEToTool + 统一断连保护),避免历史分支行为不一致
|
|
3111
3174
|
const useLegacySpecialSSEBranches = false;
|
|
3112
3175
|
if (useLegacySpecialSSEBranches && targetType === 'claude-code' && this.isOpenAIType(sourceType)) {
|
|
3113
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
3176
|
+
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
3114
3177
|
res.setHeader('Cache-Control', 'no-cache');
|
|
3115
3178
|
res.setHeader('Connection', 'keep-alive');
|
|
3116
3179
|
const parser = new streaming_1.SSEParserTransform();
|
|
@@ -3217,7 +3280,7 @@ class ProxyServer {
|
|
|
3217
3280
|
return;
|
|
3218
3281
|
}
|
|
3219
3282
|
if (useLegacySpecialSSEBranches && targetType === 'codex' && this.isClaudeSource(sourceType)) {
|
|
3220
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
3283
|
+
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
3221
3284
|
res.setHeader('Cache-Control', 'no-cache');
|
|
3222
3285
|
res.setHeader('Connection', 'keep-alive');
|
|
3223
3286
|
const parser = new streaming_1.SSEParserTransform();
|
|
@@ -3319,7 +3382,7 @@ class ProxyServer {
|
|
|
3319
3382
|
}
|
|
3320
3383
|
// Gemini / Gemini Chat -> Claude Code 流式转换
|
|
3321
3384
|
if (useLegacySpecialSSEBranches && targetType === 'claude-code' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
|
|
3322
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
3385
|
+
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
3323
3386
|
res.setHeader('Cache-Control', 'no-cache');
|
|
3324
3387
|
res.setHeader('Connection', 'keep-alive');
|
|
3325
3388
|
const parser = new streaming_1.SSEParserTransform();
|
|
@@ -3331,9 +3394,9 @@ class ProxyServer {
|
|
|
3331
3394
|
const usage = converter.getUsage();
|
|
3332
3395
|
if (usage) {
|
|
3333
3396
|
usageForLog = {
|
|
3334
|
-
inputTokens: usage.input_tokens,
|
|
3335
|
-
outputTokens: usage.output_tokens,
|
|
3336
|
-
cacheReadInputTokens: usage.cache_read_input_tokens,
|
|
3397
|
+
inputTokens: (usage === null || usage === void 0 ? void 0 : usage.input_tokens) || 0,
|
|
3398
|
+
outputTokens: (usage === null || usage === void 0 ? void 0 : usage.output_tokens) || 0,
|
|
3399
|
+
cacheReadInputTokens: (usage === null || usage === void 0 ? void 0 : usage.cache_read_input_tokens) || 0,
|
|
3337
3400
|
};
|
|
3338
3401
|
}
|
|
3339
3402
|
else {
|
|
@@ -3415,7 +3478,7 @@ class ProxyServer {
|
|
|
3415
3478
|
}
|
|
3416
3479
|
// Gemini / Gemini Chat -> Codex 流式转换
|
|
3417
3480
|
if (useLegacySpecialSSEBranches && targetType === 'codex' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
|
|
3418
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
3481
|
+
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
3419
3482
|
res.setHeader('Cache-Control', 'no-cache');
|
|
3420
3483
|
res.setHeader('Connection', 'keep-alive');
|
|
3421
3484
|
const parser = new streaming_1.SSEParserTransform();
|
|
@@ -3620,7 +3683,7 @@ class ProxyServer {
|
|
|
3620
3683
|
let responseData = response.data;
|
|
3621
3684
|
if (streamRequested && response.data && typeof response.data.on === 'function' && !isEventStream) {
|
|
3622
3685
|
const raw = yield this.readStreamBody(response.data);
|
|
3623
|
-
responseData = (
|
|
3686
|
+
responseData = (_d = this.safeJsonParse(raw)) !== null && _d !== void 0 ? _d : raw;
|
|
3624
3687
|
}
|
|
3625
3688
|
// 收集响应头
|
|
3626
3689
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
@@ -3662,7 +3725,7 @@ class ProxyServer {
|
|
|
3662
3725
|
console.error('Proxy error:', error);
|
|
3663
3726
|
// 检测是否是 timeout 错误
|
|
3664
3727
|
const isTimeout = error.code === 'ECONNABORTED' ||
|
|
3665
|
-
((
|
|
3728
|
+
((_e = error.message) === null || _e === void 0 ? void 0 : _e.toLowerCase().includes('timeout')) ||
|
|
3666
3729
|
(error.errno && error.errno === 'ETIMEDOUT');
|
|
3667
3730
|
const baseErrorMessage = isTimeout
|
|
3668
3731
|
? 'Request timeout - the upstream API took too long to respond'
|
|
@@ -3687,10 +3750,10 @@ class ProxyServer {
|
|
|
3687
3750
|
targetType,
|
|
3688
3751
|
targetServiceId: service.id,
|
|
3689
3752
|
targetServiceName: service.name,
|
|
3690
|
-
targetModel: rule.targetModel || ((
|
|
3753
|
+
targetModel: rule.targetModel || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
|
|
3691
3754
|
vendorId: service.vendorId,
|
|
3692
3755
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3693
|
-
requestModel: (
|
|
3756
|
+
requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
|
|
3694
3757
|
upstreamRequest: upstreamRequestForLog,
|
|
3695
3758
|
responseTime: Date.now() - startTime,
|
|
3696
3759
|
});
|
|
@@ -3714,7 +3777,7 @@ class ProxyServer {
|
|
|
3714
3777
|
};
|
|
3715
3778
|
if (streamRequested) {
|
|
3716
3779
|
// 流式请求:使用 SSE 格式
|
|
3717
|
-
res.setHeader('Content-Type', 'text/event-stream');
|
|
3780
|
+
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
3718
3781
|
res.setHeader('Cache-Control', 'no-cache');
|
|
3719
3782
|
res.setHeader('Connection', 'keep-alive');
|
|
3720
3783
|
res.status(200);
|