opencode-gateway 0.2.2 → 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.
Files changed (79) hide show
  1. package/dist/cli.js +0 -0
  2. package/dist/index.js +20907 -52
  3. package/dist/telegram/client.d.ts +1 -1
  4. package/dist/telegram/poller.d.ts +16 -1
  5. package/dist/telegram/runtime.d.ts +3 -1
  6. package/dist/telegram/state.d.ts +7 -0
  7. package/package.json +1 -1
  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 -179
  58. package/dist/telegram/media.js +0 -65
  59. package/dist/telegram/normalize.js +0 -119
  60. package/dist/telegram/poller.js +0 -97
  61. package/dist/telegram/runtime.js +0 -133
  62. package/dist/telegram/state.js +0 -128
  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,96 +0,0 @@
1
- const CANCEL_WORDS = new Set(["/cancel", "cancel", "/reject", "reject"]);
2
- export function parseQuestionReply(request, text) {
3
- if (text === null) {
4
- return {
5
- kind: "invalid",
6
- message: "This question currently accepts text replies only.",
7
- };
8
- }
9
- const trimmed = text.trim();
10
- if (trimmed.length === 0) {
11
- return {
12
- kind: "invalid",
13
- message: "Reply text must not be empty.",
14
- };
15
- }
16
- if (CANCEL_WORDS.has(trimmed.toLowerCase())) {
17
- return {
18
- kind: "reject",
19
- };
20
- }
21
- if (request.questions.length === 1) {
22
- const parsedAnswer = parseQuestionLine(request.questions[0], trimmed);
23
- return parsedAnswer.kind === "invalid"
24
- ? parsedAnswer
25
- : {
26
- kind: "reply",
27
- answers: [parsedAnswer.answer],
28
- };
29
- }
30
- const lines = trimmed
31
- .split(/\r?\n/u)
32
- .map((line) => line.trim())
33
- .filter((line) => line.length > 0);
34
- if (lines.length !== request.questions.length) {
35
- return {
36
- kind: "invalid",
37
- message: `Expected ${request.questions.length} non-empty lines, but received ${lines.length}.`,
38
- };
39
- }
40
- const answers = [];
41
- for (const [index, question] of request.questions.entries()) {
42
- const parsedAnswer = parseQuestionLine(question, lines[index]);
43
- if (parsedAnswer.kind === "invalid") {
44
- return parsedAnswer;
45
- }
46
- answers.push(parsedAnswer.answer);
47
- }
48
- return {
49
- kind: "reply",
50
- answers,
51
- };
52
- }
53
- function parseQuestionLine(question, line) {
54
- const rawSelections = question.multiple
55
- ? line
56
- .split(/[,\n]/u)
57
- .map((token) => token.trim())
58
- .filter((token) => token.length > 0)
59
- : [line];
60
- if (rawSelections.length === 0) {
61
- return {
62
- kind: "invalid",
63
- message: "At least one answer is required.",
64
- };
65
- }
66
- const answers = [];
67
- for (const rawSelection of rawSelections) {
68
- const option = resolveOptionSelection(question, rawSelection);
69
- if (option !== null) {
70
- answers.push(option);
71
- continue;
72
- }
73
- if (!question.custom) {
74
- return {
75
- kind: "invalid",
76
- message: `Answer "${rawSelection}" does not match any allowed option.`,
77
- };
78
- }
79
- answers.push(rawSelection);
80
- }
81
- return {
82
- kind: "answer",
83
- answer: answers,
84
- };
85
- }
86
- function resolveOptionSelection(question, selection) {
87
- if (question.options.length === 0) {
88
- return null;
89
- }
90
- const numericIndex = Number.parseInt(selection, 10);
91
- if (Number.isSafeInteger(numericIndex) && String(numericIndex) === selection && numericIndex >= 1) {
92
- return question.options[numericIndex - 1]?.label ?? null;
93
- }
94
- const normalized = selection.toLowerCase();
95
- return question.options.find((option) => option.label.toLowerCase() === normalized)?.label ?? null;
96
- }
@@ -1,195 +0,0 @@
1
- import { recordTelegramSendFailure, recordTelegramSendSuccess } from "../telegram/state";
2
- import { formatError } from "../utils/error";
3
- import { formatPlainTextQuestion, formatQuestionReplyError } from "./format";
4
- import { normalizeQuestionEvent } from "./normalize";
5
- import { parseQuestionReply } from "./parser";
6
- export class GatewayQuestionRuntime {
7
- client;
8
- directory;
9
- store;
10
- sessions;
11
- transport;
12
- telegramClient;
13
- logger;
14
- constructor(client, directory, store, sessions, transport, telegramClient, logger) {
15
- this.client = client;
16
- this.directory = directory;
17
- this.store = store;
18
- this.sessions = sessions;
19
- this.transport = transport;
20
- this.telegramClient = telegramClient;
21
- this.logger = logger;
22
- }
23
- handleEvent(event) {
24
- const normalized = normalizeQuestionEvent(event);
25
- if (normalized === null) {
26
- return;
27
- }
28
- void this.processEvent(normalized).catch((error) => {
29
- this.logger.log("warn", `question bridge failed: ${formatError(error)}`);
30
- });
31
- }
32
- async tryHandleInboundMessage(message) {
33
- const pending = this.store.getPendingQuestionForTarget(message.deliveryTarget);
34
- if (pending === null) {
35
- return false;
36
- }
37
- const parsed = parseQuestionReply(pending, message.text);
38
- switch (parsed.kind) {
39
- case "invalid":
40
- await this.sendPlainText(pending.deliveryTarget, formatQuestionReplyError(pending, parsed.message));
41
- return true;
42
- case "reject":
43
- await this.rejectQuestion(pending.requestId);
44
- this.store.deletePendingQuestion(pending.requestId);
45
- return true;
46
- case "reply":
47
- await this.replyQuestion(pending.requestId, parsed.answers);
48
- this.store.deletePendingQuestion(pending.requestId);
49
- return true;
50
- }
51
- }
52
- async handleTelegramCallbackQuery(query) {
53
- if (this.telegramClient === null) {
54
- return false;
55
- }
56
- const pending = this.store.getPendingQuestionForTelegramMessage(query.deliveryTarget, query.messageId);
57
- if (pending === null) {
58
- await this.telegramClient.answerCallbackQuery(query.callbackQueryId, "This question is no longer pending.");
59
- return true;
60
- }
61
- const answer = resolveCallbackAnswer(query.data, pending);
62
- if (answer === null) {
63
- await this.telegramClient.answerCallbackQuery(query.callbackQueryId, "This button is no longer valid.");
64
- return true;
65
- }
66
- await this.replyQuestion(pending.requestId, [[answer]]);
67
- this.store.deletePendingQuestion(pending.requestId);
68
- await this.telegramClient.answerCallbackQuery(query.callbackQueryId, `Sent: ${answer}`);
69
- return true;
70
- }
71
- async processEvent(event) {
72
- switch (event.kind) {
73
- case "asked":
74
- await this.handleQuestionAsked(event.request);
75
- return;
76
- case "resolved":
77
- this.store.deletePendingQuestion(event.requestId);
78
- return;
79
- }
80
- }
81
- async handleQuestionAsked(request) {
82
- const targets = this.sessions.listReplyTargets(request.sessionId);
83
- if (targets.length === 0) {
84
- this.logger.log("warn", `question ${request.requestId} has no reply target for session ${request.sessionId}`);
85
- return;
86
- }
87
- const deliveredTargets = [];
88
- for (const target of targets) {
89
- try {
90
- deliveredTargets.push(await this.sendQuestion(target, request));
91
- }
92
- catch (error) {
93
- this.logger.log("warn", `question ${request.requestId} delivery failed for ${target.channel}:${target.target}: ${formatError(error)}`);
94
- }
95
- }
96
- if (deliveredTargets.length === 0) {
97
- return;
98
- }
99
- this.store.replacePendingQuestion({
100
- requestId: request.requestId,
101
- sessionId: request.sessionId,
102
- questions: request.questions,
103
- targets: deliveredTargets,
104
- recordedAtMs: Date.now(),
105
- });
106
- }
107
- async sendQuestion(target, request) {
108
- const nativeKeyboard = buildTelegramInlineKeyboard(request);
109
- if (target.channel === "telegram" && nativeKeyboard !== null && this.telegramClient !== null) {
110
- try {
111
- const sent = await this.telegramClient.sendInteractiveMessage(target.target, formatTelegramNativeQuestion(request), target.topic, nativeKeyboard);
112
- recordTelegramSendSuccess(this.store, Date.now());
113
- return {
114
- deliveryTarget: target,
115
- telegramMessageId: sent.message_id,
116
- };
117
- }
118
- catch (error) {
119
- recordTelegramSendFailure(this.store, formatError(error), Date.now());
120
- throw error;
121
- }
122
- }
123
- await this.sendPlainText(target, formatPlainTextQuestion(request));
124
- return {
125
- deliveryTarget: target,
126
- telegramMessageId: null,
127
- };
128
- }
129
- async sendPlainText(target, body) {
130
- const ack = await this.transport.sendMessage({
131
- deliveryTarget: target,
132
- body,
133
- });
134
- if (ack.errorMessage !== null) {
135
- throw new Error(ack.errorMessage);
136
- }
137
- }
138
- async replyQuestion(requestId, answers) {
139
- await this.client.question.reply({
140
- requestID: requestId,
141
- directory: this.directory,
142
- answers,
143
- }, {
144
- responseStyle: "data",
145
- throwOnError: true,
146
- });
147
- }
148
- async rejectQuestion(requestId) {
149
- await this.client.question.reject({
150
- requestID: requestId,
151
- directory: this.directory,
152
- }, {
153
- responseStyle: "data",
154
- throwOnError: true,
155
- });
156
- }
157
- }
158
- function buildTelegramInlineKeyboard(request) {
159
- if (request.questions.length !== 1) {
160
- return null;
161
- }
162
- const [question] = request.questions;
163
- if (question.multiple || question.options.length === 0) {
164
- return null;
165
- }
166
- return {
167
- inline_keyboard: question.options.map((option, index) => [
168
- {
169
- text: option.label,
170
- callback_data: `q:${index}`,
171
- },
172
- ]),
173
- };
174
- }
175
- function formatTelegramNativeQuestion(request) {
176
- const [question] = request.questions;
177
- return [
178
- "OpenCode needs additional input before it can continue.",
179
- "",
180
- `${question.header}: ${question.question}`,
181
- "",
182
- "Tap a button below or reply with text.",
183
- ].join("\n");
184
- }
185
- function resolveCallbackAnswer(data, pending) {
186
- if (data === null || !data.startsWith("q:") || pending.questions.length !== 1) {
187
- return null;
188
- }
189
- const indexText = data.slice(2);
190
- const index = Number.parseInt(indexText, 10);
191
- if (!Number.isSafeInteger(index) || index < 0) {
192
- return null;
193
- }
194
- return pending.questions[0]?.options[index]?.label ?? null;
195
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,12 +0,0 @@
1
- import { rm } from "node:fs/promises";
2
- export async function deleteInboundAttachmentFiles(entries, logger) {
3
- const paths = new Set(entries.flatMap((entry) => entry.attachments.map((attachment) => attachment.localPath)));
4
- await Promise.all([...paths].map(async (path) => {
5
- try {
6
- await rm(path, { force: true });
7
- }
8
- catch (error) {
9
- logger.log("warn", `failed to remove cached inbound attachment ${path}: ${String(error)}`);
10
- }
11
- }));
12
- }
@@ -1,22 +0,0 @@
1
- export class ConversationCoordinator {
2
- tails = new Map();
3
- async runExclusive(conversationKey, operation) {
4
- const previous = this.tails.get(conversationKey) ?? Promise.resolve();
5
- let release;
6
- const current = new Promise((resolve) => {
7
- release = resolve;
8
- });
9
- const tail = previous.then(() => current, () => current);
10
- this.tails.set(conversationKey, tail);
11
- await previous;
12
- try {
13
- return await operation();
14
- }
15
- finally {
16
- release();
17
- if (this.tails.get(conversationKey) === tail) {
18
- this.tails.delete(conversationKey);
19
- }
20
- }
21
- }
22
- }