diff --git a/.gitignore b/.gitignore
index 63485fc..c375cbc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,6 @@ node_modules
.claude/
CLAUDE.md
config.json
-configs/pwd
-configs/provider_pools.json
provider_pools.json
plugins.json
fetch_system_prompt.txt
diff --git a/configs/pwd b/configs/pwd
new file mode 100644
index 0000000..32e9c62
--- /dev/null
+++ b/configs/pwd
@@ -0,0 +1 @@
+admin123
\ No newline at end of file
diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs
deleted file mode 100644
index 57c9887..0000000
--- a/ecosystem.config.cjs
+++ /dev/null
@@ -1,23 +0,0 @@
-module.exports = {
- apps: [
- {
- name: 'aiclient-2-api',
- script: './src/core/master.js',
- cwd: '/root/.openclaw/workspace/projects/AIClient-2-API',
- interpreter: '/usr/bin/node',
- instances: 1,
- exec_mode: 'fork',
- autorestart: true,
- watch: false,
- max_memory_restart: '1G',
- env: {
- NODE_ENV: 'production'
- },
- error_file: './logs/pm2-error.log',
- out_file: './logs/pm2-out.log',
- log_file: './logs/pm2-combined.log',
- time: true,
- merge_logs: true
- }
- ]
-}
diff --git a/package-lock.json b/package-lock.json
index b3b8bfa..8e81846 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "AIClient-2-API",
+ "name": "AIClient2API",
"lockfileVersion": 3,
"requires": true,
"packages": {
@@ -7,7 +7,7 @@
"dependencies": {
"@anthropic-ai/tokenizer": "^0.0.4",
"adm-zip": "^0.5.16",
- "axios": "^1.14.0",
+ "axios": "^1.10.0",
"deepmerge": "^4.3.1",
"dotenv": "^16.4.5",
"google-auth-library": "^10.1.0",
@@ -2473,14 +2473,14 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.14.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
- "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
+ "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
"license": "MIT",
"dependencies": {
- "follow-redirects": "^1.15.11",
- "form-data": "^4.0.5",
- "proxy-from-env": "^2.1.0"
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
}
},
"node_modules/babel-jest": {
@@ -3725,9 +3725,9 @@
}
},
"node_modules/follow-redirects": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
@@ -3745,9 +3745,9 @@
}
},
"node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
@@ -5695,13 +5695,10 @@
}
},
"node_modules/proxy-from-env": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
- "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
},
"node_modules/pure-rand": {
"version": "6.1.0",
diff --git a/src/converters/strategies/GrokConverter.js b/src/converters/strategies/GrokConverter.js
index 86ece0e..38ebfb5 100644
--- a/src/converters/strategies/GrokConverter.js
+++ b/src/converters/strategies/GrokConverter.js
@@ -264,8 +264,6 @@ export class GrokConverter extends BaseConverter {
return this.toOpenAIResponsesResponse(data, model);
case MODEL_PROTOCOL_PREFIX.CODEX:
return this.toCodexResponse(data, model);
- case MODEL_PROTOCOL_PREFIX.CLAUDE:
- return this.toClaudeResponse(data, model);
default:
return data;
}
@@ -276,8 +274,6 @@ export class GrokConverter extends BaseConverter {
*/
convertStreamChunk(chunk, targetProtocol, model) {
switch (targetProtocol) {
- case MODEL_PROTOCOL_PREFIX.CLAUDE:
- return this.toClaudeStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.OPENAI:
return this.toOpenAIStreamChunk(chunk, model);
case MODEL_PROTOCOL_PREFIX.GEMINI:
@@ -1143,76 +1139,6 @@ export class GrokConverter extends BaseConverter {
};
}
- /**
- * Grok响应 -> Claude响应 (通过OpenAI中转)
- */
- toClaudeResponse(grokResponse, model) {
- const openaiRes = this.toOpenAIResponse(grokResponse, model);
- if (!openaiRes) {
- return {
- id: `msg_${uuidv4()}`,
- type: "message",
- role: "assistant",
- content: [],
- model: model,
- stop_reason: "end_turn",
- stop_sequence: null,
- usage: { input_tokens: 0, output_tokens: 0 }
- };
- }
-
- const choice = openaiRes.choices?.[0] || {};
- const message = choice.message || {};
- const contentList = [];
-
- // 工具调用
- const toolCalls = message.tool_calls || message.function_calls || [];
- for (const tc of (toolCalls || [])) {
- if (tc.function) {
- let argObj;
- try {
- argObj = typeof tc.function.arguments === 'string' ? JSON.parse(tc.function.arguments) : tc.function.arguments;
- } catch (e) {
- argObj = {};
- }
- contentList.push({
- type: "tool_use",
- id: tc.id || `toolu_${uuidv4().slice(0, 8)}`,
- name: tc.function.name || "",
- input: argObj
- });
- }
- }
-
- // 文本内容
- const text = message.content || "";
- if (text) {
- contentList.push({ type: "text", text });
- }
-
- // 空内容兜底
- if (contentList.length === 0) {
- contentList.push({ type: "text", text: "" });
- }
-
- const finishReason = choice.finish_reason || "stop";
- const stopReason = finishReason === "stop" ? "end_turn" : finishReason === "length" ? "max_tokens" : finishReason === "tool_calls" ? "tool_use" : finishReason;
-
- return {
- id: `msg_${uuidv4()}`,
- type: "message",
- role: "assistant",
- content: contentList,
- model: choice.model || model,
- stop_reason: stopReason,
- stop_sequence: null,
- usage: {
- input_tokens: openaiRes.usage?.prompt_tokens || 0,
- output_tokens: openaiRes.usage?.completion_tokens || 0
- }
- };
- }
-
/**
* Grok流式响应块 -> Codex流式响应块
*/
diff --git a/src/plugins/model-router/index.js b/src/plugins/model-router/index.js
deleted file mode 100644
index 9739219..0000000
--- a/src/plugins/model-router/index.js
+++ /dev/null
@@ -1,287 +0,0 @@
-/**
- * Model Router 插件 - 自定义模型别名路由
- *
- * 功能:
- * - 将自定义模型别名映射到 provider:model 格式
- * - 支持热编辑(通过 API 或配置文件)
- * - 支持 /plugin/model-router/ 路径查看和编辑映射规则
- *
- * 原理:
- * - 作为 middleware 插件,在请求处理最早期拦截
- * - 读取 body,匹配别名,改写 model 字段和 MODEL_PROVIDER
- * - 将处理后的 body 缓存到 req._cachedBody 供下游使用
- */
-
-import logger from '../../utils/logger.js';
-import { promises as fs } from 'fs';
-import path from 'path';
-
-const CONFIG_FILE = path.join(process.cwd(), 'src', 'plugins', 'model-router', 'config.json');
-
-/**
- * 加载映射配置
- */
-async function loadMappings() {
- try {
- const content = await fs.readFile(CONFIG_FILE, 'utf8');
- const config = JSON.parse(content);
- return config.mappings || {};
- } catch (error) {
- logger.error(`[ModelRouter] Failed to load config: ${error.message}`);
- return {};
- }
-}
-
-/**
- * 保存映射配置
- */
-async function saveMappings(mappings) {
- const config = { mappings };
- await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
-}
-
-/**
- * 匹配模型别名
- * 优先级:精确匹配 > 不区分大小写 > 包含匹配
- */
-function matchAlias(requestedModel, mappings) {
- const lower = requestedModel.toLowerCase();
-
- // 1. 精确匹配
- if (mappings[requestedModel]) return mappings[requestedModel];
-
- // 2. 不区分大小写
- for (const [alias, rule] of Object.entries(mappings)) {
- if (alias.toLowerCase() === lower) return rule;
- }
-
- // 3. 包含匹配
- for (const [alias, rule] of Object.entries(mappings)) {
- if (lower.includes(alias.toLowerCase()) || alias.toLowerCase().includes(lower)) {
- return rule;
- }
- }
-
- return null;
-}
-
-/**
- * 读取完整的 request body 并缓存
- */
-function readFullBody(req) {
- return new Promise((resolve, reject) => {
- const chunks = [];
- req.on('data', chunk => chunks.push(chunk));
- req.on('end', () => resolve(Buffer.concat(chunks)));
- req.on('error', reject);
- });
-}
-
-/**
- * Monkey-patch getRequestBody 以支持缓存的 body
- */
-function patchGetRequestBody() {
- const commonModuleUrl = new URL('../../utils/common.js', import.meta.url);
- import(commonModuleUrl).then(common => {
- // 保存原始函数引用(通过重新导入)
- // 由于 ESM 的 export 是只读的,我们用另一种方式
- }).catch(() => {});
-}
-
-const modelRouterPlugin = {
- name: 'model-router',
- version: '1.0.0',
- description: '模型别名路由插件 - 将自定义模型名映射到 provider:model
管理:model-router 管理面板',
- type: 'middleware',
- _priority: 1, // 最高优先级,最先执行
-
- async init(config) {
- // Monkey-patch getRequestBody to support cached body from this plugin
- try {
- const common = await import('../../utils/common.js');
- const origFn = common.getRequestBody;
- // We can't directly reassign ESM exports, but we can wrap the module
- // Instead, we'll patch it at runtime by modifying the imported reference
- // The trick: store the patched version on req for downstream to use
- // Actually, the simplest approach: we make getRequestBody check req._cachedBodyString
- } catch (e) {
- logger.warn(`[ModelRouter] Init warning: ${e.message}`);
- }
- logger.info('[ModelRouter] Plugin initialized');
- },
-
- /**
- * 中间件:拦截请求,改写模型名
- */
- async middleware(req, res, requestUrl, config) {
- if (req.method !== 'POST') return null;
-
- const pathName = requestUrl.pathname;
- const isApiRequest = pathName.includes('/chat/completions') ||
- pathName.includes('/embeddings') ||
- pathName.includes('/images/generations') ||
- pathName.includes('/responses') ||
- pathName.includes('/messages') ||
- pathName.includes('/generateContent');
-
- if (!isApiRequest) return null;
-
- // 如果已经被本插件处理过,跳过
- if (req._modelRouterProcessed) return null;
- req._modelRouterProcessed = true;
-
- // 读取完整 body
- let rawBody;
- try {
- rawBody = await readFullBody(req);
- } catch (e) {
- return null;
- }
-
- let body;
- try {
- body = JSON.parse(rawBody.toString() || '{}');
- } catch (e) {
- return null;
- }
-
- const requestedModel = body.model;
- if (!requestedModel) {
- // 不改写,但需要让 body 可被下游重新读取
- req._cachedBodyBuffer = rawBody;
- req._cachedBodyString = rawBody.toString();
- return null;
- }
-
- // 已经是 provider:model 格式且有对应号池,跳过
- if (requestedModel.includes(':')) {
- const prefix = requestedModel.split(':')[0];
- if (config.providerPools?.[prefix]) {
- req._cachedBodyBuffer = rawBody;
- req._cachedBodyString = rawBody.toString();
- return null;
- }
- }
-
- // 匹配别名
- const mappings = await loadMappings();
- const rule = matchAlias(requestedModel, mappings);
-
- let finalBodyString = rawBody.toString();
-
- if (rule && rule.target) {
- logger.info(`[ModelRouter] "${requestedModel}" → "${rule.target}" (${rule.description || ''})`);
-
- body.model = rule.target;
- finalBodyString = JSON.stringify(body);
-
- // 设置 MODEL_PROVIDER
- const [provider] = rule.target.split(':');
- if (provider) {
- config.MODEL_PROVIDER = provider;
- }
- }
-
- // 缓存 body 供下游读取
- req._cachedBodyBuffer = Buffer.from(finalBodyString);
- req._cachedBodyString = finalBodyString;
-
- return null;
- },
-
- /**
- * API 路由:查看和编辑映射规则
- */
- routes: [
- {
- method: 'GET',
- path: '/plugin/model-router/',
- handler: async (method, pathUrl, req, res) => {
- try {
- const htmlPath = path.join(process.cwd(), 'src', 'plugins', 'model-router', 'static', 'index.html');
- const html = await fs.readFile(htmlPath, 'utf8');
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
- res.end(html);
- } catch (e) {
- res.writeHead(500, { 'Content-Type': 'text/plain' });
- res.end('Plugin page not found');
- }
- return true;
- }
- },
- {
- method: 'GET',
- path: '/plugin/model-router/api/mappings',
- handler: async (method, pathUrl, req, res) => {
- const mappings = await loadMappings();
- res.writeHead(200, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: true, mappings }, null, 2));
- return true;
- }
- },
- {
- method: 'PUT',
- path: '/plugin/model-router/api/mappings',
- handler: async (method, pathUrl, req, res) => {
- try {
- const bodyBuffer = await readFullBody(req);
- const body = JSON.parse(bodyBuffer.toString());
-
- const { alias, target, description } = body;
- if (!alias || !target) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, error: 'alias and target are required' }));
- return true;
- }
-
- const mappings = await loadMappings();
- mappings[alias] = { target, description: description || '' };
- await saveMappings(mappings);
-
- logger.info(`[ModelRouter] Mapping added: ${alias} → ${target}`);
- res.writeHead(200, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: true, mappings }));
- return true;
- } catch (error) {
- res.writeHead(500, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, error: error.message }));
- return true;
- }
- }
- },
- {
- method: 'DELETE',
- path: '/plugin/model-router/api/mappings',
- handler: async (method, pathUrl, req, res) => {
- try {
- const bodyBuffer = await readFullBody(req);
- const body = JSON.parse(bodyBuffer.toString());
-
- const { alias } = body;
- if (!alias) {
- res.writeHead(400, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, error: 'alias is required' }));
- return true;
- }
-
- const mappings = await loadMappings();
- if (mappings[alias]) {
- delete mappings[alias];
- await saveMappings(mappings);
- logger.info(`[ModelRouter] Mapping deleted: ${alias}`);
- }
-
- res.writeHead(200, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: true, mappings }));
- return true;
- } catch (error) {
- res.writeHead(500, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ success: false, error: error.message }));
- return true;
- }
- }
- }
- ]
-};
-
-export default modelRouterPlugin;
diff --git a/src/plugins/model-router/static/index.html b/src/plugins/model-router/static/index.html
deleted file mode 100644
index d38925d..0000000
--- a/src/plugins/model-router/static/index.html
+++ /dev/null
@@ -1,180 +0,0 @@
-
-
-
| 别名 | 目标 | 描述 | 操作 |
|---|---|---|---|
| 加载中... | |||