opencode-gateway 0.2.3 → 0.2.4
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/cli.js +0 -0
- package/dist/index.js +20907 -52
- package/package.json +1 -1
- package/dist/binding/execution.js +0 -1
- package/dist/binding/gateway.js +0 -1
- package/dist/binding/index.js +0 -4
- package/dist/binding/opencode.js +0 -1
- package/dist/cli/args.js +0 -53
- package/dist/cli/doctor.js +0 -49
- package/dist/cli/init.js +0 -40
- package/dist/cli/opencode-config-file.js +0 -18
- package/dist/cli/opencode-config.js +0 -194
- package/dist/cli/paths.js +0 -22
- package/dist/cli/templates.js +0 -41
- package/dist/config/cron.js +0 -52
- package/dist/config/gateway.js +0 -148
- package/dist/config/memory.js +0 -105
- package/dist/config/paths.js +0 -39
- package/dist/config/telegram.js +0 -91
- package/dist/cron/runtime.js +0 -402
- package/dist/delivery/telegram.js +0 -75
- package/dist/delivery/text.js +0 -175
- package/dist/gateway.js +0 -117
- package/dist/host/file-sender.js +0 -59
- package/dist/host/logger.js +0 -53
- package/dist/host/transport.js +0 -35
- package/dist/mailbox/router.js +0 -16
- package/dist/media/mime.js +0 -45
- package/dist/memory/prompt.js +0 -122
- package/dist/opencode/adapter.js +0 -340
- package/dist/opencode/driver-hub.js +0 -82
- package/dist/opencode/event-normalize.js +0 -48
- package/dist/opencode/event-stream.js +0 -65
- package/dist/opencode/events.js +0 -1
- package/dist/questions/client.js +0 -36
- package/dist/questions/format.js +0 -36
- package/dist/questions/normalize.js +0 -45
- package/dist/questions/parser.js +0 -96
- package/dist/questions/runtime.js +0 -195
- package/dist/questions/types.js +0 -1
- package/dist/runtime/attachments.js +0 -12
- package/dist/runtime/conversation-coordinator.js +0 -22
- package/dist/runtime/executor.js +0 -407
- package/dist/runtime/mailbox.js +0 -112
- package/dist/runtime/opencode-runner.js +0 -79
- package/dist/runtime/runtime-singleton.js +0 -28
- package/dist/session/context.js +0 -23
- package/dist/session/conversation-key.js +0 -3
- package/dist/session/switcher.js +0 -59
- package/dist/session/system-prompt.js +0 -52
- package/dist/store/migrations.js +0 -197
- package/dist/store/sqlite.js +0 -777
- package/dist/telegram/client.js +0 -180
- package/dist/telegram/media.js +0 -65
- package/dist/telegram/normalize.js +0 -119
- package/dist/telegram/poller.js +0 -166
- package/dist/telegram/runtime.js +0 -157
- package/dist/telegram/state.js +0 -149
- package/dist/telegram/types.js +0 -1
- package/dist/tools/channel-new-session.js +0 -27
- package/dist/tools/channel-send-file.js +0 -27
- package/dist/tools/channel-target.js +0 -34
- package/dist/tools/cron-run.js +0 -20
- package/dist/tools/cron-upsert.js +0 -51
- package/dist/tools/gateway-dispatch-cron.js +0 -33
- package/dist/tools/gateway-status.js +0 -25
- package/dist/tools/schedule-cancel.js +0 -12
- package/dist/tools/schedule-format.js +0 -48
- package/dist/tools/schedule-list.js +0 -17
- package/dist/tools/schedule-once.js +0 -43
- package/dist/tools/schedule-status.js +0 -23
- package/dist/tools/telegram-send-test.js +0 -26
- package/dist/tools/telegram-status.js +0 -49
- package/dist/tools/time.js +0 -25
- package/dist/utils/error.js +0 -57
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { readTelegramChatType, recordTelegramChatType, recordTelegramDraftFailure, recordTelegramDraftSuccess, recordTelegramStreamFallback, } from "../telegram/state";
|
|
2
|
-
import { formatError } from "../utils/error";
|
|
3
|
-
export class TelegramProgressiveSupport {
|
|
4
|
-
client;
|
|
5
|
-
store;
|
|
6
|
-
logger;
|
|
7
|
-
constructor(client, store, logger) {
|
|
8
|
-
this.client = client;
|
|
9
|
-
this.store = store;
|
|
10
|
-
this.logger = logger;
|
|
11
|
-
}
|
|
12
|
-
async resolveMode(target, preference) {
|
|
13
|
-
if (target.channel !== "telegram" || preference === "oneshot") {
|
|
14
|
-
return "oneshot";
|
|
15
|
-
}
|
|
16
|
-
const isPrivateChat = await this.isPrivateChat(target.target);
|
|
17
|
-
if (preference === "stream") {
|
|
18
|
-
if (!isPrivateChat) {
|
|
19
|
-
throw new Error("telegram draft stream is only supported for private chats");
|
|
20
|
-
}
|
|
21
|
-
return "progressive";
|
|
22
|
-
}
|
|
23
|
-
if (!isPrivateChat) {
|
|
24
|
-
recordTelegramStreamFallback(this.store, "non_private_chat", Date.now());
|
|
25
|
-
return "oneshot";
|
|
26
|
-
}
|
|
27
|
-
return "progressive";
|
|
28
|
-
}
|
|
29
|
-
async sendDraft(target, draftId, text) {
|
|
30
|
-
if (this.client === null) {
|
|
31
|
-
throw new Error("telegram transport is not configured");
|
|
32
|
-
}
|
|
33
|
-
try {
|
|
34
|
-
await this.client.sendMessageDraft(target.target, draftId, text, target.topic);
|
|
35
|
-
recordTelegramDraftSuccess(this.store, Date.now());
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
const message = formatError(error);
|
|
39
|
-
recordTelegramDraftFailure(this.store, message, Date.now());
|
|
40
|
-
recordTelegramStreamFallback(this.store, "draft_send_failed", Date.now());
|
|
41
|
-
this.logger.log("warn", `telegram draft send failed: ${message}`);
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
startTyping(target) {
|
|
46
|
-
if (this.client === null) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
void this.client.sendChatAction(target.target, "typing", target.topic).catch(() => {
|
|
50
|
-
// Typing hints are best-effort only.
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
async isPrivateChat(chatId) {
|
|
54
|
-
const cachedChatType = readTelegramChatType(this.store, chatId);
|
|
55
|
-
if (cachedChatType !== null) {
|
|
56
|
-
return cachedChatType === "private";
|
|
57
|
-
}
|
|
58
|
-
if (this.client === null) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
const chat = await this.client.getChat(chatId);
|
|
63
|
-
recordTelegramChatType(this.store, chatId, chat.type, Date.now());
|
|
64
|
-
return chat.type === "private";
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
this.logger.log("warn", `failed to resolve Telegram chat type for ${chatId}: ${formatError(error)}`);
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
export function createDraftId() {
|
|
73
|
-
const draftId = crypto.getRandomValues(new Uint32Array(1))[0];
|
|
74
|
-
return draftId === 0 ? 1 : draftId;
|
|
75
|
-
}
|
package/dist/delivery/text.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { recordTelegramPreviewEmit, recordTelegramStreamFallback } from "../telegram/state";
|
|
2
|
-
import { createDraftId } from "./telegram";
|
|
3
|
-
export class GatewayTextDelivery {
|
|
4
|
-
transport;
|
|
5
|
-
store;
|
|
6
|
-
telegramSupport;
|
|
7
|
-
constructor(transport, store, telegramSupport) {
|
|
8
|
-
this.transport = transport;
|
|
9
|
-
this.store = store;
|
|
10
|
-
this.telegramSupport = telegramSupport;
|
|
11
|
-
}
|
|
12
|
-
async open(target, preference) {
|
|
13
|
-
const [session] = await this.openMany([target], preference);
|
|
14
|
-
return session;
|
|
15
|
-
}
|
|
16
|
-
async openMany(targets, preference) {
|
|
17
|
-
const uniqueTargets = dedupeTargets(targets);
|
|
18
|
-
if (uniqueTargets.length === 0) {
|
|
19
|
-
return [new NoopTextDeliverySession()];
|
|
20
|
-
}
|
|
21
|
-
const sessions = await Promise.all(uniqueTargets.map(async (target) => {
|
|
22
|
-
const mode = await this.telegramSupport.resolveMode(target, preference);
|
|
23
|
-
if (mode === "progressive") {
|
|
24
|
-
const session = new ProgressiveTextDeliverySession(target, this.transport, this.telegramSupport, this.store);
|
|
25
|
-
session.start();
|
|
26
|
-
return session;
|
|
27
|
-
}
|
|
28
|
-
return new OneshotTextDeliverySession(target, this.transport);
|
|
29
|
-
}));
|
|
30
|
-
if (sessions.length === 1) {
|
|
31
|
-
return sessions;
|
|
32
|
-
}
|
|
33
|
-
return [new FanoutTextDeliverySession(sessions)];
|
|
34
|
-
}
|
|
35
|
-
async sendTest(target, text, preference) {
|
|
36
|
-
const session = await this.open(target, preference);
|
|
37
|
-
if (session.mode === "progressive") {
|
|
38
|
-
await session.preview(text.slice(0, Math.max(1, Math.ceil(text.length / 2))));
|
|
39
|
-
}
|
|
40
|
-
return {
|
|
41
|
-
delivered: await session.finish(text),
|
|
42
|
-
mode: session.mode,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
class NoopTextDeliverySession {
|
|
47
|
-
mode = "oneshot";
|
|
48
|
-
async preview(_text) { }
|
|
49
|
-
async finish(_finalText) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
class FanoutTextDeliverySession {
|
|
54
|
-
sessions;
|
|
55
|
-
mode;
|
|
56
|
-
constructor(sessions) {
|
|
57
|
-
this.sessions = sessions;
|
|
58
|
-
this.mode = sessions.some((session) => session.mode === "progressive") ? "progressive" : "oneshot";
|
|
59
|
-
}
|
|
60
|
-
async preview(text) {
|
|
61
|
-
await Promise.all(this.sessions.map((session) => session.preview(text)));
|
|
62
|
-
}
|
|
63
|
-
async finish(finalText) {
|
|
64
|
-
const results = await Promise.allSettled(this.sessions.map((session) => session.finish(finalText)));
|
|
65
|
-
const firstFailure = results.find((result) => result.status === "rejected");
|
|
66
|
-
if (firstFailure?.status === "rejected") {
|
|
67
|
-
throw firstFailure.reason;
|
|
68
|
-
}
|
|
69
|
-
return results.some((result) => result.status === "fulfilled" && result.value);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
class OneshotTextDeliverySession {
|
|
73
|
-
target;
|
|
74
|
-
transport;
|
|
75
|
-
mode = "oneshot";
|
|
76
|
-
constructor(target, transport) {
|
|
77
|
-
this.target = target;
|
|
78
|
-
this.transport = transport;
|
|
79
|
-
}
|
|
80
|
-
async preview(_text) { }
|
|
81
|
-
async finish(finalText) {
|
|
82
|
-
if (finalText === null || finalText.trim().length === 0) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
const ack = await this.transport.sendMessage({
|
|
86
|
-
deliveryTarget: this.target,
|
|
87
|
-
body: finalText,
|
|
88
|
-
});
|
|
89
|
-
if (ack.errorMessage !== null) {
|
|
90
|
-
throw new Error(ack.errorMessage);
|
|
91
|
-
}
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
class ProgressiveTextDeliverySession {
|
|
96
|
-
target;
|
|
97
|
-
transport;
|
|
98
|
-
telegramSupport;
|
|
99
|
-
store;
|
|
100
|
-
mode = "progressive";
|
|
101
|
-
previewFailed = false;
|
|
102
|
-
previewDelivered = false;
|
|
103
|
-
closed = false;
|
|
104
|
-
pendingPreviewCount = 0;
|
|
105
|
-
pendingPreview = Promise.resolve();
|
|
106
|
-
draftId = createDraftId();
|
|
107
|
-
constructor(target, transport, telegramSupport, store) {
|
|
108
|
-
this.target = target;
|
|
109
|
-
this.transport = transport;
|
|
110
|
-
this.telegramSupport = telegramSupport;
|
|
111
|
-
this.store = store;
|
|
112
|
-
}
|
|
113
|
-
start() {
|
|
114
|
-
this.telegramSupport.startTyping(this.target);
|
|
115
|
-
}
|
|
116
|
-
async preview(text) {
|
|
117
|
-
if (this.previewFailed || this.closed || text.trim().length === 0) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const runPreview = async () => {
|
|
121
|
-
try {
|
|
122
|
-
if (this.previewFailed || this.closed) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
recordTelegramPreviewEmit(this.store, Date.now());
|
|
127
|
-
await this.telegramSupport.sendDraft(this.target, this.draftId, text);
|
|
128
|
-
this.previewDelivered = true;
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
this.previewFailed = true;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
this.pendingPreviewCount = Math.max(0, this.pendingPreviewCount - 1);
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
this.pendingPreviewCount += 1;
|
|
139
|
-
this.pendingPreview = this.pendingPreview.then(runPreview, runPreview);
|
|
140
|
-
await this.pendingPreview;
|
|
141
|
-
}
|
|
142
|
-
async finish(finalText) {
|
|
143
|
-
this.closed = true;
|
|
144
|
-
if (this.pendingPreviewCount > 0) {
|
|
145
|
-
await this.pendingPreview;
|
|
146
|
-
}
|
|
147
|
-
if (finalText === null || finalText.trim().length === 0) {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
if (!this.previewDelivered && !this.previewFailed) {
|
|
151
|
-
recordTelegramStreamFallback(this.store, "preview_not_established", Date.now());
|
|
152
|
-
}
|
|
153
|
-
const ack = await this.transport.sendMessage({
|
|
154
|
-
deliveryTarget: this.target,
|
|
155
|
-
body: finalText,
|
|
156
|
-
});
|
|
157
|
-
if (ack.errorMessage !== null) {
|
|
158
|
-
throw new Error(ack.errorMessage);
|
|
159
|
-
}
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
function dedupeTargets(targets) {
|
|
164
|
-
const seen = new Set();
|
|
165
|
-
const unique = [];
|
|
166
|
-
for (const target of targets) {
|
|
167
|
-
const key = `${target.channel}:${target.target}:${target.topic ?? ""}`;
|
|
168
|
-
if (seen.has(key)) {
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
seen.add(key);
|
|
172
|
-
unique.push(target);
|
|
173
|
-
}
|
|
174
|
-
return unique;
|
|
175
|
-
}
|
package/dist/gateway.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
|
-
import { loadGatewayConfig } from "./config/gateway";
|
|
3
|
-
import { GatewayCronRuntime } from "./cron/runtime";
|
|
4
|
-
import { TelegramProgressiveSupport } from "./delivery/telegram";
|
|
5
|
-
import { GatewayTextDelivery } from "./delivery/text";
|
|
6
|
-
import { ChannelFileSender } from "./host/file-sender";
|
|
7
|
-
import { ConsoleLoggerHost } from "./host/logger";
|
|
8
|
-
import { GatewayTransportHost } from "./host/transport";
|
|
9
|
-
import { GatewayMailboxRouter } from "./mailbox/router";
|
|
10
|
-
import { GatewayMemoryPromptProvider } from "./memory/prompt";
|
|
11
|
-
import { OpencodeSdkAdapter } from "./opencode/adapter";
|
|
12
|
-
import { OpencodeEventStream } from "./opencode/event-stream";
|
|
13
|
-
import { OpencodeEventHub } from "./opencode/events";
|
|
14
|
-
import { createQuestionClient } from "./questions/client";
|
|
15
|
-
import { GatewayQuestionRuntime } from "./questions/runtime";
|
|
16
|
-
import { GatewayExecutor } from "./runtime/executor";
|
|
17
|
-
import { GatewayMailboxRuntime } from "./runtime/mailbox";
|
|
18
|
-
import { getOrCreateRuntimeSingleton } from "./runtime/runtime-singleton";
|
|
19
|
-
import { GatewaySessionContext } from "./session/context";
|
|
20
|
-
import { resolveConversationKeyForTarget } from "./session/conversation-key";
|
|
21
|
-
import { ChannelSessionSwitcher } from "./session/switcher";
|
|
22
|
-
import { GatewaySystemPromptBuilder } from "./session/system-prompt";
|
|
23
|
-
import { openSqliteStore } from "./store/sqlite";
|
|
24
|
-
import { TelegramBotClient } from "./telegram/client";
|
|
25
|
-
import { TelegramInboundMediaStore } from "./telegram/media";
|
|
26
|
-
import { TelegramPollingService } from "./telegram/poller";
|
|
27
|
-
import { GatewayTelegramRuntime } from "./telegram/runtime";
|
|
28
|
-
export class GatewayPluginRuntime {
|
|
29
|
-
contract;
|
|
30
|
-
executor;
|
|
31
|
-
cron;
|
|
32
|
-
telegram;
|
|
33
|
-
files;
|
|
34
|
-
channelSessions;
|
|
35
|
-
sessionContext;
|
|
36
|
-
systemPrompts;
|
|
37
|
-
constructor(contract, executor, cron, telegram, files, channelSessions, sessionContext, systemPrompts) {
|
|
38
|
-
this.contract = contract;
|
|
39
|
-
this.executor = executor;
|
|
40
|
-
this.cron = cron;
|
|
41
|
-
this.telegram = telegram;
|
|
42
|
-
this.files = files;
|
|
43
|
-
this.channelSessions = channelSessions;
|
|
44
|
-
this.sessionContext = sessionContext;
|
|
45
|
-
this.systemPrompts = systemPrompts;
|
|
46
|
-
}
|
|
47
|
-
status() {
|
|
48
|
-
const rustStatus = this.contract.gatewayStatus();
|
|
49
|
-
return {
|
|
50
|
-
runtimeMode: rustStatus.runtimeMode,
|
|
51
|
-
supportsTelegram: rustStatus.supportsTelegram,
|
|
52
|
-
supportsCron: rustStatus.supportsCron,
|
|
53
|
-
hasWebUi: rustStatus.hasWebUi,
|
|
54
|
-
cronTimezone: this.cron.timeZone(),
|
|
55
|
-
cronEnabled: this.cron.isEnabled(),
|
|
56
|
-
cronPolling: this.cron.isRunning(),
|
|
57
|
-
cronRunningJobs: this.cron.runningJobs(),
|
|
58
|
-
telegramEnabled: this.telegram.isEnabled(),
|
|
59
|
-
telegramPolling: this.telegram.isPolling(),
|
|
60
|
-
telegramAllowlistMode: this.telegram.allowlistMode(),
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
export async function createGatewayRuntime(module, input) {
|
|
65
|
-
const config = await loadGatewayConfig();
|
|
66
|
-
return await getOrCreateRuntimeSingleton(config.configPath, async () => {
|
|
67
|
-
await mkdir(config.workspaceDirPath, { recursive: true });
|
|
68
|
-
const logger = new ConsoleLoggerHost(config.logLevel);
|
|
69
|
-
if (config.hasLegacyGatewayTimezone) {
|
|
70
|
-
const suffix = config.legacyGatewayTimezone === null ? "" : ` (${config.legacyGatewayTimezone})`;
|
|
71
|
-
logger.log("warn", `gateway.timezone${suffix} is ignored; use cron.timezone instead`);
|
|
72
|
-
}
|
|
73
|
-
const effectiveCronTimeZone = resolveEffectiveCronTimeZone(module, config);
|
|
74
|
-
const store = await openSqliteStore(config.stateDbPath);
|
|
75
|
-
const sessionContext = new GatewaySessionContext(store);
|
|
76
|
-
const memoryPrompts = new GatewayMemoryPromptProvider(config.memory, logger);
|
|
77
|
-
const systemPrompts = new GatewaySystemPromptBuilder(sessionContext, memoryPrompts);
|
|
78
|
-
const telegramClient = config.telegram.enabled ? new TelegramBotClient(config.telegram.botToken) : null;
|
|
79
|
-
const telegramMediaStore = config.telegram.enabled && telegramClient !== null
|
|
80
|
-
? new TelegramInboundMediaStore(telegramClient, config.mediaRootPath)
|
|
81
|
-
: null;
|
|
82
|
-
const mailboxRouter = new GatewayMailboxRouter(config.mailbox.routes);
|
|
83
|
-
const opencodeEvents = new OpencodeEventHub();
|
|
84
|
-
const opencode = new OpencodeSdkAdapter(input.client, config.workspaceDirPath);
|
|
85
|
-
const questionClient = createQuestionClient(input.client, input.serverUrl, config.workspaceDirPath);
|
|
86
|
-
const transport = new GatewayTransportHost(telegramClient, store);
|
|
87
|
-
const files = new ChannelFileSender(telegramClient);
|
|
88
|
-
const channelSessions = new ChannelSessionSwitcher(store, sessionContext, mailboxRouter, module, opencode, config.telegram.enabled);
|
|
89
|
-
const questions = new GatewayQuestionRuntime(questionClient, config.workspaceDirPath, store, sessionContext, transport, telegramClient, logger);
|
|
90
|
-
const progressiveSupport = new TelegramProgressiveSupport(telegramClient, store, logger);
|
|
91
|
-
const delivery = new GatewayTextDelivery(transport, store, progressiveSupport);
|
|
92
|
-
const executor = new GatewayExecutor(module, store, opencode, opencodeEvents, delivery, logger);
|
|
93
|
-
const mailbox = new GatewayMailboxRuntime(executor, store, logger, config.mailbox, questions);
|
|
94
|
-
const cron = new GatewayCronRuntime(executor, module, store, logger, config.cron, effectiveCronTimeZone, (target) => resolveConversationKeyForTarget(target, mailboxRouter, module));
|
|
95
|
-
const eventStream = new OpencodeEventStream(input.client, config.workspaceDirPath, opencodeEvents, [questions], logger);
|
|
96
|
-
const telegramPolling = config.telegram.enabled && telegramClient !== null && telegramMediaStore !== null
|
|
97
|
-
? new TelegramPollingService(telegramClient, mailbox, store, logger, config.telegram, mailboxRouter, telegramMediaStore, questions)
|
|
98
|
-
: null;
|
|
99
|
-
const telegram = new GatewayTelegramRuntime(telegramClient, delivery, store, logger, config.telegram, telegramPolling, eventStream);
|
|
100
|
-
eventStream.start();
|
|
101
|
-
cron.start();
|
|
102
|
-
mailbox.start();
|
|
103
|
-
telegram.start();
|
|
104
|
-
return new GatewayPluginRuntime(module, executor, cron, telegram, files, channelSessions, sessionContext, systemPrompts);
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
function resolveEffectiveCronTimeZone(module, config) {
|
|
108
|
-
const candidate = config.cron.timezone ?? resolveRuntimeLocalTimeZone();
|
|
109
|
-
return module.normalizeCronTimeZone(candidate);
|
|
110
|
-
}
|
|
111
|
-
function resolveRuntimeLocalTimeZone() {
|
|
112
|
-
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
113
|
-
if (typeof timeZone !== "string" || timeZone.trim().length === 0) {
|
|
114
|
-
throw new Error("runtime local time zone could not be determined");
|
|
115
|
-
}
|
|
116
|
-
return timeZone;
|
|
117
|
-
}
|
package/dist/host/file-sender.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { stat } from "node:fs/promises";
|
|
2
|
-
import { isAbsolute } from "node:path";
|
|
3
|
-
import { inferLocalFileMimeType, isImageMimeType } from "../media/mime";
|
|
4
|
-
export class ChannelFileSender {
|
|
5
|
-
telegramClient;
|
|
6
|
-
constructor(telegramClient) {
|
|
7
|
-
this.telegramClient = telegramClient;
|
|
8
|
-
}
|
|
9
|
-
hasEnabledChannel() {
|
|
10
|
-
return this.telegramClient !== null;
|
|
11
|
-
}
|
|
12
|
-
async sendFile(target, filePath, caption) {
|
|
13
|
-
const normalizedPath = normalizeAbsoluteFilePath(filePath);
|
|
14
|
-
await assertRegularFile(normalizedPath);
|
|
15
|
-
const mimeType = await inferLocalFileMimeType(normalizedPath);
|
|
16
|
-
if (target.channel !== "telegram") {
|
|
17
|
-
throw new Error(`unsupported outbound channel: ${target.channel}`);
|
|
18
|
-
}
|
|
19
|
-
if (this.telegramClient === null) {
|
|
20
|
-
throw new Error("telegram transport is not configured");
|
|
21
|
-
}
|
|
22
|
-
if (isImageMimeType(mimeType)) {
|
|
23
|
-
await this.telegramClient.sendPhoto(target.target, normalizedPath, caption, target.topic, mimeType);
|
|
24
|
-
return {
|
|
25
|
-
channel: target.channel,
|
|
26
|
-
target: target.target,
|
|
27
|
-
topic: target.topic,
|
|
28
|
-
filePath: normalizedPath,
|
|
29
|
-
mimeType,
|
|
30
|
-
deliveryKind: "photo",
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
await this.telegramClient.sendDocument(target.target, normalizedPath, caption, target.topic, mimeType);
|
|
34
|
-
return {
|
|
35
|
-
channel: target.channel,
|
|
36
|
-
target: target.target,
|
|
37
|
-
topic: target.topic,
|
|
38
|
-
filePath: normalizedPath,
|
|
39
|
-
mimeType,
|
|
40
|
-
deliveryKind: "document",
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function normalizeAbsoluteFilePath(filePath) {
|
|
45
|
-
const trimmed = filePath.trim();
|
|
46
|
-
if (trimmed.length === 0) {
|
|
47
|
-
throw new Error("file_path must not be empty");
|
|
48
|
-
}
|
|
49
|
-
if (!isAbsolute(trimmed)) {
|
|
50
|
-
throw new Error("file_path must be an absolute path");
|
|
51
|
-
}
|
|
52
|
-
return trimmed;
|
|
53
|
-
}
|
|
54
|
-
async function assertRegularFile(filePath) {
|
|
55
|
-
const metadata = await stat(filePath);
|
|
56
|
-
if (!metadata.isFile()) {
|
|
57
|
-
throw new Error(`file_path is not a regular file: ${filePath}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
package/dist/host/logger.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
const LOG_LEVEL_PRIORITY = {
|
|
2
|
-
debug: 10,
|
|
3
|
-
info: 20,
|
|
4
|
-
warn: 30,
|
|
5
|
-
error: 40,
|
|
6
|
-
};
|
|
7
|
-
export class ConsoleLoggerHost {
|
|
8
|
-
threshold;
|
|
9
|
-
constructor(threshold) {
|
|
10
|
-
this.threshold = threshold;
|
|
11
|
-
}
|
|
12
|
-
log(level, message) {
|
|
13
|
-
if (!shouldLog(level, this.threshold)) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
const line = `[gateway:${level}] ${message}`;
|
|
17
|
-
switch (level) {
|
|
18
|
-
case "error":
|
|
19
|
-
console.error(line);
|
|
20
|
-
return;
|
|
21
|
-
case "warn":
|
|
22
|
-
console.warn(line);
|
|
23
|
-
return;
|
|
24
|
-
default:
|
|
25
|
-
console.info(line);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export function parseGatewayLogLevel(value, field) {
|
|
30
|
-
if (value === undefined) {
|
|
31
|
-
return "off";
|
|
32
|
-
}
|
|
33
|
-
if (typeof value !== "string") {
|
|
34
|
-
throw new Error(`${field} must be a string when present`);
|
|
35
|
-
}
|
|
36
|
-
const normalized = value.trim().toLowerCase();
|
|
37
|
-
if (normalized === "off") {
|
|
38
|
-
return "off";
|
|
39
|
-
}
|
|
40
|
-
if (isBindingLogLevel(normalized)) {
|
|
41
|
-
return normalized;
|
|
42
|
-
}
|
|
43
|
-
throw new Error(`${field} must be one of: off, error, warn, info, debug`);
|
|
44
|
-
}
|
|
45
|
-
function shouldLog(level, threshold) {
|
|
46
|
-
if (threshold === "off") {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[threshold];
|
|
50
|
-
}
|
|
51
|
-
function isBindingLogLevel(value) {
|
|
52
|
-
return value === "debug" || value === "info" || value === "warn" || value === "error";
|
|
53
|
-
}
|
package/dist/host/transport.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { recordTelegramSendFailure, recordTelegramSendSuccess } from "../telegram/state";
|
|
2
|
-
import { formatError } from "../utils/error";
|
|
3
|
-
export class GatewayTransportHost {
|
|
4
|
-
telegramClient;
|
|
5
|
-
store;
|
|
6
|
-
constructor(telegramClient, store) {
|
|
7
|
-
this.telegramClient = telegramClient;
|
|
8
|
-
this.store = store;
|
|
9
|
-
}
|
|
10
|
-
async sendMessage(message) {
|
|
11
|
-
try {
|
|
12
|
-
if (message.deliveryTarget.channel !== "telegram") {
|
|
13
|
-
throw new Error(`unsupported outbound channel: ${message.deliveryTarget.channel}`);
|
|
14
|
-
}
|
|
15
|
-
if (this.telegramClient === null) {
|
|
16
|
-
throw new Error("telegram transport is not configured");
|
|
17
|
-
}
|
|
18
|
-
const body = message.body.trim();
|
|
19
|
-
if (body.length === 0) {
|
|
20
|
-
throw new Error("telegram outbound message body must not be empty");
|
|
21
|
-
}
|
|
22
|
-
await this.telegramClient.sendMessage(message.deliveryTarget.target, body, message.deliveryTarget.topic);
|
|
23
|
-
recordTelegramSendSuccess(this.store, Date.now());
|
|
24
|
-
return {
|
|
25
|
-
errorMessage: null,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
recordTelegramSendFailure(this.store, formatError(error), Date.now());
|
|
30
|
-
return {
|
|
31
|
-
errorMessage: formatError(error),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
package/dist/mailbox/router.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export class GatewayMailboxRouter {
|
|
2
|
-
routes;
|
|
3
|
-
constructor(routes) {
|
|
4
|
-
this.routes = routes;
|
|
5
|
-
}
|
|
6
|
-
resolve(target) {
|
|
7
|
-
for (const route of this.routes) {
|
|
8
|
-
if (route.channel === target.channel &&
|
|
9
|
-
route.target === target.target &&
|
|
10
|
-
route.topic === (target.topic ?? null)) {
|
|
11
|
-
return route.mailboxKey;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
}
|
package/dist/media/mime.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { open } from "node:fs/promises";
|
|
2
|
-
const FALLBACK_MIME_TYPE = "application/octet-stream";
|
|
3
|
-
export async function inferLocalFileMimeType(filePath) {
|
|
4
|
-
const bunMimeType = Bun.file(filePath).type.trim();
|
|
5
|
-
if (bunMimeType.length > 0 && bunMimeType !== FALLBACK_MIME_TYPE) {
|
|
6
|
-
return bunMimeType;
|
|
7
|
-
}
|
|
8
|
-
const header = await readFileHeader(filePath, 16);
|
|
9
|
-
return inferImageMimeTypeFromHeader(header) ?? (bunMimeType.length > 0 ? bunMimeType : FALLBACK_MIME_TYPE);
|
|
10
|
-
}
|
|
11
|
-
export function isImageMimeType(mimeType) {
|
|
12
|
-
return mimeType.startsWith("image/");
|
|
13
|
-
}
|
|
14
|
-
async function readFileHeader(filePath, length) {
|
|
15
|
-
const file = await open(filePath, "r");
|
|
16
|
-
try {
|
|
17
|
-
const buffer = new Uint8Array(length);
|
|
18
|
-
const result = await file.read(buffer, 0, length, 0);
|
|
19
|
-
return buffer.subarray(0, result.bytesRead);
|
|
20
|
-
}
|
|
21
|
-
finally {
|
|
22
|
-
await file.close();
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function inferImageMimeTypeFromHeader(header) {
|
|
26
|
-
if (matchesPrefix(header, [0xff, 0xd8, 0xff])) {
|
|
27
|
-
return "image/jpeg";
|
|
28
|
-
}
|
|
29
|
-
if (matchesPrefix(header, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) {
|
|
30
|
-
return "image/png";
|
|
31
|
-
}
|
|
32
|
-
if (matchesAsciiPrefix(header, "GIF87a") || matchesAsciiPrefix(header, "GIF89a")) {
|
|
33
|
-
return "image/gif";
|
|
34
|
-
}
|
|
35
|
-
if (matchesAsciiPrefix(header, "RIFF") && matchesAsciiPrefix(header.subarray(8), "WEBP")) {
|
|
36
|
-
return "image/webp";
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
function matchesPrefix(header, prefix) {
|
|
41
|
-
return prefix.every((byte, index) => header[index] === byte);
|
|
42
|
-
}
|
|
43
|
-
function matchesAsciiPrefix(header, prefix) {
|
|
44
|
-
return prefix.split("").every((char, index) => header[index] === char.charCodeAt(0));
|
|
45
|
-
}
|