mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-02-02 23:00:50 +08:00
translate comment to english
This commit is contained in:
@@ -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", () => {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 "{}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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[] = [];
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
// 对于 Router,merge 策略等同于 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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user