mioku-plugin-chat 2.9.10 → 2.10.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.
- package/configs/base.ts +1 -1
- package/core/chat-turn.ts +1 -1
- package/core/media/image-analyzer.ts +3 -9
- package/handlers/idle-debug.ts +1 -1
- package/handlers/message.ts +2 -0
- package/handlers/poke.ts +1 -1
- package/index.ts +1 -1
- package/manage/cooldown.ts +2 -2
- package/manage/idle-check.ts +1 -1
- package/manage/queue-processor.ts +2 -2
- package/manage/rate-limit-guard.ts +47 -31
- package/manage/types.ts +6 -1
- package/package.json +1 -1
- package/utils/message.ts +2 -11
package/configs/base.ts
CHANGED
package/core/chat-turn.ts
CHANGED
|
@@ -351,7 +351,7 @@ export async function processChat(
|
|
|
351
351
|
}
|
|
352
352
|
: undefined,
|
|
353
353
|
),
|
|
354
|
-
{ userId, groupId, label: isGroup ? "group-chat" : "private-chat" },
|
|
354
|
+
{ userId, groupId, label: isGroup ? "group-chat" : "private-chat", skipRetryOnRateLimit: true },
|
|
355
355
|
);
|
|
356
356
|
|
|
357
357
|
if (!result) {
|
|
@@ -157,7 +157,7 @@ export async function processImage(
|
|
|
157
157
|
|
|
158
158
|
const record: ImageRecord = {
|
|
159
159
|
hash,
|
|
160
|
-
url:
|
|
160
|
+
url: imageUrl,
|
|
161
161
|
type: "image",
|
|
162
162
|
description: analysis.description || "未知",
|
|
163
163
|
createdAt: Date.now(),
|
|
@@ -177,17 +177,11 @@ export async function getImageTag(
|
|
|
177
177
|
const exact = db.getImageByUrl(imageUrl);
|
|
178
178
|
if (exact) return `[image:${exact.description}]`;
|
|
179
179
|
|
|
180
|
-
const normalized = normalizeImageUrl(imageUrl);
|
|
181
|
-
if (normalized && normalized !== imageUrl) {
|
|
182
|
-
const normalizedHit = db.getImageByUrl(normalized);
|
|
183
|
-
if (normalizedHit) return `[image:${normalizedHit.description}]`;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
180
|
const hash = await calculateImageHash(imageUrl);
|
|
187
181
|
if (hash) {
|
|
188
182
|
const byHash = db.getImageByHash(hash);
|
|
189
183
|
if (byHash) {
|
|
190
|
-
db.saveImage(asImageRecord(byHash,
|
|
184
|
+
db.saveImage(asImageRecord(byHash, imageUrl));
|
|
191
185
|
return `[image:${byHash.description}]`;
|
|
192
186
|
}
|
|
193
187
|
}
|
|
@@ -198,7 +192,7 @@ export async function getImageTag(
|
|
|
198
192
|
function asImageRecord(record: ImageRecord, url: string): ImageRecord {
|
|
199
193
|
return {
|
|
200
194
|
...record,
|
|
201
|
-
url
|
|
195
|
+
url,
|
|
202
196
|
type: "image",
|
|
203
197
|
};
|
|
204
198
|
}
|
package/handlers/idle-debug.ts
CHANGED
|
@@ -104,7 +104,7 @@ export async function handleIdleCheckDebug(
|
|
|
104
104
|
pluginCtx.humanize,
|
|
105
105
|
pluginCtx.skillManager,
|
|
106
106
|
),
|
|
107
|
-
{ groupId: targetGroupId, label: "idle-check" },
|
|
107
|
+
{ groupId: targetGroupId, label: "idle-check", skipRetryOnRateLimit: true },
|
|
108
108
|
);
|
|
109
109
|
if (!result) {
|
|
110
110
|
await e.reply(`[空闲检测] 群 ${targetGroupId} 因限流被跳过`);
|
package/handlers/message.ts
CHANGED
|
@@ -98,6 +98,7 @@ export function createMessageHandler(
|
|
|
98
98
|
userId,
|
|
99
99
|
groupId,
|
|
100
100
|
label: "history-media",
|
|
101
|
+
skipRetryOnRateLimit: true,
|
|
101
102
|
}),
|
|
102
103
|
)
|
|
103
104
|
: undefined;
|
|
@@ -114,6 +115,7 @@ export function createMessageHandler(
|
|
|
114
115
|
userId,
|
|
115
116
|
groupId,
|
|
116
117
|
label: "image-analysis",
|
|
118
|
+
skipRetryOnRateLimit: true,
|
|
117
119
|
}),
|
|
118
120
|
}).catch((err) =>
|
|
119
121
|
ctx.logger.error(`[image-analyzer] Failed: ${err}`),
|
package/handlers/poke.ts
CHANGED
package/index.ts
CHANGED
package/manage/cooldown.ts
CHANGED
|
@@ -277,7 +277,7 @@ export class CooldownManager {
|
|
|
277
277
|
),
|
|
278
278
|
},
|
|
279
279
|
),
|
|
280
|
-
{ userId: targetMessage.userId, groupId, label: "cooldown" },
|
|
280
|
+
{ userId: targetMessage.userId, groupId, label: "cooldown", skipRetryOnRateLimit: true },
|
|
281
281
|
);
|
|
282
282
|
if (!result) return;
|
|
283
283
|
|
|
@@ -414,7 +414,7 @@ export class CooldownManager {
|
|
|
414
414
|
),
|
|
415
415
|
},
|
|
416
416
|
),
|
|
417
|
-
{ userId: targetMessage.userId, groupId, label: "cooldown-planner" },
|
|
417
|
+
{ userId: targetMessage.userId, groupId, label: "cooldown-planner", skipRetryOnRateLimit: true },
|
|
418
418
|
);
|
|
419
419
|
if (!result) {
|
|
420
420
|
this.ctx.logger.warn(
|
package/manage/idle-check.ts
CHANGED
|
@@ -277,7 +277,7 @@ export class IdleCheckManager {
|
|
|
277
277
|
this.humanize,
|
|
278
278
|
this.skillManager,
|
|
279
279
|
),
|
|
280
|
-
{ groupId, label: "idle-check" },
|
|
280
|
+
{ groupId, label: "idle-check", skipRetryOnRateLimit: true },
|
|
281
281
|
);
|
|
282
282
|
if (!result) {
|
|
283
283
|
this.groupMessageCount.set(groupSessionId, 0);
|
|
@@ -199,7 +199,7 @@ export class QueueProcessor {
|
|
|
199
199
|
}, this.humanize, this.skillManager, {
|
|
200
200
|
manager: this.groupStructuredHistory, ttlMs: cfg.groupStructuredHistoryTtlMs, currentUserInputs: structuredUserInputs,
|
|
201
201
|
}),
|
|
202
|
-
{ userId: targetMessage.userId, groupId, label: "dynamic-delay" },
|
|
202
|
+
{ userId: targetMessage.userId, groupId, label: "dynamic-delay", skipRetryOnRateLimit: true },
|
|
203
203
|
);
|
|
204
204
|
if (!result) return;
|
|
205
205
|
|
|
@@ -280,7 +280,7 @@ export class QueueProcessor {
|
|
|
280
280
|
}, this.humanize, this.skillManager, {
|
|
281
281
|
manager: this.groupStructuredHistory, ttlMs: cfg.groupStructuredHistoryTtlMs, currentUserInputs: structuredUserInputs,
|
|
282
282
|
}),
|
|
283
|
-
{ userId: targetMessage.userId, groupId, label: "queue" },
|
|
283
|
+
{ userId: targetMessage.userId, groupId, label: "queue", skipRetryOnRateLimit: true },
|
|
284
284
|
);
|
|
285
285
|
if (!result) return;
|
|
286
286
|
|
|
@@ -1,12 +1,38 @@
|
|
|
1
1
|
import type { RateLimiter } from "./rate-limiter";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const RATE_LIMIT_MAX_RETRIES = 2;
|
|
3
|
+
const RATE_LIMIT_RETRY_DELAYS_MS = [5_000, 10_000, 30_000, 60_000];
|
|
5
4
|
|
|
6
5
|
type Logger = { warn: (...args: unknown[]) => void };
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
function isRateLimitError(err: unknown): boolean {
|
|
8
|
+
if (err === null || err === undefined) return false;
|
|
9
|
+
const s = String(err).toLowerCase();
|
|
10
|
+
return s.includes("429") || s.includes("rate limit") || s.includes("rate_limit");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function retryOn429<T>(
|
|
14
|
+
fn: () => Promise<T>,
|
|
15
|
+
log: Logger,
|
|
16
|
+
label?: string,
|
|
17
|
+
): Promise<T> {
|
|
18
|
+
let attempt = 0;
|
|
19
|
+
while (true) {
|
|
20
|
+
try {
|
|
21
|
+
return await fn();
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (!isRateLimitError(err)) throw err;
|
|
24
|
+
if (attempt >= RATE_LIMIT_RETRY_DELAYS_MS.length) throw err;
|
|
25
|
+
const delay = RATE_LIMIT_RETRY_DELAYS_MS[attempt];
|
|
26
|
+
const attemptLabel = `${attempt + 1}/${RATE_LIMIT_RETRY_DELAYS_MS.length}`;
|
|
27
|
+
log.warn(
|
|
28
|
+
`[Chat]${label ? ` ${label}` : ""} 命中 429,等待 ${delay / 1000}s 后第 ${attemptLabel} 次重试: ${err}`,
|
|
29
|
+
);
|
|
30
|
+
attempt += 1;
|
|
31
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
export class RateLimitGuard {
|
|
11
37
|
private blockedUntil = 0;
|
|
12
38
|
|
|
@@ -20,17 +46,17 @@ export class RateLimitGuard {
|
|
|
20
46
|
}
|
|
21
47
|
|
|
22
48
|
private markBlocked(): void {
|
|
23
|
-
this.blockedUntil = Date.now() +
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
private isRateLimitError(err: unknown): boolean {
|
|
27
|
-
const s = String(err).toLowerCase();
|
|
28
|
-
return s.includes("429") || s.includes("rate limit");
|
|
49
|
+
this.blockedUntil = Date.now() + RATE_LIMIT_RETRY_DELAYS_MS[0];
|
|
29
50
|
}
|
|
30
51
|
|
|
31
52
|
async run<T>(
|
|
32
53
|
request: () => Promise<T>,
|
|
33
|
-
opts?: {
|
|
54
|
+
opts?: {
|
|
55
|
+
userId?: number;
|
|
56
|
+
groupId?: number;
|
|
57
|
+
label?: string;
|
|
58
|
+
skipRetryOnRateLimit?: boolean;
|
|
59
|
+
},
|
|
34
60
|
): Promise<T | null> {
|
|
35
61
|
if (this.isBlocked()) {
|
|
36
62
|
this.log.warn(
|
|
@@ -46,25 +72,15 @@ export class RateLimitGuard {
|
|
|
46
72
|
}
|
|
47
73
|
this.rateLimiter.recordAIRequest(opts?.userId, opts?.groupId);
|
|
48
74
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (retries >= RATE_LIMIT_MAX_RETRIES) throw err;
|
|
59
|
-
retries += 1;
|
|
60
|
-
this.log.warn(
|
|
61
|
-
`[Chat] Rate limit hit, waiting ${RATE_LIMIT_RETRY_DELAY_MS / 1000}s...`,
|
|
62
|
-
);
|
|
63
|
-
await new Promise((resolve) =>
|
|
64
|
-
setTimeout(resolve, RATE_LIMIT_RETRY_DELAY_MS),
|
|
65
|
-
);
|
|
66
|
-
if (this.isBlocked()) this.blockedUntil = 0;
|
|
67
|
-
}
|
|
75
|
+
if (opts?.skipRetryOnRateLimit) {
|
|
76
|
+
return await request();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
return await retryOn429(request, this.log, opts?.label);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
this.markBlocked();
|
|
83
|
+
throw err;
|
|
68
84
|
}
|
|
69
85
|
}
|
|
70
|
-
}
|
|
86
|
+
}
|
package/manage/types.ts
CHANGED
|
@@ -21,7 +21,12 @@ export type HistoryMediaOptions = {
|
|
|
21
21
|
|
|
22
22
|
export type RunRateLimitGuard = <T>(
|
|
23
23
|
request: () => Promise<T>,
|
|
24
|
-
opts?: {
|
|
24
|
+
opts?: {
|
|
25
|
+
userId?: number;
|
|
26
|
+
groupId?: number;
|
|
27
|
+
label?: string;
|
|
28
|
+
skipRetryOnRateLimit?: boolean;
|
|
29
|
+
},
|
|
25
30
|
) => Promise<T | null>;
|
|
26
31
|
|
|
27
32
|
export type GetGroupHistoryMessages = (
|
package/package.json
CHANGED
package/utils/message.ts
CHANGED
|
@@ -14,10 +14,7 @@ import {
|
|
|
14
14
|
type HistoryMediaProcessingOptions,
|
|
15
15
|
type MediaMessageSegment,
|
|
16
16
|
} from "../core/media/history-media";
|
|
17
|
-
import {
|
|
18
|
-
calculateImageHash,
|
|
19
|
-
normalizeImageUrl,
|
|
20
|
-
} from "../core/media/image-analyzer";
|
|
17
|
+
import { calculateImageHash } from "../core/media/image-analyzer";
|
|
21
18
|
|
|
22
19
|
const HISTORY_MEDIA_CONCURRENCY = 8;
|
|
23
20
|
|
|
@@ -310,12 +307,6 @@ async function getImageTagWithHashCache(
|
|
|
310
307
|
const exact = db.getImageByUrl?.(imageUrl);
|
|
311
308
|
if (exact) return `[image:${exact.description}]`;
|
|
312
309
|
|
|
313
|
-
const normalized = normalizeImageUrl(imageUrl);
|
|
314
|
-
if (normalized && normalized !== imageUrl) {
|
|
315
|
-
const hit = db.getImageByUrl?.(normalized);
|
|
316
|
-
if (hit) return `[image:${hit.description}]`;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
310
|
let hash = ctx.hashLookupCache.get(`hash:${imageUrl}`);
|
|
320
311
|
if (hash === undefined) {
|
|
321
312
|
hash = (await calculateImageHash(imageUrl)) || null;
|
|
@@ -328,7 +319,7 @@ async function getImageTagWithHashCache(
|
|
|
328
319
|
try {
|
|
329
320
|
db.saveImage?.({
|
|
330
321
|
hash: byHash.hash,
|
|
331
|
-
url:
|
|
322
|
+
url: imageUrl,
|
|
332
323
|
type: "image",
|
|
333
324
|
description: byHash.description,
|
|
334
325
|
createdAt: byHash.createdAt,
|