mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
130 lines
4.4 KiB
Python
130 lines
4.4 KiB
Python
"""Pre-call HTTP data fetch for StartCall node.
|
|
|
|
Executes an HTTP request before a voice call starts to enrich the
|
|
call context with data from external systems (CRM, ERP, etc.).
|
|
"""
|
|
|
|
from typing import Any, Dict, Optional
|
|
|
|
import httpx
|
|
from loguru import logger
|
|
|
|
from api.db import db_client
|
|
from api.utils.credential_auth import build_auth_header
|
|
|
|
PRE_CALL_FETCH_TIMEOUT_SECONDS = 10
|
|
|
|
|
|
def _extract_initial_context(response_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Pull the context variables out of a pre-call fetch response.
|
|
|
|
The canonical key is ``initial_context``. The legacy ``dynamic_variables``
|
|
key is still accepted for backward compatibility, so existing endpoints
|
|
keep working; ``initial_context`` takes precedence when both are present.
|
|
|
|
Either key may appear at the top level or nested under ``call_inbound``:
|
|
{"call_inbound": {"initial_context": {...}}} | {"initial_context": {...}}
|
|
{"call_inbound": {"dynamic_variables": {...}}} | {"dynamic_variables": {...}}
|
|
"""
|
|
container = response_data.get("call_inbound")
|
|
if not isinstance(container, dict):
|
|
container = response_data
|
|
|
|
for key in ("initial_context", "dynamic_variables"):
|
|
value = container.get(key)
|
|
if isinstance(value, dict):
|
|
return value
|
|
|
|
return {}
|
|
|
|
|
|
async def execute_pre_call_fetch(
|
|
*,
|
|
url: str,
|
|
credential_uuid: Optional[str],
|
|
call_context_vars: Dict[str, Any],
|
|
workflow_id: int,
|
|
organization_id: int,
|
|
) -> Dict[str, Any]:
|
|
"""Execute a POST request to fetch data before a call starts.
|
|
|
|
Sends a standardized payload with call metadata (agent_id, from/to numbers).
|
|
The response JSON is returned as a dict to be merged into initial_context.
|
|
|
|
Returns:
|
|
Response JSON dict on success, empty dict on any failure.
|
|
Never raises.
|
|
"""
|
|
# Build standardized payload
|
|
payload = {
|
|
"event": "call_inbound",
|
|
"call_inbound": {
|
|
"agent_id": workflow_id,
|
|
"from_number": call_context_vars.get("caller_number", ""),
|
|
"to_number": call_context_vars.get("called_number", ""),
|
|
},
|
|
}
|
|
|
|
# Build headers
|
|
headers: Dict[str, str] = {"Content-Type": "application/json"}
|
|
|
|
if credential_uuid:
|
|
try:
|
|
credential = await db_client.get_credential_by_uuid(
|
|
credential_uuid, organization_id
|
|
)
|
|
if credential:
|
|
headers.update(build_auth_header(credential))
|
|
else:
|
|
logger.warning(
|
|
f"Pre-call fetch: credential {credential_uuid} not found"
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Pre-call fetch: failed to resolve credential: {e}")
|
|
|
|
logger.info(f"Pre-call fetch: POST {url}")
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=PRE_CALL_FETCH_TIMEOUT_SECONDS) as client:
|
|
response = await client.post(url, headers=headers, json=payload)
|
|
|
|
try:
|
|
response_data = response.json()
|
|
except Exception:
|
|
response_data = {}
|
|
|
|
if response.is_success:
|
|
if not isinstance(response_data, dict):
|
|
logger.warning(
|
|
"Pre-call fetch: response is not a JSON object, skipping"
|
|
)
|
|
return {}
|
|
|
|
# Extract the variables to merge into initial_context. Prefers
|
|
# the canonical `initial_context` key, falling back to the
|
|
# legacy `dynamic_variables` key for backward compatibility.
|
|
initial_context_vars = _extract_initial_context(response_data)
|
|
|
|
logger.info(
|
|
f"Pre-call fetch: success ({response.status_code}), "
|
|
f"initial_context keys: {list(initial_context_vars.keys())}"
|
|
)
|
|
return initial_context_vars
|
|
else:
|
|
logger.warning(
|
|
f"Pre-call fetch: HTTP {response.status_code} - "
|
|
f"{response.text[:200]}"
|
|
)
|
|
return {}
|
|
|
|
except httpx.TimeoutException:
|
|
logger.error(
|
|
f"Pre-call fetch: timed out after {PRE_CALL_FETCH_TIMEOUT_SECONDS}s"
|
|
)
|
|
return {}
|
|
except httpx.RequestError as e:
|
|
logger.error(f"Pre-call fetch: request failed: {e}")
|
|
return {}
|
|
except Exception as e:
|
|
logger.error(f"Pre-call fetch: unexpected error: {e}")
|
|
return {}
|