From a67cc010bb1bbe728804e6200a5ba64465f40106 Mon Sep 17 00:00:00 2001 From: banteg <4562643+banteg@users.noreply.github.com> Date: Mon, 29 Dec 2025 14:15:13 +0400 Subject: [PATCH] docs: update readme and add developing --- codex_telegram_bridge/developing.md | 135 ++++++++++++++++++++++++++++ codex_telegram_bridge/readme.md | 111 ++++++++++++++++------- 2 files changed, 213 insertions(+), 33 deletions(-) create mode 100644 codex_telegram_bridge/developing.md diff --git a/codex_telegram_bridge/developing.md b/codex_telegram_bridge/developing.md new file mode 100644 index 0000000..5520a2a --- /dev/null +++ b/codex_telegram_bridge/developing.md @@ -0,0 +1,135 @@ +# codex_telegram_bridge — Developer Guide + +This document describes the internal architecture and module responsibilities. + +## Module Responsibilities + +### `exec_bridge.py` — Main Entry Point + +The orchestrator module containing: + +| Component | Purpose | +|-----------|---------| +| `main()` / `run()` | CLI entry point via Typer | +| `BridgeConfig` | Frozen dataclass holding runtime config | +| `CodexExecRunner` | Spawns `codex exec`, streams JSONL, handles cancellation | +| `_run_main_loop()` | Long-poll loop for Telegram updates | +| `_handle_message()` | Per-message handler with progress updates | +| `extract_session_id()` | Parses `resume: ` from message text | +| `truncate_for_telegram()` | Smart truncation preserving resume lines | + +**Key patterns:** +- Per-session locks prevent concurrent resumes to the same `session_id` +- `asyncio.Semaphore` limits overall concurrency (default: 16) +- Progress edits are throttled to ~2s intervals +- Subprocess stderr is drained to a bounded deque for error reporting + +### `telegram_client.py` — Telegram Bot API + +Minimal async client wrapping the Bot API: + +```python +class TelegramClient: + async def get_updates(...) # Long-polling + async def send_message(...) # With entities support + async def edit_message_text(...) + async def delete_message(...) +``` + +**Features:** +- Automatic retry on 429 (rate limit) with `retry_after` +- Raises `TelegramAPIError` with payload details on failure + +### `exec_render.py` — JSONL Event Rendering + +Transforms Codex JSONL events into human-readable text: + +| Function/Class | Purpose | +|----------------|---------| +| `format_event()` | Core dispatcher returning `(item_num, cli_lines, progress_line, prefix)` | +| `render_event_cli()` | Simplified wrapper for console logging | +| `ExecProgressRenderer` | Stateful renderer tracking recent actions for progress display | +| `format_elapsed()` | Formats seconds as `Xh Ym`, `Xm Ys`, or `Xs` | + +**Supported event types:** +- `thread.started`, `turn.started/completed/failed` +- `item.started/completed` for: `agent_message`, `reasoning`, `command_execution`, `mcp_tool_call`, `web_search`, `file_change`, `error` + +### `rendering.py` — Markdown to Telegram + +Converts Markdown to Telegram-compatible text with entities: + +```python +def render_markdown(md: str) -> tuple[str, list[dict[str, Any]]]: + # Uses markdown-it-py + sulguk for entity extraction + # Fixes: replaces bullets, removes invalid language fields +``` + +### `config.py` — Configuration Loading + +```python +def load_telegram_config(path=None) -> dict: + # Loads ~/.codex/telegram.toml (or custom path) +``` + +### `constants.py` — Shared Constants + +```python +TELEGRAM_HARD_LIMIT = 4096 # Max message length +TELEGRAM_CONFIG_PATH = ~/.codex/telegram.toml +``` + +### `logging.py` — Secure Logging Setup + +```python +class RedactTokenFilter: + # Redacts bot tokens from log output + +def setup_logging(*, debug: bool): + # Configures root logger with redaction filter +``` + +## Data Flow + +### New Message Flow + +``` +Telegram Update + ↓ +_run_main_loop() validates chat_id, extracts text + ↓ +_handle_message() spawned as task + ↓ +Send initial progress message (silent) + ↓ +CodexExecRunner.run_serialized() + ├── Spawns: codex exec --json ... - + ├── Streams JSONL from stdout + ├── Calls on_event() for each event + │ ↓ + │ ExecProgressRenderer.note_event() + │ ↓ + │ Throttled edit_message_text() + └── Returns (session_id, answer, saw_agent_message) + ↓ +render_final() with resume line + ↓ +Send/edit final message +``` + +### Resume Flow + +Same as above, but: +- `extract_session_id()` finds UUID in message or reply +- Command becomes: `codex exec --json resume -` +- Per-session lock serializes concurrent resumes + +## Error Handling + +| Scenario | Behavior | +|----------|----------| +| `codex exec` fails (rc≠0) | Shows stderr tail in error message | +| Telegram API error | Logged, edit skipped (progress continues) | +| Cancellation | Subprocess terminated, CancelledError re-raised | +| No agent_message | Final shows "error" status | + diff --git a/codex_telegram_bridge/readme.md b/codex_telegram_bridge/readme.md index 59d32a3..20c1604 100644 --- a/codex_telegram_bridge/readme.md +++ b/codex_telegram_bridge/readme.md @@ -1,54 +1,99 @@ -# Telegram Codex Bridge (Codex) +# Codex Telegram Bridge -Route Telegram replies back into Codex sessions using non-interactive -`codex exec` + `codex exec resume`. +A Telegram bot that bridges messages to [Codex](https://github.com/openai/codex) sessions using non-interactive `codex exec` and `codex exec resume`. -The bridge does not persist routes. It resumes only when the incoming -message (or the replied-to bot message) contains an explicit `resume: ` -line. +## Features -## Install +- **Stateless Resume**: No database required—sessions are resumed via `resume: ` lines embedded in messages +- **Progress Updates**: Real-time progress edits showing commands, tools, and elapsed time +- **Markdown Rendering**: Full Telegram-compatible markdown with entity support +- **Concurrency**: Handles multiple conversations with per-session serialization +- **Token Redaction**: Automatically redacts Telegram tokens from logs -1. Ensure `uv` is installed. -2. From this folder, run the entrypoints with `uv run` (uses `pyproject.toml` deps). -3. Put your Telegram credentials in `~/.codex/telegram.toml`. +## Quick Start -Example `~/.codex/telegram.toml`: +### Prerequisites + +- Python 3.12+ +- [uv](https://github.com/astral-sh/uv) package manager +- Codex CLI on PATH + +### Installation + +```bash +# Clone and enter the directory +cd codex-telegram-bridge + +# Run directly with uv (installs deps automatically) +uv run exec-bridge --help +``` + +### Configuration + +Create `~/.codex/telegram.toml`: ```toml -bot_token = "123:abc" +bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz" chat_id = 123456789 ``` -`chat_id` is used both for allowed messages and startup notifications. +| Key | Description | +|-----|-------------| +| `bot_token` | Telegram Bot API token from [@BotFather](https://t.me/BotFather) | +| `chat_id` | Allowed chat ID (also used for startup notifications) | -## Option 1: exec/resume - -Run: +### Running ```bash uv run exec-bridge ``` -Optional flags: +#### Options -- `--final-notify/--no-final-notify` (default notify via new message) -- `--debug/--no-debug` (default no debug logging; use `--debug | tee debug.log` to capture) -- `--cd PATH` (pass through to `codex --cd`) -- `--model NAME` (pass through to `codex exec`) +| Flag | Default | Description | +|------|---------|-------------| +| `--final-notify` / `--no-final-notify` | `--final-notify` | Send final response as new message (vs. edit) | +| `--debug` / `--no-debug` | `--no-debug` | Enable verbose logging | +| `--cd PATH` | cwd | Working directory for Codex | +| `--model NAME` | (codex default) | Model to use | -Progress updates are always sent silently. -Pending updates are always ignored on startup. -Progress updates are throttled to roughly every 2 seconds. +## Usage -To resume an existing thread without a database, reply with (or include) the session id shown at the end of the bot response: +### New Conversation -`resume: \`019b66fc-64c2-7a71-81cd-081c504cfeb2\`` +Send any message to your bot. The bridge will: -## Files -- `src/codex_telegram_bridge/constants.py`: limits and config path constants -- `src/codex_telegram_bridge/config.py`: config loading and chat-id parsing helpers -- `src/codex_telegram_bridge/exec_render.py`: renderers for codex exec JSONL events -- `src/codex_telegram_bridge/rendering.py`: markdown rendering -- `src/codex_telegram_bridge/telegram_client.py`: Telegram Bot API client -- `src/codex_telegram_bridge/exec_bridge.py`: codex exec + resume bridge +1. Send a silent progress message +2. Stream events from `codex exec` +3. Update progress every ~2 seconds +4. Send final response with session ID + +### Resume a Session + +Reply to a bot message (containing `resume: `), or include the resume line in your message: + +``` +resume: `019b66fc-64c2-7a71-81cd-081c504cfeb2` +``` + +## Behavior Notes + +- **Startup**: Pending updates are drained (ignored) on startup +- **Progress**: Updates are throttled to ~2s intervals, sent silently +- **Notifications**: Codex's built-in notify is disabled (bridge handles it) + +## Development + +See [`src/codex_telegram_bridge/developing.md`](src/codex_telegram_bridge/developing.md) for architecture details. + +```bash +# Run tests +uv run pytest + +# Run with debug logging +uv run exec-bridge --debug 2>&1 | tee debug.log +``` + +## License + +MIT