Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.DurableTask/Logs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ public static partial void LogTTLExpirationTimeCleared(
this ILogger logger,
AgentSessionId sessionId);

[LoggerMessage(
EventId = 16,
Level = LogLevel.Warning,
Message = "Unknown AI content metadata with runtime type '{RuntimeType}' could not be serialized. The value was omitted from durable state with failure category '{FailureCategory}'.")]
public static partial void LogUnknownContentSerializationFallback(
this ILogger logger,
string runtimeType,
string failureCategory);

// Durable workflow logs (EventIds 100-199)

[LoggerMessage(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using System.Text.Json.Serialization;

namespace Microsoft.Agents.AI.DurableTask.State;
Expand All @@ -10,6 +11,10 @@ namespace Microsoft.Agents.AI.DurableTask.State;
[JsonConverter(typeof(DurableAgentStateJsonConverter))]
internal sealed class DurableAgentState
{
internal const string CurrentSchemaVersion = "1.2.0";
private static readonly DurableAgentStateSchemaVersion s_currentSchemaVersion =
DurableAgentStateSchemaVersion.ParseSupported(CurrentSchemaVersion);
Comment on lines +14 to +16

/// <summary>
/// Gets the data of the durable agent.
/// </summary>
Expand All @@ -20,8 +25,56 @@ internal sealed class DurableAgentState
/// Gets the schema version of the durable agent state.
/// </summary>
/// <remarks>
/// The version is specified in semver (i.e. "major.minor.patch") format.
/// New states default to <see cref="CurrentSchemaVersion"/>. Deserialization assigns the
/// persisted value through this init-only property, and <see cref="Clone"/> constructs a new
/// state when an older compatible version must be promoted for a write. Future compatible
/// versions are preserved rather than rewritten.
/// </remarks>
[JsonPropertyName("schemaVersion")]
public string SchemaVersion { get; init; } = "1.1.0";
public string SchemaVersion { get; init; } = CurrentSchemaVersion;

/// <summary>
/// Gets application-defined root extension metadata from the schema's <c>extensionData</c> property.
/// </summary>
[JsonPropertyName("extensionData")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IDictionary<string, JsonElement>? ExtensionData { get; init; }

/// <summary>
/// Gets unknown root properties that are outside the declared schema.
/// </summary>
[JsonExtensionData]
public IDictionary<string, JsonElement>? UnknownProperties { get; set; }

/// <summary>
/// Creates an independent copy suitable for an atomic entity operation.
/// </summary>
public DurableAgentState Clone()
{
byte[] serialized = JsonSerializer.SerializeToUtf8Bytes(
this,
DurableAgentStateJsonContext.Default.DurableAgentState);
DurableAgentState clone = JsonSerializer.Deserialize(
serialized,
DurableAgentStateJsonContext.Default.DurableAgentState)
?? throw new JsonException("The durable agent state could not be cloned.");
DurableAgentStateMessageIdentity.EnsureMessageIds(clone.Data.ConversationHistory);

return new DurableAgentState
{
SchemaVersion = SelectSchemaVersionForWrite(clone.SchemaVersion),
Data = clone.Data,
ExtensionData = clone.ExtensionData,
UnknownProperties = clone.UnknownProperties,
};
}

private static string SelectSchemaVersionForWrite(string schemaVersion)
{
DurableAgentStateSchemaVersion sourceVersion =
DurableAgentStateSchemaVersion.ParseSupported(schemaVersion);
return sourceVersion.CompareTo(s_currentSchemaVersion) < 0
? CurrentSchemaVersion
: schemaVersion;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.Agents.AI.DurableTask.State;

/// <summary>
/// Represents a compacted transcript message written by another durable agent implementation.
/// </summary>
/// <remarks>
/// This layer serializes, deserializes, and converts the shared compaction contract. Agent entity
/// replay and retention integration are deferred to later layers.
/// </remarks>
internal sealed class DurableAgentStateCompaction : DurableAgentStateEntry;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;

namespace Microsoft.Agents.AI.DurableTask.State;

Expand Down Expand Up @@ -42,10 +43,10 @@ internal abstract class DurableAgentStateContent
JsonSerializer.SerializeToElement(value: null, jsonTypeInfo: s_objectTypeInfo);

/// <summary>
/// Gets any additional data found during deserialization that does not map to known properties.
/// Gets unknown content properties that are outside the declared schema.
/// </summary>
[JsonExtensionData]
public IDictionary<string, JsonElement>? ExtensionData { get; set; }
public IDictionary<string, JsonElement>? UnknownProperties { get; set; }

/// <summary>
/// Converts this durable agent state content to an <see cref="AIContent"/>.
Expand All @@ -57,8 +58,9 @@ internal abstract class DurableAgentStateContent
/// Creates a <see cref="DurableAgentStateContent"/> from an <see cref="AIContent"/>.
/// </summary>
/// <param name="content">The <see cref="AIContent"/> to convert.</param>
/// <param name="logger">The logger used to report safe unknown-content fallbacks.</param>
/// <returns>A <see cref="DurableAgentStateContent"/> representing the original <see cref="AIContent"/>.</returns>
public static DurableAgentStateContent FromAIContent(AIContent content)
public static DurableAgentStateContent FromAIContent(AIContent content, ILogger? logger = null)
{
return content switch
{
Expand All @@ -72,7 +74,7 @@ public static DurableAgentStateContent FromAIContent(AIContent content)
TextReasoningContent textReasoningContent => DurableAgentStateTextReasoningContent.FromTextReasoningContent(textReasoningContent),
UriContent uriContent => DurableAgentStateUriContent.FromUriContent(uriContent),
UsageContent usageContent => DurableAgentStateUsageContent.FromUsageContent(usageContent),
_ => DurableAgentStateUnknownContent.FromUnknownContent(content)
_ => DurableAgentStateUnknownContent.FromUnknownContent(content, logger)
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,49 @@ internal sealed class DurableAgentStateData
[JsonPropertyName("conversationHistory")]
public IList<DurableAgentStateEntry> ConversationHistory { get; init; } = [];

/// <summary>
/// Gets or sets the serialized inner agent session.
/// </summary>
[JsonPropertyName("session")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public JsonElement? Session { get; set; }

/// <summary>
/// Gets or sets the highest workflow conversation position ingested from each executor.
/// </summary>
/// <remarks>
/// The .NET workflow path does not populate these watermarks yet, but they are preserved for
/// cross-language schema compatibility.
/// </remarks>
[JsonPropertyName("ingestedPositions")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IDictionary<string, int>? IngestedPositions { get; set; }

/// <summary>
/// Gets or sets bounded evidence that retention removed conversation messages.
/// </summary>
[JsonPropertyName("truncation")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public DurableAgentStateTruncation? Truncation { get; set; }

/// <summary>
/// Gets or sets the expiration time (UTC) for this agent entity.
/// If the entity is idle beyond this time, it will be automatically deleted.
/// </summary>
[JsonPropertyName("expirationTimeUtc")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public DateTime? ExpirationTimeUtc { get; set; }

/// <summary>
/// Gets any additional data found during deserialization that does not map to known properties.
/// Gets application-defined data-level metadata from the schema's <c>extensionData</c> property.
/// </summary>
[JsonPropertyName("extensionData")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IDictionary<string, JsonElement>? ExtensionData { get; init; }

/// <summary>
/// Gets unknown data properties that are outside the declared schema.
/// </summary>
[JsonExtensionData]
public IDictionary<string, JsonElement>? ExtensionData { get; set; }
public IDictionary<string, JsonElement>? UnknownProperties { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ namespace Microsoft.Agents.AI.DurableTask.State;
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(DurableAgentStateRequest), "request")]
[JsonDerivedType(typeof(DurableAgentStateResponse), "response")]
[JsonDerivedType(typeof(DurableAgentStateErrorResponse), "errorResponse")]
[JsonDerivedType(typeof(DurableAgentStateCompaction), "compaction")]
internal abstract class DurableAgentStateEntry
{
/// <summary>
/// Gets the correlation ID for this entry.
/// </summary>
/// <remarks>
/// This ID is used to correlate <see cref="DurableAgentStateResponse"/> back to its
/// <see cref="DurableAgentStateRequest"/>.
/// <see cref="DurableAgentStateRequest"/>. Compaction entries do not have a correlation ID.
/// </remarks>
[JsonPropertyName("correlationId")]
public required string CorrelationId { get; init; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? CorrelationId { get; init; }

/// <summary>
/// Gets the timestamp when this entry was created.
Expand All @@ -37,8 +40,15 @@ internal abstract class DurableAgentStateEntry
public IReadOnlyList<DurableAgentStateMessage> Messages { get; init; } = [];

/// <summary>
/// Gets any additional data found during deserialization that does not map to known properties.
/// Gets application-defined entry metadata from the schema's <c>extensionData</c> property.
/// </summary>
[JsonPropertyName("extensionData")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IDictionary<string, JsonElement>? ExtensionData { get; init; }

/// <summary>
/// Gets unknown entry properties that are outside the declared schema.
/// </summary>
[JsonExtensionData]
public IDictionary<string, JsonElement>? ExtensionData { get; set; }
public IDictionary<string, JsonElement>? UnknownProperties { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.AI;

Expand Down Expand Up @@ -29,7 +30,7 @@ internal sealed class DurableAgentStateErrorContent : DurableAgentStateContent
/// </summary>
[JsonPropertyName("details")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Details { get; init; }
public JsonElement? Details { get; init; }

/// <summary>
/// Creates a <see cref="DurableAgentStateErrorContent"/> from an <see cref="ErrorContent"/>.
Expand All @@ -41,7 +42,11 @@ public static DurableAgentStateErrorContent FromErrorContent(ErrorContent conten
{
return new DurableAgentStateErrorContent()
{
Details = content.Details,
Details = content.Details is null
? null
: JsonSerializer.SerializeToElement(
content.Details,
DurableAgentStateJsonContext.Default.String),
ErrorCode = content.ErrorCode,
Message = content.Message
};
Expand All @@ -52,7 +57,11 @@ public override AIContent ToAIContent()
{
return new ErrorContent(this.Message)
{
Details = this.Details,
Details = this.Details is JsonElement details
? details.ValueKind == JsonValueKind.String
? details.GetString()
: details.GetRawText()
: null,
ErrorCode = this.ErrorCode
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

namespace Microsoft.Agents.AI.DurableTask.State;

/// <summary>
/// Represents a failed turn recorded by another durable agent implementation.
/// </summary>
/// <remarks>
/// .NET durable agents do not currently create pollable error responses, but preserve this shared-schema
/// entry kind when reading state written by another language implementation.
/// </remarks>
internal sealed class DurableAgentStateErrorResponse : DurableAgentStateResponse;
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ namespace Microsoft.Agents.AI.DurableTask.State;
[JsonSerializable(typeof(DurableAgentStateContent))]
[JsonSerializable(typeof(DurableAgentStateData))]
[JsonSerializable(typeof(DurableAgentStateEntry))]
[JsonSerializable(typeof(DurableAgentStateErrorResponse))]
[JsonSerializable(typeof(DurableAgentStateCompaction))]
[JsonSerializable(typeof(DurableAgentStateMessage))]
[JsonSerializable(typeof(DurableAgentStateTruncation))]
// Function call and result content
[JsonSerializable(typeof(Dictionary<string, object>))]
[JsonSerializable(typeof(IDictionary<string, object?>))]
Expand Down
Loading
Loading