@copilotz/admin 0.9.35 → 0.9.37

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
@@ -43,11 +43,44 @@ async function mergeHeaders(headers, getRequestHeaders) {
43
43
  async function parseJsonResponse(response) {
44
44
  const payload = await response.json().catch(() => null);
45
45
  if (!response.ok) {
46
- const message = payload?.message ?? payload?.data?.message ?? `Admin request failed (${response.status})`;
46
+ const message = payload?.message ?? payload?.data?.message ?? payload?.error?.message ?? `Admin request failed (${response.status})`;
47
47
  throw new Error(message);
48
48
  }
49
49
  return payload?.data ?? payload;
50
50
  }
51
+ async function parseJsonEnvelopeResponse(response) {
52
+ const payload = await response.json().catch(() => null);
53
+ if (!response.ok) {
54
+ const message = payload?.message ?? payload?.data?.message ?? payload?.error?.message ?? `Admin request failed (${response.status})`;
55
+ throw new Error(message);
56
+ }
57
+ return payload;
58
+ }
59
+ function isRecord(value) {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
62
+ function normalizeMessagePageInfo(value, messages) {
63
+ const oldestFromData = messages[0]?.id ?? null;
64
+ const newestFromData = messages[messages.length - 1]?.id ?? null;
65
+ if (!isRecord(value)) {
66
+ return {
67
+ hasMoreBefore: false,
68
+ oldestMessageId: oldestFromData,
69
+ newestMessageId: newestFromData
70
+ };
71
+ }
72
+ return {
73
+ hasMoreBefore: value.hasMoreBefore === true,
74
+ oldestMessageId: typeof value.oldestMessageId === "string" ? value.oldestMessageId : oldestFromData,
75
+ newestMessageId: typeof value.newestMessageId === "string" ? value.newestMessageId : newestFromData
76
+ };
77
+ }
78
+ function normalizeMessagePage(payload) {
79
+ const candidate = isRecord(payload) && isRecord(payload.data) && Array.isArray(payload.data.data) ? payload.data : payload;
80
+ const data = isRecord(candidate) && Array.isArray(candidate.data) ? candidate.data : Array.isArray(candidate) ? candidate : [];
81
+ const pageInfo = isRecord(candidate) ? normalizeMessagePageInfo(candidate.pageInfo, data) : normalizeMessagePageInfo(void 0, data);
82
+ return { data, pageInfo };
83
+ }
51
84
  function createAdminClient(options = {}) {
52
85
  const baseUrl = resolveBaseUrl(options.baseUrl);
53
86
  const paths = { ...DEFAULT_PATHS, ...options.paths };
@@ -58,6 +91,13 @@ function createAdminClient(options = {}) {
58
91
  });
59
92
  return await parseJsonResponse(response);
60
93
  };
94
+ const requestEnvelopeJson = async (path, params) => {
95
+ const url = buildUrl(baseUrl, path, params);
96
+ const response = await fetch(url.toString(), {
97
+ headers: await mergeHeaders({}, options.getRequestHeaders)
98
+ });
99
+ return await parseJsonEnvelopeResponse(response);
100
+ };
61
101
  const writeJson = async (method, path, data, params) => {
62
102
  const url = buildUrl(baseUrl, path, params);
63
103
  const response = await fetch(url.toString(), {
@@ -100,12 +140,16 @@ function createAdminClient(options = {}) {
100
140
  metric: filters.metric,
101
141
  groupBy: filters.groupBy,
102
142
  attribution: filters.attribution,
143
+ kind: filters.kind,
103
144
  threadId: filters.threadId,
104
145
  participantId: filters.participantId,
105
146
  participantType: filters.participantType,
106
147
  namespace: filters.namespace,
107
148
  provider: filters.provider,
108
- model: filters.model
149
+ model: filters.model,
150
+ resource: filters.resource,
151
+ operation: filters.operation,
152
+ status: filters.status
109
153
  }),
110
154
  listThreads: async (listOptions = {}) => await requestJson(`${paths.adminBase}/threads`, {
111
155
  search: listOptions.search,
@@ -133,13 +177,16 @@ function createAdminClient(options = {}) {
133
177
  getThread: async (threadId) => await requestJson(
134
178
  `${paths.threadsBase}/${encodeURIComponent(threadId)}`
135
179
  ),
136
- getThreadMessages: async (threadId, messageOptions = {}) => await requestJson(
137
- `${paths.threadsBase}/${encodeURIComponent(threadId)}/messages`,
138
- {
139
- limit: messageOptions.limit ? String(messageOptions.limit) : void 0,
140
- before: messageOptions.before
141
- }
142
- ),
180
+ getThreadMessages: async (threadId, messageOptions = {}) => {
181
+ const payload = await requestEnvelopeJson(
182
+ `${paths.threadsBase}/${encodeURIComponent(threadId)}/messages`,
183
+ {
184
+ limit: messageOptions.limit ? String(messageOptions.limit) : void 0,
185
+ before: messageOptions.before
186
+ }
187
+ );
188
+ return normalizeMessagePage(payload);
189
+ },
143
190
  getThreadEvent: async (threadId) => await requestJson(
144
191
  `${paths.threadsBase}/${encodeURIComponent(threadId)}/events`
145
192
  ),
@@ -1355,7 +1402,11 @@ var EMPTY_USAGE_TOTALS = {
1355
1402
  reasoningTokens: 0,
1356
1403
  totalCalls: 0,
1357
1404
  totalCostUsd: 0,
1358
- totalTokens: 0
1405
+ failedCalls: 0,
1406
+ totalCredits: 0,
1407
+ totalDurationMs: 0,
1408
+ totalTokens: 0,
1409
+ unpricedCalls: 0
1359
1410
  };
1360
1411
  var USAGE_CHART_COLORS = [
1361
1412
  "hsl(var(--primary))",
@@ -1366,53 +1417,66 @@ var USAGE_CHART_COLORS = [
1366
1417
  "hsl(var(--muted-foreground))"
1367
1418
  ];
1368
1419
  function addUsageTotals(target, source) {
1369
- target.cacheCreationInputCostUsd += source.cacheCreationInputCostUsd;
1370
- target.cacheCreationInputTokens += source.cacheCreationInputTokens;
1371
- target.cacheReadInputCostUsd += source.cacheReadInputCostUsd;
1372
- target.cacheReadInputTokens += source.cacheReadInputTokens;
1373
- target.inputCostUsd += source.inputCostUsd;
1374
- target.inputTokens += source.inputTokens;
1375
- target.outputCostUsd += source.outputCostUsd;
1376
- target.outputTokens += source.outputTokens;
1377
- target.reasoningCostUsd += source.reasoningCostUsd;
1378
- target.reasoningTokens += source.reasoningTokens;
1379
- target.totalCalls += source.totalCalls;
1380
- target.totalCostUsd += source.totalCostUsd;
1381
- target.totalTokens += source.totalTokens;
1420
+ target.cacheCreationInputCostUsd += safeUsageNumber(source.cacheCreationInputCostUsd);
1421
+ target.cacheCreationInputTokens += safeUsageNumber(source.cacheCreationInputTokens);
1422
+ target.cacheReadInputCostUsd += safeUsageNumber(source.cacheReadInputCostUsd);
1423
+ target.cacheReadInputTokens += safeUsageNumber(source.cacheReadInputTokens);
1424
+ target.inputCostUsd += safeUsageNumber(source.inputCostUsd);
1425
+ target.inputTokens += safeUsageNumber(source.inputTokens);
1426
+ target.outputCostUsd += safeUsageNumber(source.outputCostUsd);
1427
+ target.outputTokens += safeUsageNumber(source.outputTokens);
1428
+ target.reasoningCostUsd += safeUsageNumber(source.reasoningCostUsd);
1429
+ target.reasoningTokens += safeUsageNumber(source.reasoningTokens);
1430
+ target.totalCalls += safeUsageNumber(source.totalCalls);
1431
+ target.totalCostUsd += safeUsageNumber(source.totalCostUsd);
1432
+ target.failedCalls += safeUsageNumber(source.failedCalls);
1433
+ target.totalCredits += safeUsageNumber(source.totalCredits);
1434
+ target.totalDurationMs += safeUsageNumber(source.totalDurationMs);
1435
+ target.totalTokens += safeUsageNumber(source.totalTokens);
1436
+ target.unpricedCalls += safeUsageNumber(source.unpricedCalls);
1437
+ }
1438
+ function safeUsageNumber(value) {
1439
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1382
1440
  }
1383
1441
  function getUsageTotalValue(totals, metricKind, dimension) {
1384
- if (metricKind === "calls") return totals.totalCalls;
1442
+ if (metricKind === "calls") return safeUsageNumber(totals.totalCalls);
1443
+ if (metricKind === "duration") {
1444
+ return safeUsageNumber(totals.totalDurationMs);
1445
+ }
1446
+ if (metricKind === "credits") return safeUsageNumber(totals.totalCredits);
1447
+ if (metricKind === "failures") return safeUsageNumber(totals.failedCalls);
1448
+ if (metricKind === "unpriced") return safeUsageNumber(totals.unpricedCalls);
1385
1449
  if (metricKind === "cost") {
1386
1450
  switch (dimension) {
1387
1451
  case "input":
1388
- return totals.inputCostUsd;
1452
+ return safeUsageNumber(totals.inputCostUsd);
1389
1453
  case "output":
1390
- return totals.outputCostUsd;
1454
+ return safeUsageNumber(totals.outputCostUsd);
1391
1455
  case "reasoning":
1392
- return totals.reasoningCostUsd;
1456
+ return safeUsageNumber(totals.reasoningCostUsd);
1393
1457
  case "cacheRead":
1394
- return totals.cacheReadInputCostUsd;
1458
+ return safeUsageNumber(totals.cacheReadInputCostUsd);
1395
1459
  case "cacheWrite":
1396
- return totals.cacheCreationInputCostUsd;
1460
+ return safeUsageNumber(totals.cacheCreationInputCostUsd);
1397
1461
  case "total":
1398
1462
  default:
1399
- return totals.totalCostUsd;
1463
+ return safeUsageNumber(totals.totalCostUsd);
1400
1464
  }
1401
1465
  }
1402
1466
  switch (dimension) {
1403
1467
  case "input":
1404
- return totals.inputTokens;
1468
+ return safeUsageNumber(totals.inputTokens);
1405
1469
  case "output":
1406
- return totals.outputTokens;
1470
+ return safeUsageNumber(totals.outputTokens);
1407
1471
  case "reasoning":
1408
- return totals.reasoningTokens;
1472
+ return safeUsageNumber(totals.reasoningTokens);
1409
1473
  case "cacheRead":
1410
- return totals.cacheReadInputTokens;
1474
+ return safeUsageNumber(totals.cacheReadInputTokens);
1411
1475
  case "cacheWrite":
1412
- return totals.cacheCreationInputTokens;
1476
+ return safeUsageNumber(totals.cacheCreationInputTokens);
1413
1477
  case "total":
1414
1478
  default:
1415
- return totals.totalTokens;
1479
+ return safeUsageNumber(totals.totalTokens);
1416
1480
  }
1417
1481
  }
1418
1482
  function aggregateUsageRows(points, metricKind, dimension) {
@@ -1524,6 +1588,14 @@ function getUsageDimensionLabel(dimension) {
1524
1588
  }
1525
1589
  function getUsageGroupLabel(groupBy) {
1526
1590
  switch (groupBy) {
1591
+ case "kind":
1592
+ return "Kind";
1593
+ case "resource":
1594
+ return "Resource";
1595
+ case "operation":
1596
+ return "Operation";
1597
+ case "status":
1598
+ return "Status";
1527
1599
  case "thread":
1528
1600
  return "Thread";
1529
1601
  case "namespace":
@@ -1537,6 +1609,25 @@ function getUsageGroupLabel(groupBy) {
1537
1609
  return "Participant";
1538
1610
  }
1539
1611
  }
1612
+ function getUsageMetricLabel(metricKind) {
1613
+ switch (metricKind) {
1614
+ case "cost":
1615
+ return "Spend";
1616
+ case "tokens":
1617
+ return "Tokens";
1618
+ case "duration":
1619
+ return "Duration";
1620
+ case "credits":
1621
+ return "Credits";
1622
+ case "failures":
1623
+ return "Failures";
1624
+ case "unpriced":
1625
+ return "Unpriced";
1626
+ case "calls":
1627
+ default:
1628
+ return "Calls";
1629
+ }
1630
+ }
1540
1631
  function formatUsageBucket(bucket, interval) {
1541
1632
  const date = new Date(bucket);
1542
1633
  if (Number.isNaN(date.getTime())) return bucket;
@@ -1579,6 +1670,9 @@ function formatMetricValue(value, metricKind) {
1579
1670
  style: "currency"
1580
1671
  }).format(value);
1581
1672
  }
1673
+ if (metricKind === "duration") {
1674
+ return formatDuration(value);
1675
+ }
1582
1676
  return formatNumber(value);
1583
1677
  }
1584
1678
  function formatCompactMetric(value, metricKind) {
@@ -1591,12 +1685,30 @@ function formatCompactMetric(value, metricKind) {
1591
1685
  style: "currency"
1592
1686
  }).format(value);
1593
1687
  }
1688
+ if (metricKind === "duration") {
1689
+ if (value >= 36e5) return `${(value / 36e5).toFixed(1)}h`;
1690
+ if (value >= 6e4) return `${(value / 6e4).toFixed(1)}m`;
1691
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}s`;
1692
+ return `${Math.round(value)}ms`;
1693
+ }
1594
1694
  return new Intl.NumberFormat(void 0, {
1595
1695
  compactDisplay: "short",
1596
1696
  maximumFractionDigits: 1,
1597
1697
  notation: "compact"
1598
1698
  }).format(value);
1599
1699
  }
1700
+ function formatDuration(value) {
1701
+ if (value >= 36e5) {
1702
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 36e5)} h`;
1703
+ }
1704
+ if (value >= 6e4) {
1705
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 6e4)} min`;
1706
+ }
1707
+ if (value >= 1e3) {
1708
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value / 1e3)} sec`;
1709
+ }
1710
+ return `${formatNumber(Math.round(value))} ms`;
1711
+ }
1600
1712
 
1601
1713
  // src/modules/agents/index.tsx
1602
1714
  import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
@@ -2704,7 +2816,9 @@ function ThreadsPage({ context }) {
2704
2816
  align: "right",
2705
2817
  id: "participants",
2706
2818
  header: "Participants",
2707
- render: (thread) => formatNumber(thread.participantIds.length)
2819
+ render: (thread) => formatNumber(
2820
+ Array.isArray(thread.participantIds) ? thread.participantIds.length : 0
2821
+ )
2708
2822
  },
2709
2823
  {
2710
2824
  align: "right",
@@ -2755,8 +2869,8 @@ function ThreadInspector({
2755
2869
  ]).then(([nextThread, page]) => {
2756
2870
  if (!active) return;
2757
2871
  setThread(nextThread);
2758
- setMessages(page.data);
2759
- setPageInfo(page.pageInfo);
2872
+ setMessages(Array.isArray(page?.data) ? page.data : []);
2873
+ setPageInfo(page?.pageInfo ?? null);
2760
2874
  }).catch((cause) => {
2761
2875
  if (active) setError(cause instanceof Error ? cause.message : "Failed to load thread");
2762
2876
  }).finally(() => {
@@ -2776,8 +2890,11 @@ function ThreadInspector({
2776
2890
  before: pageInfo.oldestMessageId,
2777
2891
  limit: MESSAGE_PAGE_SIZE
2778
2892
  });
2779
- setMessages((current) => [...page.data, ...current]);
2780
- setPageInfo(page.pageInfo);
2893
+ setMessages((current) => [
2894
+ ...Array.isArray(page?.data) ? page.data : [],
2895
+ ...current
2896
+ ]);
2897
+ setPageInfo(page?.pageInfo ?? null);
2781
2898
  } finally {
2782
2899
  setIsLoadingMore(false);
2783
2900
  }
@@ -2915,7 +3032,15 @@ import {
2915
3032
  XAxis,
2916
3033
  YAxis
2917
3034
  } from "recharts";
2918
- import { Activity as Activity3, BarChart3, DollarSign, Sparkles as Sparkles2 } from "lucide-react";
3035
+ import {
3036
+ Activity as Activity3,
3037
+ AlertTriangle as AlertTriangle2,
3038
+ BarChart3,
3039
+ Clock,
3040
+ Coins,
3041
+ DollarSign,
3042
+ Sparkles as Sparkles2
3043
+ } from "lucide-react";
2919
3044
 
2920
3045
  // src/components/ui/chart.tsx
2921
3046
  import * as React12 from "react";
@@ -3043,6 +3168,22 @@ var USAGE_DIMENSIONS = [
3043
3168
  "cacheRead",
3044
3169
  "cacheWrite"
3045
3170
  ];
3171
+ var USAGE_WORKLOADS = [
3172
+ "all",
3173
+ "llm",
3174
+ "tool",
3175
+ "asset",
3176
+ "rag",
3177
+ "embedding"
3178
+ ];
3179
+ var STATUS_OPTIONS = [
3180
+ "all",
3181
+ "completed",
3182
+ "failed",
3183
+ "cancelled",
3184
+ "aborted",
3185
+ "error"
3186
+ ];
3046
3187
  function usageModule() {
3047
3188
  return {
3048
3189
  group: "operate",
@@ -3069,11 +3210,12 @@ function UsagePage({ context }) {
3069
3210
  "7d"
3070
3211
  );
3071
3212
  const [interval, setInterval] = React13.useState("day");
3213
+ const [workload, setWorkload] = React13.useState("all");
3072
3214
  const [metricKind, setMetricKind] = React13.useState(
3073
- "cost"
3215
+ "calls"
3074
3216
  );
3075
3217
  const [dimension, setDimension] = React13.useState("total");
3076
- const [groupBy, setGroupBy] = React13.useState("participant");
3218
+ const [groupBy, setGroupBy] = React13.useState("kind");
3077
3219
  const [attribution, setAttribution] = React13.useState(
3078
3220
  "initiatedBy"
3079
3221
  );
@@ -3082,6 +3224,11 @@ function UsagePage({ context }) {
3082
3224
  const [participantId, setParticipantId] = React13.useState("");
3083
3225
  const [provider, setProvider] = React13.useState("");
3084
3226
  const [model, setModel] = React13.useState("");
3227
+ const [resource, setResource] = React13.useState("");
3228
+ const [operation, setOperation] = React13.useState("");
3229
+ const [status, setStatus] = React13.useState(
3230
+ "all"
3231
+ );
3085
3232
  const [customFrom, setCustomFrom] = React13.useState("");
3086
3233
  const [customTo, setCustomTo] = React13.useState("");
3087
3234
  const [usage, setUsage] = React13.useState(null);
@@ -3091,6 +3238,32 @@ function UsagePage({ context }) {
3091
3238
  () => getUsageRange(period, customFrom, customTo),
3092
3239
  [customFrom, customTo, period]
3093
3240
  );
3241
+ const metricOptions = React13.useMemo(
3242
+ () => getUsageMetricOptions(workload),
3243
+ [workload]
3244
+ );
3245
+ const groupOptions = React13.useMemo(
3246
+ () => getUsageGroupOptions(workload),
3247
+ [workload]
3248
+ );
3249
+ const showLlmFields = workload === "llm";
3250
+ const showResourceFields = workload !== "llm";
3251
+ const showDimension = metricKind === "tokens" || metricKind === "cost" && workload === "llm";
3252
+ React13.useEffect(() => {
3253
+ if (!metricOptions.includes(metricKind)) {
3254
+ setMetricKind(metricOptions[0]);
3255
+ }
3256
+ }, [metricKind, metricOptions]);
3257
+ React13.useEffect(() => {
3258
+ if (!groupOptions.includes(groupBy)) {
3259
+ setGroupBy(groupOptions[0]);
3260
+ }
3261
+ }, [groupBy, groupOptions]);
3262
+ React13.useEffect(() => {
3263
+ if (!showDimension && dimension !== "total") {
3264
+ setDimension("total");
3265
+ }
3266
+ }, [dimension, showDimension]);
3094
3267
  React13.useEffect(() => {
3095
3268
  let active = true;
3096
3269
  setIsLoading(true);
@@ -3099,13 +3272,17 @@ function UsagePage({ context }) {
3099
3272
  attribution,
3100
3273
  from: range.from,
3101
3274
  groupBy,
3275
+ kind: workload,
3102
3276
  interval,
3103
3277
  metric: metricKind,
3104
- model: emptyToUndefined(model),
3278
+ model: showLlmFields ? emptyToUndefined(model) : void 0,
3105
3279
  namespace: context.scope.namespace || void 0,
3280
+ operation: showResourceFields ? emptyToUndefined(operation) : void 0,
3106
3281
  participantId: emptyToUndefined(participantId),
3107
3282
  participantType,
3108
- provider: emptyToUndefined(provider),
3283
+ provider: showLlmFields ? emptyToUndefined(provider) : void 0,
3284
+ resource: showResourceFields ? emptyToUndefined(resource) : void 0,
3285
+ status: status === "all" ? void 0 : status,
3109
3286
  threadId: emptyToUndefined(threadId),
3110
3287
  to: range.to
3111
3288
  }).then((next) => {
@@ -3130,12 +3307,18 @@ function UsagePage({ context }) {
3130
3307
  interval,
3131
3308
  metricKind,
3132
3309
  model,
3310
+ operation,
3133
3311
  participantId,
3134
3312
  participantType,
3135
3313
  provider,
3136
3314
  range.from,
3137
3315
  range.to,
3138
- threadId
3316
+ resource,
3317
+ showLlmFields,
3318
+ showResourceFields,
3319
+ status,
3320
+ threadId,
3321
+ workload
3139
3322
  ]);
3140
3323
  const points = usage?.points ?? [];
3141
3324
  const totals = usage?.totals ?? EMPTY_USAGE_TOTALS;
@@ -3152,9 +3335,10 @@ function UsagePage({ context }) {
3152
3335
  PageHeader,
3153
3336
  {
3154
3337
  title: "Usage",
3155
- description: "Inspect cost, token, and call attribution across namespaces, models, participants, threads, and providers.",
3338
+ description: "Inspect metered LLM, tool, and resource activity by workload, actor, status, cost, duration, tokens, and credits.",
3156
3339
  badges: [
3157
- { label: period },
3340
+ { label: workload },
3341
+ { label: getUsageMetricLabel(metricKind), variant: "secondary" },
3158
3342
  { label: getUsageGroupLabel(groupBy), variant: "secondary" },
3159
3343
  ...isLoading ? [{ label: "Loading" }] : []
3160
3344
  ]
@@ -3182,10 +3366,21 @@ function UsagePage({ context }) {
3182
3366
  /* @__PURE__ */ jsx27(
3183
3367
  UsageSelect,
3184
3368
  {
3185
- label: "Metric",
3369
+ label: "Workload",
3370
+ onValueChange: (value) => setWorkload(value),
3371
+ options: USAGE_WORKLOADS,
3372
+ value: workload,
3373
+ formatOption: getUsageKindLabel
3374
+ }
3375
+ ),
3376
+ /* @__PURE__ */ jsx27(
3377
+ UsageSelect,
3378
+ {
3379
+ label: "Measure",
3186
3380
  onValueChange: (value) => setMetricKind(value),
3187
- options: ["cost", "tokens", "calls"],
3188
- value: metricKind
3381
+ options: metricOptions,
3382
+ value: metricKind,
3383
+ formatOption: getUsageMetricLabel
3189
3384
  }
3190
3385
  ),
3191
3386
  /* @__PURE__ */ jsx27(
@@ -3193,20 +3388,22 @@ function UsagePage({ context }) {
3193
3388
  {
3194
3389
  label: "Group",
3195
3390
  onValueChange: (value) => setGroupBy(value),
3196
- options: ["participant", "thread", "namespace", "provider", "model"],
3197
- value: groupBy
3391
+ options: groupOptions,
3392
+ value: groupBy,
3393
+ formatOption: getUsageGroupLabel
3198
3394
  }
3199
3395
  ),
3200
3396
  /* @__PURE__ */ jsx27(
3201
3397
  UsageSelect,
3202
3398
  {
3203
- label: "Attribution",
3399
+ label: "Actor",
3204
3400
  onValueChange: (value) => setAttribution(value),
3205
3401
  options: ["initiatedBy", "generatedBy"],
3206
- value: attribution
3402
+ value: attribution,
3403
+ formatOption: getUsageAttributionLabel
3207
3404
  }
3208
3405
  ),
3209
- /* @__PURE__ */ jsx27(
3406
+ showDimension && /* @__PURE__ */ jsx27(
3210
3407
  UsageSelect,
3211
3408
  {
3212
3409
  label: "Dimension",
@@ -3241,12 +3438,21 @@ function UsagePage({ context }) {
3241
3438
  /* @__PURE__ */ jsx27(
3242
3439
  UsageSelect,
3243
3440
  {
3244
- label: "Type",
3441
+ label: "Actor type",
3245
3442
  onValueChange: (value) => setParticipantType(value),
3246
3443
  options: ["all", "human", "agent", "job"],
3247
3444
  value: participantType
3248
3445
  }
3249
3446
  ),
3447
+ /* @__PURE__ */ jsx27(
3448
+ UsageSelect,
3449
+ {
3450
+ label: "Status",
3451
+ onValueChange: (value) => setStatus(value),
3452
+ options: STATUS_OPTIONS,
3453
+ value: status
3454
+ }
3455
+ ),
3250
3456
  /* @__PURE__ */ jsx27(
3251
3457
  Input,
3252
3458
  {
@@ -3265,46 +3471,86 @@ function UsagePage({ context }) {
3265
3471
  value: participantId
3266
3472
  }
3267
3473
  ),
3268
- /* @__PURE__ */ jsx27(
3269
- Input,
3270
- {
3271
- className: "h-8 w-[140px]",
3272
- onChange: (event) => setProvider(event.target.value),
3273
- placeholder: "Provider",
3274
- value: provider
3275
- }
3276
- ),
3277
- /* @__PURE__ */ jsx27(
3278
- Input,
3279
- {
3280
- className: "h-8 w-[140px]",
3281
- onChange: (event) => setModel(event.target.value),
3282
- placeholder: "Model",
3283
- value: model
3284
- }
3285
- )
3474
+ showResourceFields && /* @__PURE__ */ jsxs19(Fragment2, { children: [
3475
+ /* @__PURE__ */ jsx27(
3476
+ Input,
3477
+ {
3478
+ className: "h-8 w-[170px]",
3479
+ onChange: (event) => setResource(event.target.value),
3480
+ placeholder: "Resource / tool",
3481
+ value: resource
3482
+ }
3483
+ ),
3484
+ /* @__PURE__ */ jsx27(
3485
+ Input,
3486
+ {
3487
+ className: "h-8 w-[150px]",
3488
+ onChange: (event) => setOperation(event.target.value),
3489
+ placeholder: "Operation",
3490
+ value: operation
3491
+ }
3492
+ )
3493
+ ] }),
3494
+ showLlmFields && /* @__PURE__ */ jsxs19(Fragment2, { children: [
3495
+ /* @__PURE__ */ jsx27(
3496
+ Input,
3497
+ {
3498
+ className: "h-8 w-[140px]",
3499
+ onChange: (event) => setProvider(event.target.value),
3500
+ placeholder: "Provider",
3501
+ value: provider
3502
+ }
3503
+ ),
3504
+ /* @__PURE__ */ jsx27(
3505
+ Input,
3506
+ {
3507
+ className: "h-8 w-[140px]",
3508
+ onChange: (event) => setModel(event.target.value),
3509
+ placeholder: "Model",
3510
+ value: model
3511
+ }
3512
+ )
3513
+ ] })
3286
3514
  ] }),
3287
3515
  /* @__PURE__ */ jsx27(
3288
3516
  MetricStrip,
3289
3517
  {
3290
3518
  items: [
3291
3519
  {
3292
- detail: `${totals.totalCalls} calls`,
3520
+ detail: `${formatMetricValue(totals.unpricedCalls, "unpriced")} unpriced`,
3293
3521
  icon: DollarSign,
3294
- label: "Cost",
3522
+ label: "Spend",
3295
3523
  value: formatMetricValue(totals.totalCostUsd, "cost")
3296
3524
  },
3297
3525
  {
3298
- detail: `${formatMetricValue(totals.inputTokens, "tokens")} input`,
3526
+ detail: `${formatMetricValue(totals.failedCalls, "failures")} failed`,
3299
3527
  icon: Activity3,
3528
+ label: "Calls",
3529
+ value: formatMetricValue(totals.totalCalls, "calls")
3530
+ },
3531
+ {
3532
+ detail: "Tool and resource runtime",
3533
+ icon: Clock,
3534
+ label: "Duration",
3535
+ value: formatMetricValue(totals.totalDurationMs, "duration")
3536
+ },
3537
+ {
3538
+ detail: `${formatMetricValue(totals.inputTokens, "tokens")} input`,
3539
+ icon: Sparkles2,
3300
3540
  label: "Tokens",
3301
3541
  value: formatMetricValue(totals.totalTokens, "tokens")
3302
3542
  },
3303
3543
  {
3304
- detail: `${formatMetricValue(totals.reasoningTokens, "tokens")} reasoning`,
3305
- icon: Sparkles2,
3306
- label: "Calls",
3307
- value: formatMetricValue(totals.totalCalls, "calls")
3544
+ detail: "Custom metering",
3545
+ icon: Coins,
3546
+ label: "Credits",
3547
+ value: formatMetricValue(totals.totalCredits, "credits")
3548
+ },
3549
+ {
3550
+ detail: "Failed or aborted work",
3551
+ icon: AlertTriangle2,
3552
+ label: "Failures",
3553
+ value: formatMetricValue(totals.failedCalls, "failures")
3308
3554
  }
3309
3555
  ]
3310
3556
  }
@@ -3313,7 +3559,7 @@ function UsagePage({ context }) {
3313
3559
  EmptyState,
3314
3560
  {
3315
3561
  title: "No usage",
3316
- description: "Usage records will appear here when provider-native usage is captured."
3562
+ description: "Metered LLM, tool, and resource records will appear here after work is captured in the usage ledger."
3317
3563
  }
3318
3564
  ) : /* @__PURE__ */ jsxs19(Fragment2, { children: [
3319
3565
  /* @__PURE__ */ jsx27(Card, { className: "h-[320px] py-3", children: /* @__PURE__ */ jsx27(CardContent, { className: "h-full px-3", children: /* @__PURE__ */ jsx27(UsageChart, { chartState, metricKind }) }) }),
@@ -3392,6 +3638,7 @@ function UsageRowsTable({
3392
3638
  totals
3393
3639
  }) {
3394
3640
  const totalValue = getUsageTotalValue(totals, metricKind, dimension);
3641
+ const valueHeader = (metricKind === "tokens" || metricKind === "cost") && dimension !== "total" ? `${getUsageDimensionLabel(dimension)} ${getUsageMetricLabel(metricKind)}` : getUsageMetricLabel(metricKind);
3395
3642
  return /* @__PURE__ */ jsx27(
3396
3643
  ResourceTable,
3397
3644
  {
@@ -3409,7 +3656,7 @@ function UsageRowsTable({
3409
3656
  {
3410
3657
  align: "right",
3411
3658
  id: "value",
3412
- header: `${getUsageDimensionLabel(dimension)} ${metricKind}`,
3659
+ header: valueHeader,
3413
3660
  render: (row) => formatMetricValue(row.value, metricKind)
3414
3661
  },
3415
3662
  {
@@ -3426,20 +3673,100 @@ function UsageRowsTable({
3426
3673
  },
3427
3674
  {
3428
3675
  align: "right",
3429
- id: "input",
3430
- header: "Input",
3431
- render: (row) => formatMetricValue(getUsageTotalValue(row, metricKind, "input"), metricKind)
3676
+ id: "spend",
3677
+ header: "Spend",
3678
+ render: (row) => formatMetricValue(row.totalCostUsd, "cost")
3432
3679
  },
3433
3680
  {
3434
3681
  align: "right",
3435
- id: "output",
3436
- header: "Output",
3437
- render: (row) => formatMetricValue(getUsageTotalValue(row, metricKind, "output"), metricKind)
3682
+ id: "duration",
3683
+ header: "Duration",
3684
+ render: (row) => formatMetricValue(row.totalDurationMs, "duration")
3685
+ },
3686
+ {
3687
+ align: "right",
3688
+ id: "tokens",
3689
+ header: "Tokens",
3690
+ render: (row) => formatMetricValue(row.totalTokens, "tokens")
3691
+ },
3692
+ {
3693
+ align: "right",
3694
+ id: "failures",
3695
+ header: "Failures",
3696
+ render: (row) => formatMetricValue(row.failedCalls, "failures")
3697
+ },
3698
+ {
3699
+ align: "right",
3700
+ id: "unpriced",
3701
+ header: "Unpriced",
3702
+ render: (row) => formatMetricValue(row.unpricedCalls, "unpriced")
3438
3703
  }
3439
3704
  ]
3440
3705
  }
3441
3706
  );
3442
3707
  }
3708
+ function getUsageMetricOptions(workload) {
3709
+ if (workload === "llm") {
3710
+ return ["cost", "tokens", "calls", "failures", "unpriced"];
3711
+ }
3712
+ if (workload === "tool") {
3713
+ return ["calls", "duration", "cost", "credits", "failures", "unpriced"];
3714
+ }
3715
+ return [
3716
+ "calls",
3717
+ "cost",
3718
+ "duration",
3719
+ "tokens",
3720
+ "credits",
3721
+ "failures",
3722
+ "unpriced"
3723
+ ];
3724
+ }
3725
+ function getUsageGroupOptions(workload) {
3726
+ if (workload === "llm") {
3727
+ return ["participant", "model", "provider", "thread", "namespace", "status"];
3728
+ }
3729
+ if (workload === "tool") {
3730
+ return [
3731
+ "resource",
3732
+ "operation",
3733
+ "status",
3734
+ "participant",
3735
+ "thread",
3736
+ "namespace"
3737
+ ];
3738
+ }
3739
+ return [
3740
+ "kind",
3741
+ "resource",
3742
+ "operation",
3743
+ "status",
3744
+ "participant",
3745
+ "thread",
3746
+ "namespace"
3747
+ ];
3748
+ }
3749
+ function getUsageKindLabel(kind) {
3750
+ switch (kind) {
3751
+ case "all":
3752
+ return "All";
3753
+ case "llm":
3754
+ return "LLM";
3755
+ case "tool":
3756
+ return "Tools";
3757
+ case "asset":
3758
+ return "Assets";
3759
+ case "rag":
3760
+ return "RAG";
3761
+ case "embedding":
3762
+ return "Embeddings";
3763
+ default:
3764
+ return kind;
3765
+ }
3766
+ }
3767
+ function getUsageAttributionLabel(attribution) {
3768
+ return attribution === "initiatedBy" ? "Initiator" : "Performer";
3769
+ }
3443
3770
  function emptyToUndefined(value) {
3444
3771
  const trimmed = value.trim();
3445
3772
  return trimmed.length > 0 ? trimmed : void 0;
@@ -3927,6 +4254,7 @@ export {
3927
4254
  formatUsageBucket,
3928
4255
  getUsageDimensionLabel,
3929
4256
  getUsageGroupLabel,
4257
+ getUsageMetricLabel,
3930
4258
  getUsageRange,
3931
4259
  getUsageTotalValue,
3932
4260
  mergeAdminConfig,