amd-gaia 0.15.2__py3-none-any.whl → 0.15.3__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.15.2.dist-info → amd_gaia-0.15.3.dist-info}/METADATA +2 -1
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/RECORD +17 -15
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/WHEEL +1 -1
- gaia/agents/base/agent.py +272 -23
- gaia/agents/base/console.py +208 -9
- gaia/agents/tools/__init__.py +11 -0
- gaia/agents/tools/file_tools.py +715 -0
- gaia/cli.py +242 -2
- gaia/installer/init_command.py +433 -103
- gaia/installer/lemonade_installer.py +62 -3
- gaia/llm/lemonade_client.py +143 -0
- gaia/llm/lemonade_manager.py +55 -11
- gaia/llm/providers/lemonade.py +9 -0
- gaia/version.py +2 -2
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/entry_points.txt +0 -0
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/licenses/LICENSE.md +0 -0
- {amd_gaia-0.15.2.dist-info → amd_gaia-0.15.3.dist-info}/top_level.txt +0 -0
gaia/agents/base/console.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import subprocess
|
|
5
6
|
import threading
|
|
6
7
|
import time
|
|
7
8
|
from abc import ABC, abstractmethod
|
|
@@ -57,7 +58,7 @@ class OutputHandler(ABC):
|
|
|
57
58
|
# === Core Progress/State Methods (Required) ===
|
|
58
59
|
|
|
59
60
|
@abstractmethod
|
|
60
|
-
def print_processing_start(self, query: str, max_steps: int):
|
|
61
|
+
def print_processing_start(self, query: str, max_steps: int, model_id: str = None):
|
|
61
62
|
"""Print processing start message."""
|
|
62
63
|
...
|
|
63
64
|
|
|
@@ -204,13 +205,14 @@ class OutputHandler(ABC):
|
|
|
204
205
|
|
|
205
206
|
|
|
206
207
|
class ProgressIndicator:
|
|
207
|
-
"""A simple progress indicator that shows a spinner or dots animation."""
|
|
208
|
+
"""A simple progress indicator that shows a spinner or dots animation with elapsed time."""
|
|
208
209
|
|
|
209
|
-
def __init__(self, message="Processing"):
|
|
210
|
+
def __init__(self, message="Processing", show_timer=False):
|
|
210
211
|
"""Initialize the progress indicator.
|
|
211
212
|
|
|
212
213
|
Args:
|
|
213
214
|
message: The message to display before the animation
|
|
215
|
+
show_timer: If True, show elapsed time
|
|
214
216
|
"""
|
|
215
217
|
self.message = message
|
|
216
218
|
self.is_running = False
|
|
@@ -220,6 +222,9 @@ class ProgressIndicator:
|
|
|
220
222
|
self.spinner_idx = 0
|
|
221
223
|
self.dot_idx = 0
|
|
222
224
|
self.rich_spinner = None
|
|
225
|
+
self.show_timer = show_timer
|
|
226
|
+
self.start_time = None
|
|
227
|
+
self._update_timer_thread = None # Timer update thread
|
|
223
228
|
if RICH_AVAILABLE:
|
|
224
229
|
self.rich_spinner = Spinner("dots", text=message)
|
|
225
230
|
self.live = None
|
|
@@ -259,19 +264,23 @@ class ProgressIndicator:
|
|
|
259
264
|
|
|
260
265
|
time.sleep(0.1)
|
|
261
266
|
|
|
262
|
-
def start(self, message=None):
|
|
267
|
+
def start(self, message=None, show_timer=None):
|
|
263
268
|
"""Start the progress indicator.
|
|
264
269
|
|
|
265
270
|
Args:
|
|
266
271
|
message: Optional new message to display
|
|
272
|
+
show_timer: Optional override for showing timer
|
|
267
273
|
"""
|
|
268
274
|
if message:
|
|
269
275
|
self.message = message
|
|
276
|
+
if show_timer is not None:
|
|
277
|
+
self.show_timer = show_timer
|
|
270
278
|
|
|
271
279
|
if self.is_running:
|
|
272
280
|
return
|
|
273
281
|
|
|
274
282
|
self.is_running = True
|
|
283
|
+
self.start_time = time.time()
|
|
275
284
|
|
|
276
285
|
if RICH_AVAILABLE:
|
|
277
286
|
if self.rich_spinner:
|
|
@@ -281,11 +290,31 @@ class ProgressIndicator:
|
|
|
281
290
|
self.rich_spinner, refresh_per_second=10, transient=True
|
|
282
291
|
)
|
|
283
292
|
self.live.start()
|
|
293
|
+
|
|
294
|
+
# Update with timer if enabled
|
|
295
|
+
if self.show_timer:
|
|
296
|
+
self._update_timer_thread = threading.Thread(
|
|
297
|
+
target=self._update_timer
|
|
298
|
+
)
|
|
299
|
+
self._update_timer_thread.daemon = True
|
|
300
|
+
self._update_timer_thread.start()
|
|
284
301
|
else:
|
|
285
302
|
self.thread = threading.Thread(target=self._animate)
|
|
286
303
|
self.thread.daemon = True
|
|
287
304
|
self.thread.start()
|
|
288
305
|
|
|
306
|
+
def _update_timer(self):
|
|
307
|
+
"""Update spinner text with elapsed time."""
|
|
308
|
+
while self.is_running and self.live:
|
|
309
|
+
elapsed = time.time() - self.start_time
|
|
310
|
+
timer_text = f"{self.message} ({int(elapsed)}s)"
|
|
311
|
+
try:
|
|
312
|
+
self.rich_spinner.text = timer_text
|
|
313
|
+
self.live.update(self.rich_spinner)
|
|
314
|
+
time.sleep(1.0) # Update every 1 second
|
|
315
|
+
except Exception:
|
|
316
|
+
break
|
|
317
|
+
|
|
289
318
|
def stop(self):
|
|
290
319
|
"""Stop the progress indicator."""
|
|
291
320
|
if not self.is_running:
|
|
@@ -293,6 +322,13 @@ class ProgressIndicator:
|
|
|
293
322
|
|
|
294
323
|
self.is_running = False
|
|
295
324
|
|
|
325
|
+
# Stop timer thread if running
|
|
326
|
+
if RICH_AVAILABLE and hasattr(self, "_update_timer_thread"):
|
|
327
|
+
try:
|
|
328
|
+
self._update_timer_thread.join(timeout=0.5)
|
|
329
|
+
except Exception:
|
|
330
|
+
pass
|
|
331
|
+
|
|
296
332
|
if RICH_AVAILABLE and self.live:
|
|
297
333
|
self.live.stop()
|
|
298
334
|
elif self.thread:
|
|
@@ -404,7 +440,6 @@ class AgentConsole(OutputHandler):
|
|
|
404
440
|
else:
|
|
405
441
|
# Regular JSON output
|
|
406
442
|
# Convert to formatted JSON string with safe fallback for non-serializable types (e.g., numpy.float32)
|
|
407
|
-
print(data)
|
|
408
443
|
try:
|
|
409
444
|
json_str = json.dumps(data, indent=2)
|
|
410
445
|
except TypeError:
|
|
@@ -463,21 +498,28 @@ class AgentConsole(OutputHandler):
|
|
|
463
498
|
print(f"\n⏸️ Paused after step: {description}")
|
|
464
499
|
print("Press Enter to continue, or 'n'/'q' to stop...")
|
|
465
500
|
|
|
466
|
-
def print_processing_start(
|
|
501
|
+
def print_processing_start(
|
|
502
|
+
self, query: str, max_steps: int, model_id: str = None
|
|
503
|
+
) -> None:
|
|
467
504
|
"""
|
|
468
505
|
Print the initial processing message.
|
|
469
506
|
|
|
470
507
|
Args:
|
|
471
508
|
query: The user query being processed
|
|
472
509
|
max_steps: Maximum number of steps allowed (kept for API compatibility)
|
|
510
|
+
model_id: Optional model ID to display
|
|
473
511
|
"""
|
|
474
512
|
if self.rich_available:
|
|
475
513
|
self.console.print(f"\n[bold blue]🤖 Processing:[/bold blue] '{query}'")
|
|
476
514
|
self.console.print("=" * 50)
|
|
515
|
+
if model_id:
|
|
516
|
+
self.console.print(f"[dim]Model: {model_id}[/dim]")
|
|
477
517
|
self.console.print()
|
|
478
518
|
else:
|
|
479
519
|
print(f"\n🤖 Processing: '{query}'")
|
|
480
520
|
print("=" * 50)
|
|
521
|
+
if model_id:
|
|
522
|
+
print(f"Model: {model_id}")
|
|
481
523
|
print()
|
|
482
524
|
|
|
483
525
|
def print_separator(self, length: int = 50) -> None:
|
|
@@ -803,6 +845,162 @@ class AgentConsole(OutputHandler):
|
|
|
803
845
|
else:
|
|
804
846
|
print(f"\n✅ SUCCESS: {message}\n")
|
|
805
847
|
|
|
848
|
+
def print_image(
|
|
849
|
+
self, image_path: str, caption: str = None, prompt_to_open: bool = False
|
|
850
|
+
) -> None:
|
|
851
|
+
"""
|
|
852
|
+
Display an image in terminal and optionally prompt to open in viewer.
|
|
853
|
+
|
|
854
|
+
Args:
|
|
855
|
+
image_path: Path to the image file
|
|
856
|
+
caption: Optional caption to display
|
|
857
|
+
prompt_to_open: If True, prompt user to open in default viewer after display
|
|
858
|
+
"""
|
|
859
|
+
import os
|
|
860
|
+
import sys
|
|
861
|
+
from pathlib import Path
|
|
862
|
+
|
|
863
|
+
path = Path(image_path)
|
|
864
|
+
if not path.exists():
|
|
865
|
+
return
|
|
866
|
+
|
|
867
|
+
if self.rich_available:
|
|
868
|
+
try:
|
|
869
|
+
# Try term-image with Sixel protocol for full resolution
|
|
870
|
+
# Sixel works in: Windows Terminal Preview, iTerm2, Kitty, WezTerm, etc.
|
|
871
|
+
from term_image.image import from_file
|
|
872
|
+
|
|
873
|
+
# Load image with auto-detected best protocol
|
|
874
|
+
img = from_file(str(path))
|
|
875
|
+
|
|
876
|
+
# Try to enable Sixel if supported
|
|
877
|
+
try:
|
|
878
|
+
# Set render method to auto-detect best available (Sixel, Kitty, iTerm2)
|
|
879
|
+
img.set_render_method("auto")
|
|
880
|
+
except Exception:
|
|
881
|
+
pass
|
|
882
|
+
|
|
883
|
+
# Set size maintaining aspect ratio
|
|
884
|
+
# Terminal characters are ~2:1 (height:width), so use fit_to_width
|
|
885
|
+
img.set_size(columns=60, fit_to_width=True)
|
|
886
|
+
|
|
887
|
+
# Render the image
|
|
888
|
+
if caption:
|
|
889
|
+
# Create a panel around the rendered image
|
|
890
|
+
rendered = str(img)
|
|
891
|
+
self.console.print(
|
|
892
|
+
Panel(
|
|
893
|
+
rendered,
|
|
894
|
+
title=f"🖼️ {caption}",
|
|
895
|
+
border_style="cyan",
|
|
896
|
+
padding=(0, 0),
|
|
897
|
+
),
|
|
898
|
+
justify="center",
|
|
899
|
+
)
|
|
900
|
+
else:
|
|
901
|
+
# Print image directly with centering
|
|
902
|
+
# Note: term-image returns ANSI escape codes with actual image data
|
|
903
|
+
print(
|
|
904
|
+
str(img)
|
|
905
|
+
) # Use plain print to avoid Rich interfering with image codes
|
|
906
|
+
|
|
907
|
+
self.console.print()
|
|
908
|
+
|
|
909
|
+
except (ImportError, Exception):
|
|
910
|
+
# Fallback to rich-pixels for broader compatibility
|
|
911
|
+
try:
|
|
912
|
+
from PIL import Image
|
|
913
|
+
from rich_pixels import Pixels
|
|
914
|
+
|
|
915
|
+
# Load image to check dimensions
|
|
916
|
+
pil_img = Image.open(path)
|
|
917
|
+
img_width, img_height = pil_img.size
|
|
918
|
+
aspect_ratio = img_width / img_height
|
|
919
|
+
|
|
920
|
+
# Terminal characters are roughly 2:1 (height:width)
|
|
921
|
+
# A char is ~2x taller than wide, so to show square image as square:
|
|
922
|
+
# need width_chars = height_chars * 2
|
|
923
|
+
# For proper aspect: width_chars = height_chars * 2 * image_aspect_ratio
|
|
924
|
+
target_height = 50 # rows
|
|
925
|
+
target_width = int(target_height * 2.0 * aspect_ratio) # columns
|
|
926
|
+
|
|
927
|
+
# Resize to these dimensions maintaining aspect
|
|
928
|
+
pixels = Pixels.from_image_path(
|
|
929
|
+
str(path), resize=(target_width, target_height)
|
|
930
|
+
)
|
|
931
|
+
|
|
932
|
+
if caption:
|
|
933
|
+
self.console.print(
|
|
934
|
+
Panel(
|
|
935
|
+
pixels,
|
|
936
|
+
title=f"🖼️ {caption}",
|
|
937
|
+
border_style="cyan",
|
|
938
|
+
padding=(0, 0),
|
|
939
|
+
),
|
|
940
|
+
justify="center",
|
|
941
|
+
)
|
|
942
|
+
else:
|
|
943
|
+
self.console.print(pixels, justify="center")
|
|
944
|
+
self.console.print()
|
|
945
|
+
|
|
946
|
+
except ImportError:
|
|
947
|
+
# No image libraries, show file info only
|
|
948
|
+
try:
|
|
949
|
+
from PIL import Image
|
|
950
|
+
|
|
951
|
+
img = Image.open(path)
|
|
952
|
+
info = f"[cyan]{path.name}[/cyan]\n"
|
|
953
|
+
info += f"[dim]Size: {img.width}x{img.height} | Format: {img.format} | File: {path.stat().st_size:,} bytes[/dim]"
|
|
954
|
+
|
|
955
|
+
if caption:
|
|
956
|
+
self.console.print(
|
|
957
|
+
Panel(
|
|
958
|
+
info, title=f"🖼️ {caption}", border_style="cyan"
|
|
959
|
+
),
|
|
960
|
+
justify="center",
|
|
961
|
+
)
|
|
962
|
+
else:
|
|
963
|
+
self.console.print(
|
|
964
|
+
Panel(info, border_style="cyan"), justify="center"
|
|
965
|
+
)
|
|
966
|
+
except Exception:
|
|
967
|
+
# Fallback to just showing the path
|
|
968
|
+
self.console.print(
|
|
969
|
+
f"[cyan]🖼️ Image: {path}[/cyan]", justify="center"
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
# Prompt to open in default viewer
|
|
973
|
+
if prompt_to_open and sys.platform == "win32":
|
|
974
|
+
try:
|
|
975
|
+
response = (
|
|
976
|
+
input("\nOpen image in default viewer? [Y/n]: ").strip().lower()
|
|
977
|
+
)
|
|
978
|
+
if response in ("", "y", "yes"):
|
|
979
|
+
if sys.platform == "win32":
|
|
980
|
+
os.startfile(str(path)) # pylint: disable=no-member
|
|
981
|
+
elif sys.platform == "darwin":
|
|
982
|
+
subprocess.run(["open", str(path)], check=False)
|
|
983
|
+
else:
|
|
984
|
+
subprocess.run(["xdg-open", str(path)], check=False)
|
|
985
|
+
except (KeyboardInterrupt, EOFError):
|
|
986
|
+
pass # User cancelled
|
|
987
|
+
else:
|
|
988
|
+
# Text-only terminal
|
|
989
|
+
print(f"\n🖼️ Image: {path}")
|
|
990
|
+
if caption:
|
|
991
|
+
print(f" {caption}")
|
|
992
|
+
|
|
993
|
+
# Prompt to open in default viewer
|
|
994
|
+
if prompt_to_open and sys.platform == "win32":
|
|
995
|
+
try:
|
|
996
|
+
response = input("\nOpen image? [Y/n]: ").strip().lower()
|
|
997
|
+
if response in ("", "y", "yes"):
|
|
998
|
+
os.startfile(str(path)) # pylint: disable=no-member
|
|
999
|
+
except (KeyboardInterrupt, EOFError):
|
|
1000
|
+
pass
|
|
1001
|
+
|
|
1002
|
+
print()
|
|
1003
|
+
|
|
806
1004
|
def print_diff(self, diff: str, filename: str) -> None:
|
|
807
1005
|
"""
|
|
808
1006
|
Print a code diff with syntax highlighting.
|
|
@@ -974,12 +1172,13 @@ class AgentConsole(OutputHandler):
|
|
|
974
1172
|
# Print the table in a panel
|
|
975
1173
|
self.console.print(Panel(table, border_style="blue"))
|
|
976
1174
|
|
|
977
|
-
def start_progress(self, message: str) -> None:
|
|
1175
|
+
def start_progress(self, message: str, show_timer: bool = False) -> None:
|
|
978
1176
|
"""
|
|
979
1177
|
Start the progress indicator.
|
|
980
1178
|
|
|
981
1179
|
Args:
|
|
982
1180
|
message: Message to display with the indicator
|
|
1181
|
+
show_timer: If True, show elapsed time in progress message
|
|
983
1182
|
"""
|
|
984
1183
|
# If file preview is active, pause it temporarily
|
|
985
1184
|
self._paused_preview = False
|
|
@@ -993,7 +1192,7 @@ class AgentConsole(OutputHandler):
|
|
|
993
1192
|
except Exception:
|
|
994
1193
|
pass
|
|
995
1194
|
|
|
996
|
-
self.progress.start(message)
|
|
1195
|
+
self.progress.start(message, show_timer=show_timer)
|
|
997
1196
|
|
|
998
1197
|
def stop_progress(self) -> None:
|
|
999
1198
|
"""Stop the progress indicator."""
|
|
@@ -1853,7 +2052,7 @@ class SilentConsole(OutputHandler):
|
|
|
1853
2052
|
console.print(Panel(table, border_style="blue"))
|
|
1854
2053
|
|
|
1855
2054
|
# All other abstract methods as no-ops
|
|
1856
|
-
def print_processing_start(self, query: str, max_steps: int):
|
|
2055
|
+
def print_processing_start(self, query: str, max_steps: int, model_id: str = None):
|
|
1857
2056
|
"""No-op implementation."""
|
|
1858
2057
|
|
|
1859
2058
|
def print_step_header(self, step_num: int, step_limit: int):
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
Shared tools for GAIA agents.
|
|
5
|
+
|
|
6
|
+
This package contains tool mixins that can be used across multiple agents.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .file_tools import FileSearchToolsMixin
|
|
10
|
+
|
|
11
|
+
__all__ = ["FileSearchToolsMixin"]
|