amd-gaia 0.15.1__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.
Files changed (38) hide show
  1. {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.3.dist-info}/METADATA +2 -2
  2. {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.3.dist-info}/RECORD +38 -32
  3. {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.3.dist-info}/WHEEL +1 -1
  4. gaia/agents/base/agent.py +317 -113
  5. gaia/agents/base/api_agent.py +0 -1
  6. gaia/agents/base/console.py +334 -9
  7. gaia/agents/base/tools.py +7 -2
  8. gaia/agents/blender/__init__.py +7 -0
  9. gaia/agents/blender/agent.py +7 -10
  10. gaia/agents/blender/core/view.py +2 -2
  11. gaia/agents/chat/agent.py +22 -48
  12. gaia/agents/chat/app.py +7 -0
  13. gaia/agents/chat/tools/rag_tools.py +23 -8
  14. gaia/agents/chat/tools/shell_tools.py +1 -0
  15. gaia/agents/code/prompts/code_patterns.py +2 -4
  16. gaia/agents/docker/agent.py +1 -0
  17. gaia/agents/emr/agent.py +3 -5
  18. gaia/agents/emr/cli.py +1 -1
  19. gaia/agents/emr/dashboard/server.py +2 -4
  20. gaia/agents/tools/__init__.py +11 -0
  21. gaia/agents/tools/file_tools.py +715 -0
  22. gaia/apps/llm/app.py +14 -3
  23. gaia/chat/app.py +2 -4
  24. gaia/cli.py +751 -333
  25. gaia/installer/__init__.py +23 -0
  26. gaia/installer/init_command.py +1605 -0
  27. gaia/installer/lemonade_installer.py +678 -0
  28. gaia/llm/__init__.py +2 -1
  29. gaia/llm/lemonade_client.py +427 -99
  30. gaia/llm/lemonade_manager.py +55 -11
  31. gaia/llm/providers/lemonade.py +21 -14
  32. gaia/rag/sdk.py +1 -1
  33. gaia/security.py +24 -4
  34. gaia/talk/app.py +2 -4
  35. gaia/version.py +2 -2
  36. {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.3.dist-info}/entry_points.txt +0 -0
  37. {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.3.dist-info}/licenses/LICENSE.md +0 -0
  38. {amd_gaia-0.15.1.dist-info → amd_gaia-0.15.3.dist-info}/top_level.txt +0 -0
@@ -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(self, query: str, max_steps: int) -> None:
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."""
@@ -1233,6 +1432,132 @@ class AgentConsole(OutputHandler):
1233
1432
  else:
1234
1433
  print(f"✅ Model {status}: {model_name}")
1235
1434
 
1435
+ # === Download Progress Methods ===
1436
+
1437
+ def print_download_start(self, model_name: str) -> None:
1438
+ """
1439
+ Print download starting notification.
1440
+
1441
+ Args:
1442
+ model_name: Name of the model being downloaded
1443
+ """
1444
+ if self.rich_available and self.console:
1445
+ self.console.print()
1446
+ self.console.print(
1447
+ f"[bold blue]📥 Downloading:[/bold blue] [cyan]{model_name}[/cyan]"
1448
+ )
1449
+ else:
1450
+ rprint(f"\n📥 Downloading: {model_name}")
1451
+
1452
+ def print_download_progress(
1453
+ self,
1454
+ percent: int,
1455
+ bytes_downloaded: int,
1456
+ bytes_total: int,
1457
+ speed_mbps: float = 0.0,
1458
+ ) -> None:
1459
+ """
1460
+ Print download progress with a progress bar that updates in place.
1461
+
1462
+ Args:
1463
+ percent: Download percentage (0-100)
1464
+ bytes_downloaded: Bytes downloaded so far
1465
+ bytes_total: Total bytes to download
1466
+ speed_mbps: Download speed in MB/s (optional)
1467
+ """
1468
+ import sys
1469
+
1470
+ # Format sizes
1471
+ if bytes_total > 1024**3: # > 1 GB
1472
+ dl_str = f"{bytes_downloaded / 1024**3:.2f} GB"
1473
+ total_str = f"{bytes_total / 1024**3:.2f} GB"
1474
+ elif bytes_total > 1024**2: # > 1 MB
1475
+ dl_str = f"{bytes_downloaded / 1024**2:.0f} MB"
1476
+ total_str = f"{bytes_total / 1024**2:.0f} MB"
1477
+ else:
1478
+ dl_str = f"{bytes_downloaded / 1024:.0f} KB"
1479
+ total_str = f"{bytes_total / 1024:.0f} KB"
1480
+
1481
+ # Progress bar characters
1482
+ bar_width = 25
1483
+ filled = int(bar_width * percent / 100)
1484
+ bar = "━" * filled + "─" * (bar_width - filled)
1485
+
1486
+ # Build progress line with optional speed
1487
+ progress_line = f" [{bar}] {percent:3d}% {dl_str} / {total_str}"
1488
+ if speed_mbps > 0.1:
1489
+ progress_line += f" @ {speed_mbps:.0f} MB/s"
1490
+
1491
+ # Update in place with carriage return
1492
+ sys.stdout.write(f"\r{progress_line:<80}")
1493
+ sys.stdout.flush()
1494
+
1495
+ def print_download_complete(self, model_name: str = None) -> None:
1496
+ """
1497
+ Print download complete notification.
1498
+
1499
+ Args:
1500
+ model_name: Optional name of the downloaded model
1501
+ """
1502
+ if self.rich_available and self.console:
1503
+ self.console.print() # Newline after progress bar
1504
+ if model_name:
1505
+ self.console.print(
1506
+ f" [green]✅ Downloaded successfully:[/green] [cyan]{model_name}[/cyan]"
1507
+ )
1508
+ else:
1509
+ self.console.print(" [green]✅ Download complete[/green]")
1510
+ else:
1511
+ rprint()
1512
+ msg = (
1513
+ f" ✅ Downloaded: {model_name}"
1514
+ if model_name
1515
+ else " ✅ Download complete"
1516
+ )
1517
+ rprint(msg)
1518
+
1519
+ def print_download_error(self, error_message: str, model_name: str = None) -> None:
1520
+ """
1521
+ Print download error notification.
1522
+
1523
+ Args:
1524
+ error_message: Error description
1525
+ model_name: Optional name of the model that failed
1526
+ """
1527
+ if self.rich_available and self.console:
1528
+ self.console.print() # Newline after progress bar
1529
+ if model_name:
1530
+ self.console.print(
1531
+ f" [red]❌ Download failed for {model_name}:[/red] {error_message}"
1532
+ )
1533
+ else:
1534
+ self.console.print(f" [red]❌ Download failed:[/red] {error_message}")
1535
+ else:
1536
+ rprint()
1537
+ msg = (
1538
+ f" ❌ Download failed for {model_name}: {error_message}"
1539
+ if model_name
1540
+ else f" ❌ Download failed: {error_message}"
1541
+ )
1542
+ rprint(msg)
1543
+
1544
+ def print_download_skipped(
1545
+ self, model_name: str, reason: str = "already downloaded"
1546
+ ) -> None:
1547
+ """
1548
+ Print download skipped notification.
1549
+
1550
+ Args:
1551
+ model_name: Name of the model that was skipped
1552
+ reason: Reason for skipping
1553
+ """
1554
+ if self.rich_available and self.console:
1555
+ self.console.print(
1556
+ f"[green]✅[/green] [cyan]{model_name}[/cyan] [dim]({reason})[/dim]"
1557
+ )
1558
+ else:
1559
+ rprint(f"✅ {model_name} ({reason})")
1560
+
1236
1561
  def print_extraction_start(
1237
1562
  self, image_num: int, page_num: int, mime_type: str
1238
1563
  ) -> None:
@@ -1727,7 +2052,7 @@ class SilentConsole(OutputHandler):
1727
2052
  console.print(Panel(table, border_style="blue"))
1728
2053
 
1729
2054
  # All other abstract methods as no-ops
1730
- 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):
1731
2056
  """No-op implementation."""
1732
2057
 
1733
2058
  def print_step_header(self, step_num: int, step_limit: int):
gaia/agents/base/tools.py CHANGED
@@ -17,7 +17,10 @@ _TOOL_REGISTRY = {}
17
17
 
18
18
 
19
19
  def tool(
20
- func: Callable = None, **kwargs # pylint: disable=unused-argument
20
+ func: Callable = None,
21
+ *,
22
+ atomic: bool = False,
23
+ **kwargs, # pylint: disable=unused-argument
21
24
  ) -> Callable:
22
25
  """
23
26
  Decorator to register a function as a tool.
@@ -28,6 +31,7 @@ def tool(
28
31
 
29
32
  Args:
30
33
  func: Function to register as a tool (when used as @tool)
34
+ atomic: If True, marks this tool as atomic (can execute without multi-step planning)
31
35
  **kwargs: Optional arguments (ignored, for backward compatibility)
32
36
 
33
37
  Returns:
@@ -63,12 +67,13 @@ def tool(
63
67
 
64
68
  params[name] = param_info
65
69
 
66
- # Register the tool
70
+ # Register the tool with atomic metadata
67
71
  _TOOL_REGISTRY[tool_name] = {
68
72
  "name": tool_name,
69
73
  "description": f.__doc__ or "",
70
74
  "parameters": params,
71
75
  "function": f,
76
+ "atomic": atomic,
72
77
  }
73
78
 
74
79
  # Return the function unchanged
@@ -0,0 +1,7 @@
1
+ # Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2
+ # SPDX-License-Identifier: MIT
3
+ """Blender Agent for 3D scene automation."""
4
+
5
+ from gaia.agents.blender.agent import BlenderAgent
6
+
7
+ __all__ = ["BlenderAgent"]
@@ -24,9 +24,6 @@ class BlenderAgent(Agent):
24
24
  Inherits core functionality from the base Agent class.
25
25
  """
26
26
 
27
- # Define Blender-specific tools that can execute directly without requiring a plan
28
- SIMPLE_TOOLS = ["clear_scene", "get_scene_info"]
29
-
30
27
  def __init__(
31
28
  self,
32
29
  mcp: Optional[MCPClient] = None,
@@ -80,7 +77,7 @@ class BlenderAgent(Agent):
80
77
  def _get_system_prompt(self) -> str:
81
78
  """Generate the system prompt for the Blender agent."""
82
79
  # Get formatted tools from registry
83
- return f"""
80
+ return """
84
81
  You are a specialized Blender 3D assistant that can create and modify 3D scenes.
85
82
  You will use a set of tools to accomplish tasks based on the user's request.
86
83
 
@@ -169,7 +166,7 @@ Examples of colored requests:
169
166
  def _register_tools(self):
170
167
  """Register all Blender-related tools for the agent."""
171
168
 
172
- @tool
169
+ @tool(atomic=True)
173
170
  def clear_scene() -> Dict[str, Any]:
174
171
  """
175
172
  Remove all objects from the current Blender scene.
@@ -283,7 +280,7 @@ Examples of colored requests:
283
280
  return {"status": "error", "error": str(e)}
284
281
 
285
282
  # @tool
286
- def get_object_info(name: str) -> Dict[str, Any]:
283
+ def _get_object_info(name: str) -> Dict[str, Any]:
287
284
  """
288
285
  Get information about an object in the scene.
289
286
 
@@ -354,7 +351,7 @@ Examples of colored requests:
354
351
  return {"status": "error", "error": str(e)}
355
352
 
356
353
  # @tool
357
- def delete_object(name: str) -> Dict[str, Any]:
354
+ def _delete_object(name: str) -> Dict[str, Any]:
358
355
  """
359
356
  Delete an object from the scene.
360
357
 
@@ -382,7 +379,7 @@ Examples of colored requests:
382
379
  self.error_history.append(str(e))
383
380
  return {"status": "error", "error": str(e)}
384
381
 
385
- @tool
382
+ @tool(atomic=True)
386
383
  def get_scene_info() -> Dict[str, Any]:
387
384
  """
388
385
  Get information about the current scene.
@@ -407,7 +404,7 @@ Examples of colored requests:
407
404
  return {"status": "error", "error": str(e)}
408
405
 
409
406
  # @tool
410
- def execute_blender_code(code: str) -> Dict[str, Any]:
407
+ def _execute_blender_code(code: str) -> Dict[str, Any]:
411
408
  """
412
409
  Execute arbitrary Python code in Blender with error handling.
413
410
 
@@ -436,7 +433,7 @@ Examples of colored requests:
436
433
  return {"status": "error", "error": str(e)}
437
434
 
438
435
  # @tool
439
- def diagnose_scene() -> Dict[str, Any]:
436
+ def _diagnose_scene() -> Dict[str, Any]:
440
437
  """
441
438
  Diagnose the current Blender scene for common issues.
442
439
  Returns information about objects, materials, and potential problems.
@@ -13,13 +13,13 @@ class ViewManager:
13
13
  self.mcp = mcp
14
14
 
15
15
  def adjust_for_large_scale(
16
- self, clip_end: float = 100000, orbit_selection: bool = True
16
+ self, clip_end: float = 100000, _orbit_selection: bool = True
17
17
  ) -> Dict:
18
18
  """Adjust viewport settings to properly view large-scale objects like Earth.
19
19
 
20
20
  Args:
21
21
  clip_end: The maximum view distance to set for the 3D viewport (default: 100000)
22
- orbit_selection: Whether to enable orbit around selection (default: True, but may not work in all Blender versions)
22
+ _orbit_selection: Whether to enable orbit around selection (default: True, but may not work in all Blender versions)
23
23
  """
24
24
 
25
25
  def generate_code():