feat: migrate packaging and auto updates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-29 22:36:44 +02:00
co-authored by Copilot
parent 66b2a94977
commit 15071fdc47
28 changed files with 1254 additions and 901 deletions
@@ -15,6 +15,7 @@ internal sealed class AryxCopilotAgent : AIAgent, IAsyncDisposable
private const string DefaultName = "GitHub Copilot Agent";
private const string DefaultDescription = "An AI agent powered by GitHub Copilot";
private const string HandoffToolPrefix = "handoff_to_";
private static readonly JsonSerializerOptions ToolArgumentJsonOptions = JsonSerialization.CreateWebOptions();
private readonly CopilotClient _copilotClient;
private readonly string? _id;
private readonly string _name;
@@ -473,11 +474,11 @@ internal sealed class AryxCopilotAgent : AIAgent, IAsyncDisposable
return null;
}
return JsonSerializer.Deserialize<Dictionary<string, object?>>(jsonElement.GetRawText());
return JsonSerializer.Deserialize<Dictionary<string, object?>>(jsonElement.GetRawText(), ToolArgumentJsonOptions);
}
string json = JsonSerializer.Serialize(arguments, arguments.GetType());
return JsonSerializer.Deserialize<Dictionary<string, object?>>(json);
string json = JsonSerializer.Serialize(arguments, arguments.GetType(), ToolArgumentJsonOptions);
return JsonSerializer.Deserialize<Dictionary<string, object?>>(json, ToolArgumentJsonOptions);
}
internal static async Task<(List<UserMessageDataAttachmentsItem>? Attachments, string? MessageMode, string? TempDir)> ProcessMessageAttachmentsAsync(
@@ -601,6 +602,8 @@ internal sealed class AryxCopilotAgent : AIAgent, IAsyncDisposable
internal sealed class AryxCopilotAgentSession : AgentSession
{
private static readonly JsonSerializerOptions DefaultJsonOptions = JsonSerialization.CreateWebOptions();
public AryxCopilotAgentSession()
{
}
@@ -617,7 +620,7 @@ internal sealed class AryxCopilotAgentSession : AgentSession
internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
{
JsonSerializerOptions options = jsonSerializerOptions ?? new JsonSerializerOptions(JsonSerializerDefaults.Web);
JsonSerializerOptions options = jsonSerializerOptions ?? DefaultJsonOptions;
return JsonSerializer.SerializeToElement(this, options);
}
@@ -630,7 +633,7 @@ internal sealed class AryxCopilotAgentSession : AgentSession
throw new ArgumentException("The serialized session state must be a JSON object.", nameof(serializedState));
}
JsonSerializerOptions options = jsonSerializerOptions ?? new JsonSerializerOptions(JsonSerializerDefaults.Web);
JsonSerializerOptions options = jsonSerializerOptions ?? DefaultJsonOptions;
return serializedState.Deserialize<AryxCopilotAgentSession>(options)
?? new AryxCopilotAgentSession();
}
@@ -20,10 +20,7 @@ internal static class CopilotSessionHooks
ReportIntentToolName,
TaskCompleteToolName,
};
private static readonly JsonSerializerOptions HookJsonOptions = new(JsonSerializerDefaults.Web)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
private static readonly JsonSerializerOptions HookJsonOptions = CreateHookJsonOptions();
public static SessionHooks Create(
RunTurnCommandDto command,
@@ -281,6 +278,13 @@ internal static class CopilotSessionHooks
private static string SerializeHookValue(object? value)
=> JsonSerializer.Serialize(value, HookJsonOptions);
private static JsonSerializerOptions CreateHookJsonOptions()
{
JsonSerializerOptions options = JsonSerialization.CreateWebOptions();
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
return options;
}
private static bool IsAlwaysAllowedTool(string? toolName)
{
string? normalizedToolName = Normalize(toolName);
@@ -5,12 +5,7 @@ namespace Aryx.AgentHost.Services;
internal static class HookConfigLoader
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
AllowTrailingCommas = true,
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
};
private static readonly JsonSerializerOptions JsonOptions = CreateJsonOptions();
public static async Task<ResolvedHookSet> LoadAsync(string projectPath, CancellationToken cancellationToken)
{
@@ -204,4 +199,13 @@ internal static class HookConfigLoader
private static string? NormalizeOptionalString(string? value)
=> string.IsNullOrWhiteSpace(value) ? null : value.Trim();
private static JsonSerializerOptions CreateJsonOptions()
{
JsonSerializerOptions options = JsonSerialization.CreateWebOptions();
options.AllowTrailingCommas = true;
options.PropertyNameCaseInsensitive = true;
options.ReadCommentHandling = JsonCommentHandling.Skip;
return options;
}
}
@@ -0,0 +1,15 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
namespace Aryx.AgentHost.Services;
internal static class JsonSerialization
{
public static JsonSerializerOptions CreateWebOptions()
{
return new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
};
}
}
@@ -6,10 +6,7 @@ namespace Aryx.AgentHost.Services;
internal static class QuotaSnapshotMapper
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true,
};
private static readonly JsonSerializerOptions JsonOptions = CreateJsonOptions();
public static Dictionary<string, QuotaSnapshotDto> Map(
IReadOnlyDictionary<string, AccountGetQuotaResultQuotaSnapshotsValue>? snapshots)
@@ -102,4 +99,11 @@ internal static class QuotaSnapshotMapper
return deserialized is null ? null : Map(deserialized);
}
private static JsonSerializerOptions CreateJsonOptions()
{
JsonSerializerOptions options = JsonSerialization.CreateWebOptions();
options.PropertyNameCaseInsensitive = true;
return options;
}
}
@@ -67,11 +67,9 @@ public sealed class SidecarProtocolHost
_workflowRunner = workflowRunner ?? new CopilotWorkflowRunner(_patternValidator);
_capabilitiesProvider = capabilitiesProvider ?? BuildCapabilitiesAsync;
_sessionManager = sessionManager ?? new CopilotSessionManager();
_jsonOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNameCaseInsensitive = true,
};
_jsonOptions = JsonSerialization.CreateWebOptions();
_jsonOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
_jsonOptions.PropertyNameCaseInsensitive = true;
_commandHandlers = new Dictionary<string, Func<CommandContext, Task>>(StringComparer.Ordinal)
{
[DescribeCapabilitiesCommandType] = HandleDescribeCapabilitiesAsync,
@@ -12,6 +12,7 @@ internal static class WorkflowRequestInfoInterpreter
private const string ToolCallingActivityType = "tool-calling";
private const string CodeInterpreterToolName = "code interpreter";
private const string ImageGenerationToolName = "image generation";
private static readonly JsonSerializerOptions JsonOptions = JsonSerialization.CreateWebOptions();
public static AgentActivityEventDto? TryCreateActivityFromRequest(
RunTurnCommandDto command,
@@ -193,8 +194,8 @@ internal static class WorkflowRequestInfoInterpreter
private static WorkflowRequestHandoffPayload? DeserializeHandoffPayload(object handoffValue)
{
string json = JsonSerializer.Serialize(handoffValue, handoffValue.GetType());
return JsonSerializer.Deserialize<WorkflowRequestHandoffPayload>(json);
string json = JsonSerializer.Serialize(handoffValue, handoffValue.GetType(), JsonOptions);
return JsonSerializer.Deserialize<WorkflowRequestHandoffPayload>(json, JsonOptions);
}
private abstract record RequestInterpretation;
@@ -0,0 +1,41 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Aryx.AgentHost.Services;
namespace Aryx.AgentHost.Tests;
public sealed class JsonSerializationTests
{
[Fact]
public void CreateWebOptions_UsesDefaultJsonTypeInfoResolver()
{
JsonSerializerOptions options = JsonSerialization.CreateWebOptions();
Assert.IsType<DefaultJsonTypeInfoResolver>(options.TypeInfoResolver);
}
[Fact]
public void CreateWebOptions_RoundTripsRuntimeTypedPayloads()
{
JsonSerializerOptions options = JsonSerialization.CreateWebOptions();
object payload = new TestPayload
{
Type = "describe-capabilities",
RequestId = "req-1",
};
string json = JsonSerializer.Serialize(payload, payload.GetType(), options);
TestPayload? deserialized = JsonSerializer.Deserialize<TestPayload>(json, options);
Assert.NotNull(deserialized);
Assert.Equal("describe-capabilities", deserialized.Type);
Assert.Equal("req-1", deserialized.RequestId);
}
private sealed class TestPayload
{
public string? Type { get; init; }
public string? RequestId { get; init; }
}
}