11using System . ClientModel . Primitives ;
22using System . ComponentModel ;
3+ using System . Runtime . CompilerServices ;
34using Devlooped . Extensions . AI ;
45using Spectre . Console ;
5- using Spectre . Console . Json ;
66
77namespace Microsoft . Extensions . AI ;
88
@@ -17,41 +17,28 @@ public static class JsonConsoleLoggingExtensions
1717 /// console using Spectre.Console rich JSON formatting, but only if the console is interactive.
1818 /// </summary>
1919 /// <typeparam name="TOptions">The options type to configure for HTTP logging.</typeparam>
20- /// <param name="options ">The options instance to configure.</param>
20+ /// <param name="pipelineOptions ">The options instance to configure.</param>
2121 /// <remarks>
2222 /// NOTE: this is the lowst-level logging after all chat pipeline processing has been done.
2323 /// <para>
2424 /// If the options already provide a transport, it will be wrapped with the console
2525 /// logging transport to minimize the impact on existing configurations.
2626 /// </para>
2727 /// </remarks>
28- public static TOptions UseJsonConsoleLogging < TOptions > ( this TOptions options )
29- where TOptions : ClientPipelineOptions
30- => UseJsonConsoleLogging ( options , ConsoleExtensions . IsConsoleInteractive ) ;
31-
32- /// <summary>
33- /// Sets a <see cref="ClientPipelineOptions.Transport"/> that renders HTTP messages to the
34- /// console using Spectre.Console rich JSON formatting.
35- /// </summary>
36- /// <typeparam name="TOptions">The options type to configure for HTTP logging.</typeparam>
37- /// <param name="options">The options instance to configure.</param>
38- /// <param name="askConfirmation">Whether to confirm logging before enabling it.</param>
39- /// <remarks>
40- /// NOTE: this is the lowst-level logging after all chat pipeline processing has been done.
41- /// <para>
42- /// If the options already provide a transport, it will be wrapped with the console
43- /// logging transport to minimize the impact on existing configurations.
44- /// </para>
45- /// </remarks>
46- public static TOptions UseJsonConsoleLogging < TOptions > ( this TOptions options , bool askConfirmation )
28+ public static TOptions UseJsonConsoleLogging < TOptions > ( this TOptions pipelineOptions , JsonConsoleOptions ? consoleOptions = null )
4729 where TOptions : ClientPipelineOptions
4830 {
49- if ( askConfirmation && ! AnsiConsole . Confirm ( "Do you want to enable rich JSON console logging for HTTP pipeline messages?" ) )
50- return options ;
31+ consoleOptions ??= JsonConsoleOptions . Default ;
32+
33+ if ( consoleOptions . InteractiveConfirm && ConsoleExtensions . IsConsoleInteractive && ! AnsiConsole . Confirm ( "Do you want to enable rich JSON console logging for HTTP pipeline messages?" ) )
34+ return pipelineOptions ;
35+
36+ if ( consoleOptions . InteractiveOnly && ! ConsoleExtensions . IsConsoleInteractive )
37+ return pipelineOptions ;
5138
52- options . Transport = new ConsoleLoggingPipelineTransport ( options . Transport ?? HttpClientPipelineTransport . Shared ) ;
39+ pipelineOptions . Transport = new ConsoleLoggingPipelineTransport ( pipelineOptions . Transport ?? HttpClientPipelineTransport . Shared , consoleOptions ) ;
5340
54- return options ;
41+ return pipelineOptions ;
5542 }
5643
5744 /// <summary>
@@ -62,36 +49,24 @@ public static TOptions UseJsonConsoleLogging<TOptions>(this TOptions options, bo
6249 /// Confirmation will be asked if the console is interactive, otherwise, it will be
6350 /// enabled unconditionally.
6451 /// </remarks>
65- public static ChatClientBuilder UseJsonConsoleLogging ( this ChatClientBuilder builder )
66- => UseJsonConsoleLogging ( builder , ConsoleExtensions . IsConsoleInteractive ) ;
67-
68- /// <summary>
69- /// Renders chat messages and responses to the console using Spectre.Console rich JSON formatting.
70- /// </summary>
71- /// <param name="builder">The builder in use.</param>
72- /// <param name="askConfirmation">If true, prompts the user for confirmation before enabling console logging.</param>
73- /// <param name="maxLength">Optional maximum length to render for string values. Replaces remaining characters with "...".</param>
74- public static ChatClientBuilder UseJsonConsoleLogging ( this ChatClientBuilder builder , bool askConfirmation = false , Action < JsonConsoleLoggingChatClient > ? configure = null )
52+ public static ChatClientBuilder UseJsonConsoleLogging ( this ChatClientBuilder builder , JsonConsoleOptions ? consoleOptions = null )
7553 {
76- if ( askConfirmation && ! AnsiConsole . Confirm ( "Do you want to enable console logging for chat messages?" ) )
54+ consoleOptions ??= JsonConsoleOptions . Default ;
55+
56+ if ( consoleOptions . InteractiveConfirm && ConsoleExtensions . IsConsoleInteractive && ! AnsiConsole . Confirm ( "Do you want to enable rich JSON console logging for HTTP pipeline messages?" ) )
7757 return builder ;
7858
79- return builder . Use ( inner =>
80- {
81- var client = new JsonConsoleLoggingChatClient ( inner ) ;
82- configure ? . Invoke ( client ) ;
83- return client ;
84- } ) ;
59+ if ( consoleOptions . InteractiveOnly && ! ConsoleExtensions . IsConsoleInteractive )
60+ return builder ;
61+
62+ return builder . Use ( inner => new JsonConsoleLoggingChatClient ( inner , consoleOptions ) ) ;
8563 }
8664
87- class ConsoleLoggingPipelineTransport ( PipelineTransport inner ) : PipelineTransport
65+ class ConsoleLoggingPipelineTransport ( PipelineTransport inner , JsonConsoleOptions consoleOptions ) : PipelineTransport
8866 {
8967 public static PipelineTransport Default { get ; } = new ConsoleLoggingPipelineTransport ( ) ;
9068
91- public ConsoleLoggingPipelineTransport ( ) : this ( HttpClientPipelineTransport . Shared ) { }
92-
93- protected override PipelineMessage CreateMessageCore ( ) => inner . CreateMessage ( ) ;
94- protected override void ProcessCore ( PipelineMessage message ) => inner . Process ( message ) ;
69+ public ConsoleLoggingPipelineTransport ( ) : this ( HttpClientPipelineTransport . Shared , JsonConsoleOptions . Default ) { }
9570
9671 protected override async ValueTask ProcessCoreAsync ( PipelineMessage message )
9772 {
@@ -105,15 +80,52 @@ protected override async ValueTask ProcessCoreAsync(PipelineMessage message)
10580 memory . Position = 0 ;
10681 using var reader = new StreamReader ( memory ) ;
10782 var content = await reader . ReadToEndAsync ( ) ;
108-
109- AnsiConsole . Write ( new Panel ( new JsonText ( content ) ) ) ;
83+ AnsiConsole . Write ( consoleOptions . CreatePanel ( content ) ) ;
11084 }
11185
11286 if ( message . Response != null )
11387 {
114- AnsiConsole . Write ( new Panel ( new JsonText ( message . Response . Content . ToString ( ) ) ) ) ;
88+ AnsiConsole . Write ( consoleOptions . CreatePanel ( message . Response . Content . ToString ( ) ) ) ;
11589 }
11690 }
91+
92+ protected override PipelineMessage CreateMessageCore ( ) => inner . CreateMessage ( ) ;
93+ protected override void ProcessCore ( PipelineMessage message ) => inner . Process ( message ) ;
11794 }
11895
96+ class JsonConsoleLoggingChatClient ( IChatClient inner , JsonConsoleOptions consoleOptions ) : DelegatingChatClient ( inner )
97+ {
98+ public override async Task < ChatResponse > GetResponseAsync ( IEnumerable < ChatMessage > messages , ChatOptions ? options = null , CancellationToken cancellationToken = default )
99+ {
100+ AnsiConsole . Write ( consoleOptions . CreatePanel ( new
101+ {
102+ messages = messages . Where ( x => x . Role != ChatRole . System ) . ToArray ( ) ,
103+ options
104+ } ) ) ;
105+
106+ var response = await InnerClient . GetResponseAsync ( messages , options , cancellationToken ) ;
107+ AnsiConsole . Write ( consoleOptions . CreatePanel ( response ) ) ;
108+
109+ return response ;
110+ }
111+
112+ public override async IAsyncEnumerable < ChatResponseUpdate > GetStreamingResponseAsync ( IEnumerable < ChatMessage > messages , ChatOptions ? options = null , [ EnumeratorCancellation ] CancellationToken cancellationToken = default )
113+ {
114+ AnsiConsole . Write ( consoleOptions . CreatePanel ( new
115+ {
116+ messages = messages . Where ( x => x . Role != ChatRole . System ) . ToArray ( ) ,
117+ options
118+ } ) ) ;
119+
120+ List < ChatResponseUpdate > updates = [ ] ;
121+
122+ await foreach ( var update in base . GetStreamingResponseAsync ( messages , options , cancellationToken ) )
123+ {
124+ updates . Add ( update ) ;
125+ yield return update ;
126+ }
127+
128+ AnsiConsole . Write ( consoleOptions . CreatePanel ( updates ) ) ;
129+ }
130+ }
119131}
0 commit comments