jarviscore-framework 0.1.0__py3-none-any.whl → 0.2.0__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 (99) hide show
  1. examples/autoagent_distributed_example.py +211 -0
  2. examples/custom_profile_decorator.py +134 -0
  3. examples/custom_profile_wrap.py +168 -0
  4. examples/customagent_distributed_example.py +362 -0
  5. examples/customagent_p2p_example.py +347 -0
  6. jarviscore/__init__.py +60 -15
  7. jarviscore/adapter/__init__.py +40 -0
  8. jarviscore/adapter/decorator.py +336 -0
  9. jarviscore/adapter/wrapper.py +303 -0
  10. jarviscore/cli/check.py +18 -13
  11. jarviscore/cli/scaffold.py +178 -0
  12. jarviscore/cli/smoketest.py +3 -2
  13. jarviscore/context/__init__.py +40 -0
  14. jarviscore/context/dependency.py +160 -0
  15. jarviscore/context/jarvis_context.py +207 -0
  16. jarviscore/context/memory.py +155 -0
  17. jarviscore/core/agent.py +44 -1
  18. jarviscore/core/mesh.py +196 -35
  19. jarviscore/data/.env.example +146 -0
  20. jarviscore/data/__init__.py +7 -0
  21. jarviscore/data/examples/autoagent_distributed_example.py +211 -0
  22. jarviscore/data/examples/calculator_agent_example.py +77 -0
  23. jarviscore/data/examples/customagent_distributed_example.py +362 -0
  24. jarviscore/data/examples/customagent_p2p_example.py +347 -0
  25. jarviscore/data/examples/multi_agent_workflow.py +132 -0
  26. jarviscore/data/examples/research_agent_example.py +76 -0
  27. jarviscore/docs/API_REFERENCE.md +264 -51
  28. jarviscore/docs/AUTOAGENT_GUIDE.md +198 -0
  29. jarviscore/docs/CONFIGURATION.md +41 -23
  30. jarviscore/docs/CUSTOMAGENT_GUIDE.md +415 -0
  31. jarviscore/docs/GETTING_STARTED.md +113 -17
  32. jarviscore/docs/TROUBLESHOOTING.md +155 -13
  33. jarviscore/docs/USER_GUIDE.md +144 -363
  34. jarviscore/execution/llm.py +23 -16
  35. jarviscore/orchestration/engine.py +20 -8
  36. jarviscore/p2p/__init__.py +10 -0
  37. jarviscore/p2p/coordinator.py +129 -0
  38. jarviscore/p2p/messages.py +87 -0
  39. jarviscore/p2p/peer_client.py +576 -0
  40. jarviscore/p2p/peer_tool.py +268 -0
  41. jarviscore_framework-0.2.0.dist-info/METADATA +143 -0
  42. jarviscore_framework-0.2.0.dist-info/RECORD +132 -0
  43. {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.2.0.dist-info}/WHEEL +1 -1
  44. {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.2.0.dist-info}/top_level.txt +1 -0
  45. test_logs/code_registry/functions/data_generator-558779ed_560ebc37.py +7 -0
  46. test_logs/code_registry/functions/data_generator-5ed3609e_560ebc37.py +7 -0
  47. test_logs/code_registry/functions/data_generator-66da0356_43970bb9.py +25 -0
  48. test_logs/code_registry/functions/data_generator-7a2fac83_583709d9.py +36 -0
  49. test_logs/code_registry/functions/data_generator-888b670f_aa235863.py +9 -0
  50. test_logs/code_registry/functions/data_generator-9ca5f642_aa235863.py +9 -0
  51. test_logs/code_registry/functions/data_generator-bfd90775_560ebc37.py +7 -0
  52. test_logs/code_registry/functions/data_generator-e95d2f7d_aa235863.py +9 -0
  53. test_logs/code_registry/functions/data_generator-f60ca8a2_327eb8c2.py +29 -0
  54. test_logs/code_registry/functions/mathematician-02adf9ee_958658d9.py +19 -0
  55. test_logs/code_registry/functions/mathematician-0706fb57_5df13441.py +23 -0
  56. test_logs/code_registry/functions/mathematician-153c9c4a_ba59c918.py +83 -0
  57. test_logs/code_registry/functions/mathematician-287e61c0_41daa793.py +18 -0
  58. test_logs/code_registry/functions/mathematician-2967af5a_863c2cc6.py +17 -0
  59. test_logs/code_registry/functions/mathematician-303ca6d6_5df13441.py +23 -0
  60. test_logs/code_registry/functions/mathematician-308a4afd_cbf5064d.py +73 -0
  61. test_logs/code_registry/functions/mathematician-353f16e2_0968bcf5.py +18 -0
  62. test_logs/code_registry/functions/mathematician-3c22475a_41daa793.py +17 -0
  63. test_logs/code_registry/functions/mathematician-5bac1029_0968bcf5.py +18 -0
  64. test_logs/code_registry/functions/mathematician-640f76b2_9198780b.py +19 -0
  65. test_logs/code_registry/functions/mathematician-752fa7ea_863c2cc6.py +17 -0
  66. test_logs/code_registry/functions/mathematician-baf9ef39_0968bcf5.py +18 -0
  67. test_logs/code_registry/functions/mathematician-bc8b2a2f_5df13441.py +23 -0
  68. test_logs/code_registry/functions/mathematician-c31e4686_41daa793.py +18 -0
  69. test_logs/code_registry/functions/mathematician-cc84c84c_863c2cc6.py +17 -0
  70. test_logs/code_registry/functions/mathematician-dd7c7144_9198780b.py +19 -0
  71. test_logs/code_registry/functions/mathematician-e671c256_41ea4487.py +74 -0
  72. test_logs/code_registry/functions/report_generator-1a878fcc_18d44bdc.py +47 -0
  73. test_logs/code_registry/functions/report_generator-25c1c331_cea57d0d.py +35 -0
  74. test_logs/code_registry/functions/report_generator-37552117_e711c2b9.py +35 -0
  75. test_logs/code_registry/functions/report_generator-bc662768_e711c2b9.py +35 -0
  76. test_logs/code_registry/functions/report_generator-d6c0e76b_5e7722ec.py +44 -0
  77. test_logs/code_registry/functions/report_generator-f270fb02_680529c3.py +44 -0
  78. test_logs/code_registry/functions/text_processor-11393b14_4370d3ed.py +40 -0
  79. test_logs/code_registry/functions/text_processor-7d02dfc3_d3b569be.py +37 -0
  80. test_logs/code_registry/functions/text_processor-8adb5e32_9168c5fe.py +13 -0
  81. test_logs/code_registry/functions/text_processor-c58ffc19_78b4ceac.py +42 -0
  82. test_logs/code_registry/functions/text_processor-cd5977b1_9168c5fe.py +13 -0
  83. test_logs/code_registry/functions/text_processor-ec1c8773_9168c5fe.py +13 -0
  84. tests/test_01_analyst_standalone.py +124 -0
  85. tests/test_02_assistant_standalone.py +164 -0
  86. tests/test_03_analyst_with_framework.py +945 -0
  87. tests/test_04_assistant_with_framework.py +1002 -0
  88. tests/test_05_integration.py +1301 -0
  89. tests/test_06_real_llm_integration.py +760 -0
  90. tests/test_07_distributed_single_node.py +578 -0
  91. tests/test_08_distributed_multi_node.py +454 -0
  92. tests/test_09_distributed_autoagent.py +509 -0
  93. tests/test_10_distributed_customagent.py +787 -0
  94. tests/test_context.py +467 -0
  95. tests/test_decorator.py +622 -0
  96. tests/test_mesh.py +35 -4
  97. jarviscore_framework-0.1.0.dist-info/METADATA +0 -136
  98. jarviscore_framework-0.1.0.dist-info/RECORD +0 -55
  99. {jarviscore_framework-0.1.0.dist-info → jarviscore_framework-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,336 @@
1
+ """
2
+ @jarvis_agent decorator - Convert any class into a JarvisCore agent.
3
+
4
+ Wraps existing classes (LangChain, CrewAI, raw Python) to work with
5
+ JarvisCore orchestration without requiring them to inherit from CustomAgent.
6
+ """
7
+ from typing import List, Optional, Type, Any
8
+ import inspect
9
+ import logging
10
+
11
+ from jarviscore.profiles.customagent import CustomAgent
12
+ from jarviscore.context import JarvisContext, create_context
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ # Common method names to auto-detect
18
+ EXECUTE_METHODS = [
19
+ "run", # Most common
20
+ "invoke", # LangChain
21
+ "execute", # Generic
22
+ "call", # Callable pattern
23
+ "__call__", # Callable objects
24
+ "process", # Processing agents
25
+ "handle", # Handler pattern
26
+ ]
27
+
28
+
29
+ def detect_execute_method(cls: Type) -> Optional[str]:
30
+ """
31
+ Auto-detect the execute method on a class.
32
+
33
+ Checks for common method names in order of preference.
34
+ Only detects methods defined on the class itself, not inherited
35
+ from object (like __call__ from type).
36
+
37
+ Args:
38
+ cls: Class to inspect
39
+
40
+ Returns:
41
+ Method name or None if not found
42
+ """
43
+ for method_name in EXECUTE_METHODS:
44
+ # Check if method is defined on the class itself (not inherited from object)
45
+ # We walk the MRO but stop before 'object'
46
+ for klass in cls.__mro__:
47
+ if klass is object:
48
+ break
49
+ if method_name in klass.__dict__:
50
+ attr = getattr(cls, method_name)
51
+ if callable(attr):
52
+ return method_name
53
+ break
54
+ return None
55
+
56
+
57
+ def jarvis_agent(
58
+ role: str,
59
+ capabilities: List[str],
60
+ execute_method: Optional[str] = None
61
+ ):
62
+ """
63
+ Decorator to convert any class into a JarvisCore agent.
64
+
65
+ The decorated class can use any framework (LangChain, CrewAI, raw Python).
66
+ JarvisCore provides orchestration (data handoff, dependencies, memory).
67
+
68
+ Args:
69
+ role: Agent role identifier (used for step routing)
70
+ capabilities: List of capabilities (used for step matching)
71
+ execute_method: Method name to call for execution (auto-detected if not provided)
72
+
73
+ Returns:
74
+ Wrapped class that extends CustomAgent
75
+
76
+ Example:
77
+ # Simple agent
78
+ @jarvis_agent(role="processor", capabilities=["data_processing"])
79
+ class DataProcessor:
80
+ def run(self, data):
81
+ return {"processed": data * 2}
82
+
83
+ # Agent with context access
84
+ @jarvis_agent(role="aggregator", capabilities=["aggregation"])
85
+ class Aggregator:
86
+ def run(self, task, ctx: JarvisContext):
87
+ prev = ctx.previous("step1")
88
+ return {"result": prev}
89
+
90
+ # Agent with custom method name
91
+ @jarvis_agent(role="researcher", capabilities=["research"], execute_method="invoke")
92
+ class Researcher:
93
+ def invoke(self, query):
94
+ return {"findings": search(query)}
95
+
96
+ Usage:
97
+ mesh = Mesh(mode="autonomous")
98
+ mesh.add(DataProcessor)
99
+ await mesh.start()
100
+ results = await mesh.workflow("pipeline", [...])
101
+ """
102
+ def decorator(cls: Type) -> Type:
103
+ # Detect execute method if not provided
104
+ method_name = execute_method or detect_execute_method(cls)
105
+ if not method_name:
106
+ raise ValueError(
107
+ f"Could not detect execute method on {cls.__name__}. "
108
+ f"Please specify execute_method parameter or add one of: {EXECUTE_METHODS}"
109
+ )
110
+
111
+ # Verify method exists
112
+ if not hasattr(cls, method_name):
113
+ raise ValueError(
114
+ f"{cls.__name__} has no method '{method_name}'"
115
+ )
116
+
117
+ # Check if method expects context parameter
118
+ method = getattr(cls, method_name)
119
+ sig = inspect.signature(method)
120
+ params = list(sig.parameters.keys())
121
+
122
+ # Remove 'self' from params
123
+ if params and params[0] == 'self':
124
+ params = params[1:]
125
+
126
+ expects_context = 'ctx' in params or 'context' in params
127
+
128
+ logger.debug(
129
+ f"Wrapping {cls.__name__}: method={method_name}, "
130
+ f"expects_context={expects_context}, params={params}"
131
+ )
132
+
133
+ # Create wrapped CustomAgent subclass
134
+ class WrappedAgent(CustomAgent):
135
+ """Wrapped agent created by @jarvis_agent decorator."""
136
+
137
+ # Override class attributes from decorator
138
+ # (Can't use nonlocal role/capabilities directly due to scoping)
139
+ pass
140
+
141
+ # Set class attributes (must do this after class creation)
142
+ WrappedAgent.role = role
143
+ WrappedAgent.capabilities = capabilities
144
+
145
+ # Store metadata for introspection
146
+ WrappedAgent._wrapped_class = cls
147
+ WrappedAgent._execute_method = method_name
148
+ WrappedAgent._expects_context = expects_context
149
+ WrappedAgent._original_params = params
150
+
151
+ # Override __init__
152
+ original_init = WrappedAgent.__init__
153
+
154
+ def new_init(self, agent_id=None, **kwargs):
155
+ # Call CustomAgent init
156
+ CustomAgent.__init__(self, agent_id)
157
+
158
+ # Instantiate the wrapped class
159
+ try:
160
+ self._instance = cls(**kwargs) if kwargs else cls()
161
+ except TypeError:
162
+ # Class might not accept kwargs
163
+ self._instance = cls()
164
+
165
+ self._logger.debug(f"Created wrapped instance of {cls.__name__}")
166
+
167
+ WrappedAgent.__init__ = new_init
168
+
169
+ # Override setup
170
+ async def new_setup(self):
171
+ await CustomAgent.setup(self)
172
+
173
+ # Call wrapped class setup if it exists
174
+ if hasattr(self._instance, 'setup'):
175
+ setup_fn = self._instance.setup
176
+ if inspect.iscoroutinefunction(setup_fn):
177
+ await setup_fn()
178
+ else:
179
+ setup_fn()
180
+
181
+ self._logger.debug(f"Setup complete for wrapped {cls.__name__}")
182
+
183
+ WrappedAgent.setup = new_setup
184
+
185
+ # Override execute_task
186
+ async def new_execute_task(self, task: dict) -> dict:
187
+ """Execute by calling the wrapped class's method."""
188
+ # Get the execute method from instance
189
+ method = getattr(self._instance, method_name)
190
+
191
+ # Get context if method expects it
192
+ ctx = None
193
+ if expects_context:
194
+ # Use pre-injected context from WorkflowEngine if available
195
+ ctx = task.get('_jarvis_context')
196
+
197
+ # Fallback: build context manually (for standalone usage)
198
+ if ctx is None:
199
+ memory_dict = {}
200
+ dep_manager = None
201
+
202
+ if self._mesh:
203
+ engine = getattr(self._mesh, '_workflow_engine', None)
204
+ if engine:
205
+ memory_dict = engine.memory
206
+ dep_manager = getattr(engine, 'dependency_manager', None)
207
+
208
+ ctx = create_context(
209
+ workflow_id=task.get('context', {}).get('workflow_id', 'unknown'),
210
+ step_id=task.get('context', {}).get('step_id', task.get('id', 'unknown')),
211
+ task=task.get('task', ''),
212
+ params=task.get('params', {}),
213
+ memory_dict=memory_dict,
214
+ dependency_manager=dep_manager
215
+ )
216
+
217
+ # Prepare arguments based on method signature
218
+ args = self._prepare_args(task, ctx, params)
219
+
220
+ # Call the method
221
+ try:
222
+ result = method(*args)
223
+
224
+ # Handle async methods
225
+ if inspect.isawaitable(result):
226
+ result = await result
227
+
228
+ except Exception as e:
229
+ self._logger.error(f"Error in {cls.__name__}.{method_name}: {e}")
230
+ return {
231
+ 'status': 'failure',
232
+ 'error': str(e),
233
+ 'agent': self.agent_id
234
+ }
235
+
236
+ # Normalize result to expected format
237
+ return self._normalize_result(result)
238
+
239
+ WrappedAgent.execute_task = new_execute_task
240
+
241
+ # Helper to prepare arguments
242
+ def prepare_args(self, task: dict, ctx: Optional[JarvisContext], param_names: List[str]) -> tuple:
243
+ """Prepare arguments for the wrapped method."""
244
+ args = []
245
+
246
+ # Get params, handling both dict and non-dict cases
247
+ task_params = task.get('params', {})
248
+ params_is_dict = isinstance(task_params, dict)
249
+
250
+ for param in param_names:
251
+ if param in ('ctx', 'context'):
252
+ args.append(ctx)
253
+ elif param == 'task':
254
+ args.append(task.get('task', task))
255
+ elif param == 'data':
256
+ # Try to get data from params or context
257
+ if params_is_dict:
258
+ args.append(task_params.get('data', task_params))
259
+ else:
260
+ args.append(task_params)
261
+ elif param == 'params':
262
+ args.append(task_params)
263
+ elif param in ('input', 'query', 'text', 'message'):
264
+ # Common input parameter names
265
+ if params_is_dict:
266
+ args.append(task.get('task', task_params.get(param, '')))
267
+ else:
268
+ args.append(task.get('task', task_params))
269
+ else:
270
+ # Try to get from params, fall back to task dict
271
+ if params_is_dict and param in task_params:
272
+ args.append(task_params[param])
273
+ elif param in task:
274
+ args.append(task[param])
275
+ else:
276
+ # Pass whole params as fallback
277
+ args.append(task_params)
278
+ break # Only do this once
279
+
280
+ # If no args were determined, pass the task/params
281
+ if not args:
282
+ if 'params' in task:
283
+ args.append(task_params)
284
+ else:
285
+ args.append(task)
286
+
287
+ return tuple(args)
288
+
289
+ WrappedAgent._prepare_args = prepare_args
290
+
291
+ # Helper to normalize result
292
+ def normalize_result(self, result: Any) -> dict:
293
+ """Normalize result to expected format."""
294
+ if isinstance(result, dict):
295
+ if 'status' not in result:
296
+ return {
297
+ 'status': 'success',
298
+ 'output': result,
299
+ 'agent': self.agent_id
300
+ }
301
+ if 'agent' not in result:
302
+ result['agent'] = self.agent_id
303
+ return result
304
+ else:
305
+ return {
306
+ 'status': 'success',
307
+ 'output': result,
308
+ 'agent': self.agent_id
309
+ }
310
+
311
+ WrappedAgent._normalize_result = normalize_result
312
+
313
+ # Override teardown
314
+ async def new_teardown(self):
315
+ # Call wrapped class teardown if it exists
316
+ if hasattr(self._instance, 'teardown'):
317
+ teardown_fn = self._instance.teardown
318
+ if inspect.iscoroutinefunction(teardown_fn):
319
+ await teardown_fn()
320
+ else:
321
+ teardown_fn()
322
+
323
+ await CustomAgent.teardown(self)
324
+ self._logger.debug(f"Teardown complete for wrapped {cls.__name__}")
325
+
326
+ WrappedAgent.teardown = new_teardown
327
+
328
+ # Copy class metadata
329
+ WrappedAgent.__name__ = cls.__name__
330
+ WrappedAgent.__qualname__ = cls.__qualname__
331
+ WrappedAgent.__doc__ = cls.__doc__ or f"Wrapped agent from {cls.__name__}"
332
+ WrappedAgent.__module__ = cls.__module__
333
+
334
+ return WrappedAgent
335
+
336
+ return decorator
@@ -0,0 +1,303 @@
1
+ """
2
+ wrap() function - Wrap an existing instance as a JarvisCore agent.
3
+
4
+ Alternative to @jarvis_agent decorator when you have an existing instance
5
+ rather than a class definition.
6
+
7
+ Example:
8
+ from jarviscore import wrap, Mesh
9
+
10
+ # Your existing agent instance
11
+ my_langchain_agent = LangChainAgent(llm=my_llm)
12
+
13
+ # Wrap it for JarvisCore
14
+ wrapped = wrap(
15
+ my_langchain_agent,
16
+ role="researcher",
17
+ capabilities=["research", "analysis"],
18
+ execute_method="invoke"
19
+ )
20
+
21
+ mesh = Mesh(mode="autonomous")
22
+ mesh.add(wrapped)
23
+ await mesh.start()
24
+ """
25
+ from typing import List, Optional, Any
26
+ import inspect
27
+ import logging
28
+
29
+ from jarviscore.profiles.customagent import CustomAgent
30
+ from jarviscore.context import JarvisContext, create_context
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ # Common method names to auto-detect
35
+ EXECUTE_METHODS = [
36
+ "run", # Most common
37
+ "invoke", # LangChain
38
+ "execute", # Generic
39
+ "call", # Callable pattern
40
+ "__call__", # Callable objects
41
+ "process", # Processing agents
42
+ "handle", # Handler pattern
43
+ ]
44
+
45
+
46
+ def detect_execute_method(instance: Any) -> Optional[str]:
47
+ """
48
+ Auto-detect the execute method on an instance.
49
+
50
+ Args:
51
+ instance: Object instance to inspect
52
+
53
+ Returns:
54
+ Method name or None if not found
55
+ """
56
+ for method_name in EXECUTE_METHODS:
57
+ if hasattr(instance, method_name):
58
+ attr = getattr(instance, method_name)
59
+ if callable(attr) and method_name != '__call__':
60
+ return method_name
61
+ elif method_name == '__call__':
62
+ # Check if __call__ is defined on the class itself
63
+ for klass in type(instance).__mro__:
64
+ if klass is object:
65
+ break
66
+ if '__call__' in klass.__dict__:
67
+ return method_name
68
+ return None
69
+
70
+
71
+ def wrap(
72
+ instance: Any,
73
+ role: str,
74
+ capabilities: List[str],
75
+ execute_method: Optional[str] = None
76
+ ) -> CustomAgent:
77
+ """
78
+ Wrap an existing instance as a JarvisCore agent.
79
+
80
+ Use this when you have an already-instantiated object (like a LangChain
81
+ agent or custom class instance) that you want to use with JarvisCore.
82
+
83
+ Args:
84
+ instance: The object instance to wrap
85
+ role: Agent role identifier (used for step routing)
86
+ capabilities: List of capabilities (used for step matching)
87
+ execute_method: Method name to call for execution (auto-detected if not provided)
88
+
89
+ Returns:
90
+ A CustomAgent instance wrapping the original object
91
+
92
+ Example:
93
+ # Wrap a LangChain agent
94
+ from langchain.agents import AgentExecutor
95
+
96
+ langchain_agent = AgentExecutor(agent=my_agent, tools=my_tools)
97
+
98
+ wrapped = wrap(
99
+ langchain_agent,
100
+ role="assistant",
101
+ capabilities=["chat", "tools"],
102
+ execute_method="invoke"
103
+ )
104
+
105
+ mesh.add(wrapped)
106
+
107
+ # Wrap a simple Python object
108
+ class MyProcessor:
109
+ def run(self, data):
110
+ return {"processed": data * 2}
111
+
112
+ processor = MyProcessor()
113
+ wrapped = wrap(processor, role="processor", capabilities=["processing"])
114
+
115
+ # Wrap with context access
116
+ class MyAggregator:
117
+ def run(self, task, ctx: JarvisContext):
118
+ prev = ctx.previous("step1")
119
+ return {"aggregated": prev}
120
+
121
+ aggregator = MyAggregator()
122
+ wrapped = wrap(aggregator, role="aggregator", capabilities=["aggregation"])
123
+ """
124
+ # Detect execute method if not provided
125
+ method_name = execute_method or detect_execute_method(instance)
126
+ if not method_name:
127
+ raise ValueError(
128
+ f"Could not detect execute method on {type(instance).__name__}. "
129
+ f"Please specify execute_method parameter or add one of: {EXECUTE_METHODS}"
130
+ )
131
+
132
+ # Verify method exists
133
+ if not hasattr(instance, method_name):
134
+ raise ValueError(
135
+ f"{type(instance).__name__} has no method '{method_name}'"
136
+ )
137
+
138
+ # Check if method expects context parameter
139
+ method = getattr(instance, method_name)
140
+ sig = inspect.signature(method)
141
+ params = list(sig.parameters.keys())
142
+
143
+ # Remove 'self' from params (shouldn't be there for bound methods, but just in case)
144
+ if params and params[0] == 'self':
145
+ params = params[1:]
146
+
147
+ expects_context = 'ctx' in params or 'context' in params
148
+
149
+ logger.debug(
150
+ f"Wrapping instance {type(instance).__name__}: method={method_name}, "
151
+ f"expects_context={expects_context}, params={params}"
152
+ )
153
+
154
+ # Create a CustomAgent subclass dynamically
155
+ class WrappedInstanceAgent(CustomAgent):
156
+ """Agent wrapping an existing instance via wrap()."""
157
+ pass
158
+
159
+ # Set class attributes
160
+ WrappedInstanceAgent.role = role
161
+ WrappedInstanceAgent.capabilities = capabilities
162
+
163
+ # Store metadata
164
+ WrappedInstanceAgent._wrapped_instance = instance
165
+ WrappedInstanceAgent._execute_method = method_name
166
+ WrappedInstanceAgent._expects_context = expects_context
167
+ WrappedInstanceAgent._original_params = params
168
+
169
+ # Create the agent instance
170
+ agent = WrappedInstanceAgent.__new__(WrappedInstanceAgent)
171
+
172
+ # Initialize CustomAgent parts
173
+ CustomAgent.__init__(agent)
174
+
175
+ # Store the wrapped instance
176
+ agent._instance = instance
177
+
178
+ # Override execute_task
179
+ async def execute_task(self, task: dict) -> dict:
180
+ """Execute by calling the wrapped instance's method."""
181
+ method = getattr(self._instance, method_name)
182
+
183
+ # Get context if method expects it
184
+ ctx = None
185
+ if expects_context:
186
+ # Use pre-injected context from WorkflowEngine if available
187
+ ctx = task.get('_jarvis_context')
188
+
189
+ # Fallback: build context manually (for standalone usage)
190
+ if ctx is None:
191
+ memory_dict = {}
192
+ dep_manager = None
193
+
194
+ if self._mesh:
195
+ engine = getattr(self._mesh, '_workflow_engine', None)
196
+ if engine:
197
+ memory_dict = engine.memory
198
+ dep_manager = getattr(engine, 'dependency_manager', None)
199
+
200
+ ctx = create_context(
201
+ workflow_id=task.get('context', {}).get('workflow_id', 'unknown'),
202
+ step_id=task.get('context', {}).get('step_id', task.get('id', 'unknown')),
203
+ task=task.get('task', ''),
204
+ params=task.get('params', {}),
205
+ memory_dict=memory_dict,
206
+ dependency_manager=dep_manager
207
+ )
208
+
209
+ # Prepare arguments
210
+ args = _prepare_args(task, ctx, params)
211
+
212
+ # Call the method
213
+ try:
214
+ result = method(*args)
215
+
216
+ # Handle async methods
217
+ if inspect.isawaitable(result):
218
+ result = await result
219
+
220
+ except Exception as e:
221
+ logger.error(f"Error in {type(instance).__name__}.{method_name}: {e}")
222
+ return {
223
+ 'status': 'failure',
224
+ 'error': str(e),
225
+ 'agent': self.agent_id
226
+ }
227
+
228
+ # Normalize result
229
+ return _normalize_result(self, result)
230
+
231
+ # Bind the method to the agent
232
+ import types
233
+ agent.execute_task = types.MethodType(execute_task, agent)
234
+
235
+ # Copy class name for better debugging
236
+ WrappedInstanceAgent.__name__ = f"Wrapped_{type(instance).__name__}"
237
+ WrappedInstanceAgent.__qualname__ = f"Wrapped_{type(instance).__name__}"
238
+
239
+ logger.debug(f"Created wrapped agent: role={role}, instance={type(instance).__name__}")
240
+
241
+ return agent
242
+
243
+
244
+ def _prepare_args(task: dict, ctx: Optional[JarvisContext], param_names: List[str]) -> tuple:
245
+ """Prepare arguments for the wrapped method."""
246
+ args = []
247
+
248
+ task_params = task.get('params', {})
249
+ params_is_dict = isinstance(task_params, dict)
250
+
251
+ for param in param_names:
252
+ if param in ('ctx', 'context'):
253
+ args.append(ctx)
254
+ elif param == 'task':
255
+ args.append(task.get('task', task))
256
+ elif param == 'data':
257
+ if params_is_dict:
258
+ args.append(task_params.get('data', task_params))
259
+ else:
260
+ args.append(task_params)
261
+ elif param == 'params':
262
+ args.append(task_params)
263
+ elif param in ('input', 'query', 'text', 'message'):
264
+ if params_is_dict:
265
+ args.append(task.get('task', task_params.get(param, '')))
266
+ else:
267
+ args.append(task.get('task', task_params))
268
+ else:
269
+ if params_is_dict and param in task_params:
270
+ args.append(task_params[param])
271
+ elif param in task:
272
+ args.append(task[param])
273
+ else:
274
+ args.append(task_params)
275
+ break
276
+
277
+ if not args:
278
+ if 'params' in task:
279
+ args.append(task_params)
280
+ else:
281
+ args.append(task)
282
+
283
+ return tuple(args)
284
+
285
+
286
+ def _normalize_result(agent: CustomAgent, result: Any) -> dict:
287
+ """Normalize result to expected format."""
288
+ if isinstance(result, dict):
289
+ if 'status' not in result:
290
+ return {
291
+ 'status': 'success',
292
+ 'output': result,
293
+ 'agent': agent.agent_id
294
+ }
295
+ if 'agent' not in result:
296
+ result['agent'] = agent.agent_id
297
+ return result
298
+ else:
299
+ return {
300
+ 'status': 'success',
301
+ 'output': result,
302
+ 'agent': agent.agent_id
303
+ }
jarviscore/cli/check.py CHANGED
@@ -222,14 +222,17 @@ class HealthChecker:
222
222
  return False
223
223
 
224
224
  async def _test_gemini(self) -> bool:
225
- """Test Gemini connectivity."""
225
+ """Test Gemini connectivity using the new google.genai SDK."""
226
226
  try:
227
- import google.generativeai as genai
227
+ from google import genai
228
228
 
229
- genai.configure(api_key=os.getenv('GEMINI_API_KEY'))
230
- model = genai.GenerativeModel(os.getenv('GEMINI_MODEL', 'gemini-1.5-flash'))
229
+ client = genai.Client(api_key=os.getenv('GEMINI_API_KEY'))
230
+ model_name = os.getenv('GEMINI_MODEL', 'gemini-2.0-flash')
231
231
 
232
- response = await model.generate_content_async("Reply with just 'OK'")
232
+ response = await client.aio.models.generate_content(
233
+ model=model_name,
234
+ contents="Reply with just 'OK'"
235
+ )
233
236
  return 'OK' in response.text.upper()
234
237
 
235
238
  except Exception:
@@ -291,13 +294,15 @@ class HealthChecker:
291
294
  print("\n" + "="*70)
292
295
  print(" Next Steps")
293
296
  print("="*70)
294
- print("\n1. Copy .env.example to .env:")
297
+ print("\n1. Initialize project (creates .env.example and examples):")
298
+ print(" python -m jarviscore.cli.scaffold --examples")
299
+ print("\n2. Configure your environment:")
295
300
  print(" cp .env.example .env")
296
- print("\n2. Add your LLM API key to .env (choose one):")
297
- print(" - CLAUDE_API_KEY=sk-ant-...")
298
- print(" - AZURE_API_KEY=...")
299
- print(" - GEMINI_API_KEY=...")
300
- print(" - LLM_ENDPOINT=http://localhost:8000 (for local vLLM)")
301
+ print(" # Edit .env and add one of:")
302
+ print(" # CLAUDE_API_KEY=sk-ant-...")
303
+ print(" # AZURE_API_KEY=...")
304
+ print(" # GEMINI_API_KEY=...")
305
+ print(" # LLM_ENDPOINT=http://localhost:8000 (for local vLLM)")
301
306
  print("\n3. Run health check again:")
302
307
  print(" python -m jarviscore.cli.check --validate-llm")
303
308
  print("\n4. Try the smoke test:")
@@ -309,8 +314,8 @@ class HealthChecker:
309
314
  print("\n✓ All checks passed! Ready to use JarvisCore.\n")
310
315
  print("Next steps:")
311
316
  print(" 1. Run smoke test: python -m jarviscore.cli.smoketest")
312
- print(" 2. Try examples: python examples/calculator_agent_example.py")
313
- print(" 3. Read guide: docs/GETTING_STARTED.md")
317
+ print(" 2. Get examples: python -m jarviscore.cli.scaffold --examples")
318
+ print(" 3. Try example: python examples/calculator_agent_example.py")
314
319
  print()
315
320
 
316
321
  return True