koishi-plugin-dify-ai 0.1.6 → 0.1.7
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.js +238 -140
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -165,7 +165,8 @@ var uploadSchema = import_koishi.Schema.union([
|
|
|
165
165
|
mode: import_koishi.Schema.const("on").required(),
|
|
166
166
|
maxCount: import_koishi.Schema.natural().default(0).description("数量上限,0 不限"),
|
|
167
167
|
maxSizeMb: import_koishi.Schema.natural().default(0).description("单文件大小上限 MB,0 不限")
|
|
168
|
-
}).default({ mode: "on", maxCount: 0, maxSizeMb: 0 }).description("开启")
|
|
168
|
+
}).default({ mode: "on", maxCount: 0, maxSizeMb: 0 }).description("开启"),
|
|
169
|
+
import_koishi.Schema.boolean().description("旧版开关")
|
|
169
170
|
]);
|
|
170
171
|
var Config = import_koishi.Schema.intersect([
|
|
171
172
|
import_koishi.Schema.object({
|
|
@@ -316,14 +317,14 @@ function toVariable(entry) {
|
|
|
316
317
|
}
|
|
317
318
|
if (entry.source === "meta") {
|
|
318
319
|
if (entry.key === "none") return null;
|
|
319
|
-
return { field, source: { type: "meta", key: entry.key
|
|
320
|
+
return { field, source: { type: "meta", key: entry.key } };
|
|
320
321
|
}
|
|
321
322
|
if (entry.source === "history") {
|
|
322
323
|
const limit = Number(entry.limit);
|
|
323
324
|
return { field, source: { type: "history", ...limit > 0 ? { limit } : {} } };
|
|
324
325
|
}
|
|
325
326
|
if (entry.key === "none" || entry.key === "history") return null;
|
|
326
|
-
return { field, source: { type: "session", key: entry.key
|
|
327
|
+
return { field, source: { type: "session", key: entry.key } };
|
|
327
328
|
}
|
|
328
329
|
__name(toVariable, "toVariable");
|
|
329
330
|
function resolveUploadConfig(config) {
|
|
@@ -333,34 +334,29 @@ function resolveUploadConfig(config) {
|
|
|
333
334
|
if (config.uploadFiles?.mode === "off") return { enabled: false, maxCount: 0, maxMb: 0 };
|
|
334
335
|
return {
|
|
335
336
|
enabled: true,
|
|
336
|
-
maxCount:
|
|
337
|
-
maxMb:
|
|
337
|
+
maxCount: Number(config.uploadFiles?.maxCount) || 0,
|
|
338
|
+
maxMb: Number(config.uploadFiles?.maxSizeMb) || 0
|
|
338
339
|
};
|
|
339
340
|
}
|
|
340
341
|
__name(resolveUploadConfig, "resolveUploadConfig");
|
|
341
342
|
function resolveQuoteConfig(config) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
const quote = config.quote;
|
|
344
|
+
const isDefaultQuote = !quote || quote.mode === "on" && !(Number(quote.minElapsedSeconds) > 0);
|
|
345
|
+
if (typeof config.quoteTrigger === "boolean" && isDefaultQuote) {
|
|
346
|
+
return { enabled: config.quoteTrigger, minElapsedSeconds: 0 };
|
|
345
347
|
}
|
|
346
|
-
if (
|
|
347
|
-
return { enabled: true, minElapsedSeconds: 0 };
|
|
348
|
+
if (quote?.mode === "off") return { enabled: false, minElapsedSeconds: 0 };
|
|
349
|
+
return { enabled: true, minElapsedSeconds: Number(quote?.minElapsedSeconds) || 0 };
|
|
348
350
|
}
|
|
349
351
|
__name(resolveQuoteConfig, "resolveQuoteConfig");
|
|
350
352
|
function toCooldown(entry) {
|
|
351
353
|
if (entry?.mode === "fixed") {
|
|
352
|
-
|
|
353
|
-
return seconds > 0 ? { mode: "fixed", ms: seconds * 1e3 } : null;
|
|
354
|
+
return { mode: "fixed", ms: Number(entry.seconds) * 1e3 };
|
|
354
355
|
}
|
|
355
356
|
if (entry?.mode === "sliding") {
|
|
356
357
|
const minSeconds = Number(entry.minSeconds);
|
|
357
358
|
const maxSeconds = Number(entry.maxSeconds);
|
|
358
|
-
|
|
359
|
-
return {
|
|
360
|
-
mode: "sliding",
|
|
361
|
-
minMs: minSeconds * 1e3,
|
|
362
|
-
maxMs: Math.max(minSeconds, maxSeconds) * 1e3
|
|
363
|
-
};
|
|
359
|
+
return { mode: "sliding", minMs: minSeconds * 1e3, maxMs: Math.max(minSeconds, maxSeconds) * 1e3 };
|
|
364
360
|
}
|
|
365
361
|
return null;
|
|
366
362
|
}
|
|
@@ -371,12 +367,16 @@ function normalizeConfig(config) {
|
|
|
371
367
|
if (app.name?.trim() && !apps.has(app.name)) apps.set(app.name, app);
|
|
372
368
|
}
|
|
373
369
|
const rules = [];
|
|
370
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
374
371
|
for (const entry of config.rules ?? []) {
|
|
375
372
|
if (!entry.name?.trim() || entry.enabled === false) continue;
|
|
373
|
+
const name2 = entry.name.trim();
|
|
374
|
+
if (seenNames.has(name2)) continue;
|
|
375
|
+
seenNames.add(name2);
|
|
376
376
|
const conditions = (entry.conditions ?? []).filter((condition) => condition != null).map(toCondition).filter((condition) => condition != null);
|
|
377
377
|
const variables = (entry.variables ?? []).filter((variable) => variable != null).map(toVariable).filter((variable) => variable != null);
|
|
378
378
|
rules.push({
|
|
379
|
-
name:
|
|
379
|
+
name: name2,
|
|
380
380
|
enabled: true,
|
|
381
381
|
priority: Number(entry.priority) || 0,
|
|
382
382
|
app: entry.app?.trim() ?? "",
|
|
@@ -449,9 +449,6 @@ var DifyClient = class {
|
|
|
449
449
|
getAppInfo() {
|
|
450
450
|
return this.request("GET", "/info");
|
|
451
451
|
}
|
|
452
|
-
getParameters() {
|
|
453
|
-
return this.request("GET", "/parameters");
|
|
454
|
-
}
|
|
455
452
|
/** chat / agent-chat / advanced-chat(conversation_id 传空串表示新建会话) */
|
|
456
453
|
runChat(body) {
|
|
457
454
|
return this.request("POST", "/chat-messages", body);
|
|
@@ -500,6 +497,7 @@ __name(createDifyClient, "createDifyClient");
|
|
|
500
497
|
// src/utils/session-text.ts
|
|
501
498
|
var import_crypto = require("crypto");
|
|
502
499
|
var import_koishi2 = require("koishi");
|
|
500
|
+
var IMAGE_HASH_LENGTH = 8;
|
|
503
501
|
function parseTextSafe(content) {
|
|
504
502
|
if (!content) return "";
|
|
505
503
|
try {
|
|
@@ -519,7 +517,7 @@ __name(extractText, "extractText");
|
|
|
519
517
|
var IMAGE_PLACEHOLDER = "[图片]";
|
|
520
518
|
function imagePlaceholder(url) {
|
|
521
519
|
if (!url) return IMAGE_PLACEHOLDER;
|
|
522
|
-
return `[image:${(0, import_crypto.createHash)("sha1").update(url).digest("hex").slice(0,
|
|
520
|
+
return `[image:${(0, import_crypto.createHash)("sha1").update(url).digest("hex").slice(0, IMAGE_HASH_LENGTH)}]`;
|
|
523
521
|
}
|
|
524
522
|
__name(imagePlaceholder, "imagePlaceholder");
|
|
525
523
|
function annotateDroppedPlaceholders(text, droppedUrls) {
|
|
@@ -531,20 +529,23 @@ function annotateDroppedPlaceholders(text, droppedUrls) {
|
|
|
531
529
|
return result;
|
|
532
530
|
}
|
|
533
531
|
__name(annotateDroppedPlaceholders, "annotateDroppedPlaceholders");
|
|
534
|
-
function
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
532
|
+
function elementsOf(session) {
|
|
533
|
+
if (session.elements?.length) return session.elements;
|
|
534
|
+
try {
|
|
535
|
+
return import_koishi2.h.parse(session.content ?? "");
|
|
536
|
+
} catch {
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
__name(elementsOf, "elementsOf");
|
|
541
|
+
function extractTextWithPlaceholders(session, options = {}) {
|
|
542
542
|
const parts = [];
|
|
543
|
-
for (const element of
|
|
543
|
+
for (const element of elementsOf(session)) {
|
|
544
544
|
if (element.type === "text") {
|
|
545
545
|
const text = element.attrs?.["content"] ?? "";
|
|
546
546
|
if (text) parts.push(text);
|
|
547
547
|
} else if (element.type === "at") {
|
|
548
|
+
if (options.excludeBotAt && element.attrs?.["id"] === session.selfId) continue;
|
|
548
549
|
const name2 = element.attrs?.["name"] ?? element.attrs?.["id"];
|
|
549
550
|
if (name2) parts.push(`@${name2}`);
|
|
550
551
|
} else if (element.type === "img" || element.type === "image") {
|
|
@@ -554,6 +555,16 @@ function extractTextWithPlaceholders(session) {
|
|
|
554
555
|
return parts.join(" ").replace(/\s+/g, " ").trim();
|
|
555
556
|
}
|
|
556
557
|
__name(extractTextWithPlaceholders, "extractTextWithPlaceholders");
|
|
558
|
+
function extractImageUrls(session) {
|
|
559
|
+
const urls = [];
|
|
560
|
+
for (const element of elementsOf(session)) {
|
|
561
|
+
if (element.type !== "img" && element.type !== "image") continue;
|
|
562
|
+
const source = element.attrs?.["url"] ?? element.attrs?.["src"];
|
|
563
|
+
if (typeof source === "string" && source) urls.push(source);
|
|
564
|
+
}
|
|
565
|
+
return urls;
|
|
566
|
+
}
|
|
567
|
+
__name(extractImageUrls, "extractImageUrls");
|
|
557
568
|
|
|
558
569
|
// src/history.ts
|
|
559
570
|
var HISTORY_CAPACITY_MULTIPLIER = 2;
|
|
@@ -575,7 +586,7 @@ function formatHistoryEntries(entries) {
|
|
|
575
586
|
}
|
|
576
587
|
__name(formatHistoryEntries, "formatHistoryEntries");
|
|
577
588
|
function formatHistoryBrief(entries) {
|
|
578
|
-
return entries.map((entry) => entry.messageId ? `[msg:${entry.messageId}] ${entry.
|
|
589
|
+
return entries.map((entry) => entry.messageId ? `[msg:${entry.messageId}] ${entry.contentText}` : entry.contentText).join("\n");
|
|
579
590
|
}
|
|
580
591
|
__name(formatHistoryBrief, "formatHistoryBrief");
|
|
581
592
|
function buildQuote(session) {
|
|
@@ -592,12 +603,15 @@ __name(buildQuote, "buildQuote");
|
|
|
592
603
|
function buildHistoryEntry(session, role = "user") {
|
|
593
604
|
const content = extractTextWithPlaceholders(session);
|
|
594
605
|
if (!content) return null;
|
|
606
|
+
const imageUrls = extractImageUrls(session);
|
|
595
607
|
return {
|
|
596
608
|
role,
|
|
597
609
|
userId: role === "assistant" ? session.selfId : session.userId ?? "未知用户",
|
|
598
|
-
username: role === "assistant" ? "你自己的发言" : session.username || session.userId
|
|
610
|
+
username: role === "assistant" ? "你自己的发言" : session.username || session.userId,
|
|
599
611
|
messageId: session.messageId,
|
|
600
612
|
content,
|
|
613
|
+
contentText: extractTextWithPlaceholders(session, { excludeBotAt: true }),
|
|
614
|
+
...imageUrls.length ? { imageUrls } : {},
|
|
601
615
|
timestamp: session.timestamp ?? Date.now(),
|
|
602
616
|
quote: role === "user" ? buildQuote(session) : void 0
|
|
603
617
|
};
|
|
@@ -635,41 +649,49 @@ function hasAt(session, id) {
|
|
|
635
649
|
return session.elements?.some((element) => element.type === "at" && element.attrs?.["id"] === id) ?? false;
|
|
636
650
|
}
|
|
637
651
|
__name(hasAt, "hasAt");
|
|
652
|
+
var NON_TEXT_PREFIX = new RegExp(`^(?:@\\S+|\\[image:[0-9a-f]{${IMAGE_HASH_LENGTH}}\\]|\\[图片\\]|\\s)*`);
|
|
653
|
+
function stripPrefixKeyword(verbatim, keyword) {
|
|
654
|
+
const prefix = NON_TEXT_PREFIX.exec(verbatim)?.[0] ?? "";
|
|
655
|
+
const rest = verbatim.slice(prefix.length);
|
|
656
|
+
if (!rest.startsWith(keyword)) return null;
|
|
657
|
+
return (prefix + rest.slice(keyword.length)).trim();
|
|
658
|
+
}
|
|
659
|
+
__name(stripPrefixKeyword, "stripPrefixKeyword");
|
|
638
660
|
function evaluateCondition(condition, session) {
|
|
639
661
|
const text = extractText(session);
|
|
640
|
-
const verbatim = extractTextWithPlaceholders(session);
|
|
662
|
+
const verbatim = extractTextWithPlaceholders(session, { excludeBotAt: true });
|
|
641
663
|
switch (condition.type) {
|
|
642
664
|
case "keyword-prefix": {
|
|
643
665
|
const keyword = condition.keywords.find((k) => k && text.startsWith(k));
|
|
644
666
|
if (!keyword) return null;
|
|
645
|
-
const rest = text.slice(keyword.length).trim();
|
|
646
|
-
return { type: "keyword-prefix",
|
|
667
|
+
const rest = verbatim.startsWith(keyword) ? verbatim.slice(keyword.length).trim() : stripPrefixKeyword(verbatim, keyword) ?? text.slice(keyword.length).trim();
|
|
668
|
+
return { type: "keyword-prefix", contentText: rest || verbatim };
|
|
647
669
|
}
|
|
648
670
|
case "keyword-contains": {
|
|
649
671
|
const keyword = condition.keywords.find((k) => k && text.includes(k));
|
|
650
672
|
if (!keyword) return null;
|
|
651
|
-
return { type: "keyword-contains",
|
|
673
|
+
return { type: "keyword-contains", contentText: verbatim };
|
|
652
674
|
}
|
|
653
675
|
case "at-bot": {
|
|
654
676
|
const botId = session.bot?.userId;
|
|
655
677
|
if (!botId) return null;
|
|
656
678
|
if (!session.stripped?.atSelf && !hasAt(session, botId)) return null;
|
|
657
|
-
return { type: "at-bot",
|
|
679
|
+
return { type: "at-bot", contentText: verbatim };
|
|
658
680
|
}
|
|
659
681
|
case "at-user": {
|
|
660
682
|
const isAtAnyUser = condition.userIds.some((id) => hasAt(session, id));
|
|
661
683
|
if (!isAtAnyUser) return null;
|
|
662
|
-
return { type: "at-user",
|
|
684
|
+
return { type: "at-user", contentText: verbatim };
|
|
663
685
|
}
|
|
664
686
|
case "quote-bot": {
|
|
665
687
|
const botId = session.bot?.userId;
|
|
666
688
|
if (!botId || session.quote?.user?.id !== botId) return null;
|
|
667
|
-
return { type: "quote-bot",
|
|
689
|
+
return { type: "quote-bot", contentText: verbatim };
|
|
668
690
|
}
|
|
669
691
|
case "quote-user": {
|
|
670
692
|
const author = session.quote?.user?.id;
|
|
671
693
|
if (!author || !condition.userIds.includes(author)) return null;
|
|
672
|
-
return { type: "quote-user",
|
|
694
|
+
return { type: "quote-user", contentText: verbatim };
|
|
673
695
|
}
|
|
674
696
|
default:
|
|
675
697
|
return null;
|
|
@@ -808,13 +830,7 @@ var RuleScheduler = class {
|
|
|
808
830
|
}
|
|
809
831
|
entries = [];
|
|
810
832
|
sync(rules) {
|
|
811
|
-
|
|
812
|
-
const nextEntries = [];
|
|
813
|
-
for (const rule of rules) {
|
|
814
|
-
const nextRunAt = previous.get(rule.name) ?? this.computeNextRun(rule);
|
|
815
|
-
if (nextRunAt != null) nextEntries.push({ rule, nextRunAt });
|
|
816
|
-
}
|
|
817
|
-
this.entries = nextEntries;
|
|
833
|
+
this.entries = rules.map((rule) => ({ rule, nextRunAt: this.computeNextRun(rule) })).filter((entry) => entry.nextRunAt != null);
|
|
818
834
|
}
|
|
819
835
|
tick() {
|
|
820
836
|
const now = Date.now();
|
|
@@ -864,8 +880,9 @@ var RuleScheduler = class {
|
|
|
864
880
|
|
|
865
881
|
// src/trigger/cooldown.ts
|
|
866
882
|
var BUFFER_CAPACITY = 1024;
|
|
883
|
+
var DENSITY_LANE_PREFIX = "density:";
|
|
867
884
|
function densityLane(index) {
|
|
868
|
-
return
|
|
885
|
+
return `${DENSITY_LANE_PREFIX}${index}`;
|
|
869
886
|
}
|
|
870
887
|
__name(densityLane, "densityLane");
|
|
871
888
|
function laneKeyOf(ruleName, lane) {
|
|
@@ -896,30 +913,33 @@ var TriggerCooldown = class {
|
|
|
896
913
|
});
|
|
897
914
|
}
|
|
898
915
|
isCooling(channelKey, ruleName, lane, now) {
|
|
899
|
-
const state = this.
|
|
916
|
+
const state = this.channels.get(channelKey)?.get(laneKeyOf(ruleName, lane));
|
|
900
917
|
return state != null && now < state.deadline;
|
|
901
918
|
}
|
|
902
|
-
/**
|
|
903
|
-
|
|
904
|
-
if (this.setting?.mode !== "sliding") return;
|
|
905
|
-
const state = this.lanesOf(channelKey).get(laneKeyOf(ruleName, lane));
|
|
906
|
-
if (!state) return;
|
|
919
|
+
/** 滑动窗口顺延:截止推到「now + 最小窗口」,不超过「触发时刻 + 最大窗口」 */
|
|
920
|
+
slide(state, setting, now) {
|
|
907
921
|
state.deadline = Math.min(
|
|
908
|
-
Math.max(state.deadline, now +
|
|
909
|
-
state.fireAt +
|
|
922
|
+
Math.max(state.deadline, now + setting.minMs),
|
|
923
|
+
state.fireAt + setting.maxMs
|
|
910
924
|
);
|
|
911
925
|
}
|
|
912
|
-
/**
|
|
926
|
+
/** 滑动窗口顺延:已到期的通道不复活 */
|
|
927
|
+
extend(channelKey, ruleName, lane, now) {
|
|
928
|
+
const setting = this.setting;
|
|
929
|
+
if (setting?.mode !== "sliding") return;
|
|
930
|
+
const state = this.channels.get(channelKey)?.get(laneKeyOf(ruleName, lane));
|
|
931
|
+
if (!state || now >= state.deadline) return;
|
|
932
|
+
this.slide(state, setting, now);
|
|
933
|
+
}
|
|
934
|
+
/** 该频道所有密度通道由一条过路消息顺延(密度冷却期内的消息积攒驱动窗口滑动);已到期的不复活 */
|
|
913
935
|
extendDensityLanes(channelKey, now) {
|
|
914
|
-
|
|
936
|
+
const setting = this.setting;
|
|
937
|
+
if (setting?.mode !== "sliding") return;
|
|
915
938
|
const lanes = this.channels.get(channelKey);
|
|
916
939
|
if (!lanes) return;
|
|
917
940
|
for (const [key, state] of lanes) {
|
|
918
|
-
if (!key.includes("\ndensity:")) continue;
|
|
919
|
-
state
|
|
920
|
-
Math.max(state.deadline, now + this.setting.minMs),
|
|
921
|
-
state.fireAt + this.setting.maxMs
|
|
922
|
-
);
|
|
941
|
+
if (!key.includes("\ndensity:") || now >= state.deadline) continue;
|
|
942
|
+
this.slide(state, setting, now);
|
|
923
943
|
}
|
|
924
944
|
}
|
|
925
945
|
/** 冷却期内缓存一条命中消息(仅消息通道) */
|
|
@@ -931,22 +951,24 @@ var TriggerCooldown = class {
|
|
|
931
951
|
state.buffer.splice(0, state.buffer.length - BUFFER_CAPACITY);
|
|
932
952
|
}
|
|
933
953
|
}
|
|
934
|
-
/**
|
|
935
|
-
collectExpired(now) {
|
|
954
|
+
/** 到期扫描:取走到期通道(连缓冲一并交出),由调用方决定是否重新登记;限定频道时只扫该频道 */
|
|
955
|
+
collectExpired(now, channelKey) {
|
|
936
956
|
const expired = [];
|
|
937
|
-
|
|
938
|
-
|
|
957
|
+
const targets = channelKey ? [[channelKey, this.channels.get(channelKey)]] : this.channels;
|
|
958
|
+
for (const [key, lanes] of targets) {
|
|
959
|
+
if (!lanes) continue;
|
|
960
|
+
for (const [laneKey, state] of lanes) {
|
|
939
961
|
if (now < state.deadline) continue;
|
|
940
|
-
const separator =
|
|
941
|
-
lanes.delete(
|
|
962
|
+
const separator = laneKey.indexOf("\n");
|
|
963
|
+
lanes.delete(laneKey);
|
|
942
964
|
expired.push({
|
|
943
|
-
channelKey,
|
|
944
|
-
ruleName:
|
|
945
|
-
lane:
|
|
965
|
+
channelKey: key,
|
|
966
|
+
ruleName: laneKey.slice(0, separator),
|
|
967
|
+
lane: laneKey.slice(separator + 1),
|
|
946
968
|
buffer: state.buffer
|
|
947
969
|
});
|
|
948
970
|
}
|
|
949
|
-
if (!lanes.size) this.channels.delete(
|
|
971
|
+
if (!lanes.size) this.channels.delete(key);
|
|
950
972
|
}
|
|
951
973
|
return expired;
|
|
952
974
|
}
|
|
@@ -962,6 +984,29 @@ var TriggerCooldown = class {
|
|
|
962
984
|
|
|
963
985
|
// src/trigger/engine.ts
|
|
964
986
|
var SCHEDULER_TICK_MS = 1e3;
|
|
987
|
+
function collectImageUrls(entries) {
|
|
988
|
+
return entries.flatMap((entry) => entry?.role === "user" ? entry.imageUrls ?? [] : []);
|
|
989
|
+
}
|
|
990
|
+
__name(collectImageUrls, "collectImageUrls");
|
|
991
|
+
function sessionIdentityOf(session) {
|
|
992
|
+
return {
|
|
993
|
+
platform: session.platform,
|
|
994
|
+
selfId: session.selfId,
|
|
995
|
+
channelId: session.channelId,
|
|
996
|
+
guildId: session.guildId ?? session.channelId,
|
|
997
|
+
userId: session.userId
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
__name(sessionIdentityOf, "sessionIdentityOf");
|
|
1001
|
+
function poolIdentityOf(channel) {
|
|
1002
|
+
return {
|
|
1003
|
+
platform: channel.platform,
|
|
1004
|
+
selfId: channel.selfId,
|
|
1005
|
+
channelId: channel.channelId,
|
|
1006
|
+
guildId: channel.guildId
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
__name(poolIdentityOf, "poolIdentityOf");
|
|
965
1010
|
var TriggerEngine = class {
|
|
966
1011
|
constructor(ctx, runner, history, rules, cooldown) {
|
|
967
1012
|
this.ctx = ctx;
|
|
@@ -998,6 +1043,7 @@ var TriggerEngine = class {
|
|
|
998
1043
|
if (this.ctx.bots[session.uid]) return next();
|
|
999
1044
|
if (!session.channelId) return next();
|
|
1000
1045
|
const channelKey = channelKeyOf(session.platform, session.channelId);
|
|
1046
|
+
this.tickCooldown(channelKey);
|
|
1001
1047
|
const entry = buildHistoryEntry(session);
|
|
1002
1048
|
if (entry) {
|
|
1003
1049
|
this.history.recordEntry(channelKey, entry);
|
|
@@ -1013,13 +1059,14 @@ var TriggerEngine = class {
|
|
|
1013
1059
|
}
|
|
1014
1060
|
}
|
|
1015
1061
|
return next(async (nextMiddleware) => {
|
|
1062
|
+
this.tickCooldown(channelKey);
|
|
1016
1063
|
const matched = this.matchRule(session);
|
|
1017
1064
|
if (!matched) return nextMiddleware();
|
|
1018
1065
|
if (this.cooldown.isCooling(channelKey, matched.rule.name, "message", Date.now())) {
|
|
1019
1066
|
this.cooldown.bufferMessage(channelKey, matched.rule.name, {
|
|
1020
1067
|
session,
|
|
1021
1068
|
entry,
|
|
1022
|
-
contentText: matched.condition.
|
|
1069
|
+
contentText: matched.condition.contentText
|
|
1023
1070
|
});
|
|
1024
1071
|
this.cooldown.extend(channelKey, matched.rule.name, "message", Date.now());
|
|
1025
1072
|
return;
|
|
@@ -1050,31 +1097,24 @@ var TriggerEngine = class {
|
|
|
1050
1097
|
this.beginCooldown(channelKeyOf(session.platform, session.channelId), rule.name, "message");
|
|
1051
1098
|
this.run({
|
|
1052
1099
|
session,
|
|
1053
|
-
|
|
1054
|
-
selfId: session.selfId,
|
|
1055
|
-
channelId: session.channelId,
|
|
1056
|
-
guildId: session.guildId ?? session.channelId,
|
|
1057
|
-
userId: session.userId,
|
|
1100
|
+
...sessionIdentityOf(session),
|
|
1058
1101
|
triggerType: condition.type,
|
|
1059
1102
|
rule,
|
|
1060
1103
|
// query 为该条消息的统一格式单行;纯 @ / 纯引用等无文本消息 entry 为空,由请求器占位兜底
|
|
1061
1104
|
queryText: entry ? formatHistoryEntry(entry) : "",
|
|
1062
|
-
contentText: condition.
|
|
1105
|
+
contentText: condition.contentText
|
|
1063
1106
|
});
|
|
1064
1107
|
}
|
|
1065
1108
|
fireDensity(fire, session) {
|
|
1066
1109
|
this.beginCooldown(channelKeyOf(session.platform, session.channelId), fire.rule.name, densityLane(fire.conditionIndex));
|
|
1067
1110
|
this.run({
|
|
1068
1111
|
session,
|
|
1069
|
-
|
|
1070
|
-
selfId: session.selfId,
|
|
1071
|
-
channelId: session.channelId,
|
|
1072
|
-
guildId: session.guildId ?? session.channelId,
|
|
1073
|
-
userId: session.userId,
|
|
1112
|
+
...sessionIdentityOf(session),
|
|
1074
1113
|
triggerType: "density",
|
|
1075
1114
|
rule: fire.rule,
|
|
1076
1115
|
queryText: formatHistoryEntries(fire.entries),
|
|
1077
|
-
contentText: formatHistoryBrief(fire.entries)
|
|
1116
|
+
contentText: formatHistoryBrief(fire.entries),
|
|
1117
|
+
imageUrls: collectImageUrls(fire.entries)
|
|
1078
1118
|
});
|
|
1079
1119
|
}
|
|
1080
1120
|
/** 冷却到期:把缓冲内的触发消息合并为一次触发,session 取首条(供投递与文件上传) */
|
|
@@ -1085,29 +1125,37 @@ var TriggerEngine = class {
|
|
|
1085
1125
|
this.beginCooldown(channelKeyOf(first.session.platform, first.session.channelId), rule.name, "message");
|
|
1086
1126
|
this.run({
|
|
1087
1127
|
session: first.session,
|
|
1088
|
-
|
|
1089
|
-
selfId: first.session.selfId,
|
|
1090
|
-
channelId: first.session.channelId,
|
|
1091
|
-
guildId: first.session.guildId ?? first.session.channelId,
|
|
1092
|
-
userId: first.session.userId,
|
|
1128
|
+
...sessionIdentityOf(first.session),
|
|
1093
1129
|
triggerType: "merged",
|
|
1094
1130
|
rule,
|
|
1095
1131
|
queryText,
|
|
1096
|
-
contentText
|
|
1132
|
+
contentText,
|
|
1133
|
+
// 补齐缓冲内非首条消息的图片(首条由 session 元素提取,重复由上传入口去重)
|
|
1134
|
+
imageUrls: collectImageUrls(buffer.map((item) => item.entry))
|
|
1097
1135
|
});
|
|
1098
1136
|
}
|
|
1099
1137
|
/** 密度冷却到期复查达标:无当前消息,用池身份构造上下文(投递同定时路径) */
|
|
1100
|
-
fireDensityExpiry(rule, conditionIndex, channel) {
|
|
1138
|
+
fireDensityExpiry(rule, conditionIndex, channel, now) {
|
|
1101
1139
|
this.beginCooldown(channelKeyOf(channel.platform, channel.channelId), rule.name, densityLane(conditionIndex));
|
|
1140
|
+
const condition = rule.conditions[conditionIndex];
|
|
1141
|
+
const windowMs = condition.type === "density" ? condition.densityWindowSeconds * 1e3 : 0;
|
|
1142
|
+
let userId;
|
|
1143
|
+
for (let i = channel.entries.length - 1; i >= 0; i--) {
|
|
1144
|
+
const item = channel.entries[i];
|
|
1145
|
+
if (item.role === "user" && now - item.timestamp <= windowMs) {
|
|
1146
|
+
userId = item.userId;
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1102
1150
|
this.run({
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
channelId: channel.channelId,
|
|
1106
|
-
guildId: channel.guildId,
|
|
1151
|
+
...poolIdentityOf(channel),
|
|
1152
|
+
userId,
|
|
1107
1153
|
triggerType: "density",
|
|
1108
1154
|
rule,
|
|
1109
1155
|
queryText: formatHistoryEntries(channel.entries),
|
|
1110
|
-
contentText: formatHistoryBrief(channel.entries)
|
|
1156
|
+
contentText: formatHistoryBrief(channel.entries),
|
|
1157
|
+
imageUrls: collectImageUrls(channel.entries),
|
|
1158
|
+
isDirect: channel.isDirect
|
|
1111
1159
|
});
|
|
1112
1160
|
}
|
|
1113
1161
|
/**
|
|
@@ -1117,22 +1165,21 @@ var TriggerEngine = class {
|
|
|
1117
1165
|
fireSchedule(rule) {
|
|
1118
1166
|
for (const channel of this.pool.takeSchedule(rule)) {
|
|
1119
1167
|
this.run({
|
|
1120
|
-
|
|
1121
|
-
selfId: channel.selfId,
|
|
1122
|
-
channelId: channel.channelId,
|
|
1123
|
-
guildId: channel.guildId,
|
|
1168
|
+
...poolIdentityOf(channel),
|
|
1124
1169
|
triggerType: "schedule",
|
|
1125
1170
|
rule,
|
|
1126
1171
|
queryText: formatHistoryEntries(channel.entries),
|
|
1127
|
-
contentText: formatHistoryBrief(channel.entries)
|
|
1172
|
+
contentText: formatHistoryBrief(channel.entries),
|
|
1173
|
+
imageUrls: collectImageUrls(channel.entries),
|
|
1174
|
+
isDirect: channel.isDirect
|
|
1128
1175
|
});
|
|
1129
1176
|
}
|
|
1130
1177
|
}
|
|
1131
|
-
/**
|
|
1132
|
-
tickCooldown() {
|
|
1178
|
+
/** 扫描冷却到期(带频道只扫该频道,消息路径用;每秒 interval 全量扫描):消息通道合并触发,密度通道复查池 */
|
|
1179
|
+
tickCooldown(channelKey) {
|
|
1133
1180
|
if (!this.cooldown.enabled) return;
|
|
1134
1181
|
const now = Date.now();
|
|
1135
|
-
for (const expired of this.cooldown.collectExpired(now)) {
|
|
1182
|
+
for (const expired of this.cooldown.collectExpired(now, channelKey)) {
|
|
1136
1183
|
this.onCooldownExpired(expired, now);
|
|
1137
1184
|
}
|
|
1138
1185
|
}
|
|
@@ -1143,11 +1190,11 @@ var TriggerEngine = class {
|
|
|
1143
1190
|
if (expired.buffer.length) this.fireMerged(rule, expired.buffer);
|
|
1144
1191
|
return;
|
|
1145
1192
|
}
|
|
1146
|
-
const index = Number(expired.lane.slice(
|
|
1193
|
+
const index = Number(expired.lane.slice(DENSITY_LANE_PREFIX.length));
|
|
1147
1194
|
const condition = rule.conditions[index];
|
|
1148
1195
|
if (condition?.type !== "density") return;
|
|
1149
1196
|
const channel = this.pool.takeDensityChannel(rule, condition, expired.channelKey, now);
|
|
1150
|
-
if (channel) this.fireDensityExpiry(rule, index, channel);
|
|
1197
|
+
if (channel) this.fireDensityExpiry(rule, index, channel, now);
|
|
1151
1198
|
}
|
|
1152
1199
|
};
|
|
1153
1200
|
|
|
@@ -1239,11 +1286,26 @@ function extractFileCandidates(session) {
|
|
|
1239
1286
|
return candidates;
|
|
1240
1287
|
}
|
|
1241
1288
|
__name(extractFileCandidates, "extractFileCandidates");
|
|
1289
|
+
function percentDecodeBytes(input) {
|
|
1290
|
+
const bytes = [];
|
|
1291
|
+
for (let i = 0; i < input.length; i++) {
|
|
1292
|
+
const char = input[i];
|
|
1293
|
+
if (char === "%" && /^[0-9a-fA-F]{2}$/.test(input.slice(i + 1, i + 3))) {
|
|
1294
|
+
bytes.push(parseInt(input.slice(i + 1, i + 3), 16));
|
|
1295
|
+
i += 2;
|
|
1296
|
+
} else {
|
|
1297
|
+
bytes.push(char.charCodeAt(0) & 255);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return Buffer.from(bytes);
|
|
1301
|
+
}
|
|
1302
|
+
__name(percentDecodeBytes, "percentDecodeBytes");
|
|
1242
1303
|
function parseDataUrl(source) {
|
|
1243
|
-
const match = source.match(/^data:([
|
|
1304
|
+
const match = source.match(/^data:([^;,]*)((?:;[^;,]*)*)?,(.+)$/);
|
|
1244
1305
|
if (!match) return null;
|
|
1245
|
-
const mimeType = match[1];
|
|
1246
|
-
const
|
|
1306
|
+
const mimeType = match[1] || "application/octet-stream";
|
|
1307
|
+
const isBase64 = /(?:^|;)base64/.test(match[2] ?? "");
|
|
1308
|
+
const buffer = isBase64 ? Buffer.from(match[3], "base64") : percentDecodeBytes(match[3]);
|
|
1247
1309
|
return { buffer, fileName: `file.${guessExtension(mimeType)}`, mimeType };
|
|
1248
1310
|
}
|
|
1249
1311
|
__name(parseDataUrl, "parseDataUrl");
|
|
@@ -1317,19 +1379,28 @@ async function uploadOne(ctx, client, candidate, difyUser, limit) {
|
|
|
1317
1379
|
}
|
|
1318
1380
|
}
|
|
1319
1381
|
__name(uploadOne, "uploadOne");
|
|
1320
|
-
async function
|
|
1321
|
-
const
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1382
|
+
async function uploadCandidates(ctx, client, candidates, difyUser, limit) {
|
|
1383
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1384
|
+
const unique = candidates.filter((candidate) => {
|
|
1385
|
+
if (seen.has(candidate.source)) return false;
|
|
1386
|
+
seen.add(candidate.source);
|
|
1387
|
+
return true;
|
|
1388
|
+
});
|
|
1389
|
+
const limited = limit.maxCount > 0 ? unique.slice(0, limit.maxCount) : unique;
|
|
1390
|
+
const dropped = [];
|
|
1391
|
+
const markDropped = /* @__PURE__ */ __name((source) => {
|
|
1392
|
+
if (!dropped.includes(source)) dropped.push(source);
|
|
1393
|
+
}, "markDropped");
|
|
1394
|
+
unique.slice(limited.length).forEach((candidate) => markDropped(candidate.source));
|
|
1324
1395
|
const files = [];
|
|
1325
1396
|
for (const candidate of limited) {
|
|
1326
1397
|
const uploaded = await uploadOne(ctx, client, candidate, difyUser, limit);
|
|
1327
1398
|
if (uploaded) files.push(uploaded);
|
|
1328
|
-
else
|
|
1399
|
+
else markDropped(candidate.source);
|
|
1329
1400
|
}
|
|
1330
1401
|
return { files, dropped };
|
|
1331
1402
|
}
|
|
1332
|
-
__name(
|
|
1403
|
+
__name(uploadCandidates, "uploadCandidates");
|
|
1333
1404
|
|
|
1334
1405
|
// src/requester/variables.ts
|
|
1335
1406
|
function resolveSessionField(key, fireCtx, now) {
|
|
@@ -1358,7 +1429,7 @@ function resolveSessionField(key, fireCtx, now) {
|
|
|
1358
1429
|
case "timestamp":
|
|
1359
1430
|
return now;
|
|
1360
1431
|
case "isDirect":
|
|
1361
|
-
return session?.isDirect ?? false;
|
|
1432
|
+
return session?.isDirect ?? fireCtx.isDirect ?? false;
|
|
1362
1433
|
default:
|
|
1363
1434
|
return "";
|
|
1364
1435
|
}
|
|
@@ -1440,7 +1511,7 @@ ${line}` : line;
|
|
|
1440
1511
|
__name(splitText, "splitText");
|
|
1441
1512
|
function renderReply(input) {
|
|
1442
1513
|
const messages = [];
|
|
1443
|
-
const segments = splitText(input.text, input.config.maxSegmentLength
|
|
1514
|
+
const segments = splitText(input.text, input.config.maxSegmentLength);
|
|
1444
1515
|
segments.forEach((segment, index) => {
|
|
1445
1516
|
const elements = [];
|
|
1446
1517
|
if (index === 0 && input.quoteMessageId) {
|
|
@@ -1496,6 +1567,7 @@ function extractTextOutputs(outputs) {
|
|
|
1496
1567
|
__name(extractTextOutputs, "extractTextOutputs");
|
|
1497
1568
|
function parseWorkflowResponse(response) {
|
|
1498
1569
|
const data = response.data;
|
|
1570
|
+
if (!data) throw new DifyApiError("workflow 响应缺少 data", 200);
|
|
1499
1571
|
if (data.error) throw new DifyApiError(data.error, 200);
|
|
1500
1572
|
if (data.status && !SUCCESS_STATUSES.includes(data.status)) {
|
|
1501
1573
|
throw new DifyApiError(`workflow ${data.status}`, 200);
|
|
@@ -1538,11 +1610,13 @@ var RequestRunner = class {
|
|
|
1538
1610
|
uploadedFiles: []
|
|
1539
1611
|
};
|
|
1540
1612
|
const upload = resolveUploadConfig(this.config);
|
|
1541
|
-
if (upload.enabled
|
|
1542
|
-
const
|
|
1613
|
+
if (upload.enabled) {
|
|
1614
|
+
const sessionCandidates = fireCtx.session ? extractFileCandidates(fireCtx.session) : [];
|
|
1615
|
+
const urlCandidates = (fireCtx.imageUrls ?? []).filter((url) => !sessionCandidates.some((candidate) => candidate.source === url)).map((url) => ({ source: url, type: "image" }));
|
|
1616
|
+
const { files, dropped } = await uploadCandidates(
|
|
1543
1617
|
this.ctx,
|
|
1544
1618
|
client,
|
|
1545
|
-
|
|
1619
|
+
[...sessionCandidates, ...urlCandidates],
|
|
1546
1620
|
requestCtx.difyUser,
|
|
1547
1621
|
{ maxCount: upload.maxCount, maxSizeMb: upload.maxMb }
|
|
1548
1622
|
);
|
|
@@ -1566,17 +1640,20 @@ var RequestRunner = class {
|
|
|
1566
1640
|
this.ctx.logger.warn("规则 %s 的请求未返回任何内容", rule.name);
|
|
1567
1641
|
return;
|
|
1568
1642
|
}
|
|
1569
|
-
const quote = resolveQuoteConfig(this.config);
|
|
1570
|
-
const elapsedSeconds = (Date.now() - startedAt) / 1e3;
|
|
1571
|
-
const shouldQuote = quote.enabled && elapsedSeconds >= quote.minElapsedSeconds && isMessageTrigger(fireCtx.triggerType);
|
|
1572
1643
|
const messages = renderReply({
|
|
1573
1644
|
text: reply.text,
|
|
1574
1645
|
files: reply.files,
|
|
1575
|
-
quoteMessageId:
|
|
1646
|
+
quoteMessageId: this.shouldQuoteReply(fireCtx, startedAt) ? fireCtx.session?.messageId : void 0,
|
|
1576
1647
|
config: this.config
|
|
1577
1648
|
});
|
|
1578
1649
|
await this.deliver(fireCtx, messages);
|
|
1579
1650
|
}
|
|
1651
|
+
/** 引用判定:开关开启、回复耗时达到阈值、且由消息触发(定时与密度没有单一触发消息) */
|
|
1652
|
+
shouldQuoteReply(fireCtx, startedAt) {
|
|
1653
|
+
const quote = resolveQuoteConfig(this.config);
|
|
1654
|
+
if (!quote.enabled) return false;
|
|
1655
|
+
return (Date.now() - startedAt) / 1e3 >= quote.minElapsedSeconds && isMessageTrigger(fireCtx.triggerType);
|
|
1656
|
+
}
|
|
1580
1657
|
/** 按应用类型分流:workflow/completion 无会话,chat 系续接 conversation_id */
|
|
1581
1658
|
async dispatch(app, client, fireCtx, requestCtx) {
|
|
1582
1659
|
const base = {
|
|
@@ -1614,7 +1691,8 @@ var RequestRunner = class {
|
|
|
1614
1691
|
}
|
|
1615
1692
|
return;
|
|
1616
1693
|
}
|
|
1617
|
-
const
|
|
1694
|
+
const bots = Object.values(this.ctx.bots);
|
|
1695
|
+
const bot = bots.find((b) => b.platform === fireCtx.platform && b.selfId === fireCtx.selfId) ?? bots.find((b) => b.platform === fireCtx.platform);
|
|
1618
1696
|
if (!bot) {
|
|
1619
1697
|
this.ctx.logger.warn("平台 %s 上没有可用 bot,无法发送主动消息", fireCtx.platform);
|
|
1620
1698
|
return;
|
|
@@ -1636,7 +1714,7 @@ var ConversationStore = class {
|
|
|
1636
1714
|
}
|
|
1637
1715
|
/** 粒度归一:bindingKey 与 difyUser 共用,保证两者始终对齐 */
|
|
1638
1716
|
resolveScopeDimension(rule, fireCtx) {
|
|
1639
|
-
if (fireCtx
|
|
1717
|
+
if (this.isDirectOf(fireCtx) || rule.conversationScope === "per-user") return "user";
|
|
1640
1718
|
switch (rule.conversationScope) {
|
|
1641
1719
|
case "global":
|
|
1642
1720
|
return "global";
|
|
@@ -1646,13 +1724,17 @@ var ConversationStore = class {
|
|
|
1646
1724
|
return "guild";
|
|
1647
1725
|
}
|
|
1648
1726
|
}
|
|
1727
|
+
/** 私聊判定:有 session 读 session,无 session 的主动触发读池透传的标记 */
|
|
1728
|
+
isDirectOf(fireCtx) {
|
|
1729
|
+
return fireCtx.session?.isDirect ?? fireCtx.isDirect ?? false;
|
|
1730
|
+
}
|
|
1649
1731
|
/** bindingKey = 应用 ID + 粒度 + 平台身份 */
|
|
1650
1732
|
computeBindingKey(appId, rule, fireCtx) {
|
|
1651
1733
|
const base = `${appId}:${fireCtx.platform}:${fireCtx.selfId}`;
|
|
1652
1734
|
switch (this.resolveScopeDimension(rule, fireCtx)) {
|
|
1653
1735
|
// 群内按「群+用户」区分;私聊用 private 段占位
|
|
1654
1736
|
case "user":
|
|
1655
|
-
return fireCtx
|
|
1737
|
+
return this.isDirectOf(fireCtx) ? `${base}:private:${fireCtx.userId ?? fireCtx.channelId}` : `${base}:user:${fireCtx.guildId ?? fireCtx.channelId}:${fireCtx.userId ?? fireCtx.channelId}`;
|
|
1656
1738
|
case "guild":
|
|
1657
1739
|
return `${base}:guild:${fireCtx.guildId ?? fireCtx.channelId}`;
|
|
1658
1740
|
case "channel":
|
|
@@ -1669,7 +1751,7 @@ var ConversationStore = class {
|
|
|
1669
1751
|
computeDifyUser(rule, fireCtx) {
|
|
1670
1752
|
switch (this.resolveScopeDimension(rule, fireCtx)) {
|
|
1671
1753
|
case "user":
|
|
1672
|
-
return fireCtx
|
|
1754
|
+
return this.isDirectOf(fireCtx) ? `koishi:${fireCtx.platform}:private:${fireCtx.userId ?? fireCtx.channelId}` : `koishi:${fireCtx.platform}:${fireCtx.guildId ?? fireCtx.channelId}:${fireCtx.userId ?? fireCtx.channelId}`;
|
|
1673
1755
|
case "guild":
|
|
1674
1756
|
return `koishi:${fireCtx.platform}:${fireCtx.guildId ?? fireCtx.channelId}`;
|
|
1675
1757
|
case "channel":
|
|
@@ -1709,16 +1791,25 @@ var ConversationStore = class {
|
|
|
1709
1791
|
/**
|
|
1710
1792
|
* 重置匹配的绑定:本地删行 + 尽力删除 Dify 侧会话(失败仅记录)。
|
|
1711
1793
|
* 表已无 guildKey 列,按频道匹配时全表读回后解析 bindingKey 过滤(行数量级很小)。
|
|
1794
|
+
* 覆盖 guild / user(群内)/ channel 段与私聊执行时的 private 段;
|
|
1795
|
+
* global 段跨频道,不随单频道重置。
|
|
1712
1796
|
*/
|
|
1713
1797
|
async resetWhere(filter) {
|
|
1714
1798
|
const rows = await this.ctx.database.get("dify_conversation", {});
|
|
1799
|
+
const splitKey = /* @__PURE__ */ __name((key) => {
|
|
1800
|
+
const separator = key.indexOf(":");
|
|
1801
|
+
return separator < 0 ? [key, ""] : [key.slice(0, separator), key.slice(separator + 1)];
|
|
1802
|
+
}, "splitKey");
|
|
1803
|
+
const [guildPlatform, guildId] = splitKey(filter.guildKey ?? "");
|
|
1804
|
+
const [channelPlatform, channelId] = splitKey(filter.channelKey ?? "");
|
|
1715
1805
|
const matched = rows.filter((row) => {
|
|
1716
1806
|
if (filter.bindingKey) return row.bindingKey === filter.bindingKey;
|
|
1717
1807
|
const { platform, scope, ids } = this.parseBindingKey(row.bindingKey);
|
|
1718
|
-
const [guildPlatform, guildId] = (filter.guildKey ?? "").split(":");
|
|
1719
1808
|
if (platform !== guildPlatform) return false;
|
|
1720
1809
|
if (scope === "guild") return ids[0] === guildId;
|
|
1721
1810
|
if (scope === "user") return ids.length === 2 ? ids[0] === guildId : false;
|
|
1811
|
+
if (scope === "channel") return platform === channelPlatform && ids[0] === channelId;
|
|
1812
|
+
if (scope === "private") return filter.isDirect === true && ids[0] === filter.userId;
|
|
1722
1813
|
return false;
|
|
1723
1814
|
});
|
|
1724
1815
|
let reset = 0;
|
|
@@ -1761,8 +1852,12 @@ __name(defineTables, "defineTables");
|
|
|
1761
1852
|
// src/commands/misc.ts
|
|
1762
1853
|
function registerMiscCommands(parent, conversationStore) {
|
|
1763
1854
|
parent.subcommand(".conv.reset", { authority: 3 }).action(async ({ session }) => {
|
|
1764
|
-
const
|
|
1765
|
-
|
|
1855
|
+
const reset = await conversationStore.resetWhere({
|
|
1856
|
+
guildKey: guildKeyOf(session.platform, session.guildId, session.channelId),
|
|
1857
|
+
channelKey: channelKeyOf(session.platform, session.channelId),
|
|
1858
|
+
isDirect: session.isDirect,
|
|
1859
|
+
userId: session.userId
|
|
1860
|
+
});
|
|
1766
1861
|
if (!reset) return session.text("dify-ai.commands.conv-empty");
|
|
1767
1862
|
return session.text("dify-ai.commands.conv-reset", [reset]);
|
|
1768
1863
|
});
|
|
@@ -1772,6 +1867,7 @@ __name(registerMiscCommands, "registerMiscCommands");
|
|
|
1772
1867
|
// src/index.ts
|
|
1773
1868
|
var name = "dify-ai";
|
|
1774
1869
|
var inject = { required: ["database"] };
|
|
1870
|
+
var PROBE_TIMEOUT_MS = 1e4;
|
|
1775
1871
|
function apply(ctx, config) {
|
|
1776
1872
|
ctx.i18n.define("zh-CN", zh_CN_default);
|
|
1777
1873
|
ctx.i18n.define("en-US", en_US_default);
|
|
@@ -1782,18 +1878,20 @@ function apply(ctx, config) {
|
|
|
1782
1878
|
const conversationStore = new ConversationStore(ctx, apps);
|
|
1783
1879
|
const runner = new RequestRunner(ctx, config, conversationStore, history, apps, appTypes);
|
|
1784
1880
|
const engine = new TriggerEngine(ctx, runner, history, rules, cooldown);
|
|
1785
|
-
ctx.on("ready",
|
|
1786
|
-
await probeAppTypes(ctx, config, apps, appTypes);
|
|
1881
|
+
ctx.on("ready", () => {
|
|
1787
1882
|
engine.start();
|
|
1883
|
+
void probeAppTypes(ctx, apps, appTypes);
|
|
1788
1884
|
});
|
|
1789
1885
|
const admin = ctx.command("dify", { authority: 3 });
|
|
1790
1886
|
registerMiscCommands(admin, conversationStore);
|
|
1791
1887
|
}
|
|
1792
1888
|
__name(apply, "apply");
|
|
1793
|
-
async function probeAppTypes(ctx,
|
|
1889
|
+
async function probeAppTypes(ctx, apps, appTypes) {
|
|
1890
|
+
const autoApps = [...apps.values()].filter((app) => app.appType === "auto");
|
|
1891
|
+
if (!autoApps.length) return;
|
|
1794
1892
|
await Promise.all(
|
|
1795
|
-
|
|
1796
|
-
const client = createDifyClient(ctx, app,
|
|
1893
|
+
autoApps.map(async (app) => {
|
|
1894
|
+
const client = createDifyClient(ctx, app, PROBE_TIMEOUT_MS);
|
|
1797
1895
|
if (!client) return;
|
|
1798
1896
|
try {
|
|
1799
1897
|
const info = await client.getAppInfo();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-dify-ai",
|
|
3
3
|
"description": "Invoke Dify apps (chatflow / workflow / completion) in Koishi with triggers and conversation bindings",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7",
|
|
5
5
|
"author": "PinkElysia",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/PinkElysiaDev/dify-ai-plugin-for-Koishi#readme",
|