@grinev/opencode-telegram-bot 0.20.6 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -2
- package/README.md +10 -0
- package/dist/bot/commands/definitions.js +1 -0
- package/dist/bot/commands/messages.js +537 -0
- package/dist/bot/handlers/model.js +205 -42
- package/dist/bot/index.js +16 -3
- package/dist/bot/streaming/response-streamer.js +8 -0
- package/dist/bot/utils/assistant-run-footer.js +15 -4
- package/dist/bot/utils/busy-reconciliation.js +8 -0
- package/dist/config.js +2 -1
- package/dist/i18n/de.js +27 -0
- package/dist/i18n/en.js +27 -0
- package/dist/i18n/es.js +27 -0
- package/dist/i18n/fr.js +27 -0
- package/dist/i18n/ru.js +27 -0
- package/dist/i18n/zh.js +27 -0
- package/dist/model/manager.js +38 -0
- package/package.json +1 -1
package/dist/i18n/fr.js
CHANGED
|
@@ -4,6 +4,7 @@ export const fr = {
|
|
|
4
4
|
"cmd.description.stop": "Arrêter l'action en cours",
|
|
5
5
|
"cmd.description.detach": "Se détacher de la session actuelle",
|
|
6
6
|
"cmd.description.sessions": "Lister les sessions",
|
|
7
|
+
"cmd.description.messages": "Parcourir les messages de session",
|
|
7
8
|
"cmd.description.tts": "Basculer les réponses audio",
|
|
8
9
|
"cmd.description.projects": "Lister les projets",
|
|
9
10
|
"cmd.description.worktree": "Changer de worktree git",
|
|
@@ -126,6 +127,26 @@ export const fr = {
|
|
|
126
127
|
"sessions.preview.title": "Messages récents :",
|
|
127
128
|
"sessions.preview.you": "Vous :",
|
|
128
129
|
"sessions.preview.agent": "Agent :",
|
|
130
|
+
"messages.project_not_selected": "🏗 Aucun projet sélectionné.\n\nSélectionnez d'abord un projet avec /projects.",
|
|
131
|
+
"messages.session_not_selected": "💬 Aucune session sélectionnée.\n\nChoisissez d'abord une session avec /sessions ou créez-en une avec /new.",
|
|
132
|
+
"messages.session_project_mismatch": "⚠️ La session sélectionnée ne correspond pas au projet actuel. Choisissez à nouveau la session via /sessions.",
|
|
133
|
+
"messages.empty": "📭 Aucun message utilisateur dans la session actuelle.",
|
|
134
|
+
"messages.select": "Choisissez un message :",
|
|
135
|
+
"messages.select_page": "Choisissez un message (page {page}) :",
|
|
136
|
+
"messages.fetch_error": "🔴 OpenCode Server est indisponible ou une erreur est survenue pendant le chargement des messages.",
|
|
137
|
+
"messages.inactive_callback": "Ce menu de messages est inactif",
|
|
138
|
+
"messages.cancelled_callback": "Annulé",
|
|
139
|
+
"messages.page_empty_callback": "Aucun message sur cette page",
|
|
140
|
+
"messages.button.prev_page": "⬅️ Précédent",
|
|
141
|
+
"messages.button.next_page": "Suivant ➡️",
|
|
142
|
+
"messages.button.revert": "↩️ Revert",
|
|
143
|
+
"messages.button.fork": "🔀 Fork",
|
|
144
|
+
"messages.button.back": "⬅️ Retour",
|
|
145
|
+
"messages.button.cancel": "❌ Annuler",
|
|
146
|
+
"messages.revert_success": "✅ Retour au message :\n\n{text}",
|
|
147
|
+
"messages.revert_error": "❌ Impossible de revenir au message. Veuillez réessayer.",
|
|
148
|
+
"messages.fork_success": "🔀 Fork créé à partir du message :\n\n{text}",
|
|
149
|
+
"messages.fork_error": "❌ Échec de la création du fork. Veuillez réessayer.",
|
|
129
150
|
"attach.project_not_selected": "🏗 Aucun projet sélectionné.\n\nSélectionnez d'abord un projet avec /projects.",
|
|
130
151
|
"attach.session_not_selected": "💬 Aucune session sélectionnée.\n\nSélectionnez d'abord une session avec /sessions.",
|
|
131
152
|
"attach.session_project_mismatch": "⚠️ La session sélectionnée ne correspond pas au projet actuel. Sélectionnez-la de nouveau via /sessions.",
|
|
@@ -190,6 +211,12 @@ export const fr = {
|
|
|
190
211
|
"model.menu.recent_empty": "— Vide.",
|
|
191
212
|
"model.menu.favorites_hint": "ℹ️ Ajoutez des modèles aux favoris dans l'interface OpenCode pour les garder en tête de liste.",
|
|
192
213
|
"model.menu.error": "🔴 Impossible de récupérer la liste des modèles",
|
|
214
|
+
"model.search.button": "🔍 Rechercher",
|
|
215
|
+
"model.search.prompt": "🔍 Entrez le nom du modèle à rechercher :",
|
|
216
|
+
"model.search.results_title": "Résultats de recherche pour \"{query}\" :",
|
|
217
|
+
"model.search.no_results": "Aucun modèle trouvé pour \"{query}\"",
|
|
218
|
+
"model.search.search_again": "↩ Rechercher à nouveau",
|
|
219
|
+
"model.search.error": "Échec de la recherche",
|
|
193
220
|
"variant.model_not_selected_callback": "Erreur : aucun modèle sélectionné",
|
|
194
221
|
"variant.changed_callback": "Variante modifiée : {name}",
|
|
195
222
|
"variant.changed_message": "✅ Variante définie sur : {name}",
|
package/dist/i18n/ru.js
CHANGED
|
@@ -4,6 +4,7 @@ export const ru = {
|
|
|
4
4
|
"cmd.description.stop": "Прервать текущее действие",
|
|
5
5
|
"cmd.description.detach": "Отсоединиться от текущей сессии",
|
|
6
6
|
"cmd.description.sessions": "Список сессий",
|
|
7
|
+
"cmd.description.messages": "Сообщения текущей сессии",
|
|
7
8
|
"cmd.description.tts": "Переключить аудиоответы",
|
|
8
9
|
"cmd.description.projects": "Список проектов",
|
|
9
10
|
"cmd.description.worktree": "Переключить git worktree",
|
|
@@ -126,6 +127,26 @@ export const ru = {
|
|
|
126
127
|
"sessions.preview.title": "Последние сообщения:",
|
|
127
128
|
"sessions.preview.you": "Вы:",
|
|
128
129
|
"sessions.preview.agent": "Агент:",
|
|
130
|
+
"messages.project_not_selected": "🏗 Проект не выбран.\n\nСначала выберите проект командой /projects.",
|
|
131
|
+
"messages.session_not_selected": "💬 Сессия не выбрана.\n\nСначала выберите сессию через /sessions или создайте новую через /new.",
|
|
132
|
+
"messages.session_project_mismatch": "⚠️ Выбранная сессия не соответствует текущему проекту. Повторно выберите сессию через /sessions.",
|
|
133
|
+
"messages.empty": "📭 В текущей сессии нет сообщений пользователя.",
|
|
134
|
+
"messages.select": "Выберите сообщение:",
|
|
135
|
+
"messages.select_page": "Выберите сообщение (страница {page}):",
|
|
136
|
+
"messages.fetch_error": "🔴 OpenCode Server недоступен или произошла ошибка при получении списка сообщений.",
|
|
137
|
+
"messages.inactive_callback": "Это меню сообщений уже неактивно",
|
|
138
|
+
"messages.cancelled_callback": "Отменено",
|
|
139
|
+
"messages.page_empty_callback": "На этой странице нет сообщений",
|
|
140
|
+
"messages.button.prev_page": "⬅️ Назад",
|
|
141
|
+
"messages.button.next_page": "Вперёд ➡️",
|
|
142
|
+
"messages.button.revert": "↩️ Revert",
|
|
143
|
+
"messages.button.fork": "🔀 Fork",
|
|
144
|
+
"messages.button.back": "⬅️ Назад",
|
|
145
|
+
"messages.button.cancel": "❌ Отмена",
|
|
146
|
+
"messages.revert_success": "✅ Откатили до сообщения:\n\n{text}",
|
|
147
|
+
"messages.revert_error": "❌ Не удалось откатить сообщение. Попробуйте ещё раз.",
|
|
148
|
+
"messages.fork_success": "🔀 Создан форк от сообщения:\n\n{text}",
|
|
149
|
+
"messages.fork_error": "❌ Не удалось создать форк. Попробуйте ещё раз.",
|
|
129
150
|
"attach.project_not_selected": "🏗 Проект не выбран.\n\nСначала выберите проект командой /projects.",
|
|
130
151
|
"attach.session_not_selected": "💬 Сессия не выбрана.\n\nСначала выберите сессию через /sessions.",
|
|
131
152
|
"attach.session_project_mismatch": "⚠️ Выбранная сессия не соответствует текущему проекту. Повторно выберите сессию через /sessions.",
|
|
@@ -190,6 +211,12 @@ export const ru = {
|
|
|
190
211
|
"model.menu.recent_empty": "— Список пуст.",
|
|
191
212
|
"model.menu.favorites_hint": "ℹ️ Добавляйте модели в избранное через OpenCode CLI, чтобы они были вверху списка.",
|
|
192
213
|
"model.menu.error": "🔴 Не удалось получить список моделей",
|
|
214
|
+
"model.search.button": "🔍 Поиск",
|
|
215
|
+
"model.search.prompt": "🔍 Введите название модели для поиска:",
|
|
216
|
+
"model.search.results_title": "Результаты поиска для \"{query}\":",
|
|
217
|
+
"model.search.no_results": "Модели не найдены для \"{query}\"",
|
|
218
|
+
"model.search.search_again": "↩ Искать снова",
|
|
219
|
+
"model.search.error": "Ошибка поиска",
|
|
193
220
|
"variant.model_not_selected_callback": "Ошибка: модель не выбрана",
|
|
194
221
|
"variant.changed_callback": "Вариант изменен: {name}",
|
|
195
222
|
"variant.changed_message": "✅ Вариант изменен на: {name}",
|
package/dist/i18n/zh.js
CHANGED
|
@@ -4,6 +4,7 @@ export const zh = {
|
|
|
4
4
|
"cmd.description.stop": "停止当前操作",
|
|
5
5
|
"cmd.description.detach": "从当前会话分离",
|
|
6
6
|
"cmd.description.sessions": "列出会话",
|
|
7
|
+
"cmd.description.messages": "浏览会话消息",
|
|
7
8
|
"cmd.description.tts": "切换语音回复",
|
|
8
9
|
"cmd.description.projects": "列出项目",
|
|
9
10
|
"cmd.description.worktree": "切换 git worktree",
|
|
@@ -126,6 +127,26 @@ export const zh = {
|
|
|
126
127
|
"sessions.preview.title": "最近消息:",
|
|
127
128
|
"sessions.preview.you": "你:",
|
|
128
129
|
"sessions.preview.agent": "代理:",
|
|
130
|
+
"messages.project_not_selected": "🏗 未选择项目。\n\n请先使用 /projects 选择项目。",
|
|
131
|
+
"messages.session_not_selected": "💬 未选择会话。\n\n请先使用 /sessions 选择会话,或使用 /new 创建会话。",
|
|
132
|
+
"messages.session_project_mismatch": "⚠️ 所选会话与当前项目不匹配。请通过 /sessions 重新选择会话。",
|
|
133
|
+
"messages.empty": "📭 当前会话中没有用户消息。",
|
|
134
|
+
"messages.select": "选择一条消息:",
|
|
135
|
+
"messages.select_page": "选择一条消息(第 {page} 页):",
|
|
136
|
+
"messages.fetch_error": "🔴 OpenCode Server 不可用,或加载消息时发生错误。",
|
|
137
|
+
"messages.inactive_callback": "此消息菜单已失效",
|
|
138
|
+
"messages.cancelled_callback": "已取消",
|
|
139
|
+
"messages.page_empty_callback": "此页面没有消息",
|
|
140
|
+
"messages.button.prev_page": "⬅️ 上一页",
|
|
141
|
+
"messages.button.next_page": "下一页 ➡️",
|
|
142
|
+
"messages.button.revert": "↩️ Revert",
|
|
143
|
+
"messages.button.fork": "🔀 Fork",
|
|
144
|
+
"messages.button.back": "⬅️ 返回",
|
|
145
|
+
"messages.button.cancel": "❌ 取消",
|
|
146
|
+
"messages.revert_success": "✅ 已恢复到消息:\n\n{text}",
|
|
147
|
+
"messages.revert_error": "❌ 无法恢复消息。请重试。",
|
|
148
|
+
"messages.fork_success": "🔀 从消息创建分支:\n\n{text}",
|
|
149
|
+
"messages.fork_error": "❌ 创建分支失败。请重试。",
|
|
129
150
|
"attach.project_not_selected": "🏗 未选择项目。\n\n请先使用 /projects 选择一个项目。",
|
|
130
151
|
"attach.session_not_selected": "💬 未选择会话。\n\n请先使用 /sessions 选择一个会话。",
|
|
131
152
|
"attach.session_project_mismatch": "⚠️ 选中的会话与当前项目不匹配。请通过 /sessions 重新选择。",
|
|
@@ -190,6 +211,12 @@ export const zh = {
|
|
|
190
211
|
"model.menu.recent_empty": "— 列表为空。",
|
|
191
212
|
"model.menu.favorites_hint": "ℹ️ 可在 OpenCode CLI 中将模型加入收藏,使其显示在列表顶部。",
|
|
192
213
|
"model.menu.error": "🔴 获取模型列表失败",
|
|
214
|
+
"model.search.button": "🔍 搜索",
|
|
215
|
+
"model.search.prompt": "🔍 输入模型名称进行搜索:",
|
|
216
|
+
"model.search.results_title": "\"{query}\" 的搜索结果:",
|
|
217
|
+
"model.search.no_results": "未找到 \"{query}\" 的模型",
|
|
218
|
+
"model.search.search_again": "↩ 重新搜索",
|
|
219
|
+
"model.search.error": "搜索失败",
|
|
193
220
|
"variant.model_not_selected_callback": "错误:未选择模型",
|
|
194
221
|
"variant.changed_callback": "变体已更改:{name}",
|
|
195
222
|
"variant.changed_message": "✅ 变体已切换为:{name}",
|
package/dist/model/manager.js
CHANGED
|
@@ -11,8 +11,10 @@ const SERVER_UNAVAILABLE_ERROR_MARKERS = [
|
|
|
11
11
|
"connect refused",
|
|
12
12
|
];
|
|
13
13
|
let cachedValidModelKeys = null;
|
|
14
|
+
let cachedAllModels = null;
|
|
14
15
|
let modelCatalogCacheExpiresAt = 0;
|
|
15
16
|
let modelCatalogFetchInFlight = null;
|
|
17
|
+
const SEARCH_RESULTS_LIMIT = 10;
|
|
16
18
|
function getModelKey(providerID, modelID) {
|
|
17
19
|
return `${providerID}/${modelID}`;
|
|
18
20
|
}
|
|
@@ -117,12 +119,15 @@ async function getValidModelKeys(options) {
|
|
|
117
119
|
return null;
|
|
118
120
|
}
|
|
119
121
|
const validModelKeys = new Set();
|
|
122
|
+
const allModels = [];
|
|
120
123
|
for (const provider of response.data.providers) {
|
|
121
124
|
for (const modelID of Object.keys(provider.models)) {
|
|
122
125
|
validModelKeys.add(getModelKey(provider.id, modelID));
|
|
126
|
+
allModels.push({ providerID: provider.id, modelID });
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
129
|
cachedValidModelKeys = validModelKeys;
|
|
130
|
+
cachedAllModels = allModels;
|
|
126
131
|
modelCatalogCacheExpiresAt = Date.now() + MODEL_CATALOG_CACHE_TTL_MS;
|
|
127
132
|
logger.debug(`[ModelManager] Model catalog refreshed: providers=${response.data.providers.length}, models=${validModelKeys.size}`);
|
|
128
133
|
return cachedValidModelKeys;
|
|
@@ -261,6 +266,7 @@ export async function reconcileStoredModelSelection(options) {
|
|
|
261
266
|
}
|
|
262
267
|
export function __resetModelCatalogCacheForTests() {
|
|
263
268
|
cachedValidModelKeys = null;
|
|
269
|
+
cachedAllModels = null;
|
|
264
270
|
modelCatalogCacheExpiresAt = 0;
|
|
265
271
|
modelCatalogFetchInFlight = null;
|
|
266
272
|
}
|
|
@@ -272,6 +278,38 @@ export async function getFavoriteModels() {
|
|
|
272
278
|
const { favorites } = await getModelSelectionLists();
|
|
273
279
|
return favorites;
|
|
274
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* Search models across the full provider catalog.
|
|
283
|
+
* Matches case-insensitive substring against "providerID/modelID".
|
|
284
|
+
* Returns at most SEARCH_RESULTS_LIMIT results sorted alphabetically.
|
|
285
|
+
* @param query Search query string
|
|
286
|
+
* @returns Matching models, empty array if catalog unavailable or no matches
|
|
287
|
+
*/
|
|
288
|
+
export async function searchModels(query) {
|
|
289
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
290
|
+
if (!normalizedQuery) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
// Ensure catalog is loaded (uses cache if fresh)
|
|
294
|
+
const validKeys = await getValidModelKeys();
|
|
295
|
+
if (!validKeys || !cachedAllModels) {
|
|
296
|
+
logger.warn("[ModelManager] Model catalog unavailable, skipping search");
|
|
297
|
+
return [];
|
|
298
|
+
}
|
|
299
|
+
const results = cachedAllModels
|
|
300
|
+
.filter((model) => {
|
|
301
|
+
const key = getModelKey(model.providerID, model.modelID).toLowerCase();
|
|
302
|
+
return key.includes(normalizedQuery);
|
|
303
|
+
})
|
|
304
|
+
.sort((a, b) => {
|
|
305
|
+
const keyA = getModelKey(a.providerID, a.modelID).toLowerCase();
|
|
306
|
+
const keyB = getModelKey(b.providerID, b.modelID).toLowerCase();
|
|
307
|
+
return keyA.localeCompare(keyB);
|
|
308
|
+
})
|
|
309
|
+
.slice(0, SEARCH_RESULTS_LIMIT);
|
|
310
|
+
logger.debug(`[ModelManager] Model search: query="${query}", results=${results.length}`);
|
|
311
|
+
return results;
|
|
312
|
+
}
|
|
275
313
|
/**
|
|
276
314
|
* Get current model from settings or fallback to config
|
|
277
315
|
* @returns Current model info
|