1616
1717import asyncio
1818from contextlib import asynccontextmanager
19+ import importlib
1920import logging
2021import os
2122import time
7576from ..evaluation .eval_sets_manager import EvalSetsManager
7677from ..events .event import Event
7778from ..memory .base_memory_service import BaseMemoryService
79+ from ..plugins .base_plugin import BasePlugin
7880from ..runners import Runner
7981from ..sessions .base_session_service import BaseSessionService
8082from ..sessions .session import Session
@@ -354,6 +356,7 @@ def __init__(
354356 eval_sets_manager : EvalSetsManager ,
355357 eval_set_results_manager : EvalSetResultsManager ,
356358 agents_dir : str ,
359+ extra_plugins : Optional [list [str ]] = None ,
357360 ):
358361 self .agent_loader = agent_loader
359362 self .session_service = session_service
@@ -363,39 +366,94 @@ def __init__(
363366 self .eval_sets_manager = eval_sets_manager
364367 self .eval_set_results_manager = eval_set_results_manager
365368 self .agents_dir = agents_dir
369+ self .extra_plugins = extra_plugins or []
366370 # Internal propeties we want to allow being modified from callbacks.
367371 self .runners_to_clean : set [str ] = set ()
368372 self .current_app_name_ref : SharedValue [str ] = SharedValue (value = "" )
369373 self .runner_dict = {}
370374
371375 async def get_runner_async (self , app_name : str ) -> Runner :
372- """Returns the runner for the given app."""
376+ """Returns the cached runner for the given app."""
377+ # Handle cleanup
373378 if app_name in self .runners_to_clean :
374379 self .runners_to_clean .remove (app_name )
375380 runner = self .runner_dict .pop (app_name , None )
376381 await cleanup .close_runners (list ([runner ]))
377382
378- envs . load_dotenv_for_agent ( os . path . basename ( app_name ), self . agents_dir )
383+ # Return cached runner if exists
379384 if app_name in self .runner_dict :
380385 return self .runner_dict [app_name ]
386+
387+ # Create new runner
388+ envs .load_dotenv_for_agent (os .path .basename (app_name ), self .agents_dir )
381389 agent_or_app = self .agent_loader .load_agent (app_name )
382- agentic_app = None
390+
391+ # Instantiate extra plugins if configured
392+ extra_plugins_instances = self ._instantiate_extra_plugins ()
393+
383394 if isinstance (agent_or_app , BaseAgent ):
384395 agentic_app = App (
385396 name = app_name ,
386397 root_agent = agent_or_app ,
398+ plugins = extra_plugins_instances ,
387399 )
388400 else :
389- agentic_app = agent_or_app
390- runner = Runner (
401+ # Combine existing plugins with extra plugins
402+ all_plugins = (agent_or_app .plugins or []) + extra_plugins_instances
403+ agentic_app = App (
404+ name = agent_or_app .name ,
405+ root_agent = agent_or_app .root_agent ,
406+ plugins = all_plugins ,
407+ )
408+
409+ runner = self ._create_runner (agentic_app )
410+ self .runner_dict [app_name ] = runner
411+ return runner
412+
413+ def _create_runner (self , agentic_app : App ) -> Runner :
414+ """Create a runner with common services."""
415+ return Runner (
391416 app = agentic_app ,
392417 artifact_service = self .artifact_service ,
393418 session_service = self .session_service ,
394419 memory_service = self .memory_service ,
395420 credential_service = self .credential_service ,
396421 )
397- self .runner_dict [app_name ] = runner
398- return runner
422+
423+ def _instantiate_extra_plugins (self ) -> list [BasePlugin ]:
424+ """Instantiate extra plugins from the configured list.
425+
426+ Returns:
427+ List of instantiated BasePlugin objects.
428+ """
429+ extra_plugins_instances = []
430+ for qualified_name in self .extra_plugins :
431+ try :
432+ plugin_obj = self ._import_plugin_object (qualified_name )
433+ if isinstance (plugin_obj , BasePlugin ):
434+ extra_plugins_instances .append (plugin_obj )
435+ elif issubclass (plugin_obj , BasePlugin ):
436+ extra_plugins_instances .append (plugin_obj (name = qualified_name ))
437+ except Exception as e :
438+ logger .error ("Failed to load plugin %s: %s" , qualified_name , e )
439+ return extra_plugins_instances
440+
441+ def _import_plugin_object (self , qualified_name : str ) -> Any :
442+ """Import a plugin object (class or instance) from a fully qualified name.
443+
444+ Args:
445+ qualified_name: Fully qualified name (e.g., 'my_package.my_plugin.MyPlugin')
446+
447+ Returns:
448+ The imported object, which can be either a class or an instance.
449+
450+ Raises:
451+ ImportError: If the module cannot be imported.
452+ AttributeError: If the object doesn't exist in the module.
453+ """
454+ module_name , obj_name = qualified_name .rsplit ("." , 1 )
455+ module = importlib .import_module (module_name )
456+ return getattr (module , obj_name )
399457
400458 def get_fast_api_app (
401459 self ,
0 commit comments