@openclaw-china/qqbot 2026.3.18 → 2026.3.20
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 +490 -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,89 @@ 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
|
+
let activeDeliveryMode;
|
|
7166
|
+
if (replyMode === "active") {
|
|
7167
|
+
activeDeliveryMode = await prompter.askSelect(
|
|
7168
|
+
"\u4E3B\u52A8\u53D1\u9001\u6A21\u5F0F\uFF08activeDeliveryMode\uFF09",
|
|
7169
|
+
[
|
|
7170
|
+
{ value: "split", label: "split\uFF08\u9010\u5757\u53D1\u9001\uFF0C\u63A8\u8350\uFF09" },
|
|
7171
|
+
{ value: "merged", label: "merged\uFF08\u5408\u5E76\u540E\u5355\u6B21\u53D1\u9001\uFF09" }
|
|
7172
|
+
],
|
|
7173
|
+
toTrimmedString2(existing.activeDeliveryMode) ?? "split"
|
|
7174
|
+
);
|
|
7175
|
+
}
|
|
7176
|
+
const renderMarkdown = await prompter.askConfirm(
|
|
7177
|
+
"\u542F\u7528 Markdown \u6E32\u67D3\uFF08\u63A8\u8350\u5F00\u542F\uFF09",
|
|
7178
|
+
toBoolean(existing.renderMarkdown, true)
|
|
7179
|
+
);
|
|
7180
|
+
const welcomeText = await prompter.askText({
|
|
7181
|
+
label: "\u6B22\u8FCE\u8BED\uFF08\u53EF\u9009\uFF09",
|
|
7182
|
+
defaultValue: toTrimmedString2(existing.welcomeText),
|
|
7183
|
+
required: false
|
|
7184
|
+
});
|
|
7185
|
+
return mergeChannelConfig(cfg, "wechat-mp", {
|
|
7186
|
+
webhookPath,
|
|
7187
|
+
appId,
|
|
7188
|
+
appSecret: appSecret || void 0,
|
|
7189
|
+
token,
|
|
7190
|
+
encodingAESKey: messageMode === "plain" ? void 0 : encodingAESKey,
|
|
7191
|
+
messageMode,
|
|
7192
|
+
replyMode,
|
|
7193
|
+
activeDeliveryMode,
|
|
7194
|
+
renderMarkdown,
|
|
7099
7195
|
welcomeText: welcomeText || void 0
|
|
7100
7196
|
});
|
|
7101
7197
|
}
|
|
@@ -7157,6 +7253,8 @@ async function configureSingleChannel(channel, prompter, cfg) {
|
|
|
7157
7253
|
return configureWecomApp(prompter, cfg);
|
|
7158
7254
|
case "wecom-kf":
|
|
7159
7255
|
return configureWecomKf(prompter, cfg);
|
|
7256
|
+
case "wechat-mp":
|
|
7257
|
+
return configureWechatMp(prompter, cfg);
|
|
7160
7258
|
case "qqbot":
|
|
7161
7259
|
return configureQQBot(prompter, cfg);
|
|
7162
7260
|
default:
|
|
@@ -7298,6 +7396,7 @@ var SUPPORTED_CHANNELS = [
|
|
|
7298
7396
|
"wecom",
|
|
7299
7397
|
"wecom-app",
|
|
7300
7398
|
"wecom-kf",
|
|
7399
|
+
"wechat-mp",
|
|
7301
7400
|
"qqbot"
|
|
7302
7401
|
];
|
|
7303
7402
|
var CHINA_INSTALL_HINT_SHOWN_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
@@ -10136,7 +10235,7 @@ function extractLocalMediaFromText(params) {
|
|
|
10136
10235
|
const MARKDOWN_LINKED_IMAGE_RE2 = /\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)/g;
|
|
10137
10236
|
const MARKDOWN_IMAGE_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
10138
10237
|
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;
|
|
10238
|
+
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
10239
|
const collectLocalRichMedia = (rawValue, allowedTypes) => {
|
|
10141
10240
|
const candidate = stripTitleFromUrl(rawValue.trim());
|
|
10142
10241
|
if (!candidate || !isLocalReference(candidate)) {
|
|
@@ -10245,6 +10344,10 @@ var MARKDOWN_INLINE_STRUCTURE_RE = /(?:\*\*[^*\n]+\*\*|__[^_\n]+__|`[^`\n]+`|~~[
|
|
|
10245
10344
|
var MARKDOWN_BOUNDARY_GUARD_RE = /[`*_~|]/;
|
|
10246
10345
|
var EXPLICIT_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*(?:markdown|md)\s*\n([\s\S]*?)\n\2(?=\n|$)/gi;
|
|
10247
10346
|
var GENERIC_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*\n([\s\S]*?)\n\2(?=\n|$)/g;
|
|
10347
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_THRESHOLD = 128;
|
|
10348
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MIN = 16;
|
|
10349
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MAX = 320;
|
|
10350
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_RATIO = 0.18;
|
|
10248
10351
|
function extractFinalBlocks(text) {
|
|
10249
10352
|
const matches = Array.from(text.matchAll(FINAL_BLOCK_RE));
|
|
10250
10353
|
if (matches.length === 0) return void 0;
|
|
@@ -10384,6 +10487,72 @@ function appendQQBotBufferedText(bufferedTexts, nextText) {
|
|
|
10384
10487
|
}
|
|
10385
10488
|
return [...bufferedTexts, normalized];
|
|
10386
10489
|
}
|
|
10490
|
+
function resolveQQBotLastNonEmptyLine(text) {
|
|
10491
|
+
return text.split("\n").map((line) => line.trimEnd()).reverse().find((line) => line.trim().length > 0);
|
|
10492
|
+
}
|
|
10493
|
+
function resolveQQBotFirstNonEmptyLine(text) {
|
|
10494
|
+
return text.split("\n").map((line) => line.trimStart()).find((line) => line.trim().length > 0);
|
|
10495
|
+
}
|
|
10496
|
+
function resolveQQBotTrailingMarkdownTableColumnCount(text) {
|
|
10497
|
+
const lines = text.split("\n");
|
|
10498
|
+
for (let index = Math.max(0, lines.length - 2); index >= 0; index -= 1) {
|
|
10499
|
+
if (!isQQBotMarkdownTableStart(lines, index)) {
|
|
10500
|
+
continue;
|
|
10501
|
+
}
|
|
10502
|
+
const trailingLines = lines.slice(index + 2).filter((line) => line.trim().length > 0);
|
|
10503
|
+
if (trailingLines.length === 0 || trailingLines.every((line) => line.includes("|"))) {
|
|
10504
|
+
return parseQQBotMarkdownTableRowCells(lines[index] ?? "").length;
|
|
10505
|
+
}
|
|
10506
|
+
}
|
|
10507
|
+
return void 0;
|
|
10508
|
+
}
|
|
10509
|
+
function mergeQQBotBufferedTextSegments(current, next) {
|
|
10510
|
+
const currentTrimmed = current.trimEnd();
|
|
10511
|
+
const nextTrimmed = next.trimStart();
|
|
10512
|
+
if (!currentTrimmed) return nextTrimmed;
|
|
10513
|
+
if (!nextTrimmed) return currentTrimmed;
|
|
10514
|
+
if (currentTrimmed === nextTrimmed || currentTrimmed.includes(nextTrimmed)) {
|
|
10515
|
+
return currentTrimmed;
|
|
10516
|
+
}
|
|
10517
|
+
if (nextTrimmed.includes(currentTrimmed)) {
|
|
10518
|
+
return nextTrimmed;
|
|
10519
|
+
}
|
|
10520
|
+
const lastLine = resolveQQBotLastNonEmptyLine(currentTrimmed) ?? "";
|
|
10521
|
+
const firstLine = resolveQQBotFirstNonEmptyLine(nextTrimmed) ?? "";
|
|
10522
|
+
const trailingTableColumnCount = resolveQQBotTrailingMarkdownTableColumnCount(currentTrimmed);
|
|
10523
|
+
const lastLineLooksLikeTable = lastLine.includes("|");
|
|
10524
|
+
const firstLineLooksLikeTable = firstLine.startsWith("|");
|
|
10525
|
+
const firstLineContainsTableCell = firstLine.includes("|");
|
|
10526
|
+
const expectedPipeCount = typeof trailingTableColumnCount === "number" && trailingTableColumnCount > 0 ? trailingTableColumnCount + 1 : void 0;
|
|
10527
|
+
const lastLinePipeCount = (lastLine.match(/\|/g) ?? []).length;
|
|
10528
|
+
const firstLinePipeCount = (firstLine.match(/\|/g) ?? []).length;
|
|
10529
|
+
const sameRowJoiner = typeof expectedPipeCount === "number" && lastLinePipeCount + firstLinePipeCount < expectedPipeCount ? " | " : " ";
|
|
10530
|
+
const lastLineEndsInsideTableRow = Boolean(
|
|
10531
|
+
trailingTableColumnCount && lastLineLooksLikeTable && (!lastLine.trimEnd().endsWith("|") || typeof expectedPipeCount === "number" && lastLinePipeCount < expectedPipeCount)
|
|
10532
|
+
);
|
|
10533
|
+
if (lastLineEndsInsideTableRow && nextTrimmed.includes("|")) {
|
|
10534
|
+
return `${currentTrimmed}${sameRowJoiner}${nextTrimmed}`;
|
|
10535
|
+
}
|
|
10536
|
+
if (firstLineLooksLikeTable) {
|
|
10537
|
+
if (lastLineLooksLikeTable || hasQQBotMarkdownTable(currentTrimmed)) {
|
|
10538
|
+
return `${currentTrimmed}
|
|
10539
|
+
${nextTrimmed}`;
|
|
10540
|
+
}
|
|
10541
|
+
}
|
|
10542
|
+
if (trailingTableColumnCount && firstLineContainsTableCell) {
|
|
10543
|
+
return `${currentTrimmed}${sameRowJoiner}${nextTrimmed}`;
|
|
10544
|
+
}
|
|
10545
|
+
return joinQQBotMarkdownPieces([currentTrimmed, nextTrimmed]);
|
|
10546
|
+
}
|
|
10547
|
+
function combineQQBotBufferedText(bufferedTexts) {
|
|
10548
|
+
return bufferedTexts.reduce((combined, segment) => {
|
|
10549
|
+
const normalized = segment.trim();
|
|
10550
|
+
if (!normalized) {
|
|
10551
|
+
return combined;
|
|
10552
|
+
}
|
|
10553
|
+
return mergeQQBotBufferedTextSegments(combined, normalized);
|
|
10554
|
+
}, "");
|
|
10555
|
+
}
|
|
10387
10556
|
function normalizeQQBotRenderedMarkdown(text) {
|
|
10388
10557
|
if (!text.trim()) return "";
|
|
10389
10558
|
let next = text.trim();
|
|
@@ -10603,6 +10772,25 @@ function parseQQBotMarkdownBlocks(text) {
|
|
|
10603
10772
|
function hasQQBotBoundaryGuard(text) {
|
|
10604
10773
|
return MARKDOWN_BOUNDARY_GUARD_RE.test(text);
|
|
10605
10774
|
}
|
|
10775
|
+
function measureQQBotUtf8Length(text) {
|
|
10776
|
+
return Buffer.byteLength(text, "utf8");
|
|
10777
|
+
}
|
|
10778
|
+
function findQQBotIndexWithinUtf8Limit(text, limit) {
|
|
10779
|
+
if (limit <= 0 || !text) {
|
|
10780
|
+
return 0;
|
|
10781
|
+
}
|
|
10782
|
+
let totalBytes = 0;
|
|
10783
|
+
let index = 0;
|
|
10784
|
+
for (const char of text) {
|
|
10785
|
+
const charBytes = measureQQBotUtf8Length(char);
|
|
10786
|
+
if (totalBytes + charBytes > limit) {
|
|
10787
|
+
break;
|
|
10788
|
+
}
|
|
10789
|
+
totalBytes += charBytes;
|
|
10790
|
+
index += char.length;
|
|
10791
|
+
}
|
|
10792
|
+
return index;
|
|
10793
|
+
}
|
|
10606
10794
|
function isQQBotSafeMarkdownBoundary(text, index) {
|
|
10607
10795
|
const left = text.slice(Math.max(0, index - 3), index).replace(/\s+/g, "");
|
|
10608
10796
|
const right = text.slice(index, Math.min(text.length, index + 3)).replace(/\s+/g, "");
|
|
@@ -10611,13 +10799,14 @@ function isQQBotSafeMarkdownBoundary(text, index) {
|
|
|
10611
10799
|
return !hasQQBotBoundaryGuard(leftEdge) && !hasQQBotBoundaryGuard(rightEdge);
|
|
10612
10800
|
}
|
|
10613
10801
|
function findQQBotRegexBoundary(text, limit, pattern) {
|
|
10614
|
-
const
|
|
10802
|
+
const scopedIndex = findQQBotIndexWithinUtf8Limit(text, limit);
|
|
10803
|
+
const scopedText = text.slice(0, scopedIndex);
|
|
10615
10804
|
const regex = new RegExp(pattern.source, pattern.flags);
|
|
10616
10805
|
let match = regex.exec(scopedText);
|
|
10617
10806
|
let lastBoundary;
|
|
10618
10807
|
while (match) {
|
|
10619
10808
|
const boundary = match.index + match[0].length;
|
|
10620
|
-
if (boundary > 0 && boundary <= limit && isQQBotSafeMarkdownBoundary(text, boundary)) {
|
|
10809
|
+
if (boundary > 0 && measureQQBotUtf8Length(text.slice(0, boundary)) <= limit && isQQBotSafeMarkdownBoundary(text, boundary)) {
|
|
10621
10810
|
lastBoundary = boundary;
|
|
10622
10811
|
}
|
|
10623
10812
|
match = regex.exec(scopedText);
|
|
@@ -10625,13 +10814,14 @@ function findQQBotRegexBoundary(text, limit, pattern) {
|
|
|
10625
10814
|
return lastBoundary;
|
|
10626
10815
|
}
|
|
10627
10816
|
function findQQBotFallbackBoundary(text, limit) {
|
|
10628
|
-
const
|
|
10629
|
-
|
|
10817
|
+
const maxIndex = findQQBotIndexWithinUtf8Limit(text, limit);
|
|
10818
|
+
const minIndex = Math.max(1, maxIndex - 120);
|
|
10819
|
+
for (let index = maxIndex; index >= minIndex; index -= 1) {
|
|
10630
10820
|
if (isQQBotSafeMarkdownBoundary(text, index)) {
|
|
10631
10821
|
return index;
|
|
10632
10822
|
}
|
|
10633
10823
|
}
|
|
10634
|
-
return
|
|
10824
|
+
return maxIndex;
|
|
10635
10825
|
}
|
|
10636
10826
|
function findQQBotSafeSplitIndex(text, limit) {
|
|
10637
10827
|
const boundaryPatterns = [
|
|
@@ -10650,14 +10840,15 @@ function findQQBotSafeSplitIndex(text, limit) {
|
|
|
10650
10840
|
return findQQBotFallbackBoundary(text, limit);
|
|
10651
10841
|
}
|
|
10652
10842
|
function splitQQBotHardText(text, limit) {
|
|
10653
|
-
if (limit <= 0 || text
|
|
10843
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10654
10844
|
return [text];
|
|
10655
10845
|
}
|
|
10656
10846
|
const chunks = [];
|
|
10657
10847
|
let remaining = text;
|
|
10658
|
-
while (remaining
|
|
10659
|
-
|
|
10660
|
-
remaining
|
|
10848
|
+
while (measureQQBotUtf8Length(remaining) > limit) {
|
|
10849
|
+
const nextIndex = Math.max(1, findQQBotIndexWithinUtf8Limit(remaining, limit));
|
|
10850
|
+
chunks.push(remaining.slice(0, nextIndex));
|
|
10851
|
+
remaining = remaining.slice(nextIndex);
|
|
10661
10852
|
}
|
|
10662
10853
|
if (remaining) {
|
|
10663
10854
|
chunks.push(remaining);
|
|
@@ -10665,14 +10856,14 @@ function splitQQBotHardText(text, limit) {
|
|
|
10665
10856
|
return chunks;
|
|
10666
10857
|
}
|
|
10667
10858
|
function splitQQBotTextSafely(text, limit, options) {
|
|
10668
|
-
if (limit <= 0 || text
|
|
10859
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10669
10860
|
return [text];
|
|
10670
10861
|
}
|
|
10671
10862
|
const trimLeading = options?.trimLeading ?? true;
|
|
10672
10863
|
const trimTrailing = options?.trimTrailing ?? true;
|
|
10673
10864
|
const chunks = [];
|
|
10674
10865
|
let remaining = text;
|
|
10675
|
-
while (remaining
|
|
10866
|
+
while (measureQQBotUtf8Length(remaining) > limit) {
|
|
10676
10867
|
const splitIndex = findQQBotSafeSplitIndex(remaining, limit);
|
|
10677
10868
|
let nextChunk = remaining.slice(0, splitIndex);
|
|
10678
10869
|
let nextRemaining = remaining.slice(splitIndex);
|
|
@@ -10683,7 +10874,8 @@ function splitQQBotTextSafely(text, limit, options) {
|
|
|
10683
10874
|
nextRemaining = nextRemaining.trimStart();
|
|
10684
10875
|
}
|
|
10685
10876
|
if (!nextChunk) {
|
|
10686
|
-
const
|
|
10877
|
+
const hardChunkIndex = Math.max(1, findQQBotIndexWithinUtf8Limit(remaining, limit));
|
|
10878
|
+
const hardChunk = remaining.slice(0, hardChunkIndex);
|
|
10687
10879
|
chunks.push(hardChunk);
|
|
10688
10880
|
remaining = remaining.slice(hardChunk.length);
|
|
10689
10881
|
continue;
|
|
@@ -10698,7 +10890,7 @@ function splitQQBotTextSafely(text, limit, options) {
|
|
|
10698
10890
|
return chunks;
|
|
10699
10891
|
}
|
|
10700
10892
|
function splitQQBotMarkdownLineBlock(text, limit) {
|
|
10701
|
-
if (limit <= 0 || text
|
|
10893
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10702
10894
|
return [text];
|
|
10703
10895
|
}
|
|
10704
10896
|
const lines = text.split("\n");
|
|
@@ -10717,12 +10909,12 @@ function splitQQBotMarkdownLineBlock(text, limit) {
|
|
|
10717
10909
|
for (const line of lines) {
|
|
10718
10910
|
const candidate = currentLines.length > 0 ? `${currentLines.join("\n")}
|
|
10719
10911
|
${line}` : line;
|
|
10720
|
-
if (candidate
|
|
10912
|
+
if (measureQQBotUtf8Length(candidate) <= limit) {
|
|
10721
10913
|
currentLines.push(line);
|
|
10722
10914
|
continue;
|
|
10723
10915
|
}
|
|
10724
10916
|
flushCurrent();
|
|
10725
|
-
if (line
|
|
10917
|
+
if (measureQQBotUtf8Length(line) <= limit) {
|
|
10726
10918
|
currentLines.push(line);
|
|
10727
10919
|
continue;
|
|
10728
10920
|
}
|
|
@@ -10738,16 +10930,133 @@ ${line}` : line;
|
|
|
10738
10930
|
flushCurrent();
|
|
10739
10931
|
return chunks;
|
|
10740
10932
|
}
|
|
10933
|
+
function parseQQBotMarkdownTableRowCells(row, columnCount) {
|
|
10934
|
+
const trimmed = row.trim();
|
|
10935
|
+
const inner = trimmed.replace(/^\|\s*/, "").replace(/\s*\|$/, "");
|
|
10936
|
+
const cells = inner.split("|").map((cell) => cell.trim());
|
|
10937
|
+
if (typeof columnCount !== "number" || !Number.isFinite(columnCount) || columnCount <= 0) {
|
|
10938
|
+
return cells;
|
|
10939
|
+
}
|
|
10940
|
+
if (cells.length >= columnCount) {
|
|
10941
|
+
return cells.slice(0, columnCount);
|
|
10942
|
+
}
|
|
10943
|
+
return [...cells, ...Array.from({ length: columnCount - cells.length }, () => "")];
|
|
10944
|
+
}
|
|
10945
|
+
function renderQQBotMarkdownTableRow(cells) {
|
|
10946
|
+
return `| ${cells.join(" | ")} |`;
|
|
10947
|
+
}
|
|
10948
|
+
function splitQQBotMarkdownTableRowByCells(params) {
|
|
10949
|
+
const { row, columnCount, limit } = params;
|
|
10950
|
+
const normalizedCells = parseQQBotMarkdownTableRowCells(row, columnCount);
|
|
10951
|
+
const normalizedRow = renderQQBotMarkdownTableRow(normalizedCells);
|
|
10952
|
+
if (measureQQBotUtf8Length(normalizedRow) <= limit) {
|
|
10953
|
+
return [normalizedRow];
|
|
10954
|
+
}
|
|
10955
|
+
const anchorColumnCount = Math.min(
|
|
10956
|
+
Math.max(1, params.anchorColumnCount ?? 2),
|
|
10957
|
+
columnCount
|
|
10958
|
+
);
|
|
10959
|
+
const anchorCells = normalizedCells.map(
|
|
10960
|
+
(cell, index) => index < anchorColumnCount ? cell : ""
|
|
10961
|
+
);
|
|
10962
|
+
const chunks = [];
|
|
10963
|
+
let currentCells = [...anchorCells];
|
|
10964
|
+
let hasContent = false;
|
|
10965
|
+
const flushCurrent = () => {
|
|
10966
|
+
if (!hasContent) {
|
|
10967
|
+
return;
|
|
10968
|
+
}
|
|
10969
|
+
chunks.push(renderQQBotMarkdownTableRow(currentCells));
|
|
10970
|
+
currentCells = [...anchorCells];
|
|
10971
|
+
hasContent = false;
|
|
10972
|
+
};
|
|
10973
|
+
for (let index = anchorColumnCount; index < columnCount; index += 1) {
|
|
10974
|
+
const cell = normalizedCells[index] ?? "";
|
|
10975
|
+
if (!cell) {
|
|
10976
|
+
continue;
|
|
10977
|
+
}
|
|
10978
|
+
const candidateCells = [...currentCells];
|
|
10979
|
+
candidateCells[index] = cell;
|
|
10980
|
+
if (measureQQBotUtf8Length(renderQQBotMarkdownTableRow(candidateCells)) <= limit) {
|
|
10981
|
+
currentCells[index] = cell;
|
|
10982
|
+
hasContent = true;
|
|
10983
|
+
continue;
|
|
10984
|
+
}
|
|
10985
|
+
if (hasContent) {
|
|
10986
|
+
flushCurrent();
|
|
10987
|
+
const nextCandidateCells = [...currentCells];
|
|
10988
|
+
nextCandidateCells[index] = cell;
|
|
10989
|
+
if (measureQQBotUtf8Length(renderQQBotMarkdownTableRow(nextCandidateCells)) <= limit) {
|
|
10990
|
+
currentCells[index] = cell;
|
|
10991
|
+
hasContent = true;
|
|
10992
|
+
continue;
|
|
10993
|
+
}
|
|
10994
|
+
}
|
|
10995
|
+
const emptyRowBytes = measureQQBotUtf8Length(renderQQBotMarkdownTableRow(currentCells));
|
|
10996
|
+
const availableCellBytes = Math.max(1, limit - emptyRowBytes);
|
|
10997
|
+
for (const cellPiece of splitQQBotTextSafely(cell, availableCellBytes, {
|
|
10998
|
+
trimLeading: false,
|
|
10999
|
+
trimTrailing: false
|
|
11000
|
+
})) {
|
|
11001
|
+
const pieceCells = [...anchorCells];
|
|
11002
|
+
pieceCells[index] = cellPiece;
|
|
11003
|
+
chunks.push(renderQQBotMarkdownTableRow(pieceCells));
|
|
11004
|
+
}
|
|
11005
|
+
currentCells = [...anchorCells];
|
|
11006
|
+
hasContent = false;
|
|
11007
|
+
}
|
|
11008
|
+
flushCurrent();
|
|
11009
|
+
return chunks.length > 0 ? chunks : splitQQBotTextSafely(normalizedRow, limit);
|
|
11010
|
+
}
|
|
11011
|
+
function resolveQQBotMarkdownTableBlockLimit(params) {
|
|
11012
|
+
const { header, separator, rows, limit } = params;
|
|
11013
|
+
if (limit <= 512) {
|
|
11014
|
+
return limit;
|
|
11015
|
+
}
|
|
11016
|
+
const columnCount = parseQQBotMarkdownTableRowCells(header).length;
|
|
11017
|
+
if (columnCount < 8) {
|
|
11018
|
+
return limit;
|
|
11019
|
+
}
|
|
11020
|
+
const tablePrefixBytes = measureQQBotUtf8Length(`${header}
|
|
11021
|
+
${separator}`);
|
|
11022
|
+
const extraColumnCount = Math.max(0, columnCount - 7);
|
|
11023
|
+
const columnPenalty = Math.min(240, extraColumnCount * 55);
|
|
11024
|
+
const headerPenalty = Math.min(120, Math.floor(tablePrefixBytes * 0.25));
|
|
11025
|
+
const reducedLimit = limit - columnPenalty - headerPenalty;
|
|
11026
|
+
const minTableLimit = Math.max(tablePrefixBytes + 64, Math.floor(limit * 0.45));
|
|
11027
|
+
const maxSingleRowRequirement = Math.min(
|
|
11028
|
+
limit,
|
|
11029
|
+
rows.reduce((max, row) => {
|
|
11030
|
+
const normalizedRow = renderQQBotMarkdownTableRow(
|
|
11031
|
+
parseQQBotMarkdownTableRowCells(row, columnCount)
|
|
11032
|
+
);
|
|
11033
|
+
return Math.max(max, measureQQBotUtf8Length(`${header}
|
|
11034
|
+
${separator}
|
|
11035
|
+
${normalizedRow}`));
|
|
11036
|
+
}, tablePrefixBytes)
|
|
11037
|
+
);
|
|
11038
|
+
return Math.min(
|
|
11039
|
+
limit,
|
|
11040
|
+
Math.max(maxSingleRowRequirement, minTableLimit, Math.min(limit, reducedLimit))
|
|
11041
|
+
);
|
|
11042
|
+
}
|
|
10741
11043
|
function splitQQBotMarkdownTableBlock(text, limit) {
|
|
10742
|
-
if (limit <= 0 || text
|
|
11044
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10743
11045
|
return [text];
|
|
10744
11046
|
}
|
|
10745
11047
|
const lines = text.split("\n");
|
|
10746
11048
|
const header = lines[0] ?? "";
|
|
10747
11049
|
const separator = lines[1] ?? "";
|
|
10748
11050
|
const rows = lines.slice(2);
|
|
11051
|
+
const packingLimit = resolveQQBotMarkdownTableBlockLimit({
|
|
11052
|
+
header,
|
|
11053
|
+
separator,
|
|
11054
|
+
rows,
|
|
11055
|
+
limit
|
|
11056
|
+
});
|
|
10749
11057
|
const tablePrefix = `${header}
|
|
10750
11058
|
${separator}`;
|
|
11059
|
+
const columnCount = parseQQBotMarkdownTableRowCells(header).length;
|
|
10751
11060
|
const chunks = [];
|
|
10752
11061
|
let currentRows = [];
|
|
10753
11062
|
const flushCurrent = () => {
|
|
@@ -10763,20 +11072,21 @@ ${currentRows.join("\n")}`);
|
|
|
10763
11072
|
${currentRows.join("\n")}
|
|
10764
11073
|
${row}` : `${tablePrefix}
|
|
10765
11074
|
${row}`;
|
|
10766
|
-
if (candidate
|
|
11075
|
+
if (measureQQBotUtf8Length(candidate) <= packingLimit) {
|
|
10767
11076
|
currentRows.push(row);
|
|
10768
11077
|
continue;
|
|
10769
11078
|
}
|
|
10770
11079
|
flushCurrent();
|
|
10771
|
-
if (`${tablePrefix}
|
|
10772
|
-
${row}
|
|
11080
|
+
if (measureQQBotUtf8Length(`${tablePrefix}
|
|
11081
|
+
${row}`) <= limit) {
|
|
10773
11082
|
currentRows.push(row);
|
|
10774
11083
|
continue;
|
|
10775
11084
|
}
|
|
10776
|
-
const maxRowLength = Math.max(16, limit - tablePrefix
|
|
10777
|
-
for (const rowPiece of
|
|
10778
|
-
|
|
10779
|
-
|
|
11085
|
+
const maxRowLength = Math.max(16, limit - measureQQBotUtf8Length(tablePrefix) - 1);
|
|
11086
|
+
for (const rowPiece of splitQQBotMarkdownTableRowByCells({
|
|
11087
|
+
row,
|
|
11088
|
+
columnCount,
|
|
11089
|
+
limit: maxRowLength
|
|
10780
11090
|
})) {
|
|
10781
11091
|
chunks.push(`${tablePrefix}
|
|
10782
11092
|
${rowPiece}`);
|
|
@@ -10786,7 +11096,7 @@ ${rowPiece}`);
|
|
|
10786
11096
|
return chunks.length > 0 ? chunks : [text];
|
|
10787
11097
|
}
|
|
10788
11098
|
function splitQQBotMarkdownCodeFence(text, limit) {
|
|
10789
|
-
if (limit <= 0 || text
|
|
11099
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10790
11100
|
return [text];
|
|
10791
11101
|
}
|
|
10792
11102
|
const lines = text.split("\n");
|
|
@@ -10795,7 +11105,7 @@ function splitQQBotMarkdownCodeFence(text, limit) {
|
|
|
10795
11105
|
const hasClosingFence = lines.length > 1 && isQQBotFenceClosingLine(lines[lines.length - 1] ?? "", delimiter);
|
|
10796
11106
|
const closingLine = hasClosingFence ? lines[lines.length - 1] ?? delimiter : delimiter;
|
|
10797
11107
|
const codeLines = lines.slice(1, hasClosingFence ? -1 : lines.length);
|
|
10798
|
-
const fixedOverhead = openingLine
|
|
11108
|
+
const fixedOverhead = measureQQBotUtf8Length(openingLine) + measureQQBotUtf8Length(closingLine) + 2;
|
|
10799
11109
|
const availableLineLength = Math.max(1, limit - fixedOverhead);
|
|
10800
11110
|
const chunks = [];
|
|
10801
11111
|
let currentCodeLines = [];
|
|
@@ -10815,14 +11125,14 @@ ${codeLine}
|
|
|
10815
11125
|
${closingLine}` : `${openingLine}
|
|
10816
11126
|
${codeLine}
|
|
10817
11127
|
${closingLine}`;
|
|
10818
|
-
if (candidate
|
|
11128
|
+
if (measureQQBotUtf8Length(candidate) <= limit) {
|
|
10819
11129
|
currentCodeLines.push(codeLine);
|
|
10820
11130
|
continue;
|
|
10821
11131
|
}
|
|
10822
11132
|
flushCurrent();
|
|
10823
|
-
if (`${openingLine}
|
|
11133
|
+
if (measureQQBotUtf8Length(`${openingLine}
|
|
10824
11134
|
${codeLine}
|
|
10825
|
-
${closingLine}
|
|
11135
|
+
${closingLine}`) <= limit) {
|
|
10826
11136
|
currentCodeLines.push(codeLine);
|
|
10827
11137
|
continue;
|
|
10828
11138
|
}
|
|
@@ -10836,7 +11146,7 @@ ${closingLine}`);
|
|
|
10836
11146
|
return chunks.length > 0 ? chunks : [text];
|
|
10837
11147
|
}
|
|
10838
11148
|
function splitQQBotMarkdownBlock(block, limit) {
|
|
10839
|
-
if (limit <= 0 || block.text
|
|
11149
|
+
if (limit <= 0 || measureQQBotUtf8Length(block.text) <= limit) {
|
|
10840
11150
|
return [block.text];
|
|
10841
11151
|
}
|
|
10842
11152
|
switch (block.kind) {
|
|
@@ -10857,11 +11167,93 @@ function splitQQBotMarkdownBlock(block, limit) {
|
|
|
10857
11167
|
return [block.text];
|
|
10858
11168
|
}
|
|
10859
11169
|
}
|
|
10860
|
-
function
|
|
11170
|
+
function resolveQQBotStructuredMarkdownSoftLimit(limit, safeChunkByteLimit) {
|
|
11171
|
+
if (limit <= QQBOT_MARKDOWN_SOFT_LIMIT_THRESHOLD) {
|
|
11172
|
+
return limit;
|
|
11173
|
+
}
|
|
11174
|
+
const configuredSafeLimit = typeof safeChunkByteLimit === "number" && Number.isFinite(safeChunkByteLimit) && safeChunkByteLimit > 0 ? Math.floor(safeChunkByteLimit) : void 0;
|
|
11175
|
+
if (configuredSafeLimit) {
|
|
11176
|
+
return Math.min(limit, configuredSafeLimit);
|
|
11177
|
+
}
|
|
11178
|
+
const reservedLength = Math.min(
|
|
11179
|
+
QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MAX,
|
|
11180
|
+
Math.max(
|
|
11181
|
+
QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MIN,
|
|
11182
|
+
Math.floor(limit * QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_RATIO)
|
|
11183
|
+
)
|
|
11184
|
+
);
|
|
11185
|
+
const softLimit = limit - reservedLength;
|
|
11186
|
+
const boundedSoftLimit = Math.min(
|
|
11187
|
+
softLimit > 0 ? softLimit : limit,
|
|
11188
|
+
DEFAULT_QQBOT_C2C_MARKDOWN_SAFE_CHUNK_BYTE_LIMIT
|
|
11189
|
+
);
|
|
11190
|
+
return boundedSoftLimit > 0 ? boundedSoftLimit : limit;
|
|
11191
|
+
}
|
|
11192
|
+
function maybePrefixQQBotContinuationPiece(params) {
|
|
11193
|
+
const prefix = params.prefix?.trim();
|
|
11194
|
+
if (!prefix) {
|
|
11195
|
+
return params.piece;
|
|
11196
|
+
}
|
|
11197
|
+
const prefixedPiece = joinQQBotMarkdownPieces([prefix, params.piece]);
|
|
11198
|
+
return measureQQBotUtf8Length(prefixedPiece) <= params.limit ? prefixedPiece : params.piece;
|
|
11199
|
+
}
|
|
11200
|
+
function resolveQQBotMarkdownLeadPiece(blocks, index, limit) {
|
|
11201
|
+
const block = blocks[index];
|
|
11202
|
+
if (!block) {
|
|
11203
|
+
return void 0;
|
|
11204
|
+
}
|
|
11205
|
+
if (block.kind === "heading") {
|
|
11206
|
+
const nextBlock = blocks[index + 1];
|
|
11207
|
+
if (nextBlock && nextBlock.kind !== "thematic-break") {
|
|
11208
|
+
const nextPieces = splitQQBotMarkdownBlock(nextBlock, limit);
|
|
11209
|
+
const firstBodyPiece = nextPieces[0];
|
|
11210
|
+
if (firstBodyPiece) {
|
|
11211
|
+
const pairedText = joinQQBotMarkdownPieces([block.text, firstBodyPiece]);
|
|
11212
|
+
if (measureQQBotUtf8Length(pairedText) <= limit) {
|
|
11213
|
+
return pairedText;
|
|
11214
|
+
}
|
|
11215
|
+
}
|
|
11216
|
+
}
|
|
11217
|
+
}
|
|
11218
|
+
return splitQQBotMarkdownBlock(block, limit).map((piece) => piece.trim()).find(Boolean);
|
|
11219
|
+
}
|
|
11220
|
+
function shouldQQBotCarryThematicBreakToNextBlock(params) {
|
|
11221
|
+
const block = params.blocks[params.index];
|
|
11222
|
+
if (!block || block.kind !== "thematic-break") {
|
|
11223
|
+
return false;
|
|
11224
|
+
}
|
|
11225
|
+
if (params.currentPieces.length === 0) {
|
|
11226
|
+
return true;
|
|
11227
|
+
}
|
|
11228
|
+
const withBreak = joinQQBotMarkdownPieces([...params.currentPieces, block.text]);
|
|
11229
|
+
if (measureQQBotUtf8Length(withBreak) > params.limit) {
|
|
11230
|
+
return true;
|
|
11231
|
+
}
|
|
11232
|
+
const nextLeadPiece = resolveQQBotMarkdownLeadPiece(
|
|
11233
|
+
params.blocks,
|
|
11234
|
+
params.index + 1,
|
|
11235
|
+
params.limit
|
|
11236
|
+
);
|
|
11237
|
+
if (!nextLeadPiece) {
|
|
11238
|
+
return false;
|
|
11239
|
+
}
|
|
11240
|
+
const prefixedLeadPiece = joinQQBotMarkdownPieces([block.text, nextLeadPiece]);
|
|
11241
|
+
if (measureQQBotUtf8Length(prefixedLeadPiece) > params.limit) {
|
|
11242
|
+
return false;
|
|
11243
|
+
}
|
|
11244
|
+
const sectionCandidate = joinQQBotMarkdownPieces([
|
|
11245
|
+
...params.currentPieces,
|
|
11246
|
+
block.text,
|
|
11247
|
+
nextLeadPiece
|
|
11248
|
+
]);
|
|
11249
|
+
return measureQQBotUtf8Length(sectionCandidate) > params.limit;
|
|
11250
|
+
}
|
|
11251
|
+
function chunkQQBotStructuredMarkdown(text, limit, safeChunkByteLimit) {
|
|
10861
11252
|
const blocks = parseQQBotMarkdownBlocks(text);
|
|
10862
11253
|
if (blocks.length === 0 || limit <= 0) {
|
|
10863
11254
|
return [text.trim()];
|
|
10864
11255
|
}
|
|
11256
|
+
const chunkLimit = resolveQQBotStructuredMarkdownSoftLimit(limit, safeChunkByteLimit);
|
|
10865
11257
|
const chunks = [];
|
|
10866
11258
|
let currentPieces = [];
|
|
10867
11259
|
let pendingPrefixPieces = [];
|
|
@@ -10879,14 +11271,14 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10879
11271
|
if (!piece) {
|
|
10880
11272
|
return;
|
|
10881
11273
|
}
|
|
10882
|
-
const pieces = piece
|
|
11274
|
+
const pieces = measureQQBotUtf8Length(piece) > chunkLimit ? splitQQBotTextSafely(piece, chunkLimit) : [piece];
|
|
10883
11275
|
for (const nextPiece of pieces) {
|
|
10884
11276
|
const normalizedPiece = nextPiece.trim();
|
|
10885
11277
|
if (!normalizedPiece) {
|
|
10886
11278
|
continue;
|
|
10887
11279
|
}
|
|
10888
11280
|
const candidate = joinQQBotMarkdownPieces([...currentPieces, normalizedPiece]);
|
|
10889
|
-
if (currentPieces.length === 0 || candidate
|
|
11281
|
+
if (currentPieces.length === 0 || measureQQBotUtf8Length(candidate) <= chunkLimit) {
|
|
10890
11282
|
currentPieces.push(normalizedPiece);
|
|
10891
11283
|
continue;
|
|
10892
11284
|
}
|
|
@@ -10908,9 +11300,19 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10908
11300
|
continue;
|
|
10909
11301
|
}
|
|
10910
11302
|
if (block.kind === "thematic-break") {
|
|
11303
|
+
if (shouldQQBotCarryThematicBreakToNextBlock({
|
|
11304
|
+
blocks,
|
|
11305
|
+
index,
|
|
11306
|
+
currentPieces,
|
|
11307
|
+
limit: chunkLimit
|
|
11308
|
+
})) {
|
|
11309
|
+
flushCurrent();
|
|
11310
|
+
pendingPrefixPieces.push(block.text);
|
|
11311
|
+
continue;
|
|
11312
|
+
}
|
|
10911
11313
|
if (currentPieces.length > 0) {
|
|
10912
11314
|
const candidate = joinQQBotMarkdownPieces([...currentPieces, block.text]);
|
|
10913
|
-
if (candidate
|
|
11315
|
+
if (measureQQBotUtf8Length(candidate) <= chunkLimit) {
|
|
10914
11316
|
currentPieces.push(block.text);
|
|
10915
11317
|
continue;
|
|
10916
11318
|
}
|
|
@@ -10923,7 +11325,7 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10923
11325
|
const headingText = consumePendingPrefix(block.text);
|
|
10924
11326
|
const nextBlock = blocks[index + 1];
|
|
10925
11327
|
if (nextBlock && nextBlock.kind !== "thematic-break") {
|
|
10926
|
-
const nextPieces = splitQQBotMarkdownBlock(nextBlock,
|
|
11328
|
+
const nextPieces = splitQQBotMarkdownBlock(nextBlock, chunkLimit);
|
|
10927
11329
|
const firstBodyPiece = nextPieces[0];
|
|
10928
11330
|
if (firstBodyPiece) {
|
|
10929
11331
|
const pairedText = joinQQBotMarkdownPieces([headingText, firstBodyPiece]);
|
|
@@ -10932,19 +11334,33 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10932
11334
|
headingText,
|
|
10933
11335
|
firstBodyPiece
|
|
10934
11336
|
]);
|
|
10935
|
-
if (pairedText
|
|
11337
|
+
if (measureQQBotUtf8Length(pairedText) <= chunkLimit && (currentPieces.length === 0 || measureQQBotUtf8Length(pairedCandidate) <= chunkLimit)) {
|
|
10936
11338
|
currentPieces.push(headingText, firstBodyPiece);
|
|
10937
11339
|
for (let pieceIndex = 1; pieceIndex < nextPieces.length; pieceIndex += 1) {
|
|
10938
|
-
|
|
11340
|
+
const nextPiece = nextPieces[pieceIndex] ?? "";
|
|
11341
|
+
appendPiece(
|
|
11342
|
+
nextBlock.kind === "table" ? maybePrefixQQBotContinuationPiece({
|
|
11343
|
+
prefix: headingText,
|
|
11344
|
+
piece: nextPiece,
|
|
11345
|
+
limit: chunkLimit
|
|
11346
|
+
}) : nextPiece
|
|
11347
|
+
);
|
|
10939
11348
|
}
|
|
10940
11349
|
index += 1;
|
|
10941
11350
|
continue;
|
|
10942
11351
|
}
|
|
10943
|
-
if (currentPieces.length > 0 && pairedText
|
|
11352
|
+
if (currentPieces.length > 0 && measureQQBotUtf8Length(pairedText) <= chunkLimit) {
|
|
10944
11353
|
flushCurrent();
|
|
10945
11354
|
currentPieces.push(headingText, firstBodyPiece);
|
|
10946
11355
|
for (let pieceIndex = 1; pieceIndex < nextPieces.length; pieceIndex += 1) {
|
|
10947
|
-
|
|
11356
|
+
const nextPiece = nextPieces[pieceIndex] ?? "";
|
|
11357
|
+
appendPiece(
|
|
11358
|
+
nextBlock.kind === "table" ? maybePrefixQQBotContinuationPiece({
|
|
11359
|
+
prefix: headingText,
|
|
11360
|
+
piece: nextPiece,
|
|
11361
|
+
limit: chunkLimit
|
|
11362
|
+
}) : nextPiece
|
|
11363
|
+
);
|
|
10948
11364
|
}
|
|
10949
11365
|
index += 1;
|
|
10950
11366
|
continue;
|
|
@@ -10955,16 +11371,29 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10955
11371
|
continue;
|
|
10956
11372
|
}
|
|
10957
11373
|
const blockText = consumePendingPrefix(block.text);
|
|
10958
|
-
for (const piece of splitQQBotMarkdownBlock({ ...block, text: blockText },
|
|
11374
|
+
for (const piece of splitQQBotMarkdownBlock({ ...block, text: blockText }, chunkLimit)) {
|
|
10959
11375
|
appendPiece(piece);
|
|
10960
11376
|
}
|
|
10961
11377
|
}
|
|
10962
11378
|
if (pendingPrefixPieces.length > 0 && currentPieces.length > 0) {
|
|
10963
11379
|
const trailingCandidate = joinQQBotMarkdownPieces([...currentPieces, ...pendingPrefixPieces]);
|
|
10964
|
-
if (trailingCandidate
|
|
11380
|
+
if (measureQQBotUtf8Length(trailingCandidate) <= chunkLimit) {
|
|
10965
11381
|
currentPieces.push(...pendingPrefixPieces);
|
|
11382
|
+
pendingPrefixPieces = [];
|
|
11383
|
+
}
|
|
11384
|
+
}
|
|
11385
|
+
if (pendingPrefixPieces.length > 0 && chunks.length > 0) {
|
|
11386
|
+
const trailingPrefix = joinQQBotMarkdownPieces(pendingPrefixPieces);
|
|
11387
|
+
const lastChunk = chunks[chunks.length - 1] ?? "";
|
|
11388
|
+
const trailingCandidate = joinQQBotMarkdownPieces([lastChunk, trailingPrefix]);
|
|
11389
|
+
if (measureQQBotUtf8Length(trailingCandidate) <= chunkLimit) {
|
|
11390
|
+
chunks[chunks.length - 1] = trailingCandidate;
|
|
11391
|
+
pendingPrefixPieces = [];
|
|
10966
11392
|
}
|
|
10967
11393
|
}
|
|
11394
|
+
if (pendingPrefixPieces.length > 0) {
|
|
11395
|
+
currentPieces.push(joinQQBotMarkdownPieces(pendingPrefixPieces));
|
|
11396
|
+
}
|
|
10968
11397
|
flushCurrent();
|
|
10969
11398
|
return chunks.length > 0 ? chunks : [text.trim()];
|
|
10970
11399
|
}
|
|
@@ -10991,7 +11420,7 @@ function chunkC2CMarkdownText(params) {
|
|
|
10991
11420
|
if (params.limit <= 0 || !looksLikeStructuredMarkdown(normalized)) {
|
|
10992
11421
|
return params.fallbackChunkText ? params.fallbackChunkText(normalized) : [normalized];
|
|
10993
11422
|
}
|
|
10994
|
-
return chunkQQBotStructuredMarkdown(normalized, params.limit);
|
|
11423
|
+
return chunkQQBotStructuredMarkdown(normalized, params.limit, params.safeChunkByteLimit);
|
|
10995
11424
|
}
|
|
10996
11425
|
async function sendQQBotMediaWithFallback(params) {
|
|
10997
11426
|
const { qqCfg, to, mediaQueue, replyToId, replyEventId, accountId, logger, onDelivered, onError } = params;
|
|
@@ -11367,6 +11796,7 @@ async function dispatchToAgent(params) {
|
|
|
11367
11796
|
const markdownSupport = qqCfg.markdownSupport ?? true;
|
|
11368
11797
|
const c2cMarkdownDeliveryMode = qqCfg.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
11369
11798
|
const c2cMarkdownChunkStrategy = qqCfg.c2cMarkdownChunkStrategy ?? "markdown-block";
|
|
11799
|
+
const c2cMarkdownSafeChunkByteLimit = resolveQQBotC2CMarkdownSafeChunkByteLimit(qqCfg);
|
|
11370
11800
|
const isC2CTarget = isQQBotC2CTarget(target.to);
|
|
11371
11801
|
const useC2CMarkdownTransport = markdownSupport && isC2CTarget;
|
|
11372
11802
|
let bufferedC2CMarkdownTexts = [];
|
|
@@ -11404,11 +11834,12 @@ async function dispatchToAgent(params) {
|
|
|
11404
11834
|
text: finalMarkdownText,
|
|
11405
11835
|
limit,
|
|
11406
11836
|
strategy: c2cMarkdownChunkStrategy,
|
|
11837
|
+
safeChunkByteLimit: c2cMarkdownSafeChunkByteLimit,
|
|
11407
11838
|
fallbackChunkText: chunkText
|
|
11408
11839
|
}) : [];
|
|
11409
11840
|
const deliveryLabel = textReplyRefs.forceProactive ? "c2c-markdown-proactive" : "c2c-markdown-passive";
|
|
11410
11841
|
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}`
|
|
11842
|
+
`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
11843
|
);
|
|
11413
11844
|
if (!shouldSuppressVisibleReplies()) {
|
|
11414
11845
|
if (mediaQueue.length > 0) {
|
|
@@ -11473,7 +11904,7 @@ async function dispatchToAgent(params) {
|
|
|
11473
11904
|
bufferedC2CMarkdownMediaSeen.clear();
|
|
11474
11905
|
return;
|
|
11475
11906
|
}
|
|
11476
|
-
const combinedText = bufferedC2CMarkdownTexts
|
|
11907
|
+
const combinedText = combineQQBotBufferedText(bufferedC2CMarkdownTexts);
|
|
11477
11908
|
const combinedMediaUrls = [...bufferedC2CMarkdownMediaUrls];
|
|
11478
11909
|
bufferedC2CMarkdownTexts = [];
|
|
11479
11910
|
bufferedC2CMarkdownMediaUrls = [];
|
|
@@ -11519,7 +11950,7 @@ async function dispatchToAgent(params) {
|
|
|
11519
11950
|
const textToSend = suppressText ? "" : cleanedText;
|
|
11520
11951
|
if (useC2CMarkdownTransport) {
|
|
11521
11952
|
const shouldBufferFinalOnlyPayload = replyFinalOnly && (!info?.kind || info.kind === "final");
|
|
11522
|
-
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && looksLikeStructuredMarkdown(textToSend);
|
|
11953
|
+
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && (hasBufferedC2CMarkdownReply() || looksLikeStructuredMarkdown(textToSend));
|
|
11523
11954
|
if (shouldBufferFinalOnlyPayload || shouldBufferStructuredMarkdownPayload) {
|
|
11524
11955
|
if (textToSend) {
|
|
11525
11956
|
bufferedC2CMarkdownTexts = appendQQBotBufferedText(bufferedC2CMarkdownTexts, textToSend);
|
|
@@ -12304,6 +12735,7 @@ var qqbotPlugin = {
|
|
|
12304
12735
|
type: "string",
|
|
12305
12736
|
enum: ["markdown-block", "length"]
|
|
12306
12737
|
},
|
|
12738
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12307
12739
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12308
12740
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12309
12741
|
requireMention: { type: "boolean" },
|
|
@@ -12357,6 +12789,7 @@ var qqbotPlugin = {
|
|
|
12357
12789
|
type: "string",
|
|
12358
12790
|
enum: ["markdown-block", "length"]
|
|
12359
12791
|
},
|
|
12792
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12360
12793
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12361
12794
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12362
12795
|
requireMention: { type: "boolean" },
|
|
@@ -12569,6 +13002,7 @@ var plugin = {
|
|
|
12569
13002
|
type: "string",
|
|
12570
13003
|
enum: ["markdown-block", "length"]
|
|
12571
13004
|
},
|
|
13005
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12572
13006
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12573
13007
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12574
13008
|
requireMention: { type: "boolean" },
|
|
@@ -12622,6 +13056,7 @@ var plugin = {
|
|
|
12622
13056
|
type: "string",
|
|
12623
13057
|
enum: ["markdown-block", "length"]
|
|
12624
13058
|
},
|
|
13059
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12625
13060
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12626
13061
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12627
13062
|
requireMention: { type: "boolean" },
|