@sweetoburrito/backstage-plugin-ai-assistant-backend 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,21 +1,30 @@
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.";
4
- const DEFAULT_SYSTEM_PROMPT = `
3
+ const DEFAULT_CONVERSATION_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
+ const DEFAULT_SUMMARY_PROMPT = "Summarize the following content 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 content that doesn't contribute to the summary.";
5
+ const DEFAULT_IDENTITY_PROMPT = `
5
6
  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
-
7
- Rules:
7
+ `;
8
+ const DEFAULT_FORMATTING_PROMPT = `
9
+ CRITICAL FORMATTING RULES - MUST ALWAYS FOLLOW:
10
+ 1. **ALWAYS use proper markdown formatting in ALL responses**
11
+ 2. **NEVER output plain URLs** - ALWAYS convert them to clickable markdown links using [description](url) syntax
12
+ 3. **For images, ALWAYS use markdown image syntax**: ![alt text](image-url)
13
+ 4. **For all URLs, ALWAYS format as**: [descriptive text](url) - never just paste the raw URL
14
+ 5. Use headings (##, ###), bullet points, numbered lists, and **bold**/*italic* text appropriately
15
+ 6. Format code with backticks: \`inline code\` or \`\`\`language for code blocks
16
+ 7. Structure responses clearly with proper spacing and organization
17
+ `;
18
+ const DEFAULT_SYSTEM_PROMPT = `
19
+ Content Rules:
8
20
  1. Always base your answers on the provided context. Do not make up information.
9
21
  2. When relevant, cite or reference the source information provided in the context.
10
- 3. Format answers clearly and concisely. Use bullet points for lists when appropriate.
11
- 4. Maintain a professional, friendly, and helpful tone.
12
- 5. Return only the relevant information without any filler or unnecessary details.
13
- 6. If you don't know the answer, admit it and suggest ways to find the information.
14
- 7. Always return a well-structured response using markdown.
15
- 8. **Actively use available tools** to enhance your responses:
16
- 9. Adapt your approach based on the specific tools and capabilities available in the current session
17
- 10. When you have a link to an image, include it in your response using markdown format ![description](image_url).
18
- 11. When you have a link to a document, include it in your response using markdown format [description](document_url).
22
+ 3. Maintain a professional, friendly, and helpful tone.
23
+ 4. Return only the relevant information without any filler or unnecessary details.
24
+ 5. If you don't know the answer, admit it and suggest ways to find the information.
25
+ 6. **Actively use available tools** to enhance your responses
26
+ 7. Adapt your approach based on the specific tools and capabilities available in the current session
27
+ 8. When you do not have the information needed to answer, use the tools provided to gather more context before responding.
19
28
  `;
20
29
  const DEFAULT_TOOL_GUIDELINE = `
21
30
  TOOL USAGE GUIDELINES:
@@ -27,6 +36,9 @@ TOOL USAGE GUIDELINES:
27
36
  - If a tool fails, try an alternative approach before using another tool
28
37
  `;
29
38
 
39
+ exports.DEFAULT_CONVERSATION_SUMMARY_PROMPT = DEFAULT_CONVERSATION_SUMMARY_PROMPT;
40
+ exports.DEFAULT_FORMATTING_PROMPT = DEFAULT_FORMATTING_PROMPT;
41
+ exports.DEFAULT_IDENTITY_PROMPT = DEFAULT_IDENTITY_PROMPT;
30
42
  exports.DEFAULT_SUMMARY_PROMPT = DEFAULT_SUMMARY_PROMPT;
31
43
  exports.DEFAULT_SYSTEM_PROMPT = DEFAULT_SYSTEM_PROMPT;
32
44
  exports.DEFAULT_TOOL_GUIDELINE = DEFAULT_TOOL_GUIDELINE;
@@ -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_SYSTEM_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\nRules:\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. Format answers clearly and concisely. Use bullet points for lists when appropriate.\n4. Maintain a professional, friendly, and helpful tone.\n5. Return only the relevant information without any filler or unnecessary details.\n6. If you don't know the answer, admit it and suggest ways to find the information.\n7. Always return a well-structured response using markdown.\n8. **Actively use available tools** to enhance your responses:\n9. Adapt your approach based on the specific tools and capabilities available in the current session\n10. When you have a link to an image, include it in your response using markdown format ![description](image_url).\n11. When you have a link to a document, include it in your response using markdown format [description](document_url).\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,qBAAA,GAAwB;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiB9B,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_CONVERSATION_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_SUMMARY_PROMPT =\n \"Summarize the following content 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 content that doesn't contribute to 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,mCAAA,GACX;AAEK,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,9 @@ 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');
14
+ var callbacks = require('./services/callbacks.cjs.js');
15
+ var summarizer = require('./services/summarizer.cjs.js');
13
16
 
14
17
  const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
15
18
  pluginId: "ai-assistant",
@@ -17,6 +20,7 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
17
20
  const ingestors = [];
18
21
  const models = [];
19
22
  const tools = [];
23
+ const callbacks$1 = [];
20
24
  let embeddingsProvider;
21
25
  env.registerExtensionPoint(backstagePluginAiAssistantNode.dataIngestorExtensionPoint, {
22
26
  registerIngestor: (ingestor) => {
@@ -52,6 +56,11 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
52
56
  tools.push(tool);
53
57
  }
54
58
  });
59
+ env.registerExtensionPoint(backstagePluginAiAssistantNode.callbackProviderExtensionPoint, {
60
+ register: (callbackProvider) => {
61
+ callbacks$1.push(callbackProvider);
62
+ }
63
+ });
55
64
  env.registerInit({
56
65
  deps: {
57
66
  httpRouter: backendPluginApi.coreServices.httpRouter,
@@ -67,7 +76,7 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
67
76
  auth: backendPluginApi.coreServices.auth
68
77
  },
69
78
  async init(options) {
70
- const { httpRouter, database } = options;
79
+ const { httpRouter, database, config } = options;
71
80
  const client = await database.getClient();
72
81
  await migrations.applyDatabaseMigrations(client);
73
82
  const vectorStore = await pgVectorStore.PgVectorStore.fromConfig(options);
@@ -80,14 +89,28 @@ const aiAssistantPlugin = backendPluginApi.createBackendPlugin({
80
89
  vectorStore,
81
90
  ingestors
82
91
  });
92
+ const mcp$1 = await mcp.createMcpService(options);
83
93
  const searchKnowledgeTool = searchKnowledge.createSearchKnowledgeTool({ vectorStore });
84
94
  tools.push(searchKnowledgeTool);
95
+ const callback = await callbacks.createCallbackService({
96
+ callbacks: callbacks$1
97
+ });
98
+ const summarizer$1 = await summarizer.createSummarizerService({
99
+ config,
100
+ models,
101
+ callback
102
+ });
85
103
  const chat$1 = await chat.createChatService({
86
104
  ...options,
87
105
  models,
88
- tools
106
+ tools,
107
+ mcp: mcp$1,
108
+ callback,
109
+ summarizer: summarizer$1
89
110
  });
90
- httpRouter.use(await index.createRouter({ ...options, chat: chat$1 }));
111
+ httpRouter.use(
112
+ await index.createRouter({ ...options, chat: chat$1, mcp: mcp$1, summarizer: summarizer$1 })
113
+ );
91
114
  dataIngestionPipeline.start();
92
115
  }
93
116
  });
@@ -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';\n\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 } = options;\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 });\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","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 callbackProviderExtensionPoint,\n CallbackProvider,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { createDataIngestionPipeline } from './services/ingestor';\nimport { createChatService } from './services/chat';\nimport { applyDatabaseMigrations } from './database/migrations';\nimport { PgVectorStore } from './database';\nimport { signalsServiceRef } from '@backstage/plugin-signals-node';\nimport { createSearchKnowledgeTool } from './services/tools/searchKnowledge';\nimport { catalogServiceRef } from '@backstage/plugin-catalog-node';\nimport { createMcpService } from './services/mcp';\nimport { createCallbackService } from './services/callbacks';\nimport { createSummarizerService } from './services/summarizer';\n/**\n * aiAssistantPlugin backend plugin\n *\n * @public\n */\n\nexport const aiAssistantPlugin = createBackendPlugin({\n pluginId: 'ai-assistant',\n register(env) {\n const ingestors: Ingestor[] = [];\n const models: Model[] = [];\n const tools: Tool[] = [];\n const callbacks: CallbackProvider[] = [];\n\n let embeddingsProvider: EmbeddingsProvider;\n\n env.registerExtensionPoint(dataIngestorExtensionPoint, {\n registerIngestor: ingestor => {\n const existingIngestor = ingestors.find(i => i.id === ingestor.id);\n if (existingIngestor) {\n throw new Error(\n `Ingestor with id ${ingestor.id} is already registered.`,\n );\n }\n ingestors.push(ingestor);\n },\n });\n\n env.registerExtensionPoint(embeddingsProviderExtensionPoint, {\n register: provider => {\n embeddingsProvider = provider;\n },\n });\n\n env.registerExtensionPoint(modelProviderExtensionPoint, {\n register: model => {\n const existingModel = models.find(m => m.id === model.id);\n if (existingModel) {\n throw new Error(`Model with id ${model.id} is already registered.`);\n }\n models.push(model);\n },\n });\n\n env.registerExtensionPoint(toolExtensionPoint, {\n register: tool => {\n const existingTool = tools.find(t => t.name === tool.name);\n if (existingTool) {\n throw new Error(`Tool with name ${tool.name} is already registered.`);\n }\n tools.push(tool);\n },\n });\n\n env.registerExtensionPoint(callbackProviderExtensionPoint, {\n register: callbackProvider => {\n callbacks.push(callbackProvider);\n },\n });\n\n env.registerInit({\n deps: {\n httpRouter: coreServices.httpRouter,\n database: coreServices.database,\n logger: coreServices.logger,\n config: coreServices.rootConfig,\n scheduler: coreServices.scheduler,\n httpAuth: coreServices.httpAuth,\n userInfo: coreServices.userInfo,\n signals: signalsServiceRef,\n catalog: catalogServiceRef,\n cache: coreServices.cache,\n auth: coreServices.auth,\n },\n\n async init(options) {\n const { httpRouter, database, config } = options;\n\n const client = await database.getClient();\n\n await applyDatabaseMigrations(client);\n\n const vectorStore = await PgVectorStore.fromConfig(options);\n\n if (!embeddingsProvider) {\n throw new Error('No Embeddings Provider was registered.');\n }\n\n vectorStore.connectEmbeddings(await embeddingsProvider.getEmbeddings());\n\n const dataIngestionPipeline = createDataIngestionPipeline({\n ...options,\n vectorStore,\n ingestors,\n });\n\n const mcp = await createMcpService(options);\n\n const searchKnowledgeTool = createSearchKnowledgeTool({ vectorStore });\n tools.push(searchKnowledgeTool);\n\n const callback = await createCallbackService({\n callbacks,\n });\n\n const summarizer = await createSummarizerService({\n config,\n models,\n callback,\n });\n\n const chat = await createChatService({\n ...options,\n models,\n tools,\n mcp,\n callback,\n summarizer,\n });\n\n httpRouter.use(\n await createRouter({ ...options, chat, mcp, summarizer }),\n );\n dataIngestionPipeline.start();\n },\n });\n },\n});\n"],"names":["createBackendPlugin","callbacks","dataIngestorExtensionPoint","embeddingsProviderExtensionPoint","modelProviderExtensionPoint","toolExtensionPoint","callbackProviderExtensionPoint","coreServices","signalsServiceRef","catalogServiceRef","applyDatabaseMigrations","PgVectorStore","createDataIngestionPipeline","mcp","createMcpService","createSearchKnowledgeTool","createCallbackService","summarizer","createSummarizerService","chat","createChatService","createRouter"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const createCallbackService = async ({
4
+ callbacks
5
+ }) => {
6
+ const getChainCallbacks = async (options) => {
7
+ const callbackHandlers = [];
8
+ for (const { chainCallback } of callbacks) {
9
+ if (!chainCallback) {
10
+ continue;
11
+ }
12
+ const callbackHandler = await chainCallback(options);
13
+ callbackHandlers.push(callbackHandler);
14
+ }
15
+ return {
16
+ callbacks: callbackHandlers
17
+ };
18
+ };
19
+ const getChainMetadata = async (options) => {
20
+ const metadata = {};
21
+ for (const { chainMetadataCallback } of callbacks) {
22
+ if (!chainMetadataCallback) {
23
+ continue;
24
+ }
25
+ const callbackData = await chainMetadataCallback(options);
26
+ Object.assign(metadata, callbackData.metadata);
27
+ }
28
+ return { metadata };
29
+ };
30
+ const handleScoreCallbacks = async ({
31
+ name,
32
+ message
33
+ }) => {
34
+ callbacks.forEach(async ({ scoreCallback }) => {
35
+ if (!scoreCallback) {
36
+ return;
37
+ }
38
+ scoreCallback({ name, message });
39
+ });
40
+ };
41
+ return {
42
+ getChainCallbacks,
43
+ getChainMetadata,
44
+ handleScoreCallbacks
45
+ };
46
+ };
47
+
48
+ exports.createCallbackService = createCallbackService;
49
+ //# sourceMappingURL=callbacks.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callbacks.cjs.js","sources":["../../src/services/callbacks.ts"],"sourcesContent":["import {\n CallbackProvider,\n ChainMetadata,\n ChainCallbackOptions,\n ChainCallback,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nexport type CallbackService = {\n getChainCallbacks: (options: ChainCallbackOptions) => Promise<{\n callbacks: ReturnType<ChainCallback>[];\n }>;\n\n getChainMetadata: (\n options: ChainCallbackOptions,\n ) => Promise<{ metadata: ChainMetadata }>;\n\n handleScoreCallbacks: NonNullable<CallbackProvider['scoreCallback']>;\n};\n\nexport type CreateCallbackServiceOptions = {\n callbacks: CallbackProvider[];\n};\n\nexport const createCallbackService = async ({\n callbacks,\n}: CreateCallbackServiceOptions): Promise<CallbackService> => {\n const getChainCallbacks: CallbackService['getChainCallbacks'] =\n async options => {\n const callbackHandlers: ReturnType<ChainCallback>[] = [];\n\n for (const { chainCallback } of callbacks) {\n if (!chainCallback) {\n continue;\n }\n\n const callbackHandler = await chainCallback(options);\n\n callbackHandlers.push(callbackHandler);\n }\n\n return {\n callbacks: callbackHandlers,\n };\n };\n\n const getChainMetadata: CallbackService['getChainMetadata'] =\n async options => {\n const metadata: ChainMetadata = {};\n\n for (const { chainMetadataCallback } of callbacks) {\n if (!chainMetadataCallback) {\n continue;\n }\n\n const callbackData = await chainMetadataCallback(options);\n\n Object.assign(metadata, callbackData.metadata);\n }\n\n return { metadata };\n };\n\n const handleScoreCallbacks: CallbackService['handleScoreCallbacks'] = async ({\n name,\n message,\n }) => {\n callbacks.forEach(async ({ scoreCallback }) => {\n if (!scoreCallback) {\n return;\n }\n\n scoreCallback({ name, message });\n });\n };\n\n return {\n getChainCallbacks,\n getChainMetadata,\n handleScoreCallbacks,\n };\n};\n"],"names":[],"mappings":";;AAuBO,MAAM,wBAAwB,OAAO;AAAA,EAC1C;AACF,CAAA,KAA8D;AAC5D,EAAA,MAAM,iBAAA,GACJ,OAAM,OAAA,KAAW;AACf,IAAA,MAAM,mBAAgD,EAAC;AAEvD,IAAA,KAAA,MAAW,EAAE,aAAA,EAAc,IAAK,SAAA,EAAW;AACzC,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,eAAA,GAAkB,MAAM,aAAA,CAAc,OAAO,CAAA;AAEnD,MAAA,gBAAA,CAAiB,KAAK,eAAe,CAAA;AAAA,IACvC;AAEA,IAAA,OAAO;AAAA,MACL,SAAA,EAAW;AAAA,KACb;AAAA,EACF,CAAA;AAEF,EAAA,MAAM,gBAAA,GACJ,OAAM,OAAA,KAAW;AACf,IAAA,MAAM,WAA0B,EAAC;AAEjC,IAAA,KAAA,MAAW,EAAE,qBAAA,EAAsB,IAAK,SAAA,EAAW;AACjD,MAAA,IAAI,CAAC,qBAAA,EAAuB;AAC1B,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,YAAA,GAAe,MAAM,qBAAA,CAAsB,OAAO,CAAA;AAExD,MAAA,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,YAAA,CAAa,QAAQ,CAAA;AAAA,IAC/C;AAEA,IAAA,OAAO,EAAE,QAAA,EAAS;AAAA,EACpB,CAAA;AAEF,EAAA,MAAM,uBAAgE,OAAO;AAAA,IAC3E,IAAA;AAAA,IACA;AAAA,GACF,KAAM;AACJ,IAAA,SAAA,CAAU,OAAA,CAAQ,OAAO,EAAE,aAAA,EAAc,KAAM;AAC7C,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA;AAAA,MACF;AAEA,MAAA,aAAA,CAAc,EAAE,IAAA,EAAM,OAAA,EAAS,CAAA;AAAA,IACjC,CAAC,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,iBAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
@@ -2,10 +2,10 @@
2
2
 
3
3
  var chatStore = require('../database/chat-store.cjs.js');
4
4
  var prompts = require('../constants/prompts.cjs.js');
5
+ var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
5
6
  var tools = require('@langchain/core/tools');
6
7
  var prebuilt = require('@langchain/langgraph/prebuilt');
7
8
  var prompts$1 = require('@langchain/core/prompts');
8
- var summarizer = require('./summarizer.cjs.js');
9
9
  var uuid = require('uuid');
10
10
 
11
11
  const createChatService = async ({
@@ -17,14 +17,24 @@ const createChatService = async ({
17
17
  config,
18
18
  catalog,
19
19
  cache,
20
- auth
20
+ auth,
21
+ mcp,
22
+ userInfo,
23
+ callback,
24
+ summarizer
21
25
  }) => {
22
26
  logger.info(`Available models: ${models.map((m) => m.id).join(", ")}`);
23
- const system = config.getOptionalString("aiAssistant.prompt.system") || prompts.DEFAULT_SYSTEM_PROMPT;
27
+ logger.info(`Available tools: ${tools$1.map((t) => t.name).join(", ")}`);
28
+ const identityPrompt = config.getOptionalString("aiAssistant.prompt.identity") || prompts.DEFAULT_IDENTITY_PROMPT;
29
+ const formattingPrompt = config.getOptionalString("aiAssistant.prompt.formatting") || prompts.DEFAULT_FORMATTING_PROMPT;
30
+ const contentPrompt = config.getOptionalString("aiAssistant.prompt.content") || prompts.DEFAULT_SYSTEM_PROMPT;
31
+ const combinedBasePrompt = `${identityPrompt}
32
+
33
+ ${formattingPrompt}
34
+
35
+ ${contentPrompt}`;
24
36
  const toolGuideline = config.getOptionalString("aiAssistant.prompt.toolGuideline") || prompts.DEFAULT_TOOL_GUIDELINE;
25
37
  const chatStore$1 = await chatStore.ChatStore.fromConfig({ database });
26
- const summarizer$1 = await summarizer.createSummarizerService({ config, models });
27
- const agentTools = tools$1.map((tool) => new tools.DynamicStructuredTool(tool));
28
38
  const systemPromptTemplate = prompts$1.SystemMessagePromptTemplate.fromTemplate(`
29
39
  PURPOSE:
30
40
  {basePrompt}
@@ -73,7 +83,10 @@ const createChatService = async ({
73
83
  chatStore$1.addChatMessage(messages, userRef, conversationId);
74
84
  return;
75
85
  }
76
- const summary = await summarizer$1.summarize(recentMessages, "25 characters");
86
+ const summary = await summarizer.summarizeConversation({
87
+ messages: recentMessages,
88
+ length: "25 characters"
89
+ });
77
90
  conversation.title = summary;
78
91
  chatStore$1.updateConversation(conversation);
79
92
  chatStore$1.addChatMessage(messages, userRef, conversationId);
@@ -91,12 +104,13 @@ const createChatService = async ({
91
104
  messages,
92
105
  modelId,
93
106
  stream = true,
94
- userEntityRef
107
+ userCredentials
95
108
  }) => {
96
109
  const model = models.find((m) => m.id === modelId)?.chatModel;
97
110
  if (!model) {
98
111
  throw new Error(`Model with id ${modelId} not found`);
99
112
  }
113
+ const { userEntityRef } = await userInfo.getUserInfo(userCredentials);
100
114
  const streamFn = async () => {
101
115
  const recentConversationMessages = await chatStore$1.getChatMessages(
102
116
  conversationId,
@@ -105,15 +119,18 @@ const createChatService = async ({
105
119
  ["tool"]
106
120
  );
107
121
  const credentials = await auth.getOwnServiceCredentials();
108
- const user = await getUser(cache, userEntityRef, credentials, catalog);
122
+ const user = await backstagePluginAiAssistantNode.getUser(cache, userEntityRef, credentials, catalog);
123
+ const mcpTools = await mcp.getTools(userCredentials);
124
+ const agentTools = tools$1.map((tool) => new tools.DynamicStructuredTool(tool)).concat(mcpTools.map((tool) => new tools.DynamicStructuredTool(tool)));
125
+ const messagesWithoutSystem = messages.filter((m) => m.role !== "system");
109
126
  addMessages(
110
- messages,
127
+ messagesWithoutSystem,
111
128
  userEntityRef,
112
129
  conversationId,
113
130
  recentConversationMessages
114
131
  );
115
132
  const systemPrompt = await systemPromptTemplate.formatMessages({
116
- basePrompt: system,
133
+ basePrompt: combinedBasePrompt,
117
134
  toolGuideline,
118
135
  toolList: agentTools.map((tool) => `- ${tool.name}: ${tool.description}`).join("\n"),
119
136
  context: `none`,
@@ -124,22 +141,45 @@ const createChatService = async ({
124
141
  tools: agentTools,
125
142
  prompt: systemPrompt[0].text
126
143
  });
144
+ const { callbacks } = await callback.getChainCallbacks({
145
+ conversationId,
146
+ userId: userEntityRef,
147
+ modelId
148
+ });
149
+ const { metadata: promptMetadata } = await callback.getChainMetadata({
150
+ conversationId,
151
+ userId: userEntityRef,
152
+ modelId
153
+ });
154
+ const traceId = uuid.v4();
127
155
  const promptStream = await agent.stream(
128
156
  {
129
157
  messages: [...recentConversationMessages, ...messages]
130
158
  },
131
- { streamMode: ["values"] }
159
+ {
160
+ streamMode: ["values"],
161
+ runName: "ai-assistant-chat",
162
+ runId: traceId,
163
+ metadata: promptMetadata,
164
+ callbacks
165
+ }
132
166
  );
133
167
  const responseMessages = [];
134
168
  for await (const [, chunk] of promptStream) {
135
169
  const { messages: promptMessages } = chunk;
136
- const newMessages = promptMessages.filter((m) => responseMessages.findIndex((rm) => rm.id === m.id) === -1).filter(
170
+ const newMessages = promptMessages.filter(
171
+ (m) => responseMessages.findIndex(
172
+ (rm) => m.id === rm.metadata.langGraphId
173
+ ) === -1
174
+ ).filter(
137
175
  (m) => recentConversationMessages.findIndex((rm) => rm.id === m.id) === -1
138
176
  ).filter((m) => m.getType() !== "human").map((m) => {
139
- const id = m.id ?? "";
177
+ const id = uuid.v4();
140
178
  const role = m.getType();
141
179
  const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
142
- const metadata = {};
180
+ const metadata = {
181
+ langGraphId: m.id ?? ""
182
+ };
143
183
  if (role === "ai") {
144
184
  const aiMessage = m;
145
185
  metadata.toolCalls = aiMessage.tool_calls || [];
@@ -154,14 +194,18 @@ const createChatService = async ({
154
194
  id,
155
195
  role,
156
196
  content,
157
- metadata
197
+ metadata,
198
+ score: 0,
199
+ traceId
158
200
  };
159
201
  });
160
202
  for await (const m of newMessages) {
161
- const parts = m.content.split(" ");
203
+ const words = m.content.split(" ");
204
+ const chunkSize = 5;
162
205
  let messageBuilder = "";
163
- for await (const part of parts) {
164
- messageBuilder = messageBuilder.concat(part).concat(" ");
206
+ for (let i = 0; i < words.length; i += chunkSize) {
207
+ const wordChunk = words.slice(i, i + chunkSize).join(" ");
208
+ messageBuilder = messageBuilder.concat(wordChunk).concat(" ");
165
209
  m.content = messageBuilder;
166
210
  await new Promise((resolve) => setTimeout(resolve, 50));
167
211
  signals.publish({
@@ -176,9 +220,6 @@ const createChatService = async ({
176
220
  }
177
221
  responseMessages.push(...newMessages);
178
222
  }
179
- responseMessages.forEach((m) => {
180
- m.id = uuid.v4();
181
- });
182
223
  addMessages(responseMessages, userEntityRef, conversationId, [
183
224
  ...recentConversationMessages,
184
225
  ...messages
@@ -205,25 +246,30 @@ const createChatService = async ({
205
246
  const conversations = await chatStore$1.getConversations(userEntityRef);
206
247
  return conversations;
207
248
  };
249
+ const scoreMessage = async (messageId, score) => {
250
+ const message = await chatStore$1.getMessageById(messageId);
251
+ if (!message) {
252
+ throw new Error(`Message with id ${messageId} not found`);
253
+ }
254
+ const updatedMessage = {
255
+ ...message,
256
+ score
257
+ };
258
+ chatStore$1.updateMessage(updatedMessage);
259
+ callback.handleScoreCallbacks({
260
+ name: "helpfulness",
261
+ message: updatedMessage
262
+ });
263
+ };
208
264
  return {
209
265
  prompt,
210
266
  getAvailableModels,
211
267
  getConversation,
212
268
  getConversations,
213
- addMessages
269
+ addMessages,
270
+ scoreMessage
214
271
  };
215
272
  };
216
- async function getUser(cache, userEntityRef, credentials, catalog) {
217
- const cached = await cache.get(userEntityRef);
218
- if (cached) {
219
- return JSON.parse(String(cached));
220
- }
221
- const user = await catalog.getEntityByRef(userEntityRef, {
222
- credentials
223
- });
224
- await cache.set(userEntityRef, JSON.stringify(user));
225
- return user;
226
- }
227
273
 
228
274
  exports.createChatService = createChatService;
229
275
  //# sourceMappingURL=chat.cjs.js.map