mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-05 12:29:40 +00:00
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
105 lines
4.5 KiB
C#
105 lines
4.5 KiB
C#
using System.Net.Http.Headers;
|
|
|
|
using AIStudio.Chat;
|
|
using AIStudio.Provider;
|
|
using AIStudio.Settings;
|
|
using AIStudio.Tools;
|
|
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
|
|
namespace AIStudio.Tests.Tools;
|
|
|
|
/// <summary>
|
|
/// Checks the User-Agent which AI Studio sends to self-hosted servers.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Operators of a gateway such as LiteLLM read it in their logs to see which assistant or agent sent a
|
|
/// request. It has to stay a valid header value whatever a plugin calls its assistant, because an
|
|
/// invalid one would either break the request or silently vanish from the logs. Who receives it at all
|
|
/// is a privacy question: nobody without the consent of the user, and a cloud provider never.
|
|
/// </remarks>
|
|
[TestFixture]
|
|
public sealed class AppUserAgentTests
|
|
{
|
|
[TestCase(LLMProviders.SELF_HOSTED)]
|
|
[TestCase(LLMProviders.LITE_LLM)]
|
|
public void ASelfHostedServerReceivesItOnlyWhenSharingIsOn(LLMProviders provider)
|
|
{
|
|
var settingsManager = CreateSettingsManager();
|
|
Assert.That(AppUserAgent.IsAllowedFor(provider, settingsManager), Is.False, "Sharing is off by default, so not even a self-hosted server may learn anything.");
|
|
|
|
settingsManager.ConfigurationData.App.ShareFeatureUsageWithSelfHostedServerOperators = true;
|
|
Assert.That(AppUserAgent.IsAllowedFor(provider, settingsManager), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void NoOtherProviderEverReceivesIt()
|
|
{
|
|
var settingsManager = CreateSettingsManager();
|
|
settingsManager.ConfigurationData.App.ShareFeatureUsageWithSelfHostedServerOperators = true;
|
|
|
|
//
|
|
// Listed by exclusion on purpose: a provider added later falls on the safe side, and whoever
|
|
// wants it to receive the User-Agent has to change this test and say why.
|
|
//
|
|
foreach (var provider in Enum.GetValues<LLMProviders>().Except([LLMProviders.SELF_HOSTED, LLMProviders.LITE_LLM]))
|
|
Assert.That(AppUserAgent.IsAllowedFor(provider, settingsManager), Is.False, $"The provider {provider} must never learn which feature sent a request.");
|
|
}
|
|
|
|
[Test]
|
|
public void TheBaseNamesTheApp()
|
|
{
|
|
Assert.That(AppUserAgent.BASE, Does.StartWith("MindWorkAIStudio/"));
|
|
Assert.That(ProductInfoHeaderValue.TryParse(AppUserAgent.BASE.Split(' ')[0], out _), Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void ABuiltInComponentIsNamedByItsEnumValue()
|
|
{
|
|
var userAgent = AppUserAgent.ForComponent(AIStudio.Tools.Components.TRANSLATION_ASSISTANT);
|
|
Assert.That(userAgent, Is.EqualTo($"{AppUserAgent.BASE} Component/TRANSLATION_ASSISTANT"));
|
|
}
|
|
|
|
[Test]
|
|
public void NoComponentKeepsTheBase()
|
|
{
|
|
Assert.That(AppUserAgent.ForComponent(AIStudio.Tools.Components.NONE), Is.EqualTo(AppUserAgent.BASE));
|
|
}
|
|
|
|
[TestCase("Übersetzung für Straßen", "Ubersetzung-fur-Strassen")]
|
|
[TestCase(" My (cool) Assistant/v2 ", "My-cool-Assistant-v2")]
|
|
[TestCase("already_valid-name.1", "already_valid-name.1")]
|
|
[TestCase("日本語", "")]
|
|
public void APluginNameBecomesAValidProductToken(string pluginName, string expectedToken)
|
|
{
|
|
var userAgent = AppUserAgent.ForComponent(AIStudio.Tools.Components.DYNAMIC_ASSISTANT, pluginName);
|
|
var expected = string.IsNullOrEmpty(expectedToken)
|
|
? $"{AppUserAgent.BASE} Component/DYNAMIC_ASSISTANT"
|
|
: $"{AppUserAgent.BASE} Component/DYNAMIC_ASSISTANT Assistant/{expectedToken}";
|
|
|
|
Assert.That(userAgent, Is.EqualTo(expected));
|
|
}
|
|
|
|
[Test]
|
|
public void ARequestHeaderReplacesTheDefault()
|
|
{
|
|
using var request = new HttpRequestMessage();
|
|
request.Headers.UserAgent.ParseAdd(AppUserAgent.BASE);
|
|
|
|
AppUserAgent.ApplyComponent(request.Headers, new ChatThread { RuntimeComponent = AIStudio.Tools.Components.EMAIL_ASSISTANT });
|
|
|
|
Assert.That(request.Headers.UserAgent.ToString(), Is.EqualTo($"{AppUserAgent.BASE} Component/EMAIL_ASSISTANT"));
|
|
Assert.That(request.Headers.UserAgent.Count, Is.EqualTo(3), "The header must still parse as product, platform comment, and component.");
|
|
}
|
|
|
|
[Test]
|
|
public void AThreadWithoutAComponentDoesNotCountAsChat()
|
|
{
|
|
using var request = new HttpRequestMessage();
|
|
AppUserAgent.ApplyComponent(request.Headers, new ChatThread());
|
|
|
|
Assert.That(request.Headers.UserAgent.ToString(), Is.EqualTo(AppUserAgent.BASE), "A thread whose sender forgot to name its component must not be counted as chat, nor as anything else.");
|
|
}
|
|
|
|
private static SettingsManager CreateSettingsManager() => new(NullLogger<SettingsManager>.Instance, null!);
|
|
} |