aden-ts 0.1.1 → 0.2.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.
- package/dist/index.d.mts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.js +143 -61
- package/dist/index.mjs +143 -61
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -140,6 +140,8 @@ interface BudgetRule {
|
|
|
140
140
|
alerts: BudgetAlert[];
|
|
141
141
|
/** Notification settings */
|
|
142
142
|
notifications: BudgetNotifications;
|
|
143
|
+
/** Legacy context_id for backwards compatibility */
|
|
144
|
+
context_id?: string;
|
|
143
145
|
}
|
|
144
146
|
/**
|
|
145
147
|
* Throttle rule - rate limiting
|
|
@@ -285,13 +287,13 @@ interface ControlAgentOptions {
|
|
|
285
287
|
/**
|
|
286
288
|
* Enable hybrid enforcement (local + server-side validation).
|
|
287
289
|
* When enabled, budgets above the threshold are validated with the server.
|
|
288
|
-
* Default:
|
|
290
|
+
* Default: true
|
|
289
291
|
*/
|
|
290
292
|
enableHybridEnforcement?: boolean;
|
|
291
293
|
/**
|
|
292
294
|
* Budget usage threshold (percentage) at which to start server validation.
|
|
293
295
|
* Requests below this threshold use local-only enforcement.
|
|
294
|
-
* Default:
|
|
296
|
+
* Default: 5
|
|
295
297
|
*/
|
|
296
298
|
serverValidationThreshold?: number;
|
|
297
299
|
/**
|
|
@@ -308,7 +310,7 @@ interface ControlAgentOptions {
|
|
|
308
310
|
/**
|
|
309
311
|
* Minimum remaining budget (USD) that triggers forced server validation.
|
|
310
312
|
* Only applies when adaptiveThresholdEnabled is true.
|
|
311
|
-
* Default:
|
|
313
|
+
* Default: 5.0
|
|
312
314
|
*/
|
|
313
315
|
adaptiveMinRemainingUsd?: number;
|
|
314
316
|
/**
|
|
@@ -2354,7 +2356,26 @@ declare class ControlAgent implements IControlAgent {
|
|
|
2354
2356
|
*/
|
|
2355
2357
|
private matchesBlockRule;
|
|
2356
2358
|
/**
|
|
2357
|
-
*
|
|
2359
|
+
* Get action priority for finding most restrictive decision.
|
|
2360
|
+
* Higher priority = more restrictive.
|
|
2361
|
+
*/
|
|
2362
|
+
private getActionPriority;
|
|
2363
|
+
/**
|
|
2364
|
+
* Evaluate a single budget using local-only enforcement.
|
|
2365
|
+
* Returns a decision if the budget triggers an action, null otherwise.
|
|
2366
|
+
*/
|
|
2367
|
+
private evaluateBudgetLocally;
|
|
2368
|
+
/**
|
|
2369
|
+
* Find budgets that apply to the given request based on budget type.
|
|
2370
|
+
*
|
|
2371
|
+
* Matching logic by budget type:
|
|
2372
|
+
* - global: Matches ALL requests
|
|
2373
|
+
* - agent: Matches if request.metadata.agent == budget.name or budget.id
|
|
2374
|
+
* - tenant: Matches if request.metadata.tenant_id == budget.name or budget.id
|
|
2375
|
+
* - customer: Matches if request.metadata.customer_id == budget.name or budget.id
|
|
2376
|
+
* - feature: Matches if request.metadata.feature == budget.name or budget.id
|
|
2377
|
+
* - tag: Matches if any request.metadata.tags intersect with budget.tags
|
|
2378
|
+
* - legacy (context_id): Matches if request.context_id == budget.context_id
|
|
2358
2379
|
*/
|
|
2359
2380
|
private findApplicableBudgets;
|
|
2360
2381
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -140,6 +140,8 @@ interface BudgetRule {
|
|
|
140
140
|
alerts: BudgetAlert[];
|
|
141
141
|
/** Notification settings */
|
|
142
142
|
notifications: BudgetNotifications;
|
|
143
|
+
/** Legacy context_id for backwards compatibility */
|
|
144
|
+
context_id?: string;
|
|
143
145
|
}
|
|
144
146
|
/**
|
|
145
147
|
* Throttle rule - rate limiting
|
|
@@ -285,13 +287,13 @@ interface ControlAgentOptions {
|
|
|
285
287
|
/**
|
|
286
288
|
* Enable hybrid enforcement (local + server-side validation).
|
|
287
289
|
* When enabled, budgets above the threshold are validated with the server.
|
|
288
|
-
* Default:
|
|
290
|
+
* Default: true
|
|
289
291
|
*/
|
|
290
292
|
enableHybridEnforcement?: boolean;
|
|
291
293
|
/**
|
|
292
294
|
* Budget usage threshold (percentage) at which to start server validation.
|
|
293
295
|
* Requests below this threshold use local-only enforcement.
|
|
294
|
-
* Default:
|
|
296
|
+
* Default: 5
|
|
295
297
|
*/
|
|
296
298
|
serverValidationThreshold?: number;
|
|
297
299
|
/**
|
|
@@ -308,7 +310,7 @@ interface ControlAgentOptions {
|
|
|
308
310
|
/**
|
|
309
311
|
* Minimum remaining budget (USD) that triggers forced server validation.
|
|
310
312
|
* Only applies when adaptiveThresholdEnabled is true.
|
|
311
|
-
* Default:
|
|
313
|
+
* Default: 5.0
|
|
312
314
|
*/
|
|
313
315
|
adaptiveMinRemainingUsd?: number;
|
|
314
316
|
/**
|
|
@@ -2354,7 +2356,26 @@ declare class ControlAgent implements IControlAgent {
|
|
|
2354
2356
|
*/
|
|
2355
2357
|
private matchesBlockRule;
|
|
2356
2358
|
/**
|
|
2357
|
-
*
|
|
2359
|
+
* Get action priority for finding most restrictive decision.
|
|
2360
|
+
* Higher priority = more restrictive.
|
|
2361
|
+
*/
|
|
2362
|
+
private getActionPriority;
|
|
2363
|
+
/**
|
|
2364
|
+
* Evaluate a single budget using local-only enforcement.
|
|
2365
|
+
* Returns a decision if the budget triggers an action, null otherwise.
|
|
2366
|
+
*/
|
|
2367
|
+
private evaluateBudgetLocally;
|
|
2368
|
+
/**
|
|
2369
|
+
* Find budgets that apply to the given request based on budget type.
|
|
2370
|
+
*
|
|
2371
|
+
* Matching logic by budget type:
|
|
2372
|
+
* - global: Matches ALL requests
|
|
2373
|
+
* - agent: Matches if request.metadata.agent == budget.name or budget.id
|
|
2374
|
+
* - tenant: Matches if request.metadata.tenant_id == budget.name or budget.id
|
|
2375
|
+
* - customer: Matches if request.metadata.customer_id == budget.name or budget.id
|
|
2376
|
+
* - feature: Matches if request.metadata.feature == budget.name or budget.id
|
|
2377
|
+
* - tag: Matches if any request.metadata.tags intersect with budget.tags
|
|
2378
|
+
* - legacy (context_id): Matches if request.context_id == budget.context_id
|
|
2358
2379
|
*/
|
|
2359
2380
|
private findApplicableBudgets;
|
|
2360
2381
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1886,12 +1886,12 @@ var ControlAgent = class {
|
|
|
1886
1886
|
instanceId: options.instanceId ?? (0, import_crypto6.randomUUID)(),
|
|
1887
1887
|
onAlert: options.onAlert ?? (() => {
|
|
1888
1888
|
}),
|
|
1889
|
-
// Hybrid enforcement options
|
|
1890
|
-
enableHybridEnforcement: options.enableHybridEnforcement ??
|
|
1891
|
-
serverValidationThreshold: options.serverValidationThreshold ??
|
|
1889
|
+
// Hybrid enforcement options (defaults match Python SDK)
|
|
1890
|
+
enableHybridEnforcement: options.enableHybridEnforcement ?? true,
|
|
1891
|
+
serverValidationThreshold: options.serverValidationThreshold ?? 5,
|
|
1892
1892
|
serverValidationTimeoutMs: options.serverValidationTimeoutMs ?? 2e3,
|
|
1893
1893
|
adaptiveThresholdEnabled: options.adaptiveThresholdEnabled ?? true,
|
|
1894
|
-
adaptiveMinRemainingUsd: options.adaptiveMinRemainingUsd ??
|
|
1894
|
+
adaptiveMinRemainingUsd: options.adaptiveMinRemainingUsd ?? 5,
|
|
1895
1895
|
samplingEnabled: options.samplingEnabled ?? true,
|
|
1896
1896
|
samplingBaseRate: options.samplingBaseRate ?? 0.1,
|
|
1897
1897
|
samplingFullValidationPercent: options.samplingFullValidationPercent ?? 95,
|
|
@@ -2139,66 +2139,53 @@ var ControlAgent = class {
|
|
|
2139
2139
|
}
|
|
2140
2140
|
if (policy.budgets) {
|
|
2141
2141
|
const applicableBudgets = this.findApplicableBudgets(policy.budgets, request);
|
|
2142
|
-
if (
|
|
2142
|
+
if (applicableBudgets.length > 0) {
|
|
2143
|
+
let mostRestrictiveDecision = null;
|
|
2144
|
+
let mostRestrictivePriority = -1;
|
|
2143
2145
|
for (const budget of applicableBudgets) {
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2146
|
+
let decision = null;
|
|
2147
|
+
if (this.options.enableHybridEnforcement) {
|
|
2148
|
+
decision = await this.evaluateBudgetWithHybridEnforcement(
|
|
2149
|
+
request,
|
|
2150
|
+
budget,
|
|
2151
|
+
throttleInfo
|
|
2152
|
+
);
|
|
2153
|
+
} else {
|
|
2154
|
+
decision = this.evaluateBudgetLocally(request, budget, throttleInfo);
|
|
2151
2155
|
}
|
|
2152
|
-
if (policy.degradations) {
|
|
2156
|
+
if (!decision && policy.degradations) {
|
|
2153
2157
|
for (const degrade of policy.degradations) {
|
|
2154
2158
|
if (degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2155
2159
|
const usagePercent = budget.spent / budget.limit * 100;
|
|
2156
2160
|
if (usagePercent >= degrade.threshold_percent) {
|
|
2157
|
-
|
|
2161
|
+
decision = {
|
|
2158
2162
|
action: "degrade",
|
|
2159
2163
|
reason: `Budget "${budget.name}" at ${usagePercent.toFixed(1)}% (threshold: ${degrade.threshold_percent}%)`,
|
|
2160
2164
|
degradeToModel: degrade.to_model,
|
|
2161
2165
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2162
2166
|
};
|
|
2167
|
+
break;
|
|
2163
2168
|
}
|
|
2164
2169
|
}
|
|
2165
2170
|
}
|
|
2166
2171
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
if (budget.limitAction === "degrade" && budget.degradeToModel) {
|
|
2173
|
-
return {
|
|
2174
|
-
action: "degrade",
|
|
2175
|
-
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`,
|
|
2176
|
-
degradeToModel: budget.degradeToModel,
|
|
2177
|
-
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2178
|
-
};
|
|
2172
|
+
if (decision) {
|
|
2173
|
+
const priority = this.getActionPriority(decision.action);
|
|
2174
|
+
if (priority > mostRestrictivePriority) {
|
|
2175
|
+
mostRestrictiveDecision = decision;
|
|
2176
|
+
mostRestrictivePriority = priority;
|
|
2179
2177
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
action,
|
|
2183
|
-
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
if (policy.degradations) {
|
|
2187
|
-
for (const degrade of policy.degradations) {
|
|
2188
|
-
if (degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2189
|
-
const usagePercent = budget.spent / budget.limit * 100;
|
|
2190
|
-
if (usagePercent >= degrade.threshold_percent) {
|
|
2191
|
-
return {
|
|
2192
|
-
action: "degrade",
|
|
2193
|
-
reason: `Budget "${budget.name}" at ${usagePercent.toFixed(1)}% (threshold: ${degrade.threshold_percent}%)`,
|
|
2194
|
-
degradeToModel: degrade.to_model,
|
|
2195
|
-
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2196
|
-
};
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2178
|
+
if (decision.action === "block") {
|
|
2179
|
+
break;
|
|
2199
2180
|
}
|
|
2200
2181
|
}
|
|
2201
2182
|
}
|
|
2183
|
+
if (mostRestrictiveDecision) {
|
|
2184
|
+
if (throttleInfo && mostRestrictiveDecision.action !== "block" && !mostRestrictiveDecision.throttleDelayMs) {
|
|
2185
|
+
mostRestrictiveDecision.throttleDelayMs = throttleInfo.delayMs;
|
|
2186
|
+
}
|
|
2187
|
+
return mostRestrictiveDecision;
|
|
2188
|
+
}
|
|
2202
2189
|
}
|
|
2203
2190
|
}
|
|
2204
2191
|
if (policy.degradations) {
|
|
@@ -2291,36 +2278,96 @@ var ControlAgent = class {
|
|
|
2291
2278
|
return true;
|
|
2292
2279
|
}
|
|
2293
2280
|
/**
|
|
2294
|
-
*
|
|
2281
|
+
* Get action priority for finding most restrictive decision.
|
|
2282
|
+
* Higher priority = more restrictive.
|
|
2283
|
+
*/
|
|
2284
|
+
getActionPriority(action) {
|
|
2285
|
+
const priority = {
|
|
2286
|
+
allow: 0,
|
|
2287
|
+
alert: 1,
|
|
2288
|
+
throttle: 2,
|
|
2289
|
+
degrade: 3,
|
|
2290
|
+
block: 4
|
|
2291
|
+
};
|
|
2292
|
+
return priority[action] ?? 0;
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* Evaluate a single budget using local-only enforcement.
|
|
2296
|
+
* Returns a decision if the budget triggers an action, null otherwise.
|
|
2297
|
+
*/
|
|
2298
|
+
evaluateBudgetLocally(request, budget, throttleInfo) {
|
|
2299
|
+
const projectedSpend = budget.spent + (request.estimated_cost ?? 0);
|
|
2300
|
+
if (projectedSpend > budget.limit) {
|
|
2301
|
+
if (budget.limitAction === "degrade" && budget.degradeToModel) {
|
|
2302
|
+
return {
|
|
2303
|
+
action: "degrade",
|
|
2304
|
+
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`,
|
|
2305
|
+
degradeToModel: budget.degradeToModel,
|
|
2306
|
+
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
const action = budget.limitAction === "kill" ? "block" : budget.limitAction;
|
|
2310
|
+
return {
|
|
2311
|
+
action,
|
|
2312
|
+
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
return null;
|
|
2316
|
+
}
|
|
2317
|
+
/**
|
|
2318
|
+
* Find budgets that apply to the given request based on budget type.
|
|
2319
|
+
*
|
|
2320
|
+
* Matching logic by budget type:
|
|
2321
|
+
* - global: Matches ALL requests
|
|
2322
|
+
* - agent: Matches if request.metadata.agent == budget.name or budget.id
|
|
2323
|
+
* - tenant: Matches if request.metadata.tenant_id == budget.name or budget.id
|
|
2324
|
+
* - customer: Matches if request.metadata.customer_id == budget.name or budget.id
|
|
2325
|
+
* - feature: Matches if request.metadata.feature == budget.name or budget.id
|
|
2326
|
+
* - tag: Matches if any request.metadata.tags intersect with budget.tags
|
|
2327
|
+
* - legacy (context_id): Matches if request.context_id == budget.context_id
|
|
2295
2328
|
*/
|
|
2296
2329
|
findApplicableBudgets(budgets, request) {
|
|
2297
2330
|
const result = [];
|
|
2298
2331
|
const metadata = request.metadata || {};
|
|
2299
2332
|
for (const budget of budgets) {
|
|
2333
|
+
if (budget.context_id && request.context_id) {
|
|
2334
|
+
if (budget.context_id === request.context_id) {
|
|
2335
|
+
result.push(budget);
|
|
2336
|
+
continue;
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2300
2339
|
switch (budget.type) {
|
|
2301
2340
|
case "global":
|
|
2302
2341
|
result.push(budget);
|
|
2303
2342
|
break;
|
|
2304
|
-
case "agent":
|
|
2305
|
-
|
|
2343
|
+
case "agent": {
|
|
2344
|
+
const agent = metadata.agent;
|
|
2345
|
+
if (agent && (agent === budget.name || agent === budget.id)) {
|
|
2306
2346
|
result.push(budget);
|
|
2307
2347
|
}
|
|
2308
2348
|
break;
|
|
2309
|
-
|
|
2310
|
-
|
|
2349
|
+
}
|
|
2350
|
+
case "tenant": {
|
|
2351
|
+
const tenantId = metadata.tenant_id;
|
|
2352
|
+
if (tenantId && (tenantId === budget.name || tenantId === budget.id)) {
|
|
2311
2353
|
result.push(budget);
|
|
2312
2354
|
}
|
|
2313
2355
|
break;
|
|
2314
|
-
|
|
2315
|
-
|
|
2356
|
+
}
|
|
2357
|
+
case "customer": {
|
|
2358
|
+
const customerId = metadata.customer_id;
|
|
2359
|
+
if (customerId && (customerId === budget.name || customerId === budget.id) || request.context_id && (request.context_id === budget.name || request.context_id === budget.id)) {
|
|
2316
2360
|
result.push(budget);
|
|
2317
2361
|
}
|
|
2318
2362
|
break;
|
|
2319
|
-
|
|
2320
|
-
|
|
2363
|
+
}
|
|
2364
|
+
case "feature": {
|
|
2365
|
+
const feature = metadata.feature;
|
|
2366
|
+
if (feature && (feature === budget.name || feature === budget.id)) {
|
|
2321
2367
|
result.push(budget);
|
|
2322
2368
|
}
|
|
2323
2369
|
break;
|
|
2370
|
+
}
|
|
2324
2371
|
case "tag":
|
|
2325
2372
|
if (budget.tags && Array.isArray(metadata.tags)) {
|
|
2326
2373
|
const requestTags = metadata.tags;
|
|
@@ -2370,13 +2417,48 @@ var ControlAgent = class {
|
|
|
2370
2417
|
if (this.cachedPolicy?.budgets && event.total_tokens > 0) {
|
|
2371
2418
|
const estimatedCost = this.estimateCost(event);
|
|
2372
2419
|
const contextId2 = this.options.getContextId?.();
|
|
2420
|
+
const metadata = event.metadata || {};
|
|
2373
2421
|
for (const budget of this.cachedPolicy.budgets) {
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2422
|
+
let shouldUpdate = false;
|
|
2423
|
+
switch (budget.type) {
|
|
2424
|
+
case "global":
|
|
2425
|
+
shouldUpdate = true;
|
|
2426
|
+
break;
|
|
2427
|
+
case "agent": {
|
|
2428
|
+
const agent = metadata.agent;
|
|
2429
|
+
shouldUpdate = Boolean(agent && (agent === budget.name || agent === budget.id));
|
|
2430
|
+
break;
|
|
2431
|
+
}
|
|
2432
|
+
case "tenant": {
|
|
2433
|
+
const tenantId = metadata.tenant_id;
|
|
2434
|
+
shouldUpdate = Boolean(tenantId && (tenantId === budget.name || tenantId === budget.id));
|
|
2435
|
+
break;
|
|
2379
2436
|
}
|
|
2437
|
+
case "customer": {
|
|
2438
|
+
const customerId = metadata.customer_id;
|
|
2439
|
+
shouldUpdate = Boolean(
|
|
2440
|
+
customerId && (customerId === budget.name || customerId === budget.id) || contextId2 && (contextId2 === budget.name || contextId2 === budget.id)
|
|
2441
|
+
);
|
|
2442
|
+
break;
|
|
2443
|
+
}
|
|
2444
|
+
case "feature": {
|
|
2445
|
+
const feature = metadata.feature;
|
|
2446
|
+
shouldUpdate = Boolean(feature && (feature === budget.name || feature === budget.id));
|
|
2447
|
+
break;
|
|
2448
|
+
}
|
|
2449
|
+
case "tag": {
|
|
2450
|
+
if (budget.tags && Array.isArray(metadata.tags)) {
|
|
2451
|
+
const requestTags = metadata.tags;
|
|
2452
|
+
shouldUpdate = budget.tags.some((tag) => requestTags.includes(tag));
|
|
2453
|
+
}
|
|
2454
|
+
break;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
if (!shouldUpdate && budget.context_id && contextId2) {
|
|
2458
|
+
shouldUpdate = budget.context_id === contextId2;
|
|
2459
|
+
}
|
|
2460
|
+
if (shouldUpdate) {
|
|
2461
|
+
budget.spent += estimatedCost;
|
|
2380
2462
|
}
|
|
2381
2463
|
}
|
|
2382
2464
|
}
|
|
@@ -2441,7 +2523,7 @@ var ControlAgent = class {
|
|
|
2441
2523
|
if (this.options.adaptiveThresholdEnabled) {
|
|
2442
2524
|
if (remainingBudgetUsd <= this.options.adaptiveMinRemainingUsd) {
|
|
2443
2525
|
console.debug(
|
|
2444
|
-
`[aden] Remaining budget $${remainingBudgetUsd.toFixed(
|
|
2526
|
+
`[aden] Remaining budget $${remainingBudgetUsd.toFixed(4)} <= $${this.options.adaptiveMinRemainingUsd.toFixed(2)}, forcing validation`
|
|
2445
2527
|
);
|
|
2446
2528
|
return true;
|
|
2447
2529
|
}
|
|
@@ -2578,7 +2660,7 @@ var ControlAgent = class {
|
|
|
2578
2660
|
}
|
|
2579
2661
|
if (this.shouldValidateWithServer(usagePercent, remaining, limit)) {
|
|
2580
2662
|
console.debug(
|
|
2581
|
-
`[aden] Budget '${budget.
|
|
2663
|
+
`[aden] Budget '${budget.name}' at ${usagePercent.toFixed(1)}% ($${currentSpend.toFixed(6)}/$${limit.toFixed(6)}), validating with server`
|
|
2582
2664
|
);
|
|
2583
2665
|
const validation = await this.validateBudgetWithServer(
|
|
2584
2666
|
budget.id,
|
package/dist/index.mjs
CHANGED
|
@@ -1769,12 +1769,12 @@ var ControlAgent = class {
|
|
|
1769
1769
|
instanceId: options.instanceId ?? randomUUID6(),
|
|
1770
1770
|
onAlert: options.onAlert ?? (() => {
|
|
1771
1771
|
}),
|
|
1772
|
-
// Hybrid enforcement options
|
|
1773
|
-
enableHybridEnforcement: options.enableHybridEnforcement ??
|
|
1774
|
-
serverValidationThreshold: options.serverValidationThreshold ??
|
|
1772
|
+
// Hybrid enforcement options (defaults match Python SDK)
|
|
1773
|
+
enableHybridEnforcement: options.enableHybridEnforcement ?? true,
|
|
1774
|
+
serverValidationThreshold: options.serverValidationThreshold ?? 5,
|
|
1775
1775
|
serverValidationTimeoutMs: options.serverValidationTimeoutMs ?? 2e3,
|
|
1776
1776
|
adaptiveThresholdEnabled: options.adaptiveThresholdEnabled ?? true,
|
|
1777
|
-
adaptiveMinRemainingUsd: options.adaptiveMinRemainingUsd ??
|
|
1777
|
+
adaptiveMinRemainingUsd: options.adaptiveMinRemainingUsd ?? 5,
|
|
1778
1778
|
samplingEnabled: options.samplingEnabled ?? true,
|
|
1779
1779
|
samplingBaseRate: options.samplingBaseRate ?? 0.1,
|
|
1780
1780
|
samplingFullValidationPercent: options.samplingFullValidationPercent ?? 95,
|
|
@@ -2022,66 +2022,53 @@ var ControlAgent = class {
|
|
|
2022
2022
|
}
|
|
2023
2023
|
if (policy.budgets) {
|
|
2024
2024
|
const applicableBudgets = this.findApplicableBudgets(policy.budgets, request);
|
|
2025
|
-
if (
|
|
2025
|
+
if (applicableBudgets.length > 0) {
|
|
2026
|
+
let mostRestrictiveDecision = null;
|
|
2027
|
+
let mostRestrictivePriority = -1;
|
|
2026
2028
|
for (const budget of applicableBudgets) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2029
|
+
let decision = null;
|
|
2030
|
+
if (this.options.enableHybridEnforcement) {
|
|
2031
|
+
decision = await this.evaluateBudgetWithHybridEnforcement(
|
|
2032
|
+
request,
|
|
2033
|
+
budget,
|
|
2034
|
+
throttleInfo
|
|
2035
|
+
);
|
|
2036
|
+
} else {
|
|
2037
|
+
decision = this.evaluateBudgetLocally(request, budget, throttleInfo);
|
|
2034
2038
|
}
|
|
2035
|
-
if (policy.degradations) {
|
|
2039
|
+
if (!decision && policy.degradations) {
|
|
2036
2040
|
for (const degrade of policy.degradations) {
|
|
2037
2041
|
if (degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2038
2042
|
const usagePercent = budget.spent / budget.limit * 100;
|
|
2039
2043
|
if (usagePercent >= degrade.threshold_percent) {
|
|
2040
|
-
|
|
2044
|
+
decision = {
|
|
2041
2045
|
action: "degrade",
|
|
2042
2046
|
reason: `Budget "${budget.name}" at ${usagePercent.toFixed(1)}% (threshold: ${degrade.threshold_percent}%)`,
|
|
2043
2047
|
degradeToModel: degrade.to_model,
|
|
2044
2048
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2045
2049
|
};
|
|
2050
|
+
break;
|
|
2046
2051
|
}
|
|
2047
2052
|
}
|
|
2048
2053
|
}
|
|
2049
2054
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
if (budget.limitAction === "degrade" && budget.degradeToModel) {
|
|
2056
|
-
return {
|
|
2057
|
-
action: "degrade",
|
|
2058
|
-
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`,
|
|
2059
|
-
degradeToModel: budget.degradeToModel,
|
|
2060
|
-
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2061
|
-
};
|
|
2055
|
+
if (decision) {
|
|
2056
|
+
const priority = this.getActionPriority(decision.action);
|
|
2057
|
+
if (priority > mostRestrictivePriority) {
|
|
2058
|
+
mostRestrictiveDecision = decision;
|
|
2059
|
+
mostRestrictivePriority = priority;
|
|
2062
2060
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
action,
|
|
2066
|
-
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
if (policy.degradations) {
|
|
2070
|
-
for (const degrade of policy.degradations) {
|
|
2071
|
-
if (degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2072
|
-
const usagePercent = budget.spent / budget.limit * 100;
|
|
2073
|
-
if (usagePercent >= degrade.threshold_percent) {
|
|
2074
|
-
return {
|
|
2075
|
-
action: "degrade",
|
|
2076
|
-
reason: `Budget "${budget.name}" at ${usagePercent.toFixed(1)}% (threshold: ${degrade.threshold_percent}%)`,
|
|
2077
|
-
degradeToModel: degrade.to_model,
|
|
2078
|
-
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2079
|
-
};
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2061
|
+
if (decision.action === "block") {
|
|
2062
|
+
break;
|
|
2082
2063
|
}
|
|
2083
2064
|
}
|
|
2084
2065
|
}
|
|
2066
|
+
if (mostRestrictiveDecision) {
|
|
2067
|
+
if (throttleInfo && mostRestrictiveDecision.action !== "block" && !mostRestrictiveDecision.throttleDelayMs) {
|
|
2068
|
+
mostRestrictiveDecision.throttleDelayMs = throttleInfo.delayMs;
|
|
2069
|
+
}
|
|
2070
|
+
return mostRestrictiveDecision;
|
|
2071
|
+
}
|
|
2085
2072
|
}
|
|
2086
2073
|
}
|
|
2087
2074
|
if (policy.degradations) {
|
|
@@ -2174,36 +2161,96 @@ var ControlAgent = class {
|
|
|
2174
2161
|
return true;
|
|
2175
2162
|
}
|
|
2176
2163
|
/**
|
|
2177
|
-
*
|
|
2164
|
+
* Get action priority for finding most restrictive decision.
|
|
2165
|
+
* Higher priority = more restrictive.
|
|
2166
|
+
*/
|
|
2167
|
+
getActionPriority(action) {
|
|
2168
|
+
const priority = {
|
|
2169
|
+
allow: 0,
|
|
2170
|
+
alert: 1,
|
|
2171
|
+
throttle: 2,
|
|
2172
|
+
degrade: 3,
|
|
2173
|
+
block: 4
|
|
2174
|
+
};
|
|
2175
|
+
return priority[action] ?? 0;
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Evaluate a single budget using local-only enforcement.
|
|
2179
|
+
* Returns a decision if the budget triggers an action, null otherwise.
|
|
2180
|
+
*/
|
|
2181
|
+
evaluateBudgetLocally(request, budget, throttleInfo) {
|
|
2182
|
+
const projectedSpend = budget.spent + (request.estimated_cost ?? 0);
|
|
2183
|
+
if (projectedSpend > budget.limit) {
|
|
2184
|
+
if (budget.limitAction === "degrade" && budget.degradeToModel) {
|
|
2185
|
+
return {
|
|
2186
|
+
action: "degrade",
|
|
2187
|
+
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`,
|
|
2188
|
+
degradeToModel: budget.degradeToModel,
|
|
2189
|
+
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
const action = budget.limitAction === "kill" ? "block" : budget.limitAction;
|
|
2193
|
+
return {
|
|
2194
|
+
action,
|
|
2195
|
+
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* Find budgets that apply to the given request based on budget type.
|
|
2202
|
+
*
|
|
2203
|
+
* Matching logic by budget type:
|
|
2204
|
+
* - global: Matches ALL requests
|
|
2205
|
+
* - agent: Matches if request.metadata.agent == budget.name or budget.id
|
|
2206
|
+
* - tenant: Matches if request.metadata.tenant_id == budget.name or budget.id
|
|
2207
|
+
* - customer: Matches if request.metadata.customer_id == budget.name or budget.id
|
|
2208
|
+
* - feature: Matches if request.metadata.feature == budget.name or budget.id
|
|
2209
|
+
* - tag: Matches if any request.metadata.tags intersect with budget.tags
|
|
2210
|
+
* - legacy (context_id): Matches if request.context_id == budget.context_id
|
|
2178
2211
|
*/
|
|
2179
2212
|
findApplicableBudgets(budgets, request) {
|
|
2180
2213
|
const result = [];
|
|
2181
2214
|
const metadata = request.metadata || {};
|
|
2182
2215
|
for (const budget of budgets) {
|
|
2216
|
+
if (budget.context_id && request.context_id) {
|
|
2217
|
+
if (budget.context_id === request.context_id) {
|
|
2218
|
+
result.push(budget);
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2183
2222
|
switch (budget.type) {
|
|
2184
2223
|
case "global":
|
|
2185
2224
|
result.push(budget);
|
|
2186
2225
|
break;
|
|
2187
|
-
case "agent":
|
|
2188
|
-
|
|
2226
|
+
case "agent": {
|
|
2227
|
+
const agent = metadata.agent;
|
|
2228
|
+
if (agent && (agent === budget.name || agent === budget.id)) {
|
|
2189
2229
|
result.push(budget);
|
|
2190
2230
|
}
|
|
2191
2231
|
break;
|
|
2192
|
-
|
|
2193
|
-
|
|
2232
|
+
}
|
|
2233
|
+
case "tenant": {
|
|
2234
|
+
const tenantId = metadata.tenant_id;
|
|
2235
|
+
if (tenantId && (tenantId === budget.name || tenantId === budget.id)) {
|
|
2194
2236
|
result.push(budget);
|
|
2195
2237
|
}
|
|
2196
2238
|
break;
|
|
2197
|
-
|
|
2198
|
-
|
|
2239
|
+
}
|
|
2240
|
+
case "customer": {
|
|
2241
|
+
const customerId = metadata.customer_id;
|
|
2242
|
+
if (customerId && (customerId === budget.name || customerId === budget.id) || request.context_id && (request.context_id === budget.name || request.context_id === budget.id)) {
|
|
2199
2243
|
result.push(budget);
|
|
2200
2244
|
}
|
|
2201
2245
|
break;
|
|
2202
|
-
|
|
2203
|
-
|
|
2246
|
+
}
|
|
2247
|
+
case "feature": {
|
|
2248
|
+
const feature = metadata.feature;
|
|
2249
|
+
if (feature && (feature === budget.name || feature === budget.id)) {
|
|
2204
2250
|
result.push(budget);
|
|
2205
2251
|
}
|
|
2206
2252
|
break;
|
|
2253
|
+
}
|
|
2207
2254
|
case "tag":
|
|
2208
2255
|
if (budget.tags && Array.isArray(metadata.tags)) {
|
|
2209
2256
|
const requestTags = metadata.tags;
|
|
@@ -2253,13 +2300,48 @@ var ControlAgent = class {
|
|
|
2253
2300
|
if (this.cachedPolicy?.budgets && event.total_tokens > 0) {
|
|
2254
2301
|
const estimatedCost = this.estimateCost(event);
|
|
2255
2302
|
const contextId2 = this.options.getContextId?.();
|
|
2303
|
+
const metadata = event.metadata || {};
|
|
2256
2304
|
for (const budget of this.cachedPolicy.budgets) {
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2305
|
+
let shouldUpdate = false;
|
|
2306
|
+
switch (budget.type) {
|
|
2307
|
+
case "global":
|
|
2308
|
+
shouldUpdate = true;
|
|
2309
|
+
break;
|
|
2310
|
+
case "agent": {
|
|
2311
|
+
const agent = metadata.agent;
|
|
2312
|
+
shouldUpdate = Boolean(agent && (agent === budget.name || agent === budget.id));
|
|
2313
|
+
break;
|
|
2314
|
+
}
|
|
2315
|
+
case "tenant": {
|
|
2316
|
+
const tenantId = metadata.tenant_id;
|
|
2317
|
+
shouldUpdate = Boolean(tenantId && (tenantId === budget.name || tenantId === budget.id));
|
|
2318
|
+
break;
|
|
2262
2319
|
}
|
|
2320
|
+
case "customer": {
|
|
2321
|
+
const customerId = metadata.customer_id;
|
|
2322
|
+
shouldUpdate = Boolean(
|
|
2323
|
+
customerId && (customerId === budget.name || customerId === budget.id) || contextId2 && (contextId2 === budget.name || contextId2 === budget.id)
|
|
2324
|
+
);
|
|
2325
|
+
break;
|
|
2326
|
+
}
|
|
2327
|
+
case "feature": {
|
|
2328
|
+
const feature = metadata.feature;
|
|
2329
|
+
shouldUpdate = Boolean(feature && (feature === budget.name || feature === budget.id));
|
|
2330
|
+
break;
|
|
2331
|
+
}
|
|
2332
|
+
case "tag": {
|
|
2333
|
+
if (budget.tags && Array.isArray(metadata.tags)) {
|
|
2334
|
+
const requestTags = metadata.tags;
|
|
2335
|
+
shouldUpdate = budget.tags.some((tag) => requestTags.includes(tag));
|
|
2336
|
+
}
|
|
2337
|
+
break;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
if (!shouldUpdate && budget.context_id && contextId2) {
|
|
2341
|
+
shouldUpdate = budget.context_id === contextId2;
|
|
2342
|
+
}
|
|
2343
|
+
if (shouldUpdate) {
|
|
2344
|
+
budget.spent += estimatedCost;
|
|
2263
2345
|
}
|
|
2264
2346
|
}
|
|
2265
2347
|
}
|
|
@@ -2324,7 +2406,7 @@ var ControlAgent = class {
|
|
|
2324
2406
|
if (this.options.adaptiveThresholdEnabled) {
|
|
2325
2407
|
if (remainingBudgetUsd <= this.options.adaptiveMinRemainingUsd) {
|
|
2326
2408
|
console.debug(
|
|
2327
|
-
`[aden] Remaining budget $${remainingBudgetUsd.toFixed(
|
|
2409
|
+
`[aden] Remaining budget $${remainingBudgetUsd.toFixed(4)} <= $${this.options.adaptiveMinRemainingUsd.toFixed(2)}, forcing validation`
|
|
2328
2410
|
);
|
|
2329
2411
|
return true;
|
|
2330
2412
|
}
|
|
@@ -2461,7 +2543,7 @@ var ControlAgent = class {
|
|
|
2461
2543
|
}
|
|
2462
2544
|
if (this.shouldValidateWithServer(usagePercent, remaining, limit)) {
|
|
2463
2545
|
console.debug(
|
|
2464
|
-
`[aden] Budget '${budget.
|
|
2546
|
+
`[aden] Budget '${budget.name}' at ${usagePercent.toFixed(1)}% ($${currentSpend.toFixed(6)}/$${limit.toFixed(6)}), validating with server`
|
|
2465
2547
|
);
|
|
2466
2548
|
const validation = await this.validateBudgetWithServer(
|
|
2467
2549
|
budget.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aden-ts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "LLM Observability & Cost Control SDK - Real-time usage tracking, budget enforcement, and cost control for OpenAI, Anthropic, and Gemini",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|