Add assistant plugin deletion with eligibility checks

This commit is contained in:
Thorsten Sommer 2026-07-15 20:49:17 +02:00
parent cba505a632
commit 521b495fa9
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
15 changed files with 365 additions and 175 deletions

View File

@ -2389,6 +2389,24 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T3571008422"] = "Assistan
-- The result is ready.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T661906146"] = "The result is ready."
-- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T1318944584"] = "The assistant cannot be deleted while background work is still running."
-- Delete assistant plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T1692493145"] = "Delete assistant plugin"
-- Delete Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3637071001"] = "Delete Assistant Plugin"
-- The '{0}' assistant plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3815023384"] = "The '{0}' assistant plugin has been successfully removed."
-- The assistant plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3985264168"] = "The assistant plugin '{0}' could not be deleted: {1}"
-- Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T4033722845"] = "Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files."
-- Show or hide the detailed security information.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1045105126"] = "Show or hide the detailed security information."
@ -7282,9 +7300,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1506922856"] = "Assistant Audit"
-- Internal Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Internal Plugins"
-- Delete assistant plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1692493145"] = "Delete assistant plugin"
-- Disabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins"
@ -7318,24 +7333,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3143506997"] = "The assistant plugin
-- Close
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3448155331"] = "Close"
-- Delete Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3637071001"] = "Delete Assistant Plugin"
-- Revise assistant plugin with AI
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3801095542"] = "Revise assistant plugin with AI"
-- The '{0}' assistant plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3815023384"] = "The '{0}' assistant plugin has been successfully removed."
-- Actions
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3865031940"] = "Actions"
-- The assistant plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3985264168"] = "The assistant plugin '{0}' could not be deleted: {1}"
-- Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4033722845"] = "Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files."
-- The automatic security audit for the assistant plugin '{0}' failed. Please run it manually.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4066679817"] = "The automatic security audit for the assistant plugin '{0}' failed. Please run it manually."
@ -8857,6 +8860,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1211
-- Only assistant plugins can be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1288328479"] = "Only assistant plugins can be edited."
-- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1318944584"] = "The assistant cannot be deleted while background work is still running."
-- No Lua plugin code was generated.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1839013358"] = "No Lua plugin code was generated."
@ -8887,6 +8893,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T2955
-- The generated assistant plugin uses the ID of an internal AI Studio plugin.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3162363526"] = "The generated assistant plugin uses the ID of an internal AI Studio plugin."
-- Config Server managed assistant plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3751820312"] = "Config Server managed assistant plugins cannot be deleted."
-- Only assistants generated by the Assistant Builder can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3940247198"] = "Only assistants generated by the Assistant Builder can be deleted."
-- The edited plugin is not an assistant plugin. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3984111892"] = "The edited plugin is not an assistant plugin. Issue: {0}"

View File

@ -50,6 +50,7 @@
{
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" Color="Color.Default" OnClick="@this.OpenSettingsDialog"/>
}
@this.AdditionalActions
</MudButtonGroup>
@if (this.SecurityBadge is not null)
{

View File

@ -43,6 +43,9 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
[Parameter]
public RenderFragment? SecurityBadge { get; set; }
[Parameter]
public RenderFragment? AdditionalActions { get; set; }
[Parameter]
public Tools.Components Component { get; set; } = Tools.Components.NONE;

View File

@ -0,0 +1,13 @@
@inherits MSGComponentBase
@if (this.CanDelete)
{
<MudTooltip Text="@this.Tooltip">
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
Color="Color.Error"
Variant="Variant.Text"
Size="Size.Medium"
Disabled="@this.IsBlockedByActiveWork"
OnClick="@this.DeleteAssistantPluginAsync" />
</MudTooltip>
}

View File

@ -0,0 +1,90 @@
using AIStudio.Dialogs;
using AIStudio.Tools.Media;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Components;
public partial class AssistantPluginDeleteAction : MSGComponentBase
{
[Parameter, EditorRequired]
public IAvailablePlugin Plugin { get; set; } = null!;
[Inject]
private IDialogService DialogService { get; init; } = null!;
[Inject]
private AssistantPluginInstallService AssistantPluginInstallService { get; init; } = null!;
[Inject]
private MediaTranscriptionService MediaTranscriptionService { get; init; } = null!;
[Inject]
private ILogger<AssistantPluginDeleteAction> Logger { get; init; } = null!;
private bool CanDelete => AssistantPluginInstallService.CanDeleteInstalledAssistant(this.Plugin);
private bool IsBlockedByActiveWork => this.AssistantPluginInstallService.HasActiveAssistantWork(this.Plugin.Id);
private string Tooltip => this.IsBlockedByActiveWork
? this.T("The assistant cannot be deleted while background work is still running.")
: this.T("Delete assistant plugin");
protected override async Task OnInitializedAsync()
{
this.ApplyFilters([], [ Event.ASSISTANT_SESSION_CHANGED, Event.ASSISTANT_SESSION_FINISHED ]);
this.MediaTranscriptionService.StateChanged += this.OnMediaTranscriptionStateChanged;
await base.OnInitializedAsync();
}
private async Task DeleteAssistantPluginAsync()
{
if (!this.CanDelete || this.IsBlockedByActiveWork)
return;
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{
x => x.Message,
string.Format(this.T("Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files."), this.Plugin.Name)
},
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(this.T("Delete Assistant Plugin"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var result = await this.AssistantPluginInstallService.DeleteInstalledAssistantAsync(this.Plugin, CancellationToken.None);
if (!result.Success)
{
this.Logger.LogError("Failed to delete assistant plugin '{PluginName}' ({PluginId}) from '{PluginDirectory}' with issue '{Issue}'.", result.PluginName, result.PluginId, result.PluginDirectory, result.Issue);
await this.MessageBus.SendError(new(Icons.Material.Filled.DeleteForever, string.Format(this.T("The assistant plugin '{0}' could not be deleted: {1}"), this.Plugin.Name, result.Issue)));
return;
}
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.Check, string.Format(this.T("The '{0}' assistant plugin has been successfully removed."), result.PluginName)));
}
private void OnMediaTranscriptionStateChanged(MediaImportOwner owner)
{
if (owner.Kind is MediaImportOwnerKind.ASSISTANT && owner.Id.EndsWith($":{this.Plugin.Id}", StringComparison.Ordinal))
_ = this.InvokeAsync(this.StateHasChanged);
}
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
{
if (triggeredEvent is Event.ASSISTANT_SESSION_CHANGED or Event.ASSISTANT_SESSION_FINISHED)
this.StateHasChanged();
return base.ProcessIncomingMessage(sendingComponent, triggeredEvent, data);
}
protected override void DisposeResources()
{
this.MediaTranscriptionService.StateChanged -= this.OnMediaTranscriptionStateChanged;
base.DisposeResources();
}
}

View File

@ -1,6 +1,7 @@
@attribute [Route(Routes.ASSISTANTS)]
@using AIStudio.Dialogs.Settings
@using AIStudio.Settings.DataModel
@using AIStudio.Tools.PluginSystem
@using AIStudio.Tools.PluginSystem.Assistants
@inherits MSGComponentBase
@ -45,6 +46,7 @@
{
var securityState = PluginAssistantSecurityResolver.Resolve(this.SettingsManager, assistantPlugin);
var launchLink = assistantPlugin.StartsChatDirectly ? string.Empty : $"{Routes.ASSISTANT_DYNAMIC}?assistantId={assistantPlugin.Id}";
var availablePlugin = PluginFactory.AvailablePlugins.OfType<IAvailablePlugin>().FirstOrDefault(plugin => plugin.Id == assistantPlugin.Id);
<AssistantBlock TSettings="NoSettingsPanel"
Name="@T(assistantPlugin.AssistantTitle)"
Description="@T(assistantPlugin.Description)"
@ -53,6 +55,12 @@
AssistantSessionInstanceId="@assistantPlugin.Id.ToString()"
Link="@launchLink"
OnClick="@(() => this.StartAssistantPluginAsync(assistantPlugin))">
<AdditionalActions>
@if (availablePlugin is not null)
{
<AssistantPluginDeleteAction Plugin="@availablePlugin" />
}
</AdditionalActions>
<SecurityBadge>
<AssistantPluginSecurityCard Plugin="@assistantPlugin" Compact="@true" />
</SecurityBadge>

View File

@ -115,11 +115,9 @@
</MudTooltip>
}
@if (context is IAvailablePlugin availablePlugin && CanDeleteAssistantPlugin(availablePlugin))
@if (context is IAvailablePlugin availablePlugin)
{
<MudTooltip Text="@T("Delete assistant plugin")">
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline" Size="Size.Medium" OnClick="@(() => this.DeleteAssistantPluginAsync(availablePlugin))"/>
</MudTooltip>
<AssistantPluginDeleteAction Plugin="@availablePlugin" />
}
</MudButtonGroup>
</MudStack>

View File

@ -28,9 +28,6 @@ public partial class Plugins : MSGComponentBase
[Inject]
private AssistantPluginAuditService AssistantPluginAuditService { get; init; } = null!;
[Inject]
private AssistantPluginInstallService AssistantPluginInstallService { get; init; } = null!;
private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(Plugins));
#region Overrides of ComponentBase
@ -190,8 +187,6 @@ public partial class Plugins : MSGComponentBase
private static bool CanEditAssistantPlugin(IAvailablePlugin plugin) => plugin is { IsInternal: false, Type: PluginType.ASSISTANT } && !string.IsNullOrWhiteSpace(plugin.LocalPath);
private static bool CanDeleteAssistantPlugin(IAvailablePlugin plugin) => plugin is { IsInternal: false, Type: PluginType.ASSISTANT } && !string.IsNullOrWhiteSpace(plugin.LocalPath);
private static bool CanReviseAssistantPlugin(IAvailablePlugin plugin)
{
var assistantPlugin = PluginFactory.RunningPlugins.OfType<PluginAssistants>().FirstOrDefault(x => x.Id == plugin.Id);
@ -239,33 +234,6 @@ public partial class Plugins : MSGComponentBase
await this.InvokeAsync(this.StateHasChanged);
}
private async Task DeleteAssistantPluginAsync(IAvailablePlugin plugin)
{
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{
x => x.Message,
string.Format(this.T("Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files."), plugin.Name)
},
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(this.T("Delete Assistant Plugin"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var result = await this.AssistantPluginInstallService.DeleteInstalledAssistantAsync(plugin, CancellationToken.None);
if (!result.Success)
{
LOG.LogError($"Failed to delete assistant plugin '{result.PluginName}' ({result.PluginId}) from '{result.PluginDirectory}' with issue '{result.Issue}'.");
await this.MessageBus.SendError(new(Icons.Material.Filled.DeleteForever, string.Format(this.T("The assistant plugin '{0}' could not be deleted: {1}"), plugin.Name, result.Issue)));
return;
}
await this.MessageBus.SendSuccess(new(Icons.Material.Filled.Check, string.Format(this.T("The '{0}' assistant plugin has been successfully removed."), result.PluginName)));
await this.InvokeAsync(this.StateHasChanged);
}
private static bool IsSendingMail(string sourceUrl) => sourceUrl.TrimStart().StartsWith("mailto:", StringComparison.OrdinalIgnoreCase);
private PluginAssistants? TryGetAssistantPlugin(Guid pluginId) => PluginFactory.RunningPlugins.OfType<PluginAssistants>().FirstOrDefault(x => x.Id == pluginId);

View File

@ -82,6 +82,7 @@ Each assistant plugin lives in its own directory under the assistants plugin roo
## Structure
- `ASSISTANT` is the root table. It must contain `Title`, `Description`, `SystemPrompt`, `SubmitText`, `AllowProfiles`, and the nested `UI` definition.
- `DEPLOYED_USING_CONFIG_SERVER` identifies who manages the assistant plugin. Set it to `false` for locally managed plugins. A missing field is also treated as local for compatibility with existing plugins. Enterprise-distributed plugins must set it to `true` and cannot be revised with AI in AI Studio.
- `AI_STUDIO_ASSISTANT_BUILDER = {Generated = true, SchemaVersion = 1}` is reserved for plugins generated by the AI Studio Assistant Builder. It enables Builder-specific actions such as safe deletion and must not be added to manually authored or enterprise-distributed assistants. Newly generated Builder assistants always set `DEPLOYED_USING_CONFIG_SERVER = false` explicitly.
- `ASSISTANT` may optionally define direct-launch metadata for assistant tiles:
- `LaunchBehavior = "OPEN_WORKSPACE_CHAT_BY_NAME"`
- `WorkspaceName = "<target workspace name>"`

View File

@ -49,6 +49,11 @@ DEPRECATION_MESSAGE = ""
-- Enterprise-managed assistants cannot be revised with AI. Keep false for locally managed plugins:
DEPLOYED_USING_CONFIG_SERVER = false
-- Reserved for assistants created by the AI Studio Assistant Builder. Generated assistants use this
-- metadata so AI Studio can identify them and offer Builder-specific actions such as safe deletion.
-- Manually authored or enterprise-distributed assistants must not set this metadata:
-- AI_STUDIO_ASSISTANT_BUILDER = {Generated = true, SchemaVersion = 1}
ASSISTANT = {
["Title"] = "<Title of your assistant>",
["Description"] = "<Description presented to the users, explaining your assistant>",

View File

@ -2391,6 +2391,24 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T3571008422"] = "Assisten
-- The result is ready.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T661906146"] = "Das Ergebnis ist fertig."
-- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T1318944584"] = "Der Assistent kann nicht gelöscht werden, solange noch Hintergrundaufgaben ausgeführt werden."
-- Delete assistant plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T1692493145"] = "Assistenten-Plugin löschen"
-- Delete Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3637071001"] = "Assistenten-Plugin löschen"
-- The '{0}' assistant plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3815023384"] = "Das Assistenten-Plugin „{0}“ wurde erfolgreich entfernt."
-- The assistant plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3985264168"] = "Das Assistenten-Plugin „{0}“ konnte nicht gelöscht werden: {1}"
-- Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T4033722845"] = "Möchtest du das Assistenten-Plug-in „{0}“ wirklich löschen? Dadurch werden die lokalen Plug-in-Dateien dauerhaft gelöscht."
-- Show or hide the detailed security information.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1045105126"] = "Detaillierte Sicherheitsinformationen anzeigen oder ausblenden."
@ -7284,9 +7302,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1506922856"] = "Assistentenprüfung"
-- Internal Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Interne Plugins"
-- Delete assistant plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1692493145"] = "Assistenten-Plugin löschen"
-- Disabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Deaktivierte Plugins"
@ -7320,24 +7335,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3143506997"] = "Das Assistent-Plugin
-- Close
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3448155331"] = "Schließen"
-- Delete Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3637071001"] = "Assistant-Plug-in löschen"
-- Revise assistant plugin with AI
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3801095542"] = "Assistenten-Plugin mit KI überarbeiten"
-- The '{0}' assistant plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3815023384"] = "Das Assistenten-Plugin „{0}“ wurde erfolgreich entfernt."
-- Actions
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3865031940"] = "Aktionen"
-- The assistant plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3985264168"] = "Das Assistenten-Plugin „{0}“ konnte nicht gelöscht werden: {1}"
-- Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4033722845"] = "Möchten Sie das Assistenten-Plugin „{0}“ wirklich löschen? Dadurch werden die lokalen Plugin-Dateien dauerhaft gelöscht."
-- The automatic security audit for the assistant plugin '{0}' failed. Please run it manually.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4066679817"] = "Die automatische Sicherheitsprüfung für das Assistenten-Plugin „{0}“ ist fehlgeschlagen. Bitte führen Sie sie manuell aus."
@ -8748,42 +8751,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source Code
-- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Dokument"
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "Der konfigurierte Transkriptionsanbieter konnte nicht erstellt werden."
-- The selected media file no longer exists.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T129859547"] = "Die ausgewählte Mediendatei ist nicht mehr vorhanden."
-- The selected media file does not contain an audio track.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T134825479"] = "Die ausgewählte Mediendatei enthält keine Audiospur."
-- The media file could not be transcribed.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1543974632"] = "Die Mediendatei konnte nicht transkribiert werden."
-- The selected file cannot be processed as media.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1707342767"] = "Die ausgewählte Datei kann nicht als Medium verarbeitet werden."
-- The audio track contains no audible signal, so there is nothing to transcribe.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1988190152"] = "Die Audiospur enthält kein hörbares Signal. Daher gibt es nichts zu transkribieren."
-- The media file is damaged or its format could not be identified.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2004316549"] = "Die Mediendatei ist beschädigt oder ihr Format konnte nicht erkannt werden."
-- This media format or audio codec is not supported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2142564510"] = "Dieses Medienformat oder dieser Audiocodec wird nicht unterstützt."
-- No usable transcription provider is configured.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2282521655"] = "Es ist kein nutzbarer Transkriptionsanbieter konfiguriert."
-- The media file could not be prepared for transcription.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2749117459"] = "Die Mediendatei konnte nicht für die Transkription vorbereitet werden."
-- The transcription provider could not transcribe the media file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T3091669215"] = "Der Transkriptionsanbieter konnte die Mediendatei nicht transkribieren."
-- The media pipeline ended without an output file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T632852430"] = "Die Medienpipeline wurde beendet, ohne eine Ausgabedatei zu erzeugen."
-- The Assistant Builder context could not be loaded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T108292972"] = "Der Kontext des Assistenten-Builders konnte nicht geladen werden."
@ -8895,6 +8862,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1211
-- Only assistant plugins can be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1288328479"] = "Nur Assistant-Plugins können bearbeitet werden."
-- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1318944584"] = "Der Assistent kann nicht gelöscht werden, solange noch Hintergrundaktivitäten ausgeführt werden."
-- No Lua plugin code was generated.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1839013358"] = "Es wurde kein Lua-Plugin-Code generiert."
@ -8925,6 +8895,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T2955
-- The generated assistant plugin uses the ID of an internal AI Studio plugin.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3162363526"] = "Das generierte Assistent-Plugin verwendet die ID eines internen AI-Studio-Plugins."
-- Config Server managed assistant plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3751820312"] = "Von einem Config-Server verwaltete Assistenten-Plugins können nicht gelöscht werden."
-- Only assistants generated by the Assistant Builder can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3940247198"] = "Nur mit dem Assistant Builder erstellte Assistenten können gelöscht werden."
-- The edited plugin is not an assistant plugin. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3984111892"] = "Das bearbeitete Plugin ist kein Assistenten-Plugin. Problem: {0}"
@ -8946,6 +8922,42 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T8163
-- The generated assistant plugin is invalid. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T939708112"] = "Das generierte Assistenten-Plugin ist ungültig. Problem: {0}"
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "Der konfigurierte Transkriptionsanbieter konnte nicht erstellt werden."
-- The selected media file no longer exists.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T129859547"] = "Die ausgewählte Mediendatei ist nicht mehr vorhanden."
-- The selected media file does not contain an audio track.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T134825479"] = "Die ausgewählte Mediendatei enthält keine Audiospur."
-- The media file could not be transcribed.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1543974632"] = "Die Mediendatei konnte nicht transkribiert werden."
-- The selected file cannot be processed as media.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1707342767"] = "Die ausgewählte Datei kann nicht als Medium verarbeitet werden."
-- The audio track contains no audible signal, so there is nothing to transcribe.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1988190152"] = "Die Audiospur enthält kein hörbares Signal. Daher gibt es nichts zu transkribieren."
-- The media file is damaged or its format could not be identified.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2004316549"] = "Die Mediendatei ist beschädigt oder ihr Format konnte nicht erkannt werden."
-- This media format or audio codec is not supported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2142564510"] = "Dieses Medienformat oder dieser Audiocodec wird nicht unterstützt."
-- No usable transcription provider is configured.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2282521655"] = "Es ist kein nutzbarer Transkriptionsanbieter konfiguriert."
-- The media file could not be prepared for transcription.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2749117459"] = "Die Mediendatei konnte nicht für die Transkription vorbereitet werden."
-- The transcription provider could not transcribe the media file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T3091669215"] = "Der Transkriptionsanbieter konnte die Mediendatei nicht transkribieren."
-- The media pipeline ended without an output file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T632852430"] = "Die Medienpipeline wurde beendet, ohne eine Ausgabedatei zu erzeugen."
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T185447014"] = "Pandoc-Installation"

View File

@ -2391,6 +2391,24 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T3571008422"] = "Assistan
-- The result is ready.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T661906146"] = "The result is ready."
-- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T1318944584"] = "The assistant cannot be deleted while background work is still running."
-- Delete assistant plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T1692493145"] = "Delete assistant plugin"
-- Delete Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3637071001"] = "Delete Assistant Plugin"
-- The '{0}' assistant plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3815023384"] = "The '{0}' assistant plugin has been successfully removed."
-- The assistant plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T3985264168"] = "The assistant plugin '{0}' could not be deleted: {1}"
-- Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINDELETEACTION::T4033722845"] = "Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files."
-- Show or hide the detailed security information.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTPLUGINSECURITYCARD::T1045105126"] = "Show or hide the detailed security information."
@ -7284,9 +7302,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1506922856"] = "Assistant Audit"
-- Internal Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Internal Plugins"
-- Delete assistant plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1692493145"] = "Delete assistant plugin"
-- Disabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins"
@ -7320,24 +7335,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3143506997"] = "The assistant plugin
-- Close
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3448155331"] = "Close"
-- Delete Assistant Plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3637071001"] = "Delete Assistant Plugin"
-- Revise assistant plugin with AI
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3801095542"] = "Revise assistant plugin with AI"
-- The '{0}' assistant plugin has been successfully removed.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3815023384"] = "The '{0}' assistant plugin has been successfully removed."
-- Actions
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3865031940"] = "Actions"
-- The assistant plugin '{0}' could not be deleted: {1}
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3985264168"] = "The assistant plugin '{0}' could not be deleted: {1}"
-- Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4033722845"] = "Do you really want to delete the assistant plugin '{0}'? This will permanently delete the local plugin files."
-- The automatic security audit for the assistant plugin '{0}' failed. Please run it manually.
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4066679817"] = "The automatic security audit for the assistant plugin '{0}' failed. Please run it manually."
@ -8748,42 +8751,6 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T378481461"] = "Source like p
-- Document
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPES::T4165204724"] = "Document"
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "The configured transcription provider could not be created."
-- The selected media file no longer exists.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T129859547"] = "The selected media file no longer exists."
-- The selected media file does not contain an audio track.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T134825479"] = "The selected media file does not contain an audio track."
-- The media file could not be transcribed.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1543974632"] = "The media file could not be transcribed."
-- The selected file cannot be processed as media.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1707342767"] = "The selected file cannot be processed as media."
-- The audio track contains no audible signal, so there is nothing to transcribe.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1988190152"] = "The audio track contains no audible signal, so there is nothing to transcribe."
-- The media file is damaged or its format could not be identified.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2004316549"] = "The media file is damaged or its format could not be identified."
-- This media format or audio codec is not supported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2142564510"] = "This media format or audio codec is not supported."
-- No usable transcription provider is configured.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2282521655"] = "No usable transcription provider is configured."
-- The media file could not be prepared for transcription.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2749117459"] = "The media file could not be prepared for transcription."
-- The transcription provider could not transcribe the media file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T3091669215"] = "The transcription provider could not transcribe the media file."
-- The media pipeline ended without an output file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T632852430"] = "The media pipeline ended without an output file."
-- The Assistant Builder context could not be loaded.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGINGENERATIONSERVICE::T108292972"] = "The Assistant Builder context could not be loaded."
@ -8895,6 +8862,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1211
-- Only assistant plugins can be edited.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1288328479"] = "Only assistant plugins can be edited."
-- The assistant cannot be deleted while background work is still running.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1318944584"] = "The assistant cannot be deleted while background work is still running."
-- No Lua plugin code was generated.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T1839013358"] = "No Lua plugin code was generated."
@ -8925,6 +8895,12 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T2955
-- The generated assistant plugin uses the ID of an internal AI Studio plugin.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3162363526"] = "The generated assistant plugin uses the ID of an internal AI Studio plugin."
-- Config Server managed assistant plugins cannot be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3751820312"] = "Config Server managed assistant plugins cannot be deleted."
-- Only assistants generated by the Assistant Builder can be deleted.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3940247198"] = "Only assistants generated by the Assistant Builder can be deleted."
-- The edited plugin is not an assistant plugin. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T3984111892"] = "The edited plugin is not an assistant plugin. Issue: {0}"
@ -8946,6 +8922,42 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T8163
-- The generated assistant plugin is invalid. Issue: {0}
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::ASSISTANTPLUGININSTALLSERVICE::T939708112"] = "The generated assistant plugin is invalid. Issue: {0}"
-- The configured transcription provider could not be created.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1235984176"] = "The configured transcription provider could not be created."
-- The selected media file no longer exists.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T129859547"] = "The selected media file no longer exists."
-- The selected media file does not contain an audio track.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T134825479"] = "The selected media file does not contain an audio track."
-- The media file could not be transcribed.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1543974632"] = "The media file could not be transcribed."
-- The selected file cannot be processed as media.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1707342767"] = "The selected file cannot be processed as media."
-- The audio track contains no audible signal, so there is nothing to transcribe.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T1988190152"] = "The audio track contains no audible signal, so there is nothing to transcribe."
-- The media file is damaged or its format could not be identified.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2004316549"] = "The media file is damaged or its format could not be identified."
-- This media format or audio codec is not supported.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2142564510"] = "This media format or audio codec is not supported."
-- No usable transcription provider is configured.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2282521655"] = "No usable transcription provider is configured."
-- The media file could not be prepared for transcription.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T2749117459"] = "The media file could not be prepared for transcription."
-- The transcription provider could not transcribe the media file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T3091669215"] = "The transcription provider could not transcribe the media file."
-- The media pipeline ended without an output file.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::MEDIATRANSCRIPTIONSERVICE::T632852430"] = "The media pipeline ended without an output file."
-- Pandoc Installation
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T185447014"] = "Pandoc Installation"

View File

@ -37,6 +37,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
public bool HasEmbeddedProfileSelection { get; private set; }
public bool HasCustomPromptBuilder => this.buildPromptFunction is not null;
public bool IsAssistantBuilderGenerated { get; private set; }
public bool HasDeploymentManagementMetadata { get; private set; }
public bool IsManagedByConfigServer { get; private set; }
public AssistantPluginLaunchBehavior LaunchBehavior { get; private set; }
public string LaunchWorkspaceName { get; private set; } = string.Empty;
@ -66,6 +67,7 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
message = string.Empty;
this.HasEmbeddedProfileSelection = false;
this.IsAssistantBuilderGenerated = false;
this.HasDeploymentManagementMetadata = false;
this.IsManagedByConfigServer = false;
this.buildPromptFunction = null;
this.LaunchBehavior = AssistantPluginLaunchBehavior.NONE;
@ -169,7 +171,10 @@ public sealed class PluginAssistants(bool isInternal, LuaState state, PluginType
private void TryReadDeploymentMetadata()
{
if (this.State.Environment["DEPLOYED_USING_CONFIG_SERVER"].TryRead<bool>(out var deployedUsingConfigServer))
{
this.HasDeploymentManagementMetadata = true;
this.IsManagedByConfigServer = deployedUsingConfigServer;
}
}
private bool TryReadLaunchConfiguration(LuaTable assistantTable, out string message)

View File

@ -115,7 +115,7 @@ public sealed class AssistantPluginGenerationService(ILogger<AssistantPluginGene
if (!generatedAssistant.IsAssistantBuilderGenerated)
return InitialFailure(TB("The generated assistant plugin must include the Assistant Builder metadata."));
if (generatedAssistant.IsManagedByConfigServer)
if (!generatedAssistant.HasDeploymentManagementMetadata || generatedAssistant.IsManagedByConfigServer)
return InitialFailure(TB("The generated assistant plugin must be marked as locally managed."));
return new(true, fullLua, parsedResponse.Plugin?.Name ?? string.Empty, string.Empty);
@ -168,7 +168,8 @@ public sealed class AssistantPluginGenerationService(ILogger<AssistantPluginGene
if (plugin.IsAssistantBuilderGenerated && !revisedAssistant.IsAssistantBuilderGenerated)
return RevisionFailure(TB("The revised assistant plugin must keep the Assistant Builder metadata."));
if (revisedAssistant.IsManagedByConfigServer)
if (revisedAssistant.IsManagedByConfigServer ||
plugin.IsAssistantBuilderGenerated && !revisedAssistant.HasDeploymentManagementMetadata)
return RevisionFailure(TB("The revised assistant plugin must remain locally managed."));
return new(true, revisedLua, parsedResponse.Plugin?.Name ?? plugin.Name, string.Empty);
@ -364,7 +365,7 @@ public sealed class AssistantPluginGenerationService(ILogger<AssistantPluginGene
{
var companionLua = FormatCompanionLuaFiles(plugin);
var builderMetadataRule = plugin.IsAssistantBuilderGenerated
? "- Keep AI_STUDIO_ASSISTANT_BUILDER = {Generated = true, SchemaVersion = 1}."
? "- Keep AI_STUDIO_ASSISTANT_BUILDER = {Generated = true, SchemaVersion = 1} and set DEPLOYED_USING_CONFIG_SERVER = false explicitly."
: string.Empty;
return $$"""
Revise an existing locally managed AI Studio Lua assistant plugin.

View File

@ -1,5 +1,7 @@
using System.Text;
using AIStudio.Settings;
using AIStudio.Tools.AssistantSessions;
using AIStudio.Tools.Media;
using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.PluginSystem.Assistants;
@ -24,6 +26,8 @@ public sealed class AssistantPluginInstallService
private readonly ILogger<AssistantPluginInstallService> logger;
private readonly SettingsManager settingsManager;
private readonly AssistantSessionService assistantSessionService;
private readonly MediaTranscriptionService mediaTranscriptionService;
private readonly SemaphoreSlim installSemaphore = new(1, 1);
private static AssistantPluginInstallResult Error(string issue) => new(false, Guid.Empty, string.Empty, string.Empty, false, issue);
@ -34,13 +38,39 @@ public sealed class AssistantPluginInstallService
private static AssistantPluginUpdateResult UpdateError(IPluginMetadata plugin, string pluginDirectory, string issue) => new(false, plugin.Id, plugin.Name, pluginDirectory, issue);
public AssistantPluginInstallService(ILogger<AssistantPluginInstallService> logger, SettingsManager settingsManager)
public AssistantPluginInstallService(
ILogger<AssistantPluginInstallService> logger,
SettingsManager settingsManager,
AssistantSessionService assistantSessionService,
MediaTranscriptionService mediaTranscriptionService)
{
this.logger = logger;
this.settingsManager = settingsManager;
this.assistantSessionService = assistantSessionService;
this.mediaTranscriptionService = mediaTranscriptionService;
this.logger.LogInformation("The assistant plugin install service has been initialized.");
}
/// <summary>
/// Checks whether a local plugin is an Assistant Builder generated assistant that users may delete.
/// </summary>
public static bool CanDeleteInstalledAssistant(IAvailablePlugin plugin) => string.IsNullOrWhiteSpace(GetAssistantDeletionEligibilityIssue(plugin));
/// <summary>
/// Checks whether an assistant still owns running or canceling background work.
/// </summary>
public bool HasActiveAssistantWork(Guid pluginId)
{
var instanceId = pluginId.ToString();
if (this.assistantSessionService.GetSnapshots().Any(snapshot => snapshot.IsActive && string.Equals(snapshot.Key.InstanceId, instanceId, StringComparison.Ordinal)))
return true;
var ownerIdSuffix = $":{instanceId}";
return this.mediaTranscriptionService.GetSnapshots().Any(snapshot =>
snapshot is { IsBusy: true, Owner.Kind: MediaImportOwnerKind.ASSISTANT } &&
snapshot.Owner.Id.EndsWith(ownerIdSuffix, StringComparison.Ordinal));
}
/// <summary>
/// Checks whether generated Lua assistant plugin code can be loaded and installed.
/// The plugin is written to a temporary staging directory and validated through the
@ -232,32 +262,28 @@ public sealed class AssistantPluginInstallService
/// </returns>
public async Task<AssistantPluginDeleteResult> DeleteInstalledAssistantAsync(IAvailablePlugin plugin, CancellationToken token)
{
if (plugin.Type is not PluginType.ASSISTANT)
return DeleteError(plugin, plugin.LocalPath, TB("Only assistant plugins can be deleted."));
var eligibilityIssue = GetAssistantDeletionEligibilityIssue(plugin);
if (!string.IsNullOrEmpty(eligibilityIssue))
return DeleteError(plugin, plugin.LocalPath, eligibilityIssue);
if (plugin.IsInternal)
return DeleteError(plugin, plugin.LocalPath, TB("Internal assistant plugins cannot be deleted."));
if (string.IsNullOrWhiteSpace(plugin.LocalPath))
return DeleteError(plugin, string.Empty, TB("The assistant plugin has no local directory."));
if (!TryGetAssistantPluginsRoot(out var assistantPluginsRoot, out var rootIssue))
return DeleteError(plugin, plugin.LocalPath, rootIssue);
var pluginDirectory = plugin.LocalPath;
if (!IsPathInsideDirectory(assistantPluginsRoot, pluginDirectory) || IsSameDirectory(assistantPluginsRoot, pluginDirectory))
return DeleteError(plugin, pluginDirectory, TB("The assistant plugin directory is outside the local assistant plugin directory."));
if (!Directory.Exists(pluginDirectory))
return DeleteError(plugin, pluginDirectory, TB("The assistant plugin directory does not exist."));
if (this.HasActiveAssistantWork(plugin.Id))
return DeleteError(plugin, plugin.LocalPath, TB("The assistant cannot be deleted while background work is still running."));
await this.installSemaphore.WaitAsync(token);
var pluginDirectory = plugin.LocalPath;
var backupDirectory = string.Empty;
var wasEnabled = false;
var removedAudits = new List<PluginAssistantAudit>();
try
{
eligibilityIssue = GetAssistantDeletionEligibilityIssue(plugin);
if (!string.IsNullOrEmpty(eligibilityIssue))
return DeleteError(plugin, pluginDirectory, eligibilityIssue);
if (this.HasActiveAssistantWork(plugin.Id))
return DeleteError(plugin, pluginDirectory, TB("The assistant cannot be deleted while background work is still running."));
backupDirectory = CreateDeleteBackupDirectory(plugin);
Directory.CreateDirectory(Path.GetDirectoryName(backupDirectory)!);
Directory.Move(pluginDirectory, backupDirectory);
@ -486,6 +512,41 @@ public sealed class AssistantPluginInstallService
return true;
}
private static string GetAssistantDeletionEligibilityIssue(IAvailablePlugin plugin)
{
if (plugin.Type is not PluginType.ASSISTANT)
return TB("Only assistant plugins can be deleted.");
if (plugin.IsInternal)
return TB("Internal assistant plugins cannot be deleted.");
if (plugin.IsManagedByConfigServer)
return TB("Config Server managed assistant plugins cannot be deleted.");
if (string.IsNullOrWhiteSpace(plugin.LocalPath))
return TB("The assistant plugin has no local directory.");
var assistantPlugin = PluginFactory.RunningPlugins
.OfType<PluginAssistants>()
.FirstOrDefault(candidate => candidate.Id == plugin.Id && IsSameDirectory(candidate.PluginPath, plugin.LocalPath));
if (assistantPlugin is null || assistantPlugin.IsInternal || !assistantPlugin.IsAssistantBuilderGenerated)
return TB("Only assistants generated by the Assistant Builder can be deleted.");
if (assistantPlugin.IsManagedByConfigServer)
return TB("Config Server managed assistant plugins cannot be deleted.");
if (!TryGetAssistantPluginsRoot(out var assistantPluginsRoot, out var rootIssue))
return rootIssue;
if (!IsPathInsideDirectory(assistantPluginsRoot, plugin.LocalPath) || IsSameDirectory(assistantPluginsRoot, plugin.LocalPath))
return TB("The assistant plugin directory is outside the local assistant plugin directory.");
return Directory.Exists(plugin.LocalPath)
? string.Empty
: TB("The assistant plugin directory does not exist.");
}
private void TryDeleteStagingDirectory(string stagingDirectory)
{
TryDeleteDirectory(stagingDirectory, "assistant plugin staging", this.logger);
@ -645,4 +706,4 @@ public sealed class AssistantPluginInstallService
{
public static AssistantPluginValidationResult Failure(string issue) => new(false, string.Empty, null, issue);
}
}
}