Skip to content

Commit 4fd08fe

Browse files
committed
fix: 代码质量优化与杂项修复
1 parent 0b98f41 commit 4fd08fe

27 files changed

Lines changed: 453 additions & 156 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ package.json
88
PULL_REQUEST_TEMPLATE.md
99
plan&change.md
1010
/.vscode/settings.json
11+
ROLE.md

build-ncc.mjs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ console.log('Building with ncc...');
1515
await execAsync('ncc build dist/cli.js -o bundle --minify');
1616

1717
// Copy WASM file
18-
copyFileSync(
19-
'node_modules/sql.js/dist/sql-wasm.wasm',
20-
'bundle/sql-wasm.wasm',
21-
);
18+
copyFileSync('node_modules/sql.js/dist/sql-wasm.wasm', 'bundle/sql-wasm.wasm');
2219

2320
// Rename index.js to cli.cjs
2421
if (existsSync('bundle/index.js')) {

build-shim.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { fileURLToPath as _fileURLToPath } from 'url';
2-
import { dirname as _dirname } from 'path';
3-
import { createRequire as _createRequire } from 'module';
1+
import {fileURLToPath as _fileURLToPath} from 'url';
2+
import {dirname as _dirname} from 'path';
3+
import {createRequire as _createRequire} from 'module';
44

55
export const __filename = _fileURLToPath(import.meta.url);
66
export const __dirname = _dirname(__filename);

build.mjs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,7 @@ if (typeof globalThis.FormData === 'undefined') {
7676
});
7777

7878
// Copy WASM files
79-
copyFileSync(
80-
'node_modules/sql.js/dist/sql-wasm.wasm',
81-
'bundle/sql-wasm.wasm',
82-
);
79+
copyFileSync('node_modules/sql.js/dist/sql-wasm.wasm', 'bundle/sql-wasm.wasm');
8380
copyFileSync(
8481
'node_modules/tiktoken/tiktoken_bg.wasm',
8582
'bundle/tiktoken_bg.wasm',

package-lock.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@
127127
}
128128
},
129129
"prettier": "@vdemedes/prettier-config",
130+
"dependencies": {
131+
"@iarna/toml": "^2.2.5",
132+
"@types/markdown-it": "^14.1.2",
133+
"cli-table3": "^0.6.5"
134+
},
130135
"optionalDependencies": {
131136
"sharp": "^0.34.5"
132137
}

source/api/anthropic.ts

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ async function* parseSSEStream(
399399
): AsyncGenerator<any, void, unknown> {
400400
const decoder = new TextDecoder();
401401
let buffer = '';
402+
let dataCount = 0; // 记录成功解析的数据块数量
403+
let lastEventType = ''; // 记录最后一个事件类型
402404

403405
try {
404406
while (true) {
@@ -407,12 +409,18 @@ async function* parseSSEStream(
407409
if (done) {
408410
// ✅ 关键修复:检查buffer是否有残留数据
409411
if (buffer.trim()) {
410-
// 连接异常中断,抛出明确错误
412+
// 连接异常中断,抛出明确错误,并包含断点信息
413+
const errorContext = {
414+
dataCount,
415+
lastEventType,
416+
bufferLength: buffer.length,
417+
bufferPreview: buffer.substring(0, 200),
418+
};
419+
420+
const errorMessage = `[API_ERROR] [RETRIABLE] Anthropic stream terminated unexpectedly with incomplete data`;
421+
logger.error(errorMessage, errorContext);
411422
throw new Error(
412-
`Stream terminated unexpectedly with incomplete data: ${buffer.substring(
413-
0,
414-
100,
415-
)}...`,
423+
`${errorMessage}. Context: ${JSON.stringify(errorContext)}`,
416424
);
417425
}
418426
break; // 正常结束
@@ -432,7 +440,10 @@ async function* parseSSEStream(
432440

433441
// Handle both "event: " and "event:" formats
434442
if (trimmed.startsWith('event:')) {
435-
// Event type, will be followed by data
443+
// 记录事件类型用于断点恢复
444+
lastEventType = trimmed.startsWith('event: ')
445+
? trimmed.slice(7)
446+
: trimmed.slice(6);
436447
continue;
437448
}
438449

@@ -448,20 +459,34 @@ async function* parseSSEStream(
448459
});
449460

450461
if (parseResult.success) {
462+
dataCount++;
451463
yield parseResult.data;
452464
}
453465
}
454466
}
455467
}
456468
} catch (error) {
457469
const {logger} = await import('../utils/core/logger.js');
458-
logger.error('SSE stream parsing error:', {
470+
471+
// 增强错误日志,包含断点状态
472+
const errorContext = {
459473
error: error instanceof Error ? error.message : 'Unknown error',
460-
remainingBuffer: buffer.substring(0, 200),
461-
});
474+
dataCount,
475+
lastEventType,
476+
bufferLength: buffer.length,
477+
bufferPreview: buffer.substring(0, 200),
478+
};
479+
logger.error(
480+
'[API_ERROR] [RETRIABLE] Anthropic SSE stream parsing error with checkpoint context:',
481+
errorContext,
482+
);
462483
throw error;
463484
}
464485
}
486+
487+
/**
488+
* Create streaming Anthropic completion with retry support
489+
*/
465490
export async function* createStreamingAnthropicCompletion(
466491
options: AnthropicOptions,
467492
abortSignal?: AbortSignal,
@@ -611,13 +636,24 @@ export async function* createStreamingAnthropicCompletion(
611636

612637
if (!response.ok) {
613638
const errorText = await response.text();
614-
throw new Error(
615-
`Anthropic API error: ${response.status} ${response.statusText} - ${errorText}`,
616-
);
639+
const errorMsg = `[API_ERROR] Anthropic API HTTP ${response.status}: ${response.statusText} - ${errorText}`;
640+
logger.error(errorMsg, {
641+
status: response.status,
642+
statusText: response.statusText,
643+
url,
644+
model: requestBody.model,
645+
});
646+
throw new Error(errorMsg);
617647
}
618648

619649
if (!response.body) {
620-
throw new Error('No response body from Anthropic API');
650+
const errorMsg =
651+
'[API_ERROR] No response body from Anthropic API (empty response)';
652+
logger.error(errorMsg, {
653+
url,
654+
model: requestBody.model,
655+
});
656+
throw new Error(errorMsg);
621657
}
622658

623659
let contentBuffer = '';

source/api/chat.ts

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import type {
1515
UsageInfo,
1616
ImageContent,
1717
} from './types.js';
18+
import {logger} from '../utils/core/logger.js';
1819
import {addProxyToFetchOptions} from '../utils/core/proxyUtils.js';
1920
import {saveUsageToFile} from '../utils/core/usageLogger.js';
21+
import { getVersionHeader } from '../utils/core/version.js';
2022

2123
export type {
2224
ChatMessage,
@@ -274,6 +276,7 @@ export interface StreamChunk {
274276
usage?: UsageInfo; // Token usage information
275277
reasoning_content?: string; // Complete reasoning content for DeepSeek R1 models
276278
}
279+
277280
/**
278281
* Parse Server-Sent Events (SSE) stream
279282
*/
@@ -282,6 +285,8 @@ async function* parseSSEStream(
282285
): AsyncGenerator<any, void, unknown> {
283286
const decoder = new TextDecoder();
284287
let buffer = '';
288+
let dataCount = 0; // 记录成功解析的数据块数量
289+
let lastEventType = ''; // 记录最后一个事件类型
285290

286291
try {
287292
while (true) {
@@ -290,12 +295,18 @@ async function* parseSSEStream(
290295
if (done) {
291296
// ✅ 关键修复:检查buffer是否有残留数据
292297
if (buffer.trim()) {
293-
// 连接异常中断,抛出明确错误
298+
// 连接异常中断,抛出明确错误,包含更详细的断点信息
299+
const errorContext = {
300+
dataCount,
301+
lastEventType,
302+
bufferLength: buffer.length,
303+
bufferPreview: buffer.substring(0, 200),
304+
};
305+
306+
const errorMessage = `[API_ERROR] [RETRIABLE] OpenAI stream terminated unexpectedly with incomplete data`;
307+
logger.error(errorMessage, errorContext);
294308
throw new Error(
295-
`Stream terminated unexpectedly with incomplete data: ${buffer.substring(
296-
0,
297-
100,
298-
)}...`,
309+
`${errorMessage}. Context: ${JSON.stringify(errorContext)}`,
299310
);
300311
}
301312
break; // 正常结束
@@ -315,7 +326,10 @@ async function* parseSSEStream(
315326

316327
// Handle both "event: " and "event:" formats
317328
if (trimmed.startsWith('event:')) {
318-
// Event type, will be followed by data
329+
// 记录事件类型用于断点恢复
330+
lastEventType = trimmed.startsWith('event: ')
331+
? trimmed.slice(7)
332+
: trimmed.slice(6);
319333
continue;
320334
}
321335

@@ -331,17 +345,27 @@ async function* parseSSEStream(
331345
});
332346

333347
if (parseResult.success) {
348+
dataCount++;
334349
yield parseResult.data;
335350
}
336351
}
337352
}
338353
}
339354
} catch (error) {
340355
const {logger} = await import('../utils/core/logger.js');
341-
logger.error('SSE stream parsing error:', {
356+
357+
// 增强错误日志,包含断点状态
358+
const errorContext = {
342359
error: error instanceof Error ? error.message : 'Unknown error',
343-
remainingBuffer: buffer.substring(0, 200),
344-
});
360+
dataCount,
361+
lastEventType,
362+
bufferLength: buffer.length,
363+
bufferPreview: buffer.substring(0, 200),
364+
};
365+
logger.error(
366+
'[API_ERROR] [RETRIABLE] OpenAI SSE stream parsing error with checkpoint context:',
367+
errorContext,
368+
);
345369
throw error;
346370
}
347371
}
@@ -458,13 +482,24 @@ export async function* createStreamingChatCompletion(
458482

459483
if (!response.ok) {
460484
const errorText = await response.text();
461-
throw new Error(
462-
`OpenAI API error: ${response.status} ${response.statusText} - ${errorText}`,
463-
);
485+
const errorMsg = `[API_ERROR] OpenAI API HTTP ${response.status}: ${response.statusText} - ${errorText}`;
486+
logger.error(errorMsg, {
487+
status: response.status,
488+
statusText: response.statusText,
489+
url,
490+
model: requestBody.model,
491+
});
492+
throw new Error(errorMsg);
464493
}
465494

466495
if (!response.body) {
467-
throw new Error('No response body from OpenAI API');
496+
const errorMsg =
497+
'[API_ERROR] No response body from OpenAI API (empty response)';
498+
logger.error(errorMsg, {
499+
url,
500+
model: requestBody.model,
501+
});
502+
throw new Error(errorMsg);
468503
}
469504

470505
let contentBuffer = '';

source/api/embedding.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {loadCodebaseConfig} from '../utils/config/codebaseConfig.js';
2+
import {logger} from '../utils/core/logger.js';
23
import {addProxyToFetchOptions} from '../utils/core/proxyUtils.js';
34
import {getVersionHeader} from '../utils/core/version.js';
45

@@ -51,17 +52,23 @@ export async function createEmbeddings(
5152
const {input, task} = options;
5253

5354
if (!model) {
54-
throw new Error('Embedding model name is required');
55+
const errorMsg = '[API_ERROR] Embedding model name is required';
56+
logger.error(errorMsg);
57+
throw new Error(errorMsg);
5558
}
5659
if (!baseUrl) {
57-
throw new Error('Embedding base URL is required');
60+
const errorMsg = '[API_ERROR] Embedding base URL is required';
61+
logger.error(errorMsg);
62+
throw new Error(errorMsg);
5863
}
5964
// API key is optional for local deployments (e.g., Ollama)
6065
// if (!apiKey) {
6166
// throw new Error('Embedding API key is required');
6267
// }
6368
if (!input || input.length === 0) {
64-
throw new Error('Input texts are required');
69+
const errorMsg = '[API_ERROR] Input texts are required for embedding';
70+
logger.error(errorMsg);
71+
throw new Error(errorMsg);
6572
}
6673

6774
// Build request body
@@ -118,7 +125,13 @@ export async function createEmbeddings(
118125

119126
if (!response.ok) {
120127
const errorText = await response.text();
121-
throw new Error(`Embedding API error (${response.status}): ${errorText}`);
128+
const errorMsg = `[API_ERROR] Embedding API HTTP ${response.status}: ${errorText}`;
129+
logger.error(errorMsg, {
130+
status: response.status,
131+
url,
132+
model,
133+
});
134+
throw new Error(errorMsg);
122135
}
123136

124137
const data = await response.json();

0 commit comments

Comments
 (0)