opencode-tbot 0.1.17 → 0.1.19

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/dist/plugin.js CHANGED
@@ -1,9 +1,8 @@
1
- import { c as loadAppConfig, i as preparePluginConfiguration, o as OPENCODE_TBOT_VERSION } from "./assets/plugin-config-DA71_jD3.js";
1
+ import { c as loadAppConfig, i as preparePluginConfiguration, o as OPENCODE_TBOT_VERSION } from "./assets/plugin-config-B8ginwol.js";
2
2
  import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
3
- import { basename, dirname, extname, isAbsolute, join } from "node:path";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
4
  import { parse, printParseErrorCode } from "jsonc-parser";
5
5
  import { z } from "zod";
6
- import { OpenRouter } from "@openrouter/sdk";
7
6
  import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
8
7
  import { randomUUID } from "node:crypto";
9
8
  import { run } from "@grammyjs/runner";
@@ -1087,14 +1086,6 @@ var TelegramFileDownloadError = class extends Error {
1087
1086
  this.data = { message };
1088
1087
  }
1089
1088
  };
1090
- var VoiceMessageUnsupportedError = class extends Error {
1091
- data;
1092
- constructor(message) {
1093
- super(message);
1094
- this.name = "VoiceMessageUnsupportedError";
1095
- this.data = { message };
1096
- }
1097
- };
1098
1089
  var TelegramFileClient = class {
1099
1090
  baseUrl;
1100
1091
  fetchFn;
@@ -1109,11 +1100,11 @@ var TelegramFileClient = class {
1109
1100
  try {
1110
1101
  response = await this.fetchFn(new URL(filePath, this.baseUrl));
1111
1102
  } catch (error) {
1112
- throw new TelegramFileDownloadError(extractErrorMessage$1(error) ?? "Failed to download the Telegram voice file.");
1103
+ throw new TelegramFileDownloadError(extractErrorMessage(error) ?? "Failed to download the Telegram file.");
1113
1104
  }
1114
1105
  if (!response.ok) throw new TelegramFileDownloadError(await buildDownloadFailureMessage(response));
1115
1106
  const data = new Uint8Array(await response.arrayBuffer());
1116
- if (data.byteLength === 0) throw new TelegramFileDownloadError("Telegram returned an empty voice file.");
1107
+ if (data.byteLength === 0) throw new TelegramFileDownloadError("Telegram returned an empty file.");
1117
1108
  return {
1118
1109
  data,
1119
1110
  mimeType: response.headers.get("content-type")
@@ -1135,7 +1126,7 @@ async function safeReadResponseText(response) {
1135
1126
  return null;
1136
1127
  }
1137
1128
  }
1138
- function extractErrorMessage$1(error) {
1129
+ function extractErrorMessage(error) {
1139
1130
  return error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : null;
1140
1131
  }
1141
1132
  //#endregion
@@ -1178,258 +1169,6 @@ var NOOP_FOREGROUND_SESSION_TRACKER = {
1178
1169
  }
1179
1170
  };
1180
1171
  //#endregion
1181
- //#region src/services/voice-transcription/openrouter-voice.client.ts
1182
- var VoiceTranscriptionNotConfiguredError = class extends Error {
1183
- data;
1184
- constructor(message) {
1185
- super(message);
1186
- this.name = "VoiceTranscriptionNotConfiguredError";
1187
- this.data = { message };
1188
- }
1189
- };
1190
- var VoiceTranscriptionFailedError = class extends Error {
1191
- data;
1192
- constructor(message) {
1193
- super(message);
1194
- this.name = "VoiceTranscriptionFailedError";
1195
- this.data = { message };
1196
- }
1197
- };
1198
- var VoiceTranscriptEmptyError = class extends Error {
1199
- data;
1200
- constructor(message) {
1201
- super(message);
1202
- this.name = "VoiceTranscriptEmptyError";
1203
- this.data = { message };
1204
- }
1205
- };
1206
- var DisabledVoiceTranscriptionClient = class {
1207
- getStatus() {
1208
- return {
1209
- status: "not_configured",
1210
- model: null
1211
- };
1212
- }
1213
- async transcribe() {
1214
- throw new VoiceTranscriptionNotConfiguredError("Set openrouter.apiKey in the global config (~/.config/opencode/opencode-tbot/config.json) to enable Telegram voice transcription.");
1215
- }
1216
- };
1217
- var OpenRouterVoiceTranscriptionClient = class {
1218
- model;
1219
- sdk;
1220
- timeoutMs;
1221
- transcriptionPrompt;
1222
- constructor(options, sdk) {
1223
- this.model = options.model;
1224
- this.sdk = sdk;
1225
- this.timeoutMs = options.timeoutMs;
1226
- this.transcriptionPrompt = options.transcriptionPrompt?.trim() || null;
1227
- }
1228
- getStatus() {
1229
- return {
1230
- status: "configured",
1231
- model: this.model
1232
- };
1233
- }
1234
- async transcribe(input) {
1235
- const format = resolveAudioFormat(input.filename, input.mimeType);
1236
- const audioData = toBase64(input.data);
1237
- const prompt = buildTranscriptionPrompt(this.transcriptionPrompt);
1238
- let response;
1239
- try {
1240
- response = await this.sdk.chat.send({ chatGenerationParams: {
1241
- messages: [{
1242
- role: "user",
1243
- content: [{
1244
- type: "text",
1245
- text: prompt
1246
- }, {
1247
- type: "input_audio",
1248
- inputAudio: {
1249
- data: audioData,
1250
- format
1251
- }
1252
- }]
1253
- }],
1254
- model: this.model,
1255
- stream: false,
1256
- temperature: 0
1257
- } }, { timeoutMs: this.timeoutMs });
1258
- } catch (error) {
1259
- throw new VoiceTranscriptionFailedError(buildTranscriptionErrorMessage(error, {
1260
- format,
1261
- model: this.model
1262
- }));
1263
- }
1264
- return { text: extractTranscript(response) };
1265
- }
1266
- };
1267
- var MIME_TYPE_FORMAT_MAP = {
1268
- "audio/mp3": "mp3",
1269
- "audio/mpeg": "mp3",
1270
- "audio/ogg": "ogg",
1271
- "audio/wav": "wav",
1272
- "audio/wave": "wav",
1273
- "audio/x-wav": "wav",
1274
- "audio/vnd.wave": "wav"
1275
- };
1276
- var FILE_EXTENSION_FORMAT_MAP = {
1277
- ".mp3": "mp3",
1278
- ".oga": "ogg",
1279
- ".ogg": "ogg",
1280
- ".wav": "wav"
1281
- };
1282
- function resolveAudioFormat(filename, mimeType) {
1283
- const normalizedMimeType = mimeType?.trim().toLowerCase() || null;
1284
- if (normalizedMimeType && MIME_TYPE_FORMAT_MAP[normalizedMimeType]) return MIME_TYPE_FORMAT_MAP[normalizedMimeType];
1285
- const extension = extname(basename(filename).trim()).toLowerCase();
1286
- if (extension && FILE_EXTENSION_FORMAT_MAP[extension]) return FILE_EXTENSION_FORMAT_MAP[extension];
1287
- return "ogg";
1288
- }
1289
- function toBase64(data) {
1290
- const bytes = toUint8Array(data);
1291
- return Buffer.from(bytes).toString("base64");
1292
- }
1293
- function toUint8Array(data) {
1294
- return data instanceof Uint8Array ? data : new Uint8Array(data);
1295
- }
1296
- function buildTranscriptionPrompt(transcriptionPrompt) {
1297
- const basePrompt = [
1298
- "Transcribe the provided audio verbatim.",
1299
- "Return only the transcript text.",
1300
- "Do not translate, summarize, explain, or add speaker labels.",
1301
- "If the audio is empty or unintelligible, return an empty string."
1302
- ].join(" ");
1303
- return transcriptionPrompt ? `${basePrompt}\n\nAdditional instructions: ${transcriptionPrompt}` : basePrompt;
1304
- }
1305
- function extractTranscript(response) {
1306
- const content = response.choices?.[0]?.message?.content;
1307
- if (typeof content === "string") return content.trim();
1308
- if (!Array.isArray(content)) return "";
1309
- return content.flatMap((item) => isTextContentItem(item) ? [item.text.trim()] : []).filter((text) => text.length > 0).join("\n").trim();
1310
- }
1311
- function isTextContentItem(value) {
1312
- return !!value && typeof value === "object" && "type" in value && value.type === "text" && "text" in value && typeof value.text === "string";
1313
- }
1314
- function buildTranscriptionErrorMessage(error, context) {
1315
- const parsedBody = parseJsonBody(extractStringField(error, "body"));
1316
- const rawMessages = dedupeNonEmptyStrings([
1317
- extractErrorMessage(error),
1318
- extractErrorMessage(readField(error, "error")),
1319
- extractErrorMessage(readField(error, "data")),
1320
- extractErrorMessage(readField(readField(error, "data"), "error")),
1321
- extractErrorMessage(parsedBody),
1322
- extractErrorMessage(readField(parsedBody, "error")),
1323
- extractMetadataRawMessage(error),
1324
- extractMetadataRawMessage(parsedBody)
1325
- ]);
1326
- const messages = rawMessages.some((message) => !isGenericProviderMessage(message)) ? rawMessages.filter((message) => !isGenericProviderMessage(message)) : rawMessages;
1327
- const providerName = extractProviderName(error) ?? extractProviderName(parsedBody);
1328
- const statusCode = extractNumericField(error, "statusCode") ?? extractNumericField(parsedBody, "statusCode");
1329
- const errorCode = extractErrorCode(error, parsedBody);
1330
- return joinNonEmptyParts$1([
1331
- ...messages,
1332
- `model: ${context.model}`,
1333
- `format: ${context.format}`,
1334
- providerName ? `provider: ${providerName}` : null,
1335
- statusCode !== null ? `status: ${statusCode}` : null,
1336
- errorCode !== null ? `code: ${errorCode}` : null
1337
- ]) ?? "Failed to reach OpenRouter voice transcription.";
1338
- }
1339
- function extractErrorMessage(error) {
1340
- if (error instanceof Error && error.message.trim().length > 0) return error.message.trim();
1341
- return extractStringField(error, "message");
1342
- }
1343
- function extractMetadataRawMessage(value) {
1344
- const raw = extractStringField(readField(value, "metadata") ?? readField(readField(value, "error"), "metadata") ?? readField(readField(readField(value, "data"), "error"), "metadata"), "raw");
1345
- if (!raw) return null;
1346
- return raw.length <= 280 ? raw : `${raw.slice(0, 277)}...`;
1347
- }
1348
- function extractProviderName(value) {
1349
- const candidates = [
1350
- readField(value, "metadata"),
1351
- readField(readField(value, "error"), "metadata"),
1352
- readField(readField(readField(value, "data"), "error"), "metadata")
1353
- ];
1354
- for (const candidate of candidates) {
1355
- const providerName = extractStringField(candidate, "provider_name") ?? extractStringField(candidate, "providerName") ?? extractStringField(candidate, "provider");
1356
- if (providerName) return providerName;
1357
- }
1358
- return null;
1359
- }
1360
- function extractErrorCode(...values) {
1361
- for (const value of values) {
1362
- const candidates = [
1363
- value,
1364
- readField(value, "error"),
1365
- readField(value, "data"),
1366
- readField(readField(value, "data"), "error")
1367
- ];
1368
- for (const candidate of candidates) {
1369
- const code = extractNumericField(candidate, "code");
1370
- if (code !== null) return code;
1371
- }
1372
- }
1373
- return null;
1374
- }
1375
- function extractNumericField(value, fieldName) {
1376
- if (!value || typeof value !== "object" || !(fieldName in value)) return null;
1377
- const fieldValue = value[fieldName];
1378
- return typeof fieldValue === "number" && Number.isFinite(fieldValue) ? fieldValue : null;
1379
- }
1380
- function extractStringField(value, fieldName) {
1381
- if (!value || typeof value !== "object" || !(fieldName in value)) return null;
1382
- const fieldValue = value[fieldName];
1383
- return typeof fieldValue === "string" && fieldValue.trim().length > 0 ? fieldValue.trim() : null;
1384
- }
1385
- function readField(value, fieldName) {
1386
- return value && typeof value === "object" && fieldName in value ? value[fieldName] : null;
1387
- }
1388
- function parseJsonBody(body) {
1389
- if (!body) return null;
1390
- try {
1391
- return JSON.parse(body);
1392
- } catch {
1393
- return null;
1394
- }
1395
- }
1396
- function dedupeNonEmptyStrings(values) {
1397
- const seen = /* @__PURE__ */ new Set();
1398
- const result = [];
1399
- for (const value of values) {
1400
- const normalized = value?.trim();
1401
- if (!normalized) continue;
1402
- const key = normalized.toLowerCase();
1403
- if (seen.has(key)) continue;
1404
- seen.add(key);
1405
- result.push(normalized);
1406
- }
1407
- return result;
1408
- }
1409
- function joinNonEmptyParts$1(parts) {
1410
- const filtered = parts.map((part) => part?.trim()).filter((part) => !!part);
1411
- return filtered.length > 0 ? filtered.join(" | ") : null;
1412
- }
1413
- function isGenericProviderMessage(message) {
1414
- const normalized = message.trim().toLowerCase();
1415
- return normalized === "provider returned error" || normalized === "failed to reach openrouter voice transcription.";
1416
- }
1417
- //#endregion
1418
- //#region src/services/voice-transcription/voice-transcription.service.ts
1419
- var VoiceTranscriptionService = class {
1420
- constructor(client) {
1421
- this.client = client;
1422
- }
1423
- getStatus() {
1424
- return this.client.getStatus();
1425
- }
1426
- async transcribeVoice(input) {
1427
- const text = (await this.client.transcribe(input)).text.trim();
1428
- if (!text) throw new VoiceTranscriptEmptyError("Voice transcription returned empty text.");
1429
- return { text };
1430
- }
1431
- };
1432
- //#endregion
1433
1172
  //#region src/use-cases/abort-prompt.usecase.ts
1434
1173
  var AbortPromptUseCase = class {
1435
1174
  constructor(sessionRepo, opencodeClient) {
@@ -1535,14 +1274,13 @@ var GetPathUseCase = class {
1535
1274
  //#endregion
1536
1275
  //#region src/use-cases/get-status.usecase.ts
1537
1276
  var GetStatusUseCase = class {
1538
- constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, voiceTranscriptionService) {
1277
+ constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo) {
1539
1278
  this.getHealthUseCase = getHealthUseCase;
1540
1279
  this.getPathUseCase = getPathUseCase;
1541
1280
  this.listLspUseCase = listLspUseCase;
1542
1281
  this.listMcpUseCase = listMcpUseCase;
1543
1282
  this.listSessionsUseCase = listSessionsUseCase;
1544
1283
  this.sessionRepo = sessionRepo;
1545
- this.voiceTranscriptionService = voiceTranscriptionService;
1546
1284
  }
1547
1285
  async execute(input) {
1548
1286
  const [health, path, lsp, mcp] = await Promise.allSettled([
@@ -1557,7 +1295,6 @@ var GetStatusUseCase = class {
1557
1295
  health: mapSettledResult(health),
1558
1296
  path: pathResult,
1559
1297
  plugins,
1560
- voiceRecognition: this.voiceTranscriptionService.getStatus(),
1561
1298
  workspace,
1562
1299
  lsp: mapSettledResult(lsp),
1563
1300
  mcp: mapSettledResult(mcp)
@@ -2181,7 +1918,6 @@ function createContainer(config, opencodeClient, logger) {
2181
1918
  apiRoot: config.telegramApiRoot
2182
1919
  });
2183
1920
  const uploadFileUseCase = new UploadFileUseCase(telegramFileClient);
2184
- const voiceTranscriptionService = new VoiceTranscriptionService(createVoiceTranscriptionClient(config.openrouter));
2185
1921
  const abortPromptUseCase = new AbortPromptUseCase(sessionRepo, opencodeClient);
2186
1922
  const createSessionUseCase = new CreateSessionUseCase(sessionRepo, opencodeClient, logger);
2187
1923
  const getHealthUseCase = new GetHealthUseCase(opencodeClient);
@@ -2190,7 +1926,7 @@ function createContainer(config, opencodeClient, logger) {
2190
1926
  const listLspUseCase = new ListLspUseCase(sessionRepo, opencodeClient);
2191
1927
  const listMcpUseCase = new ListMcpUseCase(sessionRepo, opencodeClient);
2192
1928
  const listSessionsUseCase = new ListSessionsUseCase(sessionRepo, opencodeClient);
2193
- const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, voiceTranscriptionService);
1929
+ const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo);
2194
1930
  const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
2195
1931
  const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, logger);
2196
1932
  const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger, foregroundSessionTracker);
@@ -2214,7 +1950,6 @@ function createContainer(config, opencodeClient, logger) {
2214
1950
  renameSessionUseCase,
2215
1951
  sessionRepo,
2216
1952
  sendPromptUseCase,
2217
- voiceTranscriptionService,
2218
1953
  switchAgentUseCase,
2219
1954
  switchModelUseCase,
2220
1955
  switchSessionUseCase,
@@ -2231,16 +1966,6 @@ function createContainer(config, opencodeClient, logger) {
2231
1966
  }
2232
1967
  };
2233
1968
  }
2234
- function createVoiceTranscriptionClient(config) {
2235
- return config.configured && config.apiKey ? new OpenRouterVoiceTranscriptionClient({
2236
- model: config.model,
2237
- timeoutMs: config.timeoutMs,
2238
- transcriptionPrompt: config.transcriptionPrompt
2239
- }, new OpenRouter({
2240
- apiKey: config.apiKey,
2241
- timeoutMs: config.timeoutMs
2242
- })) : new DisabledVoiceTranscriptionClient();
2243
- }
2244
1969
  //#endregion
2245
1970
  //#region src/app/bootstrap.ts
2246
1971
  function bootstrapPluginApp(client, configSource = {}, options = {}) {
@@ -2432,7 +2157,7 @@ var EN_BOT_COPY = {
2432
2157
  "1. Run `/status` to confirm the server is ready.",
2433
2158
  "2. Run `/new [title]` to create a fresh session.",
2434
2159
  "",
2435
- "Send a text, image, or voice message directly."
2160
+ "Send a text or image message directly."
2436
2161
  ] },
2437
2162
  systemStatus: { title: "System Status" },
2438
2163
  common: {
@@ -2471,11 +2196,7 @@ var EN_BOT_COPY = {
2471
2196
  requestAborted: "Request was aborted.",
2472
2197
  promptTimeout: "OpenCode request timed out.",
2473
2198
  structuredOutput: "Structured output validation failed.",
2474
- voiceNotConfigured: "Voice transcription is not configured.",
2475
- voiceDownload: "Failed to download the Telegram voice file.",
2476
- voiceTranscription: "Voice transcription failed.",
2477
- voiceEmpty: "Voice transcription returned empty text.",
2478
- voiceUnsupported: "Voice message file is too large or unsupported.",
2199
+ voiceUnsupported: "Voice messages are not supported. Send text or an image instead.",
2479
2200
  imageDownload: "Failed to download the Telegram image file.",
2480
2201
  imageUnsupported: "Image file is too large or unsupported.",
2481
2202
  outputLength: "Reply hit the model output limit.",
@@ -2641,7 +2362,7 @@ var ZH_CN_BOT_COPY = {
2641
2362
  "1. 先运行 `/status` 确认服务状态正常。",
2642
2363
  "2. 运行 `/new [title]` 创建一个新会话。",
2643
2364
  "",
2644
- "直接发送文本、图片或语音消息即可。"
2365
+ "直接发送文本或图片消息即可。"
2645
2366
  ] },
2646
2367
  systemStatus: { title: "系统状态" },
2647
2368
  common: {
@@ -2680,11 +2401,7 @@ var ZH_CN_BOT_COPY = {
2680
2401
  requestAborted: "请求已中止。",
2681
2402
  promptTimeout: "OpenCode 响应超时。",
2682
2403
  structuredOutput: "结构化输出校验失败。",
2683
- voiceNotConfigured: "未配置语音转写服务。",
2684
- voiceDownload: "下载 Telegram 语音文件失败。",
2685
- voiceTranscription: "语音转写失败。",
2686
- voiceEmpty: "语音转写结果为空。",
2687
- voiceUnsupported: "语音文件过大或不受支持。",
2404
+ voiceUnsupported: "暂不支持语音消息,请改发文本或图片。",
2688
2405
  imageDownload: "下载 Telegram 图片文件失败。",
2689
2406
  imageUnsupported: "图片文件过大或不受支持。",
2690
2407
  outputLength: "回复触发了模型输出长度上限。",
@@ -3030,26 +2747,6 @@ function normalizeError(error, copy) {
3030
2747
  message: copy.errors.structuredOutput,
3031
2748
  cause: joinNonEmptyParts([extractMessage(error.data), extractRetries(error.data)])
3032
2749
  };
3033
- if (isNamedError(error, "VoiceTranscriptionNotConfiguredError")) return {
3034
- message: copy.errors.voiceNotConfigured,
3035
- cause: extractMessage(error.data) ?? null
3036
- };
3037
- if (isNamedError(error, "TelegramFileDownloadError")) return {
3038
- message: copy.errors.voiceDownload,
3039
- cause: extractMessage(error.data) ?? null
3040
- };
3041
- if (isNamedError(error, "VoiceTranscriptionFailedError")) return {
3042
- message: copy.errors.voiceTranscription,
3043
- cause: extractMessage(error.data) ?? null
3044
- };
3045
- if (isNamedError(error, "VoiceTranscriptEmptyError")) return {
3046
- message: copy.errors.voiceEmpty,
3047
- cause: extractMessage(error.data) ?? null
3048
- };
3049
- if (isNamedError(error, "VoiceMessageUnsupportedError")) return {
3050
- message: copy.errors.voiceUnsupported,
3051
- cause: extractMessage(error.data) ?? null
3052
- };
3053
2750
  if (isNamedError(error, "ImageFileDownloadError")) return {
3054
2751
  message: copy.errors.imageDownload,
3055
2752
  cause: extractMessage(error.data) ?? null
@@ -3182,7 +2879,7 @@ function presentStatusMarkdownSection(title, lines) {
3182
2879
  return [`## ${title}`, ...lines].join("\n");
3183
2880
  }
3184
2881
  function presentStatusPlainOverviewLines(input, copy, layout) {
3185
- const lines = [presentPlainStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentPlainStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition, layout))];
2882
+ const lines = [presentPlainStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout))];
3186
2883
  if (input.health.status === "error") return [
3187
2884
  ...lines,
3188
2885
  ...presentStatusPlainErrorDetailLines(input.health.error, copy, layout),
@@ -3195,7 +2892,7 @@ function presentStatusPlainOverviewLines(input, copy, layout) {
3195
2892
  ];
3196
2893
  }
3197
2894
  function presentStatusMarkdownOverviewLines(input, copy, layout) {
3198
- const lines = [presentMarkdownStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout)), presentMarkdownStatusBullet(layout.voiceRecognitionLabel, formatVoiceRecognitionBadge(input.voiceRecognition, layout))];
2895
+ const lines = [presentMarkdownStatusBullet(layout.connectivityLabel, input.health.status === "error" ? layout.errorStatus : formatHealthBadge(input.health.data.healthy, layout))];
3199
2896
  if (input.health.status === "error") return [
3200
2897
  ...lines,
3201
2898
  ...presentStatusMarkdownErrorDetailLines(input.health.error, copy, layout),
@@ -3295,10 +2992,6 @@ function splitStatusLines(text) {
3295
2992
  function formatHealthBadge(healthy, layout) {
3296
2993
  return healthy ? "🟢" : layout.errorStatus;
3297
2994
  }
3298
- function formatVoiceRecognitionBadge(status, _layout) {
3299
- if (status.status === "configured") return status.model ? `\uD83D\uDFE2 (${status.model})` : "🟡";
3300
- return "⚪";
3301
- }
3302
2995
  function formatLspStatusBadge(status) {
3303
2996
  switch (status.status) {
3304
2997
  case "connected": return "🟢";
@@ -3367,7 +3060,6 @@ function getStatusLayoutCopy(copy) {
3367
3060
  rootLabel: "Root",
3368
3061
  statusLabel: "Status",
3369
3062
  tbotVersionLabel: "opencode-tbot Version",
3370
- voiceRecognitionLabel: "Voice Recognition",
3371
3063
  workspaceTitle: "📁 Workspace"
3372
3064
  };
3373
3065
  return {
@@ -3390,7 +3082,6 @@ function getStatusLayoutCopy(copy) {
3390
3082
  rootLabel: "根目录",
3391
3083
  statusLabel: "状态",
3392
3084
  tbotVersionLabel: "opencode-tbot版本",
3393
- voiceRecognitionLabel: "语音识别",
3394
3085
  workspaceTitle: "📁 工作区"
3395
3086
  };
3396
3087
  }
@@ -4540,13 +4231,13 @@ function isRecoverableStructuredOutputError(promptReply) {
4540
4231
  }
4541
4232
  //#endregion
4542
4233
  //#region src/bot/handlers/file.handler.ts
4543
- var TELEGRAM_MAX_DOWNLOAD_BYTES$1 = 20 * 1024 * 1024;
4234
+ var TELEGRAM_MAX_DOWNLOAD_BYTES = 20 * 1024 * 1024;
4544
4235
  async function handleImageMessage(ctx, dependencies) {
4545
4236
  const image = resolveTelegramImage(ctx.message);
4546
4237
  if (!image) return;
4547
4238
  if (await replyIfSessionRenamePending(ctx, dependencies)) return;
4548
4239
  await executePromptRequest(ctx, dependencies, async () => {
4549
- if (typeof image.fileSize === "number" && image.fileSize > TELEGRAM_MAX_DOWNLOAD_BYTES$1) throw new ImageMessageUnsupportedError(`Image file size ${image.fileSize} exceeds the Telegram download limit of ${TELEGRAM_MAX_DOWNLOAD_BYTES$1} bytes.`);
4240
+ if (typeof image.fileSize === "number" && image.fileSize > TELEGRAM_MAX_DOWNLOAD_BYTES) throw new ImageMessageUnsupportedError(`Image file size ${image.fileSize} exceeds the Telegram download limit of ${TELEGRAM_MAX_DOWNLOAD_BYTES} bytes.`);
4550
4241
  const filePath = (await ctx.getFile()).file_path?.trim();
4551
4242
  if (!filePath) throw new ImageMessageUnsupportedError("Telegram did not provide a downloadable image file path.");
4552
4243
  return {
@@ -4612,39 +4303,17 @@ function registerMessageHandler(bot, dependencies) {
4612
4303
  }
4613
4304
  //#endregion
4614
4305
  //#region src/bot/handlers/voice.handler.ts
4615
- var DEFAULT_VOICE_FILE_NAME = "telegram-voice.ogg";
4616
- var DEFAULT_VOICE_MIME_TYPE = "audio/ogg";
4617
- var TELEGRAM_MAX_DOWNLOAD_BYTES = 20 * 1024 * 1024;
4618
4306
  async function handleVoiceMessage(ctx, dependencies) {
4619
4307
  if (!ctx.message.voice) return;
4620
4308
  if (await replyIfSessionRenamePending(ctx, dependencies)) return;
4621
- await executePromptRequest(ctx, dependencies, async () => {
4622
- const voice = ctx.message.voice;
4623
- if (!voice) throw new VoiceMessageUnsupportedError("Telegram voice payload is missing.");
4624
- if (typeof voice.file_size === "number" && voice.file_size > TELEGRAM_MAX_DOWNLOAD_BYTES) throw new VoiceMessageUnsupportedError(`Voice file size ${voice.file_size} exceeds the Telegram download limit of ${TELEGRAM_MAX_DOWNLOAD_BYTES} bytes.`);
4625
- const filePath = (await ctx.getFile()).file_path?.trim();
4626
- if (!filePath) throw new VoiceMessageUnsupportedError("Telegram did not provide a downloadable voice file path.");
4627
- const download = await dependencies.telegramFileClient.downloadFile({ filePath });
4628
- const mimeType = voice.mime_type?.trim() || download.mimeType || DEFAULT_VOICE_MIME_TYPE;
4629
- const filename = resolveVoiceFilename(filePath);
4630
- return { text: (await dependencies.voiceTranscriptionService.transcribeVoice({
4631
- data: download.data,
4632
- filename,
4633
- mimeType
4634
- })).text };
4635
- });
4309
+ const copy = await getChatCopy(dependencies.sessionRepo, ctx.chat.id);
4310
+ await ctx.reply(copy.errors.voiceUnsupported);
4636
4311
  }
4637
4312
  function registerVoiceHandler(bot, dependencies) {
4638
4313
  bot.on("message:voice", async (ctx) => {
4639
4314
  await handleVoiceMessage(ctx, dependencies);
4640
4315
  });
4641
4316
  }
4642
- function resolveVoiceFilename(filePath) {
4643
- const normalizedPath = filePath.trim();
4644
- if (!normalizedPath) return DEFAULT_VOICE_FILE_NAME;
4645
- const filename = normalizedPath.split("/").at(-1)?.trim();
4646
- return filename && filename.length > 0 ? filename : DEFAULT_VOICE_FILE_NAME;
4647
- }
4648
4317
  //#endregion
4649
4318
  //#region src/bot/middlewares/auth.ts
4650
4319
  function createAuthMiddleware(allowedChatIds) {