@inline-chat/hermes-agent-adapter 1.0.0-alpha.1 → 1.0.0-alpha.3
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/package.json +4 -4
- package/plugin/inline/adapter.py +10 -8
- package/plugin/inline/cli.py +1 -1
- package/plugin/inline/plugin.yaml +1 -1
- package/plugin/inline/tools.py +27 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inline-chat/hermes-agent-adapter",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.3",
|
|
4
4
|
"description": "Hermes Agent platform adapter for Inline, with a native Python plugin and bundled Inline realtime sidecar.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -88,9 +88,9 @@
|
|
|
88
88
|
"testedHermesVersion": "0.22.5",
|
|
89
89
|
"testedHermesCommit": "bf0203a794a07aac3ff764972ab9087baa4e4f4e"
|
|
90
90
|
},
|
|
91
|
-
"gitHead": "
|
|
91
|
+
"gitHead": "6294aa252af0ba2226afbed22d26a3fdf767a37d",
|
|
92
92
|
"inlineRelease": {
|
|
93
|
-
"tag": "hermes-agent-v1.0.0-alpha.
|
|
94
|
-
"sha": "
|
|
93
|
+
"tag": "hermes-agent-v1.0.0-alpha.3",
|
|
94
|
+
"sha": "6294aa252af0ba2226afbed22d26a3fdf767a37d"
|
|
95
95
|
}
|
|
96
96
|
}
|
package/plugin/inline/adapter.py
CHANGED
|
@@ -1176,18 +1176,19 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
1176
1176
|
return {**(metadata or {}), "thread_id": thread_id} if thread_id else metadata
|
|
1177
1177
|
|
|
1178
1178
|
def set_reply_threads_from_tool(
|
|
1179
|
-
self, *, chat_id: str, chat_type: str,
|
|
1179
|
+
self, *, chat_id: str, chat_type: str, parent_chat_id: str, actor_id: str, actor_is_bot: bool, mode: str,
|
|
1180
1180
|
) -> Dict[str, Any]:
|
|
1181
|
-
"""Change
|
|
1182
|
-
|
|
1181
|
+
"""Change reply threads because the person talking to the agent asked, with the access
|
|
1182
|
+
/threads needs. Inside a reply thread it changes the parent chat, as /threads does."""
|
|
1183
1183
|
if actor_is_bot or not actor_id:
|
|
1184
1184
|
raise PermissionError("only a person can change reply threads")
|
|
1185
|
-
|
|
1186
|
-
|
|
1185
|
+
target_chat_id = parent_chat_id or chat_id
|
|
1186
|
+
if parent_chat_id and self._actor_authorization(chat_type, actor_id, parent_chat_id) is not True:
|
|
1187
|
+
raise PermissionError("parent chat access could not be confirmed")
|
|
1187
1188
|
if not self._may_change_reply_threads(actor_id, chat_type):
|
|
1188
1189
|
raise PermissionError("changing reply threads needs access to the threads command")
|
|
1189
|
-
self._set_reply_threads_for_chat(
|
|
1190
|
-
return {"chat_id": self._chat_key(
|
|
1190
|
+
self._set_reply_threads_for_chat(target_chat_id, None if mode in {"default", "reset"} else _reply_thread_mode(mode))
|
|
1191
|
+
return {"chat_id": self._chat_key(target_chat_id), "mode": self._reply_thread_mode_for_chat(target_chat_id)}
|
|
1191
1192
|
|
|
1192
1193
|
@staticmethod
|
|
1193
1194
|
def _bot_settings_chat_type(info: Dict[str, Any]) -> str:
|
|
@@ -2795,7 +2796,8 @@ class InlineAdapter(BasePlatformAdapter):
|
|
|
2795
2796
|
_inline_tools.remember_turn_message(
|
|
2796
2797
|
session_chat_id=session_chat_id, thread_id=str(thread_id or ""), message_id=msg_id,
|
|
2797
2798
|
chat_id=chat_id, sender_id=from_id, sender_is_bot=sender_profile.get("bot") is True,
|
|
2798
|
-
chat_type=chat_type,
|
|
2799
|
+
chat_type=chat_type,
|
|
2800
|
+
parent_chat_id=parent_chat_id if parent_chat_id and parent_chat_id != chat_id else "",
|
|
2799
2801
|
)
|
|
2800
2802
|
except Exception:
|
|
2801
2803
|
logger.debug("[inline] failed to record the turn's message for tools", exc_info=True)
|
package/plugin/inline/cli.py
CHANGED
|
@@ -27,7 +27,7 @@ _CLI_INSTALL_URL = "https://inline.chat/cli/install.sh"
|
|
|
27
27
|
_MAX_TOKEN_BYTES = 16 * 1024
|
|
28
28
|
_MAX_PROBE_RESPONSE_BYTES = 64 * 1024
|
|
29
29
|
_MACHINE_SETUP_PROTOCOL_VERSION = 1
|
|
30
|
-
_PROBE_USER_AGENT = "inline-hermes-agent-adapter/1.0.0-alpha.
|
|
30
|
+
_PROBE_USER_AGENT = "inline-hermes-agent-adapter/1.0.0-alpha.3"
|
|
31
31
|
_ENV_REFERENCE_RE = re.compile(r"^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$")
|
|
32
32
|
|
|
33
33
|
|
package/plugin/inline/tools.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import http.client
|
|
5
6
|
from collections import OrderedDict
|
|
6
7
|
import os
|
|
7
8
|
import re
|
|
@@ -87,12 +88,13 @@ _TURN_MESSAGES_MAX = 1000
|
|
|
87
88
|
|
|
88
89
|
def remember_turn_message(
|
|
89
90
|
*, session_chat_id: str, thread_id: str, message_id: str, chat_id: str, sender_id: str, sender_is_bot: bool,
|
|
90
|
-
chat_type: str = "",
|
|
91
|
+
chat_type: str = "", parent_chat_id: str = "",
|
|
91
92
|
) -> None:
|
|
93
|
+
"""``parent_chat_id`` is set only when the message itself was sent inside a reply thread."""
|
|
92
94
|
key = (str(hermes_home()), str(session_chat_id or ""), str(thread_id or ""), str(message_id or ""))
|
|
93
95
|
_turn_messages[key] = {
|
|
94
96
|
"chatId": str(chat_id), "senderId": str(sender_id or ""), "senderIsBot": bool(sender_is_bot),
|
|
95
|
-
"chatType": str(chat_type or ""), "
|
|
97
|
+
"chatType": str(chat_type or ""), "parentChatId": str(parent_chat_id or ""),
|
|
96
98
|
}
|
|
97
99
|
_turn_messages.move_to_end(key)
|
|
98
100
|
while len(_turn_messages) > _TURN_MESSAGES_MAX:
|
|
@@ -107,14 +109,15 @@ def _current_turn() -> Dict[str, Any]:
|
|
|
107
109
|
known = _turn_messages.get((str(hermes_home()), session_chat, thread_id, message_id)) or {}
|
|
108
110
|
# The turn already runs in a thread when reply threads moved it there (its message lives
|
|
109
111
|
# in the parent chat) or when its message was sent inside the thread.
|
|
110
|
-
|
|
112
|
+
parent_chat_id = known.get("parentChatId") or ""
|
|
113
|
+
in_thread = bool(thread_id) and (bool(parent_chat_id) or (known.get("chatId") or session_chat) != thread_id)
|
|
111
114
|
return {
|
|
112
115
|
"messageId": message_id,
|
|
113
116
|
"chatId": known.get("chatId") or session_chat,
|
|
114
117
|
"senderId": known.get("senderId") or "",
|
|
115
118
|
"senderIsBot": known.get("senderIsBot"),
|
|
116
119
|
"chatType": known.get("chatType") or "",
|
|
117
|
-
"
|
|
120
|
+
"parentChatId": parent_chat_id,
|
|
118
121
|
"threadId": thread_id if in_thread else "",
|
|
119
122
|
}
|
|
120
123
|
|
|
@@ -242,7 +245,10 @@ def tool_context_prompt(
|
|
|
242
245
|
lines.append(f"- Current Inline reply thread: `{thread_id}`. Use this as `chat_id` for thread-scoped reads.")
|
|
243
246
|
lines.append(f"- Link the current thread as `[this thread](inline://thread?id={thread_id})` when asked for a thread link.")
|
|
244
247
|
if parent_chat_id:
|
|
245
|
-
lines.append(
|
|
248
|
+
lines.append(
|
|
249
|
+
f"- Parent Inline chat: `{parent_chat_id}`. Earlier conversation lives there; "
|
|
250
|
+
"read it with get_history or search_messages using that chat_id."
|
|
251
|
+
)
|
|
246
252
|
else:
|
|
247
253
|
lines.append(f"- Current Inline chat: `{chat_id}`.")
|
|
248
254
|
lines.append(f"- Link the current chat as `[this chat](inline://chat?id={chat_id})` when asked for a chat link.")
|
|
@@ -265,7 +271,7 @@ INLINE_TOOL_SCHEMA = {
|
|
|
265
271
|
"When chat_id is omitted, the tool uses the current Inline reply thread if present, otherwise the current chat. "
|
|
266
272
|
"Return normal assistant replies as text. Use send_message only for a button card or a different destination. "
|
|
267
273
|
"Agent-owned button presses return as ordinary turns, and the normal response replaces the source card while clearing omitted buttons. "
|
|
268
|
-
"Use create_thread on the current triggering message when someone asks to continue in a thread; your answer then goes into it, so don't also post it in the chat. "
|
|
274
|
+
"Use create_thread on the current triggering message when someone asks to continue in a thread; your answer then goes into it, so don't also post it in the chat, and it starts with a short summary of the conversation so far. "
|
|
269
275
|
"Use set_reply_threads when someone asks you to always or never answer in threads here. "
|
|
270
276
|
"Use create_chat to create a new top-level destination outside the current conversation; it defaults to private, and public creation must explicitly set is_public with a space_id. "
|
|
271
277
|
"Use search_messages for exact catch-up across older chat history. "
|
|
@@ -619,7 +625,7 @@ def _set_reply_threads(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
619
625
|
return owner.set_reply_threads_from_tool(
|
|
620
626
|
chat_id=turn["chatId"],
|
|
621
627
|
chat_type=turn.get("chatType") or "group",
|
|
622
|
-
|
|
628
|
+
parent_chat_id=turn.get("parentChatId") or "",
|
|
623
629
|
actor_id=turn["senderId"],
|
|
624
630
|
actor_is_bot=bool(turn.get("senderIsBot")),
|
|
625
631
|
mode=mode,
|
|
@@ -646,9 +652,18 @@ def _create_thread(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
646
652
|
thread_id = _inline_id(result.get("chatId"))
|
|
647
653
|
if for_this_turn and owner is not None and thread_id:
|
|
648
654
|
owner.note_turn_thread(turn["chatId"], turn["messageId"], thread_id)
|
|
655
|
+
result["message"] = _NEW_TURN_THREAD_MESSAGE
|
|
649
656
|
return result
|
|
650
657
|
|
|
651
658
|
|
|
659
|
+
# Later messages in a thread opened mid-turn start a new session that reads the thread's
|
|
660
|
+
# history, so the answer that opens it carries what matters from this conversation.
|
|
661
|
+
_NEW_TURN_THREAD_MESSAGE = (
|
|
662
|
+
"Your answer goes into this thread. Start it with a short summary of what matters from the "
|
|
663
|
+
"conversation so far; later messages here start a new session that reads this thread."
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
|
|
652
667
|
_FOLLOW_CONFIRMATIONS = {
|
|
653
668
|
"following": "Following this chat.",
|
|
654
669
|
"unfollowed": "Unfollowed; mentions and replies still reach me.",
|
|
@@ -680,13 +695,16 @@ def _sidecar_call(path: str, body: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
680
695
|
method="POST",
|
|
681
696
|
)
|
|
682
697
|
try:
|
|
683
|
-
|
|
698
|
+
# IPC stays on loopback even when the host configures an HTTP proxy.
|
|
699
|
+
# The sidecar token must never be forwarded to that proxy.
|
|
700
|
+
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
|
|
701
|
+
with opener.open(request, timeout=15) as response:
|
|
684
702
|
raw = response.read().decode("utf-8")
|
|
685
703
|
status = response.status
|
|
686
704
|
except urllib.error.HTTPError as exc:
|
|
687
705
|
raw = exc.read().decode("utf-8", errors="replace")
|
|
688
706
|
status = exc.code
|
|
689
|
-
except
|
|
707
|
+
except (OSError, http.client.HTTPException) as exc:
|
|
690
708
|
raise InlineToolError(f"Inline sidecar is not reachable: {exc}", "transient") from exc
|
|
691
709
|
|
|
692
710
|
try:
|