@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.
- package/dist/constants/prompts.cjs.js +1 -0
- package/dist/constants/prompts.cjs.js.map +1 -1
- package/dist/database/pg-vector-store.cjs.js +100 -38
- package/dist/database/pg-vector-store.cjs.js.map +1 -1
- package/dist/plugin.cjs.js +25 -41
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/services/agent/helpers/message-parser.cjs.js +31 -0
- package/dist/services/agent/helpers/message-parser.cjs.js.map +1 -0
- package/dist/services/agent/helpers/tool-filter.cjs.js +13 -0
- package/dist/services/agent/helpers/tool-filter.cjs.js.map +1 -0
- package/dist/services/agent/index.cjs.js +140 -0
- package/dist/services/agent/index.cjs.js.map +1 -0
- package/dist/services/callbacks.cjs.js +23 -9
- package/dist/services/callbacks.cjs.js.map +1 -1
- package/dist/services/chat.cjs.js +88 -253
- package/dist/services/chat.cjs.js.map +1 -1
- package/dist/services/conversation.cjs.js +133 -0
- package/dist/services/conversation.cjs.js.map +1 -0
- package/dist/services/ingestor.cjs.js +5 -11
- package/dist/services/ingestor.cjs.js.map +1 -1
- package/dist/services/mcp/index.cjs.js +37 -24
- package/dist/services/mcp/index.cjs.js.map +1 -1
- package/dist/services/model.cjs.js +45 -0
- package/dist/services/model.cjs.js.map +1 -0
- package/dist/services/router/agent.cjs.js +68 -0
- package/dist/services/router/agent.cjs.js.map +1 -0
- package/dist/services/router/chat.cjs.js +8 -13
- package/dist/services/router/chat.cjs.js.map +1 -1
- package/dist/services/router/index.cjs.js +4 -0
- package/dist/services/router/index.cjs.js.map +1 -1
- package/dist/services/router/models.cjs.js +2 -2
- package/dist/services/router/models.cjs.js.map +1 -1
- package/dist/services/router/settings/index.cjs.js +0 -3
- package/dist/services/router/settings/index.cjs.js.map +1 -1
- package/dist/services/router/summary.cjs.js +5 -3
- package/dist/services/router/summary.cjs.js.map +1 -1
- package/dist/services/router/tools.cjs.js +21 -0
- package/dist/services/router/tools.cjs.js.map +1 -0
- package/dist/services/summarizer.cjs.js +47 -28
- package/dist/services/summarizer.cjs.js.map +1 -1
- package/dist/services/tools.cjs.js +65 -0
- package/dist/services/tools.cjs.js.map +1 -0
- package/dist/services/user-settings.cjs.js +15 -0
- package/dist/services/user-settings.cjs.js.map +1 -1
- package/dist/tools/searchKnowledge.cjs.js +1 -6
- package/dist/tools/searchKnowledge.cjs.js.map +1 -1
- package/migrations/20260108_data_ingestion_refactor.js +27 -0
- 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":";;;;;;;;;;AAoGO,MAAM,oBAAoB,OAAO;AAAA,EACtC,MAAA;AAAA,SACAA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,GAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,KAAgD;AAC9C,EAAA,MAAA,CAAO,IAAA,CAAK,CAAA,kBAAA,EAAqB,MAAA,CAAO,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,EAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AACnE,EAAA,MAAA,CAAO,IAAA,CAAK,CAAA,iBAAA,EAAoBA,OAAA,CAAM,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,IAAI,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAEnE,EAAA,MAAM,cAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,6BAA6B,CAAA,IACtDC,+BAAA;AAEF,EAAA,MAAM,gBAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,+BAA+B,CAAA,IACxDC,iCAAA;AAEF,EAAA,MAAM,aAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,4BAA4B,CAAA,IACrDC,6BAAA;AAEF,EAAA,MAAM,kBAAA,GAAqB,GAAG,cAAc;;AAAA,EAAO,gBAAgB;;AAAA,EAAO,aAAa,CAAA,CAAA;AAEvF,EAAA,MAAM,aAAA,GACJ,MAAA,CAAO,iBAAA,CAAkB,kCAAkC,CAAA,IAC3DC,8BAAA;AAEF,EAAA,MAAMC,cAAY,MAAMC,mBAAA,CAAU,UAAA,CAAW,EAAE,UAAU,CAAA;AAEzD,EAAA,MAAM,oBAAA,GAAuBC,sCAA4B,YAAA,CAAa;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA,aAAA,CAc1D,CAAA;AAEZ,EAAA,MAAM,WAAA,GAA0C,OAC9C,QAAA,EACA,OAAA,EACA,gBACA,0BAAA,KACG;AAEH,IAAA,MAAM,cAAA,GACJ,0BAAA,IACC,MAAMF,WAAA,CAAU,eAAA,CAAgB,gBAAgB,OAAA,EAAS,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEvE,IAAA,MAAM,gBAAA,GAAA,CAAoB,cAAA,EAAgB,MAAA,IAAU,CAAA,IAAK,QAAA,CAAS,MAAA;AAElE,IAAA,IAAI,cAAA,CAAe,WAAW,CAAA,EAAG;AAC/B,MAAA,MAAMG,aAAAA,GAA6B;AAAA,QACjC,EAAA,EAAI,cAAA;AAAA,QACJ,KAAA,EAAO,kBAAA;AAAA,QACP;AAAA,OACF;AACA,MAAAH,WAAA,CAAU,mBAAmBG,aAAY,CAAA;AACzC,MAAAH,WAAA,CAAU,cAAA,CAAe,QAAA,EAAU,OAAA,EAAS,cAAc,CAAA;AAE1D,MAAA,OAAA,CAAQ,OAAA,CAAQ;AAAA,QACd,OAAA,EAAS,CAAA,6CAAA,CAAA;AAAA,QACT,OAAA,EAAS,EAAE,YAAA,EAAAG,aAAAA,EAAa;AAAA,QACxB,UAAA,EAAY;AAAA,UACV,IAAA,EAAM,MAAA;AAAA,UACN,SAAA,EAAW;AAAA;AACb,OACD,CAAA;AACD,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,mBAAmB,CAAA,EAAG;AACxB,MAAAH,WAAA,CAAU,cAAA,CAAe,QAAA,EAAU,OAAA,EAAS,cAAc,CAAA;AAC1D,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,YAAA,GAAe,MAAMA,WAAA,CAAU,eAAA;AAAA,MACnC,cAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,IAAI,YAAA,CAAa,UAAU,kBAAA,EAAoB;AAC7C,MAAAA,WAAA,CAAU,cAAA,CAAe,QAAA,EAAU,OAAA,EAAS,cAAc,CAAA;AAC1D,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAU,MAAM,UAAA,CAAW,qBAAA,CAAsB;AAAA,MACrD,QAAA,EAAU,cAAA;AAAA,MACV,MAAA,EAAQ;AAAA,KACT,CAAA;AAED,IAAA,YAAA,CAAa,KAAA,GAAQ,OAAA;AAErB,IAAAA,WAAA,CAAU,mBAAmB,YAAY,CAAA;AACzC,IAAAA,WAAA,CAAU,cAAA,CAAe,QAAA,EAAU,OAAA,EAAS,cAAc,CAAA;AAE1D,IAAA,OAAA,CAAQ,OAAA,CAAQ;AAAA,MACd,OAAA,EAAS,CAAA,6CAAA,CAAA;AAAA,MACT,OAAA,EAAS,EAAE,YAAA,EAAa;AAAA,MACxB,UAAA,EAAY;AAAA,QACV,IAAA,EAAM,MAAA;AAAA,QACN,SAAA,EAAW;AAAA;AACb,KACD,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,SAAgC,OAAO;AAAA,IAC3C,cAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA,GAAS,IAAA;AAAA,IACT,eAAA;AAAA,IACA,KAAA,EAAO;AAAA,GACT,KAAqB;AACnB,IAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,CAAK,OAAK,CAAA,CAAE,EAAA,KAAO,OAAO,CAAA,EAAG,SAAA;AAElD,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,cAAA,EAAiB,OAAO,CAAA,UAAA,CAAY,CAAA;AAAA,IACtD;AAEA,IAAA,MAAM,EAAE,aAAA,EAAc,GAAI,MAAM,QAAA,CAAS,YAAY,eAAe,CAAA;AAEpE,IAAA,MAAM,WAAW,YAAY;AAC3B,MAAA,MAAM,0BAAA,GAA6B,MAAMA,WAAA,CAAU,eAAA;AAAA,QACjD,cAAA;AAAA,QACA,aAAA;AAAA,QACA,EAAA;AAAA,QACA,CAAC,MAAM;AAAA,OACT;AAEA,MAAA,MAAM,WAAA,GAAc,MAAM,IAAA,CAAK,wBAAA,EAAyB;AACxD,MAAA,MAAM,OAAO,MAAMI,sCAAA,CAAQ,KAAA,EAAO,aAAA,EAAe,aAAa,OAAO,CAAA;AAErE,MAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,QAAA,CAAS,eAAe,CAAA;AAEnD,MAAA,MAAM,UAAA,GAAa,CAAC,GAAGT,OAAA,EAAO,GAAG,QAAQ,CAAA,CACtC,OAAO,CAAA,IAAA,KAAQ;AAEd,QAAA,IAAI,YAAA,KAAiB,QAAW,OAAO,IAAA;AAGvC,QAAA,IAAI,YAAA,CAAa,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA;AAEtC,QAAA,MAAM,UAAU,YAAA,CAAa,IAAA;AAAA,UAC3B,iBACE,WAAA,CAAY,IAAA,KAAS,KAAK,IAAA,IAC1B,WAAA,CAAY,aAAa,IAAA,CAAK;AAAA,SAClC;AACA,QAAA,OAAO,CAAC,CAAC,OAAA;AAAA,MACX,CAAC,CAAA,CACA,GAAA,CAAI,UAAQ,IAAIU,2BAAA,CAAsB,IAAI,CAAC,CAAA;AAE9C,MAAA,MAAM,wBAAwB,QAAA,CAAS,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,SAAS,QAAQ,CAAA;AAEtE,MAAA,WAAA;AAAA,QACE,qBAAA;AAAA,QACA,aAAA;AAAA,QACA,cAAA;AAAA,QACA;AAAA,OACF;AAEA,MAAA,MAAM,YAAA,GAAe,MAAM,oBAAA,CAAqB,cAAA,CAAe;AAAA,QAC7D,UAAA,EAAY,kBAAA;AAAA,QACZ,aAAA;AAAA,QACA,QAAA,EAAU,UAAA,CACP,GAAA,CAAI,CAAA,IAAA,KAAQ,CAAA,EAAA,EAAK,IAAA,CAAK,IAAI,CAAA,EAAA,EAAK,IAAA,CAAK,WAAW,CAAA,CAAE,CAAA,CACjD,KAAK,IAAI,CAAA;AAAA,QACZ,OAAA,EAAS,CAAA,IAAA,CAAA;AAAA,QACT;AAAA,OACD,CAAA;AAED,MAAA,MAAM,QAAQC,yBAAA,CAAiB;AAAA,QAC7B,GAAA,EAAK,KAAA;AAAA,QACL,KAAA,EAAO,UAAA;AAAA,QACP,MAAA,EAAQ,YAAA,CAAa,CAAC,CAAA,CAAE;AAAA,OACzB,CAAA;AAED,MAAA,MAAM,EAAE,SAAA,EAAU,GAAI,MAAM,SAAS,iBAAA,CAAkB;AAAA,QACrD,cAAA;AAAA,QACA,MAAA,EAAQ,aAAA;AAAA,QACR;AAAA,OACD,CAAA;AAED,MAAA,MAAM,EAAE,QAAA,EAAU,cAAA,EAAe,GAAI,MAAM,SAAS,gBAAA,CAAiB;AAAA,QACnE,cAAA;AAAA,QACA,MAAA,EAAQ,aAAA;AAAA,QACR;AAAA,OACD,CAAA;AAED,MAAA,MAAM,UAAUC,OAAA,EAAK;AAErB,MAAA,MAAM,YAAA,GAAe,MAAM,KAAA,CAAM,MAAA;AAAA,QAC/B;AAAA,UACE,QAAA,EAAU,CAAC,GAAG,0BAAA,EAA4B,GAAG,QAAQ;AAAA,SACvD;AAAA,QACA;AAAA,UACE,UAAA,EAAY,CAAC,QAAQ,CAAA;AAAA,UACrB,OAAA,EAAS,mBAAA;AAAA,UACT,KAAA,EAAO,OAAA;AAAA,UACP,QAAA,EAAU,cAAA;AAAA,UACV;AAAA;AACF,OACF;AAEA,MAAA,MAAM,mBAAgD,EAAC;AAEvD,MAAA,WAAA,MAAiB,GAAG,KAAK,CAAA,IAAK,YAAA,EAAc;AAC1C,QAAA,MAAM,EAAE,QAAA,EAAU,cAAA,EAAe,GAAI,KAAA;AAErC,QAAA,MAAM,cAA2C,cAAA,CAC9C,MAAA;AAAA,UACC,OACE,gBAAA,CAAiB,SAAA;AAAA,YACf,CAAA,EAAA,KAAM,CAAA,CAAE,EAAA,KAAO,EAAA,CAAG,QAAA,CAAS;AAAA,WAC7B,KAAM;AAAA,SACV,CACC,MAAA;AAAA,UACC,CAAA,CAAA,KACE,2BAA2B,SAAA,CAAU,CAAA,EAAA,KAAM,GAAG,EAAA,KAAO,CAAA,CAAE,EAAE,CAAA,KAAM;AAAA,SACnE,CACC,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,SAAQ,KAAM,OAAO,CAAA,CACnC,GAAA,CAAI,CAAA,CAAA,KAAK;AACR,UAAA,MAAM,KAAKA,OAAA,EAAK;AAChB,UAAA,MAAM,IAAA,GAAO,EAAE,OAAA,EAAQ;AACvB,UAAA,MAAM,OAAA,GACJ,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,GACjB,EAAE,OAAA,GACF,IAAA,CAAK,SAAA,CAAU,CAAA,CAAE,OAAO,CAAA;AAE9B,UAAA,MAAM,QAAA,GAAuB;AAAA,YAC3B,WAAA,EAAa,EAAE,EAAA,IAAM;AAAA,WACvB;AAEA,UAAA,IAAI,SAAS,IAAA,EAAM;AACjB,YAAA,MAAM,SAAA,GAAY,CAAA;AAClB,YAAA,QAAA,CAAS,SAAA,GAAY,SAAA,CAAU,UAAA,IAAc,EAAC;AAC9C,YAAA,QAAA,CAAS,YAAA,GACP,SAAA,CAAU,iBAAA,CAAkB,aAAA,IAAiB,MAAA;AAC/C,YAAA,QAAA,CAAS,SAAA,GACP,SAAA,CAAU,iBAAA,CAAkB,UAAA,IAAc,MAAA;AAAA,UAC9C;AAEA,UAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,YAAA,MAAM,WAAA,GAAc,CAAA;AACpB,YAAA,QAAA,CAAS,IAAA,GAAO,YAAY,IAAA,IAAQ,EAAA;AAAA,UACtC;AAEA,UAAA,OAAO;AAAA,YACL,EAAA;AAAA,YACA,IAAA;AAAA,YACA,OAAA;AAAA,YACA,QAAA;AAAA,YACA,KAAA,EAAO,CAAA;AAAA,YACP;AAAA,WACF;AAAA,QACF,CAAC,CAAA;AAGH,QAAA,WAAA,MAAiB,KAAK,WAAA,EAAa;AACjC,UAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,OAAA,CAAQ,KAAA,CAAM,GAAG,CAAA;AACjC,UAAA,MAAM,SAAA,GAAY,CAAA;AAClB,UAAA,IAAI,cAAA,GAAiB,EAAA;AAErB,UAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,SAAA,EAAW;AAChD,YAAA,MAAM,SAAA,GAAY,MAAM,KAAA,CAAM,CAAA,EAAG,IAAI,SAAS,CAAA,CAAE,KAAK,GAAG,CAAA;AACxD,YAAA,cAAA,GAAiB,cAAA,CAAe,MAAA,CAAO,SAAS,CAAA,CAAE,OAAO,GAAG,CAAA;AAC5D,YAAA,CAAA,CAAE,OAAA,GAAU,cAAA;AAEZ,YAAA,MAAM,IAAI,OAAA,CAAQ,CAAA,OAAA,KAAW,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AAEpD,YAAA,OAAA,CAAQ,OAAA,CAAQ;AAAA,cACd,OAAA,EAAS,yCAAyC,cAAc,CAAA,CAAA;AAAA,cAChE,OAAA,EAAS,EAAE,QAAA,EAAU,CAAC,CAAC,CAAA,EAAE;AAAA,cACzB,UAAA,EAAY;AAAA,gBACV,IAAA,EAAM,MAAA;AAAA,gBACN,SAAA,EAAW;AAAA;AACb,aACD,CAAA;AAAA,UACH;AAAA,QACF;AAEA,QAAA,gBAAA,CAAiB,IAAA,CAAK,GAAG,WAAW,CAAA;AAAA,MACtC;AAEA,MAAA,WAAA,CAAY,gBAAA,EAAkB,eAAe,cAAA,EAAgB;AAAA,QAC3D,GAAG,0BAAA;AAAA,QACH,GAAG;AAAA,OACJ,CAAA;AAED,MAAA,OAAO,gBAAA;AAAA,IACT,CAAA;AAEA,IAAA,MAAM,SAAS,QAAA,EAAS;AAExB,IAAA,OAAO,MAAA,GAAS,EAAC,GAAI,MAAA;AAAA,EACvB,CAAA;AAEA,EAAA,MAAM,qBAAwD,YAAY;AACxE,IAAA,OAAO,MAAA,CAAO,GAAA,CAAI,CAAA,CAAA,KAAK,CAAA,CAAE,EAAE,CAAA;AAAA,EAC7B,CAAA;AAEA,EAAA,MAAM,eAAA,GAAkD,OACtD,OAAA,KACG;AACH,IAAA,MAAM,EAAE,cAAA,EAAgB,aAAA,EAAc,GAAI,OAAA;AAE1C,IAAA,MAAM,YAAA,GAAe,MAAMP,WAAA,CAAU,eAAA;AAAA,MACnC,cAAA;AAAA,MACA;AAAA,KACF;AAEA,IAAA,OAAO,YAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,mBAAoD,OAAO;AAAA,IAC/D;AAAA,GACF,KAA+B;AAC7B,IAAA,MAAM,aAAA,GAAgB,MAAMA,WAAA,CAAU,gBAAA,CAAiB,aAAa,CAAA;AAEpE,IAAA,OAAO,aAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,YAAA,GAA4C,OAChD,SAAA,EACA,KAAA,KACG;AACH,IAAA,MAAM,OAAA,GAAU,MAAMA,WAAA,CAAU,cAAA,CAAe,SAAS,CAAA;AAExD,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gBAAA,EAAmB,SAAS,CAAA,UAAA,CAAY,CAAA;AAAA,IAC1D;AAEA,IAAA,MAAM,cAAA,GAAoC;AAAA,MACxC,GAAG,OAAA;AAAA,MACH;AAAA,KACF;AAEA,IAAAA,WAAA,CAAU,cAAc,cAAc,CAAA;AAEtC,IAAA,QAAA,CAAS,oBAAA,CAAqB;AAAA,MAC5B,IAAA,EAAM,aAAA;AAAA,MACN,OAAA,EAAS;AAAA,KACV,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,oBAAsD,OAAO;AAAA,IACjE;AAAA,GACF,KAAM;AACJ,IAAA,MAAM,QAAA,GAAW,MAAM,GAAA,CAAI,QAAA,CAAS,WAAW,CAAA;AAE/C,IAAA,MAAM,iBAA6BL,OAAA,CAAM,MAAA,CAAO,QAAQ,CAAA,CAAE,IAAI,CAAA,IAAA,MAAS;AAAA,MACrE,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,aAAa,IAAA,CAAK;AAAA,KACpB,CAAE,CAAA;AAEF,IAAA,OAAO,cAAA;AAAA,EACT,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,kBAAA;AAAA,IACA,eAAA;AAAA,IACA,gBAAA;AAAA,IACA,WAAA;AAAA,IACA,YAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
|
|
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.
|
|
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.
|
|
63
|
+
logger.debug(`Adding documents to vector store...`);
|
|
70
64
|
const allChunks = documentChunks.flat();
|
|
71
|
-
logger.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
27
|
-
|
|
28
|
-
userEntityRef,
|
|
25
|
+
const mcpConfig = await userSettings.getSettingsForType(
|
|
26
|
+
credentials,
|
|
29
27
|
MCP_SETTINGS_TYPE
|
|
30
28
|
);
|
|
31
|
-
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
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 =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
120
|
-
|
|
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
|
|
136
|
-
|
|
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;;;;"}
|