diff --git a/apps/web/app/(Main UI)/sidekick-studio/(main-layout)/admin/document-stores/page.tsx b/apps/web/app/(Main UI)/sidekick-studio/(main-layout)/admin/document-stores/page.tsx
new file mode 100644
index 00000000000..8bb4da64ab3
--- /dev/null
+++ b/apps/web/app/(Main UI)/sidekick-studio/(main-layout)/admin/document-stores/page.tsx
@@ -0,0 +1,19 @@
+'use client'
+
+import React from 'react'
+import dynamic from 'next/dynamic'
+import { Box, Container } from '@mui/material'
+
+const AdminDocumentStores = dynamic(() => import('@ui/Admin/DocumentStores'), { ssr: false })
+
+const Page = () => {
+ return (
+
+
+
+
+
+ )
+}
+
+export default Page
diff --git a/packages-answers/assets/aai-models.json b/packages-answers/assets/aai-models.json
index 6ec2bb4ce8a..e377c0dc03b 100644
--- a/packages-answers/assets/aai-models.json
+++ b/packages-answers/assets/aai-models.json
@@ -523,6 +523,13 @@
{
"name": "chatGoogleGenerativeAI",
"models": [
+ {
+ "label": "gemini-2.5-flash-image-preview",
+ "name": "gemini-2.5-flash-image-preview",
+ "description": "Latest Gemini model with image generation and understanding capabilities",
+ "input_cost": 0.15e-6,
+ "output_cost": 6e-7
+ },
{
"label": "gemini-2.5-flash-preview-05-20",
"name": "gemini-2.5-flash-preview-05-20",
@@ -789,6 +796,24 @@
{
"name": "chatOpenAI",
"models": [
+ {
+ "label": "gpt-5",
+ "name": "gpt-5",
+ "input_cost": 1.25e-6,
+ "output_cost": 1e-5
+ },
+ {
+ "label": "gpt-5-mini",
+ "name": "gpt-5-mini",
+ "input_cost": 2.5e-7,
+ "output_cost": 2e-6
+ },
+ {
+ "label": "gpt-5-nano",
+ "name": "gpt-5-nano",
+ "input_cost": 5e-8,
+ "output_cost": 4e-7
+ },
{
"label": "gpt-4.1",
"name": "gpt-4.1",
diff --git a/packages-answers/assets/models.json b/packages-answers/assets/models.json
index 6ec2bb4ce8a..e377c0dc03b 100644
--- a/packages-answers/assets/models.json
+++ b/packages-answers/assets/models.json
@@ -523,6 +523,13 @@
{
"name": "chatGoogleGenerativeAI",
"models": [
+ {
+ "label": "gemini-2.5-flash-image-preview",
+ "name": "gemini-2.5-flash-image-preview",
+ "description": "Latest Gemini model with image generation and understanding capabilities",
+ "input_cost": 0.15e-6,
+ "output_cost": 6e-7
+ },
{
"label": "gemini-2.5-flash-preview-05-20",
"name": "gemini-2.5-flash-preview-05-20",
@@ -789,6 +796,24 @@
{
"name": "chatOpenAI",
"models": [
+ {
+ "label": "gpt-5",
+ "name": "gpt-5",
+ "input_cost": 1.25e-6,
+ "output_cost": 1e-5
+ },
+ {
+ "label": "gpt-5-mini",
+ "name": "gpt-5-mini",
+ "input_cost": 2.5e-7,
+ "output_cost": 2e-6
+ },
+ {
+ "label": "gpt-5-nano",
+ "name": "gpt-5-nano",
+ "input_cost": 5e-8,
+ "output_cost": 4e-7
+ },
{
"label": "gpt-4.1",
"name": "gpt-4.1",
diff --git a/packages-answers/ui/src/Admin/Chatflows/index.tsx b/packages-answers/ui/src/Admin/Chatflows/index.tsx
index 25d36626a7a..3d2f5101a3d 100644
--- a/packages-answers/ui/src/Admin/Chatflows/index.tsx
+++ b/packages-answers/ui/src/Admin/Chatflows/index.tsx
@@ -161,9 +161,21 @@ const AdminChatflows = () => {
}
const getCanvasRoute = (chatflow: any) => {
- // Check the actual flow type, not the filter selection
+ // For react-router-dom Link component (relative paths)
console.log('🤖id', chatflow.id)
console.log('type', chatflow)
+ if (chatflow.type === 'AGENTFLOW') {
+ return `/v2/agentcanvas/${chatflow.id}`
+ } else if (chatflow.type === 'MULTIAGENT') {
+ return `/agentcanvas/${chatflow.id}`
+ } else {
+ // Default to regular chatflow canvas
+ return `/canvas/${chatflow.id}`
+ }
+ }
+
+ const getCanvasFullUrl = (chatflow: any) => {
+ // For window.open() (full URLs with base path)
if (chatflow.type === 'AGENTFLOW') {
return `/sidekick-studio/v2/agentcanvas/${chatflow.id}`
} else if (chatflow.type === 'MULTIAGENT') {
@@ -1371,7 +1383,7 @@ const AdminChatflows = () => {
window.open(getCanvasRoute(chatflow), '_blank')}
+ onClick={() => window.open(getCanvasFullUrl(chatflow), '_blank')}
sx={{
color: 'rgba(255, 255, 255, 0.7)',
'&:hover': { color: 'rgba(255, 255, 255, 0.9)' }
diff --git a/packages-answers/ui/src/Admin/DocumentStores/index.tsx b/packages-answers/ui/src/Admin/DocumentStores/index.tsx
new file mode 100644
index 00000000000..861c049d752
--- /dev/null
+++ b/packages-answers/ui/src/Admin/DocumentStores/index.tsx
@@ -0,0 +1,293 @@
+'use client'
+import { useMemo, useState, type ChangeEvent } from 'react'
+import { Link } from 'react-router-dom'
+import {
+ Box,
+ Button,
+ Chip,
+ IconButton,
+ Paper,
+ Skeleton,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TablePagination,
+ TableRow,
+ TableSortLabel,
+ TextField,
+ Tooltip,
+ Typography
+} from '@mui/material'
+import RefreshIcon from '@mui/icons-material/Refresh'
+import LaunchIcon from '@mui/icons-material/Launch'
+import documentStoreApi from '@/api/documentstore'
+import useApi from '@ui/hooks/useApi'
+import { format } from 'date-fns'
+
+export type AdminDocumentStore = {
+ id: string
+ name: string
+ description?: string
+ status: string
+ createdDate: string
+ updatedDate: string
+ loaders?: Array<{ id: string }>
+ totalChunks?: number
+ totalChars?: number
+ user?: {
+ id?: string
+ name?: string | null
+ email?: string | null
+ }
+ organizationId?: string
+ userId?: string
+ isOwner?: boolean
+}
+
+const columns: Array<{ id: keyof AdminDocumentStore | 'owner' | 'loadersCount'; label: string }> = [
+ { id: 'name', label: 'Name' },
+ { id: 'status', label: 'Status' },
+ { id: 'owner', label: 'Owner' },
+ { id: 'loadersCount', label: 'Loaders' },
+ { id: 'totalChunks', label: 'Chunks' },
+ { id: 'updatedDate', label: 'Last Updated' }
+]
+
+type OrderableColumn = 'name' | 'updatedDate' | 'totalChunks' | 'loadersCount'
+
+type Order = 'asc' | 'desc'
+
+const AdminDocumentStores = () => {
+ const {
+ data: documentStoresData,
+ isLoading,
+ isError,
+ refresh
+ } = useApi('/api/admin/document-stores', () => documentStoreApi.getAdminDocumentStores())
+
+ const [orderBy, setOrderBy] = useState('updatedDate')
+ const [order, setOrder] = useState('desc')
+ const [page, setPage] = useState(0)
+ const [rowsPerPage, setRowsPerPage] = useState(25)
+ const [search, setSearch] = useState('')
+
+ const filteredAndSorted = useMemo(() => {
+ if (!documentStoresData) return []
+
+ const normalizedSearch = search.trim().toLowerCase()
+ const filtered = documentStoresData.filter((store) => {
+ if (!normalizedSearch) return true
+ const ownerLabel = store.user?.name || store.user?.email || ''
+ return (
+ store.name.toLowerCase().includes(normalizedSearch) ||
+ (store.description || '').toLowerCase().includes(normalizedSearch) ||
+ ownerLabel.toLowerCase().includes(normalizedSearch)
+ )
+ })
+
+ const sorted = [...filtered].sort((a, b) => {
+ let aValue: string | number = ''
+ let bValue: string | number = ''
+
+ switch (orderBy) {
+ case 'name':
+ aValue = a.name.toLowerCase()
+ bValue = b.name.toLowerCase()
+ break
+ case 'totalChunks':
+ aValue = a.totalChunks ?? 0
+ bValue = b.totalChunks ?? 0
+ break
+ case 'loadersCount':
+ aValue = a.loaders?.length ?? 0
+ bValue = b.loaders?.length ?? 0
+ break
+ case 'updatedDate':
+ default:
+ aValue = new Date(a.updatedDate).getTime()
+ bValue = new Date(b.updatedDate).getTime()
+ break
+ }
+
+ if (aValue < bValue) {
+ return order === 'asc' ? -1 : 1
+ }
+ if (aValue > bValue) {
+ return order === 'asc' ? 1 : -1
+ }
+ return 0
+ })
+
+ return sorted
+ }, [documentStoresData, order, orderBy, search])
+
+ const paginatedData = useMemo(() => {
+ const start = page * rowsPerPage
+ return filteredAndSorted.slice(start, start + rowsPerPage)
+ }, [filteredAndSorted, page, rowsPerPage])
+
+ const handleRequestSort = (property: OrderableColumn) => {
+ const isAsc = orderBy === property && order === 'asc'
+ setOrder(isAsc ? 'desc' : 'asc')
+ setOrderBy(property)
+ }
+
+ const handleChangePage = (_: unknown, newPage: number) => {
+ setPage(newPage)
+ }
+
+ const handleChangeRowsPerPage = (event: ChangeEvent) => {
+ setRowsPerPage(parseInt(event.target.value, 10))
+ setPage(0)
+ }
+
+ const renderSkeleton = () => {
+ return Array.from({ length: 5 }).map((_, index) => (
+
+ {columns.map((column) => (
+
+
+
+ ))}
+
+
+
+
+ ))
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ Document Stores
+
+
+ Review and manage every document store in your organization.
+
+
+
+ {
+ setSearch(event.target.value)
+ setPage(0)
+ }}
+ size='small'
+ placeholder='Search by name, description, or owner'
+ sx={{ minWidth: 280 }}
+ />
+
+ refresh()} color='primary'>
+
+
+
+
+
+
+
+
+
+
+ {columns.map((column) => {
+ const isSortable = ['name', 'updatedDate', 'totalChunks', 'loadersCount'].includes(column.id)
+ return (
+
+ {isSortable ? (
+ handleRequestSort(column.id as OrderableColumn)}
+ >
+ {column.label}
+
+ ) : (
+ column.label
+ )}
+
+ )
+ })}
+ Actions
+
+
+
+ {isLoading && renderSkeleton()}
+ {!isLoading && paginatedData.length === 0 && (
+
+
+
+ {isError ? 'Failed to load document stores.' : 'No document stores found.'}
+
+
+
+ )}
+ {!isLoading &&
+ paginatedData.map((store) => {
+ const ownerLabel = store.user?.name || store.user?.email || 'Unknown owner'
+ const loadersCount = store.loaders?.length ?? 0
+ const updatedAt = format(new Date(store.updatedDate), 'MMM d, yyyy p')
+ const detailPath = `/sidekick-studio/document-stores/${store.id}`
+
+ return (
+
+
+
+ {store.name}
+ {store.description && (
+
+ {store.description}
+
+ )}
+
+
+
+
+
+
+ {ownerLabel}
+
+
+ {loadersCount}
+
+
+ {store.totalChunks ?? 0}
+
+
+ {updatedAt}
+
+
+
+
+
+
+
+
+
+ )
+ })}
+
+
+
+
+
+ )
+}
+
+export default AdminDocumentStores
diff --git a/packages-answers/ui/src/Admin/index.tsx b/packages-answers/ui/src/Admin/index.tsx
index e7c432ee507..9a52c23366f 100644
--- a/packages-answers/ui/src/Admin/index.tsx
+++ b/packages-answers/ui/src/Admin/index.tsx
@@ -46,6 +46,19 @@ const AdminDashboard = () => {
+
+
+
+ Document Stores
+
+ View and manage document stores across the organization.
+
+
+
+
+
diff --git a/packages/components/nodes/agents/ToolAgent/AAIToolAgent.ts b/packages/components/nodes/agents/ToolAgent/AAIToolAgent.ts
index 8dcea0a3559..df04be84d86 100644
--- a/packages/components/nodes/agents/ToolAgent/AAIToolAgent.ts
+++ b/packages/components/nodes/agents/ToolAgent/AAIToolAgent.ts
@@ -1,15 +1,154 @@
+import { BaseOutputParser } from '@langchain/core/output_parsers'
+import { OutputFixingParser } from 'langchain/output_parsers'
+import { ICommonObject, INodeData } from '../../../src/Interface'
+import { formatResponse } from '../../outputparsers/OutputParserHelpers'
+
const { nodeClass: OriginalToolAgent } = require('./ToolAgent')
-// AAI-branded clone of ToolAgent for Answer tab
-class AAIToolAgent_Agents extends (OriginalToolAgent as any) {
+// Create a properly typed base class
+const ToolAgentBase = OriginalToolAgent as new (fields?: { sessionId?: string }) => any
+
+// AAI-branded clone of ToolAgent for Answer tab with structured output support
+class AAIToolAgent_Agents extends ToolAgentBase {
+ outputParser: BaseOutputParser
+
constructor(fields?: { sessionId?: string }) {
super(fields)
this.label = 'Tool Agent'
this.name = 'aaiToolAgent'
this.category = 'Agents'
- this.description = 'Tool Agent • Zero configuration required'
+ this.description = 'Tool Agent • Zero configuration required • Supports structured output'
this.tags = ['AAI']
+
+ // Add output parser input to the existing inputs
+ this.inputs = [
+ ...this.inputs,
+ {
+ label: 'Output Parser',
+ name: 'outputParser',
+ type: 'BaseLLMOutputParser',
+ optional: true,
+ description: 'Parse the agent output into a structured format'
+ }
+ ]
+ }
+
+ async init(nodeData: INodeData, input: string, options: ICommonObject): Promise {
+ // Initialize output parser if provided
+ const llmOutputParser = nodeData.inputs?.outputParser as BaseOutputParser
+ this.outputParser = llmOutputParser
+
+ if (llmOutputParser) {
+ let autoFix = (llmOutputParser as any).autoFix
+ if (autoFix === true) {
+ // Get the model from nodeData to create OutputFixingParser
+ const model = nodeData.inputs?.model
+ if (model) {
+ this.outputParser = OutputFixingParser.fromLLM(model, llmOutputParser)
+ }
+ }
+
+ // Inject format instructions into system message and Chat Prompt Template
+ this.injectFormatInstructions(nodeData)
+ }
+
+ // Call the parent init method
+ return await super.init(nodeData, input, options)
+ }
+
+ async run(nodeData: INodeData, input: string, options: ICommonObject): Promise {
+ // Call the parent run method
+ const result = await super.run(nodeData, input, options)
+
+ // Apply output parser to the final result if parser is available
+ if (this.outputParser && result) {
+ return await this.parseOutput(result)
+ }
+
+ return result
+ }
+
+ private injectFormatInstructions(nodeData: INodeData) {
+ if (!this.outputParser || !nodeData.inputs) return
+
+ const formatInstructions = this.outputParser.getFormatInstructions()
+ if (!formatInstructions) return
+
+ // Escape curly braces to prevent LangChain template parsing issues
+ const escapedInstructions = formatInstructions.replace(/\{/g, '{{').replace(/\}/g, '}}')
+ const instructionText = `\n\nIMPORTANT: Your final response must follow this exact format:\n${escapedInstructions}`
+
+ // Handle Chat Prompt Template if it exists
+ const chatPromptTemplate = nodeData.inputs?.chatPromptTemplate
+ if (chatPromptTemplate && chatPromptTemplate.promptMessages?.length > 0) {
+ // Inject into the first system message of the Chat Prompt Template
+ const systemMessage = chatPromptTemplate.promptMessages[0]
+ if (systemMessage && (systemMessage as any).prompt?.template) {
+ const currentTemplate = (systemMessage as any).prompt.template
+ ;(systemMessage as any).prompt.template = currentTemplate + instructionText
+ }
+ } else {
+ // Handle regular system message
+ let systemMessage = (nodeData.inputs?.systemMessage as string) || ''
+ if (systemMessage) {
+ nodeData.inputs.systemMessage = systemMessage + instructionText
+ } else {
+ nodeData.inputs.systemMessage = `You are a helpful assistant.${instructionText}`
+ }
+ }
+ }
+
+ private async parseOutput(result: string | ICommonObject): Promise {
+ try {
+ let textToParse: string
+
+ // Extract text from result
+ if (typeof result === 'string') {
+ textToParse = result
+ } else if (result && typeof result === 'object' && 'text' in result) {
+ textToParse = result.text as string
+ } else {
+ // If result is not in expected format, return as-is
+ return result
+ }
+
+ // Parse the output using the output parser
+ const parsedOutput = await this.outputParser.parse(textToParse)
+
+ // Format the response properly for consistency with LLM Chain
+ const formattedOutput = formatResponse(parsedOutput as string | object)
+
+ // If original result was an object with additional metadata, preserve it
+ if (typeof result === 'object' && result !== null && 'text' in result) {
+ // For Tool Agent with metadata, return the structured format like LLM Chain
+ // Remove the 'text' property and merge the formatted output at the top level
+ const { text: _text, ...metadata } = result
+
+ // Handle both object and string formatted outputs
+ let finalResult: ICommonObject
+ if (typeof formattedOutput === 'object' && formattedOutput !== null) {
+ finalResult = {
+ ...formattedOutput, // This adds the 'json' property at top level
+ ...metadata, // Preserve usedTools, sourceDocuments, artifacts, etc.
+ parsedOutput: parsedOutput // Include the raw structured parsed output for debugging
+ }
+ } else {
+ // If formattedOutput is a string, create json wrapper like LLM Chain does
+ finalResult = {
+ json: parsedOutput, // Direct structured output
+ ...metadata, // Preserve usedTools, sourceDocuments, artifacts, etc.
+ parsedOutput: parsedOutput // Include the raw structured parsed output for debugging
+ }
+ }
+ return finalResult
+ }
+
+ return formattedOutput
+ } catch (error) {
+ // If parsing fails, return the original result
+ return result
+ }
}
}
-module.exports = { nodeClass: AAIToolAgent_Agents }
\ No newline at end of file
+module.exports = { nodeClass: AAIToolAgent_Agents }
diff --git a/packages/components/nodes/chatmodels/ChatAnthropic/AAIChatAnthropic.ts b/packages/components/nodes/chatmodels/ChatAnthropic/AAIChatAnthropic.ts
index 0ff48bb427d..51eb6bc98f8 100644
--- a/packages/components/nodes/chatmodels/ChatAnthropic/AAIChatAnthropic.ts
+++ b/packages/components/nodes/chatmodels/ChatAnthropic/AAIChatAnthropic.ts
@@ -17,7 +17,7 @@ class AAIChatAnthropic_ChatModels implements INode {
baseClasses: string[]
inputs: INodeParams[]
tags: string[]
-
+
constructor() {
this.label = 'Answer ChatAnthropic'
this.name = 'aaiChatAnthropic'
@@ -39,7 +39,7 @@ class AAIChatAnthropic_ChatModels implements INode {
name: 'modelName',
type: 'asyncOptions',
loadMethod: 'listModels',
- default: 'claude-3-sonnet-20240229'
+ default: 'claude-sonnet-4-0'
},
{
label: 'Temperature',
@@ -170,4 +170,4 @@ class AAIChatAnthropic_ChatModels implements INode {
}
}
-module.exports = { nodeClass: AAIChatAnthropic_ChatModels }
\ No newline at end of file
+module.exports = { nodeClass: AAIChatAnthropic_ChatModels }
diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/AAIChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/AAIChatGoogleGenerativeAI.ts
index dedff8a66d5..6ef89778c70 100644
--- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/AAIChatGoogleGenerativeAI.ts
+++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/AAIChatGoogleGenerativeAI.ts
@@ -47,7 +47,7 @@ class AAIGoogleGenerativeAI_ChatModels implements INode {
name: 'modelName',
type: 'asyncOptions',
loadMethod: 'listModels',
- default: 'gemini-1.5-flash'
+ default: 'gemini-2.5-flash'
},
{
label: 'Custom Model Name',
@@ -173,6 +173,25 @@ class AAIGoogleGenerativeAI_ChatModels implements INode {
'Allow image input. Refer to the docs for more details.',
default: false,
optional: true
+ },
+ {
+ label: 'Response Modalities',
+ name: 'responseModalities',
+ type: 'multiOptions',
+ description: 'Specify output modalities. Enable IMAGE for image generation capabilities.',
+ options: [
+ {
+ label: 'TEXT',
+ name: 'TEXT'
+ },
+ {
+ label: 'IMAGE',
+ name: 'IMAGE'
+ }
+ ],
+ default: ['TEXT'],
+ optional: true,
+ additionalParams: true
}
]
}
@@ -184,7 +203,7 @@ class AAIGoogleGenerativeAI_ChatModels implements INode {
}
}
- async init(nodeData: INodeData, _: string, options: ICommonObject): Promise {
+ async init(nodeData: INodeData, _: string, _options: ICommonObject): Promise {
// Use AAI default credentials instead of user-provided credentials
const apiKey = process.env.AAI_DEFAULT_GOOGLE_GENERATIVE_AI_API_KEY
@@ -204,6 +223,7 @@ class AAIGoogleGenerativeAI_ChatModels implements INode {
const contextCache = nodeData.inputs?.contextCache as FlowiseGoogleAICacheManager
const streaming = nodeData.inputs?.streaming as boolean
const baseUrl = nodeData.inputs?.baseUrl as string | undefined
+ const responseModalities = nodeData.inputs?.responseModalities as string
const allowImageUploads = nodeData.inputs?.allowImageUploads as boolean
@@ -219,6 +239,7 @@ class AAIGoogleGenerativeAI_ChatModels implements INode {
if (cache) obj.cache = cache
if (temperature) obj.temperature = parseFloat(temperature)
if (baseUrl) obj.baseUrl = baseUrl
+ if (responseModalities) obj.responseModalities = convertMultiOptionsToStringArray(responseModalities)
// Safety Settings
let harmCategories: string[] = convertMultiOptionsToStringArray(harmCategory)
@@ -243,8 +264,17 @@ class AAIGoogleGenerativeAI_ChatModels implements INode {
model.setMultiModalOption(multiModalOption)
if (contextCache) model.setContextCache(contextCache)
+ // Set user context for image uploads
+ if (_options?.user) {
+ model.setUserContext({
+ organizationId: _options.user.organizationId,
+ userId: _options.user.id,
+ userEmail: _options.user.email || `${_options.user.id}@local`
+ })
+ }
+
return model
}
}
-module.exports = { nodeClass: AAIGoogleGenerativeAI_ChatModels }
\ No newline at end of file
+module.exports = { nodeClass: AAIGoogleGenerativeAI_ChatModels }
diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts
index 3f01b8624ba..c02730b5e21 100644
--- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts
+++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/ChatGoogleGenerativeAI.ts
@@ -248,6 +248,15 @@ class GoogleGenerativeAI_ChatModels implements INode {
model.setMultiModalOption(multiModalOption)
if (contextCache) model.setContextCache(contextCache)
+ // Set user context for image uploads
+ if (options?.user) {
+ model.setUserContext({
+ organizationId: options.user.organizationId,
+ userId: options.user.id,
+ userEmail: options.user.email || `${options.user.id}@local`
+ })
+ }
+
return model
}
}
diff --git a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/FlowiseChatGoogleGenerativeAI.ts b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/FlowiseChatGoogleGenerativeAI.ts
index 87c7445e896..df6ac027e95 100644
--- a/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/FlowiseChatGoogleGenerativeAI.ts
+++ b/packages/components/nodes/chatmodels/ChatGoogleGenerativeAI/FlowiseChatGoogleGenerativeAI.ts
@@ -59,6 +59,7 @@ export interface GoogleGenerativeAIChatInput extends BaseChatModelParams, Pick 0) {
+ modelConfig.config = { responseModalities: this.responseModalities }
+ }
+
+ this.client = new GenerativeAI(this.apiKey ?? '').getGenerativeModel(modelConfig, {
+ baseUrl: this.baseUrl
+ })
if (this.contextCache) {
const cachedContent = await this.contextCache.lookup({
contents: prompt ? [{ ...prompt[0], parts: prompt[0].parts.slice(0, 1) }] : [],
@@ -231,6 +247,82 @@ class LangchainChatGoogleGenerativeAI
this.contextCache = contextCache
}
+ setUserContext(userContext: { organizationId: string; userId: string; userEmail: string }): void {
+ this.userContext = userContext
+ }
+
+ /**
+ * Extract user context from stored instance or use fallbacks
+ */
+ extractUserContext(options?: any): { organizationId: string; userId: string; userEmail: string } {
+ // First try stored user context (set during initialization)
+ if (this.userContext) {
+ return this.userContext
+ }
+
+ // Try to get user from Flowise run params (from buildAgentflow.ts)
+ const user = options?.user || options?.runParams?.user
+
+ if (user?.id && user?.organizationId) {
+ return {
+ organizationId: user.organizationId,
+ userId: user.id,
+ userEmail: user.email || `${user.id}@local`
+ }
+ }
+
+ // Fallback to system context for tool calls or when user context is unavailable
+ return {
+ organizationId: 'system-org',
+ userId: 'chat-system',
+ userEmail: 'system@chat.local'
+ }
+ }
+
+ async uploadImageToStorage(imageData: any, userContext: { organizationId: string; userId: string; userEmail: string }) {
+ try {
+ // Import and use the storage utility directly
+ const { addSingleFileToStorage } = await import('../../../src/storageUtils')
+ const crypto = await import('node:crypto')
+
+ // Convert base64 to Buffer
+ const buffer = Buffer.from(imageData.data, 'base64')
+
+ // Generate unique identifier for this image generation session
+ const timestamp = Date.now()
+ const randomSuffix = crypto.randomBytes(8).toString('hex')
+ const sessionId = `${timestamp}_${randomSuffix}`
+
+ // Create image filename with session ID
+ const mimeType = imageData.mimeType || 'image/png'
+ const fileExtension = mimeType.split('/')[1] || 'png'
+ const imageFilename = `${sessionId}_chat_generated_image.${fileExtension}`
+
+ // Store the image using organization/user folder structure
+ const imageStorageUrl = await addSingleFileToStorage(
+ mimeType,
+ buffer,
+ imageFilename,
+ 'gemini-images',
+ userContext.organizationId,
+ userContext.userId
+ )
+
+ // Convert FILE-STORAGE:: reference to a full URL with domain
+ const domain = process.env.DOMAIN || process.env.FLOWISE_DOMAIN || 'http://localhost:4000'
+ const imageFileName = imageStorageUrl.replace('FILE-STORAGE::', '')
+ const fullImageUrl = `${domain}/api/v1/get-upload-file?chatflowId=gemini-images&chatId=${userContext.organizationId}%2F${userContext.userId}&fileName=${imageFileName}`
+ return {
+ url: fullImageUrl,
+ success: true,
+ sessionId
+ }
+ } catch (error) {
+ console.warn('Upload error:', error)
+ return { success: false, error: String(error) }
+ }
+ }
+
async getNumTokens(prompt: BaseMessage[]) {
const contents = convertBaseMessagesToContent(prompt, this._isMultimodalModel)
const { totalTokens } = await this.client.countTokens({ contents })
@@ -293,6 +385,30 @@ class LangchainChatGoogleGenerativeAI
// Map the API response to a ChatResult object for LangChain
const generationResult = mapGenerateContentResultToChatResult(res.response, this.modelName, genAIUsageMetadata)
+ // Handle image data if present in non-streaming mode
+ if (generationResult.generations?.[0]?.message?.additional_kwargs?.imageData) {
+ try {
+ const imageData = generationResult.generations[0].message.additional_kwargs.imageData
+
+ // Extract user context from LangChain run config or use fallbacks
+ const userContext = this.extractUserContext(options)
+
+ const uploadResponse = await this.uploadImageToStorage(imageData, userContext)
+
+ if (uploadResponse.success) {
+ // Update the generation with the uploaded image URL
+ generationResult.generations[0].message.additional_kwargs.imageUrl = uploadResponse.url
+ generationResult.generations[0].message.additional_kwargs.imageSessionId = uploadResponse.sessionId
+ const imageMarkdown = `\n\n`
+ generationResult.generations[0].text += imageMarkdown
+ // CRITICAL: Also update the message content to include the image markdown
+ generationResult.generations[0].message.content = generationResult.generations[0].text
+ }
+ } catch (error) {
+ console.warn('Failed to upload generated image:', error)
+ }
+ }
+
// Optionally notify the run manager of the new token (for streaming UI updates)
await _runManager?.handleLLMNewToken(generationResult.generations?.length ? generationResult.generations[0].text : '')
return generationResult
@@ -323,6 +439,8 @@ class LangchainChatGoogleGenerativeAI
const stream = this._streamResponseChunks(messages, options, runManager)
// Store the final chunks by their index (for multi-candidate support)
const finalChunks: Record = {}
+ // Track custom text modifications (like image markdown) separately
+ const customTextModifications: Record = {}
// Aggregate all streamed chunks by their index
for await (const chunk of stream) {
@@ -339,10 +457,16 @@ class LangchainChatGoogleGenerativeAI
if (finalChunks[index] === undefined) {
// First chunk for this index
finalChunks[index] = chunk
+ // Track any custom text modifications
+ customTextModifications[index] = chunk.text
} else {
// Concatenate the chunks for the same index
const existingChunk = finalChunks[index]
const concatenated = existingChunk.concat(chunk)
+
+ // Preserve custom text modifications by manually tracking them
+ customTextModifications[index] = (customTextModifications[index] || '') + chunk.text
+
// Use the latest chunk's usage_metadata (which has the correct diff details)
// @ts-ignore - Custom metadata structure
concatenated.message.usage_metadata = chunk.message.usage_metadata
@@ -376,7 +500,18 @@ class LangchainChatGoogleGenerativeAI
// Sort and collect all generations in order by index
const generations = Object.entries(finalChunks)
.sort(([aKey], [bKey]) => parseInt(aKey, 10) - parseInt(bKey, 10))
- .map(([_, value]) => value)
+ .map(([indexStr, value]) => {
+ const index = parseInt(indexStr, 10)
+ // Override the text with our custom modifications to preserve image markdown
+ if (customTextModifications[index] !== undefined) {
+ value.text = customTextModifications[index]
+ // Also update the message content
+ if (value.message) {
+ value.message.content = customTextModifications[index]
+ }
+ }
+ return value
+ })
// Attach aggregated token usage to each generation
for (const generation of generations) {
@@ -410,6 +545,9 @@ class LangchainChatGoogleGenerativeAI
let prompt = convertBaseMessagesToContent(messages, this._isMultimodalModel)
prompt = checkIfEmptyContentAndSameRole(prompt)
+ // Convert any function response parts in the prompt for compatibility
+ this.convertFunctionResponse(prompt)
+
// Prepare API request parameters
const parameters = this.invocationParams(options)
const request = {
@@ -520,6 +658,30 @@ class LangchainChatGoogleGenerativeAI
index += 1 // Increment chunk index for next chunk
if (!chunk) continue // Skip if chunk is null
+ // Handle image data if present
+ if (chunk.message.additional_kwargs?.imageData) {
+ try {
+ const imageData = chunk.message.additional_kwargs.imageData
+
+ // Extract user context from LangChain run config or use fallbacks
+ const userContext = this.extractUserContext(options)
+
+ const uploadResponse = await this.uploadImageToStorage(imageData, userContext)
+
+ if (uploadResponse.success) {
+ // Update the chunk with the uploaded image URL
+ chunk.message.additional_kwargs.imageUrl = uploadResponse.url
+ chunk.message.additional_kwargs.imageSessionId = uploadResponse.sessionId
+ const imageMarkdown = `\n\n`
+ chunk.text += imageMarkdown
+ // CRITICAL: Also update the message content to include the image markdown
+ chunk.message.content = chunk.text
+ }
+ } catch (error) {
+ console.warn('Failed to upload generated image:', error)
+ }
+ }
+
// Yield the chunk to the consumer
yield chunk
// Optionally notify the run manager of the new token (for streaming UI updates)
@@ -789,25 +951,44 @@ function mapGenerateContentResultToChatResult(
// Extract content and generation info from the candidate
const { content, ...generationInfo } = candidate
- // Get the generated text (if any)
- const text = content?.parts[0]?.text ?? ''
+
+ // Handle both text and image content
+ let text = ''
+ let imageData: any = null
+
+ if (content?.parts) {
+ for (const part of content.parts) {
+ if (part.text) {
+ text += part.text
+ } else if (part.inlineData) {
+ // Store image data for handling
+ imageData = part.inlineData
+ }
+ }
+ }
// Extract usage metadata if available (for reporting token usage)
const usageMetadata: any = extra?.usageMetadata
// Build the ChatGeneration object for LangChain
+ const additionalKwargs: any = {
+ model_name,
+ ...generationInfo
+ }
+
+ if (imageData) {
+ additionalKwargs.imageData = imageData
+ }
+
const generation: ChatGeneration = {
text,
message: new AIMessage({
content: text,
tool_calls: functionCalls,
- additional_kwargs: {
- model_name,
- ...generationInfo
- },
+ additional_kwargs: additionalKwargs,
usage_metadata: {
input_tokens: usageMetadata?.promptTokenCount ?? 0, // Number of prompt tokens used
- output_tokens: usageMetadata?.candidatesTokenCount ?? 0 + usageMetadata?.thoughtsTokenCount ?? 0, // Output tokens
+ output_tokens: (usageMetadata?.candidatesTokenCount ?? 0) + (usageMetadata?.thoughtsTokenCount ?? 0), // Output tokens
total_tokens: usageMetadata?.totalTokenCount ?? 0, // Total tokens used
input_token_details: Array.isArray(usageMetadata?.promptTokensDetails)
? usageMetadata?.promptTokensDetails.reduce((acc: any, curr: any) => {
@@ -848,7 +1029,21 @@ function convertResponseContentToChatGenerationChunk(
const functionCalls = response.functionCalls()
const [candidate] = response.candidates
const { content, ...generationInfo } = candidate
- const text = content?.parts?.[0]?.text ?? ''
+
+ // Handle both text and image content
+ let text = ''
+ let imageData: any = null
+
+ if (content?.parts) {
+ for (const part of content.parts) {
+ if (part.text) {
+ text += part.text
+ } else if (part.inlineData) {
+ // Store image data for handling
+ imageData = part.inlineData
+ }
+ }
+ }
const toolCallChunks: ToolCallChunk[] = []
if (functionCalls) {
@@ -861,6 +1056,11 @@ function convertResponseContentToChatGenerationChunk(
)
}
+ const additionalKwargs: any = {}
+ if (imageData) {
+ additionalKwargs.imageData = imageData
+ }
+
return new ChatGenerationChunk({
text,
message: new AIMessageChunk({
@@ -869,7 +1069,7 @@ function convertResponseContentToChatGenerationChunk(
tool_call_chunks: toolCallChunks,
// Each chunk can have unique "generationInfo", and merging strategy is unclear,
// so leave blank for now.
- additional_kwargs: {},
+ additional_kwargs: additionalKwargs,
usage_metadata: extra.usageMetadata as any
}),
generationInfo
diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/AAIChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/AAIChatOpenAI.ts
index cdfb0925e7f..0fa01e67911 100644
--- a/packages/components/nodes/chatmodels/ChatOpenAI/AAIChatOpenAI.ts
+++ b/packages/components/nodes/chatmodels/ChatOpenAI/AAIChatOpenAI.ts
@@ -17,7 +17,7 @@ class AAIChatOpenAI_ChatModels implements INode {
baseClasses: string[]
inputs: INodeParams[]
tags: string[]
-
+
constructor() {
this.label = 'Answer ChatOpenAI'
this.name = 'aaiChatOpenAI'
@@ -193,6 +193,14 @@ class AAIChatOpenAI_ChatModels implements INode {
default: 'medium',
optional: false,
additionalParams: true
+ },
+ {
+ label: 'Prompt Cache Key',
+ name: 'promptCacheKey',
+ type: 'string',
+ description: 'Cache key for OpenAI prompt caching to optimize cache hit rates and reduce costs',
+ optional: true,
+ additionalParams: true
}
]
}
@@ -219,6 +227,7 @@ class AAIChatOpenAI_ChatModels implements INode {
const proxyUrl = nodeData.inputs?.proxyUrl as string
const baseOptions = nodeData.inputs?.baseOptions
const reasoningEffort = nodeData.inputs?.reasoningEffort as OpenAIClient.Chat.ChatCompletionReasoningEffort
+ const promptCacheKey = nodeData.inputs?.promptCacheKey as string
const allowImageUploads = nodeData.inputs?.allowImageUploads as boolean
const imageResolution = nodeData.inputs?.imageResolution as string
@@ -256,6 +265,7 @@ class AAIChatOpenAI_ChatModels implements INode {
obj.stop = stopSequenceArray
}
if (strictToolCalling) obj.supportsStrictToolCalling = strictToolCalling
+ if (promptCacheKey) (obj as any).prompt_cache_key = promptCacheKey
let parsedBaseOptions: any | undefined = undefined
@@ -294,4 +304,4 @@ class AAIChatOpenAI_ChatModels implements INode {
}
}
-module.exports = { nodeClass: AAIChatOpenAI_ChatModels }
\ No newline at end of file
+module.exports = { nodeClass: AAIChatOpenAI_ChatModels }
diff --git a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts
index 31b62d91e20..58239028229 100644
--- a/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts
+++ b/packages/components/nodes/chatmodels/ChatOpenAI/ChatOpenAI.ts
@@ -199,6 +199,14 @@ class ChatOpenAI_ChatModels implements INode {
default: 'medium',
optional: false,
additionalParams: true
+ },
+ {
+ label: 'Prompt Cache Key',
+ name: 'promptCacheKey',
+ type: 'string',
+ description: 'Cache key for OpenAI prompt caching to optimize cache hit rates and reduce costs',
+ optional: true,
+ additionalParams: true
}
]
}
@@ -225,6 +233,7 @@ class ChatOpenAI_ChatModels implements INode {
const proxyUrl = nodeData.inputs?.proxyUrl as string
const baseOptions = nodeData.inputs?.baseOptions
const reasoningEffort = nodeData.inputs?.reasoningEffort as OpenAIClient.Chat.ChatCompletionReasoningEffort
+ const promptCacheKey = nodeData.inputs?.promptCacheKey as string
const allowImageUploads = nodeData.inputs?.allowImageUploads as boolean
const imageResolution = nodeData.inputs?.imageResolution as string
@@ -261,6 +270,7 @@ class ChatOpenAI_ChatModels implements INode {
obj.stop = stopSequenceArray
}
if (strictToolCalling) obj.supportsStrictToolCalling = strictToolCalling
+ if (promptCacheKey) (obj as any).prompt_cache_key = promptCacheKey
let parsedBaseOptions: any | undefined = undefined
diff --git a/packages/components/nodes/tools/CreateGoogleGenerativeAIImage/GoogleGemini.svg b/packages/components/nodes/tools/CreateGoogleGenerativeAIImage/GoogleGemini.svg
new file mode 100644
index 00000000000..53b497fa1a0
--- /dev/null
+++ b/packages/components/nodes/tools/CreateGoogleGenerativeAIImage/GoogleGemini.svg
@@ -0,0 +1,34 @@
+
diff --git a/packages/components/nodes/tools/MCP/AnswerAgent/AnswerAgentMCP.ts b/packages/components/nodes/tools/MCP/AnswerAgent/AnswerAgentMCP.ts
index 5d8f5e1c4e2..3da359a5fc1 100644
--- a/packages/components/nodes/tools/MCP/AnswerAgent/AnswerAgentMCP.ts
+++ b/packages/components/nodes/tools/MCP/AnswerAgent/AnswerAgentMCP.ts
@@ -103,13 +103,15 @@ class AnswerAgent_MCP implements INode {
// Get API host from environment variable
const apiHost = options.user?.chatflowDomain
if (!apiHost) {
- throw new Error('API_HOST environment variable is not set')
+ console.error('AnswerAgent MCP: API_HOST environment variable is not set')
+ return []
}
// Get user's API key from database
const apiKey = await this.getUserApiKey(nodeData, options)
if (!apiKey) {
- throw new Error('Unable to retrieve user API key from database')
+ console.error('AnswerAgent MCP: Unable to retrieve user API key from database')
+ return []
}
// Get the package path for the AnswerAgent MCP server
diff --git a/packages/components/nodes/tools/MCP/Atlassian/AtlassianMcp.ts b/packages/components/nodes/tools/MCP/Atlassian/AtlassianMcp.ts
index 11af2a1b7ea..207fa730c29 100644
--- a/packages/components/nodes/tools/MCP/Atlassian/AtlassianMcp.ts
+++ b/packages/components/nodes/tools/MCP/Atlassian/AtlassianMcp.ts
@@ -125,7 +125,8 @@ class Atlassian_MCP implements INode {
const credentialData = await getCredentialData(nodeData.credential || '', options)
if (!credentialData.access_token) {
- throw new Error('Access token not found in credential data')
+ console.error('Atlassian MCP: Access token not found in credential data')
+ return []
}
if (!process.env.ATLASSIAN_MCP_SERVER_URL) {
diff --git a/packages/components/nodes/tools/MCP/Contentful/ContentfulMCP.ts b/packages/components/nodes/tools/MCP/Contentful/ContentfulMCP.ts
index 86cedbe3394..1a843e043c2 100644
--- a/packages/components/nodes/tools/MCP/Contentful/ContentfulMCP.ts
+++ b/packages/components/nodes/tools/MCP/Contentful/ContentfulMCP.ts
@@ -119,7 +119,8 @@ class Contentful_MCP implements INode {
const environmentId = nodeData.inputs?.environmentId
if (!managementToken || !spaceId || !environmentId) {
- throw new Error('Missing Credentials')
+ console.error('Contentful MCP: Missing Credentials - managementToken, spaceId, or environmentId not provided')
+ return []
}
const packagePath = getNodeModulesPackagePath('@last-rev/contentful-mcp-server/bin/mcp-server.js')
diff --git a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts
index fc49d237ce7..fecd5d811c2 100644
--- a/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts
+++ b/packages/components/nodes/tools/MCP/CustomMCP/CustomMCP.ts
@@ -78,6 +78,7 @@ class Custom_MCP implements INode {
description: rest.description || name
}))
} catch (error) {
+ console.error('Custom MCP: Error listing actions:', error)
return [
{
label: 'No Available Actions',
@@ -109,7 +110,8 @@ class Custom_MCP implements INode {
const mcpServerConfig = nodeData.inputs?.mcpServerConfig as string
if (!mcpServerConfig) {
- throw new Error('MCP Server Config is required')
+ console.error('Custom MCP: MCP Server Config is required')
+ return []
}
try {
@@ -135,7 +137,8 @@ class Custom_MCP implements INode {
return tools as Tool[]
} catch (error) {
- throw new Error(`Invalid MCP Server Config: ${error}`)
+ console.error('Custom MCP: Invalid MCP Server Config:', error)
+ return []
}
}
}
diff --git a/packages/components/nodes/tools/MCP/Github/GithubMCP.ts b/packages/components/nodes/tools/MCP/Github/GithubMCP.ts
index cd6928886b0..a0648b0a88a 100644
--- a/packages/components/nodes/tools/MCP/Github/GithubMCP.ts
+++ b/packages/components/nodes/tools/MCP/Github/GithubMCP.ts
@@ -106,7 +106,8 @@ class Github_MCP implements INode {
const accessToken = getCredentialParam('accessToken', credentialData, nodeData)
if (!accessToken) {
- throw new Error('Missing Github Access Token')
+ console.error('Github MCP: Missing Github Access Token')
+ return []
}
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-github/dist/index.js')
diff --git a/packages/components/nodes/tools/MCP/PostgreSQL/PostgreSQLMCP.ts b/packages/components/nodes/tools/MCP/PostgreSQL/PostgreSQLMCP.ts
index 70d010fdd83..a1ffc61959f 100644
--- a/packages/components/nodes/tools/MCP/PostgreSQL/PostgreSQLMCP.ts
+++ b/packages/components/nodes/tools/MCP/PostgreSQL/PostgreSQLMCP.ts
@@ -106,7 +106,8 @@ class PostgreSQL_MCP implements INode {
const postgresUrl = getCredentialParam('postgresUrl', credentialData, nodeData)
if (!postgresUrl) {
- throw new Error('No postgres url provided')
+ console.error('PostgreSQL MCP: No postgres url provided')
+ return []
}
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-postgres/dist/index.js')
diff --git a/packages/components/nodes/tools/MCP/SalesforceOauthMcp/SalesforceOauthMcp.ts b/packages/components/nodes/tools/MCP/SalesforceOauthMcp/SalesforceOauthMcp.ts
index 04b3d219a3f..41fa740f81e 100644
--- a/packages/components/nodes/tools/MCP/SalesforceOauthMcp/SalesforceOauthMcp.ts
+++ b/packages/components/nodes/tools/MCP/SalesforceOauthMcp/SalesforceOauthMcp.ts
@@ -115,7 +115,8 @@ class SalesforceOauth_MCP implements INode {
// Validate refresh token
if (!refreshToken) {
- throw new Error('Refresh token is required for Salesforce OAuth MCP')
+ console.error('Salesforce OAuth MCP: Refresh token is required for Salesforce OAuth MCP')
+ return []
}
// Get environment variables for OAuth configuration
@@ -125,9 +126,10 @@ class SalesforceOauth_MCP implements INode {
// Validate environment variables
if (!salesforceClientId || !salesforceClientSecret || !salesforceInstanceUrl) {
- throw new Error(
- 'Missing required environment variables: SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET, SALESFORCE_INSTANCE_URL'
+ console.error(
+ 'Salesforce OAuth MCP: Missing required environment variables: SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET, SALESFORCE_INSTANCE_URL'
)
+ return []
}
const packagePath = getNodeModulesPackagePath('@answerai/salesforce-mcp/dist/index.js')
diff --git a/packages/components/nodes/tools/MCP/Slack/SlackMCP.ts b/packages/components/nodes/tools/MCP/Slack/SlackMCP.ts
index f117150f6a7..d5d2cc67a00 100644
--- a/packages/components/nodes/tools/MCP/Slack/SlackMCP.ts
+++ b/packages/components/nodes/tools/MCP/Slack/SlackMCP.ts
@@ -107,7 +107,8 @@ class Slack_MCP implements INode {
const teamId = getCredentialParam('teamId', credentialData, nodeData)
if (!botToken || !teamId) {
- throw new Error('Missing Credentials')
+ console.error('Slack MCP: Missing Credentials - botToken or teamId not provided')
+ return []
}
const packagePath = getNodeModulesPackagePath('@modelcontextprotocol/server-slack/dist/index.js')
diff --git a/packages/components/nodes/tools/MCP/Youtube/YoutubeMCP.ts b/packages/components/nodes/tools/MCP/Youtube/YoutubeMCP.ts
index 4d673c984b7..f794f8a6e98 100644
--- a/packages/components/nodes/tools/MCP/Youtube/YoutubeMCP.ts
+++ b/packages/components/nodes/tools/MCP/Youtube/YoutubeMCP.ts
@@ -106,7 +106,8 @@ class Youtube_MCP implements INode {
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
if (!apiKey) {
- throw new Error('Missing Youtube API Key')
+ console.error('Youtube MCP: Missing Youtube API Key')
+ return []
}
const packagePath = getNodeModulesPackagePath('youtube-data-mcp-server/dist/index.js')
diff --git a/packages/components/nodes/tools/MCP/core.ts b/packages/components/nodes/tools/MCP/core.ts
index 5f48bfc3255..0fbc82dfa5a 100644
--- a/packages/components/nodes/tools/MCP/core.ts
+++ b/packages/components/nodes/tools/MCP/core.ts
@@ -87,33 +87,53 @@ export class MCPToolkit extends BaseToolkit {
async initialize() {
if (this._tools === null) {
- this.client = await this.createClient()
+ try {
+ this.client = await this.createClient()
- this._tools = await this.client.request({ method: 'tools/list' }, ListToolsResultSchema)
+ this._tools = await this.client.request({ method: 'tools/list' }, ListToolsResultSchema)
- this.tools = await this.get_tools()
+ this.tools = await this.get_tools()
- // Close the initial client after initialization
- await this.client.close()
+ // Close the initial client after initialization
+ await this.client.close()
+ } catch (error) {
+ console.error('MCP Toolkit: Failed to initialize, setting empty tools:', error)
+ this._tools = { tools: [] }
+ this.tools = []
+ if (this.client) {
+ try {
+ await this.client.close()
+ } catch (closeError) {
+ console.error('MCP Toolkit: Error closing client:', closeError)
+ }
+ }
+ }
}
}
async get_tools(): Promise {
- if (this._tools === null || this.client === null) {
- throw new Error('Must initialize the toolkit first')
+ if (this._tools === null) {
+ console.error('MCP Toolkit: get_tools called before initialization, returning empty array')
+ return []
}
- const toolsPromises = this._tools.tools.map(async (tool: any) => {
- if (this.client === null) {
- throw new Error('Client is not initialized')
- }
- return await MCPTool({
- toolkit: this,
- name: tool.name,
- description: tool.description || '',
- argsSchema: createSchemaModel(tool.inputSchema)
+ if (this.client === null) {
+ console.error('MCP Toolkit: Client not initialized, returning empty array')
+ return []
+ }
+ try {
+ const toolsPromises = this._tools.tools.map(async (tool: any) => {
+ return await MCPTool({
+ toolkit: this,
+ name: tool.name,
+ description: tool.description || '',
+ argsSchema: createSchemaModel(tool.inputSchema)
+ })
})
- })
- return Promise.all(toolsPromises)
+ return Promise.all(toolsPromises)
+ } catch (error) {
+ console.error('MCP Toolkit: Error creating tools, returning empty array:', error)
+ return []
+ }
}
}
diff --git a/packages/components/package.json b/packages/components/package.json
index 8423a39ef3a..3bc46d9cffa 100644
--- a/packages/components/package.json
+++ b/packages/components/package.json
@@ -24,7 +24,7 @@
"dependencies": {
"@answerai/answeragent-mcp": "^1.0.1",
"@answerai/confluence-mcp": "^1.0.0",
- "@answerai/jira-mcp": "^1.0.2",
+ "@answerai/jira-mcp": "^1.1.0",
"@answerai/salesforce-mcp": "^0.1.0",
"@apidevtools/json-schema-ref-parser": "^12.0.2",
"@arizeai/openinference-instrumentation-langchain": "^2.0.0",
diff --git a/packages/server/src/controllers/documentstore/index.ts b/packages/server/src/controllers/documentstore/index.ts
index 7f68d659cbd..668250308ec 100644
--- a/packages/server/src/controllers/documentstore/index.ts
+++ b/packages/server/src/controllers/documentstore/index.ts
@@ -33,6 +33,23 @@ const getAllDocumentStores = async (req: Request, res: Response, next: NextFunct
}
}
+const getAdminDocumentStores = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const isAdmin = req.user?.roles?.includes('Admin') || req.user?.permissions?.includes('org:manage')
+ if (!isAdmin) {
+ throw new InternalFlowiseError(
+ StatusCodes.FORBIDDEN,
+ `Error: documentStoreController.getAdminDocumentStores - admin permissions required!`
+ )
+ }
+
+ const apiResponse = await documentStoreService.getAdminDocumentStores(req.user!)
+ return res.json(apiResponse)
+ } catch (error) {
+ next(error)
+ }
+}
+
const deleteLoaderFromDocumentStore = async (req: Request, res: Response, next: NextFunction) => {
try {
const storeId = req.params.id
@@ -532,6 +549,7 @@ export default {
deleteDocumentStore,
createDocumentStore,
getAllDocumentStores,
+ getAdminDocumentStores,
deleteLoaderFromDocumentStore,
getDocumentStoreById,
getDocumentStoreFileChunks,
diff --git a/packages/server/src/routes/admin/index.ts b/packages/server/src/routes/admin/index.ts
index 5d33f6e5f6e..1147ac0b1d1 100644
--- a/packages/server/src/routes/admin/index.ts
+++ b/packages/server/src/routes/admin/index.ts
@@ -1,5 +1,6 @@
import express from 'express'
import chatflowsController from '../../controllers/chatflows'
+import documentStoreController from '../../controllers/documentstore'
import organizationsController from '../../controllers/organizations'
import enforceAbility from '../../middlewares/authentication/enforceAbility'
const router = express.Router()
@@ -13,6 +14,10 @@ router.get('/chatflows/:id/versions', enforceAbility('ChatFlow'), chatflowsContr
router.put('/chatflows/bulk-update', enforceAbility('ChatFlow'), chatflowsController.bulkUpdateChatflows)
router.post('/chatflows/:id/rollback/:version', enforceAbility('ChatFlow'), chatflowsController.rollbackChatflowToVersion)
+// DOCUMENT STORES
+// READ
+router.get('/document-stores', enforceAbility('DocumentStore'), documentStoreController.getAdminDocumentStores)
+
// ORGANIZATIONS
// READ
router.get('/organizations/credentials', enforceAbility('Organization'), organizationsController.getOrganizationCredentials)
diff --git a/packages/server/src/services/documentstore/index.ts b/packages/server/src/services/documentstore/index.ts
index 7f72a31434d..d3e16adacc0 100644
--- a/packages/server/src/services/documentstore/index.ts
+++ b/packages/server/src/services/documentstore/index.ts
@@ -69,6 +69,17 @@ import { INPUT_PARAMS_TYPE, OMIT_QUEUE_JOB_DATA } from '../../utils/constants'
const DOCUMENT_STORE_BASE_FOLDER = 'docustore'
+type AdminDocumentStoreDTO = DocumentStoreDTO & {
+ user: {
+ id?: string
+ name?: string | null
+ email?: string | null
+ }
+ userId?: string
+ organizationId?: string
+ isOwner: boolean
+}
+
const createDocumentStore = async (newDocumentStore: DocumentStore, userId: string, organizationId: string) => {
try {
const appServer = getRunningExpressApp()
@@ -101,6 +112,51 @@ const getAllDocumentStores = async (user: IUser) => {
}
}
+const getAdminDocumentStores = async (user: IUser): Promise => {
+ try {
+ const appServer = getRunningExpressApp()
+ const { id: userId, organizationId, permissions, roles } = user
+ const documentStoreRepository = appServer.AppDataSource.getRepository(DocumentStore)
+ const queryBuilder = documentStoreRepository
+ .createQueryBuilder('documentStore')
+ .leftJoin('User', 'user', 'user.id = documentStore.userId')
+ .addSelect(['user.id', 'user.name', 'user.email'])
+
+ if (organizationId) {
+ queryBuilder.where('documentStore.organizationId = :organizationId', { organizationId })
+ }
+
+ const isAdmin = Boolean(roles?.includes('Admin') || permissions?.includes('org:manage'))
+ if (!isAdmin && userId) {
+ queryBuilder.andWhere('documentStore.userId = :userId', { userId })
+ }
+
+ const rawResults = await queryBuilder.orderBy('documentStore.updatedDate', 'DESC').getRawAndEntities()
+
+ return rawResults.entities.map((entity, index) => {
+ const dto = DocumentStoreDTO.fromEntity(entity)
+ const raw = rawResults.raw[index]
+
+ return {
+ ...dto,
+ user: {
+ id: raw?.user_id ?? entity.userId,
+ name: raw?.user_name ?? null,
+ email: raw?.user_email ?? null
+ },
+ userId: entity.userId,
+ organizationId: entity.organizationId,
+ isOwner: entity.userId === userId
+ }
+ })
+ } catch (error) {
+ throw new InternalFlowiseError(
+ StatusCodes.INTERNAL_SERVER_ERROR,
+ `Error: documentStoreServices.getAdminDocumentStores - ${getErrorMessage(error)}`
+ )
+ }
+}
+
const getAllDocumentFileChunks = async (user: IUser) => {
try {
const appServer = getRunningExpressApp()
@@ -2264,6 +2320,7 @@ export default {
createDocumentStore,
deleteLoaderFromDocumentStore,
getAllDocumentStores,
+ getAdminDocumentStores,
getAllDocumentFileChunks,
getDocumentStoreById,
getUsedChatflowNames,
diff --git a/packages/server/src/utils/getUploadsConfig.ts b/packages/server/src/utils/getUploadsConfig.ts
index 937fcab02d7..21b94ee07eb 100644
--- a/packages/server/src/utils/getUploadsConfig.ts
+++ b/packages/server/src/utils/getUploadsConfig.ts
@@ -80,7 +80,7 @@ export const utilGetUploadsConfig = async (chatflowid: string): Promise imgUploadAllowedNodes.includes(node.data.name))) {
+ // Check if any chat model has allowImageUploads enabled
+ const hasChatModelWithImageUploads = nodes.some((node: IReactFlowNode) => {
+ return node.data.category === 'Chat Models' && node.data.inputs?.['allowImageUploads'] === true
+ })
+
+ if (nodes.some((node) => imgUploadAllowedNodes.includes(node.data.name)) || hasChatModelWithImageUploads) {
nodes.forEach((node: IReactFlowNode) => {
const data = node.data
if (data.category === 'Chat Models' && data.inputs?.['allowImageUploads'] === true) {
diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts
index 5a2faa0fe3f..f48e23af06e 100644
--- a/packages/server/src/utils/index.ts
+++ b/packages/server/src/utils/index.ts
@@ -68,42 +68,6 @@ import {
} from '@aws-sdk/client-secrets-manager'
import { fetchMCPMetadata, MCPOAuthMetadata } from './mcp-metadata'
-/**
- * Enhanced error message for MCP connection errors with deep links to credentials
- */
-const enhanceMcpInitializationError = (error: any, reactFlowNode: IReactFlowNode, baseURL: string): string => {
- const errorMessage = getErrorMessage(error)
-
- // Check if this is an MCP connection error
- if (errorMessage.includes('MCP') && errorMessage.includes('Connection closed')) {
- // Extract credential name from input parameters
- let credentialType = ''
- if (reactFlowNode.data.inputParams) {
- const credentialParam = reactFlowNode.data.inputParams.find((param: any) => param.type === 'credential')
- if (credentialParam && credentialParam.credentialNames && credentialParam.credentialNames.length > 0) {
- credentialType = credentialParam.credentialNames[0]
- }
- }
-
- if (credentialType) {
- // Generate deep link URL for credential setup
- const credentialLink = `/sidekick-studio/credentials?cred=${credentialType}`
- const mcpDocsLink = `https://answeragent.ai/docs/sidekick-studio/chatflows/tools-mcp`
-
- return `**This Sidekick needs an integration setup.** [Click here to set it up](${credentialLink}). [Read more about how to troubleshoot MCP servers and integrations](${mcpDocsLink}).`
- }
-
- // Generic MCP error message if we can't identify the specific credential
- const credentialsLink = `/sidekick-studio/credentials`
- const mcpDocsLink = `https://answeragent.ai/docs/sidekick-studio/chatflows/tools-mcp`
-
- return `**This Sidekick needs an integration setup.** [Click here to check your credentials](${credentialsLink}) | [Read more about how to troubleshoot MCP servers and integrations](${mcpDocsLink}).`
- }
-
- // Return original error message if not an MCP connection error
- return errorMessage
-}
-
export const QUESTION_VAR_PREFIX = 'question'
export const FILE_ATTACHMENT_PREFIX = 'file_attachment'
export const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
@@ -743,11 +707,7 @@ export const buildFlow = async ({
}
} catch (e: any) {
logger.error(e)
-
- // Enhanced error message for MCP connection issues
- const enhancedErrorMessage = enhanceMcpInitializationError(e, reactFlowNode, baseURL || 'http://localhost:3000')
-
- throw new Error(enhancedErrorMessage)
+ throw e
}
let neighbourNodeIds = graph[nodeId]
diff --git a/packages/ui/src/api/documentstore.js b/packages/ui/src/api/documentstore.js
index 16cf05e252a..d1c89c7988a 100644
--- a/packages/ui/src/api/documentstore.js
+++ b/packages/ui/src/api/documentstore.js
@@ -1,6 +1,7 @@
import client from './client'
const getAllDocumentStores = () => client.get('/document-store/store')
+const getAdminDocumentStores = () => client.get('/admin/document-stores')
const getDocumentLoaders = () => client.get('/document-store/components/loaders')
const getSpecificDocumentStore = (id) => client.get(`/document-store/store/${id}`)
const createDocumentStore = (body) => client.post(`/document-store/store`, body)
@@ -33,6 +34,7 @@ const generateDocStoreToolDesc = (storeId, body) => client.post('/document-store
export default {
getAllDocumentStores,
+ getAdminDocumentStores,
getSpecificDocumentStore,
createDocumentStore,
deleteLoaderFromStore,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 30bdb2709ef..dda494acb49 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -253,7 +253,7 @@ importers:
version: 14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1)
next-auth:
specifier: ^4.24.11
- version: 4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
openai:
specifier: 4.96.0
version: 4.96.0(encoding@0.1.13)(ws@8.17.1(bufferutil@4.0.9)(utf-8-validate@6.0.5))(zod@3.25.76)
@@ -543,7 +543,7 @@ importers:
version: 14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1)
next-auth:
specifier: ^4.24.11
- version: 4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react:
specifier: 18.2.0
version: 18.2.0
@@ -730,7 +730,7 @@ importers:
version: 6.12.0(@aws-sdk/credential-providers@3.887.0(aws-crt@1.27.3(bufferutil@4.0.9)(utf-8-validate@6.0.5)))(socks@2.8.7)
next-auth:
specifier: ^4.24.11
- version: 4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
node-fetch:
specifier: ^3.3.2
version: 3.3.2
@@ -857,8 +857,8 @@ importers:
specifier: ^1.0.0
version: 1.0.0
'@answerai/jira-mcp':
- specifier: ^1.0.2
- version: 1.0.2
+ specifier: ^1.1.0
+ version: 1.1.0
'@answerai/salesforce-mcp':
specifier: ^0.1.0
version: 0.1.0(@types/node@24.3.1)(encoding@0.1.13)
@@ -2351,8 +2351,8 @@ packages:
engines: { node: '>=18.0.0' }
hasBin: true
- '@answerai/jira-mcp@1.0.2':
- resolution: { integrity: sha512-nLdQY6Kfcj/X0u/l+ehG+0bUIyOrPLDIJT2F7eg3ceusgOZpmdUEG02G5KjVDpC4q6SiNxFLxIGuGqupC4QNMw== }
+ '@answerai/jira-mcp@1.1.0':
+ resolution: { integrity: sha512-LXRITz1f8L+dGoZEPogn2gWIV5Lg5EVc2orxjc0kL8RUAV0ilq2MO/b8XtLT0CyE2Nkihrmkn4mu7rFmYEQHoA== }
engines: { node: '>=18.0.0' }
hasBin: true
@@ -22089,7 +22089,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@answerai/jira-mcp@1.0.2':
+ '@answerai/jira-mcp@1.1.0':
dependencies:
'@modelcontextprotocol/sdk': 1.18.0
typescript: 5.9.2
@@ -28367,7 +28367,7 @@ snapshots:
'@next-auth/prisma-adapter@1.0.7(@prisma/client@5.22.0(prisma@5.22.0))(next-auth@4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))':
dependencies:
'@prisma/client': 5.22.0(prisma@5.22.0)
- next-auth: 4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ next-auth: 4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@next/bundle-analyzer@13.5.11(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies:
@@ -35819,8 +35819,8 @@ snapshots:
'@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.5.4)
eslint: 7.32.0
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@7.32.0)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.10.1)(eslint@7.32.0)
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint@7.32.0))(eslint@7.32.0)
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint@7.32.0))(eslint@7.32.0))(eslint@7.32.0)
eslint-plugin-jsx-a11y: 6.10.2(eslint@7.32.0)
eslint-plugin-react: 7.37.5(eslint@7.32.0)
eslint-plugin-react-hooks: 4.6.2(eslint@7.32.0)
@@ -35883,7 +35883,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@7.32.0):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint@7.32.0))(eslint@7.32.0):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.1(supports-color@8.1.1)
@@ -35894,7 +35894,7 @@ snapshots:
tinyglobby: 0.2.15
unrs-resolver: 1.11.1
optionalDependencies:
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.10.1)(eslint@7.32.0)
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint@7.32.0))(eslint@7.32.0))(eslint@7.32.0)
transitivePeerDependencies:
- supports-color
@@ -35913,14 +35913,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@7.32.0):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint@7.32.0))(eslint@7.32.0))(eslint@7.32.0):
dependencies:
debug: 3.2.7(supports-color@5.5.0)
optionalDependencies:
'@typescript-eslint/parser': 5.62.0(eslint@7.32.0)(typescript@5.5.4)
eslint: 7.32.0
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@7.32.0)
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint@7.32.0))(eslint@7.32.0)
transitivePeerDependencies:
- supports-color
@@ -35953,7 +35953,7 @@ snapshots:
lodash: 4.17.21
string-natural-compare: 3.0.1
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.10.1)(eslint@7.32.0):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint@7.32.0))(eslint@7.32.0))(eslint@7.32.0):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -35964,7 +35964,7 @@ snapshots:
doctrine: 2.1.0
eslint: 7.32.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@7.32.0)
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@5.62.0(eslint@7.32.0)(typescript@5.5.4))(eslint@7.32.0))(eslint@7.32.0))(eslint@7.32.0)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -41790,7 +41790,7 @@ snapshots:
netmask@2.0.2: {}
- next-auth@4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ next-auth@4.24.11(next@14.2.32(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(@playwright/test@1.55.0)(babel-plugin-macros@3.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.92.1))(nodemailer@6.9.16)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.4
'@panva/hkdf': 1.2.1