@@ -9,7 +9,7 @@ public final class TerminalChatPlugin: ChatPlugin {
99 public static var name : String { " Terminal " }
1010 public static var description : String { """
1111 Run the command in the message from terminal.
12-
12+
1313 You can use environment variable `$FILE_PATH` and `$PROJECT_ROOT` to access the current file path and project root.
1414 """ }
1515
@@ -23,41 +23,11 @@ public final class TerminalChatPlugin: ChatPlugin {
2323 terminal = Terminal ( )
2424 }
2525
26- public func formatContent( _ content: Response . Content ) -> Response . Content {
27- switch content {
28- case let . text( content) :
29- return . text( """
30- ```sh
31- \( content)
32- ```
33- """ )
34- }
35- }
36-
37- public func send( _ request: Request ) async -> AsyncThrowingStream < Response , any Error > {
26+ public func getTextContent( from request: Request ) async
27+ -> AsyncStream < String >
28+ {
3829 return . init { continuation in
3930 let task = Task {
40- var updateTime = Date ( )
41-
42- func streamOutput( _ content: String ) {
43- defer { updateTime = Date ( ) }
44- if Date ( ) . timeIntervalSince ( updateTime) > 60 * 2 {
45- continuation. yield ( . startNewMessage)
46- continuation. yield ( . startAction(
47- id: " run " ,
48- task: " Continue ` \( request. text) ` "
49- ) )
50- continuation. yield ( . finishAction(
51- id: " run " ,
52- result: . success( " Executed. " )
53- ) )
54- continuation. yield ( . content( . text( " [continue] \n " ) ) )
55- continuation. yield ( . content( . text( content) ) )
56- } else {
57- continuation. yield ( . content( . text( content) ) )
58- }
59- }
60-
6131 do {
6232 let fileURL = XcodeInspector . shared. realtimeActiveDocumentURL
6333 let projectURL = XcodeInspector . shared. realtimeActiveProjectURL
@@ -75,34 +45,25 @@ public final class TerminalChatPlugin: ChatPlugin {
7545 let env = ProcessInfo . processInfo. environment
7646 let shell = env [ " SHELL " ] ?? " /bin/bash "
7747
78- continuation. yield ( . startAction( id: " run " , task: " Run ` \( request. text) ` " ) )
79-
8048 let output = terminal. streamCommand (
8149 shell,
8250 arguments: [ " -i " , " -l " , " -c " , request. text] ,
8351 currentDirectoryURL: projectURL,
8452 environment: environment
8553 )
8654
87- continuation. yield ( . finishAction(
88- id: " run " ,
89- result: . success( " Executed. " )
90- ) )
91-
55+ var accumulatedOutput = " "
9256 for try await content in output {
9357 try Task . checkCancellation ( )
94- streamOutput ( content)
58+ accumulatedOutput += content
59+ continuation. yield ( accumulatedOutput)
9560 }
9661 } catch let error as Terminal . TerminationError {
97- continuation. yield ( . content( . text( """
98-
99- [error: \( error. reason) ]
100- """ ) ) )
62+ let errorMessage = " \n \n [error: \( error. reason) ] "
63+ continuation. yield ( errorMessage)
10164 } catch {
102- continuation. yield ( . content( . text( """
103-
104- [error: \( error. localizedDescription) ]
105- """ ) ) )
65+ let errorMessage = " \n \n [error: \( error. localizedDescription) ] "
66+ continuation. yield ( errorMessage)
10667 }
10768
10869 continuation. finish ( )
@@ -116,5 +77,89 @@ public final class TerminalChatPlugin: ChatPlugin {
11677 }
11778 }
11879 }
80+
81+ public func sendForTextResponse( _ request: Request ) async
82+ -> AsyncThrowingStream < String , any Error >
83+ {
84+ let stream = await getTextContent ( from: request)
85+ return . init { continuation in
86+ let task = Task {
87+ continuation. yield ( " Executing command: ` \( request. text) ` \n \n " )
88+ continuation. yield ( " ```console \n " )
89+ for await text in stream {
90+ try Task . checkCancellation ( )
91+ continuation. yield ( text)
92+ }
93+ continuation. yield ( " \n ``` \n " )
94+ continuation. finish ( )
95+ }
96+
97+ continuation. onTermination = { _ in
98+ task. cancel ( )
99+ }
100+ }
101+ }
102+
103+ public func formatContent( _ content: Response . Content ) -> Response . Content {
104+ switch content {
105+ case let . text( content) :
106+ return . text( """
107+ ```console
108+ \( content)
109+ ```
110+ """ )
111+ }
112+ }
113+
114+ public func sendForComplicatedResponse( _ request: Request ) async
115+ -> AsyncThrowingStream < Response , any Error >
116+ {
117+ return . init { continuation in
118+ let task = Task {
119+ var updateTime = Date ( )
120+
121+ continuation. yield ( . startAction( id: " run " , task: " Run ` \( request. text) ` " ) )
122+
123+ let textStream = await getTextContent ( from: request)
124+ var previousOutput = " "
125+
126+ continuation. yield ( . finishAction(
127+ id: " run " ,
128+ result: . success( " Executed. " )
129+ ) )
130+
131+ for await accumulatedOutput in textStream {
132+ try Task . checkCancellation ( )
133+
134+ let newContent = accumulatedOutput. dropFirst ( previousOutput. count)
135+ previousOutput = accumulatedOutput
136+
137+ if !newContent. isEmpty {
138+ if Date ( ) . timeIntervalSince ( updateTime) > 60 * 2 {
139+ continuation. yield ( . startNewMessage)
140+ continuation. yield ( . startAction(
141+ id: " run " ,
142+ task: " Continue ` \( request. text) ` "
143+ ) )
144+ continuation. yield ( . finishAction(
145+ id: " run " ,
146+ result: . success( " Executed. " )
147+ ) )
148+ continuation. yield ( . content( . text( " [continue] \n " ) ) )
149+ updateTime = Date ( )
150+ }
151+
152+ continuation. yield ( . content( . text( String ( newContent) ) ) )
153+ }
154+ }
155+
156+ continuation. finish ( )
157+ }
158+
159+ continuation. onTermination = { _ in
160+ task. cancel ( )
161+ }
162+ }
163+ }
119164}
120165
0 commit comments