diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 0b73e387..12148c55 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -72,12 +72,18 @@ Keep the docs focused on `CrestApps.Core`. If you need to mention the Orchard Co
- Add a blank line before and after `if` blocks, `switch` statements, and loops unless the block is immediately preceded by `{`
- Do not add a blank line between an `if`/`else`/`switch`/loop condition and its opening `{`
- Use `var` consistently with repository style
+- Do not use `global using` files; add explicit `using` directives at the top of each file instead.
+- Prefer top-of-file `using` directives over fully qualified type names in code.
- Only use expression-bodied members when the entire member fits on a single short line; use a full block body for anything longer or split across lines
- Avoid `DateTime.UtcNow`; prefer injected `TimeProvider`.
- Keep public docs and comments honest to the current code.
- Always document new interfaces, their methods and arguments along with documenting every property on domain models using `` block.
- Always treat warnings are errors in the solutions and ensure every warning is addressed.
- Always learn from my prompts, preference and styles and update the `copilot-instructions.md` file with any new preferences that I share in the future.
+- Prefer SOLID and DRY refactors that consolidate duplicated provider, transport, or store logic into shared abstractions before adding new one-off implementations.
+- Favor additive shared infrastructure first, then migrate consumers in behavior-safe steps when a full replacement is too risky for a single change.
+- When working in framework code meant for external adoption, optimize for consistency and long-term maintainability across providers and hosts, not just local fixes.
+- For optional provider integrations in sample hosts, do not eagerly read validated options in UI setup paths when an unconfigured provider should simply appear unavailable rather than crash the page.
## Runtime notes
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 6e4e4e2a..b1e6a080 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,17 +4,16 @@
true1.2.0
-
-
+
-
+
@@ -31,13 +30,13 @@
-
+
@@ -49,29 +48,26 @@
-
+
-
-
+
-
-
@@ -79,9 +75,8 @@
-
-
+
\ No newline at end of file
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IAIChatSessionHandler.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IAIChatSessionHandler.cs
index da7a766b..403185af 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IAIChatSessionHandler.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IAIChatSessionHandler.cs
@@ -22,5 +22,6 @@ public interface IAIChatSessionHandler : ICatalogEntryHandler
/// profile, session, messages, and an scoped
/// to the current request.
///
- Task MessageCompletedAsync(ChatMessageCompletedContext context);
+ /// The token to monitor for cancellation requests.
+ Task MessageCompletedAsync(ChatMessageCompletedContext context, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IAIChatSessionManager.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IAIChatSessionManager.cs
index 9e598f6e..8771a649 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IAIChatSessionManager.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IAIChatSessionManager.cs
@@ -12,21 +12,23 @@ public interface IAIChatSessionManager
/// Asynchronously retrieves an existing AI chat session by its session ID.
///
/// The unique identifier of the chat session.
+ /// The token to monitor for cancellation requests.
///
/// A task representing the asynchronous operation. The task result is the if found,
/// or null if no session with the specified ID exists.
///
- Task FindByIdAsync(string id);
+ Task FindByIdAsync(string id, CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves an existing AI chat session by its session ID after applying ownership check.
///
/// The unique identifier of the chat session. Must not be null or empty.
+ /// The token to monitor for cancellation requests.
///
/// A task representing the asynchronous operation. The task result is the if found,
/// or null if no session with the specified session ID exists.
///
- Task FindAsync(string id);
+ Task FindAsync(string id, CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves a list of top AI chat sessions based on the provided pagination parameters and query context.
@@ -34,48 +36,53 @@ public interface IAIChatSessionManager
/// The page number to retrieve (1-based index). Must be greater than 0.
/// The number of sessions to retrieve per page. Must be greater than 0.
/// The context used to filter and order the chat sessions. Must not be null.
+ /// The token to monitor for cancellation requests.
///
/// A task representing the asynchronous operation. The task result is a list of objects,
/// which represent the top sessions based on the query context and pagination parameters.
///
- Task PageAsync(int page, int pageSize, AIChatSessionQueryContext context = null);
+ Task PageAsync(int page, int pageSize, AIChatSessionQueryContext context = null, CancellationToken cancellationToken = default);
///
/// Asynchronously creates a new AI chat session for the specified AI chat profile.
///
/// The AI chat profile for which the new session will be created. Must not be null.
/// The request context
+ /// The token to monitor for cancellation requests.
///
/// A task representing the asynchronous operation. The task result is a new
/// associated with the provided profile.
///
- Task NewAsync(AIProfile profile, NewAIChatSessionContext context);
+ Task NewAsync(AIProfile profile, NewAIChatSessionContext context, CancellationToken cancellationToken = default);
///
/// Asynchronously saves or updates the specified AI chat session.
///
/// The AI chat session to save or update. Must not be null.
+ /// The token to monitor for cancellation requests.
///
/// A task representing the asynchronous operation. This method does not return any value.
///
- Task SaveAsync(AIChatSession chatSession);
+ Task SaveAsync(AIChatSession chatSession, CancellationToken cancellationToken = default);
///
/// Asynchronously deletes the specified AI chat session.
///
/// The unique identifier of the chat session to delete. Must not be null or empty.
+ /// The token to monitor for cancellation requests.
///
/// A task representing the asynchronous operation. The task result is true if the session was successfully deleted,
/// or false if the session was not found or could not be deleted.
///
- Task DeleteAsync(string sessionId);
+ Task DeleteAsync(string sessionId, CancellationToken cancellationToken = default);
///
/// Asynchronously deletes all AI chat sessions for the specified profile and current user.
///
/// The profile identifier to filter sessions. Must not be null or empty.
+ /// The token to monitor for cancellation requests.
///
/// A task representing the asynchronous operation. The task result is the number of sessions that were deleted.
///
- Task DeleteAllAsync(string profileId);
+ Task DeleteAllAsync(string profileId, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IChatInteractionSettingsHandler.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IChatInteractionSettingsHandler.cs
index b8111eae..1d2c9254 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IChatInteractionSettingsHandler.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Chat/IChatInteractionSettingsHandler.cs
@@ -20,12 +20,14 @@ public interface IChatInteractionSettingsHandler
///
/// The being updated.
/// The raw settings payload from the client.
- Task UpdatingAsync(ChatInteraction interaction, JsonElement settings);
+ /// The token to monitor for cancellation requests.
+ Task UpdatingAsync(ChatInteraction interaction, JsonElement settings, CancellationToken cancellationToken = default);
///
/// Called after the has been persisted.
///
/// The updated .
/// The raw settings payload from the client.
- Task UpdatedAsync(ChatInteraction interaction, JsonElement settings);
+ /// The token to monitor for cancellation requests.
+ Task UpdatedAsync(ChatInteraction interaction, JsonElement settings, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionContextBuilder.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionContextBuilder.cs
index 0e987706..6f52b82d 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionContextBuilder.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionContextBuilder.cs
@@ -19,10 +19,11 @@ public interface IAICompletionContextBuilder
///
/// The resource object (e.g., or ChatInteraction) used to seed and configure the completion context. Must not be .
/// An optional delegate to override or fine-tune the context after handlers have run BuildingAsync but before BuiltAsync.
+ /// The token to monitor for cancellation requests.
/// A task that completes with the fully built .
/// Thrown if is .
///
///
///
- ValueTask BuildAsync(object resource, Action configure = null);
+ ValueTask BuildAsync(object resource, Action configure = null, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionHandler.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionHandler.cs
index 01a3521b..f479b102 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionHandler.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionHandler.cs
@@ -13,13 +13,15 @@ public interface IAICompletionHandler
/// Handles a received message asynchronously.
///
/// The context containing details of the received message.
+ /// The token to monitor for cancellation requests.
/// A task that represents the asynchronous operation.
- Task ReceivedMessageAsync(ReceivedMessageContext context);
+ Task ReceivedMessageAsync(ReceivedMessageContext context, CancellationToken cancellationToken = default);
///
/// Handles a received update asynchronously.
///
/// The context containing details of the received update.
+ /// The token to monitor for cancellation requests.
/// A task that represents the asynchronous operation.
- Task ReceivedUpdateAsync(ReceivedUpdateContext context);
+ Task ReceivedUpdateAsync(ReceivedUpdateContext context, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionServiceHandler.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionServiceHandler.cs
index 6075655b..def851de 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionServiceHandler.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Completions/IAICompletionServiceHandler.cs
@@ -16,6 +16,7 @@ public interface IAICompletionServiceHandler
///
/// The that provides access to request-specific options and settings.
///
+ /// The token to monitor for cancellation requests.
/// A task that represents the asynchronous operation.
- Task ConfigureAsync(CompletionServiceConfigureContext context);
+ Task ConfigureAsync(CompletionServiceConfigureContext context, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Deployments/IAIDeploymentManager.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Deployments/IAIDeploymentManager.cs
index 5474d8eb..7869511e 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Deployments/IAIDeploymentManager.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Deployments/IAIDeploymentManager.cs
@@ -14,21 +14,23 @@ public interface IAIDeploymentManager : INamedSourceCatalogManager
/// Asynchronously retrieves a list of model deployments for the specified client.
///
/// The name of the client. Must not be null or empty.
+ /// The token to monitor for cancellation requests.
///
/// A ValueTask that represents the asynchronous operation. The result is an
/// containing the model deployments for the specified client.
///
- ValueTask> GetAllAsync(string clientName);
+ ValueTask> GetAllAsync(string clientName, CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves all deployments supporting the specified type.
///
/// The deployment type to filter by.
+ /// The token to monitor for cancellation requests.
///
/// A ValueTask that represents the asynchronous operation. The result is an
/// containing all deployments matching the specified type.
///
- ValueTask> GetByTypeAsync(AIDeploymentType type);
+ ValueTask> GetByTypeAsync(AIDeploymentType type, CancellationToken cancellationToken = default);
///
/// Resolves the default deployment of a given type for a specific client.
@@ -37,7 +39,8 @@ public interface IAIDeploymentManager : INamedSourceCatalogManager
///
/// The name of the client to resolve the default deployment for.
/// The deployment type to filter by.
- ValueTask GetDefaultAsync(string clientName, AIDeploymentType type);
+ /// The token to monitor for cancellation requests.
+ ValueTask GetDefaultAsync(string clientName, AIDeploymentType type, CancellationToken cancellationToken = default);
///
/// Resolves a deployment using the full fallback chain:
@@ -49,7 +52,8 @@ public interface IAIDeploymentManager : INamedSourceCatalogManager
/// The deployment type to resolve.
/// The optional deployment name to look up directly.
/// The optional client name to scope the resolution.
- ValueTask ResolveOrDefaultAsync(AIDeploymentType type, string deploymentName = null, string clientName = null);
+ /// The token to monitor for cancellation requests.
+ ValueTask ResolveOrDefaultAsync(AIDeploymentType type, string deploymentName = null, string clientName = null, CancellationToken cancellationToken = default);
///
/// Gets all deployments of a given type, optionally filtered by client.
@@ -57,5 +61,6 @@ public interface IAIDeploymentManager : INamedSourceCatalogManager
///
/// The deployment type to filter by.
/// The optional client name to further filter results.
- ValueTask> GetAllByTypeAsync(AIDeploymentType type, string clientName = null);
+ /// The token to monitor for cancellation requests.
+ ValueTask> GetAllByTypeAsync(AIDeploymentType type, string clientName = null, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Exceptions/NoRegisteredCompletionClient.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Exceptions/NoRegisteredCompletionClient.cs
deleted file mode 100644
index 82b87770..00000000
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Exceptions/NoRegisteredCompletionClient.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace CrestApps.Core.AI.Exceptions;
-
-public class UnregisteredCompletionClientException : Exception
-{
- public UnregisteredCompletionClientException(string clientName)
- : base($"No registered completion client was found to match '{clientName}'.")
- {
- }
-}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Exceptions/UnregisteredCompletionClientException.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Exceptions/UnregisteredCompletionClientException.cs
new file mode 100644
index 00000000..e81a1717
--- /dev/null
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Exceptions/UnregisteredCompletionClientException.cs
@@ -0,0 +1,9 @@
+namespace CrestApps.Core.AI.Exceptions;
+
+public sealed class UnregisteredCompletionClientException : Exception
+{
+ public UnregisteredCompletionClientException(string clientName)
+ : base($"No registered completion client was found to match '{clientName}'.")
+ {
+ }
+}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AICompletionContextBuiltContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AICompletionContextBuiltContext.cs
index 3c026db7..98cf1eca 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AICompletionContextBuiltContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AICompletionContextBuiltContext.cs
@@ -33,6 +33,11 @@ public AICompletionContextBuiltContext(object resource, AICompletionContext cont
///
public object Resource { get; }
+ ///
+ /// Gets the resource as the specified type.
+ ///
+ public T GetResource() where T : class => Resource as T;
+
///
/// Gets the finalized .
///
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIDataSource.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIDataSource.cs
index b78003ab..e11f46a5 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIDataSource.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIDataSource.cs
@@ -5,10 +5,10 @@ namespace CrestApps.Core.AI.Models;
public sealed class AIDataSource : CatalogItem, IDisplayTextAwareModel, ICloneable
{
- [Obsolete("Do no use any more.")]
+ [Obsolete("Do not use any more.")]
public string ProfileSource { get; set; }
- [Obsolete("Do no use any more.")]
+ [Obsolete("Do not use any more.")]
public string Type { get; set; }
public string DisplayText { get; set; }
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfileExtensions.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfileExtensions.cs
index a32c3b01..e170a225 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfileExtensions.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfileExtensions.cs
@@ -1,6 +1,5 @@
using System.Text.Json;
using System.Text.Json.Nodes;
-using System.Text.Json.Serialization;
namespace CrestApps.Core.AI.Models;
@@ -9,22 +8,13 @@ namespace CrestApps.Core.AI.Models;
///
public static class AIProfileExtensions
{
- private static readonly JsonSerializerOptions _ignoreDefaultValuesSerializer = new()
- {
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- ReferenceHandler = ReferenceHandler.IgnoreCycles,
- PropertyNameCaseInsensitive = true,
- Converters =
- {
- new JsonStringEnumConverter()
- }
- };
+ private static JsonSerializerOptions _jsonOptions => ExtensibleEntityExtensions.JsonSerializerOptions;
///
/// Retrieves settings of type from the profile.
/// If the settings do not exist, a new instance of is returned.
///
- public static T GetSettings(this AIProfile profile)
+ public static T GetSettings(this AIProfile profile, JsonSerializerOptions jsonSerializerOptions = null)
where T : new()
{
if (profile.Settings == null)
@@ -39,13 +29,13 @@ public static T GetSettings(this AIProfile profile)
return new T();
}
- return node.Deserialize(_ignoreDefaultValuesSerializer) ?? new T();
+ return node.Deserialize(jsonSerializerOptions ?? _jsonOptions) ?? new T();
}
///
/// Attempts to retrieve settings of type from the profile.
///
- public static bool TryGetSettings(this AIProfile profile, out T settings)
+ public static bool TryGetSettings(this AIProfile profile, out T settings, JsonSerializerOptions jsonSerializerOptions = null)
where T : class
{
if (profile.Settings == null)
@@ -59,10 +49,11 @@ public static bool TryGetSettings(this AIProfile profile, out T settings)
if (node == null)
{
settings = null;
+
return false;
}
- settings = node.Deserialize(_ignoreDefaultValuesSerializer);
+ settings = node.Deserialize(jsonSerializerOptions ?? _jsonOptions);
return true;
}
@@ -70,22 +61,23 @@ public static bool TryGetSettings(this AIProfile profile, out T settings)
///
/// Alters existing settings or adds new settings of type if one does not exists.
///
- public static AIProfile AlterSettings(this AIProfile profile, Action setting)
+ public static AIProfile AlterSettings(this AIProfile profile, Action setting, JsonSerializerOptions jsonSerializerOptions = null)
where T : class, new()
{
var existingJObject = profile.Settings[typeof(T).Name] as JsonObject;
if (existingJObject == null)
{
- existingJObject = JsonExtensions.FromObject(new T(), _ignoreDefaultValuesSerializer);
+ existingJObject = JsonExtensions.FromObject(new T(), jsonSerializerOptions ?? _jsonOptions);
+
profile.Settings[typeof(T).Name] = existingJObject;
}
- var settingsToMerge = existingJObject.Deserialize(_ignoreDefaultValuesSerializer);
+ var settingsToMerge = existingJObject.Deserialize(jsonSerializerOptions ?? _jsonOptions);
setting(settingsToMerge);
- profile.Settings[typeof(T).Name] = JsonExtensions.FromObject(settingsToMerge, _ignoreDefaultValuesSerializer);
+ profile.Settings[typeof(T).Name] = JsonExtensions.FromObject(settingsToMerge, jsonSerializerOptions ?? _jsonOptions);
return profile;
}
@@ -93,11 +85,11 @@ public static AIProfile AlterSettings(this AIProfile profile, Action setti
///
/// Sets or replaces the settings of type in the profile.
///
- public static AIProfile WithSettings(this AIProfile profile, T settings)
+ public static AIProfile WithSettings(this AIProfile profile, T settings, JsonSerializerOptions jsonSerializerOptions = null)
{
ArgumentNullException.ThrowIfNull(settings);
- var jObject = JsonExtensions.FromObject(settings, _ignoreDefaultValuesSerializer);
+ var jObject = JsonExtensions.FromObject(settings, jsonSerializerOptions ?? _jsonOptions);
profile.Settings[typeof(T).Name] = jObject;
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfileTemplate.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfileTemplate.cs
index 65d4e172..fa501a4e 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfileTemplate.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfileTemplate.cs
@@ -5,7 +5,7 @@ namespace CrestApps.Core.AI.Models;
///
/// Represents a reusable template. The template holds only generic metadata;
-/// source-specific data is stored in
+/// source-specific data is stored in
/// via metadata classes such as or
/// .
///
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ChatNotificationActionContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ChatNotificationActionContext.cs
index 231cfd92..829d65db 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ChatNotificationActionContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ChatNotificationActionContext.cs
@@ -36,5 +36,9 @@ public sealed class ChatNotificationActionContext
///
/// Gets the scoped service provider for resolving dependencies.
///
+ ///
+ /// Prefer constructor injection over resolving services from this provider.
+ /// This property is provided for extensibility scenarios where constructor injection is not available.
+ ///
public required IServiceProvider Services { get; init; }
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/CompletionServiceConfigureContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/CompletionServiceConfigureContext.cs
index cafa9654..35d27ebb 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/CompletionServiceConfigureContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/CompletionServiceConfigureContext.cs
@@ -6,15 +6,13 @@ public sealed class CompletionServiceConfigureContext
{
public string ClientName { get; set; }
- public string ImplemenationName { get; set; }
-
public string DeploymentName { get; set; }
public bool IsStreaming { get; set; }
public ChatOptions ChatOptions { get; }
- public readonly AICompletionContext CompletionContext;
+ public AICompletionContext CompletionContext { get; }
public bool IsFunctionInvocationSupported { get; }
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ExportingAIProviderConnectionContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ExportingAIProviderConnectionContext.cs
index aecf1326..cbcb0f23 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ExportingAIProviderConnectionContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ExportingAIProviderConnectionContext.cs
@@ -4,9 +4,9 @@ namespace CrestApps.Core.AI.Models;
public class ExportingAIProviderConnectionContext
{
- public readonly AIProviderConnection Connection;
+ public AIProviderConnection Connection { get; }
- public readonly JsonObject ExportData;
+ public JsonObject ExportData { get; }
public ExportingAIProviderConnectionContext(AIProviderConnection connection, JsonObject exportData)
{
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ExternalChatRelayEventType.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ExternalChatRelayEventTypes.cs
similarity index 100%
rename from src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ExternalChatRelayEventType.cs
rename to src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ExternalChatRelayEventTypes.cs
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/InitializingAIProviderConnectionContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/InitializingAIProviderConnectionContext.cs
index 27210cda..21c96ec0 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/InitializingAIProviderConnectionContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/InitializingAIProviderConnectionContext.cs
@@ -2,9 +2,9 @@ namespace CrestApps.Core.AI.Models;
public class InitializingAIProviderConnectionContext
{
- public readonly Dictionary Values = [];
+ public Dictionary Values { get; } = [];
- public readonly AIProviderConnection Connection;
+ public AIProviderConnection Connection { get; }
public InitializingAIProviderConnectionContext(AIProviderConnection connection)
{
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestrationContextBuildingContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestrationContextBuildingContext.cs
index bf5a280f..100eb607 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestrationContextBuildingContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestrationContextBuildingContext.cs
@@ -33,6 +33,11 @@ public OrchestrationContextBuildingContext(object resource, OrchestrationContext
///
public object Resource { get; }
+ ///
+ /// Gets the resource as the specified type.
+ ///
+ public T GetResource() where T : class => Resource as T;
+
///
/// Gets the mutable being built. Handlers may mutate this instance.
///
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestrationContextBuiltContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestrationContextBuiltContext.cs
index f59ef461..dd00a4e9 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestrationContextBuiltContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestrationContextBuiltContext.cs
@@ -33,6 +33,11 @@ public OrchestrationContextBuiltContext(object resource, OrchestrationContext co
///
public object Resource { get; }
+ ///
+ /// Gets the resource as the specified type.
+ ///
+ public T GetResource() where T : class => Resource as T;
+
///
/// Gets the finalized .
///
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestratorContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestratorContext.cs
index 4e420079..e72e56fc 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestratorContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/OrchestratorContext.cs
@@ -41,8 +41,11 @@ public sealed class OrchestrationContext
///
/// Gets or sets the scoped service provider for this orchestration session.
- /// Allows orchestrators to resolve services without constructor injection.
///
+ ///
+ /// Prefer constructor injection over resolving services from this provider.
+ /// This property is provided for extensibility scenarios where constructor injection is not available.
+ ///
public IServiceProvider ServiceProvider { get; set; }
///
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/PreemptiveRagContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/PreemptiveRagContext.cs
index 30ad17f7..fe0224cb 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/PreemptiveRagContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/PreemptiveRagContext.cs
@@ -29,6 +29,11 @@ public PreemptiveRagContext(OrchestrationContext orchestrationContext, object re
///
public object Resource { get; }
+ ///
+ /// Gets the resource as the specified type.
+ ///
+ public T GetResource() where T : class => Resource as T;
+
///
/// Gets the focused search queries extracted by the preemptive search query provider.
///
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ProfileTemplateMetadata.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ProfileTemplateMetadata.cs
index 2d2ab4e9..e2f45989 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ProfileTemplateMetadata.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/ProfileTemplateMetadata.cs
@@ -5,7 +5,7 @@ namespace CrestApps.Core.AI.Models;
///
/// Metadata for templates with a "Profile" source.
-/// Stored in the template's via
+/// Stored in the template's via
/// Put<ProfileTemplateMetadata> / As<ProfileTemplateMetadata>.
///
public sealed class ProfileTemplateMetadata
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/SystemPromptTemplateMetadata.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/SystemPromptTemplateMetadata.cs
index 11e152a4..5422c5c8 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/SystemPromptTemplateMetadata.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/SystemPromptTemplateMetadata.cs
@@ -2,7 +2,7 @@ namespace CrestApps.Core.AI.Models;
///
/// Metadata for templates with a "SystemPrompt" source.
-/// Stored in the template's via
+/// Stored in the template's via
/// Put<SystemPromptTemplateMetadata> / As<SystemPromptTemplateMetadata>.
///
public sealed class SystemPromptTemplateMetadata
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileManager.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileManager.cs
index 1343fec3..4b59019f 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileManager.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileManager.cs
@@ -14,6 +14,7 @@ public interface IAIProfileManager : INamedCatalogManager
/// Asynchronously retrieves a collection of AI chat profiles of the specified type.
///
/// The type of AI chat profiles to retrieve.
+ /// The token to monitor for cancellation requests.
/// A ValueTask that represents the asynchronous operation. The result is an enumerable collection of AIProfile objects matching the specified type.
- ValueTask> GetAsync(AIProfileType type);
+ ValueTask> GetAsync(AIProfileType type, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileTemplateManager.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileTemplateManager.cs
index 2fa904f5..16d0a3bb 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileTemplateManager.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileTemplateManager.cs
@@ -13,5 +13,5 @@ public interface IAIProfileTemplateManager : INamedSourceCatalogManager
- ValueTask> GetListableAsync();
+ ValueTask> GetListableAsync(CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileTemplateProvider.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileTemplateProvider.cs
index 5351abb5..744ae4f1 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileTemplateProvider.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Profiles/IAIProfileTemplateProvider.cs
@@ -10,5 +10,5 @@ public interface IAIProfileTemplateProvider
///
/// Gets all profile templates from this provider.
///
- Task> GetTemplatesAsync();
+ Task> GetTemplatesAsync(CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/ResponseHandling/ChatResponseHandlerContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/ResponseHandling/ChatResponseHandlerContext.cs
index 419dde79..dcc47c23 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/ResponseHandling/ChatResponseHandlerContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/ResponseHandling/ChatResponseHandlerContext.cs
@@ -42,6 +42,10 @@ public sealed class ChatResponseHandlerContext
///
/// Gets the scoped service provider for resolving services.
///
+ ///
+ /// Prefer constructor injection over resolving services from this provider.
+ /// This property is provided for extensibility scenarios where constructor injection is not available.
+ ///
public required IServiceProvider Services { get; init; }
///
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Tooling/AIToolExecutionContext.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Tooling/AIToolExecutionContext.cs
index 5e88c047..0d6200d5 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Tooling/AIToolExecutionContext.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Tooling/AIToolExecutionContext.cs
@@ -23,6 +23,12 @@ public sealed class AIToolExecutionContext
///
public object Resource { get; }
+ ///
+ /// Gets the resource as the specified type.
+ ///
+ public T GetResource()
+ where T : class => Resource as T;
+
public AIToolExecutionContext(object resource)
{
ArgumentNullException.ThrowIfNull(resource);
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/ExtensibleEntityExtensions.cs b/src/Abstractions/CrestApps.Core.Abstractions/ExtensibleEntityExtensions.cs
index 3b32236d..3b7c0877 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/ExtensibleEntityExtensions.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/ExtensibleEntityExtensions.cs
@@ -35,7 +35,7 @@ public static JsonSerializerOptions JsonSerializerOptions
///
/// Gets a strongly-typed object stored in the entity's properties.
///
- public static T GetOrCreate(this ExtensibleEntity entity)
+ public static T GetOrCreate(this ExtensibleEntity entity, JsonSerializerOptions jsonSerializerOptions = null)
where T : new()
{
ArgumentNullException.ThrowIfNull(entity);
@@ -43,20 +43,20 @@ public static T GetOrCreate(this ExtensibleEntity entity)
var key = typeof(T).Name;
return entity.Properties.TryGetValue(key, out var value)
- ? DeserializeValue(value) ?? new T()
+ ? DeserializeValue(value, jsonSerializerOptions ?? _jsonOptions) ?? new T()
: new T();
}
///
/// Gets a strongly-typed object stored in the entity's properties, or null if not found.
///
- public static T Get(this ExtensibleEntity entity, string name)
+ public static T Get(this ExtensibleEntity entity, string name, JsonSerializerOptions jsonSerializerOptions = null)
{
ArgumentNullException.ThrowIfNull(entity);
ArgumentException.ThrowIfNullOrEmpty(name);
return entity.Properties.TryGetValue(name, out var value)
- ? DeserializeValue(value)
+ ? DeserializeValue(value, jsonSerializerOptions ?? _jsonOptions)
: default;
}
@@ -90,7 +90,7 @@ public static ExtensibleEntity Put(this ExtensibleEntity entity, string name, ob
/// Tries to get a strongly-typed object stored in the entity's properties.
/// Returns true if a non-null value was found and deserialized.
///
- public static bool TryGet(this ExtensibleEntity entity, out T result)
+ public static bool TryGet(this ExtensibleEntity entity, out T result, JsonSerializerOptions jsonSerializerOptions = null)
where T : class
{
ArgumentNullException.ThrowIfNull(entity);
@@ -99,11 +99,13 @@ public static bool TryGet(this ExtensibleEntity entity, out T result)
if (entity.Properties.TryGetValue(key, out var value) && value is not null)
{
- result = DeserializeValue(value);
+ result = DeserializeValue(value, jsonSerializerOptions ?? _jsonOptions);
+
return result is not null;
}
result = default;
+
return false;
}
@@ -121,14 +123,16 @@ public static bool Has(this ExtensibleEntity entity)
/// Modifies a stored object in-place. If no object exists, a new instance is created,
/// modified, and stored.
///
- public static ExtensibleEntity Alter(this ExtensibleEntity entity, Action alter)
+ public static ExtensibleEntity Alter(this ExtensibleEntity entity, Action alter, JsonSerializerOptions jsonSerializerOptions = null)
where T : new()
{
ArgumentNullException.ThrowIfNull(entity);
ArgumentNullException.ThrowIfNull(alter);
- var obj = entity.GetOrCreate();
+ var obj = entity.GetOrCreate(jsonSerializerOptions ?? _jsonOptions);
+
alter(obj);
+
entity.Put(obj);
return entity;
@@ -146,7 +150,7 @@ public static ExtensibleEntity Remove(this ExtensibleEntity entity)
return entity;
}
- private static T DeserializeValue(object value)
+ private static T DeserializeValue(object value, JsonSerializerOptions jsonSerializerOptions = null)
{
if (value is null)
{
@@ -165,15 +169,16 @@ private static T DeserializeValue(object value)
return default;
}
- return jsonElement.Deserialize(_jsonOptions);
+ return jsonElement.Deserialize(jsonSerializerOptions ?? _jsonOptions);
}
if (value is JsonNode jsonNode)
{
- return jsonNode.Deserialize(_jsonOptions);
+ return jsonNode.Deserialize(jsonSerializerOptions ?? _jsonOptions);
}
- var json = JsonSerializer.Serialize(value, _jsonOptions);
- return JsonSerializer.Deserialize(json, _jsonOptions);
+ var json = JsonSerializer.Serialize(value, jsonSerializerOptions ?? _jsonOptions);
+
+ return JsonSerializer.Deserialize(json, jsonSerializerOptions ?? _jsonOptions);
}
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/IDisplayTextAwareModel.cs b/src/Abstractions/CrestApps.Core.Abstractions/IDisplayTextAwareModel.cs
index 288f70ef..8c6353fc 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/IDisplayTextAwareModel.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/IDisplayTextAwareModel.cs
@@ -7,7 +7,7 @@ namespace CrestApps.Core;
public interface IDisplayTextAwareModel
{
///
- /// Gets or sets the human-readable display text for this model.
+ /// Gets the human-readable display text for this model.
///
- string DisplayText { get; set; }
+ string DisplayText { get; }
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/INameAwareModel.cs b/src/Abstractions/CrestApps.Core.Abstractions/INameAwareModel.cs
index f78b296d..f8160a57 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/INameAwareModel.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/INameAwareModel.cs
@@ -7,7 +7,7 @@ namespace CrestApps.Core;
public interface INameAwareModel
{
///
- /// Gets or sets the unique technical name for this model.
+ /// Gets the unique technical name for this model.
///
- string Name { get; set; }
+ string Name { get; }
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/ISourceAwareModel.cs b/src/Abstractions/CrestApps.Core.Abstractions/ISourceAwareModel.cs
index e614c623..07939fd1 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/ISourceAwareModel.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/ISourceAwareModel.cs
@@ -9,5 +9,9 @@ public interface ISourceAwareModel
///
/// Gets or sets the name of the source or provider that owns this model.
///
+ ///
+ /// The setter is retained because framework code assigns this property through
+ /// the interface type constraint (e.g., in SourceCatalogManager).
+ ///
string Source { get; set; }
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalog.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalog.cs
index be7b305c..f0b33936 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalog.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalog.cs
@@ -11,19 +11,22 @@ public interface ICatalog : IReadCatalog
/// Asynchronously deletes the specified entry from the catalog.
///
/// The entry to delete.
+ /// The token to monitor for cancellation requests.
/// if the entry was successfully deleted; otherwise, .
- ValueTask DeleteAsync(T entry);
+ ValueTask DeleteAsync(T entry, CancellationToken cancellationToken = default);
///
/// Asynchronously creates the specified entry in the catalog.
///
/// The entry to create.
- ValueTask CreateAsync(T entry);
+ /// The token to monitor for cancellation requests.
+ ValueTask CreateAsync(T entry, CancellationToken cancellationToken = default);
///
/// Asynchronously updates the specified entry in the catalog.
///
/// The entry to update.
- ValueTask UpdateAsync(T entry);
+ /// The token to monitor for cancellation requests.
+ ValueTask UpdateAsync(T entry, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogEntryHandler.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogEntryHandler.cs
index 95a93dd1..728e7be3 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogEntryHandler.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogEntryHandler.cs
@@ -14,65 +14,76 @@ public interface ICatalogEntryHandler
/// Called when a catalog entry is being initialized with default values.
///
/// The context containing the entry being initialized.
- Task InitializingAsync(InitializingContext context);
+ /// The token to monitor for cancellation requests.
+ Task InitializingAsync(InitializingContext context, CancellationToken cancellationToken = default);
///
/// Called after a catalog entry has been initialized with default values.
///
/// The context containing the initialized entry.
- Task InitializedAsync(InitializedContext context);
+ /// The token to monitor for cancellation requests.
+ Task InitializedAsync(InitializedContext context, CancellationToken cancellationToken = default);
///
/// Called after a catalog entry has been loaded from the store.
///
/// The context containing the loaded entry.
- Task LoadedAsync(LoadedContext context);
+ /// The token to monitor for cancellation requests.
+ Task LoadedAsync(LoadedContext context, CancellationToken cancellationToken = default);
///
/// Called when a catalog entry is about to be validated.
///
/// The context containing the entry to validate.
- Task ValidatingAsync(ValidatingContext context);
+ /// The token to monitor for cancellation requests.
+ Task ValidatingAsync(ValidatingContext context, CancellationToken cancellationToken = default);
///
/// Called after a catalog entry has been validated.
///
/// The context containing the validated entry and any validation results.
- Task ValidatedAsync(ValidatedContext context);
+ /// The token to monitor for cancellation requests.
+ Task ValidatedAsync(ValidatedContext context, CancellationToken cancellationToken = default);
///
/// Called when a catalog entry is about to be deleted.
///
/// The context containing the entry to delete.
- Task DeletingAsync(DeletingContext context);
+ /// The token to monitor for cancellation requests.
+ Task DeletingAsync(DeletingContext context, CancellationToken cancellationToken = default);
///
/// Called after a catalog entry has been deleted.
///
/// The context containing the deleted entry.
- Task DeletedAsync(DeletedContext context);
+ /// The token to monitor for cancellation requests.
+ Task DeletedAsync(DeletedContext context, CancellationToken cancellationToken = default);
///
/// Called when a catalog entry is about to be updated.
///
/// The context containing the entry to update.
- Task UpdatingAsync(UpdatingContext context);
+ /// The token to monitor for cancellation requests.
+ Task UpdatingAsync(UpdatingContext context, CancellationToken cancellationToken = default);
///
/// Called after a catalog entry has been updated.
///
/// The context containing the updated entry.
- Task UpdatedAsync(UpdatedContext context);
+ /// The token to monitor for cancellation requests.
+ Task UpdatedAsync(UpdatedContext context, CancellationToken cancellationToken = default);
///
/// Called when a catalog entry is about to be created.
///
/// The context containing the entry to create.
- Task CreatingAsync(CreatingContext context);
+ /// The token to monitor for cancellation requests.
+ Task CreatingAsync(CreatingContext context, CancellationToken cancellationToken = default);
///
/// Called after a catalog entry has been created.
///
/// The context containing the created entry.
- Task CreatedAsync(CreatedContext context);
+ /// The token to monitor for cancellation requests.
+ Task CreatedAsync(CreatedContext context, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogEventHandler.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogEventHandler.cs
new file mode 100644
index 00000000..d4939b70
--- /dev/null
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogEventHandler.cs
@@ -0,0 +1,115 @@
+using CrestApps.Core.Models;
+
+namespace CrestApps.Core.Services;
+
+///
+/// Handler invoked when a catalog entry is being created.
+///
+/// The type of catalog entry.
+public interface ICatalogCreatingHandler where T : class
+{
+ ///
+ /// Called when a catalog entry is about to be created.
+ ///
+ /// The context containing the entry to create.
+ /// The token to monitor for cancellation requests.
+ Task CreatingAsync(CreatingContext context, CancellationToken cancellationToken = default);
+}
+
+///
+/// Handler invoked after a catalog entry has been created.
+///
+/// The type of catalog entry.
+public interface ICatalogCreatedHandler where T : class
+{
+ ///
+ /// Called after a catalog entry has been created.
+ ///
+ /// The context containing the created entry.
+ /// The token to monitor for cancellation requests.
+ Task CreatedAsync(CreatedContext context, CancellationToken cancellationToken = default);
+}
+
+///
+/// Handler invoked when a catalog entry is being updated.
+///
+/// The type of catalog entry.
+public interface ICatalogUpdatingHandler where T : class
+{
+ ///
+ /// Called when a catalog entry is about to be updated.
+ ///
+ /// The context containing the entry to update.
+ /// The token to monitor for cancellation requests.
+ Task UpdatingAsync(UpdatingContext context, CancellationToken cancellationToken = default);
+}
+
+///
+/// Handler invoked after a catalog entry has been updated.
+///
+/// The type of catalog entry.
+public interface ICatalogUpdatedHandler where T : class
+{
+ ///
+ /// Called after a catalog entry has been updated.
+ ///
+ /// The context containing the updated entry.
+ /// The token to monitor for cancellation requests.
+ Task UpdatedAsync(UpdatedContext context, CancellationToken cancellationToken = default);
+}
+
+///
+/// Handler invoked when a catalog entry is being deleted.
+///
+/// The type of catalog entry.
+public interface ICatalogDeletingHandler where T : class
+{
+ ///
+ /// Called when a catalog entry is about to be deleted.
+ ///
+ /// The context containing the entry to delete.
+ /// The token to monitor for cancellation requests.
+ Task DeletingAsync(DeletingContext context, CancellationToken cancellationToken = default);
+}
+
+///
+/// Handler invoked after a catalog entry has been deleted.
+///
+/// The type of catalog entry.
+public interface ICatalogDeletedHandler where T : class
+{
+ ///
+ /// Called after a catalog entry has been deleted.
+ ///
+ /// The context containing the deleted entry.
+ /// The token to monitor for cancellation requests.
+ Task DeletedAsync(DeletedContext context, CancellationToken cancellationToken = default);
+}
+
+///
+/// Handler invoked when a catalog entry is being validated.
+///
+/// The type of catalog entry.
+public interface ICatalogValidatingHandler where T : class
+{
+ ///
+ /// Called when a catalog entry is about to be validated.
+ ///
+ /// The context containing the entry to validate.
+ /// The token to monitor for cancellation requests.
+ Task ValidatingAsync(ValidatingContext context, CancellationToken cancellationToken = default);
+}
+
+///
+/// Handler invoked after a catalog entry has been validated.
+///
+/// The type of catalog entry.
+public interface ICatalogValidatedHandler where T : class
+{
+ ///
+ /// Called after a catalog entry has been validated.
+ ///
+ /// The context containing the validated entry and any validation results.
+ /// The token to monitor for cancellation requests.
+ Task ValidatedAsync(ValidatedContext context, CancellationToken cancellationToken = default);
+}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogManager.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogManager.cs
index 7fb6a065..e98dfd40 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogManager.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/ICatalogManager.cs
@@ -15,33 +15,38 @@ public interface ICatalogManager : IReadCatalogManager
/// Asynchronously deletes the specified model from the catalog.
///
/// The model to delete.
+ /// The token to monitor for cancellation requests.
/// if the model was successfully deleted; otherwise, .
- ValueTask DeleteAsync(T model);
+ ValueTask DeleteAsync(T model, CancellationToken cancellationToken = default);
///
/// Asynchronously creates a new model instance, optionally populating it from JSON data.
///
/// Optional JSON data to seed the new model.
+ /// The token to monitor for cancellation requests.
/// A newly created and initialized model instance.
- ValueTask NewAsync(JsonNode data = null);
+ ValueTask NewAsync(JsonNode data = null, CancellationToken cancellationToken = default);
///
/// Asynchronously creates the specified model in the catalog.
///
/// The model to create.
- ValueTask CreateAsync(T model);
+ /// The token to monitor for cancellation requests.
+ ValueTask CreateAsync(T model, CancellationToken cancellationToken = default);
///
/// Asynchronously updates the specified model in the catalog, optionally merging changes from JSON data.
///
/// The model to update.
/// Optional JSON data containing fields to merge into the model.
- ValueTask UpdateAsync(T model, JsonNode data = null);
+ /// The token to monitor for cancellation requests.
+ ValueTask UpdateAsync(T model, JsonNode data = null, CancellationToken cancellationToken = default);
///
/// Asynchronously validates the specified model and returns the validation result.
///
/// The model to validate.
+ /// The token to monitor for cancellation requests.
/// The validation result details indicating success or failure with error messages.
- ValueTask ValidateAsync(T model);
+ ValueTask ValidateAsync(T model, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalog.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalog.cs
index d9163763..7f6b9cc6 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalog.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalog.cs
@@ -13,6 +13,7 @@ public interface INamedCatalog : ICatalog
/// Asynchronously finds a catalog entry by its unique name.
///
/// The unique name of the entry to find.
+ /// The token to monitor for cancellation requests.
/// The matching entry, or if no entry with the specified name exists.
- ValueTask FindByNameAsync(string name);
+ ValueTask FindByNameAsync(string name, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalogManager.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalogManager.cs
index 4f80f9e5..e9ef0015 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalogManager.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalogManager.cs
@@ -13,6 +13,7 @@ public interface INamedCatalogManager : ICatalogManager
/// Asynchronously finds a catalog entry by its unique name.
///
/// The unique name of the entry to find.
+ /// The token to monitor for cancellation requests.
/// The matching entry, or if no entry with the specified name exists.
- ValueTask FindByNameAsync(string name);
+ ValueTask FindByNameAsync(string name, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalogSource.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalogSource.cs
index 94c4bcd9..87b9735b 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalogSource.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedCatalogSource.cs
@@ -22,6 +22,7 @@ public interface INamedCatalogSource
/// Entries already collected from higher-priority sources, allowing this source
/// to skip entries whose names conflict with existing ones.
///
+ /// The token to monitor for cancellation requests.
/// A read-only collection of entries from this source.
- ValueTask> GetEntriesAsync(IReadOnlyCollection knownEntries);
+ ValueTask> GetEntriesAsync(IReadOnlyCollection knownEntries, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedSourceCatalog.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedSourceCatalog.cs
index 56278736..6ca7a1a1 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedSourceCatalog.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedSourceCatalog.cs
@@ -14,6 +14,7 @@ public interface INamedSourceCatalog : INamedCatalog, ISourceCatalog
///
/// The unique name of the entry.
/// The source or provider name of the entry.
+ /// The token to monitor for cancellation requests.
/// The matching entry, or if not found.
- ValueTask GetAsync(string name, string source);
+ ValueTask GetAsync(string name, string source, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedSourceCatalogManager.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedSourceCatalogManager.cs
index 276b06cd..d9b26c24 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedSourceCatalogManager.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/INamedSourceCatalogManager.cs
@@ -14,6 +14,7 @@ public interface INamedSourceCatalogManager : INamedCatalogManager, ISourc
///
/// The unique name of the entry.
/// The source or provider name of the entry.
+ /// The token to monitor for cancellation requests.
/// The matching entry, or if not found.
- ValueTask GetAsync(string name, string source);
+ ValueTask GetAsync(string name, string source, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/IReadCatalog.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/IReadCatalog.cs
index 1f4652e5..4bdc15d3 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/IReadCatalog.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/IReadCatalog.cs
@@ -13,21 +13,24 @@ public interface IReadCatalog
/// Asynchronously finds a catalog entry by its unique identifier.
///
/// The unique identifier of the entry.
+ /// The token to monitor for cancellation requests.
/// The matching entry, or if not found.
- ValueTask FindByIdAsync(string id);
+ ValueTask FindByIdAsync(string id, CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves all entries in the catalog.
///
+ /// The token to monitor for cancellation requests.
/// A read-only collection of all catalog entries.
- ValueTask> GetAllAsync();
+ ValueTask> GetAllAsync(CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves catalog entries matching the specified identifiers.
///
/// The identifiers of the entries to retrieve.
+ /// The token to monitor for cancellation requests.
/// A read-only collection of matching entries.
- ValueTask> GetAsync(IEnumerable ids);
+ ValueTask> GetAsync(IEnumerable ids, CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves a paginated subset of catalog entries using the specified query context.
@@ -36,7 +39,8 @@ public interface IReadCatalog
/// The one-based page number to retrieve.
/// The number of entries per page.
/// The query context used to filter and order results.
+ /// The token to monitor for cancellation requests.
/// A page result containing the entries and total count.
- ValueTask> PageAsync(int page, int pageSize, TQuery context)
+ ValueTask> PageAsync(int page, int pageSize, TQuery context, CancellationToken cancellationToken = default)
where TQuery : QueryContext;
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/IReadCatalogManager.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/IReadCatalogManager.cs
index 4a17ac9b..609ea408 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/IReadCatalogManager.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/IReadCatalogManager.cs
@@ -13,14 +13,16 @@ public interface IReadCatalogManager
/// Asynchronously finds a catalog entry by its unique identifier.
///
/// The unique identifier of the entry.
+ /// The token to monitor for cancellation requests.
/// The matching entry, or if not found.
- ValueTask FindByIdAsync(string id);
+ ValueTask FindByIdAsync(string id, CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves all entries in the catalog.
///
+ /// The token to monitor for cancellation requests.
/// An enumerable of all catalog entries.
- ValueTask> GetAllAsync();
+ ValueTask> GetAllAsync(CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves a paginated subset of catalog entries using the specified query context.
@@ -29,7 +31,8 @@ public interface IReadCatalogManager
/// The one-based page number to retrieve.
/// The number of entries per page.
/// The query context used to filter and order results.
+ /// The token to monitor for cancellation requests.
/// A page result containing the entries and total count.
- ValueTask> PageAsync(int page, int pageSize, TQuery context)
+ ValueTask> PageAsync(int page, int pageSize, TQuery context, CancellationToken cancellationToken = default)
where TQuery : QueryContext;
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/ISourceCatalog.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/ISourceCatalog.cs
index c7dd8a50..426e2a6e 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/ISourceCatalog.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/ISourceCatalog.cs
@@ -12,6 +12,7 @@ public interface ISourceCatalog : ICatalog
/// Asynchronously retrieves all catalog entries belonging to the specified source.
///
/// The source or provider name to filter by.
+ /// The token to monitor for cancellation requests.
/// A read-only collection of entries matching the specified source.
- ValueTask> GetAsync(string source);
+ ValueTask> GetAsync(string source, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/ISourceCatalogManager.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/ISourceCatalogManager.cs
index 574d981e..a870c26f 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/ISourceCatalogManager.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/ISourceCatalogManager.cs
@@ -16,20 +16,23 @@ public interface ISourceCatalogManager : ICatalogManager
///
/// The source or provider name to assign to the new model.
/// Optional JSON data to seed the new model.
+ /// The token to monitor for cancellation requests.
/// A newly created and initialized model instance assigned to the specified source.
- ValueTask NewAsync(string source, JsonNode data = null);
+ ValueTask NewAsync(string source, JsonNode data = null, CancellationToken cancellationToken = default);
///
/// Asynchronously retrieves all catalog entries belonging to the specified source.
///
/// The source or provider name to filter by.
+ /// The token to monitor for cancellation requests.
/// An enumerable of entries matching the specified source.
- ValueTask> GetAsync(string source);
+ ValueTask> GetAsync(string source, CancellationToken cancellationToken = default);
///
/// Asynchronously finds all catalog entries that belong to the specified source.
///
/// The source or provider name to search for.
+ /// The token to monitor for cancellation requests.
/// An enumerable of entries matching the specified source.
- ValueTask> FindBySourceAsync(string source);
+ ValueTask> FindBySourceAsync(string source, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/IWritableNamedCatalogSource.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/IWritableNamedCatalogSource.cs
index 93029f64..96a3fb5c 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/IWritableNamedCatalogSource.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/IWritableNamedCatalogSource.cs
@@ -13,18 +13,21 @@ public interface IWritableNamedCatalogSource : INamedCatalogSource
/// Asynchronously deletes the specified entry from this source.
///
/// The entry to delete.
+ /// The token to monitor for cancellation requests.
/// if the entry was successfully deleted; otherwise, .
- ValueTask DeleteAsync(T entry);
+ ValueTask DeleteAsync(T entry, CancellationToken cancellationToken = default);
///
/// Asynchronously creates the specified entry in this source.
///
/// The entry to create.
- ValueTask CreateAsync(T entry);
+ /// The token to monitor for cancellation requests.
+ ValueTask CreateAsync(T entry, CancellationToken cancellationToken = default);
///
/// Asynchronously updates the specified entry in this source.
///
/// The entry to update.
- ValueTask UpdateAsync(T entry);
+ /// The token to monitor for cancellation requests.
+ ValueTask UpdateAsync(T entry, CancellationToken cancellationToken = default);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/WritableCatalogBindingSource.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/WritableCatalogBindingSource.cs
index 0acd019c..09dabc8d 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/WritableCatalogBindingSource.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/WritableCatalogBindingSource.cs
@@ -21,15 +21,15 @@ public WritableCatalogBindingSource(INamedSourceCatalog inner)
///
public int Order => 0;
- public ValueTask> GetEntriesAsync(IReadOnlyCollection knownEntries)
- => _inner.GetAllAsync();
+ public ValueTask> GetEntriesAsync(IReadOnlyCollection knownEntries, CancellationToken cancellationToken = default)
+ => _inner.GetAllAsync(cancellationToken);
- public ValueTask DeleteAsync(T entry)
- => _inner.DeleteAsync(entry);
+ public ValueTask DeleteAsync(T entry, CancellationToken cancellationToken = default)
+ => _inner.DeleteAsync(entry, cancellationToken);
- public ValueTask CreateAsync(T entry)
- => _inner.CreateAsync(entry);
+ public ValueTask CreateAsync(T entry, CancellationToken cancellationToken = default)
+ => _inner.CreateAsync(entry, cancellationToken);
- public ValueTask UpdateAsync(T entry)
- => _inner.UpdateAsync(entry);
+ public ValueTask UpdateAsync(T entry, CancellationToken cancellationToken = default)
+ => _inner.UpdateAsync(entry, cancellationToken);
}
diff --git a/src/Abstractions/CrestApps.Core.Abstractions/Services/WritableNamedCatalogBindingSource.cs b/src/Abstractions/CrestApps.Core.Abstractions/Services/WritableNamedCatalogBindingSource.cs
index 55ce1867..090306d6 100644
--- a/src/Abstractions/CrestApps.Core.Abstractions/Services/WritableNamedCatalogBindingSource.cs
+++ b/src/Abstractions/CrestApps.Core.Abstractions/Services/WritableNamedCatalogBindingSource.cs
@@ -21,15 +21,15 @@ public WritableNamedCatalogBindingSource(INamedCatalog inner)
///
public int Order => 0;
- public ValueTask> GetEntriesAsync(IReadOnlyCollection knownEntries)
- => _inner.GetAllAsync();
+ public ValueTask> GetEntriesAsync(IReadOnlyCollection knownEntries, CancellationToken cancellationToken = default)
+ => _inner.GetAllAsync(cancellationToken);
- public ValueTask DeleteAsync(T entry)
- => _inner.DeleteAsync(entry);
+ public ValueTask DeleteAsync(T entry, CancellationToken cancellationToken = default)
+ => _inner.DeleteAsync(entry, cancellationToken);
- public ValueTask CreateAsync(T entry)
- => _inner.CreateAsync(entry);
+ public ValueTask CreateAsync(T entry, CancellationToken cancellationToken = default)
+ => _inner.CreateAsync(entry, cancellationToken);
- public ValueTask UpdateAsync(T entry)
- => _inner.UpdateAsync(entry);
+ public ValueTask UpdateAsync(T entry, CancellationToken cancellationToken = default)
+ => _inner.UpdateAsync(entry, cancellationToken);
}
diff --git a/src/Abstractions/CrestApps.Core.Infrastructure.Abstractions/Indexing/ISearchIndexProfileStore.cs b/src/Abstractions/CrestApps.Core.Infrastructure.Abstractions/Indexing/ISearchIndexProfileStore.cs
index aabb467c..05ccc307 100644
--- a/src/Abstractions/CrestApps.Core.Infrastructure.Abstractions/Indexing/ISearchIndexProfileStore.cs
+++ b/src/Abstractions/CrestApps.Core.Infrastructure.Abstractions/Indexing/ISearchIndexProfileStore.cs
@@ -8,9 +8,6 @@ namespace CrestApps.Core.Infrastructure.Indexing;
///
public interface ISearchIndexProfileStore : ICatalog, INamedCatalog
{
- ///
- /// Gets all index profiles of the specified type (e.g., "AIDocuments", "DataSourceIndex", "AIMemory").
-
///
/// Gets all index profiles of the specified type (e.g., "AIDocuments", "DataSourceIndex", "AIMemory").
///
diff --git a/src/CrestApps.Core.Docs/docs/a2a/client.md b/src/CrestApps.Core.Docs/docs/a2a/client.md
index ab51708c..98bcadc1 100644
--- a/src/CrestApps.Core.Docs/docs/a2a/client.md
+++ b/src/CrestApps.Core.Docs/docs/a2a/client.md
@@ -423,7 +423,7 @@ Authentication metadata is stored in `A2AConnectionMetadata`:
public sealed class A2AConnectionMetadata
{
// Which authentication type to use
- public A2AClientAuthenticationType AuthenticationType { get; set; }
+ public ClientAuthenticationType AuthenticationType { get; set; }
// API Key authentication
public string ApiKeyHeaderName { get; set; } // Default: "Authorization"
@@ -522,7 +522,7 @@ var connection = new A2AConnection
var metadata = new A2AConnectionMetadata
{
- AuthenticationType = A2AClientAuthenticationType.ApiKey,
+ AuthenticationType = ClientAuthenticationType.ApiKey,
ApiKeyHeaderName = "Authorization",
ApiKeyPrefix = "Bearer",
ApiKey = protector.Protect("sk-partner-key-12345"),
@@ -538,7 +538,7 @@ await connectionStore.CreateAsync(connection);
```csharp
var metadata = new A2AConnectionMetadata
{
- AuthenticationType = A2AClientAuthenticationType.OAuth2ClientCredentials,
+ AuthenticationType = ClientAuthenticationType.OAuth2ClientCredentials,
OAuth2TokenEndpoint = "https://auth.partner.com/oauth2/token",
OAuth2ClientId = "my-app-client-id",
OAuth2ClientSecret = protector.Protect("my-client-secret"),
@@ -553,3 +553,4 @@ var metadata = new A2AConnectionMetadata
- Authentication configuration forms for all supported types
- Connection assignment to AI profiles via the profile editor
- Agent card preview and cache invalidation
+
diff --git a/src/CrestApps.Core.Docs/docs/changelog/v1.0.0.md b/src/CrestApps.Core.Docs/docs/changelog/v1.0.0.md
index 9af3df23..8c5312ba 100644
--- a/src/CrestApps.Core.Docs/docs/changelog/v1.0.0.md
+++ b/src/CrestApps.Core.Docs/docs/changelog/v1.0.0.md
@@ -34,11 +34,13 @@ description: Initial standalone release notes for the CrestApps.Core repository.
- removes Azure OpenAI connection-level logging flags in favor of shared `CrestApps:AI:AzureClient` settings, keeps Azure completion resolution deployment-driven, and refreshes the AI client docs around `ClientName`-based configuration
- clarifies deployment-store registration by introducing `IAIDeploymentStore` for persisted deployments, moves Chat Interactions ahead of AI Profiles / AI Chat in the MVC sample onboarding flow, and adds dedicated AI Profile documentation that explains how profiles power reusable chat, agents, orchestration, retrieval, and session processing
- centralizes reusable MCP runtime registration in `AddCoreAIMcpServices()`, moves the shared MCP metadata, capability-resolution, tool-registry, SSE settings-handler, and invoke-function services into `CrestApps.Core.AI.Mcp`, and splits optional StdIO transport registration so hosts can enable it only where needed
+- standardizes A2A and MCP connection authentication on the shared `ClientAuthenticationType` enum, removes the protocol-specific duplicate enums, and adds an `AzureOpenAIClientMarker` so Azure OpenAI can participate in the same provider-marker conventions as the other AI clients without changing current runtime behavior
- treats aborted and canceled request-stream failures in the Aspire AppHost as observed task exceptions so local development no longer floods the console with benign unobserved-task noise
- generates external `.map` source map files for all JS and CSS assets in the gulp build pipeline, copies them into `dist/` during npm package preparation, and includes them in the `@crestapps/ai-chat-ui` package exports
- adds per-message text-to-speech play/pause controls on assistant messages in the AI Chat and Chat Interaction UIs, keeps the action toolbar pinned to the bottom-right of each response without reserving a separate action row, automatically stops other message players before starting a new one, and hides manual playback controls during Conversation mode
- renders sample-host `[doc:n]` citations as superscript markers and shows the resolved document links below each cited assistant response in both the MVC and Blazor chat UIs
- adds `AddReferenceDownloads()` plus `AddDownloadAIDocumentEndpoint()` so attached-document citation links can be registered and downloaded explicitly in sample or custom hosts
+- detects when uploaded chat-interaction or chat-session documents are being used for whole-document tasks such as summarization, review, rewrite, translation, or complete extraction work, and injects the full document text instead of relying only on chunk-level RAG
- upgrades the MVC sample host to Font Awesome 7 and adds draggable, resizable AI Chat widget layout persistence with a reset-size control that hosts can disable through widget config
- adds Chat History page listing previous sessions per AI Profile sorted by creation date, with resume, delete, delete-all, and new-chat actions
- adds Test page for Utility and Agent AI Profiles providing a single-prompt/single-response streamed UI
@@ -46,3 +48,5 @@ description: Initial standalone release notes for the CrestApps.Core repository.
- splits document ingestion, document-processing services, document endpoints, and document RAG into the dedicated `CrestApps.Core.AI.Documents` package, renames the format-specific helpers to `CrestApps.Core.AI.Documents.OpenXml` and `CrestApps.Core.AI.Documents.Pdf`, removes the data-ingestion dependency from `CrestApps.Core.AI`, persists uploaded files through `IDocumentFileStore` with GUID-based stored file names plus database-backed stored file metadata so hosts can redirect or clean up physical files reliably, and now registers a default filesystem-backed `IDocumentFileStore` from `AddCoreAIDocumentProcessing()` with `DocumentFileSystemFileStoreOptions` for base-path overrides
- simplifies template discovery by splitting generic `Templates/` loading from prompt-only `Templates/Prompts/`, keeps generic file discovery flat so provider-specific subfolders are not double-loaded, adds `Kind`-based template selection through `ITemplateService`, suppresses duplicate template IDs with first-match wins behavior, and removes Orchard-specific embedded-resource path handling from the standalone framework templating providers
- registers shared indexing services in the framework by default, including `ISearchIndexProfileManager`, `ISearchIndexProfileProvisioningService`, and a null fallback `ISearchIndexProfileStore`, so hosts only need `.AddIndexingServices(...).AddYesSqlStores()` or `.AddEntityCoreStores()` when they want persisted index profile records
+- keeps the MVC and Blazor sample-host AI profile, template, and chat-edit screens usable when Claude is not configured by treating failed Claude options validation as "provider unavailable" instead of crashing the page, and removes the legacy memory-settings compatibility shim so profile/template memory state now flows only through `MemoryMetadata`
+- updates the shared A2A and MCP sample clients so one client app can target either the MVC or Blazor sample host through a built-in server selector, and wires the Aspire AppHost to advertise both endpoints to those samples
diff --git a/src/CrestApps.Core.Docs/docs/core/ai-core.md b/src/CrestApps.Core.Docs/docs/core/ai-core.md
index b1506b91..b90c6268 100644
--- a/src/CrestApps.Core.Docs/docs/core/ai-core.md
+++ b/src/CrestApps.Core.Docs/docs/core/ai-core.md
@@ -89,14 +89,14 @@ public interface IAICompletionService
{
Task CompleteAsync(
AIDeployment deployment,
- IList messages,
- ChatOptions options = null,
+ IEnumerable messages,
+ AICompletionContext context,
CancellationToken cancellationToken = default);
- IAsyncEnumerable CompleteStreamingAsync(
+ IAsyncEnumerable CompleteStreamingAsync(
AIDeployment deployment,
- IList messages,
- ChatOptions options = null,
+ IEnumerable messages,
+ AICompletionContext context,
CancellationToken cancellationToken = default);
}
```
@@ -125,11 +125,15 @@ Implement this interface to add a new AI provider. Each provider registers its o
```csharp
public interface IAICompletionClient
{
+ string ClientName { get; }
+
Task CompleteAsync(
+ IEnumerable messages,
AICompletionContext context,
CancellationToken cancellationToken = default);
- IAsyncEnumerable CompleteStreamingAsync(
+ IAsyncEnumerable CompleteStreamingAsync(
+ IEnumerable messages,
AICompletionContext context,
CancellationToken cancellationToken = default);
}
@@ -307,7 +311,7 @@ To integrate an AI provider that is not already supported (e.g., Anthropic, Mist
```csharp
public interface IAICompletionClient
{
- string Name { get; }
+ string ClientName { get; }
Task CompleteAsync(
IEnumerable messages,
@@ -337,7 +341,7 @@ public sealed class MyProviderCompletionClient : IAICompletionClient
_logger = logger;
}
- public string Name => "MyProvider";
+ public string ClientName => "MyProvider";
public async Task CompleteAsync(
IEnumerable messages,
diff --git a/src/CrestApps.Core.Docs/docs/core/ai-documents.md b/src/CrestApps.Core.Docs/docs/core/ai-documents.md
index 56cd71e0..2b85553b 100644
--- a/src/CrestApps.Core.Docs/docs/core/ai-documents.md
+++ b/src/CrestApps.Core.Docs/docs/core/ai-documents.md
@@ -254,6 +254,13 @@ public interface IVectorSearchService
The user's query is embedded, and the resulting vector is compared against indexed chunks using cosine similarity.
+For uploaded chat-interaction and chat-session documents, the framework now switches between two context-loading strategies automatically:
+
+- targeted questions continue to use semantic chunk retrieval (`SearchDocumentsTool` and preemptive RAG)
+- whole-document tasks such as summarizing, reviewing, rewriting, translating, or extracting complete information from an attached file inject the full document text instead of a few chunks
+
+That keeps RAG efficient for lookup-style questions while avoiding partial-context answers for requests that depend on the entire uploaded file.
+
## Built-in Document Readers
| Reader | Extensions | Embeddable | Notes |
@@ -553,4 +560,3 @@ public sealed class InteractionDocumentSettings
| `ReadDocumentTool` | — | System tool | Full document read |
| `ReadTabularDataTool` | — | System tool | Tabular data queries |
-
diff --git a/src/CrestApps.Core.Docs/docs/core/ai-profiles.md b/src/CrestApps.Core.Docs/docs/core/ai-profiles.md
index 80347e41..67528d80 100644
--- a/src/CrestApps.Core.Docs/docs/core/ai-profiles.md
+++ b/src/CrestApps.Core.Docs/docs/core/ai-profiles.md
@@ -121,6 +121,8 @@ That turns a profile into more than a prompt container. It becomes the contract
Profiles can opt into user memory so experiences can carry durable context forward between sessions instead of starting from zero every time.
+That toggle is stored directly as `MemoryMetadata`, so profile and template consumers read and write one shared metadata shape instead of carrying legacy memory-setting aliases forward.
+
## Profile types
`AIProfile.Type` lets one model support different runtime roles.
diff --git a/src/CrestApps.Core.Docs/docs/core/architecture.md b/src/CrestApps.Core.Docs/docs/core/architecture.md
index 02fe0e04..ad046160 100644
--- a/src/CrestApps.Core.Docs/docs/core/architecture.md
+++ b/src/CrestApps.Core.Docs/docs/core/architecture.md
@@ -82,7 +82,7 @@ This page describes the project architecture and how the major layers depend on
| Project | Role |
|---------|------|
| `CrestApps.Core.Mvc.Web` | Standalone ASP.NET Core MVC application with full admin UI |
-| Blazor / Other | Future: Blazor Server/WASM, minimal APIs, etc. |
+| Blazor / Other | Blazor Server/WASM (`CrestApps.Core.Blazor.Web`), minimal APIs, etc. |
## Data Flow
diff --git a/src/CrestApps.Core.Docs/docs/core/core-services.md b/src/CrestApps.Core.Docs/docs/core/core-services.md
index 19daaaf8..0c3c50ff 100644
--- a/src/CrestApps.Core.Docs/docs/core/core-services.md
+++ b/src/CrestApps.Core.Docs/docs/core/core-services.md
@@ -166,7 +166,7 @@ Validates OData filter strings before they are passed to data source backends (E
```csharp
public interface IODataValidator
{
- bool TryValidate(string filter, out IReadOnlyList errors);
+ bool IsValidFilter(string filter);
}
```
diff --git a/src/CrestApps.Core.Docs/docs/core/document-processing.md b/src/CrestApps.Core.Docs/docs/core/document-processing.md
index 651631f4..c82e1ee6 100644
--- a/src/CrestApps.Core.Docs/docs/core/document-processing.md
+++ b/src/CrestApps.Core.Docs/docs/core/document-processing.md
@@ -113,6 +113,8 @@ These tools are automatically available to the orchestrator when documents are a
| `ReadDocumentTool` | Reads full text of a specific document |
| `ReadTabularDataTool` | Reads and parses CSV/TSV/Excel data |
+For chat-interaction and chat-session uploads, the orchestration layer now chooses between chunked retrieval and full-document injection automatically. Lookup-style questions still use semantic search, while whole-document requests such as summaries, reviews, rewrites, translations, or complete extraction tasks preload the full uploaded file content into context.
+
## Key Interfaces
diff --git a/src/CrestApps.Core.Docs/docs/core/mvc-example.md b/src/CrestApps.Core.Docs/docs/core/mvc-example.md
index d98cbdf0..65fe5a8b 100644
--- a/src/CrestApps.Core.Docs/docs/core/mvc-example.md
+++ b/src/CrestApps.Core.Docs/docs/core/mvc-example.md
@@ -372,4 +372,6 @@ The middleware pipeline includes:
dotnet run --project .\src\Startup\CrestApps.Core.Mvc.Web\CrestApps.Core.Mvc.Web.csproj
```
+The MVC sample resolves its content root to the project directory automatically, so you can run this command from the repository root without breaking view, static-file, or `App_Data` discovery.
+
The application starts on `https://localhost:5001`. Configure AI provider connections in `App_Data/appsettings.json` before using AI features.
diff --git a/src/CrestApps.Core.Docs/docs/core/signalr.md b/src/CrestApps.Core.Docs/docs/core/signalr.md
index 4f1756af..f3a81633 100644
--- a/src/CrestApps.Core.Docs/docs/core/signalr.md
+++ b/src/CrestApps.Core.Docs/docs/core/signalr.md
@@ -186,8 +186,8 @@ Configure the Redis connection in your environment:
```json title="appsettings.json"
{
- "Configuration": "localhost:6379,allowAdmin=true"
- }
+ "Redis": {
+ "Configuration": "localhost:6379,allowAdmin=true"
}
}
```
@@ -195,6 +195,7 @@ Configure the Redis connection in your environment:
Or via environment variables:
```bash
+export Redis__Configuration="localhost:6379,allowAdmin=true"
```
:::info
diff --git a/src/CrestApps.Core.Docs/docs/core/tools.md b/src/CrestApps.Core.Docs/docs/core/tools.md
index 2a8a5ba6..7973680a 100644
--- a/src/CrestApps.Core.Docs/docs/core/tools.md
+++ b/src/CrestApps.Core.Docs/docs/core/tools.md
@@ -76,7 +76,7 @@ public sealed class WeatherTool : AITool
// Tool parameters are defined as a record or class
private sealed record WeatherInput(string Location, string Units = "celsius");
- protected override async Task