mioku-plugin-chat 2.0.0

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.
@@ -0,0 +1,312 @@
1
+ import type { AIRequestLimitConfig, DynamicDelayConfig } from "../types";
2
+
3
+ export class RateLimiter {
4
+ // 用户触发记录:userId -> timestamp[]
5
+ private userTriggers: Map<number, number[]> = new Map();
6
+ // 用户最近消息:userId -> {content, timestamp}[]
7
+ private userMessages: Map<number, { content: string; timestamp: number }[]> =
8
+ new Map();
9
+ // 群组最后响应时间:groupId -> timestamp
10
+ private groupLastResponse: Map<number, number> = new Map();
11
+ private groupInteractions: Map<number, Map<number, number[]>> = new Map();
12
+ private userAiRequests: Map<number, number[]> = new Map();
13
+ private groupAiRequests: Map<number, number[]> = new Map();
14
+
15
+ private readonly maxTriggersPerWindow: number;
16
+ private readonly windowMs: number;
17
+ private readonly dedupWindowMs: number;
18
+ private readonly groupCooldownMs: number;
19
+ private readonly cleanupTimer: ReturnType<typeof setInterval>;
20
+ private readonly dynamicDelayConfig: DynamicDelayConfig;
21
+ private readonly aiRequestLimitConfig: AIRequestLimitConfig;
22
+ // 外部注入的队列长度获取函数
23
+ private getQueueLengthFn: ((groupId: number) => number) | null = null;
24
+
25
+ /**
26
+ * 设置队列长度获取函数
27
+ */
28
+ setQueueLengthGetter(fn: (groupId: number) => number): void {
29
+ this.getQueueLengthFn = fn;
30
+ }
31
+
32
+ constructor(options?: {
33
+ maxTriggersPerWindow?: number;
34
+ windowMs?: number;
35
+ dedupWindowMs?: number;
36
+ groupCooldownMs?: number;
37
+ dynamicDelay?: DynamicDelayConfig;
38
+ aiRequestLimits?: AIRequestLimitConfig;
39
+ }) {
40
+ this.maxTriggersPerWindow = options?.maxTriggersPerWindow ?? 5;
41
+ this.windowMs = options?.windowMs ?? 60_000;
42
+ this.dedupWindowMs = options?.dedupWindowMs ?? 30_000;
43
+ this.groupCooldownMs = options?.groupCooldownMs ?? 1_000;
44
+ this.dynamicDelayConfig = options?.dynamicDelay ?? {
45
+ enabled: true,
46
+ interactionWindowMs: 60_000,
47
+ baseDelayMs: 30_000,
48
+ maxDelayMs: 300_000,
49
+ };
50
+ this.aiRequestLimitConfig = options?.aiRequestLimits ?? {
51
+ userRpm: 3,
52
+ groupRpm: 6,
53
+ windowMs: 60_000,
54
+ };
55
+
56
+ this.cleanupTimer = setInterval(() => this.cleanup(), 300_000);
57
+ }
58
+
59
+ canProcess(
60
+ userId: number,
61
+ groupId: number | undefined,
62
+ content: string,
63
+ ): boolean {
64
+ const now = Date.now();
65
+
66
+ // 群组冷却检查
67
+ if (groupId) {
68
+ const lastResponse = this.groupLastResponse.get(groupId);
69
+ if (lastResponse && now - lastResponse < this.groupCooldownMs) {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ // 用户频率检查
75
+ const triggers = this.userTriggers.get(userId) ?? [];
76
+ const recentTriggers = triggers.filter((t) => now - t < this.windowMs);
77
+ if (recentTriggers.length >= this.maxTriggersPerWindow) {
78
+ return false;
79
+ }
80
+
81
+ // 重复消息检查
82
+ const messages = this.userMessages.get(userId) ?? [];
83
+ const recentSame = messages.find(
84
+ (m) => m.content === content && now - m.timestamp < this.dedupWindowMs,
85
+ );
86
+ return !recentSame;
87
+ }
88
+
89
+ /**
90
+ * 记录已处理的消息
91
+ */
92
+ record(userId: number, groupId: number | undefined, content: string): void {
93
+ const now = Date.now();
94
+
95
+ // 记录触发
96
+ const triggers = this.userTriggers.get(userId) ?? [];
97
+ triggers.push(now);
98
+ this.userTriggers.set(userId, triggers);
99
+
100
+ // 记录消息(只保留最近 3 条)
101
+ const messages = this.userMessages.get(userId) ?? [];
102
+ messages.push({ content, timestamp: now });
103
+ if (messages.length > 3) messages.shift();
104
+ this.userMessages.set(userId, messages);
105
+
106
+ // 记录群组响应
107
+ if (groupId) {
108
+ this.groupLastResponse.set(groupId, now);
109
+ }
110
+ }
111
+
112
+ recordInteraction(groupId: number, userId: number): void {
113
+ if (!this.dynamicDelayConfig.enabled) return;
114
+
115
+ const now = Date.now();
116
+ const windowMs = this.dynamicDelayConfig.interactionWindowMs;
117
+
118
+ let groupUsers = this.groupInteractions.get(groupId);
119
+ if (!groupUsers) {
120
+ groupUsers = new Map();
121
+ this.groupInteractions.set(groupId, groupUsers);
122
+ }
123
+
124
+ let timestamps = groupUsers.get(userId) ?? [];
125
+ timestamps = timestamps.filter((t) => now - t < windowMs);
126
+ timestamps.push(now);
127
+ groupUsers.set(userId, timestamps);
128
+ }
129
+
130
+ canRunAIRequest(userId?: number, groupId?: number): boolean {
131
+ const now = Date.now();
132
+ const { userRpm, groupRpm, windowMs } = this.aiRequestLimitConfig;
133
+
134
+ if (typeof userId === "number") {
135
+ const userRequests = (this.userAiRequests.get(userId) ?? []).filter(
136
+ (timestamp) => now - timestamp < windowMs,
137
+ );
138
+ if (userRequests.length >= userRpm) {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ if (typeof groupId === "number") {
144
+ const groupRequests = (this.groupAiRequests.get(groupId) ?? []).filter(
145
+ (timestamp) => now - timestamp < windowMs,
146
+ );
147
+ if (groupRequests.length >= groupRpm) {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ return true;
153
+ }
154
+
155
+ recordAIRequest(userId?: number, groupId?: number): void {
156
+ const now = Date.now();
157
+ const windowMs = this.aiRequestLimitConfig.windowMs;
158
+
159
+ if (typeof userId === "number") {
160
+ const userRequests = (this.userAiRequests.get(userId) ?? []).filter(
161
+ (timestamp) => now - timestamp < windowMs,
162
+ );
163
+ userRequests.push(now);
164
+ this.userAiRequests.set(userId, userRequests);
165
+ }
166
+
167
+ if (typeof groupId === "number") {
168
+ const groupRequests = (this.groupAiRequests.get(groupId) ?? []).filter(
169
+ (timestamp) => now - timestamp < windowMs,
170
+ );
171
+ groupRequests.push(now);
172
+ this.groupAiRequests.set(groupId, groupRequests);
173
+ }
174
+ }
175
+
176
+ getInteractionCount(groupId: number): number {
177
+ const now = Date.now();
178
+ const windowMs = this.dynamicDelayConfig.interactionWindowMs;
179
+
180
+ // 优先使用队列长度作为互动人数计算
181
+ if (this.getQueueLengthFn) {
182
+ const queueLength = this.getQueueLengthFn(groupId);
183
+ if (queueLength > 0) {
184
+ return queueLength;
185
+ }
186
+ }
187
+
188
+ // 回退到 groupInteractions 统计
189
+ const groupUsers = this.groupInteractions.get(groupId);
190
+ if (!groupUsers) return 0;
191
+
192
+ let count = 0;
193
+ for (const [, timestamps] of groupUsers) {
194
+ const recentTimestamps = timestamps.filter((t) => now - t < windowMs);
195
+ if (recentTimestamps.length > 0) {
196
+ count++;
197
+ }
198
+ }
199
+ return count;
200
+ }
201
+
202
+ calculateDelay(groupId: number): number {
203
+ if (!this.dynamicDelayConfig.enabled) return 0;
204
+
205
+ const interactionCount = this.getInteractionCount(groupId);
206
+ if (interactionCount <= 1) return 0;
207
+
208
+ const { baseDelayMs, maxDelayMs } = this.dynamicDelayConfig;
209
+ const delay = (interactionCount - 1) * baseDelayMs;
210
+ return Math.min(delay, maxDelayMs);
211
+ }
212
+
213
+ getDelayInfo(groupId: number): {
214
+ delayMs: number;
215
+ interactionCount: number;
216
+ shouldDelay: boolean;
217
+ } {
218
+ const interactionCount = this.getInteractionCount(groupId);
219
+ const delayMs = this.calculateDelay(groupId);
220
+ return {
221
+ delayMs,
222
+ interactionCount,
223
+ shouldDelay: delayMs > 0,
224
+ };
225
+ }
226
+
227
+ clearGroupInteractions(groupId: number): void {
228
+ this.groupInteractions.delete(groupId);
229
+ }
230
+
231
+ cleanup(): void {
232
+ const now = Date.now();
233
+
234
+ for (const [userId, triggers] of this.userTriggers) {
235
+ const valid = triggers.filter((t) => now - t < this.windowMs);
236
+ if (valid.length === 0) {
237
+ this.userTriggers.delete(userId);
238
+ } else {
239
+ this.userTriggers.set(userId, valid);
240
+ }
241
+ }
242
+
243
+ for (const [userId, messages] of this.userMessages) {
244
+ const valid = messages.filter(
245
+ (m) => now - m.timestamp < this.dedupWindowMs,
246
+ );
247
+ if (valid.length === 0) {
248
+ this.userMessages.delete(userId);
249
+ } else {
250
+ this.userMessages.set(userId, valid);
251
+ }
252
+ }
253
+
254
+ for (const [groupId, timestamp] of this.groupLastResponse) {
255
+ if (now - timestamp > this.groupCooldownMs * 10) {
256
+ this.groupLastResponse.delete(groupId);
257
+ }
258
+ }
259
+
260
+ const aiWindowMs = this.aiRequestLimitConfig.windowMs;
261
+ for (const [userId, requests] of this.userAiRequests) {
262
+ const valid = requests.filter(
263
+ (timestamp) => now - timestamp < aiWindowMs,
264
+ );
265
+ if (valid.length === 0) {
266
+ this.userAiRequests.delete(userId);
267
+ } else {
268
+ this.userAiRequests.set(userId, valid);
269
+ }
270
+ }
271
+
272
+ for (const [groupId, requests] of this.groupAiRequests) {
273
+ const valid = requests.filter(
274
+ (timestamp) => now - timestamp < aiWindowMs,
275
+ );
276
+ if (valid.length === 0) {
277
+ this.groupAiRequests.delete(groupId);
278
+ } else {
279
+ this.groupAiRequests.set(groupId, valid);
280
+ }
281
+ }
282
+
283
+ if (this.dynamicDelayConfig.enabled) {
284
+ const windowMs = this.dynamicDelayConfig.interactionWindowMs;
285
+ for (const [groupId, groupUsers] of this.groupInteractions) {
286
+ let hasActiveUser = false;
287
+ for (const [userId, timestamps] of groupUsers) {
288
+ const valid = timestamps.filter((t) => now - t < windowMs);
289
+ if (valid.length === 0) {
290
+ groupUsers.delete(userId);
291
+ } else {
292
+ groupUsers.set(userId, valid);
293
+ hasActiveUser = true;
294
+ }
295
+ }
296
+ if (!hasActiveUser) {
297
+ this.groupInteractions.delete(groupId);
298
+ }
299
+ }
300
+ }
301
+ }
302
+
303
+ dispose(): void {
304
+ clearInterval(this.cleanupTimer);
305
+ this.userTriggers.clear();
306
+ this.userMessages.clear();
307
+ this.groupLastResponse.clear();
308
+ this.groupInteractions.clear();
309
+ this.userAiRequests.clear();
310
+ this.groupAiRequests.clear();
311
+ }
312
+ }
@@ -0,0 +1,96 @@
1
+ import type { SessionMeta, SessionType } from "../types";
2
+ import type { ChatDatabase } from "../db";
3
+
4
+ /**
5
+ * LRU 会话管理器
6
+ */
7
+ export class SessionManager {
8
+ private cache: Map<string, SessionMeta> = new Map();
9
+ private readonly maxSize: number;
10
+ private db: ChatDatabase;
11
+
12
+ constructor(db: ChatDatabase, maxSize: number) {
13
+ this.db = db;
14
+ this.maxSize = maxSize;
15
+ }
16
+
17
+ /**
18
+ * 获取或创建会话
19
+ */
20
+ getOrCreate(id: string, type: SessionType, targetId: number): SessionMeta {
21
+ // 先查缓存
22
+ if (this.cache.has(id)) {
23
+ return this.touch(id);
24
+ }
25
+
26
+ // 查数据库
27
+ const existing = this.db.getSession(id);
28
+ if (existing) {
29
+ this.addToCache(id, existing);
30
+ return existing;
31
+ }
32
+
33
+ // 创建新会话
34
+ const now = Date.now();
35
+ const session: SessionMeta = {
36
+ id,
37
+ type,
38
+ targetId,
39
+ createdAt: now,
40
+ updatedAt: now,
41
+ compressedContext: null,
42
+ };
43
+ this.db.saveSession(session);
44
+ this.addToCache(id, session);
45
+ return session;
46
+ }
47
+
48
+ /**
49
+ * 获取会话(不创建)
50
+ */
51
+ get(id: string): SessionMeta | null {
52
+ if (this.cache.has(id)) {
53
+ return this.touch(id);
54
+ }
55
+ const existing = this.db.getSession(id);
56
+ if (existing) {
57
+ this.addToCache(id, existing);
58
+ return existing;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * 更新访问时间并移至最近
65
+ */
66
+ touch(id: string): SessionMeta {
67
+ const session = this.cache.get(id);
68
+ if (!session) {
69
+ throw new Error(`Session ${id} not found in cache`);
70
+ }
71
+ // 删除再插入,移到 Map 末尾(最近使用)
72
+ this.cache.delete(id);
73
+ session.updatedAt = Date.now();
74
+ this.cache.set(id, session);
75
+ this.db.saveSession(session);
76
+ return session;
77
+ }
78
+
79
+ /**
80
+ * 重置 bot 消息
81
+ */
82
+ resetBotMessages(id: string): void {
83
+ this.db.deleteBotMessages(id);
84
+ }
85
+
86
+ private addToCache(id: string, session: SessionMeta): void {
87
+ // LRU 淘汰
88
+ if (this.cache.size >= this.maxSize) {
89
+ const oldest = this.cache.keys().next().value;
90
+ if (oldest !== undefined) {
91
+ this.cache.delete(oldest);
92
+ }
93
+ }
94
+ this.cache.set(id, session);
95
+ }
96
+ }
@@ -0,0 +1,173 @@
1
+ import type { AITool } from "mioku";
2
+ import type { SkillSession } from "../types";
3
+ import type { FeatureName } from "../core/feature-prompts";
4
+
5
+ /**
6
+ * 技能会话管理器
7
+ * 管理每个 session 的技能加载和工具注册
8
+ */
9
+ export class SkillSessionManager {
10
+ private sessions: Map<string, Map<string, SkillSession>> = new Map();
11
+ private features: Map<string, Map<string, FeatureSession>> = new Map();
12
+ private EXPIRY_MS = 60 * 60 * 1000; // 1 hour
13
+
14
+ getTools(sessionId: string): Map<string, AITool> {
15
+ const result = new Map<string, AITool>();
16
+ const sessionSkills = this.sessions.get(sessionId);
17
+ if (!sessionSkills) return result;
18
+
19
+ const now = Date.now();
20
+ for (const [skillName, session] of sessionSkills) {
21
+ if (now > session.expiresAt) {
22
+ sessionSkills.delete(skillName);
23
+ continue;
24
+ }
25
+ for (const [toolName, tool] of session.tools) {
26
+ result.set(toolName, tool);
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+
32
+ loadSkill(
33
+ sessionId: string,
34
+ skillName: string,
35
+ tools: AITool[],
36
+ ): SkillSession {
37
+ let sessionSkills = this.sessions.get(sessionId);
38
+ if (!sessionSkills) {
39
+ sessionSkills = new Map();
40
+ this.sessions.set(sessionId, sessionSkills);
41
+ }
42
+
43
+ const now = Date.now();
44
+ const toolMap = new Map<string, AITool>();
45
+ for (const tool of tools) {
46
+ toolMap.set(`${skillName}.${tool.name}`, tool);
47
+ }
48
+
49
+ const session: SkillSession = {
50
+ skillName,
51
+ tools: toolMap,
52
+ loadedAt: now,
53
+ expiresAt: now + this.EXPIRY_MS,
54
+ };
55
+ sessionSkills.set(skillName, session);
56
+ return session;
57
+ }
58
+
59
+ unloadSkill(sessionId: string, skillName: string): boolean {
60
+ const sessionSkills = this.sessions.get(sessionId);
61
+ if (!sessionSkills) return false;
62
+ return sessionSkills.delete(skillName);
63
+ }
64
+
65
+ getActiveSkillsInfo(
66
+ sessionId: string,
67
+ isSkillVisible?: (skillName: string) => boolean,
68
+ ): string {
69
+ const sessionSkills = this.sessions.get(sessionId);
70
+ if (!sessionSkills || sessionSkills.size === 0) return "";
71
+
72
+ const now = Date.now();
73
+ const lines: string[] = [];
74
+
75
+ for (const [skillName, session] of sessionSkills) {
76
+ if (now > session.expiresAt) {
77
+ sessionSkills.delete(skillName);
78
+ continue;
79
+ }
80
+ if (isSkillVisible && !isSkillVisible(skillName)) {
81
+ continue;
82
+ }
83
+ const remainingMin = Math.ceil((session.expiresAt - now) / 60000);
84
+ const toolNames = [...session.tools.keys()].join(", ");
85
+ lines.push(
86
+ `- ${skillName} (expires in ${remainingMin}min): ${toolNames}`,
87
+ );
88
+ }
89
+
90
+ if (lines.length === 0) return "";
91
+ return `## Loaded External Skills\n${lines.join("\n")}`;
92
+ }
93
+
94
+ cleanup(): void {
95
+ const now = Date.now();
96
+ for (const [sessionId, sessionSkills] of this.sessions) {
97
+ for (const [skillName, session] of sessionSkills) {
98
+ if (now > session.expiresAt) {
99
+ sessionSkills.delete(skillName);
100
+ }
101
+ }
102
+ if (sessionSkills.size === 0) {
103
+ this.sessions.delete(sessionId);
104
+ }
105
+ }
106
+ for (const [sessionId, sessionFeatures] of this.features) {
107
+ for (const [featureName, feature] of sessionFeatures) {
108
+ if (now > feature.expiresAt) {
109
+ sessionFeatures.delete(featureName);
110
+ }
111
+ }
112
+ if (sessionFeatures.size === 0) {
113
+ this.features.delete(sessionId);
114
+ }
115
+ }
116
+ }
117
+
118
+ loadFeature(
119
+ sessionId: string,
120
+ featureName: FeatureName,
121
+ ttlMs: number,
122
+ ): void {
123
+ let sessionFeatures = this.features.get(sessionId);
124
+ if (!sessionFeatures) {
125
+ sessionFeatures = new Map();
126
+ this.features.set(sessionId, sessionFeatures);
127
+ }
128
+
129
+ const now = Date.now();
130
+ sessionFeatures.set(featureName, {
131
+ featureName,
132
+ loadedAt: now,
133
+ expiresAt: now + ttlMs,
134
+ });
135
+ }
136
+
137
+ getActiveFeatureNames(sessionId: string): FeatureName[] {
138
+ const sessionFeatures = this.features.get(sessionId);
139
+ if (!sessionFeatures) return [];
140
+
141
+ const now = Date.now();
142
+ const result: FeatureName[] = [];
143
+ for (const [featureName, feature] of sessionFeatures) {
144
+ if (now > feature.expiresAt) {
145
+ sessionFeatures.delete(featureName);
146
+ continue;
147
+ }
148
+ result.push(feature.featureName);
149
+ }
150
+ return result;
151
+ }
152
+
153
+ getActiveFeatureTools(sessionId: string): FeatureName[] {
154
+ // Only return features that have tools (web_search, web_read_page, recall_memory)
155
+ const all = this.getActiveFeatureNames(sessionId);
156
+ return all.filter((name) => {
157
+ switch (name) {
158
+ case "web_search":
159
+ case "web_read_page":
160
+ case "recall_memory":
161
+ return true;
162
+ default:
163
+ return false;
164
+ }
165
+ });
166
+ }
167
+ }
168
+
169
+ interface FeatureSession {
170
+ featureName: FeatureName;
171
+ loadedAt: number;
172
+ expiresAt: number;
173
+ }
@@ -0,0 +1,138 @@
1
+ import type { MiokiContext } from "mioki";
2
+ import type { AIInstance, AIService } from "mioku";
3
+ import type { SkillPermissionRole } from "mioku";
4
+ import type { ChatRuntimePromptInjection } from "mioku";
5
+ import type { ChatConfig, ChatMessage, TargetMessage, ToolContext, ChatResult } from "../types";
6
+ import type { ChatDatabase } from "../db";
7
+ import type { HumanizeEngine } from "../humanize";
8
+ import type { EmojiAgent } from "../humanize";
9
+ import type { SkillSessionManager } from "./skill-session";
10
+ import type { GroupStructuredHistoryManager, StructuredUserInput } from "./group-structured-history";
11
+ import type { SendAIResponseOptions } from "../core/base";
12
+ import type { GroupInfoResult, HumanizeContextsResult } from "../core/base";
13
+
14
+ export type { SendAIResponseOptions, GroupInfoResult, HumanizeContextsResult };
15
+
16
+ export type HistoryMediaOptions = {
17
+ ai?: AIInstance;
18
+ workingModel?: string;
19
+ multimodalWorkingModel?: string;
20
+ };
21
+
22
+ export type RunRateLimitGuard = <T>(
23
+ request: () => Promise<T>,
24
+ opts?: { userId?: number; groupId?: number; label?: string },
25
+ ) => Promise<T | null>;
26
+
27
+ export type GetGroupHistoryMessages = (
28
+ groupId: number,
29
+ groupSessionId: string,
30
+ ctx: MiokiContext,
31
+ historyCount: number,
32
+ db: ChatDatabase,
33
+ selfId: number,
34
+ mediaOptions?: HistoryMediaOptions,
35
+ ) => Promise<{ history: ChatMessage[] }>;
36
+
37
+ export type GetGroupInfoData = (
38
+ ctx: MiokiContext,
39
+ groupId: number,
40
+ selfId: number,
41
+ fallbackGroupName?: string,
42
+ ) => Promise<GroupInfoResult>;
43
+
44
+ export type GetHumanizeContexts = (
45
+ humanize: HumanizeEngine,
46
+ groupSessionId: string,
47
+ userName: string,
48
+ history: ChatMessage[],
49
+ triggerUserId?: number,
50
+ ) => Promise<HumanizeContextsResult>;
51
+
52
+ export type SendAIResponse = (options: SendAIResponseOptions, selfId: number) => Promise<void>;
53
+
54
+ export type SaveBotMessages = (
55
+ groupId: number,
56
+ groupSessionId: string,
57
+ messages: string[],
58
+ timestamp: number,
59
+ config: ChatConfig,
60
+ db: ChatDatabase,
61
+ ctx: MiokiContext,
62
+ selfId: number,
63
+ ) => void;
64
+
65
+ export type SendEmoji = (ctx: MiokiContext, groupId: number, emojiPath: string | null | undefined, selfId: number) => Promise<void>;
66
+
67
+ export type SendMessage = (
68
+ ctx: MiokiContext,
69
+ groupId: number | undefined,
70
+ userId: number,
71
+ text: string,
72
+ config: ChatConfig,
73
+ selfId: number,
74
+ ) => Promise<void>;
75
+
76
+ export type BuildToolContext = (options: {
77
+ ctx: MiokiContext;
78
+ event: any;
79
+ selfId: number;
80
+ groupSessionId: string;
81
+ groupId?: number;
82
+ userId: number;
83
+ config: ChatConfig;
84
+ aiService: AIService;
85
+ db: ChatDatabase;
86
+ botRole: "owner" | "admin" | "member";
87
+ pendingImageUrls?: string[];
88
+ humanize: HumanizeEngine;
89
+ targetMessage: TargetMessage;
90
+ }) => ToolContext;
91
+
92
+ export type BuildStructuredUserInput = (event: any, content: string, fallbackTimestamp?: number) => StructuredUserInput;
93
+
94
+ export type BuildStructuredUserInputFromTarget = (targetMessage: TargetMessage) => StructuredUserInput;
95
+
96
+ export type PromptCtxForRunChat = {
97
+ config: ChatConfig;
98
+ groupName?: string;
99
+ memberCount?: number;
100
+ botNickname: string;
101
+ botRole: "owner" | "admin" | "member";
102
+ triggerSkillRole?: SkillPermissionRole;
103
+ aiService: AIService;
104
+ isGroup: boolean;
105
+ memoryContext?: string;
106
+ topicContext?: string;
107
+ expressionContext?: string;
108
+ activeSkillsInfo?: string;
109
+ plannerThoughts?: string;
110
+ replyContext?: {
111
+ type: "reply" | "comment" | "idle" | "review" | "poked";
112
+ targetUser?: string;
113
+ targetMessage?: string;
114
+ };
115
+ reviewMessages?: {
116
+ contents: string[];
117
+ userNames: string[];
118
+ messageIds: number[];
119
+ };
120
+ promptInjections?: ChatRuntimePromptInjection[];
121
+ emojiAgent?: EmojiAgent;
122
+ };
123
+
124
+ export type RunChat = (
125
+ ai: AIInstance,
126
+ toolCtx: ToolContext,
127
+ history: ChatMessage[],
128
+ targetMessage: TargetMessage,
129
+ promptCtx: PromptCtxForRunChat,
130
+ humanize: HumanizeEngine,
131
+ skillManager: SkillSessionManager,
132
+ structuredHistory?: {
133
+ manager: GroupStructuredHistoryManager;
134
+ ttlMs: number;
135
+ currentUserInputs: StructuredUserInput[];
136
+ },
137
+ runtimeOptions?: { extraTools?: any[] },
138
+ ) => Promise<ChatResult>;