signalwire-agents 0.1.13__py3-none-any.whl → 1.0.17.dev4__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 +99 -15
- signalwire_agents/agent_server.py +248 -60
- signalwire_agents/agents/bedrock.py +296 -0
- signalwire_agents/cli/__init__.py +9 -0
- signalwire_agents/cli/build_search.py +951 -41
- signalwire_agents/cli/config.py +80 -0
- signalwire_agents/cli/core/__init__.py +10 -0
- signalwire_agents/cli/core/agent_loader.py +470 -0
- signalwire_agents/cli/core/argparse_helpers.py +179 -0
- signalwire_agents/cli/core/dynamic_config.py +71 -0
- signalwire_agents/cli/core/service_loader.py +303 -0
- signalwire_agents/cli/dokku.py +2320 -0
- signalwire_agents/cli/execution/__init__.py +10 -0
- signalwire_agents/cli/execution/datamap_exec.py +446 -0
- signalwire_agents/cli/execution/webhook_exec.py +134 -0
- signalwire_agents/cli/init_project.py +2636 -0
- signalwire_agents/cli/output/__init__.py +10 -0
- signalwire_agents/cli/output/output_formatter.py +255 -0
- signalwire_agents/cli/output/swml_dump.py +186 -0
- signalwire_agents/cli/simulation/__init__.py +10 -0
- signalwire_agents/cli/simulation/data_generation.py +374 -0
- signalwire_agents/cli/simulation/data_overrides.py +200 -0
- signalwire_agents/cli/simulation/mock_env.py +282 -0
- signalwire_agents/cli/swaig_test_wrapper.py +52 -0
- signalwire_agents/cli/test_swaig.py +566 -2366
- signalwire_agents/cli/types.py +81 -0
- signalwire_agents/core/__init__.py +2 -2
- signalwire_agents/core/agent/__init__.py +12 -0
- signalwire_agents/core/agent/config/__init__.py +12 -0
- signalwire_agents/core/agent/deployment/__init__.py +9 -0
- signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
- signalwire_agents/core/agent/prompt/__init__.py +14 -0
- signalwire_agents/core/agent/prompt/manager.py +306 -0
- signalwire_agents/core/agent/routing/__init__.py +9 -0
- signalwire_agents/core/agent/security/__init__.py +9 -0
- signalwire_agents/core/agent/swml/__init__.py +9 -0
- signalwire_agents/core/agent/tools/__init__.py +15 -0
- signalwire_agents/core/agent/tools/decorator.py +97 -0
- signalwire_agents/core/agent/tools/registry.py +210 -0
- signalwire_agents/core/agent_base.py +845 -2916
- signalwire_agents/core/auth_handler.py +233 -0
- signalwire_agents/core/config_loader.py +259 -0
- signalwire_agents/core/contexts.py +418 -0
- signalwire_agents/core/data_map.py +3 -15
- signalwire_agents/core/function_result.py +116 -44
- signalwire_agents/core/logging_config.py +162 -18
- signalwire_agents/core/mixins/__init__.py +28 -0
- signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
- signalwire_agents/core/mixins/auth_mixin.py +280 -0
- signalwire_agents/core/mixins/prompt_mixin.py +358 -0
- signalwire_agents/core/mixins/serverless_mixin.py +460 -0
- signalwire_agents/core/mixins/skill_mixin.py +55 -0
- signalwire_agents/core/mixins/state_mixin.py +153 -0
- signalwire_agents/core/mixins/tool_mixin.py +230 -0
- signalwire_agents/core/mixins/web_mixin.py +1142 -0
- signalwire_agents/core/security_config.py +333 -0
- signalwire_agents/core/skill_base.py +84 -1
- signalwire_agents/core/skill_manager.py +62 -20
- signalwire_agents/core/swaig_function.py +18 -5
- signalwire_agents/core/swml_builder.py +207 -11
- signalwire_agents/core/swml_handler.py +27 -21
- signalwire_agents/core/swml_renderer.py +123 -312
- signalwire_agents/core/swml_service.py +171 -203
- signalwire_agents/mcp_gateway/__init__.py +29 -0
- signalwire_agents/mcp_gateway/gateway_service.py +564 -0
- signalwire_agents/mcp_gateway/mcp_manager.py +513 -0
- signalwire_agents/mcp_gateway/session_manager.py +218 -0
- signalwire_agents/prefabs/concierge.py +0 -3
- signalwire_agents/prefabs/faq_bot.py +0 -3
- signalwire_agents/prefabs/info_gatherer.py +0 -3
- signalwire_agents/prefabs/receptionist.py +0 -3
- signalwire_agents/prefabs/survey.py +0 -3
- signalwire_agents/schema.json +9218 -5489
- signalwire_agents/search/__init__.py +7 -1
- signalwire_agents/search/document_processor.py +490 -31
- signalwire_agents/search/index_builder.py +307 -37
- signalwire_agents/search/migration.py +418 -0
- signalwire_agents/search/models.py +30 -0
- signalwire_agents/search/pgvector_backend.py +748 -0
- signalwire_agents/search/query_processor.py +162 -31
- signalwire_agents/search/search_engine.py +916 -35
- signalwire_agents/search/search_service.py +376 -53
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/__init__.py +14 -2
- signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
- signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
- signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
- signalwire_agents/skills/datasphere/README.md +210 -0
- signalwire_agents/skills/datasphere/skill.py +84 -3
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datasphere_serverless/__init__.py +9 -0
- signalwire_agents/skills/datasphere_serverless/skill.py +82 -1
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/datetime/__init__.py +9 -0
- signalwire_agents/skills/datetime/skill.py +20 -7
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/joke/__init__.py +9 -0
- signalwire_agents/skills/joke/skill.py +21 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/math/__init__.py +9 -0
- signalwire_agents/skills/math/skill.py +18 -4
- signalwire_agents/skills/mcp_gateway/README.md +230 -0
- signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
- signalwire_agents/skills/mcp_gateway/skill.py +421 -0
- signalwire_agents/skills/native_vector_search/README.md +210 -0
- signalwire_agents/skills/native_vector_search/__init__.py +9 -0
- signalwire_agents/skills/native_vector_search/skill.py +569 -101
- signalwire_agents/skills/play_background_file/README.md +218 -0
- signalwire_agents/skills/play_background_file/__init__.py +12 -0
- signalwire_agents/skills/play_background_file/skill.py +242 -0
- signalwire_agents/skills/registry.py +395 -40
- signalwire_agents/skills/spider/README.md +236 -0
- signalwire_agents/skills/spider/__init__.py +13 -0
- signalwire_agents/skills/spider/skill.py +598 -0
- signalwire_agents/skills/swml_transfer/README.md +395 -0
- signalwire_agents/skills/swml_transfer/__init__.py +10 -0
- signalwire_agents/skills/swml_transfer/skill.py +359 -0
- signalwire_agents/skills/weather_api/README.md +178 -0
- signalwire_agents/skills/weather_api/__init__.py +12 -0
- signalwire_agents/skills/weather_api/skill.py +191 -0
- signalwire_agents/skills/web_search/README.md +163 -0
- signalwire_agents/skills/web_search/__init__.py +9 -0
- signalwire_agents/skills/web_search/skill.py +586 -112
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
- signalwire_agents/skills/{wikipedia → wikipedia_search}/skill.py +33 -3
- signalwire_agents/web/__init__.py +17 -0
- signalwire_agents/web/web_service.py +559 -0
- signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-agent-init.1 +400 -0
- signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-search.1 +483 -0
- signalwire_agents-1.0.17.dev4.data/data/share/man/man1/swaig-test.1 +308 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/METADATA +347 -215
- signalwire_agents-1.0.17.dev4.dist-info/RECORD +147 -0
- signalwire_agents-1.0.17.dev4.dist-info/entry_points.txt +6 -0
- signalwire_agents/core/state/file_state_manager.py +0 -219
- signalwire_agents/core/state/state_manager.py +0 -101
- signalwire_agents/skills/wikipedia/__init__.py +0 -9
- signalwire_agents-0.1.13.data/data/schema.json +0 -5611
- signalwire_agents-0.1.13.dist-info/RECORD +0 -67
- signalwire_agents-0.1.13.dist-info/entry_points.txt +0 -3
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Copyright (c) 2025 SignalWire
|
|
4
|
+
|
|
5
|
+
This file is part of the SignalWire AI Agents SDK.
|
|
6
|
+
|
|
7
|
+
Licensed under the MIT License.
|
|
8
|
+
See LICENSE file in the project root for full license information.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
Generate fake SWML post_data and related helpers
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import uuid
|
|
16
|
+
import json
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from typing import Dict, Any, Optional
|
|
19
|
+
from ..types import CallData, VarsData, PostData
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def generate_fake_uuid() -> str:
|
|
23
|
+
"""Generate a fake UUID for testing"""
|
|
24
|
+
return str(uuid.uuid4())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate_fake_node_id() -> str:
|
|
28
|
+
"""Generate a fake node ID for testing"""
|
|
29
|
+
return f"test-node-{uuid.uuid4().hex[:8]}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def generate_fake_sip_from(call_type: str) -> str:
|
|
33
|
+
"""Generate a fake 'from' address based on call type"""
|
|
34
|
+
if call_type == "sip":
|
|
35
|
+
return f"+1555{uuid.uuid4().hex[:7]}" # Fake phone number
|
|
36
|
+
else: # webrtc
|
|
37
|
+
return f"user-{uuid.uuid4().hex[:8]}@test.domain"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def generate_fake_sip_to(call_type: str) -> str:
|
|
41
|
+
"""Generate a fake 'to' address based on call type"""
|
|
42
|
+
if call_type == "sip":
|
|
43
|
+
return f"+1444{uuid.uuid4().hex[:7]}" # Fake phone number
|
|
44
|
+
else: # webrtc
|
|
45
|
+
return f"agent-{uuid.uuid4().hex[:8]}@test.domain"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def adapt_for_call_type(call_data: Dict[str, Any], call_type: str) -> Dict[str, Any]:
|
|
49
|
+
"""
|
|
50
|
+
Adapt call data structure based on call type (sip vs webrtc)
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
call_data: Base call data structure
|
|
54
|
+
call_type: "sip" or "webrtc"
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Adapted call data with appropriate addresses and metadata
|
|
58
|
+
"""
|
|
59
|
+
call_data = call_data.copy()
|
|
60
|
+
|
|
61
|
+
# Update addresses based on call type
|
|
62
|
+
call_data["from"] = generate_fake_sip_from(call_type)
|
|
63
|
+
call_data["to"] = generate_fake_sip_to(call_type)
|
|
64
|
+
|
|
65
|
+
# Add call type specific metadata
|
|
66
|
+
if call_type == "sip":
|
|
67
|
+
call_data["type"] = "phone"
|
|
68
|
+
call_data["headers"] = {
|
|
69
|
+
"User-Agent": f"Test-SIP-Client/1.0.0",
|
|
70
|
+
"From": f"<sip:{call_data['from']}@test.sip.provider>",
|
|
71
|
+
"To": f"<sip:{call_data['to']}@test.sip.provider>",
|
|
72
|
+
"Call-ID": call_data["call_id"]
|
|
73
|
+
}
|
|
74
|
+
else: # webrtc
|
|
75
|
+
call_data["type"] = "webrtc"
|
|
76
|
+
call_data["headers"] = {
|
|
77
|
+
"User-Agent": "Test-WebRTC-Client/1.0.0",
|
|
78
|
+
"Origin": "https://test.webrtc.app",
|
|
79
|
+
"Sec-WebSocket-Protocol": "sip"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return call_data
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def generate_fake_swml_post_data(call_type: str = "webrtc",
|
|
86
|
+
call_direction: str = "inbound",
|
|
87
|
+
call_state: str = "created") -> Dict[str, Any]:
|
|
88
|
+
"""
|
|
89
|
+
Generate fake SWML post_data that matches real SignalWire structure
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
call_type: "sip" or "webrtc" (default: webrtc)
|
|
93
|
+
call_direction: "inbound" or "outbound" (default: inbound)
|
|
94
|
+
call_state: Call state (default: created)
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Fake post_data dict with call, vars, and envs structure
|
|
98
|
+
"""
|
|
99
|
+
call_id = generate_fake_uuid()
|
|
100
|
+
project_id = generate_fake_uuid()
|
|
101
|
+
space_id = generate_fake_uuid()
|
|
102
|
+
current_time = datetime.now().isoformat()
|
|
103
|
+
|
|
104
|
+
# Base call structure
|
|
105
|
+
call_data = {
|
|
106
|
+
"call_id": call_id,
|
|
107
|
+
"node_id": generate_fake_node_id(),
|
|
108
|
+
"segment_id": generate_fake_uuid(),
|
|
109
|
+
"call_session_id": generate_fake_uuid(),
|
|
110
|
+
"tag": call_id,
|
|
111
|
+
"state": call_state,
|
|
112
|
+
"direction": call_direction,
|
|
113
|
+
"type": call_type,
|
|
114
|
+
"from": generate_fake_sip_from(call_type),
|
|
115
|
+
"to": generate_fake_sip_to(call_type),
|
|
116
|
+
"timeout": 30,
|
|
117
|
+
"max_duration": 14400,
|
|
118
|
+
"answer_on_bridge": False,
|
|
119
|
+
"hangup_after_bridge": True,
|
|
120
|
+
"ringback": [],
|
|
121
|
+
"record": {},
|
|
122
|
+
"project_id": project_id,
|
|
123
|
+
"space_id": space_id,
|
|
124
|
+
"created_at": current_time,
|
|
125
|
+
"updated_at": current_time
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Adapt for specific call type
|
|
129
|
+
call_data = adapt_for_call_type(call_data, call_type)
|
|
130
|
+
|
|
131
|
+
# Complete post_data structure
|
|
132
|
+
post_data = {
|
|
133
|
+
"call": call_data,
|
|
134
|
+
"vars": {
|
|
135
|
+
"userVariables": {} # Empty by default, can be filled via overrides
|
|
136
|
+
},
|
|
137
|
+
"envs": {} # Empty by default, can be filled via overrides
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return post_data
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def generate_comprehensive_post_data(function_name: str, args: Dict[str, Any],
|
|
144
|
+
custom_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
145
|
+
"""
|
|
146
|
+
Generate comprehensive post_data that matches what SignalWire would send
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
function_name: Name of the SWAIG function being called
|
|
150
|
+
args: Function arguments
|
|
151
|
+
custom_data: Optional custom data to override defaults
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Complete post_data dict with all possible keys
|
|
155
|
+
"""
|
|
156
|
+
call_id = str(uuid.uuid4())
|
|
157
|
+
session_id = str(uuid.uuid4())
|
|
158
|
+
project_id = str(uuid.uuid4())
|
|
159
|
+
space_id = str(uuid.uuid4())
|
|
160
|
+
space_name = "test-space"
|
|
161
|
+
environment = "production"
|
|
162
|
+
|
|
163
|
+
current_time = datetime.now().isoformat()
|
|
164
|
+
|
|
165
|
+
# Base structure with all keys
|
|
166
|
+
post_data = {
|
|
167
|
+
"call_id": call_id,
|
|
168
|
+
"call": {
|
|
169
|
+
"call_id": call_id,
|
|
170
|
+
"node_id": f"test-node-{uuid.uuid4().hex[:8]}",
|
|
171
|
+
"segment_id": str(uuid.uuid4()),
|
|
172
|
+
"call_session_id": str(uuid.uuid4()),
|
|
173
|
+
"to": "+15551234567",
|
|
174
|
+
"from": "+15559876543",
|
|
175
|
+
"direction": "inbound",
|
|
176
|
+
"state": "answered",
|
|
177
|
+
"tag": call_id,
|
|
178
|
+
"project_id": project_id,
|
|
179
|
+
"space_id": space_id,
|
|
180
|
+
"headers": {"User-Agent": "SignalWire/1.0"},
|
|
181
|
+
"type": "phone",
|
|
182
|
+
"timeout": 30,
|
|
183
|
+
"answer_on_bridge": False,
|
|
184
|
+
"created_at": current_time,
|
|
185
|
+
"updated_at": current_time
|
|
186
|
+
},
|
|
187
|
+
"vars": {
|
|
188
|
+
"environment": environment,
|
|
189
|
+
"space_id": space_id,
|
|
190
|
+
"userVariables": {},
|
|
191
|
+
"call_data": {
|
|
192
|
+
"id": call_id,
|
|
193
|
+
"state": "answered",
|
|
194
|
+
"type": "phone",
|
|
195
|
+
"from": "+15559876543",
|
|
196
|
+
"to": "+15551234567",
|
|
197
|
+
"project_id": project_id,
|
|
198
|
+
"created_at": current_time
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"params": args,
|
|
202
|
+
"space_id": space_id,
|
|
203
|
+
"project_id": project_id,
|
|
204
|
+
"meta_data": {
|
|
205
|
+
"application": {
|
|
206
|
+
"name": "SignalWire AI Agent",
|
|
207
|
+
"version": "1.0.0"
|
|
208
|
+
},
|
|
209
|
+
"swml": {
|
|
210
|
+
"version": "1.0.0",
|
|
211
|
+
"session_id": session_id
|
|
212
|
+
},
|
|
213
|
+
"ai": {
|
|
214
|
+
"call_id": call_id,
|
|
215
|
+
"session_id": session_id,
|
|
216
|
+
"conversation_id": session_id
|
|
217
|
+
},
|
|
218
|
+
"request": {
|
|
219
|
+
"method": "POST",
|
|
220
|
+
"source_ip": "192.168.1.1",
|
|
221
|
+
"user_agent": "SignalWire-AI-Agent/1.0"
|
|
222
|
+
},
|
|
223
|
+
"timing": {
|
|
224
|
+
"request_start": current_time,
|
|
225
|
+
"function_start": current_time
|
|
226
|
+
},
|
|
227
|
+
"user": {
|
|
228
|
+
"id": f"user-{uuid.uuid4().hex[:8]}",
|
|
229
|
+
"session_start": current_time,
|
|
230
|
+
"last_updated": current_time
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
# Global application data
|
|
235
|
+
"global_data": {
|
|
236
|
+
"app_name": "test_application",
|
|
237
|
+
"environment": "test",
|
|
238
|
+
"user_preferences": {"language": "en"},
|
|
239
|
+
"session_data": {"start_time": current_time}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
# Conversation context
|
|
243
|
+
"call_log": [
|
|
244
|
+
{
|
|
245
|
+
"role": "system",
|
|
246
|
+
"content": "You are a helpful AI assistant created with SignalWire AI Agents."
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"role": "user",
|
|
250
|
+
"content": f"Please call the {function_name} function"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"role": "assistant",
|
|
254
|
+
"content": f"I'll call the {function_name} function for you.",
|
|
255
|
+
"tool_calls": [
|
|
256
|
+
{
|
|
257
|
+
"id": f"call_{call_id[:8]}",
|
|
258
|
+
"type": "function",
|
|
259
|
+
"function": {
|
|
260
|
+
"name": function_name,
|
|
261
|
+
"arguments": json.dumps(args)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
],
|
|
267
|
+
"raw_call_log": [
|
|
268
|
+
{
|
|
269
|
+
"role": "system",
|
|
270
|
+
"content": "You are a helpful AI assistant created with SignalWire AI Agents."
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"role": "user",
|
|
274
|
+
"content": "Hello"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"role": "assistant",
|
|
278
|
+
"content": "Hello! How can I help you today?"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"role": "user",
|
|
282
|
+
"content": f"Please call the {function_name} function"
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"role": "assistant",
|
|
286
|
+
"content": f"I'll call the {function_name} function for you.",
|
|
287
|
+
"tool_calls": [
|
|
288
|
+
{
|
|
289
|
+
"id": f"call_{call_id[:8]}",
|
|
290
|
+
"type": "function",
|
|
291
|
+
"function": {
|
|
292
|
+
"name": function_name,
|
|
293
|
+
"arguments": json.dumps(args)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
],
|
|
299
|
+
|
|
300
|
+
# SWML and prompt variables
|
|
301
|
+
"prompt_vars": {
|
|
302
|
+
# From SWML prompt variables
|
|
303
|
+
"ai_instructions": "You are a helpful assistant",
|
|
304
|
+
"temperature": 0.7,
|
|
305
|
+
"max_tokens": 1000,
|
|
306
|
+
# From global_data
|
|
307
|
+
"app_name": "test_application",
|
|
308
|
+
"environment": "test",
|
|
309
|
+
"user_preferences": {"language": "en"},
|
|
310
|
+
"session_data": {"start_time": current_time},
|
|
311
|
+
# SWML system variables
|
|
312
|
+
"current_timestamp": current_time,
|
|
313
|
+
"call_duration": "00:02:15",
|
|
314
|
+
"caller_number": "+15551234567",
|
|
315
|
+
"to_number": "+15559876543"
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
# Permission flags (from SWML parameters)
|
|
319
|
+
"swaig_allow_swml": True,
|
|
320
|
+
"swaig_post_conversation": True,
|
|
321
|
+
"swaig_post_swml_vars": True,
|
|
322
|
+
|
|
323
|
+
# Additional context
|
|
324
|
+
"http_method": "POST",
|
|
325
|
+
"webhook_url": f"https://test.example.com/webhook/{function_name}",
|
|
326
|
+
"user_agent": "SignalWire-AI-Agent/1.0",
|
|
327
|
+
"request_headers": {
|
|
328
|
+
"Content-Type": "application/json",
|
|
329
|
+
"User-Agent": "SignalWire-AI-Agent/1.0",
|
|
330
|
+
"X-Signalwire-Call-Id": call_id,
|
|
331
|
+
"X-Signalwire-Session-Id": session_id
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
# SignalWire environment data
|
|
335
|
+
"swml_env": {
|
|
336
|
+
"space_id": space_id,
|
|
337
|
+
"project_id": project_id,
|
|
338
|
+
"environment": environment,
|
|
339
|
+
"space_name": space_name,
|
|
340
|
+
"api_version": "1.0.0"
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# Apply custom data overrides if provided
|
|
345
|
+
if custom_data:
|
|
346
|
+
# Deep merge custom_data into post_data
|
|
347
|
+
for key, value in custom_data.items():
|
|
348
|
+
if key in post_data and isinstance(post_data[key], dict) and isinstance(value, dict):
|
|
349
|
+
# Merge dictionaries
|
|
350
|
+
post_data[key].update(value)
|
|
351
|
+
else:
|
|
352
|
+
# Replace value
|
|
353
|
+
post_data[key] = value
|
|
354
|
+
|
|
355
|
+
return post_data
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def generate_minimal_post_data(function_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
359
|
+
"""
|
|
360
|
+
Generate minimal post_data with only essential keys
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
function_name: Name of the SWAIG function being called
|
|
364
|
+
args: Function arguments
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Minimal post_data dict
|
|
368
|
+
"""
|
|
369
|
+
call_id = str(uuid.uuid4())
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
"call_id": call_id,
|
|
373
|
+
"params": args
|
|
374
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Copyright (c) 2025 SignalWire
|
|
4
|
+
|
|
5
|
+
This file is part of the SignalWire AI Agents SDK.
|
|
6
|
+
|
|
7
|
+
Licensed under the MIT License.
|
|
8
|
+
See LICENSE file in the project root for full license information.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
Handle CLI overrides and mapping to nested data
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import uuid
|
|
17
|
+
import argparse
|
|
18
|
+
from typing import Dict, Any, List
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def set_nested_value(data: Dict[str, Any], path: str, value: Any) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Set a nested value using dot notation path
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
data: Dictionary to modify
|
|
27
|
+
path: Dot-notation path (e.g., "call.call_id" or "vars.userVariables.custom")
|
|
28
|
+
value: Value to set
|
|
29
|
+
"""
|
|
30
|
+
keys = path.split('.')
|
|
31
|
+
current = data
|
|
32
|
+
|
|
33
|
+
# Navigate to the parent of the target key
|
|
34
|
+
for key in keys[:-1]:
|
|
35
|
+
if key not in current:
|
|
36
|
+
current[key] = {}
|
|
37
|
+
current = current[key]
|
|
38
|
+
|
|
39
|
+
# Set the final value
|
|
40
|
+
current[keys[-1]] = value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse_value(value_str: str) -> Any:
|
|
44
|
+
"""
|
|
45
|
+
Parse a string value into appropriate Python type
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
value_str: String representation of value
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Parsed value (str, int, float, bool, None, or JSON object)
|
|
52
|
+
"""
|
|
53
|
+
# Handle special values
|
|
54
|
+
if value_str.lower() == 'null':
|
|
55
|
+
return None
|
|
56
|
+
elif value_str.lower() == 'true':
|
|
57
|
+
return True
|
|
58
|
+
elif value_str.lower() == 'false':
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
# Try parsing as number
|
|
62
|
+
try:
|
|
63
|
+
if '.' in value_str:
|
|
64
|
+
return float(value_str)
|
|
65
|
+
else:
|
|
66
|
+
return int(value_str)
|
|
67
|
+
except ValueError:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
# Try parsing as JSON (for objects/arrays)
|
|
71
|
+
try:
|
|
72
|
+
return json.loads(value_str)
|
|
73
|
+
except json.JSONDecodeError:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# Return as string
|
|
77
|
+
return value_str
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def apply_overrides(data: Dict[str, Any], overrides: List[str],
|
|
81
|
+
json_overrides: List[str]) -> Dict[str, Any]:
|
|
82
|
+
"""
|
|
83
|
+
Apply override values to data using dot notation paths
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
data: Data dictionary to modify
|
|
87
|
+
overrides: List of "path=value" strings
|
|
88
|
+
json_overrides: List of "path=json_value" strings
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Modified data dictionary
|
|
92
|
+
"""
|
|
93
|
+
data = data.copy()
|
|
94
|
+
|
|
95
|
+
# Apply simple overrides
|
|
96
|
+
for override in overrides:
|
|
97
|
+
if '=' not in override:
|
|
98
|
+
continue
|
|
99
|
+
path, value_str = override.split('=', 1)
|
|
100
|
+
value = parse_value(value_str)
|
|
101
|
+
set_nested_value(data, path, value)
|
|
102
|
+
|
|
103
|
+
# Apply JSON overrides
|
|
104
|
+
for json_override in json_overrides:
|
|
105
|
+
if '=' not in json_override:
|
|
106
|
+
continue
|
|
107
|
+
path, json_str = json_override.split('=', 1)
|
|
108
|
+
try:
|
|
109
|
+
value = json.loads(json_str)
|
|
110
|
+
set_nested_value(data, path, value)
|
|
111
|
+
except json.JSONDecodeError as e:
|
|
112
|
+
print(f"Warning: Invalid JSON in override '{json_override}': {e}")
|
|
113
|
+
|
|
114
|
+
return data
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def apply_convenience_mappings(data: Dict[str, Any], args: argparse.Namespace) -> Dict[str, Any]:
|
|
118
|
+
"""
|
|
119
|
+
Apply convenience CLI arguments to data structure
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
data: Data dictionary to modify
|
|
123
|
+
args: Parsed CLI arguments
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Modified data dictionary
|
|
127
|
+
"""
|
|
128
|
+
data = data.copy()
|
|
129
|
+
|
|
130
|
+
# Map high-level arguments to specific paths
|
|
131
|
+
if hasattr(args, 'call_id') and args.call_id:
|
|
132
|
+
# Set at root level for SWAIG functions
|
|
133
|
+
data["call_id"] = args.call_id
|
|
134
|
+
# Also set in call object if it exists
|
|
135
|
+
if "call" in data:
|
|
136
|
+
set_nested_value(data, "call.call_id", args.call_id)
|
|
137
|
+
set_nested_value(data, "call.tag", args.call_id) # tag often matches call_id
|
|
138
|
+
|
|
139
|
+
if hasattr(args, 'project_id') and args.project_id:
|
|
140
|
+
set_nested_value(data, "call.project_id", args.project_id)
|
|
141
|
+
|
|
142
|
+
if hasattr(args, 'space_id') and args.space_id:
|
|
143
|
+
set_nested_value(data, "call.space_id", args.space_id)
|
|
144
|
+
|
|
145
|
+
if hasattr(args, 'call_state') and args.call_state:
|
|
146
|
+
set_nested_value(data, "call.state", args.call_state)
|
|
147
|
+
|
|
148
|
+
if hasattr(args, 'call_direction') and args.call_direction:
|
|
149
|
+
set_nested_value(data, "call.direction", args.call_direction)
|
|
150
|
+
|
|
151
|
+
# Handle from/to addresses with fake generation if needed
|
|
152
|
+
if hasattr(args, 'from_number') and args.from_number:
|
|
153
|
+
# If looks like phone number, use as-is, otherwise generate fake
|
|
154
|
+
if args.from_number.startswith('+') or args.from_number.isdigit():
|
|
155
|
+
set_nested_value(data, "call.from", args.from_number)
|
|
156
|
+
else:
|
|
157
|
+
# Generate fake phone number or SIP address
|
|
158
|
+
call_type = getattr(args, 'call_type', 'webrtc')
|
|
159
|
+
if call_type == 'sip':
|
|
160
|
+
set_nested_value(data, "call.from", f"+1555{uuid.uuid4().hex[:7]}")
|
|
161
|
+
else:
|
|
162
|
+
set_nested_value(data, "call.from", f"{args.from_number}@test.domain")
|
|
163
|
+
|
|
164
|
+
if hasattr(args, 'to_extension') and args.to_extension:
|
|
165
|
+
# Similar logic for 'to' address
|
|
166
|
+
if args.to_extension.startswith('+') or args.to_extension.isdigit():
|
|
167
|
+
set_nested_value(data, "call.to", args.to_extension)
|
|
168
|
+
else:
|
|
169
|
+
call_type = getattr(args, 'call_type', 'webrtc')
|
|
170
|
+
if call_type == 'sip':
|
|
171
|
+
set_nested_value(data, "call.to", f"+1444{uuid.uuid4().hex[:7]}")
|
|
172
|
+
else:
|
|
173
|
+
set_nested_value(data, "call.to", f"{args.to_extension}@test.domain")
|
|
174
|
+
|
|
175
|
+
# Merge user variables
|
|
176
|
+
user_vars = {}
|
|
177
|
+
|
|
178
|
+
# Add user_vars if provided
|
|
179
|
+
if hasattr(args, 'user_vars') and args.user_vars:
|
|
180
|
+
try:
|
|
181
|
+
user_vars.update(json.loads(args.user_vars))
|
|
182
|
+
except json.JSONDecodeError as e:
|
|
183
|
+
print(f"Warning: Invalid JSON in --user-vars: {e}")
|
|
184
|
+
|
|
185
|
+
# Add query_params if provided (merged into userVariables)
|
|
186
|
+
if hasattr(args, 'query_params') and args.query_params:
|
|
187
|
+
try:
|
|
188
|
+
user_vars.update(json.loads(args.query_params))
|
|
189
|
+
except json.JSONDecodeError as e:
|
|
190
|
+
print(f"Warning: Invalid JSON in --query-params: {e}")
|
|
191
|
+
|
|
192
|
+
# Apply user variables
|
|
193
|
+
if user_vars:
|
|
194
|
+
if "vars" not in data:
|
|
195
|
+
data["vars"] = {}
|
|
196
|
+
if "userVariables" not in data["vars"]:
|
|
197
|
+
data["vars"]["userVariables"] = {}
|
|
198
|
+
data["vars"]["userVariables"].update(user_vars)
|
|
199
|
+
|
|
200
|
+
return data
|