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 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
- if (hasRawSdkMethod(this.client, "get")) return this.requestRaw("get", { url: "/session/status" });
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() + PROMPT_MESSAGE_POLL_TIMEOUT_MS;
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
- return selectPromptResponseCandidate([bestCandidate, latest], candidateOptions) ?? bestCandidate;
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
- if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponse(await this.requestRaw("get", {
432
- url: "/session/{sessionID}/message/{messageID}",
433
- path: {
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
- if (typeof this.client.session.message !== "function") return null;
439
- return normalizePromptResponse(unwrapSdkData(await this.client.session.message({
440
- sessionID: sessionId,
441
- messageID: messageId
442
- }, SDK_OPTIONS)));
443
- } catch {
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
- if (hasRawSdkMethod(this.client, "get")) return normalizePromptResponses(await this.requestRaw("get", {
455
- url: "/session/{sessionID}/message",
456
- path: { sessionID: sessionId },
457
- query: { limit: PROMPT_MESSAGE_POLL_LIMIT }
458
- }));
459
- if (typeof this.client.session.messages !== "function") return null;
460
- return normalizePromptResponses(unwrapSdkData(await this.client.session.messages({
461
- sessionID: sessionId,
462
- limit: PROMPT_MESSAGE_POLL_LIMIT
463
- }, SDK_OPTIONS)));
464
- } catch {
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.getSessionStatuses())[sessionId] ?? null;
471
- } catch {
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
- if (hasRawSdkMethod(this.client, "post")) return normalizePromptResponse(await this.requestRaw("post", {
501
- url: "/session/{sessionID}/message",
502
- path: { sessionID: input.sessionId },
503
- body: {
504
- ...input.agent ? { agent: input.agent } : {},
505
- ...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
506
- ...input.model ? { model: input.model } : {},
507
- ...input.variant ? { variant: input.variant } : {},
508
- parts
509
- }
510
- }));
511
- return normalizePromptResponse(unwrapSdkData(await this.client.session.prompt({
512
- sessionID: input.sessionId,
513
- ...input.agent ? { agent: input.agent } : {},
514
- ...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
515
- ...input.model ? { model: input.model } : {},
516
- ...input.variant ? { variant: input.variant } : {},
517
- parts
518
- }, SDK_OPTIONS)));
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)])