opencode-tbot 0.1.26 → 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.md +4 -2
- package/README.zh-CN.md +7 -1
- package/dist/plugin.js +292 -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,72 @@ 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
|
-
if (rawClient?.get) return await this.requestRaw("get", { url: "/global/health" });
|
|
295
|
-
const healthEndpoint = this.client.global?.health;
|
|
296
|
-
if (typeof healthEndpoint === "function") return unwrapSdkData(await healthEndpoint.call(this.client.global, SDK_OPTIONS));
|
|
297
|
-
if (!rawClient?.get) throw new Error("OpenCode SDK client does not expose a compatible health endpoint.");
|
|
298
|
-
return this.requestRaw("get", { url: "/global/health" });
|
|
298
|
+
return this.callScopedSdkMethod("global", "health", {});
|
|
299
299
|
}
|
|
300
300
|
async abortSession(sessionId) {
|
|
301
|
-
|
|
302
|
-
return this.callScopedSdkMethod("session", "abort", {
|
|
303
|
-
legacyOptions: { path: { id: sessionId } },
|
|
304
|
-
v2Parameters: { sessionID: sessionId }
|
|
305
|
-
});
|
|
301
|
+
return this.callScopedSdkMethod("session", "abort", { parameters: { sessionID: sessionId } });
|
|
306
302
|
}
|
|
307
303
|
async deleteSession(sessionId) {
|
|
308
|
-
|
|
309
|
-
return this.callScopedSdkMethod("session", "delete", {
|
|
310
|
-
legacyOptions: { path: { id: sessionId } },
|
|
311
|
-
v2Parameters: { sessionID: sessionId }
|
|
312
|
-
});
|
|
304
|
+
return this.callScopedSdkMethod("session", "delete", { parameters: { sessionID: sessionId } });
|
|
313
305
|
}
|
|
314
306
|
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
|
-
});
|
|
307
|
+
return this.callScopedSdkMethod("session", "fork", { parameters: {
|
|
308
|
+
sessionID: sessionId,
|
|
309
|
+
...messageId?.trim() ? { messageID: messageId.trim() } : {}
|
|
310
|
+
} });
|
|
326
311
|
}
|
|
327
312
|
async getPath() {
|
|
328
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/path" });
|
|
329
313
|
return this.callScopedSdkMethod("path", "get", {});
|
|
330
314
|
}
|
|
331
315
|
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
|
-
});
|
|
316
|
+
return this.callScopedSdkMethod("lsp", "status", { parameters: directory ? { directory } : void 0 });
|
|
340
317
|
}
|
|
341
318
|
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
|
-
});
|
|
319
|
+
return this.callScopedSdkMethod("mcp", "status", { parameters: directory ? { directory } : void 0 });
|
|
350
320
|
}
|
|
351
321
|
async getSessionStatuses() {
|
|
352
322
|
return this.loadSessionStatuses();
|
|
353
323
|
}
|
|
354
324
|
async listProjects() {
|
|
355
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project" });
|
|
356
325
|
return this.callScopedSdkMethod("project", "list", {});
|
|
357
326
|
}
|
|
358
327
|
async listSessions() {
|
|
359
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/session" });
|
|
360
328
|
return this.callScopedSdkMethod("session", "list", {});
|
|
361
329
|
}
|
|
362
330
|
async getCurrentProject() {
|
|
363
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project/current" });
|
|
364
331
|
return this.callScopedSdkMethod("project", "current", {});
|
|
365
332
|
}
|
|
366
333
|
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
|
-
});
|
|
334
|
+
return this.callScopedSdkMethod("session", "create", { parameters: title ? {
|
|
335
|
+
directory,
|
|
336
|
+
title
|
|
337
|
+
} : { directory } });
|
|
382
338
|
}
|
|
383
339
|
async renameSession(sessionId, title) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
body: { title }
|
|
389
|
-
},
|
|
390
|
-
v2Parameters: {
|
|
391
|
-
sessionID: sessionId,
|
|
392
|
-
title
|
|
393
|
-
}
|
|
394
|
-
});
|
|
340
|
+
return this.callScopedSdkMethod("session", "update", { parameters: {
|
|
341
|
+
sessionID: sessionId,
|
|
342
|
+
title
|
|
343
|
+
} });
|
|
395
344
|
}
|
|
396
345
|
async listAgents() {
|
|
397
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/agent" });
|
|
398
346
|
return this.callScopedSdkMethod("app", "agents", {});
|
|
399
347
|
}
|
|
400
348
|
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
|
-
});
|
|
349
|
+
return this.callScopedSdkMethod("permission", "list", { parameters: directory ? { directory } : void 0 });
|
|
409
350
|
}
|
|
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
|
-
});
|
|
351
|
+
async replyToPermission(requestId, reply, message, _sessionId) {
|
|
352
|
+
return this.callScopedSdkMethod("permission", "reply", { parameters: {
|
|
353
|
+
requestID: requestId,
|
|
354
|
+
reply,
|
|
355
|
+
...message?.trim() ? { message: message.trim() } : {}
|
|
356
|
+
} });
|
|
445
357
|
}
|
|
446
358
|
async listModels() {
|
|
447
359
|
const now = Date.now();
|
|
@@ -466,9 +378,10 @@ var OpenCodeClient = class {
|
|
|
466
378
|
url: file.url
|
|
467
379
|
}))];
|
|
468
380
|
if (parts.length === 0) throw new Error("Prompt requires text or file attachments.");
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
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), {
|
|
472
385
|
emptyResponseText: EMPTY_RESPONSE_TEXT,
|
|
473
386
|
finishedAt: Date.now(),
|
|
474
387
|
startedAt,
|
|
@@ -488,10 +401,12 @@ var OpenCodeClient = class {
|
|
|
488
401
|
};
|
|
489
402
|
let bestCandidate = selectPromptResponseCandidate(data ? [data] : [], candidateOptions);
|
|
490
403
|
let lastProgressAt = Date.now();
|
|
404
|
+
let lastStatus = null;
|
|
491
405
|
const deadlineAt = startedAt + this.promptTimeoutPolicy.waitTimeoutMs;
|
|
492
406
|
let idleStatusSeen = false;
|
|
493
407
|
let attempt = 0;
|
|
494
408
|
while (true) {
|
|
409
|
+
throwIfAborted(input.signal);
|
|
495
410
|
const remainingWaitMs = deadlineAt - Date.now();
|
|
496
411
|
const remainingInactivityMs = this.promptTimeoutPolicy.recoveryInactivityTimeoutMs - (Date.now() - lastProgressAt);
|
|
497
412
|
if (remainingWaitMs <= 0 || remainingInactivityMs <= 0) break;
|
|
@@ -500,10 +415,10 @@ var OpenCodeClient = class {
|
|
|
500
415
|
if (delayMs > 0) {
|
|
501
416
|
const remainingMs = Math.min(remainingWaitMs, remainingInactivityMs);
|
|
502
417
|
if (remainingMs <= 0) break;
|
|
503
|
-
await delay(Math.min(delayMs, remainingMs));
|
|
418
|
+
await delay(Math.min(delayMs, remainingMs), input.signal);
|
|
504
419
|
}
|
|
505
420
|
if (messageId) {
|
|
506
|
-
const next = await this.fetchPromptMessage(input.sessionId, messageId);
|
|
421
|
+
const next = await this.fetchPromptMessage(input.sessionId, messageId, input.signal);
|
|
507
422
|
if (next) {
|
|
508
423
|
const nextCandidate = selectPromptResponseCandidate([bestCandidate, next], candidateOptions);
|
|
509
424
|
if (nextCandidate) {
|
|
@@ -513,10 +428,10 @@ var OpenCodeClient = class {
|
|
|
513
428
|
}
|
|
514
429
|
bestCandidate = nextCandidate;
|
|
515
430
|
}
|
|
516
|
-
if (bestCandidate && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
431
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
517
432
|
}
|
|
518
433
|
}
|
|
519
|
-
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages");
|
|
434
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages", input.signal);
|
|
520
435
|
if (latest) {
|
|
521
436
|
const nextCandidate = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
|
|
522
437
|
if (nextCandidate) {
|
|
@@ -526,9 +441,10 @@ var OpenCodeClient = class {
|
|
|
526
441
|
}
|
|
527
442
|
bestCandidate = nextCandidate;
|
|
528
443
|
}
|
|
529
|
-
if (bestCandidate && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
444
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
|
|
530
445
|
}
|
|
531
|
-
const status = await this.fetchPromptSessionStatus(input.sessionId);
|
|
446
|
+
const status = await this.fetchPromptSessionStatus(input.sessionId, input.signal);
|
|
447
|
+
lastStatus = status;
|
|
532
448
|
if (status?.type === "busy" || status?.type === "retry") {
|
|
533
449
|
lastProgressAt = Date.now();
|
|
534
450
|
idleStatusSeen = false;
|
|
@@ -536,12 +452,14 @@ var OpenCodeClient = class {
|
|
|
536
452
|
if (idleStatusSeen) break;
|
|
537
453
|
idleStatusSeen = true;
|
|
538
454
|
}
|
|
539
|
-
if (bestCandidate && isCompletedEmptyPromptResponse(bestCandidate, structured) && status?.type !== "busy" && status?.type !== "retry") break;
|
|
455
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && isCompletedEmptyPromptResponse(bestCandidate, structured) && status?.type !== "busy" && status?.type !== "retry") break;
|
|
540
456
|
if (Date.now() >= deadlineAt) break;
|
|
541
457
|
}
|
|
542
|
-
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan");
|
|
458
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan", input.signal);
|
|
543
459
|
const resolved = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
|
|
544
|
-
|
|
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)) {
|
|
545
463
|
const timeoutReason = Date.now() >= deadlineAt ? "max-wait" : "recovery-inactivity";
|
|
546
464
|
const timeoutMs = timeoutReason === "max-wait" ? this.promptTimeoutPolicy.waitTimeoutMs : this.promptTimeoutPolicy.recoveryInactivityTimeoutMs;
|
|
547
465
|
const error = createOpenCodePromptTimeoutError({
|
|
@@ -553,7 +471,6 @@ var OpenCodeClient = class {
|
|
|
553
471
|
this.logPromptRequest("warn", {
|
|
554
472
|
lastProgressAt,
|
|
555
473
|
messageId: messageId ?? void 0,
|
|
556
|
-
sdkFlavor: this.sdkFlavor,
|
|
557
474
|
sessionId: input.sessionId,
|
|
558
475
|
stage: "final-scan",
|
|
559
476
|
timeoutMs,
|
|
@@ -561,31 +478,26 @@ var OpenCodeClient = class {
|
|
|
561
478
|
}, "OpenCode prompt recovery timed out");
|
|
562
479
|
throw error;
|
|
563
480
|
}
|
|
564
|
-
return
|
|
481
|
+
return requestScopedResolved;
|
|
565
482
|
}
|
|
566
|
-
async fetchPromptMessage(sessionId, messageId) {
|
|
483
|
+
async fetchPromptMessage(sessionId, messageId, signal) {
|
|
567
484
|
try {
|
|
568
485
|
return await this.runPromptRequestWithTimeout({
|
|
569
486
|
sessionId,
|
|
570
487
|
stage: "poll-message",
|
|
571
488
|
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs,
|
|
572
489
|
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;
|
|
490
|
+
}, async (requestSignal) => {
|
|
576
491
|
return normalizePromptResponse(await this.callScopedSdkMethod("session", "message", {
|
|
577
|
-
|
|
578
|
-
id: sessionId,
|
|
579
|
-
messageID: messageId
|
|
580
|
-
} },
|
|
581
|
-
signal,
|
|
582
|
-
v2Parameters: {
|
|
492
|
+
parameters: {
|
|
583
493
|
sessionID: sessionId,
|
|
584
494
|
messageID: messageId
|
|
585
|
-
}
|
|
495
|
+
},
|
|
496
|
+
signal: requestSignal
|
|
586
497
|
}));
|
|
587
|
-
});
|
|
498
|
+
}, signal);
|
|
588
499
|
} catch (error) {
|
|
500
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
589
501
|
this.logPromptRequestFailure(error, {
|
|
590
502
|
sessionId,
|
|
591
503
|
stage: "poll-message",
|
|
@@ -595,36 +507,28 @@ var OpenCodeClient = class {
|
|
|
595
507
|
return null;
|
|
596
508
|
}
|
|
597
509
|
}
|
|
598
|
-
async captureKnownMessageIds(sessionId) {
|
|
599
|
-
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);
|
|
600
512
|
if (!messages) return /* @__PURE__ */ new Set();
|
|
601
513
|
return new Set(messages.map((message) => extractMessageId(message.info)).filter((id) => typeof id === "string" && id.length > 0));
|
|
602
514
|
}
|
|
603
|
-
async fetchRecentPromptMessages(sessionId, stage) {
|
|
515
|
+
async fetchRecentPromptMessages(sessionId, stage, signal) {
|
|
604
516
|
try {
|
|
605
517
|
return await this.runPromptRequestWithTimeout({
|
|
606
518
|
sessionId,
|
|
607
519
|
stage,
|
|
608
520
|
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;
|
|
521
|
+
}, async (requestSignal) => {
|
|
615
522
|
return normalizePromptResponses(await this.callScopedSdkMethod("session", "messages", {
|
|
616
|
-
|
|
617
|
-
path: { id: sessionId },
|
|
618
|
-
query: { limit: PROMPT_MESSAGE_POLL_LIMIT }
|
|
619
|
-
},
|
|
620
|
-
signal,
|
|
621
|
-
v2Parameters: {
|
|
523
|
+
parameters: {
|
|
622
524
|
sessionID: sessionId,
|
|
623
525
|
limit: PROMPT_MESSAGE_POLL_LIMIT
|
|
624
|
-
}
|
|
526
|
+
},
|
|
527
|
+
signal: requestSignal
|
|
625
528
|
}));
|
|
626
|
-
});
|
|
529
|
+
}, signal);
|
|
627
530
|
} catch (error) {
|
|
531
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
628
532
|
this.logPromptRequestFailure(error, {
|
|
629
533
|
sessionId,
|
|
630
534
|
stage,
|
|
@@ -633,14 +537,15 @@ var OpenCodeClient = class {
|
|
|
633
537
|
return null;
|
|
634
538
|
}
|
|
635
539
|
}
|
|
636
|
-
async fetchPromptSessionStatus(sessionId) {
|
|
540
|
+
async fetchPromptSessionStatus(sessionId, signal) {
|
|
637
541
|
try {
|
|
638
542
|
return (await this.runPromptRequestWithTimeout({
|
|
639
543
|
sessionId,
|
|
640
544
|
stage: "poll-status",
|
|
641
545
|
timeoutMs: this.promptTimeoutPolicy.pollRequestTimeoutMs
|
|
642
|
-
}, async (
|
|
546
|
+
}, async (requestSignal) => this.loadSessionStatuses(requestSignal), signal))[sessionId] ?? null;
|
|
643
547
|
} catch (error) {
|
|
548
|
+
if (isPromptRequestAbort(error)) throw error;
|
|
644
549
|
this.logPromptRequestFailure(error, {
|
|
645
550
|
sessionId,
|
|
646
551
|
stage: "poll-status",
|
|
@@ -649,8 +554,8 @@ var OpenCodeClient = class {
|
|
|
649
554
|
return null;
|
|
650
555
|
}
|
|
651
556
|
}
|
|
652
|
-
async findLatestPromptResponse(sessionId, options, stage) {
|
|
653
|
-
const messages = await this.fetchRecentPromptMessages(sessionId, stage);
|
|
557
|
+
async findLatestPromptResponse(sessionId, options, stage, signal) {
|
|
558
|
+
const messages = await this.fetchRecentPromptMessages(sessionId, stage, signal);
|
|
654
559
|
if (!messages || messages.length === 0) return null;
|
|
655
560
|
return selectPromptResponseCandidate(messages, options);
|
|
656
561
|
}
|
|
@@ -666,11 +571,9 @@ var OpenCodeClient = class {
|
|
|
666
571
|
return models;
|
|
667
572
|
}
|
|
668
573
|
async loadConfig() {
|
|
669
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/config" });
|
|
670
574
|
return this.callScopedSdkMethod("config", "get", {});
|
|
671
575
|
}
|
|
672
576
|
async loadProviderCatalog() {
|
|
673
|
-
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/config/providers" });
|
|
674
577
|
return this.callScopedSdkMethod("config", "providers", {});
|
|
675
578
|
}
|
|
676
579
|
async sendPromptRequest(input, parts) {
|
|
@@ -686,26 +589,6 @@ var OpenCodeClient = class {
|
|
|
686
589
|
...requestBody
|
|
687
590
|
};
|
|
688
591
|
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
592
|
if (typeof this.client.session?.promptAsync === "function") {
|
|
710
593
|
await this.runPromptRequestWithTimeout({
|
|
711
594
|
sessionId: input.sessionId,
|
|
@@ -713,15 +596,11 @@ var OpenCodeClient = class {
|
|
|
713
596
|
timeoutMs: this.promptTimeoutPolicy.waitTimeoutMs
|
|
714
597
|
}, async (signal) => {
|
|
715
598
|
await this.callScopedSdkMethod("session", "promptAsync", {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
path: { id: input.sessionId }
|
|
719
|
-
},
|
|
720
|
-
signal,
|
|
721
|
-
v2Parameters: requestParameters
|
|
599
|
+
parameters: requestParameters,
|
|
600
|
+
signal
|
|
722
601
|
});
|
|
723
|
-
});
|
|
724
|
-
return
|
|
602
|
+
}, input.signal);
|
|
603
|
+
return;
|
|
725
604
|
}
|
|
726
605
|
} catch (error) {
|
|
727
606
|
this.logPromptRequestFailure(error, {
|
|
@@ -731,27 +610,33 @@ var OpenCodeClient = class {
|
|
|
731
610
|
});
|
|
732
611
|
throw error;
|
|
733
612
|
}
|
|
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
|
-
}));
|
|
613
|
+
throw new Error("OpenCode SDK client does not expose session.promptAsync().");
|
|
743
614
|
}
|
|
744
615
|
async loadSessionStatuses(signal) {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
616
|
+
return this.callScopedSdkMethod("session", "status", {
|
|
617
|
+
signal,
|
|
618
|
+
parameters: void 0
|
|
748
619
|
});
|
|
749
|
-
return this.callScopedSdkMethod("session", "status", { signal });
|
|
750
620
|
}
|
|
751
|
-
async runPromptRequestWithTimeout(input, operation) {
|
|
621
|
+
async runPromptRequestWithTimeout(input, operation, signal) {
|
|
752
622
|
const startedAt = Date.now();
|
|
753
623
|
const controller = new AbortController();
|
|
624
|
+
const requestSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
754
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;
|
|
755
640
|
const timeoutPromise = new Promise((_, reject) => {
|
|
756
641
|
timeoutHandle = setTimeout(() => {
|
|
757
642
|
reject(createOpenCodePromptTimeoutError({
|
|
@@ -765,9 +650,14 @@ var OpenCodeClient = class {
|
|
|
765
650
|
}, input.timeoutMs);
|
|
766
651
|
});
|
|
767
652
|
try {
|
|
768
|
-
return await Promise.race([
|
|
653
|
+
return await Promise.race([
|
|
654
|
+
operation(requestSignal),
|
|
655
|
+
timeoutPromise,
|
|
656
|
+
...abortPromise ? [abortPromise] : []
|
|
657
|
+
]);
|
|
769
658
|
} finally {
|
|
770
659
|
if (timeoutHandle !== null) clearTimeout(timeoutHandle);
|
|
660
|
+
removeAbortListener();
|
|
771
661
|
}
|
|
772
662
|
}
|
|
773
663
|
logPromptRequestFailure(error, input) {
|
|
@@ -776,7 +666,6 @@ var OpenCodeClient = class {
|
|
|
776
666
|
endpointKind: resolvePromptEndpointKind(error.data.stage),
|
|
777
667
|
elapsedMs: error.data.elapsedMs,
|
|
778
668
|
messageId: error.data.messageId,
|
|
779
|
-
sdkFlavor: this.sdkFlavor,
|
|
780
669
|
sessionId: error.data.sessionId,
|
|
781
670
|
stage: error.data.stage,
|
|
782
671
|
timeoutMs: error.data.timeoutMs
|
|
@@ -787,7 +676,6 @@ var OpenCodeClient = class {
|
|
|
787
676
|
endpointKind: resolvePromptEndpointKind(input.stage),
|
|
788
677
|
error,
|
|
789
678
|
messageId: input.messageId ?? void 0,
|
|
790
|
-
sdkFlavor: this.sdkFlavor,
|
|
791
679
|
sessionId: input.sessionId,
|
|
792
680
|
stage: input.stage,
|
|
793
681
|
timeoutMs: input.timeoutMs
|
|
@@ -978,8 +866,23 @@ function extractMessageId(message) {
|
|
|
978
866
|
if (!isPlainRecord(message)) return null;
|
|
979
867
|
return typeof message.id === "string" && message.id.trim().length > 0 ? message.id : null;
|
|
980
868
|
}
|
|
981
|
-
function delay(ms) {
|
|
982
|
-
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
|
+
});
|
|
983
886
|
}
|
|
984
887
|
function didPromptResponseAdvance(previous, next, structured) {
|
|
985
888
|
return getPromptResponseProgressSignature(previous, structured) !== getPromptResponseProgressSignature(next, structured);
|
|
@@ -1063,46 +966,6 @@ function resolvePromptTimeoutPolicy(input) {
|
|
|
1063
966
|
waitTimeoutMs: input.waitTimeoutMs ?? DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY.waitTimeoutMs
|
|
1064
967
|
};
|
|
1065
968
|
}
|
|
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
969
|
function normalizeAssistantError(value) {
|
|
1107
970
|
if (!isPlainRecord(value) || typeof value.name !== "string" || value.name.trim().length === 0) return;
|
|
1108
971
|
return {
|
|
@@ -1120,23 +983,6 @@ function isCompletedEmptyPromptResponse(data, structured) {
|
|
|
1120
983
|
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
1121
984
|
return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText && !bodyMd;
|
|
1122
985
|
}
|
|
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
986
|
function extractStructuredPayload(message) {
|
|
1141
987
|
if (!isPlainRecord(message)) return null;
|
|
1142
988
|
if ("structured" in message && message.structured !== void 0) return message.structured;
|
|
@@ -1151,7 +997,7 @@ function selectPromptResponseCandidate(candidates, options) {
|
|
|
1151
997
|
function comparePromptResponseCandidates(left, right, options) {
|
|
1152
998
|
const leftRank = getPromptResponseCandidateRank(left, options);
|
|
1153
999
|
const rightRank = getPromptResponseCandidateRank(right, options);
|
|
1154
|
-
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;
|
|
1155
1001
|
}
|
|
1156
1002
|
function getPromptResponseCandidateRank(message, options) {
|
|
1157
1003
|
const assistant = toAssistantMessage(message.info);
|
|
@@ -1166,7 +1012,7 @@ function getPromptResponseCandidateRank(message, options) {
|
|
|
1166
1012
|
};
|
|
1167
1013
|
}
|
|
1168
1014
|
function resolvePromptCandidateStartTime(startedAt, initialMessage) {
|
|
1169
|
-
if (!initialMessage) return
|
|
1015
|
+
if (!initialMessage) return startedAt;
|
|
1170
1016
|
const initialCreatedAt = coerceFiniteNumber(toAssistantMessage(initialMessage.info)?.time?.created);
|
|
1171
1017
|
if (initialCreatedAt === null) return startedAt;
|
|
1172
1018
|
return areComparablePromptTimestamps(startedAt, initialCreatedAt) ? startedAt : initialCreatedAt;
|
|
@@ -1190,10 +1036,28 @@ function isPromptResponseNewSinceRequestStart(messageId, createdAt, knownMessage
|
|
|
1190
1036
|
if (requestStartedAt === null) return true;
|
|
1191
1037
|
return createdAt >= requestStartedAt;
|
|
1192
1038
|
}
|
|
1039
|
+
function isPromptResponseForCurrentRequest(response, options) {
|
|
1040
|
+
const rank = getPromptResponseCandidateRank(response, options);
|
|
1041
|
+
return rank.isInitial || rank.sharesParent || rank.isNewSinceRequestStart;
|
|
1042
|
+
}
|
|
1193
1043
|
function areComparablePromptTimestamps(left, right) {
|
|
1194
1044
|
const epochThresholdMs = 0xe8d4a51000;
|
|
1195
1045
|
return left >= epochThresholdMs && right >= epochThresholdMs;
|
|
1196
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
|
+
}
|
|
1197
1061
|
function isPlainRecord(value) {
|
|
1198
1062
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1199
1063
|
}
|
|
@@ -1361,85 +1225,119 @@ function extractErrorMessage(error) {
|
|
|
1361
1225
|
//#endregion
|
|
1362
1226
|
//#region src/services/session-activity/foreground-session-tracker.ts
|
|
1363
1227
|
var ForegroundSessionTracker = class {
|
|
1364
|
-
|
|
1365
|
-
counts = /* @__PURE__ */ new Map();
|
|
1228
|
+
requests = /* @__PURE__ */ new Map();
|
|
1366
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
|
+
}
|
|
1367
1254
|
begin(chatId, sessionId) {
|
|
1368
|
-
const
|
|
1369
|
-
|
|
1370
|
-
|
|
1255
|
+
const lease = this.acquire(chatId);
|
|
1256
|
+
if (!lease) return () => void 0;
|
|
1257
|
+
lease.attachSession(sessionId);
|
|
1371
1258
|
return () => {
|
|
1372
|
-
|
|
1259
|
+
lease.dispose();
|
|
1373
1260
|
};
|
|
1374
1261
|
}
|
|
1375
1262
|
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);
|
|
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;
|
|
1389
1268
|
}
|
|
1390
|
-
|
|
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;
|
|
1391
1278
|
}
|
|
1392
1279
|
getActiveSessionId(chatId) {
|
|
1393
|
-
return this.
|
|
1280
|
+
return this.requests.get(chatId)?.sessionId ?? null;
|
|
1281
|
+
}
|
|
1282
|
+
hasActiveRequest(chatId) {
|
|
1283
|
+
return this.requests.has(chatId);
|
|
1394
1284
|
}
|
|
1395
1285
|
isForeground(sessionId) {
|
|
1396
|
-
return this.
|
|
1286
|
+
return this.sessionChats.has(sessionId);
|
|
1397
1287
|
}
|
|
1398
1288
|
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
|
-
|
|
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;
|
|
1423
1315
|
}
|
|
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);
|
|
1316
|
+
this.sessionChats.set(sessionId, chatIds);
|
|
1431
1317
|
}
|
|
1432
1318
|
};
|
|
1433
1319
|
var NOOP_FOREGROUND_SESSION_TRACKER = {
|
|
1320
|
+
acquire() {
|
|
1321
|
+
return null;
|
|
1322
|
+
},
|
|
1323
|
+
abort() {
|
|
1324
|
+
return false;
|
|
1325
|
+
},
|
|
1434
1326
|
begin() {
|
|
1435
1327
|
return () => void 0;
|
|
1436
1328
|
},
|
|
1437
1329
|
clear() {
|
|
1438
1330
|
return false;
|
|
1439
1331
|
},
|
|
1332
|
+
fail() {
|
|
1333
|
+
return false;
|
|
1334
|
+
},
|
|
1440
1335
|
getActiveSessionId() {
|
|
1441
1336
|
return null;
|
|
1442
1337
|
},
|
|
1338
|
+
hasActiveRequest() {
|
|
1339
|
+
return false;
|
|
1340
|
+
},
|
|
1443
1341
|
isForeground() {
|
|
1444
1342
|
return false;
|
|
1445
1343
|
},
|
|
@@ -1456,23 +1354,40 @@ var AbortPromptUseCase = class {
|
|
|
1456
1354
|
this.foregroundSessionTracker = foregroundSessionTracker;
|
|
1457
1355
|
}
|
|
1458
1356
|
async execute(input) {
|
|
1357
|
+
const hasForegroundRequest = this.foregroundSessionTracker.hasActiveRequest(input.chatId);
|
|
1459
1358
|
const activeSessionId = this.foregroundSessionTracker.getActiveSessionId(input.chatId);
|
|
1460
1359
|
const binding = activeSessionId ? null : await this.sessionRepo.getByChatId(input.chatId);
|
|
1461
1360
|
const sessionId = activeSessionId ?? binding?.sessionId ?? null;
|
|
1462
|
-
if (!sessionId) return {
|
|
1361
|
+
if (!hasForegroundRequest && !sessionId) return {
|
|
1463
1362
|
sessionId: null,
|
|
1464
1363
|
status: "no_session",
|
|
1465
1364
|
sessionStatus: null
|
|
1466
1365
|
};
|
|
1467
|
-
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
|
+
}
|
|
1468
1377
|
if (!sessionStatus || sessionStatus.type === "idle") return {
|
|
1469
1378
|
sessionId,
|
|
1470
1379
|
status: "not_running",
|
|
1471
1380
|
sessionStatus
|
|
1472
1381
|
};
|
|
1382
|
+
if (!sessionId) return {
|
|
1383
|
+
sessionId: null,
|
|
1384
|
+
status: "not_running",
|
|
1385
|
+
sessionStatus
|
|
1386
|
+
};
|
|
1387
|
+
const runningSessionId = sessionId;
|
|
1473
1388
|
return {
|
|
1474
|
-
sessionId,
|
|
1475
|
-
status: await this.opencodeClient.abortSession(
|
|
1389
|
+
sessionId: runningSessionId,
|
|
1390
|
+
status: await this.opencodeClient.abortSession(runningSessionId) ? "aborted" : "not_running",
|
|
1476
1391
|
sessionStatus
|
|
1477
1392
|
};
|
|
1478
1393
|
}
|
|
@@ -1914,11 +1829,10 @@ var RenameSessionUseCase = class {
|
|
|
1914
1829
|
//#endregion
|
|
1915
1830
|
//#region src/use-cases/send-prompt.usecase.ts
|
|
1916
1831
|
var SendPromptUseCase = class {
|
|
1917
|
-
constructor(sessionRepo, opencodeClient, logger
|
|
1832
|
+
constructor(sessionRepo, opencodeClient, logger) {
|
|
1918
1833
|
this.sessionRepo = sessionRepo;
|
|
1919
1834
|
this.opencodeClient = opencodeClient;
|
|
1920
1835
|
this.logger = logger;
|
|
1921
|
-
this.foregroundSessionTracker = foregroundSessionTracker;
|
|
1922
1836
|
}
|
|
1923
1837
|
async execute(input) {
|
|
1924
1838
|
const files = input.files ?? [];
|
|
@@ -1972,7 +1886,7 @@ var SendPromptUseCase = class {
|
|
|
1972
1886
|
}
|
|
1973
1887
|
const temporarySessionId = shouldIsolateImageTurn ? await this.createTemporaryImageSession(input.chatId, activeBinding.sessionId) : null;
|
|
1974
1888
|
const executionSessionId = temporarySessionId ?? activeBinding.sessionId;
|
|
1975
|
-
|
|
1889
|
+
input.onExecutionSession?.(executionSessionId);
|
|
1976
1890
|
let result;
|
|
1977
1891
|
try {
|
|
1978
1892
|
result = await this.opencodeClient.promptSession({
|
|
@@ -1982,10 +1896,10 @@ var SendPromptUseCase = class {
|
|
|
1982
1896
|
...selectedAgent ? { agent: selectedAgent.name } : {},
|
|
1983
1897
|
structured: true,
|
|
1984
1898
|
...model ? { model } : {},
|
|
1899
|
+
...input.signal ? { signal: input.signal } : {},
|
|
1985
1900
|
...activeBinding.modelVariant ? { variant: activeBinding.modelVariant } : {}
|
|
1986
1901
|
});
|
|
1987
1902
|
} finally {
|
|
1988
|
-
endForegroundSession();
|
|
1989
1903
|
if (temporarySessionId) await this.cleanupTemporaryImageSession(input.chatId, activeBinding.sessionId, temporarySessionId);
|
|
1990
1904
|
}
|
|
1991
1905
|
await this.sessionRepo.touch(input.chatId);
|
|
@@ -2253,7 +2167,7 @@ function createContainer(config, opencodeClient, logger) {
|
|
|
2253
2167
|
const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo);
|
|
2254
2168
|
const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
|
|
2255
2169
|
const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, logger);
|
|
2256
|
-
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger
|
|
2170
|
+
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, logger);
|
|
2257
2171
|
const switchAgentUseCase = new SwitchAgentUseCase(sessionRepo, opencodeClient, logger);
|
|
2258
2172
|
const switchModelUseCase = new SwitchModelUseCase(sessionRepo, opencodeClient, logger);
|
|
2259
2173
|
const switchSessionUseCase = new SwitchSessionUseCase(sessionRepo, opencodeClient, logger);
|
|
@@ -2360,21 +2274,25 @@ function escapeMarkdownV2(value) {
|
|
|
2360
2274
|
async function handleTelegramBotPluginEvent(runtime, event) {
|
|
2361
2275
|
switch (event.type) {
|
|
2362
2276
|
case "permission.asked":
|
|
2363
|
-
await handlePermissionAsked(runtime, event
|
|
2277
|
+
await handlePermissionAsked(runtime, event);
|
|
2364
2278
|
return;
|
|
2365
2279
|
case "permission.replied":
|
|
2366
|
-
await handlePermissionReplied(runtime, event
|
|
2280
|
+
await handlePermissionReplied(runtime, event);
|
|
2367
2281
|
return;
|
|
2368
2282
|
case "session.error":
|
|
2369
|
-
await handleSessionError(runtime, event
|
|
2283
|
+
await handleSessionError(runtime, event);
|
|
2370
2284
|
return;
|
|
2371
2285
|
case "session.idle":
|
|
2372
|
-
await handleSessionIdle(runtime, event
|
|
2286
|
+
await handleSessionIdle(runtime, event);
|
|
2287
|
+
return;
|
|
2288
|
+
case "session.status":
|
|
2289
|
+
await handleSessionStatus(runtime, event);
|
|
2373
2290
|
return;
|
|
2374
2291
|
default: return;
|
|
2375
2292
|
}
|
|
2376
2293
|
}
|
|
2377
|
-
async function handlePermissionAsked(runtime,
|
|
2294
|
+
async function handlePermissionAsked(runtime, event) {
|
|
2295
|
+
const request = event.properties;
|
|
2378
2296
|
const bindings = await runtime.container.sessionRepo.listBySessionId(request.sessionID);
|
|
2379
2297
|
const chatIds = new Set([...bindings.map((binding) => binding.chatId), ...runtime.container.foregroundSessionTracker.listChatIds(request.sessionID)]);
|
|
2380
2298
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(request.id);
|
|
@@ -2403,7 +2321,9 @@ async function handlePermissionAsked(runtime, request) {
|
|
|
2403
2321
|
}
|
|
2404
2322
|
}
|
|
2405
2323
|
}
|
|
2406
|
-
async function handlePermissionReplied(runtime,
|
|
2324
|
+
async function handlePermissionReplied(runtime, event) {
|
|
2325
|
+
const requestId = event.properties.requestID;
|
|
2326
|
+
const reply = event.properties.reply;
|
|
2407
2327
|
const approvals = await runtime.container.permissionApprovalRepo.listByRequestId(requestId);
|
|
2408
2328
|
await Promise.all(approvals.map(async (approval) => {
|
|
2409
2329
|
try {
|
|
@@ -2418,27 +2338,37 @@ async function handlePermissionReplied(runtime, requestId, reply) {
|
|
|
2418
2338
|
await runtime.container.permissionApprovalRepo.set(toResolvedApproval(approval, reply));
|
|
2419
2339
|
}));
|
|
2420
2340
|
}
|
|
2421
|
-
async function handleSessionError(runtime,
|
|
2341
|
+
async function handleSessionError(runtime, event) {
|
|
2342
|
+
const sessionId = event.properties.sessionID;
|
|
2343
|
+
const error = event.properties.error;
|
|
2422
2344
|
if (!sessionId) {
|
|
2423
2345
|
runtime.container.logger.error({ error }, "session error received without a session id");
|
|
2424
2346
|
return;
|
|
2425
2347
|
}
|
|
2426
|
-
if (runtime.container.foregroundSessionTracker.
|
|
2348
|
+
if (runtime.container.foregroundSessionTracker.fail(sessionId, error ?? /* @__PURE__ */ new Error("Unknown session error."))) {
|
|
2427
2349
|
runtime.container.logger.warn({
|
|
2428
2350
|
error,
|
|
2429
2351
|
sessionId
|
|
2430
2352
|
}, "session error suppressed for foreground Telegram session");
|
|
2431
2353
|
return;
|
|
2432
2354
|
}
|
|
2433
|
-
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."}`);
|
|
2434
2356
|
}
|
|
2435
|
-
async function handleSessionIdle(runtime,
|
|
2357
|
+
async function handleSessionIdle(runtime, event) {
|
|
2358
|
+
const sessionId = event.properties.sessionID;
|
|
2436
2359
|
if (runtime.container.foregroundSessionTracker.clear(sessionId)) {
|
|
2437
2360
|
runtime.container.logger.info({ sessionId }, "session idle notification suppressed for foreground Telegram session");
|
|
2438
2361
|
return;
|
|
2439
2362
|
}
|
|
2440
2363
|
await notifyBoundChats(runtime, sessionId, `Session finished.\n\nSession: ${sessionId}`);
|
|
2441
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
|
+
}
|
|
2442
2372
|
async function notifyBoundChats(runtime, sessionId, text) {
|
|
2443
2373
|
const bindings = await runtime.container.sessionRepo.listBySessionId(sessionId);
|
|
2444
2374
|
const chatIds = [...new Set(bindings.map((binding) => binding.chatId))];
|
|
@@ -4726,23 +4656,26 @@ function parseSessionActionTarget(data, prefix) {
|
|
|
4726
4656
|
}
|
|
4727
4657
|
//#endregion
|
|
4728
4658
|
//#region src/bot/handlers/prompt.handler.ts
|
|
4729
|
-
var activePromptChats = /* @__PURE__ */ new Set();
|
|
4730
4659
|
async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
4731
4660
|
const copy = await getChatCopy(dependencies.sessionRepo, ctx.chat.id);
|
|
4732
|
-
|
|
4661
|
+
const foregroundRequest = dependencies.foregroundSessionTracker.acquire(ctx.chat.id);
|
|
4662
|
+
if (!foregroundRequest) {
|
|
4733
4663
|
await ctx.reply(copy.status.alreadyProcessing);
|
|
4734
4664
|
return;
|
|
4735
4665
|
}
|
|
4736
4666
|
let processingMessage = null;
|
|
4737
4667
|
let sentTerminalReply = false;
|
|
4738
4668
|
try {
|
|
4739
|
-
activePromptChats.add(ctx.chat.id);
|
|
4740
4669
|
processingMessage = await ctx.reply(copy.status.processing);
|
|
4741
4670
|
const promptInput = await resolvePrompt();
|
|
4742
4671
|
const telegramReply = buildTelegramPromptReply(normalizePromptReplyForDisplay((await dependencies.sendPromptUseCase.execute({
|
|
4743
4672
|
chatId: ctx.chat.id,
|
|
4744
|
-
|
|
4745
|
-
|
|
4673
|
+
files: promptInput.files,
|
|
4674
|
+
onExecutionSession: (sessionId) => {
|
|
4675
|
+
foregroundRequest.attachSession(sessionId);
|
|
4676
|
+
},
|
|
4677
|
+
signal: foregroundRequest.signal,
|
|
4678
|
+
text: promptInput.text
|
|
4746
4679
|
})).assistantReply, copy, dependencies), copy);
|
|
4747
4680
|
try {
|
|
4748
4681
|
await ctx.reply(telegramReply.preferred.text, telegramReply.preferred.options);
|
|
@@ -4756,7 +4689,7 @@ async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
|
4756
4689
|
await ctx.reply(presentError(error, copy));
|
|
4757
4690
|
sentTerminalReply = true;
|
|
4758
4691
|
} finally {
|
|
4759
|
-
|
|
4692
|
+
foregroundRequest.dispose();
|
|
4760
4693
|
if (processingMessage && sentTerminalReply) try {
|
|
4761
4694
|
await ctx.api.deleteMessage(ctx.chat.id, processingMessage.message_id);
|
|
4762
4695
|
} catch (error) {
|