docs: update readme and add developing
This commit is contained in:
@@ -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 |
|
||||||
|
|
||||||
@@ -1,54 +1,99 @@
|
|||||||
# Telegram Codex Bridge (Codex)
|
# Codex Telegram Bridge
|
||||||
|
|
||||||
Route Telegram replies back into Codex sessions using non-interactive
|
A Telegram bot that bridges messages to [Codex](https://github.com/openai/codex) sessions using non-interactive `codex exec` and `codex exec resume`.
|
||||||
`codex exec` + `codex exec resume`.
|
|
||||||
|
|
||||||
The bridge does not persist routes. It resumes only when the incoming
|
## Features
|
||||||
message (or the replied-to bot message) contains an explicit `resume: <uuid>`
|
|
||||||
line.
|
|
||||||
|
|
||||||
## 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.
|
## Quick Start
|
||||||
2. From this folder, run the entrypoints with `uv run` (uses `pyproject.toml` deps).
|
|
||||||
3. Put your Telegram credentials in `~/.codex/telegram.toml`.
|
|
||||||
|
|
||||||
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
|
```toml
|
||||||
bot_token = "123:abc"
|
bot_token = "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"
|
||||||
chat_id = 123456789
|
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
|
### Running
|
||||||
|
|
||||||
Run:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run exec-bridge
|
uv run exec-bridge
|
||||||
```
|
```
|
||||||
|
|
||||||
Optional flags:
|
#### Options
|
||||||
|
|
||||||
- `--final-notify/--no-final-notify` (default notify via new message)
|
| Flag | Default | Description |
|
||||||
- `--debug/--no-debug` (default no debug logging; use `--debug | tee debug.log` to capture)
|
|------|---------|-------------|
|
||||||
- `--cd PATH` (pass through to `codex --cd`)
|
| `--final-notify` / `--no-final-notify` | `--final-notify` | Send final response as new message (vs. edit) |
|
||||||
- `--model NAME` (pass through to `codex exec`)
|
| `--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.
|
## Usage
|
||||||
Pending updates are always ignored on startup.
|
|
||||||
Progress updates are throttled to roughly every 2 seconds.
|
|
||||||
|
|
||||||
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
|
1. Send a silent progress message
|
||||||
- `src/codex_telegram_bridge/constants.py`: limits and config path constants
|
2. Stream events from `codex exec`
|
||||||
- `src/codex_telegram_bridge/config.py`: config loading and chat-id parsing helpers
|
3. Update progress every ~2 seconds
|
||||||
- `src/codex_telegram_bridge/exec_render.py`: renderers for codex exec JSONL events
|
4. Send final response with session ID
|
||||||
- `src/codex_telegram_bridge/rendering.py`: markdown rendering
|
|
||||||
- `src/codex_telegram_bridge/telegram_client.py`: Telegram Bot API client
|
### Resume a Session
|
||||||
- `src/codex_telegram_bridge/exec_bridge.py`: codex exec + resume bridge
|
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user