@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,167 @@
1
+ import type { LlmModel } from "./model.js";
2
+ import { type AgentEvent, isMessageEvent } from "../agent/agent_event.js";
3
+ import type { Content, TextContentPart } from "../content.js";
4
+ import { logger } from "../logger.js";
5
+
6
+ export class UtilLlm {
7
+ private model: LlmModel;
8
+
9
+ constructor(model: LlmModel) {
10
+ this.model = model;
11
+ }
12
+
13
+ async generateSessionTitle(events: AgentEvent[]): Promise<string> {
14
+ const conversationText = events
15
+ .filter(isMessageEvent)
16
+ .map((event) => {
17
+ const role = event.role === "user" ? "User" : "Agent";
18
+ const text = event.parts
19
+ .filter((part): part is TextContentPart => part.type === "text")
20
+ .map((part) => part.text)
21
+ .join(" ");
22
+ return `[${role}]: ${text}`;
23
+ })
24
+ .join("\n");
25
+
26
+ if (!conversationText) {
27
+ return "New Session";
28
+ }
29
+
30
+ const prompt = `Based on the following conversation, generate a short, user-friendly title (3-5 words). Do not use quotes or special characters in the title.\n\nConversation:\n${conversationText}\n\nTitle:`;
31
+
32
+ const request = {
33
+ contents: [
34
+ {
35
+ role: "user" as const,
36
+ parts: [{ type: "text" as const, text: prompt }],
37
+ },
38
+ ],
39
+ systemInstructions:
40
+ "You are a helpful assistant that generates concise titles for conversations.",
41
+ };
42
+
43
+ try {
44
+ const generator = this.model.run(request);
45
+ let title = "";
46
+ for await (const response of generator) {
47
+ if (response.errorMessage) {
48
+ throw new Error(response.errorMessage);
49
+ }
50
+ if (response.content && response.content.parts) {
51
+ for (const part of response.content.parts) {
52
+ if (part.type === "text") {
53
+ title += part.text;
54
+ }
55
+ }
56
+ }
57
+ }
58
+ return title.trim() || "New Session";
59
+ } catch (e) {
60
+ console.error("Failed to generate session title:", e);
61
+ return "New Session";
62
+ }
63
+ }
64
+
65
+ async compactHistory(contents: Content[]): Promise<string> {
66
+ const conversationText = contents
67
+ .map((content) => {
68
+ const role = content.role === "user" ? "User" : "Agent";
69
+ const text = content.parts
70
+ .filter((part): part is TextContentPart => part.type === "text")
71
+ .map((part) => part.text)
72
+ .join(" ");
73
+ return `[${role}]: ${text}`;
74
+ })
75
+ .join("\n");
76
+
77
+ const prompt = `Summarize the following conversation history strictly retaining facts, code snippets, and user preferences. Be concise.\n\nConversation:\n${conversationText}\n\nSummary:`;
78
+
79
+ const request = {
80
+ contents: [
81
+ {
82
+ role: "user" as const,
83
+ parts: [{ type: "text" as const, text: prompt }],
84
+ },
85
+ ],
86
+ systemInstructions:
87
+ "You are a helpful assistant that summarizes conversation history.",
88
+ };
89
+
90
+ try {
91
+ const generator = this.model.run(request);
92
+ let summary = "";
93
+ for await (const response of generator) {
94
+ if (response.errorMessage) {
95
+ throw new Error(response.errorMessage);
96
+ }
97
+ if (response.content && response.content.parts) {
98
+ for (const part of response.content.parts) {
99
+ if (part.type === "text") {
100
+ summary += part.text;
101
+ }
102
+ }
103
+ }
104
+ }
105
+ return summary.trim();
106
+ } catch (e) {
107
+ logger.error("Failed to compact history:", e);
108
+ throw e;
109
+ }
110
+ }
111
+
112
+ async scanProjectConstants(context: string): Promise<Record<string, any>> {
113
+ const prompt = `Analyze the following project information and define project constants.
114
+ Identify:
115
+ - language, framework, package managers, build tools
116
+ - project structure
117
+ - coding style, rules and conventions
118
+
119
+ Output the result as a JSON object. Do not include markdown formatting in the response, just the raw JSON.
120
+
121
+ Project Info:
122
+ ${context}
123
+ `;
124
+
125
+ const request = {
126
+ contents: [
127
+ {
128
+ role: "user" as const,
129
+ parts: [{ type: "text" as const, text: prompt }],
130
+ },
131
+ ],
132
+ systemInstructions:
133
+ "You are a helpful assistant that analyzes codebases and extracts project conventions and constants in JSON format.",
134
+ };
135
+
136
+ try {
137
+ const generator = this.model.run(request);
138
+ let resultStr = "";
139
+ for await (const response of generator) {
140
+ if (response.errorMessage) {
141
+ throw new Error(response.errorMessage);
142
+ }
143
+ if (response.content && response.content.parts) {
144
+ for (const part of response.content.parts) {
145
+ if (part.type === "text") {
146
+ resultStr += part.text;
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ let cleanedStr = resultStr.trim();
153
+ if (cleanedStr.startsWith("\`\`\`json")) {
154
+ cleanedStr = cleanedStr.substring(7);
155
+ }
156
+ if (cleanedStr.endsWith("\`\`\`")) {
157
+ cleanedStr = cleanedStr.substring(0, cleanedStr.length - 3);
158
+ }
159
+ cleanedStr = cleanedStr.trim();
160
+
161
+ return JSON.parse(cleanedStr);
162
+ } catch (e) {
163
+ logger.error("Failed to scan project constants:", e);
164
+ throw e;
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,16 @@
1
+ import { type AgentEvent } from "../agent/agent_event.js";
2
+
3
+ export interface SessionMetadata {
4
+ id: string;
5
+ title?: string;
6
+ agentName: string;
7
+ timestamp: string;
8
+ }
9
+
10
+ export interface Session extends SessionMetadata {
11
+ id: string;
12
+ title?: string;
13
+ events: AgentEvent[];
14
+ agentName: string;
15
+ timestamp: string;
16
+ }
@@ -0,0 +1,207 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { Session, SessionMetadata } from "./session.js";
3
+ import { type AgentEvent } from "../agent/agent_event.js";
4
+ import { APP_FILE_DIR } from "../config/app_dir.js";
5
+ import * as fs from "node:fs/promises";
6
+ import * as path from "node:path";
7
+
8
+ export class SessionFileService {
9
+ private rootDir: string;
10
+ private initialized: boolean = false;
11
+ private locks: Map<string, Promise<void>> = new Map();
12
+
13
+ constructor() {
14
+ this.rootDir = path.join(APP_FILE_DIR, "sessions");
15
+ }
16
+
17
+ private async init() {
18
+ if (this.initialized) {
19
+ return;
20
+ }
21
+
22
+ await fs.mkdir(this.rootDir, { recursive: true });
23
+ }
24
+
25
+ private async lock(sessionId: string): Promise<() => void> {
26
+ let unlock: () => void = () => {};
27
+ const previous = this.locks.get(sessionId) || Promise.resolve();
28
+
29
+ const current = new Promise<void>((r) => {
30
+ unlock = r;
31
+ });
32
+
33
+ this.locks.set(
34
+ sessionId,
35
+ previous.then(() => current),
36
+ );
37
+
38
+ await previous;
39
+
40
+ return unlock;
41
+ }
42
+
43
+ async getSession(sessionId: string): Promise<Session> {
44
+ const sessionFilePath = getSessionFileName(this.rootDir, sessionId);
45
+ const metaFilePath = getSessionMetadataFileName(this.rootDir, sessionId);
46
+ const session = await loadFileData<Session>(sessionFilePath);
47
+ const sessionMeta = await loadFileData<SessionMetadata>(metaFilePath);
48
+
49
+ if (!session || !sessionMeta) {
50
+ throw new Error(`Session ${sessionId} not found`);
51
+ }
52
+
53
+ return {
54
+ ...session,
55
+ ...sessionMeta,
56
+ } as Session;
57
+ }
58
+
59
+ async getSessionMetadata(
60
+ sessionId: string,
61
+ ): Promise<SessionMetadata | undefined> {
62
+ const metaFilePath = getSessionMetadataFileName(this.rootDir, sessionId);
63
+ const sessionMeta = await loadFileData<SessionMetadata>(metaFilePath);
64
+
65
+ return sessionMeta;
66
+ }
67
+
68
+ async createSession(
69
+ agentName: string,
70
+ events: AgentEvent[],
71
+ ): Promise<Session> {
72
+ await this.init();
73
+
74
+ const session: Session = {
75
+ id: randomUUID(),
76
+ agentName: agentName,
77
+ events: events,
78
+ timestamp: new Date().toISOString(),
79
+ };
80
+
81
+ const sessionDir = path.join(this.rootDir, session.id);
82
+ await fs.mkdir(sessionDir, { recursive: true });
83
+
84
+ await saveToFile(getSessionMetadataFileName(this.rootDir, session.id), {
85
+ title: session.title,
86
+ agentName: session.agentName,
87
+ timestamp: session.timestamp,
88
+ });
89
+ await saveToFile(getSessionFileName(this.rootDir, session.id), session);
90
+
91
+ return session;
92
+ }
93
+
94
+ async updateSession(
95
+ sessionId: string,
96
+ { title }: { title: string },
97
+ ): Promise<void> {
98
+ await this.init();
99
+ const unlock = await this.lock(sessionId);
100
+ try {
101
+ const metaFilePath = getSessionMetadataFileName(this.rootDir, sessionId);
102
+ const sessionMeta = (await loadFileData(metaFilePath)) as Session;
103
+
104
+ if (!sessionMeta) {
105
+ return;
106
+ }
107
+
108
+ sessionMeta.title = title;
109
+ await saveToFile(metaFilePath, sessionMeta);
110
+ } finally {
111
+ unlock();
112
+ }
113
+ }
114
+
115
+ async appendEvent(sessionId: string, agentEvent: AgentEvent): Promise<void> {
116
+ await this.init();
117
+ const unlock = await this.lock(sessionId);
118
+ try {
119
+ const sessionFilePath = getSessionFileName(this.rootDir, sessionId);
120
+ const session = (await loadFileData(sessionFilePath)) as Session;
121
+
122
+ if (!session) {
123
+ return;
124
+ }
125
+
126
+ session.events.push(agentEvent);
127
+
128
+ await saveToFile(sessionFilePath, session);
129
+ } finally {
130
+ unlock();
131
+ }
132
+ }
133
+
134
+ async listSessions(): Promise<Array<SessionMetadata>> {
135
+ await this.init();
136
+
137
+ const folders = await listFiles(this.rootDir);
138
+
139
+ const result = await Promise.all(
140
+ folders.map(async (f) => {
141
+ try {
142
+ return await loadFileData<SessionMetadata>(
143
+ path.join(this.rootDir, f, "metadata.json"),
144
+ );
145
+ } catch (e) {
146
+ console.warn(
147
+ `Failed to load metadata for session in folder ${f}:`,
148
+ e,
149
+ );
150
+ return undefined;
151
+ }
152
+ }),
153
+ );
154
+
155
+ return result.filter((f): f is SessionMetadata => !!f);
156
+ }
157
+ }
158
+
159
+ export async function listFiles(folderPath: string): Promise<string[]> {
160
+ try {
161
+ return await fs.readdir(folderPath);
162
+ } catch (e) {
163
+ console.error(`Failed to list files in folder ${folderPath}`, e);
164
+
165
+ return [];
166
+ }
167
+ }
168
+
169
+ export function getSessionFileName(rootDir: string, sessionId: string): string {
170
+ return path.join(rootDir, sessionId, "session.json");
171
+ }
172
+
173
+ export function getSessionMetadataFileName(
174
+ rootDir: string,
175
+ sessionId: string,
176
+ ): string {
177
+ return path.join(rootDir, sessionId, "metadata.json");
178
+ }
179
+
180
+ export async function saveToFile<T>(filePath: string, data: T): Promise<void> {
181
+ try {
182
+ await fs.writeFile(
183
+ filePath,
184
+ typeof data === "string" ? data : JSON.stringify(data, null, 2),
185
+ { encoding: "utf-8" },
186
+ );
187
+ } catch (e) {
188
+ console.error(`Failed to write file ${filePath}:`, e);
189
+
190
+ throw e;
191
+ }
192
+ }
193
+
194
+ export async function loadFileData<T>(
195
+ filePath: string,
196
+ ): Promise<T | undefined> {
197
+ try {
198
+ return JSON.parse(await fs.readFile(filePath, { encoding: "utf-8" })) as T;
199
+ } catch (e: any) {
200
+ if (e.code === "ENOENT") {
201
+ return undefined;
202
+ }
203
+ console.error(`Failed to read or parse file ${filePath}:`, e);
204
+
205
+ throw e;
206
+ }
207
+ }
@@ -0,0 +1,38 @@
1
+ import type { FunctionDeclaration } from "../tools/tool.js";
2
+ import { Type } from "../tools/schema.js";
3
+
4
+ export interface SkillFrontmatter {
5
+ name: string;
6
+ description: string;
7
+ license?: string;
8
+ compatibility?: string;
9
+ allowedTools?: string;
10
+ metadata?: Record<string, unknown>;
11
+ }
12
+
13
+ export interface Script {
14
+ src: string;
15
+ }
16
+
17
+ export interface Resources {
18
+ references?: Record<string, string | Buffer>;
19
+ assets?: Record<string, string | Buffer>;
20
+ scripts?: Record<string, Script>;
21
+ }
22
+
23
+ export interface Skill {
24
+ frontmatter: SkillFrontmatter;
25
+ instructions: string;
26
+ resources?: Resources;
27
+ }
28
+
29
+ export function toFunctionDeclaration(skill: Skill): FunctionDeclaration {
30
+ return {
31
+ name: `${skill.frontmatter.name}_skill`,
32
+ description: skill.frontmatter.description,
33
+ parameters: {
34
+ type: Type.OBJECT,
35
+ properties: {},
36
+ },
37
+ };
38
+ }
@@ -0,0 +1,80 @@
1
+ import { FunctionalTool } from "../functional_tool.js";
2
+ import { type Schema, Type } from "../schema.js";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+
6
+ export const FIND_TOOL = new FunctionalTool({
7
+ name: "find",
8
+ description: "Finds files matching a pattern.",
9
+ params: {
10
+ type: Type.OBJECT,
11
+ properties: {
12
+ pattern: {
13
+ type: Type.STRING,
14
+ description:
15
+ "The regular expression pattern to match against file names.",
16
+ },
17
+ path: {
18
+ type: Type.STRING,
19
+ description: "The path to search in. Defaults to '.' if not specified.",
20
+ },
21
+ },
22
+ required: ["pattern"],
23
+ } as Schema,
24
+ output: {
25
+ type: Type.OBJECT,
26
+ properties: {
27
+ files: {
28
+ type: Type.ARRAY,
29
+ items: {
30
+ type: Type.STRING,
31
+ },
32
+ description: "List of matching file paths",
33
+ },
34
+ },
35
+ } as Schema,
36
+ execute: async (input: unknown) => {
37
+ const typedInput = input as { pattern: string; path?: string };
38
+ const patternStr = typedInput.pattern;
39
+ const searchPath = typedInput.path || ".";
40
+ const resolvedPath = path.resolve(searchPath);
41
+ const cwd = process.cwd();
42
+
43
+ if (!resolvedPath.startsWith(cwd)) {
44
+ throw new Error(
45
+ `Access denied: Path ${resolvedPath} is outside the project directory.`,
46
+ );
47
+ }
48
+
49
+ const regex = new RegExp(patternStr);
50
+ const files: string[] = [];
51
+
52
+ async function searchDir(dir: string) {
53
+ const entries = await fs.readdir(dir, { withFileTypes: true });
54
+ for (const entry of entries) {
55
+ const fullPath = path.join(dir, entry.name);
56
+
57
+ // Match against the file/directory name
58
+ if (regex.test(entry.name)) {
59
+ files.push(path.relative(cwd, fullPath));
60
+ }
61
+
62
+ if (entry.isDirectory()) {
63
+ if (entry.name === ".git" || entry.name === "node_modules") {
64
+ continue;
65
+ }
66
+ await searchDir(fullPath);
67
+ }
68
+ }
69
+ }
70
+
71
+ const stats = await fs.stat(resolvedPath);
72
+ if (stats.isDirectory()) {
73
+ await searchDir(resolvedPath);
74
+ } else {
75
+ throw new Error(`Path ${resolvedPath} is not a directory.`);
76
+ }
77
+
78
+ return { files };
79
+ },
80
+ });
@@ -0,0 +1,101 @@
1
+ import { FunctionalTool } from "../functional_tool.js";
2
+ import { type Schema, Type } from "../schema.js";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+
6
+ export const GREP_TOOL = new FunctionalTool({
7
+ name: "grep",
8
+ description: "Searches for a regular expression pattern in files.",
9
+ params: {
10
+ type: Type.OBJECT,
11
+ properties: {
12
+ pattern: {
13
+ type: Type.STRING,
14
+ description: "The regular expression pattern to search for.",
15
+ },
16
+ path: {
17
+ type: Type.STRING,
18
+ description: "The path to search in. Defaults to '.' if not specified.",
19
+ },
20
+ },
21
+ required: ["pattern"],
22
+ } as Schema,
23
+ output: {
24
+ type: Type.OBJECT,
25
+ properties: {
26
+ matches: {
27
+ type: Type.ARRAY,
28
+ items: {
29
+ type: Type.OBJECT,
30
+ properties: {
31
+ file: { type: Type.STRING },
32
+ line: { type: Type.INTEGER },
33
+ content: { type: Type.STRING },
34
+ },
35
+ },
36
+ description: "List of matches found",
37
+ },
38
+ },
39
+ } as Schema,
40
+ execute: async (input: unknown) => {
41
+ const typedInput = input as { pattern: string; path?: string };
42
+ const patternStr = typedInput.pattern;
43
+ const searchPath = typedInput.path || ".";
44
+ const resolvedPath = path.resolve(searchPath);
45
+ const cwd = process.cwd();
46
+
47
+ if (!resolvedPath.startsWith(cwd)) {
48
+ throw new Error(
49
+ `Access denied: Path ${resolvedPath} is outside the project directory.`,
50
+ );
51
+ }
52
+
53
+ const regex = new RegExp(patternStr);
54
+ const matches: Array<{ file: string; line: number; content: string }> = [];
55
+
56
+ async function searchDir(dir: string) {
57
+ const entries = await fs.readdir(dir, { withFileTypes: true });
58
+ for (const entry of entries) {
59
+ const fullPath = path.join(dir, entry.name);
60
+ if (entry.isDirectory()) {
61
+ if (entry.name === ".git" || entry.name === "node_modules") {
62
+ continue; // Skip these by default for sanity
63
+ }
64
+ await searchDir(fullPath);
65
+ } else if (entry.isFile()) {
66
+ await searchFile(fullPath);
67
+ }
68
+ }
69
+ }
70
+
71
+ async function searchFile(filePath: string) {
72
+ try {
73
+ const content = await fs.readFile(filePath, "utf-8");
74
+ const lines = content.split("\n");
75
+ for (let i = 0; i < lines.length; i++) {
76
+ if (regex.test(lines[i])) {
77
+ matches.push({
78
+ file: path.relative(cwd, filePath),
79
+ line: i + 1,
80
+ content: lines[i],
81
+ });
82
+ }
83
+ }
84
+ } catch (error) {
85
+ // Ignore files that cannot be read as text or other errors
86
+ }
87
+ }
88
+
89
+ // Check if resolvedPath is a file or directory
90
+ const stats = await fs.stat(resolvedPath);
91
+ if (stats.isFile()) {
92
+ await searchFile(resolvedPath);
93
+ } else if (stats.isDirectory()) {
94
+ await searchDir(resolvedPath);
95
+ } else {
96
+ throw new Error(`Path ${resolvedPath} is neither a file nor a directory.`);
97
+ }
98
+
99
+ return { matches };
100
+ },
101
+ });
@@ -0,0 +1,13 @@
1
+ import { LIST_DIR_TOOL } from "./list_dir.js";
2
+ import { VIEW_FILE_TOOL } from "./view_file.js";
3
+ import { WRITE_FILE_TOOL } from "./write_file.js";
4
+ import { GREP_TOOL } from "./grep.js";
5
+ import { FIND_TOOL } from "./find.js";
6
+
7
+ export const BUILD_IN_TOOLS = [
8
+ LIST_DIR_TOOL,
9
+ VIEW_FILE_TOOL,
10
+ WRITE_FILE_TOOL,
11
+ GREP_TOOL,
12
+ FIND_TOOL,
13
+ ];
@@ -0,0 +1,50 @@
1
+ import { FunctionalTool } from "../functional_tool.js";
2
+ import { type Schema, Type } from "../schema.js";
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+
6
+ export const LIST_DIR_TOOL = new FunctionalTool({
7
+ name: "list_dir",
8
+ description: "Lists the contents of a directory.",
9
+ params: {
10
+ type: Type.OBJECT,
11
+ properties: {
12
+ path: {
13
+ type: Type.STRING,
14
+ description:
15
+ "The directory path to list. Defaults to '.' if not specified.",
16
+ },
17
+ },
18
+ } as Schema,
19
+ output: {
20
+ type: Type.OBJECT,
21
+ properties: {
22
+ files: {
23
+ type: Type.ARRAY,
24
+ items: {
25
+ type: Type.STRING,
26
+ },
27
+ description: "List of file and directory names",
28
+ },
29
+ },
30
+ } as Schema,
31
+ execute: async (input: unknown) => {
32
+ const typedInput = input as { path?: string };
33
+ const dirPath = typedInput.path || ".";
34
+ const resolvedPath = path.resolve(dirPath);
35
+ const cwd = process.cwd();
36
+
37
+ if (!resolvedPath.startsWith(cwd)) {
38
+ throw new Error(
39
+ `Access denied: Path ${resolvedPath} is outside the project directory.`,
40
+ );
41
+ }
42
+
43
+ try {
44
+ const files = await fs.readdir(resolvedPath);
45
+ return { files };
46
+ } catch (error: any) {
47
+ throw new Error(`Failed to list directory: ${error.message}`);
48
+ }
49
+ },
50
+ });