code-puppy 0.0.354__py3-none-any.whl → 0.0.356__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.
- code_puppy/agents/__init__.py +2 -0
- code_puppy/agents/event_stream_handler.py +74 -1
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +92 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +446 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +198 -0
- code_puppy/api/routers/config.py +74 -0
- code_puppy/api/routers/sessions.py +191 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +73 -0
- code_puppy/command_line/core_commands.py +85 -0
- code_puppy/config.py +63 -0
- code_puppy/messaging/__init__.py +15 -0
- code_puppy/messaging/messages.py +27 -0
- code_puppy/messaging/rich_renderer.py +34 -0
- code_puppy/messaging/spinner/__init__.py +20 -2
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/status_display.py +6 -2
- code_puppy/tools/agent_tools.py +53 -49
- code_puppy/tools/command_runner.py +292 -100
- code_puppy/tools/common.py +176 -1
- code_puppy/tools/display.py +6 -1
- code_puppy/tools/subagent_context.py +158 -0
- {code_puppy-0.0.354.dist-info → code_puppy-0.0.356.dist-info}/METADATA +4 -3
- {code_puppy-0.0.354.dist-info → code_puppy-0.0.356.dist-info}/RECORD +38 -21
- {code_puppy-0.0.354.data → code_puppy-0.0.356.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.354.data → code_puppy-0.0.356.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.354.dist-info → code_puppy-0.0.356.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.354.dist-info → code_puppy-0.0.356.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.354.dist-info → code_puppy-0.0.356.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Callback registration for frontend event emission.
|
|
2
|
+
|
|
3
|
+
This module registers callbacks for various agent events and emits them
|
|
4
|
+
to subscribed WebSocket handlers via the emitter module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from code_puppy.callbacks import register_callback
|
|
12
|
+
from code_puppy.plugins.frontend_emitter.emitter import emit_event
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def on_pre_tool_call(
|
|
18
|
+
tool_name: str, tool_args: Dict[str, Any], context: Any = None
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Emit an event when a tool call starts.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
tool_name: Name of the tool being called
|
|
24
|
+
tool_args: Arguments being passed to the tool
|
|
25
|
+
context: Optional context data for the tool call
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
emit_event(
|
|
29
|
+
"tool_call_start",
|
|
30
|
+
{
|
|
31
|
+
"tool_name": tool_name,
|
|
32
|
+
"tool_args": _sanitize_args(tool_args),
|
|
33
|
+
"start_time": time.time(),
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
logger.debug(f"Emitted tool_call_start for {tool_name}")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.error(f"Failed to emit pre_tool_call event: {e}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def on_post_tool_call(
|
|
42
|
+
tool_name: str,
|
|
43
|
+
tool_args: Dict[str, Any],
|
|
44
|
+
result: Any,
|
|
45
|
+
duration_ms: float,
|
|
46
|
+
context: Any = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Emit an event when a tool call completes.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
tool_name: Name of the tool that was called
|
|
52
|
+
tool_args: Arguments that were passed to the tool
|
|
53
|
+
result: The result returned by the tool
|
|
54
|
+
duration_ms: Execution time in milliseconds
|
|
55
|
+
context: Optional context data for the tool call
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
emit_event(
|
|
59
|
+
"tool_call_complete",
|
|
60
|
+
{
|
|
61
|
+
"tool_name": tool_name,
|
|
62
|
+
"tool_args": _sanitize_args(tool_args),
|
|
63
|
+
"duration_ms": duration_ms,
|
|
64
|
+
"success": _is_successful_result(result),
|
|
65
|
+
"result_summary": _summarize_result(result),
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
logger.debug(
|
|
69
|
+
f"Emitted tool_call_complete for {tool_name} ({duration_ms:.2f}ms)"
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Failed to emit post_tool_call event: {e}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def on_stream_event(
|
|
76
|
+
event_type: str, event_data: Any, agent_session_id: Optional[str] = None
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Emit streaming events from the agent.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
event_type: Type of the streaming event
|
|
82
|
+
event_data: Data associated with the event
|
|
83
|
+
agent_session_id: Optional session ID of the agent emitting the event
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
emit_event(
|
|
87
|
+
"stream_event",
|
|
88
|
+
{
|
|
89
|
+
"event_type": event_type,
|
|
90
|
+
"event_data": _sanitize_event_data(event_data),
|
|
91
|
+
"agent_session_id": agent_session_id,
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
logger.debug(f"Emitted stream_event: {event_type}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Failed to emit stream_event: {e}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def on_invoke_agent(*args: Any, **kwargs: Any) -> None:
|
|
100
|
+
"""Emit an event when an agent is invoked.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
*args: Positional arguments from the invoke_agent callback
|
|
104
|
+
**kwargs: Keyword arguments from the invoke_agent callback
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
# Extract relevant info from args/kwargs
|
|
108
|
+
agent_info = {
|
|
109
|
+
"agent_name": kwargs.get("agent_name") or (args[0] if args else None),
|
|
110
|
+
"session_id": kwargs.get("session_id"),
|
|
111
|
+
"prompt_preview": _truncate_string(
|
|
112
|
+
kwargs.get("prompt") or (args[1] if len(args) > 1 else None),
|
|
113
|
+
max_length=200,
|
|
114
|
+
),
|
|
115
|
+
}
|
|
116
|
+
emit_event("agent_invoked", agent_info)
|
|
117
|
+
logger.debug(f"Emitted agent_invoked: {agent_info.get('agent_name')}")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error(f"Failed to emit invoke_agent event: {e}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _sanitize_args(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
123
|
+
"""Sanitize tool arguments for safe emission.
|
|
124
|
+
|
|
125
|
+
Truncates large values and removes potentially sensitive data.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
args: The raw tool arguments
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Sanitized arguments safe for emission
|
|
132
|
+
"""
|
|
133
|
+
if not isinstance(args, dict):
|
|
134
|
+
return {}
|
|
135
|
+
|
|
136
|
+
sanitized: Dict[str, Any] = {}
|
|
137
|
+
for key, value in args.items():
|
|
138
|
+
if isinstance(value, str):
|
|
139
|
+
sanitized[key] = _truncate_string(value, max_length=500)
|
|
140
|
+
elif isinstance(value, (int, float, bool, type(None))):
|
|
141
|
+
sanitized[key] = value
|
|
142
|
+
elif isinstance(value, (list, dict)):
|
|
143
|
+
# Just indicate the type and length for complex types
|
|
144
|
+
sanitized[key] = f"<{type(value).__name__}[{len(value)}]>"
|
|
145
|
+
else:
|
|
146
|
+
sanitized[key] = f"<{type(value).__name__}>"
|
|
147
|
+
|
|
148
|
+
return sanitized
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _sanitize_event_data(data: Any) -> Any:
|
|
152
|
+
"""Sanitize event data for safe emission.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
data: The raw event data
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Sanitized data safe for emission
|
|
159
|
+
"""
|
|
160
|
+
if data is None:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
if isinstance(data, str):
|
|
164
|
+
return _truncate_string(data, max_length=1000)
|
|
165
|
+
|
|
166
|
+
if isinstance(data, (int, float, bool)):
|
|
167
|
+
return data
|
|
168
|
+
|
|
169
|
+
if isinstance(data, dict):
|
|
170
|
+
return {k: _sanitize_event_data(v) for k, v in list(data.items())[:20]}
|
|
171
|
+
|
|
172
|
+
if isinstance(data, (list, tuple)):
|
|
173
|
+
return [_sanitize_event_data(item) for item in data[:20]]
|
|
174
|
+
|
|
175
|
+
return f"<{type(data).__name__}>"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_successful_result(result: Any) -> bool:
|
|
179
|
+
"""Determine if a tool result indicates success.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
result: The tool result
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
True if the result appears successful
|
|
186
|
+
"""
|
|
187
|
+
if result is None:
|
|
188
|
+
return True # No result often means success
|
|
189
|
+
|
|
190
|
+
if isinstance(result, dict):
|
|
191
|
+
# Check for error indicators
|
|
192
|
+
if result.get("error"):
|
|
193
|
+
return False
|
|
194
|
+
if result.get("success") is False:
|
|
195
|
+
return False
|
|
196
|
+
return True
|
|
197
|
+
|
|
198
|
+
if isinstance(result, bool):
|
|
199
|
+
return result
|
|
200
|
+
|
|
201
|
+
return True # Default to success
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _summarize_result(result: Any) -> str:
|
|
205
|
+
"""Create a brief summary of a tool result.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
result: The tool result
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
A string summary of the result
|
|
212
|
+
"""
|
|
213
|
+
if result is None:
|
|
214
|
+
return "<no result>"
|
|
215
|
+
|
|
216
|
+
if isinstance(result, str):
|
|
217
|
+
return _truncate_string(result, max_length=200)
|
|
218
|
+
|
|
219
|
+
if isinstance(result, dict):
|
|
220
|
+
if "error" in result:
|
|
221
|
+
return f"Error: {_truncate_string(str(result['error']), max_length=100)}"
|
|
222
|
+
if "message" in result:
|
|
223
|
+
return _truncate_string(str(result["message"]), max_length=100)
|
|
224
|
+
return f"<dict with {len(result)} keys>"
|
|
225
|
+
|
|
226
|
+
if isinstance(result, (list, tuple)):
|
|
227
|
+
return f"<{type(result).__name__}[{len(result)}]>"
|
|
228
|
+
|
|
229
|
+
return _truncate_string(str(result), max_length=200)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _truncate_string(value: Any, max_length: int = 100) -> Optional[str]:
|
|
233
|
+
"""Truncate a string value if it exceeds max_length.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
value: The value to truncate (will be converted to str)
|
|
237
|
+
max_length: Maximum length before truncation
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Truncated string or None if value is None
|
|
241
|
+
"""
|
|
242
|
+
if value is None:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
s = str(value)
|
|
246
|
+
if len(s) > max_length:
|
|
247
|
+
return s[: max_length - 3] + "..."
|
|
248
|
+
return s
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def register() -> None:
|
|
252
|
+
"""Register all frontend emitter callbacks."""
|
|
253
|
+
register_callback("pre_tool_call", on_pre_tool_call)
|
|
254
|
+
register_callback("post_tool_call", on_post_tool_call)
|
|
255
|
+
register_callback("stream_event", on_stream_event)
|
|
256
|
+
register_callback("invoke_agent", on_invoke_agent)
|
|
257
|
+
logger.debug("Frontend emitter callbacks registered")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# Auto-register callbacks when this module is imported
|
|
261
|
+
register()
|
code_puppy/status_display.py
CHANGED
|
@@ -7,8 +7,6 @@ from rich.panel import Panel
|
|
|
7
7
|
from rich.spinner import Spinner
|
|
8
8
|
from rich.text import Text
|
|
9
9
|
|
|
10
|
-
from code_puppy.messaging import emit_info
|
|
11
|
-
|
|
12
10
|
# Global variable to track current token per second rate
|
|
13
11
|
CURRENT_TOKEN_RATE = 0.0
|
|
14
12
|
|
|
@@ -186,6 +184,9 @@ class StatusDisplay:
|
|
|
186
184
|
|
|
187
185
|
async def _update_display(self) -> None:
|
|
188
186
|
"""Update the display continuously while active using Rich Live display"""
|
|
187
|
+
# Lazy import to avoid circular dependency during module initialization
|
|
188
|
+
from code_puppy.messaging import emit_info
|
|
189
|
+
|
|
189
190
|
# Add a newline to ensure we're below the blue bar
|
|
190
191
|
emit_info("")
|
|
191
192
|
|
|
@@ -214,6 +215,9 @@ class StatusDisplay:
|
|
|
214
215
|
|
|
215
216
|
def stop(self) -> None:
|
|
216
217
|
"""Stop the status display"""
|
|
218
|
+
# Lazy import to avoid circular dependency during module initialization
|
|
219
|
+
from code_puppy.messaging import emit_info
|
|
220
|
+
|
|
217
221
|
if self.is_active:
|
|
218
222
|
self.is_active = False
|
|
219
223
|
if self.task:
|
code_puppy/tools/agent_tools.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# agent_tools.py
|
|
2
2
|
import asyncio
|
|
3
|
+
from functools import partial
|
|
3
4
|
import hashlib
|
|
4
5
|
import itertools
|
|
5
6
|
import json
|
|
@@ -28,12 +29,13 @@ from code_puppy.messaging import (
|
|
|
28
29
|
SubAgentResponseMessage,
|
|
29
30
|
emit_error,
|
|
30
31
|
emit_info,
|
|
32
|
+
emit_success,
|
|
31
33
|
get_message_bus,
|
|
32
34
|
get_session_context,
|
|
33
35
|
set_session_context,
|
|
34
36
|
)
|
|
35
|
-
from code_puppy.model_utils import is_claude_code_model
|
|
36
37
|
from code_puppy.tools.common import generate_group_id
|
|
38
|
+
from code_puppy.tools.subagent_context import subagent_context
|
|
37
39
|
|
|
38
40
|
# Set to track active subagent invocation tasks
|
|
39
41
|
_active_subagent_tasks: Set[asyncio.Task] = set()
|
|
@@ -413,6 +415,9 @@ def register_invoke_agent(agent):
|
|
|
413
415
|
session_id = f"{session_id}-{hash_suffix}"
|
|
414
416
|
# else: continuing existing session, use session_id as-is
|
|
415
417
|
|
|
418
|
+
# Lazy imports to avoid circular dependency
|
|
419
|
+
from code_puppy.agents.subagent_stream_handler import subagent_stream_handler
|
|
420
|
+
|
|
416
421
|
# Emit structured invocation message via MessageBus
|
|
417
422
|
bus = get_message_bus()
|
|
418
423
|
bus.emit(
|
|
@@ -486,9 +491,6 @@ def register_invoke_agent(agent):
|
|
|
486
491
|
manager = get_mcp_manager()
|
|
487
492
|
mcp_servers = manager.get_servers_for_agent()
|
|
488
493
|
|
|
489
|
-
# Import display function for non-streaming output
|
|
490
|
-
from code_puppy.tools.display import display_non_streamed_result
|
|
491
|
-
|
|
492
494
|
if get_use_dbos():
|
|
493
495
|
from pydantic_ai.durable_exec.dbos import DBOSAgent
|
|
494
496
|
|
|
@@ -543,24 +545,36 @@ def register_invoke_agent(agent):
|
|
|
543
545
|
# Pass the message_history from the session to continue the conversation
|
|
544
546
|
workflow_id = None # Track for potential cancellation
|
|
545
547
|
|
|
546
|
-
#
|
|
547
|
-
#
|
|
548
|
-
stream_handler =
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
548
|
+
# Always use subagent_stream_handler to silence output and update console manager
|
|
549
|
+
# This ensures all sub-agent output goes through the aggregated dashboard
|
|
550
|
+
stream_handler = partial(subagent_stream_handler, session_id=session_id)
|
|
551
|
+
|
|
552
|
+
# Wrap the agent run in subagent context for tracking
|
|
553
|
+
with subagent_context(agent_name):
|
|
554
|
+
if get_use_dbos():
|
|
555
|
+
# Generate a unique workflow ID for DBOS - ensures no collisions in back-to-back calls
|
|
556
|
+
workflow_id = _generate_dbos_workflow_id(group_id)
|
|
557
|
+
|
|
558
|
+
# Add MCP servers to the DBOS agent's toolsets
|
|
559
|
+
# (temp_agent is discarded after this invocation, so no need to restore)
|
|
560
|
+
if subagent_mcp_servers:
|
|
561
|
+
temp_agent._toolsets = (
|
|
562
|
+
temp_agent._toolsets + subagent_mcp_servers
|
|
563
|
+
)
|
|
562
564
|
|
|
563
|
-
|
|
565
|
+
with SetWorkflowID(workflow_id):
|
|
566
|
+
task = asyncio.create_task(
|
|
567
|
+
temp_agent.run(
|
|
568
|
+
prompt,
|
|
569
|
+
message_history=message_history,
|
|
570
|
+
usage_limits=UsageLimits(
|
|
571
|
+
request_limit=get_message_limit()
|
|
572
|
+
),
|
|
573
|
+
event_stream_handler=stream_handler,
|
|
574
|
+
)
|
|
575
|
+
)
|
|
576
|
+
_active_subagent_tasks.add(task)
|
|
577
|
+
else:
|
|
564
578
|
task = asyncio.create_task(
|
|
565
579
|
temp_agent.run(
|
|
566
580
|
prompt,
|
|
@@ -570,38 +584,18 @@ def register_invoke_agent(agent):
|
|
|
570
584
|
)
|
|
571
585
|
)
|
|
572
586
|
_active_subagent_tasks.add(task)
|
|
573
|
-
else:
|
|
574
|
-
task = asyncio.create_task(
|
|
575
|
-
temp_agent.run(
|
|
576
|
-
prompt,
|
|
577
|
-
message_history=message_history,
|
|
578
|
-
usage_limits=UsageLimits(request_limit=get_message_limit()),
|
|
579
|
-
event_stream_handler=stream_handler,
|
|
580
|
-
)
|
|
581
|
-
)
|
|
582
|
-
_active_subagent_tasks.add(task)
|
|
583
587
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
588
|
+
try:
|
|
589
|
+
result = await task
|
|
590
|
+
finally:
|
|
591
|
+
_active_subagent_tasks.discard(task)
|
|
592
|
+
if task.cancelled():
|
|
593
|
+
if get_use_dbos() and workflow_id:
|
|
594
|
+
DBOS.cancel_workflow(workflow_id)
|
|
591
595
|
|
|
592
596
|
# Extract the response from the result
|
|
593
597
|
response = result.output
|
|
594
598
|
|
|
595
|
-
# Display the response using non-streaming output
|
|
596
|
-
from code_puppy.agents.event_stream_handler import get_streaming_console
|
|
597
|
-
|
|
598
|
-
display_non_streamed_result(
|
|
599
|
-
content=response,
|
|
600
|
-
console=get_streaming_console(),
|
|
601
|
-
banner_text=f"\u2713 {agent_name.upper()} RESPONSE",
|
|
602
|
-
banner_name="subagent_response",
|
|
603
|
-
)
|
|
604
|
-
|
|
605
599
|
# Update the session history with the new messages from this interaction
|
|
606
600
|
# The result contains all_messages which includes the full conversation
|
|
607
601
|
updated_history = result.all_messages()
|
|
@@ -624,13 +618,23 @@ def register_invoke_agent(agent):
|
|
|
624
618
|
)
|
|
625
619
|
)
|
|
626
620
|
|
|
621
|
+
# Emit clean completion summary
|
|
622
|
+
emit_success(
|
|
623
|
+
f"✓ {agent_name} completed successfully", message_group=group_id
|
|
624
|
+
)
|
|
625
|
+
|
|
627
626
|
return AgentInvokeOutput(
|
|
628
627
|
response=response, agent_name=agent_name, session_id=session_id
|
|
629
628
|
)
|
|
630
629
|
|
|
631
|
-
except Exception:
|
|
630
|
+
except Exception as e:
|
|
631
|
+
# Emit clean failure summary
|
|
632
|
+
emit_error(f"✗ {agent_name} failed: {str(e)}", message_group=group_id)
|
|
633
|
+
|
|
634
|
+
# Full traceback for debugging
|
|
632
635
|
error_msg = f"Error invoking agent '{agent_name}': {traceback.format_exc()}"
|
|
633
636
|
emit_error(error_msg, message_group=group_id)
|
|
637
|
+
|
|
634
638
|
return AgentInvokeOutput(
|
|
635
639
|
response=None,
|
|
636
640
|
agent_name=agent_name,
|