opencode-tbot 0.1.25 → 0.1.27
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/README.ja.md +5 -0
- package/README.md +12 -1
- package/README.zh-CN.md +12 -1
- package/dist/assets/{plugin-config-CCeFjxSf.js → plugin-config-DNeV2Ckw.js} +20 -1
- package/dist/assets/plugin-config-DNeV2Ckw.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.js +419 -285
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/plugin-config-CCeFjxSf.js.map +0 -1
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as loadAppConfig, i as preparePluginConfiguration, o as OPENCODE_TBOT_VERSION } from "./assets/plugin-config-
|
|
1
|
+
import { c as loadAppConfig, i as preparePluginConfiguration, o as OPENCODE_TBOT_VERSION } from "./assets/plugin-config-DNeV2Ckw.js";
|
|
2
2
|
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join } from "node:path";
|
|
4
4
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
@@ -218,6 +218,17 @@ var OpenCodePromptTimeoutError = class extends Error {
|
|
|
218
218
|
this.data = data;
|
|
219
219
|
}
|
|
220
220
|
};
|
|
221
|
+
var OpenCodeMessageAbortedError = class extends Error {
|
|
222
|
+
data;
|
|
223
|
+
constructor(message = "Request was aborted.") {
|
|
224
|
+
super(message);
|
|
225
|
+
this.name = "MessageAbortedError";
|
|
226
|
+
this.data = { message };
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function createMessageAbortedError(message = "Request was aborted.") {
|
|
230
|
+
return new OpenCodeMessageAbortedError(message);
|
|
231
|
+
}
|
|
221
232
|
var EMPTY_RESPONSE_TEXT = "OpenCode returned empty response.";
|
|
222
233
|
var PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS = [
|
|
223
234
|
0,
|
|
@@ -227,11 +238,13 @@ var PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS = [
|
|
|
227
238
|
1e3
|
|
228
239
|
];
|
|
229
240
|
var PROMPT_MESSAGE_POLL_INTERVAL_MS = 2e3;
|
|
230
|
-
var PROMPT_POLL_REQUEST_TIMEOUT_MS = 15e3;
|
|
231
|
-
var PROMPT_SEND_TIMEOUT_MS = 3e4;
|
|
232
|
-
var PROMPT_MESSAGE_POLL_TIMEOUT_MS = 6e4;
|
|
233
241
|
var PROMPT_MESSAGE_POLL_LIMIT = 20;
|
|
234
242
|
var PROMPT_LOG_SERVICE = "opencode-tbot";
|
|
243
|
+
var DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY = {
|
|
244
|
+
pollRequestTimeoutMs: 15e3,
|
|
245
|
+
recoveryInactivityTimeoutMs: 12e4,
|
|
246
|
+
waitTimeoutMs: 18e5
|
|
247
|
+
};
|
|
235
248
|
var STRUCTURED_REPLY_SCHEMA = {
|
|
236
249
|
type: "json_schema",
|
|
237
250
|
retryCount: 2,
|
|
@@ -253,134 +266,94 @@ var StructuredReplySchema = z.object({ body_md: z.string() });
|
|
|
253
266
|
var OpenCodeClient = class {
|
|
254
267
|
client;
|
|
255
268
|
fetchFn;
|
|
256
|
-
|
|
257
|
-
pollRequestMs: PROMPT_POLL_REQUEST_TIMEOUT_MS,
|
|
258
|
-
sendMs: PROMPT_SEND_TIMEOUT_MS,
|
|
259
|
-
totalPollMs: PROMPT_MESSAGE_POLL_TIMEOUT_MS
|
|
260
|
-
};
|
|
269
|
+
promptTimeoutPolicy;
|
|
261
270
|
modelCache = {
|
|
262
271
|
expiresAt: 0,
|
|
263
272
|
promise: null,
|
|
264
273
|
value: null
|
|
265
274
|
};
|
|
266
|
-
constructor(options, client, fetchFn = fetch) {
|
|
275
|
+
constructor(options, client, fetchFn = fetch, promptTimeoutPolicy = {}) {
|
|
267
276
|
if (!options && !client) throw new Error("OpenCodeClient requires either base URL options or an injected SDK client.");
|
|
268
277
|
this.client = client ?? createOpencodeClient(buildOpenCodeSdkConfig(options));
|
|
269
278
|
this.fetchFn = fetchFn;
|
|
279
|
+
this.promptTimeoutPolicy = resolvePromptTimeoutPolicy(promptTimeoutPolicy);
|
|
280
|
+
}
|
|
281
|
+
configurePromptTimeoutPolicy(promptTimeoutPolicy) {
|
|
282
|
+
this.promptTimeoutPolicy = resolvePromptTimeoutPolicy({
|
|
283
|
+
...this.promptTimeoutPolicy,
|
|
284
|
+
...promptTimeoutPolicy
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
async callScopedSdkMethod(scope, method, input) {
|
|
288
|
+
const target = this.client[scope];
|
|
289
|
+
const handler = target?.[method];
|
|
290
|
+
if (typeof handler !== "function") throw new Error(`OpenCode SDK client does not expose a compatible ${scope}.${method} method.`);
|
|
291
|
+
const options = {
|
|
292
|
+
...SDK_OPTIONS,
|
|
293
|
+
...input.signal ? { signal: input.signal } : {}
|
|
294
|
+
};
|
|
295
|
+
return unwrapSdkData(input.parameters === void 0 ? await handler.call(target, options) : await handler.call(target, input.parameters, options));
|
|
270
296
|
}
|
|
271
297
|
async getHealth() {
|
|
272
|
-
|
|
273
|
-
if (rawClient?.get) return await this.requestRaw("get", { url: "/global/health" });
|
|
274
|
-
const healthEndpoint = this.client.global?.health;
|
|
275
|
-
if (typeof healthEndpoint === "function") return unwrapSdkData(await healthEndpoint.call(this.client.global, SDK_OPTIONS));
|
|
276
|
-
if (!rawClient?.get) throw new Error("OpenCode SDK client does not expose a compatible health endpoint.");
|
|
277
|
-
return this.requestRaw("get", { url: "/global/health" });
|
|
298
|
+
return this.callScopedSdkMethod("global", "health", {});
|
|
278
299
|
}
|
|
279
300
|
async abortSession(sessionId) {
|
|
280
|
-
|
|
281
|
-
url: "/session/{sessionID}/abort",
|
|
282
|
-
path: { sessionID: sessionId }
|
|
283
|
-
});
|
|
284
|
-
return unwrapSdkData(await this.client.session.abort({ sessionID: sessionId }, SDK_OPTIONS));
|
|
301
|
+
return this.callScopedSdkMethod("session", "abort", { parameters: { sessionID: sessionId } });
|
|
285
302
|
}
|
|
286
303
|
async deleteSession(sessionId) {
|
|
287
|
-
|
|
288
|
-
url: "/session/{sessionID}",
|
|
289
|
-
path: { sessionID: sessionId }
|
|
290
|
-
});
|
|
291
|
-
return unwrapSdkData(await this.client.session.delete({ sessionID: sessionId }, SDK_OPTIONS));
|
|
304
|
+
return this.callScopedSdkMethod("session", "delete", { parameters: { sessionID: sessionId } });
|
|
292
305
|
}
|
|
293
306
|
async forkSession(sessionId, messageId) {
|
|
294
|
-
|
|
295
|
-
url: "/session/{sessionID}/fork",
|
|
296
|
-
path: { sessionID: sessionId },
|
|
297
|
-
...messageId?.trim() ? { body: { messageID: messageId.trim() } } : {}
|
|
298
|
-
});
|
|
299
|
-
return unwrapSdkData(await this.client.session.fork({
|
|
307
|
+
return this.callScopedSdkMethod("session", "fork", { parameters: {
|
|
300
308
|
sessionID: sessionId,
|
|
301
309
|
...messageId?.trim() ? { messageID: messageId.trim() } : {}
|
|
302
|
-
}
|
|
310
|
+
} });
|
|
303
311
|
}
|
|
304
312
|
async getPath() {
|
|
305
|
-
|
|
306
|
-
return unwrapSdkData(await this.client.path.get(void 0, SDK_OPTIONS));
|
|
313
|
+
return this.callScopedSdkMethod("path", "get", {});
|
|
307
314
|
}
|
|
308
315
|
async listLspStatuses(directory) {
|
|
309
|
-
|
|
310
|
-
url: "/lsp",
|
|
311
|
-
...directory ? { query: { directory } } : {}
|
|
312
|
-
});
|
|
313
|
-
return unwrapSdkData(await this.client.lsp.status(directory ? { directory } : void 0, SDK_OPTIONS));
|
|
316
|
+
return this.callScopedSdkMethod("lsp", "status", { parameters: directory ? { directory } : void 0 });
|
|
314
317
|
}
|
|
315
318
|
async listMcpStatuses(directory) {
|
|
316
|
-
|
|
317
|
-
url: "/mcp",
|
|
318
|
-
...directory ? { query: { directory } } : {}
|
|
319
|
-
});
|
|
320
|
-
return unwrapSdkData(await this.client.mcp.status(directory ? { directory } : void 0, SDK_OPTIONS));
|
|
319
|
+
return this.callScopedSdkMethod("mcp", "status", { parameters: directory ? { directory } : void 0 });
|
|
321
320
|
}
|
|
322
321
|
async getSessionStatuses() {
|
|
323
322
|
return this.loadSessionStatuses();
|
|
324
323
|
}
|
|
325
324
|
async listProjects() {
|
|
326
|
-
|
|
327
|
-
return unwrapSdkData(await this.client.project.list(void 0, SDK_OPTIONS));
|
|
325
|
+
return this.callScopedSdkMethod("project", "list", {});
|
|
328
326
|
}
|
|
329
327
|
async listSessions() {
|
|
330
|
-
|
|
331
|
-
return unwrapSdkData(await this.client.session.list(void 0, SDK_OPTIONS));
|
|
328
|
+
return this.callScopedSdkMethod("session", "list", {});
|
|
332
329
|
}
|
|
333
330
|
async getCurrentProject() {
|
|
334
|
-
|
|
335
|
-
return unwrapSdkData(await this.client.project.current(void 0, SDK_OPTIONS));
|
|
331
|
+
return this.callScopedSdkMethod("project", "current", {});
|
|
336
332
|
}
|
|
337
333
|
async createSessionForDirectory(directory, title) {
|
|
338
|
-
|
|
339
|
-
url: "/session",
|
|
340
|
-
query: { directory },
|
|
341
|
-
...title ? { body: { title } } : {}
|
|
342
|
-
});
|
|
343
|
-
return unwrapSdkData(await this.client.session.create(title ? {
|
|
334
|
+
return this.callScopedSdkMethod("session", "create", { parameters: title ? {
|
|
344
335
|
directory,
|
|
345
336
|
title
|
|
346
|
-
} : { directory }
|
|
337
|
+
} : { directory } });
|
|
347
338
|
}
|
|
348
339
|
async renameSession(sessionId, title) {
|
|
349
|
-
|
|
350
|
-
url: "/session/{sessionID}",
|
|
351
|
-
path: { sessionID: sessionId },
|
|
352
|
-
body: { title }
|
|
353
|
-
});
|
|
354
|
-
return unwrapSdkData(await this.client.session.update({
|
|
340
|
+
return this.callScopedSdkMethod("session", "update", { parameters: {
|
|
355
341
|
sessionID: sessionId,
|
|
356
342
|
title
|
|
357
|
-
}
|
|
343
|
+
} });
|
|
358
344
|
}
|
|
359
345
|
async listAgents() {
|
|
360
|
-
|
|
361
|
-
return unwrapSdkData(await this.client.app.agents(void 0, SDK_OPTIONS));
|
|
346
|
+
return this.callScopedSdkMethod("app", "agents", {});
|
|
362
347
|
}
|
|
363
348
|
async listPendingPermissions(directory) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return unwrapSdkData(await this.client.permission.list(directory ? { directory } : void 0, SDK_OPTIONS));
|
|
369
|
-
}
|
|
370
|
-
async replyToPermission(requestId, reply, message) {
|
|
371
|
-
if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
|
|
372
|
-
url: "/permission/{requestID}/reply",
|
|
373
|
-
path: { requestID: requestId },
|
|
374
|
-
body: {
|
|
375
|
-
reply,
|
|
376
|
-
...message?.trim() ? { message: message.trim() } : {}
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
return unwrapSdkData(await this.client.permission.reply({
|
|
349
|
+
return this.callScopedSdkMethod("permission", "list", { parameters: directory ? { directory } : void 0 });
|
|
350
|
+
}
|
|
351
|
+
async replyToPermission(requestId, reply, message, _sessionId) {
|
|
352
|
+
return this.callScopedSdkMethod("permission", "reply", { parameters: {
|
|
380
353
|
requestID: requestId,
|
|
381
354
|
reply,
|
|
382
355
|
...message?.trim() ? { message: message.trim() } : {}
|
|
383
|
-
}
|
|
356
|
+
} });
|
|
384
357
|
}
|
|
385
358
|
async listModels() {
|
|
386
359
|
const now = Date.now();
|
|
@@ -405,9 +378,10 @@ var OpenCodeClient = class {
|
|
|
405
378
|
url: file.url
|
|
406
379
|
}))];
|
|
407
380
|
if (parts.length === 0) throw new Error("Prompt requires text or file attachments.");
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
381
|
+
throwIfAborted(input.signal);
|
|
382
|
+
const knownMessageIds = await this.captureKnownMessageIds(input.sessionId, input.signal);
|
|
383
|
+
await this.sendPromptRequest(input, parts);
|
|
384
|
+
return buildPromptSessionResult(await this.resolvePromptResponse(input, null, knownMessageIds, startedAt), {
|
|
411
385
|
emptyResponseText: EMPTY_RESPONSE_TEXT,
|
|
412
386
|
finishedAt: Date.now(),
|
|
413
387
|
startedAt,
|
|
@@ -416,7 +390,7 @@ var OpenCodeClient = class {
|
|
|
416
390
|
}
|
|
417
391
|
async resolvePromptResponse(input, data, knownMessageIds, startedAt) {
|
|
418
392
|
const structured = input.structured ?? false;
|
|
419
|
-
if (data &&
|
|
393
|
+
if (data && shouldReturnPromptResponseImmediately(data, structured)) return data;
|
|
420
394
|
const messageId = data ? extractMessageId(data.info) : null;
|
|
421
395
|
const candidateOptions = {
|
|
422
396
|
initialMessageId: messageId,
|
|
@@ -426,145 +400,162 @@ var OpenCodeClient = class {
|
|
|
426
400
|
structured
|
|
427
401
|
};
|
|
428
402
|
let bestCandidate = selectPromptResponseCandidate(data ? [data] : [], candidateOptions);
|
|
429
|
-
|
|
403
|
+
let lastProgressAt = Date.now();
|
|
404
|
+
let lastStatus = null;
|
|
405
|
+
const deadlineAt = startedAt + this.promptTimeoutPolicy.waitTimeoutMs;
|
|
430
406
|
let idleStatusSeen = false;
|
|
431
407
|
let attempt = 0;
|
|
432
408
|
while (true) {
|
|
409
|
+
throwIfAborted(input.signal);
|
|
410
|
+
const remainingWaitMs = deadlineAt - Date.now();
|
|
411
|
+
const remainingInactivityMs = this.promptTimeoutPolicy.recoveryInactivityTimeoutMs - (Date.now() - lastProgressAt);
|
|
412
|
+
if (remainingWaitMs <= 0 || remainingInactivityMs <= 0) break;
|
|
433
413
|
const delayMs = getPromptMessagePollDelayMs(attempt);
|
|
434
414
|
attempt += 1;
|
|
435
415
|
if (delayMs > 0) {
|
|
436
|
-
const remainingMs =
|
|
416
|
+
const remainingMs = Math.min(remainingWaitMs, remainingInactivityMs);
|
|
437
417
|
if (remainingMs <= 0) break;
|
|
438
|
-
await delay(Math.min(delayMs, remainingMs));
|
|
418
|
+
await delay(Math.min(delayMs, remainingMs), input.signal);
|
|
439
419
|
}
|
|
440
420
|
if (messageId) {
|
|
441
|
-
const next = await this.fetchPromptMessage(input.sessionId, messageId);
|
|
421
|
+
const next = await this.fetchPromptMessage(input.sessionId, messageId, input.signal);
|
|
442
422
|
if (next) {
|
|
443
423
|
const nextCandidate = selectPromptResponseCandidate([bestCandidate, next], candidateOptions);
|
|
444
|
-
if (nextCandidate)
|
|
445
|
-
|
|
424
|
+
if (nextCandidate) {
|
|
425
|
+
if (didPromptResponseAdvance(bestCandidate, nextCandidate, structured)) {
|
|
426
|
+
lastProgressAt = Date.now();
|
|
427
|
+
idleStatusSeen = false;
|
|
428
|
+
}
|
|
429
|
+
bestCandidate = nextCandidate;
|
|
430
|
+
}
|
|
431
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
446
432
|
}
|
|
447
433
|
}
|
|
448
|
-
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages");
|
|
434
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages", input.signal);
|
|
449
435
|
if (latest) {
|
|
450
436
|
const nextCandidate = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
|
|
451
|
-
if (nextCandidate)
|
|
452
|
-
|
|
437
|
+
if (nextCandidate) {
|
|
438
|
+
if (didPromptResponseAdvance(bestCandidate, nextCandidate, structured)) {
|
|
439
|
+
lastProgressAt = Date.now();
|
|
440
|
+
idleStatusSeen = false;
|
|
441
|
+
}
|
|
442
|
+
bestCandidate = nextCandidate;
|
|
443
|
+
}
|
|
444
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
453
445
|
}
|
|
454
|
-
|
|
446
|
+
const status = await this.fetchPromptSessionStatus(input.sessionId, input.signal);
|
|
447
|
+
lastStatus = status;
|
|
448
|
+
if (status?.type === "busy" || status?.type === "retry") {
|
|
449
|
+
lastProgressAt = Date.now();
|
|
450
|
+
idleStatusSeen = false;
|
|
451
|
+
} else if (status?.type === "idle") {
|
|
455
452
|
if (idleStatusSeen) break;
|
|
456
453
|
idleStatusSeen = true;
|
|
457
454
|
}
|
|
455
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && isCompletedEmptyPromptResponse(bestCandidate, structured) && status?.type !== "busy" && status?.type !== "retry") break;
|
|
458
456
|
if (Date.now() >= deadlineAt) break;
|
|
459
457
|
}
|
|
460
|
-
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan");
|
|
458
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan", input.signal);
|
|
461
459
|
const resolved = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
|
|
462
|
-
|
|
460
|
+
const requestScopedResolved = resolved && isPromptResponseForCurrentRequest(resolved, candidateOptions) ? resolved : null;
|
|
461
|
+
if (lastStatus?.type === "idle" && (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved, structured))) throw createMessageAbortedError();
|
|
462
|
+
if (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved, structured)) {
|
|
463
|
+
const timeoutReason = Date.now() >= deadlineAt ? "max-wait" : "recovery-inactivity";
|
|
464
|
+
const timeoutMs = timeoutReason === "max-wait" ? this.promptTimeoutPolicy.waitTimeoutMs : this.promptTimeoutPolicy.recoveryInactivityTimeoutMs;
|
|
463
465
|
const error = createOpenCodePromptTimeoutError({
|
|
464
466
|
sessionId: input.sessionId,
|
|
465
467
|
stage: "final-scan",
|
|
466
|
-
timeoutMs
|
|
468
|
+
timeoutMs,
|
|
467
469
|
messageId: messageId ?? void 0
|
|
468
470
|
});
|
|
469
|
-
this.
|
|
471
|
+
this.logPromptRequest("warn", {
|
|
472
|
+
lastProgressAt,
|
|
473
|
+
messageId: messageId ?? void 0,
|
|
470
474
|
sessionId: input.sessionId,
|
|
471
475
|
stage: "final-scan",
|
|
472
|
-
timeoutMs
|
|
473
|
-
|
|
474
|
-
});
|
|
476
|
+
timeoutMs,
|
|
477
|
+
timeoutReason
|
|
478
|
+
}, "OpenCode prompt recovery timed out");
|
|
475
479
|
throw error;
|
|
476
480
|
}
|
|
477
|
-
return
|
|
481
|
+
return requestScopedResolved;
|
|
478
482
|
}
|
|
479
|
-
async fetchPromptMessage(sessionId, messageId) {
|
|
483
|
+
async fetchPromptMessage(sessionId, messageId, signal) {
|
|
480
484
|
try {
|
|
481
485
|
return await this.runPromptRequestWithTimeout({
|
|
482
486
|
sessionId,
|
|
483
487
|
stage: "poll-message",
|
|
484
|
-
timeoutMs: this.
|
|
488
|
+
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs,
|
|
485
489
|
messageId
|
|
486
|
-
}, async (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
path: {
|
|
490
|
+
}, async (requestSignal) => {
|
|
491
|
+
return normalizePromptResponse(await this.callScopedSdkMethod("session", "message", {
|
|
492
|
+
parameters: {
|
|
490
493
|
sessionID: sessionId,
|
|
491
494
|
messageID: messageId
|
|
492
495
|
},
|
|
493
|
-
signal
|
|
496
|
+
signal: requestSignal
|
|
494
497
|
}));
|
|
495
|
-
|
|
496
|
-
return normalizePromptResponse(unwrapSdkData(await this.client.session.message({
|
|
497
|
-
sessionID: sessionId,
|
|
498
|
-
messageID: messageId
|
|
499
|
-
}, {
|
|
500
|
-
...SDK_OPTIONS,
|
|
501
|
-
signal
|
|
502
|
-
})));
|
|
503
|
-
});
|
|
498
|
+
}, signal);
|
|
504
499
|
} catch (error) {
|
|
500
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
505
501
|
this.logPromptRequestFailure(error, {
|
|
506
502
|
sessionId,
|
|
507
503
|
stage: "poll-message",
|
|
508
|
-
timeoutMs: this.
|
|
504
|
+
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs,
|
|
509
505
|
messageId
|
|
510
506
|
});
|
|
511
507
|
return null;
|
|
512
508
|
}
|
|
513
509
|
}
|
|
514
|
-
async captureKnownMessageIds(sessionId) {
|
|
515
|
-
const messages = await this.fetchRecentPromptMessages(sessionId, "capture-known-messages");
|
|
510
|
+
async captureKnownMessageIds(sessionId, signal) {
|
|
511
|
+
const messages = await this.fetchRecentPromptMessages(sessionId, "capture-known-messages", signal);
|
|
516
512
|
if (!messages) return /* @__PURE__ */ new Set();
|
|
517
513
|
return new Set(messages.map((message) => extractMessageId(message.info)).filter((id) => typeof id === "string" && id.length > 0));
|
|
518
514
|
}
|
|
519
|
-
async fetchRecentPromptMessages(sessionId, stage) {
|
|
515
|
+
async fetchRecentPromptMessages(sessionId, stage, signal) {
|
|
520
516
|
try {
|
|
521
517
|
return await this.runPromptRequestWithTimeout({
|
|
522
518
|
sessionId,
|
|
523
519
|
stage,
|
|
524
|
-
timeoutMs: this.
|
|
525
|
-
}, async (
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
520
|
+
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
521
|
+
}, async (requestSignal) => {
|
|
522
|
+
return normalizePromptResponses(await this.callScopedSdkMethod("session", "messages", {
|
|
523
|
+
parameters: {
|
|
524
|
+
sessionID: sessionId,
|
|
525
|
+
limit: PROMPT_MESSAGE_POLL_LIMIT
|
|
526
|
+
},
|
|
527
|
+
signal: requestSignal
|
|
531
528
|
}));
|
|
532
|
-
|
|
533
|
-
return normalizePromptResponses(unwrapSdkData(await this.client.session.messages({
|
|
534
|
-
sessionID: sessionId,
|
|
535
|
-
limit: PROMPT_MESSAGE_POLL_LIMIT
|
|
536
|
-
}, {
|
|
537
|
-
...SDK_OPTIONS,
|
|
538
|
-
signal
|
|
539
|
-
})));
|
|
540
|
-
});
|
|
529
|
+
}, signal);
|
|
541
530
|
} catch (error) {
|
|
531
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
542
532
|
this.logPromptRequestFailure(error, {
|
|
543
533
|
sessionId,
|
|
544
534
|
stage,
|
|
545
|
-
timeoutMs: this.
|
|
535
|
+
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
546
536
|
});
|
|
547
537
|
return null;
|
|
548
538
|
}
|
|
549
539
|
}
|
|
550
|
-
async fetchPromptSessionStatus(sessionId) {
|
|
540
|
+
async fetchPromptSessionStatus(sessionId, signal) {
|
|
551
541
|
try {
|
|
552
542
|
return (await this.runPromptRequestWithTimeout({
|
|
553
543
|
sessionId,
|
|
554
544
|
stage: "poll-status",
|
|
555
|
-
timeoutMs: this.
|
|
556
|
-
}, async (
|
|
545
|
+
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
546
|
+
}, async (requestSignal) => this.loadSessionStatuses(requestSignal), signal))[sessionId] ?? null;
|
|
557
547
|
} catch (error) {
|
|
548
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
558
549
|
this.logPromptRequestFailure(error, {
|
|
559
550
|
sessionId,
|
|
560
551
|
stage: "poll-status",
|
|
561
|
-
timeoutMs: this.
|
|
552
|
+
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
562
553
|
});
|
|
563
554
|
return null;
|
|
564
555
|
}
|
|
565
556
|
}
|
|
566
|
-
async findLatestPromptResponse(sessionId, options, stage) {
|
|
567
|
-
const messages = await this.fetchRecentPromptMessages(sessionId, stage);
|
|
557
|
+
async findLatestPromptResponse(sessionId, options, stage, signal) {
|
|
558
|
+
const messages = await this.fetchRecentPromptMessages(sessionId, stage, signal);
|
|
568
559
|
if (!messages || messages.length === 0) return null;
|
|
569
560
|
return selectPromptResponseCandidate(messages, options);
|
|
570
561
|
}
|
|
@@ -580,12 +571,10 @@ var OpenCodeClient = class {
|
|
|
580
571
|
return models;
|
|
581
572
|
}
|
|
582
573
|
async loadConfig() {
|
|
583
|
-
|
|
584
|
-
return unwrapSdkData(await this.client.config.get(void 0, SDK_OPTIONS));
|
|
574
|
+
return this.callScopedSdkMethod("config", "get", {});
|
|
585
575
|
}
|
|
586
576
|
async loadProviderCatalog() {
|
|
587
|
-
|
|
588
|
-
return unwrapSdkData(await this.client.config.providers(void 0, SDK_OPTIONS));
|
|
577
|
+
return this.callScopedSdkMethod("config", "providers", {});
|
|
589
578
|
}
|
|
590
579
|
async sendPromptRequest(input, parts) {
|
|
591
580
|
const requestBody = {
|
|
@@ -604,62 +593,50 @@ var OpenCodeClient = class {
|
|
|
604
593
|
await this.runPromptRequestWithTimeout({
|
|
605
594
|
sessionId: input.sessionId,
|
|
606
595
|
stage: "send-prompt",
|
|
607
|
-
timeoutMs: this.
|
|
596
|
+
timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
|
|
608
597
|
}, async (signal) => {
|
|
609
|
-
await this.
|
|
610
|
-
|
|
598
|
+
await this.callScopedSdkMethod("session", "promptAsync", {
|
|
599
|
+
parameters: requestParameters,
|
|
611
600
|
signal
|
|
612
601
|
});
|
|
613
|
-
});
|
|
614
|
-
return
|
|
602
|
+
}, input.signal);
|
|
603
|
+
return;
|
|
615
604
|
}
|
|
616
|
-
return await this.runPromptRequestWithTimeout({
|
|
617
|
-
sessionId: input.sessionId,
|
|
618
|
-
stage: "send-prompt",
|
|
619
|
-
timeoutMs: this.promptRequestTimeouts.sendMs
|
|
620
|
-
}, async (signal) => {
|
|
621
|
-
if (hasRawSdkMethod(this.client, "post")) return normalizePromptResponse(await this.requestRaw("post", {
|
|
622
|
-
url: "/session/{sessionID}/message",
|
|
623
|
-
path: { sessionID: input.sessionId },
|
|
624
|
-
body: requestBody,
|
|
625
|
-
signal
|
|
626
|
-
}));
|
|
627
|
-
return normalizePromptResponse(unwrapSdkData(await this.client.session.prompt(requestParameters, {
|
|
628
|
-
...SDK_OPTIONS,
|
|
629
|
-
signal
|
|
630
|
-
})));
|
|
631
|
-
});
|
|
632
605
|
} catch (error) {
|
|
633
606
|
this.logPromptRequestFailure(error, {
|
|
634
607
|
sessionId: input.sessionId,
|
|
635
608
|
stage: "send-prompt",
|
|
636
|
-
timeoutMs: this.
|
|
609
|
+
timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
|
|
637
610
|
});
|
|
638
611
|
throw error;
|
|
639
612
|
}
|
|
640
|
-
|
|
641
|
-
async requestRaw(method, options) {
|
|
642
|
-
const handler = getRawSdkClient(this.client)?.[method];
|
|
643
|
-
if (typeof handler !== "function") throw new Error(`OpenCode SDK client does not expose a compatible raw ${method.toUpperCase()} method.`);
|
|
644
|
-
return unwrapSdkData(await handler({
|
|
645
|
-
...SDK_OPTIONS,
|
|
646
|
-
...options
|
|
647
|
-
}));
|
|
613
|
+
throw new Error("OpenCode SDK client does not expose session.promptAsync().");
|
|
648
614
|
}
|
|
649
615
|
async loadSessionStatuses(signal) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
616
|
+
return this.callScopedSdkMethod("session", "status", {
|
|
617
|
+
signal,
|
|
618
|
+
parameters: void 0
|
|
653
619
|
});
|
|
654
|
-
return unwrapSdkData(await this.client.session.status(void 0, {
|
|
655
|
-
...SDK_OPTIONS,
|
|
656
|
-
...signal ? { signal } : {}
|
|
657
|
-
}));
|
|
658
620
|
}
|
|
659
|
-
async runPromptRequestWithTimeout(input, operation) {
|
|
621
|
+
async runPromptRequestWithTimeout(input, operation, signal) {
|
|
660
622
|
const startedAt = Date.now();
|
|
661
623
|
const controller = new AbortController();
|
|
624
|
+
const requestSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
662
625
|
let timeoutHandle = null;
|
|
626
|
+
let removeAbortListener = () => void 0;
|
|
627
|
+
const abortPromise = signal ? new Promise((_, reject) => {
|
|
628
|
+
const onAbort = () => {
|
|
629
|
+
reject(normalizeAbortReason(signal.reason));
|
|
630
|
+
};
|
|
631
|
+
if (signal.aborted) {
|
|
632
|
+
onAbort();
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
636
|
+
removeAbortListener = () => {
|
|
637
|
+
signal.removeEventListener("abort", onAbort);
|
|
638
|
+
};
|
|
639
|
+
}) : null;
|
|
663
640
|
const timeoutPromise = new Promise((_, reject) => {
|
|
664
641
|
timeoutHandle = setTimeout(() => {
|
|
665
642
|
reject(createOpenCodePromptTimeoutError({
|
|
@@ -673,14 +650,20 @@ var OpenCodeClient = class {
|
|
|
673
650
|
}, input.timeoutMs);
|
|
674
651
|
});
|
|
675
652
|
try {
|
|
676
|
-
return await Promise.race([
|
|
653
|
+
return await Promise.race([
|
|
654
|
+
operation(requestSignal),
|
|
655
|
+
timeoutPromise,
|
|
656
|
+
...abortPromise ? [abortPromise] : []
|
|
657
|
+
]);
|
|
677
658
|
} finally {
|
|
678
659
|
if (timeoutHandle !== null) clearTimeout(timeoutHandle);
|
|
660
|
+
removeAbortListener();
|
|
679
661
|
}
|
|
680
662
|
}
|
|
681
663
|
logPromptRequestFailure(error, input) {
|
|
682
664
|
if (error instanceof OpenCodePromptTimeoutError) {
|
|
683
665
|
this.logPromptRequest("warn", {
|
|
666
|
+
endpointKind: resolvePromptEndpointKind(error.data.stage),
|
|
684
667
|
elapsedMs: error.data.elapsedMs,
|
|
685
668
|
messageId: error.data.messageId,
|
|
686
669
|
sessionId: error.data.sessionId,
|
|
@@ -690,6 +673,7 @@ var OpenCodeClient = class {
|
|
|
690
673
|
return;
|
|
691
674
|
}
|
|
692
675
|
this.logPromptRequest("warn", {
|
|
676
|
+
endpointKind: resolvePromptEndpointKind(input.stage),
|
|
693
677
|
error,
|
|
694
678
|
messageId: input.messageId ?? void 0,
|
|
695
679
|
sessionId: input.sessionId,
|
|
@@ -708,8 +692,8 @@ var OpenCodeClient = class {
|
|
|
708
692
|
}).catch(() => void 0);
|
|
709
693
|
}
|
|
710
694
|
};
|
|
711
|
-
function createOpenCodeClientFromSdkClient(client, fetchFn = fetch) {
|
|
712
|
-
return new OpenCodeClient(void 0, client, fetchFn);
|
|
695
|
+
function createOpenCodeClientFromSdkClient(client, fetchFn = fetch, promptTimeoutPolicy = {}) {
|
|
696
|
+
return new OpenCodeClient(void 0, client, fetchFn, promptTimeoutPolicy);
|
|
713
697
|
}
|
|
714
698
|
function buildSelectableModels(config, providers, providerAvailability = /* @__PURE__ */ new Map()) {
|
|
715
699
|
const configuredProviders = config.provider ?? {};
|
|
@@ -816,7 +800,14 @@ function shouldPollPromptMessage(data, structured) {
|
|
|
816
800
|
const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
|
|
817
801
|
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
818
802
|
const hasAssistantError = !!assistantInfo?.error;
|
|
819
|
-
|
|
803
|
+
const isCompleted = isAssistantMessageCompleted(assistantInfo);
|
|
804
|
+
return !hasText && !bodyMd && !hasAssistantError && !isCompleted;
|
|
805
|
+
}
|
|
806
|
+
function shouldReturnPromptResponseImmediately(data, structured) {
|
|
807
|
+
return !shouldPollPromptMessage(data, structured) && !isCompletedEmptyPromptResponse(data, structured);
|
|
808
|
+
}
|
|
809
|
+
function isPromptResponseUsable(data, structured) {
|
|
810
|
+
return !shouldPollPromptMessage(data, structured) && !isCompletedEmptyPromptResponse(data, structured);
|
|
820
811
|
}
|
|
821
812
|
function normalizePromptResponse(response) {
|
|
822
813
|
return {
|
|
@@ -875,8 +866,26 @@ function extractMessageId(message) {
|
|
|
875
866
|
if (!isPlainRecord(message)) return null;
|
|
876
867
|
return typeof message.id === "string" && message.id.trim().length > 0 ? message.id : null;
|
|
877
868
|
}
|
|
878
|
-
function delay(ms) {
|
|
879
|
-
return new Promise((resolve) =>
|
|
869
|
+
function delay(ms, signal) {
|
|
870
|
+
return new Promise((resolve, reject) => {
|
|
871
|
+
const handle = setTimeout(() => {
|
|
872
|
+
signal?.removeEventListener("abort", onAbort);
|
|
873
|
+
resolve();
|
|
874
|
+
}, ms);
|
|
875
|
+
const onAbort = () => {
|
|
876
|
+
clearTimeout(handle);
|
|
877
|
+
signal?.removeEventListener("abort", onAbort);
|
|
878
|
+
reject(normalizeAbortReason(signal?.reason));
|
|
879
|
+
};
|
|
880
|
+
if (signal?.aborted) {
|
|
881
|
+
onAbort();
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
function didPromptResponseAdvance(previous, next, structured) {
|
|
888
|
+
return getPromptResponseProgressSignature(previous, structured) !== getPromptResponseProgressSignature(next, structured);
|
|
880
889
|
}
|
|
881
890
|
function createOpenCodePromptTimeoutError(input) {
|
|
882
891
|
return new OpenCodePromptTimeoutError({
|
|
@@ -885,6 +894,16 @@ function createOpenCodePromptTimeoutError(input) {
|
|
|
885
894
|
message: input.message ?? "The OpenCode host did not finish this request in time."
|
|
886
895
|
});
|
|
887
896
|
}
|
|
897
|
+
function resolvePromptEndpointKind(stage) {
|
|
898
|
+
switch (stage) {
|
|
899
|
+
case "capture-known-messages":
|
|
900
|
+
case "poll-messages":
|
|
901
|
+
case "final-scan": return "messages";
|
|
902
|
+
case "poll-message": return "message";
|
|
903
|
+
case "poll-status": return "status";
|
|
904
|
+
default: return "prompt";
|
|
905
|
+
}
|
|
906
|
+
}
|
|
888
907
|
function getPromptMessagePollDelayMs(attempt) {
|
|
889
908
|
return PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS[attempt] ?? PROMPT_MESSAGE_POLL_INTERVAL_MS;
|
|
890
909
|
}
|
|
@@ -940,11 +959,12 @@ function unwrapSdkData(response) {
|
|
|
940
959
|
if (response && typeof response === "object" && "data" in response) return response.data;
|
|
941
960
|
return response;
|
|
942
961
|
}
|
|
943
|
-
function
|
|
944
|
-
return
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
962
|
+
function resolvePromptTimeoutPolicy(input) {
|
|
963
|
+
return {
|
|
964
|
+
pollRequestTimeoutMs: input.pollRequestTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.pollRequestTimeoutMs,
|
|
965
|
+
recoveryInactivityTimeoutMs: input.recoveryInactivityTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.recoveryInactivityTimeoutMs,
|
|
966
|
+
waitTimeoutMs: input.waitTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.waitTimeoutMs
|
|
967
|
+
};
|
|
948
968
|
}
|
|
949
969
|
function normalizeAssistantError(value) {
|
|
950
970
|
if (!isPlainRecord(value) || typeof value.name !== "string" || value.name.trim().length === 0) return;
|
|
@@ -954,6 +974,15 @@ function normalizeAssistantError(value) {
|
|
|
954
974
|
...isPlainRecord(value.data) ? { data: value.data } : {}
|
|
955
975
|
};
|
|
956
976
|
}
|
|
977
|
+
function isAssistantMessageCompleted(message) {
|
|
978
|
+
return !!message?.error || typeof message?.time?.completed === "number" || typeof message?.finish === "string" && message.finish.trim().length > 0;
|
|
979
|
+
}
|
|
980
|
+
function isCompletedEmptyPromptResponse(data, structured) {
|
|
981
|
+
const assistantInfo = toAssistantMessage(data.info);
|
|
982
|
+
const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
|
|
983
|
+
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
984
|
+
return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText && !bodyMd;
|
|
985
|
+
}
|
|
957
986
|
function extractStructuredPayload(message) {
|
|
958
987
|
if (!isPlainRecord(message)) return null;
|
|
959
988
|
if ("structured" in message && message.structured !== void 0) return message.structured;
|
|
@@ -968,7 +997,7 @@ function selectPromptResponseCandidate(candidates, options) {
|
|
|
968
997
|
function comparePromptResponseCandidates(left, right, options) {
|
|
969
998
|
const leftRank = getPromptResponseCandidateRank(left, options);
|
|
970
999
|
const rightRank = getPromptResponseCandidateRank(right, options);
|
|
971
|
-
return Number(rightRank.
|
|
1000
|
+
return Number(rightRank.isInitial) - Number(leftRank.isInitial) || Number(rightRank.sharesParent) - Number(leftRank.sharesParent) || Number(rightRank.isNewSinceRequestStart) - Number(leftRank.isNewSinceRequestStart) || Number(rightRank.isUsable) - Number(leftRank.isUsable) || rightRank.createdAt - leftRank.createdAt;
|
|
972
1001
|
}
|
|
973
1002
|
function getPromptResponseCandidateRank(message, options) {
|
|
974
1003
|
const assistant = toAssistantMessage(message.info);
|
|
@@ -978,25 +1007,57 @@ function getPromptResponseCandidateRank(message, options) {
|
|
|
978
1007
|
createdAt,
|
|
979
1008
|
isInitial: !!id && id === options.initialMessageId,
|
|
980
1009
|
isNewSinceRequestStart: isPromptResponseNewSinceRequestStart(id, createdAt, options.knownMessageIds, options.requestStartedAt),
|
|
981
|
-
isUsable:
|
|
1010
|
+
isUsable: isPromptResponseUsable(message, options.structured),
|
|
982
1011
|
sharesParent: !!assistant?.parentID && assistant.parentID === options.initialParentId
|
|
983
1012
|
};
|
|
984
1013
|
}
|
|
985
1014
|
function resolvePromptCandidateStartTime(startedAt, initialMessage) {
|
|
986
|
-
if (!initialMessage) return
|
|
1015
|
+
if (!initialMessage) return startedAt;
|
|
987
1016
|
const initialCreatedAt = coerceFiniteNumber(toAssistantMessage(initialMessage.info)?.time?.created);
|
|
988
1017
|
if (initialCreatedAt === null) return startedAt;
|
|
989
1018
|
return areComparablePromptTimestamps(startedAt, initialCreatedAt) ? startedAt : initialCreatedAt;
|
|
990
1019
|
}
|
|
1020
|
+
function getPromptResponseProgressSignature(response, structured) {
|
|
1021
|
+
if (!response) return "null";
|
|
1022
|
+
const assistant = toAssistantMessage(response.info);
|
|
1023
|
+
const responseParts = Array.isArray(response.parts) ? response.parts : [];
|
|
1024
|
+
return JSON.stringify({
|
|
1025
|
+
assistantError: assistant?.error?.name ?? null,
|
|
1026
|
+
bodyMd: structured ? extractStructuredMarkdown(extractStructuredPayload(assistant)) : null,
|
|
1027
|
+
completedAt: assistant?.time?.completed ?? null,
|
|
1028
|
+
finish: assistant?.finish ?? null,
|
|
1029
|
+
id: assistant?.id ?? null,
|
|
1030
|
+
partCount: responseParts.length,
|
|
1031
|
+
text: extractTextFromParts(responseParts)
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
991
1034
|
function isPromptResponseNewSinceRequestStart(messageId, createdAt, knownMessageIds, requestStartedAt) {
|
|
992
1035
|
if (!messageId || knownMessageIds.has(messageId)) return false;
|
|
993
1036
|
if (requestStartedAt === null) return true;
|
|
994
1037
|
return createdAt >= requestStartedAt;
|
|
995
1038
|
}
|
|
1039
|
+
function isPromptResponseForCurrentRequest(response, options) {
|
|
1040
|
+
const rank = getPromptResponseCandidateRank(response, options);
|
|
1041
|
+
return rank.isInitial || rank.sharesParent || rank.isNewSinceRequestStart;
|
|
1042
|
+
}
|
|
996
1043
|
function areComparablePromptTimestamps(left, right) {
|
|
997
1044
|
const epochThresholdMs = 0xe8d4a51000;
|
|
998
1045
|
return left >= epochThresholdMs && right >= epochThresholdMs;
|
|
999
1046
|
}
|
|
1047
|
+
function isPromptRequestAbort(error) {
|
|
1048
|
+
return error instanceof OpenCodeMessageAbortedError || error instanceof Error && error.name === "AbortError" || isNamedAbortError(error);
|
|
1049
|
+
}
|
|
1050
|
+
function isNamedAbortError(error) {
|
|
1051
|
+
return !!error && typeof error === "object" && "name" in error && error.name === "MessageAbortedError";
|
|
1052
|
+
}
|
|
1053
|
+
function normalizeAbortReason(reason) {
|
|
1054
|
+
if (reason instanceof Error || isNamedAbortError(reason)) return reason;
|
|
1055
|
+
return createMessageAbortedError();
|
|
1056
|
+
}
|
|
1057
|
+
function throwIfAborted(signal) {
|
|
1058
|
+
if (!signal?.aborted) return;
|
|
1059
|
+
throw normalizeAbortReason(signal.reason);
|
|
1060
|
+
}
|
|
1000
1061
|
function isPlainRecord(value) {
|
|
1001
1062
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1002
1063
|
}
|
|
@@ -1164,85 +1225,119 @@ function extractErrorMessage(error) {
|
|
|
1164
1225
|
//#endregion
|
|
1165
1226
|
//#region src/services/session-activity/foreground-session-tracker.ts
|
|
1166
1227
|
var ForegroundSessionTracker = class {
|
|
1167
|
-
|
|
1168
|
-
counts = /* @__PURE__ */ new Map();
|
|
1228
|
+
requests = /* @__PURE__ */ new Map();
|
|
1169
1229
|
sessionChats = /* @__PURE__ */ new Map();
|
|
1230
|
+
acquire(chatId) {
|
|
1231
|
+
if (this.requests.has(chatId)) return null;
|
|
1232
|
+
const state = {
|
|
1233
|
+
chatId,
|
|
1234
|
+
controller: new AbortController(),
|
|
1235
|
+
sessionId: null
|
|
1236
|
+
};
|
|
1237
|
+
this.requests.set(chatId, state);
|
|
1238
|
+
return {
|
|
1239
|
+
signal: state.controller.signal,
|
|
1240
|
+
attachSession: (sessionId) => {
|
|
1241
|
+
this.attachSession(chatId, sessionId);
|
|
1242
|
+
},
|
|
1243
|
+
dispose: () => {
|
|
1244
|
+
this.release(chatId);
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
abort(chatId, reason = createMessageAbortedError()) {
|
|
1249
|
+
const state = this.requests.get(chatId);
|
|
1250
|
+
if (!state) return false;
|
|
1251
|
+
if (!state.controller.signal.aborted) state.controller.abort(reason);
|
|
1252
|
+
return true;
|
|
1253
|
+
}
|
|
1170
1254
|
begin(chatId, sessionId) {
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1255
|
+
const lease = this.acquire(chatId);
|
|
1256
|
+
if (!lease) return () => void 0;
|
|
1257
|
+
lease.attachSession(sessionId);
|
|
1174
1258
|
return () => {
|
|
1175
|
-
|
|
1259
|
+
lease.dispose();
|
|
1176
1260
|
};
|
|
1177
1261
|
}
|
|
1178
1262
|
clear(sessionId) {
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const stack = this.chatStacks.get(chatId);
|
|
1185
|
-
if (!stack) continue;
|
|
1186
|
-
const nextStack = stack.filter((trackedSessionId) => trackedSessionId !== sessionId);
|
|
1187
|
-
if (nextStack.length === 0) {
|
|
1188
|
-
this.chatStacks.delete(chatId);
|
|
1189
|
-
continue;
|
|
1190
|
-
}
|
|
1191
|
-
this.chatStacks.set(chatId, nextStack);
|
|
1263
|
+
const chatIds = this.listChatIds(sessionId);
|
|
1264
|
+
if (chatIds.length === 0) return false;
|
|
1265
|
+
for (const chatId of chatIds) {
|
|
1266
|
+
const state = this.requests.get(chatId);
|
|
1267
|
+
if (state?.sessionId === sessionId) state.sessionId = null;
|
|
1192
1268
|
}
|
|
1193
|
-
|
|
1269
|
+
this.sessionChats.delete(sessionId);
|
|
1270
|
+
return true;
|
|
1271
|
+
}
|
|
1272
|
+
fail(sessionId, error) {
|
|
1273
|
+
const chatIds = this.listChatIds(sessionId);
|
|
1274
|
+
if (chatIds.length === 0) return false;
|
|
1275
|
+
this.clear(sessionId);
|
|
1276
|
+
for (const chatId of chatIds) this.abort(chatId, error);
|
|
1277
|
+
return true;
|
|
1194
1278
|
}
|
|
1195
1279
|
getActiveSessionId(chatId) {
|
|
1196
|
-
return this.
|
|
1280
|
+
return this.requests.get(chatId)?.sessionId ?? null;
|
|
1281
|
+
}
|
|
1282
|
+
hasActiveRequest(chatId) {
|
|
1283
|
+
return this.requests.has(chatId);
|
|
1197
1284
|
}
|
|
1198
1285
|
isForeground(sessionId) {
|
|
1199
|
-
return this.
|
|
1286
|
+
return this.sessionChats.has(sessionId);
|
|
1200
1287
|
}
|
|
1201
1288
|
listChatIds(sessionId) {
|
|
1202
|
-
return [...this.sessionChats.get(sessionId)
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
this.
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1289
|
+
return [...this.sessionChats.get(sessionId) ?? /* @__PURE__ */ new Set()];
|
|
1290
|
+
}
|
|
1291
|
+
attachSession(chatId, sessionId) {
|
|
1292
|
+
const normalizedSessionId = sessionId.trim();
|
|
1293
|
+
const state = this.requests.get(chatId);
|
|
1294
|
+
if (!state || normalizedSessionId.length === 0) return;
|
|
1295
|
+
if (state.sessionId === normalizedSessionId) return;
|
|
1296
|
+
if (state.sessionId) this.detachChatFromSession(chatId, state.sessionId);
|
|
1297
|
+
state.sessionId = normalizedSessionId;
|
|
1298
|
+
const chatIds = this.sessionChats.get(normalizedSessionId) ?? /* @__PURE__ */ new Set();
|
|
1299
|
+
chatIds.add(chatId);
|
|
1300
|
+
this.sessionChats.set(normalizedSessionId, chatIds);
|
|
1301
|
+
}
|
|
1302
|
+
release(chatId) {
|
|
1303
|
+
const state = this.requests.get(chatId);
|
|
1304
|
+
if (!state) return;
|
|
1305
|
+
if (state.sessionId) this.detachChatFromSession(chatId, state.sessionId);
|
|
1306
|
+
this.requests.delete(chatId);
|
|
1307
|
+
}
|
|
1308
|
+
detachChatFromSession(chatId, sessionId) {
|
|
1309
|
+
const chatIds = this.sessionChats.get(sessionId);
|
|
1310
|
+
if (!chatIds) return;
|
|
1311
|
+
chatIds.delete(chatId);
|
|
1312
|
+
if (chatIds.size === 0) {
|
|
1313
|
+
this.sessionChats.delete(sessionId);
|
|
1314
|
+
return;
|
|
1226
1315
|
}
|
|
1227
|
-
|
|
1228
|
-
if (!chatCounts) return;
|
|
1229
|
-
const currentCount = chatCounts.get(chatId) ?? 0;
|
|
1230
|
-
if (currentCount <= 1) chatCounts.delete(chatId);
|
|
1231
|
-
else chatCounts.set(chatId, currentCount - 1);
|
|
1232
|
-
if (chatCounts.size === 0) this.sessionChats.delete(sessionId);
|
|
1233
|
-
else this.sessionChats.set(sessionId, chatCounts);
|
|
1316
|
+
this.sessionChats.set(sessionId, chatIds);
|
|
1234
1317
|
}
|
|
1235
1318
|
};
|
|
1236
1319
|
var NOOP_FOREGROUND_SESSION_TRACKER = {
|
|
1320
|
+
acquire() {
|
|
1321
|
+
return null;
|
|
1322
|
+
},
|
|
1323
|
+
abort() {
|
|
1324
|
+
return false;
|
|
1325
|
+
},
|
|
1237
1326
|
begin() {
|
|
1238
1327
|
return () => void 0;
|
|
1239
1328
|
},
|
|
1240
1329
|
clear() {
|
|
1241
1330
|
return false;
|
|
1242
1331
|
},
|
|
1332
|
+
fail() {
|
|
1333
|
+
return false;
|
|
1334
|
+
},
|
|
1243
1335
|
getActiveSessionId() {
|
|
1244
1336
|
return null;
|
|
1245
1337
|
},
|
|
1338
|
+
hasActiveRequest() {
|
|
1339
|
+
return false;
|
|
1340
|
+
},
|
|
1246
1341
|
isForeground() {
|
|
1247
1342
|
return false;
|
|
1248
1343
|
},
|
|
@@ -1259,23 +1354,40 @@ var AbortPromptUseCase = class {
|
|
|
1259
1354
|
this.foregroundSessionTracker = foregroundSessionTracker;
|
|
1260
1355
|
}
|
|
1261
1356
|
async execute(input) {
|
|
1357
|
+
const hasForegroundRequest = this.foregroundSessionTracker.hasActiveRequest(input.chatId);
|
|
1262
1358
|
const activeSessionId = this.foregroundSessionTracker.getActiveSessionId(input.chatId);
|
|
1263
1359
|
const binding = activeSessionId ? null : await this.sessionRepo.getByChatId(input.chatId);
|
|
1264
1360
|
const sessionId = activeSessionId ?? binding?.sessionId ?? null;
|
|
1265
|
-
if (!sessionId) return {
|
|
1361
|
+
if (!hasForegroundRequest && !sessionId) return {
|
|
1266
1362
|
sessionId: null,
|
|
1267
1363
|
status: "no_session",
|
|
1268
1364
|
sessionStatus: null
|
|
1269
1365
|
};
|
|
1270
|
-
const
|
|
1366
|
+
const sessionStatuses = sessionId ? await this.opencodeClient.getSessionStatuses() : {};
|
|
1367
|
+
const sessionStatus = sessionId ? sessionStatuses[sessionId] ?? null : null;
|
|
1368
|
+
if (hasForegroundRequest) {
|
|
1369
|
+
if (sessionId && sessionStatus && sessionStatus.type !== "idle") await this.opencodeClient.abortSession(sessionId);
|
|
1370
|
+
this.foregroundSessionTracker.abort(input.chatId, createMessageAbortedError());
|
|
1371
|
+
return {
|
|
1372
|
+
sessionId,
|
|
1373
|
+
status: "aborted",
|
|
1374
|
+
sessionStatus
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1271
1377
|
if (!sessionStatus || sessionStatus.type === "idle") return {
|
|
1272
1378
|
sessionId,
|
|
1273
1379
|
status: "not_running",
|
|
1274
1380
|
sessionStatus
|
|
1275
1381
|
};
|
|
1382
|
+
if (!sessionId) return {
|
|
1383
|
+
sessionId: null,
|
|
1384
|
+
status: "not_running",
|
|
1385
|
+
sessionStatus
|
|
1386
|
+
};
|
|
1387
|
+
const runningSessionId = sessionId;
|
|
1276
1388
|
return {
|
|
1277
|
-
sessionId,
|
|
1278
|
-
status: await this.opencodeClient.abortSession(
|
|
1389
|
+
sessionId: runningSessionId,
|
|
1390
|
+
status: await this.opencodeClient.abortSession(runningSessionId) ? "aborted" : "not_running",
|
|
1279
1391
|
sessionStatus
|
|
1280
1392
|
};
|
|
1281
1393
|
}
|
|
@@ -1717,11 +1829,10 @@ var RenameSessionUseCase = class {
|
|
|
1717
1829
|
//#endregion
|
|
1718
1830
|
//#region src/use-cases/send-prompt.usecase.ts
|
|
1719
1831
|
var SendPromptUseCase = class {
|
|
1720
|
-
constructor(sessionRepo, opencodeClient, logger
|
|
1832
|
+
constructor(sessionRepo, opencodeClient, logger) {
|
|
1721
1833
|
this.sessionRepo = sessionRepo;
|
|
1722
1834
|
this.opencodeClient = opencodeClient;
|
|
1723
1835
|
this.logger = logger;
|
|
1724
|
-
this.foregroundSessionTracker = foregroundSessionTracker;
|
|
1725
1836
|
}
|
|
1726
1837
|
async execute(input) {
|
|
1727
1838
|
const files = input.files ?? [];
|
|
@@ -1775,7 +1886,7 @@ var SendPromptUseCase = class {
|
|
|
1775
1886
|
}
|
|
1776
1887
|
const temporarySessionId = shouldIsolateImageTurn ? await this.createTemporaryImageSession(input.chatId, activeBinding.sessionId) : null;
|
|
1777
1888
|
const executionSessionId = temporarySessionId ?? activeBinding.sessionId;
|
|
1778
|
-
|
|
1889
|
+
input.onExecutionSession?.(executionSessionId);
|
|
1779
1890
|
let result;
|
|
1780
1891
|
try {
|
|
1781
1892
|
result = await this.opencodeClient.promptSession({
|
|
@@ -1785,10 +1896,10 @@ var SendPromptUseCase = class {
|
|
|
1785
1896
|
...selectedAgent ? { agent: selectedAgent.name } : {},
|
|
1786
1897
|
structured: true,
|
|
1787
1898
|
...model ? { model } : {},
|
|
1899
|
+
...input.signal ? { signal: input.signal } : {},
|
|
1788
1900
|
...activeBinding.modelVariant ? { variant: activeBinding.modelVariant } : {}
|
|
1789
1901
|
});
|
|
1790
1902
|
} finally {
|
|
1791
|
-
endForegroundSession();
|
|
1792
1903
|
if (temporarySessionId) await this.cleanupTemporaryImageSession(input.chatId, activeBinding.sessionId, temporarySessionId);
|
|
1793
1904
|
}
|
|
1794
1905
|
await this.sessionRepo.touch(input.chatId);
|
|
@@ -2025,7 +2136,11 @@ function resolveExtension(mimeType) {
|
|
|
2025
2136
|
//#region src/app/container.ts
|
|
2026
2137
|
function createAppContainer(config, client) {
|
|
2027
2138
|
const logger = createOpenCodeAppLogger(client, { level: config.logLevel });
|
|
2028
|
-
return createContainer(config, createOpenCodeClientFromSdkClient(client
|
|
2139
|
+
return createContainer(config, createOpenCodeClientFromSdkClient(client, fetch, {
|
|
2140
|
+
waitTimeoutMs: config.promptWaitTimeoutMs,
|
|
2141
|
+
pollRequestTimeoutMs: config.promptPollRequestTimeoutMs,
|
|
2142
|
+
recoveryInactivityTimeoutMs: config.promptRecoveryInactivityTimeoutMs
|
|
2143
|
+
}), logger);
|
|
2029
2144
|
}
|
|
2030
2145
|
function createContainer(config, opencodeClient, logger) {
|
|
2031
2146
|
const stateStore = new JsonStateStore({
|
|
@@ -2052,7 +2167,7 @@ function createContainer(config, opencodeClient, logger) {
|
|
|
2052
2167
|
const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo);
|
|
2053
2168
|
const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
|
|
2054
2169
|
const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, logger);
|
|
2055
|
-
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger
|
|
2170
|
+
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger);
|
|
2056
2171
|
const switchAgentUseCase = new SwitchAgentUseCase(sessionRepo, opencodeClient, logger);
|
|
2057
2172
|
const switchModelUseCase = new SwitchModelUseCase(sessionRepo, opencodeClient, logger);
|
|
2058
2173
|
const switchSessionUseCase = new SwitchSessionUseCase(sessionRepo, opencodeClient, logger);
|
|
@@ -2159,21 +2274,25 @@ function escapeMarkdownV2(value) {
|
|
|
2159
2274
|
async function handleTelegramBotPluginEvent(runtime, event) {
|
|
2160
2275
|
switch (event.type) {
|
|
2161
2276
|
case "permission.asked":
|
|
2162
|
-
await handlePermissionAsked(runtime, event
|
|
2277
|
+
await handlePermissionAsked(runtime, event);
|
|
2163
2278
|
return;
|
|
2164
2279
|
case "permission.replied":
|
|
2165
|
-
await handlePermissionReplied(runtime, event
|
|
2280
|
+
await handlePermissionReplied(runtime, event);
|
|
2166
2281
|
return;
|
|
2167
2282
|
case "session.error":
|
|
2168
|
-
await handleSessionError(runtime, event
|
|
2283
|
+
await handleSessionError(runtime, event);
|
|
2169
2284
|
return;
|
|
2170
2285
|
case "session.idle":
|
|
2171
|
-
await handleSessionIdle(runtime, event
|
|
2286
|
+
await handleSessionIdle(runtime, event);
|
|
2287
|
+
return;
|
|
2288
|
+
case "session.status":
|
|
2289
|
+
await handleSessionStatus(runtime, event);
|
|
2172
2290
|
return;
|
|
2173
2291
|
default: return;
|
|
2174
2292
|
}
|
|
2175
2293
|
}
|
|
2176
|
-
async function handlePermissionAsked(runtime,
|
|
2294
|
+
async function handlePermissionAsked(runtime, event) {
|
|
2295
|
+
const request = event.properties;
|
|
2177
2296
|
const bindings = await runtime.container.sessionRepo.listBySessionId(request.sessionID);
|
|
2178
2297
|
const chatIds = new Set([...bindings.map((binding) => binding.chatId), ...runtime.container.foregroundSessionTracker.listChatIds(request.sessionID)]);
|
|
2179
2298
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(request.id);
|
|
@@ -2202,7 +2321,9 @@ async function handlePermissionAsked(runtime, request) {
|
|
|
2202
2321
|
}
|
|
2203
2322
|
}
|
|
2204
2323
|
}
|
|
2205
|
-
async function handlePermissionReplied(runtime,
|
|
2324
|
+
async function handlePermissionReplied(runtime, event) {
|
|
2325
|
+
const requestId = event.properties.requestID;
|
|
2326
|
+
const reply = event.properties.reply;
|
|
2206
2327
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(requestId);
|
|
2207
2328
|
await Promise.all(approvals.map(async (approval) => {
|
|
2208
2329
|
try {
|
|
@@ -2217,27 +2338,37 @@ async function handlePermissionReplied(runtime, requestId, reply) {
|
|
|
2217
2338
|
await runtime.container.permissionApprovalRepo.set(toResolvedApproval(approval, reply));
|
|
2218
2339
|
}));
|
|
2219
2340
|
}
|
|
2220
|
-
async function handleSessionError(runtime,
|
|
2341
|
+
async function handleSessionError(runtime, event) {
|
|
2342
|
+
const sessionId = event.properties.sessionID;
|
|
2343
|
+
const error = event.properties.error;
|
|
2221
2344
|
if (!sessionId) {
|
|
2222
2345
|
runtime.container.logger.error({ error }, "session error received without a session id");
|
|
2223
2346
|
return;
|
|
2224
2347
|
}
|
|
2225
|
-
if (runtime.container.foregroundSessionTracker.
|
|
2348
|
+
if (runtime.container.foregroundSessionTracker.fail(sessionId, error ?? /* @__PURE__ */ new Error("Unknown session error."))) {
|
|
2226
2349
|
runtime.container.logger.warn({
|
|
2227
2350
|
error,
|
|
2228
2351
|
sessionId
|
|
2229
2352
|
}, "session error suppressed for foreground Telegram session");
|
|
2230
2353
|
return;
|
|
2231
2354
|
}
|
|
2232
|
-
await notifyBoundChats(runtime, sessionId, `Session failed.\n\nSession: ${sessionId}\nError: ${error?.data?.message
|
|
2355
|
+
await notifyBoundChats(runtime, sessionId, `Session failed.\n\nSession: ${sessionId}\nError: ${(typeof error?.data?.message === "string" ? error.data.message.trim() : "") || error?.name?.trim() || "Unknown session error."}`);
|
|
2233
2356
|
}
|
|
2234
|
-
async function handleSessionIdle(runtime,
|
|
2357
|
+
async function handleSessionIdle(runtime, event) {
|
|
2358
|
+
const sessionId = event.properties.sessionID;
|
|
2235
2359
|
if (runtime.container.foregroundSessionTracker.clear(sessionId)) {
|
|
2236
2360
|
runtime.container.logger.info({ sessionId }, "session idle notification suppressed for foreground Telegram session");
|
|
2237
2361
|
return;
|
|
2238
2362
|
}
|
|
2239
2363
|
await notifyBoundChats(runtime, sessionId, `Session finished.\n\nSession: ${sessionId}`);
|
|
2240
2364
|
}
|
|
2365
|
+
async function handleSessionStatus(runtime, event) {
|
|
2366
|
+
if (event.properties.status.type !== "idle") return;
|
|
2367
|
+
await handleSessionIdle(runtime, {
|
|
2368
|
+
type: "session.idle",
|
|
2369
|
+
properties: { sessionID: event.properties.sessionID }
|
|
2370
|
+
});
|
|
2371
|
+
}
|
|
2241
2372
|
async function notifyBoundChats(runtime, sessionId, text) {
|
|
2242
2373
|
const bindings = await runtime.container.sessionRepo.listBySessionId(sessionId);
|
|
2243
2374
|
const chatIds = [...new Set(bindings.map((binding) => binding.chatId))];
|
|
@@ -4478,8 +4609,8 @@ async function handlePermissionApprovalCallback(ctx, dependencies) {
|
|
|
4478
4609
|
const parsed = parsePermissionApprovalCallbackData(data);
|
|
4479
4610
|
if (!parsed) return;
|
|
4480
4611
|
try {
|
|
4481
|
-
await dependencies.opencodeClient.replyToPermission(parsed.requestId, parsed.reply);
|
|
4482
4612
|
const approval = (await dependencies.permissionApprovalRepo.listByRequestId(parsed.requestId)).find((item) => item.chatId === ctx.chat?.id);
|
|
4613
|
+
await dependencies.opencodeClient.replyToPermission(parsed.requestId, parsed.reply, void 0, approval?.sessionId);
|
|
4483
4614
|
if (approval) await dependencies.permissionApprovalRepo.set({
|
|
4484
4615
|
...approval,
|
|
4485
4616
|
status: parsed.reply,
|
|
@@ -4525,23 +4656,26 @@ function parseSessionActionTarget(data, prefix) {
|
|
|
4525
4656
|
}
|
|
4526
4657
|
//#endregion
|
|
4527
4658
|
//#region src/bot/handlers/prompt.handler.ts
|
|
4528
|
-
var activePromptChats = /* @__PURE__ */ new Set();
|
|
4529
4659
|
async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
4530
4660
|
const copy = await getChatCopy(dependencies.sessionRepo, ctx.chat.id);
|
|
4531
|
-
|
|
4661
|
+
const foregroundRequest = dependencies.foregroundSessionTracker.acquire(ctx.chat.id);
|
|
4662
|
+
if (!foregroundRequest) {
|
|
4532
4663
|
await ctx.reply(copy.status.alreadyProcessing);
|
|
4533
4664
|
return;
|
|
4534
4665
|
}
|
|
4535
4666
|
let processingMessage = null;
|
|
4536
4667
|
let sentTerminalReply = false;
|
|
4537
4668
|
try {
|
|
4538
|
-
activePromptChats.add(ctx.chat.id);
|
|
4539
4669
|
processingMessage = await ctx.reply(copy.status.processing);
|
|
4540
4670
|
const promptInput = await resolvePrompt();
|
|
4541
4671
|
const telegramReply = buildTelegramPromptReply(normalizePromptReplyForDisplay((await dependencies.sendPromptUseCase.execute({
|
|
4542
4672
|
chatId: ctx.chat.id,
|
|
4543
|
-
|
|
4544
|
-
|
|
4673
|
+
files: promptInput.files,
|
|
4674
|
+
onExecutionSession: (sessionId) => {
|
|
4675
|
+
foregroundRequest.attachSession(sessionId);
|
|
4676
|
+
},
|
|
4677
|
+
signal: foregroundRequest.signal,
|
|
4678
|
+
text: promptInput.text
|
|
4545
4679
|
})).assistantReply, copy, dependencies), copy);
|
|
4546
4680
|
try {
|
|
4547
4681
|
await ctx.reply(telegramReply.preferred.text, telegramReply.preferred.options);
|
|
@@ -4555,7 +4689,7 @@ async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
|
4555
4689
|
await ctx.reply(presentError(error, copy));
|
|
4556
4690
|
sentTerminalReply = true;
|
|
4557
4691
|
} finally {
|
|
4558
|
-
|
|
4692
|
+
foregroundRequest.dispose();
|
|
4559
4693
|
if (processingMessage && sentTerminalReply) try {
|
|
4560
4694
|
await ctx.api.deleteMessage(ctx.chat.id, processingMessage.message_id);
|
|
4561
4695
|
} catch (error) {
|