kubiya-control-plane-api 0.1.0__py3-none-any.whl → 0.3.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.
Potentially problematic release.
This version of kubiya-control-plane-api might be problematic. Click here for more details.
- control_plane_api/README.md +266 -0
- control_plane_api/__init__.py +0 -0
- control_plane_api/__version__.py +1 -0
- control_plane_api/alembic/README +1 -0
- control_plane_api/alembic/env.py +98 -0
- control_plane_api/alembic/script.py.mako +28 -0
- control_plane_api/alembic/versions/1382bec74309_initial_migration_with_all_models.py +251 -0
- control_plane_api/alembic/versions/1f54bc2a37e3_add_analytics_tables.py +162 -0
- control_plane_api/alembic/versions/2e4cb136dc10_rename_toolset_ids_to_skill_ids_in_teams.py +30 -0
- control_plane_api/alembic/versions/31cd69a644ce_add_skill_templates_table.py +28 -0
- control_plane_api/alembic/versions/89e127caa47d_add_jobs_and_job_executions_tables.py +161 -0
- control_plane_api/alembic/versions/add_llm_models_table.py +51 -0
- control_plane_api/alembic/versions/b0e10697f212_add_runtime_column_to_teams_simple.py +42 -0
- control_plane_api/alembic/versions/ce43b24b63bf_add_execution_trigger_source_and_fix_.py +155 -0
- control_plane_api/alembic/versions/d4eaf16e3f8d_rename_toolsets_to_skills.py +84 -0
- control_plane_api/alembic/versions/efa2dc427da1_rename_metadata_to_custom_metadata.py +32 -0
- control_plane_api/alembic/versions/f973b431d1ce_add_workflow_executor_to_skill_types.py +44 -0
- control_plane_api/alembic.ini +148 -0
- control_plane_api/api/index.py +12 -0
- control_plane_api/app/__init__.py +11 -0
- control_plane_api/app/activities/__init__.py +20 -0
- control_plane_api/app/activities/agent_activities.py +379 -0
- control_plane_api/app/activities/team_activities.py +410 -0
- control_plane_api/app/activities/temporal_cloud_activities.py +577 -0
- control_plane_api/app/config/__init__.py +35 -0
- control_plane_api/app/config/api_config.py +354 -0
- control_plane_api/app/config/model_pricing.py +318 -0
- control_plane_api/app/config.py +95 -0
- control_plane_api/app/database.py +135 -0
- control_plane_api/app/exceptions.py +408 -0
- control_plane_api/app/lib/__init__.py +11 -0
- control_plane_api/app/lib/job_executor.py +312 -0
- control_plane_api/app/lib/kubiya_client.py +235 -0
- control_plane_api/app/lib/litellm_pricing.py +166 -0
- control_plane_api/app/lib/planning_tools/__init__.py +22 -0
- control_plane_api/app/lib/planning_tools/agents.py +155 -0
- control_plane_api/app/lib/planning_tools/base.py +189 -0
- control_plane_api/app/lib/planning_tools/environments.py +214 -0
- control_plane_api/app/lib/planning_tools/resources.py +240 -0
- control_plane_api/app/lib/planning_tools/teams.py +198 -0
- control_plane_api/app/lib/policy_enforcer_client.py +939 -0
- control_plane_api/app/lib/redis_client.py +436 -0
- control_plane_api/app/lib/supabase.py +71 -0
- control_plane_api/app/lib/temporal_client.py +138 -0
- control_plane_api/app/lib/validation/__init__.py +20 -0
- control_plane_api/app/lib/validation/runtime_validation.py +287 -0
- control_plane_api/app/main.py +128 -0
- control_plane_api/app/middleware/__init__.py +8 -0
- control_plane_api/app/middleware/auth.py +513 -0
- control_plane_api/app/middleware/exception_handler.py +267 -0
- control_plane_api/app/middleware/rate_limiting.py +384 -0
- control_plane_api/app/middleware/request_id.py +202 -0
- control_plane_api/app/models/__init__.py +27 -0
- control_plane_api/app/models/agent.py +79 -0
- control_plane_api/app/models/analytics.py +206 -0
- control_plane_api/app/models/associations.py +81 -0
- control_plane_api/app/models/environment.py +63 -0
- control_plane_api/app/models/execution.py +93 -0
- control_plane_api/app/models/job.py +179 -0
- control_plane_api/app/models/llm_model.py +75 -0
- control_plane_api/app/models/presence.py +49 -0
- control_plane_api/app/models/project.py +47 -0
- control_plane_api/app/models/session.py +38 -0
- control_plane_api/app/models/team.py +66 -0
- control_plane_api/app/models/workflow.py +55 -0
- control_plane_api/app/policies/README.md +121 -0
- control_plane_api/app/policies/approved_users.rego +62 -0
- control_plane_api/app/policies/business_hours.rego +51 -0
- control_plane_api/app/policies/rate_limiting.rego +100 -0
- control_plane_api/app/policies/tool_restrictions.rego +86 -0
- control_plane_api/app/routers/__init__.py +4 -0
- control_plane_api/app/routers/agents.py +364 -0
- control_plane_api/app/routers/agents_v2.py +1260 -0
- control_plane_api/app/routers/analytics.py +1014 -0
- control_plane_api/app/routers/context_manager.py +562 -0
- control_plane_api/app/routers/environment_context.py +270 -0
- control_plane_api/app/routers/environments.py +715 -0
- control_plane_api/app/routers/execution_environment.py +517 -0
- control_plane_api/app/routers/executions.py +1911 -0
- control_plane_api/app/routers/health.py +92 -0
- control_plane_api/app/routers/health_v2.py +326 -0
- control_plane_api/app/routers/integrations.py +274 -0
- control_plane_api/app/routers/jobs.py +1344 -0
- control_plane_api/app/routers/models.py +82 -0
- control_plane_api/app/routers/models_v2.py +361 -0
- control_plane_api/app/routers/policies.py +639 -0
- control_plane_api/app/routers/presence.py +234 -0
- control_plane_api/app/routers/projects.py +902 -0
- control_plane_api/app/routers/runners.py +379 -0
- control_plane_api/app/routers/runtimes.py +172 -0
- control_plane_api/app/routers/secrets.py +155 -0
- control_plane_api/app/routers/skills.py +1001 -0
- control_plane_api/app/routers/skills_definitions.py +140 -0
- control_plane_api/app/routers/task_planning.py +1256 -0
- control_plane_api/app/routers/task_queues.py +654 -0
- control_plane_api/app/routers/team_context.py +270 -0
- control_plane_api/app/routers/teams.py +1400 -0
- control_plane_api/app/routers/worker_queues.py +1545 -0
- control_plane_api/app/routers/workers.py +935 -0
- control_plane_api/app/routers/workflows.py +204 -0
- control_plane_api/app/runtimes/__init__.py +6 -0
- control_plane_api/app/runtimes/validation.py +344 -0
- control_plane_api/app/schemas/job_schemas.py +295 -0
- control_plane_api/app/services/__init__.py +1 -0
- control_plane_api/app/services/agno_service.py +619 -0
- control_plane_api/app/services/litellm_service.py +190 -0
- control_plane_api/app/services/policy_service.py +525 -0
- control_plane_api/app/services/temporal_cloud_provisioning.py +150 -0
- control_plane_api/app/skills/__init__.py +44 -0
- control_plane_api/app/skills/base.py +229 -0
- control_plane_api/app/skills/business_intelligence.py +189 -0
- control_plane_api/app/skills/data_visualization.py +154 -0
- control_plane_api/app/skills/docker.py +104 -0
- control_plane_api/app/skills/file_generation.py +94 -0
- control_plane_api/app/skills/file_system.py +110 -0
- control_plane_api/app/skills/python.py +92 -0
- control_plane_api/app/skills/registry.py +65 -0
- control_plane_api/app/skills/shell.py +102 -0
- control_plane_api/app/skills/workflow_executor.py +469 -0
- control_plane_api/app/utils/workflow_executor.py +354 -0
- control_plane_api/app/workflows/__init__.py +11 -0
- control_plane_api/app/workflows/agent_execution.py +507 -0
- control_plane_api/app/workflows/agent_execution_with_skills.py +222 -0
- control_plane_api/app/workflows/namespace_provisioning.py +326 -0
- control_plane_api/app/workflows/team_execution.py +399 -0
- control_plane_api/scripts/seed_models.py +239 -0
- control_plane_api/worker/__init__.py +0 -0
- control_plane_api/worker/activities/__init__.py +0 -0
- control_plane_api/worker/activities/agent_activities.py +1241 -0
- control_plane_api/worker/activities/approval_activities.py +234 -0
- control_plane_api/worker/activities/runtime_activities.py +388 -0
- control_plane_api/worker/activities/skill_activities.py +267 -0
- control_plane_api/worker/activities/team_activities.py +1217 -0
- control_plane_api/worker/config/__init__.py +31 -0
- control_plane_api/worker/config/worker_config.py +275 -0
- control_plane_api/worker/control_plane_client.py +529 -0
- control_plane_api/worker/examples/analytics_integration_example.py +362 -0
- control_plane_api/worker/models/__init__.py +1 -0
- control_plane_api/worker/models/inputs.py +89 -0
- control_plane_api/worker/runtimes/__init__.py +31 -0
- control_plane_api/worker/runtimes/base.py +789 -0
- control_plane_api/worker/runtimes/claude_code_runtime.py +1443 -0
- control_plane_api/worker/runtimes/default_runtime.py +617 -0
- control_plane_api/worker/runtimes/factory.py +173 -0
- control_plane_api/worker/runtimes/validation.py +93 -0
- control_plane_api/worker/services/__init__.py +1 -0
- control_plane_api/worker/services/agent_executor.py +422 -0
- control_plane_api/worker/services/agent_executor_v2.py +383 -0
- control_plane_api/worker/services/analytics_collector.py +457 -0
- control_plane_api/worker/services/analytics_service.py +464 -0
- control_plane_api/worker/services/approval_tools.py +310 -0
- control_plane_api/worker/services/approval_tools_agno.py +207 -0
- control_plane_api/worker/services/cancellation_manager.py +177 -0
- control_plane_api/worker/services/data_visualization.py +827 -0
- control_plane_api/worker/services/jira_tools.py +257 -0
- control_plane_api/worker/services/runtime_analytics.py +328 -0
- control_plane_api/worker/services/session_service.py +194 -0
- control_plane_api/worker/services/skill_factory.py +175 -0
- control_plane_api/worker/services/team_executor.py +574 -0
- control_plane_api/worker/services/team_executor_v2.py +465 -0
- control_plane_api/worker/services/workflow_executor_tools.py +1418 -0
- control_plane_api/worker/tests/__init__.py +1 -0
- control_plane_api/worker/tests/e2e/__init__.py +0 -0
- control_plane_api/worker/tests/e2e/test_execution_flow.py +571 -0
- control_plane_api/worker/tests/integration/__init__.py +0 -0
- control_plane_api/worker/tests/integration/test_control_plane_integration.py +308 -0
- control_plane_api/worker/tests/unit/__init__.py +0 -0
- control_plane_api/worker/tests/unit/test_control_plane_client.py +401 -0
- control_plane_api/worker/utils/__init__.py +1 -0
- control_plane_api/worker/utils/chunk_batcher.py +305 -0
- control_plane_api/worker/utils/retry_utils.py +60 -0
- control_plane_api/worker/utils/streaming_utils.py +373 -0
- control_plane_api/worker/worker.py +753 -0
- control_plane_api/worker/workflows/__init__.py +0 -0
- control_plane_api/worker/workflows/agent_execution.py +589 -0
- control_plane_api/worker/workflows/team_execution.py +429 -0
- kubiya_control_plane_api-0.3.4.dist-info/METADATA +229 -0
- kubiya_control_plane_api-0.3.4.dist-info/RECORD +182 -0
- kubiya_control_plane_api-0.3.4.dist-info/entry_points.txt +2 -0
- kubiya_control_plane_api-0.3.4.dist-info/top_level.txt +1 -0
- kubiya_control_plane_api-0.1.0.dist-info/METADATA +0 -66
- kubiya_control_plane_api-0.1.0.dist-info/RECORD +0 -5
- kubiya_control_plane_api-0.1.0.dist-info/top_level.txt +0 -1
- {kubiya_control_plane_api-0.1.0.dist-info/licenses → control_plane_api}/LICENSE +0 -0
- {kubiya_control_plane_api-0.1.0.dist-info → kubiya_control_plane_api-0.3.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
"""Streaming utilities for agent and team execution"""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, Callable, Optional
|
|
4
|
+
import structlog
|
|
5
|
+
|
|
6
|
+
logger = structlog.get_logger()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StreamingHelper:
|
|
10
|
+
"""
|
|
11
|
+
Helper for handling streaming from Agno Agent/Team executions.
|
|
12
|
+
|
|
13
|
+
Provides utilities for:
|
|
14
|
+
- Publishing events to Control Plane
|
|
15
|
+
- Tracking run_id from streaming chunks
|
|
16
|
+
- Collecting response content
|
|
17
|
+
- Publishing tool execution events
|
|
18
|
+
- Handling member message streaming
|
|
19
|
+
- Tracking tool IDs for proper start/complete matching
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, control_plane_client, execution_id: str):
|
|
23
|
+
self.control_plane = control_plane_client
|
|
24
|
+
self.execution_id = execution_id
|
|
25
|
+
self.run_id_published = False
|
|
26
|
+
self.response_content = []
|
|
27
|
+
self.member_message_ids = {} # Track message_id per member
|
|
28
|
+
self.active_streaming_member = None # Track which member is streaming
|
|
29
|
+
self.tool_execution_ids = {} # Track tool IDs for matching start/complete events
|
|
30
|
+
|
|
31
|
+
def handle_run_id(self, chunk: Any, on_run_id: Optional[Callable[[str], None]] = None) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Capture and publish run_id from first streaming chunk.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
chunk: Streaming chunk from Agno
|
|
37
|
+
on_run_id: Optional callback when run_id is captured
|
|
38
|
+
"""
|
|
39
|
+
if not self.run_id_published and hasattr(chunk, 'run_id') and chunk.run_id:
|
|
40
|
+
run_id = chunk.run_id
|
|
41
|
+
|
|
42
|
+
logger.info("run_id_captured", execution_id=self.execution_id[:8], run_id=run_id[:16])
|
|
43
|
+
|
|
44
|
+
# Publish to Control Plane for UI
|
|
45
|
+
self.control_plane.publish_event(
|
|
46
|
+
execution_id=self.execution_id,
|
|
47
|
+
event_type="run_started",
|
|
48
|
+
data={
|
|
49
|
+
"run_id": run_id,
|
|
50
|
+
"execution_id": self.execution_id,
|
|
51
|
+
"cancellable": True,
|
|
52
|
+
}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
self.run_id_published = True
|
|
56
|
+
|
|
57
|
+
# Call callback if provided (for cancellation manager)
|
|
58
|
+
if on_run_id:
|
|
59
|
+
on_run_id(run_id)
|
|
60
|
+
|
|
61
|
+
async def handle_content_chunk(
|
|
62
|
+
self,
|
|
63
|
+
chunk: Any,
|
|
64
|
+
message_id: str,
|
|
65
|
+
print_to_console: bool = True
|
|
66
|
+
) -> Optional[str]:
|
|
67
|
+
"""
|
|
68
|
+
Handle content chunk from streaming response.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
chunk: Streaming chunk
|
|
72
|
+
message_id: Unique message ID for this turn
|
|
73
|
+
print_to_console: Whether to print to stdout
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Content string if present, None otherwise
|
|
77
|
+
"""
|
|
78
|
+
# Check for both 'response' (RuntimeExecutionResult) and 'content' (legacy/Agno)
|
|
79
|
+
content = None
|
|
80
|
+
|
|
81
|
+
# DEBUG: Log what attributes the chunk has
|
|
82
|
+
print(f"[DEBUG] StreamingHelper.handle_content_chunk: chunk type = {type(chunk).__name__}")
|
|
83
|
+
print(f"[DEBUG] StreamingHelper.handle_content_chunk: has 'response' = {hasattr(chunk, 'response')}")
|
|
84
|
+
print(f"[DEBUG] StreamingHelper.handle_content_chunk: has 'content' = {hasattr(chunk, 'content')}")
|
|
85
|
+
|
|
86
|
+
if hasattr(chunk, 'response') and chunk.response:
|
|
87
|
+
content = str(chunk.response)
|
|
88
|
+
print(f"[DEBUG] StreamingHelper.handle_content_chunk: extracted from 'response': {repr(content[:100])}")
|
|
89
|
+
elif hasattr(chunk, 'content') and chunk.content:
|
|
90
|
+
content = str(chunk.content)
|
|
91
|
+
print(f"[DEBUG] StreamingHelper.handle_content_chunk: extracted from 'content': {repr(content[:100])}")
|
|
92
|
+
else:
|
|
93
|
+
print(f"[DEBUG] StreamingHelper.handle_content_chunk: NO CONTENT FOUND!")
|
|
94
|
+
|
|
95
|
+
if content:
|
|
96
|
+
self.response_content.append(content)
|
|
97
|
+
|
|
98
|
+
if print_to_console:
|
|
99
|
+
print(content, end='', flush=True)
|
|
100
|
+
|
|
101
|
+
# Stream to Control Plane for real-time UI updates (NON-BLOCKING)
|
|
102
|
+
await self.control_plane.publish_event_async(
|
|
103
|
+
execution_id=self.execution_id,
|
|
104
|
+
event_type="message_chunk",
|
|
105
|
+
data={
|
|
106
|
+
"role": "assistant",
|
|
107
|
+
"content": content,
|
|
108
|
+
"is_chunk": True,
|
|
109
|
+
"message_id": message_id,
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return content
|
|
114
|
+
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def get_full_response(self) -> str:
|
|
118
|
+
"""Get the complete response accumulated from all chunks."""
|
|
119
|
+
return ''.join(self.response_content)
|
|
120
|
+
|
|
121
|
+
def handle_member_content_chunk(
|
|
122
|
+
self,
|
|
123
|
+
member_name: str,
|
|
124
|
+
content: str,
|
|
125
|
+
print_to_console: bool = True
|
|
126
|
+
) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Handle content chunk from a team member.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
member_name: Name of the team member
|
|
132
|
+
content: Content string
|
|
133
|
+
print_to_console: Whether to print to stdout
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
The member's message_id
|
|
137
|
+
"""
|
|
138
|
+
import time
|
|
139
|
+
|
|
140
|
+
# Generate unique message ID for this member if not exists
|
|
141
|
+
if member_name not in self.member_message_ids:
|
|
142
|
+
self.member_message_ids[member_name] = f"{self.execution_id}_{member_name}_{int(time.time() * 1000000)}"
|
|
143
|
+
|
|
144
|
+
# Print member name header once when they start
|
|
145
|
+
if print_to_console:
|
|
146
|
+
print(f"\n[{member_name}] ", end='', flush=True)
|
|
147
|
+
|
|
148
|
+
# If switching to a different member, mark the previous one as complete
|
|
149
|
+
if self.active_streaming_member and self.active_streaming_member != member_name:
|
|
150
|
+
self.publish_member_complete(self.active_streaming_member)
|
|
151
|
+
|
|
152
|
+
# Track that this member is now actively streaming
|
|
153
|
+
self.active_streaming_member = member_name
|
|
154
|
+
|
|
155
|
+
# Print content without repeated member name prefix
|
|
156
|
+
if print_to_console:
|
|
157
|
+
print(content, end='', flush=True)
|
|
158
|
+
|
|
159
|
+
# Stream member chunk to Control Plane
|
|
160
|
+
message_id = self.member_message_ids[member_name]
|
|
161
|
+
self.control_plane.publish_event(
|
|
162
|
+
execution_id=self.execution_id,
|
|
163
|
+
event_type="member_message_chunk",
|
|
164
|
+
data={
|
|
165
|
+
"role": "assistant",
|
|
166
|
+
"content": content,
|
|
167
|
+
"is_chunk": True,
|
|
168
|
+
"message_id": message_id,
|
|
169
|
+
"source": "team_member",
|
|
170
|
+
"member_name": member_name,
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return message_id
|
|
175
|
+
|
|
176
|
+
def publish_member_complete(self, member_name: str) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Publish member_message_complete event.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
member_name: Name of the member to mark as complete
|
|
182
|
+
"""
|
|
183
|
+
if member_name in self.member_message_ids:
|
|
184
|
+
self.control_plane.publish_event(
|
|
185
|
+
execution_id=self.execution_id,
|
|
186
|
+
event_type="member_message_complete",
|
|
187
|
+
data={
|
|
188
|
+
"message_id": self.member_message_ids[member_name],
|
|
189
|
+
"member_name": member_name,
|
|
190
|
+
"source": "team_member",
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def finalize_streaming(self) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Finalize streaming by marking any active member as complete.
|
|
197
|
+
Call this when streaming ends.
|
|
198
|
+
"""
|
|
199
|
+
if self.active_streaming_member:
|
|
200
|
+
self.publish_member_complete(self.active_streaming_member)
|
|
201
|
+
self.active_streaming_member = None
|
|
202
|
+
|
|
203
|
+
def publish_tool_start(
|
|
204
|
+
self,
|
|
205
|
+
tool_name: str,
|
|
206
|
+
tool_execution_id: str,
|
|
207
|
+
tool_args: Optional[Dict[str, Any]] = None,
|
|
208
|
+
source: str = "agent",
|
|
209
|
+
member_name: Optional[str] = None
|
|
210
|
+
) -> str:
|
|
211
|
+
"""
|
|
212
|
+
Publish tool execution start event.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
tool_name: Name of the tool
|
|
216
|
+
tool_execution_id: Unique ID for this tool execution
|
|
217
|
+
tool_args: Tool arguments
|
|
218
|
+
source: "agent" or "team_member" or "team_leader" or "team"
|
|
219
|
+
member_name: Name of member (if tool is from a member)
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
message_id for this tool execution
|
|
223
|
+
"""
|
|
224
|
+
import time
|
|
225
|
+
|
|
226
|
+
message_id = f"{self.execution_id}_tool_{tool_execution_id}"
|
|
227
|
+
is_member_tool = member_name is not None
|
|
228
|
+
parent_message_id = self.member_message_ids.get(member_name) if is_member_tool else None
|
|
229
|
+
|
|
230
|
+
# Store tool info for matching with completion event
|
|
231
|
+
tool_key = f"{member_name or 'leader'}_{tool_name}_{int(time.time())}"
|
|
232
|
+
self.tool_execution_ids[tool_key] = {
|
|
233
|
+
"tool_execution_id": tool_execution_id,
|
|
234
|
+
"message_id": message_id,
|
|
235
|
+
"tool_name": tool_name,
|
|
236
|
+
"member_name": member_name,
|
|
237
|
+
"parent_message_id": parent_message_id,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
event_type = "member_tool_started" if is_member_tool else "tool_started"
|
|
241
|
+
|
|
242
|
+
self.control_plane.publish_event(
|
|
243
|
+
execution_id=self.execution_id,
|
|
244
|
+
event_type=event_type,
|
|
245
|
+
data={
|
|
246
|
+
"tool_name": tool_name,
|
|
247
|
+
"tool_execution_id": tool_execution_id,
|
|
248
|
+
"message_id": message_id,
|
|
249
|
+
"tool_arguments": tool_args,
|
|
250
|
+
"source": "team_member" if is_member_tool else "team_leader",
|
|
251
|
+
"member_name": member_name,
|
|
252
|
+
"parent_message_id": parent_message_id,
|
|
253
|
+
"message": f"🔧 Executing tool: {tool_name}",
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return message_id
|
|
258
|
+
|
|
259
|
+
def publish_tool_complete(
|
|
260
|
+
self,
|
|
261
|
+
tool_name: str,
|
|
262
|
+
tool_execution_id: str,
|
|
263
|
+
status: str = "success",
|
|
264
|
+
output: Optional[str] = None,
|
|
265
|
+
error: Optional[str] = None,
|
|
266
|
+
source: str = "agent",
|
|
267
|
+
member_name: Optional[str] = None
|
|
268
|
+
) -> None:
|
|
269
|
+
"""
|
|
270
|
+
Publish tool execution completion event.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
tool_name: Name of the tool
|
|
274
|
+
tool_execution_id: Unique ID for this tool execution
|
|
275
|
+
status: "success" or "failed"
|
|
276
|
+
output: Tool output (if successful)
|
|
277
|
+
error: Error message (if failed)
|
|
278
|
+
source: "agent" or "team_member" or "team_leader" or "team"
|
|
279
|
+
member_name: Name of member (if tool is from a member)
|
|
280
|
+
"""
|
|
281
|
+
import time
|
|
282
|
+
|
|
283
|
+
# Find the stored tool info from the start event
|
|
284
|
+
tool_key_pattern = f"{member_name or 'leader'}_{tool_name}"
|
|
285
|
+
matching_tool = None
|
|
286
|
+
for key, tool_info in list(self.tool_execution_ids.items()):
|
|
287
|
+
if key.startswith(tool_key_pattern):
|
|
288
|
+
matching_tool = tool_info
|
|
289
|
+
# Remove from tracking dict
|
|
290
|
+
del self.tool_execution_ids[key]
|
|
291
|
+
break
|
|
292
|
+
|
|
293
|
+
if matching_tool:
|
|
294
|
+
message_id = matching_tool["message_id"]
|
|
295
|
+
parent_message_id = matching_tool["parent_message_id"]
|
|
296
|
+
# Use the stored tool_execution_id from the start event
|
|
297
|
+
tool_execution_id = matching_tool["tool_execution_id"]
|
|
298
|
+
else:
|
|
299
|
+
# Fallback if start event wasn't captured
|
|
300
|
+
message_id = f"{self.execution_id}_tool_{tool_execution_id}"
|
|
301
|
+
parent_message_id = self.member_message_ids.get(member_name) if member_name else None
|
|
302
|
+
logger.warning("tool_completion_without_start", tool_name=tool_name, member_name=member_name)
|
|
303
|
+
|
|
304
|
+
is_member_tool = member_name is not None
|
|
305
|
+
event_type = "member_tool_completed" if is_member_tool else "tool_completed"
|
|
306
|
+
|
|
307
|
+
self.control_plane.publish_event(
|
|
308
|
+
execution_id=self.execution_id,
|
|
309
|
+
event_type=event_type,
|
|
310
|
+
data={
|
|
311
|
+
"tool_name": tool_name,
|
|
312
|
+
"tool_execution_id": tool_execution_id, # Now uses the stored ID from start event
|
|
313
|
+
"message_id": message_id,
|
|
314
|
+
"status": status,
|
|
315
|
+
"tool_output": output[:1000] if output else None, # Limit size
|
|
316
|
+
"tool_error": error,
|
|
317
|
+
"source": "team_member" if is_member_tool else "team_leader",
|
|
318
|
+
"member_name": member_name,
|
|
319
|
+
"parent_message_id": parent_message_id,
|
|
320
|
+
"message": f"{'✅' if status == 'success' else '❌'} Tool {status}: {tool_name}",
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def create_tool_hook(control_plane_client, execution_id: str):
|
|
326
|
+
"""
|
|
327
|
+
Create a tool hook function for Agno Agent/Team.
|
|
328
|
+
|
|
329
|
+
This hook is called before and after each tool execution
|
|
330
|
+
to publish real-time updates to the Control Plane.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
control_plane_client: Control Plane client instance
|
|
334
|
+
execution_id: Execution ID
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Hook function compatible with Agno tool_hooks
|
|
338
|
+
"""
|
|
339
|
+
import time
|
|
340
|
+
|
|
341
|
+
def tool_hook(tool_name: str, tool_args: dict, result: Any = None, error: Exception = None):
|
|
342
|
+
"""Tool hook for real-time updates"""
|
|
343
|
+
tool_execution_id = f"{tool_name}_{int(time.time() * 1000000)}"
|
|
344
|
+
|
|
345
|
+
if error is None and result is None:
|
|
346
|
+
# Tool starting
|
|
347
|
+
control_plane_client.publish_event(
|
|
348
|
+
execution_id=execution_id,
|
|
349
|
+
event_type="tool_started",
|
|
350
|
+
data={
|
|
351
|
+
"tool_name": tool_name,
|
|
352
|
+
"tool_execution_id": tool_execution_id,
|
|
353
|
+
"tool_arguments": tool_args,
|
|
354
|
+
"message": f"🔧 Starting: {tool_name}",
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
else:
|
|
358
|
+
# Tool completed
|
|
359
|
+
status = "failed" if error else "success"
|
|
360
|
+
control_plane_client.publish_event(
|
|
361
|
+
execution_id=execution_id,
|
|
362
|
+
event_type="tool_completed",
|
|
363
|
+
data={
|
|
364
|
+
"tool_name": tool_name,
|
|
365
|
+
"tool_execution_id": tool_execution_id,
|
|
366
|
+
"status": status,
|
|
367
|
+
"tool_output": str(result)[:1000] if result else None,
|
|
368
|
+
"tool_error": str(error) if error else None,
|
|
369
|
+
"message": f"{'✅' if status == 'success' else '❌'} {status}: {tool_name}",
|
|
370
|
+
}
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
return tool_hook
|