diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfilePostSessionSettings.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfilePostSessionSettings.cs
index 31054dbf..35456523 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfilePostSessionSettings.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/AIProfilePostSessionSettings.cs
@@ -22,4 +22,10 @@ public sealed class AIProfilePostSessionSettings
/// When tools are configured, the AI model can invoke them during post-session analysis.
///
public string[] ToolNames { get; set; } = [];
+
+ ///
+ /// Gets or sets the AI tool instance names to make available during post-session processing.
+ /// When instances are configured, the AI model can invoke them during post-session analysis.
+ ///
+ public string[] ToolInstanceNames { get; set; } = [];
}
diff --git a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/PostSessionTask.cs b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/PostSessionTask.cs
index 332fcc63..4eb9842e 100644
--- a/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/PostSessionTask.cs
+++ b/src/Abstractions/CrestApps.Core.AI.Abstractions/Models/PostSessionTask.cs
@@ -40,4 +40,9 @@ public sealed class PostSessionTask
/// Gets or sets the AI tool names available to this task during post-session processing.
///
public string[] ToolNames { get; set; } = [];
+
+ ///
+ /// Gets or sets the AI tool instance names available to this task during post-session processing.
+ ///
+ public string[] ToolInstanceNames { get; set; } = [];
}
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 08011b47..96c4ec14 100644
--- a/src/CrestApps.Core.Docs/docs/changelog/v1.0.0.md
+++ b/src/CrestApps.Core.Docs/docs/changelog/v1.0.0.md
@@ -118,3 +118,4 @@ description: Initial standalone release notes for the CrestApps.Core repository.
- adds a source dropdown to the AI tool instance create form in the MVC and Blazor sample hosts that reveals only the selected source's fields (the source is fixed and shown read-only on edit), and relocates the "AI Tool Instances" admin menu item next to "AI Profiles" in both samples
- requires the key, title, and content field mappings when creating or editing an AI data source in the MVC and Blazor sample hosts, and makes every built-in source reader fall back to the document key instead of the serialized source document when no title is mapped, so chat citations never render a full JSON document as a reference title
- makes the Copilot CLI acquisition work behind corporate proxies and artifact mirrors, and downloads it only once per machine: `CrestApps.Core.AI.Copilot` now resolves the effective npm registry from `NPM_CONFIG_REGISTRY` or `npm config get registry` before the `GitHub.Copilot.SDK` targets download the CLI tarball (the SDK hardcodes `https://registry.npmjs.org`, and MSBuild's `DownloadFile` task cannot read npm configuration), and redirects the SDK's per-project, per-configuration cache to a shared cache under the NuGet global packages folder so a multi-project solution, a fresh worktree, or a CI agent no longer re-downloads the same large tarball for every project; both behaviors are opt-out through `CopilotResolveNpmRegistry` and `CopilotUseSharedCliCache`, the cache location is configurable through `CopilotCliCacheDir` (point it at a pre-seeded directory to build offline), and an explicitly set `CopilotNpmRegistryUrl`, `CopilotCliBinaryPath`, or `CopilotSkipCliDownload` always takes precedence
+- lets post-session processing invoke parameterized AI tool instances through the new `AIProfilePostSessionSettings.ToolInstanceNames` and `PostSessionTask.ToolInstanceNames`, merged and forwarded to the tool registry alongside the equivalent `ToolNames` so configuring only tool instances is enough to enable the tool-driven post-session path, and surfaces the per-task selection on the **Capabilities** tab of each post-session task in the AI profile create and edit screens of both the MVC and Blazor sample hosts
diff --git a/src/CrestApps.Core.Docs/docs/core/chat.md b/src/CrestApps.Core.Docs/docs/core/chat.md
index 156738b5..11601562 100644
--- a/src/CrestApps.Core.Docs/docs/core/chat.md
+++ b/src/CrestApps.Core.Docs/docs/core/chat.md
@@ -244,7 +244,9 @@ NewAsync() SaveAsync() (inactivity / explicit close)
| `ExtractedData` | `Dictionary` | Extracted conversation fields |
| `PostSessionProcessingStatus` | `PostSessionProcessingStatus` | Status of post-session tasks |
-Each `PostSessionResults` entry now keeps `AttemptHistory` for failed or incomplete retries, and `ProcessedAtUtc` is only populated once the task reaches a terminal success or final failure state. Pending retries keep their last attempt details in history instead of surfacing a default timestamp. Post-session tool resolution also honors task-scoped `PostSessionTask.ToolNames` in addition to profile-level post-session tool configuration, and post-close retries default to 5 attempts before the task is treated as terminally failed.
+Each `PostSessionResults` entry now keeps `AttemptHistory` for failed or incomplete retries, and `ProcessedAtUtc` is only populated once the task reaches a terminal success or final failure state. Pending retries keep their last attempt details in history instead of surfacing a default timestamp. Post-session tool resolution also honors task-scoped `PostSessionTask.ToolNames` and `PostSessionTask.ToolInstanceNames` in addition to profile-level post-session tool configuration, and post-close retries default to 5 attempts before the task is treated as terminally failed.
+
+Parameterized AI tool instances can also be invoked during post-session analysis alongside regular tools. Configure them at the profile level through `AIProfilePostSessionSettings.ToolInstanceNames`, per task through `PostSessionTask.ToolInstanceNames`, or both; the two sets are merged. Configuring only tool instances is enough to enable the tool-driven post-session path, so no regular tool names are required. In the MVC and Blazor sample hosts, the per-task selection is exposed on the **Capabilities** tab of each post-session task, next to the AI tools picker.
Hosts can override that retry cap through the shared `AIChatSessionProcessingOptions.MaxPostCloseAttempts` site setting. The MVC admin settings page surfaces the same value as **Max post-close attempts**, and the shared processor reads it through `IOptionsMonitor<>` so both the default hosted runner and custom schedulers honor the same limit.
diff --git a/src/Primitives/CrestApps.Core.AI.Chat/Services/PostSessionProcessingService.cs b/src/Primitives/CrestApps.Core.AI.Chat/Services/PostSessionProcessingService.cs
index e027107d..c8d3b4f0 100644
--- a/src/Primitives/CrestApps.Core.AI.Chat/Services/PostSessionProcessingService.cs
+++ b/src/Primitives/CrestApps.Core.AI.Chat/Services/PostSessionProcessingService.cs
@@ -382,7 +382,7 @@ public async Task> ProcessAsync(
new(ChatRole.User, prompt),
};
- var tools = await ResolveToolsAsync(session.SessionId, settings.ToolNames, tasksToProcess);
+ var tools = await ResolveToolsAsync(session.SessionId, settings.ToolNames, settings.ToolInstanceNames, tasksToProcess);
// When tools are configured (e.g., sendEmail), use non-generic GetResponseAsync
// to allow tool execution. The generic version uses structured output which
@@ -1394,11 +1394,13 @@ private async Task GetChatClientAsync(AIProfile profile)
private async Task> ResolveToolsAsync(
string sessionId,
string[] profileToolNames,
+ string[] profileToolInstanceNames,
List tasks)
{
- var toolNames = CollectToolNames(profileToolNames, tasks);
+ var toolNames = CollectNames(profileToolNames, tasks, static task => task.ToolNames);
+ var toolInstanceNames = CollectNames(profileToolInstanceNames, tasks, static task => task.ToolInstanceNames);
- if (toolNames is null || toolNames.Length == 0)
+ if (toolNames.Length == 0 && toolInstanceNames.Length == 0)
{
if (_logger.IsEnabled(LogLevel.Information))
{
@@ -1413,15 +1415,17 @@ private async Task> ResolveToolsAsync(
if (_logger.IsEnabled(LogLevel.Information))
{
_logger.LogInformation(
- "Resolving {ToolCount} tool(s) for post-session processing of session '{SessionId}': [{ToolNames}].",
+ "Resolving {ToolCount} tool(s) and {ToolInstanceCount} tool instance(s) for post-session processing of session '{SessionId}': [{ToolNames}].",
toolNames.Length,
+ toolInstanceNames.Length,
sessionId,
- string.Join(", ", toolNames));
+ string.Join(", ", toolNames.Concat(toolInstanceNames)));
}
var completionContext = new AICompletionContext
{
ToolNames = toolNames,
+ ToolInstanceNames = toolInstanceNames,
};
var entries = await _toolRegistry.GetAllAsync(completionContext);
@@ -1433,7 +1437,7 @@ private async Task> ResolveToolsAsync(
_logger.LogWarning(
"Tool registry returned no entries for post-session processing of session '{SessionId}'. Requested tool names: [{ToolNames}].",
sessionId,
- string.Join(", ", toolNames));
+ string.Join(", ", toolNames.Concat(toolInstanceNames)));
}
return null;
@@ -1481,41 +1485,40 @@ private async Task> ResolveToolsAsync(
return tools.Count > 0 ? tools : null;
}
- private static string[] CollectToolNames(
- string[] profileToolNames,
- List tasks)
+ private static string[] CollectNames(
+ string[] profileNames,
+ List tasks,
+ Func taskNamesSelector)
{
- var toolNames = new HashSet(StringComparer.OrdinalIgnoreCase);
+ var names = new HashSet(StringComparer.OrdinalIgnoreCase);
- if (profileToolNames is not null)
+ AddNames(names, profileNames);
+
+ if (tasks is not null)
{
- foreach (var name in profileToolNames)
+ foreach (var task in tasks)
{
- if (!string.IsNullOrWhiteSpace(name))
- {
- toolNames.Add(name);
- }
+ AddNames(names, taskNamesSelector(task));
}
}
- if (tasks is not null)
+ return names.Count > 0 ? [.. names] : [];
+ }
+
+ private static void AddNames(HashSet target, string[] names)
+ {
+ if (names is null)
{
- foreach (var task in tasks)
+ return;
+ }
+
+ foreach (var name in names)
+ {
+ if (!string.IsNullOrWhiteSpace(name))
{
- if (task.ToolNames is not null)
- {
- foreach (var name in task.ToolNames)
- {
- if (!string.IsNullOrWhiteSpace(name))
- {
- toolNames.Add(name);
- }
- }
- }
+ target.Add(name);
}
}
-
- return toolNames.Count > 0 ? [.. toolNames] : [];
}
private async Task RenderTranscriptAsync(
diff --git a/src/Startup/CrestApps.Core.Blazor.Web/Components/Pages/AI/AIProfiles/Create.razor b/src/Startup/CrestApps.Core.Blazor.Web/Components/Pages/AI/AIProfiles/Create.razor
index 1c9a37b3..e44645c1 100644
--- a/src/Startup/CrestApps.Core.Blazor.Web/Components/Pages/AI/AIProfiles/Create.razor
+++ b/src/Startup/CrestApps.Core.Blazor.Web/Components/Pages/AI/AIProfiles/Create.razor
@@ -1077,6 +1077,27 @@
}
}
}
+
+