Merge pull request #1467 from escapade-mckv/fix-integrations-saving

Fix integrations saving
This commit is contained in:
Bobbie 2025-08-27 02:52:09 +05:30 committed by GitHub
commit 14edb083e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 240 additions and 163 deletions

View File

@ -120,6 +120,7 @@ class AgentUpdateRequest(BaseModel):
icon_name: Optional[str] = None
icon_color: Optional[str] = None
icon_background: Optional[str] = None
replace_mcps: Optional[bool] = None
class AgentResponse(BaseModel):
agent_id: str
@ -145,7 +146,7 @@ class AgentResponse(BaseModel):
current_version: Optional[AgentVersionResponse] = None
metadata: Optional[Dict[str, Any]] = None
from utils.pagination import PaginationService, PaginationParams, PaginatedResponse
from utils.pagination import PaginationParams
class PaginationInfo(BaseModel):
current_page: int
@ -2105,16 +2106,32 @@ async def update_agent(
print(f"[DEBUG] update_agent: Prepared update_data with icon fields - icon_name={update_data.get('icon_name')}, icon_color={update_data.get('icon_color')}, icon_background={update_data.get('icon_background')}")
current_system_prompt = agent_data.system_prompt if agent_data.system_prompt is not None else current_version_data.get('system_prompt', '')
current_configured_mcps = agent_data.configured_mcps if agent_data.configured_mcps is not None else current_version_data.get('configured_mcps', [])
if agent_data.configured_mcps is not None:
if agent_data.replace_mcps:
current_configured_mcps = agent_data.configured_mcps
logger.debug(f"Replacing configured MCPs for agent {agent_id}: {current_configured_mcps}")
else:
current_configured_mcps = agent_data.configured_mcps
else:
current_configured_mcps = current_version_data.get('configured_mcps', [])
# Handle custom MCPs - either replace or merge based on the flag
if agent_data.custom_mcps is not None:
current_custom_mcps = merge_custom_mcps(
current_version_data.get('custom_mcps', []),
agent_data.custom_mcps
)
if agent_data.replace_mcps:
# Replace mode: use the provided list as-is
current_custom_mcps = agent_data.custom_mcps
logger.debug(f"Replacing custom MCPs for agent {agent_id}: {current_custom_mcps}")
else:
# Merge mode: merge with existing MCPs (default behavior)
current_custom_mcps = merge_custom_mcps(
current_version_data.get('custom_mcps', []),
agent_data.custom_mcps
)
logger.debug(f"Merging custom MCPs for agent {agent_id}")
else:
current_custom_mcps = current_version_data.get('custom_mcps', [])
current_agentpress_tools = agent_data.agentpress_tools if agent_data.agentpress_tools is not None else current_version_data.get('agentpress_tools', {})
current_avatar = agent_data.avatar if agent_data.avatar is not None else existing_data.get('avatar')
current_avatar_color = agent_data.avatar_color if agent_data.avatar_color is not None else existing_data.get('avatar_color')

View File

@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer';
import { useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
import { useUpdateAgentMCPs } from '@/hooks/react-query/agents/use-update-agent-mcps';
import { useCreateAgentVersion, useActivateAgentVersion } from '@/hooks/react-query/agents/use-agent-versions';
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
@ -23,7 +24,6 @@ import { useAgentConfigTour } from '@/hooks/use-agent-config-tour';
import Joyride, { CallBackProps, STATUS, Step } from 'react-joyride';
import { TourConfirmationDialog } from '@/components/tour/TourConfirmationDialog';
// Tour steps for agent configuration
const agentConfigTourSteps: Step[] = [
{
target: '[data-tour="agent-header"]',
@ -90,7 +90,6 @@ const agentConfigTourSteps: Step[] = [
},
];
// Form data interface
interface FormData {
name: string;
description: string;
@ -119,10 +118,31 @@ function AgentConfigurationContent() {
const { setHasUnsavedChanges } = useAgentVersionStore();
const updateAgentMutation = useUpdateAgent();
const updateAgentMCPsMutation = useUpdateAgentMCPs();
const createVersionMutation = useCreateAgentVersion();
const activateVersionMutation = useActivateAgentVersion();
const exportMutation = useExportAgent();
// Use refs for stable references to avoid callback recreation
const agentIdRef = useRef(agentId);
const mutationsRef = useRef({
updateAgent: updateAgentMutation,
updateMCPs: updateAgentMCPsMutation,
export: exportMutation,
activate: activateVersionMutation,
});
// Update refs when values change
useEffect(() => {
agentIdRef.current = agentId;
mutationsRef.current = {
updateAgent: updateAgentMutation,
updateMCPs: updateAgentMCPsMutation,
export: exportMutation,
activate: activateVersionMutation,
};
}, [agentId, updateAgentMutation, updateAgentMCPsMutation, exportMutation, activateVersionMutation]);
const [formData, setFormData] = useState<FormData>({
name: '',
description: '',
@ -140,9 +160,19 @@ function AgentConfigurationContent() {
const [originalData, setOriginalData] = useState<FormData>(formData);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const [lastLoadedVersionId, setLastLoadedVersionId] = useState<string | null>(null);
useEffect(() => {
if (!agent) return;
const currentVersionId = versionData?.version_id || agent.current_version_id || 'current';
const shouldResetForm = !lastLoadedVersionId || lastLoadedVersionId !== currentVersionId;
if (!shouldResetForm) {
setLastLoadedVersionId(currentVersionId);
return;
}
let configSource = agent;
if (versionData) {
configSource = {
@ -174,7 +204,8 @@ function AgentConfigurationContent() {
};
setFormData(newFormData);
setOriginalData(newFormData);
}, [agent, versionData]);
setLastLoadedVersionId(currentVersionId);
}, [agent, versionData, lastLoadedVersionId]);
const displayData = isViewingOldVersion && versionData ? {
name: formData.name,
@ -191,24 +222,127 @@ function AgentConfigurationContent() {
icon_background: versionData.icon_background || formData.icon_background || '#e5e5e5',
} : formData;
const handleFieldChange = useCallback((field: string, value: any) => {
const handleFieldChange = (field: string, value: any) => {
setFormData(prev => ({
...prev,
[field]: value
}));
}, []);
};
const handleMCPChange = useCallback((updates: { configured_mcps: any[]; custom_mcps: any[] }) => {
const previousConfigured = formData.configured_mcps;
const previousCustom = formData.custom_mcps;
setFormData(prev => ({
...prev,
configured_mcps: updates.configured_mcps,
custom_mcps: updates.custom_mcps
configured_mcps: updates.configured_mcps || [],
custom_mcps: updates.custom_mcps || []
}));
mutationsRef.current.updateMCPs.mutate({
agentId: agentIdRef.current,
configured_mcps: updates.configured_mcps || [],
custom_mcps: updates.custom_mcps || [],
replace_mcps: true
}, {
onSuccess: () => {
setOriginalData(prev => ({
...prev,
configured_mcps: updates.configured_mcps || [],
custom_mcps: updates.custom_mcps || []
}));
toast.success('MCP configuration updated');
},
onError: (error) => {
setFormData(prev => ({
...prev,
configured_mcps: previousConfigured,
custom_mcps: previousCustom
}));
toast.error('Failed to update MCP configuration');
console.error('MCP update error:', error);
}
});
}, []);
const handleExport = useCallback(() => {
exportMutation.mutate(agentId);
}, [agentId, exportMutation]);
const saveField = useCallback(async (fieldData: Partial<FormData>) => {
try {
await mutationsRef.current.updateAgent.mutateAsync({
agentId: agentIdRef.current,
...fieldData,
});
setFormData(prev => ({ ...prev, ...fieldData }));
setOriginalData(prev => ({ ...prev, ...fieldData }));
return true;
} catch (error) {
console.error('Failed to save field:', error);
throw error;
}
}, []);
const handleNameSave = async (name: string) => {
try {
await saveField({ name });
toast.success('Agent name updated');
} catch {
toast.error('Failed to update agent name');
throw new Error('Failed to update agent name');
}
};
const handleProfileImageSave = async (profileImageUrl: string | null) => {
try {
await saveField({ profile_image_url: profileImageUrl || '' });
} catch {
toast.error('Failed to update profile picture');
throw new Error('Failed to update profile picture');
}
};
const handleIconSave = async (iconName: string | null, iconColor: string, iconBackground: string) => {
try {
await saveField({ icon_name: iconName, icon_color: iconColor, icon_background: iconBackground });
toast.success('Agent icon updated');
} catch {
toast.error('Failed to update agent icon');
throw new Error('Failed to update agent icon');
}
};
const handleSystemPromptSave = async (system_prompt: string) => {
try {
await saveField({ system_prompt });
toast.success('System prompt updated');
} catch {
toast.error('Failed to update system prompt');
throw new Error('Failed to update system prompt');
}
};
const handleModelSave = async (model: string) => {
try {
await saveField({ model });
toast.success('Model updated');
} catch {
toast.error('Failed to update model');
throw new Error('Failed to update model');
}
};
const handleToolsSave = async (agentpress_tools: Record<string, boolean | { enabled: boolean; description: string }>) => {
try {
await saveField({ agentpress_tools });
toast.success('Tools updated');
} catch {
toast.error('Failed to update tools');
throw new Error('Failed to update tools');
}
};
const handleExport = () => {
mutationsRef.current.export.mutate(agentIdRef.current);
};
const { hasUnsavedChanges, isCurrentVersion } = React.useMemo(() => {
const formDataStr = JSON.stringify(formData);
@ -234,141 +368,33 @@ function AgentConfigurationContent() {
const handleActivateVersion = async (versionId: string) => {
try {
await activateVersionMutation.mutateAsync({ agentId, versionId });
router.push(`/agents/config/${agentId}`);
await mutationsRef.current.activate.mutateAsync({ agentId: agentIdRef.current, versionId });
router.push(`/agents/config/${agentIdRef.current}`);
} catch (error) {
console.error('Failed to activate version:', error);
}
};
// OPTIMIZED: Simplified save with stable reference
const handleSave = useCallback(async () => {
if (hasUnsavedChanges) {
const currentFormData = formData;
const hasChanges = JSON.stringify(currentFormData) !== JSON.stringify(originalData);
if (hasChanges) {
try {
await updateAgentMutation.mutateAsync({
agentId,
name: formData.name,
description: formData.description,
is_default: formData.is_default,
profile_image_url: formData.profile_image_url,
icon_name: formData.icon_name,
icon_color: formData.icon_color,
icon_background: formData.icon_background,
system_prompt: formData.system_prompt,
agentpress_tools: formData.agentpress_tools,
configured_mcps: formData.configured_mcps,
custom_mcps: formData.custom_mcps,
await mutationsRef.current.updateAgent.mutateAsync({
agentId: agentIdRef.current,
...currentFormData,
});
setOriginalData(formData);
setOriginalData(currentFormData);
toast.success('Agent updated successfully');
} catch (error) {
toast.error('Failed to update agent');
console.error('Failed to save agent:', error);
}
}
}, [agentId, formData, hasUnsavedChanges, updateAgentMutation]);
const handleNameSave = useCallback(async (name: string) => {
try {
await updateAgentMutation.mutateAsync({
agentId,
name,
});
setFormData(prev => ({ ...prev, name }));
setOriginalData(prev => ({ ...prev, name }));
toast.success('Agent name updated');
} catch (error) {
toast.error('Failed to update agent name');
throw error;
}
}, [agentId, updateAgentMutation]);
const handleProfileImageSave = useCallback(async (profileImageUrl: string | null) => {
try {
await updateAgentMutation.mutateAsync({
agentId,
profile_image_url: profileImageUrl || '',
});
setFormData(prev => ({ ...prev, profile_image_url: profileImageUrl || '' }));
setOriginalData(prev => ({ ...prev, profile_image_url: profileImageUrl || '' }));
} catch (error) {
toast.error('Failed to update profile picture');
throw error;
}
}, [agentId, updateAgentMutation]);
const handleIconSave = useCallback(async (iconName: string | null, iconColor: string, iconBackground: string) => {
try {
await updateAgentMutation.mutateAsync({
agentId,
icon_name: iconName,
icon_color: iconColor,
icon_background: iconBackground,
});
setFormData(prev => ({
...prev,
icon_name: iconName,
icon_color: iconColor,
icon_background: iconBackground,
}));
setOriginalData(prev => ({
...prev,
icon_name: iconName,
icon_color: iconColor,
icon_background: iconBackground,
}));
toast.success('Agent icon updated');
} catch (error) {
toast.error('Failed to update agent icon');
throw error;
}
}, [agentId, updateAgentMutation]);
const handleSystemPromptSave = useCallback(async (value: string) => {
try {
await updateAgentMutation.mutateAsync({
agentId,
system_prompt: value,
});
setFormData(prev => ({ ...prev, system_prompt: value }));
setOriginalData(prev => ({ ...prev, system_prompt: value }));
toast.success('System prompt updated');
} catch (error) {
toast.error('Failed to update system prompt');
throw error;
}
}, [agentId, updateAgentMutation]);
const handleModelSave = useCallback(async (model: string) => {
try {
setFormData(prev => ({ ...prev, model }));
setOriginalData(prev => ({ ...prev, model }));
toast.success('Model updated');
} catch (error) {
toast.error('Failed to update model');
throw error;
}
}, []);
const handleToolsSave = useCallback(async (tools: Record<string, boolean | { enabled: boolean; description: string }>) => {
try {
await updateAgentMutation.mutateAsync({
agentId,
agentpress_tools: tools,
});
setFormData(prev => ({ ...prev, agentpress_tools: tools }));
setOriginalData(prev => ({ ...prev, agentpress_tools: tools }));
toast.success('Tools updated');
} catch (error) {
toast.error('Failed to update tools');
throw error;
}
}, [agentId, updateAgentMutation]);
}, []); // Using snapshot of formData in function instead of dependency
if (error) {
return (
@ -572,7 +598,8 @@ export default function AgentConfigurationPage() {
handleWelcomeDecline,
} = useAgentConfigTour();
const handleTourCallback = useCallback((data: CallBackProps) => {
// OPTIMIZED: Simple function instead of useCallback with stable dependencies
const handleTourCallback = (data: CallBackProps) => {
const { status, type, index } = data;
if (status === STATUS.FINISHED || status === STATUS.SKIPPED) {
@ -580,7 +607,7 @@ export default function AgentConfigurationPage() {
} else if (type === 'step:after') {
setStepIndex(index + 1);
}
}, [stopTour, setStepIndex]);
};
return (
<>

View File

@ -45,18 +45,23 @@ export const AgentMCPConfiguration: React.FC<AgentMCPConfigurationProps> = ({
};
}
// Map 'sse' backend type to 'http' for frontend display
const displayType = customMcp.type === 'sse' ? 'http' : (customMcp.type || customMcp.customType);
return {
name: customMcp.name,
qualifiedName: customMcp.qualifiedName || `custom_${customMcp.type || customMcp.customType}_${customMcp.name.replace(' ', '_').toLowerCase()}`,
qualifiedName: customMcp.qualifiedName || `custom_${displayType}_${customMcp.name.replace(' ', '_').toLowerCase()}`,
config: customMcp.config,
enabledTools: customMcp.enabledTools,
isCustom: true,
customType: customMcp.type || customMcp.customType
customType: displayType
};
})
];
const handleConfigurationChange = (mcps: any[]) => {
console.log('[AgentMCPConfiguration] Configuration changed:', mcps);
const configured = mcps.filter(mcp => !mcp.isCustom);
const custom = mcps
.filter(mcp => mcp.isCustom)
@ -71,15 +76,20 @@ export const AgentMCPConfiguration: React.FC<AgentMCPConfigurationProps> = ({
};
}
// Map 'http' to 'sse' for backend compatibility
const backendType = mcp.customType === 'http' ? 'sse' : mcp.customType;
return {
name: mcp.name,
type: mcp.customType,
type: backendType,
customType: mcp.customType,
config: mcp.config,
enabledTools: mcp.enabledTools
};
});
console.log('[AgentMCPConfiguration] Sending to parent - configured:', configured, 'custom:', custom);
onMCPChange({
configured_mcps: configured,
custom_mcps: custom

View File

@ -7,14 +7,14 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Search, Zap, X, Settings, ChevronDown, ChevronUp, Loader2, Server } from 'lucide-react';
import { useComposioCategories, useComposioToolkitsInfinite } from '@/hooks/react-query/composio/use-composio';
import { useComposioProfiles } from '@/hooks/react-query/composio/use-composio-profiles';
import { useAgent, useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
import { useAgent } from '@/hooks/react-query/agents/use-agents';
import { useUpdateAgentMCPs } from '@/hooks/react-query/agents/use-update-agent-mcps';
import { ComposioConnector } from './composio-connector';
import { ComposioToolsManager } from './composio-tools-manager';
import type { ComposioToolkit, ComposioProfile } from '@/hooks/react-query/composio/utils';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import { useQueryClient } from '@tanstack/react-query';
// import { AgentSelector } from '../../thread/chat-input/agent-selector';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { CustomMCPDialog } from '../mcp/custom-mcp-dialog';
@ -168,6 +168,7 @@ const ConnectedAppCard = ({
size="sm"
onClick={() => onManageTools(connectedApp)}
disabled={isUpdating}
type="button"
>
<Settings className="h-4 w-4" />
</Button>
@ -311,7 +312,7 @@ export const ComposioRegistry: React.FC<ComposioRegistryProps> = ({
const currentAgentId = selectedAgentId ?? internalSelectedAgentId;
const { data: agent, isLoading: isLoadingAgent } = useAgent(currentAgentId || '');
const { mutate: updateAgent, isPending: isUpdatingAgent } = useUpdateAgent();
const { mutate: updateAgent, isPending: isUpdatingAgent } = useUpdateAgentMCPs(); // Use the MCP-specific hook
const handleAgentSelect = (agentId: string | undefined) => {
if (onAgentChange) {
@ -429,7 +430,8 @@ export const ComposioRegistry: React.FC<ComposioRegistryProps> = ({
return new Promise((resolve, reject) => {
updateAgent({
agentId: currentAgentId,
custom_mcps: updatedCustomMcps
custom_mcps: updatedCustomMcps,
replace_mcps: true // Use replace mode to ensure proper updates
}, {
onSuccess: () => {
toast.success(`Custom MCP "${customConfig.name}" added successfully`);

View File

@ -127,6 +127,7 @@ const MCPConfigurationItem: React.FC<{
className="h-8 w-8 p-0"
onClick={() => onConfigureTools(index)}
title="Configure tools"
type="button"
>
<Settings className="h-4 w-4" />
</Button>
@ -137,6 +138,7 @@ const MCPConfigurationItem: React.FC<{
className="h-8 w-8 p-0 text-destructive hover:text-destructive"
onClick={() => onRemove(index)}
title="Remove integration"
type="button"
>
<Trash2 className="h-4 w-4" />
</Button>
@ -196,7 +198,7 @@ export const ConfiguredMcpList: React.FC<ConfiguredMcpListProps> = ({
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={confirmDelete}
className="bg-destructive hover:bg-destructive/90"
className="bg-destructive hover:bg-destructive/90 text-white"
>
Remove Integration
</AlertDialogAction>

View File

@ -407,15 +407,16 @@ export const CustomMCPDialog: React.FC<CustomMCPDialogProps> = ({
<DialogFooter className="flex-shrink-0 pt-4">
{step === 'tools' ? (
<>
<Button variant="outline" onClick={handleBack} disabled={isSaving}>
<Button variant="outline" onClick={handleBack} disabled={isSaving} type="button">
Back
</Button>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSaving}>
<Button variant="outline" onClick={() => onOpenChange(false)} disabled={isSaving} type="button">
Cancel
</Button>
<Button
onClick={handleToolsNext}
disabled={selectedTools.size === 0 || isSaving}
type="button"
>
{isSaving ? (
<>
@ -429,12 +430,13 @@ export const CustomMCPDialog: React.FC<CustomMCPDialogProps> = ({
</>
) : (
<>
<Button variant="outline" onClick={() => onOpenChange(false)}>
<Button variant="outline" onClick={() => onOpenChange(false)} type="button">
Cancel
</Button>
<Button
onClick={validateAndDiscoverTools}
disabled={!configText.trim() || !manualServerName.trim() || isValidating}
type="button"
>
{isValidating ? (
<>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Zap, Server, Store } from 'lucide-react'
import { Zap, Server, Store, Settings } from 'lucide-react'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { MCPConfigurationProps, MCPConfiguration as MCPConfigurationType } from './types';
import { ConfiguredMcpList } from './configured-mcp-list';
@ -29,10 +29,6 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
const [selectedAgentId, setSelectedAgentId] = useState<string | undefined>(agentId);
const queryClient = useQueryClient();
useEffect(() => {
setSelectedAgentId(agentId);
}, [agentId]);
const handleAgentChange = (newAgentId: string | undefined) => {
setSelectedAgentId(newAgentId);
};
@ -64,8 +60,7 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
};
const handleRemoveMCP = (index: number) => {
const newMCPs = [...configuredMCPs];
newMCPs.splice(index, 1);
const newMCPs = configuredMCPs.filter((_, i) => i !== index);
onConfigurationChange(newMCPs);
};
@ -84,8 +79,9 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
const handleToolsSelected = (profileId: string, selectedTools: string[], appName: string, appSlug: string) => {
setShowRegistryDialog(false);
queryClient.invalidateQueries({ queryKey: ['agents'] });
queryClient.invalidateQueries({ queryKey: ['agent', selectedAgentId] });
if (selectedAgentId) {
queryClient.invalidateQueries({ queryKey: ['agents', 'detail', selectedAgentId] });
}
queryClient.invalidateQueries({ queryKey: ['composio', 'profiles'] });
toast.success(`Connected ${appName} integration!`);
};
@ -107,11 +103,11 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex gap-2">
<Button onClick={() => setShowRegistryDialog(true)} size="sm" variant="default" className="gap-2">
<Button onClick={() => setShowRegistryDialog(true)} size="sm" variant="default" className="gap-2" type="button">
<Store className="h-4 w-4" />
Browse Apps
</Button>
<Button onClick={() => setShowCustomDialog(true)} size="sm" variant="outline" className="gap-2">
<Button onClick={() => setShowCustomDialog(true)} size="sm" variant="outline" className="gap-2" type="button">
<Server className="h-4 w-4" />
Custom MCP
</Button>
@ -155,8 +151,9 @@ export const MCPConfigurationNew: React.FC<MCPConfigurationProps> = ({
onToolsSelected={handleToolsSelected}
onClose={() => {
setShowRegistryDialog(false);
queryClient.invalidateQueries({ queryKey: ['agents'] });
queryClient.invalidateQueries({ queryKey: ['agent', selectedAgentId] });
if (selectedAgentId) {
queryClient.invalidateQueries({ queryKey: ['agents', 'detail', selectedAgentId] });
}
}}
/>
</DialogContent>

View File

@ -0,0 +1,18 @@
import { createMutationHook } from '@/hooks/use-query';
import { useQueryClient } from '@tanstack/react-query';
import { AgentUpdateRequest, updateAgent } from './utils';
import { agentKeys } from './keys';
export const useUpdateAgentMCPs = () => {
const queryClient = useQueryClient();
return createMutationHook(
({ agentId, ...data }: { agentId: string } & AgentUpdateRequest) =>
updateAgent(agentId, data),
{
onSuccess: (data, variables) => {
queryClient.setQueryData(agentKeys.detail(variables.agentId), data);
},
}
)();
};

View File

@ -163,6 +163,8 @@ export type AgentUpdateRequest = {
icon_name?: string | null;
icon_color?: string | null;
icon_background?: string | null;
// MCP replacement flag
replace_mcps?: boolean;
};
export const getAgents = async (params: AgentsParams = {}): Promise<AgentsResponse> => {