Skip to content

Commit bbe9401

Browse files
authored
Merge pull request #475 from phodal/master
feat: add completion and devins lang support #471
2 parents 8c6d995 + 160e8fa commit bbe9401

File tree

84 files changed

+13177
-2157
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+13177
-2157
lines changed
Lines changed: 254 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,262 @@
11
package cc.unitmesh.agent.tool.impl
22

3+
import cc.unitmesh.codegraph.model.*
34
import cc.unitmesh.codegraph.parser.CodeParser
4-
import cc.unitmesh.codegraph.parser.ios.IosCodeParser
5+
import cc.unitmesh.codegraph.parser.Language
56

67
/**
7-
* Android implementation of CodeParser factory
8-
* Uses the same JVM-based implementation as regular JVM
8+
* Android implementation of CodeParser factory.
9+
*
10+
* Note: Android cannot access jvmMain code directly, so we provide a simplified
11+
* regex-based implementation similar to iOS. For full TreeSitter functionality,
12+
* consider using server-side parsing.
913
*/
1014
actual fun createCodeParser(): CodeParser {
11-
// Android uses JVM backend, but IosCodeParser is a fallback
12-
// In practice, we should use JvmCodeParser but it's not accessible from androidMain
13-
// For now, use the simplified iOS implementation
14-
return IosCodeParser()
15+
return AndroidCodeParser()
16+
}
17+
18+
/**
19+
* Simplified CodeParser for Android platform.
20+
* Uses regex-based parsing to extract basic code structure information.
21+
*/
22+
private class AndroidCodeParser : CodeParser {
23+
24+
override suspend fun parseNodes(
25+
sourceCode: String,
26+
filePath: String,
27+
language: Language
28+
): List<CodeNode> {
29+
return when (language) {
30+
Language.JAVA, Language.KOTLIN -> parseOOPNodes(sourceCode, filePath, language)
31+
Language.JAVASCRIPT, Language.TYPESCRIPT -> parseJSNodes(sourceCode, filePath, language)
32+
Language.PYTHON -> parsePythonNodes(sourceCode, filePath, language)
33+
else -> emptyList()
34+
}
35+
}
36+
37+
override suspend fun parseNodesAndRelationships(
38+
sourceCode: String,
39+
filePath: String,
40+
language: Language
41+
): Pair<List<CodeNode>, List<CodeRelationship>> {
42+
val nodes = parseNodes(sourceCode, filePath, language)
43+
val relationships = buildRelationships(nodes)
44+
return Pair(nodes, relationships)
45+
}
46+
47+
override suspend fun parseCodeGraph(
48+
files: Map<String, String>,
49+
language: Language
50+
): CodeGraph {
51+
val allNodes = mutableListOf<CodeNode>()
52+
val allRelationships = mutableListOf<CodeRelationship>()
53+
54+
for ((filePath, sourceCode) in files) {
55+
val (nodes, relationships) = parseNodesAndRelationships(sourceCode, filePath, language)
56+
allNodes.addAll(nodes)
57+
allRelationships.addAll(relationships)
58+
}
59+
60+
return CodeGraph(
61+
nodes = allNodes,
62+
relationships = allRelationships,
63+
metadata = mapOf(
64+
"language" to language.name,
65+
"fileCount" to files.size.toString(),
66+
"platform" to "Android"
67+
)
68+
)
69+
}
70+
71+
override suspend fun parseImports(
72+
sourceCode: String,
73+
filePath: String,
74+
language: Language
75+
): List<ImportInfo> {
76+
return when (language) {
77+
Language.JAVA, Language.KOTLIN -> extractJvmImports(sourceCode, filePath)
78+
Language.PYTHON -> extractPythonImports(sourceCode, filePath)
79+
Language.JAVASCRIPT, Language.TYPESCRIPT -> extractJsImports(sourceCode, filePath)
80+
else -> emptyList()
81+
}
82+
}
83+
84+
private fun extractJvmImports(content: String, filePath: String): List<ImportInfo> {
85+
val importRegex = Regex("""import\s+(static\s+)?([a-zA-Z_][\w.]*[\w*])""")
86+
return importRegex.findAll(content).map { match ->
87+
// Calculate actual line number from match position
88+
val lineNumber = content.substring(0, match.range.first).count { it == '\n' } + 1
89+
ImportInfo(
90+
path = match.groupValues[2].removeSuffix(".*"),
91+
type = ImportType.MODULE,
92+
filePath = filePath,
93+
startLine = lineNumber,
94+
endLine = lineNumber
95+
)
96+
}.toList()
97+
}
98+
99+
private fun extractPythonImports(content: String, filePath: String): List<ImportInfo> {
100+
val imports = mutableListOf<ImportInfo>()
101+
102+
val fromImportRegex = Regex("""from\s+([\w.]+)\s+import""")
103+
fromImportRegex.findAll(content).forEach { match ->
104+
// Calculate actual line number from match position
105+
val lineNumber = content.substring(0, match.range.first).count { it == '\n' } + 1
106+
imports.add(ImportInfo(
107+
path = match.groupValues[1],
108+
type = ImportType.MODULE,
109+
filePath = filePath,
110+
startLine = lineNumber,
111+
endLine = lineNumber
112+
))
113+
}
114+
115+
val importRegex = Regex("""^import\s+([\w.]+)""", RegexOption.MULTILINE)
116+
importRegex.findAll(content).forEach { match ->
117+
// Calculate actual line number from match position
118+
val lineNumber = content.substring(0, match.range.first).count { it == '\n' } + 1
119+
imports.add(ImportInfo(
120+
path = match.groupValues[1],
121+
type = ImportType.MODULE,
122+
filePath = filePath,
123+
startLine = lineNumber,
124+
endLine = lineNumber
125+
))
126+
}
127+
128+
return imports
129+
}
130+
131+
private fun extractJsImports(content: String, filePath: String): List<ImportInfo> {
132+
val imports = mutableListOf<ImportInfo>()
133+
134+
val es6ImportRegex = Regex("""import\s+(?:.+\s+from\s+)?['"]([@\w./-]+)['"]""")
135+
es6ImportRegex.findAll(content).forEach { match ->
136+
// Calculate actual line number from match position
137+
val lineNumber = content.substring(0, match.range.first).count { it == '\n' } + 1
138+
imports.add(ImportInfo(
139+
path = match.groupValues[1],
140+
type = ImportType.MODULE,
141+
filePath = filePath,
142+
startLine = lineNumber,
143+
endLine = lineNumber
144+
))
145+
}
146+
147+
return imports
148+
}
149+
150+
private fun parseOOPNodes(
151+
sourceCode: String,
152+
filePath: String,
153+
language: Language
154+
): List<CodeNode> {
155+
val nodes = mutableListOf<CodeNode>()
156+
val lines = sourceCode.lines()
157+
val packageName = extractPackageName(sourceCode)
158+
159+
val classPattern = Regex("""(class|interface|enum|object)\s+(\w+)""")
160+
161+
for ((index, line) in lines.withIndex()) {
162+
val currentLine = index + 1
163+
164+
classPattern.find(line)?.let { match ->
165+
val type = when (match.groupValues[1]) {
166+
"class", "object" -> CodeElementType.CLASS
167+
"interface" -> CodeElementType.INTERFACE
168+
"enum" -> CodeElementType.ENUM
169+
else -> CodeElementType.CLASS
170+
}
171+
val name = match.groupValues[2]
172+
nodes.add(createCodeNode(name, type, packageName, filePath, currentLine, language))
173+
}
174+
}
175+
176+
return nodes
177+
}
178+
179+
private fun parseJSNodes(
180+
sourceCode: String,
181+
filePath: String,
182+
language: Language
183+
): List<CodeNode> {
184+
val nodes = mutableListOf<CodeNode>()
185+
val lines = sourceCode.lines()
186+
187+
val classPattern = Regex("""class\s+(\w+)""")
188+
189+
for ((index, line) in lines.withIndex()) {
190+
val currentLine = index + 1
191+
192+
classPattern.find(line)?.let { match ->
193+
val name = match.groupValues[1]
194+
nodes.add(createCodeNode(name, CodeElementType.CLASS, "", filePath, currentLine, language))
195+
}
196+
}
197+
198+
return nodes
199+
}
200+
201+
private fun parsePythonNodes(
202+
sourceCode: String,
203+
filePath: String,
204+
language: Language
205+
): List<CodeNode> {
206+
val nodes = mutableListOf<CodeNode>()
207+
val lines = sourceCode.lines()
208+
209+
val classPattern = Regex("""class\s+(\w+)""")
210+
211+
for ((index, line) in lines.withIndex()) {
212+
val currentLine = index + 1
213+
214+
classPattern.find(line)?.let { match ->
215+
val name = match.groupValues[1]
216+
nodes.add(createCodeNode(name, CodeElementType.CLASS, "", filePath, currentLine, language))
217+
}
218+
}
219+
220+
return nodes
221+
}
222+
223+
private fun extractPackageName(sourceCode: String): String {
224+
val packagePattern = Regex("""package\s+([\w.]+)""")
225+
return packagePattern.find(sourceCode)?.groupValues?.get(1) ?: ""
226+
}
227+
228+
private fun createCodeNode(
229+
name: String,
230+
type: CodeElementType,
231+
packageName: String,
232+
filePath: String,
233+
startLine: Int,
234+
language: Language
235+
): CodeNode {
236+
val qualifiedName = if (packageName.isNotEmpty()) "$packageName.$name" else name
237+
// Use deterministic composite ID to avoid collisions
238+
val id = "$filePath:$startLine:$qualifiedName"
239+
240+
return CodeNode(
241+
id = id,
242+
type = type,
243+
name = name,
244+
packageName = packageName,
245+
filePath = filePath,
246+
startLine = startLine,
247+
// Approximate end line: regex parsing cannot determine actual end line,
248+
// so we use a reasonable default. For accurate end lines, use TreeSitter-based parsing.
249+
endLine = startLine + 10,
250+
startColumn = 0,
251+
endColumn = 0,
252+
qualifiedName = qualifiedName,
253+
content = "",
254+
metadata = mapOf("language" to language.name, "platform" to "Android")
255+
)
256+
}
257+
258+
private fun buildRelationships(nodes: List<CodeNode>): List<CodeRelationship> {
259+
// Simplified: no relationships for basic parsing
260+
return emptyList()
261+
}
15262
}

mpp-core/src/commonMain/kotlin/cc/unitmesh/agent/document/DocumentAgent.kt

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,32 @@ import cc.unitmesh.agent.tool.registry.ToolRegistry
1717
import cc.unitmesh.agent.tool.schema.AgentToolFormatter
1818
import cc.unitmesh.agent.tool.shell.DefaultShellExecutor
1919
import cc.unitmesh.agent.tool.shell.ShellExecutor
20+
import cc.unitmesh.devins.compiler.template.TemplateCompiler
21+
import cc.unitmesh.devins.compiler.variable.VariableTable
2022
import cc.unitmesh.devins.document.DocumentParserService
2123
import cc.unitmesh.llm.KoogLLMService
2224
import kotlinx.coroutines.CoroutineScope
2325
import kotlinx.coroutines.Dispatchers
2426
import kotlinx.coroutines.SupervisorJob
2527
import kotlinx.coroutines.launch
28+
import kotlinx.datetime.Clock
2629

2730
/**
28-
* Document Task - represents a user query about a document
31+
* Document Agent Mode - determines the agent's behavior
32+
*/
33+
enum class DocumentAgentMode {
34+
DOCUMENT_QUERY, // Original document query mode using DocQL
35+
FEATURE_TREE // Product feature tree generation mode
36+
}
37+
38+
/**
39+
* Document Task - represents a user query about a document or feature tree request
2940
*/
3041
data class DocumentTask(
3142
val query: String,
32-
val documentPath: String? = null
43+
val documentPath: String? = null,
44+
val mode: DocumentAgentMode = DocumentAgentMode.DOCUMENT_QUERY,
45+
val language: String = "EN"
3346
)
3447

3548
/**
@@ -154,11 +167,35 @@ class DocumentAgent(
154167
private fun buildContext(task: DocumentTask): DocumentContext {
155168
return DocumentContext(
156169
query = task.query,
157-
documentPath = task.documentPath
170+
documentPath = task.documentPath,
171+
mode = task.mode,
172+
language = task.language
158173
)
159174
}
160175

161176
private fun buildSystemPrompt(context: DocumentContext): String {
177+
return when (context.mode) {
178+
DocumentAgentMode.FEATURE_TREE -> buildFeatureTreePrompt(context)
179+
DocumentAgentMode.DOCUMENT_QUERY -> buildDocumentQueryPrompt(context)
180+
}
181+
}
182+
183+
private fun buildFeatureTreePrompt(context: DocumentContext): String {
184+
val template = when (context.language.uppercase()) {
185+
"ZH", "CN" -> ProductFeatureTreeTemplate.ZH
186+
else -> ProductFeatureTreeTemplate.EN
187+
}
188+
189+
val variableTable = VariableTable()
190+
variableTable.addVariable("projectPath", cc.unitmesh.devins.compiler.variable.VariableType.STRING, actualFileSystem.getProjectPath() ?: ".")
191+
variableTable.addVariable("timestamp", cc.unitmesh.devins.compiler.variable.VariableType.STRING, Clock.System.now().toString())
192+
variableTable.addVariable("toolList", cc.unitmesh.devins.compiler.variable.VariableType.STRING, AgentToolFormatter.formatToolListForAI(toolRegistry.getAllTools().values.toList()))
193+
194+
val compiler = TemplateCompiler(variableTable)
195+
return compiler.compile(template)
196+
}
197+
198+
private fun buildDocumentQueryPrompt(context: DocumentContext): String {
162199
return """ You are a Code-First Project Research Assistant.
163200
Your job is to answer developer questions based on the source code (should be exist can be run by DocQL) and project documentation.
164201
DocQL Tool supports structured code search using a TreeSitter parser.
@@ -270,6 +307,8 @@ ${AgentToolFormatter.formatToolListForAI(toolRegistry.getAllTools().values.toLis
270307

271308
data class DocumentContext(
272309
val query: String,
273-
val documentPath: String?
310+
val documentPath: String?,
311+
val mode: DocumentAgentMode = DocumentAgentMode.DOCUMENT_QUERY,
312+
val language: String = "EN"
274313
)
275314
}

0 commit comments

Comments
 (0)