@kalenkevich/agent_007 0.0.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.
Files changed (247) hide show
  1. package/dist/agent/agent.d.ts +17 -0
  2. package/dist/agent/agent.js +6 -0
  3. package/dist/agent/agent.js.map +1 -0
  4. package/dist/agent/agent_event.d.ts +108 -0
  5. package/dist/agent/agent_event.js +56 -0
  6. package/dist/agent/agent_event.js.map +1 -0
  7. package/dist/agent/agent_event_utils.d.ts +5 -0
  8. package/dist/agent/agent_event_utils.js +66 -0
  9. package/dist/agent/agent_event_utils.js.map +1 -0
  10. package/dist/agent/cli_agent/cli_agent.d.ts +41 -0
  11. package/dist/agent/cli_agent/cli_agent.js +324 -0
  12. package/dist/agent/cli_agent/cli_agent.js.map +1 -0
  13. package/dist/agent/cli_agent/system_prompt.d.ts +1 -0
  14. package/dist/agent/cli_agent/system_prompt.js +58 -0
  15. package/dist/agent/cli_agent/system_prompt.js.map +1 -0
  16. package/dist/agent/cli_agent.d.ts +34 -0
  17. package/dist/agent/cli_agent.js +91 -0
  18. package/dist/agent/cli_agent.js.map +1 -0
  19. package/dist/agent/planner_agent/planner_agent.d.ts +28 -0
  20. package/dist/agent/planner_agent/planner_agent.js +102 -0
  21. package/dist/agent/planner_agent/planner_agent.js.map +1 -0
  22. package/dist/agent/request_processor/basic_request_processor.d.ts +17 -0
  23. package/dist/agent/request_processor/basic_request_processor.js +28 -0
  24. package/dist/agent/request_processor/basic_request_processor.js.map +1 -0
  25. package/dist/agent/request_processor/compaction_processor.d.ts +17 -0
  26. package/dist/agent/request_processor/compaction_processor.js +118 -0
  27. package/dist/agent/request_processor/compaction_processor.js.map +1 -0
  28. package/dist/agent/request_processor/request_processor.d.ts +11 -0
  29. package/dist/agent/request_processor/request_processor.js +2 -0
  30. package/dist/agent/request_processor/request_processor.js.map +1 -0
  31. package/dist/cli/init_project_command_handler.d.ts +3 -0
  32. package/dist/cli/init_project_command_handler.js +58 -0
  33. package/dist/cli/init_project_command_handler.js.map +1 -0
  34. package/dist/cli/loader.d.ts +6 -0
  35. package/dist/cli/loader.js +27 -0
  36. package/dist/cli/loader.js.map +1 -0
  37. package/dist/cli/prompt_utils.d.ts +11 -0
  38. package/dist/cli/prompt_utils.js +18 -0
  39. package/dist/cli/prompt_utils.js.map +1 -0
  40. package/dist/cli/run_command.d.ts +6 -0
  41. package/dist/cli/run_command.js +72 -0
  42. package/dist/cli/run_command.js.map +1 -0
  43. package/dist/cli/run_interactive_command.d.ts +6 -0
  44. package/dist/cli/run_interactive_command.js +282 -0
  45. package/dist/cli/run_interactive_command.js.map +1 -0
  46. package/dist/cli/run_noninteractive_command.d.ts +6 -0
  47. package/dist/cli/run_noninteractive_command.js +60 -0
  48. package/dist/cli/run_noninteractive_command.js.map +1 -0
  49. package/dist/cli_entrypoint.d.ts +2 -0
  50. package/dist/cli_entrypoint.js +110 -0
  51. package/dist/cli_entrypoint.js.map +1 -0
  52. package/dist/command/commnad_handler.d.ts +3 -0
  53. package/dist/command/commnad_handler.js +2 -0
  54. package/dist/command/commnad_handler.js.map +1 -0
  55. package/dist/command/init_project_command_handler.d.ts +4 -0
  56. package/dist/command/init_project_command_handler.js +58 -0
  57. package/dist/command/init_project_command_handler.js.map +1 -0
  58. package/dist/config/app_dir.d.ts +1 -0
  59. package/dist/config/app_dir.js +4 -0
  60. package/dist/config/app_dir.js.map +1 -0
  61. package/dist/config/config.d.ts +21 -0
  62. package/dist/config/config.js +2 -0
  63. package/dist/config/config.js.map +1 -0
  64. package/dist/config/config_loader.d.ts +2 -0
  65. package/dist/config/config_loader.js +47 -0
  66. package/dist/config/config_loader.js.map +1 -0
  67. package/dist/config/config_store.d.ts +10 -0
  68. package/dist/config/config_store.js +44 -0
  69. package/dist/config/config_store.js.map +1 -0
  70. package/dist/content.d.ts +51 -0
  71. package/dist/content.js +16 -0
  72. package/dist/content.js.map +1 -0
  73. package/dist/core/functions.d.ts +1 -0
  74. package/dist/core/functions.js +6 -0
  75. package/dist/core/functions.js.map +1 -0
  76. package/dist/core/loop.d.ts +20 -0
  77. package/dist/core/loop.js +121 -0
  78. package/dist/core/loop.js.map +1 -0
  79. package/dist/core/project_service.d.ts +11 -0
  80. package/dist/core/project_service.js +26 -0
  81. package/dist/core/project_service.js.map +1 -0
  82. package/dist/core/run.d.ts +6 -0
  83. package/dist/core/run.js +25 -0
  84. package/dist/core/run.js.map +1 -0
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.js +2 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/logger.d.ts +27 -0
  89. package/dist/logger.js +98 -0
  90. package/dist/logger.js.map +1 -0
  91. package/dist/model/adaptive_model.d.ts +16 -0
  92. package/dist/model/adaptive_model.js +57 -0
  93. package/dist/model/adaptive_model.js.map +1 -0
  94. package/dist/model/google/gemini_model.d.ts +15 -0
  95. package/dist/model/google/gemini_model.js +131 -0
  96. package/dist/model/google/gemini_model.js.map +1 -0
  97. package/dist/model/google/gemini_response_utils.d.ts +3 -0
  98. package/dist/model/google/gemini_response_utils.js +36 -0
  99. package/dist/model/google/gemini_response_utils.js.map +1 -0
  100. package/dist/model/google/gemini_streaming_utils.d.ts +28 -0
  101. package/dist/model/google/gemini_streaming_utils.js +235 -0
  102. package/dist/model/google/gemini_streaming_utils.js.map +1 -0
  103. package/dist/model/google/gen_ai_convert_utils.d.ts +6 -0
  104. package/dist/model/google/gen_ai_convert_utils.js +117 -0
  105. package/dist/model/google/gen_ai_convert_utils.js.map +1 -0
  106. package/dist/model/model.d.ts +11 -0
  107. package/dist/model/model.js +2 -0
  108. package/dist/model/model.js.map +1 -0
  109. package/dist/model/registry.d.ts +19 -0
  110. package/dist/model/registry.js +47 -0
  111. package/dist/model/registry.js.map +1 -0
  112. package/dist/model/request.d.ts +13 -0
  113. package/dist/model/request.js +2 -0
  114. package/dist/model/request.js.map +1 -0
  115. package/dist/model/request_builder_utils.d.ts +19 -0
  116. package/dist/model/request_builder_utils.js +43 -0
  117. package/dist/model/request_builder_utils.js.map +1 -0
  118. package/dist/model/response.d.ts +60 -0
  119. package/dist/model/response.js +2 -0
  120. package/dist/model/response.js.map +1 -0
  121. package/dist/model/util_llm.d.ts +10 -0
  122. package/dist/model/util_llm.js +149 -0
  123. package/dist/model/util_llm.js.map +1 -0
  124. package/dist/session/session.d.ts +14 -0
  125. package/dist/session/session.js +2 -0
  126. package/dist/session/session.js.map +1 -0
  127. package/dist/session/session_file_service.d.ts +23 -0
  128. package/dist/session/session_file_service.js +147 -0
  129. package/dist/session/session_file_service.js.map +1 -0
  130. package/dist/skills/skill.d.ts +23 -0
  131. package/dist/skills/skill.js +12 -0
  132. package/dist/skills/skill.js.map +1 -0
  133. package/dist/tools/build_in/find.d.ts +3 -0
  134. package/dist/tools/build_in/find.js +71 -0
  135. package/dist/tools/build_in/find.js.map +1 -0
  136. package/dist/tools/build_in/grep.d.ts +3 -0
  137. package/dist/tools/build_in/grep.js +97 -0
  138. package/dist/tools/build_in/grep.js.map +1 -0
  139. package/dist/tools/build_in/index.d.ts +1 -0
  140. package/dist/tools/build_in/index.js +13 -0
  141. package/dist/tools/build_in/index.js.map +1 -0
  142. package/dist/tools/build_in/list_dir.d.ts +3 -0
  143. package/dist/tools/build_in/list_dir.js +46 -0
  144. package/dist/tools/build_in/list_dir.js.map +1 -0
  145. package/dist/tools/build_in/view_file.d.ts +3 -0
  146. package/dist/tools/build_in/view_file.js +44 -0
  147. package/dist/tools/build_in/view_file.js.map +1 -0
  148. package/dist/tools/build_in/write_file.d.ts +3 -0
  149. package/dist/tools/build_in/write_file.js +50 -0
  150. package/dist/tools/build_in/write_file.js.map +1 -0
  151. package/dist/tools/functional_tool.d.ts +18 -0
  152. package/dist/tools/functional_tool.js +20 -0
  153. package/dist/tools/functional_tool.js.map +1 -0
  154. package/dist/tools/schema.d.ts +86 -0
  155. package/dist/tools/schema.js +37 -0
  156. package/dist/tools/schema.js.map +1 -0
  157. package/dist/tools/tool.d.ts +30 -0
  158. package/dist/tools/tool.js +9 -0
  159. package/dist/tools/tool.js.map +1 -0
  160. package/dist/tools/tool_call_policy.d.ts +4 -0
  161. package/dist/tools/tool_call_policy.js +4 -0
  162. package/dist/tools/tool_call_policy.js.map +1 -0
  163. package/dist/tools/tool_policy.d.ts +4 -0
  164. package/dist/tools/tool_policy.js +4 -0
  165. package/dist/tools/tool_policy.js.map +1 -0
  166. package/dist/ui/ui.d.ts +1 -0
  167. package/dist/ui/ui.js +2 -0
  168. package/dist/ui/ui.js.map +1 -0
  169. package/dist/user_input.d.ts +20 -0
  170. package/dist/user_input.js +21 -0
  171. package/dist/user_input.js.map +1 -0
  172. package/package.json +44 -0
  173. package/src/agent/agent.ts +18 -0
  174. package/src/agent/agent_event.ts +171 -0
  175. package/src/agent/agent_event_utils.ts +87 -0
  176. package/src/agent/cli_agent/cli_agent.ts +418 -0
  177. package/src/agent/cli_agent/system_prompt.ts +57 -0
  178. package/src/agent/planner_agent/planner_agent.ts +136 -0
  179. package/src/agent/request_processor/basic_request_processor.ts +46 -0
  180. package/src/agent/request_processor/compaction_processor.ts +164 -0
  181. package/src/agent/request_processor/request_processor.ts +13 -0
  182. package/src/cli/loader.ts +27 -0
  183. package/src/cli/prompt_utils.ts +19 -0
  184. package/src/cli/run_interactive_command.ts +337 -0
  185. package/src/cli/run_noninteractive_command.ts +74 -0
  186. package/src/cli_entrypoint.ts +128 -0
  187. package/src/command/commnad_handler.ts +3 -0
  188. package/src/command/init_project_command_handler.ts +66 -0
  189. package/src/config/app_dir.ts +4 -0
  190. package/src/config/config.ts +24 -0
  191. package/src/config/config_loader.ts +57 -0
  192. package/src/config/config_store.ts +50 -0
  193. package/src/content.ts +87 -0
  194. package/src/core/functions.ts +7 -0
  195. package/src/core/loop.ts +165 -0
  196. package/src/core/project_service.ts +38 -0
  197. package/src/core/run.ts +36 -0
  198. package/src/index.ts +1 -0
  199. package/src/logger.ts +128 -0
  200. package/src/model/adaptive_model.ts +77 -0
  201. package/src/model/google/gemini_model.ts +194 -0
  202. package/src/model/google/gemini_response_utils.ts +46 -0
  203. package/src/model/google/gemini_streaming_utils.ts +294 -0
  204. package/src/model/google/gen_ai_convert_utils.ts +149 -0
  205. package/src/model/model.ts +18 -0
  206. package/src/model/registry.ts +61 -0
  207. package/src/model/request.ts +15 -0
  208. package/src/model/request_builder_utils.ts +79 -0
  209. package/src/model/response.ts +66 -0
  210. package/src/model/util_llm.ts +167 -0
  211. package/src/session/session.ts +16 -0
  212. package/src/session/session_file_service.ts +207 -0
  213. package/src/skills/skill.ts +38 -0
  214. package/src/tools/build_in/find.ts +80 -0
  215. package/src/tools/build_in/grep.ts +101 -0
  216. package/src/tools/build_in/index.ts +13 -0
  217. package/src/tools/build_in/list_dir.ts +50 -0
  218. package/src/tools/build_in/view_file.ts +47 -0
  219. package/src/tools/build_in/write_file.ts +53 -0
  220. package/src/tools/functional_tool.ts +59 -0
  221. package/src/tools/schema.ts +87 -0
  222. package/src/tools/tool.ts +68 -0
  223. package/src/tools/tool_call_policy.ts +7 -0
  224. package/src/ui/ui.ts +0 -0
  225. package/src/user_input.ts +51 -0
  226. package/tests/integration/util_llm_test.ts +42 -0
  227. package/tests/unit/adaptive_model_test.ts +122 -0
  228. package/tests/unit/agent/request_processor/compaction_processor_test.ts +121 -0
  229. package/tests/unit/cli/prompt_utils_test.ts +47 -0
  230. package/tests/unit/cli_agent_test.ts +476 -0
  231. package/tests/unit/content_test.ts +56 -0
  232. package/tests/unit/logger_test.ts +109 -0
  233. package/tests/unit/loop_test.ts +141 -0
  234. package/tests/unit/model/gemini_model_test.ts +111 -0
  235. package/tests/unit/planner_agent_test.ts +52 -0
  236. package/tests/unit/project_service_test.ts +102 -0
  237. package/tests/unit/session_file_service_test.ts +160 -0
  238. package/tests/unit/tools/find_test.ts +40 -0
  239. package/tests/unit/tools/grep_test.ts +43 -0
  240. package/tests/unit/tools/list_dir_test.ts +32 -0
  241. package/tests/unit/tools/view_file_test.ts +32 -0
  242. package/tests/unit/tools/write_file_test.ts +44 -0
  243. package/tests/unit/user_input_test.ts +55 -0
  244. package/tests/unit/util_llm_test.ts +61 -0
  245. package/todo.md +29 -0
  246. package/tsconfig.json +33 -0
  247. package/vitest.config.ts +33 -0
@@ -0,0 +1,141 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { CoreAgentLoop, AgentLoopType } from "../../src/core/loop.js";
3
+ import { CliAgent } from "../../src/agent/cli_agent/cli_agent.js";
4
+ import { AgentEventType } from "../../src/agent/agent_event.js";
5
+
6
+ vi.mock("../../src/agent/cli_agent/cli_agent.js");
7
+
8
+ vi.mock("../../src/model/adaptive_model.js");
9
+
10
+ vi.mock("../../src/session/session_file_service.js", () => {
11
+ return {
12
+ SessionFileService: class {
13
+ getSession = vi.fn().mockResolvedValue({ events: [], title: undefined });
14
+ getSessionMetadata = vi.fn().mockResolvedValue({ title: undefined });
15
+ createSession = vi.fn().mockResolvedValue({ id: "test-session-id" });
16
+ appendEvent = vi.fn().mockResolvedValue(undefined);
17
+ updateSession = vi.fn().mockResolvedValue(undefined);
18
+ },
19
+ };
20
+ });
21
+
22
+ describe("CoreAgentLoop", () => {
23
+ it("should yield events on success", async () => {
24
+ const mockAgent = {
25
+ run: vi.fn().mockImplementation(async function* () {
26
+ yield { type: AgentEventType.START, streamId: "123" };
27
+ yield { type: AgentEventType.END, streamId: "123" };
28
+ }),
29
+ };
30
+ vi.mocked(CliAgent).mockImplementation(function () {
31
+ return mockAgent as any;
32
+ });
33
+
34
+ const loop = new CoreAgentLoop({
35
+ models: {
36
+ main: { modelName: "gemini-3.1-pro-preview", apiKey: "dummy" },
37
+ util: { modelName: "gemini-3-flash-preview", apiKey: "dummy" },
38
+ },
39
+ } as any);
40
+
41
+ const events: any[] = [];
42
+ loop.on(AgentLoopType.AGENT_EVENT, (event) => {
43
+ events.push(event);
44
+ });
45
+
46
+ await loop.run("hello");
47
+
48
+ expect(events.length).toBe(2);
49
+ expect(events[0].type).toBe(AgentEventType.START);
50
+ expect(events[1].type).toBe(AgentEventType.END);
51
+ });
52
+
53
+ it("should handle errors and emit ERROR event", async () => {
54
+ const mockAgent = {
55
+ run: vi.fn().mockImplementation(async function* () {
56
+ yield { type: AgentEventType.START, streamId: "123" };
57
+ throw new Error("Test error");
58
+ }),
59
+ };
60
+ vi.mocked(CliAgent).mockImplementation(function () {
61
+ return mockAgent as any;
62
+ });
63
+
64
+ const loop = new CoreAgentLoop({
65
+ models: {
66
+ main: { modelName: "gemini-3.1-pro-preview", apiKey: "dummy" },
67
+ util: { modelName: "gemini-3-flash-preview", apiKey: "dummy" },
68
+ },
69
+ } as any);
70
+
71
+ const events: any[] = [];
72
+ loop.on(AgentLoopType.AGENT_EVENT, (event) => {
73
+ events.push(event);
74
+ });
75
+
76
+ await loop.run("hello");
77
+
78
+ expect(events.length).toBe(2);
79
+ expect(events[0].type).toBe(AgentEventType.START);
80
+ expect(events[1].type).toBe(AgentEventType.ERROR);
81
+ expect(events[1].errorMessage).toBe("Test error");
82
+ expect(events[1].streamId).toBe("123"); // Should use last seen streamId
83
+ });
84
+
85
+ it("should fallback to unknown streamId if error occurs before any event", async () => {
86
+ const mockAgent = {
87
+ run: vi.fn().mockImplementation(async function* () {
88
+ throw new Error("Immediate error");
89
+ }),
90
+ };
91
+ vi.mocked(CliAgent).mockImplementation(function () {
92
+ return mockAgent as any;
93
+ });
94
+
95
+ const loop = new CoreAgentLoop({
96
+ models: {
97
+ main: { modelName: "gemini-3.1-pro-preview", apiKey: "dummy" },
98
+ util: { modelName: "gemini-3-flash-preview", apiKey: "dummy" },
99
+ },
100
+ } as any);
101
+
102
+ const events: any[] = [];
103
+ loop.on(AgentLoopType.AGENT_EVENT, (event) => {
104
+ events.push(event);
105
+ });
106
+
107
+ await loop.run("hello");
108
+
109
+ expect(events.length).toBe(1);
110
+ expect(events[0].type).toBe(AgentEventType.ERROR);
111
+ expect(events[0].errorMessage).toBe("Immediate error");
112
+ expect(events[0].streamId).toBe("unknown");
113
+ });
114
+
115
+ it("should append events to session service", async () => {
116
+ const mockAgent = {
117
+ run: vi.fn().mockImplementation(async function* () {
118
+ yield { type: AgentEventType.START, streamId: "123" };
119
+ yield { type: AgentEventType.END, streamId: "123" };
120
+ }),
121
+ };
122
+ vi.mocked(CliAgent).mockImplementation(function () {
123
+ return mockAgent as any;
124
+ });
125
+
126
+ const loop = new CoreAgentLoop({
127
+ models: {
128
+ main: { modelName: "gemini-3.1-pro-preview", apiKey: "dummy" },
129
+ util: { modelName: "gemini-3-flash-preview", apiKey: "dummy" },
130
+ },
131
+ } as any);
132
+
133
+ const mockSessionService = (loop as any).sessionService;
134
+
135
+ await loop.run("hello");
136
+
137
+ expect(mockSessionService.appendEvent).toHaveBeenCalledTimes(2);
138
+ expect(mockSessionService.appendEvent).toHaveBeenCalledWith("test-session-id", expect.objectContaining({ type: AgentEventType.START }));
139
+ expect(mockSessionService.appendEvent).toHaveBeenCalledWith("test-session-id", expect.objectContaining({ type: AgentEventType.END }));
140
+ });
141
+ });
@@ -0,0 +1,111 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { Gemini } from "../../../src/model/google/gemini_model.js";
3
+
4
+ vi.mock("@google/genai", () => {
5
+ const mockGenerateContent = vi.fn();
6
+ const mockGenerateContentStream = vi.fn();
7
+ const mockCountTokens = vi.fn();
8
+
9
+ return {
10
+ GoogleGenAI: vi.fn().mockImplementation(function () {
11
+ return {
12
+ models: {
13
+ generateContent: mockGenerateContent,
14
+ generateContentStream: mockGenerateContentStream,
15
+ countTokens: mockCountTokens,
16
+ },
17
+ };
18
+ }),
19
+ ThinkingLevel: {
20
+ LOW: "LOW",
21
+ MEDIUM: "MEDIUM",
22
+ HIGH: "HIGH",
23
+ },
24
+ };
25
+ });
26
+
27
+ describe("Gemini Model", () => {
28
+ let gemini: Gemini;
29
+ let mockClient: any;
30
+
31
+ beforeEach(() => {
32
+ vi.clearAllMocks();
33
+ gemini = new Gemini({ modelName: "test-model", apiKey: "test-key" });
34
+ mockClient = (gemini as any).client;
35
+ });
36
+
37
+ it("should call generateContent for non-streaming request", async () => {
38
+ const mockResponse = {
39
+ candidates: [
40
+ {
41
+ content: {
42
+ parts: [{ text: "Hello response" }],
43
+ },
44
+ },
45
+ ],
46
+ };
47
+ mockClient.models.generateContent.mockResolvedValue(mockResponse);
48
+
49
+ const generator = gemini.run({
50
+ contents: [{ role: "user", parts: [{ type: "text", text: "hello" }] }],
51
+ });
52
+ const results = [];
53
+ for await (const res of generator) {
54
+ results.push(res);
55
+ }
56
+
57
+ expect(mockClient.models.generateContent).toHaveBeenCalled();
58
+ expect(results.length).toBe(1);
59
+ expect(results[0].errorCode).toBeUndefined();
60
+ });
61
+
62
+ it("should call generateContentStream for streaming request", async () => {
63
+ const mockStream = [
64
+ {
65
+ candidates: [
66
+ {
67
+ content: {
68
+ parts: [{ text: "Hello " }],
69
+ },
70
+ },
71
+ ],
72
+ },
73
+ {
74
+ candidates: [
75
+ {
76
+ content: {
77
+ parts: [{ text: "response" }],
78
+ },
79
+ },
80
+ ],
81
+ },
82
+ ];
83
+
84
+ mockClient.models.generateContentStream.mockResolvedValue(mockStream);
85
+
86
+ const generator = gemini.run(
87
+ {
88
+ contents: [{ role: "user", parts: [{ type: "text", text: "hello" }] }],
89
+ },
90
+ { stream: true },
91
+ );
92
+ const results = [];
93
+ for await (const res of generator) {
94
+ results.push(res);
95
+ }
96
+
97
+ expect(mockClient.models.generateContentStream).toHaveBeenCalled();
98
+ expect(results.length).toBeGreaterThan(0);
99
+ });
100
+
101
+ it("should call countTokens", async () => {
102
+ mockClient.models.countTokens.mockResolvedValue({ totalTokens: 42 });
103
+
104
+ const tokens = await gemini.countTokens({
105
+ contents: [{ role: "user", parts: [{ type: "text", text: "hello" }] }],
106
+ });
107
+
108
+ expect(mockClient.models.countTokens).toHaveBeenCalled();
109
+ expect(tokens).toBe(42);
110
+ });
111
+ });
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { PlannerAgent } from "../../src/agent/planner_agent/planner_agent.js";
3
+ import { AgentEventType } from "../../src/agent/agent_event.js";
4
+ import * as fs from "node:fs/promises";
5
+
6
+ vi.mock("node:fs/promises", async (importOriginal) => {
7
+ const actual = (await importOriginal()) as any;
8
+ return {
9
+ ...actual,
10
+ writeFile: vi.fn().mockResolvedValue(undefined),
11
+ };
12
+ });
13
+
14
+ describe("PlannerAgent", () => {
15
+ const mockModel = {
16
+ run: vi.fn().mockImplementation(async function* () {
17
+ yield {
18
+ content: {
19
+ role: "agent",
20
+ parts: [{ type: "text", text: "Step 1: Do something\nStep 2: Done" }],
21
+ },
22
+ };
23
+ }),
24
+ };
25
+
26
+ beforeEach(() => {
27
+ vi.clearAllMocks();
28
+ });
29
+
30
+ it("should generate plan and ask for approval", async () => {
31
+ const agent = new PlannerAgent({
32
+ model: mockModel as any,
33
+ });
34
+
35
+ const events: any[] = [];
36
+ for await (const event of agent.run("Plan a trip")) {
37
+ events.push(event);
38
+ }
39
+
40
+ expect(mockModel.run).toHaveBeenCalled();
41
+ expect(fs.writeFile).toHaveBeenCalled();
42
+
43
+ expect(events.length).toBeGreaterThanOrEqual(4);
44
+ expect(events[0].type).toBe(AgentEventType.START);
45
+
46
+ const userInputRequest = events.find(
47
+ (e) => e.type === AgentEventType.USER_INPUT_REQUEST,
48
+ );
49
+ expect(userInputRequest).toBeTruthy();
50
+ expect(userInputRequest.requestSchema.type).toBe("plan_approval");
51
+ });
52
+ });
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { ProjectService } from "../../src/core/project_service.js";
3
+ import { configStore } from "../../src/config/config_store.js";
4
+ import { createHash } from "node:crypto";
5
+
6
+ vi.mock("../../src/config/config_store.js");
7
+
8
+ describe("ProjectService", () => {
9
+ const mockCwd = "/mock/project";
10
+ let service: ProjectService;
11
+ let expectedProjectId: string;
12
+
13
+ beforeEach(() => {
14
+ vi.resetAllMocks();
15
+ service = new ProjectService(mockCwd);
16
+ expectedProjectId = createHash("sha256").update(mockCwd).digest("hex");
17
+ });
18
+
19
+ describe("constructor", () => {
20
+ it("should use default process.cwd() if not provided", () => {
21
+ const mockProcessCwd = "/default/cwd";
22
+ const spy = vi.spyOn(process, "cwd").mockReturnValue(mockProcessCwd);
23
+
24
+ const defaultService = new ProjectService();
25
+ const expectedHash = createHash("sha256")
26
+ .update(mockProcessCwd)
27
+ .digest("hex");
28
+
29
+ expect(defaultService.getProjectId()).toBe(expectedHash);
30
+
31
+ spy.mockRestore();
32
+ });
33
+ });
34
+
35
+ describe("getProjectId", () => {
36
+ it("should return the sha256 hash of the cwd", () => {
37
+ expect(service.getProjectId()).toBe(expectedProjectId);
38
+ });
39
+ });
40
+
41
+ describe("getConstantsPath", () => {
42
+ it("should return the correct path including project ID", () => {
43
+ expect(service.getConstantsPath()).toBe(
44
+ `projects/${expectedProjectId}/constants.json`,
45
+ );
46
+ });
47
+ });
48
+
49
+ describe("getConstants", () => {
50
+ it("should call configStore.get with the correct path", async () => {
51
+ const mockData = JSON.stringify({ foo: "bar" });
52
+ vi.mocked(configStore.get).mockResolvedValue(mockData);
53
+
54
+ const result = await service.getConstants();
55
+
56
+ expect(configStore.get).toHaveBeenCalledWith(
57
+ `projects/${expectedProjectId}/constants.json`,
58
+ );
59
+ expect(result).toBe(mockData);
60
+ });
61
+
62
+ it("should return null if configStore.get returns null", async () => {
63
+ vi.mocked(configStore.get).mockResolvedValue(null);
64
+
65
+ const result = await service.getConstants();
66
+
67
+ expect(result).toBeNull();
68
+ });
69
+ });
70
+
71
+ describe("saveConstants", () => {
72
+ it("should call configStore.set with correct path and stringified data", async () => {
73
+ const mockConstants = { foo: "bar" };
74
+ vi.mocked(configStore.set).mockResolvedValue(undefined);
75
+
76
+ await service.saveConstants(mockConstants);
77
+
78
+ expect(configStore.set).toHaveBeenCalledWith(
79
+ `projects/${expectedProjectId}/constants.json`,
80
+ JSON.stringify(mockConstants, null, 2),
81
+ );
82
+ });
83
+ });
84
+
85
+ describe("isInitialized", () => {
86
+ it("should return true if constants exist", async () => {
87
+ vi.mocked(configStore.get).mockResolvedValue(JSON.stringify({}));
88
+
89
+ const result = await service.isInitialized();
90
+
91
+ expect(result).toBe(true);
92
+ });
93
+
94
+ it("should return false if constants do not exist", async () => {
95
+ vi.mocked(configStore.get).mockResolvedValue(null);
96
+
97
+ const result = await service.isInitialized();
98
+
99
+ expect(result).toBe(false);
100
+ });
101
+ });
102
+ });
@@ -0,0 +1,160 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import {
3
+ SessionFileService,
4
+ loadFileData,
5
+ } from "../../src/session/session_file_service.js";
6
+ import * as fs from "node:fs/promises";
7
+ import * as path from "node:path";
8
+
9
+ vi.mock("node:fs/promises");
10
+ vi.mock("../../src/config/app_dir.js", () => ({
11
+ APP_FILE_DIR: "/mock/app/dir",
12
+ }));
13
+
14
+ describe("SessionFileService", () => {
15
+ let service: SessionFileService;
16
+ const rootDir = "/mock/app/dir/sessions";
17
+
18
+ beforeEach(() => {
19
+ vi.resetAllMocks();
20
+ service = new SessionFileService();
21
+ });
22
+
23
+ describe("createSession", () => {
24
+ it("should create session directory and files", async () => {
25
+ const mockAgentName = "test-agent";
26
+ const mockEvents = [{ type: "test-event" } as any];
27
+
28
+ vi.mocked(fs.mkdir).mockResolvedValue(undefined);
29
+ vi.mocked(fs.writeFile).mockResolvedValue(undefined);
30
+
31
+ const session = await service.createSession(mockAgentName, mockEvents);
32
+
33
+ expect(session).toBeTruthy();
34
+ expect(session.id).toBeTruthy();
35
+ expect(session.agentName).toBe(mockAgentName);
36
+
37
+ const sessionDir = path.join(rootDir, session.id);
38
+ expect(fs.mkdir).toHaveBeenCalledWith(rootDir, { recursive: true });
39
+ expect(fs.mkdir).toHaveBeenCalledWith(sessionDir, { recursive: true });
40
+ expect(fs.writeFile).toHaveBeenCalledTimes(2);
41
+ });
42
+ });
43
+
44
+ describe("getSession", () => {
45
+ it("should return combined session data", async () => {
46
+ const sessionId = "123";
47
+ const mockSession = { id: sessionId, events: [] };
48
+ const mockMeta = { id: sessionId, title: "Test" };
49
+
50
+ vi.mocked(fs.readFile).mockImplementation(async (filePath: any) => {
51
+ if (filePath.includes("session.json"))
52
+ return JSON.stringify(mockSession);
53
+ if (filePath.includes("metadata.json")) return JSON.stringify(mockMeta);
54
+ throw new Error("File not found");
55
+ });
56
+
57
+ const session = await service.getSession(sessionId);
58
+ expect(session).toEqual({ id: sessionId, events: [], title: "Test" });
59
+ });
60
+
61
+ it("should throw error if session or meta is missing", async () => {
62
+ const sessionId = "123";
63
+ const error = new Error("File not found");
64
+ (error as any).code = "ENOENT";
65
+ vi.mocked(fs.readFile).mockRejectedValue(error);
66
+
67
+ await expect(service.getSession(sessionId)).rejects.toThrow(
68
+ `Session ${sessionId} not found`,
69
+ );
70
+ });
71
+ });
72
+
73
+ describe("listSessions", () => {
74
+ it("should return list of session metadata", async () => {
75
+ vi.mocked(fs.readdir).mockResolvedValue(["session1", "session2"] as any);
76
+
77
+ vi.mocked(fs.readFile).mockImplementation(async (filePath: any) => {
78
+ if (filePath.includes("session1"))
79
+ return JSON.stringify({ id: "session1", title: "Title 1" });
80
+ if (filePath.includes("session2"))
81
+ return JSON.stringify({ id: "session2", title: "Title 2" });
82
+ throw new Error("File not found");
83
+ });
84
+
85
+ const list = await service.listSessions();
86
+ expect(list.length).toBe(2);
87
+ expect(list[0].id).toBe("session1");
88
+ expect(list[1].id).toBe("session2");
89
+ });
90
+
91
+ it("should handle corrupt metadata gracefully", async () => {
92
+ vi.mocked(fs.readdir).mockResolvedValue(["session1", "session2"] as any);
93
+
94
+ vi.mocked(fs.readFile).mockImplementation(async (filePath: any) => {
95
+ if (filePath.includes("session1"))
96
+ return JSON.stringify({ id: "session1", title: "Title 1" });
97
+ if (filePath.includes("session2")) throw new Error("JSON parse error");
98
+ throw new Error("File not found");
99
+ });
100
+
101
+ const list = await service.listSessions();
102
+ expect(list.length).toBe(1);
103
+ expect(list[0].id).toBe("session1");
104
+ });
105
+ });
106
+
107
+ describe("updateSession", () => {
108
+ it("should update session title", async () => {
109
+ const sessionId = "123";
110
+ const mockMeta = { id: sessionId, title: "Old Title" };
111
+ vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockMeta));
112
+ vi.mocked(fs.writeFile).mockResolvedValue(undefined);
113
+
114
+ await service.updateSession(sessionId, { title: "New Title" });
115
+
116
+ expect(fs.writeFile).toHaveBeenCalledTimes(1);
117
+ const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
118
+ const writtenData = JSON.parse(writeCall[1] as string);
119
+ expect(writtenData.title).toBe("New Title");
120
+ });
121
+ });
122
+
123
+ describe("appendEvent", () => {
124
+ it("should append event to session", async () => {
125
+ const sessionId = "123";
126
+ const mockSession = { id: sessionId, events: [] };
127
+ vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockSession));
128
+ vi.mocked(fs.writeFile).mockResolvedValue(undefined);
129
+
130
+ const event = { type: "test" } as any;
131
+ await service.appendEvent(sessionId, event);
132
+
133
+ expect(fs.writeFile).toHaveBeenCalledTimes(1);
134
+ const writeCall = vi.mocked(fs.writeFile).mock.calls[0];
135
+ const writtenData = JSON.parse(writeCall[1] as string);
136
+ expect(writtenData.events.length).toBe(1);
137
+ expect(writtenData.events[0]).toEqual(event);
138
+ });
139
+ });
140
+
141
+ describe("loadFileData", () => {
142
+ it("should return undefined on ENOENT", async () => {
143
+ const error = new Error("File not found");
144
+ (error as any).code = "ENOENT";
145
+ vi.mocked(fs.readFile).mockRejectedValue(error);
146
+
147
+ const result = await loadFileData("/some/file");
148
+ expect(result).toBeUndefined();
149
+ });
150
+
151
+ it("should throw on other errors", async () => {
152
+ const error = new Error("Some other error");
153
+ vi.mocked(fs.readFile).mockRejectedValue(error);
154
+
155
+ await expect(loadFileData("/some/file")).rejects.toThrow(
156
+ "Some other error",
157
+ );
158
+ });
159
+ });
160
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { FIND_TOOL } from "../../../src/tools/build_in/find.js";
3
+ import * as fs from "node:fs/promises";
4
+
5
+ vi.mock("node:fs/promises");
6
+
7
+ describe("FindTool", () => {
8
+ beforeEach(() => {
9
+ vi.resetAllMocks();
10
+ });
11
+
12
+ it("should find files matching pattern", async () => {
13
+ const mockStats = { isDirectory: () => true };
14
+ vi.mocked(fs.stat).mockResolvedValue(mockStats as any);
15
+ vi.mocked(fs.readdir).mockResolvedValue([
16
+ { name: "file1.txt", isDirectory: () => false, isFile: () => true },
17
+ { name: "file2.js", isDirectory: () => false, isFile: () => true },
18
+ ] as any);
19
+
20
+ const result = await FIND_TOOL.execute({ pattern: "\\.txt$", path: "." });
21
+
22
+ expect(result.files).toHaveLength(1);
23
+ expect(result.files[0]).toBe("file1.txt");
24
+ });
25
+
26
+ it("should throw error if path is outside project", async () => {
27
+ await expect(
28
+ FIND_TOOL.execute({ pattern: "test", path: "../outside" }),
29
+ ).rejects.toThrow(/Access denied/);
30
+ });
31
+
32
+ it("should throw error if path is not a directory", async () => {
33
+ const mockStats = { isDirectory: () => false };
34
+ vi.mocked(fs.stat).mockResolvedValue(mockStats as any);
35
+
36
+ await expect(
37
+ FIND_TOOL.execute({ pattern: "test", path: "file.txt" }),
38
+ ).rejects.toThrow(/is not a directory/);
39
+ });
40
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { GREP_TOOL } from "../../../src/tools/build_in/grep.js";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+
6
+ vi.mock("node:fs/promises");
7
+
8
+ describe("GrepTool", () => {
9
+ beforeEach(() => {
10
+ vi.resetAllMocks();
11
+ });
12
+
13
+ it("should find matches in a file", async () => {
14
+ const mockStats = { isFile: () => true, isDirectory: () => false };
15
+ vi.mocked(fs.stat).mockResolvedValue(mockStats as any);
16
+ vi.mocked(fs.readFile).mockResolvedValue("line1\nmatch this\nline3" as any);
17
+
18
+ const result = await GREP_TOOL.execute({ pattern: "match", path: "test.txt" });
19
+
20
+ expect(result.matches).toHaveLength(1);
21
+ expect(result.matches[0]).toEqual({
22
+ file: "test.txt",
23
+ line: 2,
24
+ content: "match this",
25
+ });
26
+ });
27
+
28
+ it("should throw error if path is outside project", async () => {
29
+ await expect(
30
+ GREP_TOOL.execute({ pattern: "test", path: "../outside.txt" }),
31
+ ).rejects.toThrow(/Access denied/);
32
+ });
33
+
34
+ it("should handle file read errors gracefully (ignore file)", async () => {
35
+ const mockStats = { isFile: () => true, isDirectory: () => false };
36
+ vi.mocked(fs.stat).mockResolvedValue(mockStats as any);
37
+ vi.mocked(fs.readFile).mockRejectedValue(new Error("Read error") as any);
38
+
39
+ const result = await GREP_TOOL.execute({ pattern: "test", path: "test.txt" });
40
+
41
+ expect(result.matches).toHaveLength(0);
42
+ });
43
+ });
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { LIST_DIR_TOOL } from "../../../src/tools/build_in/list_dir.js";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+
6
+ vi.mock("node:fs/promises");
7
+
8
+ describe("ListDirTool", () => {
9
+ it("should list directory contents", async () => {
10
+ const mockFiles = ["file1.txt", "dir1"];
11
+ vi.mocked(fs.readdir).mockResolvedValue(mockFiles as any);
12
+
13
+ const result = await LIST_DIR_TOOL.execute({ path: "." });
14
+
15
+ expect(result).toEqual({ files: mockFiles });
16
+ expect(fs.readdir).toHaveBeenCalledWith(path.resolve("."));
17
+ });
18
+
19
+ it("should throw error if path is outside project", async () => {
20
+ await expect(LIST_DIR_TOOL.execute({ path: "../outside" })).rejects.toThrow(
21
+ /Access denied/,
22
+ );
23
+ });
24
+
25
+ it("should handle readdir errors", async () => {
26
+ vi.mocked(fs.readdir).mockRejectedValue(new Error("Read error") as any);
27
+
28
+ await expect(LIST_DIR_TOOL.execute({ path: "." })).rejects.toThrow(
29
+ /Failed to list directory/,
30
+ );
31
+ });
32
+ });