opencode-router 0.11.87 → 0.11.89
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/README.md +6 -1
- package/dist/bridge.js +45 -13
- package/dist/telegram.js +20 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,13 +50,18 @@ opencode-router
|
|
|
50
50
|
|
|
51
51
|
Telegram support is configured via identities. You can either:
|
|
52
52
|
- Use env vars for a single bot: `TELEGRAM_BOT_TOKEN=...`
|
|
53
|
-
|
|
53
|
+
- Or add multiple bots to the config file (`opencode-router.json`) using the CLI:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
opencode-router telegram add <token> --id default
|
|
57
57
|
opencode-router telegram list
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
Important for direct sends and bindings:
|
|
61
|
+
- Telegram targets must use numeric `chat_id` values.
|
|
62
|
+
- `@username` values are not valid direct `peerId` targets for router sends.
|
|
63
|
+
- If a user has not started a chat with the bot yet, Telegram may return `chat not found`.
|
|
64
|
+
|
|
60
65
|
## Slack (Socket Mode)
|
|
61
66
|
|
|
62
67
|
Slack support uses Socket Mode and replies in threads when @mentioned in channels.
|
package/dist/bridge.js
CHANGED
|
@@ -8,7 +8,7 @@ import { startHealthServer } from "./health.js";
|
|
|
8
8
|
import { buildPermissionRules, createClient } from "./opencode.js";
|
|
9
9
|
import { chunkText, formatInputSummary, truncateText } from "./text.js";
|
|
10
10
|
import { createSlackAdapter } from "./slack.js";
|
|
11
|
-
import { createTelegramAdapter } from "./telegram.js";
|
|
11
|
+
import { createTelegramAdapter, isTelegramPeerId } from "./telegram.js";
|
|
12
12
|
async function startAdapterBounded(adapter, options) {
|
|
13
13
|
const outcome = adapter
|
|
14
14
|
.start()
|
|
@@ -50,6 +50,15 @@ const CHANNEL_LABELS = {
|
|
|
50
50
|
const TYPING_INTERVAL_MS = 6000;
|
|
51
51
|
const OPENCODE_ROUTER_AGENT_FILE_RELATIVE_PATH = ".opencode/agents/opencode-router.md";
|
|
52
52
|
const OPENCODE_ROUTER_AGENT_MAX_CHARS = 16_000;
|
|
53
|
+
const DEFAULT_MESSAGING_AGENT_INSTRUCTIONS = [
|
|
54
|
+
"Respond for non-technical users first.",
|
|
55
|
+
"Do not tell users to run router commands; use tools on their behalf.",
|
|
56
|
+
"Never expose raw peer IDs or Telegram chat IDs unless the user explicitly asks for debug details.",
|
|
57
|
+
"Do not ask end users for peer IDs or identity IDs.",
|
|
58
|
+
"For Telegram send requests, try delivery immediately using existing bindings or direct tool calls.",
|
|
59
|
+
"If Telegram returns 'chat not found', explain that the recipient must message the bot first (for example with /start), then ask the user to retry.",
|
|
60
|
+
"Keep status updates concise and action-oriented.",
|
|
61
|
+
].join("\n");
|
|
53
62
|
// Model presets for quick switching
|
|
54
63
|
const MODEL_PRESETS = {
|
|
55
64
|
opus: { providerID: "anthropic", modelID: "claude-opus-4-5-20251101" },
|
|
@@ -76,6 +85,11 @@ function setUserModel(channel, identityId, peerId, model) {
|
|
|
76
85
|
function adapterKey(channel, identityId) {
|
|
77
86
|
return `${channel}:${identityId}`;
|
|
78
87
|
}
|
|
88
|
+
function invalidTelegramPeerIdError() {
|
|
89
|
+
const error = new Error("Telegram requires a numeric chat_id for direct targets. Usernames like @name cannot be used as peerId.");
|
|
90
|
+
error.status = 400;
|
|
91
|
+
return error;
|
|
92
|
+
}
|
|
79
93
|
function normalizeIdentityId(value) {
|
|
80
94
|
const trimmed = (value ?? "").trim();
|
|
81
95
|
if (!trimmed)
|
|
@@ -792,6 +806,9 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
792
806
|
if (!peerKey || !directory) {
|
|
793
807
|
throw new Error("peerId and directory are required");
|
|
794
808
|
}
|
|
809
|
+
if (channel === "telegram" && !isTelegramPeerId(peerKey)) {
|
|
810
|
+
throw invalidTelegramPeerIdError();
|
|
811
|
+
}
|
|
795
812
|
const scoped = resolveScopedDirectory(directory);
|
|
796
813
|
if (!scoped.ok) {
|
|
797
814
|
const error = new Error(scoped.error);
|
|
@@ -833,6 +850,9 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
833
850
|
if (!directoryInput && !peerId) {
|
|
834
851
|
throw new Error("directory or peerId is required");
|
|
835
852
|
}
|
|
853
|
+
if (channel === "telegram" && peerId && !isTelegramPeerId(peerId)) {
|
|
854
|
+
throw invalidTelegramPeerIdError();
|
|
855
|
+
}
|
|
836
856
|
const normalizedDir = directoryInput ? (() => {
|
|
837
857
|
const scoped = resolveScopedDirectory(directoryInput);
|
|
838
858
|
if (!scoped.ok) {
|
|
@@ -935,6 +955,16 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
935
955
|
let sent = 0;
|
|
936
956
|
for (const binding of bindings) {
|
|
937
957
|
attempted += 1;
|
|
958
|
+
if (channel === "telegram" && !isTelegramPeerId(binding.peer_id)) {
|
|
959
|
+
store.deleteBinding(channel, binding.identity_id, binding.peer_id);
|
|
960
|
+
store.deleteSession(channel, binding.identity_id, binding.peer_id);
|
|
961
|
+
failures.push({
|
|
962
|
+
identityId: binding.identity_id,
|
|
963
|
+
peerId: binding.peer_id,
|
|
964
|
+
error: "Invalid Telegram peerId binding removed (expected numeric chat_id)",
|
|
965
|
+
});
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
938
968
|
const adapter = adapters.get(adapterKey(channel, binding.identity_id));
|
|
939
969
|
if (!adapter) {
|
|
940
970
|
failures.push({
|
|
@@ -1189,18 +1219,20 @@ export async function startBridge(config, logger, reporter, deps = {}) {
|
|
|
1189
1219
|
try {
|
|
1190
1220
|
const effectiveModel = getUserModel(inbound.channel, inbound.identityId, peerKey, config.model);
|
|
1191
1221
|
const messagingAgent = await loadMessagingAgentConfig();
|
|
1192
|
-
const
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
:
|
|
1222
|
+
const effectiveInstructions = [messagingAgent.instructions, DEFAULT_MESSAGING_AGENT_INSTRUCTIONS]
|
|
1223
|
+
.map((value) => value.trim())
|
|
1224
|
+
.filter(Boolean)
|
|
1225
|
+
.join("\n\n");
|
|
1226
|
+
const promptText = [
|
|
1227
|
+
"You are handling a Slack/Telegram message via OpenWork.",
|
|
1228
|
+
`Workspace agent file: ${messagingAgent.filePath}`,
|
|
1229
|
+
...(messagingAgent.selectedAgent ? [`Selected OpenCode agent: ${messagingAgent.selectedAgent}`] : []),
|
|
1230
|
+
"Follow these workspace messaging instructions:",
|
|
1231
|
+
effectiveInstructions,
|
|
1232
|
+
"",
|
|
1233
|
+
"Incoming user message:",
|
|
1234
|
+
inbound.text,
|
|
1235
|
+
].join("\n");
|
|
1204
1236
|
logger.debug({
|
|
1205
1237
|
sessionID,
|
|
1206
1238
|
length: inbound.text.length,
|
package/dist/telegram.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { Bot } from "grammy";
|
|
2
2
|
const MAX_TEXT_LENGTH = 4096;
|
|
3
|
+
const TELEGRAM_CHAT_ID_PATTERN = /^-?\d+$/;
|
|
4
|
+
export function isTelegramPeerId(peerId) {
|
|
5
|
+
return TELEGRAM_CHAT_ID_PATTERN.test(peerId.trim());
|
|
6
|
+
}
|
|
7
|
+
export function parseTelegramPeerId(peerId) {
|
|
8
|
+
const trimmed = peerId.trim();
|
|
9
|
+
if (!isTelegramPeerId(trimmed))
|
|
10
|
+
return null;
|
|
11
|
+
const parsed = Number(trimmed);
|
|
12
|
+
if (!Number.isFinite(parsed))
|
|
13
|
+
return null;
|
|
14
|
+
return parsed;
|
|
15
|
+
}
|
|
3
16
|
export function createTelegramAdapter(identity, config, logger, onMessage) {
|
|
4
17
|
const token = identity.token?.trim() ?? "";
|
|
5
18
|
if (!token) {
|
|
@@ -72,7 +85,13 @@ export function createTelegramAdapter(identity, config, logger, onMessage) {
|
|
|
72
85
|
log.info("telegram adapter stopped");
|
|
73
86
|
},
|
|
74
87
|
async sendText(peerId, text) {
|
|
75
|
-
|
|
88
|
+
const chatId = parseTelegramPeerId(peerId);
|
|
89
|
+
if (chatId === null) {
|
|
90
|
+
const error = new Error("Telegram peerId must be a numeric chat_id. Usernames like @name are not valid direct targets.");
|
|
91
|
+
error.status = 400;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
await bot.api.sendMessage(chatId, text);
|
|
76
95
|
},
|
|
77
96
|
};
|
|
78
97
|
}
|