prompts,
diff --git a/src/Primitives/CrestApps.Core.AI/Templates/Prompts/post-session-analysis-prompt.md b/src/Primitives/CrestApps.Core.AI/Templates/Prompts/post-session-analysis-prompt.md
index 91a7e40f..c94b13f2 100644
--- a/src/Primitives/CrestApps.Core.AI/Templates/Prompts/post-session-analysis-prompt.md
+++ b/src/Primitives/CrestApps.Core.AI/Templates/Prompts/post-session-analysis-prompt.md
@@ -10,7 +10,7 @@ Parameters:
Analyze the following completed chat conversation and produce results for the requested tasks.
Return exactly one structured result for each task listed below. Do not omit tasks, and do not return an empty tasks array. If a task does not need a tool call, still return its result value.
-IMPORTANT: If you call any tools, you MUST still return the JSON output with the "tasks" array as your final response after all tool calls complete.
+IMPORTANT: If a task's instructions tell you to call a tool (e.g., sendEmail), you MUST call that tool. Execute all required tool calls FIRST, then return the JSON output with the "tasks" array as your final response.
Tasks to process:
{% for task in tasks %}
diff --git a/src/Primitives/CrestApps.Core.AI/Templates/Prompts/post-session-analysis.md b/src/Primitives/CrestApps.Core.AI/Templates/Prompts/post-session-analysis.md
index ae6a8750..0d50279c 100644
--- a/src/Primitives/CrestApps.Core.AI/Templates/Prompts/post-session-analysis.md
+++ b/src/Primitives/CrestApps.Core.AI/Templates/Prompts/post-session-analysis.md
@@ -11,11 +11,12 @@ You are a post-session analysis assistant. Your job is to analyze a completed ch
1. Analyze the ENTIRE conversation transcript provided.
2. For PredefinedOptions tasks: select the best matching option(s) from the provided list. Use the option descriptions to guide your selection. If "allowMultiple" is true, you may select more than one option separated by commas. If false, select exactly one.
3. For Semantic tasks: follow the provided instructions and produce a freeform text result.
-4. Return ONLY valid JSON only. Do NOT wrap the response in markdown code fences (```). No explanations, no comments.
-5. Return exactly one result for every requested task, using the same task name.
-6. Never return an empty "tasks" array. If a task does not require a tool call, still return the task result value.
-7. Only return tasks that were requested.
-8. If you are given tools and you call them, you MUST still produce the JSON output below AFTER all tool calls have completed. Tool execution does not replace the required JSON response. Your final message MUST always be the JSON output.
+4. When tools are available and a task's instructions reference calling a tool, you MUST call that tool as part of processing the task. Do NOT skip tool calls that the task instructions require. Execute all required tool calls BEFORE producing your final response.
+5. Return ONLY valid JSON only. Do NOT wrap the response in markdown code fences (```). No explanations, no comments.
+6. Return exactly one result for every requested task, using the same task name.
+7. Never return an empty "tasks" array. If a task does not require a tool call, still return the task result value.
+8. Only return tasks that were requested.
+9. After all tool calls have completed, you MUST still produce the JSON output below as your final message. Tool execution does not replace the required JSON response.
[Output Format]
{
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 ff02280d..9d4b1297 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
@@ -1026,58 +1026,7 @@
Select the capabilities available to this task.
-
AI Agents
- @if (_model.AvailableAgents.Count == 0)
- {
-
No agent profiles available.
- }
- else
- {
- @foreach (var agent in _model.AvailableAgents)
- {
-
- ToggleTaskAgent(task, agent.Name, (bool)e.Value)" />
-
-
- }
- }
-
A2A Hosts
- @if (_model.AvailableA2AConnections.Count == 0)
- {
-
No A2A hosts configured.
- }
- else
- {
- @foreach (var conn in _model.AvailableA2AConnections)
- {
-
- ToggleTaskA2A(task, conn.ItemId, (bool)e.Value)" />
-
-
- }
- }
-
MCP Hosts
- @if (_model.AvailableMcpConnections.Count == 0)
- {
-
No MCP hosts configured.
- }
- else
- {
- @foreach (var conn in _model.AvailableMcpConnections)
- {
-
- ToggleTaskMcp(task, conn.ItemId, (bool)e.Value)" />
-
-
- }
- }
-
AI Tools
+
AI Tools
@if (_model.AvailableTools.Count == 0)
{
No AI tools registered.
@@ -1354,30 +1303,6 @@
private string GetTaskActiveTab(int index) => _taskActiveTabs.TryGetValue(index, out var tab) ? tab : "info";
private void SetTaskActiveTab(int index, string tab) => _taskActiveTabs[index] = tab;
- private void ToggleTaskAgent(PostSessionTaskItem task, string name, bool selected)
- {
- var list = task.SelectedAgentNames.ToList();
- if (selected && !list.Contains(name)) list.Add(name);
- else if (!selected) list.Remove(name);
- task.SelectedAgentNames = list.ToArray();
- }
-
- private void ToggleTaskA2A(PostSessionTaskItem task, string id, bool selected)
- {
- var list = task.SelectedA2AConnectionIds.ToList();
- if (selected && !list.Contains(id)) list.Add(id);
- else if (!selected) list.Remove(id);
- task.SelectedA2AConnectionIds = list.ToArray();
- }
-
- private void ToggleTaskMcp(PostSessionTaskItem task, string id, bool selected)
- {
- var list = task.SelectedMcpConnectionIds.ToList();
- if (selected && !list.Contains(id)) list.Add(id);
- else if (!selected) list.Remove(id);
- task.SelectedMcpConnectionIds = list.ToArray();
- }
-
private void ToggleTaskTool(PostSessionTaskItem task, string name, bool selected)
{
var list = task.SelectedToolNames.ToList();
diff --git a/src/Startup/CrestApps.Core.Blazor.Web/Components/Pages/AI/AIProfiles/Edit.razor b/src/Startup/CrestApps.Core.Blazor.Web/Components/Pages/AI/AIProfiles/Edit.razor
index b4d26652..50cbfdb4 100644
--- a/src/Startup/CrestApps.Core.Blazor.Web/Components/Pages/AI/AIProfiles/Edit.razor
+++ b/src/Startup/CrestApps.Core.Blazor.Web/Components/Pages/AI/AIProfiles/Edit.razor
@@ -952,58 +952,7 @@ else if (_model != null)
Select the capabilities available to this task.
-
AI Agents
- @if (_model.AvailableAgents.Count == 0)
- {
-
No agent profiles available.
- }
- else
- {
- @foreach (var agent in _model.AvailableAgents)
- {
-
- ToggleTaskAgent(task, agent.Name, (bool)e.Value)" />
-
-
- }
- }
-
A2A Hosts
- @if (_model.AvailableA2AConnections.Count == 0)
- {
-
No A2A hosts configured.
- }
- else
- {
- @foreach (var conn in _model.AvailableA2AConnections)
- {
-
- ToggleTaskA2A(task, conn.ItemId, (bool)e.Value)" />
-
-
- }
- }
-
MCP Hosts
- @if (_model.AvailableMcpConnections.Count == 0)
- {
-
No MCP hosts configured.
- }
- else
- {
- @foreach (var conn in _model.AvailableMcpConnections)
- {
-
- ToggleTaskMcp(task, conn.ItemId, (bool)e.Value)" />
-
-
- }
- }
-
AI Tools
+
AI Tools
@if (_model.AvailableTools.Count == 0)
{
No AI tools registered.
@@ -1214,30 +1163,6 @@ else if (_model != null)
private string GetTaskActiveTab(int index) => _taskActiveTabs.TryGetValue(index, out var tab) ? tab : "info";
private void SetTaskActiveTab(int index, string tab) => _taskActiveTabs[index] = tab;
- private void ToggleTaskAgent(PostSessionTaskItem task, string name, bool selected)
- {
- var list = task.SelectedAgentNames.ToList();
- if (selected && !list.Contains(name)) list.Add(name);
- else if (!selected) list.Remove(name);
- task.SelectedAgentNames = list.ToArray();
- }
-
- private void ToggleTaskA2A(PostSessionTaskItem task, string id, bool selected)
- {
- var list = task.SelectedA2AConnectionIds.ToList();
- if (selected && !list.Contains(id)) list.Add(id);
- else if (!selected) list.Remove(id);
- task.SelectedA2AConnectionIds = list.ToArray();
- }
-
- private void ToggleTaskMcp(PostSessionTaskItem task, string id, bool selected)
- {
- var list = task.SelectedMcpConnectionIds.ToList();
- if (selected && !list.Contains(id)) list.Add(id);
- else if (!selected) list.Remove(id);
- task.SelectedMcpConnectionIds = list.ToArray();
- }
-
private void ToggleTaskTool(PostSessionTaskItem task, string name, bool selected)
{
var list = task.SelectedToolNames.ToList();
diff --git a/src/Startup/CrestApps.Core.Blazor.Web/ViewModels/AIProfileViewModel.cs b/src/Startup/CrestApps.Core.Blazor.Web/ViewModels/AIProfileViewModel.cs
index 77d9404f..e49e4e17 100644
--- a/src/Startup/CrestApps.Core.Blazor.Web/ViewModels/AIProfileViewModel.cs
+++ b/src/Startup/CrestApps.Core.Blazor.Web/ViewModels/AIProfileViewModel.cs
@@ -248,9 +248,6 @@ public static AIProfileViewModel FromProfile(AIProfile profile)
AllowMultipleValues = t.AllowMultipleValues,
Options = string.Join(Environment.NewLine, t.Options.Select(o => o.Value)),
SelectedToolNames = t.ToolNames ?? [],
- SelectedAgentNames = t.AgentNames ?? [],
- SelectedA2AConnectionIds = t.A2AConnectionIds ?? [],
- SelectedMcpConnectionIds = t.McpConnectionIds ?? [],
}).ToList(),
EnableUserMemory = memoryMetadata.EnableUserMemory ?? false,
@@ -551,9 +548,6 @@ public void ApplyTo(AIProfile profile)
.Select(o => new PostSessionTaskOption { Value = o.Trim() })
.ToList(),
ToolNames = t.SelectedToolNames ?? [],
- AgentNames = t.SelectedAgentNames ?? [],
- A2AConnectionIds = t.SelectedA2AConnectionIds ?? [],
- McpConnectionIds = t.SelectedMcpConnectionIds ?? [],
}).ToList();
});
@@ -630,12 +624,6 @@ public sealed class PostSessionTaskItem
public string Options { get; set; }
public string[] SelectedToolNames { get; set; } = [];
-
- public string[] SelectedAgentNames { get; set; } = [];
-
- public string[] SelectedA2AConnectionIds { get; set; } = [];
-
- public string[] SelectedMcpConnectionIds { get; set; } = [];
}
public sealed class PromptTemplateSelectionItem
diff --git a/src/Startup/CrestApps.Core.Blazor.Web/ViewModels/AITemplateViewModel.cs b/src/Startup/CrestApps.Core.Blazor.Web/ViewModels/AITemplateViewModel.cs
index 82aa436f..f844cb7c 100644
--- a/src/Startup/CrestApps.Core.Blazor.Web/ViewModels/AITemplateViewModel.cs
+++ b/src/Startup/CrestApps.Core.Blazor.Web/ViewModels/AITemplateViewModel.cs
@@ -318,9 +318,6 @@ public static AITemplateViewModel FromTemplate(AIProfileTemplate template)
AllowMultipleValues = t.AllowMultipleValues,
Options = string.Join(Environment.NewLine, t.Options.Select(o => o.Value)),
SelectedToolNames = t.ToolNames ?? [],
- SelectedAgentNames = t.AgentNames ?? [],
- SelectedA2AConnectionIds = t.A2AConnectionIds ?? [],
- SelectedMcpConnectionIds = t.McpConnectionIds ?? [],
})
.ToList();
}
@@ -524,9 +521,6 @@ public void ApplyTo(AIProfileTemplate template)
.Select(o => new PostSessionTaskOption { Value = o.Trim() })
.ToList(),
ToolNames = t.SelectedToolNames ?? [],
- AgentNames = t.SelectedAgentNames ?? [],
- A2AConnectionIds = t.SelectedA2AConnectionIds ?? [],
- McpConnectionIds = t.SelectedMcpConnectionIds ?? [],
})
.ToList(),
});
diff --git a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/ViewModels/AIProfileViewModel.cs b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/ViewModels/AIProfileViewModel.cs
index f5f30caf..59689731 100644
--- a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/ViewModels/AIProfileViewModel.cs
+++ b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/ViewModels/AIProfileViewModel.cs
@@ -235,9 +235,6 @@ public static AIProfileViewModel FromProfile(AIProfile profile)
AllowMultipleValues = t.AllowMultipleValues,
Options = string.Join(Environment.NewLine, t.Options.Select(o => o.Value)),
SelectedToolNames = t.ToolNames ?? [],
- SelectedAgentNames = t.AgentNames ?? [],
- SelectedA2AConnectionIds = t.A2AConnectionIds ?? [],
- SelectedMcpConnectionIds = t.McpConnectionIds ?? [],
}).ToList(),
EnableUserMemory = memoryMetadata.EnableUserMemory ?? false,
};
@@ -539,9 +536,6 @@ public void ApplyTo(AIProfile profile)
.Select(o => new PostSessionTaskOption { Value = o.Trim() })
.ToList(),
ToolNames = t.SelectedToolNames ?? [],
- AgentNames = t.SelectedAgentNames ?? [],
- A2AConnectionIds = t.SelectedA2AConnectionIds ?? [],
- McpConnectionIds = t.SelectedMcpConnectionIds ?? [],
}).ToList();
});
@@ -619,12 +613,6 @@ public sealed class PostSessionTaskItem
public string Options { get; set; }
public string[] SelectedToolNames { get; set; } = [];
-
- public string[] SelectedAgentNames { get; set; } = [];
-
- public string[] SelectedA2AConnectionIds { get; set; } = [];
-
- public string[] SelectedMcpConnectionIds { get; set; } = [];
}
public sealed class PromptTemplateSelectionItem
diff --git a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/ViewModels/AITemplateViewModel.cs b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/ViewModels/AITemplateViewModel.cs
index c4995df8..bd320f18 100644
--- a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/ViewModels/AITemplateViewModel.cs
+++ b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/ViewModels/AITemplateViewModel.cs
@@ -302,9 +302,6 @@ public static AITemplateViewModel FromTemplate(AIProfileTemplate template)
AllowMultipleValues = t.AllowMultipleValues,
Options = string.Join(Environment.NewLine, t.Options.Select(o => o.Value)),
SelectedToolNames = t.ToolNames ?? [],
- SelectedAgentNames = t.AgentNames ?? [],
- SelectedA2AConnectionIds = t.A2AConnectionIds ?? [],
- SelectedMcpConnectionIds = t.McpConnectionIds ?? [],
})
.ToList();
}
@@ -508,9 +505,6 @@ public void ApplyTo(AIProfileTemplate template)
.Select(o => new PostSessionTaskOption { Value = o.Trim() })
.ToList(),
ToolNames = t.SelectedToolNames ?? [],
- AgentNames = t.SelectedAgentNames ?? [],
- A2AConnectionIds = t.SelectedA2AConnectionIds ?? [],
- McpConnectionIds = t.SelectedMcpConnectionIds ?? [],
})
.ToList(),
});
diff --git a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AIProfile/Create.cshtml b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AIProfile/Create.cshtml
index d267d171..ce9b9c56 100644
--- a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AIProfile/Create.cshtml
+++ b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AIProfile/Create.cshtml
@@ -1170,24 +1170,12 @@
let taskIndex = 0;
const taskCapabilities = {
- agents: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableAgents.Select(a => new { a.Name, a.DisplayText }))),
- a2a: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableA2AConnections.Select(c => new { c.ItemId, c.DisplayText }))),
- mcp: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableMcpConnections.Select(c => new { c.ItemId, c.DisplayText }))),
tools: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableTools.GroupBy(t => t.Category).OrderBy(g => g.Key).Select(g => new { Category = g.Key, Items = g.Select(t => new { t.Name, t.Title }) })))
};
function buildTaskCapabilitiesHtml(idx) {
let html = '
Select the capabilities available to this task.
';
- html += '
AI Agents
';
- if (taskCapabilities.agents.length === 0) { html += '
No agent profiles available.
'; }
- else { taskCapabilities.agents.forEach(a => { html += `
`; }); }
- html += '
A2A Hosts
';
- if (taskCapabilities.a2a.length === 0) { html += '
No A2A hosts configured.
'; }
- else { taskCapabilities.a2a.forEach(c => { html += `
`; }); }
- html += '
MCP Hosts
';
- if (taskCapabilities.mcp.length === 0) { html += '
No MCP hosts configured.
'; }
- else { taskCapabilities.mcp.forEach(c => { html += `
`; }); }
- html += '
AI Tools
';
+ html += '
AI Tools
';
if (taskCapabilities.tools.length === 0) { html += '
No AI tools registered.
'; }
else { taskCapabilities.tools.forEach(g => { html += `
${g.Category}
`; g.Items.forEach(t => { html += `
`; }); }); }
return html;
diff --git a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AIProfile/Edit.cshtml b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AIProfile/Edit.cshtml
index c4252bc8..2d329bfa 100644
--- a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AIProfile/Edit.cshtml
+++ b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AIProfile/Edit.cshtml
@@ -860,55 +860,7 @@
Select the capabilities available to this task.
-
AI Agents
- @if (Model.AvailableAgents.Count == 0)
- {
-
No agent profiles available.
- }
- else
- {
- @foreach (var agent in Model.AvailableAgents)
- {
-
-
-
-
- }
- }
-
A2A Hosts
- @if (Model.AvailableA2AConnections.Count == 0)
- {
-
No A2A hosts configured.
- }
- else
- {
- @foreach (var conn in Model.AvailableA2AConnections)
- {
-
-
-
-
- }
- }
-
MCP Hosts
- @if (Model.AvailableMcpConnections.Count == 0)
- {
-
No MCP hosts configured.
- }
- else
- {
- @foreach (var conn in Model.AvailableMcpConnections)
- {
-
-
-
-
- }
- }
-
AI Tools
+
AI Tools
@if (Model.AvailableTools.Count == 0)
{
No AI tools registered.
@@ -1416,24 +1368,12 @@
var taskIndex = @Model.PostSessionTasks.Count;
var taskCapabilities = {
- agents: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableAgents.Select(a => new { a.Name, a.DisplayText }))),
- a2a: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableA2AConnections.Select(c => new { c.ItemId, c.DisplayText }))),
- mcp: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableMcpConnections.Select(c => new { c.ItemId, c.DisplayText }))),
tools: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableTools.GroupBy(t => t.Category).OrderBy(g => g.Key).Select(g => new { Category = g.Key, Items = g.Select(t => new { t.Name, t.Title }) })))
};
function buildTaskCapabilitiesHtml(idx) {
var html = '
Select the capabilities available to this task.
';
- html += '
AI Agents
';
- if (taskCapabilities.agents.length === 0) { html += '
No agent profiles available.
'; }
- else { taskCapabilities.agents.forEach(function (a) { html += '
'; }); }
- html += '
A2A Hosts
';
- if (taskCapabilities.a2a.length === 0) { html += '
No A2A hosts configured.
'; }
- else { taskCapabilities.a2a.forEach(function (c) { html += '
'; }); }
- html += '
MCP Hosts
';
- if (taskCapabilities.mcp.length === 0) { html += '
No MCP hosts configured.
'; }
- else { taskCapabilities.mcp.forEach(function (c) { html += '
'; }); }
- html += '
AI Tools
';
+ html += '
AI Tools
';
if (taskCapabilities.tools.length === 0) { html += '
No AI tools registered.
'; }
else { taskCapabilities.tools.forEach(function (g) { html += '
' + g.Category + '
'; g.Items.forEach(function (t) { html += '
'; }); }); }
return html;
diff --git a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AITemplate/Create.cshtml b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AITemplate/Create.cshtml
index dc46e74f..8e91c41b 100644
--- a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AITemplate/Create.cshtml
+++ b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AITemplate/Create.cshtml
@@ -1064,24 +1064,12 @@
var taskIndex = 0;
var taskCapabilities = {
- agents: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableAgents.Select(a => new { a.Name, a.DisplayText }))),
- a2a: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableA2AConnections.Select(c => new { c.ItemId, c.DisplayText }))),
- mcp: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableMcpConnections.Select(c => new { c.ItemId, c.DisplayText }))),
tools: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableTools.GroupBy(t => t.Category).OrderBy(g => g.Key).Select(g => new { Category = g.Key, Items = g.Select(t => new { t.Name, t.Title }) })))
};
function buildTaskCapabilitiesHtml(idx) {
var html = '
Select the capabilities available to this task.
';
- html += '
AI Agents
';
- if (taskCapabilities.agents.length === 0) { html += '
No agent profiles available.
'; }
- else { taskCapabilities.agents.forEach(function (a) { html += '
'; }); }
- html += '
A2A Hosts
';
- if (taskCapabilities.a2a.length === 0) { html += '
No A2A hosts configured.
'; }
- else { taskCapabilities.a2a.forEach(function (c) { html += '
'; }); }
- html += '
MCP Hosts
';
- if (taskCapabilities.mcp.length === 0) { html += '
No MCP hosts configured.
'; }
- else { taskCapabilities.mcp.forEach(function (c) { html += '
'; }); }
- html += '
AI Tools
';
+ html += '
AI Tools
';
if (taskCapabilities.tools.length === 0) { html += '
No AI tools registered.
'; }
else { taskCapabilities.tools.forEach(function (g) { html += '
' + g.Category + '
'; g.Items.forEach(function (t) { html += '
'; }); }); }
return html;
diff --git a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AITemplate/Edit.cshtml b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AITemplate/Edit.cshtml
index 919d7d9f..f4ce4e8b 100644
--- a/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AITemplate/Edit.cshtml
+++ b/src/Startup/CrestApps.Core.Mvc.Web/Areas/AI/Views/AITemplate/Edit.cshtml
@@ -749,55 +749,7 @@
Select the capabilities available to this task.
-
AI Agents
- @if (Model.AvailableAgents.Count == 0)
- {
-
No agent profiles available.
- }
- else
- {
- @foreach (var agent in Model.AvailableAgents)
- {
-
-
-
-
- }
- }
-
A2A Hosts
- @if (Model.AvailableA2AConnections.Count == 0)
- {
-
No A2A hosts configured.
- }
- else
- {
- @foreach (var conn in Model.AvailableA2AConnections)
- {
-
-
-
-
- }
- }
-
MCP Hosts
- @if (Model.AvailableMcpConnections.Count == 0)
- {
-
No MCP hosts configured.
- }
- else
- {
- @foreach (var conn in Model.AvailableMcpConnections)
- {
-
-
-
-
- }
- }
-
AI Tools
+
AI Tools
@if (Model.AvailableTools.Count == 0)
{
No AI tools registered.
@@ -1340,24 +1292,12 @@
var taskIndex = @Model.PostSessionTasks.Count;
var taskCapabilities = {
- agents: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableAgents.Select(a => new { a.Name, a.DisplayText }))),
- a2a: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableA2AConnections.Select(c => new { c.ItemId, c.DisplayText }))),
- mcp: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableMcpConnections.Select(c => new { c.ItemId, c.DisplayText }))),
tools: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.AvailableTools.GroupBy(t => t.Category).OrderBy(g => g.Key).Select(g => new { Category = g.Key, Items = g.Select(t => new { t.Name, t.Title }) })))
};
function buildTaskCapabilitiesHtml(idx) {
var html = '
Select the capabilities available to this task.
';
- html += '
AI Agents
';
- if (taskCapabilities.agents.length === 0) { html += '
No agent profiles available.
'; }
- else { taskCapabilities.agents.forEach(function (a) { html += '
'; }); }
- html += '
A2A Hosts
';
- if (taskCapabilities.a2a.length === 0) { html += '
No A2A hosts configured.
'; }
- else { taskCapabilities.a2a.forEach(function (c) { html += '
'; }); }
- html += '
MCP Hosts
';
- if (taskCapabilities.mcp.length === 0) { html += '
No MCP hosts configured.
'; }
- else { taskCapabilities.mcp.forEach(function (c) { html += '
'; }); }
- html += '
AI Tools
';
+ html += '
AI Tools
';
if (taskCapabilities.tools.length === 0) { html += '
No AI tools registered.
'; }
else { taskCapabilities.tools.forEach(function (g) { html += '
' + g.Category + '
'; g.Items.forEach(function (t) { html += '
'; }); }); }
return html;
diff --git a/src/Startup/CrestApps.Core.Mvc.Web/Program.cs b/src/Startup/CrestApps.Core.Mvc.Web/Program.cs
index f5a6e7b4..c6c2ef87 100644
--- a/src/Startup/CrestApps.Core.Mvc.Web/Program.cs
+++ b/src/Startup/CrestApps.Core.Mvc.Web/Program.cs
@@ -16,6 +16,7 @@
using CrestApps.Core.AI.Mcp.Ftp;
using CrestApps.Core.AI.Mcp.Models;
using CrestApps.Core.AI.Mcp.Sftp;
+using CrestApps.Core.AI.Models;
using CrestApps.Core.AI.Ollama;
using CrestApps.Core.AI.OpenAI;
using CrestApps.Core.AI.OpenAI.Azure;
diff --git a/tests/CrestApps.Core.Tests/ChatNotifications/EndSessionNotificationActionHandlerTests.cs b/tests/CrestApps.Core.Tests/ChatNotifications/EndSessionNotificationActionHandlerTests.cs
index 60f02130..18ceb262 100644
--- a/tests/CrestApps.Core.Tests/ChatNotifications/EndSessionNotificationActionHandlerTests.cs
+++ b/tests/CrestApps.Core.Tests/ChatNotifications/EndSessionNotificationActionHandlerTests.cs
@@ -1,10 +1,10 @@
using CrestApps.Core.AI.Chat;
using CrestApps.Core.AI.Chat.Services;
-using CrestApps.Core.AI;
using CrestApps.Core.AI.Clients;
using CrestApps.Core.AI.Deployments;
using CrestApps.Core.AI.Models;
using CrestApps.Core.AI.Profiles;
+using CrestApps.Core.AI.Tooling;
using CrestApps.Core.Templates.Parsing;
using CrestApps.Core.Templates.Services;
using Microsoft.Extensions.DependencyInjection;
@@ -240,7 +240,7 @@ private static AIChatSessionPostCloseProcessor CreatePostCloseProcessor(TimeProv
var postSessionProcessingService = new PostSessionProcessingService(
Mock.Of
(),
- Mock.Of(),
+ Mock.Of(),
templateService.Object,
[markdownParser.Object],
new DefaultAIOptions(),
diff --git a/tests/CrestApps.Core.Tests/Core/Services/PostSession/AIChatSessionPostCloseProcessorTests.cs b/tests/CrestApps.Core.Tests/Core/Services/PostSession/AIChatSessionPostCloseProcessorTests.cs
index a54e914b..e09fe2d0 100644
--- a/tests/CrestApps.Core.Tests/Core/Services/PostSession/AIChatSessionPostCloseProcessorTests.cs
+++ b/tests/CrestApps.Core.Tests/Core/Services/PostSession/AIChatSessionPostCloseProcessorTests.cs
@@ -1,8 +1,8 @@
-using CrestApps.Core.AI;
using CrestApps.Core.AI.Chat.Services;
using CrestApps.Core.AI.Clients;
using CrestApps.Core.AI.Deployments;
using CrestApps.Core.AI.Models;
+using CrestApps.Core.AI.Tooling;
using CrestApps.Core.Templates.Parsing;
using CrestApps.Core.Templates.Services;
using Microsoft.Extensions.AI;
@@ -319,7 +319,7 @@ private static PostSessionProcessingService CreatePostSessionService(
return new PostSessionProcessingService(
mockClientFactory.Object,
- Mock.Of(),
+ Mock.Of(),
mockTemplateService.Object,
[new DefaultMarkdownTemplateParser()],
new DefaultAIOptions
diff --git a/tests/CrestApps.Core.Tests/Core/Services/PostSession/PostSessionProcessingServiceTests.cs b/tests/CrestApps.Core.Tests/Core/Services/PostSession/PostSessionProcessingServiceTests.cs
index df02aa98..2620c80c 100644
--- a/tests/CrestApps.Core.Tests/Core/Services/PostSession/PostSessionProcessingServiceTests.cs
+++ b/tests/CrestApps.Core.Tests/Core/Services/PostSession/PostSessionProcessingServiceTests.cs
@@ -3,6 +3,7 @@
using CrestApps.Core.AI.Clients;
using CrestApps.Core.AI.Deployments;
using CrestApps.Core.AI.Models;
+using CrestApps.Core.AI.Tooling;
using CrestApps.Core.Templates.Parsing;
using CrestApps.Core.Templates.Services;
using Microsoft.Extensions.AI;
@@ -154,8 +155,8 @@ public async Task ProcessAsync_WithTaskScopedToolNames_ShouldResolveToolsAndUseT
var session = CreateSession();
var prompts = CreatePrompts();
var mockTool = new TestAIFunction("sendEmail");
- var mockToolsService = new Mock();
- mockToolsService.Setup(t => t.GetByNameAsync("sendEmail")).ReturnsAsync(mockTool);
+ var mockToolRegistry = new Mock();
+ mockToolRegistry.Setup(t => t.GetAllAsync(It.IsAny(), It.IsAny())).ReturnsAsync(new List { CreateToolEntry("sendEmail", mockTool) });
var mockChatClient = new Mock();
// The tools path calls non-generic GetResponseAsync.
// Simulate response with a JSON result.
@@ -168,13 +169,13 @@ public async Task ProcessAsync_WithTaskScopedToolNames_ShouldResolveToolsAndUseT
mockTemplateService.Setup(t => t
.RenderAsync(It.IsAny(), It.IsAny>(), It.IsAny()))
.ReturnsAsync("Rendered prompt");
- var service = CreateService(chatClient: mockChatClient.Object, toolsService: mockToolsService.Object, templateService: mockTemplateService.Object);
+ var service = CreateService(chatClient: mockChatClient.Object, toolRegistry: mockToolRegistry.Object, templateService: mockTemplateService.Object);
// Act
var result = await service.ProcessAsync(profile, session, prompts, TestContext.Current.CancellationToken);
// Assert: tools service was asked to resolve the tool.
- mockToolsService.Verify(t => t.GetByNameAsync("sendEmail"), Times.Once);
+ mockToolRegistry.Verify(t => t.GetAllAsync(It.IsAny(), It.IsAny()), Times.Once);
// Assert: the chat client was invoked with tools in the options.
mockChatClient.Verify(c => c.GetResponseAsync(It.IsAny>(), It.Is(opts => opts.Tools != null && opts.Tools.Count > 0), It.IsAny()), Times.Once);
@@ -198,8 +199,8 @@ public async Task ProcessAsync_WhenToolResponseContainsOnlyInvalidTaskEntriesWit
var session = CreateSession();
var prompts = CreatePrompts();
var mockTool = new TestAIFunction("sendEmail");
- var mockToolsService = new Mock();
- mockToolsService.Setup(t => t.GetByNameAsync("sendEmail")).ReturnsAsync(mockTool);
+ var mockToolRegistry = new Mock();
+ mockToolRegistry.Setup(t => t.GetAllAsync(It.IsAny(), It.IsAny())).ReturnsAsync(new List { CreateToolEntry("sendEmail", mockTool) });
var mockChatClient = new Mock();
mockChatClient.SetupSequence(c => c
.GetResponseAsync(It.IsAny>(), It.IsAny(), It.IsAny()))
@@ -210,7 +211,7 @@ public async Task ProcessAsync_WhenToolResponseContainsOnlyInvalidTaskEntriesWit
mockTemplateService.Setup(t => t
.RenderAsync(It.IsAny(), It.IsAny>(), It.IsAny()))
.ReturnsAsync("Rendered prompt");
- var service = CreateService(chatClient: mockChatClient.Object, toolsService: mockToolsService.Object, templateService: mockTemplateService.Object);
+ var service = CreateService(chatClient: mockChatClient.Object, toolRegistry: mockToolRegistry.Object, templateService: mockTemplateService.Object);
var result = await service.ProcessAsync(profile, session, prompts, TestContext.Current.CancellationToken);
@@ -239,8 +240,8 @@ public async Task ProcessAsync_WhenToolResponseContainsEmptyTasksArray_ShouldRet
var session = CreateSession();
var prompts = CreatePrompts();
var mockTool = new TestAIFunction("sendEmail");
- var mockToolsService = new Mock();
- mockToolsService.Setup(t => t.GetByNameAsync("sendEmail")).ReturnsAsync(mockTool);
+ var mockToolRegistry = new Mock();
+ mockToolRegistry.Setup(t => t.GetAllAsync(It.IsAny(), It.IsAny())).ReturnsAsync(new List { CreateToolEntry("sendEmail", mockTool) });
var mockChatClient = new Mock();
mockChatClient.Setup(c => c
.GetResponseAsync(It.IsAny>(), It.IsAny(), It.IsAny()))
@@ -249,7 +250,7 @@ public async Task ProcessAsync_WhenToolResponseContainsEmptyTasksArray_ShouldRet
mockTemplateService.Setup(t => t
.RenderAsync(It.IsAny(), It.IsAny>(), It.IsAny()))
.ReturnsAsync("Rendered prompt");
- var service = CreateService(chatClient: mockChatClient.Object, toolsService: mockToolsService.Object, templateService: mockTemplateService.Object);
+ var service = CreateService(chatClient: mockChatClient.Object, toolRegistry: mockToolRegistry.Object, templateService: mockTemplateService.Object);
var result = await service.ProcessAsync(profile, session, prompts, TestContext.Current.CancellationToken);
@@ -277,8 +278,8 @@ public async Task ProcessAsync_WhenToolNotFound_ShouldLogWarningAndFallToStructu
});
var session = CreateSession();
var prompts = CreatePrompts();
- var mockToolsService = new Mock();
- mockToolsService.Setup(t => t.GetByNameAsync("nonExistentTool")).ReturnsAsync((AITool)null);
+ var mockToolRegistry = new Mock();
+ mockToolRegistry.Setup(t => t.GetAllAsync(It.IsAny(), It.IsAny())).ReturnsAsync(new List());
var mockChatClient = new Mock();
mockChatClient.Setup(c => c
.GetResponseAsync(It.IsAny>(), It.IsAny(), It.IsAny()))
@@ -287,13 +288,13 @@ public async Task ProcessAsync_WhenToolNotFound_ShouldLogWarningAndFallToStructu
mockTemplateService.Setup(t => t
.RenderAsync(It.IsAny(), It.IsAny>(), It.IsAny()))
.ReturnsAsync("Rendered prompt");
- var service = CreateService(chatClient: mockChatClient.Object, toolsService: mockToolsService.Object, templateService: mockTemplateService.Object);
+ var service = CreateService(chatClient: mockChatClient.Object, toolRegistry: mockToolRegistry.Object, templateService: mockTemplateService.Object);
// Act
var result = await service.ProcessAsync(profile, session, prompts, TestContext.Current.CancellationToken);
// Assert: tool resolution was attempted.
- mockToolsService.Verify(t => t.GetByNameAsync("nonExistentTool"), Times.Once);
+ mockToolRegistry.Verify(t => t.GetAllAsync(It.IsAny(), It.IsAny()), Times.Once);
// Assert: when no tools resolve, the structured output path is used (no tools in options).
mockChatClient.Verify(c => c.GetResponseAsync(It.IsAny>(), It.Is(opts => opts.Tools == null || opts.Tools.Count == 0), It.IsAny()), Times.Once);
@@ -557,7 +558,7 @@ private static List CreateAssistantOnlyPrompts()
];
}
- private static PostSessionProcessingService CreateService(IChatClient chatClient = null, IAIToolsService toolsService = null, ITemplateService templateService = null)
+ private static PostSessionProcessingService CreateService(IChatClient chatClient = null, IToolRegistry toolRegistry = null, ITemplateService templateService = null)
{
var mockClientFactory = new Mock();
if (chatClient is not null)
@@ -583,7 +584,7 @@ private static PostSessionProcessingService CreateService(IChatClient chatClient
.ReturnsAsync(deployment);
}
- var mockToolsService = toolsService is not null ? null : new Mock();
+ var mockToolRegistry = toolRegistry is not null ? null : new Mock();
var mockTemplateService = templateService is not null ? null : new Mock();
// Set up default template renders.
if (mockTemplateService is not null)
@@ -600,7 +601,7 @@ private static PostSessionProcessingService CreateService(IChatClient chatClient
return new PostSessionProcessingService(
mockClientFactory.Object,
- toolsService ?? mockToolsService.Object,
+ toolRegistry ?? mockToolRegistry.Object,
templateService ?? mockTemplateService.Object,
[new DefaultMarkdownTemplateParser()],
defaultOptions,
@@ -637,6 +638,17 @@ protected override ValueTask