@@ -49,7 +49,7 @@ namespace GitHub.Copilot.SDK;
4949/// await session.SendAsync(new MessageOptions { Prompt = "Hello!" });
5050/// </code>
5151/// </example>
52- public class CopilotClient : IDisposable , IAsyncDisposable
52+ public partial class CopilotClient : IDisposable , IAsyncDisposable
5353{
5454 private readonly ConcurrentDictionary < string , CopilotSession > _sessions = new ( ) ;
5555 private readonly CopilotClientOptions _options ;
@@ -461,7 +461,7 @@ public async Task<PingResponse> PingAsync(string? message = null, CancellationTo
461461 var connection = await EnsureConnectedAsync ( cancellationToken ) ;
462462
463463 return await connection . Rpc . InvokeWithCancellationAsync < PingResponse > (
464- "ping" , [ new { message } ] , cancellationToken ) ;
464+ "ping" , [ new PingRequest { Message = message } ] , cancellationToken ) ;
465465 }
466466
467467 /// <summary>
@@ -510,7 +510,7 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell
510510 var connection = await EnsureConnectedAsync ( cancellationToken ) ;
511511
512512 var response = await connection . Rpc . InvokeWithCancellationAsync < DeleteSessionResponse > (
513- "session.delete" , [ new { sessionId } ] , cancellationToken ) ;
513+ "session.delete" , [ new DeleteSessionRequest ( sessionId ) ] , cancellationToken ) ;
514514
515515 if ( ! response . Success )
516516 {
@@ -560,7 +560,7 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio
560560 {
561561 var expectedVersion = SdkProtocolVersion . GetVersion ( ) ;
562562 var pingResponse = await connection . Rpc . InvokeWithCancellationAsync < PingResponse > (
563- "ping" , [ new { message = ( string ? ) null } ] , cancellationToken ) ;
563+ "ping" , [ new PingRequest ( ) ] , cancellationToken ) ;
564564
565565 if ( ! pingResponse . ProtocolVersion . HasValue )
566566 {
@@ -710,23 +710,45 @@ private async Task<Connection> ConnectToServerAsync(Process? cliProcess, string?
710710 outputStream = networkStream ;
711711 }
712712
713- var rpc = new JsonRpc ( new HeaderDelimitedMessageHandler ( outputStream , inputStream , CreateFormatter ( ) ) ) ;
714- rpc . AddLocalRpcTarget ( new RpcHandler ( this ) ) ;
713+ var rpc = new JsonRpc ( new HeaderDelimitedMessageHandler (
714+ outputStream ,
715+ inputStream ,
716+ CreateSystemTextJsonFormatter ( ) ) )
717+ {
718+ TraceSource = new LoggerTraceSource ( _logger ) ,
719+ } ;
720+
721+ var handler = new RpcHandler ( this ) ;
722+ rpc . AddLocalRpcMethod ( "session.event" , handler . OnSessionEvent ) ;
723+ rpc . AddLocalRpcMethod ( "tool.call" , handler . OnToolCall ) ;
724+ rpc . AddLocalRpcMethod ( "permission.request" , handler . OnPermissionRequest ) ;
715725 rpc . StartListening ( ) ;
716726 return new Connection ( rpc , cliProcess , tcpClient , networkStream ) ;
717727 }
718728
719- [ UnconditionalSuppressMessage ( "Trimming" , "IL2026" , Justification = "Using the Json source generator." ) ]
720- [ UnconditionalSuppressMessage ( "AOT" , "IL3050" , Justification = "Using the Json source generator." ) ]
721- static IJsonRpcMessageFormatter CreateFormatter ( )
729+ [ UnconditionalSuppressMessage ( "Trimming" , "IL2026" , Justification = "Using happy path from https://microsoft.github.io/vs-streamjsonrpc/docs/nativeAOT.html" ) ]
730+ [ UnconditionalSuppressMessage ( "AOT" , "IL3050" , Justification = "Using happy path from https://microsoft.github.io/vs-streamjsonrpc/docs/nativeAOT.html" ) ]
731+ private static SystemTextJsonFormatter CreateSystemTextJsonFormatter ( ) =>
732+ new SystemTextJsonFormatter ( ) { JsonSerializerOptions = SerializerOptionsForMessageFormatter } ;
733+
734+ private static JsonSerializerOptions SerializerOptionsForMessageFormatter { get ; } = CreateSerializerOptions ( ) ;
735+
736+ private static JsonSerializerOptions CreateSerializerOptions ( )
722737 {
723738 var options = new JsonSerializerOptions ( JsonSerializerDefaults . Web )
724739 {
725740 AllowOutOfOrderMetadataProperties = true ,
726741 DefaultIgnoreCondition = JsonIgnoreCondition . WhenWritingNull
727742 } ;
728743
729- return new SystemTextJsonFormatter ( ) { JsonSerializerOptions = options } ;
744+ options . TypeInfoResolverChain . Add ( ClientJsonContext . Default ) ;
745+ options . TypeInfoResolverChain . Add ( TypesJsonContext . Default ) ;
746+ options . TypeInfoResolverChain . Add ( CopilotSession . SessionJsonContext . Default ) ;
747+ options . TypeInfoResolverChain . Add ( SessionEventsJsonContext . Default ) ;
748+
749+ options . MakeReadOnly ( ) ;
750+
751+ return options ;
730752 }
731753
732754 internal CopilotSession ? GetSession ( string sessionId ) =>
@@ -759,9 +781,7 @@ public async ValueTask DisposeAsync()
759781
760782 private class RpcHandler ( CopilotClient client )
761783 {
762- [ JsonRpcMethod ( "session.event" ) ]
763- public void OnSessionEvent ( string sessionId ,
764- JsonElement ? @event )
784+ public void OnSessionEvent ( string sessionId , JsonElement ? @event )
765785 {
766786 var session = client . GetSession ( sessionId ) ;
767787 if ( session != null && @event != null )
@@ -774,7 +794,6 @@ public void OnSessionEvent(string sessionId,
774794 }
775795 }
776796
777- [ JsonRpcMethod ( "tool.call" ) ]
778797 public async Task < ToolCallResponse > OnToolCall ( string sessionId ,
779798 string toolCallId ,
780799 string toolName ,
@@ -847,7 +866,7 @@ public async Task<ToolCallResponse> OnToolCall(string sessionId,
847866 // something we don't control? an error?)
848867 TextResultForLlm = result is JsonElement { ValueKind : JsonValueKind . String } je
849868 ? je . GetString ( ) !
850- : JsonSerializer . Serialize ( result , tool . JsonSerializerOptions ) ,
869+ : JsonSerializer . Serialize ( result , tool . JsonSerializerOptions . GetTypeInfo ( typeof ( object ) ) ) ,
851870 } ;
852871 return new ToolCallResponse ( toolResultObject ) ;
853872 }
@@ -864,7 +883,6 @@ public async Task<ToolCallResponse> OnToolCall(string sessionId,
864883 }
865884 }
866885
867- [ JsonRpcMethod ( "permission.request" ) ]
868886 public async Task < PermissionRequestResponse > OnPermissionRequest ( string sessionId , JsonElement permissionRequest )
869887 {
870888 var session = client . GetSession ( sessionId ) ;
@@ -915,7 +933,7 @@ public static string Escape(string arg)
915933 }
916934
917935 // Request/Response types for RPC
918- private record CreateSessionRequest (
936+ internal record CreateSessionRequest (
919937 string ? Model ,
920938 string ? SessionId ,
921939 List < ToolDefinition > ? Tools ,
@@ -931,7 +949,7 @@ private record CreateSessionRequest(
931949 List < string > ? SkillDirectories ,
932950 List < string > ? DisabledSkills ) ;
933951
934- private record ToolDefinition (
952+ internal record ToolDefinition (
935953 string Name ,
936954 string ? Description ,
937955 JsonElement Parameters /* JSON schema */ )
@@ -940,10 +958,10 @@ public static ToolDefinition FromAIFunction(AIFunction function)
940958 => new ToolDefinition ( function . Name , function . Description , function . JsonSchema ) ;
941959 }
942960
943- private record CreateSessionResponse (
961+ internal record CreateSessionResponse (
944962 string SessionId ) ;
945963
946- private record ResumeSessionRequest (
964+ internal record ResumeSessionRequest (
947965 string SessionId ,
948966 List < ToolDefinition > ? Tools ,
949967 ProviderConfig ? Provider ,
@@ -954,24 +972,93 @@ private record ResumeSessionRequest(
954972 List < string > ? SkillDirectories ,
955973 List < string > ? DisabledSkills ) ;
956974
957- private record ResumeSessionResponse (
975+ internal record ResumeSessionResponse (
958976 string SessionId ) ;
959977
960- private record GetLastSessionIdResponse (
978+ internal record GetLastSessionIdResponse (
961979 string ? SessionId ) ;
962980
963- private record DeleteSessionResponse (
981+ internal record DeleteSessionRequest (
982+ string SessionId ) ;
983+
984+ internal record DeleteSessionResponse (
964985 bool Success ,
965986 string ? Error ) ;
966987
967- private record ListSessionsResponse (
988+ internal record ListSessionsResponse (
968989 List < SessionMetadata > Sessions ) ;
969990
970- private record ToolCallResponse (
991+ internal record ToolCallResponse (
971992 ToolResultObject ? Result ) ;
972993
973- private record PermissionRequestResponse (
994+ internal record PermissionRequestResponse (
974995 PermissionRequestResult Result ) ;
996+
997+ /// <summary>Trace source that forwards all logs to the ILogger.</summary>
998+ internal sealed class LoggerTraceSource : TraceSource
999+ {
1000+ public LoggerTraceSource ( ILogger logger ) : base ( nameof ( LoggerTraceSource ) , SourceLevels . All )
1001+ {
1002+ Listeners . Clear ( ) ;
1003+ Listeners . Add ( new LoggerTraceListener ( logger ) ) ;
1004+ }
1005+
1006+ private sealed class LoggerTraceListener ( ILogger logger ) : TraceListener
1007+ {
1008+ public override void TraceEvent ( TraceEventCache ? eventCache , string source , TraceEventType eventType , int id , string ? message ) =>
1009+ logger . Log ( MapLevel ( eventType ) , "[{Source}] {Message}" , source , message ) ;
1010+
1011+ public override void TraceEvent ( TraceEventCache ? eventCache , string source , TraceEventType eventType , int id , string ? format , params object ? [ ] ? args ) =>
1012+ logger . Log ( MapLevel ( eventType ) , "[{Source}] {Message}" , source , args is null || args . Length == 0 ? format : string . Format ( format ?? "" , args ) ) ;
1013+
1014+ public override void TraceData ( TraceEventCache ? eventCache , string source , TraceEventType eventType , int id , object ? data ) =>
1015+ logger . Log ( MapLevel ( eventType ) , "[{Source}] {Data}" , source , data ) ;
1016+
1017+ public override void TraceData ( TraceEventCache ? eventCache , string source , TraceEventType eventType , int id , params object ? [ ] ? data ) =>
1018+ logger . Log ( MapLevel ( eventType ) , "[{Source}] {Data}" , source , data is null ? null : string . Join ( ", " , data ) ) ;
1019+
1020+ public override void Write ( string ? message ) =>
1021+ logger . LogTrace ( "{Message}" , message ) ;
1022+
1023+ public override void WriteLine ( string ? message ) =>
1024+ logger . LogTrace ( "{Message}" , message ) ;
1025+
1026+ private static LogLevel MapLevel ( TraceEventType eventType ) => eventType switch
1027+ {
1028+ TraceEventType . Critical => LogLevel . Critical ,
1029+ TraceEventType . Error => LogLevel . Error ,
1030+ TraceEventType . Warning => LogLevel . Warning ,
1031+ TraceEventType . Information => LogLevel . Information ,
1032+ TraceEventType . Verbose => LogLevel . Debug ,
1033+ _ => LogLevel . Trace
1034+ } ;
1035+ }
1036+ }
1037+
1038+ [ JsonSourceGenerationOptions (
1039+ JsonSerializerDefaults . Web ,
1040+ AllowOutOfOrderMetadataProperties = true ,
1041+ NumberHandling = JsonNumberHandling . AllowReadingFromString ,
1042+ DefaultIgnoreCondition = JsonIgnoreCondition . WhenWritingNull ) ]
1043+ [ JsonSerializable ( typeof ( CreateSessionRequest ) ) ]
1044+ [ JsonSerializable ( typeof ( CreateSessionResponse ) ) ]
1045+ [ JsonSerializable ( typeof ( CustomAgentConfig ) ) ]
1046+ [ JsonSerializable ( typeof ( DeleteSessionRequest ) ) ]
1047+ [ JsonSerializable ( typeof ( DeleteSessionResponse ) ) ]
1048+ [ JsonSerializable ( typeof ( GetLastSessionIdResponse ) ) ]
1049+ [ JsonSerializable ( typeof ( ListSessionsResponse ) ) ]
1050+ [ JsonSerializable ( typeof ( PermissionRequestResponse ) ) ]
1051+ [ JsonSerializable ( typeof ( PermissionRequestResult ) ) ]
1052+ [ JsonSerializable ( typeof ( ProviderConfig ) ) ]
1053+ [ JsonSerializable ( typeof ( ResumeSessionRequest ) ) ]
1054+ [ JsonSerializable ( typeof ( ResumeSessionResponse ) ) ]
1055+ [ JsonSerializable ( typeof ( SessionMetadata ) ) ]
1056+ [ JsonSerializable ( typeof ( SystemMessageConfig ) ) ]
1057+ [ JsonSerializable ( typeof ( ToolCallResponse ) ) ]
1058+ [ JsonSerializable ( typeof ( ToolDefinition ) ) ]
1059+ [ JsonSerializable ( typeof ( ToolResultAIContent ) ) ]
1060+ [ JsonSerializable ( typeof ( ToolResultObject ) ) ]
1061+ internal partial class ClientJsonContext : JsonSerializerContext ;
9751062}
9761063
9771064// Must inherit from AIContent as a signal to MEAI to avoid JSON-serializing the
0 commit comments