foliko 1.0.86 → 1.1.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.
Files changed (104) hide show
  1. package/.agent/data/default.json +3 -4310
  2. package/.agent/data/plugins-state.json +34 -1
  3. package/.agent/mcp_config.json +0 -1
  4. package/.agent/memory/core.md +1 -0
  5. package/.agent/memory/project/mnn93ogy-ypjn27.md +9 -0
  6. package/.agent/memory/project/mnn98fqy-5nhc1u.md +25 -0
  7. package/.agent/memory/user/mnm67t9m-x8rekk.md +9 -0
  8. package/.agent/memory/user/mnn5mmqh-w6aktx.md +11 -0
  9. package/.agent/memory/user/mnnbfhhn-dk1bd1.md +22 -0
  10. package/.agent/plugins/__pycache__/file_writer.cpython-312.pyc +0 -0
  11. package/.agent/plugins/marknative/index.js +2 -7
  12. package/.agent/plugins/poster-plugin/README.md +304 -0
  13. package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
  14. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
  15. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
  16. package/.agent/plugins/poster-plugin/index.js +13 -0
  17. package/.agent/plugins/poster-plugin/package.json +28 -0
  18. package/.agent/plugins/poster-plugin/src/canvas.js +161 -0
  19. package/.agent/plugins/poster-plugin/src/components/arrow.js +84 -0
  20. package/.agent/plugins/poster-plugin/src/components/avatar.js +71 -0
  21. package/.agent/plugins/poster-plugin/src/components/badge.js +85 -0
  22. package/.agent/plugins/poster-plugin/src/components/card.js +88 -0
  23. package/.agent/plugins/poster-plugin/src/components/chart.js +127 -0
  24. package/.agent/plugins/poster-plugin/src/components/chip.js +88 -0
  25. package/.agent/plugins/poster-plugin/src/components/columns.js +107 -0
  26. package/.agent/plugins/poster-plugin/src/components/cta.js +85 -0
  27. package/.agent/plugins/poster-plugin/src/components/divider.js +55 -0
  28. package/.agent/plugins/poster-plugin/src/components/feature.js +85 -0
  29. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +112 -0
  30. package/.agent/plugins/poster-plugin/src/components/grid.js +118 -0
  31. package/.agent/plugins/poster-plugin/src/components/imageFrame.js +155 -0
  32. package/.agent/plugins/poster-plugin/src/components/index.js +62 -0
  33. package/.agent/plugins/poster-plugin/src/components/listItem.js +146 -0
  34. package/.agent/plugins/poster-plugin/src/components/notification.js +123 -0
  35. package/.agent/plugins/poster-plugin/src/components/progress.js +79 -0
  36. package/.agent/plugins/poster-plugin/src/components/progressCircle.js +117 -0
  37. package/.agent/plugins/poster-plugin/src/components/quote.js +97 -0
  38. package/.agent/plugins/poster-plugin/src/components/rating.js +85 -0
  39. package/.agent/plugins/poster-plugin/src/components/star.js +70 -0
  40. package/.agent/plugins/poster-plugin/src/components/statCard.js +105 -0
  41. package/.agent/plugins/poster-plugin/src/components/stepper.js +118 -0
  42. package/.agent/plugins/poster-plugin/src/components/table.js +159 -0
  43. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +78 -0
  44. package/.agent/plugins/poster-plugin/src/components/timeline.js +105 -0
  45. package/.agent/plugins/poster-plugin/src/components/watermark.js +52 -0
  46. package/.agent/plugins/poster-plugin/src/composer.js +1904 -0
  47. package/.agent/plugins/poster-plugin/src/elements/artText.js +60 -0
  48. package/.agent/plugins/poster-plugin/src/elements/background.js +52 -0
  49. package/.agent/plugins/poster-plugin/src/elements/circle.js +31 -0
  50. package/.agent/plugins/poster-plugin/src/elements/image.js +71 -0
  51. package/.agent/plugins/poster-plugin/src/elements/index.js +26 -0
  52. package/.agent/plugins/poster-plugin/src/elements/line.js +23 -0
  53. package/.agent/plugins/poster-plugin/src/elements/polygon.js +32 -0
  54. package/.agent/plugins/poster-plugin/src/elements/rectangle.js +32 -0
  55. package/.agent/plugins/poster-plugin/src/elements/svg.js +92 -0
  56. package/.agent/plugins/poster-plugin/src/elements/text.js +38 -0
  57. package/.agent/plugins/poster-plugin/src/fonts.js +118 -0
  58. package/.agent/plugins/poster-plugin/src/index.js +1659 -0
  59. package/.agent/plugins/poster-plugin/src/presets.js +36 -0
  60. package/.agent/plugins/poster-plugin/src/templates/business.js +60 -0
  61. package/.agent/plugins/poster-plugin/src/templates/gradient.js +64 -0
  62. package/.agent/plugins/poster-plugin/src/templates/index.js +43 -0
  63. package/.agent/plugins/poster-plugin/src/templates/modern.js +69 -0
  64. package/.agent/plugins/poster-plugin/src/templates/simple.js +58 -0
  65. package/.agent/plugins/poster-plugin/src/templates/social.js +62 -0
  66. package/.agent/plugins/poster-plugin/src/templates/tech.js +84 -0
  67. package/.agent/sessions/cli_default.json +24265 -0
  68. package/.agent/weixin.json +6 -0
  69. package/.claude/settings.local.json +176 -171
  70. package/CLAUDE.md +144 -108
  71. package/docs/CONTEXT_DESIGN.md +1596 -0
  72. package/examples/test-concurrent-chat.js +60 -0
  73. package/examples/test-long-chat.js +77 -0
  74. package/examples/test-session-chat.js +93 -0
  75. package/output/beef-love-poster.png +0 -0
  76. package/package.json +2 -2
  77. package/plugins/default-plugins.js +2 -1
  78. package/plugins/extension-executor-plugin.js +11 -0
  79. package/plugins/memory-plugin.js +984 -0
  80. package/plugins/session-plugin.js +78 -1
  81. package/plugins/weixin-plugin.js +24 -22
  82. package/skills/poster-guide/SKILL.md +743 -0
  83. package/skills/python-plugin-dev/SKILL.md +238 -238
  84. package/skills/skill-guide/SKILL.md +130 -108
  85. package/src/capabilities/skill-manager.js +99 -0
  86. package/src/core/agent-chat.js +627 -180
  87. package/src/core/agent-context.js +188 -0
  88. package/src/core/agent.js +9 -63
  89. package/src/core/context-manager.js +283 -0
  90. package/src/core/framework.js +264 -3
  91. package/src/core/plugin-manager.js +79 -2
  92. package/src/core/request-context.js +98 -0
  93. package/src/core/session-context.js +341 -0
  94. package/src/core/session-storage.js +274 -0
  95. package/src/executors/mcp-executor.js +2 -2
  96. package/src/utils/index.js +239 -67
  97. package/src/utils/plugin-helpers.js +17 -0
  98. package//346/265/267/346/212/245/346/217/222/344/273/266.md +621 -0
  99. package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
  100. package/.agent/plugins/temp-repo/LICENSE +0 -201
  101. package/.agent/plugins/temp-repo/puppeteer-plugin/README.md +0 -147
  102. package/.agent/plugins/temp-repo/puppeteer-plugin/index.js +0 -1418
  103. package/.agent/plugins/temp-repo/puppeteer-plugin/package.json +0 -9
  104. package/.agent/plugins/test_plugin.py +0 -304
@@ -10,6 +10,9 @@ const { PluginManager } = require('./plugin-manager');
10
10
  const { ToolRegistry } = require('./tool-registry');
11
11
  const { Agent } = require('./agent');
12
12
  const { SubAgentConfigManager } = require('./sub-agent-config');
13
+ const { ContextManager } = require('./context-manager');
14
+ const { SessionContext } = require('./session-context');
15
+ const { SessionStorageAdapter, getDefaultAdapter } = require('./session-storage');
13
16
 
14
17
  // 添加框架目录的 node_modules 到全局搜索路径
15
18
  // 让项目插件能 require 框架内置的包(如 zod)
@@ -18,9 +21,14 @@ if (!module.paths.includes(frameworkNodeModules)) {
18
21
  module.paths.unshift(frameworkNodeModules);
19
22
  }
20
23
 
21
- // AsyncLocalStorage 实现真正的上下文隔离(支持并行处理)
24
+ // 现有的 AsyncLocalStorage(保持向后兼容)
22
25
  const asyncLocalStorage = new AsyncLocalStorage();
23
26
 
27
+ // 新增:三层 AsyncLocalStorage
28
+ const requestStorage = new AsyncLocalStorage(); // Request 级别
29
+ const sessionStorage = new AsyncLocalStorage(); // Session 级别
30
+ const agentStorage = new AsyncLocalStorage(); // Agent 级别
31
+
24
32
  class Framework extends EventEmitter {
25
33
  /**
26
34
  * @param {Object} config - 配置
@@ -48,6 +56,20 @@ class Framework extends EventEmitter {
48
56
  // 执行上下文(工具调用时可用)
49
57
  this._executionContext = null;
50
58
 
59
+ // ========== 分层上下文系统 ==========
60
+ // Context 管理器
61
+ this._contextManager = new ContextManager(this);
62
+
63
+ // SessionContext 缓存(轻量管理,不依赖 ContextManager)
64
+ this._sessionContexts = new Map(); // sessionId → SessionContext
65
+
66
+ // Session 存储适配器(默认使用文件存储)
67
+ this._sessionStorage = null;
68
+ this._sessionStorageConfig = config.sessionStorage || {
69
+ type: 'file',
70
+ baseDir: '.agent/sessions',
71
+ };
72
+
51
73
  // 事件转发 - ToolRegistry 事件
52
74
  this.toolRegistry.on('tool:registered', (tool) => {
53
75
  this.emit('tool:registered', tool);
@@ -283,6 +305,239 @@ class Framework extends EventEmitter {
283
305
  */
284
306
  clearExecutionContext() {}
285
307
 
308
+ // ============================================================================
309
+ // 分层上下文系统 - Session Context
310
+ // ============================================================================
311
+
312
+ /**
313
+ * 获取或创建 SessionContext
314
+ * @param {string} sessionId - 会话 ID
315
+ * @param {Object} options - 配置选项
316
+ * @returns {SessionContext}
317
+ */
318
+ getOrCreateSessionContext(sessionId, options = {}) {
319
+ if (!sessionId) {
320
+ return null;
321
+ }
322
+
323
+ // 先检查内存缓存
324
+ let ctx = this._sessionContexts.get(sessionId);
325
+ if (ctx) {
326
+ return ctx;
327
+ }
328
+
329
+ // 尝试从存储同步加载(仅文件存储支持)
330
+ const storage = this._getSessionStorage();
331
+ const loadedCtx = SessionContext.loadSync(sessionId, this, { ...options, storage });
332
+ if (loadedCtx) {
333
+ this._sessionContexts.set(sessionId, loadedCtx);
334
+ this.emit('session:context-created', loadedCtx);
335
+ return loadedCtx;
336
+ }
337
+
338
+ // 创建新的 SessionContext
339
+ ctx = new SessionContext(sessionId, this, { ...options, storage });
340
+ this._sessionContexts.set(sessionId, ctx);
341
+ this.emit('session:context-created', ctx);
342
+ return ctx;
343
+ }
344
+
345
+ /**
346
+ * 异步加载 SessionContext(如果存在)
347
+ * @param {string} sessionId
348
+ * @param {Object} options
349
+ * @returns {Promise<SessionContext|null>}
350
+ */
351
+ async loadSessionContext(sessionId, options = {}) {
352
+ if (!sessionId) {
353
+ return null;
354
+ }
355
+
356
+ // 先检查内存缓存
357
+ let ctx = this._sessionContexts.get(sessionId);
358
+ if (ctx) {
359
+ return ctx;
360
+ }
361
+
362
+ const storage = this._getSessionStorage();
363
+ ctx = await SessionContext.load(sessionId, this, { ...options, storage });
364
+ if (ctx) {
365
+ this._sessionContexts.set(sessionId, ctx);
366
+ this.emit('session:context-created', ctx);
367
+ }
368
+ return ctx;
369
+ }
370
+
371
+ /**
372
+ * 获取 Session 存储适配器
373
+ * @returns {SessionStorageAdapter}
374
+ * @private
375
+ */
376
+ _getSessionStorage() {
377
+ if (!this._sessionStorage) {
378
+ this._sessionStorage = new SessionStorageAdapter(this._sessionStorageConfig);
379
+ }
380
+ return this._sessionStorage;
381
+ }
382
+
383
+ /**
384
+ * 配置 Session 存储类型
385
+ * @param {Object} config - 存储配置
386
+ * @param {string} [config.type='memory'] - 存储类型: 'memory', 'file'
387
+ * @param {string} [config.baseDir='.agent/sessions'] - 文件存储目录
388
+ */
389
+ configureSessionStorage(config) {
390
+ this._sessionStorageConfig = config;
391
+ this._sessionStorage = new SessionStorageAdapter(config);
392
+ }
393
+
394
+ /**
395
+ * 获取当前 Session 存储适配器
396
+ * @returns {SessionStorageAdapter}
397
+ */
398
+ getSessionStorage() {
399
+ return this._getSessionStorage();
400
+ }
401
+
402
+ /**
403
+ * 获取 SessionContext
404
+ * @param {string} sessionId - 会话 ID
405
+ * @returns {SessionContext|null}
406
+ */
407
+ getSessionContext(sessionId) {
408
+ return this._sessionContexts.get(sessionId) || null;
409
+ }
410
+
411
+ /**
412
+ * 获取当前 Session 上下文(从 AsyncLocalStorage 获取)
413
+ * @returns {SessionContext|null}
414
+ */
415
+ getCurrentSessionContext() {
416
+ const store = sessionStorage.getStore();
417
+ return store?.sessionContext || null;
418
+ }
419
+
420
+ /**
421
+ * 获取当前 sessionId(从 AsyncLocalStorage 获取)
422
+ * @returns {string|null}
423
+ */
424
+ getCurrentSessionId() {
425
+ const store = sessionStorage.getStore();
426
+ return store?.sessionId || null;
427
+ }
428
+
429
+ /**
430
+ * 在 Session 上下文中执行函数
431
+ * @param {string} sessionId - 会话 ID
432
+ * @param {Object} options - Session 选项
433
+ * @param {Function} fn - 异步函数
434
+ * @returns {Promise}
435
+ */
436
+ runInSession(sessionId, options, fn) {
437
+ if (!sessionId) {
438
+ // 无 session,直接执行
439
+ return fn();
440
+ }
441
+
442
+ const sessionCtx = this.getOrCreateSessionContext(sessionId, options);
443
+ const context = { sessionContext: sessionCtx, sessionId };
444
+ return sessionStorage.run(context, fn);
445
+ }
446
+
447
+ /**
448
+ * 销毁 Session Context
449
+ * @param {string} sessionId
450
+ */
451
+ destroySessionContext(sessionId) {
452
+ if (this._sessionContexts.has(sessionId)) {
453
+ const ctx = this._sessionContexts.get(sessionId);
454
+ ctx.destroy();
455
+ this._sessionContexts.delete(sessionId);
456
+
457
+ // 从存储中删除
458
+ if (this._sessionStorage) {
459
+ this._sessionStorage.delete(sessionId).catch(() => {});
460
+ }
461
+
462
+ this.emit('session:context-destroyed', ctx);
463
+ }
464
+ }
465
+
466
+ /**
467
+ * 列出所有活跃的 Session ID
468
+ * @returns {string[]}
469
+ */
470
+ listActiveSessions() {
471
+ return Array.from(this._sessionContexts.keys());
472
+ }
473
+
474
+ // ============================================================================
475
+ // 分层上下文系统 - Request Context
476
+ // ============================================================================
477
+
478
+ /**
479
+ * 获取当前 Request 上下文
480
+ * @returns {Object|null}
481
+ */
482
+ getRequestContext() {
483
+ return requestStorage.getStore() || null;
484
+ }
485
+
486
+ /**
487
+ * 在 Request 上下文中执行函数
488
+ * @param {Object} context - Request 上下文
489
+ * @param {Function} fn - 异步函数
490
+ * @returns {Promise}
491
+ */
492
+ runWithRequestContext(context, fn) {
493
+ return requestStorage.run(context, fn);
494
+ }
495
+
496
+ // ============================================================================
497
+ // 分层上下文系统 - Agent Context
498
+ // ============================================================================
499
+
500
+ /**
501
+ * 获取当前 Agent 上下文
502
+ * @returns {Object|null}
503
+ */
504
+ getAgentContext() {
505
+ return agentStorage.getStore()?.agentContext || null;
506
+ }
507
+
508
+ /**
509
+ * 在 Agent 上下文中执行函数
510
+ * @param {string} agentId - Agent ID
511
+ * @param {Object} context - Agent 上下文
512
+ * @param {Function} fn - 异步函数
513
+ * @returns {Promise}
514
+ */
515
+ runWithAgentContext(agentId, context, fn) {
516
+ return agentStorage.run({ agentId, ...context }, fn);
517
+ }
518
+
519
+ // ============================================================================
520
+ // 组合上下文
521
+ // ============================================================================
522
+
523
+ /**
524
+ * 在完整上下文中执行(Request + Session)
525
+ * @param {Object} requestOptions - Request 选项
526
+ * @param {string} sessionId - Session ID
527
+ * @param {Function} fn - 异步函数
528
+ * @returns {Promise}
529
+ */
530
+ async runInContext(requestOptions, sessionId, fn) {
531
+ const sessionCtx = sessionId ? this.getOrCreateSessionContext(sessionId) : null;
532
+
533
+ return requestStorage.run(requestOptions, () => {
534
+ if (sessionCtx) {
535
+ return sessionStorage.run({ sessionContext: sessionCtx, sessionId }, fn);
536
+ }
537
+ return fn();
538
+ });
539
+ }
540
+
286
541
  /**
287
542
  * 创建 Agent
288
543
  * @param {Object} config - Agent 配置
@@ -484,10 +739,16 @@ class Framework extends EventEmitter {
484
739
  }
485
740
 
486
741
  /**
487
- * 销毁框架)
742
+ * 销毁框架
488
743
  */
489
744
  async destroy() {
490
- // 卸载所有插件)
745
+ // 刷新所有 Session 存储
746
+ if (this._sessionStorage) {
747
+ await this._sessionStorage.flush();
748
+ await this._sessionStorage.destroy();
749
+ }
750
+
751
+ // 卸载所有插件
491
752
  const plugins = this.pluginManager.getAll();
492
753
  for (const { name } of plugins) {
493
754
  await this.pluginManager.unload(name);
@@ -215,6 +215,13 @@ class PluginManager {
215
215
  instance: pluginInstance,
216
216
  status: 'registered',
217
217
  enabled,
218
+ // 记录插件来源路径(用于热重载)
219
+ // 尝试从实例、prototype 或原始 plugin 对象获取
220
+ sourcePath:
221
+ pluginInstance._sourcePath ||
222
+ (plugin.prototype && plugin.prototype._sourcePath) ||
223
+ plugin._sourcePath ||
224
+ null,
218
225
  });
219
226
 
220
227
  this.framework.emit('plugin:registered', pluginInstance);
@@ -284,6 +291,11 @@ class PluginManager {
284
291
  return pluginInstance;
285
292
  }
286
293
 
294
+ // 如果 entry 没有 sourcePath,尝试从 plugin prototype 获取(loadPlugin 直接传入类的情况)
295
+ if (!entry.sourcePath && typeof plugin === 'function' && plugin.prototype) {
296
+ entry.sourcePath = plugin.prototype._sourcePath || null;
297
+ }
298
+
287
299
  // 调用 install
288
300
  try {
289
301
  await entry.instance.install(this.framework);
@@ -476,6 +488,19 @@ class PluginManager {
476
488
  plugin = pluginModule;
477
489
  }
478
490
 
491
+ // 记录插件源路径(用于热重载)
492
+ let sourcePathSet = false;
493
+ if (typeof plugin === 'function' && plugin.prototype) {
494
+ plugin.prototype._sourcePath = pluginPath;
495
+ sourcePathSet = true;
496
+ } else if (typeof plugin === 'function') {
497
+ plugin._sourcePath = pluginPath;
498
+ sourcePathSet = true;
499
+ }
500
+ if (sourcePathSet) {
501
+ this._log.debug(`Set _sourcePath for plugin ${pluginName}: ${pluginPath}`);
502
+ }
503
+
479
504
  // 获取插件名称
480
505
  let resolvedPluginName;
481
506
  try {
@@ -639,8 +664,60 @@ class PluginManager {
639
664
  await entry.instance.stopBot();
640
665
  }
641
666
  }
642
- // 调用 reload 让插件重新初始化(会调用 install 和 start)
643
- await this.reload(name);
667
+
668
+ // 如果有 sourcePath,重新 require 文件获取最新代码
669
+ if (entry.sourcePath) {
670
+ this._log.info(`Re-requiring plugin '${name}' from ${entry.sourcePath}`);
671
+ // 清除 require 缓存
672
+ delete require.cache[require.resolve(entry.sourcePath)];
673
+ const pluginModule = require(entry.sourcePath);
674
+
675
+ let NewPluginClass = pluginModule;
676
+ if (pluginModule.default) {
677
+ NewPluginClass = pluginModule.default;
678
+ }
679
+
680
+ // 创建新实例
681
+ let newInstance;
682
+ if (typeof NewPluginClass === 'function') {
683
+ if (NewPluginClass.prototype instanceof require('./plugin-base')) {
684
+ newInstance = new NewPluginClass();
685
+ } else {
686
+ newInstance = NewPluginClass();
687
+ }
688
+ } else {
689
+ newInstance = NewPluginClass;
690
+ }
691
+
692
+ // 复制 sourcePath 到新实例(prototype 上的属性不会自动继承)
693
+ if (entry.sourcePath) {
694
+ newInstance._sourcePath = entry.sourcePath;
695
+ }
696
+
697
+ // 复制 config 到新实例(用户配置需要保留)
698
+ if (entry.instance && entry.instance.config) {
699
+ newInstance.config = { ...entry.instance.config };
700
+ }
701
+
702
+ // 替换旧实例
703
+ entry.instance = newInstance;
704
+
705
+ // 调用 install
706
+ await newInstance.install(this.framework);
707
+
708
+ // 调用 start
709
+ await newInstance.start(this.framework);
710
+ newInstance._started = true;
711
+
712
+ // 通知监听器(如 extension-executor-plugin)重新扫描
713
+ this.framework.emit('plugin:reloaded', newInstance);
714
+
715
+ this._log.info(`Plugin '${name}' re-required from ${entry.sourcePath}`);
716
+ } else {
717
+ this._log.info(`No sourcePath for plugin '${name}', calling reload() only`);
718
+ // 没有 sourcePath,只调用 reload
719
+ await this.reload(name);
720
+ }
644
721
  } catch (err) {
645
722
  this._log.error(`Enable/reload failed for '${name}':`, err.message);
646
723
  }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * RequestContext - Request 级别的上下文封装
3
+ *
4
+ * 职责:
5
+ * 1. 追踪请求链路(requestId, traceId)
6
+ * 2. 管理请求级超时
7
+ * 3. 传递请求元数据
8
+ */
9
+
10
+ const { uuid } = require('../utils/id');
11
+
12
+ class RequestContext {
13
+ /**
14
+ * @param {Object} options - 配置选项
15
+ * @param {string} [options.requestId] - 请求 ID,默认自动生成
16
+ * @param {string} [options.traceId] - 追踪 ID,默认自动生成
17
+ * @param {string} [options.parentSpanId] - 父 span ID
18
+ * @param {number} [options.timeout] - 超时时间(毫秒),默认 120000
19
+ * @param {boolean} [options.isStream] - 是否为流式请求
20
+ * @param {string} [options.userId] - 用户标识
21
+ */
22
+ constructor(options = {}) {
23
+ this.requestId = options.requestId || `req_${uuid()}`;
24
+ this.traceId = options.traceId || `trace_${uuid()}`;
25
+ this.traceId = options.traceId || generateId();
26
+ this.parentSpanId = options.parentSpanId || null;
27
+
28
+ this.timeout = options.timeout || 120000; // 默认 2 分钟
29
+ this.startTime = options.startTime || Date.now();
30
+
31
+ this.isStream = options.isStream || false;
32
+ this.userId = options.userId || null;
33
+
34
+ // 请求级状态
35
+ this.cancelled = false;
36
+ this.abortController = null;
37
+
38
+ // 元数据
39
+ this.metadata = options.metadata || {};
40
+ }
41
+
42
+ /**
43
+ * 创建 AbortController(延迟初始化)
44
+ * @returns {AbortController}
45
+ */
46
+ getAbortController() {
47
+ if (!this.abortController) {
48
+ this.abortController = new AbortController();
49
+ }
50
+ return this.abortController;
51
+ }
52
+
53
+ /**
54
+ * 检查是否超时
55
+ * @returns {boolean}
56
+ */
57
+ isTimeout() {
58
+ return Date.now() - this.startTime > this.timeout;
59
+ }
60
+
61
+ /**
62
+ * 取消请求
63
+ */
64
+ cancel() {
65
+ this.cancelled = true;
66
+ if (this.abortController) {
67
+ this.abortController.abort();
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 获取请求耗时(毫秒)
73
+ * @returns {number}
74
+ */
75
+ getElapsed() {
76
+ return Date.now() - this.startTime;
77
+ }
78
+
79
+ /**
80
+ * 获取请求摘要
81
+ * @returns {Object}
82
+ */
83
+ toJSON() {
84
+ return {
85
+ requestId: this.requestId,
86
+ traceId: this.traceId,
87
+ parentSpanId: this.parentSpanId,
88
+ timeout: this.timeout,
89
+ startTime: this.startTime,
90
+ elapsed: this.getElapsed(),
91
+ isStream: this.isStream,
92
+ userId: this.userId,
93
+ cancelled: this.cancelled,
94
+ };
95
+ }
96
+ }
97
+
98
+ module.exports = { RequestContext };