2
2
using System . Text ;
3
3
4
4
using AIShell . Abstraction ;
5
+ using AIShell . Kernel . Mcp ;
5
6
using Markdig . Helpers ;
6
7
using Microsoft . PowerShell ;
7
8
using Spectre . Console ;
9
+ using Spectre . Console . Json ;
10
+ using Spectre . Console . Rendering ;
8
11
9
12
namespace AIShell . Kernel ;
10
13
@@ -175,7 +178,7 @@ public void RenderFullResponse(string response)
175
178
/// <inheritdoc/>
176
179
public void RenderTable < T > ( IList < T > sources )
177
180
{
178
- RequireStdoutOrStderr ( operation : "render table" ) ;
181
+ RequireStdout ( operation : "render table" ) ;
179
182
ArgumentNullException . ThrowIfNull ( sources ) ;
180
183
181
184
if ( sources . Count is 0 )
@@ -198,7 +201,7 @@ public void RenderTable<T>(IList<T> sources)
198
201
/// <inheritdoc/>
199
202
public void RenderTable < T > ( IList < T > sources , IList < IRenderElement < T > > elements )
200
203
{
201
- RequireStdoutOrStderr ( operation : "render table" ) ;
204
+ RequireStdout ( operation : "render table" ) ;
202
205
203
206
ArgumentNullException . ThrowIfNull ( sources ) ;
204
207
ArgumentNullException . ThrowIfNull ( elements ) ;
@@ -240,7 +243,7 @@ public void RenderTable<T>(IList<T> sources, IList<IRenderElement<T>> elements)
240
243
/// <inheritdoc/>
241
244
public void RenderList < T > ( T source )
242
245
{
243
- RequireStdoutOrStderr ( operation : "render list" ) ;
246
+ RequireStdout ( operation : "render list" ) ;
244
247
ArgumentNullException . ThrowIfNull ( source ) ;
245
248
246
249
if ( source is IDictionary < string , string > dict )
@@ -271,7 +274,7 @@ public void RenderList<T>(T source)
271
274
/// <inheritdoc/>
272
275
public void RenderList < T > ( T source , IList < IRenderElement < T > > elements )
273
276
{
274
- RequireStdoutOrStderr ( operation : "render list" ) ;
277
+ RequireStdout ( operation : "render list" ) ;
275
278
276
279
ArgumentNullException . ThrowIfNull ( source ) ;
277
280
ArgumentNullException . ThrowIfNull ( elements ) ;
@@ -313,7 +316,7 @@ public void RenderList<T>(T source, IList<IRenderElement<T>> elements)
313
316
public void RenderDivider ( string text , DividerAlignment alignment )
314
317
{
315
318
ArgumentException . ThrowIfNullOrEmpty ( text ) ;
316
- RequireStdoutOrStderr ( operation : "render divider" ) ;
319
+ RequireStdout ( operation : "render divider" ) ;
317
320
318
321
if ( ! text . Contains ( "[/]" ) )
319
322
{
@@ -550,15 +553,134 @@ public string PromptForArgument(ArgumentInfo argInfo, bool printCaption)
550
553
internal void RenderReferenceText ( string header , string content )
551
554
{
552
555
RequireStdoutOrStderr ( operation : "Render reference" ) ;
556
+ IAnsiConsole ansiConsole = _outputRedirected ? _stderrConsole : AnsiConsole . Console ;
553
557
554
558
var panel = new Panel ( $ "\n [italic]{ content . EscapeMarkup ( ) } [/]\n ")
555
559
. RoundedBorder ( )
556
560
. BorderColor ( Color . DarkCyan )
557
561
. Header ( $ "[orange3 on italic] { header . Trim ( ) } [/]") ;
558
562
559
- AnsiConsole . WriteLine ( ) ;
560
- AnsiConsole . Write ( panel ) ;
561
- AnsiConsole . WriteLine ( ) ;
563
+ ansiConsole . WriteLine ( ) ;
564
+ ansiConsole . Write ( panel ) ;
565
+ ansiConsole . WriteLine ( ) ;
566
+ }
567
+
568
+ /// <summary>
569
+ /// Render the MCP tool call request.
570
+ /// </summary>
571
+ /// <param name="tool">The MCP tool.</param>
572
+ /// <param name="jsonArgs">The arguments in JSON form to be sent for the tool call.</param>
573
+ internal void RenderToolCallRequest ( McpTool tool , string jsonArgs )
574
+ {
575
+ RequireStdoutOrStderr ( operation : "render tool call request" ) ;
576
+ IAnsiConsole ansiConsole = _outputRedirected ? _stderrConsole : AnsiConsole . Console ;
577
+
578
+ bool hasArgs = ! string . IsNullOrEmpty ( jsonArgs ) ;
579
+ IRenderable content = new Markup ( $ """
580
+
581
+ [bold]Run [olive]{ tool . OriginalName } [/] from [olive]{ tool . ServerName } [/] (MCP server)[/]
582
+
583
+ { tool . Description }
584
+
585
+ Input:{ ( hasArgs ? string . Empty : " <none>" ) }
586
+ """ ) ;
587
+
588
+ if ( hasArgs )
589
+ {
590
+ var json = new JsonText ( jsonArgs )
591
+ . MemberColor ( Color . Aqua )
592
+ . ColonColor ( Color . White )
593
+ . CommaColor ( Color . White )
594
+ . StringStyle ( Color . Tan ) ;
595
+
596
+ content = new Grid ( )
597
+ . AddColumn ( new GridColumn ( ) )
598
+ . AddRow ( content )
599
+ . AddRow ( json ) ;
600
+ }
601
+
602
+ var panel = new Panel ( content )
603
+ . Expand ( )
604
+ . RoundedBorder ( )
605
+ . Header ( "[green] Tool Call Request [/]" )
606
+ . BorderColor ( Color . Grey ) ;
607
+
608
+ ansiConsole . WriteLine ( ) ;
609
+ ansiConsole . Write ( panel ) ;
610
+ FancyStreamRender . ConsoleUpdated ( ) ;
611
+ }
612
+
613
+ /// <summary>
614
+ /// Render a table with information about available MCP servers and tools.
615
+ /// </summary>
616
+ /// <param name="mcpManager">The MCP manager instance.</param>
617
+ internal void RenderMcpServersAndTools ( McpManager mcpManager )
618
+ {
619
+ RequireStdout ( operation : "render MCP servers and tools" ) ;
620
+
621
+ var toolTable = new Table ( )
622
+ . LeftAligned ( )
623
+ . SimpleBorder ( )
624
+ . BorderColor ( Color . Green ) ;
625
+
626
+ toolTable . AddColumn ( "[green bold]Server[/]" ) ;
627
+ toolTable . AddColumn ( "[green bold]Tool[/]" ) ;
628
+ toolTable . AddColumn ( "[green bold]Description[/]" ) ;
629
+
630
+ List < ( string name , string status , string info ) > readyServers = null , startingServers = null , failedServers = null ;
631
+ foreach ( var ( name , server ) in mcpManager . McpServers )
632
+ {
633
+ ( int code , string status , string info ) = server . IsInitFinished
634
+ ? server . Error is null
635
+ ? ( 1 , "[green]\u2713 Ready[/]" , string . Empty )
636
+ : ( - 1 , "[red]\u2717 Failed[/]" , $ "[red]{ server . Error . Message . EscapeMarkup ( ) } [/]")
637
+ : ( 0 , "[yellow]\u25CB Starting[/]" , string . Empty ) ;
638
+
639
+ var list = code switch
640
+ {
641
+ 1 => readyServers ??= [ ] ,
642
+ 0 => startingServers ??= [ ] ,
643
+ _ => failedServers ??= [ ] ,
644
+ } ;
645
+
646
+ list . Add ( ( name , status , info ) ) ;
647
+ }
648
+
649
+ if ( startingServers is not null )
650
+ {
651
+ foreach ( var ( name , status , info ) in startingServers )
652
+ {
653
+ toolTable . AddRow ( $ "[olive underline]{ name } [/]", status , info ) ;
654
+ }
655
+ }
656
+
657
+ if ( failedServers is not null )
658
+ {
659
+ foreach ( var ( name , status , info ) in failedServers )
660
+ {
661
+ toolTable . AddRow ( $ "[olive underline]{ name } [/]", status , info ) ;
662
+ }
663
+ }
664
+
665
+ if ( readyServers is not null )
666
+ {
667
+ foreach ( var ( name , status , info ) in readyServers )
668
+ {
669
+ if ( toolTable . Rows is { Count : > 0 } )
670
+ {
671
+ toolTable . AddEmptyRow ( ) ;
672
+ }
673
+
674
+ var server = mcpManager . McpServers [ name ] ;
675
+ toolTable . AddRow ( $ "[olive underline]{ name } [/]", status , info ) ;
676
+ foreach ( var item in server . Tools )
677
+ {
678
+ toolTable . AddRow ( string . Empty , item . Key . EscapeMarkup ( ) , item . Value . Description . EscapeMarkup ( ) ) ;
679
+ }
680
+ }
681
+ }
682
+
683
+ AnsiConsole . Write ( toolTable ) ;
562
684
}
563
685
564
686
private static Spinner GetSpinner ( SpinnerKind ? kind )
@@ -583,6 +705,19 @@ private void RequireStdin(string operation)
583
705
}
584
706
}
585
707
708
+ /// <summary>
709
+ /// Throw exception if standard output is redirected.
710
+ /// </summary>
711
+ /// <param name="operation">The intended operation.</param>
712
+ /// <exception cref="InvalidOperationException">Throw the exception if stdout is redirected.</exception>
713
+ private void RequireStdout ( string operation )
714
+ {
715
+ if ( _outputRedirected )
716
+ {
717
+ throw new InvalidOperationException ( $ "Cannot { operation } when the stdout is redirected.") ;
718
+ }
719
+ }
720
+
586
721
/// <summary>
587
722
/// Throw exception if both standard output and error are redirected.
588
723
/// </summary>
0 commit comments