signalwire-agents 0.1.12__py3-none-any.whl → 0.1.14__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.
@@ -46,8 +46,14 @@ Examples:
46
46
  swaig-test examples/my_agent.py --dump-swml --raw | jq '.sections.main[1].ai.SWAIG.functions'
47
47
  """
48
48
 
49
+ # CRITICAL: Set environment variable BEFORE any imports to suppress logs for --raw
49
50
  import sys
50
51
  import os
52
+
53
+ if "--raw" in sys.argv or "--dump-swml" in sys.argv:
54
+ os.environ["SIGNALWIRE_LOG_MODE"] = "off"
55
+
56
+ import warnings
51
57
  import json
52
58
  import importlib.util
53
59
  import argparse
@@ -56,13 +62,18 @@ import time
56
62
  import hashlib
57
63
  import re
58
64
  import requests
59
- import warnings
60
65
  from pathlib import Path
61
66
  from typing import Dict, Any, Optional, List, Tuple
62
67
  from datetime import datetime
63
68
  import logging
64
69
  import inspect
65
70
 
71
+ # Store original print function before any potential suppression
72
+ original_print = print
73
+
74
+ # Add the parent directory to the path so we can import signalwire_agents
75
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
76
+
66
77
  try:
67
78
  # Try to import the AgentBase class
68
79
  from signalwire_agents.core.agent_base import AgentBase
@@ -72,22 +83,15 @@ except ImportError:
72
83
  AgentBase = None
73
84
  SwaigFunctionResult = None
74
85
 
75
- # Early setup for raw mode using the central logging system
76
- if "--raw" in sys.argv:
77
- # Use the central logging system's OFF mode for raw output
78
- # This eliminates all logging output without complex suppression hacks
79
- os.environ["SIGNALWIRE_LOG_MODE"] = "off"
80
- warnings.filterwarnings("ignore")
81
-
82
- # Store original print function before any potential suppression
83
- original_print = print
84
-
85
- # Add the parent directory to the path so we can import signalwire_agents
86
- sys.path.insert(0, str(Path(__file__).parent.parent.parent))
87
-
88
- from signalwire_agents import AgentBase
89
- from signalwire_agents.core.function_result import SwaigFunctionResult
90
-
86
+ # Reset logging configuration if --raw flag was set
87
+ # This must happen AFTER signalwire_agents imports but BEFORE any logging is used
88
+ if "--raw" in sys.argv or "--dump-swml" in sys.argv:
89
+ try:
90
+ from signalwire_agents.core.logging_config import reset_logging_configuration, configure_logging
91
+ reset_logging_configuration()
92
+ configure_logging() # Reconfigure with the new environment variable
93
+ except ImportError:
94
+ pass
91
95
 
92
96
  # ===== MOCK REQUEST OBJECTS FOR DYNAMIC AGENT TESTING =====
93
97
 
@@ -666,7 +670,6 @@ def handle_dump_swml(agent: 'AgentBase', args: argparse.Namespace) -> int:
666
670
  Exit code (0 for success, 1 for error)
667
671
  """
668
672
  if not args.raw:
669
- print("\nGenerating SWML document...")
670
673
  if args.verbose:
671
674
  print(f"Agent: {agent.get_name()}")
672
675
  print(f"Route: {agent.route}")
@@ -767,20 +770,13 @@ def handle_dump_swml(agent: 'AgentBase', args: argparse.Namespace) -> int:
767
770
  # Output only the raw JSON for piping to jq/yq
768
771
  print(swml_doc)
769
772
  else:
770
- # Normal output with headers
771
- print("SWML Document:")
772
- print("=" * 50)
773
- print(swml_doc)
774
- print("=" * 50)
775
-
776
- if args.verbose:
777
- # Parse and show formatted JSON for better readability
778
- try:
779
- swml_parsed = json.loads(swml_doc)
780
- print("\nFormatted SWML:")
781
- print(json.dumps(swml_parsed, indent=2))
782
- except json.JSONDecodeError:
783
- print("\nNote: SWML document is not valid JSON format")
773
+ # Output formatted JSON (like raw but pretty-printed)
774
+ try:
775
+ swml_parsed = json.loads(swml_doc)
776
+ print(json.dumps(swml_parsed, indent=2))
777
+ except json.JSONDecodeError:
778
+ # If not valid JSON, show raw
779
+ print(swml_doc)
784
780
 
785
781
  return 0
786
782
 
@@ -1375,6 +1371,94 @@ def execute_external_webhook_function(func: 'SWAIGFunction', function_name: str,
1375
1371
  return {"error": error_msg}
1376
1372
 
1377
1373
 
1374
+ def display_agent_tools(agent: 'AgentBase', verbose: bool = False) -> None:
1375
+ """
1376
+ Display the available SWAIG functions for an agent
1377
+
1378
+ Args:
1379
+ agent: The agent instance
1380
+ verbose: Whether to show verbose details
1381
+ """
1382
+ print("\nAvailable SWAIG functions:")
1383
+ if hasattr(agent, '_swaig_functions') and agent._swaig_functions:
1384
+ for name, func in agent._swaig_functions.items():
1385
+ if isinstance(func, dict):
1386
+ # DataMap function
1387
+ description = func.get('description', 'DataMap function (serverless)')
1388
+ print(f" {name} - {description}")
1389
+
1390
+ # Show parameters for DataMap functions
1391
+ if 'parameters' in func and func['parameters']:
1392
+ params = func['parameters']
1393
+ # Handle both formats: direct properties dict or full schema
1394
+ if 'properties' in params:
1395
+ properties = params['properties']
1396
+ required_fields = params.get('required', [])
1397
+ else:
1398
+ properties = params
1399
+ required_fields = []
1400
+
1401
+ if properties:
1402
+ print(f" Parameters:")
1403
+ for param_name, param_def in properties.items():
1404
+ param_type = param_def.get('type', 'unknown')
1405
+ param_desc = param_def.get('description', 'No description')
1406
+ is_required = param_name in required_fields
1407
+ required_marker = " (required)" if is_required else ""
1408
+ print(f" {param_name} ({param_type}){required_marker}: {param_desc}")
1409
+ else:
1410
+ print(f" Parameters: None")
1411
+ else:
1412
+ print(f" Parameters: None")
1413
+
1414
+ if verbose:
1415
+ print(f" Config: {json.dumps(func, indent=6)}")
1416
+ else:
1417
+ # Regular SWAIG function
1418
+ func_type = ""
1419
+ if hasattr(func, 'webhook_url') and func.webhook_url and func.is_external:
1420
+ func_type = " (EXTERNAL webhook)"
1421
+ elif hasattr(func, 'webhook_url') and func.webhook_url:
1422
+ func_type = " (webhook)"
1423
+ else:
1424
+ func_type = " (LOCAL webhook)"
1425
+
1426
+ print(f" {name} - {func.description}{func_type}")
1427
+
1428
+ # Show external URL if applicable
1429
+ if hasattr(func, 'webhook_url') and func.webhook_url and func.is_external:
1430
+ print(f" External URL: {func.webhook_url}")
1431
+
1432
+ # Show parameters
1433
+ if hasattr(func, 'parameters') and func.parameters:
1434
+ params = func.parameters
1435
+ # Handle both formats: direct properties dict or full schema
1436
+ if 'properties' in params:
1437
+ properties = params['properties']
1438
+ required_fields = params.get('required', [])
1439
+ else:
1440
+ properties = params
1441
+ required_fields = []
1442
+
1443
+ if properties:
1444
+ print(f" Parameters:")
1445
+ for param_name, param_def in properties.items():
1446
+ param_type = param_def.get('type', 'unknown')
1447
+ param_desc = param_def.get('description', 'No description')
1448
+ is_required = param_name in required_fields
1449
+ required_marker = " (required)" if is_required else ""
1450
+ print(f" {param_name} ({param_type}){required_marker}: {param_desc}")
1451
+ else:
1452
+ print(f" Parameters: None")
1453
+ else:
1454
+ print(f" Parameters: None")
1455
+
1456
+ if verbose:
1457
+ print(f" Function object: {func}")
1458
+ else:
1459
+ print(" No SWAIG functions registered")
1460
+
1461
+
1378
1462
  def discover_agents_in_file(agent_path: str) -> List[Dict[str, Any]]:
1379
1463
  """
1380
1464
  Discover all available agents in a Python file without instantiating them
@@ -2315,7 +2399,29 @@ Examples:
2315
2399
 
2316
2400
  print()
2317
2401
 
2318
- if len(agents) > 1:
2402
+ # Show tools if there's only one agent or if --agent-class is specified
2403
+ show_tools = False
2404
+ selected_agent = None
2405
+
2406
+ if len(agents) == 1:
2407
+ # Single agent - show tools automatically
2408
+ show_tools = True
2409
+ selected_agent = agents[0]['object']
2410
+ print("This file contains a single agent, no --agent-class needed.")
2411
+ elif args.agent_class:
2412
+ # Specific agent class requested - show tools for that agent
2413
+ for agent_info in agents:
2414
+ if agent_info['class_name'] == args.agent_class:
2415
+ show_tools = True
2416
+ selected_agent = agent_info['object']
2417
+ break
2418
+
2419
+ if not selected_agent:
2420
+ print(f"Error: Agent class '{args.agent_class}' not found.")
2421
+ print(f"Available agents: {[a['class_name'] for a in agents]}")
2422
+ return 1
2423
+ else:
2424
+ # Multiple agents, no specific class - show usage examples
2319
2425
  print("To use a specific agent with this tool:")
2320
2426
  print(f" swaig-test {args.agent_path} [tool_name] [args] --agent-class <AgentClassName>")
2321
2427
  print()
@@ -2324,8 +2430,24 @@ Examples:
2324
2430
  print(f" swaig-test {args.agent_path} --list-tools --agent-class {agent_info['class_name']}")
2325
2431
  print(f" swaig-test {args.agent_path} --dump-swml --agent-class {agent_info['class_name']}")
2326
2432
  print()
2327
- else:
2328
- print("This file contains a single agent, no --agent-class needed.")
2433
+
2434
+ # Show tools if we have a selected agent
2435
+ if show_tools and selected_agent:
2436
+ try:
2437
+ # If it's a class, try to instantiate it
2438
+ if not isinstance(selected_agent, AgentBase):
2439
+ if isinstance(selected_agent, type) and issubclass(selected_agent, AgentBase):
2440
+ selected_agent = selected_agent()
2441
+ else:
2442
+ print(f"Warning: Cannot instantiate agent to show tools")
2443
+ return 0
2444
+
2445
+ display_agent_tools(selected_agent, args.verbose)
2446
+ except Exception as e:
2447
+ print(f"Warning: Could not load agent tools: {e}")
2448
+ if args.verbose:
2449
+ import traceback
2450
+ traceback.print_exc()
2329
2451
 
2330
2452
  return 0
2331
2453
 
@@ -2374,84 +2496,7 @@ Examples:
2374
2496
 
2375
2497
  # List tools if requested
2376
2498
  if args.list_tools:
2377
- print("\nAvailable SWAIG functions:")
2378
- if hasattr(agent, '_swaig_functions') and agent._swaig_functions:
2379
- for name, func in agent._swaig_functions.items():
2380
- if isinstance(func, dict):
2381
- # DataMap function
2382
- description = func.get('description', 'DataMap function (serverless)')
2383
- print(f" {name} - {description}")
2384
-
2385
- # Show parameters for DataMap functions
2386
- if 'parameters' in func and func['parameters']:
2387
- params = func['parameters']
2388
- # Handle both formats: direct properties dict or full schema
2389
- if 'properties' in params:
2390
- properties = params['properties']
2391
- required_fields = params.get('required', [])
2392
- else:
2393
- properties = params
2394
- required_fields = []
2395
-
2396
- if properties:
2397
- print(f" Parameters:")
2398
- for param_name, param_def in properties.items():
2399
- param_type = param_def.get('type', 'unknown')
2400
- param_desc = param_def.get('description', 'No description')
2401
- is_required = param_name in required_fields
2402
- required_marker = " (required)" if is_required else ""
2403
- print(f" {param_name} ({param_type}){required_marker}: {param_desc}")
2404
- else:
2405
- print(f" Parameters: None")
2406
- else:
2407
- print(f" Parameters: None")
2408
-
2409
- if args.verbose:
2410
- print(f" Config: {json.dumps(func, indent=6)}")
2411
- else:
2412
- # Regular SWAIG function
2413
- func_type = ""
2414
- if hasattr(func, 'webhook_url') and func.webhook_url and func.is_external:
2415
- func_type = " (EXTERNAL webhook)"
2416
- elif hasattr(func, 'webhook_url') and func.webhook_url:
2417
- func_type = " (webhook)"
2418
- else:
2419
- func_type = " (LOCAL webhook)"
2420
-
2421
- print(f" {name} - {func.description}{func_type}")
2422
-
2423
- # Show external URL if applicable
2424
- if hasattr(func, 'webhook_url') and func.webhook_url and func.is_external:
2425
- print(f" External URL: {func.webhook_url}")
2426
-
2427
- # Show parameters
2428
- if hasattr(func, 'parameters') and func.parameters:
2429
- params = func.parameters
2430
- # Handle both formats: direct properties dict or full schema
2431
- if 'properties' in params:
2432
- properties = params['properties']
2433
- required_fields = params.get('required', [])
2434
- else:
2435
- properties = params
2436
- required_fields = []
2437
-
2438
- if properties:
2439
- print(f" Parameters:")
2440
- for param_name, param_def in properties.items():
2441
- param_type = param_def.get('type', 'unknown')
2442
- param_desc = param_def.get('description', 'No description')
2443
- is_required = param_name in required_fields
2444
- required_marker = " (required)" if is_required else ""
2445
- print(f" {param_name} ({param_type}){required_marker}: {param_desc}")
2446
- else:
2447
- print(f" Parameters: None")
2448
- else:
2449
- print(f" Parameters: None")
2450
-
2451
- if args.verbose:
2452
- print(f" Function object: {func}")
2453
- else:
2454
- print(" No SWAIG functions registered")
2499
+ display_agent_tools(agent, args.verbose)
2455
2500
  return 0
2456
2501
 
2457
2502
  # Dump SWML if requested
@@ -2606,4 +2651,4 @@ def console_entry_point():
2606
2651
 
2607
2652
 
2608
2653
  if __name__ == "__main__":
2609
- sys.exit(main())
2654
+ sys.exit(main())
@@ -44,31 +44,7 @@ except ImportError:
44
44
  "uvicorn is required. Install it with: pip install uvicorn"
45
45
  )
46
46
 
47
- try:
48
- import structlog
49
- # Configure structlog only if not already configured
50
- if not structlog.is_configured():
51
- structlog.configure(
52
- processors=[
53
- structlog.stdlib.filter_by_level,
54
- structlog.stdlib.add_logger_name,
55
- structlog.stdlib.add_log_level,
56
- structlog.stdlib.PositionalArgumentsFormatter(),
57
- structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
58
- structlog.processors.StackInfoRenderer(),
59
- structlog.processors.format_exc_info,
60
- structlog.processors.UnicodeDecoder(),
61
- structlog.dev.ConsoleRenderer()
62
- ],
63
- context_class=dict,
64
- logger_factory=structlog.stdlib.LoggerFactory(),
65
- wrapper_class=structlog.stdlib.BoundLogger,
66
- cache_logger_on_first_use=True,
67
- )
68
- except ImportError:
69
- raise ImportError(
70
- "structlog is required. Install it with: pip install structlog"
71
- )
47
+
72
48
 
73
49
  from signalwire_agents.core.pom_builder import PomBuilder
74
50
  from signalwire_agents.core.swaig_function import SWAIGFunction
@@ -82,8 +58,8 @@ from signalwire_agents.core.skill_manager import SkillManager
82
58
  from signalwire_agents.utils.schema_utils import SchemaUtils
83
59
  from signalwire_agents.core.logging_config import get_logger, get_execution_mode
84
60
 
85
- # Create a logger
86
- logger = structlog.get_logger("agent_base")
61
+ # Create a logger using centralized system
62
+ logger = get_logger("agent_base")
87
63
 
88
64
  class EphemeralAgentConfig:
89
65
  """
@@ -1363,8 +1339,8 @@ class AgentBase(SWMLService):
1363
1339
  host = self.host
1364
1340
  base_url = f"http://{host}:{self.port}{self.route}"
1365
1341
 
1366
- # Add auth if requested (only for server mode)
1367
- if include_auth and mode == 'server':
1342
+ # Add auth if requested (applies to all modes now)
1343
+ if include_auth:
1368
1344
  username, password = self._basic_auth
1369
1345
  url = urlparse(base_url)
1370
1346
  return url._replace(netloc=f"{username}:{password}@{url.netloc}").geturl()
@@ -1386,11 +1362,8 @@ class AgentBase(SWMLService):
1386
1362
  mode = get_execution_mode()
1387
1363
 
1388
1364
  if mode != 'server':
1389
- # In serverless mode, use the serverless-appropriate URL
1390
- base_url = self.get_full_url()
1391
-
1392
- # For serverless, we don't need auth in webhook URLs since auth is handled differently
1393
- # and we want to return the actual platform URL
1365
+ # In serverless mode, use the serverless-appropriate URL with auth
1366
+ base_url = self.get_full_url(include_auth=True)
1394
1367
 
1395
1368
  # Ensure the endpoint has a trailing slash to prevent redirects
1396
1369
  if endpoint in ["swaig", "post_prompt"]:
@@ -2053,8 +2026,6 @@ class AgentBase(SWMLService):
2053
2026
  Returns:
2054
2027
  Function execution result
2055
2028
  """
2056
- import structlog
2057
-
2058
2029
  # Use the existing logger
2059
2030
  req_log = self.log.bind(
2060
2031
  endpoint="serverless_swaig",
@@ -2227,8 +2198,6 @@ class AgentBase(SWMLService):
2227
2198
 
2228
2199
  self.log.info("callback_endpoint_registered", path=callback_path)
2229
2200
 
2230
- @classmethod
2231
-
2232
2201
  # ----------------------------------------------------------------------
2233
2202
  # AI Verb Configuration Methods
2234
2203
  # ----------------------------------------------------------------------
@@ -89,12 +89,44 @@ class StructuredLoggerWrapper:
89
89
  # Also support the 'warn' alias
90
90
  warn = warning
91
91
 
92
+ def bind(self, **kwargs) -> 'StructuredLoggerWrapper':
93
+ """
94
+ Create a new logger instance with bound context data
95
+
96
+ This maintains compatibility with structlog's bind() method.
97
+ The bound data will be included in all subsequent log messages.
98
+ """
99
+ # Create a new wrapper that includes the bound context
100
+ return BoundStructuredLoggerWrapper(self._logger, kwargs)
101
+
92
102
  # Support direct access to underlying logger attributes if needed
93
103
  def __getattr__(self, name: str) -> Any:
94
104
  """Delegate any unknown attributes to the underlying logger"""
95
105
  return getattr(self._logger, name)
96
106
 
97
107
 
108
+ class BoundStructuredLoggerWrapper(StructuredLoggerWrapper):
109
+ """
110
+ A structured logger wrapper that includes bound context data in all messages
111
+ """
112
+
113
+ def __init__(self, logger: logging.Logger, bound_data: Dict[str, Any]):
114
+ super().__init__(logger)
115
+ self._bound_data = bound_data
116
+
117
+ def _format_structured_message(self, message: str, **kwargs) -> str:
118
+ """Format a message with both bound data and additional keyword arguments"""
119
+ # Combine bound data with additional kwargs
120
+ all_kwargs = {**self._bound_data, **kwargs}
121
+ return super()._format_structured_message(message, **all_kwargs)
122
+
123
+ def bind(self, **kwargs) -> 'BoundStructuredLoggerWrapper':
124
+ """Create a new logger with additional bound context"""
125
+ # Combine existing bound data with new data
126
+ new_bound_data = {**self._bound_data, **kwargs}
127
+ return BoundStructuredLoggerWrapper(self._logger, new_bound_data)
128
+
129
+
98
130
  def get_execution_mode() -> str:
99
131
  """
100
132
  Determine the execution mode based on environment variables
@@ -111,6 +143,16 @@ def get_execution_mode() -> str:
111
143
  return 'server'
112
144
 
113
145
 
146
+ def reset_logging_configuration():
147
+ """
148
+ Reset the logging configuration flag to allow reconfiguration
149
+
150
+ This is useful when environment variables change after initial configuration.
151
+ """
152
+ global _logging_configured
153
+ _logging_configured = False
154
+
155
+
114
156
  def configure_logging():
115
157
  """
116
158
  Configure logging system once, globally, based on environment variables
@@ -182,31 +224,39 @@ def _configure_off_mode():
182
224
 
183
225
 
184
226
  def _configure_stderr_mode(log_level: str):
185
- """Configure logging to stderr"""
227
+ """Configure logging to stderr with colored formatting"""
186
228
  # Clear existing handlers
187
229
  logging.getLogger().handlers.clear()
188
230
 
189
231
  # Convert log level
190
232
  numeric_level = getattr(logging, log_level.upper(), logging.INFO)
191
233
 
192
- # Configure to stderr
193
- logging.basicConfig(
194
- stream=sys.stderr,
195
- level=numeric_level,
196
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
197
- )
234
+ # Create handler with colored formatter
235
+ handler = logging.StreamHandler(sys.stderr)
236
+ handler.setFormatter(ColoredFormatter())
237
+
238
+ # Configure root logger
239
+ root_logger = logging.getLogger()
240
+ root_logger.setLevel(numeric_level)
241
+ root_logger.addHandler(handler)
198
242
 
199
243
 
200
244
  def _configure_default_mode(log_level: str):
201
- """Configure standard logging behavior"""
245
+ """Configure standard logging behavior with colored formatting"""
246
+ # Clear existing handlers
247
+ logging.getLogger().handlers.clear()
248
+
202
249
  # Convert log level
203
250
  numeric_level = getattr(logging, log_level.upper(), logging.INFO)
204
251
 
205
- # Configure standard logging
206
- logging.basicConfig(
207
- level=numeric_level,
208
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
209
- )
252
+ # Create handler with colored formatter
253
+ handler = logging.StreamHandler()
254
+ handler.setFormatter(ColoredFormatter())
255
+
256
+ # Configure root logger
257
+ root_logger = logging.getLogger()
258
+ root_logger.setLevel(numeric_level)
259
+ root_logger.addHandler(handler)
210
260
 
211
261
 
212
262
  def get_logger(name: str) -> StructuredLoggerWrapper:
@@ -229,4 +279,83 @@ def get_logger(name: str) -> StructuredLoggerWrapper:
229
279
  python_logger = logging.getLogger(name)
230
280
 
231
281
  # Wrap it with our structured logging interface
232
- return StructuredLoggerWrapper(python_logger)
282
+ return StructuredLoggerWrapper(python_logger)
283
+
284
+
285
+ class ColoredFormatter(logging.Formatter):
286
+ """
287
+ A beautiful colored logging formatter that makes logs easy to read and visually appealing
288
+ """
289
+
290
+ # ANSI color codes
291
+ COLORS = {
292
+ 'DEBUG': '\033[36m', # Cyan
293
+ 'INFO': '\033[32m', # Green
294
+ 'WARNING': '\033[33m', # Yellow
295
+ 'ERROR': '\033[31m', # Red
296
+ 'CRITICAL': '\033[35m', # Magenta
297
+ 'RESET': '\033[0m', # Reset
298
+ 'BOLD': '\033[1m', # Bold
299
+ 'DIM': '\033[2m', # Dim
300
+ 'WHITE': '\033[37m', # White
301
+ 'BLUE': '\033[34m', # Blue
302
+ 'BLACK': '\033[30m', # Black (for brackets)
303
+ }
304
+
305
+ def __init__(self):
306
+ super().__init__()
307
+
308
+ def format(self, record):
309
+ # Check if we should use colors (not in raw mode, and stdout is a tty)
310
+ use_colors = (
311
+ hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() and
312
+ os.getenv('SIGNALWIRE_LOG_MODE') != 'off' and
313
+ '--raw' not in sys.argv and '--dump-swml' not in sys.argv
314
+ )
315
+
316
+ if use_colors:
317
+ # Get colors
318
+ level_color = self.COLORS.get(record.levelname, self.COLORS['WHITE'])
319
+ reset = self.COLORS['RESET']
320
+ dim = self.COLORS['DIM']
321
+ bold = self.COLORS['BOLD']
322
+ blue = self.COLORS['BLUE']
323
+ black = self.COLORS['BLACK']
324
+
325
+ # Format timestamp in a compact, readable way
326
+ timestamp = self.formatTime(record, '%H:%M:%S')
327
+
328
+ # Format level with appropriate color and consistent width
329
+ level_name = f"{level_color}{record.levelname:<8}{reset}"
330
+
331
+ # Format logger name - keep it short and readable
332
+ logger_name = record.name
333
+ if len(logger_name) > 15:
334
+ # Truncate long logger names but keep the end (most specific part)
335
+ logger_name = "..." + logger_name[-12:]
336
+
337
+ # Get function and line info if available
338
+ func_info = ""
339
+ if hasattr(record, 'funcName') and hasattr(record, 'lineno'):
340
+ func_name = getattr(record, 'funcName', '')
341
+ line_no = getattr(record, 'lineno', 0)
342
+ if func_name and func_name != '<module>':
343
+ func_info = f" {dim}({func_name}:{line_no}){reset}"
344
+
345
+ # Format the message
346
+ message = record.getMessage()
347
+
348
+ # Create the final formatted message with a clean, readable layout
349
+ formatted = (
350
+ f"{black}[{reset}{dim}{timestamp}{reset}{black}]{reset} "
351
+ f"{level_name} "
352
+ f"{blue}{logger_name:<15}{reset}"
353
+ f"{func_info} "
354
+ f"{message}"
355
+ )
356
+
357
+ return formatted
358
+ else:
359
+ # Non-colored format (fallback for files, pipes, etc.)
360
+ timestamp = self.formatTime(record, '%Y-%m-%d %H:%M:%S')
361
+ return f"{timestamp} {record.levelname:<8} {record.name} {record.getMessage()}"
@@ -8,7 +8,7 @@ See LICENSE file in the project root for full license information.
8
8
  """
9
9
 
10
10
  from typing import Dict, List, Type, Any, Optional
11
- import logging
11
+ from signalwire_agents.core.logging_config import get_logger
12
12
  from signalwire_agents.core.skill_base import SkillBase
13
13
 
14
14
  class SkillManager:
@@ -17,7 +17,7 @@ class SkillManager:
17
17
  def __init__(self, agent):
18
18
  self.agent = agent
19
19
  self.loaded_skills: Dict[str, SkillBase] = {}
20
- self.logger = logging.getLogger("skill_manager")
20
+ self.logger = get_logger("skill_manager")
21
21
 
22
22
  def load_skill(self, skill_name: str, skill_class: Type[SkillBase] = None, params: Optional[Dict[str, Any]] = None) -> tuple[bool, str]:
23
23
  """
@@ -15,6 +15,8 @@ from typing import Dict, Any, Optional, Callable, List, Type, Union
15
15
  import inspect
16
16
  import logging
17
17
 
18
+ # Import here to avoid circular imports
19
+ from signalwire_agents.core.function_result import SwaigFunctionResult
18
20
 
19
21
  class SWAIGFunction:
20
22
  """
@@ -101,9 +103,6 @@ class SWAIGFunction:
101
103
  # Call the handler with both args and raw_data
102
104
  result = self.handler(args, raw_data)
103
105
 
104
- # Import here to avoid circular imports
105
- from signalwire_agents.core.function_result import SwaigFunctionResult
106
-
107
106
  # Handle different result types - everything must end up as a SwaigFunctionResult
108
107
  if isinstance(result, SwaigFunctionResult):
109
108
  # Already a SwaigFunctionResult - just convert to dict