wingbot 3.73.0 → 3.73.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wingbot",
3
- "version": "3.73.0",
3
+ "version": "3.73.1",
4
4
  "description": "Enterprise Messaging Bot Conversation Engine",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -58,7 +58,9 @@
58
58
  "compress-json": "^3.0.0",
59
59
  "deep-extend": "^0.6.0",
60
60
  "form-data": "^4.0.0",
61
- "graphql": "^16.8.1",
61
+ "graphql": "^16.11.0",
62
+ "graphql-depth-limit": "^1.1.0",
63
+ "graphql-query-complexity": "^1.1.0",
62
64
  "jsonwebtoken": "^9.0.2",
63
65
  "node-fetch": "^2.6.7",
64
66
  "path-to-regexp": "^6.3.0",
@@ -41,12 +41,12 @@ class BotAppSender extends ReturnSender {
41
41
  /**
42
42
  *
43
43
  * @param {SenderOptions} options
44
- * @param {string} userId
44
+ * @param {string} senderId
45
45
  * @param {object} incommingMessage
46
46
  * @param {ChatLogStorage} logger - console like logger
47
47
  */
48
- constructor (options, userId, incommingMessage, logger = null) {
49
- super(options, userId, incommingMessage, logger);
48
+ constructor (options, senderId, incommingMessage, logger = null) {
49
+ super(options, senderId, incommingMessage, logger);
50
50
 
51
51
  this.waits = true;
52
52
 
@@ -98,14 +98,16 @@ class BotAppSender extends ReturnSender {
98
98
  * @param {Buffer} data
99
99
  * @param {string} contentType
100
100
  * @param {string} fileName
101
+ * @param {string} [senderId]
101
102
  * @returns {Promise<UploadResult>}
102
103
  */
103
- async upload (data, contentType, fileName) {
104
+ async upload (data, contentType, fileName, senderId = this._senderId) {
104
105
  const formData = new FormData();
105
106
 
106
107
  const nonce = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(36).padEnd(11, '0');
107
108
 
108
109
  formData.append('nonce', nonce);
110
+ formData.append('senderId', senderId || '');
109
111
  formData.append('f0', data, { filename: fileName, contentType });
110
112
 
111
113
  const [token, agent] = await Promise.all([
package/src/LLM.js CHANGED
@@ -82,6 +82,11 @@ const Ai = require('./Ai');
82
82
  * @prop {string} [model]
83
83
  */
84
84
 
85
+ /**
86
+ * @typedef {object} LLMLogOptions
87
+ * @prop {VectorSearchResult} [vectorSearchResult]
88
+ */
89
+
85
90
  /**
86
91
  * @typedef {object} LLMChatProvider
87
92
  * @prop {LLMChatProviderPrompt} requestChat
@@ -237,9 +242,10 @@ class LLM {
237
242
  *
238
243
  * @param {LLMSession} session
239
244
  * @param {LLMProviderOptions} [options={}]
245
+ * @param {LLMLogOptions} [logOptions]
240
246
  * @returns {Promise<LLMMessage>}
241
247
  */
242
- async generate (session, options = {}) {
248
+ async generate (session, options = {}, logOptions = {}) {
243
249
  /** @type {LLMProviderOptions} */
244
250
  const opts = {
245
251
  ...(this._configuration.model && { model: this._configuration.model }),
@@ -248,7 +254,7 @@ class LLM {
248
254
 
249
255
  const prompt = session.toArray(true);
250
256
  const result = await this._provider.requestChat(prompt, opts);
251
- this.logPrompt(prompt, result);
257
+ this.logPrompt(prompt, result, logOptions.vectorSearchResult);
252
258
  return result;
253
259
  }
254
260
 
package/src/LLMSession.js CHANGED
@@ -7,6 +7,7 @@ const LLM = require('./LLM');
7
7
 
8
8
  /** @typedef {import('./Responder').QuickReply} QuickReply */
9
9
  /** @typedef {import('./LLM').LLMProviderOptions} LLMProviderOptions */
10
+ /** @typedef {import('./LLM').LLMLogOptions} LLMLogOptions */
10
11
 
11
12
  /** @typedef {'user'|'assistant'} LLMChatRole */
12
13
  /** @typedef {'system'} LLMSystemRole */
@@ -272,11 +273,12 @@ class LLMSession {
272
273
 
273
274
  /**
274
275
  *
275
- * @param {LLMProviderOptions} [options={}]
276
+ * @param {LLMProviderOptions} [providerOptions={}]
277
+ * @param {LLMLogOptions} [logOptions]
276
278
  * @returns {Promise<LLMMessage<any>>}
277
279
  */
278
- async generate (options = {}) {
279
- const result = await this._llm.generate(this, options);
280
+ async generate (providerOptions = {}, logOptions = {}) {
281
+ const result = await this._llm.generate(this, providerOptions, logOptions);
280
282
 
281
283
  this._generatedIndex = this._chat.length;
282
284
  this._chat.push(result);
@@ -8,6 +8,7 @@ const WingbotApiConnector = require('./WingbotApiConnector');
8
8
  // @ts-ignore
9
9
  const packageJson = require('../../package.json');
10
10
  const headersToAuditMeta = require('../utils/headersToAuditMeta');
11
+ const gqlRules = require('./gqlRules');
11
12
 
12
13
  const DEFAULT_GROUPS = ['botEditor', 'botAdmin', 'appToken'];
13
14
  const KEYS_URL = 'https://api.wingbot.ai/keys';
@@ -27,6 +28,12 @@ const DEFAULT_CACHE = 86400000; // 24 hours
27
28
  /** @typedef {import('../CallbackAuditLog')} AuditLog */
28
29
  /** @typedef {import('graphql')} GqlLib */
29
30
 
31
+ /**
32
+ * @typedef {object} Logger
33
+ * @prop {Function} log
34
+ * @prop {Function} error
35
+ */
36
+
30
37
  class GraphApi {
31
38
 
32
39
  /**
@@ -38,8 +45,11 @@ class GraphApi {
38
45
  * @param {string[]} [options.groups] - list of allowed bot groups
39
46
  * @param {boolean} [options.useBundledGql] - uses library bundled graphql definition
40
47
  * @param {AuditLog} [options.auditLog]
48
+ * @param {boolean} [options.isProduction]
49
+ * @param {boolean} [options.hideVerboseErrors]
50
+ * @param {Logger} [log=console]
41
51
  */
42
- constructor (apis, options) {
52
+ constructor (apis, options, log = console) {
43
53
  this._root = {
44
54
  version () {
45
55
  return packageJson.version;
@@ -62,6 +72,13 @@ class GraphApi {
62
72
  }
63
73
  };
64
74
 
75
+ this._log = log;
76
+ this._options = {
77
+ hideVerboseErrors: true,
78
+ isProduction: true,
79
+ ...options
80
+ };
81
+
65
82
  Object.assign(opts, options);
66
83
 
67
84
  apis.forEach((api) => Object.assign(this._root, api));
@@ -149,6 +166,19 @@ class GraphApi {
149
166
 
150
167
  const schema = await this._schema();
151
168
 
169
+ const { isProduction = true, hideVerboseErrors } = this._options;
170
+ const ast = this._gql.parse(body.query);
171
+ const errors = this._gql.validate(
172
+ schema,
173
+ ast,
174
+ gqlRules(body.variables, isProduction, hideVerboseErrors, this._log)
175
+ );
176
+
177
+ if (errors.length > 0) {
178
+ this._log.error('GQL failed', errors);
179
+ return { errors };
180
+ }
181
+
152
182
  const ctx = {
153
183
  token,
154
184
  groups: this._defaultGroups,
@@ -0,0 +1,87 @@
1
+ /* eslint-disable global-require */
2
+ /**
3
+ * @author David Menger
4
+ */
5
+ 'use strict';
6
+
7
+ /** @typedef {import('graphql').ValidationRule} ValidationRule */
8
+
9
+ /**
10
+ * @typedef {object} Logger
11
+ * @prop {Function} log
12
+ * @prop {Function} error
13
+ */
14
+
15
+ /**
16
+ *
17
+ * @param {object} variables
18
+ * @param {boolean} isProduction
19
+ * @param {boolean} hideVerboseErrors
20
+ * @param {Logger} [log=console]
21
+ * @returns {ValidationRule[]}
22
+ */
23
+ function gqlRules (variables, isProduction, hideVerboseErrors, log = console) {
24
+ // OPTIMIZATION FOR LAMBDA PERFORMANCE
25
+ const { GraphQLError, NoSchemaIntrospectionCustomRule } = require('graphql');
26
+ const { createComplexityRule, simpleEstimator } = require('graphql-query-complexity');
27
+ const depthLimit = require('graphql-depth-limit');
28
+
29
+ return [
30
+ ...(hideVerboseErrors ? [NoSchemaIntrospectionCustomRule] : []),
31
+ depthLimit(10),
32
+ createComplexityRule({
33
+ // The maximum allowed query complexity, queries above this threshold will be rejected
34
+ maximumComplexity: 1000,
35
+
36
+ // The query variables. This is needed because the variables are not available
37
+ // in the visitor of the graphql-js library
38
+ variables,
39
+
40
+ // The context object for the request (optional)
41
+ context: {},
42
+
43
+ // The maximum number of query nodes to evaluate (fields, fragments, composite types).
44
+ // If a query contains more than the specified number of nodes, the complexity rule will
45
+ // throw an error, regardless of the complexity of the query.
46
+ //
47
+ // Default: 10_000
48
+ maxQueryNodes: 10000,
49
+
50
+ // Optional callback function to retrieve the determined query complexity
51
+ // Will be invoked whether the query is rejected or not
52
+ // This can be used for logging or to implement rate limiting
53
+ onComplete: (complexity) => {
54
+ if (!isProduction) {
55
+ log.log('Determined query complexity: ', complexity);
56
+ }
57
+ },
58
+
59
+ // Optional function to create a custom error
60
+ createError: (max, actual) => {
61
+ const msg = `GQL Query too complex: ${actual}. Maximum allowed: ${max}`;
62
+
63
+ if (hideVerboseErrors) {
64
+ log.error(msg);
65
+ return new GraphQLError('');
66
+ }
67
+ log.log(msg);
68
+ return new GraphQLError(`Query is too complex: ${actual}. Maximum allowed complexity: ${max}`);
69
+ },
70
+
71
+ // Add any number of estimators. The estimators are invoked in order, the first
72
+ // numeric value that is being returned by an estimator is used as the field complexity.
73
+ // If no estimator returns a value, an exception is raised.
74
+ estimators: [
75
+ // Add more estimators here...
76
+
77
+ // This will assign each field a complexity of 1 if no other estimator
78
+ // returned a value.
79
+ simpleEstimator({
80
+ defaultComplexity: 1
81
+ })
82
+ ]
83
+ })
84
+ ];
85
+ }
86
+
87
+ module.exports = gqlRules;