zardbot-telegram 1.0.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.
Files changed (111) hide show
  1. package/.env.example +116 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/dist/agent/manager.js +88 -0
  5. package/dist/agent/types.js +26 -0
  6. package/dist/app/start-bot-app.js +49 -0
  7. package/dist/bot/commands/abort.js +121 -0
  8. package/dist/bot/commands/commands.js +480 -0
  9. package/dist/bot/commands/definitions.js +27 -0
  10. package/dist/bot/commands/help.js +10 -0
  11. package/dist/bot/commands/models.js +38 -0
  12. package/dist/bot/commands/new.js +70 -0
  13. package/dist/bot/commands/opencode-start.js +101 -0
  14. package/dist/bot/commands/opencode-stop.js +44 -0
  15. package/dist/bot/commands/projects.js +223 -0
  16. package/dist/bot/commands/rename.js +139 -0
  17. package/dist/bot/commands/sessions.js +351 -0
  18. package/dist/bot/commands/start.js +43 -0
  19. package/dist/bot/commands/status.js +95 -0
  20. package/dist/bot/commands/task.js +399 -0
  21. package/dist/bot/commands/tasklist.js +220 -0
  22. package/dist/bot/commands/voice.js +145 -0
  23. package/dist/bot/handlers/agent.js +118 -0
  24. package/dist/bot/handlers/context.js +100 -0
  25. package/dist/bot/handlers/document.js +65 -0
  26. package/dist/bot/handlers/inline-menu.js +119 -0
  27. package/dist/bot/handlers/model.js +143 -0
  28. package/dist/bot/handlers/permission.js +235 -0
  29. package/dist/bot/handlers/prompt.js +240 -0
  30. package/dist/bot/handlers/question.js +390 -0
  31. package/dist/bot/handlers/tts.js +89 -0
  32. package/dist/bot/handlers/variant.js +138 -0
  33. package/dist/bot/handlers/voice.js +173 -0
  34. package/dist/bot/index.js +977 -0
  35. package/dist/bot/message-patterns.js +4 -0
  36. package/dist/bot/middleware/auth.js +30 -0
  37. package/dist/bot/middleware/interaction-guard.js +95 -0
  38. package/dist/bot/middleware/unknown-command.js +22 -0
  39. package/dist/bot/streaming/response-streamer.js +286 -0
  40. package/dist/bot/streaming/tool-call-streamer.js +285 -0
  41. package/dist/bot/utils/busy-guard.js +15 -0
  42. package/dist/bot/utils/commands.js +21 -0
  43. package/dist/bot/utils/file-download.js +91 -0
  44. package/dist/bot/utils/finalize-assistant-response.js +52 -0
  45. package/dist/bot/utils/keyboard.js +69 -0
  46. package/dist/bot/utils/send-with-markdown-fallback.js +165 -0
  47. package/dist/bot/utils/telegram-text.js +28 -0
  48. package/dist/bot/utils/thinking-message.js +8 -0
  49. package/dist/cli/args.js +98 -0
  50. package/dist/cli.js +80 -0
  51. package/dist/config.js +97 -0
  52. package/dist/i18n/de.js +357 -0
  53. package/dist/i18n/en.js +357 -0
  54. package/dist/i18n/es.js +357 -0
  55. package/dist/i18n/fr.js +357 -0
  56. package/dist/i18n/index.js +109 -0
  57. package/dist/i18n/ru.js +357 -0
  58. package/dist/i18n/zh.js +357 -0
  59. package/dist/index.js +26 -0
  60. package/dist/interaction/busy.js +8 -0
  61. package/dist/interaction/cleanup.js +32 -0
  62. package/dist/interaction/guard.js +140 -0
  63. package/dist/interaction/manager.js +106 -0
  64. package/dist/interaction/types.js +1 -0
  65. package/dist/keyboard/manager.js +172 -0
  66. package/dist/keyboard/types.js +1 -0
  67. package/dist/model/capabilities.js +62 -0
  68. package/dist/model/context-limit.js +57 -0
  69. package/dist/model/manager.js +259 -0
  70. package/dist/model/types.js +24 -0
  71. package/dist/opencode/client.js +13 -0
  72. package/dist/opencode/events.js +140 -0
  73. package/dist/permission/manager.js +100 -0
  74. package/dist/permission/types.js +1 -0
  75. package/dist/pinned/format.js +29 -0
  76. package/dist/pinned/manager.js +682 -0
  77. package/dist/pinned/types.js +1 -0
  78. package/dist/process/manager.js +273 -0
  79. package/dist/process/types.js +1 -0
  80. package/dist/project/manager.js +88 -0
  81. package/dist/question/manager.js +176 -0
  82. package/dist/question/types.js +1 -0
  83. package/dist/rename/manager.js +53 -0
  84. package/dist/runtime/bootstrap.js +350 -0
  85. package/dist/runtime/mode.js +74 -0
  86. package/dist/runtime/paths.js +37 -0
  87. package/dist/scheduled-task/creation-manager.js +113 -0
  88. package/dist/scheduled-task/display.js +239 -0
  89. package/dist/scheduled-task/executor.js +87 -0
  90. package/dist/scheduled-task/foreground-state.js +32 -0
  91. package/dist/scheduled-task/next-run.js +207 -0
  92. package/dist/scheduled-task/runtime.js +368 -0
  93. package/dist/scheduled-task/schedule-parser.js +169 -0
  94. package/dist/scheduled-task/store.js +65 -0
  95. package/dist/scheduled-task/types.js +19 -0
  96. package/dist/session/cache-manager.js +455 -0
  97. package/dist/session/manager.js +10 -0
  98. package/dist/settings/manager.js +158 -0
  99. package/dist/stt/client.js +97 -0
  100. package/dist/summary/aggregator.js +1136 -0
  101. package/dist/summary/formatter.js +491 -0
  102. package/dist/summary/subagent-formatter.js +63 -0
  103. package/dist/summary/tool-message-batcher.js +90 -0
  104. package/dist/tts/client.js +130 -0
  105. package/dist/utils/error-format.js +29 -0
  106. package/dist/utils/logger.js +127 -0
  107. package/dist/utils/safe-background-task.js +33 -0
  108. package/dist/utils/telegram-rate-limit-retry.js +93 -0
  109. package/dist/variant/manager.js +103 -0
  110. package/dist/variant/types.js +1 -0
  111. package/package.json +79 -0
@@ -0,0 +1,130 @@
1
+ import { config } from "../config.js";
2
+ import { logger } from "../utils/logger.js";
3
+ const TTS_REQUEST_TIMEOUT_MS = 2_400_000; // 40 minutes for long responses
4
+ /**
5
+ * Returns true if TTS is configured (API URL is set).
6
+ */
7
+ export function isTtsConfigured() {
8
+ return Boolean(config.tts?.apiUrl);
9
+ }
10
+ /**
11
+ * Fetches available voices from the TTS server.
12
+ *
13
+ * GETs from `{TTS_API_URL}/v1/audio/voices` (Pocket TTS API).
14
+ *
15
+ * @returns Array of available voices
16
+ */
17
+ export async function getAvailableVoices() {
18
+ if (!isTtsConfigured()) {
19
+ logger.warn("[TTS] Cannot fetch voices: TTS API URL not configured");
20
+ return [];
21
+ }
22
+ try {
23
+ const voicesUrl = `${config.tts.apiUrl}/v1/audio/voices`;
24
+ const response = await fetch(voicesUrl, {
25
+ method: "GET",
26
+ signal: AbortSignal.timeout(30_000),
27
+ });
28
+ if (response.ok) {
29
+ const rawData = await response.text();
30
+ logger.debug("[TTS] Raw voices response:", rawData.substring(0, 200));
31
+ const parsed = JSON.parse(rawData);
32
+ // Pocket TTS returns {voices: [{voice_id, name}, ...]} or an array
33
+ const voices = Array.isArray(parsed)
34
+ ? parsed
35
+ : parsed.voices ||
36
+ parsed.data ||
37
+ [];
38
+ logger.info(`[TTS] Parsed voices count: ${voices.length}`);
39
+ if (Array.isArray(voices) && voices.length > 0) {
40
+ return voices.map((v) => {
41
+ const voice = v;
42
+ const id = voice.voice_id || voice.id || voice.name || String(v);
43
+ const name = voice.name ||
44
+ id
45
+ .split("-")
46
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
47
+ .join(" ");
48
+ return {
49
+ id,
50
+ name,
51
+ description: voice.name ? undefined : "Pocket TTS voice",
52
+ };
53
+ });
54
+ }
55
+ }
56
+ else {
57
+ logger.warn(`[TTS] Voices endpoint returned HTTP ${response.status}`);
58
+ }
59
+ }
60
+ catch (err) {
61
+ logger.warn("[TTS] Failed to fetch voices from server", err);
62
+ }
63
+ // Fallback: return just the configured default voice
64
+ const defaultVoices = [
65
+ {
66
+ id: config.tts.voice || "david-attenborough-original",
67
+ name: config.tts.voice || "David Attenborough",
68
+ description: "Default voice",
69
+ },
70
+ ];
71
+ logger.debug(`[TTS] Using ${defaultVoices.length} default voice(s)`);
72
+ return defaultVoices;
73
+ }
74
+ /**
75
+ * Synthesizes speech from text using the TTS API.
76
+ *
77
+ * Sends a JSON POST to `{TTS_API_URL}/v1/audio/speech`.
78
+ *
79
+ * @param text - Text to synthesize
80
+ * @param voice - Voice ID to use (defaults to configured default voice)
81
+ * @returns Audio buffer and content type
82
+ */
83
+ export async function synthesizeSpeech(text, voice) {
84
+ if (!isTtsConfigured()) {
85
+ throw new Error("TTS is not configured: TTS_API_URL is required");
86
+ }
87
+ const url = `${config.tts.apiUrl}/v1/audio/speech`;
88
+ let selectedVoice = voice || config.tts.voice || "david-attenborough-original";
89
+ if (!selectedVoice.match(/\.\w+$/)) {
90
+ selectedVoice += ".wav";
91
+ }
92
+ const body = {
93
+ model: config.tts.model || "tts-1",
94
+ input: text,
95
+ voice: selectedVoice,
96
+ response_format: "wav",
97
+ speed: config.tts.speed || 1.0,
98
+ };
99
+ logger.debug(`[TTS] Sending speech synthesis request: url=${url}, voice=${selectedVoice}, text=${text.length} chars`);
100
+ const controller = new AbortController();
101
+ const timeout = setTimeout(() => controller.abort(), TTS_REQUEST_TIMEOUT_MS);
102
+ try {
103
+ const response = await fetch(url, {
104
+ method: "POST",
105
+ headers: {
106
+ "Content-Type": "application/json",
107
+ },
108
+ body: JSON.stringify(body),
109
+ signal: controller.signal,
110
+ });
111
+ if (!response.ok) {
112
+ const errorBody = await response.text().catch(() => "");
113
+ throw new Error(`TTS API returned HTTP ${response.status}: ${errorBody || response.statusText}`);
114
+ }
115
+ const contentType = response.headers.get("content-type") || "audio/wav";
116
+ const arrayBuffer = await response.arrayBuffer();
117
+ const audioBuffer = Buffer.from(arrayBuffer);
118
+ logger.debug(`[TTS] Synthesized audio: ${audioBuffer.length} bytes, type=${contentType}`);
119
+ return { audio: audioBuffer, contentType };
120
+ }
121
+ catch (err) {
122
+ if (err instanceof DOMException && err.name === "AbortError") {
123
+ throw new Error(`TTS request timed out after ${TTS_REQUEST_TIMEOUT_MS}ms`);
124
+ }
125
+ throw err;
126
+ }
127
+ finally {
128
+ clearTimeout(timeout);
129
+ }
130
+ }
@@ -0,0 +1,29 @@
1
+ const DEFAULT_MAX_ERROR_DETAILS_LENGTH = 1500;
2
+ function clipText(value, maxLength) {
3
+ if (value.length <= maxLength) {
4
+ return value;
5
+ }
6
+ return `${value.slice(0, Math.max(0, maxLength - 3))}...`;
7
+ }
8
+ export function formatErrorDetails(error, maxLength = DEFAULT_MAX_ERROR_DETAILS_LENGTH) {
9
+ let details = "";
10
+ if (error instanceof Error) {
11
+ details = error.stack ?? `${error.name}: ${error.message}`;
12
+ }
13
+ else if (typeof error === "string") {
14
+ details = error;
15
+ }
16
+ else {
17
+ try {
18
+ details = JSON.stringify(error, null, 2);
19
+ }
20
+ catch {
21
+ details = String(error);
22
+ }
23
+ }
24
+ const normalized = details.trim();
25
+ if (!normalized || normalized === "{}" || normalized === "[object Object]") {
26
+ return "unknown error";
27
+ }
28
+ return clipText(normalized, maxLength);
29
+ }
@@ -0,0 +1,127 @@
1
+ import { config } from "../config.js";
2
+ /**
3
+ * Mapping of log levels to numeric values for comparison
4
+ * Used to determine if a message should be logged based on configured level
5
+ */
6
+ const LOG_LEVELS = {
7
+ debug: 0,
8
+ info: 1,
9
+ warn: 2,
10
+ error: 3,
11
+ };
12
+ /**
13
+ * Normalizes a string value to a valid LogLevel
14
+ * Falls back to 'info' if the value is invalid
15
+ *
16
+ * @param value - The log level string to normalize
17
+ * @returns A valid LogLevel
18
+ */
19
+ function normalizeLogLevel(value) {
20
+ if (value in LOG_LEVELS) {
21
+ return value;
22
+ }
23
+ return "info";
24
+ }
25
+ /**
26
+ * Formats the log message prefix with timestamp and level
27
+ *
28
+ * @param level - The log level for the message
29
+ * @returns Formatted prefix string
30
+ */
31
+ function formatPrefix(level) {
32
+ return `[${new Date().toISOString()}] [${level.toUpperCase()}]`;
33
+ }
34
+ /**
35
+ * Formats individual arguments for logging
36
+ * Special handling for Error objects to extract stack trace
37
+ *
38
+ * @param arg - The argument to format
39
+ * @returns Formatted argument
40
+ */
41
+ function formatArg(arg) {
42
+ if (arg instanceof Error) {
43
+ return arg.stack ?? `${arg.name}: ${arg.message}`;
44
+ }
45
+ return arg;
46
+ }
47
+ /**
48
+ * Prepends formatted prefix to log arguments
49
+ * Handles different argument formats (string vs non-string first argument)
50
+ *
51
+ * @param level - The log level for prefix formatting
52
+ * @param args - The arguments to log
53
+ * @returns Array with prefix prepended
54
+ */
55
+ function withPrefix(level, args) {
56
+ const formattedArgs = args.map((arg) => formatArg(arg));
57
+ const prefix = formatPrefix(level);
58
+ if (formattedArgs.length === 0) {
59
+ return [prefix];
60
+ }
61
+ if (typeof formattedArgs[0] === "string") {
62
+ return [`${prefix} ${formattedArgs[0]}`, ...formattedArgs.slice(1)];
63
+ }
64
+ return [prefix, ...formattedArgs];
65
+ }
66
+ /**
67
+ * Determines if a message should be logged based on configured log level
68
+ * Messages with level >= configured level will be logged
69
+ *
70
+ * @param level - The level of the message to check
71
+ * @returns True if the message should be logged
72
+ */
73
+ function shouldLog(level) {
74
+ const configLevel = normalizeLogLevel(config.server.logLevel);
75
+ return LOG_LEVELS[level] >= LOG_LEVELS[configLevel];
76
+ }
77
+ /**
78
+ * Logger interface with methods for different log levels
79
+ * Each method checks if the message should be logged based on configured level
80
+ * and formats the output with timestamp and level prefix
81
+ */
82
+ export const logger = {
83
+ /**
84
+ * Logs debug-level messages (most verbose)
85
+ * Used for detailed diagnostics and internal operations
86
+ *
87
+ * @param args - Arguments to log
88
+ */
89
+ debug: (...args) => {
90
+ if (shouldLog("debug")) {
91
+ console.log(...withPrefix("debug", args));
92
+ }
93
+ },
94
+ /**
95
+ * Logs info-level messages
96
+ * Used for important events and general information
97
+ *
98
+ * @param args - Arguments to log
99
+ */
100
+ info: (...args) => {
101
+ if (shouldLog("info")) {
102
+ console.log(...withPrefix("info", args));
103
+ }
104
+ },
105
+ /**
106
+ * Logs warning-level messages
107
+ * Used for recoverable errors and potential issues
108
+ *
109
+ * @param args - Arguments to log
110
+ */
111
+ warn: (...args) => {
112
+ if (shouldLog("warn")) {
113
+ console.warn(...withPrefix("warn", args));
114
+ }
115
+ },
116
+ /**
117
+ * Logs error-level messages
118
+ * Used for critical failures and exceptions
119
+ *
120
+ * @param args - Arguments to log
121
+ */
122
+ error: (...args) => {
123
+ if (shouldLog("error")) {
124
+ console.error(...withPrefix("error", args));
125
+ }
126
+ },
127
+ };
@@ -0,0 +1,33 @@
1
+ import { logger } from "./logger.js";
2
+ function runHookSafely(taskName, hookName, hook, value) {
3
+ if (!hook) {
4
+ return;
5
+ }
6
+ try {
7
+ void Promise.resolve(hook(value)).catch((hookError) => {
8
+ logger.error(`[safeBackgroundTask] ${taskName}: ${hookName} failed:`, hookError);
9
+ });
10
+ }
11
+ catch (hookError) {
12
+ logger.error(`[safeBackgroundTask] ${taskName}: ${hookName} failed:`, hookError);
13
+ }
14
+ }
15
+ export function safeBackgroundTask({ taskName, task, onSuccess, onError, }) {
16
+ const handleError = (error) => {
17
+ logger.error(`[safeBackgroundTask] ${taskName} failed:`, error);
18
+ runHookSafely(taskName, "onError", onError, error);
19
+ };
20
+ try {
21
+ const taskPromise = task();
22
+ void taskPromise
23
+ .then((result) => {
24
+ runHookSafely(taskName, "onSuccess", onSuccess, result);
25
+ })
26
+ .catch((error) => {
27
+ handleError(error);
28
+ });
29
+ }
30
+ catch (error) {
31
+ handleError(error);
32
+ }
33
+ }
@@ -0,0 +1,93 @@
1
+ function getErrorMessage(error) {
2
+ const parts = [];
3
+ if (error instanceof Error) {
4
+ parts.push(error.message);
5
+ }
6
+ if (typeof error === "object" && error !== null) {
7
+ const description = Reflect.get(error, "description");
8
+ if (typeof description === "string") {
9
+ parts.push(description);
10
+ }
11
+ const message = Reflect.get(error, "message");
12
+ if (typeof message === "string") {
13
+ parts.push(message);
14
+ }
15
+ }
16
+ if (typeof error === "string") {
17
+ parts.push(error);
18
+ }
19
+ return parts.join("\n");
20
+ }
21
+ function getRetryAfterSecondsFromError(error) {
22
+ if (typeof error === "object" && error !== null) {
23
+ const parameters = Reflect.get(error, "parameters");
24
+ if (typeof parameters === "object" && parameters !== null) {
25
+ const retryAfter = Reflect.get(parameters, "retry_after");
26
+ if (typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter > 0) {
27
+ return retryAfter;
28
+ }
29
+ }
30
+ }
31
+ const message = getErrorMessage(error);
32
+ const retryMatch = message.match(/retry after\s+(\d+)/i);
33
+ if (!retryMatch) {
34
+ return null;
35
+ }
36
+ const parsedSeconds = Number.parseInt(retryMatch[1], 10);
37
+ if (!Number.isFinite(parsedSeconds) || parsedSeconds <= 0) {
38
+ return null;
39
+ }
40
+ return parsedSeconds;
41
+ }
42
+ function isTelegramRateLimitError(error) {
43
+ if (typeof error === "object" && error !== null) {
44
+ const status = Reflect.get(error, "status");
45
+ if (typeof status === "number" && status === 429) {
46
+ return true;
47
+ }
48
+ const errorCode = Reflect.get(error, "error_code");
49
+ if (typeof errorCode === "number" && errorCode === 429) {
50
+ return true;
51
+ }
52
+ }
53
+ const message = getErrorMessage(error).toLowerCase();
54
+ return /\b429\b/.test(message) || message.includes("too many requests");
55
+ }
56
+ function wait(ms) {
57
+ return new Promise((resolve) => {
58
+ setTimeout(resolve, ms);
59
+ });
60
+ }
61
+ export function getTelegramRetryAfterMs(error, fallbackDelayMs = 1000) {
62
+ if (!isTelegramRateLimitError(error)) {
63
+ return null;
64
+ }
65
+ const retryAfterSeconds = getRetryAfterSecondsFromError(error);
66
+ if (retryAfterSeconds !== null) {
67
+ return retryAfterSeconds * 1000;
68
+ }
69
+ return Math.max(1, Math.floor(fallbackDelayMs));
70
+ }
71
+ export async function withTelegramRateLimitRetry(operation, options) {
72
+ const maxRetries = Math.max(0, Math.floor(options?.maxRetries ?? 3));
73
+ const fallbackDelayMs = options?.fallbackDelayMs ?? 1000;
74
+ let attempt = 0;
75
+ while (true) {
76
+ try {
77
+ return await operation();
78
+ }
79
+ catch (error) {
80
+ const retryAfterMs = getTelegramRetryAfterMs(error, fallbackDelayMs);
81
+ if (retryAfterMs === null || attempt >= maxRetries) {
82
+ throw error;
83
+ }
84
+ attempt += 1;
85
+ options?.onRetry?.({
86
+ attempt,
87
+ retryAfterMs,
88
+ error,
89
+ });
90
+ await wait(retryAfterMs);
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Variant Manager - manages model variants (reasoning modes)
3
+ */
4
+ import { opencodeClient } from "../opencode/client.js";
5
+ import { getCurrentModel, setCurrentModel } from "../settings/manager.js";
6
+ import { logger } from "../utils/logger.js";
7
+ /**
8
+ * Get available variants for a model from OpenCode API
9
+ * @param providerID Provider ID
10
+ * @param modelID Model ID
11
+ * @returns Array of available variants
12
+ */
13
+ export async function getAvailableVariants(providerID, modelID) {
14
+ try {
15
+ const { data, error } = await opencodeClient.config.providers();
16
+ if (error || !data) {
17
+ logger.warn("[VariantManager] Failed to fetch providers:", error);
18
+ return [{ id: "default" }];
19
+ }
20
+ const provider = data.providers.find((p) => p.id === providerID);
21
+ if (!provider) {
22
+ logger.warn(`[VariantManager] Provider ${providerID} not found`);
23
+ return [{ id: "default" }];
24
+ }
25
+ const model = provider.models[modelID];
26
+ if (!model) {
27
+ logger.warn(`[VariantManager] Model ${modelID} not found in provider ${providerID}`);
28
+ return [{ id: "default" }];
29
+ }
30
+ // Start with default variant (always present)
31
+ const variants = [{ id: "default" }];
32
+ if (model.variants) {
33
+ // Add other variants from API (excluding default if it's already there)
34
+ const apiVariants = Object.entries(model.variants)
35
+ .filter(([id]) => id !== "default")
36
+ .map(([id, info]) => ({
37
+ id,
38
+ disabled: info.disabled,
39
+ }));
40
+ variants.push(...apiVariants);
41
+ logger.debug(`[VariantManager] Found ${variants.length} variants for ${providerID}/${modelID} (including default)`);
42
+ }
43
+ else {
44
+ logger.debug(`[VariantManager] No variants found for ${providerID}/${modelID}, using default only`);
45
+ }
46
+ return variants;
47
+ }
48
+ catch (err) {
49
+ logger.error("[VariantManager] Error fetching variants:", err);
50
+ return [{ id: "default" }];
51
+ }
52
+ }
53
+ /**
54
+ * Get current variant from settings
55
+ * @returns Current variant ID (defaults to "default")
56
+ */
57
+ export function getCurrentVariant() {
58
+ const currentModel = getCurrentModel();
59
+ return currentModel?.variant || "default";
60
+ }
61
+ /**
62
+ * Set current variant in settings
63
+ * @param variantId Variant ID to set
64
+ */
65
+ export function setCurrentVariant(variantId) {
66
+ const currentModel = getCurrentModel();
67
+ if (!currentModel) {
68
+ logger.warn("[VariantManager] Cannot set variant: no current model");
69
+ return;
70
+ }
71
+ currentModel.variant = variantId;
72
+ setCurrentModel(currentModel);
73
+ logger.info(`[VariantManager] Variant set to: ${variantId}`);
74
+ }
75
+ /**
76
+ * Format variant for button display
77
+ * @param variantId Variant ID (e.g., "default", "low", "high")
78
+ * @returns Formatted string "💭 Default", "💭 Low", etc.
79
+ */
80
+ export function formatVariantForButton(variantId) {
81
+ const capitalized = variantId.charAt(0).toUpperCase() + variantId.slice(1);
82
+ return `💡 ${capitalized}`;
83
+ }
84
+ /**
85
+ * Format variant for display in messages
86
+ * @param variantId Variant ID
87
+ * @returns Formatted string with capitalized first letter
88
+ */
89
+ export function formatVariantForDisplay(variantId) {
90
+ return variantId.charAt(0).toUpperCase() + variantId.slice(1);
91
+ }
92
+ /**
93
+ * Validate if a model supports a specific variant
94
+ * @param providerID Provider ID
95
+ * @param modelID Model ID
96
+ * @param variantId Variant ID to validate
97
+ * @returns true if variant is supported, false otherwise
98
+ */
99
+ export async function validateVariantForModel(providerID, modelID, variantId) {
100
+ const variants = await getAvailableVariants(providerID, modelID);
101
+ const found = variants.find((v) => v.id === variantId && !v.disabled);
102
+ return found !== undefined;
103
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "zardbot-telegram",
3
+ "version": "1.0.0",
4
+ "description": "ZardBot Telegram Bot - A Telegram client for OpenCode with TTS and STT support.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "zardbot-telegram": "dist/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": "./dist/index.js",
12
+ "./cli": "./dist/cli.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE",
18
+ ".env.example"
19
+ ],
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "start": "node dist/index.js",
26
+ "dev": "npm run build && npm start",
27
+ "release:prepare": "node scripts/release-prepare.mjs",
28
+ "release:rc": "node scripts/release-prepare.mjs rc",
29
+ "release:notes:preview": "node scripts/release-notes-preview.mjs",
30
+ "lint": "eslint src --ext .ts --max-warnings=0",
31
+ "format": "prettier --write \"src/**/*.ts\"",
32
+ "test": "vitest run",
33
+ "test:coverage": "vitest run --coverage"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/ai-joe-git/zardbot-telegram.git"
38
+ },
39
+ "keywords": [
40
+ "zardbot",
41
+ "opencode",
42
+ "telegram",
43
+ "telegram-bot",
44
+ "grammy",
45
+ "tts",
46
+ "stt",
47
+ "developer-tools",
48
+ "cli"
49
+ ],
50
+ "author": "ZardBot Team",
51
+ "license": "MIT",
52
+ "bugs": {
53
+ "url": "https://github.com/ai-joe-git/zardbot-telegram/issues"
54
+ },
55
+ "homepage": "https://github.com/ai-joe-git/zardbot-telegram#readme",
56
+ "dependencies": {
57
+ "@grammyjs/menu": "^1.3.1",
58
+ "@opencode-ai/sdk": "^1.1.21",
59
+ "better-sqlite3": "^12.6.2",
60
+ "dotenv": "^17.2.3",
61
+ "grammy": "^1.39.2",
62
+ "https-proxy-agent": "^7.0.6",
63
+ "socks-proxy-agent": "^8.0.5",
64
+ "telegram-markdown-v2": "^0.0.4"
65
+ },
66
+ "devDependencies": {
67
+ "@types/better-sqlite3": "^7.6.13",
68
+ "@types/node": "^25.0.8",
69
+ "@typescript-eslint/eslint-plugin": "^8.53.0",
70
+ "@typescript-eslint/parser": "^8.53.0",
71
+ "@vitest/coverage-v8": "^3.2.4",
72
+ "eslint": "^8.57.1",
73
+ "eslint-config-prettier": "^10.1.8",
74
+ "prettier": "^3.8.0",
75
+ "tsx": "^4.21.0",
76
+ "typescript": "^5.9.3",
77
+ "vitest": "^3.2.4"
78
+ }
79
+ }