opencode-tbot 0.1.31 → 0.1.33
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.ja.md +16 -4
- package/README.md +15 -5
- package/README.zh-CN.md +15 -5
- package/dist/assets/{plugin-config-jkAZYbFW.js → plugin-config-LIr8LS0-.js} +25 -2
- package/dist/assets/plugin-config-LIr8LS0-.js.map +1 -0
- package/dist/cli.js +5 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin.js +250 -164
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/plugin-config-jkAZYbFW.js.map +0 -1
package/dist/plugin.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { i as OPENCODE_TBOT_VERSION, n as preparePluginConfiguration, o as loadAppConfig } from "./assets/plugin-config-
|
|
1
|
+
import { i as OPENCODE_TBOT_VERSION, n as preparePluginConfiguration, o as loadAppConfig } from "./assets/plugin-config-LIr8LS0-.js";
|
|
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
|
-
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
5
|
-
import { z } from "zod";
|
|
6
4
|
import { randomUUID } from "node:crypto";
|
|
7
5
|
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
8
6
|
import { run } from "@grammyjs/runner";
|
|
@@ -571,6 +569,139 @@ var FileSessionRepository = class {
|
|
|
571
569
|
}
|
|
572
570
|
};
|
|
573
571
|
//#endregion
|
|
572
|
+
//#region src/infra/utils/markdown-text.ts
|
|
573
|
+
var HTML_TAG_PATTERN = /<\/?[A-Za-z][^>]*>/g;
|
|
574
|
+
function stripMarkdownToPlainText(markdown) {
|
|
575
|
+
const lines = preprocessMarkdownForPlainText(markdown).split("\n");
|
|
576
|
+
const rendered = [];
|
|
577
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
578
|
+
const tableBlock = consumeMarkdownTable$1(lines, index);
|
|
579
|
+
if (tableBlock) {
|
|
580
|
+
rendered.push(renderTableAsPlainText(tableBlock.rows));
|
|
581
|
+
index = tableBlock.nextIndex - 1;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
rendered.push(lines[index] ?? "");
|
|
585
|
+
}
|
|
586
|
+
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();
|
|
587
|
+
}
|
|
588
|
+
function preprocessMarkdownForPlainText(markdown) {
|
|
589
|
+
const lines = markdown.replace(/\r\n?/g, "\n").split("\n");
|
|
590
|
+
const processed = [];
|
|
591
|
+
let activeFence = null;
|
|
592
|
+
for (const line of lines) {
|
|
593
|
+
const fenceMatch = line.match(/^```([A-Za-z0-9_-]+)?\s*$/);
|
|
594
|
+
if (fenceMatch) {
|
|
595
|
+
const language = (fenceMatch[1] ?? "").toLowerCase();
|
|
596
|
+
if (activeFence === "markdown") {
|
|
597
|
+
activeFence = null;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (activeFence === "plain") {
|
|
601
|
+
processed.push(line);
|
|
602
|
+
activeFence = null;
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (language === "md" || language === "markdown") {
|
|
606
|
+
activeFence = "markdown";
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
activeFence = "plain";
|
|
610
|
+
processed.push(line);
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
processed.push(line);
|
|
614
|
+
}
|
|
615
|
+
return processed.join("\n");
|
|
616
|
+
}
|
|
617
|
+
function consumeMarkdownTable$1(lines, startIndex) {
|
|
618
|
+
if (startIndex + 1 >= lines.length) return null;
|
|
619
|
+
const headerCells = parseMarkdownTableRow$1(lines[startIndex] ?? "");
|
|
620
|
+
const separatorCells = parseMarkdownTableSeparator$1(lines[startIndex + 1] ?? "");
|
|
621
|
+
if (!headerCells || !separatorCells || headerCells.length !== separatorCells.length) return null;
|
|
622
|
+
const rows = [headerCells];
|
|
623
|
+
let index = startIndex + 2;
|
|
624
|
+
while (index < lines.length) {
|
|
625
|
+
const rowCells = parseMarkdownTableRow$1(lines[index] ?? "");
|
|
626
|
+
if (!rowCells || rowCells.length !== headerCells.length) break;
|
|
627
|
+
rows.push(rowCells);
|
|
628
|
+
index += 1;
|
|
629
|
+
}
|
|
630
|
+
return {
|
|
631
|
+
rows,
|
|
632
|
+
nextIndex: index
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function parseMarkdownTableRow$1(line) {
|
|
636
|
+
const trimmed = line.trim();
|
|
637
|
+
if (!trimmed.includes("|")) return null;
|
|
638
|
+
const cells = splitMarkdownTableCells$1(trimmed).map((cell) => normalizeTableCell$1(cell));
|
|
639
|
+
return cells.length >= 2 ? cells : null;
|
|
640
|
+
}
|
|
641
|
+
function parseMarkdownTableSeparator$1(line) {
|
|
642
|
+
const cells = splitMarkdownTableCells$1(line.trim());
|
|
643
|
+
if (cells.length < 2) return null;
|
|
644
|
+
return cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim())) ? cells : null;
|
|
645
|
+
}
|
|
646
|
+
function splitMarkdownTableCells$1(line) {
|
|
647
|
+
const content = line.replace(/^\|/, "").replace(/\|$/, "");
|
|
648
|
+
const cells = [];
|
|
649
|
+
let current = "";
|
|
650
|
+
let escaped = false;
|
|
651
|
+
for (const char of content) {
|
|
652
|
+
if (escaped) {
|
|
653
|
+
current += char;
|
|
654
|
+
escaped = false;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (char === "\\") {
|
|
658
|
+
escaped = true;
|
|
659
|
+
current += char;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (char === "|") {
|
|
663
|
+
cells.push(current);
|
|
664
|
+
current = "";
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
current += char;
|
|
668
|
+
}
|
|
669
|
+
cells.push(current);
|
|
670
|
+
return cells;
|
|
671
|
+
}
|
|
672
|
+
function normalizeTableCell$1(cell) {
|
|
673
|
+
return cell.trim().replace(/\\\|/g, "|").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)").replace(/(\*\*|__)(.*?)\1/g, "$2").replace(/(\*|_)(.*?)\1/g, "$2").replace(/`([^`]+)`/g, "$1").replace(HTML_TAG_PATTERN, "");
|
|
674
|
+
}
|
|
675
|
+
function renderTableAsPlainText(rows) {
|
|
676
|
+
return buildAlignedTableLines$1(rows).join("\n");
|
|
677
|
+
}
|
|
678
|
+
function buildAlignedTableLines$1(rows) {
|
|
679
|
+
const columnWidths = calculateTableColumnWidths$1(rows);
|
|
680
|
+
return [
|
|
681
|
+
formatTableRow$1(rows[0] ?? [], columnWidths),
|
|
682
|
+
columnWidths.map((width) => "-".repeat(Math.max(3, width))).join("-+-"),
|
|
683
|
+
...rows.slice(1).map((row) => formatTableRow$1(row, columnWidths))
|
|
684
|
+
];
|
|
685
|
+
}
|
|
686
|
+
function calculateTableColumnWidths$1(rows) {
|
|
687
|
+
return (rows[0] ?? []).map((_, columnIndex) => rows.reduce((maxWidth, row) => Math.max(maxWidth, getDisplayWidth$1(row[columnIndex] ?? "")), 0));
|
|
688
|
+
}
|
|
689
|
+
function formatTableRow$1(row, columnWidths) {
|
|
690
|
+
return row.map((cell, index) => padDisplayWidth$1(cell, columnWidths[index] ?? 0)).join(" | ");
|
|
691
|
+
}
|
|
692
|
+
function padDisplayWidth$1(value, targetWidth) {
|
|
693
|
+
const padding = Math.max(0, targetWidth - getDisplayWidth$1(value));
|
|
694
|
+
return `${value}${" ".repeat(padding)}`;
|
|
695
|
+
}
|
|
696
|
+
function getDisplayWidth$1(value) {
|
|
697
|
+
let width = 0;
|
|
698
|
+
for (const char of value) width += isWideCharacter$1(char.codePointAt(0) ?? 0) ? 2 : 1;
|
|
699
|
+
return width;
|
|
700
|
+
}
|
|
701
|
+
function isWideCharacter$1(codePoint) {
|
|
702
|
+
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);
|
|
703
|
+
}
|
|
704
|
+
//#endregion
|
|
574
705
|
//#region src/services/opencode/opencode.client.ts
|
|
575
706
|
function buildOpenCodeSdkConfig(options) {
|
|
576
707
|
const apiKey = options.apiKey?.trim();
|
|
@@ -614,24 +745,11 @@ var DEFAULT_OPENCODE_PROMPT_TIMEOUT_POLICY = {
|
|
|
614
745
|
recoveryInactivityTimeoutMs: 12e4,
|
|
615
746
|
waitTimeoutMs: 18e5
|
|
616
747
|
};
|
|
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
748
|
var SDK_OPTIONS = {
|
|
631
749
|
responseStyle: "data",
|
|
632
750
|
throwOnError: true
|
|
633
751
|
};
|
|
634
|
-
var
|
|
752
|
+
var TEXT_OUTPUT_FORMAT = { type: "text" };
|
|
635
753
|
var OpenCodeClient = class {
|
|
636
754
|
client;
|
|
637
755
|
fetchFn;
|
|
@@ -800,6 +918,9 @@ var OpenCodeClient = class {
|
|
|
800
918
|
this.modelCache.promise = refreshPromise;
|
|
801
919
|
return refreshPromise;
|
|
802
920
|
}
|
|
921
|
+
async listConfiguredPlugins() {
|
|
922
|
+
return normalizeConfiguredPluginSpecs((await this.loadConfig()).plugin);
|
|
923
|
+
}
|
|
803
924
|
async promptSession(input) {
|
|
804
925
|
const startedAt = Date.now();
|
|
805
926
|
const promptText = input.prompt?.trim() ?? "";
|
|
@@ -819,20 +940,17 @@ var OpenCodeClient = class {
|
|
|
819
940
|
return buildPromptSessionResult(await this.resolvePromptResponse(input, null, knownMessageIds, startedAt), {
|
|
820
941
|
emptyResponseText: EMPTY_RESPONSE_TEXT,
|
|
821
942
|
finishedAt: Date.now(),
|
|
822
|
-
startedAt
|
|
823
|
-
structured: input.structured ?? false
|
|
943
|
+
startedAt
|
|
824
944
|
});
|
|
825
945
|
}
|
|
826
946
|
async resolvePromptResponse(input, data, knownMessageIds, startedAt) {
|
|
827
|
-
|
|
828
|
-
if (data && shouldReturnPromptResponseImmediately(data, structured)) return data;
|
|
947
|
+
if (data && shouldReturnPromptResponseImmediately(data)) return data;
|
|
829
948
|
const messageId = data ? extractMessageId(data.info) : null;
|
|
830
949
|
const candidateOptions = {
|
|
831
950
|
initialMessageId: messageId,
|
|
832
951
|
initialParentId: data ? toAssistantMessage(data.info)?.parentID ?? null : null,
|
|
833
952
|
knownMessageIds,
|
|
834
|
-
requestStartedAt: resolvePromptCandidateStartTime(startedAt, data)
|
|
835
|
-
structured
|
|
953
|
+
requestStartedAt: resolvePromptCandidateStartTime(startedAt, data)
|
|
836
954
|
};
|
|
837
955
|
let bestCandidate = selectPromptResponseCandidate(data ? [data] : [], candidateOptions);
|
|
838
956
|
let lastProgressAt = Date.now();
|
|
@@ -857,26 +975,26 @@ var OpenCodeClient = class {
|
|
|
857
975
|
if (next) {
|
|
858
976
|
const nextCandidate = selectPromptResponseCandidate([bestCandidate, next], candidateOptions);
|
|
859
977
|
if (nextCandidate) {
|
|
860
|
-
if (didPromptResponseAdvance(bestCandidate, nextCandidate
|
|
978
|
+
if (didPromptResponseAdvance(bestCandidate, nextCandidate)) {
|
|
861
979
|
lastProgressAt = Date.now();
|
|
862
980
|
idleStatusSeen = false;
|
|
863
981
|
}
|
|
864
982
|
bestCandidate = nextCandidate;
|
|
865
983
|
}
|
|
866
|
-
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate
|
|
984
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate)) return bestCandidate;
|
|
867
985
|
}
|
|
868
986
|
}
|
|
869
987
|
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "poll-messages", input.signal);
|
|
870
988
|
if (latest) {
|
|
871
989
|
const nextCandidate = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
|
|
872
990
|
if (nextCandidate) {
|
|
873
|
-
if (didPromptResponseAdvance(bestCandidate, nextCandidate
|
|
991
|
+
if (didPromptResponseAdvance(bestCandidate, nextCandidate)) {
|
|
874
992
|
lastProgressAt = Date.now();
|
|
875
993
|
idleStatusSeen = false;
|
|
876
994
|
}
|
|
877
995
|
bestCandidate = nextCandidate;
|
|
878
996
|
}
|
|
879
|
-
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate
|
|
997
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && shouldReturnPromptResponseImmediately(bestCandidate)) return bestCandidate;
|
|
880
998
|
}
|
|
881
999
|
const status = await this.fetchPromptSessionStatus(input.sessionId, input.signal);
|
|
882
1000
|
lastStatus = status;
|
|
@@ -887,14 +1005,14 @@ var OpenCodeClient = class {
|
|
|
887
1005
|
if (idleStatusSeen) break;
|
|
888
1006
|
idleStatusSeen = true;
|
|
889
1007
|
}
|
|
890
|
-
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && isCompletedEmptyPromptResponse(bestCandidate
|
|
1008
|
+
if (bestCandidate && isPromptResponseForCurrentRequest(bestCandidate, candidateOptions) && isCompletedEmptyPromptResponse(bestCandidate) && status?.type !== "busy" && status?.type !== "retry") break;
|
|
891
1009
|
if (Date.now() >= deadlineAt) break;
|
|
892
1010
|
}
|
|
893
1011
|
const latest = await this.findLatestPromptResponse(input.sessionId, candidateOptions, "final-scan", input.signal);
|
|
894
1012
|
const resolved = selectPromptResponseCandidate([bestCandidate, latest], candidateOptions);
|
|
895
1013
|
const requestScopedResolved = resolved && isPromptResponseForCurrentRequest(resolved, candidateOptions) ? resolved : null;
|
|
896
|
-
if (lastStatus?.type === "idle" && (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved
|
|
897
|
-
if (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved
|
|
1014
|
+
if (lastStatus?.type === "idle" && (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved))) throw createMessageAbortedError();
|
|
1015
|
+
if (!requestScopedResolved || shouldPollPromptMessage(requestScopedResolved)) {
|
|
898
1016
|
const timeoutReason = Date.now() >= deadlineAt ? "max-wait" : "recovery-inactivity";
|
|
899
1017
|
const timeoutMs = timeoutReason === "max-wait" ? this.promptTimeoutPolicy.waitTimeoutMs : this.promptTimeoutPolicy.recoveryInactivityTimeoutMs;
|
|
900
1018
|
const error = createOpenCodePromptTimeoutError({
|
|
@@ -1020,9 +1138,10 @@ var OpenCodeClient = class {
|
|
|
1020
1138
|
return this.callScopedSdkMethod("config", "providers", {});
|
|
1021
1139
|
}
|
|
1022
1140
|
async sendPromptRequest(input, parts) {
|
|
1141
|
+
const format = input.format ?? TEXT_OUTPUT_FORMAT;
|
|
1023
1142
|
const requestBody = {
|
|
1024
1143
|
...input.agent ? { agent: input.agent } : {},
|
|
1025
|
-
|
|
1144
|
+
format,
|
|
1026
1145
|
...input.model ? { model: input.model } : {},
|
|
1027
1146
|
...input.variant ? { variant: input.variant } : {},
|
|
1028
1147
|
parts
|
|
@@ -1239,10 +1358,9 @@ function extractTextFromParts(parts) {
|
|
|
1239
1358
|
}
|
|
1240
1359
|
function buildPromptSessionResult(data, options) {
|
|
1241
1360
|
const assistantInfo = toAssistantMessage(data.info);
|
|
1242
|
-
const structuredPayload = extractStructuredPayload(assistantInfo);
|
|
1243
|
-
const bodyMd = options.structured ? extractStructuredMarkdown(structuredPayload) : null;
|
|
1244
1361
|
const responseParts = Array.isArray(data.parts) ? data.parts : [];
|
|
1245
|
-
const
|
|
1362
|
+
const bodyMd = extractTextFromParts(responseParts) || null;
|
|
1363
|
+
const fallbackText = bodyMd ? stripMarkdownToPlainText(bodyMd) || bodyMd : options.emptyResponseText;
|
|
1246
1364
|
return {
|
|
1247
1365
|
assistantError: assistantInfo?.error ?? null,
|
|
1248
1366
|
bodyMd,
|
|
@@ -1250,22 +1368,21 @@ function buildPromptSessionResult(data, options) {
|
|
|
1250
1368
|
info: assistantInfo,
|
|
1251
1369
|
metrics: extractPromptMetrics(assistantInfo, options.startedAt, options.finishedAt),
|
|
1252
1370
|
parts: responseParts,
|
|
1253
|
-
structured:
|
|
1371
|
+
structured: null
|
|
1254
1372
|
};
|
|
1255
1373
|
}
|
|
1256
|
-
function shouldPollPromptMessage(data
|
|
1374
|
+
function shouldPollPromptMessage(data) {
|
|
1257
1375
|
const assistantInfo = toAssistantMessage(data.info);
|
|
1258
|
-
const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
|
|
1259
1376
|
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
1260
1377
|
const hasAssistantError = !!assistantInfo?.error;
|
|
1261
1378
|
const isCompleted = isAssistantMessageCompleted(assistantInfo);
|
|
1262
|
-
return !hasText && !
|
|
1379
|
+
return !hasText && !hasAssistantError && !isCompleted;
|
|
1263
1380
|
}
|
|
1264
|
-
function shouldReturnPromptResponseImmediately(data
|
|
1265
|
-
return !shouldPollPromptMessage(data
|
|
1381
|
+
function shouldReturnPromptResponseImmediately(data) {
|
|
1382
|
+
return !shouldPollPromptMessage(data) && !isCompletedEmptyPromptResponse(data);
|
|
1266
1383
|
}
|
|
1267
|
-
function isPromptResponseUsable(data
|
|
1268
|
-
return !shouldPollPromptMessage(data
|
|
1384
|
+
function isPromptResponseUsable(data) {
|
|
1385
|
+
return !shouldPollPromptMessage(data) && !isCompletedEmptyPromptResponse(data);
|
|
1269
1386
|
}
|
|
1270
1387
|
function normalizePromptResponse(response) {
|
|
1271
1388
|
return {
|
|
@@ -1300,8 +1417,6 @@ function toAssistantMessage(message) {
|
|
|
1300
1417
|
if ("providerID" in message && typeof message.providerID === "string" && message.providerID.trim().length > 0) normalized.providerID = message.providerID;
|
|
1301
1418
|
if ("role" in message && message.role === "assistant") normalized.role = "assistant";
|
|
1302
1419
|
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
1420
|
if ("summary" in message && typeof message.summary === "boolean") normalized.summary = message.summary;
|
|
1306
1421
|
if ("time" in message && isPlainRecord$1(message.time)) normalized.time = {
|
|
1307
1422
|
...typeof message.time.created === "number" && Number.isFinite(message.time.created) ? { created: message.time.created } : {},
|
|
@@ -1342,8 +1457,8 @@ function delay(ms, signal) {
|
|
|
1342
1457
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1343
1458
|
});
|
|
1344
1459
|
}
|
|
1345
|
-
function didPromptResponseAdvance(previous, next
|
|
1346
|
-
return getPromptResponseProgressSignature(previous
|
|
1460
|
+
function didPromptResponseAdvance(previous, next) {
|
|
1461
|
+
return getPromptResponseProgressSignature(previous) !== getPromptResponseProgressSignature(next);
|
|
1347
1462
|
}
|
|
1348
1463
|
function createOpenCodePromptTimeoutError(input) {
|
|
1349
1464
|
return new OpenCodePromptTimeoutError({
|
|
@@ -1365,12 +1480,6 @@ function resolvePromptEndpointKind(stage) {
|
|
|
1365
1480
|
function getPromptMessagePollDelayMs(attempt) {
|
|
1366
1481
|
return PROMPT_MESSAGE_POLL_INITIAL_DELAYS_MS[attempt] ?? PROMPT_MESSAGE_POLL_INTERVAL_MS;
|
|
1367
1482
|
}
|
|
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
1483
|
function extractPromptMetrics(info, startedAt, finishedAt) {
|
|
1375
1484
|
const createdAt = typeof info?.time?.created === "number" && Number.isFinite(info.time.created) ? info.time.created : null;
|
|
1376
1485
|
const completedAt = typeof info?.time?.completed === "number" && Number.isFinite(info.time.completed) ? info.time.completed : null;
|
|
@@ -1432,6 +1541,19 @@ function normalizePermissionRequest(permission) {
|
|
|
1432
1541
|
sessionID
|
|
1433
1542
|
};
|
|
1434
1543
|
}
|
|
1544
|
+
function normalizeConfiguredPluginSpecs(value) {
|
|
1545
|
+
if (!Array.isArray(value)) return [];
|
|
1546
|
+
const normalizedPlugins = [];
|
|
1547
|
+
const seenPlugins = /* @__PURE__ */ new Set();
|
|
1548
|
+
for (const item of value) {
|
|
1549
|
+
if (typeof item !== "string") continue;
|
|
1550
|
+
const normalizedItem = item.trim();
|
|
1551
|
+
if (normalizedItem.length === 0 || seenPlugins.has(normalizedItem)) continue;
|
|
1552
|
+
seenPlugins.add(normalizedItem);
|
|
1553
|
+
normalizedPlugins.push(normalizedItem);
|
|
1554
|
+
}
|
|
1555
|
+
return normalizedPlugins;
|
|
1556
|
+
}
|
|
1435
1557
|
function normalizePermissionPatterns$1(permission) {
|
|
1436
1558
|
if (Array.isArray(permission.patterns)) return permission.patterns.filter((value) => typeof value === "string");
|
|
1437
1559
|
if (typeof permission.pattern === "string" && permission.pattern.trim().length > 0) return [permission.pattern];
|
|
@@ -1463,17 +1585,10 @@ function normalizeAssistantError(value) {
|
|
|
1463
1585
|
function isAssistantMessageCompleted(message) {
|
|
1464
1586
|
return !!message?.error || typeof message?.time?.completed === "number" || typeof message?.finish === "string" && message.finish.trim().length > 0;
|
|
1465
1587
|
}
|
|
1466
|
-
function isCompletedEmptyPromptResponse(data
|
|
1588
|
+
function isCompletedEmptyPromptResponse(data) {
|
|
1467
1589
|
const assistantInfo = toAssistantMessage(data.info);
|
|
1468
|
-
const bodyMd = structured ? extractStructuredMarkdown(extractStructuredPayload(assistantInfo)) : null;
|
|
1469
1590
|
const hasText = extractTextFromParts(Array.isArray(data.parts) ? data.parts : []).length > 0;
|
|
1470
|
-
return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText
|
|
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;
|
|
1591
|
+
return isAssistantMessageCompleted(assistantInfo) && !assistantInfo?.error && !hasText;
|
|
1477
1592
|
}
|
|
1478
1593
|
function selectPromptResponseCandidate(candidates, options) {
|
|
1479
1594
|
const availableCandidates = candidates.filter((candidate) => !!candidate).filter((candidate) => toAssistantMessage(candidate.info) !== null);
|
|
@@ -1493,7 +1608,7 @@ function getPromptResponseCandidateRank(message, options) {
|
|
|
1493
1608
|
createdAt,
|
|
1494
1609
|
isInitial: !!id && id === options.initialMessageId,
|
|
1495
1610
|
isNewSinceRequestStart: isPromptResponseNewSinceRequestStart(id, createdAt, options.knownMessageIds, options.requestStartedAt),
|
|
1496
|
-
isUsable: isPromptResponseUsable(message
|
|
1611
|
+
isUsable: isPromptResponseUsable(message),
|
|
1497
1612
|
sharesParent: !!assistant?.parentID && assistant.parentID === options.initialParentId
|
|
1498
1613
|
};
|
|
1499
1614
|
}
|
|
@@ -1503,13 +1618,13 @@ function resolvePromptCandidateStartTime(startedAt, initialMessage) {
|
|
|
1503
1618
|
if (initialCreatedAt === null) return startedAt;
|
|
1504
1619
|
return areComparablePromptTimestamps(startedAt, initialCreatedAt) ? startedAt : initialCreatedAt;
|
|
1505
1620
|
}
|
|
1506
|
-
function getPromptResponseProgressSignature(response
|
|
1621
|
+
function getPromptResponseProgressSignature(response) {
|
|
1507
1622
|
if (!response) return "null";
|
|
1508
1623
|
const assistant = toAssistantMessage(response.info);
|
|
1509
1624
|
const responseParts = Array.isArray(response.parts) ? response.parts : [];
|
|
1510
1625
|
return JSON.stringify({
|
|
1511
1626
|
assistantError: assistant?.error?.name ?? null,
|
|
1512
|
-
bodyMd:
|
|
1627
|
+
bodyMd: extractTextFromParts(responseParts) || null,
|
|
1513
1628
|
completedAt: assistant?.time?.completed ?? null,
|
|
1514
1629
|
finish: assistant?.finish ?? null,
|
|
1515
1630
|
id: assistant?.id ?? null,
|
|
@@ -1639,7 +1754,7 @@ async function readStateFile(filePath, createDefaultState) {
|
|
|
1639
1754
|
const content = await readFile(filePath, "utf8");
|
|
1640
1755
|
return JSON.parse(content);
|
|
1641
1756
|
} catch (error) {
|
|
1642
|
-
if (isMissingFileError
|
|
1757
|
+
if (isMissingFileError(error)) return createDefaultState();
|
|
1643
1758
|
throw error;
|
|
1644
1759
|
}
|
|
1645
1760
|
}
|
|
@@ -1652,7 +1767,7 @@ async function writeStateFile(filePath, state) {
|
|
|
1652
1767
|
function cloneState(state) {
|
|
1653
1768
|
return JSON.parse(JSON.stringify(state));
|
|
1654
1769
|
}
|
|
1655
|
-
function isMissingFileError
|
|
1770
|
+
function isMissingFileError(error) {
|
|
1656
1771
|
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
1657
1772
|
}
|
|
1658
1773
|
//#endregion
|
|
@@ -1956,27 +2071,30 @@ var GetPathUseCase = class {
|
|
|
1956
2071
|
//#endregion
|
|
1957
2072
|
//#region src/use-cases/get-status.usecase.ts
|
|
1958
2073
|
var GetStatusUseCase = class {
|
|
1959
|
-
constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo) {
|
|
2074
|
+
constructor(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, configuredPluginReader) {
|
|
1960
2075
|
this.getHealthUseCase = getHealthUseCase;
|
|
1961
2076
|
this.getPathUseCase = getPathUseCase;
|
|
1962
2077
|
this.listLspUseCase = listLspUseCase;
|
|
1963
2078
|
this.listMcpUseCase = listMcpUseCase;
|
|
1964
2079
|
this.listSessionsUseCase = listSessionsUseCase;
|
|
1965
2080
|
this.sessionRepo = sessionRepo;
|
|
2081
|
+
this.configuredPluginReader = configuredPluginReader;
|
|
1966
2082
|
}
|
|
1967
2083
|
async execute(input) {
|
|
1968
|
-
const [health, path, lsp, mcp] = await Promise.allSettled([
|
|
2084
|
+
const [health, path, lsp, mcp, plugins] = await Promise.allSettled([
|
|
1969
2085
|
this.getHealthUseCase.execute(),
|
|
1970
2086
|
this.getPathUseCase.execute(),
|
|
1971
2087
|
this.listLspUseCase.execute({ chatId: input.chatId }),
|
|
1972
|
-
this.listMcpUseCase.execute({ chatId: input.chatId })
|
|
2088
|
+
this.listMcpUseCase.execute({ chatId: input.chatId }),
|
|
2089
|
+
this.configuredPluginReader.listConfiguredPlugins()
|
|
1973
2090
|
]);
|
|
1974
2091
|
const pathResult = mapSettledResult(path);
|
|
1975
|
-
const
|
|
2092
|
+
const pluginResult = loadConfiguredPluginsResult(plugins);
|
|
2093
|
+
const workspace = await loadWorkspaceStatusResult(input.chatId, pathResult, this.listSessionsUseCase, this.sessionRepo);
|
|
1976
2094
|
return {
|
|
1977
2095
|
health: mapSettledResult(health),
|
|
1978
2096
|
path: pathResult,
|
|
1979
|
-
plugins,
|
|
2097
|
+
plugins: pluginResult,
|
|
1980
2098
|
workspace,
|
|
1981
2099
|
lsp: mapSettledResult(lsp),
|
|
1982
2100
|
mcp: mapSettledResult(mcp)
|
|
@@ -1993,49 +2111,14 @@ function mapSettledResult(result) {
|
|
|
1993
2111
|
status: "error"
|
|
1994
2112
|
};
|
|
1995
2113
|
}
|
|
1996
|
-
|
|
1997
|
-
if (
|
|
1998
|
-
error:
|
|
2114
|
+
function loadConfiguredPluginsResult(result) {
|
|
2115
|
+
if (result.status === "rejected") return {
|
|
2116
|
+
error: result.reason,
|
|
1999
2117
|
status: "error"
|
|
2000
2118
|
};
|
|
2001
|
-
try {
|
|
2002
|
-
return {
|
|
2003
|
-
data: await loadConfiguredPlugins(path.data.config),
|
|
2004
|
-
status: "ok"
|
|
2005
|
-
};
|
|
2006
|
-
} catch (error) {
|
|
2007
|
-
return {
|
|
2008
|
-
error,
|
|
2009
|
-
status: "error"
|
|
2010
|
-
};
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
async function loadConfiguredPlugins(configFilePath) {
|
|
2014
|
-
const resolvedConfigFilePath = await resolveOpenCodeConfigFilePath(configFilePath);
|
|
2015
|
-
let content;
|
|
2016
|
-
try {
|
|
2017
|
-
content = await readFile(resolvedConfigFilePath, "utf8");
|
|
2018
|
-
} catch (error) {
|
|
2019
|
-
if (isMissingFileError(error)) return {
|
|
2020
|
-
configFilePath: resolvedConfigFilePath,
|
|
2021
|
-
plugins: []
|
|
2022
|
-
};
|
|
2023
|
-
throw error;
|
|
2024
|
-
}
|
|
2025
|
-
const parseErrors = [];
|
|
2026
|
-
const parsed = parse(content, parseErrors, { allowTrailingComma: true });
|
|
2027
|
-
if (parseErrors.length > 0) {
|
|
2028
|
-
const errorSummary = parseErrors.map((error) => printParseErrorCode(error.error)).join(", ");
|
|
2029
|
-
throw new Error(`Failed to parse ${resolvedConfigFilePath}: ${errorSummary}`);
|
|
2030
|
-
}
|
|
2031
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
|
|
2032
|
-
configFilePath: resolvedConfigFilePath,
|
|
2033
|
-
plugins: []
|
|
2034
|
-
};
|
|
2035
|
-
const pluginSpecs = Array.isArray(parsed.plugin) ? parsed.plugin : [];
|
|
2036
2119
|
return {
|
|
2037
|
-
|
|
2038
|
-
|
|
2120
|
+
data: { plugins: normalizeConfiguredPluginLabels(result.value) },
|
|
2121
|
+
status: "ok"
|
|
2039
2122
|
};
|
|
2040
2123
|
}
|
|
2041
2124
|
async function loadWorkspaceStatusResult(chatId, path, listSessionsUseCase, sessionRepo) {
|
|
@@ -2078,17 +2161,43 @@ function isUsableWorkspacePath(value) {
|
|
|
2078
2161
|
const normalized = value.trim();
|
|
2079
2162
|
return normalized.length > 0 && normalized !== "/" && normalized !== "\\" && isAbsolute(normalized);
|
|
2080
2163
|
}
|
|
2081
|
-
|
|
2164
|
+
function normalizeConfiguredPluginLabels(plugins) {
|
|
2165
|
+
const normalizedPlugins = [];
|
|
2166
|
+
const seenPlugins = /* @__PURE__ */ new Set();
|
|
2167
|
+
for (const plugin of plugins) {
|
|
2168
|
+
const normalizedPlugin = normalizeConfiguredPluginLabel(plugin);
|
|
2169
|
+
if (normalizedPlugin.length === 0 || seenPlugins.has(normalizedPlugin)) continue;
|
|
2170
|
+
seenPlugins.add(normalizedPlugin);
|
|
2171
|
+
normalizedPlugins.push(normalizedPlugin);
|
|
2172
|
+
}
|
|
2173
|
+
return normalizedPlugins;
|
|
2174
|
+
}
|
|
2175
|
+
function normalizeConfiguredPluginLabel(plugin) {
|
|
2176
|
+
const normalizedPlugin = plugin.trim();
|
|
2177
|
+
const localPluginName = extractLocalPluginName(normalizedPlugin);
|
|
2178
|
+
return localPluginName ? `${localPluginName} (local plugin)` : normalizedPlugin;
|
|
2179
|
+
}
|
|
2180
|
+
function extractLocalPluginName(plugin) {
|
|
2181
|
+
const filePath = parseFilePluginPath(plugin);
|
|
2182
|
+
if (!filePath) return null;
|
|
2183
|
+
const pathSegments = filePath.split("/").filter((segment) => segment.length > 0);
|
|
2184
|
+
const fileName = pathSegments.at(-1);
|
|
2185
|
+
const parentDirectoryName = pathSegments.at(-2);
|
|
2186
|
+
if (!fileName || parentDirectoryName !== "plugins") return null;
|
|
2187
|
+
const pluginName = fileName.replace(/\.(?:[cm]?js|[cm]?ts)$/iu, "");
|
|
2188
|
+
return pluginName.trim().length > 0 ? pluginName.trim() : null;
|
|
2189
|
+
}
|
|
2190
|
+
function parseFilePluginPath(plugin) {
|
|
2191
|
+
if (!plugin.startsWith("file://")) return null;
|
|
2082
2192
|
try {
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2193
|
+
const fileUrl = new URL(plugin);
|
|
2194
|
+
if (fileUrl.protocol !== "file:") return null;
|
|
2195
|
+
const normalizedPath = decodeURIComponent(fileUrl.pathname).replace(/^\/([A-Za-z]:)/u, "$1").replace(/\\/gu, "/");
|
|
2196
|
+
return normalizedPath.length > 0 ? normalizedPath : null;
|
|
2197
|
+
} catch {
|
|
2198
|
+
return null;
|
|
2087
2199
|
}
|
|
2088
2200
|
}
|
|
2089
|
-
function isMissingFileError(error) {
|
|
2090
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
2091
|
-
}
|
|
2092
2201
|
function isSelectableAgent(agent) {
|
|
2093
2202
|
return !agent.hidden && agent.mode !== "subagent";
|
|
2094
2203
|
}
|
|
@@ -2396,7 +2505,7 @@ var SendPromptUseCase = class {
|
|
|
2396
2505
|
prompt: promptText,
|
|
2397
2506
|
...files.length > 0 ? { files } : {},
|
|
2398
2507
|
...selectedAgent ? { agent: selectedAgent.name } : {},
|
|
2399
|
-
|
|
2508
|
+
format: { type: "text" },
|
|
2400
2509
|
...model ? { model } : {},
|
|
2401
2510
|
...input.signal ? { signal: input.signal } : {},
|
|
2402
2511
|
...activeBinding.modelVariant ? { variant: activeBinding.modelVariant } : {}
|
|
@@ -2698,7 +2807,7 @@ function createContainer(config, opencodeClient, logger) {
|
|
|
2698
2807
|
const listLspUseCase = new ListLspUseCase(sessionRepo, opencodeClient);
|
|
2699
2808
|
const listMcpUseCase = new ListMcpUseCase(sessionRepo, opencodeClient);
|
|
2700
2809
|
const listSessionsUseCase = new ListSessionsUseCase(sessionRepo, opencodeClient);
|
|
2701
|
-
const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo);
|
|
2810
|
+
const getStatusUseCase = new GetStatusUseCase(getHealthUseCase, getPathUseCase, listLspUseCase, listMcpUseCase, listSessionsUseCase, sessionRepo, opencodeClient);
|
|
2702
2811
|
const listModelsUseCase = new ListModelsUseCase(sessionRepo, opencodeClient);
|
|
2703
2812
|
const renameSessionUseCase = new RenameSessionUseCase(sessionRepo, opencodeClient, opencodeLogger);
|
|
2704
2813
|
const sendPromptUseCase = new SendPromptUseCase(sessionRepo, opencodeClient, promptLogger);
|
|
@@ -2900,7 +3009,7 @@ async function handleSessionError(runtime, event) {
|
|
|
2900
3009
|
component: "plugin-event",
|
|
2901
3010
|
sessionId: event.sessionId
|
|
2902
3011
|
});
|
|
2903
|
-
if (runtime.container.foregroundSessionTracker.fail(event.sessionId, event.error
|
|
3012
|
+
if (runtime.container.foregroundSessionTracker.fail(event.sessionId, normalizeForegroundSessionError(event.error))) {
|
|
2904
3013
|
logger.warn({
|
|
2905
3014
|
error: event.error,
|
|
2906
3015
|
event: "plugin-event.session.error.foreground_suppressed"
|
|
@@ -2915,11 +3024,7 @@ async function handleSessionIdle(runtime, event) {
|
|
|
2915
3024
|
component: "plugin-event",
|
|
2916
3025
|
sessionId: event.sessionId
|
|
2917
3026
|
});
|
|
2918
|
-
if (runtime.container.foregroundSessionTracker.clear(event.sessionId)) {
|
|
2919
|
-
logPluginEvent(logger, { event: "plugin-event.session.idle.foreground_suppressed" }, "session idle notification suppressed for foreground Telegram session");
|
|
2920
|
-
return;
|
|
2921
|
-
}
|
|
2922
|
-
await notifyBoundChats(runtime, event.sessionId, `Session finished.\n\nSession: ${event.sessionId}`);
|
|
3027
|
+
if (runtime.container.foregroundSessionTracker.clear(event.sessionId)) logPluginEvent(logger, { event: "plugin-event.session.idle.foreground_suppressed" }, "session idle notification suppressed for foreground Telegram session");
|
|
2923
3028
|
}
|
|
2924
3029
|
async function handleSessionStatus(runtime, event) {
|
|
2925
3030
|
if (event.statusType !== "idle") return;
|
|
@@ -3018,6 +3123,16 @@ function extractSessionErrorMessage(error) {
|
|
|
3018
3123
|
if (isPlainRecord(error.data) && typeof error.data.message === "string" && error.data.message.trim().length > 0) return error.data.message.trim();
|
|
3019
3124
|
return asNonEmptyString(error.name);
|
|
3020
3125
|
}
|
|
3126
|
+
function normalizeForegroundSessionError(error) {
|
|
3127
|
+
if (error instanceof Error) return error;
|
|
3128
|
+
if (isPlainRecord(error) && typeof error.name === "string" && error.name.trim().length > 0) {
|
|
3129
|
+
const normalized = new Error(extractSessionErrorMessage(error) ?? "Unknown session error.");
|
|
3130
|
+
normalized.name = error.name.trim();
|
|
3131
|
+
if (isPlainRecord(error.data)) normalized.data = error.data;
|
|
3132
|
+
return normalized;
|
|
3133
|
+
}
|
|
3134
|
+
return /* @__PURE__ */ new Error("Unknown session error.");
|
|
3135
|
+
}
|
|
3021
3136
|
function asNonEmptyString(value) {
|
|
3022
3137
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3023
3138
|
}
|
|
@@ -4259,7 +4374,7 @@ function getStatusLayoutCopy(copy) {
|
|
|
4259
4374
|
mcpNotesLabel: "Notes",
|
|
4260
4375
|
mcpRegistrationRequiredStatus: "🟡",
|
|
4261
4376
|
mcpTitle: "🔌 MCP",
|
|
4262
|
-
noPluginsMessage: "No plugins
|
|
4377
|
+
noPluginsMessage: "No plugins detected in the current OpenCode setup.",
|
|
4263
4378
|
noneStatus: "⚪",
|
|
4264
4379
|
openCodeVersionLabel: "OpenCode Version",
|
|
4265
4380
|
overviewTitle: "🖥️ Overview",
|
|
@@ -4280,7 +4395,7 @@ function getStatusLayoutCopy(copy) {
|
|
|
4280
4395
|
mcpNotesLabel: "補足",
|
|
4281
4396
|
mcpRegistrationRequiredStatus: "🟡",
|
|
4282
4397
|
mcpTitle: "🔌 MCP",
|
|
4283
|
-
noPluginsMessage: "現在の OpenCode
|
|
4398
|
+
noPluginsMessage: "現在の OpenCode 構成ではプラグインが検出されていません。",
|
|
4284
4399
|
noneStatus: "⚪",
|
|
4285
4400
|
openCodeVersionLabel: "OpenCode バージョン",
|
|
4286
4401
|
overviewTitle: "🖥️ 概要",
|
|
@@ -4301,7 +4416,7 @@ function getStatusLayoutCopy(copy) {
|
|
|
4301
4416
|
mcpNotesLabel: "说明",
|
|
4302
4417
|
mcpRegistrationRequiredStatus: "🟡",
|
|
4303
4418
|
mcpTitle: "🔌 MCP",
|
|
4304
|
-
noPluginsMessage: "当前 OpenCode
|
|
4419
|
+
noPluginsMessage: "当前 OpenCode 环境中未检测到插件。",
|
|
4305
4420
|
noneStatus: "⚪",
|
|
4306
4421
|
openCodeVersionLabel: "OpenCode版本",
|
|
4307
4422
|
overviewTitle: "🖥️ 概览",
|
|
@@ -4713,7 +4828,7 @@ var MARKDOWN_SPECIAL_CHARACTERS = /([_*\[\]()~`>#+\-=|{}.!\\])/g;
|
|
|
4713
4828
|
function buildTelegramPromptReply(result, copy = BOT_COPY) {
|
|
4714
4829
|
const renderedMarkdown = result.bodyMd ? renderMarkdownToTelegramMarkdownV2(result.bodyMd) : null;
|
|
4715
4830
|
const footerPlain = formatPlainMetricsFooter(result.metrics, copy);
|
|
4716
|
-
const fallback = { text: joinBodyAndFooter(truncatePlainBody(normalizePlainBody(result,
|
|
4831
|
+
const fallback = { text: joinBodyAndFooter(truncatePlainBody(normalizePlainBody(result, copy), footerPlain), footerPlain) };
|
|
4717
4832
|
if (!renderedMarkdown) return {
|
|
4718
4833
|
preferred: fallback,
|
|
4719
4834
|
fallback
|
|
@@ -4817,24 +4932,10 @@ function renderMarkdownToTelegramMarkdownV2(markdown) {
|
|
|
4817
4932
|
if (inCodeBlock) return null;
|
|
4818
4933
|
return rendered.join("\n");
|
|
4819
4934
|
}
|
|
4820
|
-
function
|
|
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) {
|
|
4935
|
+
function normalizePlainBody(result, copy) {
|
|
4835
4936
|
const fromStructured = result.bodyMd ? stripMarkdownToPlainText(result.bodyMd) : "";
|
|
4836
4937
|
const fromFallback = result.fallbackText.trim();
|
|
4837
|
-
return (
|
|
4938
|
+
return (fromStructured || fromFallback).trim() || result.fallbackText || copy.prompt.emptyResponse;
|
|
4838
4939
|
}
|
|
4839
4940
|
function truncatePlainBody(body, footer) {
|
|
4840
4941
|
const reservedLength = footer.length + 2;
|
|
@@ -4958,9 +5059,6 @@ function renderTableAsTelegramCodeBlock(rows) {
|
|
|
4958
5059
|
"```"
|
|
4959
5060
|
].join("\n");
|
|
4960
5061
|
}
|
|
4961
|
-
function renderTableAsPlainText(rows) {
|
|
4962
|
-
return buildAlignedTableLines(rows).join("\n");
|
|
4963
|
-
}
|
|
4964
5062
|
function buildAlignedTableLines(rows) {
|
|
4965
5063
|
const columnWidths = calculateTableColumnWidths(rows);
|
|
4966
5064
|
return [
|
|
@@ -5417,7 +5515,7 @@ async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
|
5417
5515
|
},
|
|
5418
5516
|
signal: foregroundRequest.signal,
|
|
5419
5517
|
text: promptInput.text
|
|
5420
|
-
})).assistantReply, copy
|
|
5518
|
+
})).assistantReply, copy), copy);
|
|
5421
5519
|
try {
|
|
5422
5520
|
await ctx.reply(telegramReply.preferred.text, telegramReply.preferred.options);
|
|
5423
5521
|
} catch (replyError) {
|
|
@@ -5438,26 +5536,14 @@ async function executePromptRequest(ctx, dependencies, resolvePrompt) {
|
|
|
5438
5536
|
}
|
|
5439
5537
|
}
|
|
5440
5538
|
}
|
|
5441
|
-
function normalizePromptReplyForDisplay(promptReply, copy
|
|
5539
|
+
function normalizePromptReplyForDisplay(promptReply, copy) {
|
|
5442
5540
|
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
5541
|
return {
|
|
5451
5542
|
...promptReply,
|
|
5452
5543
|
bodyMd: null,
|
|
5453
5544
|
fallbackText: presentError(promptReply.assistantError, copy)
|
|
5454
5545
|
};
|
|
5455
5546
|
}
|
|
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
5547
|
//#endregion
|
|
5462
5548
|
//#region src/bot/handlers/file.handler.ts
|
|
5463
5549
|
var TELEGRAM_MAX_DOWNLOAD_BYTES = 20 * 1024 * 1024;
|