feat(plugins): expose thread_id to plugins (#99)
This commit is contained in:
@@ -183,6 +183,15 @@ Command handlers receive a `CommandContext` with:
|
|||||||
Use `ctx.executor.run_one(...)` or `ctx.executor.run_many(...)` to reuse Takopi's
|
Use `ctx.executor.run_one(...)` or `ctx.executor.run_many(...)` to reuse Takopi's
|
||||||
engine pipeline. Use `mode="capture"` to collect results and build a custom reply.
|
engine pipeline. Use `mode="capture"` to collect results and build a custom reply.
|
||||||
|
|
||||||
|
`ctx.message` and `ctx.reply_to` are `MessageRef` objects with:
|
||||||
|
|
||||||
|
- `channel_id` (`int | str`, chat/channel id)
|
||||||
|
- `message_id` (`int | str`, message id)
|
||||||
|
- `thread_id` (`int | str | None`; set when the transport supports threads, like Telegram topics)
|
||||||
|
- `raw` (transport-specific payload, may be `None`)
|
||||||
|
|
||||||
|
Example: key per-thread state by `(ctx.message.channel_id, ctx.message.thread_id)`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## TransportRuntime helpers
|
## TransportRuntime helpers
|
||||||
@@ -228,6 +237,7 @@ async def on_message(...):
|
|||||||
message_id=...,
|
message_id=...,
|
||||||
text=...,
|
text=...,
|
||||||
reply_to=...,
|
reply_to=...,
|
||||||
|
thread_id=...,
|
||||||
)
|
)
|
||||||
await handle_message(
|
await handle_message(
|
||||||
exec_cfg,
|
exec_cfg,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from .transport import (
|
|||||||
MessageRef,
|
MessageRef,
|
||||||
RenderedMessage,
|
RenderedMessage,
|
||||||
SendOptions,
|
SendOptions,
|
||||||
|
ThreadId,
|
||||||
Transport,
|
Transport,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ class IncomingMessage:
|
|||||||
message_id: MessageId
|
message_id: MessageId
|
||||||
text: str
|
text: str
|
||||||
reply_to: MessageRef | None = None
|
reply_to: MessageRef | None = None
|
||||||
thread_id: int | None = None
|
thread_id: ThreadId | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -110,7 +111,7 @@ async def _send_or_edit_message(
|
|||||||
reply_to: MessageRef | None = None,
|
reply_to: MessageRef | None = None,
|
||||||
notify: bool = True,
|
notify: bool = True,
|
||||||
replace_ref: MessageRef | None = None,
|
replace_ref: MessageRef | None = None,
|
||||||
thread_id: int | None = None,
|
thread_id: ThreadId | None = None,
|
||||||
) -> tuple[MessageRef | None, bool]:
|
) -> tuple[MessageRef | None, bool]:
|
||||||
msg = message
|
msg = message
|
||||||
followups = message.extra.get("followups")
|
followups = message.extra.get("followups")
|
||||||
@@ -248,7 +249,7 @@ async def send_initial_progress(
|
|||||||
tracker: ProgressTracker,
|
tracker: ProgressTracker,
|
||||||
resume_formatter: Callable[[ResumeToken], str] | None = None,
|
resume_formatter: Callable[[ResumeToken], str] | None = None,
|
||||||
context_line: str | None = None,
|
context_line: str | None = None,
|
||||||
thread_id: int | None = None,
|
thread_id: ThreadId | None = None,
|
||||||
) -> ProgressMessageState:
|
) -> ProgressMessageState:
|
||||||
progress_ref: MessageRef | None = None
|
progress_ref: MessageRef | None = None
|
||||||
last_rendered: RenderedMessage | None = None
|
last_rendered: RenderedMessage | None = None
|
||||||
@@ -358,7 +359,7 @@ async def send_result_message(
|
|||||||
edit_ref: MessageRef | None,
|
edit_ref: MessageRef | None,
|
||||||
replace_ref: MessageRef | None = None,
|
replace_ref: MessageRef | None = None,
|
||||||
delete_tag: str = "final",
|
delete_tag: str = "final",
|
||||||
thread_id: int | None = None,
|
thread_id: ThreadId | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
final_msg, edited = await _send_or_edit_message(
|
final_msg, edited = await _send_or_edit_message(
|
||||||
cfg.transport,
|
cfg.transport,
|
||||||
|
|||||||
@@ -8,16 +8,17 @@ import anyio
|
|||||||
|
|
||||||
from .context import RunContext
|
from .context import RunContext
|
||||||
from .model import ResumeToken
|
from .model import ResumeToken
|
||||||
|
from .transport import ChannelId, MessageId, ThreadId
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class ThreadJob:
|
class ThreadJob:
|
||||||
chat_id: int
|
chat_id: ChannelId
|
||||||
user_msg_id: int
|
user_msg_id: MessageId
|
||||||
text: str
|
text: str
|
||||||
resume_token: ResumeToken
|
resume_token: ResumeToken
|
||||||
context: RunContext | None = None
|
context: RunContext | None = None
|
||||||
thread_id: int | None = None
|
thread_id: ThreadId | None = None
|
||||||
|
|
||||||
|
|
||||||
RunJob = Callable[[ThreadJob], Awaitable[None]]
|
RunJob = Callable[[ThreadJob], Awaitable[None]]
|
||||||
@@ -65,12 +66,12 @@ class ThreadScheduler:
|
|||||||
|
|
||||||
async def enqueue_resume(
|
async def enqueue_resume(
|
||||||
self,
|
self,
|
||||||
chat_id: int,
|
chat_id: ChannelId,
|
||||||
user_msg_id: int,
|
user_msg_id: MessageId,
|
||||||
text: str,
|
text: str,
|
||||||
resume_token: ResumeToken,
|
resume_token: ResumeToken,
|
||||||
context: RunContext | None = None,
|
context: RunContext | None = None,
|
||||||
thread_id: int | None = None,
|
thread_id: ThreadId | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await self.enqueue(
|
await self.enqueue(
|
||||||
ThreadJob(
|
ThreadJob(
|
||||||
|
|||||||
@@ -185,7 +185,11 @@ class TelegramTransport:
|
|||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
notify = options.notify
|
notify = options.notify
|
||||||
message_thread_id = options.thread_id
|
message_thread_id = (
|
||||||
|
cast(int | None, options.thread_id)
|
||||||
|
if options.thread_id is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
reply_to_message_id = cast(
|
reply_to_message_id = cast(
|
||||||
int | None,
|
int | None,
|
||||||
@@ -219,10 +223,16 @@ class TelegramTransport:
|
|||||||
notify=notify,
|
notify=notify,
|
||||||
)
|
)
|
||||||
message_id = sent.message_id
|
message_id = sent.message_id
|
||||||
|
thread_id = (
|
||||||
|
sent.message_thread_id
|
||||||
|
if sent.message_thread_id is not None
|
||||||
|
else message_thread_id
|
||||||
|
)
|
||||||
return MessageRef(
|
return MessageRef(
|
||||||
channel_id=chat_id,
|
channel_id=chat_id,
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
raw=sent,
|
raw=sent,
|
||||||
|
thread_id=thread_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def edit(
|
async def edit(
|
||||||
@@ -261,10 +271,16 @@ class TelegramTransport:
|
|||||||
notify=notify,
|
notify=notify,
|
||||||
)
|
)
|
||||||
message_id = edited.message_id
|
message_id = edited.message_id
|
||||||
|
thread_id = (
|
||||||
|
edited.message_thread_id
|
||||||
|
if edited.message_thread_id is not None
|
||||||
|
else ref.thread_id
|
||||||
|
)
|
||||||
return MessageRef(
|
return MessageRef(
|
||||||
channel_id=chat_id,
|
channel_id=chat_id,
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
raw=edited,
|
raw=edited,
|
||||||
|
thread_id=thread_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def delete(self, *, ref: MessageRef) -> bool:
|
async def delete(self, *, ref: MessageRef) -> bool:
|
||||||
|
|||||||
@@ -1178,11 +1178,15 @@ class _CaptureTransport:
|
|||||||
message: RenderedMessage,
|
message: RenderedMessage,
|
||||||
options: SendOptions | None = None,
|
options: SendOptions | None = None,
|
||||||
) -> MessageRef:
|
) -> MessageRef:
|
||||||
_ = options
|
thread_id = options.thread_id if options is not None else None
|
||||||
ref = MessageRef(channel_id=channel_id, message_id=self._next_id)
|
ref = MessageRef(channel_id=channel_id, message_id=self._next_id)
|
||||||
self._next_id += 1
|
self._next_id += 1
|
||||||
self.last_message = message
|
self.last_message = message
|
||||||
return ref
|
return MessageRef(
|
||||||
|
channel_id=ref.channel_id,
|
||||||
|
message_id=ref.message_id,
|
||||||
|
thread_id=thread_id,
|
||||||
|
)
|
||||||
|
|
||||||
async def edit(
|
async def edit(
|
||||||
self, *, ref: MessageRef, message: RenderedMessage, wait: bool = True
|
self, *, ref: MessageRef, message: RenderedMessage, wait: bool = True
|
||||||
@@ -1218,7 +1222,11 @@ class _TelegramCommandExecutor(CommandExecutor):
|
|||||||
self._chat_id = chat_id
|
self._chat_id = chat_id
|
||||||
self._user_msg_id = user_msg_id
|
self._user_msg_id = user_msg_id
|
||||||
self._thread_id = thread_id
|
self._thread_id = thread_id
|
||||||
self._reply_ref = MessageRef(channel_id=chat_id, message_id=user_msg_id)
|
self._reply_ref = MessageRef(
|
||||||
|
channel_id=chat_id,
|
||||||
|
message_id=user_msg_id,
|
||||||
|
thread_id=thread_id,
|
||||||
|
)
|
||||||
|
|
||||||
def _apply_default_context(self, request: RunRequest) -> RunRequest:
|
def _apply_default_context(self, request: RunRequest) -> RunRequest:
|
||||||
if request.context is not None:
|
if request.context is not None:
|
||||||
@@ -1336,7 +1344,11 @@ async def _dispatch_command(
|
|||||||
chat_id = msg.chat_id
|
chat_id = msg.chat_id
|
||||||
user_msg_id = msg.message_id
|
user_msg_id = msg.message_id
|
||||||
reply_ref = (
|
reply_ref = (
|
||||||
MessageRef(channel_id=chat_id, message_id=msg.reply_to_message_id)
|
MessageRef(
|
||||||
|
channel_id=chat_id,
|
||||||
|
message_id=msg.reply_to_message_id,
|
||||||
|
thread_id=msg.thread_id,
|
||||||
|
)
|
||||||
if msg.reply_to_message_id is not None
|
if msg.reply_to_message_id is not None
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
@@ -1349,7 +1361,9 @@ async def _dispatch_command(
|
|||||||
user_msg_id=user_msg_id,
|
user_msg_id=user_msg_id,
|
||||||
thread_id=msg.thread_id,
|
thread_id=msg.thread_id,
|
||||||
)
|
)
|
||||||
message_ref = MessageRef(channel_id=chat_id, message_id=user_msg_id)
|
message_ref = MessageRef(
|
||||||
|
channel_id=chat_id, message_id=user_msg_id, thread_id=msg.thread_id
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
backend = get_command(command_id, allowlist=allowlist, required=False)
|
backend = get_command(command_id, allowlist=allowlist, required=False)
|
||||||
except ConfigError as exc:
|
except ConfigError as exc:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
from collections.abc import AsyncIterator, Awaitable, Callable
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
from anyio.abc import TaskGroup
|
from anyio.abc import TaskGroup
|
||||||
@@ -417,12 +418,12 @@ async def run_main_loop(
|
|||||||
|
|
||||||
async def run_thread_job(job: ThreadJob) -> None:
|
async def run_thread_job(job: ThreadJob) -> None:
|
||||||
await run_job(
|
await run_job(
|
||||||
job.chat_id,
|
cast(int, job.chat_id),
|
||||||
job.user_msg_id,
|
cast(int, job.user_msg_id),
|
||||||
job.text,
|
job.text,
|
||||||
job.resume_token,
|
job.resume_token,
|
||||||
job.context,
|
job.context,
|
||||||
job.thread_id,
|
cast(int | None, job.thread_id),
|
||||||
None,
|
None,
|
||||||
scheduler.note_thread_known,
|
scheduler.note_thread_known,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from typing import Any, Protocol, TypeAlias
|
|||||||
|
|
||||||
ChannelId: TypeAlias = int | str
|
ChannelId: TypeAlias = int | str
|
||||||
MessageId: TypeAlias = int | str
|
MessageId: TypeAlias = int | str
|
||||||
|
ThreadId: TypeAlias = int | str
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -12,6 +13,7 @@ class MessageRef:
|
|||||||
channel_id: ChannelId
|
channel_id: ChannelId
|
||||||
message_id: MessageId
|
message_id: MessageId
|
||||||
raw: Any | None = field(default=None, compare=False, hash=False)
|
raw: Any | None = field(default=None, compare=False, hash=False)
|
||||||
|
thread_id: ThreadId | None = field(default=None, compare=False, hash=False)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -25,7 +27,7 @@ class SendOptions:
|
|||||||
reply_to: MessageRef | None = None
|
reply_to: MessageRef | None = None
|
||||||
notify: bool = True
|
notify: bool = True
|
||||||
replace: MessageRef | None = None
|
replace: MessageRef | None = None
|
||||||
thread_id: int | None = None
|
thread_id: ThreadId | None = None
|
||||||
|
|
||||||
|
|
||||||
class Transport(Protocol):
|
class Transport(Protocol):
|
||||||
|
|||||||
Reference in New Issue
Block a user