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

View File

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

View File

@@ -1,13 +1,13 @@
/**
* 预设导出功能 CLI 层
* 负责处理 CLI 交互,核心逻辑在 shared 包中
* Preset export functionality CLI layer
* Handles CLI interactions, core logic is in shared package
*/
import { input } from '@inquirer/prompts';
import { readConfigFile } from '../index';
import { exportPreset as exportPresetCore, ExportOptions } from '@CCR/shared';
// ANSI 颜色代码
// ANSI color codes
const RESET = "\x1B[0m";
const GREEN = "\x1B[32m";
const BOLDGREEN = "\x1B[1m\x1B[32m";
@@ -15,9 +15,9 @@ const YELLOW = "\x1B[33m";
const BOLDCYAN = "\x1B[1m\x1B[36m";
/**
* 导出预设配置CLI 版本,带交互)
* @param presetName 预设名称
* @param options 导出选项
* Export preset configuration (CLI version, with interaction)
* @param presetName Preset name
* @param options Export options
*/
export async function exportPresetCli(
presetName: string,
@@ -28,10 +28,10 @@ export async function exportPresetCli(
console.log(`${BOLDCYAN} Preset Export${RESET}`);
console.log(`${BOLDCYAN}═══════════════════════════════════════════════${RESET}\n`);
// 1. 读取当前配置
// 1. Read current configuration
const config = await readConfigFile();
// 2. 如果没有通过命令行提供,交互式询问元数据
// 2. Interactively ask for metadata if not provided via command line
if (!options.description) {
try {
options.description = await input({
@@ -39,7 +39,7 @@ export async function exportPresetCli(
default: '',
});
} catch {
// 用户取消,使用默认值
// User cancelled, use default value
options.description = '';
}
}
@@ -67,10 +67,10 @@ export async function exportPresetCli(
}
}
// 3. 调用核心导出功能
// 3. Call core export functionality
const result = await exportPresetCore(presetName, config, options);
// 4. 显示摘要
// 4. Display summary
console.log(`\n${BOLDGREEN}✓ Preset exported successfully${RESET}\n`);
console.log(`${BOLDCYAN}Location:${RESET} ${result.outputPath}\n`);
console.log(`${BOLDCYAN}Summary:${RESET}`);
@@ -80,7 +80,7 @@ export async function exportPresetCli(
console.log(` - Sensitive fields sanitized: ${YELLOW}${result.sanitizedCount}${RESET}`);
}
// 显示元数据
// Display metadata
if (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(', ')}`);
}
// 显示分享提示
// Display sharing tips
console.log(`\n${BOLDCYAN}To share this preset:${RESET}`);
console.log(` 1. Share the file: ${result.outputPath}`);
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 {
@@ -22,7 +22,7 @@ import password from '@inquirer/password';
import checkbox from '@inquirer/checkbox';
import editor from '@inquirer/editor';
// ANSI 颜色代码
// ANSI color codes
export const COLORS = {
RESET: "\x1B[0m",
GREEN: "\x1B[32m",
@@ -34,41 +34,41 @@ export const COLORS = {
};
/**
* 收集用户输入(支持动态配置)
* Collect user input (supports dynamic configuration)
*/
export async function collectUserInputs(
schema: RequiredInput[],
presetConfig: PresetConfigSection,
existingValues?: UserInputValues
): Promise<UserInputValues> {
// 按依赖关系排序
// Sort by dependencies
const sortedFields = sortFieldsByDependencies(schema);
// 初始化值
// Initialize values
const values: UserInputValues = { ...existingValues };
// 收集所有输入
// Collect all inputs
for (const field of sortedFields) {
// 检查是否应该显示此字段
// Check if this field should be displayed
if (!shouldShowField(field, values)) {
// 跳过,并清除该字段的值(如果之前存在)
// Skip and clear the field value (if it existed before)
delete values[field.id];
continue;
}
// 如果已有值且不是初始收集,跳过
// Skip if value already exists and not initial collection
if (existingValues && field.id in existingValues) {
continue;
}
// 获取输入值
// Get input value
const value = await promptField(field, presetConfig, values);
// 验证
// Validate
const validation = validateInput(field, value);
if (!validation.valid) {
console.error(`${COLORS.YELLOW}Error:${COLORS.RESET} ${validation.error}`);
// 对于必填字段,抛出错误
// Throw error for required fields
if (field.required !== false) {
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(
changedFieldId: string,
@@ -95,24 +95,24 @@ export async function recollectAffectedFields(
const values = { ...currentValues };
// 对受影响的字段重新收集输入
// Recollect input for affected fields
for (const fieldId of affectedFields) {
const field = sortedFields.find(f => f.id === fieldId);
if (!field) {
continue;
}
// 检查是否应该显示
// Check if should be displayed
if (!shouldShowField(field, values)) {
delete values[field.id];
continue;
}
// 重新收集输入
// Recollect input
const value = await promptField(field, presetConfig, values);
values[field.id] = value;
// 级联更新:如果这个字段的变化又影响了其他字段
// Cascade update: if this field change affects other fields
const newAffected = getAffectedFields(field.id, schema);
for (const newAffectedId of newAffected) {
if (!affectedFields.has(newAffectedId)) {
@@ -125,7 +125,7 @@ export async function recollectAffectedFields(
}
/**
* 提示单个字段
* Prompt for a single field
*/
async function promptField(
field: RequiredInput,
@@ -187,7 +187,7 @@ async function promptField(
return field.defaultValue ?? [];
}
// @inquirer/prompts 没有多选,使用 checkbox
// @inquirer/prompts doesn't have multi-select, use checkbox
return await checkbox({
message,
choices: options.map(opt => ({
@@ -206,7 +206,7 @@ async function promptField(
}
default:
// 默认使用 input
// Use input by default
return await input({
message,
default: field.defaultValue,
@@ -215,7 +215,7 @@ async function promptField(
}
/**
* 收集敏感信息(兼容旧版)
* Collect sensitive information (legacy compatible)
*/
export async function collectSensitiveInputs(
schema: RequiredInput[],
@@ -226,7 +226,7 @@ export async function collectSensitiveInputs(
const values = await collectUserInputs(schema, presetConfig, existingValues);
// 显示摘要
// Display summary
console.log(`${COLORS.GREEN}${COLORS.RESET} All required information collected\n`);
return values;

View File

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

View File

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

View File

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

View File

@@ -68,7 +68,7 @@ export function convertToOpenAI(
request: UnifiedChatRequest
): OpenAIChatRequest {
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) => {
if (msg.role === "tool" && msg.tool_call_id) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
/**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 processor
*/

View File

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

View File

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

View File

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