usertrust 1.4.0 → 1.5.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/anomaly/detector.d.ts.map +1 -1
- package/dist/anomaly/detector.js +36 -11
- package/dist/anomaly/detector.js.map +1 -1
- package/dist/anomaly/signals/spend-velocity.d.ts +39 -12
- package/dist/anomaly/signals/spend-velocity.d.ts.map +1 -1
- package/dist/anomaly/signals/spend-velocity.js +66 -43
- package/dist/anomaly/signals/spend-velocity.js.map +1 -1
- package/dist/anomaly/signals/token-rate.d.ts +33 -13
- package/dist/anomaly/signals/token-rate.d.ts.map +1 -1
- package/dist/anomaly/signals/token-rate.js +124 -38
- package/dist/anomaly/signals/token-rate.js.map +1 -1
- package/dist/anomaly/types.d.ts +28 -6
- package/dist/anomaly/types.d.ts.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +98 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/main.js +0 -0
- package/dist/detect.d.ts +34 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +124 -0
- package/dist/detect.js.map +1 -1
- package/dist/govern.d.ts +8 -1
- package/dist/govern.d.ts.map +1 -1
- package/dist/govern.js +191 -12
- package/dist/govern.js.map +1 -1
- package/dist/headless.d.ts +41 -2
- package/dist/headless.d.ts.map +1 -1
- package/dist/headless.js +54 -3
- package/dist/headless.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ledger/pricing.d.ts +54 -0
- package/dist/ledger/pricing.d.ts.map +1 -1
- package/dist/ledger/pricing.js +128 -8
- package/dist/ledger/pricing.js.map +1 -1
- package/dist/shared/types.d.ts +175 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +55 -5
- package/dist/shared/types.js.map +1 -1
- package/dist/streaming.d.ts +8 -3
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +61 -30
- package/dist/streaming.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAEX,aAAa,EACb,sBAAsB,EACtB,oBAAoB,EACpB,YAAY,EAGZ,cAAc,EACd,qBAAqB,EACrB,MAAM,YAAY,CAAC;AAIpB,qDAAqD;AACrD,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,qBAAqB,CAQ/E;AA+CD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IAC/C,0CAA0C;IAC1C,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,wFAAwF;IACxF,KAAK,IAAI,cAAc,CAAC;IACxB,oDAAoD;IACpD,KAAK,IAAI,IAAI,CAAC;IACd,2EAA2E;IAC3E,SAAS,IAAI,OAAO,CAAC;CACrB;AAED,wBAAgB,qBAAqB,CACpC,GAAG,CAAC,EAAE,aAAa,EACnB,OAAO,CAAC,EAAE,sBAAsB,GAC9B,eAAe,CAoKjB"}
|
package/dist/anomaly/detector.js
CHANGED
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
* - After cooldownMs the detector auto-resets; reset() can be called manually.
|
|
15
15
|
*
|
|
16
16
|
* Defaults are tuned conservatively (500 tok/s sustained, $1/min, 3 injections in 60s).
|
|
17
|
-
*
|
|
17
|
+
* Local-scope events (event.endpointClass === "local", stamped by govern.ts) use
|
|
18
|
+
* local-calibrated thresholds: 5000 tok/s (perModel glob overrides first) and
|
|
19
|
+
* 10_000 nominal usertokens/min. Detection is opt-in (anomaly.enabled=true) —
|
|
20
|
+
* when off, observe/check are no-ops.
|
|
18
21
|
*/
|
|
19
|
-
import { estimateCost } from "../ledger/pricing.js";
|
|
22
|
+
import { costFromRates, estimateCost } from "../ledger/pricing.js";
|
|
20
23
|
import { createInjectionCascadeSignal, resolveInjectionCascadeConfig, } from "./signals/injection-cascade.js";
|
|
21
24
|
import { createSpendVelocitySignal, resolveSpendVelocityConfig, } from "./signals/spend-velocity.js";
|
|
22
25
|
import { createTokenRateSignal, resolveTokenRateConfig, } from "./signals/token-rate.js";
|
|
@@ -33,11 +36,31 @@ export function resolveAnomalyConfig(cfg) {
|
|
|
33
36
|
}
|
|
34
37
|
/** USD value of one usertoken. 1 usertoken = $0.0001 (one basis point of a cent). */
|
|
35
38
|
const USERTOKENS_PER_DOLLAR = 10_000;
|
|
36
|
-
function defaultCostCalculator(model, inputTokens, outputTokens) {
|
|
37
|
-
|
|
39
|
+
function defaultCostCalculator(model, inputTokens, outputTokens, event) {
|
|
40
|
+
if (event?.endpointClass === "local") {
|
|
41
|
+
// Local scope without an injected calculator: price in nominal usertokens at
|
|
42
|
+
// the shipped default local rate {0,0}. costFromRates floors at 1, so the
|
|
43
|
+
// cumulative cost is a constant and spend-velocity stays flat — token_rate is
|
|
44
|
+
// the primary local signal. govern.ts injects a config-aware calculator that
|
|
45
|
+
// prices via resolveRates (operator-set local rates enable velocity showback).
|
|
46
|
+
return costFromRates({ inputPer1k: 0, outputPer1k: 0 }, inputTokens, outputTokens);
|
|
47
|
+
}
|
|
48
|
+
// estimateCost returns usertokens; convert to dollars (cloud usd-proxy scope).
|
|
38
49
|
const usertokens = estimateCost(model, inputTokens, outputTokens);
|
|
39
50
|
return usertokens / USERTOKENS_PER_DOLLAR;
|
|
40
51
|
}
|
|
52
|
+
/** Verdict text for a token-rate result; names non-legacy thresholds (local/perModel). */
|
|
53
|
+
function tokenRateMessage(r) {
|
|
54
|
+
const base = `tokens/s ${r.metric.toFixed(1)} >= ${r.threshold}`;
|
|
55
|
+
return r.thresholdLabel === "thresholdTokPerSec" ? base : `${base} (${r.thresholdLabel})`;
|
|
56
|
+
}
|
|
57
|
+
/** Verdict text for a spend-velocity result; never fakes dollars for nominal spend. */
|
|
58
|
+
function spendVelocityMessage(r) {
|
|
59
|
+
if (r.unit === "usertokens") {
|
|
60
|
+
return `usertokens/min ${r.metric.toFixed(1)} >= ${r.threshold} (${r.thresholdLabel})`;
|
|
61
|
+
}
|
|
62
|
+
return `$/min ${r.metric.toFixed(4)} >= ${r.threshold}`;
|
|
63
|
+
}
|
|
41
64
|
export function createAnomalyDetector(cfg, options) {
|
|
42
65
|
const config = resolveAnomalyConfig(cfg);
|
|
43
66
|
const now = options?.now ?? (() => Date.now());
|
|
@@ -60,9 +83,11 @@ export function createAnomalyDetector(cfg, options) {
|
|
|
60
83
|
}
|
|
61
84
|
function observeChunk(ev) {
|
|
62
85
|
const t = ev.at ?? now();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
// Per-event scope: the event's model/endpointClass (stamped by govern.ts)
|
|
87
|
+
// override options.model / the default cloud scope when present (M2).
|
|
88
|
+
tokenRate.observe(ev.deltaTokens, t, ev);
|
|
89
|
+
const cost = costCalculator(ev.model ?? model, ev.cumulativeInputTokens, ev.cumulativeOutputTokens, ev);
|
|
90
|
+
spendVelocity.observe(cost, t, ev.endpointClass ?? "cloud");
|
|
66
91
|
}
|
|
67
92
|
function observeInjection(ev) {
|
|
68
93
|
injectionCascade.observe(ev.at ?? now());
|
|
@@ -91,11 +116,11 @@ export function createAnomalyDetector(cfg, options) {
|
|
|
91
116
|
// Check signals in priority order: token-rate first (cheapest), spend-velocity, then injection.
|
|
92
117
|
const tr = tokenRate.check(t);
|
|
93
118
|
if (tr.tripped) {
|
|
94
|
-
return markTripped("token_rate",
|
|
119
|
+
return markTripped("token_rate", tokenRateMessage(tr), tr.metric, tr.threshold);
|
|
95
120
|
}
|
|
96
121
|
const sv = spendVelocity.check(t);
|
|
97
122
|
if (sv.tripped) {
|
|
98
|
-
return markTripped("spend_velocity",
|
|
123
|
+
return markTripped("spend_velocity", spendVelocityMessage(sv), sv.metric, sv.threshold);
|
|
99
124
|
}
|
|
100
125
|
const ic = injectionCascade.check(t);
|
|
101
126
|
if (ic.tripped) {
|
|
@@ -112,7 +137,7 @@ export function createAnomalyDetector(cfg, options) {
|
|
|
112
137
|
return {
|
|
113
138
|
tripped: true,
|
|
114
139
|
kind,
|
|
115
|
-
message:
|
|
140
|
+
message: tokenRateMessage(r),
|
|
116
141
|
metric: r.metric,
|
|
117
142
|
threshold: r.threshold,
|
|
118
143
|
};
|
|
@@ -122,7 +147,7 @@ export function createAnomalyDetector(cfg, options) {
|
|
|
122
147
|
return {
|
|
123
148
|
tripped: true,
|
|
124
149
|
kind,
|
|
125
|
-
message:
|
|
150
|
+
message: spendVelocityMessage(r),
|
|
126
151
|
metric: r.metric,
|
|
127
152
|
threshold: r.threshold,
|
|
128
153
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/anomaly/detector.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;AAEjC;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAEN,4BAA4B,EAC5B,6BAA6B,GAC7B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEN,yBAAyB,EACzB,0BAA0B,GAC1B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEN,qBAAqB,EACrB,sBAAsB,GACtB,MAAM,yBAAyB,CAAC;AAajC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC,qDAAqD;AACrD,MAAM,UAAU,oBAAoB,CAAC,GAAmB;IACvD,OAAO;QACN,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,KAAK;QAC9B,SAAS,EAAE,sBAAsB,CAAC,GAAG,EAAE,SAAS,CAAC;QACjD,aAAa,EAAE,0BAA0B,CAAC,GAAG,EAAE,aAAa,CAAC;QAC7D,gBAAgB,EAAE,6BAA6B,CAAC,GAAG,EAAE,gBAAgB,CAAC;QACtE,UAAU,EAAE,GAAG,EAAE,UAAU,IAAI,mBAAmB;KAClD,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAErC,SAAS,qBAAqB,CAC7B,KAAa,EACb,WAAmB,EACnB,YAAoB,EACpB,KAAyB;IAEzB,IAAI,KAAK,EAAE,aAAa,KAAK,OAAO,EAAE,CAAC;QACtC,6EAA6E;QAC7E,0EAA0E;QAC1E,8EAA8E;QAC9E,6EAA6E;QAC7E,+EAA+E;QAC/E,OAAO,aAAa,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;IACpF,CAAC;IACD,+EAA+E;IAC/E,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;IAClE,OAAO,UAAU,GAAG,qBAAqB,CAAC;AAC3C,CAAC;AAED,0FAA0F;AAC1F,SAAS,gBAAgB,CAAC,CAIzB;IACA,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC;IACjE,OAAO,CAAC,CAAC,cAAc,KAAK,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,cAAc,GAAG,CAAC;AAC3F,CAAC;AAED,uFAAuF;AACvF,SAAS,oBAAoB,CAAC,CAK7B;IACA,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAC7B,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,cAAc,GAAG,CAAC;IACxF,CAAC;IACD,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC;AACzD,CAAC;AAeD,MAAM,UAAU,qBAAqB,CACpC,GAAmB,EACnB,OAAgC;IAEhC,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,SAAS,CAAC;IAC1C,MAAM,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,qBAAqB,CAAC;IAExE,MAAM,SAAS,GAAoB,qBAAqB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC3E,MAAM,aAAa,GAAwB,yBAAyB,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAC3F,MAAM,gBAAgB,GAA2B,4BAA4B,CAC5E,MAAM,CAAC,gBAAgB,CACvB,CAAC;IAEF,MAAM,KAAK,GAAyB;QACnC,OAAO,EAAE,KAAK;QACd,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,IAAI;KACjB,CAAC;IAEF,SAAS,gBAAgB;QACxB,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI;YAAE,OAAO;QACtD,IAAI,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;YAClD,OAAO,EAAE,CAAC;QACX,CAAC;IACF,CAAC;IAED,SAAS,YAAY,CAAC,EAAqB;QAC1C,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC;QACzB,0EAA0E;QAC1E,sEAAsE;QACtE,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,cAAc,CAC1B,EAAE,CAAC,KAAK,IAAI,KAAK,EACjB,EAAE,CAAC,qBAAqB,EACxB,EAAE,CAAC,sBAAsB,EACzB,EAAE,CACF,CAAC;QACF,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,aAAa,IAAI,OAAO,CAAC,CAAC;IAC7D,CAAC;IAED,SAAS,gBAAgB,CAAC,EAAyB;QAClD,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,SAAS,OAAO,CAAC,KAAmB;QACnC,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QAC5B,gBAAgB,EAAE,CAAC;QACnB,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC5B,YAAY,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;aAAM,CAAC;YACP,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;IACF,CAAC;IAED,SAAS,KAAK;QACb,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QAC/C,gBAAgB,EAAE,CAAC;QACnB,0EAA0E;QAC1E,4DAA4D;QAC5D,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,WAAW,IAAI,IAAI,EAAE,CAAC;YAChD,OAAO,cAAc,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QAC1C,CAAC;QAED,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAEhB,gGAAgG;QAChG,MAAM,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC9B,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,WAAW,CAAC,YAAY,EAAE,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;QACjF,CAAC;QAED,MAAM,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,WAAW,CAAC,gBAAgB,EAAE,oBAAoB,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;QACzF,CAAC;QAED,MAAM,EAAE,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,WAAW,CACjB,mBAAmB,EACnB,oBAAoB,EAAE,CAAC,MAAM,OAAO,EAAE,CAAC,SAAS,gBAAgB,EAChE,EAAE,CAAC,MAAM,EACT,EAAE,CAAC,SAAS,CACZ,CAAC;QACH,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED,SAAS,cAAc,CAAC,IAAiB;QACxC,wEAAwE;QACxE,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;QAChB,QAAQ,IAAI,EAAE,CAAC;YACd,KAAK,YAAY,CAAC,CAAC,CAAC;gBACnB,MAAM,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAC7B,OAAO;oBACN,OAAO,EAAE,IAAI;oBACb,IAAI;oBACJ,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;oBAC5B,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,SAAS,EAAE,CAAC,CAAC,SAAS;iBACtB,CAAC;YACH,CAAC;YACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACvB,MAAM,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACjC,OAAO;oBACN,OAAO,EAAE,IAAI;oBACb,IAAI;oBACJ,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;oBAChC,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,SAAS,EAAE,CAAC,CAAC,SAAS;iBACtB,CAAC;YACH,CAAC;YACD,KAAK,mBAAmB,CAAC,CAAC,CAAC;gBAC1B,MAAM,CAAC,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACpC,OAAO;oBACN,OAAO,EAAE,IAAI;oBACb,IAAI;oBACJ,OAAO,EAAE,oBAAoB,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,SAAS,gBAAgB;oBACvE,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,SAAS,EAAE,CAAC,CAAC,SAAS;iBACtB,CAAC;YACH,CAAC;QACF,CAAC;IACF,CAAC;IAED,SAAS,WAAW,CACnB,IAAiB,EACjB,OAAe,EACf,MAAc,EACd,SAAiB;QAEjB,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;QACxB,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;QACzB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC5D,CAAC;IAED,SAAS,OAAO;QACf,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;QACtB,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;QACvB,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;QACzB,SAAS,CAAC,KAAK,EAAE,CAAC;QAClB,aAAa,CAAC,KAAK,EAAE,CAAC;QACtB,gBAAgB,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IAED,SAAS,SAAS;QACjB,wEAAwE;QACxE,+DAA+D;QAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACtC,KAAK,EAAE,CAAC;QACT,CAAC;QACD,gBAAgB,EAAE,CAAC;QACnB,OAAO,KAAK,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,OAAO;QACN,MAAM;QACN,KAAK;QACL,OAAO;QACP,KAAK;QACL,KAAK,EAAE,OAAO;QACd,SAAS;KACT,CAAC;AACH,CAAC"}
|
|
@@ -1,37 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* spend-velocity.ts — Spend-velocity anomaly signal.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Dual denomination (M2): cloud-scope observations accumulate usd-proxy cost
|
|
5
|
+
* (dollars) against `thresholdDollarsPerMin`; local-scope observations
|
|
6
|
+
* accumulate NOMINAL USERTOKENS against `localThresholdUsertokensPerMin`
|
|
7
|
+
* (default 10_000). The two series are independent rolling windows; check()
|
|
8
|
+
* reports which denomination (and threshold) applied via `unit`/`thresholdLabel`
|
|
9
|
+
* so verdict text never fakes dollars for nominal spend.
|
|
7
10
|
*
|
|
8
|
-
*
|
|
11
|
+
* Explicit documented behavior: with the default local rate {0,0}, per-chunk
|
|
12
|
+
* cumulative nominal cost is a constant floor of 1 usertoken, so spend_velocity
|
|
13
|
+
* CANNOT trip on default local config — token_rate is the primary local signal.
|
|
14
|
+
* Velocity becomes meaningful when the operator sets nonzero local rates
|
|
15
|
+
* (amortized-usd showback).
|
|
16
|
+
*
|
|
17
|
+
* Default: $1.00/min (cloud) / 10_000 ut/min (local) over a 10s rolling window.
|
|
9
18
|
*
|
|
10
19
|
* Cost is computed via a `costCalculator` callback so the signal stays
|
|
11
20
|
* decoupled from the SDK's pricing module.
|
|
12
21
|
*/
|
|
22
|
+
import type { EndpointClass } from "../../shared/types.js";
|
|
13
23
|
import type { SpendVelocityConfig } from "../types.js";
|
|
24
|
+
/** Denomination of a velocity measurement. */
|
|
25
|
+
export type SpendUnit = "dollars" | "usertokens";
|
|
14
26
|
interface CostSample {
|
|
15
27
|
tMs: number;
|
|
16
|
-
|
|
28
|
+
/** Cumulative cost in the series' denomination (dollars or nominal usertokens). */
|
|
29
|
+
cumulative: number;
|
|
17
30
|
}
|
|
18
31
|
export interface SpendVelocitySignalState {
|
|
32
|
+
/** Cloud-scope samples (cumulative dollars). */
|
|
19
33
|
samples: CostSample[];
|
|
34
|
+
/** Local-scope samples (cumulative nominal usertokens). */
|
|
35
|
+
localSamples: CostSample[];
|
|
20
36
|
lastDollarsPerMin: number;
|
|
37
|
+
lastUsertokensPerMin: number;
|
|
38
|
+
}
|
|
39
|
+
export interface SpendVelocityCheckResult {
|
|
40
|
+
tripped: boolean;
|
|
41
|
+
metric: number;
|
|
42
|
+
threshold: number;
|
|
43
|
+
/** Denomination of metric/threshold. */
|
|
44
|
+
unit: SpendUnit;
|
|
45
|
+
/** Which config threshold applied: "thresholdDollarsPerMin" | "localThresholdUsertokensPerMin". */
|
|
46
|
+
thresholdLabel: string;
|
|
21
47
|
}
|
|
22
48
|
export interface SpendVelocitySignal {
|
|
23
49
|
state: Readonly<SpendVelocitySignalState>;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
50
|
+
/**
|
|
51
|
+
* Record cumulative cost at time `nowMs`. `scope` selects the series:
|
|
52
|
+
* "local" accumulates nominal usertokens, "cloud" (default) dollars.
|
|
53
|
+
*/
|
|
54
|
+
observe(cumulativeCost: number, nowMs: number, scope?: EndpointClass): void;
|
|
55
|
+
check(nowMs: number): SpendVelocityCheckResult;
|
|
31
56
|
reset(): void;
|
|
32
57
|
}
|
|
33
58
|
export interface ResolvedSpendVelocityConfig {
|
|
34
59
|
thresholdDollarsPerMin: number;
|
|
60
|
+
/** Local-scope threshold; defaulted to 10_000 at consumption when absent. */
|
|
61
|
+
localThresholdUsertokensPerMin?: number | undefined;
|
|
35
62
|
windowMs: number;
|
|
36
63
|
}
|
|
37
64
|
export declare const SPEND_VELOCITY_DEFAULTS: ResolvedSpendVelocityConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spend-velocity.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"spend-velocity.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAKvD,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;AAEjD,UAAU,UAAU;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACxC,gDAAgD;IAChD,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,2DAA2D;IAC3D,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,IAAI,EAAE,SAAS,CAAC;IAChB,mGAAmG;IACnG,cAAc,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,QAAQ,CAAC,wBAAwB,CAAC,CAAC;IAC1C;;;OAGG;IACH,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5E,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,wBAAwB,CAAC;IAC/C,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B;IAC3C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,6EAA6E;IAC7E,8BAA8B,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpD,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,uBAAuB,EAAE,2BAGrC,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,GAAG,CAAC,EAAE,mBAAmB,GAAG,2BAA2B,CAQjG;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,2BAA2B,GAAG,mBAAmB,CAiG/F"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright 2026 Usertools, Inc.
|
|
3
|
+
/** Default local-scope threshold, applied where consumed (resolved config keeps it optional). */
|
|
4
|
+
const LOCAL_THRESHOLD_USERTOKENS_PER_MIN_DEFAULT = 10_000;
|
|
3
5
|
export const SPEND_VELOCITY_DEFAULTS = {
|
|
4
6
|
thresholdDollarsPerMin: 1.0,
|
|
5
7
|
windowMs: 10_000,
|
|
@@ -7,20 +9,25 @@ export const SPEND_VELOCITY_DEFAULTS = {
|
|
|
7
9
|
export function resolveSpendVelocityConfig(cfg) {
|
|
8
10
|
return {
|
|
9
11
|
thresholdDollarsPerMin: cfg?.thresholdDollarsPerMin ?? SPEND_VELOCITY_DEFAULTS.thresholdDollarsPerMin,
|
|
12
|
+
// M2 field stays optional in resolved config — defaulted where consumed.
|
|
13
|
+
localThresholdUsertokensPerMin: cfg?.localThresholdUsertokensPerMin,
|
|
10
14
|
windowMs: cfg?.windowMs ?? SPEND_VELOCITY_DEFAULTS.windowMs,
|
|
11
15
|
};
|
|
12
16
|
}
|
|
13
17
|
export function createSpendVelocitySignal(cfg) {
|
|
14
18
|
const state = {
|
|
15
19
|
samples: [],
|
|
20
|
+
localSamples: [],
|
|
16
21
|
lastDollarsPerMin: 0,
|
|
22
|
+
lastUsertokensPerMin: 0,
|
|
17
23
|
};
|
|
18
|
-
|
|
24
|
+
const localThreshold = cfg.localThresholdUsertokensPerMin ?? LOCAL_THRESHOLD_USERTOKENS_PER_MIN_DEFAULT;
|
|
25
|
+
function pruneOlderThan(series, cutoffMs) {
|
|
19
26
|
// Keep at least one sample older than the cutoff so we can compute deltas.
|
|
20
27
|
// Find the most recent sample at or before cutoffMs and drop everything before it.
|
|
21
28
|
let lastBeforeCutoff = -1;
|
|
22
|
-
for (let i = 0; i <
|
|
23
|
-
const sample =
|
|
29
|
+
for (let i = 0; i < series.length; i++) {
|
|
30
|
+
const sample = series[i];
|
|
24
31
|
if (sample !== undefined && sample.tMs <= cutoffMs) {
|
|
25
32
|
lastBeforeCutoff = i;
|
|
26
33
|
}
|
|
@@ -29,61 +36,77 @@ export function createSpendVelocitySignal(cfg) {
|
|
|
29
36
|
}
|
|
30
37
|
}
|
|
31
38
|
if (lastBeforeCutoff > 0) {
|
|
32
|
-
|
|
39
|
+
series.splice(0, lastBeforeCutoff);
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
|
-
function observe(
|
|
42
|
+
function observe(cumulativeCost, nowMs, scope = "cloud") {
|
|
36
43
|
// Reject NaN/negative deltas — keep the cumulative monotonic non-decreasing.
|
|
37
|
-
if (!Number.isFinite(
|
|
44
|
+
if (!Number.isFinite(cumulativeCost) || cumulativeCost < 0)
|
|
38
45
|
return;
|
|
39
|
-
const
|
|
40
|
-
|
|
46
|
+
const series = scope === "local" ? state.localSamples : state.samples;
|
|
47
|
+
const last = series[series.length - 1];
|
|
48
|
+
if (last !== undefined && cumulativeCost < last.cumulative) {
|
|
41
49
|
// Provider regressed (shouldn't happen) — clamp to last
|
|
42
|
-
|
|
50
|
+
series.push({ tMs: nowMs, cumulative: last.cumulative });
|
|
43
51
|
}
|
|
44
52
|
else {
|
|
45
|
-
|
|
53
|
+
series.push({ tMs: nowMs, cumulative: cumulativeCost });
|
|
46
54
|
}
|
|
47
|
-
pruneOlderThan(nowMs - cfg.windowMs);
|
|
55
|
+
pruneOlderThan(series, nowMs - cfg.windowMs);
|
|
48
56
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const last = state.samples[state.samples.length - 1];
|
|
60
|
-
if (first === undefined || last === undefined) {
|
|
61
|
-
return {
|
|
62
|
-
tripped: false,
|
|
63
|
-
metric: 0,
|
|
64
|
-
threshold: cfg.thresholdDollarsPerMin,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const dollarsDelta = last.cumulativeDollars - first.cumulativeDollars;
|
|
57
|
+
/** Rolling per-minute rate over a series, or null when not computable. */
|
|
58
|
+
function ratePerMin(series, nowMs) {
|
|
59
|
+
pruneOlderThan(series, nowMs - cfg.windowMs);
|
|
60
|
+
if (series.length < 2)
|
|
61
|
+
return null;
|
|
62
|
+
const first = series[0];
|
|
63
|
+
const last = series[series.length - 1];
|
|
64
|
+
if (first === undefined || last === undefined)
|
|
65
|
+
return null;
|
|
66
|
+
const costDelta = last.cumulative - first.cumulative;
|
|
68
67
|
const minutesDelta = (last.tMs - first.tMs) / 60_000;
|
|
69
|
-
if (minutesDelta <= 0)
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
if (minutesDelta <= 0)
|
|
69
|
+
return null;
|
|
70
|
+
return costDelta / minutesDelta;
|
|
71
|
+
}
|
|
72
|
+
function check(nowMs) {
|
|
73
|
+
const cloudRate = ratePerMin(state.samples, nowMs);
|
|
74
|
+
const localRate = ratePerMin(state.localSamples, nowMs);
|
|
75
|
+
if (cloudRate !== null)
|
|
76
|
+
state.lastDollarsPerMin = cloudRate;
|
|
77
|
+
if (localRate !== null)
|
|
78
|
+
state.lastUsertokensPerMin = localRate;
|
|
79
|
+
const cloudResult = (tripped) => ({
|
|
80
|
+
tripped,
|
|
81
|
+
metric: cloudRate ?? 0,
|
|
81
82
|
threshold: cfg.thresholdDollarsPerMin,
|
|
82
|
-
|
|
83
|
+
unit: "dollars",
|
|
84
|
+
thresholdLabel: "thresholdDollarsPerMin",
|
|
85
|
+
});
|
|
86
|
+
const localResult = (tripped, rate) => ({
|
|
87
|
+
tripped,
|
|
88
|
+
metric: rate,
|
|
89
|
+
threshold: localThreshold,
|
|
90
|
+
unit: "usertokens",
|
|
91
|
+
thresholdLabel: "localThresholdUsertokensPerMin",
|
|
92
|
+
});
|
|
93
|
+
if (cloudRate !== null && cloudRate >= cfg.thresholdDollarsPerMin)
|
|
94
|
+
return cloudResult(true);
|
|
95
|
+
if (localRate !== null && localRate >= localThreshold)
|
|
96
|
+
return localResult(true, localRate);
|
|
97
|
+
// Neither tripped: report the denomination closer to its threshold
|
|
98
|
+
// (cloud on tie / when nothing is computable — legacy behavior).
|
|
99
|
+
if (localRate !== null &&
|
|
100
|
+
(cloudRate === null || localRate / localThreshold > cloudRate / cfg.thresholdDollarsPerMin)) {
|
|
101
|
+
return localResult(false, localRate);
|
|
102
|
+
}
|
|
103
|
+
return cloudResult(false);
|
|
83
104
|
}
|
|
84
105
|
function reset() {
|
|
85
106
|
state.samples.length = 0;
|
|
107
|
+
state.localSamples.length = 0;
|
|
86
108
|
state.lastDollarsPerMin = 0;
|
|
109
|
+
state.lastUsertokensPerMin = 0;
|
|
87
110
|
}
|
|
88
111
|
return { state, observe, check, reset };
|
|
89
112
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spend-velocity.js","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,iCAAiC;
|
|
1
|
+
{"version":3,"file":"spend-velocity.js","sourceRoot":"","sources":["../../../src/anomaly/signals/spend-velocity.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
* token-rate.ts — Token-rate anomaly signal.
|
|
3
|
-
*
|
|
4
|
-
* Buckets observed chunks into fixed-width windows (default 2 s) and counts
|
|
5
|
-
* tokens-per-second within each window. Trips when N consecutive windows
|
|
6
|
-
* exceed the configured threshold (default 500 tok/s, 3 windows).
|
|
7
|
-
*
|
|
8
|
-
* Brief spikes (1-2 hot windows) are ignored — only sustained runaway
|
|
9
|
-
* behavior trips the circuit.
|
|
10
|
-
*/
|
|
1
|
+
import type { EndpointClass } from "../../shared/types.js";
|
|
11
2
|
import type { TokenRateConfig } from "../types.js";
|
|
12
|
-
|
|
3
|
+
/** Threshold-source labels: "thresholdTokPerSec", "localThresholdTokPerSec", or `perModel["<pattern>"]`. */
|
|
4
|
+
export type TokenRateThresholdLabel = string;
|
|
5
|
+
/** Per-observation scope context (an AnomalyChunkEvent satisfies this shape). */
|
|
6
|
+
export interface TokenRateObserveContext {
|
|
7
|
+
model?: string | undefined;
|
|
8
|
+
endpointClass?: EndpointClass | undefined;
|
|
9
|
+
}
|
|
10
|
+
/** Rolling-window state for a single endpoint scope (cloud or local). */
|
|
11
|
+
export interface ScopeWindowState {
|
|
13
12
|
/** Per-window tokens accumulated in the current bucket. */
|
|
14
13
|
currentWindowTokens: number;
|
|
15
14
|
/** Start time of the current window. */
|
|
@@ -18,11 +17,27 @@ export interface TokenRateSignalState {
|
|
|
18
17
|
consecutiveHotWindows: number;
|
|
19
18
|
/** Last computed peak rate (for the most recently closed window). */
|
|
20
19
|
lastWindowRateTokPerSec: number;
|
|
20
|
+
/** Whether any observation has been routed to this scope yet. */
|
|
21
|
+
initialized: boolean;
|
|
22
|
+
/** Effective threshold for this scope's windows (resolved per observation). */
|
|
23
|
+
threshold: number;
|
|
24
|
+
/** Source label of the effective threshold. */
|
|
25
|
+
thresholdLabel: TokenRateThresholdLabel;
|
|
26
|
+
}
|
|
27
|
+
export interface TokenRateSignalState {
|
|
28
|
+
/** Cloud/unscoped window state (legacy `thresholdTokPerSec`). */
|
|
29
|
+
cloud: Readonly<ScopeWindowState>;
|
|
30
|
+
/** Local window state (`perModel` glob / `localThresholdTokPerSec`). */
|
|
31
|
+
local: Readonly<ScopeWindowState>;
|
|
21
32
|
}
|
|
22
33
|
export interface TokenRateSignal {
|
|
23
34
|
state: Readonly<TokenRateSignalState>;
|
|
24
|
-
/**
|
|
25
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Observe a chunk's delta tokens at time `nowMs`. `context` (the chunk
|
|
37
|
+
* event) selects the effective threshold for subsequent window evaluation;
|
|
38
|
+
* omitted/unscoped context means the legacy cloud threshold.
|
|
39
|
+
*/
|
|
40
|
+
observe(deltaTokens: number, nowMs: number, context?: TokenRateObserveContext): void;
|
|
26
41
|
/**
|
|
27
42
|
* Check if the threshold has been crossed.
|
|
28
43
|
* Returns the metric (peak window rate) when tripped, else null.
|
|
@@ -32,11 +47,16 @@ export interface TokenRateSignal {
|
|
|
32
47
|
metric: number;
|
|
33
48
|
threshold: number;
|
|
34
49
|
hotWindows: number;
|
|
50
|
+
thresholdLabel: TokenRateThresholdLabel;
|
|
35
51
|
};
|
|
36
52
|
reset(): void;
|
|
37
53
|
}
|
|
38
54
|
export interface ResolvedTokenRateConfig {
|
|
39
55
|
thresholdTokPerSec: number;
|
|
56
|
+
/** Local-scope threshold; defaulted to 5000 at consumption when absent. */
|
|
57
|
+
localThresholdTokPerSec?: number | undefined;
|
|
58
|
+
/** Per-model overrides for local-scope events (trailing-* glob keys). */
|
|
59
|
+
perModel?: Record<string, number> | undefined;
|
|
40
60
|
windowMs: number;
|
|
41
61
|
consecutiveWindows: number;
|
|
42
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-rate.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/token-rate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"token-rate.d.ts","sourceRoot":"","sources":["../../../src/anomaly/signals/token-rate.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAKnD,4GAA4G;AAC5G,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE7C,iFAAiF;AACjF,MAAM,WAAW,uBAAuB;IACvC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC1C;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAChC,2DAA2D;IAC3D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,wCAAwC;IACxC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qEAAqE;IACrE,uBAAuB,EAAE,MAAM,CAAC;IAChC,iEAAiE;IACjE,WAAW,EAAE,OAAO,CAAC;IACrB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,cAAc,EAAE,uBAAuB,CAAC;CACxC;AAED,MAAM,WAAW,oBAAoB;IACpC,iEAAiE;IACjE,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAClC,wEAAwE;IACxE,KAAK,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACtC;;;;OAIG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACrF;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG;QACrB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,uBAAuB,CAAC;KACxC,CAAC;IACF,KAAK,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACvC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2EAA2E;IAC3E,uBAAuB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,mBAAmB,EAAE,uBAIjC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,GAAG,CAAC,EAAE,eAAe,GAAG,uBAAuB,CASrF;AAUD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,uBAAuB,GAAG,eAAe,CAsInF"}
|