mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-11 16:13:48 +00:00
Multiple Profiles can now be selected
This commit is contained in:
1 parent
d043fbc8f0
commit
cefc5464d5
89 files changed
+1360
-503
No files matched your search
@@ -207,23 +207,23 @@ public static class ComponentsExtensions
|
||||
{
|
||||
var storedValue = component switch
|
||||
{
|
||||
Components.AGENDA_ASSISTANT => settingsManager.ConfigurationData.Agenda.PreselectOptions ? settingsManager.ConfigurationData.Agenda.PreselectedProfile : string.Empty,
|
||||
Components.CODING_ASSISTANT => settingsManager.ConfigurationData.Coding.PreselectOptions ? settingsManager.ConfigurationData.Coding.PreselectedProfile : string.Empty,
|
||||
Components.EMAIL_ASSISTANT => settingsManager.ConfigurationData.EMail.PreselectOptions ? settingsManager.ConfigurationData.EMail.PreselectedProfile : string.Empty,
|
||||
Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.LegalCheck.PreselectedProfile : string.Empty,
|
||||
Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.MyTasks.PreselectedProfile : string.Empty,
|
||||
Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfile : string.Empty,
|
||||
Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.ERI.PreselectedProfile : string.Empty,
|
||||
Components.SLIDE_BUILDER_ASSISTANT => settingsManager.ConfigurationData.SlideBuilder.PreselectOptions ? settingsManager.ConfigurationData.SlideBuilder.PreselectedProfile : string.Empty,
|
||||
Components.VISUAL_BRIEFING_ASSISTANT => settingsManager.ConfigurationData.VisualBriefing.PreselectedProfile,
|
||||
Components.AGENDA_ASSISTANT => settingsManager.ConfigurationData.Agenda.PreselectOptions ? settingsManager.ConfigurationData.Agenda.PreselectedProfileIds : null,
|
||||
Components.CODING_ASSISTANT => settingsManager.ConfigurationData.Coding.PreselectOptions ? settingsManager.ConfigurationData.Coding.PreselectedProfileIds : null,
|
||||
Components.EMAIL_ASSISTANT => settingsManager.ConfigurationData.EMail.PreselectOptions ? settingsManager.ConfigurationData.EMail.PreselectedProfileIds : null,
|
||||
Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.LegalCheck.PreselectedProfileIds : null,
|
||||
Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.MyTasks.PreselectedProfileIds : null,
|
||||
Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProfileIds : null,
|
||||
Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.ERI.PreselectedProfileIds : null,
|
||||
Components.SLIDE_BUILDER_ASSISTANT => settingsManager.ConfigurationData.SlideBuilder.PreselectOptions ? settingsManager.ConfigurationData.SlideBuilder.PreselectedProfileIds : null,
|
||||
Components.VISUAL_BRIEFING_ASSISTANT => settingsManager.ConfigurationData.VisualBriefing.PreselectedProfileIds,
|
||||
// Dynamic assistants have no dedicated settings yet, so they derive their defaults from the chat:
|
||||
Components.DYNAMIC_ASSISTANT or Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Chat.PreselectedProfile : string.Empty,
|
||||
Components.DYNAMIC_ASSISTANT or Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Chat.PreselectedProfileIds : null,
|
||||
|
||||
// The Document Analysis Assistant does not have a preselected profile at the component level.
|
||||
// The profile is selected per policy instead. We do this inside the Document Analysis Assistant component:
|
||||
Components.DOCUMENT_ANALYSIS_ASSISTANT => Profile.NO_PROFILE.Id,
|
||||
Components.DOCUMENT_ANALYSIS_ASSISTANT => [],
|
||||
|
||||
_ => string.Empty,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return ProfilePreselection.FromStoredValue(storedValue);
|
||||
@@ -236,4 +236,4 @@ public static class ComponentsExtensions
|
||||
|
||||
_ => ChatTemplate.NO_CHAT_TEMPLATE,
|
||||
};
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
namespace AIStudio.Tools.PluginSystem.Assistants;
|
||||
|
||||
/// <param name="ProfileIds">The exact profiles to use, an empty list for none, or null for chat defaults.</param>
|
||||
/// <param name="ToolIds">The tools preselected for the chat, or null when the launcher names none.</param>
|
||||
public sealed record AssistantChatLaunchConfiguration(string WorkspaceName, Guid? ProviderId, Guid? ProfileId, Guid? ChatTemplateId, IReadOnlyList<Guid>? DataSourceIds, IReadOnlyList<string>? ToolIds);
|
||||
public sealed record AssistantChatLaunchConfiguration(string WorkspaceName, Guid? ProviderId, IReadOnlyList<Guid>? ProfileIds, Guid? ChatTemplateId, IReadOnlyList<Guid>? DataSourceIds, IReadOnlyList<string>? ToolIds);
|
||||
+11
-5
@@ -169,14 +169,20 @@ public static class DirectChatLauncherLuaWriter
|
||||
builder.AppendLine($" [\"WorkspaceName\"] = \"{Escape(definition.Launch.WorkspaceName.Trim())}\",");
|
||||
|
||||
//
|
||||
// Omitted IDs mean "use the chat defaults", while an empty GUID explicitly selects no
|
||||
// profile or no chat template. An empty provider GUID has no such meaning and is invalid:
|
||||
// Omitted IDs mean "use the chat defaults", while an empty profile list or chat-template
|
||||
// GUID explicitly selects none. An empty provider GUID has no such meaning and is invalid:
|
||||
//
|
||||
if (definition.Launch.ProviderId is { } providerId && providerId != Guid.Empty)
|
||||
builder.AppendLine($" [\"ProviderId\"] = \"{providerId}\",");
|
||||
|
||||
if (definition.Launch.ProfileId is { } profileId)
|
||||
builder.AppendLine($" [\"ProfileId\"] = \"{profileId}\",");
|
||||
if (definition.Launch.ProfileIds is { } profileIds)
|
||||
{
|
||||
builder.AppendLine(" [\"ProfileIds\"] = {");
|
||||
foreach (var profileId in profileIds)
|
||||
builder.AppendLine($" \"{profileId}\",");
|
||||
|
||||
builder.AppendLine(" },");
|
||||
}
|
||||
|
||||
if (definition.Launch.ChatTemplateId is { } chatTemplateId)
|
||||
builder.AppendLine($" [\"ChatTemplateId\"] = \"{chatTemplateId}\",");
|
||||
@@ -236,4 +242,4 @@ public static class DirectChatLauncherLuaWriter
|
||||
.Replace("\r", "\\r", StringComparison.Ordinal)
|
||||
.Replace("\n", "\\n", StringComparison.Ordinal)
|
||||
.Replace("\t", "\\t", StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.PluginSystem.Assistants.DataModel;
|
||||
using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout;
|
||||
using Lua;
|
||||
@@ -241,13 +242,13 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
}
|
||||
|
||||
if (!TryReadOptionalGuid(assistantTable, "ProviderId", false, out var providerId, out message) ||
|
||||
!TryReadOptionalGuid(assistantTable, "ProfileId", true, out var profileId, out message) ||
|
||||
!TryReadOptionalProfileIds(assistantTable, out var profileIds, out message) ||
|
||||
!TryReadOptionalGuid(assistantTable, "ChatTemplateId", true, out var chatTemplateId, out message) ||
|
||||
!TryReadOptionalDataSourceIds(assistantTable, out var dataSourceIds, out message) ||
|
||||
!TryReadOptionalToolIds(assistantTable, out var toolIds, out message))
|
||||
return false;
|
||||
|
||||
this.ChatLaunchConfiguration = new(workspaceName, providerId, profileId, chatTemplateId, dataSourceIds, toolIds);
|
||||
this.ChatLaunchConfiguration = new(workspaceName, providerId, profileIds, chatTemplateId, dataSourceIds, toolIds);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -309,6 +310,56 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryReadOptionalProfileIds(LuaTable assistantTable, out IReadOnlyList<Guid>? profileIds, out string message)
|
||||
{
|
||||
profileIds = null;
|
||||
message = string.Empty;
|
||||
var hasLegacyId = assistantTable.TryGetValue("ProfileId", out _);
|
||||
var hasIds = assistantTable.TryGetValue("ProfileIds", out var profileIdsValue);
|
||||
if (hasLegacyId && hasIds)
|
||||
{
|
||||
message = TB("The ASSISTANT table contains both ProfileId and ProfileIds. Use only one of them.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasLegacyId)
|
||||
{
|
||||
if (!TryReadOptionalGuid(assistantTable, "ProfileId", true, out var profileId, out message))
|
||||
return false;
|
||||
|
||||
profileIds = profileId == Guid.Empty ? [] : profileId is { } id ? [id] : null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!hasIds)
|
||||
return true;
|
||||
|
||||
if (!profileIdsValue.TryRead<LuaTable>(out var profileIdsTable))
|
||||
{
|
||||
message = TB("The ASSISTANT table contains invalid ProfileIds. Expected a list of unique, non-empty GUIDs.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var parsedIds = new List<Guid>(profileIdsTable.ArrayLength);
|
||||
var uniqueIds = new HashSet<Guid>();
|
||||
for (var index = 1; index <= profileIdsTable.ArrayLength; index++)
|
||||
{
|
||||
if (!profileIdsTable[index].TryRead<string>(out var idText) ||
|
||||
!Guid.TryParse(idText, out var parsedId) ||
|
||||
parsedId == Guid.Empty ||
|
||||
!uniqueIds.Add(parsedId))
|
||||
{
|
||||
message = TB("The ASSISTANT table contains invalid ProfileIds. Expected a list of unique, non-empty GUIDs.");
|
||||
return false;
|
||||
}
|
||||
|
||||
parsedIds.Add(parsedId);
|
||||
}
|
||||
|
||||
profileIds = parsedIds.ToImmutableArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the tools an assistant names: the ones a launcher preselects for its chat, or the ones
|
||||
/// the assistant itself runs with.
|
||||
@@ -383,12 +434,14 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
|
||||
InitializeState(this.RootComponent.Children, assistantState);
|
||||
|
||||
var input = assistantState.ToLuaTable(this.RootComponent?.Children ?? []);
|
||||
input["profiles"] = new LuaTable();
|
||||
input["profile"] = new LuaTable
|
||||
{
|
||||
["Name"] = string.Empty,
|
||||
["NeedToKnow"] = string.Empty,
|
||||
["Actions"] = string.Empty,
|
||||
["Num"] = 0,
|
||||
["Id"] = Profile.NO_PROFILE.Id,
|
||||
["Name"] = Profile.NO_PROFILE.Name,
|
||||
["NeedToKnow"] = Profile.NO_PROFILE.NeedToKnow,
|
||||
["Actions"] = Profile.NO_PROFILE.Actions,
|
||||
["Num"] = Profile.NO_PROFILE.Num,
|
||||
};
|
||||
|
||||
var prompt = await this.TryBuildPromptAsync(input, cancellationToken);
|
||||
|
||||
@@ -210,6 +210,9 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
if (!TryValidateMinimumProviderConfidenceConfiguration(settingsTable, out message))
|
||||
return false;
|
||||
|
||||
if (!TryValidateProfilePreselectionConfiguration(settingsTable, out message))
|
||||
return false;
|
||||
|
||||
this.DeclaredSettingsCount = CountDeclaredSettings(settingsTable);
|
||||
|
||||
// Config: check for updates, and if so, how often?
|
||||
@@ -346,13 +349,29 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
// Config: preselected provider?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
|
||||
|
||||
// Config: preselected profile?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.App, x => x.PreselectedProfile, Guid.Empty, this.Id, settingsTable, dryRun);
|
||||
// Config: preselected profiles? The singular name remains a compatibility alias.
|
||||
var appProfilesValid = settingsTable.TryGetValue("DataApp.PreselectedProfileIds", out _)
|
||||
? ManagedConfiguration.TryProcessProfileIds(x => x.App, x => x.PreselectedProfileIds, this.Id, settingsTable, dryRun)
|
||||
: !settingsTable.TryGetValue("DataApp.PreselectedProfile", out _) ||
|
||||
ManagedConfiguration.TryProcessLegacyProfileIds(x => x.App, x => x.PreselectedProfileIds, "DataApp.PreselectedProfile", this.Id, settingsTable, dryRun);
|
||||
if (!appProfilesValid)
|
||||
{
|
||||
message = TB("The configured app profile preselection is invalid.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Config: preselected chat options?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectOptions, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedProvider, Guid.Empty, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedProfile, this.Id, settingsTable, dryRun);
|
||||
var chatProfilesValid = settingsTable.TryGetValue("DataChat.PreselectedProfileIds", out _)
|
||||
? ManagedConfiguration.TryProcessProfilePreselection(x => x.Chat, x => x.PreselectedProfileIds, this.Id, settingsTable, dryRun)
|
||||
: !settingsTable.TryGetValue("DataChat.PreselectedProfile", out _) ||
|
||||
ManagedConfiguration.TryProcessLegacyProfilePreselection(x => x.Chat, x => x.PreselectedProfileIds, "DataChat.PreselectedProfile", this.Id, settingsTable, dryRun);
|
||||
if (!chatProfilesValid)
|
||||
{
|
||||
message = TB("The configured chat profile preselection is invalid.");
|
||||
return false;
|
||||
}
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedChatTemplate, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedDataSourcesDisabled, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedDataSourcesAutomaticSelection, this.Id, settingsTable, dryRun);
|
||||
@@ -360,6 +379,16 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.PreselectedDataSourceIds, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.Chat, x => x.SendToChatDataSourceBehavior, this.Id, settingsTable, dryRun);
|
||||
|
||||
var visualBriefingProfilesValid = settingsTable.TryGetValue("DataVisualBriefing.PreselectedProfileIds", out _)
|
||||
? ManagedConfiguration.TryProcessProfilePreselection(x => x.VisualBriefing, x => x.PreselectedProfileIds, this.Id, settingsTable, dryRun)
|
||||
: !settingsTable.TryGetValue("DataVisualBriefing.PreselectedProfile", out _) ||
|
||||
ManagedConfiguration.TryProcessLegacyProfilePreselection(x => x.VisualBriefing, x => x.PreselectedProfileIds, "DataVisualBriefing.PreselectedProfile", this.Id, settingsTable, dryRun);
|
||||
if (!visualBriefingProfilesValid)
|
||||
{
|
||||
message = TB("The configured visual briefing profile preselection is invalid.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Config: Batch Processing Assistant defaults?
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.PreselectOptions, this.Id, settingsTable, dryRun);
|
||||
ManagedConfiguration.TryProcessConfiguration(x => x.BatchProcessing, x => x.InputDirectory, this.Id, settingsTable, dryRun);
|
||||
@@ -391,6 +420,27 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryValidateProfilePreselectionConfiguration(LuaTable settings, out string message)
|
||||
{
|
||||
foreach (var settingPrefix in new[] { "DataApp", "DataChat", "DataVisualBriefing" })
|
||||
{
|
||||
var singularName = $"{settingPrefix}.PreselectedProfile";
|
||||
var pluralName = $"{settingPrefix}.PreselectedProfileIds";
|
||||
if (settings.TryGetValue(singularName, out _) && settings.TryGetValue(pluralName, out _))
|
||||
{
|
||||
message = string.Format(TB("The SETTINGS table contains both '{0}' and '{1}'. Use only one of them."), singularName, pluralName);
|
||||
return false;
|
||||
}
|
||||
|
||||
var legacyOverrideName = $"{singularName}.AllowUserOverride";
|
||||
if (settings.TryGetValue(legacyOverrideName, out var legacyOverride))
|
||||
settings[$"{pluralName}.AllowUserOverride"] = legacyOverride;
|
||||
}
|
||||
|
||||
message = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryValidateMinimumProviderConfidenceConfiguration(LuaTable settingsTable, out string message)
|
||||
{
|
||||
const string SETTING_NAME = "DataTools.MinimumProviderConfidenceByToolId";
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace AIStudio.Tools.Services;
|
||||
/// </summary>
|
||||
/// <param name="WorkspaceName">The workspace the chat is created in.</param>
|
||||
/// <param name="ProviderId">The provider to preselect, or null for the chat default.</param>
|
||||
/// <param name="ProfileId">The profile to preselect; the empty GUID selects no profile.</param>
|
||||
/// <param name="ProfileIds">The exact profiles to use, an empty list for none, or null for chat defaults.</param>
|
||||
/// <param name="ChatTemplateId">The chat template to preselect; the empty GUID selects none.</param>
|
||||
/// <param name="DataSourceIds">The data sources to preselect, or null for the chat defaults.</param>
|
||||
/// <param name="ToolIds">The tools to preselect, or null for the chat defaults.</param>
|
||||
public sealed record AssistantBuilderChatLaunchRequest(string WorkspaceName, string? ProviderId, string? ProfileId, string? ChatTemplateId, IReadOnlyList<string>? DataSourceIds, IReadOnlyList<string>? ToolIds);
|
||||
public sealed record AssistantBuilderChatLaunchRequest(string WorkspaceName, string? ProviderId, IReadOnlyList<string>? ProfileIds, string? ChatTemplateId, IReadOnlyList<string>? DataSourceIds, IReadOnlyList<string>? ToolIds);
|
||||
@@ -164,7 +164,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
new(
|
||||
chatLaunch.WorkspaceName.Trim(),
|
||||
ParseOptionalGuid(chatLaunch.ProviderId),
|
||||
ParseOptionalGuid(chatLaunch.ProfileId),
|
||||
chatLaunch.ProfileIds?.Select(Guid.Parse).ToArray(),
|
||||
ParseOptionalGuid(chatLaunch.ChatTemplateId),
|
||||
chatLaunch.DataSourceIds?.Select(Guid.Parse).ToArray(),
|
||||
chatLaunch.ToolIds));
|
||||
@@ -505,9 +505,9 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
: $$"""
|
||||
- Describe a direct chat launcher, not a form assistant.
|
||||
- Copy the structured ChatLaunch selections faithfully into the {{TB("Chat Launcher")}}, {{TB("Workspace")}}, {{TB("Chat Configuration")}}, {{TB("Data Sources")}}, and {{TB("Tools")}} sections.
|
||||
- Explain omitted provider, profile, template, data-source, or tool values as using the normal chat defaults.
|
||||
- Explain omitted provider, profiles, template, data-source, or tool values as using the normal chat defaults.
|
||||
- In the {{TB("Tools")}} section, say what the preselected tools let the chat do and that users may change the selection once the chat is open.
|
||||
- Explain the empty profile/template GUID as explicitly selecting no profile/template.
|
||||
- Explain an empty profile list as explicitly selecting no profiles and the empty template GUID as selecting no template.
|
||||
- Do not propose UI components, submit behavior, BuildPrompt, or a plugin SystemPrompt for a chat launcher.
|
||||
""";
|
||||
|
||||
@@ -619,9 +619,9 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
- Set assistant.kind to "CHAT_LAUNCHER" exactly when the revised ASSISTANT table uses LaunchBehavior = "OPEN_WORKSPACE_CHAT_BY_NAME"; otherwise set it to "FORM".
|
||||
- For a form assistant, include system_prompt, submit_text, and allow_ai_studio_profiles in the JSON assistant object and omit launch. Include tool_ids exactly when the revised ASSISTANT table carries ToolIds.
|
||||
- Change ASSISTANT.ToolIds only when the requested change asks for it. Use only tool IDs from the "Available tools" list in the plugin context for tools you add; never invent an ID. Drop the field entirely rather than writing an empty list.
|
||||
- For a chat launcher, include launch with the exact WorkspaceName and optional ProviderId, ProfileId, ChatTemplateId, DataSourceIds, and ToolIds values from the revised ASSISTANT table; omit system_prompt, submit_text, and allow_ai_studio_profiles.
|
||||
- For a chat launcher, include launch with the exact WorkspaceName and optional ProviderId, ProfileIds, ChatTemplateId, DataSourceIds, and ToolIds values from the revised ASSISTANT table; omit system_prompt, submit_text, and allow_ai_studio_profiles.
|
||||
- A chat launcher must not include SystemPrompt, SubmitText, AllowProfiles, BuildPrompt, or UI in its ASSISTANT table.
|
||||
- Preserve an empty profile or template GUID when it explicitly means no profile or no template. Do not emit empty provider or data-source GUIDs.
|
||||
- Preserve an empty ProfileIds list when it explicitly means no profiles, and preserve an empty template GUID when it means no template. Do not emit empty provider, profile, or data-source GUIDs.
|
||||
- Preserve existing behavior unless the requested change explicitly modifies it.
|
||||
- Apply the requested change directly to plugin.lua; do not describe how to change it.
|
||||
- Do not create companion files, new require(...) dependencies, hidden behavior, or obfuscated behavior.
|
||||
@@ -754,7 +754,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
|
||||
if (string.IsNullOrWhiteSpace(launch.WorkspaceName) ||
|
||||
!IsOptionalGuid(launch.ProviderId, allowEmpty: false) ||
|
||||
!IsOptionalGuid(launch.ProfileId, allowEmpty: true) ||
|
||||
!IsOptionalGuidList(launch.ProfileIds, allowEmptyList: true) ||
|
||||
!IsOptionalGuid(launch.ChatTemplateId, allowEmpty: true))
|
||||
return false;
|
||||
|
||||
@@ -784,10 +784,14 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
if (actual is null ||
|
||||
!string.Equals(requested.WorkspaceName.Trim(), actual.WorkspaceName, StringComparison.Ordinal) ||
|
||||
ParseOptionalGuid(requested.ProviderId) != actual.ProviderId ||
|
||||
ParseOptionalGuid(requested.ProfileId) != actual.ProfileId ||
|
||||
ParseOptionalGuid(requested.ChatTemplateId) != actual.ChatTemplateId)
|
||||
return false;
|
||||
|
||||
var requestedProfileIds = requested.ProfileIds?.Select(Guid.Parse).ToArray();
|
||||
if (!(requestedProfileIds is null && actual.ProfileIds is null ||
|
||||
requestedProfileIds is not null && actual.ProfileIds is not null && requestedProfileIds.SequenceEqual(actual.ProfileIds)))
|
||||
return false;
|
||||
|
||||
var requestedDataSourceIds = requested.DataSourceIds?.Select(Guid.Parse).ToArray();
|
||||
if (!(requestedDataSourceIds is null && actual.DataSourceIds is null ||
|
||||
requestedDataSourceIds is not null && actual.DataSourceIds is not null &&
|
||||
@@ -832,7 +836,7 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
var request = new AssistantBuilderChatLaunchRequest(
|
||||
launch.WorkspaceName,
|
||||
launch.ProviderId,
|
||||
launch.ProfileId,
|
||||
launch.ProfileIds,
|
||||
launch.ChatTemplateId,
|
||||
launch.DataSourceIds,
|
||||
launch.ToolIds);
|
||||
@@ -868,6 +872,11 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
private static bool IsOptionalGuid(string? value, bool allowEmpty) => value is null ||
|
||||
Guid.TryParse(value, out var parsed) && (allowEmpty || parsed != Guid.Empty);
|
||||
|
||||
private static bool IsOptionalGuidList(IReadOnlyList<string>? values, bool allowEmptyList) => values is null ||
|
||||
(allowEmptyList || values.Count > 0) &&
|
||||
values.All(value => Guid.TryParse(value, out var parsed) && parsed != Guid.Empty) &&
|
||||
values.Distinct(StringComparer.OrdinalIgnoreCase).Count() == values.Count;
|
||||
|
||||
private static Guid? ParseOptionalGuid(string? value) => value is null ? null : Guid.Parse(value);
|
||||
|
||||
private static string SerializeUntrustedPromptData(object value) => JsonSerializer.Serialize(value, UNTRUSTED_PROMPT_JSON_OPTIONS);
|
||||
@@ -888,4 +897,4 @@ public sealed class AssistantPluginGenerationService(ToolRegistry toolRegistry,
|
||||
private static AssistantPluginRevisionDraft RevisionFailure(string issue) => new(false, string.Empty, string.Empty, issue);
|
||||
|
||||
private readonly record struct AssistantContextFile(string Title, string RelativePath, bool IsRequired);
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@ public sealed class DirectChatService(SettingsManager settingsManager, DataSourc
|
||||
if (providerResult.IsExplicit && providerResult.Provider == ProviderSettings.NONE)
|
||||
return new(null, providerResult.ErrorMessage);
|
||||
|
||||
var profileResult = this.ResolveProfile(launchConfiguration.ProfileId);
|
||||
var profile = profileResult.Profile;
|
||||
if (profile is null)
|
||||
var profileResult = this.ResolveProfiles(launchConfiguration.ProfileIds);
|
||||
var profiles = profileResult.Profiles;
|
||||
if (profiles is null)
|
||||
return new(null, profileResult.ErrorMessage);
|
||||
|
||||
var chatTemplateResult = this.ResolveChatTemplate(launchConfiguration.ChatTemplateId);
|
||||
@@ -36,13 +36,13 @@ public sealed class DirectChatService(SettingsManager settingsManager, DataSourc
|
||||
// its profile selection for such templates, so keeping the profile would pin one that the
|
||||
// user can neither see nor change. We drop it instead of failing the whole launch.
|
||||
//
|
||||
if (!chatTemplate.AllowProfileUsage && profile != Profile.NO_PROFILE)
|
||||
if (!chatTemplate.AllowProfileUsage && profiles.Count > 0)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Assistant plugin '{PluginName}' selects the profile '{ProfileName}', but its chat template '{ChatTemplateName}' does not allow profiles. The chat starts without a profile.",
|
||||
assistantPlugin.Name, profile.GetSafeName(), chatTemplate.GetSafeName());
|
||||
"Assistant plugin '{PluginName}' selects profiles, but its chat template '{ChatTemplateName}' does not allow profiles. The chat starts without profiles.",
|
||||
assistantPlugin.Name, chatTemplate.GetSafeName());
|
||||
|
||||
profile = Profile.NO_PROFILE;
|
||||
profiles = [];
|
||||
}
|
||||
|
||||
var dataSourceOptionsResult = await this.ResolveDataSourceOptionsAsync(providerResult.Provider, launchConfiguration.DataSourceIds);
|
||||
@@ -81,7 +81,7 @@ public sealed class DirectChatService(SettingsManager settingsManager, DataSourc
|
||||
{
|
||||
IncludeDateTime = true,
|
||||
SelectedProvider = providerResult.Provider == ProviderSettings.NONE ? string.Empty : providerResult.Provider.Id,
|
||||
SelectedProfile = profile.Id,
|
||||
SelectedProfileIds = profiles.Select(profile => profile.Id).ToHashSet(StringComparer.OrdinalIgnoreCase),
|
||||
SelectedChatTemplate = chatTemplate.Id,
|
||||
// The provider confidence is checked later, when the chat sends a message:
|
||||
SelectedToolIds = selectedToolIds,
|
||||
@@ -121,23 +121,19 @@ public sealed class DirectChatService(SettingsManager settingsManager, DataSourc
|
||||
return new(provider, true, string.Empty);
|
||||
}
|
||||
|
||||
private (Profile? Profile, string ErrorMessage) ResolveProfile(Guid? profileId)
|
||||
private (IReadOnlyList<Profile>? Profiles, string ErrorMessage) ResolveProfiles(IReadOnlyList<Guid>? profileIds)
|
||||
{
|
||||
if (profileId is null)
|
||||
return new(settingsManager.GetPreselectedProfile(Components.CHAT), string.Empty);
|
||||
if (profileIds is null)
|
||||
return new(settingsManager.GetPreselectedProfiles(Components.CHAT), string.Empty);
|
||||
|
||||
// The launcher explicitly wants no profile:
|
||||
if (profileId == Guid.Empty)
|
||||
return new(Profile.NO_PROFILE, string.Empty);
|
||||
if (profileIds.Count == 0)
|
||||
return new([], string.Empty);
|
||||
|
||||
//
|
||||
// We already handled the empty GUID above, so GetProfileById returning the no-profile
|
||||
// entry here can only mean that the referenced profile is gone:
|
||||
//
|
||||
var profile = settingsManager.GetProfileById(profileId.Value.ToString());
|
||||
return profile == Profile.NO_PROFILE
|
||||
? new(null, string.Format(TB("The assistant chat launcher references profile '{0}', but that profile does not exist."), profileId))
|
||||
: new(profile, string.Empty);
|
||||
var profiles = settingsManager.ResolveProfiles(profileIds.Select(profileId => profileId.ToString()));
|
||||
if (profiles.Count != profileIds.Count)
|
||||
return new(null, TB("The assistant chat launcher references one or more profiles that do not exist."));
|
||||
|
||||
return new(profiles, string.Empty);
|
||||
}
|
||||
|
||||
private (ChatTemplate? ChatTemplate, string ErrorMessage) ResolveChatTemplate(Guid? chatTemplateId)
|
||||
@@ -212,4 +208,4 @@ public sealed class DirectChatService(SettingsManager settingsManager, DataSourc
|
||||
PreselectedDataSourceIds = requestedDataSources.Select(source => source.Id).ToList(),
|
||||
}, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user