44
55namespace Cortex \Console ;
66
7- use Throwable ;
87use Cortex \Cortex ;
98use Cortex \Agents \Agent ;
109use InvalidArgumentException ;
11- use Cortex \LLM \Enums \ChunkType ;
1210use Illuminate \Console \Command ;
1311use Cortex \Facades \AgentRegistry ;
14- use Cortex \LLM \Data \ChatGenerationChunk ;
15- use Cortex \LLM \Data \Messages \UserMessage ;
12+
13+ use function Laravel \Prompts \info ;
14+ use function Laravel \Prompts \outro ;
15+ use function Laravel \Prompts \table ;
16+ use function Laravel \Prompts \error as promptsError ;
1617
1718class AgentChat extends Command
1819{
@@ -21,7 +22,7 @@ class AgentChat extends Command
2122 *
2223 * @var string
2324 */
24- protected $ signature = 'cortex:chat {agent : The name of the agent to chat with} ' ;
25+ protected $ signature = 'cortex:chat {agent : The name of the agent to chat with} {--debug : Show debug information including tool calls} ' ;
2526
2627 /**
2728 * The console command description.
@@ -41,204 +42,32 @@ public function handle(): int
4142 return self ::FAILURE ;
4243 }
4344
44- $ this ->info ('Chatting with agent: ' . $ agent ->getName ());
45- $ this ->line ("Type 'exit' or 'quit' to end the conversation. \n" );
46-
47- while (true ) {
48- $ userInput = $ this ->ask ('You ' );
49-
50- if ($ userInput === null || in_array (strtolower (trim ($ userInput )), ['exit ' , 'quit ' , 'q ' ], true )) {
51- $ this ->info ('Goodbye! ' );
52- break ;
53- }
54-
55- if (trim ($ userInput ) === '' ) {
56- continue ;
57- }
45+ // Create and start the TUI
46+ $ chatPrompt = new ChatPrompt ($ agent , $ this ->option ('debug ' ));
47+ $ chatPrompt ->prompt ();
5848
59- try {
60- $ this ->line ("\nAgent: " );
61- $ this ->streamAgentResponse ($ agent , new UserMessage ($ userInput ));
62- $ this ->newLine ();
63- } catch (Throwable $ e ) {
64- $ this ->renderErrorMessage ($ e ->getMessage ());
65-
66- return self ::FAILURE ;
67- }
68- }
49+ outro ('Goodbye! ' );
6950
7051 return self ::SUCCESS ;
7152 }
7253
73- /**
74- * Stream the agent's response and display it in real-time.
75- */
76- protected function streamAgentResponse (Agent $ agent , UserMessage $ userMessage ): void
77- {
78- $ result = $ agent ->stream (messages: [$ userMessage ]);
79-
80- $ lastContentLength = 0 ;
81-
82- foreach ($ result as $ chunk ) {
83- // Handle different chunk types
84- match ($ chunk ->type ) {
85- ChunkType::TextDelta => $ this ->handleTextDelta ($ chunk , $ lastContentLength ),
86- ChunkType::TextStart => $ this ->handleTextStart (),
87- ChunkType::TextEnd => $ this ->handleTextEnd (),
88- ChunkType::ToolInputStart => $ this ->handleToolInputStart ($ chunk ),
89- ChunkType::ToolInputEnd => $ this ->handleToolInputEnd ($ chunk ),
90- ChunkType::ToolOutputEnd => $ this ->handleToolOutputEnd ($ chunk ),
91- ChunkType::StepStart => $ this ->handleStepStart (),
92- ChunkType::StepEnd => $ this ->handleStepEnd (),
93- ChunkType::RunStart => $ this ->handleRunStart (),
94- ChunkType::RunEnd => $ this ->handleRunEnd (),
95- ChunkType::Error => $ this ->handleError ($ chunk ),
96- default => null ,
97- };
98-
99- // Update last displayed length from contentSoFar for final chunks
100- if ($ chunk ->isFinal && $ chunk ->contentSoFar !== '' ) {
101- $ lastContentLength = strlen ($ chunk ->contentSoFar );
102- }
103- }
104- }
105-
106- /**
107- * Handle text delta chunks - display incremental text updates.
108- */
109- protected function handleTextDelta (ChatGenerationChunk $ chunk , int &$ lastContentLength ): void
110- {
111- // Use contentSoFar to get the cumulative text and display only the new part
112- if ($ chunk ->contentSoFar !== '' ) {
113- $ newText = substr ($ chunk ->contentSoFar , $ lastContentLength );
114-
115- if ($ newText !== '' ) {
116- $ this ->output ->write ($ newText );
117- $ lastContentLength = strlen ($ chunk ->contentSoFar );
118- }
119- } else {
120- // Fallback to text() if contentSoFar is not available
121- $ text = $ chunk ->text ();
122-
123- if ($ text !== null && $ text !== '' ) {
124- $ this ->output ->write ($ text );
125- }
126- }
127- }
128-
129- /**
130- * Handle text start chunks.
131- */
132- protected function handleTextStart (): void
133- {
134- // Text start - no visual output needed
135- }
136-
137- /**
138- * Handle text end chunks.
139- */
140- protected function handleTextEnd (): void
141- {
142- // Text end - ensure newline
143- $ this ->newLine ();
144- }
145-
146- /**
147- * Handle tool input start chunks.
148- */
149- protected function handleToolInputStart (ChatGenerationChunk $ chunk ): void
150- {
151- $ toolCalls = $ chunk ->message ->toolCalls ;
152-
153- if ($ toolCalls === null || $ toolCalls ->isEmpty ()) {
154- return ;
155- }
156-
157- $ this ->newLine ();
158- $ this ->line ('<fg=cyan>🔧 Calling tools:</fg=cyan> ' );
159- foreach ($ toolCalls as $ toolCall ) {
160- $ this ->line (sprintf (' - <fg=yellow>%s</fg=yellow> ' , $ toolCall ->function ->name ));
161- }
162- }
163-
164- /**
165- * Handle tool input end chunks.
166- */
167- protected function handleToolInputEnd (ChatGenerationChunk $ chunk ): void
168- {
169- // Tool input complete - already displayed in start
170- }
171-
172- /**
173- * Handle tool output end chunks.
174- */
175- protected function handleToolOutputEnd (ChatGenerationChunk $ chunk ): void
176- {
177- $ this ->line ('<fg=green>✓ Tool execution complete</fg=green> ' );
178- }
179-
180- /**
181- * Handle step start chunks.
182- */
183- protected function handleStepStart (): void
184- {
185- // Step start - no visual output needed for now
186- }
187-
188- /**
189- * Handle step end chunks.
190- */
191- protected function handleStepEnd (): void
192- {
193- // Step end - no visual output needed
194- }
195-
196- /**
197- * Handle run start chunks.
198- */
199- protected function handleRunStart (): void
200- {
201- // Run start - no visual output needed
202- }
203-
204- /**
205- * Handle run end chunks.
206- */
207- protected function handleRunEnd (): void
208- {
209- // Run end - no visual output needed
210- }
211-
212- /**
213- * Handle error chunks.
214- */
215- protected function handleError (ChatGenerationChunk $ chunk ): void
216- {
217- $ this ->renderErrorMessage ($ chunk ->exception ?->getMessage() ?? 'An error occurred ' );
218- }
219-
220- protected function renderErrorMessage (string $ message ): void
221- {
222- $ this ->newLine ();
223- $ this ->error ('Error: ' . $ message );
224- }
225-
22654 protected function getAgent (): ?Agent
22755 {
22856 $ agentName = $ this ->argument ('agent ' );
22957
23058 try {
23159 return Cortex::agent ($ agentName );
23260 } catch (InvalidArgumentException ) {
233- $ this -> error (sprintf ("Agent '%s' not found in registry. " , $ agentName ));
61+ promptsError (sprintf ("Agent '%s' not found in registry. " , $ agentName ));
23462
23563 $ availableAgents = AgentRegistry::names ();
23664
23765 if (! empty ($ availableAgents )) {
238- $ this ->info ('Available agents: ' );
239- foreach ($ availableAgents as $ name ) {
240- $ this ->line (' - ' . $ name );
241- }
66+ info ('Available agents: ' );
67+ table (
68+ headers: ['Name ' ],
69+ rows: array_map (fn (string $ name ): array => [$ name ], $ availableAgents ),
70+ );
24271 }
24372
24473 return null ;
0 commit comments