@mongoosejs/studio 0.3.11 → 0.4.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.
@@ -3,7 +3,9 @@
3
3
  const Archetype = require('archetype');
4
4
  const assert = require('assert');
5
5
  const authorize = require('../../authorize');
6
+ const agentSystemPrompt = require('../../chatAgent/agentSystemPrompt');
6
7
  const callLLM = require('../../integrations/callLLM');
8
+ const getAgentTools = require('../../chatAgent/getAgentTools');
7
9
  const getModelDescriptions = require('../../helpers/getModelDescriptions');
8
10
  const mongoose = require('mongoose');
9
11
 
@@ -81,12 +83,14 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
81
83
 
82
84
  const modelDescriptions = getModelDescriptions(db);
83
85
  const system = [
84
- systemPrompt,
86
+ chatThread.agentMode ? agentSystemPrompt : systemPrompt,
85
87
  currentDateTime ? `Current date: ${currentDateTime}` : null,
86
88
  modelDescriptions,
87
89
  options?.context
88
90
  ].filter(Boolean).join('\n\n');
89
91
 
92
+ const llmOptions = chatThread.agentMode ? { ...options, tools: getAgentTools(db) } : options;
93
+
90
94
  // Create the chat message and get LLM response in parallel
91
95
  const chatMessages = await Promise.all([
92
96
  ChatMessage.create({
@@ -96,12 +100,20 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
96
100
  script,
97
101
  executionResult: null
98
102
  }),
99
- callLLM(llmMessages, system, options).then(res => {
103
+ callLLM(llmMessages, system, llmOptions).then(res => {
100
104
  const content = res.text;
105
+ const toolCalls = (res.steps || []).flatMap(step =>
106
+ (step.toolCalls || []).map(tc => ({
107
+ toolName: tc.toolName,
108
+ input: tc.toolName === 'typeCheck' ? {} : tc.args,
109
+ status: 'done'
110
+ }))
111
+ );
101
112
  return ChatMessage.create({
102
113
  chatThreadId,
103
114
  role: 'assistant',
104
- content
115
+ content,
116
+ toolCalls: toolCalls.length ? toolCalls : undefined
105
117
  });
106
118
  })
107
119
  ]);
@@ -22,11 +22,15 @@ const CreateChatThreadParams = new Archetype({
22
22
  dashboardId: {
23
23
  $type: mongoose.Types.ObjectId,
24
24
  $required: false
25
+ },
26
+ agentMode: {
27
+ $type: 'boolean',
28
+ $required: false
25
29
  }
26
30
  }).compile('CreateChatThreadParams');
27
31
 
28
32
  module.exports = ({ studioConnection }) => async function createChatThread(params) {
29
- const { initiatedById, roles, $workspaceId, initialMessage, dashboardId } = new CreateChatThreadParams(params);
33
+ const { initiatedById, roles, $workspaceId, initialMessage, dashboardId, agentMode } = new CreateChatThreadParams(params);
30
34
  const ChatThread = studioConnection.model('__Studio_ChatThread');
31
35
  const ChatMessage = studioConnection.model('__Studio_ChatMessage');
32
36
 
@@ -42,6 +46,9 @@ module.exports = ({ studioConnection }) => async function createChatThread(param
42
46
  if (dashboardId) {
43
47
  doc.dashboardId = dashboardId;
44
48
  }
49
+ if (agentMode != null) {
50
+ doc.agentMode = agentMode;
51
+ }
45
52
  const chatThread = await ChatThread.create(doc);
46
53
 
47
54
  if (initialMessage != null && initialMessage.trim() !== '') {
@@ -6,3 +6,4 @@ exports.getChatThread = require('./getChatThread');
6
6
  exports.listChatThreads = require('./listChatThreads');
7
7
  exports.shareChatThread = require('./shareChatThread');
8
8
  exports.streamChatMessage = require('./streamChatMessage');
9
+ exports.toggleAgentMode = require('./toggleAgentMode');
@@ -4,6 +4,8 @@ const Archetype = require('archetype');
4
4
  const assert = require('assert');
5
5
  const authorize = require('../../authorize');
6
6
  const callLLM = require('../../integrations/callLLM');
7
+ const agentSystemPrompt = require('../../chatAgent/agentSystemPrompt');
8
+ const getAgentTools = require('../../chatAgent/getAgentTools');
7
9
  const streamLLM = require('../../integrations/streamLLM');
8
10
  const getModelDescriptions = require('../../helpers/getModelDescriptions');
9
11
  const mongoose = require('mongoose');
@@ -27,7 +29,7 @@ const CreateChatMessageParams = new Archetype({
27
29
  }
28
30
  }).compile('CreateChatMessageParams');
29
31
 
30
- module.exports = ({ db, studioConnection, options }) => async function* createChatMessage(params) {
32
+ module.exports = ({ db, studioConnection, options }) => async function* streamChatMessage(params) {
31
33
  const { chatThreadId, initiatedById, content, currentDateTime, script, roles } = new CreateChatMessageParams(params);
32
34
  const ChatThread = studioConnection.model('__Studio_ChatThread');
33
35
  const ChatMessage = studioConnection.model('__Studio_ChatMessage');
@@ -73,16 +75,20 @@ module.exports = ({ db, studioConnection, options }) => async function* createCh
73
75
  }],
74
76
  'You are a helpful assistant that summarizes chat threads into titles.',
75
77
  options
76
- ).then(res => {
77
- const title = res.text;
78
- chatThread.title = title;
79
- return chatThread.save();
80
- });
78
+ ).then(
79
+ res => {
80
+ const title = res.text;
81
+ chatThread.title = title;
82
+ return chatThread.save();
83
+ },
84
+ // Avoid unhandled promise rejection
85
+ () => {}
86
+ );
81
87
  }
82
88
 
83
89
  const modelDescriptions = getModelDescriptions(db);
84
90
  const system = [
85
- systemPrompt,
91
+ chatThread.agentMode ? agentSystemPrompt : systemPrompt,
86
92
  currentDateTime ? `Current date: ${currentDateTime}` : null,
87
93
  modelDescriptions,
88
94
  options?.context
@@ -105,16 +111,43 @@ module.exports = ({ db, studioConnection, options }) => async function* createCh
105
111
  script: null,
106
112
  executionResult: null
107
113
  });
108
- const textStream = streamLLM(llmMessages, system, options);
114
+ const llmOptions = chatThread.agentMode ? { ...options, tools: getAgentTools(db) } : options;
115
+ let textStream;
116
+ try {
117
+ textStream = streamLLM(llmMessages, system, llmOptions);
118
+ } catch (err) {
119
+ yield { message: err?.message || 'Failed to stream response' };
120
+ return {};
121
+ }
122
+ const toolCalls = [];
109
123
  let count = 0;
110
- for await (const textPart of textStream) {
111
- assistantChatMessage.content += textPart;
112
- // Only save every 10th chunk for performance
113
- if (count++ % 10 === 0) {
114
- await assistantChatMessage.save();
124
+ try {
125
+ for await (const event of textStream) {
126
+ if (typeof event === 'string') {
127
+ assistantChatMessage.content += event;
128
+ // Only save every 10th chunk for performance
129
+ if (count++ % 10 === 0) {
130
+ await assistantChatMessage.save();
131
+ }
132
+ yield { textPart: event };
133
+ } else if (event.toolCall) {
134
+ toolCalls.push({ toolName: event.toolCall.toolName, input: event.toolCall.input, status: 'running' });
135
+ yield { toolCall: event.toolCall };
136
+ } else if (event.toolResult) {
137
+ const tc = toolCalls.find(t => t.toolName === event.toolResult.toolName && t.status === 'running');
138
+ if (tc) {
139
+ tc.status = 'done';
140
+ }
141
+ yield { toolResult: event.toolResult };
142
+ } else {
143
+ yield { textPart: event };
144
+ }
115
145
  }
116
- yield { textPart };
146
+ } catch (err) {
147
+ yield { message: err?.message || 'Failed to stream chat response' };
148
+ return {};
117
149
  }
150
+ assistantChatMessage.toolCalls = toolCalls;
118
151
 
119
152
  await assistantChatMessage.save();
120
153
  yield { chatMessage: assistantChatMessage };
@@ -139,6 +172,8 @@ const systemPrompt = `
139
172
 
140
173
  Assume the user has pre-defined schemas and models. Do not define any new schemas or models for the user.
141
174
 
175
+ Avoid using the aggregation framework unless explicitly asked by the user.
176
+
142
177
  Use async/await where possible. Assume top-level await is allowed.
143
178
 
144
179
  Write at most one script, unless the user explicitly asks for multiple scripts.
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const authorize = require('../../authorize');
5
+ const mongoose = require('mongoose');
6
+
7
+ const ToggleAgentModeParams = new Archetype({
8
+ chatThreadId: {
9
+ $type: mongoose.Types.ObjectId
10
+ },
11
+ agentMode: {
12
+ $type: 'boolean'
13
+ },
14
+ initiatedById: {
15
+ $type: mongoose.Types.ObjectId
16
+ },
17
+ roles: {
18
+ $type: ['string']
19
+ }
20
+ }).compile('ToggleAgentModeParams');
21
+
22
+ module.exports = ({ studioConnection }) => async function toggleAgentMode(params) {
23
+ const { chatThreadId, agentMode, initiatedById, roles } = new ToggleAgentModeParams(params);
24
+ const ChatThread = studioConnection.model('__Studio_ChatThread');
25
+
26
+ await authorize('ChatThread.toggleAgentMode', roles);
27
+
28
+ const chatThread = await ChatThread.findById(chatThreadId).orFail();
29
+ if (initiatedById != null && chatThread.userId?.toString() !== initiatedById.toString()) {
30
+ throw new Error('Not authorized');
31
+ }
32
+
33
+ chatThread.agentMode = agentMode;
34
+ await chatThread.save();
35
+
36
+ return { chatThread };
37
+ };
@@ -3,6 +3,7 @@
3
3
  const Archetype = require('archetype');
4
4
  const assert = require('assert');
5
5
  const authorize = require('../../authorize');
6
+ const getAgentTools = require('../../chatAgent/getAgentTools');
6
7
  const streamLLM = require('../../integrations/streamLLM');
7
8
  const getModelDescriptions = require('../../helpers/getModelDescriptions');
8
9
 
@@ -18,6 +19,10 @@ const StreamChatMessageParams = new Archetype({
18
19
  documentData: {
19
20
  $type: 'string'
20
21
  },
22
+ agentMode: {
23
+ $type: 'boolean',
24
+ $default: true
25
+ },
21
26
  currentDateTime: {
22
27
  $type: 'string',
23
28
  $transform: v => v == null ? null : decodeURIComponent(v),
@@ -29,7 +34,7 @@ const StreamChatMessageParams = new Archetype({
29
34
  }).compile('StreamChatMessageParams');
30
35
 
31
36
  module.exports = ({ db, options }) => async function* streamChatMessage(params) {
32
- const { model, content, documentData, currentDateTime, roles } = new StreamChatMessageParams(params);
37
+ const { model, content, documentData, currentDateTime, agentMode, roles } = new StreamChatMessageParams(params);
33
38
 
34
39
  await authorize('Model.streamChatMessage', roles);
35
40
 
@@ -51,10 +56,16 @@ module.exports = ({ db, options }) => async function* streamChatMessage(params)
51
56
  ].filter(Boolean).join('\n\n');
52
57
 
53
58
  const llmMessages = [{ role: 'user', content: [{ type: 'text', text: content }] }];
54
- const textStream = streamLLM(llmMessages, system, options);
59
+ const llmOptions = agentMode ? { ...options, tools: getAgentTools(db) } : options;
60
+ const textStream = streamLLM(llmMessages, system, llmOptions);
55
61
 
56
- for await (const textPart of textStream) {
57
- yield { textPart };
62
+ try {
63
+ for await (const textPart of textStream) {
64
+ yield { textPart };
65
+ }
66
+ } catch (err) {
67
+ yield { message: err?.message || 'Failed to stream chat response' };
68
+ return {};
58
69
  }
59
70
 
60
71
  return {};
@@ -7,6 +7,7 @@ const actionsToRequiredRoles = {
7
7
  'ChatThread.getChatThread': ['owner', 'admin', 'member'],
8
8
  'ChatThread.listChatThreads': ['owner', 'admin', 'member'],
9
9
  'ChatThread.shareChatThread': ['owner', 'admin', 'member'],
10
+ 'ChatThread.toggleAgentMode': ['owner', 'admin', 'member'],
10
11
  'Dashboard.createDashboard': ['owner', 'admin', 'member'],
11
12
  'Dashboard.deleteDashboard': ['owner', 'admin', 'member'],
12
13
  'Dashboard.getDashboard': ['owner', 'admin', 'member', 'readonly', 'dashboards'],
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ module.exports = `
4
+ You are a data querying assistant with access to tools that can query MongoDB directly. Use these tools to investigate the user's data before writing a final script. The final script must be written in JavaScript.
5
+
6
+ Your tools are for EXPLORATION ONLY — use them to understand the data before writing a script. NEVER use tool results as the data source for the final script. The final script must be fully self-contained: it must query MongoDB itself and not depend on any data you retrieved via tools.
7
+
8
+ Follow this process for each query:
9
+
10
+ 1. **Identify models**: Based on the user's question and the model descriptions below, identify which models are relevant.
11
+ 2. **Check document counts**: Use estimatedDocumentCount on the relevant models to understand data volume. This helps you decide whether a query is safe to run or needs limits/filters.
12
+ 3. **Test assumptions**: Use find or findOne to sample real documents and verify your assumptions about the shape of the data, field values, and relationships. Do not guess field values when you can look them up.
13
+ 4. **Type-check**: Before presenting the script, run it through the typeCheck tool to catch errors. The sandbox globals are: db (a mongoose.Connection — use db.model('ModelName') to get models), mongoose, ObjectId, console, and MongooseStudioChartColors. Fix any errors and re-check until clean.
14
+ 5. **Write the final script**: Once you understand the data, produce a self-contained script that queries MongoDB directly. The script must NOT use hard-coded data from your tool calls — it must run its own queries.
15
+
16
+ Keep scripts concise. Avoid unnecessary comments, error handling, and temporary variables.
17
+
18
+ Do not write any imports or require() statements, that will cause the script to break.
19
+
20
+ If the user approves the script, the script will run in the Node.js server in a sandboxed vm.createContext() call with the following globals: db (the Mongoose connection), mongoose, ObjectId (mongoose.Types.ObjectId), console, and MongooseStudioChartColors (an array of 8 hex color strings for chart dataset colors). The script return value will then send the response via JSON to the client. Be aware that the result of the query will be serialized to JSON before being displayed to the user. MAKE SURE TO RETURN A VALUE FROM THE SCRIPT.
21
+
22
+ Optimize scripts for readability first, followed by reliability, followed by performance. Avoid using the aggregation framework unless explicitly requested by the user. Use indexed fields in queries where possible.
23
+
24
+ Avoid using the aggregation framework unless explicitly asked by the user.
25
+
26
+ Assume the user has pre-defined schemas and models. Do not define any new schemas or models for the user.
27
+
28
+ Use async/await where possible. Assume top-level await is allowed.
29
+
30
+ Write at most one script, unless the user explicitly asks for multiple scripts.
31
+
32
+ Format output as Markdown, including code fences for any scripts the user requested.
33
+
34
+ Add a brief text description of what the script does.
35
+
36
+ If the user's query is best answered with a chart, return a Chart.js 4 configuration as \`return { $chart: chartJSConfig };\`. Disable ChartJS animation by default unless user asks for it. Set responsive: true, maintainAspectRatio: false options unless the user explicitly asks. Use MongooseStudioChartColors for dataset backgroundColor and borderColor by default. For line/bar charts, use MongooseStudioChartColors[i] as borderColor and MongooseStudioChartColors[i] + '33' as backgroundColor for each dataset. For pie/doughnut charts, use MongooseStudioChartColors.slice(0, data.length) as backgroundColor. Only use custom colors if the user explicitly requests specific colors.
37
+
38
+ If the user's query is best answered by a map, return an object { $featureCollection } which contains a GeoJSON FeatureCollection
39
+
40
+ If the user's query is best answered by a table, return an object { $table: { columns: string[], rows: any[][] } }
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
+ `.trim();
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ module.exports = [
4
+ {
5
+ name: 'estimatedDocumentCount',
6
+ description: 'Count the estimated number of documents in a collection. Times out after 5 seconds.'
7
+ },
8
+ {
9
+ name: 'find',
10
+ description: 'Run a find() query on a Mongoose model and return an array of documents. Times out after 5 seconds.'
11
+ },
12
+ {
13
+ name: 'findOne',
14
+ description: 'Run a findOne() query on a Mongoose model and return a single document or null. Times out after 5 seconds.'
15
+ },
16
+ {
17
+ name: 'typeCheck',
18
+ description: 'Type-checks the script and validates syntax using TypeScript and node --check'
19
+ }
20
+ ];
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+
3
+ const vm = require('vm');
4
+ const ts = require('typescript');
5
+ const { tool } = require('ai');
6
+ const { jsonSchema } = require('@ai-sdk/provider-utils');
7
+ const agentToolMetadata = require('./agentToolMetadata');
8
+
9
+ module.exports = function getAgentTools(db) {
10
+ const modelNames = Object.keys(db.models).filter(name => !name.startsWith('__Studio'));
11
+ const toolDescriptions = Object.fromEntries(agentToolMetadata.map(tool => [tool.name, tool.description]));
12
+
13
+ return {
14
+ estimatedDocumentCount: tool({
15
+ description: toolDescriptions.estimatedDocumentCount + ' Takes a model name.',
16
+ inputSchema: jsonSchema({
17
+ type: 'object',
18
+ properties: {
19
+ modelName: { type: 'string', description: 'The Mongoose model name' }
20
+ },
21
+ required: ['modelName']
22
+ }),
23
+ execute: async({ modelName }) => {
24
+ console.log(`estimatedDocumentCount: modelName=${modelName}`);
25
+ const Model = db.models[modelName];
26
+ if (Model == null) {
27
+ return { error: `Model ${modelName} not found. Available models: ${modelNames.join(', ')}` };
28
+ }
29
+ const count = await Model.estimatedDocumentCount().maxTimeMS(5000);
30
+ return { modelName, count };
31
+ }
32
+ }),
33
+ find: tool({
34
+ description: toolDescriptions.find,
35
+ inputSchema: jsonSchema({
36
+ type: 'object',
37
+ properties: {
38
+ modelName: { type: 'string', description: 'The Mongoose model name' },
39
+ filter: { type: 'object', description: 'MongoDB query filter', default: {} },
40
+ limit: { type: 'number', description: 'Maximum number of documents to return', default: 10 }
41
+ },
42
+ required: ['modelName']
43
+ }),
44
+ execute: async({ modelName, filter = {}, limit = 10 }) => {
45
+ console.log(`find: modelName=${modelName}, filter=${JSON.stringify(filter)}, limit=${limit}`);
46
+ const Model = db.models[modelName];
47
+ if (Model == null) {
48
+ return { error: `Model ${modelName} not found. Available models: ${modelNames.join(', ')}` };
49
+ }
50
+ const docs = await Model.find(filter).limit(limit).maxTimeMS(5000);
51
+ return { modelName, count: docs.length, docs };
52
+ }
53
+ }),
54
+ findOne: tool({
55
+ description: toolDescriptions.findOne,
56
+ inputSchema: jsonSchema({
57
+ type: 'object',
58
+ properties: {
59
+ modelName: { type: 'string', description: 'The Mongoose model name' },
60
+ filter: { type: 'object', description: 'MongoDB query filter', default: {} }
61
+ },
62
+ required: ['modelName']
63
+ }),
64
+ execute: async({ modelName, filter = {} }) => {
65
+ console.log(`findOne: modelName=${modelName}, filter=${JSON.stringify(filter)}`);
66
+ const Model = db.models[modelName];
67
+ if (Model == null) {
68
+ return { error: `Model ${modelName} not found. Available models: ${modelNames.join(', ')}` };
69
+ }
70
+ const doc = await Model.findOne(filter).maxTimeMS(5000);
71
+ return { modelName, doc };
72
+ }
73
+ }),
74
+ typeCheck: tool({
75
+ description: toolDescriptions.typeCheck + ' The script will run in a sandbox with globals: db (mongoose.Connection), mongoose, ObjectId (mongoose.Types.ObjectId), console, and MongooseStudioChartColors (string[]). Pass the raw script body (no imports, no wrapping function). Returns any TypeScript errors or JavaScript syntax errors found. Remember that you should write JavaScript, NOT TypeScript. This tool is just to check for obvious errors.',
76
+ inputSchema: jsonSchema({
77
+ type: 'object',
78
+ properties: {
79
+ script: { type: 'string', description: 'The script to type-check' }
80
+ },
81
+ required: ['script']
82
+ }),
83
+ execute: async({ script }) => {
84
+ const wrapped = wrapScriptForTypeCheck(script);
85
+ const fileName = '__check.ts';
86
+ const compilerOptions = {
87
+ noEmit: true,
88
+ strict: false,
89
+ target: ts.ScriptTarget.ESNext,
90
+ module: ts.ModuleKind.ESNext,
91
+ moduleResolution: ts.ModuleResolutionKind.Node10,
92
+ esModuleInterop: true,
93
+ baseUrl: require('path').resolve(__dirname, '..', '..'),
94
+ paths: { mongoose: ['./node_modules/mongoose'] }
95
+ };
96
+ const host = ts.createCompilerHost(compilerOptions);
97
+ const originalGetSourceFile = host.getSourceFile;
98
+ host.getSourceFile = (name, languageVersion) => {
99
+ if (name === fileName) {
100
+ return ts.createSourceFile(name, wrapped, languageVersion);
101
+ }
102
+ return originalGetSourceFile.call(host, name, languageVersion);
103
+ };
104
+ const program = ts.createProgram([fileName], compilerOptions, host);
105
+ const diagnostics = ts.getPreEmitDiagnostics(program)
106
+ .filter(d => d.file?.fileName === fileName);
107
+
108
+ const errors = diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n'));
109
+
110
+ // Also check that the script is valid JavaScript (not TypeScript) via vm.Script
111
+ try {
112
+ new vm.Script('(async () => {\n' + script + '\n})()');
113
+ } catch (e) {
114
+ errors.push('JavaScript syntax error: ' + e.message);
115
+ }
116
+
117
+ if (errors.length === 0) {
118
+ return { ok: true };
119
+ }
120
+
121
+ console.log('Errors', errors);
122
+
123
+ return { ok: false, errors };
124
+ }
125
+ })
126
+ };
127
+ };
128
+
129
+ const wrapScriptForTypeCheck = (script) => `
130
+ import mongoose from 'mongoose';
131
+ declare const db: Omit<mongoose.Connection, 'model'> & {
132
+ model(name: string): mongoose.Model<any>;
133
+ };
134
+ declare const ObjectId: typeof mongoose.Types.ObjectId;
135
+ declare const console: Console;
136
+ declare const MongooseStudioChartColors: string[];
137
+ async function __script() {
138
+ ${script}
139
+ }
140
+ `;
@@ -20,6 +20,11 @@ const chatMessageSchema = new mongoose.Schema({
20
20
  type: String,
21
21
  required: false
22
22
  },
23
+ toolCalls: [{
24
+ toolName: String,
25
+ input: mongoose.Schema.Types.Mixed,
26
+ status: String
27
+ }],
23
28
  executionResult: {
24
29
  output: mongoose.Schema.Types.Mixed,
25
30
  logs: String,
@@ -18,6 +18,10 @@ const chatThreadSchema = new mongoose.Schema({
18
18
  type: mongoose.ObjectId,
19
19
  ref: 'Workspace'
20
20
  },
21
+ agentMode: {
22
+ type: Boolean,
23
+ default: false
24
+ },
21
25
  sharingOptions: {
22
26
  sharedWithWorkspace: {
23
27
  type: Boolean,
@@ -3,7 +3,7 @@
3
3
  const { createAnthropic } = require('@ai-sdk/anthropic');
4
4
  const { createGoogleGenerativeAI } = require('@ai-sdk/google');
5
5
  const { createOpenAI } = require('@ai-sdk/openai');
6
- const { generateText } = require('ai');
6
+ const { generateText, stepCountIs } = require('ai');
7
7
  const { defaultMothershipURL } = require('../../constants');
8
8
 
9
9
  module.exports = async function callLLM(messages, system, options) {
@@ -46,7 +46,9 @@ module.exports = async function callLLM(messages, system, options) {
46
46
  return generateText({
47
47
  model: provider(model),
48
48
  system,
49
- messages
49
+ messages,
50
+ tools: options?.tools,
51
+ stopWhen: options?.tools ? stepCountIs(10) : undefined
50
52
  });
51
53
  }
52
54
 
@@ -3,7 +3,7 @@
3
3
  const { createAnthropic } = require('@ai-sdk/anthropic');
4
4
  const { createGoogleGenerativeAI } = require('@ai-sdk/google');
5
5
  const { createOpenAI } = require('@ai-sdk/openai');
6
- const { streamText } = require('ai');
6
+ const { streamText, stepCountIs } = require('ai');
7
7
  const { defaultMothershipURL } = require('../../constants');
8
8
 
9
9
  module.exports = async function* streamLLM(messages, system, options) {
@@ -44,16 +44,24 @@ module.exports = async function* streamLLM(messages, system, options) {
44
44
 
45
45
  if (provider) {
46
46
  let error = null;
47
- const { textStream } = streamText({
47
+ const { fullStream } = streamText({
48
48
  model: provider(model),
49
49
  system,
50
50
  messages,
51
+ tools: options?.tools,
52
+ stopWhen: options?.tools ? stepCountIs(10) : undefined,
51
53
  onError(err) {
52
54
  error = err.error;
53
55
  }
54
56
  });
55
- for await (const chunk of textStream) {
56
- yield chunk;
57
+ for await (const chunk of fullStream) {
58
+ if (chunk.type === 'text-delta') {
59
+ yield chunk.text;
60
+ } else if (chunk.type === 'tool-call') {
61
+ yield { toolCall: { toolName: chunk.toolName, input: chunk.input } };
62
+ } else if (chunk.type === 'tool-result') {
63
+ yield { toolResult: { toolName: chunk.toolName, output: chunk.output } };
64
+ }
57
65
  }
58
66
  if (error) {
59
67
  throw error;
package/eslint.config.js CHANGED
@@ -15,7 +15,8 @@ module.exports = defineConfig([
15
15
  fetch: true,
16
16
  setTimeout: true,
17
17
  process: true,
18
- console: true
18
+ console: true,
19
+ __dirname: true
19
20
  }
20
21
  },
21
22
  extends: [config]