koishi-plugin-chatluna 1.4.0-alpha.21 → 1.4.0-alpha.23
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/lib/index.cjs +115 -159
- package/lib/index.mjs +115 -159
- package/lib/llm-core/agent/index.cjs +3 -3
- package/lib/llm-core/agent/index.mjs +3 -3
- package/lib/llm-core/platform/client.cjs +2 -2
- package/lib/llm-core/platform/client.mjs +2 -2
- package/lib/llm-core/platform/model.cjs +9 -9
- package/lib/llm-core/platform/model.mjs +9 -9
- package/lib/services/chat.cjs +1 -1
- package/lib/services/chat.mjs +1 -1
- package/package.json +1 -1
package/lib/index.cjs
CHANGED
|
@@ -3364,6 +3364,9 @@ var SUPPORTED_AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
3364
3364
|
"audio/aiff"
|
|
3365
3365
|
]);
|
|
3366
3366
|
function apply18(ctx, config, chain) {
|
|
3367
|
+
const forwardHistory = /* @__PURE__ */ new WeakMap();
|
|
3368
|
+
const fileSizes = /* @__PURE__ */ new WeakMap();
|
|
3369
|
+
const handledAudio = /* @__PURE__ */ new WeakSet();
|
|
3367
3370
|
chain.middleware("read_chat_message", async (session, context) => {
|
|
3368
3371
|
let message = context.command != null ? context.message : session.elements;
|
|
3369
3372
|
if (typeof message === "string") {
|
|
@@ -3442,15 +3445,14 @@ function apply18(ctx, config, chain) {
|
|
|
3442
3445
|
}
|
|
3443
3446
|
);
|
|
3444
3447
|
if (config.attachForwardMsgIdToContext) {
|
|
3445
|
-
const
|
|
3446
|
-
const state = kwargs?.[forwardHistoryInternalKey];
|
|
3448
|
+
const state = forwardHistory.get(transformedMessage);
|
|
3447
3449
|
if (state?.hasForwardHistory) {
|
|
3448
3450
|
if (state.ids.length > 0) {
|
|
3451
|
+
transformedMessage.additional_kwargs ??= {};
|
|
3449
3452
|
transformedMessage.additional_kwargs.forwardMessageIds = state.ids;
|
|
3450
3453
|
}
|
|
3451
3454
|
addMessageContent(transformedMessage, "[聊天记录]");
|
|
3452
3455
|
}
|
|
3453
|
-
delete kwargs?.[forwardHistoryInternalKey];
|
|
3454
3456
|
}
|
|
3455
3457
|
if (transformedMessage.content.length < 1 && (0, import_string4.getMessageContent)(transformedMessage.content).trim().length < 1) {
|
|
3456
3458
|
return 1 /* STOP */;
|
|
@@ -3484,7 +3486,7 @@ function apply18(ctx, config, chain) {
|
|
|
3484
3486
|
"forward",
|
|
3485
3487
|
async (session, element, message) => {
|
|
3486
3488
|
if (!config.attachForwardMsgIdToContext) return;
|
|
3487
|
-
trackForwardId(element, message);
|
|
3489
|
+
trackForwardId(forwardHistory, element, message);
|
|
3488
3490
|
}
|
|
3489
3491
|
);
|
|
3490
3492
|
ctx.chatluna.messageTransformer.intercept(
|
|
@@ -3492,7 +3494,7 @@ function apply18(ctx, config, chain) {
|
|
|
3492
3494
|
async (session, element, message) => {
|
|
3493
3495
|
if (!config.attachForwardMsgIdToContext) return;
|
|
3494
3496
|
if (!(0, import_koishi6.isForwardMessageElement)(element)) return;
|
|
3495
|
-
trackForwardId(element, message);
|
|
3497
|
+
trackForwardId(forwardHistory, element, message);
|
|
3496
3498
|
}
|
|
3497
3499
|
);
|
|
3498
3500
|
ctx.chatluna.messageTransformer.intercept(
|
|
@@ -3508,59 +3510,62 @@ function apply18(ctx, config, chain) {
|
|
|
3508
3510
|
ctx.chatluna.messageTransformer.intercept(
|
|
3509
3511
|
"img",
|
|
3510
3512
|
async (session, element, message, model) => {
|
|
3511
|
-
const
|
|
3513
|
+
const supportsImage = modelSupportsElement(ctx, model, "img");
|
|
3512
3514
|
const isInstalledImageService = ctx.chatluna.getPlugin("multimodal-service") != null;
|
|
3513
|
-
if (
|
|
3514
|
-
import_types2.ModelCapabilities.ImageInput
|
|
3515
|
-
)) {
|
|
3515
|
+
if (!supportsImage) {
|
|
3516
3516
|
if (!isInstalledImageService) {
|
|
3517
3517
|
logger6.warn(
|
|
3518
3518
|
`Model "${model}" does not support image input. Please use a model that supports vision capabilities, or install chatluna-multimodal-service (multimodal-service) plugin to enable image description.`
|
|
3519
3519
|
);
|
|
3520
3520
|
}
|
|
3521
|
-
return false;
|
|
3522
3521
|
}
|
|
3523
3522
|
const url = element.attrs.url ?? element.attrs.src;
|
|
3523
|
+
const hash = await (0, import_string4.hashString)(url, 8);
|
|
3524
|
+
element.attrs["imageHash"] = hash;
|
|
3524
3525
|
const displayUrl = url.length > 100 ? url.substring(0, 100) + "..." : url;
|
|
3525
3526
|
logger6.debug(`Processing image: ${displayUrl}`);
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
message,
|
|
3531
|
-
element,
|
|
3532
|
-
isInstalledImageService
|
|
3533
|
-
);
|
|
3534
|
-
}
|
|
3535
|
-
const { buffer, ext } = await readImage(ctx, url);
|
|
3536
|
-
if (ext == null) {
|
|
3527
|
+
const image = await readImage(ctx, url);
|
|
3528
|
+
const buffer = image.buffer;
|
|
3529
|
+
const ext = image.ext;
|
|
3530
|
+
if (ext == null || buffer == null) {
|
|
3537
3531
|
return false;
|
|
3538
3532
|
}
|
|
3539
3533
|
if (ext === "image/gif") {
|
|
3540
3534
|
if (!isInstalledImageService) {
|
|
3541
3535
|
logger6.warn(
|
|
3542
|
-
|
|
3536
|
+
"Detected GIF image, which is not supported by most models. Please install chatluna-multimodal-service (multimodal-service) plugin to parse GIF animations."
|
|
3543
3537
|
);
|
|
3544
3538
|
}
|
|
3539
|
+
if (ctx.chatluna_storage)
|
|
3540
|
+
setElementUrl(
|
|
3541
|
+
element,
|
|
3542
|
+
(await ctx.chatluna_storage.createTempFile(
|
|
3543
|
+
buffer,
|
|
3544
|
+
`${hash}.gif`
|
|
3545
|
+
)).url
|
|
3546
|
+
);
|
|
3545
3547
|
return false;
|
|
3546
3548
|
}
|
|
3547
|
-
const fileExt = ext.includes("/") ? ext.split("/")[1] : ext;
|
|
3548
|
-
element.attrs["ext"] = fileExt;
|
|
3549
3549
|
let fileName = element.attrs["filename"];
|
|
3550
3550
|
if (fileName == null || fileName.length > 50) {
|
|
3551
|
-
fileName = `${
|
|
3551
|
+
fileName = `${hash}.${ext.includes("/") ? ext.split("/")[1] : ext}`;
|
|
3552
3552
|
}
|
|
3553
|
+
element.attrs["ext"] = ext.includes("/") ? ext.split("/")[1] : ext;
|
|
3553
3554
|
logger6.debug(`Saving image as temp file: ${fileName}`);
|
|
3554
|
-
const tempFile = await ctx.chatluna_storage.createTempFile(
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
);
|
|
3558
|
-
|
|
3555
|
+
const tempFile = ctx.chatluna_storage ? await ctx.chatluna_storage.createTempFile(buffer, fileName) : null;
|
|
3556
|
+
const imageUrl = tempFile?.url ?? image.base64Source;
|
|
3557
|
+
const imageText = tempFile?.url ?? hash;
|
|
3558
|
+
if (tempFile) setElementUrl(element, tempFile.url);
|
|
3559
|
+
if (!supportsImage) {
|
|
3560
|
+
addTextPart(message, `[image:${imageText}]`);
|
|
3561
|
+
return false;
|
|
3562
|
+
}
|
|
3563
|
+
addTextPart(message, `[image:${imageText}]`);
|
|
3559
3564
|
message.content.push({
|
|
3560
3565
|
type: "image_url",
|
|
3561
|
-
image_url: { url:
|
|
3566
|
+
image_url: { url: imageUrl }
|
|
3562
3567
|
});
|
|
3563
|
-
|
|
3568
|
+
return false;
|
|
3564
3569
|
},
|
|
3565
3570
|
-100
|
|
3566
3571
|
);
|
|
@@ -3571,7 +3576,7 @@ function apply18(ctx, config, chain) {
|
|
|
3571
3576
|
"audio",
|
|
3572
3577
|
async (session, element, message, model) => {
|
|
3573
3578
|
const modelInfo = model != null ? ctx2.chatluna.platform.findModel(model) : void 0;
|
|
3574
|
-
if (
|
|
3579
|
+
if (handledAudio.has(element)) {
|
|
3575
3580
|
logger6.debug(
|
|
3576
3581
|
"Skip sst audio2text because audio is already handled."
|
|
3577
3582
|
);
|
|
@@ -3588,7 +3593,8 @@ function apply18(ctx, config, chain) {
|
|
|
3588
3593
|
const content = await ctx2.sst.audio2text(session);
|
|
3589
3594
|
logger6.debug(`audio2text: ${content}`);
|
|
3590
3595
|
addMessageContent(message, content);
|
|
3591
|
-
|
|
3596
|
+
handledAudio.add(element);
|
|
3597
|
+
return false;
|
|
3592
3598
|
}
|
|
3593
3599
|
),
|
|
3594
3600
|
-100
|
|
@@ -3597,18 +3603,9 @@ function apply18(ctx, config, chain) {
|
|
|
3597
3603
|
ctx.chatluna.messageTransformer.intercept(
|
|
3598
3604
|
"file",
|
|
3599
3605
|
async (session, element, message, model) => {
|
|
3600
|
-
const modelInfo = model != null ? ctx.chatluna.platform.findModel(model) : void 0;
|
|
3601
|
-
if (modelInfo?.value != null && !modelInfo.value.capabilities.includes(
|
|
3602
|
-
import_types2.ModelCapabilities.FileInput
|
|
3603
|
-
)) {
|
|
3604
|
-
addMessageContent(
|
|
3605
|
-
message,
|
|
3606
|
-
`[file: ${element.attrs["file"] ?? element.attrs["filename"] ?? "attachment"} (skipped: model does not support file input)]`
|
|
3607
|
-
);
|
|
3608
|
-
return;
|
|
3609
|
-
}
|
|
3610
3606
|
await handleFileElement(
|
|
3611
3607
|
ctx,
|
|
3608
|
+
fileSizes,
|
|
3612
3609
|
session,
|
|
3613
3610
|
element,
|
|
3614
3611
|
message,
|
|
@@ -3620,18 +3617,9 @@ function apply18(ctx, config, chain) {
|
|
|
3620
3617
|
ctx.chatluna.messageTransformer.intercept(
|
|
3621
3618
|
"video",
|
|
3622
3619
|
async (session, element, message, model) => {
|
|
3623
|
-
const modelInfo = model != null ? ctx.chatluna.platform.findModel(model) : void 0;
|
|
3624
|
-
if (modelInfo?.value != null && !modelInfo.value.capabilities.includes(
|
|
3625
|
-
import_types2.ModelCapabilities.VideoInput
|
|
3626
|
-
)) {
|
|
3627
|
-
addMessageContent(
|
|
3628
|
-
message,
|
|
3629
|
-
`[video: ${element.attrs["file"] ?? element.attrs["filename"] ?? "attachment"} (skipped: model does not support video input)]`
|
|
3630
|
-
);
|
|
3631
|
-
return;
|
|
3632
|
-
}
|
|
3633
3620
|
await handleFileElement(
|
|
3634
3621
|
ctx,
|
|
3622
|
+
fileSizes,
|
|
3635
3623
|
session,
|
|
3636
3624
|
element,
|
|
3637
3625
|
message,
|
|
@@ -3643,20 +3631,9 @@ function apply18(ctx, config, chain) {
|
|
|
3643
3631
|
ctx.chatluna.messageTransformer.intercept(
|
|
3644
3632
|
"audio",
|
|
3645
3633
|
async (session, element, message, model) => {
|
|
3646
|
-
if (isAudioHandled(message, element)) {
|
|
3647
|
-
logger6.debug(
|
|
3648
|
-
"Skip audio file handler because audio is already handled."
|
|
3649
|
-
);
|
|
3650
|
-
return false;
|
|
3651
|
-
}
|
|
3652
|
-
const modelInfo = model != null ? ctx.chatluna.platform.findModel(model) : void 0;
|
|
3653
|
-
if (modelInfo?.value != null && !modelInfo.value.capabilities.includes(
|
|
3654
|
-
import_types2.ModelCapabilities.AudioInput
|
|
3655
|
-
)) {
|
|
3656
|
-
return false;
|
|
3657
|
-
}
|
|
3658
3634
|
const handled = await handleFileElement(
|
|
3659
3635
|
ctx,
|
|
3636
|
+
fileSizes,
|
|
3660
3637
|
session,
|
|
3661
3638
|
element,
|
|
3662
3639
|
message,
|
|
@@ -3664,7 +3641,7 @@ function apply18(ctx, config, chain) {
|
|
|
3664
3641
|
"audio"
|
|
3665
3642
|
);
|
|
3666
3643
|
if (handled) {
|
|
3667
|
-
|
|
3644
|
+
handledAudio.add(element);
|
|
3668
3645
|
}
|
|
3669
3646
|
return false;
|
|
3670
3647
|
}
|
|
@@ -3721,15 +3698,8 @@ async function resolveSourceUrl(ctx, session, element) {
|
|
|
3721
3698
|
return null;
|
|
3722
3699
|
}
|
|
3723
3700
|
__name(resolveSourceUrl, "resolveSourceUrl");
|
|
3724
|
-
async function handleFileElement(ctx, session, element, message, model, elementType) {
|
|
3725
|
-
const
|
|
3726
|
-
if (elementType === "audio" && isAudioHandled(message, element)) {
|
|
3727
|
-
logger6.debug(
|
|
3728
|
-
"Skip handling audio file because audio is already handled."
|
|
3729
|
-
);
|
|
3730
|
-
return false;
|
|
3731
|
-
}
|
|
3732
|
-
const fileName = element.attrs["file"] ?? element.attrs["name"] ?? element.attrs["filename"];
|
|
3701
|
+
async function handleFileElement(ctx, fileSizes, session, element, message, model, elementType) {
|
|
3702
|
+
const name2 = element.attrs["file"] ?? element.attrs["name"] ?? element.attrs["filename"];
|
|
3733
3703
|
const sourceUrl = await resolveSourceUrl(ctx, session, element);
|
|
3734
3704
|
if (!sourceUrl) return false;
|
|
3735
3705
|
const fileConfig = await getFileConfig(ctx, model);
|
|
@@ -3750,7 +3720,31 @@ async function handleFileElement(ctx, session, element, message, model, elementT
|
|
|
3750
3720
|
logger6.error(`Failed to read file from ${sourceUrl}:`, error);
|
|
3751
3721
|
return false;
|
|
3752
3722
|
}
|
|
3753
|
-
const mimeType = responseMimeType ?? (0, import_string4.getMimeTypeFromSource)(sourceUrl,
|
|
3723
|
+
const mimeType = responseMimeType ?? (0, import_string4.getMimeTypeFromSource)(sourceUrl, name2);
|
|
3724
|
+
const fileName = name2 ?? "attachment";
|
|
3725
|
+
let label;
|
|
3726
|
+
switch (elementType) {
|
|
3727
|
+
case "audio":
|
|
3728
|
+
label = "voice";
|
|
3729
|
+
break;
|
|
3730
|
+
case "video":
|
|
3731
|
+
label = "video";
|
|
3732
|
+
break;
|
|
3733
|
+
default:
|
|
3734
|
+
label = "file";
|
|
3735
|
+
}
|
|
3736
|
+
const file = ctx.chatluna_storage ? await ctx.chatluna_storage.createTempFile(buffer, fileName) : null;
|
|
3737
|
+
const fileUrl = file ? file.url : `data:${mimeType ?? "application/octet-stream"};base64,${buffer.toString("base64")}`;
|
|
3738
|
+
element.attrs["file"] = file?.name ?? fileName;
|
|
3739
|
+
element.attrs["filename"] = file?.name ?? fileName;
|
|
3740
|
+
element.attrs["chatluna_file_url"] = file?.url ?? sourceUrl;
|
|
3741
|
+
addTextPart(message, `[${label}:${file?.name ?? fileName}]`);
|
|
3742
|
+
if (!modelSupportsElement(ctx, model, elementType)) {
|
|
3743
|
+
logger6.warn(
|
|
3744
|
+
`Model "${model}" does not support ${label} input. The file was saved and fallback text was kept.`
|
|
3745
|
+
);
|
|
3746
|
+
return false;
|
|
3747
|
+
}
|
|
3754
3748
|
if (elementType === "audio" && mimeType != null) {
|
|
3755
3749
|
if (!SUPPORTED_AUDIO_MIME_TYPES.has(mimeType)) {
|
|
3756
3750
|
const isInstalledMultimodalService = ctx.chatluna.getPlugin("multimodal-service") != null;
|
|
@@ -3766,7 +3760,7 @@ async function handleFileElement(ctx, session, element, message, model, elementT
|
|
|
3766
3760
|
if (mimeType != null && !fileConfig.supportedMimeTypes.has(mimeType)) {
|
|
3767
3761
|
addMessageContent(
|
|
3768
3762
|
message,
|
|
3769
|
-
`[${
|
|
3763
|
+
`[${label}: ${file?.name ?? fileName} (skipped: unsupported MIME type "${mimeType}")]`
|
|
3770
3764
|
);
|
|
3771
3765
|
return false;
|
|
3772
3766
|
}
|
|
@@ -3778,76 +3772,25 @@ async function handleFileElement(ctx, session, element, message, model, elementT
|
|
|
3778
3772
|
if (encodedSize > maxSize) {
|
|
3779
3773
|
addMessageContent(
|
|
3780
3774
|
message,
|
|
3781
|
-
`[${
|
|
3775
|
+
`[${label}: ${file?.name ?? fileName} (skipped: file size ${encodedSize} bytes exceeds limit ${maxSize} bytes)]`
|
|
3782
3776
|
);
|
|
3783
3777
|
return false;
|
|
3784
3778
|
}
|
|
3785
|
-
const
|
|
3786
|
-
|
|
3787
|
-
if (newTotal > fileConfig.maxTotalSizeBytes) {
|
|
3779
|
+
const size = (fileSizes.get(message) ?? 0) + encodedSize;
|
|
3780
|
+
if (size > fileConfig.maxTotalSizeBytes) {
|
|
3788
3781
|
addMessageContent(
|
|
3789
3782
|
message,
|
|
3790
|
-
`[${
|
|
3783
|
+
`[${label}: ${file?.name ?? fileName} (skipped: total inline size would exceed limit)]`
|
|
3791
3784
|
);
|
|
3792
3785
|
return false;
|
|
3793
3786
|
}
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
element.attrs["file"] = resolvedFileName;
|
|
3797
|
-
element.attrs["filename"] = resolvedFileName;
|
|
3798
|
-
element.attrs["chatluna_file_url"] = sourceUrl;
|
|
3799
|
-
const label = elementType === "audio" ? "voice" : elementType === "video" ? "video" : "file";
|
|
3800
|
-
let fileUrl;
|
|
3801
|
-
if (ctx.chatluna_storage) {
|
|
3802
|
-
const file = await ctx.chatluna_storage.createTempFile(
|
|
3803
|
-
buffer,
|
|
3804
|
-
resolvedFileName
|
|
3805
|
-
);
|
|
3806
|
-
const displayFileName = fileName ?? file.name;
|
|
3807
|
-
element.attrs["file"] = displayFileName;
|
|
3808
|
-
element.attrs["filename"] = displayFileName;
|
|
3809
|
-
element.attrs["chatluna_file_url"] = file.url;
|
|
3810
|
-
fileUrl = file.url;
|
|
3811
|
-
ensureContentArray(message, `[${label}:${displayFileName}]`);
|
|
3812
|
-
} else {
|
|
3813
|
-
const base64 = buffer.toString("base64");
|
|
3814
|
-
fileUrl = `data:${mimeType ?? "application/octet-stream"};base64,${base64}`;
|
|
3815
|
-
ensureContentArray(message, `[${label}:${resolvedFileName}]`);
|
|
3787
|
+
fileSizes.set(message, size);
|
|
3788
|
+
addFileSize(message, size);
|
|
3816
3789
|
}
|
|
3817
3790
|
pushTypedContent(message, elementType, fileUrl, mimeType);
|
|
3818
3791
|
return true;
|
|
3819
3792
|
}
|
|
3820
3793
|
__name(handleFileElement, "handleFileElement");
|
|
3821
|
-
async function oldImageRead(ctx, url, message, element, isInstalledImageService) {
|
|
3822
|
-
const imageHash = await (0, import_string4.hashString)(url, 8);
|
|
3823
|
-
element.attrs["imageHash"] = imageHash;
|
|
3824
|
-
try {
|
|
3825
|
-
const { base64Source, ext } = await readImage(ctx, url);
|
|
3826
|
-
if (ext == null) {
|
|
3827
|
-
return false;
|
|
3828
|
-
}
|
|
3829
|
-
if (ext === "image/gif") {
|
|
3830
|
-
if (!isInstalledImageService) {
|
|
3831
|
-
logger6.warn(
|
|
3832
|
-
`Detected GIF image, which is not supported by most models. Please install chatluna-multimodal-service (multimodal-service) plugin to parse GIF animations.`
|
|
3833
|
-
);
|
|
3834
|
-
}
|
|
3835
|
-
return false;
|
|
3836
|
-
}
|
|
3837
|
-
ensureContentArray(message, `[image:${imageHash}]`);
|
|
3838
|
-
message.content.push({
|
|
3839
|
-
type: "image_url",
|
|
3840
|
-
image_url: { url: base64Source, hash: imageHash }
|
|
3841
|
-
});
|
|
3842
|
-
} catch (error) {
|
|
3843
|
-
const displayUrl = url.length > 100 ? url.substring(0, 100) + "..." : url;
|
|
3844
|
-
logger6.warn(
|
|
3845
|
-
`Failed to read image from ${displayUrl}. Please check your Koishi chat adapter.`,
|
|
3846
|
-
error
|
|
3847
|
-
);
|
|
3848
|
-
}
|
|
3849
|
-
}
|
|
3850
|
-
__name(oldImageRead, "oldImageRead");
|
|
3851
3794
|
async function readImage(ctx, url) {
|
|
3852
3795
|
if (url.startsWith("data:image") && url.includes("base64")) {
|
|
3853
3796
|
const buffer = Buffer.from(url.split(",")[1], "base64");
|
|
@@ -3904,12 +3847,6 @@ function pushTypedContent(message, elementType, url, mimeType) {
|
|
|
3904
3847
|
}
|
|
3905
3848
|
}
|
|
3906
3849
|
__name(pushTypedContent, "pushTypedContent");
|
|
3907
|
-
function getFileTotalSize(message) {
|
|
3908
|
-
const kwargs = message.additional_kwargs ?? {};
|
|
3909
|
-
const value = kwargs["__file_total_size"];
|
|
3910
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3911
|
-
}
|
|
3912
|
-
__name(getFileTotalSize, "getFileTotalSize");
|
|
3913
3850
|
function toContentParts(content) {
|
|
3914
3851
|
if (content == null) {
|
|
3915
3852
|
return [];
|
|
@@ -3920,23 +3857,16 @@ function toContentParts(content) {
|
|
|
3920
3857
|
return Array.isArray(content) ? content : [content];
|
|
3921
3858
|
}
|
|
3922
3859
|
__name(toContentParts, "toContentParts");
|
|
3923
|
-
function
|
|
3860
|
+
function addTextPart(message, text) {
|
|
3924
3861
|
const parts = toContentParts(message.content);
|
|
3925
|
-
|
|
3926
|
-
message.content = parts;
|
|
3927
|
-
return;
|
|
3928
|
-
}
|
|
3929
|
-
message.content = [{ type: "text", text: fallbackText }];
|
|
3862
|
+
message.content = [...parts, { type: "text", text }];
|
|
3930
3863
|
}
|
|
3931
|
-
__name(
|
|
3932
|
-
function
|
|
3933
|
-
|
|
3864
|
+
__name(addTextPart, "addTextPart");
|
|
3865
|
+
function addFileSize(message, size) {
|
|
3866
|
+
message.additional_kwargs ??= {};
|
|
3867
|
+
message.additional_kwargs["__file_total_size"] = size;
|
|
3934
3868
|
}
|
|
3935
|
-
__name(
|
|
3936
|
-
function markAudioHandled(_message, element) {
|
|
3937
|
-
element.attrs["_audioHandled"] = true;
|
|
3938
|
-
}
|
|
3939
|
-
__name(markAudioHandled, "markAudioHandled");
|
|
3869
|
+
__name(addFileSize, "addFileSize");
|
|
3940
3870
|
function addMessageContent(message, content) {
|
|
3941
3871
|
if (typeof message.content === "string" && typeof content === "string") {
|
|
3942
3872
|
message.content += content;
|
|
@@ -3950,13 +3880,39 @@ function addMessageContent(message, content) {
|
|
|
3950
3880
|
message.content = [...currentParts, ...incomingParts];
|
|
3951
3881
|
}
|
|
3952
3882
|
__name(addMessageContent, "addMessageContent");
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3883
|
+
function modelSupportsElement(ctx, model, type) {
|
|
3884
|
+
const info = model != null ? ctx.chatluna.platform.findModel(model) : null;
|
|
3885
|
+
if (info?.value == null) return true;
|
|
3886
|
+
switch (type) {
|
|
3887
|
+
case "img":
|
|
3888
|
+
return info.value.capabilities.includes(
|
|
3889
|
+
import_types2.ModelCapabilities.ImageInput
|
|
3890
|
+
);
|
|
3891
|
+
case "audio":
|
|
3892
|
+
return info.value.capabilities.includes(
|
|
3893
|
+
import_types2.ModelCapabilities.AudioInput
|
|
3894
|
+
);
|
|
3895
|
+
case "video":
|
|
3896
|
+
return info.value.capabilities.includes(
|
|
3897
|
+
import_types2.ModelCapabilities.VideoInput
|
|
3898
|
+
);
|
|
3899
|
+
default:
|
|
3900
|
+
return info.value.capabilities.includes(import_types2.ModelCapabilities.FileInput);
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
__name(modelSupportsElement, "modelSupportsElement");
|
|
3904
|
+
function setElementUrl(element, url) {
|
|
3905
|
+
element.attrs["imageUrl"] = url;
|
|
3906
|
+
element.attrs["src"] = url;
|
|
3907
|
+
element.attrs["url"] = url;
|
|
3908
|
+
}
|
|
3909
|
+
__name(setElementUrl, "setElementUrl");
|
|
3910
|
+
function trackForwardId(history, element, message) {
|
|
3911
|
+
const state = history.get(message) ?? {
|
|
3957
3912
|
ids: [],
|
|
3958
3913
|
hasForwardHistory: false
|
|
3959
3914
|
};
|
|
3915
|
+
history.set(message, state);
|
|
3960
3916
|
state.hasForwardHistory = true;
|
|
3961
3917
|
const id = (0, import_koishi6.pickForwardMessageId)(element);
|
|
3962
3918
|
if (id && !state.ids.includes(id)) {
|
package/lib/index.mjs
CHANGED
|
@@ -3354,6 +3354,9 @@ var SUPPORTED_AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
3354
3354
|
"audio/aiff"
|
|
3355
3355
|
]);
|
|
3356
3356
|
function apply18(ctx, config, chain) {
|
|
3357
|
+
const forwardHistory = /* @__PURE__ */ new WeakMap();
|
|
3358
|
+
const fileSizes = /* @__PURE__ */ new WeakMap();
|
|
3359
|
+
const handledAudio = /* @__PURE__ */ new WeakSet();
|
|
3357
3360
|
chain.middleware("read_chat_message", async (session, context) => {
|
|
3358
3361
|
let message = context.command != null ? context.message : session.elements;
|
|
3359
3362
|
if (typeof message === "string") {
|
|
@@ -3432,15 +3435,14 @@ function apply18(ctx, config, chain) {
|
|
|
3432
3435
|
}
|
|
3433
3436
|
);
|
|
3434
3437
|
if (config.attachForwardMsgIdToContext) {
|
|
3435
|
-
const
|
|
3436
|
-
const state = kwargs?.[forwardHistoryInternalKey];
|
|
3438
|
+
const state = forwardHistory.get(transformedMessage);
|
|
3437
3439
|
if (state?.hasForwardHistory) {
|
|
3438
3440
|
if (state.ids.length > 0) {
|
|
3441
|
+
transformedMessage.additional_kwargs ??= {};
|
|
3439
3442
|
transformedMessage.additional_kwargs.forwardMessageIds = state.ids;
|
|
3440
3443
|
}
|
|
3441
3444
|
addMessageContent(transformedMessage, "[聊天记录]");
|
|
3442
3445
|
}
|
|
3443
|
-
delete kwargs?.[forwardHistoryInternalKey];
|
|
3444
3446
|
}
|
|
3445
3447
|
if (transformedMessage.content.length < 1 && getMessageContent3(transformedMessage.content).trim().length < 1) {
|
|
3446
3448
|
return 1 /* STOP */;
|
|
@@ -3474,7 +3476,7 @@ function apply18(ctx, config, chain) {
|
|
|
3474
3476
|
"forward",
|
|
3475
3477
|
async (session, element, message) => {
|
|
3476
3478
|
if (!config.attachForwardMsgIdToContext) return;
|
|
3477
|
-
trackForwardId(element, message);
|
|
3479
|
+
trackForwardId(forwardHistory, element, message);
|
|
3478
3480
|
}
|
|
3479
3481
|
);
|
|
3480
3482
|
ctx.chatluna.messageTransformer.intercept(
|
|
@@ -3482,7 +3484,7 @@ function apply18(ctx, config, chain) {
|
|
|
3482
3484
|
async (session, element, message) => {
|
|
3483
3485
|
if (!config.attachForwardMsgIdToContext) return;
|
|
3484
3486
|
if (!isForwardMessageElement(element)) return;
|
|
3485
|
-
trackForwardId(element, message);
|
|
3487
|
+
trackForwardId(forwardHistory, element, message);
|
|
3486
3488
|
}
|
|
3487
3489
|
);
|
|
3488
3490
|
ctx.chatluna.messageTransformer.intercept(
|
|
@@ -3498,59 +3500,62 @@ function apply18(ctx, config, chain) {
|
|
|
3498
3500
|
ctx.chatluna.messageTransformer.intercept(
|
|
3499
3501
|
"img",
|
|
3500
3502
|
async (session, element, message, model) => {
|
|
3501
|
-
const
|
|
3503
|
+
const supportsImage = modelSupportsElement(ctx, model, "img");
|
|
3502
3504
|
const isInstalledImageService = ctx.chatluna.getPlugin("multimodal-service") != null;
|
|
3503
|
-
if (
|
|
3504
|
-
ModelCapabilities.ImageInput
|
|
3505
|
-
)) {
|
|
3505
|
+
if (!supportsImage) {
|
|
3506
3506
|
if (!isInstalledImageService) {
|
|
3507
3507
|
logger6.warn(
|
|
3508
3508
|
`Model "${model}" does not support image input. Please use a model that supports vision capabilities, or install chatluna-multimodal-service (multimodal-service) plugin to enable image description.`
|
|
3509
3509
|
);
|
|
3510
3510
|
}
|
|
3511
|
-
return false;
|
|
3512
3511
|
}
|
|
3513
3512
|
const url = element.attrs.url ?? element.attrs.src;
|
|
3513
|
+
const hash = await hashString(url, 8);
|
|
3514
|
+
element.attrs["imageHash"] = hash;
|
|
3514
3515
|
const displayUrl = url.length > 100 ? url.substring(0, 100) + "..." : url;
|
|
3515
3516
|
logger6.debug(`Processing image: ${displayUrl}`);
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
message,
|
|
3521
|
-
element,
|
|
3522
|
-
isInstalledImageService
|
|
3523
|
-
);
|
|
3524
|
-
}
|
|
3525
|
-
const { buffer, ext } = await readImage(ctx, url);
|
|
3526
|
-
if (ext == null) {
|
|
3517
|
+
const image = await readImage(ctx, url);
|
|
3518
|
+
const buffer = image.buffer;
|
|
3519
|
+
const ext = image.ext;
|
|
3520
|
+
if (ext == null || buffer == null) {
|
|
3527
3521
|
return false;
|
|
3528
3522
|
}
|
|
3529
3523
|
if (ext === "image/gif") {
|
|
3530
3524
|
if (!isInstalledImageService) {
|
|
3531
3525
|
logger6.warn(
|
|
3532
|
-
|
|
3526
|
+
"Detected GIF image, which is not supported by most models. Please install chatluna-multimodal-service (multimodal-service) plugin to parse GIF animations."
|
|
3533
3527
|
);
|
|
3534
3528
|
}
|
|
3529
|
+
if (ctx.chatluna_storage)
|
|
3530
|
+
setElementUrl(
|
|
3531
|
+
element,
|
|
3532
|
+
(await ctx.chatluna_storage.createTempFile(
|
|
3533
|
+
buffer,
|
|
3534
|
+
`${hash}.gif`
|
|
3535
|
+
)).url
|
|
3536
|
+
);
|
|
3535
3537
|
return false;
|
|
3536
3538
|
}
|
|
3537
|
-
const fileExt = ext.includes("/") ? ext.split("/")[1] : ext;
|
|
3538
|
-
element.attrs["ext"] = fileExt;
|
|
3539
3539
|
let fileName = element.attrs["filename"];
|
|
3540
3540
|
if (fileName == null || fileName.length > 50) {
|
|
3541
|
-
fileName = `${
|
|
3541
|
+
fileName = `${hash}.${ext.includes("/") ? ext.split("/")[1] : ext}`;
|
|
3542
3542
|
}
|
|
3543
|
+
element.attrs["ext"] = ext.includes("/") ? ext.split("/")[1] : ext;
|
|
3543
3544
|
logger6.debug(`Saving image as temp file: ${fileName}`);
|
|
3544
|
-
const tempFile = await ctx.chatluna_storage.createTempFile(
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
);
|
|
3548
|
-
|
|
3545
|
+
const tempFile = ctx.chatluna_storage ? await ctx.chatluna_storage.createTempFile(buffer, fileName) : null;
|
|
3546
|
+
const imageUrl = tempFile?.url ?? image.base64Source;
|
|
3547
|
+
const imageText = tempFile?.url ?? hash;
|
|
3548
|
+
if (tempFile) setElementUrl(element, tempFile.url);
|
|
3549
|
+
if (!supportsImage) {
|
|
3550
|
+
addTextPart(message, `[image:${imageText}]`);
|
|
3551
|
+
return false;
|
|
3552
|
+
}
|
|
3553
|
+
addTextPart(message, `[image:${imageText}]`);
|
|
3549
3554
|
message.content.push({
|
|
3550
3555
|
type: "image_url",
|
|
3551
|
-
image_url: { url:
|
|
3556
|
+
image_url: { url: imageUrl }
|
|
3552
3557
|
});
|
|
3553
|
-
|
|
3558
|
+
return false;
|
|
3554
3559
|
},
|
|
3555
3560
|
-100
|
|
3556
3561
|
);
|
|
@@ -3561,7 +3566,7 @@ function apply18(ctx, config, chain) {
|
|
|
3561
3566
|
"audio",
|
|
3562
3567
|
async (session, element, message, model) => {
|
|
3563
3568
|
const modelInfo = model != null ? ctx2.chatluna.platform.findModel(model) : void 0;
|
|
3564
|
-
if (
|
|
3569
|
+
if (handledAudio.has(element)) {
|
|
3565
3570
|
logger6.debug(
|
|
3566
3571
|
"Skip sst audio2text because audio is already handled."
|
|
3567
3572
|
);
|
|
@@ -3578,7 +3583,8 @@ function apply18(ctx, config, chain) {
|
|
|
3578
3583
|
const content = await ctx2.sst.audio2text(session);
|
|
3579
3584
|
logger6.debug(`audio2text: ${content}`);
|
|
3580
3585
|
addMessageContent(message, content);
|
|
3581
|
-
|
|
3586
|
+
handledAudio.add(element);
|
|
3587
|
+
return false;
|
|
3582
3588
|
}
|
|
3583
3589
|
),
|
|
3584
3590
|
-100
|
|
@@ -3587,18 +3593,9 @@ function apply18(ctx, config, chain) {
|
|
|
3587
3593
|
ctx.chatluna.messageTransformer.intercept(
|
|
3588
3594
|
"file",
|
|
3589
3595
|
async (session, element, message, model) => {
|
|
3590
|
-
const modelInfo = model != null ? ctx.chatluna.platform.findModel(model) : void 0;
|
|
3591
|
-
if (modelInfo?.value != null && !modelInfo.value.capabilities.includes(
|
|
3592
|
-
ModelCapabilities.FileInput
|
|
3593
|
-
)) {
|
|
3594
|
-
addMessageContent(
|
|
3595
|
-
message,
|
|
3596
|
-
`[file: ${element.attrs["file"] ?? element.attrs["filename"] ?? "attachment"} (skipped: model does not support file input)]`
|
|
3597
|
-
);
|
|
3598
|
-
return;
|
|
3599
|
-
}
|
|
3600
3596
|
await handleFileElement(
|
|
3601
3597
|
ctx,
|
|
3598
|
+
fileSizes,
|
|
3602
3599
|
session,
|
|
3603
3600
|
element,
|
|
3604
3601
|
message,
|
|
@@ -3610,18 +3607,9 @@ function apply18(ctx, config, chain) {
|
|
|
3610
3607
|
ctx.chatluna.messageTransformer.intercept(
|
|
3611
3608
|
"video",
|
|
3612
3609
|
async (session, element, message, model) => {
|
|
3613
|
-
const modelInfo = model != null ? ctx.chatluna.platform.findModel(model) : void 0;
|
|
3614
|
-
if (modelInfo?.value != null && !modelInfo.value.capabilities.includes(
|
|
3615
|
-
ModelCapabilities.VideoInput
|
|
3616
|
-
)) {
|
|
3617
|
-
addMessageContent(
|
|
3618
|
-
message,
|
|
3619
|
-
`[video: ${element.attrs["file"] ?? element.attrs["filename"] ?? "attachment"} (skipped: model does not support video input)]`
|
|
3620
|
-
);
|
|
3621
|
-
return;
|
|
3622
|
-
}
|
|
3623
3610
|
await handleFileElement(
|
|
3624
3611
|
ctx,
|
|
3612
|
+
fileSizes,
|
|
3625
3613
|
session,
|
|
3626
3614
|
element,
|
|
3627
3615
|
message,
|
|
@@ -3633,20 +3621,9 @@ function apply18(ctx, config, chain) {
|
|
|
3633
3621
|
ctx.chatluna.messageTransformer.intercept(
|
|
3634
3622
|
"audio",
|
|
3635
3623
|
async (session, element, message, model) => {
|
|
3636
|
-
if (isAudioHandled(message, element)) {
|
|
3637
|
-
logger6.debug(
|
|
3638
|
-
"Skip audio file handler because audio is already handled."
|
|
3639
|
-
);
|
|
3640
|
-
return false;
|
|
3641
|
-
}
|
|
3642
|
-
const modelInfo = model != null ? ctx.chatluna.platform.findModel(model) : void 0;
|
|
3643
|
-
if (modelInfo?.value != null && !modelInfo.value.capabilities.includes(
|
|
3644
|
-
ModelCapabilities.AudioInput
|
|
3645
|
-
)) {
|
|
3646
|
-
return false;
|
|
3647
|
-
}
|
|
3648
3624
|
const handled = await handleFileElement(
|
|
3649
3625
|
ctx,
|
|
3626
|
+
fileSizes,
|
|
3650
3627
|
session,
|
|
3651
3628
|
element,
|
|
3652
3629
|
message,
|
|
@@ -3654,7 +3631,7 @@ function apply18(ctx, config, chain) {
|
|
|
3654
3631
|
"audio"
|
|
3655
3632
|
);
|
|
3656
3633
|
if (handled) {
|
|
3657
|
-
|
|
3634
|
+
handledAudio.add(element);
|
|
3658
3635
|
}
|
|
3659
3636
|
return false;
|
|
3660
3637
|
}
|
|
@@ -3711,15 +3688,8 @@ async function resolveSourceUrl(ctx, session, element) {
|
|
|
3711
3688
|
return null;
|
|
3712
3689
|
}
|
|
3713
3690
|
__name(resolveSourceUrl, "resolveSourceUrl");
|
|
3714
|
-
async function handleFileElement(ctx, session, element, message, model, elementType) {
|
|
3715
|
-
const
|
|
3716
|
-
if (elementType === "audio" && isAudioHandled(message, element)) {
|
|
3717
|
-
logger6.debug(
|
|
3718
|
-
"Skip handling audio file because audio is already handled."
|
|
3719
|
-
);
|
|
3720
|
-
return false;
|
|
3721
|
-
}
|
|
3722
|
-
const fileName = element.attrs["file"] ?? element.attrs["name"] ?? element.attrs["filename"];
|
|
3691
|
+
async function handleFileElement(ctx, fileSizes, session, element, message, model, elementType) {
|
|
3692
|
+
const name2 = element.attrs["file"] ?? element.attrs["name"] ?? element.attrs["filename"];
|
|
3723
3693
|
const sourceUrl = await resolveSourceUrl(ctx, session, element);
|
|
3724
3694
|
if (!sourceUrl) return false;
|
|
3725
3695
|
const fileConfig = await getFileConfig(ctx, model);
|
|
@@ -3740,7 +3710,31 @@ async function handleFileElement(ctx, session, element, message, model, elementT
|
|
|
3740
3710
|
logger6.error(`Failed to read file from ${sourceUrl}:`, error);
|
|
3741
3711
|
return false;
|
|
3742
3712
|
}
|
|
3743
|
-
const mimeType = responseMimeType ?? getMimeTypeFromSource(sourceUrl,
|
|
3713
|
+
const mimeType = responseMimeType ?? getMimeTypeFromSource(sourceUrl, name2);
|
|
3714
|
+
const fileName = name2 ?? "attachment";
|
|
3715
|
+
let label;
|
|
3716
|
+
switch (elementType) {
|
|
3717
|
+
case "audio":
|
|
3718
|
+
label = "voice";
|
|
3719
|
+
break;
|
|
3720
|
+
case "video":
|
|
3721
|
+
label = "video";
|
|
3722
|
+
break;
|
|
3723
|
+
default:
|
|
3724
|
+
label = "file";
|
|
3725
|
+
}
|
|
3726
|
+
const file = ctx.chatluna_storage ? await ctx.chatluna_storage.createTempFile(buffer, fileName) : null;
|
|
3727
|
+
const fileUrl = file ? file.url : `data:${mimeType ?? "application/octet-stream"};base64,${buffer.toString("base64")}`;
|
|
3728
|
+
element.attrs["file"] = file?.name ?? fileName;
|
|
3729
|
+
element.attrs["filename"] = file?.name ?? fileName;
|
|
3730
|
+
element.attrs["chatluna_file_url"] = file?.url ?? sourceUrl;
|
|
3731
|
+
addTextPart(message, `[${label}:${file?.name ?? fileName}]`);
|
|
3732
|
+
if (!modelSupportsElement(ctx, model, elementType)) {
|
|
3733
|
+
logger6.warn(
|
|
3734
|
+
`Model "${model}" does not support ${label} input. The file was saved and fallback text was kept.`
|
|
3735
|
+
);
|
|
3736
|
+
return false;
|
|
3737
|
+
}
|
|
3744
3738
|
if (elementType === "audio" && mimeType != null) {
|
|
3745
3739
|
if (!SUPPORTED_AUDIO_MIME_TYPES.has(mimeType)) {
|
|
3746
3740
|
const isInstalledMultimodalService = ctx.chatluna.getPlugin("multimodal-service") != null;
|
|
@@ -3756,7 +3750,7 @@ async function handleFileElement(ctx, session, element, message, model, elementT
|
|
|
3756
3750
|
if (mimeType != null && !fileConfig.supportedMimeTypes.has(mimeType)) {
|
|
3757
3751
|
addMessageContent(
|
|
3758
3752
|
message,
|
|
3759
|
-
`[${
|
|
3753
|
+
`[${label}: ${file?.name ?? fileName} (skipped: unsupported MIME type "${mimeType}")]`
|
|
3760
3754
|
);
|
|
3761
3755
|
return false;
|
|
3762
3756
|
}
|
|
@@ -3768,76 +3762,25 @@ async function handleFileElement(ctx, session, element, message, model, elementT
|
|
|
3768
3762
|
if (encodedSize > maxSize) {
|
|
3769
3763
|
addMessageContent(
|
|
3770
3764
|
message,
|
|
3771
|
-
`[${
|
|
3765
|
+
`[${label}: ${file?.name ?? fileName} (skipped: file size ${encodedSize} bytes exceeds limit ${maxSize} bytes)]`
|
|
3772
3766
|
);
|
|
3773
3767
|
return false;
|
|
3774
3768
|
}
|
|
3775
|
-
const
|
|
3776
|
-
|
|
3777
|
-
if (newTotal > fileConfig.maxTotalSizeBytes) {
|
|
3769
|
+
const size = (fileSizes.get(message) ?? 0) + encodedSize;
|
|
3770
|
+
if (size > fileConfig.maxTotalSizeBytes) {
|
|
3778
3771
|
addMessageContent(
|
|
3779
3772
|
message,
|
|
3780
|
-
`[${
|
|
3773
|
+
`[${label}: ${file?.name ?? fileName} (skipped: total inline size would exceed limit)]`
|
|
3781
3774
|
);
|
|
3782
3775
|
return false;
|
|
3783
3776
|
}
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
element.attrs["file"] = resolvedFileName;
|
|
3787
|
-
element.attrs["filename"] = resolvedFileName;
|
|
3788
|
-
element.attrs["chatluna_file_url"] = sourceUrl;
|
|
3789
|
-
const label = elementType === "audio" ? "voice" : elementType === "video" ? "video" : "file";
|
|
3790
|
-
let fileUrl;
|
|
3791
|
-
if (ctx.chatluna_storage) {
|
|
3792
|
-
const file = await ctx.chatluna_storage.createTempFile(
|
|
3793
|
-
buffer,
|
|
3794
|
-
resolvedFileName
|
|
3795
|
-
);
|
|
3796
|
-
const displayFileName = fileName ?? file.name;
|
|
3797
|
-
element.attrs["file"] = displayFileName;
|
|
3798
|
-
element.attrs["filename"] = displayFileName;
|
|
3799
|
-
element.attrs["chatluna_file_url"] = file.url;
|
|
3800
|
-
fileUrl = file.url;
|
|
3801
|
-
ensureContentArray(message, `[${label}:${displayFileName}]`);
|
|
3802
|
-
} else {
|
|
3803
|
-
const base64 = buffer.toString("base64");
|
|
3804
|
-
fileUrl = `data:${mimeType ?? "application/octet-stream"};base64,${base64}`;
|
|
3805
|
-
ensureContentArray(message, `[${label}:${resolvedFileName}]`);
|
|
3777
|
+
fileSizes.set(message, size);
|
|
3778
|
+
addFileSize(message, size);
|
|
3806
3779
|
}
|
|
3807
3780
|
pushTypedContent(message, elementType, fileUrl, mimeType);
|
|
3808
3781
|
return true;
|
|
3809
3782
|
}
|
|
3810
3783
|
__name(handleFileElement, "handleFileElement");
|
|
3811
|
-
async function oldImageRead(ctx, url, message, element, isInstalledImageService) {
|
|
3812
|
-
const imageHash = await hashString(url, 8);
|
|
3813
|
-
element.attrs["imageHash"] = imageHash;
|
|
3814
|
-
try {
|
|
3815
|
-
const { base64Source, ext } = await readImage(ctx, url);
|
|
3816
|
-
if (ext == null) {
|
|
3817
|
-
return false;
|
|
3818
|
-
}
|
|
3819
|
-
if (ext === "image/gif") {
|
|
3820
|
-
if (!isInstalledImageService) {
|
|
3821
|
-
logger6.warn(
|
|
3822
|
-
`Detected GIF image, which is not supported by most models. Please install chatluna-multimodal-service (multimodal-service) plugin to parse GIF animations.`
|
|
3823
|
-
);
|
|
3824
|
-
}
|
|
3825
|
-
return false;
|
|
3826
|
-
}
|
|
3827
|
-
ensureContentArray(message, `[image:${imageHash}]`);
|
|
3828
|
-
message.content.push({
|
|
3829
|
-
type: "image_url",
|
|
3830
|
-
image_url: { url: base64Source, hash: imageHash }
|
|
3831
|
-
});
|
|
3832
|
-
} catch (error) {
|
|
3833
|
-
const displayUrl = url.length > 100 ? url.substring(0, 100) + "..." : url;
|
|
3834
|
-
logger6.warn(
|
|
3835
|
-
`Failed to read image from ${displayUrl}. Please check your Koishi chat adapter.`,
|
|
3836
|
-
error
|
|
3837
|
-
);
|
|
3838
|
-
}
|
|
3839
|
-
}
|
|
3840
|
-
__name(oldImageRead, "oldImageRead");
|
|
3841
3784
|
async function readImage(ctx, url) {
|
|
3842
3785
|
if (url.startsWith("data:image") && url.includes("base64")) {
|
|
3843
3786
|
const buffer = Buffer.from(url.split(",")[1], "base64");
|
|
@@ -3894,12 +3837,6 @@ function pushTypedContent(message, elementType, url, mimeType) {
|
|
|
3894
3837
|
}
|
|
3895
3838
|
}
|
|
3896
3839
|
__name(pushTypedContent, "pushTypedContent");
|
|
3897
|
-
function getFileTotalSize(message) {
|
|
3898
|
-
const kwargs = message.additional_kwargs ?? {};
|
|
3899
|
-
const value = kwargs["__file_total_size"];
|
|
3900
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3901
|
-
}
|
|
3902
|
-
__name(getFileTotalSize, "getFileTotalSize");
|
|
3903
3840
|
function toContentParts(content) {
|
|
3904
3841
|
if (content == null) {
|
|
3905
3842
|
return [];
|
|
@@ -3910,23 +3847,16 @@ function toContentParts(content) {
|
|
|
3910
3847
|
return Array.isArray(content) ? content : [content];
|
|
3911
3848
|
}
|
|
3912
3849
|
__name(toContentParts, "toContentParts");
|
|
3913
|
-
function
|
|
3850
|
+
function addTextPart(message, text) {
|
|
3914
3851
|
const parts = toContentParts(message.content);
|
|
3915
|
-
|
|
3916
|
-
message.content = parts;
|
|
3917
|
-
return;
|
|
3918
|
-
}
|
|
3919
|
-
message.content = [{ type: "text", text: fallbackText }];
|
|
3852
|
+
message.content = [...parts, { type: "text", text }];
|
|
3920
3853
|
}
|
|
3921
|
-
__name(
|
|
3922
|
-
function
|
|
3923
|
-
|
|
3854
|
+
__name(addTextPart, "addTextPart");
|
|
3855
|
+
function addFileSize(message, size) {
|
|
3856
|
+
message.additional_kwargs ??= {};
|
|
3857
|
+
message.additional_kwargs["__file_total_size"] = size;
|
|
3924
3858
|
}
|
|
3925
|
-
__name(
|
|
3926
|
-
function markAudioHandled(_message, element) {
|
|
3927
|
-
element.attrs["_audioHandled"] = true;
|
|
3928
|
-
}
|
|
3929
|
-
__name(markAudioHandled, "markAudioHandled");
|
|
3859
|
+
__name(addFileSize, "addFileSize");
|
|
3930
3860
|
function addMessageContent(message, content) {
|
|
3931
3861
|
if (typeof message.content === "string" && typeof content === "string") {
|
|
3932
3862
|
message.content += content;
|
|
@@ -3940,13 +3870,39 @@ function addMessageContent(message, content) {
|
|
|
3940
3870
|
message.content = [...currentParts, ...incomingParts];
|
|
3941
3871
|
}
|
|
3942
3872
|
__name(addMessageContent, "addMessageContent");
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3873
|
+
function modelSupportsElement(ctx, model, type) {
|
|
3874
|
+
const info = model != null ? ctx.chatluna.platform.findModel(model) : null;
|
|
3875
|
+
if (info?.value == null) return true;
|
|
3876
|
+
switch (type) {
|
|
3877
|
+
case "img":
|
|
3878
|
+
return info.value.capabilities.includes(
|
|
3879
|
+
ModelCapabilities.ImageInput
|
|
3880
|
+
);
|
|
3881
|
+
case "audio":
|
|
3882
|
+
return info.value.capabilities.includes(
|
|
3883
|
+
ModelCapabilities.AudioInput
|
|
3884
|
+
);
|
|
3885
|
+
case "video":
|
|
3886
|
+
return info.value.capabilities.includes(
|
|
3887
|
+
ModelCapabilities.VideoInput
|
|
3888
|
+
);
|
|
3889
|
+
default:
|
|
3890
|
+
return info.value.capabilities.includes(ModelCapabilities.FileInput);
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
__name(modelSupportsElement, "modelSupportsElement");
|
|
3894
|
+
function setElementUrl(element, url) {
|
|
3895
|
+
element.attrs["imageUrl"] = url;
|
|
3896
|
+
element.attrs["src"] = url;
|
|
3897
|
+
element.attrs["url"] = url;
|
|
3898
|
+
}
|
|
3899
|
+
__name(setElementUrl, "setElementUrl");
|
|
3900
|
+
function trackForwardId(history, element, message) {
|
|
3901
|
+
const state = history.get(message) ?? {
|
|
3947
3902
|
ids: [],
|
|
3948
3903
|
hasForwardHistory: false
|
|
3949
3904
|
};
|
|
3905
|
+
history.set(message, state);
|
|
3950
3906
|
state.hasForwardHistory = true;
|
|
3951
3907
|
const id = pickForwardMessageId(element);
|
|
3952
3908
|
if (id && !state.ids.includes(id)) {
|
|
@@ -321,7 +321,7 @@ async function executeTools(actions, toolMap, config, signal, handleParsingError
|
|
|
321
321
|
if (tool == null) {
|
|
322
322
|
return {
|
|
323
323
|
action,
|
|
324
|
-
observation: `${action.tool} is not a valid tool
|
|
324
|
+
observation: `${action.tool} is not a valid tool. Try another tool. If this happens repeatedly, stop the task and tell the user you cannot call these tools right now.`
|
|
325
325
|
};
|
|
326
326
|
}
|
|
327
327
|
const mask = config?.configurable?.["toolMask"] ?? config?.configurable?.["subagentContext"]?.["toolMask"];
|
|
@@ -329,14 +329,14 @@ async function executeTools(actions, toolMap, config, signal, handleParsingError
|
|
|
329
329
|
const allowed = Object.values(toolMap).map((item) => item.name).filter((name) => applyToolMask(name, mask));
|
|
330
330
|
return {
|
|
331
331
|
action,
|
|
332
|
-
observation: `Tool '${action.tool}' is not allowed for the current agent. Available tools: ${allowed.join(", ")}
|
|
332
|
+
observation: `Tool '${action.tool}' is not allowed for the current agent. Available tools: ${allowed.join(", ")}. Try another tool, and test whether it can be called first. If this happens repeatedly, stop the task and tell the user you cannot call these tools right now.`
|
|
333
333
|
};
|
|
334
334
|
}
|
|
335
335
|
const callMask = config?.configurable?.["toolMask"]?.toolCallMask ?? config?.configurable?.["subagentContext"]?.["toolMask"]?.toolCallMask;
|
|
336
336
|
if (callMask && !applyToolMask(action.tool, callMask)) {
|
|
337
337
|
return {
|
|
338
338
|
action,
|
|
339
|
-
observation: `You do not have permission to call tool '${action.tool}'. Try another tool.`
|
|
339
|
+
observation: `You do not have permission to call tool '${action.tool}'. Try another tool, and test whether it can be called first. If this happens repeatedly, stop the task and tell the user you cannot call these tools right now.`
|
|
340
340
|
};
|
|
341
341
|
}
|
|
342
342
|
try {
|
|
@@ -300,7 +300,7 @@ async function executeTools(actions, toolMap, config, signal, handleParsingError
|
|
|
300
300
|
if (tool == null) {
|
|
301
301
|
return {
|
|
302
302
|
action,
|
|
303
|
-
observation: `${action.tool} is not a valid tool
|
|
303
|
+
observation: `${action.tool} is not a valid tool. Try another tool. If this happens repeatedly, stop the task and tell the user you cannot call these tools right now.`
|
|
304
304
|
};
|
|
305
305
|
}
|
|
306
306
|
const mask = config?.configurable?.["toolMask"] ?? config?.configurable?.["subagentContext"]?.["toolMask"];
|
|
@@ -308,14 +308,14 @@ async function executeTools(actions, toolMap, config, signal, handleParsingError
|
|
|
308
308
|
const allowed = Object.values(toolMap).map((item) => item.name).filter((name) => applyToolMask(name, mask));
|
|
309
309
|
return {
|
|
310
310
|
action,
|
|
311
|
-
observation: `Tool '${action.tool}' is not allowed for the current agent. Available tools: ${allowed.join(", ")}
|
|
311
|
+
observation: `Tool '${action.tool}' is not allowed for the current agent. Available tools: ${allowed.join(", ")}. Try another tool, and test whether it can be called first. If this happens repeatedly, stop the task and tell the user you cannot call these tools right now.`
|
|
312
312
|
};
|
|
313
313
|
}
|
|
314
314
|
const callMask = config?.configurable?.["toolMask"]?.toolCallMask ?? config?.configurable?.["subagentContext"]?.["toolMask"]?.toolCallMask;
|
|
315
315
|
if (callMask && !applyToolMask(action.tool, callMask)) {
|
|
316
316
|
return {
|
|
317
317
|
action,
|
|
318
|
-
observation: `You do not have permission to call tool '${action.tool}'. Try another tool.`
|
|
318
|
+
observation: `You do not have permission to call tool '${action.tool}'. Try another tool, and test whether it can be called first. If this happens repeatedly, stop the task and tell the user you cannot call these tools right now.`
|
|
319
319
|
};
|
|
320
320
|
}
|
|
321
321
|
try {
|
|
@@ -58,7 +58,7 @@ var BasePlatformClient = class {
|
|
|
58
58
|
return false;
|
|
59
59
|
}
|
|
60
60
|
const maxRetries = cfg.value.maxRetries ?? 5;
|
|
61
|
-
while (retryCount
|
|
61
|
+
while (retryCount <= maxRetries) {
|
|
62
62
|
let oldConfig;
|
|
63
63
|
try {
|
|
64
64
|
oldConfig = this.configPool.getConfig(true);
|
|
@@ -74,7 +74,7 @@ var BasePlatformClient = class {
|
|
|
74
74
|
unlock();
|
|
75
75
|
return false;
|
|
76
76
|
}
|
|
77
|
-
if (retryCount
|
|
77
|
+
if (retryCount >= maxRetries) {
|
|
78
78
|
if (oldConfig == null) {
|
|
79
79
|
this.ctx.logger.error(e);
|
|
80
80
|
unlock();
|
|
@@ -39,7 +39,7 @@ var BasePlatformClient = class {
|
|
|
39
39
|
return false;
|
|
40
40
|
}
|
|
41
41
|
const maxRetries = cfg.value.maxRetries ?? 5;
|
|
42
|
-
while (retryCount
|
|
42
|
+
while (retryCount <= maxRetries) {
|
|
43
43
|
let oldConfig;
|
|
44
44
|
try {
|
|
45
45
|
oldConfig = this.configPool.getConfig(true);
|
|
@@ -55,7 +55,7 @@ var BasePlatformClient = class {
|
|
|
55
55
|
unlock();
|
|
56
56
|
return false;
|
|
57
57
|
}
|
|
58
|
-
if (retryCount
|
|
58
|
+
if (retryCount >= maxRetries) {
|
|
59
59
|
if (oldConfig == null) {
|
|
60
60
|
this.ctx.logger.error(e);
|
|
61
61
|
unlock();
|
|
@@ -272,7 +272,7 @@ var ChatLunaChatModel = class extends import_chat_models.BaseChatModel {
|
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
274
|
async *_streamResponseChunks(messages, options, runManager, reportUsage = true) {
|
|
275
|
-
const
|
|
275
|
+
const maxAttempts = Math.max(1, (this._options.maxRetries ?? 0) + 1);
|
|
276
276
|
let promptTokens = 0;
|
|
277
277
|
if (reportUsage) {
|
|
278
278
|
;
|
|
@@ -285,7 +285,7 @@ var ChatLunaChatModel = class extends import_chat_models.BaseChatModel {
|
|
|
285
285
|
...this.invocationParams(options),
|
|
286
286
|
input: messages
|
|
287
287
|
};
|
|
288
|
-
for (let attempt = 0; attempt <
|
|
288
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
289
289
|
const latestTokenUsage = this._createTokenUsageTracker();
|
|
290
290
|
let stream = null;
|
|
291
291
|
let hasChunk = false;
|
|
@@ -333,7 +333,7 @@ var ChatLunaChatModel = class extends import_chat_models.BaseChatModel {
|
|
|
333
333
|
error,
|
|
334
334
|
hasChunk,
|
|
335
335
|
attempt,
|
|
336
|
-
|
|
336
|
+
maxAttempts
|
|
337
337
|
)) {
|
|
338
338
|
if (hasChunk) {
|
|
339
339
|
import_koishi_plugin_chatluna.logger.debug(
|
|
@@ -351,7 +351,7 @@ var ChatLunaChatModel = class extends import_chat_models.BaseChatModel {
|
|
|
351
351
|
throw error;
|
|
352
352
|
}
|
|
353
353
|
import_koishi_plugin_chatluna.logger.debug(
|
|
354
|
-
`Stream failed before first chunk (attempt ${attempt + 1}/${
|
|
354
|
+
`Stream failed before first chunk (attempt ${attempt + 1}/${maxAttempts}), retrying...`,
|
|
355
355
|
error
|
|
356
356
|
);
|
|
357
357
|
await (0, import_koishi.sleep)(2e3 * 2 ** attempt);
|
|
@@ -436,8 +436,8 @@ var ChatLunaChatModel = class extends import_chat_models.BaseChatModel {
|
|
|
436
436
|
);
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
|
-
_shouldRethrowStreamError(error, hasChunk, attempt,
|
|
440
|
-
return this._isAbortError(error) || hasChunk || attempt ===
|
|
439
|
+
_shouldRethrowStreamError(error, hasChunk, attempt, maxAttempts) {
|
|
440
|
+
return this._isAbortError(error) || hasChunk || attempt === maxAttempts - 1;
|
|
441
441
|
}
|
|
442
442
|
_isAbortError(error) {
|
|
443
443
|
if (error instanceof import_error.ChatLunaError) {
|
|
@@ -546,9 +546,9 @@ var ChatLunaChatModel = class extends import_chat_models.BaseChatModel {
|
|
|
546
546
|
}
|
|
547
547
|
}
|
|
548
548
|
_generateWithRetry(messages, options, runManager) {
|
|
549
|
-
const
|
|
549
|
+
const maxAttempts = Math.max(1, (this._options.maxRetries ?? 0) + 1);
|
|
550
550
|
const generateWithRetry = /* @__PURE__ */ __name(async () => {
|
|
551
|
-
for (let attempt = 0; attempt <
|
|
551
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
552
552
|
try {
|
|
553
553
|
let response;
|
|
554
554
|
if (options.stream) {
|
|
@@ -578,7 +578,7 @@ var ChatLunaChatModel = class extends import_chat_models.BaseChatModel {
|
|
|
578
578
|
}
|
|
579
579
|
return response;
|
|
580
580
|
} catch (error) {
|
|
581
|
-
if (options.stream || this._isAbortError(error) || attempt ===
|
|
581
|
+
if (options.stream || this._isAbortError(error) || attempt === maxAttempts - 1) {
|
|
582
582
|
throw error;
|
|
583
583
|
}
|
|
584
584
|
await (0, import_koishi.sleep)(2e3 * 2 ** attempt);
|
|
@@ -254,7 +254,7 @@ var ChatLunaChatModel = class extends BaseChatModel {
|
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
256
|
async *_streamResponseChunks(messages, options, runManager, reportUsage = true) {
|
|
257
|
-
const
|
|
257
|
+
const maxAttempts = Math.max(1, (this._options.maxRetries ?? 0) + 1);
|
|
258
258
|
let promptTokens = 0;
|
|
259
259
|
if (reportUsage) {
|
|
260
260
|
;
|
|
@@ -267,7 +267,7 @@ var ChatLunaChatModel = class extends BaseChatModel {
|
|
|
267
267
|
...this.invocationParams(options),
|
|
268
268
|
input: messages
|
|
269
269
|
};
|
|
270
|
-
for (let attempt = 0; attempt <
|
|
270
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
271
271
|
const latestTokenUsage = this._createTokenUsageTracker();
|
|
272
272
|
let stream = null;
|
|
273
273
|
let hasChunk = false;
|
|
@@ -315,7 +315,7 @@ var ChatLunaChatModel = class extends BaseChatModel {
|
|
|
315
315
|
error,
|
|
316
316
|
hasChunk,
|
|
317
317
|
attempt,
|
|
318
|
-
|
|
318
|
+
maxAttempts
|
|
319
319
|
)) {
|
|
320
320
|
if (hasChunk) {
|
|
321
321
|
logger.debug(
|
|
@@ -333,7 +333,7 @@ var ChatLunaChatModel = class extends BaseChatModel {
|
|
|
333
333
|
throw error;
|
|
334
334
|
}
|
|
335
335
|
logger.debug(
|
|
336
|
-
`Stream failed before first chunk (attempt ${attempt + 1}/${
|
|
336
|
+
`Stream failed before first chunk (attempt ${attempt + 1}/${maxAttempts}), retrying...`,
|
|
337
337
|
error
|
|
338
338
|
);
|
|
339
339
|
await sleep(2e3 * 2 ** attempt);
|
|
@@ -418,8 +418,8 @@ var ChatLunaChatModel = class extends BaseChatModel {
|
|
|
418
418
|
);
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
|
-
_shouldRethrowStreamError(error, hasChunk, attempt,
|
|
422
|
-
return this._isAbortError(error) || hasChunk || attempt ===
|
|
421
|
+
_shouldRethrowStreamError(error, hasChunk, attempt, maxAttempts) {
|
|
422
|
+
return this._isAbortError(error) || hasChunk || attempt === maxAttempts - 1;
|
|
423
423
|
}
|
|
424
424
|
_isAbortError(error) {
|
|
425
425
|
if (error instanceof ChatLunaError) {
|
|
@@ -528,9 +528,9 @@ var ChatLunaChatModel = class extends BaseChatModel {
|
|
|
528
528
|
}
|
|
529
529
|
}
|
|
530
530
|
_generateWithRetry(messages, options, runManager) {
|
|
531
|
-
const
|
|
531
|
+
const maxAttempts = Math.max(1, (this._options.maxRetries ?? 0) + 1);
|
|
532
532
|
const generateWithRetry = /* @__PURE__ */ __name(async () => {
|
|
533
|
-
for (let attempt = 0; attempt <
|
|
533
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
534
534
|
try {
|
|
535
535
|
let response;
|
|
536
536
|
if (options.stream) {
|
|
@@ -560,7 +560,7 @@ var ChatLunaChatModel = class extends BaseChatModel {
|
|
|
560
560
|
}
|
|
561
561
|
return response;
|
|
562
562
|
} catch (error) {
|
|
563
|
-
if (options.stream || this._isAbortError(error) || attempt ===
|
|
563
|
+
if (options.stream || this._isAbortError(error) || attempt === maxAttempts - 1) {
|
|
564
564
|
throw error;
|
|
565
565
|
}
|
|
566
566
|
await sleep(2e3 * 2 ** attempt);
|
package/lib/services/chat.cjs
CHANGED
|
@@ -5246,7 +5246,7 @@ var ChatLunaPlugin = class {
|
|
|
5246
5246
|
import_koishi5.Schema.const("default"),
|
|
5247
5247
|
import_koishi5.Schema.const("balance")
|
|
5248
5248
|
]).default("default"),
|
|
5249
|
-
maxRetries: import_koishi5.Schema.number().min(
|
|
5249
|
+
maxRetries: import_koishi5.Schema.number().min(0).max(6).default(5),
|
|
5250
5250
|
timeout: import_koishi5.Schema.number().default(300 * 1e3),
|
|
5251
5251
|
proxyMode: import_koishi5.Schema.union([
|
|
5252
5252
|
import_koishi5.Schema.const("system"),
|
package/lib/services/chat.mjs
CHANGED
|
@@ -5288,7 +5288,7 @@ var ChatLunaPlugin = class {
|
|
|
5288
5288
|
Schema.const("default"),
|
|
5289
5289
|
Schema.const("balance")
|
|
5290
5290
|
]).default("default"),
|
|
5291
|
-
maxRetries: Schema.number().min(
|
|
5291
|
+
maxRetries: Schema.number().min(0).max(6).default(5),
|
|
5292
5292
|
timeout: Schema.number().default(300 * 1e3),
|
|
5293
5293
|
proxyMode: Schema.union([
|
|
5294
5294
|
Schema.const("system"),
|