rate-budget 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculatePerRequestTokenBudget = calculatePerRequestTokenBudget;
4
+ exports.calculateTokenBudgetPortion = calculateTokenBudgetPortion;
5
+ exports.distributeFlexibleTokenBudget = distributeFlexibleTokenBudget;
6
+ const limits_js_1 = require("./limits.js");
7
+ function normalizeRatio(value) {
8
+ if (!Number.isFinite(value)) {
9
+ return 0;
10
+ }
11
+ return Math.min(1, Math.max(0, value));
12
+ }
13
+ function calculatePerRequestTokenBudget(input) {
14
+ const tokensPerWindow = (0, limits_js_1.toPositiveInteger)(input.tokensPerWindow);
15
+ const requestSpacingMs = (0, limits_js_1.toPositiveInteger)(input.requestSpacingMs);
16
+ const windowMs = (0, limits_js_1.toPositiveInteger)(input.windowMs ?? limits_js_1.DEFAULT_LIMIT_WINDOW_MS, limits_js_1.DEFAULT_LIMIT_WINDOW_MS);
17
+ const requestsPerWindow = windowMs / requestSpacingMs;
18
+ const minimumTokens = (0, limits_js_1.toPositiveInteger)(input.minimumTokens ?? 1);
19
+ return Math.max(minimumTokens, Math.floor(tokensPerWindow / Math.max(1, requestsPerWindow)));
20
+ }
21
+ function calculateTokenBudgetPortion(totalBudget, ratio) {
22
+ return Math.max(1, Math.floor((0, limits_js_1.toPositiveInteger)(totalBudget) * normalizeRatio(ratio)));
23
+ }
24
+ function distributeFlexibleTokenBudget(input) {
25
+ const parentBudget = (0, limits_js_1.toPositiveInteger)(input.parentBudget);
26
+ let remainingTokens = (0, limits_js_1.toNonNegativeInteger)(input.availableTokens);
27
+ const allocations = Object.create(null);
28
+ for (const partition of input.partitions) {
29
+ const targetBudget = Math.floor(parentBudget * normalizeRatio(partition.targetRatio));
30
+ const allocated = Math.min(targetBudget, remainingTokens);
31
+ allocations[partition.name] = allocated;
32
+ remainingTokens -= allocated;
33
+ }
34
+ for (const partition of input.partitions) {
35
+ if (remainingTokens <= 0) {
36
+ break;
37
+ }
38
+ const currentAllocation = allocations[partition.name] ?? 0;
39
+ const maxBudget = Math.max(currentAllocation, Math.floor(parentBudget * normalizeRatio(partition.maxRatio)));
40
+ const additionalTokens = Math.min(maxBudget - currentAllocation, remainingTokens);
41
+ allocations[partition.name] = currentAllocation + additionalTokens;
42
+ remainingTokens -= additionalTokens;
43
+ }
44
+ return allocations;
45
+ }
46
+ //# sourceMappingURL=token-budget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-budget.js","sourceRoot":"","sources":["../../src/token-budget.ts"],"names":[],"mappings":";;AA2BA,wEAgBC;AAED,kEAQC;AAED,sEAsCC;AA7FD,2CAIqB;AAerB,SAAS,cAAc,CAAC,KAAa;IACnC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,8BAA8B,CAC5C,KAAiC;IAEjC,MAAM,eAAe,GAAG,IAAA,6BAAiB,EAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IACjE,MAAM,gBAAgB,GAAG,IAAA,6BAAiB,EAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACnE,MAAM,QAAQ,GAAG,IAAA,6BAAiB,EAChC,KAAK,CAAC,QAAQ,IAAI,mCAAuB,EACzC,mCAAuB,CACxB,CAAC;IACF,MAAM,iBAAiB,GAAG,QAAQ,GAAG,gBAAgB,CAAC;IACtD,MAAM,aAAa,GAAG,IAAA,6BAAiB,EAAC,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC,CAAC;IAElE,OAAO,IAAI,CAAC,GAAG,CACb,aAAa,EACb,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAC7D,CAAC;AACJ,CAAC;AAED,SAAgB,2BAA2B,CACzC,WAAmB,EACnB,KAAa;IAEb,OAAO,IAAI,CAAC,GAAG,CACb,CAAC,EACD,IAAI,CAAC,KAAK,CAAC,IAAA,6BAAiB,EAAC,WAAW,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CACnE,CAAC;AACJ,CAAC;AAED,SAAgB,6BAA6B,CAAsB,KAIlE;IACC,MAAM,YAAY,GAAG,IAAA,6BAAiB,EAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3D,IAAI,eAAe,GAAG,IAAA,gCAAoB,EAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAClE,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAyB,CAAC;IAEhE,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACzC,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAC7B,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,WAAW,CAAC,CACrD,CAAC;QACF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;QAC1D,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;QACxC,eAAe,IAAI,SAAS,CAAC;IAC/B,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QACzC,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM;QACR,CAAC;QAED,MAAM,iBAAiB,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CACxB,iBAAiB,EACjB,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAC9D,CAAC;QACF,MAAM,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAC/B,SAAS,GAAG,iBAAiB,EAC7B,eAAe,CAChB,CAAC;QAEF,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,iBAAiB,GAAG,gBAAgB,CAAC;QACnE,eAAe,IAAI,gBAAgB,CAAC;IACtC,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from "./limits.js";
2
+ export * from "./memory-cooldown.js";
3
+ export * from "./retry.js";
4
+ export * from "./token-budget.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from "./limits.js";
2
+ export * from "./memory-cooldown.js";
3
+ export * from "./retry.js";
4
+ export * from "./token-budget.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,91 @@
1
+ export declare const DEFAULT_LIMIT_WINDOW_MS = 60000;
2
+ export declare const DEFAULT_LIMIT_SAFETY_BUFFER_MS = 1000;
3
+ export declare const DEFAULT_INACTIVE_COOLDOWN_MULTIPLIER = 2;
4
+ export type TimestampInput = Date | number | string | null | undefined;
5
+ export type LimitWindowOptions = {
6
+ windowMs?: number;
7
+ safetyBufferMs?: number;
8
+ };
9
+ export type RequestLimitProfileInput = {
10
+ rpmLimit: number;
11
+ tpmLimit: number;
12
+ rpdLimit: number;
13
+ dailyThresholdPercent?: number;
14
+ windowMs?: number;
15
+ safetyBufferMs?: number;
16
+ inactiveCooldownMultiplier?: number;
17
+ };
18
+ export type RequestLimitProfile = {
19
+ rpmLimit: number;
20
+ tpmLimit: number;
21
+ rpdLimit: number;
22
+ dailyThresholdPercent: number;
23
+ dailyThresholdRequests: number;
24
+ cooldownMs: number;
25
+ inactiveCooldownMs: number;
26
+ };
27
+ export type CountLimitDecision = {
28
+ allowed: true;
29
+ usedCount: number;
30
+ thresholdCount: number;
31
+ remainingCount: number;
32
+ } | {
33
+ allowed: false;
34
+ usedCount: number;
35
+ thresholdCount: number;
36
+ remainingCount: 0;
37
+ };
38
+ export type WindowUsageEntry = {
39
+ occurredAt: TimestampInput;
40
+ cost: number;
41
+ };
42
+ export type WindowUsageInput = LimitWindowOptions & {
43
+ currentCost?: number;
44
+ incomingCost: number;
45
+ limit: number;
46
+ entries: readonly WindowUsageEntry[];
47
+ now?: number;
48
+ entriesSortedOldestFirst?: boolean;
49
+ };
50
+ export type WindowUsageDecision = {
51
+ allowed: true;
52
+ reason: "within_limit";
53
+ projectedCost: number;
54
+ limit: number;
55
+ retryAfterMs: 0;
56
+ } | {
57
+ allowed: false;
58
+ reason: "single_request_exceeds_limit";
59
+ projectedCost: number;
60
+ limit: number;
61
+ retryAfterMs: null;
62
+ } | {
63
+ allowed: false;
64
+ reason: "window_exhausted";
65
+ projectedCost: number;
66
+ limit: number;
67
+ retryAfterMs: number;
68
+ };
69
+ export declare function toPositiveInteger(value: number, fallback?: number): number;
70
+ export declare function toNonNegativeInteger(value: number, fallback?: number): number;
71
+ export declare function clampPercent(value: number, fallback?: number): number;
72
+ export declare function toTimestampMs(value: TimestampInput): number | null;
73
+ export declare function calculateLimitSpacingMs(limitPerWindow: number, options?: LimitWindowOptions): number;
74
+ export declare function calculateInactiveLimitSpacingMs(limitPerWindow: number, options?: LimitWindowOptions & {
75
+ multiplier?: number;
76
+ }): number;
77
+ export declare function calculateThresholdCount(limit: number, thresholdPercent?: number): number;
78
+ export declare function evaluateCountLimit(input: {
79
+ usedCount: number;
80
+ limit: number;
81
+ thresholdPercent?: number;
82
+ }): CountLimitDecision;
83
+ export declare function calculateCooldownWaitMs(input: {
84
+ lastAcceptedAt: TimestampInput;
85
+ cooldownMs: number;
86
+ now?: number;
87
+ }): number;
88
+ export declare function evaluateWindowUsage(input: WindowUsageInput): WindowUsageDecision;
89
+ export declare function calculateWindowUsageWaitMs(input: WindowUsageInput): number;
90
+ export declare function buildRequestLimitProfile(input: RequestLimitProfileInput): RequestLimitProfile;
91
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/limits.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,170 @@
1
+ export const DEFAULT_LIMIT_WINDOW_MS = 60000;
2
+ export const DEFAULT_LIMIT_SAFETY_BUFFER_MS = 1000;
3
+ export const DEFAULT_INACTIVE_COOLDOWN_MULTIPLIER = 2;
4
+ function normalizeFiniteNumber(value, fallback) {
5
+ return Number.isFinite(value) ? value : fallback;
6
+ }
7
+ export function toPositiveInteger(value, fallback = 1) {
8
+ return Math.max(1, Math.floor(normalizeFiniteNumber(value, fallback)));
9
+ }
10
+ export function toNonNegativeInteger(value, fallback = 0) {
11
+ return Math.max(0, Math.floor(normalizeFiniteNumber(value, fallback)));
12
+ }
13
+ export function clampPercent(value, fallback = 100) {
14
+ const safeValue = normalizeFiniteNumber(value, fallback);
15
+ return Math.min(100, Math.max(0, safeValue));
16
+ }
17
+ export function toTimestampMs(value) {
18
+ if (value === null || value === undefined) {
19
+ return null;
20
+ }
21
+ if (typeof value === "number") {
22
+ return Number.isFinite(value) ? value : null;
23
+ }
24
+ const timestamp = value instanceof Date ? value.getTime() : Date.parse(value);
25
+ return Number.isFinite(timestamp) ? timestamp : null;
26
+ }
27
+ export function calculateLimitSpacingMs(limitPerWindow, options = {}) {
28
+ const limit = toPositiveInteger(limitPerWindow);
29
+ const windowMs = toPositiveInteger(options.windowMs ?? DEFAULT_LIMIT_WINDOW_MS, DEFAULT_LIMIT_WINDOW_MS);
30
+ const safetyBufferMs = toNonNegativeInteger(options.safetyBufferMs ?? DEFAULT_LIMIT_SAFETY_BUFFER_MS, DEFAULT_LIMIT_SAFETY_BUFFER_MS);
31
+ return Math.ceil(windowMs / limit + safetyBufferMs);
32
+ }
33
+ export function calculateInactiveLimitSpacingMs(limitPerWindow, options = {}) {
34
+ const multiplier = Math.max(1, normalizeFiniteNumber(options.multiplier ?? DEFAULT_INACTIVE_COOLDOWN_MULTIPLIER, DEFAULT_INACTIVE_COOLDOWN_MULTIPLIER));
35
+ return Math.ceil(calculateLimitSpacingMs(limitPerWindow, options) * multiplier);
36
+ }
37
+ export function calculateThresholdCount(limit, thresholdPercent = 100) {
38
+ const safeLimit = toPositiveInteger(limit);
39
+ const safeThresholdPercent = clampPercent(thresholdPercent);
40
+ return Math.max(1, Math.ceil(safeLimit * (safeThresholdPercent / 100)));
41
+ }
42
+ export function evaluateCountLimit(input) {
43
+ const usedCount = toNonNegativeInteger(input.usedCount);
44
+ const thresholdCount = calculateThresholdCount(input.limit, input.thresholdPercent);
45
+ const remainingCount = Math.max(0, thresholdCount - usedCount);
46
+ if (remainingCount <= 0) {
47
+ return {
48
+ allowed: false,
49
+ usedCount,
50
+ thresholdCount,
51
+ remainingCount: 0,
52
+ };
53
+ }
54
+ return {
55
+ allowed: true,
56
+ usedCount,
57
+ thresholdCount,
58
+ remainingCount,
59
+ };
60
+ }
61
+ export function calculateCooldownWaitMs(input) {
62
+ const lastAcceptedAt = toTimestampMs(input.lastAcceptedAt);
63
+ if (lastAcceptedAt === null) {
64
+ return 0;
65
+ }
66
+ const now = normalizeFiniteNumber(input.now ?? Date.now(), Date.now());
67
+ const cooldownMs = toNonNegativeInteger(input.cooldownMs);
68
+ return Math.max(0, cooldownMs - (now - lastAcceptedAt));
69
+ }
70
+ function normalizeWindowEntries(entries, entriesSortedOldestFirst, input) {
71
+ const normalized = entries.flatMap((entry) => {
72
+ const occurredAtMs = toTimestampMs(entry.occurredAt);
73
+ if (occurredAtMs === null ||
74
+ occurredAtMs < input.now - input.windowMs ||
75
+ occurredAtMs > input.now) {
76
+ return [];
77
+ }
78
+ return [
79
+ {
80
+ occurredAtMs,
81
+ cost: toNonNegativeInteger(entry.cost),
82
+ },
83
+ ];
84
+ });
85
+ if (entriesSortedOldestFirst) {
86
+ return normalized;
87
+ }
88
+ return normalized.sort((left, right) => left.occurredAtMs - right.occurredAtMs);
89
+ }
90
+ export function evaluateWindowUsage(input) {
91
+ const limit = toPositiveInteger(input.limit);
92
+ const incomingCost = toNonNegativeInteger(input.incomingCost);
93
+ const now = normalizeFiniteNumber(input.now ?? Date.now(), Date.now());
94
+ const windowMs = toPositiveInteger(input.windowMs ?? DEFAULT_LIMIT_WINDOW_MS, DEFAULT_LIMIT_WINDOW_MS);
95
+ const entries = normalizeWindowEntries(input.entries, input.entriesSortedOldestFirst ?? true, { now, windowMs });
96
+ const currentCost = input.currentCost === undefined
97
+ ? entries.reduce((sum, entry) => sum + entry.cost, 0)
98
+ : toNonNegativeInteger(input.currentCost);
99
+ const projectedCost = currentCost + incomingCost;
100
+ if (incomingCost > limit) {
101
+ return {
102
+ allowed: false,
103
+ reason: "single_request_exceeds_limit",
104
+ projectedCost,
105
+ limit,
106
+ retryAfterMs: null,
107
+ };
108
+ }
109
+ if (projectedCost <= limit) {
110
+ return {
111
+ allowed: true,
112
+ reason: "within_limit",
113
+ projectedCost,
114
+ limit,
115
+ retryAfterMs: 0,
116
+ };
117
+ }
118
+ const safetyBufferMs = toNonNegativeInteger(input.safetyBufferMs ?? DEFAULT_LIMIT_SAFETY_BUFFER_MS, DEFAULT_LIMIT_SAFETY_BUFFER_MS);
119
+ let remainingCost = projectedCost;
120
+ for (const entry of entries) {
121
+ remainingCost -= entry.cost;
122
+ if (remainingCost <= limit) {
123
+ return {
124
+ allowed: false,
125
+ reason: "window_exhausted",
126
+ projectedCost,
127
+ limit,
128
+ retryAfterMs: Math.max(0, windowMs - (now - entry.occurredAtMs) + safetyBufferMs),
129
+ };
130
+ }
131
+ }
132
+ return {
133
+ allowed: false,
134
+ reason: "window_exhausted",
135
+ projectedCost,
136
+ limit,
137
+ retryAfterMs: windowMs,
138
+ };
139
+ }
140
+ export function calculateWindowUsageWaitMs(input) {
141
+ const decision = evaluateWindowUsage(input);
142
+ if (decision.allowed) {
143
+ return 0;
144
+ }
145
+ return (decision.retryAfterMs ??
146
+ toPositiveInteger(input.windowMs ?? DEFAULT_LIMIT_WINDOW_MS, DEFAULT_LIMIT_WINDOW_MS));
147
+ }
148
+ export function buildRequestLimitProfile(input) {
149
+ const rpmLimit = toPositiveInteger(input.rpmLimit);
150
+ const tpmLimit = toPositiveInteger(input.tpmLimit);
151
+ const rpdLimit = toPositiveInteger(input.rpdLimit);
152
+ const dailyThresholdPercent = clampPercent(input.dailyThresholdPercent ?? 100);
153
+ const cooldownOptions = {
154
+ windowMs: input.windowMs,
155
+ safetyBufferMs: input.safetyBufferMs,
156
+ };
157
+ return {
158
+ rpmLimit,
159
+ tpmLimit,
160
+ rpdLimit,
161
+ dailyThresholdPercent,
162
+ dailyThresholdRequests: calculateThresholdCount(rpdLimit, dailyThresholdPercent),
163
+ cooldownMs: calculateLimitSpacingMs(rpmLimit, cooldownOptions),
164
+ inactiveCooldownMs: calculateInactiveLimitSpacingMs(rpmLimit, {
165
+ ...cooldownOptions,
166
+ multiplier: input.inactiveCooldownMultiplier,
167
+ }),
168
+ };
169
+ }
170
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/limits.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ export type ActorCooldownState = {
2
+ lastAcceptedAt: number;
3
+ lastActorId: string;
4
+ };
5
+ export type ActorCooldownDecision = {
6
+ allowed: true;
7
+ state: ActorCooldownState;
8
+ } | {
9
+ allowed: false;
10
+ retryAfterMs: number;
11
+ state: ActorCooldownState;
12
+ };
13
+ export type ActorCooldownOptions = {
14
+ sameActorCooldownMs: number;
15
+ differentActorCooldownMs: number;
16
+ stateTtlMs?: number;
17
+ cleanupInterval?: number;
18
+ };
19
+ export declare function consumeActorCooldown(currentState: ActorCooldownState | null | undefined, input: {
20
+ actorId: string;
21
+ now?: number;
22
+ sameActorCooldownMs: number;
23
+ differentActorCooldownMs: number;
24
+ }): ActorCooldownDecision;
25
+ export declare class InMemoryActorCooldownLimiter {
26
+ private readonly states;
27
+ private readonly sameActorCooldownMs;
28
+ private readonly differentActorCooldownMs;
29
+ private readonly stateTtlMs;
30
+ private readonly cleanupInterval;
31
+ private touches;
32
+ constructor(options: ActorCooldownOptions);
33
+ consume(scopeId: string, actorId: string, now?: number): ActorCooldownDecision;
34
+ reset(scopeId?: string): void;
35
+ size(): number;
36
+ private cleanup;
37
+ }
38
+ //# sourceMappingURL=memory-cooldown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-cooldown.d.ts","sourceRoot":"","sources":["../../src/memory-cooldown.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAC7B;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAA;CAAE,GAC5C;IACE,OAAO,EAAE,KAAK,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,kBAAkB,CAAC;CAC3B,CAAC;AAEN,MAAM,MAAM,oBAAoB,GAAG;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,wBAAwB,EAAE,MAAM,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EACnD,KAAK,EAAE;IACL,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mBAAmB,EAAE,MAAM,CAAC;IAC5B,wBAAwB,EAAE,MAAM,CAAC;CAClC,GACA,qBAAqB,CAkCvB;AAED,qBAAa,4BAA4B;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAChE,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAS;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,OAAO,CAAK;gBAER,OAAO,EAAE,oBAAoB;IAYzC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,SAAa;IAiB1D,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM;IAStB,IAAI;IAIJ,OAAO,CAAC,OAAO;CAahB"}
@@ -0,0 +1,76 @@
1
+ import { toNonNegativeInteger } from "./limits.js";
2
+ export function consumeActorCooldown(currentState, input) {
3
+ const now = Number.isFinite(input.now) ? input.now ?? Date.now() : Date.now();
4
+ if (!currentState) {
5
+ return {
6
+ allowed: true,
7
+ state: {
8
+ lastAcceptedAt: now,
9
+ lastActorId: input.actorId,
10
+ },
11
+ };
12
+ }
13
+ const requiredCooldownMs = currentState.lastActorId === input.actorId
14
+ ? toNonNegativeInteger(input.sameActorCooldownMs)
15
+ : toNonNegativeInteger(input.differentActorCooldownMs);
16
+ const elapsedMs = now - currentState.lastAcceptedAt;
17
+ if (elapsedMs < requiredCooldownMs) {
18
+ return {
19
+ allowed: false,
20
+ retryAfterMs: requiredCooldownMs - elapsedMs,
21
+ state: currentState,
22
+ };
23
+ }
24
+ return {
25
+ allowed: true,
26
+ state: {
27
+ lastAcceptedAt: now,
28
+ lastActorId: input.actorId,
29
+ },
30
+ };
31
+ }
32
+ export class InMemoryActorCooldownLimiter {
33
+ constructor(options) {
34
+ this.states = new Map();
35
+ this.touches = 0;
36
+ this.sameActorCooldownMs = toNonNegativeInteger(options.sameActorCooldownMs);
37
+ this.differentActorCooldownMs = toNonNegativeInteger(options.differentActorCooldownMs);
38
+ this.stateTtlMs = toNonNegativeInteger(options.stateTtlMs ?? 5 * 60000);
39
+ this.cleanupInterval = Math.max(1, toNonNegativeInteger(options.cleanupInterval ?? 256, 256));
40
+ }
41
+ consume(scopeId, actorId, now = Date.now()) {
42
+ this.cleanup(now);
43
+ const decision = consumeActorCooldown(this.states.get(scopeId), {
44
+ actorId,
45
+ now,
46
+ sameActorCooldownMs: this.sameActorCooldownMs,
47
+ differentActorCooldownMs: this.differentActorCooldownMs,
48
+ });
49
+ if (decision.allowed) {
50
+ this.states.set(scopeId, decision.state);
51
+ }
52
+ return decision;
53
+ }
54
+ reset(scopeId) {
55
+ if (scopeId) {
56
+ this.states.delete(scopeId);
57
+ return;
58
+ }
59
+ this.states.clear();
60
+ }
61
+ size() {
62
+ return this.states.size;
63
+ }
64
+ cleanup(now) {
65
+ this.touches += 1;
66
+ if (this.touches % this.cleanupInterval !== 0) {
67
+ return;
68
+ }
69
+ for (const [scopeId, state] of this.states.entries()) {
70
+ if (now - state.lastAcceptedAt > this.stateTtlMs) {
71
+ this.states.delete(scopeId);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ //# sourceMappingURL=memory-cooldown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-cooldown.js","sourceRoot":"","sources":["../../src/memory-cooldown.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export type RetryableErrorOptions = {
2
+ statusCodes?: readonly number[];
3
+ messageIncludes?: readonly string[];
4
+ };
5
+ export type RetryDelay = number | ((input: {
6
+ attemptIndex: number;
7
+ error: unknown;
8
+ }) => number);
9
+ export type RetryAsyncInput<T> = {
10
+ task: (input: {
11
+ attemptIndex: number;
12
+ }) => Promise<T>;
13
+ retryAttempts: number;
14
+ delayMs?: RetryDelay;
15
+ shouldRetry?: (error: unknown, input: {
16
+ attemptIndex: number;
17
+ }) => boolean;
18
+ onRetry?: (input: {
19
+ attemptIndex: number;
20
+ nextDelayMs: number;
21
+ error: unknown;
22
+ }) => void | Promise<void>;
23
+ };
24
+ export declare function delay(ms: number): Promise<unknown>;
25
+ export declare function getErrorStatus(error: unknown): number | null;
26
+ export declare function getErrorMessage(error: unknown): string;
27
+ export declare function isRetryableError(error: unknown, options?: RetryableErrorOptions): boolean;
28
+ export declare function retryAsync<T>(input: RetryAsyncInput<T>): Promise<T>;
29
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,UAAU,GAClB,MAAM,GACN,CAAC,CAAC,KAAK,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,KAAK,MAAM,CAAC,CAAC;AAElE,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;IAC/B,IAAI,EAAE,CAAC,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACtD,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;IAC3E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,OAAO,CAAC;KAChB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC;AAEF,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,oBAU/B;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,iBAQ5C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,UAU7C;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,qBAA0B,WAcpC;AAaD,wBAAsB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC,CAAC,cAsC5D"}
@@ -0,0 +1,75 @@
1
+ import { toNonNegativeInteger } from "./limits.js";
2
+ export function delay(ms) {
3
+ const safeMs = toNonNegativeInteger(ms);
4
+ if (safeMs <= 0) {
5
+ return Promise.resolve();
6
+ }
7
+ return new Promise((resolve) => {
8
+ setTimeout(resolve, safeMs);
9
+ });
10
+ }
11
+ export function getErrorStatus(error) {
12
+ if (!error || typeof error !== "object" || !("status" in error)) {
13
+ return null;
14
+ }
15
+ const status = error.status;
16
+ return typeof status === "number" && Number.isFinite(status) ? status : null;
17
+ }
18
+ export function getErrorMessage(error) {
19
+ if (error instanceof Error) {
20
+ return error.message;
21
+ }
22
+ if (typeof error === "string") {
23
+ return error;
24
+ }
25
+ return "";
26
+ }
27
+ export function isRetryableError(error, options = {}) {
28
+ const statusCodes = options.statusCodes ?? [429, 500, 503];
29
+ const status = getErrorStatus(error);
30
+ if (status !== null && statusCodes.includes(status)) {
31
+ return true;
32
+ }
33
+ const message = getErrorMessage(error).toLowerCase();
34
+ return (options.messageIncludes ?? []).some((fragment) => message.includes(fragment.toLowerCase()));
35
+ }
36
+ function resolveRetryDelay(delayMs, input) {
37
+ if (typeof delayMs === "function") {
38
+ return toNonNegativeInteger(delayMs(input));
39
+ }
40
+ return toNonNegativeInteger(delayMs ?? 0);
41
+ }
42
+ export async function retryAsync(input) {
43
+ const retryAttempts = toNonNegativeInteger(input.retryAttempts);
44
+ const maxAttempts = retryAttempts + 1;
45
+ let lastError = null;
46
+ for (let attemptIndex = 0; attemptIndex < maxAttempts; attemptIndex += 1) {
47
+ try {
48
+ return await input.task({ attemptIndex });
49
+ }
50
+ catch (error) {
51
+ lastError = error;
52
+ if (attemptIndex >= maxAttempts - 1) {
53
+ break;
54
+ }
55
+ const shouldRetry = input.shouldRetry
56
+ ? input.shouldRetry(error, { attemptIndex })
57
+ : isRetryableError(error);
58
+ if (!shouldRetry) {
59
+ break;
60
+ }
61
+ const nextDelayMs = resolveRetryDelay(input.delayMs, {
62
+ attemptIndex,
63
+ error,
64
+ });
65
+ await input.onRetry?.({
66
+ attemptIndex,
67
+ nextDelayMs,
68
+ error,
69
+ });
70
+ await delay(nextDelayMs);
71
+ }
72
+ }
73
+ throw lastError;
74
+ }
75
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/retry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export type PerRequestTokenBudgetInput = {
2
+ tokensPerWindow: number;
3
+ requestSpacingMs: number;
4
+ windowMs?: number;
5
+ minimumTokens?: number;
6
+ };
7
+ export type FlexibleTokenBudgetPartition<Name extends string = string> = {
8
+ name: Name;
9
+ targetRatio: number;
10
+ maxRatio: number;
11
+ };
12
+ export declare function calculatePerRequestTokenBudget(input: PerRequestTokenBudgetInput): number;
13
+ export declare function calculateTokenBudgetPortion(totalBudget: number, ratio: number): number;
14
+ export declare function distributeFlexibleTokenBudget<Name extends string>(input: {
15
+ parentBudget: number;
16
+ availableTokens: number;
17
+ partitions: readonly FlexibleTokenBudgetPartition<Name>[];
18
+ }): Record<Name, number>;
19
+ //# sourceMappingURL=token-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-budget.d.ts","sourceRoot":"","sources":["../../src/token-budget.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,0BAA0B,GAAG;IACvC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,4BAA4B,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,IAAI;IACvE,IAAI,EAAE,IAAI,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAUF,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,0BAA0B,UAelC;AAED,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,UAMd;AAED,wBAAgB,6BAA6B,CAAC,IAAI,SAAS,MAAM,EAAE,KAAK,EAAE;IACxE,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,SAAS,4BAA4B,CAAC,IAAI,CAAC,EAAE,CAAC;CAC3D,wBAkCA"}