diff --git a/.changeset/package-library-metadata.md b/.changeset/package-library-metadata.md
new file mode 100644
index 00000000..651b57bc
--- /dev/null
+++ b/.changeset/package-library-metadata.md
@@ -0,0 +1,7 @@
+---
+"PostHog": patch
+"PostHog.AspNetCore": patch
+"PostHog.AI": patch
+---
+
+Report package-specific library names and versions for ASP.NET Core and AI events.
diff --git a/src/PostHog.AI/Generated/VersionConstants.cs b/src/PostHog.AI/Generated/VersionConstants.cs
new file mode 100644
index 00000000..aba5e73c
--- /dev/null
+++ b/src/PostHog.AI/Generated/VersionConstants.cs
@@ -0,0 +1,11 @@
+/*
+ GENERATED FILE! Do not directly modify this file.
+ This is generated by an MSBuild task in PostHog.AI.csproj
+*/
+
+namespace PostHog.AI.Versioning;
+
+internal static class VersionConstants
+{
+ public const string Version = "0.1.4";
+}
diff --git a/src/PostHog.AI/PostHog.AI.csproj b/src/PostHog.AI/PostHog.AI.csproj
index c16ba848..8e35ad44 100644
--- a/src/PostHog.AI/PostHog.AI.csproj
+++ b/src/PostHog.AI/PostHog.AI.csproj
@@ -21,4 +21,10 @@
+
+
+
diff --git a/src/PostHog.AI/PostHogAIFieldNames.cs b/src/PostHog.AI/PostHogAIFieldNames.cs
index a26e64c7..4c2f96f1 100644
--- a/src/PostHog.AI/PostHogAIFieldNames.cs
+++ b/src/PostHog.AI/PostHogAIFieldNames.cs
@@ -24,10 +24,15 @@ public static class PostHogAIFieldNames
public const string Provider = "$ai_provider";
///
- /// Library identifier (e.g., "posthog-dotnet").
+ /// AI library identifier (e.g., "posthog-ai").
///
public const string Lib = "$ai_lib";
+ ///
+ /// AI library version.
+ ///
+ public const string LibVersion = "$ai_lib_version";
+
///
/// Request latency in seconds.
///
diff --git a/src/PostHog.AI/PostHogOpenAIHandler.cs b/src/PostHog.AI/PostHogOpenAIHandler.cs
index 3716ccc8..f1bfec7b 100644
--- a/src/PostHog.AI/PostHogOpenAIHandler.cs
+++ b/src/PostHog.AI/PostHogOpenAIHandler.cs
@@ -239,7 +239,8 @@ private void CaptureEvent(
// Basic Info
eventProperties[PostHogAIFieldNames.Provider] = "openai";
- eventProperties[PostHogAIFieldNames.Lib] = "posthog-dotnet";
+ eventProperties[PostHogAIFieldNames.Lib] = "posthog-ai";
+ eventProperties[PostHogAIFieldNames.LibVersion] = Versioning.VersionConstants.Version;
eventProperties[PostHogAIFieldNames.Latency] = latency;
if (request.RequestUri != null)
diff --git a/src/PostHog.AI/PublicAPI.Unshipped.txt b/src/PostHog.AI/PublicAPI.Unshipped.txt
index 7dc5c581..255a43e4 100644
--- a/src/PostHog.AI/PublicAPI.Unshipped.txt
+++ b/src/PostHog.AI/PublicAPI.Unshipped.txt
@@ -1 +1,2 @@
#nullable enable
+const PostHog.AI.PostHogAIFieldNames.LibVersion = "$ai_lib_version" -> string!
diff --git a/src/PostHog.AspNetCore/Config/PostHogConfigurationBuilderExtensions.cs b/src/PostHog.AspNetCore/Config/PostHogConfigurationBuilderExtensions.cs
index dd4bf635..364b4375 100644
--- a/src/PostHog.AspNetCore/Config/PostHogConfigurationBuilderExtensions.cs
+++ b/src/PostHog.AspNetCore/Config/PostHogConfigurationBuilderExtensions.cs
@@ -25,6 +25,11 @@ public static IPostHogConfigurationBuilder UseAspNetCore(this IPostHogConfigurat
{
services.AddSingleton();
services.AddHttpContextAccessor();
+ services.PostConfigure(options =>
+ {
+ options.LibraryName = "posthog-aspnetcore";
+ options.LibraryVersion = global::PostHog.AspNetCore.Versioning.VersionConstants.Version;
+ });
});
return builder;
}
diff --git a/src/PostHog.AspNetCore/Generated/VersionConstants.cs b/src/PostHog.AspNetCore/Generated/VersionConstants.cs
new file mode 100644
index 00000000..b8f353ca
--- /dev/null
+++ b/src/PostHog.AspNetCore/Generated/VersionConstants.cs
@@ -0,0 +1,11 @@
+/*
+ GENERATED FILE! Do not directly modify this file.
+ This is generated by an MSBuild task in PostHog.AspNetCore.csproj
+*/
+
+namespace PostHog.AspNetCore.Versioning;
+
+internal static class VersionConstants
+{
+ public const string Version = "2.9.5";
+}
diff --git a/src/PostHog.AspNetCore/PostHog.AspNetCore.csproj b/src/PostHog.AspNetCore/PostHog.AspNetCore.csproj
index a0c87f7f..261d7128 100644
--- a/src/PostHog.AspNetCore/PostHog.AspNetCore.csproj
+++ b/src/PostHog.AspNetCore/PostHog.AspNetCore.csproj
@@ -22,4 +22,11 @@
+
+
+
+
diff --git a/src/PostHog/Api/CapturedEvent.cs b/src/PostHog/Api/CapturedEvent.cs
index a160cf7f..e2beef97 100644
--- a/src/PostHog/Api/CapturedEvent.cs
+++ b/src/PostHog/Api/CapturedEvent.cs
@@ -21,6 +21,23 @@ public CapturedEvent(
string distinctId,
Dictionary? properties,
DateTimeOffset timestamp)
+ : this(
+ eventName,
+ distinctId,
+ properties,
+ timestamp,
+ PostHogApiClient.LibraryName,
+ VersionConstants.Version)
+ {
+ }
+
+ internal CapturedEvent(
+ string eventName,
+ string distinctId,
+ Dictionary? properties,
+ DateTimeOffset timestamp,
+ string libraryName,
+ string libraryVersion)
{
Uuid = Guid.NewGuid().ToString();
EventName = eventName;
@@ -31,8 +48,8 @@ public CapturedEvent(
// Every event has to have these properties.
Properties[PostHogProperties.DistinctId] = distinctId; // See `get_distinct_id` in PostHog/posthog api/capture.py line 321
- Properties[PostHogProperties.Lib] = PostHogApiClient.LibraryName;
- Properties[PostHogProperties.LibVersion] = VersionConstants.Version;
+ Properties[PostHogProperties.Lib] = libraryName;
+ Properties[PostHogProperties.LibVersion] = libraryVersion;
Properties[PostHogProperties.GeoIpDisable] = Properties.GetValueOrDefault(PostHogProperties.GeoIpDisable, true);
}
diff --git a/src/PostHog/Api/PostHogApiClient.cs b/src/PostHog/Api/PostHogApiClient.cs
index ff5777b0..bf14b396 100644
--- a/src/PostHog/Api/PostHogApiClient.cs
+++ b/src/PostHog/Api/PostHogApiClient.cs
@@ -6,7 +6,6 @@
using Microsoft.Extensions.Options;
using PostHog.Json;
using PostHog.Library;
-using PostHog.Versioning;
#if NETSTANDARD2_0 || NETSTANDARD2_1
#endif
@@ -46,7 +45,8 @@ public PostHogApiClient(
var framework = RuntimeInformation.FrameworkDescription;
var os = RuntimeInformation.OSDescription;
var arch = RuntimeInformation.ProcessArchitecture;
- _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd($"{LibraryName}/{VersionConstants.Version} ({framework}; {os}; {arch})");
+ _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(
+ $"{_options.Value.LibraryName}/{_options.Value.LibraryVersion} ({framework}; {os}; {arch})");
logger.LogTraceApiClientCreated(HostUrl);
_logger = logger;
@@ -286,8 +286,8 @@ void PrepareAndMutatePayload(Dictionary payload)
var properties = payload.GetOrAdd>("properties");
- properties[PostHogProperties.Lib] = LibraryName;
- properties[PostHogProperties.LibVersion] = VersionConstants.Version;
+ properties[PostHogProperties.Lib] = _options.Value.LibraryName;
+ properties[PostHogProperties.LibVersion] = _options.Value.LibraryVersion;
properties[PostHogProperties.Os] = RuntimeInformation.OSDescription;
properties[PostHogProperties.Framework] = RuntimeInformation.FrameworkDescription;
properties[PostHogProperties.Architecture] = RuntimeInformation.ProcessArchitecture.ToString();
diff --git a/src/PostHog/Config/PostHogOptions.cs b/src/PostHog/Config/PostHogOptions.cs
index 70b0cc1f..6dee122c 100644
--- a/src/PostHog/Config/PostHogOptions.cs
+++ b/src/PostHog/Config/PostHogOptions.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Options;
using PostHog.Api;
using PostHog.Library;
+using PostHog.Versioning;
namespace PostHog;
@@ -14,6 +15,10 @@ public sealed class PostHogOptions : IOptions
string? _secretKey;
string? _personalApiKey;
+ internal string LibraryName { get; set; } = PostHogApiClient.LibraryName;
+
+ internal string LibraryVersion { get; set; } = VersionConstants.Version;
+
///
/// The PostHog project token that identifies which project this client works with.
///
diff --git a/src/PostHog/PostHogClient.cs b/src/PostHog/PostHogClient.cs
index 70a87f15..5d059a5b 100644
--- a/src/PostHog/PostHogClient.cs
+++ b/src/PostHog/PostHogClient.cs
@@ -333,7 +333,9 @@ bool CaptureCore(
eventName,
captureContext.DistinctId,
captureContext.Properties,
- timestamp: timestamp ?? _timeProvider.GetUtcNow());
+ timestamp: timestamp ?? _timeProvider.GetUtcNow(),
+ _options.Value.LibraryName,
+ _options.Value.LibraryVersion);
if (groups is { Count: > 0 })
{
@@ -407,7 +409,7 @@ static bool ShouldSendMinimalFeatureFlagCalledEvent(bool gate, bool? hasExperime
// set, so anything merged upstream (super properties, context properties) cannot leak in. The
// CapturedEvent constructor re-adds distinct_id and defaults $geoip_disable to true when it
// was not explicitly set.
- static CapturedEvent ToMinimalFeatureFlagCalledEvent(CapturedEvent capturedEvent)
+ CapturedEvent ToMinimalFeatureFlagCalledEvent(CapturedEvent capturedEvent)
{
var properties = new Dictionary(MinimalFeatureFlagCalledEventProperties.Length);
foreach (var key in MinimalFeatureFlagCalledEventProperties)
@@ -422,7 +424,9 @@ static CapturedEvent ToMinimalFeatureFlagCalledEvent(CapturedEvent capturedEvent
capturedEvent.EventName,
capturedEvent.DistinctId,
properties,
- capturedEvent.Timestamp);
+ capturedEvent.Timestamp,
+ _options.Value.LibraryName,
+ _options.Value.LibraryVersion);
}
async Task CaptureBatchAsync(IEnumerable batch)
diff --git a/tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs b/tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs
index 587c658d..b88d2699 100644
--- a/tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs
+++ b/tests/PostHog.AI.Tests/PostHogOpenAIHandlerTests.cs
@@ -309,6 +309,17 @@ private static bool VerifyProps(Dictionary props)
|| (string)provider != "openai"
)
return false;
+ if (
+ !props.TryGetValue(PostHogAIFieldNames.Lib, out var library)
+ || (string)library != "posthog-ai"
+ )
+ return false;
+ if (
+ !props.TryGetValue(PostHogAIFieldNames.LibVersion, out var libraryVersion)
+ || (string)libraryVersion
+ != typeof(PostHogOpenAIHandler).Assembly.GetName().Version?.ToString(3)
+ )
+ return false;
return true;
}
diff --git a/tests/UnitTests.AspNetCore/RegistrationTests.cs b/tests/UnitTests.AspNetCore/RegistrationTests.cs
index c0226299..ea3ef6ea 100644
--- a/tests/UnitTests.AspNetCore/RegistrationTests.cs
+++ b/tests/UnitTests.AspNetCore/RegistrationTests.cs
@@ -1,4 +1,5 @@
using System.Net;
+using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -42,6 +43,45 @@ public void ReadsSettingsFromPostHogConfigurationSection()
Assert.Equal(TimeSpan.FromSeconds(10), options.FeatureFlagPollInterval);
}
+ [Fact]
+ public async Task UsesAspNetCoreLibraryMetadata()
+ {
+ var builder = WebApplication.CreateSlimBuilder();
+ using var messageHandler = new FakeHttpMessageHandler();
+ var flagsRequestHandler = messageHandler.AddFlagsResponse("""{"featureFlags": {}}""");
+ var batchRequestHandler = messageHandler.AddBatchResponse();
+ builder.Configuration.AddInMemoryCollection(new Dictionary
+ {
+ ["PostHog:ProjectToken"] = "fake-not-so-secret",
+ ["PostHog:EnableCompression"] = "false",
+ });
+ builder.AddPostHog(options =>
+ options.ConfigureHttpClient(httpClient =>
+ httpClient.ConfigurePrimaryHttpMessageHandler(() => messageHandler)));
+
+ await using var provider = builder.Services.BuildServiceProvider();
+ var client = provider.GetRequiredService();
+ await client.GetAllFeatureFlagsAsync("test-user", null, CancellationToken.None);
+ client.Capture("test-user", "test-event");
+ await client.FlushAsync();
+
+ var expectedVersion = typeof(Registration).Assembly.GetName().Version?.ToString(3);
+ using var flagsDocument = JsonDocument.Parse(flagsRequestHandler.GetReceivedRequestBody(indented: false));
+ var flagsProperties = flagsDocument.RootElement.GetProperty("properties");
+ Assert.Equal("posthog-aspnetcore", flagsProperties.GetProperty("$lib").GetString());
+ Assert.Equal(expectedVersion, flagsProperties.GetProperty("$lib_version").GetString());
+
+ var userAgent = flagsRequestHandler.ReceivedRequest.Headers.UserAgent;
+ var userAgentProduct = Assert.Single(userAgent, value => value.Product is not null).Product;
+ Assert.Equal("posthog-aspnetcore", userAgentProduct?.Name);
+ Assert.Equal(expectedVersion, userAgentProduct?.Version);
+
+ using var batchDocument = JsonDocument.Parse(batchRequestHandler.GetReceivedRequestBody(indented: false));
+ var eventProperties = batchDocument.RootElement.GetProperty("batch")[0].GetProperty("properties");
+ Assert.Equal("posthog-aspnetcore", eventProperties.GetProperty("$lib").GetString());
+ Assert.Equal(expectedVersion, eventProperties.GetProperty("$lib_version").GetString());
+ }
+
[Fact]
public void DoesNotLogLegacyWarningWhenOnlyProjectTokenIsConfigured()
{