amd-gaia 0.14.3__py3-none-any.whl → 0.15.1__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.
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/METADATA +223 -223
- amd_gaia-0.15.1.dist-info/RECORD +178 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/entry_points.txt +1 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/licenses/LICENSE.md +20 -20
- gaia/__init__.py +29 -29
- gaia/agents/__init__.py +19 -19
- gaia/agents/base/__init__.py +9 -9
- gaia/agents/base/agent.py +2177 -2177
- gaia/agents/base/api_agent.py +120 -120
- gaia/agents/base/console.py +1841 -1841
- gaia/agents/base/errors.py +237 -237
- gaia/agents/base/mcp_agent.py +86 -86
- gaia/agents/base/tools.py +83 -83
- gaia/agents/blender/agent.py +556 -556
- gaia/agents/blender/agent_simple.py +133 -135
- gaia/agents/blender/app.py +211 -211
- gaia/agents/blender/app_simple.py +41 -41
- gaia/agents/blender/core/__init__.py +16 -16
- gaia/agents/blender/core/materials.py +506 -506
- gaia/agents/blender/core/objects.py +316 -316
- gaia/agents/blender/core/rendering.py +225 -225
- gaia/agents/blender/core/scene.py +220 -220
- gaia/agents/blender/core/view.py +146 -146
- gaia/agents/chat/__init__.py +9 -9
- gaia/agents/chat/agent.py +835 -835
- gaia/agents/chat/app.py +1058 -1058
- gaia/agents/chat/session.py +508 -508
- gaia/agents/chat/tools/__init__.py +15 -15
- gaia/agents/chat/tools/file_tools.py +96 -96
- gaia/agents/chat/tools/rag_tools.py +1729 -1729
- gaia/agents/chat/tools/shell_tools.py +436 -436
- gaia/agents/code/__init__.py +7 -7
- gaia/agents/code/agent.py +549 -549
- gaia/agents/code/cli.py +377 -0
- gaia/agents/code/models.py +135 -135
- gaia/agents/code/orchestration/__init__.py +24 -24
- gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
- gaia/agents/code/orchestration/checklist_generator.py +713 -713
- gaia/agents/code/orchestration/factories/__init__.py +9 -9
- gaia/agents/code/orchestration/factories/base.py +63 -63
- gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
- gaia/agents/code/orchestration/factories/python_factory.py +106 -106
- gaia/agents/code/orchestration/orchestrator.py +841 -841
- gaia/agents/code/orchestration/project_analyzer.py +391 -391
- gaia/agents/code/orchestration/steps/__init__.py +67 -67
- gaia/agents/code/orchestration/steps/base.py +188 -188
- gaia/agents/code/orchestration/steps/error_handler.py +314 -314
- gaia/agents/code/orchestration/steps/nextjs.py +828 -828
- gaia/agents/code/orchestration/steps/python.py +307 -307
- gaia/agents/code/orchestration/template_catalog.py +469 -469
- gaia/agents/code/orchestration/workflows/__init__.py +14 -14
- gaia/agents/code/orchestration/workflows/base.py +80 -80
- gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
- gaia/agents/code/orchestration/workflows/python.py +94 -94
- gaia/agents/code/prompts/__init__.py +11 -11
- gaia/agents/code/prompts/base_prompt.py +77 -77
- gaia/agents/code/prompts/code_patterns.py +2036 -2036
- gaia/agents/code/prompts/nextjs_prompt.py +40 -40
- gaia/agents/code/prompts/python_prompt.py +109 -109
- gaia/agents/code/schema_inference.py +365 -365
- gaia/agents/code/system_prompt.py +41 -41
- gaia/agents/code/tools/__init__.py +42 -42
- gaia/agents/code/tools/cli_tools.py +1138 -1138
- gaia/agents/code/tools/code_formatting.py +319 -319
- gaia/agents/code/tools/code_tools.py +769 -769
- gaia/agents/code/tools/error_fixing.py +1347 -1347
- gaia/agents/code/tools/external_tools.py +180 -180
- gaia/agents/code/tools/file_io.py +845 -845
- gaia/agents/code/tools/prisma_tools.py +190 -190
- gaia/agents/code/tools/project_management.py +1016 -1016
- gaia/agents/code/tools/testing.py +321 -321
- gaia/agents/code/tools/typescript_tools.py +122 -122
- gaia/agents/code/tools/validation_parsing.py +461 -461
- gaia/agents/code/tools/validation_tools.py +806 -806
- gaia/agents/code/tools/web_dev_tools.py +1758 -1758
- gaia/agents/code/validators/__init__.py +16 -16
- gaia/agents/code/validators/antipattern_checker.py +241 -241
- gaia/agents/code/validators/ast_analyzer.py +197 -197
- gaia/agents/code/validators/requirements_validator.py +145 -145
- gaia/agents/code/validators/syntax_validator.py +171 -171
- gaia/agents/docker/__init__.py +7 -7
- gaia/agents/docker/agent.py +642 -642
- gaia/agents/emr/__init__.py +8 -8
- gaia/agents/emr/agent.py +1506 -1506
- gaia/agents/emr/cli.py +1322 -1322
- gaia/agents/emr/constants.py +475 -475
- gaia/agents/emr/dashboard/__init__.py +4 -4
- gaia/agents/emr/dashboard/server.py +1974 -1974
- gaia/agents/jira/__init__.py +11 -11
- gaia/agents/jira/agent.py +894 -894
- gaia/agents/jira/jql_templates.py +299 -299
- gaia/agents/routing/__init__.py +7 -7
- gaia/agents/routing/agent.py +567 -570
- gaia/agents/routing/system_prompt.py +75 -75
- gaia/agents/summarize/__init__.py +11 -0
- gaia/agents/summarize/agent.py +885 -0
- gaia/agents/summarize/prompts.py +129 -0
- gaia/api/__init__.py +23 -23
- gaia/api/agent_registry.py +238 -238
- gaia/api/app.py +305 -305
- gaia/api/openai_server.py +575 -575
- gaia/api/schemas.py +186 -186
- gaia/api/sse_handler.py +373 -373
- gaia/apps/__init__.py +4 -4
- gaia/apps/llm/__init__.py +6 -6
- gaia/apps/llm/app.py +173 -169
- gaia/apps/summarize/app.py +116 -633
- gaia/apps/summarize/html_viewer.py +133 -133
- gaia/apps/summarize/pdf_formatter.py +284 -284
- gaia/audio/__init__.py +2 -2
- gaia/audio/audio_client.py +439 -439
- gaia/audio/audio_recorder.py +269 -269
- gaia/audio/kokoro_tts.py +599 -599
- gaia/audio/whisper_asr.py +432 -432
- gaia/chat/__init__.py +16 -16
- gaia/chat/app.py +430 -430
- gaia/chat/prompts.py +522 -522
- gaia/chat/sdk.py +1228 -1225
- gaia/cli.py +5481 -5621
- gaia/database/__init__.py +10 -10
- gaia/database/agent.py +176 -176
- gaia/database/mixin.py +290 -290
- gaia/database/testing.py +64 -64
- gaia/eval/batch_experiment.py +2332 -2332
- gaia/eval/claude.py +542 -542
- gaia/eval/config.py +37 -37
- gaia/eval/email_generator.py +512 -512
- gaia/eval/eval.py +3179 -3179
- gaia/eval/groundtruth.py +1130 -1130
- gaia/eval/transcript_generator.py +582 -582
- gaia/eval/webapp/README.md +167 -167
- gaia/eval/webapp/package-lock.json +875 -875
- gaia/eval/webapp/package.json +20 -20
- gaia/eval/webapp/public/app.js +3402 -3402
- gaia/eval/webapp/public/index.html +87 -87
- gaia/eval/webapp/public/styles.css +3661 -3661
- gaia/eval/webapp/server.js +415 -415
- gaia/eval/webapp/test-setup.js +72 -72
- gaia/llm/__init__.py +9 -2
- gaia/llm/base_client.py +60 -0
- gaia/llm/exceptions.py +12 -0
- gaia/llm/factory.py +70 -0
- gaia/llm/lemonade_client.py +3236 -3221
- gaia/llm/lemonade_manager.py +294 -294
- gaia/llm/providers/__init__.py +9 -0
- gaia/llm/providers/claude.py +108 -0
- gaia/llm/providers/lemonade.py +120 -0
- gaia/llm/providers/openai_provider.py +79 -0
- gaia/llm/vlm_client.py +382 -382
- gaia/logger.py +189 -189
- gaia/mcp/agent_mcp_server.py +245 -245
- gaia/mcp/blender_mcp_client.py +138 -138
- gaia/mcp/blender_mcp_server.py +648 -648
- gaia/mcp/context7_cache.py +332 -332
- gaia/mcp/external_services.py +518 -518
- gaia/mcp/mcp_bridge.py +811 -550
- gaia/mcp/servers/__init__.py +6 -6
- gaia/mcp/servers/docker_mcp.py +83 -83
- gaia/perf_analysis.py +361 -0
- gaia/rag/__init__.py +10 -10
- gaia/rag/app.py +293 -293
- gaia/rag/demo.py +304 -304
- gaia/rag/pdf_utils.py +235 -235
- gaia/rag/sdk.py +2194 -2194
- gaia/security.py +163 -163
- gaia/talk/app.py +289 -289
- gaia/talk/sdk.py +538 -538
- gaia/testing/__init__.py +87 -87
- gaia/testing/assertions.py +330 -330
- gaia/testing/fixtures.py +333 -333
- gaia/testing/mocks.py +493 -493
- gaia/util.py +46 -46
- gaia/utils/__init__.py +33 -33
- gaia/utils/file_watcher.py +675 -675
- gaia/utils/parsing.py +223 -223
- gaia/version.py +100 -100
- amd_gaia-0.14.3.dist-info/RECORD +0 -168
- gaia/agents/code/app.py +0 -266
- gaia/llm/llm_client.py +0 -729
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/WHEEL +0 -0
- {amd_gaia-0.14.3.dist-info → amd_gaia-0.15.1.dist-info}/top_level.txt +0 -0
gaia/api/sse_handler.py
CHANGED
|
@@ -1,373 +1,373 @@
|
|
|
1
|
-
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
"""
|
|
4
|
-
SSE Output Handler for API streaming.
|
|
5
|
-
|
|
6
|
-
Converts agent output into Server-Sent Events format for API clients.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
import time
|
|
11
|
-
from collections import deque
|
|
12
|
-
from typing import Any, Dict, List
|
|
13
|
-
|
|
14
|
-
from gaia.agents.base.console import OutputHandler
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class SSEOutputHandler(OutputHandler):
|
|
18
|
-
"""
|
|
19
|
-
Output handler for Server-Sent Events (SSE) streaming to API clients.
|
|
20
|
-
|
|
21
|
-
Formats agent outputs as SSE-compatible JSON chunks that can be
|
|
22
|
-
streamed to API clients (e.g., VSCode extension).
|
|
23
|
-
|
|
24
|
-
Each output is converted to a dictionary and added to a queue
|
|
25
|
-
that can be consumed by the API server.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
debug_mode: If True, include verbose event details. If False, only stream
|
|
29
|
-
clean, user-friendly status updates.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
def __init__(self, debug_mode: bool = False):
|
|
33
|
-
"""Initialize the SSE output handler.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
debug_mode: Enable verbose event streaming for debugging
|
|
37
|
-
"""
|
|
38
|
-
self.queue = deque()
|
|
39
|
-
self.streaming_buffer = "" # Maintain compatibility
|
|
40
|
-
self.debug_mode = debug_mode
|
|
41
|
-
self.current_step = 0
|
|
42
|
-
self.total_steps = 0
|
|
43
|
-
|
|
44
|
-
def _add_event(self, event_type: str, data: Dict[str, Any]):
|
|
45
|
-
"""
|
|
46
|
-
Add an event to the output queue.
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
event_type: Type of event (thinking, tool_call, etc.)
|
|
50
|
-
data: Event data to send
|
|
51
|
-
"""
|
|
52
|
-
self.queue.append({"type": event_type, "data": data, "timestamp": time.time()})
|
|
53
|
-
|
|
54
|
-
def should_stream_as_content(self, event_type: str) -> bool:
|
|
55
|
-
"""
|
|
56
|
-
Determine if an event should be streamed as content to the client.
|
|
57
|
-
|
|
58
|
-
In normal mode: Only stream key status updates and final answers
|
|
59
|
-
In debug mode: Stream all events
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
event_type: Type of event
|
|
63
|
-
|
|
64
|
-
Returns:
|
|
65
|
-
True if event should be streamed as content
|
|
66
|
-
"""
|
|
67
|
-
if self.debug_mode:
|
|
68
|
-
# Debug mode: stream everything
|
|
69
|
-
return True
|
|
70
|
-
|
|
71
|
-
# Normal mode: only stream these event types
|
|
72
|
-
# Note: Errors are excluded - they're internal agent messages
|
|
73
|
-
streamable_events = {
|
|
74
|
-
"processing_start",
|
|
75
|
-
"step_header",
|
|
76
|
-
"state",
|
|
77
|
-
"tool_usage",
|
|
78
|
-
"file_preview_start",
|
|
79
|
-
"file_preview_complete",
|
|
80
|
-
"final_answer",
|
|
81
|
-
"warning", # Keep warnings for important user feedback
|
|
82
|
-
"success", # Success messages for completed operations
|
|
83
|
-
"diff", # Code diff notifications
|
|
84
|
-
"completion",
|
|
85
|
-
"checklist", # Checklist progress from orchestrator
|
|
86
|
-
"checklist_reasoning", # Checklist reasoning (debug info)
|
|
87
|
-
"message", # Generic messages from print() calls
|
|
88
|
-
"agent_selected", # Agent routing selection notification
|
|
89
|
-
}
|
|
90
|
-
return event_type in streamable_events
|
|
91
|
-
|
|
92
|
-
# === Core Progress/State Methods (Required) ===
|
|
93
|
-
|
|
94
|
-
def print_processing_start(self, query: str, max_steps: int):
|
|
95
|
-
"""Print processing start message."""
|
|
96
|
-
self.total_steps = max_steps
|
|
97
|
-
self._add_event("processing_start", {"query": query, "max_steps": max_steps})
|
|
98
|
-
|
|
99
|
-
def print_step_header(self, step_num: int, step_limit: int):
|
|
100
|
-
"""Print step header."""
|
|
101
|
-
self.current_step = step_num
|
|
102
|
-
self.total_steps = step_limit
|
|
103
|
-
self._add_event("step_header", {"step": step_num, "step_limit": step_limit})
|
|
104
|
-
|
|
105
|
-
def print_state_info(self, state_message: str):
|
|
106
|
-
"""Print current execution state."""
|
|
107
|
-
self._add_event("state", {"message": state_message})
|
|
108
|
-
|
|
109
|
-
def print_thought(self, thought: str):
|
|
110
|
-
"""Print agent's reasoning/thought."""
|
|
111
|
-
self._add_event("thought", {"message": thought})
|
|
112
|
-
|
|
113
|
-
def print_goal(self, goal: str):
|
|
114
|
-
"""Print agent's current goal."""
|
|
115
|
-
self._add_event("goal", {"message": goal})
|
|
116
|
-
|
|
117
|
-
def print_plan(self, plan: List[Any], current_step: int = None):
|
|
118
|
-
"""Print agent's plan with optional current step highlight."""
|
|
119
|
-
self._add_event("plan", {"plan": plan, "current_step": current_step})
|
|
120
|
-
|
|
121
|
-
# === Tool Execution Methods (Required) ===
|
|
122
|
-
|
|
123
|
-
def print_tool_usage(self, tool_name: str):
|
|
124
|
-
"""Print tool being called."""
|
|
125
|
-
self._add_event("tool_usage", {"tool_name": tool_name})
|
|
126
|
-
|
|
127
|
-
def print_tool_complete(self):
|
|
128
|
-
"""Print tool completion."""
|
|
129
|
-
self._add_event("tool_complete", {})
|
|
130
|
-
|
|
131
|
-
def pretty_print_json(self, data: Dict[str, Any], title: str = None):
|
|
132
|
-
"""Print JSON data (tool args/results)."""
|
|
133
|
-
self._add_event("json", {"data": data, "title": title})
|
|
134
|
-
|
|
135
|
-
# === Status Messages (Required) ===
|
|
136
|
-
|
|
137
|
-
def print_error(self, error_message: str):
|
|
138
|
-
"""Print error message."""
|
|
139
|
-
self._add_event("error", {"message": error_message})
|
|
140
|
-
|
|
141
|
-
def print_warning(self, warning_message: str):
|
|
142
|
-
"""Print warning message."""
|
|
143
|
-
self._add_event("warning", {"message": warning_message})
|
|
144
|
-
|
|
145
|
-
def print_info(self, message: str):
|
|
146
|
-
"""Print informational message."""
|
|
147
|
-
self._add_event("info", {"message": message})
|
|
148
|
-
|
|
149
|
-
def print_success(self, message: str):
|
|
150
|
-
"""Print success message."""
|
|
151
|
-
self._add_event("success", {"message": message})
|
|
152
|
-
|
|
153
|
-
def print_diff(self, diff: str, filename: str):
|
|
154
|
-
"""Print code diff."""
|
|
155
|
-
self._add_event("diff", {"diff": diff, "filename": filename})
|
|
156
|
-
|
|
157
|
-
# === Progress Indicators (Required) ===
|
|
158
|
-
|
|
159
|
-
def start_progress(self, message: str):
|
|
160
|
-
"""Start progress indicator."""
|
|
161
|
-
self._add_event("progress_start", {"message": message})
|
|
162
|
-
|
|
163
|
-
def stop_progress(self):
|
|
164
|
-
"""Stop progress indicator."""
|
|
165
|
-
self._add_event("progress_stop", {})
|
|
166
|
-
|
|
167
|
-
# === Completion Methods (Required) ===
|
|
168
|
-
|
|
169
|
-
def print_final_answer(self, answer: str):
|
|
170
|
-
"""Print final answer/result."""
|
|
171
|
-
self._add_event("final_answer", {"answer": answer})
|
|
172
|
-
|
|
173
|
-
def print_repeated_tool_warning(self):
|
|
174
|
-
"""Print warning about repeated tool calls (loop detection)."""
|
|
175
|
-
self._add_event(
|
|
176
|
-
"warning",
|
|
177
|
-
{"message": "Repeated tool call detected - possible infinite loop"},
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
def print_completion(self, steps_taken: int, steps_limit: int):
|
|
181
|
-
"""Print completion summary."""
|
|
182
|
-
# Infer status from steps_taken vs steps_limit
|
|
183
|
-
status = "success" if steps_taken < steps_limit else "incomplete"
|
|
184
|
-
self._add_event(
|
|
185
|
-
"completion",
|
|
186
|
-
{"steps_taken": steps_taken, "steps_limit": steps_limit, "status": status},
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
# === File Preview Methods (Required for Code Agent) ===
|
|
190
|
-
|
|
191
|
-
def start_file_preview(
|
|
192
|
-
self, filename: str, max_lines: int = None, title_prefix: str = ""
|
|
193
|
-
):
|
|
194
|
-
"""Start file preview display."""
|
|
195
|
-
self._add_event(
|
|
196
|
-
"file_preview_start",
|
|
197
|
-
{
|
|
198
|
-
"filename": filename,
|
|
199
|
-
"max_lines": max_lines,
|
|
200
|
-
"title_prefix": title_prefix,
|
|
201
|
-
},
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
def update_file_preview(self, content_chunk: str):
|
|
205
|
-
"""Update file preview with content."""
|
|
206
|
-
self._add_event("file_preview_update", {"content": content_chunk})
|
|
207
|
-
|
|
208
|
-
def stop_file_preview(self):
|
|
209
|
-
"""Stop file preview display."""
|
|
210
|
-
self._add_event("file_preview_complete", {})
|
|
211
|
-
|
|
212
|
-
def print_step_paused(self, description: str):
|
|
213
|
-
"""Print step paused message."""
|
|
214
|
-
self._add_event("step_paused", {"description": description})
|
|
215
|
-
|
|
216
|
-
def print_command_executing(self, command: str):
|
|
217
|
-
"""Print command executing message."""
|
|
218
|
-
self._add_event("command_executing", {"command": command})
|
|
219
|
-
|
|
220
|
-
def print_agent_selected(self, agent_name: str, language: str, project_type: str):
|
|
221
|
-
"""Print agent selected message."""
|
|
222
|
-
self._add_event(
|
|
223
|
-
"agent_selected",
|
|
224
|
-
{
|
|
225
|
-
"agent_name": agent_name,
|
|
226
|
-
"language": language,
|
|
227
|
-
"project_type": project_type,
|
|
228
|
-
},
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
def print(self, *args, **_kwargs):
|
|
232
|
-
"""
|
|
233
|
-
Handle generic print() calls - queue as message event.
|
|
234
|
-
|
|
235
|
-
This method captures print() calls from agent code and queues them
|
|
236
|
-
as SSE events so they can be streamed to the client.
|
|
237
|
-
|
|
238
|
-
Args:
|
|
239
|
-
*args: Values to print (will be joined with spaces)
|
|
240
|
-
**_kwargs: Ignored (for compatibility with built-in print)
|
|
241
|
-
"""
|
|
242
|
-
# Join args with spaces, converting to strings
|
|
243
|
-
message = " ".join(str(arg) for arg in args)
|
|
244
|
-
if message.strip():
|
|
245
|
-
self._add_event("message", {"text": message})
|
|
246
|
-
|
|
247
|
-
# === Checklist Methods (Required for Code Agent Orchestration) ===
|
|
248
|
-
|
|
249
|
-
def print_checklist(self, items: List[Any], current_idx: int) -> None:
|
|
250
|
-
"""Print checklist items with current progress."""
|
|
251
|
-
self._add_event(
|
|
252
|
-
"checklist",
|
|
253
|
-
{
|
|
254
|
-
"items": [str(item) for item in items],
|
|
255
|
-
"current_index": current_idx,
|
|
256
|
-
},
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
def print_checklist_reasoning(self, reasoning: str) -> None:
|
|
260
|
-
"""Print checklist reasoning/planning."""
|
|
261
|
-
self._add_event("checklist_reasoning", {"reasoning": reasoning})
|
|
262
|
-
|
|
263
|
-
def get_events(self) -> List[Dict[str, Any]]:
|
|
264
|
-
"""
|
|
265
|
-
Get all queued events and clear the queue.
|
|
266
|
-
|
|
267
|
-
Returns:
|
|
268
|
-
List of event dictionaries
|
|
269
|
-
"""
|
|
270
|
-
events = list(self.queue)
|
|
271
|
-
self.queue.clear()
|
|
272
|
-
return events
|
|
273
|
-
|
|
274
|
-
def has_events(self) -> bool:
|
|
275
|
-
"""Check if there are any queued events."""
|
|
276
|
-
return len(self.queue) > 0
|
|
277
|
-
|
|
278
|
-
def format_event_as_content(self, event: Dict[str, Any]) -> str:
|
|
279
|
-
"""
|
|
280
|
-
Format an event as clean content text for streaming.
|
|
281
|
-
|
|
282
|
-
Sends clean, minimal text that is OpenAI-compatible.
|
|
283
|
-
The VSCode extension will add formatting (emojis, separators) for display.
|
|
284
|
-
|
|
285
|
-
Args:
|
|
286
|
-
event: Event dictionary with type, data, and timestamp
|
|
287
|
-
|
|
288
|
-
Returns:
|
|
289
|
-
Clean content string suitable for any OpenAI-compatible client
|
|
290
|
-
"""
|
|
291
|
-
event_type = event.get("type", "message")
|
|
292
|
-
data = event.get("data", {})
|
|
293
|
-
|
|
294
|
-
if self.debug_mode:
|
|
295
|
-
# Debug mode: Include event type and data in compact format
|
|
296
|
-
return f"[{event_type}] {json.dumps(data, separators=(',', ':'))}\n"
|
|
297
|
-
|
|
298
|
-
# Normal mode: Clean, minimal status messages (no emojis/separators here)
|
|
299
|
-
# The VSCode extension will add formatting for better UX
|
|
300
|
-
|
|
301
|
-
if event_type == "processing_start":
|
|
302
|
-
return "Processing request...\n"
|
|
303
|
-
|
|
304
|
-
elif event_type == "step_header":
|
|
305
|
-
# Don't emit step headers in normal mode - too verbose
|
|
306
|
-
# Debug mode will show them via the debug format above
|
|
307
|
-
return ""
|
|
308
|
-
|
|
309
|
-
elif event_type == "state":
|
|
310
|
-
message = data.get("message", "")
|
|
311
|
-
return f"{message}\n"
|
|
312
|
-
|
|
313
|
-
elif event_type == "tool_usage":
|
|
314
|
-
tool_name = data.get("tool_name", "unknown")
|
|
315
|
-
return f"Using tool: {tool_name}\n"
|
|
316
|
-
|
|
317
|
-
elif event_type == "file_preview_start":
|
|
318
|
-
filename = data.get("filename", "unknown")
|
|
319
|
-
return f"Previewing file: {filename}\n"
|
|
320
|
-
|
|
321
|
-
elif event_type == "file_preview_update":
|
|
322
|
-
# Skip content chunks to avoid clutter
|
|
323
|
-
return ""
|
|
324
|
-
|
|
325
|
-
elif event_type == "file_preview_complete":
|
|
326
|
-
return "File preview complete\n"
|
|
327
|
-
|
|
328
|
-
elif event_type == "final_answer":
|
|
329
|
-
# Final answer is the actual response - send as-is
|
|
330
|
-
answer = data.get("answer", "")
|
|
331
|
-
return f"{answer}\n"
|
|
332
|
-
|
|
333
|
-
elif event_type == "error":
|
|
334
|
-
message = data.get("message", "An error occurred")
|
|
335
|
-
return f"Error: {message}\n"
|
|
336
|
-
|
|
337
|
-
elif event_type == "warning":
|
|
338
|
-
message = data.get("message", "")
|
|
339
|
-
return f"Warning: {message}\n"
|
|
340
|
-
|
|
341
|
-
elif event_type == "success":
|
|
342
|
-
message = data.get("message", "")
|
|
343
|
-
return f"{message}\n"
|
|
344
|
-
|
|
345
|
-
elif event_type == "diff":
|
|
346
|
-
filename = data.get("filename", "unknown")
|
|
347
|
-
return f"Modified file: {filename}\n"
|
|
348
|
-
|
|
349
|
-
elif event_type == "completion":
|
|
350
|
-
steps_taken = data.get("steps_taken", 0)
|
|
351
|
-
return f"Completed in {steps_taken} steps\n"
|
|
352
|
-
|
|
353
|
-
elif event_type == "checklist":
|
|
354
|
-
items = data.get("items", [])
|
|
355
|
-
current_idx = data.get("current_index", 0)
|
|
356
|
-
return f"Progress: step {current_idx + 1} of {len(items)}\n"
|
|
357
|
-
|
|
358
|
-
elif event_type == "checklist_reasoning":
|
|
359
|
-
# Skip reasoning in non-debug mode (too verbose)
|
|
360
|
-
return ""
|
|
361
|
-
|
|
362
|
-
elif event_type == "message":
|
|
363
|
-
text = data.get("text", "")
|
|
364
|
-
return f"{text}\n" if text else ""
|
|
365
|
-
|
|
366
|
-
elif event_type == "agent_selected":
|
|
367
|
-
agent_name = data.get("agent_name", "unknown")
|
|
368
|
-
language = data.get("language", "")
|
|
369
|
-
project_type = data.get("project_type", "")
|
|
370
|
-
return f"Agent: {agent_name} ({language}/{project_type})\n"
|
|
371
|
-
|
|
372
|
-
# For other events in normal mode, don't stream them
|
|
373
|
-
return ""
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
SSE Output Handler for API streaming.
|
|
5
|
+
|
|
6
|
+
Converts agent output into Server-Sent Events format for API clients.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import time
|
|
11
|
+
from collections import deque
|
|
12
|
+
from typing import Any, Dict, List
|
|
13
|
+
|
|
14
|
+
from gaia.agents.base.console import OutputHandler
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SSEOutputHandler(OutputHandler):
|
|
18
|
+
"""
|
|
19
|
+
Output handler for Server-Sent Events (SSE) streaming to API clients.
|
|
20
|
+
|
|
21
|
+
Formats agent outputs as SSE-compatible JSON chunks that can be
|
|
22
|
+
streamed to API clients (e.g., VSCode extension).
|
|
23
|
+
|
|
24
|
+
Each output is converted to a dictionary and added to a queue
|
|
25
|
+
that can be consumed by the API server.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
debug_mode: If True, include verbose event details. If False, only stream
|
|
29
|
+
clean, user-friendly status updates.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, debug_mode: bool = False):
|
|
33
|
+
"""Initialize the SSE output handler.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
debug_mode: Enable verbose event streaming for debugging
|
|
37
|
+
"""
|
|
38
|
+
self.queue = deque()
|
|
39
|
+
self.streaming_buffer = "" # Maintain compatibility
|
|
40
|
+
self.debug_mode = debug_mode
|
|
41
|
+
self.current_step = 0
|
|
42
|
+
self.total_steps = 0
|
|
43
|
+
|
|
44
|
+
def _add_event(self, event_type: str, data: Dict[str, Any]):
|
|
45
|
+
"""
|
|
46
|
+
Add an event to the output queue.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
event_type: Type of event (thinking, tool_call, etc.)
|
|
50
|
+
data: Event data to send
|
|
51
|
+
"""
|
|
52
|
+
self.queue.append({"type": event_type, "data": data, "timestamp": time.time()})
|
|
53
|
+
|
|
54
|
+
def should_stream_as_content(self, event_type: str) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Determine if an event should be streamed as content to the client.
|
|
57
|
+
|
|
58
|
+
In normal mode: Only stream key status updates and final answers
|
|
59
|
+
In debug mode: Stream all events
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
event_type: Type of event
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if event should be streamed as content
|
|
66
|
+
"""
|
|
67
|
+
if self.debug_mode:
|
|
68
|
+
# Debug mode: stream everything
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
# Normal mode: only stream these event types
|
|
72
|
+
# Note: Errors are excluded - they're internal agent messages
|
|
73
|
+
streamable_events = {
|
|
74
|
+
"processing_start",
|
|
75
|
+
"step_header",
|
|
76
|
+
"state",
|
|
77
|
+
"tool_usage",
|
|
78
|
+
"file_preview_start",
|
|
79
|
+
"file_preview_complete",
|
|
80
|
+
"final_answer",
|
|
81
|
+
"warning", # Keep warnings for important user feedback
|
|
82
|
+
"success", # Success messages for completed operations
|
|
83
|
+
"diff", # Code diff notifications
|
|
84
|
+
"completion",
|
|
85
|
+
"checklist", # Checklist progress from orchestrator
|
|
86
|
+
"checklist_reasoning", # Checklist reasoning (debug info)
|
|
87
|
+
"message", # Generic messages from print() calls
|
|
88
|
+
"agent_selected", # Agent routing selection notification
|
|
89
|
+
}
|
|
90
|
+
return event_type in streamable_events
|
|
91
|
+
|
|
92
|
+
# === Core Progress/State Methods (Required) ===
|
|
93
|
+
|
|
94
|
+
def print_processing_start(self, query: str, max_steps: int):
|
|
95
|
+
"""Print processing start message."""
|
|
96
|
+
self.total_steps = max_steps
|
|
97
|
+
self._add_event("processing_start", {"query": query, "max_steps": max_steps})
|
|
98
|
+
|
|
99
|
+
def print_step_header(self, step_num: int, step_limit: int):
|
|
100
|
+
"""Print step header."""
|
|
101
|
+
self.current_step = step_num
|
|
102
|
+
self.total_steps = step_limit
|
|
103
|
+
self._add_event("step_header", {"step": step_num, "step_limit": step_limit})
|
|
104
|
+
|
|
105
|
+
def print_state_info(self, state_message: str):
|
|
106
|
+
"""Print current execution state."""
|
|
107
|
+
self._add_event("state", {"message": state_message})
|
|
108
|
+
|
|
109
|
+
def print_thought(self, thought: str):
|
|
110
|
+
"""Print agent's reasoning/thought."""
|
|
111
|
+
self._add_event("thought", {"message": thought})
|
|
112
|
+
|
|
113
|
+
def print_goal(self, goal: str):
|
|
114
|
+
"""Print agent's current goal."""
|
|
115
|
+
self._add_event("goal", {"message": goal})
|
|
116
|
+
|
|
117
|
+
def print_plan(self, plan: List[Any], current_step: int = None):
|
|
118
|
+
"""Print agent's plan with optional current step highlight."""
|
|
119
|
+
self._add_event("plan", {"plan": plan, "current_step": current_step})
|
|
120
|
+
|
|
121
|
+
# === Tool Execution Methods (Required) ===
|
|
122
|
+
|
|
123
|
+
def print_tool_usage(self, tool_name: str):
|
|
124
|
+
"""Print tool being called."""
|
|
125
|
+
self._add_event("tool_usage", {"tool_name": tool_name})
|
|
126
|
+
|
|
127
|
+
def print_tool_complete(self):
|
|
128
|
+
"""Print tool completion."""
|
|
129
|
+
self._add_event("tool_complete", {})
|
|
130
|
+
|
|
131
|
+
def pretty_print_json(self, data: Dict[str, Any], title: str = None):
|
|
132
|
+
"""Print JSON data (tool args/results)."""
|
|
133
|
+
self._add_event("json", {"data": data, "title": title})
|
|
134
|
+
|
|
135
|
+
# === Status Messages (Required) ===
|
|
136
|
+
|
|
137
|
+
def print_error(self, error_message: str):
|
|
138
|
+
"""Print error message."""
|
|
139
|
+
self._add_event("error", {"message": error_message})
|
|
140
|
+
|
|
141
|
+
def print_warning(self, warning_message: str):
|
|
142
|
+
"""Print warning message."""
|
|
143
|
+
self._add_event("warning", {"message": warning_message})
|
|
144
|
+
|
|
145
|
+
def print_info(self, message: str):
|
|
146
|
+
"""Print informational message."""
|
|
147
|
+
self._add_event("info", {"message": message})
|
|
148
|
+
|
|
149
|
+
def print_success(self, message: str):
|
|
150
|
+
"""Print success message."""
|
|
151
|
+
self._add_event("success", {"message": message})
|
|
152
|
+
|
|
153
|
+
def print_diff(self, diff: str, filename: str):
|
|
154
|
+
"""Print code diff."""
|
|
155
|
+
self._add_event("diff", {"diff": diff, "filename": filename})
|
|
156
|
+
|
|
157
|
+
# === Progress Indicators (Required) ===
|
|
158
|
+
|
|
159
|
+
def start_progress(self, message: str):
|
|
160
|
+
"""Start progress indicator."""
|
|
161
|
+
self._add_event("progress_start", {"message": message})
|
|
162
|
+
|
|
163
|
+
def stop_progress(self):
|
|
164
|
+
"""Stop progress indicator."""
|
|
165
|
+
self._add_event("progress_stop", {})
|
|
166
|
+
|
|
167
|
+
# === Completion Methods (Required) ===
|
|
168
|
+
|
|
169
|
+
def print_final_answer(self, answer: str):
|
|
170
|
+
"""Print final answer/result."""
|
|
171
|
+
self._add_event("final_answer", {"answer": answer})
|
|
172
|
+
|
|
173
|
+
def print_repeated_tool_warning(self):
|
|
174
|
+
"""Print warning about repeated tool calls (loop detection)."""
|
|
175
|
+
self._add_event(
|
|
176
|
+
"warning",
|
|
177
|
+
{"message": "Repeated tool call detected - possible infinite loop"},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def print_completion(self, steps_taken: int, steps_limit: int):
|
|
181
|
+
"""Print completion summary."""
|
|
182
|
+
# Infer status from steps_taken vs steps_limit
|
|
183
|
+
status = "success" if steps_taken < steps_limit else "incomplete"
|
|
184
|
+
self._add_event(
|
|
185
|
+
"completion",
|
|
186
|
+
{"steps_taken": steps_taken, "steps_limit": steps_limit, "status": status},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# === File Preview Methods (Required for Code Agent) ===
|
|
190
|
+
|
|
191
|
+
def start_file_preview(
|
|
192
|
+
self, filename: str, max_lines: int = None, title_prefix: str = ""
|
|
193
|
+
):
|
|
194
|
+
"""Start file preview display."""
|
|
195
|
+
self._add_event(
|
|
196
|
+
"file_preview_start",
|
|
197
|
+
{
|
|
198
|
+
"filename": filename,
|
|
199
|
+
"max_lines": max_lines,
|
|
200
|
+
"title_prefix": title_prefix,
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def update_file_preview(self, content_chunk: str):
|
|
205
|
+
"""Update file preview with content."""
|
|
206
|
+
self._add_event("file_preview_update", {"content": content_chunk})
|
|
207
|
+
|
|
208
|
+
def stop_file_preview(self):
|
|
209
|
+
"""Stop file preview display."""
|
|
210
|
+
self._add_event("file_preview_complete", {})
|
|
211
|
+
|
|
212
|
+
def print_step_paused(self, description: str):
|
|
213
|
+
"""Print step paused message."""
|
|
214
|
+
self._add_event("step_paused", {"description": description})
|
|
215
|
+
|
|
216
|
+
def print_command_executing(self, command: str):
|
|
217
|
+
"""Print command executing message."""
|
|
218
|
+
self._add_event("command_executing", {"command": command})
|
|
219
|
+
|
|
220
|
+
def print_agent_selected(self, agent_name: str, language: str, project_type: str):
|
|
221
|
+
"""Print agent selected message."""
|
|
222
|
+
self._add_event(
|
|
223
|
+
"agent_selected",
|
|
224
|
+
{
|
|
225
|
+
"agent_name": agent_name,
|
|
226
|
+
"language": language,
|
|
227
|
+
"project_type": project_type,
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def print(self, *args, **_kwargs):
|
|
232
|
+
"""
|
|
233
|
+
Handle generic print() calls - queue as message event.
|
|
234
|
+
|
|
235
|
+
This method captures print() calls from agent code and queues them
|
|
236
|
+
as SSE events so they can be streamed to the client.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
*args: Values to print (will be joined with spaces)
|
|
240
|
+
**_kwargs: Ignored (for compatibility with built-in print)
|
|
241
|
+
"""
|
|
242
|
+
# Join args with spaces, converting to strings
|
|
243
|
+
message = " ".join(str(arg) for arg in args)
|
|
244
|
+
if message.strip():
|
|
245
|
+
self._add_event("message", {"text": message})
|
|
246
|
+
|
|
247
|
+
# === Checklist Methods (Required for Code Agent Orchestration) ===
|
|
248
|
+
|
|
249
|
+
def print_checklist(self, items: List[Any], current_idx: int) -> None:
|
|
250
|
+
"""Print checklist items with current progress."""
|
|
251
|
+
self._add_event(
|
|
252
|
+
"checklist",
|
|
253
|
+
{
|
|
254
|
+
"items": [str(item) for item in items],
|
|
255
|
+
"current_index": current_idx,
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def print_checklist_reasoning(self, reasoning: str) -> None:
|
|
260
|
+
"""Print checklist reasoning/planning."""
|
|
261
|
+
self._add_event("checklist_reasoning", {"reasoning": reasoning})
|
|
262
|
+
|
|
263
|
+
def get_events(self) -> List[Dict[str, Any]]:
|
|
264
|
+
"""
|
|
265
|
+
Get all queued events and clear the queue.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
List of event dictionaries
|
|
269
|
+
"""
|
|
270
|
+
events = list(self.queue)
|
|
271
|
+
self.queue.clear()
|
|
272
|
+
return events
|
|
273
|
+
|
|
274
|
+
def has_events(self) -> bool:
|
|
275
|
+
"""Check if there are any queued events."""
|
|
276
|
+
return len(self.queue) > 0
|
|
277
|
+
|
|
278
|
+
def format_event_as_content(self, event: Dict[str, Any]) -> str:
|
|
279
|
+
"""
|
|
280
|
+
Format an event as clean content text for streaming.
|
|
281
|
+
|
|
282
|
+
Sends clean, minimal text that is OpenAI-compatible.
|
|
283
|
+
The VSCode extension will add formatting (emojis, separators) for display.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
event: Event dictionary with type, data, and timestamp
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Clean content string suitable for any OpenAI-compatible client
|
|
290
|
+
"""
|
|
291
|
+
event_type = event.get("type", "message")
|
|
292
|
+
data = event.get("data", {})
|
|
293
|
+
|
|
294
|
+
if self.debug_mode:
|
|
295
|
+
# Debug mode: Include event type and data in compact format
|
|
296
|
+
return f"[{event_type}] {json.dumps(data, separators=(',', ':'))}\n"
|
|
297
|
+
|
|
298
|
+
# Normal mode: Clean, minimal status messages (no emojis/separators here)
|
|
299
|
+
# The VSCode extension will add formatting for better UX
|
|
300
|
+
|
|
301
|
+
if event_type == "processing_start":
|
|
302
|
+
return "Processing request...\n"
|
|
303
|
+
|
|
304
|
+
elif event_type == "step_header":
|
|
305
|
+
# Don't emit step headers in normal mode - too verbose
|
|
306
|
+
# Debug mode will show them via the debug format above
|
|
307
|
+
return ""
|
|
308
|
+
|
|
309
|
+
elif event_type == "state":
|
|
310
|
+
message = data.get("message", "")
|
|
311
|
+
return f"{message}\n"
|
|
312
|
+
|
|
313
|
+
elif event_type == "tool_usage":
|
|
314
|
+
tool_name = data.get("tool_name", "unknown")
|
|
315
|
+
return f"Using tool: {tool_name}\n"
|
|
316
|
+
|
|
317
|
+
elif event_type == "file_preview_start":
|
|
318
|
+
filename = data.get("filename", "unknown")
|
|
319
|
+
return f"Previewing file: {filename}\n"
|
|
320
|
+
|
|
321
|
+
elif event_type == "file_preview_update":
|
|
322
|
+
# Skip content chunks to avoid clutter
|
|
323
|
+
return ""
|
|
324
|
+
|
|
325
|
+
elif event_type == "file_preview_complete":
|
|
326
|
+
return "File preview complete\n"
|
|
327
|
+
|
|
328
|
+
elif event_type == "final_answer":
|
|
329
|
+
# Final answer is the actual response - send as-is
|
|
330
|
+
answer = data.get("answer", "")
|
|
331
|
+
return f"{answer}\n"
|
|
332
|
+
|
|
333
|
+
elif event_type == "error":
|
|
334
|
+
message = data.get("message", "An error occurred")
|
|
335
|
+
return f"Error: {message}\n"
|
|
336
|
+
|
|
337
|
+
elif event_type == "warning":
|
|
338
|
+
message = data.get("message", "")
|
|
339
|
+
return f"Warning: {message}\n"
|
|
340
|
+
|
|
341
|
+
elif event_type == "success":
|
|
342
|
+
message = data.get("message", "")
|
|
343
|
+
return f"{message}\n"
|
|
344
|
+
|
|
345
|
+
elif event_type == "diff":
|
|
346
|
+
filename = data.get("filename", "unknown")
|
|
347
|
+
return f"Modified file: {filename}\n"
|
|
348
|
+
|
|
349
|
+
elif event_type == "completion":
|
|
350
|
+
steps_taken = data.get("steps_taken", 0)
|
|
351
|
+
return f"Completed in {steps_taken} steps\n"
|
|
352
|
+
|
|
353
|
+
elif event_type == "checklist":
|
|
354
|
+
items = data.get("items", [])
|
|
355
|
+
current_idx = data.get("current_index", 0)
|
|
356
|
+
return f"Progress: step {current_idx + 1} of {len(items)}\n"
|
|
357
|
+
|
|
358
|
+
elif event_type == "checklist_reasoning":
|
|
359
|
+
# Skip reasoning in non-debug mode (too verbose)
|
|
360
|
+
return ""
|
|
361
|
+
|
|
362
|
+
elif event_type == "message":
|
|
363
|
+
text = data.get("text", "")
|
|
364
|
+
return f"{text}\n" if text else ""
|
|
365
|
+
|
|
366
|
+
elif event_type == "agent_selected":
|
|
367
|
+
agent_name = data.get("agent_name", "unknown")
|
|
368
|
+
language = data.get("language", "")
|
|
369
|
+
project_type = data.get("project_type", "")
|
|
370
|
+
return f"Agent: {agent_name} ({language}/{project_type})\n"
|
|
371
|
+
|
|
372
|
+
# For other events in normal mode, don't stream them
|
|
373
|
+
return ""
|