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
|
||||
`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
|
||||
|
||||
Reference in New Issue
Block a user