signalwire-agents 0.1.13__py3-none-any.whl → 0.1.15__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 +28 -11
- signalwire_agents/cli/build_search.py +174 -14
- signalwire_agents/cli/test_swaig.py +159 -114
- signalwire_agents/core/agent_base.py +446 -78
- signalwire_agents/core/logging_config.py +162 -18
- signalwire_agents/core/skill_manager.py +2 -2
- signalwire_agents/core/swml_service.py +5 -45
- signalwire_agents/search/document_processor.py +275 -14
- signalwire_agents/search/index_builder.py +45 -10
- signalwire_agents/search/query_processor.py +27 -12
- signalwire_agents/skills/__init__.py +1 -1
- signalwire_agents/skills/native_vector_search/skill.py +24 -6
- signalwire_agents/skills/registry.py +58 -42
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-0.1.15.dist-info}/METADATA +1 -1
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-0.1.15.dist-info}/RECORD +20 -20
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-0.1.15.dist-info}/entry_points.txt +1 -1
- {signalwire_agents-0.1.13.data → signalwire_agents-0.1.15.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-0.1.15.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-0.1.15.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.13.dist-info → signalwire_agents-0.1.15.dist-info}/top_level.txt +0 -0
@@ -25,7 +25,7 @@ import re
|
|
25
25
|
import signal
|
26
26
|
import sys
|
27
27
|
from typing import Optional, Union, List, Dict, Any, Tuple, Callable, Type
|
28
|
-
from urllib.parse import urlparse, urlencode
|
28
|
+
from urllib.parse import urlparse, urlencode, urlunparse
|
29
29
|
|
30
30
|
try:
|
31
31
|
import fastapi
|
@@ -44,31 +44,7 @@ except ImportError:
|
|
44
44
|
"uvicorn is required. Install it with: pip install uvicorn"
|
45
45
|
)
|
46
46
|
|
47
|
-
|
48
|
-
import structlog
|
49
|
-
# Configure structlog only if not already configured
|
50
|
-
if not structlog.is_configured():
|
51
|
-
structlog.configure(
|
52
|
-
processors=[
|
53
|
-
structlog.stdlib.filter_by_level,
|
54
|
-
structlog.stdlib.add_logger_name,
|
55
|
-
structlog.stdlib.add_log_level,
|
56
|
-
structlog.stdlib.PositionalArgumentsFormatter(),
|
57
|
-
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
|
58
|
-
structlog.processors.StackInfoRenderer(),
|
59
|
-
structlog.processors.format_exc_info,
|
60
|
-
structlog.processors.UnicodeDecoder(),
|
61
|
-
structlog.dev.ConsoleRenderer()
|
62
|
-
],
|
63
|
-
context_class=dict,
|
64
|
-
logger_factory=structlog.stdlib.LoggerFactory(),
|
65
|
-
wrapper_class=structlog.stdlib.BoundLogger,
|
66
|
-
cache_logger_on_first_use=True,
|
67
|
-
)
|
68
|
-
except ImportError:
|
69
|
-
raise ImportError(
|
70
|
-
"structlog is required. Install it with: pip install structlog"
|
71
|
-
)
|
47
|
+
|
72
48
|
|
73
49
|
from signalwire_agents.core.pom_builder import PomBuilder
|
74
50
|
from signalwire_agents.core.swaig_function import SWAIGFunction
|
@@ -82,8 +58,8 @@ from signalwire_agents.core.skill_manager import SkillManager
|
|
82
58
|
from signalwire_agents.utils.schema_utils import SchemaUtils
|
83
59
|
from signalwire_agents.core.logging_config import get_logger, get_execution_mode
|
84
60
|
|
85
|
-
# Create a logger
|
86
|
-
logger =
|
61
|
+
# Create a logger using centralized system
|
62
|
+
logger = get_logger("agent_base")
|
87
63
|
|
88
64
|
class EphemeralAgentConfig:
|
89
65
|
"""
|
@@ -1323,51 +1299,61 @@ class AgentBase(SWMLService):
|
|
1323
1299
|
script_name = os.getenv('SCRIPT_NAME', '')
|
1324
1300
|
base_url = f"{protocol}://{host}{script_name}"
|
1325
1301
|
elif mode == 'lambda':
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1302
|
+
# AWS Lambda Function URL format
|
1303
|
+
lambda_url = os.getenv('AWS_LAMBDA_FUNCTION_URL')
|
1304
|
+
if lambda_url:
|
1305
|
+
base_url = lambda_url.rstrip('/')
|
1329
1306
|
else:
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
base_url = function_url
|
1307
|
+
# Fallback construction for Lambda
|
1308
|
+
region = os.getenv('AWS_REGION', 'us-east-1')
|
1309
|
+
function_name = os.getenv('AWS_LAMBDA_FUNCTION_NAME', 'unknown')
|
1310
|
+
base_url = f"https://{function_name}.lambda-url.{region}.on.aws"
|
1311
|
+
elif mode == 'google_cloud_function':
|
1312
|
+
# Google Cloud Functions URL format
|
1313
|
+
project_id = os.getenv('GOOGLE_CLOUD_PROJECT') or os.getenv('GCP_PROJECT')
|
1314
|
+
region = os.getenv('FUNCTION_REGION') or os.getenv('GOOGLE_CLOUD_REGION', 'us-central1')
|
1315
|
+
service_name = os.getenv('K_SERVICE') or os.getenv('FUNCTION_TARGET', 'unknown')
|
1316
|
+
|
1317
|
+
if project_id:
|
1318
|
+
base_url = f"https://{region}-{project_id}.cloudfunctions.net/{service_name}"
|
1343
1319
|
else:
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
else:
|
1354
|
-
# Server mode - preserve existing logic
|
1355
|
-
if self._proxy_url_base:
|
1356
|
-
proxy_base = self._proxy_url_base.rstrip('/')
|
1357
|
-
route = self.route if self.route.startswith('/') else f"/{self.route}"
|
1358
|
-
base_url = f"{proxy_base}{route}"
|
1320
|
+
# Fallback for local testing or incomplete environment
|
1321
|
+
base_url = f"https://localhost:8080"
|
1322
|
+
elif mode == 'azure_function':
|
1323
|
+
# Azure Functions URL format
|
1324
|
+
function_app_name = os.getenv('WEBSITE_SITE_NAME') or os.getenv('AZURE_FUNCTIONS_APP_NAME')
|
1325
|
+
function_name = os.getenv('AZURE_FUNCTION_NAME', 'unknown')
|
1326
|
+
|
1327
|
+
if function_app_name:
|
1328
|
+
base_url = f"https://{function_app_name}.azurewebsites.net/api/{function_name}"
|
1359
1329
|
else:
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
if
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1330
|
+
# Fallback for local testing
|
1331
|
+
base_url = f"https://localhost:7071/api/{function_name}"
|
1332
|
+
else:
|
1333
|
+
# Server mode
|
1334
|
+
protocol = 'https' if self.ssl_cert and self.ssl_key else 'http'
|
1335
|
+
base_url = f"{protocol}://{self.host}:{self.port}"
|
1336
|
+
|
1337
|
+
# Add route if not already included (for server mode)
|
1338
|
+
if mode == 'server' and self.route and not base_url.endswith(self.route):
|
1339
|
+
base_url = f"{base_url}/{self.route.lstrip('/')}"
|
1340
|
+
|
1341
|
+
# Add authentication if requested
|
1342
|
+
if include_auth:
|
1343
|
+
username, password = self.get_basic_auth_credentials()
|
1344
|
+
if username and password:
|
1345
|
+
# Parse URL to insert auth
|
1346
|
+
from urllib.parse import urlparse, urlunparse
|
1347
|
+
parsed = urlparse(base_url)
|
1348
|
+
# Reconstruct with auth
|
1349
|
+
base_url = urlunparse((
|
1350
|
+
parsed.scheme,
|
1351
|
+
f"{username}:{password}@{parsed.netloc}",
|
1352
|
+
parsed.path,
|
1353
|
+
parsed.params,
|
1354
|
+
parsed.query,
|
1355
|
+
parsed.fragment
|
1356
|
+
))
|
1371
1357
|
|
1372
1358
|
return base_url
|
1373
1359
|
|
@@ -1386,11 +1372,8 @@ class AgentBase(SWMLService):
|
|
1386
1372
|
mode = get_execution_mode()
|
1387
1373
|
|
1388
1374
|
if mode != 'server':
|
1389
|
-
# In serverless mode, use the serverless-appropriate URL
|
1390
|
-
base_url = self.get_full_url()
|
1391
|
-
|
1392
|
-
# For serverless, we don't need auth in webhook URLs since auth is handled differently
|
1393
|
-
# and we want to return the actual platform URL
|
1375
|
+
# In serverless mode, use the serverless-appropriate URL with auth
|
1376
|
+
base_url = self.get_full_url(include_auth=True)
|
1394
1377
|
|
1395
1378
|
# Ensure the endpoint has a trailing slash to prevent redirects
|
1396
1379
|
if endpoint in ["swaig", "post_prompt"]:
|
@@ -1905,6 +1888,124 @@ class AgentBase(SWMLService):
|
|
1905
1888
|
else:
|
1906
1889
|
raise
|
1907
1890
|
|
1891
|
+
def _check_cgi_auth(self) -> bool:
|
1892
|
+
"""
|
1893
|
+
Check basic auth in CGI mode using environment variables
|
1894
|
+
|
1895
|
+
Returns:
|
1896
|
+
True if auth is valid, False otherwise
|
1897
|
+
"""
|
1898
|
+
# Check for HTTP_AUTHORIZATION environment variable
|
1899
|
+
auth_header = os.getenv('HTTP_AUTHORIZATION')
|
1900
|
+
if not auth_header:
|
1901
|
+
# Also check for REMOTE_USER (if web server handled auth)
|
1902
|
+
remote_user = os.getenv('REMOTE_USER')
|
1903
|
+
if remote_user:
|
1904
|
+
# If web server handled auth, trust it
|
1905
|
+
return True
|
1906
|
+
return False
|
1907
|
+
|
1908
|
+
if not auth_header.startswith('Basic '):
|
1909
|
+
return False
|
1910
|
+
|
1911
|
+
try:
|
1912
|
+
# Decode the base64 credentials
|
1913
|
+
credentials = base64.b64decode(auth_header[6:]).decode("utf-8")
|
1914
|
+
username, password = credentials.split(":", 1)
|
1915
|
+
return self.validate_basic_auth(username, password)
|
1916
|
+
except Exception:
|
1917
|
+
return False
|
1918
|
+
|
1919
|
+
def _send_cgi_auth_challenge(self) -> str:
|
1920
|
+
"""
|
1921
|
+
Send authentication challenge in CGI mode
|
1922
|
+
|
1923
|
+
Returns:
|
1924
|
+
HTTP response with 401 status and WWW-Authenticate header
|
1925
|
+
"""
|
1926
|
+
# In CGI, we need to output the complete HTTP response
|
1927
|
+
response = "Status: 401 Unauthorized\r\n"
|
1928
|
+
response += "WWW-Authenticate: Basic realm=\"SignalWire Agent\"\r\n"
|
1929
|
+
response += "Content-Type: application/json\r\n"
|
1930
|
+
response += "\r\n"
|
1931
|
+
response += json.dumps({"error": "Unauthorized"})
|
1932
|
+
return response
|
1933
|
+
|
1934
|
+
def _check_lambda_auth(self, event) -> bool:
|
1935
|
+
"""
|
1936
|
+
Check basic auth in Lambda mode using event headers
|
1937
|
+
|
1938
|
+
Args:
|
1939
|
+
event: Lambda event object containing headers
|
1940
|
+
|
1941
|
+
Returns:
|
1942
|
+
True if auth is valid, False otherwise
|
1943
|
+
"""
|
1944
|
+
if not event or 'headers' not in event:
|
1945
|
+
return False
|
1946
|
+
|
1947
|
+
headers = event['headers']
|
1948
|
+
|
1949
|
+
# Check for authorization header (case-insensitive)
|
1950
|
+
auth_header = None
|
1951
|
+
for key, value in headers.items():
|
1952
|
+
if key.lower() == 'authorization':
|
1953
|
+
auth_header = value
|
1954
|
+
break
|
1955
|
+
|
1956
|
+
if not auth_header or not auth_header.startswith('Basic '):
|
1957
|
+
return False
|
1958
|
+
|
1959
|
+
try:
|
1960
|
+
# Decode the base64 credentials
|
1961
|
+
credentials = base64.b64decode(auth_header[6:]).decode("utf-8")
|
1962
|
+
username, password = credentials.split(":", 1)
|
1963
|
+
return self.validate_basic_auth(username, password)
|
1964
|
+
except Exception:
|
1965
|
+
return False
|
1966
|
+
|
1967
|
+
def _send_lambda_auth_challenge(self) -> dict:
|
1968
|
+
"""
|
1969
|
+
Send authentication challenge in Lambda mode
|
1970
|
+
|
1971
|
+
Returns:
|
1972
|
+
Lambda response with 401 status and WWW-Authenticate header
|
1973
|
+
"""
|
1974
|
+
return {
|
1975
|
+
"statusCode": 401,
|
1976
|
+
"headers": {
|
1977
|
+
"WWW-Authenticate": "Basic realm=\"SignalWire Agent\"",
|
1978
|
+
"Content-Type": "application/json"
|
1979
|
+
},
|
1980
|
+
"body": json.dumps({"error": "Unauthorized"})
|
1981
|
+
}
|
1982
|
+
|
1983
|
+
def _check_cloud_function_auth(self, request) -> bool:
|
1984
|
+
"""
|
1985
|
+
Check basic auth in Cloud Function mode
|
1986
|
+
|
1987
|
+
Args:
|
1988
|
+
request: Cloud Function request object
|
1989
|
+
|
1990
|
+
Returns:
|
1991
|
+
True if auth is valid, False otherwise
|
1992
|
+
"""
|
1993
|
+
# This would need to be implemented based on the specific
|
1994
|
+
# cloud function framework being used (Flask, etc.)
|
1995
|
+
# For now, return True to maintain existing behavior
|
1996
|
+
return True
|
1997
|
+
|
1998
|
+
def _send_cloud_function_auth_challenge(self):
|
1999
|
+
"""
|
2000
|
+
Send authentication challenge in Cloud Function mode
|
2001
|
+
|
2002
|
+
Returns:
|
2003
|
+
Cloud Function response with 401 status
|
2004
|
+
"""
|
2005
|
+
# This would need to be implemented based on the specific
|
2006
|
+
# cloud function framework being used
|
2007
|
+
return {"error": "Unauthorized", "status": 401}
|
2008
|
+
|
1908
2009
|
def handle_serverless_request(self, event=None, context=None, mode=None):
|
1909
2010
|
"""
|
1910
2011
|
Handle serverless environment requests (CGI, Lambda, Cloud Functions)
|
@@ -1922,6 +2023,10 @@ class AgentBase(SWMLService):
|
|
1922
2023
|
|
1923
2024
|
try:
|
1924
2025
|
if mode == 'cgi':
|
2026
|
+
# Check authentication in CGI mode
|
2027
|
+
if not self._check_cgi_auth():
|
2028
|
+
return self._send_cgi_auth_challenge()
|
2029
|
+
|
1925
2030
|
path_info = os.getenv('PATH_INFO', '').strip('/')
|
1926
2031
|
if not path_info:
|
1927
2032
|
return self._render_swml()
|
@@ -1957,6 +2062,10 @@ class AgentBase(SWMLService):
|
|
1957
2062
|
return self._execute_swaig_function(path_info, args, call_id, raw_data)
|
1958
2063
|
|
1959
2064
|
elif mode == 'lambda':
|
2065
|
+
# Check authentication in Lambda mode
|
2066
|
+
if not self._check_lambda_auth(event):
|
2067
|
+
return self._send_lambda_auth_challenge()
|
2068
|
+
|
1960
2069
|
if event:
|
1961
2070
|
path = event.get('pathParameters', {}).get('proxy', '') if event.get('pathParameters') else ''
|
1962
2071
|
if not path:
|
@@ -2011,7 +2120,26 @@ class AgentBase(SWMLService):
|
|
2011
2120
|
"body": swml_response
|
2012
2121
|
}
|
2013
2122
|
|
2014
|
-
elif mode
|
2123
|
+
elif mode == 'google_cloud_function':
|
2124
|
+
# Check authentication in Google Cloud Functions mode
|
2125
|
+
if not self._check_google_cloud_function_auth(event):
|
2126
|
+
return self._send_google_cloud_function_auth_challenge()
|
2127
|
+
|
2128
|
+
return self._handle_google_cloud_function_request(event)
|
2129
|
+
|
2130
|
+
elif mode == 'azure_function':
|
2131
|
+
# Check authentication in Azure Functions mode
|
2132
|
+
if not self._check_azure_function_auth(event):
|
2133
|
+
return self._send_azure_function_auth_challenge()
|
2134
|
+
|
2135
|
+
return self._handle_azure_function_request(event)
|
2136
|
+
|
2137
|
+
elif mode in ['cloud_function']:
|
2138
|
+
# Legacy cloud function mode - deprecated
|
2139
|
+
# Check authentication in Cloud Function mode
|
2140
|
+
if not self._check_cloud_function_auth(event):
|
2141
|
+
return self._send_cloud_function_auth_challenge()
|
2142
|
+
|
2015
2143
|
return self._handle_cloud_function_request(event)
|
2016
2144
|
|
2017
2145
|
except Exception as e:
|
@@ -2053,8 +2181,6 @@ class AgentBase(SWMLService):
|
|
2053
2181
|
Returns:
|
2054
2182
|
Function execution result
|
2055
2183
|
"""
|
2056
|
-
import structlog
|
2057
|
-
|
2058
2184
|
# Use the existing logger
|
2059
2185
|
req_log = self.log.bind(
|
2060
2186
|
endpoint="serverless_swaig",
|
@@ -3439,3 +3565,245 @@ class AgentBase(SWMLService):
|
|
3439
3565
|
def has_skill(self, skill_name: str) -> bool:
|
3440
3566
|
"""Check if skill is loaded"""
|
3441
3567
|
return self.skill_manager.has_skill(skill_name)
|
3568
|
+
|
3569
|
+
def _check_google_cloud_function_auth(self, request) -> bool:
|
3570
|
+
"""
|
3571
|
+
Check basic auth in Google Cloud Functions mode using request headers
|
3572
|
+
|
3573
|
+
Args:
|
3574
|
+
request: Flask request object or similar containing headers
|
3575
|
+
|
3576
|
+
Returns:
|
3577
|
+
True if auth is valid, False otherwise
|
3578
|
+
"""
|
3579
|
+
if not hasattr(request, 'headers'):
|
3580
|
+
return False
|
3581
|
+
|
3582
|
+
# Check for authorization header (case-insensitive)
|
3583
|
+
auth_header = None
|
3584
|
+
for key in request.headers:
|
3585
|
+
if key.lower() == 'authorization':
|
3586
|
+
auth_header = request.headers[key]
|
3587
|
+
break
|
3588
|
+
|
3589
|
+
if not auth_header or not auth_header.startswith('Basic '):
|
3590
|
+
return False
|
3591
|
+
|
3592
|
+
try:
|
3593
|
+
import base64
|
3594
|
+
encoded_credentials = auth_header[6:] # Remove 'Basic '
|
3595
|
+
decoded_credentials = base64.b64decode(encoded_credentials).decode('utf-8')
|
3596
|
+
provided_username, provided_password = decoded_credentials.split(':', 1)
|
3597
|
+
|
3598
|
+
expected_username, expected_password = self.get_basic_auth_credentials()
|
3599
|
+
return (provided_username == expected_username and
|
3600
|
+
provided_password == expected_password)
|
3601
|
+
except Exception:
|
3602
|
+
return False
|
3603
|
+
|
3604
|
+
def _check_azure_function_auth(self, req) -> bool:
|
3605
|
+
"""
|
3606
|
+
Check basic auth in Azure Functions mode using request object
|
3607
|
+
|
3608
|
+
Args:
|
3609
|
+
req: Azure Functions request object containing headers
|
3610
|
+
|
3611
|
+
Returns:
|
3612
|
+
True if auth is valid, False otherwise
|
3613
|
+
"""
|
3614
|
+
if not hasattr(req, 'headers'):
|
3615
|
+
return False
|
3616
|
+
|
3617
|
+
# Check for authorization header (case-insensitive)
|
3618
|
+
auth_header = None
|
3619
|
+
for key, value in req.headers.items():
|
3620
|
+
if key.lower() == 'authorization':
|
3621
|
+
auth_header = value
|
3622
|
+
break
|
3623
|
+
|
3624
|
+
if not auth_header or not auth_header.startswith('Basic '):
|
3625
|
+
return False
|
3626
|
+
|
3627
|
+
try:
|
3628
|
+
import base64
|
3629
|
+
encoded_credentials = auth_header[6:] # Remove 'Basic '
|
3630
|
+
decoded_credentials = base64.b64decode(encoded_credentials).decode('utf-8')
|
3631
|
+
provided_username, provided_password = decoded_credentials.split(':', 1)
|
3632
|
+
|
3633
|
+
expected_username, expected_password = self.get_basic_auth_credentials()
|
3634
|
+
return (provided_username == expected_username and
|
3635
|
+
provided_password == expected_password)
|
3636
|
+
except Exception:
|
3637
|
+
return False
|
3638
|
+
|
3639
|
+
def _send_google_cloud_function_auth_challenge(self):
|
3640
|
+
"""
|
3641
|
+
Send authentication challenge in Google Cloud Functions mode
|
3642
|
+
|
3643
|
+
Returns:
|
3644
|
+
Flask-compatible response with 401 status and WWW-Authenticate header
|
3645
|
+
"""
|
3646
|
+
from flask import Response
|
3647
|
+
return Response(
|
3648
|
+
response=json.dumps({"error": "Unauthorized"}),
|
3649
|
+
status=401,
|
3650
|
+
headers={
|
3651
|
+
"WWW-Authenticate": "Basic realm=\"SignalWire Agent\"",
|
3652
|
+
"Content-Type": "application/json"
|
3653
|
+
}
|
3654
|
+
)
|
3655
|
+
|
3656
|
+
def _send_azure_function_auth_challenge(self):
|
3657
|
+
"""
|
3658
|
+
Send authentication challenge in Azure Functions mode
|
3659
|
+
|
3660
|
+
Returns:
|
3661
|
+
Azure Functions response with 401 status and WWW-Authenticate header
|
3662
|
+
"""
|
3663
|
+
import azure.functions as func
|
3664
|
+
return func.HttpResponse(
|
3665
|
+
body=json.dumps({"error": "Unauthorized"}),
|
3666
|
+
status_code=401,
|
3667
|
+
headers={
|
3668
|
+
"WWW-Authenticate": "Basic realm=\"SignalWire Agent\"",
|
3669
|
+
"Content-Type": "application/json"
|
3670
|
+
}
|
3671
|
+
)
|
3672
|
+
|
3673
|
+
def _handle_google_cloud_function_request(self, request):
|
3674
|
+
"""
|
3675
|
+
Handle Google Cloud Functions specific requests
|
3676
|
+
|
3677
|
+
Args:
|
3678
|
+
request: Flask request object from Google Cloud Functions
|
3679
|
+
|
3680
|
+
Returns:
|
3681
|
+
Flask response object
|
3682
|
+
"""
|
3683
|
+
try:
|
3684
|
+
# Get the path from the request
|
3685
|
+
path = request.path.strip('/')
|
3686
|
+
|
3687
|
+
if not path:
|
3688
|
+
# Root request - return SWML
|
3689
|
+
swml_response = self._render_swml()
|
3690
|
+
from flask import Response
|
3691
|
+
return Response(
|
3692
|
+
response=swml_response,
|
3693
|
+
status=200,
|
3694
|
+
headers={"Content-Type": "application/json"}
|
3695
|
+
)
|
3696
|
+
else:
|
3697
|
+
# SWAIG function call
|
3698
|
+
args = {}
|
3699
|
+
call_id = None
|
3700
|
+
raw_data = None
|
3701
|
+
|
3702
|
+
# Parse request data
|
3703
|
+
if request.method == 'POST':
|
3704
|
+
try:
|
3705
|
+
if request.is_json:
|
3706
|
+
raw_data = request.get_json()
|
3707
|
+
else:
|
3708
|
+
raw_data = json.loads(request.get_data(as_text=True))
|
3709
|
+
|
3710
|
+
call_id = raw_data.get("call_id")
|
3711
|
+
|
3712
|
+
# Extract arguments like the FastAPI handler does
|
3713
|
+
if "argument" in raw_data and isinstance(raw_data["argument"], dict):
|
3714
|
+
if "parsed" in raw_data["argument"] and isinstance(raw_data["argument"]["parsed"], list) and raw_data["argument"]["parsed"]:
|
3715
|
+
args = raw_data["argument"]["parsed"][0]
|
3716
|
+
elif "raw" in raw_data["argument"]:
|
3717
|
+
try:
|
3718
|
+
args = json.loads(raw_data["argument"]["raw"])
|
3719
|
+
except Exception:
|
3720
|
+
pass
|
3721
|
+
except Exception:
|
3722
|
+
# If parsing fails, continue with empty args
|
3723
|
+
pass
|
3724
|
+
|
3725
|
+
result = self._execute_swaig_function(path, args, call_id, raw_data)
|
3726
|
+
from flask import Response
|
3727
|
+
return Response(
|
3728
|
+
response=json.dumps(result) if isinstance(result, dict) else str(result),
|
3729
|
+
status=200,
|
3730
|
+
headers={"Content-Type": "application/json"}
|
3731
|
+
)
|
3732
|
+
|
3733
|
+
except Exception as e:
|
3734
|
+
import logging
|
3735
|
+
logging.error(f"Error in Google Cloud Function request handler: {e}")
|
3736
|
+
from flask import Response
|
3737
|
+
return Response(
|
3738
|
+
response=json.dumps({"error": str(e)}),
|
3739
|
+
status=500,
|
3740
|
+
headers={"Content-Type": "application/json"}
|
3741
|
+
)
|
3742
|
+
|
3743
|
+
def _handle_azure_function_request(self, req):
|
3744
|
+
"""
|
3745
|
+
Handle Azure Functions specific requests
|
3746
|
+
|
3747
|
+
Args:
|
3748
|
+
req: Azure Functions HttpRequest object
|
3749
|
+
|
3750
|
+
Returns:
|
3751
|
+
Azure Functions HttpResponse object
|
3752
|
+
"""
|
3753
|
+
try:
|
3754
|
+
import azure.functions as func
|
3755
|
+
|
3756
|
+
# Get the path from the request
|
3757
|
+
path = req.url.split('/')[-1] if req.url else ''
|
3758
|
+
|
3759
|
+
if not path or path == 'api':
|
3760
|
+
# Root request - return SWML
|
3761
|
+
swml_response = self._render_swml()
|
3762
|
+
return func.HttpResponse(
|
3763
|
+
body=swml_response,
|
3764
|
+
status_code=200,
|
3765
|
+
headers={"Content-Type": "application/json"}
|
3766
|
+
)
|
3767
|
+
else:
|
3768
|
+
# SWAIG function call
|
3769
|
+
args = {}
|
3770
|
+
call_id = None
|
3771
|
+
raw_data = None
|
3772
|
+
|
3773
|
+
# Parse request data
|
3774
|
+
if req.method == 'POST':
|
3775
|
+
try:
|
3776
|
+
body = req.get_body()
|
3777
|
+
if body:
|
3778
|
+
raw_data = json.loads(body.decode('utf-8'))
|
3779
|
+
call_id = raw_data.get("call_id")
|
3780
|
+
|
3781
|
+
# Extract arguments like the FastAPI handler does
|
3782
|
+
if "argument" in raw_data and isinstance(raw_data["argument"], dict):
|
3783
|
+
if "parsed" in raw_data["argument"] and isinstance(raw_data["argument"]["parsed"], list) and raw_data["argument"]["parsed"]:
|
3784
|
+
args = raw_data["argument"]["parsed"][0]
|
3785
|
+
elif "raw" in raw_data["argument"]:
|
3786
|
+
try:
|
3787
|
+
args = json.loads(raw_data["argument"]["raw"])
|
3788
|
+
except Exception:
|
3789
|
+
pass
|
3790
|
+
except Exception:
|
3791
|
+
# If parsing fails, continue with empty args
|
3792
|
+
pass
|
3793
|
+
|
3794
|
+
result = self._execute_swaig_function(path, args, call_id, raw_data)
|
3795
|
+
return func.HttpResponse(
|
3796
|
+
body=json.dumps(result) if isinstance(result, dict) else str(result),
|
3797
|
+
status_code=200,
|
3798
|
+
headers={"Content-Type": "application/json"}
|
3799
|
+
)
|
3800
|
+
|
3801
|
+
except Exception as e:
|
3802
|
+
import logging
|
3803
|
+
logging.error(f"Error in Azure Function request handler: {e}")
|
3804
|
+
import azure.functions as func
|
3805
|
+
return func.HttpResponse(
|
3806
|
+
body=json.dumps({"error": str(e)}),
|
3807
|
+
status_code=500,
|
3808
|
+
headers={"Content-Type": "application/json"}
|
3809
|
+
)
|