signalwire-agents 0.1.13__py3-none-any.whl → 1.0.7__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 +176 -23
- 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/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 +1225 -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 +825 -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 +287 -0
- signalwire_agents/core/mixins/prompt_mixin.py +358 -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 +153 -0
- signalwire_agents/core/mixins/tool_mixin.py +230 -0
- signalwire_agents/core/mixins/web_mixin.py +1134 -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 +167 -200
- 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 +752 -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 +10 -1
- 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.7.data/data/share/man/man1/sw-agent-init.1 +307 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/sw-search.1 +483 -0
- signalwire_agents-1.0.7.data/data/share/man/man1/swaig-test.1 +308 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.7.dist-info}/METADATA +344 -215
- signalwire_agents-1.0.7.dist-info/RECORD +142 -0
- signalwire_agents-1.0.7.dist-info/entry_points.txt +4 -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.7.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.7.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-1.0.7.dist-info}/top_level.txt +0 -0
|
@@ -24,51 +24,11 @@ import types
|
|
|
24
24
|
from typing import Dict, List, Any, Optional, Union, Callable, Tuple, Type
|
|
25
25
|
from urllib.parse import urlparse
|
|
26
26
|
|
|
27
|
-
# Import
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if not hasattr(structlog, "_configured") or not structlog._configured:
|
|
33
|
-
structlog.configure(
|
|
34
|
-
processors=[
|
|
35
|
-
structlog.stdlib.filter_by_level,
|
|
36
|
-
structlog.stdlib.add_logger_name,
|
|
37
|
-
structlog.stdlib.add_log_level,
|
|
38
|
-
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
39
|
-
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
|
|
40
|
-
structlog.processors.StackInfoRenderer(),
|
|
41
|
-
structlog.processors.format_exc_info,
|
|
42
|
-
structlog.processors.UnicodeDecoder(),
|
|
43
|
-
structlog.dev.ConsoleRenderer()
|
|
44
|
-
],
|
|
45
|
-
context_class=dict,
|
|
46
|
-
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
47
|
-
wrapper_class=structlog.stdlib.BoundLogger,
|
|
48
|
-
cache_logger_on_first_use=True,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# Set up root logger with structlog
|
|
52
|
-
logging.basicConfig(
|
|
53
|
-
format="%(message)s",
|
|
54
|
-
stream=sys.stdout,
|
|
55
|
-
level=logging.INFO,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
# Mark as configured to avoid duplicate configuration
|
|
59
|
-
structlog._configured = True
|
|
60
|
-
|
|
61
|
-
# Create the module logger
|
|
62
|
-
logger = structlog.get_logger("swml_service")
|
|
63
|
-
|
|
64
|
-
except ImportError:
|
|
65
|
-
# Fallback to standard logging if structlog is not available
|
|
66
|
-
logging.basicConfig(
|
|
67
|
-
level=logging.INFO,
|
|
68
|
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
69
|
-
stream=sys.stdout
|
|
70
|
-
)
|
|
71
|
-
logger = logging.getLogger("swml_service")
|
|
27
|
+
# Import centralized logging system
|
|
28
|
+
from signalwire_agents.core.logging_config import get_logger
|
|
29
|
+
|
|
30
|
+
# Create the module logger using centralized system
|
|
31
|
+
logger = get_logger("swml_service")
|
|
72
32
|
|
|
73
33
|
try:
|
|
74
34
|
import fastapi
|
|
@@ -82,6 +42,7 @@ except ImportError:
|
|
|
82
42
|
|
|
83
43
|
from signalwire_agents.utils.schema_utils import SchemaUtils
|
|
84
44
|
from signalwire_agents.core.swml_handler import VerbHandlerRegistry, SWMLVerbHandler
|
|
45
|
+
from signalwire_agents.core.security_config import SecurityConfig
|
|
85
46
|
|
|
86
47
|
|
|
87
48
|
class SWMLService:
|
|
@@ -105,7 +66,8 @@ class SWMLService:
|
|
|
105
66
|
host: str = "0.0.0.0",
|
|
106
67
|
port: int = 3000,
|
|
107
68
|
basic_auth: Optional[Tuple[str, str]] = None,
|
|
108
|
-
schema_path: Optional[str] = None
|
|
69
|
+
schema_path: Optional[str] = None,
|
|
70
|
+
config_file: Optional[str] = None
|
|
109
71
|
):
|
|
110
72
|
"""
|
|
111
73
|
Initialize a new SWML service
|
|
@@ -117,21 +79,34 @@ class SWMLService:
|
|
|
117
79
|
port: Port to bind the web server to
|
|
118
80
|
basic_auth: Optional (username, password) tuple for basic auth
|
|
119
81
|
schema_path: Optional path to the schema file
|
|
82
|
+
config_file: Optional path to configuration file
|
|
120
83
|
"""
|
|
121
84
|
self.name = name
|
|
122
85
|
self.route = route.rstrip("/") # Ensure no trailing slash
|
|
123
86
|
self.host = host
|
|
124
87
|
self.port = port
|
|
125
|
-
|
|
126
|
-
|
|
88
|
+
|
|
89
|
+
# Initialize logger for this instance FIRST before using it
|
|
90
|
+
self.log = logger.bind(service=name)
|
|
91
|
+
|
|
92
|
+
# Load unified security configuration with optional config file
|
|
93
|
+
self.security = SecurityConfig(config_file=config_file, service_name=name)
|
|
94
|
+
self.security.log_config("SWMLService")
|
|
95
|
+
|
|
96
|
+
# For backward compatibility, expose SSL settings as instance attributes
|
|
97
|
+
self.ssl_enabled = self.security.ssl_enabled
|
|
98
|
+
self.domain = self.security.domain
|
|
99
|
+
self.ssl_cert_path = self.security.ssl_cert_path
|
|
100
|
+
self.ssl_key_path = self.security.ssl_key_path
|
|
127
101
|
|
|
128
102
|
# Initialize proxy detection attributes
|
|
129
103
|
self._proxy_url_base = os.environ.get('SWML_PROXY_URL_BASE')
|
|
104
|
+
self._proxy_url_base_from_env = bool(self._proxy_url_base) # Track if it came from environment
|
|
105
|
+
if self._proxy_url_base:
|
|
106
|
+
self.log.warning("SWML_PROXY_URL_BASE is set in environment - This overrides SSL configuration and port settings. Remove this variable to use automatic detection.",
|
|
107
|
+
proxy_url_base=self._proxy_url_base)
|
|
130
108
|
self._proxy_detection_done = False
|
|
131
109
|
self._proxy_debug = os.environ.get('SWML_PROXY_DEBUG', '').lower() in ('true', '1', 'yes')
|
|
132
|
-
|
|
133
|
-
# Initialize logger for this instance
|
|
134
|
-
self.log = logger.bind(service=name)
|
|
135
110
|
self.log.info("service_initializing", route=self.route, host=host, port=port)
|
|
136
111
|
|
|
137
112
|
# Set basic auth credentials
|
|
@@ -139,18 +114,8 @@ class SWMLService:
|
|
|
139
114
|
# Use provided credentials
|
|
140
115
|
self._basic_auth = basic_auth
|
|
141
116
|
else:
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
env_pass = os.environ.get('SWML_BASIC_AUTH_PASSWORD')
|
|
145
|
-
|
|
146
|
-
if env_user and env_pass:
|
|
147
|
-
# Use environment variables
|
|
148
|
-
self._basic_auth = (env_user, env_pass)
|
|
149
|
-
else:
|
|
150
|
-
# Generate random credentials as fallback
|
|
151
|
-
username = f"user_{secrets.token_hex(4)}"
|
|
152
|
-
password = secrets.token_urlsafe(16)
|
|
153
|
-
self._basic_auth = (username, password)
|
|
117
|
+
# Use unified security config for auth credentials
|
|
118
|
+
self._basic_auth = self.security.get_basic_auth()
|
|
154
119
|
|
|
155
120
|
# Find the schema file if not provided
|
|
156
121
|
if schema_path is None:
|
|
@@ -687,10 +652,8 @@ class SWMLService:
|
|
|
687
652
|
Returns:
|
|
688
653
|
Response with SWML document or error
|
|
689
654
|
"""
|
|
690
|
-
#
|
|
691
|
-
|
|
692
|
-
self._detect_proxy_from_request(request)
|
|
693
|
-
self._proxy_detection_done = True
|
|
655
|
+
# Always detect proxy from current request - allows mixing direct and proxied access
|
|
656
|
+
self._detect_proxy_from_request(request)
|
|
694
657
|
|
|
695
658
|
# Check auth
|
|
696
659
|
if not self._check_basic_auth(request):
|
|
@@ -789,21 +752,21 @@ class SWMLService:
|
|
|
789
752
|
"""
|
|
790
753
|
import uvicorn
|
|
791
754
|
|
|
792
|
-
# Store SSL configuration
|
|
793
|
-
|
|
794
|
-
|
|
755
|
+
# Store SSL configuration (override environment if explicitly provided)
|
|
756
|
+
if ssl_enabled is not None:
|
|
757
|
+
self.ssl_enabled = ssl_enabled
|
|
758
|
+
if domain is not None:
|
|
759
|
+
self.domain = domain
|
|
795
760
|
|
|
796
|
-
# Set SSL paths
|
|
797
|
-
ssl_cert_path = ssl_cert
|
|
798
|
-
ssl_key_path = ssl_key
|
|
761
|
+
# Set SSL paths (use provided paths or fall back to environment)
|
|
762
|
+
ssl_cert_path = ssl_cert or getattr(self, 'ssl_cert_path', None)
|
|
763
|
+
ssl_key_path = ssl_key or getattr(self, 'ssl_key_path', None)
|
|
799
764
|
|
|
800
765
|
# Validate SSL configuration if enabled
|
|
801
766
|
if self.ssl_enabled:
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
self.
|
|
805
|
-
elif not ssl_key_path or not os.path.exists(ssl_key_path):
|
|
806
|
-
self.log.warning("ssl_key_not_found", path=ssl_key_path)
|
|
767
|
+
is_valid, error = self.security.validate_ssl_config()
|
|
768
|
+
if not is_valid:
|
|
769
|
+
self.log.warning("ssl_config_invalid", error=error)
|
|
807
770
|
self.ssl_enabled = False
|
|
808
771
|
elif not self.domain:
|
|
809
772
|
self.log.warning("ssl_domain_not_specified")
|
|
@@ -818,8 +781,11 @@ class SWMLService:
|
|
|
818
781
|
# This avoids the FastAPI error about prefixes ending with slashes
|
|
819
782
|
normalized_route = "/" + self.route.strip("/")
|
|
820
783
|
|
|
821
|
-
# Include router with the normalized prefix
|
|
822
|
-
|
|
784
|
+
# Include router with the normalized prefix (handle root route special case)
|
|
785
|
+
if normalized_route == "/":
|
|
786
|
+
app.include_router(router)
|
|
787
|
+
else:
|
|
788
|
+
app.include_router(router, prefix=normalized_route)
|
|
823
789
|
|
|
824
790
|
# Add a catch-all route handler that will handle both /path and /path/ formats
|
|
825
791
|
# This provides the same behavior without using a trailing slash in the prefix
|
|
@@ -876,27 +842,27 @@ class SWMLService:
|
|
|
876
842
|
# Get the auth credentials
|
|
877
843
|
username, password = self._basic_auth
|
|
878
844
|
|
|
879
|
-
#
|
|
880
|
-
|
|
881
|
-
display_host = self.domain if self.ssl_enabled and self.domain else f"{host}:{port}"
|
|
845
|
+
# Get the proper URL using unified URL building
|
|
846
|
+
startup_url = self._build_full_url(include_auth=False)
|
|
882
847
|
|
|
883
848
|
self.log.info("starting_server",
|
|
884
|
-
url=
|
|
849
|
+
url=startup_url,
|
|
885
850
|
ssl_enabled=self.ssl_enabled,
|
|
886
851
|
username=username,
|
|
887
852
|
password_length=len(password))
|
|
888
853
|
|
|
889
854
|
# Print user-friendly startup message (keep for UX)
|
|
890
855
|
print(f"Service '{self.name}' is available at:")
|
|
891
|
-
print(f"URL: {
|
|
892
|
-
print(f"URL with trailing slash: {
|
|
856
|
+
print(f"URL: {startup_url}")
|
|
857
|
+
print(f"URL with trailing slash: {startup_url}/")
|
|
893
858
|
print(f"Basic Auth: {username}:{password}")
|
|
894
859
|
|
|
895
860
|
# Check if SIP routing is enabled and log additional info
|
|
896
861
|
if self._routing_callbacks:
|
|
897
862
|
print(f"Callback endpoints:")
|
|
898
863
|
for path in self._routing_callbacks:
|
|
899
|
-
|
|
864
|
+
callback_url = self._build_full_url(endpoint=path.lstrip('/'), include_auth=False)
|
|
865
|
+
print(f" {callback_url}")
|
|
900
866
|
|
|
901
867
|
# Start uvicorn with or without SSL
|
|
902
868
|
if self.ssl_enabled and ssl_cert_path and ssl_key_path:
|
|
@@ -972,149 +938,146 @@ class SWMLService:
|
|
|
972
938
|
|
|
973
939
|
return username, password
|
|
974
940
|
|
|
975
|
-
# Keep the existing methods for backward compatibility
|
|
976
|
-
|
|
977
|
-
def add_answer_verb(self, max_duration: Optional[int] = None, codecs: Optional[str] = None) -> bool:
|
|
978
|
-
"""
|
|
979
|
-
Add an answer verb to the current document
|
|
980
941
|
|
|
981
|
-
|
|
982
|
-
max_duration: Maximum duration in seconds
|
|
983
|
-
codecs: Comma-separated list of codecs
|
|
984
|
-
|
|
985
|
-
Returns:
|
|
986
|
-
True if added successfully, False otherwise
|
|
942
|
+
def _get_base_url(self, include_auth: bool = True) -> str:
|
|
987
943
|
"""
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
config["codecs"] = codecs
|
|
993
|
-
|
|
994
|
-
return self.add_verb("answer", config)
|
|
995
|
-
|
|
996
|
-
def add_hangup_verb(self, reason: Optional[str] = None) -> bool:
|
|
997
|
-
"""
|
|
998
|
-
Add a hangup verb to the current document
|
|
944
|
+
Get the base URL for this service, using proxy info if available or falling back to configured values
|
|
945
|
+
|
|
946
|
+
This is the central method for URL building that handles both startup configuration
|
|
947
|
+
and per-request proxy detection.
|
|
999
948
|
|
|
1000
949
|
Args:
|
|
1001
|
-
|
|
950
|
+
include_auth: Whether to include authentication credentials in the URL
|
|
1002
951
|
|
|
1003
952
|
Returns:
|
|
1004
|
-
|
|
953
|
+
Base URL string (protocol://[auth@]host[:port])
|
|
1005
954
|
"""
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
955
|
+
# Debug logging to understand state
|
|
956
|
+
self.log.debug("_get_base_url called",
|
|
957
|
+
has_proxy_url_base=hasattr(self, '_proxy_url_base'),
|
|
958
|
+
proxy_url_base=getattr(self, '_proxy_url_base', None),
|
|
959
|
+
proxy_url_base_from_env=getattr(self, '_proxy_url_base_from_env', False),
|
|
960
|
+
env_var=os.environ.get('SWML_PROXY_URL_BASE'),
|
|
961
|
+
include_auth=include_auth,
|
|
962
|
+
caller=inspect.stack()[1].function if len(inspect.stack()) > 1 else "unknown")
|
|
963
|
+
|
|
964
|
+
# Check if we have proxy information from a request
|
|
965
|
+
if hasattr(self, '_proxy_url_base') and self._proxy_url_base:
|
|
966
|
+
base = self._proxy_url_base.rstrip('/')
|
|
967
|
+
self.log.debug("Using proxy URL base", proxy_url_base=base)
|
|
968
|
+
|
|
969
|
+
# Add auth credentials if requested
|
|
970
|
+
if include_auth:
|
|
971
|
+
username, password = self._basic_auth
|
|
972
|
+
url = urlparse(base)
|
|
973
|
+
base = url._replace(netloc=f"{username}:{password}@{url.netloc}").geturl()
|
|
1009
974
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
def add_ai_verb(self,
|
|
1013
|
-
prompt_text: Optional[str] = None,
|
|
1014
|
-
prompt_pom: Optional[List[Dict[str, Any]]] = None,
|
|
1015
|
-
post_prompt: Optional[str] = None,
|
|
1016
|
-
post_prompt_url: Optional[str] = None,
|
|
1017
|
-
swaig: Optional[Dict[str, Any]] = None,
|
|
1018
|
-
**kwargs) -> bool:
|
|
1019
|
-
"""
|
|
1020
|
-
Add an AI verb to the current document
|
|
975
|
+
return base
|
|
1021
976
|
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
post_prompt: Post-prompt text
|
|
1026
|
-
post_prompt_url: Post-prompt URL
|
|
1027
|
-
swaig: SWAIG configuration
|
|
1028
|
-
**kwargs: Additional parameters
|
|
1029
|
-
|
|
1030
|
-
Returns:
|
|
1031
|
-
True if added successfully, False otherwise
|
|
1032
|
-
"""
|
|
1033
|
-
config = {}
|
|
977
|
+
# No proxy, use configured values
|
|
978
|
+
# Determine protocol based on SSL settings
|
|
979
|
+
protocol = "https" if self.ssl_enabled else "http"
|
|
1034
980
|
|
|
1035
|
-
#
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
981
|
+
# Debug logging
|
|
982
|
+
self.log.debug("_get_base_url",
|
|
983
|
+
ssl_enabled=self.ssl_enabled,
|
|
984
|
+
domain=self.domain,
|
|
985
|
+
port=self.port,
|
|
986
|
+
protocol=protocol)
|
|
987
|
+
|
|
988
|
+
# Determine host part
|
|
989
|
+
if self.ssl_enabled and self.domain:
|
|
990
|
+
# Use domain for SSL
|
|
991
|
+
if protocol == "https" and self.port == 443:
|
|
992
|
+
host_part = self.domain # Don't include port for standard HTTPS
|
|
993
|
+
elif protocol == "http" and self.port == 80:
|
|
994
|
+
host_part = self.domain # Don't include port for standard HTTP
|
|
995
|
+
else:
|
|
996
|
+
host_part = f"{self.domain}:{self.port}"
|
|
997
|
+
self.log.debug("Using domain with port", domain=self.domain, port=self.port, host_part=host_part)
|
|
998
|
+
else:
|
|
999
|
+
# Use configured host
|
|
1000
|
+
if self.host in ("0.0.0.0", "127.0.0.1", "localhost"):
|
|
1001
|
+
host = "localhost"
|
|
1002
|
+
else:
|
|
1003
|
+
host = self.host
|
|
1040
1004
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1005
|
+
# Include port unless it's the standard port for the protocol
|
|
1006
|
+
if (protocol == "https" and self.port == 443) or (protocol == "http" and self.port == 80):
|
|
1007
|
+
host_part = host
|
|
1008
|
+
else:
|
|
1009
|
+
host_part = f"{host}:{self.port}"
|
|
1044
1010
|
|
|
1045
|
-
#
|
|
1046
|
-
if
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
config["SWAIG"] = swaig
|
|
1052
|
-
|
|
1053
|
-
# Handle additional parameters
|
|
1054
|
-
for key, value in kwargs.items():
|
|
1055
|
-
if value is not None:
|
|
1056
|
-
config[key] = value
|
|
1057
|
-
|
|
1058
|
-
return self.add_verb("ai", config)
|
|
1011
|
+
# Build base URL
|
|
1012
|
+
if include_auth:
|
|
1013
|
+
username, password = self._basic_auth
|
|
1014
|
+
base = f"{protocol}://{username}:{password}@{host_part}"
|
|
1015
|
+
else:
|
|
1016
|
+
base = f"{protocol}://{host_part}"
|
|
1059
1017
|
|
|
1060
|
-
|
|
1018
|
+
return base
|
|
1019
|
+
|
|
1020
|
+
def _build_full_url(self, endpoint: str = "", include_auth: bool = True, query_params: Optional[Dict[str, str]] = None) -> str:
|
|
1061
1021
|
"""
|
|
1062
|
-
|
|
1022
|
+
Build the full URL for this service or a specific endpoint
|
|
1023
|
+
|
|
1024
|
+
This is the internal implementation used by both get_full_url (for AgentBase compatibility)
|
|
1025
|
+
and _build_webhook_url.
|
|
1063
1026
|
|
|
1064
1027
|
Args:
|
|
1065
|
-
endpoint:
|
|
1028
|
+
endpoint: Optional endpoint path (e.g., "swaig", "post_prompt")
|
|
1029
|
+
include_auth: Whether to include authentication credentials in the URL
|
|
1066
1030
|
query_params: Optional query parameters to append
|
|
1067
1031
|
|
|
1068
1032
|
Returns:
|
|
1069
|
-
|
|
1033
|
+
Full URL string
|
|
1070
1034
|
"""
|
|
1071
|
-
#
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
#
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1035
|
+
# Get base URL using central method
|
|
1036
|
+
base = self._get_base_url(include_auth=include_auth)
|
|
1037
|
+
|
|
1038
|
+
# Build path
|
|
1039
|
+
if endpoint:
|
|
1040
|
+
# Ensure endpoint doesn't start with slash
|
|
1041
|
+
endpoint = endpoint.lstrip('/')
|
|
1042
|
+
# Add trailing slash to endpoint to prevent redirects
|
|
1043
|
+
if not endpoint.endswith('/'):
|
|
1044
|
+
endpoint = f"{endpoint}/"
|
|
1045
|
+
path = f"{self.route}/{endpoint}"
|
|
1080
1046
|
else:
|
|
1081
|
-
#
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
# Use domain if available and SSL is enabled
|
|
1085
|
-
if getattr(self, 'ssl_enabled', False) and getattr(self, 'domain', None):
|
|
1086
|
-
host_part = self.domain
|
|
1087
|
-
else:
|
|
1088
|
-
# For local URLs
|
|
1089
|
-
if self.host in ("0.0.0.0", "127.0.0.1", "localhost"):
|
|
1090
|
-
host = "localhost"
|
|
1091
|
-
else:
|
|
1092
|
-
host = self.host
|
|
1093
|
-
|
|
1094
|
-
host_part = f"{host}:{self.port}"
|
|
1095
|
-
|
|
1096
|
-
# Always include auth credentials
|
|
1097
|
-
username, password = self._basic_auth
|
|
1098
|
-
base = f"{protocol}://{username}:{password}@{host_part}"
|
|
1047
|
+
# Just the route itself
|
|
1048
|
+
path = self.route if self.route != "/" else ""
|
|
1099
1049
|
|
|
1100
|
-
# Ensure the endpoint has a trailing slash to prevent redirects
|
|
1101
|
-
if endpoint and not endpoint.endswith('/'):
|
|
1102
|
-
endpoint = f"{endpoint}/"
|
|
1103
|
-
|
|
1104
|
-
# Simple path - use the route directly with the endpoint
|
|
1105
|
-
path = f"{self.route}/{endpoint}"
|
|
1106
|
-
|
|
1107
1050
|
# Construct full URL
|
|
1108
1051
|
url = f"{base}{path}"
|
|
1109
1052
|
|
|
1110
|
-
# Add query parameters if any
|
|
1053
|
+
# Add query parameters if any
|
|
1111
1054
|
if query_params:
|
|
1112
1055
|
filtered_params = {k: v for k, v in query_params.items() if v}
|
|
1113
1056
|
if filtered_params:
|
|
1114
1057
|
params = "&".join([f"{k}={v}" for k, v in filtered_params.items()])
|
|
1115
1058
|
url = f"{url}?{params}"
|
|
1059
|
+
|
|
1060
|
+
return url
|
|
1061
|
+
|
|
1062
|
+
def _build_webhook_url(self, endpoint: str, query_params: Optional[Dict[str, str]] = None) -> str:
|
|
1063
|
+
"""
|
|
1064
|
+
Helper method to build webhook URLs consistently
|
|
1065
|
+
|
|
1066
|
+
Args:
|
|
1067
|
+
endpoint: The endpoint path (e.g., "swaig", "post_prompt")
|
|
1068
|
+
query_params: Optional query parameters to append
|
|
1116
1069
|
|
|
1117
|
-
|
|
1070
|
+
Returns:
|
|
1071
|
+
Fully constructed webhook URL
|
|
1072
|
+
"""
|
|
1073
|
+
self.log.debug("_build_webhook_url called",
|
|
1074
|
+
endpoint=endpoint,
|
|
1075
|
+
query_params=query_params,
|
|
1076
|
+
proxy_url_base=getattr(self, '_proxy_url_base', None),
|
|
1077
|
+
proxy_url_base_from_env=getattr(self, '_proxy_url_base_from_env', False))
|
|
1078
|
+
|
|
1079
|
+
# Use the central URL building method
|
|
1080
|
+
return self._build_full_url(endpoint=endpoint, include_auth=True, query_params=query_params)
|
|
1118
1081
|
|
|
1119
1082
|
def _detect_proxy_from_request(self, request: Request) -> None:
|
|
1120
1083
|
"""
|
|
@@ -1124,6 +1087,10 @@ class SWMLService:
|
|
|
1124
1087
|
Args:
|
|
1125
1088
|
request: FastAPI Request object
|
|
1126
1089
|
"""
|
|
1090
|
+
# If SWML_PROXY_URL_BASE was already set (e.g., from environment), don't override it
|
|
1091
|
+
if self._proxy_url_base:
|
|
1092
|
+
return
|
|
1093
|
+
|
|
1127
1094
|
# First check for standard X-Forwarded headers (used by most proxies including ngrok)
|
|
1128
1095
|
forwarded_host = request.headers.get("X-Forwarded-Host")
|
|
1129
1096
|
forwarded_proto = request.headers.get("X-Forwarded-Proto", "http")
|
|
@@ -1208,4 +1175,4 @@ class SWMLService:
|
|
|
1208
1175
|
if proxy_url:
|
|
1209
1176
|
self._proxy_url_base = proxy_url.rstrip('/')
|
|
1210
1177
|
self.log.info("proxy_url_manually_set", proxy_url_base=self._proxy_url_base)
|
|
1211
|
-
self._proxy_detection_done = True
|
|
1178
|
+
self._proxy_detection_done = True
|
|
@@ -52,7 +52,6 @@ class ConciergeAgent(AgentBase):
|
|
|
52
52
|
welcome_message: Optional[str] = None,
|
|
53
53
|
name: str = "concierge",
|
|
54
54
|
route: str = "/concierge",
|
|
55
|
-
enable_state_tracking: bool = True,
|
|
56
55
|
**kwargs
|
|
57
56
|
):
|
|
58
57
|
"""
|
|
@@ -67,7 +66,6 @@ class ConciergeAgent(AgentBase):
|
|
|
67
66
|
welcome_message: Optional custom welcome message
|
|
68
67
|
name: Agent name for the route
|
|
69
68
|
route: HTTP route for this agent
|
|
70
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
|
71
69
|
**kwargs: Additional arguments for AgentBase
|
|
72
70
|
"""
|
|
73
71
|
# Initialize the base agent
|
|
@@ -75,7 +73,6 @@ class ConciergeAgent(AgentBase):
|
|
|
75
73
|
name=name,
|
|
76
74
|
route=route,
|
|
77
75
|
use_pom=True,
|
|
78
|
-
enable_state_tracking=enable_state_tracking,
|
|
79
76
|
**kwargs
|
|
80
77
|
)
|
|
81
78
|
|
|
@@ -51,7 +51,6 @@ class FAQBotAgent(AgentBase):
|
|
|
51
51
|
persona: Optional[str] = None,
|
|
52
52
|
name: str = "faq_bot",
|
|
53
53
|
route: str = "/faq",
|
|
54
|
-
enable_state_tracking: bool = True, # Enable state tracking by default
|
|
55
54
|
**kwargs
|
|
56
55
|
):
|
|
57
56
|
"""
|
|
@@ -66,7 +65,6 @@ class FAQBotAgent(AgentBase):
|
|
|
66
65
|
persona: Optional custom personality description
|
|
67
66
|
name: Agent name for the route
|
|
68
67
|
route: HTTP route for this agent
|
|
69
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
|
70
68
|
**kwargs: Additional arguments for AgentBase
|
|
71
69
|
"""
|
|
72
70
|
# Initialize the base agent
|
|
@@ -74,7 +72,6 @@ class FAQBotAgent(AgentBase):
|
|
|
74
72
|
name=name,
|
|
75
73
|
route=route,
|
|
76
74
|
use_pom=True,
|
|
77
|
-
enable_state_tracking=enable_state_tracking, # Pass state tracking parameter to base
|
|
78
75
|
**kwargs
|
|
79
76
|
)
|
|
80
77
|
|
|
@@ -45,7 +45,6 @@ class InfoGathererAgent(AgentBase):
|
|
|
45
45
|
questions: Optional[List[Dict[str, str]]] = None,
|
|
46
46
|
name: str = "info_gatherer",
|
|
47
47
|
route: str = "/info_gatherer",
|
|
48
|
-
enable_state_tracking: bool = True, # Enable state tracking by default for InfoGatherer
|
|
49
48
|
**kwargs
|
|
50
49
|
):
|
|
51
50
|
"""
|
|
@@ -59,7 +58,6 @@ class InfoGathererAgent(AgentBase):
|
|
|
59
58
|
- confirm: (Optional) If set to True, the agent will confirm the answer before submitting
|
|
60
59
|
name: Agent name for the route
|
|
61
60
|
route: HTTP route for this agent
|
|
62
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
|
63
61
|
**kwargs: Additional arguments for AgentBase
|
|
64
62
|
"""
|
|
65
63
|
# Initialize the base agent
|
|
@@ -67,7 +65,6 @@ class InfoGathererAgent(AgentBase):
|
|
|
67
65
|
name=name,
|
|
68
66
|
route=route,
|
|
69
67
|
use_pom=True,
|
|
70
|
-
enable_state_tracking=enable_state_tracking, # Pass state tracking parameter to base
|
|
71
68
|
**kwargs
|
|
72
69
|
)
|
|
73
70
|
|
|
@@ -41,7 +41,6 @@ class ReceptionistAgent(AgentBase):
|
|
|
41
41
|
route: str = "/receptionist",
|
|
42
42
|
greeting: str = "Thank you for calling. How can I help you today?",
|
|
43
43
|
voice: str = "rime.spore",
|
|
44
|
-
enable_state_tracking: bool = True, # Enable state tracking by default
|
|
45
44
|
**kwargs
|
|
46
45
|
):
|
|
47
46
|
"""
|
|
@@ -56,7 +55,6 @@ class ReceptionistAgent(AgentBase):
|
|
|
56
55
|
route: HTTP route for this agent
|
|
57
56
|
greeting: Initial greeting message
|
|
58
57
|
voice: Voice ID to use
|
|
59
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
|
60
58
|
**kwargs: Additional arguments for AgentBase
|
|
61
59
|
"""
|
|
62
60
|
# Initialize the base agent
|
|
@@ -64,7 +62,6 @@ class ReceptionistAgent(AgentBase):
|
|
|
64
62
|
name=name,
|
|
65
63
|
route=route,
|
|
66
64
|
use_pom=True,
|
|
67
|
-
enable_state_tracking=enable_state_tracking, # Pass state tracking parameter to base
|
|
68
65
|
**kwargs
|
|
69
66
|
)
|
|
70
67
|
|
|
@@ -62,7 +62,6 @@ class SurveyAgent(AgentBase):
|
|
|
62
62
|
max_retries: int = 2,
|
|
63
63
|
name: str = "survey",
|
|
64
64
|
route: str = "/survey",
|
|
65
|
-
enable_state_tracking: bool = True, # Enable state tracking by default
|
|
66
65
|
**kwargs
|
|
67
66
|
):
|
|
68
67
|
"""
|
|
@@ -83,7 +82,6 @@ class SurveyAgent(AgentBase):
|
|
|
83
82
|
max_retries: Maximum number of times to retry invalid answers
|
|
84
83
|
name: Name for the agent (default: "survey")
|
|
85
84
|
route: HTTP route for the agent (default: "/survey")
|
|
86
|
-
enable_state_tracking: Whether to enable state tracking (default: True)
|
|
87
85
|
**kwargs: Additional arguments for AgentBase
|
|
88
86
|
"""
|
|
89
87
|
# Initialize the base agent
|
|
@@ -91,7 +89,6 @@ class SurveyAgent(AgentBase):
|
|
|
91
89
|
name=name,
|
|
92
90
|
route=route,
|
|
93
91
|
use_pom=True,
|
|
94
|
-
enable_state_tracking=enable_state_tracking, # Pass state tracking parameter to base
|
|
95
92
|
**kwargs
|
|
96
93
|
)
|
|
97
94
|
|