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