Add provider rate limit snapshot state
This commit is contained in:
1 parent
c6be8a4ee4
commit
0388e1003b
7 files changed
+102
-34
No files matched your search
@@ -4,6 +4,7 @@ import { BrowserWindow, session } from "electron";
|
|||||||
import type { CreateProviderResponseInput, SaveProviderCredentialInput, UpdateProviderPolicyInput } from "../../shared/commands";
|
import type { CreateProviderResponseInput, SaveProviderCredentialInput, UpdateProviderPolicyInput } from "../../shared/commands";
|
||||||
import { FEATURE_FLAG_KEY, PROVIDER_AUTH_TYPE, PROVIDER_DATA_POLICY, PROVIDER_EXECUTION_MODE, PROVIDER_REQUEST_STATE, SLO_SIGNAL_KEY, type ProviderAuthType, type ProviderExecutionMode } from "../../shared/domain";
|
import { FEATURE_FLAG_KEY, PROVIDER_AUTH_TYPE, PROVIDER_DATA_POLICY, PROVIDER_EXECUTION_MODE, PROVIDER_REQUEST_STATE, SLO_SIGNAL_KEY, type ProviderAuthType, type ProviderExecutionMode } from "../../shared/domain";
|
||||||
import { PROVIDER_CATALOG } from "../../shared/provider-catalog";
|
import { PROVIDER_CATALOG } from "../../shared/provider-catalog";
|
||||||
|
import { currentProviderUsageWindow } from "../../shared/provider-runtime-domain";
|
||||||
import { createId } from "../../shared/ids";
|
import { createId } from "../../shared/ids";
|
||||||
import { AuditService } from "./audit-service";
|
import { AuditService } from "./audit-service";
|
||||||
import { AuthBrokerService, type ProviderSecretVault } from "./auth-broker-service";
|
import { AuthBrokerService, type ProviderSecretVault } from "./auth-broker-service";
|
||||||
@@ -271,7 +272,7 @@ export class ProviderService {
|
|||||||
if (executionMode === PROVIDER_EXECUTION_MODE.BACKGROUND && provider.background_enabled !== 1) {
|
if (executionMode === PROVIDER_EXECUTION_MODE.BACKGROUND && provider.background_enabled !== 1) {
|
||||||
throw new Error("Background execution is disabled for this provider policy.");
|
throw new Error("Background execution is disabled for this provider policy.");
|
||||||
}
|
}
|
||||||
const window = currentMonthlyWindow();
|
const window = currentProviderUsageWindow();
|
||||||
const usage = this.db.prepare(`
|
const usage = this.db.prepare(`
|
||||||
select request_count
|
select request_count
|
||||||
from provider_usage_windows
|
from provider_usage_windows
|
||||||
@@ -341,7 +342,7 @@ export class ProviderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private recordUsage(orgId: string, providerId: string, inputCharCount: number, outputCharCount: number): void {
|
private recordUsage(orgId: string, providerId: string, inputCharCount: number, outputCharCount: number): void {
|
||||||
const window = currentMonthlyWindow();
|
const window = currentProviderUsageWindow();
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
this.db.prepare(`
|
this.db.prepare(`
|
||||||
insert into provider_usage_windows(
|
insert into provider_usage_windows(
|
||||||
@@ -374,10 +375,3 @@ interface ProviderRow {
|
|||||||
function hashPrompt(prompt: string): string {
|
function hashPrompt(prompt: string): string {
|
||||||
return createHash("sha256").update(prompt).digest("hex");
|
return createHash("sha256").update(prompt).digest("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentMonthlyWindow(): { start: string; end: string } {
|
|
||||||
const now = new Date();
|
|
||||||
const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
|
||||||
const end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1));
|
|
||||||
return { start: start.toISOString(), end: end.toISOString() };
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import type { DatabaseSync, StatementSync } from "node:sqlite";
|
import type { DatabaseSync, StatementSync } from "node:sqlite";
|
||||||
import type { AppSnapshot } from "../../shared/domain";
|
import type { AppSnapshot } from "../../shared/domain";
|
||||||
import { PROVIDER_CATALOG } from "../../shared/provider-catalog";
|
import { PROVIDER_CATALOG } from "../../shared/provider-catalog";
|
||||||
|
import { currentProviderUsageWindow } from "../../shared/provider-runtime-domain";
|
||||||
|
|
||||||
type Row = Record<string, unknown>;
|
type Row = Record<string, unknown>;
|
||||||
|
type ProviderUsageWindowRow = Pick<
|
||||||
|
AppSnapshot["providerRateLimits"][number],
|
||||||
|
"orgId" | "providerId" | "windowStart" | "windowEnd" | "requestCount" | "inputCharCount" | "outputCharCount"
|
||||||
|
>;
|
||||||
|
|
||||||
function all<T>(statement: StatementSync): T[] {
|
function all<T>(statement: StatementSync): T[] {
|
||||||
return statement.all() as T[];
|
return statement.all() as T[];
|
||||||
@@ -45,6 +50,8 @@ export class SnapshotService {
|
|||||||
.map((provider) => ({ ...provider, enabled: toBool(provider.enabled), backgroundEnabled: toBool(provider.backgroundEnabled) }));
|
.map((provider) => ({ ...provider, enabled: toBool(provider.enabled), backgroundEnabled: toBool(provider.backgroundEnabled) }));
|
||||||
const providerAuthProfiles = list<AppSnapshot["providerAuthProfiles"][number]>(this.db, "provider_auth_profiles", "created_at desc")
|
const providerAuthProfiles = list<AppSnapshot["providerAuthProfiles"][number]>(this.db, "provider_auth_profiles", "created_at desc")
|
||||||
.map((profile) => ({ ...profile, active: toBool(profile.active) }));
|
.map((profile) => ({ ...profile, active: toBool(profile.active) }));
|
||||||
|
const providerUsageWindows = list<ProviderUsageWindowRow>(this.db, "provider_usage_windows", "window_start desc");
|
||||||
|
const providerRateLimits = buildProviderRateLimits(providers, providerUsageWindows);
|
||||||
const skillBindings = list<AppSnapshot["skillBindings"][number]>(this.db, "skill_bindings", "priority asc")
|
const skillBindings = list<AppSnapshot["skillBindings"][number]>(this.db, "skill_bindings", "priority asc")
|
||||||
.map((binding) => ({ ...binding, enabled: toBool(binding.enabled) }));
|
.map((binding) => ({ ...binding, enabled: toBool(binding.enabled) }));
|
||||||
const files = list<AppSnapshot["files"][number]>(this.db, "file_objects", "created_at desc")
|
const files = list<AppSnapshot["files"][number]>(this.db, "file_objects", "created_at desc")
|
||||||
@@ -95,6 +102,7 @@ export class SnapshotService {
|
|||||||
providerAuthProfiles,
|
providerAuthProfiles,
|
||||||
providerCatalog: PROVIDER_CATALOG,
|
providerCatalog: PROVIDER_CATALOG,
|
||||||
providerRequests: list(this.db, "provider_requests", "created_at desc"),
|
providerRequests: list(this.db, "provider_requests", "created_at desc"),
|
||||||
|
providerRateLimits,
|
||||||
skillDefinitions: list(this.db, "skill_definitions", "created_at asc"),
|
skillDefinitions: list(this.db, "skill_definitions", "created_at asc"),
|
||||||
skillRevisions: list(this.db, "skill_revisions", "published_at desc"),
|
skillRevisions: list(this.db, "skill_revisions", "published_at desc"),
|
||||||
skillBindings,
|
skillBindings,
|
||||||
@@ -140,3 +148,30 @@ export class SnapshotService {
|
|||||||
return known[0] ?? null;
|
return known[0] ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildProviderRateLimits(
|
||||||
|
providers: AppSnapshot["providers"],
|
||||||
|
providerUsageWindows: ProviderUsageWindowRow[],
|
||||||
|
): AppSnapshot["providerRateLimits"] {
|
||||||
|
const window = currentProviderUsageWindow();
|
||||||
|
return providers.map((provider) => {
|
||||||
|
const usage = providerUsageWindows.find((item) => item.providerId === provider.id && item.windowStart === window.start);
|
||||||
|
const requestCount = usage?.requestCount ?? 0;
|
||||||
|
const inputCharCount = usage?.inputCharCount ?? 0;
|
||||||
|
const outputCharCount = usage?.outputCharCount ?? 0;
|
||||||
|
const remainingRequestCount = Math.max(0, provider.monthlyRequestLimit - requestCount);
|
||||||
|
return {
|
||||||
|
orgId: provider.orgId,
|
||||||
|
providerId: provider.id,
|
||||||
|
providerKey: provider.providerKey,
|
||||||
|
windowStart: usage?.windowStart ?? window.start,
|
||||||
|
windowEnd: usage?.windowEnd ?? window.end,
|
||||||
|
monthlyRequestLimit: provider.monthlyRequestLimit,
|
||||||
|
requestCount,
|
||||||
|
inputCharCount,
|
||||||
|
outputCharCount,
|
||||||
|
remainingRequestCount,
|
||||||
|
exhausted: remainingRequestCount === 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -65,6 +65,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
|
|||||||
const employees = useMemo(() => snapshot.employees.filter((item) => item.orgId === orgId), [snapshot.employees, orgId]);
|
const employees = useMemo(() => snapshot.employees.filter((item) => item.orgId === orgId), [snapshot.employees, orgId]);
|
||||||
const agents = useMemo(() => snapshot.agents.filter((item) => item.orgId === orgId), [snapshot.agents, orgId]);
|
const agents = useMemo(() => snapshot.agents.filter((item) => item.orgId === orgId), [snapshot.agents, orgId]);
|
||||||
const providers = useMemo(() => snapshot.providers.filter((item) => item.orgId === orgId), [snapshot.providers, orgId]);
|
const providers = useMemo(() => snapshot.providers.filter((item) => item.orgId === orgId), [snapshot.providers, orgId]);
|
||||||
|
const providerRateLimits = useMemo(() => snapshot.providerRateLimits.filter((item) => item.orgId === orgId), [snapshot.providerRateLimits, orgId]);
|
||||||
const skills = useMemo(() => snapshot.skillDefinitions.filter((item) => item.orgId === orgId), [snapshot.skillDefinitions, orgId]);
|
const skills = useMemo(() => snapshot.skillDefinitions.filter((item) => item.orgId === orgId), [snapshot.skillDefinitions, orgId]);
|
||||||
const skillRevisions = useMemo(() => {
|
const skillRevisions = useMemo(() => {
|
||||||
const skillIds = new Set(skills.map((skill) => skill.id));
|
const skillIds = new Set(skills.map((skill) => skill.id));
|
||||||
@@ -133,7 +134,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
|
|||||||
providers={providers}
|
providers={providers}
|
||||||
catalog={snapshot.providerCatalog}
|
catalog={snapshot.providerCatalog}
|
||||||
agents={agents}
|
agents={agents}
|
||||||
providerRequests={providerRequests}
|
providerRateLimits={providerRateLimits}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { Dialog } from "../../shared/Dialog";
|
|||||||
import type {
|
import type {
|
||||||
AgentRecord,
|
AgentRecord,
|
||||||
ProviderCatalogEntry,
|
ProviderCatalogEntry,
|
||||||
|
ProviderRateLimitStateRecord,
|
||||||
ProviderRecord,
|
ProviderRecord,
|
||||||
ProviderRequestRecord,
|
|
||||||
} from "../../../../shared/domain";
|
} from "../../../../shared/domain";
|
||||||
import { PROVIDER_DATA_POLICY } from "../../../../shared/domain";
|
import { PROVIDER_DATA_POLICY } from "../../../../shared/domain";
|
||||||
import { ProviderLogo } from "./ProviderLogo";
|
import { ProviderLogo } from "./ProviderLogo";
|
||||||
@@ -16,7 +16,7 @@ interface ProvidersPanelProps {
|
|||||||
providers: ProviderRecord[];
|
providers: ProviderRecord[];
|
||||||
catalog: ProviderCatalogEntry[];
|
catalog: ProviderCatalogEntry[];
|
||||||
agents: AgentRecord[];
|
agents: AgentRecord[];
|
||||||
providerRequests: ProviderRequestRecord[];
|
providerRateLimits: ProviderRateLimitStateRecord[];
|
||||||
actions: SnapshotActions;
|
actions: SnapshotActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ interface CloudFields {
|
|||||||
|
|
||||||
const CLOUD_DEFAULTS: CloudFields = { baseUrl: "", region: "", projectId: "", deployment: "" };
|
const CLOUD_DEFAULTS: CloudFields = { baseUrl: "", region: "", projectId: "", deployment: "" };
|
||||||
|
|
||||||
export function ProvidersPanel({ orgId, providers, catalog, agents, providerRequests, actions }: ProvidersPanelProps) {
|
export function ProvidersPanel({ orgId, providers, catalog, agents, providerRateLimits, actions }: ProvidersPanelProps) {
|
||||||
const [active, setActive] = useState<ProviderRecord | null>(null);
|
const [active, setActive] = useState<ProviderRecord | null>(null);
|
||||||
const [signInLaunched, setSignInLaunched] = useState(false);
|
const [signInLaunched, setSignInLaunched] = useState(false);
|
||||||
const connected = providers.filter((provider) => provider.enabled);
|
const connected = providers.filter((provider) => provider.enabled);
|
||||||
@@ -82,7 +82,7 @@ export function ProvidersPanel({ orgId, providers, catalog, agents, providerRequ
|
|||||||
<ProviderPolicyTable
|
<ProviderPolicyTable
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
providers={connected}
|
providers={connected}
|
||||||
providerRequests={providerRequests}
|
providerRateLimits={providerRateLimits}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -122,12 +122,12 @@ export function ProvidersPanel({ orgId, providers, catalog, agents, providerRequ
|
|||||||
function ProviderPolicyTable({
|
function ProviderPolicyTable({
|
||||||
orgId,
|
orgId,
|
||||||
providers,
|
providers,
|
||||||
providerRequests,
|
providerRateLimits,
|
||||||
actions,
|
actions,
|
||||||
}: {
|
}: {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
providers: ProviderRecord[];
|
providers: ProviderRecord[];
|
||||||
providerRequests: ProviderRequestRecord[];
|
providerRateLimits: ProviderRateLimitStateRecord[];
|
||||||
actions: SnapshotActions;
|
actions: SnapshotActions;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -140,16 +140,19 @@ function ProviderPolicyTable({
|
|||||||
>
|
>
|
||||||
<div>Provider</div><div>Data policy</div><div>Background</div><div>Monthly limit</div><div>Usage</div>
|
<div>Provider</div><div>Data policy</div><div>Background</div><div>Monthly limit</div><div>Usage</div>
|
||||||
</div>
|
</div>
|
||||||
{providers.map((provider, index) => (
|
{providers.map((provider, index) => {
|
||||||
<ProviderPolicyRow
|
const rateLimit = requireProviderRateLimit(provider, providerRateLimits);
|
||||||
key={`${provider.id}:${provider.dataPolicy}:${provider.backgroundEnabled}:${provider.monthlyRequestLimit}`}
|
return (
|
||||||
orgId={orgId}
|
<ProviderPolicyRow
|
||||||
provider={provider}
|
key={`${provider.id}:${provider.dataPolicy}:${provider.backgroundEnabled}:${provider.monthlyRequestLimit}`}
|
||||||
requests={providerRequests.filter((request) => request.providerId === provider.id && inCurrentUtcMonth(request.createdAt))}
|
orgId={orgId}
|
||||||
last={index === providers.length - 1}
|
provider={provider}
|
||||||
actions={actions}
|
rateLimit={rateLimit}
|
||||||
/>
|
last={index === providers.length - 1}
|
||||||
))}
|
actions={actions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -158,13 +161,13 @@ function ProviderPolicyTable({
|
|||||||
function ProviderPolicyRow({
|
function ProviderPolicyRow({
|
||||||
orgId,
|
orgId,
|
||||||
provider,
|
provider,
|
||||||
requests,
|
rateLimit,
|
||||||
last,
|
last,
|
||||||
actions,
|
actions,
|
||||||
}: {
|
}: {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
provider: ProviderRecord;
|
provider: ProviderRecord;
|
||||||
requests: ProviderRequestRecord[];
|
rateLimit: ProviderRateLimitStateRecord;
|
||||||
last: boolean;
|
last: boolean;
|
||||||
actions: SnapshotActions;
|
actions: SnapshotActions;
|
||||||
}) {
|
}) {
|
||||||
@@ -228,7 +231,7 @@ function ProviderPolicyRow({
|
|||||||
onChange={(event) => setMonthlyRequestLimit(event.target.value)}
|
onChange={(event) => setMonthlyRequestLimit(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<div className="num mono">{requests.length} / {provider.monthlyRequestLimit}</div>
|
<div className="num mono">{rateLimit.requestCount} / {rateLimit.monthlyRequestLimit}</div>
|
||||||
<button type="button" className="button small" aria-label={`Save ${provider.label} policy`} onClick={savePolicy}>
|
<button type="button" className="button small" aria-label={`Save ${provider.label} policy`} onClick={savePolicy}>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
@@ -439,8 +442,11 @@ function readableAuth(authType: ProviderRecord["authType"]): string {
|
|||||||
return authType;
|
return authType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function inCurrentUtcMonth(iso: string): boolean {
|
function requireProviderRateLimit(
|
||||||
const value = new Date(iso);
|
provider: ProviderRecord,
|
||||||
const now = new Date();
|
rateLimits: ProviderRateLimitStateRecord[],
|
||||||
return value.getUTCFullYear() === now.getUTCFullYear() && value.getUTCMonth() === now.getUTCMonth();
|
): ProviderRateLimitStateRecord {
|
||||||
|
const rateLimit = rateLimits.find((item) => item.providerId === provider.id);
|
||||||
|
if (!rateLimit) throw new Error(`Provider rate limit state missing for ${provider.id}.`);
|
||||||
|
return rateLimit;
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import type { ApprovalAssignmentRecord, ApprovalRoutingRuleRecord, HiringPolicyR
|
|||||||
import type { ChaosDrillRecord, ChaosDrillRunRecord, FeatureFlagRecord, OperationalEventRecord, SloTargetRecord } from "./operations-domain";
|
import type { ChaosDrillRecord, ChaosDrillRunRecord, FeatureFlagRecord, OperationalEventRecord, SloTargetRecord } from "./operations-domain";
|
||||||
import type { ProviderAuthProfileRecord } from "./provider-auth-domain";
|
import type { ProviderAuthProfileRecord } from "./provider-auth-domain";
|
||||||
import type { ProviderDataPolicy, ProviderExecutionMode } from "./provider-policy-domain";
|
import type { ProviderDataPolicy, ProviderExecutionMode } from "./provider-policy-domain";
|
||||||
|
import type { ProviderRateLimitStateRecord } from "./provider-runtime-domain";
|
||||||
import type { RollbackSnapshotRecord } from "./rollback-domain";
|
import type { RollbackSnapshotRecord } from "./rollback-domain";
|
||||||
import type { EvalCaseRecord, EvalRunRecord, EvalSuiteRecord, PrBotGateRecord, SdlcReviewAgentRecord, SdlcReviewRunRecord } from "./sdlc-domain";
|
import type { EvalCaseRecord, EvalRunRecord, EvalSuiteRecord, PrBotGateRecord, SdlcReviewAgentRecord, SdlcReviewRunRecord } from "./sdlc-domain";
|
||||||
export { BOARD_INTERVENTION_ACTION, BOARD_INTERVENTION_MODE, CHAT_THREAD_BOARD_STATE, type BoardInterventionAction, type BoardInterventionMode, type BoardInterventionRecord, type ChatThreadBoardState } from "./board-domain";
|
export { BOARD_INTERVENTION_ACTION, BOARD_INTERVENTION_MODE, CHAT_THREAD_BOARD_STATE, type BoardInterventionAction, type BoardInterventionMode, type BoardInterventionRecord, type ChatThreadBoardState } from "./board-domain";
|
||||||
@@ -16,6 +17,7 @@ export { ROUTE_TO_TYPE, type ApprovalAssignmentRecord, type ApprovalRoutingRuleR
|
|||||||
export { CHAOS_DRILL_SCENARIO, CHAOS_DRILL_STATE, FEATURE_FLAG_KEY, SLO_SIGNAL_KEY, type ChaosDrillRecord, type ChaosDrillRunRecord, type ChaosDrillScenario, type ChaosDrillState, type FeatureFlagKey, type FeatureFlagRecord, type OperationalEventRecord, type SloSignalKey, type SloTargetRecord } from "./operations-domain";
|
export { CHAOS_DRILL_SCENARIO, CHAOS_DRILL_STATE, FEATURE_FLAG_KEY, SLO_SIGNAL_KEY, type ChaosDrillRecord, type ChaosDrillRunRecord, type ChaosDrillScenario, type ChaosDrillState, type FeatureFlagKey, type FeatureFlagRecord, type OperationalEventRecord, type SloSignalKey, type SloTargetRecord } from "./operations-domain";
|
||||||
export type { ProviderAuthProfileRecord } from "./provider-auth-domain";
|
export type { ProviderAuthProfileRecord } from "./provider-auth-domain";
|
||||||
export { PROVIDER_DATA_POLICY, PROVIDER_EXECUTION_MODE, type ProviderDataPolicy, type ProviderExecutionMode } from "./provider-policy-domain";
|
export { PROVIDER_DATA_POLICY, PROVIDER_EXECUTION_MODE, type ProviderDataPolicy, type ProviderExecutionMode } from "./provider-policy-domain";
|
||||||
|
export type { ProviderRateLimitStateRecord } from "./provider-runtime-domain";
|
||||||
export type { RollbackSnapshotRecord } from "./rollback-domain";
|
export type { RollbackSnapshotRecord } from "./rollback-domain";
|
||||||
export { EVAL_SUITE_KEY, PR_BOT_GATE_KEY, SDLC_REVIEW_AGENT_KEY, SDLC_REVIEW_TARGET_TYPE, SDLC_RUN_STATE, type EvalCaseRecord, type EvalRunRecord, type EvalSuiteKey, type EvalSuiteRecord, type PrBotGateKey, type PrBotGateRecord, type SdlcReviewAgentKey, type SdlcReviewAgentRecord, type SdlcReviewRunRecord, type SdlcReviewTargetType, type SdlcRunState } from "./sdlc-domain";
|
export { EVAL_SUITE_KEY, PR_BOT_GATE_KEY, SDLC_REVIEW_AGENT_KEY, SDLC_REVIEW_TARGET_TYPE, SDLC_RUN_STATE, type EvalCaseRecord, type EvalRunRecord, type EvalSuiteKey, type EvalSuiteRecord, type PrBotGateKey, type PrBotGateRecord, type SdlcReviewAgentKey, type SdlcReviewAgentRecord, type SdlcReviewRunRecord, type SdlcReviewTargetType, type SdlcRunState } from "./sdlc-domain";
|
||||||
export { SEARCH_ENTITY_TYPES, type SearchEntityType, type SearchResultRecord } from "./search-domain";
|
export { SEARCH_ENTITY_TYPES, type SearchEntityType, type SearchResultRecord } from "./search-domain";
|
||||||
@@ -470,6 +472,7 @@ export interface AppSnapshot {
|
|||||||
providerAuthProfiles: ProviderAuthProfileRecord[];
|
providerAuthProfiles: ProviderAuthProfileRecord[];
|
||||||
providerCatalog: ProviderCatalogEntry[];
|
providerCatalog: ProviderCatalogEntry[];
|
||||||
providerRequests: ProviderRequestRecord[];
|
providerRequests: ProviderRequestRecord[];
|
||||||
|
providerRateLimits: ProviderRateLimitStateRecord[];
|
||||||
skillDefinitions: SkillDefinitionRecord[];
|
skillDefinitions: SkillDefinitionRecord[];
|
||||||
skillRevisions: SkillRevisionRecord[];
|
skillRevisions: SkillRevisionRecord[];
|
||||||
skillBindings: SkillBindingRecord[];
|
skillBindings: SkillBindingRecord[];
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export interface ProviderRateLimitStateRecord {
|
||||||
|
orgId: string;
|
||||||
|
providerId: string;
|
||||||
|
providerKey: string;
|
||||||
|
windowStart: string;
|
||||||
|
windowEnd: string;
|
||||||
|
monthlyRequestLimit: number;
|
||||||
|
requestCount: number;
|
||||||
|
inputCharCount: number;
|
||||||
|
outputCharCount: number;
|
||||||
|
remainingRequestCount: number;
|
||||||
|
exhausted: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function currentProviderUsageWindow(now = new Date()): { start: string; end: string } {
|
||||||
|
const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
|
||||||
|
const end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1));
|
||||||
|
return { start: start.toISOString(), end: end.toISOString() };
|
||||||
|
}
|
||||||
@@ -212,13 +212,23 @@ describe("provider service", () => {
|
|||||||
where org_id = ? and provider_id = ?
|
where org_id = ? and provider_id = ?
|
||||||
`).get(orgId, openAiProviderId) as { request_count: number; input_char_count: number; output_char_count: number };
|
`).get(orgId, openAiProviderId) as { request_count: number; input_char_count: number; output_char_count: number };
|
||||||
expect(usage).toEqual({ request_count: 1, input_char_count: 16, output_char_count: 25 });
|
expect(usage).toEqual({ request_count: 1, input_char_count: 16, output_char_count: 25 });
|
||||||
expect(snapshot.load().providerRequests[0]).toMatchObject({
|
const state = snapshot.load();
|
||||||
|
expect(state.providerRequests[0]).toMatchObject({
|
||||||
providerId: openAiProviderId,
|
providerId: openAiProviderId,
|
||||||
inputCharCount: 16,
|
inputCharCount: 16,
|
||||||
outputCharCount: 25,
|
outputCharCount: 25,
|
||||||
executionMode: PROVIDER_EXECUTION_MODE.FOREGROUND,
|
executionMode: PROVIDER_EXECUTION_MODE.FOREGROUND,
|
||||||
policyDecision: "allowed:standard",
|
policyDecision: "allowed:standard",
|
||||||
});
|
});
|
||||||
|
expect(state.providerRateLimits.find((item) => item.providerId === openAiProviderId)).toMatchObject({
|
||||||
|
providerId: openAiProviderId,
|
||||||
|
monthlyRequestLimit: 1,
|
||||||
|
requestCount: 1,
|
||||||
|
inputCharCount: 16,
|
||||||
|
outputCharCount: 25,
|
||||||
|
remainingRequestCount: 0,
|
||||||
|
exhausted: true,
|
||||||
|
});
|
||||||
connection.close();
|
connection.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user