Client
Client
The FabrCore.Client package provides Blazor Server (Interactive Server) integration for communicating with the FabrCore agent cluster. It handles Orleans grain communication, observer subscriptions, and resource lifecycle management — designed specifically for Blazor's interactive server-side rendering model.
Setup
var builder = WebApplication.CreateBuilder(args);
builder.AddFabrCoreClient();
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
var app = builder.Build();
app.UseFabrCoreClient();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
{
"Orleans": {
"ClusteringMode": "Localhost",
"ClusterId": "fabrcore-cluster",
"ServiceId": "fabrcore-service"
},
"FabrHostUrl": "http://localhost:5000"
}
ClientContext
ClientContext encapsulates the complexity of Orleans communication, observer subscriptions, and resource lifecycle. Use it for custom chat interfaces.
Factory Interface
| Method | Description | Ownership |
|---|---|---|
CreateAsync(handle) | Create a new, independent context | Caller responsible for disposal |
GetOrCreateAsync(handle) | Return cached context or create new | Factory manages lifecycle |
ReleaseAsync(handle) | Remove and dispose cached context | N/A |
HasContext(handle) | Check if cached context exists | N/A |
FabrCore.Client relies on persistent SignalR connections for Orleans observer subscriptions and real-time agent communication. This requires Blazor's Interactive Server render mode — it is not compatible with Blazor WebAssembly, static SSR, or standalone WebAPI projects.
Usage Pattern
@inject IClientContextFactory ClientContextFactory
@implements IAsyncDisposable
@code {
private IClientContext? _context;
protected override async Task OnInitializedAsync()
{
_context = await ClientContextFactory
.GetOrCreateAsync(userId);
_context.AgentMessageReceived += OnMessageReceived;
}
public ValueTask DisposeAsync()
{
if (_context != null)
_context.AgentMessageReceived -= OnMessageReceived;
return ValueTask.CompletedTask;
}
}
Direct Messaging
For lightweight fire-and-forget messages without full ClientContext overhead:
public class NotificationService
{
private readonly IDirectMessageSender _sender;
public async Task NotifyAgent(string handle, string text)
{
await _sender.SendMessageAsync(new AgentMessage
{
FromHandle = "notification-service",
ToHandle = handle,
Message = text,
MessageType = "notification"
});
}
}
Health Monitoring
Check agent health from the client:
var health = await context.GetAgentHealth(
"my-agent", HealthDetailLevel.Full);
switch (health.State)
{
case HealthState.Healthy:
Console.WriteLine("Agent is ready");
break;
case HealthState.Degraded:
Console.WriteLine($"Issues: {health.Message}");
break;
}
Client State Persistence
The client automatically persists tracked agents and pending messages to Orleans grain storage:
| Data | When Persisted | Behavior |
|---|---|---|
| Tracked Agents | Immediately on CreateAgent() | Survives grain deactivation |
| Pending Messages | On grain deactivation | Messages older than 1 hour discarded on restore |
OrleansClusterOptions
Configure the Orleans cluster connection used by the client to reach the FabrCore server.
| Property | Type | Default | Description |
|---|---|---|---|
ClusterId | string | "fabrcore-cluster" | Membership cluster ID — must match server |
ServiceId | string | "fabrcore-service" | Service ID — must match server |
ClusteringMode | ClusteringMode | Localhost | Provider: Localhost, SqlServer, AzureStorage |
ConnectionString | string? | null | Required for SqlServer and AzureStorage modes |
ConnectionRetryCount | int | 5 | Max retry attempts (0 = no retries) |
ConnectionRetryDelay | TimeSpan | 00:00:03 | Delay between connection retries |
GatewayListRefreshPeriod | TimeSpan | 00:00:30 | Gateway list refresh timeout |
public enum ClusteringMode
{
Localhost, // Development (in-memory)
SqlServer, // Production (SQL Server)
AzureStorage // Azure (Table Storage)
}
Connection Retry Configuration
{
"Orleans": {
"ClusterId": "fabrcore-cluster",
"ServiceId": "fabrcore-service",
"ClusteringMode": "Localhost",
"ConnectionRetryCount": 10,
"ConnectionRetryDelay": "00:00:05",
"GatewayListRefreshPeriod": "00:00:30"
}
}
IFabrHostApiClient
A typed HTTP client for the FabrCore Host REST API. Automatically registered when calling AddFabrCoreClient(). Configure the host URL in appsettings.json.
public interface IFabrHostApiClient
{
// Agent Operations
Task<CreateAgentsResponse> CreateAgentsAsync(string userId,
IEnumerable<AgentConfiguration> configs, ...);
Task<AgentHealthStatus> GetAgentHealthAsync(string userId,
string handle, ...);
Task<AgentMessage> ChatAsync(string userId,
string handle, string message, ...);
Task<AgentsListResponse> GetAgentsAsync(...);
Task<AgentInfo?> GetAgentAsync(string key, ...);
Task<AgentStatisticsResponse> GetAgentStatisticsAsync(...);
Task<PurgeAgentsResponse> PurgeOldAgentsAsync(int olderThanHours, ...);
// Configuration
Task<ModelConfigResponse> GetModelConfigAsync(string name, ...);
Task<ApiKeyResponse> GetApiKeyAsync(string alias, ...);
// File Operations
Task<string> UploadFileAsync(Stream fileStream,
string fileName, int ttlSeconds = 3600, ...);
Task<Stream?> GetFileAsync(string fileId, ...);
Task<FileMetadataResponse?> GetFileInfoAsync(string fileId, ...);
// Embeddings
Task<EmbeddingResponse> GetEmbeddingsAsync(string text, ...);
Task<BatchEmbeddingResponse> GetBatchEmbeddingsAsync(
List<BatchEmbeddingItem> items, ...);
}
Response Types
| Type | Properties |
|---|---|
ModelConfigResponse | Name, Provider, Uri, Model, ApiKeyAlias |
ApiKeyResponse | Value |
AgentsListResponse | Count, Agents |
AgentStatisticsResponse | Statistics (Dictionary) |
PurgeAgentsResponse | PurgedCount, Message |
FileMetadataResponse | FileId, OriginalFileName, ExpiresAt, FileSize, ContentType |
EmbeddingResponse | Vector, Dimensions |
BatchEmbeddingItem | Id, Text |
BatchEmbeddingRequest | Items (List<BatchEmbeddingItem>) |
BatchEmbeddingResultItem | Id, Vector (float[]), Dimensions |
BatchEmbeddingResponse | Results (List<BatchEmbeddingResultItem>) |
CreateAgentsResponse | TotalRequested, SuccessCount, FailureCount, Results |
Usage Example
public class AgentConfigService
{
private readonly IFabrHostApiClient _apiClient;
public AgentConfigService(IFabrHostApiClient apiClient)
=> _apiClient = apiClient;
public async Task<IChatClient> CreateChatClientAsync(string modelName)
{
var modelConfig = await _apiClient.GetModelConfigAsync(modelName);
var apiKey = await _apiClient.GetApiKeyAsync(modelConfig.ApiKeyAlias);
return new OpenAIChatClient(
model: modelConfig.Model,
apiKey: apiKey.Value);
}
}
WebAPI Pattern
Use ClientContextFactory in WebAPI controllers for per-request agent communication:
[ApiController]
[Route("api/[controller]")]
public class ChatController : ControllerBase
{
private readonly IClientContextFactory _factory;
public ChatController(IClientContextFactory factory)
=> _factory = factory;
[HttpPost("send")]
public async Task<IActionResult> SendMessage(
[FromBody] ChatRequest request)
{
var userId = User
.FindFirst(ClaimTypes.NameIdentifier)?.Value
?? throw new UnauthorizedAccessException();
await using var context =
await _factory.CreateAsync(userId);
var response = await context
.SendAndReceiveMessage(new AgentMessage
{
FromHandle = userId,
ToHandle = request.AgentHandle,
Message = request.Message
});
return Ok(new { response.Message });
}
}
Telemetry
FabrCore.Client includes comprehensive OpenTelemetry instrumentation for observability.
Activity Sources
| Source | Description |
|---|---|
FabrCore.Client.Extensions | Client configuration and setup |
FabrCore.Client.Connection | Orleans connection lifecycle |
FabrCore.Client.FabrHostApiClient | HTTP API client operations |
FabrCore.Client.ClientContext | Context operations |
FabrCore.Client.ClientContextFactory | Factory operations |
FabrCore.Client.DirectMessageSender | Direct messaging operations |
FabrCore.Client.FabrCoreClientAgentProxy | Agent proxy operations |
Connection Metrics
| Metric | Type | Description |
|---|---|---|
fabrcore.client.connection.retries | Counter | Connection retry attempts |
fabrcore.client.connection.retry_attempts | Counter | Individual retry operations |
fabrcore.client.connection.success | Counter | Successful connections |
fabrcore.client.connection.failures | Counter | Failed connections |
API Client Metrics
| Metric | Type | Description |
|---|---|---|
fabrcore.api_client.requests | Counter | API requests made |
fabrcore.api_client.errors | Counter | API errors |
fabrcore.api_client.request.duration | Histogram | API request duration |
ClientContextFactory Metrics
| Metric | Type | Description |
|---|---|---|
fabrcore.client.factory.contexts.created | Counter | Total contexts created |
fabrcore.client.factory.contexts.cached | Counter | Contexts served from cache |
fabrcore.client.factory.contexts.released | Counter | Contexts released/disposed |
fabrcore.client.factory.creation.duration | Histogram | Context creation time |
fabrcore.client.factory.errors | Counter | Factory errors |
Direct Messaging Metrics
| Metric | Type | Description |
|---|---|---|
fabrcore.direct.messages.sent | Counter | Direct messages sent |
fabrcore.direct.events.sent | Counter | Direct events sent |
fabrcore.direct.errors | Counter | Direct messaging errors |
Enabling Telemetry
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("FabrCore.Client.*"))
.WithMetrics(metrics => metrics
.AddMeter("FabrCore.Client.*"));