Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.6 KiB
TypeScript
68 lines
2.6 KiB
TypeScript
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 interface ProviderObservedRateLimitState {
|
|
requestLimit: number | null;
|
|
remainingRequests: number | null;
|
|
resetAt: string | null;
|
|
retryAfterMs: number | null;
|
|
rawStateJson: string;
|
|
}
|
|
|
|
export interface ProviderRateLimitObservationRecord extends ProviderObservedRateLimitState {
|
|
id: string;
|
|
orgId: string;
|
|
providerId: string;
|
|
providerKey: string;
|
|
model: string | null;
|
|
observedAt: string;
|
|
}
|
|
|
|
/**
|
|
* Exact message recorded on runs blocked by a provider's monthly usage window.
|
|
* The heartbeat scheduler matches task_runs.error_message against this string
|
|
* so quota-blocked attempts never consume a task's retry budget — the text is
|
|
* a cross-module contract and also matches rows written by earlier builds
|
|
* that inlined it.
|
|
*/
|
|
export const PROVIDER_MONTHLY_LIMIT_MESSAGE = "Provider monthly request limit has been reached.";
|
|
|
|
/**
|
|
* Failure-message signatures of transient provider conditions — the account's
|
|
* usage window filling up, rate limiting, or momentary overload. These verdicts
|
|
* come from the provider's side of the wire and say nothing about the task, so
|
|
* schedulers must treat them as "try again later", never as attempts consumed.
|
|
* Matched case-insensitively against recorded task_runs.error_message rows,
|
|
* which also heals historical rows written before this classification existed.
|
|
*/
|
|
export const TRANSIENT_PROVIDER_FAILURE_SIGNATURES = [
|
|
PROVIDER_MONTHLY_LIMIT_MESSAGE,
|
|
"usage limit", // ChatGPT-account plans: "The usage limit has been reached"
|
|
"rate limit",
|
|
"too many requests", // HTTP 429 status text
|
|
"overloaded", // "Our servers are currently overloaded. Please try again later."
|
|
].map((signature) => signature.toLowerCase());
|
|
|
|
export function isTransientProviderFailure(message: string | null | undefined): boolean {
|
|
if (!message) return false;
|
|
const normalized = message.toLowerCase();
|
|
return TRANSIENT_PROVIDER_FAILURE_SIGNATURES.some((signature) => normalized.includes(signature));
|
|
}
|
|
|
|
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() };
|
|
}
|