feat: wire Twilio SMS handler with agent + ack + response sending

The Twilio webhook now:
1. Sends "Message received! Working on it now..." via Twilio API
2. Routes to DeepResearchAgent (via bridge or direct fallback)
3. Sends the research response back as SMS
4. Handles missing bridge gracefully
5. Formats response for SMS (strips markdown, 1500 char limit)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jon Saad-Falcon
2026-03-28 16:27:32 -07:00
co-authored by Claude Opus 4.6
parent 6233ae1aa1
commit eea5f65ea0
+107 -8
View File
@@ -111,15 +111,114 @@ def create_webhook_router(
from_number = params.get("From", "")
body = params.get("Body", "")
# Dispatch to background — return TwiML immediately
task = asyncio.create_task(
asyncio.to_thread(
bridge.handle_incoming,
from_number,
body,
"twilio",
max_length=1600,
if not from_number or not body:
return Response(
content="<Response></Response>",
media_type="application/xml",
)
# Use bridge or app.state.channel_bridge
active_bridge = bridge or getattr(
request.app.state, "channel_bridge", None,
)
def _handle_twilio() -> None:
# Send ack via Twilio API
try:
from twilio.rest import Client
# Get creds from app state bindings
mgr = getattr(
request.app.state, "agent_manager", None,
)
twilio_client = None
twilio_from = ""
if mgr:
for agent in mgr.list_agents():
aid = agent.get("id", "")
for b in mgr.list_channel_bindings(aid):
if b.get("channel_type") == "twilio":
cfg = b.get("config", {})
sid = cfg.get("account_sid", "")
tok = cfg.get("auth_token", "")
twilio_from = cfg.get(
"phone_number", "",
)
if sid and tok:
twilio_client = Client(
sid, tok,
)
break
if twilio_client and twilio_from:
twilio_client.messages.create(
body=(
"Message received! "
"Working on it now..."
),
from_=twilio_from,
to=from_number,
)
except Exception as _e:
logger.warning("Twilio ack failed: %s", _e)
# Process via bridge or agent directly
response = ""
if active_bridge:
response = active_bridge.handle_incoming(
from_number, body, "twilio",
max_length=1600,
)
else:
# Direct agent fallback
try:
from openjarvis.agents.deep_research import (
DeepResearchAgent,
)
from openjarvis.server.agent_manager_routes import (
_build_deep_research_tools,
)
engine = getattr(
request.app.state, "engine", None,
)
if engine:
tools = _build_deep_research_tools(
engine=engine, model="",
)
agent = DeepResearchAgent(
engine=engine,
model=getattr(
engine, "_model", "",
),
tools=tools,
max_turns=5,
)
result = agent.run(body)
response = result.content or ""
except Exception as _exc:
response = f"Error: {_exc}"
# Send response via Twilio
if response and twilio_client and twilio_from:
try:
clean = _format_for_sms(response)
# Twilio SMS limit is 1600 chars
if len(clean) > 1500:
clean = clean[:1500] + "\n\n(truncated)"
twilio_client.messages.create(
body=clean,
from_=twilio_from,
to=from_number,
)
except Exception as _e:
logger.warning(
"Twilio reply failed: %s", _e,
)
task = asyncio.create_task(
asyncio.to_thread(_handle_twilio),
)
task.add_done_callback(_log_task_exception)