-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreact_tool.py
More file actions
112 lines (93 loc) · 4.08 KB
/
Copy pathreact_tool.py
File metadata and controls
112 lines (93 loc) · 4.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"""Sendblue tapback reaction tool.
Exposes a single-purpose ``sendblue_react`` tool that lets the agent
send iMessage tapback reactions (love, like, dislike, laugh, emphasize,
question) to the most recent inbound message in the current Sendblue
chat. The adapter holds an in-memory per-chat last-inbound handle cache;
this tool resolves it implicitly so the model doesn't need to track
opaque message_handles.
Registered by ``register(ctx)`` via ``ctx.register_tool`` under toolset
``"sendblue"``, which the auto-generated ``hermes-sendblue`` composite
picks up; the check_fn gates it to active Sendblue sessions.
"""
from __future__ import annotations
import json
import os
from gateway.session_context import get_session_env
try: # hermes-agent >= the release that added the opt-out
from tools.registry import no_cache_check_fn
except ImportError: # pragma: no cover - older host, cache behaviour unchanged
def no_cache_check_fn(fn):
return fn
REACTION_TYPES = ["love", "like", "dislike", "laugh", "emphasize", "question"]
SENDBLUE_REACT_SCHEMA = {
"name": "sendblue_react",
"description": (
"Send an iMessage tapback reaction (love/like/dislike/laugh/"
"emphasize/question) to the most recent inbound message in the "
"current Sendblue chat. Use sparingly — reactions are best for "
"acknowledgement, not as a primary reply."
),
"parameters": {
"type": "object",
"properties": {
"reaction": {
"type": "string",
"enum": REACTION_TYPES,
"description": "Which tapback to send.",
},
},
"required": ["reaction"],
},
}
@no_cache_check_fn
def check_sendblue_react_available() -> bool:
"""Whether the current turn is a Sendblue session.
Registered uncached: tools.registry TTL-caches check_fn results under (fn, profile scope),
but this answer depends on the SESSION platform, which changes turn to turn within one
profile. Left cached, a False from any non-Sendblue turn -- including the no-session sweep
the gateway runs at startup -- hides the tapback tool from a real Sendblue turn until the
TTL lapses. The probe is a contextvar read, so skipping the cache costs nothing.
"""
platform = get_session_env("HERMES_SESSION_PLATFORM", "") or os.getenv(
"HERMES_SESSION_PLATFORM", ""
)
return platform.strip().lower() == "sendblue"
def handle_sendblue_react(args: dict, **_kw) -> str:
# check_fn gates whether the tool is *listed*; the registry does not
# re-check it at dispatch, so guard here too — the handler reaches the
# live gateway runner and must refuse to fire outside a Sendblue session.
if not check_sendblue_react_available():
return json.dumps({"error": "sendblue_react is only available in a Sendblue session"})
reaction = str(args.get("reaction") or "").strip().lower()
if reaction not in REACTION_TYPES:
return json.dumps(
{"error": f"Invalid reaction. Use one of: {', '.join(REACTION_TYPES)}"}
)
chat_id = (
get_session_env("HERMES_SESSION_CHAT_ID", "")
or os.getenv("HERMES_SESSION_CHAT_ID", "")
).strip()
if not chat_id:
return json.dumps({"error": "No active Sendblue chat"})
try:
from gateway.run import _gateway_runner_ref
runner = _gateway_runner_ref()
except Exception:
runner = None
if runner is None:
return json.dumps({"error": "Gateway runner not available"})
adapter = None
try:
# runner.adapters is keyed by the Platform enum, not the bare string
# (gateway/run.py: Dict[Platform, BasePlatformAdapter]).
from gateway.config import Platform
adapter = runner.adapters.get(Platform("sendblue"))
except Exception:
adapter = None
if adapter is None:
return json.dumps({"error": "Sendblue adapter not running"})
from model_tools import _run_async
ok = _run_async(adapter.send_reaction(chat_id, reaction))
return json.dumps(
{"success": bool(ok), "chat_id": chat_id, "reaction": reaction}
)