aicodeswitch 5.1.2 → 5.2.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.
Files changed (32) hide show
  1. package/README.md +1 -0
  2. package/bin/restore.js +14 -7
  3. package/bin/utils/managed-fields.js +62 -0
  4. package/dist/server/access-keys/index.js +173 -0
  5. package/dist/server/access-keys/key-logger.js +358 -0
  6. package/dist/server/access-keys/key-resolver.js +51 -0
  7. package/dist/server/access-keys/key-session-tracker.js +217 -0
  8. package/dist/server/access-keys/manager.js +206 -0
  9. package/dist/server/access-keys/policy-manager.js +144 -0
  10. package/dist/server/access-keys/quota-checker.js +197 -0
  11. package/dist/server/access-keys/usage-tracker.js +279 -0
  12. package/dist/server/auth.js +16 -4
  13. package/dist/server/coding-plan-headers.js +121 -0
  14. package/dist/server/config-managed-fields.js +2 -0
  15. package/dist/server/conversions/index.js +8 -0
  16. package/dist/server/conversions/utils/tool-result.js +35 -0
  17. package/dist/server/fs-database.js +72 -1
  18. package/dist/server/main.js +1162 -13
  19. package/dist/server/proxy-server.js +662 -128
  20. package/dist/server/rules-status-service.js +32 -3
  21. package/dist/server/session-launcher.js +282 -0
  22. package/dist/server/session-migration.js +419 -0
  23. package/dist/server/transformers/chunk-collector.js +28 -1
  24. package/dist/server/transformers/model-rewrite-transform.js +128 -0
  25. package/dist/ui/assets/claude-XtpLmGtF.webp +0 -0
  26. package/dist/ui/assets/index-Cws89pD2.js +828 -0
  27. package/dist/ui/assets/index-CzfKxImD.css +1 -0
  28. package/dist/ui/assets/openai-CPEiZpaN.webp +0 -0
  29. package/dist/ui/index.html +2 -2
  30. package/package.json +1 -1
  31. package/dist/ui/assets/index-BHR12ImE.css +0 -1
  32. package/dist/ui/assets/index-CumAhpXg.js +0 -517
@@ -0,0 +1,419 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.extractSessionContent = extractSessionContent;
13
+ exports.previewMigration = previewMigration;
14
+ exports.migrateSession = migrateSession;
15
+ function parseSSEChunks(sourceText) {
16
+ const chunks = sourceText.split('\n').map(item => item.trim()).join('\n')
17
+ .split('\n\n').filter(s => s.trim());
18
+ const events = [];
19
+ for (const chunk of chunks) {
20
+ let event = '';
21
+ let dataLines = [];
22
+ let dataInsert = 0;
23
+ const lines = chunk.split('\n');
24
+ lines.forEach((line) => {
25
+ if (/^[a-z]+:/.test(line)) {
26
+ const at = line.indexOf(':');
27
+ const type = line.slice(0, at).trim();
28
+ const content = line.slice(at + 1).trim();
29
+ if (type === 'event') {
30
+ event = content;
31
+ }
32
+ else if (type === 'data') {
33
+ dataLines.push(content);
34
+ dataInsert = 1;
35
+ }
36
+ else if (dataLines.length) {
37
+ dataInsert = -1;
38
+ }
39
+ }
40
+ else if (dataInsert === 1) {
41
+ dataLines.push(line);
42
+ }
43
+ });
44
+ if (dataLines.length) {
45
+ const raw = dataLines.join('\n');
46
+ let parsed = raw;
47
+ try {
48
+ parsed = JSON.parse(raw);
49
+ }
50
+ catch ( /* keep raw string */_a) { /* keep raw string */ }
51
+ events.push({ event, data: parsed, raw });
52
+ }
53
+ }
54
+ return events;
55
+ }
56
+ function assembleStreamText(events) {
57
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
58
+ let text = '', thinking = '';
59
+ let inTextBlock = false, inThinkingBlock = false;
60
+ let reasoningAccumulated = false;
61
+ for (const ev of events) {
62
+ const data = ev.data;
63
+ if (!data)
64
+ continue;
65
+ // Claude format
66
+ if (typeof data === 'object' && data.type) {
67
+ if (data.type === 'content_block_start') {
68
+ if (((_a = data.content_block) === null || _a === void 0 ? void 0 : _a.type) === 'text')
69
+ inTextBlock = true;
70
+ else if (((_b = data.content_block) === null || _b === void 0 ? void 0 : _b.type) === 'thinking')
71
+ inThinkingBlock = true;
72
+ }
73
+ if (data.type === 'content_block_delta') {
74
+ if (((_c = data.delta) === null || _c === void 0 ? void 0 : _c.type) === 'text_delta' && inTextBlock)
75
+ text += data.delta.text || '';
76
+ if (((_d = data.delta) === null || _d === void 0 ? void 0 : _d.type) === 'thinking_delta' && inThinkingBlock)
77
+ thinking += data.delta.thinking || '';
78
+ }
79
+ if (data.type === 'content_block_stop') {
80
+ inTextBlock = false;
81
+ inThinkingBlock = false;
82
+ }
83
+ }
84
+ // Responses API format
85
+ if (typeof data === 'object' && data.type) {
86
+ if (data.type === 'response.reasoning_text.delta') {
87
+ thinking += data.delta || '';
88
+ reasoningAccumulated = true;
89
+ }
90
+ if (data.type === 'response.content_part.done') {
91
+ if (((_e = data.part) === null || _e === void 0 ? void 0 : _e.type) === 'output_text' && ((_f = data.part) === null || _f === void 0 ? void 0 : _f.text)) {
92
+ text += data.part.text;
93
+ }
94
+ if (((_g = data.part) === null || _g === void 0 ? void 0 : _g.type) === 'reasoning_text' && ((_h = data.part) === null || _h === void 0 ? void 0 : _h.text) && !reasoningAccumulated) {
95
+ thinking += data.part.text;
96
+ }
97
+ }
98
+ }
99
+ // OpenAI Chat format
100
+ if (typeof data === 'object' && ((_k = (_j = data.choices) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.delta)) {
101
+ const delta = data.choices[0].delta;
102
+ if (delta.content)
103
+ text += delta.content;
104
+ if ((_l = delta.thinking) === null || _l === void 0 ? void 0 : _l.content)
105
+ thinking += delta.thinking.content;
106
+ }
107
+ // DeepSeek direct format
108
+ if (typeof data === 'object' && (data.reasoning_content || data.thinking)) {
109
+ thinking += data.reasoning_content || data.thinking;
110
+ }
111
+ }
112
+ return { text, thinking };
113
+ }
114
+ // ─── 工具调用摘要化 ───
115
+ const TOOL_SUMMARIES = {
116
+ Bash: (i) => `🔧 执行命令: \`${i.command || i.cmd || ''}\``,
117
+ Read: (i) => `📖 读取文件: ${i.file_path || ''}`,
118
+ Write: (i) => `📝 写入文件: ${i.file_path || ''}`,
119
+ Edit: (i) => `✏️ 编辑文件: ${i.file_path || i.target_file || ''}`,
120
+ Glob: (i) => `🔍 搜索文件: ${i.pattern || ''}`,
121
+ Grep: (i) => `🔍 搜索内容: ${i.pattern || ''}`,
122
+ TodoWrite: () => `📋 更新任务列表`,
123
+ Agent: () => `🤖 启动子代理`,
124
+ TaskOutput: () => `📤 获取任务输出`,
125
+ SendMessage: () => `💬 发送消息`,
126
+ shell: (i) => `🔧 执行命令: \`${i.command || ''}\``,
127
+ apply_diff: (_i) => `✏️ 应用代码变更`,
128
+ create_file: (_i) => `📝 创建文件`,
129
+ };
130
+ function summarizeToolCall(toolName, input) {
131
+ const summarizer = TOOL_SUMMARIES[toolName];
132
+ if (summarizer) {
133
+ try {
134
+ return summarizer(input);
135
+ }
136
+ catch (_a) {
137
+ return `🔧 调用工具: ${toolName}`;
138
+ }
139
+ }
140
+ return `🔧 调用工具: ${toolName}`;
141
+ }
142
+ function extractUserTextFromClaudeMessages(messages) {
143
+ const userMsgs = messages.filter(m => m.role === 'user');
144
+ if (!userMsgs.length)
145
+ return '';
146
+ const lastMsg = userMsgs[userMsgs.length - 1];
147
+ if (typeof lastMsg.content === 'string')
148
+ return lastMsg.content;
149
+ if (Array.isArray(lastMsg.content)) {
150
+ return lastMsg.content
151
+ .filter(b => b.type === 'text' && b.text)
152
+ .map(b => b.text)
153
+ .join('\n');
154
+ }
155
+ return '';
156
+ }
157
+ function extractToolCallsFromClaudeMessage(msg) {
158
+ if (!Array.isArray(msg.content))
159
+ return [];
160
+ return msg.content
161
+ .filter(b => b.type === 'tool_use')
162
+ .map(b => summarizeToolCall(b.name || 'unknown', b.input || {}));
163
+ }
164
+ function extractUserTextFromCodexInput(inputArr) {
165
+ const userItems = inputArr.filter(i => i.type === 'message' && i.role === 'user');
166
+ if (!userItems.length)
167
+ return '';
168
+ const lastItem = userItems[userItems.length - 1];
169
+ if (typeof lastItem.content === 'string')
170
+ return lastItem.content;
171
+ if (Array.isArray(lastItem.content)) {
172
+ return lastItem.content
173
+ .filter((b) => b.type === 'input_text' && b.text)
174
+ .map((b) => b.text)
175
+ .join('\n');
176
+ }
177
+ return '';
178
+ }
179
+ function extractToolCallsFromCodexInput(inputArr) {
180
+ return inputArr
181
+ .filter(i => i.type === 'function_call')
182
+ .map(i => summarizeToolCall(i.name || 'unknown', typeof i.arguments === 'string' ? JSON.parse(i.arguments || '{}') : (i.arguments || {})));
183
+ }
184
+ function extractAssistantResponseFromLog(log) {
185
+ var _a, _b, _c;
186
+ let text = '', thinking = '';
187
+ // 1. Try downstreamResponseBody (SSE)
188
+ const sourceText = log.downstreamResponseBody;
189
+ if (typeof sourceText === 'string' && (sourceText.includes('event:') || sourceText.includes('data:'))) {
190
+ const events = parseSSEChunks(sourceText);
191
+ const assembled = assembleStreamText(events);
192
+ text = assembled.text;
193
+ thinking = assembled.thinking;
194
+ }
195
+ // 2. Try responseBody (JSON)
196
+ if (!text && !thinking && log.responseBody) {
197
+ try {
198
+ const parsed = typeof log.responseBody === 'string' ? JSON.parse(log.responseBody) : log.responseBody;
199
+ if (parsed.content) {
200
+ if (Array.isArray(parsed.content)) {
201
+ const parts = [];
202
+ for (const block of parsed.content) {
203
+ if (block.type === 'text' && block.text)
204
+ parts.push(block.text);
205
+ else if (block.type === 'thinking' && block.thinking)
206
+ thinking += block.thinking;
207
+ }
208
+ text = parts.join('\n\n');
209
+ }
210
+ else if (typeof parsed.content === 'string') {
211
+ text = parsed.content;
212
+ }
213
+ }
214
+ if (!text && ((_c = (_b = (_a = parsed.choices) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.content)) {
215
+ text = parsed.choices[0].message.content;
216
+ }
217
+ if (!text && parsed.output) {
218
+ const parts = [];
219
+ for (const item of parsed.output) {
220
+ if (item.type === 'message') {
221
+ if (Array.isArray(item.content)) {
222
+ for (const c of item.content) {
223
+ if (c.type === 'output_text' && c.text)
224
+ parts.push(c.text);
225
+ }
226
+ }
227
+ }
228
+ }
229
+ text = parts.join('\n\n');
230
+ }
231
+ }
232
+ catch ( /* ignore parse errors */_d) { /* ignore parse errors */ }
233
+ }
234
+ // 3. Try streamChunks
235
+ if (!text && Array.isArray(log.streamChunks)) {
236
+ const chunksText = log.streamChunks.join('');
237
+ if (chunksText.includes('event:') || chunksText.includes('data:')) {
238
+ const events = parseSSEChunks(chunksText);
239
+ const assembled = assembleStreamText(events);
240
+ text = assembled.text;
241
+ thinking = assembled.thinking;
242
+ }
243
+ }
244
+ return { text: text.trim(), thinking: thinking.trim() };
245
+ }
246
+ function isClaudeCodeBody(body) {
247
+ return (body === null || body === void 0 ? void 0 : body.messages) && Array.isArray(body.messages);
248
+ }
249
+ function isCodexBody(body) {
250
+ return (body === null || body === void 0 ? void 0 : body.input) && Array.isArray(body.input);
251
+ }
252
+ // ─── Token 估算 ───
253
+ function estimateTokens(text) {
254
+ let cjk = 0, other = 0;
255
+ for (const ch of text) {
256
+ const code = ch.charCodeAt(0);
257
+ if ((code >= 0x4E00 && code <= 0x9FFF) ||
258
+ (code >= 0x3400 && code <= 0x4DBF) ||
259
+ (code >= 0x20000 && code <= 0x2A6DF) ||
260
+ (code >= 0xF900 && code <= 0xFAFF)) {
261
+ cjk++;
262
+ }
263
+ else {
264
+ other++;
265
+ }
266
+ }
267
+ return Math.ceil(cjk + other * 0.25);
268
+ }
269
+ // ─── 迁移 Prompt 生成 ───
270
+ function formatSessionTitle(title) {
271
+ if (!title)
272
+ return '(无标题)';
273
+ return title.replace(/<\/?session>/g, '').trim() || '(无标题)';
274
+ }
275
+ function generateMigrationPrompt(content, targetTool) {
276
+ const toolLabels = {
277
+ 'claude-code': 'Claude Code',
278
+ 'codex': 'Codex',
279
+ };
280
+ const sourceLabel = toolLabels[content.sourceTool];
281
+ const targetLabel = toolLabels[targetTool];
282
+ const lines = [];
283
+ lines.push(`# 会话迁移上下文`);
284
+ lines.push('');
285
+ lines.push(`> 以下内容从 ${sourceLabel} 会话「${formatSessionTitle(content.sessionTitle)}」迁移而来`);
286
+ lines.push(`> 目标工具:${targetLabel}`);
287
+ lines.push(`> 迁移时间:${new Date().toISOString()}`);
288
+ lines.push(`> 原始会话共 ${content.totalRounds} 轮对话,此处包含最近 ${content.extractedRounds} 轮`);
289
+ lines.push('');
290
+ lines.push('---');
291
+ lines.push('');
292
+ lines.push('## 对话历史');
293
+ lines.push('');
294
+ for (const round of content.rounds) {
295
+ lines.push(`### 👤 用户`);
296
+ lines.push(round.userMessage || '(无文本内容)');
297
+ lines.push('');
298
+ if (round.toolCallSummaries.length > 0) {
299
+ for (const summary of round.toolCallSummaries) {
300
+ lines.push(`> ${summary}`);
301
+ }
302
+ lines.push('');
303
+ }
304
+ lines.push(`### 🤖 助手`);
305
+ lines.push(round.assistantResponse || '(无文本内容)');
306
+ lines.push('');
307
+ if (round.thinking) {
308
+ lines.push('<details>');
309
+ lines.push('<summary>思考过程</summary>');
310
+ lines.push('');
311
+ lines.push(round.thinking);
312
+ lines.push('');
313
+ lines.push('</details>');
314
+ lines.push('');
315
+ }
316
+ lines.push('---');
317
+ lines.push('');
318
+ }
319
+ lines.push(`> ⚠️ 注意:以上对话历史和工具操作仅为上下文摘要,实际的文件修改和工具执行结果不会在目标工具中生效。`);
320
+ lines.push(`> 请基于以上上下文继续工作。`);
321
+ return lines.join('\n');
322
+ }
323
+ // ─── 主要导出 ───
324
+ function extractSessionContent(dbManager, sessionId, options) {
325
+ return __awaiter(this, void 0, void 0, function* () {
326
+ const logs = yield dbManager.getLogsBySessionId(sessionId, 10000);
327
+ const session = dbManager.getSession(sessionId);
328
+ if (!session) {
329
+ throw new Error(`Session not found: ${sessionId}`);
330
+ }
331
+ const sortedLogs = logs.sort((a, b) => a.timestamp - b.timestamp);
332
+ const rounds = [];
333
+ for (let i = 0; i < sortedLogs.length; i++) {
334
+ const log = sortedLogs[i];
335
+ const body = log.body
336
+ ? (typeof log.body === 'string' ? JSON.parse(log.body) : log.body)
337
+ : null;
338
+ if (!body)
339
+ continue;
340
+ // Extract user message
341
+ let userMessage = '';
342
+ let toolCallSummaries = [];
343
+ if (isClaudeCodeBody(body)) {
344
+ userMessage = extractUserTextFromClaudeMessages(body.messages);
345
+ // Extract tool calls from assistant messages
346
+ if (options.includeToolCalls !== false && body.messages) {
347
+ for (const msg of body.messages) {
348
+ if (msg.role === 'assistant') {
349
+ toolCallSummaries.push(...extractToolCallsFromClaudeMessage(msg));
350
+ }
351
+ }
352
+ }
353
+ }
354
+ else if (isCodexBody(body)) {
355
+ userMessage = extractUserTextFromCodexInput(body.input);
356
+ if (options.includeToolCalls !== false) {
357
+ toolCallSummaries = extractToolCallsFromCodexInput(body.input);
358
+ }
359
+ }
360
+ if (!userMessage && !toolCallSummaries.length)
361
+ continue;
362
+ // Extract assistant response
363
+ const { text, thinking } = extractAssistantResponseFromLog(log);
364
+ rounds.push({
365
+ index: rounds.length + 1,
366
+ userMessage,
367
+ assistantResponse: text,
368
+ toolCallSummaries,
369
+ thinking: options.includeThinking ? (thinking || undefined) : undefined,
370
+ timestamp: log.timestamp,
371
+ });
372
+ }
373
+ const maxRounds = options.maxRounds || 0;
374
+ const extractedRounds = maxRounds > 0 ? rounds.slice(-maxRounds) : rounds;
375
+ return {
376
+ sessionId,
377
+ sessionTitle: session.title || '',
378
+ sourceTool: session.targetType,
379
+ rounds: extractedRounds,
380
+ totalRounds: sortedLogs.length,
381
+ extractedRounds: extractedRounds.length,
382
+ };
383
+ });
384
+ }
385
+ function previewMigration(content, targetTool) {
386
+ const warnings = [];
387
+ const prompt = generateMigrationPrompt(content, targetTool);
388
+ const estimatedTokens = estimateTokens(prompt);
389
+ if (content.totalRounds > content.extractedRounds && content.extractedRounds > 0) {
390
+ warnings.push(`会话较长(${content.totalRounds} 轮),已截断到最近 ${content.extractedRounds} 轮对话`);
391
+ }
392
+ if (estimatedTokens > 100000) {
393
+ warnings.push(`迁移 Prompt 约 ${estimatedTokens.toLocaleString()} tokens,可能超过目标模型的上下文窗口`);
394
+ }
395
+ if (content.totalRounds === 0) {
396
+ warnings.push('该会话没有可提取的对话内容');
397
+ }
398
+ return {
399
+ content,
400
+ generatedPrompt: prompt,
401
+ estimatedTokens,
402
+ warnings,
403
+ };
404
+ }
405
+ function migrateSession(content, targetTool, editedPrompt) {
406
+ const prompt = editedPrompt || generateMigrationPrompt(content, targetTool);
407
+ const estimatedTokens = estimateTokens(prompt);
408
+ const warnings = [];
409
+ if (estimatedTokens > 100000) {
410
+ warnings.push(`迁移 Prompt 约 ${estimatedTokens.toLocaleString()} tokens,可能超过目标模型的上下文窗口`);
411
+ }
412
+ return {
413
+ success: true,
414
+ prompt,
415
+ format: 'markdown',
416
+ estimatedTokens,
417
+ warnings,
418
+ };
419
+ }
@@ -23,7 +23,7 @@ function isClientDisconnectError(error) {
23
23
  * 这个Transform会记录所有经过它的数据块,同时将数据原封不动地传递给下一个stream
24
24
  */
25
25
  class ChunkCollectorTransform extends stream_1.Transform {
26
- constructor() {
26
+ constructor(refreshCallback) {
27
27
  super({ writableObjectMode: true, readableObjectMode: true });
28
28
  Object.defineProperty(this, "chunks", {
29
29
  enumerable: true,
@@ -43,6 +43,25 @@ class ChunkCollectorTransform extends stream_1.Transform {
43
43
  writable: true,
44
44
  value: new string_decoder_1.StringDecoder('utf8')
45
45
  });
46
+ Object.defineProperty(this, "lastRefreshTime", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: 0
51
+ });
52
+ Object.defineProperty(this, "refreshCallback", {
53
+ enumerable: true,
54
+ configurable: true,
55
+ writable: true,
56
+ value: void 0
57
+ });
58
+ Object.defineProperty(this, "REFRESH_INTERVAL", {
59
+ enumerable: true,
60
+ configurable: true,
61
+ writable: true,
62
+ value: 5000
63
+ }); // 每5秒最多刷新一次
64
+ this.refreshCallback = refreshCallback;
46
65
  this.on('error', (err) => {
47
66
  if (isClientDisconnectError(err)) {
48
67
  console.warn('[ChunkCollectorTransform] Stream closed (client disconnected)');
@@ -69,6 +88,14 @@ class ChunkCollectorTransform extends stream_1.Transform {
69
88
  }
70
89
  // 将chunk传递给下一个stream
71
90
  this.push(chunk);
91
+ // 节流刷新规则使用状态(仅在有数据流过时触发)
92
+ if (this.refreshCallback) {
93
+ const now = Date.now();
94
+ if (now - this.lastRefreshTime >= this.REFRESH_INTERVAL) {
95
+ this.lastRefreshTime = now;
96
+ this.refreshCallback();
97
+ }
98
+ }
72
99
  callback();
73
100
  }
74
101
  catch (error) {
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ModelRewriteTransform = void 0;
4
+ exports.rewriteResponseModel = rewriteResponseModel;
5
+ const stream_1 = require("stream");
6
+ const string_decoder_1 = require("string_decoder");
7
+ /**
8
+ * 流式 SSE 响应中的 model 字段回写 Transform。
9
+ *
10
+ * 插入位置:SSESerializerTransform 之后、ChunkCollectorTransform 之前。
11
+ * 输入/输出均为 text mode(字符串),通过正则替换 SSE data 行中的
12
+ * "model":"上游模型名" → "model":"客户端原始模型名"。
13
+ */
14
+ class ModelRewriteTransform extends stream_1.Transform {
15
+ constructor(originalModel) {
16
+ super({
17
+ writableObjectMode: false, // 接收序列化后的文本
18
+ readableObjectMode: false, // 输出文本
19
+ });
20
+ Object.defineProperty(this, "modelRegex", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: void 0
25
+ });
26
+ Object.defineProperty(this, "replacement", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: void 0
31
+ });
32
+ Object.defineProperty(this, "modelVersionRegex", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: null
37
+ });
38
+ Object.defineProperty(this, "modelVersionReplacement", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: null
43
+ });
44
+ Object.defineProperty(this, "stringDecoder", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: new string_decoder_1.StringDecoder('utf8')
49
+ });
50
+ Object.defineProperty(this, "buffer", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: ''
55
+ });
56
+ // 转义原始模型名中的特殊字符,防止 JSON 注入
57
+ const escaped = originalModel.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
58
+ this.replacement = `"model":"${escaped}"`;
59
+ this.modelRegex = /"model"\s*:\s*"[^"]*"/g;
60
+ // 处理 Gemini 直通场景中的 modelVersion 字段
61
+ if (originalModel) {
62
+ this.modelVersionReplacement = `"modelVersion":"${escaped}"`;
63
+ this.modelVersionRegex = /"modelVersion"\s*:\s*"[^"]*"/g;
64
+ }
65
+ }
66
+ _transform(chunk, _encoding, callback) {
67
+ try {
68
+ this.buffer += this.stringDecoder.write(chunk);
69
+ // 按行处理(SSE 协议以 \n 分隔)
70
+ const lines = this.buffer.split('\n');
71
+ this.buffer = lines.pop() || '';
72
+ for (const line of lines) {
73
+ this.push(this.rewriteLine(line) + '\n');
74
+ }
75
+ callback();
76
+ }
77
+ catch (error) {
78
+ callback(error);
79
+ }
80
+ }
81
+ _flush(callback) {
82
+ try {
83
+ const remaining = this.stringDecoder.end();
84
+ if (remaining)
85
+ this.buffer += remaining;
86
+ if (this.buffer) {
87
+ this.push(this.rewriteLine(this.buffer));
88
+ }
89
+ callback();
90
+ }
91
+ catch (error) {
92
+ callback(error);
93
+ }
94
+ }
95
+ rewriteLine(line) {
96
+ // 只处理 SSE data 行
97
+ if (!line.startsWith('data:'))
98
+ return line;
99
+ let result = line;
100
+ // 重置正则 lastIndex(因为带 g 标志)
101
+ this.modelRegex.lastIndex = 0;
102
+ result = result.replace(this.modelRegex, this.replacement);
103
+ if (this.modelVersionRegex && this.modelVersionReplacement) {
104
+ this.modelVersionRegex.lastIndex = 0;
105
+ result = result.replace(this.modelVersionRegex, this.modelVersionReplacement);
106
+ }
107
+ return result;
108
+ }
109
+ }
110
+ exports.ModelRewriteTransform = ModelRewriteTransform;
111
+ /**
112
+ * 非流式响应中的 model 字段回写。
113
+ *
114
+ * 将响应对象的 model(以及 Gemini 的 modelVersion)改写为客户端原始模型名。
115
+ * 直接修改传入对象(原地修改)。
116
+ */
117
+ function rewriteResponseModel(responseData, originalModel) {
118
+ if (!responseData || !originalModel || typeof responseData !== 'object')
119
+ return;
120
+ // 所有格式在转换后统一使用 "model" 字段
121
+ if ('model' in responseData) {
122
+ responseData.model = originalModel;
123
+ }
124
+ // Gemini 直通场景可能保留 modelVersion
125
+ if ('modelVersion' in responseData) {
126
+ responseData.modelVersion = originalModel;
127
+ }
128
+ }