mioku-plugin-chat 2.9.11 → 2.10.1

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 CHANGED
@@ -4,4 +4,4 @@ export const BASE_CONFIG = {
4
4
  historyCount: 100,
5
5
  maxIterations: 20,
6
6
  enableMediaRecognition: true,
7
- };
7
+ };
@@ -721,7 +721,6 @@ function cleanMarkers(text: string): string {
721
721
  let cleaned = stripThinkBlocks(text).trim();
722
722
 
723
723
  cleaned = cleaned
724
- .replace(/<Ai>\s*<think>[\s\S]*?<\/Ai>/gi, "")
725
724
  .replace(/<||DSML||tool_calls>[\s\S]*?<\/||DSML||tool_calls>/gi, "")
726
725
  .replace(/<||DSML||invoke[^>]*>[\s\S]*?<\/||DSML||invoke>/gi, "")
727
726
  .replace(
@@ -824,7 +823,7 @@ function stripThinkBlocks(text: string): string {
824
823
  source = afterOpen.slice(close.end);
825
824
  }
826
825
 
827
- return output.replace(/<\/think\s*>/gi, "");
826
+ return output.replace(/<\/?(?:think|thinking)\s*>/gi, "");
828
827
  }
829
828
 
830
829
  function createThinkTagStreamFilter() {
@@ -840,7 +839,7 @@ function createThinkTagStreamFilter() {
840
839
  if (insideThink) {
841
840
  const close = findThinkCloseTag(buffer);
842
841
  if (!close) {
843
- buffer = force ? "" : keepTagPrefixSuffix(buffer, "</think>");
842
+ buffer = force ? "" : keepTagPrefixSuffix(buffer, "</thinking>");
844
843
  break;
845
844
  }
846
845
 
@@ -851,7 +850,7 @@ function createThinkTagStreamFilter() {
851
850
 
852
851
  const open = findThinkOpenTag(buffer);
853
852
  if (!open) {
854
- const keep = force ? "" : keepTagPrefixSuffix(buffer, "<think");
853
+ const keep = force ? "" : keepTagPrefixSuffix(buffer, "<thinking>");
855
854
  output += buffer.slice(0, buffer.length - keep.length);
856
855
  buffer = keep;
857
856
  break;
@@ -862,38 +861,22 @@ function createThinkTagStreamFilter() {
862
861
  insideThink = true;
863
862
  }
864
863
 
865
- return output.replace(/<\/think\s*>/gi, "");
864
+ return output.replace(/<\/?(?:think|thinking)\s*>/gi, "");
866
865
  },
867
866
  };
868
867
  }
869
868
 
870
869
  function findThinkOpenTag(text: string): { index: number; end: number } | null {
871
- const lower = text.toLowerCase();
872
- const index = lower.indexOf("<think");
873
- if (index < 0) {
874
- return null;
875
- }
876
-
877
- const afterName = text[index + "<think".length];
878
- if (afterName && !/[\s>]/.test(afterName)) {
879
- const next = findThinkOpenTag(text.slice(index + 1));
880
- return next
881
- ? { index: index + 1 + next.index, end: index + 1 + next.end }
882
- : null;
883
- }
884
-
885
- const closeIndex = text.indexOf(">", index + "<think".length);
886
- if (closeIndex < 0) {
887
- return { index, end: text.length };
888
- }
889
-
890
- return { index, end: closeIndex + 1 };
870
+ const match = /<(think|thinking)\b[^>]*>/i.exec(text);
871
+ return match
872
+ ? { index: match.index, end: match.index + match[0].length }
873
+ : null;
891
874
  }
892
875
 
893
876
  function findThinkCloseTag(
894
877
  text: string,
895
878
  ): { index: number; end: number } | null {
896
- const match = /<\/think\s*>/i.exec(text);
879
+ const match = /<\/(think|thinking)\s*>/i.exec(text);
897
880
  return match
898
881
  ? { index: match.index, end: match.index + match[0].length }
899
882
  : null;
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) {
@@ -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} 因限流被跳过`);
@@ -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
@@ -120,7 +120,7 @@ export function createPokeHandler(
120
120
  ],
121
121
  },
122
122
  ),
123
- { userId, groupId, label: "poke" },
123
+ { userId, groupId, label: "poke", skipRetryOnRateLimit: true },
124
124
  );
125
125
  if (!result) return;
126
126
 
package/index.ts CHANGED
@@ -342,7 +342,7 @@ export default definePlugin({
342
342
  return;
343
343
  }
344
344
 
345
- roleModels = resolved.models;
345
+ roleModels = resolved.models;
346
346
  roleIsMultimodal = resolved.isMultimodal;
347
347
  await refreshBaseCache();
348
348
 
@@ -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(
@@ -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 RATE_LIMIT_RETRY_DELAY_MS = 5_000;
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
- // Wraps an AI request with global rate-limit blocking + per-user/group RPM
9
- // limiting. On a 429 it backs off and retries up to RATE_LIMIT_MAX_RETRIES.
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() + RATE_LIMIT_RETRY_DELAY_MS;
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?: { userId?: number; groupId?: number; label?: string },
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
- let retries = 0;
50
- while (true) {
51
- try {
52
- const result = await request();
53
- this.blockedUntil = 0;
54
- return result;
55
- } catch (err) {
56
- if (!this.isRateLimitError(err)) throw err;
57
- this.markBlocked();
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?: { userId?: number; groupId?: number; label?: string },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-chat",
3
- "version": "2.9.11",
3
+ "version": "2.10.1",
4
4
  "description": "AI 智能聊天插件",
5
5
  "main": "index.ts",
6
6
  "type": "module",