signalwire-agents 0.1.23__py3-none-any.whl → 0.1.25__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 (64) hide show
  1. signalwire_agents/__init__.py +1 -1
  2. signalwire_agents/agent_server.py +2 -1
  3. signalwire_agents/cli/config.py +61 -0
  4. signalwire_agents/cli/core/__init__.py +1 -0
  5. signalwire_agents/cli/core/agent_loader.py +254 -0
  6. signalwire_agents/cli/core/argparse_helpers.py +164 -0
  7. signalwire_agents/cli/core/dynamic_config.py +62 -0
  8. signalwire_agents/cli/execution/__init__.py +1 -0
  9. signalwire_agents/cli/execution/datamap_exec.py +437 -0
  10. signalwire_agents/cli/execution/webhook_exec.py +125 -0
  11. signalwire_agents/cli/output/__init__.py +1 -0
  12. signalwire_agents/cli/output/output_formatter.py +132 -0
  13. signalwire_agents/cli/output/swml_dump.py +177 -0
  14. signalwire_agents/cli/simulation/__init__.py +1 -0
  15. signalwire_agents/cli/simulation/data_generation.py +365 -0
  16. signalwire_agents/cli/simulation/data_overrides.py +187 -0
  17. signalwire_agents/cli/simulation/mock_env.py +271 -0
  18. signalwire_agents/cli/test_swaig.py +522 -2539
  19. signalwire_agents/cli/types.py +72 -0
  20. signalwire_agents/core/agent/__init__.py +1 -3
  21. signalwire_agents/core/agent/config/__init__.py +1 -3
  22. signalwire_agents/core/agent/prompt/manager.py +25 -7
  23. signalwire_agents/core/agent/tools/decorator.py +2 -0
  24. signalwire_agents/core/agent/tools/registry.py +8 -0
  25. signalwire_agents/core/agent_base.py +492 -3053
  26. signalwire_agents/core/function_result.py +31 -42
  27. signalwire_agents/core/mixins/__init__.py +28 -0
  28. signalwire_agents/core/mixins/ai_config_mixin.py +373 -0
  29. signalwire_agents/core/mixins/auth_mixin.py +287 -0
  30. signalwire_agents/core/mixins/prompt_mixin.py +345 -0
  31. signalwire_agents/core/mixins/serverless_mixin.py +368 -0
  32. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  33. signalwire_agents/core/mixins/state_mixin.py +219 -0
  34. signalwire_agents/core/mixins/tool_mixin.py +295 -0
  35. signalwire_agents/core/mixins/web_mixin.py +1130 -0
  36. signalwire_agents/core/skill_manager.py +3 -1
  37. signalwire_agents/core/swaig_function.py +10 -1
  38. signalwire_agents/core/swml_service.py +140 -58
  39. signalwire_agents/skills/README.md +452 -0
  40. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  41. signalwire_agents/skills/datasphere/README.md +210 -0
  42. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  43. signalwire_agents/skills/datetime/README.md +132 -0
  44. signalwire_agents/skills/joke/README.md +149 -0
  45. signalwire_agents/skills/math/README.md +161 -0
  46. signalwire_agents/skills/native_vector_search/skill.py +33 -13
  47. signalwire_agents/skills/play_background_file/README.md +218 -0
  48. signalwire_agents/skills/spider/README.md +236 -0
  49. signalwire_agents/skills/spider/__init__.py +4 -0
  50. signalwire_agents/skills/spider/skill.py +479 -0
  51. signalwire_agents/skills/swml_transfer/README.md +395 -0
  52. signalwire_agents/skills/swml_transfer/__init__.py +1 -0
  53. signalwire_agents/skills/swml_transfer/skill.py +257 -0
  54. signalwire_agents/skills/weather_api/README.md +178 -0
  55. signalwire_agents/skills/web_search/README.md +163 -0
  56. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  57. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/METADATA +47 -2
  58. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/RECORD +62 -22
  59. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/entry_points.txt +1 -1
  60. signalwire_agents/core/agent/config/ephemeral.py +0 -176
  61. signalwire_agents-0.1.23.data/data/schema.json +0 -5611
  62. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/WHEEL +0 -0
  63. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/licenses/LICENSE +0 -0
  64. {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/top_level.txt +0 -0
@@ -18,7 +18,7 @@ A package for building AI agents using SignalWire's AI and SWML capabilities.
18
18
  from .core.logging_config import configure_logging
19
19
  configure_logging()
20
20
 
21
- __version__ = "0.1.23"
21
+ __version__ = "0.1.25"
22
22
 
23
23
  # Import core classes for easier access
24
24
  from .core.agent_base import AgentBase
@@ -610,8 +610,9 @@ class AgentServer:
610
610
  self.logger.info(f"Starting server on {protocol}://{display_host}")
611
611
  for route, agent in self.agents.items():
612
612
  username, password = agent.get_basic_auth_credentials()
613
+ agent_url = agent.get_full_url(include_auth=False)
613
614
  self.logger.info(f"Agent '{agent.get_name()}' available at:")
614
- self.logger.info(f"URL: {protocol}://{display_host}{route}")
615
+ self.logger.info(f"URL: {agent_url}")
615
616
  self.logger.info(f"Basic Auth: {username}:{password}")
616
617
 
617
618
  # Start the server with or without SSL
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Configuration constants for the CLI tools
4
+ """
5
+
6
+ # Default values for call configuration
7
+ DEFAULT_CALL_TYPE = "webrtc"
8
+ DEFAULT_CALL_DIRECTION = "inbound"
9
+ DEFAULT_CALL_STATE = "created"
10
+ DEFAULT_HTTP_METHOD = "POST"
11
+ DEFAULT_TOKEN_EXPIRY = 3600
12
+
13
+ # Default fake data values
14
+ DEFAULT_PROJECT_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
15
+ DEFAULT_SPACE_ID = "zzzzzzzz-yyyy-xxxx-wwww-vvvvvvvvvvvv"
16
+ DEFAULT_SPACE_NAME = "example-space"
17
+ DEFAULT_ENVIRONMENT = "production"
18
+
19
+ # Request timeouts
20
+ HTTP_REQUEST_TIMEOUT = 30
21
+
22
+ # Output formatting
23
+ RESULT_LINE_SEP = "-" * 60
24
+
25
+ # Platform-specific constants
26
+ SERVERLESS_PLATFORMS = ["lambda", "cgi", "cloud_function", "azure_function"]
27
+
28
+ # AWS Lambda defaults
29
+ AWS_DEFAULT_REGION = "us-east-1"
30
+ AWS_DEFAULT_STAGE = "prod"
31
+
32
+ # Google Cloud defaults
33
+ GCP_DEFAULT_REGION = "us-central1"
34
+
35
+ # Error messages
36
+ ERROR_MISSING_AGENT = "Error: Missing required argument."
37
+ ERROR_MULTIPLE_AGENTS = "Multiple agents found in file. Please specify --agent-class"
38
+ ERROR_NO_AGENTS = "No agents found in file: {file_path}"
39
+ ERROR_AGENT_NOT_FOUND = "Agent class '{class_name}' not found in file: {file_path}"
40
+ ERROR_FUNCTION_NOT_FOUND = "Function '{function_name}' not found in agent"
41
+ ERROR_CGI_HOST_REQUIRED = "CGI simulation requires --cgi-host"
42
+
43
+ # Help messages
44
+ HELP_DESCRIPTION = "Test SWAIG functions and generate SWML documents for SignalWire AI agents"
45
+ HELP_EPILOG_SHORT = """
46
+ examples:
47
+ # Execute a function
48
+ %(prog)s agent.py --exec search --query "test" --limit 5
49
+
50
+ # Generate SWML
51
+ %(prog)s agent.py --dump-swml --raw | jq '.'
52
+
53
+ # Test with specific agent
54
+ %(prog)s multi_agent.py --agent-class MattiAgent --list-tools
55
+
56
+ # Simulate serverless
57
+ %(prog)s agent.py --simulate-serverless lambda --exec my_function
58
+
59
+ For platform-specific options: %(prog)s --help-platforms
60
+ For more examples: %(prog)s --help-examples
61
+ """
@@ -0,0 +1 @@
1
+ """Core functionality for CLI tools"""
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent discovery and loading functionality
4
+ """
5
+
6
+ import importlib.util
7
+ from pathlib import Path
8
+ from typing import List, Dict, Any, Optional
9
+
10
+ # Import after checking if available
11
+ try:
12
+ from signalwire_agents.core.agent_base import AgentBase
13
+ AGENT_BASE_AVAILABLE = True
14
+ except ImportError:
15
+ AgentBase = None
16
+ AGENT_BASE_AVAILABLE = False
17
+
18
+
19
+ def discover_agents_in_file(agent_path: str) -> List[Dict[str, Any]]:
20
+ """
21
+ Discover all available agents in a Python file without instantiating them
22
+
23
+ Args:
24
+ agent_path: Path to the Python file containing agents
25
+
26
+ Returns:
27
+ List of dictionaries with agent information
28
+
29
+ Raises:
30
+ ImportError: If the file cannot be imported
31
+ FileNotFoundError: If the file doesn't exist
32
+ """
33
+ if not AGENT_BASE_AVAILABLE:
34
+ raise ImportError("AgentBase not available. Please install signalwire-agents package.")
35
+
36
+ agent_path = Path(agent_path).resolve()
37
+
38
+ if not agent_path.exists():
39
+ raise FileNotFoundError(f"Agent file not found: {agent_path}")
40
+
41
+ if not agent_path.suffix == '.py':
42
+ raise ValueError(f"Agent file must be a Python file (.py): {agent_path}")
43
+
44
+ # Load the module, but prevent main() execution by setting __name__ to something other than "__main__"
45
+ spec = importlib.util.spec_from_file_location("agent_module", agent_path)
46
+ module = importlib.util.module_from_spec(spec)
47
+
48
+ try:
49
+ # Set __name__ to prevent if __name__ == "__main__": blocks from running
50
+ module.__name__ = "agent_module"
51
+ spec.loader.exec_module(module)
52
+ except Exception as e:
53
+ raise ImportError(f"Failed to load agent module: {e}")
54
+
55
+ agents_found = []
56
+
57
+ # Look for AgentBase instances
58
+ for name, obj in vars(module).items():
59
+ if isinstance(obj, AgentBase):
60
+ agents_found.append({
61
+ 'name': name,
62
+ 'class_name': obj.__class__.__name__,
63
+ 'type': 'instance',
64
+ 'agent_name': getattr(obj, 'name', 'Unknown'),
65
+ 'route': getattr(obj, 'route', 'Unknown'),
66
+ 'description': obj.__class__.__doc__,
67
+ 'object': obj
68
+ })
69
+
70
+ # Look for AgentBase subclasses (that could be instantiated)
71
+ for name, obj in vars(module).items():
72
+ if (isinstance(obj, type) and
73
+ issubclass(obj, AgentBase) and
74
+ obj != AgentBase):
75
+ # Check if we already found an instance of this class
76
+ instance_found = any(agent['class_name'] == name for agent in agents_found)
77
+ if not instance_found:
78
+ try:
79
+ # Try to get class information without instantiating
80
+ agent_info = {
81
+ 'name': name,
82
+ 'class_name': name,
83
+ 'type': 'class',
84
+ 'agent_name': 'Unknown (not instantiated)',
85
+ 'route': 'Unknown (not instantiated)',
86
+ 'description': obj.__doc__,
87
+ 'object': obj
88
+ }
89
+ agents_found.append(agent_info)
90
+ except Exception:
91
+ # If we can't get info, still record that the class exists
92
+ agents_found.append({
93
+ 'name': name,
94
+ 'class_name': name,
95
+ 'type': 'class',
96
+ 'agent_name': 'Unknown (not instantiated)',
97
+ 'route': 'Unknown (not instantiated)',
98
+ 'description': obj.__doc__ or 'No description available',
99
+ 'object': obj
100
+ })
101
+
102
+ return agents_found
103
+
104
+
105
+ def load_agent_from_file(agent_path: str, agent_class_name: Optional[str] = None) -> 'AgentBase':
106
+ """
107
+ Load an agent from a Python file
108
+
109
+ Args:
110
+ agent_path: Path to the Python file containing the agent
111
+ agent_class_name: Optional name of the agent class to instantiate
112
+
113
+ Returns:
114
+ AgentBase instance
115
+
116
+ Raises:
117
+ ImportError: If the file cannot be imported
118
+ ValueError: If no agent is found in the file
119
+ """
120
+ if not AGENT_BASE_AVAILABLE:
121
+ raise ImportError("AgentBase not available. Please install signalwire-agents package.")
122
+
123
+ agent_path = Path(agent_path).resolve()
124
+
125
+ if not agent_path.exists():
126
+ raise FileNotFoundError(f"Agent file not found: {agent_path}")
127
+
128
+ if not agent_path.suffix == '.py':
129
+ raise ValueError(f"Agent file must be a Python file (.py): {agent_path}")
130
+
131
+ # Load the module, but prevent main() execution by setting __name__ to something other than "__main__"
132
+ spec = importlib.util.spec_from_file_location("agent_module", agent_path)
133
+ module = importlib.util.module_from_spec(spec)
134
+
135
+ try:
136
+ # Set __name__ to prevent if __name__ == "__main__": blocks from running
137
+ module.__name__ = "agent_module"
138
+ spec.loader.exec_module(module)
139
+ except Exception as e:
140
+ raise ImportError(f"Failed to load agent module: {e}")
141
+
142
+ # Find the agent instance
143
+ agent = None
144
+
145
+ # If agent_class_name is specified, try to instantiate that specific class first
146
+ if agent_class_name:
147
+ if hasattr(module, agent_class_name):
148
+ obj = getattr(module, agent_class_name)
149
+ if isinstance(obj, type) and issubclass(obj, AgentBase) and obj != AgentBase:
150
+ try:
151
+ agent = obj()
152
+ if agent and not agent.route.endswith('dummy'): # Avoid test agents with dummy routes
153
+ pass # Successfully created specific agent
154
+ else:
155
+ agent = obj() # Create anyway if requested specifically
156
+ except Exception as e:
157
+ raise ValueError(f"Failed to instantiate agent class '{agent_class_name}': {e}")
158
+ else:
159
+ raise ValueError(f"'{agent_class_name}' is not a valid AgentBase subclass")
160
+ else:
161
+ raise ValueError(f"Agent class '{agent_class_name}' not found in {agent_path}")
162
+
163
+ # Strategy 1: Look for 'agent' variable (most common pattern)
164
+ if agent is None and hasattr(module, 'agent') and isinstance(module.agent, AgentBase):
165
+ agent = module.agent
166
+
167
+ # Strategy 2: Look for any AgentBase instance in module globals
168
+ if agent is None:
169
+ agents_found = []
170
+ for name, obj in vars(module).items():
171
+ if isinstance(obj, AgentBase):
172
+ agents_found.append((name, obj))
173
+
174
+ if len(agents_found) == 1:
175
+ agent = agents_found[0][1]
176
+ elif len(agents_found) > 1:
177
+ # Multiple agents found, prefer one named 'agent'
178
+ for name, obj in agents_found:
179
+ if name == 'agent':
180
+ agent = obj
181
+ break
182
+ # If no 'agent' variable, use the first one
183
+ if agent is None:
184
+ agent = agents_found[0][1]
185
+ print(f"Warning: Multiple agents found, using '{agents_found[0][0]}'")
186
+ print(f"Hint: Use --agent-class parameter to choose specific agent")
187
+
188
+ # Strategy 3: Look for AgentBase subclass and try to instantiate it
189
+ if agent is None:
190
+ agent_classes_found = []
191
+ for name, obj in vars(module).items():
192
+ if (isinstance(obj, type) and
193
+ issubclass(obj, AgentBase) and
194
+ obj != AgentBase):
195
+ agent_classes_found.append((name, obj))
196
+
197
+ if len(agent_classes_found) == 1:
198
+ try:
199
+ agent = agent_classes_found[0][1]()
200
+ except Exception as e:
201
+ print(f"Warning: Failed to instantiate {agent_classes_found[0][0]}: {e}")
202
+ elif len(agent_classes_found) > 1:
203
+ # Multiple agent classes found
204
+ class_names = [name for name, _ in agent_classes_found]
205
+ raise ValueError(f"Multiple agent classes found: {', '.join(class_names)}. "
206
+ f"Please specify which agent class to use with --agent-class parameter. "
207
+ f"Usage: swaig-test {agent_path} [tool_name] [args] --agent-class <AgentClassName>")
208
+ else:
209
+ # Try instantiating any AgentBase class we can find
210
+ for name, obj in vars(module).items():
211
+ if (isinstance(obj, type) and
212
+ issubclass(obj, AgentBase) and
213
+ obj != AgentBase):
214
+ try:
215
+ agent = obj()
216
+ break
217
+ except Exception as e:
218
+ print(f"Warning: Failed to instantiate {name}: {e}")
219
+
220
+ # Strategy 4: Try calling a modified main() function that doesn't start the server
221
+ if agent is None and hasattr(module, 'main'):
222
+ print("Warning: No agent instance found, attempting to call main() without server startup")
223
+ try:
224
+ # Temporarily patch AgentBase.serve to prevent server startup
225
+ original_serve = AgentBase.serve
226
+ captured_agent = []
227
+
228
+ def mock_serve(self, *args, **kwargs):
229
+ captured_agent.append(self)
230
+ print(f" (Intercepted serve() call, agent captured for testing)")
231
+ return self
232
+
233
+ AgentBase.serve = mock_serve
234
+
235
+ try:
236
+ result = module.main()
237
+ if isinstance(result, AgentBase):
238
+ agent = result
239
+ elif captured_agent:
240
+ agent = captured_agent[0]
241
+ finally:
242
+ # Restore original serve method
243
+ AgentBase.serve = original_serve
244
+
245
+ except Exception as e:
246
+ print(f"Warning: Failed to call main() function: {e}")
247
+
248
+ if agent is None:
249
+ raise ValueError(f"No agent found in {agent_path}. The file must contain either:\n"
250
+ f"- An AgentBase instance (e.g., agent = MyAgent())\n"
251
+ f"- An AgentBase subclass that can be instantiated\n"
252
+ f"- A main() function that creates and returns an agent")
253
+
254
+ return agent
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Custom argument parsing and function argument parsing
4
+ """
5
+
6
+ import sys
7
+ import argparse
8
+ from typing import List, Dict, Any
9
+
10
+
11
+ class CustomArgumentParser(argparse.ArgumentParser):
12
+ """Custom ArgumentParser with better error handling"""
13
+
14
+ def __init__(self, *args, **kwargs):
15
+ super().__init__(*args, **kwargs)
16
+ self._suppress_usage = False
17
+
18
+ def _print_message(self, message, file=None):
19
+ """Override to suppress usage output for specific errors"""
20
+ if self._suppress_usage:
21
+ return
22
+ super()._print_message(message, file)
23
+
24
+ def error(self, message):
25
+ """Override error method to provide user-friendly error messages"""
26
+ if "required" in message.lower() and "agent_path" in message:
27
+ self._suppress_usage = True
28
+ print("Error: Missing required argument.")
29
+ print()
30
+ print(f"Usage: {self.prog} <agent_path> [options]")
31
+ print()
32
+ print("Examples:")
33
+ print(f" {self.prog} examples/my_agent.py --list-tools")
34
+ print(f" {self.prog} examples/my_agent.py --dump-swml")
35
+ print(f" {self.prog} examples/my_agent.py --exec my_function --param value")
36
+ print()
37
+ print(f"For full help: {self.prog} --help")
38
+ sys.exit(2)
39
+ else:
40
+ # For other errors, use the default behavior
41
+ super().error(message)
42
+
43
+ def print_usage(self, file=None):
44
+ """Override print_usage to suppress output when we want custom error handling"""
45
+ if self._suppress_usage:
46
+ return
47
+ super().print_usage(file)
48
+
49
+ def parse_args(self, args=None, namespace=None):
50
+ """Override parse_args to provide custom error handling for missing arguments"""
51
+ # Check if no arguments provided (just the program name)
52
+ if args is None:
53
+ args = sys.argv[1:]
54
+
55
+ # If no arguments provided, show custom error
56
+ if not args:
57
+ print("Error: Missing required argument.")
58
+ print()
59
+ print(f"Usage: {self.prog} <agent_path> [options]")
60
+ print()
61
+ print("Examples:")
62
+ print(f" {self.prog} examples/my_agent.py --list-tools")
63
+ print(f" {self.prog} examples/my_agent.py --dump-swml")
64
+ print(f" {self.prog} examples/my_agent.py --exec my_function --param value")
65
+ print()
66
+ print(f"For full help: {self.prog} --help")
67
+ sys.exit(2)
68
+
69
+ # Otherwise, use default parsing
70
+ return super().parse_args(args, namespace)
71
+
72
+
73
+ def parse_function_arguments(function_args_list: List[str], func_schema: Dict[str, Any]) -> Dict[str, Any]:
74
+ """
75
+ Parse function arguments from command line with type coercion based on schema
76
+
77
+ Args:
78
+ function_args_list: List of command line arguments after --args
79
+ func_schema: Function schema with parameter definitions
80
+
81
+ Returns:
82
+ Dictionary of parsed function arguments
83
+ """
84
+ parsed_args = {}
85
+ i = 0
86
+
87
+ # Get parameter schema
88
+ parameters = {}
89
+ required_params = []
90
+
91
+ if isinstance(func_schema, dict):
92
+ # DataMap function
93
+ if 'parameters' in func_schema:
94
+ params = func_schema['parameters']
95
+ if 'properties' in params:
96
+ parameters = params['properties']
97
+ required_params = params.get('required', [])
98
+ else:
99
+ parameters = params
100
+ else:
101
+ parameters = func_schema
102
+ else:
103
+ # Regular SWAIG function
104
+ if hasattr(func_schema, 'parameters') and func_schema.parameters:
105
+ params = func_schema.parameters
106
+ if 'properties' in params:
107
+ parameters = params['properties']
108
+ required_params = params.get('required', [])
109
+ else:
110
+ parameters = params
111
+
112
+ # Parse arguments
113
+ while i < len(function_args_list):
114
+ arg = function_args_list[i]
115
+
116
+ if arg.startswith('--'):
117
+ param_name = arg[2:] # Remove --
118
+
119
+ # Convert kebab-case to snake_case for parameter lookup
120
+ param_key = param_name.replace('-', '_')
121
+
122
+ # Check if this parameter exists in schema
123
+ param_schema = parameters.get(param_key, {})
124
+ param_type = param_schema.get('type', 'string')
125
+
126
+ if param_type == 'boolean':
127
+ # Check if next arg is a boolean value or if this is a flag
128
+ if i + 1 < len(function_args_list) and function_args_list[i + 1].lower() in ['true', 'false']:
129
+ parsed_args[param_key] = function_args_list[i + 1].lower() == 'true'
130
+ i += 2
131
+ else:
132
+ # Treat as flag (present = true)
133
+ parsed_args[param_key] = True
134
+ i += 1
135
+ else:
136
+ # Need a value
137
+ if i + 1 >= len(function_args_list):
138
+ raise ValueError(f"Parameter --{param_name} requires a value")
139
+
140
+ value = function_args_list[i + 1]
141
+
142
+ # Type coercion
143
+ if param_type == 'integer':
144
+ try:
145
+ parsed_args[param_key] = int(value)
146
+ except ValueError:
147
+ raise ValueError(f"Parameter --{param_name} must be an integer, got: {value}")
148
+ elif param_type == 'number':
149
+ try:
150
+ parsed_args[param_key] = float(value)
151
+ except ValueError:
152
+ raise ValueError(f"Parameter --{param_name} must be a number, got: {value}")
153
+ elif param_type == 'array':
154
+ # Handle comma-separated arrays
155
+ parsed_args[param_key] = [item.strip() for item in value.split(',')]
156
+ else:
157
+ # String (default)
158
+ parsed_args[param_key] = value
159
+
160
+ i += 2
161
+ else:
162
+ raise ValueError(f"Expected parameter name starting with --, got: {arg}")
163
+
164
+ return parsed_args
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Apply dynamic configuration to agents
4
+ """
5
+
6
+ from typing import Optional, TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from signalwire_agents.core.agent_base import AgentBase
10
+ from ..simulation.mock_env import MockRequest
11
+
12
+
13
+ def apply_dynamic_config(agent: 'AgentBase', mock_request: Optional['MockRequest'] = None, verbose: bool = False) -> None:
14
+ """
15
+ Apply dynamic configuration callback if the agent has one
16
+
17
+ Args:
18
+ agent: The agent instance
19
+ mock_request: Optional mock request object
20
+ verbose: Whether to print verbose output
21
+ """
22
+ # Check if dynamic config has already been applied to this agent
23
+ if hasattr(agent, '_dynamic_config_applied') and agent._dynamic_config_applied:
24
+ if verbose:
25
+ print("Dynamic configuration already applied, skipping...")
26
+ return
27
+
28
+ # Check if agent has dynamic config callback
29
+ if hasattr(agent, '_dynamic_config_callback') and agent._dynamic_config_callback:
30
+ try:
31
+ # Create mock request data if not provided
32
+ if mock_request is None:
33
+ from ..simulation.mock_env import create_mock_request
34
+ mock_request = create_mock_request()
35
+
36
+ # Extract request data
37
+ query_params = dict(mock_request.query_params)
38
+ body_params = {} # Empty for GET requests
39
+ headers = dict(mock_request.headers)
40
+
41
+ if verbose:
42
+ print("Applying dynamic configuration callback...")
43
+
44
+ # Call the user's configuration callback directly with the agent
45
+ # This is what pc_builder_service.py expects - to get the agent itself
46
+ agent._dynamic_config_callback(query_params, body_params, headers, agent)
47
+
48
+ # Mark that dynamic config has been applied to prevent duplicate application
49
+ agent._dynamic_config_applied = True
50
+
51
+ if verbose:
52
+ print("Dynamic configuration callback applied successfully")
53
+ # Show loaded skills after dynamic config
54
+ skills = agent.list_skills()
55
+ if skills:
56
+ print(f"Skills loaded by dynamic config: {', '.join(skills)}")
57
+
58
+ except Exception as e:
59
+ if verbose:
60
+ print(f"Warning: Failed to apply dynamic configuration: {e}")
61
+ import traceback
62
+ traceback.print_exc()
@@ -0,0 +1 @@
1
+ """Function execution modules"""