agentcrew-ai 0.8.3__py3-none-any.whl → 0.8.4__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.
AgentCrew/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.8.3"
1
+ __version__ = "0.8.4"
AgentCrew/main.py CHANGED
@@ -58,7 +58,9 @@ def common_options(func):
58
58
  help="LLM provider to use (claude, groq, openai, google, github_copilot, or deepinfra)",
59
59
  )
60
60
  @click.option(
61
- "--agent-config", default=None, help="Path/URL to the agent configuration file."
61
+ "--agent-config",
62
+ default=None,
63
+ help="Path/URL to the agent configuration file.",
62
64
  )
63
65
  @click.option(
64
66
  "--mcp-config", default=None, help="Path to the mcp servers configuration file."
@@ -11,6 +11,8 @@ from a2a.types import (
11
11
  SecurityScheme,
12
12
  APIKeySecurityScheme,
13
13
  AgentProvider,
14
+ AgentInterface,
15
+ TransportProtocol,
14
16
  )
15
17
  from AgentCrew import __version__
16
18
 
@@ -107,16 +109,20 @@ def create_agent_card(agent: LocalAgent, base_url: str) -> AgentCard:
107
109
  )
108
110
 
109
111
  return AgentCard(
112
+ protocol_version="0.3.0",
110
113
  name=agent.name if hasattr(agent, "name") else "AgentCrew Assistant",
111
114
  description=agent.description
112
115
  if hasattr(agent, "description")
113
116
  else "An AI assistant powered by AgentCrew",
114
117
  url=base_url,
118
+ preferred_transport=TransportProtocol.jsonrpc,
119
+ additional_interfaces=[
120
+ AgentInterface(url=base_url, transport=TransportProtocol.jsonrpc)
121
+ ],
115
122
  provider=provider,
116
- version=__version__, # Should match AgentCrew version
123
+ version=__version__,
117
124
  capabilities=capabilities,
118
125
  skills=skills,
119
- # Most SwissKnife agents work with text and files
120
126
  default_input_modes=["text/plain", "application/octet-stream"],
121
127
  default_output_modes=["text/plain", "application/octet-stream"],
122
128
  security_schemes={"apiKey": security_schemes},
@@ -0,0 +1,72 @@
1
+ """
2
+ A2A-specific error code helpers for proper error handling.
3
+
4
+ This module provides convenience functions for creating A2A-specific errors
5
+ with contextual data according to the A2A protocol v0.3.0 specification.
6
+
7
+ Error Codes (Section 8.2):
8
+ - -32001: TaskNotFoundError
9
+ - -32002: TaskNotCancelableError
10
+ - -32003: PushNotificationNotSupportedError
11
+ - -32004: UnsupportedOperationError
12
+ - -32005: ContentTypeNotSupportedError
13
+ - -32006: InvalidAgentResponseError
14
+ - -32007: AuthenticatedExtendedCardNotConfiguredError
15
+ """
16
+
17
+ from typing import Optional
18
+ from a2a.types import (
19
+ TaskNotFoundError,
20
+ TaskNotCancelableError,
21
+ PushNotificationNotSupportedError,
22
+ UnsupportedOperationError,
23
+ ContentTypeNotSupportedError,
24
+ InvalidAgentResponseError,
25
+ AuthenticatedExtendedCardNotConfiguredError,
26
+ )
27
+
28
+
29
+ class A2AError:
30
+ @staticmethod
31
+ def task_not_found(task_id: str) -> TaskNotFoundError:
32
+ error = TaskNotFoundError()
33
+ error.data = {"task_id": task_id}
34
+ return error
35
+
36
+ @staticmethod
37
+ def task_not_cancelable(task_id: str, current_state: str) -> TaskNotCancelableError:
38
+ error = TaskNotCancelableError()
39
+ error.data = {"task_id": task_id, "state": current_state}
40
+ return error
41
+
42
+ @staticmethod
43
+ def push_notification_not_supported() -> PushNotificationNotSupportedError:
44
+ return PushNotificationNotSupportedError()
45
+
46
+ @staticmethod
47
+ def unsupported_operation(operation: str) -> UnsupportedOperationError:
48
+ error = UnsupportedOperationError()
49
+ error.data = {"operation": operation}
50
+ return error
51
+
52
+ @staticmethod
53
+ def content_type_not_supported(
54
+ mime_type: str, supported_types: Optional[list[str]] = None
55
+ ) -> ContentTypeNotSupportedError:
56
+ error = ContentTypeNotSupportedError()
57
+ error.data = {"mime_type": mime_type}
58
+ if supported_types:
59
+ error.data["supported_types"] = supported_types
60
+ return error
61
+
62
+ @staticmethod
63
+ def invalid_agent_response(details: str) -> InvalidAgentResponseError:
64
+ error = InvalidAgentResponseError()
65
+ error.data = {"details": details}
66
+ return error
67
+
68
+ @staticmethod
69
+ def authenticated_extended_card_not_configured() -> (
70
+ AuthenticatedExtendedCardNotConfiguredError
71
+ ):
72
+ return AuthenticatedExtendedCardNotConfiguredError()
@@ -20,7 +20,6 @@ from .registry import AgentRegistry
20
20
  from .task_manager import MultiAgentTaskManager
21
21
  from a2a.types import (
22
22
  A2ARequest,
23
- JSONRPCError,
24
23
  JSONRPCResponse,
25
24
  JSONRPCErrorResponse,
26
25
  InvalidRequestError,
@@ -30,6 +29,8 @@ from a2a.types import (
30
29
  SendStreamingMessageRequest,
31
30
  GetTaskRequest,
32
31
  CancelTaskRequest,
32
+ TaskResubscriptionRequest,
33
+ MethodNotFoundError,
33
34
  )
34
35
 
35
36
 
@@ -228,13 +229,31 @@ class A2AServer:
228
229
  result = await task_manager.on_cancel_task(json_rpc_request.root)
229
230
  return JSONResponse(result.model_dump(exclude_none=True))
230
231
 
232
+ elif method == "tasks/resubscribe" and isinstance(
233
+ json_rpc_request.root, TaskResubscriptionRequest
234
+ ):
235
+ result_stream = task_manager.on_resubscribe_to_task(
236
+ json_rpc_request.root
237
+ )
238
+
239
+ if isinstance(result_stream, JSONRPCResponse):
240
+ return JSONResponse(result_stream.model_dump(exclude_none=True))
241
+
242
+ async def event_generator():
243
+ async for item in result_stream: # type: ignore
244
+ yield {
245
+ "data": json.dumps(item.model_dump(exclude_none=True))
246
+ }
247
+
248
+ return EventSourceResponse(event_generator())
249
+
231
250
  else:
232
251
  logger.error(f"Invalid method requested: {method}")
233
252
  logger.error(f"Request ID: {json_rpc_request.root.id}")
234
253
  logger.error(f"Request params: {json_rpc_request.root.params}") # type: ignore
235
254
  error = JSONRPCErrorResponse(
236
255
  id=json_rpc_request.root.id,
237
- error=JSONRPCError(code=-32601, message="Method not found"),
256
+ error=MethodNotFoundError(),
238
257
  )
239
258
  return JSONResponse(
240
259
  error.model_dump(exclude_none=True), status_code=400
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  from datetime import datetime
5
5
  from typing import TYPE_CHECKING
6
+ from collections import defaultdict
6
7
  from AgentCrew.modules.agents.base import MessageType
7
8
  from loguru import logger
8
9
  import tempfile
@@ -26,8 +27,6 @@ from a2a.types import (
26
27
  TaskState,
27
28
  TaskStatusUpdateEvent,
28
29
  TaskArtifactUpdateEvent,
29
- TaskNotFoundError,
30
- TaskNotCancelableError,
31
30
  )
32
31
 
33
32
  from AgentCrew.modules.agents import LocalAgent
@@ -37,12 +36,14 @@ from .adapters import (
37
36
  convert_agent_message_to_a2a,
38
37
  )
39
38
  from .common.server.task_manager import TaskManager
39
+ from .errors import A2AError
40
40
 
41
41
  if TYPE_CHECKING:
42
42
  from typing import Any, AsyncIterable, Dict, Optional, Union
43
43
  from AgentCrew.modules.agents import AgentManager
44
44
  from a2a.types import (
45
45
  CancelTaskRequest,
46
+ TaskNotCancelableError,
46
47
  GetTaskPushNotificationConfigRequest,
47
48
  GetTaskRequest,
48
49
  SendMessageRequest,
@@ -56,6 +57,8 @@ if TYPE_CHECKING:
56
57
  class AgentTaskManager(TaskManager):
57
58
  """Manages tasks for a specific agent"""
58
59
 
60
+ TERMINAL_STATES = {TaskState.completed, TaskState.canceled, TaskState.failed}
61
+
59
62
  def __init__(self, agent_name: str, agent_manager: AgentManager):
60
63
  self.agent_name = agent_name
61
64
  self.agent_manager = agent_manager
@@ -64,12 +67,38 @@ class AgentTaskManager(TaskManager):
64
67
  self.streaming_tasks: Dict[str, asyncio.Queue] = {}
65
68
  self.file_handler = None
66
69
 
70
+ self.task_events: Dict[
71
+ str, list[Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent]]
72
+ ] = defaultdict(list)
73
+ self.streaming_enabled_tasks: set[str] = set()
74
+
67
75
  self.agent = self.agent_manager.get_agent(self.agent_name)
68
76
  if self.agent is None or not isinstance(self.agent, LocalAgent):
69
77
  raise ValueError(f"Agent {agent_name} not found or is not a LocalAgent")
70
78
 
71
79
  self.memory_service = self.agent.services["memory"]
72
80
 
81
+ def _is_terminal_state(self, state: TaskState) -> bool:
82
+ """Check if a state is terminal."""
83
+ return state in self.TERMINAL_STATES
84
+
85
+ def _validate_task_not_terminal(
86
+ self, task: Task, operation: str
87
+ ) -> Optional[TaskNotCancelableError]:
88
+ """
89
+ Validate that task is not in terminal state.
90
+
91
+ Args:
92
+ task: Task to check
93
+ operation: Operation being attempted
94
+
95
+ Returns:
96
+ JSONRPCError if invalid, None if valid
97
+ """
98
+ if self._is_terminal_state(task.status.state):
99
+ return A2AError.task_not_cancelable(task.id, task.status.state.value)
100
+ return None
101
+
73
102
  async def on_send_message(
74
103
  self, request: SendMessageRequest | SendStreamingMessageRequest
75
104
  ) -> SendMessageResponse:
@@ -98,6 +127,14 @@ class AgentTaskManager(TaskManager):
98
127
  or f"task_{request.params.message.message_id}"
99
128
  )
100
129
 
130
+ if task_id in self.tasks:
131
+ existing_task = self.tasks[task_id]
132
+ error = self._validate_task_not_terminal(existing_task, "send message")
133
+ if error:
134
+ return SendMessageResponse(
135
+ root=JSONRPCErrorResponse(id=request.id, error=error)
136
+ )
137
+
101
138
  if task_id not in self.tasks:
102
139
  # Create task with initial state
103
140
  task = Task(
@@ -176,6 +213,8 @@ class AgentTaskManager(TaskManager):
176
213
  or f"task_{request.params.message.message_id}"
177
214
  )
178
215
 
216
+ self.streaming_enabled_tasks.add(task_id)
217
+
179
218
  # Create streaming queue
180
219
  queue = asyncio.Queue()
181
220
  self.streaming_tasks[task_id] = queue
@@ -204,6 +243,27 @@ class AgentTaskManager(TaskManager):
204
243
  # Clean up
205
244
  self.streaming_tasks.pop(task_id, None)
206
245
 
246
+ def _record_and_emit_event(
247
+ self, task_id: str, event: Union[TaskStatusUpdateEvent, TaskArtifactUpdateEvent]
248
+ ):
249
+ """
250
+ Record event for replay and broadcast to all active subscribers.
251
+
252
+ Args:
253
+ task_id: Task ID
254
+ event: Event to record and emit
255
+ """
256
+ self.task_events[task_id].append(event)
257
+
258
+ for key, queue in list(self.streaming_tasks.items()):
259
+ if key.startswith(task_id):
260
+ try:
261
+ queue.put_nowait(event)
262
+ except asyncio.QueueFull:
263
+ logger.warning(f"Queue full for {key}")
264
+ except Exception as e:
265
+ logger.error(f"Error emitting event to {key}: {e}")
266
+
207
267
  async def _process_agent_task(self, agent: LocalAgent, task: Task):
208
268
  """
209
269
  Process a task with the agent (background task).
@@ -213,6 +273,12 @@ class AgentTaskManager(TaskManager):
213
273
  message: The message to process
214
274
  task: The task object to update
215
275
  """
276
+ if self._is_terminal_state(task.status.state):
277
+ logger.warning(
278
+ f"Attempted to process task {task.id} in terminal state {task.status.state}"
279
+ )
280
+ return
281
+
216
282
  try:
217
283
  artifacts = []
218
284
  if task.id not in self.task_history:
@@ -253,15 +319,14 @@ class AgentTaskManager(TaskManager):
253
319
  task.status.timestamp = datetime.now().isoformat()
254
320
 
255
321
  # If this is a streaming task, send updates
256
- if task.id in self.streaming_tasks:
257
- queue = self.streaming_tasks[task.id]
258
-
322
+ if task.id in self.streaming_enabled_tasks:
259
323
  # Send thinking update if available
260
324
  if thinking_chunk:
261
325
  think_text_chunk, signature = thinking_chunk
262
326
  if think_text_chunk:
263
327
  thinking_content += think_text_chunk
264
- await queue.put(
328
+ self._record_and_emit_event(
329
+ task.id,
265
330
  TaskStatusUpdateEvent(
266
331
  task_id=task.id,
267
332
  context_id=task.context_id,
@@ -276,7 +341,7 @@ class AgentTaskManager(TaskManager):
276
341
  ),
277
342
  ),
278
343
  final=False,
279
- )
344
+ ),
280
345
  )
281
346
  if signature:
282
347
  thinking_signature += signature
@@ -287,30 +352,30 @@ class AgentTaskManager(TaskManager):
287
352
  chunk_text,
288
353
  artifact_id=f"artifact_{task.id}_{len(artifacts)}",
289
354
  )
290
- await queue.put(
355
+ self._record_and_emit_event(
356
+ task.id,
291
357
  TaskArtifactUpdateEvent(
292
358
  task_id=task.id,
293
359
  context_id=task.context_id,
294
360
  artifact=artifact,
295
- )
361
+ ),
296
362
  )
297
363
 
298
364
  if tool_uses and len(tool_uses) > 0:
299
- if task.id in self.streaming_tasks:
300
- queue = self.streaming_tasks[task.id]
365
+ if task.id in self.streaming_enabled_tasks:
301
366
  artifact = convert_agent_response_to_a2a_artifact(
302
367
  "",
303
368
  artifact_id=f"artifact_{task.id}_{len(artifacts)}",
304
369
  tool_uses=tool_uses,
305
370
  )
306
- await queue.put(
371
+ self._record_and_emit_event(
372
+ task.id,
307
373
  TaskArtifactUpdateEvent(
308
374
  task_id=task.id,
309
375
  context_id=task.context_id,
310
376
  artifact=artifact,
311
- )
377
+ ),
312
378
  )
313
- # prevent the execute_tool_call take the control of event loop before queue has been process
314
379
  await asyncio.sleep(0.7)
315
380
 
316
381
  # Add thinking content as a separate message if available
@@ -399,21 +464,21 @@ class AgentTaskManager(TaskManager):
399
464
  task.artifacts = artifacts
400
465
 
401
466
  # If this is a streaming task, send final update
402
- if task.id in self.streaming_tasks:
403
- queue = self.streaming_tasks[task.id]
404
-
405
- # Send final status
406
- await queue.put(
467
+ if task.id in self.streaming_enabled_tasks:
468
+ self._record_and_emit_event(
469
+ task.id,
407
470
  TaskStatusUpdateEvent(
408
471
  task_id=task.id,
409
472
  context_id=task.context_id,
410
473
  status=task.status,
411
474
  final=True,
412
- )
475
+ ),
413
476
  )
414
477
 
415
- # Mark queue as done
416
- await queue.put(None)
478
+ for key in list(self.streaming_tasks.keys()):
479
+ if key.startswith(task.id):
480
+ queue = self.streaming_tasks[key]
481
+ await queue.put(None)
417
482
 
418
483
  except Exception as e:
419
484
  logger.error(str(e))
@@ -422,17 +487,21 @@ class AgentTaskManager(TaskManager):
422
487
  task.status.timestamp = datetime.now().isoformat()
423
488
 
424
489
  # If this is a streaming task, send error
425
- if task.id in self.streaming_tasks:
426
- queue = self.streaming_tasks[task.id]
427
- await queue.put(
490
+ if task.id in self.streaming_enabled_tasks:
491
+ self._record_and_emit_event(
492
+ task.id,
428
493
  TaskStatusUpdateEvent(
429
494
  task_id=task.id,
430
495
  context_id=task.context_id,
431
496
  status=task.status,
432
497
  final=True,
433
- )
498
+ ),
434
499
  )
435
- await queue.put(None)
500
+
501
+ for key in list(self.streaming_tasks.keys()):
502
+ if key.startswith(task.id):
503
+ queue = self.streaming_tasks[key]
504
+ await queue.put(None)
436
505
 
437
506
  async def on_get_task(self, request: GetTaskRequest) -> GetTaskResponse:
438
507
  """
@@ -447,7 +516,9 @@ class AgentTaskManager(TaskManager):
447
516
  task_id = request.params.id
448
517
  if task_id not in self.tasks:
449
518
  return GetTaskResponse(
450
- root=JSONRPCErrorResponse(id=request.id, error=TaskNotFoundError())
519
+ root=JSONRPCErrorResponse(
520
+ id=request.id, error=A2AError.task_not_found(task_id)
521
+ )
451
522
  )
452
523
 
453
524
  return GetTaskResponse(
@@ -467,19 +538,17 @@ class AgentTaskManager(TaskManager):
467
538
  task_id = request.params.id
468
539
  if task_id not in self.tasks:
469
540
  return CancelTaskResponse(
470
- root=JSONRPCErrorResponse(id=request.id, error=TaskNotFoundError())
541
+ root=JSONRPCErrorResponse(
542
+ id=request.id, error=A2AError.task_not_found(task_id)
543
+ )
471
544
  )
472
545
 
473
546
  task = self.tasks[task_id]
474
547
 
475
- # Check if task can be canceled
476
- if task.status.state in [
477
- TaskState.completed,
478
- TaskState.failed,
479
- TaskState.canceled,
480
- ]:
548
+ error = self._validate_task_not_terminal(task, "cancel")
549
+ if error:
481
550
  return CancelTaskResponse(
482
- root=JSONRPCErrorResponse(id=request.id, error=TaskNotCancelableError())
551
+ root=JSONRPCErrorResponse(id=request.id, error=error)
483
552
  )
484
553
 
485
554
  # Update task status
@@ -506,17 +575,89 @@ class AgentTaskManager(TaskManager):
506
575
  async def on_set_task_push_notification(
507
576
  self, request: SetTaskPushNotificationConfigRequest
508
577
  ) -> SetTaskPushNotificationConfigResponse:
509
- raise NotImplementedError("")
578
+ return SetTaskPushNotificationConfigResponse(
579
+ root=JSONRPCErrorResponse(
580
+ id=request.id, error=A2AError.push_notification_not_supported()
581
+ )
582
+ )
510
583
 
511
584
  async def on_get_task_push_notification(
512
585
  self, request: GetTaskPushNotificationConfigRequest
513
586
  ) -> GetTaskPushNotificationConfigResponse:
514
- raise NotImplementedError("")
587
+ return GetTaskPushNotificationConfigResponse(
588
+ root=JSONRPCErrorResponse(
589
+ id=request.id, error=A2AError.push_notification_not_supported()
590
+ )
591
+ )
515
592
 
516
593
  async def on_resubscribe_to_task(
517
594
  self, request: TaskResubscriptionRequest
518
595
  ) -> Union[AsyncIterable[SendStreamingMessageResponse], JSONRPCResponse]:
519
- raise NotImplementedError("")
596
+ """
597
+ Handle tasks/resubscribe request.
598
+
599
+ Replays all events from task creation and continues with live updates.
600
+
601
+ Args:
602
+ request: The resubscription request
603
+
604
+ Yields:
605
+ Streaming responses with task updates
606
+ """
607
+ task_id = request.params.task_id
608
+
609
+ if task_id not in self.tasks:
610
+ error = A2AError.task_not_found(task_id)
611
+ yield SendStreamingMessageResponse(
612
+ root=JSONRPCErrorResponse(id=request.id, error=error)
613
+ )
614
+ return
615
+
616
+ if task_id not in self.streaming_enabled_tasks:
617
+ error = A2AError.unsupported_operation(
618
+ "Task was not created with streaming enabled"
619
+ )
620
+ yield SendStreamingMessageResponse(
621
+ root=JSONRPCErrorResponse(id=request.id, error=error)
622
+ )
623
+ return
624
+
625
+ task = self.tasks[task_id]
626
+
627
+ if task_id in self.task_events:
628
+ for event in self.task_events[task_id]:
629
+ yield SendStreamingMessageResponse(
630
+ root=SendStreamingMessageSuccessResponse(
631
+ id=request.id, result=event
632
+ )
633
+ )
634
+
635
+ if self._is_terminal_state(task.status.state):
636
+ return
637
+
638
+ queue = asyncio.Queue()
639
+ resubscribe_key = f"{task_id}_resub_{request.id}"
640
+ self.streaming_tasks[resubscribe_key] = queue
641
+
642
+ try:
643
+ while True:
644
+ event = await queue.get()
645
+ if event is None:
646
+ break
647
+
648
+ yield SendStreamingMessageResponse(
649
+ root=SendStreamingMessageSuccessResponse(
650
+ id=request.id, result=event
651
+ )
652
+ )
653
+
654
+ if isinstance(event, TaskStatusUpdateEvent):
655
+ if self._is_terminal_state(event.status.state):
656
+ break
657
+
658
+ finally:
659
+ if resubscribe_key in self.streaming_tasks:
660
+ del self.streaming_tasks[resubscribe_key]
520
661
 
521
662
  # Legacy methods for backward compatibility
522
663
  async def on_send_task(self, request: SendMessageRequest) -> SendMessageResponse:
@@ -186,10 +186,8 @@
186
186
  });
187
187
  }
188
188
  } else {
189
- // For elements without href, deduplicate by tagName + text combination
190
- const elementKey = elementType.toLowerCase() + "|" + displayText;
191
- if (!seenElements.has(elementKey)) {
192
- seenElements.add(elementKey);
189
+ if (!seenElements.has(xpath)) {
190
+ seenElements.add(xpath);
193
191
  clickableElements.push({
194
192
  type: elementType,
195
193
  xpath: xpath,
@@ -105,8 +105,14 @@ class CommandProcessor:
105
105
  asssistant_messages_iterator = reversed(
106
106
  [
107
107
  msg
108
- for msg in self.message_handler.streamline_messages
108
+ for i, msg in enumerate(self.message_handler.streamline_messages)
109
109
  if msg.get("role") == "assistant"
110
+ and (
111
+ self.message_handler.streamline_messages[i + 1].get("role")
112
+ == "user"
113
+ if i + 1 < len(self.message_handler.streamline_messages)
114
+ else True
115
+ )
110
116
  ]
111
117
  )
112
118
  latest_assistant_blk = None
@@ -26,6 +26,7 @@ class CodeAnalysisService:
26
26
  ".cxx": "cpp",
27
27
  ".hxx": "cpp",
28
28
  ".rb": "ruby",
29
+ ".sh": "bash",
29
30
  ".rake": "ruby",
30
31
  ".go": "go",
31
32
  ".rs": "rust",
@@ -36,6 +37,7 @@ class CodeAnalysisService:
36
37
  ".json": "config",
37
38
  ".toml": "config",
38
39
  ".yaml": "config",
40
+ ".yml": "config",
39
41
  # Add more languages as needed
40
42
  }
41
43
 
@@ -784,6 +786,43 @@ class CodeAnalysisService:
784
786
  return result
785
787
  break # Only capture the first identifier
786
788
  return result
789
+ else:
790
+ if node.type in [
791
+ "type_declaration",
792
+ "function_declaration",
793
+ "method_declaration",
794
+ "interface_declaration",
795
+ ]:
796
+ for child in node.children:
797
+ if (
798
+ child.type == "identifier"
799
+ or child.type == "field_identifier"
800
+ ):
801
+ result["name"] = self._extract_node_text(
802
+ child, source_code
803
+ )
804
+ result["first_line"] = (
805
+ self._extract_node_text(node, source_code)
806
+ .split("\n")[0]
807
+ .strip("{")
808
+ )
809
+ return result
810
+ return result
811
+ elif (
812
+ node.type == "var_declaration"
813
+ or node.type == "const_declaration"
814
+ ):
815
+ # Handle Go variable and constant declarations
816
+ for child in node.children:
817
+ if child.type == "var_spec" or child.type == "const_spec":
818
+ for subchild in child.children:
819
+ if subchild.type == "identifier":
820
+ result["type"] = "variable_declaration"
821
+ result["name"] = self._extract_node_text(
822
+ subchild, source_code
823
+ )
824
+ return result
825
+ return result
787
826
 
788
827
  # Recursively process children
789
828
  children = []
@@ -72,7 +72,16 @@ def get_code_analysis_tool_handler(
72
72
  if isinstance(result, dict) and "error" in result:
73
73
  raise Exception(f"Failed to analyze code: {result['error']}")
74
74
 
75
- return result
75
+ return [
76
+ {
77
+ "type": "text",
78
+ "text": result,
79
+ },
80
+ {
81
+ "type": "text",
82
+ "text": "Base on the code analysis, learn about the patterns and development flows, adapt project behaviors if possible for better response.",
83
+ },
84
+ ]
76
85
 
77
86
  return handler
78
87