diff --git a/lib/error-responses.js b/lib/error-responses.js index dd1860b..6b325a3 100644 --- a/lib/error-responses.js +++ b/lib/error-responses.js @@ -25,6 +25,19 @@ function noNluDeclared() { return `Could not evaluate request, no NLU engines declared in the manifest`; } +function errorSettingBuiltInContext(path) { + return 'Error setting built-in context property ' + path; +} + +function errorDeletingBuiltInContext(path) { + return 'Error deleting built-in context property ' + path; +} + +function builtInContextValidationError() { + return 'Error validating your built-in context object, it will not be saved'; +} + + // Return response errors module.exports = { @@ -32,5 +45,8 @@ module.exports = { couldNotReadNluType: couldNotReadNluType, invalidAction: invalidAction, noEvaluationAction: noEvaluationAction, - noNluDeclared: noNluDeclared + noNluDeclared: noNluDeclared, + errorSettingBuiltInContext: errorSettingBuiltInContext, + errorDeletingBuiltInContext: errorDeletingBuiltInContext, + builtInContextValidationError: builtInContextValidationError }; diff --git a/lib/evaluation-response.js b/lib/evaluation-response.js index 6edbb14..904b3de 100644 --- a/lib/evaluation-response.js +++ b/lib/evaluation-response.js @@ -6,7 +6,9 @@ function EvaluationResponse(callback) { responseCode: ErrorCodes.ok, requestResult: {}, intentities: [], + threshold: 0.85, handleUtterance: true, + routeByEntities: true, context: { }, internal:{ @@ -26,4 +28,13 @@ EvaluationResponse.prototype.rejectUtterance = function() { return this; }; +EvaluationResponse.prototype.setRoutingByEntities = function(value) { + if(typeof(value) !== 'boolean') { + console.warn('Could not set RouteByEntities flag, given value was not a boolean'); + } else { + this.response.routeByEntities = value; + } + return this; +}; + module.exports = EvaluationResponse; diff --git a/lib/handler.js b/lib/handler.js index 66d4dc0..1df0ccb 100644 --- a/lib/handler.js +++ b/lib/handler.js @@ -2,7 +2,6 @@ © Copyright IBM Corp. 2017 */ - 'use strict'; const i18 = require('i18next'); @@ -10,13 +9,16 @@ const sprintf = require('i18next-sprintf-postprocessor'); const Response = require('./response'); const EvaluationResponse = require('./evaluation-response'); const logger = require('./logger'); -const Conversation = require('watson-developer-cloud/conversation/v1'); +const Assistant = require('watson-developer-cloud/assistant/v1'); const Promise = require('bluebird'); const fs = require('fs'); const path = require('path'); const async = require('async'); const errors = require('./error-responses'); const instrument = require('./instrument.js'); +const WaEnvUtils = require('./nlu/utils'); +const builtInContextUtils = require('wa-context-utils'); + const nluFolder = '/nlu'; const wcsFileName = '/wcs.json'; @@ -29,17 +31,15 @@ let Handler = function () { * Initializes the handler, sets up the connection to wcs */ Handler.prototype.initialize = function () { - if(process.env.WCS_USERNAME && process.env.WCS_URL && process.env.WCS_PASSWORD && process.env.WCS_VERSION_DATE - && process.env.WCS_VERSION && process.env.WCS_WORKSPACE_ID && process.env.WCS_WORKSPACE_NAME && - process.env.WCS_WORKSPACE_LANGUAGE) { - setupWCSCredentialsFromEnv(this); + if(WaEnvUtils.isValidWaEnvVars()) { + this.wcsCredentials = WaEnvUtils.setupWCSCredentialsFromEnv(); } else { let rawJson = fs.readFileSync(path.join(process.env.skillSDKResDir, nluFolder, wcsFileName)); this.wcsCredentials = JSON.parse(rawJson); } if (this.wcsCredentials) { let credentials = this.wcsCredentials.credentials; - setupWcs(this, credentials.url, credentials.username, credentials.password, credentials.version_date) + setupWcs(this, credentials.url, credentials.username, credentials.password, credentials.version_date, credentials.version, credentials.iam_apikey) } }; @@ -49,6 +49,7 @@ Handler.prototype.initialize = function () { * @param callback - a callback function that gets the evaluation response */ Handler.prototype.handleEvaluationRequest = function (request, callback) { + let self = this; if(!this.engines || this.engines.length < 1) { callback({responseCode: 500, requestResult: errors.noNluDeclared()}); return; @@ -56,9 +57,16 @@ Handler.prototype.handleEvaluationRequest = function (request, callback) { let evaluationResponse = new EvaluationResponse((err, result) => { instrument.exitTime(evaluationResponse.response, "evaluate"); + if(self.manifest && self.manifest.threshold) { + evaluationResponse.response.threshold = self.manifest.threshold; + } saveContext(this, request, context, evaluationResponse); callback(err, evaluationResponse.response); }); + // set the routingByEntities flag to the value defined in the manifest + if(this.manifest && this.manifest.routeByEntities !== undefined) { + evaluationResponse.setRoutingByEntities(this.manifest.routeByEntities); + } let context = createContext(request); let state = this.state || 'DEFAULT'; do { @@ -101,22 +109,15 @@ Handler.prototype.evaluateRequest = function (request, evaluationResponse, conte evaluationResponse.response.intentities.push(intentity); } }); - let textResponse; - if(output) { - context.skill = output.context ? output.context : {}; - if(output.actions) { - for(let action of output.actions) { - evaluationResponse.response.actions.push(action) - } - } - textResponse = output.text ? output.text : undefined; - } + let textResponse = output && output.text ? output.text : undefined; + copyFromNluResponse(context, evaluationResponse, output); callback(textResponse, evaluationResponse, context, undefined); } }); }; + Handler.prototype.handleRequest = function (request, callback) { logger.info('Request', JSON.stringify(request)); // State and session context for short access @@ -227,6 +228,69 @@ Handler.prototype.saveEvaluationContext = function (context, evaluationContext) context.session = evaluationContext.session.attributes; }; +/** + * sset built in context + * @param context - context object created by the sdk + * @param path - path to property + * @param value - desired value to be set + */ +Handler.prototype.setBuiltinContextProperty = function(context, path, value) { + const result = builtInContextUtils.setProperty(JSON.parse(JSON.stringify(context.builtin)), path, value); + if(result.valid) { + context.builtin = result.obj; + logger.info('Successfully set built-in context property ' + path); + return result + } else { + logger.error(errors.errorSettingBuiltInContext(path)); + return result; + } +}; + +/** + * delete a built in context property + * @param context - sdk context object + * @param path - path to property + * @returns {{}} + */ +Handler.prototype.deleteBuiltinContextProperty = function(context, path) { + const result = builtInContextUtils.deleteProperty(JSON.parse(JSON.stringify(context.builtin)), path); + if(result.valid) { + context.builtin = result.obj; + logger.log('Successfully deleted built-in context property ' + path); + return result; + } else { + logger.error(errors.errorDeletingBuiltInContext(path)); + return result; + } +}; + + +/** + * get built in context + * @param context + * @param path + * @returns {*} + */ +Handler.prototype.getBuiltinContextProperty = function(context, path) { + const result = builtInContextUtils.getProperty(context.builtin, path); + if(result.valid) { + return result; + } else { + logger.error('Error getting built-in context property ' + path); + return result; + } +}; + +/** + * validate built in context + * @param context - user altered built-in context + * @returns {boolean} + */ +Handler.prototype.validateBuiltinContext = function(context) { + return builtInContextUtils.validateBuiltinContext(context.builtin); +}; + + module.exports = Handler; @@ -260,21 +324,34 @@ let getIntentity = function (self, request, cb) { * sets up your WCS credentials, these will be used to access your * WCS service * + * @param self * @param wcsUrl - your wcs url, for US: https://gateway.watsonplatform.net/conversation/api * for Germany: https://gateway-fra.watsonplatform.net/conversation/api * @param wcsUsername - your wcs username * @param wcsPassword - your wcs password * @param versionDate - your wcs version date + * @param apiKey */ -let setupWcs = function (self, wcsUrl, wcsUsername, wcsPassword, versionDate) { +let setupWcs = function (handler, wcsUrl, wcsUsername, wcsPassword, versionDate, version, apiKey) { try { - self.conversation = Promise.promisifyAll( - new Conversation({ + let options; + if(apiKey) { + options = { + version: version, + iam_apikey: apiKey, + url: wcsUrl + } + + } else { + options = { url: wcsUrl, username: wcsUsername, password: wcsPassword, - version_date: versionDate - }) + version: versionDate + } + } + handler.conversation = Promise.promisifyAll( + new Assistant(options) ); } catch (err) { console.error('Conversation service failure or missing credentials.'); @@ -339,7 +416,8 @@ let createContext = function (request) { return { utterance: request.context.application.attributes, session: request.context.session.attributes, - skill: skillContext + skill: skillContext, + builtin: request.context.builtin }; }; @@ -351,6 +429,10 @@ let createContext = function (request) { * @param response */ let saveContext = function (self, request, context, response) { + const validationResult = builtInContextUtils.validateBuiltinContext(context.builtin); + if(!validationResult.valid) { + logger.error(errors.builtInContextValidationError()); + } request.context.session.attributes.state = self.state; if(response.response.context && response.response.context.inConversation !== undefined) { context.skill.inConversation = response.response.context.inConversation; @@ -365,30 +447,35 @@ let saveContext = function (self, request, context, response) { Object.assign(response.response, { context: { application: request.context.application, - session: sessionContext + session: sessionContext, + builtin: context.builtin } }); }; + /** - * Sets up the handler's wcs credentials from environment variables - * @param self + * copies data from the Nlu evaluation response to the evaluation response and context + * @param context + * @param evaluationResponse + * @param NluResponse */ - -let setupWCSCredentialsFromEnv = function (self) { - self.wcsCredentials = { - "workspace": { - [process.env.WCS_WORKSPACE_LANGUAGE]: { - "name": process.env.WCS_WORKSPACE_NAME, - "workspace_id": process.env.WCS_WORKSPACE_ID +let copyFromNluResponse = function(context, evaluationResponse, NluResponse) { + if(NluResponse) { + if(NluResponse.context) { + context.skill = NluResponse.context || {}; + //set handleUtterance flag from NLU context + evaluationResponse.response.handleUtterance = NluResponse.context.handleUtterance !== undefined ? NluResponse.context.handleUtterance : evaluationResponse.response.handleUtterance; + //set routeByEntities flag from NLU context + evaluationResponse.response.routeByEntities = NluResponse.context.routeByEntities !== undefined ? NluResponse.context.routeByEntities : evaluationResponse.response.routeByEntities; + } + if(NluResponse.actions) { + for(let action of NluResponse.actions) { + evaluationResponse.response.actions.push(action) } - }, - "credentials": { - "url": process.env.WCS_URL, - "version": process.env.WCS_VERSION, - "version_date": process.env.WCS_VERSION_DATE, - "password": process.env.WCS_PASSWORD, - "username": process.env.WCS_USERNAME } } -}; \ No newline at end of file +}; + + + diff --git a/lib/nlu/bundles/engines/wcs/workspace.js b/lib/nlu/bundles/engines/wcs/workspace.js index 178858e..eb78459 100644 --- a/lib/nlu/bundles/engines/wcs/workspace.js +++ b/lib/nlu/bundles/engines/wcs/workspace.js @@ -4,17 +4,19 @@ 'use strict'; -const watson = require('watson-developer-cloud'); +const Assistant = require('watson-developer-cloud/assistant/v1'); const hash = require('object-hash'); const logger = require('../../../../logger'); var Workspace = function (credentials) { let options = JSON.parse(JSON.stringify(credentials)); - // options.url = "https://gateway-fra.watsonplatform.net/conversation/api/"; - + // hack for WA changing version to version_date + if(options.version_date) { + options.version = options.version_date; + } try { - this.wcs = watson.conversation(options); + this.wcs = new Assistant(options); } catch (error) { logger.error("Error setting up your wcs workspace, please check your credentials" + " (either in env vars or wcs.json). \n" + error); diff --git a/lib/nlu/factory.js b/lib/nlu/factory.js index 4a8c007..6f7aba7 100644 --- a/lib/nlu/factory.js +++ b/lib/nlu/factory.js @@ -14,6 +14,7 @@ const {getSupportedUser} = require('./supported'); const errors = require('../error-responses'); const fs = require('fs'); const resPath = process.env.skillSDKResDir; +const WaEnvUtils = require('./utils'); /** @@ -126,40 +127,20 @@ module.exports = Factory; function getNLU(type, name) { return new Promise(function (resolve, reject) { - let nlu; - if(process.env.WCS_USERNAME && process.env.WCS_URL && process.env.WCS_PASSWORD && process.env.WCS_VERSION_DATE - && process.env.WCS_VERSION && process.env.WCS_WORKSPACE_ID && process.env.WCS_WORKSPACE_NAME && - process.env.WCS_WORKSPACE_LANGUAGE) { - nlu = createNLUFromEnv(); - } else { - nlu = require(`${resPath}/nlu/${type}`); - } - if (!nlu) { - reject(errors.couldNotReadNluType); - } - nlu.name = name; - nlu.type = type; - resolve(nlu) + let nlu; + if(WaEnvUtils.isValidWaEnvVars() && type === 'wcs') { + nlu = WaEnvUtils.setupWCSCredentialsFromEnv(); + } else { + nlu = require(`${resPath}/nlu/${type}`); + } + if (!nlu) { + reject(errors.couldNotReadNluType); + } + nlu.name = name; + nlu.type = type; + resolve(nlu) }).catch(function(err) { logger.error(errors.couldNotReadNluType(type)); next(); }) } - -function createNLUFromEnv() { - return { - "workspace": { - [process.env.WCS_WORKSPACE_LANGUAGE]: { - "name": process.env.WCS_WORKSPACE_NAME, - "workspace_id": process.env.WCS_WORKSPACE_ID - } - }, - "credentials": { - "url": process.env.WCS_URL, - "version": process.env.WCS_VERSION, - "version_date": process.env.WCS_VERSION_DATE, - "password": process.env.WCS_PASSWORD, - "username": process.env.WCS_USERNAME - } - }; -} diff --git a/lib/nlu/utils.js b/lib/nlu/utils.js new file mode 100644 index 0000000..10ef8d4 --- /dev/null +++ b/lib/nlu/utils.js @@ -0,0 +1,62 @@ +/* +© Copyright IBM Corp. 2017 +*/ + + + +'use strict'; + + +/** + * check that all the WA env var credentials exist + * @returns {string} + */ +let isValidWaEnvVars = function() { + return (((process.env.WCS_USERNAME && process.env.WCS_URL && process.env.WCS_PASSWORD && process.env.WCS_VERSION_DATE + && process.env.WCS_VERSION) || + (process.env.WA_API_KEY && process.env.WCS_VERSION_DATE)) && + (process.env.WCS_WORKSPACE_ID && process.env.WCS_WORKSPACE_NAME && + process.env.WCS_WORKSPACE_LANGUAGE)); +}; + + +/** + * Sets up the handler's wcs credentials from environment variables + * @param handler + */ + +let setupWCSCredentialsFromEnv = function () { + let options = { + "workspace": { + [process.env.WCS_WORKSPACE_LANGUAGE]: { + "name": process.env.WCS_WORKSPACE_NAME, + "workspace_id": process.env.WCS_WORKSPACE_ID + } + } + }; + // new way of wa authentication - with iam api-key + if(process.env.WA_API_KEY) { + options.credentials = { + "url": process.env.WCS_URL, + "version": process.env.WCS_VERSION_DATE, + "iam_apikey": process.env.WA_API_KEY + } + } + // deprecated way of wa authentication + else if(process.env.WCS_PASSWORD && process.env.WCS_USERNAME) { + options.credentials = { + "url": process.env.WCS_URL, + "version": process.env.WCS_VERSION, + "version_date": process.env.WCS_VERSION_DATE, + "password": process.env.WCS_PASSWORD, + "username": process.env.WCS_USERNAME + } + } + return options; +}; + + +module.exports = { + isValidWaEnvVars: isValidWaEnvVars, + setupWCSCredentialsFromEnv: setupWCSCredentialsFromEnv +}; \ No newline at end of file diff --git a/lib/server/api/swagger/swagger.yaml b/lib/server/api/swagger/swagger.yaml index 2d796ce..d7047ba 100644 --- a/lib/server/api/swagger/swagger.yaml +++ b/lib/server/api/swagger/swagger.yaml @@ -283,7 +283,7 @@ definitions: error: type: integer example: 200 - shouldEndSession: + deleteSkillSession: type: boolean example: true captureInput: @@ -348,6 +348,10 @@ definitions: type: string example: 'Hello' description: utterance + retext: + type: string + example: 'hello' + description: utterance context: type: object description: context of conversation diff --git a/package.json b/package.json index b39d774..2d2a576 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "skill-sdk-nodejs", - "version": "0.0.16", + "version": "0.0.18", "description": "This is the SDK to be used when developing a skill for Watson Assistant Solutions using Node.js", "main": "index.js", "scripts": { @@ -32,9 +32,10 @@ "promise-reflect": "^1.1.0", "swagger-express-mw": "^0.7.0", "swagger-tools": "^0.10.1", - "watson-developer-cloud": "^2.4.3", + "watson-developer-cloud": "^3.13.0", "winston": "^2.3.1", - "xregexp": "^3.1.1" + "xregexp": "^3.1.1", + "wa-context-utils": "^0.0.3" }, "devDependencies": { "mocha": "^3.2.0",