@oneuptime/common 12.0.24 → 12.0.25
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/Models/AnalyticsModels/Span.ts +101 -0
- package/Server/API/BaseAPI.ts +0 -24
- package/Server/API/SlackAPI.ts +0 -2
- package/Server/Middleware/SlackAuthorization.ts +96 -18
- package/Server/Utils/Telemetry/LlmMetricSpend.ts +56 -5
- package/Server/Utils/Telemetry/LlmSpan.ts +46 -0
- package/Server/Utils/Workspace/Slack/Actions/Auth.ts +0 -12
- package/Tests/App/Dashboard/LlmCallsTableIdentity.test.tsx +322 -0
- package/Tests/App/Dashboard/LlmOverview.test.tsx +335 -0
- package/Tests/App/Dashboard/LlmSpanDisplay.test.ts +391 -0
- package/Tests/App/Dashboard/LlmUsageBreakdown.test.tsx +1007 -0
- package/Tests/Server/API/BaseAPI.test.ts +41 -0
- package/Tests/Server/API/BaseAPIUpdatePayloadValidation.test.ts +9 -16
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +262 -5
- package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +37 -18
- package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +143 -2
- package/Tests/Server/Utils/Telemetry/LlmSpan.test.ts +804 -0
- package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +391 -0
- package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +298 -0
- package/Types/Telemetry/LlmConventions.ts +255 -0
- package/Types/Telemetry/LlmMetricConventions.ts +212 -7
- package/Utils/Telemetry/LlmMetricQuery.ts +83 -0
- package/build/dist/Models/AnalyticsModels/Span.js +89 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +3 -19
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +0 -2
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/Middleware/SlackAuthorization.js +58 -7
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +52 -3
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmSpan.js +24 -1
- package/build/dist/Server/Utils/Telemetry/LlmSpan.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js +0 -10
- package/build/dist/Server/Utils/Workspace/Slack/Actions/Auth.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmConventions.js +236 -0
- package/build/dist/Types/Telemetry/LlmConventions.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmMetricConventions.js +197 -5
- package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -1
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js +57 -1
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import Includes from "../../../../Types/BaseDatabase/Includes";
|
|
|
5
5
|
import ObjectID from "../../../../Types/ObjectID";
|
|
6
6
|
import {
|
|
7
7
|
LlmCostMetricNames,
|
|
8
|
+
LlmMicroUsdCostMetricNames,
|
|
8
9
|
LlmTokenTypeAttributeKeys,
|
|
9
10
|
LlmTokenUsageMetricNames,
|
|
10
11
|
} from "../../../../Types/Telemetry/LlmMetricConventions";
|
|
@@ -111,6 +112,65 @@ describe("LlmMetricSpend", () => {
|
|
|
111
112
|
});
|
|
112
113
|
});
|
|
113
114
|
|
|
115
|
+
/*
|
|
116
|
+
* The micro-USD aggregate. It exists as a SECOND query for one reason: the
|
|
117
|
+
* recognized cost metrics come in two units, and a single Sum over both
|
|
118
|
+
* would add millionths of a dollar to dollars with no way to recover the
|
|
119
|
+
* unit afterwards. A $3 Codex turn would reach a budget as $3,000,000.
|
|
120
|
+
*/
|
|
121
|
+
describe("buildMicroUsdCostAggregateBy", () => {
|
|
122
|
+
test("selects the micro-USD metric names", () => {
|
|
123
|
+
const query: Record<string, unknown> =
|
|
124
|
+
LlmMetricSpend.buildMicroUsdCostAggregateBy(scope())
|
|
125
|
+
.query as unknown as Record<string, unknown>;
|
|
126
|
+
|
|
127
|
+
expect((query["name"] as Includes).values).toEqual(
|
|
128
|
+
LlmMicroUsdCostMetricNames,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("never selects a USD cost metric name", () => {
|
|
133
|
+
const query: Record<string, unknown> =
|
|
134
|
+
LlmMetricSpend.buildMicroUsdCostAggregateBy(scope())
|
|
135
|
+
.query as unknown as Record<string, unknown>;
|
|
136
|
+
|
|
137
|
+
const names: Array<string> = (query["name"] as Includes)
|
|
138
|
+
.values as Array<string>;
|
|
139
|
+
|
|
140
|
+
for (const name of names) {
|
|
141
|
+
expect(LlmCostMetricNames).not.toContain(name);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("sums the value column over the whole window, like the USD query", () => {
|
|
146
|
+
const aggregate: AggregateBy<Metric> =
|
|
147
|
+
LlmMetricSpend.buildMicroUsdCostAggregateBy(scope());
|
|
148
|
+
|
|
149
|
+
expect(aggregate.aggregationType).toBe(AggregationType.Sum);
|
|
150
|
+
expect(aggregate.aggregateColumnName).toBe("value");
|
|
151
|
+
expect(aggregate.aggregationInterval).toBe(AggregationInterval.Total);
|
|
152
|
+
expect(aggregate.startTimestamp).toBe(START);
|
|
153
|
+
expect(aggregate.endTimestamp).toBe(END);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("fails loud on query timeout", () => {
|
|
157
|
+
expect(
|
|
158
|
+
LlmMetricSpend.buildMicroUsdCostAggregateBy(scope())
|
|
159
|
+
.timeoutOverflowMode,
|
|
160
|
+
).toBe("throw");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("carries the caller's scoping into the query", () => {
|
|
164
|
+
const query: Record<string, unknown> =
|
|
165
|
+
LlmMetricSpend.buildMicroUsdCostAggregateBy(
|
|
166
|
+
scope({ serviceId: SERVICE_ID, llmSystem: "openai" }),
|
|
167
|
+
).query as unknown as Record<string, unknown>;
|
|
168
|
+
|
|
169
|
+
expect(query["primaryEntityId"]).toBe(SERVICE_ID);
|
|
170
|
+
expect(query["attributes"]).toEqual({ "gen_ai.system": "openai" });
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
114
174
|
describe("buildTokenAggregateBy", () => {
|
|
115
175
|
test("selects the token usage metric names", () => {
|
|
116
176
|
const query: Record<string, unknown> =
|
|
@@ -185,12 +245,93 @@ describe("LlmMetricSpend", () => {
|
|
|
185
245
|
);
|
|
186
246
|
});
|
|
187
247
|
|
|
188
|
-
|
|
248
|
+
/*
|
|
249
|
+
* TWO aggregates, not one — this count changed deliberately when the
|
|
250
|
+
* micro-USD emitters (the Codex CLI) were recognized. The two name lists
|
|
251
|
+
* carry different UNITS, so they cannot share a Sum; see
|
|
252
|
+
* buildMicroUsdCostAggregateBy above.
|
|
253
|
+
*/
|
|
254
|
+
test("issues one aggregate per cost unit", async () => {
|
|
189
255
|
mockedMetricService.aggregateBy.mockResolvedValue({ data: [] });
|
|
190
256
|
|
|
191
257
|
await LlmMetricSpend.getCostInUSD(scope());
|
|
192
258
|
|
|
193
|
-
expect(mockedMetricService.aggregateBy).toHaveBeenCalledTimes(
|
|
259
|
+
expect(mockedMetricService.aggregateBy).toHaveBeenCalledTimes(2);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("the two cost aggregates select disjoint metric names", async () => {
|
|
263
|
+
mockedMetricService.aggregateBy.mockResolvedValue({ data: [] });
|
|
264
|
+
|
|
265
|
+
await LlmMetricSpend.getCostInUSD(scope());
|
|
266
|
+
|
|
267
|
+
const selected: Array<Array<string>> =
|
|
268
|
+
mockedMetricService.aggregateBy.mock.calls.map(
|
|
269
|
+
(call: Array<unknown>): Array<string> => {
|
|
270
|
+
const aggregate: AggregateBy<Metric> =
|
|
271
|
+
call[0] as AggregateBy<Metric>;
|
|
272
|
+
const query: Record<string, unknown> =
|
|
273
|
+
aggregate.query as unknown as Record<string, unknown>;
|
|
274
|
+
|
|
275
|
+
return (query["name"] as Includes).values as Array<string>;
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
expect(selected).toHaveLength(2);
|
|
280
|
+
expect(selected[0]).toEqual(LlmCostMetricNames);
|
|
281
|
+
expect(selected[1]).toEqual(LlmMicroUsdCostMetricNames);
|
|
282
|
+
|
|
283
|
+
const overlap: Array<string> = selected[0]!.filter((name: string) => {
|
|
284
|
+
return selected[1]!.includes(name);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
expect(overlap).toEqual([]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
/*
|
|
291
|
+
* The million-fold regression, end to end: a Codex turn reported as
|
|
292
|
+
* 1,500,000 micro-USD must reach a budget as $1.50, not $1,500,000.
|
|
293
|
+
*/
|
|
294
|
+
test("scales micro-USD spend by 1e-6 before adding it to USD spend", async () => {
|
|
295
|
+
mockedMetricService.aggregateBy
|
|
296
|
+
.mockResolvedValueOnce({ data: [{ timestamp: START, value: 0.5 }] })
|
|
297
|
+
.mockResolvedValueOnce({
|
|
298
|
+
data: [{ timestamp: START, value: 1_500_000 }],
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await expect(LlmMetricSpend.getCostInUSD(scope())).resolves.toBeCloseTo(
|
|
302
|
+
2,
|
|
303
|
+
10,
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("micro-USD-only spend is still reported rather than lost", async () => {
|
|
308
|
+
// The metrics-only Codex fleet: no USD cost metric exists at all.
|
|
309
|
+
mockedMetricService.aggregateBy
|
|
310
|
+
.mockResolvedValueOnce({ data: [] })
|
|
311
|
+
.mockResolvedValueOnce({
|
|
312
|
+
data: [{ timestamp: START, value: 2_250_000 }],
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await expect(LlmMetricSpend.getCostInUSD(scope())).resolves.toBeCloseTo(
|
|
316
|
+
2.25,
|
|
317
|
+
10,
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("a micro-USD query failure is not swallowed into a low total", async () => {
|
|
322
|
+
/*
|
|
323
|
+
* Both aggregates run in parallel; a failure in EITHER must propagate.
|
|
324
|
+
* Silently treating the failed one as zero would understate spend and
|
|
325
|
+
* suppress a real budget breach — the same reasoning as the
|
|
326
|
+
* timeoutOverflowMode: "throw" above.
|
|
327
|
+
*/
|
|
328
|
+
mockedMetricService.aggregateBy
|
|
329
|
+
.mockResolvedValueOnce({ data: [{ timestamp: START, value: 1 }] })
|
|
330
|
+
.mockRejectedValueOnce(new Error("clickhouse timeout"));
|
|
331
|
+
|
|
332
|
+
await expect(LlmMetricSpend.getCostInUSD(scope())).rejects.toThrow(
|
|
333
|
+
"clickhouse timeout",
|
|
334
|
+
);
|
|
194
335
|
});
|
|
195
336
|
});
|
|
196
337
|
|