a2abase2api / server.js
dotaevo's picture
Update server.js
e6c51cc verified
Raw
History Blame Contribute Delete
18.3 kB
const express = require('express');
const fetch = require('node-fetch');
const app = express();
const PORT = process.env.PORT || 4777;
const A2BASE_API_URL = 'https://a2abase.ai/api';
app.use(express.json());
// 模型列表
const MODELS_DATA = {
"models": [
{
"id": "gemini/gemini-ultra",
"display_name": "Gemini Ultra",
"short_name": "Gemini Ultra",
"requires_subscription": false,
"input_cost_per_million_tokens": 5.0,
"output_cost_per_million_tokens": 20.0,
"context_window": 2000000,
"capabilities": ["chat", "function_calling", "vision", "structured_output", "thinking"],
"recommended": false,
"priority": 106
},
{
"id": "gemini/gemini-3-deep-think-exp",
"display_name": "Gemini 3 Deep Think",
"short_name": "Gemini 3 Deep Think",
"requires_subscription": false,
"input_cost_per_million_tokens": 2.5,
"output_cost_per_million_tokens": 10.0,
"context_window": 2000000,
"capabilities": ["chat", "function_calling", "vision", "thinking"],
"recommended": false,
"priority": 105
},
{
"id": "gemini/gemini-3-flash-preview",
"display_name": "Gemini 3 Flash",
"short_name": "Gemini 3 Flash",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.3,
"output_cost_per_million_tokens": 1.5,
"context_window": 2000000,
"capabilities": ["chat", "function_calling", "vision", "structured_output"],
"recommended": false,
"priority": 104
},
{
"id": "gemini/gemini-3-pro-preview",
"display_name": "Gemini 3",
"short_name": "Gemini 3",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.36,
"output_cost_per_million_tokens": 3.0,
"context_window": 2000000,
"capabilities": ["chat", "function_calling", "vision", "structured_output"],
"recommended": false,
"priority": 103
},
{
"id": "anthropic/claude-sonnet-4-5-20250929",
"display_name": "Claude Sonnet 4.5",
"short_name": "Claude Sonnet 4.5",
"requires_subscription": false,
"input_cost_per_million_tokens": 3.6,
"output_cost_per_million_tokens": 18.0,
"context_window": 200000,
"capabilities": ["chat", "function_calling", "vision", "thinking", "structured_output"],
"recommended": false,
"priority": 102
},
{
"id": "gemini/gemini-2.5-flash",
"display_name": "Gemini 2.5 Flash",
"short_name": "Gemini 2.5 Flash",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.36,
"output_cost_per_million_tokens": 3.0,
"context_window": 2000000,
"capabilities": ["chat", "function_calling", "vision", "structured_output"],
"recommended": false,
"priority": 102
},
{
"id": "anthropic/claude-haiku-4-5-20251001",
"display_name": "Claude Haiku 4.5",
"short_name": "Claude Haiku 4.5",
"requires_subscription": false,
"input_cost_per_million_tokens": 1.0,
"output_cost_per_million_tokens": 5.0,
"context_window": 200000,
"capabilities": ["chat", "function_calling", "vision", "structured_output"],
"recommended": false,
"priority": 101
},
{
"id": "gemini/gemini-2.5-pro",
"display_name": "Gemini 2.5 Pro",
"short_name": "Gemini 2.5 Pro",
"requires_subscription": false,
"input_cost_per_million_tokens": 1.25,
"output_cost_per_million_tokens": 5.0,
"context_window": 2000000,
"capabilities": ["chat", "function_calling", "vision", "structured_output", "thinking"],
"recommended": false,
"priority": 101
},
{
"id": "openrouter/z-ai/glm-4.7",
"display_name": "GLM 4.7",
"short_name": "GLM 4.7",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.5,
"output_cost_per_million_tokens": 2.2,
"context_window": 205000,
"capabilities": ["chat", "function_calling", "code_interpreter", "thinking"],
"recommended": false,
"priority": 100
},
{
"id": "openrouter/x-ai/grok-code-fast-1",
"display_name": "Grok Code Fast 1",
"short_name": "Grok Code Fast 1",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.24,
"output_cost_per_million_tokens": 1.8,
"context_window": 256000,
"capabilities": ["chat", "function_calling", "code_interpreter"],
"recommended": false,
"priority": 100
},
{
"id": "openrouter/z-ai/glm-4.6",
"display_name": "GLM 4.6",
"short_name": "GLM 4.6",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.48,
"output_cost_per_million_tokens": 2.1,
"context_window": 202752,
"capabilities": ["chat", "function_calling"],
"recommended": false,
"priority": 99
},
{
"id": "openrouter/minimax/minimax-m2",
"display_name": "Minimax M2",
"short_name": "Minimax M2",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.306,
"output_cost_per_million_tokens": 1.224,
"context_window": 204800,
"capabilities": ["chat", "function_calling", "vision"],
"recommended": false,
"priority": 98
},
{
"id": "openai/gpt-5.2",
"display_name": "GPT-5.2",
"short_name": "GPT-5.2",
"requires_subscription": false,
"input_cost_per_million_tokens": 5.0,
"output_cost_per_million_tokens": 15.0,
"context_window": 400000,
"capabilities": ["chat", "function_calling", "vision", "structured_output", "thinking"],
"recommended": true,
"priority": 97
},
{
"id": "openai/gpt-5-mini",
"display_name": "GPT-5 Mini",
"short_name": "GPT-5 Mini",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.3,
"output_cost_per_million_tokens": 2.4,
"context_window": 400000,
"capabilities": ["chat", "function_calling", "structured_output"],
"recommended": false,
"priority": 96
},
{
"id": "openrouter/deepseek/deepseek-chat",
"display_name": "DeepSeek Chat",
"short_name": "DeepSeek Chat",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.46,
"output_cost_per_million_tokens": 1.07,
"context_window": 128000,
"capabilities": ["chat", "function_calling"],
"recommended": false,
"priority": 95
},
{
"id": "openai/o3-mini",
"display_name": "o3-mini",
"short_name": "o3-mini",
"requires_subscription": false,
"input_cost_per_million_tokens": 3.5,
"output_cost_per_million_tokens": 14.0,
"context_window": 128000,
"capabilities": ["chat", "thinking"],
"recommended": false,
"priority": 95
},
{
"id": "openai/gpt-4.1-mini",
"display_name": "GPT-4.1 Mini",
"short_name": "GPT-4.1 Mini",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.48,
"output_cost_per_million_tokens": 1.92,
"context_window": 1047576,
"capabilities": ["chat", "function_calling", "structured_output"],
"recommended": false,
"priority": 94
},
{
"id": "openrouter/x-ai/grok-4-fast",
"display_name": "Grok 4 Fast",
"short_name": "Grok 4 Fast",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.24,
"output_cost_per_million_tokens": 0.6,
"context_window": 2000000,
"capabilities": ["chat", "function_calling"],
"recommended": false,
"priority": 93
},
{
"id": "openai/gpt-4o-2024-11-20",
"display_name": "GPT-4o (Nov 2024)",
"short_name": "GPT-4o (Nov 2024)",
"requires_subscription": false,
"input_cost_per_million_tokens": 2.5,
"output_cost_per_million_tokens": 10.0,
"context_window": 128000,
"capabilities": ["chat", "function_calling", "vision", "structured_output"],
"recommended": false,
"priority": 91
},
{
"id": "openai/gpt-4o",
"display_name": "GPT-4o",
"short_name": "GPT-4o",
"requires_subscription": false,
"input_cost_per_million_tokens": 2.5,
"output_cost_per_million_tokens": 10.0,
"context_window": 128000,
"capabilities": ["chat", "function_calling", "vision", "structured_output"],
"recommended": false,
"priority": 90
},
{
"id": "gemini/gemini-2.0-flash-exp",
"display_name": "Gemini 2.0 Flash",
"short_name": "Gemini 2.0 Flash",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.075,
"output_cost_per_million_tokens": 0.3,
"context_window": 1000000,
"capabilities": ["chat", "function_calling", "vision", "structured_output"],
"recommended": false,
"priority": 89
},
{
"id": "openai/gpt-4o-mini",
"display_name": "GPT-4o Mini",
"short_name": "GPT-4o Mini",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.18,
"output_cost_per_million_tokens": 0.72,
"context_window": 128000,
"capabilities": ["chat", "function_calling", "vision", "structured_output"],
"recommended": false,
"priority": 88
},
{
"id": "gemini/gemini-2.0-flash-lite",
"display_name": "Gemini 2.0 Flash-Lite",
"short_name": "Gemini 2.0 Flash-Lite",
"requires_subscription": false,
"input_cost_per_million_tokens": 0.0375,
"output_cost_per_million_tokens": 0.15,
"context_window": 1000000,
"capabilities": ["chat", "function_calling", "vision"],
"recommended": false,
"priority": 86
}
],
"subscription_tier": "free",
"total_models": 23,
"allowed_models_count": 23
};
// GET /v1/models - 返回模型列表
app.get('/v1/models', (req, res) => {
res.json({
object: 'list',
data: MODELS_DATA.models.map(model => ({
id: model.id,
object: 'model',
created: Date.now(),
owned_by: 'a2base',
permission: [],
root: model.id,
parent: null
}))
});
});
// POST /v1/chat/completions - 处理聊天请求
app.post('/v1/chat/completions', async (req, res) => {
try {
// 从Authorization header中提取apiKey和agentId
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: { message: 'Missing or invalid Authorization header', type: 'invalid_request_error' } });
}
const token = authHeader.substring(7); // 去除 'Bearer '
const [apiKey, agentId] = token.split('|');
if (!apiKey || !agentId) {
return res.status(401).json({ error: { message: 'Invalid token format. Expected: apiKey|agentId', type: 'invalid_request_error' } });
}
const { messages, model, stream = false, temperature, max_tokens } = req.body;
if (!messages || !Array.isArray(messages) || messages.length === 0) {
return res.status(400).json({ error: { message: 'Messages array is required', type: 'invalid_request_error' } });
}
// 步骤1: 创建Thread
const threadResponse = await fetch(`${A2BASE_API_URL}/threads`, {
method: 'POST',
headers: {
'X-API-Key': apiKey
}
});
if (!threadResponse.ok) {
const errorText = await threadResponse.text();
return res.status(threadResponse.status).json({ error: { message: `Failed to create thread: ${errorText}`, type: 'api_error' } });
}
const threadData = await threadResponse.json();
const threadId = threadData.thread_id;
// 步骤2: 添加消息到Thread
await fetch(`${A2BASE_API_URL}/threads/${threadId}/messages`, {
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: messages.map(msg => `${msg.role === 'user' ? 'User' : 'Assistant'}: ${msg.content}`).join('\n'),
type: 'user',
is_llm_message: true
})
});
// 步骤3: 启动Agent执行
const startResponse = await fetch(`${A2BASE_API_URL}/thread/${threadId}/agent/start`, {
method: 'POST',
headers: {
'X-API-Key': apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
agent_id: agentId,
model_name: model || 'gemini/gemini-3-pro-preview',
stream: true
})
});
if (!startResponse.ok) {
const errorText = await startResponse.text();
return res.status(startResponse.status).json({ error: { message: `Failed to start agent: ${errorText}`, type: 'api_error' } });
}
const startData = await startResponse.json();
const agentRunId = startData.agent_run_id;
// 步骤4: 获取流式响应
const streamResponse = await fetch(`${A2BASE_API_URL}/agent-run/${agentRunId}/stream`, {
method: 'GET',
headers: {
'X-API-Key': apiKey,
'Accept': 'text/event-stream'
}
});
if (!streamResponse.ok) {
const errorText = await streamResponse.text();
return res.status(streamResponse.status).json({ error: { message: `Failed to get stream: ${errorText}`, type: 'api_error' } });
}
const chatId = `chatcmpl-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
if (stream) {
// 流式返回
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
let buffer = '';
streamResponse.body.on('data', (chunk) => {
// console.log('Received chunk: ', chunk.toString());
buffer += chunk.toString();
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.substring(6);
try {
const data = JSON.parse(jsonStr);
// 处理assistant类型的消息
if (data.type === 'assistant' && data.content) {
const contentObj = JSON.parse(data.content);
const metaObj = JSON.parse(data.metadata || '{}');
if (contentObj.content && metaObj.stream_status === 'chunk') {
const openaiChunk = {
id: chatId,
object: 'chat.completion.chunk',
created: created,
model: model || 'gemini/gemini-2.5-pro',
choices: [{
index: 0,
delta: {
content: contentObj.content
},
finish_reason: null
}]
};
res.write(`data: ${JSON.stringify(openaiChunk)}\n\n`);
}
}
// 处理完成状态
if (data.status === 'completed' || data.type === 'assistant_response_end') {
// const finalChunk = {
// id: chatId,
// object: 'chat.completion.chunk',
// created: created,
// model: model || 'gemini/gemini-2.5-pro',
// choices: [{
// index: 0,
// delta: {},
// finish_reason: 'stop'
// }]
// };
// res.write(`data: ${JSON.stringify(finalChunk)}\n\n`);
res.write('data: [DONE]\n\n');
res.end();
}
} catch (e) {
// 忽略解析错误
}
}
}
});
streamResponse.body.on('end', () => {
if (!res.writableEnded) {
res.end();
}
});
streamResponse.body.on('error', (err) => {
console.error('Stream error:', err);
if (!res.writableEnded) {
res.end();
}
});
} else {
// 非流式返回 - 收集所有内容后一次性返回
let fullContent = '';
let buffer = '';
await new Promise((resolve, reject) => {
streamResponse.body.on('data', (chunk) => {
buffer += chunk.toString();
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const jsonStr = line.substring(6);
try {
const data = JSON.parse(jsonStr);
if (data.type === 'assistant' && data.content) {
const contentObj = JSON.parse(data.content);
const metaObj = JSON.parse(data.metadata || '{}');
if (contentObj.content && metaObj.stream_status === 'chunk') {
fullContent += contentObj.content;
}
}
if (data.status === 'completed' || data.type === 'assistant_response_end') {
resolve();
}
} catch (e) {
// 忽略解析错误
}
}
}
});
streamResponse.body.on('end', resolve);
streamResponse.body.on('error', reject);
});
// 返回OpenAI格式的响应
const response = {
id: chatId,
object: 'chat.completion',
created: created,
model: model || 'gemini/gemini-2.5-pro',
choices: [{
index: 0,
message: {
role: 'assistant',
content: fullContent
},
finish_reason: 'stop'
}],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
}
};
res.json(response);
}
} catch (error) {
console.error('Error:', error);
res.status(500).json({ error: { message: error.message, type: 'internal_error' } });
}
});
// 健康检查端点
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(PORT, () => {
console.log(`A2Base to OpenAI API server running on port ${PORT}`);
});