Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

133 lines
4.6 KiB
Python

"""Twilio SMS notification tool.
Lets the agent push a short SMS notification through a configured
Twilio integration. The investigation planner exposes this tool
whenever a Twilio integration with the SMS channel enabled exists.
"""
from __future__ import annotations
from typing import Any
from core.tool_framework.base import BaseTool
from integrations.twilio.delivery import send_twilio_sms_report
class TwilioNotifyTool(BaseTool):
"""Send a short SMS notification via the configured Twilio integration."""
name = "twilio_notify"
source = "twilio"
description = (
"Send a short SMS notification via the configured Twilio integration. "
"Only available when a Twilio integration with the SMS channel enabled "
"exists."
)
use_cases = [
"Paging an on-call recipient with a one-line incident summary via SMS",
"Sending a follow-up SMS when a critical-severity alert escalates",
]
requires = ["twilio"]
input_schema = {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": (
"Recipient phone number in E.164 (e.g. +14155551234). "
"Defaults to the channel default_to when omitted."
),
},
"body": {
"type": "string",
"description": "SMS body (truncated to the SMS limit).",
},
},
"required": ["body"],
}
outputs = {
"sid": "Twilio Message SID for the sent SMS",
"status": "delivery dispatch status — 'sent' or 'failed'",
"error": "error detail when status is 'failed'",
}
def is_available(self, sources: dict[str, Any]) -> bool:
twilio = sources.get("twilio") or {}
if not (twilio.get("account_sid") and twilio.get("auth_token")):
return False
sms = twilio.get("sms") or {}
return bool(
sms.get("enabled") and (sms.get("from_number") or sms.get("messaging_service_sid"))
)
# NOTE: extract_params is intentionally NOT overridden. Anything it returns
# is merged into the kwargs passed to run() and recorded in tool-call
# execution traces/logs. Twilio credentials must never travel that path, so
# run() resolves them itself from the integration store instead.
def run(
self,
body: str,
to: str = "",
**_kwargs: Any,
) -> dict[str, Any]:
# Resolve Twilio credentials here rather than receiving them as kwargs:
# run() kwargs are recorded in tool-call traces/logs, so account_sid /
# auth_token must stay out of the call signature entirely.
from integrations.catalog import resolve_effective_integrations
entry = resolve_effective_integrations().get("twilio") or {}
twilio = entry.get("config") or {}
sms = twilio.get("sms") or {}
account_sid = str(twilio.get("account_sid") or "")
auth_token = str(twilio.get("auth_token") or "")
from_number = str(sms.get("from_number") or "")
messaging_service_sid = str(sms.get("messaging_service_sid") or "")
to = to or str(sms.get("default_to") or "")
if not account_sid or not auth_token:
return {
"source": "twilio",
"available": False,
"status": "failed",
"error": "Twilio integration is not configured.",
"sid": "",
}
if not (from_number or messaging_service_sid):
return {
"source": "twilio",
"available": True,
"status": "failed",
"error": "Twilio SMS channel has no from_number or messaging_service_sid.",
"sid": "",
}
if not to:
return {
"source": "twilio",
"available": True,
"status": "failed",
"error": "No recipient — pass 'to' or configure sms.default_to.",
"sid": "",
}
ok, error, sid = send_twilio_sms_report(
body,
{
"account_sid": account_sid,
"auth_token": auth_token,
"from_number": from_number,
"messaging_service_sid": messaging_service_sid,
"to": to,
},
)
return {
"source": "twilio",
"available": True,
"status": "sent" if ok else "failed",
"error": "" if ok else error,
"sid": sid,
}
twilio_notify = TwilioNotifyTool()