File size: 6,810 Bytes
fcc734a
0d3d041
fcc734a
 
40853c6
fcc734a
 
40853c6
 
d22e6fd
c0c69f5
4e3db9c
d22e6fd
0d3d041
4e3db9c
fcc734a
 
0d3d041
 
 
fcc734a
a58e6a3
fcc734a
 
 
 
0d3d041
 
fcc734a
 
0d3d041
fcc734a
 
9bcfe23
 
 
 
 
fcc734a
 
0d3d041
8350b87
0d3d041
 
 
 
 
 
 
8350b87
 
 
fcc734a
 
 
 
 
 
 
 
 
 
 
0d3d041
fcc734a
0d3d041
fcc734a
9bcfe23
 
 
 
 
fcc734a
 
 
 
 
 
 
 
 
0d3d041
4e3db9c
0d3d041
 
 
 
 
 
 
 
 
 
4e3db9c
 
 
0d3d041
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a58e6a3
 
fcc734a
a58e6a3
0d3d041
d22e6fd
a58e6a3
8350b87
fcc734a
 
 
 
a58e6a3
2d52286
8350b87
2d52286
fcc734a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a58e6a3
fcc734a
 
 
 
 
0d3d041
 
 
 
 
fcc734a
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
"""
Command center backend - handles tool-based agent launching and direct tools
"""
import json
import logging
from typing import List, Dict

logger = logging.getLogger(__name__)

# Tool definitions derived from agent registry
from .agents import get_tools, get_agent_type_map, get_tool_arg
from .tools import DIRECT_TOOL_REGISTRY

# Combine agent-launch tools with direct tools
TOOLS = get_tools() + [t["schema"] for t in DIRECT_TOOL_REGISTRY.values()]

MAX_TURNS = 10  # Limit conversation turns in command center


def stream_command_center(client, model: str, messages: List[Dict], extra_params: dict = None, abort_event=None, files_root: str = None):
    """
    Stream command center responses with agent launching capabilities

    Yields:
        dict: Updates with type 'thinking', 'launch', 'done', or 'error'
    """
    from .agents import call_llm

    turns = 0
    done = False
    debug_call_number = 0

    while not done and turns < MAX_TURNS:
        # Check abort before each turn
        if abort_event and abort_event.is_set():
            yield {"type": "aborted"}
            return

        turns += 1

        # LLM call with retries and debug events
        response = None
        for event in call_llm(client, model, messages, tools=TOOLS, extra_params=extra_params, abort_event=abort_event, call_number=debug_call_number):
            if "_response" in event:
                response = event["_response"]
                debug_call_number = event["_call_number"]
            else:
                yield event
                if event.get("type") in ("error", "aborted"):
                    return

        if response is None:
            return

        # Get response
        assistant_message = response.choices[0].message
        content = assistant_message.content or ""
        tool_calls = assistant_message.tool_calls or []

        # Send thinking content if present
        if content.strip():
            yield {"type": "thinking", "content": content}

        # Handle tool calls (agent launches + direct tools)
        if tool_calls:
            has_launches = False
            for tool_call in tool_calls:
                # Check abort between tool calls
                if abort_event and abort_event.is_set():
                    yield {"type": "aborted"}
                    return

                function_name = tool_call.function.name

                # Parse arguments
                try:
                    args = json.loads(tool_call.function.arguments)
                except:
                    yield {"type": "error", "content": "Failed to parse tool arguments"}
                    return

                # --- Direct tools (execute synchronously) ---
                if function_name in DIRECT_TOOL_REGISTRY:
                    # Emit tool_start for frontend
                    yield {
                        "type": "tool_start",
                        "tool": function_name,
                        "args": args,
                        "tool_call_id": tool_call.id,
                        "arguments": tool_call.function.arguments,
                        "thinking": content,
                    }

                    # Execute the tool via registry
                    tool_entry = DIRECT_TOOL_REGISTRY[function_name]
                    result = tool_entry["execute"](args, {"files_root": files_root})

                    # Emit tool_result for frontend
                    yield {
                        "type": "tool_result",
                        "tool": function_name,
                        "tool_call_id": tool_call.id,
                        "result": result,
                        "response": result.get("content", ""),
                    }

                    # Add to message history so LLM can continue
                    messages.append({
                        "role": "assistant",
                        "content": content,
                        "tool_calls": [{
                            "id": tool_call.id,
                            "type": "function",
                            "function": {
                                "name": function_name,
                                "arguments": tool_call.function.arguments,
                            }
                        }]
                    })
                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": result.get("content", ""),
                    })
                    continue

                # --- Agent launch tools ---
                agent_type_map = get_agent_type_map()
                agent_type = agent_type_map.get(function_name)

                if agent_type:
                    has_launches = True
                    # Get the initial message using the registered arg name for this type
                    initial_message = args.get(get_tool_arg(agent_type)) or args.get("task") or args.get("message")
                    task_id = args.get("task_id", "")

                    # Send launch action to frontend
                    yield {
                        "type": "launch",
                        "agent_type": agent_type,
                        "initial_message": initial_message,
                        "task_id": task_id,
                        "tool_call_id": tool_call.id
                    }

                    # Add tool call to message history for context
                    messages.append({
                        "role": "assistant",
                        "content": content,
                        "tool_calls": [{
                            "id": tool_call.id,
                            "type": "function",
                            "function": {
                                "name": tool_call.function.name,
                                "arguments": tool_call.function.arguments,
                            }
                        }]
                    })

                    messages.append({
                        "role": "tool",
                        "tool_call_id": tool_call.id,
                        "content": f"Launched {agent_type} agent with task: {initial_message}"
                    })

                else:
                    yield {"type": "error", "content": f"Unknown tool: {function_name}"}
                    return

            # If any agent launches happened, stop and let agents run
            # If only direct tools, continue the loop so LLM can respond
            if has_launches:
                done = True
        else:
            # No tool calls - conversation complete
            messages.append({"role": "assistant", "content": content})
            done = True

    # Send done signal
    yield {"type": "done"}