aden-ts 0.1.1 → 0.2.1
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 +280 -179
- package/dist/index.mjs +280 -179
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1739,6 +1739,101 @@ function isAnthropicInstrumented() {
|
|
|
1739
1739
|
// src/control-agent.ts
|
|
1740
1740
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
1741
1741
|
import WebSocket from "ws";
|
|
1742
|
+
|
|
1743
|
+
// src/logging.ts
|
|
1744
|
+
var LOG_LEVEL_PRIORITY = {
|
|
1745
|
+
debug: 0,
|
|
1746
|
+
info: 1,
|
|
1747
|
+
warn: 2,
|
|
1748
|
+
error: 3,
|
|
1749
|
+
silent: 4
|
|
1750
|
+
};
|
|
1751
|
+
var currentConfig = {
|
|
1752
|
+
level: parseLogLevel(process.env.ADEN_LOG_LEVEL) ?? "info",
|
|
1753
|
+
metricsLevel: parseLogLevel(process.env.ADEN_METRICS_LOG_LEVEL) ?? parseLogLevel(process.env.ADEN_LOG_LEVEL) ?? "info",
|
|
1754
|
+
handler: console
|
|
1755
|
+
};
|
|
1756
|
+
function parseLogLevel(level) {
|
|
1757
|
+
if (!level) return void 0;
|
|
1758
|
+
const normalized = level.toLowerCase();
|
|
1759
|
+
if (normalized in LOG_LEVEL_PRIORITY) {
|
|
1760
|
+
return normalized;
|
|
1761
|
+
}
|
|
1762
|
+
return void 0;
|
|
1763
|
+
}
|
|
1764
|
+
function shouldLog(level, configLevel) {
|
|
1765
|
+
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[configLevel];
|
|
1766
|
+
}
|
|
1767
|
+
function configureLogging(config) {
|
|
1768
|
+
if (config.level !== void 0) {
|
|
1769
|
+
currentConfig.level = config.level;
|
|
1770
|
+
if (config.metricsLevel === void 0) {
|
|
1771
|
+
currentConfig.metricsLevel = config.level;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
if (config.metricsLevel !== void 0) {
|
|
1775
|
+
currentConfig.metricsLevel = config.metricsLevel;
|
|
1776
|
+
}
|
|
1777
|
+
if (config.handler !== void 0) {
|
|
1778
|
+
currentConfig.handler = config.handler;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function getLoggingConfig() {
|
|
1782
|
+
return { ...currentConfig };
|
|
1783
|
+
}
|
|
1784
|
+
function resetLoggingConfig() {
|
|
1785
|
+
currentConfig = {
|
|
1786
|
+
level: parseLogLevel(process.env.ADEN_LOG_LEVEL) ?? "info",
|
|
1787
|
+
metricsLevel: parseLogLevel(process.env.ADEN_METRICS_LOG_LEVEL) ?? parseLogLevel(process.env.ADEN_LOG_LEVEL) ?? "info",
|
|
1788
|
+
handler: console
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
var logger = {
|
|
1792
|
+
debug(message, ...args) {
|
|
1793
|
+
if (shouldLog("debug", currentConfig.level)) {
|
|
1794
|
+
currentConfig.handler.debug(`[aden] ${message}`, ...args);
|
|
1795
|
+
}
|
|
1796
|
+
},
|
|
1797
|
+
info(message, ...args) {
|
|
1798
|
+
if (shouldLog("info", currentConfig.level)) {
|
|
1799
|
+
currentConfig.handler.info(`[aden] ${message}`, ...args);
|
|
1800
|
+
}
|
|
1801
|
+
},
|
|
1802
|
+
warn(message, ...args) {
|
|
1803
|
+
if (shouldLog("warn", currentConfig.level)) {
|
|
1804
|
+
currentConfig.handler.warn(`[aden] ${message}`, ...args);
|
|
1805
|
+
}
|
|
1806
|
+
},
|
|
1807
|
+
error(message, ...args) {
|
|
1808
|
+
if (shouldLog("error", currentConfig.level)) {
|
|
1809
|
+
currentConfig.handler.error(`[aden] ${message}`, ...args);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
var metricsLogger = {
|
|
1814
|
+
debug(message, ...args) {
|
|
1815
|
+
if (shouldLog("debug", currentConfig.metricsLevel)) {
|
|
1816
|
+
currentConfig.handler.debug(`[aden.metrics] ${message}`, ...args);
|
|
1817
|
+
}
|
|
1818
|
+
},
|
|
1819
|
+
info(message, ...args) {
|
|
1820
|
+
if (shouldLog("info", currentConfig.metricsLevel)) {
|
|
1821
|
+
currentConfig.handler.info(`[aden.metrics] ${message}`, ...args);
|
|
1822
|
+
}
|
|
1823
|
+
},
|
|
1824
|
+
warn(message, ...args) {
|
|
1825
|
+
if (shouldLog("warn", currentConfig.metricsLevel)) {
|
|
1826
|
+
currentConfig.handler.warn(`[aden.metrics] ${message}`, ...args);
|
|
1827
|
+
}
|
|
1828
|
+
},
|
|
1829
|
+
error(message, ...args) {
|
|
1830
|
+
if (shouldLog("error", currentConfig.metricsLevel)) {
|
|
1831
|
+
currentConfig.handler.error(`[aden.metrics] ${message}`, ...args);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
};
|
|
1835
|
+
|
|
1836
|
+
// src/control-agent.ts
|
|
1742
1837
|
var SDK_VERSION = "0.1.0";
|
|
1743
1838
|
var ControlAgent = class {
|
|
1744
1839
|
constructor(options) {
|
|
@@ -1769,12 +1864,12 @@ var ControlAgent = class {
|
|
|
1769
1864
|
instanceId: options.instanceId ?? randomUUID6(),
|
|
1770
1865
|
onAlert: options.onAlert ?? (() => {
|
|
1771
1866
|
}),
|
|
1772
|
-
// Hybrid enforcement options
|
|
1773
|
-
enableHybridEnforcement: options.enableHybridEnforcement ??
|
|
1774
|
-
serverValidationThreshold: options.serverValidationThreshold ??
|
|
1867
|
+
// Hybrid enforcement options (defaults match Python SDK)
|
|
1868
|
+
enableHybridEnforcement: options.enableHybridEnforcement ?? true,
|
|
1869
|
+
serverValidationThreshold: options.serverValidationThreshold ?? 5,
|
|
1775
1870
|
serverValidationTimeoutMs: options.serverValidationTimeoutMs ?? 2e3,
|
|
1776
1871
|
adaptiveThresholdEnabled: options.adaptiveThresholdEnabled ?? true,
|
|
1777
|
-
adaptiveMinRemainingUsd: options.adaptiveMinRemainingUsd ??
|
|
1872
|
+
adaptiveMinRemainingUsd: options.adaptiveMinRemainingUsd ?? 5,
|
|
1778
1873
|
samplingEnabled: options.samplingEnabled ?? true,
|
|
1779
1874
|
samplingBaseRate: options.samplingBaseRate ?? 0.1,
|
|
1780
1875
|
samplingFullValidationPercent: options.samplingFullValidationPercent ?? 95,
|
|
@@ -1786,9 +1881,11 @@ var ControlAgent = class {
|
|
|
1786
1881
|
*/
|
|
1787
1882
|
async connect() {
|
|
1788
1883
|
const url = this.options.serverUrl;
|
|
1884
|
+
logger.debug(`Connecting to control server: ${url}`);
|
|
1789
1885
|
if (url.startsWith("wss://") || url.startsWith("ws://")) {
|
|
1790
1886
|
await this.connectWebSocket();
|
|
1791
1887
|
} else {
|
|
1888
|
+
logger.debug("Using HTTP polling mode (no WebSocket URL)");
|
|
1792
1889
|
this.startPolling();
|
|
1793
1890
|
}
|
|
1794
1891
|
this.startHeartbeat();
|
|
@@ -1804,6 +1901,7 @@ var ControlAgent = class {
|
|
|
1804
1901
|
};
|
|
1805
1902
|
try {
|
|
1806
1903
|
const wsUrl = `${this.options.serverUrl}/v1/control/ws`;
|
|
1904
|
+
logger.debug(`Attempting WebSocket connection to: ${wsUrl}`);
|
|
1807
1905
|
this.ws = new WebSocket(wsUrl, {
|
|
1808
1906
|
headers: {
|
|
1809
1907
|
Authorization: `Bearer ${this.options.apiKey}`,
|
|
@@ -1813,7 +1911,7 @@ var ControlAgent = class {
|
|
|
1813
1911
|
this.ws.on("open", () => {
|
|
1814
1912
|
this.connected = true;
|
|
1815
1913
|
this.reconnectAttempts = 0;
|
|
1816
|
-
|
|
1914
|
+
logger.info("WebSocket connected to control server");
|
|
1817
1915
|
this.flushEventQueue();
|
|
1818
1916
|
resolve();
|
|
1819
1917
|
});
|
|
@@ -1822,12 +1920,12 @@ var ControlAgent = class {
|
|
|
1822
1920
|
});
|
|
1823
1921
|
this.ws.on("close", () => {
|
|
1824
1922
|
this.connected = false;
|
|
1825
|
-
|
|
1923
|
+
logger.info("WebSocket disconnected, falling back to polling");
|
|
1826
1924
|
this.scheduleReconnect();
|
|
1827
1925
|
this.startPolling();
|
|
1828
1926
|
});
|
|
1829
1927
|
this.ws.on("error", (error) => {
|
|
1830
|
-
|
|
1928
|
+
logger.warn("WebSocket error:", error.message);
|
|
1831
1929
|
this.errorsSinceLastHeartbeat++;
|
|
1832
1930
|
if (!this.connected) {
|
|
1833
1931
|
fallbackToPolling();
|
|
@@ -1835,12 +1933,12 @@ var ControlAgent = class {
|
|
|
1835
1933
|
});
|
|
1836
1934
|
setTimeout(() => {
|
|
1837
1935
|
if (!this.connected) {
|
|
1838
|
-
|
|
1936
|
+
logger.warn("WebSocket connection timeout, using polling");
|
|
1839
1937
|
fallbackToPolling();
|
|
1840
1938
|
}
|
|
1841
1939
|
}, this.options.timeoutMs);
|
|
1842
1940
|
} catch (error) {
|
|
1843
|
-
|
|
1941
|
+
logger.warn("WebSocket setup failed:", error);
|
|
1844
1942
|
fallbackToPolling();
|
|
1845
1943
|
}
|
|
1846
1944
|
});
|
|
@@ -1851,7 +1949,7 @@ var ControlAgent = class {
|
|
|
1851
1949
|
scheduleReconnect() {
|
|
1852
1950
|
if (this.reconnectTimer) return;
|
|
1853
1951
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
1854
|
-
|
|
1952
|
+
logger.warn("Max reconnect attempts reached, using polling only");
|
|
1855
1953
|
return;
|
|
1856
1954
|
}
|
|
1857
1955
|
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
@@ -1872,12 +1970,12 @@ var ControlAgent = class {
|
|
|
1872
1970
|
if (message.type === "policy") {
|
|
1873
1971
|
this.cachedPolicy = message.policy;
|
|
1874
1972
|
this.lastPolicyFetch = Date.now();
|
|
1875
|
-
|
|
1973
|
+
logger.info("Policy updated:", this.cachedPolicy.version);
|
|
1876
1974
|
} else if (message.type === "command") {
|
|
1877
|
-
|
|
1975
|
+
logger.info("Command received:", message);
|
|
1878
1976
|
}
|
|
1879
1977
|
} catch (error) {
|
|
1880
|
-
|
|
1978
|
+
logger.warn("Failed to parse message:", error);
|
|
1881
1979
|
}
|
|
1882
1980
|
}
|
|
1883
1981
|
/**
|
|
@@ -1886,6 +1984,7 @@ var ControlAgent = class {
|
|
|
1886
1984
|
*/
|
|
1887
1985
|
async startPolling() {
|
|
1888
1986
|
if (this.pollingTimer) return;
|
|
1987
|
+
logger.debug(`Starting HTTP polling (interval: ${this.options.pollingIntervalMs}ms)`);
|
|
1889
1988
|
await this.fetchPolicy();
|
|
1890
1989
|
this.pollingTimer = setInterval(() => {
|
|
1891
1990
|
if (!this.connected) {
|
|
@@ -1906,15 +2005,19 @@ var ControlAgent = class {
|
|
|
1906
2005
|
* Fetch policy via HTTP
|
|
1907
2006
|
*/
|
|
1908
2007
|
async fetchPolicy() {
|
|
2008
|
+
logger.debug("Fetching policy from server...");
|
|
1909
2009
|
try {
|
|
1910
2010
|
const response = await this.httpRequest("/v1/control/policy", "GET");
|
|
1911
2011
|
if (response.ok) {
|
|
1912
2012
|
const policy = await response.json();
|
|
1913
2013
|
this.cachedPolicy = policy;
|
|
1914
2014
|
this.lastPolicyFetch = Date.now();
|
|
2015
|
+
logger.debug(`Policy fetched successfully (version: ${policy.version}, budgets: ${policy.budgets?.length ?? 0})`);
|
|
2016
|
+
} else {
|
|
2017
|
+
logger.debug(`Policy fetch returned status ${response.status}`);
|
|
1915
2018
|
}
|
|
1916
2019
|
} catch (error) {
|
|
1917
|
-
|
|
2020
|
+
logger.warn("Failed to fetch policy:", error);
|
|
1918
2021
|
}
|
|
1919
2022
|
}
|
|
1920
2023
|
/**
|
|
@@ -1922,6 +2025,7 @@ var ControlAgent = class {
|
|
|
1922
2025
|
*/
|
|
1923
2026
|
startHeartbeat() {
|
|
1924
2027
|
if (this.heartbeatTimer) return;
|
|
2028
|
+
logger.debug(`Starting heartbeat (interval: ${this.options.heartbeatIntervalMs}ms)`);
|
|
1925
2029
|
this.heartbeatTimer = setInterval(() => {
|
|
1926
2030
|
this.sendHeartbeat();
|
|
1927
2031
|
}, this.options.heartbeatIntervalMs);
|
|
@@ -1958,6 +2062,7 @@ var ControlAgent = class {
|
|
|
1958
2062
|
* Disconnect from the control server
|
|
1959
2063
|
*/
|
|
1960
2064
|
async disconnect() {
|
|
2065
|
+
logger.debug("Disconnecting from control server...");
|
|
1961
2066
|
this.stopPolling();
|
|
1962
2067
|
this.stopHeartbeat();
|
|
1963
2068
|
if (this.reconnectTimer) {
|
|
@@ -1969,6 +2074,7 @@ var ControlAgent = class {
|
|
|
1969
2074
|
this.ws = null;
|
|
1970
2075
|
}
|
|
1971
2076
|
this.connected = false;
|
|
2077
|
+
logger.debug("Disconnected from control server");
|
|
1972
2078
|
}
|
|
1973
2079
|
/**
|
|
1974
2080
|
* Get a control decision for a request
|
|
@@ -2022,66 +2128,53 @@ var ControlAgent = class {
|
|
|
2022
2128
|
}
|
|
2023
2129
|
if (policy.budgets) {
|
|
2024
2130
|
const applicableBudgets = this.findApplicableBudgets(policy.budgets, request);
|
|
2025
|
-
if (
|
|
2131
|
+
if (applicableBudgets.length > 0) {
|
|
2132
|
+
let mostRestrictiveDecision = null;
|
|
2133
|
+
let mostRestrictivePriority = -1;
|
|
2026
2134
|
for (const budget of applicableBudgets) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2135
|
+
let decision = null;
|
|
2136
|
+
if (this.options.enableHybridEnforcement) {
|
|
2137
|
+
decision = await this.evaluateBudgetWithHybridEnforcement(
|
|
2138
|
+
request,
|
|
2139
|
+
budget,
|
|
2140
|
+
throttleInfo
|
|
2141
|
+
);
|
|
2142
|
+
} else {
|
|
2143
|
+
decision = this.evaluateBudgetLocally(request, budget, throttleInfo);
|
|
2034
2144
|
}
|
|
2035
|
-
if (policy.degradations) {
|
|
2145
|
+
if (!decision && policy.degradations) {
|
|
2036
2146
|
for (const degrade of policy.degradations) {
|
|
2037
2147
|
if (degrade.from_model === request.model && degrade.trigger === "budget_threshold" && degrade.threshold_percent) {
|
|
2038
2148
|
const usagePercent = budget.spent / budget.limit * 100;
|
|
2039
2149
|
if (usagePercent >= degrade.threshold_percent) {
|
|
2040
|
-
|
|
2150
|
+
decision = {
|
|
2041
2151
|
action: "degrade",
|
|
2042
2152
|
reason: `Budget "${budget.name}" at ${usagePercent.toFixed(1)}% (threshold: ${degrade.threshold_percent}%)`,
|
|
2043
2153
|
degradeToModel: degrade.to_model,
|
|
2044
2154
|
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2045
2155
|
};
|
|
2156
|
+
break;
|
|
2046
2157
|
}
|
|
2047
2158
|
}
|
|
2048
2159
|
}
|
|
2049
2160
|
}
|
|
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
|
-
};
|
|
2161
|
+
if (decision) {
|
|
2162
|
+
const priority = this.getActionPriority(decision.action);
|
|
2163
|
+
if (priority > mostRestrictivePriority) {
|
|
2164
|
+
mostRestrictiveDecision = decision;
|
|
2165
|
+
mostRestrictivePriority = priority;
|
|
2062
2166
|
}
|
|
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
|
-
}
|
|
2167
|
+
if (decision.action === "block") {
|
|
2168
|
+
break;
|
|
2082
2169
|
}
|
|
2083
2170
|
}
|
|
2084
2171
|
}
|
|
2172
|
+
if (mostRestrictiveDecision) {
|
|
2173
|
+
if (throttleInfo && mostRestrictiveDecision.action !== "block" && !mostRestrictiveDecision.throttleDelayMs) {
|
|
2174
|
+
mostRestrictiveDecision.throttleDelayMs = throttleInfo.delayMs;
|
|
2175
|
+
}
|
|
2176
|
+
return mostRestrictiveDecision;
|
|
2177
|
+
}
|
|
2085
2178
|
}
|
|
2086
2179
|
}
|
|
2087
2180
|
if (policy.degradations) {
|
|
@@ -2111,7 +2204,7 @@ var ControlAgent = class {
|
|
|
2111
2204
|
timestamp: /* @__PURE__ */ new Date()
|
|
2112
2205
|
};
|
|
2113
2206
|
Promise.resolve(this.options.onAlert(alertEvent)).catch((err) => {
|
|
2114
|
-
|
|
2207
|
+
logger.warn("Alert callback error:", err);
|
|
2115
2208
|
});
|
|
2116
2209
|
return {
|
|
2117
2210
|
action: "alert",
|
|
@@ -2174,36 +2267,96 @@ var ControlAgent = class {
|
|
|
2174
2267
|
return true;
|
|
2175
2268
|
}
|
|
2176
2269
|
/**
|
|
2177
|
-
*
|
|
2270
|
+
* Get action priority for finding most restrictive decision.
|
|
2271
|
+
* Higher priority = more restrictive.
|
|
2272
|
+
*/
|
|
2273
|
+
getActionPriority(action) {
|
|
2274
|
+
const priority = {
|
|
2275
|
+
allow: 0,
|
|
2276
|
+
alert: 1,
|
|
2277
|
+
throttle: 2,
|
|
2278
|
+
degrade: 3,
|
|
2279
|
+
block: 4
|
|
2280
|
+
};
|
|
2281
|
+
return priority[action] ?? 0;
|
|
2282
|
+
}
|
|
2283
|
+
/**
|
|
2284
|
+
* Evaluate a single budget using local-only enforcement.
|
|
2285
|
+
* Returns a decision if the budget triggers an action, null otherwise.
|
|
2286
|
+
*/
|
|
2287
|
+
evaluateBudgetLocally(request, budget, throttleInfo) {
|
|
2288
|
+
const projectedSpend = budget.spent + (request.estimated_cost ?? 0);
|
|
2289
|
+
if (projectedSpend > budget.limit) {
|
|
2290
|
+
if (budget.limitAction === "degrade" && budget.degradeToModel) {
|
|
2291
|
+
return {
|
|
2292
|
+
action: "degrade",
|
|
2293
|
+
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`,
|
|
2294
|
+
degradeToModel: budget.degradeToModel,
|
|
2295
|
+
...throttleInfo && { throttleDelayMs: throttleInfo.delayMs }
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
2298
|
+
const action = budget.limitAction === "kill" ? "block" : budget.limitAction;
|
|
2299
|
+
return {
|
|
2300
|
+
action,
|
|
2301
|
+
reason: `Budget "${budget.name}" exceeded: $${projectedSpend.toFixed(4)} > $${budget.limit}`
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
return null;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Find budgets that apply to the given request based on budget type.
|
|
2308
|
+
*
|
|
2309
|
+
* Matching logic by budget type:
|
|
2310
|
+
* - global: Matches ALL requests
|
|
2311
|
+
* - agent: Matches if request.metadata.agent == budget.name or budget.id
|
|
2312
|
+
* - tenant: Matches if request.metadata.tenant_id == budget.name or budget.id
|
|
2313
|
+
* - customer: Matches if request.metadata.customer_id == budget.name or budget.id
|
|
2314
|
+
* - feature: Matches if request.metadata.feature == budget.name or budget.id
|
|
2315
|
+
* - tag: Matches if any request.metadata.tags intersect with budget.tags
|
|
2316
|
+
* - legacy (context_id): Matches if request.context_id == budget.context_id
|
|
2178
2317
|
*/
|
|
2179
2318
|
findApplicableBudgets(budgets, request) {
|
|
2180
2319
|
const result = [];
|
|
2181
2320
|
const metadata = request.metadata || {};
|
|
2182
2321
|
for (const budget of budgets) {
|
|
2322
|
+
if (budget.context_id && request.context_id) {
|
|
2323
|
+
if (budget.context_id === request.context_id) {
|
|
2324
|
+
result.push(budget);
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2183
2328
|
switch (budget.type) {
|
|
2184
2329
|
case "global":
|
|
2185
2330
|
result.push(budget);
|
|
2186
2331
|
break;
|
|
2187
|
-
case "agent":
|
|
2188
|
-
|
|
2332
|
+
case "agent": {
|
|
2333
|
+
const agent = metadata.agent;
|
|
2334
|
+
if (agent && (agent === budget.name || agent === budget.id)) {
|
|
2189
2335
|
result.push(budget);
|
|
2190
2336
|
}
|
|
2191
2337
|
break;
|
|
2192
|
-
|
|
2193
|
-
|
|
2338
|
+
}
|
|
2339
|
+
case "tenant": {
|
|
2340
|
+
const tenantId = metadata.tenant_id;
|
|
2341
|
+
if (tenantId && (tenantId === budget.name || tenantId === budget.id)) {
|
|
2194
2342
|
result.push(budget);
|
|
2195
2343
|
}
|
|
2196
2344
|
break;
|
|
2197
|
-
|
|
2198
|
-
|
|
2345
|
+
}
|
|
2346
|
+
case "customer": {
|
|
2347
|
+
const customerId = metadata.customer_id;
|
|
2348
|
+
if (customerId && (customerId === budget.name || customerId === budget.id) || request.context_id && (request.context_id === budget.name || request.context_id === budget.id)) {
|
|
2199
2349
|
result.push(budget);
|
|
2200
2350
|
}
|
|
2201
2351
|
break;
|
|
2202
|
-
|
|
2203
|
-
|
|
2352
|
+
}
|
|
2353
|
+
case "feature": {
|
|
2354
|
+
const feature = metadata.feature;
|
|
2355
|
+
if (feature && (feature === budget.name || feature === budget.id)) {
|
|
2204
2356
|
result.push(budget);
|
|
2205
2357
|
}
|
|
2206
2358
|
break;
|
|
2359
|
+
}
|
|
2207
2360
|
case "tag":
|
|
2208
2361
|
if (budget.tags && Array.isArray(metadata.tags)) {
|
|
2209
2362
|
const requestTags = metadata.tags;
|
|
@@ -2253,13 +2406,48 @@ var ControlAgent = class {
|
|
|
2253
2406
|
if (this.cachedPolicy?.budgets && event.total_tokens > 0) {
|
|
2254
2407
|
const estimatedCost = this.estimateCost(event);
|
|
2255
2408
|
const contextId2 = this.options.getContextId?.();
|
|
2409
|
+
const metadata = event.metadata || {};
|
|
2256
2410
|
for (const budget of this.cachedPolicy.budgets) {
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2411
|
+
let shouldUpdate = false;
|
|
2412
|
+
switch (budget.type) {
|
|
2413
|
+
case "global":
|
|
2414
|
+
shouldUpdate = true;
|
|
2415
|
+
break;
|
|
2416
|
+
case "agent": {
|
|
2417
|
+
const agent = metadata.agent;
|
|
2418
|
+
shouldUpdate = Boolean(agent && (agent === budget.name || agent === budget.id));
|
|
2419
|
+
break;
|
|
2262
2420
|
}
|
|
2421
|
+
case "tenant": {
|
|
2422
|
+
const tenantId = metadata.tenant_id;
|
|
2423
|
+
shouldUpdate = Boolean(tenantId && (tenantId === budget.name || tenantId === budget.id));
|
|
2424
|
+
break;
|
|
2425
|
+
}
|
|
2426
|
+
case "customer": {
|
|
2427
|
+
const customerId = metadata.customer_id;
|
|
2428
|
+
shouldUpdate = Boolean(
|
|
2429
|
+
customerId && (customerId === budget.name || customerId === budget.id) || contextId2 && (contextId2 === budget.name || contextId2 === budget.id)
|
|
2430
|
+
);
|
|
2431
|
+
break;
|
|
2432
|
+
}
|
|
2433
|
+
case "feature": {
|
|
2434
|
+
const feature = metadata.feature;
|
|
2435
|
+
shouldUpdate = Boolean(feature && (feature === budget.name || feature === budget.id));
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
case "tag": {
|
|
2439
|
+
if (budget.tags && Array.isArray(metadata.tags)) {
|
|
2440
|
+
const requestTags = metadata.tags;
|
|
2441
|
+
shouldUpdate = budget.tags.some((tag) => requestTags.includes(tag));
|
|
2442
|
+
}
|
|
2443
|
+
break;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
if (!shouldUpdate && budget.context_id && contextId2) {
|
|
2447
|
+
shouldUpdate = budget.context_id === contextId2;
|
|
2448
|
+
}
|
|
2449
|
+
if (shouldUpdate) {
|
|
2450
|
+
budget.spent += estimatedCost;
|
|
2263
2451
|
}
|
|
2264
2452
|
}
|
|
2265
2453
|
}
|
|
@@ -2323,8 +2511,8 @@ var ControlAgent = class {
|
|
|
2323
2511
|
}
|
|
2324
2512
|
if (this.options.adaptiveThresholdEnabled) {
|
|
2325
2513
|
if (remainingBudgetUsd <= this.options.adaptiveMinRemainingUsd) {
|
|
2326
|
-
|
|
2327
|
-
`
|
|
2514
|
+
logger.debug(
|
|
2515
|
+
`Remaining budget $${remainingBudgetUsd.toFixed(4)} <= $${this.options.adaptiveMinRemainingUsd.toFixed(2)}, forcing validation`
|
|
2328
2516
|
);
|
|
2329
2517
|
return true;
|
|
2330
2518
|
}
|
|
@@ -2333,13 +2521,13 @@ var ControlAgent = class {
|
|
|
2333
2521
|
const samplingRate = this.calculateSamplingRate(budgetUsagePercent);
|
|
2334
2522
|
const shouldSample = Math.random() < samplingRate;
|
|
2335
2523
|
if (!shouldSample) {
|
|
2336
|
-
|
|
2337
|
-
`
|
|
2524
|
+
logger.debug(
|
|
2525
|
+
`Skipping validation (sampling rate: ${(samplingRate * 100).toFixed(1)}%, usage: ${budgetUsagePercent.toFixed(1)}%)`
|
|
2338
2526
|
);
|
|
2339
2527
|
return false;
|
|
2340
2528
|
}
|
|
2341
|
-
|
|
2342
|
-
`
|
|
2529
|
+
logger.debug(
|
|
2530
|
+
`Sampled for validation (rate: ${(samplingRate * 100).toFixed(1)}%, usage: ${budgetUsagePercent.toFixed(1)}%)`
|
|
2343
2531
|
);
|
|
2344
2532
|
return true;
|
|
2345
2533
|
}
|
|
@@ -2397,8 +2585,8 @@ var ControlAgent = class {
|
|
|
2397
2585
|
});
|
|
2398
2586
|
if (response.ok) {
|
|
2399
2587
|
const data = await response.json();
|
|
2400
|
-
|
|
2401
|
-
`
|
|
2588
|
+
logger.debug(
|
|
2589
|
+
`Server validation response: allowed=${data.allowed}, action=${data.action}, reason=${data.reason}`
|
|
2402
2590
|
);
|
|
2403
2591
|
return {
|
|
2404
2592
|
allowed: data.allowed ?? true,
|
|
@@ -2413,14 +2601,14 @@ var ControlAgent = class {
|
|
|
2413
2601
|
degradeToModel: data.degrade_to_model
|
|
2414
2602
|
};
|
|
2415
2603
|
} else {
|
|
2416
|
-
|
|
2604
|
+
logger.warn(`Server validation returned status ${response.status}`);
|
|
2417
2605
|
return null;
|
|
2418
2606
|
}
|
|
2419
2607
|
} finally {
|
|
2420
2608
|
clearTimeout(timeoutId);
|
|
2421
2609
|
}
|
|
2422
2610
|
} catch (error) {
|
|
2423
|
-
|
|
2611
|
+
logger.warn(`Server validation failed: ${error}`);
|
|
2424
2612
|
return null;
|
|
2425
2613
|
}
|
|
2426
2614
|
}
|
|
@@ -2460,8 +2648,8 @@ var ControlAgent = class {
|
|
|
2460
2648
|
}
|
|
2461
2649
|
}
|
|
2462
2650
|
if (this.shouldValidateWithServer(usagePercent, remaining, limit)) {
|
|
2463
|
-
|
|
2464
|
-
`
|
|
2651
|
+
logger.debug(
|
|
2652
|
+
`Budget '${budget.name}' at ${usagePercent.toFixed(1)}% ($${currentSpend.toFixed(6)}/$${limit.toFixed(6)}), validating with server`
|
|
2465
2653
|
);
|
|
2466
2654
|
const validation = await this.validateBudgetWithServer(
|
|
2467
2655
|
budget.id,
|
|
@@ -2471,8 +2659,8 @@ var ControlAgent = class {
|
|
|
2471
2659
|
if (validation) {
|
|
2472
2660
|
return this.applyServerValidationResult(validation, budget.id);
|
|
2473
2661
|
} else {
|
|
2474
|
-
|
|
2475
|
-
`
|
|
2662
|
+
logger.warn(
|
|
2663
|
+
`Server validation failed for budget '${budget.id}', using local enforcement`
|
|
2476
2664
|
);
|
|
2477
2665
|
if (!this.options.failOpen) {
|
|
2478
2666
|
return {
|
|
@@ -2534,12 +2722,14 @@ var ControlAgent = class {
|
|
|
2534
2722
|
if (this.connected && this.ws?.readyState === WebSocket.OPEN) {
|
|
2535
2723
|
try {
|
|
2536
2724
|
this.ws.send(JSON.stringify(event));
|
|
2725
|
+
logger.debug(`Event sent via WebSocket: ${event.event_type}`);
|
|
2537
2726
|
return;
|
|
2538
2727
|
} catch (error) {
|
|
2539
|
-
|
|
2728
|
+
logger.warn("WebSocket send failed, queuing event");
|
|
2540
2729
|
}
|
|
2541
2730
|
}
|
|
2542
2731
|
this.queueEvent(event);
|
|
2732
|
+
logger.debug(`Event queued: ${event.event_type} (queue size: ${this.eventQueue.length})`);
|
|
2543
2733
|
if (!this.connected) {
|
|
2544
2734
|
await this.flushEventQueue();
|
|
2545
2735
|
}
|
|
@@ -2558,7 +2748,9 @@ var ControlAgent = class {
|
|
|
2558
2748
|
*/
|
|
2559
2749
|
async flushEventQueue() {
|
|
2560
2750
|
if (this.eventQueue.length === 0) return;
|
|
2751
|
+
const eventCount = this.eventQueue.length;
|
|
2561
2752
|
if (this.connected && this.ws?.readyState === WebSocket.OPEN) {
|
|
2753
|
+
logger.debug(`Flushing ${eventCount} events via WebSocket`);
|
|
2562
2754
|
const events = [...this.eventQueue];
|
|
2563
2755
|
this.eventQueue = [];
|
|
2564
2756
|
for (const event of events) {
|
|
@@ -2570,12 +2762,14 @@ var ControlAgent = class {
|
|
|
2570
2762
|
}
|
|
2571
2763
|
return;
|
|
2572
2764
|
}
|
|
2765
|
+
logger.debug(`Flushing ${eventCount} events via HTTP`);
|
|
2573
2766
|
try {
|
|
2574
2767
|
const events = [...this.eventQueue];
|
|
2575
2768
|
this.eventQueue = [];
|
|
2576
2769
|
await this.httpRequest("/v1/control/events", "POST", { events });
|
|
2770
|
+
logger.debug(`Successfully sent ${eventCount} events via HTTP`);
|
|
2577
2771
|
} catch (error) {
|
|
2578
|
-
|
|
2772
|
+
logger.warn("Failed to flush event queue:", error);
|
|
2579
2773
|
}
|
|
2580
2774
|
}
|
|
2581
2775
|
/**
|
|
@@ -6447,99 +6641,6 @@ function createReportEmitter(builder) {
|
|
|
6447
6641
|
builder.recordEvent(event);
|
|
6448
6642
|
};
|
|
6449
6643
|
}
|
|
6450
|
-
|
|
6451
|
-
// src/logging.ts
|
|
6452
|
-
var LOG_LEVEL_PRIORITY = {
|
|
6453
|
-
debug: 0,
|
|
6454
|
-
info: 1,
|
|
6455
|
-
warn: 2,
|
|
6456
|
-
error: 3,
|
|
6457
|
-
silent: 4
|
|
6458
|
-
};
|
|
6459
|
-
var currentConfig = {
|
|
6460
|
-
level: parseLogLevel(process.env.ADEN_LOG_LEVEL) ?? "info",
|
|
6461
|
-
metricsLevel: parseLogLevel(process.env.ADEN_METRICS_LOG_LEVEL) ?? parseLogLevel(process.env.ADEN_LOG_LEVEL) ?? "info",
|
|
6462
|
-
handler: console
|
|
6463
|
-
};
|
|
6464
|
-
function parseLogLevel(level) {
|
|
6465
|
-
if (!level) return void 0;
|
|
6466
|
-
const normalized = level.toLowerCase();
|
|
6467
|
-
if (normalized in LOG_LEVEL_PRIORITY) {
|
|
6468
|
-
return normalized;
|
|
6469
|
-
}
|
|
6470
|
-
return void 0;
|
|
6471
|
-
}
|
|
6472
|
-
function shouldLog(level, configLevel) {
|
|
6473
|
-
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[configLevel];
|
|
6474
|
-
}
|
|
6475
|
-
function configureLogging(config) {
|
|
6476
|
-
if (config.level !== void 0) {
|
|
6477
|
-
currentConfig.level = config.level;
|
|
6478
|
-
if (config.metricsLevel === void 0) {
|
|
6479
|
-
currentConfig.metricsLevel = config.level;
|
|
6480
|
-
}
|
|
6481
|
-
}
|
|
6482
|
-
if (config.metricsLevel !== void 0) {
|
|
6483
|
-
currentConfig.metricsLevel = config.metricsLevel;
|
|
6484
|
-
}
|
|
6485
|
-
if (config.handler !== void 0) {
|
|
6486
|
-
currentConfig.handler = config.handler;
|
|
6487
|
-
}
|
|
6488
|
-
}
|
|
6489
|
-
function getLoggingConfig() {
|
|
6490
|
-
return { ...currentConfig };
|
|
6491
|
-
}
|
|
6492
|
-
function resetLoggingConfig() {
|
|
6493
|
-
currentConfig = {
|
|
6494
|
-
level: parseLogLevel(process.env.ADEN_LOG_LEVEL) ?? "info",
|
|
6495
|
-
metricsLevel: parseLogLevel(process.env.ADEN_METRICS_LOG_LEVEL) ?? parseLogLevel(process.env.ADEN_LOG_LEVEL) ?? "info",
|
|
6496
|
-
handler: console
|
|
6497
|
-
};
|
|
6498
|
-
}
|
|
6499
|
-
var logger = {
|
|
6500
|
-
debug(message, ...args) {
|
|
6501
|
-
if (shouldLog("debug", currentConfig.level)) {
|
|
6502
|
-
currentConfig.handler.debug(`[aden] ${message}`, ...args);
|
|
6503
|
-
}
|
|
6504
|
-
},
|
|
6505
|
-
info(message, ...args) {
|
|
6506
|
-
if (shouldLog("info", currentConfig.level)) {
|
|
6507
|
-
currentConfig.handler.info(`[aden] ${message}`, ...args);
|
|
6508
|
-
}
|
|
6509
|
-
},
|
|
6510
|
-
warn(message, ...args) {
|
|
6511
|
-
if (shouldLog("warn", currentConfig.level)) {
|
|
6512
|
-
currentConfig.handler.warn(`[aden] ${message}`, ...args);
|
|
6513
|
-
}
|
|
6514
|
-
},
|
|
6515
|
-
error(message, ...args) {
|
|
6516
|
-
if (shouldLog("error", currentConfig.level)) {
|
|
6517
|
-
currentConfig.handler.error(`[aden] ${message}`, ...args);
|
|
6518
|
-
}
|
|
6519
|
-
}
|
|
6520
|
-
};
|
|
6521
|
-
var metricsLogger = {
|
|
6522
|
-
debug(message, ...args) {
|
|
6523
|
-
if (shouldLog("debug", currentConfig.metricsLevel)) {
|
|
6524
|
-
currentConfig.handler.debug(`[aden.metrics] ${message}`, ...args);
|
|
6525
|
-
}
|
|
6526
|
-
},
|
|
6527
|
-
info(message, ...args) {
|
|
6528
|
-
if (shouldLog("info", currentConfig.metricsLevel)) {
|
|
6529
|
-
currentConfig.handler.info(`[aden.metrics] ${message}`, ...args);
|
|
6530
|
-
}
|
|
6531
|
-
},
|
|
6532
|
-
warn(message, ...args) {
|
|
6533
|
-
if (shouldLog("warn", currentConfig.metricsLevel)) {
|
|
6534
|
-
currentConfig.handler.warn(`[aden.metrics] ${message}`, ...args);
|
|
6535
|
-
}
|
|
6536
|
-
},
|
|
6537
|
-
error(message, ...args) {
|
|
6538
|
-
if (shouldLog("error", currentConfig.metricsLevel)) {
|
|
6539
|
-
currentConfig.handler.error(`[aden.metrics] ${message}`, ...args);
|
|
6540
|
-
}
|
|
6541
|
-
}
|
|
6542
|
-
};
|
|
6543
6644
|
export {
|
|
6544
6645
|
AnalyticsEngine,
|
|
6545
6646
|
BudgetExceededError,
|