mioku-plugin-chat 3.0.6 → 3.1.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.
@@ -4,7 +4,7 @@ import type { TargetMessage } from "../types";
4
4
 
5
5
  export interface StructuredUserInput {
6
6
  userName: string;
7
- userId: number;
7
+ userId: string;
8
8
  userRole?: string;
9
9
  userTitle?: string;
10
10
  content: string;
@@ -192,7 +192,7 @@ function formatStructuredUserContent(input: StructuredUserInput): string {
192
192
  const lines = [
193
193
  "[Group Member Message]",
194
194
  `Name: ${input.userName || "unknown"}`,
195
- `QQ: ${input.userId || 0}`,
195
+ `user_id: ${input.userId || "unknown"}`,
196
196
  `Time: ${formatTimestamp(input.timestamp)}`,
197
197
  ];
198
198
 
@@ -25,7 +25,7 @@ export class IdleCheckManager {
25
25
  private groupLastBotMessageTime = new Map<string, number>();
26
26
  private groupMessageCountAfterBot = new Map<string, number>();
27
27
  private groupLastIdleCheckTime = new Map<string, number>();
28
- private groupBotsMapping = new Map<string, Set<number>>();
28
+ private groupBotsMapping = new Map<string, Set<string>>();
29
29
  private intervalHandle: NodeJS.Timeout | null = null;
30
30
 
31
31
  private ctx: MiokuContext;
@@ -71,11 +71,11 @@ export class IdleCheckManager {
71
71
  this.groupMessageCount.set(groupSessionId, count + 1);
72
72
  }
73
73
 
74
- recordBotMessages(groupSessionId: string, count: number, selfId: number): void {
74
+ recordBotMessages(groupSessionId: string, count: number, selfId: string): void {
75
75
  const current = this.groupMessageCountAfterBot.get(groupSessionId) ?? 0;
76
76
  this.groupMessageCountAfterBot.set(groupSessionId, current + count);
77
77
  let bots = this.groupBotsMapping.get(groupSessionId);
78
- if (!bots) { bots = new Set<number>(); this.groupBotsMapping.set(groupSessionId, bots); }
78
+ if (!bots) { bots = new Set<string>(); this.groupBotsMapping.set(groupSessionId, bots); }
79
79
  bots.add(selfId);
80
80
  this.groupLastBotMessageTime.set(groupSessionId, Date.now());
81
81
  }
@@ -106,14 +106,15 @@ export class IdleCheckManager {
106
106
 
107
107
  const now = Date.now();
108
108
  const checkInterval = 60_000;
109
- const allBotIds = Array.from(this.ctx.bots).map((bot) => Number(bot.bot_id));
109
+ const allBotIds = Array.from(this.ctx.bots).map((bot) => String(bot.bot_id));
110
110
 
111
111
  for (const [groupSessionId, lastTime] of this.groupLastActivityTime) {
112
112
  const lastCheckTime =
113
113
  this.groupLastIdleCheckTime.get(groupSessionId) ?? 0;
114
114
  if (now - lastCheckTime < checkInterval) continue;
115
115
 
116
- const groupId = parseInt(groupSessionId.split(":")[1], 10);
116
+ const groupId = String(groupSessionId.split(":")[1] ?? "").trim();
117
+ if (!groupId) continue;
117
118
  const cfg = this.configProvider(groupId);
118
119
  if (!cfg.planner?.enabled) continue;
119
120
  if (!isGroupAllowed(groupId, cfg)) continue;
@@ -130,7 +131,7 @@ export class IdleCheckManager {
130
131
  cfg.planner.idleCheckBotIds ??
131
132
  baseCfg.planner?.idleCheckBotIds ??
132
133
  allBotIds;
133
- const enabledBotIds = idleCheckBotIds.filter((id: number) =>
134
+ const enabledBotIds = idleCheckBotIds.filter((id: string) =>
134
135
  allBotIds.includes(id),
135
136
  );
136
137
 
@@ -193,8 +194,8 @@ export class IdleCheckManager {
193
194
 
194
195
  private async processIdleCheckTurn(
195
196
  groupSessionId: string,
196
- groupId: number,
197
- selfId: number,
197
+ groupId: string,
198
+ selfId: string,
198
199
  cfg: ChatConfig,
199
200
  scheduledLastActivity: number,
200
201
  scheduledAt: number,
@@ -234,7 +235,7 @@ export class IdleCheckManager {
234
235
 
235
236
  const targetMessage: TargetMessage = {
236
237
  userName: "system",
237
- userId: 0,
238
+ userId: "",
238
239
  userRole: "member",
239
240
  content: "[No one in the group is talking? I'll answer!]",
240
241
  messageId: "",
@@ -246,7 +247,7 @@ export class IdleCheckManager {
246
247
  event: null,
247
248
  groupSessionId,
248
249
  groupId,
249
- userId: 0,
250
+ userId: "",
250
251
  config: cfg,
251
252
  aiService: this.aiService,
252
253
  db: this.db,
@@ -292,7 +293,7 @@ export class IdleCheckManager {
292
293
  result,
293
294
  groupId,
294
295
  groupSessionId,
295
- userId: 0,
296
+ userId: "",
296
297
  selfId,
297
298
  toolCtx,
298
299
  send: true,
@@ -25,7 +25,7 @@ import { finalizeChatTurn } from "../core/chat-turn";
25
25
  import { extractContent, getBotRole } from "../utils";
26
26
 
27
27
  interface DynamicDelayQueueData {
28
- messages: Array<{ event: any; content: string; userName: string; userId: number; messageId: string; timestamp: number }>;
28
+ messages: Array<{ event: any; content: string; userName: string; userId: string; messageId: string; timestamp: number }>;
29
29
  timer: NodeJS.Timeout | null;
30
30
  delayUntil: number;
31
31
  }
@@ -91,7 +91,7 @@ export class QueueProcessor {
91
91
  queueData.messages.push({ event, content, userName, userId: event.user_id, messageId: event.message_id, timestamp: Date.now() });
92
92
  }
93
93
 
94
- startDynamicDelayTimer(groupSessionId: string, groupId: number, delayMs: number, selfId: number): void {
94
+ startDynamicDelayTimer(groupSessionId: string, groupId: string, delayMs: number, selfId: string): void {
95
95
  let queueData = this.dynamicDelayQueues.get(groupSessionId);
96
96
  if (!queueData) {
97
97
  queueData = { messages: [], timer: null, delayUntil: Date.now() + delayMs };
@@ -132,7 +132,7 @@ export class QueueProcessor {
132
132
  return queue != null && Date.now() < queue.delayUntil;
133
133
  }
134
134
 
135
- scheduleQueuedMessages(groupSessionId: string, selfId: number): void {
135
+ scheduleQueuedMessages(groupSessionId: string, selfId: string): void {
136
136
  void this.sessionTurnScheduler
137
137
  .run(
138
138
  groupSessionId,
@@ -156,8 +156,8 @@ export class QueueProcessor {
156
156
 
157
157
  private async processDynamicDelayMessages(
158
158
  groupSessionId: string,
159
- groupId: number,
160
- selfId: number,
159
+ groupId: string,
160
+ selfId: string,
161
161
  messages: DynamicDelayQueueData["messages"],
162
162
  ): Promise<void> {
163
163
  this.ctx.logger.info(`[DynamicDelay] group ${groupId} processes ${messages.length} delayed messages`);
@@ -217,7 +217,7 @@ export class QueueProcessor {
217
217
  });
218
218
  }
219
219
 
220
- async processQueuedMessages(groupSessionId: string, selfId: number): Promise<void> {
220
+ async processQueuedMessages(groupSessionId: string, selfId: string): Promise<void> {
221
221
  try {
222
222
  const queue = this.queueManager.getQueue(groupSessionId);
223
223
  if (!queue || queue.length === 0) {
@@ -225,7 +225,8 @@ export class QueueProcessor {
225
225
  return;
226
226
  }
227
227
 
228
- const groupId = parseInt(groupSessionId.split(":")[1], 10);
228
+ const groupId = String(groupSessionId.split(":")[1] ?? "").trim();
229
+ if (!groupId) return;
229
230
  this.ctx.logger.info(`[Queue] group ${groupSessionId} batch ${queue.length} messages`);
230
231
 
231
232
  const queuedContents: string[] = [];
@@ -255,7 +256,7 @@ export class QueueProcessor {
255
256
  const mergedContent = queuedContents.join("\n");
256
257
 
257
258
  const targetMessage: TargetMessage = {
258
- userName, userId: firstItem.event.user_id || firstItem.event.sender?.user_id,
259
+ userName, userId: String(firstItem.event.user_id ?? firstItem.event.sender?.user_id ?? "").trim(),
259
260
  userRole: firstItem.event.sender?.role || "member", content: mergedContent,
260
261
  messageId: firstItem.event.message_id, timestamp: Date.now(),
261
262
  };
@@ -52,8 +52,8 @@ export class RateLimitGuard {
52
52
  async run<T>(
53
53
  request: () => Promise<T>,
54
54
  opts?: {
55
- userId?: number;
56
- groupId?: number;
55
+ userId?: string;
56
+ groupId?: string;
57
57
  label?: string;
58
58
  skipRetryOnRateLimit?: boolean;
59
59
  },
@@ -13,16 +13,16 @@ const DEFAULT_AI_REQUEST_LIMITS: AIRequestLimitConfig = {
13
13
  windowMs: 60_000,
14
14
  };
15
15
 
16
- export type ChatConfigProvider = (groupId?: number) => ChatConfig;
16
+ export type ChatConfigProvider = (groupId?: string) => ChatConfig;
17
17
 
18
18
  export class RateLimiter {
19
- private userTriggers: Map<number, number[]> = new Map();
20
- private userMessages: Map<number, { content: string; timestamp: number }[]> =
19
+ private userTriggers: Map<string, number[]> = new Map();
20
+ private userMessages: Map<string, { content: string; timestamp: number }[]> =
21
21
  new Map();
22
- private groupLastResponse: Map<number, number> = new Map();
23
- private groupInteractions: Map<number, Map<number, number[]>> = new Map();
24
- private userAiRequests: Map<number, number[]> = new Map();
25
- private groupAiRequests: Map<number, number[]> = new Map();
22
+ private groupLastResponse: Map<string, number> = new Map();
23
+ private groupInteractions: Map<string, Map<string, number[]>> = new Map();
24
+ private userAiRequests: Map<string, number[]> = new Map();
25
+ private groupAiRequests: Map<string, number[]> = new Map();
26
26
 
27
27
  private readonly maxTriggersPerWindow: number;
28
28
  private readonly windowMs: number;
@@ -30,13 +30,13 @@ export class RateLimiter {
30
30
  private readonly groupCooldownMs: number;
31
31
  private readonly cleanupTimer: ReturnType<typeof setInterval>;
32
32
  private getConfig: ChatConfigProvider;
33
- private getQueueLengthFn: ((groupId: number) => number) | null = null;
33
+ private getQueueLengthFn: ((groupId: string) => number) | null = null;
34
34
 
35
35
  setConfigProvider(provider: ChatConfigProvider): void {
36
36
  this.getConfig = provider;
37
37
  }
38
38
 
39
- setQueueLengthGetter(fn: (groupId: number) => number): void {
39
+ setQueueLengthGetter(fn: (groupId: string) => number): void {
40
40
  this.getQueueLengthFn = fn;
41
41
  }
42
42
 
@@ -58,17 +58,17 @@ export class RateLimiter {
58
58
  this.cleanupTimer = setInterval(() => this.cleanup(), 300_000);
59
59
  }
60
60
 
61
- private getDynamicDelay(groupId?: number): DynamicDelayConfig {
61
+ private getDynamicDelay(groupId?: string): DynamicDelayConfig {
62
62
  return this.getConfig(groupId)?.dynamicDelay ?? DEFAULT_DYNAMIC_DELAY;
63
63
  }
64
64
 
65
- private getAiRequestLimits(groupId?: number): AIRequestLimitConfig {
65
+ private getAiRequestLimits(groupId?: string): AIRequestLimitConfig {
66
66
  return this.getConfig(groupId)?.aiRequestLimits ?? DEFAULT_AI_REQUEST_LIMITS;
67
67
  }
68
68
 
69
69
  canProcess(
70
- userId: number,
71
- groupId: number | undefined,
70
+ userId: string,
71
+ groupId: string | undefined,
72
72
  content: string,
73
73
  ): boolean {
74
74
  const now = Date.now();
@@ -93,7 +93,7 @@ export class RateLimiter {
93
93
  return !recentSame;
94
94
  }
95
95
 
96
- record(userId: number, groupId: number | undefined, content: string): void {
96
+ record(userId: string, groupId: string | undefined, content: string): void {
97
97
  const now = Date.now();
98
98
 
99
99
  const triggers = this.userTriggers.get(userId) ?? [];
@@ -110,7 +110,7 @@ export class RateLimiter {
110
110
  }
111
111
  }
112
112
 
113
- recordInteraction(groupId: number, userId: number): void {
113
+ recordInteraction(groupId: string, userId: string): void {
114
114
  const dynamicDelay = this.getDynamicDelay(groupId);
115
115
  if (!dynamicDelay.enabled) return;
116
116
 
@@ -129,7 +129,7 @@ export class RateLimiter {
129
129
  groupUsers.set(userId, timestamps);
130
130
  }
131
131
 
132
- canRunAIRequest(userId?: number, groupId?: number): boolean {
132
+ canRunAIRequest(userId?: string, groupId?: string): boolean {
133
133
  const now = Date.now();
134
134
  const { userRpm, groupRpm, windowMs } = this.getAiRequestLimits(groupId);
135
135
 
@@ -154,7 +154,7 @@ export class RateLimiter {
154
154
  return true;
155
155
  }
156
156
 
157
- recordAIRequest(userId?: number, groupId?: number): void {
157
+ recordAIRequest(userId?: string, groupId?: string): void {
158
158
  const now = Date.now();
159
159
  const { windowMs } = this.getAiRequestLimits(groupId);
160
160
 
@@ -175,7 +175,7 @@ export class RateLimiter {
175
175
  }
176
176
  }
177
177
 
178
- getInteractionCount(groupId: number): number {
178
+ getInteractionCount(groupId: string): number {
179
179
  const now = Date.now();
180
180
  const { interactionWindowMs: windowMs } = this.getDynamicDelay(groupId);
181
181
 
@@ -199,7 +199,7 @@ export class RateLimiter {
199
199
  return count;
200
200
  }
201
201
 
202
- calculateDelay(groupId: number): number {
202
+ calculateDelay(groupId: string): number {
203
203
  const dynamicDelay = this.getDynamicDelay(groupId);
204
204
  if (!dynamicDelay.enabled) return 0;
205
205
 
@@ -211,7 +211,7 @@ export class RateLimiter {
211
211
  return Math.min(delay, maxDelayMs);
212
212
  }
213
213
 
214
- getDelayInfo(groupId: number): {
214
+ getDelayInfo(groupId: string): {
215
215
  delayMs: number;
216
216
  interactionCount: number;
217
217
  shouldDelay: boolean;
@@ -225,7 +225,7 @@ export class RateLimiter {
225
225
  };
226
226
  }
227
227
 
228
- clearGroupInteractions(groupId: number): void {
228
+ clearGroupInteractions(groupId: string): void {
229
229
  this.groupInteractions.delete(groupId);
230
230
  }
231
231
 
package/manage/session.ts CHANGED
@@ -17,7 +17,7 @@ export class SessionManager {
17
17
  /**
18
18
  * 获取或创建会话
19
19
  */
20
- getOrCreate(id: string, type: SessionType, targetId: number): SessionMeta {
20
+ getOrCreate(id: string, type: SessionType, targetId: string): SessionMeta {
21
21
  // 先查缓存
22
22
  if (this.cache.has(id)) {
23
23
  return this.touch(id);
package/manage/types.ts CHANGED
@@ -31,27 +31,27 @@ export type HistoryMediaOptions = {
31
31
  export type RunRateLimitGuard = <T>(
32
32
  request: () => Promise<T>,
33
33
  opts?: {
34
- userId?: number;
35
- groupId?: number;
34
+ userId?: string;
35
+ groupId?: string;
36
36
  label?: string;
37
37
  skipRetryOnRateLimit?: boolean;
38
38
  },
39
39
  ) => Promise<T | null>;
40
40
 
41
41
  export type GetGroupHistoryMessages = (
42
- groupId: number,
42
+ groupId: string,
43
43
  groupSessionId: string,
44
44
  ctx: MiokuContext,
45
45
  historyCount: number,
46
46
  db: ChatDatabase,
47
- selfId: number,
47
+ selfId: string,
48
48
  mediaOptions?: HistoryMediaOptions,
49
49
  ) => Promise<{ history: ChatMessage[] }>;
50
50
 
51
51
  export type GetGroupInfoData = (
52
52
  ctx: MiokuContext,
53
- groupId: number,
54
- selfId: number,
53
+ groupId: string,
54
+ selfId: string,
55
55
  fallbackGroupName?: string,
56
56
  ) => Promise<GroupInfoResult>;
57
57
 
@@ -60,16 +60,16 @@ export type GetHumanizeContexts = (
60
60
  groupSessionId: string,
61
61
  userName: string,
62
62
  history: ChatMessage[],
63
- triggerUserId?: number,
63
+ triggerUserId?: string,
64
64
  ) => Promise<HumanizeContextsResult>;
65
65
 
66
66
  export type SendAIResponse = (
67
67
  options: SendAIResponseOptions,
68
- selfId: number,
68
+ selfId: string,
69
69
  ) => Promise<Array<string | undefined>>;
70
70
 
71
71
  export type SaveBotMessages = (
72
- groupId: number,
72
+ groupId: string,
73
73
  groupSessionId: string,
74
74
  messages: string[],
75
75
  timestamp: number,
@@ -82,28 +82,28 @@ export type SaveBotMessages = (
82
82
 
83
83
  export type SendEmoji = (
84
84
  ctx: MiokuContext,
85
- groupId: number,
85
+ groupId: string,
86
86
  emojiPath: string | null | undefined,
87
87
  bot?: import("mioku").Bot,
88
88
  ) => Promise<void>;
89
89
 
90
90
  export type SendMessage = (
91
91
  ctx: MiokuContext,
92
- groupId: number | undefined,
93
- userId: number,
92
+ groupId: string | undefined,
93
+ userId: string,
94
94
  text: string,
95
95
  config: ChatConfig,
96
- selfId: number,
96
+ selfId: string,
97
97
  audioService?: import("mioku-service-audio").AudioServiceApi,
98
98
  ) => Promise<void>;
99
99
 
100
100
  export type BuildToolContext = (options: {
101
101
  ctx: MiokuContext;
102
102
  event: any;
103
- selfId: number;
103
+ selfId: string;
104
104
  groupSessionId: string;
105
- groupId?: number;
106
- userId: number;
105
+ groupId?: string;
106
+ userId: string;
107
107
  config: ChatConfig;
108
108
  aiService: AIService;
109
109
  db: ChatDatabase;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-chat",
3
- "version": "3.0.6",
3
+ "version": "3.1.1",
4
4
  "description": "AI 智能聊天插件",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -12,21 +12,21 @@ import { executeChatRuntimeRequest } from "../core/chat-turn";
12
12
 
13
13
  function resolveRuntimeGroupId(
14
14
  options: ChatRuntimeNoticeOptions | ChatRuntimeInformationRequestOptions,
15
- ): number | undefined {
15
+ ): string | undefined {
16
16
  if ("event" in options) {
17
17
  const event = options.event;
18
18
  if (event?.kind !== "message" || event.message_type !== "group") {
19
19
  return undefined;
20
20
  }
21
- const groupId: unknown = event.group_id;
22
- return typeof groupId === "number" ? groupId : undefined;
21
+ const groupId = String(event.group_id ?? "").trim();
22
+ return groupId || undefined;
23
23
  }
24
24
  return "groupId" in options ? options.groupId : undefined;
25
25
  }
26
26
 
27
27
  export function createChatRuntime(
28
28
  pluginCtx: ChatPluginContext,
29
- getConfig: (groupId?: number) => Promise<ChatConfig>,
29
+ getConfig: (groupId?: string) => Promise<ChatConfig>,
30
30
  ): ChatRuntime {
31
31
  return {
32
32
  async requestInformation(
package/types.ts CHANGED
@@ -56,7 +56,7 @@ export interface PlannerConfig {
56
56
  // 群聊记录保底消息数量
57
57
  idleMessageCount: number;
58
58
  // 空闲检查的 bot ID 列表
59
- idleCheckBotIds: number[];
59
+ idleCheckBotIds: string[];
60
60
  }
61
61
 
62
62
  /**
@@ -183,9 +183,9 @@ export interface ChatConfig {
183
183
  webReader: WebReaderConfig;
184
184
  audio: AudioConfig;
185
185
  historyCount: number;
186
- blacklistGroups: number[];
187
- whitelistGroups: number[];
188
- mediaAnalysisBlacklistUsers: number[];
186
+ blacklistGroups: string[];
187
+ whitelistGroups: string[];
188
+ mediaAnalysisBlacklistUsers: string[];
189
189
  maxSessions: number;
190
190
  maxIterations: number;
191
191
  enableExternalSkills: boolean;
@@ -249,7 +249,7 @@ export type SessionType = "group" | "personal";
249
249
  export interface SessionMeta {
250
250
  id: string; // "group:{group_id}" 或 "personal:{user_id}"
251
251
  type: SessionType;
252
- targetId: number; // group_id 或 user_id
252
+ targetId: string; // group_id 或 user_id
253
253
  createdAt: number;
254
254
  updatedAt: number;
255
255
  compressedContext: string | null;
@@ -263,11 +263,11 @@ export interface ChatMessage {
263
263
  sessionId: string;
264
264
  role: "user" | "assistant" | "system";
265
265
  content: string; // 存储时统一为字符串
266
- userId?: number;
266
+ userId?: string;
267
267
  userName?: string;
268
268
  userRole?: string; // "owner" | "admin" | "member"
269
269
  userTitle?: string;
270
- groupId?: number;
270
+ groupId?: string;
271
271
  groupName?: string;
272
272
  timestamp: number;
273
273
  messageId?: string; // 消息 id
@@ -278,7 +278,7 @@ export interface ChatMessage {
278
278
  */
279
279
  export interface TargetMessage {
280
280
  userName: string;
281
- userId: number;
281
+ userId: string;
282
282
  userRole: string;
283
283
  userTitle?: string;
284
284
  content: string;
@@ -303,8 +303,8 @@ export interface ToolContext {
303
303
  ctx: MiokuContext;
304
304
  event: any;
305
305
  sessionId: string;
306
- groupId?: number;
307
- userId: number;
306
+ groupId?: string;
307
+ userId: string;
308
308
  triggerSkillRole: SkillPermissionRole;
309
309
  config: ChatConfig;
310
310
  aiService: AIService;
@@ -364,7 +364,7 @@ export interface TopicRecord {
364
364
  export interface ExpressionRecord {
365
365
  id?: number;
366
366
  sessionId: string;
367
- userId: number;
367
+ userId: string;
368
368
  userName: string;
369
369
  situation: string; // 使用场景
370
370
  style: string; // 表达风格
package/utils/config.ts CHANGED
@@ -33,20 +33,20 @@ export function mergeChatConfig<T extends Record<string, any>>(
33
33
  return result as T;
34
34
  }
35
35
 
36
- export function normalizeIdList(input: unknown): number[] {
36
+ export function normalizeIdList(input: unknown): string[] {
37
37
  if (!Array.isArray(input)) return [];
38
38
  return Array.from(
39
39
  new Set(
40
40
  input
41
- .map((item) => Math.floor(Number(item)))
42
- .filter((id) => Number.isFinite(id) && id > 0),
41
+ .map((item) => String(item ?? "").trim())
42
+ .filter((id) => id.length > 0),
43
43
  ),
44
44
  );
45
45
  }
46
46
 
47
47
  export function normalizeMediaAnalysisBlacklist(
48
48
  config: Record<string, any>,
49
- ): number[] {
49
+ ): string[] {
50
50
  const current = config.mediaAnalysisBlacklistUsers;
51
51
  const legacy = config.imageAnalysisBlacklistUsers;
52
52
  const source =
@@ -70,10 +70,9 @@ export function mergeGroupOverrides(
70
70
  /**
71
71
  * 从 sessionId(如 "group:123")提取群号,个人会话返回 undefined
72
72
  */
73
- export function extractGroupIdFromSession(sessionId: string): number | undefined {
73
+ export function extractGroupIdFromSession(sessionId: string): string | undefined {
74
74
  if (!sessionId || typeof sessionId !== "string") return undefined;
75
75
  if (!sessionId.startsWith("group:")) return undefined;
76
- const raw = sessionId.slice("group:".length);
77
- const parsed = parseInt(raw, 10);
78
- return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
76
+ const raw = sessionId.slice("group:".length).trim();
77
+ return raw.length > 0 ? raw : undefined;
79
78
  }
package/utils/index.ts CHANGED
@@ -9,3 +9,5 @@ export {
9
9
  buildChatMessageFromEvent,
10
10
  normalizeIdList,
11
11
  } from "./message";
12
+
13
+ export { mainModelSupportsVision } from "./model";