@@ -19,6 +19,12 @@ export interface ExternalAgentResult {
1919 duration : number ;
2020}
2121
22+ export interface StreamEvent {
23+ type : string ;
24+ content ?: string ;
25+ data ?: any ;
26+ }
27+
2228/**
2329 * Base class for external agent integrations
2430 */
@@ -42,6 +48,11 @@ export abstract class BaseExternalAgent {
4248 */
4349 abstract execute ( prompt : string ) : Promise < ExternalAgentResult > ;
4450
51+ /**
52+ * Stream output from the external agent
53+ */
54+ abstract stream ( prompt : string ) : AsyncGenerator < StreamEvent , void , unknown > ;
55+
4556 /**
4657 * Get the agent name
4758 */
@@ -97,6 +108,47 @@ export abstract class BaseExternalAgent {
97108 } ) ;
98109 }
99110
111+ /**
112+ * Stream command output line by line
113+ */
114+ protected async * streamCommand ( args : string [ ] ) : AsyncGenerator < StreamEvent , void , unknown > {
115+ const { spawn } = await import ( 'child_process' ) ;
116+
117+ const proc = spawn ( this . config . command , args , {
118+ cwd : this . config . cwd || process . cwd ( ) ,
119+ env : { ...process . env , ...this . config . env } ,
120+ stdio : [ 'pipe' , 'pipe' , 'pipe' ]
121+ } ) ;
122+
123+ if ( ! proc . stdout ) {
124+ throw new Error ( 'Failed to create stdout stream' ) ;
125+ }
126+
127+ const readline = await import ( 'readline' ) ;
128+ const rl = readline . createInterface ( {
129+ input : proc . stdout ,
130+ crlfDelay : Infinity
131+ } ) ;
132+
133+ try {
134+ for await ( const line of rl ) {
135+ if ( line . trim ( ) ) {
136+ // Try to parse as JSON first
137+ try {
138+ const event = JSON . parse ( line ) ;
139+ yield { type : 'json' , data : event } ;
140+ } catch {
141+ // If not JSON, treat as text
142+ yield { type : 'text' , content : line } ;
143+ }
144+ }
145+ }
146+ } finally {
147+ rl . close ( ) ;
148+ proc . kill ( ) ;
149+ }
150+ }
151+
100152 /**
101153 * Check if a command exists
102154 */
@@ -131,6 +183,10 @@ export class ClaudeCodeAgent extends BaseExternalAgent {
131183 return this . runCommand ( [ '--print' , prompt ] ) ;
132184 }
133185
186+ async * stream ( prompt : string ) : AsyncGenerator < StreamEvent , void , unknown > {
187+ yield * this . streamCommand ( [ '--print' , '--output-format' , 'stream-json' , prompt ] ) ;
188+ }
189+
134190 async executeWithSession ( prompt : string , sessionId ?: string ) : Promise < ExternalAgentResult > {
135191 const args = [ '--print' ] ;
136192 if ( sessionId ) {
@@ -163,6 +219,10 @@ export class GeminiCliAgent extends BaseExternalAgent {
163219 async execute ( prompt : string ) : Promise < ExternalAgentResult > {
164220 return this . runCommand ( [ '-m' , this . model , prompt ] ) ;
165221 }
222+
223+ async * stream ( prompt : string ) : AsyncGenerator < StreamEvent , void , unknown > {
224+ yield * this . streamCommand ( [ '-m' , this . model , '--json' , prompt ] ) ;
225+ }
166226}
167227
168228/**
@@ -184,6 +244,10 @@ export class CodexCliAgent extends BaseExternalAgent {
184244 async execute ( prompt : string ) : Promise < ExternalAgentResult > {
185245 return this . runCommand ( [ 'exec' , '--full-auto' , prompt ] ) ;
186246 }
247+
248+ async * stream ( prompt : string ) : AsyncGenerator < StreamEvent , void , unknown > {
249+ yield * this . streamCommand ( [ 'exec' , '--full-auto' , '--json' , prompt ] ) ;
250+ }
187251}
188252
189253/**
@@ -205,6 +269,16 @@ export class AiderAgent extends BaseExternalAgent {
205269 async execute ( prompt : string ) : Promise < ExternalAgentResult > {
206270 return this . runCommand ( [ '--message' , prompt , '--yes' ] ) ;
207271 }
272+
273+ async * stream ( prompt : string ) : AsyncGenerator < StreamEvent , void , unknown > {
274+ // Aider doesn't support JSON streaming, so just yield text events
275+ const result = await this . execute ( prompt ) ;
276+ for ( const line of result . output . split ( '\n' ) ) {
277+ if ( line . trim ( ) ) {
278+ yield { type : 'text' , content : line } ;
279+ }
280+ }
281+ }
208282}
209283
210284/**
@@ -231,6 +305,16 @@ export class GenericExternalAgent extends BaseExternalAgent {
231305 }
232306 return this . runCommand ( args ) ;
233307 }
308+
309+ async * stream ( prompt : string ) : AsyncGenerator < StreamEvent , void , unknown > {
310+ const args = [ ...( this . config . args || [ ] ) ] ;
311+ if ( this . promptArg ) {
312+ args . push ( this . promptArg , prompt ) ;
313+ } else {
314+ args . push ( prompt ) ;
315+ }
316+ yield * this . streamCommand ( args ) ;
317+ }
234318}
235319
236320/**
0 commit comments