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
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Display agent/tools and format results
4
+ """
5
+
6
+ import json
7
+ from typing import Any, TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from signalwire_agents.core.agent_base import AgentBase
11
+ from signalwire_agents.core.function_result import SwaigFunctionResult
12
+
13
+
14
+ def display_agent_tools(agent: 'AgentBase', verbose: bool = False) -> None:
15
+ """
16
+ Display the available SWAIG functions for an agent
17
+
18
+ Args:
19
+ agent: The agent instance
20
+ verbose: Whether to show verbose details
21
+ """
22
+ print("\nAvailable SWAIG functions:")
23
+ # Try to access functions from the tool registry
24
+ functions = {}
25
+ if hasattr(agent, '_tool_registry') and hasattr(agent._tool_registry, '_swaig_functions'):
26
+ functions = agent._tool_registry._swaig_functions
27
+ elif hasattr(agent, '_swaig_functions'):
28
+ functions = agent._swaig_functions
29
+
30
+ if functions:
31
+ for name, func in functions.items():
32
+ if isinstance(func, dict):
33
+ # DataMap function
34
+ description = func.get('description', 'DataMap function (serverless)')
35
+ print(f" {name} - {description}")
36
+
37
+ # Show parameters for DataMap functions
38
+ if 'parameters' in func and func['parameters']:
39
+ params = func['parameters']
40
+ # Handle both formats: direct properties dict or full schema
41
+ if 'properties' in params:
42
+ properties = params['properties']
43
+ required_fields = params.get('required', [])
44
+ else:
45
+ properties = params
46
+ required_fields = []
47
+
48
+ if properties:
49
+ print(f" Parameters:")
50
+ for param_name, param_def in properties.items():
51
+ param_type = param_def.get('type', 'unknown')
52
+ param_desc = param_def.get('description', 'No description')
53
+ is_required = param_name in required_fields
54
+ required_marker = " (required)" if is_required else ""
55
+ print(f" {param_name} ({param_type}){required_marker}: {param_desc}")
56
+ else:
57
+ print(f" Parameters: None")
58
+ else:
59
+ print(f" Parameters: None")
60
+
61
+ if verbose:
62
+ print(f" Config: {json.dumps(func, indent=6)}")
63
+ else:
64
+ # Regular SWAIG function
65
+ func_type = ""
66
+ if hasattr(func, 'webhook_url') and func.webhook_url and func.is_external:
67
+ func_type = " (EXTERNAL webhook)"
68
+ elif hasattr(func, 'webhook_url') and func.webhook_url:
69
+ func_type = " (webhook)"
70
+ else:
71
+ func_type = " (LOCAL webhook)"
72
+
73
+ print(f" {name} - {func.description}{func_type}")
74
+
75
+ # Show external URL if applicable
76
+ if hasattr(func, 'webhook_url') and func.webhook_url and func.is_external:
77
+ print(f" External URL: {func.webhook_url}")
78
+
79
+ # Show parameters
80
+ if hasattr(func, 'parameters') and func.parameters:
81
+ params = func.parameters
82
+ # Handle both formats: direct properties dict or full schema
83
+ if 'properties' in params:
84
+ properties = params['properties']
85
+ required_fields = params.get('required', [])
86
+ else:
87
+ properties = params
88
+ required_fields = []
89
+
90
+ if properties:
91
+ print(f" Parameters:")
92
+ for param_name, param_def in properties.items():
93
+ param_type = param_def.get('type', 'unknown')
94
+ param_desc = param_def.get('description', 'No description')
95
+ is_required = param_name in required_fields
96
+ required_marker = " (required)" if is_required else ""
97
+ print(f" {param_name} ({param_type}){required_marker}: {param_desc}")
98
+ else:
99
+ print(f" Parameters: None")
100
+ else:
101
+ print(f" Parameters: None")
102
+
103
+ if verbose:
104
+ print(f" Function object: {func}")
105
+ else:
106
+ print(" No SWAIG functions registered")
107
+
108
+
109
+ def format_result(result: Any) -> str:
110
+ """
111
+ Format the result of a SWAIG function call for display
112
+
113
+ Args:
114
+ result: The result from the SWAIG function
115
+
116
+ Returns:
117
+ Formatted string representation
118
+ """
119
+ # Import here to avoid circular imports
120
+ from signalwire_agents.core.function_result import SwaigFunctionResult
121
+
122
+ if isinstance(result, SwaigFunctionResult):
123
+ return f"SwaigFunctionResult: {result.response}"
124
+ elif isinstance(result, dict):
125
+ if 'response' in result:
126
+ return f"Response: {result['response']}"
127
+ else:
128
+ return f"Dict: {json.dumps(result, indent=2)}"
129
+ elif isinstance(result, str):
130
+ return f"String: {result}"
131
+ else:
132
+ return f"Other ({type(result).__name__}): {result}"
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Handle SWML document dumping
4
+ """
5
+
6
+ import sys
7
+ import json
8
+ import warnings
9
+ import argparse
10
+ from typing import TYPE_CHECKING
11
+ from ..simulation.data_generation import generate_fake_swml_post_data
12
+ from ..simulation.data_overrides import apply_convenience_mappings, apply_overrides
13
+ from ..simulation.mock_env import create_mock_request
14
+ from ..core.dynamic_config import apply_dynamic_config
15
+
16
+ if TYPE_CHECKING:
17
+ from signalwire_agents.core.agent_base import AgentBase
18
+
19
+
20
+ # Store the original print function for raw mode
21
+ original_print = print
22
+
23
+
24
+ def setup_raw_mode_suppression():
25
+ """Set up output suppression for raw mode using central logging system"""
26
+ # The central logging system is already configured via environment variable
27
+ # Just suppress any remaining warnings
28
+ warnings.filterwarnings("ignore")
29
+
30
+ # Capture and suppress print statements in raw mode if needed
31
+ def suppressed_print(*args, **kwargs):
32
+ pass
33
+
34
+ # Replace print function globally for raw mode
35
+ import builtins
36
+ builtins.print = suppressed_print
37
+
38
+
39
+ def handle_dump_swml(agent: 'AgentBase', args: argparse.Namespace) -> int:
40
+ """
41
+ Handle SWML dumping with fake post_data and mock request support
42
+
43
+ Args:
44
+ agent: The loaded agent instance
45
+ args: Parsed CLI arguments
46
+
47
+ Returns:
48
+ Exit code (0 for success, 1 for error)
49
+ """
50
+ if not args.raw:
51
+ if args.verbose:
52
+ print(f"Agent: {agent.get_name()}")
53
+ print(f"Route: {agent.route}")
54
+
55
+ # Show loaded skills
56
+ skills = agent.list_skills()
57
+ if skills:
58
+ print(f"Skills: {', '.join(skills)}")
59
+
60
+ # Show available functions
61
+ functions = agent._tool_registry.get_all_functions() if hasattr(agent, '_tool_registry') else {}
62
+ if functions:
63
+ print(f"Functions: {', '.join(functions.keys())}")
64
+
65
+ print("-" * 60)
66
+
67
+ try:
68
+ # Generate fake SWML post_data
69
+ post_data = generate_fake_swml_post_data(
70
+ call_type=args.call_type,
71
+ call_direction=args.call_direction,
72
+ call_state=args.call_state
73
+ )
74
+
75
+ # Apply convenience mappings from CLI args
76
+ post_data = apply_convenience_mappings(post_data, args)
77
+
78
+ # Apply explicit overrides
79
+ post_data = apply_overrides(post_data, args.override, args.override_json)
80
+
81
+ # Parse headers for mock request
82
+ headers = {}
83
+ for header in args.header:
84
+ if '=' in header:
85
+ key, value = header.split('=', 1)
86
+ headers[key] = value
87
+
88
+ # Parse query params for mock request (separate from userVariables)
89
+ query_params = {}
90
+ if args.query_params:
91
+ try:
92
+ query_params = json.loads(args.query_params)
93
+ except json.JSONDecodeError as e:
94
+ if not args.raw:
95
+ print(f"Warning: Invalid JSON in --query-params: {e}")
96
+
97
+ # Parse request body
98
+ request_body = {}
99
+ if args.body:
100
+ try:
101
+ request_body = json.loads(args.body)
102
+ except json.JSONDecodeError as e:
103
+ if not args.raw:
104
+ print(f"Warning: Invalid JSON in --body: {e}")
105
+
106
+ # Create mock request object
107
+ mock_request = create_mock_request(
108
+ method=args.method,
109
+ headers=headers,
110
+ query_params=query_params,
111
+ body=request_body
112
+ )
113
+
114
+ if args.verbose and not args.raw:
115
+ print(f"Using fake SWML post_data:")
116
+ print(json.dumps(post_data, indent=2))
117
+ print(f"\nMock request headers: {dict(mock_request.headers.items())}")
118
+ print(f"Mock request query params: {dict(mock_request.query_params.items())}")
119
+ print(f"Mock request method: {mock_request.method}")
120
+ print("-" * 60)
121
+
122
+ # Apply dynamic configuration with the mock request
123
+ apply_dynamic_config(agent, mock_request, verbose=args.verbose and not args.raw)
124
+
125
+ # For dynamic agents, call on_swml_request if available
126
+ if hasattr(agent, 'on_swml_request'):
127
+ try:
128
+ # Dynamic agents expect (request_data, callback_path, request)
129
+ call_id = post_data.get('call', {}).get('call_id', 'test-call-id')
130
+ modifications = agent.on_swml_request(post_data, "/swml", mock_request)
131
+
132
+ if args.verbose and not args.raw:
133
+ print(f"Dynamic agent modifications: {modifications}")
134
+
135
+ # Generate SWML with modifications
136
+ swml_doc = agent._render_swml(call_id, modifications)
137
+ except Exception as e:
138
+ if args.verbose and not args.raw:
139
+ print(f"Dynamic agent callback failed, falling back to static SWML: {e}")
140
+ # Fall back to static SWML generation
141
+ swml_doc = agent._render_swml()
142
+ else:
143
+ # Static agent - generate SWML normally
144
+ swml_doc = agent._render_swml()
145
+
146
+ if args.raw:
147
+ # Temporarily restore print for JSON output
148
+ if '--raw' in sys.argv and 'original_print' in globals():
149
+ import builtins
150
+ builtins.print = original_print
151
+
152
+ # Output only the raw JSON for piping to jq/yq
153
+ print(swml_doc)
154
+ else:
155
+ # Output formatted JSON (like raw but pretty-printed)
156
+ try:
157
+ swml_parsed = json.loads(swml_doc)
158
+ print(json.dumps(swml_parsed, indent=2))
159
+ except json.JSONDecodeError:
160
+ # If not valid JSON, show raw
161
+ print(swml_doc)
162
+
163
+ return 0
164
+
165
+ except Exception as e:
166
+ if args.raw:
167
+ # For raw mode, output error to stderr to not interfere with JSON output
168
+ original_print(f"Error generating SWML: {e}", file=sys.stderr)
169
+ if args.verbose:
170
+ import traceback
171
+ traceback.print_exc(file=sys.stderr)
172
+ else:
173
+ print(f"Error generating SWML: {e}")
174
+ if args.verbose:
175
+ import traceback
176
+ traceback.print_exc()
177
+ return 1
@@ -0,0 +1 @@
1
+ """Simulation and mock environment modules"""
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate fake SWML post_data and related helpers
4
+ """
5
+
6
+ import uuid
7
+ import json
8
+ from datetime import datetime
9
+ from typing import Dict, Any, Optional
10
+ from ..types import CallData, VarsData, PostData
11
+
12
+
13
+ def generate_fake_uuid() -> str:
14
+ """Generate a fake UUID for testing"""
15
+ return str(uuid.uuid4())
16
+
17
+
18
+ def generate_fake_node_id() -> str:
19
+ """Generate a fake node ID for testing"""
20
+ return f"test-node-{uuid.uuid4().hex[:8]}"
21
+
22
+
23
+ def generate_fake_sip_from(call_type: str) -> str:
24
+ """Generate a fake 'from' address based on call type"""
25
+ if call_type == "sip":
26
+ return f"+1555{uuid.uuid4().hex[:7]}" # Fake phone number
27
+ else: # webrtc
28
+ return f"user-{uuid.uuid4().hex[:8]}@test.domain"
29
+
30
+
31
+ def generate_fake_sip_to(call_type: str) -> str:
32
+ """Generate a fake 'to' address based on call type"""
33
+ if call_type == "sip":
34
+ return f"+1444{uuid.uuid4().hex[:7]}" # Fake phone number
35
+ else: # webrtc
36
+ return f"agent-{uuid.uuid4().hex[:8]}@test.domain"
37
+
38
+
39
+ def adapt_for_call_type(call_data: Dict[str, Any], call_type: str) -> Dict[str, Any]:
40
+ """
41
+ Adapt call data structure based on call type (sip vs webrtc)
42
+
43
+ Args:
44
+ call_data: Base call data structure
45
+ call_type: "sip" or "webrtc"
46
+
47
+ Returns:
48
+ Adapted call data with appropriate addresses and metadata
49
+ """
50
+ call_data = call_data.copy()
51
+
52
+ # Update addresses based on call type
53
+ call_data["from"] = generate_fake_sip_from(call_type)
54
+ call_data["to"] = generate_fake_sip_to(call_type)
55
+
56
+ # Add call type specific metadata
57
+ if call_type == "sip":
58
+ call_data["type"] = "phone"
59
+ call_data["headers"] = {
60
+ "User-Agent": f"Test-SIP-Client/1.0.0",
61
+ "From": f"<sip:{call_data['from']}@test.sip.provider>",
62
+ "To": f"<sip:{call_data['to']}@test.sip.provider>",
63
+ "Call-ID": call_data["call_id"]
64
+ }
65
+ else: # webrtc
66
+ call_data["type"] = "webrtc"
67
+ call_data["headers"] = {
68
+ "User-Agent": "Test-WebRTC-Client/1.0.0",
69
+ "Origin": "https://test.webrtc.app",
70
+ "Sec-WebSocket-Protocol": "sip"
71
+ }
72
+
73
+ return call_data
74
+
75
+
76
+ def generate_fake_swml_post_data(call_type: str = "webrtc",
77
+ call_direction: str = "inbound",
78
+ call_state: str = "created") -> Dict[str, Any]:
79
+ """
80
+ Generate fake SWML post_data that matches real SignalWire structure
81
+
82
+ Args:
83
+ call_type: "sip" or "webrtc" (default: webrtc)
84
+ call_direction: "inbound" or "outbound" (default: inbound)
85
+ call_state: Call state (default: created)
86
+
87
+ Returns:
88
+ Fake post_data dict with call, vars, and envs structure
89
+ """
90
+ call_id = generate_fake_uuid()
91
+ project_id = generate_fake_uuid()
92
+ space_id = generate_fake_uuid()
93
+ current_time = datetime.now().isoformat()
94
+
95
+ # Base call structure
96
+ call_data = {
97
+ "call_id": call_id,
98
+ "node_id": generate_fake_node_id(),
99
+ "segment_id": generate_fake_uuid(),
100
+ "call_session_id": generate_fake_uuid(),
101
+ "tag": call_id,
102
+ "state": call_state,
103
+ "direction": call_direction,
104
+ "type": call_type,
105
+ "from": generate_fake_sip_from(call_type),
106
+ "to": generate_fake_sip_to(call_type),
107
+ "timeout": 30,
108
+ "max_duration": 14400,
109
+ "answer_on_bridge": False,
110
+ "hangup_after_bridge": True,
111
+ "ringback": [],
112
+ "record": {},
113
+ "project_id": project_id,
114
+ "space_id": space_id,
115
+ "created_at": current_time,
116
+ "updated_at": current_time
117
+ }
118
+
119
+ # Adapt for specific call type
120
+ call_data = adapt_for_call_type(call_data, call_type)
121
+
122
+ # Complete post_data structure
123
+ post_data = {
124
+ "call": call_data,
125
+ "vars": {
126
+ "userVariables": {} # Empty by default, can be filled via overrides
127
+ },
128
+ "envs": {} # Empty by default, can be filled via overrides
129
+ }
130
+
131
+ return post_data
132
+
133
+
134
+ def generate_comprehensive_post_data(function_name: str, args: Dict[str, Any],
135
+ custom_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
136
+ """
137
+ Generate comprehensive post_data that matches what SignalWire would send
138
+
139
+ Args:
140
+ function_name: Name of the SWAIG function being called
141
+ args: Function arguments
142
+ custom_data: Optional custom data to override defaults
143
+
144
+ Returns:
145
+ Complete post_data dict with all possible keys
146
+ """
147
+ call_id = str(uuid.uuid4())
148
+ session_id = str(uuid.uuid4())
149
+ project_id = str(uuid.uuid4())
150
+ space_id = str(uuid.uuid4())
151
+ space_name = "test-space"
152
+ environment = "production"
153
+
154
+ current_time = datetime.now().isoformat()
155
+
156
+ # Base structure with all keys
157
+ post_data = {
158
+ "call_id": call_id,
159
+ "call": {
160
+ "call_id": call_id,
161
+ "node_id": f"test-node-{uuid.uuid4().hex[:8]}",
162
+ "segment_id": str(uuid.uuid4()),
163
+ "call_session_id": str(uuid.uuid4()),
164
+ "to": "+15551234567",
165
+ "from": "+15559876543",
166
+ "direction": "inbound",
167
+ "state": "answered",
168
+ "tag": call_id,
169
+ "project_id": project_id,
170
+ "space_id": space_id,
171
+ "headers": {"User-Agent": "SignalWire/1.0"},
172
+ "type": "phone",
173
+ "timeout": 30,
174
+ "answer_on_bridge": False,
175
+ "created_at": current_time,
176
+ "updated_at": current_time
177
+ },
178
+ "vars": {
179
+ "environment": environment,
180
+ "space_id": space_id,
181
+ "userVariables": {},
182
+ "call_data": {
183
+ "id": call_id,
184
+ "state": "answered",
185
+ "type": "phone",
186
+ "from": "+15559876543",
187
+ "to": "+15551234567",
188
+ "project_id": project_id,
189
+ "created_at": current_time
190
+ }
191
+ },
192
+ "params": args,
193
+ "space_id": space_id,
194
+ "project_id": project_id,
195
+ "meta_data": {
196
+ "application": {
197
+ "name": "SignalWire AI Agent",
198
+ "version": "1.0.0"
199
+ },
200
+ "swml": {
201
+ "version": "1.0.0",
202
+ "session_id": session_id
203
+ },
204
+ "ai": {
205
+ "call_id": call_id,
206
+ "session_id": session_id,
207
+ "conversation_id": session_id
208
+ },
209
+ "request": {
210
+ "method": "POST",
211
+ "source_ip": "192.168.1.1",
212
+ "user_agent": "SignalWire-AI-Agent/1.0"
213
+ },
214
+ "timing": {
215
+ "request_start": current_time,
216
+ "function_start": current_time
217
+ },
218
+ "user": {
219
+ "id": f"user-{uuid.uuid4().hex[:8]}",
220
+ "session_start": current_time,
221
+ "last_updated": current_time
222
+ }
223
+ },
224
+
225
+ # Global application data
226
+ "global_data": {
227
+ "app_name": "test_application",
228
+ "environment": "test",
229
+ "user_preferences": {"language": "en"},
230
+ "session_data": {"start_time": current_time}
231
+ },
232
+
233
+ # Conversation context
234
+ "call_log": [
235
+ {
236
+ "role": "system",
237
+ "content": "You are a helpful AI assistant created with SignalWire AI Agents."
238
+ },
239
+ {
240
+ "role": "user",
241
+ "content": f"Please call the {function_name} function"
242
+ },
243
+ {
244
+ "role": "assistant",
245
+ "content": f"I'll call the {function_name} function for you.",
246
+ "tool_calls": [
247
+ {
248
+ "id": f"call_{call_id[:8]}",
249
+ "type": "function",
250
+ "function": {
251
+ "name": function_name,
252
+ "arguments": json.dumps(args)
253
+ }
254
+ }
255
+ ]
256
+ }
257
+ ],
258
+ "raw_call_log": [
259
+ {
260
+ "role": "system",
261
+ "content": "You are a helpful AI assistant created with SignalWire AI Agents."
262
+ },
263
+ {
264
+ "role": "user",
265
+ "content": "Hello"
266
+ },
267
+ {
268
+ "role": "assistant",
269
+ "content": "Hello! How can I help you today?"
270
+ },
271
+ {
272
+ "role": "user",
273
+ "content": f"Please call the {function_name} function"
274
+ },
275
+ {
276
+ "role": "assistant",
277
+ "content": f"I'll call the {function_name} function for you.",
278
+ "tool_calls": [
279
+ {
280
+ "id": f"call_{call_id[:8]}",
281
+ "type": "function",
282
+ "function": {
283
+ "name": function_name,
284
+ "arguments": json.dumps(args)
285
+ }
286
+ }
287
+ ]
288
+ }
289
+ ],
290
+
291
+ # SWML and prompt variables
292
+ "prompt_vars": {
293
+ # From SWML prompt variables
294
+ "ai_instructions": "You are a helpful assistant",
295
+ "temperature": 0.7,
296
+ "max_tokens": 1000,
297
+ # From global_data
298
+ "app_name": "test_application",
299
+ "environment": "test",
300
+ "user_preferences": {"language": "en"},
301
+ "session_data": {"start_time": current_time},
302
+ # SWML system variables
303
+ "current_timestamp": current_time,
304
+ "call_duration": "00:02:15",
305
+ "caller_number": "+15551234567",
306
+ "to_number": "+15559876543"
307
+ },
308
+
309
+ # Permission flags (from SWML parameters)
310
+ "swaig_allow_swml": True,
311
+ "swaig_post_conversation": True,
312
+ "swaig_post_swml_vars": True,
313
+
314
+ # Additional context
315
+ "http_method": "POST",
316
+ "webhook_url": f"https://test.example.com/webhook/{function_name}",
317
+ "user_agent": "SignalWire-AI-Agent/1.0",
318
+ "request_headers": {
319
+ "Content-Type": "application/json",
320
+ "User-Agent": "SignalWire-AI-Agent/1.0",
321
+ "X-Signalwire-Call-Id": call_id,
322
+ "X-Signalwire-Session-Id": session_id
323
+ },
324
+
325
+ # SignalWire environment data
326
+ "swml_env": {
327
+ "space_id": space_id,
328
+ "project_id": project_id,
329
+ "environment": environment,
330
+ "space_name": space_name,
331
+ "api_version": "1.0.0"
332
+ }
333
+ }
334
+
335
+ # Apply custom data overrides if provided
336
+ if custom_data:
337
+ # Deep merge custom_data into post_data
338
+ for key, value in custom_data.items():
339
+ if key in post_data and isinstance(post_data[key], dict) and isinstance(value, dict):
340
+ # Merge dictionaries
341
+ post_data[key].update(value)
342
+ else:
343
+ # Replace value
344
+ post_data[key] = value
345
+
346
+ return post_data
347
+
348
+
349
+ def generate_minimal_post_data(function_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
350
+ """
351
+ Generate minimal post_data with only essential keys
352
+
353
+ Args:
354
+ function_name: Name of the SWAIG function being called
355
+ args: Function arguments
356
+
357
+ Returns:
358
+ Minimal post_data dict
359
+ """
360
+ call_id = str(uuid.uuid4())
361
+
362
+ return {
363
+ "call_id": call_id,
364
+ "params": args
365
+ }