diff --git a/src/Qdrant.Client/Grpc/QdrantChannel.cs b/src/Qdrant.Client/Grpc/QdrantChannel.cs
index 842464c..b089840 100644
--- a/src/Qdrant.Client/Grpc/QdrantChannel.cs
+++ b/src/Qdrant.Client/Grpc/QdrantChannel.cs
@@ -33,20 +33,19 @@ public override CallInvoker CreateCallInvoker()
if (Disposed)
throw new ObjectDisposedException(nameof(QdrantChannel));
- var hasApiKey = _configuration.ApiKey is not null;
- var hasHeaders = _configuration.Headers.Count > 0;
-
- if (!hasApiKey && !hasHeaders)
- return _channel.CreateCallInvoker();
-
return _channel.Intercept(metadata =>
{
- if (hasApiKey)
- metadata.Add("api-key", _configuration.ApiKey!);
+ if (_configuration.ApiKey is not null)
+ metadata.Add("api-key", _configuration.ApiKey);
foreach (var header in _configuration.Headers)
metadata.Add(header.Key, header.Value);
+ var requestHeaders = RequestHeaders.Current;
+ if (requestHeaders is not null)
+ foreach (var header in requestHeaders)
+ metadata.Add(header.Key, header.Value);
+
return metadata;
});
}
diff --git a/src/Qdrant.Client/RequestHeaders.cs b/src/Qdrant.Client/RequestHeaders.cs
new file mode 100644
index 0000000..4fd7687
--- /dev/null
+++ b/src/Qdrant.Client/RequestHeaders.cs
@@ -0,0 +1,42 @@
+namespace Qdrant.Client;
+
+///
+/// Utilities for attaching per-request headers to gRPC calls.
+///
+public static class RequestHeaders
+{
+ private static readonly AsyncLocal?> _headers = new();
+
+ internal static IReadOnlyDictionary? Current => _headers.Value;
+
+ ///
+ /// Sets key and value as metadata on requests made within the returned scope.
+ ///
+ public static IDisposable Use(string key, string value) =>
+ Use(new Dictionary { [key] = value });
+
+ ///
+ /// Sets all entries of headers as metadata on requests made within the returned scope.
+ ///
+ public static IDisposable Use(IDictionary headers)
+ {
+ var previous = _headers.Value;
+ var merged = new Dictionary();
+ if (previous is not null)
+ foreach (var header in previous)
+ merged[header.Key] = header.Value;
+ foreach (var header in headers)
+ merged[header.Key] = header.Value;
+ _headers.Value = merged;
+ return new Scope(() => _headers.Value = previous);
+ }
+
+ private sealed class Scope : IDisposable
+ {
+ private readonly Action _restore;
+
+ internal Scope(Action restore) => _restore = restore;
+
+ public void Dispose() => _restore();
+ }
+}