audit, advisories and doctors/setup work
This commit is contained in:
@@ -17,6 +17,7 @@ using StellaOps.AdvisoryAI.Chat.Models;
|
||||
using StellaOps.AdvisoryAI.Chat.Options;
|
||||
using StellaOps.AdvisoryAI.Chat.Routing;
|
||||
using StellaOps.AdvisoryAI.Chat.Services;
|
||||
using StellaOps.AdvisoryAI.Chat.Settings;
|
||||
using StellaOps.AdvisoryAI.WebService.Contracts;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.WebService.Endpoints;
|
||||
@@ -76,6 +77,29 @@ public static class ChatEndpoints
|
||||
.Produces<EvidenceBundlePreviewResponse>(StatusCodes.Status200OK)
|
||||
.Produces<ErrorResponse>(StatusCodes.Status400BadRequest);
|
||||
|
||||
// Settings endpoints
|
||||
group.MapGet("/settings", GetChatSettingsAsync)
|
||||
.WithName("GetChatSettings")
|
||||
.WithSummary("Gets effective chat settings for the caller")
|
||||
.Produces<ChatSettingsResponse>(StatusCodes.Status200OK);
|
||||
|
||||
group.MapPut("/settings", UpdateChatSettingsAsync)
|
||||
.WithName("UpdateChatSettings")
|
||||
.WithSummary("Updates chat settings overrides (tenant or user)")
|
||||
.Produces<ChatSettingsResponse>(StatusCodes.Status200OK)
|
||||
.Produces<ErrorResponse>(StatusCodes.Status400BadRequest);
|
||||
|
||||
group.MapDelete("/settings", ClearChatSettingsAsync)
|
||||
.WithName("ClearChatSettings")
|
||||
.WithSummary("Clears chat settings overrides (tenant or user)")
|
||||
.Produces(StatusCodes.Status204NoContent);
|
||||
|
||||
// Doctor endpoint
|
||||
group.MapGet("/doctor", GetChatDoctorAsync)
|
||||
.WithName("GetChatDoctor")
|
||||
.WithSummary("Returns chat limit status and tool access diagnostics")
|
||||
.Produces<ChatDoctorResponse>(StatusCodes.Status200OK);
|
||||
|
||||
// Health/status endpoint for chat service
|
||||
group.MapGet("/status", GetChatStatusAsync)
|
||||
.WithName("GetChatStatus")
|
||||
@@ -131,16 +155,48 @@ public static class ChatEndpoints
|
||||
{
|
||||
var statusCode = result.GuardrailBlocked
|
||||
? StatusCodes.Status400BadRequest
|
||||
: StatusCodes.Status500InternalServerError;
|
||||
: result.QuotaBlocked
|
||||
? StatusCodes.Status429TooManyRequests
|
||||
: result.ToolAccessDenied
|
||||
? StatusCodes.Status403Forbidden
|
||||
: StatusCodes.Status500InternalServerError;
|
||||
|
||||
var code = result.GuardrailBlocked
|
||||
? "GUARDRAIL_BLOCKED"
|
||||
: result.QuotaBlocked
|
||||
? result.QuotaCode ?? "QUOTA_EXCEEDED"
|
||||
: result.ToolAccessDenied
|
||||
? "TOOL_DENIED"
|
||||
: "PROCESSING_FAILED";
|
||||
|
||||
Dictionary<string, object>? details = null;
|
||||
if (result.GuardrailBlocked)
|
||||
{
|
||||
details = result.GuardrailViolations.ToDictionary(v => v, _ => (object)"violation");
|
||||
}
|
||||
else if (result.QuotaBlocked && result.QuotaStatus is not null)
|
||||
{
|
||||
details = new Dictionary<string, object> { ["quota"] = result.QuotaStatus };
|
||||
}
|
||||
else if (result.ToolAccessDenied)
|
||||
{
|
||||
details = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = result.ToolAccessReason ?? "Tool access denied"
|
||||
};
|
||||
}
|
||||
|
||||
var doctor = result.QuotaBlocked || result.ToolAccessDenied
|
||||
? CreateDoctorAction(code)
|
||||
: null;
|
||||
|
||||
return Results.Json(
|
||||
new ErrorResponse
|
||||
{
|
||||
Error = result.Error ?? "Query processing failed",
|
||||
Code = result.GuardrailBlocked ? "GUARDRAIL_BLOCKED" : "PROCESSING_FAILED",
|
||||
Details = result.GuardrailBlocked
|
||||
? result.GuardrailViolations.ToDictionary(v => v, _ => (object)"violation")
|
||||
: null
|
||||
Code = code,
|
||||
Details = details,
|
||||
Doctor = doctor
|
||||
},
|
||||
statusCode: statusCode);
|
||||
}
|
||||
@@ -154,6 +210,8 @@ public static class ChatEndpoints
|
||||
[FromServices] IEvidenceBundleAssembler evidenceAssembler,
|
||||
[FromServices] IAdvisoryChatInferenceClient inferenceClient,
|
||||
[FromServices] IOptions<AdvisoryChatOptions> options,
|
||||
[FromServices] IAdvisoryChatSettingsService settingsService,
|
||||
[FromServices] IAdvisoryChatQuotaService quotaService,
|
||||
[FromServices] ILogger<AdvisoryChatQueryRequest> logger,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string? tenantId,
|
||||
[FromHeader(Name = "X-User-Id")] string? userId,
|
||||
@@ -180,6 +238,7 @@ public static class ChatEndpoints
|
||||
}
|
||||
|
||||
tenantId ??= "default";
|
||||
userId ??= "anonymous";
|
||||
|
||||
httpContext.Response.ContentType = "text/event-stream";
|
||||
httpContext.Response.Headers.CacheControl = "no-cache";
|
||||
@@ -214,6 +273,55 @@ public static class ChatEndpoints
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = await settingsService.GetEffectiveSettingsAsync(
|
||||
tenantId,
|
||||
userId,
|
||||
ct);
|
||||
|
||||
var toolPolicy = AdvisoryChatToolPolicy.Resolve(
|
||||
settings.Tools,
|
||||
options.Value.DataProviders,
|
||||
includeReachability: true,
|
||||
includeBinaryPatch: true,
|
||||
includeOpsMemory: true);
|
||||
|
||||
if (!toolPolicy.AllowSbom)
|
||||
{
|
||||
await WriteStreamEventAsync(httpContext, "error", new
|
||||
{
|
||||
code = "TOOL_DENIED",
|
||||
message = "Tool access denied: sbom.read",
|
||||
doctor = CreateDoctorAction("TOOL_DENIED")
|
||||
}, ct);
|
||||
await WriteStreamEventAsync(httpContext, "done", new { success = false }, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
var quotaDecision = await quotaService.TryConsumeAsync(
|
||||
new ChatQuotaRequest
|
||||
{
|
||||
TenantId = tenantId,
|
||||
UserId = userId,
|
||||
EstimatedTokens = options.Value.Inference.MaxTokens,
|
||||
ToolCalls = toolPolicy.ToolCallCount
|
||||
},
|
||||
settings.Quotas,
|
||||
ct);
|
||||
|
||||
if (!quotaDecision.Allowed)
|
||||
{
|
||||
var quotaCode = quotaDecision.Code ?? "QUOTA_EXCEEDED";
|
||||
await WriteStreamEventAsync(httpContext, "error", new
|
||||
{
|
||||
code = quotaCode,
|
||||
message = quotaDecision.Message ?? "Quota exceeded",
|
||||
quota = quotaDecision.Status,
|
||||
doctor = CreateDoctorAction(quotaCode)
|
||||
}, ct);
|
||||
await WriteStreamEventAsync(httpContext, "done", new { success = false }, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Assemble evidence bundle
|
||||
await WriteStreamEventAsync(httpContext, "status", new { phase = "assembling_evidence" }, ct);
|
||||
|
||||
@@ -226,6 +334,15 @@ public static class ChatEndpoints
|
||||
Environment = request.Environment ?? "unknown",
|
||||
FindingId = findingId,
|
||||
PackagePurl = routingResult.Parameters.Package,
|
||||
IncludeSbom = toolPolicy.AllowSbom,
|
||||
IncludeVex = toolPolicy.AllowVex,
|
||||
IncludePolicy = toolPolicy.AllowPolicy,
|
||||
IncludeProvenance = toolPolicy.AllowProvenance,
|
||||
IncludeFix = toolPolicy.AllowFix,
|
||||
IncludeContext = toolPolicy.AllowContext,
|
||||
IncludeReachability = toolPolicy.AllowReachability,
|
||||
IncludeBinaryPatch = toolPolicy.AllowBinaryPatch,
|
||||
IncludeOpsMemory = toolPolicy.AllowOpsMemory,
|
||||
CorrelationId = correlationId
|
||||
},
|
||||
ct);
|
||||
@@ -324,7 +441,11 @@ public static class ChatEndpoints
|
||||
private static async Task<IResult> PreviewEvidenceBundleAsync(
|
||||
[FromBody] EvidencePreviewRequest request,
|
||||
[FromServices] IEvidenceBundleAssembler evidenceAssembler,
|
||||
[FromServices] IOptions<AdvisoryChatOptions> options,
|
||||
[FromServices] IAdvisoryChatSettingsService settingsService,
|
||||
[FromServices] IAdvisoryChatQuotaService quotaService,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string? tenantId,
|
||||
[FromHeader(Name = "X-User-Id")] string? userId,
|
||||
[FromHeader(Name = "X-Correlation-Id")] string? correlationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
@@ -334,6 +455,52 @@ public static class ChatEndpoints
|
||||
}
|
||||
|
||||
tenantId ??= "default";
|
||||
userId ??= "anonymous";
|
||||
|
||||
var settings = await settingsService.GetEffectiveSettingsAsync(tenantId, userId, ct);
|
||||
var toolPolicy = AdvisoryChatToolPolicy.Resolve(
|
||||
settings.Tools,
|
||||
options.Value.DataProviders,
|
||||
includeReachability: true,
|
||||
includeBinaryPatch: true,
|
||||
includeOpsMemory: true);
|
||||
|
||||
if (!toolPolicy.AllowSbom)
|
||||
{
|
||||
return Results.Json(
|
||||
new ErrorResponse
|
||||
{
|
||||
Error = "Tool access denied: sbom.read",
|
||||
Code = "TOOL_DENIED",
|
||||
Doctor = CreateDoctorAction("TOOL_DENIED")
|
||||
},
|
||||
statusCode: StatusCodes.Status403Forbidden);
|
||||
}
|
||||
|
||||
var quotaDecision = await quotaService.TryConsumeAsync(
|
||||
new ChatQuotaRequest
|
||||
{
|
||||
TenantId = tenantId,
|
||||
UserId = userId,
|
||||
EstimatedTokens = 0,
|
||||
ToolCalls = toolPolicy.ToolCallCount
|
||||
},
|
||||
settings.Quotas,
|
||||
ct);
|
||||
|
||||
if (!quotaDecision.Allowed)
|
||||
{
|
||||
var quotaCode = quotaDecision.Code ?? "QUOTA_EXCEEDED";
|
||||
return Results.Json(
|
||||
new ErrorResponse
|
||||
{
|
||||
Error = quotaDecision.Message ?? "Quota exceeded",
|
||||
Code = quotaCode,
|
||||
Details = new Dictionary<string, object> { ["quota"] = quotaDecision.Status },
|
||||
Doctor = CreateDoctorAction(quotaCode)
|
||||
},
|
||||
statusCode: StatusCodes.Status429TooManyRequests);
|
||||
}
|
||||
|
||||
var assemblyResult = await evidenceAssembler.AssembleAsync(
|
||||
new EvidenceBundleAssemblyRequest
|
||||
@@ -344,6 +511,15 @@ public static class ChatEndpoints
|
||||
Environment = request.Environment ?? "unknown",
|
||||
FindingId = request.FindingId,
|
||||
PackagePurl = request.PackagePurl,
|
||||
IncludeSbom = toolPolicy.AllowSbom,
|
||||
IncludeVex = toolPolicy.AllowVex,
|
||||
IncludePolicy = toolPolicy.AllowPolicy,
|
||||
IncludeProvenance = toolPolicy.AllowProvenance,
|
||||
IncludeFix = toolPolicy.AllowFix,
|
||||
IncludeContext = toolPolicy.AllowContext,
|
||||
IncludeReachability = toolPolicy.AllowReachability,
|
||||
IncludeBinaryPatch = toolPolicy.AllowBinaryPatch,
|
||||
IncludeOpsMemory = toolPolicy.AllowOpsMemory,
|
||||
CorrelationId = correlationId
|
||||
},
|
||||
ct);
|
||||
@@ -379,6 +555,126 @@ public static class ChatEndpoints
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetChatSettingsAsync(
|
||||
[FromServices] IAdvisoryChatSettingsService settingsService,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string? tenantId,
|
||||
[FromHeader(Name = "X-User-Id")] string? userId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
tenantId ??= "default";
|
||||
userId ??= "anonymous";
|
||||
|
||||
var settings = await settingsService.GetEffectiveSettingsAsync(tenantId, userId, ct);
|
||||
return Results.Ok(ChatSettingsResponse.FromSettings(settings));
|
||||
}
|
||||
|
||||
private static async Task<IResult> UpdateChatSettingsAsync(
|
||||
[FromBody] ChatSettingsUpdateRequest request,
|
||||
[FromServices] IAdvisoryChatSettingsService settingsService,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string? tenantId,
|
||||
[FromHeader(Name = "X-User-Id")] string? userId,
|
||||
[FromQuery(Name = "scope")] string? scope,
|
||||
CancellationToken ct)
|
||||
{
|
||||
tenantId ??= "default";
|
||||
userId ??= "anonymous";
|
||||
|
||||
if (request is null)
|
||||
{
|
||||
return Results.BadRequest(new ErrorResponse { Error = "Settings payload is required", Code = "INVALID_SETTINGS" });
|
||||
}
|
||||
|
||||
var overrides = new AdvisoryChatSettingsOverrides
|
||||
{
|
||||
Quotas = new ChatQuotaOverrides
|
||||
{
|
||||
RequestsPerMinute = request.Quotas?.RequestsPerMinute,
|
||||
RequestsPerDay = request.Quotas?.RequestsPerDay,
|
||||
TokensPerDay = request.Quotas?.TokensPerDay,
|
||||
ToolCallsPerDay = request.Quotas?.ToolCallsPerDay
|
||||
},
|
||||
Tools = new ChatToolAccessOverrides
|
||||
{
|
||||
AllowAll = request.Tools?.AllowAll,
|
||||
AllowedTools = request.Tools?.AllowedTools is null
|
||||
? null
|
||||
: request.Tools.AllowedTools.ToImmutableArray()
|
||||
}
|
||||
};
|
||||
|
||||
if (string.Equals(scope, "user", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await settingsService.SetUserOverridesAsync(tenantId, userId, overrides, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
await settingsService.SetTenantOverridesAsync(tenantId, overrides, ct);
|
||||
}
|
||||
|
||||
var effective = await settingsService.GetEffectiveSettingsAsync(tenantId, userId, ct);
|
||||
return Results.Ok(ChatSettingsResponse.FromSettings(effective));
|
||||
}
|
||||
|
||||
private static async Task<IResult> ClearChatSettingsAsync(
|
||||
[FromServices] IAdvisoryChatSettingsService settingsService,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string? tenantId,
|
||||
[FromHeader(Name = "X-User-Id")] string? userId,
|
||||
[FromQuery(Name = "scope")] string? scope,
|
||||
CancellationToken ct)
|
||||
{
|
||||
tenantId ??= "default";
|
||||
userId ??= "anonymous";
|
||||
|
||||
if (string.Equals(scope, "user", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await settingsService.ClearUserOverridesAsync(tenantId, userId, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
await settingsService.ClearTenantOverridesAsync(tenantId, ct);
|
||||
}
|
||||
|
||||
return Results.NoContent();
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetChatDoctorAsync(
|
||||
[FromServices] IAdvisoryChatSettingsService settingsService,
|
||||
[FromServices] IAdvisoryChatQuotaService quotaService,
|
||||
[FromServices] IOptions<AdvisoryChatOptions> options,
|
||||
[FromHeader(Name = "X-Tenant-Id")] string? tenantId,
|
||||
[FromHeader(Name = "X-User-Id")] string? userId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
tenantId ??= "default";
|
||||
userId ??= "anonymous";
|
||||
|
||||
var settings = await settingsService.GetEffectiveSettingsAsync(tenantId, userId, ct);
|
||||
var toolPolicy = AdvisoryChatToolPolicy.Resolve(
|
||||
settings.Tools,
|
||||
options.Value.DataProviders,
|
||||
includeReachability: true,
|
||||
includeBinaryPatch: true,
|
||||
includeOpsMemory: true);
|
||||
|
||||
var quotaStatus = quotaService.GetStatus(tenantId, userId, settings.Quotas);
|
||||
|
||||
return Results.Ok(new ChatDoctorResponse
|
||||
{
|
||||
TenantId = tenantId,
|
||||
UserId = userId,
|
||||
Quotas = ChatQuotaStatusResponse.FromStatus(quotaStatus),
|
||||
Tools = ChatToolAccessResponse.FromPolicy(settings.Tools, toolPolicy),
|
||||
LastDenied = quotaStatus.LastDenied is null
|
||||
? null
|
||||
: new ChatDenialResponse
|
||||
{
|
||||
Code = quotaStatus.LastDenied.Code,
|
||||
Message = quotaStatus.LastDenied.Message,
|
||||
DeniedAt = quotaStatus.LastDenied.DeniedAt
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static Task<IResult> GetChatStatusAsync(
|
||||
[FromServices] IOptions<AdvisoryChatOptions> options)
|
||||
{
|
||||
@@ -395,6 +691,16 @@ public static class ChatEndpoints
|
||||
}));
|
||||
}
|
||||
|
||||
private static ChatDoctorAction CreateDoctorAction(string? reason)
|
||||
{
|
||||
return new ChatDoctorAction
|
||||
{
|
||||
Endpoint = "/api/v1/chat/doctor",
|
||||
SuggestedCommand = "stella advise doctor",
|
||||
Reason = reason
|
||||
};
|
||||
}
|
||||
|
||||
private static async Task WriteStreamEventAsync<T>(
|
||||
HttpContext context,
|
||||
string eventType,
|
||||
@@ -741,12 +1047,178 @@ public sealed record ChatServiceStatusResponse
|
||||
public required bool AuditEnabled { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Chat settings update request.</summary>
|
||||
public sealed record ChatSettingsUpdateRequest
|
||||
{
|
||||
public ChatQuotaSettingsUpdateRequest? Quotas { get; init; }
|
||||
public ChatToolAccessUpdateRequest? Tools { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Quota update request.</summary>
|
||||
public sealed record ChatQuotaSettingsUpdateRequest
|
||||
{
|
||||
public int? RequestsPerMinute { get; init; }
|
||||
public int? RequestsPerDay { get; init; }
|
||||
public int? TokensPerDay { get; init; }
|
||||
public int? ToolCallsPerDay { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Tool access update request.</summary>
|
||||
public sealed record ChatToolAccessUpdateRequest
|
||||
{
|
||||
public bool? AllowAll { get; init; }
|
||||
public List<string>? AllowedTools { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Chat settings response.</summary>
|
||||
public sealed record ChatSettingsResponse
|
||||
{
|
||||
public required ChatQuotaSettingsResponse Quotas { get; init; }
|
||||
public required ChatToolAccessResponse Tools { get; init; }
|
||||
|
||||
public static ChatSettingsResponse FromSettings(AdvisoryChatSettings settings)
|
||||
{
|
||||
return new ChatSettingsResponse
|
||||
{
|
||||
Quotas = new ChatQuotaSettingsResponse
|
||||
{
|
||||
RequestsPerMinute = settings.Quotas.RequestsPerMinute,
|
||||
RequestsPerDay = settings.Quotas.RequestsPerDay,
|
||||
TokensPerDay = settings.Quotas.TokensPerDay,
|
||||
ToolCallsPerDay = settings.Quotas.ToolCallsPerDay
|
||||
},
|
||||
Tools = new ChatToolAccessResponse
|
||||
{
|
||||
AllowAll = settings.Tools.AllowAll,
|
||||
AllowedTools = settings.Tools.AllowedTools.ToList()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Quota settings response.</summary>
|
||||
public sealed record ChatQuotaSettingsResponse
|
||||
{
|
||||
public required int RequestsPerMinute { get; init; }
|
||||
public required int RequestsPerDay { get; init; }
|
||||
public required int TokensPerDay { get; init; }
|
||||
public required int ToolCallsPerDay { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Tool access response.</summary>
|
||||
public sealed record ChatToolAccessResponse
|
||||
{
|
||||
public required bool AllowAll { get; init; }
|
||||
public List<string> AllowedTools { get; init; } = [];
|
||||
public ChatToolProviderResponse? Providers { get; init; }
|
||||
|
||||
public static ChatToolAccessResponse FromPolicy(
|
||||
ChatToolAccessSettings settings,
|
||||
ChatToolPolicyResult policy)
|
||||
{
|
||||
return new ChatToolAccessResponse
|
||||
{
|
||||
AllowAll = settings.AllowAll,
|
||||
AllowedTools = policy.AllowedTools.ToList(),
|
||||
Providers = new ChatToolProviderResponse
|
||||
{
|
||||
Sbom = policy.AllowSbom,
|
||||
Vex = policy.AllowVex,
|
||||
Reachability = policy.AllowReachability,
|
||||
BinaryPatch = policy.AllowBinaryPatch,
|
||||
OpsMemory = policy.AllowOpsMemory,
|
||||
Policy = policy.AllowPolicy,
|
||||
Provenance = policy.AllowProvenance,
|
||||
Fix = policy.AllowFix,
|
||||
Context = policy.AllowContext
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Tool provider availability response.</summary>
|
||||
public sealed record ChatToolProviderResponse
|
||||
{
|
||||
public bool Sbom { get; init; }
|
||||
public bool Vex { get; init; }
|
||||
public bool Reachability { get; init; }
|
||||
public bool BinaryPatch { get; init; }
|
||||
public bool OpsMemory { get; init; }
|
||||
public bool Policy { get; init; }
|
||||
public bool Provenance { get; init; }
|
||||
public bool Fix { get; init; }
|
||||
public bool Context { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Chat doctor response.</summary>
|
||||
public sealed record ChatDoctorResponse
|
||||
{
|
||||
public required string TenantId { get; init; }
|
||||
public required string UserId { get; init; }
|
||||
public required ChatQuotaStatusResponse Quotas { get; init; }
|
||||
public required ChatToolAccessResponse Tools { get; init; }
|
||||
public ChatDenialResponse? LastDenied { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Doctor action hint.</summary>
|
||||
public sealed record ChatDoctorAction
|
||||
{
|
||||
public required string Endpoint { get; init; }
|
||||
public required string SuggestedCommand { get; init; }
|
||||
public string? Reason { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Quota status response.</summary>
|
||||
public sealed record ChatQuotaStatusResponse
|
||||
{
|
||||
public required int RequestsPerMinuteLimit { get; init; }
|
||||
public required int RequestsPerMinuteRemaining { get; init; }
|
||||
public required DateTimeOffset RequestsPerMinuteResetsAt { get; init; }
|
||||
public required int RequestsPerDayLimit { get; init; }
|
||||
public required int RequestsPerDayRemaining { get; init; }
|
||||
public required DateTimeOffset RequestsPerDayResetsAt { get; init; }
|
||||
public required int TokensPerDayLimit { get; init; }
|
||||
public required int TokensPerDayRemaining { get; init; }
|
||||
public required DateTimeOffset TokensPerDayResetsAt { get; init; }
|
||||
public required int ToolCallsPerDayLimit { get; init; }
|
||||
public required int ToolCallsPerDayRemaining { get; init; }
|
||||
public required DateTimeOffset ToolCallsPerDayResetsAt { get; init; }
|
||||
|
||||
public static ChatQuotaStatusResponse FromStatus(ChatQuotaStatus status)
|
||||
{
|
||||
return new ChatQuotaStatusResponse
|
||||
{
|
||||
RequestsPerMinuteLimit = status.RequestsPerMinuteLimit,
|
||||
RequestsPerMinuteRemaining = status.RequestsPerMinuteRemaining,
|
||||
RequestsPerMinuteResetsAt = status.RequestsPerMinuteResetsAt,
|
||||
RequestsPerDayLimit = status.RequestsPerDayLimit,
|
||||
RequestsPerDayRemaining = status.RequestsPerDayRemaining,
|
||||
RequestsPerDayResetsAt = status.RequestsPerDayResetsAt,
|
||||
TokensPerDayLimit = status.TokensPerDayLimit,
|
||||
TokensPerDayRemaining = status.TokensPerDayRemaining,
|
||||
TokensPerDayResetsAt = status.TokensPerDayResetsAt,
|
||||
ToolCallsPerDayLimit = status.ToolCallsPerDayLimit,
|
||||
ToolCallsPerDayRemaining = status.ToolCallsPerDayRemaining,
|
||||
ToolCallsPerDayResetsAt = status.ToolCallsPerDayResetsAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Quota denial response.</summary>
|
||||
public sealed record ChatDenialResponse
|
||||
{
|
||||
public required string Code { get; init; }
|
||||
public required string Message { get; init; }
|
||||
public required DateTimeOffset DeniedAt { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Error response.</summary>
|
||||
public sealed record ErrorResponse
|
||||
{
|
||||
public required string Error { get; init; }
|
||||
public string? Code { get; init; }
|
||||
public Dictionary<string, object>? Details { get; init; }
|
||||
public ChatDoctorAction? Doctor { get; init; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Reference in New Issue
Block a user