Skip to content

src/quota.ts

69 lines · 2.8 KB · TypeScript
Raw
/** Pure outbound-fetch quota helpers (no kody imports; unit-testable). */

/** Account usage for `outbound_fetches_per_day` (from `usageGet`). */
export type FetchQuota = {
	current: number
	limit: number
	percent: number
	day: string | null
}

export type QuotaLevel = 'ok' | 'warn' | 'critical'

/** At or above this share of the daily cap, tick backs off and warns. */
export const WARN_PERCENT = 80
/** At or above this share, tick tells agents to pause non-essential ticks. */
export const CRITICAL_PERCENT = 95
export const WARN_MIN_POLL_SECONDS = 300
export const CRITICAL_MIN_POLL_SECONDS = 900

/** Pick `outbound_fetches_per_day` out of a `usageGet` result. */
export function readFetchQuota(usage: unknown): FetchQuota | null {
	const u = usage as { day?: unknown; resources?: Array<Record<string, unknown>> } | null
	const row = Array.isArray(u?.resources)
		? u!.resources!.find((r) => r?.resource === 'outbound_fetches_per_day')
		: undefined
	const current = Number(row?.current)
	const limit = Number(row?.limit)
	if (!row || !Number.isFinite(current) || !Number.isFinite(limit) || limit <= 0) return null
	return {
		current,
		limit,
		percent: Math.round((current / limit) * 1000) / 10,
		day: typeof u?.day === 'string' ? u.day : null,
	}
}

export function quotaLevel(quota: FetchQuota | null): QuotaLevel {
	if (!quota) return 'ok'
	if (quota.percent >= CRITICAL_PERCENT) return 'critical'
	if (quota.percent >= WARN_PERCENT) return 'warn'
	return 'ok'
}

/** Raise the poll interval near the cap; never lowers it. */
export function adjustPoll(pollAfterSeconds: number | undefined, level: QuotaLevel): number | undefined {
	if (pollAfterSeconds == null || level === 'ok') return pollAfterSeconds
	const floor = level === 'critical' ? CRITICAL_MIN_POLL_SECONDS : WARN_MIN_POLL_SECONDS
	return Math.max(pollAfterSeconds, floor)
}

export function quotaWarning(quota: FetchQuota, level: QuotaLevel): string | null {
	if (level === 'ok') return null
	const used = `${quota.current.toLocaleString('en-US')} of ${quota.limit.toLocaleString('en-US')} outbound fetches (${quota.percent}%) used today (UTC); the cap resets at 00:00 UTC`
	return level === 'critical'
		? `${used}. Pause ticks on PRs that are only waiting, and spend what is left on merges and review replies.`
		: `${used}. Tick slower (pollAfterSeconds is raised) and avoid ticking PRs that are only waiting.`
}

/** Kody's daily plan-cap refusal ("Plan limit reached …"). */
export function isPlanLimitError(error: unknown): boolean {
	const message = error instanceof Error ? error.message : String(error ?? '')
	return /plan limit reached/i.test(message)
}

/** Next 00:00 UTC after `nowMs` (when daily caps reset). */
export function nextUtcMidnight(nowMs: number): Date {
	const d = new Date(nowMs)
	return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 1))
}