From 2977d620e2b2bb60d631a1216843fdd53e50e7c8 Mon Sep 17 00:00:00 2001 From: tbxark Date: Fri, 15 Nov 2024 11:42:57 +0800 Subject: [PATCH 1/4] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=A0=BC=E5=BC=8F=E7=9A=84=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/buildinfo.json | 2 +- dist/index.js | 49 +++++++++++++------ packages/lib/core/src/agent/message.ts | 16 +++--- packages/lib/core/src/config/version.ts | 4 +- .../lib/core/src/telegram/handler/chat.ts | 46 ++++++++++++----- 5 files changed, 77 insertions(+), 40 deletions(-) diff --git a/dist/buildinfo.json b/dist/buildinfo.json index 245a4cb3e..a7d019e9b 100644 --- a/dist/buildinfo.json +++ b/dist/buildinfo.json @@ -1 +1 @@ -{"sha":"5e2f72d","timestamp":1731639921} \ No newline at end of file +{"sha":"ce77d5f","timestamp":1731642094} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index ade5a6a7a..4a07f32b0 100644 --- a/dist/index.js +++ b/dist/index.js @@ -192,8 +192,8 @@ class ConfigMerger { } } } -const BUILD_TIMESTAMP = 1731639921; -const BUILD_VERSION = "5e2f72d"; +const BUILD_TIMESTAMP = 1731642094; +const BUILD_VERSION = "ce77d5f"; function createAgentUserConfig() { return Object.assign( {}, @@ -1981,6 +1981,29 @@ function findPhotoFileID(photos, offset) { sizeIndex = Math.max(0, Math.min(sizeIndex, photos.length - 1)); return photos[sizeIndex].file_id; } +async function extractImageURL(fileId, context) { + if (!fileId) { + return null; + } + const api = createTelegramBotAPI(context.SHARE_CONTEXT.botToken); + const file = await api.getFileWithReturns({ file_id: fileId }); + const filePath = file.result.file_path; + if (filePath) { + const url = URL.parse(`${ENV.TELEGRAM_API_DOMAIN}/file/bot${context.SHARE_CONTEXT.botToken}/${filePath}`); + if (url) { + return url; + } + } + return null; +} +function extractImageFieldID(message) { + if (message.photo && message.photo.length > 0) { + return findPhotoFileID(message.photo, ENV.TELEGRAM_PHOTO_SIZE_OFFSET); + } else if (message.document && message.document.thumbnail) { + return message.document.thumbnail.file_id; + } + return null; +} class ChatHandler { handle = async (message, context) => { const text = message.text || message.caption || ""; @@ -1988,20 +2011,14 @@ class ChatHandler { role: "user", content: text }; - if (message.photo && message.photo.length > 0) { - const id = findPhotoFileID(message.photo, ENV.TELEGRAM_PHOTO_SIZE_OFFSET); - const api = createTelegramBotAPI(context.SHARE_CONTEXT.botToken); - const file = await api.getFileWithReturns({ file_id: id }); - const filePath = file.result.file_path; - if (filePath) { - const url = URL.parse(`${ENV.TELEGRAM_API_DOMAIN}/file/bot${context.SHARE_CONTEXT.botToken}/${filePath}`); - if (url) { - params.content = [ - { type: "text", text }, - { type: "image", image: url } - ]; - } - } + const url = await extractImageURL(extractImageFieldID(message), context); + if (url) { + const contents = new Array(); + if (text) { + contents.push({ type: "text", text }); + } + contents.push({ type: "image", image: url }); + params.content = contents; } return chatWithLLM(message, params, context, null); }; diff --git a/packages/lib/core/src/agent/message.ts b/packages/lib/core/src/agent/message.ts index f3e1f0fda..2100ec16c 100644 --- a/packages/lib/core/src/agent/message.ts +++ b/packages/lib/core/src/agent/message.ts @@ -1,38 +1,38 @@ export type DataContent = string | Uint8Array | ArrayBuffer | Buffer; -interface TextPart { +export interface TextPart { type: 'text'; text: string; } -interface ImagePart { +export interface ImagePart { type: 'image'; image: DataContent | URL; mimeType?: string; } -interface ToolCallPart { +export interface ToolCallPart { type: 'tool-call'; toolCallId: string; toolName: string; args: unknown; } -interface FilePart { +export interface FilePart { type: 'file'; data: DataContent | URL; } -interface ToolResultPart { +export interface ToolResultPart { type: 'tool-result'; toolCallId: string; toolName: string; result: unknown; } -type AssistantContent = string | Array; -type UserContent = string | Array; -type ToolContent = Array; +export type AssistantContent = string | Array; +export type UserContent = string | Array; +export type ToolContent = Array; export interface CoreSystemMessage { role: 'system'; diff --git a/packages/lib/core/src/config/version.ts b/packages/lib/core/src/config/version.ts index b5cb45be0..ea22b149c 100644 --- a/packages/lib/core/src/config/version.ts +++ b/packages/lib/core/src/config/version.ts @@ -1,2 +1,2 @@ -export const BUILD_TIMESTAMP = 1731639921; -export const BUILD_VERSION = '5e2f72d'; +export const BUILD_TIMESTAMP = 1731642094; +export const BUILD_VERSION = 'ce77d5f'; diff --git a/packages/lib/core/src/telegram/handler/chat.ts b/packages/lib/core/src/telegram/handler/chat.ts index 2962ce75e..b6678fba0 100644 --- a/packages/lib/core/src/telegram/handler/chat.ts +++ b/packages/lib/core/src/telegram/handler/chat.ts @@ -8,6 +8,7 @@ import { requestCompletionsFromLLM } from '../../agent/chat'; import { ENV } from '../../config/env'; import { createTelegramBotAPI } from '../api'; import { MessageSender } from '../utils/send'; +import {FilePart, ImagePart, TextPart, UserContent} from "../../agent/message"; export async function chatWithLLM(message: Telegram.Message, params: UserMessageItem | null, context: WorkerContext, modifier: HistoryModifier | null): Promise { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); @@ -87,6 +88,31 @@ function findPhotoFileID(photos: Telegram.PhotoSize[], offset: number): string { return photos[sizeIndex].file_id; } +async function extractImageURL(fileId: string | null, context: WorkerContext): Promise { + if (!fileId) { + return null; + } + const api = createTelegramBotAPI(context.SHARE_CONTEXT.botToken); + const file = await api.getFileWithReturns({ file_id: fileId }); + const filePath = file.result.file_path; + if (filePath) { + const url = URL.parse(`${ENV.TELEGRAM_API_DOMAIN}/file/bot${context.SHARE_CONTEXT.botToken}/${filePath}`); + if (url) { + return url; + } + } + return null; +} + +function extractImageFieldID(message: Telegram.Message): string | null { + if (message.photo && message.photo.length > 0) { + return findPhotoFileID(message.photo, ENV.TELEGRAM_PHOTO_SIZE_OFFSET); + } else if (message.document && message.document.thumbnail) { + return message.document.thumbnail.file_id; + } + return null; +} + export class ChatHandler implements MessageHandler { handle = async (message: Telegram.Message, context: WorkerContext): Promise => { const text = message.text || message.caption || ''; @@ -94,20 +120,14 @@ export class ChatHandler implements MessageHandler { role: 'user', content: text, }; - if (message.photo && message.photo.length > 0) { - const id = findPhotoFileID(message.photo, ENV.TELEGRAM_PHOTO_SIZE_OFFSET); - const api = createTelegramBotAPI(context.SHARE_CONTEXT.botToken); - const file = await api.getFileWithReturns({ file_id: id }); - const filePath = file.result.file_path; - if (filePath) { - const url = URL.parse(`${ENV.TELEGRAM_API_DOMAIN}/file/bot${context.SHARE_CONTEXT.botToken}/${filePath}`); - if (url) { - params.content = [ - { type: 'text', text }, - { type: 'image', image: url }, - ]; - } + const url = await extractImageURL(extractImageFieldID(message), context); + if (url) { + const contents = new Array(); + if (text) { + contents.push({ type: 'text', text }); } + contents.push({ type: 'image', image: url }); + params.content = contents; } return chatWithLLM(message, params, context, null); }; From e919fa586ce25d97a57be1fb8c33f8f606f455bb Mon Sep 17 00:00:00 2001 From: tbxark Date: Fri, 15 Nov 2024 12:13:32 +0800 Subject: [PATCH 2/4] =?UTF-8?q?chore:=20=E5=90=88=E5=B9=B6=20extractImageF?= =?UTF-8?q?ileID=E5=92=8CfindPhotoFileID=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/buildinfo.json | 2 +- dist/index.js | 23 +++++++------------ packages/lib/core/src/config/version.ts | 4 ++-- .../lib/core/src/telegram/handler/chat.ts | 20 +++++----------- 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/dist/buildinfo.json b/dist/buildinfo.json index a7d019e9b..36ef9f962 100644 --- a/dist/buildinfo.json +++ b/dist/buildinfo.json @@ -1 +1 @@ -{"sha":"ce77d5f","timestamp":1731642094} \ No newline at end of file +{"sha":"2977d62","timestamp":1731643971} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 4a07f32b0..25b33043a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -192,8 +192,8 @@ class ConfigMerger { } } } -const BUILD_TIMESTAMP = 1731642094; -const BUILD_VERSION = "ce77d5f"; +const BUILD_TIMESTAMP = 1731643971; +const BUILD_VERSION = "2977d62"; function createAgentUserConfig() { return Object.assign( {}, @@ -1971,16 +1971,6 @@ async function chatWithLLM(message, params, context, modifier) { return sender.sendPlainText(errMsg); } } -function findPhotoFileID(photos, offset) { - let sizeIndex = 0; - if (offset >= 0) { - sizeIndex = offset; - } else if (offset < 0) { - sizeIndex = photos.length + offset; - } - sizeIndex = Math.max(0, Math.min(sizeIndex, photos.length - 1)); - return photos[sizeIndex].file_id; -} async function extractImageURL(fileId, context) { if (!fileId) { return null; @@ -1996,9 +1986,12 @@ async function extractImageURL(fileId, context) { } return null; } -function extractImageFieldID(message) { +function extractImageFileID(message) { if (message.photo && message.photo.length > 0) { - return findPhotoFileID(message.photo, ENV.TELEGRAM_PHOTO_SIZE_OFFSET); + const offset = ENV.TELEGRAM_PHOTO_SIZE_OFFSET; + const length = message.photo.length; + const sizeIndex = Math.max(0, Math.min(offset >= 0 ? offset : length + offset, length - 1)); + return message.photo[sizeIndex]?.file_id; } else if (message.document && message.document.thumbnail) { return message.document.thumbnail.file_id; } @@ -2011,7 +2004,7 @@ class ChatHandler { role: "user", content: text }; - const url = await extractImageURL(extractImageFieldID(message), context); + const url = await extractImageURL(extractImageFileID(message), context); if (url) { const contents = new Array(); if (text) { diff --git a/packages/lib/core/src/config/version.ts b/packages/lib/core/src/config/version.ts index ea22b149c..083b43db7 100644 --- a/packages/lib/core/src/config/version.ts +++ b/packages/lib/core/src/config/version.ts @@ -1,2 +1,2 @@ -export const BUILD_TIMESTAMP = 1731642094; -export const BUILD_VERSION = 'ce77d5f'; +export const BUILD_TIMESTAMP = 1731643971; +export const BUILD_VERSION = '2977d62'; diff --git a/packages/lib/core/src/telegram/handler/chat.ts b/packages/lib/core/src/telegram/handler/chat.ts index b6678fba0..8f3dcc5ec 100644 --- a/packages/lib/core/src/telegram/handler/chat.ts +++ b/packages/lib/core/src/telegram/handler/chat.ts @@ -77,17 +77,6 @@ export async function chatWithLLM(message: Telegram.Message, params: UserMessage } } -function findPhotoFileID(photos: Telegram.PhotoSize[], offset: number): string { - let sizeIndex = 0; - if (offset >= 0) { - sizeIndex = offset; - } else if (offset < 0) { - sizeIndex = photos.length + offset; - } - sizeIndex = Math.max(0, Math.min(sizeIndex, photos.length - 1)); - return photos[sizeIndex].file_id; -} - async function extractImageURL(fileId: string | null, context: WorkerContext): Promise { if (!fileId) { return null; @@ -104,9 +93,12 @@ async function extractImageURL(fileId: string | null, context: WorkerContext): P return null; } -function extractImageFieldID(message: Telegram.Message): string | null { +function extractImageFileID(message: Telegram.Message): string | null { if (message.photo && message.photo.length > 0) { - return findPhotoFileID(message.photo, ENV.TELEGRAM_PHOTO_SIZE_OFFSET); + const offset = ENV.TELEGRAM_PHOTO_SIZE_OFFSET; + const length = message.photo.length; + const sizeIndex = Math.max(0, Math.min(offset >= 0 ? offset : length + offset, length - 1)); + return message.photo[sizeIndex]?.file_id; } else if (message.document && message.document.thumbnail) { return message.document.thumbnail.file_id; } @@ -120,7 +112,7 @@ export class ChatHandler implements MessageHandler { role: 'user', content: text, }; - const url = await extractImageURL(extractImageFieldID(message), context); + const url = await extractImageURL(extractImageFileID(message), context); if (url) { const contents = new Array(); if (text) { From 76928f4314048a347784c86217e7afb6e190f2d6 Mon Sep 17 00:00:00 2001 From: tbxark Date: Fri, 15 Nov 2024 12:14:19 +0800 Subject: [PATCH 3/4] =?UTF-8?q?style:=20=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/lib/core/src/telegram/handler/chat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/core/src/telegram/handler/chat.ts b/packages/lib/core/src/telegram/handler/chat.ts index 8f3dcc5ec..2072a9a51 100644 --- a/packages/lib/core/src/telegram/handler/chat.ts +++ b/packages/lib/core/src/telegram/handler/chat.ts @@ -1,6 +1,7 @@ import type * as Telegram from 'telegram-bot-api-types'; import type { HistoryModifier, UserMessageItem } from '../../agent'; import type { StreamResultHandler } from '../../agent/chat'; +import type { FilePart, ImagePart, TextPart } from '../../agent/message'; import type { WorkerContext } from '../../config/context'; import type { MessageHandler } from './types'; import { loadChatLLM } from '../../agent'; @@ -8,7 +9,6 @@ import { requestCompletionsFromLLM } from '../../agent/chat'; import { ENV } from '../../config/env'; import { createTelegramBotAPI } from '../api'; import { MessageSender } from '../utils/send'; -import {FilePart, ImagePart, TextPart, UserContent} from "../../agent/message"; export async function chatWithLLM(message: Telegram.Message, params: UserMessageItem | null, context: WorkerContext, modifier: HistoryModifier | null): Promise { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); From 7454920a5c5eee4d3d5b219c70b1ce402f37c6d2 Mon Sep 17 00:00:00 2001 From: tbxark Date: Fri, 15 Nov 2024 13:50:17 +0800 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20=E4=B8=BAcore=E4=B8=AD=E6=AF=8F?= =?UTF-8?q?=E4=B8=AA=E5=AD=90=E6=A8=A1=E5=9D=97=E6=B7=BB=E5=8A=A0index.ts?= =?UTF-8?q?=20=E5=87=8F=E5=B0=91import=E5=B1=82=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/buildinfo.json | 2 +- dist/index.js | 454 ++++++++---------- packages/lib/core/src/agent/agent.test.ts | 2 +- packages/lib/core/src/agent/agent.ts | 2 +- packages/lib/core/src/agent/anthropic.ts | 4 +- packages/lib/core/src/agent/azure.ts | 2 +- packages/lib/core/src/agent/chat.ts | 4 +- packages/lib/core/src/agent/cohere.ts | 2 +- packages/lib/core/src/agent/gemini.ts | 2 +- packages/lib/core/src/agent/index.ts | 1 + packages/lib/core/src/agent/mistralai.ts | 2 +- packages/lib/core/src/agent/openai.ts | 4 +- packages/lib/core/src/agent/request.ts | 2 +- packages/lib/core/src/agent/types.ts | 14 +- packages/lib/core/src/agent/workersai.ts | 2 +- packages/lib/core/src/config/context.ts | 27 +- packages/lib/core/src/config/env.ts | 4 + packages/lib/core/src/config/index.ts | 6 + packages/lib/core/src/config/version.ts | 4 +- packages/lib/core/src/index.ts | 8 +- packages/lib/core/src/route/index.ts | 2 +- packages/lib/core/src/telegram/api/index.ts | 2 +- .../src/telegram/auth/{auth.ts => index.ts} | 11 +- .../core/src/telegram/callback_query/index.ts | 4 +- .../src/telegram/callback_query/system.ts | 14 +- .../core/src/telegram/callback_query/types.ts | 2 +- .../{handler/chat.ts => chat/index.ts} | 52 +- .../lib/core/src/telegram/command/auth.ts | 4 +- .../lib/core/src/telegram/command/index.ts | 6 +- .../lib/core/src/telegram/command/system.ts | 32 +- .../lib/core/src/telegram/command/types.ts | 2 +- .../lib/core/src/telegram/handler/group.ts | 8 +- .../lib/core/src/telegram/handler/handlers.ts | 18 +- .../lib/core/src/telegram/handler/index.ts | 4 +- .../lib/core/src/telegram/handler/types.ts | 2 +- packages/lib/core/src/telegram/index.ts | 2 + .../{utils/send.ts => sender/index.ts} | 7 +- .../lib/core/src/telegram/utils/md2tgmd.ts | 70 --- packages/lib/core/src/telegram/utils/utils.ts | 31 -- 39 files changed, 359 insertions(+), 462 deletions(-) create mode 100644 packages/lib/core/src/config/index.ts rename packages/lib/core/src/telegram/auth/{auth.ts => index.ts} (68%) rename packages/lib/core/src/telegram/{handler/chat.ts => chat/index.ts} (69%) create mode 100644 packages/lib/core/src/telegram/index.ts rename packages/lib/core/src/telegram/{utils/send.ts => sender/index.ts} (97%) delete mode 100644 packages/lib/core/src/telegram/utils/md2tgmd.ts delete mode 100644 packages/lib/core/src/telegram/utils/utils.ts diff --git a/dist/buildinfo.json b/dist/buildinfo.json index 36ef9f962..20fa69897 100644 --- a/dist/buildinfo.json +++ b/dist/buildinfo.json @@ -1 +1 @@ -{"sha":"2977d62","timestamp":1731643971} \ No newline at end of file +{"sha":"76928f4","timestamp":1731649723} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 25b33043a..c800c2536 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,28 +1,3 @@ -const en = { "env": { "system_init_message": "You are a helpful assistant" }, "command": { "help": { "summary": "The following commands are currently supported:\n", "help": "Get command help", "new": "Start a new conversation", "start": "Get your ID and start a new conversation", "img": "Generate an image, the complete command format is `/img image description`, for example `/img beach at moonlight`", "version": "Get the current version number to determine whether to update", "setenv": "Set user configuration, the complete command format is /setenv KEY=VALUE", "setenvs": 'Batch set user configurations, the full format of the command is /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "Delete user configuration, the complete command format is /delenv KEY", "clearenv": "Clear all user configuration", "system": "View some system information", "redo": "Redo the last conversation, /redo with modified content or directly /redo", "echo": "Echo the message", "models": "switch chat model" }, "new": { "new_chat_start": "A new conversation has started" } }, "callback_query": { "open_model_list": "Open models list", "select_provider": "Select a provider:", "select_model": "Choose model:", "change_model": "Change model to " } }; -const pt = { "env": { "system_init_message": "Você é um assistente útil" }, "command": { "help": { "summary": "Os seguintes comandos são suportados atualmente:\n", "help": "Obter ajuda sobre comandos", "new": "Iniciar uma nova conversa", "start": "Obter seu ID e iniciar uma nova conversa", "img": "Gerar uma imagem, o formato completo do comando é `/img descrição da imagem`, por exemplo `/img praia ao luar`", "version": "Obter o número da versão atual para determinar se é necessário atualizar", "setenv": "Definir configuração do usuário, o formato completo do comando é /setenv CHAVE=VALOR", "setenvs": 'Definir configurações do usuário em lote, o formato completo do comando é /setenvs {"CHAVE1": "VALOR1", "CHAVE2": "VALOR2"}', "delenv": "Excluir configuração do usuário, o formato completo do comando é /delenv CHAVE", "clearenv": "Limpar todas as configurações do usuário", "system": "Ver algumas informações do sistema", "redo": "Refazer a última conversa, /redo com conteúdo modificado ou diretamente /redo", "echo": "Repetir a mensagem", "models": "Mudar o modelo de diálogo" }, "new": { "new_chat_start": "Uma nova conversa foi iniciada" } }, "callback_query": { "open_model_list": "Abra a lista de modelos", "select_provider": "Escolha um fornecedor de modelos.:", "select_model": "Escolha um modelo:", "change_model": "O modelo de diálogo já foi modificado para" } }; -const zhHans = { "env": { "system_init_message": "你是一个得力的助手" }, "command": { "help": { "summary": "当前支持以下命令:\n", "help": "获取命令帮助", "new": "发起新的对话", "start": "获取你的ID, 并发起新的对话", "img": "生成一张图片, 命令完整格式为 `/img 图片描述`, 例如`/img 月光下的沙滩`", "version": "获取当前版本号, 判断是否需要更新", "setenv": "设置用户配置,命令完整格式为 /setenv KEY=VALUE", "setenvs": '批量设置用户配置, 命令完整格式为 /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "删除用户配置,命令完整格式为 /delenv KEY", "clearenv": "清除所有用户配置", "system": "查看当前一些系统信息", "redo": "重做上一次的对话, /redo 加修改过的内容 或者 直接 /redo", "echo": "回显消息", "models": "切换对话模型" }, "new": { "new_chat_start": "新的对话已经开始" } }, "callback_query": { "open_model_list": "打开模型列表", "select_provider": "选择一个模型提供商:", "select_model": "选择一个模型:", "change_model": "对话模型已修改至" } }; -const zhHant = { "env": { "system_init_message": "你是一個得力的助手" }, "command": { "help": { "summary": "當前支持的命令如下:\n", "help": "獲取命令幫助", "new": "開始一個新對話", "start": "獲取您的ID並開始一個新對話", "img": "生成圖片,完整命令格式為`/img 圖片描述`,例如`/img 海灘月光`", "version": "獲取當前版本號確認是否需要更新", "setenv": "設置用戶配置,完整命令格式為/setenv KEY=VALUE", "setenvs": '批量設置用户配置, 命令完整格式為 /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "刪除用戶配置,完整命令格式為/delenv KEY", "clearenv": "清除所有用戶配置", "system": "查看一些系統信息", "redo": "重做上一次的對話 /redo 加修改過的內容 或者 直接 /redo", "echo": "回显消息", "models": "切換對話模式" }, "new": { "new_chat_start": "開始一個新對話" } }, "callback_query": { "open_model_list": "打開模型清單", "select_provider": "選擇一個模型供應商:", "select_model": "選擇一個模型:", "change_model": "對話模型已經修改至" } }; -function loadI18n(lang) { - switch (lang?.toLowerCase()) { - case "cn": - case "zh-cn": - case "zh-hans": - return zhHans; - case "zh-tw": - case "zh-hk": - case "zh-mo": - case "zh-hant": - return zhHant; - case "pt": - case "pt-br": - return pt; - case "en": - case "en-us": - return en; - default: - return en; - } -} class EnvironmentConfig { LANGUAGE = "zh-cn"; UPDATE_BRANCH = "master"; @@ -121,6 +96,31 @@ class AnthropicConfig { class DefineKeys { DEFINE_KEYS = []; } +const en = { "env": { "system_init_message": "You are a helpful assistant" }, "command": { "help": { "summary": "The following commands are currently supported:\n", "help": "Get command help", "new": "Start a new conversation", "start": "Get your ID and start a new conversation", "img": "Generate an image, the complete command format is `/img image description`, for example `/img beach at moonlight`", "version": "Get the current version number to determine whether to update", "setenv": "Set user configuration, the complete command format is /setenv KEY=VALUE", "setenvs": 'Batch set user configurations, the full format of the command is /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "Delete user configuration, the complete command format is /delenv KEY", "clearenv": "Clear all user configuration", "system": "View some system information", "redo": "Redo the last conversation, /redo with modified content or directly /redo", "echo": "Echo the message", "models": "switch chat model" }, "new": { "new_chat_start": "A new conversation has started" } }, "callback_query": { "open_model_list": "Open models list", "select_provider": "Select a provider:", "select_model": "Choose model:", "change_model": "Change model to " } }; +const pt = { "env": { "system_init_message": "Você é um assistente útil" }, "command": { "help": { "summary": "Os seguintes comandos são suportados atualmente:\n", "help": "Obter ajuda sobre comandos", "new": "Iniciar uma nova conversa", "start": "Obter seu ID e iniciar uma nova conversa", "img": "Gerar uma imagem, o formato completo do comando é `/img descrição da imagem`, por exemplo `/img praia ao luar`", "version": "Obter o número da versão atual para determinar se é necessário atualizar", "setenv": "Definir configuração do usuário, o formato completo do comando é /setenv CHAVE=VALOR", "setenvs": 'Definir configurações do usuário em lote, o formato completo do comando é /setenvs {"CHAVE1": "VALOR1", "CHAVE2": "VALOR2"}', "delenv": "Excluir configuração do usuário, o formato completo do comando é /delenv CHAVE", "clearenv": "Limpar todas as configurações do usuário", "system": "Ver algumas informações do sistema", "redo": "Refazer a última conversa, /redo com conteúdo modificado ou diretamente /redo", "echo": "Repetir a mensagem", "models": "Mudar o modelo de diálogo" }, "new": { "new_chat_start": "Uma nova conversa foi iniciada" } }, "callback_query": { "open_model_list": "Abra a lista de modelos", "select_provider": "Escolha um fornecedor de modelos.:", "select_model": "Escolha um modelo:", "change_model": "O modelo de diálogo já foi modificado para" } }; +const zhHans = { "env": { "system_init_message": "你是一个得力的助手" }, "command": { "help": { "summary": "当前支持以下命令:\n", "help": "获取命令帮助", "new": "发起新的对话", "start": "获取你的ID, 并发起新的对话", "img": "生成一张图片, 命令完整格式为 `/img 图片描述`, 例如`/img 月光下的沙滩`", "version": "获取当前版本号, 判断是否需要更新", "setenv": "设置用户配置,命令完整格式为 /setenv KEY=VALUE", "setenvs": '批量设置用户配置, 命令完整格式为 /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "删除用户配置,命令完整格式为 /delenv KEY", "clearenv": "清除所有用户配置", "system": "查看当前一些系统信息", "redo": "重做上一次的对话, /redo 加修改过的内容 或者 直接 /redo", "echo": "回显消息", "models": "切换对话模型" }, "new": { "new_chat_start": "新的对话已经开始" } }, "callback_query": { "open_model_list": "打开模型列表", "select_provider": "选择一个模型提供商:", "select_model": "选择一个模型:", "change_model": "对话模型已修改至" } }; +const zhHant = { "env": { "system_init_message": "你是一個得力的助手" }, "command": { "help": { "summary": "當前支持的命令如下:\n", "help": "獲取命令幫助", "new": "開始一個新對話", "start": "獲取您的ID並開始一個新對話", "img": "生成圖片,完整命令格式為`/img 圖片描述`,例如`/img 海灘月光`", "version": "獲取當前版本號確認是否需要更新", "setenv": "設置用戶配置,完整命令格式為/setenv KEY=VALUE", "setenvs": '批量設置用户配置, 命令完整格式為 /setenvs {"KEY1": "VALUE1", "KEY2": "VALUE2"}', "delenv": "刪除用戶配置,完整命令格式為/delenv KEY", "clearenv": "清除所有用戶配置", "system": "查看一些系統信息", "redo": "重做上一次的對話 /redo 加修改過的內容 或者 直接 /redo", "echo": "回显消息", "models": "切換對話模式" }, "new": { "new_chat_start": "開始一個新對話" } }, "callback_query": { "open_model_list": "打開模型清單", "select_provider": "選擇一個模型供應商:", "select_model": "選擇一個模型:", "change_model": "對話模型已經修改至" } }; +function loadI18n(lang) { + switch (lang?.toLowerCase()) { + case "cn": + case "zh-cn": + case "zh-hans": + return zhHans; + case "zh-tw": + case "zh-hk": + case "zh-mo": + case "zh-hant": + return zhHant; + case "pt": + case "pt-br": + return pt; + case "en": + case "en-us": + return en; + default: + return en; + } +} class ConfigMerger { static parseArray(raw) { raw = raw.trim(); @@ -192,8 +192,8 @@ class ConfigMerger { } } } -const BUILD_TIMESTAMP = 1731643971; -const BUILD_VERSION = "2977d62"; +const BUILD_TIMESTAMP = 1731649723; +const BUILD_VERSION = "76928f4"; function createAgentUserConfig() { return Object.assign( {}, @@ -224,6 +224,7 @@ class Environment extends EnvironmentConfig { PLUGINS_COMMAND = {}; DATABASE = null; API_GUARD = null; + CUSTOM_MESSAGE_RENDER = null; constructor() { super(); this.merge = this.merge.bind(this); @@ -320,6 +321,125 @@ class Environment extends EnvironmentConfig { } } const ENV = new Environment(); +class ShareContext { + botId; + botToken; + botName = null; + chatHistoryKey; + lastMessageKey; + configStoreKey; + groupAdminsKey; + constructor(token, update) { + const botId = Number.parseInt(token.split(":")[0]); + const telegramIndex = ENV.TELEGRAM_AVAILABLE_TOKENS.indexOf(token); + if (telegramIndex === -1) { + throw new Error("Token not allowed"); + } + if (ENV.TELEGRAM_BOT_NAME.length > telegramIndex) { + this.botName = ENV.TELEGRAM_BOT_NAME[telegramIndex]; + } + this.botToken = token; + this.botId = botId; + const id = update.chatID; + if (id === void 0 || id === null) { + throw new Error("Chat id not found"); + } + let historyKey = `history:${id}`; + let configStoreKey = `user_config:${id}`; + if (botId) { + historyKey += `:${botId}`; + configStoreKey += `:${botId}`; + } + switch (update.chatType) { + case "group": + case "supergroup": + if (!ENV.GROUP_CHAT_BOT_SHARE_MODE && update.fromUserID) { + historyKey += `:${update.fromUserID}`; + configStoreKey += `:${update.fromUserID}`; + } + this.groupAdminsKey = `group_admin:${id}`; + break; + } + if (update.isForum && update.isTopicMessage) { + if (update.messageThreadID) { + historyKey += `:${update.messageThreadID}`; + configStoreKey += `:${update.messageThreadID}`; + } + } + this.chatHistoryKey = historyKey; + this.lastMessageKey = `last_message_id:${historyKey}`; + this.configStoreKey = configStoreKey; + } +} +class WorkerContext { + USER_CONFIG; + SHARE_CONTEXT; + constructor(USER_CONFIG, SHARE_CONTEXT) { + this.USER_CONFIG = USER_CONFIG; + this.SHARE_CONTEXT = SHARE_CONTEXT; + this.execChangeAndSave = this.execChangeAndSave.bind(this); + } + static async from(token, update) { + const context = new UpdateContext(update); + const SHARE_CONTEXT = new ShareContext(token, context); + const USER_CONFIG = Object.assign({}, ENV.USER_CONFIG); + try { + const userConfig = JSON.parse(await ENV.DATABASE.get(SHARE_CONTEXT.configStoreKey)); + ConfigMerger.merge(USER_CONFIG, ConfigMerger.trim(userConfig, ENV.LOCK_USER_CONFIG_KEYS) || {}); + } catch (e) { + console.warn(e); + } + return new WorkerContext(USER_CONFIG, SHARE_CONTEXT); + } + async execChangeAndSave(values) { + for (const ent of Object.entries(values || {})) { + let [key, value] = ent; + key = ENV_KEY_MAPPER[key] || key; + if (ENV.LOCK_USER_CONFIG_KEYS.includes(key)) { + throw new Error(`Key ${key} is locked`); + } + const configKeys = Object.keys(this.USER_CONFIG || {}) || []; + if (!configKeys.includes(key)) { + throw new Error(`Key ${key} is not allowed`); + } + this.USER_CONFIG.DEFINE_KEYS.push(key); + ConfigMerger.merge(this.USER_CONFIG, { + [key]: value + }); + console.log("Update user config: ", key, this.USER_CONFIG[key]); + } + this.USER_CONFIG.DEFINE_KEYS = Array.from(new Set(this.USER_CONFIG.DEFINE_KEYS)); + await ENV.DATABASE.put( + this.SHARE_CONTEXT.configStoreKey, + JSON.stringify(ConfigMerger.trim(this.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS)) + ); + } +} +class UpdateContext { + fromUserID; + chatID; + chatType; + isForum; + isTopicMessage; + messageThreadID; + constructor(update) { + if (update.message) { + this.fromUserID = update.message.from?.id; + this.chatID = update.message.chat.id; + this.chatType = update.message.chat.type; + this.isForum = update.message.chat.is_forum; + this.isTopicMessage = update.message.is_topic_message; + this.messageThreadID = update.message.message_thread_id; + } else if (update.callback_query) { + this.fromUserID = update.callback_query.from.id; + this.chatID = update.callback_query.message?.chat.id; + this.chatType = update.callback_query.message?.chat.type; + this.isForum = update.callback_query.message?.chat.is_forum; + } else { + console.error("Unknown update type"); + } + } +} class APIClientBase { token; baseURL = ENV.TELEGRAM_API_DOMAIN; @@ -540,56 +660,6 @@ function formatInput(input, type) { return input; } } -const escapeChars = /([_*[\]()\\~`>#+\-=|{}.!])/g; -function escape(text) { - const lines = text.split("\n"); - const stack = []; - const result = []; - let lineTrim = ""; - for (const [i, line] of lines.entries()) { - lineTrim = line.trim(); - let startIndex = 0; - if (/^```.+/.test(lineTrim)) { - stack.push(i); - } else if (lineTrim === "```") { - if (stack.length) { - startIndex = stack.pop(); - if (!stack.length) { - const content = lines.slice(startIndex, i + 1).join("\n"); - result.push(handleEscape(content, "code")); - continue; - } - } else { - stack.push(i); - } - } - if (!stack.length) { - result.push(handleEscape(line)); - } - } - if (stack.length) { - const last = `${lines.slice(stack[0]).join("\n")} -\`\`\``; - result.push(handleEscape(last, "code")); - } - return result.join("\n"); -} -function handleEscape(text, type = "text") { - if (!text.trim()) { - return text; - } - if (type === "text") { - text = text.replace(escapeChars, "\\$1").replace(/\\\*\\\*(.*?[^\\])\\\*\\\*/g, "*$1*").replace(/\\_\\_(.*?[^\\])\\_\\_/g, "__$1__").replace(/\\_(.*?[^\\])\\_/g, "_$1_").replace(/\\~(.*?[^\\])\\~/g, "~$1~").replace(/\\\|\\\|(.*?[^\\])\\\|\\\|/g, "||$1||").replace(/\\\[([^\]]+?)\\\]\\\((.+?)\\\)/g, "[$1]($2)").replace(/\\`(.*?[^\\])\\`/g, "`$1`").replace(/\\\\\\([_*[\]()\\~`>#+\-=|{}.!])/g, "\\$1").replace(/^(\s*)\\(>.+\s*)$/gm, "$1$2").replace(/^(\s*)\\-\s*(.+)$/gm, "$1• $2").replace(/^((\\#){1,3}\s)(.+)/gm, "$1*$3*"); - } else { - const codeBlank = text.length - text.trimStart().length; - if (codeBlank > 0) { - const blankReg = new RegExp(`^\\s{${codeBlank}}`, "gm"); - text = text.replace(blankReg, ""); - } - text = text.trimEnd().replace(/([\\`])/g, "\\$1").replace(/^\\`\\`\\`([\s\S]+)\\`\\`\\`$/g, "```$1```"); - } - return text; -} class MessageContext { chat_id; message_id = null; @@ -696,8 +766,8 @@ class MessageSender { } } renderMessage(parse_mode, message) { - if (parse_mode === "MarkdownV2") { - return escape(message); + if (ENV.CUSTOM_MESSAGE_RENDER) { + return ENV.CUSTOM_MESSAGE_RENDER(parse_mode, message); } return message; } @@ -1787,49 +1857,6 @@ function loadImageGen(context) { } return null; } -function isTelegramChatTypeGroup(type) { - return type === "group" || type === "supergroup"; -} -async function setUserConfig(values, context) { - for (const ent of Object.entries(values || {})) { - let [key, value] = ent; - key = ENV_KEY_MAPPER[key] || key; - if (ENV.LOCK_USER_CONFIG_KEYS.includes(key)) { - throw new Error(`Key ${key} is locked`); - } - const configKeys = Object.keys(context.USER_CONFIG || {}) || []; - if (!configKeys.includes(key)) { - throw new Error(`Key ${key} is not allowed`); - } - context.USER_CONFIG.DEFINE_KEYS.push(key); - ConfigMerger.merge(context.USER_CONFIG, { - [key]: value - }); - console.log("Update user config: ", key, context.USER_CONFIG[key]); - } - context.USER_CONFIG.DEFINE_KEYS = Array.from(new Set(context.USER_CONFIG.DEFINE_KEYS)); - await ENV.DATABASE.put( - context.SHARE_CONTEXT.configStoreKey, - JSON.stringify(ConfigMerger.trim(context.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS)) - ); -} -const TELEGRAM_AUTH_CHECKER = { - default(chatType) { - if (isTelegramChatTypeGroup(chatType)) { - return ["administrator", "creator"]; - } - return null; - }, - shareModeGroup(chatType) { - if (isTelegramChatTypeGroup(chatType)) { - if (!ENV.GROUP_CHAT_BOT_SHARE_MODE) { - return null; - } - return ["administrator", "creator"]; - } - return null; - } -}; function tokensCounter() { return (text) => { return text.length; @@ -1910,7 +1937,27 @@ async function requestCompletionsFromLLM(params, context, agent, modifier, onStr } return text; } -async function chatWithLLM(message, params, context, modifier) { +const TELEGRAM_AUTH_CHECKER = { + default(chatType) { + if (isGroupChat(chatType)) { + return ["administrator", "creator"]; + } + return null; + }, + shareModeGroup(chatType) { + if (isGroupChat(chatType)) { + if (!ENV.GROUP_CHAT_BOT_SHARE_MODE) { + return null; + } + return ["administrator", "creator"]; + } + return null; + } +}; +function isGroupChat(type) { + return type === "group" || type === "supergroup"; +} +async function chatWithMessage(message, params, context, modifier) { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); try { try { @@ -1997,24 +2044,22 @@ function extractImageFileID(message) { } return null; } -class ChatHandler { - handle = async (message, context) => { - const text = message.text || message.caption || ""; - const params = { - role: "user", - content: text - }; - const url = await extractImageURL(extractImageFileID(message), context); - if (url) { - const contents = new Array(); - if (text) { - contents.push({ type: "text", text }); - } - contents.push({ type: "image", image: url }); - params.content = contents; - } - return chatWithLLM(message, params, context, null); +async function extractUserMessageItem(message, context) { + const text = message.text || message.caption || ""; + const params = { + role: "user", + content: text }; + const url = await extractImageURL(extractImageFileID(message), context); + if (url) { + const contents = new Array(); + if (text) { + contents.push({ type: "text", text }); + } + contents.push({ type: "image", image: url }); + params.content = contents; + } + return params; } class ImgCommandHandler { command = "/img"; @@ -2082,7 +2127,7 @@ class BaseNewCommandHandler { chat_id: message.chat.id, text }; - if (ENV.SHOW_REPLY_BUTTON && !isTelegramChatTypeGroup(message.chat.type)) { + if (ENV.SHOW_REPLY_BUTTON && !isGroupChat(message.chat.type)) { params.reply_markup = { keyboard: [[{ text: "/new" }, { text: "/redo" }]], selective: true, @@ -2123,7 +2168,7 @@ class SetEnvCommandHandler { const key = subcommand.slice(0, kv); const value = subcommand.slice(kv + 1); try { - await setUserConfig({ [key]: value }, context); + await context.execChangeAndSave({ [key]: value }); return sender.sendPlainText("Update user config success"); } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); @@ -2137,7 +2182,7 @@ class SetEnvsCommandHandler { const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message); try { const values = JSON.parse(subcommand); - await setUserConfig(values, context); + await context.execChangeAndSave(values); return sender.sendPlainText("Update user config success"); } catch (e) { return sender.sendPlainText(`ERROR: ${e.message}`); @@ -2228,16 +2273,14 @@ class SystemCommandHandler { if (ENV.DEV_MODE) { const shareCtx = { ...context.SHARE_CONTEXT }; shareCtx.botToken = "******"; - context.USER_CONFIG.OPENAI_API_KEY = ["******"]; + context.USER_CONFIG.ANTHROPIC_API_KEY = "******"; context.USER_CONFIG.AZURE_API_KEY = "******"; - context.USER_CONFIG.AZURE_COMPLETIONS_API = "******"; - context.USER_CONFIG.AZURE_DALLE_API = "******"; - context.USER_CONFIG.CLOUDFLARE_ACCOUNT_ID = "******"; - context.USER_CONFIG.CLOUDFLARE_TOKEN = "******"; + context.USER_CONFIG.COHERE_API_KEY = "******"; context.USER_CONFIG.GOOGLE_API_KEY = "******"; context.USER_CONFIG.MISTRAL_API_KEY = "******"; - context.USER_CONFIG.COHERE_API_KEY = "******"; - context.USER_CONFIG.ANTHROPIC_API_KEY = "******"; + context.USER_CONFIG.OPENAI_API_KEY = ["******"]; + context.USER_CONFIG.CLOUDFLARE_ACCOUNT_ID = "******"; + context.USER_CONFIG.CLOUDFLARE_TOKEN = "******"; const config = ConfigMerger.trim(context.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS); msg = `
 ${msg}`;
@@ -2282,7 +2325,7 @@ class RedoCommandHandler {
       }
       return { history: historyCopy, message: nextMessage };
     };
-    return chatWithLLM(message, null, context, mf);
+    return chatWithMessage(message, null, context, mf);
   };
 }
 class ModelsCommandHandler {
@@ -2470,101 +2513,6 @@ function commandsDocument() {
     };
   }).filter((item) => item.description !== "");
 }
-class ShareContext {
-  botId;
-  botToken;
-  botName = null;
-  chatHistoryKey;
-  lastMessageKey;
-  configStoreKey;
-  groupAdminsKey;
-  constructor(token, update) {
-    const botId = Number.parseInt(token.split(":")[0]);
-    const telegramIndex = ENV.TELEGRAM_AVAILABLE_TOKENS.indexOf(token);
-    if (telegramIndex === -1) {
-      throw new Error("Token not allowed");
-    }
-    if (ENV.TELEGRAM_BOT_NAME.length > telegramIndex) {
-      this.botName = ENV.TELEGRAM_BOT_NAME[telegramIndex];
-    }
-    this.botToken = token;
-    this.botId = botId;
-    const id = update.chatID;
-    if (id === void 0 || id === null) {
-      throw new Error("Chat id not found");
-    }
-    let historyKey = `history:${id}`;
-    let configStoreKey = `user_config:${id}`;
-    if (botId) {
-      historyKey += `:${botId}`;
-      configStoreKey += `:${botId}`;
-    }
-    switch (update.chatType) {
-      case "group":
-      case "supergroup":
-        if (!ENV.GROUP_CHAT_BOT_SHARE_MODE && update.fromUserID) {
-          historyKey += `:${update.fromUserID}`;
-          configStoreKey += `:${update.fromUserID}`;
-        }
-        this.groupAdminsKey = `group_admin:${id}`;
-        break;
-    }
-    if (update.isForum && update.isTopicMessage) {
-      if (update.messageThreadID) {
-        historyKey += `:${update.messageThreadID}`;
-        configStoreKey += `:${update.messageThreadID}`;
-      }
-    }
-    this.chatHistoryKey = historyKey;
-    this.lastMessageKey = `last_message_id:${historyKey}`;
-    this.configStoreKey = configStoreKey;
-  }
-}
-class WorkerContext {
-  USER_CONFIG;
-  SHARE_CONTEXT;
-  constructor(USER_CONFIG, SHARE_CONTEXT) {
-    this.USER_CONFIG = USER_CONFIG;
-    this.SHARE_CONTEXT = SHARE_CONTEXT;
-  }
-  static async from(token, update) {
-    const context = new UpdateContext(update);
-    const SHARE_CONTEXT = new ShareContext(token, context);
-    const USER_CONFIG = Object.assign({}, ENV.USER_CONFIG);
-    try {
-      const userConfig = JSON.parse(await ENV.DATABASE.get(SHARE_CONTEXT.configStoreKey));
-      ConfigMerger.merge(USER_CONFIG, ConfigMerger.trim(userConfig, ENV.LOCK_USER_CONFIG_KEYS) || {});
-    } catch (e) {
-      console.warn(e);
-    }
-    return new WorkerContext(USER_CONFIG, SHARE_CONTEXT);
-  }
-}
-class UpdateContext {
-  fromUserID;
-  chatID;
-  chatType;
-  isForum;
-  isTopicMessage;
-  messageThreadID;
-  constructor(update) {
-    if (update.message) {
-      this.fromUserID = update.message.from?.id;
-      this.chatID = update.message.chat.id;
-      this.chatType = update.message.chat.type;
-      this.isForum = update.message.chat.is_forum;
-      this.isTopicMessage = update.message.is_topic_message;
-      this.messageThreadID = update.message.message_thread_id;
-    } else if (update.callback_query) {
-      this.fromUserID = update.callback_query.from.id;
-      this.chatID = update.callback_query.message?.chat.id;
-      this.chatType = update.callback_query.message?.chat.type;
-      this.isForum = update.callback_query.message?.chat.is_forum;
-    } else {
-      console.error("Unknown update type");
-    }
-  }
-}
 function checkMention(content, entities, botName, botId) {
   let isMention = false;
   for (const entity of entities) {
@@ -2598,7 +2546,7 @@ function checkMention(content, entities, botName, botId) {
 }
 class GroupMention {
   handle = async (message, context) => {
-    if (!isTelegramChatTypeGroup(message.chat.type)) {
+    if (!isGroupChat(message.chat.type)) {
       return null;
     }
     const replyMe = `${message.reply_to_message?.from?.id}` === `${context.SHARE_CONTEXT.botId}`;
@@ -2765,10 +2713,10 @@ class ModelChangeCallbackQueryHandler {
     if (!chatAgent?.modelKey) {
       throw new Error(`modelKey not found: ${agent}`);
     }
-    await setUserConfig({
+    await context.execChangeAndSave({
       AI_PROVIDER: agent,
       [chatAgent.modelKey]: model
-    }, context);
+    });
     console.log("Change model:", agent, model);
     const message = {
       chat_id: query.message.chat.id,
@@ -2856,7 +2804,7 @@ class WhiteListFilter {
       }
       return null;
     }
-    if (isTelegramChatTypeGroup(chatType)) {
+    if (isGroupChat(chatType)) {
       if (!ENV.GROUP_CHAT_BOT_ENABLE) {
         throw new Error("Not support");
       }
@@ -2962,6 +2910,12 @@ class CommandHandler {
     return null;
   };
 }
+class ChatHandler {
+  handle = async (message, context) => {
+    const params = await extractUserMessageItem(message, context);
+    return chatWithMessage(message, params, context, null);
+  };
+}
 const SHARE_HANDLER = [
   new EnvChecker(),
   new WhiteListFilter(),
diff --git a/packages/lib/core/src/agent/agent.test.ts b/packages/lib/core/src/agent/agent.test.ts
index 7a6e3116f..69d8cbf83 100644
--- a/packages/lib/core/src/agent/agent.test.ts
+++ b/packages/lib/core/src/agent/agent.test.ts
@@ -1,5 +1,5 @@
 import type { LLMChatParams } from './types';
-import { ENV } from '../config/env';
+import { ENV } from '../config';
 import { loadChatLLM } from './agent';
 import '../config/env.test';
 
diff --git a/packages/lib/core/src/agent/agent.ts b/packages/lib/core/src/agent/agent.ts
index f7abaf4e2..dc878e859 100644
--- a/packages/lib/core/src/agent/agent.ts
+++ b/packages/lib/core/src/agent/agent.ts
@@ -1,4 +1,4 @@
-import type { AgentUserConfig } from '../config/env';
+import type { AgentUserConfig } from '../config';
 import type { ChatAgent, ImageAgent } from './types';
 import { Anthropic } from './anthropic';
 import { AzureChatAI, AzureImageAI } from './azure';
diff --git a/packages/lib/core/src/agent/anthropic.ts b/packages/lib/core/src/agent/anthropic.ts
index e50c8571a..990075dc9 100644
--- a/packages/lib/core/src/agent/anthropic.ts
+++ b/packages/lib/core/src/agent/anthropic.ts
@@ -1,4 +1,4 @@
-import type { AgentUserConfig } from '../config/env';
+import type { AgentUserConfig } from '../config';
 import type { SseChatCompatibleOptions } from './request';
 import type { SSEMessage, SSEParserResult } from './stream';
 import type {
@@ -8,7 +8,7 @@ import type {
     HistoryItem,
     LLMChatParams,
 } from './types';
-import { ENV } from '../config/env';
+import { ENV } from '../config';
 import { imageToBase64String } from '../utils/image';
 import { requestChatCompletions } from './request';
 import { Stream } from './stream';
diff --git a/packages/lib/core/src/agent/azure.ts b/packages/lib/core/src/agent/azure.ts
index e88b41b3e..630606abb 100644
--- a/packages/lib/core/src/agent/azure.ts
+++ b/packages/lib/core/src/agent/azure.ts
@@ -1,4 +1,4 @@
-import type { AgentUserConfig } from '../config/env';
+import type { AgentUserConfig } from '../config';
 import type {
     ChatAgent,
     ChatAgentResponse,
diff --git a/packages/lib/core/src/agent/chat.ts b/packages/lib/core/src/agent/chat.ts
index f176b764a..eec7f7f72 100644
--- a/packages/lib/core/src/agent/chat.ts
+++ b/packages/lib/core/src/agent/chat.ts
@@ -1,6 +1,6 @@
-import type { WorkerContext } from '../config/context';
+import type { WorkerContext } from '../config';
 import type { ChatAgent, HistoryItem, HistoryModifier, LLMChatParams, UserMessageItem } from './types';
-import { ENV } from '../config/env';
+import { ENV } from '../config';
 import { extractTextContent } from './utils';
 
 /**
diff --git a/packages/lib/core/src/agent/cohere.ts b/packages/lib/core/src/agent/cohere.ts
index ea24a0623..0b4ea2a99 100644
--- a/packages/lib/core/src/agent/cohere.ts
+++ b/packages/lib/core/src/agent/cohere.ts
@@ -1,4 +1,4 @@
-import type { AgentUserConfig } from '../config/env';
+import type { AgentUserConfig } from '../config';
 import type { SseChatCompatibleOptions } from './request';
 import type { ChatAgent, ChatAgentResponse, ChatStreamTextHandler, LLMChatParams } from './types';
 import { renderOpenAIMessages } from './openai';
diff --git a/packages/lib/core/src/agent/gemini.ts b/packages/lib/core/src/agent/gemini.ts
index 4c94f0c07..c350a8889 100644
--- a/packages/lib/core/src/agent/gemini.ts
+++ b/packages/lib/core/src/agent/gemini.ts
@@ -1,4 +1,4 @@
-import type { AgentUserConfig } from '../config/env';
+import type { AgentUserConfig } from '../config';
 import type { ChatAgent, ChatAgentResponse, ChatStreamTextHandler, LLMChatParams } from './types';
 import { renderOpenAIMessages } from './openai';
 import { requestChatCompletions } from './request';
diff --git a/packages/lib/core/src/agent/index.ts b/packages/lib/core/src/agent/index.ts
index bfdf74c76..40931e1a3 100644
--- a/packages/lib/core/src/agent/index.ts
+++ b/packages/lib/core/src/agent/index.ts
@@ -1,3 +1,4 @@
 export * from './agent';
+export * from './chat';
 export * from './request';
 export * from './types';
diff --git a/packages/lib/core/src/agent/mistralai.ts b/packages/lib/core/src/agent/mistralai.ts
index 8e5122bc0..78f064088 100644
--- a/packages/lib/core/src/agent/mistralai.ts
+++ b/packages/lib/core/src/agent/mistralai.ts
@@ -1,4 +1,4 @@
-import type { AgentUserConfig } from '../config/env';
+import type { AgentUserConfig } from '../config';
 import type { ChatAgent, ChatAgentResponse, ChatStreamTextHandler, LLMChatParams } from './types';
 import { renderOpenAIMessages } from './openai';
 import { requestChatCompletions } from './request';
diff --git a/packages/lib/core/src/agent/openai.ts b/packages/lib/core/src/agent/openai.ts
index de74189c9..cc8f4c513 100644
--- a/packages/lib/core/src/agent/openai.ts
+++ b/packages/lib/core/src/agent/openai.ts
@@ -1,4 +1,4 @@
-import type { AgentUserConfig } from '../config/env';
+import type { AgentUserConfig } from '../config';
 import type {
     ChatAgent,
     ChatAgentResponse,
@@ -7,7 +7,7 @@ import type {
     ImageAgent,
     LLMChatParams,
 } from './types';
-import { ENV } from '../config/env';
+import { ENV } from '../config';
 import { imageToBase64String } from '../utils/image';
 import { requestChatCompletions } from './request';
 import { convertStringToResponseMessages, extractImageContent, loadModelsList } from './utils';
diff --git a/packages/lib/core/src/agent/request.ts b/packages/lib/core/src/agent/request.ts
index 9c2ec0d05..612a6997b 100644
--- a/packages/lib/core/src/agent/request.ts
+++ b/packages/lib/core/src/agent/request.ts
@@ -1,5 +1,5 @@
 import type { ChatStreamTextHandler } from './types';
-import { ENV } from '../config/env';
+import { ENV } from '../config';
 import { Stream } from './stream';
 
 export interface SseChatCompatibleOptions {
diff --git a/packages/lib/core/src/agent/types.ts b/packages/lib/core/src/agent/types.ts
index d1fcae3df..88f8b8a07 100644
--- a/packages/lib/core/src/agent/types.ts
+++ b/packages/lib/core/src/agent/types.ts
@@ -1,9 +1,19 @@
-import type { AgentUserConfig } from '../config/env';
-import type { CoreAssistantMessage, CoreSystemMessage, CoreToolMessage, CoreUserMessage, DataContent } from './message';
+import type { AgentUserConfig } from '../config';
+import type {
+    CoreAssistantMessage,
+    CoreSystemMessage,
+    CoreToolMessage,
+    CoreUserMessage,
+    DataContent,
+    FilePart,
+    ImagePart,
+    TextPart,
+} from './message';
 //  当使用 `ai` 包时,取消注释以下行并注释掉上一行
 // import type { CoreAssistantMessage, CoreSystemMessage, CoreToolMessage, CoreUserMessage, DataContent } from 'ai';
 
 export type DataItemContent = DataContent;
+export type UserContentPart = TextPart | ImagePart | FilePart;
 
 export type SystemMessageItem = CoreSystemMessage;
 export type UserMessageItem = CoreUserMessage;
diff --git a/packages/lib/core/src/agent/workersai.ts b/packages/lib/core/src/agent/workersai.ts
index f30268377..14ac65c16 100644
--- a/packages/lib/core/src/agent/workersai.ts
+++ b/packages/lib/core/src/agent/workersai.ts
@@ -1,4 +1,4 @@
-import type { AgentUserConfig } from '../config/env';
+import type { AgentUserConfig } from '../config';
 import type { SseChatCompatibleOptions } from './request';
 import type {
     ChatAgent,
diff --git a/packages/lib/core/src/config/context.ts b/packages/lib/core/src/config/context.ts
index e47a1eddd..f4bf0bc2f 100644
--- a/packages/lib/core/src/config/context.ts
+++ b/packages/lib/core/src/config/context.ts
@@ -1,6 +1,6 @@
 import type * as Telegram from 'telegram-bot-api-types';
 import type { AgentUserConfig } from './env';
-import { ENV } from './env';
+import { ENV, ENV_KEY_MAPPER } from './env';
 import { ConfigMerger } from './merger';
 
 export class ShareContext {
@@ -84,6 +84,7 @@ export class WorkerContext {
     constructor(USER_CONFIG: AgentUserConfig, SHARE_CONTEXT: ShareContext) {
         this.USER_CONFIG = USER_CONFIG;
         this.SHARE_CONTEXT = SHARE_CONTEXT;
+        this.execChangeAndSave = this.execChangeAndSave.bind(this);
     }
 
     static async from(token: string, update: Telegram.Update): Promise {
@@ -98,6 +99,30 @@ export class WorkerContext {
         }
         return new WorkerContext(USER_CONFIG, SHARE_CONTEXT);
     }
+
+    async execChangeAndSave(values: Record): Promise {
+        for (const ent of Object.entries(values || {})) {
+            let [key, value] = ent;
+            key = ENV_KEY_MAPPER[key] || key;
+            if (ENV.LOCK_USER_CONFIG_KEYS.includes(key)) {
+                throw new Error(`Key ${key} is locked`);
+            }
+            const configKeys = Object.keys(this.USER_CONFIG || {}) || [];
+            if (!configKeys.includes(key)) {
+                throw new Error(`Key ${key} is not allowed`);
+            }
+            this.USER_CONFIG.DEFINE_KEYS.push(key);
+            ConfigMerger.merge(this.USER_CONFIG, {
+                [key]: value,
+            });
+            console.log('Update user config: ', key, this.USER_CONFIG[key]);
+        }
+        this.USER_CONFIG.DEFINE_KEYS = Array.from(new Set(this.USER_CONFIG.DEFINE_KEYS));
+        await ENV.DATABASE.put(
+            this.SHARE_CONTEXT.configStoreKey,
+            JSON.stringify(ConfigMerger.trim(this.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS)),
+        );
+    }
 }
 
 class UpdateContext {
diff --git a/packages/lib/core/src/config/env.ts b/packages/lib/core/src/config/env.ts
index dc45d1d36..39d8cf4f4 100644
--- a/packages/lib/core/src/config/env.ts
+++ b/packages/lib/core/src/config/env.ts
@@ -50,6 +50,8 @@ export const ENV_KEY_MAPPER: Record = {
     WORKERS_AI_MODEL: 'WORKERS_CHAT_MODEL',
 };
 
+export type CustomMessageRender = (mode: string | null, message: string) => string;
+
 class Environment extends EnvironmentConfig {
     // -- 版本数据 --
     //
@@ -68,6 +70,8 @@ class Environment extends EnvironmentConfig {
     DATABASE: KVNamespace = null as any;
     API_GUARD: APIGuard | null = null;
 
+    CUSTOM_MESSAGE_RENDER: CustomMessageRender | null = null;
+
     constructor() {
         super();
         this.merge = this.merge.bind(this);
diff --git a/packages/lib/core/src/config/index.ts b/packages/lib/core/src/config/index.ts
new file mode 100644
index 000000000..2b73b9de7
--- /dev/null
+++ b/packages/lib/core/src/config/index.ts
@@ -0,0 +1,6 @@
+export * from './config';
+export * from './context';
+export * from './env';
+export * from './merger';
+export * from './types';
+export * from './version';
diff --git a/packages/lib/core/src/config/version.ts b/packages/lib/core/src/config/version.ts
index 083b43db7..9e82618da 100644
--- a/packages/lib/core/src/config/version.ts
+++ b/packages/lib/core/src/config/version.ts
@@ -1,2 +1,2 @@
-export const BUILD_TIMESTAMP = 1731643971;
-export const BUILD_VERSION = '2977d62';
+export const BUILD_TIMESTAMP = 1731649723;
+export const BUILD_VERSION = '76928f4';
diff --git a/packages/lib/core/src/index.ts b/packages/lib/core/src/index.ts
index a91b9b62d..5bdb644aa 100644
--- a/packages/lib/core/src/index.ts
+++ b/packages/lib/core/src/index.ts
@@ -1,12 +1,10 @@
-import { ENV } from './config/env';
+import { ENV } from './config';
 import { createRouter } from './route';
 
 export * from './agent';
-export * from './config/env';
+export * from './config';
 export * from './route';
-export * from './telegram/api';
-export * from './telegram/handler';
-export * from '@chatgpt-telegram-workers/plugins';
+export * from './telegram';
 
 export const Workers = {
     async fetch(request: Request, env: any): Promise {
diff --git a/packages/lib/core/src/route/index.ts b/packages/lib/core/src/route/index.ts
index 6579d398c..51601ac63 100644
--- a/packages/lib/core/src/route/index.ts
+++ b/packages/lib/core/src/route/index.ts
@@ -1,6 +1,6 @@
 import type * as Telegram from 'telegram-bot-api-types';
 import type { RouterRequest } from '../utils/router';
-import { ENV } from '../config/env';
+import { ENV } from '../config';
 import { createTelegramBotAPI } from '../telegram/api';
 import { commandsBindScope, commandsDocument } from '../telegram/command';
 import { handleUpdate } from '../telegram/handler';
diff --git a/packages/lib/core/src/telegram/api/index.ts b/packages/lib/core/src/telegram/api/index.ts
index 244886e27..23fbc634a 100644
--- a/packages/lib/core/src/telegram/api/index.ts
+++ b/packages/lib/core/src/telegram/api/index.ts
@@ -1,5 +1,5 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import { ENV } from '../../config/env';
+import { ENV } from '../../config';
 
 class APIClientBase {
     readonly token: string;
diff --git a/packages/lib/core/src/telegram/auth/auth.ts b/packages/lib/core/src/telegram/auth/index.ts
similarity index 68%
rename from packages/lib/core/src/telegram/auth/auth.ts
rename to packages/lib/core/src/telegram/auth/index.ts
index 3c61bbcf0..84a1fb609 100644
--- a/packages/lib/core/src/telegram/auth/auth.ts
+++ b/packages/lib/core/src/telegram/auth/index.ts
@@ -1,15 +1,14 @@
-import { ENV } from '../../config/env';
-import { isTelegramChatTypeGroup } from '../utils/utils';
+import { ENV } from '../../config';
 
 export const TELEGRAM_AUTH_CHECKER = {
     default(chatType: string): string[] | null {
-        if (isTelegramChatTypeGroup(chatType)) {
+        if (isGroupChat(chatType)) {
             return ['administrator', 'creator'];
         }
         return null;
     },
     shareModeGroup(chatType: string): string[] | null {
-        if (isTelegramChatTypeGroup(chatType)) {
+        if (isGroupChat(chatType)) {
             // 每个人在群里有上下文的时候,不限制
             if (!ENV.GROUP_CHAT_BOT_SHARE_MODE) {
                 return null;
@@ -19,3 +18,7 @@ export const TELEGRAM_AUTH_CHECKER = {
         return null;
     },
 };
+
+export function isGroupChat(type: string): boolean {
+    return type === 'group' || type === 'supergroup';
+}
diff --git a/packages/lib/core/src/telegram/callback_query/index.ts b/packages/lib/core/src/telegram/callback_query/index.ts
index 7f9ab4ee7..f04df957b 100644
--- a/packages/lib/core/src/telegram/callback_query/index.ts
+++ b/packages/lib/core/src/telegram/callback_query/index.ts
@@ -1,7 +1,7 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
+import type { WorkerContext } from '../../config';
 import { loadChatRoleWithContext } from '../command/auth';
-import { MessageSender } from '../utils/send';
+import { MessageSender } from '../sender';
 import { AgentListCallbackQueryHandler, ModelChangeCallbackQueryHandler, ModelListCallbackQueryHandler } from './system';
 
 const QUERY_HANDLERS = [
diff --git a/packages/lib/core/src/telegram/callback_query/system.ts b/packages/lib/core/src/telegram/callback_query/system.ts
index a309c936e..5c18051bf 100644
--- a/packages/lib/core/src/telegram/callback_query/system.ts
+++ b/packages/lib/core/src/telegram/callback_query/system.ts
@@ -1,12 +1,10 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
-import type { AgentUserConfig } from '../../config/env';
+import type { AgentUserConfig, WorkerContext } from '../../config';
 import type { CallbackQueryHandler } from './types';
 import { CHAT_AGENTS, loadChatLLM } from '../../agent';
-import { ENV } from '../../config/env';
-import { TELEGRAM_AUTH_CHECKER } from '../auth/auth';
-import { MessageSender } from '../utils/send';
-import { setUserConfig } from '../utils/utils';
+import { ENV } from '../../config';
+import { TELEGRAM_AUTH_CHECKER } from '../auth';
+import { MessageSender } from '../sender';
 
 export class AgentListCallbackQueryHandler implements CallbackQueryHandler {
     prefix = 'al:';
@@ -145,10 +143,10 @@ export class ModelChangeCallbackQueryHandler implements CallbackQueryHandler {
         if (!chatAgent?.modelKey) {
             throw new Error(`modelKey not found: ${agent}`);
         }
-        await setUserConfig({
+        await context.execChangeAndSave({
             AI_PROVIDER: agent,
             [chatAgent.modelKey]: model,
-        }, context);
+        });
         console.log('Change model:', agent, model);
         const message: Telegram.EditMessageTextParams = {
             chat_id: query.message.chat.id,
diff --git a/packages/lib/core/src/telegram/callback_query/types.ts b/packages/lib/core/src/telegram/callback_query/types.ts
index d0d88dc05..8f2475232 100644
--- a/packages/lib/core/src/telegram/callback_query/types.ts
+++ b/packages/lib/core/src/telegram/callback_query/types.ts
@@ -1,5 +1,5 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
+import type { WorkerContext } from '../../config';
 
 export interface CallbackQueryHandler {
     prefix: string;
diff --git a/packages/lib/core/src/telegram/handler/chat.ts b/packages/lib/core/src/telegram/chat/index.ts
similarity index 69%
rename from packages/lib/core/src/telegram/handler/chat.ts
rename to packages/lib/core/src/telegram/chat/index.ts
index 2072a9a51..086482049 100644
--- a/packages/lib/core/src/telegram/handler/chat.ts
+++ b/packages/lib/core/src/telegram/chat/index.ts
@@ -1,16 +1,12 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { HistoryModifier, UserMessageItem } from '../../agent';
-import type { StreamResultHandler } from '../../agent/chat';
-import type { FilePart, ImagePart, TextPart } from '../../agent/message';
-import type { WorkerContext } from '../../config/context';
-import type { MessageHandler } from './types';
-import { loadChatLLM } from '../../agent';
-import { requestCompletionsFromLLM } from '../../agent/chat';
-import { ENV } from '../../config/env';
+import type { HistoryModifier, StreamResultHandler, UserContentPart, UserMessageItem } from '../../agent';
+import type { WorkerContext } from '../../config';
+import { loadChatLLM, requestCompletionsFromLLM } from '../../agent';
+import { ENV } from '../../config';
 import { createTelegramBotAPI } from '../api';
-import { MessageSender } from '../utils/send';
+import { MessageSender } from '../sender';
 
-export async function chatWithLLM(message: Telegram.Message, params: UserMessageItem | null, context: WorkerContext, modifier: HistoryModifier | null): Promise {
+export async function chatWithMessage(message: Telegram.Message, params: UserMessageItem | null, context: WorkerContext, modifier: HistoryModifier | null): Promise {
     const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message);
     try {
         try {
@@ -77,7 +73,7 @@ export async function chatWithLLM(message: Telegram.Message, params: UserMessage
     }
 }
 
-async function extractImageURL(fileId: string | null, context: WorkerContext): Promise {
+export async function extractImageURL(fileId: string | null, context: WorkerContext): Promise {
     if (!fileId) {
         return null;
     }
@@ -93,7 +89,7 @@ async function extractImageURL(fileId: string | null, context: WorkerContext): P
     return null;
 }
 
-function extractImageFileID(message: Telegram.Message): string | null {
+export function extractImageFileID(message: Telegram.Message): string | null {
     if (message.photo && message.photo.length > 0) {
         const offset = ENV.TELEGRAM_PHOTO_SIZE_OFFSET;
         const length = message.photo.length;
@@ -105,22 +101,20 @@ function extractImageFileID(message: Telegram.Message): string | null {
     return null;
 }
 
-export class ChatHandler implements MessageHandler {
-    handle = async (message: Telegram.Message, context: WorkerContext): Promise => {
-        const text = message.text || message.caption || '';
-        const params: UserMessageItem = {
-            role: 'user',
-            content: text,
-        };
-        const url = await extractImageURL(extractImageFileID(message), context);
-        if (url) {
-            const contents = new Array();
-            if (text) {
-                contents.push({ type: 'text', text });
-            }
-            contents.push({ type: 'image', image: url });
-            params.content = contents;
-        }
-        return chatWithLLM(message, params, context, null);
+export async function extractUserMessageItem(message: Telegram.Message, context: WorkerContext): Promise {
+    const text = message.text || message.caption || '';
+    const params: UserMessageItem = {
+        role: 'user',
+        content: text,
     };
+    const url = await extractImageURL(extractImageFileID(message), context);
+    if (url) {
+        const contents = new Array();
+        if (text) {
+            contents.push({ type: 'text', text });
+        }
+        contents.push({ type: 'image', image: url });
+        params.content = contents;
+    }
+    return params;
 }
diff --git a/packages/lib/core/src/telegram/command/auth.ts b/packages/lib/core/src/telegram/command/auth.ts
index e51ebc584..9e7b3a350 100644
--- a/packages/lib/core/src/telegram/command/auth.ts
+++ b/packages/lib/core/src/telegram/command/auth.ts
@@ -1,6 +1,6 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
-import { ENV } from '../../config/env';
+import type { WorkerContext } from '../../config';
+import { ENV } from '../../config';
 import { createTelegramBotAPI } from '../api';
 
 export async function loadChatRoleWithContext(chatId: number, speakerId: number, context: WorkerContext): Promise {
diff --git a/packages/lib/core/src/telegram/command/index.ts b/packages/lib/core/src/telegram/command/index.ts
index 38c7d35af..8590c89c0 100644
--- a/packages/lib/core/src/telegram/command/index.ts
+++ b/packages/lib/core/src/telegram/command/index.ts
@@ -1,10 +1,10 @@
 import type { RequestTemplate } from '@chatgpt-telegram-workers/plugins';
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
+import type { WorkerContext } from '../../config';
 import type { CommandHandler } from './types';
 import { executeRequest, formatInput } from '@chatgpt-telegram-workers/plugins';
-import { ENV } from '../../config/env';
-import { MessageSender } from '../utils/send';
+import { ENV } from '../../config';
+import { MessageSender } from '../sender';
 import { loadChatRoleWithContext } from './auth';
 import {
     ClearEnvCommandHandler,
diff --git a/packages/lib/core/src/telegram/command/system.ts b/packages/lib/core/src/telegram/command/system.ts
index ba4270e8d..7c5e6153c 100644
--- a/packages/lib/core/src/telegram/command/system.ts
+++ b/packages/lib/core/src/telegram/command/system.ts
@@ -1,15 +1,13 @@
 import type * as Telegram from 'telegram-bot-api-types';
 import type { HistoryItem, HistoryModifierResult, UserMessageItem } from '../../agent';
-import type { WorkerContext } from '../../config/context';
+import type { WorkerContext } from '../../config';
 import type { CommandHandler } from './types';
 import { loadChatLLM, loadImageGen } from '../../agent';
-import { ENV } from '../../config/env';
-import { ConfigMerger } from '../../config/merger';
+import { ConfigMerger, ENV } from '../../config';
 import { createTelegramBotAPI } from '../api';
-import { TELEGRAM_AUTH_CHECKER } from '../auth/auth';
-import { chatWithLLM } from '../handler/chat';
-import { MessageSender } from '../utils/send';
-import { isTelegramChatTypeGroup, setUserConfig } from '../utils/utils';
+import { isGroupChat, TELEGRAM_AUTH_CHECKER } from '../auth';
+import { chatWithMessage } from '../chat';
+import { MessageSender } from '../sender';
 
 export class ImgCommandHandler implements CommandHandler {
     command = '/img';
@@ -75,7 +73,7 @@ class BaseNewCommandHandler {
             chat_id: message.chat.id,
             text,
         };
-        if (ENV.SHOW_REPLY_BUTTON && !isTelegramChatTypeGroup(message.chat.type)) {
+        if (ENV.SHOW_REPLY_BUTTON && !isGroupChat(message.chat.type)) {
             params.reply_markup = {
                 keyboard: [[{ text: '/new' }, { text: '/redo' }]],
                 selective: true,
@@ -119,7 +117,7 @@ export class SetEnvCommandHandler implements CommandHandler {
         const key = subcommand.slice(0, kv);
         const value = subcommand.slice(kv + 1);
         try {
-            await setUserConfig({ [key]: value }, context);
+            await context.execChangeAndSave({ [key]: value });
             return sender.sendPlainText('Update user config success');
         } catch (e) {
             return sender.sendPlainText(`ERROR: ${(e as Error).message}`);
@@ -134,7 +132,7 @@ export class SetEnvsCommandHandler implements CommandHandler {
         const sender = MessageSender.fromMessage(context.SHARE_CONTEXT.botToken, message);
         try {
             const values = JSON.parse(subcommand);
-            await setUserConfig(values, context);
+            await context.execChangeAndSave(values);
             return sender.sendPlainText('Update user config success');
         } catch (e) {
             return sender.sendPlainText(`ERROR: ${(e as Error).message}`);
@@ -228,16 +226,14 @@ export class SystemCommandHandler implements CommandHandler {
         if (ENV.DEV_MODE) {
             const shareCtx = { ...context.SHARE_CONTEXT };
             shareCtx.botToken = '******';
-            context.USER_CONFIG.OPENAI_API_KEY = ['******'];
+            context.USER_CONFIG.ANTHROPIC_API_KEY = '******';
             context.USER_CONFIG.AZURE_API_KEY = '******';
-            context.USER_CONFIG.AZURE_COMPLETIONS_API = '******';
-            context.USER_CONFIG.AZURE_DALLE_API = '******';
-            context.USER_CONFIG.CLOUDFLARE_ACCOUNT_ID = '******';
-            context.USER_CONFIG.CLOUDFLARE_TOKEN = '******';
+            context.USER_CONFIG.COHERE_API_KEY = '******';
             context.USER_CONFIG.GOOGLE_API_KEY = '******';
             context.USER_CONFIG.MISTRAL_API_KEY = '******';
-            context.USER_CONFIG.COHERE_API_KEY = '******';
-            context.USER_CONFIG.ANTHROPIC_API_KEY = '******';
+            context.USER_CONFIG.OPENAI_API_KEY = ['******'];
+            context.USER_CONFIG.CLOUDFLARE_ACCOUNT_ID = '******';
+            context.USER_CONFIG.CLOUDFLARE_TOKEN = '******';
             const config = ConfigMerger.trim(context.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS);
             msg = `
\n${msg}`;
             msg += `USER_CONFIG: ${JSON.stringify(config, null, 2)}\n`;
@@ -279,7 +275,7 @@ export class RedoCommandHandler implements CommandHandler {
             }
             return { history: historyCopy, message: nextMessage };
         };
-        return chatWithLLM(message, null, context, mf);
+        return chatWithMessage(message, null, context, mf);
     };
 }
 
diff --git a/packages/lib/core/src/telegram/command/types.ts b/packages/lib/core/src/telegram/command/types.ts
index f251121df..a8ad13ebf 100644
--- a/packages/lib/core/src/telegram/command/types.ts
+++ b/packages/lib/core/src/telegram/command/types.ts
@@ -1,5 +1,5 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
+import type { WorkerContext } from '../../config';
 
 export interface CommandHandler {
     command: string;
diff --git a/packages/lib/core/src/telegram/handler/group.ts b/packages/lib/core/src/telegram/handler/group.ts
index 6d3515b05..2348309e9 100644
--- a/packages/lib/core/src/telegram/handler/group.ts
+++ b/packages/lib/core/src/telegram/handler/group.ts
@@ -1,9 +1,9 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
+import type { WorkerContext } from '../../config';
 import type { MessageHandler } from './types';
-import { ENV } from '../../config/env';
+import { ENV } from '../../config';
 import { createTelegramBotAPI } from '../api';
-import { isTelegramChatTypeGroup } from '../utils/utils';
+import { isGroupChat } from '../auth';
 
 function checkMention(content: string, entities: Telegram.MessageEntity[], botName: string, botId: number): {
     isMention: boolean;
@@ -45,7 +45,7 @@ function checkMention(content: string, entities: Telegram.MessageEntity[], botNa
 export class GroupMention implements MessageHandler {
     handle = async (message: Telegram.Message, context: WorkerContext): Promise => {
         // 非群组消息不作判断,交给下一个中间件处理
-        if (!isTelegramChatTypeGroup(message.chat.type)) {
+        if (!isGroupChat(message.chat.type)) {
             return null;
         }
 
diff --git a/packages/lib/core/src/telegram/handler/handlers.ts b/packages/lib/core/src/telegram/handler/handlers.ts
index 525177a6d..6b6a19a4f 100644
--- a/packages/lib/core/src/telegram/handler/handlers.ts
+++ b/packages/lib/core/src/telegram/handler/handlers.ts
@@ -1,11 +1,12 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
+import type { WorkerContext } from '../../config';
 import type { MessageHandler, UpdateHandler } from './types';
-import { ENV } from '../../config/env';
+import { ENV } from '../../config';
+import { isGroupChat } from '../auth';
 import { handleCallbackQuery } from '../callback_query';
+import { chatWithMessage, extractUserMessageItem } from '../chat';
 import { handleCommandMessage } from '../command';
-import { MessageSender } from '../utils/send';
-import { isTelegramChatTypeGroup } from '../utils/utils';
+import { MessageSender } from '../sender';
 
 export class EnvChecker implements UpdateHandler {
     handle = async (update: Telegram.Update, context: WorkerContext): Promise => {
@@ -51,7 +52,7 @@ export class WhiteListFilter implements UpdateHandler {
         }
 
         // 判断群组消息
-        if (isTelegramChatTypeGroup(chatType)) {
+        if (isGroupChat(chatType)) {
             // 未打开群组机器人开关,直接忽略
             if (!ENV.GROUP_CHAT_BOT_ENABLE) {
                 throw new Error('Not support');
@@ -171,3 +172,10 @@ export class CommandHandler implements MessageHandler {
         return null;
     };
 }
+
+export class ChatHandler implements MessageHandler {
+    handle = async (message: Telegram.Message, context: WorkerContext): Promise => {
+        const params = await extractUserMessageItem(message, context);
+        return chatWithMessage(message, params, context, null);
+    };
+}
diff --git a/packages/lib/core/src/telegram/handler/index.ts b/packages/lib/core/src/telegram/handler/index.ts
index 6230d8fb1..3cdf8f436 100644
--- a/packages/lib/core/src/telegram/handler/index.ts
+++ b/packages/lib/core/src/telegram/handler/index.ts
@@ -1,10 +1,10 @@
 import type * as Telegram from 'telegram-bot-api-types';
 import type { UpdateHandler } from './types';
-import { WorkerContext } from '../../config/context';
-import { ChatHandler } from './chat';
+import { WorkerContext } from '../../config';
 import { GroupMention } from './group';
 import {
     CallbackQueryHandler,
+    ChatHandler,
     CommandHandler,
     EnvChecker,
     MessageFilter,
diff --git a/packages/lib/core/src/telegram/handler/types.ts b/packages/lib/core/src/telegram/handler/types.ts
index 562180ce9..da47130a1 100644
--- a/packages/lib/core/src/telegram/handler/types.ts
+++ b/packages/lib/core/src/telegram/handler/types.ts
@@ -1,5 +1,5 @@
 import type * as Telegram from 'telegram-bot-api-types';
-import type { WorkerContext } from '../../config/context';
+import type { WorkerContext } from '../../config';
 
 // 中间件定义 function (message: xxx, context: Context): Promise
 // 1. 当函数抛出异常时,结束消息处理,返回异常信息
diff --git a/packages/lib/core/src/telegram/index.ts b/packages/lib/core/src/telegram/index.ts
new file mode 100644
index 000000000..bda698e3c
--- /dev/null
+++ b/packages/lib/core/src/telegram/index.ts
@@ -0,0 +1,2 @@
+export * from './api';
+export * from './handler';
diff --git a/packages/lib/core/src/telegram/utils/send.ts b/packages/lib/core/src/telegram/sender/index.ts
similarity index 97%
rename from packages/lib/core/src/telegram/utils/send.ts
rename to packages/lib/core/src/telegram/sender/index.ts
index 6257da11d..16b9ce6f1 100644
--- a/packages/lib/core/src/telegram/utils/send.ts
+++ b/packages/lib/core/src/telegram/sender/index.ts
@@ -1,8 +1,7 @@
 import type * as Telegram from 'telegram-bot-api-types';
 import type { TelegramBotAPI } from '../api';
-import { ENV } from '../../config/env';
+import { ENV } from '../../config';
 import { createTelegramBotAPI } from '../api';
-import { escape } from './md2tgmd';
 
 class MessageContext implements Record {
     chat_id: number;
@@ -121,8 +120,8 @@ export class MessageSender {
     }
 
     private renderMessage(parse_mode: Telegram.ParseMode | null, message: string): string {
-        if (parse_mode === 'MarkdownV2') {
-            return escape(message);
+        if (ENV.CUSTOM_MESSAGE_RENDER) {
+            return ENV.CUSTOM_MESSAGE_RENDER(parse_mode, message);
         }
         return message;
     }
diff --git a/packages/lib/core/src/telegram/utils/md2tgmd.ts b/packages/lib/core/src/telegram/utils/md2tgmd.ts
deleted file mode 100644
index 6a92a61f5..000000000
--- a/packages/lib/core/src/telegram/utils/md2tgmd.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-/* eslint-disable regexp/no-super-linear-backtracking */
-
-const escapeChars = /([_*[\]()\\~`>#+\-=|{}.!])/g;
-
-export function escape(text: string): string {
-    const lines = text.split('\n');
-    const stack: number[] = [];
-    const result: string[] = [];
-    let lineTrim = '';
-    for (const [i, line] of lines.entries()) {
-        lineTrim = line.trim();
-        let startIndex: number | undefined = 0;
-        if (/^```.+/.test(lineTrim)) {
-            stack.push(i);
-        } else if (lineTrim === '```') {
-            if (stack.length) {
-                startIndex = stack.pop();
-                if (!stack.length) {
-                    const content = lines.slice(startIndex, i + 1).join('\n');
-                    result.push(handleEscape(content, 'code'));
-                    continue;
-                }
-            } else {
-                stack.push(i);
-            }
-        }
-
-        if (!stack.length) {
-            result.push(handleEscape(line));
-        }
-    }
-    if (stack.length) {
-        const last = `${lines.slice(stack[0]).join('\n')}\n\`\`\``;
-        result.push(handleEscape(last, 'code'));
-    }
-    return result.join('\n');
-}
-
-function handleEscape(text: string, type: string = 'text'): string {
-    if (!text.trim()) {
-        return text;
-    }
-    if (type === 'text') {
-        text = text
-            .replace(escapeChars, '\\$1')
-            // force all characters that need to be escaped to be escaped once.
-            .replace(/\\\*\\\*(.*?[^\\])\\\*\\\*/g, '*$1*') // bold
-            .replace(/\\_\\_(.*?[^\\])\\_\\_/g, '__$1__') // underline
-            .replace(/\\_(.*?[^\\])\\_/g, '_$1_') // italic
-            .replace(/\\~(.*?[^\\])\\~/g, '~$1~') // strikethrough
-            .replace(/\\\|\\\|(.*?[^\\])\\\|\\\|/g, '||$1||') // spoiler
-            .replace(/\\\[([^\]]+?)\\\]\\\((.+?)\\\)/g, '[$1]($2)') // url
-            .replace(/\\`(.*?[^\\])\\`/g, '`$1`') // inline code
-            .replace(/\\\\\\([_*[\]()\\~`>#+\-=|{}.!])/g, '\\$1') // restore duplicate escapes
-            .replace(/^(\s*)\\(>.+\s*)$/gm, '$1$2') // >
-            .replace(/^(\s*)\\-\s*(.+)$/gm, '$1• $2') // -
-            .replace(/^((\\#){1,3}\s)(.+)/gm, '$1*$3*'); // number sign
-    } else {
-        const codeBlank = text.length - text.trimStart().length;
-        if (codeBlank > 0) {
-            const blankReg = new RegExp(`^\\s{${codeBlank}}`, 'gm');
-            text = text.replace(blankReg, '');
-        }
-        text = text
-            .trimEnd()
-            .replace(/([\\`])/g, '\\$1')
-            .replace(/^\\`\\`\\`([\s\S]+)\\`\\`\\`$/g, '```$1```'); // code block
-    }
-    return text;
-}
diff --git a/packages/lib/core/src/telegram/utils/utils.ts b/packages/lib/core/src/telegram/utils/utils.ts
deleted file mode 100644
index 80f967e18..000000000
--- a/packages/lib/core/src/telegram/utils/utils.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import type { WorkerContext } from '../../config/context';
-import { ENV, ENV_KEY_MAPPER } from '../../config/env';
-import { ConfigMerger } from '../../config/merger';
-
-export function isTelegramChatTypeGroup(type: string): boolean {
-    return type === 'group' || type === 'supergroup';
-}
-
-export async function setUserConfig(values: Record, context: WorkerContext): Promise {
-    for (const ent of Object.entries(values || {})) {
-        let [key, value] = ent;
-        key = ENV_KEY_MAPPER[key] || key;
-        if (ENV.LOCK_USER_CONFIG_KEYS.includes(key)) {
-            throw new Error(`Key ${key} is locked`);
-        }
-        const configKeys = Object.keys(context.USER_CONFIG || {}) || [];
-        if (!configKeys.includes(key)) {
-            throw new Error(`Key ${key} is not allowed`);
-        }
-        context.USER_CONFIG.DEFINE_KEYS.push(key);
-        ConfigMerger.merge(context.USER_CONFIG, {
-            [key]: value,
-        });
-        console.log('Update user config: ', key, context.USER_CONFIG[key]);
-    }
-    context.USER_CONFIG.DEFINE_KEYS = Array.from(new Set(context.USER_CONFIG.DEFINE_KEYS));
-    await ENV.DATABASE.put(
-        context.SHARE_CONTEXT.configStoreKey,
-        JSON.stringify(ConfigMerger.trim(context.USER_CONFIG, ENV.LOCK_USER_CONFIG_KEYS)),
-    );
-}