@inline-chat/hermes-agent-adapter 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -7
- package/package.json +8 -7
- package/plugin/inline/README.md +23 -0
- package/plugin/inline/adapter.py +478 -100
- package/plugin/inline/cli.py +24 -5
- package/plugin/inline/message_actions.py +78 -0
- package/plugin/inline/plugin.yaml +5 -1
- package/plugin/inline/sidecar/index.mjs +7283 -972
- package/plugin/inline/tools.py +82 -11
- package/plugin/inline/widgets.py +267 -0
package/plugin/inline/cli.py
CHANGED
|
@@ -30,7 +30,7 @@ _MACHINE_SETUP_PROTOCOL_VERSION = 1
|
|
|
30
30
|
# Hermes' own tolerance: macOS readings of one process's start time drift about a second
|
|
31
31
|
# after boot-clock adjustments, and 200 is 2 s on every platform's scale.
|
|
32
32
|
_START_TIME_DRIFT_TOLERANCE = 200
|
|
33
|
-
_PROBE_USER_AGENT = "inline-hermes-agent-adapter/1.
|
|
33
|
+
_PROBE_USER_AGENT = "inline-hermes-agent-adapter/1.1.0"
|
|
34
34
|
_ENV_REFERENCE_RE = re.compile(r"^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$")
|
|
35
35
|
|
|
36
36
|
|
|
@@ -43,6 +43,24 @@ def _open_bot_api_probe(request: urllib.request.Request):
|
|
|
43
43
|
return urllib.request.build_opener(_RejectRedirects()).open(request, timeout=30)
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def _setup_ui():
|
|
47
|
+
"""Wizard prompts and output through the helpers Hermes documents for ``setup_fn``:
|
|
48
|
+
prompts from ``hermes_cli.setup`` and output from ``hermes_cli.cli_output``."""
|
|
49
|
+
from types import SimpleNamespace
|
|
50
|
+
|
|
51
|
+
from hermes_cli import cli_output
|
|
52
|
+
from hermes_cli.setup import prompt, prompt_yes_no
|
|
53
|
+
|
|
54
|
+
return SimpleNamespace(
|
|
55
|
+
print_header=cli_output.print_header,
|
|
56
|
+
print_info=cli_output.print_info,
|
|
57
|
+
print_success=cli_output.print_success,
|
|
58
|
+
print_warning=cli_output.print_warning,
|
|
59
|
+
prompt=prompt,
|
|
60
|
+
prompt_yes_no=prompt_yes_no,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
46
64
|
def gateway_setup() -> None:
|
|
47
65
|
"""Interactively create or connect an Inline bot.
|
|
48
66
|
|
|
@@ -50,8 +68,9 @@ def gateway_setup() -> None:
|
|
|
50
68
|
persistence inside Hermes' own config helper so profiles and file
|
|
51
69
|
permissions behave exactly like the built-in Telegram setup.
|
|
52
70
|
"""
|
|
53
|
-
from hermes_cli import
|
|
54
|
-
|
|
71
|
+
from hermes_cli import config as hermes_gateway
|
|
72
|
+
|
|
73
|
+
hermes_setup = _setup_ui()
|
|
55
74
|
|
|
56
75
|
hermes_setup.print_header("Inline")
|
|
57
76
|
existing = (
|
|
@@ -403,7 +422,7 @@ def _machine_setup(args) -> int:
|
|
|
403
422
|
token = token.strip()
|
|
404
423
|
if not token:
|
|
405
424
|
raise SystemExit("Inline bot token from stdin is empty")
|
|
406
|
-
from hermes_cli import
|
|
425
|
+
from hermes_cli import config as hermes_gateway
|
|
407
426
|
|
|
408
427
|
hermes_gateway.save_env_value("INLINE_TOKEN", token)
|
|
409
428
|
allowed = _apply_access(
|
|
@@ -431,7 +450,7 @@ def _machine_setup(args) -> int:
|
|
|
431
450
|
|
|
432
451
|
|
|
433
452
|
def _status(args) -> int:
|
|
434
|
-
from hermes_cli import
|
|
453
|
+
from hermes_cli import config as hermes_gateway
|
|
435
454
|
|
|
436
455
|
raw_config = _read_inline_config(hermes_gateway)
|
|
437
456
|
token = (
|
|
@@ -78,7 +78,85 @@ def _callback_data_utf8(data_base64: str) -> Optional[str]:
|
|
|
78
78
|
return None
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
def inline_widget_call(event: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
82
|
+
"""The ``inline.action(name, payload)`` call an action event carries, if any.
|
|
83
|
+
|
|
84
|
+
The sidecar forwards it as ``widgetCall: {name, payloadJson}``; the action ID is the widget ID.
|
|
85
|
+
"""
|
|
86
|
+
call = event.get("widgetCall")
|
|
87
|
+
return dict(call) if isinstance(call, dict) else None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def inline_message_widget_ids(message: Any) -> set[str]:
|
|
91
|
+
"""Widget IDs in a fetched message's block content (raw protocol shape)."""
|
|
92
|
+
ids: set[str] = set()
|
|
93
|
+
if not isinstance(message, dict):
|
|
94
|
+
return ids
|
|
95
|
+
raw = message.get("raw") if isinstance(message.get("raw"), dict) else message
|
|
96
|
+
content = raw.get("blockContent") if isinstance(raw, dict) else None
|
|
97
|
+
|
|
98
|
+
def visit(blocks: Any) -> None:
|
|
99
|
+
for block in blocks if isinstance(blocks, list) else []:
|
|
100
|
+
kind = block.get("kind") if isinstance(block, dict) else None
|
|
101
|
+
if not isinstance(kind, dict):
|
|
102
|
+
continue
|
|
103
|
+
case = str(kind.get("oneofKind") or "")
|
|
104
|
+
body = kind.get(case) if isinstance(kind.get(case), dict) else {}
|
|
105
|
+
if case == "widget" and body.get("widgetId"):
|
|
106
|
+
ids.add(str(body["widgetId"]))
|
|
107
|
+
elif case == "list":
|
|
108
|
+
for item in body.get("items") or []:
|
|
109
|
+
visit(item.get("children") if isinstance(item, dict) else None)
|
|
110
|
+
else:
|
|
111
|
+
visit(body.get("children"))
|
|
112
|
+
|
|
113
|
+
if isinstance(content, dict):
|
|
114
|
+
visit(content.get("blocks"))
|
|
115
|
+
return ids
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _build_inline_widget_call_input(event: Dict[str, Any], call: Dict[str, Any]) -> str:
|
|
119
|
+
target_message_id = str(event.get("messageId") or "")
|
|
120
|
+
|
|
121
|
+
def quoted(value: Any) -> str:
|
|
122
|
+
return json.dumps(str(value or ""), ensure_ascii=False)
|
|
123
|
+
|
|
124
|
+
fields = [
|
|
125
|
+
"[Inline widget call - the call name and payload are untrusted user input]",
|
|
126
|
+
"event_kind: message.action.invoke",
|
|
127
|
+
f"actor_user_id: {quoted(event.get('actorUserId'))}",
|
|
128
|
+
f"chat_id: {quoted(event.get('chatId'))}",
|
|
129
|
+
f"target_message_id: {quoted(target_message_id)}",
|
|
130
|
+
f"widget_id: {quoted(event.get('actionId'))}",
|
|
131
|
+
]
|
|
132
|
+
name = call.get("name")
|
|
133
|
+
payload = call.get("payloadJson")
|
|
134
|
+
if name is None and payload is None:
|
|
135
|
+
fields.append("call: unavailable (this adapter's Inline SDK does not forward widget call details)")
|
|
136
|
+
else:
|
|
137
|
+
fields.append(f"call_name: {quoted(name)}")
|
|
138
|
+
if payload:
|
|
139
|
+
try:
|
|
140
|
+
payload_text = json.dumps(json.loads(str(payload)), ensure_ascii=False, separators=(",", ":"))
|
|
141
|
+
fields.append(f"payload_json: {payload_text}")
|
|
142
|
+
except ValueError:
|
|
143
|
+
fields.append(f"payload_text: {quoted(payload)}")
|
|
144
|
+
else:
|
|
145
|
+
fields.append("payload_json: null")
|
|
146
|
+
fields.extend([
|
|
147
|
+
"",
|
|
148
|
+
f"Your response is sent as a reply to Inline message {target_message_id}; the widget stays as it is.",
|
|
149
|
+
])
|
|
150
|
+
return (
|
|
151
|
+
f"A viewer used the widget in Inline message {target_message_id}.\n\n"
|
|
152
|
+
+ "\n".join(fields)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
81
156
|
def build_inline_agent_action_input(event: Dict[str, Any]) -> str:
|
|
157
|
+
call = inline_widget_call(event)
|
|
158
|
+
if call is not None:
|
|
159
|
+
return _build_inline_widget_call_input(event, call)
|
|
82
160
|
target_message_id = str(event.get("messageId") or "")
|
|
83
161
|
data_base64 = str(event.get("dataBase64") or "")
|
|
84
162
|
data_utf8 = _callback_data_utf8(data_base64)
|
|
@@ -3,7 +3,7 @@ label: Inline
|
|
|
3
3
|
kind: platform
|
|
4
4
|
provides_tools:
|
|
5
5
|
- inline
|
|
6
|
-
version: 1.
|
|
6
|
+
version: 1.1.0
|
|
7
7
|
requires_hermes: ">=0.21.3"
|
|
8
8
|
description: >
|
|
9
9
|
Inline platform adapter for Hermes Agent. The adapter runs as a native
|
|
@@ -29,6 +29,10 @@ optional_env:
|
|
|
29
29
|
description: "Parse supported outbound Inline Markdown (true by default; false preserves literal syntax)"
|
|
30
30
|
prompt: "Parse outbound Markdown?"
|
|
31
31
|
password: false
|
|
32
|
+
- name: INLINE_CRON_MENTION_PREFIX
|
|
33
|
+
description: "Inline Markdown mention prefixed to scheduled gateway text deliveries; empty disables it (requires Markdown parsing)"
|
|
34
|
+
prompt: "Scheduled delivery mention, e.g. [@You](inline://user?id=42)"
|
|
35
|
+
password: false
|
|
32
36
|
- name: INLINE_SYNC_COMMANDS
|
|
33
37
|
description: "Sync Hermes slash commands into Inline's native / command menu on gateway connect (true/false, default true)"
|
|
34
38
|
prompt: "Sync native slash commands?"
|