docs: update readme and add developing

This commit is contained in:
banteg
2025-12-29 14:15:13 +04:00
parent fc9f33c24c
commit a67cc010bb
2 changed files with 213 additions and 33 deletions
+135
View File
@@ -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: <uuid>` 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 <session_id> -`
- 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 |
+78 -33
View File
@@ -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: <uuid>`
line.
## Features
## Install
- **Stateless Resume**: No database required—sessions are resumed via `resume: <uuid>` 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: <uuid>`), 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