xpander-sdk 1.60.4__py3-none-any.whl → 2.0.155__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. xpander_sdk/__init__.py +76 -7793
  2. xpander_sdk/consts/__init__.py +0 -0
  3. xpander_sdk/consts/api_routes.py +63 -0
  4. xpander_sdk/core/__init__.py +0 -0
  5. xpander_sdk/core/module_base.py +164 -0
  6. xpander_sdk/core/state.py +10 -0
  7. xpander_sdk/core/xpander_api_client.py +119 -0
  8. xpander_sdk/exceptions/__init__.py +0 -0
  9. xpander_sdk/exceptions/module_exception.py +45 -0
  10. xpander_sdk/models/__init__.py +0 -0
  11. xpander_sdk/models/activity.py +65 -0
  12. xpander_sdk/models/configuration.py +92 -0
  13. xpander_sdk/models/events.py +70 -0
  14. xpander_sdk/models/frameworks.py +64 -0
  15. xpander_sdk/models/shared.py +102 -0
  16. xpander_sdk/models/user.py +21 -0
  17. xpander_sdk/modules/__init__.py +0 -0
  18. xpander_sdk/modules/agents/__init__.py +0 -0
  19. xpander_sdk/modules/agents/agents_module.py +164 -0
  20. xpander_sdk/modules/agents/models/__init__.py +0 -0
  21. xpander_sdk/modules/agents/models/agent.py +477 -0
  22. xpander_sdk/modules/agents/models/agent_list.py +107 -0
  23. xpander_sdk/modules/agents/models/knowledge_bases.py +33 -0
  24. xpander_sdk/modules/agents/sub_modules/__init__.py +0 -0
  25. xpander_sdk/modules/agents/sub_modules/agent.py +953 -0
  26. xpander_sdk/modules/agents/utils/__init__.py +0 -0
  27. xpander_sdk/modules/agents/utils/generic.py +2 -0
  28. xpander_sdk/modules/backend/__init__.py +0 -0
  29. xpander_sdk/modules/backend/backend_module.py +425 -0
  30. xpander_sdk/modules/backend/frameworks/__init__.py +0 -0
  31. xpander_sdk/modules/backend/frameworks/agno.py +627 -0
  32. xpander_sdk/modules/backend/frameworks/dispatch.py +36 -0
  33. xpander_sdk/modules/backend/utils/__init__.py +0 -0
  34. xpander_sdk/modules/backend/utils/mcp_oauth.py +95 -0
  35. xpander_sdk/modules/events/__init__.py +0 -0
  36. xpander_sdk/modules/events/decorators/__init__.py +0 -0
  37. xpander_sdk/modules/events/decorators/on_boot.py +94 -0
  38. xpander_sdk/modules/events/decorators/on_shutdown.py +94 -0
  39. xpander_sdk/modules/events/decorators/on_task.py +203 -0
  40. xpander_sdk/modules/events/events_module.py +629 -0
  41. xpander_sdk/modules/events/models/__init__.py +0 -0
  42. xpander_sdk/modules/events/models/deployments.py +25 -0
  43. xpander_sdk/modules/events/models/events.py +57 -0
  44. xpander_sdk/modules/events/utils/__init__.py +0 -0
  45. xpander_sdk/modules/events/utils/generic.py +56 -0
  46. xpander_sdk/modules/events/utils/git_init.py +32 -0
  47. xpander_sdk/modules/knowledge_bases/__init__.py +0 -0
  48. xpander_sdk/modules/knowledge_bases/knowledge_bases_module.py +217 -0
  49. xpander_sdk/modules/knowledge_bases/models/__init__.py +0 -0
  50. xpander_sdk/modules/knowledge_bases/models/knowledge_bases.py +11 -0
  51. xpander_sdk/modules/knowledge_bases/sub_modules/__init__.py +0 -0
  52. xpander_sdk/modules/knowledge_bases/sub_modules/knowledge_base.py +107 -0
  53. xpander_sdk/modules/knowledge_bases/sub_modules/knowledge_base_document_item.py +40 -0
  54. xpander_sdk/modules/knowledge_bases/utils/__init__.py +0 -0
  55. xpander_sdk/modules/tasks/__init__.py +0 -0
  56. xpander_sdk/modules/tasks/models/__init__.py +0 -0
  57. xpander_sdk/modules/tasks/models/task.py +153 -0
  58. xpander_sdk/modules/tasks/models/tasks_list.py +107 -0
  59. xpander_sdk/modules/tasks/sub_modules/__init__.py +0 -0
  60. xpander_sdk/modules/tasks/sub_modules/task.py +887 -0
  61. xpander_sdk/modules/tasks/tasks_module.py +492 -0
  62. xpander_sdk/modules/tasks/utils/__init__.py +0 -0
  63. xpander_sdk/modules/tasks/utils/files.py +114 -0
  64. xpander_sdk/modules/tools_repository/__init__.py +0 -0
  65. xpander_sdk/modules/tools_repository/decorators/__init__.py +0 -0
  66. xpander_sdk/modules/tools_repository/decorators/register_tool.py +108 -0
  67. xpander_sdk/modules/tools_repository/models/__init__.py +0 -0
  68. xpander_sdk/modules/tools_repository/models/mcp.py +68 -0
  69. xpander_sdk/modules/tools_repository/models/tool_invocation_result.py +14 -0
  70. xpander_sdk/modules/tools_repository/sub_modules/__init__.py +0 -0
  71. xpander_sdk/modules/tools_repository/sub_modules/tool.py +578 -0
  72. xpander_sdk/modules/tools_repository/tools_repository_module.py +259 -0
  73. xpander_sdk/modules/tools_repository/utils/__init__.py +0 -0
  74. xpander_sdk/modules/tools_repository/utils/generic.py +57 -0
  75. xpander_sdk/modules/tools_repository/utils/local_tools.py +52 -0
  76. xpander_sdk/modules/tools_repository/utils/schemas.py +308 -0
  77. xpander_sdk/utils/__init__.py +0 -0
  78. xpander_sdk/utils/env.py +44 -0
  79. xpander_sdk/utils/event_loop.py +67 -0
  80. xpander_sdk/utils/tools.py +32 -0
  81. xpander_sdk-2.0.155.dist-info/METADATA +538 -0
  82. xpander_sdk-2.0.155.dist-info/RECORD +85 -0
  83. {xpander_sdk-1.60.4.dist-info → xpander_sdk-2.0.155.dist-info}/WHEEL +1 -1
  84. {xpander_sdk-1.60.4.dist-info → xpander_sdk-2.0.155.dist-info/licenses}/LICENSE +0 -1
  85. xpander_sdk/_jsii/__init__.py +0 -39
  86. xpander_sdk/_jsii/xpander-sdk@1.60.4.jsii.tgz +0 -0
  87. xpander_sdk/py.typed +0 -1
  88. xpander_sdk-1.60.4.dist-info/METADATA +0 -368
  89. xpander_sdk-1.60.4.dist-info/RECORD +0 -9
  90. {xpander_sdk-1.60.4.dist-info → xpander_sdk-2.0.155.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,95 @@
1
+ import asyncio
2
+ from datetime import datetime, timezone
3
+ from loguru import logger
4
+ from xpander_sdk.consts.api_routes import APIRoute
5
+ from xpander_sdk.core.xpander_api_client import APIClient
6
+ from xpander_sdk.models.events import TaskUpdateEventType
7
+ from xpander_sdk.modules.tasks.sub_modules.task import Task, TaskUpdateEvent
8
+ from xpander_sdk.modules.tools_repository.models.mcp import MCPOAuthGetTokenGenericResponse, MCPOAuthGetTokenResponse, MCPOAuthResponseType, MCPServerDetails
9
+
10
+ POLLING_INTERVAL = 1 # every 1s
11
+ MAX_WAIT_FOR_LOGIN = 600 # 10 mintutes
12
+
13
+ async def push_event(task: Task, event: TaskUpdateEvent):
14
+ client = APIClient(configuration=task.configuration)
15
+ await client.make_request(
16
+ path=APIRoute.PushExecutionEventToQueue.format(task_id=task.id),
17
+ method="POST",
18
+ payload=[
19
+ TaskUpdateEvent(
20
+ task_id=task.id,
21
+ organization_id=task.organization_id,
22
+ time=datetime.now(timezone.utc).isoformat(),
23
+ type=TaskUpdateEventType.AuthEvent,
24
+ data=event
25
+ ).model_dump_safe()
26
+ ]
27
+ )
28
+
29
+ async def get_token(mcp_server: MCPServerDetails, task: Task, user_id: str) -> MCPOAuthGetTokenResponse:
30
+ client = APIClient(configuration=task.configuration)
31
+ result: MCPOAuthGetTokenResponse = await client.make_request(
32
+ path=APIRoute.GetUserMCPAuthToken.format(agent_id=task.agent_id, user_id=user_id),
33
+ method="POST",
34
+ payload=mcp_server.model_dump(),
35
+ model=MCPOAuthGetTokenResponse
36
+ )
37
+ if result.type == MCPOAuthResponseType.TOKEN_READY:
38
+ return result
39
+
40
+ return None
41
+
42
+ async def authenticate_mcp_server(mcp_server: MCPServerDetails, task: Task, user_id: str) -> MCPOAuthGetTokenResponse:
43
+ try:
44
+ logger.info(f"Authenticating MCP Server {mcp_server.url}")
45
+
46
+ user_identifier = user_id if mcp_server.share_user_token_across_other_agents else f"{task.agent_id}_{user_id}"
47
+
48
+ client = APIClient(configuration=task.configuration)
49
+ result: MCPOAuthGetTokenResponse = await client.make_request(
50
+ path=APIRoute.GetUserMCPAuthToken.format(agent_id=task.agent_id, user_id=user_identifier),
51
+ method="POST",
52
+ payload=mcp_server.model_dump(),
53
+ model=MCPOAuthGetTokenResponse
54
+ )
55
+
56
+ if not result:
57
+ raise Exception("Invalid response")
58
+
59
+ if result.type == MCPOAuthResponseType.LOGIN_REQUIRED:
60
+ logger.info(f"Initiating login for MCP Server {mcp_server.url}")
61
+ # Notify user about login requirement
62
+ await push_event(task=task, event=result)
63
+
64
+ # Poll for token with timeout
65
+ elapsed_time = 0
66
+ while elapsed_time < MAX_WAIT_FOR_LOGIN:
67
+ await asyncio.sleep(POLLING_INTERVAL)
68
+ elapsed_time += POLLING_INTERVAL
69
+
70
+ # Check for token
71
+ token_result = await get_token(mcp_server=mcp_server, task=task, user_id=user_identifier)
72
+ if token_result and token_result.type == MCPOAuthResponseType.TOKEN_READY:
73
+ logger.info(f"Successful login for MCP Server {mcp_server.url}")
74
+ redacted_token_result = MCPOAuthGetTokenResponse(**token_result.model_dump_safe())
75
+ redacted_token_result.data.access_token = "REDACTED"
76
+ await push_event(task=task, event=redacted_token_result)
77
+ return token_result
78
+
79
+ # Timeout reached
80
+ raise TimeoutError(f"Authentication timeout for MCP Server {mcp_server.url} after {MAX_WAIT_FOR_LOGIN} seconds")
81
+
82
+ if result.type == MCPOAuthResponseType.TOKEN_READY:
83
+ logger.info(f"Token ready for MCP Server {mcp_server.url}")
84
+ redacted_token_result = MCPOAuthGetTokenResponse(**result.model_dump_safe())
85
+ redacted_token_result.data.access_token = "REDACTED"
86
+ await push_event(task=task, event=redacted_token_result)
87
+
88
+ return result
89
+ except Exception as e:
90
+ error = f"Failed to process MCP Auth - {str(e)}"
91
+ logger.error(error)
92
+ return MCPOAuthGetTokenResponse(
93
+ type=MCPOAuthResponseType.TOKEN_ISSUE,
94
+ data=MCPOAuthGetTokenGenericResponse(message=error)
95
+ )
File without changes
File without changes
@@ -0,0 +1,94 @@
1
+ """
2
+ xpander_sdk.decorators.on_boot
3
+
4
+ This module provides the `@on_boot` decorator, which allows developers to define
5
+ boot functions that execute before event listeners are set up in xpander.ai's event system.
6
+
7
+ The decorator ensures that the registered function:
8
+ - Can be either synchronous or asynchronous
9
+ - Executes before any event listeners are initialized
10
+ - Handles initialization logic, setup tasks, or pre-processing
11
+
12
+ Execution Notes:
13
+ - Boot handlers execute once during the application startup phase
14
+ - They run before event listeners are registered and task processing begins
15
+ - If an exception is raised in the function, the application will fail to start
16
+ - Use boot handlers for initialization tasks like database connections, configuration loading, etc.
17
+
18
+ Example usage:
19
+ --------------
20
+ >>> @on_boot
21
+ ... async def initialize_database():
22
+ ... database = Database()
23
+ ... await database.connect()
24
+ ... logger.info("Database connected successfully")
25
+
26
+ >>> @on_boot
27
+ ... def load_configuration():
28
+ ... config = load_config_file()
29
+ ... validate_config(config)
30
+ ... logger.info("Configuration loaded and validated")
31
+ """
32
+
33
+ import asyncio
34
+ from functools import wraps
35
+ from inspect import iscoroutinefunction
36
+ from typing import Optional, Callable
37
+
38
+ from xpander_sdk.models.configuration import Configuration
39
+ from xpander_sdk.modules.events.events_module import Events
40
+
41
+
42
+ def on_boot(
43
+ _func: Optional[Callable] = None,
44
+ *,
45
+ configuration: Optional[Configuration] = None
46
+ ):
47
+ """
48
+ Decorator to register a handler as a boot event executor.
49
+
50
+ The decorated function will be executed during the application boot phase, before
51
+ any event listeners are set up. The function:
52
+ - Can be either synchronous or asynchronous
53
+ - Should handle initialization logic or setup tasks
54
+ - Will be executed only once during application startup
55
+
56
+ Args:
57
+ _func (Optional[Callable]):
58
+ The function to decorate (for direct usage like `@on_boot`).
59
+ configuration (Optional[Configuration]):
60
+ An optional configuration object used to initialize the Events module.
61
+
62
+ Example:
63
+ >>> @on_boot
64
+ ... async def initialize_services():
65
+ ... await setup_database()
66
+ ... await connect_external_apis()
67
+ ... logger.info("All services initialized")
68
+
69
+ >>> @on_boot
70
+ ... def load_environment():
71
+ ... load_dotenv()
72
+ ... validate_environment_variables()
73
+ """
74
+
75
+ def decorator(func: Callable) -> Callable:
76
+ @wraps(func)
77
+ async def async_wrapper(*args, **kwargs):
78
+ return await func(*args, **kwargs)
79
+
80
+ @wraps(func)
81
+ def sync_wrapper(*args, **kwargs):
82
+ return func(*args, **kwargs)
83
+
84
+ wrapped = async_wrapper if iscoroutinefunction(func) else sync_wrapper
85
+
86
+ # Register handler directly on Events class without instantiating
87
+ Events.register_boot_handler(wrapped)
88
+
89
+ return wrapped
90
+
91
+ if _func and callable(_func):
92
+ return decorator(_func)
93
+
94
+ return decorator
@@ -0,0 +1,94 @@
1
+ """
2
+ xpander_sdk.decorators.on_shutdown
3
+
4
+ This module provides the `@on_shutdown` decorator, which allows developers to define
5
+ shutdown functions that execute when the application is exiting from xpander.ai's event system.
6
+
7
+ The decorator ensures that the registered function:
8
+ - Can be either synchronous or asynchronous
9
+ - Executes during application shutdown phase
10
+ - Handles cleanup logic, resource disposal, or finalization tasks
11
+
12
+ Execution Notes:
13
+ - Shutdown handlers execute when the application is terminating
14
+ - They run after event listeners are stopped but before final cleanup
15
+ - If an exception is raised in the function, it will be logged but won't prevent shutdown
16
+ - Use shutdown handlers for cleanup tasks like closing database connections, saving state, etc.
17
+
18
+ Example usage:
19
+ --------------
20
+ >>> @on_shutdown
21
+ ... async def cleanup_database():
22
+ ... database = get_database_connection()
23
+ ... await database.close()
24
+ ... logger.info("Database connection closed")
25
+
26
+ >>> @on_shutdown
27
+ ... def save_application_state():
28
+ ... state = get_current_state()
29
+ ... save_state_to_file(state)
30
+ ... logger.info("Application state saved")
31
+ """
32
+
33
+ import asyncio
34
+ from functools import wraps
35
+ from inspect import iscoroutinefunction
36
+ from typing import Optional, Callable
37
+
38
+ from xpander_sdk.models.configuration import Configuration
39
+ from xpander_sdk.modules.events.events_module import Events
40
+
41
+
42
+ def on_shutdown(
43
+ _func: Optional[Callable] = None,
44
+ *,
45
+ configuration: Optional[Configuration] = None
46
+ ):
47
+ """
48
+ Decorator to register a handler as a shutdown event executor.
49
+
50
+ The decorated function will be executed during the application shutdown phase, after
51
+ event listeners are stopped. The function:
52
+ - Can be either synchronous or asynchronous
53
+ - Should handle cleanup logic or resource disposal
54
+ - Will be executed once during application shutdown
55
+
56
+ Args:
57
+ _func (Optional[Callable]):
58
+ The function to decorate (for direct usage like `@on_shutdown`).
59
+ configuration (Optional[Configuration]):
60
+ An optional configuration object used to initialize the Events module.
61
+
62
+ Example:
63
+ >>> @on_shutdown
64
+ ... async def cleanup_resources():
65
+ ... await close_database_connections()
66
+ ... await cleanup_temp_files()
67
+ ... logger.info("All resources cleaned up")
68
+
69
+ >>> @on_shutdown
70
+ ... def save_final_state():
71
+ ... save_application_metrics()
72
+ ... logger.info("Final state saved")
73
+ """
74
+
75
+ def decorator(func: Callable) -> Callable:
76
+ @wraps(func)
77
+ async def async_wrapper(*args, **kwargs):
78
+ return await func(*args, **kwargs)
79
+
80
+ @wraps(func)
81
+ def sync_wrapper(*args, **kwargs):
82
+ return func(*args, **kwargs)
83
+
84
+ wrapped = async_wrapper if iscoroutinefunction(func) else sync_wrapper
85
+
86
+ # Register handler directly on Events class without instantiating
87
+ Events.register_shutdown_handler(wrapped)
88
+
89
+ return wrapped
90
+
91
+ if _func and callable(_func):
92
+ return decorator(_func)
93
+
94
+ return decorator
@@ -0,0 +1,203 @@
1
+ """
2
+ xpander_sdk.decorators.on_task
3
+
4
+ This module provides the `@on_task` decorator, which allows developers to define
5
+ task execution functions that integrate with xpander.ai’s event system.
6
+
7
+ The decorator ensures that the registered function:
8
+ - Accepts a single parameter named `task` of type `Task`
9
+ - Returns the modified `Task` after processing
10
+ - Optionally works with both sync and async function styles
11
+
12
+ Execution Notes:
13
+ - The task executor automatically handles status transitions (e.g., success or failure).
14
+ - You must set `task.result = ...` to assign your output.
15
+ - If an exception is raised in the function, the task will be marked as failed.
16
+ - You do not need to manually manage status or lifecycle hooks.
17
+
18
+ Example usage:
19
+ --------------
20
+ >>> @on_task
21
+ ... async def handle_agent_task(task: Task):
22
+ ... backend = Backend(task.configuration)
23
+ ... agno_args = await backend.aget_args(
24
+ ... agent_id=task.agent_id,
25
+ ... agent_version=task.agent_version,
26
+ ... task=task
27
+ ... )
28
+ ... agno_agent = Agent(**agno_args)
29
+ ... result = await agno_agent.arun(message=task.to_message())
30
+ ... task.result = result.content
31
+ ... return task
32
+ """
33
+
34
+ import asyncio
35
+ import argparse
36
+ import json
37
+ import sys
38
+ from functools import wraps
39
+ from inspect import iscoroutinefunction, signature
40
+ from typing import Optional, Callable
41
+
42
+ from xpander_sdk.models.configuration import Configuration
43
+ from xpander_sdk.modules.events.events_module import Events
44
+ from xpander_sdk.modules.tasks.models.task import LocalTaskTest, AgentExecutionInput
45
+ from xpander_sdk.models.shared import OutputFormat
46
+ from xpander_sdk.modules.tasks.sub_modules.task import Task
47
+
48
+
49
+ def on_task(
50
+ _func: Optional[Callable] = None,
51
+ *,
52
+ configuration: Optional[Configuration] = None,
53
+ test_task: Optional[LocalTaskTest] = None
54
+ ):
55
+ """
56
+ Decorator to register a handler as an event-driven task executor.
57
+
58
+ The decorated function will be executed when the task is triggered through
59
+ xpander.ai’s event system. The function:
60
+ - Must accept a single parameter named `task` of type `Task`
61
+ - Must return the `Task` instance after applying changes
62
+ - Should assign the final output using `task.result`
63
+ - Does not need to set the task status; it's handled automatically
64
+
65
+ - Supports both synchronous and asynchronous functions
66
+ - Automatically transitions task statuses based on function outcome
67
+ Args:
68
+ _func (Optional[Callable]):
69
+ The function to decorate (for direct usage like `@on_task`).
70
+ configuration (Optional[Configuration]):
71
+ An optional configuration object used to initialize the Events module.
72
+ test_task (Optional[LocalTaskTest]):
73
+ Optional simulated task used for local development or testing.
74
+
75
+ Raises:
76
+ TypeError: If the decorated function does not accept a `task` parameter.
77
+ TypeError: If `test_task` is provided but is not a `LocalTaskTest`.
78
+ TypeError: If the input `task` is not an instance of `Task`.
79
+ TypeError: If the return value is not an instance of `Task`.
80
+
81
+ Example:
82
+ >>> @on_task
83
+ ... async def handle_agent_task(task: Task):
84
+ ... backend = Backend(task.configuration)
85
+ ... agno_args = await backend.aget_args(
86
+ ... agent_id=task.agent_id,
87
+ ... agent_version=task.agent_version,
88
+ ... task=task
89
+ ... )
90
+ ... agno_agent = Agent(**agno_args)
91
+ ... result = await agno_agent.arun(message=task.to_message())
92
+ ... task.result = result.content
93
+ ... return task
94
+ """
95
+
96
+ def decorator(func: Callable) -> Callable:
97
+ sig = signature(func)
98
+
99
+ if 'task' not in sig.parameters:
100
+ raise TypeError(f"Function '{func.__name__}' must have a 'task' parameter.")
101
+
102
+ # Initialize effective_test_task with the passed parameter
103
+ effective_test_task = test_task
104
+
105
+ if effective_test_task and not isinstance(effective_test_task, LocalTaskTest):
106
+ raise TypeError(
107
+ f"'test_task' must be an instance of LocalTaskTest, got {type(effective_test_task).__name__}"
108
+ )
109
+
110
+ param_names = list(sig.parameters)
111
+
112
+ def get_task_from_call(args, kwargs) -> Task:
113
+ if 'task' in kwargs:
114
+ task_value = kwargs['task']
115
+ else:
116
+ task_index = param_names.index('task')
117
+ task_value = args[task_index] if len(args) > task_index else None
118
+ if not isinstance(task_value, Task):
119
+ raise TypeError(
120
+ f"'task' must be an instance of Task, got {type(task_value).__name__}"
121
+ )
122
+ return task_value
123
+
124
+ @wraps(func)
125
+ async def async_wrapper(*args, **kwargs):
126
+ get_task_from_call(args, kwargs)
127
+ result = await func(*args, **kwargs)
128
+ if not isinstance(result, Task):
129
+ raise TypeError(
130
+ f"Function '{func.__name__}' must return an instance of Task, got {type(result).__name__}"
131
+ )
132
+ return result
133
+
134
+ @wraps(func)
135
+ def sync_wrapper(*args, **kwargs):
136
+ get_task_from_call(args, kwargs)
137
+ result = func(*args, **kwargs)
138
+ if not isinstance(result, Task):
139
+ raise TypeError(
140
+ f"Function '{func.__name__}' must return an instance of Task, got {type(result).__name__}"
141
+ )
142
+ return result
143
+
144
+ wrapped = async_wrapper if iscoroutinefunction(func) else sync_wrapper
145
+
146
+ # Check for direct invocation with --invoke and --prompt arguments
147
+ if any('--invoke' in arg for arg in sys.argv):
148
+ # Parse command line arguments
149
+ parser = argparse.ArgumentParser(description='Run task handler locally')
150
+ parser.add_argument('--invoke', action='store_true', help='Invoke the task handler directly')
151
+ parser.add_argument('--prompt', type=str, required='--invoke' in sys.argv, help='Input text for the task')
152
+ parser.add_argument('--output_format', type=str, choices=['json', 'markdown', 'text'],
153
+ help='Output format (json, markdown, text)')
154
+ parser.add_argument('--output_schema', type=str, help='JSON schema as a string for output validation')
155
+
156
+ # Parse only known args to avoid conflicts with other args that might be present
157
+ args, _ = parser.parse_known_args()
158
+
159
+ if args.invoke:
160
+ # Create a LocalTaskTest instance from command line arguments (like existing test_task logic)
161
+ output_format_map = {
162
+ 'json': OutputFormat.Json,
163
+ 'markdown': OutputFormat.Markdown,
164
+ 'text': OutputFormat.Text
165
+ }
166
+
167
+ # Parse output schema if provided
168
+ output_schema = None
169
+ if args.output_schema:
170
+ try:
171
+ output_schema = json.loads(args.output_schema)
172
+ except json.JSONDecodeError as e:
173
+ print(f"Error parsing output_schema JSON: {e}")
174
+ sys.exit(1)
175
+
176
+ # Create LocalTaskTest (same as existing test_task functionality)
177
+ # Only include output_format and output_schema if explicitly provided
178
+ task_kwargs = {
179
+ 'input': AgentExecutionInput(text=args.prompt)
180
+ }
181
+
182
+ if args.output_format:
183
+ task_kwargs['output_format'] = output_format_map.get(args.output_format.lower())
184
+
185
+ if output_schema is not None:
186
+ task_kwargs['output_schema'] = output_schema
187
+
188
+ cli_test_task = LocalTaskTest(**task_kwargs)
189
+ # Override effective_test_task with CLI-provided one
190
+ effective_test_task = cli_test_task
191
+ print(f"Running task handler directly with prompt: {args.prompt}")
192
+ if output_schema:
193
+ print(f"Using output schema: {output_schema}")
194
+
195
+ events_module = Events(configuration=configuration)
196
+ events_module.register(on_task=wrapped, test_task=effective_test_task)
197
+
198
+ return wrapped
199
+
200
+ if _func and callable(_func):
201
+ return decorator(_func)
202
+
203
+ return decorator