@copilotz/admin 0.9.36 → 0.9.38

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.cjs CHANGED
@@ -63,6 +63,7 @@ __export(index_exports, {
63
63
  formatUsageBucket: () => formatUsageBucket,
64
64
  getUsageDimensionLabel: () => getUsageDimensionLabel,
65
65
  getUsageGroupLabel: () => getUsageGroupLabel,
66
+ getUsageMetricLabel: () => getUsageMetricLabel,
66
67
  getUsageRange: () => getUsageRange,
67
68
  getUsageTotalValue: () => getUsageTotalValue,
68
69
  mergeAdminConfig: () => mergeAdminConfig,
@@ -158,6 +159,21 @@ function normalizeMessagePage(payload) {
158
159
  const pageInfo = isRecord(candidate) ? normalizeMessagePageInfo(candidate.pageInfo, data) : normalizeMessagePageInfo(void 0, data);
159
160
  return { data, pageInfo };
160
161
  }
162
+ function isQueueEvent(value) {
163
+ return isRecord(value) && typeof value.id === "string" && typeof value.threadId === "string" && typeof value.eventType === "string";
164
+ }
165
+ function normalizeQueueEvent(value) {
166
+ if (isQueueEvent(value)) return value;
167
+ if (isRecord(value) && isQueueEvent(value.data)) return value.data;
168
+ return void 0;
169
+ }
170
+ function normalizeQueueEvents(value) {
171
+ if (Array.isArray(value)) return value.filter(isQueueEvent);
172
+ if (isRecord(value) && Array.isArray(value.data)) {
173
+ return value.data.filter(isQueueEvent);
174
+ }
175
+ return [];
176
+ }
161
177
  function createAdminClient(options = {}) {
162
178
  const baseUrl = resolveBaseUrl(options.baseUrl);
163
179
  const paths = { ...DEFAULT_PATHS, ...options.paths };
@@ -217,12 +233,16 @@ function createAdminClient(options = {}) {
217
233
  metric: filters.metric,
218
234
  groupBy: filters.groupBy,
219
235
  attribution: filters.attribution,
236
+ kind: filters.kind,
220
237
  threadId: filters.threadId,
221
238
  participantId: filters.participantId,
222
239
  participantType: filters.participantType,
223
240
  namespace: filters.namespace,
224
241
  provider: filters.provider,
225
- model: filters.model
242
+ model: filters.model,
243
+ resource: filters.resource,
244
+ operation: filters.operation,
245
+ status: filters.status
226
246
  }),
227
247
  listThreads: async (listOptions = {}) => await requestJson(`${paths.adminBase}/threads`, {
228
248
  search: listOptions.search,
@@ -260,9 +280,25 @@ function createAdminClient(options = {}) {
260
280
  );
261
281
  return normalizeMessagePage(payload);
262
282
  },
263
- getThreadEvent: async (threadId) => await requestJson(
264
- `${paths.threadsBase}/${encodeURIComponent(threadId)}/events`
265
- ),
283
+ listEvents: async (eventOptions = {}) => {
284
+ const payload = await requestJson(`${paths.adminBase}/events`, {
285
+ namespace: eventOptions.namespace,
286
+ threadId: eventOptions.threadId,
287
+ status: eventOptions.status,
288
+ eventType: eventOptions.eventType,
289
+ traceId: eventOptions.traceId,
290
+ search: eventOptions.search,
291
+ limit: String(eventOptions.limit ?? 50),
292
+ offset: eventOptions.offset ? String(eventOptions.offset) : void 0
293
+ });
294
+ return normalizeQueueEvents(payload);
295
+ },
296
+ getThreadEvent: async (threadId) => {
297
+ const payload = await requestJson(
298
+ `${paths.threadsBase}/${encodeURIComponent(threadId)}/events`
299
+ );
300
+ return normalizeQueueEvent(payload);
301
+ },
266
302
  listCollections: async () => await requestJson(paths.collectionsBase),
267
303
  listCollectionItems: async (collection, listOptions = {}) => await requestJson(
268
304
  `${paths.collectionsBase}/${encodeURIComponent(collection)}`,
@@ -1469,7 +1505,11 @@ var EMPTY_USAGE_TOTALS = {
1469
1505
  reasoningTokens: 0,
1470
1506
  totalCalls: 0,
1471
1507
  totalCostUsd: 0,
1472
- totalTokens: 0
1508
+ failedCalls: 0,
1509
+ totalCredits: 0,
1510
+ totalDurationMs: 0,
1511
+ totalTokens: 0,
1512
+ unpricedCalls: 0
1473
1513
  };
1474
1514
  var USAGE_CHART_COLORS = [
1475
1515
  "hsl(var(--primary))",
@@ -1480,53 +1520,66 @@ var USAGE_CHART_COLORS = [
1480
1520
  "hsl(var(--muted-foreground))"
1481
1521
  ];
1482
1522
  function addUsageTotals(target, source) {
1483
- target.cacheCreationInputCostUsd += source.cacheCreationInputCostUsd;
1484
- target.cacheCreationInputTokens += source.cacheCreationInputTokens;
1485
- target.cacheReadInputCostUsd += source.cacheReadInputCostUsd;
1486
- target.cacheReadInputTokens += source.cacheReadInputTokens;
1487
- target.inputCostUsd += source.inputCostUsd;
1488
- target.inputTokens += source.inputTokens;
1489
- target.outputCostUsd += source.outputCostUsd;
1490
- target.outputTokens += source.outputTokens;
1491
- target.reasoningCostUsd += source.reasoningCostUsd;
1492
- target.reasoningTokens += source.reasoningTokens;
1493
- target.totalCalls += source.totalCalls;
1494
- target.totalCostUsd += source.totalCostUsd;
1495
- target.totalTokens += source.totalTokens;
1523
+ target.cacheCreationInputCostUsd += safeUsageNumber(source.cacheCreationInputCostUsd);
1524
+ target.cacheCreationInputTokens += safeUsageNumber(source.cacheCreationInputTokens);
1525
+ target.cacheReadInputCostUsd += safeUsageNumber(source.cacheReadInputCostUsd);
1526
+ target.cacheReadInputTokens += safeUsageNumber(source.cacheReadInputTokens);
1527
+ target.inputCostUsd += safeUsageNumber(source.inputCostUsd);
1528
+ target.inputTokens += safeUsageNumber(source.inputTokens);
1529
+ target.outputCostUsd += safeUsageNumber(source.outputCostUsd);
1530
+ target.outputTokens += safeUsageNumber(source.outputTokens);
1531
+ target.reasoningCostUsd += safeUsageNumber(source.reasoningCostUsd);
1532
+ target.reasoningTokens += safeUsageNumber(source.reasoningTokens);
1533
+ target.totalCalls += safeUsageNumber(source.totalCalls);
1534
+ target.totalCostUsd += safeUsageNumber(source.totalCostUsd);
1535
+ target.failedCalls += safeUsageNumber(source.failedCalls);
1536
+ target.totalCredits += safeUsageNumber(source.totalCredits);
1537
+ target.totalDurationMs += safeUsageNumber(source.totalDurationMs);
1538
+ target.totalTokens += safeUsageNumber(source.totalTokens);
1539
+ target.unpricedCalls += safeUsageNumber(source.unpricedCalls);
1540
+ }
1541
+ function safeUsageNumber(value) {
1542
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1496
1543
  }
1497
1544
  function getUsageTotalValue(totals, metricKind, dimension) {
1498
- if (metricKind === "calls") return totals.totalCalls;
1545
+ if (metricKind === "calls") return safeUsageNumber(totals.totalCalls);
1546
+ if (metricKind === "duration") {
1547
+ return safeUsageNumber(totals.totalDurationMs);
1548
+ }
1549
+ if (metricKind === "credits") return safeUsageNumber(totals.totalCredits);
1550
+ if (metricKind === "failures") return safeUsageNumber(totals.failedCalls);
1551
+ if (metricKind === "unpriced") return safeUsageNumber(totals.unpricedCalls);
1499
1552
  if (metricKind === "cost") {
1500
1553
  switch (dimension) {
1501
1554
  case "input":
1502
- return totals.inputCostUsd;
1555
+ return safeUsageNumber(totals.inputCostUsd);
1503
1556
  case "output":
1504
- return totals.outputCostUsd;
1557
+ return safeUsageNumber(totals.outputCostUsd);
1505
1558
  case "reasoning":
1506
- return totals.reasoningCostUsd;
1559
+ return safeUsageNumber(totals.reasoningCostUsd);
1507
1560
  case "cacheRead":
1508
- return totals.cacheReadInputCostUsd;
1561
+ return safeUsageNumber(totals.cacheReadInputCostUsd);
1509
1562
  case "cacheWrite":
1510
- return totals.cacheCreationInputCostUsd;
1563
+ return safeUsageNumber(totals.cacheCreationInputCostUsd);
1511
1564
  case "total":
1512
1565
  default:
1513
- return totals.totalCostUsd;
1566
+ return safeUsageNumber(totals.totalCostUsd);
1514
1567
  }
1515
1568
  }
1516
1569
  switch (dimension) {
1517
1570
  case "input":
1518
- return totals.inputTokens;
1571
+ return safeUsageNumber(totals.inputTokens);
1519
1572
  case "output":
1520
- return totals.outputTokens;
1573
+ return safeUsageNumber(totals.outputTokens);
1521
1574
  case "reasoning":
1522
- return totals.reasoningTokens;
1575
+ return safeUsageNumber(totals.reasoningTokens);
1523
1576
  case "cacheRead":
1524
- return totals.cacheReadInputTokens;
1577
+ return safeUsageNumber(totals.cacheReadInputTokens);
1525
1578
  case "cacheWrite":
1526
- return totals.cacheCreationInputTokens;
1579
+ return safeUsageNumber(totals.cacheCreationInputTokens);
1527
1580
  case "total":
1528
1581
  default:
1529
- return totals.totalTokens;
1582
+ return safeUsageNumber(totals.totalTokens);
1530
1583
  }
1531
1584
  }
1532
1585
  function aggregateUsageRows(points, metricKind, dimension) {
@@ -1638,6 +1691,14 @@ function getUsageDimensionLabel(dimension) {
1638
1691
  }
1639
1692
  function getUsageGroupLabel(groupBy) {
1640
1693
  switch (groupBy) {
1694
+ case "kind":
1695
+ return "Kind";
1696
+ case "resource":
1697
+ return "Resource";
1698
+ case "operation":
1699
+ return "Operation";
1700
+ case "status":
1701
+ return "Status";
1641
1702
  case "thread":
1642
1703
  return "Thread";
1643
1704
  case "namespace":
@@ -1651,6 +1712,25 @@ function getUsageGroupLabel(groupBy) {
1651
1712
  return "Participant";
1652
1713
  }
1653
1714
  }
1715
+ function getUsageMetricLabel(metricKind) {
1716
+ switch (metricKind) {
1717
+ case "cost":
1718
+ return "Spend";
1719
+ case "tokens":
1720
+ return "Tokens";
1721
+ case "duration":
1722
+ return "Duration";
1723
+ case "credits":
1724
+ return "Credits";
1725
+ case "failures":
1726
+ return "Failures";
1727
+ case "unpriced":
1728
+ return "Unpriced";
1729
+ case "calls":
1730
+ default:
1731
+ return "Calls";
1732
+ }
1733
+ }
1654
1734
  function formatUsageBucket(bucket, interval) {
1655
1735
  const date = new Date(bucket);
1656
1736
  if (Number.isNaN(date.getTime())) return bucket;
@@ -1693,6 +1773,9 @@ function formatMetricValue(value, metricKind) {
1693
1773
  style: "currency"
1694
1774
  }).format(value);
1695
1775
  }
1776
+ if (metricKind === "duration") {
1777
+ return formatDuration(value);
1778
+ }
1696
1779
  return formatNumber(value);
1697
1780
  }
1698
1781
  function formatCompactMetric(value, metricKind) {
@@ -1705,12 +1788,30 @@ function formatCompactMetric(value, metricKind) {
1705
1788
  style: "currency"
1706
1789
  }).format(value);
1707
1790
  }
1791
+ if (metricKind === "duration") {
1792
+ if (value >= 36e5) return `${(value / 36e5).toFixed(1)}h`;
1793
+ if (value >= 6e4) return `${(value / 6e4).toFixed(1)}m`;
1794
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}s`;
1795
+ return `${Math.round(value)}ms`;
1796
+ }
1708
1797
  return new Intl.NumberFormat(void 0, {
1709
1798
  compactDisplay: "short",
1710
1799
  maximumFractionDigits: 1,
1711
1800
  notation: "compact"
1712
1801
  }).format(value);
1713
1802
  }
1803
+ function formatDuration(value) {
1804
+ if (value >= 36e5) {
1805
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 36e5)} h`;
1806
+ }
1807
+ if (value >= 6e4) {
1808
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 6e4)} min`;
1809
+ }
1810
+ if (value >= 1e3) {
1811
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 1e3)} sec`;
1812
+ }
1813
+ return `${formatNumber(Math.round(value))} ms`;
1814
+ }
1714
1815
 
1715
1816
  // src/modules/agents/index.tsx
1716
1817
  var import_jsx_runtime20 = require("react/jsx-runtime");
@@ -2188,33 +2289,61 @@ function eventsModule() {
2188
2289
  }]
2189
2290
  };
2190
2291
  }
2292
+ var EVENT_STATUSES = [
2293
+ "all",
2294
+ "pending",
2295
+ "processing",
2296
+ "completed",
2297
+ "failed",
2298
+ "expired",
2299
+ "overwritten"
2300
+ ];
2191
2301
  function EventsPage({ context }) {
2192
2302
  const [threadId, setThreadId] = import_react4.default.useState("");
2193
- const [status, setStatus] = import_react4.default.useState("");
2194
- const [type, setType] = import_react4.default.useState("");
2303
+ const [status, setStatus] = import_react4.default.useState(
2304
+ "all"
2305
+ );
2306
+ const [eventType, setEventType] = import_react4.default.useState("");
2195
2307
  const [traceId, setTraceId] = import_react4.default.useState("");
2196
- const [event, setEvent] = import_react4.default.useState(null);
2308
+ const [events, setEvents] = import_react4.default.useState([]);
2309
+ const [selectedEvent, setSelectedEvent] = import_react4.default.useState(null);
2197
2310
  const [error, setError] = import_react4.default.useState(null);
2198
- const [hasSearched, setHasSearched] = import_react4.default.useState(false);
2311
+ const [loading, setLoading] = import_react4.default.useState(false);
2312
+ const [hasLoaded, setHasLoaded] = import_react4.default.useState(false);
2199
2313
  const inspect = async () => {
2200
- if (!threadId.trim()) return;
2201
- setHasSearched(true);
2314
+ setHasLoaded(true);
2315
+ setLoading(true);
2202
2316
  setError(null);
2203
2317
  try {
2204
- const next = await context.client.getThreadEvent(threadId.trim());
2205
- setEvent(next ?? null);
2318
+ const next = await context.client.listEvents({
2319
+ namespace: context.scope.namespace || void 0,
2320
+ threadId: threadId.trim() || void 0,
2321
+ status: status === "all" ? void 0 : status,
2322
+ eventType: eventType.trim() || void 0,
2323
+ traceId: traceId.trim() || void 0,
2324
+ limit: 50
2325
+ });
2326
+ setEvents(next);
2327
+ setSelectedEvent(
2328
+ (current) => current && next.some((event) => event.id === current.id) ? current : next[0] ?? null
2329
+ );
2206
2330
  } catch (cause) {
2207
2331
  setError(cause instanceof Error ? cause.message : "Failed to load event");
2208
- setEvent(null);
2332
+ setEvents([]);
2333
+ setSelectedEvent(null);
2334
+ } finally {
2335
+ setLoading(false);
2209
2336
  }
2210
2337
  };
2211
- const rows = event && (!status || event.status.includes(status)) && (!type || event.eventType.includes(type)) && (!traceId || event.traceId?.includes(traceId)) ? [event] : [];
2338
+ import_react4.default.useEffect(() => {
2339
+ void inspect();
2340
+ }, [context.client, context.refreshKey, context.scope.namespace]);
2212
2341
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "space-y-4", children: [
2213
2342
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2214
2343
  PageHeader,
2215
2344
  {
2216
2345
  title: "Events",
2217
- description: "Queue and event inspection. Current backend support is thread-focused; filters are ready for broader event listing."
2346
+ description: "Queue and event inspection across the active namespace."
2218
2347
  }
2219
2348
  ),
2220
2349
  /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
@@ -2223,13 +2352,13 @@ function EventsPage({ context }) {
2223
2352
  actions: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2224
2353
  Button,
2225
2354
  {
2226
- disabled: !threadId.trim(),
2355
+ disabled: loading,
2227
2356
  onClick: () => void inspect(),
2228
2357
  size: "sm",
2229
2358
  type: "button",
2230
2359
  children: [
2231
2360
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react8.Search, { className: "size-3" }),
2232
- "Inspect"
2361
+ loading ? "Loading" : "Inspect"
2233
2362
  ]
2234
2363
  }
2235
2364
  ),
@@ -2238,37 +2367,45 @@ function EventsPage({ context }) {
2238
2367
  Input,
2239
2368
  {
2240
2369
  className: "h-8 w-[220px]",
2241
- onChange: (event2) => setThreadId(event2.target.value),
2242
- onKeyDown: (event2) => {
2243
- if (event2.key === "Enter") void inspect();
2370
+ onChange: (event) => setThreadId(event.target.value),
2371
+ onKeyDown: (event) => {
2372
+ if (event.key === "Enter") void inspect();
2244
2373
  },
2245
2374
  placeholder: "Thread ID",
2246
2375
  value: threadId
2247
2376
  }
2248
2377
  ),
2249
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2250
- Input,
2378
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
2379
+ Select,
2251
2380
  {
2252
- className: "h-8 w-[150px]",
2253
- onChange: (event2) => setStatus(event2.target.value),
2254
- placeholder: "Status",
2255
- value: status
2381
+ value: status,
2382
+ onValueChange: (value) => setStatus(value),
2383
+ children: [
2384
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SelectTrigger, { className: "h-8 w-[150px] text-xs", "aria-label": "Status", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SelectValue, {}) }),
2385
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SelectContent, { children: EVENT_STATUSES.map((option) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(SelectItem, { value: option, children: option === "all" ? "All statuses" : option }, option)) })
2386
+ ]
2256
2387
  }
2257
2388
  ),
2258
2389
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2259
2390
  Input,
2260
2391
  {
2261
2392
  className: "h-8 w-[180px]",
2262
- onChange: (event2) => setType(event2.target.value),
2393
+ onChange: (event) => setEventType(event.target.value),
2394
+ onKeyDown: (event) => {
2395
+ if (event.key === "Enter") void inspect();
2396
+ },
2263
2397
  placeholder: "Event type",
2264
- value: type
2398
+ value: eventType
2265
2399
  }
2266
2400
  ),
2267
2401
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2268
2402
  Input,
2269
2403
  {
2270
2404
  className: "h-8 w-[180px]",
2271
- onChange: (event2) => setTraceId(event2.target.value),
2405
+ onChange: (event) => setTraceId(event.target.value),
2406
+ onKeyDown: (event) => {
2407
+ if (event.key === "Enter") void inspect();
2408
+ },
2272
2409
  placeholder: "Trace ID",
2273
2410
  value: traceId
2274
2411
  }
@@ -2276,27 +2413,34 @@ function EventsPage({ context }) {
2276
2413
  ]
2277
2414
  }
2278
2415
  ),
2279
- error ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EmptyState, { title: "Unable to inspect event", description: error }) : !hasSearched ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2416
+ error ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EmptyState, { title: "Unable to inspect event", description: error }) : !hasLoaded && loading ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2280
2417
  EmptyState,
2281
2418
  {
2282
2419
  icon: import_lucide_react8.Activity,
2283
- title: "Choose a thread",
2284
- description: "Enter a thread ID to inspect its next pending event."
2420
+ title: "Loading events",
2421
+ description: "Fetching recent queue events for the active namespace."
2285
2422
  }
2286
2423
  ) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "grid gap-4 xl:grid-cols-[1fr_420px]", children: [
2287
2424
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2288
2425
  ResourceTable,
2289
2426
  {
2290
- rows,
2427
+ rows: events,
2291
2428
  getRowKey: (row) => row.id,
2429
+ onRowClick: setSelectedEvent,
2292
2430
  empty: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2293
2431
  EmptyState,
2294
2432
  {
2295
- title: "No matching event",
2296
- description: "No pending event matched the current filters."
2433
+ title: "No matching events",
2434
+ description: "No event rows matched the current filters."
2297
2435
  }
2298
2436
  ),
2299
2437
  columns: [
2438
+ {
2439
+ id: "thread",
2440
+ header: "Thread",
2441
+ className: "max-w-[180px] truncate font-mono text-xs",
2442
+ render: (row) => row.threadId
2443
+ },
2300
2444
  {
2301
2445
  id: "type",
2302
2446
  header: "Type",
@@ -2320,7 +2464,20 @@ function EventsPage({ context }) {
2320
2464
  ]
2321
2465
  }
2322
2466
  ),
2323
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(JsonPanel, { title: "Event JSON", value: event, minHeight: 420 })
2467
+ selectedEvent ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2468
+ JsonPanel,
2469
+ {
2470
+ title: "Event JSON",
2471
+ value: selectedEvent,
2472
+ minHeight: 420
2473
+ }
2474
+ ) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2475
+ EmptyState,
2476
+ {
2477
+ title: "No event selected",
2478
+ description: "Select an event row to inspect its payload."
2479
+ }
2480
+ )
2324
2481
  ] })
2325
2482
  ] });
2326
2483
  }
@@ -3138,6 +3295,22 @@ var USAGE_DIMENSIONS = [
3138
3295
  "cacheRead",
3139
3296
  "cacheWrite"
3140
3297
  ];
3298
+ var USAGE_WORKLOADS = [
3299
+ "all",
3300
+ "llm",
3301
+ "tool",
3302
+ "asset",
3303
+ "rag",
3304
+ "embedding"
3305
+ ];
3306
+ var STATUS_OPTIONS = [
3307
+ "all",
3308
+ "completed",
3309
+ "failed",
3310
+ "cancelled",
3311
+ "aborted",
3312
+ "error"
3313
+ ];
3141
3314
  function usageModule() {
3142
3315
  return {
3143
3316
  group: "operate",
@@ -3164,11 +3337,12 @@ function UsagePage({ context }) {
3164
3337
  "7d"
3165
3338
  );
3166
3339
  const [interval, setInterval] = import_react8.default.useState("day");
3340
+ const [workload, setWorkload] = import_react8.default.useState("all");
3167
3341
  const [metricKind, setMetricKind] = import_react8.default.useState(
3168
- "cost"
3342
+ "calls"
3169
3343
  );
3170
3344
  const [dimension, setDimension] = import_react8.default.useState("total");
3171
- const [groupBy, setGroupBy] = import_react8.default.useState("participant");
3345
+ const [groupBy, setGroupBy] = import_react8.default.useState("kind");
3172
3346
  const [attribution, setAttribution] = import_react8.default.useState(
3173
3347
  "initiatedBy"
3174
3348
  );
@@ -3177,6 +3351,11 @@ function UsagePage({ context }) {
3177
3351
  const [participantId, setParticipantId] = import_react8.default.useState("");
3178
3352
  const [provider, setProvider] = import_react8.default.useState("");
3179
3353
  const [model, setModel] = import_react8.default.useState("");
3354
+ const [resource, setResource] = import_react8.default.useState("");
3355
+ const [operation, setOperation] = import_react8.default.useState("");
3356
+ const [status, setStatus] = import_react8.default.useState(
3357
+ "all"
3358
+ );
3180
3359
  const [customFrom, setCustomFrom] = import_react8.default.useState("");
3181
3360
  const [customTo, setCustomTo] = import_react8.default.useState("");
3182
3361
  const [usage, setUsage] = import_react8.default.useState(null);
@@ -3186,6 +3365,32 @@ function UsagePage({ context }) {
3186
3365
  () => getUsageRange(period, customFrom, customTo),
3187
3366
  [customFrom, customTo, period]
3188
3367
  );
3368
+ const metricOptions = import_react8.default.useMemo(
3369
+ () => getUsageMetricOptions(workload),
3370
+ [workload]
3371
+ );
3372
+ const groupOptions = import_react8.default.useMemo(
3373
+ () => getUsageGroupOptions(workload),
3374
+ [workload]
3375
+ );
3376
+ const showLlmFields = workload === "llm";
3377
+ const showResourceFields = workload !== "llm";
3378
+ const showDimension = metricKind === "tokens" || metricKind === "cost" && workload === "llm";
3379
+ import_react8.default.useEffect(() => {
3380
+ if (!metricOptions.includes(metricKind)) {
3381
+ setMetricKind(metricOptions[0]);
3382
+ }
3383
+ }, [metricKind, metricOptions]);
3384
+ import_react8.default.useEffect(() => {
3385
+ if (!groupOptions.includes(groupBy)) {
3386
+ setGroupBy(groupOptions[0]);
3387
+ }
3388
+ }, [groupBy, groupOptions]);
3389
+ import_react8.default.useEffect(() => {
3390
+ if (!showDimension && dimension !== "total") {
3391
+ setDimension("total");
3392
+ }
3393
+ }, [dimension, showDimension]);
3189
3394
  import_react8.default.useEffect(() => {
3190
3395
  let active = true;
3191
3396
  setIsLoading(true);
@@ -3194,13 +3399,17 @@ function UsagePage({ context }) {
3194
3399
  attribution,
3195
3400
  from: range.from,
3196
3401
  groupBy,
3402
+ kind: workload,
3197
3403
  interval,
3198
3404
  metric: metricKind,
3199
- model: emptyToUndefined(model),
3405
+ model: showLlmFields ? emptyToUndefined(model) : void 0,
3200
3406
  namespace: context.scope.namespace || void 0,
3407
+ operation: showResourceFields ? emptyToUndefined(operation) : void 0,
3201
3408
  participantId: emptyToUndefined(participantId),
3202
3409
  participantType,
3203
- provider: emptyToUndefined(provider),
3410
+ provider: showLlmFields ? emptyToUndefined(provider) : void 0,
3411
+ resource: showResourceFields ? emptyToUndefined(resource) : void 0,
3412
+ status: status === "all" ? void 0 : status,
3204
3413
  threadId: emptyToUndefined(threadId),
3205
3414
  to: range.to
3206
3415
  }).then((next) => {
@@ -3225,12 +3434,18 @@ function UsagePage({ context }) {
3225
3434
  interval,
3226
3435
  metricKind,
3227
3436
  model,
3437
+ operation,
3228
3438
  participantId,
3229
3439
  participantType,
3230
3440
  provider,
3231
3441
  range.from,
3232
3442
  range.to,
3233
- threadId
3443
+ resource,
3444
+ showLlmFields,
3445
+ showResourceFields,
3446
+ status,
3447
+ threadId,
3448
+ workload
3234
3449
  ]);
3235
3450
  const points = usage?.points ?? [];
3236
3451
  const totals = usage?.totals ?? EMPTY_USAGE_TOTALS;
@@ -3247,9 +3462,10 @@ function UsagePage({ context }) {
3247
3462
  PageHeader,
3248
3463
  {
3249
3464
  title: "Usage",
3250
- description: "Inspect cost, token, and call attribution across namespaces, models, participants, threads, and providers.",
3465
+ description: "Inspect metered LLM, tool, and resource activity by workload, actor, status, cost, duration, tokens, and credits.",
3251
3466
  badges: [
3252
- { label: period },
3467
+ { label: workload },
3468
+ { label: getUsageMetricLabel(metricKind), variant: "secondary" },
3253
3469
  { label: getUsageGroupLabel(groupBy), variant: "secondary" },
3254
3470
  ...isLoading ? [{ label: "Loading" }] : []
3255
3471
  ]
@@ -3277,10 +3493,21 @@ function UsagePage({ context }) {
3277
3493
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3278
3494
  UsageSelect,
3279
3495
  {
3280
- label: "Metric",
3496
+ label: "Workload",
3497
+ onValueChange: (value) => setWorkload(value),
3498
+ options: USAGE_WORKLOADS,
3499
+ value: workload,
3500
+ formatOption: getUsageKindLabel
3501
+ }
3502
+ ),
3503
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3504
+ UsageSelect,
3505
+ {
3506
+ label: "Measure",
3281
3507
  onValueChange: (value) => setMetricKind(value),
3282
- options: ["cost", "tokens", "calls"],
3283
- value: metricKind
3508
+ options: metricOptions,
3509
+ value: metricKind,
3510
+ formatOption: getUsageMetricLabel
3284
3511
  }
3285
3512
  ),
3286
3513
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
@@ -3288,20 +3515,22 @@ function UsagePage({ context }) {
3288
3515
  {
3289
3516
  label: "Group",
3290
3517
  onValueChange: (value) => setGroupBy(value),
3291
- options: ["participant", "thread", "namespace", "provider", "model"],
3292
- value: groupBy
3518
+ options: groupOptions,
3519
+ value: groupBy,
3520
+ formatOption: getUsageGroupLabel
3293
3521
  }
3294
3522
  ),
3295
3523
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3296
3524
  UsageSelect,
3297
3525
  {
3298
- label: "Attribution",
3526
+ label: "Actor",
3299
3527
  onValueChange: (value) => setAttribution(value),
3300
3528
  options: ["initiatedBy", "generatedBy"],
3301
- value: attribution
3529
+ value: attribution,
3530
+ formatOption: getUsageAttributionLabel
3302
3531
  }
3303
3532
  ),
3304
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3533
+ showDimension && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3305
3534
  UsageSelect,
3306
3535
  {
3307
3536
  label: "Dimension",
@@ -3336,12 +3565,21 @@ function UsagePage({ context }) {
3336
3565
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3337
3566
  UsageSelect,
3338
3567
  {
3339
- label: "Type",
3568
+ label: "Actor type",
3340
3569
  onValueChange: (value) => setParticipantType(value),
3341
3570
  options: ["all", "human", "agent", "job"],
3342
3571
  value: participantType
3343
3572
  }
3344
3573
  ),
3574
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3575
+ UsageSelect,
3576
+ {
3577
+ label: "Status",
3578
+ onValueChange: (value) => setStatus(value),
3579
+ options: STATUS_OPTIONS,
3580
+ value: status
3581
+ }
3582
+ ),
3345
3583
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3346
3584
  Input,
3347
3585
  {
@@ -3360,46 +3598,86 @@ function UsagePage({ context }) {
3360
3598
  value: participantId
3361
3599
  }
3362
3600
  ),
3363
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3364
- Input,
3365
- {
3366
- className: "h-8 w-[140px]",
3367
- onChange: (event) => setProvider(event.target.value),
3368
- placeholder: "Provider",
3369
- value: provider
3370
- }
3371
- ),
3372
- /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3373
- Input,
3374
- {
3375
- className: "h-8 w-[140px]",
3376
- onChange: (event) => setModel(event.target.value),
3377
- placeholder: "Model",
3378
- value: model
3379
- }
3380
- )
3601
+ showResourceFields && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3602
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3603
+ Input,
3604
+ {
3605
+ className: "h-8 w-[170px]",
3606
+ onChange: (event) => setResource(event.target.value),
3607
+ placeholder: "Resource / tool",
3608
+ value: resource
3609
+ }
3610
+ ),
3611
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3612
+ Input,
3613
+ {
3614
+ className: "h-8 w-[150px]",
3615
+ onChange: (event) => setOperation(event.target.value),
3616
+ placeholder: "Operation",
3617
+ value: operation
3618
+ }
3619
+ )
3620
+ ] }),
3621
+ showLlmFields && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3622
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3623
+ Input,
3624
+ {
3625
+ className: "h-8 w-[140px]",
3626
+ onChange: (event) => setProvider(event.target.value),
3627
+ placeholder: "Provider",
3628
+ value: provider
3629
+ }
3630
+ ),
3631
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3632
+ Input,
3633
+ {
3634
+ className: "h-8 w-[140px]",
3635
+ onChange: (event) => setModel(event.target.value),
3636
+ placeholder: "Model",
3637
+ value: model
3638
+ }
3639
+ )
3640
+ ] })
3381
3641
  ] }),
3382
3642
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3383
3643
  MetricStrip,
3384
3644
  {
3385
3645
  items: [
3386
3646
  {
3387
- detail: `${totals.totalCalls} calls`,
3647
+ detail: `${formatMetricValue(totals.unpricedCalls, "unpriced")} unpriced`,
3388
3648
  icon: import_lucide_react12.DollarSign,
3389
- label: "Cost",
3649
+ label: "Spend",
3390
3650
  value: formatMetricValue(totals.totalCostUsd, "cost")
3391
3651
  },
3392
3652
  {
3393
- detail: `${formatMetricValue(totals.inputTokens, "tokens")} input`,
3653
+ detail: `${formatMetricValue(totals.failedCalls, "failures")} failed`,
3394
3654
  icon: import_lucide_react12.Activity,
3655
+ label: "Calls",
3656
+ value: formatMetricValue(totals.totalCalls, "calls")
3657
+ },
3658
+ {
3659
+ detail: "Tool and resource runtime",
3660
+ icon: import_lucide_react12.Clock,
3661
+ label: "Duration",
3662
+ value: formatMetricValue(totals.totalDurationMs, "duration")
3663
+ },
3664
+ {
3665
+ detail: `${formatMetricValue(totals.inputTokens, "tokens")} input`,
3666
+ icon: import_lucide_react12.Sparkles,
3395
3667
  label: "Tokens",
3396
3668
  value: formatMetricValue(totals.totalTokens, "tokens")
3397
3669
  },
3398
3670
  {
3399
- detail: `${formatMetricValue(totals.reasoningTokens, "tokens")} reasoning`,
3400
- icon: import_lucide_react12.Sparkles,
3401
- label: "Calls",
3402
- value: formatMetricValue(totals.totalCalls, "calls")
3671
+ detail: "Custom metering",
3672
+ icon: import_lucide_react12.Coins,
3673
+ label: "Credits",
3674
+ value: formatMetricValue(totals.totalCredits, "credits")
3675
+ },
3676
+ {
3677
+ detail: "Failed or aborted work",
3678
+ icon: import_lucide_react12.AlertTriangle,
3679
+ label: "Failures",
3680
+ value: formatMetricValue(totals.failedCalls, "failures")
3403
3681
  }
3404
3682
  ]
3405
3683
  }
@@ -3408,7 +3686,7 @@ function UsagePage({ context }) {
3408
3686
  EmptyState,
3409
3687
  {
3410
3688
  title: "No usage",
3411
- description: "Usage records will appear here when provider-native usage is captured."
3689
+ description: "Metered LLM, tool, and resource records will appear here after work is captured in the usage ledger."
3412
3690
  }
3413
3691
  ) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
3414
3692
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Card, { className: "h-[320px] py-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(CardContent, { className: "h-full px-3", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(UsageChart, { chartState, metricKind }) }) }),
@@ -3487,6 +3765,7 @@ function UsageRowsTable({
3487
3765
  totals
3488
3766
  }) {
3489
3767
  const totalValue = getUsageTotalValue(totals, metricKind, dimension);
3768
+ const valueHeader = (metricKind === "tokens" || metricKind === "cost") && dimension !== "total" ? `${getUsageDimensionLabel(dimension)} ${getUsageMetricLabel(metricKind)}` : getUsageMetricLabel(metricKind);
3490
3769
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
3491
3770
  ResourceTable,
3492
3771
  {
@@ -3504,7 +3783,7 @@ function UsageRowsTable({
3504
3783
  {
3505
3784
  align: "right",
3506
3785
  id: "value",
3507
- header: `${getUsageDimensionLabel(dimension)} ${metricKind}`,
3786
+ header: valueHeader,
3508
3787
  render: (row) => formatMetricValue(row.value, metricKind)
3509
3788
  },
3510
3789
  {
@@ -3521,20 +3800,100 @@ function UsageRowsTable({
3521
3800
  },
3522
3801
  {
3523
3802
  align: "right",
3524
- id: "input",
3525
- header: "Input",
3526
- render: (row) => formatMetricValue(getUsageTotalValue(row, metricKind, "input"), metricKind)
3803
+ id: "spend",
3804
+ header: "Spend",
3805
+ render: (row) => formatMetricValue(row.totalCostUsd, "cost")
3527
3806
  },
3528
3807
  {
3529
3808
  align: "right",
3530
- id: "output",
3531
- header: "Output",
3532
- render: (row) => formatMetricValue(getUsageTotalValue(row, metricKind, "output"), metricKind)
3809
+ id: "duration",
3810
+ header: "Duration",
3811
+ render: (row) => formatMetricValue(row.totalDurationMs, "duration")
3812
+ },
3813
+ {
3814
+ align: "right",
3815
+ id: "tokens",
3816
+ header: "Tokens",
3817
+ render: (row) => formatMetricValue(row.totalTokens, "tokens")
3818
+ },
3819
+ {
3820
+ align: "right",
3821
+ id: "failures",
3822
+ header: "Failures",
3823
+ render: (row) => formatMetricValue(row.failedCalls, "failures")
3824
+ },
3825
+ {
3826
+ align: "right",
3827
+ id: "unpriced",
3828
+ header: "Unpriced",
3829
+ render: (row) => formatMetricValue(row.unpricedCalls, "unpriced")
3533
3830
  }
3534
3831
  ]
3535
3832
  }
3536
3833
  );
3537
3834
  }
3835
+ function getUsageMetricOptions(workload) {
3836
+ if (workload === "llm") {
3837
+ return ["cost", "tokens", "calls", "failures", "unpriced"];
3838
+ }
3839
+ if (workload === "tool") {
3840
+ return ["calls", "duration", "cost", "credits", "failures", "unpriced"];
3841
+ }
3842
+ return [
3843
+ "calls",
3844
+ "cost",
3845
+ "duration",
3846
+ "tokens",
3847
+ "credits",
3848
+ "failures",
3849
+ "unpriced"
3850
+ ];
3851
+ }
3852
+ function getUsageGroupOptions(workload) {
3853
+ if (workload === "llm") {
3854
+ return ["participant", "model", "provider", "thread", "namespace", "status"];
3855
+ }
3856
+ if (workload === "tool") {
3857
+ return [
3858
+ "resource",
3859
+ "operation",
3860
+ "status",
3861
+ "participant",
3862
+ "thread",
3863
+ "namespace"
3864
+ ];
3865
+ }
3866
+ return [
3867
+ "kind",
3868
+ "resource",
3869
+ "operation",
3870
+ "status",
3871
+ "participant",
3872
+ "thread",
3873
+ "namespace"
3874
+ ];
3875
+ }
3876
+ function getUsageKindLabel(kind) {
3877
+ switch (kind) {
3878
+ case "all":
3879
+ return "All";
3880
+ case "llm":
3881
+ return "LLM";
3882
+ case "tool":
3883
+ return "Tools";
3884
+ case "asset":
3885
+ return "Assets";
3886
+ case "rag":
3887
+ return "RAG";
3888
+ case "embedding":
3889
+ return "Embeddings";
3890
+ default:
3891
+ return kind;
3892
+ }
3893
+ }
3894
+ function getUsageAttributionLabel(attribution) {
3895
+ return attribution === "initiatedBy" ? "Initiator" : "Performer";
3896
+ }
3538
3897
  function emptyToUndefined(value) {
3539
3898
  const trimmed = value.trim();
3540
3899
  return trimmed.length > 0 ? trimmed : void 0;
@@ -4023,6 +4382,7 @@ function useCopilotzAdmin(options = {}) {
4023
4382
  formatUsageBucket,
4024
4383
  getUsageDimensionLabel,
4025
4384
  getUsageGroupLabel,
4385
+ getUsageMetricLabel,
4026
4386
  getUsageRange,
4027
4387
  getUsageTotalValue,
4028
4388
  mergeAdminConfig,