@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.js CHANGED
@@ -81,6 +81,21 @@ function normalizeMessagePage(payload) {
81
81
  const pageInfo = isRecord(candidate) ? normalizeMessagePageInfo(candidate.pageInfo, data) : normalizeMessagePageInfo(void 0, data);
82
82
  return { data, pageInfo };
83
83
  }
84
+ function isQueueEvent(value) {
85
+ return isRecord(value) && typeof value.id === "string" && typeof value.threadId === "string" && typeof value.eventType === "string";
86
+ }
87
+ function normalizeQueueEvent(value) {
88
+ if (isQueueEvent(value)) return value;
89
+ if (isRecord(value) && isQueueEvent(value.data)) return value.data;
90
+ return void 0;
91
+ }
92
+ function normalizeQueueEvents(value) {
93
+ if (Array.isArray(value)) return value.filter(isQueueEvent);
94
+ if (isRecord(value) && Array.isArray(value.data)) {
95
+ return value.data.filter(isQueueEvent);
96
+ }
97
+ return [];
98
+ }
84
99
  function createAdminClient(options = {}) {
85
100
  const baseUrl = resolveBaseUrl(options.baseUrl);
86
101
  const paths = { ...DEFAULT_PATHS, ...options.paths };
@@ -140,12 +155,16 @@ function createAdminClient(options = {}) {
140
155
  metric: filters.metric,
141
156
  groupBy: filters.groupBy,
142
157
  attribution: filters.attribution,
158
+ kind: filters.kind,
143
159
  threadId: filters.threadId,
144
160
  participantId: filters.participantId,
145
161
  participantType: filters.participantType,
146
162
  namespace: filters.namespace,
147
163
  provider: filters.provider,
148
- model: filters.model
164
+ model: filters.model,
165
+ resource: filters.resource,
166
+ operation: filters.operation,
167
+ status: filters.status
149
168
  }),
150
169
  listThreads: async (listOptions = {}) => await requestJson(`${paths.adminBase}/threads`, {
151
170
  search: listOptions.search,
@@ -183,9 +202,25 @@ function createAdminClient(options = {}) {
183
202
  );
184
203
  return normalizeMessagePage(payload);
185
204
  },
186
- getThreadEvent: async (threadId) => await requestJson(
187
- `${paths.threadsBase}/${encodeURIComponent(threadId)}/events`
188
- ),
205
+ listEvents: async (eventOptions = {}) => {
206
+ const payload = await requestJson(`${paths.adminBase}/events`, {
207
+ namespace: eventOptions.namespace,
208
+ threadId: eventOptions.threadId,
209
+ status: eventOptions.status,
210
+ eventType: eventOptions.eventType,
211
+ traceId: eventOptions.traceId,
212
+ search: eventOptions.search,
213
+ limit: String(eventOptions.limit ?? 50),
214
+ offset: eventOptions.offset ? String(eventOptions.offset) : void 0
215
+ });
216
+ return normalizeQueueEvents(payload);
217
+ },
218
+ getThreadEvent: async (threadId) => {
219
+ const payload = await requestJson(
220
+ `${paths.threadsBase}/${encodeURIComponent(threadId)}/events`
221
+ );
222
+ return normalizeQueueEvent(payload);
223
+ },
189
224
  listCollections: async () => await requestJson(paths.collectionsBase),
190
225
  listCollectionItems: async (collection, listOptions = {}) => await requestJson(
191
226
  `${paths.collectionsBase}/${encodeURIComponent(collection)}`,
@@ -1398,7 +1433,11 @@ var EMPTY_USAGE_TOTALS = {
1398
1433
  reasoningTokens: 0,
1399
1434
  totalCalls: 0,
1400
1435
  totalCostUsd: 0,
1401
- totalTokens: 0
1436
+ failedCalls: 0,
1437
+ totalCredits: 0,
1438
+ totalDurationMs: 0,
1439
+ totalTokens: 0,
1440
+ unpricedCalls: 0
1402
1441
  };
1403
1442
  var USAGE_CHART_COLORS = [
1404
1443
  "hsl(var(--primary))",
@@ -1409,53 +1448,66 @@ var USAGE_CHART_COLORS = [
1409
1448
  "hsl(var(--muted-foreground))"
1410
1449
  ];
1411
1450
  function addUsageTotals(target, source) {
1412
- target.cacheCreationInputCostUsd += source.cacheCreationInputCostUsd;
1413
- target.cacheCreationInputTokens += source.cacheCreationInputTokens;
1414
- target.cacheReadInputCostUsd += source.cacheReadInputCostUsd;
1415
- target.cacheReadInputTokens += source.cacheReadInputTokens;
1416
- target.inputCostUsd += source.inputCostUsd;
1417
- target.inputTokens += source.inputTokens;
1418
- target.outputCostUsd += source.outputCostUsd;
1419
- target.outputTokens += source.outputTokens;
1420
- target.reasoningCostUsd += source.reasoningCostUsd;
1421
- target.reasoningTokens += source.reasoningTokens;
1422
- target.totalCalls += source.totalCalls;
1423
- target.totalCostUsd += source.totalCostUsd;
1424
- target.totalTokens += source.totalTokens;
1451
+ target.cacheCreationInputCostUsd += safeUsageNumber(source.cacheCreationInputCostUsd);
1452
+ target.cacheCreationInputTokens += safeUsageNumber(source.cacheCreationInputTokens);
1453
+ target.cacheReadInputCostUsd += safeUsageNumber(source.cacheReadInputCostUsd);
1454
+ target.cacheReadInputTokens += safeUsageNumber(source.cacheReadInputTokens);
1455
+ target.inputCostUsd += safeUsageNumber(source.inputCostUsd);
1456
+ target.inputTokens += safeUsageNumber(source.inputTokens);
1457
+ target.outputCostUsd += safeUsageNumber(source.outputCostUsd);
1458
+ target.outputTokens += safeUsageNumber(source.outputTokens);
1459
+ target.reasoningCostUsd += safeUsageNumber(source.reasoningCostUsd);
1460
+ target.reasoningTokens += safeUsageNumber(source.reasoningTokens);
1461
+ target.totalCalls += safeUsageNumber(source.totalCalls);
1462
+ target.totalCostUsd += safeUsageNumber(source.totalCostUsd);
1463
+ target.failedCalls += safeUsageNumber(source.failedCalls);
1464
+ target.totalCredits += safeUsageNumber(source.totalCredits);
1465
+ target.totalDurationMs += safeUsageNumber(source.totalDurationMs);
1466
+ target.totalTokens += safeUsageNumber(source.totalTokens);
1467
+ target.unpricedCalls += safeUsageNumber(source.unpricedCalls);
1468
+ }
1469
+ function safeUsageNumber(value) {
1470
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1425
1471
  }
1426
1472
  function getUsageTotalValue(totals, metricKind, dimension) {
1427
- if (metricKind === "calls") return totals.totalCalls;
1473
+ if (metricKind === "calls") return safeUsageNumber(totals.totalCalls);
1474
+ if (metricKind === "duration") {
1475
+ return safeUsageNumber(totals.totalDurationMs);
1476
+ }
1477
+ if (metricKind === "credits") return safeUsageNumber(totals.totalCredits);
1478
+ if (metricKind === "failures") return safeUsageNumber(totals.failedCalls);
1479
+ if (metricKind === "unpriced") return safeUsageNumber(totals.unpricedCalls);
1428
1480
  if (metricKind === "cost") {
1429
1481
  switch (dimension) {
1430
1482
  case "input":
1431
- return totals.inputCostUsd;
1483
+ return safeUsageNumber(totals.inputCostUsd);
1432
1484
  case "output":
1433
- return totals.outputCostUsd;
1485
+ return safeUsageNumber(totals.outputCostUsd);
1434
1486
  case "reasoning":
1435
- return totals.reasoningCostUsd;
1487
+ return safeUsageNumber(totals.reasoningCostUsd);
1436
1488
  case "cacheRead":
1437
- return totals.cacheReadInputCostUsd;
1489
+ return safeUsageNumber(totals.cacheReadInputCostUsd);
1438
1490
  case "cacheWrite":
1439
- return totals.cacheCreationInputCostUsd;
1491
+ return safeUsageNumber(totals.cacheCreationInputCostUsd);
1440
1492
  case "total":
1441
1493
  default:
1442
- return totals.totalCostUsd;
1494
+ return safeUsageNumber(totals.totalCostUsd);
1443
1495
  }
1444
1496
  }
1445
1497
  switch (dimension) {
1446
1498
  case "input":
1447
- return totals.inputTokens;
1499
+ return safeUsageNumber(totals.inputTokens);
1448
1500
  case "output":
1449
- return totals.outputTokens;
1501
+ return safeUsageNumber(totals.outputTokens);
1450
1502
  case "reasoning":
1451
- return totals.reasoningTokens;
1503
+ return safeUsageNumber(totals.reasoningTokens);
1452
1504
  case "cacheRead":
1453
- return totals.cacheReadInputTokens;
1505
+ return safeUsageNumber(totals.cacheReadInputTokens);
1454
1506
  case "cacheWrite":
1455
- return totals.cacheCreationInputTokens;
1507
+ return safeUsageNumber(totals.cacheCreationInputTokens);
1456
1508
  case "total":
1457
1509
  default:
1458
- return totals.totalTokens;
1510
+ return safeUsageNumber(totals.totalTokens);
1459
1511
  }
1460
1512
  }
1461
1513
  function aggregateUsageRows(points, metricKind, dimension) {
@@ -1567,6 +1619,14 @@ function getUsageDimensionLabel(dimension) {
1567
1619
  }
1568
1620
  function getUsageGroupLabel(groupBy) {
1569
1621
  switch (groupBy) {
1622
+ case "kind":
1623
+ return "Kind";
1624
+ case "resource":
1625
+ return "Resource";
1626
+ case "operation":
1627
+ return "Operation";
1628
+ case "status":
1629
+ return "Status";
1570
1630
  case "thread":
1571
1631
  return "Thread";
1572
1632
  case "namespace":
@@ -1580,6 +1640,25 @@ function getUsageGroupLabel(groupBy) {
1580
1640
  return "Participant";
1581
1641
  }
1582
1642
  }
1643
+ function getUsageMetricLabel(metricKind) {
1644
+ switch (metricKind) {
1645
+ case "cost":
1646
+ return "Spend";
1647
+ case "tokens":
1648
+ return "Tokens";
1649
+ case "duration":
1650
+ return "Duration";
1651
+ case "credits":
1652
+ return "Credits";
1653
+ case "failures":
1654
+ return "Failures";
1655
+ case "unpriced":
1656
+ return "Unpriced";
1657
+ case "calls":
1658
+ default:
1659
+ return "Calls";
1660
+ }
1661
+ }
1583
1662
  function formatUsageBucket(bucket, interval) {
1584
1663
  const date = new Date(bucket);
1585
1664
  if (Number.isNaN(date.getTime())) return bucket;
@@ -1622,6 +1701,9 @@ function formatMetricValue(value, metricKind) {
1622
1701
  style: "currency"
1623
1702
  }).format(value);
1624
1703
  }
1704
+ if (metricKind === "duration") {
1705
+ return formatDuration(value);
1706
+ }
1625
1707
  return formatNumber(value);
1626
1708
  }
1627
1709
  function formatCompactMetric(value, metricKind) {
@@ -1634,12 +1716,30 @@ function formatCompactMetric(value, metricKind) {
1634
1716
  style: "currency"
1635
1717
  }).format(value);
1636
1718
  }
1719
+ if (metricKind === "duration") {
1720
+ if (value >= 36e5) return `${(value / 36e5).toFixed(1)}h`;
1721
+ if (value >= 6e4) return `${(value / 6e4).toFixed(1)}m`;
1722
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}s`;
1723
+ return `${Math.round(value)}ms`;
1724
+ }
1637
1725
  return new Intl.NumberFormat(void 0, {
1638
1726
  compactDisplay: "short",
1639
1727
  maximumFractionDigits: 1,
1640
1728
  notation: "compact"
1641
1729
  }).format(value);
1642
1730
  }
1731
+ function formatDuration(value) {
1732
+ if (value >= 36e5) {
1733
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 36e5)} h`;
1734
+ }
1735
+ if (value >= 6e4) {
1736
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 6e4)} min`;
1737
+ }
1738
+ if (value >= 1e3) {
1739
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 1e3)} sec`;
1740
+ }
1741
+ return `${formatNumber(Math.round(value))} ms`;
1742
+ }
1643
1743
 
1644
1744
  // src/modules/agents/index.tsx
1645
1745
  import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
@@ -2117,33 +2217,61 @@ function eventsModule() {
2117
2217
  }]
2118
2218
  };
2119
2219
  }
2220
+ var EVENT_STATUSES = [
2221
+ "all",
2222
+ "pending",
2223
+ "processing",
2224
+ "completed",
2225
+ "failed",
2226
+ "expired",
2227
+ "overwritten"
2228
+ ];
2120
2229
  function EventsPage({ context }) {
2121
2230
  const [threadId, setThreadId] = React8.useState("");
2122
- const [status, setStatus] = React8.useState("");
2123
- const [type, setType] = React8.useState("");
2231
+ const [status, setStatus] = React8.useState(
2232
+ "all"
2233
+ );
2234
+ const [eventType, setEventType] = React8.useState("");
2124
2235
  const [traceId, setTraceId] = React8.useState("");
2125
- const [event, setEvent] = React8.useState(null);
2236
+ const [events, setEvents] = React8.useState([]);
2237
+ const [selectedEvent, setSelectedEvent] = React8.useState(null);
2126
2238
  const [error, setError] = React8.useState(null);
2127
- const [hasSearched, setHasSearched] = React8.useState(false);
2239
+ const [loading, setLoading] = React8.useState(false);
2240
+ const [hasLoaded, setHasLoaded] = React8.useState(false);
2128
2241
  const inspect = async () => {
2129
- if (!threadId.trim()) return;
2130
- setHasSearched(true);
2242
+ setHasLoaded(true);
2243
+ setLoading(true);
2131
2244
  setError(null);
2132
2245
  try {
2133
- const next = await context.client.getThreadEvent(threadId.trim());
2134
- setEvent(next ?? null);
2246
+ const next = await context.client.listEvents({
2247
+ namespace: context.scope.namespace || void 0,
2248
+ threadId: threadId.trim() || void 0,
2249
+ status: status === "all" ? void 0 : status,
2250
+ eventType: eventType.trim() || void 0,
2251
+ traceId: traceId.trim() || void 0,
2252
+ limit: 50
2253
+ });
2254
+ setEvents(next);
2255
+ setSelectedEvent(
2256
+ (current) => current && next.some((event) => event.id === current.id) ? current : next[0] ?? null
2257
+ );
2135
2258
  } catch (cause) {
2136
2259
  setError(cause instanceof Error ? cause.message : "Failed to load event");
2137
- setEvent(null);
2260
+ setEvents([]);
2261
+ setSelectedEvent(null);
2262
+ } finally {
2263
+ setLoading(false);
2138
2264
  }
2139
2265
  };
2140
- const rows = event && (!status || event.status.includes(status)) && (!type || event.eventType.includes(type)) && (!traceId || event.traceId?.includes(traceId)) ? [event] : [];
2266
+ React8.useEffect(() => {
2267
+ void inspect();
2268
+ }, [context.client, context.refreshKey, context.scope.namespace]);
2141
2269
  return /* @__PURE__ */ jsxs14("div", { className: "space-y-4", children: [
2142
2270
  /* @__PURE__ */ jsx22(
2143
2271
  PageHeader,
2144
2272
  {
2145
2273
  title: "Events",
2146
- description: "Queue and event inspection. Current backend support is thread-focused; filters are ready for broader event listing."
2274
+ description: "Queue and event inspection across the active namespace."
2147
2275
  }
2148
2276
  ),
2149
2277
  /* @__PURE__ */ jsxs14(
@@ -2152,13 +2280,13 @@ function EventsPage({ context }) {
2152
2280
  actions: /* @__PURE__ */ jsxs14(
2153
2281
  Button,
2154
2282
  {
2155
- disabled: !threadId.trim(),
2283
+ disabled: loading,
2156
2284
  onClick: () => void inspect(),
2157
2285
  size: "sm",
2158
2286
  type: "button",
2159
2287
  children: [
2160
2288
  /* @__PURE__ */ jsx22(Search2, { className: "size-3" }),
2161
- "Inspect"
2289
+ loading ? "Loading" : "Inspect"
2162
2290
  ]
2163
2291
  }
2164
2292
  ),
@@ -2167,37 +2295,45 @@ function EventsPage({ context }) {
2167
2295
  Input,
2168
2296
  {
2169
2297
  className: "h-8 w-[220px]",
2170
- onChange: (event2) => setThreadId(event2.target.value),
2171
- onKeyDown: (event2) => {
2172
- if (event2.key === "Enter") void inspect();
2298
+ onChange: (event) => setThreadId(event.target.value),
2299
+ onKeyDown: (event) => {
2300
+ if (event.key === "Enter") void inspect();
2173
2301
  },
2174
2302
  placeholder: "Thread ID",
2175
2303
  value: threadId
2176
2304
  }
2177
2305
  ),
2178
- /* @__PURE__ */ jsx22(
2179
- Input,
2306
+ /* @__PURE__ */ jsxs14(
2307
+ Select,
2180
2308
  {
2181
- className: "h-8 w-[150px]",
2182
- onChange: (event2) => setStatus(event2.target.value),
2183
- placeholder: "Status",
2184
- value: status
2309
+ value: status,
2310
+ onValueChange: (value) => setStatus(value),
2311
+ children: [
2312
+ /* @__PURE__ */ jsx22(SelectTrigger, { className: "h-8 w-[150px] text-xs", "aria-label": "Status", children: /* @__PURE__ */ jsx22(SelectValue, {}) }),
2313
+ /* @__PURE__ */ jsx22(SelectContent, { children: EVENT_STATUSES.map((option) => /* @__PURE__ */ jsx22(SelectItem, { value: option, children: option === "all" ? "All statuses" : option }, option)) })
2314
+ ]
2185
2315
  }
2186
2316
  ),
2187
2317
  /* @__PURE__ */ jsx22(
2188
2318
  Input,
2189
2319
  {
2190
2320
  className: "h-8 w-[180px]",
2191
- onChange: (event2) => setType(event2.target.value),
2321
+ onChange: (event) => setEventType(event.target.value),
2322
+ onKeyDown: (event) => {
2323
+ if (event.key === "Enter") void inspect();
2324
+ },
2192
2325
  placeholder: "Event type",
2193
- value: type
2326
+ value: eventType
2194
2327
  }
2195
2328
  ),
2196
2329
  /* @__PURE__ */ jsx22(
2197
2330
  Input,
2198
2331
  {
2199
2332
  className: "h-8 w-[180px]",
2200
- onChange: (event2) => setTraceId(event2.target.value),
2333
+ onChange: (event) => setTraceId(event.target.value),
2334
+ onKeyDown: (event) => {
2335
+ if (event.key === "Enter") void inspect();
2336
+ },
2201
2337
  placeholder: "Trace ID",
2202
2338
  value: traceId
2203
2339
  }
@@ -2205,27 +2341,34 @@ function EventsPage({ context }) {
2205
2341
  ]
2206
2342
  }
2207
2343
  ),
2208
- error ? /* @__PURE__ */ jsx22(EmptyState, { title: "Unable to inspect event", description: error }) : !hasSearched ? /* @__PURE__ */ jsx22(
2344
+ error ? /* @__PURE__ */ jsx22(EmptyState, { title: "Unable to inspect event", description: error }) : !hasLoaded && loading ? /* @__PURE__ */ jsx22(
2209
2345
  EmptyState,
2210
2346
  {
2211
2347
  icon: Activity,
2212
- title: "Choose a thread",
2213
- description: "Enter a thread ID to inspect its next pending event."
2348
+ title: "Loading events",
2349
+ description: "Fetching recent queue events for the active namespace."
2214
2350
  }
2215
2351
  ) : /* @__PURE__ */ jsxs14("div", { className: "grid gap-4 xl:grid-cols-[1fr_420px]", children: [
2216
2352
  /* @__PURE__ */ jsx22(
2217
2353
  ResourceTable,
2218
2354
  {
2219
- rows,
2355
+ rows: events,
2220
2356
  getRowKey: (row) => row.id,
2357
+ onRowClick: setSelectedEvent,
2221
2358
  empty: /* @__PURE__ */ jsx22(
2222
2359
  EmptyState,
2223
2360
  {
2224
- title: "No matching event",
2225
- description: "No pending event matched the current filters."
2361
+ title: "No matching events",
2362
+ description: "No event rows matched the current filters."
2226
2363
  }
2227
2364
  ),
2228
2365
  columns: [
2366
+ {
2367
+ id: "thread",
2368
+ header: "Thread",
2369
+ className: "max-w-[180px] truncate font-mono text-xs",
2370
+ render: (row) => row.threadId
2371
+ },
2229
2372
  {
2230
2373
  id: "type",
2231
2374
  header: "Type",
@@ -2249,7 +2392,20 @@ function EventsPage({ context }) {
2249
2392
  ]
2250
2393
  }
2251
2394
  ),
2252
- /* @__PURE__ */ jsx22(JsonPanel, { title: "Event JSON", value: event, minHeight: 420 })
2395
+ selectedEvent ? /* @__PURE__ */ jsx22(
2396
+ JsonPanel,
2397
+ {
2398
+ title: "Event JSON",
2399
+ value: selectedEvent,
2400
+ minHeight: 420
2401
+ }
2402
+ ) : /* @__PURE__ */ jsx22(
2403
+ EmptyState,
2404
+ {
2405
+ title: "No event selected",
2406
+ description: "Select an event row to inspect its payload."
2407
+ }
2408
+ )
2253
2409
  ] })
2254
2410
  ] });
2255
2411
  }
@@ -2963,7 +3119,15 @@ import {
2963
3119
  XAxis,
2964
3120
  YAxis
2965
3121
  } from "recharts";
2966
- import { Activity as Activity3, BarChart3, DollarSign, Sparkles as Sparkles2 } from "lucide-react";
3122
+ import {
3123
+ Activity as Activity3,
3124
+ AlertTriangle as AlertTriangle2,
3125
+ BarChart3,
3126
+ Clock,
3127
+ Coins,
3128
+ DollarSign,
3129
+ Sparkles as Sparkles2
3130
+ } from "lucide-react";
2967
3131
 
2968
3132
  // src/components/ui/chart.tsx
2969
3133
  import * as React12 from "react";
@@ -3091,6 +3255,22 @@ var USAGE_DIMENSIONS = [
3091
3255
  "cacheRead",
3092
3256
  "cacheWrite"
3093
3257
  ];
3258
+ var USAGE_WORKLOADS = [
3259
+ "all",
3260
+ "llm",
3261
+ "tool",
3262
+ "asset",
3263
+ "rag",
3264
+ "embedding"
3265
+ ];
3266
+ var STATUS_OPTIONS = [
3267
+ "all",
3268
+ "completed",
3269
+ "failed",
3270
+ "cancelled",
3271
+ "aborted",
3272
+ "error"
3273
+ ];
3094
3274
  function usageModule() {
3095
3275
  return {
3096
3276
  group: "operate",
@@ -3117,11 +3297,12 @@ function UsagePage({ context }) {
3117
3297
  "7d"
3118
3298
  );
3119
3299
  const [interval, setInterval] = React13.useState("day");
3300
+ const [workload, setWorkload] = React13.useState("all");
3120
3301
  const [metricKind, setMetricKind] = React13.useState(
3121
- "cost"
3302
+ "calls"
3122
3303
  );
3123
3304
  const [dimension, setDimension] = React13.useState("total");
3124
- const [groupBy, setGroupBy] = React13.useState("participant");
3305
+ const [groupBy, setGroupBy] = React13.useState("kind");
3125
3306
  const [attribution, setAttribution] = React13.useState(
3126
3307
  "initiatedBy"
3127
3308
  );
@@ -3130,6 +3311,11 @@ function UsagePage({ context }) {
3130
3311
  const [participantId, setParticipantId] = React13.useState("");
3131
3312
  const [provider, setProvider] = React13.useState("");
3132
3313
  const [model, setModel] = React13.useState("");
3314
+ const [resource, setResource] = React13.useState("");
3315
+ const [operation, setOperation] = React13.useState("");
3316
+ const [status, setStatus] = React13.useState(
3317
+ "all"
3318
+ );
3133
3319
  const [customFrom, setCustomFrom] = React13.useState("");
3134
3320
  const [customTo, setCustomTo] = React13.useState("");
3135
3321
  const [usage, setUsage] = React13.useState(null);
@@ -3139,6 +3325,32 @@ function UsagePage({ context }) {
3139
3325
  () => getUsageRange(period, customFrom, customTo),
3140
3326
  [customFrom, customTo, period]
3141
3327
  );
3328
+ const metricOptions = React13.useMemo(
3329
+ () => getUsageMetricOptions(workload),
3330
+ [workload]
3331
+ );
3332
+ const groupOptions = React13.useMemo(
3333
+ () => getUsageGroupOptions(workload),
3334
+ [workload]
3335
+ );
3336
+ const showLlmFields = workload === "llm";
3337
+ const showResourceFields = workload !== "llm";
3338
+ const showDimension = metricKind === "tokens" || metricKind === "cost" && workload === "llm";
3339
+ React13.useEffect(() => {
3340
+ if (!metricOptions.includes(metricKind)) {
3341
+ setMetricKind(metricOptions[0]);
3342
+ }
3343
+ }, [metricKind, metricOptions]);
3344
+ React13.useEffect(() => {
3345
+ if (!groupOptions.includes(groupBy)) {
3346
+ setGroupBy(groupOptions[0]);
3347
+ }
3348
+ }, [groupBy, groupOptions]);
3349
+ React13.useEffect(() => {
3350
+ if (!showDimension && dimension !== "total") {
3351
+ setDimension("total");
3352
+ }
3353
+ }, [dimension, showDimension]);
3142
3354
  React13.useEffect(() => {
3143
3355
  let active = true;
3144
3356
  setIsLoading(true);
@@ -3147,13 +3359,17 @@ function UsagePage({ context }) {
3147
3359
  attribution,
3148
3360
  from: range.from,
3149
3361
  groupBy,
3362
+ kind: workload,
3150
3363
  interval,
3151
3364
  metric: metricKind,
3152
- model: emptyToUndefined(model),
3365
+ model: showLlmFields ? emptyToUndefined(model) : void 0,
3153
3366
  namespace: context.scope.namespace || void 0,
3367
+ operation: showResourceFields ? emptyToUndefined(operation) : void 0,
3154
3368
  participantId: emptyToUndefined(participantId),
3155
3369
  participantType,
3156
- provider: emptyToUndefined(provider),
3370
+ provider: showLlmFields ? emptyToUndefined(provider) : void 0,
3371
+ resource: showResourceFields ? emptyToUndefined(resource) : void 0,
3372
+ status: status === "all" ? void 0 : status,
3157
3373
  threadId: emptyToUndefined(threadId),
3158
3374
  to: range.to
3159
3375
  }).then((next) => {
@@ -3178,12 +3394,18 @@ function UsagePage({ context }) {
3178
3394
  interval,
3179
3395
  metricKind,
3180
3396
  model,
3397
+ operation,
3181
3398
  participantId,
3182
3399
  participantType,
3183
3400
  provider,
3184
3401
  range.from,
3185
3402
  range.to,
3186
- threadId
3403
+ resource,
3404
+ showLlmFields,
3405
+ showResourceFields,
3406
+ status,
3407
+ threadId,
3408
+ workload
3187
3409
  ]);
3188
3410
  const points = usage?.points ?? [];
3189
3411
  const totals = usage?.totals ?? EMPTY_USAGE_TOTALS;
@@ -3200,9 +3422,10 @@ function UsagePage({ context }) {
3200
3422
  PageHeader,
3201
3423
  {
3202
3424
  title: "Usage",
3203
- description: "Inspect cost, token, and call attribution across namespaces, models, participants, threads, and providers.",
3425
+ description: "Inspect metered LLM, tool, and resource activity by workload, actor, status, cost, duration, tokens, and credits.",
3204
3426
  badges: [
3205
- { label: period },
3427
+ { label: workload },
3428
+ { label: getUsageMetricLabel(metricKind), variant: "secondary" },
3206
3429
  { label: getUsageGroupLabel(groupBy), variant: "secondary" },
3207
3430
  ...isLoading ? [{ label: "Loading" }] : []
3208
3431
  ]
@@ -3230,10 +3453,21 @@ function UsagePage({ context }) {
3230
3453
  /* @__PURE__ */ jsx27(
3231
3454
  UsageSelect,
3232
3455
  {
3233
- label: "Metric",
3456
+ label: "Workload",
3457
+ onValueChange: (value) => setWorkload(value),
3458
+ options: USAGE_WORKLOADS,
3459
+ value: workload,
3460
+ formatOption: getUsageKindLabel
3461
+ }
3462
+ ),
3463
+ /* @__PURE__ */ jsx27(
3464
+ UsageSelect,
3465
+ {
3466
+ label: "Measure",
3234
3467
  onValueChange: (value) => setMetricKind(value),
3235
- options: ["cost", "tokens", "calls"],
3236
- value: metricKind
3468
+ options: metricOptions,
3469
+ value: metricKind,
3470
+ formatOption: getUsageMetricLabel
3237
3471
  }
3238
3472
  ),
3239
3473
  /* @__PURE__ */ jsx27(
@@ -3241,20 +3475,22 @@ function UsagePage({ context }) {
3241
3475
  {
3242
3476
  label: "Group",
3243
3477
  onValueChange: (value) => setGroupBy(value),
3244
- options: ["participant", "thread", "namespace", "provider", "model"],
3245
- value: groupBy
3478
+ options: groupOptions,
3479
+ value: groupBy,
3480
+ formatOption: getUsageGroupLabel
3246
3481
  }
3247
3482
  ),
3248
3483
  /* @__PURE__ */ jsx27(
3249
3484
  UsageSelect,
3250
3485
  {
3251
- label: "Attribution",
3486
+ label: "Actor",
3252
3487
  onValueChange: (value) => setAttribution(value),
3253
3488
  options: ["initiatedBy", "generatedBy"],
3254
- value: attribution
3489
+ value: attribution,
3490
+ formatOption: getUsageAttributionLabel
3255
3491
  }
3256
3492
  ),
3257
- /* @__PURE__ */ jsx27(
3493
+ showDimension && /* @__PURE__ */ jsx27(
3258
3494
  UsageSelect,
3259
3495
  {
3260
3496
  label: "Dimension",
@@ -3289,12 +3525,21 @@ function UsagePage({ context }) {
3289
3525
  /* @__PURE__ */ jsx27(
3290
3526
  UsageSelect,
3291
3527
  {
3292
- label: "Type",
3528
+ label: "Actor type",
3293
3529
  onValueChange: (value) => setParticipantType(value),
3294
3530
  options: ["all", "human", "agent", "job"],
3295
3531
  value: participantType
3296
3532
  }
3297
3533
  ),
3534
+ /* @__PURE__ */ jsx27(
3535
+ UsageSelect,
3536
+ {
3537
+ label: "Status",
3538
+ onValueChange: (value) => setStatus(value),
3539
+ options: STATUS_OPTIONS,
3540
+ value: status
3541
+ }
3542
+ ),
3298
3543
  /* @__PURE__ */ jsx27(
3299
3544
  Input,
3300
3545
  {
@@ -3313,46 +3558,86 @@ function UsagePage({ context }) {
3313
3558
  value: participantId
3314
3559
  }
3315
3560
  ),
3316
- /* @__PURE__ */ jsx27(
3317
- Input,
3318
- {
3319
- className: "h-8 w-[140px]",
3320
- onChange: (event) => setProvider(event.target.value),
3321
- placeholder: "Provider",
3322
- value: provider
3323
- }
3324
- ),
3325
- /* @__PURE__ */ jsx27(
3326
- Input,
3327
- {
3328
- className: "h-8 w-[140px]",
3329
- onChange: (event) => setModel(event.target.value),
3330
- placeholder: "Model",
3331
- value: model
3332
- }
3333
- )
3561
+ showResourceFields && /* @__PURE__ */ jsxs19(Fragment2, { children: [
3562
+ /* @__PURE__ */ jsx27(
3563
+ Input,
3564
+ {
3565
+ className: "h-8 w-[170px]",
3566
+ onChange: (event) => setResource(event.target.value),
3567
+ placeholder: "Resource / tool",
3568
+ value: resource
3569
+ }
3570
+ ),
3571
+ /* @__PURE__ */ jsx27(
3572
+ Input,
3573
+ {
3574
+ className: "h-8 w-[150px]",
3575
+ onChange: (event) => setOperation(event.target.value),
3576
+ placeholder: "Operation",
3577
+ value: operation
3578
+ }
3579
+ )
3580
+ ] }),
3581
+ showLlmFields && /* @__PURE__ */ jsxs19(Fragment2, { children: [
3582
+ /* @__PURE__ */ jsx27(
3583
+ Input,
3584
+ {
3585
+ className: "h-8 w-[140px]",
3586
+ onChange: (event) => setProvider(event.target.value),
3587
+ placeholder: "Provider",
3588
+ value: provider
3589
+ }
3590
+ ),
3591
+ /* @__PURE__ */ jsx27(
3592
+ Input,
3593
+ {
3594
+ className: "h-8 w-[140px]",
3595
+ onChange: (event) => setModel(event.target.value),
3596
+ placeholder: "Model",
3597
+ value: model
3598
+ }
3599
+ )
3600
+ ] })
3334
3601
  ] }),
3335
3602
  /* @__PURE__ */ jsx27(
3336
3603
  MetricStrip,
3337
3604
  {
3338
3605
  items: [
3339
3606
  {
3340
- detail: `${totals.totalCalls} calls`,
3607
+ detail: `${formatMetricValue(totals.unpricedCalls, "unpriced")} unpriced`,
3341
3608
  icon: DollarSign,
3342
- label: "Cost",
3609
+ label: "Spend",
3343
3610
  value: formatMetricValue(totals.totalCostUsd, "cost")
3344
3611
  },
3345
3612
  {
3346
- detail: `${formatMetricValue(totals.inputTokens, "tokens")} input`,
3613
+ detail: `${formatMetricValue(totals.failedCalls, "failures")} failed`,
3347
3614
  icon: Activity3,
3615
+ label: "Calls",
3616
+ value: formatMetricValue(totals.totalCalls, "calls")
3617
+ },
3618
+ {
3619
+ detail: "Tool and resource runtime",
3620
+ icon: Clock,
3621
+ label: "Duration",
3622
+ value: formatMetricValue(totals.totalDurationMs, "duration")
3623
+ },
3624
+ {
3625
+ detail: `${formatMetricValue(totals.inputTokens, "tokens")} input`,
3626
+ icon: Sparkles2,
3348
3627
  label: "Tokens",
3349
3628
  value: formatMetricValue(totals.totalTokens, "tokens")
3350
3629
  },
3351
3630
  {
3352
- detail: `${formatMetricValue(totals.reasoningTokens, "tokens")} reasoning`,
3353
- icon: Sparkles2,
3354
- label: "Calls",
3355
- value: formatMetricValue(totals.totalCalls, "calls")
3631
+ detail: "Custom metering",
3632
+ icon: Coins,
3633
+ label: "Credits",
3634
+ value: formatMetricValue(totals.totalCredits, "credits")
3635
+ },
3636
+ {
3637
+ detail: "Failed or aborted work",
3638
+ icon: AlertTriangle2,
3639
+ label: "Failures",
3640
+ value: formatMetricValue(totals.failedCalls, "failures")
3356
3641
  }
3357
3642
  ]
3358
3643
  }
@@ -3361,7 +3646,7 @@ function UsagePage({ context }) {
3361
3646
  EmptyState,
3362
3647
  {
3363
3648
  title: "No usage",
3364
- description: "Usage records will appear here when provider-native usage is captured."
3649
+ description: "Metered LLM, tool, and resource records will appear here after work is captured in the usage ledger."
3365
3650
  }
3366
3651
  ) : /* @__PURE__ */ jsxs19(Fragment2, { children: [
3367
3652
  /* @__PURE__ */ jsx27(Card, { className: "h-[320px] py-3", children: /* @__PURE__ */ jsx27(CardContent, { className: "h-full px-3", children: /* @__PURE__ */ jsx27(UsageChart, { chartState, metricKind }) }) }),
@@ -3440,6 +3725,7 @@ function UsageRowsTable({
3440
3725
  totals
3441
3726
  }) {
3442
3727
  const totalValue = getUsageTotalValue(totals, metricKind, dimension);
3728
+ const valueHeader = (metricKind === "tokens" || metricKind === "cost") && dimension !== "total" ? `${getUsageDimensionLabel(dimension)} ${getUsageMetricLabel(metricKind)}` : getUsageMetricLabel(metricKind);
3443
3729
  return /* @__PURE__ */ jsx27(
3444
3730
  ResourceTable,
3445
3731
  {
@@ -3457,7 +3743,7 @@ function UsageRowsTable({
3457
3743
  {
3458
3744
  align: "right",
3459
3745
  id: "value",
3460
- header: `${getUsageDimensionLabel(dimension)} ${metricKind}`,
3746
+ header: valueHeader,
3461
3747
  render: (row) => formatMetricValue(row.value, metricKind)
3462
3748
  },
3463
3749
  {
@@ -3474,20 +3760,100 @@ function UsageRowsTable({
3474
3760
  },
3475
3761
  {
3476
3762
  align: "right",
3477
- id: "input",
3478
- header: "Input",
3479
- render: (row) => formatMetricValue(getUsageTotalValue(row, metricKind, "input"), metricKind)
3763
+ id: "spend",
3764
+ header: "Spend",
3765
+ render: (row) => formatMetricValue(row.totalCostUsd, "cost")
3766
+ },
3767
+ {
3768
+ align: "right",
3769
+ id: "duration",
3770
+ header: "Duration",
3771
+ render: (row) => formatMetricValue(row.totalDurationMs, "duration")
3772
+ },
3773
+ {
3774
+ align: "right",
3775
+ id: "tokens",
3776
+ header: "Tokens",
3777
+ render: (row) => formatMetricValue(row.totalTokens, "tokens")
3778
+ },
3779
+ {
3780
+ align: "right",
3781
+ id: "failures",
3782
+ header: "Failures",
3783
+ render: (row) => formatMetricValue(row.failedCalls, "failures")
3480
3784
  },
3481
3785
  {
3482
3786
  align: "right",
3483
- id: "output",
3484
- header: "Output",
3485
- render: (row) => formatMetricValue(getUsageTotalValue(row, metricKind, "output"), metricKind)
3787
+ id: "unpriced",
3788
+ header: "Unpriced",
3789
+ render: (row) => formatMetricValue(row.unpricedCalls, "unpriced")
3486
3790
  }
3487
3791
  ]
3488
3792
  }
3489
3793
  );
3490
3794
  }
3795
+ function getUsageMetricOptions(workload) {
3796
+ if (workload === "llm") {
3797
+ return ["cost", "tokens", "calls", "failures", "unpriced"];
3798
+ }
3799
+ if (workload === "tool") {
3800
+ return ["calls", "duration", "cost", "credits", "failures", "unpriced"];
3801
+ }
3802
+ return [
3803
+ "calls",
3804
+ "cost",
3805
+ "duration",
3806
+ "tokens",
3807
+ "credits",
3808
+ "failures",
3809
+ "unpriced"
3810
+ ];
3811
+ }
3812
+ function getUsageGroupOptions(workload) {
3813
+ if (workload === "llm") {
3814
+ return ["participant", "model", "provider", "thread", "namespace", "status"];
3815
+ }
3816
+ if (workload === "tool") {
3817
+ return [
3818
+ "resource",
3819
+ "operation",
3820
+ "status",
3821
+ "participant",
3822
+ "thread",
3823
+ "namespace"
3824
+ ];
3825
+ }
3826
+ return [
3827
+ "kind",
3828
+ "resource",
3829
+ "operation",
3830
+ "status",
3831
+ "participant",
3832
+ "thread",
3833
+ "namespace"
3834
+ ];
3835
+ }
3836
+ function getUsageKindLabel(kind) {
3837
+ switch (kind) {
3838
+ case "all":
3839
+ return "All";
3840
+ case "llm":
3841
+ return "LLM";
3842
+ case "tool":
3843
+ return "Tools";
3844
+ case "asset":
3845
+ return "Assets";
3846
+ case "rag":
3847
+ return "RAG";
3848
+ case "embedding":
3849
+ return "Embeddings";
3850
+ default:
3851
+ return kind;
3852
+ }
3853
+ }
3854
+ function getUsageAttributionLabel(attribution) {
3855
+ return attribution === "initiatedBy" ? "Initiator" : "Performer";
3856
+ }
3491
3857
  function emptyToUndefined(value) {
3492
3858
  const trimmed = value.trim();
3493
3859
  return trimmed.length > 0 ? trimmed : void 0;
@@ -3975,6 +4341,7 @@ export {
3975
4341
  formatUsageBucket,
3976
4342
  getUsageDimensionLabel,
3977
4343
  getUsageGroupLabel,
4344
+ getUsageMetricLabel,
3978
4345
  getUsageRange,
3979
4346
  getUsageTotalValue,
3980
4347
  mergeAdminConfig,