@mongoosejs/studio 0.0.73 → 0.0.75

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.
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const mongoose = require('mongoose');
5
+ const vm = require('vm');
6
+
7
+ const ExecuteScriptParams = new Archetype({
8
+ userId: {
9
+ $type: mongoose.Types.ObjectId
10
+ },
11
+ chatMessageId: {
12
+ $type: mongoose.Types.ObjectId
13
+ },
14
+ script: {
15
+ $type: String
16
+ }
17
+ }).compile('ExecuteScriptParams');
18
+
19
+ module.exports = ({ db }) => async function executeScript(params) {
20
+ const { userId, chatMessageId, script } = new ExecuteScriptParams(params);
21
+ const ChatMessage = db.model('__Studio_ChatMessage');
22
+
23
+ const chatMessage = await ChatMessage.findById(chatMessageId);
24
+ if (!chatMessage) {
25
+ throw new Error('Chat message not found');
26
+ }
27
+
28
+ // Create a sandbox with the db object
29
+ const logs = [];
30
+ const sandbox = { db, console: {} };
31
+
32
+ // Capture console logs
33
+ sandbox.console.log = function() {
34
+ const args = Array.from(arguments);
35
+ logs.push(args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' '));
36
+ };
37
+
38
+ const context = vm.createContext(sandbox);
39
+
40
+ let output;
41
+ let error;
42
+
43
+ try {
44
+ // Execute the script in the sandbox
45
+ output = await vm.runInContext(wrappedScript(script), context);
46
+
47
+ chatMessage.script = script;
48
+ chatMessage.executionResult = { output, logs: logs.join('\n'), error: null };
49
+ await chatMessage.save();
50
+
51
+ return { chatMessage };
52
+ } catch (err) {
53
+ error = err.message;
54
+
55
+ // Update the chat message with the error
56
+ await ChatMessage.updateOne(
57
+ { _id: chatMessageId },
58
+ {
59
+ script,
60
+ executionResult: {
61
+ output: null,
62
+ logs: logs.join('\n'),
63
+ error
64
+ }
65
+ }
66
+ );
67
+
68
+ throw new Error(`Script execution failed: ${error}`);
69
+ }
70
+ };
71
+
72
+ const wrappedScript = script => `(async () => {
73
+ ${script}
74
+ })()`;
@@ -0,0 +1,3 @@
1
+ 'use strict';
2
+
3
+ exports.executeScript = require('./executeScript');
@@ -0,0 +1,253 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const mongoose = require('mongoose');
5
+
6
+ const CreateChatMessageParams = new Archetype({
7
+ chatThreadId: {
8
+ $type: mongoose.Types.ObjectId
9
+ },
10
+ userId: {
11
+ $type: mongoose.Types.ObjectId
12
+ },
13
+ content: {
14
+ $type: String
15
+ }
16
+ }).compile('CreateChatMessageParams');
17
+
18
+ const systemPrompt = `
19
+ You are a data querying assistant who writes scripts for users accessing MongoDB data using Node.js and Mongoose.
20
+
21
+ Keep scripts concise. Avoid unnecessary comments and error handling, unless explicitly asked for by the user.
22
+
23
+ Assume the user has pre-defined schemas and models. Do not define any new schemas or models for the user.
24
+
25
+ Use async/await where possible. Assume top-level await is allowed.
26
+
27
+ Format output as Markdown, including code fences for any scripts the user requested.
28
+
29
+ Add a brief text description of what the script does.
30
+
31
+ If the user's query is best answered with a chart, return a Chart.js 4 configuration as \`return { $chart: chartJSConfig };\`
32
+
33
+ Example output:
34
+
35
+ The following script counts the number of users which are not deleted.
36
+
37
+ \`\`\`javascript
38
+ const users = await db.model('User').find({ isDeleted: false });
39
+ return { numUsers: users.length };
40
+ \`\`\`
41
+
42
+ -----------
43
+
44
+ Here is a description of the user's models. Assume these are the only models available in the system unless explicitly instructed otherwise by the user.
45
+
46
+ Vehicle (collection name: Vehicle)
47
+ - _id: ObjectId
48
+ - userId: ObjectId (ref User)
49
+ - authorizedUsers: Array
50
+ - status: String
51
+ - _previousStatus: String
52
+ - make: String
53
+ - model: String
54
+ - year: Number
55
+ - color: String
56
+ - nickName: String
57
+ - tollTagId: String
58
+ - availabilityDate: String
59
+ - vehicleFeatures: Array
60
+ - vehicleIdentityId: ObjectId (ref VehicleIdentity)
61
+ - images: Object
62
+ - thumbnail: String
63
+ - photos: Array
64
+ - registrationPhoto: String
65
+ - smartcarId: String
66
+ - smartcarPermissions: Array
67
+ - percentRemaining: Number
68
+ - range: Number
69
+ - location: Object
70
+ - locationTimestamp: Date
71
+ - pricePerMonth: Number
72
+ - pricePerWeek: Number
73
+ - returnAddress: String
74
+ - returnLocation: Object
75
+ - description: String
76
+ - maxUnlockRadiusMiles: Number
77
+ - returnRadiusKM: Number
78
+ - numReviews: Number
79
+ - numCompletedTrips: Number
80
+ - totalReviewScore: Number
81
+ - totalRevenueGenerated: Number
82
+ - bookingsCount: Number
83
+ - odometer: Number
84
+ - pickupInstructions: String
85
+ - returnInstructions: String
86
+ - unlockInstructions: String
87
+ - chargingState: String
88
+ - isPluggedIn: Boolean
89
+ - currentBookingId: ObjectId (ref Booking)
90
+ - maxTripLengthDays: Number
91
+ - smartcarWebhookIds: Array
92
+ - lastErrorMessage: String
93
+ - vinNumber: String
94
+ - licensePlate: String
95
+ - licensePlateState: String
96
+ - registrationExpirationDate: String
97
+ - style: Object
98
+ - driveUnit: Object
99
+ - efficiencyPackage: String
100
+ - performancePackage: String
101
+ - connectivityProvider: String
102
+ - abiId: String
103
+ - platformFeePercentage: Number
104
+ - promotionsEnabled: Boolean
105
+ - modelDescription: String
106
+ - isConcierge: Boolean
107
+ - lastLoadedSuperchargerInvoicesAt: Date
108
+ - privateBookingNotes: String
109
+ - publicMarketingNotes: String
110
+ - fleetKeyState: Object
111
+ - weakBattery: Boolean
112
+ - fleetITconnectionStartDate: Date
113
+ - fleetITId: Number
114
+ - interiorTrim: String
115
+ - skipLoadingSuperchargerInvoices: Boolean
116
+ - chargeKey: Object
117
+ - securityDepositPerUser: Array
118
+
119
+ User (collection name User)
120
+ - _id: ObjectId
121
+ - firstName: String
122
+ - lastName: String
123
+ - email: String (required, lowercase)
124
+ - picture: String
125
+ - zipCode: String
126
+ - telephone: String
127
+ - verifiedTelephone: Boolean
128
+ - verifiedEmail: Boolean
129
+ - googleUserId: String
130
+ - appleUserId: String
131
+ - smartcarUserId: String
132
+ - stripeCustomerId: String
133
+ - roles: Array of String (enum: 'root', 'admin', 'manager', 'host', 'verified-driver', 'internal-tester', 'authorized-payer')
134
+ - accountPreference: String (enum: 'host', 'guest', 'both')
135
+ - defaultPaymentMethodId: ObjectId (ref PaymentMethod)
136
+ - personaInquiryId: String
137
+ - personaAccountId: String
138
+ - personaInquiryStatus: String (enum: 'created', 'approved', 'declined')
139
+ - personaHasDriverLicense: Boolean
140
+ - driverLicenseState: String
141
+ - numReviews: Number
142
+ - totalReviewScore: Number
143
+ - needsSmartcarReconnect: Boolean
144
+ - isFrozen: Boolean
145
+ - instantTransactions: Boolean
146
+ - publicNotes: String
147
+ - bookingNotes: String
148
+ - hostOnboarding: Object
149
+ - guestOnboarding: Object
150
+ - canUseApp: Boolean
151
+ - useWalletForChargeReceipt: Boolean
152
+ - appOnboardingToken: Object
153
+ - addedToLaunchlist: Boolean
154
+ - stripeAccountId: String
155
+ - isStripeConnectedAccountConfirmed: Boolean
156
+ - requestedAccountDeletion: Boolean
157
+ - qrcodeUrl: String
158
+ - checkrCandidateId: String
159
+ - checkrInvitationId: String
160
+ - checkrInvitationUrl: String
161
+ - checkrInvitationStatus: String
162
+ - referralCode: String
163
+ - referralUrl: String
164
+ - referralConversionId: ObjectId (ref ReferralConversion)
165
+ - abiOwnerId: String
166
+ - abiRenterId: String
167
+ - driverLicense: Object
168
+ - securityDeposit: Number
169
+ - tripcityOwnerId: String
170
+ - bannedUserId: ObjectId (ref BannedUser)
171
+ - isConcierge: Boolean
172
+ - lastHostDashboardLoginAt: Date
173
+ - internalSlackChannel: String
174
+ - skipLoadingSuperchargerInvoices: Boolean
175
+ `.trim();
176
+
177
+ module.exports = ({ db }) => async function createChatMessage(params) {
178
+ const { chatThreadId, userId, content, script } = new CreateChatMessageParams(params);
179
+ const ChatThread = db.model('__Studio_ChatThread');
180
+ const ChatMessage = db.model('__Studio_ChatMessage');
181
+
182
+ // Check that the user owns the thread
183
+ const chatThread = await ChatThread.findOne({ _id: chatThreadId });
184
+ if (!chatThread) {
185
+ throw new Error('Chat thread not found');
186
+ }
187
+ if (userId != null && chatThread.userId.toString() !== userId.toString()) {
188
+ throw new Error('Not authorized');
189
+ }
190
+
191
+ const messages = await ChatMessage.find({ chatThreadId }).sort({ createdAt: 1 });
192
+ const llmMessages = messages.map(m => ({
193
+ role: m.role,
194
+ content: m.content
195
+ }));
196
+ llmMessages.push({ role: 'user', content });
197
+
198
+ if (chatThread.title == null) {
199
+ getChatCompletion([
200
+ { role: 'system', content: 'Summarize the following chat thread in 6 words or less, as a helpful thread title' },
201
+ ...llmMessages
202
+ ]).then(res => {
203
+ const title = res.choices?.[0]?.message?.content;
204
+ chatThread.title = title;
205
+ return chatThread.save();
206
+ }).catch(() => {});
207
+ }
208
+
209
+ llmMessages.unshift({
210
+ role: 'system',
211
+ content: systemPrompt
212
+ });
213
+
214
+ // Create the chat message and get OpenAI response in parallel
215
+ const chatMessages = await Promise.all([
216
+ ChatMessage.create({
217
+ chatThreadId,
218
+ role: 'user',
219
+ content,
220
+ script,
221
+ executionResult: null
222
+ }),
223
+ getChatCompletion(llmMessages).then(res => {
224
+ const content = res.choices?.[0]?.message?.content || '';
225
+ console.log('Content', content, res);
226
+ return ChatMessage.create({
227
+ chatThreadId,
228
+ role: 'assistant',
229
+ content
230
+ });
231
+ })
232
+ ]);
233
+
234
+ return { chatMessages };
235
+ };
236
+
237
+ async function getChatCompletion(messages, options = {}) {
238
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
239
+ method: 'POST',
240
+ headers: {
241
+ Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
242
+ 'Content-Type': 'application/json'
243
+ },
244
+ body: JSON.stringify({
245
+ model: 'gpt-4o',
246
+ max_tokens: 2500,
247
+ ...options,
248
+ messages
249
+ })
250
+ });
251
+
252
+ return await response.json();
253
+ };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const mongoose = require('mongoose');
5
+
6
+ const CreateChatThreadParams = new Archetype({
7
+ userId: {
8
+ $type: mongoose.Types.ObjectId
9
+ }
10
+ }).compile('CreateChatThreadParams');
11
+
12
+ module.exports = ({ db }) => async function createChatThread(params) {
13
+ const { userId } = new CreateChatThreadParams(params);
14
+ const ChatThread = db.model('__Studio_ChatThread');
15
+
16
+ const chatThread = await ChatThread.create({ userId });
17
+
18
+ return { chatThread };
19
+ };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const mongoose = require('mongoose');
5
+
6
+ const GetChatThreadParams = new Archetype({
7
+ chatThreadId: {
8
+ $type: mongoose.Types.ObjectId
9
+ },
10
+ userId: {
11
+ $type: mongoose.Types.ObjectId
12
+ }
13
+ }).compile('GetChatThreadParams');
14
+
15
+ module.exports = ({ db }) => async function getChatThread(params) {
16
+ const { chatThreadId, userId } = new GetChatThreadParams(params);
17
+ const ChatThread = db.model('__Studio_ChatThread');
18
+ const ChatMessage = db.model('__Studio_ChatMessage');
19
+
20
+ const chatThread = await ChatThread.findById(chatThreadId);
21
+
22
+ if (!chatThread) {
23
+ throw new Error('Chat thread not found');
24
+ }
25
+ if (userId && chatThread.userId.toString() !== userId.toString()) {
26
+ throw new Error('Not authorized');
27
+ }
28
+
29
+ const chatMessages = await ChatMessage.find({ chatThreadId })
30
+ .sort({ createdAt: -1 });
31
+
32
+ return {
33
+ chatThread,
34
+ chatMessages: chatMessages.reverse() // Return in chronological order
35
+ };
36
+ };
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ exports.createChatMessage = require('./createChatMessage');
4
+ exports.createChatThread = require('./createChatThread');
5
+ exports.getChatThread = require('./getChatThread');
6
+ exports.listChatThreads = require('./listChatThreads');
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const mongoose = require('mongoose');
5
+
6
+ const ListChatThreadsParams = new Archetype({
7
+ userId: {
8
+ $type: mongoose.Types.ObjectId
9
+ }
10
+ }).compile('ListChatThreadsParams');
11
+
12
+ module.exports = ({ db }) => async function listChatThreads(params) {
13
+ // Just validate the params object, but no actual parameters needed
14
+ const { userId } = new ListChatThreadsParams(params);
15
+ const ChatThread = db.model('__Studio_ChatThread');
16
+
17
+ // Get all chat threads
18
+ const chatThreads = await ChatThread.find(userId ? { userId } : {})
19
+ .sort({ updatedAt: -1 }); // Sort by most recently updated
20
+
21
+ return {
22
+ chatThreads
23
+ };
24
+ };
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ exports.ChatMessage = require('./ChatMessage');
4
+ exports.ChatThread = require('./ChatThread');
3
5
  exports.Dashboard = require('./Dashboard');
4
6
  exports.Model = require('./Model');
5
7
  exports.Script = require('./Script');
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const mongoose = require('mongoose');
4
+
5
+ const chatMessageSchema = new mongoose.Schema({
6
+ chatThreadId: {
7
+ type: 'ObjectId',
8
+ required: true
9
+ },
10
+ role: {
11
+ type: String,
12
+ enum: ['user', 'assistant'],
13
+ required: true
14
+ },
15
+ content: {
16
+ type: String,
17
+ required: true
18
+ },
19
+ script: {
20
+ type: String,
21
+ required: false
22
+ },
23
+ executionResult: {
24
+ output: mongoose.Schema.Types.Mixed,
25
+ logs: String,
26
+ error: String
27
+ }
28
+ }, { timestamps: true });
29
+
30
+ module.exports = chatMessageSchema;
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const mongoose = require('mongoose');
4
+
5
+ const chatThreadSchema = new mongoose.Schema({
6
+ title: {
7
+ type: String
8
+ },
9
+ userId: {
10
+ type: mongoose.ObjectId,
11
+ ref: 'User'
12
+ }
13
+ }, { timestamps: true });
14
+
15
+ module.exports = chatThreadSchema;
package/backend/index.js CHANGED
@@ -4,12 +4,16 @@ const Actions = require('./actions');
4
4
  const { applySpec } = require('extrovert');
5
5
  const mongoose = require('mongoose');
6
6
 
7
+ const chatMessageSchema = require('./db/chatMessageSchema');
8
+ const chatThreadSchema = require('./db/chatThreadSchema');
7
9
  const dashboardSchema = require('./db/dashboardSchema');
8
10
 
9
11
  module.exports = function backend(db) {
10
12
  db = db || mongoose.connection;
11
-
13
+
12
14
  const Dashboard = db.model('__Studio_Dashboard', dashboardSchema, 'studio__dashboards');
15
+ const ChatMessage = db.model('__Studio_ChatMessage', chatMessageSchema, 'studio__chatMessages');
16
+ const ChatThread = db.model('__Studio_ChatThread', chatThreadSchema, 'studio__chatThreads');
13
17
  const actions = applySpec(Actions, { db });
14
18
  return actions;
15
- };
19
+ };
package/express.js CHANGED
@@ -63,6 +63,7 @@ module.exports = async function(apiUrl, conn, options) {
63
63
  if (!user || !roles) {
64
64
  throw new Error('Not authorized');
65
65
  }
66
+ req.params.initiatedById = user._id;
66
67
  req.params.roles = roles;
67
68
 
68
69
  next();