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.
Files changed (143) hide show
  1. signalwire_agents/__init__.py +99 -15
  2. signalwire_agents/agent_server.py +248 -60
  3. signalwire_agents/agents/bedrock.py +296 -0
  4. signalwire_agents/cli/__init__.py +9 -0
  5. signalwire_agents/cli/build_search.py +951 -41
  6. signalwire_agents/cli/config.py +80 -0
  7. signalwire_agents/cli/core/__init__.py +10 -0
  8. signalwire_agents/cli/core/agent_loader.py +470 -0
  9. signalwire_agents/cli/core/argparse_helpers.py +179 -0
  10. signalwire_agents/cli/core/dynamic_config.py +71 -0
  11. signalwire_agents/cli/core/service_loader.py +303 -0
  12. signalwire_agents/cli/dokku.py +2320 -0
  13. signalwire_agents/cli/execution/__init__.py +10 -0
  14. signalwire_agents/cli/execution/datamap_exec.py +446 -0
  15. signalwire_agents/cli/execution/webhook_exec.py +134 -0
  16. signalwire_agents/cli/init_project.py +2636 -0
  17. signalwire_agents/cli/output/__init__.py +10 -0
  18. signalwire_agents/cli/output/output_formatter.py +255 -0
  19. signalwire_agents/cli/output/swml_dump.py +186 -0
  20. signalwire_agents/cli/simulation/__init__.py +10 -0
  21. signalwire_agents/cli/simulation/data_generation.py +374 -0
  22. signalwire_agents/cli/simulation/data_overrides.py +200 -0
  23. signalwire_agents/cli/simulation/mock_env.py +282 -0
  24. signalwire_agents/cli/swaig_test_wrapper.py +52 -0
  25. signalwire_agents/cli/test_swaig.py +566 -2366
  26. signalwire_agents/cli/types.py +81 -0
  27. signalwire_agents/core/__init__.py +2 -2
  28. signalwire_agents/core/agent/__init__.py +12 -0
  29. signalwire_agents/core/agent/config/__init__.py +12 -0
  30. signalwire_agents/core/agent/deployment/__init__.py +9 -0
  31. signalwire_agents/core/agent/deployment/handlers/__init__.py +9 -0
  32. signalwire_agents/core/agent/prompt/__init__.py +14 -0
  33. signalwire_agents/core/agent/prompt/manager.py +306 -0
  34. signalwire_agents/core/agent/routing/__init__.py +9 -0
  35. signalwire_agents/core/agent/security/__init__.py +9 -0
  36. signalwire_agents/core/agent/swml/__init__.py +9 -0
  37. signalwire_agents/core/agent/tools/__init__.py +15 -0
  38. signalwire_agents/core/agent/tools/decorator.py +97 -0
  39. signalwire_agents/core/agent/tools/registry.py +210 -0
  40. signalwire_agents/core/agent_base.py +845 -2916
  41. signalwire_agents/core/auth_handler.py +233 -0
  42. signalwire_agents/core/config_loader.py +259 -0
  43. signalwire_agents/core/contexts.py +418 -0
  44. signalwire_agents/core/data_map.py +3 -15
  45. signalwire_agents/core/function_result.py +116 -44
  46. signalwire_agents/core/logging_config.py +162 -18
  47. signalwire_agents/core/mixins/__init__.py +28 -0
  48. signalwire_agents/core/mixins/ai_config_mixin.py +442 -0
  49. signalwire_agents/core/mixins/auth_mixin.py +280 -0
  50. signalwire_agents/core/mixins/prompt_mixin.py +358 -0
  51. signalwire_agents/core/mixins/serverless_mixin.py +460 -0
  52. signalwire_agents/core/mixins/skill_mixin.py +55 -0
  53. signalwire_agents/core/mixins/state_mixin.py +153 -0
  54. signalwire_agents/core/mixins/tool_mixin.py +230 -0
  55. signalwire_agents/core/mixins/web_mixin.py +1142 -0
  56. signalwire_agents/core/security_config.py +333 -0
  57. signalwire_agents/core/skill_base.py +84 -1
  58. signalwire_agents/core/skill_manager.py +62 -20
  59. signalwire_agents/core/swaig_function.py +18 -5
  60. signalwire_agents/core/swml_builder.py +207 -11
  61. signalwire_agents/core/swml_handler.py +27 -21
  62. signalwire_agents/core/swml_renderer.py +123 -312
  63. signalwire_agents/core/swml_service.py +171 -203
  64. signalwire_agents/mcp_gateway/__init__.py +29 -0
  65. signalwire_agents/mcp_gateway/gateway_service.py +564 -0
  66. signalwire_agents/mcp_gateway/mcp_manager.py +513 -0
  67. signalwire_agents/mcp_gateway/session_manager.py +218 -0
  68. signalwire_agents/prefabs/concierge.py +0 -3
  69. signalwire_agents/prefabs/faq_bot.py +0 -3
  70. signalwire_agents/prefabs/info_gatherer.py +0 -3
  71. signalwire_agents/prefabs/receptionist.py +0 -3
  72. signalwire_agents/prefabs/survey.py +0 -3
  73. signalwire_agents/schema.json +9218 -5489
  74. signalwire_agents/search/__init__.py +7 -1
  75. signalwire_agents/search/document_processor.py +490 -31
  76. signalwire_agents/search/index_builder.py +307 -37
  77. signalwire_agents/search/migration.py +418 -0
  78. signalwire_agents/search/models.py +30 -0
  79. signalwire_agents/search/pgvector_backend.py +748 -0
  80. signalwire_agents/search/query_processor.py +162 -31
  81. signalwire_agents/search/search_engine.py +916 -35
  82. signalwire_agents/search/search_service.py +376 -53
  83. signalwire_agents/skills/README.md +452 -0
  84. signalwire_agents/skills/__init__.py +14 -2
  85. signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
  86. signalwire_agents/skills/api_ninjas_trivia/__init__.py +12 -0
  87. signalwire_agents/skills/api_ninjas_trivia/skill.py +237 -0
  88. signalwire_agents/skills/datasphere/README.md +210 -0
  89. signalwire_agents/skills/datasphere/skill.py +84 -3
  90. signalwire_agents/skills/datasphere_serverless/README.md +258 -0
  91. signalwire_agents/skills/datasphere_serverless/__init__.py +9 -0
  92. signalwire_agents/skills/datasphere_serverless/skill.py +82 -1
  93. signalwire_agents/skills/datetime/README.md +132 -0
  94. signalwire_agents/skills/datetime/__init__.py +9 -0
  95. signalwire_agents/skills/datetime/skill.py +20 -7
  96. signalwire_agents/skills/joke/README.md +149 -0
  97. signalwire_agents/skills/joke/__init__.py +9 -0
  98. signalwire_agents/skills/joke/skill.py +21 -0
  99. signalwire_agents/skills/math/README.md +161 -0
  100. signalwire_agents/skills/math/__init__.py +9 -0
  101. signalwire_agents/skills/math/skill.py +18 -4
  102. signalwire_agents/skills/mcp_gateway/README.md +230 -0
  103. signalwire_agents/skills/mcp_gateway/__init__.py +10 -0
  104. signalwire_agents/skills/mcp_gateway/skill.py +421 -0
  105. signalwire_agents/skills/native_vector_search/README.md +210 -0
  106. signalwire_agents/skills/native_vector_search/__init__.py +9 -0
  107. signalwire_agents/skills/native_vector_search/skill.py +569 -101
  108. signalwire_agents/skills/play_background_file/README.md +218 -0
  109. signalwire_agents/skills/play_background_file/__init__.py +12 -0
  110. signalwire_agents/skills/play_background_file/skill.py +242 -0
  111. signalwire_agents/skills/registry.py +395 -40
  112. signalwire_agents/skills/spider/README.md +236 -0
  113. signalwire_agents/skills/spider/__init__.py +13 -0
  114. signalwire_agents/skills/spider/skill.py +598 -0
  115. signalwire_agents/skills/swml_transfer/README.md +395 -0
  116. signalwire_agents/skills/swml_transfer/__init__.py +10 -0
  117. signalwire_agents/skills/swml_transfer/skill.py +359 -0
  118. signalwire_agents/skills/weather_api/README.md +178 -0
  119. signalwire_agents/skills/weather_api/__init__.py +12 -0
  120. signalwire_agents/skills/weather_api/skill.py +191 -0
  121. signalwire_agents/skills/web_search/README.md +163 -0
  122. signalwire_agents/skills/web_search/__init__.py +9 -0
  123. signalwire_agents/skills/web_search/skill.py +586 -112
  124. signalwire_agents/skills/wikipedia_search/README.md +228 -0
  125. signalwire_agents/{core/state → skills/wikipedia_search}/__init__.py +5 -4
  126. signalwire_agents/skills/{wikipedia → wikipedia_search}/skill.py +33 -3
  127. signalwire_agents/web/__init__.py +17 -0
  128. signalwire_agents/web/web_service.py +559 -0
  129. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-agent-init.1 +400 -0
  130. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/sw-search.1 +483 -0
  131. signalwire_agents-1.0.17.dev4.data/data/share/man/man1/swaig-test.1 +308 -0
  132. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/METADATA +347 -215
  133. signalwire_agents-1.0.17.dev4.dist-info/RECORD +147 -0
  134. signalwire_agents-1.0.17.dev4.dist-info/entry_points.txt +6 -0
  135. signalwire_agents/core/state/file_state_manager.py +0 -219
  136. signalwire_agents/core/state/state_manager.py +0 -101
  137. signalwire_agents/skills/wikipedia/__init__.py +0 -9
  138. signalwire_agents-0.1.13.data/data/schema.json +0 -5611
  139. signalwire_agents-0.1.13.dist-info/RECORD +0 -67
  140. signalwire_agents-0.1.13.dist-info/entry_points.txt +0 -3
  141. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/WHEEL +0 -0
  142. {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.17.dev4.dist-info}/licenses/LICENSE +0 -0
  143. {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