Skip to content

Commit c71e0ea

Browse files
Dhivya-BharathyMervinPraison
authored andcommitted
feat(approval): add env fallbacks, SSL toggles, crash fix, and examples
- Discord: DISCORD_CHANNEL_ID env fallback + PRAISONAI_DISCORD_SSL_VERIFY toggle - Telegram: TELEGRAM_CHAT_ID env fallback + PRAISONAI_TELEGRAM_SSL_VERIFY toggle - HTTP: print approval URL to console for better UX - Webhook: fix str(status).lower() crash on non-JSON responses - Add examples/approval/ with Slack, Telegram, Discord, HTTP, Agent examples Co-authored-by: Dhivya-Bharathy <divysarah2261999@gmail.com>
1 parent bbd320c commit c71e0ea

File tree

10 files changed

+180
-8
lines changed

10 files changed

+180
-8
lines changed

examples/approval/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Approval Examples
2+
3+
Minimal examples showing how to route tool-execution approvals through
4+
different backends.
5+
6+
## Quick Start
7+
8+
```bash
9+
pip install praisonaiagents praisonai[bot]
10+
export OPENAI_API_KEY=sk-...
11+
```
12+
13+
| Backend | Script | Extra env vars |
14+
|----------|-----------------------|--------------------------------------------------|
15+
| Slack | `slack_approval.py` | `SLACK_BOT_TOKEN`, `SLACK_CHANNEL` |
16+
| Telegram | `telegram_approval.py`| `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID` |
17+
| Discord | `discord_approval.py` | `DISCORD_BOT_TOKEN`, `DISCORD_CHANNEL_ID` |
18+
| HTTP | `http_approval.py` | *(none — opens a local web dashboard)* |
19+
| Agent | `agent_approval.py` | *(none — AI reviewer agent decides)* |
20+
21+
## SSL Verification
22+
23+
For corporate proxy / CA issues, Discord and Telegram backends support
24+
optional SSL bypass:
25+
26+
```bash
27+
export PRAISONAI_DISCORD_SSL_VERIFY=false
28+
export PRAISONAI_TELEGRAM_SSL_VERIFY=false
29+
```
30+
31+
Default is `true` (secure).
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
Agent Approval Example
3+
======================
4+
Uses an AI agent as an automated reviewer to approve/deny tool calls.
5+
6+
Requires:
7+
pip install praisonaiagents
8+
export OPENAI_API_KEY=sk-...
9+
"""
10+
11+
from praisonaiagents import Agent
12+
from praisonaiagents.approval import AgentApproval
13+
from praisonaiagents.tools.shell_tools import execute_command
14+
15+
reviewer = AgentApproval(
16+
instructions="Only approve read-only commands like 'ls' or 'cat'. Deny destructive commands.",
17+
)
18+
19+
agent = Agent(
20+
name="DevOps",
21+
instructions="You are a DevOps assistant. Use shell tools when asked.",
22+
tools=[execute_command],
23+
approval=reviewer,
24+
)
25+
26+
agent.start("List files in the current directory")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
Discord Approval Example
3+
========================
4+
Routes tool approvals to a Discord channel.
5+
6+
Requires:
7+
pip install praisonaiagents praisonai[bot]
8+
export DISCORD_BOT_TOKEN=MTIz...
9+
export DISCORD_CHANNEL_ID=1234567890
10+
export OPENAI_API_KEY=sk-...
11+
"""
12+
13+
from praisonaiagents import Agent
14+
from praisonaiagents.tools.shell_tools import execute_command
15+
from praisonai.bots import DiscordApproval
16+
17+
agent = Agent(
18+
name="DevOps",
19+
instructions="You are a DevOps assistant. Use shell tools when asked.",
20+
tools=[execute_command],
21+
approval=DiscordApproval(),
22+
)
23+
24+
agent.start("List files in the current directory")

examples/approval/http_approval.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
HTTP Approval Example
3+
=====================
4+
Opens a local web dashboard for tool approvals.
5+
6+
Requires:
7+
pip install praisonaiagents praisonai[bot]
8+
export OPENAI_API_KEY=sk-...
9+
"""
10+
11+
from praisonaiagents import Agent
12+
from praisonaiagents.tools.shell_tools import execute_command
13+
from praisonai.bots import HTTPApproval
14+
15+
agent = Agent(
16+
name="DevOps",
17+
instructions="You are a DevOps assistant. Use shell tools when asked.",
18+
tools=[execute_command],
19+
approval=HTTPApproval(),
20+
)
21+
22+
agent.start("List files in the current directory")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
Slack Approval Example
3+
======================
4+
Routes tool approvals to a Slack channel.
5+
6+
Requires:
7+
pip install praisonaiagents praisonai[bot]
8+
export SLACK_BOT_TOKEN=xoxb-...
9+
export SLACK_CHANNEL=C0123456789
10+
export OPENAI_API_KEY=sk-...
11+
"""
12+
13+
from praisonaiagents import Agent
14+
from praisonaiagents.tools.shell_tools import execute_command
15+
from praisonai.bots import SlackApproval
16+
17+
agent = Agent(
18+
name="DevOps",
19+
instructions="You are a DevOps assistant. Use shell tools when asked.",
20+
tools=[execute_command],
21+
approval=SlackApproval(),
22+
)
23+
24+
agent.start("List files in the current directory")
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
Telegram Approval Example
3+
=========================
4+
Routes tool approvals to a Telegram chat.
5+
6+
Requires:
7+
pip install praisonaiagents praisonai[bot]
8+
export TELEGRAM_BOT_TOKEN=123456:ABC-...
9+
export TELEGRAM_CHAT_ID=987654321
10+
export OPENAI_API_KEY=sk-...
11+
"""
12+
13+
from praisonaiagents import Agent
14+
from praisonaiagents.tools.shell_tools import execute_command
15+
from praisonai.bots import TelegramApproval
16+
17+
agent = Agent(
18+
name="DevOps",
19+
instructions="You are a DevOps assistant. Use shell tools when asked.",
20+
tools=[execute_command],
21+
approval=TelegramApproval(),
22+
)
23+
24+
agent.start("List files in the current directory")

src/praisonai/praisonai/bots/_discord_approval.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,13 @@ def __init__(
6666
raise ValueError(
6767
"Discord bot token is required. Pass token= or set DISCORD_BOT_TOKEN env var."
6868
)
69-
self._channel_id = channel_id or ""
69+
self._channel_id = channel_id or os.environ.get("DISCORD_CHANNEL_ID", "")
7070
self._timeout = timeout
7171
self._poll_interval = poll_interval
72+
# Optional: set PRAISONAI_DISCORD_SSL_VERIFY=false to skip SSL verify
73+
# (e.g. corporate proxy / CA issues)
74+
_v = os.environ.get("PRAISONAI_DISCORD_SSL_VERIFY", "true").lower()
75+
self._ssl_verify = _v not in ("false", "0", "no")
7276

7377
def __repr__(self) -> str:
7478
masked = f"...{self._token[-4:]}" if len(self._token) > 4 else "***"
@@ -109,7 +113,9 @@ async def _do_request(s):
109113
if session is not None:
110114
return await _do_request(session)
111115
else:
112-
async with aiohttp.ClientSession() as _session:
116+
async with aiohttp.ClientSession(
117+
connector=aiohttp.TCPConnector(ssl=self._ssl_verify),
118+
) as _session:
113119
return await _do_request(_session)
114120

115121
# ── ApprovalProtocol implementation ─────────────────────────────────
@@ -126,7 +132,9 @@ async def request_approval(self, request) -> Any:
126132
reason="No Discord channel_id configured",
127133
)
128134

129-
async with aiohttp.ClientSession() as session:
135+
async with aiohttp.ClientSession(
136+
connector=aiohttp.TCPConnector(ssl=self._ssl_verify),
137+
) as session:
130138
# 1. Post approval embed
131139
embed = self._build_embed(request)
132140
content = self._build_fallback_text(request)
@@ -349,7 +357,9 @@ async def _update_message(
349357
) as resp:
350358
data = await resp.json()
351359
else:
352-
async with aiohttp.ClientSession() as _session:
360+
async with aiohttp.ClientSession(
361+
connector=aiohttp.TCPConnector(ssl=self._ssl_verify),
362+
) as _session:
353363
async with _session.patch(
354364
url, headers=headers, json=payload,
355365
timeout=aiohttp.ClientTimeout(total=10),

src/praisonai/praisonai/bots/_http_approval.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ async def request_approval(self, request) -> Any:
206206

207207
url = f"http://{self._host}:{self._port}/approve/{request_id}"
208208
logger.info(f"HTTPApproval: Waiting for decision at {url}")
209+
print(f"\n🔗 Open this URL to approve/deny:\n {url}\n")
209210

210211
# Poll for decision
211212
deadline = time.monotonic() + self._timeout

src/praisonai/praisonai/bots/_telegram_approval.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,13 @@ def __init__(
6464
raise ValueError(
6565
"Telegram bot token is required. Pass token= or set TELEGRAM_BOT_TOKEN env var."
6666
)
67-
self._chat_id = chat_id or ""
67+
self._chat_id = chat_id or os.environ.get("TELEGRAM_CHAT_ID", "")
6868
self._timeout = timeout
6969
self._poll_interval = poll_interval
70+
# Optional: set PRAISONAI_TELEGRAM_SSL_VERIFY=false to skip SSL verify
71+
# (e.g. corporate proxy / CA issues)
72+
_v = os.environ.get("PRAISONAI_TELEGRAM_SSL_VERIFY", "true").lower()
73+
self._ssl_verify = _v not in ("false", "0", "no")
7074

7175
def __repr__(self) -> str:
7276
masked = f"...{self._token[-4:]}" if len(self._token) > 4 else "***"
@@ -91,7 +95,9 @@ async def _telegram_api(
9195
) as resp:
9296
return await resp.json()
9397
else:
94-
async with aiohttp.ClientSession() as _session:
98+
async with aiohttp.ClientSession(
99+
connector=aiohttp.TCPConnector(ssl=self._ssl_verify),
100+
) as _session:
95101
async with _session.post(
96102
url, json=payload,
97103
timeout=aiohttp.ClientTimeout(total=15),
@@ -112,7 +118,9 @@ async def request_approval(self, request) -> Any:
112118
reason="No Telegram chat_id configured",
113119
)
114120

115-
async with aiohttp.ClientSession() as session:
121+
async with aiohttp.ClientSession(
122+
connector=aiohttp.TCPConnector(ssl=self._ssl_verify),
123+
) as session:
116124
# 1. Send approval message with inline keyboard
117125
text = self._build_message_text(request)
118126
keyboard = self._build_inline_keyboard(request)

src/praisonai/praisonai/bots/_webhook_approval.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ async def _poll_for_decision(
215215
continue
216216

217217
# Check if decision is available
218-
status = data.get("status", "").lower()
218+
# (status may be int from HTTP status when response isn't JSON)
219+
status = data.get("status", "")
220+
status = str(status).lower() if status is not None else ""
219221
if status == "pending":
220222
continue
221223

0 commit comments

Comments
 (0)