naas-abi-core 1.4.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 (124) hide show
  1. assets/favicon.ico +0 -0
  2. assets/logo.png +0 -0
  3. naas_abi_core/__init__.py +1 -0
  4. naas_abi_core/apps/api/api.py +245 -0
  5. naas_abi_core/apps/api/api_test.py +281 -0
  6. naas_abi_core/apps/api/openapi_doc.py +144 -0
  7. naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
  8. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  9. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  10. naas_abi_core/apps/terminal_agent/main.py +555 -0
  11. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  12. naas_abi_core/engine/Engine.py +87 -0
  13. naas_abi_core/engine/EngineProxy.py +109 -0
  14. naas_abi_core/engine/Engine_test.py +6 -0
  15. naas_abi_core/engine/IEngine.py +91 -0
  16. naas_abi_core/engine/conftest.py +45 -0
  17. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
  18. naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
  19. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  20. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
  21. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  29. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  30. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  31. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  32. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  33. naas_abi_core/integration/__init__.py +7 -0
  34. naas_abi_core/integration/integration.py +28 -0
  35. naas_abi_core/models/Model.py +198 -0
  36. naas_abi_core/models/OpenRouter.py +18 -0
  37. naas_abi_core/models/OpenRouter_test.py +36 -0
  38. naas_abi_core/module/Module.py +252 -0
  39. naas_abi_core/module/ModuleAgentLoader.py +50 -0
  40. naas_abi_core/module/ModuleUtils.py +20 -0
  41. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  42. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  43. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  44. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  46. naas_abi_core/pipeline/__init__.py +6 -0
  47. naas_abi_core/pipeline/pipeline.py +70 -0
  48. naas_abi_core/services/__init__.py +0 -0
  49. naas_abi_core/services/agent/Agent.py +1619 -0
  50. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  51. naas_abi_core/services/agent/Agent_test.py +214 -0
  52. naas_abi_core/services/agent/IntentAgent.py +1179 -0
  53. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  54. naas_abi_core/services/agent/beta/Embeddings.py +181 -0
  55. naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
  56. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  57. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  58. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  59. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  60. naas_abi_core/services/cache/CacheFactory.py +31 -0
  61. naas_abi_core/services/cache/CachePort.py +63 -0
  62. naas_abi_core/services/cache/CacheService.py +246 -0
  63. naas_abi_core/services/cache/CacheService_test.py +85 -0
  64. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  65. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  66. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  67. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  68. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  71. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  72. naas_abi_core/services/ontology/OntologyService.py +17 -0
  73. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  74. naas_abi_core/services/secret/Secret.py +138 -0
  75. naas_abi_core/services/secret/SecretPorts.py +45 -0
  76. naas_abi_core/services/secret/Secret_test.py +65 -0
  77. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  79. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  81. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
  82. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  83. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  84. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  85. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  92. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  94. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  95. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  96. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  97. naas_abi_core/services/vector_store/__init__.py +13 -0
  98. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  100. naas_abi_core/tests/test_services_imports.py +69 -0
  101. naas_abi_core/utils/Expose.py +55 -0
  102. naas_abi_core/utils/Graph.py +182 -0
  103. naas_abi_core/utils/JSON.py +49 -0
  104. naas_abi_core/utils/LazyLoader.py +44 -0
  105. naas_abi_core/utils/Logger.py +12 -0
  106. naas_abi_core/utils/OntologyReasoner.py +141 -0
  107. naas_abi_core/utils/OntologyYaml.py +681 -0
  108. naas_abi_core/utils/SPARQL.py +256 -0
  109. naas_abi_core/utils/Storage.py +33 -0
  110. naas_abi_core/utils/StorageUtils.py +398 -0
  111. naas_abi_core/utils/String.py +52 -0
  112. naas_abi_core/utils/Workers.py +114 -0
  113. naas_abi_core/utils/__init__.py +0 -0
  114. naas_abi_core/utils/onto2py/README.md +0 -0
  115. naas_abi_core/utils/onto2py/__init__.py +10 -0
  116. naas_abi_core/utils/onto2py/__main__.py +29 -0
  117. naas_abi_core/utils/onto2py/onto2py.py +611 -0
  118. naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
  119. naas_abi_core/workflow/__init__.py +5 -0
  120. naas_abi_core/workflow/workflow.py +48 -0
  121. naas_abi_core-1.4.1.dist-info/METADATA +630 -0
  122. naas_abi_core-1.4.1.dist-info/RECORD +124 -0
  123. naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
  124. naas_abi_core-1.4.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,555 @@
1
+ import re
2
+ import sys
3
+ import termios
4
+ import threading
5
+ import time
6
+ import tty
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+ from typing import Any, cast
10
+
11
+ from langchain_core.messages import AnyMessage, ToolMessage
12
+ from naas_abi_core import logger
13
+ from naas_abi_core.apps.terminal_agent.terminal_style import ( # Add console import
14
+ console,
15
+ print_image,
16
+ print_tool_response,
17
+ print_tool_usage,
18
+ )
19
+ from naas_abi_core.services.agent.Agent import Agent
20
+
21
+ # Global variable to track active agent for context-aware conversations
22
+ conversation_file = None
23
+
24
+ # Fixed width for consistent conversation logs (matches typical wide terminal)
25
+ TERMINAL_WIDTH = 77 # Matches the separator length from the user's example
26
+
27
+
28
+ def init_conversation_file():
29
+ """Initialize a new conversation file with timestamp"""
30
+ global conversation_file
31
+
32
+ # Create timestamp in format YYYYMMDDTHHMMSS
33
+ timestamp = datetime.now().strftime("%Y%m%dT%H%M%S")
34
+
35
+ # Create directory structure
36
+ conversation_dir = Path("storage/datastore/interfaces/terminal_agent")
37
+ conversation_dir.mkdir(parents=True, exist_ok=True)
38
+
39
+ # Create conversation file path
40
+ conversation_file = conversation_dir / f"{timestamp}.txt"
41
+
42
+ # Initialize file with header
43
+ with open(conversation_file, "w", encoding="utf-8") as f:
44
+ f.write(
45
+ f"# ABI Terminal Conversation - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
46
+ )
47
+ f.write(f"# Session started at: {timestamp}\n")
48
+ f.write("=" * 80 + "\n\n")
49
+
50
+ logger.info(f"💾 Conversation logging to: {conversation_file}")
51
+ return conversation_file
52
+
53
+
54
+ def save_to_conversation(line: str):
55
+ """Save exactly what appears in terminal to the conversation file"""
56
+ global conversation_file
57
+
58
+ if conversation_file is None:
59
+ return
60
+
61
+ try:
62
+ with open(conversation_file, "a", encoding="utf-8") as f:
63
+ f.write(line + "\n")
64
+ except Exception as e:
65
+ # Print error to terminal
66
+ print(f"⚠️ Error saving to conversation file: {e}")
67
+ # Try to log the error itself if possible
68
+ try:
69
+ with open(conversation_file, "a", encoding="utf-8") as f:
70
+ f.write(f"⚠️ LOGGING ERROR: {e}\n")
71
+ except Exception:
72
+ pass # If we can't log the error, give up
73
+
74
+
75
+ def get_input_with_placeholder(prompt="> ", placeholder="Send a message (/? for help)"):
76
+ """Get user input with a placeholder that disappears when typing starts"""
77
+
78
+ # Check if input is piped (not interactive terminal)
79
+ if not sys.stdin.isatty():
80
+ # For piped input, use simple input() without fancy terminal handling
81
+ print(f"\n{prompt}", end="", flush=True)
82
+ try:
83
+ return input()
84
+ except: # noqa: E722
85
+ return "/bye"
86
+
87
+ # Interactive terminal - use fancy placeholder logic
88
+ print(f"\n{prompt}", end="", flush=True)
89
+
90
+ # Show placeholder in grey
91
+ print(f"\033[90m{placeholder}\033[0m", end="", flush=True)
92
+
93
+ # Move cursor back to start of placeholder
94
+ print(f"\033[{len(placeholder)}D", end="", flush=True)
95
+
96
+ user_input = ""
97
+ placeholder_cleared = False
98
+
99
+ old_settings = termios.tcgetattr(sys.stdin)
100
+
101
+ try:
102
+ tty.setraw(sys.stdin.fileno())
103
+
104
+ while True:
105
+ char = sys.stdin.read(1)
106
+
107
+ # Handle Enter key
108
+ if ord(char) == 13: # Enter
109
+ # Clear the current line before returning
110
+ print("\r\033[2K", end="", flush=True)
111
+ break
112
+
113
+ # Handle Backspace
114
+ elif ord(char) == 127: # Backspace
115
+ if user_input:
116
+ user_input = user_input[:-1]
117
+ print("\b \b", end="", flush=True)
118
+
119
+ # Handle Ctrl+C
120
+ elif ord(char) == 3: # Ctrl+C
121
+ print("^C")
122
+ raise KeyboardInterrupt
123
+
124
+ # Handle printable characters
125
+ elif ord(char) >= 32 and ord(char) <= 126:
126
+ # Clear placeholder on first character
127
+ if not placeholder_cleared:
128
+ # Clear the placeholder text
129
+ print(f"\033[2K\r{prompt}", end="", flush=True)
130
+ placeholder_cleared = True
131
+
132
+ user_input += char
133
+ print(char, end="", flush=True)
134
+
135
+ finally:
136
+ # Restore terminal settings
137
+ termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
138
+
139
+ return user_input
140
+
141
+
142
+ def on_tool_response(message: AnyMessage) -> None:
143
+ try:
144
+ message_content: str = ""
145
+ raw_message: Any = cast(Any, message)
146
+ if isinstance(raw_message, str):
147
+ message_content = raw_message
148
+ elif isinstance(raw_message, dict) and "content" in raw_message:
149
+ message_content = str(raw_message["content"])
150
+ elif isinstance(raw_message, ToolMessage):
151
+ message_content = str(raw_message.content)
152
+ elif hasattr(raw_message, "content"):
153
+ message_content = str(getattr(raw_message, "content"))
154
+ else:
155
+ print("Unknown message type:")
156
+ print(type(raw_message))
157
+ message_content = str(raw_message)
158
+
159
+ print_tool_response(message_content)
160
+
161
+ # Check if the message contains a path to an image file
162
+ if isinstance(message_content, str):
163
+ # Look for image file paths in the message
164
+ words = message_content.split(" ")
165
+ for word in words:
166
+ if any(
167
+ word.lower().endswith(ext)
168
+ for ext in [".png", ".jpg", ".jpeg", ".gif"]
169
+ ):
170
+ print_image(word)
171
+ except Exception as e:
172
+ error_msg = f"⚠️ Tool Response Error: {e}"
173
+ print(error_msg)
174
+ # Log the error to conversation file
175
+ save_to_conversation(error_msg)
176
+
177
+
178
+ def on_ai_message(message: Any, agent_name: str) -> None:
179
+ if len(message.content) == 0:
180
+ return
181
+
182
+ print("\r" + " " * 15 + "\r", end="", flush=True)
183
+
184
+ from rich.markdown import Markdown
185
+
186
+ # Filter out think tags and their content
187
+ think_content = re.findall(r"<think>.*?</think>", message.content, flags=re.DOTALL)
188
+
189
+ # Prepare thoughts section for both display and logging
190
+ thoughts_for_log = ""
191
+ if len(think_content) > 0:
192
+ console.print("Thoughts:", style="grey66")
193
+ thoughts_for_log += "Thoughts: \n\n"
194
+ for think in think_content:
195
+ think_text = think.replace("<think>", "").replace("</think>", "").strip()
196
+ console.print(think_text, style="grey66")
197
+ thoughts_for_log += think_text + "\n"
198
+ thoughts_for_log += "\n" # Extra line after thoughts
199
+
200
+ content = re.sub(
201
+ r"<think>.*?</think>", "", message.content, flags=re.DOTALL
202
+ ).strip()
203
+
204
+ # Print agent name dynamically using the real intent_target
205
+
206
+ # Use the actual agent name from intent_target, with color coding for readability
207
+ if "abi" in agent_name.lower():
208
+ color = "bold green"
209
+ elif "claude" in agent_name.lower():
210
+ color = "bold dark_orange" # Anthropic's orange, use rich color
211
+ elif "chatgpt" in agent_name.lower():
212
+ color = "bold bright_green" # OpenAI's green
213
+ elif "deepseek" in agent_name.lower():
214
+ color = "bold blue" # DeepSeek's blue
215
+ elif "gemini" in agent_name.lower():
216
+ color = "bold bright_blue" # Google's blue
217
+ elif "gemma" in agent_name.lower():
218
+ color = "bold bright_cyan" # Google's cyan/blue
219
+ elif "grok" in agent_name.lower():
220
+ color = "bold white" # X/Twitter's white
221
+ elif "llama" in agent_name.lower():
222
+ color = "bold bright_blue" # Meta's blue
223
+ elif "mistral" in agent_name.lower():
224
+ color = "bold dark_orange" # Mistral's orange, use rich color
225
+ elif "perplexity" in agent_name.lower():
226
+ color = "bold white" # Perplexity's white
227
+ elif "qwen" in agent_name.lower():
228
+ color = "bold bright_cyan" # Alibaba's cyan
229
+ else:
230
+ color = "bold magenta"
231
+
232
+ # Format exactly as it appears in terminal
233
+ agent_message_line = f"{agent_name}: {content}"
234
+
235
+ # Display the real agent name
236
+ console.print(f"{agent_name}:", style=color, end=" ")
237
+
238
+ md = Markdown(content)
239
+ console.print(md, style="bright_white")
240
+ console.print("─" * console.width, style="dim")
241
+ print() # Add spacing after separator
242
+
243
+ # Save exact terminal format to conversation file (with fixed width) including thoughts
244
+ if thoughts_for_log:
245
+ save_to_conversation(thoughts_for_log)
246
+ save_to_conversation(agent_message_line)
247
+ save_to_conversation("─" * TERMINAL_WIDTH)
248
+ save_to_conversation("") # Empty line
249
+
250
+
251
+ def run_agent(agent: Agent):
252
+ # Initialize conversation logging
253
+ init_conversation_file()
254
+
255
+ # Initialize agent hooks.
256
+ agent.on_tool_usage(lambda message: print_tool_usage(message))
257
+ agent.on_tool_response(on_tool_response)
258
+ agent.on_ai_message(on_ai_message)
259
+
260
+ # All agents
261
+ all_agents = agent.agents + [agent]
262
+
263
+ # Show greeting when truly ready for input - instant like responses
264
+ greeting_line = f"{agent.name}: Hello, World!"
265
+
266
+ console.print(f"{agent.name}:", style="bold green", end=" ")
267
+ console.print("Hello, World!", style="bright_white")
268
+ console.print("─" * console.width, style="dim")
269
+ print() # Add spacing after separator
270
+
271
+ # Save exact terminal output to conversation file (with fixed width)
272
+ save_to_conversation(greeting_line)
273
+ save_to_conversation("─" * TERMINAL_WIDTH)
274
+ save_to_conversation("") # Empty line
275
+
276
+ # Just start chatting naturally - like the screenshot
277
+ while True:
278
+ # Get current active agent and its model
279
+ current_active_agent = agent.state.current_active_agent
280
+ if current_active_agent is None:
281
+ current_active_agent = agent.name
282
+
283
+ model_info = "unknown"
284
+
285
+ # Find the active agent in our agents list
286
+ import pydash as _
287
+
288
+ current_agent = _.find(
289
+ all_agents,
290
+ lambda a: a.name.lower() == current_active_agent.lower()
291
+ if a.name is not None
292
+ else False,
293
+ )
294
+ if current_agent:
295
+ if hasattr(current_agent.chat_model, "model_name"):
296
+ model_info = current_agent.chat_model.model_name
297
+ elif hasattr(current_agent.chat_model, "model"):
298
+ model_info = current_agent.chat_model.model
299
+
300
+ # Create clean status line showing active agent with model info
301
+ if current_active_agent:
302
+ status_line = f"Active: {current_active_agent} (model: {model_info})"
303
+ else:
304
+ status_line = "No active agent"
305
+
306
+ # Print the status line before the input prompt
307
+ console.print(status_line, style="dim")
308
+
309
+ # Save status line to conversation file
310
+ save_to_conversation(status_line)
311
+ save_to_conversation("") # Empty line for spacing
312
+
313
+ user_input = get_input_with_placeholder()
314
+
315
+ # Clean the input and check for exit commands
316
+ clean_input = user_input.strip().lower()
317
+
318
+ # Skip empty input
319
+ if not user_input.strip():
320
+ continue
321
+
322
+ # Display user message with color coding and separator (except for commands)
323
+ if not clean_input.startswith("/") and clean_input not in [
324
+ "exit",
325
+ "quit",
326
+ "reset",
327
+ ]:
328
+ # Format exactly as it appears in terminal
329
+ user_message_line = f"You: {user_input.strip()}"
330
+
331
+ # Show formatted message in chat history (same format as Abi)
332
+ console.print("You:", style="bold cyan", end=" ")
333
+ console.print(user_input.strip(), style="bright_white")
334
+ console.print("─" * console.width, style="dim")
335
+ print() # Add spacing after separator
336
+
337
+ # Save exact terminal format to conversation file (with fixed width)
338
+ save_to_conversation(user_message_line)
339
+ save_to_conversation("─" * TERMINAL_WIDTH)
340
+ save_to_conversation("") # Empty line
341
+
342
+ if clean_input in ["exit", "/exit", "/bye", "quit", "/quit"]:
343
+ # Save session end to conversation file
344
+ save_to_conversation("") # Empty line
345
+ save_to_conversation("# Session ended by user")
346
+ print(f"\n👋 See you later! Conversation saved to: {conversation_file}")
347
+ return
348
+ elif clean_input in ["reset", "/reset"]:
349
+ agent.reset()
350
+ print("🔄 Starting fresh...")
351
+ continue
352
+ elif clean_input == "/?":
353
+ print("\n📋 Available Commands:")
354
+ print(" /? - Show this help")
355
+ print(" /reset - Start fresh conversation")
356
+ print(" /bye or /exit - End conversation")
357
+ print("\n🤖 Available AI Agents:")
358
+ print(" Cloud Agents:")
359
+ cloud_agents = [
360
+ "@gemini",
361
+ "@claude",
362
+ "@mistral",
363
+ "@chatgpt",
364
+ "@perplexity",
365
+ "@llama",
366
+ ]
367
+ print(f" {' '.join(cloud_agents)}")
368
+ print(" Local Agents (Privacy-focused):")
369
+ local_agents = ["@qwen", "@deepseek", "@gemma"]
370
+ print(f" {' '.join(local_agents)}")
371
+ print("\n💡 Usage: Type @agent or 'ask agent' to switch agents")
372
+ print(
373
+ " Example: '@qwen help me code' or 'ask deepseek solve this math problem'"
374
+ )
375
+ continue
376
+
377
+ # Matrix-style animated loading indicator
378
+
379
+ # Animation control
380
+ loading = True
381
+
382
+ def matrix_loader():
383
+ i = 0
384
+ while loading:
385
+ dots_count = i % 4 # 0, 1, 2, 3, then repeat
386
+ if dots_count == 0:
387
+ dots = " " # No dots, just spaces
388
+ else:
389
+ dots = "." * dots_count + " " * (
390
+ 3 - dots_count
391
+ ) # Pad to 3 char width
392
+ print(f"\r\033[92mResponding{dots}\033[0m", end="", flush=True)
393
+ time.sleep(0.5)
394
+ i += 1
395
+
396
+ # Start the animation in a separate thread
397
+ loader_thread = threading.Thread(target=matrix_loader)
398
+ loader_thread.start()
399
+
400
+ # # Update the agent's shared state with current active agent info
401
+ # if hasattr(agent, '_state') and hasattr(agent._state, 'set_current_active_agent'):
402
+ # agent._state.set_current_active_agent(current_active_agent)
403
+
404
+ # Get the response with real streaming support
405
+ try:
406
+ # Stop the animation first
407
+ loading = False
408
+ loader_thread.join()
409
+ print("\r" + " " * 15 + "\r", end="", flush=True)
410
+
411
+ # Use the agent system properly
412
+ agent.invoke(user_input)
413
+
414
+ except Exception as e:
415
+ # Stop the animation if still running
416
+ loading = False
417
+ if "loader_thread" in locals():
418
+ loader_thread.join()
419
+
420
+ # Clear the loading line
421
+ print("\r" + " " * 15 + "\r", end="", flush=True)
422
+
423
+ # Display and log the error
424
+ error_msg = f"❌ Agent Error: {e}"
425
+ console.print(error_msg, style="bold red")
426
+ save_to_conversation(error_msg)
427
+
428
+ # Log the full traceback for debugging
429
+ import traceback
430
+
431
+ traceback_msg = f"Full traceback:\n{traceback.format_exc()}"
432
+ console.print(traceback_msg, style="dim red")
433
+ save_to_conversation(traceback_msg)
434
+ save_to_conversation("─" * TERMINAL_WIDTH)
435
+ save_to_conversation("") # Empty line
436
+ continue # Continue conversation instead of crashing
437
+
438
+
439
+ # def load_agent(agent_class: str) -> Agent | None:
440
+ # from naas_abi import modules
441
+
442
+ # if agent_class is None:
443
+ # print(
444
+ # "No agent class provided. Please set the AGENT_CLASS environment variable."
445
+ # )
446
+ # return
447
+
448
+ # for module in modules:
449
+ # for agent in module.agents:
450
+ # if agent.__class__.__name__ == agent_class:
451
+ # agent.on_tool_usage(lambda message: print_tool_usage(message))
452
+ # agent.on_tool_response(on_tool_response)
453
+ # agent.on_ai_message(on_ai_message)
454
+
455
+ # return agent
456
+
457
+ # return None
458
+
459
+
460
+ # def list_available_agents():
461
+ # from naas_abi import modules
462
+
463
+ # print("\nAvailable agents:\n")
464
+ # agents = []
465
+ # for module in modules:
466
+ # for agent in module.agents:
467
+ # agents.append(agent.__class__.__name__)
468
+
469
+ # # Sort the agents alphabetically
470
+ # agents.sort()
471
+
472
+ # # Print the agents
473
+ # for agent in agents:
474
+ # print(f" - {agent}")
475
+
476
+
477
+ # class ConsoleLoader:
478
+ # def start(self, message: str):
479
+ # # Matrix-style startup animation
480
+ # self.loading = True
481
+
482
+ # def startup_loader():
483
+ # i = 0
484
+ # while self.loading:
485
+ # dots_count = i % 4 # 0, 1, 2, 3, then repeat
486
+ # if dots_count == 0:
487
+ # dots = " " # No dots, just spaces
488
+ # else:
489
+ # dots = "." * dots_count + " " * (
490
+ # 3 - dots_count
491
+ # ) # Pad to 3 char width
492
+ # print(f"\r\033[92m{message}{dots}\033[0m", end="", flush=True)
493
+ # time.sleep(0.5)
494
+ # i += 1
495
+
496
+ # # Start the animation
497
+ # self.loader_thread = threading.Thread(target=startup_loader)
498
+ # self.loader_thread.daemon = True # Make thread die when main thread exits
499
+ # self.loader_thread.start()
500
+
501
+ # def stop(self):
502
+ # self.loading = False
503
+ # self.loader_thread.join()
504
+
505
+ # # Clear the loading line
506
+ # print("\r" + " " * 20 + "\r", end="", flush=True)
507
+
508
+
509
+ # def generic_run_agent(agent_class: Optional[str] = None) -> None:
510
+ # """Run an agent dynamically loaded from the src/modules directory.
511
+
512
+ # This method provides a generic way to run any agent that is loaded from the modules
513
+ # directory, eliminating the need to create individual run functions for each agent.
514
+ # The agents are automatically discovered and loaded through the module system.
515
+
516
+ # Args:
517
+ # agent_class (str, optional): The class name of the agent to run. If None, will
518
+ # print an error message. The class name should match exactly with the agent's
519
+ # class name in the modules.
520
+
521
+ # Example:
522
+ # >>> generic_run_agent("AbiAgent") # Runs the AbiAgent agent
523
+ # >>> generic_run_agent("ContentAssistant") # Runs the ContentAssistant agent
524
+
525
+ # Note:
526
+ # This replaces the need for individual run_*_agent() functions by dynamically
527
+ # finding and running the requested agent from the loaded modules. The agent
528
+ # must be properly registered in a module under src/modules for this to work.
529
+ # """
530
+
531
+ # assert agent_class is not None, "Agent class is required"
532
+
533
+ # agent = load_agent(agent_class)
534
+
535
+ # if agent is None:
536
+ # print(f"Agent {agent_class} not found")
537
+ # list_available_agents()
538
+
539
+ # return
540
+
541
+ # run_agent(agent)
542
+
543
+
544
+ # if __name__ == "__main__":
545
+ # import sys
546
+
547
+ # # Get the function name from command line argument
548
+ # if len(sys.argv) > 1:
549
+ # function_name = sys.argv[1]
550
+ # if function_name in globals():
551
+ # globals()[function_name](*sys.argv[2:])
552
+ # else:
553
+ # print(f"Function {function_name} not found")
554
+ # else:
555
+ # print("Please specify a function to run")