Skip to content

Commit c891776

Browse files
authored
Merge pull request #21 from John-Lin/migrate-agent-core-build-agent
feat: migrate to agent-core build_agent API with provider config
2 parents 5501b5e + dab68cb commit c891776

14 files changed

Lines changed: 519 additions & 324 deletions

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ wheels/
99
# Virtual environments
1010
.venv
1111
.envrc
12-
servers_config.json
12+
agent_config.json
1313
instructions.md
1414
access.json
15+
access.json.lock
1516
.access.pending.json
17+
.coverage
18+
logs/
19+
data/
20+
claude-home/
21+
.claude/
22+
.private-journal/

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ COPY --from=node:22-slim /usr/local/lib/node_modules /usr/local/lib/node_modules
55
RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
66
&& ln -s /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
77

8+
RUN apt-get update && apt-get install -y --no-install-recommends curl git jq \
9+
&& rm -rf /var/lib/apt/lists/*
10+
811
COPY . /app
912
WORKDIR /app
1013

README.md

Lines changed: 152 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
# agentic-telegram-bot
22

3-
A simple Telegram bot that uses the [OpenAI Agents SDK](https://github.com/openai/openai-agents-python) to interact with [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers.
3+
A Telegram bot powered by [agent-core](https://github.com/John-Lin/agent-core) that interacts with [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers. Supports two interchangeable providers:
44

5-
See also: [agentic-slackbot](https://github.com/John-Lin/agentic-slackbot) — a similar demo bot for Slack.
5+
- **OpenAI** (default) — via [OpenAI Agents SDK](https://github.com/openai/openai-agents-python); works with OpenAI and Azure OpenAI v1.
6+
- **Claude** — via [claude-agent-sdk](https://pypi.org/project/claude-agent-sdk/).
7+
8+
Switch providers by setting `"provider"` in `agent_config.json`.
9+
10+
See also: [agentic-slackbot](https://github.com/John-Lin/agentic-slackbot) and [agentic-discord-bot](https://github.com/John-Lin/agentic-discord-bot) — similar bots for Slack and Discord.
611

712
## Features
813

914
- Private chat and group chat support
1015
- Configurable DM policy (pairing / allowlist / disabled)
11-
- Connects to any MCP server via `servers_config.json`
12-
- Supports OpenAI, Azure OpenAI endpoints
1316
- Per-conversation history with automatic truncation
1417
- Group reply chain — after `@mention`, anyone can continue by replying
15-
- Optional local shell via `ShellTool`, controlled by `SHELL_ENABLED` and `SHELL_SKILLS_DIR`
18+
- Connects to any MCP server via `agent_config.json`
19+
- Optional local shell: `ShellTool` (OpenAI, via `provider.shell`) or `Bash`/`Write`/`Edit`/… (Claude, via `provider.allowedTools`). Read-only built-ins (`Read`, `Glob`, `Grep`) are always on for Claude.
20+
- Supports OpenAI, Azure OpenAI v1, and Anthropic Claude
1621

1722
## Install Dependencies
1823

@@ -28,87 +33,129 @@ uv sync
2833
- Use the command `/setprivacy` in the BotFather chat.
2934
- Select your bot.
3035
- Choose "Disable" to allow the bot to receive all messages in groups.
31-
4. Set the bot token and username in the `.envrc` or `.env` file.
36+
4. Set the bot token and username in the `.env` file.
3237

3338
## Environment Variables
3439

35-
Create a `.envrc` or `.env` file in the root directory:
40+
Create a `.env` file in the root directory. Set the key(s) for the provider you plan to use:
3641

3742
```
3843
# Telegram bot
39-
export BOT_USERNAME="@your_bot_username"
40-
export TELEGRAM_BOT_TOKEN=""
44+
BOT_USERNAME="@your_bot_username"
45+
TELEGRAM_BOT_TOKEN=""
4146
42-
# OpenAI API
43-
export OPENAI_API_KEY=""
47+
# OpenAI provider (default)
48+
OPENAI_API_KEY=""
4449
45-
# Local shell (disabled by default)
46-
# export SHELL_ENABLED=1
47-
# export SHELL_SKILLS_DIR="./skills" # optional; mount skills alongside the shell
50+
# Claude provider
51+
# ANTHROPIC_API_KEY=""
4852
49-
# Optional verbose OpenAI Agents SDK logging
50-
# export AGENT_VERBOSE_LOG=1
51-
```
53+
# Optional: override SQLite path for session storage (in-memory by default)
54+
# SESSION_DB_PATH="./sessions.db"
5255
53-
## Agent Instructions
56+
# Optional: override the path to the instructions file (default ./instructions.md)
57+
# AGENT_INSTRUCTIONS_PATH="./instructions.md"
5458
55-
The bot loads its system prompt from `instructions.md` in the project root.
56-
If the file is missing, the bot fails fast at startup.
59+
# Optional verbose OpenAI Agents SDK logging (OpenAI only)
60+
# AGENT_VERBOSE_LOG=1
61+
```
5762

58-
You can copy `instructions.md.example` as a starting point:
63+
If you are using Azure OpenAI (v1 API):
5964

60-
```bash
61-
cp instructions.md.example instructions.md
65+
```
66+
BOT_USERNAME="@your_bot_username"
67+
TELEGRAM_BOT_TOKEN=""
68+
OPENAI_API_KEY=""
69+
OPENAI_BASE_URL="https://<resource-name>.openai.azure.com/openai/v1/"
6270
```
6371

64-
If you are using Azure OpenAI (v1 API), set these instead:
72+
## Agent Instructions
6573

74+
Create an `instructions.md` file in the project root with the agent system prompt:
75+
76+
```markdown
77+
You are a helpful financial assistant. Help users look up stock data,
78+
news, and market information. Always include ticker symbols.
79+
Respond in the user's language. Keep responses concise.
6680
```
67-
export OPENAI_API_KEY=""
68-
export OPENAI_BASE_URL="https://<resource-name>.openai.azure.com/openai/v1/"
69-
```
7081

71-
## MCP Server Configuration (Optional)
82+
An example is provided in `instructions.md.example`. The bot will fail to start if this file is missing.
83+
84+
## Provider & MCP Server Configuration (Optional)
85+
86+
Create an `agent_config.json` to choose a provider and connect MCP servers. If the file is absent, the bot starts with the default OpenAI provider and no tools.
87+
88+
`provider` is a tagged union keyed by `type` (`"openai"` or `"anthropic"`). `mcp` uses an opencode-style schema keyed by server name, with `type: "local" | "remote"`.
7289

73-
Create a `servers_config.json` file to add your MCP servers. If this file is not provided, the bot starts with no MCP servers configured.
90+
### OpenAI provider (default)
7491

7592
```json
7693
{
77-
"model": "gpt-5.4",
78-
"mcpServers": {
94+
"provider": {
95+
"type": "openai",
96+
"model": "gpt-5.4",
97+
"apiType": "responses",
98+
"historyTurns": 10
99+
},
100+
"mcp": {
79101
"my-server": {
80-
"command": "uvx",
81-
"args": ["my-mcp-server"]
102+
"type": "local",
103+
"command": ["uvx", "my-mcp-server"]
82104
}
83105
}
84106
}
85107
```
86108

87-
`model` is optional and defaults to `gpt-5.4`. Each MCP server also accepts `timeout` (seconds, default `30.0`) and `enabled` (default `true`).
109+
All `provider` fields are optional (`model` defaults to `gpt-5.4`, `apiType` to `"responses"`, `historyTurns` to `10`). Each MCP entry also accepts `timeout` (seconds, default `30.0`) and `enabled` (default `true`).
88110

89-
For HTTP-based MCP servers (Streamable HTTP), use `url`:
111+
### Claude provider
90112

91113
```json
92114
{
93-
"mcpServers": {
115+
"provider": {
116+
"type": "anthropic",
117+
"model": "claude-sonnet-4-6",
118+
"allowedTools": ["WebFetch"]
119+
},
120+
"mcp": {
121+
"my-stdio": {
122+
"type": "local",
123+
"command": ["python", "-m", "srv"],
124+
"environment": {"FOO": "bar"}
125+
},
126+
"my-http": {
127+
"type": "remote",
128+
"url": "https://example.com/mcp",
129+
"headers": {"Authorization": "Bearer x"}
130+
}
131+
}
132+
}
133+
```
134+
135+
Requires `ANTHROPIC_API_KEY`. Read-only built-ins (`Read`, `Glob`, `Grep`) are always on; `allowedTools` extends that set with any tool that can mutate files or run commands (`Bash`, `Write`, `Edit`, `WebFetch`, …). Tool names are case-sensitive and validated by the SDK — an unrecognized name is silently dropped. Billing/rate-limit/`error_max_turns` errors are surfaced to the chat as a readable message via `AgentError`.
136+
137+
### Remote (HTTP) MCP servers
138+
139+
```json
140+
{
141+
"mcp": {
94142
"my-server": {
143+
"type": "remote",
95144
"url": "https://mcp.example.com/mcp",
96-
"headers": {
97-
"Accept": "application/json, text/event-stream"
98-
}
145+
"headers": {"Accept": "application/json, text/event-stream"}
99146
}
100147
}
101148
}
102149
```
103150

104-
For local MCP servers, use `uv --directory`:
151+
### Local MCP servers (via `uv --directory`)
105152

106153
```json
107154
{
108-
"mcpServers": {
155+
"mcp": {
109156
"my-server": {
110-
"command": "uv",
111-
"args": ["--directory", "/path/to/my-server", "run", "my-entrypoint"]
157+
"type": "local",
158+
"command": ["uv", "--directory", "/path/to/my-server", "run", "my-entrypoint"]
112159
}
113160
}
114161
}
@@ -178,26 +225,48 @@ uv run bot access group remove <GROUP_ID>
178225

179226
Group members do not need to pair individually — access is controlled at the group level.
180227

228+
## Conversation History
229+
230+
Each chat maintains its own conversation history. Replying to the bot's message continues the same conversation via the group reply chain.
231+
232+
OpenAI history length is controlled by `provider.historyTurns` in `agent_config.json` (default `10`). Claude history is managed on disk by `claude-agent-sdk` and resumed across restarts via a `chat_id -> session_id` mapping in SQLite (`SESSION_DB_PATH`).
233+
181234
## Local Shell (Optional)
182235

183-
The bot can expose a local `ShellTool`. This is **disabled by default**. Enable it with:
236+
Local shell tools are **disabled by default** and are configured in `agent_config.json` per provider.
184237

185-
```
186-
export SHELL_ENABLED=1
238+
### OpenAI — `provider.shell`
239+
240+
```json
241+
{
242+
"provider": {
243+
"type": "openai",
244+
"shell": {
245+
"enabled": true,
246+
"skillsDir": "./skills"
247+
}
248+
}
249+
}
187250
```
188251

189-
With just `SHELL_ENABLED=1`, the agent gets bare local shell access with no pre-defined skills.
252+
`provider.shell.enabled` must be a bool (strings are rejected). `provider.shell.skillsDir` is optional and mounts a skills directory alongside the `ShellTool`.
190253

191-
### Shell Skills (Optional)
254+
### Claude — `provider.allowedTools`
192255

193-
You can optionally mount a skills directory alongside the shell. Each immediate subdirectory containing a `SKILL.md` file is registered as a skill and exposed to the agent as a hint (skills are advisory metadata — they do **not** sandbox command execution).
256+
Read-only built-ins (`Read`, `Glob`, `Grep`) are always on. Add mutating or exec-capable tools explicitly:
194257

258+
```json
259+
{
260+
"provider": {
261+
"type": "anthropic",
262+
"allowedTools": ["Bash", "Write", "Edit", "WebFetch"]
263+
}
264+
}
195265
```
196-
export SHELL_ENABLED=1
197-
export SHELL_SKILLS_DIR="./skills"
198-
```
199266

200-
`SHELL_SKILLS_DIR` is ignored unless `SHELL_ENABLED` is set. If the directory is missing or contains no valid skills, the bot falls back to a bare shell and logs a warning.
267+
### Shell Skills (OpenAI only)
268+
269+
Each immediate subdirectory of `skillsDir` containing a `SKILL.md` file is registered as a skill and exposed to the agent as a hint (skills are advisory metadata — they do **not** sandbox command execution). If the directory is missing or contains no valid skills, the bot falls back to a bare shell and logs a warning.
201270

202271
The `SKILL.md` file should have YAML frontmatter with `name` and `description` fields:
203272

@@ -215,26 +284,52 @@ Detailed instructions for the agent...
215284
```bash
216285
docker build -t agentic-telegram-bot .
217286

287+
# OpenAI provider
218288
docker run -d \
219-
--name telegent \
289+
--name agentic-telegram-bot \
220290
-e BOT_USERNAME="@your_bot_username" \
221291
-e TELEGRAM_BOT_TOKEN="" \
222292
-e OPENAI_API_KEY="" \
223293
-v /path/to/instructions.md:/app/instructions.md \
224294
-v /path/to/access.json:/app/access.json \
225295
agentic-telegram-bot
296+
297+
# Claude provider (agent_config.json must set "provider": {"type": "anthropic"})
298+
docker run -d \
299+
--name agentic-telegram-bot \
300+
-e BOT_USERNAME="@your_bot_username" \
301+
-e TELEGRAM_BOT_TOKEN="" \
302+
-e ANTHROPIC_API_KEY="" \
303+
-v /path/to/instructions.md:/app/instructions.md \
304+
-v /path/to/agent_config.json:/app/agent_config.json \
305+
-v /path/to/access.json:/app/access.json \
306+
agentic-telegram-bot
226307
```
227308

228-
To use MCP servers, mount your config file:
309+
To use MCP servers with OpenAI, also mount the config:
229310

230311
```bash
231312
docker run -d \
232-
--name telegent \
313+
--name agentic-telegram-bot \
233314
-e BOT_USERNAME="@your_bot_username" \
234315
-e TELEGRAM_BOT_TOKEN="" \
235316
-e OPENAI_API_KEY="" \
236317
-v /path/to/instructions.md:/app/instructions.md \
237-
-v /path/to/servers_config.json:/app/servers_config.json \
318+
-v /path/to/agent_config.json:/app/agent_config.json \
238319
-v /path/to/access.json:/app/access.json \
239320
agentic-telegram-bot
240321
```
322+
323+
### Docker Compose
324+
325+
A `docker-compose.yml` and `run.sh` are provided for convenience. Both mount `instructions.md`, `agent_config.json`, `access.json`, persist sessions to `./data`, and mount `./skills` as the agent skills directory.
326+
327+
```bash
328+
docker compose up -d --build
329+
```
330+
331+
Or run the container directly:
332+
333+
```bash
334+
./run.sh
335+
```

agent_config.example.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"provider": {
3+
"type": "openai"
4+
},
5+
"mcp": {
6+
"yfmcp": {
7+
"type": "local",
8+
"command": ["uvx", "yfmcp"]
9+
}
10+
}
11+
}

app.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import logging
77
import sys
88

9-
from agent_core import OpenAIAgent
9+
from agent_core import build_agent
1010
from agents import enable_verbose_stdout_logging
1111

1212
from bot.auth import add_group
@@ -35,13 +35,13 @@ async def start_bot() -> None:
3535
_configure_logging()
3636
config = Configuration()
3737

38-
server_config = config.load_config("servers_config.json")
39-
openai_agent = OpenAIAgent.from_dict("Telegram Bot Agent", server_config)
38+
agent_config = config.load_config("agent_config.json")
39+
agent = build_agent("Telegram Bot Agent", agent_config)
4040

4141
tg_bot = TelegramMCPBot(
4242
config.telegram_bot_token,
4343
config.bot_username,
44-
openai_agent,
44+
agent,
4545
)
4646

4747
try:
@@ -51,10 +51,10 @@ async def start_bot() -> None:
5151
except (KeyboardInterrupt, asyncio.CancelledError):
5252
logging.info("Shutting down...")
5353
except Exception as e:
54-
logging.error(f"Error: {e}")
54+
logging.error(f"Error: {e}", exc_info=True)
5555
finally:
5656
await tg_bot.cleanup()
57-
await openai_agent.cleanup()
57+
await agent.cleanup()
5858

5959

6060
def cmd_pair(args: argparse.Namespace) -> None:

0 commit comments

Comments
 (0)