Web search restrictions after internal content (#1074)

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Peer HogeterpandThorsten Sommer authored and GitHub committed 2026-10-18 19:39:55 +02:00
1 parent f38c4aa563
commit 1f8d72aa45
42 files changed
+1654 -228

No files matched your search

@@ -0,0 +1,29 @@
namespace AIStudio.Settings.DataModel;
/// <summary>
/// What kind of source brought the internal content into a chat which restricts where it may
/// still send data.
/// </summary>
public enum OutboundDataOrigin
{
/// <summary>
/// No source: the chat holds no internal content, so nothing restricts it.
/// </summary>
NONE = 0,
/// <summary>
/// A configured mailbox.
/// </summary>
MAILBOX,
/// <summary>
/// A search of the configured Confluence wiki.
/// </summary>
CONFLUENCE_WIKI,
/// <summary>
/// An internal web page: one read from a private or otherwise non-public address, such as one
/// in the intranet or behind a VPN, or a page of the configured wiki, whatever its address.
/// </summary>
INTERNAL_WEB_PAGE,
}
@@ -1,29 +1,31 @@
namespace AIStudio.Settings.DataModel;
/// <summary>
/// Where a chat may still send data, and which data source demands it.
/// Where a chat may still send data, and which source of internal content demands it.
/// </summary>
/// <remarks>
/// The data source travels along so a tool which is kept from running can name it, and the user
/// learns which mailbox stands in the way. It is its id rather than its name, because a mailbox
/// may be renamed after a chat read from it.
/// The source travels along so a tool which is kept from running can name it, and the user
/// learns what stands in the way. For a mailbox, the reference is its id rather than its name,
/// because a mailbox may be renamed after a chat read from it.
/// </remarks>
/// <param name="Restriction">Where the chat may still send data.</param>
/// <param name="DataSourceId">The id of the data source which demands the restriction, or an empty text when nothing restricts the chat.</param>
public sealed record OutboundDataRequirement(OutboundDataRestriction Restriction, string DataSourceId)
/// <param name="Origin">What kind of source demands the restriction, or NONE when nothing restricts the chat.</param>
/// <param name="Reference">Which source of its kind demands the restriction: the id of a mailbox, or the host of a private web page. An empty text when the kind says it all, as for the wiki, or when nothing restricts the chat.</param>
public sealed record OutboundDataRequirement(OutboundDataRestriction Restriction, OutboundDataOrigin Origin, string Reference)
{
/// <summary>
/// Demands nothing: what a chat holds before it read from any mailbox, and what a result
/// demands which brought no content of one in.
/// Demands nothing: what a chat holds before it read any internal content, and what a result
/// demands which brought none in.
/// </summary>
public static readonly OutboundDataRequirement NONE = new(OutboundDataRestriction.UNRESTRICTED, string.Empty);
public static readonly OutboundDataRequirement NONE = new(OutboundDataRestriction.UNRESTRICTED, OutboundDataOrigin.NONE, string.Empty);
/// <summary>
/// The stricter of this requirement and another one.
/// </summary>
/// <remarks>
/// On a tie, this one is kept, so the data source which set a level first stays the one which
/// is named, and a chat does not name another mailbox with every search.
/// On a tie, this one is kept, whatever the origin of either, so the source which set a level
/// first stays the one which is named, and a chat does not name another source with every
/// search.
/// </remarks>
/// <param name="other">The other requirement.</param>
/// <returns>The requirement whose restriction is the stricter one.</returns>
@@ -11,17 +11,28 @@ public static class OutboundDataRequirementExtensions
/// </summary>
/// <remarks>
/// One text for the tool selection and for a call which was turned down, so the user reads the
/// same in both places, and the model can pass it on. It names the setting as the mailbox
/// dialog labels it, so the user finds it there. The level is the one the setting had when the
/// chat read the mails: a change made later does not loosen the chat.
/// same in both places, and the model can pass it on. It names the source which demands the
/// requirement, and the setting as the user finds it labeled. The level is the one the setting
/// had when the chat read the content: a change made later does not loosen the chat.
/// </remarks>
/// <param name="requirement">What the chat demands.</param>
/// <param name="mailboxes">The configured mailboxes, to find the name of the one which demands it.</param>
/// <param name="settings">The configuration, to find the name of the mailbox which demands it.</param>
/// <returns>The text for the user.</returns>
public static string GetToolBlockedMessage(this OutboundDataRequirement requirement, IEnumerable<DataSourceMailbox> mailboxes)
public static string GetToolBlockedMessage(this OutboundDataRequirement requirement, Data settings) => requirement.Origin switch
{
OutboundDataOrigin.MAILBOX => GetMailboxBlockedMessage(requirement, settings.Mailboxes),
OutboundDataOrigin.CONFLUENCE_WIKI => string.Format(TB("This chat searched your Confluence wiki while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."), requirement.Restriction.GetName()),
OutboundDataOrigin.INTERNAL_WEB_PAGE => string.Format(TB("This chat read an internal web page on '{0}' while the setting 'Where a chat may send data after reading internal pages' of the tool 'Read Web Page' was '{1}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."), requirement.Reference, requirement.Restriction.GetName()),
// NONE keeps no tool from running, since nothing restricts such a chat. What is left is an
// origin this version does not know:
_ => string.Format(TB("This chat read internal content while the chat was restricted to '{0}'. This tool would send data beyond that, so it is not available in this chat. A new chat can use it again."), requirement.Restriction.GetName()),
};
private static string GetMailboxBlockedMessage(OutboundDataRequirement requirement, IEnumerable<DataSourceMailbox> mailboxes)
{
var mailboxName = mailboxes
.Where(mailbox => string.Equals(mailbox.Id, requirement.DataSourceId, StringComparison.Ordinal))
.Where(mailbox => string.Equals(mailbox.Id, requirement.Reference, StringComparison.Ordinal))
.Select(mailbox => mailbox.Name)
.FirstOrDefault();
@@ -1,12 +1,13 @@
namespace AIStudio.Settings.DataModel;
/// <summary>
/// Where a chat may still send data, once it has read from a mailbox.
/// Where a chat may still send data, once internal content entered it: mails, a search of the
/// wiki, or an internal web page.
/// </summary>
/// <remarks>
/// The members run from the strictest to the most permissive, so the stricter of two levels is the
/// smaller one. Services which are configured in AI Studio, such as Confluence or the mailbox itself,
/// stay allowed on every level.
/// smaller one. Services which are configured in AI Studio, such as Confluence, the allowed private
/// hosts, or the mailbox itself, stay allowed on every level.
/// </remarks>
public enum OutboundDataRestriction
{
@@ -24,13 +24,13 @@ public static class OutboundDataRestrictionExtensions
};
/// <summary>
/// What a chat may still do on this level, once it has read from the mailbox.
/// What a chat may still do on this level, once internal content entered it.
/// </summary>
public static string GetDescription(this OutboundDataRestriction restriction) => restriction switch
{
OutboundDataRestriction.ONLY_CONFIGURED_SERVICES => TB("The chat may only use services configured in AI Studio, such as this mailbox or your Confluence. It reads no web pages, and it does not search the web."),
OutboundDataRestriction.ONLY_LINKS_FROM_CHAT => TB("The chat may also read web pages whose addresses stand in the chat, written by you or returned by a tool, exactly as they stand there. It does not search the web, and the AI cannot choose addresses of its own."),
OutboundDataRestriction.UNRESTRICTED => TB("The chat may use every tool you chose, web search and any web page included. Content of your mails can then reach third parties, e.g., inside a search query or the address of a web page."),
OutboundDataRestriction.ONLY_CONFIGURED_SERVICES => TB("The chat may only use services configured in AI Studio: your mailboxes, your Confluence wiki, and the allowed private hosts of the tool 'Read Web Page'. It reads no other web pages, and it does not search the web."),
OutboundDataRestriction.ONLY_LINKS_FROM_CHAT => TB("The chat may also read web pages whose addresses stand in the chat, written by you or returned by a tool, exactly as they stand there. It does not search the web, and the AI cannot choose addresses of its own outside the services configured in AI Studio."),
OutboundDataRestriction.UNRESTRICTED => TB("The chat may use every tool you chose, web search and any web page included. Internal content, such as your mails, can then reach third parties, e.g., inside a search query or the address of a web page."),
_ => TB("This version of AI Studio does not know this restriction, so it applies the strictest one."),
};