opencode-gateway 0.2.3 → 0.2.5

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 (79) hide show
  1. package/dist/cli.js +0 -0
  2. package/dist/index.js +36910 -56
  3. package/dist/runtime/delay.d.ts +1 -0
  4. package/dist/store/database.d.ts +22 -0
  5. package/dist/store/migrations.d.ts +2 -2
  6. package/dist/store/sqlite.d.ts +2 -2
  7. package/package.json +9 -3
  8. package/dist/binding/execution.js +0 -1
  9. package/dist/binding/gateway.js +0 -1
  10. package/dist/binding/index.js +0 -4
  11. package/dist/binding/opencode.js +0 -1
  12. package/dist/cli/args.js +0 -53
  13. package/dist/cli/doctor.js +0 -49
  14. package/dist/cli/init.js +0 -40
  15. package/dist/cli/opencode-config-file.js +0 -18
  16. package/dist/cli/opencode-config.js +0 -194
  17. package/dist/cli/paths.js +0 -22
  18. package/dist/cli/templates.js +0 -41
  19. package/dist/config/cron.js +0 -52
  20. package/dist/config/gateway.js +0 -148
  21. package/dist/config/memory.js +0 -105
  22. package/dist/config/paths.js +0 -39
  23. package/dist/config/telegram.js +0 -91
  24. package/dist/cron/runtime.js +0 -402
  25. package/dist/delivery/telegram.js +0 -75
  26. package/dist/delivery/text.js +0 -175
  27. package/dist/gateway.js +0 -117
  28. package/dist/host/file-sender.js +0 -59
  29. package/dist/host/logger.js +0 -53
  30. package/dist/host/transport.js +0 -35
  31. package/dist/mailbox/router.js +0 -16
  32. package/dist/media/mime.js +0 -45
  33. package/dist/memory/prompt.js +0 -122
  34. package/dist/opencode/adapter.js +0 -340
  35. package/dist/opencode/driver-hub.js +0 -82
  36. package/dist/opencode/event-normalize.js +0 -48
  37. package/dist/opencode/event-stream.js +0 -65
  38. package/dist/opencode/events.js +0 -1
  39. package/dist/questions/client.js +0 -36
  40. package/dist/questions/format.js +0 -36
  41. package/dist/questions/normalize.js +0 -45
  42. package/dist/questions/parser.js +0 -96
  43. package/dist/questions/runtime.js +0 -195
  44. package/dist/questions/types.js +0 -1
  45. package/dist/runtime/attachments.js +0 -12
  46. package/dist/runtime/conversation-coordinator.js +0 -22
  47. package/dist/runtime/executor.js +0 -407
  48. package/dist/runtime/mailbox.js +0 -112
  49. package/dist/runtime/opencode-runner.js +0 -79
  50. package/dist/runtime/runtime-singleton.js +0 -28
  51. package/dist/session/context.js +0 -23
  52. package/dist/session/conversation-key.js +0 -3
  53. package/dist/session/switcher.js +0 -59
  54. package/dist/session/system-prompt.js +0 -52
  55. package/dist/store/migrations.js +0 -197
  56. package/dist/store/sqlite.js +0 -777
  57. package/dist/telegram/client.js +0 -180
  58. package/dist/telegram/media.js +0 -65
  59. package/dist/telegram/normalize.js +0 -119
  60. package/dist/telegram/poller.js +0 -166
  61. package/dist/telegram/runtime.js +0 -157
  62. package/dist/telegram/state.js +0 -149
  63. package/dist/telegram/types.js +0 -1
  64. package/dist/tools/channel-new-session.js +0 -27
  65. package/dist/tools/channel-send-file.js +0 -27
  66. package/dist/tools/channel-target.js +0 -34
  67. package/dist/tools/cron-run.js +0 -20
  68. package/dist/tools/cron-upsert.js +0 -51
  69. package/dist/tools/gateway-dispatch-cron.js +0 -33
  70. package/dist/tools/gateway-status.js +0 -25
  71. package/dist/tools/schedule-cancel.js +0 -12
  72. package/dist/tools/schedule-format.js +0 -48
  73. package/dist/tools/schedule-list.js +0 -17
  74. package/dist/tools/schedule-once.js +0 -43
  75. package/dist/tools/schedule-status.js +0 -23
  76. package/dist/tools/telegram-send-test.js +0 -26
  77. package/dist/tools/telegram-status.js +0 -49
  78. package/dist/tools/time.js +0 -25
  79. package/dist/utils/error.js +0 -57
@@ -1,148 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { dirname, isAbsolute, join, resolve } from "node:path";
3
- import { parseGatewayLogLevel } from "../host/logger";
4
- import { parseCronConfig } from "./cron";
5
- import { parseMemoryConfig } from "./memory";
6
- import { defaultGatewayStateDbPath, resolveGatewayConfigPath, resolveGatewayWorkspacePath } from "./paths";
7
- import { parseTelegramConfig } from "./telegram";
8
- export async function loadGatewayConfig(env = process.env) {
9
- const configPath = resolveGatewayConfigPath(env);
10
- const workspaceDirPath = resolveGatewayWorkspacePath(configPath);
11
- const rawConfig = await readGatewayConfigFile(configPath);
12
- const stateDbValue = rawConfig?.gateway?.state_db;
13
- if (stateDbValue !== undefined && typeof stateDbValue !== "string") {
14
- throw new Error("gateway.state_db must be a string when present");
15
- }
16
- const stateDbPath = resolveStateDbPath(stateDbValue, configPath, env);
17
- return {
18
- configPath,
19
- stateDbPath,
20
- mediaRootPath: resolveMediaRootPath(stateDbPath),
21
- workspaceDirPath,
22
- logLevel: parseGatewayLogLevel(rawConfig?.gateway?.log_level, "gateway.log_level"),
23
- hasLegacyGatewayTimezone: rawConfig?.gateway?.timezone !== undefined,
24
- legacyGatewayTimezone: readLegacyGatewayTimezone(rawConfig?.gateway?.timezone),
25
- mailbox: parseMailboxConfig(rawConfig?.gateway?.mailbox),
26
- memory: await parseMemoryConfig(rawConfig?.memory, workspaceDirPath),
27
- cron: parseCronConfig(rawConfig?.cron),
28
- telegram: parseTelegramConfig(rawConfig?.channels?.telegram, env),
29
- };
30
- }
31
- function parseMailboxConfig(value) {
32
- const table = readMailboxTable(value);
33
- return {
34
- batchReplies: readBoolean(table.batch_replies, "gateway.mailbox.batch_replies", false),
35
- batchWindowMs: readBatchWindowMs(table.batch_window_ms),
36
- routes: readMailboxRoutes(table.routes),
37
- };
38
- }
39
- async function readGatewayConfigFile(path) {
40
- if (!existsSync(path)) {
41
- return null;
42
- }
43
- const source = await Bun.file(path).text();
44
- const parsed = Bun.TOML.parse(source);
45
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
46
- throw new Error(`gateway config must decode to a table: ${path}`);
47
- }
48
- return parsed;
49
- }
50
- function resolveStateDbPath(stateDb, configPath, env) {
51
- if (!stateDb || stateDb.trim().length === 0) {
52
- return defaultStateDbPath(env);
53
- }
54
- if (isAbsolute(stateDb)) {
55
- return stateDb;
56
- }
57
- return resolve(dirname(configPath), stateDb);
58
- }
59
- function readMailboxTable(value) {
60
- if (value === undefined) {
61
- return {};
62
- }
63
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
64
- throw new Error("gateway.mailbox must be a table when present");
65
- }
66
- return value;
67
- }
68
- function readBoolean(value, field, fallback) {
69
- if (value === undefined) {
70
- return fallback;
71
- }
72
- if (typeof value !== "boolean") {
73
- throw new Error(`${field} must be a boolean when present`);
74
- }
75
- return value;
76
- }
77
- function readBatchWindowMs(value) {
78
- if (value === undefined) {
79
- return 1_500;
80
- }
81
- if (typeof value !== "number" || !Number.isInteger(value)) {
82
- throw new Error("gateway.mailbox.batch_window_ms must be an integer when present");
83
- }
84
- if (value < 0 || value > 60_000) {
85
- throw new Error("gateway.mailbox.batch_window_ms must be between 0 and 60000");
86
- }
87
- return value;
88
- }
89
- function readMailboxRoutes(value) {
90
- if (value === undefined) {
91
- return [];
92
- }
93
- if (!Array.isArray(value)) {
94
- throw new Error("gateway.mailbox.routes must be an array when present");
95
- }
96
- const routes = value.map((entry, index) => readMailboxRoute(entry, index));
97
- const seen = new Set();
98
- for (const route of routes) {
99
- const key = `${route.channel}:${route.target}:${route.topic ?? ""}`;
100
- if (seen.has(key)) {
101
- throw new Error(`gateway.mailbox.routes contains a duplicate match for ${key}`);
102
- }
103
- seen.add(key);
104
- }
105
- return routes;
106
- }
107
- function readMailboxRoute(value, index) {
108
- const field = `gateway.mailbox.routes[${index}]`;
109
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
110
- throw new Error(`${field} must be a table`);
111
- }
112
- const route = value;
113
- return {
114
- channel: readRequiredString(route.channel, `${field}.channel`),
115
- target: readRequiredString(route.target, `${field}.target`),
116
- topic: readOptionalString(route.topic, `${field}.topic`),
117
- mailboxKey: readRequiredString(route.mailbox_key, `${field}.mailbox_key`),
118
- };
119
- }
120
- function readRequiredString(value, field) {
121
- if (typeof value !== "string") {
122
- throw new Error(`${field} must be a string`);
123
- }
124
- const trimmed = value.trim();
125
- if (trimmed.length === 0) {
126
- throw new Error(`${field} must not be empty`);
127
- }
128
- return trimmed;
129
- }
130
- function readOptionalString(value, field) {
131
- if (value === undefined) {
132
- return null;
133
- }
134
- return readRequiredString(value, field);
135
- }
136
- function readLegacyGatewayTimezone(value) {
137
- if (typeof value !== "string") {
138
- return null;
139
- }
140
- const trimmed = value.trim();
141
- return trimmed.length === 0 ? null : trimmed;
142
- }
143
- function defaultStateDbPath(env) {
144
- return defaultGatewayStateDbPath(env);
145
- }
146
- function resolveMediaRootPath(stateDbPath) {
147
- return join(dirname(stateDbPath), "media");
148
- }
@@ -1,105 +0,0 @@
1
- import { stat } from "node:fs/promises";
2
- import { resolve } from "node:path";
3
- export async function parseMemoryConfig(value, workspaceDirPath) {
4
- const table = readMemoryTable(value);
5
- const entries = await readMemoryEntries(table.entries, workspaceDirPath);
6
- return { entries };
7
- }
8
- function readMemoryTable(value) {
9
- if (value === undefined) {
10
- return {};
11
- }
12
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
13
- throw new Error("memory must be a table when present");
14
- }
15
- return value;
16
- }
17
- async function readMemoryEntries(value, workspaceDirPath) {
18
- if (value === undefined) {
19
- return [];
20
- }
21
- if (!Array.isArray(value)) {
22
- throw new Error("memory.entries must be an array when present");
23
- }
24
- return await Promise.all(value.map((entry, index) => readMemoryEntry(entry, index, workspaceDirPath)));
25
- }
26
- async function readMemoryEntry(value, index, workspaceDirPath) {
27
- const field = `memory.entries[${index}]`;
28
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
29
- throw new Error(`${field} must be a table`);
30
- }
31
- const entry = value;
32
- const displayPath = readRequiredString(entry.path, `${field}.path`);
33
- const description = readRequiredString(entry.description, `${field}.description`);
34
- const resolvedPath = resolve(workspaceDirPath, displayPath);
35
- const metadata = await statPath(resolvedPath, `${field}.path`);
36
- if (metadata.isFile()) {
37
- ensureDirectoryOnlyFieldIsAbsent(entry.inject_markdown_contents, `${field}.inject_markdown_contents`);
38
- ensureDirectoryOnlyFieldIsAbsent(entry.globs, `${field}.globs`);
39
- return {
40
- kind: "file",
41
- path: resolvedPath,
42
- displayPath,
43
- description,
44
- injectContent: readBoolean(entry.inject_content, `${field}.inject_content`, false),
45
- };
46
- }
47
- if (metadata.isDirectory()) {
48
- ensureFileOnlyFieldIsAbsent(entry.inject_content, `${field}.inject_content`);
49
- return {
50
- kind: "directory",
51
- path: resolvedPath,
52
- displayPath,
53
- description,
54
- injectMarkdownContents: readBoolean(entry.inject_markdown_contents, `${field}.inject_markdown_contents`, false),
55
- globs: readGlobList(entry.globs, `${field}.globs`),
56
- };
57
- }
58
- throw new Error(`${field}.path must point to a regular file or directory`);
59
- }
60
- async function statPath(path, field) {
61
- try {
62
- return await stat(path);
63
- }
64
- catch (error) {
65
- throw new Error(`${field} does not exist: ${path}`, { cause: error });
66
- }
67
- }
68
- function ensureDirectoryOnlyFieldIsAbsent(value, field) {
69
- if (value !== undefined) {
70
- throw new Error(`${field} is only valid for directory entries`);
71
- }
72
- }
73
- function ensureFileOnlyFieldIsAbsent(value, field) {
74
- if (value !== undefined) {
75
- throw new Error(`${field} is only valid for file entries`);
76
- }
77
- }
78
- function readBoolean(value, field, fallback) {
79
- if (value === undefined) {
80
- return fallback;
81
- }
82
- if (typeof value !== "boolean") {
83
- throw new Error(`${field} must be a boolean when present`);
84
- }
85
- return value;
86
- }
87
- function readGlobList(value, field) {
88
- if (value === undefined) {
89
- return [];
90
- }
91
- if (!Array.isArray(value)) {
92
- throw new Error(`${field} must be an array when present`);
93
- }
94
- return value.map((entry, index) => readRequiredString(entry, `${field}[${index}]`));
95
- }
96
- function readRequiredString(value, field) {
97
- if (typeof value !== "string") {
98
- throw new Error(`${field} must be a string`);
99
- }
100
- const trimmed = value.trim();
101
- if (trimmed.length === 0) {
102
- throw new Error(`${field} must not be empty`);
103
- }
104
- return trimmed;
105
- }
@@ -1,39 +0,0 @@
1
- import { homedir } from "node:os";
2
- import { dirname, join, resolve } from "node:path";
3
- export const GATEWAY_CONFIG_FILE = "opencode-gateway.toml";
4
- export const OPENCODE_CONFIG_FILE = "opencode.json";
5
- export const OPENCODE_CONFIG_FILE_JSONC = "opencode.jsonc";
6
- export const OPENCODE_CONFIG_FILE_CANDIDATES = [OPENCODE_CONFIG_FILE_JSONC, OPENCODE_CONFIG_FILE];
7
- export const GATEWAY_WORKSPACE_DIR = "opencode-gateway-workspace";
8
- export function resolveGatewayConfigPath(env) {
9
- const explicit = env.OPENCODE_GATEWAY_CONFIG;
10
- if (explicit && explicit.trim().length > 0) {
11
- return resolve(explicit);
12
- }
13
- return join(resolveOpencodeConfigDir(env), GATEWAY_CONFIG_FILE);
14
- }
15
- export function resolveOpencodeConfigDir(env) {
16
- const explicit = env.OPENCODE_CONFIG_DIR;
17
- if (explicit && explicit.trim().length > 0) {
18
- return resolve(explicit);
19
- }
20
- return defaultOpencodeConfigDir(env);
21
- }
22
- export function resolveManagedOpencodeConfigDir(env) {
23
- return join(resolveConfigHome(env), "opencode-gateway", "opencode");
24
- }
25
- export function resolveGatewayWorkspacePath(configPath) {
26
- return join(dirname(configPath), GATEWAY_WORKSPACE_DIR);
27
- }
28
- export function defaultGatewayStateDbPath(env) {
29
- return join(resolveDataHome(env), "opencode-gateway", "state.db");
30
- }
31
- export function resolveConfigHome(env) {
32
- return env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
33
- }
34
- export function resolveDataHome(env) {
35
- return env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
36
- }
37
- function defaultOpencodeConfigDir(env) {
38
- return join(resolveConfigHome(env), "opencode");
39
- }
@@ -1,91 +0,0 @@
1
- export function parseTelegramConfig(value, env) {
2
- const table = readTelegramTable(value);
3
- const enabled = readBoolean(table.enabled, "channels.telegram.enabled", false);
4
- if (!enabled) {
5
- return { enabled: false };
6
- }
7
- const botTokenEnv = readString(table.bot_token_env, "channels.telegram.bot_token_env", "TELEGRAM_BOT_TOKEN");
8
- const pollTimeoutSeconds = readPollTimeoutSeconds(table.poll_timeout_seconds);
9
- const allowedChats = readIdentifierList(table.allowed_chats, "channels.telegram.allowed_chats");
10
- const allowedUsers = readIdentifierList(table.allowed_users, "channels.telegram.allowed_users");
11
- const botToken = env[botTokenEnv]?.trim();
12
- if (!botToken) {
13
- throw new Error(`Telegram is enabled but ${botTokenEnv} is not set`);
14
- }
15
- if (allowedChats.length === 0 && allowedUsers.length === 0) {
16
- throw new Error("Telegram is enabled but no allowlist entries were configured");
17
- }
18
- return {
19
- enabled: true,
20
- botToken,
21
- botTokenEnv,
22
- pollTimeoutSeconds,
23
- allowedChats,
24
- allowedUsers,
25
- };
26
- }
27
- function readTelegramTable(value) {
28
- if (value === undefined) {
29
- return {};
30
- }
31
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
32
- throw new Error("channels.telegram must be a table when present");
33
- }
34
- return value;
35
- }
36
- function readBoolean(value, field, fallback) {
37
- if (value === undefined) {
38
- return fallback;
39
- }
40
- if (typeof value !== "boolean") {
41
- throw new Error(`${field} must be a boolean when present`);
42
- }
43
- return value;
44
- }
45
- function readString(value, field, fallback) {
46
- if (value === undefined) {
47
- return fallback;
48
- }
49
- if (typeof value !== "string") {
50
- throw new Error(`${field} must be a string when present`);
51
- }
52
- const trimmed = value.trim();
53
- if (trimmed.length === 0) {
54
- throw new Error(`${field} must not be empty`);
55
- }
56
- return trimmed;
57
- }
58
- function readPollTimeoutSeconds(value) {
59
- if (value === undefined) {
60
- return 25;
61
- }
62
- if (typeof value !== "number" || !Number.isInteger(value)) {
63
- throw new Error("channels.telegram.poll_timeout_seconds must be an integer when present");
64
- }
65
- if (value < 1 || value > 50) {
66
- throw new Error("channels.telegram.poll_timeout_seconds must be between 1 and 50");
67
- }
68
- return value;
69
- }
70
- function readIdentifierList(value, field) {
71
- if (value === undefined) {
72
- return [];
73
- }
74
- if (!Array.isArray(value)) {
75
- throw new Error(`${field} must be an array when present`);
76
- }
77
- return value.map((entry) => normalizeIdentifier(entry, field));
78
- }
79
- function normalizeIdentifier(value, field) {
80
- if (typeof value === "string") {
81
- const trimmed = value.trim();
82
- if (trimmed.length === 0) {
83
- throw new Error(`${field} entries must not be empty`);
84
- }
85
- return trimmed;
86
- }
87
- if (typeof value === "number" && Number.isSafeInteger(value)) {
88
- return String(value);
89
- }
90
- throw new Error(`${field} entries must be strings or safe integers`);
91
- }