import { IntegrationResponse } from '@cloudbase/functions-typings' import * as bot from './bot' import { CODES } from './codes' import { getChatRecordInputSchema, getConversationInputSchema, getFeedbackInputSchema, getRecommendQuestionsInputSchema, getTextToSpeechResultInputSchema, sendFeedbackInputSchema, sendMessageInputSchema, speechToTextInputSchema, textToSpeechInputSchema, updateConversationInputSchema, wxSendMessageInputSchema } from './schema' import { TcbContext } from './types' import { parseApiName, parseBotId, parseConversationId, parseQueryFromCtx, parseWxEnvParam, transformKeysToCamelCase } from './utils' function createIntegrationResponse( statusCode: number, code: string, message: string ): IntegrationResponse<{ code: string message: string }> { return { statusCode, headers: { 'Content-Type': 'application/json' }, body: { code, message } } } const OK_MESSAGE = 'OK' export class MultiBotRunner { static async run(event: unknown, context: TcbContext, botMap: Map) { const botId = parseBotId(context!.httpContext!.url) if (!botId) { return createIntegrationResponse(400, CODES.INVALID_BOT_REQUEST, 'Invalid bot request') } const bot = botMap.get(botId) || botMap.get('*') if (!bot) { return createIntegrationResponse(404, CODES.BOT_NOT_FOUND, `Bot with ID ${botId} not found`) } await BotRunner.run(event, context, bot) } } export class BotRunner { static async run(event: unknown, context: TcbContext, bot: bot.IBot) { return new BotRunner(bot).run(event, context) } constructor(readonly bot: bot.IBot) { // TODO: bot 校验 } async run(event: unknown, context: TcbContext) { // TODO: 公共参数校验 if (!context.httpContext) { return createIntegrationResponse(400, CODES.INVALID_HTTP_CONTEXT, 'Invalid HTTP context') } const apiFuncs: Record = { 'POST:send-message': this.sendMessageFunc.bind(this), 'POST:wx-send-message': this.wxSendMessageFunc.bind(this), 'GET:records': this.getChatRecordsFunc.bind(this), 'POST:recommend-questions': this.getRecommendQuestionsFunc.bind(this), 'POST:feedback': this.sendFeedbackFunc.bind(this), 'GET:feedback': this.getFeedbackFunc.bind(this), 'POST:speech-to-text': this.speechToTextFunc.bind(this), 'POST:text-to-speech': this.textToSpeechFunc.bind(this), 'GET:text-to-speech': this.getTextToSpeechResultFunc.bind(this), 'POST:conversation': this.createConversationFunc.bind(this), 'GET:conversation': this.getConversationFunc.bind(this), 'PATCH:conversation': this.updateConversationFunc.bind(this), 'DELETE:conversation': this.deleteConversationFunc.bind(this), 'GET:': this.getBotInfoFunc.bind(this), 'GET:healthz': async () => ({ message: OK_MESSAGE }) } const httpMethod = context.httpContext.httpMethod const apiName = parseApiName(context.httpContext.url) const apiFunc = apiFuncs[`${httpMethod}:${apiName}`] if (apiFunc) { return apiFunc(event, context) } return createIntegrationResponse(404, CODES.BOT_API_NOT_FOUND, `Bot API ${httpMethod}:${apiName} has not been supported`) } /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ private async sendMessageFunc(event: unknown, context: TcbContext) { try { return await this.bot.sendMessage(sendMessageInputSchema.passthrough().parse(event)) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'sendMessage' failed, message: ${e}`) } } private async wxSendMessageFunc(event: unknown, context: TcbContext) { try { return await this.bot.wxSendMessage({ ...wxSendMessageInputSchema.passthrough().parse(transformKeysToCamelCase(event)), ...parseWxEnvParam(context) }) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'wxSendMessage' failed, message: ${e}`) } } private async getChatRecordsFunc(event: unknown, context: TcbContext) { if (!this.bot.getChatRecords) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'getChatRecords' has not been implemented, please implement it in your bot" ) } try { return await this.bot.getChatRecords(getChatRecordInputSchema.passthrough().parse(parseQueryFromCtx(context))) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'getChatRecords' failed, message: ${e}`) } } /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ private async getRecommendQuestionsFunc(event: unknown, context: TcbContext) { if (!this.bot.getRecommendQuestions) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'getRecommendQuestions' has not been implemented, please implement it in your bot" ) } try { return await this.bot.getRecommendQuestions(getRecommendQuestionsInputSchema.passthrough().parse(event)) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'getRecommendQuestions' failed, message: ${e}`) } } private async sendFeedbackFunc(event: unknown): Promise { if (!this.bot.sendFeedback) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'sendFeedback' has not been implemented, please implement it in your bot" ) } try { return await this.bot.sendFeedback(sendFeedbackInputSchema.passthrough().parse(event)) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'sendFeedback' failed, message: ${e}`) } } private async getFeedbackFunc(event: unknown, context: TcbContext): Promise { if (!this.bot.getFeedback) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'getFeedback' has not been implemented, please implement it in your bot" ) } try { return await this.bot.getFeedback(getFeedbackInputSchema.passthrough().parse(parseQueryFromCtx(context))) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'getFeedback' failed, message: ${e}`) } } private async getBotInfoFunc(): Promise { if (!this.bot.getBotInfo) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'getBotInfo' has not been implemented, please implement it in your bot" ) } try { return await this.bot.getBotInfo() } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'getBotInfo' failed, message: ${e}`) } } private async speechToTextFunc(event: unknown, _context: TcbContext): Promise { if (!this.bot.speechToText) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'speechToText' has not been implemented, please implement it in your bot" ) } try { return await this.bot.speechToText(speechToTextInputSchema.passthrough().parse(event)) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'speechToText' failed, message: ${e}`) } } private async textToSpeechFunc(event: unknown, _context: TcbContext): Promise { if (!this.bot.textToSpeech) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'textToSpeech' has not been implemented, please implement it in your bot" ) } try { return await this.bot.textToSpeech(textToSpeechInputSchema.passthrough().parse(event)) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'textToSpeech' failed, message: ${e}`) } } private async getTextToSpeechResultFunc( event: unknown, context: TcbContext ): Promise { if (!this.bot.getTextToSpeechResult) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'getTextToSpeech' has not been implemented, please implement it in your bot" ) } try { return await this.bot.getTextToSpeechResult(getTextToSpeechResultInputSchema.passthrough().parse(parseQueryFromCtx(context))) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'getTextToSpeech' failed, message: ${e}`) } } private async createConversationFunc(_event: unknown, _context: TcbContext): Promise { if (!this.bot.createConversation) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'createConversation' has not been implemented, please implement it in your bot" ) } try { return await this.bot.createConversation() } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'createConversation' failed, message: ${e}`) } } private async getConversationFunc(event: unknown, context: TcbContext): Promise { if (!this.bot.getConversation) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'getConversation' has not been implemented, please implement it in your bot" ) } try { const conversationId = parseConversationId(context!.httpContext!.url) || '' return await this.bot.getConversation({ ...getConversationInputSchema.passthrough().parse(parseQueryFromCtx(context)), conversationId }) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'getConversation' failed, message: ${e}`) } } private async updateConversationFunc(event: unknown, context: TcbContext): Promise { if (!this.bot.updateConversation) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'updateConversation' has not been implemented, please implement it in your bot" ) } try { const conversationId = parseConversationId(context!.httpContext!.url) if (!conversationId) { return createIntegrationResponse( 404, CODES.INVALID_BOT_REQUEST, "Bot API 'updateConversation' missing required parameters 'conversationId' " ) } return await this.bot.updateConversation({ ...updateConversationInputSchema.passthrough().parse(event), conversationId }) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'updateConversation' failed, message: ${e}`) } } private async deleteConversationFunc(_event: unknown, context: TcbContext): Promise { if (!this.bot.deleteConversation) { return createIntegrationResponse( 404, CODES.BOT_API_NOT_IMPLEMENTED, "Bot API 'deleteConversation' has not been implemented, please implement it in your bot" ) } try { const conversationId = parseConversationId(context!.httpContext!.url) if (!conversationId) { return createIntegrationResponse( 404, CODES.INVALID_BOT_REQUEST, "Bot API 'deleteConversation' missing required parameters 'conversationId' " ) } return await this.bot.deleteConversation({ conversationId }) } catch (e) { return createIntegrationResponse(500, CODES.BOT_API_ERROR, `Bot API 'deleteConversation' failed, message: ${e}`) } } } interface BotMethod { (event: unknown, context: TcbContext): Promise }