@mongoosejs/studio 0.5.13 → 0.6.0
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/AGENTS.md +74 -0
- package/README.md +27 -0
- package/backend/actions/ChatMessage/executeScript.js +15 -5
- package/backend/actions/ChatThread/createChatMessage.js +7 -2
- package/backend/actions/ChatThread/getChatThread.js +8 -2
- package/backend/actions/ChatThread/listChatThreads.js +6 -1
- package/backend/actions/ChatThread/shareChatThread.js +8 -2
- package/backend/actions/ChatThread/streamChatMessage.js +7 -2
- package/backend/actions/ChatThread/toggleAgentMode.js +8 -2
- package/backend/actions/Dashboard/createDashboard.js +3 -0
- package/backend/actions/Dashboard/deleteDashboard.js +13 -3
- package/backend/actions/Dashboard/getDashboard.js +33 -10
- package/backend/actions/Dashboard/getDashboards.js +9 -1
- package/backend/actions/Dashboard/updateDashboard.js +9 -2
- package/backend/actions/Model/addField.js +9 -2
- package/backend/actions/Model/analyzeSchema.js +16 -4
- package/backend/actions/Model/createChatMessage.js +3 -0
- package/backend/actions/Model/createDocument.js +3 -0
- package/backend/actions/Model/deleteDocument.js +9 -3
- package/backend/actions/Model/deleteDocuments.js +9 -1
- package/backend/actions/Model/dropCollection.js +3 -0
- package/backend/actions/Model/dropIndex.js +3 -0
- package/backend/actions/Model/executeDocumentScript.js +33 -26
- package/backend/actions/Model/exportQueryResults.js +13 -1
- package/backend/actions/Model/getCollectionInfo.js +15 -3
- package/backend/actions/Model/getDocument.js +13 -2
- package/backend/actions/Model/getDocuments.js +25 -5
- package/backend/actions/Model/getDocumentsStream.js +38 -18
- package/backend/actions/Model/getEstimatedDocumentCounts.js +15 -2
- package/backend/actions/Model/getIndexes.js +3 -0
- package/backend/actions/Model/getSuggestedProjection.js +3 -0
- package/backend/actions/Model/listModels.js +3 -0
- package/backend/actions/Model/streamChatMessage.js +3 -0
- package/backend/actions/Model/streamDocumentChanges.js +3 -0
- package/backend/actions/Model/updateDocument.js +10 -2
- package/backend/actions/Model/updateDocuments.js +10 -2
- package/backend/actions/Model/validateDocument.js +15 -2
- package/backend/actions/Script/createScript.js +36 -0
- package/backend/actions/Script/executeScript.js +89 -0
- package/backend/actions/Script/getScript.js +43 -0
- package/backend/actions/Script/index.js +5 -0
- package/backend/actions/Task/cancelTask.js +9 -3
- package/backend/actions/Task/getTaskOverview.js +7 -3
- package/backend/actions/Task/getTasks.js +7 -3
- package/backend/actions/Task/getTasksOverTime.js +7 -3
- package/backend/actions/Task/rescheduleTask.js +9 -3
- package/backend/actions/Task/runTask.js +9 -3
- package/backend/actions/index.js +1 -0
- package/backend/authorize.js +6 -1
- package/backend/db/scriptSchema.js +29 -0
- package/backend/helpers/omitNullish.js +5 -0
- package/backend/helpers/operationOptions.js +26 -0
- package/backend/helpers/readOperationOptions.js +31 -0
- package/backend/index.js +6 -4
- package/backend/integrations/mothership.js +28 -0
- package/backend/mcp.js +140 -0
- package/backend/netlify.js +5 -3
- package/backend/next.js +5 -3
- package/backend/sandbox/createSandbox.js +2 -2
- package/backend/sandbox/createScriptDb.js +60 -20
- package/backend/util/archetypeToZodSchema.js +74 -0
- package/docs/mcp-oauth.md +106 -0
- package/eslint.config.js +1 -0
- package/eval/agent/run.js +1 -1
- package/express.js +183 -41
- package/frontend/index.js +3 -0
- package/frontend/public/app.js +740 -124
- package/frontend/public/tw.css +76 -0
- package/frontend/src/api.js +27 -1
- package/frontend/src/index.js +9 -1
- package/frontend/src/mcp/mcp.html +55 -0
- package/frontend/src/mcp/mcp.js +27 -0
- package/frontend/src/models/models.html +29 -8
- package/frontend/src/models/models.js +30 -2
- package/frontend/src/navbar/navbar.html +8 -0
- package/frontend/src/navbar/navbar.js +3 -0
- package/frontend/src/routes.js +16 -0
- package/frontend/src/script/script.html +105 -0
- package/frontend/src/script/script.js +98 -0
- package/frontend/src/tooltip/tooltip.html +28 -0
- package/frontend/src/tooltip/tooltip.js +94 -0
- package/index.d.ts +10 -1
- package/local.js +2 -1
- package/nest.d.ts +4 -0
- package/package.json +5 -3
- package/vercel.d.ts +4 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Repository guidance
|
|
2
|
+
|
|
3
|
+
The project's name is Mongoose Studio. Do not refer to the project as "Studio" - the repository is named "studio" to maintain parity between the npm package name `@mongoosejs/studio` and the GitHub repository `mongoosejs/studio`.
|
|
4
|
+
|
|
5
|
+
The Mongoose connection passed to Mongoose Studio is owned by the host application.
|
|
6
|
+
Do not change connection-level settings, options, configuration, models, middleware, plugins, collection objects, or methods on the application connection to implement Studio behavior.
|
|
7
|
+
Apply Mongoose-Studio-specific behavior to individual operations whenever possible.
|
|
8
|
+
When isolation requires a derived connection, copy any Mongoose state that `useDb()` shares with its source before changing the derived connection.
|
|
9
|
+
Sandbox wrappers must remain confined to sandbox-owned models and collections.
|
|
10
|
+
|
|
11
|
+
# Frontend Guidelines
|
|
12
|
+
|
|
13
|
+
Conceptually, the frontend's job is to render the data and provide a user interface for accessing API calls.
|
|
14
|
+
The frontend should minimize transformations and renaming of data provided by the backend: default to displaying the raw data as-is.
|
|
15
|
+
|
|
16
|
+
## API Client
|
|
17
|
+
|
|
18
|
+
`src/client/api.js` exposes one wrapper function per backend action; every wrapper POSTs to `${baseUrl}/<endpoint>` and resolves with the response payload directly (`res.data`), not the axios response. The endpoint list lives in `src/endpoints.js`, which `build.js` generates from the files in `backend/actions/` — do not edit it by hand. Flat action files become flat wrappers like `api.addToCart`, while capitalized `Model/action.js` directories become namespaced wrappers like `api.quote.createQuote`, so adding an action file automatically makes its wrapper available on the next build.
|
|
19
|
+
|
|
20
|
+
## Vue User Input
|
|
21
|
+
|
|
22
|
+
Prefer explicit DOM event handlers such as `@change`, `@input`, and `@update:modelValue` over `watch` for reacting to user input. Use `watch` only when the behavior is truly derived from state changes that do not have a single clear DOM event source.
|
|
23
|
+
|
|
24
|
+
## Vue Templates
|
|
25
|
+
|
|
26
|
+
Prefer safe property access directly in HTML over computed properties or methods for simple display values. For example, use `{{ user?.name ?? '-' }}` instead of creating a `userName` computed property. Keep display behavior local unless the logic is reused or too complex to read inline.
|
|
27
|
+
|
|
28
|
+
Prefer using mustache interpolation (`{{ expr }}`) over `v-text` for rendering text values in Vue templates.
|
|
29
|
+
|
|
30
|
+
## Component API Ownership
|
|
31
|
+
|
|
32
|
+
Components that present an async action must own the API request for that action. Do not emit an event to a parent solely so the parent can make the HTTP request. Emit events after the component finishes the request when the parent needs to close a modal, refresh a list, or update surrounding state.
|
|
33
|
+
|
|
34
|
+
## Async Buttons
|
|
35
|
+
|
|
36
|
+
For a button that triggers an async action, use the `<async-button>` component instead of tracking a `saveStatus`/`loading` flag by hand. It manages the in-progress state, disables itself while running, and shows a spinner. Do not add a component-level status property, a `try/finally` to reset it, or `.finally()` on the API call just to toggle a button label.
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<!-- ❌ Incorrect: hand-rolled status + disabled + label swap -->
|
|
40
|
+
<button :disabled="saveStatus === 'saving'" @click="save">
|
|
41
|
+
<span v-if="saveStatus === 'saving'">Saving...</span>
|
|
42
|
+
<span v-else>Save</span>
|
|
43
|
+
</button>
|
|
44
|
+
|
|
45
|
+
<!-- ✅ Correct -->
|
|
46
|
+
<async-button @click="save">Save</async-button>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The click handler is just an `async` method that does the work; let errors propagate to the global handler. Keep any real `:disabled` condition (e.g. invalid input) — `async-button` ORs it with its own in-progress state.
|
|
50
|
+
|
|
51
|
+
## Avoid trivial helpers
|
|
52
|
+
|
|
53
|
+
Minimize scattered helpers on the frontend - functions and computed properties carry real cognitive overhead and need to earn their existence.
|
|
54
|
+
Optimize for locality and terseness over abstraction.
|
|
55
|
+
|
|
56
|
+
For example, instead of:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
computed: {
|
|
60
|
+
displayName() {
|
|
61
|
+
return `${user.firstName} ${user.lastName}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
and:
|
|
67
|
+
|
|
68
|
+
<div>{{ displayName }}</div>
|
|
69
|
+
|
|
70
|
+
Write:
|
|
71
|
+
|
|
72
|
+
<div>{{ user.firstName }} {{ user.lastName }}</div>
|
|
73
|
+
|
|
74
|
+
No single-expression helper functions or computed properties unless there's heavy repetition (25+ instances).
|
package/README.md
CHANGED
|
@@ -39,6 +39,10 @@ opts.model = 'gpt-4o-mini';
|
|
|
39
39
|
opts.openAIAPIKey = process.env.OPENAI_API_KEY;
|
|
40
40
|
opts.anthropicAPIKey = process.env.ANTHROPIC_API_KEY;
|
|
41
41
|
opts.googleGeminiAPIKey = process.env.GOOGLE_GEMINI_API_KEY;
|
|
42
|
+
// Apply a maximum execution time to all read operations, including reads in scripts.
|
|
43
|
+
// MongoDB does not support maxTimeMS for inserts and index operations.
|
|
44
|
+
// This is also a ceiling: a request may ask for a lower limit, never a higher one.
|
|
45
|
+
opts.maxTimeMS = 10000;
|
|
42
46
|
|
|
43
47
|
// Mount Mongoose Studio on '/studio'
|
|
44
48
|
app.use('/studio', await studio('/studio/api', mongoose, opts));
|
|
@@ -52,6 +56,29 @@ app.use('/studio', await studio('/studio/api', mongoose, {
|
|
|
52
56
|
}));
|
|
53
57
|
```
|
|
54
58
|
|
|
59
|
+
The Express integration also exposes a Streamable HTTP MCP endpoint at `/studio/mcp`.
|
|
60
|
+
Set `mcp: false` to turn it off, along with the OAuth protected resource metadata that advertises it.
|
|
61
|
+
It provides authorized Dashboard and Model actions, plus `Script.createScript`, as MCP tools.
|
|
62
|
+
With a Pro API key, pass the logged-in Studio access token in the `Authorization` header (either directly or as a bearer token); tools receive that user's roles and permissions.
|
|
63
|
+
Without workspace authentication, the MCP endpoint uses the same localhost and `bindIp` restrictions as the rest of Studio.
|
|
64
|
+
|
|
65
|
+
#### Connecting ChatGPT or Claude with OAuth
|
|
66
|
+
|
|
67
|
+
With a Pro API key, `/studio/mcp` is also an OAuth 2.1 protected resource, so MCP clients such as ChatGPT and Claude can connect to it directly.
|
|
68
|
+
Point the client at your `/studio/mcp` URL and it will discover the Mongoose Studio authorization server, walk the user through signing in and approving access, and receive a short-lived access token.
|
|
69
|
+
Users review and revoke these connections from their Mongoose Studio account page, and the access an AI client gets can never exceed the access the user who approved it already has.
|
|
70
|
+
|
|
71
|
+
Mongoose Studio advertises itself to MCP clients using its externally reachable URL.
|
|
72
|
+
When MCP and a Pro API key are enabled, set `publicUrl` to the URL where Mongoose Studio is mounted:
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
opts.publicUrl = 'https://app.example.com/studio';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Mongoose Studio finds the authorization server through the mothership it is already configured to use, so it needs no separate authorization server configuration.
|
|
79
|
+
|
|
80
|
+
See `docs/mcp-oauth.md` for how the flow and the delegated access policy work.
|
|
81
|
+
|
|
55
82
|
### Next.js
|
|
56
83
|
|
|
57
84
|
First, add `withMongooseStudio` to your `next.config.js` file:
|
|
@@ -4,6 +4,8 @@ const Archetype = require('archetype');
|
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const createSandbox = require('../../sandbox/createSandbox');
|
|
6
6
|
const mongoose = require('mongoose');
|
|
7
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
8
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
7
9
|
|
|
8
10
|
const ExecuteScriptParams = new Archetype({
|
|
9
11
|
initiatedById: {
|
|
@@ -21,27 +23,35 @@ const ExecuteScriptParams = new Archetype({
|
|
|
21
23
|
},
|
|
22
24
|
roles: {
|
|
23
25
|
$type: ['string']
|
|
26
|
+
},
|
|
27
|
+
maxTimeMS: {
|
|
28
|
+
$type: 'number'
|
|
29
|
+
},
|
|
30
|
+
readPreference: {
|
|
31
|
+
$type: 'string',
|
|
32
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
24
33
|
}
|
|
25
34
|
}).compile('ExecuteScriptParams');
|
|
26
35
|
|
|
27
|
-
module.exports = ({ db, studioConnection }) => async function executeScript(params) {
|
|
36
|
+
module.exports = ({ db, studioConnection, options }) => async function executeScript(params) {
|
|
28
37
|
const { initiatedById, chatMessageId, script, dryRun, roles } = new ExecuteScriptParams(params);
|
|
29
38
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
30
39
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage');
|
|
31
40
|
|
|
32
41
|
await authorize('ChatMessage.executeScript', roles);
|
|
33
42
|
|
|
34
|
-
const
|
|
43
|
+
const dbOptions = operationOptions(options, params);
|
|
44
|
+
const chatMessage = await ChatMessage.findById(chatMessageId).setOptions(dbOptions);
|
|
35
45
|
if (!chatMessage) {
|
|
36
46
|
throw new Error('Chat message not found');
|
|
37
47
|
}
|
|
38
|
-
const chatThread = await ChatThread.findById(chatMessage.chatThreadId).orFail();
|
|
48
|
+
const chatThread = await ChatThread.findById(chatMessage.chatThreadId).setOptions(dbOptions).orFail();
|
|
39
49
|
|
|
40
50
|
if (initiatedById && chatThread.userId?.toString() !== initiatedById.toString()) {
|
|
41
51
|
throw new Error('Unauthorized');
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
const sandbox = createSandbox({
|
|
54
|
+
const sandbox = createSandbox(db, { ...readOperationOptions(options, params) });
|
|
45
55
|
|
|
46
56
|
let output;
|
|
47
57
|
let error;
|
|
@@ -74,7 +84,7 @@ module.exports = ({ db, studioConnection }) => async function executeScript(para
|
|
|
74
84
|
dryRun: !!dryRun
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
|
-
);
|
|
87
|
+
).setOptions(operationOptions(options, params));
|
|
78
88
|
|
|
79
89
|
throw new Error(`Script execution failed: ${error}`);
|
|
80
90
|
} finally {
|
|
@@ -8,6 +8,7 @@ const callLLM = require('../../integrations/callLLM');
|
|
|
8
8
|
const getAgentTools = require('../../chatAgent/getAgentTools');
|
|
9
9
|
const getModelDescriptions = require('../../helpers/getModelDescriptions');
|
|
10
10
|
const mongoose = require('mongoose');
|
|
11
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
11
12
|
|
|
12
13
|
const CreateChatMessageParams = new Archetype({
|
|
13
14
|
chatThreadId: {
|
|
@@ -25,6 +26,9 @@ const CreateChatMessageParams = new Archetype({
|
|
|
25
26
|
},
|
|
26
27
|
roles: {
|
|
27
28
|
$type: ['string']
|
|
29
|
+
},
|
|
30
|
+
maxTimeMS: {
|
|
31
|
+
$type: 'number'
|
|
28
32
|
}
|
|
29
33
|
}).compile('CreateChatMessageParams');
|
|
30
34
|
|
|
@@ -36,7 +40,8 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
36
40
|
await authorize('ChatThread.createChatMessage', roles);
|
|
37
41
|
|
|
38
42
|
// Check that the user owns the thread
|
|
39
|
-
const
|
|
43
|
+
const dbOptions = operationOptions(options, params);
|
|
44
|
+
const chatThread = await ChatThread.findOne({ _id: chatThreadId }).setOptions(dbOptions);
|
|
40
45
|
if (!chatThread) {
|
|
41
46
|
throw new Error('Chat thread not found');
|
|
42
47
|
}
|
|
@@ -44,7 +49,7 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
44
49
|
throw new Error('Not authorized');
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
const messages = await ChatMessage.find({ chatThreadId }).sort({ createdAt: 1 });
|
|
52
|
+
const messages = await ChatMessage.find({ chatThreadId }).setOptions(dbOptions).sort({ createdAt: 1 });
|
|
48
53
|
const llmMessages = messages.map(m => ({
|
|
49
54
|
role: m.role,
|
|
50
55
|
content: [{
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
6
7
|
|
|
7
8
|
const GetChatThreadParams = new Archetype({
|
|
8
9
|
chatThreadId: {
|
|
@@ -16,17 +17,21 @@ const GetChatThreadParams = new Archetype({
|
|
|
16
17
|
},
|
|
17
18
|
$workspaceId: {
|
|
18
19
|
$type: mongoose.Types.ObjectId
|
|
20
|
+
},
|
|
21
|
+
maxTimeMS: {
|
|
22
|
+
$type: 'number'
|
|
19
23
|
}
|
|
20
24
|
}).compile('GetChatThreadParams');
|
|
21
25
|
|
|
22
|
-
module.exports = ({ db, studioConnection }) => async function getChatThread(params) {
|
|
26
|
+
module.exports = ({ db, studioConnection, options }) => async function getChatThread(params) {
|
|
23
27
|
const { chatThreadId, initiatedById, roles, $workspaceId } = new GetChatThreadParams(params);
|
|
24
28
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
25
29
|
const ChatMessage = studioConnection.model('__Studio_ChatMessage');
|
|
26
30
|
|
|
27
31
|
await authorize('ChatThread.getChatThread', roles);
|
|
28
32
|
|
|
29
|
-
const
|
|
33
|
+
const dbOptions = operationOptions(options, params);
|
|
34
|
+
const chatThread = await ChatThread.findById(chatThreadId).setOptions(dbOptions);
|
|
30
35
|
|
|
31
36
|
if (!chatThread) {
|
|
32
37
|
throw new Error('Chat thread not found');
|
|
@@ -39,6 +44,7 @@ module.exports = ({ db, studioConnection }) => async function getChatThread(para
|
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
const chatMessages = await ChatMessage.find({ chatThreadId })
|
|
47
|
+
.setOptions(dbOptions)
|
|
42
48
|
.sort({ createdAt: -1 });
|
|
43
49
|
|
|
44
50
|
return {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
6
7
|
|
|
7
8
|
const ListChatThreadsParams = new Archetype({
|
|
8
9
|
initiatedById: {
|
|
@@ -10,10 +11,13 @@ const ListChatThreadsParams = new Archetype({
|
|
|
10
11
|
},
|
|
11
12
|
roles: {
|
|
12
13
|
$type: ['string']
|
|
14
|
+
},
|
|
15
|
+
maxTimeMS: {
|
|
16
|
+
$type: 'number'
|
|
13
17
|
}
|
|
14
18
|
}).compile('ListChatThreadsParams');
|
|
15
19
|
|
|
16
|
-
module.exports = ({ db, studioConnection }) => async function listChatThreads(params) {
|
|
20
|
+
module.exports = ({ db, studioConnection, options }) => async function listChatThreads(params) {
|
|
17
21
|
// Validate the params object
|
|
18
22
|
const { initiatedById, roles } = new ListChatThreadsParams(params);
|
|
19
23
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
@@ -24,6 +28,7 @@ module.exports = ({ db, studioConnection }) => async function listChatThreads(pa
|
|
|
24
28
|
|
|
25
29
|
// Get all chat threads
|
|
26
30
|
const chatThreads = await ChatThread.find(query)
|
|
31
|
+
.setOptions(operationOptions(options, params))
|
|
27
32
|
.sort({ updatedAt: -1 }); // Sort by most recently updated
|
|
28
33
|
|
|
29
34
|
return {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
6
7
|
|
|
7
8
|
const ShareChatThreadParams = new Archetype({
|
|
8
9
|
chatThreadId: {
|
|
@@ -19,16 +20,21 @@ const ShareChatThreadParams = new Archetype({
|
|
|
19
20
|
},
|
|
20
21
|
$workspaceId: {
|
|
21
22
|
$type: mongoose.Types.ObjectId
|
|
23
|
+
},
|
|
24
|
+
maxTimeMS: {
|
|
25
|
+
$type: 'number'
|
|
22
26
|
}
|
|
23
27
|
}).compile('ShareChatThreadParams');
|
|
24
28
|
|
|
25
|
-
module.exports = ({ studioConnection }) => async function shareChatThread(params) {
|
|
29
|
+
module.exports = ({ studioConnection, options }) => async function shareChatThread(params) {
|
|
26
30
|
const { chatThreadId, share, initiatedById, roles, $workspaceId } = new ShareChatThreadParams(params);
|
|
27
31
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
28
32
|
|
|
29
33
|
await authorize('ChatThread.shareChatThread', roles);
|
|
30
34
|
|
|
31
|
-
const chatThread = await ChatThread.findById(chatThreadId).
|
|
35
|
+
const chatThread = await ChatThread.findById(chatThreadId).
|
|
36
|
+
setOptions(operationOptions(options, params)).
|
|
37
|
+
orFail();
|
|
32
38
|
if (initiatedById != null && chatThread.userId?.toString() !== initiatedById.toString()) {
|
|
33
39
|
throw new Error('Not authorized');
|
|
34
40
|
}
|
|
@@ -8,6 +8,7 @@ const runChatAgent = require('../../chatAgent/runChatAgent');
|
|
|
8
8
|
const streamLLM = require('../../integrations/streamLLM');
|
|
9
9
|
const getModelDescriptions = require('../../helpers/getModelDescriptions');
|
|
10
10
|
const mongoose = require('mongoose');
|
|
11
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
11
12
|
|
|
12
13
|
const CreateChatMessageParams = new Archetype({
|
|
13
14
|
chatThreadId: {
|
|
@@ -25,6 +26,9 @@ const CreateChatMessageParams = new Archetype({
|
|
|
25
26
|
},
|
|
26
27
|
roles: {
|
|
27
28
|
$type: ['string']
|
|
29
|
+
},
|
|
30
|
+
maxTimeMS: {
|
|
31
|
+
$type: 'number'
|
|
28
32
|
}
|
|
29
33
|
}).compile('CreateChatMessageParams');
|
|
30
34
|
|
|
@@ -36,7 +40,8 @@ module.exports = ({ db, studioConnection, options }) => async function* streamCh
|
|
|
36
40
|
await authorize('ChatThread.createChatMessage', roles);
|
|
37
41
|
|
|
38
42
|
// Check that the user owns the thread
|
|
39
|
-
const
|
|
43
|
+
const dbOptions = operationOptions(options, params);
|
|
44
|
+
const chatThread = await ChatThread.findOne({ _id: chatThreadId }).setOptions(dbOptions);
|
|
40
45
|
if (!chatThread) {
|
|
41
46
|
throw new Error('Chat thread not found');
|
|
42
47
|
}
|
|
@@ -44,7 +49,7 @@ module.exports = ({ db, studioConnection, options }) => async function* streamCh
|
|
|
44
49
|
throw new Error('Not authorized');
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
const messages = await ChatMessage.find({ chatThreadId }).sort({ createdAt: 1 });
|
|
52
|
+
const messages = await ChatMessage.find({ chatThreadId }).setOptions(dbOptions).sort({ createdAt: 1 });
|
|
48
53
|
const llmMessages = messages.map(m => ({
|
|
49
54
|
role: m.role,
|
|
50
55
|
content: [{
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
6
7
|
|
|
7
8
|
const ToggleAgentModeParams = new Archetype({
|
|
8
9
|
chatThreadId: {
|
|
@@ -16,16 +17,21 @@ const ToggleAgentModeParams = new Archetype({
|
|
|
16
17
|
},
|
|
17
18
|
roles: {
|
|
18
19
|
$type: ['string']
|
|
20
|
+
},
|
|
21
|
+
maxTimeMS: {
|
|
22
|
+
$type: 'number'
|
|
19
23
|
}
|
|
20
24
|
}).compile('ToggleAgentModeParams');
|
|
21
25
|
|
|
22
|
-
module.exports = ({ studioConnection }) => async function toggleAgentMode(params) {
|
|
26
|
+
module.exports = ({ studioConnection, options }) => async function toggleAgentMode(params) {
|
|
23
27
|
const { chatThreadId, agentMode, initiatedById, roles } = new ToggleAgentModeParams(params);
|
|
24
28
|
const ChatThread = studioConnection.model('__Studio_ChatThread');
|
|
25
29
|
|
|
26
30
|
await authorize('ChatThread.toggleAgentMode', roles);
|
|
27
31
|
|
|
28
|
-
const chatThread = await ChatThread.findById(chatThreadId).
|
|
32
|
+
const chatThread = await ChatThread.findById(chatThreadId).
|
|
33
|
+
setOptions(operationOptions(options, params)).
|
|
34
|
+
orFail();
|
|
29
35
|
if (initiatedById != null && chatThread.userId?.toString() !== initiatedById.toString()) {
|
|
30
36
|
throw new Error('Not authorized');
|
|
31
37
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
5
6
|
|
|
6
7
|
const DeleteDashboardParams = new Archetype({
|
|
7
8
|
dashboardId: {
|
|
@@ -10,17 +11,26 @@ const DeleteDashboardParams = new Archetype({
|
|
|
10
11
|
},
|
|
11
12
|
roles: {
|
|
12
13
|
$type: ['string']
|
|
14
|
+
},
|
|
15
|
+
maxTimeMS: {
|
|
16
|
+
$type: 'number'
|
|
13
17
|
}
|
|
14
18
|
}).compile('DeleteDashboardParams');
|
|
15
19
|
|
|
16
|
-
module.exports = ({ studioConnection }) => async function deleteDashboard(params) {
|
|
20
|
+
module.exports = ({ studioConnection, options }) => async function deleteDashboard(params) {
|
|
17
21
|
const { dashboardId, roles } = new DeleteDashboardParams(params);
|
|
18
22
|
const Dashboard = studioConnection.model('__Studio_Dashboard');
|
|
19
23
|
const DashboardResult = studioConnection.model('__Studio_DashboardResult');
|
|
20
24
|
|
|
21
25
|
await authorize('Dashboard.deleteDashboard', roles);
|
|
22
26
|
|
|
23
|
-
const result = await Dashboard.deleteOne({ _id: dashboardId }).
|
|
24
|
-
|
|
27
|
+
const result = await Dashboard.deleteOne({ _id: dashboardId }).
|
|
28
|
+
setOptions(operationOptions(options, params)).
|
|
29
|
+
orFail();
|
|
30
|
+
await DashboardResult.deleteMany({ dashboardId }).
|
|
31
|
+
setOptions(operationOptions(options, params));
|
|
25
32
|
return { result };
|
|
26
33
|
};
|
|
34
|
+
|
|
35
|
+
module.exports.paramsType = DeleteDashboardParams;
|
|
36
|
+
module.exports.tags = ['destructive'];
|
|
@@ -4,6 +4,8 @@ const Archetype = require('archetype');
|
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const createSandbox = require('../../sandbox/createSandbox');
|
|
6
6
|
const mongoose = require('mongoose');
|
|
7
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
8
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
7
9
|
const { defaultMothershipURL } = require('../../../constants');
|
|
8
10
|
|
|
9
11
|
const GetDashboardParams = new Archetype({
|
|
@@ -25,6 +27,13 @@ const GetDashboardParams = new Archetype({
|
|
|
25
27
|
},
|
|
26
28
|
roles: {
|
|
27
29
|
$type: ['string']
|
|
30
|
+
},
|
|
31
|
+
maxTimeMS: {
|
|
32
|
+
$type: 'number'
|
|
33
|
+
},
|
|
34
|
+
readPreference: {
|
|
35
|
+
$type: 'string',
|
|
36
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
28
37
|
}
|
|
29
38
|
}).compile('GetDashboardParams');
|
|
30
39
|
|
|
@@ -36,10 +45,12 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
36
45
|
|
|
37
46
|
await authorize('Dashboard.getDashboard', roles);
|
|
38
47
|
|
|
39
|
-
const
|
|
48
|
+
const dashboardOptions = operationOptions(options, params);
|
|
49
|
+
const dashboard = await Dashboard.findOne({ _id: dashboardId }).
|
|
50
|
+
setOptions(dashboardOptions);
|
|
40
51
|
if (evaluate) {
|
|
41
52
|
let result = null;
|
|
42
|
-
const sandbox = createSandbox({
|
|
53
|
+
const sandbox = createSandbox(db, { ...readOperationOptions(options, params) });
|
|
43
54
|
const startExec = startDashboardEvaluate(DashboardResult, dashboardId, $workspaceId, userId);
|
|
44
55
|
startExec.catch(() => {}); // Avoid unhandled promise rejections - we will handle this error later.
|
|
45
56
|
try {
|
|
@@ -56,7 +67,8 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
56
67
|
dashboardResult._id,
|
|
57
68
|
null,
|
|
58
69
|
{ message: error.message },
|
|
59
|
-
'failed'
|
|
70
|
+
'failed',
|
|
71
|
+
dashboardOptions
|
|
60
72
|
);
|
|
61
73
|
});
|
|
62
74
|
return { dashboard, dashboardResult, error: { message: error.message } };
|
|
@@ -79,7 +91,8 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
79
91
|
dashboardResult._id,
|
|
80
92
|
result,
|
|
81
93
|
undefined,
|
|
82
|
-
'completed'
|
|
94
|
+
'completed',
|
|
95
|
+
dashboardOptions
|
|
83
96
|
);
|
|
84
97
|
});
|
|
85
98
|
|
|
@@ -93,21 +106,25 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
93
106
|
dashboardId,
|
|
94
107
|
$workspaceId,
|
|
95
108
|
authorization,
|
|
96
|
-
mothershipUrl
|
|
109
|
+
mothershipUrl,
|
|
110
|
+
dashboardOptions
|
|
97
111
|
);
|
|
98
112
|
return { dashboard, dashboardResults };
|
|
99
113
|
}
|
|
100
114
|
};
|
|
101
115
|
|
|
102
|
-
async function completeDashboardEvaluate(Dashboard, DashboardResult, dashboardResultId, result, error, status) {
|
|
103
|
-
const dashboardResult = await DashboardResult.findById(dashboardResultId).
|
|
116
|
+
async function completeDashboardEvaluate(Dashboard, DashboardResult, dashboardResultId, result, error, status, dashboardOptions) {
|
|
117
|
+
const dashboardResult = await DashboardResult.findById(dashboardResultId).
|
|
118
|
+
setOptions(dashboardOptions).
|
|
119
|
+
orFail();
|
|
104
120
|
const finishedEvaluatingAt = new Date();
|
|
105
121
|
dashboardResult.finishedEvaluatingAt = finishedEvaluatingAt;
|
|
106
122
|
dashboardResult.result = result;
|
|
107
123
|
dashboardResult.error = error;
|
|
108
124
|
dashboardResult.status = status;
|
|
109
125
|
await dashboardResult.save();
|
|
110
|
-
await Dashboard.updateOne({ _id: dashboardResult.dashboardId }, { $set: { lastEvaluatedAt: finishedEvaluatingAt } })
|
|
126
|
+
await Dashboard.updateOne({ _id: dashboardResult.dashboardId }, { $set: { lastEvaluatedAt: finishedEvaluatingAt } }).
|
|
127
|
+
setOptions(dashboardOptions);
|
|
111
128
|
return { dashboardResult };
|
|
112
129
|
}
|
|
113
130
|
|
|
@@ -123,14 +140,17 @@ async function startDashboardEvaluate(DashboardResult, dashboardId, workspaceId,
|
|
|
123
140
|
return { dashboardResult };
|
|
124
141
|
}
|
|
125
142
|
|
|
126
|
-
async function getDashboardResults(DashboardResult, dashboardId, workspaceId, authorization, mothershipUrl) {
|
|
143
|
+
async function getDashboardResults(DashboardResult, dashboardId, workspaceId, authorization, mothershipUrl, dashboardOptions) {
|
|
127
144
|
const filter = { dashboardId };
|
|
128
145
|
if (workspaceId != null) {
|
|
129
146
|
filter.workspaceId = workspaceId;
|
|
130
147
|
}
|
|
131
148
|
|
|
132
149
|
const [localResultsRes, remoteResultsRes] = await Promise.allSettled([
|
|
133
|
-
DashboardResult.find(filter).
|
|
150
|
+
DashboardResult.find(filter).
|
|
151
|
+
sort({ _id: -1 }).
|
|
152
|
+
limit(10).
|
|
153
|
+
setOptions(dashboardOptions),
|
|
134
154
|
getMothershipDashboardResults(dashboardId, workspaceId, authorization, mothershipUrl)
|
|
135
155
|
]);
|
|
136
156
|
|
|
@@ -201,3 +221,6 @@ function addDocumentSchemaPaths(result) {
|
|
|
201
221
|
}
|
|
202
222
|
result.$document.schemaPaths = schemaPaths;
|
|
203
223
|
}
|
|
224
|
+
|
|
225
|
+
module.exports.paramsType = GetDashboardParams;
|
|
226
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
5
6
|
|
|
6
7
|
const GetDashboardParams = new Archetype({
|
|
7
8
|
roles: {
|
|
8
9
|
$type: ['string']
|
|
10
|
+
},
|
|
11
|
+
maxTimeMS: {
|
|
12
|
+
$type: 'number'
|
|
9
13
|
}
|
|
10
14
|
}).compile('GetDashboardParams');
|
|
11
15
|
|
|
12
|
-
module.exports = ({ studioConnection }) => async function getDashboards(params) {
|
|
16
|
+
module.exports = ({ studioConnection, options }) => async function getDashboards(params) {
|
|
13
17
|
const Dashboard = studioConnection.model('__Studio_Dashboard');
|
|
14
18
|
const { roles } = new GetDashboardParams(params);
|
|
15
19
|
|
|
@@ -17,7 +21,11 @@ module.exports = ({ studioConnection }) => async function getDashboards(params)
|
|
|
17
21
|
|
|
18
22
|
const dashboards = await Dashboard
|
|
19
23
|
.find()
|
|
24
|
+
.setOptions(operationOptions(options, params))
|
|
20
25
|
.sort({ isPinned: -1, createdAt: -1, _id: -1 });
|
|
21
26
|
|
|
22
27
|
return { dashboards };
|
|
23
28
|
};
|
|
29
|
+
|
|
30
|
+
module.exports.paramsType = GetDashboardParams;
|
|
31
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
5
6
|
|
|
6
7
|
const UpdateDashboardParams = new Archetype({
|
|
7
8
|
dashboardId: {
|
|
@@ -22,10 +23,13 @@ const UpdateDashboardParams = new Archetype({
|
|
|
22
23
|
},
|
|
23
24
|
roles: {
|
|
24
25
|
$type: ['string']
|
|
26
|
+
},
|
|
27
|
+
maxTimeMS: {
|
|
28
|
+
$type: 'number'
|
|
25
29
|
}
|
|
26
30
|
}).compile('UpdateDashboardParams');
|
|
27
31
|
|
|
28
|
-
module.exports = ({ studioConnection }) => async function updateDashboard(params) {
|
|
32
|
+
module.exports = ({ studioConnection, options }) => async function updateDashboard(params) {
|
|
29
33
|
const { dashboardId, code, title, description, isPinned, roles, evaluate } = new UpdateDashboardParams(params);
|
|
30
34
|
|
|
31
35
|
const Dashboard = studioConnection.models['__Studio_Dashboard'];
|
|
@@ -54,7 +58,10 @@ module.exports = ({ studioConnection }) => async function updateDashboard(params
|
|
|
54
58
|
dashboardId,
|
|
55
59
|
updateObj,
|
|
56
60
|
{ sanitizeFilter: true, returnDocument: 'after' }
|
|
57
|
-
);
|
|
61
|
+
).setOptions(operationOptions(options, params));
|
|
58
62
|
|
|
59
63
|
return { doc };
|
|
60
64
|
};
|
|
65
|
+
|
|
66
|
+
module.exports.paramsType = UpdateDashboardParams;
|
|
67
|
+
module.exports.tags = [];
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
5
6
|
|
|
6
7
|
const AddFieldParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -22,10 +23,13 @@ const AddFieldParams = new Archetype({
|
|
|
22
23
|
},
|
|
23
24
|
roles: {
|
|
24
25
|
$type: ['string']
|
|
26
|
+
},
|
|
27
|
+
maxTimeMS: {
|
|
28
|
+
$type: 'number'
|
|
25
29
|
}
|
|
26
30
|
}).compile('AddFieldParams');
|
|
27
31
|
|
|
28
|
-
module.exports = ({ db }) => async function addField(params) {
|
|
32
|
+
module.exports = ({ db, options }) => async function addField(params) {
|
|
29
33
|
const { model, _id, fieldName, fieldValue, roles } = new AddFieldParams(params);
|
|
30
34
|
|
|
31
35
|
await authorize('Model.updateDocument', roles);
|
|
@@ -48,7 +52,10 @@ module.exports = ({ db }) => async function addField(params) {
|
|
|
48
52
|
runValidators: false,
|
|
49
53
|
strict: false
|
|
50
54
|
}
|
|
51
|
-
);
|
|
55
|
+
).setOptions(operationOptions(options, params));
|
|
52
56
|
|
|
53
57
|
return { doc };
|
|
54
58
|
};
|
|
59
|
+
|
|
60
|
+
module.exports.paramsType = AddFieldParams;
|
|
61
|
+
module.exports.tags = [];
|