opencode-tbot 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.js +218 -53
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -211,6 +211,14 @@ function buildOpenCodeSdkConfig(options) {
|
|
|
211
211
|
...apiKey ? { auth: apiKey } : {}
|
|
212
212
|
};
|
|
213
213
|
}
|
|
214
|
+
var OpenCodePromptTimeoutError = class extends Error {
|
|
215
|
+
data;
|
|
216
|
+
constructor(data) {
|
|
217
|
+
super(data.message ?? "OpenCode prompt timed out.");
|
|
218
|
+
this.name = "OpenCodePromptTimeoutError";
|
|
219
|
+
this.data = data;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
214
222
|
var EMPTY_RESPONSE_TEXT = "OpenCode returned empty response.";
|
|
215
223
|
var PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS = [
|
|
216
224
|
0,
|
|
@@ -220,8 +228,11 @@ var PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS = [
|
|
|
220
228
|
1e3
|
|
221
229
|
];
|
|
222
230
|
var PROMPT_MESSAGE_POLL_INTERVAL_MS = 2e3;
|
|
231
|
+
var PROMPT_POLL_REQUEST_TIMEOUT_MS = 15e3;
|
|
232
|
+
var PROMPT_SEND_TIMEOUT_MS = 3e4;
|
|
223
233
|
var PROMPT_MESSAGE_POLL_TIMEOUT_MS = 6e4;
|
|
224
234
|
var PROMPT_MESSAGE_POLL_LIMIT = 20;
|
|
235
|
+
var PROMPT_LOG_SERVICE = "opencode-tbot";
|
|
225
236
|
var STRUCTURED_REPLY_SCHEMA = {
|
|
226
237
|
type: "json_schema",
|
|
227
238
|
retryCount: 2,
|
|
@@ -243,6 +254,11 @@ var StructuredReplySchema = z.object({ body_md: z.string() });
|
|
|
243
254
|
var OpenCodeClient = class {
|
|
244
255
|
client;
|
|
245
256
|
fetchFn;
|
|
257
|
+
promptRequestTimeouts = {
|
|
258
|
+
pollRequestMs: PROMPT_POLL_REQUEST_TIMEOUT_MS,
|
|
259
|
+
sendMs: PROMPT_SEND_TIMEOUT_MS,
|
|
260
|
+
totalPollMs: PROMPT_MESSAGE_POLL_TIMEOUT_MS
|
|
261
|
+
};
|
|
246
262
|
modelCache = {
|
|
247
263
|
expiresAt: 0,
|
|
248
264
|
promise: null,
|
|
@@ -287,8 +303,7 @@ var OpenCodeClient = class {
|
|
|
287
303
|
return unwrapSdkData(await this.client.mcp.status(directory ? { directory } : void 0, SDK_OPTIONS));
|
|
288
304
|
}
|
|
289
305
|
async getSessionStatuses() {
|
|
290
|
-
|
|
291
|
-
return unwrapSdkData(await this.client.session.status(void 0, SDK_OPTIONS));
|
|
306
|
+
return this.loadSessionStatuses();
|
|
292
307
|
}
|
|
293
308
|
async listProjects() {
|
|
294
309
|
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/project" });
|
|
@@ -394,7 +409,7 @@ var OpenCodeClient = class {
|
|
|
394
409
|
structured
|
|
395
410
|
};
|
|
396
411
|
let bestCandidate = selectPromptResponseCandidate([data], candidateOptions) ?? data;
|
|
397
|
-
const deadlineAt = Date.now() +
|
|
412
|
+
const deadlineAt = Date.now() + this.promptRequestTimeouts.totalPollMs;
|
|
398
413
|
let idleStatusSeen = false;
|
|
399
414
|
let attempt = 0;
|
|
400
415
|
while (true) {
|
|
@@ -412,7 +427,7 @@ var OpenCodeClient = class {
|
|
|
412
427
|
if (!shouldPollPromptMessage(next, structured)) return bestCandidate;
|
|
413
428
|
}
|
|
414
429
|
}
|
|
415
|
-
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions);
|
|
430
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages");
|
|
416
431
|
if (latest) {
|
|
417
432
|
bestCandidate = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions) ?? bestCandidate;
|
|
418
433
|
if (!shouldPollPromptMessage(bestCandidate, structured)) return bestCandidate;
|
|
@@ -423,57 +438,114 @@ var OpenCodeClient = class {
|
|
|
423
438
|
}
|
|
424
439
|
if (Date.now() >= deadlineAt) break;
|
|
425
440
|
}
|
|
426
|
-
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions);
|
|
427
|
-
|
|
441
|
+
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan");
|
|
442
|
+
const resolved = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions) ?? bestCandidate;
|
|
443
|
+
if (shouldPollPromptMessage(resolved, structured)) {
|
|
444
|
+
const error = createOpenCodePromptTimeoutError({
|
|
445
|
+
sessionId: input.sessionId,
|
|
446
|
+
stage: "final-scan",
|
|
447
|
+
timeoutMs: this.promptRequestTimeouts.totalPollMs,
|
|
448
|
+
messageId: messageId ?? void 0
|
|
449
|
+
});
|
|
450
|
+
this.logPromptRequestFailure(error, {
|
|
451
|
+
sessionId: input.sessionId,
|
|
452
|
+
stage: "final-scan",
|
|
453
|
+
timeoutMs: this.promptRequestTimeouts.totalPollMs,
|
|
454
|
+
messageId
|
|
455
|
+
});
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
return resolved;
|
|
428
459
|
}
|
|
429
460
|
async fetchPromptMessage(sessionId, messageId) {
|
|
430
461
|
try {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
462
|
+
return await this.runPromptRequestWithTimeout({
|
|
463
|
+
sessionId,
|
|
464
|
+
stage: "poll-message",
|
|
465
|
+
timeoutMs: this.promptRequestTimeouts.pollRequestMs,
|
|
466
|
+
messageId
|
|
467
|
+
}, async (signal) => {
|
|
468
|
+
if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponse(await this.requestRaw("get", {
|
|
469
|
+
url: "/session/{sessionID}/message/{messageID}",
|
|
470
|
+
path: {
|
|
471
|
+
sessionID: sessionId,
|
|
472
|
+
messageID: messageId
|
|
473
|
+
},
|
|
474
|
+
signal
|
|
475
|
+
}));
|
|
476
|
+
if (typeof this.client.session.message !== "function") return null;
|
|
477
|
+
return normalizePromptResponse(unwrapSdkData(await this.client.session.message({
|
|
434
478
|
sessionID: sessionId,
|
|
435
479
|
messageID: messageId
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
480
|
+
}, {
|
|
481
|
+
...SDK_OPTIONS,
|
|
482
|
+
signal
|
|
483
|
+
})));
|
|
484
|
+
});
|
|
485
|
+
} catch (error) {
|
|
486
|
+
this.logPromptRequestFailure(error, {
|
|
487
|
+
sessionId,
|
|
488
|
+
stage: "poll-message",
|
|
489
|
+
timeoutMs: this.promptRequestTimeouts.pollRequestMs,
|
|
490
|
+
messageId
|
|
491
|
+
});
|
|
444
492
|
return null;
|
|
445
493
|
}
|
|
446
494
|
}
|
|
447
495
|
async captureKnownMessageIds(sessionId) {
|
|
448
|
-
const messages = await this.fetchRecentPromptMessages(sessionId);
|
|
496
|
+
const messages = await this.fetchRecentPromptMessages(sessionId, "capture-known-messages");
|
|
449
497
|
if (!messages) return /* @__PURE__ */ new Set();
|
|
450
498
|
return new Set(messages.map((message) => extractMessageId(message.info)).filter((id) => typeof id === "string" && id.length > 0));
|
|
451
499
|
}
|
|
452
|
-
async fetchRecentPromptMessages(sessionId) {
|
|
500
|
+
async fetchRecentPromptMessages(sessionId, stage) {
|
|
453
501
|
try {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
502
|
+
return await this.runPromptRequestWithTimeout({
|
|
503
|
+
sessionId,
|
|
504
|
+
stage,
|
|
505
|
+
timeoutMs: this.promptRequestTimeouts.pollRequestMs
|
|
506
|
+
}, async (signal) => {
|
|
507
|
+
if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponses(await this.requestRaw("get", {
|
|
508
|
+
url: "/session/{sessionID}/message",
|
|
509
|
+
path: { sessionID: sessionId },
|
|
510
|
+
query: { limit: PROMPT_MESSAGE_POLL_LIMIT },
|
|
511
|
+
signal
|
|
512
|
+
}));
|
|
513
|
+
if (typeof this.client.session.messages !== "function") return null;
|
|
514
|
+
return normalizePromptResponses(unwrapSdkData(await this.client.session.messages({
|
|
515
|
+
sessionID: sessionId,
|
|
516
|
+
limit: PROMPT_MESSAGE_POLL_LIMIT
|
|
517
|
+
}, {
|
|
518
|
+
...SDK_OPTIONS,
|
|
519
|
+
signal
|
|
520
|
+
})));
|
|
521
|
+
});
|
|
522
|
+
} catch (error) {
|
|
523
|
+
this.logPromptRequestFailure(error, {
|
|
524
|
+
sessionId,
|
|
525
|
+
stage,
|
|
526
|
+
timeoutMs: this.promptRequestTimeouts.pollRequestMs
|
|
527
|
+
});
|
|
465
528
|
return null;
|
|
466
529
|
}
|
|
467
530
|
}
|
|
468
531
|
async fetchPromptSessionStatus(sessionId) {
|
|
469
532
|
try {
|
|
470
|
-
return (await this.
|
|
471
|
-
|
|
533
|
+
return (await this.runPromptRequestWithTimeout({
|
|
534
|
+
sessionId,
|
|
535
|
+
stage: "poll-status",
|
|
536
|
+
timeoutMs: this.promptRequestTimeouts.pollRequestMs
|
|
537
|
+
}, async (signal) => this.loadSessionStatuses(signal)))[sessionId] ?? null;
|
|
538
|
+
} catch (error) {
|
|
539
|
+
this.logPromptRequestFailure(error, {
|
|
540
|
+
sessionId,
|
|
541
|
+
stage: "poll-status",
|
|
542
|
+
timeoutMs: this.promptRequestTimeouts.pollRequestMs
|
|
543
|
+
});
|
|
472
544
|
return null;
|
|
473
545
|
}
|
|
474
546
|
}
|
|
475
|
-
async findLatestPromptResponse(sessionId, options) {
|
|
476
|
-
const messages = await this.fetchRecentPromptMessages(sessionId);
|
|
547
|
+
async findLatestPromptResponse(sessionId, options, stage) {
|
|
548
|
+
const messages = await this.fetchRecentPromptMessages(sessionId, stage);
|
|
477
549
|
if (!messages || messages.length === 0) return null;
|
|
478
550
|
return selectPromptResponseCandidate(messages, options);
|
|
479
551
|
}
|
|
@@ -497,25 +569,44 @@ var OpenCodeClient = class {
|
|
|
497
569
|
return unwrapSdkData(await this.client.config.providers(void 0, SDK_OPTIONS));
|
|
498
570
|
}
|
|
499
571
|
async sendPromptRequest(input, parts) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
572
|
+
try {
|
|
573
|
+
return await this.runPromptRequestWithTimeout({
|
|
574
|
+
sessionId: input.sessionId,
|
|
575
|
+
stage: "send-prompt",
|
|
576
|
+
timeoutMs: this.promptRequestTimeouts.sendMs
|
|
577
|
+
}, async (signal) => {
|
|
578
|
+
if (hasRawSdkMethod(this.client, "post")) return normalizePromptResponse(await this.requestRaw("post", {
|
|
579
|
+
url: "/session/{sessionID}/message",
|
|
580
|
+
path: { sessionID: input.sessionId },
|
|
581
|
+
body: {
|
|
582
|
+
...input.agent ? { agent: input.agent } : {},
|
|
583
|
+
...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
|
|
584
|
+
...input.model ? { model: input.model } : {},
|
|
585
|
+
...input.variant ? { variant: input.variant } : {},
|
|
586
|
+
parts
|
|
587
|
+
},
|
|
588
|
+
signal
|
|
589
|
+
}));
|
|
590
|
+
return normalizePromptResponse(unwrapSdkData(await this.client.session.prompt({
|
|
591
|
+
sessionID: input.sessionId,
|
|
592
|
+
...input.agent ? { agent: input.agent } : {},
|
|
593
|
+
...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
|
|
594
|
+
...input.model ? { model: input.model } : {},
|
|
595
|
+
...input.variant ? { variant: input.variant } : {},
|
|
596
|
+
parts
|
|
597
|
+
}, {
|
|
598
|
+
...SDK_OPTIONS,
|
|
599
|
+
signal
|
|
600
|
+
})));
|
|
601
|
+
});
|
|
602
|
+
} catch (error) {
|
|
603
|
+
this.logPromptRequestFailure(error, {
|
|
604
|
+
sessionId: input.sessionId,
|
|
605
|
+
stage: "send-prompt",
|
|
606
|
+
timeoutMs: this.promptRequestTimeouts.sendMs
|
|
607
|
+
});
|
|
608
|
+
throw error;
|
|
609
|
+
}
|
|
519
610
|
}
|
|
520
611
|
async requestRaw(method, options) {
|
|
521
612
|
const handler = getRawSdkClient(this.client)?.[method];
|
|
@@ -525,6 +616,67 @@ var OpenCodeClient = class {
|
|
|
525
616
|
...options
|
|
526
617
|
}));
|
|
527
618
|
}
|
|
619
|
+
async loadSessionStatuses(signal) {
|
|
620
|
+
if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", {
|
|
621
|
+
url: "/session/status",
|
|
622
|
+
...signal ? { signal } : {}
|
|
623
|
+
});
|
|
624
|
+
return unwrapSdkData(await this.client.session.status(void 0, {
|
|
625
|
+
...SDK_OPTIONS,
|
|
626
|
+
...signal ? { signal } : {}
|
|
627
|
+
}));
|
|
628
|
+
}
|
|
629
|
+
async runPromptRequestWithTimeout(input, operation) {
|
|
630
|
+
const startedAt = Date.now();
|
|
631
|
+
const controller = new AbortController();
|
|
632
|
+
let timeoutHandle = null;
|
|
633
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
634
|
+
timeoutHandle = setTimeout(() => {
|
|
635
|
+
reject(createOpenCodePromptTimeoutError({
|
|
636
|
+
sessionId: input.sessionId,
|
|
637
|
+
stage: input.stage,
|
|
638
|
+
timeoutMs: input.timeoutMs,
|
|
639
|
+
messageId: input.messageId ?? void 0,
|
|
640
|
+
elapsedMs: Date.now() - startedAt
|
|
641
|
+
}));
|
|
642
|
+
controller.abort();
|
|
643
|
+
}, input.timeoutMs);
|
|
644
|
+
});
|
|
645
|
+
try {
|
|
646
|
+
return await Promise.race([operation(controller.signal), timeoutPromise]);
|
|
647
|
+
} finally {
|
|
648
|
+
if (timeoutHandle !== null) clearTimeout(timeoutHandle);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
logPromptRequestFailure(error, input) {
|
|
652
|
+
if (error instanceof OpenCodePromptTimeoutError) {
|
|
653
|
+
this.logPromptRequest("warn", {
|
|
654
|
+
elapsedMs: error.data.elapsedMs,
|
|
655
|
+
messageId: error.data.messageId,
|
|
656
|
+
sessionId: error.data.sessionId,
|
|
657
|
+
stage: error.data.stage,
|
|
658
|
+
timeoutMs: error.data.timeoutMs
|
|
659
|
+
}, "OpenCode prompt request timed out");
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
this.logPromptRequest("warn", {
|
|
663
|
+
error,
|
|
664
|
+
messageId: input.messageId ?? void 0,
|
|
665
|
+
sessionId: input.sessionId,
|
|
666
|
+
stage: input.stage,
|
|
667
|
+
timeoutMs: input.timeoutMs
|
|
668
|
+
}, "OpenCode prompt request failed");
|
|
669
|
+
}
|
|
670
|
+
logPromptRequest(level, extra, message) {
|
|
671
|
+
const log = this.client.app?.log;
|
|
672
|
+
if (typeof log !== "function") return;
|
|
673
|
+
log.call(this.client.app, {
|
|
674
|
+
service: PROMPT_LOG_SERVICE,
|
|
675
|
+
level,
|
|
676
|
+
message,
|
|
677
|
+
extra
|
|
678
|
+
}).catch(() => void 0);
|
|
679
|
+
}
|
|
528
680
|
};
|
|
529
681
|
function createOpenCodeClientFromSdkClient(client, fetchFn = fetch) {
|
|
530
682
|
return new OpenCodeClient(void 0, client, fetchFn);
|
|
@@ -696,6 +848,13 @@ function extractMessageId(message) {
|
|
|
696
848
|
function delay(ms) {
|
|
697
849
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
698
850
|
}
|
|
851
|
+
function createOpenCodePromptTimeoutError(input) {
|
|
852
|
+
return new OpenCodePromptTimeoutError({
|
|
853
|
+
...input,
|
|
854
|
+
elapsedMs: input.elapsedMs ?? input.timeoutMs,
|
|
855
|
+
message: input.message ?? "The OpenCode host did not finish this request in time."
|
|
856
|
+
});
|
|
857
|
+
}
|
|
699
858
|
function getPromptMessagePollDelayMs(attempt) {
|
|
700
859
|
return PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS[attempt] ?? PROMPT_MESSAGE_POLL_INTERVAL_MS;
|
|
701
860
|
}
|
|
@@ -2336,6 +2495,7 @@ var EN_BOT_COPY = {
|
|
|
2336
2495
|
unexpected: "Unexpected error.",
|
|
2337
2496
|
providerAuth: "Provider authentication failed.",
|
|
2338
2497
|
requestAborted: "Request was aborted.",
|
|
2498
|
+
promptTimeout: "OpenCode request timed out.",
|
|
2339
2499
|
structuredOutput: "Structured output validation failed.",
|
|
2340
2500
|
voiceNotConfigured: "Voice transcription is not configured.",
|
|
2341
2501
|
voiceDownload: "Failed to download the Telegram voice file.",
|
|
@@ -2571,6 +2731,7 @@ var ZH_CN_BOT_COPY = {
|
|
|
2571
2731
|
unexpected: "发生未知错误。",
|
|
2572
2732
|
providerAuth: "Provider 认证失败。",
|
|
2573
2733
|
requestAborted: "请求已中止。",
|
|
2734
|
+
promptTimeout: "OpenCode 响应超时。",
|
|
2574
2735
|
structuredOutput: "结构化输出校验失败。",
|
|
2575
2736
|
voiceNotConfigured: "未配置语音转写服务。",
|
|
2576
2737
|
voiceDownload: "下载 Telegram 语音文件失败。",
|
|
@@ -2918,6 +3079,10 @@ function normalizeError(error, copy) {
|
|
|
2918
3079
|
message: copy.errors.requestAborted,
|
|
2919
3080
|
cause: extractMessage(error.data) ?? null
|
|
2920
3081
|
};
|
|
3082
|
+
if (isNamedError(error, "OpenCodePromptTimeoutError")) return {
|
|
3083
|
+
message: copy.errors.promptTimeout,
|
|
3084
|
+
cause: null
|
|
3085
|
+
};
|
|
2921
3086
|
if (isNamedError(error, "StructuredOutputError")) return {
|
|
2922
3087
|
message: copy.errors.structuredOutput,
|
|
2923
3088
|
cause: joinNonEmptyParts([extractMessage(error.data), extractRetries(error.data)])
|