const { Signer } = require('@volcengine/openapi'); const fetch = require('node-fetch'); const { AccessToken, privileges } = require('../lib/token'); class VolcengineService { constructor() { this.baseUrl = 'https://rtc.volcengineapi.com'; this.service = 'rtc'; this.region = 'cn-north-1'; this.version = '2024-12-01'; } async startVoiceChat(config) { console.log('[Volcengine] Starting voice chat (S2S端到端 + LLM混合, API v2024-12-01)'); console.log('[Volcengine] RoomId:', config.RoomId); // ProviderParams 可能是 JSON 字符串或对象 let pp = config.Config.S2SConfig?.ProviderParams; if (typeof pp === 'string') { try { pp = JSON.parse(pp); } catch (e) { pp = {}; } } console.log('[Volcengine] S2S AppId:', pp?.app?.appid); console.log('[Volcengine] S2S model:', pp?.dialog?.extra?.model); console.log('[Volcengine] S2S speaker:', pp?.tts?.speaker); console.log('[Volcengine] ProviderParams type:', typeof config.Config.S2SConfig?.ProviderParams); console.log('[Volcengine] LLM EndPointId:', config.Config.LLMConfig?.EndPointId); console.log('[Volcengine] Tools:', config.Config.LLMConfig?.Tools?.length || 0); console.log('[Volcengine] Full request body:', JSON.stringify(config, null, 2)); const result = await this._callOpenAPI('StartVoiceChat', config); console.log('[Volcengine] StartVoiceChat response:', JSON.stringify(result, null, 2)); return result; } async updateVoiceChat(params) { console.log('[Volcengine] Updating voice chat (v2024-12-01)'); console.log('[Volcengine] UpdateVoiceChat params:', JSON.stringify(params, null, 2)); const result = await this._callOpenAPI('UpdateVoiceChat', params); console.log('[Volcengine] UpdateVoiceChat response:', JSON.stringify(result, null, 2)); return result; } async stopVoiceChat(params) { console.log('[Volcengine] Stopping voice chat, RoomId:', params.RoomId); return this._callOpenAPI('StopVoiceChat', params); } /** * 生成 RTC 入房 Token * 使用官方 AccessToken 库:https://github.com/volcengine/rtc-aigc-demo/blob/main/Server/token.js */ generateRTCToken(roomId, userId) { const appId = process.env.VOLC_RTC_APP_ID; const appKey = process.env.VOLC_RTC_APP_KEY; if (!appId || !appKey || appKey === 'your_rtc_app_key') { console.warn('[Volcengine] RTC AppKey not configured, returning placeholder token'); return `placeholder_token_${roomId}_${userId}_${Date.now()}`; } const token = new AccessToken(appId, appKey, roomId, userId); const expireTime = Math.floor(Date.now() / 1000) + 24 * 3600; // 24 小时有效 token.expireTime(expireTime); token.addPrivilege(privileges.PrivPublishStream, 0); token.addPrivilege(privileges.PrivSubscribeStream, 0); const serialized = token.serialize(); console.log(`[Volcengine] RTC Token generated for room=${roomId}, user=${userId}`); return serialized; } async _callOpenAPI(action, body, versionOverride) { const ak = process.env.VOLC_ACCESS_KEY_ID; const sk = process.env.VOLC_SECRET_ACCESS_KEY; const version = versionOverride || this.version; if (!ak || !sk || ak === 'your_access_key_id') { console.warn(`[Volcengine] Credentials not configured, returning mock response for ${action}`); return this._mockResponse(action, body); } // 与官方 rtc-aigc-demo 完全一致的签名方式 const openApiRequestData = { region: this.region, method: 'POST', params: { Action: action, Version: version, }, headers: { Host: 'rtc.volcengineapi.com', 'Content-type': 'application/json', }, body, }; const signer = new Signer(openApiRequestData, this.service); signer.addAuthorization({ accessKeyId: ak, secretKey: sk }); const url = `${this.baseUrl}?Action=${action}&Version=${version}`; console.log(`[Volcengine] ${action} calling:`, url); try { const response = await fetch(url, { method: 'POST', headers: openApiRequestData.headers, body: JSON.stringify(body), }); const data = await response.json(); if (data?.ResponseMetadata?.Error) { const err = data.ResponseMetadata.Error; throw new Error(`${action} failed: ${err.Code} - ${err.Message}`); } return data; } catch (error) { console.error(`[Volcengine] ${action} error:`, error.message); throw error; } } /** * Mock 响应(开发阶段凭证未配置时使用) */ _mockResponse(action, params) { console.log(`[Volcengine][MOCK] ${action} called with:`, JSON.stringify(params, null, 2).substring(0, 500)); return { ResponseMetadata: { RequestId: `mock-${Date.now()}`, Action: action }, Result: { Message: 'Mock response - credentials not configured' }, }; } } module.exports = new VolcengineService();