@openclaw-china/qqbot 2026.3.18 → 2026.3.19
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/index.d.ts +27 -0
- package/dist/index.js +473 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4232,6 +4232,7 @@ var QQBotTypingHeartbeatModeSchema = external_exports.enum(["none", "idle", "alw
|
|
|
4232
4232
|
var DEFAULT_QQBOT_TYPING_HEARTBEAT_MODE = "idle";
|
|
4233
4233
|
var DEFAULT_QQBOT_TYPING_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
4234
4234
|
var DEFAULT_QQBOT_TYPING_INPUT_SECONDS = 60;
|
|
4235
|
+
var DEFAULT_QQBOT_C2C_MARKDOWN_SAFE_CHUNK_BYTE_LIMIT = 1200;
|
|
4235
4236
|
var QQBotAccountSchema = external_exports.object({
|
|
4236
4237
|
name: external_exports.string().optional(),
|
|
4237
4238
|
enabled: external_exports.boolean().optional(),
|
|
@@ -4247,6 +4248,7 @@ var QQBotAccountSchema = external_exports.object({
|
|
|
4247
4248
|
markdownSupport: external_exports.boolean().optional().default(true),
|
|
4248
4249
|
c2cMarkdownDeliveryMode: QQBotC2CMarkdownDeliveryModeSchema,
|
|
4249
4250
|
c2cMarkdownChunkStrategy: QQBotC2CMarkdownChunkStrategySchema,
|
|
4251
|
+
c2cMarkdownSafeChunkByteLimit: external_exports.number().int().positive().optional(),
|
|
4250
4252
|
typingHeartbeatMode: QQBotTypingHeartbeatModeSchema,
|
|
4251
4253
|
typingHeartbeatIntervalMs: external_exports.number().int().positive().optional().default(
|
|
4252
4254
|
DEFAULT_QQBOT_TYPING_HEARTBEAT_INTERVAL_MS
|
|
@@ -4303,6 +4305,10 @@ function resolveInboundMediaKeepDays(config) {
|
|
|
4303
4305
|
function resolveQQBotAutoSendLocalPathMedia(config) {
|
|
4304
4306
|
return config?.autoSendLocalPathMedia ?? true;
|
|
4305
4307
|
}
|
|
4308
|
+
function resolveQQBotC2CMarkdownSafeChunkByteLimit(config) {
|
|
4309
|
+
const value = config?.c2cMarkdownSafeChunkByteLimit;
|
|
4310
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
4311
|
+
}
|
|
4306
4312
|
function resolveQQBotTypingHeartbeatMode(config) {
|
|
4307
4313
|
return config?.typingHeartbeatMode ?? DEFAULT_QQBOT_TYPING_HEARTBEAT_MODE;
|
|
4308
4314
|
}
|
|
@@ -6568,6 +6574,7 @@ var CHANNEL_ORDER = [
|
|
|
6568
6574
|
"wecom",
|
|
6569
6575
|
"wecom-app",
|
|
6570
6576
|
"wecom-kf",
|
|
6577
|
+
"wechat-mp",
|
|
6571
6578
|
"feishu-china"
|
|
6572
6579
|
];
|
|
6573
6580
|
var CHANNEL_DISPLAY_LABELS = {
|
|
@@ -6576,6 +6583,7 @@ var CHANNEL_DISPLAY_LABELS = {
|
|
|
6576
6583
|
wecom: "WeCom\uFF08\u4F01\u4E1A\u5FAE\u4FE1-\u667A\u80FD\u673A\u5668\u4EBA\uFF09",
|
|
6577
6584
|
"wecom-app": "WeCom App\uFF08\u81EA\u5EFA\u5E94\u7528-\u53EF\u63A5\u5165\u5FAE\u4FE1\uFF09",
|
|
6578
6585
|
"wecom-kf": "WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09",
|
|
6586
|
+
"wechat-mp": "WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09",
|
|
6579
6587
|
qqbot: "QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09"
|
|
6580
6588
|
};
|
|
6581
6589
|
var CHANNEL_GUIDE_LINKS = {
|
|
@@ -6584,6 +6592,7 @@ var CHANNEL_GUIDE_LINKS = {
|
|
|
6584
6592
|
wecom: `${GUIDES_BASE}/wecom/configuration.md`,
|
|
6585
6593
|
"wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
|
|
6586
6594
|
"wecom-kf": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/extensions/wecom-kf/README.md",
|
|
6595
|
+
"wechat-mp": `${GUIDES_BASE}/wechat-mp/configuration.md`,
|
|
6587
6596
|
qqbot: `${GUIDES_BASE}/qqbot/configuration.md`
|
|
6588
6597
|
};
|
|
6589
6598
|
var CHINA_CLI_STATE_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-cli-state");
|
|
@@ -6793,7 +6802,9 @@ function isChannelConfigured(cfg, channelId) {
|
|
|
6793
6802
|
case "wecom-app":
|
|
6794
6803
|
return hasTokenPair(channelCfg);
|
|
6795
6804
|
case "wecom-kf":
|
|
6796
|
-
return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.
|
|
6805
|
+
return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.token) && hasNonEmptyString(channelCfg.encodingAESKey);
|
|
6806
|
+
case "wechat-mp":
|
|
6807
|
+
return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.token);
|
|
6797
6808
|
default:
|
|
6798
6809
|
return false;
|
|
6799
6810
|
}
|
|
@@ -7054,6 +7065,15 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
7054
7065
|
section("\u914D\u7F6E WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09");
|
|
7055
7066
|
showGuideLink("wecom-kf");
|
|
7056
7067
|
const existing = getChannelConfig(cfg, "wecom-kf");
|
|
7068
|
+
Ve(
|
|
7069
|
+
[
|
|
7070
|
+
"\u5411\u5BFC\u987A\u5E8F\uFF1AwebhookPath / token / encodingAESKey / corpId / open_kfid / corpSecret",
|
|
7071
|
+
"\u57FA\u7840\u5FC5\u586B\uFF1AcorpId / token / encodingAESKey / open_kfid",
|
|
7072
|
+
"corpSecret \u4F1A\u4F5C\u4E3A\u6700\u540E\u4E00\u4E2A\u53C2\u6570\u8BE2\u95EE\uFF1B\u9996\u6B21\u63A5\u5165\u53EF\u5148\u7559\u7A7A\uFF0C\u5F85\u56DE\u8C03 URL \u6821\u9A8C\u901A\u8FC7\u5E76\u70B9\u51FB\u201C\u5F00\u59CB\u4F7F\u7528\u201D\u540E\u518D\u8865",
|
|
7073
|
+
"webhookPath \u9ED8\u8BA4\u503C\uFF1A/wecom-kf"
|
|
7074
|
+
].join("\n"),
|
|
7075
|
+
"\u53C2\u6570\u8BF4\u660E"
|
|
7076
|
+
);
|
|
7057
7077
|
const webhookPath = await prompter.askText({
|
|
7058
7078
|
label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wecom-kf\uFF09",
|
|
7059
7079
|
defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wecom-kf",
|
|
@@ -7074,19 +7094,14 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
7074
7094
|
defaultValue: toTrimmedString2(existing.corpId),
|
|
7075
7095
|
required: true
|
|
7076
7096
|
});
|
|
7077
|
-
const corpSecret = await prompter.askSecret({
|
|
7078
|
-
label: "\u5FAE\u4FE1\u5BA2\u670D Secret",
|
|
7079
|
-
existingValue: toTrimmedString2(existing.corpSecret),
|
|
7080
|
-
required: true
|
|
7081
|
-
});
|
|
7082
7097
|
const openKfId = await prompter.askText({
|
|
7083
7098
|
label: "open_kfid",
|
|
7084
7099
|
defaultValue: toTrimmedString2(existing.openKfId),
|
|
7085
7100
|
required: true
|
|
7086
7101
|
});
|
|
7087
|
-
const
|
|
7088
|
-
label: "\
|
|
7089
|
-
|
|
7102
|
+
const corpSecret = await prompter.askSecret({
|
|
7103
|
+
label: "\u5FAE\u4FE1\u5BA2\u670D Secret\uFF08\u6700\u540E\u586B\u5199\uFF1B\u9996\u6B21\u63A5\u5165\u53EF\u5148\u7559\u7A7A\uFF09",
|
|
7104
|
+
existingValue: toTrimmedString2(existing.corpSecret),
|
|
7090
7105
|
required: false
|
|
7091
7106
|
});
|
|
7092
7107
|
return mergeChannelConfig(cfg, "wecom-kf", {
|
|
@@ -7094,8 +7109,72 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
7094
7109
|
token,
|
|
7095
7110
|
encodingAESKey,
|
|
7096
7111
|
corpId,
|
|
7097
|
-
corpSecret,
|
|
7098
7112
|
openKfId,
|
|
7113
|
+
corpSecret: corpSecret || void 0
|
|
7114
|
+
});
|
|
7115
|
+
}
|
|
7116
|
+
async function configureWechatMp(prompter, cfg) {
|
|
7117
|
+
section("\u914D\u7F6E WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09");
|
|
7118
|
+
showGuideLink("wechat-mp");
|
|
7119
|
+
const existing = getChannelConfig(cfg, "wechat-mp");
|
|
7120
|
+
const webhookPath = await prompter.askText({
|
|
7121
|
+
label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wechat-mp\uFF09",
|
|
7122
|
+
defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wechat-mp",
|
|
7123
|
+
required: true
|
|
7124
|
+
});
|
|
7125
|
+
const appId = await prompter.askText({
|
|
7126
|
+
label: "\u516C\u4F17\u53F7 appId",
|
|
7127
|
+
defaultValue: toTrimmedString2(existing.appId),
|
|
7128
|
+
required: true
|
|
7129
|
+
});
|
|
7130
|
+
const appSecret = await prompter.askSecret({
|
|
7131
|
+
label: "\u516C\u4F17\u53F7 appSecret\uFF08\u4E3B\u52A8\u53D1\u9001\u9700\u8981\uFF09",
|
|
7132
|
+
existingValue: toTrimmedString2(existing.appSecret),
|
|
7133
|
+
required: false
|
|
7134
|
+
});
|
|
7135
|
+
const token = await prompter.askSecret({
|
|
7136
|
+
label: "\u670D\u52A1\u5668\u914D\u7F6E token",
|
|
7137
|
+
existingValue: toTrimmedString2(existing.token),
|
|
7138
|
+
required: true
|
|
7139
|
+
});
|
|
7140
|
+
const messageMode = await prompter.askSelect(
|
|
7141
|
+
"\u6D88\u606F\u52A0\u89E3\u5BC6\u6A21\u5F0F",
|
|
7142
|
+
[
|
|
7143
|
+
{ value: "plain", label: "plain\uFF08\u660E\u6587\uFF09" },
|
|
7144
|
+
{ value: "safe", label: "safe\uFF08\u5B89\u5168\u6A21\u5F0F\uFF09" },
|
|
7145
|
+
{ value: "compat", label: "compat\uFF08\u517C\u5BB9\u6A21\u5F0F\uFF09" }
|
|
7146
|
+
],
|
|
7147
|
+
toTrimmedString2(existing.messageMode) ?? "safe"
|
|
7148
|
+
);
|
|
7149
|
+
let encodingAESKey = toTrimmedString2(existing.encodingAESKey);
|
|
7150
|
+
if (messageMode !== "plain") {
|
|
7151
|
+
encodingAESKey = await prompter.askSecret({
|
|
7152
|
+
label: "EncodingAESKey\uFF08safe/compat \u5FC5\u586B\uFF09",
|
|
7153
|
+
existingValue: encodingAESKey,
|
|
7154
|
+
required: true
|
|
7155
|
+
});
|
|
7156
|
+
}
|
|
7157
|
+
const replyMode = await prompter.askSelect(
|
|
7158
|
+
"\u56DE\u590D\u6A21\u5F0F",
|
|
7159
|
+
[
|
|
7160
|
+
{ value: "passive", label: "passive\uFF085 \u79D2\u5185\u88AB\u52A8\u56DE\u590D\uFF09" },
|
|
7161
|
+
{ value: "active", label: "active\uFF08\u5BA2\u670D\u6D88\u606F\u4E3B\u52A8\u53D1\u9001\uFF09" }
|
|
7162
|
+
],
|
|
7163
|
+
toTrimmedString2(existing.replyMode) ?? "passive"
|
|
7164
|
+
);
|
|
7165
|
+
const welcomeText = await prompter.askText({
|
|
7166
|
+
label: "\u6B22\u8FCE\u8BED\uFF08\u53EF\u9009\uFF09",
|
|
7167
|
+
defaultValue: toTrimmedString2(existing.welcomeText),
|
|
7168
|
+
required: false
|
|
7169
|
+
});
|
|
7170
|
+
return mergeChannelConfig(cfg, "wechat-mp", {
|
|
7171
|
+
webhookPath,
|
|
7172
|
+
appId,
|
|
7173
|
+
appSecret: appSecret || void 0,
|
|
7174
|
+
token,
|
|
7175
|
+
encodingAESKey: messageMode === "plain" ? void 0 : encodingAESKey,
|
|
7176
|
+
messageMode,
|
|
7177
|
+
replyMode,
|
|
7099
7178
|
welcomeText: welcomeText || void 0
|
|
7100
7179
|
});
|
|
7101
7180
|
}
|
|
@@ -7157,6 +7236,8 @@ async function configureSingleChannel(channel, prompter, cfg) {
|
|
|
7157
7236
|
return configureWecomApp(prompter, cfg);
|
|
7158
7237
|
case "wecom-kf":
|
|
7159
7238
|
return configureWecomKf(prompter, cfg);
|
|
7239
|
+
case "wechat-mp":
|
|
7240
|
+
return configureWechatMp(prompter, cfg);
|
|
7160
7241
|
case "qqbot":
|
|
7161
7242
|
return configureQQBot(prompter, cfg);
|
|
7162
7243
|
default:
|
|
@@ -7298,6 +7379,7 @@ var SUPPORTED_CHANNELS = [
|
|
|
7298
7379
|
"wecom",
|
|
7299
7380
|
"wecom-app",
|
|
7300
7381
|
"wecom-kf",
|
|
7382
|
+
"wechat-mp",
|
|
7301
7383
|
"qqbot"
|
|
7302
7384
|
];
|
|
7303
7385
|
var CHINA_INSTALL_HINT_SHOWN_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
@@ -10136,7 +10218,7 @@ function extractLocalMediaFromText(params) {
|
|
|
10136
10218
|
const MARKDOWN_LINKED_IMAGE_RE2 = /\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)/g;
|
|
10137
10219
|
const MARKDOWN_IMAGE_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
10138
10220
|
const MARKDOWN_LINK_RE2 = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
10139
|
-
const BARE_LOCAL_MEDIA_PATH_RE = /`?((?:\/(?:tmp|var|private|Users|home|root)\/[^\s`'",)]+|[A-Za-z]:[\\/][^\s`'",)]+)\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico|mp3|wav|ogg|m4a|amr|flac|aac|wma|mp4|mov|avi|mkv|webm|flv|wmv|m4v))`?/gi;
|
|
10221
|
+
const BARE_LOCAL_MEDIA_PATH_RE = /`?((?:\/(?:tmp|var|private|Users|home|root)\/[^\s`'",)]+|\/mnt\/[A-Za-z]\/[^\s`'",)]+|[A-Za-z]:[\\/][^\s`'",)]+)\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico|mp3|wav|ogg|m4a|amr|flac|aac|wma|mp4|mov|avi|mkv|webm|flv|wmv|m4v))`?/gi;
|
|
10140
10222
|
const collectLocalRichMedia = (rawValue, allowedTypes) => {
|
|
10141
10223
|
const candidate = stripTitleFromUrl(rawValue.trim());
|
|
10142
10224
|
if (!candidate || !isLocalReference(candidate)) {
|
|
@@ -10245,6 +10327,10 @@ var MARKDOWN_INLINE_STRUCTURE_RE = /(?:\*\*[^*\n]+\*\*|__[^_\n]+__|`[^`\n]+`|~~[
|
|
|
10245
10327
|
var MARKDOWN_BOUNDARY_GUARD_RE = /[`*_~|]/;
|
|
10246
10328
|
var EXPLICIT_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*(?:markdown|md)\s*\n([\s\S]*?)\n\2(?=\n|$)/gi;
|
|
10247
10329
|
var GENERIC_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*\n([\s\S]*?)\n\2(?=\n|$)/g;
|
|
10330
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_THRESHOLD = 128;
|
|
10331
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MIN = 16;
|
|
10332
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MAX = 320;
|
|
10333
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_RATIO = 0.18;
|
|
10248
10334
|
function extractFinalBlocks(text) {
|
|
10249
10335
|
const matches = Array.from(text.matchAll(FINAL_BLOCK_RE));
|
|
10250
10336
|
if (matches.length === 0) return void 0;
|
|
@@ -10384,6 +10470,72 @@ function appendQQBotBufferedText(bufferedTexts, nextText) {
|
|
|
10384
10470
|
}
|
|
10385
10471
|
return [...bufferedTexts, normalized];
|
|
10386
10472
|
}
|
|
10473
|
+
function resolveQQBotLastNonEmptyLine(text) {
|
|
10474
|
+
return text.split("\n").map((line) => line.trimEnd()).reverse().find((line) => line.trim().length > 0);
|
|
10475
|
+
}
|
|
10476
|
+
function resolveQQBotFirstNonEmptyLine(text) {
|
|
10477
|
+
return text.split("\n").map((line) => line.trimStart()).find((line) => line.trim().length > 0);
|
|
10478
|
+
}
|
|
10479
|
+
function resolveQQBotTrailingMarkdownTableColumnCount(text) {
|
|
10480
|
+
const lines = text.split("\n");
|
|
10481
|
+
for (let index = Math.max(0, lines.length - 2); index >= 0; index -= 1) {
|
|
10482
|
+
if (!isQQBotMarkdownTableStart(lines, index)) {
|
|
10483
|
+
continue;
|
|
10484
|
+
}
|
|
10485
|
+
const trailingLines = lines.slice(index + 2).filter((line) => line.trim().length > 0);
|
|
10486
|
+
if (trailingLines.length === 0 || trailingLines.every((line) => line.includes("|"))) {
|
|
10487
|
+
return parseQQBotMarkdownTableRowCells(lines[index] ?? "").length;
|
|
10488
|
+
}
|
|
10489
|
+
}
|
|
10490
|
+
return void 0;
|
|
10491
|
+
}
|
|
10492
|
+
function mergeQQBotBufferedTextSegments(current, next) {
|
|
10493
|
+
const currentTrimmed = current.trimEnd();
|
|
10494
|
+
const nextTrimmed = next.trimStart();
|
|
10495
|
+
if (!currentTrimmed) return nextTrimmed;
|
|
10496
|
+
if (!nextTrimmed) return currentTrimmed;
|
|
10497
|
+
if (currentTrimmed === nextTrimmed || currentTrimmed.includes(nextTrimmed)) {
|
|
10498
|
+
return currentTrimmed;
|
|
10499
|
+
}
|
|
10500
|
+
if (nextTrimmed.includes(currentTrimmed)) {
|
|
10501
|
+
return nextTrimmed;
|
|
10502
|
+
}
|
|
10503
|
+
const lastLine = resolveQQBotLastNonEmptyLine(currentTrimmed) ?? "";
|
|
10504
|
+
const firstLine = resolveQQBotFirstNonEmptyLine(nextTrimmed) ?? "";
|
|
10505
|
+
const trailingTableColumnCount = resolveQQBotTrailingMarkdownTableColumnCount(currentTrimmed);
|
|
10506
|
+
const lastLineLooksLikeTable = lastLine.includes("|");
|
|
10507
|
+
const firstLineLooksLikeTable = firstLine.startsWith("|");
|
|
10508
|
+
const firstLineContainsTableCell = firstLine.includes("|");
|
|
10509
|
+
const expectedPipeCount = typeof trailingTableColumnCount === "number" && trailingTableColumnCount > 0 ? trailingTableColumnCount + 1 : void 0;
|
|
10510
|
+
const lastLinePipeCount = (lastLine.match(/\|/g) ?? []).length;
|
|
10511
|
+
const firstLinePipeCount = (firstLine.match(/\|/g) ?? []).length;
|
|
10512
|
+
const sameRowJoiner = typeof expectedPipeCount === "number" && lastLinePipeCount + firstLinePipeCount < expectedPipeCount ? " | " : " ";
|
|
10513
|
+
const lastLineEndsInsideTableRow = Boolean(
|
|
10514
|
+
trailingTableColumnCount && lastLineLooksLikeTable && (!lastLine.trimEnd().endsWith("|") || typeof expectedPipeCount === "number" && lastLinePipeCount < expectedPipeCount)
|
|
10515
|
+
);
|
|
10516
|
+
if (lastLineEndsInsideTableRow && nextTrimmed.includes("|")) {
|
|
10517
|
+
return `${currentTrimmed}${sameRowJoiner}${nextTrimmed}`;
|
|
10518
|
+
}
|
|
10519
|
+
if (firstLineLooksLikeTable) {
|
|
10520
|
+
if (lastLineLooksLikeTable || hasQQBotMarkdownTable(currentTrimmed)) {
|
|
10521
|
+
return `${currentTrimmed}
|
|
10522
|
+
${nextTrimmed}`;
|
|
10523
|
+
}
|
|
10524
|
+
}
|
|
10525
|
+
if (trailingTableColumnCount && firstLineContainsTableCell) {
|
|
10526
|
+
return `${currentTrimmed}${sameRowJoiner}${nextTrimmed}`;
|
|
10527
|
+
}
|
|
10528
|
+
return joinQQBotMarkdownPieces([currentTrimmed, nextTrimmed]);
|
|
10529
|
+
}
|
|
10530
|
+
function combineQQBotBufferedText(bufferedTexts) {
|
|
10531
|
+
return bufferedTexts.reduce((combined, segment) => {
|
|
10532
|
+
const normalized = segment.trim();
|
|
10533
|
+
if (!normalized) {
|
|
10534
|
+
return combined;
|
|
10535
|
+
}
|
|
10536
|
+
return mergeQQBotBufferedTextSegments(combined, normalized);
|
|
10537
|
+
}, "");
|
|
10538
|
+
}
|
|
10387
10539
|
function normalizeQQBotRenderedMarkdown(text) {
|
|
10388
10540
|
if (!text.trim()) return "";
|
|
10389
10541
|
let next = text.trim();
|
|
@@ -10603,6 +10755,25 @@ function parseQQBotMarkdownBlocks(text) {
|
|
|
10603
10755
|
function hasQQBotBoundaryGuard(text) {
|
|
10604
10756
|
return MARKDOWN_BOUNDARY_GUARD_RE.test(text);
|
|
10605
10757
|
}
|
|
10758
|
+
function measureQQBotUtf8Length(text) {
|
|
10759
|
+
return Buffer.byteLength(text, "utf8");
|
|
10760
|
+
}
|
|
10761
|
+
function findQQBotIndexWithinUtf8Limit(text, limit) {
|
|
10762
|
+
if (limit <= 0 || !text) {
|
|
10763
|
+
return 0;
|
|
10764
|
+
}
|
|
10765
|
+
let totalBytes = 0;
|
|
10766
|
+
let index = 0;
|
|
10767
|
+
for (const char of text) {
|
|
10768
|
+
const charBytes = measureQQBotUtf8Length(char);
|
|
10769
|
+
if (totalBytes + charBytes > limit) {
|
|
10770
|
+
break;
|
|
10771
|
+
}
|
|
10772
|
+
totalBytes += charBytes;
|
|
10773
|
+
index += char.length;
|
|
10774
|
+
}
|
|
10775
|
+
return index;
|
|
10776
|
+
}
|
|
10606
10777
|
function isQQBotSafeMarkdownBoundary(text, index) {
|
|
10607
10778
|
const left = text.slice(Math.max(0, index - 3), index).replace(/\s+/g, "");
|
|
10608
10779
|
const right = text.slice(index, Math.min(text.length, index + 3)).replace(/\s+/g, "");
|
|
@@ -10611,13 +10782,14 @@ function isQQBotSafeMarkdownBoundary(text, index) {
|
|
|
10611
10782
|
return !hasQQBotBoundaryGuard(leftEdge) && !hasQQBotBoundaryGuard(rightEdge);
|
|
10612
10783
|
}
|
|
10613
10784
|
function findQQBotRegexBoundary(text, limit, pattern) {
|
|
10614
|
-
const
|
|
10785
|
+
const scopedIndex = findQQBotIndexWithinUtf8Limit(text, limit);
|
|
10786
|
+
const scopedText = text.slice(0, scopedIndex);
|
|
10615
10787
|
const regex = new RegExp(pattern.source, pattern.flags);
|
|
10616
10788
|
let match = regex.exec(scopedText);
|
|
10617
10789
|
let lastBoundary;
|
|
10618
10790
|
while (match) {
|
|
10619
10791
|
const boundary = match.index + match[0].length;
|
|
10620
|
-
if (boundary > 0 && boundary <= limit && isQQBotSafeMarkdownBoundary(text, boundary)) {
|
|
10792
|
+
if (boundary > 0 && measureQQBotUtf8Length(text.slice(0, boundary)) <= limit && isQQBotSafeMarkdownBoundary(text, boundary)) {
|
|
10621
10793
|
lastBoundary = boundary;
|
|
10622
10794
|
}
|
|
10623
10795
|
match = regex.exec(scopedText);
|
|
@@ -10625,13 +10797,14 @@ function findQQBotRegexBoundary(text, limit, pattern) {
|
|
|
10625
10797
|
return lastBoundary;
|
|
10626
10798
|
}
|
|
10627
10799
|
function findQQBotFallbackBoundary(text, limit) {
|
|
10628
|
-
const
|
|
10629
|
-
|
|
10800
|
+
const maxIndex = findQQBotIndexWithinUtf8Limit(text, limit);
|
|
10801
|
+
const minIndex = Math.max(1, maxIndex - 120);
|
|
10802
|
+
for (let index = maxIndex; index >= minIndex; index -= 1) {
|
|
10630
10803
|
if (isQQBotSafeMarkdownBoundary(text, index)) {
|
|
10631
10804
|
return index;
|
|
10632
10805
|
}
|
|
10633
10806
|
}
|
|
10634
|
-
return
|
|
10807
|
+
return maxIndex;
|
|
10635
10808
|
}
|
|
10636
10809
|
function findQQBotSafeSplitIndex(text, limit) {
|
|
10637
10810
|
const boundaryPatterns = [
|
|
@@ -10650,14 +10823,15 @@ function findQQBotSafeSplitIndex(text, limit) {
|
|
|
10650
10823
|
return findQQBotFallbackBoundary(text, limit);
|
|
10651
10824
|
}
|
|
10652
10825
|
function splitQQBotHardText(text, limit) {
|
|
10653
|
-
if (limit <= 0 || text
|
|
10826
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10654
10827
|
return [text];
|
|
10655
10828
|
}
|
|
10656
10829
|
const chunks = [];
|
|
10657
10830
|
let remaining = text;
|
|
10658
|
-
while (remaining
|
|
10659
|
-
|
|
10660
|
-
remaining
|
|
10831
|
+
while (measureQQBotUtf8Length(remaining) > limit) {
|
|
10832
|
+
const nextIndex = Math.max(1, findQQBotIndexWithinUtf8Limit(remaining, limit));
|
|
10833
|
+
chunks.push(remaining.slice(0, nextIndex));
|
|
10834
|
+
remaining = remaining.slice(nextIndex);
|
|
10661
10835
|
}
|
|
10662
10836
|
if (remaining) {
|
|
10663
10837
|
chunks.push(remaining);
|
|
@@ -10665,14 +10839,14 @@ function splitQQBotHardText(text, limit) {
|
|
|
10665
10839
|
return chunks;
|
|
10666
10840
|
}
|
|
10667
10841
|
function splitQQBotTextSafely(text, limit, options) {
|
|
10668
|
-
if (limit <= 0 || text
|
|
10842
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10669
10843
|
return [text];
|
|
10670
10844
|
}
|
|
10671
10845
|
const trimLeading = options?.trimLeading ?? true;
|
|
10672
10846
|
const trimTrailing = options?.trimTrailing ?? true;
|
|
10673
10847
|
const chunks = [];
|
|
10674
10848
|
let remaining = text;
|
|
10675
|
-
while (remaining
|
|
10849
|
+
while (measureQQBotUtf8Length(remaining) > limit) {
|
|
10676
10850
|
const splitIndex = findQQBotSafeSplitIndex(remaining, limit);
|
|
10677
10851
|
let nextChunk = remaining.slice(0, splitIndex);
|
|
10678
10852
|
let nextRemaining = remaining.slice(splitIndex);
|
|
@@ -10683,7 +10857,8 @@ function splitQQBotTextSafely(text, limit, options) {
|
|
|
10683
10857
|
nextRemaining = nextRemaining.trimStart();
|
|
10684
10858
|
}
|
|
10685
10859
|
if (!nextChunk) {
|
|
10686
|
-
const
|
|
10860
|
+
const hardChunkIndex = Math.max(1, findQQBotIndexWithinUtf8Limit(remaining, limit));
|
|
10861
|
+
const hardChunk = remaining.slice(0, hardChunkIndex);
|
|
10687
10862
|
chunks.push(hardChunk);
|
|
10688
10863
|
remaining = remaining.slice(hardChunk.length);
|
|
10689
10864
|
continue;
|
|
@@ -10698,7 +10873,7 @@ function splitQQBotTextSafely(text, limit, options) {
|
|
|
10698
10873
|
return chunks;
|
|
10699
10874
|
}
|
|
10700
10875
|
function splitQQBotMarkdownLineBlock(text, limit) {
|
|
10701
|
-
if (limit <= 0 || text
|
|
10876
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10702
10877
|
return [text];
|
|
10703
10878
|
}
|
|
10704
10879
|
const lines = text.split("\n");
|
|
@@ -10717,12 +10892,12 @@ function splitQQBotMarkdownLineBlock(text, limit) {
|
|
|
10717
10892
|
for (const line of lines) {
|
|
10718
10893
|
const candidate = currentLines.length > 0 ? `${currentLines.join("\n")}
|
|
10719
10894
|
${line}` : line;
|
|
10720
|
-
if (candidate
|
|
10895
|
+
if (measureQQBotUtf8Length(candidate) <= limit) {
|
|
10721
10896
|
currentLines.push(line);
|
|
10722
10897
|
continue;
|
|
10723
10898
|
}
|
|
10724
10899
|
flushCurrent();
|
|
10725
|
-
if (line
|
|
10900
|
+
if (measureQQBotUtf8Length(line) <= limit) {
|
|
10726
10901
|
currentLines.push(line);
|
|
10727
10902
|
continue;
|
|
10728
10903
|
}
|
|
@@ -10738,16 +10913,133 @@ ${line}` : line;
|
|
|
10738
10913
|
flushCurrent();
|
|
10739
10914
|
return chunks;
|
|
10740
10915
|
}
|
|
10916
|
+
function parseQQBotMarkdownTableRowCells(row, columnCount) {
|
|
10917
|
+
const trimmed = row.trim();
|
|
10918
|
+
const inner = trimmed.replace(/^\|\s*/, "").replace(/\s*\|$/, "");
|
|
10919
|
+
const cells = inner.split("|").map((cell) => cell.trim());
|
|
10920
|
+
if (typeof columnCount !== "number" || !Number.isFinite(columnCount) || columnCount <= 0) {
|
|
10921
|
+
return cells;
|
|
10922
|
+
}
|
|
10923
|
+
if (cells.length >= columnCount) {
|
|
10924
|
+
return cells.slice(0, columnCount);
|
|
10925
|
+
}
|
|
10926
|
+
return [...cells, ...Array.from({ length: columnCount - cells.length }, () => "")];
|
|
10927
|
+
}
|
|
10928
|
+
function renderQQBotMarkdownTableRow(cells) {
|
|
10929
|
+
return `| ${cells.join(" | ")} |`;
|
|
10930
|
+
}
|
|
10931
|
+
function splitQQBotMarkdownTableRowByCells(params) {
|
|
10932
|
+
const { row, columnCount, limit } = params;
|
|
10933
|
+
const normalizedCells = parseQQBotMarkdownTableRowCells(row, columnCount);
|
|
10934
|
+
const normalizedRow = renderQQBotMarkdownTableRow(normalizedCells);
|
|
10935
|
+
if (measureQQBotUtf8Length(normalizedRow) <= limit) {
|
|
10936
|
+
return [normalizedRow];
|
|
10937
|
+
}
|
|
10938
|
+
const anchorColumnCount = Math.min(
|
|
10939
|
+
Math.max(1, params.anchorColumnCount ?? 2),
|
|
10940
|
+
columnCount
|
|
10941
|
+
);
|
|
10942
|
+
const anchorCells = normalizedCells.map(
|
|
10943
|
+
(cell, index) => index < anchorColumnCount ? cell : ""
|
|
10944
|
+
);
|
|
10945
|
+
const chunks = [];
|
|
10946
|
+
let currentCells = [...anchorCells];
|
|
10947
|
+
let hasContent = false;
|
|
10948
|
+
const flushCurrent = () => {
|
|
10949
|
+
if (!hasContent) {
|
|
10950
|
+
return;
|
|
10951
|
+
}
|
|
10952
|
+
chunks.push(renderQQBotMarkdownTableRow(currentCells));
|
|
10953
|
+
currentCells = [...anchorCells];
|
|
10954
|
+
hasContent = false;
|
|
10955
|
+
};
|
|
10956
|
+
for (let index = anchorColumnCount; index < columnCount; index += 1) {
|
|
10957
|
+
const cell = normalizedCells[index] ?? "";
|
|
10958
|
+
if (!cell) {
|
|
10959
|
+
continue;
|
|
10960
|
+
}
|
|
10961
|
+
const candidateCells = [...currentCells];
|
|
10962
|
+
candidateCells[index] = cell;
|
|
10963
|
+
if (measureQQBotUtf8Length(renderQQBotMarkdownTableRow(candidateCells)) <= limit) {
|
|
10964
|
+
currentCells[index] = cell;
|
|
10965
|
+
hasContent = true;
|
|
10966
|
+
continue;
|
|
10967
|
+
}
|
|
10968
|
+
if (hasContent) {
|
|
10969
|
+
flushCurrent();
|
|
10970
|
+
const nextCandidateCells = [...currentCells];
|
|
10971
|
+
nextCandidateCells[index] = cell;
|
|
10972
|
+
if (measureQQBotUtf8Length(renderQQBotMarkdownTableRow(nextCandidateCells)) <= limit) {
|
|
10973
|
+
currentCells[index] = cell;
|
|
10974
|
+
hasContent = true;
|
|
10975
|
+
continue;
|
|
10976
|
+
}
|
|
10977
|
+
}
|
|
10978
|
+
const emptyRowBytes = measureQQBotUtf8Length(renderQQBotMarkdownTableRow(currentCells));
|
|
10979
|
+
const availableCellBytes = Math.max(1, limit - emptyRowBytes);
|
|
10980
|
+
for (const cellPiece of splitQQBotTextSafely(cell, availableCellBytes, {
|
|
10981
|
+
trimLeading: false,
|
|
10982
|
+
trimTrailing: false
|
|
10983
|
+
})) {
|
|
10984
|
+
const pieceCells = [...anchorCells];
|
|
10985
|
+
pieceCells[index] = cellPiece;
|
|
10986
|
+
chunks.push(renderQQBotMarkdownTableRow(pieceCells));
|
|
10987
|
+
}
|
|
10988
|
+
currentCells = [...anchorCells];
|
|
10989
|
+
hasContent = false;
|
|
10990
|
+
}
|
|
10991
|
+
flushCurrent();
|
|
10992
|
+
return chunks.length > 0 ? chunks : splitQQBotTextSafely(normalizedRow, limit);
|
|
10993
|
+
}
|
|
10994
|
+
function resolveQQBotMarkdownTableBlockLimit(params) {
|
|
10995
|
+
const { header, separator, rows, limit } = params;
|
|
10996
|
+
if (limit <= 512) {
|
|
10997
|
+
return limit;
|
|
10998
|
+
}
|
|
10999
|
+
const columnCount = parseQQBotMarkdownTableRowCells(header).length;
|
|
11000
|
+
if (columnCount < 8) {
|
|
11001
|
+
return limit;
|
|
11002
|
+
}
|
|
11003
|
+
const tablePrefixBytes = measureQQBotUtf8Length(`${header}
|
|
11004
|
+
${separator}`);
|
|
11005
|
+
const extraColumnCount = Math.max(0, columnCount - 7);
|
|
11006
|
+
const columnPenalty = Math.min(240, extraColumnCount * 55);
|
|
11007
|
+
const headerPenalty = Math.min(120, Math.floor(tablePrefixBytes * 0.25));
|
|
11008
|
+
const reducedLimit = limit - columnPenalty - headerPenalty;
|
|
11009
|
+
const minTableLimit = Math.max(tablePrefixBytes + 64, Math.floor(limit * 0.45));
|
|
11010
|
+
const maxSingleRowRequirement = Math.min(
|
|
11011
|
+
limit,
|
|
11012
|
+
rows.reduce((max, row) => {
|
|
11013
|
+
const normalizedRow = renderQQBotMarkdownTableRow(
|
|
11014
|
+
parseQQBotMarkdownTableRowCells(row, columnCount)
|
|
11015
|
+
);
|
|
11016
|
+
return Math.max(max, measureQQBotUtf8Length(`${header}
|
|
11017
|
+
${separator}
|
|
11018
|
+
${normalizedRow}`));
|
|
11019
|
+
}, tablePrefixBytes)
|
|
11020
|
+
);
|
|
11021
|
+
return Math.min(
|
|
11022
|
+
limit,
|
|
11023
|
+
Math.max(maxSingleRowRequirement, minTableLimit, Math.min(limit, reducedLimit))
|
|
11024
|
+
);
|
|
11025
|
+
}
|
|
10741
11026
|
function splitQQBotMarkdownTableBlock(text, limit) {
|
|
10742
|
-
if (limit <= 0 || text
|
|
11027
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10743
11028
|
return [text];
|
|
10744
11029
|
}
|
|
10745
11030
|
const lines = text.split("\n");
|
|
10746
11031
|
const header = lines[0] ?? "";
|
|
10747
11032
|
const separator = lines[1] ?? "";
|
|
10748
11033
|
const rows = lines.slice(2);
|
|
11034
|
+
const packingLimit = resolveQQBotMarkdownTableBlockLimit({
|
|
11035
|
+
header,
|
|
11036
|
+
separator,
|
|
11037
|
+
rows,
|
|
11038
|
+
limit
|
|
11039
|
+
});
|
|
10749
11040
|
const tablePrefix = `${header}
|
|
10750
11041
|
${separator}`;
|
|
11042
|
+
const columnCount = parseQQBotMarkdownTableRowCells(header).length;
|
|
10751
11043
|
const chunks = [];
|
|
10752
11044
|
let currentRows = [];
|
|
10753
11045
|
const flushCurrent = () => {
|
|
@@ -10763,20 +11055,21 @@ ${currentRows.join("\n")}`);
|
|
|
10763
11055
|
${currentRows.join("\n")}
|
|
10764
11056
|
${row}` : `${tablePrefix}
|
|
10765
11057
|
${row}`;
|
|
10766
|
-
if (candidate
|
|
11058
|
+
if (measureQQBotUtf8Length(candidate) <= packingLimit) {
|
|
10767
11059
|
currentRows.push(row);
|
|
10768
11060
|
continue;
|
|
10769
11061
|
}
|
|
10770
11062
|
flushCurrent();
|
|
10771
|
-
if (`${tablePrefix}
|
|
10772
|
-
${row}
|
|
11063
|
+
if (measureQQBotUtf8Length(`${tablePrefix}
|
|
11064
|
+
${row}`) <= limit) {
|
|
10773
11065
|
currentRows.push(row);
|
|
10774
11066
|
continue;
|
|
10775
11067
|
}
|
|
10776
|
-
const maxRowLength = Math.max(16, limit - tablePrefix
|
|
10777
|
-
for (const rowPiece of
|
|
10778
|
-
|
|
10779
|
-
|
|
11068
|
+
const maxRowLength = Math.max(16, limit - measureQQBotUtf8Length(tablePrefix) - 1);
|
|
11069
|
+
for (const rowPiece of splitQQBotMarkdownTableRowByCells({
|
|
11070
|
+
row,
|
|
11071
|
+
columnCount,
|
|
11072
|
+
limit: maxRowLength
|
|
10780
11073
|
})) {
|
|
10781
11074
|
chunks.push(`${tablePrefix}
|
|
10782
11075
|
${rowPiece}`);
|
|
@@ -10786,7 +11079,7 @@ ${rowPiece}`);
|
|
|
10786
11079
|
return chunks.length > 0 ? chunks : [text];
|
|
10787
11080
|
}
|
|
10788
11081
|
function splitQQBotMarkdownCodeFence(text, limit) {
|
|
10789
|
-
if (limit <= 0 || text
|
|
11082
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10790
11083
|
return [text];
|
|
10791
11084
|
}
|
|
10792
11085
|
const lines = text.split("\n");
|
|
@@ -10795,7 +11088,7 @@ function splitQQBotMarkdownCodeFence(text, limit) {
|
|
|
10795
11088
|
const hasClosingFence = lines.length > 1 && isQQBotFenceClosingLine(lines[lines.length - 1] ?? "", delimiter);
|
|
10796
11089
|
const closingLine = hasClosingFence ? lines[lines.length - 1] ?? delimiter : delimiter;
|
|
10797
11090
|
const codeLines = lines.slice(1, hasClosingFence ? -1 : lines.length);
|
|
10798
|
-
const fixedOverhead = openingLine
|
|
11091
|
+
const fixedOverhead = measureQQBotUtf8Length(openingLine) + measureQQBotUtf8Length(closingLine) + 2;
|
|
10799
11092
|
const availableLineLength = Math.max(1, limit - fixedOverhead);
|
|
10800
11093
|
const chunks = [];
|
|
10801
11094
|
let currentCodeLines = [];
|
|
@@ -10815,14 +11108,14 @@ ${codeLine}
|
|
|
10815
11108
|
${closingLine}` : `${openingLine}
|
|
10816
11109
|
${codeLine}
|
|
10817
11110
|
${closingLine}`;
|
|
10818
|
-
if (candidate
|
|
11111
|
+
if (measureQQBotUtf8Length(candidate) <= limit) {
|
|
10819
11112
|
currentCodeLines.push(codeLine);
|
|
10820
11113
|
continue;
|
|
10821
11114
|
}
|
|
10822
11115
|
flushCurrent();
|
|
10823
|
-
if (`${openingLine}
|
|
11116
|
+
if (measureQQBotUtf8Length(`${openingLine}
|
|
10824
11117
|
${codeLine}
|
|
10825
|
-
${closingLine}
|
|
11118
|
+
${closingLine}`) <= limit) {
|
|
10826
11119
|
currentCodeLines.push(codeLine);
|
|
10827
11120
|
continue;
|
|
10828
11121
|
}
|
|
@@ -10836,7 +11129,7 @@ ${closingLine}`);
|
|
|
10836
11129
|
return chunks.length > 0 ? chunks : [text];
|
|
10837
11130
|
}
|
|
10838
11131
|
function splitQQBotMarkdownBlock(block, limit) {
|
|
10839
|
-
if (limit <= 0 || block.text
|
|
11132
|
+
if (limit <= 0 || measureQQBotUtf8Length(block.text) <= limit) {
|
|
10840
11133
|
return [block.text];
|
|
10841
11134
|
}
|
|
10842
11135
|
switch (block.kind) {
|
|
@@ -10857,11 +11150,93 @@ function splitQQBotMarkdownBlock(block, limit) {
|
|
|
10857
11150
|
return [block.text];
|
|
10858
11151
|
}
|
|
10859
11152
|
}
|
|
10860
|
-
function
|
|
11153
|
+
function resolveQQBotStructuredMarkdownSoftLimit(limit, safeChunkByteLimit) {
|
|
11154
|
+
if (limit <= QQBOT_MARKDOWN_SOFT_LIMIT_THRESHOLD) {
|
|
11155
|
+
return limit;
|
|
11156
|
+
}
|
|
11157
|
+
const configuredSafeLimit = typeof safeChunkByteLimit === "number" && Number.isFinite(safeChunkByteLimit) && safeChunkByteLimit > 0 ? Math.floor(safeChunkByteLimit) : void 0;
|
|
11158
|
+
if (configuredSafeLimit) {
|
|
11159
|
+
return Math.min(limit, configuredSafeLimit);
|
|
11160
|
+
}
|
|
11161
|
+
const reservedLength = Math.min(
|
|
11162
|
+
QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MAX,
|
|
11163
|
+
Math.max(
|
|
11164
|
+
QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MIN,
|
|
11165
|
+
Math.floor(limit * QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_RATIO)
|
|
11166
|
+
)
|
|
11167
|
+
);
|
|
11168
|
+
const softLimit = limit - reservedLength;
|
|
11169
|
+
const boundedSoftLimit = Math.min(
|
|
11170
|
+
softLimit > 0 ? softLimit : limit,
|
|
11171
|
+
DEFAULT_QQBOT_C2C_MARKDOWN_SAFE_CHUNK_BYTE_LIMIT
|
|
11172
|
+
);
|
|
11173
|
+
return boundedSoftLimit > 0 ? boundedSoftLimit : limit;
|
|
11174
|
+
}
|
|
11175
|
+
function maybePrefixQQBotContinuationPiece(params) {
|
|
11176
|
+
const prefix = params.prefix?.trim();
|
|
11177
|
+
if (!prefix) {
|
|
11178
|
+
return params.piece;
|
|
11179
|
+
}
|
|
11180
|
+
const prefixedPiece = joinQQBotMarkdownPieces([prefix, params.piece]);
|
|
11181
|
+
return measureQQBotUtf8Length(prefixedPiece) <= params.limit ? prefixedPiece : params.piece;
|
|
11182
|
+
}
|
|
11183
|
+
function resolveQQBotMarkdownLeadPiece(blocks, index, limit) {
|
|
11184
|
+
const block = blocks[index];
|
|
11185
|
+
if (!block) {
|
|
11186
|
+
return void 0;
|
|
11187
|
+
}
|
|
11188
|
+
if (block.kind === "heading") {
|
|
11189
|
+
const nextBlock = blocks[index + 1];
|
|
11190
|
+
if (nextBlock && nextBlock.kind !== "thematic-break") {
|
|
11191
|
+
const nextPieces = splitQQBotMarkdownBlock(nextBlock, limit);
|
|
11192
|
+
const firstBodyPiece = nextPieces[0];
|
|
11193
|
+
if (firstBodyPiece) {
|
|
11194
|
+
const pairedText = joinQQBotMarkdownPieces([block.text, firstBodyPiece]);
|
|
11195
|
+
if (measureQQBotUtf8Length(pairedText) <= limit) {
|
|
11196
|
+
return pairedText;
|
|
11197
|
+
}
|
|
11198
|
+
}
|
|
11199
|
+
}
|
|
11200
|
+
}
|
|
11201
|
+
return splitQQBotMarkdownBlock(block, limit).map((piece) => piece.trim()).find(Boolean);
|
|
11202
|
+
}
|
|
11203
|
+
function shouldQQBotCarryThematicBreakToNextBlock(params) {
|
|
11204
|
+
const block = params.blocks[params.index];
|
|
11205
|
+
if (!block || block.kind !== "thematic-break") {
|
|
11206
|
+
return false;
|
|
11207
|
+
}
|
|
11208
|
+
if (params.currentPieces.length === 0) {
|
|
11209
|
+
return true;
|
|
11210
|
+
}
|
|
11211
|
+
const withBreak = joinQQBotMarkdownPieces([...params.currentPieces, block.text]);
|
|
11212
|
+
if (measureQQBotUtf8Length(withBreak) > params.limit) {
|
|
11213
|
+
return true;
|
|
11214
|
+
}
|
|
11215
|
+
const nextLeadPiece = resolveQQBotMarkdownLeadPiece(
|
|
11216
|
+
params.blocks,
|
|
11217
|
+
params.index + 1,
|
|
11218
|
+
params.limit
|
|
11219
|
+
);
|
|
11220
|
+
if (!nextLeadPiece) {
|
|
11221
|
+
return false;
|
|
11222
|
+
}
|
|
11223
|
+
const prefixedLeadPiece = joinQQBotMarkdownPieces([block.text, nextLeadPiece]);
|
|
11224
|
+
if (measureQQBotUtf8Length(prefixedLeadPiece) > params.limit) {
|
|
11225
|
+
return false;
|
|
11226
|
+
}
|
|
11227
|
+
const sectionCandidate = joinQQBotMarkdownPieces([
|
|
11228
|
+
...params.currentPieces,
|
|
11229
|
+
block.text,
|
|
11230
|
+
nextLeadPiece
|
|
11231
|
+
]);
|
|
11232
|
+
return measureQQBotUtf8Length(sectionCandidate) > params.limit;
|
|
11233
|
+
}
|
|
11234
|
+
function chunkQQBotStructuredMarkdown(text, limit, safeChunkByteLimit) {
|
|
10861
11235
|
const blocks = parseQQBotMarkdownBlocks(text);
|
|
10862
11236
|
if (blocks.length === 0 || limit <= 0) {
|
|
10863
11237
|
return [text.trim()];
|
|
10864
11238
|
}
|
|
11239
|
+
const chunkLimit = resolveQQBotStructuredMarkdownSoftLimit(limit, safeChunkByteLimit);
|
|
10865
11240
|
const chunks = [];
|
|
10866
11241
|
let currentPieces = [];
|
|
10867
11242
|
let pendingPrefixPieces = [];
|
|
@@ -10879,14 +11254,14 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10879
11254
|
if (!piece) {
|
|
10880
11255
|
return;
|
|
10881
11256
|
}
|
|
10882
|
-
const pieces = piece
|
|
11257
|
+
const pieces = measureQQBotUtf8Length(piece) > chunkLimit ? splitQQBotTextSafely(piece, chunkLimit) : [piece];
|
|
10883
11258
|
for (const nextPiece of pieces) {
|
|
10884
11259
|
const normalizedPiece = nextPiece.trim();
|
|
10885
11260
|
if (!normalizedPiece) {
|
|
10886
11261
|
continue;
|
|
10887
11262
|
}
|
|
10888
11263
|
const candidate = joinQQBotMarkdownPieces([...currentPieces, normalizedPiece]);
|
|
10889
|
-
if (currentPieces.length === 0 || candidate
|
|
11264
|
+
if (currentPieces.length === 0 || measureQQBotUtf8Length(candidate) <= chunkLimit) {
|
|
10890
11265
|
currentPieces.push(normalizedPiece);
|
|
10891
11266
|
continue;
|
|
10892
11267
|
}
|
|
@@ -10908,9 +11283,19 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10908
11283
|
continue;
|
|
10909
11284
|
}
|
|
10910
11285
|
if (block.kind === "thematic-break") {
|
|
11286
|
+
if (shouldQQBotCarryThematicBreakToNextBlock({
|
|
11287
|
+
blocks,
|
|
11288
|
+
index,
|
|
11289
|
+
currentPieces,
|
|
11290
|
+
limit: chunkLimit
|
|
11291
|
+
})) {
|
|
11292
|
+
flushCurrent();
|
|
11293
|
+
pendingPrefixPieces.push(block.text);
|
|
11294
|
+
continue;
|
|
11295
|
+
}
|
|
10911
11296
|
if (currentPieces.length > 0) {
|
|
10912
11297
|
const candidate = joinQQBotMarkdownPieces([...currentPieces, block.text]);
|
|
10913
|
-
if (candidate
|
|
11298
|
+
if (measureQQBotUtf8Length(candidate) <= chunkLimit) {
|
|
10914
11299
|
currentPieces.push(block.text);
|
|
10915
11300
|
continue;
|
|
10916
11301
|
}
|
|
@@ -10923,7 +11308,7 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10923
11308
|
const headingText = consumePendingPrefix(block.text);
|
|
10924
11309
|
const nextBlock = blocks[index + 1];
|
|
10925
11310
|
if (nextBlock && nextBlock.kind !== "thematic-break") {
|
|
10926
|
-
const nextPieces = splitQQBotMarkdownBlock(nextBlock,
|
|
11311
|
+
const nextPieces = splitQQBotMarkdownBlock(nextBlock, chunkLimit);
|
|
10927
11312
|
const firstBodyPiece = nextPieces[0];
|
|
10928
11313
|
if (firstBodyPiece) {
|
|
10929
11314
|
const pairedText = joinQQBotMarkdownPieces([headingText, firstBodyPiece]);
|
|
@@ -10932,19 +11317,33 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10932
11317
|
headingText,
|
|
10933
11318
|
firstBodyPiece
|
|
10934
11319
|
]);
|
|
10935
|
-
if (pairedText
|
|
11320
|
+
if (measureQQBotUtf8Length(pairedText) <= chunkLimit && (currentPieces.length === 0 || measureQQBotUtf8Length(pairedCandidate) <= chunkLimit)) {
|
|
10936
11321
|
currentPieces.push(headingText, firstBodyPiece);
|
|
10937
11322
|
for (let pieceIndex = 1; pieceIndex < nextPieces.length; pieceIndex += 1) {
|
|
10938
|
-
|
|
11323
|
+
const nextPiece = nextPieces[pieceIndex] ?? "";
|
|
11324
|
+
appendPiece(
|
|
11325
|
+
nextBlock.kind === "table" ? maybePrefixQQBotContinuationPiece({
|
|
11326
|
+
prefix: headingText,
|
|
11327
|
+
piece: nextPiece,
|
|
11328
|
+
limit: chunkLimit
|
|
11329
|
+
}) : nextPiece
|
|
11330
|
+
);
|
|
10939
11331
|
}
|
|
10940
11332
|
index += 1;
|
|
10941
11333
|
continue;
|
|
10942
11334
|
}
|
|
10943
|
-
if (currentPieces.length > 0 && pairedText
|
|
11335
|
+
if (currentPieces.length > 0 && measureQQBotUtf8Length(pairedText) <= chunkLimit) {
|
|
10944
11336
|
flushCurrent();
|
|
10945
11337
|
currentPieces.push(headingText, firstBodyPiece);
|
|
10946
11338
|
for (let pieceIndex = 1; pieceIndex < nextPieces.length; pieceIndex += 1) {
|
|
10947
|
-
|
|
11339
|
+
const nextPiece = nextPieces[pieceIndex] ?? "";
|
|
11340
|
+
appendPiece(
|
|
11341
|
+
nextBlock.kind === "table" ? maybePrefixQQBotContinuationPiece({
|
|
11342
|
+
prefix: headingText,
|
|
11343
|
+
piece: nextPiece,
|
|
11344
|
+
limit: chunkLimit
|
|
11345
|
+
}) : nextPiece
|
|
11346
|
+
);
|
|
10948
11347
|
}
|
|
10949
11348
|
index += 1;
|
|
10950
11349
|
continue;
|
|
@@ -10955,16 +11354,29 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10955
11354
|
continue;
|
|
10956
11355
|
}
|
|
10957
11356
|
const blockText = consumePendingPrefix(block.text);
|
|
10958
|
-
for (const piece of splitQQBotMarkdownBlock({ ...block, text: blockText },
|
|
11357
|
+
for (const piece of splitQQBotMarkdownBlock({ ...block, text: blockText }, chunkLimit)) {
|
|
10959
11358
|
appendPiece(piece);
|
|
10960
11359
|
}
|
|
10961
11360
|
}
|
|
10962
11361
|
if (pendingPrefixPieces.length > 0 && currentPieces.length > 0) {
|
|
10963
11362
|
const trailingCandidate = joinQQBotMarkdownPieces([...currentPieces, ...pendingPrefixPieces]);
|
|
10964
|
-
if (trailingCandidate
|
|
11363
|
+
if (measureQQBotUtf8Length(trailingCandidate) <= chunkLimit) {
|
|
10965
11364
|
currentPieces.push(...pendingPrefixPieces);
|
|
11365
|
+
pendingPrefixPieces = [];
|
|
11366
|
+
}
|
|
11367
|
+
}
|
|
11368
|
+
if (pendingPrefixPieces.length > 0 && chunks.length > 0) {
|
|
11369
|
+
const trailingPrefix = joinQQBotMarkdownPieces(pendingPrefixPieces);
|
|
11370
|
+
const lastChunk = chunks[chunks.length - 1] ?? "";
|
|
11371
|
+
const trailingCandidate = joinQQBotMarkdownPieces([lastChunk, trailingPrefix]);
|
|
11372
|
+
if (measureQQBotUtf8Length(trailingCandidate) <= chunkLimit) {
|
|
11373
|
+
chunks[chunks.length - 1] = trailingCandidate;
|
|
11374
|
+
pendingPrefixPieces = [];
|
|
10966
11375
|
}
|
|
10967
11376
|
}
|
|
11377
|
+
if (pendingPrefixPieces.length > 0) {
|
|
11378
|
+
currentPieces.push(joinQQBotMarkdownPieces(pendingPrefixPieces));
|
|
11379
|
+
}
|
|
10968
11380
|
flushCurrent();
|
|
10969
11381
|
return chunks.length > 0 ? chunks : [text.trim()];
|
|
10970
11382
|
}
|
|
@@ -10991,7 +11403,7 @@ function chunkC2CMarkdownText(params) {
|
|
|
10991
11403
|
if (params.limit <= 0 || !looksLikeStructuredMarkdown(normalized)) {
|
|
10992
11404
|
return params.fallbackChunkText ? params.fallbackChunkText(normalized) : [normalized];
|
|
10993
11405
|
}
|
|
10994
|
-
return chunkQQBotStructuredMarkdown(normalized, params.limit);
|
|
11406
|
+
return chunkQQBotStructuredMarkdown(normalized, params.limit, params.safeChunkByteLimit);
|
|
10995
11407
|
}
|
|
10996
11408
|
async function sendQQBotMediaWithFallback(params) {
|
|
10997
11409
|
const { qqCfg, to, mediaQueue, replyToId, replyEventId, accountId, logger, onDelivered, onError } = params;
|
|
@@ -11367,6 +11779,7 @@ async function dispatchToAgent(params) {
|
|
|
11367
11779
|
const markdownSupport = qqCfg.markdownSupport ?? true;
|
|
11368
11780
|
const c2cMarkdownDeliveryMode = qqCfg.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
11369
11781
|
const c2cMarkdownChunkStrategy = qqCfg.c2cMarkdownChunkStrategy ?? "markdown-block";
|
|
11782
|
+
const c2cMarkdownSafeChunkByteLimit = resolveQQBotC2CMarkdownSafeChunkByteLimit(qqCfg);
|
|
11370
11783
|
const isC2CTarget = isQQBotC2CTarget(target.to);
|
|
11371
11784
|
const useC2CMarkdownTransport = markdownSupport && isC2CTarget;
|
|
11372
11785
|
let bufferedC2CMarkdownTexts = [];
|
|
@@ -11404,11 +11817,12 @@ async function dispatchToAgent(params) {
|
|
|
11404
11817
|
text: finalMarkdownText,
|
|
11405
11818
|
limit,
|
|
11406
11819
|
strategy: c2cMarkdownChunkStrategy,
|
|
11820
|
+
safeChunkByteLimit: c2cMarkdownSafeChunkByteLimit,
|
|
11407
11821
|
fallbackChunkText: chunkText
|
|
11408
11822
|
}) : [];
|
|
11409
11823
|
const deliveryLabel = textReplyRefs.forceProactive ? "c2c-markdown-proactive" : "c2c-markdown-passive";
|
|
11410
11824
|
logger.info(
|
|
11411
|
-
`delivery=${deliveryLabel} to=${target.to} chunks=${textChunks.length} media=${mediaQueue.length} replyToId=${textReplyRefs.replyToId ? "yes" : "no"} replyEventId=${textReplyRefs.replyEventId ? "yes" : "no"} phase=${params2.phase} tableMode=${String(resolvedTableMode)} chunkMode=${String(chunkMode ?? "default")} chunkStrategy=${c2cMarkdownChunkStrategy}`
|
|
11825
|
+
`delivery=${deliveryLabel} to=${target.to} chunks=${textChunks.length} media=${mediaQueue.length} replyToId=${textReplyRefs.replyToId ? "yes" : "no"} replyEventId=${textReplyRefs.replyEventId ? "yes" : "no"} phase=${params2.phase} tableMode=${String(resolvedTableMode)} chunkMode=${String(chunkMode ?? "default")} chunkStrategy=${c2cMarkdownChunkStrategy} safeChunkByteLimit=${String(c2cMarkdownSafeChunkByteLimit ?? "auto")}`
|
|
11412
11826
|
);
|
|
11413
11827
|
if (!shouldSuppressVisibleReplies()) {
|
|
11414
11828
|
if (mediaQueue.length > 0) {
|
|
@@ -11473,7 +11887,7 @@ async function dispatchToAgent(params) {
|
|
|
11473
11887
|
bufferedC2CMarkdownMediaSeen.clear();
|
|
11474
11888
|
return;
|
|
11475
11889
|
}
|
|
11476
|
-
const combinedText = bufferedC2CMarkdownTexts
|
|
11890
|
+
const combinedText = combineQQBotBufferedText(bufferedC2CMarkdownTexts);
|
|
11477
11891
|
const combinedMediaUrls = [...bufferedC2CMarkdownMediaUrls];
|
|
11478
11892
|
bufferedC2CMarkdownTexts = [];
|
|
11479
11893
|
bufferedC2CMarkdownMediaUrls = [];
|
|
@@ -11519,7 +11933,7 @@ async function dispatchToAgent(params) {
|
|
|
11519
11933
|
const textToSend = suppressText ? "" : cleanedText;
|
|
11520
11934
|
if (useC2CMarkdownTransport) {
|
|
11521
11935
|
const shouldBufferFinalOnlyPayload = replyFinalOnly && (!info?.kind || info.kind === "final");
|
|
11522
|
-
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && looksLikeStructuredMarkdown(textToSend);
|
|
11936
|
+
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && (hasBufferedC2CMarkdownReply() || looksLikeStructuredMarkdown(textToSend));
|
|
11523
11937
|
if (shouldBufferFinalOnlyPayload || shouldBufferStructuredMarkdownPayload) {
|
|
11524
11938
|
if (textToSend) {
|
|
11525
11939
|
bufferedC2CMarkdownTexts = appendQQBotBufferedText(bufferedC2CMarkdownTexts, textToSend);
|
|
@@ -12304,6 +12718,7 @@ var qqbotPlugin = {
|
|
|
12304
12718
|
type: "string",
|
|
12305
12719
|
enum: ["markdown-block", "length"]
|
|
12306
12720
|
},
|
|
12721
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12307
12722
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12308
12723
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12309
12724
|
requireMention: { type: "boolean" },
|
|
@@ -12357,6 +12772,7 @@ var qqbotPlugin = {
|
|
|
12357
12772
|
type: "string",
|
|
12358
12773
|
enum: ["markdown-block", "length"]
|
|
12359
12774
|
},
|
|
12775
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12360
12776
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12361
12777
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12362
12778
|
requireMention: { type: "boolean" },
|
|
@@ -12569,6 +12985,7 @@ var plugin = {
|
|
|
12569
12985
|
type: "string",
|
|
12570
12986
|
enum: ["markdown-block", "length"]
|
|
12571
12987
|
},
|
|
12988
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12572
12989
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12573
12990
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12574
12991
|
requireMention: { type: "boolean" },
|
|
@@ -12622,6 +13039,7 @@ var plugin = {
|
|
|
12622
13039
|
type: "string",
|
|
12623
13040
|
enum: ["markdown-block", "length"]
|
|
12624
13041
|
},
|
|
13042
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12625
13043
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12626
13044
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12627
13045
|
requireMention: { type: "boolean" },
|