koishi-plugin-dify-ai 0.1.2 → 0.1.4
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/config.d.ts +4 -1
- package/lib/history.d.ts +1 -1
- package/lib/index.js +107 -94
- package/lib/trigger/conditions.d.ts +1 -1
- package/lib/trigger/engine.d.ts +3 -4
- package/lib/trigger/pool.d.ts +35 -0
- package/lib/types.d.ts +3 -1
- package/package.json +1 -1
- package/readme.md +1 -1
- package/lib/commands/app.d.ts +0 -4
- package/lib/commands/rule.d.ts +0 -4
- package/lib/services/app-service.d.ts +0 -17
- package/lib/trigger/density.d.ts +0 -15
- package/lib/trigger/digest.d.ts +0 -26
package/lib/config.d.ts
CHANGED
|
@@ -35,11 +35,14 @@ export type ConditionEntry = {
|
|
|
35
35
|
densityCount?: number;
|
|
36
36
|
densityWindowSeconds?: number;
|
|
37
37
|
};
|
|
38
|
-
/**
|
|
38
|
+
/** 变量映射的条件分支形态:先选来源分支,再填该分支的字段(历史消息是独立分支) */
|
|
39
39
|
export type VariableEntry = {
|
|
40
40
|
source: 'session';
|
|
41
41
|
field: string;
|
|
42
42
|
key: string;
|
|
43
|
+
} | {
|
|
44
|
+
source: 'history';
|
|
45
|
+
field: string;
|
|
43
46
|
limit?: number;
|
|
44
47
|
} | {
|
|
45
48
|
source: 'meta';
|
package/lib/history.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface HistoryEntry {
|
|
|
24
24
|
export declare function formatHistoryEntry(entry: HistoryEntry): string;
|
|
25
25
|
/** 多条消息逐行拼接 */
|
|
26
26
|
export declare function formatHistoryEntries(entries: HistoryEntry[]): string;
|
|
27
|
-
/** 从会话消息构造记录条目(历史缓冲 /
|
|
27
|
+
/** 从会话消息构造记录条目(历史缓冲 / 消息池共用);assistant 行标注为机器人自己的发言 */
|
|
28
28
|
export declare function buildHistoryEntry(session: Session, role?: 'user' | 'assistant'): HistoryEntry | null;
|
|
29
29
|
/**
|
|
30
30
|
* 每个频道在内存中保留最近消息(含 bot 自己的回复),
|
package/lib/index.js
CHANGED
|
@@ -103,8 +103,7 @@ var sessionKeySchema = import_koishi.Schema.union([
|
|
|
103
103
|
import_koishi.Schema.const("quoteAuthorId").description("被引用作者"),
|
|
104
104
|
import_koishi.Schema.const("username").description("用户名"),
|
|
105
105
|
import_koishi.Schema.const("timestamp").description("触发时间"),
|
|
106
|
-
import_koishi.Schema.const("isDirect").description("是否私聊")
|
|
107
|
-
import_koishi.Schema.const("history").description("历史消息")
|
|
106
|
+
import_koishi.Schema.const("isDirect").description("是否私聊")
|
|
108
107
|
]);
|
|
109
108
|
var metaKeySchema = import_koishi.Schema.union([
|
|
110
109
|
import_koishi.Schema.const("triggerType").description("触发类型"),
|
|
@@ -112,20 +111,16 @@ var metaKeySchema = import_koishi.Schema.union([
|
|
|
112
111
|
import_koishi.Schema.const("triggerTime").description("触发时间")
|
|
113
112
|
]);
|
|
114
113
|
var variableSchema = import_koishi.Schema.union([
|
|
115
|
-
import_koishi.Schema.
|
|
116
|
-
import_koishi.Schema.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
import_koishi.Schema.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}).default({ key: "history", limit: 0 }),
|
|
126
|
-
import_koishi.Schema.object({})
|
|
127
|
-
])
|
|
128
|
-
]).default({ source: "session", field: "", key: "content" }).description("会话字段"),
|
|
114
|
+
import_koishi.Schema.object({
|
|
115
|
+
source: import_koishi.Schema.const("session").required(),
|
|
116
|
+
field: import_koishi.Schema.string().required().description("字段名"),
|
|
117
|
+
key: sessionKeySchema.default("content").description("变量")
|
|
118
|
+
}).default({ source: "session", field: "", key: "content" }).description("会话字段"),
|
|
119
|
+
import_koishi.Schema.object({
|
|
120
|
+
source: import_koishi.Schema.const("history").required(),
|
|
121
|
+
field: import_koishi.Schema.string().required().description("字段名"),
|
|
122
|
+
limit: import_koishi.Schema.natural().default(0).min(0).max(100).description("条数,0 用全局")
|
|
123
|
+
}).default({ source: "history", field: "", limit: 0 }).description("历史消息"),
|
|
129
124
|
import_koishi.Schema.object({
|
|
130
125
|
source: import_koishi.Schema.const("meta").required(),
|
|
131
126
|
field: import_koishi.Schema.string().required().description("字段名"),
|
|
@@ -277,10 +272,11 @@ function toVariable(entry) {
|
|
|
277
272
|
if (entry.source === "meta") {
|
|
278
273
|
return { field, source: { type: "meta", key: entry.key || "triggerType" } };
|
|
279
274
|
}
|
|
280
|
-
if (entry.
|
|
275
|
+
if (entry.source === "history") {
|
|
281
276
|
const limit = Number(entry.limit);
|
|
282
277
|
return { field, source: { type: "history", ...limit > 0 ? { limit } : {} } };
|
|
283
278
|
}
|
|
279
|
+
if (entry.key === "history") return null;
|
|
284
280
|
return { field, source: { type: "session", key: entry.key || "content" } };
|
|
285
281
|
}
|
|
286
282
|
__name(toVariable, "toVariable");
|
|
@@ -467,9 +463,9 @@ function formatTimestamp(timestamp) {
|
|
|
467
463
|
}
|
|
468
464
|
__name(formatTimestamp, "formatTimestamp");
|
|
469
465
|
function formatHistoryEntry(entry) {
|
|
470
|
-
const quote = entry.quote ? `(引用了 ${formatTimestamp(entry.quote.timestamp)} ${entry.quote.username} (${entry.quote.userId}) 说:${entry.quote.content})` : "";
|
|
466
|
+
const quote = entry.quote ? `(引用了 [${formatTimestamp(entry.quote.timestamp)}] ${entry.quote.username} (${entry.quote.userId}) 说:${entry.quote.content})` : "";
|
|
471
467
|
const messageId = entry.messageId ? ` [msg:${entry.messageId}]` : "";
|
|
472
|
-
return
|
|
468
|
+
return `[${formatTimestamp(entry.timestamp)}] ${entry.username} (${entry.userId})${messageId}${quote}${quote ? "" : " "}说:${entry.content}`;
|
|
473
469
|
}
|
|
474
470
|
__name(formatHistoryEntry, "formatHistoryEntry");
|
|
475
471
|
function formatHistoryEntries(entries) {
|
|
@@ -493,7 +489,7 @@ function buildHistoryEntry(session, role = "user") {
|
|
|
493
489
|
return {
|
|
494
490
|
role,
|
|
495
491
|
userId: role === "assistant" ? session.selfId : session.userId ?? "未知用户",
|
|
496
|
-
username: role === "assistant" ? "
|
|
492
|
+
username: role === "assistant" ? "你自己的发言" : session.username || session.userId || "用户",
|
|
497
493
|
messageId: session.messageId,
|
|
498
494
|
content,
|
|
499
495
|
timestamp: session.timestamp ?? Date.now(),
|
|
@@ -600,73 +596,81 @@ function guildKeyOf(platform, guildId, channelId) {
|
|
|
600
596
|
}
|
|
601
597
|
__name(guildKeyOf, "guildKeyOf");
|
|
602
598
|
|
|
603
|
-
// src/trigger/
|
|
604
|
-
var
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
599
|
+
// src/trigger/pool.ts
|
|
600
|
+
var POOL_CAPACITY = 256;
|
|
601
|
+
function scopeSessionOf(pool, entry) {
|
|
602
|
+
return {
|
|
603
|
+
platform: pool.platform,
|
|
604
|
+
selfId: pool.selfId,
|
|
605
|
+
channelId: pool.channelId,
|
|
606
|
+
guildId: pool.guildId,
|
|
607
|
+
userId: entry.userId,
|
|
608
|
+
isDirect: pool.isDirect
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
__name(scopeSessionOf, "scopeSessionOf");
|
|
612
|
+
var MessagePool = class {
|
|
608
613
|
static {
|
|
609
|
-
__name(this, "
|
|
614
|
+
__name(this, "MessagePool");
|
|
615
|
+
}
|
|
616
|
+
channels = /* @__PURE__ */ new Map();
|
|
617
|
+
/** 一条消息(含 bot 自己的发言)入池,全量记录不做规则过滤 */
|
|
618
|
+
record(session, entry) {
|
|
619
|
+
const key = channelKeyOf(session.platform, session.channelId);
|
|
620
|
+
let pool = this.channels.get(key);
|
|
621
|
+
if (!pool) {
|
|
622
|
+
pool = {
|
|
623
|
+
platform: session.platform,
|
|
624
|
+
channelId: session.channelId,
|
|
625
|
+
guildId: session.guildId,
|
|
626
|
+
selfId: session.selfId,
|
|
627
|
+
isDirect: session.isDirect,
|
|
628
|
+
entries: []
|
|
629
|
+
};
|
|
630
|
+
this.channels.set(key, pool);
|
|
631
|
+
}
|
|
632
|
+
pool.selfId = session.selfId;
|
|
633
|
+
pool.guildId = session.guildId;
|
|
634
|
+
pool.isDirect = session.isDirect;
|
|
635
|
+
pool.entries.push(entry);
|
|
636
|
+
if (pool.entries.length > POOL_CAPACITY) {
|
|
637
|
+
pool.entries.splice(0, pool.entries.length - POOL_CAPACITY);
|
|
638
|
+
}
|
|
610
639
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
640
|
+
/**
|
|
641
|
+
* 新消息入池后的密度评估:时效内(相对触发消息)且通过规则作用域的真人条数达到阈值。
|
|
642
|
+
* 触发即取走并清空该频道全池(bot 行不推高密度,防自激励);池清空后其余规则无从触发。
|
|
643
|
+
*/
|
|
644
|
+
checkDensity(rules, session) {
|
|
645
|
+
const key = channelKeyOf(session.platform, session.channelId);
|
|
646
|
+
const pool = this.channels.get(key);
|
|
647
|
+
if (!pool) return [];
|
|
648
|
+
const now = session.timestamp ?? Date.now();
|
|
614
649
|
for (const rule of rules) {
|
|
615
650
|
for (const condition of rule.conditions) {
|
|
616
651
|
if (condition.type !== "density") continue;
|
|
617
652
|
const windowMs = condition.densityWindowSeconds * 1e3;
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (
|
|
622
|
-
this.
|
|
623
|
-
|
|
624
|
-
} else {
|
|
625
|
-
this.windows.set(key, entries);
|
|
653
|
+
const count = pool.entries.filter(
|
|
654
|
+
(item) => item.role === "user" && now - item.timestamp <= windowMs && matchScope(rule.scope, scopeSessionOf(pool, item))
|
|
655
|
+
).length;
|
|
656
|
+
if (count >= condition.densityCount) {
|
|
657
|
+
this.channels.delete(key);
|
|
658
|
+
return [{ rule, entries: pool.entries }];
|
|
626
659
|
}
|
|
627
660
|
}
|
|
628
661
|
}
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
/** 把一条消息记入所有 schedule 规则(作用域匹配)的窗口 */
|
|
640
|
-
record(rules, session, entry) {
|
|
641
|
-
for (const rule of rules) {
|
|
642
|
-
if (!rule.conditions.some((condition) => condition.type === "schedule")) continue;
|
|
643
|
-
if (!matchScope(rule.scope, session)) continue;
|
|
644
|
-
const key = channelKeyOf(session.platform, session.channelId);
|
|
645
|
-
let channels = this.windows.get(rule.name);
|
|
646
|
-
if (!channels) {
|
|
647
|
-
channels = /* @__PURE__ */ new Map();
|
|
648
|
-
this.windows.set(rule.name, channels);
|
|
649
|
-
}
|
|
650
|
-
let digest = channels.get(key);
|
|
651
|
-
if (!digest) {
|
|
652
|
-
digest = {
|
|
653
|
-
platform: session.platform,
|
|
654
|
-
channelId: session.channelId,
|
|
655
|
-
guildId: session.guildId,
|
|
656
|
-
selfId: session.selfId,
|
|
657
|
-
entries: []
|
|
658
|
-
};
|
|
659
|
-
channels.set(key, digest);
|
|
660
|
-
}
|
|
661
|
-
digest.entries.push(entry);
|
|
662
|
+
return [];
|
|
663
|
+
}
|
|
664
|
+
/** 定时到点:取走该规则作用域内有消息的所有频道(整池清空,含未过作用域的条目) */
|
|
665
|
+
takeSchedule(rule) {
|
|
666
|
+
const taken = [];
|
|
667
|
+
for (const [key, pool] of this.channels) {
|
|
668
|
+
const matched = pool.entries.some((entry) => matchScope(rule.scope, scopeSessionOf(pool, entry)));
|
|
669
|
+
if (!matched) continue;
|
|
670
|
+
taken.push(pool);
|
|
671
|
+
this.channels.delete(key);
|
|
662
672
|
}
|
|
663
|
-
|
|
664
|
-
/** 取走规则窗口内的全部非空频道缓冲(并清空该规则的窗口) */
|
|
665
|
-
take(rule) {
|
|
666
|
-
const channels = this.windows.get(rule.name);
|
|
667
|
-
this.windows.delete(rule.name);
|
|
668
|
-
if (!channels) return [];
|
|
669
|
-
return [...channels.values()].filter((digest) => digest.entries.length > 0);
|
|
673
|
+
return taken;
|
|
670
674
|
}
|
|
671
675
|
};
|
|
672
676
|
|
|
@@ -746,21 +750,22 @@ var TriggerEngine = class {
|
|
|
746
750
|
this.history = history;
|
|
747
751
|
this.rules = rules;
|
|
748
752
|
this.scheduler = new RuleScheduler(ctx, (rule) => this.fireSchedule(rule));
|
|
749
|
-
this.
|
|
750
|
-
this.digest = new MessageDigest();
|
|
753
|
+
this.pool = new MessagePool();
|
|
751
754
|
}
|
|
752
755
|
static {
|
|
753
756
|
__name(this, "TriggerEngine");
|
|
754
757
|
}
|
|
755
758
|
scheduler;
|
|
756
|
-
|
|
757
|
-
digest;
|
|
759
|
+
pool;
|
|
758
760
|
start() {
|
|
759
761
|
this.ctx.middleware((session, next) => this.handleMessage(session, next));
|
|
760
762
|
this.ctx.setInterval(() => this.scheduler.tick(), SCHEDULER_TICK_MS);
|
|
761
763
|
this.ctx.on("send", (session) => {
|
|
762
764
|
const entry = session.channelId ? buildHistoryEntry(session, "assistant") : null;
|
|
763
|
-
if (entry)
|
|
765
|
+
if (!entry) return;
|
|
766
|
+
const key = channelKeyOf(session.platform, session.channelId);
|
|
767
|
+
this.history.recordEntry(key, entry);
|
|
768
|
+
this.pool.record(session, entry);
|
|
764
769
|
});
|
|
765
770
|
this.scheduler.sync(this.rules);
|
|
766
771
|
this.ctx.logger.info("已加载 %d 条启用规则", this.rules.length);
|
|
@@ -771,13 +776,15 @@ var TriggerEngine = class {
|
|
|
771
776
|
const entry = buildHistoryEntry(session);
|
|
772
777
|
if (entry) {
|
|
773
778
|
this.history.recordEntry(channelKeyOf(session.platform, session.channelId), entry);
|
|
774
|
-
this.
|
|
775
|
-
this.
|
|
779
|
+
this.pool.record(session, entry);
|
|
780
|
+
for (const fire of this.pool.checkDensity(this.rules, session)) {
|
|
781
|
+
this.fireDensity(fire.rule, session, fire.entries);
|
|
782
|
+
}
|
|
776
783
|
}
|
|
777
784
|
return next(async (nextMiddleware) => {
|
|
778
785
|
const matched = this.matchRule(session);
|
|
779
786
|
if (!matched) return nextMiddleware();
|
|
780
|
-
this.fireMessage(matched.rule, session, matched.condition);
|
|
787
|
+
this.fireMessage(matched.rule, session, matched.condition, entry);
|
|
781
788
|
});
|
|
782
789
|
}
|
|
783
790
|
matchRule(session) {
|
|
@@ -795,7 +802,7 @@ var TriggerEngine = class {
|
|
|
795
802
|
run(fireCtx) {
|
|
796
803
|
void this.runner.run(fireCtx).catch((error) => this.ctx.logger.warn(error));
|
|
797
804
|
}
|
|
798
|
-
fireMessage(rule, session, condition) {
|
|
805
|
+
fireMessage(rule, session, condition, entry) {
|
|
799
806
|
this.run({
|
|
800
807
|
session,
|
|
801
808
|
platform: session.platform,
|
|
@@ -805,10 +812,13 @@ var TriggerEngine = class {
|
|
|
805
812
|
userId: session.userId,
|
|
806
813
|
triggerType: condition.type,
|
|
807
814
|
rule,
|
|
808
|
-
|
|
815
|
+
// query 为该条消息的统一格式单行;纯 @ / 纯引用等无文本消息 entry 为空,由请求器占位兜底
|
|
816
|
+
queryText: entry ? formatHistoryEntry(entry) : "",
|
|
817
|
+
contentText: condition.queryText
|
|
809
818
|
});
|
|
810
819
|
}
|
|
811
820
|
fireDensity(rule, session, entries) {
|
|
821
|
+
const windowText = formatHistoryEntries(entries);
|
|
812
822
|
this.run({
|
|
813
823
|
session,
|
|
814
824
|
platform: session.platform,
|
|
@@ -818,15 +828,17 @@ var TriggerEngine = class {
|
|
|
818
828
|
userId: session.userId,
|
|
819
829
|
triggerType: "density",
|
|
820
830
|
rule,
|
|
821
|
-
queryText:
|
|
831
|
+
queryText: windowText,
|
|
832
|
+
contentText: windowText
|
|
822
833
|
});
|
|
823
834
|
}
|
|
824
835
|
/**
|
|
825
|
-
*
|
|
826
|
-
*
|
|
836
|
+
* 定时触发(消息摘要):把消息池内该规则作用域有消息的每个频道各发一次请求(取走即清空全池)。
|
|
837
|
+
* 频道身份取自池内最近一条消息;无消息的频道自然跳过。
|
|
827
838
|
*/
|
|
828
839
|
fireSchedule(rule) {
|
|
829
|
-
for (const channel of this.
|
|
840
|
+
for (const channel of this.pool.takeSchedule(rule)) {
|
|
841
|
+
const windowText = formatHistoryEntries(channel.entries);
|
|
830
842
|
this.run({
|
|
831
843
|
platform: channel.platform,
|
|
832
844
|
selfId: channel.selfId,
|
|
@@ -834,7 +846,8 @@ var TriggerEngine = class {
|
|
|
834
846
|
guildId: channel.guildId,
|
|
835
847
|
triggerType: "schedule",
|
|
836
848
|
rule,
|
|
837
|
-
queryText:
|
|
849
|
+
queryText: windowText,
|
|
850
|
+
contentText: windowText
|
|
838
851
|
});
|
|
839
852
|
}
|
|
840
853
|
}
|
|
@@ -1028,7 +1041,7 @@ function resolveSessionField(key, fireCtx, now) {
|
|
|
1028
1041
|
case "messageId":
|
|
1029
1042
|
return session?.messageId ?? "";
|
|
1030
1043
|
case "content":
|
|
1031
|
-
return fireCtx.
|
|
1044
|
+
return fireCtx.contentText;
|
|
1032
1045
|
case "quoteContent":
|
|
1033
1046
|
return parseTextSafe(session?.quote?.content);
|
|
1034
1047
|
case "quoteAuthorId":
|
|
@@ -1146,7 +1159,7 @@ function errorMessage(error) {
|
|
|
1146
1159
|
__name(errorMessage, "errorMessage");
|
|
1147
1160
|
|
|
1148
1161
|
// src/requester/runner.ts
|
|
1149
|
-
var FALLBACK_QUERY = "
|
|
1162
|
+
var FALLBACK_QUERY = "(本次触发无文本内容,请参考变量中的上下文)";
|
|
1150
1163
|
var SUCCESS_STATUSES = ["succeeded", "partial-succeeded"];
|
|
1151
1164
|
var FALLBACK_APP_TYPE = "chat";
|
|
1152
1165
|
function isFileObject(value) {
|
|
@@ -2,7 +2,7 @@ import { Session } from 'koishi';
|
|
|
2
2
|
import { MessageTriggerType, TriggerCondition, TriggerScope } from '../types';
|
|
3
3
|
export interface ConditionMatched {
|
|
4
4
|
type: MessageTriggerType;
|
|
5
|
-
/** 触发后用作
|
|
5
|
+
/** 触发后用作 content 变量的剥离文本(query 由引擎按统一消息格式另行构造) */
|
|
6
6
|
queryText: string;
|
|
7
7
|
}
|
|
8
8
|
/** 评估单个消息条件;返回 null 表示未命中 */
|
package/lib/trigger/engine.d.ts
CHANGED
|
@@ -12,8 +12,7 @@ export declare class TriggerEngine {
|
|
|
12
12
|
private history;
|
|
13
13
|
private rules;
|
|
14
14
|
private scheduler;
|
|
15
|
-
private
|
|
16
|
-
private digest;
|
|
15
|
+
private pool;
|
|
17
16
|
constructor(ctx: Context, runner: RequestRunner, history: MessageHistory, rules: DifyRule[]);
|
|
18
17
|
start(): void;
|
|
19
18
|
private handleMessage;
|
|
@@ -23,8 +22,8 @@ export declare class TriggerEngine {
|
|
|
23
22
|
private fireMessage;
|
|
24
23
|
private fireDensity;
|
|
25
24
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* 定时触发(消息摘要):把消息池内该规则作用域有消息的每个频道各发一次请求(取走即清空全池)。
|
|
26
|
+
* 频道身份取自池内最近一条消息;无消息的频道自然跳过。
|
|
28
27
|
*/
|
|
29
28
|
private fireSchedule;
|
|
30
29
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Session } from 'koishi';
|
|
2
|
+
import { DifyRule } from '../types';
|
|
3
|
+
import { HistoryEntry } from '../history';
|
|
4
|
+
/** 一个频道的缓冲池:密度与定时共用,谁先触发谁取走清空 */
|
|
5
|
+
export interface ChannelPool {
|
|
6
|
+
platform: string;
|
|
7
|
+
channelId: string;
|
|
8
|
+
guildId?: string;
|
|
9
|
+
/** 定时投递凭据,跟随最近一条消息的 bot */
|
|
10
|
+
selfId: string;
|
|
11
|
+
isDirect?: boolean;
|
|
12
|
+
entries: HistoryEntry[];
|
|
13
|
+
}
|
|
14
|
+
export interface DensityFire {
|
|
15
|
+
rule: DifyRule;
|
|
16
|
+
/** 触发时取走的该频道全池消息(含 bot 行与时效外条目) */
|
|
17
|
+
entries: HistoryEntry[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 频道消息池:密度与定时触发共用的缓冲层。
|
|
21
|
+
* 密度只作触发依据(时效内真人条数达阈值),触发即取走全池;
|
|
22
|
+
* 定时到点取走作用域内有消息的频道全池——两者天然互相清空、重新积累。
|
|
23
|
+
*/
|
|
24
|
+
export declare class MessagePool {
|
|
25
|
+
private channels;
|
|
26
|
+
/** 一条消息(含 bot 自己的发言)入池,全量记录不做规则过滤 */
|
|
27
|
+
record(session: Session, entry: HistoryEntry): void;
|
|
28
|
+
/**
|
|
29
|
+
* 新消息入池后的密度评估:时效内(相对触发消息)且通过规则作用域的真人条数达到阈值。
|
|
30
|
+
* 触发即取走并清空该频道全池(bot 行不推高密度,防自激励);池清空后其余规则无从触发。
|
|
31
|
+
*/
|
|
32
|
+
checkDensity(rules: DifyRule[], session: Session): DensityFire[];
|
|
33
|
+
/** 定时到点:取走该规则作用域内有消息的所有频道(整池清空,含未过作用域的条目) */
|
|
34
|
+
takeSchedule(rule: DifyRule): ChannelPool[];
|
|
35
|
+
}
|
package/lib/types.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ export interface FireContext {
|
|
|
94
94
|
userId?: string;
|
|
95
95
|
triggerType: TriggerType;
|
|
96
96
|
rule: DifyRule;
|
|
97
|
-
/**
|
|
97
|
+
/** 发给 Dify 的 query:统一消息格式(消息触发为单行,窗口触发为逐行拼接) */
|
|
98
98
|
queryText: string;
|
|
99
|
+
/** content 变量的值:消息触发为剥离文本,窗口触发为统一格式拼接 */
|
|
100
|
+
contentText: string;
|
|
99
101
|
}
|
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.4",
|
|
5
5
|
"author": "PinkElysia",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/PinkElysiaDev/dify-ai-plugin-for-Koishi#readme",
|
package/readme.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
|
-
- **8 种触发条件**:关键词开头 / 包含关键词 / @ 机器人 / @ 指定用户 / 引用机器人 / 引用指定用户 / 定时摘要(Cron
|
|
7
|
+
- **8 种触发条件**:关键词开头 / 包含关键词 / @ 机器人 / @ 指定用户 / 引用机器人 / 引用指定用户 / 定时摘要(Cron 或固定间隔,把消息池内积攒的群消息合并请求一次,无消息则跳过)/ 消息密度(池内消息达到阈值即触发;两种触发共用消息池,谁先触发谁清空),同一规则多条件任一命中即触发
|
|
8
8
|
- **多应用多规则**:规则按优先级匹配,按平台 / Bot / 群聊 / 频道 / 用户限定作用域
|
|
9
9
|
- **会话绑定**:按群聊 / 用户 / 频道 / 全局粒度续接 Dify 会话,`dify.conv.reset` 可重置
|
|
10
10
|
- **变量系统**:把会话字段、历史消息、触发元信息映射为 Dify inputs
|
package/lib/commands/app.d.ts
DELETED
package/lib/commands/rule.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Context } from 'koishi';
|
|
2
|
-
import { RequestRunner } from '../requester/runner';
|
|
3
|
-
import { DifyApp } from '../state/tables';
|
|
4
|
-
/** 应用接入的共享业务层:命令(dify.app.*)调用。 */
|
|
5
|
-
/** 新建应用接入(重名拒绝);返回新行 id */
|
|
6
|
-
export declare function createApp(ctx: Context, runner: RequestRunner, input: {
|
|
7
|
-
name: string;
|
|
8
|
-
apiURL: string;
|
|
9
|
-
apiKey: string;
|
|
10
|
-
}): Promise<string>;
|
|
11
|
-
/** 探测应用类型(GET /info + /parameters),成败都回写 status/lastCheckAt */
|
|
12
|
-
export declare function testApp(ctx: Context, runner: RequestRunner, app: DifyApp): Promise<{
|
|
13
|
-
mode: string;
|
|
14
|
-
name: string;
|
|
15
|
-
}>;
|
|
16
|
-
/** 删除应用;被规则引用时拒绝 */
|
|
17
|
-
export declare function deleteApp(ctx: Context, runner: RequestRunner, app: DifyApp): Promise<void>;
|
package/lib/trigger/density.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Session } from 'koishi';
|
|
2
|
-
import { DifyRule } from '../types';
|
|
3
|
-
import { HistoryEntry } from '../history';
|
|
4
|
-
/**
|
|
5
|
-
* 消息密度触发:按 (规则, 频道) 维护滑动窗口内的消息,
|
|
6
|
-
* 窗口内条数达到阈值即回调触发,并把窗口内这些消息交给触发方作 query 上下文。
|
|
7
|
-
* 触发后清空窗口,需重新积累即天然防连发。
|
|
8
|
-
*/
|
|
9
|
-
export declare class DensityTracker {
|
|
10
|
-
private onTrigger;
|
|
11
|
-
private windows;
|
|
12
|
-
constructor(onTrigger: (rule: DifyRule, session: Session, entries: HistoryEntry[]) => void);
|
|
13
|
-
/** 把一条消息计入所有 density 规则的窗口;达到阈值的窗口立即触发 */
|
|
14
|
-
record(rules: DifyRule[], session: Session, entry: HistoryEntry): void;
|
|
15
|
-
}
|
package/lib/trigger/digest.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Session } from 'koishi';
|
|
2
|
-
import { DifyRule } from '../types';
|
|
3
|
-
import { HistoryEntry } from '../history';
|
|
4
|
-
/** 一个频道的定时窗口缓冲:身份字段取自该频道最近一条被记录的消息 */
|
|
5
|
-
export interface ChannelDigest {
|
|
6
|
-
platform: string;
|
|
7
|
-
channelId: string;
|
|
8
|
-
guildId?: string;
|
|
9
|
-
selfId: string;
|
|
10
|
-
entries: HistoryEntry[];
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* 定时触发的消息窗口收集器。
|
|
14
|
-
*
|
|
15
|
-
* 定时规则是「消息摘要」模型:持续收集作用域内各频道的消息,
|
|
16
|
-
* 到点(cron/间隔)把每个频道窗口内的消息合并为一次 Dify 请求,窗口空则跳过。
|
|
17
|
-
* take() 取走即清空——窗口即「上次触发至今」;规则各自独立缓冲,互不干扰。
|
|
18
|
-
*/
|
|
19
|
-
export declare class MessageDigest {
|
|
20
|
-
/** ruleName → channelKey → 窗口缓冲 */
|
|
21
|
-
private windows;
|
|
22
|
-
/** 把一条消息记入所有 schedule 规则(作用域匹配)的窗口 */
|
|
23
|
-
record(rules: DifyRule[], session: Session, entry: HistoryEntry): void;
|
|
24
|
-
/** 取走规则窗口内的全部非空频道缓冲(并清空该规则的窗口) */
|
|
25
|
-
take(rule: DifyRule): ChannelDigest[];
|
|
26
|
-
}
|