@@ -6,11 +6,38 @@ import ai.devchat.common.Settings
66import com.alibaba.fastjson.JSON
77import com.alibaba.fastjson.JSONArray
88import com.intellij.util.containers.addIfNotNull
9- import java.io.BufferedReader
9+ import kotlinx.coroutines.*
1010import java.io.IOException
1111
1212private const val DEFAULT_LOG_MAX_COUNT = 10000
1313
14+
15+ private suspend fun Process.await (
16+ onOutput : (String ) -> Unit ,
17+ onError : (String ) -> Unit
18+ ): Int = coroutineScope {
19+ launch(Dispatchers .IO ) {
20+ inputStream.bufferedReader().forEachLine { onOutput(it) }
21+ errorStream.bufferedReader().forEachLine { onError(it) }
22+ }
23+ val processExitCode = this @await.waitFor()
24+ processExitCode
25+ }
26+
27+ suspend fun executeCommand (
28+ command : List <String >,
29+ env : Map <String , String >,
30+ onOutputLine : (String ) -> Unit ,
31+ onErrorLine : (String ) -> Unit
32+ ): Int {
33+ val processBuilder = ProcessBuilder (command)
34+ env.forEach { (key, value) -> processBuilder.environment()[key] = value}
35+ val process = withContext(Dispatchers .IO ) {
36+ processBuilder.start()
37+ }
38+ return process.await(onOutputLine, onErrorLine)
39+ }
40+
1441class DevChatWrapper (
1542 private val command : String = DevChatPathUtil .devchatBinPath,
1643 private var apiBase : String? = null ,
@@ -26,10 +53,8 @@ class DevChatWrapper(
2653 }
2754 }
2855
29- private fun execCommand (commands : List <String >, callback : ((String ) -> Unit )? ): String? {
30- val pb = ProcessBuilder (commands)
31- val env = pb.environment()
32-
56+ private fun getEnv (): Map <String , String > {
57+ val env: MutableMap <String , String > = mutableMapOf ()
3358 apiBase?.let {
3459 env[" OPENAI_API_BASE" ] = it
3560 Log .info(" api_base: $it " )
@@ -38,36 +63,50 @@ class DevChatWrapper(
3863 env[" OPENAI_API_KEY" ] = it
3964 Log .info(" api_key: ${it.substring(0 , 5 )} ...${it.substring(it.length - 4 )} " )
4065 }
66+ return env
67+ }
68+
69+ private fun execCommand (commands : List <String >): String {
70+ Log .info(" Executing command: ${commands.joinToString(" " )} }" )
4171 return try {
42- Log .info(" Executing command: ${commands.joinToString(" " )} }" )
43- val process = pb.start()
44- val text = process.inputStream.bufferedReader().use { reader ->
45- callback?.let {
46- reader.forEachLine(it)
47- " "
48- } ? : reader.readText()
72+ val outputLines: MutableList <String > = mutableListOf ()
73+ val errorLines: MutableList <String > = mutableListOf ()
74+ val exitCode = runBlocking {
75+ executeCommand(commands, getEnv(), outputLines::add, errorLines::add)
4976 }
50- val errors = process.errorStream.bufferedReader().use(BufferedReader ::readText)
51- process.waitFor()
52- val exitCode = process.exitValue()
77+ val errors = errorLines.joinToString(" \n " )
5378
5479 if (exitCode != 0 ) {
55- Log .error(" Failed to execute command: $commands Exit Code: $exitCode Error: $errors " )
56- throw RuntimeException (
57- " Failed to execute command: $commands Exit Code: $exitCode Error: $errors "
58- )
80+ throw RuntimeException (" Command failure with exit Code: $exitCode , Errors: $errors " )
5981 } else {
60- text
82+ outputLines.joinToString( " \n " )
6183 }
6284 } catch (e: IOException ) {
63- Log .error(" Failed to execute command: $commands " )
64- throw RuntimeException (" Failed to execute command: $commands " , e)
65- } catch (e: InterruptedException ) {
66- Log .error(" Failed to execute command: $commands " )
85+ Log .error(" Failed to execute command: $commands , Exception: $e " )
6786 throw RuntimeException (" Failed to execute command: $commands " , e)
6887 }
6988 }
7089
90+ private fun execCommandAsync (
91+ commands : List <String >,
92+ onOutput : (String ) -> Unit ,
93+ onError : (String ) -> Unit = Log : :error
94+ ): Job {
95+ Log .info(" Executing command: ${commands.joinToString(" " )} }" )
96+ val exceptionHandler = CoroutineExceptionHandler { _, exception ->
97+ Log .error(" Failed to execute command: $commands , Exception: $exception " )
98+ throw RuntimeException (" Failed to execute command: $commands " , exception)
99+ }
100+ val cmdScope = CoroutineScope (SupervisorJob () + Dispatchers .Default )
101+
102+ return cmdScope.launch(exceptionHandler) {
103+ val exitCode = executeCommand(commands, getEnv(), onOutput, onError)
104+ if (exitCode != 0 ) {
105+ throw RuntimeException (" Command failure with exit Code: $exitCode " )
106+ }
107+ }
108+ }
109+
71110 val prompt: (MutableList <Pair <String , String ?>>, String , ((String ) -> Unit )? ) -> Unit get() = {
72111 flags: MutableList <Pair <String , String ?>>, message: String , callback: ((String ) -> Unit )? ->
73112 flags.addAll(listOf (" model" to currentModel, " " to message))
@@ -98,7 +137,7 @@ class DevChatWrapper(
98137 cmd.addIfNotNull(value)
99138 }
100139 return try {
101- execCommand (cmd, callback)
140+ callback?. let { execCommandAsync (cmd, callback); " " } ? : execCommand(cmd )
102141 } catch (e: Exception ) {
103142 Log .error(" Failed to run command $cmd : ${e.message} " )
104143 throw RuntimeException (" Failed to run command $cmd " , e)
@@ -114,7 +153,7 @@ class DevChatWrapper(
114153 cmd.addIfNotNull(value)
115154 }
116155 try {
117- execCommand (cmd, callback)
156+ callback?. let { execCommandAsync (cmd, callback); " " } ? : execCommand(cmd )
118157 } catch (e: Exception ) {
119158 Log .error(" Failed to run command $cmd : ${e.message} " )
120159 throw RuntimeException (" Failed to run command $cmd " , e)
0 commit comments