opencode-gateway 0.1.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.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/binding/execution.d.ts +24 -0
- package/dist/binding/execution.js +1 -0
- package/dist/binding/gateway.d.ts +71 -0
- package/dist/binding/gateway.js +1 -0
- package/dist/binding/index.d.ts +15 -0
- package/dist/binding/index.js +4 -0
- package/dist/binding/opencode.d.ts +123 -0
- package/dist/binding/opencode.js +1 -0
- package/dist/cli/args.d.ts +9 -0
- package/dist/cli/args.js +53 -0
- package/dist/cli/doctor.d.ts +6 -0
- package/dist/cli/doctor.js +59 -0
- package/dist/cli/init.d.ts +6 -0
- package/dist/cli/init.js +35 -0
- package/dist/cli/opencode-config.d.ts +10 -0
- package/dist/cli/opencode-config.js +62 -0
- package/dist/cli/paths.d.ts +7 -0
- package/dist/cli/paths.js +22 -0
- package/dist/cli/templates.d.ts +1 -0
- package/dist/cli/templates.js +26 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +314 -0
- package/dist/config/cron.d.ts +7 -0
- package/dist/config/cron.js +52 -0
- package/dist/config/gateway.d.ts +26 -0
- package/dist/config/gateway.js +142 -0
- package/dist/config/paths.d.ts +10 -0
- package/dist/config/paths.js +33 -0
- package/dist/config/telegram.d.ts +13 -0
- package/dist/config/telegram.js +91 -0
- package/dist/cron/runtime.d.ts +40 -0
- package/dist/cron/runtime.js +237 -0
- package/dist/delivery/telegram.d.ts +16 -0
- package/dist/delivery/telegram.js +75 -0
- package/dist/delivery/text.d.ts +21 -0
- package/dist/delivery/text.js +175 -0
- package/dist/gateway.d.ts +33 -0
- package/dist/gateway.js +105 -0
- package/dist/host/file-sender.d.ts +16 -0
- package/dist/host/file-sender.js +59 -0
- package/dist/host/noop.d.ts +4 -0
- package/dist/host/noop.js +14 -0
- package/dist/host/transport.d.ts +9 -0
- package/dist/host/transport.js +35 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +52 -0
- package/dist/mailbox/router.d.ts +7 -0
- package/dist/mailbox/router.js +16 -0
- package/dist/media/mime.d.ts +2 -0
- package/dist/media/mime.js +45 -0
- package/dist/opencode/adapter.d.ts +19 -0
- package/dist/opencode/adapter.js +291 -0
- package/dist/opencode/driver-hub.d.ts +15 -0
- package/dist/opencode/driver-hub.js +82 -0
- package/dist/opencode/event-normalize.d.ts +48 -0
- package/dist/opencode/event-normalize.js +48 -0
- package/dist/opencode/event-stream.d.ts +23 -0
- package/dist/opencode/event-stream.js +65 -0
- package/dist/opencode/events.d.ts +2 -0
- package/dist/opencode/events.js +1 -0
- package/dist/questions/client.d.ts +5 -0
- package/dist/questions/client.js +36 -0
- package/dist/questions/format.d.ts +3 -0
- package/dist/questions/format.js +36 -0
- package/dist/questions/normalize.d.ts +10 -0
- package/dist/questions/normalize.js +45 -0
- package/dist/questions/parser.d.ts +11 -0
- package/dist/questions/parser.js +96 -0
- package/dist/questions/runtime.d.ts +53 -0
- package/dist/questions/runtime.js +195 -0
- package/dist/questions/types.d.ts +22 -0
- package/dist/questions/types.js +1 -0
- package/dist/runtime/attachments.d.ts +3 -0
- package/dist/runtime/attachments.js +12 -0
- package/dist/runtime/executor.d.ts +24 -0
- package/dist/runtime/executor.js +188 -0
- package/dist/runtime/mailbox.d.ts +25 -0
- package/dist/runtime/mailbox.js +112 -0
- package/dist/runtime/opencode-runner.d.ts +26 -0
- package/dist/runtime/opencode-runner.js +79 -0
- package/dist/session/context.d.ts +10 -0
- package/dist/session/context.js +44 -0
- package/dist/session/conversation-key.d.ts +3 -0
- package/dist/session/conversation-key.js +3 -0
- package/dist/session/switcher.d.ts +25 -0
- package/dist/session/switcher.js +59 -0
- package/dist/store/migrations.d.ts +2 -0
- package/dist/store/migrations.js +183 -0
- package/dist/store/sqlite.d.ts +127 -0
- package/dist/store/sqlite.js +678 -0
- package/dist/telegram/client.d.ts +35 -0
- package/dist/telegram/client.js +179 -0
- package/dist/telegram/media.d.ts +13 -0
- package/dist/telegram/media.js +65 -0
- package/dist/telegram/normalize.d.ts +47 -0
- package/dist/telegram/normalize.js +119 -0
- package/dist/telegram/poller.d.ts +29 -0
- package/dist/telegram/poller.js +97 -0
- package/dist/telegram/runtime.d.ts +51 -0
- package/dist/telegram/runtime.js +133 -0
- package/dist/telegram/state.d.ts +36 -0
- package/dist/telegram/state.js +128 -0
- package/dist/telegram/types.d.ts +80 -0
- package/dist/telegram/types.js +1 -0
- package/dist/tools/channel-new-session.d.ts +4 -0
- package/dist/tools/channel-new-session.js +27 -0
- package/dist/tools/channel-send-file.d.ts +9 -0
- package/dist/tools/channel-send-file.js +27 -0
- package/dist/tools/channel-target.d.ts +7 -0
- package/dist/tools/channel-target.js +28 -0
- package/dist/tools/cron-list.d.ts +3 -0
- package/dist/tools/cron-list.js +34 -0
- package/dist/tools/cron-remove.d.ts +3 -0
- package/dist/tools/cron-remove.js +12 -0
- package/dist/tools/cron-run.d.ts +3 -0
- package/dist/tools/cron-run.js +20 -0
- package/dist/tools/cron-upsert.d.ts +3 -0
- package/dist/tools/cron-upsert.js +37 -0
- package/dist/tools/gateway-dispatch-cron.d.ts +3 -0
- package/dist/tools/gateway-dispatch-cron.js +33 -0
- package/dist/tools/gateway-status.d.ts +3 -0
- package/dist/tools/gateway-status.js +25 -0
- package/dist/tools/telegram-send-test.d.ts +3 -0
- package/dist/tools/telegram-send-test.js +26 -0
- package/dist/tools/telegram-status.d.ts +3 -0
- package/dist/tools/telegram-status.js +49 -0
- package/dist/tools/time.d.ts +3 -0
- package/dist/tools/time.js +25 -0
- package/dist/utils/error.d.ts +1 -0
- package/dist/utils/error.js +57 -0
- package/generated/wasm/pkg/opencode_gateway_ffi.d.ts +23 -0
- package/generated/wasm/pkg/opencode_gateway_ffi.js +574 -0
- package/generated/wasm/pkg/opencode_gateway_ffi_bg.wasm +0 -0
- package/generated/wasm/pkg/opencode_gateway_ffi_bg.wasm.d.ts +22 -0
- package/package.json +61 -0
package/dist/gateway.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { loadGatewayConfig } from "./config/gateway";
|
|
2
|
+
import { GatewayCronRuntime } from "./cron/runtime";
|
|
3
|
+
import { TelegramProgressiveSupport } from "./delivery/telegram";
|
|
4
|
+
import { GatewayTextDelivery } from "./delivery/text";
|
|
5
|
+
import { ChannelFileSender } from "./host/file-sender";
|
|
6
|
+
import { ConsoleLoggerHost } from "./host/noop";
|
|
7
|
+
import { GatewayTransportHost } from "./host/transport";
|
|
8
|
+
import { GatewayMailboxRouter } from "./mailbox/router";
|
|
9
|
+
import { OpencodeSdkAdapter } from "./opencode/adapter";
|
|
10
|
+
import { OpencodeEventStream } from "./opencode/event-stream";
|
|
11
|
+
import { OpencodeEventHub } from "./opencode/events";
|
|
12
|
+
import { createQuestionClient } from "./questions/client";
|
|
13
|
+
import { GatewayQuestionRuntime } from "./questions/runtime";
|
|
14
|
+
import { GatewayExecutor } from "./runtime/executor";
|
|
15
|
+
import { GatewayMailboxRuntime } from "./runtime/mailbox";
|
|
16
|
+
import { GatewaySessionContext } from "./session/context";
|
|
17
|
+
import { ChannelSessionSwitcher } from "./session/switcher";
|
|
18
|
+
import { openSqliteStore } from "./store/sqlite";
|
|
19
|
+
import { TelegramBotClient } from "./telegram/client";
|
|
20
|
+
import { TelegramInboundMediaStore } from "./telegram/media";
|
|
21
|
+
import { TelegramPollingService } from "./telegram/poller";
|
|
22
|
+
import { GatewayTelegramRuntime } from "./telegram/runtime";
|
|
23
|
+
export class GatewayPluginRuntime {
|
|
24
|
+
contract;
|
|
25
|
+
executor;
|
|
26
|
+
cron;
|
|
27
|
+
telegram;
|
|
28
|
+
files;
|
|
29
|
+
channelSessions;
|
|
30
|
+
sessionContext;
|
|
31
|
+
constructor(contract, executor, cron, telegram, files, channelSessions, sessionContext) {
|
|
32
|
+
this.contract = contract;
|
|
33
|
+
this.executor = executor;
|
|
34
|
+
this.cron = cron;
|
|
35
|
+
this.telegram = telegram;
|
|
36
|
+
this.files = files;
|
|
37
|
+
this.channelSessions = channelSessions;
|
|
38
|
+
this.sessionContext = sessionContext;
|
|
39
|
+
}
|
|
40
|
+
status() {
|
|
41
|
+
const rustStatus = this.contract.gatewayStatus();
|
|
42
|
+
return {
|
|
43
|
+
runtimeMode: rustStatus.runtimeMode,
|
|
44
|
+
supportsTelegram: rustStatus.supportsTelegram,
|
|
45
|
+
supportsCron: rustStatus.supportsCron,
|
|
46
|
+
hasWebUi: rustStatus.hasWebUi,
|
|
47
|
+
cronTimezone: this.cron.timeZone(),
|
|
48
|
+
cronEnabled: this.cron.isEnabled(),
|
|
49
|
+
cronPolling: this.cron.isRunning(),
|
|
50
|
+
cronRunningJobs: this.cron.runningJobs(),
|
|
51
|
+
telegramEnabled: this.telegram.isEnabled(),
|
|
52
|
+
telegramPolling: this.telegram.isPolling(),
|
|
53
|
+
telegramAllowlistMode: this.telegram.allowlistMode(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export async function createGatewayRuntime(module, input) {
|
|
58
|
+
const config = await loadGatewayConfig();
|
|
59
|
+
const logger = new ConsoleLoggerHost();
|
|
60
|
+
if (config.hasLegacyGatewayTimezone) {
|
|
61
|
+
const suffix = config.legacyGatewayTimezone === null ? "" : ` (${config.legacyGatewayTimezone})`;
|
|
62
|
+
logger.log("warn", `gateway.timezone${suffix} is ignored; use cron.timezone instead`);
|
|
63
|
+
}
|
|
64
|
+
const effectiveCronTimeZone = resolveEffectiveCronTimeZone(module, config);
|
|
65
|
+
const store = await openSqliteStore(config.stateDbPath);
|
|
66
|
+
const sessionContext = new GatewaySessionContext(store);
|
|
67
|
+
const telegramClient = config.telegram.enabled ? new TelegramBotClient(config.telegram.botToken) : null;
|
|
68
|
+
const telegramMediaStore = config.telegram.enabled && telegramClient !== null
|
|
69
|
+
? new TelegramInboundMediaStore(telegramClient, config.mediaRootPath)
|
|
70
|
+
: null;
|
|
71
|
+
const mailboxRouter = new GatewayMailboxRouter(config.mailbox.routes);
|
|
72
|
+
const opencodeEvents = new OpencodeEventHub();
|
|
73
|
+
const opencode = new OpencodeSdkAdapter(input.client, input.directory);
|
|
74
|
+
const questionClient = createQuestionClient(input.client, input.serverUrl, input.directory);
|
|
75
|
+
const transport = new GatewayTransportHost(telegramClient, store);
|
|
76
|
+
const files = new ChannelFileSender(telegramClient);
|
|
77
|
+
const channelSessions = new ChannelSessionSwitcher(store, sessionContext, mailboxRouter, module, opencode, config.telegram.enabled);
|
|
78
|
+
const questions = new GatewayQuestionRuntime(questionClient, input.directory, store, sessionContext, transport, telegramClient, logger);
|
|
79
|
+
const progressiveSupport = new TelegramProgressiveSupport(telegramClient, store, logger);
|
|
80
|
+
const delivery = new GatewayTextDelivery(transport, store, progressiveSupport);
|
|
81
|
+
const executor = new GatewayExecutor(module, store, opencode, opencodeEvents, delivery, logger);
|
|
82
|
+
const mailbox = new GatewayMailboxRuntime(executor, store, logger, config.mailbox, questions);
|
|
83
|
+
const cron = new GatewayCronRuntime(executor, module, store, logger, config.cron, effectiveCronTimeZone);
|
|
84
|
+
const eventStream = new OpencodeEventStream(input.client, input.directory, opencodeEvents, [questions], logger);
|
|
85
|
+
const telegramPolling = config.telegram.enabled && telegramClient !== null && telegramMediaStore !== null
|
|
86
|
+
? new TelegramPollingService(telegramClient, mailbox, store, logger, config.telegram, mailboxRouter, telegramMediaStore, questions)
|
|
87
|
+
: null;
|
|
88
|
+
const telegram = new GatewayTelegramRuntime(telegramClient, delivery, store, logger, config.telegram, telegramPolling, eventStream);
|
|
89
|
+
eventStream.start();
|
|
90
|
+
cron.start();
|
|
91
|
+
mailbox.start();
|
|
92
|
+
telegram.start();
|
|
93
|
+
return new GatewayPluginRuntime(module, executor, cron, telegram, files, channelSessions, sessionContext);
|
|
94
|
+
}
|
|
95
|
+
function resolveEffectiveCronTimeZone(module, config) {
|
|
96
|
+
const candidate = config.cron.timezone ?? resolveRuntimeLocalTimeZone();
|
|
97
|
+
return module.normalizeCronTimeZone(candidate);
|
|
98
|
+
}
|
|
99
|
+
function resolveRuntimeLocalTimeZone() {
|
|
100
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
101
|
+
if (typeof timeZone !== "string" || timeZone.trim().length === 0) {
|
|
102
|
+
throw new Error("runtime local time zone could not be determined");
|
|
103
|
+
}
|
|
104
|
+
return timeZone;
|
|
105
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BindingDeliveryTarget } from "../binding";
|
|
2
|
+
import type { TelegramFileSendClientLike } from "../telegram/client";
|
|
3
|
+
export type ChannelFileSendResult = {
|
|
4
|
+
channel: string;
|
|
5
|
+
target: string;
|
|
6
|
+
topic: string | null;
|
|
7
|
+
filePath: string;
|
|
8
|
+
mimeType: string;
|
|
9
|
+
deliveryKind: "photo" | "document";
|
|
10
|
+
};
|
|
11
|
+
export declare class ChannelFileSender {
|
|
12
|
+
private readonly telegramClient;
|
|
13
|
+
constructor(telegramClient: TelegramFileSendClientLike | null);
|
|
14
|
+
hasEnabledChannel(): boolean;
|
|
15
|
+
sendFile(target: BindingDeliveryTarget, filePath: string, caption: string | null): Promise<ChannelFileSendResult>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class ConsoleLoggerHost {
|
|
2
|
+
log(level, message) {
|
|
3
|
+
const line = `[gateway:${level}] ${message}`;
|
|
4
|
+
if (level === "error") {
|
|
5
|
+
console.error(line);
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (level === "warn") {
|
|
9
|
+
console.warn(line);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
console.info(line);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BindingHostAck, BindingOutboundMessage, BindingTransportHost } from "../binding";
|
|
2
|
+
import type { SqliteStore } from "../store/sqlite";
|
|
3
|
+
import type { TelegramSendClientLike } from "../telegram/client";
|
|
4
|
+
export declare class GatewayTransportHost implements BindingTransportHost {
|
|
5
|
+
private readonly telegramClient;
|
|
6
|
+
private readonly store;
|
|
7
|
+
constructor(telegramClient: TelegramSendClientLike | null, store: SqliteStore);
|
|
8
|
+
sendMessage(message: BindingOutboundMessage): Promise<BindingHostAck>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
/**
|
|
3
|
+
* OpenCode plugin entrypoint that loads the local wasm binding and exposes the
|
|
4
|
+
* gateway, cron, and Telegram operational tools.
|
|
5
|
+
*/
|
|
6
|
+
export declare const OpencodeGatewayPlugin: Plugin;
|
|
7
|
+
export default OpencodeGatewayPlugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { loadGatewayBindingModule } from "./binding";
|
|
2
|
+
import { createGatewayRuntime } from "./gateway";
|
|
3
|
+
import { createChannelNewSessionTool } from "./tools/channel-new-session";
|
|
4
|
+
import { createChannelSendFileTool } from "./tools/channel-send-file";
|
|
5
|
+
import { createCronListTool } from "./tools/cron-list";
|
|
6
|
+
import { createCronRemoveTool } from "./tools/cron-remove";
|
|
7
|
+
import { createCronRunTool } from "./tools/cron-run";
|
|
8
|
+
import { createCronUpsertTool } from "./tools/cron-upsert";
|
|
9
|
+
import { createGatewayDispatchCronTool } from "./tools/gateway-dispatch-cron";
|
|
10
|
+
import { createGatewayStatusTool } from "./tools/gateway-status";
|
|
11
|
+
import { createTelegramSendTestTool } from "./tools/telegram-send-test";
|
|
12
|
+
import { createTelegramStatusTool } from "./tools/telegram-status";
|
|
13
|
+
/**
|
|
14
|
+
* OpenCode plugin entrypoint that loads the local wasm binding and exposes the
|
|
15
|
+
* gateway, cron, and Telegram operational tools.
|
|
16
|
+
*/
|
|
17
|
+
export const OpencodeGatewayPlugin = async (input) => {
|
|
18
|
+
const gatewayModule = await loadGatewayBindingModule();
|
|
19
|
+
const runtime = await createGatewayRuntime(gatewayModule, input);
|
|
20
|
+
const tools = {
|
|
21
|
+
cron_list: createCronListTool(runtime.cron),
|
|
22
|
+
cron_remove: createCronRemoveTool(runtime.cron),
|
|
23
|
+
cron_run: createCronRunTool(runtime.cron),
|
|
24
|
+
cron_upsert: createCronUpsertTool(runtime.cron),
|
|
25
|
+
gateway_status: createGatewayStatusTool(runtime),
|
|
26
|
+
gateway_dispatch_cron: createGatewayDispatchCronTool(runtime.executor),
|
|
27
|
+
};
|
|
28
|
+
if (runtime.files.hasEnabledChannel()) {
|
|
29
|
+
tools.channel_send_file = createChannelSendFileTool(runtime.files, runtime.sessionContext);
|
|
30
|
+
}
|
|
31
|
+
if (runtime.channelSessions.hasEnabledChannel()) {
|
|
32
|
+
tools.channel_new_session = createChannelNewSessionTool(runtime.channelSessions, runtime.sessionContext);
|
|
33
|
+
}
|
|
34
|
+
if (runtime.telegram.isEnabled()) {
|
|
35
|
+
tools.telegram_status = createTelegramStatusTool(runtime.telegram);
|
|
36
|
+
tools.telegram_send_test = createTelegramSendTestTool(runtime.telegram);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
tool: tools,
|
|
40
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
41
|
+
const sessionId = input.sessionID;
|
|
42
|
+
if (!sessionId) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const systemPrompt = runtime.sessionContext.buildSystemPrompt(sessionId);
|
|
46
|
+
if (systemPrompt !== null) {
|
|
47
|
+
output.system.push(systemPrompt);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export default OpencodeGatewayPlugin;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BindingDeliveryTarget } from "../binding";
|
|
2
|
+
import type { GatewayMailboxRouteConfig } from "../config/gateway";
|
|
3
|
+
export declare class GatewayMailboxRouter {
|
|
4
|
+
private readonly routes;
|
|
5
|
+
constructor(routes: GatewayMailboxRouteConfig[]);
|
|
6
|
+
resolve(target: BindingDeliveryTarget): string | null;
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
import type { BindingOpencodeCommand, BindingOpencodeCommandResult } from "../binding";
|
|
3
|
+
type OpencodeClient = PluginInput["client"];
|
|
4
|
+
export declare class OpencodeSdkAdapter {
|
|
5
|
+
private readonly client;
|
|
6
|
+
private readonly directory;
|
|
7
|
+
constructor(client: OpencodeClient, directory: string);
|
|
8
|
+
createFreshSession(title: string): Promise<string>;
|
|
9
|
+
execute(command: BindingOpencodeCommand): Promise<BindingOpencodeCommandResult>;
|
|
10
|
+
private lookupSession;
|
|
11
|
+
private createSession;
|
|
12
|
+
private waitUntilIdle;
|
|
13
|
+
private appendPrompt;
|
|
14
|
+
private sendPromptAsync;
|
|
15
|
+
private awaitPromptResponse;
|
|
16
|
+
private readMessage;
|
|
17
|
+
private listMessages;
|
|
18
|
+
}
|
|
19
|
+
export {};
|