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.
- package/dist/cli.js +0 -0
- package/dist/index.js +20907 -52
- package/dist/telegram/client.d.ts +1 -1
- package/dist/telegram/poller.d.ts +16 -1
- package/dist/telegram/runtime.d.ts +3 -1
- package/dist/telegram/state.d.ts +7 -0
- 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 -179
- package/dist/telegram/media.js +0 -65
- package/dist/telegram/normalize.js +0 -119
- package/dist/telegram/poller.js +0 -97
- package/dist/telegram/runtime.js +0 -133
- package/dist/telegram/state.js +0 -128
- 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
package/dist/opencode/adapter.js
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import { basename } from "node:path";
|
|
2
|
-
import { pathToFileURL } from "node:url";
|
|
3
|
-
const SESSION_IDLE_POLL_MS = 250;
|
|
4
|
-
const PROMPT_RESPONSE_TIMEOUT_MS = 90_000;
|
|
5
|
-
export class OpencodeSdkAdapter {
|
|
6
|
-
client;
|
|
7
|
-
directory;
|
|
8
|
-
constructor(client, directory) {
|
|
9
|
-
this.client = client;
|
|
10
|
-
this.directory = directory;
|
|
11
|
-
}
|
|
12
|
-
async createFreshSession(title) {
|
|
13
|
-
const session = await this.client.session.create({
|
|
14
|
-
body: { title },
|
|
15
|
-
query: { directory: this.directory },
|
|
16
|
-
responseStyle: "data",
|
|
17
|
-
throwOnError: true,
|
|
18
|
-
});
|
|
19
|
-
return unwrapData(session).id;
|
|
20
|
-
}
|
|
21
|
-
async isSessionBusy(sessionId) {
|
|
22
|
-
const statuses = await this.client.session.status({
|
|
23
|
-
query: { directory: this.directory },
|
|
24
|
-
responseStyle: "data",
|
|
25
|
-
throwOnError: true,
|
|
26
|
-
});
|
|
27
|
-
const current = unwrapData(statuses)[sessionId];
|
|
28
|
-
return current?.type === "busy";
|
|
29
|
-
}
|
|
30
|
-
async abortSession(sessionId) {
|
|
31
|
-
try {
|
|
32
|
-
await this.client.session.abort({
|
|
33
|
-
path: { id: sessionId },
|
|
34
|
-
query: { directory: this.directory },
|
|
35
|
-
responseStyle: "data",
|
|
36
|
-
throwOnError: true,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
if (isMissingSessionError(error)) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
throw error;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
async execute(command) {
|
|
47
|
-
try {
|
|
48
|
-
switch (command.kind) {
|
|
49
|
-
case "lookupSession":
|
|
50
|
-
return await this.lookupSession(command.sessionId);
|
|
51
|
-
case "createSession":
|
|
52
|
-
return await this.createSession(command.title);
|
|
53
|
-
case "waitUntilIdle":
|
|
54
|
-
return await this.waitUntilIdle(command.sessionId);
|
|
55
|
-
case "appendPrompt":
|
|
56
|
-
return await this.appendPrompt(command);
|
|
57
|
-
case "sendPromptAsync":
|
|
58
|
-
return await this.sendPromptAsync(command);
|
|
59
|
-
case "awaitPromptResponse":
|
|
60
|
-
return await this.awaitPromptResponse(command);
|
|
61
|
-
case "readMessage":
|
|
62
|
-
return await this.readMessage(command);
|
|
63
|
-
case "listMessages":
|
|
64
|
-
return await this.listMessages(command);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
return toErrorResult(command, error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
async lookupSession(sessionId) {
|
|
72
|
-
try {
|
|
73
|
-
await this.client.session.get({
|
|
74
|
-
path: { id: sessionId },
|
|
75
|
-
query: { directory: this.directory },
|
|
76
|
-
responseStyle: "data",
|
|
77
|
-
throwOnError: true,
|
|
78
|
-
});
|
|
79
|
-
return {
|
|
80
|
-
kind: "lookupSession",
|
|
81
|
-
sessionId,
|
|
82
|
-
found: true,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
if (isMissingSessionError(error)) {
|
|
87
|
-
return {
|
|
88
|
-
kind: "lookupSession",
|
|
89
|
-
sessionId,
|
|
90
|
-
found: false,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async createSession(title) {
|
|
97
|
-
return {
|
|
98
|
-
kind: "createSession",
|
|
99
|
-
sessionId: await this.createFreshSession(title),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
async waitUntilIdle(sessionId) {
|
|
103
|
-
for (;;) {
|
|
104
|
-
const statuses = await this.client.session.status({
|
|
105
|
-
query: { directory: this.directory },
|
|
106
|
-
responseStyle: "data",
|
|
107
|
-
throwOnError: true,
|
|
108
|
-
});
|
|
109
|
-
const current = unwrapData(statuses)[sessionId];
|
|
110
|
-
if (!current || current.type === "idle") {
|
|
111
|
-
return {
|
|
112
|
-
kind: "waitUntilIdle",
|
|
113
|
-
sessionId,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
await Bun.sleep(SESSION_IDLE_POLL_MS);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
async appendPrompt(command) {
|
|
120
|
-
await this.client.session.prompt({
|
|
121
|
-
path: { id: command.sessionId },
|
|
122
|
-
query: { directory: this.directory },
|
|
123
|
-
body: {
|
|
124
|
-
messageID: command.messageId,
|
|
125
|
-
noReply: true,
|
|
126
|
-
parts: command.parts.map(toSessionPromptPart),
|
|
127
|
-
},
|
|
128
|
-
responseStyle: "data",
|
|
129
|
-
throwOnError: true,
|
|
130
|
-
});
|
|
131
|
-
return {
|
|
132
|
-
kind: "appendPrompt",
|
|
133
|
-
sessionId: command.sessionId,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
async sendPromptAsync(command) {
|
|
137
|
-
await this.client.session.promptAsync({
|
|
138
|
-
path: { id: command.sessionId },
|
|
139
|
-
query: { directory: this.directory },
|
|
140
|
-
body: {
|
|
141
|
-
messageID: command.messageId,
|
|
142
|
-
parts: command.parts.map(toSessionPromptPart),
|
|
143
|
-
},
|
|
144
|
-
throwOnError: true,
|
|
145
|
-
});
|
|
146
|
-
return {
|
|
147
|
-
kind: "sendPromptAsync",
|
|
148
|
-
sessionId: command.sessionId,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
async awaitPromptResponse(command) {
|
|
152
|
-
const deadline = Date.now() + PROMPT_RESPONSE_TIMEOUT_MS;
|
|
153
|
-
let stableCandidateKey = null;
|
|
154
|
-
for (;;) {
|
|
155
|
-
const messages = await this.client.session.messages({
|
|
156
|
-
path: { id: command.sessionId },
|
|
157
|
-
query: {
|
|
158
|
-
directory: this.directory,
|
|
159
|
-
limit: 64,
|
|
160
|
-
},
|
|
161
|
-
responseStyle: "data",
|
|
162
|
-
throwOnError: true,
|
|
163
|
-
});
|
|
164
|
-
const response = selectAssistantResponse(unwrapData(messages), command.messageId);
|
|
165
|
-
if (response !== null) {
|
|
166
|
-
const candidateKey = createAssistantCandidateKey(response);
|
|
167
|
-
if (stableCandidateKey === candidateKey) {
|
|
168
|
-
return {
|
|
169
|
-
kind: "awaitPromptResponse",
|
|
170
|
-
sessionId: command.sessionId,
|
|
171
|
-
messageId: response.info.id,
|
|
172
|
-
parts: response.parts.flatMap(toBindingMessagePart),
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
stableCandidateKey = candidateKey;
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
stableCandidateKey = null;
|
|
179
|
-
}
|
|
180
|
-
if (Date.now() >= deadline) {
|
|
181
|
-
throw new Error(`assistant message for prompt ${command.messageId} is unavailable after prompt completion`);
|
|
182
|
-
}
|
|
183
|
-
await Bun.sleep(SESSION_IDLE_POLL_MS);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
async readMessage(command) {
|
|
187
|
-
const message = await this.client.session.message({
|
|
188
|
-
path: {
|
|
189
|
-
id: command.sessionId,
|
|
190
|
-
messageID: command.messageId,
|
|
191
|
-
},
|
|
192
|
-
query: { directory: this.directory },
|
|
193
|
-
responseStyle: "data",
|
|
194
|
-
throwOnError: true,
|
|
195
|
-
});
|
|
196
|
-
return {
|
|
197
|
-
kind: "readMessage",
|
|
198
|
-
sessionId: command.sessionId,
|
|
199
|
-
messageId: command.messageId,
|
|
200
|
-
parts: unwrapData(message).parts.flatMap(toBindingMessagePart),
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
async listMessages(command) {
|
|
204
|
-
const messages = await this.client.session.messages({
|
|
205
|
-
path: { id: command.sessionId },
|
|
206
|
-
query: {
|
|
207
|
-
directory: this.directory,
|
|
208
|
-
limit: 32,
|
|
209
|
-
},
|
|
210
|
-
responseStyle: "data",
|
|
211
|
-
throwOnError: true,
|
|
212
|
-
});
|
|
213
|
-
return {
|
|
214
|
-
kind: "listMessages",
|
|
215
|
-
sessionId: command.sessionId,
|
|
216
|
-
messages: unwrapData(messages).flatMap(toBindingMessage),
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
function unwrapData(value) {
|
|
221
|
-
return typeof value === "object" && value !== null && "data" in value ? value.data : value;
|
|
222
|
-
}
|
|
223
|
-
function toSessionPromptPart(part) {
|
|
224
|
-
switch (part.kind) {
|
|
225
|
-
case "text":
|
|
226
|
-
return {
|
|
227
|
-
id: part.partId,
|
|
228
|
-
type: "text",
|
|
229
|
-
text: part.text,
|
|
230
|
-
};
|
|
231
|
-
case "file":
|
|
232
|
-
return {
|
|
233
|
-
id: part.partId,
|
|
234
|
-
type: "file",
|
|
235
|
-
mime: part.mimeType,
|
|
236
|
-
url: pathToFileURL(part.localPath).href,
|
|
237
|
-
filename: part.fileName ?? basename(part.localPath),
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function selectAssistantResponse(messages, userMessageId) {
|
|
242
|
-
const assistantChildren = messages.filter(isAssistantChildMessage(userMessageId));
|
|
243
|
-
for (let index = assistantChildren.length - 1; index >= 0; index -= 1) {
|
|
244
|
-
const candidate = assistantChildren[index];
|
|
245
|
-
if (hasVisibleText(candidate)) {
|
|
246
|
-
return candidate;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
for (let index = assistantChildren.length - 1; index >= 0; index -= 1) {
|
|
250
|
-
const candidate = assistantChildren[index];
|
|
251
|
-
if (candidate.info?.finish === "stop" || candidate.info?.error !== undefined) {
|
|
252
|
-
return candidate;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
function createAssistantCandidateKey(message) {
|
|
258
|
-
return JSON.stringify({
|
|
259
|
-
messageId: message.info.id,
|
|
260
|
-
finish: message.info.finish ?? null,
|
|
261
|
-
hasError: message.info.error !== undefined,
|
|
262
|
-
parts: message.parts.map((part) => ({
|
|
263
|
-
id: part.id ?? null,
|
|
264
|
-
type: part.type,
|
|
265
|
-
text: typeof part.text === "string" ? part.text : null,
|
|
266
|
-
ignored: part.ignored === true,
|
|
267
|
-
})),
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
function isAssistantChildMessage(userMessageId) {
|
|
271
|
-
return (message) => message.info?.role === "assistant" && message.info.parentID === userMessageId;
|
|
272
|
-
}
|
|
273
|
-
function toBindingMessagePart(part) {
|
|
274
|
-
if (typeof part.id !== "string" || typeof part.messageID !== "string" || part.type.length === 0) {
|
|
275
|
-
return [];
|
|
276
|
-
}
|
|
277
|
-
return [
|
|
278
|
-
{
|
|
279
|
-
messageId: part.messageID,
|
|
280
|
-
partId: part.id,
|
|
281
|
-
type: part.type,
|
|
282
|
-
text: typeof part.text === "string" ? part.text : null,
|
|
283
|
-
ignored: part.ignored === true,
|
|
284
|
-
},
|
|
285
|
-
];
|
|
286
|
-
}
|
|
287
|
-
function hasVisibleText(message) {
|
|
288
|
-
return message.parts.some((part) => part.type === "text" &&
|
|
289
|
-
part.ignored !== true &&
|
|
290
|
-
typeof part.text === "string" &&
|
|
291
|
-
part.text.trim().length > 0);
|
|
292
|
-
}
|
|
293
|
-
function toBindingMessage(message) {
|
|
294
|
-
if (typeof message.info?.id !== "string" ||
|
|
295
|
-
typeof message.info.role !== "string" ||
|
|
296
|
-
message.info.role.length === 0) {
|
|
297
|
-
return [];
|
|
298
|
-
}
|
|
299
|
-
return [
|
|
300
|
-
{
|
|
301
|
-
messageId: message.info.id,
|
|
302
|
-
role: message.info.role,
|
|
303
|
-
parentId: typeof message.info.parentID === "string" ? message.info.parentID : null,
|
|
304
|
-
parts: message.parts.flatMap(toBindingMessagePart),
|
|
305
|
-
},
|
|
306
|
-
];
|
|
307
|
-
}
|
|
308
|
-
function toErrorResult(command, error) {
|
|
309
|
-
return {
|
|
310
|
-
kind: "error",
|
|
311
|
-
commandKind: command.kind,
|
|
312
|
-
sessionId: "sessionId" in command ? command.sessionId : null,
|
|
313
|
-
code: isMissingSessionError(error) ? "missingSession" : "unknown",
|
|
314
|
-
message: extractErrorMessage(error),
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
function isMissingSessionError(error) {
|
|
318
|
-
if (typeof error !== "object" || error === null) {
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
const name = "name" in error ? error.name : undefined;
|
|
322
|
-
const message = "data" in error ? extractDataMessage(error.data) : null;
|
|
323
|
-
return name === "NotFoundError" && message?.includes("Session not found:") === true;
|
|
324
|
-
}
|
|
325
|
-
function extractDataMessage(value) {
|
|
326
|
-
if (typeof value !== "object" || value === null) {
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
const message = value.message;
|
|
330
|
-
return typeof message === "string" ? message : null;
|
|
331
|
-
}
|
|
332
|
-
function extractErrorMessage(error) {
|
|
333
|
-
if (error instanceof Error && error.message.length > 0) {
|
|
334
|
-
return error.message;
|
|
335
|
-
}
|
|
336
|
-
if (typeof error === "string" && error.length > 0) {
|
|
337
|
-
return error;
|
|
338
|
-
}
|
|
339
|
-
return "OpenCode command failed";
|
|
340
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { normalizeExecutionObservation } from "./event-normalize";
|
|
2
|
-
export class OpencodeEventHub {
|
|
3
|
-
activeDrivers = new Map();
|
|
4
|
-
nextDriverId = 0;
|
|
5
|
-
registerDriver(sessionId, driver, onPreview) {
|
|
6
|
-
const driverId = this.nextDriverId++;
|
|
7
|
-
attachDriver(this.activeDrivers, sessionId, driverId, {
|
|
8
|
-
driver,
|
|
9
|
-
onPreview,
|
|
10
|
-
});
|
|
11
|
-
return {
|
|
12
|
-
dispose: () => {
|
|
13
|
-
detachDriver(this.activeDrivers, sessionId, driverId);
|
|
14
|
-
},
|
|
15
|
-
updateSession: (nextSessionId) => {
|
|
16
|
-
if (nextSessionId === sessionId) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const current = this.activeDrivers.get(sessionId)?.get(driverId);
|
|
20
|
-
if (!current) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
detachDriver(this.activeDrivers, sessionId, driverId);
|
|
24
|
-
attachDriver(this.activeDrivers, nextSessionId, driverId, current);
|
|
25
|
-
sessionId = nextSessionId;
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
handleEvent(event) {
|
|
30
|
-
const observation = normalizeExecutionObservation(event);
|
|
31
|
-
if (observation === null) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if ("sessionId" in observation) {
|
|
35
|
-
this.dispatchToSession(observation.sessionId, observation);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
for (const drivers of this.activeDrivers.values()) {
|
|
39
|
-
for (const driver of drivers.values()) {
|
|
40
|
-
this.publishDirective(driver, driver.driver.observeEvent(observation, monotonicNowMs()));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
dispatchToSession(sessionId, observation) {
|
|
45
|
-
const drivers = this.activeDrivers.get(sessionId);
|
|
46
|
-
if (!drivers) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
for (const driver of drivers.values()) {
|
|
50
|
-
this.publishDirective(driver, driver.driver.observeEvent(observation, monotonicNowMs()));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
publishDirective(driver, directive) {
|
|
54
|
-
if (directive.kind !== "preview" || directive.text === null) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
void Promise.resolve(driver.onPreview(directive.text)).catch(() => {
|
|
58
|
-
// Preview delivery must not break the final response path.
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function monotonicNowMs() {
|
|
63
|
-
return Math.trunc(performance.now());
|
|
64
|
-
}
|
|
65
|
-
function attachDriver(activeDrivers, sessionId, driverId, driver) {
|
|
66
|
-
let drivers = activeDrivers.get(sessionId);
|
|
67
|
-
if (!drivers) {
|
|
68
|
-
drivers = new Map();
|
|
69
|
-
activeDrivers.set(sessionId, drivers);
|
|
70
|
-
}
|
|
71
|
-
drivers.set(driverId, driver);
|
|
72
|
-
}
|
|
73
|
-
function detachDriver(activeDrivers, sessionId, driverId) {
|
|
74
|
-
const drivers = activeDrivers.get(sessionId);
|
|
75
|
-
if (!drivers) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
drivers.delete(driverId);
|
|
79
|
-
if (drivers.size === 0) {
|
|
80
|
-
activeDrivers.delete(sessionId);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export function normalizeExecutionObservation(event) {
|
|
2
|
-
if (isMessageUpdatedEvent(event)) {
|
|
3
|
-
const info = event.properties.info;
|
|
4
|
-
return {
|
|
5
|
-
kind: "messageUpdated",
|
|
6
|
-
sessionId: info.sessionID,
|
|
7
|
-
messageId: info.id,
|
|
8
|
-
role: info.role,
|
|
9
|
-
parentId: typeof info.parentID === "string" ? info.parentID : null,
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
if (isMessagePartUpdatedEvent(event)) {
|
|
13
|
-
const part = event.properties.part;
|
|
14
|
-
if (part.type !== "text") {
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
kind: "textPartUpdated",
|
|
19
|
-
sessionId: part.sessionID,
|
|
20
|
-
messageId: part.messageID,
|
|
21
|
-
partId: part.id,
|
|
22
|
-
text: typeof part.text === "string" ? part.text : null,
|
|
23
|
-
delta: typeof event.properties.delta === "string" ? event.properties.delta : null,
|
|
24
|
-
ignored: part.ignored === true,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
if (isMessagePartDeltaEvent(event)) {
|
|
28
|
-
if (event.properties.field !== "text" || event.properties.delta.length === 0) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
kind: "textPartDelta",
|
|
33
|
-
messageId: event.properties.messageID,
|
|
34
|
-
partId: event.properties.partID,
|
|
35
|
-
delta: event.properties.delta,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
function isMessageUpdatedEvent(event) {
|
|
41
|
-
return event.type === "message.updated" && typeof event.properties === "object" && event.properties !== null;
|
|
42
|
-
}
|
|
43
|
-
function isMessagePartUpdatedEvent(event) {
|
|
44
|
-
return event.type === "message.part.updated" && typeof event.properties === "object" && event.properties !== null;
|
|
45
|
-
}
|
|
46
|
-
function isMessagePartDeltaEvent(event) {
|
|
47
|
-
return event.type === "message.part.delta" && typeof event.properties === "object" && event.properties !== null;
|
|
48
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { formatError } from "../utils/error";
|
|
2
|
-
const RECONNECT_DELAY_MS = 1_000;
|
|
3
|
-
export class OpencodeEventStream {
|
|
4
|
-
client;
|
|
5
|
-
directory;
|
|
6
|
-
hub;
|
|
7
|
-
consumers;
|
|
8
|
-
logger;
|
|
9
|
-
running = false;
|
|
10
|
-
connected = false;
|
|
11
|
-
lastError = null;
|
|
12
|
-
constructor(client, directory, hub, consumers, logger) {
|
|
13
|
-
this.client = client;
|
|
14
|
-
this.directory = directory;
|
|
15
|
-
this.hub = hub;
|
|
16
|
-
this.consumers = consumers;
|
|
17
|
-
this.logger = logger;
|
|
18
|
-
}
|
|
19
|
-
isConnected() {
|
|
20
|
-
return this.connected;
|
|
21
|
-
}
|
|
22
|
-
lastStreamError() {
|
|
23
|
-
return this.lastError;
|
|
24
|
-
}
|
|
25
|
-
start() {
|
|
26
|
-
if (this.running) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
this.running = true;
|
|
30
|
-
void this.runLoop();
|
|
31
|
-
}
|
|
32
|
-
stop() {
|
|
33
|
-
this.running = false;
|
|
34
|
-
}
|
|
35
|
-
async runLoop() {
|
|
36
|
-
while (this.running) {
|
|
37
|
-
try {
|
|
38
|
-
const events = await this.client.event.subscribe({
|
|
39
|
-
query: { directory: this.directory },
|
|
40
|
-
onSseError: (error) => {
|
|
41
|
-
this.connected = false;
|
|
42
|
-
this.lastError = formatError(error);
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
this.connected = true;
|
|
46
|
-
this.lastError = null;
|
|
47
|
-
for await (const event of events.stream) {
|
|
48
|
-
const runtimeEvent = event;
|
|
49
|
-
this.hub.handleEvent(runtimeEvent);
|
|
50
|
-
for (const consumer of this.consumers) {
|
|
51
|
-
consumer.handleEvent(runtimeEvent);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
this.lastError = formatError(error);
|
|
57
|
-
this.logger.log("warn", `opencode event stream failed: ${this.lastError}`);
|
|
58
|
-
}
|
|
59
|
-
finally {
|
|
60
|
-
this.connected = false;
|
|
61
|
-
}
|
|
62
|
-
await Bun.sleep(RECONNECT_DELAY_MS);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
package/dist/opencode/events.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { OpencodeEventHub } from "./driver-hub";
|
package/dist/questions/client.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
2
|
-
export function createQuestionClient(client, serverUrl, directory) {
|
|
3
|
-
const snapshot = readClientConfig(client);
|
|
4
|
-
return createOpencodeClient({
|
|
5
|
-
baseUrl: snapshot.baseUrl ?? serverUrl.toString(),
|
|
6
|
-
directory,
|
|
7
|
-
headers: stripManagedHeaders(snapshot.headers),
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
function readClientConfig(client) {
|
|
11
|
-
const configReader = client._client?.getConfig;
|
|
12
|
-
if (typeof configReader !== "function") {
|
|
13
|
-
return {};
|
|
14
|
-
}
|
|
15
|
-
return configReader() ?? {};
|
|
16
|
-
}
|
|
17
|
-
function stripManagedHeaders(headers) {
|
|
18
|
-
if (headers === undefined) {
|
|
19
|
-
return undefined;
|
|
20
|
-
}
|
|
21
|
-
const normalized = new Headers(toHeadersInit(headers));
|
|
22
|
-
normalized.delete("x-opencode-directory");
|
|
23
|
-
const result = Object.fromEntries(normalized.entries());
|
|
24
|
-
return Object.keys(result).length === 0 ? undefined : result;
|
|
25
|
-
}
|
|
26
|
-
function toHeadersInit(headers) {
|
|
27
|
-
if (headers instanceof Headers || Array.isArray(headers)) {
|
|
28
|
-
return headers;
|
|
29
|
-
}
|
|
30
|
-
if (typeof headers === "object" && headers !== null) {
|
|
31
|
-
return Object.fromEntries(Object.entries(headers)
|
|
32
|
-
.filter((entry) => typeof entry[1] === "string")
|
|
33
|
-
.map(([key, value]) => [key, value]));
|
|
34
|
-
}
|
|
35
|
-
return {};
|
|
36
|
-
}
|
package/dist/questions/format.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export function formatPlainTextQuestion(request) {
|
|
2
|
-
return [
|
|
3
|
-
"OpenCode needs additional input before it can continue.",
|
|
4
|
-
"",
|
|
5
|
-
...request.questions.flatMap((question, index) => formatQuestionBlock(question, index)),
|
|
6
|
-
formatReplyInstructions(request.questions),
|
|
7
|
-
].join("\n");
|
|
8
|
-
}
|
|
9
|
-
export function formatQuestionReplyError(request, message) {
|
|
10
|
-
return [message, "", formatReplyInstructions(request.questions)].join("\n");
|
|
11
|
-
}
|
|
12
|
-
function formatQuestionBlock(question, index) {
|
|
13
|
-
const label = `Question ${index + 1}: ${question.header}`;
|
|
14
|
-
const options = question.options.length === 0
|
|
15
|
-
? []
|
|
16
|
-
: [
|
|
17
|
-
"Options:",
|
|
18
|
-
...question.options.map((option, optionIndex) => `${optionIndex + 1}. ${option.label} - ${option.description}`),
|
|
19
|
-
];
|
|
20
|
-
return [label, question.question, ...options, ""];
|
|
21
|
-
}
|
|
22
|
-
function formatReplyInstructions(questions) {
|
|
23
|
-
if (questions.length === 1) {
|
|
24
|
-
const question = questions[0];
|
|
25
|
-
const selectionHint = question.multiple
|
|
26
|
-
? "Reply with one line. You may send option numbers or labels separated by commas."
|
|
27
|
-
: "Reply with one line. You may send an option number, an option label, or custom text.";
|
|
28
|
-
return ["How to reply:", `- ${selectionHint}`, "- Reply /cancel to reject this question."].join("\n");
|
|
29
|
-
}
|
|
30
|
-
return [
|
|
31
|
-
"How to reply:",
|
|
32
|
-
"- Reply with one non-empty line per question, in order.",
|
|
33
|
-
"- Each line may use option numbers, option labels, or custom text when allowed.",
|
|
34
|
-
"- Reply /cancel to reject this question.",
|
|
35
|
-
].join("\n");
|
|
36
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export function normalizeQuestionEvent(event) {
|
|
2
|
-
if (isQuestionAskedEvent(event)) {
|
|
3
|
-
return {
|
|
4
|
-
kind: "asked",
|
|
5
|
-
request: {
|
|
6
|
-
requestId: event.properties.id,
|
|
7
|
-
sessionId: event.properties.sessionID,
|
|
8
|
-
questions: event.properties.questions.map((question) => ({
|
|
9
|
-
header: question.header,
|
|
10
|
-
question: question.question,
|
|
11
|
-
options: question.options.map((option) => ({
|
|
12
|
-
label: option.label,
|
|
13
|
-
description: option.description,
|
|
14
|
-
})),
|
|
15
|
-
multiple: question.multiple === true,
|
|
16
|
-
custom: question.custom !== false,
|
|
17
|
-
})),
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
if (isQuestionResolvedEvent(event)) {
|
|
22
|
-
return {
|
|
23
|
-
kind: "resolved",
|
|
24
|
-
requestId: event.properties.requestID,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
function isQuestionAskedEvent(event) {
|
|
30
|
-
if (event.type !== "question.asked" || typeof event.properties !== "object" || event.properties === null) {
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
const properties = event.properties;
|
|
34
|
-
return (typeof properties.id === "string" &&
|
|
35
|
-
typeof properties.sessionID === "string" &&
|
|
36
|
-
Array.isArray(properties.questions));
|
|
37
|
-
}
|
|
38
|
-
function isQuestionResolvedEvent(event) {
|
|
39
|
-
if ((event.type !== "question.replied" && event.type !== "question.rejected") ||
|
|
40
|
-
typeof event.properties !== "object" ||
|
|
41
|
-
event.properties === null) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
return typeof event.properties.requestID === "string";
|
|
45
|
-
}
|