Added opt-in sharing of feature usage with self-hosted servers (#1020)
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 }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (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

Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
This commit is contained in:
Dominic NeuburgandThorsten Sommer authored and GitHub committed 2026-10-04 14:44:16 +02:00
1 parent 90c90910b3
commit 50aa11a34c
27 files changed
+531 -12

No files matched your search

@@ -37,6 +37,11 @@ public abstract class BaseProvider : IProvider, ISecretId
/// </summary>
private readonly ILogger logger;
/// <summary>
/// Whether the requests of this provider carry the User-Agent of AI Studio, see AppUserAgent.
/// </summary>
private readonly bool sharesFeatureUsage;
protected static readonly JsonSerializerOptions JSON_SERIALIZER_OPTIONS = ProviderJsonOptions.OPTIONS;
/// <summary>
@@ -52,6 +57,17 @@ public abstract class BaseProvider : IProvider, ISecretId
this.Provider = provider;
this.BaseUri = baseUri;
this.HttpClient = ExternalHttpClientTimeout.CreateHttpClient(baseUri, trustPolicy);
//
// Decided once per provider, so that all requests of one provider agree. As the default header
// of the client, the User-Agent reaches every request: transcriptions, embeddings, and model
// lists name the app this way, while chat requests additionally name their component, see
// StreamOpenAICompatibleChatCompletion. The settings are read the same way the client above
// reads its timeout:
//
this.sharesFeatureUsage = AppUserAgent.IsAllowedFor(provider, Program.SERVICE_PROVIDER.GetRequiredService<SettingsManager>());
if (this.sharesFeatureUsage)
AppUserAgent.Apply(this.HttpClient);
}
#region Handling of IProvider, which all providers must implement
@@ -1304,6 +1320,18 @@ public abstract class BaseProvider : IProvider, ISecretId
// Parse the API parameters:
var apiParameters = this.ParseAdditionalApiParameters("parallel_tool_calls");
//
// Where the User-Agent is shared, a chat request also names the component which sent it. This
// covers both ways to the provider below: the single request, and every round of a tool run.
//
Action<HttpRequestHeaders>? requestHeadersAction = this.sharesFeatureUsage
? headers =>
{
headersAction?.Invoke(headers);
AppUserAgent.ApplyComponent(headers, chatThread);
}
: headersAction;
var toolRegistry = Program.SERVICE_PROVIDER.GetService<ToolRegistry>();
var toolExecutor = Program.SERVICE_PROVIDER.GetService<ToolExecutor>();
var currentAssistantContent = chatThread.Blocks.LastOrDefault(x => x.Role is ChatRole.AI)?.Content as ContentText;
@@ -1335,7 +1363,7 @@ public abstract class BaseProvider : IProvider, ISecretId
{
var adapter = new ChatCompletionToolCallingAdapter<TRequest>(requestFactory, systemPrompt, apiParameters,
runnableTools.Select(x => ProviderToolAdapters.ToChatCompletionTool(x.Definition, enforcesStrictToolSchemas)).ToList(), mayAskForSequentialToolCalls, runnableTools,
(requestDto, requestToken) => this.StreamChatCompletionRequest(requestDto, providerName, requestPath, requestedSecret, headersAction, requestToken),
(requestDto, requestToken) => this.StreamChatCompletionRequest(requestDto, providerName, requestPath, requestedSecret, requestHeadersAction, requestToken),
ChatCompletionSourceReader.Read<TDelta, TAnnotation>,
this.logger);
@@ -1381,7 +1409,7 @@ public abstract class BaseProvider : IProvider, ISecretId
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(Program.ENCRYPTION));
// Set provider-specific headers:
headersAction?.Invoke(request.Headers);
requestHeadersAction?.Invoke(request.Headers);
// Set the content:
request.Content = new StringContent(providerChatRequest, Encoding.UTF8, "application/json");