coding-tool-x 3.8.0 → 3.8.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.
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const { getCodexDir } = require('./codex-config');
4
4
  const { parseSession, parseSessionMeta, extractSessionMeta, readJSONL } = require('./codex-parser');
5
5
  const { globalCache, CacheKeys } = require('./enhanced-cache');
6
+ const { listProjects: idxListProjects, listSessions: idxListSessions, getSessionStatus: idxGetSessionStatus, getRecentSessions: idxGetRecent, searchSessions: idxSearch } = require('./session-history-index');
6
7
 
7
8
  const COUNTS_CACHE_TTL_MS = 30 * 1000;
8
9
  const SCAN_FILES_CACHE_TTL_MS = 15 * 1000;
@@ -227,78 +228,15 @@ function normalizeSession(codexSession) {
227
228
  * 聚合项目列表
228
229
  * @returns {Array} 项目对象数组
229
230
  */
230
- function getProjects(options = {}) {
231
- if (options.force) {
232
- invalidateCodexSessionCaches();
233
- } else {
234
- const cached = globalCache.get(CODEX_PROJECTS_CACHE_KEY);
235
- if (cached) {
236
- return cached;
237
- }
238
- }
239
-
240
- const sessions = getAllSessions();
241
- const projectMap = new Map();
242
-
243
- sessions.forEach(session => {
244
- const meta = session.meta;
245
-
246
- // 优先使用 Git 仓库名,否则使用 cwd 的最后一级目录
247
- const projectName = extractCodexProjectNameFromMeta(meta);
248
- const projectPath = (typeof meta.cwd === 'string' && meta.cwd.trim()) ? meta.cwd.trim() : projectName;
249
- if (!projectName) return;
250
-
251
- if (!projectMap.has(projectName)) {
252
- projectMap.set(projectName, {
253
- name: projectName,
254
- displayName: projectName,
255
- fullPath: projectPath,
256
- path: projectPath,
257
- gitRepo: meta.git?.repositoryUrl,
258
- branch: meta.git?.branch,
259
- sessionCount: 0,
260
- lastUsed: null,
261
- source: 'codex'
262
- });
263
- }
264
-
265
- const project = projectMap.get(projectName);
266
- project.sessionCount++;
267
-
268
- // 更新最后活动时间
269
- const sessionTime = session.mtimeMs || new Date(session.meta.timestamp || 0).getTime() || 0;
270
- if (!project.lastUsed || sessionTime > project.lastUsed) {
271
- project.lastUsed = sessionTime;
272
- }
273
- });
274
-
275
- // 获取保存的排序
231
+ async function getProjects(options = {}) {
232
+ const projects = await idxListProjects('codex', options);
276
233
  const savedOrder = getProjectOrder();
277
- const projects = Array.from(projectMap.values());
278
-
279
- // 应用保存的排序
280
- if (savedOrder.length > 0) {
281
- const ordered = [];
282
- const projectsMap = new Map(projects.map(p => [p.name, p]));
283
-
284
- // 按保存的顺序添加项目
285
- for (const projectName of savedOrder) {
286
- if (projectsMap.has(projectName)) {
287
- ordered.push(projectsMap.get(projectName));
288
- projectsMap.delete(projectName);
289
- }
290
- }
291
-
292
- // 添加剩余的新项目(不在保存顺序中的)
293
- ordered.push(...projectsMap.values());
294
- globalCache.set(CODEX_PROJECTS_CACHE_KEY, ordered, PROJECTS_CACHE_TTL_MS);
295
- return ordered;
296
- }
234
+ if (savedOrder.length === 0) return projects;
297
235
 
298
- // 默认按最后活动时间排序
299
- const sorted = projects.sort((a, b) => (b.lastUsed || 0) - (a.lastUsed || 0));
300
- globalCache.set(CODEX_PROJECTS_CACHE_KEY, sorted, PROJECTS_CACHE_TTL_MS);
301
- return sorted;
236
+ const byName = new Map(projects.map(project => [project.name, project]));
237
+ const ordered = savedOrder.flatMap(name => byName.has(name) ? [byName.get(name)] : []);
238
+ savedOrder.forEach(name => byName.delete(name));
239
+ return [...ordered, ...byName.values()];
302
240
  }
303
241
 
304
242
  /**
@@ -306,71 +244,23 @@ function getProjects(options = {}) {
306
244
  * @param {string} projectName - 项目名称
307
245
  * @returns {Array} 归一化的会话数组
308
246
  */
309
- function getSessionsByProject(projectName, options = {}) {
310
- const cacheKey = getCodexSessionsCacheKey(projectName);
311
- if (options.force) {
312
- invalidateCodexSessionCaches({ projectName });
313
- } else {
314
- const cached = globalCache.get(cacheKey);
315
- if (cached) {
316
- return cached;
317
- }
318
- }
319
-
320
- const sessions = getAllSessions();
321
-
322
- // 获取 fork 关系
323
- const { getForkRelations } = require('./sessions');
324
- const forkRelations = getForkRelations();
325
-
326
- // 获取保存的排序
247
+ async function getSessionsByProject(projectName, options = {}) {
248
+ const indexed = await idxListSessions('codex', projectName, options);
249
+ const forkRelations = require('./sessions').getForkRelations();
250
+ const aliases = require('./alias').loadAliases();
251
+ const sessions = indexed.map(session => ({
252
+ ...session,
253
+ mtime: new Date(session.mtime).toISOString(),
254
+ forkedFrom: forkRelations[session.sessionId] || null,
255
+ alias: aliases[session.sessionId] || null
256
+ }));
327
257
  const savedOrder = getSessionOrder(projectName);
258
+ if (savedOrder.length === 0) return sessions;
328
259
 
329
- // 过滤并归一化会话
330
- const filteredSessions = sessions
331
- .filter(session => {
332
- const sessionProjectName = extractCodexProjectNameFromMeta(session.meta);
333
- return sessionProjectName === projectName;
334
- })
335
- .map(session => {
336
- const normalized = normalizeSession(session);
337
- // 添加 fork 关系
338
- normalized.forkedFrom = forkRelations[normalized.sessionId] || null;
339
- return normalized;
340
- });
341
-
342
- // 应用保存的排序
343
- let orderedSessions = filteredSessions;
344
- if (savedOrder.length > 0) {
345
- const orderedFromSaved = [];
346
- const sessionMap = new Map(filteredSessions.map(s => [s.sessionId, s]));
347
-
348
- for (const sessionId of savedOrder) {
349
- const session = sessionMap.get(sessionId);
350
- if (session) {
351
- orderedFromSaved.push(session);
352
- sessionMap.delete(sessionId);
353
- }
354
- }
355
-
356
- const newSessions = [...sessionMap.values()];
357
- newSessions.sort((a, b) => {
358
- return new Date(b.mtime).getTime() - new Date(a.mtime).getTime();
359
- });
360
-
361
- // 新会话在前,旧会话在后(按保存顺序)
362
- orderedSessions = [...newSessions, ...orderedFromSaved];
363
- } else {
364
- // 默认按时间倒序
365
- orderedSessions.sort((a, b) => {
366
- return new Date(b.mtime).getTime() - new Date(a.mtime).getTime();
367
- });
368
- }
369
-
370
- globalCache.set(cacheKey, orderedSessions, PROJECT_SESSIONS_CACHE_TTL_MS);
371
- codexSessionCacheKeys.add(cacheKey);
372
-
373
- return orderedSessions;
260
+ const byId = new Map(sessions.map(session => [session.sessionId, session]));
261
+ const ordered = savedOrder.flatMap(id => byId.has(id) ? [byId.get(id)] : []);
262
+ savedOrder.forEach(id => byId.delete(id));
263
+ return [...byId.values(), ...ordered];
374
264
  }
375
265
 
376
266
  /**
@@ -378,23 +268,12 @@ function getSessionsByProject(projectName, options = {}) {
378
268
  * @param {string} sessionId - 会话 ID
379
269
  * @returns {Object|null} 归一化的会话对象
380
270
  */
381
- function getSessionById(sessionId) {
382
- const file = findSessionFileById(sessionId);
383
-
384
- if (!file) {
385
- return null;
386
- }
387
-
388
- const session = parseSession(file.filePath);
389
- if (!session) {
390
- return null;
391
- }
392
-
393
- return {
394
- ...normalizeSession(session),
395
- messages: session.messages, // 包含完整消息
396
- filePath: file.filePath
397
- };
271
+ async function getSessionById(sessionId) {
272
+ const status = await idxGetSessionStatus('codex', sessionId);
273
+ if (!status) return null;
274
+ const session = parseSession(status.filePath);
275
+ if (!session) return null;
276
+ return { ...normalizeSession(session), messages: session.messages, filePath: status.filePath };
398
277
  }
399
278
 
400
279
  /**
@@ -402,56 +281,8 @@ function getSessionById(sessionId) {
402
281
  * @param {string} keyword - 搜索关键词
403
282
  * @returns {Array} 搜索结果
404
283
  */
405
- function searchSessions(keyword) {
406
- const files = scanSessionFiles();
407
- const results = [];
408
-
409
- files.forEach(file => {
410
- // 使用完整解析获取消息内容
411
- const session = parseSession(file.filePath);
412
-
413
- if (!session || !session.messages || !Array.isArray(session.messages)) {
414
- return;
415
- }
416
-
417
- session.messages.forEach((message, index) => {
418
- if (message.role !== 'user' && message.role !== 'assistant') {
419
- return;
420
- }
421
-
422
- const content = (message.content || '').toLowerCase();
423
- const keywordLower = keyword.toLowerCase();
424
-
425
- if (content.includes(keywordLower)) {
426
- // 提取上下文
427
- const startIndex = Math.max(0, content.indexOf(keywordLower) - 50);
428
- const endIndex = Math.min(content.length, content.indexOf(keywordLower) + keyword.length + 50);
429
- const context = content.substring(startIndex, endIndex);
430
-
431
- // 确定项目名
432
- let projectName;
433
- if (session.meta?.git?.repositoryUrl) {
434
- projectName = session.meta.git.repositoryUrl.split('/').pop().replace('.git', '');
435
- } else if (session.meta?.cwd) {
436
- projectName = path.basename(session.meta.cwd);
437
- } else {
438
- projectName = 'Unknown';
439
- }
440
-
441
- results.push({
442
- sessionId: file.sessionId,
443
- projectName,
444
- messageIndex: index,
445
- role: message.role,
446
- context: (startIndex > 0 ? '...' : '') + context + (endIndex < content.length ? '...' : ''),
447
- timestamp: message.timestamp,
448
- source: 'codex'
449
- });
450
- }
451
- });
452
- });
453
-
454
- return results;
284
+ async function searchSessions(keyword) {
285
+ return idxSearch('codex', keyword);
455
286
  }
456
287
 
457
288
  /**
@@ -545,39 +376,16 @@ function deleteProject(projectName) {
545
376
  * @param {number} limit - 返回数量限制,默认 5
546
377
  * @returns {Array} 最近会话数组
547
378
  */
548
- function getRecentSessions(limit = 5) {
549
- const sessions = getAllSessions();
550
-
551
- // 获取 fork 关系和别名
552
- const { getForkRelations } = require('./sessions');
553
- const { loadAliases } = require('./alias');
554
- const forkRelations = getForkRelations();
555
- const aliases = loadAliases();
556
-
557
- // 归一化所有会话
558
- const allNormalizedSessions = sessions.map(session => {
559
- const normalized = normalizeSession(session);
560
-
561
- // 添加项目信息
562
- const projectName = extractCodexProjectNameFromMeta(session.meta) || 'Unknown';
563
- const projectPath = (typeof session.meta.cwd === 'string' && session.meta.cwd.trim())
564
- ? session.meta.cwd
565
- : projectName;
566
-
567
- return {
568
- ...normalized,
569
- forkedFrom: forkRelations[normalized.sessionId] || null,
570
- alias: aliases[normalized.sessionId] || null,
571
- projectName: projectName,
572
- projectDisplayName: projectName,
573
- projectFullPath: projectPath
574
- };
575
- });
576
-
577
- // 按 mtime 倒序排序,取前 N 个
578
- return allNormalizedSessions
579
- .sort((a, b) => new Date(b.mtime).getTime() - new Date(a.mtime).getTime())
580
- .slice(0, limit);
379
+ async function getRecentSessions(limit = 5) {
380
+ const indexed = await idxGetRecent('codex', limit);
381
+ const forkRelations = require('./sessions').getForkRelations();
382
+ const aliases = require('./alias').loadAliases();
383
+ return indexed.map(session => ({
384
+ ...session,
385
+ mtime: new Date(session.mtime).toISOString(),
386
+ forkedFrom: forkRelations[session.sessionId] || null,
387
+ alias: aliases[session.sessionId] || null
388
+ }));
581
389
  }
582
390
 
583
391
  /**
@@ -32,12 +32,12 @@ async function buildProjectsPayload(source, config = {}, options = {}) {
32
32
  }
33
33
  case 'codex': {
34
34
  const { getProjects } = require('./codex-sessions');
35
- const projects = getProjects({ force: options.force === true });
35
+ const projects = await getProjects({ force: options.force === true });
36
36
  return { projects, currentProject: projects[0] ? projects[0].name : null };
37
37
  }
38
38
  case 'gemini': {
39
39
  const { getProjects } = require('./gemini-sessions');
40
- const projects = getProjects({ force: options.force === true });
40
+ const projects = await getProjects({ force: options.force === true });
41
41
  return { projects, currentProject: projects[0] ? projects[0].name : null };
42
42
  }
43
43
  case 'opencode': {
@@ -47,7 +47,7 @@ async function buildProjectsPayload(source, config = {}, options = {}) {
47
47
  }
48
48
  case 'omp': {
49
49
  const { getProjects } = require('./omp-sessions');
50
- const projects = getProjects({ force: options.force === true });
50
+ const projects = await getProjects({ force: options.force === true });
51
51
  return { projects, currentProject: projects[0] ? projects[0].name : null };
52
52
  }
53
53
  default:
@@ -55,10 +55,10 @@ async function buildProjectsPayload(source, config = {}, options = {}) {
55
55
  }
56
56
  }
57
57
 
58
- function buildCountsPayload(source, config = {}, options = {}) {
58
+ async function buildCountsPayload(source, config = {}, options = {}) {
59
59
  switch (source) {
60
60
  case 'claude':
61
- return require('./sessions').getProjectAndSessionCounts(config, { force: options.force === true });
61
+ return require('./sessions').getProjectAndSessionCounts(config);
62
62
  case 'codex':
63
63
  return require('./codex-sessions').getProjectAndSessionCounts({ force: options.force === true });
64
64
  case 'gemini':
@@ -4,6 +4,7 @@ const crypto = require('crypto');
4
4
  const { HOME_DIR } = require('../../config/paths');
5
5
  const { getGeminiDir } = require('./gemini-config');
6
6
  const { resolveModelPricing } = require('../utils/pricing');
7
+ const { listProjects: idxListProjects, listSessions: idxListSessions, getSessionStatus: idxGetSessionStatus, getRecentSessions: idxGetRecent, searchSessions: idxSearch } = require('./session-history-index');
7
8
 
8
9
  const HASH_RE = /^[a-f0-9]{64}$/;
9
10
  const SESSION_FILE_RE = /^session-(.*)-([a-f0-9]+)\.(json|jsonl)$/;
@@ -628,59 +629,20 @@ function getProjectPath(projectHash, options = {}) {
628
629
  * 聚合项目列表
629
630
  * @returns {Array} 项目对象数组
630
631
  */
631
- function getProjects(options = {}) {
632
- if (options.force) {
633
- pathMappingCache = null;
634
- pathMappingCacheTime = 0;
635
- }
636
- const sessions = getAllSessions();
637
- const projectMap = new Map();
638
-
639
- sessions.forEach(session => {
640
- const projectHash = session.projectHash;
641
-
642
- if (!projectMap.has(projectHash)) {
643
- const projectPath = session.projectRoot || getProjectPath(projectHash);
644
-
645
- // 如果找到了真实路径,使用目录名作为显示名称
646
- let displayName;
647
- if (projectPath) {
648
- displayName = path.basename(projectPath);
649
- // 如果是 home 目录,显示 ~
650
- if (projectPath === HOME_DIR) {
651
- displayName = '~';
652
- }
653
- } else {
654
- // 未找到路径,使用 hash 前 8 位
655
- displayName = `Project ${projectHash.substring(0, 8)}`;
656
- }
657
-
658
- projectMap.set(projectHash, {
659
- name: projectHash,
660
- displayName,
661
- path: projectPath,
662
- fullPath: projectPath,
663
- storageName: session.storageName,
664
- sessionCount: 0,
665
- lastUpdated: session.lastUpdated,
666
- source: 'gemini'
667
- });
668
- }
669
-
670
- const project = projectMap.get(projectHash);
671
- project.sessionCount++;
672
-
673
- // 更新最后活动时间
674
- if (new Date(session.lastUpdated) > new Date(project.lastUpdated)) {
675
- project.lastUpdated = session.lastUpdated;
676
- }
632
+ async function getProjects(options = {}) {
633
+ const projects = await idxListProjects('gemini', options);
634
+ const paths = buildPathMapping();
635
+ return projects.map(project => {
636
+ const fullPath = project.fullPath || paths.get(project.name) || null;
637
+ return {
638
+ ...project,
639
+ displayName: fullPath ? path.basename(fullPath) : project.displayName,
640
+ path: fullPath,
641
+ fullPath,
642
+ storageName: fullPath ? path.basename(fullPath) : project.name,
643
+ lastUpdated: new Date(project.lastUsed).toISOString()
644
+ };
677
645
  });
678
-
679
- // 转换为数组并排序(按最后活动时间)
680
- const projects = Array.from(projectMap.values());
681
- projects.sort((a, b) => new Date(b.lastUpdated) - new Date(a.lastUpdated));
682
-
683
- return projects;
684
646
  }
685
647
 
686
648
  /**
@@ -688,15 +650,25 @@ function getProjects(options = {}) {
688
650
  * @param {string} projectHash - 项目 hash
689
651
  * @returns {Array} 会话对象数组
690
652
  */
691
- function getProjectSessions(projectHash, options = {}) {
692
- if (options.force) {
693
- pathMappingCache = null;
694
- pathMappingCacheTime = 0;
695
- }
696
- const allSessions = getAllSessions();
697
- return allSessions
698
- .filter(session => session.projectHash === projectHash)
699
- .map(normalizeSession);
653
+ async function getProjectSessions(projectHash, options = {}) {
654
+ const indexed = await idxListSessions('gemini', projectHash, options);
655
+ return indexed.map(session => ({
656
+ sessionId: session.sessionId,
657
+ mtime: new Date(session.mtime).toISOString(),
658
+ size: session.size,
659
+ filePath: session.filePath,
660
+ gitBranch: null,
661
+ firstMessage: session.firstMessage,
662
+ forkedFrom: session.extra?.forkedFrom || null,
663
+ source: 'gemini',
664
+ tokens: session.tokens?.total ?? session.tokens ?? 0,
665
+ cost: session.extra?.cost || 0,
666
+ model: session.model || 'gemini-2.5-pro',
667
+ projectHash: session.projectName,
668
+ projectName: session.projectName,
669
+ storageName: session.extra?.storageName || session.projectName,
670
+ projectRoot: session.projectFullPath || null
671
+ }));
700
672
  }
701
673
 
702
674
  /**
@@ -704,15 +676,9 @@ function getProjectSessions(projectHash, options = {}) {
704
676
  * @param {string} sessionId - 会话 ID
705
677
  * @returns {Object|null} 完整会话数据
706
678
  */
707
- function getSession(sessionId) {
708
- const allSessions = getAllSessions();
709
- const session = allSessions.find(s => s.sessionId === sessionId);
710
-
711
- if (!session) {
712
- return null;
713
- }
714
-
715
- return readSessionFull(session.filePath);
679
+ async function getSession(sessionId) {
680
+ const status = await idxGetSessionStatus('gemini', sessionId);
681
+ return status ? readSessionFull(status.filePath) : null;
716
682
  }
717
683
 
718
684
  /**
@@ -773,11 +739,13 @@ function saveProjectOrder(order) {
773
739
  * @param {number} limit - 限制数量
774
740
  * @returns {Array} 会话对象数组
775
741
  */
776
- function getRecentSessions(limit = 5) {
777
- const allSessions = getAllSessions();
778
- return allSessions
779
- .slice(0, limit)
780
- .map(normalizeSession);
742
+ async function getRecentSessions(limit = 5) {
743
+ const sessions = await idxGetRecent('gemini', limit);
744
+ return sessions.map(session => ({
745
+ ...session,
746
+ mtime: new Date(session.mtime).toISOString(),
747
+ projectHash: session.projectName
748
+ }));
781
749
  }
782
750
 
783
751
  /**
@@ -785,26 +753,16 @@ function getRecentSessions(limit = 5) {
785
753
  * @param {string} sessionId - 会话 ID
786
754
  * @returns {Object|null} 完整会话数据
787
755
  */
788
- function getSessionById(sessionId) {
789
- const allSessions = getAllSessions();
790
- const sessionMeta = allSessions.find(s => s.sessionId === sessionId);
791
-
792
- if (!sessionMeta) {
793
- return null;
794
- }
795
-
796
- const fullSession = readSessionFull(sessionMeta.filePath);
797
-
798
- if (!fullSession) {
799
- return null;
800
- }
801
-
802
- // 合并元数据
756
+ async function getSessionById(sessionId) {
757
+ const status = await idxGetSessionStatus('gemini', sessionId);
758
+ if (!status) return null;
759
+ const fullSession = readSessionFull(status.filePath);
760
+ if (!fullSession) return null;
803
761
  return {
804
762
  ...fullSession,
805
- filePath: sessionMeta.filePath,
806
- size: sessionMeta.size,
807
- mtime: sessionMeta.mtime,
763
+ filePath: status.filePath,
764
+ size: status.size,
765
+ mtime: new Date(status.lastModified).toISOString(),
808
766
  source: 'gemini'
809
767
  };
810
768
  }
@@ -815,70 +773,22 @@ function getSessionById(sessionId) {
815
773
  * @param {number} contextLength - 上下文长度(可选)
816
774
  * @returns {Array} 搜索结果数组
817
775
  */
818
- function searchSessions(keyword, contextLength = 35) {
819
- const allSessions = getAllSessions();
820
- const results = [];
821
-
822
- allSessions.forEach(sessionMeta => {
823
- const fullSession = readSessionFull(sessionMeta.filePath);
824
-
825
- if (!fullSession || !fullSession.messages) {
826
- return;
827
- }
828
-
829
- const matches = [];
830
-
831
- fullSession.messages.forEach((msg, index) => {
832
- let content = '';
833
-
834
- // 提取消息内容
835
- if (msg.type === 'user' || msg.type === 'assistant' || msg.type === 'gemini') {
836
- content = extractContentText(msg.content);
837
- }
838
-
839
- // 搜索关键词
840
- const lowerContent = content.toLowerCase();
841
- const lowerKeyword = keyword.toLowerCase();
842
-
843
- if (lowerContent.includes(lowerKeyword)) {
844
- const keywordIndex = lowerContent.indexOf(lowerKeyword);
845
-
846
- // 提取上下文
847
- const start = Math.max(0, keywordIndex - contextLength);
848
- const end = Math.min(content.length, keywordIndex + keyword.length + contextLength);
849
-
850
- let context = content.substring(start, end);
851
-
852
- // 添加省略号
853
- if (start > 0) context = '...' + context;
854
- if (end < content.length) context = context + '...';
855
-
856
- matches.push({
857
- messageIndex: index,
858
- role: msg.type,
859
- context,
860
- timestamp: msg.timestamp
861
- });
862
- }
863
- });
864
-
865
- if (matches.length > 0) {
866
- results.push({
867
- sessionId: sessionMeta.sessionId,
868
- projectHash: sessionMeta.projectHash,
869
- firstMessage: sessionMeta.firstMessage,
870
- lastUpdated: sessionMeta.lastUpdated,
871
- matches,
872
- matchCount: matches.length,
873
- source: 'gemini'
874
- });
875
- }
876
- });
877
-
878
- // 按匹配数量排序
879
- results.sort((a, b) => b.matchCount - a.matchCount);
880
-
881
- return results;
776
+ async function searchSessions(keyword, contextLength = 35) {
777
+ const results = await idxSearch('gemini', keyword, { contextLength });
778
+ return results.map(result => ({
779
+ sessionId: result.sessionId,
780
+ projectHash: result.projectName,
781
+ firstMessage: result.firstMessage,
782
+ lastUpdated: result.updatedAt || null,
783
+ matches: result.matches.map(match => ({
784
+ messageIndex: match.ordinal,
785
+ role: match.type === 'gemini' ? 'gemini' : match.role,
786
+ context: match.context,
787
+ timestamp: match.timestamp
788
+ })),
789
+ matchCount: result.matchCount,
790
+ source: 'gemini'
791
+ }));
882
792
  }
883
793
 
884
794
  /**