@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,47 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { isYes, parseUserAction } from "../../../src/cli/prompt_utils.js";
3
+
4
+ describe("prompt_utils", () => {
5
+ describe("isYes", () => {
6
+ it("should return true for 'yes', 'y', 'accept' case-insensitive", () => {
7
+ expect(isYes("yes")).toBe(true);
8
+ expect(isYes("YES")).toBe(true);
9
+ expect(isYes("y")).toBe(true);
10
+ expect(isYes("Y")).toBe(true);
11
+ expect(isYes("accept")).toBe(true);
12
+ expect(isYes("Accept")).toBe(true);
13
+ });
14
+
15
+ it("should return false for other strings", () => {
16
+ expect(isYes("no")).toBe(false);
17
+ expect(isYes("n")).toBe(false);
18
+ expect(isYes("random")).toBe(false);
19
+ });
20
+
21
+ it("should handle whitespace", () => {
22
+ expect(isYes(" yes ")).toBe(true);
23
+ expect(isYes("y ")).toBe(true);
24
+ });
25
+
26
+ it("should use default value for empty input", () => {
27
+ expect(isYes("")).toBe(false);
28
+ expect(isYes("", true)).toBe(true);
29
+ expect(isYes(" ")).toBe(false);
30
+ expect(isYes(" ", true)).toBe(true);
31
+ });
32
+ });
33
+
34
+ describe("parseUserAction", () => {
35
+ it("should return 'accept' for yes-like inputs", () => {
36
+ expect(parseUserAction("yes")).toBe("accept");
37
+ expect(parseUserAction("y")).toBe("accept");
38
+ expect(parseUserAction("accept")).toBe("accept");
39
+ });
40
+
41
+ it("should return 'decline' for other inputs", () => {
42
+ expect(parseUserAction("no")).toBe("decline");
43
+ expect(parseUserAction("n")).toBe("decline");
44
+ expect(parseUserAction("")).toBe("decline");
45
+ });
46
+ });
47
+ });
@@ -0,0 +1,476 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { CliAgent } from "../../src/agent/cli_agent/cli_agent.js";
3
+ import {
4
+ AgentEventType,
5
+ type ToolCallEvent,
6
+ } from "../../src/agent/agent_event.js";
7
+ import type { Tool } from "../../src/tools/tool.js";
8
+ import type { ToolCallPolicy } from "../../src/tools/tool_call_policy.js";
9
+
10
+ vi.mock("node:fs/promises", async (importOriginal) => {
11
+ const actual = (await importOriginal()) as any;
12
+ return {
13
+ ...actual,
14
+ readFile: vi.fn().mockResolvedValue("Mocked plan content"),
15
+ };
16
+ });
17
+
18
+ describe("CliAgent - Tool Confirmation", () => {
19
+ it("should request confirmation when policy requires it", async () => {
20
+ const mockModel = {
21
+ run: vi.fn().mockImplementation(async function* () {
22
+ yield {
23
+ content: {
24
+ role: "agent",
25
+ parts: [
26
+ {
27
+ type: "function_call",
28
+ id: "call_123",
29
+ name: "test_tool",
30
+ args: { arg1: "val1" },
31
+ },
32
+ ],
33
+ },
34
+ };
35
+ }),
36
+ };
37
+
38
+ const mockTool: Tool = {
39
+ name: "test_tool",
40
+ description: "test",
41
+ params: {} as any,
42
+ output: {} as any,
43
+ execute: vi.fn().mockResolvedValue({ result: "ok" }),
44
+ toFunctionDeclaration: () => ({ name: "test_tool", description: "test" }),
45
+ };
46
+
47
+ const policy: ToolCallPolicy = { confirmationRequired: true };
48
+
49
+ const agent = new CliAgent({
50
+ model: mockModel as any,
51
+ toolPolicies: { test_tool: policy },
52
+ tools: [mockTool],
53
+ });
54
+
55
+ const events: any[] = [];
56
+ for await (const event of agent.run("hello")) {
57
+ console.log("Event yielded:", event.type);
58
+ events.push(event);
59
+ }
60
+
61
+ expect(events.length).toBe(4);
62
+ expect(events[0].type).toBe(AgentEventType.START);
63
+ expect(events[1].type).toBe(AgentEventType.MESSAGE);
64
+ expect(events[2].type).toBe(AgentEventType.TOOL_CALL);
65
+ expect(events[3].type).toBe(AgentEventType.USER_INPUT_REQUEST);
66
+ expect(events[3].requestId).toBe("call_123");
67
+
68
+ expect(mockTool.execute).not.toHaveBeenCalled();
69
+ });
70
+
71
+ it("should execute tool when resumed with 'yes'", async () => {
72
+ const mockModel = {
73
+ run: vi.fn().mockImplementation(async function* () {
74
+ yield {
75
+ content: {
76
+ role: "agent",
77
+ parts: [{ type: "text", text: "Model response after tool" }],
78
+ },
79
+ };
80
+ }),
81
+ };
82
+
83
+ const mockTool: Tool = {
84
+ name: "test_tool",
85
+ description: "test",
86
+ params: {} as any,
87
+ output: {} as any,
88
+ execute: vi.fn().mockResolvedValue({ result: "ok" }),
89
+ toFunctionDeclaration: () => ({ name: "test_tool", description: "test" }),
90
+ };
91
+
92
+ const policy: ToolCallPolicy = { confirmationRequired: true };
93
+
94
+ const agent = new CliAgent({
95
+ model: mockModel as any,
96
+ toolPolicies: { test_tool: policy },
97
+ tools: [mockTool],
98
+ });
99
+
100
+ const streamId = "stream_123";
101
+ (agent as any).history = [
102
+ { type: AgentEventType.START, streamId, id: "1" } as any,
103
+ {
104
+ type: AgentEventType.MESSAGE,
105
+ streamId,
106
+ id: "2",
107
+ role: "user",
108
+ parts: [{ type: "text", text: "hello" }],
109
+ } as any,
110
+ {
111
+ type: AgentEventType.TOOL_CALL,
112
+ streamId,
113
+ id: "3",
114
+ requestId: "call_123",
115
+ name: "test_tool",
116
+ args: {},
117
+ } as any,
118
+ {
119
+ type: AgentEventType.USER_INPUT_REQUEST,
120
+ streamId,
121
+ id: "4",
122
+ requestId: "call_123",
123
+ message: "Confirm?",
124
+ } as any,
125
+ ];
126
+ (agent as any).historyContent = [
127
+ { role: "user", parts: [{ type: "text", text: "hello" }] },
128
+ {
129
+ role: "agent",
130
+ parts: [
131
+ {
132
+ type: "function_call",
133
+ id: "call_123",
134
+ name: "test_tool",
135
+ args: {},
136
+ },
137
+ ],
138
+ },
139
+ ];
140
+ (agent as any).streamId = streamId;
141
+
142
+ const events: any[] = [];
143
+ for await (const event of agent.run({
144
+ type: AgentEventType.USER_INPUT_RESPONSE,
145
+ id: "resp_123",
146
+ streamId,
147
+ timestamp: new Date().toISOString(),
148
+ role: "user",
149
+ requestId: "call_123",
150
+ action: "accept",
151
+ })) {
152
+ console.log("Event yielded (yes):", event.type);
153
+ events.push(event);
154
+ }
155
+
156
+ expect(events.length).toBe(3);
157
+ expect(events[0].type).toBe(AgentEventType.TOOL_RESPONSE);
158
+ expect(events[2].type).toBe(AgentEventType.END);
159
+ expect(events[0].requestId).toBe("call_123");
160
+ expect(events[0].result).toEqual({ result: "ok" });
161
+
162
+ expect(mockTool.execute).toHaveBeenCalled();
163
+ });
164
+
165
+ it("should decline tool when resumed with 'no'", async () => {
166
+ const mockModel = {
167
+ run: vi.fn().mockImplementation(async function* () {
168
+ yield {
169
+ content: {
170
+ role: "agent",
171
+ parts: [{ type: "text", text: "Model response after decline" }],
172
+ },
173
+ };
174
+ }),
175
+ };
176
+
177
+ const mockTool: Tool = {
178
+ name: "test_tool",
179
+ description: "test",
180
+ params: {} as any,
181
+ output: {} as any,
182
+ execute: vi.fn().mockResolvedValue({ result: "ok" }),
183
+ toFunctionDeclaration: () => ({ name: "test_tool", description: "test" }),
184
+ };
185
+
186
+ const policy: ToolCallPolicy = { confirmationRequired: true };
187
+
188
+ const agent = new CliAgent({
189
+ model: mockModel as any,
190
+ toolPolicies: { test_tool: policy },
191
+ tools: [mockTool],
192
+ });
193
+
194
+ const streamId = "stream_123";
195
+ (agent as any).history = [
196
+ { type: AgentEventType.START, streamId, id: "1" } as any,
197
+ {
198
+ type: AgentEventType.MESSAGE,
199
+ streamId,
200
+ id: "2",
201
+ role: "user",
202
+ parts: [{ type: "text", text: "hello" }],
203
+ } as any,
204
+ {
205
+ type: AgentEventType.TOOL_CALL,
206
+ streamId,
207
+ id: "3",
208
+ requestId: "call_123",
209
+ name: "test_tool",
210
+ args: {},
211
+ } as any,
212
+ {
213
+ type: AgentEventType.USER_INPUT_REQUEST,
214
+ streamId,
215
+ id: "4",
216
+ requestId: "call_123",
217
+ message: "Confirm?",
218
+ } as any,
219
+ ];
220
+ (agent as any).historyContent = [
221
+ { role: "user", parts: [{ type: "text", text: "hello" }] },
222
+ {
223
+ role: "agent",
224
+ parts: [
225
+ {
226
+ type: "function_call",
227
+ id: "call_123",
228
+ name: "test_tool",
229
+ args: {},
230
+ },
231
+ ],
232
+ },
233
+ ];
234
+ (agent as any).streamId = streamId;
235
+
236
+ const events: any[] = [];
237
+ for await (const event of agent.run({
238
+ type: AgentEventType.USER_INPUT_RESPONSE,
239
+ id: "resp_456",
240
+ streamId,
241
+ timestamp: new Date().toISOString(),
242
+ role: "user",
243
+ requestId: "call_123",
244
+ action: "decline",
245
+ })) {
246
+ console.log("Event yielded (no):", event.type);
247
+ events.push(event);
248
+ }
249
+
250
+ expect(events.length).toBe(3);
251
+ expect(events[0].type).toBe(AgentEventType.TOOL_RESPONSE);
252
+ expect(events[2].type).toBe(AgentEventType.END);
253
+ expect(events[0].requestId).toBe("call_123");
254
+ expect(events[0].error).toBe("User declined tool execution");
255
+
256
+ expect(mockTool.execute).not.toHaveBeenCalled();
257
+ });
258
+
259
+ it("should trigger context compaction when token limit is exceeded", async () => {
260
+ const mockModel = {
261
+ run: vi.fn().mockImplementation(async function* () {
262
+ yield {
263
+ content: {
264
+ role: "agent",
265
+ parts: [{ type: "text", text: "Response after compaction" }],
266
+ },
267
+ };
268
+ }),
269
+ countTokens: vi.fn().mockResolvedValue(1000),
270
+ modelName: "mock-model",
271
+ };
272
+
273
+ const agent = new CliAgent({
274
+ model: mockModel as any,
275
+ compactionConfig: {
276
+ enabled: true,
277
+ strategy: "truncate",
278
+ maxTokens: 800,
279
+ triggerThreshold: 0.8,
280
+ },
281
+ });
282
+
283
+ (agent as any).historyContent = [
284
+ { role: "user", parts: [{ type: "text", text: "m1" }] },
285
+ { role: "agent", parts: [{ type: "text", text: "r1" }] },
286
+ { role: "user", parts: [{ type: "text", text: "m2" }] },
287
+ { role: "agent", parts: [{ type: "text", text: "r2" }] },
288
+ { role: "user", parts: [{ type: "text", text: "m3" }] },
289
+ { role: "agent", parts: [{ type: "text", text: "r3" }] },
290
+ { role: "user", parts: [{ type: "text", text: "m4" }] },
291
+ { role: "agent", parts: [{ type: "text", text: "r4" }] },
292
+ { role: "user", parts: [{ type: "text", text: "m5" }] },
293
+ { role: "agent", parts: [{ type: "text", text: "r5" }] },
294
+ ];
295
+
296
+ const events: any[] = [];
297
+ for await (const event of agent.run("hello")) {
298
+ events.push(event);
299
+ }
300
+
301
+ expect(events.map((e) => e.type)).toContain(AgentEventType.MESSAGE);
302
+
303
+ const compactionMessage = events.find(
304
+ (e) =>
305
+ e.type === AgentEventType.COMPACTION &&
306
+ e.parts &&
307
+ e.parts[0].text &&
308
+ e.parts[0].text.includes("Context compacted"),
309
+ );
310
+ expect(compactionMessage).toBeTruthy();
311
+
312
+ // Initial 10 + 1 (user) = 11. Remove 2 = 9. Add compaction message = 10. Add response = 11.
313
+ expect((agent as any).historyContent.length).toBe(11);
314
+
315
+ // Check that the oldest messages were removed (m1 and r1)
316
+ // So the new first message should be m2
317
+ const firstContent = (agent as any).historyContent[0];
318
+ expect(firstContent.parts[0].text).toBe("m2");
319
+ });
320
+
321
+ it("should trigger context compaction with 'compact' strategy", async () => {
322
+ const mockModel = {
323
+ run: vi.fn().mockImplementation(async function* (request) {
324
+ if (
325
+ request.contents[0].parts[0].text.includes(
326
+ "Summarize the following conversation",
327
+ )
328
+ ) {
329
+ yield {
330
+ content: {
331
+ role: "agent",
332
+ parts: [{ type: "text", text: "Summarized history" }],
333
+ },
334
+ };
335
+ return;
336
+ }
337
+ yield {
338
+ content: {
339
+ role: "agent",
340
+ parts: [{ type: "text", text: "Response after compaction" }],
341
+ },
342
+ };
343
+ }),
344
+ countTokens: vi.fn().mockResolvedValue(1000),
345
+ modelName: "mock-model",
346
+ };
347
+
348
+ const agent = new CliAgent({
349
+ model: mockModel as any,
350
+ compactionConfig: {
351
+ enabled: true,
352
+ strategy: "summarize",
353
+ maxTokens: 800,
354
+ triggerThreshold: 0.8,
355
+ },
356
+ });
357
+
358
+ (agent as any).historyContent = [
359
+ { role: "user", parts: [{ type: "text", text: "m1" }] },
360
+ { role: "agent", parts: [{ type: "text", text: "r1" }] },
361
+ ];
362
+
363
+ const events: any[] = [];
364
+ for await (const event of agent.run("hello")) {
365
+ events.push(event);
366
+ }
367
+
368
+ expect(events.map((e) => e.type)).toContain(AgentEventType.MESSAGE);
369
+
370
+ const compactionMessage = events.find(
371
+ (e) =>
372
+ e.type === AgentEventType.COMPACTION &&
373
+ e.parts &&
374
+ e.parts[0].text &&
375
+ e.parts[0].text.includes("Context compacted using LLM summarization"),
376
+ );
377
+ expect(compactionMessage).toBeTruthy();
378
+
379
+ expect((agent as any).historyContent.length).toBe(3);
380
+
381
+ const firstContent = (agent as any).historyContent[0];
382
+ expect(firstContent.parts[0].text).toContain(
383
+ "Summary of previous conversation",
384
+ );
385
+ expect(firstContent.parts[0].text).toContain("Summarized history");
386
+ });
387
+ });
388
+
389
+ describe("CliAgent - Basic Flow", () => {
390
+ it("should yield message events when model responds with text", async () => {
391
+ const mockModel = {
392
+ run: vi.fn().mockImplementation(async function* () {
393
+ yield {
394
+ content: {
395
+ role: "agent",
396
+ parts: [{ type: "text", text: "Hello from agent" }],
397
+ },
398
+ };
399
+ }),
400
+ countTokens: vi.fn().mockResolvedValue(10),
401
+ };
402
+
403
+ const agent = new CliAgent({
404
+ model: mockModel as any,
405
+ });
406
+
407
+ const events: any[] = [];
408
+ for await (const event of agent.run("hello")) {
409
+ events.push(event);
410
+ }
411
+
412
+ expect(events.length).toBe(4); // START, MESSAGE (user), MESSAGE (agent), END
413
+ expect(events[0].type).toBe(AgentEventType.START);
414
+ expect(events[1].type).toBe(AgentEventType.MESSAGE); // User message
415
+ expect(events[2].type).toBe(AgentEventType.MESSAGE); // Agent response
416
+ expect(events[2].parts[0].text).toBe("Hello from agent");
417
+ expect(events[3].type).toBe(AgentEventType.END);
418
+ });
419
+ });
420
+
421
+ describe("CliAgent - Plan Execution", () => {
422
+ it("should execute plan when resumed with approved plan", async () => {
423
+ const mockModel = {
424
+ run: vi.fn().mockImplementation(async function* () {
425
+ yield {
426
+ content: {
427
+ role: "agent",
428
+ parts: [{ type: "text", text: "Model response executing plan" }],
429
+ },
430
+ };
431
+ }),
432
+ };
433
+
434
+ const agent = new CliAgent({
435
+ model: mockModel as any,
436
+ });
437
+
438
+ const streamId = "stream_123";
439
+ (agent as any).history = [
440
+ {
441
+ type: AgentEventType.USER_INPUT_REQUEST,
442
+ streamId,
443
+ id: "1",
444
+ requestId: "req_123",
445
+ message: "Do you approve this plan?",
446
+ requestSchema: {
447
+ type: "plan_approval",
448
+ planFilePath: "/tmp/plan_123.md",
449
+ },
450
+ } as any,
451
+ ];
452
+ (agent as any).streamId = streamId;
453
+
454
+ const events: any[] = [];
455
+ for await (const event of agent.run({
456
+ type: AgentEventType.USER_INPUT_RESPONSE,
457
+ id: "resp_123",
458
+ streamId,
459
+ timestamp: new Date().toISOString(),
460
+ role: "user",
461
+ requestId: "req_123",
462
+ action: "accept",
463
+ })) {
464
+ events.push(event);
465
+ }
466
+
467
+ expect(events.map((e) => e.type)).toContain(AgentEventType.MESSAGE);
468
+
469
+ const planMessage = events.find(
470
+ (e) => e.type === AgentEventType.MESSAGE && e.role === "user",
471
+ );
472
+ expect(planMessage).toBeTruthy();
473
+ expect(planMessage.parts[0].text).toContain("Plan approved");
474
+ expect(planMessage.parts[0].text).toContain("Mocked plan content");
475
+ });
476
+ });
@@ -0,0 +1,56 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ isTextContentPart,
4
+ isThoughtContentPart,
5
+ isMediaContentPart,
6
+ isFunctionCallContentPart,
7
+ isFunctionResponseContentPart,
8
+ ContentPart,
9
+ } from "../../src/content";
10
+
11
+ describe("content type guards", () => {
12
+ it("should identify text content part", () => {
13
+ const part: ContentPart = { type: "text", text: "hello" };
14
+ expect(isTextContentPart(part)).toBe(true);
15
+ expect(isThoughtContentPart(part)).toBe(false);
16
+ expect(isMediaContentPart(part)).toBe(false);
17
+ expect(isFunctionCallContentPart(part)).toBe(false);
18
+ expect(isFunctionResponseContentPart(part)).toBe(false);
19
+ });
20
+
21
+ it("should identify thought content part", () => {
22
+ const part: ContentPart = { type: "thought", thought: "thinking" };
23
+ expect(isThoughtContentPart(part)).toBe(true);
24
+ expect(isTextContentPart(part)).toBe(false);
25
+ expect(isMediaContentPart(part)).toBe(false);
26
+ expect(isFunctionCallContentPart(part)).toBe(false);
27
+ expect(isFunctionResponseContentPart(part)).toBe(false);
28
+ });
29
+
30
+ it("should identify media content part", () => {
31
+ const part: ContentPart = { type: "media", uri: "http://example.com" };
32
+ expect(isMediaContentPart(part)).toBe(true);
33
+ expect(isTextContentPart(part)).toBe(false);
34
+ expect(isThoughtContentPart(part)).toBe(false);
35
+ expect(isFunctionCallContentPart(part)).toBe(false);
36
+ expect(isFunctionResponseContentPart(part)).toBe(false);
37
+ });
38
+
39
+ it("should identify function call content part", () => {
40
+ const part: ContentPart = { type: "function_call", name: "greet" };
41
+ expect(isFunctionCallContentPart(part)).toBe(true);
42
+ expect(isTextContentPart(part)).toBe(false);
43
+ expect(isThoughtContentPart(part)).toBe(false);
44
+ expect(isMediaContentPart(part)).toBe(false);
45
+ expect(isFunctionResponseContentPart(part)).toBe(false);
46
+ });
47
+
48
+ it("should identify function response content part", () => {
49
+ const part: ContentPart = { type: "function_response", name: "greet" };
50
+ expect(isFunctionResponseContentPart(part)).toBe(true);
51
+ expect(isTextContentPart(part)).toBe(false);
52
+ expect(isThoughtContentPart(part)).toBe(false);
53
+ expect(isMediaContentPart(part)).toBe(false);
54
+ expect(isFunctionCallContentPart(part)).toBe(false);
55
+ });
56
+ });
@@ -0,0 +1,109 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import * as fs from "fs";
3
+ import { Logger, ConsoleTransport } from "../../src/logger";
4
+
5
+ vi.mock("fs", async () => {
6
+ const actual = await vi.importActual<typeof import("fs")>("fs");
7
+ return {
8
+ ...actual,
9
+ appendFileSync: vi.fn(),
10
+ };
11
+ });
12
+
13
+ describe("Logger", () => {
14
+ let consoleLogSpy: any;
15
+ let consoleInfoSpy: any;
16
+ let consoleWarnSpy: any;
17
+ let consoleErrorSpy: any;
18
+
19
+ beforeEach(() => {
20
+ consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
21
+ consoleInfoSpy = vi.spyOn(console, "info").mockImplementation(() => {});
22
+ consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
23
+ consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
24
+ vi.mocked(fs.appendFileSync).mockClear();
25
+ });
26
+
27
+ afterEach(() => {
28
+ vi.restoreAllMocks();
29
+ });
30
+
31
+ it("should log to console.log", () => {
32
+ const logger = new Logger([new ConsoleTransport(true)]);
33
+ logger.log("test log");
34
+ expect(consoleLogSpy).toHaveBeenCalledWith("test log");
35
+ });
36
+
37
+ it("should log to console.info", () => {
38
+ const logger = new Logger([new ConsoleTransport(true)]);
39
+ logger.info("test info");
40
+ expect(consoleInfoSpy).toHaveBeenCalledWith("test info");
41
+ });
42
+
43
+ it("should log to console.warn", () => {
44
+ const logger = new Logger([new ConsoleTransport(true)]);
45
+ logger.warn("test warn");
46
+ expect(consoleWarnSpy).toHaveBeenCalledWith("test warn");
47
+ });
48
+
49
+ it("should log to console.error", () => {
50
+ const logger = new Logger([new ConsoleTransport(true)]);
51
+ logger.error("test error");
52
+ expect(consoleErrorSpy).toHaveBeenCalledWith("test error");
53
+ });
54
+
55
+ it("should write to file if DEBUG_LOGGER is set", () => {
56
+ process.env.DEBUG_LOGGER = "true";
57
+ const logger = new Logger();
58
+ logger.log("test file log");
59
+ expect(fs.appendFileSync).toHaveBeenCalled();
60
+ const call = vi.mocked(fs.appendFileSync).mock.calls[0];
61
+ expect(call[0]).toContain("debug.log");
62
+ expect(call[1]).toContain("test file log");
63
+ delete process.env.DEBUG_LOGGER;
64
+ });
65
+
66
+ it("should NOT write to file if DEBUG_LOGGER is not set", () => {
67
+ delete process.env.DEBUG_LOGGER;
68
+ const logger = new Logger();
69
+ logger.log("test no file log");
70
+ expect(fs.appendFileSync).not.toHaveBeenCalled();
71
+ });
72
+
73
+ it("should format message with timestamp and level", () => {
74
+ process.env.DEBUG_LOGGER = "true";
75
+ const logger = new Logger();
76
+ logger.log("test format");
77
+ expect(fs.appendFileSync).toHaveBeenCalled();
78
+ const call = vi.mocked(fs.appendFileSync).mock.calls[0];
79
+ expect(call[1]).toMatch(
80
+ /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[LOG\] test format/,
81
+ );
82
+ delete process.env.DEBUG_LOGGER;
83
+ });
84
+
85
+ it("should use custom transport", () => {
86
+ const mockTransport = {
87
+ enabled: true,
88
+ log: vi.fn(),
89
+ };
90
+ const logger = new Logger([mockTransport]);
91
+ logger.log("test custom log");
92
+ expect(mockTransport.log).toHaveBeenCalledWith(
93
+ "LOG",
94
+ expect.stringMatching(
95
+ /\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z\] \[LOG\] test custom log/,
96
+ ),
97
+ ["test custom log"],
98
+ );
99
+ });
100
+
101
+ it("should use multiple custom transports", () => {
102
+ const mockTransport1 = { enabled: true, log: vi.fn() };
103
+ const mockTransport2 = { enabled: true, log: vi.fn() };
104
+ const logger = new Logger([mockTransport1, mockTransport2]);
105
+ logger.log("test multiple");
106
+ expect(mockTransport1.log).toHaveBeenCalled();
107
+ expect(mockTransport2.log).toHaveBeenCalled();
108
+ });
109
+ });