signalwire-agents 0.1.27__py3-none-any.whl → 0.1.28__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 -4
- signalwire_agents/cli/config.py +11 -1
- signalwire_agents/cli/simulation/data_overrides.py +6 -2
- signalwire_agents/cli/test_swaig.py +6 -0
- signalwire_agents/core/agent_base.py +1 -12
- signalwire_agents/core/mixins/state_mixin.py +1 -67
- signalwire_agents/core/mixins/tool_mixin.py +0 -65
- 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/skills/mcp_gateway/README.md +230 -0
- signalwire_agents/skills/mcp_gateway/__init__.py +1 -0
- signalwire_agents/skills/mcp_gateway/skill.py +339 -0
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/METADATA +1 -59
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/RECORD +21 -21
- signalwire_agents/core/state/__init__.py +0 -17
- signalwire_agents/core/state/file_state_manager.py +0 -219
- signalwire_agents/core/state/state_manager.py +0 -101
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/entry_points.txt +0 -0
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.27.dist-info → signalwire_agents-0.1.28.dist-info}/top_level.txt +0 -0
@@ -1,219 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Copyright (c) 2025 SignalWire
|
3
|
-
|
4
|
-
This file is part of the SignalWire AI Agents SDK.
|
5
|
-
|
6
|
-
Licensed under the MIT License.
|
7
|
-
See LICENSE file in the project root for full license information.
|
8
|
-
"""
|
9
|
-
|
10
|
-
"""
|
11
|
-
File-based implementation of the StateManager interface
|
12
|
-
"""
|
13
|
-
|
14
|
-
import os
|
15
|
-
import json
|
16
|
-
import tempfile
|
17
|
-
import time
|
18
|
-
from datetime import datetime, timedelta
|
19
|
-
from typing import Dict, Any, Optional, List
|
20
|
-
|
21
|
-
from .state_manager import StateManager
|
22
|
-
|
23
|
-
|
24
|
-
class FileStateManager(StateManager):
|
25
|
-
"""
|
26
|
-
File-based state manager implementation
|
27
|
-
|
28
|
-
This implementation stores state data as JSON files in a directory.
|
29
|
-
Each call's state is stored in a separate file named by call_id.
|
30
|
-
Files older than expiry_days are automatically cleaned up.
|
31
|
-
|
32
|
-
This is suitable for development and low-volume deployments.
|
33
|
-
For production, consider using database or Redis implementations.
|
34
|
-
"""
|
35
|
-
|
36
|
-
def __init__(
|
37
|
-
self,
|
38
|
-
storage_dir: Optional[str] = None,
|
39
|
-
expiry_days: float = 1.0
|
40
|
-
):
|
41
|
-
"""
|
42
|
-
Initialize the file state manager
|
43
|
-
|
44
|
-
Args:
|
45
|
-
storage_dir: Directory to store state files (default: system temp dir)
|
46
|
-
expiry_days: Days after which state files are considered expired
|
47
|
-
"""
|
48
|
-
self.storage_dir = storage_dir or os.path.join(tempfile.gettempdir(), "signalwire_state")
|
49
|
-
self.expiry_days = expiry_days
|
50
|
-
|
51
|
-
# Create storage directory if it doesn't exist
|
52
|
-
if not os.path.exists(self.storage_dir):
|
53
|
-
os.makedirs(self.storage_dir)
|
54
|
-
|
55
|
-
def _get_file_path(self, call_id: str) -> str:
|
56
|
-
"""Get the file path for a call_id"""
|
57
|
-
# Sanitize call_id to ensure it's safe for a filename
|
58
|
-
sanitized_id = "".join(c for c in call_id if c.isalnum() or c in "-_.")
|
59
|
-
return os.path.join(self.storage_dir, f"{sanitized_id}.json")
|
60
|
-
|
61
|
-
def store(self, call_id: str, data: Dict[str, Any]) -> bool:
|
62
|
-
"""
|
63
|
-
Store state data for a call
|
64
|
-
|
65
|
-
Args:
|
66
|
-
call_id: Unique identifier for the call
|
67
|
-
data: Dictionary of state data to store
|
68
|
-
|
69
|
-
Returns:
|
70
|
-
True if successful, False otherwise
|
71
|
-
"""
|
72
|
-
try:
|
73
|
-
# Add metadata including timestamp
|
74
|
-
state_data = {
|
75
|
-
"call_id": call_id,
|
76
|
-
"created_at": datetime.now().isoformat(),
|
77
|
-
"last_updated": datetime.now().isoformat(),
|
78
|
-
"data": data
|
79
|
-
}
|
80
|
-
|
81
|
-
file_path = self._get_file_path(call_id)
|
82
|
-
with open(file_path, "w") as f:
|
83
|
-
json.dump(state_data, f, indent=2)
|
84
|
-
return True
|
85
|
-
except Exception as e:
|
86
|
-
print(f"Error storing state for call {call_id}: {e}")
|
87
|
-
return False
|
88
|
-
|
89
|
-
def retrieve(self, call_id: str) -> Optional[Dict[str, Any]]:
|
90
|
-
"""
|
91
|
-
Retrieve state data for a call
|
92
|
-
|
93
|
-
Args:
|
94
|
-
call_id: Unique identifier for the call
|
95
|
-
|
96
|
-
Returns:
|
97
|
-
Dictionary of state data or None if not found
|
98
|
-
"""
|
99
|
-
file_path = self._get_file_path(call_id)
|
100
|
-
if not os.path.exists(file_path):
|
101
|
-
return None
|
102
|
-
|
103
|
-
try:
|
104
|
-
with open(file_path, "r") as f:
|
105
|
-
state_data = json.load(f)
|
106
|
-
|
107
|
-
# Check if the file is expired
|
108
|
-
created_at = datetime.fromisoformat(state_data["created_at"])
|
109
|
-
if (datetime.now() - created_at) > timedelta(days=self.expiry_days):
|
110
|
-
# Expired, so delete it and return None
|
111
|
-
os.remove(file_path)
|
112
|
-
return None
|
113
|
-
|
114
|
-
return state_data["data"]
|
115
|
-
except Exception as e:
|
116
|
-
print(f"Error retrieving state for call {call_id}: {e}")
|
117
|
-
return None
|
118
|
-
|
119
|
-
def update(self, call_id: str, data: Dict[str, Any]) -> bool:
|
120
|
-
"""
|
121
|
-
Update state data for a call
|
122
|
-
|
123
|
-
Args:
|
124
|
-
call_id: Unique identifier for the call
|
125
|
-
data: Dictionary of state data to update (merged with existing)
|
126
|
-
|
127
|
-
Returns:
|
128
|
-
True if successful, False otherwise
|
129
|
-
"""
|
130
|
-
file_path = self._get_file_path(call_id)
|
131
|
-
if not os.path.exists(file_path):
|
132
|
-
# If no existing data, just store new data
|
133
|
-
return self.store(call_id, data)
|
134
|
-
|
135
|
-
try:
|
136
|
-
# Read existing data
|
137
|
-
with open(file_path, "r") as f:
|
138
|
-
state_data = json.load(f)
|
139
|
-
|
140
|
-
# Update the data (deep merge)
|
141
|
-
existing_data = state_data["data"]
|
142
|
-
self._deep_update(existing_data, data)
|
143
|
-
|
144
|
-
# Update metadata
|
145
|
-
state_data["last_updated"] = datetime.now().isoformat()
|
146
|
-
state_data["data"] = existing_data
|
147
|
-
|
148
|
-
# Write back to file
|
149
|
-
with open(file_path, "w") as f:
|
150
|
-
json.dump(state_data, f, indent=2)
|
151
|
-
|
152
|
-
return True
|
153
|
-
except Exception as e:
|
154
|
-
print(f"Error updating state for call {call_id}: {e}")
|
155
|
-
return False
|
156
|
-
|
157
|
-
def delete(self, call_id: str) -> bool:
|
158
|
-
"""
|
159
|
-
Delete state data for a call
|
160
|
-
|
161
|
-
Args:
|
162
|
-
call_id: Unique identifier for the call
|
163
|
-
|
164
|
-
Returns:
|
165
|
-
True if successful, False otherwise
|
166
|
-
"""
|
167
|
-
file_path = self._get_file_path(call_id)
|
168
|
-
if not os.path.exists(file_path):
|
169
|
-
return False
|
170
|
-
|
171
|
-
try:
|
172
|
-
os.remove(file_path)
|
173
|
-
return True
|
174
|
-
except Exception as e:
|
175
|
-
print(f"Error deleting state for call {call_id}: {e}")
|
176
|
-
return False
|
177
|
-
|
178
|
-
def cleanup_expired(self) -> int:
|
179
|
-
"""
|
180
|
-
Clean up expired state files
|
181
|
-
|
182
|
-
Returns:
|
183
|
-
Number of expired files cleaned up
|
184
|
-
"""
|
185
|
-
count = 0
|
186
|
-
try:
|
187
|
-
# Get all state files
|
188
|
-
for filename in os.listdir(self.storage_dir):
|
189
|
-
if not filename.endswith(".json"):
|
190
|
-
continue
|
191
|
-
|
192
|
-
file_path = os.path.join(self.storage_dir, filename)
|
193
|
-
|
194
|
-
try:
|
195
|
-
# Read the file to check creation time
|
196
|
-
with open(file_path, "r") as f:
|
197
|
-
state_data = json.load(f)
|
198
|
-
|
199
|
-
# Check if the file is expired
|
200
|
-
created_at = datetime.fromisoformat(state_data["created_at"])
|
201
|
-
if (datetime.now() - created_at) > timedelta(days=self.expiry_days):
|
202
|
-
os.remove(file_path)
|
203
|
-
count += 1
|
204
|
-
except Exception:
|
205
|
-
# Skip files that can't be processed
|
206
|
-
continue
|
207
|
-
|
208
|
-
return count
|
209
|
-
except Exception as e:
|
210
|
-
print(f"Error cleaning up expired state files: {e}")
|
211
|
-
return count
|
212
|
-
|
213
|
-
def _deep_update(self, d: Dict, u: Dict) -> None:
|
214
|
-
"""Recursively update a nested dictionary"""
|
215
|
-
for k, v in u.items():
|
216
|
-
if isinstance(v, dict) and k in d and isinstance(d[k], dict):
|
217
|
-
self._deep_update(d[k], v)
|
218
|
-
else:
|
219
|
-
d[k] = v
|
@@ -1,101 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Copyright (c) 2025 SignalWire
|
3
|
-
|
4
|
-
This file is part of the SignalWire AI Agents SDK.
|
5
|
-
|
6
|
-
Licensed under the MIT License.
|
7
|
-
See LICENSE file in the project root for full license information.
|
8
|
-
"""
|
9
|
-
|
10
|
-
"""
|
11
|
-
Abstract base class for state management
|
12
|
-
"""
|
13
|
-
|
14
|
-
from abc import ABC, abstractmethod
|
15
|
-
from typing import Dict, Any, Optional
|
16
|
-
|
17
|
-
|
18
|
-
class StateManager(ABC):
|
19
|
-
"""
|
20
|
-
Abstract base class for state management
|
21
|
-
|
22
|
-
This defines the interface that all state manager implementations
|
23
|
-
must follow. State managers are responsible for storing, retrieving,
|
24
|
-
and managing call-specific state data.
|
25
|
-
"""
|
26
|
-
|
27
|
-
@abstractmethod
|
28
|
-
def store(self, call_id: str, data: Dict[str, Any]) -> bool:
|
29
|
-
"""
|
30
|
-
Store state data for a call
|
31
|
-
|
32
|
-
Args:
|
33
|
-
call_id: Unique identifier for the call
|
34
|
-
data: Dictionary of state data to store
|
35
|
-
|
36
|
-
Returns:
|
37
|
-
True if successful, False otherwise
|
38
|
-
"""
|
39
|
-
pass
|
40
|
-
|
41
|
-
@abstractmethod
|
42
|
-
def retrieve(self, call_id: str) -> Optional[Dict[str, Any]]:
|
43
|
-
"""
|
44
|
-
Retrieve state data for a call
|
45
|
-
|
46
|
-
Args:
|
47
|
-
call_id: Unique identifier for the call
|
48
|
-
|
49
|
-
Returns:
|
50
|
-
Dictionary of state data or None if not found
|
51
|
-
"""
|
52
|
-
pass
|
53
|
-
|
54
|
-
@abstractmethod
|
55
|
-
def update(self, call_id: str, data: Dict[str, Any]) -> bool:
|
56
|
-
"""
|
57
|
-
Update state data for a call
|
58
|
-
|
59
|
-
Args:
|
60
|
-
call_id: Unique identifier for the call
|
61
|
-
data: Dictionary of state data to update (merged with existing)
|
62
|
-
|
63
|
-
Returns:
|
64
|
-
True if successful, False otherwise
|
65
|
-
"""
|
66
|
-
pass
|
67
|
-
|
68
|
-
@abstractmethod
|
69
|
-
def delete(self, call_id: str) -> bool:
|
70
|
-
"""
|
71
|
-
Delete state data for a call
|
72
|
-
|
73
|
-
Args:
|
74
|
-
call_id: Unique identifier for the call
|
75
|
-
|
76
|
-
Returns:
|
77
|
-
True if successful, False otherwise
|
78
|
-
"""
|
79
|
-
pass
|
80
|
-
|
81
|
-
@abstractmethod
|
82
|
-
def cleanup_expired(self) -> int:
|
83
|
-
"""
|
84
|
-
Clean up expired state data
|
85
|
-
|
86
|
-
Returns:
|
87
|
-
Number of expired items cleaned up
|
88
|
-
"""
|
89
|
-
pass
|
90
|
-
|
91
|
-
def exists(self, call_id: str) -> bool:
|
92
|
-
"""
|
93
|
-
Check if state exists for a call
|
94
|
-
|
95
|
-
Args:
|
96
|
-
call_id: Unique identifier for the call
|
97
|
-
|
98
|
-
Returns:
|
99
|
-
True if state exists, False otherwise
|
100
|
-
"""
|
101
|
-
return self.retrieve(call_id) is not None
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|