opencode-tbot 0.1.31 → 0.1.32

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
@@ -2,7 +2,6 @@ import { i as OPENCODE_TBOT_VERSION, n as preparePluginConfiguration, o as loadA
2
2
  import { appendFile, mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join } from "node:path";
4
4
  import { parse, printParseErrorCode } from "jsonc-parser";
5
- import { z } from "zod";
6
5
  import { randomUUID } from "node:crypto";
7
6
  import { createOpencodeClient } from "@opencode-ai/sdk";
8
7
  import { run } from "@grammyjs/runner";
@@ -571,6 +570,139 @@ var FileSessionRepository = class {
571
570
  }
572
571
  };
573
572
  //#endregion
573
+ //#region src/infra/utils/markdown-text.ts
574
+ var HTML_TAG_PATTERN = /<\/?[A-Za-z][^>]*>/g;
575
+ function stripMarkdownToPlainText(markdown) {
576
+ const lines = preprocessMarkdownForPlainText(markdown).split("\n");
577
+ const rendered = [];
578
+ for (let index = 0; index < lines.length; index += 1) {
579
+ const tableBlock = consumeMarkdownTable$1(lines, index);
580
+ if (tableBlock) {
581
+ rendered.push(renderTableAsPlainText(tableBlock.rows));
582
+ index = tableBlock.nextIndex - 1;
583
+ continue;
584
+ }
585
+ rendered.push(lines[index] ?? "");
586
+ }
587
+ return rendered.join("\n").replace(/```[A-Za-z0-9_-]*\n?/g, "").replace(/```/g, "").replace(/^#{1,6}\s+/gm, "").replace(/^\s*>\s?/gm, "").replace(/^(\s*)[-+*]\s+/gm, "$1- ").replace(/^(\s*)(\d+)[.)]\s+/gm, "$1$2. ").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/(\*\*|__)(.*?)\1/g, "$2").replace(/(\*|_)(.*?)\1/g, "$2").replace(/`([^`]+)`/g, "$1").replace(HTML_TAG_PATTERN, "").trim();
588
+ }
589
+ function preprocessMarkdownForPlainText(markdown) {
590
+ const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
591
+ const processed = [];
592
+ let activeFence = null;
593
+ for (const line of lines) {
594
+ const fenceMatch = line.match(/^```([A-Za-z0-9_-]+)?\s*$/);
595
+ if (fenceMatch) {
596
+ const language = (fenceMatch[1] ?? "").toLowerCase();
597
+ if (activeFence === "markdown") {
598
+ activeFence = null;
599
+ continue;
600
+ }
601
+ if (activeFence === "plain") {
602
+ processed.push(line);
603
+ activeFence = null;
604
+ continue;
605
+ }
606
+ if (language === "md" || language === "markdown") {
607
+ activeFence = "markdown";
608
+ continue;
609
+ }
610
+ activeFence = "plain";
611
+ processed.push(line);
612
+ continue;
613
+ }
614
+ processed.push(line);
615
+ }
616
+ return processed.join("\n");
617
+ }
618
+ function consumeMarkdownTable$1(lines, startIndex) {
619
+ if (startIndex + 1 >= lines.length) return null;
620
+ const headerCells = parseMarkdownTableRow$1(lines[startIndex] ?? "");
621
+ const separatorCells = parseMarkdownTableSeparator$1(lines[startIndex + 1] ?? "");
622
+ if (!headerCells || !separatorCells || headerCells.length !== separatorCells.length) return null;
623
+ const rows = [headerCells];
624
+ let index = startIndex + 2;
625
+ while (index < lines.length) {
626
+ const rowCells = parseMarkdownTableRow$1(lines[index] ?? "");
627
+ if (!rowCells || rowCells.length !== headerCells.length) break;
628
+ rows.push(rowCells);
629
+ index += 1;
630
+ }
631
+ return {
632
+ rows,
633
+ nextIndex: index
634
+ };
635
+ }
636
+ function parseMarkdownTableRow$1(line) {
637
+ const trimmed = line.trim();
638
+ if (!trimmed.includes("|")) return null;
639
+ const cells = splitMarkdownTableCells$1(trimmed).map((cell) => normalizeTableCell$1(cell));
640
+ return cells.length >= 2 ? cells : null;
641
+ }
642
+ function parseMarkdownTableSeparator$1(line) {
643
+ const cells = splitMarkdownTableCells$1(line.trim());
644
+ if (cells.length < 2) return null;
645
+ return cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim())) ? cells : null;
646
+ }
647
+ function splitMarkdownTableCells$1(line) {
648
+ const content = line.replace(/^\|/, "").replace(/\|$/, "");
649
+ const cells = [];
650
+ let current = "";
651
+ let escaped = false;
652
+ for (const char of content) {
653
+ if (escaped) {
654
+ current += char;
655
+ escaped = false;
656
+ continue;
657
+ }
658
+ if (char === "\\") {
659
+ escaped = true;
660
+ current += char;
661
+ continue;
662
+ }
663
+ if (char === "|") {
664
+ cells.push(current);
665
+ current = "";
666
+ continue;
667
+ }
668
+ current += char;
669
+ }
670
+ cells.push(current);
671
+ return cells;
672
+ }
673
+ function normalizeTableCell$1(cell) {
674
+ return cell.trim().replace(/\\\|/g, "|").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/(\*\*|__)(.*?)\1/g, "$2").replace(/(\*|_)(.*?)\1/g, "$2").replace(/`([^`]+)`/g, "$1").replace(HTML_TAG_PATTERN, "");
675
+ }
676
+ function renderTableAsPlainText(rows) {
677
+ return buildAlignedTableLines$1(rows).join("\n");
678
+ }
679
+ function buildAlignedTableLines$1(rows) {
680
+ const columnWidths = calculateTableColumnWidths$1(rows);
681
+ return [
682
+ formatTableRow$1(rows[0] ?? [], columnWidths),
683
+ columnWidths.map((width) => "-".repeat(Math.max(3, width))).join("-+-"),
684
+ ...rows.slice(1).map((row) => formatTableRow$1(row, columnWidths))
685
+ ];
686
+ }
687
+ function calculateTableColumnWidths$1(rows) {
688
+ return (rows[0] ?? []).map((_, columnIndex) => rows.reduce((maxWidth, row) => Math.max(maxWidth, getDisplayWidth$1(row[columnIndex] ?? "")), 0));
689
+ }
690
+ function formatTableRow$1(row, columnWidths) {
691
+ return row.map((cell, index) => padDisplayWidth$1(cell, columnWidths[index] ?? 0)).join(" | ");
692
+ }
693
+ function padDisplayWidth$1(value, targetWidth) {
694
+ const padding = Math.max(0, targetWidth - getDisplayWidth$1(value));
695
+ return `${value}${" ".repeat(padding)}`;
696
+ }
697
+ function getDisplayWidth$1(value) {
698
+ let width = 0;
699
+ for (const char of value) width += isWideCharacter$1(char.codePointAt(0) ?? 0) ? 2 : 1;
700
+ return width;
701
+ }
702
+ function isWideCharacter$1(codePoint) {
703
+ return codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || codePoint >= 11904 && codePoint <= 42191 && codePoint !== 12351 || codePoint >= 44032 && codePoint <= 55203 || codePoint >= 63744 && codePoint <= 64255 || codePoint >= 65040 && codePoint <= 65049 || codePoint >= 65072 && codePoint <= 65135 || codePoint >= 65280 && codePoint <= 65376 || codePoint >= 65504 && codePoint <= 65510);
704
+ }
705
+ //#endregion
574
706
  //#region src/services/opencode/opencode.client.ts
575
707
  function buildOpenCodeSdkConfig(options) {
576
708
  const apiKey = options.apiKey?.trim();
@@ -614,24 +746,11 @@ var DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY = {
614
746
  recoveryInactivityTimeoutMs: 12e4,
615
747
  waitTimeoutMs: 18e5
616
748
  };
617
- var STRUCTURED_REPLY_SCHEMA = {
618
- type: "json_schema",
619
- retryCount: 2,
620
- schema: {
621
- type: "object",
622
- additionalProperties: false,
623
- required: ["body_md"],
624
- properties: { body_md: {
625
- type: "string",
626
- description: "Markdown body only. Do not include duration, token usage, or any footer. Do not wrap the whole answer in ```md or ```markdown fences. Use Markdown formatting directly unless the user explicitly asks for raw Markdown source."
627
- } }
628
- }
629
- };
630
749
  var SDK_OPTIONS = {
631
750
  responseStyle: "data",
632
751
  throwOnError: true
633
752
  };
634
- var StructuredReplySchema = z.object({ body_md: z.string() });
753
+ var TEXT_OUTPUT_FORMAT = { type: "text" };
635
754
  var OpenCodeClient = class {
636
755
  client;
637
756
  fetchFn;
@@ -819,20 +938,17 @@ var OpenCodeClient = class {
819
938
  return buildPromptSessionResult(await this.resolvePromptResponse(input, null, knownMessageIds, startedAt), {
820
939
  emptyResponseText: EMPTY_RESPONSE_TEXT,
821
940
  finishedAt: Date.now(),
822
- startedAt,
823
- structured: input.structured ?? false
941
+ startedAt
824
942
  });
825
943
  }
826
944
  async resolvePromptResponse(input, data, knownMessageIds, startedAt) {
827
- const structured = input.structured ?? false;
828
- if (data && shouldReturnPromptResponseImmediately(data, structured)) return data;
945
+ if (data && shouldReturnPromptResponseImmediately(data)) return data;
829
946
  const messageId = data ? extractMessageId(data.info) : null;
830
947
  const candidateOptions = {
831
948
  initialMessageId: messageId,
832
949
  initialParentId: data ? toAssistantMessage(data.info)?.parentID ?? null : null,
833
950
  knownMessageIds,
834
- requestStartedAt: resolvePromptCandidateStartTime(startedAt, data),
835
- structured
951
+ requestStartedAt: resolvePromptCandidateStartTime(startedAt, data)
836
952
  };
837
953
  let bestCandidate = selectPromptResponseCandidate(data ? [data] : [], candidateOptions);
838
954
  let lastProgressAt = Date.now();
@@ -857,26 +973,26 @@ var OpenCodeClient = class {
857
973
  if (next) {
858
974
  const nextCandidate = selectPromptResponseCandidate([bestCandidate, next], candidateOptions);
859
975
  if (nextCandidate) {
860
- if (didPromptResponseAdvance(bestCandidate, nextCandidate, structured)) {
976
+ if (didPromptResponseAdvance(bestCandidate, nextCandidate)) {
861
977
  lastProgressAt = Date.now();
862
978
  idleStatusSeen = false;
863
979
  }
864
980
  bestCandidate = nextCandidate;
865
981
  }
866
- if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
982
+ if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate)) return bestCandidate;
867
983
  }
868
984
  }
869
985
  const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages", input.signal);
870
986
  if (latest) {
871
987
  const nextCandidate = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
872
988
  if (nextCandidate) {
873
- if (didPromptResponseAdvance(bestCandidate, nextCandidate, structured)) {
989
+ if (didPromptResponseAdvance(bestCandidate, nextCandidate)) {
874
990
  lastProgressAt = Date.now();
875
991
  idleStatusSeen = false;
876
992
  }
877
993
  bestCandidate = nextCandidate;
878
994
  }
879
- if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate, structured)) return bestCandidate;
995
+ if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate)) return bestCandidate;
880
996
  }
881
997
  const status = await this.fetchPromptSessionStatus(input.sessionId, input.signal);
882
998
  lastStatus = status;
@@ -887,14 +1003,14 @@ var OpenCodeClient = class {
887
1003
  if (idleStatusSeen) break;
888
1004
  idleStatusSeen = true;
889
1005
  }
890
- if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && isCompletedEmptyPromptResponse(bestCandidate, structured) && status?.type !== "busy" && status?.type !== "retry") break;
1006
+ if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && isCompletedEmptyPromptResponse(bestCandidate) && status?.type !== "busy" && status?.type !== "retry") break;
891
1007
  if (Date.now() >= deadlineAt) break;
892
1008
  }
893
1009
  const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan", input.signal);
894
1010
  const resolved = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
895
1011
  const requestScopedResolved = resolved && isPromptResponseForCurrentRequest(resolved, candidateOptions) ? resolved : null;
896
- if (lastStatus?.type === "idle" && (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved, structured))) throw createMessageAbortedError();
897
- if (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved, structured)) {
1012
+ if (lastStatus?.type === "idle" && (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved))) throw createMessageAbortedError();
1013
+ if (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved)) {
898
1014
  const timeoutReason = Date.now() >= deadlineAt ? "max-wait" : "recovery-inactivity";
899
1015
  const timeoutMs = timeoutReason === "max-wait" ? this.promptTimeoutPolicy.waitTimeoutMs : this.promptTimeoutPolicy.recoveryInactivityTimeoutMs;
900
1016
  const error = createOpenCodePromptTimeoutError({
@@ -1020,9 +1136,10 @@ var OpenCodeClient = class {
1020
1136
  return this.callScopedSdkMethod("config", "providers", {});
1021
1137
  }
1022
1138
  async sendPromptRequest(input, parts) {
1139
+ const format = input.format ?? TEXT_OUTPUT_FORMAT;
1023
1140
  const requestBody = {
1024
1141
  ...input.agent ? { agent: input.agent } : {},
1025
- ...input.structured ? { format: STRUCTURED_REPLY_SCHEMA } : {},
1142
+ format,
1026
1143
  ...input.model ? { model: input.model } : {},
1027
1144
  ...input.variant ? { variant: input.variant } : {},
1028
1145
  parts
@@ -1239,10 +1356,9 @@ function extractTextFromParts(parts) {
1239
1356
  }
1240
1357
  function buildPromptSessionResult(data, options) {
1241
1358
  const assistantInfo = toAssistantMessage(data.info);
1242
- const structuredPayload = extractStructuredPayload(assistantInfo);
1243
- const bodyMd = options.structured ? extractStructuredMarkdown(structuredPayload) : null;
1244
1359
  const responseParts = Array.isArray(data.parts) ? data.parts : [];
1245
- const fallbackText = extractTextFromParts(responseParts) || bodyMd || options.emptyResponseText;
1360
+ const bodyMd = extractTextFromParts(responseParts) || null;
1361
+ const fallbackText = bodyMd ? stripMarkdownToPlainText(bodyMd) || bodyMd : options.emptyResponseText;
1246
1362
  return {
1247
1363
  assistantError: assistantInfo?.error ?? null,
1248
1364
  bodyMd,
@@ -1250,22 +1366,21 @@ function buildPromptSessionResult(data, options) {
1250
1366
  info: assistantInfo,
1251
1367
  metrics: extractPromptMetrics(assistantInfo, options.startedAt, options.finishedAt),
1252
1368
  parts: responseParts,
1253
- structured: structuredPayload ?? null
1369
+ structured: null
1254
1370
  };
1255
1371
  }
1256
- function shouldPollPromptMessage(data, structured) {
1372
+ function shouldPollPromptMessage(data) {
1257
1373
  const assistantInfo = toAssistantMessage(data.info);
1258
- const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
1259
1374
  const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
1260
1375
  const hasAssistantError = !!assistantInfo?.error;
1261
1376
  const isCompleted = isAssistantMessageCompleted(assistantInfo);
1262
- return !hasText && !bodyMd && !hasAssistantError && !isCompleted;
1377
+ return !hasText && !hasAssistantError && !isCompleted;
1263
1378
  }
1264
- function shouldReturnPromptResponseImmediately(data, structured) {
1265
- return !shouldPollPromptMessage(data, structured) && !isCompletedEmptyPromptResponse(data, structured);
1379
+ function shouldReturnPromptResponseImmediately(data) {
1380
+ return !shouldPollPromptMessage(data) && !isCompletedEmptyPromptResponse(data);
1266
1381
  }
1267
- function isPromptResponseUsable(data, structured) {
1268
- return !shouldPollPromptMessage(data, structured) && !isCompletedEmptyPromptResponse(data, structured);
1382
+ function isPromptResponseUsable(data) {
1383
+ return !shouldPollPromptMessage(data) && !isCompletedEmptyPromptResponse(data);
1269
1384
  }
1270
1385
  function normalizePromptResponse(response) {
1271
1386
  return {
@@ -1300,8 +1415,6 @@ function toAssistantMessage(message) {
1300
1415
  if ("providerID" in message && typeof message.providerID === "string" && message.providerID.trim().length > 0) normalized.providerID = message.providerID;
1301
1416
  if ("role" in message && message.role === "assistant") normalized.role = "assistant";
1302
1417
  if ("sessionID" in message && typeof message.sessionID === "string" && message.sessionID.trim().length > 0) normalized.sessionID = message.sessionID;
1303
- const structuredPayload = extractStructuredPayload(message);
1304
- if (structuredPayload !== null) normalized.structured = structuredPayload;
1305
1418
  if ("summary" in message && typeof message.summary === "boolean") normalized.summary = message.summary;
1306
1419
  if ("time" in message && isPlainRecord$1(message.time)) normalized.time = {
1307
1420
  ...typeof message.time.created === "number" && Number.isFinite(message.time.created) ? { created: message.time.created } : {},
@@ -1342,8 +1455,8 @@ function delay(ms, signal) {
1342
1455
  signal?.addEventListener("abort", onAbort, { once: true });
1343
1456
  });
1344
1457
  }
1345
- function didPromptResponseAdvance(previous, next, structured) {
1346
- return getPromptResponseProgressSignature(previous, structured) !== getPromptResponseProgressSignature(next, structured);
1458
+ function didPromptResponseAdvance(previous, next) {
1459
+ return getPromptResponseProgressSignature(previous) !== getPromptResponseProgressSignature(next);
1347
1460
  }
1348
1461
  function createOpenCodePromptTimeoutError(input) {
1349
1462
  return new OpenCodePromptTimeoutError({
@@ -1365,12 +1478,6 @@ function resolvePromptEndpointKind(stage) {
1365
1478
  function getPromptMessagePollDelayMs(attempt) {
1366
1479
  return PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS[attempt] ?? PROMPT_MESSAGE_POLL_INTERVAL_MS;
1367
1480
  }
1368
- function extractStructuredMarkdown(structured) {
1369
- const parsed = StructuredReplySchema.safeParse(structured);
1370
- if (!parsed.success) return null;
1371
- const bodyMd = parsed.data.body_md.replace(/\r\n?/g, "\n").trim();
1372
- return bodyMd.length > 0 ? bodyMd : null;
1373
- }
1374
1481
  function extractPromptMetrics(info, startedAt, finishedAt) {
1375
1482
  const createdAt = typeof info?.time?.created === "number" && Number.isFinite(info.time.created) ? info.time.created : null;
1376
1483
  const completedAt = typeof info?.time?.completed === "number" && Number.isFinite(info.time.completed) ? info.time.completed : null;
@@ -1463,17 +1570,10 @@ function normalizeAssistantError(value) {
1463
1570
  function isAssistantMessageCompleted(message) {
1464
1571
  return !!message?.error || typeof message?.time?.completed === "number" || typeof message?.finish === "string" && message.finish.trim().length > 0;
1465
1572
  }
1466
- function isCompletedEmptyPromptResponse(data, structured) {
1573
+ function isCompletedEmptyPromptResponse(data) {
1467
1574
  const assistantInfo = toAssistantMessage(data.info);
1468
- const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
1469
1575
  const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
1470
- return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText && !bodyMd;
1471
- }
1472
- function extractStructuredPayload(message) {
1473
- if (!isPlainRecord$1(message)) return null;
1474
- if ("structured" in message && message.structured !== void 0) return message.structured;
1475
- if ("structured_output" in message && message.structured_output !== void 0) return message.structured_output;
1476
- return null;
1576
+ return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText;
1477
1577
  }
1478
1578
  function selectPromptResponseCandidate(candidates, options) {
1479
1579
  const availableCandidates = candidates.filter((candidate) => !!candidate).filter((candidate) => toAssistantMessage(candidate.info) !== null);
@@ -1493,7 +1593,7 @@ function getPromptResponseCandidateRank(message, options) {
1493
1593
  createdAt,
1494
1594
  isInitial: !!id && id === options.initialMessageId,
1495
1595
  isNewSinceRequestStart: isPromptResponseNewSinceRequestStart(id, createdAt, options.knownMessageIds, options.requestStartedAt),
1496
- isUsable: isPromptResponseUsable(message, options.structured),
1596
+ isUsable: isPromptResponseUsable(message),
1497
1597
  sharesParent: !!assistant?.parentID && assistant.parentID === options.initialParentId
1498
1598
  };
1499
1599
  }
@@ -1503,13 +1603,13 @@ function resolvePromptCandidateStartTime(startedAt, initialMessage) {
1503
1603
  if (initialCreatedAt === null) return startedAt;
1504
1604
  return areComparablePromptTimestamps(startedAt, initialCreatedAt) ? startedAt : initialCreatedAt;
1505
1605
  }
1506
- function getPromptResponseProgressSignature(response, structured) {
1606
+ function getPromptResponseProgressSignature(response) {
1507
1607
  if (!response) return "null";
1508
1608
  const assistant = toAssistantMessage(response.info);
1509
1609
  const responseParts = Array.isArray(response.parts) ? response.parts : [];
1510
1610
  return JSON.stringify({
1511
1611
  assistantError: assistant?.error?.name ?? null,
1512
- bodyMd: structured ? extractStructuredMarkdown(extractStructuredPayload(assistant)) : null,
1612
+ bodyMd: extractTextFromParts(responseParts) || null,
1513
1613
  completedAt: assistant?.time?.completed ?? null,
1514
1614
  finish: assistant?.finish ?? null,
1515
1615
  id: assistant?.id ?? null,
@@ -2396,7 +2496,7 @@ var SendPromptUseCase = class {
2396
2496
  prompt: promptText,
2397
2497
  ...files.length > 0 ? { files } : {},
2398
2498
  ...selectedAgent ? { agent: selectedAgent.name } : {},
2399
- structured: true,
2499
+ format: { type: "text" },
2400
2500
  ...model ? { model } : {},
2401
2501
  ...input.signal ? { signal: input.signal } : {},
2402
2502
  ...activeBinding.modelVariant ? { variant: activeBinding.modelVariant } : {}
@@ -2900,7 +3000,7 @@ async function handleSessionError(runtime, event) {
2900
3000
  component: "plugin-event",
2901
3001
  sessionId: event.sessionId
2902
3002
  });
2903
- if (runtime.container.foregroundSessionTracker.fail(event.sessionId, event.error instanceof Error ? event.error : /* @__PURE__ */ new Error("Unknown session error."))) {
3003
+ if (runtime.container.foregroundSessionTracker.fail(event.sessionId, normalizeForegroundSessionError(event.error))) {
2904
3004
  logger.warn({
2905
3005
  error: event.error,
2906
3006
  event: "plugin-event.session.error.foreground_suppressed"
@@ -3018,6 +3118,16 @@ function extractSessionErrorMessage(error) {
3018
3118
  if (isPlainRecord(error.data) && typeof error.data.message === "string" && error.data.message.trim().length > 0) return error.data.message.trim();
3019
3119
  return asNonEmptyString(error.name);
3020
3120
  }
3121
+ function normalizeForegroundSessionError(error) {
3122
+ if (error instanceof Error) return error;
3123
+ if (isPlainRecord(error) && typeof error.name === "string" && error.name.trim().length > 0) {
3124
+ const normalized = new Error(extractSessionErrorMessage(error) ?? "Unknown session error.");
3125
+ normalized.name = error.name.trim();
3126
+ if (isPlainRecord(error.data)) normalized.data = error.data;
3127
+ return normalized;
3128
+ }
3129
+ return /* @__PURE__ */ new Error("Unknown session error.");
3130
+ }
3021
3131
  function asNonEmptyString(value) {
3022
3132
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
3023
3133
  }
@@ -4713,7 +4823,7 @@ var MARKDOWN_SPECIAL_CHARACTERS = /([_*\[\]()~`>#+\-=|{}.!\\])/g;
4713
4823
  function buildTelegramPromptReply(result, copy = BOT_COPY) {
4714
4824
  const renderedMarkdown = result.bodyMd ? renderMarkdownToTelegramMarkdownV2(result.bodyMd) : null;
4715
4825
  const footerPlain = formatPlainMetricsFooter(result.metrics, copy);
4716
- const fallback = { text: joinBodyAndFooter(truncatePlainBody(normalizePlainBody(result, renderedMarkdown !== null, copy), footerPlain), footerPlain) };
4826
+ const fallback = { text: joinBodyAndFooter(truncatePlainBody(normalizePlainBody(result, copy), footerPlain), footerPlain) };
4717
4827
  if (!renderedMarkdown) return {
4718
4828
  preferred: fallback,
4719
4829
  fallback
@@ -4817,24 +4927,10 @@ function renderMarkdownToTelegramMarkdownV2(markdown) {
4817
4927
  if (inCodeBlock) return null;
4818
4928
  return rendered.join("\n");
4819
4929
  }
4820
- function stripMarkdownToPlainText(markdown) {
4821
- const lines = preprocessMarkdownForTelegram(markdown).split("\n");
4822
- const rendered = [];
4823
- for (let index = 0; index < lines.length; index += 1) {
4824
- const tableBlock = consumeMarkdownTable(lines, index);
4825
- if (tableBlock) {
4826
- rendered.push(renderTableAsPlainText(tableBlock.rows));
4827
- index = tableBlock.nextIndex - 1;
4828
- continue;
4829
- }
4830
- rendered.push(lines[index]);
4831
- }
4832
- return rendered.join("\n").replace(/```[A-Za-z0-9_-]*\n?/g, "").replace(/```/g, "").replace(/^#{1,6}\s+/gm, "").replace(/^\s*>\s?/gm, "").replace(/^(\s*)[-+*]\s+/gm, "$1- ").replace(/^(\s*)(\d+)[.)]\s+/gm, "$1$2. ").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/(\*\*|__)(.*?)\1/g, "$2").replace(/(\*|_)(.*?)\1/g, "$2").replace(/`([^`]+)`/g, "$1").trim();
4833
- }
4834
- function normalizePlainBody(result, preferStructured, copy) {
4930
+ function normalizePlainBody(result, copy) {
4835
4931
  const fromStructured = result.bodyMd ? stripMarkdownToPlainText(result.bodyMd) : "";
4836
4932
  const fromFallback = result.fallbackText.trim();
4837
- return (preferStructured ? fromStructured || fromFallback : fromFallback || fromStructured).trim() || result.fallbackText || copy.prompt.emptyResponse;
4933
+ return (fromStructured || fromFallback).trim() || result.fallbackText || copy.prompt.emptyResponse;
4838
4934
  }
4839
4935
  function truncatePlainBody(body, footer) {
4840
4936
  const reservedLength = footer.length + 2;
@@ -4958,9 +5054,6 @@ function renderTableAsTelegramCodeBlock(rows) {
4958
5054
  "```"
4959
5055
  ].join("\n");
4960
5056
  }
4961
- function renderTableAsPlainText(rows) {
4962
- return buildAlignedTableLines(rows).join("\n");
4963
- }
4964
5057
  function buildAlignedTableLines(rows) {
4965
5058
  const columnWidths = calculateTableColumnWidths(rows);
4966
5059
  return [
@@ -5417,7 +5510,7 @@ async function executePromptRequest(ctx, dependencies, resolvePrompt) {
5417
5510
  },
5418
5511
  signal: foregroundRequest.signal,
5419
5512
  text: promptInput.text
5420
- })).assistantReply, copy, dependencies), copy);
5513
+ })).assistantReply, copy), copy);
5421
5514
  try {
5422
5515
  await ctx.reply(telegramReply.preferred.text, telegramReply.preferred.options);
5423
5516
  } catch (replyError) {
@@ -5438,26 +5531,14 @@ async function executePromptRequest(ctx, dependencies, resolvePrompt) {
5438
5531
  }
5439
5532
  }
5440
5533
  }
5441
- function normalizePromptReplyForDisplay(promptReply, copy, dependencies) {
5534
+ function normalizePromptReplyForDisplay(promptReply, copy) {
5442
5535
  if (!promptReply.assistantError) return promptReply;
5443
- if (isRecoverableStructuredOutputError(promptReply)) {
5444
- dependencies.logger.warn?.({ error: promptReply.assistantError }, "structured output validation failed, falling back to assistant text reply");
5445
- return {
5446
- ...promptReply,
5447
- assistantError: null
5448
- };
5449
- }
5450
5536
  return {
5451
5537
  ...promptReply,
5452
5538
  bodyMd: null,
5453
5539
  fallbackText: presentError(promptReply.assistantError, copy)
5454
5540
  };
5455
5541
  }
5456
- function isRecoverableStructuredOutputError(promptReply) {
5457
- if (promptReply.assistantError?.name !== "StructuredOutputError") return false;
5458
- if (promptReply.bodyMd?.trim()) return true;
5459
- return promptReply.parts.some((part) => part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0);
5460
- }
5461
5542
  //#endregion
5462
5543
  //#region src/bot/handlers/file.handler.ts
5463
5544
  var TELEGRAM_MAX_DOWNLOAD_BYTES = 20 * 1024 * 1024;