@sweetoburrito/backstage-plugin-ai-assistant-backend 0.13.0 → 0.15.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.
Files changed (48) hide show
  1. package/dist/constants/prompts.cjs.js +1 -0
  2. package/dist/constants/prompts.cjs.js.map +1 -1
  3. package/dist/database/pg-vector-store.cjs.js +100 -38
  4. package/dist/database/pg-vector-store.cjs.js.map +1 -1
  5. package/dist/plugin.cjs.js +25 -41
  6. package/dist/plugin.cjs.js.map +1 -1
  7. package/dist/services/agent/helpers/message-parser.cjs.js +31 -0
  8. package/dist/services/agent/helpers/message-parser.cjs.js.map +1 -0
  9. package/dist/services/agent/helpers/tool-filter.cjs.js +13 -0
  10. package/dist/services/agent/helpers/tool-filter.cjs.js.map +1 -0
  11. package/dist/services/agent/index.cjs.js +140 -0
  12. package/dist/services/agent/index.cjs.js.map +1 -0
  13. package/dist/services/callbacks.cjs.js +23 -9
  14. package/dist/services/callbacks.cjs.js.map +1 -1
  15. package/dist/services/chat.cjs.js +88 -253
  16. package/dist/services/chat.cjs.js.map +1 -1
  17. package/dist/services/conversation.cjs.js +133 -0
  18. package/dist/services/conversation.cjs.js.map +1 -0
  19. package/dist/services/ingestor.cjs.js +5 -11
  20. package/dist/services/ingestor.cjs.js.map +1 -1
  21. package/dist/services/mcp/index.cjs.js +37 -24
  22. package/dist/services/mcp/index.cjs.js.map +1 -1
  23. package/dist/services/model.cjs.js +45 -0
  24. package/dist/services/model.cjs.js.map +1 -0
  25. package/dist/services/router/agent.cjs.js +68 -0
  26. package/dist/services/router/agent.cjs.js.map +1 -0
  27. package/dist/services/router/chat.cjs.js +8 -13
  28. package/dist/services/router/chat.cjs.js.map +1 -1
  29. package/dist/services/router/index.cjs.js +4 -0
  30. package/dist/services/router/index.cjs.js.map +1 -1
  31. package/dist/services/router/models.cjs.js +2 -2
  32. package/dist/services/router/models.cjs.js.map +1 -1
  33. package/dist/services/router/settings/index.cjs.js +0 -3
  34. package/dist/services/router/settings/index.cjs.js.map +1 -1
  35. package/dist/services/router/summary.cjs.js +5 -3
  36. package/dist/services/router/summary.cjs.js.map +1 -1
  37. package/dist/services/router/tools.cjs.js +21 -0
  38. package/dist/services/router/tools.cjs.js.map +1 -0
  39. package/dist/services/summarizer.cjs.js +47 -28
  40. package/dist/services/summarizer.cjs.js.map +1 -1
  41. package/dist/services/tools.cjs.js +65 -0
  42. package/dist/services/tools.cjs.js.map +1 -0
  43. package/dist/services/user-settings.cjs.js +15 -0
  44. package/dist/services/user-settings.cjs.js.map +1 -1
  45. package/dist/tools/searchKnowledge.cjs.js +1 -6
  46. package/dist/tools/searchKnowledge.cjs.js.map +1 -1
  47. package/migrations/20260108_data_ingestion_refactor.js +27 -0
  48. package/package.json +6 -4
@@ -1 +1 @@
1
- {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import { CatalogService } from '@backstage/plugin-catalog-node';\nimport {\n LoggerService,\n RootConfigService,\n DatabaseService,\n AuthService,\n} from '@backstage/backend-plugin-api';\nimport { ChatStore } from '../database/chat-store';\nimport {\n Conversation,\n Message,\n JsonObject,\n Tool,\n UserTool,\n EnabledTool,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { SignalsService } from '@backstage/plugin-signals-node';\nimport {\n DEFAULT_FORMATTING_PROMPT,\n DEFAULT_IDENTITY_PROMPT,\n DEFAULT_SYSTEM_PROMPT,\n DEFAULT_TOOL_GUIDELINE,\n} from '../constants/prompts';\nimport {\n Model,\n getUser,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { DynamicStructuredTool } from '@langchain/core/tools';\nimport { createReactAgent } from '@langchain/langgraph/prebuilt';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\nimport { SummarizerService } from './summarizer';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n UserInfoService,\n} from '@backstage/backend-plugin-api';\nimport { AIMessage, ToolMessage } from '@langchain/core/messages';\nimport { McpService } from './mcp';\nimport { CallbackService } from './callbacks';\n\nexport type ChatServiceOptions = {\n models: Model[];\n tools: Tool[];\n logger: LoggerService;\n config: RootConfigService;\n database: DatabaseService;\n signals: SignalsService;\n catalog: CatalogService;\n cache: CacheService;\n auth: AuthService;\n mcp: McpService;\n userInfo: UserInfoService;\n callback: CallbackService;\n summarizer: SummarizerService;\n};\n\ntype PromptOptions = {\n modelId: string;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n userCredentials: BackstageCredentials;\n tools?: EnabledTool[];\n};\n\ntype GetConversationOptions = {\n conversationId: string;\n userEntityRef: string;\n};\n\ntype GetConversationsOptions = {\n userEntityRef: string;\n};\n\n// Helper type for messages with required fields except traceId which remains optional\ntype MessageWithRequiredFields = Required<Omit<Message, 'traceId'>> &\n Pick<Message, 'traceId'>;\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<MessageWithRequiredFields[]>;\n getAvailableModels: () => Promise<string[]>;\n getConversation: (\n options: GetConversationOptions,\n ) => Promise<MessageWithRequiredFields[]>;\n getConversations: (\n options: GetConversationsOptions,\n ) => Promise<Conversation[]>;\n addMessages: (\n messages: Message[],\n userRef: string,\n conversationId: string,\n recentConversationMessages?: Message[],\n ) => Promise<void>;\n scoreMessage: (messageId: string, score: number) => Promise<void>;\n getAvailableTools: (options: {\n credentials: BackstageCredentials;\n }) => Promise<UserTool[]>;\n};\n\nexport const createChatService = async ({\n models,\n tools,\n logger,\n database,\n signals,\n config,\n catalog,\n cache,\n auth,\n mcp,\n userInfo,\n callback,\n summarizer,\n}: ChatServiceOptions): Promise<ChatService> => {\n logger.info(`Available models: ${models.map(m => m.id).join(', ')}`);\n logger.info(`Available tools: ${tools.map(t => t.name).join(', ')}`);\n\n const identityPrompt =\n config.getOptionalString('aiAssistant.prompt.identity') ||\n DEFAULT_IDENTITY_PROMPT;\n\n const formattingPrompt =\n config.getOptionalString('aiAssistant.prompt.formatting') ||\n DEFAULT_FORMATTING_PROMPT;\n\n const contentPrompt =\n config.getOptionalString('aiAssistant.prompt.content') ||\n DEFAULT_SYSTEM_PROMPT;\n\n const combinedBasePrompt = `${identityPrompt}\\n\\n${formattingPrompt}\\n\\n${contentPrompt}`;\n\n const toolGuideline =\n config.getOptionalString('aiAssistant.prompt.toolGuideline') ||\n DEFAULT_TOOL_GUIDELINE;\n\n const chatStore = await ChatStore.fromConfig({ database });\n\n const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n PURPOSE:\n {basePrompt}\n\n TOOL USAGE GUIDELINES:\n {toolGuideline}\n\n Available tools:\n {toolList}\n\n Calling User:\n {user}\n\n Context:\n {context}`);\n\n const addMessages: ChatService['addMessages'] = async (\n messages,\n userRef,\n conversationId,\n recentConversationMessages,\n ) => {\n // If we have recentConversationMessages, use them; otherwise, fetch the last 5 messages\n const recentMessages =\n recentConversationMessages ||\n (await chatStore.getChatMessages(conversationId, userRef, 5, ['tool']));\n\n const conversationSize = (recentMessages?.length ?? 0) + messages.length;\n\n if (recentMessages.length === 0) {\n const conversation: Conversation = {\n id: conversationId,\n title: 'New Conversation',\n userRef,\n };\n chatStore.createConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n return;\n }\n\n if (conversationSize < 5) {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const conversation = await chatStore.getConversation(\n conversationId,\n userRef,\n );\n\n if (conversation.title !== 'New Conversation') {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const summary = await summarizer.summarizeConversation({\n messages: recentMessages,\n length: '25 characters',\n });\n\n conversation.title = summary;\n\n chatStore.updateConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n };\n\n const prompt: ChatService['prompt'] = async ({\n conversationId,\n messages,\n modelId,\n stream = true,\n userCredentials,\n tools: enabledTools,\n }: PromptOptions) => {\n const model = models.find(m => m.id === modelId)?.chatModel;\n\n if (!model) {\n throw new Error(`Model with id ${modelId} not found`);\n }\n\n const { userEntityRef } = await userInfo.getUserInfo(userCredentials);\n\n const streamFn = async () => {\n const recentConversationMessages = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n 10,\n ['tool'],\n );\n\n const credentials = await auth.getOwnServiceCredentials();\n const user = await getUser(cache, userEntityRef, credentials, catalog);\n\n const mcpTools = await mcp.getTools(userCredentials);\n\n const agentTools = [...tools, ...mcpTools]\n .filter(tool => {\n // If tools parameter is undefined, allow all tools\n if (enabledTools === undefined) return true;\n\n // If empty array, no tools should be enabled\n if (enabledTools.length === 0) return false;\n // Otherwise, only allow tools that are in the enabled list\n const enabled = enabledTools.find(\n enabledTool =>\n enabledTool.name === tool.name &&\n enabledTool.provider === tool.provider,\n );\n return !!enabled;\n })\n .map(tool => new DynamicStructuredTool(tool));\n\n const messagesWithoutSystem = messages.filter(m => m.role !== 'system');\n\n addMessages(\n messagesWithoutSystem,\n userEntityRef,\n conversationId,\n recentConversationMessages,\n );\n\n const systemPrompt = await systemPromptTemplate.formatMessages({\n basePrompt: combinedBasePrompt,\n toolGuideline,\n toolList: agentTools\n .map(tool => `- ${tool.name}: ${tool.description}`)\n .join('\\n'),\n context: `none`,\n user,\n });\n\n const agent = createReactAgent({\n llm: model,\n tools: agentTools,\n prompt: systemPrompt[0].text,\n });\n\n const { callbacks } = await callback.getChainCallbacks({\n conversationId,\n userId: userEntityRef,\n modelId,\n });\n\n const { metadata: promptMetadata } = await callback.getChainMetadata({\n conversationId,\n userId: userEntityRef,\n modelId,\n });\n\n const traceId = uuid();\n\n const promptStream = await agent.stream(\n {\n messages: [...recentConversationMessages, ...messages],\n },\n {\n streamMode: ['values'],\n runName: 'ai-assistant-chat',\n runId: traceId,\n metadata: promptMetadata,\n callbacks,\n },\n );\n\n const responseMessages: MessageWithRequiredFields[] = [];\n\n for await (const [, chunk] of promptStream) {\n const { messages: promptMessages } = chunk;\n\n const newMessages: MessageWithRequiredFields[] = promptMessages\n .filter(\n m =>\n responseMessages.findIndex(\n rm => m.id === rm.metadata.langGraphId,\n ) === -1,\n )\n .filter(\n m =>\n recentConversationMessages.findIndex(rm => rm.id === m.id) === -1,\n )\n .filter(m => m.getType() !== 'human')\n .map(m => {\n const id = uuid();\n const role = m.getType();\n const content =\n typeof m.content === 'string'\n ? m.content\n : JSON.stringify(m.content);\n\n const metadata: JsonObject = {\n langGraphId: m.id ?? '',\n };\n\n if (role === 'ai') {\n const aiMessage = m as AIMessage;\n metadata.toolCalls = aiMessage.tool_calls || [];\n metadata.finishReason =\n aiMessage.response_metadata.finish_reason || undefined;\n metadata.modelName =\n aiMessage.response_metadata.model_name || undefined;\n }\n\n if (role === 'tool') {\n const toolMessage = m as ToolMessage;\n metadata.name = toolMessage.name || '';\n }\n\n return {\n id,\n role,\n content,\n metadata,\n score: 0,\n traceId: traceId,\n };\n });\n\n // Simulate streaming until langchain messages error is better understood\n for await (const m of newMessages) {\n const words = m.content.split(' ');\n const chunkSize = 5; // Send 5 words at a time\n let messageBuilder = '';\n\n for (let i = 0; i < words.length; i += chunkSize) {\n const wordChunk = words.slice(i, i + chunkSize).join(' ');\n messageBuilder = messageBuilder.concat(wordChunk).concat(' ');\n m.content = messageBuilder;\n\n await new Promise(resolve => setTimeout(resolve, 50));\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-stream:${conversationId}`,\n message: { messages: [m] },\n recipients: {\n type: 'user',\n entityRef: userEntityRef,\n },\n });\n }\n }\n\n responseMessages.push(...newMessages);\n }\n\n addMessages(responseMessages, userEntityRef, conversationId, [\n ...recentConversationMessages,\n ...messages,\n ]);\n\n return responseMessages;\n };\n\n const result = streamFn();\n\n return stream ? [] : result;\n };\n\n const getAvailableModels: ChatService['getAvailableModels'] = async () => {\n return models.map(x => x.id);\n };\n\n const getConversation: ChatService['getConversation'] = async (\n options: GetConversationOptions,\n ) => {\n const { conversationId, userEntityRef } = options;\n\n const conversation = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n );\n\n return conversation;\n };\n\n const getConversations: ChatService['getConversations'] = async ({\n userEntityRef,\n }: GetConversationsOptions) => {\n const conversations = await chatStore.getConversations(userEntityRef);\n\n return conversations;\n };\n\n const scoreMessage: ChatService['scoreMessage'] = async (\n messageId: string,\n score: number,\n ) => {\n const message = await chatStore.getMessageById(messageId);\n\n if (!message) {\n throw new Error(`Message with id ${messageId} not found`);\n }\n\n const updatedMessage: Required<Message> = {\n ...message,\n score,\n };\n\n chatStore.updateMessage(updatedMessage);\n\n callback.handleScoreCallbacks({\n name: 'helpfulness',\n message: updatedMessage,\n });\n };\n\n const getAvailableTools: ChatService['getAvailableTools'] = async ({\n credentials,\n }) => {\n const mcpTools = await mcp.getTools(credentials);\n\n const availableTools: UserTool[] = tools.concat(mcpTools).map(tool => ({\n name: tool.name,\n provider: tool.provider,\n description: tool.description,\n }));\n\n return availableTools;\n };\n\n return {\n prompt,\n getAvailableModels,\n getConversation,\n getConversations,\n addMessages,\n scoreMessage,\n getAvailableTools,\n };\n};\n"],"names":["tools","DEFAULT_IDENTITY_PROMPT","DEFAULT_FORMATTING_PROMPT","DEFAULT_SYSTEM_PROMPT","DEFAULT_TOOL_GUIDELINE","chatStore","ChatStore","SystemMessagePromptTemplate","conversation","getUser","DynamicStructuredTool","createReactAgent","uuid"],"mappings":"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"}
1
+ {"version":3,"file":"chat.cjs.js","sources":["../../src/services/chat.ts"],"sourcesContent":["import {\n CatalogService,\n catalogServiceRef,\n} from '@backstage/plugin-catalog-node';\nimport {\n SignalsService,\n signalsServiceRef,\n} from '@backstage/plugin-signals-node';\n\nimport {\n Message,\n EnabledTool,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nimport { getUser } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { v4 as uuid } from 'uuid';\nimport type {\n BackstageCredentials,\n CacheService,\n UserInfoService,\n AuthService,\n ServiceRef,\n} from '@backstage/backend-plugin-api';\nimport {\n coreServices,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { ConversationService, conversationServiceRef } from './conversation';\nimport { agentServiceRef, AgentService } from './agent';\nimport { SystemMessagePromptTemplate } from '@langchain/core/prompts';\n\nexport type ChatServiceOptions = {\n signals: SignalsService;\n catalog: CatalogService;\n cache: CacheService;\n auth: AuthService;\n userInfo: UserInfoService;\n conversation: ConversationService;\n agent: AgentService;\n};\n\ntype PromptOptions = {\n credentials: BackstageCredentials;\n messages: Message[];\n conversationId: string;\n stream?: boolean;\n tools?: EnabledTool[];\n modelId?: string;\n};\n\nexport type ChatService = {\n prompt: (options: PromptOptions) => Promise<Message[]>;\n};\n\nexport const createChatService = async ({\n signals,\n catalog,\n cache,\n auth,\n userInfo,\n conversation,\n agent,\n}: ChatServiceOptions): Promise<ChatService> => {\n const contextPromptTemplate = SystemMessagePromptTemplate.fromTemplate(`\n Calling User:\n {user}`);\n\n const prompt: ChatService['prompt'] = async ({\n conversationId,\n messages,\n stream = true,\n credentials,\n tools: enabledTools,\n modelId,\n }: PromptOptions) => {\n const streamFn = async () => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n const recentConversationMessages =\n await conversation.getRecentConversationMessages({\n conversationId,\n userEntityRef,\n limit: 10,\n excludeRoles: ['tool'],\n });\n\n const user = await getUser(cache, userEntityRef, catalog, auth);\n\n const messagesWithoutSystem = messages.filter(m => m.role !== 'system');\n\n conversation.addMessages(\n messagesWithoutSystem,\n userEntityRef,\n conversationId,\n recentConversationMessages,\n );\n\n const traceId = uuid();\n\n const context = await contextPromptTemplate.formatMessages({\n user,\n });\n\n const responseMessages: Message[] = [];\n\n const conversationMessages = [...recentConversationMessages, ...messages];\n\n agent.stream({\n credentials,\n messages: conversationMessages,\n tools: enabledTools,\n modelId,\n metadata: {\n conversationId,\n userId: userEntityRef,\n runName: 'ai-assistant-chat',\n runId: traceId,\n },\n context: context[0].text,\n onStreamChunk: async chunkMessages => {\n const newMessages: Message[] = chunkMessages.filter(\n m => conversationMessages.findIndex(cm => cm.id === m.id) === -1,\n );\n\n if (newMessages.length !== 0) {\n conversation.addMessages(\n newMessages,\n userEntityRef,\n conversationId,\n conversationMessages,\n );\n\n conversationMessages.push(...newMessages);\n responseMessages.push(...newMessages);\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 },\n });\n\n return responseMessages;\n };\n\n return stream ? await streamFn() : [];\n };\n\n return {\n prompt,\n };\n};\n\nexport const chatServiceRef: ServiceRef<ChatService, 'plugin', 'singleton'> =\n createServiceRef<ChatService>({\n id: 'ai-assistant.chat-service',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {\n cache: coreServices.cache,\n auth: coreServices.auth,\n userInfo: coreServices.userInfo,\n signals: signalsServiceRef,\n catalog: catalogServiceRef,\n conversation: conversationServiceRef,\n agent: agentServiceRef,\n },\n factory: async options => {\n return createChatService(options);\n },\n }),\n });\n"],"names":["SystemMessagePromptTemplate","getUser","uuid","createServiceRef","createServiceFactory","coreServices","signalsServiceRef","catalogServiceRef","conversationServiceRef","agentServiceRef"],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+ var chatStore = require('../database/chat-store.cjs.js');
5
+ var callbacks = require('./callbacks.cjs.js');
6
+ var pluginSignalsNode = require('@backstage/plugin-signals-node');
7
+ var summarizer = require('./summarizer.cjs.js');
8
+
9
+ const createConversationService = async ({
10
+ database,
11
+ callback,
12
+ signals,
13
+ summarizer
14
+ }) => {
15
+ const chatStore$1 = await chatStore.ChatStore.fromConfig({ database });
16
+ const getConversation = async (options) => {
17
+ const { conversationId, userEntityRef } = options;
18
+ const conversation = await chatStore$1.getChatMessages(
19
+ conversationId,
20
+ userEntityRef
21
+ );
22
+ return conversation;
23
+ };
24
+ const getConversations = async ({
25
+ userEntityRef
26
+ }) => {
27
+ const conversations = await chatStore$1.getConversations(userEntityRef);
28
+ return conversations;
29
+ };
30
+ const scoreMessage = async (messageId, score) => {
31
+ const message = await chatStore$1.getMessageById(messageId);
32
+ if (!message) {
33
+ throw new Error(`Message with id ${messageId} not found`);
34
+ }
35
+ const updatedMessage = {
36
+ ...message,
37
+ score
38
+ };
39
+ chatStore$1.updateMessage(updatedMessage);
40
+ callback.handleScoreCallbacks({
41
+ name: "helpfulness",
42
+ message: updatedMessage
43
+ });
44
+ };
45
+ const getRecentConversationMessages = async ({ conversationId, userEntityRef, excludeRoles, limit }) => {
46
+ const recentConversationMessages = await chatStore$1.getChatMessages(
47
+ conversationId,
48
+ userEntityRef,
49
+ limit,
50
+ excludeRoles
51
+ );
52
+ return recentConversationMessages;
53
+ };
54
+ const addMessages = async (messages, userRef, conversationId, recentConversationMessages) => {
55
+ const recentMessages = recentConversationMessages || await getRecentConversationMessages({
56
+ conversationId,
57
+ userEntityRef: userRef,
58
+ limit: 5,
59
+ excludeRoles: ["tool"]
60
+ });
61
+ const conversationSize = (recentMessages?.length ?? 0) + messages.length;
62
+ if (recentMessages.length === 0) {
63
+ const conversation2 = {
64
+ id: conversationId,
65
+ title: "New Conversation",
66
+ userRef
67
+ };
68
+ chatStore$1.createConversation(conversation2);
69
+ chatStore$1.addChatMessage(messages, userRef, conversationId);
70
+ signals.publish({
71
+ channel: `ai-assistant.chat.conversation-details-update`,
72
+ message: { conversation: conversation2 },
73
+ recipients: {
74
+ type: "user",
75
+ entityRef: userRef
76
+ }
77
+ });
78
+ return;
79
+ }
80
+ if (conversationSize < 5) {
81
+ chatStore$1.addChatMessage(messages, userRef, conversationId);
82
+ return;
83
+ }
84
+ const conversation = await chatStore$1.getConversation(
85
+ conversationId,
86
+ userRef
87
+ );
88
+ if (conversation.title !== "New Conversation") {
89
+ chatStore$1.addChatMessage(messages, userRef, conversationId);
90
+ return;
91
+ }
92
+ const summary = await summarizer.summarizeConversation({
93
+ messages: recentMessages,
94
+ length: "25 characters"
95
+ });
96
+ conversation.title = summary;
97
+ chatStore$1.updateConversation(conversation);
98
+ chatStore$1.addChatMessage(messages, userRef, conversationId);
99
+ signals.publish({
100
+ channel: `ai-assistant.chat.conversation-details-update`,
101
+ message: { conversation },
102
+ recipients: {
103
+ type: "user",
104
+ entityRef: userRef
105
+ }
106
+ });
107
+ };
108
+ return {
109
+ getConversation,
110
+ getConversations,
111
+ addMessages,
112
+ scoreMessage,
113
+ getRecentConversationMessages
114
+ };
115
+ };
116
+ const conversationServiceRef = backendPluginApi.createServiceRef({
117
+ id: "ai-assistant.conversation-service",
118
+ defaultFactory: async (service) => backendPluginApi.createServiceFactory({
119
+ service,
120
+ deps: {
121
+ database: backendPluginApi.coreServices.database,
122
+ callback: callbacks.callbackServiceRef,
123
+ signals: pluginSignalsNode.signalsServiceRef,
124
+ summarizer: summarizer.summarizerServiceRef
125
+ },
126
+ factory: async (options) => {
127
+ return createConversationService(options);
128
+ }
129
+ })
130
+ });
131
+
132
+ exports.conversationServiceRef = conversationServiceRef;
133
+ //# sourceMappingURL=conversation.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation.cjs.js","sources":["../../src/services/conversation.ts"],"sourcesContent":["import {\n coreServices,\n createServiceFactory,\n createServiceRef,\n DatabaseService,\n ServiceRef,\n} from '@backstage/backend-plugin-api';\n\nimport { ChatStore } from '../database/chat-store';\nimport {\n Conversation,\n Message,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { CallbackService, callbackServiceRef } from './callbacks';\n\nimport {\n SignalsService,\n signalsServiceRef,\n} from '@backstage/plugin-signals-node';\nimport { SummarizerService, summarizerServiceRef } from './summarizer';\n\nexport type ConversationService = {\n getConversation: (options: {\n conversationId: string;\n userEntityRef: string;\n }) => Promise<Message[]>;\n getConversations: (options: {\n userEntityRef: string;\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 getRecentConversationMessages: (options: {\n conversationId: string;\n userEntityRef: string;\n limit?: number;\n excludeRoles?: Message['role'][];\n }) => Promise<Message[]>;\n};\n\nexport type CreateConversationServiceOptions = {\n database: DatabaseService;\n callback: CallbackService;\n signals: SignalsService;\n summarizer: SummarizerService;\n};\n\nconst createConversationService = async ({\n database,\n callback,\n signals,\n summarizer,\n}: CreateConversationServiceOptions): Promise<ConversationService> => {\n const chatStore = await ChatStore.fromConfig({ database });\n\n const getConversation: ConversationService['getConversation'] =\n async options => {\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: ConversationService['getConversations'] = async ({\n userEntityRef,\n }) => {\n const conversations = await chatStore.getConversations(userEntityRef);\n\n return conversations;\n };\n\n const scoreMessage: ConversationService['scoreMessage'] = async (\n messageId: string,\n score: number,\n ) => {\n const message = await chatStore.getMessageById(messageId);\n\n if (!message) {\n throw new Error(`Message with id ${messageId} not found`);\n }\n\n const updatedMessage: Required<Message> = {\n ...message,\n score,\n };\n\n chatStore.updateMessage(updatedMessage);\n\n callback.handleScoreCallbacks({\n name: 'helpfulness',\n message: updatedMessage,\n });\n };\n\n const getRecentConversationMessages: ConversationService['getRecentConversationMessages'] =\n async ({ conversationId, userEntityRef, excludeRoles, limit }) => {\n const recentConversationMessages = await chatStore.getChatMessages(\n conversationId,\n userEntityRef,\n limit,\n excludeRoles,\n );\n\n return recentConversationMessages;\n };\n\n const addMessages: ConversationService['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 getRecentConversationMessages({\n conversationId,\n userEntityRef: userRef,\n limit: 5,\n excludeRoles: ['tool'],\n }));\n\n const conversationSize = (recentMessages?.length ?? 0) + messages.length;\n\n if (recentMessages.length === 0) {\n const conversation: Conversation = {\n id: conversationId,\n title: 'New Conversation',\n userRef,\n };\n chatStore.createConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n return;\n }\n\n if (conversationSize < 5) {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const conversation = await chatStore.getConversation(\n conversationId,\n userRef,\n );\n\n if (conversation.title !== 'New Conversation') {\n chatStore.addChatMessage(messages, userRef, conversationId);\n return;\n }\n\n const summary = await summarizer.summarizeConversation({\n messages: recentMessages,\n length: '25 characters',\n });\n\n conversation.title = summary;\n\n chatStore.updateConversation(conversation);\n chatStore.addChatMessage(messages, userRef, conversationId);\n\n signals.publish({\n channel: `ai-assistant.chat.conversation-details-update`,\n message: { conversation },\n recipients: {\n type: 'user',\n entityRef: userRef,\n },\n });\n };\n\n return {\n getConversation,\n getConversations,\n addMessages,\n scoreMessage,\n getRecentConversationMessages,\n };\n};\n\nexport const conversationServiceRef: ServiceRef<\n ConversationService,\n 'plugin',\n 'singleton'\n> = createServiceRef<ConversationService>({\n id: 'ai-assistant.conversation-service',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {\n database: coreServices.database,\n callback: callbackServiceRef,\n signals: signalsServiceRef,\n summarizer: summarizerServiceRef,\n },\n factory: async options => {\n return createConversationService(options);\n },\n }),\n});\n"],"names":["chatStore","ChatStore","conversation","createServiceRef","createServiceFactory","coreServices","callbackServiceRef","signalsServiceRef","summarizerServiceRef"],"mappings":"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"}
@@ -41,7 +41,7 @@ const createDataIngestionPipeline = ({
41
41
  for await (const ingestor of ingestors) {
42
42
  logger.info(`Running ingestor: ${ingestor.id}`);
43
43
  const saveDocumentsBatch = async (documents2) => {
44
- logger.info(
44
+ logger.debug(
45
45
  `Ingested documents for ${ingestor.id}: ${documents2.length}`
46
46
  );
47
47
  const splitter = new textsplitters.RecursiveCharacterTextSplitter({
@@ -50,12 +50,6 @@ const createDataIngestionPipeline = ({
50
50
  });
51
51
  const documentChunks = await Promise.all(
52
52
  documents2.map(async (document) => {
53
- logger.debug(
54
- `Deleting existing documents with id: [${document.metadata.id}] and source: [${ingestor.id}]`
55
- );
56
- await vectorStore.deleteDocuments({
57
- filter: { source: ingestor.id, id: document.metadata.id }
58
- });
59
53
  const chunks = await splitter.splitText(document.content);
60
54
  const docChunks = chunks.flatMap(
61
55
  (chunk, i) => ({
@@ -66,19 +60,19 @@ const createDataIngestionPipeline = ({
66
60
  return docChunks;
67
61
  })
68
62
  );
69
- logger.info(`Adding documents to vector store...`);
63
+ logger.debug(`Adding documents to vector store...`);
70
64
  const allChunks = documentChunks.flat();
71
- logger.info(
65
+ logger.debug(
72
66
  `Total document chunks for batch to add for ${ingestor.id}: ${allChunks.length}`
73
67
  );
74
68
  for (let i = 0; i < allChunks.length; i += maxChunkProcessingSize) {
75
69
  const chunkBatch = allChunks.slice(i, i + maxChunkProcessingSize);
76
- logger.info(
70
+ logger.debug(
77
71
  `Adding batch of ${chunkBatch.length} document chunks to vector store for ${ingestor.id}`
78
72
  );
79
73
  await vectorStore.addDocuments(chunkBatch);
80
74
  }
81
- logger.info(`Added documents to vector store for ${ingestor.id}`);
75
+ logger.debug(`Added documents to vector store for ${ingestor.id}`);
82
76
  };
83
77
  const documents = await ingestor.ingest({
84
78
  saveDocumentsBatch
@@ -1 +1 @@
1
- {"version":3,"file":"ingestor.cjs.js","sources":["../../src/services/ingestor.ts"],"sourcesContent":["import {\n DataIngestionPipeline,\n DataIngestionPipelineOptions,\n EmbeddingDocument,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nimport {\n SchedulerServiceTaskScheduleDefinition,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n} from '@backstage/backend-plugin-api';\n\nimport { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';\n\nconst DEFAULT_DATA_INGESTION_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: {\n hours: 24,\n },\n timeout: {\n hours: 3,\n },\n };\n\nconst DEFAULT_MAX_CHUNK_PROCESSING_SIZE = 100;\nconst DEFAULT_CHUNK_SIZE = 1000;\nconst DEFAULT_CHUNK_OVERLAP = 100;\n\nexport const createDataIngestionPipeline = ({\n config,\n logger,\n scheduler,\n ingestors,\n vectorStore,\n}: DataIngestionPipelineOptions): DataIngestionPipeline => {\n const schedule = config.has('aiAssistant.ingestion.schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('aiAssistant.ingestion.schedule'),\n )\n : DEFAULT_DATA_INGESTION_SCHEDULE;\n\n const chunkSize =\n config.getOptionalNumber('aiAssistant.ingestion.chunking.chunkSize') ??\n DEFAULT_CHUNK_SIZE;\n\n const chunkOverlap =\n config.getOptionalNumber('aiAssistant.ingestion.chunking.chunkOverlap') ??\n DEFAULT_CHUNK_OVERLAP;\n\n const maxChunkProcessingSize =\n config.getOptionalNumber(\n 'aiAssistant.ingestion.chunking.maxChunkProcessingSize',\n ) ?? DEFAULT_MAX_CHUNK_PROCESSING_SIZE;\n\n const taskRunner = scheduler.createScheduledTaskRunner(schedule);\n\n const taskId = `ai-assistant.data-ingestion:start`;\n\n const dataIngestion = async () => {\n logger.info('Starting data ingestion...');\n\n if (ingestors.length === 0) {\n logger.warn('No ingestors available for data ingestion.');\n return;\n }\n\n logger.info(`Ingestors available: ${ingestors.map(i => i.id).join(', ')}`);\n\n for await (const ingestor of ingestors) {\n logger.info(`Running ingestor: ${ingestor.id}`);\n\n const saveDocumentsBatch = async (documents: EmbeddingDocument[]) => {\n logger.info(\n `Ingested documents for ${ingestor.id}: ${documents.length}`,\n );\n\n const splitter = new RecursiveCharacterTextSplitter({\n chunkSize,\n chunkOverlap,\n });\n\n const documentChunks = await Promise.all(\n documents.map(async document => {\n // Delete existing documents with this document id and ingestor source\n logger.debug(\n `Deleting existing documents with id: [${document.metadata.id}] and source: [${ingestor.id}]`,\n );\n await vectorStore.deleteDocuments({\n filter: { source: ingestor.id, id: document.metadata.id },\n });\n\n const chunks = await splitter.splitText(document.content);\n\n const docChunks: EmbeddingDocument[] = chunks.flatMap(\n (chunk, i) => ({\n metadata: { ...document.metadata, chunk: String(i) },\n content: chunk,\n }),\n );\n\n return docChunks;\n }),\n );\n\n logger.info(`Adding documents to vector store...`);\n const allChunks = documentChunks.flat();\n\n logger.info(\n `Total document chunks for batch to add for ${ingestor.id}: ${allChunks.length}`,\n );\n\n for (let i = 0; i < allChunks.length; i += maxChunkProcessingSize) {\n const chunkBatch = allChunks.slice(i, i + maxChunkProcessingSize);\n logger.info(\n `Adding batch of ${chunkBatch.length} document chunks to vector store for ${ingestor.id}`,\n );\n\n await vectorStore.addDocuments(chunkBatch);\n }\n\n logger.info(`Added documents to vector store for ${ingestor.id}`);\n };\n\n const documents = await ingestor.ingest({\n saveDocumentsBatch,\n });\n\n if (documents) {\n saveDocumentsBatch(documents);\n }\n\n logger.info(`Finished processing ingestor: ${ingestor.id}`);\n }\n\n logger.info('Data ingestion completed.');\n };\n\n const start = async () => {\n taskRunner.run({\n id: taskId,\n fn: dataIngestion,\n });\n };\n\n return {\n start,\n };\n};\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","documents","RecursiveCharacterTextSplitter"],"mappings":"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"}
1
+ {"version":3,"file":"ingestor.cjs.js","sources":["../../src/services/ingestor.ts"],"sourcesContent":["import {\n DataIngestionPipeline,\n DataIngestionPipelineOptions,\n EmbeddingDocument,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nimport {\n SchedulerServiceTaskScheduleDefinition,\n readSchedulerServiceTaskScheduleDefinitionFromConfig,\n} from '@backstage/backend-plugin-api';\n\nimport { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';\n\nconst DEFAULT_DATA_INGESTION_SCHEDULE: SchedulerServiceTaskScheduleDefinition =\n {\n frequency: {\n hours: 24,\n },\n timeout: {\n hours: 3,\n },\n };\n\nconst DEFAULT_MAX_CHUNK_PROCESSING_SIZE = 100;\nconst DEFAULT_CHUNK_SIZE = 1000;\nconst DEFAULT_CHUNK_OVERLAP = 100;\n\nexport const createDataIngestionPipeline = ({\n config,\n logger,\n scheduler,\n ingestors,\n vectorStore,\n}: DataIngestionPipelineOptions): DataIngestionPipeline => {\n const schedule = config.has('aiAssistant.ingestion.schedule')\n ? readSchedulerServiceTaskScheduleDefinitionFromConfig(\n config.getConfig('aiAssistant.ingestion.schedule'),\n )\n : DEFAULT_DATA_INGESTION_SCHEDULE;\n\n const chunkSize =\n config.getOptionalNumber('aiAssistant.ingestion.chunking.chunkSize') ??\n DEFAULT_CHUNK_SIZE;\n\n const chunkOverlap =\n config.getOptionalNumber('aiAssistant.ingestion.chunking.chunkOverlap') ??\n DEFAULT_CHUNK_OVERLAP;\n\n const maxChunkProcessingSize =\n config.getOptionalNumber(\n 'aiAssistant.ingestion.chunking.maxChunkProcessingSize',\n ) ?? DEFAULT_MAX_CHUNK_PROCESSING_SIZE;\n\n const taskRunner = scheduler.createScheduledTaskRunner(schedule);\n\n const taskId = `ai-assistant.data-ingestion:start`;\n\n const dataIngestion = async () => {\n logger.info('Starting data ingestion...');\n\n if (ingestors.length === 0) {\n logger.warn('No ingestors available for data ingestion.');\n return;\n }\n\n logger.info(`Ingestors available: ${ingestors.map(i => i.id).join(', ')}`);\n\n for await (const ingestor of ingestors) {\n logger.info(`Running ingestor: ${ingestor.id}`);\n\n const saveDocumentsBatch = async (documents: EmbeddingDocument[]) => {\n logger.debug(\n `Ingested documents for ${ingestor.id}: ${documents.length}`,\n );\n\n const splitter = new RecursiveCharacterTextSplitter({\n chunkSize,\n chunkOverlap,\n });\n\n const documentChunks = await Promise.all(\n documents.map(async document => {\n const chunks = await splitter.splitText(document.content);\n\n const docChunks: EmbeddingDocument[] = chunks.flatMap(\n (chunk, i) => ({\n metadata: { ...document.metadata, chunk: String(i) },\n content: chunk,\n }),\n );\n\n return docChunks;\n }),\n );\n\n logger.debug(`Adding documents to vector store...`);\n const allChunks = documentChunks.flat();\n\n logger.debug(\n `Total document chunks for batch to add for ${ingestor.id}: ${allChunks.length}`,\n );\n\n for (let i = 0; i < allChunks.length; i += maxChunkProcessingSize) {\n const chunkBatch = allChunks.slice(i, i + maxChunkProcessingSize);\n logger.debug(\n `Adding batch of ${chunkBatch.length} document chunks to vector store for ${ingestor.id}`,\n );\n\n await vectorStore.addDocuments(chunkBatch);\n }\n\n logger.debug(`Added documents to vector store for ${ingestor.id}`);\n };\n\n const documents = await ingestor.ingest({\n saveDocumentsBatch,\n });\n\n if (documents) {\n saveDocumentsBatch(documents);\n }\n\n logger.info(`Finished processing ingestor: ${ingestor.id}`);\n }\n\n logger.info('Data ingestion completed.');\n };\n\n const start = async () => {\n taskRunner.run({\n id: taskId,\n fn: dataIngestion,\n });\n };\n\n return {\n start,\n };\n};\n"],"names":["readSchedulerServiceTaskScheduleDefinitionFromConfig","documents","RecursiveCharacterTextSplitter"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
1
  'use strict';
2
2
 
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
3
4
  var mcpAdapters = require('@langchain/mcp-adapters');
4
5
  var backstagePluginAiAssistantNode = require('@sweetoburrito/backstage-plugin-ai-assistant-node');
5
6
  var helpers = require('./helpers.cjs.js');
6
- var userSettingsStore = require('../../database/user-settings-store.cjs.js');
7
+ var userSettings = require('../user-settings.cjs.js');
7
8
 
8
9
  const MCP_SETTINGS_TYPE = "mcp_server_config";
9
10
  const createMcpService = async ({
10
11
  config,
11
- userInfo,
12
- database
12
+ userSettings
13
13
  }) => {
14
14
  const serversConfig = config.getOptionalConfigArray(
15
15
  "aiAssistant.mcp.servers"
@@ -21,31 +21,30 @@ const createMcpService = async ({
21
21
  acc[serverName] = options;
22
22
  return acc;
23
23
  }, {}) : {};
24
- const userSettingsStore$1 = await userSettingsStore.UserSettingsStore.fromConfig({ database });
25
24
  const getUserMcpServerConfigNames = async (credentials) => {
26
- const { userEntityRef } = await userInfo.getUserInfo(credentials);
27
- const mcpConfig = await userSettingsStore$1.getUserSettingsByType(
28
- userEntityRef,
25
+ const mcpConfig = await userSettings.getSettingsForType(
26
+ credentials,
29
27
  MCP_SETTINGS_TYPE
30
28
  );
31
- if (!mcpConfig) {
29
+ const names = Object.keys(mcpConfig);
30
+ if (names.length === 0) {
32
31
  return [];
33
32
  }
34
- const names = Object.keys(mcpConfig);
35
33
  return names;
36
34
  };
37
35
  const getUserMcpServerConfig = async (credentials) => {
38
- const { userEntityRef } = await userInfo.getUserInfo(credentials);
39
- const mcpConfigEncrypted = await userSettingsStore$1.getUserSettingsByType(userEntityRef, MCP_SETTINGS_TYPE);
40
- if (!mcpConfigEncrypted) {
36
+ const mcpConfigEncrypted = await userSettings.getSettingsForType(
37
+ credentials,
38
+ MCP_SETTINGS_TYPE
39
+ );
40
+ const entries = Object.entries(mcpConfigEncrypted);
41
+ if (entries.length === 0) {
41
42
  return [];
42
43
  }
43
- const mcpConfig = Object.entries(mcpConfigEncrypted).map(
44
- ([name, data]) => ({
45
- name,
46
- options: JSON.parse(backstagePluginAiAssistantNode.decrypt(data, encryptionKey))
47
- })
48
- );
44
+ const mcpConfig = entries.map(([name, data]) => ({
45
+ name,
46
+ options: JSON.parse(backstagePluginAiAssistantNode.decrypt(data, encryptionKey))
47
+ }));
49
48
  return mcpConfig;
50
49
  };
51
50
  const getTools = async (credentials) => {
@@ -94,7 +93,6 @@ const createMcpService = async ({
94
93
  }
95
94
  };
96
95
  const setUserMcpServerConfig = async (credentials, mcpConfig) => {
97
- const { userEntityRef } = await userInfo.getUserInfo(credentials);
98
96
  const { name } = mcpConfig;
99
97
  const existingConfig = await getUserMcpServerConfig(credentials);
100
98
  const existingServerIndex = existingConfig.findIndex(
@@ -116,14 +114,13 @@ const createMcpService = async ({
116
114
  },
117
115
  {}
118
116
  );
119
- await userSettingsStore$1.setUserSettings(
120
- userEntityRef,
117
+ await userSettings.setSettingsForType(
118
+ credentials,
121
119
  MCP_SETTINGS_TYPE,
122
120
  updatedConfig
123
121
  );
124
122
  };
125
123
  const deleteUserMcpServerConfig = async (credentials, name) => {
126
- const { userEntityRef } = await userInfo.getUserInfo(credentials);
127
124
  const existingConfig = await getUserMcpServerConfig(credentials);
128
125
  const updatedConfig = existingConfig.filter((server) => server.name !== name).reduce((acc, server) => {
129
126
  acc[server.name] = backstagePluginAiAssistantNode.encrypt(
@@ -132,8 +129,8 @@ const createMcpService = async ({
132
129
  );
133
130
  return acc;
134
131
  }, {});
135
- await userSettingsStore$1.setUserSettings(
136
- userEntityRef,
132
+ await userSettings.setSettingsForType(
133
+ credentials,
137
134
  MCP_SETTINGS_TYPE,
138
135
  updatedConfig
139
136
  );
@@ -145,6 +142,22 @@ const createMcpService = async ({
145
142
  setUserMcpServerConfig
146
143
  };
147
144
  };
145
+ const mcpServiceRef = backendPluginApi.createServiceRef({
146
+ id: "ai-assistant.mcp-service",
147
+ defaultFactory: async (service) => backendPluginApi.createServiceFactory({
148
+ service,
149
+ deps: {
150
+ config: backendPluginApi.coreServices.rootConfig,
151
+ userInfo: backendPluginApi.coreServices.userInfo,
152
+ database: backendPluginApi.coreServices.database,
153
+ userSettings: userSettings.userSettingsServiceRef
154
+ },
155
+ factory: async (options) => {
156
+ return createMcpService(options);
157
+ }
158
+ })
159
+ });
148
160
 
149
161
  exports.createMcpService = createMcpService;
162
+ exports.mcpServiceRef = mcpServiceRef;
150
163
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../../../src/services/mcp/index.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 Tool,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport {\n encrypt,\n decrypt,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { getToolsForServer } from './helpers';\nimport { UserSettingsStore } from '../../database/user-settings-store';\n\ntype CreateMcpServiceOptions = {\n config: RootConfigService;\n userInfo: UserInfoService;\n database: DatabaseService;\n};\n\nconst MCP_SETTINGS_TYPE = 'mcp_server_config';\n\nexport type McpService = {\n getTools: (credentials: BackstageCredentials) => Promise<Tool[]>;\n getUserMcpServerConfigNames: (\n credentials: BackstageCredentials,\n ) => Promise<string[]>;\n deleteUserMcpServerConfig: (\n credentials: BackstageCredentials,\n name: string,\n ) => Promise<void>;\n setUserMcpServerConfig: (\n credentials: BackstageCredentials,\n config: McpServerConfig,\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: Record<string, McpServerConfigOptions> =\n 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 : {};\n\n const userSettingsStore = await UserSettingsStore.fromConfig({ database });\n\n const getUserMcpServerConfigNames: McpService['getUserMcpServerConfigNames'] =\n async credentials => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const mcpConfig = await userSettingsStore.getUserSettingsByType(\n userEntityRef,\n MCP_SETTINGS_TYPE,\n );\n if (!mcpConfig) {\n return [];\n }\n\n const names = Object.keys(mcpConfig);\n\n return names;\n };\n\n const getUserMcpServerConfig = async (\n credentials: BackstageCredentials,\n ): Promise<McpServerConfig[]> => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const mcpConfigEncrypted = await userSettingsStore.getUserSettingsByType<\n Record<string, string>\n >(userEntityRef, MCP_SETTINGS_TYPE);\n\n if (!mcpConfigEncrypted) {\n return [];\n }\n\n const mcpConfig: McpServerConfig[] = Object.entries(mcpConfigEncrypted).map(\n ([name, data]) => ({\n name,\n options: JSON.parse(decrypt(data, encryptionKey)),\n }),\n );\n\n return mcpConfig;\n };\n\n const getTools: McpService['getTools'] = async credentials => {\n const userMcpConfig = await getUserMcpServerConfig(credentials);\n\n const userMcpServers: Record<string, McpServerConfigOptions> =\n userMcpConfig.length\n ? 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\n const mcpServers: Record<string, McpServerConfigOptions> = {\n ...preConfiguredMcpServers,\n ...userMcpServers,\n };\n\n const serverNames = Object.keys(mcpServers);\n\n if (serverNames.length === 0) {\n return [];\n }\n\n const mcpClient = new MultiServerMCPClient({\n prefixToolNameWithServerName: true,\n useStandardContentBlocks: true,\n mcpServers,\n });\n\n const serverToolPromises = serverNames.map(serverName =>\n getToolsForServer(mcpClient, serverName),\n );\n const toolsByServer = await Promise.all(serverToolPromises);\n\n return toolsByServer.flat();\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 setUserMcpServerConfig: McpService['setUserMcpServerConfig'] = async (\n credentials,\n mcpConfig,\n ) => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n const { name } = mcpConfig;\n\n const existingConfig = await getUserMcpServerConfig(credentials);\n\n const existingServerIndex = existingConfig.findIndex(\n server => server.name === name,\n );\n\n if (existingServerIndex === -1) {\n existingConfig.push(mcpConfig);\n } else {\n existingConfig[existingServerIndex] = mcpConfig;\n }\n\n await validateMcpServerConfig(existingConfig);\n\n const updatedConfig: Record<string, string> = existingConfig.reduce(\n (acc, server) => {\n acc[server.name] = encrypt(\n JSON.stringify(server.options),\n encryptionKey,\n );\n return acc;\n },\n {} as Record<string, string>,\n );\n\n await userSettingsStore.setUserSettings(\n userEntityRef,\n MCP_SETTINGS_TYPE,\n updatedConfig,\n );\n };\n\n const deleteUserMcpServerConfig: McpService['deleteUserMcpServerConfig'] =\n async (credentials, name) => {\n const { userEntityRef } = await userInfo.getUserInfo(credentials);\n\n const existingConfig = await getUserMcpServerConfig(credentials);\n\n const updatedConfig: Record<string, string> = existingConfig\n .filter(server => server.name !== name)\n .reduce((acc, server) => {\n acc[server.name] = encrypt(\n JSON.stringify(server.options),\n encryptionKey,\n );\n return acc;\n }, {} as Record<string, string>);\n\n await userSettingsStore.setUserSettings(\n userEntityRef,\n MCP_SETTINGS_TYPE,\n updatedConfig,\n );\n };\n\n return {\n getTools,\n getUserMcpServerConfigNames,\n deleteUserMcpServerConfig,\n setUserMcpServerConfig,\n };\n};\n"],"names":["userSettingsStore","UserSettingsStore","decrypt","MultiServerMCPClient","getToolsForServer","encrypt"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../../../src/services/mcp/index.ts"],"sourcesContent":["import {\n BackstageCredentials,\n DatabaseService,\n RootConfigService,\n createServiceFactory,\n createServiceRef,\n ServiceRef,\n coreServices,\n} from '@backstage/backend-plugin-api';\nimport { MultiServerMCPClient } from '@langchain/mcp-adapters';\nimport {\n McpServerConfig,\n McpServerConfigOptions,\n Tool,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport {\n encrypt,\n decrypt,\n} from '@sweetoburrito/backstage-plugin-ai-assistant-node';\nimport { getToolsForServer } from './helpers';\nimport { UserSettingsService, userSettingsServiceRef } from '../user-settings';\n\ntype CreateMcpServiceOptions = {\n config: RootConfigService;\n database: DatabaseService;\n userSettings: UserSettingsService;\n};\n\nconst MCP_SETTINGS_TYPE = 'mcp_server_config';\n\nexport type McpService = {\n getTools: (credentials: BackstageCredentials) => Promise<Tool[]>;\n getUserMcpServerConfigNames: (\n credentials: BackstageCredentials,\n ) => Promise<string[]>;\n deleteUserMcpServerConfig: (\n credentials: BackstageCredentials,\n name: string,\n ) => Promise<void>;\n setUserMcpServerConfig: (\n credentials: BackstageCredentials,\n config: McpServerConfig,\n ) => Promise<void>;\n};\n\nexport const createMcpService = async ({\n config,\n userSettings,\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: Record<string, McpServerConfigOptions> =\n 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 : {};\n\n const getUserMcpServerConfigNames: McpService['getUserMcpServerConfigNames'] =\n async credentials => {\n const mcpConfig = await userSettings.getSettingsForType(\n credentials,\n MCP_SETTINGS_TYPE,\n );\n\n const names = Object.keys(mcpConfig);\n\n if (names.length === 0) {\n return [];\n }\n\n return names;\n };\n\n const getUserMcpServerConfig = async (\n credentials: BackstageCredentials,\n ): Promise<McpServerConfig[]> => {\n const mcpConfigEncrypted = await userSettings.getSettingsForType(\n credentials,\n MCP_SETTINGS_TYPE,\n );\n\n const entries = Object.entries(mcpConfigEncrypted) as [string, string][];\n\n if (entries.length === 0) {\n return [];\n }\n\n const mcpConfig: McpServerConfig[] = entries.map(([name, data]) => ({\n name,\n options: JSON.parse(decrypt(data, 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: Record<string, McpServerConfigOptions> =\n userMcpConfig.length\n ? 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\n const mcpServers: Record<string, McpServerConfigOptions> = {\n ...preConfiguredMcpServers,\n ...userMcpServers,\n };\n\n const serverNames = Object.keys(mcpServers);\n\n if (serverNames.length === 0) {\n return [];\n }\n\n const mcpClient = new MultiServerMCPClient({\n prefixToolNameWithServerName: true,\n useStandardContentBlocks: true,\n mcpServers,\n });\n\n const serverToolPromises = serverNames.map(serverName =>\n getToolsForServer(mcpClient, serverName),\n );\n const toolsByServer = await Promise.all(serverToolPromises);\n\n return toolsByServer.flat();\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 setUserMcpServerConfig: McpService['setUserMcpServerConfig'] = async (\n credentials,\n mcpConfig,\n ) => {\n const { name } = mcpConfig;\n\n const existingConfig = await getUserMcpServerConfig(credentials);\n\n const existingServerIndex = existingConfig.findIndex(\n server => server.name === name,\n );\n\n if (existingServerIndex === -1) {\n existingConfig.push(mcpConfig);\n } else {\n existingConfig[existingServerIndex] = mcpConfig;\n }\n\n await validateMcpServerConfig(existingConfig);\n\n const updatedConfig: Record<string, string> = existingConfig.reduce(\n (acc, server) => {\n acc[server.name] = encrypt(\n JSON.stringify(server.options),\n encryptionKey,\n );\n return acc;\n },\n {} as Record<string, string>,\n );\n\n await userSettings.setSettingsForType(\n credentials,\n MCP_SETTINGS_TYPE,\n updatedConfig,\n );\n };\n\n const deleteUserMcpServerConfig: McpService['deleteUserMcpServerConfig'] =\n async (credentials, name) => {\n const existingConfig = await getUserMcpServerConfig(credentials);\n\n const updatedConfig: Record<string, string> = existingConfig\n .filter(server => server.name !== name)\n .reduce((acc, server) => {\n acc[server.name] = encrypt(\n JSON.stringify(server.options),\n encryptionKey,\n );\n return acc;\n }, {} as Record<string, string>);\n\n await userSettings.setSettingsForType(\n credentials,\n MCP_SETTINGS_TYPE,\n updatedConfig,\n );\n };\n\n return {\n getTools,\n getUserMcpServerConfigNames,\n deleteUserMcpServerConfig,\n setUserMcpServerConfig,\n };\n};\n\nexport const mcpServiceRef: ServiceRef<McpService, 'plugin', 'singleton'> =\n createServiceRef<McpService>({\n id: 'ai-assistant.mcp-service',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {\n config: coreServices.rootConfig,\n userInfo: coreServices.userInfo,\n database: coreServices.database,\n userSettings: userSettingsServiceRef,\n },\n factory: async options => {\n return createMcpService(options);\n },\n }),\n });\n"],"names":["decrypt","MultiServerMCPClient","getToolsForServer","encrypt","createServiceRef","createServiceFactory","coreServices","userSettingsServiceRef"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+
5
+ const createModelService = async ({
6
+ logger
7
+ }) => {
8
+ const models = [];
9
+ const registerModels = async (providers) => {
10
+ models.push(...providers);
11
+ };
12
+ const getModel = (id) => {
13
+ if (models.length === 0) {
14
+ logger.error("No models have been registered.");
15
+ throw new Error("No models have been registered.");
16
+ }
17
+ const provider = models.find((m) => m.id === id);
18
+ if (!provider) {
19
+ logger.warn(
20
+ `Model with id ${id} not found, using default ${models[0].id}.`
21
+ );
22
+ return models[0];
23
+ }
24
+ return provider;
25
+ };
26
+ const getAvailableModels = () => {
27
+ return models.map((x) => x.id);
28
+ };
29
+ return { registerModels, getModel, getAvailableModels };
30
+ };
31
+ const modelServiceRef = backendPluginApi.createServiceRef({
32
+ id: "ai-assistant.model-service",
33
+ defaultFactory: async (service) => backendPluginApi.createServiceFactory({
34
+ service,
35
+ deps: {
36
+ logger: backendPluginApi.coreServices.logger
37
+ },
38
+ factory: async (options) => {
39
+ return createModelService(options);
40
+ }
41
+ })
42
+ });
43
+
44
+ exports.modelServiceRef = modelServiceRef;
45
+ //# sourceMappingURL=model.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.cjs.js","sources":["../../src/services/model.ts"],"sourcesContent":["import {\n coreServices,\n createServiceFactory,\n createServiceRef,\n LoggerService,\n ServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { Model } from '@sweetoburrito/backstage-plugin-ai-assistant-node';\n\nexport type ModelService = {\n registerModels: (modelProviders: Model[]) => void;\n getModel: (id: string) => Model;\n getAvailableModels: () => string[];\n};\n\nexport type CreateModelServiceOptions = {\n logger: LoggerService;\n};\n\nconst createModelService = async ({\n logger,\n}: CreateModelServiceOptions): Promise<ModelService> => {\n const models: Model[] = [];\n\n const registerModels: ModelService['registerModels'] = async providers => {\n models.push(...providers);\n };\n\n const getModel: ModelService['getModel'] = id => {\n if (models.length === 0) {\n logger.error('No models have been registered.');\n throw new Error('No models have been registered.');\n }\n\n const provider = models.find(m => m.id === id);\n\n if (!provider) {\n logger.warn(\n `Model with id ${id} not found, using default ${models[0].id}.`,\n );\n return models[0];\n }\n\n return provider;\n };\n\n const getAvailableModels: ModelService['getAvailableModels'] = () => {\n return models.map(x => x.id);\n };\n\n return { registerModels, getModel, getAvailableModels };\n};\n\nexport const modelServiceRef: ServiceRef<ModelService, 'plugin', 'singleton'> =\n createServiceRef<ModelService>({\n id: 'ai-assistant.model-service',\n defaultFactory: async service =>\n createServiceFactory({\n service,\n deps: {\n logger: coreServices.logger,\n },\n factory: async options => {\n return createModelService(options);\n },\n }),\n });\n"],"names":["createServiceRef","createServiceFactory","coreServices"],"mappings":";;;;AAmBA,MAAM,qBAAqB,OAAO;AAAA,EAChC;AACF,CAAA,KAAwD;AACtD,EAAA,MAAM,SAAkB,EAAC;AAEzB,EAAA,MAAM,cAAA,GAAiD,OAAM,SAAA,KAAa;AACxE,IAAA,MAAA,CAAO,IAAA,CAAK,GAAG,SAAS,CAAA;AAAA,EAC1B,CAAA;AAEA,EAAA,MAAM,WAAqC,CAAA,EAAA,KAAM;AAC/C,IAAA,IAAI,MAAA,CAAO,WAAW,CAAA,EAAG;AACvB,MAAA,MAAA,CAAO,MAAM,iCAAiC,CAAA;AAC9C,MAAA,MAAM,IAAI,MAAM,iCAAiC,CAAA;AAAA,IACnD;AAEA,IAAA,MAAM,WAAW,MAAA,CAAO,IAAA,CAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,EAAE,CAAA;AAE7C,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,MAAA,CAAO,IAAA;AAAA,QACL,iBAAiB,EAAE,CAAA,0BAAA,EAA6B,MAAA,CAAO,CAAC,EAAE,EAAE,CAAA,CAAA;AAAA,OAC9D;AACA,MAAA,OAAO,OAAO,CAAC,CAAA;AAAA,IACjB;AAEA,IAAA,OAAO,QAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,qBAAyD,MAAM;AACnE,IAAA,OAAO,MAAA,CAAO,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,EAAE,CAAA;AAAA,EAC7B,CAAA;AAEA,EAAA,OAAO,EAAE,cAAA,EAAgB,QAAA,EAAU,kBAAA,EAAmB;AACxD,CAAA;AAEO,MAAM,kBACXA,iCAAA,CAA+B;AAAA,EAC7B,EAAA,EAAI,4BAAA;AAAA,EACJ,cAAA,EAAgB,OAAM,OAAA,KACpBC,qCAAA,CAAqB;AAAA,IACnB,OAAA;AAAA,IACA,IAAA,EAAM;AAAA,MACJ,QAAQC,6BAAA,CAAa;AAAA,KACvB;AAAA,IACA,OAAA,EAAS,OAAM,OAAA,KAAW;AACxB,MAAA,OAAO,mBAAmB,OAAO,CAAA;AAAA,IACnC;AAAA,GACD;AACL,CAAC;;;;"}
@@ -0,0 +1,68 @@
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
+ var uuid = require('uuid');
7
+
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
+
10
+ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
11
+ var z__default = /*#__PURE__*/_interopDefaultCompat(z);
12
+
13
+ async function createAgentRouter(options) {
14
+ const { agent, httpAuth } = options;
15
+ const router = Router__default.default();
16
+ const messageSchema = z__default.default.object({
17
+ modelId: z__default.default.string().optional(),
18
+ systemPrompt: z__default.default.string().optional(),
19
+ context: z__default.default.string().optional(),
20
+ messages: z__default.default.array(
21
+ z__default.default.object({
22
+ id: z__default.default.uuid().optional().default(uuid.v4),
23
+ role: z__default.default.string(),
24
+ content: z__default.default.string()
25
+ })
26
+ ),
27
+ tools: z__default.default.array(
28
+ z__default.default.object({
29
+ name: z__default.default.string(),
30
+ provider: z__default.default.string()
31
+ })
32
+ ).optional(),
33
+ metadata: z__default.default.object({
34
+ runName: z__default.default.string(),
35
+ runId: z__default.default.uuid().optional().default(uuid.v4),
36
+ userId: z__default.default.string()
37
+ })
38
+ });
39
+ router.post(
40
+ "/prompt",
41
+ validation.validation(messageSchema, "body"),
42
+ async (req, res) => {
43
+ const { messages, modelId, tools, systemPrompt, metadata, context } = req.body;
44
+ const credentials = await httpAuth.credentials(req);
45
+ const responseMessages = await agent.prompt({
46
+ messages,
47
+ modelId,
48
+ credentials,
49
+ systemPrompt,
50
+ tools,
51
+ context,
52
+ metadata: {
53
+ runName: metadata.runName,
54
+ userId: metadata.userId,
55
+ conversationId: "system:agent-prompt",
56
+ runId: metadata.runId
57
+ }
58
+ });
59
+ res.json({
60
+ messages: responseMessages
61
+ });
62
+ }
63
+ );
64
+ return router;
65
+ }
66
+
67
+ exports.createAgentRouter = createAgentRouter;
68
+ //# sourceMappingURL=agent.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.cjs.js","sources":["../../../src/services/router/agent.ts"],"sourcesContent":["import express from 'express';\nimport Router from 'express-promise-router';\nimport z from 'zod';\nimport { validation } from './middleware/validation';\nimport { v4 as uuid } from 'uuid';\n\nimport { AgentService } from '../agent';\nimport { HttpAuthService } from '@backstage/backend-plugin-api';\n\nexport type AgentRouterOptions = {\n agent: AgentService;\n httpAuth: HttpAuthService;\n};\n\nexport async function createAgentRouter(\n options: AgentRouterOptions,\n): Promise<express.Router> {\n const { agent, httpAuth } = options;\n\n const router = Router();\n\n const messageSchema = z.object({\n modelId: z.string().optional(),\n systemPrompt: z.string().optional(),\n context: z.string().optional(),\n\n messages: z.array(\n z.object({\n id: z.uuid().optional().default(uuid),\n role: z.string(),\n content: z.string(),\n }),\n ),\n\n tools: z\n .array(\n z.object({\n name: z.string(),\n provider: z.string(),\n }),\n )\n .optional(),\n\n metadata: z.object({\n runName: z.string(),\n runId: z.uuid().optional().default(uuid),\n userId: z.string(),\n }),\n });\n\n router.post(\n '/prompt',\n validation(messageSchema, 'body'),\n async (req, res) => {\n const { messages, modelId, tools, systemPrompt, metadata, context } =\n req.body;\n\n const credentials = await httpAuth.credentials(req);\n\n const responseMessages = await agent.prompt({\n messages,\n modelId,\n credentials,\n systemPrompt,\n tools,\n context,\n metadata: {\n runName: metadata.runName,\n userId: metadata.userId,\n conversationId: 'system:agent-prompt',\n runId: metadata.runId,\n },\n });\n\n res.json({\n messages: responseMessages,\n });\n },\n );\n\n return router;\n}\n"],"names":["Router","z","uuid","validation"],"mappings":";;;;;;;;;;;;AAcA,eAAsB,kBACpB,OAAA,EACyB;AACzB,EAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAS,GAAI,OAAA;AAE5B,EAAA,MAAM,SAASA,uBAAA,EAAO;AAEtB,EAAA,MAAM,aAAA,GAAgBC,mBAAE,MAAA,CAAO;AAAA,IAC7B,OAAA,EAASA,kBAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAC7B,YAAA,EAAcA,kBAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAClC,OAAA,EAASA,kBAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,IAE7B,UAAUA,kBAAA,CAAE,KAAA;AAAA,MACVA,mBAAE,MAAA,CAAO;AAAA,QACP,IAAIA,kBAAA,CAAE,IAAA,GAAO,QAAA,EAAS,CAAE,QAAQC,OAAI,CAAA;AAAA,QACpC,IAAA,EAAMD,mBAAE,MAAA,EAAO;AAAA,QACf,OAAA,EAASA,mBAAE,MAAA;AAAO,OACnB;AAAA,KACH;AAAA,IAEA,OAAOA,kBAAA,CACJ,KAAA;AAAA,MACCA,mBAAE,MAAA,CAAO;AAAA,QACP,IAAA,EAAMA,mBAAE,MAAA,EAAO;AAAA,QACf,QAAA,EAAUA,mBAAE,MAAA;AAAO,OACpB;AAAA,MAEF,QAAA,EAAS;AAAA,IAEZ,QAAA,EAAUA,mBAAE,MAAA,CAAO;AAAA,MACjB,OAAA,EAASA,mBAAE,MAAA,EAAO;AAAA,MAClB,OAAOA,kBAAA,CAAE,IAAA,GAAO,QAAA,EAAS,CAAE,QAAQC,OAAI,CAAA;AAAA,MACvC,MAAA,EAAQD,mBAAE,MAAA;AAAO,KAClB;AAAA,GACF,CAAA;AAED,EAAA,MAAA,CAAO,IAAA;AAAA,IACL,SAAA;AAAA,IACAE,qBAAA,CAAW,eAAe,MAAM,CAAA;AAAA,IAChC,OAAO,KAAK,GAAA,KAAQ;AAClB,MAAA,MAAM,EAAE,UAAU,OAAA,EAAS,KAAA,EAAO,cAAc,QAAA,EAAU,OAAA,KACxD,GAAA,CAAI,IAAA;AAEN,MAAA,MAAM,WAAA,GAAc,MAAM,QAAA,CAAS,WAAA,CAAY,GAAG,CAAA;AAElD,MAAA,MAAM,gBAAA,GAAmB,MAAM,KAAA,CAAM,MAAA,CAAO;AAAA,QAC1C,QAAA;AAAA,QACA,OAAA;AAAA,QACA,WAAA;AAAA,QACA,YAAA;AAAA,QACA,KAAA;AAAA,QACA,OAAA;AAAA,QACA,QAAA,EAAU;AAAA,UACR,SAAS,QAAA,CAAS,OAAA;AAAA,UAClB,QAAQ,QAAA,CAAS,MAAA;AAAA,UACjB,cAAA,EAAgB,qBAAA;AAAA,UAChB,OAAO,QAAA,CAAS;AAAA;AAClB,OACD,CAAA;AAED,MAAA,GAAA,CAAI,IAAA,CAAK;AAAA,QACP,QAAA,EAAU;AAAA,OACX,CAAA;AAAA,IACH;AAAA,GACF;AAEA,EAAA,OAAO,MAAA;AACT;;;;"}