amd-gaia 0.15.0__py3-none-any.whl → 0.15.2__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.
Files changed (185) hide show
  1. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/METADATA +222 -223
  2. amd_gaia-0.15.2.dist-info/RECORD +182 -0
  3. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/WHEEL +1 -1
  4. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/entry_points.txt +1 -0
  5. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.dist-info}/licenses/LICENSE.md +20 -20
  6. gaia/__init__.py +29 -29
  7. gaia/agents/__init__.py +19 -19
  8. gaia/agents/base/__init__.py +9 -9
  9. gaia/agents/base/agent.py +2132 -2177
  10. gaia/agents/base/api_agent.py +119 -120
  11. gaia/agents/base/console.py +1967 -1841
  12. gaia/agents/base/errors.py +237 -237
  13. gaia/agents/base/mcp_agent.py +86 -86
  14. gaia/agents/base/tools.py +88 -83
  15. gaia/agents/blender/__init__.py +7 -0
  16. gaia/agents/blender/agent.py +553 -556
  17. gaia/agents/blender/agent_simple.py +133 -135
  18. gaia/agents/blender/app.py +211 -211
  19. gaia/agents/blender/app_simple.py +41 -41
  20. gaia/agents/blender/core/__init__.py +16 -16
  21. gaia/agents/blender/core/materials.py +506 -506
  22. gaia/agents/blender/core/objects.py +316 -316
  23. gaia/agents/blender/core/rendering.py +225 -225
  24. gaia/agents/blender/core/scene.py +220 -220
  25. gaia/agents/blender/core/view.py +146 -146
  26. gaia/agents/chat/__init__.py +9 -9
  27. gaia/agents/chat/agent.py +809 -835
  28. gaia/agents/chat/app.py +1065 -1058
  29. gaia/agents/chat/session.py +508 -508
  30. gaia/agents/chat/tools/__init__.py +15 -15
  31. gaia/agents/chat/tools/file_tools.py +96 -96
  32. gaia/agents/chat/tools/rag_tools.py +1744 -1729
  33. gaia/agents/chat/tools/shell_tools.py +437 -436
  34. gaia/agents/code/__init__.py +7 -7
  35. gaia/agents/code/agent.py +549 -549
  36. gaia/agents/code/cli.py +377 -0
  37. gaia/agents/code/models.py +135 -135
  38. gaia/agents/code/orchestration/__init__.py +24 -24
  39. gaia/agents/code/orchestration/checklist_executor.py +1763 -1763
  40. gaia/agents/code/orchestration/checklist_generator.py +713 -713
  41. gaia/agents/code/orchestration/factories/__init__.py +9 -9
  42. gaia/agents/code/orchestration/factories/base.py +63 -63
  43. gaia/agents/code/orchestration/factories/nextjs_factory.py +118 -118
  44. gaia/agents/code/orchestration/factories/python_factory.py +106 -106
  45. gaia/agents/code/orchestration/orchestrator.py +841 -841
  46. gaia/agents/code/orchestration/project_analyzer.py +391 -391
  47. gaia/agents/code/orchestration/steps/__init__.py +67 -67
  48. gaia/agents/code/orchestration/steps/base.py +188 -188
  49. gaia/agents/code/orchestration/steps/error_handler.py +314 -314
  50. gaia/agents/code/orchestration/steps/nextjs.py +828 -828
  51. gaia/agents/code/orchestration/steps/python.py +307 -307
  52. gaia/agents/code/orchestration/template_catalog.py +469 -469
  53. gaia/agents/code/orchestration/workflows/__init__.py +14 -14
  54. gaia/agents/code/orchestration/workflows/base.py +80 -80
  55. gaia/agents/code/orchestration/workflows/nextjs.py +186 -186
  56. gaia/agents/code/orchestration/workflows/python.py +94 -94
  57. gaia/agents/code/prompts/__init__.py +11 -11
  58. gaia/agents/code/prompts/base_prompt.py +77 -77
  59. gaia/agents/code/prompts/code_patterns.py +2034 -2036
  60. gaia/agents/code/prompts/nextjs_prompt.py +40 -40
  61. gaia/agents/code/prompts/python_prompt.py +109 -109
  62. gaia/agents/code/schema_inference.py +365 -365
  63. gaia/agents/code/system_prompt.py +41 -41
  64. gaia/agents/code/tools/__init__.py +42 -42
  65. gaia/agents/code/tools/cli_tools.py +1138 -1138
  66. gaia/agents/code/tools/code_formatting.py +319 -319
  67. gaia/agents/code/tools/code_tools.py +769 -769
  68. gaia/agents/code/tools/error_fixing.py +1347 -1347
  69. gaia/agents/code/tools/external_tools.py +180 -180
  70. gaia/agents/code/tools/file_io.py +845 -845
  71. gaia/agents/code/tools/prisma_tools.py +190 -190
  72. gaia/agents/code/tools/project_management.py +1016 -1016
  73. gaia/agents/code/tools/testing.py +321 -321
  74. gaia/agents/code/tools/typescript_tools.py +122 -122
  75. gaia/agents/code/tools/validation_parsing.py +461 -461
  76. gaia/agents/code/tools/validation_tools.py +806 -806
  77. gaia/agents/code/tools/web_dev_tools.py +1758 -1758
  78. gaia/agents/code/validators/__init__.py +16 -16
  79. gaia/agents/code/validators/antipattern_checker.py +241 -241
  80. gaia/agents/code/validators/ast_analyzer.py +197 -197
  81. gaia/agents/code/validators/requirements_validator.py +145 -145
  82. gaia/agents/code/validators/syntax_validator.py +171 -171
  83. gaia/agents/docker/__init__.py +7 -7
  84. gaia/agents/docker/agent.py +643 -642
  85. gaia/agents/emr/__init__.py +8 -8
  86. gaia/agents/emr/agent.py +1504 -1506
  87. gaia/agents/emr/cli.py +1322 -1322
  88. gaia/agents/emr/constants.py +475 -475
  89. gaia/agents/emr/dashboard/__init__.py +4 -4
  90. gaia/agents/emr/dashboard/server.py +1972 -1974
  91. gaia/agents/jira/__init__.py +11 -11
  92. gaia/agents/jira/agent.py +894 -894
  93. gaia/agents/jira/jql_templates.py +299 -299
  94. gaia/agents/routing/__init__.py +7 -7
  95. gaia/agents/routing/agent.py +567 -570
  96. gaia/agents/routing/system_prompt.py +75 -75
  97. gaia/agents/summarize/__init__.py +11 -0
  98. gaia/agents/summarize/agent.py +885 -0
  99. gaia/agents/summarize/prompts.py +129 -0
  100. gaia/api/__init__.py +23 -23
  101. gaia/api/agent_registry.py +238 -238
  102. gaia/api/app.py +305 -305
  103. gaia/api/openai_server.py +575 -575
  104. gaia/api/schemas.py +186 -186
  105. gaia/api/sse_handler.py +373 -373
  106. gaia/apps/__init__.py +4 -4
  107. gaia/apps/llm/__init__.py +6 -6
  108. gaia/apps/llm/app.py +184 -169
  109. gaia/apps/summarize/app.py +116 -633
  110. gaia/apps/summarize/html_viewer.py +133 -133
  111. gaia/apps/summarize/pdf_formatter.py +284 -284
  112. gaia/audio/__init__.py +2 -2
  113. gaia/audio/audio_client.py +439 -439
  114. gaia/audio/audio_recorder.py +269 -269
  115. gaia/audio/kokoro_tts.py +599 -599
  116. gaia/audio/whisper_asr.py +432 -432
  117. gaia/chat/__init__.py +16 -16
  118. gaia/chat/app.py +428 -430
  119. gaia/chat/prompts.py +522 -522
  120. gaia/chat/sdk.py +1228 -1225
  121. gaia/cli.py +5659 -5632
  122. gaia/database/__init__.py +10 -10
  123. gaia/database/agent.py +176 -176
  124. gaia/database/mixin.py +290 -290
  125. gaia/database/testing.py +64 -64
  126. gaia/eval/batch_experiment.py +2332 -2332
  127. gaia/eval/claude.py +542 -542
  128. gaia/eval/config.py +37 -37
  129. gaia/eval/email_generator.py +512 -512
  130. gaia/eval/eval.py +3179 -3179
  131. gaia/eval/groundtruth.py +1130 -1130
  132. gaia/eval/transcript_generator.py +582 -582
  133. gaia/eval/webapp/README.md +167 -167
  134. gaia/eval/webapp/package-lock.json +875 -875
  135. gaia/eval/webapp/package.json +20 -20
  136. gaia/eval/webapp/public/app.js +3402 -3402
  137. gaia/eval/webapp/public/index.html +87 -87
  138. gaia/eval/webapp/public/styles.css +3661 -3661
  139. gaia/eval/webapp/server.js +415 -415
  140. gaia/eval/webapp/test-setup.js +72 -72
  141. gaia/installer/__init__.py +23 -0
  142. gaia/installer/init_command.py +1275 -0
  143. gaia/installer/lemonade_installer.py +619 -0
  144. gaia/llm/__init__.py +10 -2
  145. gaia/llm/base_client.py +60 -0
  146. gaia/llm/exceptions.py +12 -0
  147. gaia/llm/factory.py +70 -0
  148. gaia/llm/lemonade_client.py +3421 -3221
  149. gaia/llm/lemonade_manager.py +294 -294
  150. gaia/llm/providers/__init__.py +9 -0
  151. gaia/llm/providers/claude.py +108 -0
  152. gaia/llm/providers/lemonade.py +118 -0
  153. gaia/llm/providers/openai_provider.py +79 -0
  154. gaia/llm/vlm_client.py +382 -382
  155. gaia/logger.py +189 -189
  156. gaia/mcp/agent_mcp_server.py +245 -245
  157. gaia/mcp/blender_mcp_client.py +138 -138
  158. gaia/mcp/blender_mcp_server.py +648 -648
  159. gaia/mcp/context7_cache.py +332 -332
  160. gaia/mcp/external_services.py +518 -518
  161. gaia/mcp/mcp_bridge.py +811 -550
  162. gaia/mcp/servers/__init__.py +6 -6
  163. gaia/mcp/servers/docker_mcp.py +83 -83
  164. gaia/perf_analysis.py +361 -0
  165. gaia/rag/__init__.py +10 -10
  166. gaia/rag/app.py +293 -293
  167. gaia/rag/demo.py +304 -304
  168. gaia/rag/pdf_utils.py +235 -235
  169. gaia/rag/sdk.py +2194 -2194
  170. gaia/security.py +183 -163
  171. gaia/talk/app.py +287 -289
  172. gaia/talk/sdk.py +538 -538
  173. gaia/testing/__init__.py +87 -87
  174. gaia/testing/assertions.py +330 -330
  175. gaia/testing/fixtures.py +333 -333
  176. gaia/testing/mocks.py +493 -493
  177. gaia/util.py +46 -46
  178. gaia/utils/__init__.py +33 -33
  179. gaia/utils/file_watcher.py +675 -675
  180. gaia/utils/parsing.py +223 -223
  181. gaia/version.py +100 -100
  182. amd_gaia-0.15.0.dist-info/RECORD +0 -168
  183. gaia/agents/code/app.py +0 -266
  184. gaia/llm/llm_client.py +0 -723
  185. {amd_gaia-0.15.0.dist-info → amd_gaia-0.15.2.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 ""