signalwire-agents 0.1.23__py3-none-any.whl → 0.1.24__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.
- signalwire_agents/__init__.py +1 -1
- signalwire_agents/agent_server.py +2 -1
- signalwire_agents/cli/config.py +61 -0
- signalwire_agents/cli/core/__init__.py +1 -0
- signalwire_agents/cli/core/agent_loader.py +254 -0
- signalwire_agents/cli/core/argparse_helpers.py +164 -0
- signalwire_agents/cli/core/dynamic_config.py +62 -0
- signalwire_agents/cli/execution/__init__.py +1 -0
- signalwire_agents/cli/execution/datamap_exec.py +437 -0
- signalwire_agents/cli/execution/webhook_exec.py +125 -0
- signalwire_agents/cli/output/__init__.py +1 -0
- signalwire_agents/cli/output/output_formatter.py +132 -0
- signalwire_agents/cli/output/swml_dump.py +177 -0
- signalwire_agents/cli/simulation/__init__.py +1 -0
- signalwire_agents/cli/simulation/data_generation.py +365 -0
- signalwire_agents/cli/simulation/data_overrides.py +187 -0
- signalwire_agents/cli/simulation/mock_env.py +271 -0
- signalwire_agents/cli/test_swaig.py +522 -2539
- signalwire_agents/cli/types.py +72 -0
- signalwire_agents/core/agent/__init__.py +1 -3
- signalwire_agents/core/agent/config/__init__.py +1 -3
- signalwire_agents/core/agent/prompt/manager.py +25 -7
- signalwire_agents/core/agent/tools/decorator.py +2 -0
- signalwire_agents/core/agent/tools/registry.py +8 -0
- signalwire_agents/core/agent_base.py +492 -3053
- signalwire_agents/core/function_result.py +31 -42
- signalwire_agents/core/mixins/__init__.py +28 -0
- signalwire_agents/core/mixins/ai_config_mixin.py +373 -0
- signalwire_agents/core/mixins/auth_mixin.py +287 -0
- signalwire_agents/core/mixins/prompt_mixin.py +345 -0
- signalwire_agents/core/mixins/serverless_mixin.py +368 -0
- signalwire_agents/core/mixins/skill_mixin.py +55 -0
- signalwire_agents/core/mixins/state_mixin.py +219 -0
- signalwire_agents/core/mixins/tool_mixin.py +295 -0
- signalwire_agents/core/mixins/web_mixin.py +1130 -0
- signalwire_agents/core/skill_manager.py +3 -1
- signalwire_agents/core/swaig_function.py +10 -1
- signalwire_agents/core/swml_service.py +140 -58
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
- signalwire_agents/skills/datasphere/README.md +210 -0
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/native_vector_search/skill.py +33 -13
- signalwire_agents/skills/play_background_file/README.md +218 -0
- signalwire_agents/skills/spider/README.md +236 -0
- signalwire_agents/skills/spider/__init__.py +4 -0
- signalwire_agents/skills/spider/skill.py +479 -0
- signalwire_agents/skills/swml_transfer/README.md +395 -0
- signalwire_agents/skills/swml_transfer/__init__.py +1 -0
- signalwire_agents/skills/swml_transfer/skill.py +257 -0
- signalwire_agents/skills/weather_api/README.md +178 -0
- signalwire_agents/skills/web_search/README.md +163 -0
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/METADATA +47 -2
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/RECORD +62 -22
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/entry_points.txt +1 -1
- signalwire_agents/core/agent/config/ephemeral.py +0 -176
- signalwire_agents-0.1.23.data/data/schema.json +0 -5611
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.24.dist-info}/top_level.txt +0 -0
signalwire_agents/__init__.py
CHANGED
@@ -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.
|
21
|
+
__version__ = "0.1.24"
|
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: {
|
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"""
|