Skip to content

Commit ce54e43

Browse files
authored
Add copy constructors and Clone methods to the various .NET config option bags (#422)
* Add copy constructors and Clone methods to the various .NET config option bags Consuming libraries like Agent Framework sometimes have a need to clone the various options bags, e.g. their caller passes in options and that middle library needs to tweak the settings before passing them along (e.g. set Streaming to true or false) but it doesn't want to mutate the caller's object. Without clone methods, such libraries need to manually copy every property, which then means when new properties are added, they get ignored and options are lost. This PR adds such public Clone methods, and accomodates the non-sealed nature of the types by adding protected copy constructors that the virtual Clone methods use. (If instead we want to seal these types, that'd be viable as well.) * Address copilot feedback
1 parent 304d812 commit ce54e43

File tree

2 files changed

+407
-0
lines changed

2 files changed

+407
-0
lines changed

dotnet/src/Types.cs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,34 @@ public enum ConnectionState
2424

2525
public class CopilotClientOptions
2626
{
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="CopilotClientOptions"/> class.
29+
/// </summary>
30+
public CopilotClientOptions() { }
31+
32+
/// <summary>
33+
/// Initializes a new instance of the <see cref="CopilotClientOptions"/> class
34+
/// by copying the properties of the specified instance.
35+
/// </summary>
36+
protected CopilotClientOptions(CopilotClientOptions? other)
37+
{
38+
if (other is null) return;
39+
40+
AutoRestart = other.AutoRestart;
41+
AutoStart = other.AutoStart;
42+
CliArgs = (string[]?)other.CliArgs?.Clone();
43+
CliPath = other.CliPath;
44+
CliUrl = other.CliUrl;
45+
Cwd = other.Cwd;
46+
Environment = other.Environment;
47+
GithubToken = other.GithubToken;
48+
Logger = other.Logger;
49+
LogLevel = other.LogLevel;
50+
Port = other.Port;
51+
UseLoggedInUser = other.UseLoggedInUser;
52+
UseStdio = other.UseStdio;
53+
}
54+
2755
/// <summary>
2856
/// Path to the Copilot CLI executable. If not specified, uses the bundled CLI from the SDK.
2957
/// </summary>
@@ -53,6 +81,17 @@ public class CopilotClientOptions
5381
/// Default: true (but defaults to false when GithubToken is provided).
5482
/// </summary>
5583
public bool? UseLoggedInUser { get; set; }
84+
85+
/// <summary>
86+
/// Creates a shallow clone of this <see cref="CopilotClientOptions"/> instance.
87+
/// </summary>
88+
/// <remarks>
89+
/// Mutable collection properties are copied into new collection instances so that modifications
90+
/// to those collections on the clone do not affect the original.
91+
/// Other reference-type properties (for example delegates and the logger) are not
92+
/// deep-cloned; the original and the clone will share those objects.
93+
/// </remarks>
94+
public virtual CopilotClientOptions Clone() => new(this);
5695
}
5796

5897
public class ToolBinaryResult
@@ -692,6 +731,42 @@ public class InfiniteSessionConfig
692731

693732
public class SessionConfig
694733
{
734+
/// <summary>
735+
/// Initializes a new instance of the <see cref="SessionConfig"/> class.
736+
/// </summary>
737+
public SessionConfig() { }
738+
739+
/// <summary>
740+
/// Initializes a new instance of the <see cref="SessionConfig"/> class
741+
/// by copying the properties of the specified instance.
742+
/// </summary>
743+
protected SessionConfig(SessionConfig? other)
744+
{
745+
if (other is null) return;
746+
747+
AvailableTools = other.AvailableTools is not null ? [.. other.AvailableTools] : null;
748+
ConfigDir = other.ConfigDir;
749+
CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null;
750+
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
751+
ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null;
752+
Hooks = other.Hooks;
753+
InfiniteSessions = other.InfiniteSessions;
754+
McpServers = other.McpServers is not null
755+
? new Dictionary<string, object>(other.McpServers, other.McpServers.Comparer)
756+
: null;
757+
Model = other.Model;
758+
OnPermissionRequest = other.OnPermissionRequest;
759+
OnUserInputRequest = other.OnUserInputRequest;
760+
Provider = other.Provider;
761+
ReasoningEffort = other.ReasoningEffort;
762+
SessionId = other.SessionId;
763+
SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null;
764+
Streaming = other.Streaming;
765+
SystemMessage = other.SystemMessage;
766+
Tools = other.Tools is not null ? [.. other.Tools] : null;
767+
WorkingDirectory = other.WorkingDirectory;
768+
}
769+
695770
public string? SessionId { get; set; }
696771
public string? Model { get; set; }
697772

@@ -769,10 +844,58 @@ public class SessionConfig
769844
/// When enabled (default), sessions automatically manage context limits and persist state.
770845
/// </summary>
771846
public InfiniteSessionConfig? InfiniteSessions { get; set; }
847+
848+
/// <summary>
849+
/// Creates a shallow clone of this <see cref="SessionConfig"/> instance.
850+
/// </summary>
851+
/// <remarks>
852+
/// Mutable collection properties are copied into new collection instances so that modifications
853+
/// to those collections on the clone do not affect the original.
854+
/// Other reference-type properties (for example provider configuration, system messages,
855+
/// hooks, infinite session configuration, and delegates) are not deep-cloned; the original
856+
/// and the clone will share those nested objects, and changes to them may affect both.
857+
/// </remarks>
858+
public virtual SessionConfig Clone() => new(this);
772859
}
773860

774861
public class ResumeSessionConfig
775862
{
863+
/// <summary>
864+
/// Initializes a new instance of the <see cref="ResumeSessionConfig"/> class.
865+
/// </summary>
866+
public ResumeSessionConfig() { }
867+
868+
/// <summary>
869+
/// Initializes a new instance of the <see cref="ResumeSessionConfig"/> class
870+
/// by copying the properties of the specified instance.
871+
/// </summary>
872+
protected ResumeSessionConfig(ResumeSessionConfig? other)
873+
{
874+
if (other is null) return;
875+
876+
AvailableTools = other.AvailableTools is not null ? [.. other.AvailableTools] : null;
877+
ConfigDir = other.ConfigDir;
878+
CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null;
879+
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
880+
DisableResume = other.DisableResume;
881+
ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null;
882+
Hooks = other.Hooks;
883+
InfiniteSessions = other.InfiniteSessions;
884+
McpServers = other.McpServers is not null
885+
? new Dictionary<string, object>(other.McpServers, other.McpServers.Comparer)
886+
: null;
887+
Model = other.Model;
888+
OnPermissionRequest = other.OnPermissionRequest;
889+
OnUserInputRequest = other.OnUserInputRequest;
890+
Provider = other.Provider;
891+
ReasoningEffort = other.ReasoningEffort;
892+
SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null;
893+
Streaming = other.Streaming;
894+
SystemMessage = other.SystemMessage;
895+
Tools = other.Tools is not null ? [.. other.Tools] : null;
896+
WorkingDirectory = other.WorkingDirectory;
897+
}
898+
776899
/// <summary>
777900
/// Model to use for this session. Can change the model when resuming.
778901
/// </summary>
@@ -870,13 +993,54 @@ public class ResumeSessionConfig
870993
/// Infinite session configuration for persistent workspaces and automatic compaction.
871994
/// </summary>
872995
public InfiniteSessionConfig? InfiniteSessions { get; set; }
996+
997+
/// <summary>
998+
/// Creates a shallow clone of this <see cref="ResumeSessionConfig"/> instance.
999+
/// </summary>
1000+
/// <remarks>
1001+
/// Mutable collection properties are copied into new collection instances so that modifications
1002+
/// to those collections on the clone do not affect the original.
1003+
/// Other reference-type properties (for example provider configuration, system messages,
1004+
/// hooks, infinite session configuration, and delegates) are not deep-cloned; the original
1005+
/// and the clone will share those nested objects, and changes to them may affect both.
1006+
/// </remarks>
1007+
public virtual ResumeSessionConfig Clone() => new(this);
8731008
}
8741009

8751010
public class MessageOptions
8761011
{
1012+
/// <summary>
1013+
/// Initializes a new instance of the <see cref="MessageOptions"/> class.
1014+
/// </summary>
1015+
public MessageOptions() { }
1016+
1017+
/// <summary>
1018+
/// Initializes a new instance of the <see cref="MessageOptions"/> class
1019+
/// by copying the properties of the specified instance.
1020+
/// </summary>
1021+
protected MessageOptions(MessageOptions? other)
1022+
{
1023+
if (other is null) return;
1024+
1025+
Attachments = other.Attachments is not null ? [.. other.Attachments] : null;
1026+
Mode = other.Mode;
1027+
Prompt = other.Prompt;
1028+
}
1029+
8771030
public string Prompt { get; set; } = string.Empty;
8781031
public List<UserMessageDataAttachmentsItem>? Attachments { get; set; }
8791032
public string? Mode { get; set; }
1033+
1034+
/// <summary>
1035+
/// Creates a shallow clone of this <see cref="MessageOptions"/> instance.
1036+
/// </summary>
1037+
/// <remarks>
1038+
/// Mutable collection properties are copied into new collection instances so that modifications
1039+
/// to those collections on the clone do not affect the original.
1040+
/// Other reference-type properties (for example attachment items) are not deep-cloned;
1041+
/// the original and the clone will share those nested objects.
1042+
/// </remarks>
1043+
public virtual MessageOptions Clone() => new(this);
8801044
}
8811045

8821046
public delegate void SessionEventHandler(SessionEvent sessionEvent);

0 commit comments

Comments
 (0)