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 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
- * Uses gpt-4o pricing as default: $2.50/1M input, $10/1M output
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
- * Uses gpt-4o pricing as default: $2.50/1M input, $10/1M output
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
- * Uses gpt-4o pricing as default: $2.50/1M input, $10/1M output
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
- const inputCost = event.input_tokens * 25e-7;
2578
- const outputCost = event.output_tokens * 1e-5;
2579
- return inputCost + outputCost;
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
- * Uses gpt-4o pricing as default: $2.50/1M input, $10/1M output
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
- const inputCost = event.input_tokens * 25e-7;
2461
- const outputCost = event.output_tokens * 1e-5;
2462
- return inputCost + outputCost;
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.1",
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",