@kirosnn/mosaic 0.0.7

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 (154) hide show
  1. package/.mosaic/mosaic.local.jsonc +0 -0
  2. package/MOSAIC.md +188 -0
  3. package/README.md +127 -0
  4. package/docs/mosaic.png +0 -0
  5. package/package.json +42 -0
  6. package/src/agent/Agent.ts +131 -0
  7. package/src/agent/context.ts +96 -0
  8. package/src/agent/index.ts +2 -0
  9. package/src/agent/prompts/systemPrompt.ts +138 -0
  10. package/src/agent/prompts/toolsPrompt.ts +139 -0
  11. package/src/agent/provider/anthropic.ts +122 -0
  12. package/src/agent/provider/google.ts +124 -0
  13. package/src/agent/provider/mistral.ts +117 -0
  14. package/src/agent/provider/ollama.ts +531 -0
  15. package/src/agent/provider/openai.ts +220 -0
  16. package/src/agent/provider/xai.ts +122 -0
  17. package/src/agent/tools/bash.ts +20 -0
  18. package/src/agent/tools/definitions.ts +27 -0
  19. package/src/agent/tools/edit.ts +23 -0
  20. package/src/agent/tools/executor.ts +751 -0
  21. package/src/agent/tools/explore.ts +18 -0
  22. package/src/agent/tools/exploreExecutor.ts +320 -0
  23. package/src/agent/tools/glob.ts +16 -0
  24. package/src/agent/tools/grep.ts +19 -0
  25. package/src/agent/tools/index.ts +4 -0
  26. package/src/agent/tools/list.ts +20 -0
  27. package/src/agent/tools/question.ts +20 -0
  28. package/src/agent/tools/read.ts +15 -0
  29. package/src/agent/tools/write.ts +21 -0
  30. package/src/agent/types.ts +155 -0
  31. package/src/components/App.tsx +174 -0
  32. package/src/components/CommandsModal.tsx +77 -0
  33. package/src/components/CustomInput.tsx +328 -0
  34. package/src/components/Main.tsx +1112 -0
  35. package/src/components/Notification.tsx +91 -0
  36. package/src/components/SelectList.tsx +47 -0
  37. package/src/components/Setup.tsx +528 -0
  38. package/src/components/ShortcutsModal.tsx +67 -0
  39. package/src/components/Welcome.tsx +39 -0
  40. package/src/components/main/ApprovalPanel.tsx +134 -0
  41. package/src/components/main/ChatPage.tsx +516 -0
  42. package/src/components/main/HomePage.tsx +111 -0
  43. package/src/components/main/QuestionPanel.tsx +85 -0
  44. package/src/components/main/ThinkingIndicator.tsx +101 -0
  45. package/src/components/main/types.ts +55 -0
  46. package/src/components/main/wrapText.ts +41 -0
  47. package/src/index.tsx +212 -0
  48. package/src/utils/approvalBridge.ts +129 -0
  49. package/src/utils/commands/echo.ts +22 -0
  50. package/src/utils/commands/help.ts +25 -0
  51. package/src/utils/commands/index.ts +68 -0
  52. package/src/utils/commands/init.ts +68 -0
  53. package/src/utils/commands/redo.ts +74 -0
  54. package/src/utils/commands/registry.ts +29 -0
  55. package/src/utils/commands/sessions.ts +129 -0
  56. package/src/utils/commands/types.ts +20 -0
  57. package/src/utils/commands/undo.ts +75 -0
  58. package/src/utils/commands/web.ts +77 -0
  59. package/src/utils/config.ts +357 -0
  60. package/src/utils/diff.ts +201 -0
  61. package/src/utils/diffRendering.tsx +62 -0
  62. package/src/utils/exploreBridge.ts +87 -0
  63. package/src/utils/fileChangeTracker.ts +98 -0
  64. package/src/utils/fileChangesBridge.ts +18 -0
  65. package/src/utils/history.ts +106 -0
  66. package/src/utils/markdown.tsx +232 -0
  67. package/src/utils/models.ts +304 -0
  68. package/src/utils/questionBridge.ts +122 -0
  69. package/src/utils/terminalUtils.ts +25 -0
  70. package/src/utils/toolFormatting.ts +384 -0
  71. package/src/utils/undoRedo.ts +429 -0
  72. package/src/utils/undoRedoBridge.ts +45 -0
  73. package/src/utils/undoRedoDb.ts +338 -0
  74. package/src/utils/uninstall.ts +45 -0
  75. package/src/utils/version.ts +3 -0
  76. package/src/web/app.tsx +606 -0
  77. package/src/web/assets/css/ChatPage.css +212 -0
  78. package/src/web/assets/css/FileExplorer.css +202 -0
  79. package/src/web/assets/css/HomePage.css +119 -0
  80. package/src/web/assets/css/Markdown.css +178 -0
  81. package/src/web/assets/css/MessageItem.css +160 -0
  82. package/src/web/assets/css/Sidebar.css +208 -0
  83. package/src/web/assets/css/SidebarModal.css +137 -0
  84. package/src/web/assets/css/ThinkingIndicator.css +47 -0
  85. package/src/web/assets/css/ToolMessage.css +148 -0
  86. package/src/web/assets/css/global.css +226 -0
  87. package/src/web/assets/fonts/Geist-Black.woff2 +0 -0
  88. package/src/web/assets/fonts/Geist-BlackItalic.woff2 +0 -0
  89. package/src/web/assets/fonts/Geist-Bold.woff2 +0 -0
  90. package/src/web/assets/fonts/Geist-BoldItalic.woff2 +0 -0
  91. package/src/web/assets/fonts/Geist-ExtraBold.woff2 +0 -0
  92. package/src/web/assets/fonts/Geist-ExtraBoldItalic.woff2 +0 -0
  93. package/src/web/assets/fonts/Geist-ExtraLight.woff2 +0 -0
  94. package/src/web/assets/fonts/Geist-ExtraLightItalic.woff2 +0 -0
  95. package/src/web/assets/fonts/Geist-Italic[wght].woff2 +0 -0
  96. package/src/web/assets/fonts/Geist-Light.woff2 +0 -0
  97. package/src/web/assets/fonts/Geist-LightItalic.woff2 +0 -0
  98. package/src/web/assets/fonts/Geist-Medium.woff2 +0 -0
  99. package/src/web/assets/fonts/Geist-MediumItalic.woff2 +0 -0
  100. package/src/web/assets/fonts/Geist-Regular.woff2 +0 -0
  101. package/src/web/assets/fonts/Geist-RegularItalic.woff2 +0 -0
  102. package/src/web/assets/fonts/Geist-SemiBold.woff2 +0 -0
  103. package/src/web/assets/fonts/Geist-SemiBoldItalic.woff2 +0 -0
  104. package/src/web/assets/fonts/Geist-Thin.woff2 +0 -0
  105. package/src/web/assets/fonts/Geist-ThinItalic.woff2 +0 -0
  106. package/src/web/assets/fonts/GeistMono-Black.woff2 +0 -0
  107. package/src/web/assets/fonts/GeistMono-BlackItalic.woff2 +0 -0
  108. package/src/web/assets/fonts/GeistMono-Bold.woff2 +0 -0
  109. package/src/web/assets/fonts/GeistMono-BoldItalic.woff2 +0 -0
  110. package/src/web/assets/fonts/GeistMono-ExtraBold.woff2 +0 -0
  111. package/src/web/assets/fonts/GeistMono-ExtraBoldItalic.woff2 +0 -0
  112. package/src/web/assets/fonts/GeistMono-ExtraLight.woff2 +0 -0
  113. package/src/web/assets/fonts/GeistMono-ExtraLightItalic.woff2 +0 -0
  114. package/src/web/assets/fonts/GeistMono-Italic.woff2 +0 -0
  115. package/src/web/assets/fonts/GeistMono-Italic[wght].woff2 +0 -0
  116. package/src/web/assets/fonts/GeistMono-Light.woff2 +0 -0
  117. package/src/web/assets/fonts/GeistMono-LightItalic.woff2 +0 -0
  118. package/src/web/assets/fonts/GeistMono-Medium.woff2 +0 -0
  119. package/src/web/assets/fonts/GeistMono-MediumItalic.woff2 +0 -0
  120. package/src/web/assets/fonts/GeistMono-Regular.woff2 +0 -0
  121. package/src/web/assets/fonts/GeistMono-SemiBold.woff2 +0 -0
  122. package/src/web/assets/fonts/GeistMono-SemiBoldItalic.woff2 +0 -0
  123. package/src/web/assets/fonts/GeistMono-Thin.woff2 +0 -0
  124. package/src/web/assets/fonts/GeistMono-ThinItalic.woff2 +0 -0
  125. package/src/web/assets/fonts/GeistMono[wght].woff2 +0 -0
  126. package/src/web/assets/fonts/Geist[wght].woff2 +0 -0
  127. package/src/web/assets/fonts/blauer-nue-regular.woff2 +0 -0
  128. package/src/web/assets/fonts/neue-montreal-regular.woff2 +0 -0
  129. package/src/web/assets/images/favicon-v2.svg +6 -0
  130. package/src/web/assets/images/favicon.png +0 -0
  131. package/src/web/assets/images/foruse.svg +5 -0
  132. package/src/web/assets/images/logo_black.svg +5 -0
  133. package/src/web/assets/images/logo_white.svg +5 -0
  134. package/src/web/assets/images/logoblack.png +0 -0
  135. package/src/web/assets/images/logowhite.png +0 -0
  136. package/src/web/build.ts +23 -0
  137. package/src/web/components/ApprovalPanel.tsx +191 -0
  138. package/src/web/components/ChatPage.tsx +273 -0
  139. package/src/web/components/FileExplorer.tsx +162 -0
  140. package/src/web/components/HomePage.tsx +121 -0
  141. package/src/web/components/MessageItem.tsx +178 -0
  142. package/src/web/components/Modal.tsx +30 -0
  143. package/src/web/components/QuestionPanel.tsx +149 -0
  144. package/src/web/components/Setup.tsx +211 -0
  145. package/src/web/components/Sidebar.tsx +292 -0
  146. package/src/web/components/ThinkingIndicator.tsx +85 -0
  147. package/src/web/logo_black.svg +5 -0
  148. package/src/web/logo_white.svg +5 -0
  149. package/src/web/router.ts +46 -0
  150. package/src/web/server.tsx +662 -0
  151. package/src/web/storage.ts +92 -0
  152. package/src/web/types.ts +17 -0
  153. package/src/web/utils.ts +61 -0
  154. package/tsconfig.json +33 -0
@@ -0,0 +1,18 @@
1
+ import { tool, type CoreTool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { executeExploreTool } from './exploreExecutor';
4
+
5
+ export const explore: CoreTool = tool({
6
+ description: `Explore the codebase autonomously to gather information.
7
+ This tool launches an exploration agent that will use read, glob, grep, and list tools iteratively.
8
+ The agent will continue exploring until it has gathered enough information to answer the purpose.
9
+ Use this for open-ended exploration tasks like understanding code structure, finding implementations, etc.`,
10
+ parameters: z.object({
11
+ purpose: z.string().describe('The goal of the exploration - what information you need to gather'),
12
+ }),
13
+ execute: async (args) => {
14
+ const result = await executeExploreTool(args.purpose);
15
+ if (!result.success) return { error: result.error || 'Unknown error occurred' };
16
+ return result.result;
17
+ },
18
+ });
@@ -0,0 +1,320 @@
1
+ import { streamText, tool as createTool } from 'ai';
2
+ import { createAnthropic } from '@ai-sdk/anthropic';
3
+ import { createOpenAI } from '@ai-sdk/openai';
4
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
5
+ import { createMistral } from '@ai-sdk/mistral';
6
+ import { createXai } from '@ai-sdk/xai';
7
+ import { z } from 'zod';
8
+ import { readConfig } from '../../utils/config';
9
+ import { executeTool } from './executor';
10
+ import { getExploreAbortSignal, isExploreAborted, notifyExploreTool } from '../../utils/exploreBridge';
11
+
12
+ interface ExploreLog {
13
+ tool: string;
14
+ args: Record<string, unknown>;
15
+ success: boolean;
16
+ resultPreview?: string;
17
+ }
18
+
19
+ let exploreLogs: ExploreLog[] = [];
20
+
21
+ const EXPLORE_TIMEOUT = 8 * 60 * 1000;
22
+
23
+ const EXPLORE_SYSTEM_PROMPT = `You are an exploration agent that gathers information from a codebase.
24
+
25
+ Your goal is to explore the codebase to fulfill the given purpose. You have access to these tools:
26
+ - read: Read file contents
27
+ - glob: Find files by pattern
28
+ - grep: Search for text in files
29
+ - list: List directory contents
30
+
31
+ IMPORTANT RULES:
32
+ 1. Be thorough but efficient - don't repeat the same searches
33
+ 2. When you have gathered enough information to answer the purpose, call the "done" tool with a comprehensive summary
34
+ 3. If you cannot find the information after reasonable exploration, call "done" with what you found
35
+ 4. Focus on the purpose - don't explore unrelated areas
36
+ 5. Summarize findings clearly and include relevant file paths and code snippets
37
+ 6. You MUST call the "done" tool when finished - this is the only way to complete the exploration`;
38
+
39
+ const MAX_STEPS = 50;
40
+
41
+ interface ExploreResult {
42
+ success: boolean;
43
+ result?: string;
44
+ error?: string;
45
+ }
46
+
47
+ function createModelProvider(config: { provider: string; model: string; apiKey?: string }) {
48
+ const cleanApiKey = config.apiKey?.trim().replace(/[\r\n]+/g, '');
49
+ const cleanModel = config.model.trim().replace(/[\r\n]+/g, '');
50
+
51
+ switch (config.provider) {
52
+ case 'anthropic': {
53
+ const anthropic = createAnthropic({ apiKey: cleanApiKey });
54
+ return anthropic(cleanModel);
55
+ }
56
+ case 'openai': {
57
+ const openai = createOpenAI({ apiKey: cleanApiKey });
58
+ return openai(cleanModel);
59
+ }
60
+ case 'google': {
61
+ const google = createGoogleGenerativeAI({ apiKey: cleanApiKey });
62
+ return google(cleanModel);
63
+ }
64
+ case 'mistral': {
65
+ const mistral = createMistral({ apiKey: cleanApiKey });
66
+ return mistral(cleanModel);
67
+ }
68
+ case 'xai': {
69
+ const xai = createXai({ apiKey: cleanApiKey });
70
+ return xai(cleanModel);
71
+ }
72
+ case 'ollama':
73
+ throw new Error('Ollama provider is not supported for explore tool');
74
+ default:
75
+ throw new Error(`Unknown provider: ${config.provider}`);
76
+ }
77
+ }
78
+
79
+ let exploreDoneResult: string | null = null;
80
+
81
+ function getResultPreview(result: string | undefined): string {
82
+ if (!result) return '';
83
+ const lines = result.split('\n');
84
+ if (lines.length <= 3) return result.substring(0, 200);
85
+ return lines.slice(0, 3).join('\n').substring(0, 200) + '...';
86
+ }
87
+
88
+ function createExploreTools() {
89
+ return {
90
+ read: createTool({
91
+ description: 'Read the contents of a file',
92
+ parameters: z.object({
93
+ path: z.string().describe('Path to the file to read'),
94
+ }),
95
+ execute: async (args) => {
96
+ if (isExploreAborted()) return { error: 'Exploration aborted' };
97
+ const result = await executeTool('read', args);
98
+ const resultLen = result.result?.length || 0;
99
+ const preview = result.success ? `${(result.result || '').split('\n').length} lines` : (result.error || 'error');
100
+ exploreLogs.push({ tool: 'read', args, success: result.success, resultPreview: preview });
101
+ notifyExploreTool('read', args, { success: result.success, preview }, resultLen);
102
+ if (!result.success) return { error: result.error };
103
+ return result.result;
104
+ },
105
+ }),
106
+ glob: createTool({
107
+ description: 'Find files matching a glob pattern',
108
+ parameters: z.object({
109
+ pattern: z.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.tsx")'),
110
+ path: z.string().optional().describe('Directory to search in (default: workspace root)'),
111
+ }),
112
+ execute: async (args) => {
113
+ if (isExploreAborted()) return { error: 'Exploration aborted' };
114
+ const result = await executeTool('glob', args);
115
+ const resultLen = result.result?.length || 0;
116
+ let preview = result.error || 'error';
117
+ if (result.success && result.result) {
118
+ try {
119
+ const files = JSON.parse(result.result);
120
+ preview = `${Array.isArray(files) ? files.length : 0} files`;
121
+ } catch { preview = 'ok'; }
122
+ }
123
+ exploreLogs.push({ tool: 'glob', args, success: result.success, resultPreview: preview });
124
+ notifyExploreTool('glob', args, { success: result.success, preview }, resultLen);
125
+ if (!result.success) return { error: result.error };
126
+ return result.result;
127
+ },
128
+ }),
129
+ grep: createTool({
130
+ description: 'Search for text content within files',
131
+ parameters: z.object({
132
+ pattern: z.string().describe('Glob pattern to match files'),
133
+ query: z.string().describe('Text to search for'),
134
+ path: z.string().optional().describe('Directory to search in'),
135
+ case_sensitive: z.boolean().optional().describe('Case-sensitive search'),
136
+ max_results: z.number().optional().describe('Maximum results'),
137
+ }),
138
+ execute: async (args) => {
139
+ if (isExploreAborted()) return { error: 'Exploration aborted' };
140
+ const result = await executeTool('grep', args);
141
+ const resultLen = result.result?.length || 0;
142
+ let preview = result.error || 'error';
143
+ if (result.success && result.result) {
144
+ try {
145
+ const matches = JSON.parse(result.result);
146
+ preview = `${Array.isArray(matches) ? matches.length : 0} matches`;
147
+ } catch { preview = 'ok'; }
148
+ }
149
+ exploreLogs.push({ tool: 'grep', args, success: result.success, resultPreview: preview });
150
+ notifyExploreTool('grep', args, { success: result.success, preview }, resultLen);
151
+ if (!result.success) return { error: result.error };
152
+ return result.result;
153
+ },
154
+ }),
155
+ list: createTool({
156
+ description: 'List files and directories',
157
+ parameters: z.object({
158
+ path: z.string().describe('Path to list'),
159
+ recursive: z.boolean().optional().describe('List recursively'),
160
+ filter: z.string().optional().describe('Filter pattern'),
161
+ include_hidden: z.boolean().optional().describe('Include hidden files'),
162
+ }),
163
+ execute: async (args) => {
164
+ if (isExploreAborted()) return { error: 'Exploration aborted' };
165
+ const result = await executeTool('list', args);
166
+ const resultLen = result.result?.length || 0;
167
+ let preview = result.error || 'error';
168
+ if (result.success && result.result) {
169
+ try {
170
+ const items = JSON.parse(result.result);
171
+ preview = `${Array.isArray(items) ? items.length : 0} items`;
172
+ } catch { preview = 'ok'; }
173
+ }
174
+ exploreLogs.push({ tool: 'list', args, success: result.success, resultPreview: preview });
175
+ notifyExploreTool('list', args, { success: result.success, preview }, resultLen);
176
+ if (!result.success) return { error: result.error };
177
+ return result.result;
178
+ },
179
+ }),
180
+ done: createTool({
181
+ description: 'Call this when you have gathered enough information. Provide a comprehensive summary of your findings. This MUST be called to complete the exploration.',
182
+ parameters: z.object({
183
+ summary: z.string().describe('Comprehensive summary of what was found during exploration'),
184
+ }),
185
+ execute: async (args) => {
186
+ exploreDoneResult = args.summary;
187
+ return { done: true, summary: args.summary };
188
+ },
189
+ }),
190
+ };
191
+ }
192
+
193
+ function formatExploreLogs(): string {
194
+ if (exploreLogs.length === 0) return '';
195
+
196
+ const lines = ['Tools used:'];
197
+ for (const log of exploreLogs) {
198
+ const argStr = log.args.path || log.args.pattern || log.args.query || '';
199
+ const status = log.success ? '+' : '-';
200
+ lines.push(` ${status} ${log.tool}(${argStr}) -> ${log.resultPreview || 'ok'}`);
201
+ }
202
+ return lines.join('\n');
203
+ }
204
+
205
+ function formatDuration(ms: number): string {
206
+ const seconds = Math.floor(ms / 1000);
207
+ if (seconds < 60) return `${seconds}s`;
208
+ const minutes = Math.floor(seconds / 60);
209
+ const remainingSeconds = seconds % 60;
210
+ return `${minutes}m ${remainingSeconds}s`;
211
+ }
212
+
213
+ export async function executeExploreTool(purpose: string): Promise<ExploreResult> {
214
+ const startTime = Date.now();
215
+ const userConfig = readConfig();
216
+
217
+ if (!userConfig.provider || !userConfig.model) {
218
+ return {
219
+ success: false,
220
+ error: 'No provider or model configured',
221
+ };
222
+ }
223
+
224
+ exploreDoneResult = null;
225
+ exploreLogs = [];
226
+
227
+ const abortSignal = getExploreAbortSignal();
228
+ const timeoutId = setTimeout(() => {
229
+ if (!exploreDoneResult) {
230
+ exploreDoneResult = '[Exploration timed out after 8 minutes]';
231
+ }
232
+ }, EXPLORE_TIMEOUT);
233
+
234
+ try {
235
+ const model = createModelProvider({
236
+ provider: userConfig.provider,
237
+ model: userConfig.model,
238
+ apiKey: userConfig.apiKey,
239
+ });
240
+
241
+ const tools = createExploreTools();
242
+
243
+ const result = streamText({
244
+ model,
245
+ messages: [
246
+ {
247
+ role: 'user',
248
+ content: `Explore the codebase to: ${purpose}`,
249
+ },
250
+ ],
251
+ system: EXPLORE_SYSTEM_PROMPT,
252
+ tools,
253
+ maxSteps: MAX_STEPS,
254
+ abortSignal,
255
+ });
256
+
257
+ let lastError: string | null = null;
258
+
259
+ for await (const chunk of result.fullStream as any) {
260
+ if (isExploreAborted()) {
261
+ break;
262
+ }
263
+ const c: any = chunk;
264
+ if (c.type === 'error') {
265
+ lastError = c.error instanceof Error ? c.error.message : String(c.error);
266
+ }
267
+ if (exploreDoneResult !== null) {
268
+ break;
269
+ }
270
+ }
271
+
272
+ clearTimeout(timeoutId);
273
+ const duration = formatDuration(Date.now() - startTime);
274
+
275
+ if (isExploreAborted()) {
276
+ const logsStr = formatExploreLogs();
277
+ return {
278
+ success: false,
279
+ error: `Exploration interrupted (${duration})${logsStr ? '\n\n' + logsStr : ''}`,
280
+ };
281
+ }
282
+
283
+ if (lastError && exploreLogs.length === 0) {
284
+ return {
285
+ success: false,
286
+ error: lastError,
287
+ };
288
+ }
289
+
290
+ const logsStr = formatExploreLogs();
291
+
292
+ if (exploreDoneResult !== null) {
293
+ return {
294
+ success: true,
295
+ result: `Completed in ${duration}\n${logsStr}\n\nSummary:\n${exploreDoneResult}`,
296
+ };
297
+ }
298
+
299
+ return {
300
+ success: true,
301
+ result: `Completed in ${duration}\n${logsStr}\n\nExploration completed after ${MAX_STEPS} steps without explicit summary.`,
302
+ };
303
+ } catch (error) {
304
+ clearTimeout(timeoutId);
305
+ const duration = formatDuration(Date.now() - startTime);
306
+ const logsStr = formatExploreLogs();
307
+
308
+ if (isExploreAborted()) {
309
+ return {
310
+ success: false,
311
+ error: `Exploration interrupted (${duration})${logsStr ? '\n\n' + logsStr : ''}`,
312
+ };
313
+ }
314
+
315
+ return {
316
+ success: false,
317
+ error: `${error instanceof Error ? error.message : 'Unknown error'} (${duration})${logsStr ? '\n\n' + logsStr : ''}`,
318
+ };
319
+ }
320
+ }
@@ -0,0 +1,16 @@
1
+ import { tool, type CoreTool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { executeTool } from './executor';
4
+
5
+ export const glob: CoreTool = tool({
6
+ description: 'Find files matching a glob pattern. Fast pattern-based file discovery.',
7
+ parameters: z.object({
8
+ pattern: z.string().describe('Glob pattern to match files (e.g., "*.ts", "**/*.tsx", "src/**/*.js")'),
9
+ path: z.string().nullable().optional().describe('Directory to search in (use null for workspace root)'),
10
+ }),
11
+ execute: async (args) => {
12
+ const result = await executeTool('glob', args);
13
+ if (!result.success) return { error: result.error || 'Unknown error occurred' };
14
+ return result.result;
15
+ },
16
+ });
@@ -0,0 +1,19 @@
1
+ import { tool, type CoreTool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { executeTool } from './executor';
4
+
5
+ export const grep: CoreTool = tool({
6
+ description: 'Search for text content within files matching a glob pattern. Combines pattern matching with content search.',
7
+ parameters: z.object({
8
+ pattern: z.string().describe('Glob pattern to match files (e.g., "*.ts", "**/*.tsx", "src/**/*.js")'),
9
+ query: z.string().describe('Text content to search for within the matched files'),
10
+ path: z.string().nullable().optional().describe('Directory to search in (use null for workspace root)'),
11
+ case_sensitive: z.boolean().nullable().optional().describe('Whether text search should be case-sensitive (use null for false)'),
12
+ max_results: z.number().nullable().optional().describe('Maximum number of results to return (use null for 100)'),
13
+ }),
14
+ execute: async (args) => {
15
+ const result = await executeTool('grep', args);
16
+ if (!result.success) return { error: result.error || 'Unknown error occurred' };
17
+ return result.result;
18
+ },
19
+ });
@@ -0,0 +1,4 @@
1
+ export { tools } from './definitions';
2
+ export type { CoreTool } from 'ai';
3
+ export { executeTool } from './executor';
4
+ export type { ToolResult } from './executor';
@@ -0,0 +1,20 @@
1
+ import { tool, type CoreTool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { executeTool } from './executor';
4
+
5
+ export const list: CoreTool = tool({
6
+ description: 'List files and directories in a directory with optional recursive listing and filtering',
7
+ parameters: z.object({
8
+ path: z
9
+ .string()
10
+ .describe('The path to the directory relative to the workspace root. Use "." for the root directory.'),
11
+ recursive: z.boolean().nullable().optional().describe('If true, list files recursively in all subdirectories (use null for false)'),
12
+ filter: z.string().nullable().optional().describe('Optional glob pattern to filter results (use null for no filter)'),
13
+ include_hidden: z.boolean().nullable().optional().describe('If true, include hidden files (starting with .) (use null for false)'),
14
+ }),
15
+ execute: async (args) => {
16
+ const result = await executeTool('list', args);
17
+ if (!result.success) return { error: result.error || 'Unknown error occurred' };
18
+ return result.result;
19
+ },
20
+ });
@@ -0,0 +1,20 @@
1
+ import { tool, type CoreTool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { askQuestion } from '../../utils/questionBridge';
4
+
5
+ export const question: CoreTool = tool({
6
+ description: 'Ask the user a question with predefined options and wait for their selection. The user can select one of the options OR type a custom response directly in the text input field below the options. The returned answer includes: index (selected option index), label (option label), value (option value if set), and customText (if user provided a custom text response).',
7
+ parameters: z.object({
8
+ prompt: z.string().describe('The question to show to the user.'),
9
+ options: z.array(
10
+ z.object({
11
+ label: z.string().describe('The option label shown to the user.'),
12
+ value: z.string().nullable().optional().describe('Optional value returned for the selected option. Use null if not needed.'),
13
+ })
14
+ ).describe('List of options the user can pick from. A text input field is automatically displayed below the options where the user can type a custom response instead.'),
15
+ }),
16
+ execute: async (args) => {
17
+ const answer = await askQuestion(args.prompt, args.options);
18
+ return answer;
19
+ },
20
+ });
@@ -0,0 +1,15 @@
1
+ import { tool, type CoreTool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { executeTool } from './executor';
4
+
5
+ export const read: CoreTool = tool({
6
+ description: 'Read the contents of a file from the workspace',
7
+ parameters: z.object({
8
+ path: z.string().describe('The path to the file relative to the workspace root'),
9
+ }),
10
+ execute: async (args) => {
11
+ const result = await executeTool('read', args);
12
+ if (!result.success) return { error: result.error || 'Unknown error occurred' };
13
+ return result.result;
14
+ },
15
+ });
@@ -0,0 +1,21 @@
1
+ import { tool, type CoreTool } from 'ai';
2
+ import { z } from 'zod';
3
+ import { executeTool } from './executor';
4
+
5
+ export const write: CoreTool = tool({
6
+ description: 'Write or overwrite a file in the workspace. Creates parent directories automatically if they do not exist. IMPORTANT: This operation requires user approval - the user will see a preview and must approve before the file is written. If rejected, ask the user for clarification using the question tool.',
7
+ parameters: z.object({
8
+ path: z.string().describe('The path to the file relative to the workspace root'),
9
+ content: z.string().nullable().optional().transform((v) => v ?? '').describe('The content to write to the file (use null for empty string)'),
10
+ append: z.boolean().nullable().optional().transform((v) => v ?? false).describe('If true, append to the file instead of overwriting (use null for false)'),
11
+ }),
12
+ execute: async (args) => {
13
+ const result = await executeTool('write', args);
14
+ if (!result.success) {
15
+ return result.userMessage
16
+ ? { error: result.error, userMessage: result.userMessage }
17
+ : { error: result.error };
18
+ }
19
+ return result;
20
+ },
21
+ });
@@ -0,0 +1,155 @@
1
+ import { CoreMessage, CoreTool } from 'ai';
2
+
3
+ export type AgentEventType =
4
+ | 'text-delta'
5
+ | 'reasoning-start'
6
+ | 'reasoning-delta'
7
+ | 'reasoning-end'
8
+ | 'tool-input-start'
9
+ | 'tool-input-delta'
10
+ | 'tool-input-end'
11
+ | 'tool-call-start'
12
+ | 'tool-call-end'
13
+ | 'tool-result'
14
+ | 'step-start'
15
+ | 'step-finish'
16
+ | 'finish'
17
+ | 'error';
18
+
19
+ export interface BaseEvent {
20
+ type: AgentEventType;
21
+ }
22
+
23
+ export interface TextDeltaEvent extends BaseEvent {
24
+ type: 'text-delta';
25
+ content: string;
26
+ }
27
+
28
+ export interface ReasoningStartEvent extends BaseEvent {
29
+ type: 'reasoning-start';
30
+ }
31
+
32
+ export interface ReasoningDeltaEvent extends BaseEvent {
33
+ type: 'reasoning-delta';
34
+ content: string;
35
+ }
36
+
37
+ export interface ReasoningEndEvent extends BaseEvent {
38
+ type: 'reasoning-end';
39
+ }
40
+
41
+ export interface ToolInputStartEvent extends BaseEvent {
42
+ type: 'tool-input-start';
43
+ toolCallId: string;
44
+ toolName: string;
45
+ }
46
+
47
+ export interface ToolInputDeltaEvent extends BaseEvent {
48
+ type: 'tool-input-delta';
49
+ toolCallId: string;
50
+ delta: string;
51
+ }
52
+
53
+ export interface ToolInputEndEvent extends BaseEvent {
54
+ type: 'tool-input-end';
55
+ toolCallId: string;
56
+ }
57
+
58
+ export interface ToolCallStartEvent extends BaseEvent {
59
+ type: 'tool-call-start';
60
+ toolCallId: string;
61
+ toolName: string;
62
+ }
63
+
64
+ export interface ToolCallEndEvent extends BaseEvent {
65
+ type: 'tool-call-end';
66
+ toolCallId: string;
67
+ toolName: string;
68
+ args: Record<string, unknown>;
69
+ }
70
+
71
+ export interface ToolResultEvent extends BaseEvent {
72
+ type: 'tool-result';
73
+ toolCallId: string;
74
+ toolName: string;
75
+ result: unknown;
76
+ }
77
+
78
+ export interface StepStartEvent extends BaseEvent {
79
+ type: 'step-start';
80
+ stepNumber: number;
81
+ }
82
+
83
+ export interface StepFinishEvent extends BaseEvent {
84
+ type: 'step-finish';
85
+ stepNumber: number;
86
+ finishReason: string;
87
+ }
88
+
89
+ export interface FinishEvent extends BaseEvent {
90
+ type: 'finish';
91
+ finishReason: string;
92
+ usage?: {
93
+ promptTokens: number;
94
+ completionTokens: number;
95
+ totalTokens: number;
96
+ };
97
+ }
98
+
99
+ export interface ErrorEvent extends BaseEvent {
100
+ type: 'error';
101
+ error: string;
102
+ }
103
+
104
+ export type AgentEvent =
105
+ | TextDeltaEvent
106
+ | ReasoningStartEvent
107
+ | ReasoningDeltaEvent
108
+ | ReasoningEndEvent
109
+ | ToolInputStartEvent
110
+ | ToolInputDeltaEvent
111
+ | ToolInputEndEvent
112
+ | ToolCallStartEvent
113
+ | ToolCallEndEvent
114
+ | ToolResultEvent
115
+ | StepStartEvent
116
+ | StepFinishEvent
117
+ | FinishEvent
118
+ | ErrorEvent;
119
+
120
+ export interface ProviderConfig {
121
+ provider: string;
122
+ model: string;
123
+ apiKey?: string;
124
+ systemPrompt: string;
125
+ tools?: Record<string, CoreTool>;
126
+ maxSteps?: number;
127
+ }
128
+
129
+ export interface AgentConfig {
130
+ maxSteps?: number;
131
+ }
132
+
133
+ export interface AgentContext {
134
+ messages: CoreMessage[];
135
+ systemPrompt: string;
136
+ tools: Record<string, CoreTool>;
137
+ config: AgentConfig;
138
+ }
139
+
140
+ export interface AgentMessage {
141
+ role: 'user' | 'assistant';
142
+ content: string;
143
+ }
144
+
145
+ export interface ProviderSendOptions {
146
+ abortSignal?: AbortSignal;
147
+ }
148
+
149
+ export interface Provider {
150
+ sendMessage(
151
+ messages: CoreMessage[],
152
+ config: ProviderConfig,
153
+ options?: ProviderSendOptions
154
+ ): AsyncGenerator<AgentEvent>;
155
+ }