translate comment to english

This commit is contained in:
musistudio
2025-12-30 18:53:16 +08:00
parent 21d35e3d4c
commit 3cb1275e0c
18 changed files with 252 additions and 254 deletions

View File

@@ -21,7 +21,7 @@ import {handlePresetCommand} from "./utils/preset";
const command = process.argv[2]; const command = process.argv[2];
// 定义所有已知命令 // Define all known commands
const KNOWN_COMMANDS = [ const KNOWN_COMMANDS = [
"start", "start",
"stop", "stop",
@@ -95,34 +95,34 @@ async function waitForService(
async function main() { async function main() {
const isRunning = await isServiceRunning() const isRunning = await isServiceRunning()
// 如果命令不是已知命令,检查是否是 preset // If command is not a known command, check if it's a preset
if (command && !KNOWN_COMMANDS.includes(command)) { if (command && !KNOWN_COMMANDS.includes(command)) {
const presetData: any = await readPresetFile(command); const presetData: any = await readPresetFile(command);
if (presetData) { if (presetData) {
// 这是一个 preset执行 code 命令 // This is a preset, execute code command
const codeArgs = process.argv.slice(3); // 获取剩余参数 const codeArgs = process.argv.slice(3); // Get remaining arguments
// 检查 noServer 配置 // Check noServer configuration
const shouldStartServer = presetData.noServer !== true; const shouldStartServer = presetData.noServer !== true;
// 构建环境变量覆盖 // Build environment variable overrides
let envOverrides: Record<string, string> | undefined; let envOverrides: Record<string, string> | undefined;
// 处理 provider 配置(支持新旧两种格式) // Handle provider configuration (supports both old and new formats)
let provider: any = null; let provider: any = null;
// 旧格式:presetData.provider provider 名称 // Old format: presetData.provider is the provider name
if (presetData.provider && typeof presetData.provider === 'string') { if (presetData.provider && typeof presetData.provider === 'string') {
const config = await readConfigFile(); const config = await readConfigFile();
provider = config.Providers?.find((p: any) => p.name === presetData.provider); provider = config.Providers?.find((p: any) => p.name === presetData.provider);
} }
// 新格式:presetData.Providers provider 数组 // New format: presetData.Providers is an array of providers
else if (presetData.Providers && presetData.Providers.length > 0) { else if (presetData.Providers && presetData.Providers.length > 0) {
provider = presetData.Providers[0]; provider = presetData.Providers[0];
} }
// 如果 noServer 不为 true使用本地 server baseurl // If noServer is not true, use local server baseurl
if (shouldStartServer) { if (shouldStartServer) {
const config = await readConfigFile(); const config = await readConfigFile();
const port = config.PORT || 3456; const port = config.PORT || 3456;
@@ -132,7 +132,7 @@ async function main() {
ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}/preset/${presetName}`, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}/preset/${presetName}`,
}; };
} else if (provider) { } else if (provider) {
// 处理 api_base_url,去掉 /v1/messages 后缀 // Handle api_base_url, remove /v1/messages suffix
if (provider.api_base_url) { if (provider.api_base_url) {
let baseUrl = provider.api_base_url; let baseUrl = provider.api_base_url;
if (baseUrl.endsWith('/v1/messages')) { if (baseUrl.endsWith('/v1/messages')) {
@@ -146,7 +146,7 @@ async function main() {
}; };
} }
// 处理 api_key // Handle api_key
if (provider.api_key) { if (provider.api_key) {
envOverrides = { envOverrides = {
...envOverrides, ...envOverrides,
@@ -155,7 +155,7 @@ async function main() {
} }
} }
// 构建 PresetConfig // Build PresetConfig
const presetConfig: PresetConfig = { const presetConfig: PresetConfig = {
noServer: presetData.noServer, noServer: presetData.noServer,
claudeCodeSettings: presetData.claudeCodeSettings, claudeCodeSettings: presetData.claudeCodeSettings,
@@ -187,7 +187,7 @@ async function main() {
process.exit(1); process.exit(1);
} }
} else { } else {
// 服务已运行或不需要启动 server // Service is already running or no need to start server
if (shouldStartServer && !isRunning) { if (shouldStartServer && !isRunning) {
console.error("Service is not running. Please start it first with `ccr start`"); console.error("Service is not running. Please start it first with `ccr start`");
process.exit(1); process.exit(1);
@@ -196,7 +196,7 @@ async function main() {
} }
return; return;
} else { } else {
// 不是 preset 也不是已知命令 // Not a preset nor a known command
console.log(HELP_TEXT); console.log(HELP_TEXT);
process.exit(1); process.exit(1);
} }
@@ -232,7 +232,7 @@ async function main() {
await showStatus(); await showStatus();
break; break;
case "statusline": case "statusline":
// 从stdin读取JSON输入 // Read JSON input from stdin
let inputData = ""; let inputData = "";
process.stdin.setEncoding("utf-8"); process.stdin.setEncoding("utf-8");
process.stdin.on("readable", () => { process.stdin.on("readable", () => {

View File

@@ -30,17 +30,17 @@ export async function executeCodeCommand(
const config = await readConfigFile(); const config = await readConfigFile();
const env = await createEnvVariables(); const env = await createEnvVariables();
// 应用环境变量覆盖(从 preset provider 配置中获取) // Apply environment variable overrides (from preset's provider configuration)
if (envOverrides) { if (envOverrides) {
Object.assign(env, envOverrides); Object.assign(env, envOverrides);
} }
// 构建 settingsFlag // Build settingsFlag
let settingsFlag: ClaudeSettingsFlag = { let settingsFlag: ClaudeSettingsFlag = {
env: env as ClaudeSettingsFlag['env'] env: env as ClaudeSettingsFlag['env']
}; };
// 如果配置了 StatusLine,添加 statusLine // Add statusLine if StatusLine is configured
if (config?.StatusLine?.enabled) { if (config?.StatusLine?.enabled) {
settingsFlag.statusLine = { settingsFlag.statusLine = {
type: "command", type: "command",
@@ -49,12 +49,12 @@ export async function executeCodeCommand(
} }
} }
// 如果 preset 中有 claudeCodeSettings,合并到 settingsFlag // Merge claudeCodeSettings from preset into settingsFlag
if (presetConfig?.claudeCodeSettings) { if (presetConfig?.claudeCodeSettings) {
settingsFlag = { settingsFlag = {
...settingsFlag, ...settingsFlag,
...presetConfig.claudeCodeSettings, ...presetConfig.claudeCodeSettings,
// 深度合并 env // Deep merge env
env: { env: {
...settingsFlag.env, ...settingsFlag.env,
...presetConfig.claudeCodeSettings.env, ...presetConfig.claudeCodeSettings.env,

View File

@@ -1,13 +1,13 @@
/** /**
* 预设导出功能 CLI 层 * Preset export functionality CLI layer
* 负责处理 CLI 交互,核心逻辑在 shared 包中 * Handles CLI interactions, core logic is in shared package
*/ */
import { input } from '@inquirer/prompts'; import { input } from '@inquirer/prompts';
import { readConfigFile } from '../index'; import { readConfigFile } from '../index';
import { exportPreset as exportPresetCore, ExportOptions } from '@CCR/shared'; import { exportPreset as exportPresetCore, ExportOptions } from '@CCR/shared';
// ANSI 颜色代码 // ANSI color codes
const RESET = "\x1B[0m"; const RESET = "\x1B[0m";
const GREEN = "\x1B[32m"; const GREEN = "\x1B[32m";
const BOLDGREEN = "\x1B[1m\x1B[32m"; const BOLDGREEN = "\x1B[1m\x1B[32m";
@@ -15,9 +15,9 @@ const YELLOW = "\x1B[33m";
const BOLDCYAN = "\x1B[1m\x1B[36m"; const BOLDCYAN = "\x1B[1m\x1B[36m";
/** /**
* 导出预设配置CLI 版本,带交互) * Export preset configuration (CLI version, with interaction)
* @param presetName 预设名称 * @param presetName Preset name
* @param options 导出选项 * @param options Export options
*/ */
export async function exportPresetCli( export async function exportPresetCli(
presetName: string, presetName: string,
@@ -28,10 +28,10 @@ export async function exportPresetCli(
console.log(`${BOLDCYAN} Preset Export${RESET}`); console.log(`${BOLDCYAN} Preset Export${RESET}`);
console.log(`${BOLDCYAN}═══════════════════════════════════════════════${RESET}\n`); console.log(`${BOLDCYAN}═══════════════════════════════════════════════${RESET}\n`);
// 1. 读取当前配置 // 1. Read current configuration
const config = await readConfigFile(); const config = await readConfigFile();
// 2. 如果没有通过命令行提供,交互式询问元数据 // 2. Interactively ask for metadata if not provided via command line
if (!options.description) { if (!options.description) {
try { try {
options.description = await input({ options.description = await input({
@@ -39,7 +39,7 @@ export async function exportPresetCli(
default: '', default: '',
}); });
} catch { } catch {
// 用户取消,使用默认值 // User cancelled, use default value
options.description = ''; options.description = '';
} }
} }
@@ -67,10 +67,10 @@ export async function exportPresetCli(
} }
} }
// 3. 调用核心导出功能 // 3. Call core export functionality
const result = await exportPresetCore(presetName, config, options); const result = await exportPresetCore(presetName, config, options);
// 4. 显示摘要 // 4. Display summary
console.log(`\n${BOLDGREEN}✓ Preset exported successfully${RESET}\n`); console.log(`\n${BOLDGREEN}✓ Preset exported successfully${RESET}\n`);
console.log(`${BOLDCYAN}Location:${RESET} ${result.outputPath}\n`); console.log(`${BOLDCYAN}Location:${RESET} ${result.outputPath}\n`);
console.log(`${BOLDCYAN}Summary:${RESET}`); console.log(`${BOLDCYAN}Summary:${RESET}`);
@@ -80,7 +80,7 @@ export async function exportPresetCli(
console.log(` - Sensitive fields sanitized: ${YELLOW}${result.sanitizedCount}${RESET}`); console.log(` - Sensitive fields sanitized: ${YELLOW}${result.sanitizedCount}${RESET}`);
} }
// 显示元数据 // Display metadata
if (result.metadata.description) { if (result.metadata.description) {
console.log(`\n${BOLDCYAN}Description:${RESET} ${result.metadata.description}`); console.log(`\n${BOLDCYAN}Description:${RESET} ${result.metadata.description}`);
} }
@@ -91,7 +91,7 @@ export async function exportPresetCli(
console.log(`${BOLDCYAN}Keywords:${RESET} ${result.metadata.keywords.join(', ')}`); console.log(`${BOLDCYAN}Keywords:${RESET} ${result.metadata.keywords.join(', ')}`);
} }
// 显示分享提示 // Display sharing tips
console.log(`\n${BOLDCYAN}To share this preset:${RESET}`); console.log(`\n${BOLDCYAN}To share this preset:${RESET}`);
console.log(` 1. Share the file: ${result.outputPath}`); console.log(` 1. Share the file: ${result.outputPath}`);
console.log(` 2. Upload to GitHub Gist or your repository`); console.log(` 2. Upload to GitHub Gist or your repository`);

View File

@@ -1,6 +1,6 @@
/** /**
* 动态配置 CLI 交互处理器 * Dynamic configuration CLI interaction handler
* 处理各种输入类型的用户交互 * Handles user interactions for various input types
*/ */
import { import {
@@ -22,7 +22,7 @@ import password from '@inquirer/password';
import checkbox from '@inquirer/checkbox'; import checkbox from '@inquirer/checkbox';
import editor from '@inquirer/editor'; import editor from '@inquirer/editor';
// ANSI 颜色代码 // ANSI color codes
export const COLORS = { export const COLORS = {
RESET: "\x1B[0m", RESET: "\x1B[0m",
GREEN: "\x1B[32m", GREEN: "\x1B[32m",
@@ -34,41 +34,41 @@ export const COLORS = {
}; };
/** /**
* 收集用户输入(支持动态配置) * Collect user input (supports dynamic configuration)
*/ */
export async function collectUserInputs( export async function collectUserInputs(
schema: RequiredInput[], schema: RequiredInput[],
presetConfig: PresetConfigSection, presetConfig: PresetConfigSection,
existingValues?: UserInputValues existingValues?: UserInputValues
): Promise<UserInputValues> { ): Promise<UserInputValues> {
// 按依赖关系排序 // Sort by dependencies
const sortedFields = sortFieldsByDependencies(schema); const sortedFields = sortFieldsByDependencies(schema);
// 初始化值 // Initialize values
const values: UserInputValues = { ...existingValues }; const values: UserInputValues = { ...existingValues };
// 收集所有输入 // Collect all inputs
for (const field of sortedFields) { for (const field of sortedFields) {
// 检查是否应该显示此字段 // Check if this field should be displayed
if (!shouldShowField(field, values)) { if (!shouldShowField(field, values)) {
// 跳过,并清除该字段的值(如果之前存在) // Skip and clear the field value (if it existed before)
delete values[field.id]; delete values[field.id];
continue; continue;
} }
// 如果已有值且不是初始收集,跳过 // Skip if value already exists and not initial collection
if (existingValues && field.id in existingValues) { if (existingValues && field.id in existingValues) {
continue; continue;
} }
// 获取输入值 // Get input value
const value = await promptField(field, presetConfig, values); const value = await promptField(field, presetConfig, values);
// 验证 // Validate
const validation = validateInput(field, value); const validation = validateInput(field, value);
if (!validation.valid) { if (!validation.valid) {
console.error(`${COLORS.YELLOW}Error:${COLORS.RESET} ${validation.error}`); console.error(`${COLORS.YELLOW}Error:${COLORS.RESET} ${validation.error}`);
// 对于必填字段,抛出错误 // Throw error for required fields
if (field.required !== false) { if (field.required !== false) {
throw new Error(validation.error); throw new Error(validation.error);
} }
@@ -82,7 +82,7 @@ export async function collectUserInputs(
} }
/** /**
* 重新收集受影响的字段(当某个字段值变化时) * Recollect affected fields (when a field value changes)
*/ */
export async function recollectAffectedFields( export async function recollectAffectedFields(
changedFieldId: string, changedFieldId: string,
@@ -95,24 +95,24 @@ export async function recollectAffectedFields(
const values = { ...currentValues }; const values = { ...currentValues };
// 对受影响的字段重新收集输入 // Recollect input for affected fields
for (const fieldId of affectedFields) { for (const fieldId of affectedFields) {
const field = sortedFields.find(f => f.id === fieldId); const field = sortedFields.find(f => f.id === fieldId);
if (!field) { if (!field) {
continue; continue;
} }
// 检查是否应该显示 // Check if should be displayed
if (!shouldShowField(field, values)) { if (!shouldShowField(field, values)) {
delete values[field.id]; delete values[field.id];
continue; continue;
} }
// 重新收集输入 // Recollect input
const value = await promptField(field, presetConfig, values); const value = await promptField(field, presetConfig, values);
values[field.id] = value; values[field.id] = value;
// 级联更新:如果这个字段的变化又影响了其他字段 // Cascade update: if this field change affects other fields
const newAffected = getAffectedFields(field.id, schema); const newAffected = getAffectedFields(field.id, schema);
for (const newAffectedId of newAffected) { for (const newAffectedId of newAffected) {
if (!affectedFields.has(newAffectedId)) { if (!affectedFields.has(newAffectedId)) {
@@ -125,7 +125,7 @@ export async function recollectAffectedFields(
} }
/** /**
* 提示单个字段 * Prompt for a single field
*/ */
async function promptField( async function promptField(
field: RequiredInput, field: RequiredInput,
@@ -187,7 +187,7 @@ async function promptField(
return field.defaultValue ?? []; return field.defaultValue ?? [];
} }
// @inquirer/prompts 没有多选,使用 checkbox // @inquirer/prompts doesn't have multi-select, use checkbox
return await checkbox({ return await checkbox({
message, message,
choices: options.map(opt => ({ choices: options.map(opt => ({
@@ -206,7 +206,7 @@ async function promptField(
} }
default: default:
// 默认使用 input // Use input by default
return await input({ return await input({
message, message,
default: field.defaultValue, default: field.defaultValue,
@@ -215,7 +215,7 @@ async function promptField(
} }
/** /**
* 收集敏感信息(兼容旧版) * Collect sensitive information (legacy compatible)
*/ */
export async function collectSensitiveInputs( export async function collectSensitiveInputs(
schema: RequiredInput[], schema: RequiredInput[],
@@ -226,7 +226,7 @@ export async function collectSensitiveInputs(
const values = await collectUserInputs(schema, presetConfig, existingValues); const values = await collectUserInputs(schema, presetConfig, existingValues);
// 显示摘要 // Display summary
console.log(`${COLORS.GREEN}${COLORS.RESET} All required information collected\n`); console.log(`${COLORS.GREEN}${COLORS.RESET} All required information collected\n`);
return values; return values;

View File

@@ -6,37 +6,37 @@ import { readFileSync } from "fs";
const execPromise = promisify(exec); const execPromise = promisify(exec);
/** /**
* 检查是否有新版本可用 * Check if new version is available
* @param currentVersion 当前版本 * @param currentVersion Current version
* @returns 包含更新信息的对象 * @returns Object containing update information
*/ */
export async function checkForUpdates(currentVersion: string) { export async function checkForUpdates(currentVersion: string) {
try { try {
// npm registry获取最新版本信息 // Get latest version info from npm registry
const { stdout } = await execPromise("npm view @musistudio/claude-code-router version"); const { stdout } = await execPromise("npm view @musistudio/claude-code-router version");
const latestVersion = stdout.trim(); const latestVersion = stdout.trim();
// 比较版本 // Compare versions
const hasUpdate = compareVersions(latestVersion, currentVersion) > 0; const hasUpdate = compareVersions(latestVersion, currentVersion) > 0;
// 如果有更新,获取更新日志 // If there is an update, get changelog
let changelog = ""; let changelog = "";
return { hasUpdate, latestVersion, changelog }; return { hasUpdate, latestVersion, changelog };
} catch (error) { } catch (error) {
console.error("Error checking for updates:", error); console.error("Error checking for updates:", error);
// 如果检查失败,假设没有更新 // If check fails, assume no update
return { hasUpdate: false, latestVersion: currentVersion, changelog: "" }; return { hasUpdate: false, latestVersion: currentVersion, changelog: "" };
} }
} }
/** /**
* 执行更新操作 * Perform update operation
* @returns 更新结果 * @returns Update result
*/ */
export async function performUpdate() { export async function performUpdate() {
try { try {
// 执行npm update命令 // Execute npm update command
const { stdout, stderr } = await execPromise("npm update -g @musistudio/claude-code-router"); const { stdout, stderr } = await execPromise("npm update -g @musistudio/claude-code-router");
if (stderr) { if (stderr) {
@@ -59,9 +59,9 @@ export async function performUpdate() {
} }
/** /**
* 比较两个版本号 * Compare two version numbers
* @param v1 版本号1 * @param v1 Version number 1
* @param v2 版本号2 * @param v2 Version number 2
* @returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal * @returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal
*/ */
function compareVersions(v1: string, v2: string): number { function compareVersions(v1: string, v2: string): number {

View File

@@ -235,14 +235,14 @@ export class ForceReasoningTransformer implements Transformer {
} }
} else if (fsmState === "FINAL") { } else if (fsmState === "FINAL") {
if (currentContent.length > 0) { if (currentContent.length > 0) {
// 检查内容是否只包含换行符 // Check if content contains only newlines
const isOnlyNewlines = /^\s*$/.test(currentContent); const isOnlyNewlines = /^\s*$/.test(currentContent);
if (isOnlyNewlines) { if (isOnlyNewlines) {
// 如果只有换行符,添加到缓冲区但不发送 // If only newlines, add to buffer but don't send
finalBuffer += currentContent; finalBuffer += currentContent;
} else { } else {
// 如果有非换行符内容,将缓冲区和新内容一起发送 // If non-whitespace content, send buffer and new content together
const finalPart = finalBuffer + currentContent; const finalPart = finalBuffer + currentContent;
const newDelta = { const newDelta = {
...originalData.choices[0].delta, ...originalData.choices[0].delta,
@@ -258,7 +258,7 @@ export class ForceReasoningTransformer implements Transformer {
controller.enqueue( controller.enqueue(
encoder.encode(`data: ${JSON.stringify(finalChunk)}\n\n`) encoder.encode(`data: ${JSON.stringify(finalChunk)}\n\n`)
); );
// 发送后清空缓冲区 // Clear buffer after sending
finalBuffer = ""; finalBuffer = "";
} }
} }

View File

@@ -68,7 +68,7 @@ Examples:
const encoder = new TextEncoder(); const encoder = new TextEncoder();
let exitToolIndex = -1; let exitToolIndex = -1;
let exitToolResponse = ""; let exitToolResponse = "";
let buffer = ""; // 用于缓冲不完整的数据 let buffer = ""; // Buffer for incomplete data
const stream = new ReadableStream({ const stream = new ReadableStream({
async start(controller) { async start(controller) {
@@ -188,7 +188,7 @@ Examples:
}); });
} catch (error) { } catch (error) {
console.error("Error processing line:", line, error); console.error("Error processing line:", line, error);
// 如果解析失败,直接传递原始行 // If parsing fails, pass through the original line
controller.enqueue(encoder.encode(line + "\n")); controller.enqueue(encoder.encode(line + "\n"));
} }
} }

View File

@@ -68,7 +68,7 @@ export function convertToOpenAI(
request: UnifiedChatRequest request: UnifiedChatRequest
): OpenAIChatRequest { ): OpenAIChatRequest {
const messages: OpenAIMessage[] = []; const messages: OpenAIMessage[] = [];
const toolResponsesQueue: Map<string, any> = new Map(); // 用于存储工具响应 const toolResponsesQueue: Map<string, any> = new Map(); // For storing tool responses
request.messages.forEach((msg) => { request.messages.forEach((msg) => {
if (msg.role === "tool" && msg.tool_call_id) { if (msg.role === "tool" && msg.tool_call_id) {

View File

@@ -91,7 +91,7 @@ const getProjectSpecificRouter = async (
req: any, req: any,
configService: ConfigService configService: ConfigService
) => { ) => {
// 检查是否有项目特定的配置 // Check if there is project-specific configuration
if (req.sessionId) { if (req.sessionId) {
const project = await searchProjectBySession(req.sessionId); const project = await searchProjectBySession(req.sessionId);
if (project) { if (project) {
@@ -102,7 +102,7 @@ const getProjectSpecificRouter = async (
`${req.sessionId}.json` `${req.sessionId}.json`
); );
// 首先尝试读取sessionConfig文件 // First try to read sessionConfig file
try { try {
const sessionConfig = JSON.parse(await readFile(sessionConfigPath, "utf8")); const sessionConfig = JSON.parse(await readFile(sessionConfigPath, "utf8"));
if (sessionConfig && sessionConfig.Router) { if (sessionConfig && sessionConfig.Router) {
@@ -117,7 +117,7 @@ const getProjectSpecificRouter = async (
} catch {} } catch {}
} }
} }
return undefined; // 返回undefined表示使用原始配置 return undefined; // Return undefined to use original configuration
}; };
const getUseModel = async ( const getUseModel = async (
@@ -256,9 +256,9 @@ export const router = async (req: any, _res: any, context: RouterContext) => {
return; return;
}; };
// 内存缓存存储sessionId到项目名称的映射 // Memory cache for sessionId to project name mapping
// null值表示之前已查找过但未找到项目 // null value indicates previously searched but not found
// 使用LRU缓存限制最大1000个条目 // Uses LRU cache with max 1000 entries
const sessionProjectCache = new LRUCache<string, string>({ const sessionProjectCache = new LRUCache<string, string>({
max: 1000, max: 1000,
}); });
@@ -266,7 +266,7 @@ const sessionProjectCache = new LRUCache<string, string>({
export const searchProjectBySession = async ( export const searchProjectBySession = async (
sessionId: string sessionId: string
): Promise<string | null> => { ): Promise<string | null> => {
// 首先检查缓存 // Check cache first
if (sessionProjectCache.has(sessionId)) { if (sessionProjectCache.has(sessionId)) {
const result = sessionProjectCache.get(sessionId); const result = sessionProjectCache.get(sessionId);
if (!result || result === '') { if (!result || result === '') {
@@ -279,14 +279,14 @@ export const searchProjectBySession = async (
const dir = await opendir(CLAUDE_PROJECTS_DIR); const dir = await opendir(CLAUDE_PROJECTS_DIR);
const folderNames: string[] = []; const folderNames: string[] = [];
// 收集所有文件夹名称 // Collect all folder names
for await (const dirent of dir) { for await (const dirent of dir) {
if (dirent.isDirectory()) { if (dirent.isDirectory()) {
folderNames.push(dirent.name); folderNames.push(dirent.name);
} }
} }
// 并发检查每个项目文件夹中是否存在sessionId.jsonl文件 // Concurrently check each project folder for sessionId.jsonl file
const checkPromises = folderNames.map(async (folderName) => { const checkPromises = folderNames.map(async (folderName) => {
const sessionFilePath = join( const sessionFilePath = join(
CLAUDE_PROJECTS_DIR, CLAUDE_PROJECTS_DIR,
@@ -297,28 +297,28 @@ export const searchProjectBySession = async (
const fileStat = await stat(sessionFilePath); const fileStat = await stat(sessionFilePath);
return fileStat.isFile() ? folderName : null; return fileStat.isFile() ? folderName : null;
} catch { } catch {
// 文件不存在,继续检查下一个 // File does not exist, continue checking next
return null; return null;
} }
}); });
const results = await Promise.all(checkPromises); const results = await Promise.all(checkPromises);
// 返回第一个存在的项目目录名称 // Return the first existing project directory name
for (const result of results) { for (const result of results) {
if (result) { if (result) {
// 缓存找到的结果 // Cache the found result
sessionProjectCache.set(sessionId, result); sessionProjectCache.set(sessionId, result);
return result; return result;
} }
} }
// 缓存未找到的结果null值表示之前已查找过但未找到项目 // Cache not found result (null value means previously searched but not found)
sessionProjectCache.set(sessionId, ''); sessionProjectCache.set(sessionId, '');
return null; // 没有找到匹配的项目 return null; // No matching project found
} catch (error) { } catch (error) {
console.error("Error searching for project by session:", error); console.error("Error searching for project by session:", error);
// 出错时也缓存null结果避免重复出错 // Cache null result on error to avoid repeated errors
sessionProjectCache.set(sessionId, ''); sessionProjectCache.set(sessionId, '');
return null; return null;
} }

View File

@@ -2,13 +2,11 @@ import JSON5 from "json5";
import { jsonrepair } from "jsonrepair"; import { jsonrepair } from "jsonrepair";
/** /**
* 解析工具调用参数的函数
* Parse tool call arguments function * Parse tool call arguments function
* 先尝试标准JSON解析然后JSON5解析最后使用jsonrepair进行安全修复
* First try standard JSON parsing, then JSON5 parsing, finally use jsonrepair for safe repair * First try standard JSON parsing, then JSON5 parsing, finally use jsonrepair for safe repair
* *
* @param argsString - 需要解析的参数字符串 / Parameter string to parse * @param argsString - Parameter string to parse
* @returns 解析后的参数对象或安全的空对象 / Parsed parameter object or safe empty object * @returns Parsed parameter object or safe empty object
*/ */
export function parseToolArguments(argsString: string, logger?: any): string { export function parseToolArguments(argsString: string, logger?: any): string {
// Handle empty or null input // Handle empty or null input
@@ -25,25 +23,25 @@ export function parseToolArguments(argsString: string, logger?: any): string {
try { try {
// Second attempt: JSON5 parsing for relaxed syntax // Second attempt: JSON5 parsing for relaxed syntax
const args = JSON5.parse(argsString); const args = JSON5.parse(argsString);
logger?.debug(`工具调用参数JSON5解析成功 / Tool arguments JSON5 parsing successful`); logger?.debug(`Tool arguments JSON5 parsing successful`);
return JSON.stringify(args); return JSON.stringify(args);
} catch (json5Error: any) { } catch (json5Error: any) {
try { try {
// Third attempt: Safe JSON repair without code execution // Third attempt: Safe JSON repair without code execution
const repairedJson = jsonrepair(argsString); const repairedJson = jsonrepair(argsString);
logger?.debug(`工具调用参数安全修复成功 / Tool arguments safely repaired`); logger?.debug(`Tool arguments safely repaired`);
return repairedJson; return repairedJson;
} catch (repairError: any) { } catch (repairError: any) {
// All parsing attempts failed - log errors and return safe fallback // All parsing attempts failed - log errors and return safe fallback
logger?.error( logger?.error(
`JSON解析失败 / JSON parsing failed: ${jsonError.message}. ` + `JSON parsing failed: ${jsonError.message}. ` +
`JSON5解析失败 / JSON5 parsing failed: ${json5Error.message}. ` + `JSON5 parsing failed: ${json5Error.message}. ` +
`JSON修复失败 / JSON repair failed: ${repairError.message}. ` + `JSON repair failed: ${repairError.message}. ` +
`输入数据 / Input data: ${JSON.stringify(argsString)}` `Input data: ${JSON.stringify(argsString)}`
); );
// Return safe empty object as fallback instead of potentially malformed input // Return safe empty object as fallback instead of potentially malformed input
logger?.debug(`返回安全的空对象作为后备方案 / Returning safe empty object as fallback`); logger?.debug(`Returning safe empty object as fallback`);
return "{}"; return "{}";
} }
} }

View File

@@ -1,6 +1,6 @@
import { UnifiedChatRequest, UnifiedMessage, UnifiedTool } from "../types/llm"; import { UnifiedChatRequest, UnifiedMessage, UnifiedTool } from "../types/llm";
// Vertex Claude消息接口 // Vertex Claude message interface
interface ClaudeMessage { interface ClaudeMessage {
role: "user" | "assistant"; role: "user" | "assistant";
content: Array<{ content: Array<{
@@ -14,7 +14,7 @@ interface ClaudeMessage {
}>; }>;
} }
// Vertex Claude工具接口 // Vertex Claude tool interface
interface ClaudeTool { interface ClaudeTool {
name: string; name: string;
description: string; description: string;
@@ -27,7 +27,7 @@ interface ClaudeTool {
}; };
} }
// Vertex Claude请求接口 // Vertex Claude request interface
interface VertexClaudeRequest { interface VertexClaudeRequest {
anthropic_version: "vertex-2023-10-16"; anthropic_version: "vertex-2023-10-16";
messages: ClaudeMessage[]; messages: ClaudeMessage[];
@@ -40,7 +40,7 @@ interface VertexClaudeRequest {
tool_choice?: "auto" | "none" | { type: "tool"; name: string }; tool_choice?: "auto" | "none" | { type: "tool"; name: string };
} }
// Vertex Claude响应接口 // Vertex Claude response interface
interface VertexClaudeResponse { interface VertexClaudeResponse {
content: Array<{ content: Array<{
type: "text"; type: "text";
@@ -76,7 +76,7 @@ export function buildRequestBody(
const content: ClaudeMessage["content"] = []; const content: ClaudeMessage["content"] = [];
if (typeof message.content === "string") { if (typeof message.content === "string") {
// 保留所有字符串内容,即使是空字符串,因为可能包含重要信息 // Keep all string content, even empty strings, as it may contain important information
content.push({ content.push({
type: "text", type: "text",
text: message.content, text: message.content,
@@ -84,13 +84,13 @@ export function buildRequestBody(
} else if (Array.isArray(message.content)) { } else if (Array.isArray(message.content)) {
message.content.forEach((item) => { message.content.forEach((item) => {
if (item.type === "text") { if (item.type === "text") {
// 保留所有文本内容,即使是空字符串 // Keep all text content, even empty strings
content.push({ content.push({
type: "text", type: "text",
text: item.text || "", text: item.text || "",
}); });
} else if (item.type === "image_url") { } else if (item.type === "image_url") {
// 处理图片内容 // Handle image content
content.push({ content.push({
type: "image", type: "image",
source: { source: {
@@ -103,7 +103,7 @@ export function buildRequestBody(
}); });
} }
// 只跳过完全空的非最后一条消息(没有内容和工具调用) // Only skip completely empty non-last messages (no content and no tool calls)
if ( if (
!isLastMessage && !isLastMessage &&
content.length === 0 && content.length === 0 &&
@@ -113,7 +113,7 @@ export function buildRequestBody(
continue; continue;
} }
// 对于最后一条 assistant 消息,如果没有内容但有工具调用,则添加空内容 // For last assistant message, add empty content if no content but has tool calls
if ( if (
isLastMessage && isLastMessage &&
isAssistantMessage && isAssistantMessage &&
@@ -140,7 +140,7 @@ export function buildRequestBody(
...(request.temperature && { temperature: request.temperature }), ...(request.temperature && { temperature: request.temperature }),
}; };
// 处理工具定义 // Handle tool definitions
if (request.tools && request.tools.length > 0) { if (request.tools && request.tools.length > 0) {
requestBody.tools = request.tools.map((tool: UnifiedTool) => ({ requestBody.tools = request.tools.map((tool: UnifiedTool) => ({
name: tool.function.name, name: tool.function.name,
@@ -149,12 +149,12 @@ export function buildRequestBody(
})); }));
} }
// 处理工具选择 // Handle tool choice
if (request.tool_choice) { if (request.tool_choice) {
if (request.tool_choice === "auto" || request.tool_choice === "none") { if (request.tool_choice === "auto" || request.tool_choice === "none") {
requestBody.tool_choice = request.tool_choice; requestBody.tool_choice = request.tool_choice;
} else if (typeof request.tool_choice === "string") { } else if (typeof request.tool_choice === "string") {
// 如果 tool_choice 是字符串,假设是工具名称 // If tool_choice is a string, assume it's the tool name
requestBody.tool_choice = { requestBody.tool_choice = {
type: "tool", type: "tool",
name: request.tool_choice, name: request.tool_choice,
@@ -206,7 +206,7 @@ export function transformRequestOut(
stream: vertexRequest.stream, stream: vertexRequest.stream,
}; };
// 处理工具定义 // Handle tool definitions
if (vertexRequest.tools && vertexRequest.tools.length > 0) { if (vertexRequest.tools && vertexRequest.tools.length > 0) {
result.tools = vertexRequest.tools.map((tool) => ({ result.tools = vertexRequest.tools.map((tool) => ({
type: "function" as const, type: "function" as const,
@@ -224,7 +224,7 @@ export function transformRequestOut(
})); }));
} }
// 处理工具选择 // Handle tool choice
if (vertexRequest.tool_choice) { if (vertexRequest.tool_choice) {
if (typeof vertexRequest.tool_choice === "string") { if (typeof vertexRequest.tool_choice === "string") {
result.tool_choice = vertexRequest.tool_choice; result.tool_choice = vertexRequest.tool_choice;
@@ -244,7 +244,7 @@ export async function transformResponseOut(
if (response.headers.get("Content-Type")?.includes("application/json")) { if (response.headers.get("Content-Type")?.includes("application/json")) {
const jsonResponse = (await response.json()) as VertexClaudeResponse; const jsonResponse = (await response.json()) as VertexClaudeResponse;
// 处理工具调用 // Handle tool calls
let tool_calls = undefined; let tool_calls = undefined;
if (jsonResponse.tool_use && jsonResponse.tool_use.length > 0) { if (jsonResponse.tool_use && jsonResponse.tool_use.length > 0) {
tool_calls = jsonResponse.tool_use.map((tool) => ({ tool_calls = jsonResponse.tool_use.map((tool) => ({
@@ -257,7 +257,7 @@ export async function transformResponseOut(
})); }));
} }
// 转换为OpenAI格式的响应 // Convert to OpenAI format response
const res = { const res = {
id: jsonResponse.id, id: jsonResponse.id,
choices: [ choices: [
@@ -288,7 +288,7 @@ export async function transformResponseOut(
headers: response.headers, headers: response.headers,
}); });
} else if (response.headers.get("Content-Type")?.includes("stream")) { } else if (response.headers.get("Content-Type")?.includes("stream")) {
// 处理流式响应 // Handle streaming response
if (!response.body) { if (!response.body) {
return response; return response;
} }
@@ -307,12 +307,12 @@ export async function transformResponseOut(
try { try {
const chunk = JSON.parse(chunkStr); const chunk = JSON.parse(chunkStr);
// 处理 Anthropic 原生格式的流式响应 // Handle Anthropic native format streaming response
if ( if (
chunk.type === "content_block_delta" && chunk.type === "content_block_delta" &&
chunk.delta?.type === "text_delta" chunk.delta?.type === "text_delta"
) { ) {
// 这是 Anthropic 原生格式,需要转换为 OpenAI 格式 // This is Anthropic native format, need to convert to OpenAI format
const res = { const res = {
choices: [ choices: [
{ {
@@ -345,7 +345,7 @@ export async function transformResponseOut(
chunk.type === "content_block_delta" && chunk.type === "content_block_delta" &&
chunk.delta?.type === "input_json_delta" chunk.delta?.type === "input_json_delta"
) { ) {
// 处理工具调用的参数增量 // Handle tool call argument delta
const res = { const res = {
choices: [ choices: [
{ {
@@ -384,7 +384,7 @@ export async function transformResponseOut(
chunk.type === "content_block_start" && chunk.type === "content_block_start" &&
chunk.content_block?.type === "tool_use" chunk.content_block?.type === "tool_use"
) { ) {
// 处理工具调用开始 // Handle tool call start
const res = { const res = {
choices: [ choices: [
{ {
@@ -423,7 +423,7 @@ export async function transformResponseOut(
encoder.encode(`data: ${JSON.stringify(res)}\n\n`) encoder.encode(`data: ${JSON.stringify(res)}\n\n`)
); );
} else if (chunk.type === "message_delta") { } else if (chunk.type === "message_delta") {
// 处理消息结束 // Handle message end
const res = { const res = {
choices: [ choices: [
{ {
@@ -457,10 +457,10 @@ export async function transformResponseOut(
encoder.encode(`data: ${JSON.stringify(res)}\n\n`) encoder.encode(`data: ${JSON.stringify(res)}\n\n`)
); );
} else if (chunk.type === "message_stop") { } else if (chunk.type === "message_stop") {
// 发送结束标记 // Send end marker
controller.enqueue(encoder.encode(`data: [DONE]\n\n`)); controller.enqueue(encoder.encode(`data: [DONE]\n\n`));
} else { } else {
// 处理其他格式的响应(保持原有逻辑作为后备) // Handle other format responses (keep original logic as fallback)
const res = { const res = {
choices: [ choices: [
{ {

View File

@@ -5,25 +5,25 @@ export class AgentsManager {
private agents: Map<string, IAgent> = new Map(); private agents: Map<string, IAgent> = new Map();
/** /**
* 注册一个agent * Register an agent
* @param agent 要注册的agent实例 * @param agent The agent instance to register
* @param isDefault 是否设为默认agent * @param isDefault Whether to set as default agent
*/ */
registerAgent(agent: IAgent): void { registerAgent(agent: IAgent): void {
this.agents.set(agent.name, agent); this.agents.set(agent.name, agent);
} }
/** /**
* 根据名称查找agent * Find agent by name
* @param name agent名称 * @param name Agent name
* @returns 找到的agent实例未找到返回undefined * @returns Found agent instance, undefined if not found
*/ */
getAgent(name: string): IAgent | undefined { getAgent(name: string): IAgent | undefined {
return this.agents.get(name); return this.agents.get(name);
} }
/** /**
* 获取所有已注册的agents * Get all registered agents
* @returns 所有agent实例的数组 * @returns Array of all agent instances
*/ */
getAllAgents(): IAgent[] { getAllAgents(): IAgent[] {
return Array.from(this.agents.values()); return Array.from(this.agents.values());
@@ -31,8 +31,8 @@ export class AgentsManager {
/** /**
* 获取所有agent的工具 * Get all agent tools
* @returns 工具数组 * @returns Array of tools
*/ */
getAllTools(): any[] { getAllTools(): any[] {
const allTools: any[] = []; const allTools: any[] = [];

View File

@@ -94,13 +94,13 @@ async function getServer(options: RunOptions = {}) {
let loggerConfig: any; let loggerConfig: any;
// 如果外部传入了 logger 配置,使用外部的 // Use external logger configuration if provided
if (options.logger !== undefined) { if (options.logger !== undefined) {
loggerConfig = options.logger; loggerConfig = options.logger;
} else { } else {
// 如果没有传入,并且 config.LOG !== false,则启用 logger // Enable logger if not provided and config.LOG !== false
if (config.LOG !== false) { if (config.LOG !== false) {
// config.LOG 设为 true(如果它还未设置) // Set config.LOG to true (if not already set)
if (config.LOG === undefined) { if (config.LOG === undefined) {
config.LOG = true; config.LOG = true;
} }
@@ -166,7 +166,7 @@ async function getServer(options: RunOptions = {}) {
for (const agent of agentsManager.getAllAgents()) { for (const agent of agentsManager.getAllAgents()) {
if (agent.shouldHandle(req, config)) { if (agent.shouldHandle(req, config)) {
// 设置agent标识 // Set agent identifier
useAgents.push(agent.name) useAgents.push(agent.name)
// change request body // change request body
@@ -209,10 +209,10 @@ async function getServer(options: RunOptions = {}) {
let currentToolId = '' let currentToolId = ''
const toolMessages: any[] = [] const toolMessages: any[] = []
const assistantMessages: any[] = [] const assistantMessages: any[] = []
// 存储Anthropic格式的消息体,区分文本和工具类型 // Store Anthropic format message body, distinguishing text and tool types
return done(null, rewriteStream(eventStream, async (data, controller) => { return done(null, rewriteStream(eventStream, async (data, controller) => {
try { try {
// 检测工具调用开始 // Detect tool call start
if (data.event === 'content_block_start' && data?.data?.content_block?.name) { if (data.event === 'content_block_start' && data?.data?.content_block?.name) {
const agent = req.agents.find((name: string) => agentsManager.getAgent(name)?.tools.get(data.data.content_block.name)) const agent = req.agents.find((name: string) => agentsManager.getAgent(name)?.tools.get(data.data.content_block.name))
if (agent) { if (agent) {
@@ -224,13 +224,13 @@ async function getServer(options: RunOptions = {}) {
} }
} }
// 收集工具参数 // Collect tool arguments
if (currentToolIndex > -1 && data.data.index === currentToolIndex && data.data?.delta?.type === 'input_json_delta') { if (currentToolIndex > -1 && data.data.index === currentToolIndex && data.data?.delta?.type === 'input_json_delta') {
currentToolArgs += data.data?.delta?.partial_json; currentToolArgs += data.data?.delta?.partial_json;
return undefined; return undefined;
} }
// 工具调用完成处理agent调用 // Tool call completed, handle agent invocation
if (currentToolIndex > -1 && data.data.index === currentToolIndex && data.data.type === 'content_block_stop') { if (currentToolIndex > -1 && data.data.index === currentToolIndex && data.data.type === 'content_block_stop') {
try { try {
const args = JSON5.parse(currentToolArgs); const args = JSON5.parse(currentToolArgs);
@@ -293,7 +293,7 @@ async function getServer(options: RunOptions = {}) {
continue continue
} }
// 检查流是否仍然可写 // Check if stream is still writable
if (!controller.desiredSize) { if (!controller.desiredSize) {
break; break;
} }
@@ -301,7 +301,7 @@ async function getServer(options: RunOptions = {}) {
controller.enqueue(eventData) controller.enqueue(eventData)
}catch (readError: any) { }catch (readError: any) {
if (readError.name === 'AbortError' || readError.code === 'ERR_STREAM_PREMATURE_CLOSE') { if (readError.name === 'AbortError' || readError.code === 'ERR_STREAM_PREMATURE_CLOSE') {
abortController.abort(); // 中止所有相关操作 abortController.abort(); // Abort all related operations
break; break;
} }
throw readError; throw readError;
@@ -314,13 +314,13 @@ async function getServer(options: RunOptions = {}) {
}catch (error: any) { }catch (error: any) {
console.error('Unexpected error in stream processing:', error); console.error('Unexpected error in stream processing:', error);
// 处理流提前关闭的错误 // Handle premature stream closure error
if (error.code === 'ERR_STREAM_PREMATURE_CLOSE') { if (error.code === 'ERR_STREAM_PREMATURE_CLOSE') {
abortController.abort(); abortController.abort();
return undefined; return undefined;
} }
// 其他错误仍然抛出 // Re-throw other errors
throw error; throw error;
} }
}).pipeThrough(new SSESerializerTransform())) }).pipeThrough(new SSESerializerTransform()))
@@ -405,7 +405,7 @@ export type { RunOptions };
export type { IAgent, ITool } from "./agents/type"; export type { IAgent, ITool } from "./agents/type";
export { initDir, initConfig, readConfigFile, writeConfigFile, backupConfigFile } from "./utils"; export { initDir, initConfig, readConfigFile, writeConfigFile, backupConfigFile } from "./utils";
// 如果是直接运行此文件,则启动服务 // Start service if this file is run directly
if (require.main === module) { if (require.main === module) {
run().catch((error) => { run().catch((error) => {
console.error('Failed to start server:', error); console.error('Failed to start server:', error);

View File

@@ -8,7 +8,7 @@ export class SSEParserTransform extends TransformStream<string, any> {
this.buffer += chunk; this.buffer += chunk;
const lines = this.buffer.split('\n'); const lines = this.buffer.split('\n');
// 保留最后一行(可能不完整) // Keep last line (may be incomplete)
this.buffer = lines.pop() || ''; this.buffer = lines.pop() || '';
for (const line of lines) { for (const line of lines) {
@@ -19,14 +19,14 @@ export class SSEParserTransform extends TransformStream<string, any> {
} }
}, },
flush: (controller) => { flush: (controller) => {
// 处理缓冲区中剩余的内容 // Process remaining content in buffer
if (this.buffer.trim()) { if (this.buffer.trim()) {
const events: any[] = []; const events: any[] = [];
this.processLine(this.buffer.trim(), events); this.processLine(this.buffer.trim(), events);
events.forEach(event => controller.enqueue(event)); events.forEach(event => controller.enqueue(event));
} }
// 推送最后一个事件(如果有) // Push last event (if any)
if (Object.keys(this.currentEvent).length > 0) { if (Object.keys(this.currentEvent).length > 0) {
controller.enqueue(this.currentEvent); controller.enqueue(this.currentEvent);
} }

View File

@@ -1,5 +1,5 @@
/**rewriteStream /**rewriteStream
* 读取源readablestream,返回一个新的readablestream,由processor对源数据进行处理后将返回的新值推送到新的stream如果没有返回值则不推送 * Read source readablestream and return a new readablestream, processor processes source data and pushes returned new value to new stream, no push if no return value
* @param stream * @param stream
* @param processor * @param processor
*/ */

View File

@@ -1,6 +1,6 @@
/** /**
* 预设导出核心功能 * Preset export core functionality
* 注意:这个模块不包含 CLI 交互逻辑,交互逻辑由调用者提供 * Note: This module does not contain CLI interaction logic, interaction logic is provided by the caller
*/ */
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
@@ -12,7 +12,7 @@ import { PresetFile, PresetMetadata, ManifestFile } from './types';
import { HOME_DIR } from '../constants'; import { HOME_DIR } from '../constants';
/** /**
* 导出选项 * Export options
*/ */
export interface ExportOptions { export interface ExportOptions {
output?: string; output?: string;
@@ -23,7 +23,7 @@ export interface ExportOptions {
} }
/** /**
* 导出结果 * Export result
*/ */
export interface ExportResult { export interface ExportResult {
outputPath: string; outputPath: string;
@@ -34,11 +34,11 @@ export interface ExportResult {
} }
/** /**
* 创建 manifest 对象 * Create manifest object
* @param presetName 预设名称 * @param presetName Preset name
* @param config 配置对象 * @param config Configuration object
* @param sanitizedConfig 脱敏后的配置 * @param sanitizedConfig Sanitized configuration
* @param options 导出选项 * @param options Export options
*/ */
export function createManifest( export function createManifest(
presetName: string, presetName: string,
@@ -63,18 +63,18 @@ export function createManifest(
} }
/** /**
* 导出预设配置 * Export preset configuration
* @param presetName 预设名称 * @param presetName Preset name
* @param config 当前配置 * @param config Current configuration
* @param options 导出选项 * @param options Export options
* @returns 导出结果 * @returns Export result
*/ */
export async function exportPreset( export async function exportPreset(
presetName: string, presetName: string,
config: any, config: any,
options: ExportOptions = {} options: ExportOptions = {}
): Promise<ExportResult> { ): Promise<ExportResult> {
// 1. 收集元数据 // 1. Collect metadata
const metadata: PresetMetadata = { const metadata: PresetMetadata = {
name: presetName, name: presetName,
version: '1.0.0', version: '1.0.0',
@@ -83,28 +83,28 @@ export async function exportPreset(
keywords: options.tags ? options.tags.split(',').map(t => t.trim()) : undefined, keywords: options.tags ? options.tags.split(',').map(t => t.trim()) : undefined,
}; };
// 2. 脱敏配置 // 2. Sanitize configuration
const { sanitizedConfig, requiredInputs, sanitizedCount } = await sanitizeConfig(config); const { sanitizedConfig, requiredInputs, sanitizedCount } = await sanitizeConfig(config);
// 3. 生成manifest.json(扁平化结构) // 3. Generate manifest.json (flattened structure)
const manifest: ManifestFile = { const manifest: ManifestFile = {
...metadata, ...metadata,
...sanitizedConfig, ...sanitizedConfig,
requiredInputs: options.includeSensitive ? undefined : requiredInputs, requiredInputs: options.includeSensitive ? undefined : requiredInputs,
}; };
// 4. 确定输出路径 // 4. Determine output path
const presetsDir = path.join(HOME_DIR, 'presets'); const presetsDir = path.join(HOME_DIR, 'presets');
// 确保预设目录存在 // Ensure presets directory exists
await fs.mkdir(presetsDir, { recursive: true }); await fs.mkdir(presetsDir, { recursive: true });
const outputPath = options.output || path.join(presetsDir, `${presetName}.ccrsets`); const outputPath = options.output || path.join(presetsDir, `${presetName}.ccrsets`);
// 5. 创建压缩包 // 5. Create archive
const output = fsSync.createWriteStream(outputPath); const output = fsSync.createWriteStream(outputPath);
const archive = archiver('zip', { const archive = archiver('zip', {
zlib: { level: 9 } // 最高压缩级别 zlib: { level: 9 } // Highest compression level
}); });
return new Promise<ExportResult>((resolve, reject) => { return new Promise<ExportResult>((resolve, reject) => {
@@ -122,13 +122,13 @@ export async function exportPreset(
reject(err); reject(err);
}); });
// 连接输出流 // Connect output stream
archive.pipe(output); archive.pipe(output);
// 添加manifest.json到压缩包 // Add manifest.json to archive
archive.append(JSON.stringify(manifest, null, 2), { name: 'manifest.json' }); archive.append(JSON.stringify(manifest, null, 2), { name: 'manifest.json' });
// 完成压缩 // Finalize archive
archive.finalize(); archive.finalize();
}); });
} }

View File

@@ -1,12 +1,12 @@
/** /**
* 配置合并策略 * Configuration merge strategies
*/ */
import { MergeStrategy, ProviderConfig, RouterConfig, TransformerConfig } from './types'; import { MergeStrategy, ProviderConfig, RouterConfig, TransformerConfig } from './types';
/** /**
* 合并 Provider 配置 * Merge Provider configuration
* 如果 provider 已存在则直接覆盖,否则添加 * Overwrite if provider exists, otherwise add
*/ */
function mergeProviders( function mergeProviders(
existing: ProviderConfig[], existing: ProviderConfig[],
@@ -18,10 +18,10 @@ function mergeProviders(
for (const provider of incoming) { for (const provider of incoming) {
const existingIndex = existingNames.get(provider.name); const existingIndex = existingNames.get(provider.name);
if (existingIndex !== undefined) { if (existingIndex !== undefined) {
// Provider 已存在,直接覆盖 // Provider exists, overwrite directly
result[existingIndex] = provider; result[existingIndex] = provider;
} else { } else {
// 新 Provider,直接添加 // New provider, add directly
result.push(provider); result.push(provider);
} }
} }
@@ -30,7 +30,7 @@ function mergeProviders(
} }
/** /**
* 合并 Router 配置 * Merge Router configuration
*/ */
async function mergeRouter( async function mergeRouter(
existing: RouterConfig, existing: RouterConfig,
@@ -48,10 +48,10 @@ async function mergeRouter(
const existingValue = result[key]; const existingValue = result[key];
if (existingValue === undefined || existingValue === null) { if (existingValue === undefined || existingValue === null) {
// 现有配置中没有这个路由规则,直接添加 // No such routing rule in existing config, add directly
result[key] = value; result[key] = value;
} else { } else {
// 存在冲突 // Conflict exists
if (strategy === MergeStrategy.ASK && onRouterConflict) { if (strategy === MergeStrategy.ASK && onRouterConflict) {
const shouldOverwrite = await onRouterConflict(key, existingValue, value); const shouldOverwrite = await onRouterConflict(key, existingValue, value);
if (shouldOverwrite) { if (shouldOverwrite) {
@@ -60,10 +60,10 @@ async function mergeRouter(
} else if (strategy === MergeStrategy.OVERWRITE) { } else if (strategy === MergeStrategy.OVERWRITE) {
result[key] = value; result[key] = value;
} else if (strategy === MergeStrategy.MERGE) { } else if (strategy === MergeStrategy.MERGE) {
// 对于 Routermerge 策略等同于 skip保留现有值 // For Router, merge strategy equals skip, keep existing value
// 或者可以询问用户 // Or can ask user
} }
// skip 策略:保留现有值,不做任何操作 // skip strategy: keep existing value, do nothing
} }
} }
@@ -71,7 +71,7 @@ async function mergeRouter(
} }
/** /**
* 合并 Transformer 配置 * Merge Transformer configuration
*/ */
async function mergeTransformers( async function mergeTransformers(
existing: TransformerConfig[], existing: TransformerConfig[],
@@ -87,33 +87,33 @@ async function mergeTransformers(
return existing; return existing;
} }
// Transformer 合并逻辑:按路径匹配 // Transformer merge logic: match by path
const result = [...existing]; const result = [...existing];
const existingPaths = new Set(existing.map(t => t.path)); const existingPaths = new Set(existing.map(t => t.path));
for (const transformer of incoming) { for (const transformer of incoming) {
if (!transformer.path) { if (!transformer.path) {
// 没有 path 的 transformer直接添加 // Transformer without path, add directly
result.push(transformer); result.push(transformer);
continue; continue;
} }
if (existingPaths.has(transformer.path)) { if (existingPaths.has(transformer.path)) {
// 已存在相同 path 的 transformer // Transformer with same path already exists
if (strategy === MergeStrategy.ASK && onTransformerConflict) { if (strategy === MergeStrategy.ASK && onTransformerConflict) {
const action = await onTransformerConflict(transformer.path); const action = await onTransformerConflict(transformer.path);
if (action === 'overwrite') { if (action === 'overwrite') {
const index = result.findIndex(t => t.path === transformer.path); const index = result.findIndex(t => t.path === transformer.path);
result[index] = transformer; result[index] = transformer;
} }
// keep skip 都不做操作 // keep and skip do nothing
} else if (strategy === MergeStrategy.OVERWRITE) { } else if (strategy === MergeStrategy.OVERWRITE) {
const index = result.findIndex(t => t.path === transformer.path); const index = result.findIndex(t => t.path === transformer.path);
result[index] = transformer; result[index] = transformer;
} }
// merge skip 策略:保留现有 // merge and skip strategies: keep existing
} else { } else {
// transformer,直接添加 // New transformer, add directly
result.push(transformer); result.push(transformer);
} }
} }
@@ -122,7 +122,7 @@ async function mergeTransformers(
} }
/** /**
* 合并其他顶级配置 * Merge other top-level configurations
*/ */
async function mergeOtherConfig( async function mergeOtherConfig(
existing: any, existing: any,
@@ -145,10 +145,10 @@ async function mergeOtherConfig(
const existingValue = result[key]; const existingValue = result[key];
if (existingValue === undefined || existingValue === null) { if (existingValue === undefined || existingValue === null) {
// 现有配置中没有这个字段,直接添加 // No such field in existing config, add directly
result[key] = value; result[key] = value;
} else { } else {
// 存在冲突 // Conflict exists
if (strategy === MergeStrategy.ASK && onConfigConflict) { if (strategy === MergeStrategy.ASK && onConfigConflict) {
const shouldOverwrite = await onConfigConflict(key); const shouldOverwrite = await onConfigConflict(key);
if (shouldOverwrite) { if (shouldOverwrite) {
@@ -157,7 +157,7 @@ async function mergeOtherConfig(
} else if (strategy === MergeStrategy.OVERWRITE) { } else if (strategy === MergeStrategy.OVERWRITE) {
result[key] = value; result[key] = value;
} }
// merge skip 策略:保留现有 // merge and skip strategies: keep existing
} }
} }
@@ -165,7 +165,7 @@ async function mergeOtherConfig(
} }
/** /**
* 合并交互回调接口 * Merge interaction callback interface
*/ */
export interface MergeCallbacks { export interface MergeCallbacks {
onRouterConflict?: (key: string, existingValue: any, newValue: any) => Promise<boolean>; onRouterConflict?: (key: string, existingValue: any, newValue: any) => Promise<boolean>;
@@ -174,12 +174,12 @@ export interface MergeCallbacks {
} }
/** /**
* 主配置合并函数 * Main configuration merge function
* @param baseConfig 基础配置(现有配置) * @param baseConfig Base configuration (existing configuration)
* @param presetConfig 预设配置 * @param presetConfig Preset configuration
* @param strategy 合并策略 * @param strategy Merge strategy
* @param callbacks 交互式回调函数 * @param callbacks Interactive callback functions
* @returns 合并后的配置 * @returns Merged configuration
*/ */
export async function mergeConfig( export async function mergeConfig(
baseConfig: any, baseConfig: any,
@@ -189,7 +189,7 @@ export async function mergeConfig(
): Promise<any> { ): Promise<any> {
const result = { ...baseConfig }; const result = { ...baseConfig };
// 合并 Providers // Merge Providers
if (presetConfig.Providers) { if (presetConfig.Providers) {
result.Providers = mergeProviders( result.Providers = mergeProviders(
result.Providers || [], result.Providers || [],
@@ -197,7 +197,7 @@ export async function mergeConfig(
); );
} }
// 合并 Router // Merge Router
if (presetConfig.Router) { if (presetConfig.Router) {
result.Router = await mergeRouter( result.Router = await mergeRouter(
result.Router || {}, result.Router || {},
@@ -207,7 +207,7 @@ export async function mergeConfig(
); );
} }
// 合并 transformers // Merge transformers
if (presetConfig.transformers) { if (presetConfig.transformers) {
result.transformers = await mergeTransformers( result.transformers = await mergeTransformers(
result.transformers || [], result.transformers || [],
@@ -217,7 +217,7 @@ export async function mergeConfig(
); );
} }
// 合并其他配置 // Merge other configurations
const otherConfig = await mergeOtherConfig( const otherConfig = await mergeOtherConfig(
result, result,
presetConfig, presetConfig,

View File

@@ -1,10 +1,10 @@
/** /**
* 敏感字段识别和脱敏功能 * Sensitive field identification and sanitization functionality
*/ */
import { RequiredInput, SanitizeResult } from './types'; import { RequiredInput, SanitizeResult } from './types';
// 敏感字段模式列表 // Sensitive field pattern list
const SENSITIVE_PATTERNS = [ const SENSITIVE_PATTERNS = [
'api_key', 'apikey', 'apiKey', 'APIKEY', 'api_key', 'apikey', 'apiKey', 'APIKEY',
'api_secret', 'apisecret', 'apiSecret', 'api_secret', 'apisecret', 'apiSecret',
@@ -15,11 +15,11 @@ const SENSITIVE_PATTERNS = [
'access_key', 'accessKey', 'access_key', 'accessKey',
]; ];
// 环境变量占位符正则 // Environment variable placeholder regex
const ENV_VAR_REGEX = /^\$\{?[A-Z_][A-Z0-9_]*\}?$/; const ENV_VAR_REGEX = /^\$\{?[A-Z_][A-Z0-9_]*\}?$/;
/** /**
* 检查字段名是否为敏感字段 * Check if field name is sensitive
*/ */
function isSensitiveField(fieldName: string): boolean { function isSensitiveField(fieldName: string): boolean {
const lowerFieldName = fieldName.toLowerCase(); const lowerFieldName = fieldName.toLowerCase();
@@ -29,22 +29,22 @@ function isSensitiveField(fieldName: string): boolean {
} }
/** /**
* 生成环境变量名称 * Generate environment variable name
* @param fieldType 字段类型 (provider, transformer, global) * @param fieldType Field type (provider, transformer, global)
* @param entityName 实体名称 (如 provider name) * @param entityName Entity name (e.g., provider name)
* @param fieldName 字段名称 * @param fieldName Field name
*/ */
export function generateEnvVarName( export function generateEnvVarName(
fieldType: 'provider' | 'transformer' | 'global', fieldType: 'provider' | 'transformer' | 'global',
entityName: string, entityName: string,
fieldName: string fieldName: string
): string { ): string {
// 生成大写的环境变量名 // Generate uppercase environment variable name
// 例如: DEEPSEEK_API_KEY, CUSTOM_TRANSFORMER_SECRET // e.g., DEEPSEEK_API_KEY, CUSTOM_TRANSFORMER_SECRET
const prefix = entityName.toUpperCase().replace(/[^A-Z0-9]/g, '_'); const prefix = entityName.toUpperCase().replace(/[^A-Z0-9]/g, '_');
const field = fieldName.toUpperCase().replace(/[^A-Z0-9]/g, '_'); const field = fieldName.toUpperCase().replace(/[^A-Z0-9]/g, '_');
// 如果前缀和字段名相同(如 API_KEY避免重复 // If prefix and field name are the same (e.g., API_KEY), avoid duplication
if (prefix === field) { if (prefix === field) {
return prefix; return prefix;
} }
@@ -53,7 +53,7 @@ export function generateEnvVarName(
} }
/** /**
* 检查值是否已经是环境变量占位符 * Check if value is already an environment variable placeholder
*/ */
function isEnvPlaceholder(value: any): boolean { function isEnvPlaceholder(value: any): boolean {
if (typeof value !== 'string') { if (typeof value !== 'string') {
@@ -63,19 +63,19 @@ function isEnvPlaceholder(value: any): boolean {
} }
/** /**
* 从环境变量占位符中提取变量名 * Extract variable name from environment variable placeholder
* @param value 环境变量值(如 $VAR ${VAR} * @param value Environment variable value (e.g., $VAR or ${VAR})
*/ */
function extractEnvVarName(value: string): string | null { function extractEnvVarName(value: string): string | null {
const trimmed = value.trim(); const trimmed = value.trim();
// 匹配 ${VAR_NAME} 格式 // Match ${VAR_NAME} format
const bracedMatch = trimmed.match(/^\$\{([A-Z_][A-Z0-9_]*)\}$/); const bracedMatch = trimmed.match(/^\$\{([A-Z_][A-Z0-9_]*)\}$/);
if (bracedMatch) { if (bracedMatch) {
return bracedMatch[1]; return bracedMatch[1];
} }
// 匹配 $VAR_NAME 格式 // Match $VAR_NAME format
const unbracedMatch = trimmed.match(/^\$([A-Z_][A-Z0-9_]*)$/); const unbracedMatch = trimmed.match(/^\$([A-Z_][A-Z0-9_]*)$/);
if (unbracedMatch) { if (unbracedMatch) {
return unbracedMatch[1]; return unbracedMatch[1];
@@ -85,11 +85,11 @@ function extractEnvVarName(value: string): string | null {
} }
/** /**
* 递归遍历对象,识别和脱敏敏感字段 * Recursively traverse object to identify and sanitize sensitive fields
* @param config 配置对象 * @param config Configuration object
* @param path 当前字段路径 * @param path Current field path
* @param requiredInputs 必需输入数组(累积) * @param requiredInputs Required inputs array (cumulative)
* @param sanitizedCount 脱敏字段计数 * @param sanitizedCount Sanitized field count
*/ */
function sanitizeObject( function sanitizeObject(
config: any, config: any,
@@ -121,12 +121,12 @@ function sanitizeObject(
for (const [key, value] of Object.entries(config)) { for (const [key, value] of Object.entries(config)) {
const currentPath = path ? `${path}.${key}` : key; const currentPath = path ? `${path}.${key}` : key;
// 检查是否是敏感字段 // Check if this is a sensitive field
if (isSensitiveField(key) && typeof value === 'string') { if (isSensitiveField(key) && typeof value === 'string') {
// 如果值已经是环境变量,保持不变 // If value is already an environment variable, keep unchanged
if (isEnvPlaceholder(value)) { if (isEnvPlaceholder(value)) {
sanitizedObj[key] = value; sanitizedObj[key] = value;
// 仍然需要记录为必需输入,但使用已有环境变量 // Still need to record as required input, but use existing environment variable
const envVarName = extractEnvVarName(value); const envVarName = extractEnvVarName(value);
if (envVarName && !requiredInputs.some(input => input.id === currentPath)) { if (envVarName && !requiredInputs.some(input => input.id === currentPath)) {
requiredInputs.push({ requiredInputs.push({
@@ -136,19 +136,19 @@ function sanitizeObject(
}); });
} }
} else { } else {
// 脱敏:替换为环境变量占位符 // Sanitize: replace with environment variable placeholder
// 尝试从路径中推断实体名称 // Try to infer entity name from path
let entityName = 'CONFIG'; let entityName = 'CONFIG';
const pathParts = currentPath.split('.'); const pathParts = currentPath.split('.');
// 如果路径包含 Providers transformers,尝试提取实体名称 // If path contains Providers or transformers, try to extract entity name
for (let i = 0; i < pathParts.length; i++) { for (let i = 0; i < pathParts.length; i++) {
if (pathParts[i] === 'Providers' || pathParts[i] === 'transformers') { if (pathParts[i] === 'Providers' || pathParts[i] === 'transformers') {
// 查找 name 字段 // Find name field
if (i + 1 < pathParts.length && pathParts[i + 1].match(/^\d+$/)) { if (i + 1 < pathParts.length && pathParts[i + 1].match(/^\d+$/)) {
// 这是数组索引,查找同级的 name 字段 // This is array index, find name field at same level
const parentPath = pathParts.slice(0, i + 2).join('.'); const parentPath = pathParts.slice(0, i + 2).join('.');
// 在当前上下文中查找 name // Find name in current context
const context = config; const context = config;
if (context.name) { if (context.name) {
entityName = context.name; entityName = context.name;
@@ -161,7 +161,7 @@ function sanitizeObject(
const envVarName = generateEnvVarName('global', entityName, key); const envVarName = generateEnvVarName('global', entityName, key);
sanitizedObj[key] = `\${${envVarName}}`; sanitizedObj[key] = `\${${envVarName}}`;
// 记录为必需输入 // Record as required input
requiredInputs.push({ requiredInputs.push({
id: currentPath, id: currentPath,
prompt: `Enter ${key}`, prompt: `Enter ${key}`,
@@ -171,13 +171,13 @@ function sanitizeObject(
sanitizedCount++; sanitizedCount++;
} }
} else if (typeof value === 'object' && value !== null) { } else if (typeof value === 'object' && value !== null) {
// 递归处理嵌套对象 // Recursively process nested objects
const result = sanitizeObject(value, currentPath, requiredInputs, sanitizedCount); const result = sanitizeObject(value, currentPath, requiredInputs, sanitizedCount);
sanitizedObj[key] = result.sanitized; sanitizedObj[key] = result.sanitized;
requiredInputs = result.requiredInputs; requiredInputs = result.requiredInputs;
sanitizedCount = result.count; sanitizedCount = result.count;
} else { } else {
// 保留原始值 // Keep original value
sanitizedObj[key] = value; sanitizedObj[key] = value;
} }
} }
@@ -186,12 +186,12 @@ function sanitizeObject(
} }
/** /**
* 脱敏配置对象 * Sanitize configuration object
* @param config 原始配置 * @param config Original configuration
* @returns 脱敏结果 * @returns Sanitization result
*/ */
export async function sanitizeConfig(config: any): Promise<SanitizeResult> { export async function sanitizeConfig(config: any): Promise<SanitizeResult> {
// 深拷贝配置,避免修改原始对象 // Deep copy configuration to avoid modifying original object
const configCopy = JSON.parse(JSON.stringify(config)); const configCopy = JSON.parse(JSON.stringify(config));
const result = sanitizeObject(configCopy); const result = sanitizeObject(configCopy);
@@ -204,10 +204,10 @@ export async function sanitizeConfig(config: any): Promise<SanitizeResult> {
} }
/** /**
* 填充敏感信息到配置中 * Fill sensitive information into configuration
* @param config 预设配置(包含环境变量占位符) * @param config Preset configuration (containing environment variable placeholders)
* @param inputs 用户输入的敏感信息 * @param inputs User input sensitive information
* @returns 填充后的配置 * @returns Filled configuration
*/ */
export function fillSensitiveInputs(config: any, inputs: Record<string, string>): any { export function fillSensitiveInputs(config: any, inputs: Record<string, string>): any {
const configCopy = JSON.parse(JSON.stringify(config)); const configCopy = JSON.parse(JSON.stringify(config));
@@ -228,7 +228,7 @@ export function fillSensitiveInputs(config: any, inputs: Record<string, string>)
const currentPath = path ? `${path}.${key}` : key; const currentPath = path ? `${path}.${key}` : key;
if (typeof value === 'string' && isEnvPlaceholder(value)) { if (typeof value === 'string' && isEnvPlaceholder(value)) {
// 查找是否有用户输入 // Check if there is user input
const input = inputs[currentPath]; const input = inputs[currentPath];
if (input) { if (input) {
result[key] = input; result[key] = input;