@sweetoburrito/backstage-plugin-ai-assistant-backend 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/config.d.ts +16 -0
  2. package/dist/database/user-settings-store.cjs.js +42 -0
  3. package/dist/database/user-settings-store.cjs.js.map +1 -0
  4. package/dist/plugin.cjs.js +14 -6
  5. package/dist/plugin.cjs.js.map +1 -1
  6. package/dist/services/chat.cjs.js +23 -3
  7. package/dist/services/chat.cjs.js.map +1 -1
  8. package/dist/services/ingestor.cjs.js +24 -8
  9. package/dist/services/ingestor.cjs.js.map +1 -1
  10. package/dist/services/mcp/helpers.cjs.js +31 -0
  11. package/dist/services/mcp/helpers.cjs.js.map +1 -0
  12. package/dist/services/mcp/index.cjs.js +150 -0
  13. package/dist/services/mcp/index.cjs.js.map +1 -0
  14. package/dist/services/router/chat.cjs.js +17 -5
  15. package/dist/services/router/chat.cjs.js.map +1 -1
  16. package/dist/services/router/index.cjs.js +2 -2
  17. package/dist/services/router/index.cjs.js.map +1 -1
  18. package/dist/services/router/settings/index.cjs.js +51 -0
  19. package/dist/services/router/settings/index.cjs.js.map +1 -0
  20. package/dist/services/router/{mcp.cjs.js → settings/mcp.cjs.js} +3 -3
  21. package/dist/services/router/settings/mcp.cjs.js.map +1 -0
  22. package/dist/services/user-settings.cjs.js +34 -0
  23. package/dist/services/user-settings.cjs.js.map +1 -0
  24. package/dist/{services/tools → tools}/searchKnowledge.cjs.js +1 -1
  25. package/dist/tools/searchKnowledge.cjs.js.map +1 -0
  26. package/migrations/20251124_user_settings.js +131 -0
  27. package/package.json +3 -3
  28. package/dist/database/mcp-store.cjs.js +0 -46
  29. package/dist/database/mcp-store.cjs.js.map +0 -1
  30. package/dist/services/mcp.cjs.js +0 -121
  31. package/dist/services/mcp.cjs.js.map +0 -1
  32. package/dist/services/router/mcp.cjs.js.map +0 -1
  33. package/dist/services/tools/searchKnowledge.cjs.js.map +0 -1
package/config.d.ts CHANGED
@@ -28,6 +28,22 @@ export interface Config {
28
28
  };
29
29
  ingestion?: {
30
30
  schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
31
+ chunking?: {
32
+ /**
33
+ * The size of text chunks to split documents into during ingestion
34
+ */
35
+ chunkSize?: number;
36
+
37
+ /**
38
+ * The amount of overlap between text chunks during ingestion
39
+ */
40
+ chunkOverlap?: number;
41
+
42
+ /**
43
+ * The maximum number of chunks to process in a single batch when ingesting documents
44
+ */
45
+ maxChunkProcessingSize?: number;
46
+ };
31
47
  };
32
48
  mcp: {
33
49
  /**
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const USER_SETTINGS_TABLE = "user_ai_assistant_settings";
4
+ class UserSettingsStore {
5
+ /**
6
+ * Creates an instance of UserSettingsStore.
7
+ * @param client - The Knex client to interact with the PostgreSQL database.
8
+ */
9
+ constructor(client) {
10
+ this.client = client;
11
+ }
12
+ static async fromConfig({ database }) {
13
+ const client = await database.getClient();
14
+ return new UserSettingsStore(client);
15
+ }
16
+ userSettingsTable() {
17
+ return this.client(USER_SETTINGS_TABLE);
18
+ }
19
+ async getUserSettings(userRef) {
20
+ const rows = await this.userSettingsTable().where({ userRef }).select();
21
+ return rows.map((row) => {
22
+ const data = JSON.parse(row.data);
23
+ return { type: row.type, data };
24
+ });
25
+ }
26
+ async getUserSettingsByType(userRef, type) {
27
+ const row = await this.userSettingsTable().where({ userRef, type }).first();
28
+ if (!row) {
29
+ return null;
30
+ }
31
+ return row.data;
32
+ }
33
+ async setUserSettings(userRef, type, data) {
34
+ await this.userSettingsTable().insert({ userRef, type, data }).onConflict(["userRef", "type"]).merge({ data, updated_at: this.client.fn.now() });
35
+ }
36
+ async deleteUserSettings(userRef, type) {
37
+ await this.userSettingsTable().where({ userRef, type }).delete();
38
+ }
39
+ }
40
+
41
+ exports.UserSettingsStore = UserSettingsStore;
42
+ //# sourceMappingURL=user-settings-store.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-settings-store.cjs.js","sources":["../../src/database/user-settings-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\n\nimport { Knex } from 'knex';\n\nconst USER_SETTINGS_TABLE = 'user_ai_assistant_settings';\n\nexport type UserSettingsStoreOptions = {\n database: DatabaseService;\n};\n\nexport class UserSettingsStore {\n /**\n * Creates an instance of UserSettingsStore.\n * @param client - The Knex client to interact with the PostgreSQL database.\n */\n constructor(private readonly client: Knex) {}\n\n static async fromConfig({ database }: UserSettingsStoreOptions) {\n const client = await database.getClient();\n return new UserSettingsStore(client);\n }\n\n userSettingsTable() {\n return this.client(USER_SETTINGS_TABLE);\n }\n\n async getUserSettings<T extends Record<string, unknown>>(\n userRef: string,\n ): Promise<Array<{ type: string; data: Partial<T> }>> {\n const rows = await this.userSettingsTable().where({ userRef }).select();\n return rows.map(row => {\n const data: T = JSON.parse(row.data);\n\n return { type: row.type, data };\n });\n }\n\n async getUserSettingsByType<T extends Record<string, unknown>>(\n userRef: string,\n type: string,\n ): Promise<T | null> {\n const row = await this.userSettingsTable().where({ userRef, type }).first();\n if (!row) {\n return null;\n }\n\n return row.data;\n }\n\n async setUserSettings<T extends Record<string, unknown>>(\n userRef: string,\n type: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.userSettingsTable()\n .insert({ userRef, type, data })\n .onConflict(['userRef', 'type'])\n .merge({ data, updated_at: this.client.fn.now() });\n }\n\n async deleteUserSettings(userRef: string, type: string): Promise<void> {\n await this.userSettingsTable().where({ userRef, type }).delete();\n }\n}\n"],"names":[],"mappings":";;AAIA,MAAM,mBAAA,GAAsB,4BAAA;AAMrB,MAAM,iBAAA,CAAkB;AAAA;AAAA;AAAA;AAAA;AAAA,EAK7B,YAA6B,MAAA,EAAc;AAAd,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAe;AAAA,EAE5C,aAAa,UAAA,CAAW,EAAE,QAAA,EAAS,EAA6B;AAC9D,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,SAAA,EAAU;AACxC,IAAA,OAAO,IAAI,kBAAkB,MAAM,CAAA;AAAA,EACrC;AAAA,EAEA,iBAAA,GAAoB;AAClB,IAAA,OAAO,IAAA,CAAK,OAAO,mBAAmB,CAAA;AAAA,EACxC;AAAA,EAEA,MAAM,gBACJ,OAAA,EACoD;AACpD,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,iBAAA,EAAkB,CAAE,MAAM,EAAE,OAAA,EAAS,CAAA,CAAE,MAAA,EAAO;AACtE,IAAA,OAAO,IAAA,CAAK,IAAI,CAAA,GAAA,KAAO;AACrB,MAAA,MAAM,IAAA,GAAU,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,IAAI,CAAA;AAEnC,MAAA,OAAO,EAAE,IAAA,EAAM,GAAA,CAAI,IAAA,EAAM,IAAA,EAAK;AAAA,IAChC,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,qBAAA,CACJ,OAAA,EACA,IAAA,EACmB;AACnB,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,iBAAA,EAAkB,CAAE,KAAA,CAAM,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,KAAA,EAAM;AAC1E,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,OAAO,GAAA,CAAI,IAAA;AAAA,EACb;AAAA,EAEA,MAAM,eAAA,CACJ,OAAA,EACA,IAAA,EACA,IAAA,EACe;AACf,IAAA,MAAM,IAAA,CAAK,iBAAA,EAAkB,CAC1B,MAAA,CAAO,EAAE,SAAS,IAAA,EAAM,IAAA,EAAM,CAAA,CAC9B,UAAA,CAAW,CAAC,WAAW,MAAM,CAAC,CAAA,CAC9B,KAAA,CAAM,EAAE,IAAA,EAAM,UAAA,EAAY,IAAA,CAAK,MAAA,CAAO,EAAA,CAAG,GAAA,EAAI,EAAG,CAAA;AAAA,EACrD;AAAA,EAEA,MAAM,kBAAA,CAAmB,OAAA,EAAiB,IAAA,EAA6B;AACrE,IAAA,MAAM,IAAA,CAAK,mBAAkB,CAAE,KAAA,CAAM,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO;AAAA,EACjE;AACF;;;;"}
@@ -1,18 +1,19 @@
1
1
  'use strict';
2
2
 
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
- var index = require('./services/router/index.cjs.js');
4
+ var index$1 = require('./services/router/index.cjs.js');
5
5
  var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
6
6
  var ingestor = require('./services/ingestor.cjs.js');
7
7
  var chat = require('./services/chat.cjs.js');
8
8
  var migrations = require('./database/migrations.cjs.js');
9
9
  var pgVectorStore = require('./database/pg-vector-store.cjs.js');
10
10
  var pluginSignalsNode = require('@backstage/plugin-signals-node');
11
- var searchKnowledge = require('./services/tools/searchKnowledge.cjs.js');
11
+ var searchKnowledge = require('./tools/searchKnowledge.cjs.js');
12
12
  var pluginCatalogNode = require('@backstage/plugin-catalog-node');
13
- var mcp = require('./services/mcp.cjs.js');
13
+ var index = require('./services/mcp/index.cjs.js');
14
14
  var callbacks = require('./services/callbacks.cjs.js');
15
15
  var summarizer = require('./services/summarizer.cjs.js');
16
+ var userSettings = require('./services/user-settings.cjs.js');
16
17
 
17
18
  const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
18
19
  pluginId: "ai-assistant",
@@ -89,7 +90,7 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
89
90
  vectorStore,
90
91
  ingestors
91
92
  });
92
- const mcp$1 = await mcp.createMcpService(options);
93
+ const mcp = await index.createMcpService(options);
93
94
  const searchKnowledgeTool = searchKnowledge.createSearchKnowledgeTool({ vectorStore });
94
95
  tools.push(searchKnowledgeTool);
95
96
  const callback = await callbacks.createCallbackService({
@@ -104,12 +105,19 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
104
105
  ...options,
105
106
  models,
106
107
  tools,
107
- mcp: mcp$1,
108
+ mcp,
108
109
  callback,
109
110
  summarizer: summarizer$1
110
111
  });
112
+ const userSettings$1 = await userSettings.createUserSettingsService(options);
111
113
  httpRouter.use(
112
- await index.createRouter({ ...options, chat: chat$1, mcp: mcp$1, summarizer: summarizer$1 })
114
+ await index$1.createRouter({
115
+ ...options,
116
+ chat: chat$1,
117
+ mcp,
118
+ summarizer: summarizer$1,
119
+ userSettings: userSettings$1
120
+ })
113
121
  );
114
122
  dataIngestionPipeline.start();
115
123
  }
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './services/router';\nimport {\n dataIngestorExtensionPoint,\n EmbeddingsProvider,\n embeddingsProviderExtensionPoint,\n Ingestor,\n Model,\n modelProviderExtensionPoint,\n Tool,\n toolExtensionPoint,\n callbackProviderExtensionPoint,\n CallbackProvider,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { createDataIngestionPipeline } from './services/ingestor';\nimport { createChatService } from './services/chat';\nimport { applyDatabaseMigrations } from './database/migrations';\nimport { PgVectorStore } from './database';\nimport { signalsServiceRef } from '@backstage/plugin-signals-node';\nimport { createSearchKnowledgeTool } from './services/tools/searchKnowledge';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createMcpService } from './services/mcp';\nimport { createCallbackService } from './services/callbacks';\nimport { createSummarizerService } from './services/summarizer';\n/**\n * aiAssistantPlugin backend plugin\n *\n * @public\n */\n\nexport const aiAssistantPlugin = createBackendPlugin({\n pluginId: 'ai-assistant',\n register(env) {\n const ingestors: Ingestor[] = [];\n const models: Model[] = [];\n const tools: Tool[] = [];\n const callbacks: CallbackProvider[] = [];\n\n let embeddingsProvider: EmbeddingsProvider;\n\n env.registerExtensionPoint(dataIngestorExtensionPoint, {\n registerIngestor: ingestor => {\n const existingIngestor = ingestors.find(i => i.id === ingestor.id);\n if (existingIngestor) {\n throw new Error(\n `Ingestor with id ${ingestor.id} is already registered.`,\n );\n }\n ingestors.push(ingestor);\n },\n });\n\n env.registerExtensionPoint(embeddingsProviderExtensionPoint, {\n register: provider => {\n embeddingsProvider = provider;\n },\n });\n\n env.registerExtensionPoint(modelProviderExtensionPoint, {\n register: model => {\n const existingModel = models.find(m => m.id === model.id);\n if (existingModel) {\n throw new Error(`Model with id ${model.id} is already registered.`);\n }\n models.push(model);\n },\n });\n\n env.registerExtensionPoint(toolExtensionPoint, {\n register: tool => {\n const existingTool = tools.find(t => t.name === tool.name);\n if (existingTool) {\n throw new Error(`Tool with name ${tool.name} is already registered.`);\n }\n tools.push(tool);\n },\n });\n\n env.registerExtensionPoint(callbackProviderExtensionPoint, {\n register: callbackProvider => {\n callbacks.push(callbackProvider);\n },\n });\n\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n database: coreServices.database,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n scheduler: coreServices.scheduler,\n httpAuth: coreServices.httpAuth,\n userInfo: coreServices.userInfo,\n signals: signalsServiceRef,\n catalog: catalogServiceRef,\n cache: coreServices.cache,\n auth: coreServices.auth,\n },\n\n async init(options) {\n const { httpRouter, database, config } = options;\n\n const client = await database.getClient();\n\n await applyDatabaseMigrations(client);\n\n const vectorStore = await PgVectorStore.fromConfig(options);\n\n if (!embeddingsProvider) {\n throw new Error('No Embeddings Provider was registered.');\n }\n\n vectorStore.connectEmbeddings(await embeddingsProvider.getEmbeddings());\n\n const dataIngestionPipeline = createDataIngestionPipeline({\n ...options,\n vectorStore,\n ingestors,\n });\n\n const mcp = await createMcpService(options);\n\n const searchKnowledgeTool = createSearchKnowledgeTool({ vectorStore });\n tools.push(searchKnowledgeTool);\n\n const callback = await createCallbackService({\n callbacks,\n });\n\n const summarizer = await createSummarizerService({\n config,\n models,\n callback,\n });\n\n const chat = await createChatService({\n ...options,\n models,\n tools,\n mcp,\n callback,\n summarizer,\n });\n\n httpRouter.use(\n await createRouter({ ...options, chat, mcp, summarizer }),\n );\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","callbacks","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","callbackProviderExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","mcp","createMcpService","createSearchKnowledgeTool","createCallbackService","summarizer","createSummarizerService","chat","createChatService","createRouter"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n coreServices,\n createBackendPlugin,\n} from '@backstage/backend-plugin-api';\nimport { createRouter } from './services/router';\nimport {\n dataIngestorExtensionPoint,\n EmbeddingsProvider,\n embeddingsProviderExtensionPoint,\n Ingestor,\n Model,\n modelProviderExtensionPoint,\n toolExtensionPoint,\n callbackProviderExtensionPoint,\n CallbackProvider,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { Tool } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { createDataIngestionPipeline } from './services/ingestor';\nimport { createChatService } from './services/chat';\nimport { applyDatabaseMigrations } from './database/migrations';\nimport { PgVectorStore } from './database';\nimport { signalsServiceRef } from '@backstage/plugin-signals-node';\nimport { createSearchKnowledgeTool } from './tools/searchKnowledge';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createMcpService } from './services/mcp';\nimport { createCallbackService } from './services/callbacks';\nimport { createSummarizerService } from './services/summarizer';\nimport { createUserSettingsService } from './services/user-settings';\n/**\n * aiAssistantPlugin backend plugin\n *\n * @public\n */\n\nexport const aiAssistantPlugin = createBackendPlugin({\n pluginId: 'ai-assistant',\n register(env) {\n const ingestors: Ingestor[] = [];\n const models: Model[] = [];\n const tools: Tool[] = [];\n const callbacks: CallbackProvider[] = [];\n\n let embeddingsProvider: EmbeddingsProvider;\n\n env.registerExtensionPoint(dataIngestorExtensionPoint, {\n registerIngestor: ingestor => {\n const existingIngestor = ingestors.find(i => i.id === ingestor.id);\n if (existingIngestor) {\n throw new Error(\n `Ingestor with id ${ingestor.id} is already registered.`,\n );\n }\n ingestors.push(ingestor);\n },\n });\n\n env.registerExtensionPoint(embeddingsProviderExtensionPoint, {\n register: provider => {\n embeddingsProvider = provider;\n },\n });\n\n env.registerExtensionPoint(modelProviderExtensionPoint, {\n register: model => {\n const existingModel = models.find(m => m.id === model.id);\n if (existingModel) {\n throw new Error(`Model with id ${model.id} is already registered.`);\n }\n models.push(model);\n },\n });\n\n env.registerExtensionPoint(toolExtensionPoint, {\n register: tool => {\n const existingTool = tools.find(t => t.name === tool.name);\n if (existingTool) {\n throw new Error(`Tool with name ${tool.name} is already registered.`);\n }\n tools.push(tool);\n },\n });\n\n env.registerExtensionPoint(callbackProviderExtensionPoint, {\n register: callbackProvider => {\n callbacks.push(callbackProvider);\n },\n });\n\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n database: coreServices.database,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n scheduler: coreServices.scheduler,\n httpAuth: coreServices.httpAuth,\n userInfo: coreServices.userInfo,\n signals: signalsServiceRef,\n catalog: catalogServiceRef,\n cache: coreServices.cache,\n auth: coreServices.auth,\n },\n\n async init(options) {\n const { httpRouter, database, config } = options;\n\n const client = await database.getClient();\n\n await applyDatabaseMigrations(client);\n\n const vectorStore = await PgVectorStore.fromConfig(options);\n\n if (!embeddingsProvider) {\n throw new Error('No Embeddings Provider was registered.');\n }\n\n vectorStore.connectEmbeddings(await embeddingsProvider.getEmbeddings());\n\n const dataIngestionPipeline = createDataIngestionPipeline({\n ...options,\n vectorStore,\n ingestors,\n });\n\n const mcp = await createMcpService(options);\n\n const searchKnowledgeTool = createSearchKnowledgeTool({ vectorStore });\n tools.push(searchKnowledgeTool);\n\n const callback = await createCallbackService({\n callbacks,\n });\n\n const summarizer = await createSummarizerService({\n config,\n models,\n callback,\n });\n\n const chat = await createChatService({\n ...options,\n models,\n tools,\n mcp,\n callback,\n summarizer,\n });\n\n const userSettings = await createUserSettingsService(options);\n\n httpRouter.use(\n await createRouter({\n ...options,\n chat,\n mcp,\n summarizer,\n userSettings,\n }),\n );\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","callbacks","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","callbackProviderExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","createMcpService","createSearchKnowledgeTool","createCallbackService","summarizer","createSummarizerService","chat","createChatService","userSettings","createUserSettingsService","createRouter"],"mappings":"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"}
@@ -104,7 +104,8 @@ ${contentPrompt}`;
104
104
  messages,
105
105
  modelId,
106
106
  stream = true,
107
- userCredentials
107
+ userCredentials,
108
+ tools: enabledTools
108
109
  }) => {
109
110
  const model = models.find((m) => m.id === modelId)?.chatModel;
110
111
  if (!model) {
@@ -121,7 +122,14 @@ ${contentPrompt}`;
121
122
  const credentials = await auth.getOwnServiceCredentials();
122
123
  const user = await backstagePluginAiAssistantNode.getUser(cache, userEntityRef, credentials, catalog);
123
124
  const mcpTools = await mcp.getTools(userCredentials);
124
- const agentTools = tools$1.map((tool) => new tools.DynamicStructuredTool(tool)).concat(mcpTools.map((tool) => new tools.DynamicStructuredTool(tool)));
125
+ const agentTools = [...tools$1, ...mcpTools].filter((tool) => {
126
+ if (enabledTools === void 0) return true;
127
+ if (enabledTools.length === 0) return false;
128
+ const enabled = enabledTools.find(
129
+ (enabledTool) => enabledTool.name === tool.name && enabledTool.provider === tool.provider
130
+ );
131
+ return !!enabled;
132
+ }).map((tool) => new tools.DynamicStructuredTool(tool));
125
133
  const messagesWithoutSystem = messages.filter((m) => m.role !== "system");
126
134
  addMessages(
127
135
  messagesWithoutSystem,
@@ -261,13 +269,25 @@ ${contentPrompt}`;
261
269
  message: updatedMessage
262
270
  });
263
271
  };
272
+ const getAvailableTools = async ({
273
+ credentials
274
+ }) => {
275
+ const mcpTools = await mcp.getTools(credentials);
276
+ const availableTools = tools$1.concat(mcpTools).map((tool) => ({
277
+ name: tool.name,
278
+ provider: tool.provider,
279
+ description: tool.description
280
+ }));
281
+ return availableTools;
282
+ };
264
283
  return {
265
284
  prompt,
266
285
  getAvailableModels,
267
286
  getConversation,
268
287
  getConversations,
269
288
  addMessages,
270
- scoreMessage
289
+ scoreMessage,
290
+ getAvailableTools
271
291
  };
272
292
  };
273
293
 
@@ -1 +1 @@
1
- {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n LoggerService,\n RootConfigService,\n DatabaseService,\n AuthService,\n} from '@backstage/backend-plugin-api';\nimport { ChatStore } from '../database/chat-store';\nimport {\n Conversation,\n Message,\n JsonObject,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { SignalsService } from '@backstage/plugin-signals-node';\nimport {\n DEFAULT_FORMATTING_PROMPT,\n DEFAULT_IDENTITY_PROMPT,\n DEFAULT_SYSTEM_PROMPT,\n DEFAULT_TOOL_GUIDELINE,\n} from '../constants/prompts';\nimport {\n Tool,\n Model,\n getUser,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { DynamicStructuredTool } from '@langchain/core/tools';\nimport { createReactAgent } from '@langchain/langgraph/prebuilt';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\nimport { SummarizerService } from './summarizer';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { McpService } from './mcp';\nimport { CallbackService } from './callbacks';\n\nexport type ChatServiceOptions = {\n models: Model[];\n tools: Tool[];\n logger: LoggerService;\n config: RootConfigService;\n database: DatabaseService;\n signals: SignalsService;\n catalog: CatalogService;\n cache: CacheService;\n auth: AuthService;\n mcp: McpService;\n userInfo: UserInfoService;\n callback: CallbackService;\n summarizer: SummarizerService;\n};\n\ntype PromptOptions = {\n modelId: string;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n userCredentials: BackstageCredentials;\n};\n\ntype GetConversationOptions = {\n conversationId: string;\n userEntityRef: string;\n};\n\ntype GetConversationsOptions = {\n userEntityRef: string;\n};\n\n// Helper type for messages with required fields except traceId which remains optional\ntype MessageWithRequiredFields = Required<Omit<Message, 'traceId'>> &\n Pick<Message, 'traceId'>;\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<MessageWithRequiredFields[]>;\n getAvailableModels: () => Promise<string[]>;\n getConversation: (\n options: GetConversationOptions,\n ) => Promise<MessageWithRequiredFields[]>;\n getConversations: (\n options: GetConversationsOptions,\n ) => Promise<Conversation[]>;\n addMessages: (\n messages: Message[],\n userRef: string,\n conversationId: string,\n recentConversationMessages?: Message[],\n ) => Promise<void>;\n scoreMessage: (messageId: string, score: number) => Promise<void>;\n};\n\nexport const createChatService = async ({\n models,\n tools,\n logger,\n database,\n signals,\n config,\n catalog,\n cache,\n auth,\n mcp,\n userInfo,\n callback,\n summarizer,\n}: ChatServiceOptions): Promise<ChatService> => {\n logger.info(`Available models: ${models.map(m => m.id).join(', ')}`);\n logger.info(`Available tools: ${tools.map(t => t.name).join(', ')}`);\n\n const identityPrompt =\n config.getOptionalString('aiAssistant.prompt.identity') ||\n DEFAULT_IDENTITY_PROMPT;\n\n const formattingPrompt =\n config.getOptionalString('aiAssistant.prompt.formatting') ||\n DEFAULT_FORMATTING_PROMPT;\n\n const contentPrompt =\n config.getOptionalString('aiAssistant.prompt.content') ||\n DEFAULT_SYSTEM_PROMPT;\n\n const combinedBasePrompt = `${identityPrompt}\\n\\n${formattingPrompt}\\n\\n${contentPrompt}`;\n\n const toolGuideline =\n config.getOptionalString('aiAssistant.prompt.toolGuideline') ||\n DEFAULT_TOOL_GUIDELINE;\n\n const chatStore = await ChatStore.fromConfig({ database });\n\n const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {basePrompt}\n\n TOOL USAGE GUIDELINES:\n {toolGuideline}\n\n Available tools:\n {toolList}\n\n Calling User:\n {user}\n\n Context:\n {context}`);\n\n const addMessages: ChatService['addMessages'] = async (\n messages,\n userRef,\n conversationId,\n recentConversationMessages,\n ) => {\n // If we have recentConversationMessages, use them; otherwise, fetch the last 5 messages\n const recentMessages =\n recentConversationMessages ||\n (await chatStore.getChatMessages(conversationId, userRef, 5, ['tool']));\n\n const conversationSize = (recentMessages?.length ?? 0) + messages.length;\n\n if (recentMessages.length === 0) {\n const conversation: Conversation = {\n id: conversationId,\n title: 'New Conversation',\n userRef,\n };\n chatStore.createConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n return;\n }\n\n if (conversationSize < 5) {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const conversation = await chatStore.getConversation(\n conversationId,\n userRef,\n );\n\n if (conversation.title !== 'New Conversation') {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const summary = await summarizer.summarizeConversation({\n messages: recentMessages,\n length: '25 characters',\n });\n\n conversation.title = summary;\n\n chatStore.updateConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n };\n\n const prompt: ChatService['prompt'] = async ({\n conversationId,\n messages,\n modelId,\n stream = true,\n userCredentials,\n }: PromptOptions) => {\n const model = models.find(m => m.id === modelId)?.chatModel;\n\n if (!model) {\n throw new Error(`Model with id ${modelId} not found`);\n }\n\n const { userEntityRef } = await userInfo.getUserInfo(userCredentials);\n\n const streamFn = async () => {\n const recentConversationMessages = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n 10,\n ['tool'],\n );\n\n const credentials = await auth.getOwnServiceCredentials();\n const user = await getUser(cache, userEntityRef, credentials, catalog);\n\n const mcpTools = await mcp.getTools(userCredentials);\n\n const agentTools = tools\n .map(tool => new DynamicStructuredTool(tool))\n .concat(mcpTools.map(tool => new DynamicStructuredTool(tool)));\n\n const messagesWithoutSystem = messages.filter(m => m.role !== 'system');\n\n addMessages(\n messagesWithoutSystem,\n userEntityRef,\n conversationId,\n recentConversationMessages,\n );\n\n const systemPrompt = await systemPromptTemplate.formatMessages({\n basePrompt: combinedBasePrompt,\n toolGuideline,\n toolList: agentTools\n .map(tool => `- ${tool.name}: ${tool.description}`)\n .join('\\n'),\n context: `none`,\n user,\n });\n\n const agent = createReactAgent({\n llm: model,\n tools: agentTools,\n prompt: systemPrompt[0].text,\n });\n\n const { callbacks } = await callback.getChainCallbacks({\n conversationId,\n userId: userEntityRef,\n modelId,\n });\n\n const { metadata: promptMetadata } = await callback.getChainMetadata({\n conversationId,\n userId: userEntityRef,\n modelId,\n });\n\n const traceId = uuid();\n\n const promptStream = await agent.stream(\n {\n messages: [...recentConversationMessages, ...messages],\n },\n {\n streamMode: ['values'],\n runName: 'ai-assistant-chat',\n runId: traceId,\n metadata: promptMetadata,\n callbacks,\n },\n );\n\n const responseMessages: MessageWithRequiredFields[] = [];\n\n for await (const [, chunk] of promptStream) {\n const { messages: promptMessages } = chunk;\n\n const newMessages: MessageWithRequiredFields[] = promptMessages\n .filter(\n m =>\n responseMessages.findIndex(\n rm => m.id === rm.metadata.langGraphId,\n ) === -1,\n )\n .filter(\n m =>\n recentConversationMessages.findIndex(rm => rm.id === m.id) === -1,\n )\n .filter(m => m.getType() !== 'human')\n .map(m => {\n const id = uuid();\n const role = m.getType();\n const content =\n typeof m.content === 'string'\n ? m.content\n : JSON.stringify(m.content);\n\n const metadata: JsonObject = {\n langGraphId: m.id ?? '',\n };\n\n if (role === 'ai') {\n const aiMessage = m as AIMessage;\n metadata.toolCalls = aiMessage.tool_calls || [];\n metadata.finishReason =\n aiMessage.response_metadata.finish_reason || undefined;\n metadata.modelName =\n aiMessage.response_metadata.model_name || undefined;\n }\n\n if (role === 'tool') {\n const toolMessage = m as ToolMessage;\n metadata.name = toolMessage.name || '';\n }\n\n return {\n id,\n role,\n content,\n metadata,\n score: 0,\n traceId: traceId,\n };\n });\n\n // Simulate streaming until langchain messages error is better understood\n for await (const m of newMessages) {\n const words = m.content.split(' ');\n const chunkSize = 5; // Send 5 words at a time\n let messageBuilder = '';\n\n for (let i = 0; i < words.length; i += chunkSize) {\n const wordChunk = words.slice(i, i + chunkSize).join(' ');\n messageBuilder = messageBuilder.concat(wordChunk).concat(' ');\n m.content = messageBuilder;\n\n await new Promise(resolve => setTimeout(resolve, 50));\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-stream:${conversationId}`,\n message: { messages: [m] },\n recipients: {\n type: 'user',\n entityRef: userEntityRef,\n },\n });\n }\n }\n\n responseMessages.push(...newMessages);\n }\n\n addMessages(responseMessages, userEntityRef, conversationId, [\n ...recentConversationMessages,\n ...messages,\n ]);\n\n return responseMessages;\n };\n\n const result = streamFn();\n\n return stream ? [] : result;\n };\n\n const getAvailableModels: ChatService['getAvailableModels'] = async () => {\n return models.map(x => x.id);\n };\n\n const getConversation: ChatService['getConversation'] = async (\n options: GetConversationOptions,\n ) => {\n const { conversationId, userEntityRef } = options;\n\n const conversation = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n );\n\n return conversation;\n };\n\n const getConversations: ChatService['getConversations'] = async ({\n userEntityRef,\n }: GetConversationsOptions) => {\n const conversations = await chatStore.getConversations(userEntityRef);\n\n return conversations;\n };\n\n const scoreMessage: ChatService['scoreMessage'] = async (\n messageId: string,\n score: number,\n ) => {\n const message = await chatStore.getMessageById(messageId);\n\n if (!message) {\n throw new Error(`Message with id ${messageId} not found`);\n }\n\n const updatedMessage: Required<Message> = {\n ...message,\n score,\n };\n\n chatStore.updateMessage(updatedMessage);\n\n callback.handleScoreCallbacks({\n name: 'helpfulness',\n message: updatedMessage,\n });\n };\n\n return {\n prompt,\n getAvailableModels,\n getConversation,\n getConversations,\n addMessages,\n scoreMessage,\n };\n};\n"],"names":["tools","DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","chatStore","ChatStore","SystemMessagePromptTemplate","conversation","getUser","DynamicStructuredTool","createReactAgent","uuid"],"mappings":"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"}
1
+ {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n LoggerService,\n RootConfigService,\n DatabaseService,\n AuthService,\n} from '@backstage/backend-plugin-api';\nimport { ChatStore } from '../database/chat-store';\nimport {\n Conversation,\n Message,\n JsonObject,\n Tool,\n UserTool,\n EnabledTool,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { SignalsService } from '@backstage/plugin-signals-node';\nimport {\n DEFAULT_FORMATTING_PROMPT,\n DEFAULT_IDENTITY_PROMPT,\n DEFAULT_SYSTEM_PROMPT,\n DEFAULT_TOOL_GUIDELINE,\n} from '../constants/prompts';\nimport {\n Model,\n getUser,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { DynamicStructuredTool } from '@langchain/core/tools';\nimport { createReactAgent } from '@langchain/langgraph/prebuilt';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\nimport { SummarizerService } from './summarizer';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { McpService } from './mcp';\nimport { CallbackService } from './callbacks';\n\nexport type ChatServiceOptions = {\n models: Model[];\n tools: Tool[];\n logger: LoggerService;\n config: RootConfigService;\n database: DatabaseService;\n signals: SignalsService;\n catalog: CatalogService;\n cache: CacheService;\n auth: AuthService;\n mcp: McpService;\n userInfo: UserInfoService;\n callback: CallbackService;\n summarizer: SummarizerService;\n};\n\ntype PromptOptions = {\n modelId: string;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n userCredentials: BackstageCredentials;\n tools?: EnabledTool[];\n};\n\ntype GetConversationOptions = {\n conversationId: string;\n userEntityRef: string;\n};\n\ntype GetConversationsOptions = {\n userEntityRef: string;\n};\n\n// Helper type for messages with required fields except traceId which remains optional\ntype MessageWithRequiredFields = Required<Omit<Message, 'traceId'>> &\n Pick<Message, 'traceId'>;\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<MessageWithRequiredFields[]>;\n getAvailableModels: () => Promise<string[]>;\n getConversation: (\n options: GetConversationOptions,\n ) => Promise<MessageWithRequiredFields[]>;\n getConversations: (\n options: GetConversationsOptions,\n ) => Promise<Conversation[]>;\n addMessages: (\n messages: Message[],\n userRef: string,\n conversationId: string,\n recentConversationMessages?: Message[],\n ) => Promise<void>;\n scoreMessage: (messageId: string, score: number) => Promise<void>;\n getAvailableTools: (options: {\n credentials: BackstageCredentials;\n }) => Promise<UserTool[]>;\n};\n\nexport const createChatService = async ({\n models,\n tools,\n logger,\n database,\n signals,\n config,\n catalog,\n cache,\n auth,\n mcp,\n userInfo,\n callback,\n summarizer,\n}: ChatServiceOptions): Promise<ChatService> => {\n logger.info(`Available models: ${models.map(m => m.id).join(', ')}`);\n logger.info(`Available tools: ${tools.map(t => t.name).join(', ')}`);\n\n const identityPrompt =\n config.getOptionalString('aiAssistant.prompt.identity') ||\n DEFAULT_IDENTITY_PROMPT;\n\n const formattingPrompt =\n config.getOptionalString('aiAssistant.prompt.formatting') ||\n DEFAULT_FORMATTING_PROMPT;\n\n const contentPrompt =\n config.getOptionalString('aiAssistant.prompt.content') ||\n DEFAULT_SYSTEM_PROMPT;\n\n const combinedBasePrompt = `${identityPrompt}\\n\\n${formattingPrompt}\\n\\n${contentPrompt}`;\n\n const toolGuideline =\n config.getOptionalString('aiAssistant.prompt.toolGuideline') ||\n DEFAULT_TOOL_GUIDELINE;\n\n const chatStore = await ChatStore.fromConfig({ database });\n\n const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {basePrompt}\n\n TOOL USAGE GUIDELINES:\n {toolGuideline}\n\n Available tools:\n {toolList}\n\n Calling User:\n {user}\n\n Context:\n {context}`);\n\n const addMessages: ChatService['addMessages'] = async (\n messages,\n userRef,\n conversationId,\n recentConversationMessages,\n ) => {\n // If we have recentConversationMessages, use them; otherwise, fetch the last 5 messages\n const recentMessages =\n recentConversationMessages ||\n (await chatStore.getChatMessages(conversationId, userRef, 5, ['tool']));\n\n const conversationSize = (recentMessages?.length ?? 0) + messages.length;\n\n if (recentMessages.length === 0) {\n const conversation: Conversation = {\n id: conversationId,\n title: 'New Conversation',\n userRef,\n };\n chatStore.createConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n return;\n }\n\n if (conversationSize < 5) {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const conversation = await chatStore.getConversation(\n conversationId,\n userRef,\n );\n\n if (conversation.title !== 'New Conversation') {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const summary = await summarizer.summarizeConversation({\n messages: recentMessages,\n length: '25 characters',\n });\n\n conversation.title = summary;\n\n chatStore.updateConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n };\n\n const prompt: ChatService['prompt'] = async ({\n conversationId,\n messages,\n modelId,\n stream = true,\n userCredentials,\n tools: enabledTools,\n }: PromptOptions) => {\n const model = models.find(m => m.id === modelId)?.chatModel;\n\n if (!model) {\n throw new Error(`Model with id ${modelId} not found`);\n }\n\n const { userEntityRef } = await userInfo.getUserInfo(userCredentials);\n\n const streamFn = async () => {\n const recentConversationMessages = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n 10,\n ['tool'],\n );\n\n const credentials = await auth.getOwnServiceCredentials();\n const user = await getUser(cache, userEntityRef, credentials, catalog);\n\n const mcpTools = await mcp.getTools(userCredentials);\n\n const agentTools = [...tools, ...mcpTools]\n .filter(tool => {\n // If tools parameter is undefined, allow all tools\n if (enabledTools === undefined) return true;\n\n // If empty array, no tools should be enabled\n if (enabledTools.length === 0) return false;\n // Otherwise, only allow tools that are in the enabled list\n const enabled = enabledTools.find(\n enabledTool =>\n enabledTool.name === tool.name &&\n enabledTool.provider === tool.provider,\n );\n return !!enabled;\n })\n .map(tool => new DynamicStructuredTool(tool));\n\n const messagesWithoutSystem = messages.filter(m => m.role !== 'system');\n\n addMessages(\n messagesWithoutSystem,\n userEntityRef,\n conversationId,\n recentConversationMessages,\n );\n\n const systemPrompt = await systemPromptTemplate.formatMessages({\n basePrompt: combinedBasePrompt,\n toolGuideline,\n toolList: agentTools\n .map(tool => `- ${tool.name}: ${tool.description}`)\n .join('\\n'),\n context: `none`,\n user,\n });\n\n const agent = createReactAgent({\n llm: model,\n tools: agentTools,\n prompt: systemPrompt[0].text,\n });\n\n const { callbacks } = await callback.getChainCallbacks({\n conversationId,\n userId: userEntityRef,\n modelId,\n });\n\n const { metadata: promptMetadata } = await callback.getChainMetadata({\n conversationId,\n userId: userEntityRef,\n modelId,\n });\n\n const traceId = uuid();\n\n const promptStream = await agent.stream(\n {\n messages: [...recentConversationMessages, ...messages],\n },\n {\n streamMode: ['values'],\n runName: 'ai-assistant-chat',\n runId: traceId,\n metadata: promptMetadata,\n callbacks,\n },\n );\n\n const responseMessages: MessageWithRequiredFields[] = [];\n\n for await (const [, chunk] of promptStream) {\n const { messages: promptMessages } = chunk;\n\n const newMessages: MessageWithRequiredFields[] = promptMessages\n .filter(\n m =>\n responseMessages.findIndex(\n rm => m.id === rm.metadata.langGraphId,\n ) === -1,\n )\n .filter(\n m =>\n recentConversationMessages.findIndex(rm => rm.id === m.id) === -1,\n )\n .filter(m => m.getType() !== 'human')\n .map(m => {\n const id = uuid();\n const role = m.getType();\n const content =\n typeof m.content === 'string'\n ? m.content\n : JSON.stringify(m.content);\n\n const metadata: JsonObject = {\n langGraphId: m.id ?? '',\n };\n\n if (role === 'ai') {\n const aiMessage = m as AIMessage;\n metadata.toolCalls = aiMessage.tool_calls || [];\n metadata.finishReason =\n aiMessage.response_metadata.finish_reason || undefined;\n metadata.modelName =\n aiMessage.response_metadata.model_name || undefined;\n }\n\n if (role === 'tool') {\n const toolMessage = m as ToolMessage;\n metadata.name = toolMessage.name || '';\n }\n\n return {\n id,\n role,\n content,\n metadata,\n score: 0,\n traceId: traceId,\n };\n });\n\n // Simulate streaming until langchain messages error is better understood\n for await (const m of newMessages) {\n const words = m.content.split(' ');\n const chunkSize = 5; // Send 5 words at a time\n let messageBuilder = '';\n\n for (let i = 0; i < words.length; i += chunkSize) {\n const wordChunk = words.slice(i, i + chunkSize).join(' ');\n messageBuilder = messageBuilder.concat(wordChunk).concat(' ');\n m.content = messageBuilder;\n\n await new Promise(resolve => setTimeout(resolve, 50));\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-stream:${conversationId}`,\n message: { messages: [m] },\n recipients: {\n type: 'user',\n entityRef: userEntityRef,\n },\n });\n }\n }\n\n responseMessages.push(...newMessages);\n }\n\n addMessages(responseMessages, userEntityRef, conversationId, [\n ...recentConversationMessages,\n ...messages,\n ]);\n\n return responseMessages;\n };\n\n const result = streamFn();\n\n return stream ? [] : result;\n };\n\n const getAvailableModels: ChatService['getAvailableModels'] = async () => {\n return models.map(x => x.id);\n };\n\n const getConversation: ChatService['getConversation'] = async (\n options: GetConversationOptions,\n ) => {\n const { conversationId, userEntityRef } = options;\n\n const conversation = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n );\n\n return conversation;\n };\n\n const getConversations: ChatService['getConversations'] = async ({\n userEntityRef,\n }: GetConversationsOptions) => {\n const conversations = await chatStore.getConversations(userEntityRef);\n\n return conversations;\n };\n\n const scoreMessage: ChatService['scoreMessage'] = async (\n messageId: string,\n score: number,\n ) => {\n const message = await chatStore.getMessageById(messageId);\n\n if (!message) {\n throw new Error(`Message with id ${messageId} not found`);\n }\n\n const updatedMessage: Required<Message> = {\n ...message,\n score,\n };\n\n chatStore.updateMessage(updatedMessage);\n\n callback.handleScoreCallbacks({\n name: 'helpfulness',\n message: updatedMessage,\n });\n };\n\n const getAvailableTools: ChatService['getAvailableTools'] = async ({\n credentials,\n }) => {\n const mcpTools = await mcp.getTools(credentials);\n\n const availableTools: UserTool[] = tools.concat(mcpTools).map(tool => ({\n name: tool.name,\n provider: tool.provider,\n description: tool.description,\n }));\n\n return availableTools;\n };\n\n return {\n prompt,\n getAvailableModels,\n getConversation,\n getConversations,\n addMessages,\n scoreMessage,\n getAvailableTools,\n };\n};\n"],"names":["tools","DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","chatStore","ChatStore","SystemMessagePromptTemplate","conversation","getUser","DynamicStructuredTool","createReactAgent","uuid"],"mappings":"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"}
@@ -11,6 +11,9 @@ const DEFAULT_DATA_INGESTION_SCHEDULE = {
11
11
  hours: 3
12
12
  }
13
13
  };
14
+ const DEFAULT_MAX_CHUNK_PROCESSING_SIZE = 100;
15
+ const DEFAULT_CHUNK_SIZE = 1e3;
16
+ const DEFAULT_CHUNK_OVERLAP = 100;
14
17
  const createDataIngestionPipeline = ({
15
18
  config,
16
19
  logger,
@@ -21,6 +24,11 @@ const createDataIngestionPipeline = ({
21
24
  const schedule = config.has("aiAssistant.ingestion.schedule") ? backendPluginApi.readSchedulerServiceTaskScheduleDefinitionFromConfig(
22
25
  config.getConfig("aiAssistant.ingestion.schedule")
23
26
  ) : DEFAULT_DATA_INGESTION_SCHEDULE;
27
+ const chunkSize = config.getOptionalNumber("aiAssistant.ingestion.chunking.chunkSize") ?? DEFAULT_CHUNK_SIZE;
28
+ const chunkOverlap = config.getOptionalNumber("aiAssistant.ingestion.chunking.chunkOverlap") ?? DEFAULT_CHUNK_OVERLAP;
29
+ const maxChunkProcessingSize = config.getOptionalNumber(
30
+ "aiAssistant.ingestion.chunking.maxChunkProcessingSize"
31
+ ) ?? DEFAULT_MAX_CHUNK_PROCESSING_SIZE;
24
32
  const taskRunner = scheduler.createScheduledTaskRunner(schedule);
25
33
  const taskId = `ai-assistant.data-ingestion:start`;
26
34
  const dataIngestion = async () => {
@@ -37,12 +45,10 @@ const createDataIngestionPipeline = ({
37
45
  `Ingested documents for ${ingestor.id}: ${documents2.length}`
38
46
  );
39
47
  const splitter = new textsplitters.RecursiveCharacterTextSplitter({
40
- chunkSize: 500,
41
- // TODO: Make chunk size configurable
42
- chunkOverlap: 50
43
- // TODO: Make chunk overlap configurable
48
+ chunkSize,
49
+ chunkOverlap
44
50
  });
45
- const docs = await Promise.all(
51
+ const documentChunks = await Promise.all(
46
52
  documents2.map(async (document) => {
47
53
  logger.debug(
48
54
  `Deleting existing documents with id: [${document.metadata.id}] and source: [${ingestor.id}]`
@@ -51,17 +57,27 @@ const createDataIngestionPipeline = ({
51
57
  filter: { source: ingestor.id, id: document.metadata.id }
52
58
  });
53
59
  const chunks = await splitter.splitText(document.content);
54
- const chunkDocs = chunks.flatMap(
60
+ const docChunks = chunks.flatMap(
55
61
  (chunk, i) => ({
56
62
  metadata: { ...document.metadata, chunk: String(i) },
57
63
  content: chunk
58
64
  })
59
65
  );
60
- return chunkDocs;
66
+ return docChunks;
61
67
  })
62
68
  );
63
69
  logger.info(`Adding documents to vector store...`);
64
- await vectorStore.addDocuments(docs.flat());
70
+ const allChunks = documentChunks.flat();
71
+ logger.info(
72
+ `Total document chunks for batch to add for ${ingestor.id}: ${allChunks.length}`
73
+ );
74
+ for (let i = 0; i < allChunks.length; i += maxChunkProcessingSize) {
75
+ const chunkBatch = allChunks.slice(i, i + maxChunkProcessingSize);
76
+ logger.info(
77
+ `Adding batch of ${chunkBatch.length} document chunks to vector store for ${ingestor.id}`
78
+ );
79
+ await vectorStore.addDocuments(chunkBatch);
80
+ }
65
81
  logger.info(`Added documents to vector store for ${ingestor.id}`);
66
82
  };
67
83
  const documents = await ingestor.ingest({
@@ -1 +1 @@
1
- {"version":3,"file":"ingestor.cjs.js","sources":["../../src/services/ingestor.ts"],"sourcesContent":["import {\n DataIngestionPipeline,\n DataIngestionPipelineOptions,\n EmbeddingDocument,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nimport {\n SchedulerServiceTaskScheduleDefinition,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n} from '@backstage/backend-plugin-api';\n\nimport { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';\n\nconst DEFAULT_DATA_INGESTION_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: {\n hours: 24,\n },\n timeout: {\n hours: 3,\n },\n };\n\nexport const createDataIngestionPipeline = ({\n config,\n logger,\n scheduler,\n ingestors,\n vectorStore,\n}: DataIngestionPipelineOptions): DataIngestionPipeline => {\n const schedule = config.has('aiAssistant.ingestion.schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('aiAssistant.ingestion.schedule'),\n )\n : DEFAULT_DATA_INGESTION_SCHEDULE;\n\n const taskRunner = scheduler.createScheduledTaskRunner(schedule);\n\n const taskId = `ai-assistant.data-ingestion:start`;\n\n const dataIngestion = async () => {\n logger.info('Starting data ingestion...');\n\n if (ingestors.length === 0) {\n logger.warn('No ingestors available for data ingestion.');\n return;\n }\n\n logger.info(`Ingestors available: ${ingestors.map(i => i.id).join(', ')}`);\n\n for await (const ingestor of ingestors) {\n logger.info(`Running ingestor: ${ingestor.id}`);\n\n const saveDocumentsBatch = async (documents: EmbeddingDocument[]) => {\n logger.info(\n `Ingested documents for ${ingestor.id}: ${documents.length}`,\n );\n\n const splitter = new RecursiveCharacterTextSplitter({\n chunkSize: 500, // TODO: Make chunk size configurable\n chunkOverlap: 50, // TODO: Make chunk overlap configurable\n });\n\n const docs = await Promise.all(\n documents.map(async document => {\n // Delete existing documents with this document id and ingestor source\n logger.debug(\n `Deleting existing documents with id: [${document.metadata.id}] and source: [${ingestor.id}]`,\n );\n await vectorStore.deleteDocuments({\n filter: { source: ingestor.id, id: document.metadata.id },\n });\n\n const chunks = await splitter.splitText(document.content);\n\n const chunkDocs: EmbeddingDocument[] = chunks.flatMap(\n (chunk, i) => ({\n metadata: { ...document.metadata, chunk: String(i) },\n content: chunk,\n }),\n );\n\n return chunkDocs;\n }),\n );\n\n logger.info(`Adding documents to vector store...`);\n await vectorStore.addDocuments(docs.flat());\n logger.info(`Added documents to vector store for ${ingestor.id}`);\n };\n\n const documents = await ingestor.ingest({\n saveDocumentsBatch,\n });\n\n if (documents) {\n saveDocumentsBatch(documents);\n }\n\n logger.info(`Finished processing ingestor: ${ingestor.id}`);\n }\n\n logger.info('Data ingestion completed.');\n };\n\n const start = async () => {\n taskRunner.run({\n id: taskId,\n fn: dataIngestion,\n });\n };\n\n return {\n start,\n };\n};\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","documents","RecursiveCharacterTextSplitter"],"mappings":"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"}
1
+ {"version":3,"file":"ingestor.cjs.js","sources":["../../src/services/ingestor.ts"],"sourcesContent":["import {\n DataIngestionPipeline,\n DataIngestionPipelineOptions,\n EmbeddingDocument,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nimport {\n SchedulerServiceTaskScheduleDefinition,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n} from '@backstage/backend-plugin-api';\n\nimport { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';\n\nconst DEFAULT_DATA_INGESTION_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: {\n hours: 24,\n },\n timeout: {\n hours: 3,\n },\n };\n\nconst DEFAULT_MAX_CHUNK_PROCESSING_SIZE = 100;\nconst DEFAULT_CHUNK_SIZE = 1000;\nconst DEFAULT_CHUNK_OVERLAP = 100;\n\nexport const createDataIngestionPipeline = ({\n config,\n logger,\n scheduler,\n ingestors,\n vectorStore,\n}: DataIngestionPipelineOptions): DataIngestionPipeline => {\n const schedule = config.has('aiAssistant.ingestion.schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('aiAssistant.ingestion.schedule'),\n )\n : DEFAULT_DATA_INGESTION_SCHEDULE;\n\n const chunkSize =\n config.getOptionalNumber('aiAssistant.ingestion.chunking.chunkSize') ??\n DEFAULT_CHUNK_SIZE;\n\n const chunkOverlap =\n config.getOptionalNumber('aiAssistant.ingestion.chunking.chunkOverlap') ??\n DEFAULT_CHUNK_OVERLAP;\n\n const maxChunkProcessingSize =\n config.getOptionalNumber(\n 'aiAssistant.ingestion.chunking.maxChunkProcessingSize',\n ) ?? DEFAULT_MAX_CHUNK_PROCESSING_SIZE;\n\n const taskRunner = scheduler.createScheduledTaskRunner(schedule);\n\n const taskId = `ai-assistant.data-ingestion:start`;\n\n const dataIngestion = async () => {\n logger.info('Starting data ingestion...');\n\n if (ingestors.length === 0) {\n logger.warn('No ingestors available for data ingestion.');\n return;\n }\n\n logger.info(`Ingestors available: ${ingestors.map(i => i.id).join(', ')}`);\n\n for await (const ingestor of ingestors) {\n logger.info(`Running ingestor: ${ingestor.id}`);\n\n const saveDocumentsBatch = async (documents: EmbeddingDocument[]) => {\n logger.info(\n `Ingested documents for ${ingestor.id}: ${documents.length}`,\n );\n\n const splitter = new RecursiveCharacterTextSplitter({\n chunkSize,\n chunkOverlap,\n });\n\n const documentChunks = await Promise.all(\n documents.map(async document => {\n // Delete existing documents with this document id and ingestor source\n logger.debug(\n `Deleting existing documents with id: [${document.metadata.id}] and source: [${ingestor.id}]`,\n );\n await vectorStore.deleteDocuments({\n filter: { source: ingestor.id, id: document.metadata.id },\n });\n\n const chunks = await splitter.splitText(document.content);\n\n const docChunks: EmbeddingDocument[] = chunks.flatMap(\n (chunk, i) => ({\n metadata: { ...document.metadata, chunk: String(i) },\n content: chunk,\n }),\n );\n\n return docChunks;\n }),\n );\n\n logger.info(`Adding documents to vector store...`);\n const allChunks = documentChunks.flat();\n\n logger.info(\n `Total document chunks for batch to add for ${ingestor.id}: ${allChunks.length}`,\n );\n\n for (let i = 0; i < allChunks.length; i += maxChunkProcessingSize) {\n const chunkBatch = allChunks.slice(i, i + maxChunkProcessingSize);\n logger.info(\n `Adding batch of ${chunkBatch.length} document chunks to vector store for ${ingestor.id}`,\n );\n\n await vectorStore.addDocuments(chunkBatch);\n }\n\n logger.info(`Added documents to vector store for ${ingestor.id}`);\n };\n\n const documents = await ingestor.ingest({\n saveDocumentsBatch,\n });\n\n if (documents) {\n saveDocumentsBatch(documents);\n }\n\n logger.info(`Finished processing ingestor: ${ingestor.id}`);\n }\n\n logger.info('Data ingestion completed.');\n };\n\n const start = async () => {\n taskRunner.run({\n id: taskId,\n fn: dataIngestion,\n });\n };\n\n return {\n start,\n };\n};\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","documents","RecursiveCharacterTextSplitter"],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
4
+
5
+ const convertToAssistantTool = (mcpTool, serverName) => {
6
+ const { name, description, schema } = mcpTool;
7
+ const provider = `mcp server:${serverName}`;
8
+ const tool = {
9
+ name,
10
+ provider,
11
+ description,
12
+ schema,
13
+ func: async (params) => {
14
+ const result = await mcpTool.invoke(params);
15
+ return {
16
+ content: JSON.stringify(result)
17
+ };
18
+ }
19
+ };
20
+ return backstagePluginAiAssistantNode.createAssistantTool({ tool });
21
+ };
22
+ const getToolsForServer = async (mcpClient, serverName) => {
23
+ const mcpTools = await mcpClient.getTools(serverName);
24
+ const assistantTools = mcpTools.map(
25
+ (mcpTool) => convertToAssistantTool(mcpTool, serverName)
26
+ );
27
+ return assistantTools;
28
+ };
29
+
30
+ exports.getToolsForServer = getToolsForServer;
31
+ //# sourceMappingURL=helpers.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.cjs.js","sources":["../../../src/services/mcp/helpers.ts"],"sourcesContent":["import { DynamicStructuredTool } from '@langchain/core/tools';\nimport { MultiServerMCPClient } from '@langchain/mcp-adapters';\nimport { Tool } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { createAssistantTool } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nconst convertToAssistantTool = (\n mcpTool: DynamicStructuredTool,\n serverName: string,\n): Tool => {\n const { name, description, schema } = mcpTool;\n const provider = `mcp server:${serverName}`;\n\n const tool = {\n name,\n provider,\n description,\n schema: schema as Tool['schema'],\n func: async (params: any) => {\n const result = await mcpTool.invoke(params);\n return {\n content: JSON.stringify(result),\n };\n },\n };\n\n return createAssistantTool({ tool });\n};\n\nexport const getToolsForServer = async (\n mcpClient: MultiServerMCPClient,\n serverName: string,\n): Promise<Tool[]> => {\n const mcpTools = await mcpClient.getTools(serverName);\n\n const assistantTools = mcpTools.map(mcpTool =>\n convertToAssistantTool(mcpTool, serverName),\n );\n return assistantTools;\n};\n"],"names":["createAssistantTool"],"mappings":";;;;AAKA,MAAM,sBAAA,GAAyB,CAC7B,OAAA,EACA,UAAA,KACS;AACT,EAAA,MAAM,EAAE,IAAA,EAAM,WAAA,EAAa,MAAA,EAAO,GAAI,OAAA;AACtC,EAAA,MAAM,QAAA,GAAW,cAAc,UAAU,CAAA,CAAA;AAEzC,EAAA,MAAM,IAAA,GAAO;AAAA,IACX,IAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA,EAAM,OAAO,MAAA,KAAgB;AAC3B,MAAA,MAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,MAAA,CAAO,MAAM,CAAA;AAC1C,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,IAAA,CAAK,SAAA,CAAU,MAAM;AAAA,OAChC;AAAA,IACF;AAAA,GACF;AAEA,EAAA,OAAOA,kDAAA,CAAoB,EAAE,IAAA,EAAM,CAAA;AACrC,CAAA;AAEO,MAAM,iBAAA,GAAoB,OAC/B,SAAA,EACA,UAAA,KACoB;AACpB,EAAA,MAAM,QAAA,GAAW,MAAM,SAAA,CAAU,QAAA,CAAS,UAAU,CAAA;AAEpD,EAAA,MAAM,iBAAiB,QAAA,CAAS,GAAA;AAAA,IAAI,CAAA,OAAA,KAClC,sBAAA,CAAuB,OAAA,EAAS,UAAU;AAAA,GAC5C;AACA,EAAA,OAAO,cAAA;AACT;;;;"}
@@ -0,0 +1,150 @@
1
+ 'use strict';
2
+
3
+ var mcpAdapters = require('@langchain/mcp-adapters');
4
+ var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
5
+ var helpers = require('./helpers.cjs.js');
6
+ var userSettingsStore = require('../../database/user-settings-store.cjs.js');
7
+
8
+ const MCP_SETTINGS_TYPE = "mcp_server_config";
9
+ const createMcpService = async ({
10
+ config,
11
+ userInfo,
12
+ database
13
+ }) => {
14
+ const serversConfig = config.getOptionalConfigArray(
15
+ "aiAssistant.mcp.servers"
16
+ );
17
+ const encryptionKey = config.getString("aiAssistant.mcp.encryptionKey");
18
+ const preConfiguredMcpServers = serversConfig ? serversConfig.reduce((acc, server) => {
19
+ const serverName = server.getString("name");
20
+ const options = server.get("options");
21
+ acc[serverName] = options;
22
+ return acc;
23
+ }, {}) : {};
24
+ const userSettingsStore$1 = await userSettingsStore.UserSettingsStore.fromConfig({ database });
25
+ const getUserMcpServerConfigNames = async (credentials) => {
26
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
27
+ const mcpConfig = await userSettingsStore$1.getUserSettingsByType(
28
+ userEntityRef,
29
+ MCP_SETTINGS_TYPE
30
+ );
31
+ if (!mcpConfig) {
32
+ return [];
33
+ }
34
+ const names = Object.keys(mcpConfig);
35
+ return names;
36
+ };
37
+ const getUserMcpServerConfig = async (credentials) => {
38
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
39
+ const mcpConfigEncrypted = await userSettingsStore$1.getUserSettingsByType(userEntityRef, MCP_SETTINGS_TYPE);
40
+ if (!mcpConfigEncrypted) {
41
+ return [];
42
+ }
43
+ const mcpConfig = Object.entries(mcpConfigEncrypted).map(
44
+ ([name, data]) => ({
45
+ name,
46
+ options: JSON.parse(backstagePluginAiAssistantNode.decrypt(data, encryptionKey))
47
+ })
48
+ );
49
+ return mcpConfig;
50
+ };
51
+ const getTools = async (credentials) => {
52
+ const userMcpConfig = await getUserMcpServerConfig(credentials);
53
+ const userMcpServers = userMcpConfig.length ? userMcpConfig.reduce((acc, server) => {
54
+ const { name, options } = server;
55
+ acc[name] = options;
56
+ return acc;
57
+ }, {}) : {};
58
+ const mcpServers = {
59
+ ...preConfiguredMcpServers,
60
+ ...userMcpServers
61
+ };
62
+ const serverNames = Object.keys(mcpServers);
63
+ if (serverNames.length === 0) {
64
+ return [];
65
+ }
66
+ const mcpClient = new mcpAdapters.MultiServerMCPClient({
67
+ prefixToolNameWithServerName: true,
68
+ useStandardContentBlocks: true,
69
+ mcpServers
70
+ });
71
+ const serverToolPromises = serverNames.map(
72
+ (serverName) => helpers.getToolsForServer(mcpClient, serverName)
73
+ );
74
+ const toolsByServer = await Promise.all(serverToolPromises);
75
+ return toolsByServer.flat();
76
+ };
77
+ const validateMcpServerConfig = async (mcpConfig) => {
78
+ try {
79
+ const userMcpServers = mcpConfig.reduce((acc, server) => {
80
+ const { name, options } = server;
81
+ acc[name] = options;
82
+ return acc;
83
+ }, {});
84
+ const userConfigClient = new mcpAdapters.MultiServerMCPClient({
85
+ prefixToolNameWithServerName: true,
86
+ useStandardContentBlocks: true,
87
+ mcpServers: userMcpServers
88
+ });
89
+ await userConfigClient.getTools();
90
+ } catch (e) {
91
+ const error = new Error("Invalid MCP server configuration");
92
+ error.name = "McpConfigurationError";
93
+ throw error;
94
+ }
95
+ };
96
+ const setUserMcpServerConfig = async (credentials, mcpConfig) => {
97
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
98
+ const { name } = mcpConfig;
99
+ const existingConfig = await getUserMcpServerConfig(credentials);
100
+ const existingServerIndex = existingConfig.findIndex(
101
+ (server) => server.name === name
102
+ );
103
+ if (existingServerIndex === -1) {
104
+ existingConfig.push(mcpConfig);
105
+ } else {
106
+ existingConfig[existingServerIndex] = mcpConfig;
107
+ }
108
+ await validateMcpServerConfig(existingConfig);
109
+ const updatedConfig = existingConfig.reduce(
110
+ (acc, server) => {
111
+ acc[server.name] = backstagePluginAiAssistantNode.encrypt(
112
+ JSON.stringify(server.options),
113
+ encryptionKey
114
+ );
115
+ return acc;
116
+ },
117
+ {}
118
+ );
119
+ await userSettingsStore$1.setUserSettings(
120
+ userEntityRef,
121
+ MCP_SETTINGS_TYPE,
122
+ updatedConfig
123
+ );
124
+ };
125
+ const deleteUserMcpServerConfig = async (credentials, name) => {
126
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
127
+ const existingConfig = await getUserMcpServerConfig(credentials);
128
+ const updatedConfig = existingConfig.filter((server) => server.name !== name).reduce((acc, server) => {
129
+ acc[server.name] = backstagePluginAiAssistantNode.encrypt(
130
+ JSON.stringify(server.options),
131
+ encryptionKey
132
+ );
133
+ return acc;
134
+ }, {});
135
+ await userSettingsStore$1.setUserSettings(
136
+ userEntityRef,
137
+ MCP_SETTINGS_TYPE,
138
+ updatedConfig
139
+ );
140
+ };
141
+ return {
142
+ getTools,
143
+ getUserMcpServerConfigNames,
144
+ deleteUserMcpServerConfig,
145
+ setUserMcpServerConfig
146
+ };
147
+ };
148
+
149
+ exports.createMcpService = createMcpService;
150
+ //# sourceMappingURL=index.cjs.js.map