@grinev/opencode-telegram-bot 0.1.0-rc.1
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 +34 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/agent/manager.js +92 -0
- package/dist/agent/types.js +26 -0
- package/dist/app/start-bot-app.js +26 -0
- package/dist/bot/commands/agent.js +16 -0
- package/dist/bot/commands/definitions.js +20 -0
- package/dist/bot/commands/help.js +7 -0
- package/dist/bot/commands/model.js +16 -0
- package/dist/bot/commands/models.js +37 -0
- package/dist/bot/commands/new.js +58 -0
- package/dist/bot/commands/opencode-start.js +87 -0
- package/dist/bot/commands/opencode-stop.js +46 -0
- package/dist/bot/commands/projects.js +104 -0
- package/dist/bot/commands/server-restart.js +23 -0
- package/dist/bot/commands/server-start.js +23 -0
- package/dist/bot/commands/sessions.js +240 -0
- package/dist/bot/commands/start.js +40 -0
- package/dist/bot/commands/status.js +63 -0
- package/dist/bot/commands/stop.js +92 -0
- package/dist/bot/handlers/agent.js +96 -0
- package/dist/bot/handlers/context.js +112 -0
- package/dist/bot/handlers/model.js +115 -0
- package/dist/bot/handlers/permission.js +158 -0
- package/dist/bot/handlers/question.js +294 -0
- package/dist/bot/handlers/variant.js +126 -0
- package/dist/bot/index.js +573 -0
- package/dist/bot/middleware/auth.js +30 -0
- package/dist/bot/utils/keyboard.js +66 -0
- package/dist/cli/args.js +97 -0
- package/dist/cli.js +90 -0
- package/dist/config.js +46 -0
- package/dist/index.js +26 -0
- package/dist/keyboard/manager.js +171 -0
- package/dist/keyboard/types.js +1 -0
- package/dist/model/manager.js +123 -0
- package/dist/model/types.js +26 -0
- package/dist/opencode/client.js +13 -0
- package/dist/opencode/events.js +79 -0
- package/dist/opencode/server.js +104 -0
- package/dist/permission/manager.js +78 -0
- package/dist/permission/types.js +1 -0
- package/dist/pinned/manager.js +610 -0
- package/dist/pinned/types.js +1 -0
- package/dist/pinned-message/service.js +54 -0
- package/dist/process/manager.js +273 -0
- package/dist/process/types.js +1 -0
- package/dist/project/manager.js +28 -0
- package/dist/question/manager.js +143 -0
- package/dist/question/types.js +1 -0
- package/dist/runtime/bootstrap.js +278 -0
- package/dist/runtime/mode.js +74 -0
- package/dist/runtime/paths.js +37 -0
- package/dist/session/manager.js +10 -0
- package/dist/session/state.js +24 -0
- package/dist/settings/manager.js +99 -0
- package/dist/status/formatter.js +44 -0
- package/dist/summary/aggregator.js +427 -0
- package/dist/summary/formatter.js +226 -0
- package/dist/utils/formatting.js +237 -0
- package/dist/utils/logger.js +59 -0
- package/dist/utils/safe-background-task.js +33 -0
- package/dist/variant/manager.js +103 -0
- package/dist/variant/types.js +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
function normalizeParseMode(value) {
|
|
4
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
5
|
+
return "MarkdownV2";
|
|
6
|
+
}
|
|
7
|
+
const raw = value.trim();
|
|
8
|
+
const normalized = raw.toLowerCase();
|
|
9
|
+
switch (normalized) {
|
|
10
|
+
case "markdownv2":
|
|
11
|
+
return "MarkdownV2";
|
|
12
|
+
case "markdown":
|
|
13
|
+
return "Markdown";
|
|
14
|
+
case "html":
|
|
15
|
+
return "HTML";
|
|
16
|
+
case "none":
|
|
17
|
+
return "None";
|
|
18
|
+
default:
|
|
19
|
+
logger.warn(`[Formatting] Invalid TELEGRAM_PARSE_MODE="${raw}", falling back to MarkdownV2`);
|
|
20
|
+
return "MarkdownV2";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function getParseMode() {
|
|
24
|
+
return normalizeParseMode(config.telegram.parseMode);
|
|
25
|
+
}
|
|
26
|
+
export function sendMessageOptions() {
|
|
27
|
+
const mode = getParseMode();
|
|
28
|
+
if (mode === "None") {
|
|
29
|
+
return {};
|
|
30
|
+
}
|
|
31
|
+
return { parse_mode: mode };
|
|
32
|
+
}
|
|
33
|
+
function escapeHtml(text) {
|
|
34
|
+
return text
|
|
35
|
+
.replaceAll("&", "&")
|
|
36
|
+
.replaceAll("<", "<")
|
|
37
|
+
.replaceAll(">", ">")
|
|
38
|
+
.replaceAll('"', """)
|
|
39
|
+
.replaceAll("'", "'");
|
|
40
|
+
}
|
|
41
|
+
function escapeMarkdownV2Text(text) {
|
|
42
|
+
let result = text.replaceAll("\\", "\\\\");
|
|
43
|
+
const specialChars = [
|
|
44
|
+
"_",
|
|
45
|
+
"*",
|
|
46
|
+
"[",
|
|
47
|
+
"]",
|
|
48
|
+
"(",
|
|
49
|
+
")",
|
|
50
|
+
"~",
|
|
51
|
+
"`",
|
|
52
|
+
">",
|
|
53
|
+
"#",
|
|
54
|
+
"+",
|
|
55
|
+
"-",
|
|
56
|
+
"=",
|
|
57
|
+
"|",
|
|
58
|
+
"{",
|
|
59
|
+
"}",
|
|
60
|
+
".",
|
|
61
|
+
"!",
|
|
62
|
+
];
|
|
63
|
+
for (const char of specialChars) {
|
|
64
|
+
result = result.split(char).join(`\\${char}`);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function escapeMarkdownText(text) {
|
|
69
|
+
let result = text.replaceAll("\\", "\\\\");
|
|
70
|
+
const specialChars = ["_", "*", "`", "[", "]"];
|
|
71
|
+
for (const char of specialChars) {
|
|
72
|
+
result = result.split(char).join(`\\${char}`);
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function escapeMarkdownV2Code(text) {
|
|
77
|
+
return text.replaceAll("\\", "\\\\").replaceAll("`", "\\`");
|
|
78
|
+
}
|
|
79
|
+
function escapeMarkdownCode(text) {
|
|
80
|
+
return text.replaceAll("\\", "\\\\").replaceAll("`", "\\`");
|
|
81
|
+
}
|
|
82
|
+
export function escapeText(text) {
|
|
83
|
+
const mode = getParseMode();
|
|
84
|
+
switch (mode) {
|
|
85
|
+
case "MarkdownV2":
|
|
86
|
+
return escapeMarkdownV2Text(text);
|
|
87
|
+
case "Markdown":
|
|
88
|
+
return escapeMarkdownText(text);
|
|
89
|
+
case "HTML":
|
|
90
|
+
return escapeHtml(text);
|
|
91
|
+
case "None":
|
|
92
|
+
return text;
|
|
93
|
+
default:
|
|
94
|
+
return text;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function formatBold(text) {
|
|
98
|
+
const mode = getParseMode();
|
|
99
|
+
switch (mode) {
|
|
100
|
+
case "MarkdownV2":
|
|
101
|
+
return `*${escapeMarkdownV2Text(text)}*`;
|
|
102
|
+
case "Markdown":
|
|
103
|
+
return `*${escapeMarkdownText(text)}*`;
|
|
104
|
+
case "HTML":
|
|
105
|
+
return `<b>${escapeHtml(text)}</b>`;
|
|
106
|
+
case "None":
|
|
107
|
+
return text;
|
|
108
|
+
default:
|
|
109
|
+
return text;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function formatItalic(text) {
|
|
113
|
+
const mode = getParseMode();
|
|
114
|
+
switch (mode) {
|
|
115
|
+
case "MarkdownV2":
|
|
116
|
+
return `_${escapeMarkdownV2Text(text)}_`;
|
|
117
|
+
case "Markdown":
|
|
118
|
+
return `_${escapeMarkdownText(text)}_`;
|
|
119
|
+
case "HTML":
|
|
120
|
+
return `<i>${escapeHtml(text)}</i>`;
|
|
121
|
+
case "None":
|
|
122
|
+
return text;
|
|
123
|
+
default:
|
|
124
|
+
return text;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export function formatCode(text) {
|
|
128
|
+
const mode = getParseMode();
|
|
129
|
+
switch (mode) {
|
|
130
|
+
case "MarkdownV2":
|
|
131
|
+
return `\`${escapeMarkdownV2Code(text)}\``;
|
|
132
|
+
case "Markdown":
|
|
133
|
+
return `\`${escapeMarkdownCode(text)}\``;
|
|
134
|
+
case "HTML":
|
|
135
|
+
return `<code>${escapeHtml(text)}</code>`;
|
|
136
|
+
case "None":
|
|
137
|
+
return text;
|
|
138
|
+
default:
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export function formatCodeBlock(text) {
|
|
143
|
+
const mode = getParseMode();
|
|
144
|
+
switch (mode) {
|
|
145
|
+
case "MarkdownV2":
|
|
146
|
+
return `\`\`\`\n${text}\n\`\`\``;
|
|
147
|
+
case "Markdown":
|
|
148
|
+
return `\`\`\`\n${text}\n\`\`\``;
|
|
149
|
+
case "HTML":
|
|
150
|
+
return `<pre><code>${escapeHtml(text)}</code></pre>`;
|
|
151
|
+
case "None":
|
|
152
|
+
return text;
|
|
153
|
+
default:
|
|
154
|
+
return text;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function formatInlineMarkdownLike(text) {
|
|
158
|
+
const mode = getParseMode();
|
|
159
|
+
if (mode === "None") {
|
|
160
|
+
return text;
|
|
161
|
+
}
|
|
162
|
+
const boldRe = /\*\*([^*]+)\*\*/g;
|
|
163
|
+
const codeRe = /`([^`]+)`/g;
|
|
164
|
+
let i = 0;
|
|
165
|
+
let out = "";
|
|
166
|
+
while (i < text.length) {
|
|
167
|
+
const boldMatch = boldRe.exec(text);
|
|
168
|
+
const codeMatch = codeRe.exec(text);
|
|
169
|
+
const nextBoldStart = boldMatch ? boldMatch.index : Number.POSITIVE_INFINITY;
|
|
170
|
+
const nextCodeStart = codeMatch ? codeMatch.index : Number.POSITIVE_INFINITY;
|
|
171
|
+
const nextStart = Math.min(nextBoldStart, nextCodeStart);
|
|
172
|
+
if (nextStart === Number.POSITIVE_INFINITY) {
|
|
173
|
+
out += escapeText(text.slice(i));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
if (nextStart > i) {
|
|
177
|
+
out += escapeText(text.slice(i, nextStart));
|
|
178
|
+
i = nextStart;
|
|
179
|
+
}
|
|
180
|
+
if (nextStart === nextCodeStart && codeMatch) {
|
|
181
|
+
out += formatCode(codeMatch[1]);
|
|
182
|
+
i = codeMatch.index + codeMatch[0].length;
|
|
183
|
+
boldRe.lastIndex = i;
|
|
184
|
+
codeRe.lastIndex = i;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (nextStart === nextBoldStart && boldMatch) {
|
|
188
|
+
out += formatBold(boldMatch[1]);
|
|
189
|
+
i = boldMatch.index + boldMatch[0].length;
|
|
190
|
+
boldRe.lastIndex = i;
|
|
191
|
+
codeRe.lastIndex = i;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Converts common Markdown (LLM/GitHub-like) into Telegram-compatible formatting.
|
|
199
|
+
* This is not a full Markdown parser; it focuses on a safe subset:
|
|
200
|
+
* - Headings (#, ##, ...) -> bold line
|
|
201
|
+
* - **bold** -> bold
|
|
202
|
+
* - `code` -> inline code
|
|
203
|
+
* - ```code blocks``` -> code block
|
|
204
|
+
*/
|
|
205
|
+
export function formatMarkdownLike(text) {
|
|
206
|
+
const mode = getParseMode();
|
|
207
|
+
if (mode === "None") {
|
|
208
|
+
return text;
|
|
209
|
+
}
|
|
210
|
+
const fenceRe = /```[^\n]*\n([\s\S]*?)```/g;
|
|
211
|
+
let lastIndex = 0;
|
|
212
|
+
let out = "";
|
|
213
|
+
let match;
|
|
214
|
+
while ((match = fenceRe.exec(text)) !== null) {
|
|
215
|
+
const before = text.slice(lastIndex, match.index);
|
|
216
|
+
out += formatMarkdownLikePlain(before);
|
|
217
|
+
const code = match[1] ?? "";
|
|
218
|
+
out += formatCodeBlock(code.replace(/\n$/, ""));
|
|
219
|
+
lastIndex = match.index + match[0].length;
|
|
220
|
+
}
|
|
221
|
+
out += formatMarkdownLikePlain(text.slice(lastIndex));
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
function formatMarkdownLikePlain(text) {
|
|
225
|
+
const lines = text.split("\n");
|
|
226
|
+
const outLines = [];
|
|
227
|
+
for (const line of lines) {
|
|
228
|
+
const headingMatch = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
229
|
+
if (headingMatch) {
|
|
230
|
+
const title = headingMatch[2]?.trim() ?? "";
|
|
231
|
+
outLines.push(title ? formatBold(title) : "");
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
outLines.push(formatInlineMarkdownLike(line));
|
|
235
|
+
}
|
|
236
|
+
return outLines.join("\n");
|
|
237
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
const LOG_LEVELS = {
|
|
3
|
+
debug: 0,
|
|
4
|
+
info: 1,
|
|
5
|
+
warn: 2,
|
|
6
|
+
error: 3,
|
|
7
|
+
};
|
|
8
|
+
function normalizeLogLevel(value) {
|
|
9
|
+
if (value in LOG_LEVELS) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
return "info";
|
|
13
|
+
}
|
|
14
|
+
function formatPrefix(level) {
|
|
15
|
+
return `[${new Date().toISOString()}] [${level.toUpperCase()}]`;
|
|
16
|
+
}
|
|
17
|
+
function formatArg(arg) {
|
|
18
|
+
if (arg instanceof Error) {
|
|
19
|
+
return arg.stack ?? `${arg.name}: ${arg.message}`;
|
|
20
|
+
}
|
|
21
|
+
return arg;
|
|
22
|
+
}
|
|
23
|
+
function withPrefix(level, args) {
|
|
24
|
+
const formattedArgs = args.map((arg) => formatArg(arg));
|
|
25
|
+
const prefix = formatPrefix(level);
|
|
26
|
+
if (formattedArgs.length === 0) {
|
|
27
|
+
return [prefix];
|
|
28
|
+
}
|
|
29
|
+
if (typeof formattedArgs[0] === "string") {
|
|
30
|
+
return [`${prefix} ${formattedArgs[0]}`, ...formattedArgs.slice(1)];
|
|
31
|
+
}
|
|
32
|
+
return [prefix, ...formattedArgs];
|
|
33
|
+
}
|
|
34
|
+
function shouldLog(level) {
|
|
35
|
+
const configLevel = normalizeLogLevel(config.server.logLevel);
|
|
36
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[configLevel];
|
|
37
|
+
}
|
|
38
|
+
export const logger = {
|
|
39
|
+
debug: (...args) => {
|
|
40
|
+
if (shouldLog("debug")) {
|
|
41
|
+
console.log(...withPrefix("debug", args));
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
info: (...args) => {
|
|
45
|
+
if (shouldLog("info")) {
|
|
46
|
+
console.log(...withPrefix("info", args));
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
warn: (...args) => {
|
|
50
|
+
if (shouldLog("warn")) {
|
|
51
|
+
console.warn(...withPrefix("warn", args));
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
error: (...args) => {
|
|
55
|
+
if (shouldLog("error")) {
|
|
56
|
+
console.error(...withPrefix("error", args));
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
@@ -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,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,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@grinev/opencode-telegram-bot",
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"opencode-telegram": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
".env.example"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"start": "node dist/index.js",
|
|
25
|
+
"dev": "npm run build && npm start",
|
|
26
|
+
"list-providers": "tsx scripts/list-providers.ts",
|
|
27
|
+
"lint": "eslint src --ext .ts --max-warnings=0",
|
|
28
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:coverage": "vitest run --coverage"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/grinev/my-telegram-opencode-bot.git"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [],
|
|
37
|
+
"author": "",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/grinev/my-telegram-opencode-bot/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/grinev/my-telegram-opencode-bot#readme",
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@grammyjs/menu": "^1.3.1",
|
|
45
|
+
"@opencode-ai/sdk": "^1.1.21",
|
|
46
|
+
"dotenv": "^17.2.3",
|
|
47
|
+
"grammy": "^1.39.2",
|
|
48
|
+
"pino": "^10.2.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^25.0.8",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
53
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
54
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
55
|
+
"eslint": "^8.57.1",
|
|
56
|
+
"eslint-config-prettier": "^10.1.8",
|
|
57
|
+
"pino-pretty": "^13.1.3",
|
|
58
|
+
"prettier": "^3.8.0",
|
|
59
|
+
"tsx": "^4.21.0",
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"vitest": "^3.2.4"
|
|
62
|
+
}
|
|
63
|
+
}
|