opencode-tbot 0.1.26 → 0.1.28
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.md +4 -2
- package/README.zh-CN.md +7 -1
- package/dist/plugin.js +313 -359
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -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,
|
|
@@ -255,7 +266,6 @@ var StructuredReplySchema = z.object({ body_md: z.string() });
|
|
|
255
266
|
var OpenCodeClient = class {
|
|
256
267
|
client;
|
|
257
268
|
fetchFn;
|
|
258
|
-
sdkFlavor;
|
|
259
269
|
promptTimeoutPolicy;
|
|
260
270
|
modelCache = {
|
|
261
271
|
expiresAt: 0,
|
|
@@ -266,7 +276,6 @@ var OpenCodeClient = class {
|
|
|
266
276
|
if (!options && !client) throw new Error("OpenCodeClient requires either base URL options or an injected SDK client.");
|
|
267
277
|
this.client = client ?? createOpencodeClient(buildOpenCodeSdkConfig(options));
|
|
268
278
|
this.fetchFn = fetchFn;
|
|
269
|
-
this.sdkFlavor = detectSdkFlavor(this.client);
|
|
270
279
|
this.promptTimeoutPolicy = resolvePromptTimeoutPolicy(promptTimeoutPolicy);
|
|
271
280
|
}
|
|
272
281
|
configurePromptTimeoutPolicy(promptTimeoutPolicy) {
|
|
@@ -279,169 +288,77 @@ var OpenCodeClient = class {
|
|
|
279
288
|
const target = this.client[scope];
|
|
280
289
|
const handler = target?.[method];
|
|
281
290
|
if (typeof handler !== "function") throw new Error(`OpenCode SDK client does not expose a compatible ${scope}.${method} method.`);
|
|
282
|
-
|
|
283
|
-
...SDK_OPTIONS,
|
|
284
|
-
...input.signal ? { signal: input.signal } : {},
|
|
285
|
-
...input.legacyOptions ?? {}
|
|
286
|
-
}));
|
|
287
|
-
return unwrapSdkData(await handler.call(target, input.v2Parameters, {
|
|
291
|
+
const options = {
|
|
288
292
|
...SDK_OPTIONS,
|
|
289
293
|
...input.signal ? { signal: input.signal } : {}
|
|
290
|
-
}
|
|
294
|
+
};
|
|
295
|
+
return unwrapSdkData(input.parameters === void 0 ? await handler.call(target, options) : await handler.call(target, input.parameters, options));
|
|
291
296
|
}
|
|
292
297
|
async getHealth() {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
298
|
+
try {
|
|
299
|
+
return await this.callScopedSdkMethod("global", "health", {});
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (!isMissingScopedSdkMethodError(error, "global", "health")) throw error;
|
|
302
|
+
return this.callRawSdkGet("/global/health");
|
|
303
|
+
}
|
|
299
304
|
}
|
|
300
305
|
async abortSession(sessionId) {
|
|
301
|
-
|
|
302
|
-
return this.callScopedSdkMethod("session", "abort", {
|
|
303
|
-
legacyOptions: { path: { id: sessionId } },
|
|
304
|
-
v2Parameters: { sessionID: sessionId }
|
|
305
|
-
});
|
|
306
|
+
return this.callScopedSdkMethod("session", "abort", { parameters: { sessionID: sessionId } });
|
|
306
307
|
}
|
|
307
308
|
async deleteSession(sessionId) {
|
|
308
|
-
|
|
309
|
-
return this.callScopedSdkMethod("session", "delete", {
|
|
310
|
-
legacyOptions: { path: { id: sessionId } },
|
|
311
|
-
v2Parameters: { sessionID: sessionId }
|
|
312
|
-
});
|
|
309
|
+
return this.callScopedSdkMethod("session", "delete", { parameters: { sessionID: sessionId } });
|
|
313
310
|
}
|
|
314
311
|
async forkSession(sessionId, messageId) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
...messageId?.trim() ? { body: { messageID: messageId.trim() } } : {}
|
|
320
|
-
},
|
|
321
|
-
v2Parameters: {
|
|
322
|
-
sessionID: sessionId,
|
|
323
|
-
...messageId?.trim() ? { messageID: messageId.trim() } : {}
|
|
324
|
-
}
|
|
325
|
-
});
|
|
312
|
+
return this.callScopedSdkMethod("session", "fork", { parameters: {
|
|
313
|
+
sessionID: sessionId,
|
|
314
|
+
...messageId?.trim() ? { messageID: messageId.trim() } : {}
|
|
315
|
+
} });
|
|
326
316
|
}
|
|
327
317
|
async getPath() {
|
|
328
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/path" });
|
|
329
318
|
return this.callScopedSdkMethod("path", "get", {});
|
|
330
319
|
}
|
|
331
320
|
async listLspStatuses(directory) {
|
|
332
|
-
|
|
333
|
-
url: "/lsp",
|
|
334
|
-
...directory ? { query: { directory } } : {}
|
|
335
|
-
});
|
|
336
|
-
return this.callScopedSdkMethod("lsp", "status", {
|
|
337
|
-
legacyOptions: directory ? { query: { directory } } : void 0,
|
|
338
|
-
v2Parameters: directory ? { directory } : void 0
|
|
339
|
-
});
|
|
321
|
+
return this.callScopedSdkMethod("lsp", "status", { parameters: directory ? { directory } : void 0 });
|
|
340
322
|
}
|
|
341
323
|
async listMcpStatuses(directory) {
|
|
342
|
-
|
|
343
|
-
url: "/mcp",
|
|
344
|
-
...directory ? { query: { directory } } : {}
|
|
345
|
-
});
|
|
346
|
-
return this.callScopedSdkMethod("mcp", "status", {
|
|
347
|
-
legacyOptions: directory ? { query: { directory } } : void 0,
|
|
348
|
-
v2Parameters: directory ? { directory } : void 0
|
|
349
|
-
});
|
|
324
|
+
return this.callScopedSdkMethod("mcp", "status", { parameters: directory ? { directory } : void 0 });
|
|
350
325
|
}
|
|
351
326
|
async getSessionStatuses() {
|
|
352
327
|
return this.loadSessionStatuses();
|
|
353
328
|
}
|
|
354
329
|
async listProjects() {
|
|
355
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project" });
|
|
356
330
|
return this.callScopedSdkMethod("project", "list", {});
|
|
357
331
|
}
|
|
358
332
|
async listSessions() {
|
|
359
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/session" });
|
|
360
333
|
return this.callScopedSdkMethod("session", "list", {});
|
|
361
334
|
}
|
|
362
335
|
async getCurrentProject() {
|
|
363
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project/current" });
|
|
364
336
|
return this.callScopedSdkMethod("project", "current", {});
|
|
365
337
|
}
|
|
366
338
|
async createSessionForDirectory(directory, title) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
});
|
|
372
|
-
return this.callScopedSdkMethod("session", "create", {
|
|
373
|
-
legacyOptions: {
|
|
374
|
-
query: { directory },
|
|
375
|
-
...title ? { body: { title } } : {}
|
|
376
|
-
},
|
|
377
|
-
v2Parameters: title ? {
|
|
378
|
-
directory,
|
|
379
|
-
title
|
|
380
|
-
} : { directory }
|
|
381
|
-
});
|
|
339
|
+
return this.callScopedSdkMethod("session", "create", { parameters: title ? {
|
|
340
|
+
directory,
|
|
341
|
+
title
|
|
342
|
+
} : { directory } });
|
|
382
343
|
}
|
|
383
344
|
async renameSession(sessionId, title) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
body: { title }
|
|
389
|
-
},
|
|
390
|
-
v2Parameters: {
|
|
391
|
-
sessionID: sessionId,
|
|
392
|
-
title
|
|
393
|
-
}
|
|
394
|
-
});
|
|
345
|
+
return this.callScopedSdkMethod("session", "update", { parameters: {
|
|
346
|
+
sessionID: sessionId,
|
|
347
|
+
title
|
|
348
|
+
} });
|
|
395
349
|
}
|
|
396
350
|
async listAgents() {
|
|
397
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/agent" });
|
|
398
351
|
return this.callScopedSdkMethod("app", "agents", {});
|
|
399
352
|
}
|
|
400
353
|
async listPendingPermissions(directory) {
|
|
401
|
-
|
|
402
|
-
url: "/permission",
|
|
403
|
-
...directory ? { query: { directory } } : {}
|
|
404
|
-
});
|
|
405
|
-
return this.callScopedSdkMethod("permission", "list", {
|
|
406
|
-
legacyOptions: directory ? { query: { directory } } : void 0,
|
|
407
|
-
v2Parameters: directory ? { directory } : void 0
|
|
408
|
-
});
|
|
354
|
+
return this.callScopedSdkMethod("permission", "list", { parameters: directory ? { directory } : void 0 });
|
|
409
355
|
}
|
|
410
|
-
async replyToPermission(requestId, reply, message,
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
body: { response: reply },
|
|
417
|
-
path: {
|
|
418
|
-
id: sessionId,
|
|
419
|
-
permissionID: requestId
|
|
420
|
-
}
|
|
421
|
-
}));
|
|
422
|
-
}
|
|
423
|
-
if (hasRawSdkMethod(this.client, "post")) return this.requestRaw("post", {
|
|
424
|
-
url: "/permission/{requestID}/reply",
|
|
425
|
-
path: { requestID: requestId },
|
|
426
|
-
body: {
|
|
427
|
-
reply,
|
|
428
|
-
...message?.trim() ? { message: message.trim() } : {}
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
return this.callScopedSdkMethod("permission", "reply", {
|
|
432
|
-
legacyOptions: sessionId ? {
|
|
433
|
-
body: { response: reply },
|
|
434
|
-
path: {
|
|
435
|
-
id: sessionId,
|
|
436
|
-
permissionID: requestId
|
|
437
|
-
}
|
|
438
|
-
} : void 0,
|
|
439
|
-
v2Parameters: {
|
|
440
|
-
requestID: requestId,
|
|
441
|
-
reply,
|
|
442
|
-
...message?.trim() ? { message: message.trim() } : {}
|
|
443
|
-
}
|
|
444
|
-
});
|
|
356
|
+
async replyToPermission(requestId, reply, message, _sessionId) {
|
|
357
|
+
return this.callScopedSdkMethod("permission", "reply", { parameters: {
|
|
358
|
+
requestID: requestId,
|
|
359
|
+
reply,
|
|
360
|
+
...message?.trim() ? { message: message.trim() } : {}
|
|
361
|
+
} });
|
|
445
362
|
}
|
|
446
363
|
async listModels() {
|
|
447
364
|
const now = Date.now();
|
|
@@ -466,9 +383,10 @@ var OpenCodeClient = class {
|
|
|
466
383
|
url: file.url
|
|
467
384
|
}))];
|
|
468
385
|
if (parts.length === 0) throw new Error("Prompt requires text or file attachments.");
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
386
|
+
throwIfAborted(input.signal);
|
|
387
|
+
const knownMessageIds = await this.captureKnownMessageIds(input.sessionId, input.signal);
|
|
388
|
+
await this.sendPromptRequest(input, parts);
|
|
389
|
+
return buildPromptSessionResult(await this.resolvePromptResponse(input, null, knownMessageIds, startedAt), {
|
|
472
390
|
emptyResponseText: EMPTY_RESPONSE_TEXT,
|
|
473
391
|
finishedAt: Date.now(),
|
|
474
392
|
startedAt,
|
|
@@ -488,10 +406,12 @@ var OpenCodeClient = class {
|
|
|
488
406
|
};
|
|
489
407
|
let bestCandidate = selectPromptResponseCandidate(data ? [data] : [], candidateOptions);
|
|
490
408
|
let lastProgressAt = Date.now();
|
|
409
|
+
let lastStatus = null;
|
|
491
410
|
const deadlineAt = startedAt + this.promptTimeoutPolicy.waitTimeoutMs;
|
|
492
411
|
let idleStatusSeen = false;
|
|
493
412
|
let attempt = 0;
|
|
494
413
|
while (true) {
|
|
414
|
+
throwIfAborted(input.signal);
|
|
495
415
|
const remainingWaitMs = deadlineAt - Date.now();
|
|
496
416
|
const remainingInactivityMs = this.promptTimeoutPolicy.recoveryInactivityTimeoutMs - (Date.now() - lastProgressAt);
|
|
497
417
|
if (remainingWaitMs <= 0 || remainingInactivityMs <= 0) break;
|
|
@@ -500,10 +420,10 @@ var OpenCodeClient = class {
|
|
|
500
420
|
if (delayMs > 0) {
|
|
501
421
|
const remainingMs = Math.min(remainingWaitMs, remainingInactivityMs);
|
|
502
422
|
if (remainingMs <= 0) break;
|
|
503
|
-
await delay(Math.min(delayMs, remainingMs));
|
|
423
|
+
await delay(Math.min(delayMs, remainingMs), input.signal);
|
|
504
424
|
}
|
|
505
425
|
if (messageId) {
|
|
506
|
-
const next = await this.fetchPromptMessage(input.sessionId, messageId);
|
|
426
|
+
const next = await this.fetchPromptMessage(input.sessionId, messageId, input.signal);
|
|
507
427
|
if (next) {
|
|
508
428
|
const nextCandidate = selectPromptResponseCandidate([bestCandidate, next], candidateOptions);
|
|
509
429
|
if (nextCandidate) {
|
|
@@ -513,10 +433,10 @@ var OpenCodeClient = class {
|
|
|
513
433
|
}
|
|
514
434
|
bestCandidate = nextCandidate;
|
|
515
435
|
}
|
|
516
|
-
if (bestCandidate && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
436
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
517
437
|
}
|
|
518
438
|
}
|
|
519
|
-
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages");
|
|
439
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages", input.signal);
|
|
520
440
|
if (latest) {
|
|
521
441
|
const nextCandidate = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
|
|
522
442
|
if (nextCandidate) {
|
|
@@ -526,9 +446,10 @@ var OpenCodeClient = class {
|
|
|
526
446
|
}
|
|
527
447
|
bestCandidate = nextCandidate;
|
|
528
448
|
}
|
|
529
|
-
if (bestCandidate && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
449
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
530
450
|
}
|
|
531
|
-
const status = await this.fetchPromptSessionStatus(input.sessionId);
|
|
451
|
+
const status = await this.fetchPromptSessionStatus(input.sessionId, input.signal);
|
|
452
|
+
lastStatus = status;
|
|
532
453
|
if (status?.type === "busy" || status?.type === "retry") {
|
|
533
454
|
lastProgressAt = Date.now();
|
|
534
455
|
idleStatusSeen = false;
|
|
@@ -536,12 +457,14 @@ var OpenCodeClient = class {
|
|
|
536
457
|
if (idleStatusSeen) break;
|
|
537
458
|
idleStatusSeen = true;
|
|
538
459
|
}
|
|
539
|
-
if (bestCandidate && isCompletedEmptyPromptResponse(bestCandidate, structured) && status?.type !== "busy" && status?.type !== "retry") break;
|
|
460
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && isCompletedEmptyPromptResponse(bestCandidate, structured) && status?.type !== "busy" && status?.type !== "retry") break;
|
|
540
461
|
if (Date.now() >= deadlineAt) break;
|
|
541
462
|
}
|
|
542
|
-
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan");
|
|
463
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan", input.signal);
|
|
543
464
|
const resolved = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
|
|
544
|
-
|
|
465
|
+
const requestScopedResolved = resolved && isPromptResponseForCurrentRequest(resolved, candidateOptions) ? resolved : null;
|
|
466
|
+
if (lastStatus?.type === "idle" && (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved, structured))) throw createMessageAbortedError();
|
|
467
|
+
if (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved, structured)) {
|
|
545
468
|
const timeoutReason = Date.now() >= deadlineAt ? "max-wait" : "recovery-inactivity";
|
|
546
469
|
const timeoutMs = timeoutReason === "max-wait" ? this.promptTimeoutPolicy.waitTimeoutMs : this.promptTimeoutPolicy.recoveryInactivityTimeoutMs;
|
|
547
470
|
const error = createOpenCodePromptTimeoutError({
|
|
@@ -553,7 +476,6 @@ var OpenCodeClient = class {
|
|
|
553
476
|
this.logPromptRequest("warn", {
|
|
554
477
|
lastProgressAt,
|
|
555
478
|
messageId: messageId ?? void 0,
|
|
556
|
-
sdkFlavor: this.sdkFlavor,
|
|
557
479
|
sessionId: input.sessionId,
|
|
558
480
|
stage: "final-scan",
|
|
559
481
|
timeoutMs,
|
|
@@ -561,31 +483,26 @@ var OpenCodeClient = class {
|
|
|
561
483
|
}, "OpenCode prompt recovery timed out");
|
|
562
484
|
throw error;
|
|
563
485
|
}
|
|
564
|
-
return
|
|
486
|
+
return requestScopedResolved;
|
|
565
487
|
}
|
|
566
|
-
async fetchPromptMessage(sessionId, messageId) {
|
|
488
|
+
async fetchPromptMessage(sessionId, messageId, signal) {
|
|
567
489
|
try {
|
|
568
490
|
return await this.runPromptRequestWithTimeout({
|
|
569
491
|
sessionId,
|
|
570
492
|
stage: "poll-message",
|
|
571
493
|
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs,
|
|
572
494
|
messageId
|
|
573
|
-
}, async (
|
|
574
|
-
if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponse(await this.requestRaw("get", buildRawSessionMessageRequest(this.sdkFlavor, sessionId, messageId, { signal })));
|
|
575
|
-
if (typeof this.client.session.message !== "function") return null;
|
|
495
|
+
}, async (requestSignal) => {
|
|
576
496
|
return normalizePromptResponse(await this.callScopedSdkMethod("session", "message", {
|
|
577
|
-
|
|
578
|
-
id: sessionId,
|
|
579
|
-
messageID: messageId
|
|
580
|
-
} },
|
|
581
|
-
signal,
|
|
582
|
-
v2Parameters: {
|
|
497
|
+
parameters: {
|
|
583
498
|
sessionID: sessionId,
|
|
584
499
|
messageID: messageId
|
|
585
|
-
}
|
|
500
|
+
},
|
|
501
|
+
signal: requestSignal
|
|
586
502
|
}));
|
|
587
|
-
});
|
|
503
|
+
}, signal);
|
|
588
504
|
} catch (error) {
|
|
505
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
589
506
|
this.logPromptRequestFailure(error, {
|
|
590
507
|
sessionId,
|
|
591
508
|
stage: "poll-message",
|
|
@@ -595,36 +512,28 @@ var OpenCodeClient = class {
|
|
|
595
512
|
return null;
|
|
596
513
|
}
|
|
597
514
|
}
|
|
598
|
-
async captureKnownMessageIds(sessionId) {
|
|
599
|
-
const messages = await this.fetchRecentPromptMessages(sessionId, "capture-known-messages");
|
|
515
|
+
async captureKnownMessageIds(sessionId, signal) {
|
|
516
|
+
const messages = await this.fetchRecentPromptMessages(sessionId, "capture-known-messages", signal);
|
|
600
517
|
if (!messages) return /* @__PURE__ */ new Set();
|
|
601
518
|
return new Set(messages.map((message) => extractMessageId(message.info)).filter((id) => typeof id === "string" && id.length > 0));
|
|
602
519
|
}
|
|
603
|
-
async fetchRecentPromptMessages(sessionId, stage) {
|
|
520
|
+
async fetchRecentPromptMessages(sessionId, stage, signal) {
|
|
604
521
|
try {
|
|
605
522
|
return await this.runPromptRequestWithTimeout({
|
|
606
523
|
sessionId,
|
|
607
524
|
stage,
|
|
608
525
|
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
609
|
-
}, async (
|
|
610
|
-
if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponses(await this.requestRaw("get", buildRawSessionRequest(this.sdkFlavor, sessionId, "/message", {
|
|
611
|
-
query: { limit: PROMPT_MESSAGE_POLL_LIMIT },
|
|
612
|
-
signal
|
|
613
|
-
})));
|
|
614
|
-
if (typeof this.client.session.messages !== "function") return null;
|
|
526
|
+
}, async (requestSignal) => {
|
|
615
527
|
return normalizePromptResponses(await this.callScopedSdkMethod("session", "messages", {
|
|
616
|
-
|
|
617
|
-
path: { id: sessionId },
|
|
618
|
-
query: { limit: PROMPT_MESSAGE_POLL_LIMIT }
|
|
619
|
-
},
|
|
620
|
-
signal,
|
|
621
|
-
v2Parameters: {
|
|
528
|
+
parameters: {
|
|
622
529
|
sessionID: sessionId,
|
|
623
530
|
limit: PROMPT_MESSAGE_POLL_LIMIT
|
|
624
|
-
}
|
|
531
|
+
},
|
|
532
|
+
signal: requestSignal
|
|
625
533
|
}));
|
|
626
|
-
});
|
|
534
|
+
}, signal);
|
|
627
535
|
} catch (error) {
|
|
536
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
628
537
|
this.logPromptRequestFailure(error, {
|
|
629
538
|
sessionId,
|
|
630
539
|
stage,
|
|
@@ -633,14 +542,15 @@ var OpenCodeClient = class {
|
|
|
633
542
|
return null;
|
|
634
543
|
}
|
|
635
544
|
}
|
|
636
|
-
async fetchPromptSessionStatus(sessionId) {
|
|
545
|
+
async fetchPromptSessionStatus(sessionId, signal) {
|
|
637
546
|
try {
|
|
638
547
|
return (await this.runPromptRequestWithTimeout({
|
|
639
548
|
sessionId,
|
|
640
549
|
stage: "poll-status",
|
|
641
550
|
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
642
|
-
}, async (
|
|
551
|
+
}, async (requestSignal) => this.loadSessionStatuses(requestSignal), signal))[sessionId] ?? null;
|
|
643
552
|
} catch (error) {
|
|
553
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
644
554
|
this.logPromptRequestFailure(error, {
|
|
645
555
|
sessionId,
|
|
646
556
|
stage: "poll-status",
|
|
@@ -649,8 +559,8 @@ var OpenCodeClient = class {
|
|
|
649
559
|
return null;
|
|
650
560
|
}
|
|
651
561
|
}
|
|
652
|
-
async findLatestPromptResponse(sessionId, options, stage) {
|
|
653
|
-
const messages = await this.fetchRecentPromptMessages(sessionId, stage);
|
|
562
|
+
async findLatestPromptResponse(sessionId, options, stage, signal) {
|
|
563
|
+
const messages = await this.fetchRecentPromptMessages(sessionId, stage, signal);
|
|
654
564
|
if (!messages || messages.length === 0) return null;
|
|
655
565
|
return selectPromptResponseCandidate(messages, options);
|
|
656
566
|
}
|
|
@@ -666,11 +576,9 @@ var OpenCodeClient = class {
|
|
|
666
576
|
return models;
|
|
667
577
|
}
|
|
668
578
|
async loadConfig() {
|
|
669
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/config" });
|
|
670
579
|
return this.callScopedSdkMethod("config", "get", {});
|
|
671
580
|
}
|
|
672
581
|
async loadProviderCatalog() {
|
|
673
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/config/providers" });
|
|
674
582
|
return this.callScopedSdkMethod("config", "providers", {});
|
|
675
583
|
}
|
|
676
584
|
async sendPromptRequest(input, parts) {
|
|
@@ -686,26 +594,6 @@ var OpenCodeClient = class {
|
|
|
686
594
|
...requestBody
|
|
687
595
|
};
|
|
688
596
|
try {
|
|
689
|
-
if (hasRawSdkMethod(this.client, "post")) return await this.runPromptRequestWithTimeout({
|
|
690
|
-
sessionId: input.sessionId,
|
|
691
|
-
stage: "send-prompt",
|
|
692
|
-
timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
|
|
693
|
-
}, async (signal) => normalizePromptResponse(await this.requestRaw("post", buildRawSessionRequest(this.sdkFlavor, input.sessionId, "/message", {
|
|
694
|
-
body: requestBody,
|
|
695
|
-
signal
|
|
696
|
-
}))));
|
|
697
|
-
if (typeof this.client.session?.prompt === "function") return await this.runPromptRequestWithTimeout({
|
|
698
|
-
sessionId: input.sessionId,
|
|
699
|
-
stage: "send-prompt",
|
|
700
|
-
timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
|
|
701
|
-
}, async (signal) => normalizePromptResponse(await this.callScopedSdkMethod("session", "prompt", {
|
|
702
|
-
legacyOptions: {
|
|
703
|
-
body: requestBody,
|
|
704
|
-
path: { id: input.sessionId }
|
|
705
|
-
},
|
|
706
|
-
signal,
|
|
707
|
-
v2Parameters: requestParameters
|
|
708
|
-
})));
|
|
709
597
|
if (typeof this.client.session?.promptAsync === "function") {
|
|
710
598
|
await this.runPromptRequestWithTimeout({
|
|
711
599
|
sessionId: input.sessionId,
|
|
@@ -713,15 +601,11 @@ var OpenCodeClient = class {
|
|
|
713
601
|
timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
|
|
714
602
|
}, async (signal) => {
|
|
715
603
|
await this.callScopedSdkMethod("session", "promptAsync", {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
path: { id: input.sessionId }
|
|
719
|
-
},
|
|
720
|
-
signal,
|
|
721
|
-
v2Parameters: requestParameters
|
|
604
|
+
parameters: requestParameters,
|
|
605
|
+
signal
|
|
722
606
|
});
|
|
723
|
-
});
|
|
724
|
-
return
|
|
607
|
+
}, input.signal);
|
|
608
|
+
return;
|
|
725
609
|
}
|
|
726
610
|
} catch (error) {
|
|
727
611
|
this.logPromptRequestFailure(error, {
|
|
@@ -731,27 +615,42 @@ var OpenCodeClient = class {
|
|
|
731
615
|
});
|
|
732
616
|
throw error;
|
|
733
617
|
}
|
|
734
|
-
throw new Error("OpenCode SDK client does not expose
|
|
735
|
-
}
|
|
736
|
-
async requestRaw(method, options) {
|
|
737
|
-
const handler = getRawSdkClient(this.client)?.[method];
|
|
738
|
-
if (typeof handler !== "function") throw new Error(`OpenCode SDK client does not expose a compatible raw ${method.toUpperCase()} method.`);
|
|
739
|
-
return unwrapSdkData(await handler({
|
|
740
|
-
...SDK_OPTIONS,
|
|
741
|
-
...options
|
|
742
|
-
}));
|
|
618
|
+
throw new Error("OpenCode SDK client does not expose session.promptAsync().");
|
|
743
619
|
}
|
|
744
620
|
async loadSessionStatuses(signal) {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
621
|
+
return this.callScopedSdkMethod("session", "status", {
|
|
622
|
+
signal,
|
|
623
|
+
parameters: void 0
|
|
748
624
|
});
|
|
749
|
-
return this.callScopedSdkMethod("session", "status", { signal });
|
|
750
625
|
}
|
|
751
|
-
async
|
|
626
|
+
async callRawSdkGet(url, signal) {
|
|
627
|
+
const rawClient = getRawSdkRequestClient(this.client);
|
|
628
|
+
if (typeof rawClient?.get !== "function") throw new Error(`OpenCode SDK client does not expose a compatible raw GET endpoint for ${url}.`);
|
|
629
|
+
return unwrapSdkData(await rawClient.get({
|
|
630
|
+
...SDK_OPTIONS,
|
|
631
|
+
...signal ? { signal } : {},
|
|
632
|
+
url
|
|
633
|
+
}));
|
|
634
|
+
}
|
|
635
|
+
async runPromptRequestWithTimeout(input, operation, signal) {
|
|
752
636
|
const startedAt = Date.now();
|
|
753
637
|
const controller = new AbortController();
|
|
638
|
+
const requestSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
754
639
|
let timeoutHandle = null;
|
|
640
|
+
let removeAbortListener = () => void 0;
|
|
641
|
+
const abortPromise = signal ? new Promise((_, reject) => {
|
|
642
|
+
const onAbort = () => {
|
|
643
|
+
reject(normalizeAbortReason(signal.reason));
|
|
644
|
+
};
|
|
645
|
+
if (signal.aborted) {
|
|
646
|
+
onAbort();
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
650
|
+
removeAbortListener = () => {
|
|
651
|
+
signal.removeEventListener("abort", onAbort);
|
|
652
|
+
};
|
|
653
|
+
}) : null;
|
|
755
654
|
const timeoutPromise = new Promise((_, reject) => {
|
|
756
655
|
timeoutHandle = setTimeout(() => {
|
|
757
656
|
reject(createOpenCodePromptTimeoutError({
|
|
@@ -765,9 +664,14 @@ var OpenCodeClient = class {
|
|
|
765
664
|
}, input.timeoutMs);
|
|
766
665
|
});
|
|
767
666
|
try {
|
|
768
|
-
return await Promise.race([
|
|
667
|
+
return await Promise.race([
|
|
668
|
+
operation(requestSignal),
|
|
669
|
+
timeoutPromise,
|
|
670
|
+
...abortPromise ? [abortPromise] : []
|
|
671
|
+
]);
|
|
769
672
|
} finally {
|
|
770
673
|
if (timeoutHandle !== null) clearTimeout(timeoutHandle);
|
|
674
|
+
removeAbortListener();
|
|
771
675
|
}
|
|
772
676
|
}
|
|
773
677
|
logPromptRequestFailure(error, input) {
|
|
@@ -776,7 +680,6 @@ var OpenCodeClient = class {
|
|
|
776
680
|
endpointKind: resolvePromptEndpointKind(error.data.stage),
|
|
777
681
|
elapsedMs: error.data.elapsedMs,
|
|
778
682
|
messageId: error.data.messageId,
|
|
779
|
-
sdkFlavor: this.sdkFlavor,
|
|
780
683
|
sessionId: error.data.sessionId,
|
|
781
684
|
stage: error.data.stage,
|
|
782
685
|
timeoutMs: error.data.timeoutMs
|
|
@@ -787,7 +690,6 @@ var OpenCodeClient = class {
|
|
|
787
690
|
endpointKind: resolvePromptEndpointKind(input.stage),
|
|
788
691
|
error,
|
|
789
692
|
messageId: input.messageId ?? void 0,
|
|
790
|
-
sdkFlavor: this.sdkFlavor,
|
|
791
693
|
sessionId: input.sessionId,
|
|
792
694
|
stage: input.stage,
|
|
793
695
|
timeoutMs: input.timeoutMs
|
|
@@ -978,8 +880,23 @@ function extractMessageId(message) {
|
|
|
978
880
|
if (!isPlainRecord(message)) return null;
|
|
979
881
|
return typeof message.id === "string" && message.id.trim().length > 0 ? message.id : null;
|
|
980
882
|
}
|
|
981
|
-
function delay(ms) {
|
|
982
|
-
return new Promise((resolve) =>
|
|
883
|
+
function delay(ms, signal) {
|
|
884
|
+
return new Promise((resolve, reject) => {
|
|
885
|
+
const handle = setTimeout(() => {
|
|
886
|
+
signal?.removeEventListener("abort", onAbort);
|
|
887
|
+
resolve();
|
|
888
|
+
}, ms);
|
|
889
|
+
const onAbort = () => {
|
|
890
|
+
clearTimeout(handle);
|
|
891
|
+
signal?.removeEventListener("abort", onAbort);
|
|
892
|
+
reject(normalizeAbortReason(signal?.reason));
|
|
893
|
+
};
|
|
894
|
+
if (signal?.aborted) {
|
|
895
|
+
onAbort();
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
899
|
+
});
|
|
983
900
|
}
|
|
984
901
|
function didPromptResponseAdvance(previous, next, structured) {
|
|
985
902
|
return getPromptResponseProgressSignature(previous, structured) !== getPromptResponseProgressSignature(next, structured);
|
|
@@ -1056,6 +973,13 @@ function unwrapSdkData(response) {
|
|
|
1056
973
|
if (response && typeof response === "object" && "data" in response) return response.data;
|
|
1057
974
|
return response;
|
|
1058
975
|
}
|
|
976
|
+
function getRawSdkRequestClient(client) {
|
|
977
|
+
const compatibleClient = client;
|
|
978
|
+
return compatibleClient.client ?? compatibleClient._client ?? null;
|
|
979
|
+
}
|
|
980
|
+
function isMissingScopedSdkMethodError(error, scope, method) {
|
|
981
|
+
return error instanceof Error && error.message === `OpenCode SDK client does not expose a compatible ${scope}.${method} method.`;
|
|
982
|
+
}
|
|
1059
983
|
function resolvePromptTimeoutPolicy(input) {
|
|
1060
984
|
return {
|
|
1061
985
|
pollRequestTimeoutMs: input.pollRequestTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.pollRequestTimeoutMs,
|
|
@@ -1063,46 +987,6 @@ function resolvePromptTimeoutPolicy(input) {
|
|
|
1063
987
|
waitTimeoutMs: input.waitTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.waitTimeoutMs
|
|
1064
988
|
};
|
|
1065
989
|
}
|
|
1066
|
-
function detectSdkFlavor(client) {
|
|
1067
|
-
if (typeof client.global?.health === "function" || "permission" in client) return "v2";
|
|
1068
|
-
return clientExposesTwoArgumentSdkMethod(client) ? "v2" : "legacy";
|
|
1069
|
-
}
|
|
1070
|
-
function buildRawSessionRequest(sdkFlavor, sessionId, suffix = "", extra = {}) {
|
|
1071
|
-
return {
|
|
1072
|
-
url: sdkFlavor === "legacy" ? `/session/{id}${suffix}` : `/session/{sessionID}${suffix}`,
|
|
1073
|
-
path: sdkFlavor === "legacy" ? { id: sessionId } : { sessionID: sessionId },
|
|
1074
|
-
...extra
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
function buildRawSessionMessageRequest(sdkFlavor, sessionId, messageId, extra = {}) {
|
|
1078
|
-
return {
|
|
1079
|
-
url: sdkFlavor === "legacy" ? "/session/{id}/message/{messageID}" : "/session/{sessionID}/message/{messageID}",
|
|
1080
|
-
path: sdkFlavor === "legacy" ? {
|
|
1081
|
-
id: sessionId,
|
|
1082
|
-
messageID: messageId
|
|
1083
|
-
} : {
|
|
1084
|
-
sessionID: sessionId,
|
|
1085
|
-
messageID: messageId
|
|
1086
|
-
},
|
|
1087
|
-
...extra
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
function buildRawLegacyPermissionReplyRequest(sessionId, requestId, reply) {
|
|
1091
|
-
return {
|
|
1092
|
-
body: { response: reply },
|
|
1093
|
-
path: {
|
|
1094
|
-
id: sessionId,
|
|
1095
|
-
permissionID: requestId
|
|
1096
|
-
},
|
|
1097
|
-
url: "/session/{id}/permissions/{permissionID}"
|
|
1098
|
-
};
|
|
1099
|
-
}
|
|
1100
|
-
function getRawSdkClient(client) {
|
|
1101
|
-
return client.client ?? client._client ?? null;
|
|
1102
|
-
}
|
|
1103
|
-
function hasRawSdkMethod(client, method) {
|
|
1104
|
-
return typeof getRawSdkClient(client)?.[method] === "function";
|
|
1105
|
-
}
|
|
1106
990
|
function normalizeAssistantError(value) {
|
|
1107
991
|
if (!isPlainRecord(value) || typeof value.name !== "string" || value.name.trim().length === 0) return;
|
|
1108
992
|
return {
|
|
@@ -1120,23 +1004,6 @@ function isCompletedEmptyPromptResponse(data, structured) {
|
|
|
1120
1004
|
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
1121
1005
|
return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText && !bodyMd;
|
|
1122
1006
|
}
|
|
1123
|
-
function clientExposesTwoArgumentSdkMethod(client) {
|
|
1124
|
-
return [
|
|
1125
|
-
"app",
|
|
1126
|
-
"config",
|
|
1127
|
-
"global",
|
|
1128
|
-
"lsp",
|
|
1129
|
-
"mcp",
|
|
1130
|
-
"path",
|
|
1131
|
-
"permission",
|
|
1132
|
-
"project",
|
|
1133
|
-
"session"
|
|
1134
|
-
].some((scope) => {
|
|
1135
|
-
const target = client[scope];
|
|
1136
|
-
if (!target || typeof target !== "object") return false;
|
|
1137
|
-
return Object.values(target).some((value) => typeof value === "function" && value.length >= 2);
|
|
1138
|
-
});
|
|
1139
|
-
}
|
|
1140
1007
|
function extractStructuredPayload(message) {
|
|
1141
1008
|
if (!isPlainRecord(message)) return null;
|
|
1142
1009
|
if ("structured" in message && message.structured !== void 0) return message.structured;
|
|
@@ -1151,7 +1018,7 @@ function selectPromptResponseCandidate(candidates, options) {
|
|
|
1151
1018
|
function comparePromptResponseCandidates(left, right, options) {
|
|
1152
1019
|
const leftRank = getPromptResponseCandidateRank(left, options);
|
|
1153
1020
|
const rightRank = getPromptResponseCandidateRank(right, options);
|
|
1154
|
-
return Number(rightRank.
|
|
1021
|
+
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;
|
|
1155
1022
|
}
|
|
1156
1023
|
function getPromptResponseCandidateRank(message, options) {
|
|
1157
1024
|
const assistant = toAssistantMessage(message.info);
|
|
@@ -1166,7 +1033,7 @@ function getPromptResponseCandidateRank(message, options) {
|
|
|
1166
1033
|
};
|
|
1167
1034
|
}
|
|
1168
1035
|
function resolvePromptCandidateStartTime(startedAt, initialMessage) {
|
|
1169
|
-
if (!initialMessage) return
|
|
1036
|
+
if (!initialMessage) return startedAt;
|
|
1170
1037
|
const initialCreatedAt = coerceFiniteNumber(toAssistantMessage(initialMessage.info)?.time?.created);
|
|
1171
1038
|
if (initialCreatedAt === null) return startedAt;
|
|
1172
1039
|
return areComparablePromptTimestamps(startedAt, initialCreatedAt) ? startedAt : initialCreatedAt;
|
|
@@ -1190,10 +1057,28 @@ function isPromptResponseNewSinceRequestStart(messageId, createdAt, knownMessage
|
|
|
1190
1057
|
if (requestStartedAt === null) return true;
|
|
1191
1058
|
return createdAt >= requestStartedAt;
|
|
1192
1059
|
}
|
|
1060
|
+
function isPromptResponseForCurrentRequest(response, options) {
|
|
1061
|
+
const rank = getPromptResponseCandidateRank(response, options);
|
|
1062
|
+
return rank.isInitial || rank.sharesParent || rank.isNewSinceRequestStart;
|
|
1063
|
+
}
|
|
1193
1064
|
function areComparablePromptTimestamps(left, right) {
|
|
1194
1065
|
const epochThresholdMs = 0xe8d4a51000;
|
|
1195
1066
|
return left >= epochThresholdMs && right >= epochThresholdMs;
|
|
1196
1067
|
}
|
|
1068
|
+
function isPromptRequestAbort(error) {
|
|
1069
|
+
return error instanceof OpenCodeMessageAbortedError || error instanceof Error && error.name === "AbortError" || isNamedAbortError(error);
|
|
1070
|
+
}
|
|
1071
|
+
function isNamedAbortError(error) {
|
|
1072
|
+
return !!error && typeof error === "object" && "name" in error && error.name === "MessageAbortedError";
|
|
1073
|
+
}
|
|
1074
|
+
function normalizeAbortReason(reason) {
|
|
1075
|
+
if (reason instanceof Error || isNamedAbortError(reason)) return reason;
|
|
1076
|
+
return createMessageAbortedError();
|
|
1077
|
+
}
|
|
1078
|
+
function throwIfAborted(signal) {
|
|
1079
|
+
if (!signal?.aborted) return;
|
|
1080
|
+
throw normalizeAbortReason(signal.reason);
|
|
1081
|
+
}
|
|
1197
1082
|
function isPlainRecord(value) {
|
|
1198
1083
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1199
1084
|
}
|
|
@@ -1361,85 +1246,119 @@ function extractErrorMessage(error) {
|
|
|
1361
1246
|
//#endregion
|
|
1362
1247
|
//#region src/services/session-activity/foreground-session-tracker.ts
|
|
1363
1248
|
var ForegroundSessionTracker = class {
|
|
1364
|
-
|
|
1365
|
-
counts = /* @__PURE__ */ new Map();
|
|
1249
|
+
requests = /* @__PURE__ */ new Map();
|
|
1366
1250
|
sessionChats = /* @__PURE__ */ new Map();
|
|
1251
|
+
acquire(chatId) {
|
|
1252
|
+
if (this.requests.has(chatId)) return null;
|
|
1253
|
+
const state = {
|
|
1254
|
+
chatId,
|
|
1255
|
+
controller: new AbortController(),
|
|
1256
|
+
sessionId: null
|
|
1257
|
+
};
|
|
1258
|
+
this.requests.set(chatId, state);
|
|
1259
|
+
return {
|
|
1260
|
+
signal: state.controller.signal,
|
|
1261
|
+
attachSession: (sessionId) => {
|
|
1262
|
+
this.attachSession(chatId, sessionId);
|
|
1263
|
+
},
|
|
1264
|
+
dispose: () => {
|
|
1265
|
+
this.release(chatId);
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
abort(chatId, reason = createMessageAbortedError()) {
|
|
1270
|
+
const state = this.requests.get(chatId);
|
|
1271
|
+
if (!state) return false;
|
|
1272
|
+
if (!state.controller.signal.aborted) state.controller.abort(reason);
|
|
1273
|
+
return true;
|
|
1274
|
+
}
|
|
1367
1275
|
begin(chatId, sessionId) {
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
|
|
1276
|
+
const lease = this.acquire(chatId);
|
|
1277
|
+
if (!lease) return () => void 0;
|
|
1278
|
+
lease.attachSession(sessionId);
|
|
1371
1279
|
return () => {
|
|
1372
|
-
|
|
1280
|
+
lease.dispose();
|
|
1373
1281
|
};
|
|
1374
1282
|
}
|
|
1375
1283
|
clear(sessionId) {
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
const stack = this.chatStacks.get(chatId);
|
|
1382
|
-
if (!stack) continue;
|
|
1383
|
-
const nextStack = stack.filter((trackedSessionId) => trackedSessionId !== sessionId);
|
|
1384
|
-
if (nextStack.length === 0) {
|
|
1385
|
-
this.chatStacks.delete(chatId);
|
|
1386
|
-
continue;
|
|
1387
|
-
}
|
|
1388
|
-
this.chatStacks.set(chatId, nextStack);
|
|
1284
|
+
const chatIds = this.listChatIds(sessionId);
|
|
1285
|
+
if (chatIds.length === 0) return false;
|
|
1286
|
+
for (const chatId of chatIds) {
|
|
1287
|
+
const state = this.requests.get(chatId);
|
|
1288
|
+
if (state?.sessionId === sessionId) state.sessionId = null;
|
|
1389
1289
|
}
|
|
1390
|
-
|
|
1290
|
+
this.sessionChats.delete(sessionId);
|
|
1291
|
+
return true;
|
|
1292
|
+
}
|
|
1293
|
+
fail(sessionId, error) {
|
|
1294
|
+
const chatIds = this.listChatIds(sessionId);
|
|
1295
|
+
if (chatIds.length === 0) return false;
|
|
1296
|
+
this.clear(sessionId);
|
|
1297
|
+
for (const chatId of chatIds) this.abort(chatId, error);
|
|
1298
|
+
return true;
|
|
1391
1299
|
}
|
|
1392
1300
|
getActiveSessionId(chatId) {
|
|
1393
|
-
return this.
|
|
1301
|
+
return this.requests.get(chatId)?.sessionId ?? null;
|
|
1302
|
+
}
|
|
1303
|
+
hasActiveRequest(chatId) {
|
|
1304
|
+
return this.requests.has(chatId);
|
|
1394
1305
|
}
|
|
1395
1306
|
isForeground(sessionId) {
|
|
1396
|
-
return this.
|
|
1307
|
+
return this.sessionChats.has(sessionId);
|
|
1397
1308
|
}
|
|
1398
1309
|
listChatIds(sessionId) {
|
|
1399
|
-
return [...this.sessionChats.get(sessionId)
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
const
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
const
|
|
1409
|
-
|
|
1410
|
-
this.
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1310
|
+
return [...this.sessionChats.get(sessionId) ?? /* @__PURE__ */ new Set()];
|
|
1311
|
+
}
|
|
1312
|
+
attachSession(chatId, sessionId) {
|
|
1313
|
+
const normalizedSessionId = sessionId.trim();
|
|
1314
|
+
const state = this.requests.get(chatId);
|
|
1315
|
+
if (!state || normalizedSessionId.length === 0) return;
|
|
1316
|
+
if (state.sessionId === normalizedSessionId) return;
|
|
1317
|
+
if (state.sessionId) this.detachChatFromSession(chatId, state.sessionId);
|
|
1318
|
+
state.sessionId = normalizedSessionId;
|
|
1319
|
+
const chatIds = this.sessionChats.get(normalizedSessionId) ?? /* @__PURE__ */ new Set();
|
|
1320
|
+
chatIds.add(chatId);
|
|
1321
|
+
this.sessionChats.set(normalizedSessionId, chatIds);
|
|
1322
|
+
}
|
|
1323
|
+
release(chatId) {
|
|
1324
|
+
const state = this.requests.get(chatId);
|
|
1325
|
+
if (!state) return;
|
|
1326
|
+
if (state.sessionId) this.detachChatFromSession(chatId, state.sessionId);
|
|
1327
|
+
this.requests.delete(chatId);
|
|
1328
|
+
}
|
|
1329
|
+
detachChatFromSession(chatId, sessionId) {
|
|
1330
|
+
const chatIds = this.sessionChats.get(sessionId);
|
|
1331
|
+
if (!chatIds) return;
|
|
1332
|
+
chatIds.delete(chatId);
|
|
1333
|
+
if (chatIds.size === 0) {
|
|
1334
|
+
this.sessionChats.delete(sessionId);
|
|
1335
|
+
return;
|
|
1423
1336
|
}
|
|
1424
|
-
|
|
1425
|
-
if (!chatCounts) return;
|
|
1426
|
-
const currentCount = chatCounts.get(chatId) ?? 0;
|
|
1427
|
-
if (currentCount <= 1) chatCounts.delete(chatId);
|
|
1428
|
-
else chatCounts.set(chatId, currentCount - 1);
|
|
1429
|
-
if (chatCounts.size === 0) this.sessionChats.delete(sessionId);
|
|
1430
|
-
else this.sessionChats.set(sessionId, chatCounts);
|
|
1337
|
+
this.sessionChats.set(sessionId, chatIds);
|
|
1431
1338
|
}
|
|
1432
1339
|
};
|
|
1433
1340
|
var NOOP_FOREGROUND_SESSION_TRACKER = {
|
|
1341
|
+
acquire() {
|
|
1342
|
+
return null;
|
|
1343
|
+
},
|
|
1344
|
+
abort() {
|
|
1345
|
+
return false;
|
|
1346
|
+
},
|
|
1434
1347
|
begin() {
|
|
1435
1348
|
return () => void 0;
|
|
1436
1349
|
},
|
|
1437
1350
|
clear() {
|
|
1438
1351
|
return false;
|
|
1439
1352
|
},
|
|
1353
|
+
fail() {
|
|
1354
|
+
return false;
|
|
1355
|
+
},
|
|
1440
1356
|
getActiveSessionId() {
|
|
1441
1357
|
return null;
|
|
1442
1358
|
},
|
|
1359
|
+
hasActiveRequest() {
|
|
1360
|
+
return false;
|
|
1361
|
+
},
|
|
1443
1362
|
isForeground() {
|
|
1444
1363
|
return false;
|
|
1445
1364
|
},
|
|
@@ -1456,23 +1375,40 @@ var AbortPromptUseCase = class {
|
|
|
1456
1375
|
this.foregroundSessionTracker = foregroundSessionTracker;
|
|
1457
1376
|
}
|
|
1458
1377
|
async execute(input) {
|
|
1378
|
+
const hasForegroundRequest = this.foregroundSessionTracker.hasActiveRequest(input.chatId);
|
|
1459
1379
|
const activeSessionId = this.foregroundSessionTracker.getActiveSessionId(input.chatId);
|
|
1460
1380
|
const binding = activeSessionId ? null : await this.sessionRepo.getByChatId(input.chatId);
|
|
1461
1381
|
const sessionId = activeSessionId ?? binding?.sessionId ?? null;
|
|
1462
|
-
if (!sessionId) return {
|
|
1382
|
+
if (!hasForegroundRequest && !sessionId) return {
|
|
1463
1383
|
sessionId: null,
|
|
1464
1384
|
status: "no_session",
|
|
1465
1385
|
sessionStatus: null
|
|
1466
1386
|
};
|
|
1467
|
-
const
|
|
1387
|
+
const sessionStatuses = sessionId ? await this.opencodeClient.getSessionStatuses() : {};
|
|
1388
|
+
const sessionStatus = sessionId ? sessionStatuses[sessionId] ?? null : null;
|
|
1389
|
+
if (hasForegroundRequest) {
|
|
1390
|
+
if (sessionId && sessionStatus && sessionStatus.type !== "idle") await this.opencodeClient.abortSession(sessionId);
|
|
1391
|
+
this.foregroundSessionTracker.abort(input.chatId, createMessageAbortedError());
|
|
1392
|
+
return {
|
|
1393
|
+
sessionId,
|
|
1394
|
+
status: "aborted",
|
|
1395
|
+
sessionStatus
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1468
1398
|
if (!sessionStatus || sessionStatus.type === "idle") return {
|
|
1469
1399
|
sessionId,
|
|
1470
1400
|
status: "not_running",
|
|
1471
1401
|
sessionStatus
|
|
1472
1402
|
};
|
|
1403
|
+
if (!sessionId) return {
|
|
1404
|
+
sessionId: null,
|
|
1405
|
+
status: "not_running",
|
|
1406
|
+
sessionStatus
|
|
1407
|
+
};
|
|
1408
|
+
const runningSessionId = sessionId;
|
|
1473
1409
|
return {
|
|
1474
|
-
sessionId,
|
|
1475
|
-
status: await this.opencodeClient.abortSession(
|
|
1410
|
+
sessionId: runningSessionId,
|
|
1411
|
+
status: await this.opencodeClient.abortSession(runningSessionId) ? "aborted" : "not_running",
|
|
1476
1412
|
sessionStatus
|
|
1477
1413
|
};
|
|
1478
1414
|
}
|
|
@@ -1914,11 +1850,10 @@ var RenameSessionUseCase = class {
|
|
|
1914
1850
|
//#endregion
|
|
1915
1851
|
//#region src/use-cases/send-prompt.usecase.ts
|
|
1916
1852
|
var SendPromptUseCase = class {
|
|
1917
|
-
constructor(sessionRepo, opencodeClient, logger
|
|
1853
|
+
constructor(sessionRepo, opencodeClient, logger) {
|
|
1918
1854
|
this.sessionRepo = sessionRepo;
|
|
1919
1855
|
this.opencodeClient = opencodeClient;
|
|
1920
1856
|
this.logger = logger;
|
|
1921
|
-
this.foregroundSessionTracker = foregroundSessionTracker;
|
|
1922
1857
|
}
|
|
1923
1858
|
async execute(input) {
|
|
1924
1859
|
const files = input.files ?? [];
|
|
@@ -1972,7 +1907,7 @@ var SendPromptUseCase = class {
|
|
|
1972
1907
|
}
|
|
1973
1908
|
const temporarySessionId = shouldIsolateImageTurn ? await this.createTemporaryImageSession(input.chatId, activeBinding.sessionId) : null;
|
|
1974
1909
|
const executionSessionId = temporarySessionId ?? activeBinding.sessionId;
|
|
1975
|
-
|
|
1910
|
+
input.onExecutionSession?.(executionSessionId);
|
|
1976
1911
|
let result;
|
|
1977
1912
|
try {
|
|
1978
1913
|
result = await this.opencodeClient.promptSession({
|
|
@@ -1982,10 +1917,10 @@ var SendPromptUseCase = class {
|
|
|
1982
1917
|
...selectedAgent ? { agent: selectedAgent.name } : {},
|
|
1983
1918
|
structured: true,
|
|
1984
1919
|
...model ? { model } : {},
|
|
1920
|
+
...input.signal ? { signal: input.signal } : {},
|
|
1985
1921
|
...activeBinding.modelVariant ? { variant: activeBinding.modelVariant } : {}
|
|
1986
1922
|
});
|
|
1987
1923
|
} finally {
|
|
1988
|
-
endForegroundSession();
|
|
1989
1924
|
if (temporarySessionId) await this.cleanupTemporaryImageSession(input.chatId, activeBinding.sessionId, temporarySessionId);
|
|
1990
1925
|
}
|
|
1991
1926
|
await this.sessionRepo.touch(input.chatId);
|
|
@@ -2253,7 +2188,7 @@ function createContainer(config, opencodeClient, logger) {
|
|
|
2253
2188
|
const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo);
|
|
2254
2189
|
const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
|
|
2255
2190
|
const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, logger);
|
|
2256
|
-
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger
|
|
2191
|
+
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger);
|
|
2257
2192
|
const switchAgentUseCase = new SwitchAgentUseCase(sessionRepo, opencodeClient, logger);
|
|
2258
2193
|
const switchModelUseCase = new SwitchModelUseCase(sessionRepo, opencodeClient, logger);
|
|
2259
2194
|
const switchSessionUseCase = new SwitchSessionUseCase(sessionRepo, opencodeClient, logger);
|
|
@@ -2360,21 +2295,25 @@ function escapeMarkdownV2(value) {
|
|
|
2360
2295
|
async function handleTelegramBotPluginEvent(runtime, event) {
|
|
2361
2296
|
switch (event.type) {
|
|
2362
2297
|
case "permission.asked":
|
|
2363
|
-
await handlePermissionAsked(runtime, event
|
|
2298
|
+
await handlePermissionAsked(runtime, event);
|
|
2364
2299
|
return;
|
|
2365
2300
|
case "permission.replied":
|
|
2366
|
-
await handlePermissionReplied(runtime, event
|
|
2301
|
+
await handlePermissionReplied(runtime, event);
|
|
2367
2302
|
return;
|
|
2368
2303
|
case "session.error":
|
|
2369
|
-
await handleSessionError(runtime, event
|
|
2304
|
+
await handleSessionError(runtime, event);
|
|
2370
2305
|
return;
|
|
2371
2306
|
case "session.idle":
|
|
2372
|
-
await handleSessionIdle(runtime, event
|
|
2307
|
+
await handleSessionIdle(runtime, event);
|
|
2308
|
+
return;
|
|
2309
|
+
case "session.status":
|
|
2310
|
+
await handleSessionStatus(runtime, event);
|
|
2373
2311
|
return;
|
|
2374
2312
|
default: return;
|
|
2375
2313
|
}
|
|
2376
2314
|
}
|
|
2377
|
-
async function handlePermissionAsked(runtime,
|
|
2315
|
+
async function handlePermissionAsked(runtime, event) {
|
|
2316
|
+
const request = event.properties;
|
|
2378
2317
|
const bindings = await runtime.container.sessionRepo.listBySessionId(request.sessionID);
|
|
2379
2318
|
const chatIds = new Set([...bindings.map((binding) => binding.chatId), ...runtime.container.foregroundSessionTracker.listChatIds(request.sessionID)]);
|
|
2380
2319
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(request.id);
|
|
@@ -2403,7 +2342,9 @@ async function handlePermissionAsked(runtime, request) {
|
|
|
2403
2342
|
}
|
|
2404
2343
|
}
|
|
2405
2344
|
}
|
|
2406
|
-
async function handlePermissionReplied(runtime,
|
|
2345
|
+
async function handlePermissionReplied(runtime, event) {
|
|
2346
|
+
const requestId = event.properties.requestID;
|
|
2347
|
+
const reply = event.properties.reply;
|
|
2407
2348
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(requestId);
|
|
2408
2349
|
await Promise.all(approvals.map(async (approval) => {
|
|
2409
2350
|
try {
|
|
@@ -2418,27 +2359,37 @@ async function handlePermissionReplied(runtime, requestId, reply) {
|
|
|
2418
2359
|
await runtime.container.permissionApprovalRepo.set(toResolvedApproval(approval, reply));
|
|
2419
2360
|
}));
|
|
2420
2361
|
}
|
|
2421
|
-
async function handleSessionError(runtime,
|
|
2362
|
+
async function handleSessionError(runtime, event) {
|
|
2363
|
+
const sessionId = event.properties.sessionID;
|
|
2364
|
+
const error = event.properties.error;
|
|
2422
2365
|
if (!sessionId) {
|
|
2423
2366
|
runtime.container.logger.error({ error }, "session error received without a session id");
|
|
2424
2367
|
return;
|
|
2425
2368
|
}
|
|
2426
|
-
if (runtime.container.foregroundSessionTracker.
|
|
2369
|
+
if (runtime.container.foregroundSessionTracker.fail(sessionId, error ?? /* @__PURE__ */ new Error("Unknown session error."))) {
|
|
2427
2370
|
runtime.container.logger.warn({
|
|
2428
2371
|
error,
|
|
2429
2372
|
sessionId
|
|
2430
2373
|
}, "session error suppressed for foreground Telegram session");
|
|
2431
2374
|
return;
|
|
2432
2375
|
}
|
|
2433
|
-
await notifyBoundChats(runtime, sessionId, `Session failed.\n\nSession: ${sessionId}\nError: ${error?.data?.message
|
|
2376
|
+
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."}`);
|
|
2434
2377
|
}
|
|
2435
|
-
async function handleSessionIdle(runtime,
|
|
2378
|
+
async function handleSessionIdle(runtime, event) {
|
|
2379
|
+
const sessionId = event.properties.sessionID;
|
|
2436
2380
|
if (runtime.container.foregroundSessionTracker.clear(sessionId)) {
|
|
2437
2381
|
runtime.container.logger.info({ sessionId }, "session idle notification suppressed for foreground Telegram session");
|
|
2438
2382
|
return;
|
|
2439
2383
|
}
|
|
2440
2384
|
await notifyBoundChats(runtime, sessionId, `Session finished.\n\nSession: ${sessionId}`);
|
|
2441
2385
|
}
|
|
2386
|
+
async function handleSessionStatus(runtime, event) {
|
|
2387
|
+
if (event.properties.status.type !== "idle") return;
|
|
2388
|
+
await handleSessionIdle(runtime, {
|
|
2389
|
+
type: "session.idle",
|
|
2390
|
+
properties: { sessionID: event.properties.sessionID }
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2442
2393
|
async function notifyBoundChats(runtime, sessionId, text) {
|
|
2443
2394
|
const bindings = await runtime.container.sessionRepo.listBySessionId(sessionId);
|
|
2444
2395
|
const chatIds = [...new Set(bindings.map((binding) => binding.chatId))];
|
|
@@ -4726,23 +4677,26 @@ function parseSessionActionTarget(data, prefix) {
|
|
|
4726
4677
|
}
|
|
4727
4678
|
//#endregion
|
|
4728
4679
|
//#region src/bot/handlers/prompt.handler.ts
|
|
4729
|
-
var activePromptChats = /* @__PURE__ */ new Set();
|
|
4730
4680
|
async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
4731
4681
|
const copy = await getChatCopy(dependencies.sessionRepo, ctx.chat.id);
|
|
4732
|
-
|
|
4682
|
+
const foregroundRequest = dependencies.foregroundSessionTracker.acquire(ctx.chat.id);
|
|
4683
|
+
if (!foregroundRequest) {
|
|
4733
4684
|
await ctx.reply(copy.status.alreadyProcessing);
|
|
4734
4685
|
return;
|
|
4735
4686
|
}
|
|
4736
4687
|
let processingMessage = null;
|
|
4737
4688
|
let sentTerminalReply = false;
|
|
4738
4689
|
try {
|
|
4739
|
-
activePromptChats.add(ctx.chat.id);
|
|
4740
4690
|
processingMessage = await ctx.reply(copy.status.processing);
|
|
4741
4691
|
const promptInput = await resolvePrompt();
|
|
4742
4692
|
const telegramReply = buildTelegramPromptReply(normalizePromptReplyForDisplay((await dependencies.sendPromptUseCase.execute({
|
|
4743
4693
|
chatId: ctx.chat.id,
|
|
4744
|
-
|
|
4745
|
-
|
|
4694
|
+
files: promptInput.files,
|
|
4695
|
+
onExecutionSession: (sessionId) => {
|
|
4696
|
+
foregroundRequest.attachSession(sessionId);
|
|
4697
|
+
},
|
|
4698
|
+
signal: foregroundRequest.signal,
|
|
4699
|
+
text: promptInput.text
|
|
4746
4700
|
})).assistantReply, copy, dependencies), copy);
|
|
4747
4701
|
try {
|
|
4748
4702
|
await ctx.reply(telegramReply.preferred.text, telegramReply.preferred.options);
|
|
@@ -4756,7 +4710,7 @@ async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
|
4756
4710
|
await ctx.reply(presentError(error, copy));
|
|
4757
4711
|
sentTerminalReply = true;
|
|
4758
4712
|
} finally {
|
|
4759
|
-
|
|
4713
|
+
foregroundRequest.dispose();
|
|
4760
4714
|
if (processingMessage && sentTerminalReply) try {
|
|
4761
4715
|
await ctx.api.deleteMessage(ctx.chat.id, processingMessage.message_id);
|
|
4762
4716
|
} catch (error) {
|