mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: Add comprehensive Amazon Bedrock integration support
Add complete Amazon Bedrock integration to Claude Relay Service with: ## Core Features - ✅ Bedrock account management with encrypted AWS credential storage - ✅ Full request routing to AWS Bedrock with streaming support - ✅ Integration with unified Claude scheduler system - ✅ Support for Inference Profiles and Application Inference Profiles - ✅ Configurable default and small-fast model settings ## Backend Services - Add bedrockAccountService.js for account management - Add bedrockRelayService.js for request forwarding - Integrate Bedrock accounts into unifiedClaudeScheduler.js - Update admin and API routes to support Bedrock endpoints - Add comprehensive configuration options to config.example.js ## Frontend Integration - Complete Vue.js Web UI for Bedrock account management - Account creation form with AWS credentials and model configuration - Real-time account status monitoring and statistics - Edit/update capabilities for existing accounts ## CLI Support - Interactive CLI commands for Bedrock account operations - Account creation, listing, updating, and testing - Status monitoring and connection validation ## Security & Performance - AES encrypted storage of AWS credentials in Redis - Support for temporary credentials (session tokens) - Region-specific configuration support - Rate limiting and error handling This integration enables the relay service to support three AI platforms: 1. Claude (OAuth) - Original Claude.ai integration 2. Gemini - Google AI integration 3. Amazon Bedrock - New AWS Bedrock integration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
382
src/services/bedrockAccountService.js
Normal file
382
src/services/bedrockAccountService.js
Normal file
@@ -0,0 +1,382 @@
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const crypto = require('crypto');
|
||||
const redis = require('../models/redis');
|
||||
const logger = require('../utils/logger');
|
||||
const config = require('../../config/config');
|
||||
const bedrockRelayService = require('./bedrockRelayService');
|
||||
|
||||
class BedrockAccountService {
|
||||
constructor() {
|
||||
// 加密相关常量
|
||||
this.ENCRYPTION_ALGORITHM = 'aes-256-cbc';
|
||||
this.ENCRYPTION_SALT = 'salt';
|
||||
}
|
||||
|
||||
// 🏢 创建Bedrock账户
|
||||
async createAccount(options = {}) {
|
||||
const {
|
||||
name = 'Unnamed Bedrock Account',
|
||||
description = '',
|
||||
region = process.env.AWS_REGION || 'us-east-1',
|
||||
awsCredentials = null, // { accessKeyId, secretAccessKey, sessionToken }
|
||||
defaultModel = 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
|
||||
isActive = true,
|
||||
accountType = 'shared', // 'dedicated' or 'shared'
|
||||
priority = 50, // 调度优先级 (1-100,数字越小优先级越高)
|
||||
schedulable = true, // 是否可被调度
|
||||
credentialType = 'default' // 'default', 'access_key', 'bearer_token'
|
||||
} = options;
|
||||
|
||||
const accountId = uuidv4();
|
||||
|
||||
let accountData = {
|
||||
id: accountId,
|
||||
name,
|
||||
description,
|
||||
region,
|
||||
defaultModel,
|
||||
isActive,
|
||||
accountType,
|
||||
priority,
|
||||
schedulable,
|
||||
credentialType,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
type: 'bedrock' // 标识这是Bedrock账户
|
||||
};
|
||||
|
||||
// 加密存储AWS凭证
|
||||
if (awsCredentials) {
|
||||
accountData.awsCredentials = this._encryptAwsCredentials(awsCredentials);
|
||||
}
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
await client.set(`bedrock_account:${accountId}`, JSON.stringify(accountData));
|
||||
|
||||
logger.info(`✅ 创建Bedrock账户成功 - ID: ${accountId}, 名称: ${name}, 区域: ${region}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
id: accountId,
|
||||
name,
|
||||
description,
|
||||
region,
|
||||
defaultModel,
|
||||
isActive,
|
||||
accountType,
|
||||
priority,
|
||||
schedulable,
|
||||
credentialType,
|
||||
createdAt: accountData.createdAt,
|
||||
type: 'bedrock'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 🔍 获取账户信息
|
||||
async getAccount(accountId) {
|
||||
try {
|
||||
const client = redis.getClientSafe();
|
||||
const accountData = await client.get(`bedrock_account:${accountId}`);
|
||||
if (!accountData) {
|
||||
return { success: false, error: 'Account not found' };
|
||||
}
|
||||
|
||||
const account = JSON.parse(accountData);
|
||||
|
||||
// 解密AWS凭证用于内部使用
|
||||
if (account.awsCredentials) {
|
||||
account.awsCredentials = this._decryptAwsCredentials(account.awsCredentials);
|
||||
}
|
||||
|
||||
logger.debug(`🔍 获取Bedrock账户 - ID: ${accountId}, 名称: ${account.name}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: account
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`❌ 获取Bedrock账户失败 - ID: ${accountId}`, error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 📋 获取所有账户列表
|
||||
async getAllAccounts() {
|
||||
try {
|
||||
const client = redis.getClientSafe();
|
||||
const keys = await client.keys('bedrock_account:*');
|
||||
const accounts = [];
|
||||
|
||||
for (const key of keys) {
|
||||
const accountData = await client.get(key);
|
||||
if (accountData) {
|
||||
const account = JSON.parse(accountData);
|
||||
|
||||
// 返回给前端时,不包含敏感信息,只显示掩码
|
||||
accounts.push({
|
||||
id: account.id,
|
||||
name: account.name,
|
||||
description: account.description,
|
||||
region: account.region,
|
||||
defaultModel: account.defaultModel,
|
||||
isActive: account.isActive,
|
||||
accountType: account.accountType,
|
||||
priority: account.priority,
|
||||
schedulable: account.schedulable,
|
||||
credentialType: account.credentialType,
|
||||
createdAt: account.createdAt,
|
||||
updatedAt: account.updatedAt,
|
||||
type: 'bedrock',
|
||||
hasCredentials: !!account.awsCredentials
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 按优先级和名称排序
|
||||
accounts.sort((a, b) => {
|
||||
if (a.priority !== b.priority) return a.priority - b.priority;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
logger.debug(`📋 获取所有Bedrock账户 - 共 ${accounts.length} 个`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: accounts
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('❌ 获取Bedrock账户列表失败', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// ✏️ 更新账户信息
|
||||
async updateAccount(accountId, updates = {}) {
|
||||
try {
|
||||
const accountResult = await this.getAccount(accountId);
|
||||
if (!accountResult.success) {
|
||||
return accountResult;
|
||||
}
|
||||
|
||||
const account = accountResult.data;
|
||||
|
||||
// 更新字段
|
||||
if (updates.name !== undefined) account.name = updates.name;
|
||||
if (updates.description !== undefined) account.description = updates.description;
|
||||
if (updates.region !== undefined) account.region = updates.region;
|
||||
if (updates.defaultModel !== undefined) account.defaultModel = updates.defaultModel;
|
||||
if (updates.isActive !== undefined) account.isActive = updates.isActive;
|
||||
if (updates.accountType !== undefined) account.accountType = updates.accountType;
|
||||
if (updates.priority !== undefined) account.priority = updates.priority;
|
||||
if (updates.schedulable !== undefined) account.schedulable = updates.schedulable;
|
||||
if (updates.credentialType !== undefined) account.credentialType = updates.credentialType;
|
||||
|
||||
// 更新AWS凭证
|
||||
if (updates.awsCredentials !== undefined) {
|
||||
if (updates.awsCredentials) {
|
||||
account.awsCredentials = this._encryptAwsCredentials(updates.awsCredentials);
|
||||
} else {
|
||||
delete account.awsCredentials;
|
||||
}
|
||||
}
|
||||
|
||||
account.updatedAt = new Date().toISOString();
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
await client.set(`bedrock_account:${accountId}`, JSON.stringify(account));
|
||||
|
||||
logger.info(`✅ 更新Bedrock账户成功 - ID: ${accountId}, 名称: ${account.name}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
id: account.id,
|
||||
name: account.name,
|
||||
description: account.description,
|
||||
region: account.region,
|
||||
defaultModel: account.defaultModel,
|
||||
isActive: account.isActive,
|
||||
accountType: account.accountType,
|
||||
priority: account.priority,
|
||||
schedulable: account.schedulable,
|
||||
credentialType: account.credentialType,
|
||||
updatedAt: account.updatedAt,
|
||||
type: 'bedrock'
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`❌ 更新Bedrock账户失败 - ID: ${accountId}`, error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 🗑️ 删除账户
|
||||
async deleteAccount(accountId) {
|
||||
try {
|
||||
const accountResult = await this.getAccount(accountId);
|
||||
if (!accountResult.success) {
|
||||
return accountResult;
|
||||
}
|
||||
|
||||
const client = redis.getClientSafe();
|
||||
await client.del(`bedrock_account:${accountId}`);
|
||||
|
||||
logger.info(`✅ 删除Bedrock账户成功 - ID: ${accountId}`);
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
logger.error(`❌ 删除Bedrock账户失败 - ID: ${accountId}`, error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 🎯 选择可用的Bedrock账户 (用于请求转发)
|
||||
async selectAvailableAccount() {
|
||||
try {
|
||||
const accountsResult = await this.getAllAccounts();
|
||||
if (!accountsResult.success) {
|
||||
return { success: false, error: 'Failed to get accounts' };
|
||||
}
|
||||
|
||||
const availableAccounts = accountsResult.data.filter(account =>
|
||||
account.isActive && account.schedulable
|
||||
);
|
||||
|
||||
if (availableAccounts.length === 0) {
|
||||
return { success: false, error: 'No available Bedrock accounts' };
|
||||
}
|
||||
|
||||
// 简单的轮询选择策略 - 选择优先级最高的账户
|
||||
const selectedAccount = availableAccounts[0];
|
||||
|
||||
// 获取完整账户信息(包含解密的凭证)
|
||||
const fullAccountResult = await this.getAccount(selectedAccount.id);
|
||||
if (!fullAccountResult.success) {
|
||||
return { success: false, error: 'Failed to get selected account details' };
|
||||
}
|
||||
|
||||
logger.debug(`🎯 选择Bedrock账户 - ID: ${selectedAccount.id}, 名称: ${selectedAccount.name}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: fullAccountResult.data
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('❌ 选择Bedrock账户失败', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 🧪 测试账户连接
|
||||
async testAccount(accountId) {
|
||||
try {
|
||||
const accountResult = await this.getAccount(accountId);
|
||||
if (!accountResult.success) {
|
||||
return accountResult;
|
||||
}
|
||||
|
||||
const account = accountResult.data;
|
||||
|
||||
logger.info(`🧪 测试Bedrock账户连接 - ID: ${accountId}, 名称: ${account.name}`);
|
||||
|
||||
// 尝试获取模型列表来测试连接
|
||||
const models = await bedrockRelayService.getAvailableModels(account);
|
||||
|
||||
if (models && models.length > 0) {
|
||||
logger.info(`✅ Bedrock账户测试成功 - ID: ${accountId}, 发现 ${models.length} 个模型`);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
status: 'connected',
|
||||
modelsCount: models.length,
|
||||
region: account.region,
|
||||
credentialType: account.credentialType
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unable to retrieve models from Bedrock'
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`❌ 测试Bedrock账户失败 - ID: ${accountId}`, error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 🔐 加密AWS凭证
|
||||
_encryptAwsCredentials(credentials) {
|
||||
try {
|
||||
const key = Buffer.from(config.security.encryptionKey, 'utf8');
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipher(this.ENCRYPTION_ALGORITHM, key);
|
||||
|
||||
const credentialsString = JSON.stringify(credentials);
|
||||
let encrypted = cipher.update(credentialsString, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
return {
|
||||
encrypted: encrypted,
|
||||
iv: iv.toString('hex')
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('❌ AWS凭证加密失败', error);
|
||||
throw new Error('Credentials encryption failed');
|
||||
}
|
||||
}
|
||||
|
||||
// 🔓 解密AWS凭证
|
||||
_decryptAwsCredentials(encryptedData) {
|
||||
try {
|
||||
const key = Buffer.from(config.security.encryptionKey, 'utf8');
|
||||
const decipher = crypto.createDecipher(this.ENCRYPTION_ALGORITHM, key);
|
||||
|
||||
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
return JSON.parse(decrypted);
|
||||
} catch (error) {
|
||||
logger.error('❌ AWS凭证解密失败', error);
|
||||
throw new Error('Credentials decryption failed');
|
||||
}
|
||||
}
|
||||
|
||||
// 🔍 获取账户统计信息
|
||||
async getAccountStats() {
|
||||
try {
|
||||
const accountsResult = await this.getAllAccounts();
|
||||
if (!accountsResult.success) {
|
||||
return { success: false, error: accountsResult.error };
|
||||
}
|
||||
|
||||
const accounts = accountsResult.data;
|
||||
const stats = {
|
||||
total: accounts.length,
|
||||
active: accounts.filter(acc => acc.isActive).length,
|
||||
inactive: accounts.filter(acc => !acc.isActive).length,
|
||||
schedulable: accounts.filter(acc => acc.schedulable).length,
|
||||
byRegion: {},
|
||||
byCredentialType: {}
|
||||
};
|
||||
|
||||
// 按区域统计
|
||||
accounts.forEach(acc => {
|
||||
stats.byRegion[acc.region] = (stats.byRegion[acc.region] || 0) + 1;
|
||||
stats.byCredentialType[acc.credentialType] = (stats.byCredentialType[acc.credentialType] || 0) + 1;
|
||||
});
|
||||
|
||||
return { success: true, data: stats };
|
||||
} catch (error) {
|
||||
logger.error('❌ 获取Bedrock账户统计失败', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new BedrockAccountService();
|
||||
Reference in New Issue
Block a user