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 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 kwargs = transformedMessage.additional_kwargs;
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 parsedModelInfo = model != null ? ctx.chatluna.platform.findModel(model) : void 0;
3513
+ const supportsImage = modelSupportsElement(ctx, model, "img");
3512
3514
  const isInstalledImageService = ctx.chatluna.getPlugin("multimodal-service") != null;
3513
- if (parsedModelInfo?.value != null && !parsedModelInfo.value.capabilities.includes(
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
- if (!ctx.chatluna_storage) {
3527
- return await oldImageRead(
3528
- ctx,
3529
- url,
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
- `Detected GIF image, which is not supported by most models. Please install chatluna-multimodal-service (multimodal-service) plugin to parse GIF animations.`
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 = `${await (0, import_string4.hashString)(url, 8)}.${fileExt}`;
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
- buffer,
3556
- fileName
3557
- );
3558
- ensureContentArray(message, `[image:${tempFile.url}]`);
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: tempFile.url }
3566
+ image_url: { url: imageUrl }
3562
3567
  });
3563
- element.attrs["imageUrl"] = tempFile.url;
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 (isAudioHandled(message, element)) {
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
- markAudioHandled(message, element);
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
- markAudioHandled(message, element);
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 displayElementType = elementType === "audio" ? "voice" : elementType;
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, fileName);
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
- `[${displayElementType}: ${fileName ?? "attachment"} (skipped: unsupported MIME type "${mimeType}")]`
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
- `[${displayElementType}: ${fileName ?? "attachment"} (skipped: file size ${encodedSize} bytes exceeds limit ${maxSize} bytes)]`
3775
+ `[${label}: ${file?.name ?? fileName} (skipped: file size ${encodedSize} bytes exceeds limit ${maxSize} bytes)]`
3782
3776
  );
3783
3777
  return false;
3784
3778
  }
3785
- const currentTotal = getFileTotalSize(message);
3786
- const newTotal = currentTotal + encodedSize;
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
- `[${displayElementType}: ${fileName ?? "attachment"} (skipped: total inline size would exceed limit)]`
3783
+ `[${label}: ${file?.name ?? fileName} (skipped: total inline size would exceed limit)]`
3791
3784
  );
3792
3785
  return false;
3793
3786
  }
3794
- }
3795
- const resolvedFileName = fileName ?? "attachment";
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 ensureContentArray(message, fallbackText) {
3860
+ function addTextPart(message, text) {
3924
3861
  const parts = toContentParts(message.content);
3925
- if (parts.length > 0) {
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(ensureContentArray, "ensureContentArray");
3932
- function isAudioHandled(_message, element) {
3933
- return element.attrs["_audioHandled"] === true;
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(isAudioHandled, "isAudioHandled");
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
- var forwardHistoryInternalKey = "__chatluna_forwardHistory";
3954
- function trackForwardId(element, message) {
3955
- const kwargs = message.additional_kwargs ??= {};
3956
- const state = kwargs[forwardHistoryInternalKey] ??= {
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 kwargs = transformedMessage.additional_kwargs;
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 parsedModelInfo = model != null ? ctx.chatluna.platform.findModel(model) : void 0;
3503
+ const supportsImage = modelSupportsElement(ctx, model, "img");
3502
3504
  const isInstalledImageService = ctx.chatluna.getPlugin("multimodal-service") != null;
3503
- if (parsedModelInfo?.value != null && !parsedModelInfo.value.capabilities.includes(
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
- if (!ctx.chatluna_storage) {
3517
- return await oldImageRead(
3518
- ctx,
3519
- url,
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
- `Detected GIF image, which is not supported by most models. Please install chatluna-multimodal-service (multimodal-service) plugin to parse GIF animations.`
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 = `${await hashString(url, 8)}.${fileExt}`;
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
- buffer,
3546
- fileName
3547
- );
3548
- ensureContentArray(message, `[image:${tempFile.url}]`);
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: tempFile.url }
3556
+ image_url: { url: imageUrl }
3552
3557
  });
3553
- element.attrs["imageUrl"] = tempFile.url;
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 (isAudioHandled(message, element)) {
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
- markAudioHandled(message, element);
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
- markAudioHandled(message, element);
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 displayElementType = elementType === "audio" ? "voice" : elementType;
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, fileName);
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
- `[${displayElementType}: ${fileName ?? "attachment"} (skipped: unsupported MIME type "${mimeType}")]`
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
- `[${displayElementType}: ${fileName ?? "attachment"} (skipped: file size ${encodedSize} bytes exceeds limit ${maxSize} bytes)]`
3765
+ `[${label}: ${file?.name ?? fileName} (skipped: file size ${encodedSize} bytes exceeds limit ${maxSize} bytes)]`
3772
3766
  );
3773
3767
  return false;
3774
3768
  }
3775
- const currentTotal = getFileTotalSize(message);
3776
- const newTotal = currentTotal + encodedSize;
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
- `[${displayElementType}: ${fileName ?? "attachment"} (skipped: total inline size would exceed limit)]`
3773
+ `[${label}: ${file?.name ?? fileName} (skipped: total inline size would exceed limit)]`
3781
3774
  );
3782
3775
  return false;
3783
3776
  }
3784
- }
3785
- const resolvedFileName = fileName ?? "attachment";
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 ensureContentArray(message, fallbackText) {
3850
+ function addTextPart(message, text) {
3914
3851
  const parts = toContentParts(message.content);
3915
- if (parts.length > 0) {
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(ensureContentArray, "ensureContentArray");
3922
- function isAudioHandled(_message, element) {
3923
- return element.attrs["_audioHandled"] === true;
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(isAudioHandled, "isAudioHandled");
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
- var forwardHistoryInternalKey = "__chatluna_forwardHistory";
3944
- function trackForwardId(element, message) {
3945
- const kwargs = message.additional_kwargs ??= {};
3946
- const state = kwargs[forwardHistoryInternalKey] ??= {
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, try another one.`
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, try another one.`
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 < (maxRetries ?? 1)) {
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 === maxRetries - 1) {
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 < (maxRetries ?? 1)) {
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 === maxRetries - 1) {
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 maxRetries = Math.max(1, this._options.maxRetries ?? 1);
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 < maxRetries; 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
- maxRetries
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}/${maxRetries}), retrying...`,
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, maxRetries) {
440
- return this._isAbortError(error) || hasChunk || attempt === maxRetries - 1;
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 maxRetries = Math.max(1, this._options.maxRetries ?? 1);
549
+ const maxAttempts = Math.max(1, (this._options.maxRetries ?? 0) + 1);
550
550
  const generateWithRetry = /* @__PURE__ */ __name(async () => {
551
- for (let attempt = 0; attempt < maxRetries; 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 === maxRetries - 1) {
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 maxRetries = Math.max(1, this._options.maxRetries ?? 1);
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 < maxRetries; 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
- maxRetries
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}/${maxRetries}), retrying...`,
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, maxRetries) {
422
- return this._isAbortError(error) || hasChunk || attempt === maxRetries - 1;
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 maxRetries = Math.max(1, this._options.maxRetries ?? 1);
531
+ const maxAttempts = Math.max(1, (this._options.maxRetries ?? 0) + 1);
532
532
  const generateWithRetry = /* @__PURE__ */ __name(async () => {
533
- for (let attempt = 0; attempt < maxRetries; 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 === maxRetries - 1) {
563
+ if (options.stream || this._isAbortError(error) || attempt === maxAttempts - 1) {
564
564
  throw error;
565
565
  }
566
566
  await sleep(2e3 * 2 ** attempt);
@@ -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(1).max(6).default(5),
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"),
@@ -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(1).max(6).default(5),
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"),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna",
3
3
  "description": "chatluna for koishi",
4
- "version": "1.4.0-alpha.21",
4
+ "version": "1.4.0-alpha.23",
5
5
  "main": "lib/index.cjs",
6
6
  "module": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",