@inline-chat/hermes-agent-adapter 0.0.8 → 0.0.10
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 +13 -7
- package/dist/install.js +92 -42
- package/package.json +5 -4
- package/plugin/inline/adapter.py +203 -22
- package/plugin/inline/cli.py +88 -27
- package/plugin/inline/message_actions.py +109 -0
- package/plugin/inline/plugin.yaml +2 -2
- package/plugin/inline/sidecar/index.mjs +1390 -34
- package/plugin/inline/tools.py +162 -12
package/plugin/inline/tools.py
CHANGED
|
@@ -8,6 +8,8 @@ import urllib.error
|
|
|
8
8
|
import urllib.request
|
|
9
9
|
from typing import Any, Dict, Iterable, Optional
|
|
10
10
|
|
|
11
|
+
from .message_actions import build_inline_agent_action_id
|
|
12
|
+
|
|
11
13
|
try:
|
|
12
14
|
from tools.registry import tool_error, tool_result
|
|
13
15
|
except Exception: # pragma: no cover - used by lightweight package tests
|
|
@@ -27,6 +29,11 @@ _MAX_MESSAGE_IDS = 100
|
|
|
27
29
|
_MAX_INLINE_ID = 9_223_372_036_854_775_807
|
|
28
30
|
_DEFAULT_HISTORY_LIMIT = 20
|
|
29
31
|
_MAX_TEXT_CHARS = 4000
|
|
32
|
+
_MAX_ACTION_ROWS = 8
|
|
33
|
+
_MAX_ACTIONS_PER_ROW = 8
|
|
34
|
+
_MAX_ACTION_LABEL_CHARS = 64
|
|
35
|
+
_MAX_ACTION_CALLBACK_BYTES = 1024
|
|
36
|
+
_MAX_ACTION_COPY_TEXT_CHARS = 4096
|
|
30
37
|
_MAX_QUERY_CHARS = 500
|
|
31
38
|
_MAX_RESULT_TEXT_CHARS = 1600
|
|
32
39
|
_MAX_MESSAGE_ENTITIES = 12
|
|
@@ -58,7 +65,11 @@ INLINE_PLATFORM_GUIDANCE = (
|
|
|
58
65
|
"while keeping the stable user ID in the link target. Link chats as "
|
|
59
66
|
"[title](inline://chat?id=123), and link reply threads as "
|
|
60
67
|
"[title](inline://thread?id=123). In Inline, reply threads are chat ids; "
|
|
61
|
-
"do not treat thread ids as reply/quote message ids.
|
|
68
|
+
"do not treat thread ids as reply/quote message ids. Supported Markdown is "
|
|
69
|
+
"emphasis, inline or fenced code, links, headings, lists or checklists, "
|
|
70
|
+
"quotes, tables, separators, and HTTP(S) images. Do not rely on "
|
|
71
|
+
"strikethrough, footnotes, or arbitrary HTML; unsupported or ambiguous "
|
|
72
|
+
"incomplete syntax remains visible text. Use ordinary Markdown "
|
|
62
73
|
"tables for tabular data, never fenced-code tables. For collapsible work, "
|
|
63
74
|
"use <details open>, then <summary>Title</summary>, body Markdown, and "
|
|
64
75
|
"</details>; put kind=\"progress\" on summary only while work is in progress. "
|
|
@@ -68,11 +79,12 @@ INLINE_PLATFORM_GUIDANCE = (
|
|
|
68
79
|
_sidecar: Dict[str, Any] = {}
|
|
69
80
|
|
|
70
81
|
_ACTION_MANIFEST = [
|
|
82
|
+
("send_message", "(chat_id?|user_id?, text, buttons?)", "Send a message, including optional agent-owned buttons."),
|
|
71
83
|
("get_chat", "(chat_id?)", "Get metadata for a chat or reply thread."),
|
|
72
84
|
("get_messages", "(chat_id?, message_ids)", "Fetch specific Inline messages by ID."),
|
|
73
85
|
("get_history", "(chat_id?, limit?, anchor_id?)", "Fetch recent or anchored Inline history."),
|
|
74
86
|
("search_messages", "(chat_id?|user_id?, query, limit?, offset_id?)", "Search Inline message text in a chat, thread, or DM."),
|
|
75
|
-
("edit_message", "(chat_id?|user_id?, message_id, text)", "Edit a bot-owned message."),
|
|
87
|
+
("edit_message", "(chat_id?|user_id?, message_id, text, buttons?)", "Edit a bot-owned message and optionally replace or clear its buttons."),
|
|
76
88
|
("delete_message", "(chat_id?|user_id?, message_id)", "Delete a bot-owned message."),
|
|
77
89
|
("add_reaction", "(chat_id?|user_id?, message_id?, emoji)", "Add a reaction to an Inline message."),
|
|
78
90
|
("remove_reaction", "(chat_id?|user_id?, message_id?, emoji)", "Remove the bot's reaction from an Inline message."),
|
|
@@ -83,6 +95,10 @@ _ACTION_MANIFEST = [
|
|
|
83
95
|
("create_thread", "(parent_chat_id?, parent_message_id?, title?)", "Create an Inline reply thread."),
|
|
84
96
|
("create_chat", "(title, space_id?, participant_user_ids?, is_public?)", "Create a top-level Inline thread/chat."),
|
|
85
97
|
("create_agent", "(name, skill_key?, instructions?)", "Create a named Inline Agent backed by this bot."),
|
|
98
|
+
("get_agent", "(agent_id)", "Inspect one Agent backed by this bot."),
|
|
99
|
+
("list_agents", "()", "List Agents backed by this bot."),
|
|
100
|
+
("update_agent", "(agent_id, fields...)", "Update a named Inline Agent; empty optional fields clear them."),
|
|
101
|
+
("delete_agent", "(agent_id)", "Delete an Agent backed by this bot."),
|
|
86
102
|
("set_presence", "(chat_id?|user_id?, kind, comment?)", "Set the bot avatar presence/status message."),
|
|
87
103
|
]
|
|
88
104
|
_ACTIONS = [name for name, _, _ in _ACTION_MANIFEST]
|
|
@@ -139,7 +155,9 @@ def tool_context_prompt(
|
|
|
139
155
|
return None
|
|
140
156
|
lines = [
|
|
141
157
|
"- The `inline` tool is available for Inline history, search, exact message lookup, reactions, pins, and explicit thread management.",
|
|
142
|
-
"-
|
|
158
|
+
"- Return normal replies as text. Use inline send_message only to create a message with action buttons or to send into a different destination.",
|
|
159
|
+
"- An Inline action-button turn names its source message. A normal response replaces that message; omitted buttons clear the old buttons. Use edit_message with buttons to replace them explicitly.",
|
|
160
|
+
"- If edit_message already finalized the source action card during this turn, return NO_REPLY so the automatic final response does not overwrite that edit.",
|
|
143
161
|
]
|
|
144
162
|
if thread_id:
|
|
145
163
|
lines.append(f"- Current Inline reply thread: `{thread_id}`. Use this as `chat_id` for thread-scoped reads.")
|
|
@@ -166,7 +184,8 @@ INLINE_TOOL_SCHEMA = {
|
|
|
166
184
|
+ "\n".join(f" {name}{sig} - {desc}" for name, sig, desc in _ACTION_MANIFEST)
|
|
167
185
|
+ "\n\n"
|
|
168
186
|
"When chat_id is omitted, the tool uses the current Inline reply thread if present, otherwise the current chat. "
|
|
169
|
-
"
|
|
187
|
+
"Return normal assistant replies as text. Use send_message only for a button card or a different destination. "
|
|
188
|
+
"Agent-owned button presses return as ordinary turns, and the normal response replaces the source card while clearing omitted buttons. "
|
|
170
189
|
"Use create_thread with the current triggering message as parent_message_id to move large top-level discussions into a reply thread. "
|
|
171
190
|
"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. "
|
|
172
191
|
"Use search_messages for exact catch-up across older chat history. "
|
|
@@ -196,11 +215,12 @@ INLINE_TOOL_SCHEMA = {
|
|
|
196
215
|
},
|
|
197
216
|
"title": {"type": "string", "description": "Thread title. Required for create_chat and optional for create_thread."},
|
|
198
217
|
"name": {"type": "string", "description": "Agent name for create_agent."},
|
|
218
|
+
"agent_id": {"type": "string", "description": "Globally unique Inline Agent ID for get_agent, update_agent, or delete_agent."},
|
|
199
219
|
"handle": {"type": "string", "description": "Optional Agent handle."},
|
|
200
|
-
"skill_key": {"type": "string", "description": "Optional harness skill key for create_agent."},
|
|
201
|
-
"instructions": {"type": "string", "description": "Optional specialized Agent instructions."},
|
|
202
|
-
"description": {"type": "string", "description": "Optional thread
|
|
203
|
-
"emoji": {"type": "string", "description": "Optional thread
|
|
220
|
+
"skill_key": {"type": "string", "description": "Optional harness skill key for create_agent or update_agent."},
|
|
221
|
+
"instructions": {"type": "string", "description": "Optional specialized Agent instructions for create_agent or update_agent."},
|
|
222
|
+
"description": {"type": "string", "description": "Optional thread or Agent description."},
|
|
223
|
+
"emoji": {"type": "string", "description": "Optional thread or Agent emoji, or reaction emoji for reaction actions."},
|
|
204
224
|
"space_id": {"type": "string", "description": "Optional parent space ID for create_chat."},
|
|
205
225
|
"participant_user_ids": {
|
|
206
226
|
"type": "array",
|
|
@@ -213,8 +233,26 @@ INLINE_TOOL_SCHEMA = {
|
|
|
213
233
|
"description": "Whether create_chat is visible to the parent space. Defaults to false and requires space_id when true.",
|
|
214
234
|
},
|
|
215
235
|
"query": {"type": "string", "description": "Search query for search_messages."},
|
|
216
|
-
"text": {"type": "string", "description": "Message text for edit_message."},
|
|
217
|
-
"
|
|
236
|
+
"text": {"type": "string", "description": "Message text for send_message or edit_message."},
|
|
237
|
+
"buttons": {
|
|
238
|
+
"type": "array",
|
|
239
|
+
"maxItems": _MAX_ACTION_ROWS,
|
|
240
|
+
"description": "Button rows. Omit to preserve buttons on edit; pass [] to clear them.",
|
|
241
|
+
"items": {
|
|
242
|
+
"type": "array",
|
|
243
|
+
"maxItems": _MAX_ACTIONS_PER_ROW,
|
|
244
|
+
"items": {
|
|
245
|
+
"type": "object",
|
|
246
|
+
"properties": {
|
|
247
|
+
"text": {"type": "string", "description": "Visible button label."},
|
|
248
|
+
"callback_data": {"type": "string", "description": "Opaque data returned in an agent turn when pressed."},
|
|
249
|
+
"copy_text": {"type": "string", "description": "Text copied locally instead of invoking the agent."},
|
|
250
|
+
},
|
|
251
|
+
"required": ["text"],
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
"parse_markdown": {"type": "boolean", "description": "Whether Inline should parse its supported Markdown surface. Defaults to true; false preserves the supplied syntax literally."},
|
|
218
256
|
"limit": {
|
|
219
257
|
"type": "integer",
|
|
220
258
|
"minimum": 1,
|
|
@@ -261,6 +299,17 @@ def _handle_inline_tool(args: Dict[str, Any], **_: Any) -> str:
|
|
|
261
299
|
|
|
262
300
|
|
|
263
301
|
def _request_for_action(action: str, args: Dict[str, Any]) -> tuple[str, Dict[str, Any]]:
|
|
302
|
+
if action == "send_message":
|
|
303
|
+
body = {
|
|
304
|
+
"target": _target(args),
|
|
305
|
+
"text": _required_str(args, "text", max_chars=_MAX_TEXT_CHARS),
|
|
306
|
+
"parseMarkdown": _bool(args.get("parse_markdown"), True),
|
|
307
|
+
}
|
|
308
|
+
actions = _message_actions(args)
|
|
309
|
+
if actions is not None:
|
|
310
|
+
body["actions"] = actions
|
|
311
|
+
return "/send", body
|
|
312
|
+
|
|
264
313
|
if action == "get_chat":
|
|
265
314
|
return "/chat", {"target": _target(args)}
|
|
266
315
|
|
|
@@ -292,12 +341,16 @@ def _request_for_action(action: str, args: Dict[str, Any]) -> tuple[str, Dict[st
|
|
|
292
341
|
return "/search", body
|
|
293
342
|
|
|
294
343
|
if action == "edit_message":
|
|
295
|
-
|
|
344
|
+
body = {
|
|
296
345
|
"target": _target(args),
|
|
297
346
|
"messageId": _required_id(args, "message_id"),
|
|
298
347
|
"text": _required_str(args, "text", max_chars=_MAX_TEXT_CHARS),
|
|
299
348
|
"parseMarkdown": _bool(args.get("parse_markdown"), True),
|
|
300
349
|
}
|
|
350
|
+
actions = _message_actions(args)
|
|
351
|
+
if actions is not None:
|
|
352
|
+
body["actions"] = actions
|
|
353
|
+
return "/edit", body
|
|
301
354
|
|
|
302
355
|
if action == "delete_message":
|
|
303
356
|
return "/delete", {
|
|
@@ -376,6 +429,26 @@ def _request_for_action(action: str, args: Dict[str, Any]) -> tuple[str, Dict[st
|
|
|
376
429
|
body[key] = value
|
|
377
430
|
return "/create-agent", body
|
|
378
431
|
|
|
432
|
+
if action == "get_agent":
|
|
433
|
+
return "/get-agent", {"agentId": _required_id(args, "agent_id")}
|
|
434
|
+
|
|
435
|
+
if action == "list_agents":
|
|
436
|
+
return "/list-agents", {}
|
|
437
|
+
|
|
438
|
+
if action == "update_agent":
|
|
439
|
+
body = {"agentId": _required_id(args, "agent_id")}
|
|
440
|
+
if "name" in args:
|
|
441
|
+
body["name"] = _required_str(args, "name", max_chars=256)
|
|
442
|
+
for key in ("handle", "emoji", "description", "skill_key", "instructions"):
|
|
443
|
+
if key in args and args.get(key) is not None:
|
|
444
|
+
body[key] = _str(args.get(key))
|
|
445
|
+
if len(body) == 1:
|
|
446
|
+
raise InlineToolError("update_agent requires at least one field", "bad_format")
|
|
447
|
+
return "/update-agent", body
|
|
448
|
+
|
|
449
|
+
if action == "delete_agent":
|
|
450
|
+
return "/delete-agent", {"agentId": _required_id(args, "agent_id")}
|
|
451
|
+
|
|
379
452
|
if action == "set_presence":
|
|
380
453
|
kind = _str(args.get("kind"))
|
|
381
454
|
if kind not in _PRESENCE_KINDS:
|
|
@@ -504,7 +577,75 @@ def _message_id_or_current(args: Dict[str, Any]) -> str:
|
|
|
504
577
|
return message_id
|
|
505
578
|
|
|
506
579
|
|
|
580
|
+
def _message_actions(args: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
581
|
+
if "buttons" not in args:
|
|
582
|
+
return None
|
|
583
|
+
raw_rows = args.get("buttons")
|
|
584
|
+
if not isinstance(raw_rows, list):
|
|
585
|
+
raise InlineToolError("buttons must be an array of button rows", "bad_format")
|
|
586
|
+
if len(raw_rows) > _MAX_ACTION_ROWS:
|
|
587
|
+
raise InlineToolError(f"buttons supports at most {_MAX_ACTION_ROWS} rows", "bad_format")
|
|
588
|
+
|
|
589
|
+
rows: list[Dict[str, Any]] = []
|
|
590
|
+
for row_index, raw_row in enumerate(raw_rows):
|
|
591
|
+
if not isinstance(raw_row, list) or not raw_row:
|
|
592
|
+
raise InlineToolError("each buttons row must be a non-empty array", "bad_format")
|
|
593
|
+
if len(raw_row) > _MAX_ACTIONS_PER_ROW:
|
|
594
|
+
raise InlineToolError(
|
|
595
|
+
f"each buttons row supports at most {_MAX_ACTIONS_PER_ROW} buttons",
|
|
596
|
+
"bad_format",
|
|
597
|
+
)
|
|
598
|
+
actions: list[Dict[str, str]] = []
|
|
599
|
+
for action_index, raw_button in enumerate(raw_row):
|
|
600
|
+
if not isinstance(raw_button, dict):
|
|
601
|
+
raise InlineToolError("each button must be an object", "bad_format")
|
|
602
|
+
label = _str(raw_button.get("text"))
|
|
603
|
+
if not label:
|
|
604
|
+
raise InlineToolError("button text is required", "bad_format")
|
|
605
|
+
if len(label) > _MAX_ACTION_LABEL_CHARS:
|
|
606
|
+
raise InlineToolError(
|
|
607
|
+
f"button text supports at most {_MAX_ACTION_LABEL_CHARS} characters",
|
|
608
|
+
"bad_format",
|
|
609
|
+
)
|
|
610
|
+
has_callback = "callback_data" in raw_button
|
|
611
|
+
has_copy = "copy_text" in raw_button
|
|
612
|
+
if has_callback == has_copy:
|
|
613
|
+
raise InlineToolError(
|
|
614
|
+
"each button requires exactly one of callback_data or copy_text",
|
|
615
|
+
"bad_format",
|
|
616
|
+
)
|
|
617
|
+
action: Dict[str, str] = {
|
|
618
|
+
"id": build_inline_agent_action_id(row_index, action_index),
|
|
619
|
+
"text": label,
|
|
620
|
+
}
|
|
621
|
+
if has_callback:
|
|
622
|
+
callback_data = raw_button.get("callback_data")
|
|
623
|
+
if not isinstance(callback_data, str):
|
|
624
|
+
raise InlineToolError("callback_data must be a string", "bad_format")
|
|
625
|
+
if len(callback_data.encode("utf-8")) > _MAX_ACTION_CALLBACK_BYTES:
|
|
626
|
+
raise InlineToolError(
|
|
627
|
+
f"callback_data supports at most {_MAX_ACTION_CALLBACK_BYTES} UTF-8 bytes",
|
|
628
|
+
"bad_format",
|
|
629
|
+
)
|
|
630
|
+
action["callback"] = callback_data
|
|
631
|
+
else:
|
|
632
|
+
copy_text = raw_button.get("copy_text")
|
|
633
|
+
if not isinstance(copy_text, str) or not copy_text:
|
|
634
|
+
raise InlineToolError("copy_text must be a non-empty string", "bad_format")
|
|
635
|
+
if len(copy_text) > _MAX_ACTION_COPY_TEXT_CHARS:
|
|
636
|
+
raise InlineToolError(
|
|
637
|
+
f"copy_text supports at most {_MAX_ACTION_COPY_TEXT_CHARS} characters",
|
|
638
|
+
"bad_format",
|
|
639
|
+
)
|
|
640
|
+
action["copyText"] = copy_text
|
|
641
|
+
actions.append(action)
|
|
642
|
+
rows.append({"actions": actions})
|
|
643
|
+
return {"rows": rows}
|
|
644
|
+
|
|
645
|
+
|
|
507
646
|
def _compact_result(action: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
647
|
+
if action in {"send_message", "edit_message"}:
|
|
648
|
+
return {"messageId": _str(result.get("messageId"))}
|
|
508
649
|
if action == "get_chat":
|
|
509
650
|
return _compact_chat(result)
|
|
510
651
|
if action in {"get_messages", "get_history", "search_messages"}:
|
|
@@ -545,8 +686,17 @@ def _compact_result(action: str, result: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
545
686
|
"chat": _compact_chat(result.get("chat") if isinstance(result.get("chat"), dict) else {}),
|
|
546
687
|
"dialog": _summarize_value(result.get("dialog")) if result.get("dialog") is not None else None,
|
|
547
688
|
}
|
|
548
|
-
if action
|
|
689
|
+
if action in {"create_agent", "update_agent"}:
|
|
549
690
|
return {"agent": _summarize_value(result.get("agent"))}
|
|
691
|
+
if action == "get_agent":
|
|
692
|
+
return {
|
|
693
|
+
"bot": _summarize_value(result.get("bot")),
|
|
694
|
+
"agent": _summarize_value(result.get("agent")),
|
|
695
|
+
}
|
|
696
|
+
if action == "list_agents":
|
|
697
|
+
return {"agents": _summarize_value(result.get("agents"))}
|
|
698
|
+
if action == "delete_agent":
|
|
699
|
+
return {"agentId": _str(result.get("agentId") or result.get("agent_id"))}
|
|
550
700
|
return result
|
|
551
701
|
|
|
552
702
|
|