@ai-setting/roy-agent-cli 1.0.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 (158) hide show
  1. package/README.md +126 -0
  2. package/dist/bin/roy.js +127297 -0
  3. package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
  4. package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
  5. package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
  6. package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
  7. package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
  8. package/package.json +91 -0
  9. package/src/bin/roy.ts +12 -0
  10. package/src/cli.ts +101 -0
  11. package/src/commands/act.ts +480 -0
  12. package/src/commands/commands-add.ts +110 -0
  13. package/src/commands/commands-dirs.ts +70 -0
  14. package/src/commands/commands-info.ts +90 -0
  15. package/src/commands/commands-list.ts +161 -0
  16. package/src/commands/commands-remove.ts +147 -0
  17. package/src/commands/commands.ts +55 -0
  18. package/src/commands/config/config-service.test.ts +449 -0
  19. package/src/commands/config/config-service.ts +312 -0
  20. package/src/commands/config/deep-merge.test.ts +168 -0
  21. package/src/commands/config/deep-merge.ts +63 -0
  22. package/src/commands/config/export.ts +97 -0
  23. package/src/commands/config/filter-history-e2e.test.ts +141 -0
  24. package/src/commands/config/import-preserve-refs.test.ts +212 -0
  25. package/src/commands/config/import.ts +119 -0
  26. package/src/commands/config/index.ts +35 -0
  27. package/src/commands/config/list.ts +281 -0
  28. package/src/commands/config/roy-config-e2e.test.ts +297 -0
  29. package/src/commands/config/types.ts +54 -0
  30. package/src/commands/debug/index.ts +38 -0
  31. package/src/commands/debug/log.test.ts +233 -0
  32. package/src/commands/debug/log.ts +123 -0
  33. package/src/commands/debug/span.test.ts +297 -0
  34. package/src/commands/debug/span.ts +211 -0
  35. package/src/commands/debug/trace.test.ts +254 -0
  36. package/src/commands/debug/trace.ts +140 -0
  37. package/src/commands/eventsource/add.ts +133 -0
  38. package/src/commands/eventsource/index.ts +48 -0
  39. package/src/commands/eventsource/list.ts +194 -0
  40. package/src/commands/eventsource/remove.ts +95 -0
  41. package/src/commands/eventsource/start.ts +103 -0
  42. package/src/commands/eventsource/status.ts +185 -0
  43. package/src/commands/eventsource/stop.ts +89 -0
  44. package/src/commands/index.ts +22 -0
  45. package/src/commands/input-handler.test.ts +76 -0
  46. package/src/commands/input-handler.ts +43 -0
  47. package/src/commands/interactive-esc.test.ts +254 -0
  48. package/src/commands/interactive.shutdown.test.ts +122 -0
  49. package/src/commands/interactive.test.ts +221 -0
  50. package/src/commands/interactive.ts +1015 -0
  51. package/src/commands/lsp/check.ts +92 -0
  52. package/src/commands/lsp/index.ts +32 -0
  53. package/src/commands/lsp/install.ts +126 -0
  54. package/src/commands/lsp/list.ts +64 -0
  55. package/src/commands/mcp/index.ts +27 -0
  56. package/src/commands/mcp/list.ts +116 -0
  57. package/src/commands/mcp/reload.ts +70 -0
  58. package/src/commands/mcp/tools.ts +121 -0
  59. package/src/commands/memory/extract-e2e.test.ts +388 -0
  60. package/src/commands/memory/index.ts +11 -0
  61. package/src/commands/memory/memory-simplified.test.ts +58 -0
  62. package/src/commands/memory/memory.ts +25 -0
  63. package/src/commands/memory/organize.ts +300 -0
  64. package/src/commands/memory/recall.test.ts +120 -0
  65. package/src/commands/memory/recall.ts +88 -0
  66. package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
  67. package/src/commands/memory/record-prompt-component.test.ts +343 -0
  68. package/src/commands/memory/record.test.ts +92 -0
  69. package/src/commands/memory/record.ts +332 -0
  70. package/src/commands/plugin.test.ts +292 -0
  71. package/src/commands/plugin.ts +267 -0
  72. package/src/commands/sessions/active.ts +96 -0
  73. package/src/commands/sessions/add-message.ts +96 -0
  74. package/src/commands/sessions/checkpoints.ts +154 -0
  75. package/src/commands/sessions/compact.test.ts +215 -0
  76. package/src/commands/sessions/compact.ts +269 -0
  77. package/src/commands/sessions/delete.ts +236 -0
  78. package/src/commands/sessions/get.ts +165 -0
  79. package/src/commands/sessions/grep.ts +233 -0
  80. package/src/commands/sessions/index.ts +95 -0
  81. package/src/commands/sessions/list.ts +210 -0
  82. package/src/commands/sessions/messages.test.ts +333 -0
  83. package/src/commands/sessions/messages.ts +248 -0
  84. package/src/commands/sessions/mock.ts +194 -0
  85. package/src/commands/sessions/new.ts +82 -0
  86. package/src/commands/sessions/rename.ts +98 -0
  87. package/src/commands/shared/event-handler.ts +213 -0
  88. package/src/commands/shared/event-message-formatter.ts +295 -0
  89. package/src/commands/shared/index.ts +11 -0
  90. package/src/commands/shared/query-executor.test.ts +434 -0
  91. package/src/commands/shared/query-executor.ts +324 -0
  92. package/src/commands/shared/repl-engine.test.ts +354 -0
  93. package/src/commands/shared/session-manager.test.ts +212 -0
  94. package/src/commands/shared/session-manager.ts +114 -0
  95. package/src/commands/skills/get.ts +90 -0
  96. package/src/commands/skills/index.ts +39 -0
  97. package/src/commands/skills/list.ts +129 -0
  98. package/src/commands/skills/reload.ts +59 -0
  99. package/src/commands/skills/search.ts +132 -0
  100. package/src/commands/skills/show-config.ts +93 -0
  101. package/src/commands/tasks/complete.ts +92 -0
  102. package/src/commands/tasks/create.ts +118 -0
  103. package/src/commands/tasks/delete.ts +86 -0
  104. package/src/commands/tasks/get.ts +116 -0
  105. package/src/commands/tasks/index.ts +53 -0
  106. package/src/commands/tasks/list.ts +140 -0
  107. package/src/commands/tasks/operations.ts +120 -0
  108. package/src/commands/tasks/update.ts +122 -0
  109. package/src/commands/tools/exec-tool.ts +128 -0
  110. package/src/commands/tools/get.ts +114 -0
  111. package/src/commands/tools/index.ts +35 -0
  112. package/src/commands/tools/list.ts +107 -0
  113. package/src/commands/tools/shared/index.ts +7 -0
  114. package/src/commands/tools/shared/schema-helper.ts +111 -0
  115. package/src/commands/workflow/commands/add.ts +315 -0
  116. package/src/commands/workflow/commands/get.ts +193 -0
  117. package/src/commands/workflow/commands/list.ts +137 -0
  118. package/src/commands/workflow/commands/nodes.ts +528 -0
  119. package/src/commands/workflow/commands/remove.ts +94 -0
  120. package/src/commands/workflow/commands/run.ts +398 -0
  121. package/src/commands/workflow/commands/status.ts +147 -0
  122. package/src/commands/workflow/commands/stop.ts +91 -0
  123. package/src/commands/workflow/commands/update.ts +130 -0
  124. package/src/commands/workflow/commands/validate.ts +139 -0
  125. package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
  126. package/src/commands/workflow/index.ts +65 -0
  127. package/src/commands/workflow/renderers.ts +358 -0
  128. package/src/commands/workflow/validators/index.ts +8 -0
  129. package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
  130. package/src/commands/workflow/validators/node-validator.ts +125 -0
  131. package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
  132. package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
  133. package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
  134. package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
  135. package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
  136. package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
  137. package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
  138. package/src/commands/workflow/validators/types.ts +78 -0
  139. package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
  140. package/src/commands/workflow/validators/workflow-validator.ts +320 -0
  141. package/src/index.ts +19 -0
  142. package/src/plugin/apply.ts +103 -0
  143. package/src/plugin/discover.ts +219 -0
  144. package/src/plugin/index.ts +45 -0
  145. package/src/plugin/registry.ts +272 -0
  146. package/src/plugin/types.ts +165 -0
  147. package/src/services/context-handler.service.test.ts +501 -0
  148. package/src/services/context-handler.service.ts +372 -0
  149. package/src/services/environment.service.commands-prompt.test.ts +167 -0
  150. package/src/services/environment.service.ts +656 -0
  151. package/src/services/output.service.test.ts +92 -0
  152. package/src/services/output.service.ts +122 -0
  153. package/src/services/quiet-mode.service.test.ts +114 -0
  154. package/src/services/quiet-mode.service.ts +81 -0
  155. package/src/services/stream-output.service.test.ts +214 -0
  156. package/src/services/stream-output.service.ts +323 -0
  157. package/src/util/which.test.ts +101 -0
  158. package/src/util/which.ts +55 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @fileoverview OutputService Tests
3
+ */
4
+
5
+ import { describe, it, expect } from "bun:test";
6
+ import { OutputService } from "./output.service";
7
+
8
+ describe("OutputService", () => {
9
+ describe("configure", () => {
10
+ it("should have default values", () => {
11
+ const output = new OutputService();
12
+ expect(output.isQuiet()).toBe(false);
13
+ expect(output.isJsonOutput()).toBe(false);
14
+ });
15
+
16
+ it("should enable quiet mode", () => {
17
+ const output = new OutputService();
18
+ output.configure({ quiet: true });
19
+ expect(output.isQuiet()).toBe(true);
20
+ });
21
+
22
+ it("should enable json output mode", () => {
23
+ const output = new OutputService();
24
+ output.configure({ jsonOutput: true });
25
+ expect(output.isJsonOutput()).toBe(true);
26
+ });
27
+ });
28
+
29
+ describe("info", () => {
30
+ it("should output info message", () => {
31
+ const output = new OutputService();
32
+ output.info("test message");
33
+ });
34
+
35
+ it("should not output in quiet mode", () => {
36
+ const output = new OutputService();
37
+ output.configure({ quiet: true });
38
+ output.info("test message");
39
+ });
40
+ });
41
+
42
+ describe("success", () => {
43
+ it("should output success message", () => {
44
+ const output = new OutputService();
45
+ output.success("operation succeeded");
46
+ });
47
+
48
+ it("should not output in quiet mode", () => {
49
+ const output = new OutputService();
50
+ output.configure({ quiet: true });
51
+ output.success("test");
52
+ });
53
+ });
54
+
55
+ describe("warn", () => {
56
+ it("should output warning message", () => {
57
+ const output = new OutputService();
58
+ output.warn("warning message");
59
+ });
60
+
61
+ it("should not output in quiet mode", () => {
62
+ const output = new OutputService();
63
+ output.configure({ quiet: true });
64
+ output.warn("warning");
65
+ });
66
+ });
67
+
68
+ describe("error", () => {
69
+ it("should output error message", () => {
70
+ const output = new OutputService();
71
+ output.error("error message");
72
+ });
73
+
74
+ it("should output even in quiet mode", () => {
75
+ const output = new OutputService();
76
+ output.configure({ quiet: true });
77
+ output.error("error in quiet mode");
78
+ });
79
+ });
80
+
81
+ describe("printJson", () => {
82
+ it("should output formatted json", () => {
83
+ const output = new OutputService();
84
+ output.printJson({ key: "value" });
85
+ });
86
+
87
+ it("should output compact json when pretty is false", () => {
88
+ const output = new OutputService();
89
+ output.printJson({ key: "value" }, false);
90
+ });
91
+ });
92
+ });
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @fileoverview Output Service
3
+ *
4
+ * 统一的输出格式化服务(非交互式)
5
+ */
6
+
7
+ import chalk from "chalk";
8
+
9
+ export interface OutputOptions {
10
+ quiet?: boolean;
11
+ jsonOutput?: boolean;
12
+ noColor?: boolean;
13
+ }
14
+
15
+ /**
16
+ * OutputService - CLI 输出服务
17
+ *
18
+ * 提供统一的格式化输出:
19
+ * - 普通消息
20
+ * - 成功/警告/错误消息
21
+ * - JSON 输出
22
+ */
23
+ export class OutputService {
24
+ private quiet: boolean = false;
25
+ private jsonOutput: boolean = false;
26
+ private noColor: boolean = false;
27
+
28
+ /**
29
+ * 配置输出选项
30
+ */
31
+ configure(options: OutputOptions): void {
32
+ this.quiet = options.quiet ?? this.quiet;
33
+ this.jsonOutput = options.jsonOutput ?? this.jsonOutput;
34
+ this.noColor = options.noColor ?? this.noColor;
35
+ }
36
+
37
+ /**
38
+ * 获取配置状态
39
+ */
40
+ isQuiet(): boolean {
41
+ return this.quiet;
42
+ }
43
+
44
+ isJsonOutput(): boolean {
45
+ return this.jsonOutput;
46
+ }
47
+
48
+ isNoColor(): boolean {
49
+ return this.noColor;
50
+ }
51
+
52
+ /**
53
+ * 输出普通消息
54
+ */
55
+ info(message: string): void {
56
+ if (!this.quiet) {
57
+ console.log(message);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 输出日志消息(不添加前缀)
63
+ */
64
+ log(message?: string): void {
65
+ console.log(message ?? "");
66
+ }
67
+
68
+ /**
69
+ * 输出成功消息
70
+ */
71
+ success(message: string): void {
72
+ if (!this.quiet) {
73
+ console.log(chalk.green("✓ ") + message);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * 输出警告消息
79
+ */
80
+ warn(message: string): void {
81
+ if (!this.quiet) {
82
+ console.warn(chalk.yellow("⚠ ") + message);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 输出错误消息
88
+ *
89
+ * 注意:错误消息即使在安静模式下也会输出
90
+ */
91
+ error(message: string): void {
92
+ console.error(chalk.red("✗ ") + message);
93
+ }
94
+
95
+ /**
96
+ * 输出错误消息(详细版)
97
+ */
98
+ errorDetailed(message: string, details?: unknown): void {
99
+ console.error(chalk.red("✗ ") + message);
100
+ if (details && !this.quiet) {
101
+ console.error(JSON.stringify(details, null, 2));
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 格式化并输出 JSON
107
+ */
108
+ printJson(data: unknown, pretty: boolean = true): void {
109
+ if (pretty) {
110
+ console.log(JSON.stringify(data, null, 2));
111
+ } else {
112
+ console.log(JSON.stringify(data));
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 输出 JSON(别名方法)
118
+ */
119
+ json(data: unknown): void {
120
+ this.printJson(data);
121
+ }
122
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @fileoverview QuietMode Service Tests
3
+ *
4
+ * TDD: 测试统一的 QuietMode 服务
5
+ */
6
+
7
+ import { describe, test, expect, beforeEach, afterEach, vi } from "bun:test";
8
+ import { CliQuietModeService } from "./quiet-mode.service";
9
+
10
+ describe("CliQuietModeService", () => {
11
+ let service: CliQuietModeService;
12
+
13
+ beforeEach(() => {
14
+ service = new CliQuietModeService();
15
+ });
16
+
17
+ afterEach(() => {
18
+ // 重置 quiet 模式状态
19
+ service.reset();
20
+ });
21
+
22
+ describe("singleton", () => {
23
+ test("should return singleton instance", () => {
24
+ const instance1 = CliQuietModeService.getInstance();
25
+ const instance2 = CliQuietModeService.getInstance();
26
+ expect(instance1).toBe(instance2);
27
+ });
28
+ });
29
+
30
+ describe("setQuiet", () => {
31
+ test("should enable quiet mode", () => {
32
+ service.setQuiet(true);
33
+ expect(service.isQuiet()).toBe(true);
34
+ });
35
+
36
+ test("should disable quiet mode", () => {
37
+ service.setQuiet(true);
38
+ service.setQuiet(false);
39
+ expect(service.isQuiet()).toBe(false);
40
+ });
41
+ });
42
+
43
+ describe("setQuietFromArgv", () => {
44
+ test("should set quiet mode from argv when quiet is true", () => {
45
+ const argv = { quiet: true };
46
+ service.setQuietFromArgv(argv);
47
+ expect(service.isQuiet()).toBe(true);
48
+ });
49
+
50
+ test("should not change quiet mode when quiet is false or undefined", () => {
51
+ service.setQuiet(true);
52
+ const argv = { quiet: false };
53
+ service.setQuietFromArgv(argv);
54
+ expect(service.isQuiet()).toBe(true);
55
+
56
+ service.reset();
57
+ const argv2 = {};
58
+ service.setQuietFromArgv(argv2);
59
+ expect(service.isQuiet()).toBe(false);
60
+ });
61
+ });
62
+
63
+ describe("reset", () => {
64
+ test("should reset quiet mode to false", () => {
65
+ service.setQuiet(true);
66
+ service.reset();
67
+ expect(service.isQuiet()).toBe(false);
68
+ });
69
+ });
70
+ });
71
+
72
+ describe("CliQuietModeService integration", () => {
73
+ let service: CliQuietModeService;
74
+
75
+ beforeEach(() => {
76
+ service = CliQuietModeService.getInstance();
77
+ service.reset();
78
+ });
79
+
80
+ afterEach(() => {
81
+ service.reset();
82
+ });
83
+
84
+ test("should enable quiet mode when -q flag is provided", () => {
85
+ // sessions grep -q
86
+ const argv = { quiet: true };
87
+ service.setQuietFromArgv(argv);
88
+ expect(service.isQuiet()).toBe(true);
89
+ });
90
+
91
+ test("should not enable quiet mode when -q flag is not provided", () => {
92
+ // tasks list (no -q)
93
+ const argv = {};
94
+ service.setQuietFromArgv(argv);
95
+ expect(service.isQuiet()).toBe(false);
96
+ });
97
+
98
+ test("should preserve quiet mode across command sequence when always enabled", () => {
99
+ // 如果命令行参数一直带有 -q,quiet 模式应该保持
100
+ service.setQuietFromArgv({ quiet: true });
101
+ expect(service.isQuiet()).toBe(true);
102
+
103
+ service.setQuietFromArgv({ quiet: true });
104
+ expect(service.isQuiet()).toBe(true);
105
+ });
106
+
107
+ test("should reset quiet mode manually when needed", () => {
108
+ service.setQuietFromArgv({ quiet: true });
109
+ expect(service.isQuiet()).toBe(true);
110
+
111
+ service.reset();
112
+ expect(service.isQuiet()).toBe(false);
113
+ });
114
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * @fileoverview CLI Quiet Mode Service
3
+ *
4
+ * 统一管理 CLI 的 quiet 模式状态
5
+ *
6
+ * 设计目标:
7
+ * 1. 单例模式 - 全局共享状态
8
+ * 2. 与 LogTraceComponent 集成 - 通过 ConfigComponent 配置
9
+ * 3. 支持命令行参数和配置文件双重控制
10
+ *
11
+ * 使用方式:
12
+ * ```typescript
13
+ * // 1. 从命令行参数设置
14
+ * const service = CliQuietModeService.getInstance();
15
+ * service.setQuietFromArgv(argv);
16
+ *
17
+ * // 2. 在命令中使用
18
+ * if (service.isQuiet()) {
19
+ * setQuietMode(true);
20
+ * }
21
+ *
22
+ * // 3. 重置状态(新命令开始时)
23
+ * service.reset();
24
+ * ```
25
+ */
26
+
27
+ import { setQuietMode as setCoreQuietMode, isQuietMode as isCoreQuietMode } from "@ai-setting/roy-agent-core";
28
+
29
+ export class CliQuietModeService {
30
+ private static instance: CliQuietModeService;
31
+
32
+ private constructor() {}
33
+
34
+ /**
35
+ * 获取单例实例
36
+ */
37
+ static getInstance(): CliQuietModeService {
38
+ if (!CliQuietModeService.instance) {
39
+ CliQuietModeService.instance = new CliQuietModeService();
40
+ }
41
+ return CliQuietModeService.instance;
42
+ }
43
+
44
+ /**
45
+ * 设置 quiet 模式
46
+ *
47
+ * 内部调用 core 的 setQuietMode,同步到 LogTraceComponent
48
+ */
49
+ setQuiet(value: boolean): void {
50
+ setCoreQuietMode(value);
51
+ }
52
+
53
+ /**
54
+ * 从命令行参数设置 quiet 模式
55
+ *
56
+ * @param argv - yargs 解析后的参数对象
57
+ *
58
+ * 注意:只在 quiet 为 true 时设置,避免覆盖配置文件中的设置
59
+ */
60
+ setQuietFromArgv(argv: Record<string, unknown>): void {
61
+ if (argv.quiet === true) {
62
+ this.setQuiet(true);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * 获取当前 quiet 模式状态
68
+ */
69
+ isQuiet(): boolean {
70
+ return isCoreQuietMode();
71
+ }
72
+
73
+ /**
74
+ * 重置 quiet 模式为 false
75
+ *
76
+ * 用于新命令开始时清理状态
77
+ */
78
+ reset(): void {
79
+ setCoreQuietMode(false);
80
+ }
81
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * @fileoverview StreamOutputService Tests
3
+ */
4
+
5
+ import { describe, test, expect, beforeEach } from "bun:test";
6
+ import { StreamOutputService, resetStreamAbort } from "./stream-output.service";
7
+ import type { EnvEvent } from "@ai-setting/roy-agent-core";
8
+
9
+ describe("StreamOutputService", () => {
10
+ let service: StreamOutputService;
11
+
12
+ beforeEach(() => {
13
+ // 重置全局 abort 状态,避免被其他测试污染
14
+ resetStreamAbort();
15
+ service = new StreamOutputService();
16
+ });
17
+
18
+ describe("handleEvent", () => {
19
+ test("should collect text deltas", () => {
20
+ const textEvent: EnvEvent = {
21
+ id: "1",
22
+ type: "llm.text",
23
+ timestamp: Date.now(),
24
+ metadata: {},
25
+ payload: { delta: "Hello" }
26
+ };
27
+
28
+ service.handleEvent(textEvent);
29
+ expect(service.getFullText()).toBe("Hello");
30
+ });
31
+
32
+ test("should accumulate text deltas", () => {
33
+ const event1: EnvEvent = {
34
+ id: "1",
35
+ type: "llm.text",
36
+ timestamp: Date.now(),
37
+ metadata: {},
38
+ payload: { delta: "Hello " }
39
+ };
40
+
41
+ const event2: EnvEvent = {
42
+ id: "2",
43
+ type: "llm.text",
44
+ timestamp: Date.now(),
45
+ metadata: {},
46
+ payload: { delta: "world" }
47
+ };
48
+
49
+ service.handleEvent(event1);
50
+ service.handleEvent(event2);
51
+ expect(service.getFullText()).toBe("Hello world");
52
+ });
53
+
54
+ test("should handle reasoning events when showReasoning is enabled", () => {
55
+ const reasoningService = new StreamOutputService({ showReasoning: true });
56
+
57
+ const reasoningEvent: EnvEvent = {
58
+ id: "1",
59
+ type: "llm.reasoning",
60
+ timestamp: Date.now(),
61
+ metadata: {},
62
+ payload: { content: "Let me think..." }
63
+ };
64
+
65
+ // Should not throw
66
+ expect(() => reasoningService.handleEvent(reasoningEvent)).not.toThrow();
67
+ });
68
+
69
+ test("should ignore reasoning events when showReasoning is disabled", () => {
70
+ const reasoningService = new StreamOutputService({ showReasoning: false });
71
+
72
+ const reasoningEvent: EnvEvent = {
73
+ id: "1",
74
+ type: "llm.reasoning",
75
+ timestamp: Date.now(),
76
+ metadata: {},
77
+ payload: { content: "Let me think..." }
78
+ };
79
+
80
+ // Should not throw
81
+ expect(() => reasoningService.handleEvent(reasoningEvent)).not.toThrow();
82
+ });
83
+
84
+ test("should handle tool_call events when showToolCalls is enabled", () => {
85
+ const toolService = new StreamOutputService({ showToolCalls: true });
86
+
87
+ const toolEvent: EnvEvent = {
88
+ id: "1",
89
+ type: "llm.tool_call",
90
+ timestamp: Date.now(),
91
+ metadata: {},
92
+ payload: {
93
+ toolCall: {
94
+ id: "call_123",
95
+ name: "bash",
96
+ arguments: '{"command":"ls"}'
97
+ }
98
+ }
99
+ };
100
+
101
+ // Should not throw
102
+ expect(() => toolService.handleEvent(toolEvent)).not.toThrow();
103
+ });
104
+
105
+ test("should handle completed event and extract usage info", () => {
106
+ const completedEvent: EnvEvent = {
107
+ id: "1",
108
+ type: "llm.completed",
109
+ timestamp: Date.now(),
110
+ metadata: {},
111
+ payload: {
112
+ content: "Final response",
113
+ metadata: {
114
+ model: "minimax/MiniMax-M2.5-highspeed",
115
+ usage: {
116
+ promptTokens: 1000,
117
+ completionTokens: 500,
118
+ totalTokens: 1500,
119
+ },
120
+ },
121
+ },
122
+ };
123
+
124
+ // Should not throw
125
+ expect(() => service.handleEvent(completedEvent)).not.toThrow();
126
+
127
+ // Verify usage info is extracted
128
+ const usageInfo = service.getUsageInfo();
129
+ expect(usageInfo).toEqual({
130
+ model: "MiniMax-M2.5-highspeed",
131
+ providerId: "minimax",
132
+ promptTokens: 1000,
133
+ completionTokens: 500,
134
+ totalTokens: 1500,
135
+ });
136
+ });
137
+
138
+ test("should handle completed event without usage info", () => {
139
+ const completedEvent: EnvEvent = {
140
+ id: "1",
141
+ type: "llm.completed",
142
+ timestamp: Date.now(),
143
+ metadata: {},
144
+ payload: {
145
+ content: "Final response",
146
+ },
147
+ };
148
+
149
+ // Should not throw
150
+ expect(() => service.handleEvent(completedEvent)).not.toThrow();
151
+
152
+ // Usage info should be undefined
153
+ const usageInfo = service.getUsageInfo();
154
+ expect(usageInfo).toBeUndefined();
155
+ });
156
+
157
+ test("should reset usage info on reset", () => {
158
+ const completedEvent: EnvEvent = {
159
+ id: "1",
160
+ type: "llm.completed",
161
+ timestamp: Date.now(),
162
+ metadata: {},
163
+ payload: {
164
+ content: "Final response",
165
+ metadata: {
166
+ model: "minimax/MiniMax-M2.5-highspeed",
167
+ usage: {
168
+ promptTokens: 1000,
169
+ completionTokens: 500,
170
+ totalTokens: 1500,
171
+ },
172
+ },
173
+ },
174
+ };
175
+
176
+ service.handleEvent(completedEvent);
177
+ expect(service.getUsageInfo()).toBeDefined();
178
+
179
+ service.reset();
180
+ expect(service.getUsageInfo()).toBeUndefined();
181
+ });
182
+
183
+ test("should handle start event", () => {
184
+ const startEvent: EnvEvent = {
185
+ id: "1",
186
+ type: "llm.start",
187
+ timestamp: Date.now(),
188
+ metadata: {},
189
+ payload: { metadata: { model: "test-model" } }
190
+ };
191
+
192
+ // Should not throw
193
+ expect(() => service.handleEvent(startEvent)).not.toThrow();
194
+ });
195
+ });
196
+
197
+ describe("reset", () => {
198
+ test("should reset fullText", () => {
199
+ const textEvent: EnvEvent = {
200
+ id: "1",
201
+ type: "llm.text",
202
+ timestamp: Date.now(),
203
+ metadata: {},
204
+ payload: { delta: "Hello" }
205
+ };
206
+
207
+ service.handleEvent(textEvent);
208
+ expect(service.getFullText()).toBe("Hello");
209
+
210
+ service.reset();
211
+ expect(service.getFullText()).toBe("");
212
+ });
213
+ });
214
+ });