@4onstudios/iris-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/api/acp/acpServer.d.ts +23 -0
- package/dist/api/acp/acpServer.js +814 -0
- package/dist/api/acp/index.d.ts +2 -0
- package/dist/api/acp/index.js +2 -0
- package/dist/api/acp/irisClient.d.ts +58 -0
- package/dist/api/acp/irisClient.js +214 -0
- package/dist/api/agent.d.ts +14 -0
- package/dist/api/agent.js +4171 -0
- package/dist/api/backendService.d.ts +4 -0
- package/dist/api/backendService.js +14 -0
- package/dist/api/core/agent/host/AgentContract.d.ts +82 -0
- package/dist/api/core/agent/host/AgentContract.js +1 -0
- package/dist/api/core/agent/host/AgentRegistry.d.ts +24 -0
- package/dist/api/core/agent/host/AgentRegistry.js +125 -0
- package/dist/api/core/agent/host/defaultRegistry.d.ts +15 -0
- package/dist/api/core/agent/host/defaultRegistry.js +46 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.d.ts +65 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.js +117 -0
- package/dist/api/core/agent/host/hostSessionManager.d.ts +111 -0
- package/dist/api/core/agent/host/hostSessionManager.js +352 -0
- package/dist/api/core/agent/host/index.d.ts +5 -0
- package/dist/api/core/agent/host/index.js +5 -0
- package/dist/api/core/agent/index.d.ts +87 -0
- package/dist/api/core/agent/index.js +1024 -0
- package/dist/api/core/agent/publicContracts.d.ts +35 -0
- package/dist/api/core/agent/publicContracts.js +103 -0
- package/dist/api/core/agent/tools/applyDiff.d.ts +51 -0
- package/dist/api/core/agent/tools/applyDiff.js +131 -0
- package/dist/api/core/agent/tools/backgroundTasks.d.ts +240 -0
- package/dist/api/core/agent/tools/backgroundTasks.js +313 -0
- package/dist/api/core/agent/tools/createDirectory.d.ts +43 -0
- package/dist/api/core/agent/tools/createDirectory.js +77 -0
- package/dist/api/core/agent/tools/deleteFile.d.ts +37 -0
- package/dist/api/core/agent/tools/deleteFile.js +66 -0
- package/dist/api/core/agent/tools/editFile.d.ts +73 -0
- package/dist/api/core/agent/tools/editFile.js +173 -0
- package/dist/api/core/agent/tools/executeCommand.d.ts +59 -0
- package/dist/api/core/agent/tools/executeCommand.js +250 -0
- package/dist/api/core/agent/tools/fileContent.d.ts +54 -0
- package/dist/api/core/agent/tools/fileContent.js +227 -0
- package/dist/api/core/agent/tools/findDefinition.d.ts +72 -0
- package/dist/api/core/agent/tools/findDefinition.js +109 -0
- package/dist/api/core/agent/tools/findReferences.d.ts +86 -0
- package/dist/api/core/agent/tools/findReferences.js +135 -0
- package/dist/api/core/agent/tools/formatDocument.d.ts +67 -0
- package/dist/api/core/agent/tools/formatDocument.js +90 -0
- package/dist/api/core/agent/tools/getCodeActions.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeActions.js +97 -0
- package/dist/api/core/agent/tools/getCodeCompletion.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeCompletion.js +174 -0
- package/dist/api/core/agent/tools/getCodeContext.d.ts +14 -0
- package/dist/api/core/agent/tools/getCodeContext.js +282 -0
- package/dist/api/core/agent/tools/getSignatureHelp.d.ts +61 -0
- package/dist/api/core/agent/tools/getSignatureHelp.js +85 -0
- package/dist/api/core/agent/tools/getSymbols.d.ts +50 -0
- package/dist/api/core/agent/tools/getSymbols.js +164 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.d.ts +58 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.js +147 -0
- package/dist/api/core/agent/tools/getTypeInfo.d.ts +70 -0
- package/dist/api/core/agent/tools/getTypeInfo.js +84 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.d.ts +80 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.js +281 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.d.ts +53 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.js +97 -0
- package/dist/api/core/agent/tools/grepSearch.d.ts +100 -0
- package/dist/api/core/agent/tools/grepSearch.js +211 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.d.ts +13 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.js +22 -0
- package/dist/api/core/agent/tools/listDirectory.d.ts +66 -0
- package/dist/api/core/agent/tools/listDirectory.js +161 -0
- package/dist/api/core/agent/tools/mcpTools.d.ts +48 -0
- package/dist/api/core/agent/tools/mcpTools.js +442 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.d.ts +120 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.js +306 -0
- package/dist/api/core/agent/tools/readFile.d.ts +92 -0
- package/dist/api/core/agent/tools/readFile.js +337 -0
- package/dist/api/core/agent/tools/renameFile.d.ts +49 -0
- package/dist/api/core/agent/tools/renameFile.js +86 -0
- package/dist/api/core/agent/tools/renameSymbol.d.ts +70 -0
- package/dist/api/core/agent/tools/renameSymbol.js +106 -0
- package/dist/api/core/agent/tools/runTerminalCommand.d.ts +72 -0
- package/dist/api/core/agent/tools/runTerminalCommand.js +69 -0
- package/dist/api/core/agent/tools/searchFiles.d.ts +82 -0
- package/dist/api/core/agent/tools/searchFiles.js +280 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.d.ts +3 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.js +676 -0
- package/dist/api/core/agent/tools/treeTraversal.d.ts +60 -0
- package/dist/api/core/agent/tools/treeTraversal.js +217 -0
- package/dist/api/core/agent/tools/webSearch.d.ts +37 -0
- package/dist/api/core/agent/tools/webSearch.js +80 -0
- package/dist/api/core/agent/tools/writeFile.d.ts +59 -0
- package/dist/api/core/agent/tools/writeFile.js +97 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.d.ts +12 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.js +67 -0
- package/dist/api/core/agent/utils/diffUtils.d.ts +14 -0
- package/dist/api/core/agent/utils/diffUtils.js +39 -0
- package/dist/api/core/agent/utils/environmentSnapshot.d.ts +42 -0
- package/dist/api/core/agent/utils/environmentSnapshot.js +213 -0
- package/dist/api/core/agent/utils/errorRecovery.d.ts +49 -0
- package/dist/api/core/agent/utils/errorRecovery.js +229 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.d.ts +8 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.js +238 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.d.ts +12 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.js +34 -0
- package/dist/api/core/agent/utils/pathRecovery.d.ts +1 -0
- package/dist/api/core/agent/utils/pathRecovery.js +29 -0
- package/dist/api/core/agent/utils/repoMapIndex.d.ts +3 -0
- package/dist/api/core/agent/utils/repoMapIndex.js +197 -0
- package/dist/api/core/agent/utils/skillsDiscovery.d.ts +10 -0
- package/dist/api/core/agent/utils/skillsDiscovery.js +50 -0
- package/dist/api/core/agent/utils/toolCallBudget.d.ts +9 -0
- package/dist/api/core/agent/utils/toolCallBudget.js +55 -0
- package/dist/api/core/agent/utils/toolLifecycle.d.ts +30 -0
- package/dist/api/core/agent/utils/toolLifecycle.js +361 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.d.ts +12 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.js +57 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.d.ts +13 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.js +149 -0
- package/dist/api/core/agent/utils/workspacePathGuard.d.ts +7 -0
- package/dist/api/core/agent/utils/workspacePathGuard.js +79 -0
- package/dist/api/core/containers/chat/toolResultSerialization.d.ts +8 -0
- package/dist/api/core/containers/chat/toolResultSerialization.js +71 -0
- package/dist/api/core/library/BrowserManager.d.ts +11 -0
- package/dist/api/core/library/BrowserManager.js +66 -0
- package/dist/api/core/library/desktopWorkspace.d.ts +1 -0
- package/dist/api/core/library/desktopWorkspace.js +3 -0
- package/dist/api/core/library/extensionManager.d.ts +10 -0
- package/dist/api/core/library/extensionManager.js +27 -0
- package/dist/api/core/library/knowledgeGraph.d.ts +268 -0
- package/dist/api/core/library/knowledgeGraph.js +989 -0
- package/dist/api/core/library/languageModelTools.d.ts +141 -0
- package/dist/api/core/library/languageModelTools.js +207 -0
- package/dist/api/core/library/localRuntime.d.ts +23 -0
- package/dist/api/core/library/localRuntime.js +144 -0
- package/dist/api/core/library/lsp/coreLsp.d.ts +367 -0
- package/dist/api/core/library/lsp/coreLsp.js +2076 -0
- package/dist/api/core/library/lsp/protocol.d.ts +25 -0
- package/dist/api/core/library/lsp/protocol.js +1 -0
- package/dist/api/core/library/lsp/serverManager.d.ts +37 -0
- package/dist/api/core/library/lsp/serverManager.js +427 -0
- package/dist/api/core/library/mcpServerProvider.d.ts +69 -0
- package/dist/api/core/library/mcpServerProvider.js +152 -0
- package/dist/api/core/library/mcpSettings.d.ts +20 -0
- package/dist/api/core/library/mcpSettings.js +131 -0
- package/dist/api/core/library/patternMatcher.d.ts +66 -0
- package/dist/api/core/library/patternMatcher.js +284 -0
- package/dist/api/core/library/regexEscape.d.ts +4 -0
- package/dist/api/core/library/regexEscape.js +4 -0
- package/dist/api/core/library/runtimeEventBus.d.ts +27 -0
- package/dist/api/core/library/runtimeEventBus.js +78 -0
- package/dist/api/core/library/safetyMiddleware.d.ts +98 -0
- package/dist/api/core/library/safetyMiddleware.js +215 -0
- package/dist/api/core/library/tauri.d.ts +71 -0
- package/dist/api/core/library/tauri.js +222 -0
- package/dist/api/core/library/tauriImport.d.ts +5 -0
- package/dist/api/core/library/tauriImport.js +7 -0
- package/dist/api/core/library/terminalAutoApproveSettings.d.ts +32 -0
- package/dist/api/core/library/terminalAutoApproveSettings.js +529 -0
- package/dist/api/core/library/workspaceIdentity.d.ts +10 -0
- package/dist/api/core/library/workspaceIdentity.js +49 -0
- package/dist/api/core/library/workspaceSummary.d.ts +19 -0
- package/dist/api/core/library/workspaceSummary.js +35 -0
- package/dist/api/core/skills/agent-customization/SKILL.md +27 -0
- package/dist/api/core/skills/bug-fix/SKILL.md +21 -0
- package/dist/api/core/skills/create-pr/SKILL.md +32 -0
- package/dist/api/core/skills/dev-server/SKILL.md +20 -0
- package/dist/api/core/skills/fix-suggestions/SKILL.md +21 -0
- package/dist/api/core/skills/github-search/SKILL.md +21 -0
- package/dist/api/core/skills/github-summary/SKILL.md +25 -0
- package/dist/api/core/skills/integration-tests/SKILL.md +29 -0
- package/dist/api/core/skills/pr-comments/SKILL.md +23 -0
- package/dist/api/core/skills/project-setup/SKILL.md +20 -0
- package/dist/api/core/skills/search-integration/SKILL.md +19 -0
- package/dist/api/core/skills/search-results/SKILL.md +19 -0
- package/dist/api/core/skills/typescript-upgrade/SKILL.md +21 -0
- package/dist/api/data/runStore.d.ts +46 -0
- package/dist/api/data/runStore.js +244 -0
- package/dist/api/helpers/agentUtils.d.ts +11 -0
- package/dist/api/helpers/agentUtils.js +23 -0
- package/dist/api/helpers/modelTokenLimits.d.ts +2 -0
- package/dist/api/helpers/modelTokenLimits.js +158 -0
- package/dist/api/helpers/observationalMemory.d.ts +10 -0
- package/dist/api/helpers/observationalMemory.js +36 -0
- package/dist/api/helpers/promptBudget.d.ts +30 -0
- package/dist/api/helpers/promptBudget.js +216 -0
- package/dist/api/helpers/resolveImageMessageParts.d.ts +9 -0
- package/dist/api/helpers/resolveImageMessageParts.js +140 -0
- package/dist/api/helpers/slashCommands.d.ts +31 -0
- package/dist/api/helpers/slashCommands.js +129 -0
- package/dist/api/helpers/tokenUsage.d.ts +19 -0
- package/dist/api/helpers/tokenUsage.js +81 -0
- package/dist/api/routes/fileRoutes.d.ts +2 -0
- package/dist/api/routes/fileRoutes.js +108 -0
- package/dist/api/routes/lspDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspDocumentRoutes.js +84 -0
- package/dist/api/routes/lspHierarchyRoutes.d.ts +4 -0
- package/dist/api/routes/lspHierarchyRoutes.js +122 -0
- package/dist/api/routes/lspPositionRoutes.d.ts +4 -0
- package/dist/api/routes/lspPositionRoutes.js +263 -0
- package/dist/api/routes/lspQueryRoutes.d.ts +4 -0
- package/dist/api/routes/lspQueryRoutes.js +679 -0
- package/dist/api/routes/lspResolveRoutes.d.ts +4 -0
- package/dist/api/routes/lspResolveRoutes.js +143 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.js +98 -0
- package/dist/api/routes/semanticRoutes.d.ts +2 -0
- package/dist/api/routes/semanticRoutes.js +126 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +160 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +38 -0
- package/package.json +100 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree Traversal Utilities for Directory Operations
|
|
3
|
+
* Based on standard tree traversal algorithms
|
|
4
|
+
*/
|
|
5
|
+
export type TraversableNode<T extends TraversableNode<T> = TraversableNode<any>> = {
|
|
6
|
+
children?: T[];
|
|
7
|
+
};
|
|
8
|
+
export type TraversalType = "bfs" | "pre" | "post";
|
|
9
|
+
/**
|
|
10
|
+
* Generator-based Pre-Order traversal (DFS)
|
|
11
|
+
* Useful for processing directories before their contents
|
|
12
|
+
* Order: current -> left -> right (parent -> children)
|
|
13
|
+
*/
|
|
14
|
+
export declare function walkPreOrder<T extends TraversableNode<T>>(node: T | null | undefined): Generator<T, void, undefined>;
|
|
15
|
+
/**
|
|
16
|
+
* Generator-based Post-Order traversal (DFS)
|
|
17
|
+
* Useful for processing directory contents before the directory itself
|
|
18
|
+
* Order: left -> right -> current (children -> parent)
|
|
19
|
+
* Good for operations like delete (delete files before directory)
|
|
20
|
+
*/
|
|
21
|
+
export declare function walkPostOrder<T extends TraversableNode<T>>(node: T | null | undefined): Generator<T, void, undefined>;
|
|
22
|
+
/**
|
|
23
|
+
* Generator-based Breadth-First Search (BFS)
|
|
24
|
+
* Processes directories level by level
|
|
25
|
+
* Good for finding files at specific depth or displaying hierarchy
|
|
26
|
+
*/
|
|
27
|
+
export declare function walkBFS<T extends TraversableNode<T>>(node: T | null | undefined): Generator<T, void, undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* BFS with level information
|
|
30
|
+
* Returns [node, level] tuples
|
|
31
|
+
*/
|
|
32
|
+
export declare function walkBFSWithLevel<T extends TraversableNode<T>>(node: T | null | undefined): Generator<[T, number], void, undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Get all nodes at a specific level
|
|
35
|
+
*/
|
|
36
|
+
export declare function getNodesAtLevel<T extends TraversableNode<T>>(root: T | null | undefined, targetLevel: number): T[];
|
|
37
|
+
/**
|
|
38
|
+
* Find node by predicate using BFS (finds closest match)
|
|
39
|
+
*/
|
|
40
|
+
export declare function findNode<T extends TraversableNode<T>>(root: T | null | undefined, predicate: (node: T) => boolean): T | null;
|
|
41
|
+
/**
|
|
42
|
+
* Find all nodes matching predicate
|
|
43
|
+
*/
|
|
44
|
+
export declare function findAllNodes<T extends TraversableNode<T>>(root: T | null | undefined, predicate: (node: T) => boolean): T[];
|
|
45
|
+
/**
|
|
46
|
+
* Count total nodes in tree
|
|
47
|
+
*/
|
|
48
|
+
export declare function countNodes<T extends TraversableNode<T>>(root: T | null | undefined): number;
|
|
49
|
+
/**
|
|
50
|
+
* Get maximum depth of tree
|
|
51
|
+
*/
|
|
52
|
+
export declare function getMaxDepth<T extends TraversableNode<T>>(root: T | null | undefined): number;
|
|
53
|
+
/**
|
|
54
|
+
* Flatten tree to array using specified traversal
|
|
55
|
+
*/
|
|
56
|
+
export declare function flattenTree<T extends TraversableNode<T>>(root: T | null | undefined, traversalType?: TraversalType): T[];
|
|
57
|
+
/**
|
|
58
|
+
* Example usage for directory trees
|
|
59
|
+
*/
|
|
60
|
+
export declare function exampleUsage(): void;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree Traversal Utilities for Directory Operations
|
|
3
|
+
* Based on standard tree traversal algorithms
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generator-based Pre-Order traversal (DFS)
|
|
7
|
+
* Useful for processing directories before their contents
|
|
8
|
+
* Order: current -> left -> right (parent -> children)
|
|
9
|
+
*/
|
|
10
|
+
export function* walkPreOrder(node) {
|
|
11
|
+
if (!node)
|
|
12
|
+
return;
|
|
13
|
+
const stack = [node];
|
|
14
|
+
while (stack.length) {
|
|
15
|
+
const item = stack.pop();
|
|
16
|
+
if (!item) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
yield item;
|
|
20
|
+
// Push children in reverse order (right first) so left is processed first
|
|
21
|
+
if (item.children) {
|
|
22
|
+
for (let i = item.children.length - 1; i >= 0; i--) {
|
|
23
|
+
stack.push(item.children[i]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generator-based Post-Order traversal (DFS)
|
|
30
|
+
* Useful for processing directory contents before the directory itself
|
|
31
|
+
* Order: left -> right -> current (children -> parent)
|
|
32
|
+
* Good for operations like delete (delete files before directory)
|
|
33
|
+
*/
|
|
34
|
+
export function* walkPostOrder(node) {
|
|
35
|
+
if (!node)
|
|
36
|
+
return;
|
|
37
|
+
const tempStack = [node];
|
|
38
|
+
const resultStack = [];
|
|
39
|
+
while (tempStack.length) {
|
|
40
|
+
const item = tempStack.pop();
|
|
41
|
+
if (!item) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
resultStack.push(item);
|
|
45
|
+
// Push children in normal order
|
|
46
|
+
if (item.children) {
|
|
47
|
+
for (const child of item.children) {
|
|
48
|
+
tempStack.push(child);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Yield in reverse order
|
|
53
|
+
while (resultStack.length) {
|
|
54
|
+
const item = resultStack.pop();
|
|
55
|
+
if (item) {
|
|
56
|
+
yield item;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generator-based Breadth-First Search (BFS)
|
|
62
|
+
* Processes directories level by level
|
|
63
|
+
* Good for finding files at specific depth or displaying hierarchy
|
|
64
|
+
*/
|
|
65
|
+
export function* walkBFS(node) {
|
|
66
|
+
if (!node)
|
|
67
|
+
return;
|
|
68
|
+
const queue = [node];
|
|
69
|
+
while (queue.length) {
|
|
70
|
+
const item = queue.shift();
|
|
71
|
+
if (!item) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
yield item;
|
|
75
|
+
if (item.children) {
|
|
76
|
+
for (const child of item.children) {
|
|
77
|
+
queue.push(child);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* BFS with level information
|
|
84
|
+
* Returns [node, level] tuples
|
|
85
|
+
*/
|
|
86
|
+
export function* walkBFSWithLevel(node) {
|
|
87
|
+
if (!node)
|
|
88
|
+
return;
|
|
89
|
+
const queue = [[node, 0]];
|
|
90
|
+
while (queue.length) {
|
|
91
|
+
const [item, level] = queue.shift();
|
|
92
|
+
if (!item) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
yield [item, level];
|
|
96
|
+
if (item.children) {
|
|
97
|
+
for (const child of item.children) {
|
|
98
|
+
queue.push([child, level + 1]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get all nodes at a specific level
|
|
105
|
+
*/
|
|
106
|
+
export function getNodesAtLevel(root, targetLevel) {
|
|
107
|
+
const nodes = [];
|
|
108
|
+
for (const [node, level] of walkBFSWithLevel(root)) {
|
|
109
|
+
if (level === targetLevel) {
|
|
110
|
+
nodes.push(node);
|
|
111
|
+
}
|
|
112
|
+
else if (level > targetLevel) {
|
|
113
|
+
break; // BFS guarantees we won't find more at target level
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return nodes;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Find node by predicate using BFS (finds closest match)
|
|
120
|
+
*/
|
|
121
|
+
export function findNode(root,
|
|
122
|
+
// eslint-disable-next-line no-unused-vars
|
|
123
|
+
predicate) {
|
|
124
|
+
for (const node of walkBFS(root)) {
|
|
125
|
+
if (predicate(node)) {
|
|
126
|
+
return node;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Find all nodes matching predicate
|
|
133
|
+
*/
|
|
134
|
+
export function findAllNodes(root,
|
|
135
|
+
// eslint-disable-next-line no-unused-vars
|
|
136
|
+
predicate) {
|
|
137
|
+
const results = [];
|
|
138
|
+
for (const node of walkBFS(root)) {
|
|
139
|
+
if (predicate(node)) {
|
|
140
|
+
results.push(node);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Count total nodes in tree
|
|
147
|
+
*/
|
|
148
|
+
export function countNodes(root) {
|
|
149
|
+
let count = 0;
|
|
150
|
+
for (const node of walkBFS(root)) {
|
|
151
|
+
void node;
|
|
152
|
+
count++;
|
|
153
|
+
}
|
|
154
|
+
return count;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get maximum depth of tree
|
|
158
|
+
*/
|
|
159
|
+
export function getMaxDepth(root) {
|
|
160
|
+
let maxDepth = 0;
|
|
161
|
+
for (const [, level] of walkBFSWithLevel(root)) {
|
|
162
|
+
maxDepth = Math.max(maxDepth, level);
|
|
163
|
+
}
|
|
164
|
+
return maxDepth;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Flatten tree to array using specified traversal
|
|
168
|
+
*/
|
|
169
|
+
export function flattenTree(root, traversalType = "bfs") {
|
|
170
|
+
const walker = traversalType === "pre"
|
|
171
|
+
? walkPreOrder
|
|
172
|
+
: traversalType === "post"
|
|
173
|
+
? walkPostOrder
|
|
174
|
+
: walkBFS;
|
|
175
|
+
return Array.from(walker(root));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Example usage for directory trees
|
|
179
|
+
*/
|
|
180
|
+
export function exampleUsage() {
|
|
181
|
+
// Example tree structure (like directory tree from getWorkspaceInfo)
|
|
182
|
+
const tree = {
|
|
183
|
+
name: "project",
|
|
184
|
+
type: "directory",
|
|
185
|
+
children: [
|
|
186
|
+
{
|
|
187
|
+
name: "src",
|
|
188
|
+
type: "directory",
|
|
189
|
+
children: [
|
|
190
|
+
{ name: "index.js", type: "file" },
|
|
191
|
+
{ name: "utils.js", type: "file" },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "package.json",
|
|
196
|
+
type: "file",
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
console.log("=== BFS Traversal (Level by Level) ===");
|
|
201
|
+
for (const node of walkBFS(tree)) {
|
|
202
|
+
console.log(node.name);
|
|
203
|
+
}
|
|
204
|
+
console.log("\n=== Pre-Order Traversal (Parent First) ===");
|
|
205
|
+
for (const node of walkPreOrder(tree)) {
|
|
206
|
+
console.log(node.name);
|
|
207
|
+
}
|
|
208
|
+
console.log("\n=== Post-Order Traversal (Children First) ===");
|
|
209
|
+
for (const node of walkPostOrder(tree)) {
|
|
210
|
+
console.log(node.name);
|
|
211
|
+
}
|
|
212
|
+
console.log("\n=== Find all files ===");
|
|
213
|
+
const files = findAllNodes(tree, (node) => node.type === "file");
|
|
214
|
+
console.log(files.map((f) => f.name));
|
|
215
|
+
console.log("\n=== Get depth ===");
|
|
216
|
+
console.log("Max depth:", getMaxDepth(tree));
|
|
217
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
type SearchEngine = "duckduckgo" | "bing";
|
|
3
|
+
type SearchResult = {
|
|
4
|
+
title: string;
|
|
5
|
+
url: string;
|
|
6
|
+
};
|
|
7
|
+
type SearchWebParams = {
|
|
8
|
+
query: string;
|
|
9
|
+
engine?: SearchEngine;
|
|
10
|
+
maxResults?: number;
|
|
11
|
+
};
|
|
12
|
+
type SearchWebResult = {
|
|
13
|
+
success: true;
|
|
14
|
+
results: SearchResult[];
|
|
15
|
+
} | {
|
|
16
|
+
success: false;
|
|
17
|
+
error: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function searchWeb({ query, engine, maxResults, }: SearchWebParams): Promise<SearchWebResult>;
|
|
20
|
+
export declare const webSearchTool: {
|
|
21
|
+
description: string;
|
|
22
|
+
parameters: z.ZodObject<{
|
|
23
|
+
query: z.ZodString;
|
|
24
|
+
engine: z.ZodDefault<z.ZodEnum<["duckduckgo", "bing"]>>;
|
|
25
|
+
maxResults: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
query?: string;
|
|
28
|
+
maxResults?: number;
|
|
29
|
+
engine?: "duckduckgo" | "bing";
|
|
30
|
+
}, {
|
|
31
|
+
query?: string;
|
|
32
|
+
maxResults?: number;
|
|
33
|
+
engine?: "duckduckgo" | "bing";
|
|
34
|
+
}>;
|
|
35
|
+
execute: typeof searchWeb;
|
|
36
|
+
};
|
|
37
|
+
export default webSearchTool;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import BrowserManager from "../../library/BrowserManager.js";
|
|
3
|
+
/**
|
|
4
|
+
* Web search tool using a search API
|
|
5
|
+
* @param {Object} params
|
|
6
|
+
* @param {string} params.query - Search query
|
|
7
|
+
* @param {number} [params.maxResults=5] - Max results to return
|
|
8
|
+
* @returns {Promise<Object>} Search results
|
|
9
|
+
*/
|
|
10
|
+
// Engine-specific selectors that target actual search result links,
|
|
11
|
+
// avoiding nav/footer/ad anchors returned by a broad querySelectorAll("a").
|
|
12
|
+
const RESULT_SELECTORS = {
|
|
13
|
+
duckduckgo: "[data-testid='result-title-a']",
|
|
14
|
+
bing: ".b_algo h2 a",
|
|
15
|
+
};
|
|
16
|
+
async function webSearch(query, engine = "duckduckgo", maxResults = 5) {
|
|
17
|
+
if (!query) {
|
|
18
|
+
throw new Error("Search query is required");
|
|
19
|
+
}
|
|
20
|
+
const browserManager = await BrowserManager.getInstance();
|
|
21
|
+
const page = await browserManager.newPage();
|
|
22
|
+
try {
|
|
23
|
+
const url = engine === "duckduckgo"
|
|
24
|
+
? `https://duckduckgo.com/?q=${encodeURIComponent(query)}`
|
|
25
|
+
: `https://bing.com/search?q=${encodeURIComponent(query)}`;
|
|
26
|
+
// "domcontentloaded" is sufficient for scraping rendered HTML and is
|
|
27
|
+
// significantly faster than "networkidle2".
|
|
28
|
+
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 15000 });
|
|
29
|
+
const selector = RESULT_SELECTORS[engine] ?? RESULT_SELECTORS.duckduckgo;
|
|
30
|
+
const results = await page.evaluate((resultSelector, limit) => {
|
|
31
|
+
return Array.from(document.querySelectorAll(resultSelector))
|
|
32
|
+
.slice(0, limit)
|
|
33
|
+
.map((a) => ({
|
|
34
|
+
title: a.textContent?.trim() ?? "",
|
|
35
|
+
url: a.href,
|
|
36
|
+
}));
|
|
37
|
+
}, selector, maxResults);
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await page.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//this is the function that out webSearchTool will be calling
|
|
45
|
+
export async function searchWeb({ query, engine, maxResults = 5, }) {
|
|
46
|
+
try {
|
|
47
|
+
//performs web search and returns results, if failed it returns error
|
|
48
|
+
const results = await webSearch(query, engine, maxResults);
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
results,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const err = error;
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: err.message,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export const webSearchTool = {
|
|
63
|
+
description: "Search the web for real-time information",
|
|
64
|
+
parameters: z.object({
|
|
65
|
+
query: z.string().describe("The search query to look up on the web"),
|
|
66
|
+
engine: z
|
|
67
|
+
.enum(["duckduckgo", "bing"])
|
|
68
|
+
.default("duckduckgo")
|
|
69
|
+
.describe("Search engine to use"),
|
|
70
|
+
maxResults: z
|
|
71
|
+
.number()
|
|
72
|
+
.int()
|
|
73
|
+
.min(1)
|
|
74
|
+
.max(20)
|
|
75
|
+
.default(5)
|
|
76
|
+
.describe("Maximum number of results to return"),
|
|
77
|
+
}),
|
|
78
|
+
execute: searchWeb,
|
|
79
|
+
};
|
|
80
|
+
export default webSearchTool;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
type WriteFileParams = {
|
|
3
|
+
filePath: string;
|
|
4
|
+
content: string;
|
|
5
|
+
encoding?: BufferEncoding;
|
|
6
|
+
createDirectories?: boolean;
|
|
7
|
+
};
|
|
8
|
+
type WriteFileSuccessResult = {
|
|
9
|
+
success: true;
|
|
10
|
+
filePath: string;
|
|
11
|
+
fileExisted: boolean;
|
|
12
|
+
size: number;
|
|
13
|
+
created: Date;
|
|
14
|
+
modified: Date;
|
|
15
|
+
linesAdded: number;
|
|
16
|
+
linesRemoved: number;
|
|
17
|
+
diff: string;
|
|
18
|
+
oldContent: string;
|
|
19
|
+
newContent: string;
|
|
20
|
+
};
|
|
21
|
+
type WriteFileErrorResult = {
|
|
22
|
+
success: false;
|
|
23
|
+
error: string;
|
|
24
|
+
};
|
|
25
|
+
type WriteFileResult = WriteFileSuccessResult | WriteFileErrorResult;
|
|
26
|
+
/**
|
|
27
|
+
* Tool for writing content to files
|
|
28
|
+
* @param {Object} params - The parameters for writing a file
|
|
29
|
+
* @param {string} params.filePath - The path to the file to write
|
|
30
|
+
* @param {string} params.content - The content to write to the file
|
|
31
|
+
* @param {string} [params.encoding='utf8'] - The encoding to use when writing the file
|
|
32
|
+
* @param {boolean} [params.createDirectories=true] - Whether to create parent directories if they don't exist
|
|
33
|
+
* @returns {Promise<Object>} Object containing success status and file info or error
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeFile({ filePath, content, encoding, createDirectories, }: WriteFileParams): Promise<WriteFileResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Tool metadata for agent system
|
|
38
|
+
*/
|
|
39
|
+
export declare const writeFileTool: {
|
|
40
|
+
description: string;
|
|
41
|
+
parameters: z.ZodObject<{
|
|
42
|
+
filePath: z.ZodString;
|
|
43
|
+
content: z.ZodString;
|
|
44
|
+
encoding: z.ZodDefault<z.ZodEnum<["utf8", "ascii", "base64", "binary", "hex"]>>;
|
|
45
|
+
createDirectories: z.ZodDefault<z.ZodBoolean>;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
content?: string;
|
|
48
|
+
encoding?: "ascii" | "utf8" | "base64" | "binary" | "hex";
|
|
49
|
+
filePath?: string;
|
|
50
|
+
createDirectories?: boolean;
|
|
51
|
+
}, {
|
|
52
|
+
content?: string;
|
|
53
|
+
encoding?: "ascii" | "utf8" | "base64" | "binary" | "hex";
|
|
54
|
+
filePath?: string;
|
|
55
|
+
createDirectories?: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
execute: typeof writeFile;
|
|
58
|
+
};
|
|
59
|
+
export default writeFileTool;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { generateDiff } from "../utils/diffUtils.js";
|
|
5
|
+
/**
|
|
6
|
+
* Tool for writing content to files
|
|
7
|
+
* @param {Object} params - The parameters for writing a file
|
|
8
|
+
* @param {string} params.filePath - The path to the file to write
|
|
9
|
+
* @param {string} params.content - The content to write to the file
|
|
10
|
+
* @param {string} [params.encoding='utf8'] - The encoding to use when writing the file
|
|
11
|
+
* @param {boolean} [params.createDirectories=true] - Whether to create parent directories if they don't exist
|
|
12
|
+
* @returns {Promise<Object>} Object containing success status and file info or error
|
|
13
|
+
*/
|
|
14
|
+
export async function writeFile({ filePath, content, encoding = "utf8", createDirectories = true, }) {
|
|
15
|
+
try {
|
|
16
|
+
// Validate input
|
|
17
|
+
if (!filePath) {
|
|
18
|
+
return {
|
|
19
|
+
success: false,
|
|
20
|
+
error: "File path is required",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (content === undefined || content === null) {
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
error: "Content is required",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Resolve to absolute path
|
|
30
|
+
const absolutePath = path.resolve(filePath);
|
|
31
|
+
// Track whether the file existed before this write so undo can decide
|
|
32
|
+
// between restoring content vs deleting a newly created file.
|
|
33
|
+
let fileExisted = false;
|
|
34
|
+
// Try to read existing content for diff (may not exist for new files)
|
|
35
|
+
let oldContent = "";
|
|
36
|
+
try {
|
|
37
|
+
fileExisted = true;
|
|
38
|
+
oldContent = await fs.readFile(absolutePath, encoding);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// File doesn't exist yet - that's fine
|
|
42
|
+
}
|
|
43
|
+
// Create parent directories if needed
|
|
44
|
+
if (createDirectories) {
|
|
45
|
+
const dir = path.dirname(absolutePath);
|
|
46
|
+
await fs.mkdir(dir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
// Write file content
|
|
49
|
+
await fs.writeFile(absolutePath, content, encoding);
|
|
50
|
+
// Get file stats
|
|
51
|
+
const stats = await fs.stat(absolutePath);
|
|
52
|
+
// Generate diff for UI display (includes accurate line counts)
|
|
53
|
+
const { diff, linesAdded, linesRemoved } = generateDiff(oldContent, content, absolutePath);
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
filePath: absolutePath,
|
|
57
|
+
fileExisted,
|
|
58
|
+
size: stats.size,
|
|
59
|
+
created: stats.birthtime,
|
|
60
|
+
modified: stats.mtime,
|
|
61
|
+
linesAdded,
|
|
62
|
+
linesRemoved,
|
|
63
|
+
diff,
|
|
64
|
+
oldContent,
|
|
65
|
+
newContent: content,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const err = error;
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
error: err.message,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Tool metadata for agent system
|
|
78
|
+
*/
|
|
79
|
+
export const writeFileTool = {
|
|
80
|
+
description: "Write content to a file, creating it if it doesn't exist",
|
|
81
|
+
parameters: z.object({
|
|
82
|
+
filePath: z
|
|
83
|
+
.string()
|
|
84
|
+
.describe("The path to the file to write (can be relative or absolute)"),
|
|
85
|
+
content: z.string().describe("The content to write to the file"),
|
|
86
|
+
encoding: z
|
|
87
|
+
.enum(["utf8", "ascii", "base64", "binary", "hex"])
|
|
88
|
+
.default("utf8")
|
|
89
|
+
.describe("The encoding to use when writing the file"),
|
|
90
|
+
createDirectories: z
|
|
91
|
+
.boolean()
|
|
92
|
+
.default(true)
|
|
93
|
+
.describe("Whether to create parent directories if they don't exist"),
|
|
94
|
+
}),
|
|
95
|
+
execute: writeFile,
|
|
96
|
+
};
|
|
97
|
+
export default writeFileTool;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { WorkspaceMutationBridge } from "./workspaceMutationBridge.js";
|
|
2
|
+
type CapturedResult = {
|
|
3
|
+
toolCallId: string;
|
|
4
|
+
result: Record<string, unknown>;
|
|
5
|
+
};
|
|
6
|
+
export declare class CapturedWorkspaceMutationBridge extends WorkspaceMutationBridge {
|
|
7
|
+
private readonly processedResults;
|
|
8
|
+
takeProcessedResults(generationId: string, toolCallIds: string[]): CapturedResult[];
|
|
9
|
+
clearProcessedResults(generationId: string): void;
|
|
10
|
+
processToolResult(args: Parameters<WorkspaceMutationBridge["processToolResult"]>[0]): Promise<import("@mastra/core/agent").MessageList>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { WorkspaceMutationBridge } from "./workspaceMutationBridge.js";
|
|
2
|
+
import { redactToolResult } from "./toolResultSafetyProcessor.js";
|
|
3
|
+
const getGenerationId = (requestContext) => {
|
|
4
|
+
if (!requestContext || typeof requestContext !== "object")
|
|
5
|
+
return undefined;
|
|
6
|
+
const get = requestContext.get;
|
|
7
|
+
if (typeof get !== "function")
|
|
8
|
+
return undefined;
|
|
9
|
+
const value = get.call(requestContext, "workspaceMutationGenerationId");
|
|
10
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
11
|
+
};
|
|
12
|
+
export class CapturedWorkspaceMutationBridge extends WorkspaceMutationBridge {
|
|
13
|
+
processedResults = new Map();
|
|
14
|
+
takeProcessedResults(generationId, toolCallIds) {
|
|
15
|
+
const results = [];
|
|
16
|
+
const generationResults = this.processedResults.get(generationId);
|
|
17
|
+
if (!generationResults)
|
|
18
|
+
return results;
|
|
19
|
+
for (const toolCallId of toolCallIds) {
|
|
20
|
+
const result = generationResults.get(toolCallId);
|
|
21
|
+
if (!result)
|
|
22
|
+
continue;
|
|
23
|
+
generationResults.delete(toolCallId);
|
|
24
|
+
results.push({ toolCallId, result });
|
|
25
|
+
}
|
|
26
|
+
if (generationResults.size === 0) {
|
|
27
|
+
this.processedResults.delete(generationId);
|
|
28
|
+
}
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
31
|
+
clearProcessedResults(generationId) {
|
|
32
|
+
this.processedResults.delete(generationId);
|
|
33
|
+
}
|
|
34
|
+
async processToolResult(args) {
|
|
35
|
+
const messageList = args.messageList;
|
|
36
|
+
const generationId = getGenerationId(args.requestContext);
|
|
37
|
+
const originalUpdateToolInvocation = messageList.updateToolInvocation;
|
|
38
|
+
messageList.updateToolInvocation = (update) => {
|
|
39
|
+
const invocation = update.toolInvocation;
|
|
40
|
+
const sanitizedResult = redactToolResult(invocation?.result).result;
|
|
41
|
+
if (invocation?.state === "result" &&
|
|
42
|
+
typeof invocation.toolCallId === "string" &&
|
|
43
|
+
sanitizedResult &&
|
|
44
|
+
typeof sanitizedResult === "object" &&
|
|
45
|
+
generationId) {
|
|
46
|
+
let generationResults = this.processedResults.get(generationId);
|
|
47
|
+
if (!generationResults) {
|
|
48
|
+
generationResults = new Map();
|
|
49
|
+
this.processedResults.set(generationId, generationResults);
|
|
50
|
+
}
|
|
51
|
+
generationResults.set(invocation.toolCallId, sanitizedResult);
|
|
52
|
+
}
|
|
53
|
+
return originalUpdateToolInvocation.call(messageList, {
|
|
54
|
+
...update,
|
|
55
|
+
toolInvocation: invocation
|
|
56
|
+
? { ...invocation, result: sanitizedResult }
|
|
57
|
+
: invocation,
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
return await super.processToolResult(args);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
messageList.updateToolInvocation = originalUpdateToolInvocation;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type DiffResult = {
|
|
2
|
+
diff: string;
|
|
3
|
+
linesAdded: number;
|
|
4
|
+
linesRemoved: number;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Generate a unified diff string from old and new content
|
|
8
|
+
* @param {string} oldContent - The original content
|
|
9
|
+
* @param {string} newContent - The modified content
|
|
10
|
+
* @param {string} filePath - The file path (used for diff header)
|
|
11
|
+
* @returns {{diff: string, linesAdded: number, linesRemoved: number}} Diff result with accurate line counts
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateDiff(oldContent: string, newContent: string, filePath: string): DiffResult;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { diffLines } from "diff";
|
|
3
|
+
/**
|
|
4
|
+
* Generate a unified diff string from old and new content
|
|
5
|
+
* @param {string} oldContent - The original content
|
|
6
|
+
* @param {string} newContent - The modified content
|
|
7
|
+
* @param {string} filePath - The file path (used for diff header)
|
|
8
|
+
* @returns {{diff: string, linesAdded: number, linesRemoved: number}} Diff result with accurate line counts
|
|
9
|
+
*/
|
|
10
|
+
export function generateDiff(oldContent, newContent, filePath) {
|
|
11
|
+
const changes = diffLines(oldContent, newContent);
|
|
12
|
+
let diff = `--- a/${path.basename(filePath)}\n+++ b/${path.basename(filePath)}\n`;
|
|
13
|
+
let linesAdded = 0;
|
|
14
|
+
let linesRemoved = 0;
|
|
15
|
+
changes.forEach((part) => {
|
|
16
|
+
const lines = part.value
|
|
17
|
+
.split("\n")
|
|
18
|
+
.filter((line, idx, arr) => idx < arr.length - 1 || line !== "");
|
|
19
|
+
if (part.added) {
|
|
20
|
+
linesAdded += lines.length;
|
|
21
|
+
lines.forEach((line) => {
|
|
22
|
+
diff += `+${line}\n`;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else if (part.removed) {
|
|
26
|
+
linesRemoved += lines.length;
|
|
27
|
+
lines.forEach((line) => {
|
|
28
|
+
diff += `-${line}\n`;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Context lines (unchanged)
|
|
33
|
+
lines.forEach((line) => {
|
|
34
|
+
diff += ` ${line}\n`;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return { diff, linesAdded, linesRemoved };
|
|
39
|
+
}
|