aicodeswitch 4.0.0 → 4.0.1

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.
@@ -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
- let data = '';
2263
+ const chunks = [];
2264
2264
  stream.on('data', (chunk) => {
2265
- data += chunk.toString();
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
- return ((_b = (_a = request.body) === null || _a === void 0 ? void 0 : _a.metadata) === null || _b === void 0 ? void 0 : _b.user_id) || null;
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 messages = (_a = request.body) === null || _a === void 0 ? void 0 : _a.messages;
2318
- if (Array.isArray(messages) && messages.length > 0) {
2319
- // 查找第一条 user 消息
2320
- const firstUserMessage = messages.find((msg) => msg.role === 'user');
2321
- if (firstUserMessage) {
2322
- const content = firstUserMessage.content;
2323
- let rawText = '';
2324
- if (typeof content === 'string') {
2325
- rawText = content;
2326
- }
2327
- else if (Array.isArray(content) && content.length > 0) {
2328
- // 处理结构化内容(如图片+文本)
2329
- // 从最后一个元素取值,通常最后的文本才是真正的用户输入
2330
- const lastBlock = content[content.length - 1];
2331
- if ((lastBlock === null || lastBlock === void 0 ? void 0 : lastBlock.type) === 'text' && (lastBlock === null || lastBlock === void 0 ? void 0 : lastBlock.text)) {
2332
- rawText = lastBlock.text;
2333
- }
2334
- else {
2335
- // 如果最后一个不是 text 类型,尝试找到第一个 text 类型作为备用
2336
- const textBlock = content.find((block) => (block === null || block === void 0 ? void 0 : block.type) === 'text');
2337
- if (textBlock === null || textBlock === void 0 ? void 0 : textBlock.text) {
2338
- rawText = textBlock.text;
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
- if (rawText) {
2343
- return this.formatSessionTitle(rawText);
2344
- }
2396
+ }
2397
+ if (textParts.length > 0) {
2398
+ return textParts.join(' ');
2345
2399
  }
2346
2400
  }
2347
- return undefined;
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 vendor = vendors.find(v => v.id === service.vendorId);
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: vendor === null || vendor === void 0 ? void 0 : vendor.name,
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
- const sessionId = this.defaultExtractSessionId(req, targetType);
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: vendor === null || vendor === void 0 ? void 0 : vendor.name,
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.broadcastUsageUpdate(rule.id, updatedRule.totalTokensUsed || 0, updatedRule.totalRequestsUsed || 0);
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.broadcastUsageUpdate(rule.id, updatedRule.totalTokensUsed || 0, updatedRule.totalRequestsUsed || 0);
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 = (_a = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : requestBody) !== null && _a !== void 0 ? _a : {};
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 = (_b = this.safeJsonParse(raw)) !== null && _b !== void 0 ? _b : raw;
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 = (_c = this.safeJsonParse(raw)) !== null && _c !== void 0 ? _c : raw;
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
- ((_d = error.message) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes('timeout')) ||
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 || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
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: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
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);