refactor: simplify runtime, config, and telegram (#85)

This commit is contained in:
banteg
2026-01-11 14:48:39 +04:00
committed by GitHub
parent 2380b3e5e9
commit 194cc02bba
42 changed files with 3204 additions and 3717 deletions
+52 -47
View File
@@ -7,23 +7,26 @@ import pytest
from takopi import commands, plugins
import takopi.telegram.bridge as bridge
import takopi.telegram.loop as telegram_loop
import takopi.telegram.commands as telegram_commands
import takopi.telegram.topics as telegram_topics
from takopi.directives import parse_directives
from takopi.telegram.bridge import (
TelegramBridgeConfig,
TelegramFilesConfig,
TelegramPresenter,
TelegramTransport,
_build_bot_commands,
_handle_callback_cancel,
_handle_cancel,
_is_cancel_command,
_send_with_resume,
build_bot_commands,
handle_callback_cancel,
handle_cancel,
is_cancel_command,
send_with_resume,
run_main_loop,
)
from takopi.telegram.client import BotClient
from takopi.telegram.topic_state import TopicStateStore, resolve_state_path
from takopi.context import RunContext
from takopi.config import ProjectConfig, ProjectsConfig, empty_projects_config
from takopi.config import ProjectConfig, ProjectsConfig
from takopi.runner_bridge import ExecBridgeConfig, RunningTask
from takopi.markdown import MarkdownPresenter
from takopi.model import EngineId, ResumeToken
@@ -42,6 +45,10 @@ from tests.plugin_fixtures import FakeEntryPoint, install_entrypoints
CODEX_ENGINE = EngineId("codex")
def _empty_projects() -> ProjectsConfig:
return ProjectsConfig(projects={}, default_project=None)
def _make_router(runner) -> AutoRouter:
return AutoRouter(
entries=[RunnerEntry(engine=runner.engine, runner=runner)],
@@ -288,7 +295,7 @@ def _make_cfg(
)
runtime = TransportRuntime(
router=_make_router(runner),
projects=empty_projects_config(),
projects=_empty_projects(),
)
return TelegramBridgeConfig(
bot=_FakeBot(),
@@ -303,7 +310,7 @@ def test_parse_directives_inline_engine() -> None:
directives = parse_directives(
"/claude do it",
engine_ids=("codex", "claude"),
projects=empty_projects_config(),
projects=_empty_projects(),
)
assert directives.engine == "claude"
assert directives.prompt == "do it"
@@ -313,7 +320,7 @@ def test_parse_directives_newline() -> None:
directives = parse_directives(
"/codex\nhello",
engine_ids=("codex", "claude"),
projects=empty_projects_config(),
projects=_empty_projects(),
)
assert directives.engine == "codex"
assert directives.prompt == "hello"
@@ -323,7 +330,7 @@ def test_parse_directives_ignores_unknown() -> None:
directives = parse_directives(
"/unknown hi",
engine_ids=("codex", "claude"),
projects=empty_projects_config(),
projects=_empty_projects(),
)
assert directives.engine is None
assert directives.prompt == "/unknown hi"
@@ -333,7 +340,7 @@ def test_parse_directives_bot_suffix() -> None:
directives = parse_directives(
"/claude@bunny_agent_bot hi",
engine_ids=("claude",),
projects=empty_projects_config(),
projects=_empty_projects(),
)
assert directives.engine == "claude"
assert directives.prompt == "hi"
@@ -343,7 +350,7 @@ def test_parse_directives_only_first_non_empty_line() -> None:
directives = parse_directives(
"hello\n/claude hi",
engine_ids=("codex", "claude"),
projects=empty_projects_config(),
projects=_empty_projects(),
)
assert directives.engine is None
assert directives.prompt == "hello\n/claude hi"
@@ -355,9 +362,9 @@ def test_build_bot_commands_includes_cancel_and_engine() -> None:
)
runtime = TransportRuntime(
router=_make_router(runner),
projects=empty_projects_config(),
projects=_empty_projects(),
)
commands = _build_bot_commands(runtime)
commands = build_bot_commands(runtime)
assert {"command": "cancel", "description": "cancel run"} in commands
assert {"command": "file", "description": "upload or fetch files"} in commands
@@ -386,7 +393,7 @@ def test_build_bot_commands_includes_projects() -> None:
)
runtime = TransportRuntime(router=router, projects=projects)
commands = _build_bot_commands(runtime)
commands = build_bot_commands(runtime)
assert any(cmd["command"] == "good" for cmd in commands)
assert not any(cmd["command"] == "bad-name" for cmd in commands)
@@ -413,10 +420,10 @@ def test_build_bot_commands_includes_command_plugins(monkeypatch) -> None:
runner = ScriptRunner([Return(answer="ok")], engine=CODEX_ENGINE)
runtime = TransportRuntime(
router=_make_router(runner),
projects=empty_projects_config(),
projects=_empty_projects(),
)
commands_list = _build_bot_commands(runtime)
commands_list = build_bot_commands(runtime)
assert {"command": "pingcmd", "description": "ping command"} in commands_list
@@ -439,7 +446,7 @@ def test_build_bot_commands_caps_total() -> None:
)
runtime = TransportRuntime(router=router, projects=projects)
commands = _build_bot_commands(runtime)
commands = build_bot_commands(runtime)
assert len(commands) == 100
assert any(cmd["command"] == "codex" for cmd in commands)
@@ -667,7 +674,7 @@ async def test_handle_cancel_without_reply_prompts_user() -> None:
)
running_tasks: dict = {}
await _handle_cancel(cfg, msg, running_tasks)
await handle_cancel(cfg, msg, running_tasks)
assert len(transport.send_calls) == 1
assert "reply to the progress message" in transport.send_calls[0]["message"].text
@@ -688,7 +695,7 @@ async def test_handle_cancel_with_no_progress_message_says_nothing_running() ->
)
running_tasks: dict = {}
await _handle_cancel(cfg, msg, running_tasks)
await handle_cancel(cfg, msg, running_tasks)
assert len(transport.send_calls) == 1
assert "nothing is currently running" in transport.send_calls[0]["message"].text
@@ -710,7 +717,7 @@ async def test_handle_cancel_with_finished_task_says_nothing_running() -> None:
)
running_tasks: dict = {}
await _handle_cancel(cfg, msg, running_tasks)
await handle_cancel(cfg, msg, running_tasks)
assert len(transport.send_calls) == 1
assert "nothing is currently running" in transport.send_calls[0]["message"].text
@@ -733,7 +740,7 @@ async def test_handle_cancel_cancels_running_task() -> None:
running_task = RunningTask()
running_tasks = {MessageRef(channel_id=123, message_id=progress_id): running_task}
await _handle_cancel(cfg, msg, running_tasks)
await handle_cancel(cfg, msg, running_tasks)
assert running_task.cancel_requested.is_set() is True
assert len(transport.send_calls) == 0 # No error message sent
@@ -759,7 +766,7 @@ async def test_handle_cancel_only_cancels_matching_progress_message() -> None:
MessageRef(channel_id=123, message_id=2): task_second,
}
await _handle_cancel(cfg, msg, running_tasks)
await handle_cancel(cfg, msg, running_tasks)
assert task_first.cancel_requested.is_set() is True
assert task_second.cancel_requested.is_set() is False
@@ -824,7 +831,9 @@ async def test_handle_file_put_writes_file(tmp_path: Path) -> None:
),
)
await bridge._handle_file_put(cfg, msg, "/proj uploads/hello.txt", None, None)
await telegram_commands._handle_file_put(
cfg, msg, "/proj uploads/hello.txt", None, None
)
target = tmp_path / "uploads" / "hello.txt"
assert target.read_bytes() == payload
@@ -883,7 +892,7 @@ async def test_handle_file_get_sends_document_for_allowed_user(
chat_type="supergroup",
)
await bridge._handle_file_get(cfg, msg, "/proj hello.txt", None, None)
await telegram_commands._handle_file_get(cfg, msg, "/proj hello.txt", None, None)
assert bot.document_calls
assert bot.document_calls[0]["filename"] == "hello.txt"
@@ -906,7 +915,7 @@ async def test_handle_callback_cancel_cancels_running_task() -> None:
sender_id=123,
)
await _handle_callback_cancel(cfg, query, running_tasks)
await handle_callback_cancel(cfg, query, running_tasks)
assert running_task.cancel_requested.is_set() is True
assert len(transport.send_calls) == 0
@@ -928,7 +937,7 @@ async def test_handle_callback_cancel_without_task_acknowledges() -> None:
sender_id=123,
)
await _handle_callback_cancel(cfg, query, {})
await handle_callback_cancel(cfg, query, {})
assert len(transport.send_calls) == 0
bot = cast(_FakeBot, cfg.bot)
@@ -937,9 +946,9 @@ async def test_handle_callback_cancel_without_task_acknowledges() -> None:
def test_cancel_command_accepts_extra_text() -> None:
assert _is_cancel_command("/cancel now") is True
assert _is_cancel_command("/cancel@takopi please") is True
assert _is_cancel_command("/cancelled") is False
assert is_cancel_command("/cancel now") is True
assert is_cancel_command("/cancel@takopi please") is True
assert is_cancel_command("/cancelled") is False
def test_resolve_message_accepts_backticked_ctx_line() -> None:
@@ -971,24 +980,21 @@ def test_topic_title_matches_command_syntax() -> None:
transport = _FakeTransport()
cfg = _make_cfg(transport)
title = bridge._topic_title(
cfg=cfg,
title = telegram_topics._topic_title(
runtime=cfg.runtime,
context=RunContext(project="takopi", branch="master"),
)
assert title == "takopi @master"
title = bridge._topic_title(
cfg=cfg,
title = telegram_topics._topic_title(
runtime=cfg.runtime,
context=RunContext(project="takopi", branch=None),
)
assert title == "takopi"
title = bridge._topic_title(
cfg=cfg,
title = telegram_topics._topic_title(
runtime=cfg.runtime,
context=RunContext(project=None, branch="main"),
)
@@ -1006,8 +1012,7 @@ def test_topic_title_projects_scope_includes_project() -> None:
),
)
title = bridge._topic_title(
cfg=cfg,
title = telegram_topics._topic_title(
runtime=cfg.runtime,
context=RunContext(project="takopi", branch="master"),
)
@@ -1028,7 +1033,7 @@ async def test_maybe_rename_topic_updates_title(tmp_path: Path) -> None:
topic_title="takopi @old",
)
await bridge._maybe_rename_topic(
await telegram_topics._maybe_rename_topic(
cfg,
store,
chat_id=123,
@@ -1058,7 +1063,7 @@ async def test_maybe_rename_topic_skips_when_title_matches(tmp_path: Path) -> No
)
snapshot = await store.get_thread(123, 77)
await bridge._maybe_rename_topic(
await telegram_topics._maybe_rename_topic(
cfg,
store,
chat_id=123,
@@ -1096,7 +1101,7 @@ async def test_send_with_resume_waits_for_token() -> None:
async with anyio.create_task_group() as tg:
tg.start_soon(trigger_resume)
await _send_with_resume(
await send_with_resume(
cfg,
enqueue,
running_task,
@@ -1138,7 +1143,7 @@ async def test_send_with_resume_reports_when_missing() -> None:
running_task = RunningTask()
running_task.done.set()
await _send_with_resume(
await send_with_resume(
cfg,
enqueue,
running_task,
@@ -1175,7 +1180,7 @@ async def test_run_main_loop_routes_reply_to_running_resume() -> None:
)
runtime = TransportRuntime(
router=_make_router(runner),
projects=empty_projects_config(),
projects=_empty_projects(),
)
cfg = TelegramBridgeConfig(
bot=bot,
@@ -1311,7 +1316,7 @@ async def test_run_main_loop_replies_in_same_thread() -> None:
)
runtime = TransportRuntime(
router=_make_router(runner),
projects=empty_projects_config(),
projects=_empty_projects(),
)
cfg = TelegramBridgeConfig(
bot=bot,
@@ -1489,7 +1494,7 @@ async def test_run_main_loop_handles_command_plugins(monkeypatch) -> None:
)
runtime = TransportRuntime(
router=_make_router(runner),
projects=empty_projects_config(),
projects=_empty_projects(),
)
cfg = TelegramBridgeConfig(
bot=bot,
@@ -1714,7 +1719,7 @@ async def test_run_main_loop_refreshes_command_ids(monkeypatch) -> None:
return []
return ["late_cmd"]
monkeypatch.setattr(bridge, "list_command_ids", _list_command_ids)
monkeypatch.setattr(telegram_loop, "list_command_ids", _list_command_ids)
transport = _FakeTransport()
bot = _FakeBot()
@@ -1726,7 +1731,7 @@ async def test_run_main_loop_refreshes_command_ids(monkeypatch) -> None:
)
runtime = TransportRuntime(
router=_make_router(runner),
projects=empty_projects_config(),
projects=_empty_projects(),
)
cfg = TelegramBridgeConfig(
bot=bot,