console.log({ query: req.query, params: req.params, body: req.body }); const msgrSettings = settings_667ef98d3ee8930a5debcbdb; const paSettings = proactive_agent_settings; const studioToken = msgrSettings ? { Authorization: `Bearer ${msgrSettings.messenger.token}`, } : {}; const channel = req.body?.metadata?.channel | "copilot"; let bot_user = { ...paSettings.messenger.user, }; const stripHTMLTags = (input) => { if (typeof input !== "string") { throw new Error("Input must be a string"); } return input.replace(/<\/?[^>]+(>|$)/g, ""); // Matches and removes HTML tags } const app = { routes: [], route: function (regexp, fn) { this.routes.push({ regexp: new RegExp(regexp), function: fn }); }, run: function (req, res) { const path = req.params[0].split( `/ProxyScript/run/${req.params.workspaceId}/${req.params.branch}/${req.params.route}` )[1] || "/"; for (var route of this.routes) { console.log(`Checking route: ${route.regexp}`); console.log(`Against path: ${path}`); if (route.regexp.test(path)) { console.log(`Matched route: ${route.regexp}`); route.function(req, res, route.regexp.exec(path)); return; } } // If no route matches, return a 404 status const status = { path: req.params[0], status: 404 }; res.status(404).send(status); }, }; const redisKey = (userId) => { return `user-map-${userId}`; }; const axioshelper = (message, config, callback, retries = 3, delay = 100) => { console.log(config); return new Promise(async (resolve, reject) => { for (let i = 0; i < retries; i++) { try { const response = await axios(config); if (response.status < 200 || response.status > 299) { if (callback) { await callback(); } else { throw new Error( `Axios Request failed! ${JSON.stringify(response)}` ); } } resolve(response); break; } catch (error) { if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx console.log(error.response.data); console.log(error.response.status); console.log(error.response.headers); } else if (error.request) { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js console.log(error.request); } else { // Something happened in setting up the request that triggered an Error console.log("Error", error.message); } console.log(error.config); if (i === retries - 1) { if (callback) { await callback(); } console.log({ retry: i + 1, error: "out of retries", message, url: config.url, }); reject(error); } else { console.log({ level: "warn", message: `Attempt ${i + 1} failed.`, url: config.url, }); await new Promise((resolve) => setTimeout(resolve, delay)); } } } }); }; async function createSessionMap(umStr, sessionMap) { console.log({ method: "createSessionMap", sessionMap }); let keys = Object.keys(sessionMap); keys.forEach(async (key) => { await redis.hSet(umStr, key, sessionMap[key]); }); await redis.expire(umStr, 3600); } async function m2mAuthentication() { let data = new URLSearchParams({ grant_type: paSettings.copilot.oauth2.grant_type, client_id: paSettings.copilot.oauth2.clientId, client_secret: paSettings.copilot.oauth2.clientSecret, audience: paSettings.copilot.oauth2.audience, }); let token = await axioshelper("m2m authentication", { method: "post", url: paSettings.copilot.tokenUrl, headers: { "Content-Type": "application/x-www-form-urlencoded", }, data, }) .then((response) => { return response.data.access_token; }) .catch((error) => { console.error("Error during M2M authentication:", error); throw error; }); return token; } app.route( "^(?:/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/contexts/([^/]+))(?:/$)?$", async (req, res, match) => { try { console.log(`Received request for contexts: ${match}`); const modelName = req.params?.modelName || context_updater_settings.modelName; // expect 2 matches if (match.length > 2) { const tenantId = match[1]; const contextOwner = match[2]; const smStr = redisKey(contextOwner); let sessionMap = await redis.hGetAll(smStr); if (!sessionMap || !sessionMap.conversationId) { // no session data is okay for copilot, create one now sessionMap = { customerId: paSettings.copilot.customerId, upn: contextOwner, tenantId, }; await createSessionMap(smStr, sessionMap); } console.log({ sessionMap }); const metadata = { userId: bot_user.userId, channel, }; let input = undefined; req.body["vcx:fields"].forEach((field) => { if (field["vcx:field"] == "searchTerm") { input = "Search for " + field["vcx:typedValue"]["vcx:stringValue"]; } }); if (input) { const recognizedData = await axioshelper("run model", { method: "post", url: `${settings_667ef98d3ee8930a5debcbdb.nlu.apiBaseURL}Model/run/${req.params.workspaceId}/${req.params.branch}/${modelName}`, headers: studioToken, data: { input: input, conversationId: sessionMap.conversationId, settings: settings_667ef98d3ee8930a5debcbdb.nlu.settings, metadata: metadata, }, }) .then((response) => { return response.data; }) .catch((error) => { //If we have error handling turned off, don't record the error if (settings_667ef98d3ee8930a5debcbdb.errorHandling === false) { throw error; } else { // Conversation ID is generated by the NLU // Must make sure Conversation ID is created properly for an error transaction // Get it from session map if it exists, or generate it if we got the error at the very first transaction. const conversationId = sessionMap.conversationId || uuidv4(); let errorPayload = { id: uuidv4(), workspaceId: req.params.workspaceId, errorInfo: { name: error.name, message: error.message, stack: error.stack, }, conversationId, input: req.body.input, answers: [ settings_667ef98d3ee8930a5debcbdb.responses.modelError, ], req: { params: req.params, query: req.query, body: req.body, headers: req.headers, }, metadata, classificationResults: [ { label: "Model Error", value: 0.0, }, ], nerResults: { entities: [], sourceEntities: [], }, // Reporting tags to match GlobalSupport function reporting tag: "ERROR", reporting: { tag: "ERROR", modelResponse: "ERROR", }, }; // Add model name errorPayload.req.params.route = modelName; //add any other details you wish to see in transaction data return errorPayload; } }); if (recognizedData.conversationId) { await redis.hSet( smStr, "conversationId", recognizedData.conversationId ); await redis.expire(smStr, 3600); if (sessionMap.conversationId) { } else { db.analytics.addConversation({ id: recognizedData.conversationId, }); } } recognizedData._id = ( await db.analytics.addTransaction(recognizedData) ).insertedId; // Send Answers to Messenger new Promise(async (resolve) => { for (const [index, answer] of recognizedData.answers.entries()) { let bearerAuth = await m2mAuthentication(); if (!bearerAuth) { throw Error("Unable to Authenticate M2M"); } let body = answer; if (typeof answer === "string" || answer instanceof String) { body = stripHTMLTags(body); body = { source: "//wa/us-east-1/int/demo/plain", type: "verint.ui_messages.text.v1", id: "ca5057b8-2c1d-4758-89d6-0370d6f28cc2", time: "2020-07-30T14:44:00+00:00", data: { version: "1.0.0", format: "plain", persona_icon: { id: "Verint-Bot-Head", color: "#007ACC", }, title_icon: { id: "message-status-info", color: "#FF5722", }, body: body, }, }; } console.log({ data: { customerId: sessionMap.customerId, upn: sessionMap.upn, ...body, }, }); let cloudEventResponse = await axioshelper( "copilot cloud-event", { method: "post", url: `https://apigw.us-east-1.wrk-1.aws.hydra.verint.com/int/wa/v1/cloud-event`, headers: { Authorization: `Bearer ${bearerAuth}`, }, data: { customerId: sessionMap.customerId, upn: sessionMap.upn, ...body, }, } ); console.log({ cloudEventResponse: cloudEventResponse.data }); } resolve(true); }); // scrub response before sending delete recognizedData.req; res.send(recognizedData); } else { res.status(400).send({ error: "Bad Request no search term found." }); } } else { res .status(400) .send({ error: "Bad Request no context owner or tenantId found." }); } } catch (error) { console.error(error); return res.status(500).send({ status: 500, }); } } ); app.run(req, res);