backend-manager 3.2.14 → 3.2.16

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": "backend-manager",
3
- "version": "3.2.14",
3
+ "version": "3.2.16",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -18,7 +18,7 @@ Module.prototype.main = function () {
18
18
 
19
19
  payload.data.payload.deletionRegex = payload.data.payload.deletionRegex ? powertools.regexify(payload.data.payload.deletionRegex) : payload.data.payload.deletionRegex;
20
20
 
21
- if (!payload.user.roles.admin && assistant.meta.environment === 'production') {
21
+ if (!payload.user.roles.admin && assistant.isProduction()) {
22
22
  return reject(assistant.errorify(`Admin required.`, {code: 401, sentry: false, send: false, log: false}));
23
23
  }
24
24
 
@@ -12,7 +12,7 @@ Module.prototype.main = function () {
12
12
  return new Promise(async function(resolve, reject) {
13
13
 
14
14
  // Check if the user is an admin
15
- if (!payload.user.roles.admin && assistant.meta.environment === 'production') {
15
+ if (!payload.user.roles.admin && assistant.isProduction()) {
16
16
  return reject(assistant.errorify(`Admin required.`, {code: 401, sentry: false, send: false, log: false}));
17
17
  }
18
18
 
@@ -14,7 +14,7 @@ Module.prototype.main = function () {
14
14
  return new Promise(async function(resolve, reject) {
15
15
 
16
16
  // If the user is not an admin, reject
17
- if (!payload.user.roles.admin && assistant.meta.environment === 'production') {
17
+ if (!payload.user.roles.admin && assistant.isProduction()) {
18
18
  return reject(assistant.errorify(`Admin required.`, {code: 401, sentry: false, send: false, log: false}));
19
19
  }
20
20
 
@@ -14,9 +14,7 @@ module.exports = function (payload, config) {
14
14
  subject: `Free ${config.brand.name} download link for ${payload.name || 'you'}!`,
15
15
  template: 'd-1d730ac8cc544b7cbccc8fa4a4b3f9ce',
16
16
  group: 24077,
17
- options: {
18
- copy: false,
19
- },
17
+ copy: false,
20
18
  data: {},
21
19
  }
22
20
  }
@@ -25,7 +25,7 @@ Module.prototype.main = function () {
25
25
  Api.resolveUser({adminRequired: true})
26
26
  .then(async (user) => {
27
27
 
28
- self.ultimateJekyllOAuth2Url = assistant.meta.environment === 'development'
28
+ self.ultimateJekyllOAuth2Url = assistant.isDevelopment()
29
29
  ? `http://localhost:4000/oauth2`
30
30
  : `${Manager.config.brand.url}/oauth2`
31
31
  self.oauth2 = null;
@@ -40,11 +40,11 @@ Module.prototype.main = function () {
40
40
  : payload.data.payload.redirect
41
41
 
42
42
  payload.data.payload.referrer = typeof payload.data.payload.referrer === 'undefined'
43
- ? (assistant.meta.environment === 'development' ? `http://localhost:4000/account` : `${Manager.config.brand.url}/account`)
43
+ ? (assistant.isDevelopment() ? `http://localhost:4000/account` : `${Manager.config.brand.url}/account`)
44
44
  : payload.data.payload.referrer
45
45
 
46
46
  payload.data.payload.serverUrl = typeof payload.data.payload.serverUrl === 'undefined'
47
- ? (assistant.meta.environment === 'development' ? `${Manager.project.functionsUrl}/bm_api` : `${Manager.project.functionsUrl}/bm_api`)
47
+ ? (assistant.isDevelopment() ? `${Manager.project.functionsUrl}/bm_api` : `${Manager.project.functionsUrl}/bm_api`)
48
48
  : payload.data.payload.serverUrl
49
49
 
50
50
  payload.data.payload.provider = payload.data.payload.provider || '';
@@ -29,7 +29,7 @@ let Module = {
29
29
  action: 'create-test-accounts',
30
30
  // label: '',
31
31
  });
32
-
32
+
33
33
  let assistant = self.assistant;
34
34
 
35
35
  let response = {
@@ -51,7 +51,7 @@ let Module = {
51
51
  }
52
52
 
53
53
 
54
- if (assistant.meta.environment === 'development') {
54
+ if (assistant.isDevelopment()) {
55
55
  assistant.log(response);
56
56
  return res.status(response.status).json(response);
57
57
  } else {
@@ -37,7 +37,7 @@ function Analytics(Manager, options) {
37
37
  self._ds = 'app';
38
38
  self._uuid = options.uuid || self._request.ip || self.Manager.SERVER_UUID;
39
39
  self._uuid = self._uuid.match(uuidRegex) ? self._uuid : self.generateId(self._uuid);
40
- self._debug = typeof options.debug === 'undefined' ? (self.Manager.assistant.meta.environment === 'development') : options.debug;
40
+ self._debug = typeof options.debug === 'undefined' ? (self.Manager.assistant.isDevelopment()) : options.debug;
41
41
  self._pageview = typeof options.pageview === 'undefined' ? true : options.pageview;
42
42
  self._version = self.Manager.package.version;
43
43
  self._initialized = false;
@@ -37,7 +37,7 @@ function Analytics(Manager, options) {
37
37
  self._data_soruce = 'server';
38
38
  self._uuid = options.uuid || self._request.ip || self.Manager.SERVER_UUID;
39
39
  self._uuid = self._uuid.match(uuidRegex) ? self._uuid : self.generateId(self._uuid);
40
- self._debug = typeof options.debug === 'undefined' ? self._assistant.meta.environment === 'development' : options.debug;
40
+ self._debug = typeof options.debug === 'undefined' ? self._assistant.isDevelopment() : options.debug;
41
41
  self._pageview = typeof options.pageview === 'undefined' ? true : options.pageview;
42
42
  self._version = self.Manager.package.version;
43
43
  self._initialized = false;
@@ -207,7 +207,7 @@ Analytics.prototype.send = function (event) {
207
207
  }
208
208
 
209
209
  // Log
210
- if (self._assistant.meta.environment === 'development') {
210
+ if (self._assistant.isDevelopment()) {
211
211
  self._assistant.log('analytics().send(): Sending...', JSON.stringify(body));
212
212
  }
213
213
 
@@ -221,7 +221,7 @@ Analytics.prototype.send = function (event) {
221
221
  body: body,
222
222
  })
223
223
  .then((r) => {
224
- if (self._assistant.meta.environment === 'development') {
224
+ if (self._assistant.isDevelopment()) {
225
225
  self._assistant.log('analytics().send(): Success', r);
226
226
  }
227
227
  })
@@ -158,7 +158,7 @@ BackendAssistant.prototype.init = function (ref, options) {
158
158
 
159
159
  // Log options
160
160
  if (
161
- (self.meta.environment === 'development')
161
+ (self.isDevelopment())
162
162
  && ((self.request.method !== 'OPTIONS') || (self.request.method === 'OPTIONS' && options.showOptionsLog))
163
163
  && (self.request.method !== 'undefined')
164
164
  // && (self.request.method !== 'undefined' && typeof self.request.method !== 'undefined')
@@ -192,6 +192,18 @@ BackendAssistant.prototype.getEnvironment = function () {
192
192
  }
193
193
  };
194
194
 
195
+ BackendAssistant.prototype.isDevelopment = function () {
196
+ const self = this;
197
+
198
+ return self.meta.environment === 'development';
199
+ }
200
+
201
+ BackendAssistant.prototype.isProduction = function () {
202
+ const self = this;
203
+
204
+ return self.meta.environment === 'production';
205
+ }
206
+
195
207
  BackendAssistant.prototype.logProd = function () {
196
208
  const self = this;
197
209
 
@@ -273,7 +285,6 @@ BackendAssistant.prototype._log = function () {
273
285
  const self = this;
274
286
 
275
287
  // 1. Convert args to a normal array
276
- const isDevelopment = self.meta.environment === 'development';
277
288
  const logs = [...Array.prototype.slice.call(arguments)];
278
289
 
279
290
  // 2. Prepend log prefix log string
@@ -294,7 +305,7 @@ BackendAssistant.prototype._log = function () {
294
305
  console.debug.apply(console, logs);
295
306
  } else if (logs[1] === 'notice') {
296
307
  logs.splice(1,1)
297
- if (isDevelopment) {
308
+ if (self.isDevelopment()) {
298
309
  console.log.apply(console, logs);
299
310
  } else {
300
311
  self.ref.functions.logger.write({
@@ -330,6 +341,12 @@ BackendAssistant.prototype._log = function () {
330
341
  }
331
342
  }
332
343
 
344
+ BackendAssistant.prototype.getUser = function () {
345
+ const self = this;
346
+
347
+ return self?.usage?.user || self.request.user;
348
+ }
349
+
333
350
  BackendAssistant.prototype.errorify = function (e, options) {
334
351
  const self = this;
335
352
  const res = self.ref.res;
@@ -27,7 +27,6 @@ Middleware.prototype.run = function (library, options) {
27
27
  const data = assistant.request.data;
28
28
  const geolocation = assistant.request.geolocation;
29
29
  const client = assistant.request.client;
30
- const isProduction = assistant.meta.environment === 'production';
31
30
 
32
31
  // Set options
33
32
  options = options || {};
@@ -57,11 +56,11 @@ Middleware.prototype.run = function (library, options) {
57
56
 
58
57
  // Setup usage
59
58
  if (options.setupUsage) {
60
- assistant.usage = await Manager.Usage().init(assistant, {log: isProduction});
59
+ assistant.usage = await Manager.Usage().init(assistant, {log: assistant.isProduction()});
61
60
  }
62
61
 
63
62
  // Log working user
64
- const workingUser = assistant?.usage?.user || assistant.request.user;
63
+ const workingUser = assistant.getUser();
65
64
  assistant.log(`Middleware.process(): User (${workingUser.auth.uid}, ${workingUser.auth.email}, ${workingUser.plan.id}=${workingUser.plan.status}):`, JSON.stringify(workingUser));
66
65
 
67
66
  // Setup analytics
@@ -90,7 +90,7 @@ Manager.prototype.init = function (exporter, options) {
90
90
  self.assistant = self.Assistant().init(undefined, options.assistant);
91
91
 
92
92
  // Set more properties (need to wait for assistant to determine if DEV)
93
- self.project.functionsUrl = self.assistant.meta.environment === 'development'
93
+ self.project.functionsUrl = self.assistant.isDevelopment()
94
94
  ? `http://localhost:5001/${self.project.projectId}/${self.project.resourceZone}`
95
95
  : `https://${self.project.resourceZone}-${self.project.projectId}.cloudfunctions.net`;
96
96
 
@@ -103,7 +103,7 @@ Manager.prototype.init = function (exporter, options) {
103
103
  }
104
104
 
105
105
  // Handle dev environments
106
- if (self.assistant.meta.environment === 'development') {
106
+ if (self.assistant.isDevelopment()) {
107
107
  const semverMajor = require('semver/functions/major')
108
108
  const semverCoerce = require('semver/functions/coerce')
109
109
  const semverUsing = semverMajor(semverCoerce(process.versions.node));
@@ -152,7 +152,7 @@ Manager.prototype.init = function (exporter, options) {
152
152
  dsn: sentryDSN,
153
153
  release: sentryRelease,
154
154
  beforeSend(event, hint) {
155
- if (self.assistant.meta.environment === 'development' && !self.options.reportErrorsInDev) {
155
+ if (self.assistant.isDevelopment() && !self.options.reportErrorsInDev) {
156
156
  self.assistant.error(new Error('[Sentry] Skipping Sentry because we\'re in development'), hint)
157
157
  return null;
158
158
  }
@@ -436,7 +436,7 @@ Manager.prototype.init = function (exporter, options) {
436
436
  self.storage();
437
437
  }
438
438
 
439
- if (self.assistant.meta.environment === 'development' && options.fetchStats) {
439
+ if (self.assistant.isDevelopment() && options.fetchStats) {
440
440
  setTimeout(function () {
441
441
  self.assistant.log('Fetching meta/stats...');
442
442
  self.libraries.admin
@@ -689,7 +689,7 @@ Manager.prototype.storage = function (options) {
689
689
 
690
690
  if (
691
691
  options.temporary
692
- && self.assistant.meta.environment === 'development'
692
+ && self.assistant.isDevelopment()
693
693
  && options.clear
694
694
  ) {
695
695
  self.assistant.log('Removed temporary file @', location);
@@ -1,5 +1,23 @@
1
1
  const fetch = require('wonderful-fetch');
2
+ const jetpack = require('fs-jetpack');
2
3
  const powertools = require('node-powertools');
4
+ const _ = require('lodash');
5
+
6
+ const TOKEN_COST_TABLE = {
7
+ // Nov 6th, 2023
8
+ 'gpt-4-1106-preview': {
9
+ input: 0.0100,
10
+ output: 0.0300,
11
+ },
12
+ 'gpt-4': {
13
+ input: 0.0300,
14
+ output: 0.0600,
15
+ },
16
+ 'gpt-3.5-turbo': {
17
+ input: 0.0010,
18
+ output: 0.0020,
19
+ },
20
+ }
3
21
 
4
22
  function OpenAI(assistant, key) {
5
23
  const self = this;
@@ -9,37 +27,71 @@ function OpenAI(assistant, key) {
9
27
  self.user = assistant.user;
10
28
  self.key = key;
11
29
 
30
+ self.tokens = {
31
+ total: {
32
+ count: 0,
33
+ price: 0,
34
+ },
35
+ input: {
36
+ count: 0,
37
+ price: 0,
38
+ },
39
+ output: {
40
+ count: 0,
41
+ price: 0,
42
+ },
43
+ }
44
+
12
45
  return self;
13
46
  }
14
47
 
15
- OpenAI.prototype.request = function (options, messages, template) {
48
+ OpenAI.prototype.request = function (options) {
16
49
  const self = this;
17
50
  const Manager = self.Manager;
18
51
  const assistant = self.assistant;
19
52
 
20
- assistant.log('callOpenAI(): Starting', self.key);
21
- assistant.log('callOpenAI(): Starting', options, messages, template);
22
-
23
53
  return new Promise(async function(resolve, reject) {
24
- options = options || {};
25
- options.prompt = options.prompt || '';
26
- options.timeout = options.timeout || 120000;
27
- options.model = options.model || (
28
- self.user.plan.id === 'basic'
29
- ? BASIC_MODEL
30
- : PREMIUM_MODEL
31
- );
32
- options.user = options.user || assistant.request.geolocation.ip;
54
+ options = _.merge({}, options);
33
55
 
34
- // Load prompt
35
- options.prompt = powertools.template(
36
- jetpack.read(`${__dirname}/prompts/${options.prompt}.md`),
37
- template,
38
- );
56
+ options.model = typeof options.model === 'undefined' ? 'gpt-3.5-turbo' : options.model;
57
+ options.timeout = typeof options.timeout === 'undefined' ? 120000 : options.timeout;
58
+ options.moderate = typeof options.moderate === 'undefined' ? true : options.moderate;
59
+ options.user = options.user || assistant.getUser();
60
+
61
+ options.prompt = options.prompt || {};
62
+ options.prompt.path = options.prompt.path || '';
63
+ options.prompt.settings = options.prompt.settings || {};
64
+
65
+ options.message = options.message || {};
66
+ options.message.path = options.message.path || '';
67
+ options.message.settings = options.message.settings || {};
39
68
 
40
- // assistant.log('callOpenAI():', options);
69
+ options.history = options.history || {};
70
+ options.history.messages = options.history.messages || [];
71
+ options.history.limit = typeof options.history.limit === 'undefined' ? 5 : options.history.limit;
72
+
73
+ options.response = typeof options.response === 'undefined' ? undefined : options.response;
74
+ options.temperature = typeof options.temperature === 'undefined' ? 0.7 : options.temperature;
75
+ options.maxTokens = typeof options.maxTokens === 'undefined' ? 512 : options.maxTokens;
76
+
77
+ assistant.log('callOpenAI(): Starting', self.key);
41
78
  assistant.log('callOpenAI(): Starting', options);
42
79
 
80
+ // Load prompt
81
+ const prompt = powertools.template(
82
+ jetpack.read(options.prompt.path),
83
+ options.prompt.settings,
84
+ ).trim();
85
+ const message = powertools.template(
86
+ jetpack.read(options.message.path),
87
+ options.message.settings,
88
+ ).trim();
89
+ const user = options.user?.auth?.uid || assistant.request.geolocation.ip;
90
+
91
+ assistant.log('callOpenAI(): Prompt', prompt);
92
+ assistant.log('callOpenAI(): Message', message);
93
+ assistant.log('callOpenAI(): User', user);
94
+
43
95
  function _request(mode, options) {
44
96
  return new Promise(async function(resolve, reject) {
45
97
  let resultPath = '';
@@ -58,25 +110,33 @@ OpenAI.prototype.request = function (options, messages, template) {
58
110
 
59
111
  if (mode === 'chatgpt') {
60
112
  request.url = 'https://api.openai.com/v1/chat/completions';
61
- messages = messages.slice(-MAX_MESSAGE_LENGTH);
62
- messages.unshift({
113
+ options.history.messages = options.history.messages.slice(-options.history.limit);
114
+ options.history.messages.unshift({
63
115
  role: 'system',
64
- content: options.prompt,
116
+ content: prompt,
117
+ });
118
+ options.history.messages.push({
119
+ role: 'user',
120
+ content: message,
65
121
  });
122
+
123
+ // Log message
124
+ assistant.log('callOpenAI(): Messages', options.history.messages);
125
+
66
126
  request.body = {
67
127
  model: options.model,
68
- response_format: { type: 'json_object' },
69
- messages: messages,
70
- temperature: 0.7, // default: 0.7
71
- max_tokens: 512, // default: infinity
72
- user: options.user,
128
+ response_format: options.response === 'json' ? { type: 'json_object' } : undefined,
129
+ messages: options.history.messages,
130
+ temperature: options.temperature,
131
+ max_tokens: options.maxTokens,
132
+ user: user,
73
133
  }
74
134
  resultPath = 'choices[0].message.content';
75
135
  } else if (mode === 'moderation') {
76
136
  request.url = 'https://api.openai.com/v1/moderations';
77
137
  request.body = {
78
- input: messages.map(obj => obj.content).join('\n\nMessage:\n'),
79
- user: options.user,
138
+ input: message,
139
+ user: user,
80
140
  }
81
141
  resultPath = 'results[0]';
82
142
  }
@@ -102,25 +162,39 @@ OpenAI.prototype.request = function (options, messages, template) {
102
162
  });
103
163
  }
104
164
 
105
- await _request('moderation', options)
106
- .then(async (r) => {
107
- // assistant.log('callOpenAI(): moderation', r);
108
- assistant.log('callOpenAI(): Moderated');
165
+ // Moderate if needed
166
+ let moderation = null;
167
+ if (options.moderate) {
168
+ moderation = await _request('moderation', options)
169
+ .then(async (r) => {
170
+ assistant.log('callOpenAI(): Moderated', r);
109
171
 
110
- // If the moderation is not approved, return
111
- if (r.flagged) {
112
- return reject(new Error('Moderation not approved'));
113
- }
172
+ // Save moderation
173
+ self.moderation = r;
114
174
 
115
- await _request('chatgpt', options)
116
- .then((r) => {
117
- try {
118
- return resolve(JSON.parse(r));
119
- } catch (e) {
120
- return reject(e);
121
- }
175
+ return r;
122
176
  })
123
- .catch((e) => reject(e));
177
+ .catch((e) => e);
178
+
179
+ if (moderation.flagged) {
180
+ return reject(assistant.errorify(`This request is inappropriate`, {code: 400, sentry: false, send: false, log: false}));
181
+ }
182
+ }
183
+
184
+ // Request
185
+ await _request('chatgpt', options)
186
+ .then((r) => {
187
+ try {
188
+ const content = options.response === 'json' ? JSON.parse(r) : r;
189
+
190
+ return resolve({
191
+ content: content,
192
+ tokens: self.tokens,
193
+ moderation: moderation,
194
+ })
195
+ } catch (e) {
196
+ return reject(e);
197
+ }
124
198
  })
125
199
  .catch((e) => reject(e));
126
200
  });