@sweetoburrito/backstage-plugin-ai-assistant-backend 0.0.0-snapshot-20251029080430 → 0.0.0-snapshot-20251029150521

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/config.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { HumanDuration } from '@backstage/types';
2
2
  import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api';
3
+ import { McpServerConfig } from '@sweetoburrito/backstage-plugin-ai-assistant-common';
3
4
 
4
5
  export interface Config {
5
6
  aiAssistant: {
@@ -28,5 +29,12 @@ export interface Config {
28
29
  ingestion?: {
29
30
  schedule?: SchedulerServiceTaskScheduleDefinitionConfig;
30
31
  };
32
+ mcp: {
33
+ /**
34
+ * @visibility secret
35
+ */
36
+ encryptionKey: string;
37
+ servers?: Array<McpServerConfig>;
38
+ };
31
39
  };
32
40
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const DEFAULT_SUMMARY_PROMPT = "Summarize this conversation in a concise manner. The summary should capture the main points. Return the summary only, without any additional text.";
3
+ const DEFAULT_SUMMARY_PROMPT = "Summarize this conversation in a concise manner. The summary should capture the main points. Return the summary only, without any additional text. Do not include any introductions or other part of the conversation that doesn't contribute to the summary or form part of the overall conversation as part of the summary.";
4
4
  const DEFAULT_IDENTITY_PROMPT = `
5
5
  You are a helpful assistant that answers questions based on provided context from various documents. The context may come from sources such as internal wikis, code repositories, technical documentation, or other structured or unstructured data.
6
6
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.cjs.js","sources":["../../src/constants/prompts.ts"],"sourcesContent":["export const DEFAULT_SUMMARY_PROMPT =\n 'Summarize this conversation in a concise manner. The summary should capture the main points. Return the summary only, without any additional text.';\n\nexport const DEFAULT_IDENTITY_PROMPT = `\nYou are a helpful assistant that answers questions based on provided context from various documents. The context may come from sources such as internal wikis, code repositories, technical documentation, or other structured or unstructured data.\n`;\n\nexport const DEFAULT_FORMATTING_PROMPT = `\nCRITICAL FORMATTING RULES - MUST ALWAYS FOLLOW:\n1. **ALWAYS use proper markdown formatting in ALL responses**\n2. **NEVER output plain URLs** - ALWAYS convert them to clickable markdown links using [description](url) syntax\n3. **For images, ALWAYS use markdown image syntax**: ![alt text](image-url)\n4. **For all URLs, ALWAYS format as**: [descriptive text](url) - never just paste the raw URL\n5. Use headings (##, ###), bullet points, numbered lists, and **bold**/*italic* text appropriately\n6. Format code with backticks: \\`inline code\\` or \\`\\`\\`language for code blocks\n7. Structure responses clearly with proper spacing and organization\n`;\n\nexport const DEFAULT_SYSTEM_PROMPT = `\nContent Rules:\n1. Always base your answers on the provided context. Do not make up information.\n2. When relevant, cite or reference the source information provided in the context.\n3. Maintain a professional, friendly, and helpful tone.\n4. Return only the relevant information without any filler or unnecessary details.\n5. If you don't know the answer, admit it and suggest ways to find the information.\n6. **Actively use available tools** to enhance your responses\n7. Adapt your approach based on the specific tools and capabilities available in the current session\n8. When you do not have the information needed to answer, use the tools provided to gather more context before responding.\n`;\n\nexport const DEFAULT_TOOL_GUIDELINE = `\nTOOL USAGE GUIDELINES:\n- Only use tools when explicitly needed to answer the user's question\n- Read tool descriptions carefully before using them\n- If you can answer without tools, do so\n- IMPORTANT: When using tools, always explain why you're using each tool\n- Use tools in logical sequence, not randomly\n- If a tool fails, try an alternative approach before using another tool\n`;\n"],"names":[],"mappings":";;AAAO,MAAM,sBAAA,GACX;AAEK,MAAM,uBAAA,GAA0B;AAAA;AAAA;AAIhC,MAAM,yBAAA,GAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWlC,MAAM,qBAAA,GAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAY9B,MAAM,sBAAA,GAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;;;"}
1
+ {"version":3,"file":"prompts.cjs.js","sources":["../../src/constants/prompts.ts"],"sourcesContent":["export const DEFAULT_SUMMARY_PROMPT =\n \"Summarize this conversation in a concise manner. The summary should capture the main points. Return the summary only, without any additional text. Do not include any introductions or other part of the conversation that doesn't contribute to the summary or form part of the overall conversation as part of the summary.\";\n\nexport const DEFAULT_IDENTITY_PROMPT = `\nYou are a helpful assistant that answers questions based on provided context from various documents. The context may come from sources such as internal wikis, code repositories, technical documentation, or other structured or unstructured data.\n`;\n\nexport const DEFAULT_FORMATTING_PROMPT = `\nCRITICAL FORMATTING RULES - MUST ALWAYS FOLLOW:\n1. **ALWAYS use proper markdown formatting in ALL responses**\n2. **NEVER output plain URLs** - ALWAYS convert them to clickable markdown links using [description](url) syntax\n3. **For images, ALWAYS use markdown image syntax**: ![alt text](image-url)\n4. **For all URLs, ALWAYS format as**: [descriptive text](url) - never just paste the raw URL\n5. Use headings (##, ###), bullet points, numbered lists, and **bold**/*italic* text appropriately\n6. Format code with backticks: \\`inline code\\` or \\`\\`\\`language for code blocks\n7. Structure responses clearly with proper spacing and organization\n`;\n\nexport const DEFAULT_SYSTEM_PROMPT = `\nContent Rules:\n1. Always base your answers on the provided context. Do not make up information.\n2. When relevant, cite or reference the source information provided in the context.\n3. Maintain a professional, friendly, and helpful tone.\n4. Return only the relevant information without any filler or unnecessary details.\n5. If you don't know the answer, admit it and suggest ways to find the information.\n6. **Actively use available tools** to enhance your responses\n7. Adapt your approach based on the specific tools and capabilities available in the current session\n8. When you do not have the information needed to answer, use the tools provided to gather more context before responding.\n`;\n\nexport const DEFAULT_TOOL_GUIDELINE = `\nTOOL USAGE GUIDELINES:\n- Only use tools when explicitly needed to answer the user's question\n- Read tool descriptions carefully before using them\n- If you can answer without tools, do so\n- IMPORTANT: When using tools, always explain why you're using each tool\n- Use tools in logical sequence, not randomly\n- If a tool fails, try an alternative approach before using another tool\n`;\n"],"names":[],"mappings":";;AAAO,MAAM,sBAAA,GACX;AAEK,MAAM,uBAAA,GAA0B;AAAA;AAAA;AAIhC,MAAM,yBAAA,GAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWlC,MAAM,qBAAA,GAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAY9B,MAAM,sBAAA,GAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;;;;;;"}
@@ -33,7 +33,9 @@ class ChatStore {
33
33
  role: row.role,
34
34
  content: row.content,
35
35
  id: row.id,
36
- metadata: row.metadata
36
+ metadata: row.metadata,
37
+ score: row.score,
38
+ traceId: row.trace_id
37
39
  }));
38
40
  return chatMessages;
39
41
  }
@@ -44,6 +46,7 @@ class ChatStore {
44
46
  role: msg.role,
45
47
  content: msg.content,
46
48
  metadata: msg.metadata,
49
+ trace_id: msg.traceId,
47
50
  userRef,
48
51
  created_at: this.client.fn.now()
49
52
  }));
@@ -53,7 +56,9 @@ class ChatStore {
53
56
  await this.messageTable().where({ id: message.id }).update({
54
57
  role: message.role,
55
58
  content: message.content,
56
- metadata: message.metadata
59
+ metadata: message.metadata,
60
+ score: message.score,
61
+ trace_id: message.traceId
57
62
  });
58
63
  }
59
64
  async getConversation(conversationId, userRef) {
@@ -90,6 +95,21 @@ class ChatStore {
90
95
  }));
91
96
  return conversations;
92
97
  }
98
+ async getMessageById(messageId) {
99
+ const row = await this.messageTable().where({ id: messageId }).first();
100
+ if (!row) {
101
+ return null;
102
+ }
103
+ const message = {
104
+ id: row.id,
105
+ role: row.role,
106
+ content: row.content,
107
+ metadata: row.metadata,
108
+ score: row.score,
109
+ traceId: row.trace_id
110
+ };
111
+ return message;
112
+ }
93
113
  }
94
114
 
95
115
  exports.ChatStore = ChatStore;
@@ -1 +1 @@
1
- {"version":3,"file":"chat-store.cjs.js","sources":["../../src/database/chat-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport {\n Message,\n Conversation,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { Knex } from 'knex';\n\nconst MESSAGE_TABLE_NAME = 'message';\nconst CONVERSATION_TABLE_NAME = 'conversation';\n\nexport type ChatStoreOptions = {\n database: DatabaseService;\n};\n\nexport class ChatStore {\n /**\n * Creates an instance of ChatStore.\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 }: ChatStoreOptions) {\n const client = await database.getClient();\n return new ChatStore(client);\n }\n\n messageTable() {\n return this.client(MESSAGE_TABLE_NAME);\n }\n\n conversationTable() {\n return this.client(CONVERSATION_TABLE_NAME);\n }\n\n async getChatMessages(\n conversationId: string,\n userRef: string,\n limit?: number,\n excludeRoles?: Message['role'][],\n ): Promise<Required<Message>[]> {\n let query = this.messageTable()\n .where({ conversation_id: conversationId, userRef })\n .select('*')\n .orderBy('created_at', 'asc');\n\n if (typeof limit === 'number') {\n query = query.limit(limit).orderBy('created_at', 'desc');\n }\n\n if (excludeRoles && excludeRoles.length > 0) {\n query = query.whereNotIn('role', excludeRoles);\n }\n\n const rows = await query;\n\n const chatMessages: Required<Message>[] = rows.map(row => ({\n role: row.role,\n content: row.content,\n id: row.id,\n metadata: row.metadata,\n }));\n\n return chatMessages;\n }\n\n async addChatMessage(\n messages: Message[],\n userRef: string,\n conversationId: string,\n ): Promise<void> {\n const rows = messages.map(msg => ({\n id: msg.id,\n conversation_id: conversationId,\n role: msg.role,\n content: msg.content,\n metadata: msg.metadata,\n userRef,\n created_at: this.client.fn.now(),\n }));\n\n await this.messageTable().insert(rows);\n }\n\n async updateMessage(message: Required<Message>) {\n await this.messageTable().where({ id: message.id }).update({\n role: message.role,\n content: message.content,\n metadata: message.metadata,\n });\n }\n\n async getConversation(\n conversationId: string,\n userRef: string,\n ): Promise<Conversation> {\n const row = await this.conversationTable()\n .where({ id: conversationId, userRef })\n .first();\n\n if (!row) {\n throw new Error('Conversation not found');\n }\n\n const conversation: Conversation = {\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n };\n\n return conversation;\n }\n\n async createConversation(conversation: Conversation) {\n await this.conversationTable().insert({\n id: conversation.id,\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async updateConversation(conversation: Conversation) {\n await this.conversationTable().where({ id: conversation.id }).update({\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async getConversations(userRef: string): Promise<Conversation[]> {\n const rows = await this.conversationTable()\n .where({ userRef })\n .select('*')\n .orderBy('created_at', 'desc');\n\n const conversations: Conversation[] = rows.map(row => ({\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n }));\n\n return conversations;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-store.cjs.js","sources":["../../src/database/chat-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\nimport {\n Message,\n Conversation,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { Knex } from 'knex';\n\nconst MESSAGE_TABLE_NAME = 'message';\nconst CONVERSATION_TABLE_NAME = 'conversation';\n\nexport type ChatStoreOptions = {\n database: DatabaseService;\n};\n\nexport class ChatStore {\n /**\n * Creates an instance of ChatStore.\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 }: ChatStoreOptions) {\n const client = await database.getClient();\n return new ChatStore(client);\n }\n\n messageTable() {\n return this.client(MESSAGE_TABLE_NAME);\n }\n\n conversationTable() {\n return this.client(CONVERSATION_TABLE_NAME);\n }\n\n async getChatMessages(\n conversationId: string,\n userRef: string,\n limit?: number,\n excludeRoles?: Message['role'][],\n ): Promise<Required<Message>[]> {\n let query = this.messageTable()\n .where({ conversation_id: conversationId, userRef })\n .select('*')\n .orderBy('created_at', 'asc');\n\n if (typeof limit === 'number') {\n query = query.limit(limit).orderBy('created_at', 'desc');\n }\n\n if (excludeRoles && excludeRoles.length > 0) {\n query = query.whereNotIn('role', excludeRoles);\n }\n\n const rows = await query;\n\n const chatMessages: Required<Message>[] = rows.map(row => ({\n role: row.role,\n content: row.content,\n id: row.id,\n metadata: row.metadata,\n score: row.score,\n traceId: row.trace_id,\n }));\n\n return chatMessages;\n }\n\n async addChatMessage(\n messages: Message[],\n userRef: string,\n conversationId: string,\n ): Promise<void> {\n const rows = messages.map(msg => ({\n id: msg.id,\n conversation_id: conversationId,\n role: msg.role,\n content: msg.content,\n metadata: msg.metadata,\n trace_id: msg.traceId,\n userRef,\n created_at: this.client.fn.now(),\n }));\n\n await this.messageTable().insert(rows);\n }\n\n async updateMessage(message: Required<Message>) {\n await this.messageTable().where({ id: message.id }).update({\n role: message.role,\n content: message.content,\n metadata: message.metadata,\n score: message.score,\n trace_id: message.traceId,\n });\n }\n\n async getConversation(\n conversationId: string,\n userRef: string,\n ): Promise<Conversation> {\n const row = await this.conversationTable()\n .where({ id: conversationId, userRef })\n .first();\n\n if (!row) {\n throw new Error('Conversation not found');\n }\n\n const conversation: Conversation = {\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n };\n\n return conversation;\n }\n\n async createConversation(conversation: Conversation) {\n await this.conversationTable().insert({\n id: conversation.id,\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async updateConversation(conversation: Conversation) {\n await this.conversationTable().where({ id: conversation.id }).update({\n title: conversation.title,\n userRef: conversation.userRef,\n });\n }\n\n async getConversations(userRef: string): Promise<Conversation[]> {\n const rows = await this.conversationTable()\n .where({ userRef })\n .select('*')\n .orderBy('created_at', 'desc');\n\n const conversations: Conversation[] = rows.map(row => ({\n id: row.id,\n title: row.title,\n userRef: row.userRef,\n }));\n\n return conversations;\n }\n\n async getMessageById(messageId: string): Promise<Required<Message> | null> {\n const row = await this.messageTable().where({ id: messageId }).first();\n\n if (!row) {\n return null;\n }\n\n const message: Required<Message> = {\n id: row.id,\n role: row.role,\n content: row.content,\n metadata: row.metadata,\n score: row.score,\n traceId: row.trace_id,\n };\n\n return message;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const MCP_TABLE_NAME = "user_mcp_config";
4
+ class McpStore {
5
+ /**
6
+ * Creates an instance of ChatStore.
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 McpStore(client);
15
+ }
16
+ mcpTable() {
17
+ return this.client(MCP_TABLE_NAME);
18
+ }
19
+ async getUserUserMcpConfigNames(userRef) {
20
+ const rows = await this.mcpTable().where({ userRef }).select("name");
21
+ return rows.map((row) => row.name);
22
+ }
23
+ async getUserMcpConfigs(userRef) {
24
+ const rows = await this.mcpTable().where({ userRef }).select();
25
+ return rows.map((row) => ({
26
+ name: row.name,
27
+ encryptedOptions: row.encryptedOptions
28
+ }));
29
+ }
30
+ async createUserMcpConfig(userRef, name, encryptedOptions) {
31
+ await this.mcpTable().insert({
32
+ userRef,
33
+ name,
34
+ encryptedOptions
35
+ });
36
+ }
37
+ async updateUserMcpConfig(userRef, name, encryptedOptions) {
38
+ await this.mcpTable().where({ userRef, name }).update({ encryptedOptions });
39
+ }
40
+ async deleteUserMcpConfig(userRef, name) {
41
+ await this.mcpTable().where({ userRef, name }).delete();
42
+ }
43
+ }
44
+
45
+ exports.McpStore = McpStore;
46
+ //# sourceMappingURL=mcp-store.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-store.cjs.js","sources":["../../src/database/mcp-store.ts"],"sourcesContent":["import { DatabaseService } from '@backstage/backend-plugin-api';\n\nimport { Knex } from 'knex';\n\nconst MCP_TABLE_NAME = 'user_mcp_config';\n\nexport type McpStoreOptions = {\n database: DatabaseService;\n};\n\nexport class McpStore {\n /**\n * Creates an instance of ChatStore.\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 }: McpStoreOptions) {\n const client = await database.getClient();\n return new McpStore(client);\n }\n\n mcpTable() {\n return this.client(MCP_TABLE_NAME);\n }\n\n async getUserUserMcpConfigNames(userRef: string): Promise<string[]> {\n const rows = await this.mcpTable().where({ userRef }).select('name');\n return rows.map(row => row.name);\n }\n\n async getUserMcpConfigs(\n userRef: string,\n ): Promise<Array<{ name: string; encryptedOptions: string }>> {\n const rows = await this.mcpTable().where({ userRef }).select();\n return rows.map(row => ({\n name: row.name,\n encryptedOptions: row.encryptedOptions,\n }));\n }\n\n async createUserMcpConfig(\n userRef: string,\n name: string,\n encryptedOptions: string,\n ): Promise<void> {\n await this.mcpTable().insert({\n userRef,\n name,\n encryptedOptions,\n });\n }\n\n async updateUserMcpConfig(\n userRef: string,\n name: string,\n encryptedOptions: string,\n ): Promise<void> {\n await this.mcpTable().where({ userRef, name }).update({ encryptedOptions });\n }\n\n async deleteUserMcpConfig(userRef: string, name: string): Promise<void> {\n await this.mcpTable().where({ userRef, name }).delete();\n }\n}\n"],"names":[],"mappings":";;AAIA,MAAM,cAAA,GAAiB,iBAAA;AAMhB,MAAM,QAAA,CAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpB,YAA6B,MAAA,EAAc;AAAd,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAe;AAAA,EAE5C,aAAa,UAAA,CAAW,EAAE,QAAA,EAAS,EAAoB;AACrD,IAAA,MAAM,MAAA,GAAS,MAAM,QAAA,CAAS,SAAA,EAAU;AACxC,IAAA,OAAO,IAAI,SAAS,MAAM,CAAA;AAAA,EAC5B;AAAA,EAEA,QAAA,GAAW;AACT,IAAA,OAAO,IAAA,CAAK,OAAO,cAAc,CAAA;AAAA,EACnC;AAAA,EAEA,MAAM,0BAA0B,OAAA,EAAoC;AAClE,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,QAAA,EAAS,CAAE,KAAA,CAAM,EAAE,OAAA,EAAS,CAAA,CAAE,MAAA,CAAO,MAAM,CAAA;AACnE,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,KAAO,GAAA,CAAI,IAAI,CAAA;AAAA,EACjC;AAAA,EAEA,MAAM,kBACJ,OAAA,EAC4D;AAC5D,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,QAAA,EAAS,CAAE,MAAM,EAAE,OAAA,EAAS,CAAA,CAAE,MAAA,EAAO;AAC7D,IAAA,OAAO,IAAA,CAAK,IAAI,CAAA,GAAA,MAAQ;AAAA,MACtB,MAAM,GAAA,CAAI,IAAA;AAAA,MACV,kBAAkB,GAAA,CAAI;AAAA,KACxB,CAAE,CAAA;AAAA,EACJ;AAAA,EAEA,MAAM,mBAAA,CACJ,OAAA,EACA,IAAA,EACA,gBAAA,EACe;AACf,IAAA,MAAM,IAAA,CAAK,QAAA,EAAS,CAAE,MAAA,CAAO;AAAA,MAC3B,OAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACD,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,mBAAA,CACJ,OAAA,EACA,IAAA,EACA,gBAAA,EACe;AACf,IAAA,MAAM,IAAA,CAAK,QAAA,EAAS,CAAE,KAAA,CAAM,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,MAAA,CAAO,EAAE,gBAAA,EAAkB,CAAA;AAAA,EAC5E;AAAA,EAEA,MAAM,mBAAA,CAAoB,OAAA,EAAiB,IAAA,EAA6B;AACtE,IAAA,MAAM,IAAA,CAAK,UAAS,CAAE,KAAA,CAAM,EAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO;AAAA,EACxD;AACF;;;;"}
@@ -10,6 +10,7 @@ var pgVectorStore = require('./database/pg-vector-store.cjs.js');
10
10
  var pluginSignalsNode = require('@backstage/plugin-signals-node');
11
11
  var searchKnowledge = require('./services/tools/searchKnowledge.cjs.js');
12
12
  var pluginCatalogNode = require('@backstage/plugin-catalog-node');
13
+ var mcp = require('./services/mcp.cjs.js');
13
14
  var langfuse = require('./services/langfuse.cjs.js');
14
15
 
15
16
  const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
@@ -69,7 +70,10 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
69
70
  },
70
71
  async init(options) {
71
72
  const { httpRouter, database, config, logger } = options;
72
- const langfuseEnabled = langfuse.initLangfuse(config, logger);
73
+ const { langfuseEnabled, langfuseClient } = langfuse.initLangfuse(
74
+ config,
75
+ logger
76
+ );
73
77
  const client = await database.getClient();
74
78
  await migrations.applyDatabaseMigrations(client);
75
79
  const vectorStore = await pgVectorStore.PgVectorStore.fromConfig(options);
@@ -82,15 +86,18 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
82
86
  vectorStore,
83
87
  ingestors
84
88
  });
89
+ const mcp$1 = await mcp.createMcpService(options);
85
90
  const searchKnowledgeTool = searchKnowledge.createSearchKnowledgeTool({ vectorStore });
86
91
  tools.push(searchKnowledgeTool);
87
92
  const chat$1 = await chat.createChatService({
88
93
  ...options,
89
94
  models,
90
95
  tools,
91
- langfuseEnabled
96
+ mcp: mcp$1,
97
+ langfuseEnabled,
98
+ langfuseClient
92
99
  });
93
- httpRouter.use(await index.createRouter({ ...options, chat: chat$1 }));
100
+ httpRouter.use(await index.createRouter({ ...options, chat: chat$1, mcp: mcp$1 }));
94
101
  dataIngestionPipeline.start();
95
102
  }
96
103
  });
@@ -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} 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 { initLangfuse } from './services/langfuse';\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\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.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, logger } = options;\n\n const langfuseEnabled = initLangfuse(config, logger);\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 searchKnowledgeTool = createSearchKnowledgeTool({ vectorStore });\n tools.push(searchKnowledgeTool);\n\n const chat = await createChatService({\n ...options,\n models,\n tools,\n langfuseEnabled,\n });\n\n httpRouter.use(await createRouter({ ...options, chat }));\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","initLangfuse","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","createSearchKnowledgeTool","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 Tool,\n toolExtensionPoint,\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';\n\nimport { initLangfuse } from './services/langfuse';\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\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.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, logger } = options;\n\n const { langfuseEnabled, langfuseClient } = initLangfuse(\n config,\n logger,\n );\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 chat = await createChatService({\n ...options,\n models,\n tools,\n mcp,\n langfuseEnabled,\n langfuseClient,\n });\n\n httpRouter.use(await createRouter({ ...options, chat, mcp }));\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","initLangfuse","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","mcp","createMcpService","createSearchKnowledgeTool","chat","createChatService","createRouter"],"mappings":"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"}
@@ -19,7 +19,10 @@ const createChatService = async ({
19
19
  catalog,
20
20
  cache,
21
21
  auth,
22
- langfuseEnabled
22
+ mcp,
23
+ userInfo,
24
+ langfuseEnabled,
25
+ langfuseClient
23
26
  }) => {
24
27
  logger.info(`Available models: ${models.map((m) => m.id).join(", ")}`);
25
28
  logger.info(`Available tools: ${tools$1.map((t) => t.name).join(", ")}`);
@@ -38,7 +41,6 @@ ${contentPrompt}`;
38
41
  models,
39
42
  langfuseEnabled
40
43
  });
41
- const agentTools = tools$1.map((tool) => new tools.DynamicStructuredTool(tool));
42
44
  const systemPromptTemplate = prompts$1.SystemMessagePromptTemplate.fromTemplate(`
43
45
  PURPOSE:
44
46
  {basePrompt}
@@ -105,12 +107,13 @@ ${contentPrompt}`;
105
107
  messages,
106
108
  modelId,
107
109
  stream = true,
108
- userEntityRef
110
+ userCredentials
109
111
  }) => {
110
112
  const model = models.find((m) => m.id === modelId)?.chatModel;
111
113
  if (!model) {
112
114
  throw new Error(`Model with id ${modelId} not found`);
113
115
  }
116
+ const { userEntityRef } = await userInfo.getUserInfo(userCredentials);
114
117
  const streamFn = async () => {
115
118
  const recentConversationMessages = await chatStore$1.getChatMessages(
116
119
  conversationId,
@@ -120,6 +123,8 @@ ${contentPrompt}`;
120
123
  );
121
124
  const credentials = await auth.getOwnServiceCredentials();
122
125
  const user = await getUser(cache, userEntityRef, credentials, catalog);
126
+ const mcpTools = await mcp.getTools(userCredentials);
127
+ const agentTools = tools$1.map((tool) => new tools.DynamicStructuredTool(tool)).concat(mcpTools.map((tool) => new tools.DynamicStructuredTool(tool)));
123
128
  addMessages(
124
129
  messages,
125
130
  userEntityRef,
@@ -164,7 +169,7 @@ ${contentPrompt}`;
164
169
  const newMessages = promptMessages.filter((m) => responseMessages.findIndex((rm) => rm.id === m.id) === -1).filter(
165
170
  (m) => recentConversationMessages.findIndex((rm) => rm.id === m.id) === -1
166
171
  ).filter((m) => m.getType() !== "human").map((m) => {
167
- const id = m.id ?? "";
172
+ const id = uuid.v4();
168
173
  const role = m.getType();
169
174
  const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
170
175
  const metadata = {};
@@ -182,7 +187,9 @@ ${contentPrompt}`;
182
187
  id,
183
188
  role,
184
189
  content,
185
- metadata
190
+ metadata,
191
+ score: 0,
192
+ traceId: void 0
186
193
  };
187
194
  });
188
195
  for await (const m of newMessages) {
@@ -206,8 +213,9 @@ ${contentPrompt}`;
206
213
  }
207
214
  responseMessages.push(...newMessages);
208
215
  }
216
+ const traceId = langfuseHandler?.last_trace_id ?? void 0;
209
217
  addMessages(
210
- responseMessages.map((m) => ({ ...m, id: uuid.v4() })),
218
+ responseMessages.map((m) => ({ ...m, traceId })),
211
219
  userEntityRef,
212
220
  conversationId,
213
221
  [...recentConversationMessages, ...messages]
@@ -234,12 +242,39 @@ ${contentPrompt}`;
234
242
  const conversations = await chatStore$1.getConversations(userEntityRef);
235
243
  return conversations;
236
244
  };
245
+ const scoreMessage = async (messageId, score) => {
246
+ const message = await chatStore$1.getMessageById(messageId);
247
+ if (!message) {
248
+ throw new Error(`Message with id ${messageId} not found`);
249
+ }
250
+ if (langfuseEnabled && message.traceId) {
251
+ langfuseClient.score.create({
252
+ traceId: message.traceId,
253
+ name: "helpfulness",
254
+ value: score
255
+ });
256
+ logger.info(
257
+ `Scored message ${messageId} on Langfuse with trace ID ${message.traceId} - ${score} for helpfulness`
258
+ );
259
+ } else if (langfuseEnabled && !message.traceId) {
260
+ logger.warn(
261
+ `Message ${messageId} does not have a traceId, cannot score on Langfuse`
262
+ );
263
+ }
264
+ const updatedMessage = {
265
+ ...message,
266
+ score
267
+ };
268
+ await chatStore$1.updateMessage(updatedMessage);
269
+ logger.info(`Message ${messageId} scored ${score}`);
270
+ };
237
271
  return {
238
272
  prompt,
239
273
  getAvailableModels,
240
274
  getConversation,
241
275
  getConversations,
242
- addMessages
276
+ addMessages,
277
+ scoreMessage
243
278
  };
244
279
  };
245
280
  async function getUser(cache, userEntityRef, credentials, catalog) {
@@ -1 +1 @@
1
- {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { UserEntity } from '@backstage/catalog-model';\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 { Tool } 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 { createSummarizerService } from './summarizer';\nimport { CallbackHandler } from '@langfuse/langchain';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n} from '@backstage/backend-plugin-api';\nimport { AIMessage, ToolMessage } from '@langchain/core/messages';\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 langfuseEnabled: boolean;\n};\n\ntype PromptOptions = {\n modelId: string;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n userEntityRef: string;\n};\n\ntype GetConversationOptions = {\n conversationId: string;\n userEntityRef: string;\n};\n\ntype GetConversationsOptions = {\n userEntityRef: string;\n};\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<Required<Message>[]>;\n getAvailableModels: () => Promise<string[]>;\n getConversation: (\n options: GetConversationOptions,\n ) => Promise<Required<Message>[]>;\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};\n\nexport const createChatService = async ({\n models,\n tools,\n logger,\n database,\n signals,\n config,\n catalog,\n cache,\n auth,\n langfuseEnabled,\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 const summarizer = await createSummarizerService({\n config,\n models,\n langfuseEnabled,\n });\n\n const agentTools = tools.map(tool => new DynamicStructuredTool(tool));\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.summarize(recentMessages, '25 characters');\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 userEntityRef,\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 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 addMessages(\n messages,\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 // Initialize Langfuse CallbackHandler for tracing if credentials are available\n const langfuseHandler = langfuseEnabled\n ? new CallbackHandler({\n sessionId: conversationId,\n userId: userEntityRef,\n tags: ['backstage-ai-assistant', 'chat'],\n })\n : undefined;\n\n const promptStream = await agent.stream(\n {\n messages: [...recentConversationMessages, ...messages],\n },\n {\n streamMode: ['values'],\n runName: 'ai-assistant-chat',\n metadata: {\n langfuseUserId: userEntityRef,\n langfuseSessionId: conversationId,\n langfuseTags: ['ai-assistant', 'chat', modelId],\n },\n callbacks: langfuseHandler ? [langfuseHandler] : [],\n },\n );\n\n const responseMessages: Required<Message>[] = [];\n\n for await (const [, chunk] of promptStream) {\n const { messages: promptMessages } = chunk;\n\n const newMessages: Required<Message>[] = promptMessages\n .filter(m => responseMessages.findIndex(rm => rm.id === m.id) === -1)\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 = m.id ?? '';\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\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 };\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(\n responseMessages.map(m => ({ ...m, id: uuid() })),\n userEntityRef,\n conversationId,\n [...recentConversationMessages, ...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 return {\n prompt,\n getAvailableModels,\n getConversation,\n getConversations,\n addMessages,\n };\n};\n\nasync function getUser(\n cache: CacheService,\n userEntityRef: string,\n credentials: BackstageCredentials,\n catalog: CatalogService,\n) {\n const cached = await cache.get(userEntityRef);\n\n if (cached) {\n return JSON.parse(String(cached));\n }\n\n const user = (await catalog.getEntityByRef(userEntityRef, {\n credentials,\n })) as UserEntity | undefined;\n await cache.set(userEntityRef, JSON.stringify(user));\n\n return user;\n}\n"],"names":["tools","DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","chatStore","ChatStore","summarizer","createSummarizerService","DynamicStructuredTool","SystemMessagePromptTemplate","conversation","createReactAgent","CallbackHandler","uuid"],"mappings":"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"}
1
+ {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { CatalogService } from '@backstage/plugin-catalog-node';\nimport { UserEntity } from '@backstage/catalog-model';\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 { Tool } 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 { createSummarizerService } from './summarizer';\nimport { CallbackHandler } from '@langfuse/langchain';\nimport { LangfuseClient } from '@langfuse/client';\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';\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 langfuseEnabled: boolean;\n langfuseClient?: LangfuseClient;\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 langfuseEnabled,\n langfuseClient,\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 const summarizer = await createSummarizerService({\n config,\n models,\n langfuseEnabled,\n });\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.summarize(recentMessages, '25 characters');\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 addMessages(\n messages,\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 // Initialize Langfuse CallbackHandler for tracing if credentials are available\n const langfuseHandler = langfuseEnabled\n ? new CallbackHandler({\n sessionId: conversationId,\n userId: userEntityRef,\n tags: ['backstage-ai-assistant', 'chat'],\n })\n : undefined;\n\n const promptStream = await agent.stream(\n {\n messages: [...recentConversationMessages, ...messages],\n },\n {\n streamMode: ['values'],\n runName: 'ai-assistant-chat',\n metadata: {\n langfuseUserId: userEntityRef,\n langfuseSessionId: conversationId,\n langfuseTags: ['ai-assistant', 'chat', modelId],\n },\n callbacks: langfuseHandler ? [langfuseHandler] : [],\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(m => responseMessages.findIndex(rm => rm.id === m.id) === -1)\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(); // Generate UUID here instead of using LangChain's ID\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\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: undefined,\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 // Get the traceId from Langfuse if enabled\n const traceId = langfuseHandler?.last_trace_id ?? undefined;\n\n addMessages(\n responseMessages.map(m => ({ ...m, traceId })),\n userEntityRef,\n conversationId,\n [...recentConversationMessages, ...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 if (langfuseEnabled && message.traceId) {\n langfuseClient!.score.create({\n traceId: message.traceId,\n name: 'helpfulness',\n value: score,\n });\n logger.info(\n `Scored message ${messageId} on Langfuse with trace ID ${message.traceId} - ${score} for helpfulness`,\n );\n } else if (langfuseEnabled && !message.traceId) {\n logger.warn(\n `Message ${messageId} does not have a traceId, cannot score on Langfuse`,\n );\n }\n\n const updatedMessage: Required<Message> = {\n ...message,\n score,\n };\n\n await chatStore.updateMessage(updatedMessage);\n logger.info(`Message ${messageId} scored ${score}`);\n };\n\n return {\n prompt,\n getAvailableModels,\n getConversation,\n getConversations,\n addMessages,\n scoreMessage,\n };\n};\n\nasync function getUser(\n cache: CacheService,\n userEntityRef: string,\n credentials: BackstageCredentials,\n catalog: CatalogService,\n) {\n const cached = await cache.get(userEntityRef);\n\n if (cached) {\n return JSON.parse(String(cached));\n }\n\n const user = (await catalog.getEntityByRef(userEntityRef, {\n credentials,\n })) as UserEntity | undefined;\n await cache.set(userEntityRef, JSON.stringify(user));\n\n return user;\n}\n"],"names":["tools","DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","chatStore","ChatStore","summarizer","createSummarizerService","SystemMessagePromptTemplate","conversation","DynamicStructuredTool","createReactAgent","CallbackHandler","uuid"],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
 
3
3
  var otel = require('@langfuse/otel');
4
4
  var sdkNode = require('@opentelemetry/sdk-node');
5
+ var client = require('@langfuse/client');
5
6
 
6
7
  function initLangfuse(config, logger) {
7
8
  const langfuseSecret = config.getOptionalString(
@@ -23,16 +24,24 @@ function initLangfuse(config, logger) {
23
24
  const sdk = new sdkNode.NodeSDK({
24
25
  spanProcessors: [langfuseSpanProcessor]
25
26
  });
27
+ const langfuseClient = new client.LangfuseClient({
28
+ secretKey: langfuseSecret,
29
+ publicKey: langfusePublic,
30
+ baseUrl: langfuseBaseUrl
31
+ });
26
32
  logger.info(
27
33
  "Langfuse: Starting OpenTelemetry SDK with LangfuseSpanProcessor"
28
34
  );
29
35
  sdk.start();
30
- return true;
36
+ return { langfuseEnabled: true, langfuseClient };
31
37
  }
32
38
  logger.info(
33
39
  "Langfuse: Skipping Langfuse initialization, credentials not found."
34
40
  );
35
- return false;
41
+ return {
42
+ langfuseEnabled: false,
43
+ langfuseClient: void 0
44
+ };
36
45
  }
37
46
 
38
47
  exports.initLangfuse = initLangfuse;
@@ -1 +1 @@
1
- {"version":3,"file":"langfuse.cjs.js","sources":["../../src/services/langfuse.ts"],"sourcesContent":["import {\n RootConfigService,\n RootLoggerService,\n} from '@backstage/backend-plugin-api';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport { NodeSDK } from '@opentelemetry/sdk-node';\n\nexport function initLangfuse(\n config: RootConfigService,\n logger: RootLoggerService,\n): boolean {\n const langfuseSecret = config.getOptionalString(\n 'aiAssistant.langfuse.secretKey',\n );\n const langfusePublic = config.getOptionalString(\n 'aiAssistant.langfuse.publicKey',\n );\n const langfuseBaseUrl = config.getOptionalString(\n 'aiAssistant.langfuse.baseUrl',\n );\n\n let langfuseSpanProcessor: LangfuseSpanProcessor | undefined = undefined;\n if (langfuseSecret && langfusePublic && langfuseBaseUrl) {\n langfuseSpanProcessor = new LangfuseSpanProcessor({\n secretKey: langfuseSecret,\n publicKey: langfusePublic,\n baseUrl: langfuseBaseUrl,\n });\n\n const sdk = new NodeSDK({\n spanProcessors: [langfuseSpanProcessor],\n });\n\n logger.info(\n 'Langfuse: Starting OpenTelemetry SDK with LangfuseSpanProcessor',\n );\n sdk.start();\n return true;\n }\n logger.info(\n 'Langfuse: Skipping Langfuse initialization, credentials not found.',\n );\n return false;\n}\n"],"names":["LangfuseSpanProcessor","NodeSDK"],"mappings":";;;;;AAOO,SAAS,YAAA,CACd,QACA,MAAA,EACS;AACT,EAAA,MAAM,iBAAiB,MAAA,CAAO,iBAAA;AAAA,IAC5B;AAAA,GACF;AACA,EAAA,MAAM,iBAAiB,MAAA,CAAO,iBAAA;AAAA,IAC5B;AAAA,GACF;AACA,EAAA,MAAM,kBAAkB,MAAA,CAAO,iBAAA;AAAA,IAC7B;AAAA,GACF;AAEA,EAAA,IAAI,qBAAA,GAA2D,MAAA;AAC/D,EAAA,IAAI,cAAA,IAAkB,kBAAkB,eAAA,EAAiB;AACvD,IAAA,qBAAA,GAAwB,IAAIA,0BAAA,CAAsB;AAAA,MAChD,SAAA,EAAW,cAAA;AAAA,MACX,SAAA,EAAW,cAAA;AAAA,MACX,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAM,GAAA,GAAM,IAAIC,eAAA,CAAQ;AAAA,MACtB,cAAA,EAAgB,CAAC,qBAAqB;AAAA,KACvC,CAAA;AAED,IAAA,MAAA,CAAO,IAAA;AAAA,MACL;AAAA,KACF;AACA,IAAA,GAAA,CAAI,KAAA,EAAM;AACV,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,MAAA,CAAO,IAAA;AAAA,IACL;AAAA,GACF;AACA,EAAA,OAAO,KAAA;AACT;;;;"}
1
+ {"version":3,"file":"langfuse.cjs.js","sources":["../../src/services/langfuse.ts"],"sourcesContent":["import {\n RootConfigService,\n RootLoggerService,\n} from '@backstage/backend-plugin-api';\nimport { LangfuseSpanProcessor } from '@langfuse/otel';\nimport { NodeSDK } from '@opentelemetry/sdk-node';\nimport { LangfuseClient } from '@langfuse/client';\n\nexport function initLangfuse(\n config: RootConfigService,\n logger: RootLoggerService,\n): { langfuseEnabled: boolean; langfuseClient: LangfuseClient | undefined } {\n const langfuseSecret = config.getOptionalString(\n 'aiAssistant.langfuse.secretKey',\n );\n const langfusePublic = config.getOptionalString(\n 'aiAssistant.langfuse.publicKey',\n );\n const langfuseBaseUrl = config.getOptionalString(\n 'aiAssistant.langfuse.baseUrl',\n );\n\n let langfuseSpanProcessor: LangfuseSpanProcessor | undefined = undefined;\n if (langfuseSecret && langfusePublic && langfuseBaseUrl) {\n langfuseSpanProcessor = new LangfuseSpanProcessor({\n secretKey: langfuseSecret,\n publicKey: langfusePublic,\n baseUrl: langfuseBaseUrl,\n });\n\n const sdk = new NodeSDK({\n spanProcessors: [langfuseSpanProcessor],\n });\n\n const langfuseClient = new LangfuseClient({\n secretKey: langfuseSecret,\n publicKey: langfusePublic,\n baseUrl: langfuseBaseUrl,\n });\n\n logger.info(\n 'Langfuse: Starting OpenTelemetry SDK with LangfuseSpanProcessor',\n );\n sdk.start();\n return { langfuseEnabled: true, langfuseClient };\n }\n logger.info(\n 'Langfuse: Skipping Langfuse initialization, credentials not found.',\n );\n return {\n langfuseEnabled: false,\n langfuseClient: undefined,\n };\n}\n"],"names":["LangfuseSpanProcessor","NodeSDK","LangfuseClient"],"mappings":";;;;;;AAQO,SAAS,YAAA,CACd,QACA,MAAA,EAC0E;AAC1E,EAAA,MAAM,iBAAiB,MAAA,CAAO,iBAAA;AAAA,IAC5B;AAAA,GACF;AACA,EAAA,MAAM,iBAAiB,MAAA,CAAO,iBAAA;AAAA,IAC5B;AAAA,GACF;AACA,EAAA,MAAM,kBAAkB,MAAA,CAAO,iBAAA;AAAA,IAC7B;AAAA,GACF;AAEA,EAAA,IAAI,qBAAA,GAA2D,MAAA;AAC/D,EAAA,IAAI,cAAA,IAAkB,kBAAkB,eAAA,EAAiB;AACvD,IAAA,qBAAA,GAAwB,IAAIA,0BAAA,CAAsB;AAAA,MAChD,SAAA,EAAW,cAAA;AAAA,MACX,SAAA,EAAW,cAAA;AAAA,MACX,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAM,GAAA,GAAM,IAAIC,eAAA,CAAQ;AAAA,MACtB,cAAA,EAAgB,CAAC,qBAAqB;AAAA,KACvC,CAAA;AAED,IAAA,MAAM,cAAA,GAAiB,IAAIC,qBAAA,CAAe;AAAA,MACxC,SAAA,EAAW,cAAA;AAAA,MACX,SAAA,EAAW,cAAA;AAAA,MACX,OAAA,EAAS;AAAA,KACV,CAAA;AAED,IAAA,MAAA,CAAO,IAAA;AAAA,MACL;AAAA,KACF;AACA,IAAA,GAAA,CAAI,KAAA,EAAM;AACV,IAAA,OAAO,EAAE,eAAA,EAAiB,IAAA,EAAM,cAAA,EAAe;AAAA,EACjD;AACA,EAAA,MAAA,CAAO,IAAA;AAAA,IACL;AAAA,GACF;AACA,EAAA,OAAO;AAAA,IACL,eAAA,EAAiB,KAAA;AAAA,IACjB,cAAA,EAAgB;AAAA,GAClB;AACF;;;;"}
@@ -0,0 +1,119 @@
1
+ 'use strict';
2
+
3
+ var mcpAdapters = require('@langchain/mcp-adapters');
4
+ var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
5
+ var mcpStore = require('../database/mcp-store.cjs.js');
6
+
7
+ const createMcpService = async ({
8
+ config,
9
+ userInfo,
10
+ database
11
+ }) => {
12
+ const serversConfig = config.getOptionalConfigArray(
13
+ "aiAssistant.mcp.servers"
14
+ );
15
+ const encryptionKey = config.getString("aiAssistant.mcp.encryptionKey");
16
+ const preConfiguredMcpServers = serversConfig ? serversConfig.reduce((acc, server) => {
17
+ const serverName = server.getString("name");
18
+ const options = server.get("options");
19
+ acc[serverName] = options;
20
+ return acc;
21
+ }, {}) : null;
22
+ const preConfiguredMcpClient = preConfiguredMcpServers ? new mcpAdapters.MultiServerMCPClient({
23
+ prefixToolNameWithServerName: true,
24
+ useStandardContentBlocks: true,
25
+ mcpServers: preConfiguredMcpServers
26
+ }) : null;
27
+ const mcpStore$1 = await mcpStore.McpStore.fromConfig({ database });
28
+ const getUserMcpServerConfigNames = async (credentials) => {
29
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
30
+ return mcpStore$1.getUserUserMcpConfigNames(userEntityRef);
31
+ };
32
+ const getUserMcpServerConfig = async (credentials) => {
33
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
34
+ const encryptedMcpConfig = await mcpStore$1.getUserMcpConfigs(userEntityRef);
35
+ const mcpConfig = encryptedMcpConfig.map((c) => ({
36
+ name: c.name,
37
+ options: JSON.parse(backstagePluginAiAssistantNode.decrypt(c.encryptedOptions, encryptionKey))
38
+ }));
39
+ return mcpConfig;
40
+ };
41
+ const getTools = async (credentials) => {
42
+ const userMcpConfig = await getUserMcpServerConfig(credentials);
43
+ const userMcpServers = userMcpConfig.reduce((acc, server) => {
44
+ const { name, options } = server;
45
+ acc[name] = options;
46
+ return acc;
47
+ }, {});
48
+ const userConfigClient = new mcpAdapters.MultiServerMCPClient({
49
+ prefixToolNameWithServerName: true,
50
+ useStandardContentBlocks: true,
51
+ mcpServers: userMcpServers
52
+ });
53
+ const userMcpTools = await userConfigClient.getTools();
54
+ const preConfiguredMcpTools = preConfiguredMcpClient ? await preConfiguredMcpClient.getTools() : [];
55
+ const mcpTools = [...userMcpTools, ...preConfiguredMcpTools];
56
+ const tools = mcpTools.map((mcpTool) => {
57
+ const { name, description, schema } = mcpTool;
58
+ return backstagePluginAiAssistantNode.createAssistantTool({
59
+ tool: {
60
+ name,
61
+ description,
62
+ schema,
63
+ func: async (params) => {
64
+ const result = await mcpTool.invoke(params);
65
+ return JSON.stringify(result);
66
+ }
67
+ }
68
+ });
69
+ });
70
+ return tools;
71
+ };
72
+ const validateMcpServerConfig = async (mcpConfig) => {
73
+ try {
74
+ const userMcpServers = mcpConfig.reduce((acc, server) => {
75
+ const { name, options } = server;
76
+ acc[name] = options;
77
+ return acc;
78
+ }, {});
79
+ const userConfigClient = new mcpAdapters.MultiServerMCPClient({
80
+ prefixToolNameWithServerName: true,
81
+ useStandardContentBlocks: true,
82
+ mcpServers: userMcpServers
83
+ });
84
+ await userConfigClient.getTools();
85
+ } catch (e) {
86
+ const error = new Error("Invalid MCP server configuration");
87
+ error.name = "McpConfigurationError";
88
+ throw error;
89
+ }
90
+ };
91
+ const createUserMcpServerConfig = async (credentials, mcpConfig) => {
92
+ await validateMcpServerConfig([mcpConfig]);
93
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
94
+ const { name, options } = mcpConfig;
95
+ const encryptedOptions = backstagePluginAiAssistantNode.encrypt(JSON.stringify(options), encryptionKey);
96
+ await mcpStore$1.createUserMcpConfig(userEntityRef, name, encryptedOptions);
97
+ };
98
+ const updateUserMcpServerConfig = async (credentials, mcpConfig) => {
99
+ await validateMcpServerConfig([mcpConfig]);
100
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
101
+ const { name, options } = mcpConfig;
102
+ const encryptedOptions = backstagePluginAiAssistantNode.encrypt(JSON.stringify(options), encryptionKey);
103
+ await mcpStore$1.updateUserMcpConfig(userEntityRef, name, encryptedOptions);
104
+ };
105
+ const deleteUserMcpServerConfig = async (credentials, name) => {
106
+ const { userEntityRef } = await userInfo.getUserInfo(credentials);
107
+ await mcpStore$1.deleteUserMcpConfig(userEntityRef, name);
108
+ };
109
+ return {
110
+ getTools,
111
+ createUserMcpServerConfig,
112
+ updateUserMcpServerConfig,
113
+ getUserMcpServerConfigNames,
114
+ deleteUserMcpServerConfig
115
+ };
116
+ };
117
+
118
+ exports.createMcpService = createMcpService;
119
+ //# sourceMappingURL=mcp.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.cjs.js","sources":["../../src/services/mcp.ts"],"sourcesContent":["import {\n BackstageCredentials,\n DatabaseService,\n RootConfigService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { MultiServerMCPClient } from '@langchain/mcp-adapters';\nimport {\n McpServerConfig,\n McpServerConfigOptions,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport {\n createAssistantTool,\n Tool,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { McpStore } from '../database/mcp-store';\nimport {\n encrypt,\n decrypt,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\ntype CreateMcpServiceOptions = {\n config: RootConfigService;\n userInfo: UserInfoService;\n database: DatabaseService;\n};\n\nexport type McpService = {\n getTools: (credentials: BackstageCredentials) => Promise<Tool[]>;\n createUserMcpServerConfig: (\n credentials: BackstageCredentials,\n config: McpServerConfig,\n ) => Promise<void>;\n updateUserMcpServerConfig: (\n credentials: BackstageCredentials,\n config: McpServerConfig,\n ) => Promise<void>;\n getUserMcpServerConfigNames: (\n credentials: BackstageCredentials,\n ) => Promise<string[]>;\n deleteUserMcpServerConfig: (\n credentials: BackstageCredentials,\n name: string,\n ) => Promise<void>;\n};\n\nexport const createMcpService = async ({\n config,\n userInfo,\n database,\n}: CreateMcpServiceOptions): Promise<McpService> => {\n const serversConfig = config.getOptionalConfigArray(\n 'aiAssistant.mcp.servers',\n );\n const encryptionKey = config.getString('aiAssistant.mcp.encryptionKey');\n\n const preConfiguredMcpServers = serversConfig\n ? serversConfig.reduce((acc, server) => {\n const serverName = server.getString('name');\n const options = server.get<McpServerConfigOptions>('options');\n\n acc[serverName] = options;\n\n return acc;\n }, {} as Record<string, McpServerConfigOptions>)\n : null;\n\n const preConfiguredMcpClient = preConfiguredMcpServers\n ? new MultiServerMCPClient({\n prefixToolNameWithServerName: true,\n useStandardContentBlocks: true,\n mcpServers: preConfiguredMcpServers,\n })\n : null;\n\n const mcpStore = await McpStore.fromConfig({ database });\n\n const getUserMcpServerConfigNames: McpService['getUserMcpServerConfigNames'] =\n async credentials => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n return mcpStore.getUserUserMcpConfigNames(userEntityRef);\n };\n\n const getUserMcpServerConfig = async (\n credentials: BackstageCredentials,\n ): Promise<McpServerConfig[]> => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const encryptedMcpConfig = await mcpStore.getUserMcpConfigs(userEntityRef);\n\n const mcpConfig: McpServerConfig[] = encryptedMcpConfig.map(c => ({\n name: c.name,\n options: JSON.parse(decrypt(c.encryptedOptions, encryptionKey)),\n }));\n\n return mcpConfig;\n };\n\n const getTools: McpService['getTools'] = async credentials => {\n const userMcpConfig = await getUserMcpServerConfig(credentials);\n\n const userMcpServers = userMcpConfig.reduce((acc, server) => {\n const { name, options } = server;\n\n acc[name] = options;\n\n return acc;\n }, {} as Record<string, McpServerConfigOptions>);\n\n const userConfigClient = new MultiServerMCPClient({\n prefixToolNameWithServerName: true,\n useStandardContentBlocks: true,\n mcpServers: userMcpServers,\n });\n\n const userMcpTools = await userConfigClient.getTools();\n const preConfiguredMcpTools = preConfiguredMcpClient\n ? await preConfiguredMcpClient.getTools()\n : [];\n\n const mcpTools = [...userMcpTools, ...preConfiguredMcpTools];\n\n const tools = mcpTools.map(mcpTool => {\n const { name, description, schema } = mcpTool;\n return createAssistantTool({\n tool: {\n name,\n description,\n schema: schema as Tool['schema'],\n func: async (params: any) => {\n const result = await mcpTool.invoke(params);\n return JSON.stringify(result);\n },\n },\n });\n });\n\n return tools;\n };\n\n const validateMcpServerConfig = async (\n mcpConfig: McpServerConfig[],\n ): Promise<void> => {\n try {\n const userMcpServers = mcpConfig.reduce((acc, server) => {\n const { name, options } = server;\n\n acc[name] = options;\n\n return acc;\n }, {} as Record<string, McpServerConfigOptions>);\n\n const userConfigClient = new MultiServerMCPClient({\n prefixToolNameWithServerName: true,\n useStandardContentBlocks: true,\n mcpServers: userMcpServers,\n });\n\n await userConfigClient.getTools();\n } catch (e) {\n const error = new Error('Invalid MCP server configuration');\n error.name = 'McpConfigurationError';\n throw error;\n }\n };\n\n const createUserMcpServerConfig: McpService['createUserMcpServerConfig'] =\n async (credentials, mcpConfig) => {\n await validateMcpServerConfig([mcpConfig]);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n const { name, options } = mcpConfig;\n\n const encryptedOptions = encrypt(JSON.stringify(options), encryptionKey);\n\n await mcpStore.createUserMcpConfig(userEntityRef, name, encryptedOptions);\n };\n\n const updateUserMcpServerConfig: McpService['updateUserMcpServerConfig'] =\n async (credentials, mcpConfig) => {\n await validateMcpServerConfig([mcpConfig]);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n const { name, options } = mcpConfig;\n\n const encryptedOptions = encrypt(JSON.stringify(options), encryptionKey);\n\n await mcpStore.updateUserMcpConfig(userEntityRef, name, encryptedOptions);\n };\n\n const deleteUserMcpServerConfig: McpService['deleteUserMcpServerConfig'] =\n async (credentials, name) => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n await mcpStore.deleteUserMcpConfig(userEntityRef, name);\n };\n\n return {\n getTools,\n createUserMcpServerConfig,\n updateUserMcpServerConfig,\n getUserMcpServerConfigNames,\n deleteUserMcpServerConfig,\n };\n};\n"],"names":["MultiServerMCPClient","mcpStore","McpStore","decrypt","createAssistantTool","encrypt"],"mappings":"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"}
@@ -30,14 +30,13 @@ async function createChatRouter(options) {
30
30
  validation.validation(messageSchema, "body"),
31
31
  async (req, res) => {
32
32
  const { messages, conversationId, modelId, stream } = req.body;
33
- const credentials = await httpAuth.credentials(req);
34
- const { userEntityRef } = await userInfo.getUserInfo(credentials);
33
+ const userCredentials = await httpAuth.credentials(req);
35
34
  const responseMessages = await chat.prompt({
36
35
  modelId,
37
36
  messages,
38
37
  conversationId,
39
38
  stream,
40
- userEntityRef
39
+ userCredentials
41
40
  });
42
41
  res.json({
43
42
  messages: responseMessages,
@@ -66,6 +65,27 @@ async function createChatRouter(options) {
66
65
  });
67
66
  res.json({ conversation });
68
67
  });
68
+ router.post(
69
+ "/message/:messageId/score",
70
+ validation.validation(
71
+ z__default.default.object({
72
+ messageId: z__default.default.string().uuid()
73
+ }),
74
+ "params"
75
+ ),
76
+ validation.validation(
77
+ z__default.default.object({
78
+ score: z__default.default.number()
79
+ }),
80
+ "body"
81
+ ),
82
+ async (req, res) => {
83
+ const { messageId } = req.params;
84
+ const { score } = req.body;
85
+ await chat.scoreMessage(messageId, score);
86
+ res.status(204).end();
87
+ }
88
+ );
69
89
  return router;
70
90
  }
71
91
 
@@ -1 +1 @@
1
- {"version":3,"file":"chat.cjs.js","sources":["../../../src/services/router/chat.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { ChatService } from '../chat';\nimport z from 'zod';\nimport { validation } from './middleware/validation';\nimport { v4 as uuid } from 'uuid';\nimport {\n DatabaseService,\n HttpAuthService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\n\nexport type ChatRouterOptions = {\n chat: ChatService;\n database: DatabaseService;\n httpAuth: HttpAuthService;\n userInfo: UserInfoService;\n};\n\nexport async function createChatRouter(\n options: ChatRouterOptions,\n): Promise<express.Router> {\n const { chat, httpAuth, userInfo } = options;\n\n const router = Router();\n\n const messageSchema = z.object({\n messages: z.array(\n z.object({\n id: z.string().uuid().optional().default(uuid),\n role: z.string(),\n content: z.string(),\n }),\n ),\n modelId: z.string(),\n conversationId: z.string().uuid().optional().default(uuid),\n stream: z.boolean().optional(),\n });\n\n router.post(\n '/message',\n validation(messageSchema, 'body'),\n async (req, res) => {\n const { messages, conversationId, modelId, stream } = req.body;\n\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const responseMessages = await chat.prompt({\n modelId,\n messages,\n conversationId,\n stream,\n userEntityRef,\n });\n\n res.json({\n messages: responseMessages,\n conversationId,\n });\n },\n );\n\n const chatSchema = z.object({\n id: z.string().uuid(),\n });\n\n router.get('/conversations', async (req, res) => {\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const conversations = await chat.getConversations({\n userEntityRef,\n });\n\n res.json({ conversations });\n });\n\n router.get('/:id', validation(chatSchema, 'params'), async (req, res) => {\n const { id } = req.params;\n\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const conversation = await chat.getConversation({\n conversationId: id,\n userEntityRef,\n });\n\n res.json({ conversation });\n });\n\n return router;\n}\n"],"names":["Router","z","uuid","validation"],"mappings":"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"}
1
+ {"version":3,"file":"chat.cjs.js","sources":["../../../src/services/router/chat.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { ChatService } from '../chat';\nimport z from 'zod';\nimport { validation } from './middleware/validation';\nimport { v4 as uuid } from 'uuid';\nimport {\n DatabaseService,\n HttpAuthService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\n\nexport type ChatRouterOptions = {\n chat: ChatService;\n database: DatabaseService;\n httpAuth: HttpAuthService;\n userInfo: UserInfoService;\n};\n\nexport async function createChatRouter(\n options: ChatRouterOptions,\n): Promise<express.Router> {\n const { chat, httpAuth, userInfo } = options;\n\n const router = Router();\n\n const messageSchema = z.object({\n messages: z.array(\n z.object({\n id: z.string().uuid().optional().default(uuid),\n role: z.string(),\n content: z.string(),\n }),\n ),\n modelId: z.string(),\n conversationId: z.string().uuid().optional().default(uuid),\n stream: z.boolean().optional(),\n });\n\n router.post(\n '/message',\n validation(messageSchema, 'body'),\n async (req, res) => {\n const { messages, conversationId, modelId, stream } = req.body;\n\n const userCredentials = await httpAuth.credentials(req);\n\n const responseMessages = await chat.prompt({\n modelId,\n messages,\n conversationId,\n stream,\n userCredentials,\n });\n\n res.json({\n messages: responseMessages,\n conversationId,\n });\n },\n );\n\n const chatSchema = z.object({\n id: z.string().uuid(),\n });\n\n router.get('/conversations', async (req, res) => {\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const conversations = await chat.getConversations({\n userEntityRef,\n });\n\n res.json({ conversations });\n });\n\n router.get('/:id', validation(chatSchema, 'params'), async (req, res) => {\n const { id } = req.params;\n\n const credentials = await httpAuth.credentials(req);\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const conversation = await chat.getConversation({\n conversationId: id,\n userEntityRef,\n });\n\n res.json({ conversation });\n });\n\n router.post(\n '/message/:messageId/score',\n validation(\n z.object({\n messageId: z.string().uuid(),\n }),\n 'params',\n ),\n validation(\n z.object({\n score: z.number(),\n }),\n 'body',\n ),\n async (req, res) => {\n const { messageId } = req.params;\n const { score } = req.body;\n\n await chat.scoreMessage(messageId, score);\n\n res.status(204).end();\n },\n );\n\n return router;\n}\n"],"names":["Router","z","uuid","validation"],"mappings":"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"}
@@ -5,6 +5,7 @@ var Router = require('express-promise-router');
5
5
  var chat = require('./chat.cjs.js');
6
6
  var models = require('./models.cjs.js');
7
7
  var rootHttpRouter = require('@backstage/backend-defaults/rootHttpRouter');
8
+ var mcp = require('./mcp.cjs.js');
8
9
 
9
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
10
11
 
@@ -16,6 +17,7 @@ async function createRouter(options) {
16
17
  router.use(express__default.default.json());
17
18
  router.use("/chat", await chat.createChatRouter(options));
18
19
  router.use("/models", await models.createModelRouter(options));
20
+ router.use("/mcp", await mcp.createMcpRouter(options));
19
21
  const middleware = rootHttpRouter.MiddlewareFactory.create(options);
20
22
  router.use(middleware.error());
21
23
  return router;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../../../src/services/router/index.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { createChatRouter, ChatRouterOptions } from './chat';\nimport { createModelRouter } from './models';\nimport {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\n\nexport type RouterOptions = ChatRouterOptions & {\n config: RootConfigService;\n logger: LoggerService;\n};\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n router.use('/chat', await createChatRouter(options));\n router.use('/models', await createModelRouter(options));\n\n const middleware = MiddlewareFactory.create(options);\n\n router.use(middleware.error());\n\n return router;\n}\n"],"names":["Router","express","createChatRouter","createModelRouter","MiddlewareFactory"],"mappings":";;;;;;;;;;;;;AAeA,eAAsB,aACpB,OAAA,EACyB;AACzB,EAAA,MAAM,SAASA,uBAAA,EAAO;AACtB,EAAA,MAAA,CAAO,GAAA,CAAIC,wBAAA,CAAQ,IAAA,EAAM,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,MAAMC,qBAAA,CAAiB,OAAO,CAAC,CAAA;AACnD,EAAA,MAAA,CAAO,GAAA,CAAI,SAAA,EAAW,MAAMC,wBAAA,CAAkB,OAAO,CAAC,CAAA;AAEtD,EAAA,MAAM,UAAA,GAAaC,gCAAA,CAAkB,MAAA,CAAO,OAAO,CAAA;AAEnD,EAAA,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,KAAA,EAAO,CAAA;AAE7B,EAAA,OAAO,MAAA;AACT;;;;"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../src/services/router/index.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { createChatRouter, ChatRouterOptions } from './chat';\nimport { createModelRouter } from './models';\nimport {\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';\nimport { createMcpRouter, McpRouterOptions } from './mcp';\n\nexport type RouterOptions = ChatRouterOptions &\n McpRouterOptions & {\n config: RootConfigService;\n logger: LoggerService;\n };\n\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const router = Router();\n router.use(express.json());\n\n router.use('/chat', await createChatRouter(options));\n router.use('/models', await createModelRouter(options));\n router.use('/mcp', await createMcpRouter(options));\n\n const middleware = MiddlewareFactory.create(options);\n\n router.use(middleware.error());\n\n return router;\n}\n"],"names":["Router","express","createChatRouter","createModelRouter","createMcpRouter","MiddlewareFactory"],"mappings":";;;;;;;;;;;;;;AAiBA,eAAsB,aACpB,OAAA,EACyB;AACzB,EAAA,MAAM,SAASA,uBAAA,EAAO;AACtB,EAAA,MAAA,CAAO,GAAA,CAAIC,wBAAA,CAAQ,IAAA,EAAM,CAAA;AAEzB,EAAA,MAAA,CAAO,GAAA,CAAI,OAAA,EAAS,MAAMC,qBAAA,CAAiB,OAAO,CAAC,CAAA;AACnD,EAAA,MAAA,CAAO,GAAA,CAAI,SAAA,EAAW,MAAMC,wBAAA,CAAkB,OAAO,CAAC,CAAA;AACtD,EAAA,MAAA,CAAO,GAAA,CAAI,MAAA,EAAQ,MAAMC,mBAAA,CAAgB,OAAO,CAAC,CAAA;AAEjD,EAAA,MAAM,UAAA,GAAaC,gCAAA,CAAkB,MAAA,CAAO,OAAO,CAAA;AAEnD,EAAA,MAAA,CAAO,GAAA,CAAI,UAAA,CAAW,KAAA,EAAO,CAAA;AAE7B,EAAA,OAAO,MAAA;AACT;;;;"}
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ var Router = require('express-promise-router');
4
+ var z = require('zod');
5
+ var validation = require('./middleware/validation.cjs.js');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
8
+
9
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
10
+ var z__default = /*#__PURE__*/_interopDefaultCompat(z);
11
+
12
+ async function createMcpRouter(options) {
13
+ const { mcp, httpAuth } = options;
14
+ const router = Router__default.default();
15
+ router.get("/config", async (req, res) => {
16
+ const credentials = await httpAuth.credentials(req);
17
+ const names = await mcp.getUserMcpServerConfigNames(credentials);
18
+ res.json({
19
+ names
20
+ });
21
+ });
22
+ const configSchema = z__default.default.object({
23
+ name: z__default.default.string(),
24
+ options: z__default.default.record(z__default.default.string(), z__default.default.any())
25
+ });
26
+ router.post("/config", validation.validation(configSchema, "body"), async (req, res) => {
27
+ const credentials = await httpAuth.credentials(req);
28
+ const { name, options: mcpOptions } = req.body;
29
+ try {
30
+ await mcp.createUserMcpServerConfig(credentials, {
31
+ name,
32
+ options: mcpOptions
33
+ });
34
+ res.status(201).send();
35
+ } catch (error) {
36
+ if (error instanceof Error && error.name === "McpConfigurationError") {
37
+ res.status(400).json({ error: error.message });
38
+ return;
39
+ }
40
+ throw error;
41
+ }
42
+ });
43
+ router.patch(
44
+ "/config",
45
+ validation.validation(configSchema, "body"),
46
+ async (req, res) => {
47
+ const credentials = await httpAuth.credentials(req);
48
+ const { name, options: mcpOptions } = req.body;
49
+ try {
50
+ await mcp.updateUserMcpServerConfig(credentials, {
51
+ name,
52
+ options: mcpOptions
53
+ });
54
+ res.status(204).send();
55
+ } catch (error) {
56
+ if (error instanceof Error && error.name === "McpConfigurationError") {
57
+ res.status(400).json({ error: error.message });
58
+ return;
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ );
64
+ const deleteConfigSchema = z__default.default.object({
65
+ name: z__default.default.string()
66
+ });
67
+ router.delete(
68
+ "/config",
69
+ validation.validation(deleteConfigSchema, "body"),
70
+ async (req, res) => {
71
+ const credentials = await httpAuth.credentials(req);
72
+ const { name } = req.body;
73
+ await mcp.deleteUserMcpServerConfig(credentials, name);
74
+ res.status(204).send();
75
+ }
76
+ );
77
+ return router;
78
+ }
79
+
80
+ exports.createMcpRouter = createMcpRouter;
81
+ //# sourceMappingURL=mcp.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.cjs.js","sources":["../../../src/services/router/mcp.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport { McpService } from '../mcp';\nimport { HttpAuthService } from '@backstage/backend-plugin-api';\nimport z from 'zod';\nimport { validation } from './middleware/validation';\n\nexport type McpRouterOptions = {\n mcp: McpService;\n httpAuth: HttpAuthService;\n};\n\nexport async function createMcpRouter(\n options: McpRouterOptions,\n): Promise<express.Router> {\n const { mcp, httpAuth } = options;\n const router = Router();\n\n router.get('/config', async (req, res) => {\n const credentials = await httpAuth.credentials(req);\n\n const names = await mcp.getUserMcpServerConfigNames(credentials);\n\n res.json({\n names,\n });\n });\n\n const configSchema = z.object({\n name: z.string(),\n options: z.record(z.string(), z.any()),\n });\n\n router.post('/config', validation(configSchema, 'body'), async (req, res) => {\n const credentials = await httpAuth.credentials(req);\n const { name, options: mcpOptions } = req.body;\n try {\n await mcp.createUserMcpServerConfig(credentials, {\n name,\n options: mcpOptions,\n });\n\n res.status(201).send();\n } catch (error) {\n if (error instanceof Error && error.name === 'McpConfigurationError') {\n res.status(400).json({ error: error.message });\n return;\n }\n throw error;\n }\n });\n\n router.patch(\n '/config',\n validation(configSchema, 'body'),\n async (req, res) => {\n const credentials = await httpAuth.credentials(req);\n const { name, options: mcpOptions } = req.body;\n\n try {\n await mcp.updateUserMcpServerConfig(credentials, {\n name,\n options: mcpOptions,\n });\n res.status(204).send();\n } catch (error) {\n if (error instanceof Error && error.name === 'McpConfigurationError') {\n res.status(400).json({ error: error.message });\n return;\n }\n throw error;\n }\n },\n );\n\n const deleteConfigSchema = z.object({\n name: z.string(),\n });\n\n router.delete(\n '/config',\n validation(deleteConfigSchema, 'body'),\n async (req, res) => {\n const credentials = await httpAuth.credentials(req);\n const { name } = req.body;\n\n await mcp.deleteUserMcpServerConfig(credentials, name);\n\n res.status(204).send();\n },\n );\n\n return router;\n}\n"],"names":["Router","z","validation"],"mappings":"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"}
@@ -20,19 +20,25 @@ const createSummarizerService = async ({
20
20
  userId: "summarizer",
21
21
  tags: ["backstage-ai-assistant", "summarizer"]
22
22
  }) : void 0;
23
- const summaryPromptTemplate = prompts$1.SystemMessagePromptTemplate.fromTemplate(`
24
- PURPOSE:
25
- {summaryPrompt}
26
- Summarize the conversation in {summaryLength}
27
-
28
- Conversation:
29
- {conversation}
30
- `);
23
+ const chatPromptTemplate = prompts$1.ChatPromptTemplate.fromMessages([
24
+ prompts$1.SystemMessagePromptTemplate.fromTemplate(`
25
+ PURPOSE:
26
+ {summaryPrompt}
27
+
28
+ Please summarize the following conversation in {summaryLength}.
29
+ `),
30
+ prompts$1.HumanMessagePromptTemplate.fromTemplate(`
31
+ Conversation:
32
+ {conversation}
33
+
34
+ Please provide a summary of this conversation.
35
+ `)
36
+ ]);
31
37
  const summarize = async (messages, summaryLength = "as few words as possible") => {
32
38
  const conversationMessages = messages.filter(
33
39
  (msg) => msg.role === "ai" || msg.role === "human"
34
40
  );
35
- const prompt = await summaryPromptTemplate.formatMessages({
41
+ const prompt = await chatPromptTemplate.formatMessages({
36
42
  summaryPrompt,
37
43
  summaryLength,
38
44
  conversation: conversationMessages.map((msg) => `${msg.role}: ${msg.content}`).join("\n")
@@ -1 +1 @@
1
- {"version":3,"file":"summarizer.cjs.js","sources":["../../src/services/summarizer.ts"],"sourcesContent":["import { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { DEFAULT_SUMMARY_PROMPT } from '../constants/prompts';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\nimport { Message } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { CallbackHandler } from '@langfuse/langchain';\n\ntype SummarizerService = {\n summarize: (\n conversationMessages: Message[],\n summaryLength?: string,\n ) => Promise<string>;\n};\n\ntype SummarizerServiceOptions = {\n config: RootConfigService;\n models: Model[];\n langfuseEnabled: boolean;\n};\n\nexport const createSummarizerService = async ({\n config,\n models,\n langfuseEnabled,\n}: SummarizerServiceOptions): Promise<SummarizerService> => {\n const summaryModelId =\n config.getOptionalString('aiAssistant.conversation.summaryModel') ??\n models[0].id;\n\n const summaryPrompt =\n config.getOptionalString('aiAssistant.conversation.summaryPrompt') ??\n DEFAULT_SUMMARY_PROMPT;\n\n const model = models.find(m => m.id === summaryModelId);\n\n if (!model) {\n throw new Error(`Summary model with id ${summaryModelId} not found`);\n }\n\n const llm = model.chatModel;\n\n // Initialize Langfuse CallbackHandler for tracing if credentials are available\n const langfuseHandler = langfuseEnabled\n ? new CallbackHandler({\n userId: 'summarizer',\n tags: ['backstage-ai-assistant', 'summarizer'],\n })\n : undefined;\n\n const summaryPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {summaryPrompt}\n Summarize the conversation in {summaryLength}\n\n Conversation:\n {conversation}\n `);\n\n const summarize: SummarizerService['summarize'] = async (\n messages,\n summaryLength = 'as few words as possible',\n ) => {\n const conversationMessages = messages.filter(\n msg => msg.role === 'ai' || msg.role === 'human',\n );\n\n const prompt = await summaryPromptTemplate.formatMessages({\n summaryPrompt,\n summaryLength,\n conversation: conversationMessages\n .map(msg => `${msg.role}: ${msg.content}`)\n .join('\\n'),\n });\n\n const invokeOptions: any = {\n runName: 'conversation-summarizer',\n tags: ['summarizer'],\n };\n\n if (langfuseEnabled) {\n invokeOptions.callbacks = [langfuseHandler];\n }\n\n const { text } = await llm.invoke(prompt, invokeOptions);\n\n return text.trim();\n };\n\n return { summarize };\n};\n"],"names":["DEFAULT_SUMMARY_PROMPT","CallbackHandler","SystemMessagePromptTemplate"],"mappings":";;;;;;AAoBO,MAAM,0BAA0B,OAAO;AAAA,EAC5C,MAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,KAA4D;AAC1D,EAAA,MAAM,iBACJ,MAAA,CAAO,iBAAA,CAAkB,uCAAuC,CAAA,IAChE,MAAA,CAAO,CAAC,CAAA,CAAE,EAAA;AAEZ,EAAA,MAAM,aAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,wCAAwC,CAAA,IACjEA,8BAAA;AAEF,EAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,cAAc,CAAA;AAEtD,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,cAAc,CAAA,UAAA,CAAY,CAAA;AAAA,EACrE;AAEA,EAAA,MAAM,MAAM,KAAA,CAAM,SAAA;AAGlB,EAAA,MAAM,eAAA,GAAkB,eAAA,GACpB,IAAIC,yBAAA,CAAgB;AAAA,IAClB,MAAA,EAAQ,YAAA;AAAA,IACR,IAAA,EAAM,CAAC,wBAAA,EAA0B,YAAY;AAAA,GAC9C,CAAA,GACD,MAAA;AAEJ,EAAA,MAAM,qBAAA,GAAwBC,sCAA4B,YAAA,CAAa;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA,EAAA,CAOtE,CAAA;AAED,EAAA,MAAM,SAAA,GAA4C,OAChD,QAAA,EACA,aAAA,GAAgB,0BAAA,KACb;AACH,IAAA,MAAM,uBAAuB,QAAA,CAAS,MAAA;AAAA,MACpC,CAAA,GAAA,KAAO,GAAA,CAAI,IAAA,KAAS,IAAA,IAAQ,IAAI,IAAA,KAAS;AAAA,KAC3C;AAEA,IAAA,MAAM,MAAA,GAAS,MAAM,qBAAA,CAAsB,cAAA,CAAe;AAAA,MACxD,aAAA;AAAA,MACA,aAAA;AAAA,MACA,YAAA,EAAc,oBAAA,CACX,GAAA,CAAI,CAAA,GAAA,KAAO,CAAA,EAAG,GAAA,CAAI,IAAI,CAAA,EAAA,EAAK,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA,CACxC,KAAK,IAAI;AAAA,KACb,CAAA;AAED,IAAA,MAAM,aAAA,GAAqB;AAAA,MACzB,OAAA,EAAS,yBAAA;AAAA,MACT,IAAA,EAAM,CAAC,YAAY;AAAA,KACrB;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,aAAA,CAAc,SAAA,GAAY,CAAC,eAAe,CAAA;AAAA,IAC5C;AAEA,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,GAAA,CAAI,MAAA,CAAO,QAAQ,aAAa,CAAA;AAEvD,IAAA,OAAO,KAAK,IAAA,EAAK;AAAA,EACnB,CAAA;AAEA,EAAA,OAAO,EAAE,SAAA,EAAU;AACrB;;;;"}
1
+ {"version":3,"file":"summarizer.cjs.js","sources":["../../src/services/summarizer.ts"],"sourcesContent":["import { RootConfigService } from '@backstage/backend-plugin-api';\nimport { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { DEFAULT_SUMMARY_PROMPT } from '../constants/prompts';\nimport {\n SystemMessagePromptTemplate,\n HumanMessagePromptTemplate,\n ChatPromptTemplate,\n} from '@langchain/core/prompts';\nimport { Message } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { CallbackHandler } from '@langfuse/langchain';\n\ntype SummarizerService = {\n summarize: (\n conversationMessages: Message[],\n summaryLength?: string,\n ) => Promise<string>;\n};\n\ntype SummarizerServiceOptions = {\n config: RootConfigService;\n models: Model[];\n langfuseEnabled: boolean;\n};\n\nexport const createSummarizerService = async ({\n config,\n models,\n langfuseEnabled,\n}: SummarizerServiceOptions): Promise<SummarizerService> => {\n const summaryModelId =\n config.getOptionalString('aiAssistant.conversation.summaryModel') ??\n models[0].id;\n\n const summaryPrompt =\n config.getOptionalString('aiAssistant.conversation.summaryPrompt') ??\n DEFAULT_SUMMARY_PROMPT;\n\n const model = models.find(m => m.id === summaryModelId);\n\n if (!model) {\n throw new Error(`Summary model with id ${summaryModelId} not found`);\n }\n\n const llm = model.chatModel;\n\n // Initialize Langfuse CallbackHandler for tracing if credentials are available\n const langfuseHandler = langfuseEnabled\n ? new CallbackHandler({\n userId: 'summarizer',\n tags: ['backstage-ai-assistant', 'summarizer'],\n })\n : undefined;\n\n const chatPromptTemplate = ChatPromptTemplate.fromMessages([\n SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {summaryPrompt}\n \n Please summarize the following conversation in {summaryLength}.\n `),\n HumanMessagePromptTemplate.fromTemplate(`\n Conversation:\n {conversation}\n \n Please provide a summary of this conversation.\n `),\n ]);\n\n const summarize: SummarizerService['summarize'] = async (\n messages,\n summaryLength = 'as few words as possible',\n ) => {\n const conversationMessages = messages.filter(\n msg => msg.role === 'ai' || msg.role === 'human',\n );\n\n const prompt = await chatPromptTemplate.formatMessages({\n summaryPrompt,\n summaryLength,\n conversation: conversationMessages\n .map(msg => `${msg.role}: ${msg.content}`)\n .join('\\n'),\n });\n\n const invokeOptions: any = {\n runName: 'conversation-summarizer',\n tags: ['summarizer'],\n };\n\n if (langfuseEnabled) {\n invokeOptions.callbacks = [langfuseHandler];\n }\n\n const { text } = await llm.invoke(prompt, invokeOptions);\n\n return text.trim();\n };\n\n return { summarize };\n};\n"],"names":["DEFAULT_SUMMARY_PROMPT","CallbackHandler","ChatPromptTemplate","SystemMessagePromptTemplate","HumanMessagePromptTemplate"],"mappings":";;;;;;AAwBO,MAAM,0BAA0B,OAAO;AAAA,EAC5C,MAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,KAA4D;AAC1D,EAAA,MAAM,iBACJ,MAAA,CAAO,iBAAA,CAAkB,uCAAuC,CAAA,IAChE,MAAA,CAAO,CAAC,CAAA,CAAE,EAAA;AAEZ,EAAA,MAAM,aAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,wCAAwC,CAAA,IACjEA,8BAAA;AAEF,EAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,cAAc,CAAA;AAEtD,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,sBAAA,EAAyB,cAAc,CAAA,UAAA,CAAY,CAAA;AAAA,EACrE;AAEA,EAAA,MAAM,MAAM,KAAA,CAAM,SAAA;AAGlB,EAAA,MAAM,eAAA,GAAkB,eAAA,GACpB,IAAIC,yBAAA,CAAgB;AAAA,IAClB,MAAA,EAAQ,YAAA;AAAA,IACR,IAAA,EAAM,CAAC,wBAAA,EAA0B,YAAY;AAAA,GAC9C,CAAA,GACD,MAAA;AAEJ,EAAA,MAAM,kBAAA,GAAqBC,6BAAmB,YAAA,CAAa;AAAA,IACzDC,sCAA4B,YAAA,CAAa;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAKxC,CAAA;AAAA,IACDC,qCAA2B,YAAA,CAAa;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAKvC;AAAA,GACF,CAAA;AAED,EAAA,MAAM,SAAA,GAA4C,OAChD,QAAA,EACA,aAAA,GAAgB,0BAAA,KACb;AACH,IAAA,MAAM,uBAAuB,QAAA,CAAS,MAAA;AAAA,MACpC,CAAA,GAAA,KAAO,GAAA,CAAI,IAAA,KAAS,IAAA,IAAQ,IAAI,IAAA,KAAS;AAAA,KAC3C;AAEA,IAAA,MAAM,MAAA,GAAS,MAAM,kBAAA,CAAmB,cAAA,CAAe;AAAA,MACrD,aAAA;AAAA,MACA,aAAA;AAAA,MACA,YAAA,EAAc,oBAAA,CACX,GAAA,CAAI,CAAA,GAAA,KAAO,CAAA,EAAG,GAAA,CAAI,IAAI,CAAA,EAAA,EAAK,GAAA,CAAI,OAAO,CAAA,CAAE,CAAA,CACxC,KAAK,IAAI;AAAA,KACb,CAAA;AAED,IAAA,MAAM,aAAA,GAAqB;AAAA,MACzB,OAAA,EAAS,yBAAA;AAAA,MACT,IAAA,EAAM,CAAC,YAAY;AAAA,KACrB;AAEA,IAAA,IAAI,eAAA,EAAiB;AACnB,MAAA,aAAA,CAAc,SAAA,GAAY,CAAC,eAAe,CAAA;AAAA,IAC5C;AAEA,IAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,GAAA,CAAI,MAAA,CAAO,QAAQ,aAAa,CAAA;AAEvD,IAAA,OAAO,KAAK,IAAA,EAAK;AAAA,EACnB,CAAA;AAEA,EAAA,OAAO,EAAE,SAAA,EAAU;AACrB;;;;"}
@@ -0,0 +1,42 @@
1
+ const mcpConfigTable = 'user_mcp_config';
2
+
3
+ /**
4
+ *
5
+ * @param {import('knex').knex} knex
6
+ */
7
+
8
+ exports.down = async knex => {
9
+ const hasMcpConfigTable = await knex.schema.hasTable(mcpConfigTable);
10
+
11
+ if (hasMcpConfigTable) {
12
+ await knex.schema.dropTable(mcpConfigTable);
13
+ }
14
+ };
15
+
16
+ /**
17
+ *
18
+ * @param {import('knex').knex} knex
19
+ */
20
+
21
+ exports.up = async knex => {
22
+ await knex.schema.createTable(mcpConfigTable, table => {
23
+ table
24
+ .uuid('id')
25
+ .primary()
26
+ .notNullable()
27
+ .defaultTo(knex.raw('gen_random_uuid()'));
28
+ table
29
+ .text('userRef')
30
+ .notNullable()
31
+ .comment('Reference to the user who sent the message');
32
+ table
33
+ .text('name')
34
+ .notNullable()
35
+ .comment('Name of the MCP server configuration');
36
+ table
37
+ .text('encryptedOptions')
38
+ .notNullable()
39
+ .comment('Encrypted MCP server configuration');
40
+ table.timestamps(true, true);
41
+ });
42
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ *
3
+ * @param {import('knex').knex} knex
4
+ */
5
+
6
+ exports.down = async knex => {
7
+ const hasScoreColumn = await knex.schema.hasColumn('message', 'score');
8
+ const hasTraceIdColumn = await knex.schema.hasColumn('message', 'trace_id');
9
+
10
+ if (hasScoreColumn || hasTraceIdColumn) {
11
+ await knex.schema.alterTable('message', table => {
12
+ if (hasScoreColumn) {
13
+ table.dropColumn('score');
14
+ }
15
+ if (hasTraceIdColumn) {
16
+ table.dropColumn('trace_id');
17
+ }
18
+ });
19
+ }
20
+ };
21
+
22
+ /**
23
+ *
24
+ * @param {import('knex').knex} knex
25
+ */
26
+
27
+ exports.up = async knex => {
28
+ const hasScoreColumn = await knex.schema.hasColumn('message', 'score');
29
+ const hasTraceIdColumn = await knex.schema.hasColumn('message', 'trace_id');
30
+
31
+ if (!hasScoreColumn || !hasTraceIdColumn) {
32
+ await knex.schema.alterTable('message', table => {
33
+ if (!hasScoreColumn) {
34
+ table
35
+ .float('score')
36
+ .nullable()
37
+ .defaultTo(0)
38
+ .comment(
39
+ 'User feedback score for the message: 0 = no feedback, 1 = helpful, -1 = not helpful',
40
+ );
41
+ }
42
+ if (!hasTraceIdColumn) {
43
+ table
44
+ .string('trace_id')
45
+ .nullable()
46
+ .comment(
47
+ 'Langfuse trace ID for the message, used for scoring and observability',
48
+ );
49
+ }
50
+ });
51
+ }
52
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweetoburrito/backstage-plugin-ai-assistant-backend",
3
- "version": "0.0.0-snapshot-20251029080430",
3
+ "version": "0.0.0-snapshot-20251029150521",
4
4
  "license": "Apache-2.0",
5
5
  "main": "dist/index.cjs.js",
6
6
  "types": "dist/index.d.ts",
@@ -41,14 +41,16 @@
41
41
  "@backstage/plugin-signals-node": "backstage:^",
42
42
  "@langchain/core": "^0.3.72",
43
43
  "@langchain/langgraph": "^0.4.9",
44
+ "@langchain/mcp-adapters": "^1.0.0",
44
45
  "@langchain/textsplitters": "^0.1.0",
46
+ "@langfuse/client": "^4.3.0",
45
47
  "@langfuse/core": "^4.0.0",
46
48
  "@langfuse/langchain": "^4.0.0",
47
49
  "@langfuse/otel": "^4.3.0",
48
50
  "@opentelemetry/api": "^1.9.0",
49
51
  "@opentelemetry/sdk-node": "^0.207.0",
50
- "@sweetoburrito/backstage-plugin-ai-assistant-common": "^0.5.0",
51
- "@sweetoburrito/backstage-plugin-ai-assistant-node": "^0.5.1",
52
+ "@sweetoburrito/backstage-plugin-ai-assistant-common": "0.0.0-snapshot-20251029150521",
53
+ "@sweetoburrito/backstage-plugin-ai-assistant-node": "0.0.0-snapshot-20251029150521",
52
54
  "express": "^4.17.1",
53
55
  "express-promise-router": "^4.1.0",
54
56
  "knex": "^3.1.0",
@@ -70,6 +72,7 @@
70
72
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-catalog": "workspace:^",
71
73
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-ingestor-github": "workspace:^",
72
74
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-azure-ai": "workspace:^",
75
+ "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-google-vertex-ai": "workspace:^",
73
76
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-model-provider-ollama": "workspace:^",
74
77
  "@sweetoburrito/backstage-plugin-ai-assistant-backend-module-tool-provider-backstage": "workspace:^",
75
78
  "@types/express": "^4.0.0",