amangs commited on
Commit
c8b7138
·
1 Parent(s): 44da6b9

Switch from Claude Code/Codeman to native Avi Telegram Agent (@amangt_bot), OpenCode CLI, and DeepSeek Harness

Browse files
Dockerfile CHANGED
@@ -75,7 +75,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
75
  tmux \
76
  locales \
77
  && rm -rf /var/lib/apt/lists/* && \
78
- locale-gen en_US.UTF-8
 
79
 
80
  ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
81
 
@@ -146,7 +147,7 @@ ENV REA_APKTOOL_COMMAND="/usr/local/bin/apktool"
146
  # ------------------------------------------
147
  COPY --from=binwalk-builder /install/bin/binwalk /usr/local/bin/binwalk
148
  RUN chmod +x /usr/local/bin/binwalk && \
149
- pip3 install --no-cache-dir --break-system-packages unblob==26.6.4 pwntools
150
 
151
  ENV REA_BINWALK_COMMAND="/usr/local/bin/binwalk"
152
  ENV REA_UNBLOB_COMMAND="/usr/local/bin/unblob"
@@ -189,39 +190,20 @@ RUN curl -fsSL "https://github.com/oven-sh/bun/releases/latest/download/bun-linu
189
  rm -rf /tmp/bun*
190
 
191
  # ------------------------------------------
192
- # 12. Claude Code CLI, REA, Supergateway, Codeman + Browser Hard-Block
193
  # ------------------------------------------
194
  COPY security/stub-playwright-index.js /tmp/stub-pw.js
195
  COPY security/stub-playwright-index.mjs /tmp/stub-pw.mjs
196
- RUN npm install -g supergateway@latest && \
197
- npm install -g rea-agents@latest && \
198
- npm install -g @anthropic-ai/claude-code && \
199
- npm install -g aicodeman@latest && \
200
- npm install -g opencode-ai && \
201
- npm install -g @deepseek-ai/dsh pnpm yarn typescript && \
202
- which supergateway codeman claude rea opencode && \
203
  cp /tmp/stub-pw.js /usr/local/lib/node_modules/rea-agents/node_modules/playwright-core/index.js 2>/dev/null || true && \
204
  cp /tmp/stub-pw.mjs /usr/local/lib/node_modules/rea-agents/node_modules/playwright-core/index.mjs 2>/dev/null || true && \
205
  rm -f /tmp/stub-pw.* && \
206
  for b in chromium chromium-browser google-chrome google-chrome-stable chrome firefox playwright playwright-core puppeteer chromedriver geckodriver msedge brave-browser webkit; do \
207
  printf '#!/bin/sh\necho "ERROR: Browser engines and browser automation are strictly disabled by host security policy." >&2\nexit 126\n' > "/usr/local/bin/$b" && chmod 755 "/usr/local/bin/$b"; \
208
  done && \
209
- rm -rf /root/.npm
210
-
211
- # ------------------------------------------
212
- # 13. Claude Code Telegram Bot Bridge
213
- # ------------------------------------------
214
- COPY security/patch-claude-telegram.py /tmp/patch-claude-telegram.py
215
- RUN git clone https://github.com/RichardAtCT/claude-code-telegram.git /opt/claude-code-telegram && \
216
- python3 /tmp/patch-claude-telegram.py && \
217
- python3 -m venv /opt/claude-code-telegram/venv && \
218
- /opt/claude-code-telegram/venv/bin/pip install --no-cache-dir poetry && \
219
- /opt/claude-code-telegram/venv/bin/pip install --no-cache-dir /opt/claude-code-telegram && \
220
- rm -f /tmp/patch-claude-telegram.py
221
-
222
- COPY security/claude-wrapper.sh /tmp/claude-wrapper.sh
223
- RUN cp /tmp/claude-wrapper.sh /usr/local/bin/claude && chmod 755 /usr/local/bin/claude && rm -f /tmp/claude-wrapper.sh && \
224
- /usr/local/bin/rea setup --client claude_code --client opencode --client vscode --yes || true
225
 
226
  # ------------------------------------------
227
  # 14. Application Setup & Runtime Runner
 
75
  tmux \
76
  locales \
77
  && rm -rf /var/lib/apt/lists/* && \
78
+ sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
79
+ /usr/sbin/locale-gen en_US.UTF-8
80
 
81
  ENV LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
82
 
 
147
  # ------------------------------------------
148
  COPY --from=binwalk-builder /install/bin/binwalk /usr/local/bin/binwalk
149
  RUN chmod +x /usr/local/bin/binwalk && \
150
+ pip3 install --no-cache-dir --break-system-packages unblob==26.6.4 pwntools python-telegram-bot httpx
151
 
152
  ENV REA_BINWALK_COMMAND="/usr/local/bin/binwalk"
153
  ENV REA_UNBLOB_COMMAND="/usr/local/bin/unblob"
 
190
  rm -rf /tmp/bun*
191
 
192
  # ------------------------------------------
193
+ # 12. CLI Agents (OpenCode, DeepSeek Harness, REA, Supergateway) + Browser Hard-Block
194
  # ------------------------------------------
195
  COPY security/stub-playwright-index.js /tmp/stub-pw.js
196
  COPY security/stub-playwright-index.mjs /tmp/stub-pw.mjs
197
+ RUN npm install -g supergateway@latest rea-agents@latest opencode-ai @deepseek-ai/dsh pnpm yarn typescript && \
198
+ which supergateway rea opencode && \
 
 
 
 
 
199
  cp /tmp/stub-pw.js /usr/local/lib/node_modules/rea-agents/node_modules/playwright-core/index.js 2>/dev/null || true && \
200
  cp /tmp/stub-pw.mjs /usr/local/lib/node_modules/rea-agents/node_modules/playwright-core/index.mjs 2>/dev/null || true && \
201
  rm -f /tmp/stub-pw.* && \
202
  for b in chromium chromium-browser google-chrome google-chrome-stable chrome firefox playwright playwright-core puppeteer chromedriver geckodriver msedge brave-browser webkit; do \
203
  printf '#!/bin/sh\necho "ERROR: Browser engines and browser automation are strictly disabled by host security policy." >&2\nexit 126\n' > "/usr/local/bin/$b" && chmod 755 "/usr/local/bin/$b"; \
204
  done && \
205
+ rm -rf /root/.npm && \
206
+ /usr/local/bin/rea setup --client opencode --client vscode --yes || true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
  # ------------------------------------------
209
  # 14. Application Setup & Runtime Runner
config/Caddyfile.example CHANGED
@@ -1,5 +1,14 @@
1
  :7860 {
2
- # 1. REA MCP Server (Streamable HTTP via supergateway)
 
 
 
 
 
 
 
 
 
3
  handle /mcp* {
4
  reverse_proxy 127.0.0.1:8093 {
5
  flush_interval -1
@@ -52,10 +61,8 @@
52
  file_server
53
  }
54
 
55
- # 6. Codeman AI Coding Mission Control fallback
56
  handle {
57
- reverse_proxy 127.0.0.1:3000 {
58
- header_up Host 127.0.0.1
59
- }
60
  }
61
  }
 
1
  :7860 {
2
+ # 1. REA MCP Server (Streamable HTTP via supergateway, gated with auth)
3
+ @mcp_unauth {
4
+ path /mcp*
5
+ not header Authorization "Bearer @man.g"
6
+ not header X-API-Key "@man.g"
7
+ }
8
+ respond @mcp_unauth "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Unauthorized: valid Bearer token or X-API-Key required.\"},\"id\":null}" 401 {
9
+ close
10
+ }
11
+
12
  handle /mcp* {
13
  reverse_proxy 127.0.0.1:8093 {
14
  flush_interval -1
 
61
  file_server
62
  }
63
 
64
+ # 6. Root URL redirects directly to VS Code (/code/)
65
  handle {
66
+ redir / /code/ 302
 
 
67
  }
68
  }
security/claude-wrapper.sh DELETED
@@ -1,16 +0,0 @@
1
- #!/bin/bash
2
- # Wrapper to allow Claude Code to run as root by stripping --dangerously-skip-permissions
3
- args=("$@")
4
- newargs=()
5
- for arg in "${args[@]}"; do
6
- if [ "$arg" != "--dangerously-skip-permissions" ] && [ "$arg" != "--allow-dangerously-skip-permissions" ]; then
7
- newargs+=("$arg")
8
- fi
9
- done
10
-
11
- REAL_CLAUDE="/usr/local/lib/node_modules/@anthropic-ai/claude-code/bin/claude.js"
12
- if [ -f "$REAL_CLAUDE" ]; then
13
- exec node "$REAL_CLAUDE" "${newargs[@]}"
14
- else
15
- exec npx -y @anthropic-ai/claude-code "${newargs[@]}"
16
- fi
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
security/patch-claude-telegram.py DELETED
@@ -1,19 +0,0 @@
1
- import pathlib
2
-
3
- p = pathlib.Path("/opt/claude-code-telegram/src/bot/core.py")
4
- text = p.read_text(encoding="utf-8")
5
-
6
- needle = "builder.get_updates_connection_pool_size(8)"
7
- patch = """builder.get_updates_connection_pool_size(8)
8
-
9
- apibase = os.environ.get("TELEGRAM_BOT_APIBASE")
10
- if apibase:
11
- builder.base_url(f"https://{apibase}/bot")
12
- builder.base_file_url(f"https://{apibase}/file/bot")"""
13
-
14
- if needle in text and "TELEGRAM_BOT_APIBASE" not in text:
15
- text = text.replace(needle, patch, 1)
16
- p.write_text(text, encoding="utf-8")
17
- print("Patched TELEGRAM_BOT_APIBASE into claude-code-telegram core.py")
18
- else:
19
- print("Patch already applied or needle not found")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/25-codeman.sh DELETED
@@ -1,36 +0,0 @@
1
- #!/bin/bash
2
- # 25-codeman.sh — Codeman AI Coding Mission Control on 127.0.0.1:3000
3
- mkdir -p /data/script/codeman/data /data/workspace /tmp/logs /tmp/.codeman
4
- LOG="/tmp/logs/codeman.log"
5
-
6
- if [ -f /data/.env ]; then
7
- set -a
8
- . /data/.env
9
- set +a
10
- fi
11
-
12
- export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"
13
- export TERM="xterm-256color"
14
- export CODEMAN_PORT=3000
15
- export CODEMAN_HOST="127.0.0.1"
16
- export CODEMAN_PASSWORD="${AUTH_TOKEN:-}"
17
- export CODEMAN_ALLOWED_HOSTS=".hf.space,amangs-cbot.hf.space,localhost,127.0.0.1"
18
- export CODEMAN_CONFIG_DIR="/data/script/codeman"
19
- export CODEMAN_DATA_DIR="/data/script/codeman/data"
20
- export CODEMAN_CASES_PATH="/data/workspace"
21
- export TMPDIR="/tmp"
22
-
23
- # Route all CLI agents (Claude Code, OpenCode, Codex) to local opencode-router
24
- export ANTHROPIC_BASE_URL="http://127.0.0.1:8787"
25
- export OPENAI_BASE_URL="http://127.0.0.1:8787/v1"
26
- export OPENCODE_BASE_URL="http://127.0.0.1:8787/v1"
27
- export ANTHROPIC_API_KEY="${AUTH_TOKEN:-}"
28
- export OPENAI_API_KEY="${AUTH_TOKEN:-}"
29
-
30
- if pgrep -f "codeman.*web" >/dev/null 2>&1; then
31
- echo "codeman already running"
32
- exit 0
33
- fi
34
-
35
- cd /data/workspace || exit 1
36
- exec codeman web --host 127.0.0.1 --port 3000 >> "$LOG" 2>&1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/30-claude-telegram.sh DELETED
@@ -1,52 +0,0 @@
1
- #!/bin/bash
2
- # 30-claude-telegram.sh — Run Claude Code Telegram bot (@amangt_bot)
3
- mkdir -p /tmp/logs /data/workspace /tmp/.cache
4
- LOG="/tmp/logs/claude-telegram.log"
5
-
6
- if [ -f /data/.env ]; then
7
- set -a
8
- . /data/.env
9
- set +a
10
- fi
11
-
12
- TOKEN="${TELEGRAM_BOT_TOKEN:-}"
13
- if [ -z "$TOKEN" ]; then
14
- echo "TELEGRAM_BOT_TOKEN not set, skipping claude-code-telegram"
15
- exit 0
16
- fi
17
-
18
- export TELEGRAM_BOT_TOKEN="$TOKEN"
19
- export TELEGRAM_BOT_USERNAME="${TELEGRAM_BOT_USERNAME:-amangt_bot}"
20
- export APPROVED_DIRECTORY="/data/workspace"
21
- export ALLOWED_USERS="${TELEGRAM_USER:-973904286}"
22
- export DATABASE_URL="sqlite+aiosqlite:////tmp/claude_bot.db"
23
- export DISABLE_SECURITY_PATTERNS="true"
24
- export DISABLE_TOOL_VALIDATION="true"
25
- export INTERACTIVE_TOOL_APPROVAL="false"
26
- export CLAUDE_MAX_TURNS="100"
27
-
28
- # Caching & Temp redirected strictly to /tmp (never S3 /data)
29
- export TMPDIR="/tmp"
30
- export PIP_CACHE_DIR="/tmp/.cache/pip"
31
- export npm_config_cache="/tmp/.cache/npm"
32
- export GRADLE_USER_HOME="/tmp/.gradle"
33
- export GOCACHE="/tmp/.cache/go-build"
34
- export GOPATH="/tmp/go"
35
- export XDG_CACHE_HOME="/tmp/.cache"
36
- export HF_HOME="/tmp/.cache/huggingface"
37
-
38
- # Point Claude Code to local opencode-router if ANTHROPIC_BASE_URL is unset
39
- export ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL:-http://127.0.0.1:8787}"
40
- export ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-${AUTH_TOKEN:-}}"
41
-
42
- # Environment markers for Claude Code CLI inside Docker
43
- export IS_SANDBOX="1"
44
- export CI="1"
45
-
46
- if pgrep -f "claude-telegram-bot" >/dev/null 2>&1; then
47
- echo "claude-code-telegram already running"
48
- exit 0
49
- fi
50
-
51
- cd /opt/claude-code-telegram || exit 1
52
- exec /opt/claude-code-telegram/venv/bin/python3 -m src.main >> "$LOG" 2>&1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/30-telegram-agent.sh ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # 30-telegram-agent.sh — Avi Telegram AI Coding Agent (@amangt_bot)
3
+ mkdir -p /tmp/logs /data/workspace
4
+ LOG="/tmp/logs/telegram-agent.log"
5
+
6
+ if [ -f /tmp/.env ]; then
7
+ set -a
8
+ . /tmp/.env
9
+ set +a
10
+ fi
11
+ if [ -f /data/.env ]; then
12
+ set -a
13
+ . /data/.env
14
+ set +a
15
+ fi
16
+
17
+ TOKEN="${TELEGRAM_BOT_TOKEN:-}"
18
+ if [ -z "$TOKEN" ]; then
19
+ echo "TELEGRAM_BOT_TOKEN not set, skipping telegram agent" >> "$LOG"
20
+ exit 0
21
+ fi
22
+
23
+ export TELEGRAM_BOT_TOKEN="$TOKEN"
24
+ export TELEGRAM_BOT_APIBASE="${TELEGRAM_BOT_APIBASE:-tgp.ind.pp.ua}"
25
+ export ALLOWED_USERS="${TELEGRAM_USER:-973904286}"
26
+ export LLM_AMAN_BASE_URL="${LLM_AMAN_BASE_URL:-https://llm.aman.ltd/v1}"
27
+ export LLM_AMAN_API_KEY="${LLM_AMAN_API_KEY:-sk-mSm7P3YFDdeasXvlWgLKCGHsucUtAWJJBnLapDyRVUe6TV8w}"
28
+ export DEFAULT_MODEL="gemini-3.8-flash-high"
29
+ export WORKSPACE="/data/workspace"
30
+
31
+ if pgrep -f "services/telegram_agent.py" >/dev/null 2>&1; then
32
+ echo "telegram_agent already running"
33
+ exit 0
34
+ fi
35
+
36
+ cd /app || cd /data || exit 1
37
+ exec python3 /app/services/telegram_agent.py >> "$LOG" 2>&1
services/telegram_agent.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import json
4
+ import asyncio
5
+ import subprocess
6
+ import httpx
7
+ from telegram import Update
8
+ from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
9
+
10
+ BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN", "")
11
+ API_BASE = os.environ.get("TELEGRAM_BOT_APIBASE", "api.telegram.org")
12
+ ALLOWED_USERS = [int(u.strip()) for u in os.environ.get("ALLOWED_USERS", "973904286").split(",") if u.strip().isdigit()]
13
+ LLM_BASE = os.environ.get("LLM_AMAN_BASE_URL", "https://llm.aman.ltd/v1")
14
+ LLM_KEY = os.environ.get("LLM_AMAN_API_KEY", "")
15
+ DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "gemini-3.8-flash-high")
16
+ WORKSPACE = os.environ.get("WORKSPACE", "/data/workspace")
17
+
18
+ if not BOT_TOKEN:
19
+ print("TELEGRAM_BOT_TOKEN not configured. Exiting.")
20
+ sys.exit(0)
21
+
22
+ # In-memory per-user chat history
23
+ HISTORIES = {}
24
+ CURRENT_MODELS = {}
25
+
26
+ TOOLS = [
27
+ {
28
+ "type": "function",
29
+ "function": {
30
+ "name": "bash",
31
+ "description": "Execute a shell command in the persistent /data/workspace directory.",
32
+ "parameters": {
33
+ "type": "object",
34
+ "properties": {
35
+ "command": {"type": "string", "description": "The shell command to run"}
36
+ },
37
+ "required": ["command"]
38
+ }
39
+ }
40
+ }
41
+ ]
42
+
43
+ def run_bash(cmd: str) -> str:
44
+ try:
45
+ res = subprocess.run(cmd, shell=True, cwd=WORKSPACE, capture_output=True, text=True, timeout=120)
46
+ out = (res.stdout + res.stderr).strip()
47
+ return out if out else "(command produced no output, exit 0)"
48
+ except subprocess.TimeoutExpired:
49
+ return "Error: Command timed out after 120 seconds."
50
+ except Exception as e:
51
+ return f"Error executing command: {e}"
52
+
53
+ async def call_llm(user_id: int, user_msg: str) -> str:
54
+ model = CURRENT_MODELS.get(user_id, DEFAULT_MODEL)
55
+ history = HISTORIES.setdefault(user_id, [
56
+ {"role": "system", "content": f"You are Avi, an expert software engineer and reverse-engineering assistant on a dedicated Linux development server. You have full terminal access via the bash tool. Working directory is {WORKSPACE}. Answer concisely and accurately. Use your tools to inspect, build, and debug."}
57
+ ])
58
+ history.append({"role": "user", "content": user_msg})
59
+
60
+ # Cap history to last 16 messages
61
+ if len(history) > 17:
62
+ history = [history[0]] + history[-16:]
63
+ HISTORIES[user_id] = history
64
+
65
+ headers = {"Authorization": f"Bearer {LLM_KEY}", "Content-Type": "application/json"}
66
+ payload = {
67
+ "model": model,
68
+ "messages": history,
69
+ "tools": TOOLS,
70
+ "temperature": 0.2
71
+ }
72
+
73
+ async with httpx.AsyncClient(timeout=60.0) as client:
74
+ for _ in range(5): # Tool call loop up to 5 turns
75
+ try:
76
+ resp = await client.post(f"{LLM_BASE}/chat/completions", headers=headers, json=payload)
77
+ if resp.status_code != 200:
78
+ return f"API Error {resp.status_code}: {resp.text[:300]}"
79
+ data = resp.json()
80
+ msg = data["choices"][0]["message"]
81
+ except Exception as e:
82
+ return f"LLM error: {e}"
83
+
84
+ tool_calls = msg.get("tool_calls")
85
+ if not tool_calls:
86
+ reply = msg.get("content", "")
87
+ history.append({"role": "assistant", "content": reply})
88
+ return reply
89
+
90
+ # Handle tool calls
91
+ history.append(msg)
92
+ for tc in tool_calls:
93
+ fn_name = tc["function"]["name"]
94
+ try:
95
+ fn_args = json.loads(tc["function"]["arguments"])
96
+ except Exception:
97
+ fn_args = {}
98
+ if fn_name == "bash":
99
+ cmd = fn_args.get("command", "")
100
+ output = run_bash(cmd)
101
+ else:
102
+ output = f"Unknown tool {fn_name}"
103
+
104
+ history.append({
105
+ "role": "tool",
106
+ "tool_call_id": tc["id"],
107
+ "content": output[:8000]
108
+ })
109
+
110
+ return "Reached maximum tool execution turns."
111
+
112
+ async def handle_start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
113
+ user_id = update.effective_user.id
114
+ if ALLOWED_USERS and user_id not in ALLOWED_USERS:
115
+ return
116
+ model = CURRENT_MODELS.get(user_id, DEFAULT_MODEL)
117
+ await update.message.reply_text(
118
+ f"Avi online.\nModel: {model} (via {LLM_BASE})\nWorkspace: {WORKSPACE}\nSend a message to start coding or running commands."
119
+ )
120
+
121
+ async def handle_model(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
122
+ user_id = update.effective_user.id
123
+ if ALLOWED_USERS and user_id not in ALLOWED_USERS:
124
+ return
125
+ args = context.args
126
+ if not args:
127
+ cur = CURRENT_MODELS.get(user_id, DEFAULT_MODEL)
128
+ await update.message.reply_text(f"Current model: {cur}\nUsage: /model <name>\nPopular: gemini-3.8-flash-high, deepseek-v4-flash, Codestral, Devstral 2, gpt-oss:120b")
129
+ return
130
+ new_model = args[0]
131
+ CURRENT_MODELS[user_id] = new_model
132
+ await update.message.reply_text(f"Model switched to: {new_model}")
133
+
134
+ async def handle_clear(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
135
+ user_id = update.effective_user.id
136
+ HISTORIES[user_id] = []
137
+ await update.message.reply_text("Conversation history cleared.")
138
+
139
+ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
140
+ user_id = update.effective_user.id
141
+ if ALLOWED_USERS and user_id not in ALLOWED_USERS:
142
+ return
143
+ text = update.message.text
144
+ if not text:
145
+ return
146
+ await context.bot.send_chat_action(chat_id=update.effective_chat.id, action="typing")
147
+ reply = await call_llm(user_id, text)
148
+ # Split long replies to Telegram 4096 limit
149
+ for chunk in [reply[i:i+4000] for i in range(0, len(reply), 4000)]:
150
+ await update.message.reply_text(chunk)
151
+
152
+ def main():
153
+ builder = Application.builder()
154
+ builder.token(BOT_TOKEN)
155
+ if API_BASE and API_BASE != "api.telegram.org":
156
+ builder.base_url(f"https://{API_BASE}/bot")
157
+ builder.base_file_url(f"https://{API_BASE}/file/bot")
158
+
159
+ app = builder.build()
160
+ app.add_handler(CommandHandler("start", handle_start))
161
+ app.add_handler(CommandHandler("model", handle_model))
162
+ app.add_handler(CommandHandler("clear", handle_clear))
163
+ app.add_handler(CommandHandler("reset", handle_clear))
164
+ app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
165
+
166
+ print(f"Starting Avi Telegram agent using {API_BASE}...")
167
+ app.run_polling()
168
+
169
+ if __name__ == "__main__":
170
+ main()