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 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 ↔ OpenAI Responses)
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
  ![](public/donate-to-me.png)
@@ -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.services.get(rule.targetServiceId);
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.services.get(rule.targetServiceId);
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.services.get(rule.targetServiceId);
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.services.get(rule.targetServiceId);
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
- return this.routes.find(route => route.isActive);
404
+ const activeRoutes = this.getActiveRoutes();
405
+ return activeRoutes.find(route => route.isActive);
388
406
  }
389
407
  findRouteByTargetType(targetType) {
390
- return this.routes.find(route => route.targetType === targetType && route.isActive);
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.rules.get(routeId);
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.rules.get(routeId);
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' || sourceType === 'claude-code';
724
+ return sourceType === 'claude-chat';
697
725
  }
698
726
  isOpenAIChatSource(sourceType) {
699
- return sourceType === 'openai-chat' || sourceType === 'openai-code' || sourceType === 'deepseek-chat';
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 Responses API 格式
844
- if (this.isOpenAIResponsesSource(targetSourceType)) {
845
- // OpenAI Responses → OpenAI Responses: 直接透传路径
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 Responses → Claude: /v1/responses/completions → /v1/messages
854
- return originalPath.replace(/\/v1\/responses\/completions\b/, '/v1/messages');
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.isOpenAIResponsesSource(sourceType)) {
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, openai_responses_1.transformOpenAIResponsesRequestToClaude)(requestBody, rule.targetModel);
943
+ requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
936
944
  }
937
945
  else {
938
- res.status(400).json({ error: 'Codex requires an OpenAI Responses compatible source.' });
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 || 30000,
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.ClaudeToOpenAIResponsesEventTransform({ model: requestBody === null || requestBody === void 0 ? void 0 : requestBody.model });
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.extractTokenUsageFromClaudeUsage)(usage);
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, openai_responses_1.transformClaudeResponseToOpenAIResponses)(responseData);
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
- this.routes = this.dbManager.getRoutes().filter((g) => g.isActive);
1215
- this.rules.clear();
1216
- for (const route of this.routes) {
1217
- const routeRules = this.dbManager.getRules(route.id);
1218
- // 确保按 sortOrder 降序排序(database 层已处理,但再次确保)
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
- this.services.clear();
1225
- allServices.forEach((service) => {
1226
- this.services.set(service.id, service);
1227
- });
1228
- console.log(`Loaded ${this.routes.length} active routes and ${this.services.size} services`);
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;