aden-ts 0.2.1 → 0.2.3
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 +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +89 -10
- package/dist/index.mjs +89 -10
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -26,6 +26,8 @@ interface ControlDecision {
|
|
|
26
26
|
reason?: string;
|
|
27
27
|
/** If action is "degrade", switch to this model */
|
|
28
28
|
degradeToModel?: string;
|
|
29
|
+
/** If action is "degrade", the provider for the degraded model */
|
|
30
|
+
degradeToProvider?: string;
|
|
29
31
|
/** If action is "throttle", delay by this many milliseconds */
|
|
30
32
|
throttleDelayMs?: number;
|
|
31
33
|
/** If action is "alert", the severity level */
|
|
@@ -63,6 +65,8 @@ interface ControlEvent extends BaseEvent {
|
|
|
63
65
|
reason?: string;
|
|
64
66
|
/** If degraded, what model was used instead */
|
|
65
67
|
degraded_to?: string;
|
|
68
|
+
/** If degraded, the provider for the degraded model */
|
|
69
|
+
degraded_to_provider?: string;
|
|
66
70
|
/** If throttled, how long was the delay in ms */
|
|
67
71
|
throttle_delay_ms?: number;
|
|
68
72
|
/** Estimated cost that triggered the decision */
|
|
@@ -136,6 +140,8 @@ interface BudgetRule {
|
|
|
136
140
|
limitAction: LimitAction;
|
|
137
141
|
/** If limitAction is "degrade", switch to this model */
|
|
138
142
|
degradeToModel?: string;
|
|
143
|
+
/** If limitAction is "degrade", the provider for the degraded model */
|
|
144
|
+
degradeToProvider?: string;
|
|
139
145
|
/** Alert thresholds */
|
|
140
146
|
alerts: BudgetAlert[];
|
|
141
147
|
/** Notification settings */
|
|
@@ -173,12 +179,14 @@ interface BlockRule {
|
|
|
173
179
|
* Degrade rule - automatic model downgrade
|
|
174
180
|
*/
|
|
175
181
|
interface DegradeRule {
|
|
182
|
+
/** Provider this rule applies to (required - no cross-vendor degradation) */
|
|
183
|
+
provider: string;
|
|
176
184
|
/** Model to downgrade from */
|
|
177
185
|
from_model: string;
|
|
178
186
|
/** Model to downgrade to */
|
|
179
187
|
to_model: string;
|
|
180
188
|
/** When to trigger the downgrade */
|
|
181
|
-
trigger: "budget_threshold" | "rate_limit" | "always";
|
|
189
|
+
trigger: "budget_threshold" | "rate_limit" | "always" | "budget_exceeded";
|
|
182
190
|
/** For budget_threshold: percentage at which to trigger (0-100) */
|
|
183
191
|
threshold_percent?: number;
|
|
184
192
|
/** Context ID this rule applies to (omit for all) */
|
|
@@ -379,6 +387,8 @@ interface BudgetValidationResponse {
|
|
|
379
387
|
projectedPercent?: number;
|
|
380
388
|
/** Model to degrade to (if action is "degrade") */
|
|
381
389
|
degradeToModel?: string;
|
|
390
|
+
/** Provider for the degraded model (if action is "degrade") */
|
|
391
|
+
degradeToProvider?: string;
|
|
382
392
|
}
|
|
383
393
|
/**
|
|
384
394
|
* Control Agent interface - the public API
|
|
@@ -596,6 +606,7 @@ type BeforeRequestResult = {
|
|
|
596
606
|
} | {
|
|
597
607
|
action: "degrade";
|
|
598
608
|
toModel: string;
|
|
609
|
+
toProvider?: string;
|
|
599
610
|
reason?: string;
|
|
600
611
|
delayMs?: number;
|
|
601
612
|
} | {
|
|
@@ -2291,6 +2302,8 @@ declare class ControlAgent implements IControlAgent {
|
|
|
2291
2302
|
private requestsSinceLastHeartbeat;
|
|
2292
2303
|
private errorsSinceLastHeartbeat;
|
|
2293
2304
|
private requestCounts;
|
|
2305
|
+
private pricingCache;
|
|
2306
|
+
private pricingAliases;
|
|
2294
2307
|
constructor(options: ControlAgentOptions);
|
|
2295
2308
|
/**
|
|
2296
2309
|
* Connect to the control server
|
|
@@ -2321,6 +2334,14 @@ declare class ControlAgent implements IControlAgent {
|
|
|
2321
2334
|
* Fetch policy via HTTP
|
|
2322
2335
|
*/
|
|
2323
2336
|
private fetchPolicy;
|
|
2337
|
+
/**
|
|
2338
|
+
* Fetch pricing table from server and cache it
|
|
2339
|
+
*/
|
|
2340
|
+
private fetchPricing;
|
|
2341
|
+
/**
|
|
2342
|
+
* Get pricing for a model from cached pricing table
|
|
2343
|
+
*/
|
|
2344
|
+
private getModelPricing;
|
|
2324
2345
|
/**
|
|
2325
2346
|
* Start heartbeat timer
|
|
2326
2347
|
*/
|
|
@@ -2387,8 +2408,8 @@ declare class ControlAgent implements IControlAgent {
|
|
|
2387
2408
|
*/
|
|
2388
2409
|
reportMetric(event: MetricEvent): Promise<void>;
|
|
2389
2410
|
/**
|
|
2390
|
-
* Estimate cost from a metric event
|
|
2391
|
-
*
|
|
2411
|
+
* Estimate cost from a metric event using server pricing table.
|
|
2412
|
+
* Falls back to default pricing if model not found.
|
|
2392
2413
|
*/
|
|
2393
2414
|
private estimateCost;
|
|
2394
2415
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ interface ControlDecision {
|
|
|
26
26
|
reason?: string;
|
|
27
27
|
/** If action is "degrade", switch to this model */
|
|
28
28
|
degradeToModel?: string;
|
|
29
|
+
/** If action is "degrade", the provider for the degraded model */
|
|
30
|
+
degradeToProvider?: string;
|
|
29
31
|
/** If action is "throttle", delay by this many milliseconds */
|
|
30
32
|
throttleDelayMs?: number;
|
|
31
33
|
/** If action is "alert", the severity level */
|
|
@@ -63,6 +65,8 @@ interface ControlEvent extends BaseEvent {
|
|
|
63
65
|
reason?: string;
|
|
64
66
|
/** If degraded, what model was used instead */
|
|
65
67
|
degraded_to?: string;
|
|
68
|
+
/** If degraded, the provider for the degraded model */
|
|
69
|
+
degraded_to_provider?: string;
|
|
66
70
|
/** If throttled, how long was the delay in ms */
|
|
67
71
|
throttle_delay_ms?: number;
|
|
68
72
|
/** Estimated cost that triggered the decision */
|
|
@@ -136,6 +140,8 @@ interface BudgetRule {
|
|
|
136
140
|
limitAction: LimitAction;
|
|
137
141
|
/** If limitAction is "degrade", switch to this model */
|
|
138
142
|
degradeToModel?: string;
|
|
143
|
+
/** If limitAction is "degrade", the provider for the degraded model */
|
|
144
|
+
degradeToProvider?: string;
|
|
139
145
|
/** Alert thresholds */
|
|
140
146
|
alerts: BudgetAlert[];
|
|
141
147
|
/** Notification settings */
|
|
@@ -173,12 +179,14 @@ interface BlockRule {
|
|
|
173
179
|
* Degrade rule - automatic model downgrade
|
|
174
180
|
*/
|
|
175
181
|
interface DegradeRule {
|
|
182
|
+
/** Provider this rule applies to (required - no cross-vendor degradation) */
|
|
183
|
+
provider: string;
|
|
176
184
|
/** Model to downgrade from */
|
|
177
185
|
from_model: string;
|
|
178
186
|
/** Model to downgrade to */
|
|
179
187
|
to_model: string;
|
|
180
188
|
/** When to trigger the downgrade */
|
|
181
|
-
trigger: "budget_threshold" | "rate_limit" | "always";
|
|
189
|
+
trigger: "budget_threshold" | "rate_limit" | "always" | "budget_exceeded";
|
|
182
190
|
/** For budget_threshold: percentage at which to trigger (0-100) */
|
|
183
191
|
threshold_percent?: number;
|
|
184
192
|
/** Context ID this rule applies to (omit for all) */
|
|
@@ -379,6 +387,8 @@ interface BudgetValidationResponse {
|
|
|
379
387
|
projectedPercent?: number;
|
|
380
388
|
/** Model to degrade to (if action is "degrade") */
|
|
381
389
|
degradeToModel?: string;
|
|
390
|
+
/** Provider for the degraded model (if action is "degrade") */
|
|
391
|
+
degradeToProvider?: string;
|
|
382
392
|
}
|
|
383
393
|
/**
|
|
384
394
|
* Control Agent interface - the public API
|
|
@@ -596,6 +606,7 @@ type BeforeRequestResult = {
|
|
|
596
606
|
} | {
|
|
597
607
|
action: "degrade";
|
|
598
608
|
toModel: string;
|
|
609
|
+
toProvider?: string;
|
|
599
610
|
reason?: string;
|
|
600
611
|
delayMs?: number;
|
|
601
612
|
} | {
|
|
@@ -2291,6 +2302,8 @@ declare class ControlAgent implements IControlAgent {
|
|
|
2291
2302
|
private requestsSinceLastHeartbeat;
|
|
2292
2303
|
private errorsSinceLastHeartbeat;
|
|
2293
2304
|
private requestCounts;
|
|
2305
|
+
private pricingCache;
|
|
2306
|
+
private pricingAliases;
|
|
2294
2307
|
constructor(options: ControlAgentOptions);
|
|
2295
2308
|
/**
|
|
2296
2309
|
* Connect to the control server
|
|
@@ -2321,6 +2334,14 @@ declare class ControlAgent implements IControlAgent {
|
|
|
2321
2334
|
* Fetch policy via HTTP
|
|
2322
2335
|
*/
|
|
2323
2336
|
private fetchPolicy;
|
|
2337
|
+
/**
|
|
2338
|
+
* Fetch pricing table from server and cache it
|
|
2339
|
+
*/
|
|
2340
|
+
private fetchPricing;
|
|
2341
|
+
/**
|
|
2342
|
+
* Get pricing for a model from cached pricing table
|
|
2343
|
+
*/
|
|
2344
|
+
private getModelPricing;
|
|
2324
2345
|
/**
|
|
2325
2346
|
* Start heartbeat timer
|
|
2326
2347
|
*/
|
|
@@ -2387,8 +2408,8 @@ declare class ControlAgent implements IControlAgent {
|
|
|
2387
2408
|
*/
|
|
2388
2409
|
reportMetric(event: MetricEvent): Promise<void>;
|
|
2389
2410
|
/**
|
|
2390
|
-
* Estimate cost from a metric event
|
|
2391
|
-
*
|
|
2411
|
+
* Estimate cost from a metric event using server pricing table.
|
|
2412
|
+
* Falls back to default pricing if model not found.
|
|
2392
2413
|
*/
|
|
2393
2414
|
private estimateCost;
|
|
2394
2415
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1970,6 +1970,9 @@ var ControlAgent = class {
|
|
|
1970
1970
|
this.errorsSinceLastHeartbeat = 0;
|
|
1971
1971
|
// Rate limiting tracking
|
|
1972
1972
|
this.requestCounts = /* @__PURE__ */ new Map();
|
|
1973
|
+
// Pricing cache (fetched from server)
|
|
1974
|
+
this.pricingCache = /* @__PURE__ */ new Map();
|
|
1975
|
+
this.pricingAliases = /* @__PURE__ */ new Map();
|
|
1973
1976
|
this.options = {
|
|
1974
1977
|
serverUrl: options.serverUrl.replace(/\/$/, ""),
|
|
1975
1978
|
apiKey: options.apiKey,
|
|
@@ -2003,7 +2006,12 @@ var ControlAgent = class {
|
|
|
2003
2006
|
await this.connectWebSocket();
|
|
2004
2007
|
} else {
|
|
2005
2008
|
logger.debug("Using HTTP polling mode (no WebSocket URL)");
|
|
2006
|
-
this.startPolling();
|
|
2009
|
+
await this.startPolling();
|
|
2010
|
+
}
|
|
2011
|
+
try {
|
|
2012
|
+
await this.fetchPricing();
|
|
2013
|
+
} catch (error) {
|
|
2014
|
+
logger.warn("Failed to fetch pricing table:", error);
|
|
2007
2015
|
}
|
|
2008
2016
|
this.startHeartbeat();
|
|
2009
2017
|
}
|
|
@@ -2137,6 +2145,62 @@ var ControlAgent = class {
|
|
|
2137
2145
|
logger.warn("Failed to fetch policy:", error);
|
|
2138
2146
|
}
|
|
2139
2147
|
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Fetch pricing table from server and cache it
|
|
2150
|
+
*/
|
|
2151
|
+
async fetchPricing() {
|
|
2152
|
+
logger.debug("Fetching pricing table from server...");
|
|
2153
|
+
try {
|
|
2154
|
+
const response = await this.httpRequest("/tsdb/pricing", "GET");
|
|
2155
|
+
if (response.ok) {
|
|
2156
|
+
const data = await response.json();
|
|
2157
|
+
const pricing = data.pricing ?? {};
|
|
2158
|
+
for (const [model, rates] of Object.entries(pricing)) {
|
|
2159
|
+
const modelLower = model.toLowerCase();
|
|
2160
|
+
this.pricingCache.set(modelLower, {
|
|
2161
|
+
input: rates.input ?? 1,
|
|
2162
|
+
output: rates.output ?? 3,
|
|
2163
|
+
cached_input: rates.cached_input ?? (rates.input ?? 1) * 0.25
|
|
2164
|
+
});
|
|
2165
|
+
if (rates.aliases) {
|
|
2166
|
+
for (const alias of rates.aliases) {
|
|
2167
|
+
this.pricingAliases.set(alias.toLowerCase(), modelLower);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
logger.debug(`Loaded pricing for ${this.pricingCache.size} models`);
|
|
2172
|
+
} else {
|
|
2173
|
+
logger.debug(`Pricing fetch returned status ${response.status}`);
|
|
2174
|
+
}
|
|
2175
|
+
} catch (error) {
|
|
2176
|
+
logger.warn("Failed to fetch pricing:", error);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Get pricing for a model from cached pricing table
|
|
2181
|
+
*/
|
|
2182
|
+
getModelPricing(model) {
|
|
2183
|
+
const fallback = { input: 1, output: 3, cached_input: 0.25 };
|
|
2184
|
+
if (!model) {
|
|
2185
|
+
return fallback;
|
|
2186
|
+
}
|
|
2187
|
+
const modelLower = model.toLowerCase();
|
|
2188
|
+
if (this.pricingCache.has(modelLower)) {
|
|
2189
|
+
return this.pricingCache.get(modelLower);
|
|
2190
|
+
}
|
|
2191
|
+
if (this.pricingAliases.has(modelLower)) {
|
|
2192
|
+
const canonical = this.pricingAliases.get(modelLower);
|
|
2193
|
+
if (this.pricingCache.has(canonical)) {
|
|
2194
|
+
return this.pricingCache.get(canonical);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
for (const [cachedModel, rates] of this.pricingCache) {
|
|
2198
|
+
if (modelLower.startsWith(cachedModel) || cachedModel.startsWith(modelLower)) {
|
|
2199
|
+
return rates;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
return fallback;
|
|
2203
|
+
}
|
|
2140
2204
|
/**
|
|
2141
2205
|
* Start heartbeat timer
|
|
2142
2206
|
*/
|
|
@@ -2261,13 +2325,14 @@ var ControlAgent = class {
|
|
|
2261
2325
|
}
|
|
2262
2326
|
if (!decision && policy.degradations) {
|
|
2263
2327
|
for (const degrade of policy.degradations) {
|
|
2264
|
-
if (degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2328
|
+
if (degrade.provider === request.provider && degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2265
2329
|
const usagePercent = budget.spent / budget.limit * 100;
|
|
2266
2330
|
if (usagePercent >= degrade.threshold_percent) {
|
|
2267
2331
|
decision = {
|
|
2268
2332
|
action: "degrade",
|
|
2269
2333
|
reason: `Budget "${budget.name}" at ${usagePercent.toFixed(1)}% (threshold: ${degrade.threshold_percent}%)`,
|
|
2270
2334
|
degradeToModel: degrade.to_model,
|
|
2335
|
+
degradeToProvider: degrade.provider,
|
|
2271
2336
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2272
2337
|
};
|
|
2273
2338
|
break;
|
|
@@ -2296,12 +2361,13 @@ var ControlAgent = class {
|
|
|
2296
2361
|
}
|
|
2297
2362
|
if (policy.degradations) {
|
|
2298
2363
|
for (const degrade of policy.degradations) {
|
|
2299
|
-
if (degrade.from_model === request.model && degrade.trigger === "always") {
|
|
2364
|
+
if (degrade.provider === request.provider && degrade.from_model === request.model && degrade.trigger === "always") {
|
|
2300
2365
|
if (!degrade.context_id || degrade.context_id === request.context_id) {
|
|
2301
2366
|
return {
|
|
2302
2367
|
action: "degrade",
|
|
2303
2368
|
reason: "Model degradation rule (always)",
|
|
2304
2369
|
degradeToModel: degrade.to_model,
|
|
2370
|
+
degradeToProvider: degrade.provider,
|
|
2305
2371
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2306
2372
|
};
|
|
2307
2373
|
}
|
|
@@ -2409,6 +2475,7 @@ var ControlAgent = class {
|
|
|
2409
2475
|
action: "degrade",
|
|
2410
2476
|
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`,
|
|
2411
2477
|
degradeToModel: budget.degradeToModel,
|
|
2478
|
+
degradeToProvider: budget.degradeToProvider,
|
|
2412
2479
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2413
2480
|
};
|
|
2414
2481
|
}
|
|
@@ -2570,13 +2637,20 @@ var ControlAgent = class {
|
|
|
2570
2637
|
}
|
|
2571
2638
|
}
|
|
2572
2639
|
/**
|
|
2573
|
-
* Estimate cost from a metric event
|
|
2574
|
-
*
|
|
2640
|
+
* Estimate cost from a metric event using server pricing table.
|
|
2641
|
+
* Falls back to default pricing if model not found.
|
|
2575
2642
|
*/
|
|
2576
2643
|
estimateCost(event) {
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2644
|
+
if (event.total_tokens === 0) {
|
|
2645
|
+
return 0;
|
|
2646
|
+
}
|
|
2647
|
+
const rates = this.getModelPricing(event.model);
|
|
2648
|
+
const cachedTokens = event.cached_tokens ?? 0;
|
|
2649
|
+
const regularInput = Math.max(0, event.input_tokens - cachedTokens);
|
|
2650
|
+
const inputCost = regularInput * rates.input / 1e6;
|
|
2651
|
+
const cachedCost = cachedTokens * rates.cached_input / 1e6;
|
|
2652
|
+
const outputCost = event.output_tokens * rates.output / 1e6;
|
|
2653
|
+
return inputCost + cachedCost + outputCost;
|
|
2580
2654
|
}
|
|
2581
2655
|
// ===========================================================================
|
|
2582
2656
|
// Hybrid Enforcement - Server-Side Budget Validation
|
|
@@ -2715,7 +2789,8 @@ var ControlAgent = class {
|
|
|
2715
2789
|
updatedSpend: data.updated_spend ?? 0,
|
|
2716
2790
|
reason: data.reason,
|
|
2717
2791
|
projectedPercent: data.projected_percent,
|
|
2718
|
-
degradeToModel: data.degrade_to_model
|
|
2792
|
+
degradeToModel: data.degrade_to_model,
|
|
2793
|
+
degradeToProvider: data.degrade_to_provider
|
|
2719
2794
|
};
|
|
2720
2795
|
} else {
|
|
2721
2796
|
logger.warn(`Server validation returned status ${response.status}`);
|
|
@@ -2744,7 +2819,8 @@ var ControlAgent = class {
|
|
|
2744
2819
|
return {
|
|
2745
2820
|
action: validation.action,
|
|
2746
2821
|
reason: validation.reason ?? `Server validation: ${validation.action}`,
|
|
2747
|
-
degradeToModel: validation.degradeToModel
|
|
2822
|
+
degradeToModel: validation.degradeToModel,
|
|
2823
|
+
degradeToProvider: validation.degradeToProvider
|
|
2748
2824
|
};
|
|
2749
2825
|
}
|
|
2750
2826
|
/**
|
|
@@ -2793,6 +2869,7 @@ var ControlAgent = class {
|
|
|
2793
2869
|
action: "degrade",
|
|
2794
2870
|
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${limit} (${projectedPercent.toFixed(1)}%)`,
|
|
2795
2871
|
degradeToModel: budget.degradeToModel,
|
|
2872
|
+
degradeToProvider: budget.degradeToProvider,
|
|
2796
2873
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2797
2874
|
};
|
|
2798
2875
|
}
|
|
@@ -3059,6 +3136,7 @@ async function getControlDecision(options, provider, model, spanId) {
|
|
|
3059
3136
|
action: decision.action,
|
|
3060
3137
|
reason: decision.reason,
|
|
3061
3138
|
degraded_to: decision.degradeToModel,
|
|
3139
|
+
degraded_to_provider: decision.degradeToProvider,
|
|
3062
3140
|
throttle_delay_ms: decision.throttleDelayMs
|
|
3063
3141
|
});
|
|
3064
3142
|
return { decision, originalModel };
|
|
@@ -3347,6 +3425,7 @@ function createControlBeforeRequestHook(controlAgent, getContextId, userHook) {
|
|
|
3347
3425
|
return {
|
|
3348
3426
|
action: "degrade",
|
|
3349
3427
|
toModel: decision.degradeToModel,
|
|
3428
|
+
toProvider: decision.degradeToProvider,
|
|
3350
3429
|
reason: decision.reason,
|
|
3351
3430
|
...decision.throttleDelayMs && { delayMs: decision.throttleDelayMs }
|
|
3352
3431
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -1853,6 +1853,9 @@ var ControlAgent = class {
|
|
|
1853
1853
|
this.errorsSinceLastHeartbeat = 0;
|
|
1854
1854
|
// Rate limiting tracking
|
|
1855
1855
|
this.requestCounts = /* @__PURE__ */ new Map();
|
|
1856
|
+
// Pricing cache (fetched from server)
|
|
1857
|
+
this.pricingCache = /* @__PURE__ */ new Map();
|
|
1858
|
+
this.pricingAliases = /* @__PURE__ */ new Map();
|
|
1856
1859
|
this.options = {
|
|
1857
1860
|
serverUrl: options.serverUrl.replace(/\/$/, ""),
|
|
1858
1861
|
apiKey: options.apiKey,
|
|
@@ -1886,7 +1889,12 @@ var ControlAgent = class {
|
|
|
1886
1889
|
await this.connectWebSocket();
|
|
1887
1890
|
} else {
|
|
1888
1891
|
logger.debug("Using HTTP polling mode (no WebSocket URL)");
|
|
1889
|
-
this.startPolling();
|
|
1892
|
+
await this.startPolling();
|
|
1893
|
+
}
|
|
1894
|
+
try {
|
|
1895
|
+
await this.fetchPricing();
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
logger.warn("Failed to fetch pricing table:", error);
|
|
1890
1898
|
}
|
|
1891
1899
|
this.startHeartbeat();
|
|
1892
1900
|
}
|
|
@@ -2020,6 +2028,62 @@ var ControlAgent = class {
|
|
|
2020
2028
|
logger.warn("Failed to fetch policy:", error);
|
|
2021
2029
|
}
|
|
2022
2030
|
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Fetch pricing table from server and cache it
|
|
2033
|
+
*/
|
|
2034
|
+
async fetchPricing() {
|
|
2035
|
+
logger.debug("Fetching pricing table from server...");
|
|
2036
|
+
try {
|
|
2037
|
+
const response = await this.httpRequest("/tsdb/pricing", "GET");
|
|
2038
|
+
if (response.ok) {
|
|
2039
|
+
const data = await response.json();
|
|
2040
|
+
const pricing = data.pricing ?? {};
|
|
2041
|
+
for (const [model, rates] of Object.entries(pricing)) {
|
|
2042
|
+
const modelLower = model.toLowerCase();
|
|
2043
|
+
this.pricingCache.set(modelLower, {
|
|
2044
|
+
input: rates.input ?? 1,
|
|
2045
|
+
output: rates.output ?? 3,
|
|
2046
|
+
cached_input: rates.cached_input ?? (rates.input ?? 1) * 0.25
|
|
2047
|
+
});
|
|
2048
|
+
if (rates.aliases) {
|
|
2049
|
+
for (const alias of rates.aliases) {
|
|
2050
|
+
this.pricingAliases.set(alias.toLowerCase(), modelLower);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
logger.debug(`Loaded pricing for ${this.pricingCache.size} models`);
|
|
2055
|
+
} else {
|
|
2056
|
+
logger.debug(`Pricing fetch returned status ${response.status}`);
|
|
2057
|
+
}
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
logger.warn("Failed to fetch pricing:", error);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Get pricing for a model from cached pricing table
|
|
2064
|
+
*/
|
|
2065
|
+
getModelPricing(model) {
|
|
2066
|
+
const fallback = { input: 1, output: 3, cached_input: 0.25 };
|
|
2067
|
+
if (!model) {
|
|
2068
|
+
return fallback;
|
|
2069
|
+
}
|
|
2070
|
+
const modelLower = model.toLowerCase();
|
|
2071
|
+
if (this.pricingCache.has(modelLower)) {
|
|
2072
|
+
return this.pricingCache.get(modelLower);
|
|
2073
|
+
}
|
|
2074
|
+
if (this.pricingAliases.has(modelLower)) {
|
|
2075
|
+
const canonical = this.pricingAliases.get(modelLower);
|
|
2076
|
+
if (this.pricingCache.has(canonical)) {
|
|
2077
|
+
return this.pricingCache.get(canonical);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
for (const [cachedModel, rates] of this.pricingCache) {
|
|
2081
|
+
if (modelLower.startsWith(cachedModel) || cachedModel.startsWith(modelLower)) {
|
|
2082
|
+
return rates;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
return fallback;
|
|
2086
|
+
}
|
|
2023
2087
|
/**
|
|
2024
2088
|
* Start heartbeat timer
|
|
2025
2089
|
*/
|
|
@@ -2144,13 +2208,14 @@ var ControlAgent = class {
|
|
|
2144
2208
|
}
|
|
2145
2209
|
if (!decision && policy.degradations) {
|
|
2146
2210
|
for (const degrade of policy.degradations) {
|
|
2147
|
-
if (degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2211
|
+
if (degrade.provider === request.provider && degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2148
2212
|
const usagePercent = budget.spent / budget.limit * 100;
|
|
2149
2213
|
if (usagePercent >= degrade.threshold_percent) {
|
|
2150
2214
|
decision = {
|
|
2151
2215
|
action: "degrade",
|
|
2152
2216
|
reason: `Budget "${budget.name}" at ${usagePercent.toFixed(1)}% (threshold: ${degrade.threshold_percent}%)`,
|
|
2153
2217
|
degradeToModel: degrade.to_model,
|
|
2218
|
+
degradeToProvider: degrade.provider,
|
|
2154
2219
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2155
2220
|
};
|
|
2156
2221
|
break;
|
|
@@ -2179,12 +2244,13 @@ var ControlAgent = class {
|
|
|
2179
2244
|
}
|
|
2180
2245
|
if (policy.degradations) {
|
|
2181
2246
|
for (const degrade of policy.degradations) {
|
|
2182
|
-
if (degrade.from_model === request.model && degrade.trigger === "always") {
|
|
2247
|
+
if (degrade.provider === request.provider && degrade.from_model === request.model && degrade.trigger === "always") {
|
|
2183
2248
|
if (!degrade.context_id || degrade.context_id === request.context_id) {
|
|
2184
2249
|
return {
|
|
2185
2250
|
action: "degrade",
|
|
2186
2251
|
reason: "Model degradation rule (always)",
|
|
2187
2252
|
degradeToModel: degrade.to_model,
|
|
2253
|
+
degradeToProvider: degrade.provider,
|
|
2188
2254
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2189
2255
|
};
|
|
2190
2256
|
}
|
|
@@ -2292,6 +2358,7 @@ var ControlAgent = class {
|
|
|
2292
2358
|
action: "degrade",
|
|
2293
2359
|
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`,
|
|
2294
2360
|
degradeToModel: budget.degradeToModel,
|
|
2361
|
+
degradeToProvider: budget.degradeToProvider,
|
|
2295
2362
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2296
2363
|
};
|
|
2297
2364
|
}
|
|
@@ -2453,13 +2520,20 @@ var ControlAgent = class {
|
|
|
2453
2520
|
}
|
|
2454
2521
|
}
|
|
2455
2522
|
/**
|
|
2456
|
-
* Estimate cost from a metric event
|
|
2457
|
-
*
|
|
2523
|
+
* Estimate cost from a metric event using server pricing table.
|
|
2524
|
+
* Falls back to default pricing if model not found.
|
|
2458
2525
|
*/
|
|
2459
2526
|
estimateCost(event) {
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2527
|
+
if (event.total_tokens === 0) {
|
|
2528
|
+
return 0;
|
|
2529
|
+
}
|
|
2530
|
+
const rates = this.getModelPricing(event.model);
|
|
2531
|
+
const cachedTokens = event.cached_tokens ?? 0;
|
|
2532
|
+
const regularInput = Math.max(0, event.input_tokens - cachedTokens);
|
|
2533
|
+
const inputCost = regularInput * rates.input / 1e6;
|
|
2534
|
+
const cachedCost = cachedTokens * rates.cached_input / 1e6;
|
|
2535
|
+
const outputCost = event.output_tokens * rates.output / 1e6;
|
|
2536
|
+
return inputCost + cachedCost + outputCost;
|
|
2463
2537
|
}
|
|
2464
2538
|
// ===========================================================================
|
|
2465
2539
|
// Hybrid Enforcement - Server-Side Budget Validation
|
|
@@ -2598,7 +2672,8 @@ var ControlAgent = class {
|
|
|
2598
2672
|
updatedSpend: data.updated_spend ?? 0,
|
|
2599
2673
|
reason: data.reason,
|
|
2600
2674
|
projectedPercent: data.projected_percent,
|
|
2601
|
-
degradeToModel: data.degrade_to_model
|
|
2675
|
+
degradeToModel: data.degrade_to_model,
|
|
2676
|
+
degradeToProvider: data.degrade_to_provider
|
|
2602
2677
|
};
|
|
2603
2678
|
} else {
|
|
2604
2679
|
logger.warn(`Server validation returned status ${response.status}`);
|
|
@@ -2627,7 +2702,8 @@ var ControlAgent = class {
|
|
|
2627
2702
|
return {
|
|
2628
2703
|
action: validation.action,
|
|
2629
2704
|
reason: validation.reason ?? `Server validation: ${validation.action}`,
|
|
2630
|
-
degradeToModel: validation.degradeToModel
|
|
2705
|
+
degradeToModel: validation.degradeToModel,
|
|
2706
|
+
degradeToProvider: validation.degradeToProvider
|
|
2631
2707
|
};
|
|
2632
2708
|
}
|
|
2633
2709
|
/**
|
|
@@ -2676,6 +2752,7 @@ var ControlAgent = class {
|
|
|
2676
2752
|
action: "degrade",
|
|
2677
2753
|
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${limit} (${projectedPercent.toFixed(1)}%)`,
|
|
2678
2754
|
degradeToModel: budget.degradeToModel,
|
|
2755
|
+
degradeToProvider: budget.degradeToProvider,
|
|
2679
2756
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2680
2757
|
};
|
|
2681
2758
|
}
|
|
@@ -2942,6 +3019,7 @@ async function getControlDecision(options, provider, model, spanId) {
|
|
|
2942
3019
|
action: decision.action,
|
|
2943
3020
|
reason: decision.reason,
|
|
2944
3021
|
degraded_to: decision.degradeToModel,
|
|
3022
|
+
degraded_to_provider: decision.degradeToProvider,
|
|
2945
3023
|
throttle_delay_ms: decision.throttleDelayMs
|
|
2946
3024
|
});
|
|
2947
3025
|
return { decision, originalModel };
|
|
@@ -3230,6 +3308,7 @@ function createControlBeforeRequestHook(controlAgent, getContextId, userHook) {
|
|
|
3230
3308
|
return {
|
|
3231
3309
|
action: "degrade",
|
|
3232
3310
|
toModel: decision.degradeToModel,
|
|
3311
|
+
toProvider: decision.degradeToProvider,
|
|
3233
3312
|
reason: decision.reason,
|
|
3234
3313
|
...decision.throttleDelayMs && { delayMs: decision.throttleDelayMs }
|
|
3235
3314
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aden-ts",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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",
|