vibecore 0.2.0a1__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.

Potentially problematic release.


This version of vibecore might be problematic. Click here for more details.

Files changed (63) hide show
  1. vibecore/__init__.py +0 -0
  2. vibecore/agents/default.py +79 -0
  3. vibecore/agents/prompts.py +12 -0
  4. vibecore/agents/task_agent.py +66 -0
  5. vibecore/cli.py +131 -0
  6. vibecore/context.py +24 -0
  7. vibecore/handlers/__init__.py +5 -0
  8. vibecore/handlers/stream_handler.py +231 -0
  9. vibecore/main.py +506 -0
  10. vibecore/main.tcss +0 -0
  11. vibecore/mcp/__init__.py +6 -0
  12. vibecore/mcp/manager.py +167 -0
  13. vibecore/mcp/server_wrapper.py +109 -0
  14. vibecore/models/__init__.py +5 -0
  15. vibecore/models/anthropic.py +239 -0
  16. vibecore/prompts/common_system_prompt.txt +64 -0
  17. vibecore/py.typed +0 -0
  18. vibecore/session/__init__.py +5 -0
  19. vibecore/session/file_lock.py +127 -0
  20. vibecore/session/jsonl_session.py +236 -0
  21. vibecore/session/loader.py +193 -0
  22. vibecore/session/path_utils.py +81 -0
  23. vibecore/settings.py +161 -0
  24. vibecore/tools/__init__.py +1 -0
  25. vibecore/tools/base.py +27 -0
  26. vibecore/tools/file/__init__.py +5 -0
  27. vibecore/tools/file/executor.py +282 -0
  28. vibecore/tools/file/tools.py +184 -0
  29. vibecore/tools/file/utils.py +78 -0
  30. vibecore/tools/python/__init__.py +1 -0
  31. vibecore/tools/python/backends/__init__.py +1 -0
  32. vibecore/tools/python/backends/terminal_backend.py +58 -0
  33. vibecore/tools/python/helpers.py +80 -0
  34. vibecore/tools/python/manager.py +208 -0
  35. vibecore/tools/python/tools.py +27 -0
  36. vibecore/tools/shell/__init__.py +5 -0
  37. vibecore/tools/shell/executor.py +223 -0
  38. vibecore/tools/shell/tools.py +156 -0
  39. vibecore/tools/task/__init__.py +5 -0
  40. vibecore/tools/task/executor.py +51 -0
  41. vibecore/tools/task/tools.py +51 -0
  42. vibecore/tools/todo/__init__.py +1 -0
  43. vibecore/tools/todo/manager.py +31 -0
  44. vibecore/tools/todo/models.py +36 -0
  45. vibecore/tools/todo/tools.py +111 -0
  46. vibecore/utils/__init__.py +5 -0
  47. vibecore/utils/text.py +28 -0
  48. vibecore/widgets/core.py +332 -0
  49. vibecore/widgets/core.tcss +63 -0
  50. vibecore/widgets/expandable.py +121 -0
  51. vibecore/widgets/expandable.tcss +69 -0
  52. vibecore/widgets/info.py +25 -0
  53. vibecore/widgets/info.tcss +17 -0
  54. vibecore/widgets/messages.py +232 -0
  55. vibecore/widgets/messages.tcss +85 -0
  56. vibecore/widgets/tool_message_factory.py +121 -0
  57. vibecore/widgets/tool_messages.py +483 -0
  58. vibecore/widgets/tool_messages.tcss +289 -0
  59. vibecore-0.2.0a1.dist-info/METADATA +407 -0
  60. vibecore-0.2.0a1.dist-info/RECORD +63 -0
  61. vibecore-0.2.0a1.dist-info/WHEEL +4 -0
  62. vibecore-0.2.0a1.dist-info/entry_points.txt +2 -0
  63. vibecore-0.2.0a1.dist-info/licenses/LICENSE +21 -0
vibecore/__init__.py ADDED
File without changes
@@ -0,0 +1,79 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from agents import Agent, ModelSettings
4
+ from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
5
+ from openai.types import Reasoning
6
+
7
+ from vibecore.context import VibecoreContext
8
+ from vibecore.settings import settings
9
+ from vibecore.tools.file.tools import edit, multi_edit, read, write
10
+ from vibecore.tools.python.tools import execute_python
11
+ from vibecore.tools.shell.tools import bash, glob, grep, ls
12
+ from vibecore.tools.task.tools import task
13
+ from vibecore.tools.todo.tools import todo_read, todo_write
14
+
15
+ from .prompts import COMMON_PROMPT
16
+
17
+ if TYPE_CHECKING:
18
+ from agents.mcp import MCPServer
19
+
20
+ INSTRUCTIONS = (
21
+ COMMON_PROMPT + "\n\n"
22
+ "You are a versatile AI assistant capable of helping with a wide range of tasks. "
23
+ "You have access to various tools including file operations, shell commands, "
24
+ "Python execution, and task management. "
25
+ "Use the appropriate tools to accomplish any task the user requests. "
26
+ "You can handle programming, system administration, file manipulation, "
27
+ "automation, and general problem-solving tasks."
28
+ )
29
+
30
+
31
+ def create_default_agent(mcp_servers: list["MCPServer"] | None = None) -> Agent[VibecoreContext]:
32
+ """Create the general-purpose agent with appropriate tools.
33
+
34
+ Args:
35
+ mcp_servers: Optional list of MCP servers to connect to.
36
+
37
+ Returns:
38
+ Configured general-purpose agent.
39
+ """
40
+ tools: list = [
41
+ todo_read,
42
+ todo_write,
43
+ execute_python,
44
+ read,
45
+ edit,
46
+ multi_edit,
47
+ write,
48
+ bash,
49
+ glob,
50
+ grep,
51
+ ls,
52
+ task,
53
+ ]
54
+ instructions = INSTRUCTIONS
55
+
56
+ instructions = prompt_with_handoff_instructions(instructions)
57
+
58
+ # Configure reasoning based on settings
59
+ reasoning_config = Reasoning(summary="auto")
60
+ if settings.reasoning_effort is not None:
61
+ reasoning_config = Reasoning(effort=settings.reasoning_effort, summary="auto")
62
+
63
+ return Agent[VibecoreContext](
64
+ name="Vibecore Agent",
65
+ handoff_description="A versatile general-purpose assistant",
66
+ instructions=instructions,
67
+ tools=tools,
68
+ model=settings.model,
69
+ model_settings=ModelSettings(
70
+ include_usage=True, # Ensure token usage is tracked in streaming mode
71
+ reasoning=reasoning_config,
72
+ ),
73
+ handoffs=[],
74
+ mcp_servers=mcp_servers or [],
75
+ )
76
+
77
+
78
+ # Create default agent without MCP servers for backward compatibility
79
+ default_agent = create_default_agent()
@@ -0,0 +1,12 @@
1
+ """Common prompts and instructions for agents."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def load_common_prompt() -> str:
7
+ """Load the common system prompt from file."""
8
+ prompt_file = Path(__file__).parent.parent / "prompts" / "common_system_prompt.txt"
9
+ return prompt_file.read_text()
10
+
11
+
12
+ COMMON_PROMPT = load_common_prompt()
@@ -0,0 +1,66 @@
1
+ """Task-specific agent configuration for executing delegated tasks."""
2
+
3
+ from agents import Agent, ModelSettings
4
+ from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
5
+ from openai.types import Reasoning
6
+
7
+ from vibecore.context import VibecoreContext
8
+ from vibecore.settings import settings
9
+ from vibecore.tools.file.tools import edit, multi_edit, read, write
10
+ from vibecore.tools.python.tools import execute_python
11
+ from vibecore.tools.shell.tools import bash, glob, grep, ls
12
+ from vibecore.tools.todo.tools import todo_read, todo_write
13
+
14
+ from .prompts import COMMON_PROMPT
15
+
16
+
17
+ def create_task_agent(prompt: str) -> Agent[VibecoreContext]:
18
+ """Create a task agent with all tools except the Task tool.
19
+
20
+ This agent is used by the Task tool to execute specific tasks.
21
+ It has access to all tools except the Task tool itself to prevent
22
+ infinite recursion.
23
+
24
+ Args:
25
+ prompt: The task-specific instructions to add to the agent
26
+
27
+ Returns:
28
+ Configured task agent
29
+ """
30
+ # Same tools as default agent, but excluding task tool
31
+ tools: list = [
32
+ todo_read,
33
+ todo_write,
34
+ execute_python,
35
+ read,
36
+ edit,
37
+ multi_edit,
38
+ write,
39
+ bash,
40
+ glob,
41
+ grep,
42
+ ls,
43
+ ]
44
+
45
+ instructions = (
46
+ COMMON_PROMPT + "\n\n"
47
+ "You are a task-specific AI agent. Your purpose is to complete the following task:\n\n"
48
+ f"{prompt}\n\n"
49
+ "Focus on completing this specific task using the tools available to you. "
50
+ "Provide clear results and any relevant findings."
51
+ )
52
+
53
+ instructions = prompt_with_handoff_instructions(instructions)
54
+
55
+ return Agent[VibecoreContext](
56
+ name="Task Agent",
57
+ handoff_description="A task-specific agent",
58
+ instructions=instructions,
59
+ tools=tools,
60
+ model=settings.model,
61
+ model_settings=ModelSettings(
62
+ include_usage=True, # Ensure token usage is tracked in streaming mode
63
+ reasoning=Reasoning(summary="auto"),
64
+ ),
65
+ handoffs=[],
66
+ )
vibecore/cli.py ADDED
@@ -0,0 +1,131 @@
1
+ """Vibecore CLI interface using typer."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ import typer
7
+ from textual.logging import TextualHandler
8
+
9
+ from vibecore.agents.default import create_default_agent
10
+ from vibecore.context import VibecoreContext
11
+ from vibecore.main import VibecoreApp
12
+ from vibecore.mcp import MCPManager
13
+ from vibecore.settings import settings
14
+
15
+ app = typer.Typer()
16
+
17
+
18
+ def find_latest_session(project_path: Path | None = None, base_dir: Path | None = None) -> str | None:
19
+ """Find the most recent session file for the current project."""
20
+ from vibecore.session.path_utils import canonicalize_path
21
+ from vibecore.settings import settings
22
+
23
+ # Use provided paths or defaults
24
+ if project_path is None:
25
+ project_path = Path.cwd()
26
+ if base_dir is None:
27
+ base_dir = settings.session.base_dir
28
+
29
+ # Get the session directory for this project
30
+ canonical_project = canonicalize_path(project_path)
31
+ session_dir = base_dir / "projects" / canonical_project
32
+
33
+ if not session_dir.exists():
34
+ return None
35
+
36
+ # Find all session files
37
+ session_files = list(session_dir.glob("chat-*.jsonl"))
38
+ if not session_files:
39
+ return None
40
+
41
+ # Sort by modification time (most recent first)
42
+ session_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
43
+
44
+ # Return the session ID (filename without extension)
45
+ return session_files[0].stem
46
+
47
+
48
+ @app.command()
49
+ def run(
50
+ prompt: str | None = typer.Argument(None, help="Prompt text (requires -p flag)"),
51
+ continue_session: bool = typer.Option(
52
+ False,
53
+ "--continue",
54
+ "-c",
55
+ help="Continue the most recent session for this project",
56
+ ),
57
+ session_id: str | None = typer.Option(
58
+ None,
59
+ "--session",
60
+ "-s",
61
+ help="Continue a specific session by ID",
62
+ ),
63
+ print_mode: bool = typer.Option(
64
+ False,
65
+ "--print",
66
+ "-p",
67
+ help="Print response and exit (useful for pipes)",
68
+ ),
69
+ ):
70
+ """Run the Vibecore TUI application."""
71
+ # Set up logging
72
+ logging.basicConfig(
73
+ level="WARNING",
74
+ handlers=[TextualHandler()],
75
+ )
76
+
77
+ logger = logging.getLogger("openai.agents")
78
+ logger.addHandler(TextualHandler())
79
+
80
+ # Create context
81
+ ctx = VibecoreContext()
82
+
83
+ # Initialize MCP manager if configured
84
+ mcp_servers = []
85
+ if settings.mcp_servers:
86
+ # Create MCP manager
87
+ mcp_manager = MCPManager(settings.mcp_servers)
88
+ ctx.mcp_manager = mcp_manager
89
+
90
+ # Get the MCP servers from the manager
91
+ mcp_servers = mcp_manager.servers
92
+
93
+ # Create agent with MCP servers
94
+ agent = create_default_agent(mcp_servers=mcp_servers)
95
+
96
+ # Determine session to use
97
+ session_to_load = None
98
+ if continue_session:
99
+ session_to_load = find_latest_session()
100
+ if not session_to_load:
101
+ typer.echo("No existing sessions found for this project.")
102
+ raise typer.Exit(1)
103
+ typer.echo(f"Continuing session: {session_to_load}")
104
+ elif session_id:
105
+ session_to_load = session_id
106
+ typer.echo(f"Loading session: {session_to_load}")
107
+
108
+ # Create app
109
+ app_instance = VibecoreApp(ctx, agent, session_id=session_to_load, print_mode=print_mode)
110
+
111
+ if print_mode:
112
+ # Run in print mode
113
+ import asyncio
114
+
115
+ # Use provided prompt or None to read from stdin
116
+ result = asyncio.run(app_instance.run_print(prompt))
117
+ # Print raw output to stdout
118
+ if result:
119
+ print(result)
120
+ else:
121
+ # Run normal TUI mode
122
+ app_instance.run()
123
+
124
+
125
+ def main():
126
+ """Entry point for the CLI."""
127
+ app()
128
+
129
+
130
+ if __name__ == "__main__":
131
+ main()
vibecore/context.py ADDED
@@ -0,0 +1,24 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import TYPE_CHECKING, Optional
3
+
4
+ from vibecore.tools.python.manager import PythonExecutionManager
5
+ from vibecore.tools.todo.manager import TodoManager
6
+
7
+ if TYPE_CHECKING:
8
+ from vibecore.main import VibecoreApp
9
+ from vibecore.mcp import MCPManager
10
+
11
+
12
+ @dataclass
13
+ class VibecoreContext:
14
+ todo_manager: TodoManager = field(default_factory=TodoManager)
15
+ python_manager: PythonExecutionManager = field(default_factory=PythonExecutionManager)
16
+ app: Optional["VibecoreApp"] = None
17
+ context_fullness: float = 0.0
18
+ mcp_manager: Optional["MCPManager"] = None
19
+
20
+ def reset_state(self) -> None:
21
+ """Reset all context state for a new session."""
22
+ self.todo_manager = TodoManager()
23
+ self.python_manager = PythonExecutionManager()
24
+ self.context_fullness = 0.0
@@ -0,0 +1,5 @@
1
+ """Handlers for various application concerns."""
2
+
3
+ from vibecore.handlers.stream_handler import AgentStreamHandler
4
+
5
+ __all__ = ["AgentStreamHandler"]
@@ -0,0 +1,231 @@
1
+ """Stream handler for processing agent streaming responses."""
2
+
3
+ import json
4
+ from typing import Protocol
5
+
6
+ from agents import (
7
+ Agent,
8
+ AgentUpdatedStreamEvent,
9
+ MessageOutputItem,
10
+ RawResponsesStreamEvent,
11
+ RunItemStreamEvent,
12
+ RunResultStreaming,
13
+ StreamEvent,
14
+ ToolCallItem,
15
+ ToolCallOutputItem,
16
+ )
17
+ from openai.types.responses import (
18
+ ResponseFunctionToolCall,
19
+ ResponseOutputItemAddedEvent,
20
+ ResponseOutputItemDoneEvent,
21
+ ResponseReasoningItem,
22
+ ResponseReasoningSummaryPartAddedEvent,
23
+ ResponseReasoningSummaryTextDeltaEvent,
24
+ ResponseReasoningSummaryTextDoneEvent,
25
+ ResponseTextDeltaEvent,
26
+ )
27
+
28
+ from vibecore.widgets.messages import (
29
+ AgentMessage,
30
+ BaseMessage,
31
+ MessageStatus,
32
+ ReasoningMessage,
33
+ )
34
+ from vibecore.widgets.tool_message_factory import create_tool_message
35
+ from vibecore.widgets.tool_messages import BaseToolMessage, TaskToolMessage
36
+
37
+
38
+ class MessageHandler(Protocol):
39
+ """Protocol defining the interface for message handling."""
40
+
41
+ async def handle_agent_message(self, message: BaseMessage) -> None:
42
+ """Add a message to the widget's message list."""
43
+ ...
44
+
45
+ async def handle_agent_update(self, new_agent: Agent) -> None:
46
+ """Handle agent updates."""
47
+ ...
48
+
49
+ async def handle_agent_error(self, error: Exception) -> None:
50
+ """Handle errors during streaming."""
51
+ ...
52
+
53
+ async def handle_agent_finished(self) -> None:
54
+ """Handle when the agent has finished processing."""
55
+ ...
56
+
57
+
58
+ class AgentStreamHandler:
59
+ """Handles streaming responses from agents."""
60
+
61
+ def __init__(self, message_handler: MessageHandler) -> None:
62
+ """Initialize the stream handler.
63
+
64
+ Args:
65
+ message_handler: The MessageHandler protocol instance
66
+ """
67
+ self.message_handler = message_handler
68
+ self.message_content = ""
69
+ self.agent_message: AgentMessage | None = None
70
+ self.tool_messages: dict[str, BaseToolMessage] = {}
71
+ self.reasoning_messages: dict[str, ReasoningMessage] = {}
72
+
73
+ async def handle_text_delta(self, delta: str) -> None:
74
+ """Handle incremental text updates from the agent.
75
+
76
+ Args:
77
+ delta: The text delta to append
78
+ """
79
+ self.message_content += delta
80
+ if not self.agent_message:
81
+ self.agent_message = AgentMessage(self.message_content, status=MessageStatus.EXECUTING)
82
+ await self.message_handler.handle_agent_message(self.agent_message)
83
+ else:
84
+ self.agent_message.update(self.message_content)
85
+
86
+ async def handle_tool_call(self, tool_name: str, arguments: str, call_id: str) -> None:
87
+ """Create and display tool message when tool is invoked.
88
+
89
+ Args:
90
+ tool_name: Name of the tool being called
91
+ arguments: JSON string of tool arguments
92
+ call_id: Unique identifier for this tool call
93
+ """
94
+ # Use factory to create the appropriate tool message
95
+ tool_message = create_tool_message(tool_name, arguments)
96
+
97
+ self.tool_messages[call_id] = tool_message
98
+ await self.message_handler.handle_agent_message(tool_message)
99
+
100
+ async def handle_tool_output(self, output: str, call_id: str) -> None:
101
+ """Update tool message with execution results.
102
+
103
+ Args:
104
+ output: The tool execution output
105
+ call_id: The tool call identifier to match with pending calls
106
+ """
107
+ if call_id in self.tool_messages:
108
+ tool_message = self.tool_messages[call_id]
109
+ tool_message.update(MessageStatus.SUCCESS, str(output))
110
+
111
+ async def handle_message_complete(self) -> None:
112
+ """Finalize agent message when complete."""
113
+ if self.agent_message:
114
+ self.agent_message.update(self.message_content, status=MessageStatus.IDLE)
115
+ self.agent_message = None
116
+ self.message_content = ""
117
+
118
+ async def handle_event(self, event: StreamEvent) -> None:
119
+ """Handle a single streaming event.
120
+
121
+ Args:
122
+ event: The streaming event to process
123
+ """
124
+ match event:
125
+ case RawResponsesStreamEvent(data=data):
126
+ match data:
127
+ case ResponseOutputItemAddedEvent(item=ResponseReasoningItem() as item):
128
+ reasoning_id = item.id
129
+ reasoning_message = ReasoningMessage("Thinking...", status=MessageStatus.EXECUTING)
130
+ self.reasoning_messages[reasoning_id] = reasoning_message
131
+ await self.message_handler.handle_agent_message(reasoning_message)
132
+
133
+ case ResponseReasoningSummaryPartAddedEvent() as e:
134
+ reasoning_id = e.item_id
135
+ reasoning_message = self.reasoning_messages[reasoning_id]
136
+ assert reasoning_message, f"Reasoning message with ID {reasoning_id} not found"
137
+ updated = reasoning_message.text + "\n\n" if reasoning_message.text != "Thinking..." else ""
138
+ reasoning_message.update(updated, status=MessageStatus.EXECUTING)
139
+
140
+ case ResponseReasoningSummaryTextDeltaEvent(item_id=reasoning_id, delta=delta):
141
+ reasoning_message = self.reasoning_messages[reasoning_id]
142
+ assert reasoning_message, f"Reasoning message with ID {reasoning_id} not found"
143
+ updated = reasoning_message.text + delta
144
+ reasoning_message.update(updated, status=MessageStatus.EXECUTING)
145
+
146
+ case ResponseReasoningSummaryTextDoneEvent(item_id=reasoning_id) as e:
147
+ pass
148
+
149
+ case ResponseOutputItemDoneEvent(item=ResponseReasoningItem() as item):
150
+ reasoning_id = item.id
151
+ reasoning_message = self.reasoning_messages[reasoning_id]
152
+ assert reasoning_message, f"Reasoning message with ID {reasoning_id} not found"
153
+ reasoning_message.update(reasoning_message.text, status=MessageStatus.IDLE)
154
+
155
+ case ResponseOutputItemAddedEvent(item=ResponseFunctionToolCall(name=tool_name, call_id=call_id)):
156
+ # TODO(serialx): We should move all tool call lifecycle start to here
157
+ # ResponseOutputItemDoneEvent can have race conditions in case of task tool calls
158
+ # Because we stream sub-agent events to our method: handle_task_tool_event()
159
+ # XXX(serialx): Just handle task tool call lifecycle here for now
160
+ # I'm deferring this because `arguments` is not available here... need to refactor
161
+ if tool_name == "task":
162
+ await self.handle_tool_call(tool_name, "{}", call_id)
163
+
164
+ case ResponseTextDeltaEvent(delta=delta) if delta:
165
+ await self.handle_text_delta(delta)
166
+
167
+ case ResponseOutputItemDoneEvent(
168
+ item=ResponseFunctionToolCall(name=tool_name, arguments=arguments, call_id=call_id)
169
+ ):
170
+ # XXX(serialx): See above comments
171
+ if tool_name == "task":
172
+ assert call_id in self.tool_messages, f"Tool call ID {call_id} not found in tool messages"
173
+ task_tool_message = self.tool_messages[call_id]
174
+ assert isinstance(task_tool_message, TaskToolMessage), (
175
+ "Tool message must be a TaskToolMessage instance"
176
+ )
177
+ args = json.loads(arguments)
178
+ task_tool_message.description = args.get("description", "")
179
+ task_tool_message.prompt = args.get("prompt", "")
180
+ else:
181
+ await self.handle_tool_call(tool_name, arguments, call_id)
182
+
183
+ case RunItemStreamEvent(item=item):
184
+ match item:
185
+ case ToolCallItem():
186
+ pass
187
+ case ToolCallOutputItem(output=output, raw_item=raw_item):
188
+ # Find the corresponding tool message by call_id
189
+ if (
190
+ isinstance(raw_item, dict)
191
+ and "call_id" in raw_item
192
+ and raw_item["call_id"] in self.tool_messages
193
+ ):
194
+ await self.handle_tool_output(output, raw_item["call_id"])
195
+ case MessageOutputItem():
196
+ await self.handle_message_complete()
197
+
198
+ case AgentUpdatedStreamEvent(new_agent=new_agent):
199
+ await self.message_handler.handle_agent_update(new_agent)
200
+
201
+ async def handle_task_tool_event(self, tool_name: str, tool_call_id: str, event: StreamEvent) -> None:
202
+ """Handle streaming events from task tool sub-agents.
203
+
204
+ Args:
205
+ tool_name: Name of the tool (e.g., "task")
206
+ tool_call_id: Unique identifier for this tool call
207
+ event: The streaming event from the sub-agent
208
+
209
+ Note: This is called by the main app to handle task tool events.
210
+ The main app receives this event from the agent's task tool handler.
211
+ """
212
+
213
+ assert tool_call_id in self.tool_messages, f"Tool call ID {tool_call_id} not found in tool messages"
214
+ tool_message = self.tool_messages[tool_call_id]
215
+ assert isinstance(tool_message, TaskToolMessage), "Tool message must be a TaskToolMessage instance"
216
+ await tool_message.handle_task_tool_event(event)
217
+
218
+ async def process_stream(self, result: RunResultStreaming) -> None:
219
+ """Process all streaming events from the agent.
220
+
221
+ Args:
222
+ result: The streaming result to process
223
+ """
224
+ try:
225
+ async for event in result.stream_events():
226
+ await self.handle_event(event)
227
+ except Exception as e:
228
+ await self.message_handler.handle_agent_error(e)
229
+ finally:
230
+ await self.message_handler.handle_agent_finished()
231
+ # await self._cleanup()